highcharts.src.js 254 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671
  1. // ==ClosureCompiler==
  2. // @compilation_level SIMPLE_OPTIMIZATIONS
  3. /**
  4. * @license Highcharts JS v2.1.4 (2011-03-02)
  5. *
  6. * (c) 2009-2010 Torstein Hønsi
  7. *
  8. * License: www.highcharts.com/license
  9. */
  10. // JSLint options:
  11. /*jslint forin: true */
  12. /*global document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $ */
  13. (function() {
  14. // encapsulated variables
  15. var doc = document,
  16. win = window,
  17. math = Math,
  18. mathRound = math.round,
  19. mathFloor = math.floor,
  20. mathCeil = math.ceil,
  21. mathMax = math.max,
  22. mathMin = math.min,
  23. mathAbs = math.abs,
  24. mathCos = math.cos,
  25. mathSin = math.sin,
  26. mathPI = math.PI,
  27. deg2rad = mathPI * 2 / 360,
  28. // some variables
  29. userAgent = navigator.userAgent,
  30. isIE = /msie/i.test(userAgent) && !win.opera,
  31. docMode8 = doc.documentMode == 8,
  32. isWebKit = /AppleWebKit/.test(userAgent),
  33. isFirefox = /Firefox/.test(userAgent),
  34. //hasSVG = win.SVGAngle || doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"),
  35. hasSVG = !!doc.createElementNS && !!doc.createElementNS("http://www.w3.org/2000/svg", "svg").createSVGRect,
  36. SVG_NS = 'http://www.w3.org/2000/svg',
  37. hasTouch = 'ontouchstart' in doc.documentElement,
  38. colorCounter,
  39. symbolCounter,
  40. symbolSizes = {},
  41. idCounter = 0,
  42. timeFactor = 1, // 1 = JavaScript time, 1000 = Unix time
  43. garbageBin,
  44. defaultOptions,
  45. dateFormat, // function
  46. globalAnimation,
  47. pathAnim,
  48. // some constants for frequently used strings
  49. UNDEFINED,
  50. DIV = 'div',
  51. ABSOLUTE = 'absolute',
  52. RELATIVE = 'relative',
  53. HIDDEN = 'hidden',
  54. PREFIX = 'highcharts-',
  55. VISIBLE = 'visible',
  56. PX = 'px',
  57. NONE = 'none',
  58. M = 'M',
  59. L = 'L',
  60. /*
  61. * Empirical lowest possible opacities for TRACKER_FILL
  62. * IE6: 0.002
  63. * IE7: 0.002
  64. * IE8: 0.002
  65. * IE9: 0.00000000001 (unlimited)
  66. * FF: 0.00000000001 (unlimited)
  67. * Chrome: 0.000001
  68. * Safari: 0.000001
  69. * Opera: 0.00000000001 (unlimited)
  70. */
  71. TRACKER_FILL = 'rgba(192,192,192,'+ (hasSVG ? 0.000001 : 0.002) +')', // invisible but clickable
  72. NORMAL_STATE = '',
  73. HOVER_STATE = 'hover',
  74. SELECT_STATE = 'select',
  75. // time methods, changed based on whether or not UTC is used
  76. makeTime,
  77. getMinutes,
  78. getHours,
  79. getDay,
  80. getDate,
  81. getMonth,
  82. getFullYear,
  83. setMinutes,
  84. setHours,
  85. setDate,
  86. setMonth,
  87. setFullYear,
  88. // check for a custom HighchartsAdapter defined prior to this file
  89. globalAdapter = win.HighchartsAdapter,
  90. adapter = globalAdapter || {},
  91. // Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object
  92. // and all the utility functions will be null. In that case they are populated by the
  93. // default adapters below.
  94. each = adapter.each,
  95. grep = adapter.grep,
  96. map = adapter.map,
  97. merge = adapter.merge,
  98. hyphenate = adapter.hyphenate,
  99. addEvent = adapter.addEvent,
  100. removeEvent = adapter.removeEvent,
  101. fireEvent = adapter.fireEvent,
  102. animate = adapter.animate,
  103. stop = adapter.stop,
  104. // lookup over the types and the associated classes
  105. seriesTypes = {},
  106. hoverChart;
  107. /**
  108. * Extend an object with the members of another
  109. * @param {Object} a The object to be extended
  110. * @param {Object} b The object to add to the first one
  111. */
  112. function extend(a, b) {
  113. if (!a) {
  114. a = {};
  115. }
  116. for (var n in b) {
  117. a[n] = b[n];
  118. }
  119. return a;
  120. }
  121. /**
  122. * Shortcut for parseInt
  123. * @param {Object} s
  124. */
  125. function pInt(s, mag) {
  126. return parseInt(s, mag || 10);
  127. }
  128. /**
  129. * Check for string
  130. * @param {Object} s
  131. */
  132. function isString(s) {
  133. return typeof s == 'string';
  134. }
  135. /**
  136. * Check for object
  137. * @param {Object} obj
  138. */
  139. function isObject(obj) {
  140. return typeof obj == 'object';
  141. }
  142. /**
  143. * Check for number
  144. * @param {Object} n
  145. */
  146. function isNumber(n) {
  147. return typeof n == 'number';
  148. }
  149. /**
  150. * Remove last occurence of an item from an array
  151. * @param {Array} arr
  152. * @param {Mixed} item
  153. */
  154. function erase(arr, item) {
  155. var i = arr.length;
  156. while (i--) {
  157. if (arr[i] == item) {
  158. arr.splice(i, 1);
  159. break;
  160. }
  161. }
  162. //return arr;
  163. }
  164. /**
  165. * Returns true if the object is not null or undefined. Like MooTools' $.defined.
  166. * @param {Object} obj
  167. */
  168. function defined (obj) {
  169. return obj !== UNDEFINED && obj !== null;
  170. }
  171. /**
  172. * Set or get an attribute or an object of attributes. Can't use jQuery attr because
  173. * it attempts to set expando properties on the SVG element, which is not allowed.
  174. *
  175. * @param {Object} elem The DOM element to receive the attribute(s)
  176. * @param {String|Object} prop The property or an abject of key-value pairs
  177. * @param {String} value The value if a single property is set
  178. */
  179. function attr(elem, prop, value) {
  180. var key,
  181. setAttribute = 'setAttribute',
  182. ret;
  183. // if the prop is a string
  184. if (isString(prop)) {
  185. // set the value
  186. if (defined(value)) {
  187. elem[setAttribute](prop, value);
  188. // get the value
  189. } else if (elem && elem.getAttribute) { // elem not defined when printing pie demo...
  190. ret = elem.getAttribute(prop);
  191. }
  192. // else if prop is defined, it is a hash of key/value pairs
  193. } else if (defined(prop) && isObject(prop)) {
  194. for (key in prop) {
  195. elem[setAttribute](key, prop[key]);
  196. }
  197. }
  198. return ret;
  199. }
  200. /**
  201. * Check if an element is an array, and if not, make it into an array. Like
  202. * MooTools' $.splat.
  203. */
  204. function splat(obj) {
  205. if (!obj || obj.constructor != Array) {
  206. obj = [obj];
  207. }
  208. return obj;
  209. }
  210. /**
  211. * Return the first value that is defined. Like MooTools' $.pick.
  212. */
  213. function pick() {
  214. var args = arguments,
  215. i,
  216. arg,
  217. length = args.length;
  218. for (i = 0; i < length; i++) {
  219. arg = args[i];
  220. if (typeof arg !== 'undefined' && arg !== null) {
  221. return arg;
  222. }
  223. }
  224. }
  225. /**
  226. * Make a style string from a JS object
  227. * @param {Object} style
  228. */
  229. function serializeCSS(style) {
  230. var s = '',
  231. key;
  232. // serialize the declaration
  233. for (key in style) {
  234. s += hyphenate(key) +':'+ style[key] + ';';
  235. }
  236. return s;
  237. }
  238. /**
  239. * Set CSS on a give element
  240. * @param {Object} el
  241. * @param {Object} styles
  242. */
  243. function css (el, styles) {
  244. if (isIE) {
  245. if (styles && styles.opacity !== UNDEFINED) {
  246. styles.filter = 'alpha(opacity='+ (styles.opacity * 100) +')';
  247. }
  248. }
  249. extend(el.style, styles);
  250. }
  251. /**
  252. * Utility function to create element with attributes and styles
  253. * @param {Object} tag
  254. * @param {Object} attribs
  255. * @param {Object} styles
  256. * @param {Object} parent
  257. * @param {Object} nopad
  258. */
  259. function createElement (tag, attribs, styles, parent, nopad) {
  260. var el = doc.createElement(tag);
  261. if (attribs) {
  262. extend(el, attribs);
  263. }
  264. if (nopad) {
  265. css(el, {padding: 0, border: NONE, margin: 0});
  266. }
  267. if (styles) {
  268. css(el, styles);
  269. }
  270. if (parent) {
  271. parent.appendChild(el);
  272. }
  273. return el;
  274. }
  275. /**
  276. * Set the global animation to either a given value, or fall back to the
  277. * given chart's animation option
  278. * @param {Object} animation
  279. * @param {Object} chart
  280. */
  281. function setAnimation(animation, chart) {
  282. globalAnimation = pick(animation, chart.animation);
  283. }
  284. /*
  285. * Define the adapter for frameworks. If an external adapter is not defined,
  286. * Highcharts reverts to the built-in jQuery adapter.
  287. */
  288. if (globalAdapter && globalAdapter.init) {
  289. globalAdapter.init();
  290. }
  291. if (!globalAdapter && win.jQuery) {
  292. var jQ = jQuery;
  293. /**
  294. * Utility for iterating over an array. Parameters are reversed compared to jQuery.
  295. * @param {Array} arr
  296. * @param {Function} fn
  297. */
  298. each = function(arr, fn) {
  299. for (var i = 0, len = arr.length; i < len; i++) {
  300. if (fn.call(arr[i], arr[i], i, arr) === false) {
  301. return i;
  302. }
  303. }
  304. };
  305. /**
  306. * Filter an array
  307. */
  308. grep = jQ.grep;
  309. /**
  310. * Map an array
  311. * @param {Array} arr
  312. * @param {Function} fn
  313. */
  314. map = function(arr, fn){
  315. //return jQuery.map(arr, fn);
  316. var results = [];
  317. for (var i = 0, len = arr.length; i < len; i++) {
  318. results[i] = fn.call(arr[i], arr[i], i, arr);
  319. }
  320. return results;
  321. };
  322. /**
  323. * Deep merge two objects and return a third object
  324. */
  325. merge = function(){
  326. var args = arguments;
  327. return jQ.extend(true, null, args[0], args[1], args[2], args[3]);
  328. };
  329. /**
  330. * Convert a camelCase string to a hyphenated string
  331. * @param {String} str
  332. */
  333. hyphenate = function (str) {
  334. return str.replace(/([A-Z])/g, function(a, b){ return '-'+ b.toLowerCase(); });
  335. };
  336. /**
  337. * Add an event listener
  338. * @param {Object} el A HTML element or custom object
  339. * @param {String} event The event type
  340. * @param {Function} fn The event handler
  341. */
  342. addEvent = function (el, event, fn){
  343. jQ(el).bind(event, fn);
  344. };
  345. /**
  346. * Remove event added with addEvent
  347. * @param {Object} el The object
  348. * @param {String} eventType The event type. Leave blank to remove all events.
  349. * @param {Function} handler The function to remove
  350. */
  351. removeEvent = function(el, eventType, handler) {
  352. // workaround for jQuery issue with unbinding custom events:
  353. // http://forum.jquery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jquery-1-4-2
  354. var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent';
  355. if (doc[func] && !el[func]) {
  356. el[func] = function() {};
  357. }
  358. jQ(el).unbind(eventType, handler);
  359. };
  360. /**
  361. * Fire an event on a custom object
  362. * @param {Object} el
  363. * @param {String} type
  364. * @param {Object} eventArguments
  365. * @param {Function} defaultFunction
  366. */
  367. fireEvent = function(el, type, eventArguments, defaultFunction) {
  368. var event = jQ.Event(type),
  369. detachedType = 'detached'+ type;
  370. extend(event, eventArguments);
  371. // Prevent jQuery from triggering the object method that is named the
  372. // same as the event. For example, if the event is 'select', jQuery
  373. // attempts calling el.select and it goes into a loop.
  374. if (el[type]) {
  375. el[detachedType] = el[type];
  376. el[type] = null;
  377. }
  378. // trigger it
  379. jQ(el).trigger(event);
  380. // attach the method
  381. if (el[detachedType]) {
  382. el[type] = el[detachedType];
  383. el[detachedType] = null;
  384. }
  385. if (defaultFunction && !event.isDefaultPrevented()) {
  386. defaultFunction(event);
  387. }
  388. };
  389. /**
  390. * Animate a HTML element or SVG element wrapper
  391. * @param {Object} el
  392. * @param {Object} params
  393. * @param {Object} options jQuery-like animation options: duration, easing, callback
  394. */
  395. animate = function (el, params, options) {
  396. var $el = jQ(el);
  397. if (params.d) {
  398. el.toD = params.d; // keep the array form for paths, used in jQ.fx.step.d
  399. params.d = 1; // because in jQuery, animating to an array has a different meaning
  400. }
  401. $el.stop();
  402. $el.animate(params, options);
  403. };
  404. /**
  405. * Stop running animation
  406. */
  407. stop = function (el) {
  408. jQ(el).stop();
  409. };
  410. // extend jQuery
  411. jQ.extend( jQ.easing, {
  412. easeOutQuad: function (x, t, b, c, d) {
  413. return -c *(t/=d)*(t-2) + b;
  414. }
  415. });
  416. // extend the animate function to allow SVG animations
  417. var oldStepDefault = jQuery.fx.step._default,
  418. oldCur = jQuery.fx.prototype.cur;
  419. // do the step
  420. jQ.fx.step._default = function(fx){
  421. var elem = fx.elem;
  422. if (elem.attr) { // is SVG element wrapper
  423. elem.attr(fx.prop, fx.now);
  424. } else {
  425. oldStepDefault.apply(this, arguments);
  426. }
  427. };
  428. // animate paths
  429. jQ.fx.step.d = function(fx) {
  430. var elem = fx.elem;
  431. // Normally start and end should be set in state == 0, but sometimes,
  432. // for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped
  433. // in these cases
  434. if (!fx.started) {
  435. var ends = pathAnim.init(elem, elem.d, elem.toD);
  436. fx.start = ends[0];
  437. fx.end = ends[1];
  438. fx.started = true;
  439. }
  440. // interpolate each value of the path
  441. elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD));
  442. };
  443. // get the current value
  444. jQ.fx.prototype.cur = function() {
  445. var elem = this.elem,
  446. r;
  447. if (elem.attr) { // is SVG element wrapper
  448. r = elem.attr(this.prop);
  449. } else {
  450. r = oldCur.apply(this, arguments);
  451. }
  452. return r;
  453. };
  454. }
  455. /**
  456. * Add a global listener for mousemove events
  457. */
  458. /*addEvent(doc, 'mousemove', function(e) {
  459. if (globalMouseMove) {
  460. globalMouseMove(e);
  461. }
  462. });*/
  463. /**
  464. * Path interpolation algorithm used across adapters
  465. */
  466. pathAnim = {
  467. /**
  468. * Prepare start and end values so that the path can be animated one to one
  469. */
  470. init: function(elem, fromD, toD) {
  471. fromD = fromD || '';
  472. var shift = elem.shift,
  473. bezier = fromD.indexOf('C') > -1,
  474. numParams = bezier ? 7 : 3,
  475. endLength,
  476. slice,
  477. i,
  478. start = fromD.split(' '),
  479. end = [].concat(toD), // copy
  480. startBaseLine,
  481. endBaseLine,
  482. sixify = function(arr) { // in splines make move points have six parameters like bezier curves
  483. i = arr.length;
  484. while (i--) {
  485. if (arr[i] == M) {
  486. arr.splice(i + 1, 0, arr[i+1], arr[i+2], arr[i+1], arr[i+2]);
  487. }
  488. }
  489. };
  490. if (bezier) {
  491. sixify(start);
  492. sixify(end);
  493. }
  494. // pull out the base lines before padding
  495. if (elem.isArea) {
  496. startBaseLine = start.splice(start.length - 6, 6);
  497. endBaseLine = end.splice(end.length - 6, 6);
  498. }
  499. // if shifting points, prepend a dummy point to the end path
  500. if (shift) {
  501. end = [].concat(end).splice(0, numParams).concat(end);
  502. elem.shift = false; // reset for following animations
  503. }
  504. // copy and append last point until the length matches the end length
  505. if (start.length) {
  506. endLength = end.length;
  507. while (start.length < endLength) {
  508. //bezier && sixify(start);
  509. slice = [].concat(start).splice(start.length - numParams, numParams);
  510. if (bezier) { // disable first control point
  511. slice[numParams - 6] = slice[numParams - 2];
  512. slice[numParams - 5] = slice[numParams - 1];
  513. }
  514. start = start.concat(slice);
  515. }
  516. }
  517. if (startBaseLine) { // append the base lines for areas
  518. start = start.concat(startBaseLine);
  519. end = end.concat(endBaseLine);
  520. }
  521. return [start, end];
  522. },
  523. /**
  524. * Interpolate each value of the path and return the array
  525. */
  526. step: function(start, end, pos, complete) {
  527. var ret = [],
  528. i = start.length,
  529. startVal;
  530. if (pos == 1) { // land on the final path without adjustment points appended in the ends
  531. ret = complete;
  532. } else if (i == end.length && pos < 1) {
  533. while (i--) {
  534. startVal = parseFloat(start[i]);
  535. ret[i] =
  536. isNaN(startVal) ? // a letter instruction like M or L
  537. start[i] :
  538. pos * (parseFloat(end[i] - startVal)) + startVal;
  539. }
  540. } else { // if animation is finished or length not matching, land on right value
  541. ret = end;
  542. }
  543. return ret;
  544. }
  545. };
  546. /**
  547. * Set the time methods globally based on the useUTC option. Time method can be either
  548. * local time or UTC (default).
  549. */
  550. function setTimeMethods() {
  551. var useUTC = defaultOptions.global.useUTC;
  552. makeTime = useUTC ? Date.UTC : function(year, month, date, hours, minutes, seconds) {
  553. return new Date(
  554. year,
  555. month,
  556. pick(date, 1),
  557. pick(hours, 0),
  558. pick(minutes, 0),
  559. pick(seconds, 0)
  560. ).getTime();
  561. };
  562. getMinutes = useUTC ? 'getUTCMinutes' : 'getMinutes';
  563. getHours = useUTC ? 'getUTCHours' : 'getHours';
  564. getDay = useUTC ? 'getUTCDay' : 'getDay';
  565. getDate = useUTC ? 'getUTCDate' : 'getDate';
  566. getMonth = useUTC ? 'getUTCMonth' : 'getMonth';
  567. getFullYear = useUTC ? 'getUTCFullYear' : 'getFullYear';
  568. setMinutes = useUTC ? 'setUTCMinutes' : 'setMinutes';
  569. setHours = useUTC ? 'setUTCHours' : 'setHours';
  570. setDate = useUTC ? 'setUTCDate' : 'setDate';
  571. setMonth = useUTC ? 'setUTCMonth' : 'setMonth';
  572. setFullYear = useUTC ? 'setUTCFullYear' : 'setFullYear';
  573. }
  574. /**
  575. * Merge the default options with custom options and return the new options structure
  576. * @param {Object} options The new custom options
  577. */
  578. function setOptions(options) {
  579. defaultOptions = merge(defaultOptions, options);
  580. // apply UTC
  581. setTimeMethods();
  582. return defaultOptions;
  583. }
  584. /**
  585. * Get the updated default options. Merely exposing defaultOptions for outside modules
  586. * isn't enough because the setOptions method creates a new object.
  587. */
  588. function getOptions() {
  589. return defaultOptions;
  590. }
  591. /**
  592. * Discard an element by moving it to the bin and delete
  593. * @param {Object} The HTML node to discard
  594. */
  595. function discardElement(element) {
  596. // create a garbage bin element, not part of the DOM
  597. if (!garbageBin) {
  598. garbageBin = createElement(DIV);
  599. }
  600. // move the node and empty bin
  601. if (element) {
  602. garbageBin.appendChild(element);
  603. }
  604. garbageBin.innerHTML = '';
  605. }
  606. /* ****************************************************************************
  607. * Handle the options *
  608. *****************************************************************************/
  609. var
  610. defaultLabelOptions = {
  611. enabled: true,
  612. // rotation: 0,
  613. align: 'center',
  614. x: 0,
  615. y: 15,
  616. /*formatter: function() {
  617. return this.value;
  618. },*/
  619. style: {
  620. color: '#666',
  621. fontSize: '11px',
  622. lineHeight: '14px'
  623. }
  624. };
  625. defaultOptions = {
  626. colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE',
  627. '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'],
  628. symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
  629. lang: {
  630. loading: 'Loading...',
  631. months: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
  632. 'August', 'September', 'October', 'November', 'December'],
  633. weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
  634. decimalPoint: '.',
  635. resetZoom: 'Reset zoom',
  636. resetZoomTitle: 'Reset zoom level 1:1',
  637. thousandsSep: ','
  638. },
  639. global: {
  640. useUTC: true
  641. },
  642. chart: {
  643. //animation: true,
  644. //alignTicks: false,
  645. //reflow: true,
  646. //className: null,
  647. //events: { load, selection },
  648. //margin: [null],
  649. //marginTop: null,
  650. //marginRight: null,
  651. //marginBottom: null,
  652. //marginLeft: null,
  653. borderColor: '#4572A7',
  654. //borderWidth: 0,
  655. borderRadius: 5,
  656. defaultSeriesType: 'line',
  657. ignoreHiddenSeries: true,
  658. //inverted: false,
  659. //shadow: false,
  660. spacingTop: 10,
  661. spacingRight: 10,
  662. spacingBottom: 15,
  663. spacingLeft: 10,
  664. style: {
  665. fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font
  666. fontSize: '12px'
  667. },
  668. backgroundColor: '#FFFFFF',
  669. //plotBackgroundColor: null,
  670. plotBorderColor: '#C0C0C0'
  671. //plotBorderWidth: 0,
  672. //plotShadow: false,
  673. //zoomType: ''
  674. },
  675. title: {
  676. text: 'Chart title',
  677. align: 'center',
  678. // floating: false,
  679. // margin: 15,
  680. // x: 0,
  681. // verticalAlign: 'top',
  682. y: 15, // docs
  683. style: {
  684. color: '#3E576F',
  685. fontSize: '16px'
  686. }
  687. },
  688. subtitle: {
  689. text: '',
  690. align: 'center',
  691. // floating: false
  692. // x: 0,
  693. // verticalAlign: 'top',
  694. y: 30, // docs
  695. style: {
  696. color: '#6D869F'
  697. }
  698. },
  699. plotOptions: {
  700. line: { // base series options
  701. allowPointSelect: false,
  702. showCheckbox: false,
  703. animation: {
  704. duration: 1000
  705. },
  706. //cursor: 'default',
  707. //dashStyle: null,
  708. //enableMouseTracking: true,
  709. events: {},
  710. lineWidth: 2,
  711. shadow: true,
  712. // stacking: null,
  713. marker: {
  714. enabled: true,
  715. //symbol: null,
  716. lineWidth: 0,
  717. radius: 4,
  718. lineColor: '#FFFFFF',
  719. //fillColor: null,
  720. states: { // states for a single point
  721. hover: {
  722. //radius: base + 2
  723. },
  724. select: {
  725. fillColor: '#FFFFFF',
  726. lineColor: '#000000',
  727. lineWidth: 2
  728. }
  729. }
  730. },
  731. point: {
  732. events: {}
  733. },
  734. dataLabels: merge(defaultLabelOptions, {
  735. enabled: false,
  736. y: -6,
  737. formatter: function() {
  738. return this.y;
  739. }
  740. }),
  741. //pointStart: 0,
  742. //pointInterval: 1,
  743. showInLegend: true,
  744. states: { // states for the entire series
  745. hover: {
  746. //enabled: false,
  747. //lineWidth: base + 1,
  748. marker: {
  749. // lineWidth: base + 1,
  750. // radius: base + 1
  751. }
  752. },
  753. select: {
  754. marker: {}
  755. }
  756. },
  757. stickyTracking: true
  758. //zIndex: null
  759. }
  760. },
  761. labels: {
  762. //items: [],
  763. style: {
  764. //font: defaultFont,
  765. position: ABSOLUTE,
  766. color: '#3E576F'
  767. }
  768. },
  769. legend: {
  770. enabled: true,
  771. align: 'center',
  772. //floating: false,
  773. layout: 'horizontal',
  774. labelFormatter: function() {
  775. return this.name;
  776. },
  777. // lineHeight: 16, // docs: deprecated
  778. borderWidth: 1,
  779. borderColor: '#909090',
  780. borderRadius: 5,
  781. // margin: 10,
  782. // reversed: false,
  783. shadow: false,
  784. // backgroundColor: null,
  785. style: {
  786. padding: '5px'
  787. },
  788. itemStyle: {
  789. cursor: 'pointer',
  790. color: '#3E576F'
  791. },
  792. itemHoverStyle: {
  793. cursor: 'pointer',
  794. color: '#000000'
  795. },
  796. itemHiddenStyle: {
  797. color: '#C0C0C0'
  798. },
  799. itemCheckboxStyle: {
  800. position: ABSOLUTE,
  801. width: '13px', // for IE precision
  802. height: '13px'
  803. },
  804. // itemWidth: undefined,
  805. symbolWidth: 16,
  806. symbolPadding: 5,
  807. verticalAlign: 'bottom',
  808. // width: undefined,
  809. x: 0, // docs
  810. y: 0 // docs
  811. },
  812. loading: {
  813. hideDuration: 100,
  814. labelStyle: {
  815. fontWeight: 'bold',
  816. position: RELATIVE,
  817. top: '1em'
  818. },
  819. showDuration: 100,
  820. style: {
  821. position: ABSOLUTE,
  822. backgroundColor: 'white',
  823. opacity: 0.5,
  824. textAlign: 'center'
  825. }
  826. },
  827. tooltip: {
  828. enabled: true,
  829. //crosshairs: null,
  830. backgroundColor: 'rgba(255, 255, 255, .85)',
  831. borderWidth: 2,
  832. borderRadius: 5,
  833. //formatter: defaultFormatter,
  834. shadow: true,
  835. //shared: false,
  836. snap: hasTouch ? 25 : 10,
  837. style: {
  838. color: '#333333',
  839. fontSize: '12px',
  840. padding: '5px',
  841. whiteSpace: 'nowrap'
  842. }
  843. },
  844. toolbar: {
  845. itemStyle: {
  846. color: '#4572A7',
  847. cursor: 'pointer'
  848. }
  849. },
  850. credits: {
  851. enabled: true,
  852. text: 'Highcharts.com',
  853. href: 'http://www.highcharts.com',
  854. position: {
  855. align: 'right',
  856. x: -10,
  857. verticalAlign: 'bottom',
  858. y: -5
  859. },
  860. style: {
  861. cursor: 'pointer',
  862. color: '#909090',
  863. fontSize: '10px'
  864. }
  865. }
  866. };
  867. // Axis defaults
  868. var defaultXAxisOptions = {
  869. // allowDecimals: null,
  870. // alternateGridColor: null,
  871. // categories: [],
  872. dateTimeLabelFormats: {
  873. second: '%H:%M:%S',
  874. minute: '%H:%M',
  875. hour: '%H:%M',
  876. day: '%e. %b',
  877. week: '%e. %b',
  878. month: '%b \'%y',
  879. year: '%Y'
  880. },
  881. endOnTick: false,
  882. gridLineColor: '#C0C0C0',
  883. // gridLineDashStyle: 'solid', // docs
  884. // gridLineWidth: 0,
  885. // reversed: false,
  886. labels: defaultLabelOptions,
  887. // { step: null },
  888. lineColor: '#C0D0E0',
  889. lineWidth: 1,
  890. //linkedTo: null,
  891. max: null,
  892. min: null,
  893. minPadding: 0.01,
  894. maxPadding: 0.01,
  895. //maxZoom: null,
  896. minorGridLineColor: '#E0E0E0',
  897. // minorGridLineDashStyle: null,
  898. minorGridLineWidth: 1,
  899. minorTickColor: '#A0A0A0',
  900. //minorTickInterval: null,
  901. minorTickLength: 2,
  902. minorTickPosition: 'outside', // inside or outside
  903. //minorTickWidth: 0,
  904. //opposite: false,
  905. //offset: 0,
  906. //plotBands: [{
  907. // events: {},
  908. // zIndex: 1,
  909. // labels: { align, x, verticalAlign, y, style, rotation, textAlign }
  910. //}],
  911. //plotLines: [{
  912. // events: {}
  913. // dashStyle: {}
  914. // zIndex:
  915. // labels: { align, x, verticalAlign, y, style, rotation, textAlign }
  916. //}],
  917. //reversed: false,
  918. // showFirstLabel: true,
  919. // showLastLabel: false,
  920. startOfWeek: 1,
  921. startOnTick: false,
  922. tickColor: '#C0D0E0',
  923. //tickInterval: null,
  924. tickLength: 5,
  925. tickmarkPlacement: 'between', // on or between
  926. tickPixelInterval: 100,
  927. tickPosition: 'outside',
  928. tickWidth: 1,
  929. title: {
  930. //text: null,
  931. align: 'middle', // low, middle or high
  932. //margin: 0 for horizontal, 10 for vertical axes,
  933. //rotation: 0,
  934. //side: 'outside',
  935. style: {
  936. color: '#6D869F',
  937. //font: defaultFont.replace('normal', 'bold')
  938. fontWeight: 'bold'
  939. }
  940. //x: 0,
  941. //y: 0
  942. },
  943. type: 'linear' // linear or datetime
  944. },
  945. defaultYAxisOptions = merge(defaultXAxisOptions, {
  946. endOnTick: true,
  947. gridLineWidth: 1,
  948. tickPixelInterval: 72,
  949. showLastLabel: true,
  950. labels: {
  951. align: 'right',
  952. x: -8,
  953. y: 3
  954. },
  955. lineWidth: 0,
  956. maxPadding: 0.05,
  957. minPadding: 0.05,
  958. startOnTick: true,
  959. tickWidth: 0,
  960. title: {
  961. rotation: 270,
  962. text: 'Y-values'
  963. }
  964. }),
  965. defaultLeftAxisOptions = {
  966. labels: {
  967. align: 'right',
  968. x: -8,
  969. y: null // docs
  970. },
  971. title: {
  972. rotation: 270
  973. }
  974. },
  975. defaultRightAxisOptions = {
  976. labels: {
  977. align: 'left',
  978. x: 8,
  979. y: null // docs
  980. },
  981. title: {
  982. rotation: 90
  983. }
  984. },
  985. defaultBottomAxisOptions = { // horizontal axis
  986. labels: {
  987. align: 'center',
  988. x: 0,
  989. y: 14
  990. // staggerLines: null
  991. },
  992. title: {
  993. rotation: 0
  994. }
  995. },
  996. defaultTopAxisOptions = merge(defaultBottomAxisOptions, {
  997. labels: {
  998. y: -5
  999. // staggerLines: null
  1000. }
  1001. });
  1002. // Series defaults
  1003. var defaultPlotOptions = defaultOptions.plotOptions,
  1004. defaultSeriesOptions = defaultPlotOptions.line;
  1005. //defaultPlotOptions.line = merge(defaultSeriesOptions);
  1006. defaultPlotOptions.spline = merge(defaultSeriesOptions);
  1007. defaultPlotOptions.scatter = merge(defaultSeriesOptions, {
  1008. lineWidth: 0,
  1009. states: {
  1010. hover: {
  1011. lineWidth: 0
  1012. }
  1013. }
  1014. });
  1015. defaultPlotOptions.area = merge(defaultSeriesOptions, {
  1016. // threshold: 0,
  1017. // lineColor: null, // overrides color, but lets fillColor be unaltered
  1018. // fillOpacity: 0.75,
  1019. // fillColor: null
  1020. });
  1021. defaultPlotOptions.areaspline = merge(defaultPlotOptions.area);
  1022. defaultPlotOptions.column = merge(defaultSeriesOptions, {
  1023. borderColor: '#FFFFFF',
  1024. borderWidth: 1,
  1025. borderRadius: 0,
  1026. //colorByPoint: undefined,
  1027. groupPadding: 0.2,
  1028. marker: null, // point options are specified in the base options
  1029. pointPadding: 0.1,
  1030. //pointWidth: null,
  1031. minPointLength: 0,
  1032. states: {
  1033. hover: {
  1034. brightness: 0.1,
  1035. shadow: false
  1036. },
  1037. select: {
  1038. color: '#C0C0C0',
  1039. borderColor: '#000000',
  1040. shadow: false
  1041. }
  1042. }
  1043. });
  1044. defaultPlotOptions.bar = merge(defaultPlotOptions.column, {
  1045. dataLabels: {
  1046. align: 'left',
  1047. x: 5,
  1048. y: 0
  1049. }
  1050. });
  1051. defaultPlotOptions.pie = merge(defaultSeriesOptions, {
  1052. //dragType: '', // n/a
  1053. borderColor: '#FFFFFF',
  1054. borderWidth: 1,
  1055. center: ['50%', '50%'],
  1056. colorByPoint: true, // always true for pies
  1057. dataLabels: {
  1058. // align: null,
  1059. // connectorWidth: 1,
  1060. // connectorColor: '#606060',
  1061. // connectorPadding: 5,
  1062. distance: 30,
  1063. enabled: true,
  1064. formatter: function() {
  1065. return this.point.name;
  1066. },
  1067. y: 5
  1068. },
  1069. //innerSize: 0,
  1070. legendType: 'point',
  1071. marker: null, // point options are specified in the base options
  1072. size: '75%',
  1073. showInLegend: false,
  1074. slicedOffset: 10,
  1075. states: {
  1076. hover: {
  1077. brightness: 0.1,
  1078. shadow: false
  1079. }
  1080. }
  1081. });
  1082. // set the default time methods
  1083. setTimeMethods();
  1084. /**
  1085. * Extend a prototyped class by new members
  1086. * @param {Object} parent
  1087. * @param {Object} members
  1088. */
  1089. function extendClass(parent, members) {
  1090. var object = function(){};
  1091. object.prototype = new parent();
  1092. extend(object.prototype, members);
  1093. return object;
  1094. }
  1095. /**
  1096. * Handle color operations. The object methods are chainable.
  1097. * @param {String} input The input color in either rbga or hex format
  1098. */
  1099. var Color = function(input) {
  1100. // declare variables
  1101. var rgba = [], result;
  1102. /**
  1103. * Parse the input color to rgba array
  1104. * @param {String} input
  1105. */
  1106. function init(input) {
  1107. // rgba
  1108. if((result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/.exec(input))) {
  1109. rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
  1110. }
  1111. // hex
  1112. else if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input))) {
  1113. rgba = [pInt(result[1],16), pInt(result[2],16), pInt(result[3],16), 1];
  1114. }
  1115. }
  1116. /**
  1117. * Return the color a specified format
  1118. * @param {String} format
  1119. */
  1120. function get(format) {
  1121. var ret;
  1122. // it's NaN if gradient colors on a column chart
  1123. if (rgba && !isNaN(rgba[0])) {
  1124. if (format == 'rgb') {
  1125. ret = 'rgb('+ rgba[0] +','+ rgba[1] +','+ rgba[2] +')';
  1126. } else if (format == 'a') {
  1127. ret = rgba[3];
  1128. } else {
  1129. ret = 'rgba('+ rgba.join(',') +')';
  1130. }
  1131. } else {
  1132. ret = input;
  1133. }
  1134. return ret;
  1135. }
  1136. /**
  1137. * Brighten the color
  1138. * @param {Number} alpha
  1139. */
  1140. function brighten(alpha) {
  1141. if (isNumber(alpha) && alpha !== 0) {
  1142. var i;
  1143. for (i = 0; i < 3; i++) {
  1144. rgba[i] += pInt(alpha * 255);
  1145. if (rgba[i] < 0) {
  1146. rgba[i] = 0;
  1147. }
  1148. if (rgba[i] > 255) {
  1149. rgba[i] = 255;
  1150. }
  1151. }
  1152. }
  1153. return this;
  1154. }
  1155. /**
  1156. * Set the color's opacity to a given alpha value
  1157. * @param {Number} alpha
  1158. */
  1159. function setOpacity(alpha) {
  1160. rgba[3] = alpha;
  1161. return this;
  1162. }
  1163. // initialize: parse the input
  1164. init(input);
  1165. // public methods
  1166. return {
  1167. get: get,
  1168. brighten: brighten,
  1169. setOpacity: setOpacity
  1170. };
  1171. };
  1172. /**
  1173. * Format a number and return a string based on input settings
  1174. * @param {Number} number The input number to format
  1175. * @param {Number} decimals The amount of decimals
  1176. * @param {String} decPoint The decimal point, defaults to the one given in the lang options
  1177. * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options
  1178. */
  1179. function numberFormat (number, decimals, decPoint, thousandsSep) {
  1180. var lang = defaultOptions.lang,
  1181. // http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/
  1182. n = number, c = isNaN(decimals = mathAbs(decimals)) ? 2 : decimals,
  1183. d = decPoint === undefined ? lang.decimalPoint : decPoint,
  1184. t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep, s = n < 0 ? "-" : "",
  1185. i = pInt(n = mathAbs(+n || 0).toFixed(c)) + "", j = (j = i.length) > 3 ? j % 3 : 0;
  1186. return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) +
  1187. (c ? d + mathAbs(n - i).toFixed(c).slice(2) : "");
  1188. }
  1189. /**
  1190. * Based on http://www.php.net/manual/en/function.strftime.php
  1191. * @param {String} format
  1192. * @param {Number} timestamp
  1193. * @param {Boolean} capitalize
  1194. */
  1195. dateFormat = function (format, timestamp, capitalize) {
  1196. function pad (number) {
  1197. return number.toString().replace(/^([0-9])$/, '0$1');
  1198. }
  1199. if (!defined(timestamp) || isNaN(timestamp)) {
  1200. return 'Invalid date';
  1201. }
  1202. format = pick(format, '%Y-%m-%d %H:%M:%S');
  1203. var date = new Date(timestamp * timeFactor),
  1204. // get the basic time values
  1205. hours = date[getHours](),
  1206. day = date[getDay](),
  1207. dayOfMonth = date[getDate](),
  1208. month = date[getMonth](),
  1209. fullYear = date[getFullYear](),
  1210. lang = defaultOptions.lang,
  1211. langWeekdays = lang.weekdays,
  1212. langMonths = lang.months,
  1213. // list all format keys
  1214. replacements = {
  1215. // Day
  1216. 'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon'
  1217. 'A': langWeekdays[day], // Long weekday, like 'Monday'
  1218. 'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31
  1219. 'e': dayOfMonth, // Day of the month, 1 through 31
  1220. // Week (none implemented)
  1221. // Month
  1222. 'b': langMonths[month].substr(0, 3), // Short month, like 'Jan'
  1223. 'B': langMonths[month], // Long month, like 'January'
  1224. 'm': pad(month + 1), // Two digit month number, 01 through 12
  1225. // Year
  1226. 'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009
  1227. 'Y': fullYear, // Four digits year, like 2009
  1228. // Time
  1229. 'H': pad(hours), // Two digits hours in 24h format, 00 through 23
  1230. 'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11
  1231. 'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12
  1232. 'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59
  1233. 'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM
  1234. 'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM
  1235. 'S': pad(date.getSeconds()) // Two digits seconds, 00 through 59
  1236. };
  1237. // do the replaces
  1238. for (var key in replacements) {
  1239. format = format.replace('%'+ key, replacements[key]);
  1240. }
  1241. // Optionally capitalize the string and return
  1242. return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;
  1243. };
  1244. /**
  1245. * Loop up the node tree and add offsetWidth and offsetHeight to get the
  1246. * total page offset for a given element. Used by Opera and iOS on hover and
  1247. * all browsers on point click.
  1248. *
  1249. * @param {Object} el
  1250. *
  1251. */
  1252. function getPosition (el) {
  1253. var p = { left: el.offsetLeft, top: el.offsetTop };
  1254. while ((el = el.offsetParent)) {
  1255. p.left += el.offsetLeft;
  1256. p.top += el.offsetTop;
  1257. if (el != doc.body && el != doc.documentElement) {
  1258. p.left -= el.scrollLeft;
  1259. p.top -= el.scrollTop;
  1260. }
  1261. }
  1262. return p;
  1263. }
  1264. /**
  1265. * A wrapper object for SVG elements
  1266. */
  1267. function SVGElement () {}
  1268. SVGElement.prototype = {
  1269. /**
  1270. * Initialize the SVG renderer
  1271. * @param {Object} renderer
  1272. * @param {String} nodeName
  1273. */
  1274. init: function(renderer, nodeName) {
  1275. this.element = doc.createElementNS(SVG_NS, nodeName);
  1276. this.renderer = renderer;
  1277. },
  1278. /**
  1279. * Animate a given attribute
  1280. * @param {Object} params
  1281. * @param {Number} options The same options as in jQuery animation
  1282. * @param {Function} complete Function to perform at the end of animation
  1283. */
  1284. animate: function(params, options, complete) {
  1285. var animOptions = pick(options, globalAnimation, true);
  1286. if (animOptions) {
  1287. animOptions = merge(animOptions);
  1288. if (complete) { // allows using a callback with the global animation without overwriting it
  1289. animOptions.complete = complete;
  1290. }
  1291. animate(this, params, animOptions);
  1292. } else {
  1293. this.attr(params);
  1294. if (complete) {
  1295. complete();
  1296. }
  1297. }
  1298. },
  1299. /**
  1300. * Set or get a given attribute
  1301. * @param {Object|String} hash
  1302. * @param {Mixed|Undefined} val
  1303. */
  1304. attr: function(hash, val) {
  1305. var key,
  1306. value,
  1307. i,
  1308. child,
  1309. element = this.element,
  1310. nodeName = element.nodeName,
  1311. renderer = this.renderer,
  1312. skipAttr,
  1313. shadows = this.shadows,
  1314. hasSetSymbolSize,
  1315. ret = this;
  1316. // single key-value pair
  1317. if (isString(hash) && defined(val)) {
  1318. key = hash;
  1319. hash = {};
  1320. hash[key] = val;
  1321. }
  1322. // used as a getter: first argument is a string, second is undefined
  1323. if (isString(hash)) {
  1324. key = hash;
  1325. if (nodeName == 'circle') {
  1326. key = { x: 'cx', y: 'cy' }[key] || key;
  1327. } else if (key == 'strokeWidth') {
  1328. key = 'stroke-width';
  1329. }
  1330. ret = attr(element, key) || this[key] || 0;
  1331. if (key != 'd' && key != 'visibility') { // 'd' is string in animation step
  1332. ret = parseFloat(ret);
  1333. }
  1334. // setter
  1335. } else {
  1336. for (key in hash) {
  1337. skipAttr = false; // reset
  1338. value = hash[key];
  1339. // paths
  1340. if (key == 'd') {
  1341. if (value && value.join) { // join path
  1342. value = value.join(' ');
  1343. }
  1344. if (/(NaN| {2}|^$)/.test(value)) {
  1345. value = 'M 0 0';
  1346. }
  1347. this.d = value; // shortcut for animations
  1348. // update child tspans x values
  1349. } else if (key == 'x' && nodeName == 'text') {
  1350. for (i = 0; i < element.childNodes.length; i++ ) {
  1351. child = element.childNodes[i];
  1352. // if the x values are equal, the tspan represents a linebreak
  1353. if (attr(child, 'x') == attr(element, 'x')) {
  1354. //child.setAttribute('x', value);
  1355. attr(child, 'x', value);
  1356. }
  1357. }
  1358. if (this.rotation) {
  1359. attr(element, 'transform', 'rotate('+ this.rotation +' '+ value +' '+
  1360. pInt(hash.y || attr(element, 'y')) +')');
  1361. }
  1362. // apply gradients
  1363. } else if (key == 'fill') {
  1364. value = renderer.color(value, element, key);
  1365. // circle x and y
  1366. } else if (nodeName == 'circle' && (key == 'x' || key == 'y')) {
  1367. key = { x: 'cx', y: 'cy' }[key] || key;
  1368. // translation and text rotation
  1369. } else if (key == 'translateX' || key == 'translateY' || key == 'rotation' || key == 'verticalAlign') {
  1370. this[key] = value;
  1371. this.updateTransform();
  1372. skipAttr = true;
  1373. // apply opacity as subnode (required by legacy WebKit and Batik)
  1374. } else if (key == 'stroke') {
  1375. value = renderer.color(value, element, key);
  1376. // emulate VML's dashstyle implementation
  1377. } else if (key == 'dashstyle') {
  1378. key = 'stroke-dasharray';
  1379. if (value) {
  1380. value = value.toLowerCase()
  1381. .replace('shortdashdotdot', '3,1,1,1,1,1,')
  1382. .replace('shortdashdot', '3,1,1,1')
  1383. .replace('shortdot', '1,1,')
  1384. .replace('shortdash', '3,1,')
  1385. .replace('longdash', '8,3,')
  1386. .replace(/dot/g, '1,3,')
  1387. .replace('dash', '4,3,')
  1388. .replace(/,$/, '')
  1389. .split(','); // ending comma
  1390. i = value.length;
  1391. while (i--) {
  1392. value[i] = pInt(value[i]) * hash['stroke-width'];
  1393. }
  1394. value = value.join(',');
  1395. }
  1396. // special
  1397. } else if (key == 'isTracker') {
  1398. this[key] = value;
  1399. // IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2
  1400. // is unable to cast them. Test again with final IE9.
  1401. } else if (key == 'width') {
  1402. value = pInt(value);
  1403. // Text alignment
  1404. } else if (key == 'align') {
  1405. key = 'text-anchor';
  1406. value = { left: 'start', center: 'middle', right: 'end' }[value];
  1407. }
  1408. // jQuery animate changes case
  1409. if (key == 'strokeWidth') {
  1410. key = 'stroke-width';
  1411. }
  1412. // Chrome/Win < 6 bug (http://code.google.com/p/chromium/issues/detail?id=15461)
  1413. if (isWebKit && key == 'stroke-width' && value === 0) {
  1414. value = 0.000001;
  1415. }
  1416. // symbols
  1417. if (this.symbolName && /^(x|y|r|start|end|innerR)/.test(key)) {
  1418. if (!hasSetSymbolSize) {
  1419. this.symbolAttr(hash);
  1420. hasSetSymbolSize = true;
  1421. }
  1422. skipAttr = true;
  1423. }
  1424. // let the shadow follow the main element
  1425. if (shadows && /^(width|height|visibility|x|y|d)$/.test(key)) {
  1426. i = shadows.length;
  1427. while (i--) {
  1428. attr(shadows[i], key, value);
  1429. }
  1430. }
  1431. /* trows errors in Chrome
  1432. if ((key == 'width' || key == 'height') && nodeName == 'rect' && value < 0) {
  1433. console.log(element);
  1434. }
  1435. */
  1436. if (key == 'text') {
  1437. // only one node allowed
  1438. this.textStr = value;
  1439. if (this.added) {
  1440. renderer.buildText(this);
  1441. }
  1442. } else if (!skipAttr) {
  1443. //element.setAttribute(key, value);
  1444. attr(element, key, value);
  1445. }
  1446. }
  1447. }
  1448. return ret;
  1449. },
  1450. /**
  1451. * If one of the symbol size affecting parameters are changed,
  1452. * check all the others only once for each call to an element's
  1453. * .attr() method
  1454. * @param {Object} hash
  1455. */
  1456. symbolAttr: function(hash) {
  1457. var wrapper = this;
  1458. each (['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR'], function(key) {
  1459. wrapper[key] = pick(hash[key], wrapper[key]);
  1460. });
  1461. wrapper.attr({
  1462. d: wrapper.renderer.symbols[wrapper.symbolName](wrapper.x, wrapper.y, wrapper.r, {
  1463. start: wrapper.start,
  1464. end: wrapper.end,
  1465. width: wrapper.width,
  1466. height: wrapper.height,
  1467. innerR: wrapper.innerR
  1468. })
  1469. });
  1470. },
  1471. /**
  1472. * Apply a clipping path to this object
  1473. * @param {String} id
  1474. */
  1475. clip: function(clipRect) {
  1476. return this.attr('clip-path', 'url('+ this.renderer.url +'#'+ clipRect.id +')');
  1477. },
  1478. /**
  1479. * Calculate the coordinates needed for drawing a rectangle crisply and return the
  1480. * calculated attributes
  1481. * @param {Number} strokeWidth
  1482. * @param {Number} x
  1483. * @param {Number} y
  1484. * @param {Number} width
  1485. * @param {Number} height
  1486. */
  1487. crisp: function(strokeWidth, x, y, width, height) {
  1488. var wrapper = this,
  1489. key,
  1490. attr = {},
  1491. values = {},
  1492. normalizer;
  1493. strokeWidth = strokeWidth || wrapper.strokeWidth || 0;
  1494. normalizer = strokeWidth % 2 / 2;
  1495. // normalize for crisp edges
  1496. values.x = mathFloor(x || wrapper.x || 0) + normalizer;
  1497. values.y = mathFloor(y || wrapper.y || 0) + normalizer;
  1498. values.width = mathFloor((width || wrapper.width || 0) - 2 * normalizer);
  1499. values.height = mathFloor((height || wrapper.height || 0) - 2 * normalizer);
  1500. values.strokeWidth = strokeWidth;
  1501. for (key in values) {
  1502. if (wrapper[key] != values[key]) { // only set attribute if changed
  1503. wrapper[key] = attr[key] = values[key];
  1504. }
  1505. }
  1506. return attr;
  1507. },
  1508. /**
  1509. * Set styles for the element
  1510. * @param {Object} styles
  1511. */
  1512. css: function(styles) {
  1513. var elemWrapper = this,
  1514. elem = elemWrapper.element,
  1515. textWidth = styles && styles.width && elem.nodeName == 'text';
  1516. // convert legacy
  1517. if (styles && styles.color) {
  1518. styles.fill = styles.color;
  1519. }
  1520. // save the styles in an object
  1521. styles = extend(
  1522. elemWrapper.styles,
  1523. styles
  1524. );
  1525. // store object
  1526. elemWrapper.styles = styles;
  1527. // serialize and set style attribute
  1528. if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute
  1529. if (textWidth) {
  1530. delete styles.width;
  1531. }
  1532. css(elemWrapper.element, styles);
  1533. } else {
  1534. elemWrapper.attr({
  1535. style: serializeCSS(styles)
  1536. });
  1537. }
  1538. // re-build text
  1539. if (textWidth && elemWrapper.added) {
  1540. elemWrapper.renderer.buildText(elemWrapper);
  1541. }
  1542. return elemWrapper;
  1543. },
  1544. /**
  1545. * Add an event listener
  1546. * @param {String} eventType
  1547. * @param {Function} handler
  1548. */
  1549. on: function(eventType, handler) {
  1550. var fn = handler;
  1551. // touch
  1552. if (hasTouch && eventType == 'click') {
  1553. eventType = 'touchstart';
  1554. fn = function(e) {
  1555. e.preventDefault();
  1556. handler();
  1557. }
  1558. }
  1559. // simplest possible event model for internal use
  1560. this.element['on'+ eventType] = fn;
  1561. return this;
  1562. },
  1563. /**
  1564. * Move an object and its children by x and y values
  1565. * @param {Number} x
  1566. * @param {Number} y
  1567. */
  1568. translate: function(x, y) {
  1569. return this.attr({
  1570. translateX: x,
  1571. translateY: y
  1572. });
  1573. },
  1574. /**
  1575. * Invert a group, rotate and flip
  1576. */
  1577. invert: function() {
  1578. var wrapper = this;
  1579. wrapper.inverted = true;
  1580. wrapper.updateTransform();
  1581. return wrapper;
  1582. },
  1583. /**
  1584. * Private method to update the transform attribute based on internal
  1585. * properties
  1586. */
  1587. updateTransform: function() {
  1588. var wrapper = this,
  1589. translateX = wrapper.translateX || 0,
  1590. translateY = wrapper.translateY || 0,
  1591. inverted = wrapper.inverted,
  1592. rotation = wrapper.rotation,
  1593. transform = [];
  1594. // flipping affects translate as adjustment for flipping around the group's axis
  1595. if (inverted) {
  1596. translateX += wrapper.attr('width');
  1597. translateY += wrapper.attr('height');
  1598. }
  1599. // apply translate
  1600. if (translateX || translateY) {
  1601. transform.push('translate('+ translateX +','+ translateY +')');
  1602. }
  1603. // apply rotation
  1604. if (inverted) {
  1605. transform.push('rotate(90) scale(-1,1)');
  1606. } else if (rotation) { // text rotation
  1607. transform.push('rotate('+ rotation +' '+ wrapper.x +' '+ wrapper.y +')');
  1608. }
  1609. if (transform.length) {
  1610. attr(wrapper.element, 'transform', transform.join(' '));
  1611. }
  1612. },
  1613. /**
  1614. * Bring the element to the front
  1615. */
  1616. toFront: function() {
  1617. var element = this.element;
  1618. element.parentNode.appendChild(element);
  1619. return this;
  1620. },
  1621. /**
  1622. * Break down alignment options like align, verticalAlign, x and y
  1623. * to x and y relative to the chart.
  1624. *
  1625. * @param {Object} alignOptions
  1626. * @param {Boolean} alignByTranslate
  1627. * @param {Object} box The box to align to, needs a width and height
  1628. *
  1629. */
  1630. align: function(alignOptions, alignByTranslate, box) {
  1631. if (!alignOptions) { // called on resize
  1632. alignOptions = this.alignOptions;
  1633. alignByTranslate = this.alignByTranslate;
  1634. } else { // first call on instanciate
  1635. this.alignOptions = alignOptions;
  1636. this.alignByTranslate = alignByTranslate;
  1637. if (!box) { // boxes other than renderer handle this internally
  1638. this.renderer.alignedObjects.push(this);
  1639. }
  1640. }
  1641. box = pick(box, this.renderer);
  1642. var align = alignOptions.align,
  1643. vAlign = alignOptions.verticalAlign,
  1644. x = (box.x || 0) + (alignOptions.x || 0), // default: left align
  1645. y = (box.y || 0) + (alignOptions.y || 0), // default: top align
  1646. attribs = {};
  1647. // align
  1648. if (/^(right|center)$/.test(align)) {
  1649. x += (box.width - (alignOptions.width || 0) ) /
  1650. { right: 1, center: 2 }[align];
  1651. }
  1652. attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x);
  1653. // vertical align
  1654. if (/^(bottom|middle)$/.test(vAlign)) {
  1655. y += (box.height - (alignOptions.height || 0)) /
  1656. ({ bottom: 1, middle: 2 }[vAlign] || 1);
  1657. }
  1658. attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y);
  1659. // animate only if already placed
  1660. this[this.placed ? 'animate' : 'attr'](attribs);
  1661. this.placed = true;
  1662. return this;
  1663. },
  1664. /**
  1665. * Get the bounding box (width, height, x and y) for the element
  1666. */
  1667. getBBox: function() {
  1668. var bBox,
  1669. width,
  1670. height,
  1671. rotation = this.rotation,
  1672. rad = rotation * deg2rad;
  1673. try { // fails in Firefox if the container has display: none
  1674. // use extend because IE9 is not allowed to change width and height in case
  1675. // of rotation (below)
  1676. bBox = extend({}, this.element.getBBox());
  1677. } catch(e) {
  1678. bBox = { width: 0, height: 0 };
  1679. }
  1680. width = bBox.width;
  1681. height = bBox.height;
  1682. // adjust for rotated text
  1683. if (rotation) {
  1684. bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));
  1685. bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));
  1686. }
  1687. return bBox;
  1688. },
  1689. /* *
  1690. * Manually compute width and height of rotated text from non-rotated. Shared by SVG and VML
  1691. * @param {Object} bBox
  1692. * @param {number} rotation
  1693. * /
  1694. rotateBBox: function(bBox, rotation) {
  1695. var rad = rotation * math.PI * 2 / 360, // radians
  1696. width = bBox.width,
  1697. height = bBox.height;
  1698. },*/
  1699. /**
  1700. * Show the element
  1701. */
  1702. show: function() {
  1703. return this.attr({ visibility: VISIBLE });
  1704. },
  1705. /**
  1706. * Hide the element
  1707. */
  1708. hide: function() {
  1709. return this.attr({ visibility: HIDDEN });
  1710. },
  1711. /**
  1712. * Add the element
  1713. * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined
  1714. * to append the element to the renderer.box.
  1715. */
  1716. add: function(parent) {
  1717. var renderer = this.renderer,
  1718. parentWrapper = parent || renderer,
  1719. parentNode = parentWrapper.element || renderer.box,
  1720. childNodes = parentNode.childNodes,
  1721. element = this.element,
  1722. zIndex = attr(element, 'zIndex'),
  1723. otherElement,
  1724. otherZIndex,
  1725. i;
  1726. // mark as inverted
  1727. this.parentInverted = parent && parent.inverted;
  1728. // build formatted text
  1729. if (this.textStr !== undefined) {
  1730. renderer.buildText(this);
  1731. }
  1732. // mark the container as having z indexed children
  1733. if (zIndex) {
  1734. parentWrapper.handleZ = true;
  1735. zIndex = pInt(zIndex);
  1736. }
  1737. // insert according to this and other elements' zIndex
  1738. if (parentWrapper.handleZ) { // this element or any of its siblings has a z index
  1739. for (i = 0; i < childNodes.length; i++) {
  1740. otherElement = childNodes[i];
  1741. otherZIndex = attr(otherElement, 'zIndex');
  1742. if (otherElement != element && (
  1743. // insert before the first element with a higher zIndex
  1744. pInt(otherZIndex) > zIndex ||
  1745. // if no zIndex given, insert before the first element with a zIndex
  1746. (!defined(zIndex) && defined(otherZIndex))
  1747. )) {
  1748. parentNode.insertBefore(element, otherElement);
  1749. return this;
  1750. }
  1751. }
  1752. }
  1753. // default: append at the end
  1754. parentNode.appendChild(element);
  1755. this.added = true;
  1756. return this;
  1757. },
  1758. /**
  1759. * Destroy the element and element wrapper
  1760. */
  1761. destroy: function() {
  1762. var wrapper = this,
  1763. element = wrapper.element || {},
  1764. shadows = wrapper.shadows,
  1765. parentNode = element.parentNode,
  1766. key;
  1767. // remove events
  1768. element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = null;
  1769. stop(wrapper); // stop running animations
  1770. // remove element
  1771. if (parentNode) {
  1772. parentNode.removeChild(element);
  1773. }
  1774. // destroy shadows
  1775. if (shadows) {
  1776. each(shadows, function(shadow) {
  1777. parentNode = shadow.parentNode;
  1778. if (parentNode) { // the entire chart HTML can be overwritten
  1779. parentNode.removeChild(shadow);
  1780. }
  1781. });
  1782. }
  1783. // remove from alignObjects
  1784. erase(wrapper.renderer.alignedObjects, wrapper);
  1785. for (key in wrapper) {
  1786. delete wrapper[key];
  1787. }
  1788. return null;
  1789. },
  1790. /**
  1791. * Empty a group element
  1792. */
  1793. empty: function() {
  1794. var element = this.element,
  1795. childNodes = element.childNodes,
  1796. i = childNodes.length;
  1797. while (i--) {
  1798. element.removeChild(childNodes[i]);
  1799. }
  1800. },
  1801. /**
  1802. * Add a shadow to the element. Must be done after the element is added to the DOM
  1803. * @param {Boolean} apply
  1804. */
  1805. shadow: function(apply) {
  1806. var shadows = [],
  1807. i,
  1808. shadow,
  1809. element = this.element,
  1810. // compensate for inverted plot area
  1811. transform = this.parentInverted ? '(-1,-1)' : '(1,1)';
  1812. if (apply) {
  1813. for (i = 1; i <= 3; i++) {
  1814. shadow = element.cloneNode(0);
  1815. attr(shadow, {
  1816. 'isShadow': 'true',
  1817. 'stroke': 'rgb(0, 0, 0)',
  1818. 'stroke-opacity': 0.05 * i,
  1819. 'stroke-width': 7 - 2 * i,
  1820. 'transform': 'translate'+ transform,
  1821. 'fill': NONE
  1822. });
  1823. element.parentNode.insertBefore(shadow, element);
  1824. shadows.push(shadow);
  1825. }
  1826. this.shadows = shadows;
  1827. }
  1828. return this;
  1829. }
  1830. };
  1831. /**
  1832. * The default SVG renderer
  1833. */
  1834. var SVGRenderer = function() {
  1835. this.init.apply(this, arguments);
  1836. };
  1837. SVGRenderer.prototype = {
  1838. /**
  1839. * Initialize the SVGRenderer
  1840. * @param {Object} container
  1841. * @param {Number} width
  1842. * @param {Number} height
  1843. * @param {Boolean} forExport
  1844. */
  1845. init: function(container, width, height, forExport) {
  1846. var renderer = this,
  1847. loc = location,
  1848. boxWrapper;
  1849. renderer.Element = SVGElement;
  1850. boxWrapper = renderer.createElement('svg')
  1851. .attr({
  1852. xmlns: SVG_NS,
  1853. version: '1.1'
  1854. });
  1855. container.appendChild(boxWrapper.element);
  1856. // object properties
  1857. renderer.box = boxWrapper.element;
  1858. renderer.boxWrapper = boxWrapper;
  1859. renderer.alignedObjects = [];
  1860. renderer.url = isIE ? '' : loc.href.replace(/#.*?$/, ''); // page url used for internal references
  1861. renderer.defs = this.createElement('defs').add();
  1862. renderer.forExport = forExport;
  1863. renderer.setSize(width, height, false);
  1864. },
  1865. /**
  1866. * Create a wrapper for an SVG element
  1867. * @param {Object} nodeName
  1868. */
  1869. createElement: function(nodeName) {
  1870. var wrapper = new this.Element();
  1871. wrapper.init(this, nodeName);
  1872. return wrapper;
  1873. },
  1874. /**
  1875. * Parse a simple HTML string into SVG tspans
  1876. *
  1877. * @param {Object} textNode The parent text SVG node
  1878. */
  1879. buildText: function(wrapper) {
  1880. var textNode = wrapper.element,
  1881. lines = pick(wrapper.textStr, '').toString()
  1882. .replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
  1883. .replace(/<(i|em)>/g, '<span style="font-style:italic">')
  1884. .replace(/<a/g, '<span')
  1885. .replace(/<\/(b|strong|i|em|a)>/g, '</span>')
  1886. .split(/<br[^>]?>/g),
  1887. childNodes = textNode.childNodes,
  1888. styleRegex = /style="([^"]+)"/,
  1889. hrefRegex = /href="([^"]+)"/,
  1890. parentX = attr(textNode, 'x'),
  1891. textStyles = wrapper.styles,
  1892. reverse = isFirefox && textStyles && textStyles.HcDirection == 'rtl' && !this.forExport, // issue #38
  1893. arr,
  1894. width = textStyles && pInt(textStyles.width),
  1895. textLineHeight = textStyles && textStyles.lineHeight,
  1896. lastLine,
  1897. i = childNodes.length;
  1898. // remove old text
  1899. while (i--) {
  1900. textNode.removeChild(childNodes[i]);
  1901. }
  1902. if (width && !wrapper.added) {
  1903. this.box.appendChild(textNode); // attach it to the DOM to read offset width
  1904. }
  1905. each(lines, function(line, lineNo) {
  1906. var spans, spanNo = 0, lineHeight;
  1907. line = line.replace(/<span/g, '|||<span').replace(/<\/span>/g, '</span>|||');
  1908. spans = line.split('|||');
  1909. each(spans, function (span) {
  1910. if (span !== '' || spans.length == 1) {
  1911. var attributes = {},
  1912. tspan = doc.createElementNS(SVG_NS, 'tspan');
  1913. if (styleRegex.test(span)) {
  1914. attr(
  1915. tspan,
  1916. 'style',
  1917. span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2')
  1918. );
  1919. }
  1920. if (hrefRegex.test(span)) {
  1921. attr(tspan, 'onclick', 'location.href=\"'+ span.match(hrefRegex)[1] +'\"');
  1922. css(tspan, { cursor: 'pointer' });
  1923. }
  1924. span = span.replace(/<(.|\n)*?>/g, '') || ' ';
  1925. // issue #38 workaround.
  1926. if (reverse) {
  1927. arr = [];
  1928. i = span.length;
  1929. while (i--) {
  1930. arr.push(span.charAt(i))
  1931. }
  1932. span = arr.join('');
  1933. }
  1934. // add the text node
  1935. tspan.appendChild(doc.createTextNode(span));
  1936. if (!spanNo) { // first span in a line, align it to the left
  1937. attributes.x = parentX;
  1938. } else {
  1939. // Firefox ignores spaces at the front or end of the tspan
  1940. attributes.dx = 3; // space
  1941. }
  1942. // first span on subsequent line, add the line height
  1943. if (!spanNo) {
  1944. if (lineNo) {
  1945. // Webkit and opera sometimes return 'normal' as the line height. In that
  1946. // case, webkit uses offsetHeight, while Opera falls back to 18
  1947. lineHeight = pInt(window.getComputedStyle(lastLine, null).getPropertyValue('line-height'));
  1948. if (isNaN(lineHeight)) {
  1949. lineHeight = textLineHeight || lastLine.offsetHeight || 18;
  1950. }
  1951. attr(tspan, 'dy', lineHeight);
  1952. }
  1953. lastLine = tspan; // record for use in next line
  1954. }
  1955. // add attributes
  1956. attr(tspan, attributes);
  1957. // append it
  1958. textNode.appendChild(tspan);
  1959. spanNo++;
  1960. // check width and apply soft breaks
  1961. if (width) {
  1962. var words = span.replace(/-/g, '- ').split(' '),
  1963. tooLong,
  1964. actualWidth,
  1965. rest = [];
  1966. while (words.length || rest.length) {
  1967. actualWidth = textNode.getBBox().width;
  1968. tooLong = actualWidth > width;
  1969. if (!tooLong || words.length == 1) { // new line needed
  1970. words = rest;
  1971. rest = [];
  1972. if (words.length) {
  1973. tspan = doc.createElementNS(SVG_NS, 'tspan');
  1974. attr(tspan, {
  1975. x: parentX,
  1976. dy: textLineHeight || 16
  1977. });
  1978. textNode.appendChild(tspan);
  1979. if (actualWidth > width) { // a single word is pressing it out
  1980. width = actualWidth;
  1981. }
  1982. }
  1983. } else { // append to existing line tspan
  1984. tspan.removeChild(tspan.firstChild);
  1985. rest.unshift(words.pop());
  1986. }
  1987. tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
  1988. }
  1989. }
  1990. }
  1991. });
  1992. });
  1993. },
  1994. /**
  1995. * Make a straight line crisper by not spilling out to neighbour pixels
  1996. * @param {Array} points
  1997. * @param {Number} width
  1998. */
  1999. crispLine: function(points, width) {
  2000. // points format: [M, 0, 0, L, 100, 0]
  2001. // normalize to a crisp line
  2002. if (points[1] == points[4]) {
  2003. points[1] = points[4] = mathRound(points[1]) + (width % 2 / 2);
  2004. }
  2005. if (points[2] == points[5]) {
  2006. points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2);
  2007. }
  2008. return points;
  2009. },
  2010. /**
  2011. * Draw a path
  2012. * @param {Array} path An SVG path in array form
  2013. */
  2014. path: function (path) {
  2015. return this.createElement('path').attr({
  2016. d: path,
  2017. fill: NONE
  2018. });
  2019. },
  2020. /**
  2021. * Draw and return an SVG circle
  2022. * @param {Number} x The x position
  2023. * @param {Number} y The y position
  2024. * @param {Number} r The radius
  2025. */
  2026. circle: function (x, y, r) {
  2027. var attr = isObject(x) ?
  2028. x :
  2029. {
  2030. x: x,
  2031. y: y,
  2032. r: r
  2033. };
  2034. return this.createElement('circle').attr(attr);
  2035. },
  2036. /**
  2037. * Draw and return an arc
  2038. * @param {Number} x X position
  2039. * @param {Number} y Y position
  2040. * @param {Number} r Radius
  2041. * @param {Number} innerR Inner radius like used in donut charts
  2042. * @param {Number} start Starting angle
  2043. * @param {Number} end Ending angle
  2044. */
  2045. arc: function (x, y, r, innerR, start, end) {
  2046. // arcs are defined as symbols for the ability to set
  2047. // attributes in attr and animate
  2048. if (isObject(x)) {
  2049. y = x.y;
  2050. r = x.r;
  2051. innerR = x.innerR;
  2052. start = x.start;
  2053. end = x.end;
  2054. x = x.x;
  2055. }
  2056. return this.symbol('arc', x || 0, y || 0, r || 0, {
  2057. innerR: innerR || 0,
  2058. start: start || 0,
  2059. end: end || 0
  2060. });
  2061. },
  2062. /**
  2063. * Draw and return a rectangle
  2064. * @param {Number} x Left position
  2065. * @param {Number} y Top position
  2066. * @param {Number} width
  2067. * @param {Number} height
  2068. * @param {Number} r Border corner radius
  2069. * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing
  2070. */
  2071. rect: function (x, y, width, height, r, strokeWidth) {
  2072. if (isObject(x)) {
  2073. y = x.y;
  2074. width = x.width;
  2075. height = x.height;
  2076. r = x.r;
  2077. x = x.x;
  2078. }
  2079. var wrapper = this.createElement('rect').attr({
  2080. rx: r,
  2081. ry: r,
  2082. fill: NONE
  2083. });
  2084. return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
  2085. },
  2086. /**
  2087. * Resize the box and re-align all aligned elements
  2088. * @param {Object} width
  2089. * @param {Object} height
  2090. * @param {Boolean} animate
  2091. *
  2092. */
  2093. setSize: function(width, height, animate) {
  2094. var renderer = this,
  2095. alignedObjects = renderer.alignedObjects,
  2096. i = alignedObjects.length;
  2097. renderer.width = width;
  2098. renderer.height = height;
  2099. renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({
  2100. width: width,
  2101. height: height
  2102. });
  2103. while (i--) {
  2104. alignedObjects[i].align();
  2105. }
  2106. },
  2107. /**
  2108. * Create a group
  2109. * @param {String} name The group will be given a class name of 'highcharts-{name}'.
  2110. * This can be used for styling and scripting.
  2111. */
  2112. g: function(name) {
  2113. return this.createElement('g').attr(
  2114. defined(name) && { 'class': PREFIX + name }
  2115. );
  2116. },
  2117. /**
  2118. * Display an image
  2119. * @param {String} src
  2120. * @param {Number} x
  2121. * @param {Number} y
  2122. * @param {Number} width
  2123. * @param {Number} height
  2124. */
  2125. image: function(src, x, y, width, height) {
  2126. var attribs = {
  2127. preserveAspectRatio: NONE
  2128. },
  2129. elemWrapper;
  2130. // optional properties
  2131. if (arguments.length > 1) {
  2132. extend(attribs, {
  2133. x: x,
  2134. y: y,
  2135. width: width,
  2136. height: height
  2137. });
  2138. }
  2139. elemWrapper = this.createElement('image').attr(attribs);
  2140. // set the href in the xlink namespace
  2141. elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',
  2142. 'href', src);
  2143. return elemWrapper;
  2144. },
  2145. /**
  2146. * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object.
  2147. *
  2148. * @param {Object} symbol
  2149. * @param {Object} x
  2150. * @param {Object} y
  2151. * @param {Object} radius
  2152. * @param {Object} options
  2153. */
  2154. symbol: function(symbol, x, y, radius, options) {
  2155. var obj,
  2156. // get the symbol definition function
  2157. symbolFn = this.symbols[symbol],
  2158. // check if there's a path defined for this symbol
  2159. path = symbolFn && symbolFn(
  2160. x,
  2161. y,
  2162. radius,
  2163. options
  2164. ),
  2165. imageRegex = /^url\((.*?)\)$/,
  2166. imageSrc;
  2167. if (path) {
  2168. obj = this.path(path);
  2169. // expando properties for use in animate and attr
  2170. extend(obj, {
  2171. symbolName: symbol,
  2172. x: x,
  2173. y: y,
  2174. r: radius
  2175. });
  2176. if (options) {
  2177. extend(obj, options);
  2178. }
  2179. // image symbols
  2180. } else if (imageRegex.test(symbol)) {
  2181. imageSrc = symbol.match(imageRegex)[1];
  2182. // create the image synchronously, add attribs async
  2183. obj = this.image(imageSrc)
  2184. .attr({
  2185. x: x,
  2186. y: y
  2187. });
  2188. // create a dummy JavaScript image to get the width and height
  2189. createElement('img', {
  2190. onload: function() {
  2191. var img = this,
  2192. size = symbolSizes[img.src] || [img.width, img.height];
  2193. obj.attr({
  2194. width: size[0],
  2195. height: size[1]
  2196. }).translate(
  2197. -mathRound(size[0] / 2),
  2198. -mathRound(size[1] / 2)
  2199. );
  2200. },
  2201. src: imageSrc
  2202. });
  2203. // default circles
  2204. } else {
  2205. obj = this.circle(x, y, radius);
  2206. }
  2207. return obj;
  2208. },
  2209. /**
  2210. * An extendable collection of functions for defining symbol paths.
  2211. */
  2212. symbols: {
  2213. 'square': function (x, y, radius) {
  2214. var len = 0.707 * radius;
  2215. return [
  2216. M, x-len, y-len,
  2217. L, x+len, y-len,
  2218. x+len, y+len,
  2219. x-len, y+len,
  2220. 'Z'
  2221. ];
  2222. },
  2223. 'triangle': function (x, y, radius) {
  2224. return [
  2225. M, x, y-1.33 * radius,
  2226. L, x+radius, y + 0.67 * radius,
  2227. x-radius, y + 0.67 * radius,
  2228. 'Z'
  2229. ];
  2230. },
  2231. 'triangle-down': function (x, y, radius) {
  2232. return [
  2233. M, x, y + 1.33 * radius,
  2234. L, x-radius, y-0.67 * radius,
  2235. x+radius, y-0.67 * radius,
  2236. 'Z'
  2237. ];
  2238. },
  2239. 'diamond': function (x, y, radius) {
  2240. return [
  2241. M, x, y-radius,
  2242. L, x+radius, y,
  2243. x, y+radius,
  2244. x-radius, y,
  2245. 'Z'
  2246. ];
  2247. },
  2248. 'arc': function (x, y, radius, options) {
  2249. var start = options.start,
  2250. end = options.end - 0.000001, // to prevent cos and sin of start and end from becoming equal on 360 arcs
  2251. innerRadius = options.innerR,
  2252. cosStart = mathCos(start),
  2253. sinStart = mathSin(start),
  2254. cosEnd = mathCos(end),
  2255. sinEnd = mathSin(end),
  2256. longArc = options.end - start < mathPI ? 0 : 1;
  2257. return [
  2258. M,
  2259. x + radius * cosStart,
  2260. y + radius * sinStart,
  2261. 'A', // arcTo
  2262. radius, // x radius
  2263. radius, // y radius
  2264. 0, // slanting
  2265. longArc, // long or short arc
  2266. 1, // clockwise
  2267. x + radius * cosEnd,
  2268. y + radius * sinEnd,
  2269. L,
  2270. x + innerRadius * cosEnd,
  2271. y + innerRadius * sinEnd,
  2272. 'A', // arcTo
  2273. innerRadius, // x radius
  2274. innerRadius, // y radius
  2275. 0, // slanting
  2276. longArc, // long or short arc
  2277. 0, // clockwise
  2278. x + innerRadius * cosStart,
  2279. y + innerRadius * sinStart,
  2280. 'Z' // close
  2281. ];
  2282. }
  2283. },
  2284. /**
  2285. * Define a clipping rectangle
  2286. * @param {String} id
  2287. * @param {Number} x
  2288. * @param {Number} y
  2289. * @param {Number} width
  2290. * @param {Number} height
  2291. */
  2292. clipRect: function (x, y, width, height) {
  2293. var wrapper,
  2294. id = PREFIX + idCounter++,
  2295. clipPath = this.createElement('clipPath').attr({
  2296. id: id
  2297. }).add(this.defs);
  2298. wrapper = this.rect(x, y, width, height, 0).add(clipPath);
  2299. wrapper.id = id;
  2300. return wrapper;
  2301. },
  2302. /**
  2303. * Take a color and return it if it's a string, make it a gradient if it's a
  2304. * gradient configuration object
  2305. *
  2306. * @param {Object} color The color or config object
  2307. */
  2308. color: function(color, elem, prop) {
  2309. var colorObject,
  2310. regexRgba = /^rgba/;
  2311. if (color && color.linearGradient) {
  2312. var renderer = this,
  2313. strLinearGradient = 'linearGradient',
  2314. linearGradient = color[strLinearGradient],
  2315. id = PREFIX + idCounter++,
  2316. gradientObject,
  2317. stopColor,
  2318. stopOpacity;
  2319. gradientObject = renderer.createElement(strLinearGradient).attr({
  2320. id: id,
  2321. gradientUnits: 'userSpaceOnUse',
  2322. x1: linearGradient[0],
  2323. y1: linearGradient[1],
  2324. x2: linearGradient[2],
  2325. y2: linearGradient[3]
  2326. }).add(renderer.defs);
  2327. each(color.stops, function(stop) {
  2328. if (regexRgba.test(stop[1])) {
  2329. colorObject = Color(stop[1]);
  2330. stopColor = colorObject.get('rgb');
  2331. stopOpacity = colorObject.get('a');
  2332. } else {
  2333. stopColor = stop[1];
  2334. stopOpacity = 1;
  2335. }
  2336. renderer.createElement('stop').attr({
  2337. offset: stop[0],
  2338. 'stop-color': stopColor,
  2339. 'stop-opacity': stopOpacity
  2340. }).add(gradientObject);
  2341. });
  2342. return 'url('+ this.url +'#'+ id +')';
  2343. // Webkit and Batik can't show rgba.
  2344. } else if (regexRgba.test(color)) {
  2345. colorObject = Color(color);
  2346. attr(elem, prop +'-opacity', colorObject.get('a'));
  2347. return colorObject.get('rgb');
  2348. } else {
  2349. return color;
  2350. }
  2351. },
  2352. /**
  2353. * Add text to the SVG object
  2354. * @param {String} str
  2355. * @param {Number} x Left position
  2356. * @param {Number} y Top position
  2357. */
  2358. text: function(str, x, y) {
  2359. // declare variables
  2360. var defaultChartStyle = defaultOptions.chart.style,
  2361. wrapper;
  2362. x = mathRound(pick(x, 0));
  2363. y = mathRound(pick(y, 0));
  2364. wrapper = this.createElement('text')
  2365. .attr({
  2366. x: x,
  2367. y: y,
  2368. text: str
  2369. })
  2370. .css({
  2371. 'font-family': defaultChartStyle.fontFamily,
  2372. 'font-size': defaultChartStyle.fontSize
  2373. });
  2374. wrapper.x = x;
  2375. wrapper.y = y;
  2376. return wrapper;
  2377. }
  2378. }; // end SVGRenderer
  2379. /* ****************************************************************************
  2380. * *
  2381. * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE *
  2382. * *
  2383. * For applications and websites that don't need IE support, like platform *
  2384. * targeted mobile apps and web apps, this code can be removed. *
  2385. * *
  2386. *****************************************************************************/
  2387. var VMLRenderer;
  2388. if (!hasSVG) {
  2389. /**
  2390. * The VML element wrapper.
  2391. */
  2392. var VMLElement = extendClass( SVGElement, {
  2393. /**
  2394. * Initialize a new VML element wrapper. It builds the markup as a string
  2395. * to minimize DOM traffic.
  2396. * @param {Object} renderer
  2397. * @param {Object} nodeName
  2398. */
  2399. init: function(renderer, nodeName) {
  2400. var markup = ['<', nodeName, ' filled="f" stroked="f"'],
  2401. style = ['position: ', ABSOLUTE, ';'];
  2402. // divs and shapes need size
  2403. if (nodeName == 'shape' || nodeName == DIV) {
  2404. style.push('left:0;top:0;width:10px;height:10px;');
  2405. }
  2406. if (docMode8) {
  2407. style.push('visibility: ', nodeName == DIV ? HIDDEN : VISIBLE);
  2408. }
  2409. markup.push(' style="', style.join(''), '"/>');
  2410. // create element with default attributes and style
  2411. if (nodeName) {
  2412. markup = nodeName == DIV || nodeName == 'span' || nodeName == 'img' ?
  2413. markup.join('')
  2414. : renderer.prepVML(markup);
  2415. this.element = createElement(markup);
  2416. }
  2417. this.renderer = renderer;
  2418. },
  2419. /**
  2420. * Add the node to the given parent
  2421. * @param {Object} parent
  2422. */
  2423. add: function(parent) {
  2424. var wrapper = this,
  2425. renderer = wrapper.renderer,
  2426. element = wrapper.element,
  2427. box = renderer.box,
  2428. inverted = parent && parent.inverted,
  2429. // get the parent node
  2430. parentNode = parent ?
  2431. parent.element || parent :
  2432. box;
  2433. // if the parent group is inverted, apply inversion on all children
  2434. if (inverted) { // only on groups
  2435. renderer.invertChild(element, parentNode);
  2436. }
  2437. // issue #140 workaround - related to #61 and #74
  2438. if (docMode8 && parentNode.gVis == HIDDEN) {
  2439. css(element, { visibility: HIDDEN });
  2440. }
  2441. // append it
  2442. parentNode.appendChild(element);
  2443. // align text after adding to be able to read offset
  2444. wrapper.added = true;
  2445. if (wrapper.alignOnAdd) {
  2446. wrapper.updateTransform();
  2447. }
  2448. return wrapper;
  2449. },
  2450. /**
  2451. * Get or set attributes
  2452. */
  2453. attr: function(hash, val) {
  2454. var key,
  2455. value,
  2456. i,
  2457. element = this.element || {},
  2458. elemStyle = element.style,
  2459. nodeName = element.nodeName,
  2460. renderer = this.renderer,
  2461. symbolName = this.symbolName,
  2462. childNodes,
  2463. hasSetSymbolSize,
  2464. shadows = this.shadows,
  2465. skipAttr,
  2466. ret = this;
  2467. // single key-value pair
  2468. if (isString(hash) && defined(val)) {
  2469. key = hash;
  2470. hash = {};
  2471. hash[key] = val;
  2472. }
  2473. // used as a getter, val is undefined
  2474. if (isString(hash)) {
  2475. key = hash;
  2476. if (key == 'strokeWidth' || key == 'stroke-width') {
  2477. ret = this.strokeweight;
  2478. } else {
  2479. ret = this[key];
  2480. }
  2481. // setter
  2482. } else {
  2483. for (key in hash) {
  2484. value = hash[key];
  2485. skipAttr = false;
  2486. // prepare paths
  2487. // symbols
  2488. if (symbolName && /^(x|y|r|start|end|width|height|innerR)/.test(key)) {
  2489. // if one of the symbol size affecting parameters are changed,
  2490. // check all the others only once for each call to an element's
  2491. // .attr() method
  2492. if (!hasSetSymbolSize) {
  2493. this.symbolAttr(hash);
  2494. hasSetSymbolSize = true;
  2495. }
  2496. skipAttr = true;
  2497. } else if (key == 'd') {
  2498. value = value || [];
  2499. this.d = value.join(' '); // used in getter for animation
  2500. // convert paths
  2501. i = value.length;
  2502. var convertedPath = [];
  2503. while (i--) {
  2504. // Multiply by 10 to allow subpixel precision.
  2505. // Substracting half a pixel seems to make the coordinates
  2506. // align with SVG, but this hasn't been tested thoroughly
  2507. if (isNumber(value[i])) {
  2508. convertedPath[i] = mathRound(value[i] * 10) - 5;
  2509. }
  2510. // close the path
  2511. else if (value[i] == 'Z') {
  2512. convertedPath[i] = 'x';
  2513. }
  2514. else {
  2515. convertedPath[i] = value[i];
  2516. }
  2517. }
  2518. value = convertedPath.join(' ') || 'x';
  2519. element.path = value;
  2520. // update shadows
  2521. if (shadows) {
  2522. i = shadows.length;
  2523. while (i--) {
  2524. shadows[i].path = value;
  2525. }
  2526. }
  2527. skipAttr = true;
  2528. // directly mapped to css
  2529. } else if (key == 'zIndex' || key == 'visibility') {
  2530. // issue 61 workaround
  2531. if (docMode8 && key == 'visibility' && nodeName == 'DIV') {
  2532. element.gVis = value;
  2533. childNodes = element.childNodes;
  2534. i = childNodes.length;
  2535. while (i--) {
  2536. css(childNodes[i], { visibility: value });
  2537. }
  2538. if (value == VISIBLE) { // issue 74
  2539. value = null;
  2540. }
  2541. }
  2542. if (value) {
  2543. elemStyle[key] = value;
  2544. }
  2545. skipAttr = true;
  2546. // width and height
  2547. } else if (/^(width|height)$/.test(key)) {
  2548. // clipping rectangle special
  2549. if (this.updateClipping) {
  2550. this[key] = value;
  2551. this.updateClipping();
  2552. } else {
  2553. // normal
  2554. elemStyle[key] = value;
  2555. }
  2556. skipAttr = true;
  2557. // x and y
  2558. } else if (/^(x|y)$/.test(key)) {
  2559. this[key] = value; // used in getter
  2560. if (element.tagName == 'SPAN') {
  2561. this.updateTransform();
  2562. } else {
  2563. elemStyle[{ x: 'left', y: 'top' }[key]] = value;
  2564. }
  2565. // class name
  2566. } else if (key == 'class') {
  2567. // IE8 Standards mode has problems retrieving the className
  2568. element.className = value;
  2569. // stroke
  2570. } else if (key == 'stroke') {
  2571. value = renderer.color(value, element, key);
  2572. key = 'strokecolor';
  2573. // stroke width
  2574. } else if (key == 'stroke-width' || key == 'strokeWidth') {
  2575. element.stroked = value ? true : false;
  2576. key = 'strokeweight';
  2577. this[key] = value; // used in getter, issue #113
  2578. if (isNumber(value)) {
  2579. value += PX;
  2580. }
  2581. // dashStyle
  2582. } else if (key == 'dashstyle') {
  2583. var strokeElem = element.getElementsByTagName('stroke')[0] ||
  2584. createElement(renderer.prepVML(['<stroke/>']), null, null, element);
  2585. strokeElem[key] = value || 'solid';
  2586. this.dashstyle = value; /* because changing stroke-width will change the dash length
  2587. and cause an epileptic effect */
  2588. skipAttr = true;
  2589. // fill
  2590. } else if (key == 'fill') {
  2591. if (nodeName == 'SPAN') { // text color
  2592. elemStyle.color = value;
  2593. } else {
  2594. element.filled = value != NONE ? true : false;
  2595. value = renderer.color(value, element, key);
  2596. key = 'fillcolor';
  2597. }
  2598. // translation for animation
  2599. } else if (key == 'translateX' || key == 'translateY' || key == 'rotation' || key == 'align') {
  2600. if (key == 'align') {
  2601. key = 'textAlign';
  2602. }
  2603. this[key] = value;
  2604. this.updateTransform();
  2605. skipAttr = true;
  2606. }
  2607. // text for rotated and non-rotated elements
  2608. else if (key == 'text') {
  2609. element.innerHTML = value;
  2610. skipAttr = true;
  2611. }
  2612. // let the shadow follow the main element
  2613. if (shadows && key == 'visibility') {
  2614. i = shadows.length;
  2615. while (i--) {
  2616. shadows[i].style[key] = value;
  2617. }
  2618. }
  2619. if (!skipAttr) {
  2620. if (docMode8) { // IE8 setAttribute bug
  2621. element[key] = value;
  2622. } else {
  2623. attr(element, key, value);
  2624. }
  2625. }
  2626. }
  2627. }
  2628. return ret;
  2629. },
  2630. /**
  2631. * Set the element's clipping to a predefined rectangle
  2632. *
  2633. * @param {String} id The id of the clip rectangle
  2634. */
  2635. clip: function(clipRect) {
  2636. var wrapper = this,
  2637. clipMembers = clipRect.members;
  2638. clipMembers.push(wrapper);
  2639. wrapper.destroyClip = function() {
  2640. erase(clipMembers, wrapper);
  2641. };
  2642. return wrapper.css(clipRect.getCSS(wrapper.inverted));
  2643. },
  2644. /**
  2645. * Set styles for the element
  2646. * @param {Object} styles
  2647. */
  2648. css: function(styles) {
  2649. var wrapper = this,
  2650. element = wrapper.element,
  2651. textWidth = styles && element.tagName == 'SPAN' && styles.width;
  2652. /*if (textWidth) {
  2653. extend(styles, {
  2654. display: 'block',
  2655. whiteSpace: 'normal'
  2656. });
  2657. }*/
  2658. if (textWidth) {
  2659. delete styles.width;
  2660. wrapper.textWidth = textWidth;
  2661. wrapper.updateTransform();
  2662. }
  2663. wrapper.styles = extend(wrapper.styles, styles);
  2664. css(wrapper.element, styles);
  2665. return wrapper;
  2666. },
  2667. /**
  2668. * Extend element.destroy by removing it from the clip members array
  2669. */
  2670. destroy: function() {
  2671. var wrapper = this;
  2672. if (wrapper.destroyClip) {
  2673. wrapper.destroyClip();
  2674. }
  2675. SVGElement.prototype.destroy.apply(wrapper);
  2676. },
  2677. /**
  2678. * Remove all child nodes of a group, except the v:group element
  2679. */
  2680. empty: function() {
  2681. var element = this.element,
  2682. childNodes = element.childNodes,
  2683. i = childNodes.length,
  2684. node;
  2685. while (i--) {
  2686. node = childNodes[i];
  2687. node.parentNode.removeChild(node);
  2688. }
  2689. },
  2690. /**
  2691. * VML override for calculating the bounding box based on offsets
  2692. *
  2693. * @return {Object} A hash containing values for x, y, width and height
  2694. */
  2695. getBBox: function() {
  2696. var element = this.element;
  2697. // faking getBBox in exported SVG in legacy IE
  2698. if (element.nodeName == 'text') {
  2699. element.style.position = ABSOLUTE;
  2700. }
  2701. return {
  2702. x: element.offsetLeft,
  2703. y: element.offsetTop,
  2704. width: element.offsetWidth,
  2705. height: element.offsetHeight
  2706. };
  2707. },
  2708. /**
  2709. * Add an event listener. VML override for normalizing event parameters.
  2710. * @param {String} eventType
  2711. * @param {Function} handler
  2712. */
  2713. on: function(eventType, handler) {
  2714. // simplest possible event model for internal use
  2715. this.element['on'+ eventType] = function() {
  2716. var evt = win.event;
  2717. evt.target = evt.srcElement;
  2718. handler(evt);
  2719. };
  2720. return this;
  2721. },
  2722. /**
  2723. * VML override private method to update elements based on internal
  2724. * properties based on SVG transform
  2725. */
  2726. updateTransform: function(hash) {
  2727. // aligning non added elements is expensive
  2728. if (!this.added) {
  2729. this.alignOnAdd = true;
  2730. return;
  2731. }
  2732. var wrapper = this,
  2733. elem = wrapper.element,
  2734. translateX = wrapper.translateX || 0,
  2735. translateY = wrapper.translateY || 0,
  2736. x = wrapper.x || 0,
  2737. y = wrapper.y || 0,
  2738. align = wrapper.textAlign || 'left',
  2739. alignCorrection = { left: 0, center: 0.5, right: 1 }[align],
  2740. nonLeft = align && align != 'left';
  2741. // apply translate
  2742. if (translateX || translateY) {
  2743. wrapper.css({
  2744. marginLeft: translateX,
  2745. marginTop: translateY
  2746. });
  2747. }
  2748. // apply inversion
  2749. if (wrapper.inverted) { // wrapper is a group
  2750. each(elem.childNodes, function(child) {
  2751. wrapper.renderer.invertChild(child, elem);
  2752. });
  2753. }
  2754. if (elem.tagName == 'SPAN') {
  2755. var width, height,
  2756. rotation = wrapper.rotation,
  2757. lineHeight,
  2758. radians = 0,
  2759. costheta = 1,
  2760. sintheta = 0,
  2761. quad,
  2762. textWidth = pInt(wrapper.textWidth),
  2763. xCorr = wrapper.xCorr || 0,
  2764. yCorr = wrapper.yCorr || 0,
  2765. currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(',');
  2766. if (currentTextTransform != wrapper.cTT) { // do the calculations and DOM access only if properties changed
  2767. if (defined(rotation)) {
  2768. radians = rotation * deg2rad; // deg to rad
  2769. costheta = mathCos(radians);
  2770. sintheta = mathSin(radians);
  2771. // Adjust for alignment and rotation.
  2772. // Test case: http://highcharts.com/tests/?file=text-rotation
  2773. css(elem, {
  2774. filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
  2775. ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
  2776. ', sizingMethod=\'auto expand\')'].join('') : NONE
  2777. });
  2778. }
  2779. width = elem.offsetWidth;
  2780. height = elem.offsetHeight;
  2781. // update textWidth
  2782. if (width > textWidth) {
  2783. css(elem, {
  2784. width: textWidth +PX,
  2785. display: 'block',
  2786. whiteSpace: 'normal'
  2787. });
  2788. width = textWidth;
  2789. }
  2790. // correct x and y
  2791. lineHeight = mathRound(pInt(elem.style.fontSize || 12) * 1.2);
  2792. xCorr = costheta < 0 && -width;
  2793. yCorr = sintheta < 0 && -height;
  2794. // correct for lineHeight and corners spilling out after rotation
  2795. quad = costheta * sintheta < 0;
  2796. xCorr += sintheta * lineHeight * (quad ? 1 - alignCorrection : alignCorrection);
  2797. yCorr -= costheta * lineHeight * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1);
  2798. // correct for the length/height of the text
  2799. if (nonLeft) {
  2800. xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1);
  2801. if (rotation) {
  2802. yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1);
  2803. }
  2804. css(elem, {
  2805. textAlign: align
  2806. });
  2807. }
  2808. // record correction
  2809. wrapper.xCorr = xCorr;
  2810. wrapper.yCorr = yCorr;
  2811. }
  2812. // apply position with correction
  2813. css(elem, {
  2814. left: x + xCorr,
  2815. top: y + yCorr
  2816. });
  2817. // record current text transform
  2818. wrapper.cTT = currentTextTransform;
  2819. }
  2820. },
  2821. /**
  2822. * Apply a drop shadow by copying elements and giving them different strokes
  2823. * @param {Boolean} apply
  2824. */
  2825. shadow: function(apply) {
  2826. var shadows = [],
  2827. i,
  2828. element = this.element,
  2829. renderer = this.renderer,
  2830. shadow,
  2831. elemStyle = element.style,
  2832. markup,
  2833. path = element.path;
  2834. // the path is some mysterious string-like object that can be cast to a string
  2835. if (''+ element.path === '') {
  2836. path = 'x';
  2837. }
  2838. if (apply) {
  2839. for (i = 1; i <= 3; i++) {
  2840. markup = ['<shape isShadow="true" strokeweight="', ( 7 - 2 * i ) ,
  2841. '" filled="false" path="', path,
  2842. '" coordsize="100,100" style="', element.style.cssText, '" />'];
  2843. shadow = createElement(renderer.prepVML(markup),
  2844. null, {
  2845. left: pInt(elemStyle.left) + 1,
  2846. top: pInt(elemStyle.top) + 1
  2847. }
  2848. );
  2849. // apply the opacity
  2850. markup = ['<stroke color="black" opacity="', (0.05 * i), '"/>'];
  2851. createElement(renderer.prepVML(markup), null, null, shadow);
  2852. // insert it
  2853. element.parentNode.insertBefore(shadow, element);
  2854. // record it
  2855. shadows.push(shadow);
  2856. }
  2857. this.shadows = shadows;
  2858. }
  2859. return this;
  2860. }
  2861. });
  2862. /**
  2863. * The VML renderer
  2864. */
  2865. VMLRenderer = function() {
  2866. this.init.apply(this, arguments);
  2867. };
  2868. VMLRenderer.prototype = merge( SVGRenderer.prototype, { // inherit SVGRenderer
  2869. isIE8: userAgent.indexOf('MSIE 8.0') > -1,
  2870. /**
  2871. * Initialize the VMLRenderer
  2872. * @param {Object} container
  2873. * @param {Number} width
  2874. * @param {Number} height
  2875. */
  2876. init: function(container, width, height) {
  2877. var renderer = this,
  2878. boxWrapper;
  2879. renderer.Element = VMLElement;
  2880. renderer.alignedObjects = [];
  2881. boxWrapper = renderer.createElement(DIV);
  2882. container.appendChild(boxWrapper.element);
  2883. // generate the containing box
  2884. renderer.box = boxWrapper.element;
  2885. renderer.boxWrapper = boxWrapper;
  2886. renderer.setSize(width, height, false);
  2887. // The only way to make IE6 and IE7 print is to use a global namespace. However,
  2888. // with IE8 the only way to make the dynamic shapes visible in screen and print mode
  2889. // seems to be to add the xmlns attribute and the behaviour style inline.
  2890. if (!doc.namespaces.hcv) {
  2891. doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml');
  2892. // setup default css
  2893. doc.createStyleSheet().cssText =
  2894. 'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke'+
  2895. '{ behavior:url(#default#VML); display: inline-block; } ';
  2896. }
  2897. },
  2898. /**
  2899. * Define a clipping rectangle. In VML it is accomplished by storing the values
  2900. * for setting the CSS style to all associated members.
  2901. *
  2902. * @param {Number} x
  2903. * @param {Number} y
  2904. * @param {Number} width
  2905. * @param {Number} height
  2906. */
  2907. clipRect: function (x, y, width, height) {
  2908. // create a dummy element
  2909. var clipRect = this.createElement();
  2910. // mimic a rectangle with its style object for automatic updating in attr
  2911. return extend(clipRect, {
  2912. members: [],
  2913. left: x,
  2914. top: y,
  2915. width: width,
  2916. height: height,
  2917. getCSS: function(inverted) {
  2918. var rect = this,//clipRect.element.style,
  2919. top = rect.top,
  2920. left = rect.left,
  2921. right = left + rect.width,
  2922. bottom = top + rect.height,
  2923. ret = {
  2924. clip: 'rect('+
  2925. mathRound(inverted ? left : top) + 'px,'+
  2926. mathRound(inverted ? bottom : right) + 'px,'+
  2927. mathRound(inverted ? right : bottom) + 'px,'+
  2928. mathRound(inverted ? top : left) +'px)'
  2929. };
  2930. // issue 74 workaround
  2931. if (!inverted && docMode8) {
  2932. extend(ret, {
  2933. width: right +PX,
  2934. height: bottom +PX
  2935. });
  2936. }
  2937. return ret;
  2938. },
  2939. // used in attr and animation to update the clipping of all members
  2940. updateClipping: function() {
  2941. each(clipRect.members, function(member) {
  2942. member.css(clipRect.getCSS(member.inverted));
  2943. });
  2944. }
  2945. });
  2946. },
  2947. /**
  2948. * Take a color and return it if it's a string, make it a gradient if it's a
  2949. * gradient configuration object, and apply opacity.
  2950. *
  2951. * @param {Object} color The color or config object
  2952. */
  2953. color: function(color, elem, prop) {
  2954. var colorObject,
  2955. regexRgba = /^rgba/,
  2956. markup;
  2957. if (color && color.linearGradient) {
  2958. var stopColor,
  2959. stopOpacity,
  2960. linearGradient = color.linearGradient,
  2961. angle,
  2962. color1,
  2963. opacity1,
  2964. color2,
  2965. opacity2;
  2966. each(color.stops, function(stop, i) {
  2967. if (regexRgba.test(stop[1])) {
  2968. colorObject = Color(stop[1]);
  2969. stopColor = colorObject.get('rgb');
  2970. stopOpacity = colorObject.get('a');
  2971. } else {
  2972. stopColor = stop[1];
  2973. stopOpacity = 1;
  2974. }
  2975. if (!i) { // first
  2976. color1 = stopColor;
  2977. opacity1 = stopOpacity;
  2978. } else {
  2979. color2 = stopColor;
  2980. opacity2 = stopOpacity;
  2981. }
  2982. });
  2983. // calculate the angle based on the linear vector
  2984. angle = 90 - math.atan(
  2985. (linearGradient[3] - linearGradient[1]) / // y vector
  2986. (linearGradient[2] - linearGradient[0]) // x vector
  2987. ) * 180 / mathPI;
  2988. // when colors attribute is used, the meanings of opacity and o:opacity2
  2989. // are reversed.
  2990. markup = ['<', prop, ' colors="0% ', color1, ',100% ', color2, '" angle="', angle,
  2991. '" opacity="', opacity2, '" o:opacity2="', opacity1,
  2992. '" type="gradient" focus="100%" />'];
  2993. createElement(this.prepVML(markup), null, null, elem);
  2994. // if the color is an rgba color, split it and add a fill node
  2995. // to hold the opacity component
  2996. } else if (regexRgba.test(color) && elem.tagName != 'IMG') {
  2997. colorObject = Color(color);
  2998. markup = ['<', prop, ' opacity="', colorObject.get('a'), '"/>'];
  2999. createElement(this.prepVML(markup), null, null, elem);
  3000. return colorObject.get('rgb');
  3001. } else {
  3002. return color;
  3003. }
  3004. },
  3005. /**
  3006. * Take a VML string and prepare it for either IE8 or IE6/IE7.
  3007. * @param {Array} markup A string array of the VML markup to prepare
  3008. */
  3009. prepVML: function(markup) {
  3010. var vmlStyle = 'display:inline-block;behavior:url(#default#VML);',
  3011. isIE8 = this.isIE8;
  3012. markup = markup.join('');
  3013. if (isIE8) { // add xmlns and style inline
  3014. markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />');
  3015. if (markup.indexOf('style="') == -1) {
  3016. markup = markup.replace('/>', ' style="'+ vmlStyle +'" />');
  3017. } else {
  3018. markup = markup.replace('style="', 'style="'+ vmlStyle);
  3019. }
  3020. } else { // add namespace
  3021. markup = markup.replace('<', '<hcv:');
  3022. }
  3023. return markup;
  3024. },
  3025. /**
  3026. * Create rotated and aligned text
  3027. * @param {String} str
  3028. * @param {Number} x
  3029. * @param {Number} y
  3030. */
  3031. text: function(str, x, y) {
  3032. var defaultChartStyle = defaultOptions.chart.style;
  3033. return this.createElement('span')
  3034. .attr({
  3035. text: str,
  3036. x: mathRound(x),
  3037. y: mathRound(y)
  3038. })
  3039. .css({
  3040. whiteSpace: 'nowrap',
  3041. fontFamily: defaultChartStyle.fontFamily,
  3042. fontSize: defaultChartStyle.fontSize
  3043. });
  3044. },
  3045. /**
  3046. * Create and return a path element
  3047. * @param {Array} path
  3048. */
  3049. path: function (path) {
  3050. // create the shape
  3051. return this.createElement('shape').attr({
  3052. // subpixel precision down to 0.1 (width and height = 10px)
  3053. coordsize: '100 100',
  3054. d: path
  3055. });
  3056. },
  3057. /**
  3058. * Create and return a circle element. In VML circles are implemented as
  3059. * shapes, which is faster than v:oval
  3060. * @param {Number} x
  3061. * @param {Number} y
  3062. * @param {Number} r
  3063. */
  3064. circle: function(x, y, r) {
  3065. return this.path(this.symbols.circle(x, y, r));
  3066. },
  3067. /**
  3068. * Create a group using an outer div and an inner v:group to allow rotating
  3069. * and flipping. A simple v:group would have problems with positioning
  3070. * child HTML elements and CSS clip.
  3071. *
  3072. * @param {String} name The name of the group
  3073. */
  3074. g: function(name) {
  3075. var wrapper,
  3076. attribs;
  3077. // set the class name
  3078. if (name) {
  3079. attribs = { 'className': PREFIX + name, 'class': PREFIX + name };
  3080. }
  3081. // the div to hold HTML and clipping
  3082. wrapper = this.createElement(DIV).attr(attribs);
  3083. return wrapper;
  3084. },
  3085. /**
  3086. * VML override to create a regular HTML image
  3087. * @param {String} src
  3088. * @param {Number} x
  3089. * @param {Number} y
  3090. * @param {Number} width
  3091. * @param {Number} height
  3092. */
  3093. image: function(src, x, y, width, height) {
  3094. var obj = this.createElement('img')
  3095. .attr({ src: src });
  3096. if (arguments.length > 1) {
  3097. obj.css({
  3098. left: x,
  3099. top: y,
  3100. width: width,
  3101. height: height
  3102. });
  3103. }
  3104. return obj;
  3105. },
  3106. /**
  3107. * VML uses a shape for rect to overcome bugs and rotation problems
  3108. */
  3109. rect: function(x, y, width, height, r, strokeWidth) {
  3110. if (isObject(x)) {
  3111. y = x.y;
  3112. width = x.width;
  3113. height = x.height;
  3114. r = x.r;
  3115. x = x.x;
  3116. }
  3117. var wrapper = this.symbol('rect');
  3118. wrapper.r = r;
  3119. return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
  3120. },
  3121. /**
  3122. * In the VML renderer, each child of an inverted div (group) is inverted
  3123. * @param {Object} element
  3124. * @param {Object} parentNode
  3125. */
  3126. invertChild: function(element, parentNode) {
  3127. var parentStyle = parentNode.style;
  3128. css(element, {
  3129. flip: 'x',
  3130. left: pInt(parentStyle.width) - 10,
  3131. top: pInt(parentStyle.height) - 10,
  3132. rotation: -90
  3133. });
  3134. },
  3135. /**
  3136. * Symbol definitions that override the parent SVG renderer's symbols
  3137. *
  3138. */
  3139. symbols: {
  3140. // VML specific arc function
  3141. arc: function (x, y, radius, options) {
  3142. var start = options.start,
  3143. end = options.end,
  3144. cosStart = mathCos(start),
  3145. sinStart = mathSin(start),
  3146. cosEnd = mathCos(end),
  3147. sinEnd = mathSin(end),
  3148. innerRadius = options.innerR,
  3149. circleCorrection = 0.07 / radius,
  3150. innerCorrection = innerRadius && 0.1 / innerRadius || 0;
  3151. if (end - start === 0) { // no angle, don't show it.
  3152. return ['x'];
  3153. //} else if (end - start == 2 * mathPI) { // full circle
  3154. } else if (2 * mathPI - end + start < circleCorrection) { // full circle
  3155. // empirical correction found by trying out the limits for different radii
  3156. cosEnd = - circleCorrection;
  3157. } else if (end - start < innerCorrection) { // issue #186, another mysterious VML arc problem
  3158. cosEnd = mathCos(start + innerCorrection);
  3159. }
  3160. return [
  3161. 'wa', // clockwise arc to
  3162. x - radius, // left
  3163. y - radius, // top
  3164. x + radius, // right
  3165. y + radius, // bottom
  3166. x + radius * cosStart, // start x
  3167. y + radius * sinStart, // start y
  3168. x + radius * cosEnd, // end x
  3169. y + radius * sinEnd, // end y
  3170. 'at', // anti clockwise arc to
  3171. x - innerRadius, // left
  3172. y - innerRadius, // top
  3173. x + innerRadius, // right
  3174. y + innerRadius, // bottom
  3175. x + innerRadius * cosEnd, // start x
  3176. y + innerRadius * sinEnd, // start y
  3177. x + innerRadius * cosStart, // end x
  3178. y + innerRadius * sinStart, // end y
  3179. 'x', // finish path
  3180. 'e' // close
  3181. ];
  3182. },
  3183. // Add circle symbol path. This performs significantly faster than v:oval.
  3184. circle: function (x, y, r) {
  3185. return [
  3186. 'wa', // clockwisearcto
  3187. x - r, // left
  3188. y - r, // top
  3189. x + r, // right
  3190. y + r, // bottom
  3191. x + r, // start x
  3192. y, // start y
  3193. x + r, // end x
  3194. y, // end y
  3195. //'x', // finish path
  3196. 'e' // close
  3197. ];
  3198. },
  3199. /**
  3200. * Add rectangle symbol path which eases rotation and omits arcsize problems
  3201. * compared to the built-in VML roundrect shape
  3202. *
  3203. * @param {Number} left Left position
  3204. * @param {Number} top Top position
  3205. * @param {Number} r Border radius
  3206. * @param {Object} options Width and height
  3207. */
  3208. rect: function (left, top, r, options) {
  3209. if (!defined(options)) {
  3210. return [];
  3211. }
  3212. var width = options.width,
  3213. height = options.height,
  3214. right = left + width,
  3215. bottom = top + height;
  3216. r = mathMin(r, width, height);
  3217. return [
  3218. M,
  3219. left + r, top,
  3220. L,
  3221. right - r, top,
  3222. 'wa',
  3223. right - 2 * r, top,
  3224. right, top + 2 * r,
  3225. right - r, top,
  3226. right, top + r,
  3227. L,
  3228. right, bottom - r,
  3229. 'wa',
  3230. right - 2 * r, bottom - 2 * r,
  3231. right, bottom,
  3232. right, bottom - r,
  3233. right - r, bottom,
  3234. L,
  3235. left + r, bottom,
  3236. 'wa',
  3237. left, bottom - 2 * r,
  3238. left + 2 * r, bottom,
  3239. left + r, bottom,
  3240. left, bottom - r,
  3241. L,
  3242. left, top + r,
  3243. 'wa',
  3244. left, top,
  3245. left + 2 * r, top + 2 * r,
  3246. left, top + r,
  3247. left + r, top,
  3248. 'x',
  3249. 'e'
  3250. ];
  3251. }
  3252. }
  3253. });
  3254. }
  3255. /* ****************************************************************************
  3256. * *
  3257. * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE *
  3258. * *
  3259. *****************************************************************************/
  3260. /**
  3261. * General renderer
  3262. */
  3263. var Renderer = hasSVG ? SVGRenderer : VMLRenderer;
  3264. /**
  3265. * The chart class
  3266. * @param {Object} options
  3267. * @param {Function} callback Function to run when the chart has loaded
  3268. */
  3269. function Chart (options, callback) {
  3270. defaultXAxisOptions = merge(defaultXAxisOptions, defaultOptions.xAxis);
  3271. defaultYAxisOptions = merge(defaultYAxisOptions, defaultOptions.yAxis);
  3272. defaultOptions.xAxis = defaultOptions.yAxis = null;
  3273. // Handle regular options
  3274. options = merge(defaultOptions, options);
  3275. // Define chart variables
  3276. var optionsChart = options.chart,
  3277. optionsMargin = optionsChart.margin,
  3278. margin = isObject(optionsMargin) ?
  3279. optionsMargin :
  3280. [optionsMargin, optionsMargin, optionsMargin, optionsMargin],
  3281. optionsMarginTop = pick(optionsChart.marginTop, margin[0]),
  3282. optionsMarginRight = pick(optionsChart.marginRight, margin[1]),
  3283. optionsMarginBottom = pick(optionsChart.marginBottom, margin[2]),
  3284. optionsMarginLeft = pick(optionsChart.marginLeft, margin[3]),
  3285. spacingTop = optionsChart.spacingTop,
  3286. spacingRight = optionsChart.spacingRight,
  3287. spacingBottom = optionsChart.spacingBottom,
  3288. spacingLeft = optionsChart.spacingLeft,
  3289. spacingBox,
  3290. chartTitleOptions,
  3291. chartSubtitleOptions,
  3292. plotTop,
  3293. marginRight,
  3294. marginBottom,
  3295. plotLeft,
  3296. axisOffset,
  3297. renderTo,
  3298. renderToClone,
  3299. container,
  3300. containerId,
  3301. containerWidth,
  3302. containerHeight,
  3303. chartWidth,
  3304. chartHeight,
  3305. oldChartWidth,
  3306. oldChartHeight,
  3307. chartBackground,
  3308. plotBackground,
  3309. plotBGImage,
  3310. plotBorder,
  3311. chart = this,
  3312. chartEvents = optionsChart.events,
  3313. runChartClick = chartEvents && !!chartEvents.click,
  3314. eventType,
  3315. isInsidePlot, // function
  3316. tooltip,
  3317. mouseIsDown,
  3318. loadingDiv,
  3319. loadingSpan,
  3320. loadingShown,
  3321. plotHeight,
  3322. plotWidth,
  3323. tracker,
  3324. trackerGroup,
  3325. placeTrackerGroup,
  3326. legend,
  3327. legendWidth,
  3328. legendHeight,
  3329. chartPosition,// = getPosition(container),
  3330. hasCartesianSeries = optionsChart.showAxes,
  3331. isResizing = 0,
  3332. axes = [],
  3333. maxTicks, // handle the greatest amount of ticks on grouped axes
  3334. series = [],
  3335. inverted,
  3336. renderer,
  3337. tooltipTick,
  3338. tooltipInterval,
  3339. hoverX,
  3340. drawChartBox, // function
  3341. getMargins, // function
  3342. resetMargins, // function
  3343. setChartSize, // function
  3344. resize,
  3345. zoom, // function
  3346. zoomOut; // function
  3347. /**
  3348. * Create a new axis object
  3349. * @param {Object} chart
  3350. * @param {Object} options
  3351. */
  3352. function Axis (chart, options) {
  3353. // Define variables
  3354. var isXAxis = options.isX,
  3355. opposite = options.opposite, // needed in setOptions
  3356. horiz = inverted ? !isXAxis : isXAxis,
  3357. side = horiz ?
  3358. (opposite ? 0 /* top */ : 2 /* bottom */) :
  3359. (opposite ? 1 /* right*/ : 3 /* left */ ),
  3360. stacks = {};
  3361. options = merge(
  3362. isXAxis ? defaultXAxisOptions : defaultYAxisOptions,
  3363. [defaultTopAxisOptions, defaultRightAxisOptions,
  3364. defaultBottomAxisOptions, defaultLeftAxisOptions][side],
  3365. options
  3366. );
  3367. var axis = this,
  3368. isDatetimeAxis = options.type == 'datetime',
  3369. offset = options.offset || 0,
  3370. xOrY = isXAxis ? 'x' : 'y',
  3371. axisLength,
  3372. transA, // translation factor
  3373. oldTransA, // used for prerendering
  3374. transB = horiz ? plotLeft : marginBottom, // translation addend
  3375. translate, // fn
  3376. getPlotLinePath, // fn
  3377. axisGroup,
  3378. gridGroup,
  3379. axisLine,
  3380. dataMin,
  3381. dataMax,
  3382. associatedSeries,
  3383. userSetMin,
  3384. userSetMax,
  3385. max = null,
  3386. min = null,
  3387. oldMin,
  3388. oldMax,
  3389. minPadding = options.minPadding,
  3390. maxPadding = options.maxPadding,
  3391. isLinked = defined(options.linkedTo),
  3392. ignoreMinPadding, // can be set to true by a column or bar series
  3393. ignoreMaxPadding,
  3394. usePercentage,
  3395. events = options.events,
  3396. eventType,
  3397. plotLinesAndBands = [],
  3398. tickInterval,
  3399. minorTickInterval,
  3400. magnitude,
  3401. tickPositions, // array containing predefined positions
  3402. ticks = {},
  3403. minorTicks = {},
  3404. alternateBands = {},
  3405. tickAmount,
  3406. labelOffset,
  3407. axisTitleMargin,// = options.title.margin,
  3408. dateTimeLabelFormat,
  3409. categories = options.categories,
  3410. labelFormatter = options.labels.formatter || // can be overwritten by dynamic format
  3411. function() {
  3412. var value = this.value,
  3413. ret;
  3414. if (dateTimeLabelFormat) { // datetime axis
  3415. ret = dateFormat(dateTimeLabelFormat, value);
  3416. } else if (tickInterval % 1000000 === 0) { // use M abbreviation
  3417. ret = (value / 1000000) +'M';
  3418. } else if (tickInterval % 1000 === 0) { // use k abbreviation
  3419. ret = (value / 1000) +'k';
  3420. } else if (!categories && value >= 1000) { // add thousands separators
  3421. ret = numberFormat(value, 0);
  3422. } else { // strings (categories) and small numbers
  3423. ret = value;
  3424. }
  3425. return ret;
  3426. },
  3427. staggerLines = horiz && options.labels.staggerLines,
  3428. reversed = options.reversed,
  3429. tickmarkOffset = (categories && options.tickmarkPlacement == 'between') ? 0.5 : 0;
  3430. /**
  3431. * The Tick class
  3432. */
  3433. function Tick(pos, minor) {
  3434. var tick = this;
  3435. tick.pos = pos;
  3436. tick.minor = minor;
  3437. tick.isNew = true;
  3438. if (!minor) {
  3439. tick.addLabel();
  3440. }
  3441. }
  3442. Tick.prototype = {
  3443. /**
  3444. * Write the tick label
  3445. */
  3446. addLabel: function() {
  3447. var pos = this.pos,
  3448. labelOptions = options.labels,
  3449. str,
  3450. withLabel = !((pos == min && !pick(options.showFirstLabel, 1)) ||
  3451. (pos == max && !pick(options.showLastLabel, 0))),
  3452. width = categories && horiz && categories.length &&
  3453. !labelOptions.step && !labelOptions.staggerLines &&
  3454. !labelOptions.rotation &&
  3455. plotWidth / categories.length ||
  3456. !horiz && plotWidth / 2,
  3457. css,
  3458. label = this.label;
  3459. // get the string
  3460. str = labelFormatter.call({
  3461. isFirst: pos == tickPositions[0],
  3462. isLast: pos == tickPositions[tickPositions.length - 1],
  3463. dateTimeLabelFormat: dateTimeLabelFormat,
  3464. value: (categories && categories[pos] ? categories[pos] : pos)
  3465. });
  3466. // prepare CSS
  3467. css = width && { width: (width - 2 * (labelOptions.padding || 10)) +PX };
  3468. css = extend(css, labelOptions.style);
  3469. // first call
  3470. if (label === UNDEFINED) {
  3471. this.label =
  3472. defined(str) && withLabel && labelOptions.enabled ?
  3473. renderer.text(
  3474. str,
  3475. 0,
  3476. 0
  3477. )
  3478. .attr({
  3479. align: labelOptions.align,
  3480. rotation: labelOptions.rotation
  3481. })
  3482. // without position absolute, IE export sometimes is wrong
  3483. .css(css)
  3484. .add(axisGroup):
  3485. null;
  3486. // update
  3487. } else if (label) {
  3488. label.attr({ text: str })
  3489. .css(css);
  3490. }
  3491. },
  3492. /**
  3493. * Get the offset height or width of the label
  3494. */
  3495. getLabelSize: function() {
  3496. var label = this.label;
  3497. return label ?
  3498. ((this.labelBBox = label.getBBox()))[horiz ? 'height' : 'width'] :
  3499. 0;
  3500. },
  3501. /**
  3502. * Put everything in place
  3503. *
  3504. * @param index {Number}
  3505. * @param old {Boolean} Use old coordinates to prepare an animation into new position
  3506. */
  3507. render: function(index, old) {
  3508. var tick = this,
  3509. major = !tick.minor,
  3510. label = tick.label,
  3511. pos = tick.pos,
  3512. labelOptions = options.labels,
  3513. gridLine = tick.gridLine,
  3514. gridLineWidth = major ? options.gridLineWidth : options.minorGridLineWidth,
  3515. gridLineColor = major ? options.gridLineColor : options.minorGridLineColor,
  3516. dashStyle = major ?
  3517. options.gridLineDashStyle :
  3518. options.minorGridLineDashStyle,
  3519. gridLinePath,
  3520. mark = tick.mark,
  3521. markPath,
  3522. tickLength = major ? options.tickLength : options.minorTickLength,
  3523. tickWidth = major ? options.tickWidth : (options.minorTickWidth || 0),
  3524. tickColor = major ? options.tickColor : options.minorTickColor,
  3525. tickPosition = major ? options.tickPosition : options.minorTickPosition,
  3526. step = labelOptions.step,
  3527. cHeight = old && oldChartHeight || chartHeight,
  3528. attribs,
  3529. x,
  3530. y;
  3531. // get x and y position for ticks and labels
  3532. x = horiz ?
  3533. translate(pos + tickmarkOffset, null, null, old) + transB :
  3534. plotLeft + offset + (opposite ? (old && oldChartWidth || chartWidth) - marginRight - plotLeft : 0);
  3535. y = horiz ?
  3536. cHeight - marginBottom + offset - (opposite ? plotHeight : 0) :
  3537. cHeight - translate(pos + tickmarkOffset, null, null, old) - transB;
  3538. // create the grid line
  3539. if (gridLineWidth) {
  3540. gridLinePath = getPlotLinePath(pos + tickmarkOffset, gridLineWidth, old);
  3541. if (gridLine === UNDEFINED) {
  3542. attribs = {
  3543. stroke: gridLineColor,
  3544. 'stroke-width': gridLineWidth
  3545. };
  3546. if (dashStyle) {
  3547. attribs.dashstyle = dashStyle;
  3548. }
  3549. tick.gridLine = gridLine =
  3550. gridLineWidth ?
  3551. renderer.path(gridLinePath)
  3552. .attr(attribs).add(gridGroup) :
  3553. null;
  3554. }
  3555. if (gridLine && gridLinePath) {
  3556. gridLine.animate({
  3557. d: gridLinePath
  3558. });
  3559. }
  3560. }
  3561. // create the tick mark
  3562. if (tickWidth) {
  3563. // negate the length
  3564. if (tickPosition == 'inside') {
  3565. tickLength = -tickLength;
  3566. }
  3567. if (opposite) {
  3568. tickLength = -tickLength;
  3569. }
  3570. markPath = renderer.crispLine([
  3571. M,
  3572. x,
  3573. y,
  3574. L,
  3575. x + (horiz ? 0 : -tickLength),
  3576. y + (horiz ? tickLength : 0)
  3577. ], tickWidth);
  3578. if (mark) { // updating
  3579. mark.animate({
  3580. d: markPath
  3581. });
  3582. } else { // first time
  3583. tick.mark = renderer.path(
  3584. markPath
  3585. ).attr({
  3586. stroke: tickColor,
  3587. 'stroke-width': tickWidth
  3588. }).add(axisGroup);
  3589. }
  3590. }
  3591. // the label is created on init - now move it into place
  3592. if (label) {
  3593. x = x + labelOptions.x - (tickmarkOffset && horiz ?
  3594. tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
  3595. y = y + labelOptions.y - (tickmarkOffset && !horiz ?
  3596. tickmarkOffset * transA * (reversed ? 1 : -1) : 0);
  3597. // vertically centered
  3598. if (!defined(labelOptions.y)) {
  3599. y += parseInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;
  3600. }
  3601. // correct for staggered labels
  3602. if (staggerLines) {
  3603. y += (index % staggerLines) * 16;
  3604. }
  3605. // apply step
  3606. if (step) {
  3607. // show those indices dividable by step
  3608. label[index % step ? 'hide' : 'show']();
  3609. }
  3610. label[tick.isNew ? 'attr' : 'animate']({
  3611. x: x,
  3612. y: y
  3613. });
  3614. }
  3615. tick.isNew = false;
  3616. },
  3617. /**
  3618. * Destructor for the tick prototype
  3619. */
  3620. destroy: function() {
  3621. var tick = this,
  3622. n;
  3623. for (n in tick) {
  3624. if (tick[n] && tick[n].destroy) {
  3625. tick[n].destroy();
  3626. }
  3627. }
  3628. }
  3629. };
  3630. /**
  3631. * The object wrapper for plot lines and plot bands
  3632. * @param {Object} options
  3633. */
  3634. function PlotLineOrBand(options) {
  3635. var plotLine = this;
  3636. if (options) {
  3637. plotLine.options = options;
  3638. plotLine.id = options.id;
  3639. }
  3640. //plotLine.render()
  3641. return plotLine;
  3642. }
  3643. PlotLineOrBand.prototype = {
  3644. /**
  3645. * Render the plot line or plot band. If it is already existing,
  3646. * move it.
  3647. */
  3648. render: function () {
  3649. var plotLine = this,
  3650. options = plotLine.options,
  3651. optionsLabel = options.label,
  3652. label = plotLine.label,
  3653. width = options.width,
  3654. to = options.to,
  3655. toPath, // bands only
  3656. from = options.from,
  3657. dashStyle = options.dashStyle,
  3658. svgElem = plotLine.svgElem,
  3659. path = [],
  3660. addEvent,
  3661. eventType,
  3662. xs,
  3663. ys,
  3664. x,
  3665. y,
  3666. color = options.color,
  3667. zIndex = options.zIndex,
  3668. events = options.events,
  3669. attribs;
  3670. // plot line
  3671. if (width) {
  3672. path = getPlotLinePath(options.value, width);
  3673. attribs = {
  3674. stroke: color,
  3675. 'stroke-width': width
  3676. };
  3677. if (dashStyle) {
  3678. attribs.dashstyle = dashStyle;
  3679. }
  3680. }
  3681. // plot band
  3682. else if (defined(from) && defined(to)) {
  3683. // keep within plot area
  3684. from = mathMax(from, min);
  3685. to = mathMin(to, max);
  3686. toPath = getPlotLinePath(to);
  3687. path = getPlotLinePath(from);
  3688. if (path && toPath) {
  3689. path.push(
  3690. toPath[4],
  3691. toPath[5],
  3692. toPath[1],
  3693. toPath[2]
  3694. );
  3695. } else { // outside the axis area
  3696. path = null;
  3697. }
  3698. attribs = {
  3699. fill: color
  3700. };
  3701. } else {
  3702. return;
  3703. }
  3704. // zIndex
  3705. if (defined(zIndex)) {
  3706. attribs.zIndex = zIndex;
  3707. }
  3708. // common for lines and bands
  3709. if (svgElem) {
  3710. if (path) {
  3711. svgElem.animate({
  3712. d: path
  3713. }, null, svgElem.onGetPath);
  3714. } else {
  3715. svgElem.hide();
  3716. svgElem.onGetPath = function() {
  3717. svgElem.show();
  3718. }
  3719. }
  3720. } else if (path && path.length) {
  3721. plotLine.svgElem = svgElem = renderer.path(path)
  3722. .attr(attribs).add();
  3723. // events
  3724. if (events) {
  3725. addEvent = function(eventType) {
  3726. svgElem.on(eventType, function(e) {
  3727. events[eventType].apply(plotLine, [e]);
  3728. });
  3729. };
  3730. for (eventType in events) {
  3731. addEvent(eventType);
  3732. }
  3733. }
  3734. }
  3735. // the plot band/line label
  3736. if (optionsLabel && defined(optionsLabel.text) && path && path.length && plotWidth > 0 && plotHeight > 0) {
  3737. // apply defaults
  3738. optionsLabel = merge({
  3739. align: horiz && toPath && 'center',
  3740. x: horiz ? !toPath && 4 : 10,
  3741. verticalAlign : !horiz && toPath && 'middle',
  3742. y: horiz ? toPath ? 16 : 10 : toPath ? 6 : -4,
  3743. rotation: horiz && !toPath && 90
  3744. }, optionsLabel);
  3745. // add the SVG element
  3746. if (!label) {
  3747. plotLine.label = label = renderer.text(
  3748. optionsLabel.text,
  3749. 0,
  3750. 0
  3751. )
  3752. .attr({
  3753. align: optionsLabel.textAlign || optionsLabel.align,
  3754. rotation: optionsLabel.rotation,
  3755. zIndex: zIndex
  3756. })
  3757. .css(optionsLabel.style)
  3758. .add();
  3759. }
  3760. // get the bounding box and align the label
  3761. xs = [path[1], path[4], path[6] || path[1]];
  3762. ys = [path[2], path[5], path[7] || path[2]];
  3763. x = mathMin.apply(math, xs);
  3764. y = mathMin.apply(math, ys);
  3765. label.align(optionsLabel, false, {
  3766. x: x,
  3767. y: y,
  3768. width: mathMax.apply(math, xs) - x,
  3769. height: mathMax.apply(math, ys) - y
  3770. });
  3771. label.show();
  3772. } else if (label) { // move out of sight
  3773. label.hide();
  3774. }
  3775. // chainable
  3776. return plotLine;
  3777. },
  3778. /**
  3779. * Remove the plot line or band
  3780. */
  3781. destroy: function() {
  3782. var obj = this,
  3783. n;
  3784. for (n in obj) {
  3785. if (obj[n] && obj[n].destroy) {
  3786. obj[n].destroy(); // destroy SVG wrappers
  3787. }
  3788. delete obj[n];
  3789. }
  3790. // remove it from the lookup
  3791. erase(plotLinesAndBands, obj);
  3792. }
  3793. };
  3794. /**
  3795. * Get the minimum and maximum for the series of each axis
  3796. */
  3797. function getSeriesExtremes() {
  3798. var posStack = [],
  3799. negStack = [],
  3800. run;
  3801. // reset dataMin and dataMax in case we're redrawing
  3802. dataMin = dataMax = null;
  3803. // get an overview of what series are associated with this axis
  3804. associatedSeries = [];
  3805. each(series, function(serie) {
  3806. run = false;
  3807. // match this axis against the series' given or implicated axis
  3808. each(['xAxis', 'yAxis'], function(strAxis) {
  3809. if (
  3810. // the series is a cartesian type, and...
  3811. serie.isCartesian &&
  3812. // we're in the right x or y dimension, and...
  3813. (strAxis == 'xAxis' && isXAxis || strAxis == 'yAxis' && !isXAxis) && (
  3814. // the axis number is given in the options and matches this axis index, or
  3815. (serie.options[strAxis] == options.index) ||
  3816. // the axis index is not given
  3817. (serie.options[strAxis] === UNDEFINED && options.index === 0)
  3818. )
  3819. ) {
  3820. serie[strAxis] = axis;
  3821. associatedSeries.push(serie);
  3822. // the series is visible, run the min/max detection
  3823. run = true;
  3824. }
  3825. });
  3826. // ignore hidden series if opted
  3827. if (!serie.visible && optionsChart.ignoreHiddenSeries) {
  3828. run = false;
  3829. }
  3830. if (run) {
  3831. var stacking,
  3832. posPointStack,
  3833. negPointStack,
  3834. stackKey,
  3835. negKey;
  3836. if (!isXAxis) {
  3837. stacking = serie.options.stacking;
  3838. usePercentage = stacking == 'percent';
  3839. // create a stack for this particular series type
  3840. if (stacking) {
  3841. stackKey = serie.type + pick(serie.options.stack, '');
  3842. negKey = '-'+ stackKey;
  3843. serie.stackKey = stackKey; // used in translate
  3844. posPointStack = posStack[stackKey] || []; // contains the total values for each x
  3845. posStack[stackKey] = posPointStack;
  3846. negPointStack = negStack[negKey] || [];
  3847. negStack[negKey] = negPointStack;
  3848. }
  3849. if (usePercentage) {
  3850. dataMin = 0;
  3851. dataMax = 99;
  3852. }
  3853. }
  3854. if (serie.isCartesian) { // line, column etc. need axes, pie doesn't
  3855. each(serie.data, function(point, i) {
  3856. var pointX = point.x,
  3857. pointY = point.y,
  3858. isNegative = pointY < 0,
  3859. pointStack = isNegative ? negPointStack : posPointStack,
  3860. key = isNegative ? negKey : stackKey,
  3861. totalPos,
  3862. pointLow;
  3863. // initial values
  3864. if (dataMin === null) {
  3865. // start out with the first point
  3866. dataMin = dataMax = point[xOrY];
  3867. }
  3868. // x axis
  3869. if (isXAxis) {
  3870. if (pointX > dataMax) {
  3871. dataMax = pointX;
  3872. } else if (pointX < dataMin) {
  3873. dataMin = pointX;
  3874. }
  3875. }
  3876. // y axis
  3877. else if (defined(pointY)) {
  3878. if (stacking) {
  3879. pointStack[pointX] =
  3880. defined(pointStack[pointX]) ?
  3881. pointStack[pointX] + pointY : pointY;
  3882. }
  3883. totalPos = pointStack ? pointStack[pointX] : pointY;
  3884. pointLow = pick(point.low, totalPos);
  3885. if (!usePercentage) {
  3886. if (totalPos > dataMax) {
  3887. dataMax = totalPos;
  3888. } else if (pointLow < dataMin) {
  3889. dataMin = pointLow;
  3890. }
  3891. }
  3892. if (stacking) {
  3893. // add the series
  3894. if (!stacks[key]) {
  3895. stacks[key] = {};
  3896. }
  3897. stacks[key][pointX] = {
  3898. total: totalPos,
  3899. cum: totalPos
  3900. };
  3901. }
  3902. }
  3903. });
  3904. // For column, areas and bars, set the minimum automatically to zero
  3905. // and prevent that minPadding is added in setScale
  3906. if (/(area|column|bar)/.test(serie.type) && !isXAxis) {
  3907. if (dataMin >= 0) {
  3908. dataMin = 0;
  3909. ignoreMinPadding = true;
  3910. } else if (dataMax < 0) {
  3911. dataMax = 0;
  3912. ignoreMaxPadding = true;
  3913. }
  3914. }
  3915. }
  3916. }
  3917. });
  3918. }
  3919. /**
  3920. * Translate from axis value to pixel position on the chart, or back
  3921. *
  3922. */
  3923. translate = function(val, backwards, cvsCoord, old) {
  3924. var sign = 1,
  3925. cvsOffset = 0,
  3926. localA = old ? oldTransA : transA,
  3927. localMin = old ? oldMin : min,
  3928. returnValue;
  3929. if (!localA) {
  3930. localA = transA;
  3931. }
  3932. if (cvsCoord) {
  3933. sign *= -1; // canvas coordinates inverts the value
  3934. cvsOffset = axisLength;
  3935. }
  3936. if (reversed) { // reversed axis
  3937. sign *= -1;
  3938. cvsOffset -= sign * axisLength;
  3939. }
  3940. if (backwards) { // reverse translation
  3941. if (reversed) {
  3942. val = axisLength - val;
  3943. }
  3944. returnValue = val / localA + localMin; // from chart pixel to value
  3945. } else { // normal translation
  3946. returnValue = sign * (val - localMin) * localA + cvsOffset; // from value to chart pixel
  3947. }
  3948. return returnValue;
  3949. };
  3950. /**
  3951. * Create the path for a plot line that goes from the given value on
  3952. * this axis, across the plot to the opposite side
  3953. * @param {Number} value
  3954. * @param {Number} lineWidth Used for calculation crisp line
  3955. * @param {Number] old Use old coordinates (for resizing and rescaling)
  3956. */
  3957. getPlotLinePath = function(value, lineWidth, old) {
  3958. var x1,
  3959. y1,
  3960. x2,
  3961. y2,
  3962. translatedValue = translate(value, null, null, old),
  3963. cHeight = old && oldChartHeight || chartHeight,
  3964. cWidth = old && oldChartWidth || chartWidth,
  3965. skip;
  3966. x1 = x2 = mathRound(translatedValue + transB);
  3967. y1 = y2 = mathRound(cHeight - translatedValue - transB);
  3968. if (isNaN(translatedValue)) { // no min or max
  3969. skip = true;
  3970. } else if (horiz) {
  3971. y1 = plotTop;
  3972. y2 = cHeight - marginBottom;
  3973. if (x1 < plotLeft || x1 > plotLeft + plotWidth) {
  3974. skip = true;
  3975. }
  3976. } else {
  3977. x1 = plotLeft;
  3978. x2 = cWidth - marginRight;
  3979. if (y1 < plotTop || y1 > plotTop + plotHeight) {
  3980. skip = true;
  3981. }
  3982. }
  3983. return skip ?
  3984. null :
  3985. renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 0);
  3986. };
  3987. /**
  3988. * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
  3989. * @param {Number} interval
  3990. */
  3991. function normalizeTickInterval(interval, multiples) {
  3992. var normalized;
  3993. // round to a tenfold of 1, 2, 2.5 or 5
  3994. magnitude = multiples ? 1 : math.pow(10, mathFloor(math.log(interval) / math.LN10));
  3995. normalized = interval / magnitude;
  3996. // multiples for a linear scale
  3997. if (!multiples) {
  3998. multiples = [1, 2, 2.5, 5, 10];
  3999. //multiples = [1, 2, 2.5, 4, 5, 7.5, 10];
  4000. // the allowDecimals option
  4001. if (options.allowDecimals === false) {
  4002. if (magnitude == 1) {
  4003. multiples = [1, 2, 5, 10];
  4004. } else if (magnitude <= 0.1) {
  4005. multiples = [1 / magnitude];
  4006. }
  4007. }
  4008. }
  4009. // normalize the interval to the nearest multiple
  4010. for (var i = 0; i < multiples.length; i++) {
  4011. interval = multiples[i];
  4012. if (normalized <= (multiples[i] + (multiples[i+1] || multiples[i])) / 2) {
  4013. break;
  4014. }
  4015. }
  4016. // multiply back to the correct magnitude
  4017. interval *= magnitude;
  4018. return interval;
  4019. }
  4020. /**
  4021. * Set the tick positions to a time unit that makes sense, for example
  4022. * on the first of each month or on every Monday.
  4023. */
  4024. function setDateTimeTickPositions() {
  4025. tickPositions = [];
  4026. var i,
  4027. useUTC = defaultOptions.global.useUTC,
  4028. oneSecond = 1000 / timeFactor,
  4029. oneMinute = 60000 / timeFactor,
  4030. oneHour = 3600000 / timeFactor,
  4031. oneDay = 24 * 3600000 / timeFactor,
  4032. oneWeek = 7 * 24 * 3600000 / timeFactor,
  4033. oneMonth = 30 * 24 * 3600000 / timeFactor,
  4034. oneYear = 31556952000 / timeFactor,
  4035. units = [[
  4036. 'second', // unit name
  4037. oneSecond, // fixed incremental unit
  4038. [1, 2, 5, 10, 15, 30] // allowed multiples
  4039. ], [
  4040. 'minute', // unit name
  4041. oneMinute, // fixed incremental unit
  4042. [1, 2, 5, 10, 15, 30] // allowed multiples
  4043. ], [
  4044. 'hour', // unit name
  4045. oneHour, // fixed incremental unit
  4046. [1, 2, 3, 4, 6, 8, 12] // allowed multiples
  4047. ], [
  4048. 'day', // unit name
  4049. oneDay, // fixed incremental unit
  4050. [1, 2] // allowed multiples
  4051. ], [
  4052. 'week', // unit name
  4053. oneWeek, // fixed incremental unit
  4054. [1, 2] // allowed multiples
  4055. ], [
  4056. 'month',
  4057. oneMonth,
  4058. [1, 2, 3, 4, 6]
  4059. ], [
  4060. 'year',
  4061. oneYear,
  4062. null
  4063. ]],
  4064. unit = units[6], // default unit is years
  4065. interval = unit[1],
  4066. multiples = unit[2];
  4067. // loop through the units to find the one that best fits the tickInterval
  4068. for (i = 0; i < units.length; i++) {
  4069. unit = units[i];
  4070. interval = unit[1];
  4071. multiples = unit[2];
  4072. if (units[i+1]) {
  4073. // lessThan is in the middle between the highest multiple and the next unit.
  4074. var lessThan = (interval * multiples[multiples.length - 1] +
  4075. units[i + 1][1]) / 2;
  4076. // break and keep the current unit
  4077. if (tickInterval <= lessThan) {
  4078. break;
  4079. }
  4080. }
  4081. }
  4082. // prevent 2.5 years intervals, though 25, 250 etc. are allowed
  4083. if (interval == oneYear && tickInterval < 5 * interval) {
  4084. multiples = [1, 2, 5];
  4085. }
  4086. // get the minimum value by flooring the date
  4087. var multitude = normalizeTickInterval(tickInterval / interval, multiples),
  4088. minYear, // used in months and years as a basis for Date.UTC()
  4089. minDate = new Date(min * timeFactor);
  4090. minDate.setMilliseconds(0);
  4091. if (interval >= oneSecond) { // second
  4092. minDate.setSeconds(interval >= oneMinute ? 0 :
  4093. multitude * mathFloor(minDate.getSeconds() / multitude));
  4094. }
  4095. if (interval >= oneMinute) { // minute
  4096. minDate[setMinutes](interval >= oneHour ? 0 :
  4097. multitude * mathFloor(minDate[getMinutes]() / multitude));
  4098. }
  4099. if (interval >= oneHour) { // hour
  4100. minDate[setHours](interval >= oneDay ? 0 :
  4101. multitude * mathFloor(minDate[getHours]() / multitude));
  4102. }
  4103. if (interval >= oneDay) { // day
  4104. minDate[setDate](interval >= oneMonth ? 1 :
  4105. multitude * mathFloor(minDate[getDate]() / multitude));
  4106. }
  4107. if (interval >= oneMonth) { // month
  4108. minDate[setMonth](interval >= oneYear ? 0 :
  4109. multitude * mathFloor(minDate[getMonth]() / multitude));
  4110. minYear = minDate[getFullYear]();
  4111. }
  4112. if (interval >= oneYear) { // year
  4113. minYear -= minYear % multitude;
  4114. minDate[setFullYear](minYear);
  4115. }
  4116. // week is a special case that runs outside the hierarchy
  4117. if (interval == oneWeek) {
  4118. // get start of current week, independent of multitude
  4119. minDate[setDate](minDate[getDate]() - minDate[getDay]() +
  4120. options.startOfWeek);
  4121. }
  4122. // get tick positions
  4123. i = 1; // prevent crash just in case
  4124. minYear = minDate[getFullYear]();
  4125. var time = minDate.getTime() / timeFactor,
  4126. minMonth = minDate[getMonth](),
  4127. minDateDate = minDate[getDate]();
  4128. // iterate and add tick positions at appropriate values
  4129. while (time < max && i < plotWidth) {
  4130. tickPositions.push(time);
  4131. // if the interval is years, use Date.UTC to increase years
  4132. if (interval == oneYear) {
  4133. time = makeTime(minYear + i * multitude, 0) / timeFactor;
  4134. // if the interval is months, use Date.UTC to increase months
  4135. } else if (interval == oneMonth) {
  4136. time = makeTime(minYear, minMonth + i * multitude) / timeFactor;
  4137. // if we're using global time, the interval is not fixed as it jumps
  4138. // one hour at the DST crossover
  4139. } else if (!useUTC && (interval == oneDay || interval == oneWeek)) {
  4140. time = makeTime(minYear, minMonth, minDateDate +
  4141. i * multitude * (interval == oneDay ? 1 : 7));
  4142. // else, the interval is fixed and we use simple addition
  4143. } else {
  4144. time += interval * multitude;
  4145. }
  4146. i++;
  4147. }
  4148. // push the last time
  4149. tickPositions.push(time);
  4150. // dynamic label formatter
  4151. dateTimeLabelFormat = options.dateTimeLabelFormats[unit[0]];
  4152. }
  4153. /**
  4154. * Fix JS round off float errors
  4155. * @param {Number} num
  4156. */
  4157. function correctFloat(num) {
  4158. var invMag, ret = num;
  4159. if (defined(magnitude)) {
  4160. invMag = (magnitude < 1 ? mathRound(1 / magnitude) : 1) * 10;
  4161. ret = mathRound(num * invMag) / invMag;
  4162. }
  4163. return ret;
  4164. }
  4165. /**
  4166. * Set the tick positions of a linear axis to round values like whole tens or every five.
  4167. */
  4168. function setLinearTickPositions() {
  4169. var i,
  4170. roundedMin = mathFloor(min / tickInterval) * tickInterval,
  4171. roundedMax = mathCeil(max / tickInterval) * tickInterval;
  4172. tickPositions = [];
  4173. // populate the intermediate values
  4174. i = correctFloat(roundedMin);
  4175. while (i <= roundedMax) {
  4176. tickPositions.push(i);
  4177. i = correctFloat(i + tickInterval);
  4178. }
  4179. }
  4180. /**
  4181. * Set the tick positions to round values and optionally extend the extremes
  4182. * to the nearest tick
  4183. */
  4184. function setTickPositions(secondPass) {
  4185. var length,
  4186. catPad,
  4187. linkedParent,
  4188. linkedParentExtremes,
  4189. tickIntervalOption = options.tickInterval,
  4190. tickPixelIntervalOption = options.tickPixelInterval,
  4191. maxZoom = options.maxZoom || (
  4192. isXAxis ?
  4193. mathMin(chart.smallestInterval * 5, dataMax - dataMin) :
  4194. null
  4195. ),
  4196. zoomOffset;
  4197. axisLength = horiz ? plotWidth : plotHeight;
  4198. // linked axis gets the extremes from the parent axis
  4199. if (isLinked) {
  4200. linkedParent = chart[isXAxis ? 'xAxis' : 'yAxis'][options.linkedTo];
  4201. linkedParentExtremes = linkedParent.getExtremes();
  4202. min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin);
  4203. max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax);
  4204. }
  4205. // initial min and max from the extreme data values
  4206. else {
  4207. min = pick(userSetMin, options.min, dataMin);
  4208. max = pick(userSetMax, options.max, dataMax);
  4209. }
  4210. // maxZoom exceeded, just center the selection
  4211. if (max - min < maxZoom) {
  4212. zoomOffset = (maxZoom - max + min) / 2;
  4213. // if min and max options have been set, don't go beyond it
  4214. min = mathMax(min - zoomOffset, pick(options.min, min - zoomOffset), dataMin);
  4215. max = mathMin(min + maxZoom, pick(options.max, min + maxZoom), dataMax);
  4216. }
  4217. // pad the values to get clear of the chart's edges
  4218. if (!categories && !usePercentage && !isLinked && defined(min) && defined(max)) {
  4219. length = (max - min) || 1;
  4220. if (!defined(options.min) && !defined(userSetMin) && minPadding && (dataMin < 0 || !ignoreMinPadding)) {
  4221. min -= length * minPadding;
  4222. }
  4223. if (!defined(options.max) && !defined(userSetMax) && maxPadding && (dataMax > 0 || !ignoreMaxPadding)) {
  4224. max += length * maxPadding;
  4225. }
  4226. }
  4227. // get tickInterval
  4228. if (min == max) {
  4229. tickInterval = 1;
  4230. } else if (isLinked && !tickIntervalOption &&
  4231. tickPixelIntervalOption == linkedParent.options.tickPixelInterval) {
  4232. tickInterval = linkedParent.tickInterval;
  4233. } else {
  4234. tickInterval = pick(
  4235. tickIntervalOption,
  4236. categories ? // for categoried axis, 1 is default, for linear axis use tickPix
  4237. 1 :
  4238. (max - min) * tickPixelIntervalOption / axisLength
  4239. );
  4240. }
  4241. if (!isDatetimeAxis && !defined(options.tickInterval)) { // linear
  4242. tickInterval = normalizeTickInterval(tickInterval);
  4243. }
  4244. axis.tickInterval = tickInterval; // record for linked axis
  4245. // get minorTickInterval
  4246. minorTickInterval = options.minorTickInterval === 'auto' && tickInterval ?
  4247. tickInterval / 5 : options.minorTickInterval;
  4248. // find the tick positions
  4249. if (isDatetimeAxis) {
  4250. setDateTimeTickPositions();
  4251. } else {
  4252. setLinearTickPositions();
  4253. }
  4254. if (!isLinked) {
  4255. // pad categorised axis to nearest half unit
  4256. if (categories || (isXAxis && chart.hasColumn)) {
  4257. catPad = (categories ? 1 : tickInterval) * 0.5;
  4258. if (categories || !defined(pick(options.min, userSetMin))) {
  4259. min -= catPad;
  4260. }
  4261. if (categories || !defined(pick(options.max, userSetMax))) {
  4262. max += catPad;
  4263. }
  4264. }
  4265. // reset min/max or remove extremes based on start/end on tick
  4266. var roundedMin = tickPositions[0],
  4267. roundedMax = tickPositions[tickPositions.length - 1];
  4268. if (options.startOnTick) {
  4269. min = roundedMin;
  4270. } else if (min > roundedMin) {
  4271. tickPositions.shift();
  4272. }
  4273. if (options.endOnTick) {
  4274. max = roundedMax;
  4275. } else if (max < roundedMax) {
  4276. tickPositions.pop();
  4277. }
  4278. // record the greatest number of ticks for multi axis
  4279. if (!maxTicks) { // first call, or maxTicks have been reset after a zoom operation
  4280. maxTicks = {
  4281. x: 0,
  4282. y: 0
  4283. };
  4284. }
  4285. if (!isDatetimeAxis && tickPositions.length > maxTicks[xOrY]) {
  4286. maxTicks[xOrY] = tickPositions.length;
  4287. }
  4288. }
  4289. }
  4290. /**
  4291. * When using multiple axes, adjust the number of ticks to match the highest
  4292. * number of ticks in that group
  4293. */
  4294. function adjustTickAmount() {
  4295. if (maxTicks && !isDatetimeAxis && !categories && !isLinked) { // only apply to linear scale
  4296. var oldTickAmount = tickAmount,
  4297. calculatedTickAmount = tickPositions.length;
  4298. // set the axis-level tickAmount to use below
  4299. tickAmount = maxTicks[xOrY];
  4300. if (calculatedTickAmount < tickAmount) {
  4301. while (tickPositions.length < tickAmount) {
  4302. tickPositions.push( correctFloat(
  4303. tickPositions[tickPositions.length - 1] + tickInterval
  4304. ));
  4305. }
  4306. transA *= (calculatedTickAmount - 1) / (tickAmount - 1);
  4307. max = tickPositions[tickPositions.length - 1];
  4308. }
  4309. if (defined(oldTickAmount) && tickAmount != oldTickAmount) {
  4310. axis.isDirty = true;
  4311. }
  4312. }
  4313. }
  4314. /**
  4315. * Set the scale based on data min and max, user set min and max or options
  4316. *
  4317. */
  4318. function setScale() {
  4319. var type,
  4320. i;
  4321. oldMin = min;
  4322. oldMax = max;
  4323. // get data extremes if needed
  4324. getSeriesExtremes();
  4325. // get fixed positions based on tickInterval
  4326. setTickPositions();
  4327. // the translation factor used in translate function
  4328. oldTransA = transA;
  4329. transA = axisLength / ((max - min) || 1);
  4330. // reset stacks
  4331. if (!isXAxis) {
  4332. for (type in stacks) {
  4333. for (i in stacks[type]) {
  4334. stacks[type][i].cum = stacks[type][i].total;
  4335. }
  4336. }
  4337. }
  4338. // mark as dirty if it is not already set to dirty and extremes have changed
  4339. if (!axis.isDirty) {
  4340. axis.isDirty = (min != oldMin || max != oldMax);
  4341. }
  4342. }
  4343. /**
  4344. * Set the extremes and optionally redraw
  4345. * @param {Number} newMin
  4346. * @param {Number} newMax
  4347. * @param {Boolean} redraw
  4348. * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
  4349. * configuration
  4350. *
  4351. */
  4352. function setExtremes(newMin, newMax, redraw, animation) {
  4353. redraw = pick(redraw, true); // defaults to true
  4354. fireEvent(axis, 'setExtremes', { // fire an event to enable syncing of multiple charts
  4355. min: newMin,
  4356. max: newMax
  4357. }, function() { // the default event handler
  4358. userSetMin = newMin;
  4359. userSetMax = newMax;
  4360. // redraw
  4361. if (redraw) {
  4362. chart.redraw(animation);
  4363. }
  4364. });
  4365. }
  4366. /**
  4367. * Get the actual axis extremes
  4368. */
  4369. function getExtremes() {
  4370. return {
  4371. min: min,
  4372. max: max,
  4373. dataMin: dataMin,
  4374. dataMax: dataMax
  4375. };
  4376. }
  4377. /**
  4378. * Get the zero plane either based on zero or on the min or max value.
  4379. * Used in bar and area plots
  4380. */
  4381. function getThreshold(threshold) {
  4382. if (min > threshold) {
  4383. threshold = min;
  4384. } else if (max < threshold) {
  4385. threshold = max;
  4386. }
  4387. return translate(threshold, 0, 1);
  4388. }
  4389. /**
  4390. * Add a plot band or plot line after render time
  4391. *
  4392. * @param options {Object} The plotBand or plotLine configuration object
  4393. */
  4394. function addPlotBandOrLine(options) {
  4395. var obj = new PlotLineOrBand(options).render();
  4396. plotLinesAndBands.push(obj);
  4397. return obj;
  4398. }
  4399. /**
  4400. * Render the tick labels to a preliminary position to get their sizes
  4401. */
  4402. function getOffset() {
  4403. var hasData = associatedSeries.length && defined(min) && defined(max),
  4404. titleOffset = 0,
  4405. titleMargin = 0,
  4406. axisTitleOptions = options.title,
  4407. labelOptions = options.labels,
  4408. directionFactor = [-1, 1, 1, -1][side];
  4409. if (!axisGroup) {
  4410. axisGroup = renderer.g('axis')
  4411. .attr({ zIndex: 7 })
  4412. .add();
  4413. gridGroup = renderer.g('grid')
  4414. .attr({ zIndex: 1 })
  4415. .add();
  4416. }
  4417. labelOffset = 0; // reset
  4418. if (hasData || isLinked) {
  4419. each(tickPositions, function(pos) {
  4420. if (!ticks[pos]) {
  4421. ticks[pos] = new Tick(pos);
  4422. } else {
  4423. ticks[pos].addLabel(); // update labels depending on tick interval
  4424. }
  4425. // left side must be align: right and right side must have align: left for labels
  4426. if (side === 0 || side == 2 || { 1: 'left', 3: 'right' }[side] == labelOptions.align) {
  4427. // get the highest offset
  4428. labelOffset = mathMax(
  4429. ticks[pos].getLabelSize(),
  4430. labelOffset
  4431. );
  4432. }
  4433. });
  4434. if (staggerLines) {
  4435. labelOffset += (staggerLines - 1) * 16;
  4436. }
  4437. } else { // doesn't have data
  4438. for (var n in ticks) {
  4439. ticks[n].destroy();
  4440. delete ticks[n];
  4441. }
  4442. }
  4443. if (axisTitleOptions && axisTitleOptions.text) {
  4444. if (!axis.axisTitle) {
  4445. axis.axisTitle = renderer.text(
  4446. axisTitleOptions.text,
  4447. 0,
  4448. 0
  4449. )
  4450. .attr({
  4451. zIndex: 7,
  4452. rotation: axisTitleOptions.rotation || 0,
  4453. align:
  4454. axisTitleOptions.textAlign ||
  4455. { low: 'left', middle: 'center', high: 'right' }[axisTitleOptions.align]
  4456. })
  4457. .css(axisTitleOptions.style)
  4458. .add();
  4459. }
  4460. titleOffset = axis.axisTitle.getBBox()[horiz ? 'height' : 'width'];
  4461. titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10);
  4462. }
  4463. // handle automatic or user set offset
  4464. offset = directionFactor * (options.offset || axisOffset[side]);
  4465. axisTitleMargin =
  4466. labelOffset +
  4467. (side != 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x']) +
  4468. titleMargin;
  4469. axisOffset[side] = mathMax(
  4470. axisOffset[side],
  4471. axisTitleMargin + titleOffset + directionFactor * offset
  4472. );
  4473. }
  4474. /**
  4475. * Render the axis
  4476. */
  4477. function render() {
  4478. var axisTitleOptions = options.title,
  4479. alternateGridColor = options.alternateGridColor,
  4480. lineWidth = options.lineWidth,
  4481. lineLeft,
  4482. lineTop,
  4483. linePath,
  4484. hasRendered = chart.hasRendered,
  4485. slideInTicks = hasRendered && defined(oldMin) && !isNaN(oldMin),
  4486. hasData = associatedSeries.length && defined(min) && defined(max);
  4487. // update metrics
  4488. axisLength = horiz ? plotWidth : plotHeight;
  4489. transA = axisLength / ((max - min) || 1);
  4490. transB = horiz ? plotLeft : marginBottom; // translation addend
  4491. // If the series has data draw the ticks. Else only the line and title
  4492. if (hasData || isLinked) {
  4493. // minor ticks
  4494. if (minorTickInterval && !categories) {
  4495. var pos = min + (tickPositions[0] - min) % minorTickInterval;
  4496. for (pos; pos <= max; pos += minorTickInterval) {
  4497. if (!minorTicks[pos]) {
  4498. minorTicks[pos] = new Tick(pos, true);
  4499. }
  4500. // render new ticks in old position
  4501. if (slideInTicks && minorTicks[pos].isNew) {
  4502. minorTicks[pos].render(null, true);
  4503. }
  4504. minorTicks[pos].isActive = true;
  4505. minorTicks[pos].render();
  4506. }
  4507. }
  4508. // major ticks
  4509. each(tickPositions, function(pos, i) {
  4510. // linked axes need an extra check to find out if
  4511. if (!isLinked || (pos >= min && pos <= max)) {
  4512. // render new ticks in old position
  4513. if (slideInTicks && ticks[pos].isNew) {
  4514. ticks[pos].render(i, true);
  4515. }
  4516. ticks[pos].isActive = true;
  4517. ticks[pos].render(i);
  4518. }
  4519. });
  4520. // alternate grid color
  4521. if (alternateGridColor) {
  4522. each(tickPositions, function(pos, i) {
  4523. if (i % 2 === 0 && pos < max) {
  4524. /*plotLinesAndBands.push(new PlotLineOrBand({
  4525. from: pos,
  4526. to: tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] : max,
  4527. color: alternateGridColor
  4528. }));*/
  4529. if (!alternateBands[pos]) {
  4530. alternateBands[pos] = new PlotLineOrBand();
  4531. }
  4532. alternateBands[pos].options = {
  4533. from: pos,
  4534. to: tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] : max,
  4535. color: alternateGridColor
  4536. };
  4537. alternateBands[pos].render();
  4538. alternateBands[pos].isActive = true;
  4539. }
  4540. });
  4541. }
  4542. // custom plot bands (behind grid lines)
  4543. /*if (!hasRendered) { // only first time
  4544. each(options.plotBands || [], function(plotBandOptions) {
  4545. plotLinesAndBands.push(new PlotLineOrBand(
  4546. extend({ zIndex: 1 }, plotBandOptions)
  4547. ).render());
  4548. });
  4549. }*/
  4550. // custom plot lines and bands
  4551. if (!hasRendered) { // only first time
  4552. each((options.plotLines || []).concat(options.plotBands || []), function(plotLineOptions) {
  4553. plotLinesAndBands.push(new PlotLineOrBand(plotLineOptions).render());
  4554. });
  4555. }
  4556. } // end if hasData
  4557. // remove inactive ticks
  4558. each([ticks, minorTicks, alternateBands], function(coll) {
  4559. for (var pos in coll) {
  4560. if (!coll[pos].isActive) {
  4561. coll[pos].destroy();
  4562. delete coll[pos];
  4563. } else {
  4564. coll[pos].isActive = false; // reset
  4565. }
  4566. }
  4567. });
  4568. // Static items. As the axis group is cleared on subsequent calls
  4569. // to render, these items are added outside the group.
  4570. // axis line
  4571. if (lineWidth) {
  4572. lineLeft = plotLeft + (opposite ? plotWidth : 0) + offset;
  4573. lineTop = chartHeight - marginBottom - (opposite ? plotHeight : 0) + offset;
  4574. linePath = renderer.crispLine([
  4575. M,
  4576. horiz ?
  4577. plotLeft:
  4578. lineLeft,
  4579. horiz ?
  4580. lineTop:
  4581. plotTop,
  4582. L,
  4583. horiz ?
  4584. chartWidth - marginRight :
  4585. lineLeft,
  4586. horiz ?
  4587. lineTop:
  4588. chartHeight - marginBottom
  4589. ], lineWidth);
  4590. if (!axisLine) {
  4591. axisLine = renderer.path(linePath)
  4592. .attr({
  4593. stroke: options.lineColor,
  4594. 'stroke-width': lineWidth,
  4595. zIndex: 7
  4596. })
  4597. .add();
  4598. } else {
  4599. axisLine.animate({ d: linePath });
  4600. }
  4601. }
  4602. if (axis.axisTitle) {
  4603. // compute anchor points for each of the title align options
  4604. var margin = horiz ? plotLeft : plotTop,
  4605. fontSize = pInt(axisTitleOptions.style.fontSize || 12),
  4606. // the position in the length direction of the axis
  4607. alongAxis = {
  4608. low: margin + (horiz ? 0 : axisLength),
  4609. middle: margin + axisLength / 2,
  4610. high: margin + (horiz ? axisLength : 0)
  4611. }[axisTitleOptions.align],
  4612. // the position in the perpendicular direction of the axis
  4613. offAxis = (horiz ? plotTop + plotHeight : plotLeft) +
  4614. (horiz ? 1 : -1) * // horizontal axis reverses the margin
  4615. (opposite ? -1 : 1) * // so does opposite axes
  4616. axisTitleMargin +
  4617. //(isIE ? fontSize / 3 : 0)+ // preliminary fix for vml's centerline
  4618. (side == 2 ? fontSize : 0);
  4619. axis.axisTitle[hasRendered ? 'animate' : 'attr']({
  4620. x: horiz ?
  4621. alongAxis:
  4622. offAxis + (opposite ? plotWidth : 0) + offset +
  4623. (axisTitleOptions.x || 0), // x
  4624. y: horiz ?
  4625. offAxis - (opposite ? plotHeight : 0) + offset:
  4626. alongAxis + (axisTitleOptions.y || 0) // y
  4627. });
  4628. }
  4629. axis.isDirty = false;
  4630. }
  4631. /**
  4632. * Remove a plot band or plot line from the chart by id
  4633. * @param {Object} id
  4634. */
  4635. function removePlotBandOrLine(id) {
  4636. var i = plotLinesAndBands.length;
  4637. while (i--) {
  4638. if (plotLinesAndBands[i].id == id) {
  4639. plotLinesAndBands[i].destroy();
  4640. }
  4641. }
  4642. }
  4643. /**
  4644. * Redraw the axis to reflect changes in the data or axis extremes
  4645. */
  4646. function redraw() {
  4647. // hide tooltip and hover states
  4648. if (tracker.resetTracker) {
  4649. tracker.resetTracker();
  4650. }
  4651. // render the axis
  4652. render();
  4653. // move plot lines and bands
  4654. each(plotLinesAndBands, function(plotLine) {
  4655. plotLine.render();
  4656. });
  4657. // mark associated series as dirty and ready for redraw
  4658. each(associatedSeries, function(series) {
  4659. series.isDirty = true;
  4660. });
  4661. }
  4662. /**
  4663. * Set new axis categories and optionally redraw
  4664. * @param {Array} newCategories
  4665. * @param {Boolean} doRedraw
  4666. */
  4667. function setCategories(newCategories, doRedraw) {
  4668. // set the categories
  4669. axis.categories = categories = newCategories;
  4670. // force reindexing tooltips
  4671. each(associatedSeries, function(series) {
  4672. series.translate();
  4673. series.setTooltipPoints(true);
  4674. });
  4675. // optionally redraw
  4676. axis.isDirty = true;
  4677. if (pick(doRedraw, true)) {
  4678. chart.redraw();
  4679. }
  4680. }
  4681. // Run Axis
  4682. // inverted charts have reversed xAxes as default
  4683. if (inverted && isXAxis && reversed === UNDEFINED) {
  4684. reversed = true;
  4685. }
  4686. // expose some variables
  4687. extend(axis, {
  4688. addPlotBand: addPlotBandOrLine,
  4689. addPlotLine: addPlotBandOrLine,
  4690. adjustTickAmount: adjustTickAmount,
  4691. categories: categories,
  4692. getExtremes: getExtremes,
  4693. getPlotLinePath: getPlotLinePath,
  4694. getThreshold: getThreshold,
  4695. isXAxis: isXAxis,
  4696. options: options,
  4697. plotLinesAndBands: plotLinesAndBands,
  4698. getOffset: getOffset,
  4699. render: render,
  4700. setCategories: setCategories,
  4701. setExtremes: setExtremes,
  4702. setScale: setScale,
  4703. setTickPositions: setTickPositions,
  4704. translate: translate,
  4705. redraw: redraw,
  4706. removePlotBand: removePlotBandOrLine,
  4707. removePlotLine: removePlotBandOrLine,
  4708. reversed: reversed,
  4709. stacks: stacks
  4710. });
  4711. // register event listeners
  4712. for (eventType in events) {
  4713. addEvent(axis, eventType, events[eventType]);
  4714. }
  4715. // set min and max
  4716. setScale();
  4717. } // end Axis
  4718. /**
  4719. * The toolbar object
  4720. *
  4721. * @param {Object} chart
  4722. */
  4723. function Toolbar(chart) {
  4724. var buttons = {};
  4725. function add(id, text, title, fn) {
  4726. if (!buttons[id]) {
  4727. var button = renderer.text(
  4728. text,
  4729. 0,
  4730. 0
  4731. )
  4732. .css(options.toolbar.itemStyle)
  4733. .align({
  4734. align: 'right',
  4735. x: - marginRight - 20,
  4736. y: plotTop + 30
  4737. })
  4738. .on('click', fn)
  4739. /*.on('touchstart', function(e) {
  4740. e.stopPropagation(); // don't fire the container event
  4741. fn();
  4742. })*/
  4743. .attr({
  4744. align: 'right',
  4745. zIndex: 20
  4746. })
  4747. .add();
  4748. buttons[id] = button;
  4749. }
  4750. }
  4751. function remove(id) {
  4752. discardElement(buttons[id].element);
  4753. buttons[id] = null;
  4754. }
  4755. // public
  4756. return {
  4757. add: add,
  4758. remove: remove
  4759. };
  4760. }
  4761. /**
  4762. * The tooltip object
  4763. * @param {Object} options Tooltip options
  4764. */
  4765. function Tooltip (options) {
  4766. var currentSeries,
  4767. borderWidth = options.borderWidth,
  4768. crosshairsOptions = options.crosshairs,
  4769. crosshairs = [],
  4770. style = options.style,
  4771. shared = options.shared,
  4772. padding = pInt(style.padding),
  4773. boxOffLeft = borderWidth + padding, // off left/top position as IE can't
  4774. //properly handle negative positioned shapes
  4775. tooltipIsHidden = true,
  4776. boxWidth,
  4777. boxHeight,
  4778. currentX = 0,
  4779. currentY = 0;
  4780. // remove padding CSS and apply padding on box instead
  4781. style.padding = 0;
  4782. // create the elements
  4783. var group = renderer.g('tooltip')
  4784. .attr({ zIndex: 8 })
  4785. .add(),
  4786. box = renderer.rect(boxOffLeft, boxOffLeft, 0, 0, options.borderRadius, borderWidth)
  4787. .attr({
  4788. fill: options.backgroundColor,
  4789. 'stroke-width': borderWidth
  4790. })
  4791. .add(group)
  4792. .shadow(options.shadow),
  4793. label = renderer.text('', padding + boxOffLeft, pInt(style.fontSize) + padding + boxOffLeft)
  4794. .attr({ zIndex: 1 })
  4795. .css(style)
  4796. .add(group);
  4797. group.hide();
  4798. /**
  4799. * In case no user defined formatter is given, this will be used
  4800. */
  4801. function defaultFormatter() {
  4802. var pThis = this,
  4803. items = pThis.points || splat(pThis),
  4804. xAxis = items[0].series.xAxis,
  4805. x = pThis.x,
  4806. isDateTime = xAxis && xAxis.options.type == 'datetime',
  4807. useHeader = isString(x) || isDateTime,
  4808. series,
  4809. s;
  4810. // build the header
  4811. s = useHeader ?
  4812. ['<span style="font-size: 10px">',
  4813. (isDateTime ? dateFormat('%A, %b %e, %Y', x) : x),
  4814. '</span><br/>'] : [];
  4815. // build the values
  4816. each(items, function(item) {
  4817. s.push(item.point.tooltipFormatter(useHeader));
  4818. });
  4819. return s.join('');
  4820. }
  4821. /**
  4822. * Provide a soft movement for the tooltip
  4823. *
  4824. * @param {Number} finalX
  4825. * @param {Number} finalY
  4826. */
  4827. function move(finalX, finalY) {
  4828. currentX = tooltipIsHidden ? finalX : (2 * currentX + finalX) / 3;
  4829. currentY = tooltipIsHidden ? finalY : (currentY + finalY) / 2;
  4830. group.translate(currentX, currentY);
  4831. // run on next tick of the mouse tracker
  4832. if (mathAbs(finalX - currentX) > 1 || mathAbs(finalY - currentY) > 1) {
  4833. tooltipTick = function() {
  4834. move(finalX, finalY);
  4835. };
  4836. } else {
  4837. tooltipTick = null;
  4838. }
  4839. }
  4840. /**
  4841. * Hide the tooltip
  4842. */
  4843. function hide() {
  4844. if (!tooltipIsHidden) {
  4845. var hoverPoints = chart.hoverPoints;
  4846. group.hide();
  4847. each(crosshairs, function(crosshair) {
  4848. if (crosshair) {
  4849. crosshair.hide();
  4850. }
  4851. });
  4852. // hide previous hoverPoints and set new
  4853. if (hoverPoints) {
  4854. each (hoverPoints, function(point) {
  4855. point.setState();
  4856. });
  4857. }
  4858. chart.hoverPoints = null;
  4859. tooltipIsHidden = true;
  4860. }
  4861. }
  4862. /**
  4863. * Refresh the tooltip's text and position.
  4864. * @param {Object} point
  4865. *
  4866. */
  4867. function refresh(point) {
  4868. var x,
  4869. y,
  4870. boxX,
  4871. boxY,
  4872. show,
  4873. bBox,
  4874. plotX,
  4875. plotY = 0,
  4876. textConfig = {},
  4877. text,
  4878. pointConfig = [],
  4879. tooltipPos = point.tooltipPos,
  4880. formatter = options.formatter || defaultFormatter,
  4881. hoverPoints = chart.hoverPoints,
  4882. getConfig = function(point) {
  4883. return {
  4884. series: point.series,
  4885. point: point,
  4886. x: point.category,
  4887. y: point.y,
  4888. percentage: point.percentage,
  4889. total: point.total || point.stackTotal
  4890. };
  4891. };
  4892. // shared tooltip, array is sent over
  4893. if (shared) {
  4894. // hide previous hoverPoints and set new
  4895. if (hoverPoints) {
  4896. each (hoverPoints, function(point) {
  4897. point.setState();
  4898. });
  4899. }
  4900. chart.hoverPoints = point;
  4901. each(point, function(item, i) {
  4902. /*var series = item.series,
  4903. hoverPoint = series.hoverPoint;
  4904. if (hoverPoint) {
  4905. hoverPoint.setState();
  4906. }
  4907. series.hoverPoint = item;*/
  4908. item.setState(HOVER_STATE);
  4909. plotY += item.plotY; // for average
  4910. pointConfig.push(getConfig(item));
  4911. });
  4912. plotX = point[0].plotX;
  4913. plotY = mathRound(plotY) / point.length; // mathRound because Opera 10 has problems here
  4914. textConfig = {
  4915. x: point[0].category
  4916. };
  4917. textConfig.points = pointConfig;
  4918. point = point[0];
  4919. // single point tooltip
  4920. } else {
  4921. textConfig = getConfig(point);
  4922. }
  4923. text = formatter.call(textConfig);
  4924. // register the current series
  4925. currentSeries = point.series;
  4926. // get the reference point coordinates (pie charts use tooltipPos)
  4927. plotX = shared ? plotX : point.plotX;
  4928. plotY = shared ? plotY : point.plotY;
  4929. x = mathRound(tooltipPos ? tooltipPos[0] : (inverted ? plotWidth - plotY : plotX));
  4930. y = mathRound(tooltipPos ? tooltipPos[1] : (inverted ? plotHeight - plotX : plotY));
  4931. // hide tooltip if the point falls outside the plot
  4932. show = shared || !point.series.isCartesian || isInsidePlot(x, y);
  4933. // update the inner HTML
  4934. if (text === false || !show) {
  4935. hide();
  4936. } else {
  4937. // show it
  4938. if (tooltipIsHidden) {
  4939. group.show();
  4940. tooltipIsHidden = false;
  4941. }
  4942. // update text
  4943. label.attr({
  4944. text: text
  4945. });
  4946. // get the bounding box
  4947. bBox = label.getBBox();
  4948. boxWidth = bBox.width + 2 * padding;
  4949. boxHeight = bBox.height + 2 * padding;
  4950. // set the size of the box
  4951. box.attr({
  4952. width: boxWidth,
  4953. height: boxHeight,
  4954. stroke: options.borderColor || point.color || currentSeries.color || '#606060'
  4955. });
  4956. // keep the box within the chart area
  4957. boxX = x - boxWidth + plotLeft - 25;
  4958. boxY = y - boxHeight + plotTop + 10;
  4959. // it is too far to the left, adjust it
  4960. if (boxX < 7) {
  4961. boxX = 7;
  4962. boxY -= 30;
  4963. }
  4964. if (boxY < 5) {
  4965. boxY = 5; // above
  4966. } else if (boxY + boxHeight > chartHeight) {
  4967. boxY = chartHeight - boxHeight - 5; // below
  4968. }
  4969. // do the move
  4970. move(mathRound(boxX - boxOffLeft), mathRound(boxY - boxOffLeft));
  4971. }
  4972. // crosshairs
  4973. if (crosshairsOptions) {
  4974. crosshairsOptions = splat(crosshairsOptions); // [x, y]
  4975. var path,
  4976. i = crosshairsOptions.length,
  4977. attribs,
  4978. axis;
  4979. while (i--) {
  4980. if (crosshairsOptions[i] && (axis = point.series[i ? 'yAxis' : 'xAxis'])) {
  4981. path = axis
  4982. .getPlotLinePath(point[i ? 'y' : 'x'], 1);
  4983. if (crosshairs[i]) {
  4984. crosshairs[i].attr({ d: path, visibility: VISIBLE });
  4985. } else {
  4986. attribs = {
  4987. 'stroke-width': crosshairsOptions[i].width || 1,
  4988. stroke: crosshairsOptions[i].color || '#C0C0C0',
  4989. zIndex: 2
  4990. };
  4991. if (crosshairsOptions[i].dashStyle) {
  4992. attribs.dashstyle = crosshairsOptions[i].dashStyle;
  4993. }
  4994. crosshairs[i] = renderer.path(path)
  4995. .attr(attribs)
  4996. .add();
  4997. }
  4998. }
  4999. }
  5000. }
  5001. }
  5002. // public members
  5003. return {
  5004. shared: shared,
  5005. refresh: refresh,
  5006. hide: hide
  5007. };
  5008. }
  5009. /**
  5010. * The mouse tracker object
  5011. * @param {Object} chart
  5012. * @param {Object} options
  5013. */
  5014. function MouseTracker (chart, options) {
  5015. var mouseDownX,
  5016. mouseDownY,
  5017. hasDragged,
  5018. selectionMarker,
  5019. zoomType = optionsChart.zoomType,
  5020. zoomX = /x/.test(zoomType),
  5021. zoomY = /y/.test(zoomType),
  5022. zoomHor = zoomX && !inverted || zoomY && inverted,
  5023. zoomVert = zoomY && !inverted || zoomX && inverted;
  5024. /**
  5025. * Add crossbrowser support for chartX and chartY
  5026. * @param {Object} e The event object in standard browsers
  5027. */
  5028. function normalizeMouseEvent(e) {
  5029. var ePos;
  5030. // common IE normalizing
  5031. e = e || win.event;
  5032. if (!e.target) {
  5033. e.target = e.srcElement;
  5034. }
  5035. // iOS
  5036. ePos = e.touches ? e.touches.item(0) : e;
  5037. // in certain cases, get mouse position
  5038. if (e.type != 'mousemove' || win.opera) { // only Opera needs position on mouse move, see below
  5039. chartPosition = getPosition(container);
  5040. }
  5041. // chartX and chartY
  5042. if (isIE) { // IE including IE9 that has chartX but in a different meaning
  5043. e.chartX = e.x;
  5044. e.chartY = e.y;
  5045. } else {
  5046. if (ePos.layerX === UNDEFINED) { // Opera and iOS
  5047. e.chartX = ePos.pageX - chartPosition.left;
  5048. e.chartY = ePos.pageY - chartPosition.top;
  5049. } else {
  5050. e.chartX = e.layerX;
  5051. e.chartY = e.layerY;
  5052. }
  5053. }
  5054. return e;
  5055. }
  5056. /**
  5057. * Get the click position in terms of axis values.
  5058. *
  5059. * @param {Object} e A mouse event
  5060. */
  5061. function getMouseCoordinates(e) {
  5062. var coordinates = {
  5063. xAxis: [],
  5064. yAxis: []
  5065. };
  5066. each(axes, function(axis, i) {
  5067. var translate = axis.translate,
  5068. isXAxis = axis.isXAxis,
  5069. isHorizontal = inverted ? !isXAxis : isXAxis;
  5070. coordinates[isXAxis ? 'xAxis' : 'yAxis'].push({
  5071. axis: axis,
  5072. value: translate(
  5073. isHorizontal ?
  5074. e.chartX - plotLeft :
  5075. plotHeight - e.chartY + plotTop,
  5076. true
  5077. )
  5078. });
  5079. });
  5080. return coordinates;
  5081. }
  5082. /**
  5083. * With line type charts with a single tracker, get the point closest to the mouse
  5084. */
  5085. function onmousemove (e) {
  5086. var point,
  5087. points,
  5088. hoverPoint = chart.hoverPoint,
  5089. hoverSeries = chart.hoverSeries,
  5090. i,
  5091. j,
  5092. distance = chartWidth,
  5093. index = inverted ? e.chartY : e.chartX - plotLeft; // wtf?
  5094. // shared tooltip
  5095. if (tooltip && options.shared) {
  5096. points = [];
  5097. // loop over all series and find the ones with points closest to the mouse
  5098. i = series.length;
  5099. for (j = 0; j < i; j++) {
  5100. if (series[j].visible && series[j].tooltipPoints.length) {
  5101. point = series[j].tooltipPoints[index];
  5102. point._dist = mathAbs(index - point.plotX);
  5103. distance = mathMin(distance, point._dist);
  5104. points.push(point);
  5105. }
  5106. }
  5107. // remove furthest points
  5108. i = points.length;
  5109. while (i--) {
  5110. if (points[i]._dist > distance) {
  5111. points.splice(i, 1);
  5112. }
  5113. }
  5114. // refresh the tooltip if necessary
  5115. if (points.length && (points[0].plotX != hoverX)) {
  5116. tooltip.refresh(points);
  5117. hoverX = points[0].plotX;
  5118. }
  5119. }
  5120. // separate tooltip and general mouse events
  5121. if (hoverSeries && hoverSeries.tracker) { // only use for line-type series with common tracker
  5122. // get the point
  5123. point = hoverSeries.tooltipPoints[index];
  5124. // a new point is hovered, refresh the tooltip
  5125. if (point && point != hoverPoint) {
  5126. // trigger the events
  5127. point.onMouseOver();
  5128. }
  5129. }
  5130. }
  5131. /**
  5132. * Reset the tracking by hiding the tooltip, the hover series state and the hover point
  5133. */
  5134. function resetTracker() {
  5135. var hoverSeries = chart.hoverSeries,
  5136. hoverPoint = chart.hoverPoint;
  5137. if (hoverPoint) {
  5138. hoverPoint.onMouseOut();
  5139. }
  5140. if (hoverSeries) {
  5141. hoverSeries.onMouseOut();
  5142. }
  5143. if (tooltip) {
  5144. tooltip.hide();
  5145. }
  5146. hoverX = null;
  5147. }
  5148. /**
  5149. * Mouse up or outside the plot area
  5150. */
  5151. function drop() {
  5152. if (selectionMarker) {
  5153. var selectionData = {
  5154. xAxis: [],
  5155. yAxis: []
  5156. },
  5157. selectionBox = selectionMarker.getBBox(),
  5158. selectionLeft = selectionBox.x - plotLeft,
  5159. selectionTop = selectionBox.y - plotTop;
  5160. // a selection has been made
  5161. if (hasDragged) {
  5162. // record each axis' min and max
  5163. each(axes, function(axis, i) {
  5164. var translate = axis.translate,
  5165. isXAxis = axis.isXAxis,
  5166. isHorizontal = inverted ? !isXAxis : isXAxis,
  5167. selectionMin = translate(
  5168. isHorizontal ?
  5169. selectionLeft :
  5170. plotHeight - selectionTop - selectionBox.height,
  5171. true
  5172. ),
  5173. selectionMax = translate(
  5174. isHorizontal ?
  5175. selectionLeft + selectionBox.width :
  5176. plotHeight - selectionTop,
  5177. true
  5178. );
  5179. selectionData[isXAxis ? 'xAxis' : 'yAxis'].push({
  5180. axis: axis,
  5181. min: mathMin(selectionMin, selectionMax), // for reversed axes
  5182. max: mathMax(selectionMin, selectionMax)
  5183. });
  5184. });
  5185. fireEvent(chart, 'selection', selectionData, zoom);
  5186. }
  5187. selectionMarker = selectionMarker.destroy();
  5188. }
  5189. chart.mouseIsDown = mouseIsDown = hasDragged = false;
  5190. removeEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
  5191. }
  5192. /**
  5193. * Set the JS events on the container element
  5194. */
  5195. function setDOMEvents () {
  5196. var lastWasOutsidePlot = true;
  5197. /*
  5198. * Record the starting position of a dragoperation
  5199. */
  5200. container.onmousedown = function(e) {
  5201. e = normalizeMouseEvent(e);
  5202. // record the start position
  5203. //e.preventDefault && e.preventDefault();
  5204. chart.mouseIsDown = mouseIsDown = true;
  5205. mouseDownX = e.chartX;
  5206. mouseDownY = e.chartY;
  5207. addEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
  5208. };
  5209. // The mousemove, touchmove and touchstart event handler
  5210. var mouseMove = function(e) {
  5211. // let the system handle multitouch operations like two finger scroll
  5212. // and pinching
  5213. if (e && e.touches && e.touches.length > 1) {
  5214. return;
  5215. }
  5216. // normalize
  5217. e = normalizeMouseEvent(e);
  5218. if (!hasTouch) { // not for touch devices
  5219. e.returnValue = false;
  5220. }
  5221. var chartX = e.chartX,
  5222. chartY = e.chartY,
  5223. isOutsidePlot = !isInsidePlot(chartX - plotLeft, chartY - plotTop);
  5224. // on touch devices, only trigger click if a handler is defined
  5225. if (hasTouch && e.type == 'touchstart') {
  5226. if (attr(e.target, 'isTracker')) {
  5227. if (!chart.runTrackerClick) {
  5228. e.preventDefault();
  5229. }
  5230. } else if (!runChartClick && !isOutsidePlot) {
  5231. e.preventDefault();
  5232. }
  5233. }
  5234. // cancel on mouse outside
  5235. if (isOutsidePlot) {
  5236. if (!lastWasOutsidePlot) {
  5237. // reset the tracker
  5238. resetTracker();
  5239. }
  5240. // drop the selection if any and reset mouseIsDown and hasDragged
  5241. //drop();
  5242. if (chartX < plotLeft) {
  5243. chartX = plotLeft;
  5244. } else if (chartX > plotLeft + plotWidth) {
  5245. chartX = plotLeft + plotWidth;
  5246. }
  5247. if (chartY < plotTop) {
  5248. chartY = plotTop;
  5249. } else if (chartY > plotTop + plotHeight) {
  5250. chartY = plotTop + plotHeight;
  5251. }
  5252. }
  5253. if (mouseIsDown && e.type != 'touchstart') { // make selection
  5254. // determine if the mouse has moved more than 10px
  5255. if ((hasDragged = Math.sqrt(
  5256. Math.pow(mouseDownX - chartX, 2) +
  5257. Math.pow(mouseDownY - chartY, 2)
  5258. ) > 10)) {
  5259. // make a selection
  5260. if (hasCartesianSeries && (zoomX || zoomY) &&
  5261. isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop)) {
  5262. if (!selectionMarker) {
  5263. selectionMarker = renderer.rect(
  5264. plotLeft,
  5265. plotTop,
  5266. zoomHor ? 1 : plotWidth,
  5267. zoomVert ? 1 : plotHeight,
  5268. 0
  5269. )
  5270. .attr({
  5271. fill: 'rgba(69,114,167,0.25)',
  5272. zIndex: 7
  5273. })
  5274. .add();
  5275. }
  5276. }
  5277. // adjust the width of the selection marker
  5278. if (selectionMarker && zoomHor) {
  5279. var xSize = chartX - mouseDownX;
  5280. selectionMarker.attr({
  5281. width: mathAbs(xSize),
  5282. x: (xSize > 0 ? 0 : xSize) + mouseDownX
  5283. });
  5284. }
  5285. // adjust the height of the selection marker
  5286. if (selectionMarker && zoomVert) {
  5287. var ySize = chartY - mouseDownY;
  5288. selectionMarker.attr({
  5289. height: mathAbs(ySize),
  5290. y: (ySize > 0 ? 0 : ySize) + mouseDownY
  5291. });
  5292. }
  5293. }
  5294. } else if (!isOutsidePlot) {
  5295. // show the tooltip
  5296. onmousemove(e);
  5297. }
  5298. lastWasOutsidePlot = isOutsidePlot;
  5299. // when outside plot, allow touch-drag by returning true
  5300. return isOutsidePlot || !hasCartesianSeries;
  5301. };
  5302. /*
  5303. * When the mouse enters the container, run mouseMove
  5304. */
  5305. container.onmousemove = mouseMove;
  5306. /*
  5307. * When the mouse leaves the container, hide the tracking (tooltip).
  5308. */
  5309. addEvent(container, 'mouseleave', resetTracker);
  5310. container.ontouchstart = function(e) {
  5311. // For touch devices, use touchmove to zoom
  5312. if (zoomX || zoomY) {
  5313. container.onmousedown(e);
  5314. }
  5315. // Show tooltip and prevent the lower mouse pseudo event
  5316. mouseMove(e);
  5317. };
  5318. /*
  5319. * Allow dragging the finger over the chart to read the values on touch
  5320. * devices
  5321. */
  5322. container.ontouchmove = mouseMove;
  5323. /*
  5324. * Allow dragging the finger over the chart to read the values on touch
  5325. * devices
  5326. */
  5327. container.ontouchend = function() {
  5328. if (hasDragged) {
  5329. resetTracker();
  5330. }
  5331. };
  5332. // MooTools 1.2.3 doesn't fire this in IE when using addEvent
  5333. container.onclick = function(e) {
  5334. var hoverPoint = chart.hoverPoint;
  5335. e = normalizeMouseEvent(e);
  5336. e.cancelBubble = true; // IE specific
  5337. if (!hasDragged) {
  5338. if (hoverPoint && attr(e.target, 'isTracker')) {
  5339. var plotX = hoverPoint.plotX,
  5340. plotY = hoverPoint.plotY;
  5341. // add page position info
  5342. extend(hoverPoint, {
  5343. pageX: chartPosition.left + plotLeft +
  5344. (inverted ? plotWidth - plotY : plotX),
  5345. pageY: chartPosition.top + plotTop +
  5346. (inverted ? plotHeight - plotX : plotY)
  5347. });
  5348. // the series click event
  5349. fireEvent(hoverPoint.series, 'click', extend(e, {
  5350. point: hoverPoint
  5351. }));
  5352. // the point click event
  5353. hoverPoint.firePointEvent('click', e);
  5354. } else {
  5355. extend(e, getMouseCoordinates(e));
  5356. // fire a click event in the chart
  5357. if (isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {
  5358. fireEvent(chart, 'click', e);
  5359. }
  5360. }
  5361. }
  5362. // reset mouseIsDown and hasDragged
  5363. hasDragged = false;
  5364. };
  5365. }
  5366. /**
  5367. * Create the image map that listens for mouseovers
  5368. */
  5369. placeTrackerGroup = function() {
  5370. // first create - plot positions is not final at this stage
  5371. if (!trackerGroup) {
  5372. chart.trackerGroup = trackerGroup = renderer.g('tracker')
  5373. .attr({ zIndex: 9 })
  5374. .add();
  5375. // then position - this happens on load and after resizing and changing
  5376. // axis or box positions
  5377. } else {
  5378. trackerGroup.translate(plotLeft, plotTop);
  5379. if (inverted) {
  5380. trackerGroup.attr({
  5381. width: chart.plotWidth,
  5382. height: chart.plotHeight
  5383. }).invert();
  5384. }
  5385. }
  5386. };
  5387. // Run MouseTracker
  5388. placeTrackerGroup();
  5389. if (options.enabled) {
  5390. chart.tooltip = tooltip = Tooltip(options);
  5391. }
  5392. setDOMEvents();
  5393. // set the fixed interval ticking for the smooth tooltip
  5394. tooltipInterval = setInterval(function() {
  5395. if (tooltipTick) {
  5396. tooltipTick();
  5397. }
  5398. }, 32);
  5399. // expose properties
  5400. extend(this, {
  5401. zoomX: zoomX,
  5402. zoomY: zoomY,
  5403. resetTracker: resetTracker
  5404. });
  5405. }
  5406. /**
  5407. * The overview of the chart's series
  5408. * @param {Object} chart
  5409. */
  5410. var Legend = function(chart) {
  5411. var options = chart.options.legend;
  5412. if (!options.enabled) {
  5413. return;
  5414. }
  5415. var horizontal = options.layout == 'horizontal',
  5416. symbolWidth = options.symbolWidth,
  5417. symbolPadding = options.symbolPadding,
  5418. allItems,
  5419. style = options.style,
  5420. itemStyle = options.itemStyle,
  5421. itemHoverStyle = options.itemHoverStyle,
  5422. itemHiddenStyle = options.itemHiddenStyle,
  5423. padding = pInt(style.padding),
  5424. rightPadding = 20,
  5425. //lineHeight = options.lineHeight || 16,
  5426. y = 18,
  5427. initialItemX = 4 + padding + symbolWidth + symbolPadding,
  5428. itemX,
  5429. itemY,
  5430. lastItemY,
  5431. itemHeight = 0,
  5432. box,
  5433. legendBorderWidth = options.borderWidth,
  5434. legendBackgroundColor = options.backgroundColor,
  5435. legendGroup,
  5436. offsetWidth,
  5437. widthOption = options.width,
  5438. series = chart.series,
  5439. reversedLegend = options.reversed;
  5440. /**
  5441. * Set the colors for the legend item
  5442. * @param {Object} item A Series or Point instance
  5443. * @param {Object} visible Dimmed or colored
  5444. */
  5445. function colorizeItem(item, visible) {
  5446. var legendItem = item.legendItem,
  5447. legendLine = item.legendLine,
  5448. legendSymbol = item.legendSymbol,
  5449. hiddenColor = itemHiddenStyle.color,
  5450. textColor = visible ? options.itemStyle.color : hiddenColor,
  5451. symbolColor = visible ? item.color : hiddenColor;
  5452. if (legendItem) {
  5453. legendItem.css({ fill: textColor });
  5454. }
  5455. if (legendLine) {
  5456. legendLine.attr({ stroke: symbolColor });
  5457. }
  5458. if (legendSymbol) {
  5459. legendSymbol.attr({
  5460. stroke: symbolColor,
  5461. fill: symbolColor
  5462. });
  5463. }
  5464. }
  5465. /**
  5466. * Position the legend item
  5467. * @param {Object} item A Series or Point instance
  5468. * @param {Object} visible Dimmed or colored
  5469. */
  5470. function positionItem(item, itemX, itemY) {
  5471. var legendItem = item.legendItem,
  5472. legendLine = item.legendLine,
  5473. legendSymbol = item.legendSymbol,
  5474. checkbox = item.checkbox;
  5475. if (legendItem) {
  5476. legendItem.attr({
  5477. x: itemX,
  5478. y: itemY
  5479. });
  5480. }
  5481. if (legendLine) {
  5482. legendLine.translate(itemX, itemY - 4);
  5483. }
  5484. if (legendSymbol) {
  5485. legendSymbol.attr({
  5486. x: itemX + legendSymbol.xOff,
  5487. y: itemY + legendSymbol.yOff
  5488. });
  5489. }
  5490. if (checkbox) {
  5491. checkbox.x = itemX;
  5492. checkbox.y = itemY;
  5493. }
  5494. }
  5495. /**
  5496. * Destroy a single legend item
  5497. * @param {Object} item The series or point
  5498. */
  5499. function destroyItem(item) {
  5500. var checkbox = item.checkbox;
  5501. // pull out from the array
  5502. //erase(allItems, item);
  5503. // destroy SVG elements
  5504. each(['legendItem', 'legendLine', 'legendSymbol'], function(key) {
  5505. if (item[key]) {
  5506. item[key].destroy();
  5507. }
  5508. });
  5509. if (checkbox) {
  5510. discardElement(item.checkbox);
  5511. }
  5512. }
  5513. /**
  5514. * Position the checkboxes after the width is determined
  5515. */
  5516. function positionCheckboxes() {
  5517. each(allItems, function(item) {
  5518. var checkbox = item.checkbox;
  5519. if (checkbox) {
  5520. css(checkbox, {
  5521. left: (legendGroup.attr('translateX') + item.legendItemWidth + checkbox.x - 40) +PX,
  5522. top: (legendGroup.attr('translateY') + checkbox.y - 11) + PX
  5523. });
  5524. }
  5525. });
  5526. }
  5527. /**
  5528. * Render a single specific legend item
  5529. * @param {Object} item A series or point
  5530. */
  5531. function renderItem(item) {
  5532. var bBox,
  5533. itemWidth,
  5534. legendSymbol,
  5535. symbolX,
  5536. symbolY,
  5537. attribs,
  5538. simpleSymbol,
  5539. li = item.legendItem,
  5540. series = item.series || item,
  5541. i = allItems.length;
  5542. if (!li) { // generate it once, later move it
  5543. // let these series types use a simple symbol
  5544. simpleSymbol = /^(bar|pie|area|column)$/.test(series.type);
  5545. // generate the list item text
  5546. item.legendItem = li = renderer.text(
  5547. options.labelFormatter.call(item),
  5548. 0,
  5549. 0
  5550. )
  5551. .css(item.visible ? itemStyle : itemHiddenStyle)
  5552. .on('mouseover', function() {
  5553. item.setState(HOVER_STATE);
  5554. li.css(itemHoverStyle);
  5555. })
  5556. .on('mouseout', function() {
  5557. li.css(item.visible ? itemStyle : itemHiddenStyle);
  5558. item.setState();
  5559. })
  5560. .on('click', function(event) {
  5561. var strLegendItemClick = 'legendItemClick',
  5562. fnLegendItemClick = function() {
  5563. item.setVisible();
  5564. };
  5565. // click the name or symbol
  5566. if (item.firePointEvent) { // point
  5567. item.firePointEvent(strLegendItemClick, null, fnLegendItemClick);
  5568. } else {
  5569. fireEvent(item, strLegendItemClick, null, fnLegendItemClick);
  5570. }
  5571. })
  5572. .attr({ zIndex: 2 })
  5573. .add(legendGroup);
  5574. // draw the line
  5575. if (!simpleSymbol && item.options && item.options.lineWidth) {
  5576. var itemOptions = item.options;
  5577. attribs = {
  5578. 'stroke-width': itemOptions.lineWidth,
  5579. zIndex: 2
  5580. };
  5581. if (itemOptions.dashStyle) {
  5582. attribs.dashstyle = itemOptions.dashStyle;
  5583. }
  5584. item.legendLine = renderer.path([
  5585. M,
  5586. -symbolWidth - symbolPadding,
  5587. 0,
  5588. L,
  5589. -symbolPadding,
  5590. 0
  5591. ])
  5592. .attr(attribs)
  5593. .add(legendGroup);
  5594. }
  5595. // draw a simple symbol
  5596. if (simpleSymbol) { // bar|pie|area|column
  5597. //legendLayer.drawRect(
  5598. legendSymbol = renderer.rect(
  5599. (symbolX = -symbolWidth - symbolPadding),
  5600. (symbolY = -11),
  5601. symbolWidth,
  5602. 12,
  5603. 2
  5604. ).attr({
  5605. 'stroke-width': 0,
  5606. zIndex: 3
  5607. }).add(legendGroup);
  5608. }
  5609. // draw the marker
  5610. else if (item.options && item.options.marker && item.options.marker.enabled) {
  5611. legendSymbol = renderer.symbol(
  5612. item.symbol,
  5613. (symbolX = -symbolWidth / 2 - symbolPadding),
  5614. (symbolY = -4),
  5615. item.options.marker.radius
  5616. )
  5617. .attr(item.pointAttr[NORMAL_STATE])
  5618. .attr({ zIndex: 3 })
  5619. .add(legendGroup);
  5620. }
  5621. if (legendSymbol) {
  5622. legendSymbol.xOff = symbolX;
  5623. legendSymbol.yOff = symbolY;
  5624. }
  5625. item.legendSymbol = legendSymbol;
  5626. // colorize the items
  5627. colorizeItem(item, item.visible);
  5628. // add the HTML checkbox on top
  5629. if (item.options && item.options.showCheckbox) {
  5630. item.checkbox = createElement('input', {
  5631. type: 'checkbox',
  5632. checked: item.selected,
  5633. defaultChecked: item.selected // required by IE7
  5634. }, options.itemCheckboxStyle, container);
  5635. addEvent(item.checkbox, 'click', function(event) {
  5636. var target = event.target;
  5637. fireEvent(item, 'checkboxClick', {
  5638. checked: target.checked
  5639. },
  5640. function() {
  5641. item.select();
  5642. }
  5643. );
  5644. });
  5645. }
  5646. }
  5647. // calculate the positions for the next line
  5648. bBox = li.getBBox();
  5649. itemWidth = item.legendItemWidth =
  5650. options.itemWidth || symbolWidth + symbolPadding + bBox.width + rightPadding;
  5651. itemHeight = bBox.height;
  5652. // if the item exceeds the width, start a new line
  5653. if (horizontal && itemX - initialItemX + itemWidth >
  5654. (widthOption || (chartWidth - 2 * padding - initialItemX))) {
  5655. itemX = initialItemX;
  5656. itemY += itemHeight;
  5657. }
  5658. lastItemY = itemY;
  5659. // position the newly generated or reordered items
  5660. positionItem(item, itemX, itemY);
  5661. // advance
  5662. if (horizontal) {
  5663. itemX += itemWidth;
  5664. } else {
  5665. itemY += itemHeight;
  5666. }
  5667. // the width of the widest item
  5668. offsetWidth = widthOption || mathMax(
  5669. horizontal ? itemX - initialItemX : itemWidth,
  5670. offsetWidth
  5671. );
  5672. // add it all to an array to use below
  5673. allItems.push(item);
  5674. }
  5675. /**
  5676. * Render the legend. This method can be called both before and after
  5677. * chart.render. If called after, it will only rearrange items instead
  5678. * of creating new ones.
  5679. */
  5680. function renderLegend() {
  5681. itemX = initialItemX;
  5682. itemY = y;
  5683. offsetWidth = 0;
  5684. lastItemY = 0;
  5685. allItems = [];
  5686. if (!legendGroup) {
  5687. legendGroup = renderer.g('legend')
  5688. .attr({ zIndex: 7 })
  5689. .add();
  5690. }
  5691. // add HTML for each series
  5692. if (reversedLegend) {
  5693. series.reverse();
  5694. }
  5695. each(series, function(serie) {
  5696. if (!serie.options.showInLegend) {
  5697. return;
  5698. }
  5699. // use points or series for the legend item depending on legendType
  5700. var items = (serie.options.legendType == 'point') ?
  5701. serie.data : [serie];
  5702. // render all items
  5703. each(items, renderItem);
  5704. });
  5705. if (reversedLegend) { // restore
  5706. series.reverse();
  5707. }
  5708. // Draw the border
  5709. legendWidth = widthOption || offsetWidth;
  5710. legendHeight = lastItemY - y + itemHeight;
  5711. if (legendBorderWidth || legendBackgroundColor) {
  5712. legendWidth += 2 * padding;
  5713. legendHeight += 2 * padding;
  5714. if (!box) {
  5715. box = renderer.rect(
  5716. 0,
  5717. 0,
  5718. legendWidth,
  5719. legendHeight,
  5720. options.borderRadius,
  5721. legendBorderWidth || 0
  5722. ).attr({
  5723. stroke: options.borderColor,
  5724. 'stroke-width': legendBorderWidth || 0,
  5725. fill: legendBackgroundColor || NONE
  5726. })
  5727. .add(legendGroup)
  5728. .shadow(options.shadow);
  5729. } else if (legendWidth > 0 && legendHeight > 0) {
  5730. box.animate(
  5731. box.crisp(null, null, null, legendWidth, legendHeight)
  5732. );
  5733. }
  5734. // hide the border if no items
  5735. box[allItems.length ? 'show' : 'hide']();
  5736. }
  5737. // 1.x compatibility: positioning based on style
  5738. var props = ['left', 'right', 'top', 'bottom'],
  5739. prop,
  5740. i = 4;
  5741. while(i--) {
  5742. prop = props[i];
  5743. if (style[prop] && style[prop] != 'auto') {
  5744. options[i < 2 ? 'align' : 'verticalAlign'] = prop;
  5745. options[i < 2 ? 'x' : 'y'] = pInt(style[prop]) * (i % 2 ? -1 : 1);
  5746. }
  5747. }
  5748. legendGroup.align(extend(options, {
  5749. width: legendWidth,
  5750. height: legendHeight
  5751. }), true, spacingBox);
  5752. if (!isResizing) {
  5753. positionCheckboxes();
  5754. }
  5755. }
  5756. // run legend
  5757. renderLegend();
  5758. // move checkboxes
  5759. addEvent(chart, 'endResize', positionCheckboxes);
  5760. // expose
  5761. return {
  5762. colorizeItem: colorizeItem,
  5763. destroyItem: destroyItem,
  5764. renderLegend: renderLegend
  5765. };
  5766. };
  5767. /**
  5768. * Initialize an individual series, called internally before render time
  5769. */
  5770. function initSeries(options) {
  5771. var type = options.type || optionsChart.type || optionsChart.defaultSeriesType,
  5772. typeClass = seriesTypes[type],
  5773. serie,
  5774. hasRendered = chart.hasRendered;
  5775. // an inverted chart can't take a column series and vice versa
  5776. if (hasRendered) {
  5777. if (inverted && type == 'column') {
  5778. typeClass = seriesTypes.bar;
  5779. } else if (!inverted && type == 'bar') {
  5780. typeClass = seriesTypes.column;
  5781. }
  5782. }
  5783. serie = new typeClass();
  5784. serie.init(chart, options);
  5785. // set internal chart properties
  5786. if (!hasRendered && serie.inverted) {
  5787. inverted = true;
  5788. }
  5789. if (serie.isCartesian) {
  5790. hasCartesianSeries = serie.isCartesian;
  5791. }
  5792. series.push(serie);
  5793. return serie;
  5794. }
  5795. /**
  5796. * Add a series dynamically after time
  5797. *
  5798. * @param {Object} options The config options
  5799. * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true.
  5800. * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
  5801. * configuration
  5802. *
  5803. * @return {Object} series The newly created series object
  5804. */
  5805. function addSeries(options, redraw, animation) {
  5806. var series;
  5807. if (options) {
  5808. setAnimation(animation, chart);
  5809. redraw = pick(redraw, true); // defaults to true
  5810. fireEvent(chart, 'addSeries', { options: options }, function() {
  5811. series = initSeries(options);
  5812. series.isDirty = true;
  5813. chart.isDirtyLegend = true; // the series array is out of sync with the display
  5814. if (redraw) {
  5815. chart.redraw();
  5816. }
  5817. });
  5818. }
  5819. return series;
  5820. }
  5821. /**
  5822. * Check whether a given point is within the plot area
  5823. *
  5824. * @param {Number} x Pixel x relative to the coordinateSystem
  5825. * @param {Number} y Pixel y relative to the coordinateSystem
  5826. */
  5827. isInsidePlot = function(x, y) {
  5828. return x >= 0 &&
  5829. x <= plotWidth &&
  5830. y >= 0 &&
  5831. y <= plotHeight;
  5832. };
  5833. /**
  5834. * Adjust all axes tick amounts
  5835. */
  5836. function adjustTickAmounts() {
  5837. if (optionsChart.alignTicks !== false) {
  5838. each(axes, function(axis) {
  5839. axis.adjustTickAmount();
  5840. });
  5841. }
  5842. maxTicks = null;
  5843. }
  5844. /**
  5845. * Redraw legend, axes or series based on updated data
  5846. *
  5847. * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
  5848. * configuration
  5849. */
  5850. function redraw(animation) {
  5851. var redrawLegend = chart.isDirtyLegend,
  5852. hasStackedSeries,
  5853. isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed?
  5854. seriesLength = series.length,
  5855. i = seriesLength,
  5856. clipRect = chart.clipRect,
  5857. serie;
  5858. setAnimation(animation, chart);
  5859. // link stacked series
  5860. while (i--) {
  5861. serie = series[i];
  5862. if (serie.isDirty && serie.options.stacking) {
  5863. hasStackedSeries = true;
  5864. break;
  5865. }
  5866. }
  5867. if (hasStackedSeries) { // mark others as dirty
  5868. i = seriesLength;
  5869. while (i--) {
  5870. serie = series[i];
  5871. if (serie.options.stacking) {
  5872. serie.isDirty = true;
  5873. }
  5874. }
  5875. }
  5876. // handle updated data in the series
  5877. each(series, function(serie) {
  5878. if (serie.isDirty) { // prepare the data so axis can read it
  5879. serie.cleanData();
  5880. serie.getSegments();
  5881. if (serie.options.legendType == 'point') {
  5882. redrawLegend = true;
  5883. }
  5884. }
  5885. });
  5886. // handle added or removed series
  5887. if (redrawLegend && legend.renderLegend) { // series or pie points are added or removed
  5888. // draw legend graphics
  5889. legend.renderLegend();
  5890. chart.isDirtyLegend = false;
  5891. }
  5892. if (hasCartesianSeries) {
  5893. if (!isResizing) {
  5894. // reset maxTicks
  5895. maxTicks = null;
  5896. // set axes scales
  5897. each(axes, function(axis) {
  5898. axis.setScale();
  5899. });
  5900. }
  5901. adjustTickAmounts();
  5902. getMargins();
  5903. // redraw axes
  5904. each(axes, function(axis) {
  5905. if (axis.isDirty || isDirtyBox) {
  5906. axis.redraw();
  5907. isDirtyBox = true; // always redraw box to reflect changes in the axis labels
  5908. }
  5909. });
  5910. }
  5911. // the plot areas size has changed
  5912. if (isDirtyBox) {
  5913. drawChartBox();
  5914. placeTrackerGroup();
  5915. // move clip rect
  5916. if (clipRect) {
  5917. stop(clipRect);
  5918. clipRect.animate({ // for chart resize
  5919. width: chart.plotSizeX,
  5920. height: chart.plotSizeY
  5921. });
  5922. }
  5923. }
  5924. // redraw affected series
  5925. each(series, function(serie) {
  5926. if (serie.isDirty && serie.visible &&
  5927. (!serie.isCartesian || serie.xAxis)) { // issue #153
  5928. serie.redraw();
  5929. }
  5930. });
  5931. // hide tooltip and hover states
  5932. if (tracker && tracker.resetTracker) {
  5933. tracker.resetTracker();
  5934. }
  5935. // fire the event
  5936. fireEvent(chart, 'redraw');
  5937. }
  5938. /**
  5939. * Dim the chart and show a loading text or symbol
  5940. * @param {String} str An optional text to show in the loading label instead of the default one
  5941. */
  5942. function showLoading(str) {
  5943. var loadingOptions = options.loading;
  5944. // create the layer at the first call
  5945. if (!loadingDiv) {
  5946. loadingDiv = createElement(DIV, {
  5947. className: 'highcharts-loading'
  5948. }, extend(loadingOptions.style, {
  5949. left: plotLeft + PX,
  5950. top: plotTop + PX,
  5951. width: plotWidth + PX,
  5952. height: plotHeight + PX,
  5953. zIndex: 10,
  5954. display: NONE
  5955. }), container);
  5956. loadingSpan = createElement(
  5957. 'span',
  5958. null,
  5959. loadingOptions.labelStyle,
  5960. loadingDiv
  5961. );
  5962. }
  5963. // update text
  5964. loadingSpan.innerHTML = str || options.lang.loading;
  5965. // show it
  5966. if (!loadingShown) {
  5967. css(loadingDiv, { opacity: 0, display: '' });
  5968. animate(loadingDiv, {
  5969. opacity: loadingOptions.style.opacity
  5970. }, {
  5971. duration: loadingOptions.showDuration
  5972. });
  5973. loadingShown = true;
  5974. }
  5975. }
  5976. /**
  5977. * Hide the loading layer
  5978. */
  5979. function hideLoading() {
  5980. animate(loadingDiv, {
  5981. opacity: 0
  5982. }, {
  5983. duration: options.loading.hideDuration,
  5984. complete: function() {
  5985. css(loadingDiv, { display: NONE });
  5986. }
  5987. });
  5988. loadingShown = false;
  5989. }
  5990. /**
  5991. * Get an axis, series or point object by id.
  5992. * @param id {String} The id as given in the configuration options
  5993. */
  5994. function get(id) {
  5995. var i,
  5996. j,
  5997. data;
  5998. // search axes
  5999. for (i = 0; i < axes.length; i++) {
  6000. if (axes[i].options.id == id) {
  6001. return axes[i];
  6002. }
  6003. }
  6004. // search series
  6005. for (i = 0; i < series.length; i++) {
  6006. if (series[i].options.id == id) {
  6007. return series[i];
  6008. }
  6009. }
  6010. // search points
  6011. for (i = 0; i < series.length; i++) {
  6012. data = series[i].data;
  6013. for (j = 0; j < data.length; j++) {
  6014. if (data[j].id == id) {
  6015. return data[j];
  6016. }
  6017. }
  6018. }
  6019. return null;
  6020. }
  6021. /**
  6022. * Create the Axis instances based on the config options
  6023. */
  6024. function getAxes() {
  6025. var xAxisOptions = options.xAxis || {},
  6026. yAxisOptions = options.yAxis || {},
  6027. axis;
  6028. // make sure the options are arrays and add some members
  6029. xAxisOptions = splat(xAxisOptions);
  6030. each(xAxisOptions, function(axis, i) {
  6031. axis.index = i;
  6032. axis.isX = true;
  6033. });
  6034. yAxisOptions = splat(yAxisOptions);
  6035. each(yAxisOptions, function(axis, i) {
  6036. axis.index = i;
  6037. });
  6038. // concatenate all axis options into one array
  6039. axes = xAxisOptions.concat(yAxisOptions);
  6040. // loop the options and construct axis objects
  6041. chart.xAxis = [];
  6042. chart.yAxis = [];
  6043. axes = map(axes, function(axisOptions) {
  6044. axis = new Axis(chart, axisOptions);
  6045. chart[axis.isXAxis ? 'xAxis' : 'yAxis'].push(axis);
  6046. return axis;
  6047. });
  6048. adjustTickAmounts();
  6049. }
  6050. /**
  6051. * Get the currently selected points from all series
  6052. */
  6053. function getSelectedPoints() {
  6054. var points = [];
  6055. each(series, function(serie) {
  6056. points = points.concat( grep( serie.data, function(point) {
  6057. return point.selected;
  6058. }));
  6059. });
  6060. return points;
  6061. }
  6062. /**
  6063. * Get the currently selected series
  6064. */
  6065. function getSelectedSeries() {
  6066. return grep(series, function (serie) {
  6067. return serie.selected;
  6068. });
  6069. }
  6070. /**
  6071. * Zoom out to 1:1
  6072. */
  6073. zoomOut = function () {
  6074. fireEvent(chart, 'selection', { resetSelection: true }, zoom);
  6075. chart.toolbar.remove('zoom');
  6076. };
  6077. /**
  6078. * Zoom into a given portion of the chart given by axis coordinates
  6079. * @param {Object} event
  6080. */
  6081. zoom = function (event) {
  6082. // add button to reset selection
  6083. var lang = defaultOptions.lang,
  6084. animate = chart.pointCount < 100;
  6085. chart.toolbar.add('zoom', lang.resetZoom, lang.resetZoomTitle, zoomOut);
  6086. // if zoom is called with no arguments, reset the axes
  6087. if (!event || event.resetSelection) {
  6088. each(axes, function(axis) {
  6089. axis.setExtremes(null, null, false, animate);
  6090. });
  6091. }
  6092. // else, zoom in on all axes
  6093. else {
  6094. each(event.xAxis.concat(event.yAxis), function(axisData) {
  6095. var axis = axisData.axis;
  6096. // don't zoom more than maxZoom
  6097. if (chart.tracker[axis.isXAxis ? 'zoomX' : 'zoomY']) {
  6098. axis.setExtremes(axisData.min, axisData.max, false, animate);
  6099. }
  6100. });
  6101. }
  6102. // redraw chart
  6103. redraw();
  6104. };
  6105. /**
  6106. * Show the title and subtitle of the chart
  6107. *
  6108. * @param titleOptions {Object} New title options
  6109. * @param subtitleOptions {Object} New subtitle options
  6110. *
  6111. */
  6112. function setTitle (titleOptions, subtitleOptions) {
  6113. chartTitleOptions = merge(options.title, titleOptions);
  6114. chartSubtitleOptions = merge(options.subtitle, subtitleOptions);
  6115. // add title and subtitle
  6116. each([
  6117. ['title', titleOptions, chartTitleOptions],
  6118. ['subtitle', subtitleOptions, chartSubtitleOptions]
  6119. ], function(arr) {
  6120. var name = arr[0],
  6121. title = chart[name],
  6122. titleOptions = arr[1],
  6123. chartTitleOptions = arr[2];
  6124. if (title && titleOptions) {
  6125. title.destroy(); // remove old
  6126. title = null;
  6127. }
  6128. if (chartTitleOptions && chartTitleOptions.text && !title) {
  6129. chart[name] = renderer.text(
  6130. chartTitleOptions.text,
  6131. 0,
  6132. 0
  6133. )
  6134. .attr({
  6135. align: chartTitleOptions.align,
  6136. 'class': 'highcharts-'+ name,
  6137. zIndex: 1
  6138. })
  6139. .css(chartTitleOptions.style)
  6140. .add()
  6141. .align(chartTitleOptions, false, spacingBox);
  6142. }
  6143. });
  6144. }
  6145. /**
  6146. * Get chart width and height according to options and container size
  6147. */
  6148. function getChartSize() {
  6149. containerWidth = (renderToClone || renderTo).offsetWidth;
  6150. containerHeight = (renderToClone || renderTo).offsetHeight;
  6151. chart.chartWidth = chartWidth = optionsChart.width || containerWidth || 600;
  6152. chart.chartHeight = chartHeight = optionsChart.height ||
  6153. // the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7:
  6154. (containerHeight > 19 ? containerHeight : 400);
  6155. }
  6156. /**
  6157. * Get the containing element, determine the size and create the inner container
  6158. * div to hold the chart
  6159. */
  6160. function getContainer() {
  6161. renderTo = optionsChart.renderTo;
  6162. containerId = PREFIX + idCounter++;
  6163. if (isString(renderTo)) {
  6164. renderTo = doc.getElementById(renderTo);
  6165. }
  6166. // remove previous chart
  6167. renderTo.innerHTML = '';
  6168. // If the container doesn't have an offsetWidth, it has or is a child of a node
  6169. // that has display:none. We need to temporarily move it out to a visible
  6170. // state to determine the size, else the legend and tooltips won't render
  6171. // properly
  6172. if (!renderTo.offsetWidth) {
  6173. renderToClone = renderTo.cloneNode(0);
  6174. css(renderToClone, {
  6175. position: ABSOLUTE,
  6176. top: '-9999px',
  6177. display: ''
  6178. });
  6179. doc.body.appendChild(renderToClone);
  6180. }
  6181. // get the width and height
  6182. getChartSize();
  6183. // create the inner container
  6184. chart.container = container = createElement(DIV, {
  6185. className: 'highcharts-container' +
  6186. (optionsChart.className ? ' '+ optionsChart.className : ''),
  6187. id: containerId
  6188. }, extend({
  6189. position: RELATIVE,
  6190. overflow: HIDDEN, // needed for context menu (avoid scrollbars) and
  6191. // content overflow in IE
  6192. width: chartWidth + PX,
  6193. height: chartHeight + PX,
  6194. textAlign: 'left'
  6195. }, optionsChart.style),
  6196. renderToClone || renderTo
  6197. );
  6198. chart.renderer = renderer =
  6199. optionsChart.forExport ? // force SVG, used for SVG export
  6200. new SVGRenderer(container, chartWidth, chartHeight, true) :
  6201. new Renderer(container, chartWidth, chartHeight);
  6202. // Issue 110 workaround:
  6203. // In Firefox, if a div is positioned by percentage, its pixel position may land
  6204. // between pixels. The container itself doesn't display this, but an SVG element
  6205. // inside this container will be drawn at subpixel precision. In order to draw
  6206. // sharp lines, this must be compensated for. This doesn't seem to work inside
  6207. // iframes though (like in jsFiddle).
  6208. var subPixelFix, rect;
  6209. if (isFirefox && container.getBoundingClientRect) {
  6210. subPixelFix = function() {
  6211. css(container, { left: 0, top: 0 });
  6212. rect = container.getBoundingClientRect();
  6213. css(container, {
  6214. left: (-rect.left % 1) + PX,
  6215. top: (-rect.top % 1) + PX
  6216. });
  6217. };
  6218. // run the fix now
  6219. subPixelFix();
  6220. // run it on resize
  6221. addEvent(win, 'resize', subPixelFix);
  6222. // remove it on chart destroy
  6223. addEvent(chart, 'destroy', function() {
  6224. removeEvent(win, 'resize', subPixelFix);
  6225. });
  6226. }
  6227. }
  6228. /**
  6229. * Calculate margins by rendering axis labels in a preliminary position. Title,
  6230. * subtitle and legend have already been rendered at this stage, but will be
  6231. * moved into their final positions
  6232. */
  6233. getMargins = function() {
  6234. var legendOptions = options.legend,
  6235. legendMargin = pick(legendOptions.margin, 10),
  6236. legendX = legendOptions.x,
  6237. legendY = legendOptions.y,
  6238. align = legendOptions.align,
  6239. verticalAlign = legendOptions.verticalAlign,
  6240. titleOffset;
  6241. resetMargins();
  6242. // adjust for title and subtitle
  6243. if ((chart.title || chart.subtitle) && !defined(optionsMarginTop)) {
  6244. titleOffset = mathMax(
  6245. chart.title && !chartTitleOptions.floating && !chartTitleOptions.verticalAlign && chartTitleOptions.y || 0,
  6246. chart.subtitle && !chartSubtitleOptions.floating && !chartSubtitleOptions.verticalAlign && chartSubtitleOptions.y || 0
  6247. );
  6248. if (titleOffset) {
  6249. plotTop = mathMax(plotTop, titleOffset + pick(chartTitleOptions.margin, 15) + spacingTop);
  6250. }
  6251. }
  6252. // adjust for legend
  6253. if (legendOptions.enabled && !legendOptions.floating) {
  6254. if (align == 'right') { // horizontal alignment handled first
  6255. if (!defined(optionsMarginRight)) {
  6256. marginRight = mathMax(
  6257. marginRight,
  6258. legendWidth - legendX + legendMargin + spacingRight
  6259. );
  6260. }
  6261. } else if (align == 'left') {
  6262. if (!defined(optionsMarginLeft)) {
  6263. plotLeft = mathMax(
  6264. plotLeft,
  6265. legendWidth + legendX + legendMargin + spacingLeft
  6266. );
  6267. }
  6268. } else if (verticalAlign == 'top') {
  6269. if (!defined(optionsMarginTop)) {
  6270. plotTop = mathMax(
  6271. plotTop,
  6272. legendHeight + legendY + legendMargin + spacingTop
  6273. );
  6274. }
  6275. } else if (verticalAlign == 'bottom') {
  6276. if (!defined(optionsMarginBottom)) {
  6277. marginBottom = mathMax(
  6278. marginBottom,
  6279. legendHeight - legendY + legendMargin + spacingBottom
  6280. );
  6281. }
  6282. }
  6283. }
  6284. // pre-render axes to get labels offset width
  6285. if (hasCartesianSeries) {
  6286. each(axes, function(axis) {
  6287. axis.getOffset();
  6288. });
  6289. }
  6290. if (!defined(optionsMarginLeft)) {
  6291. plotLeft += axisOffset[3];
  6292. }
  6293. if (!defined(optionsMarginTop)) {
  6294. plotTop += axisOffset[0];
  6295. }
  6296. if (!defined(optionsMarginBottom)) {
  6297. marginBottom += axisOffset[2];
  6298. }
  6299. if (!defined(optionsMarginRight)) {
  6300. marginRight += axisOffset[1];
  6301. }
  6302. setChartSize();
  6303. };
  6304. /**
  6305. * Add the event handlers necessary for auto resizing
  6306. *
  6307. */
  6308. function initReflow() {
  6309. var reflowTimeout;
  6310. function reflow() {
  6311. var width = optionsChart.width || renderTo.offsetWidth,
  6312. height = optionsChart.height || renderTo.offsetHeight;
  6313. if (width && height) { // means container is display:none
  6314. if (width != containerWidth || height != containerHeight) {
  6315. clearTimeout(reflowTimeout);
  6316. reflowTimeout = setTimeout(function() {
  6317. resize(width, height, false);
  6318. }, 100);
  6319. }
  6320. containerWidth = width;
  6321. containerHeight = height;
  6322. }
  6323. }
  6324. addEvent(window, 'resize', reflow);
  6325. addEvent(chart, 'destroy', function() {
  6326. removeEvent(window, 'resize', reflow);
  6327. });
  6328. }
  6329. /**
  6330. * Resize the chart to a given width and height
  6331. * @param {Number} width
  6332. * @param {Number} height
  6333. * @param {Object|Boolean} animation
  6334. */
  6335. resize = function(width, height, animation) {
  6336. var chartTitle = chart.title,
  6337. chartSubtitle = chart.subtitle;
  6338. isResizing += 1;
  6339. // set the animation for the current process
  6340. setAnimation(animation, chart);
  6341. oldChartHeight = chartHeight;
  6342. oldChartWidth = chartWidth;
  6343. chartWidth = mathRound(width);
  6344. chartHeight = mathRound(height);
  6345. css(container, {
  6346. width: chartWidth + PX,
  6347. height: chartHeight + PX
  6348. });
  6349. renderer.setSize(chartWidth, chartHeight, animation);
  6350. // update axis lengths for more correct tick intervals:
  6351. plotWidth = chartWidth - plotLeft - marginRight;
  6352. plotHeight = chartHeight - plotTop - marginBottom;
  6353. // handle axes
  6354. maxTicks = null;
  6355. each(axes, function(axis) {
  6356. axis.isDirty = true;
  6357. axis.setScale();
  6358. });
  6359. // make sure non-cartesian series are also handled
  6360. each(series, function(serie) {
  6361. serie.isDirty = true;
  6362. });
  6363. chart.isDirtyLegend = true; // force legend redraw
  6364. chart.isDirtyBox = true; // force redraw of plot and chart border
  6365. getMargins();
  6366. // move titles
  6367. if (chartTitle) {
  6368. chartTitle.align(null, null, spacingBox);
  6369. }
  6370. if (chartSubtitle) {
  6371. chartSubtitle.align(null, null, spacingBox);
  6372. }
  6373. redraw(animation);
  6374. oldChartHeight = null;
  6375. fireEvent(chart, 'resize');
  6376. // fire endResize and set isResizing back
  6377. setTimeout(function() {
  6378. fireEvent(chart, 'endResize', null, function() {
  6379. isResizing -= 1;
  6380. });
  6381. }, globalAnimation && globalAnimation.duration || 500);
  6382. };
  6383. /**
  6384. * Set the public chart properties. This is done before and after the pre-render
  6385. * to determine margin sizes
  6386. */
  6387. setChartSize = function() {
  6388. chart.plotLeft = plotLeft = mathRound(plotLeft);
  6389. chart.plotTop = plotTop = mathRound(plotTop);
  6390. chart.plotWidth = plotWidth = mathRound(chartWidth - plotLeft - marginRight);
  6391. chart.plotHeight = plotHeight = mathRound(chartHeight - plotTop - marginBottom);
  6392. chart.plotSizeX = inverted ? plotHeight : plotWidth;
  6393. chart.plotSizeY = inverted ? plotWidth : plotHeight;
  6394. spacingBox = {
  6395. x: spacingLeft,
  6396. y: spacingTop,
  6397. width: chartWidth - spacingLeft - spacingRight,
  6398. height: chartHeight - spacingTop - spacingBottom
  6399. };
  6400. };
  6401. /**
  6402. * Initial margins before auto size margins are applied
  6403. */
  6404. resetMargins = function() {
  6405. plotTop = pick(optionsMarginTop, spacingTop);
  6406. marginRight = pick(optionsMarginRight, spacingRight);
  6407. marginBottom = pick(optionsMarginBottom, spacingBottom);
  6408. plotLeft = pick(optionsMarginLeft, spacingLeft);
  6409. axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
  6410. };
  6411. /**
  6412. * Draw the borders and backgrounds for chart and plot area
  6413. */
  6414. drawChartBox = function() {
  6415. var chartBorderWidth = optionsChart.borderWidth || 0,
  6416. chartBackgroundColor = optionsChart.backgroundColor,
  6417. plotBackgroundColor = optionsChart.plotBackgroundColor,
  6418. plotBackgroundImage = optionsChart.plotBackgroundImage,
  6419. mgn,
  6420. plotSize = {
  6421. x: plotLeft,
  6422. y: plotTop,
  6423. width: plotWidth,
  6424. height: plotHeight
  6425. };
  6426. // Chart area
  6427. mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);
  6428. if (chartBorderWidth || chartBackgroundColor) {
  6429. if (!chartBackground) {
  6430. chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn,
  6431. optionsChart.borderRadius, chartBorderWidth)
  6432. .attr({
  6433. stroke: optionsChart.borderColor,
  6434. 'stroke-width': chartBorderWidth,
  6435. fill: chartBackgroundColor || NONE
  6436. })
  6437. .add()
  6438. .shadow(optionsChart.shadow);
  6439. } else { // resize
  6440. chartBackground.animate(
  6441. chartBackground.crisp(null, null, null, chartWidth - mgn, chartHeight - mgn)
  6442. );
  6443. }
  6444. }
  6445. // Plot background
  6446. if (plotBackgroundColor) {
  6447. if (!plotBackground) {
  6448. plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0)
  6449. .attr({
  6450. fill: plotBackgroundColor
  6451. })
  6452. .add()
  6453. .shadow(optionsChart.plotShadow);
  6454. } else {
  6455. plotBackground.animate(plotSize);
  6456. }
  6457. }
  6458. if (plotBackgroundImage) {
  6459. if (!plotBGImage) {
  6460. plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight)
  6461. .add();
  6462. } else {
  6463. plotBGImage.animate(plotSize);
  6464. }
  6465. }
  6466. // Plot area border
  6467. if (optionsChart.plotBorderWidth) {
  6468. if (!plotBorder) {
  6469. plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, optionsChart.plotBorderWidth)
  6470. .attr({
  6471. stroke: optionsChart.plotBorderColor,
  6472. 'stroke-width': optionsChart.plotBorderWidth,
  6473. zIndex: 4
  6474. })
  6475. .add();
  6476. } else {
  6477. plotBorder.animate(
  6478. plotBorder.crisp(null, plotLeft, plotTop, plotWidth, plotHeight)
  6479. );
  6480. }
  6481. }
  6482. // reset
  6483. chart.isDirtyBox = false;
  6484. };
  6485. /**
  6486. * Render all graphics for the chart
  6487. */
  6488. function render () {
  6489. var labels = options.labels,
  6490. credits = options.credits,
  6491. creditsHref;
  6492. // Title
  6493. setTitle();
  6494. // Legend
  6495. legend = chart.legend = new Legend(chart);
  6496. // Get margins by pre-rendering axes
  6497. getMargins();
  6498. each(axes, function(axis) {
  6499. axis.setTickPositions(true); // update to reflect the new margins
  6500. });
  6501. adjustTickAmounts();
  6502. getMargins(); // second pass to check for new labels
  6503. // Draw the borders and backgrounds
  6504. drawChartBox();
  6505. // Axes
  6506. if (hasCartesianSeries) {
  6507. each(axes, function(axis) {
  6508. axis.render();
  6509. });
  6510. }
  6511. // The series
  6512. if (!chart.seriesGroup) {
  6513. chart.seriesGroup = renderer.g('series-group')
  6514. .attr({ zIndex: 3 })
  6515. .add();
  6516. }
  6517. each(series, function(serie) {
  6518. serie.translate();
  6519. serie.setTooltipPoints();
  6520. serie.render();
  6521. });
  6522. // Labels
  6523. if (labels.items) {
  6524. each(labels.items, function() {
  6525. var style = extend(labels.style, this.style),
  6526. x = pInt(style.left) + plotLeft,
  6527. y = pInt(style.top) + plotTop + 12;
  6528. // delete to prevent rewriting in IE
  6529. delete style.left;
  6530. delete style.top;
  6531. renderer.text(
  6532. this.html,
  6533. x,
  6534. y
  6535. )
  6536. .attr({ zIndex: 2 })
  6537. .css(style)
  6538. .add();
  6539. });
  6540. }
  6541. // Toolbar (don't redraw)
  6542. if (!chart.toolbar) {
  6543. chart.toolbar = Toolbar(chart);
  6544. }
  6545. // Credits
  6546. if (credits.enabled && !chart.credits) {
  6547. creditsHref = credits.href;
  6548. renderer.text(
  6549. credits.text,
  6550. 0,
  6551. 0
  6552. )
  6553. .on('click', function() {
  6554. if (creditsHref) {
  6555. location.href = creditsHref;
  6556. }
  6557. })
  6558. .attr({
  6559. align: credits.position.align,
  6560. zIndex: 8
  6561. })
  6562. .css(credits.style)
  6563. .add()
  6564. .align(credits.position);
  6565. }
  6566. placeTrackerGroup();
  6567. // Set flag
  6568. chart.hasRendered = true;
  6569. // If the chart was rendered outside the top container, put it back in
  6570. if (renderToClone) {
  6571. renderTo.appendChild(container);
  6572. discardElement(renderToClone);
  6573. //updatePosition(container);
  6574. }
  6575. }
  6576. /**
  6577. * Clean up memory usage
  6578. */
  6579. function destroy() {
  6580. var i = series.length,
  6581. parentNode = container && container.parentNode;
  6582. // fire the chart.destoy event
  6583. fireEvent(chart, 'destroy');
  6584. // remove events
  6585. removeEvent(win, 'unload', destroy);
  6586. removeEvent(chart);
  6587. each(axes, function(axis) {
  6588. removeEvent(axis);
  6589. });
  6590. // destroy each series
  6591. while (i--) {
  6592. series[i].destroy();
  6593. }
  6594. // remove container and all SVG
  6595. if (container) { // can break in IE when destroyed before finished loading
  6596. container.innerHTML = '';
  6597. removeEvent(container);
  6598. if (parentNode) {
  6599. parentNode.removeChild(container);
  6600. }
  6601. // IE6 leak
  6602. container = null;
  6603. }
  6604. // IE7 leak
  6605. if (renderer) { // can break in IE when destroyed before finished loading
  6606. renderer.alignedObjects = null;
  6607. }
  6608. // memory and CPU leak
  6609. clearInterval(tooltipInterval);
  6610. // clean it all up
  6611. for (i in chart) {
  6612. delete chart[i];
  6613. }
  6614. }
  6615. /**
  6616. * Prepare for first rendering after all data are loaded
  6617. */
  6618. function firstRender() {
  6619. // VML namespaces can't be added until after complete. Listening
  6620. // for Perini's doScroll hack is not enough.
  6621. var onreadystatechange = 'onreadystatechange';
  6622. if (!hasSVG && win == win.top && doc.readyState != 'complete') {
  6623. doc.attachEvent(onreadystatechange, function() {
  6624. doc.detachEvent(onreadystatechange, firstRender);
  6625. firstRender();
  6626. });
  6627. return;
  6628. }
  6629. // create the container
  6630. getContainer();
  6631. resetMargins();
  6632. setChartSize();
  6633. // Initialize the series
  6634. each(options.series || [], function(serieOptions) {
  6635. initSeries(serieOptions);
  6636. });
  6637. // Set the common inversion and transformation for inverted series after initSeries
  6638. chart.inverted = inverted = pick(inverted, options.chart.inverted);
  6639. getAxes();
  6640. chart.render = render;
  6641. // depends on inverted and on margins being set
  6642. chart.tracker = tracker = new MouseTracker(chart, options.tooltip);
  6643. //globalAnimation = false;
  6644. render();
  6645. fireEvent(chart, 'load');
  6646. //globalAnimation = true;
  6647. // run callbacks
  6648. if (callback) {
  6649. callback.apply(chart, [chart]);
  6650. }
  6651. each(chart.callbacks, function(fn) {
  6652. fn.apply(chart, [chart]);
  6653. });
  6654. }
  6655. // Run chart
  6656. // Set to zero for each new chart
  6657. colorCounter = 0;
  6658. symbolCounter = 0;
  6659. // Destroy the chart and free up memory.
  6660. addEvent(win, 'unload', destroy);
  6661. // Set up auto resize
  6662. if (optionsChart.reflow !== false) {
  6663. addEvent(chart, 'load', initReflow);
  6664. }
  6665. // Chart event handlers
  6666. if (chartEvents) {
  6667. for (eventType in chartEvents) {
  6668. addEvent(chart, eventType, chartEvents[eventType]);
  6669. }
  6670. }
  6671. chart.options = options;
  6672. chart.series = series;
  6673. // Expose methods and variables
  6674. chart.addSeries = addSeries;
  6675. chart.animation = pick(optionsChart.animation, true);
  6676. chart.destroy = destroy;
  6677. chart.get = get;
  6678. chart.getSelectedPoints = getSelectedPoints;
  6679. chart.getSelectedSeries = getSelectedSeries;
  6680. chart.hideLoading = hideLoading;
  6681. chart.isInsidePlot = isInsidePlot;
  6682. chart.redraw = redraw;
  6683. chart.setSize = resize;
  6684. chart.setTitle = setTitle;
  6685. chart.showLoading = showLoading;
  6686. chart.pointCount = 0;
  6687. /*
  6688. if ($) $(function() {
  6689. $container = $('#container');
  6690. var origChartWidth,
  6691. origChartHeight;
  6692. if ($container) {
  6693. $('<button>+</button>')
  6694. .insertBefore($container)
  6695. .click(function() {
  6696. if (origChartWidth === UNDEFINED) {
  6697. origChartWidth = chartWidth;
  6698. origChartHeight = chartHeight;
  6699. }
  6700. chart.resize(chartWidth *= 1.1, chartHeight *= 1.1);
  6701. });
  6702. $('<button>-</button>')
  6703. .insertBefore($container)
  6704. .click(function() {
  6705. if (origChartWidth === UNDEFINED) {
  6706. origChartWidth = chartWidth;
  6707. origChartHeight = chartHeight;
  6708. }
  6709. chart.resize(chartWidth *= 0.9, chartHeight *= 0.9);
  6710. });
  6711. $('<button>1:1</button>')
  6712. .insertBefore($container)
  6713. .click(function() {
  6714. if (origChartWidth === UNDEFINED) {
  6715. origChartWidth = chartWidth;
  6716. origChartHeight = chartHeight;
  6717. }
  6718. chart.resize(origChartWidth, origChartHeight);
  6719. });
  6720. }
  6721. })
  6722. */
  6723. firstRender();
  6724. } // end Chart
  6725. // Hook for exporting module
  6726. Chart.prototype.callbacks = [];
  6727. /**
  6728. * The Point object and prototype. Inheritable and used as base for PiePoint
  6729. */
  6730. var Point = function() {};
  6731. Point.prototype = {
  6732. /**
  6733. * Initialize the point
  6734. * @param {Object} series The series object containing this point
  6735. * @param {Object} options The data in either number, array or object format
  6736. */
  6737. init: function(series, options) {
  6738. var point = this,
  6739. defaultColors;
  6740. point.series = series;
  6741. point.applyOptions(options);
  6742. point.pointAttr = {};
  6743. if (series.options.colorByPoint) {
  6744. defaultColors = series.chart.options.colors;
  6745. if (!point.options) {
  6746. point.options = {};
  6747. }
  6748. point.color = point.options.color = point.color || defaultColors[colorCounter++];
  6749. // loop back to zero
  6750. if (colorCounter >= defaultColors.length) {
  6751. colorCounter = 0;
  6752. }
  6753. }
  6754. series.chart.pointCount++;
  6755. return point;
  6756. },
  6757. /**
  6758. * Apply the options containing the x and y data and possible some extra properties.
  6759. * This is called on point init or from point.update.
  6760. *
  6761. * @param {Object} options
  6762. */
  6763. applyOptions: function(options) {
  6764. var point = this,
  6765. series = point.series;
  6766. point.config = options;
  6767. // onedimensional array input
  6768. if (isNumber(options) || options === null) {
  6769. point.y = options;
  6770. }
  6771. // object input
  6772. else if (isObject(options) && !isNumber(options.length)) {
  6773. // copy options directly to point
  6774. extend(point, options);
  6775. point.options = options;
  6776. }
  6777. // categorized data with name in first position
  6778. else if (isString(options[0])) {
  6779. point.name = options[0];
  6780. point.y = options[1];
  6781. }
  6782. // two-dimentional array
  6783. else if (isNumber(options[0])) {
  6784. point.x = options[0];
  6785. point.y = options[1];
  6786. }
  6787. /*
  6788. * If no x is set by now, get auto incremented value. All points must have an
  6789. * x value, however the y value can be null to create a gap in the series
  6790. */
  6791. if (point.x === UNDEFINED) {
  6792. point.x = series.autoIncrement();
  6793. }
  6794. },
  6795. /**
  6796. * Destroy a point to clear memory. Its reference still stays in series.data.
  6797. */
  6798. destroy: function() {
  6799. var point = this,
  6800. series = point.series,
  6801. prop;
  6802. series.chart.pointCount--;
  6803. if (point == series.chart.hoverPoint) {
  6804. point.onMouseOut();
  6805. }
  6806. series.chart.hoverPoints = null; // remove reference
  6807. // remove all events
  6808. removeEvent(point);
  6809. each(['graphic', 'tracker', 'group', 'dataLabel', 'connector'], function(prop) {
  6810. if (point[prop]) {
  6811. point[prop].destroy();
  6812. }
  6813. });
  6814. if (point.legendItem) { // pies have legend items
  6815. point.series.chart.legend.destroyItem(point);
  6816. }
  6817. for (prop in point) {
  6818. point[prop] = null;
  6819. }
  6820. },
  6821. /**
  6822. * Toggle the selection status of a point
  6823. * @param {Boolean} selected Whether to select or unselect the point.
  6824. * @param {Boolean} accumulate Whether to add to the previous selection. By default,
  6825. * this happens if the control key (Cmd on Mac) was pressed during clicking.
  6826. */
  6827. select: function(selected, accumulate) {
  6828. var point = this,
  6829. series = point.series,
  6830. chart = series.chart;
  6831. point.selected = selected = pick(selected, !point.selected);
  6832. //series.isDirty = true;
  6833. point.firePointEvent(selected ? 'select' : 'unselect');
  6834. point.setState(selected && SELECT_STATE);
  6835. // unselect all other points unless Ctrl or Cmd + click
  6836. if (!accumulate) {
  6837. each(chart.getSelectedPoints(), function (loopPoint) {
  6838. if (loopPoint.selected && loopPoint != point) {
  6839. loopPoint.selected = false;
  6840. loopPoint.setState(NORMAL_STATE);
  6841. loopPoint.firePointEvent('unselect');
  6842. }
  6843. });
  6844. }
  6845. },
  6846. onMouseOver: function() {
  6847. var point = this,
  6848. chart = point.series.chart,
  6849. tooltip = chart.tooltip,
  6850. hoverPoint = chart.hoverPoint;
  6851. // set normal state to previous series
  6852. if (hoverPoint && hoverPoint != point) {
  6853. hoverPoint.onMouseOut();
  6854. }
  6855. // trigger the event
  6856. point.firePointEvent('mouseOver');
  6857. // update the tooltip
  6858. if (tooltip && !tooltip.shared) {
  6859. tooltip.refresh(point);
  6860. }
  6861. // hover this
  6862. point.setState(HOVER_STATE);
  6863. chart.hoverPoint = point;
  6864. },
  6865. onMouseOut: function() {
  6866. var point = this;
  6867. point.firePointEvent('mouseOut');
  6868. point.setState();
  6869. point.series.chart.hoverPoint = null;
  6870. },
  6871. /**
  6872. * Extendable method for formatting each point's tooltip line
  6873. *
  6874. * @param {Boolean} useHeader Whether a common header is used for multiple series in the tooltip
  6875. *
  6876. * @return {String} A string to be concatenated in to the common tooltip text
  6877. */
  6878. tooltipFormatter: function(useHeader) {
  6879. var point = this,
  6880. series = point.series;
  6881. return ['<span style="color:'+ series.color +'">', (point.name || series.name), '</span>: ',
  6882. (!useHeader ? ('<b>x = '+ (point.name || point.x) + ',</b> ') : ''),
  6883. '<b>', (!useHeader ? 'y = ' : '' ), point.y, '</b><br/>'].join('');
  6884. },
  6885. /**
  6886. * Get the formatted text for this point's data label
  6887. *
  6888. * @return {String} The formatted data label pseudo-HTML
  6889. */
  6890. getDataLabelText: function() {
  6891. var point = this;
  6892. return this.series.options.dataLabels.formatter.call({
  6893. x: point.x,
  6894. y: point.y,
  6895. series: point.series,
  6896. point: point,
  6897. percentage: point.percentage,
  6898. total: point.total || point.stackTotal
  6899. });
  6900. },
  6901. /**
  6902. * Update the point with new options (typically x/y data) and optionally redraw the series.
  6903. *
  6904. * @param {Object} options Point options as defined in the series.data array
  6905. * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
  6906. * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
  6907. * configuration
  6908. *
  6909. */
  6910. update: function(options, redraw, animation) {
  6911. var point = this,
  6912. series = point.series,
  6913. dataLabel = point.dataLabel,
  6914. graphic = point.graphic,
  6915. chart = series.chart;
  6916. redraw = pick(redraw, true);
  6917. // fire the event with a default handler of doing the update
  6918. point.firePointEvent('update', { options: options }, function() {
  6919. point.applyOptions(options);
  6920. if (dataLabel) {
  6921. dataLabel.attr({
  6922. text: point.getDataLabelText()
  6923. })
  6924. }
  6925. // update visuals
  6926. if (isObject(options)) {
  6927. series.getAttribs();
  6928. if (graphic) {
  6929. graphic.attr(point.pointAttr[series.state]);
  6930. }
  6931. }
  6932. // redraw
  6933. series.isDirty = true;
  6934. if (redraw) {
  6935. chart.redraw(animation);
  6936. }
  6937. });
  6938. },
  6939. /**
  6940. * Remove a point and optionally redraw the series and if necessary the axes
  6941. * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
  6942. * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
  6943. * configuration
  6944. */
  6945. remove: function(redraw, animation) {
  6946. var point = this,
  6947. series = point.series,
  6948. chart = series.chart,
  6949. data = series.data;
  6950. setAnimation(animation, chart);
  6951. redraw = pick(redraw, true);
  6952. // fire the event with a default handler of removing the point
  6953. point.firePointEvent('remove', null, function() {
  6954. erase(data, point);
  6955. point.destroy();
  6956. // redraw
  6957. series.isDirty = true;
  6958. if (redraw) {
  6959. chart.redraw();
  6960. }
  6961. });
  6962. },
  6963. /**
  6964. * Fire an event on the Point object. Must not be renamed to fireEvent, as this
  6965. * causes a name clash in MooTools
  6966. * @param {String} eventType
  6967. * @param {Object} eventArgs Additional event arguments
  6968. * @param {Function} defaultFunction Default event handler
  6969. */
  6970. firePointEvent: function(eventType, eventArgs, defaultFunction) {
  6971. var point = this,
  6972. series = this.series,
  6973. seriesOptions = series.options;
  6974. // load event handlers on demand to save time on mouseover/out
  6975. if (seriesOptions.point.events[eventType] || (
  6976. point.options && point.options.events && point.options.events[eventType])) {
  6977. this.importEvents();
  6978. }
  6979. // add default handler if in selection mode
  6980. if (eventType == 'click' && seriesOptions.allowPointSelect) {
  6981. defaultFunction = function (event) {
  6982. // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
  6983. point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
  6984. };
  6985. }
  6986. fireEvent(this, eventType, eventArgs, defaultFunction);
  6987. },
  6988. /**
  6989. * Import events from the series' and point's options. Only do it on
  6990. * demand, to save processing time on hovering.
  6991. */
  6992. importEvents: function() {
  6993. if (!this.hasImportedEvents) {
  6994. var point = this,
  6995. options = merge(point.series.options.point, point.options),
  6996. events = options.events,
  6997. eventType;
  6998. point.events = events;
  6999. for (eventType in events) {
  7000. addEvent(point, eventType, events[eventType]);
  7001. }
  7002. this.hasImportedEvents = true;
  7003. }
  7004. },
  7005. /**
  7006. * Set the point's state
  7007. * @param {String} state
  7008. */
  7009. setState: function(state) {
  7010. var point = this,
  7011. series = point.series,
  7012. stateOptions = series.options.states,
  7013. markerOptions = defaultPlotOptions[series.type].marker && series.options.marker,
  7014. normalDisabled = markerOptions && !markerOptions.enabled,
  7015. markerStateOptions = markerOptions && markerOptions.states[state],
  7016. stateDisabled = markerStateOptions && markerStateOptions.enabled === false,
  7017. stateMarkerGraphic = series.stateMarkerGraphic,
  7018. chart = series.chart,
  7019. pointAttr = point.pointAttr;
  7020. if (!state) {
  7021. state = NORMAL_STATE; // empty string
  7022. }
  7023. if (
  7024. // already has this state
  7025. state == point.state ||
  7026. // selected points don't respond to hover
  7027. (point.selected && state != SELECT_STATE) ||
  7028. // series' state options is disabled
  7029. (stateOptions[state] && stateOptions[state].enabled === false) ||
  7030. // point marker's state options is disabled
  7031. (state && (stateDisabled || normalDisabled && !markerStateOptions.enabled))
  7032. ) {
  7033. return;
  7034. }
  7035. // apply hover styles to the existing point
  7036. if (point.graphic) {
  7037. point.graphic.attr(pointAttr[state]);
  7038. }
  7039. // if a graphic is not applied to each point in the normal state, create a shared
  7040. // graphic for the hover state
  7041. else {
  7042. if (state) {
  7043. if (!stateMarkerGraphic) {
  7044. series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.circle(
  7045. 0, 0, pointAttr[state].r
  7046. )
  7047. .attr(pointAttr[state])
  7048. .add(series.group);
  7049. }
  7050. stateMarkerGraphic.translate(
  7051. point.plotX,
  7052. point.plotY
  7053. );
  7054. }
  7055. if (stateMarkerGraphic) {
  7056. stateMarkerGraphic[state ? 'show' : 'hide']();
  7057. }
  7058. }
  7059. point.state = state;
  7060. }
  7061. };
  7062. /**
  7063. * The base function which all other series types inherit from
  7064. * @param {Object} chart
  7065. * @param {Object} options
  7066. */
  7067. var Series = function() {};
  7068. Series.prototype = {
  7069. isCartesian: true,
  7070. type: 'line',
  7071. pointClass: Point,
  7072. pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
  7073. stroke: 'lineColor',
  7074. 'stroke-width': 'lineWidth',
  7075. fill: 'fillColor',
  7076. r: 'radius'
  7077. },
  7078. init: function(chart, options) {
  7079. var series = this,
  7080. eventType,
  7081. events,
  7082. //pointEvent,
  7083. index = chart.series.length;
  7084. series.chart = chart;
  7085. options = series.setOptions(options); // merge with plotOptions
  7086. // set some variables
  7087. extend(series, {
  7088. index: index,
  7089. options: options,
  7090. name: options.name || 'Series '+ (index + 1),
  7091. state: NORMAL_STATE,
  7092. pointAttr: {},
  7093. visible: options.visible !== false, // true by default
  7094. selected: options.selected === true // false by default
  7095. });
  7096. // register event listeners
  7097. events = options.events;
  7098. for (eventType in events) {
  7099. addEvent(series, eventType, events[eventType]);
  7100. }
  7101. if (
  7102. (events && events.click) ||
  7103. (options.point && options.point.events && options.point.events.click) ||
  7104. options.allowPointSelect
  7105. ) {
  7106. chart.runTrackerClick = true;
  7107. }
  7108. series.getColor();
  7109. series.getSymbol();
  7110. // set the data
  7111. series.setData(options.data, false);
  7112. },
  7113. /**
  7114. * Return an auto incremented x value based on the pointStart and pointInterval options.
  7115. * This is only used if an x value is not given for the point that calls autoIncrement.
  7116. */
  7117. autoIncrement: function() {
  7118. var series = this,
  7119. options = series.options,
  7120. xIncrement = series.xIncrement;
  7121. xIncrement = pick(xIncrement, options.pointStart, 0);
  7122. series.pointInterval = pick(series.pointInterval, options.pointInterval, 1);
  7123. series.xIncrement = xIncrement + series.pointInterval;
  7124. return xIncrement;
  7125. },
  7126. /**
  7127. * Sort the data and remove duplicates
  7128. */
  7129. cleanData: function() {
  7130. var series = this,
  7131. chart = series.chart,
  7132. data = series.data,
  7133. closestPoints,
  7134. smallestInterval,
  7135. chartSmallestInterval = chart.smallestInterval,
  7136. interval,
  7137. i;
  7138. // sort the data points
  7139. data.sort(function(a, b){
  7140. return (a.x - b.x);
  7141. });
  7142. // remove points with equal x values
  7143. // record the closest distance for calculation of column widths
  7144. for (i = data.length - 1; i >= 0; i--) {
  7145. if (data[i - 1]) {
  7146. if (data[i - 1].x == data[i].x) {
  7147. data.splice(i - 1, 1); // remove the duplicate
  7148. }
  7149. }
  7150. }
  7151. // find the closes pair of points
  7152. for (i = data.length - 1; i >= 0; i--) {
  7153. if (data[i - 1]) {
  7154. interval = data[i].x - data[i - 1].x;
  7155. if (smallestInterval === UNDEFINED || interval < smallestInterval) {
  7156. smallestInterval = interval;
  7157. closestPoints = i;
  7158. }
  7159. }
  7160. }
  7161. if (chartSmallestInterval === UNDEFINED || smallestInterval < chartSmallestInterval) {
  7162. chart.smallestInterval = smallestInterval;
  7163. }
  7164. series.closestPoints = closestPoints;
  7165. },
  7166. /**
  7167. * Divide the series data into segments divided by null values. Also sort
  7168. * the data points and delete duplicate values.
  7169. */
  7170. getSegments: function() {
  7171. var lastNull = -1,
  7172. segments = [],
  7173. data = this.data;
  7174. // create the segments
  7175. each(data, function(point, i) {
  7176. if (point.y === null) {
  7177. if (i > lastNull + 1) {
  7178. segments.push(data.slice(lastNull + 1, i));
  7179. }
  7180. lastNull = i;
  7181. } else if (i == data.length - 1) { // last value
  7182. segments.push(data.slice(lastNull + 1, i + 1));
  7183. }
  7184. });
  7185. this.segments = segments;
  7186. },
  7187. /**
  7188. * Set the series options by merging from the options tree
  7189. * @param {Object} itemOptions
  7190. */
  7191. setOptions: function(itemOptions) {
  7192. var plotOptions = this.chart.options.plotOptions,
  7193. options = merge(
  7194. plotOptions[this.type],
  7195. plotOptions.series,
  7196. itemOptions
  7197. );
  7198. return options;
  7199. },
  7200. /**
  7201. * Get the series' color
  7202. */
  7203. getColor: function(){
  7204. var defaultColors = this.chart.options.colors;
  7205. this.color = this.options.color || defaultColors[colorCounter++] || '#0000ff';
  7206. if (colorCounter >= defaultColors.length) {
  7207. colorCounter = 0;
  7208. }
  7209. },
  7210. /**
  7211. * Get the series' symbol
  7212. */
  7213. getSymbol: function(){
  7214. var defaultSymbols = this.chart.options.symbols,
  7215. symbol = this.options.marker.symbol || defaultSymbols[symbolCounter++];
  7216. this.symbol = symbol;
  7217. if (symbolCounter >= defaultSymbols.length) {
  7218. symbolCounter = 0;
  7219. }
  7220. },
  7221. /**
  7222. * Add a point dynamically after chart load time
  7223. * @param {Object} options Point options as given in series.data
  7224. * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
  7225. * @param {Boolean} shift If shift is true, a point is shifted off the start
  7226. * of the series as one is appended to the end.
  7227. * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
  7228. * configuration
  7229. */
  7230. addPoint: function(options, redraw, shift, animation) {
  7231. var series = this,
  7232. data = series.data,
  7233. graph = series.graph,
  7234. area = series.area,
  7235. chart = series.chart,
  7236. point = (new series.pointClass()).init(series, options);
  7237. setAnimation(animation, chart);
  7238. if (graph && shift) { // make graph animate sideways
  7239. graph.shift = shift;
  7240. }
  7241. if (area) {
  7242. area.shift = shift;
  7243. area.isArea = true;
  7244. }
  7245. redraw = pick(redraw, true);
  7246. data.push(point);
  7247. if (shift) {
  7248. data[0].remove(false);
  7249. }
  7250. // redraw
  7251. series.isDirty = true;
  7252. if (redraw) {
  7253. chart.redraw();
  7254. }
  7255. },
  7256. /**
  7257. * Replace the series data with a new set of data
  7258. * @param {Object} data
  7259. * @param {Object} redraw
  7260. */
  7261. setData: function(data, redraw) {
  7262. var series = this,
  7263. oldData = series.data,
  7264. initialColor = series.initialColor,
  7265. chart = series.chart,
  7266. i = oldData && oldData.length || 0;
  7267. series.xIncrement = null; // reset for new data
  7268. if (defined(initialColor)) { // reset colors for pie
  7269. colorCounter = initialColor;
  7270. }
  7271. data = map(splat(data || []), function(pointOptions) {
  7272. return (new series.pointClass()).init(series, pointOptions);
  7273. });
  7274. // destroy old points
  7275. while (i--) {
  7276. oldData[i].destroy();
  7277. }
  7278. // set the data
  7279. series.data = data;
  7280. series.cleanData();
  7281. series.getSegments();
  7282. // redraw
  7283. series.isDirty = true;
  7284. chart.isDirtyBox = true;
  7285. if (pick(redraw, true)) {
  7286. chart.redraw(false);
  7287. }
  7288. },
  7289. /**
  7290. * Remove a series and optionally redraw the chart
  7291. *
  7292. * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
  7293. * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
  7294. * configuration
  7295. */
  7296. remove: function(redraw, animation) {
  7297. var series = this,
  7298. chart = series.chart;
  7299. redraw = pick(redraw, true);
  7300. if (!series.isRemoving) { /* prevent triggering native event in jQuery
  7301. (calling the remove function from the remove event) */
  7302. series.isRemoving = true;
  7303. // fire the event with a default handler of removing the point
  7304. fireEvent(series, 'remove', null, function() {
  7305. // destroy elements
  7306. series.destroy();
  7307. // redraw
  7308. chart.isDirtyLegend = chart.isDirtyBox = true;
  7309. if (redraw) {
  7310. chart.redraw(animation);
  7311. }
  7312. });
  7313. }
  7314. series.isRemoving = false;
  7315. },
  7316. /**
  7317. * Translate data points from raw data values to chart specific positioning data
  7318. * needed later in drawPoints, drawGraph and drawTracker.
  7319. */
  7320. translate: function() {
  7321. var series = this,
  7322. chart = series.chart,
  7323. stacking = series.options.stacking,
  7324. categories = series.xAxis.categories,
  7325. yAxis = series.yAxis,
  7326. data = series.data,
  7327. i = data.length;
  7328. // do the translation
  7329. while (i--) {
  7330. var point = data[i],
  7331. xValue = point.x,
  7332. yValue = point.y,
  7333. yBottom = point.low,
  7334. stack = yAxis.stacks[(yValue < 0 ? '-' : '') + series.stackKey],
  7335. pointStack,
  7336. pointStackTotal;
  7337. point.plotX = series.xAxis.translate(xValue);
  7338. // calculate the bottom y value for stacked series
  7339. if (stacking && series.visible && stack && stack[xValue]) {
  7340. pointStack = stack[xValue];
  7341. pointStackTotal = pointStack.total;
  7342. pointStack.cum = yBottom = pointStack.cum - yValue; // start from top
  7343. yValue = yBottom + yValue;
  7344. if (stacking == 'percent') {
  7345. yBottom = pointStackTotal ? yBottom * 100 / pointStackTotal : 0;
  7346. yValue = pointStackTotal ? yValue * 100 / pointStackTotal : 0;
  7347. }
  7348. point.percentage = pointStackTotal ? point.y * 100 / pointStackTotal : 0;
  7349. point.stackTotal = pointStackTotal;
  7350. }
  7351. if (defined(yBottom)) {
  7352. point.yBottom = yAxis.translate(yBottom, 0, 1);
  7353. }
  7354. // set the y value
  7355. if (yValue !== null) {
  7356. point.plotY = yAxis.translate(yValue, 0, 1);
  7357. }
  7358. // set client related positions for mouse tracking
  7359. point.clientX = chart.inverted ?
  7360. chart.plotHeight - point.plotX :
  7361. point.plotX; // for mouse tracking
  7362. // some API data
  7363. point.category = categories && categories[point.x] !== UNDEFINED ?
  7364. categories[point.x] : point.x;
  7365. }
  7366. },
  7367. /**
  7368. * Memoize tooltip texts and positions
  7369. */
  7370. setTooltipPoints: function (renew) {
  7371. var series = this,
  7372. chart = series.chart,
  7373. inverted = chart.inverted,
  7374. data = [],
  7375. plotSize = mathRound((inverted ? chart.plotTop : chart.plotLeft) + chart.plotSizeX),
  7376. low,
  7377. high,
  7378. tooltipPoints = []; // a lookup array for each pixel in the x dimension
  7379. // renew
  7380. if (renew) {
  7381. series.tooltipPoints = null;
  7382. }
  7383. // concat segments to overcome null values
  7384. each(series.segments, function(segment){
  7385. data = data.concat(segment);
  7386. });
  7387. // loop the concatenated data and apply each point to all the closest
  7388. // pixel positions
  7389. if (series.xAxis && series.xAxis.reversed) {
  7390. data = data.reverse();//reverseArray(data);
  7391. }
  7392. each(data, function(point, i) {
  7393. low = data[i - 1] ? data[i - 1].high + 1 : 0;
  7394. high = point.high = data[i + 1] ? (
  7395. mathFloor((point.plotX + (data[i + 1] ?
  7396. data[i + 1].plotX : plotSize)) / 2)) :
  7397. plotSize;
  7398. while (low <= high) {
  7399. tooltipPoints[inverted ? plotSize - low++ : low++] = point;
  7400. }
  7401. });
  7402. series.tooltipPoints = tooltipPoints;
  7403. },
  7404. /**
  7405. * Series mouse over handler
  7406. */
  7407. onMouseOver: function() {
  7408. var series = this,
  7409. chart = series.chart,
  7410. hoverSeries = chart.hoverSeries;
  7411. if (!hasTouch && chart.mouseIsDown) {
  7412. return;
  7413. }
  7414. // set normal state to previous series
  7415. if (hoverSeries && hoverSeries != series) {
  7416. hoverSeries.onMouseOut();
  7417. }
  7418. // trigger the event, but to save processing time,
  7419. // only if defined
  7420. if (series.options.events.mouseOver) {
  7421. fireEvent(series, 'mouseOver');
  7422. }
  7423. // bring to front
  7424. // Todo: optimize. This is one of two operations slowing down the tooltip in Firefox.
  7425. // Can the tracking be done otherwise?
  7426. if (series.tracker) {
  7427. series.tracker.toFront();
  7428. }
  7429. // hover this
  7430. series.setState(HOVER_STATE);
  7431. chart.hoverSeries = series;
  7432. },
  7433. /**
  7434. * Series mouse out handler
  7435. */
  7436. onMouseOut: function() {
  7437. // trigger the event only if listeners exist
  7438. var series = this,
  7439. options = series.options,
  7440. chart = series.chart,
  7441. tooltip = chart.tooltip,
  7442. hoverPoint = chart.hoverPoint;
  7443. // trigger mouse out on the point, which must be in this series
  7444. if (hoverPoint) {
  7445. hoverPoint.onMouseOut();
  7446. }
  7447. // fire the mouse out event
  7448. if (series && options.events.mouseOut) {
  7449. fireEvent(series, 'mouseOut');
  7450. }
  7451. // hide the tooltip
  7452. if (tooltip && !options.stickyTracking) {
  7453. tooltip.hide();
  7454. }
  7455. // set normal state
  7456. series.setState();
  7457. chart.hoverSeries = null;
  7458. },
  7459. /**
  7460. * Animate in the series
  7461. */
  7462. animate: function(init) {
  7463. var series = this,
  7464. chart = series.chart,
  7465. clipRect = series.clipRect,
  7466. animation = series.options.animation;
  7467. if (animation && !isObject(animation)) {
  7468. animation = {};
  7469. }
  7470. if (init) { // initialize the animation
  7471. if (!clipRect.isAnimating) { // apply it only for one of the series
  7472. clipRect.attr( 'width', 0 );
  7473. clipRect.isAnimating = true;
  7474. }
  7475. } else { // run the animation
  7476. clipRect.animate({
  7477. width: chart.plotSizeX
  7478. }, animation);
  7479. // delete this function to allow it only once
  7480. this.animate = null;
  7481. }
  7482. },
  7483. /**
  7484. * Draw the markers
  7485. */
  7486. drawPoints: function(){
  7487. var series = this,
  7488. pointAttr,
  7489. data = series.data,
  7490. chart = series.chart,
  7491. plotX,
  7492. plotY,
  7493. i,
  7494. point,
  7495. radius,
  7496. graphic;
  7497. if (series.options.marker.enabled) {
  7498. i = data.length;
  7499. while (i--) {
  7500. point = data[i];
  7501. plotX = point.plotX;
  7502. plotY = point.plotY;
  7503. graphic = point.graphic;
  7504. // only draw the point if y is defined
  7505. if (plotY !== UNDEFINED && !isNaN(plotY)) {
  7506. /* && removed this code because points stayed after zoom
  7507. point.plotX >= 0 && point.plotX <= chart.plotSizeX &&
  7508. point.plotY >= 0 && point.plotY <= chart.plotSizeY*/
  7509. // shortcuts
  7510. pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE];
  7511. radius = pointAttr.r;
  7512. if (graphic) { // update
  7513. graphic.animate({
  7514. x: plotX,
  7515. y: plotY,
  7516. r: radius
  7517. });
  7518. } else {
  7519. point.graphic = chart.renderer.symbol(
  7520. pick(point.marker && point.marker.symbol, series.symbol),
  7521. plotX,
  7522. plotY,
  7523. radius
  7524. )
  7525. .attr(pointAttr)
  7526. .add(series.group);
  7527. }
  7528. }
  7529. }
  7530. }
  7531. },
  7532. /**
  7533. * Convert state properties from API naming conventions to SVG attributes
  7534. *
  7535. * @param {Object} options API options object
  7536. * @param {Object} base1 SVG attribute object to inherit from
  7537. * @param {Object} base2 Second level SVG attribute object to inherit from
  7538. */
  7539. convertAttribs: function(options, base1, base2, base3) {
  7540. var conversion = this.pointAttrToOptions,
  7541. attr,
  7542. option,
  7543. obj = {};
  7544. options = options || {};
  7545. base1 = base1 || {};
  7546. base2 = base2 || {};
  7547. base3 = base3 || {};
  7548. for (attr in conversion) {
  7549. option = conversion[attr];
  7550. obj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]);
  7551. }
  7552. return obj;
  7553. },
  7554. /**
  7555. * Get the state attributes. Each series type has its own set of attributes
  7556. * that are allowed to change on a point's state change. Series wide attributes are stored for
  7557. * all series, and additionally point specific attributes are stored for all
  7558. * points with individual marker options. If such options are not defined for the point,
  7559. * a reference to the series wide attributes is stored in point.pointAttr.
  7560. */
  7561. getAttribs: function() {
  7562. var series = this,
  7563. normalOptions = defaultPlotOptions[series.type].marker ? series.options.marker : series.options,
  7564. stateOptions = normalOptions.states,
  7565. stateOptionsHover = stateOptions[HOVER_STATE],
  7566. pointStateOptionsHover,
  7567. seriesColor = series.color,
  7568. normalDefaults = {
  7569. stroke: seriesColor,
  7570. fill: seriesColor
  7571. },
  7572. data = series.data,
  7573. i,
  7574. point,
  7575. seriesPointAttr = [],
  7576. pointAttr,
  7577. pointAttrToOptions = series.pointAttrToOptions,
  7578. hasPointSpecificOptions;
  7579. // series type specific modifications
  7580. if (series.options.marker) { // line, spline, area, areaspline, scatter
  7581. // if no hover radius is given, default to normal radius + 2
  7582. stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2;
  7583. stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1;
  7584. } else { // column, bar, pie
  7585. // if no hover color is given, brighten the normal color
  7586. stateOptionsHover.color = stateOptionsHover.color ||
  7587. Color(stateOptionsHover.color || seriesColor)
  7588. .brighten(stateOptionsHover.brightness).get();
  7589. }
  7590. // general point attributes for the series normal state
  7591. seriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults);
  7592. // HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius
  7593. each([HOVER_STATE, SELECT_STATE], function(state) {
  7594. seriesPointAttr[state] =
  7595. series.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]);
  7596. });
  7597. // set it
  7598. series.pointAttr = seriesPointAttr;
  7599. // Generate the point-specific attribute collections if specific point
  7600. // options are given. If not, create a referance to the series wide point
  7601. // attributes
  7602. i = data.length;
  7603. while (i--) {
  7604. point = data[i];
  7605. normalOptions = (point.options && point.options.marker) || point.options;
  7606. if (normalOptions && normalOptions.enabled === false) {
  7607. normalOptions.radius = 0;
  7608. }
  7609. hasPointSpecificOptions = false;
  7610. // check if the point has specific visual options
  7611. if (point.options) {
  7612. for (var key in pointAttrToOptions) {
  7613. if (defined(normalOptions[pointAttrToOptions[key]])) {
  7614. hasPointSpecificOptions = true;
  7615. }
  7616. }
  7617. }
  7618. // a specific marker config object is defined for the individual point:
  7619. // create it's own attribute collection
  7620. if (hasPointSpecificOptions) {
  7621. pointAttr = [];
  7622. stateOptions = normalOptions.states || {}; // reassign for individual point
  7623. pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {};
  7624. // if no hover color is given, brighten the normal color
  7625. if (!series.options.marker) { // column, bar, point
  7626. pointStateOptionsHover.color =
  7627. Color(pointStateOptionsHover.color || point.options.color)
  7628. .brighten(pointStateOptionsHover.brightness ||
  7629. stateOptionsHover.brightness).get();
  7630. }
  7631. // normal point state inherits series wide normal state
  7632. pointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, seriesPointAttr[NORMAL_STATE]);
  7633. // inherit from point normal and series hover
  7634. pointAttr[HOVER_STATE] = series.convertAttribs(
  7635. stateOptions[HOVER_STATE],
  7636. seriesPointAttr[HOVER_STATE],
  7637. pointAttr[NORMAL_STATE]
  7638. );
  7639. // inherit from point normal and series hover
  7640. pointAttr[SELECT_STATE] = series.convertAttribs(
  7641. stateOptions[SELECT_STATE],
  7642. seriesPointAttr[SELECT_STATE],
  7643. pointAttr[NORMAL_STATE]
  7644. );
  7645. // no marker config object is created: copy a reference to the series-wide
  7646. // attribute collection
  7647. } else {
  7648. pointAttr = seriesPointAttr;
  7649. }
  7650. point.pointAttr = pointAttr;
  7651. }
  7652. },
  7653. /**
  7654. * Clear DOM objects and free up memory
  7655. */
  7656. destroy: function() {
  7657. var series = this,
  7658. chart = series.chart,
  7659. //chartSeries = series.chart.series,
  7660. clipRect = series.clipRect,
  7661. issue134 = /\/5[0-9\.]+ (Safari|Mobile)\//.test(userAgent), // todo: update when Safari bug is fixed
  7662. destroy,
  7663. prop;
  7664. // remove all events
  7665. removeEvent(series);
  7666. // remove legend items
  7667. if (series.legendItem) {
  7668. series.chart.legend.destroyItem(series);
  7669. }
  7670. // destroy all points with their elements
  7671. each(series.data, function(point) {
  7672. point.destroy();
  7673. });
  7674. // destroy all SVGElements associated to the series
  7675. each(['area', 'graph', 'dataLabelsGroup', 'group', 'tracker'], function(prop) {
  7676. if (series[prop]) {
  7677. // issue 134 workaround
  7678. destroy = issue134 && prop == 'group' ?
  7679. 'hide' :
  7680. 'destroy';
  7681. series[prop][destroy]();
  7682. }
  7683. });
  7684. // remove from hoverSeries
  7685. if (chart.hoverSeries == series) {
  7686. chart.hoverSeries = null;
  7687. }
  7688. erase(chart.series, series);
  7689. // clear all members
  7690. for (prop in series) {
  7691. delete series[prop];
  7692. }
  7693. },
  7694. /**
  7695. * Draw the data labels
  7696. */
  7697. drawDataLabels: function() {
  7698. if (this.options.dataLabels.enabled) {
  7699. var series = this,
  7700. x,
  7701. y,
  7702. data = series.data,
  7703. options = series.options.dataLabels,
  7704. str,
  7705. dataLabelsGroup = series.dataLabelsGroup,
  7706. chart = series.chart,
  7707. inverted = chart.inverted,
  7708. seriesType = series.type,
  7709. color;
  7710. // create a separate group for the data labels to avoid rotation
  7711. if (!dataLabelsGroup) {
  7712. dataLabelsGroup = series.dataLabelsGroup =
  7713. chart.renderer.g(PREFIX +'data-labels')
  7714. .attr({
  7715. visibility: series.visible ? VISIBLE : HIDDEN,
  7716. zIndex: 5
  7717. })
  7718. .translate(chart.plotLeft, chart.plotTop)
  7719. .add();
  7720. }
  7721. // determine the color
  7722. color = options.color;
  7723. if (color == 'auto') { // 1.0 backwards compatibility
  7724. color = null;
  7725. }
  7726. options.style.color = pick(color, series.color);
  7727. // make the labels for each point
  7728. each(data, function(point, i){
  7729. var barX = point.barX,
  7730. plotX = barX && barX + point.barW / 2 || point.plotX || -999,
  7731. plotY = pick(point.plotY, -999),
  7732. dataLabel = point.dataLabel,
  7733. align = options.align;
  7734. // get the string
  7735. str = point.getDataLabelText();
  7736. x = (inverted ? chart.plotWidth - plotY : plotX) + options.x;
  7737. y = (inverted ? chart.plotHeight - plotX : plotY) + options.y;
  7738. // in columns, align the string to the column
  7739. if (seriesType == 'column') {
  7740. x += { left: -1, right: 1 }[align] * point.barW / 2 || 0;
  7741. }
  7742. if (dataLabel) {
  7743. dataLabel.animate({
  7744. x: x,
  7745. y: y
  7746. });
  7747. } else if (defined(str)) {
  7748. dataLabel = point.dataLabel = chart.renderer.text(
  7749. str,
  7750. x,
  7751. y
  7752. )
  7753. .attr({
  7754. align: align,
  7755. rotation: options.rotation,
  7756. zIndex: 1
  7757. })
  7758. .css(options.style)
  7759. .add(dataLabelsGroup);
  7760. }
  7761. // vertically centered
  7762. if (inverted && !options.y) {
  7763. dataLabel.attr({
  7764. y: y + parseInt(dataLabel.styles.lineHeight) * 0.9 - dataLabel.getBBox().height / 2
  7765. });
  7766. }
  7767. /*if (series.isCartesian) {
  7768. dataLabel[chart.isInsidePlot(plotX, plotY) ? 'show' : 'hide']();
  7769. }*/
  7770. });
  7771. }
  7772. },
  7773. /**
  7774. * Draw the actual graph
  7775. */
  7776. drawGraph: function(state) {
  7777. var series = this,
  7778. options = series.options,
  7779. chart = series.chart,
  7780. graph = series.graph,
  7781. graphPath = [],
  7782. fillColor,
  7783. area = series.area,
  7784. group = series.group,
  7785. color = options.lineColor || series.color,
  7786. lineWidth = options.lineWidth,
  7787. dashStyle = options.dashStyle,
  7788. segmentPath,
  7789. renderer = chart.renderer,
  7790. translatedThreshold = series.yAxis.getThreshold(options.threshold || 0),
  7791. useArea = /^area/.test(series.type),
  7792. singlePoints = [], // used in drawTracker
  7793. areaPath = [],
  7794. attribs;
  7795. // divide into segments and build graph and area paths
  7796. each(series.segments, function(segment) {
  7797. segmentPath = [];
  7798. // build the segment line
  7799. each(segment, function(point, i) {
  7800. if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
  7801. segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i));
  7802. } else {
  7803. // moveTo or lineTo
  7804. segmentPath.push(i ? L : M);
  7805. // step line?
  7806. if (i && options.step) {
  7807. var lastPoint = segment[i - 1];
  7808. segmentPath.push(
  7809. point.plotX,
  7810. lastPoint.plotY
  7811. );
  7812. }
  7813. // normal line to next point
  7814. segmentPath.push(
  7815. point.plotX,
  7816. point.plotY
  7817. );
  7818. }
  7819. });
  7820. // add the segment to the graph, or a single point for tracking
  7821. if (segment.length > 1) {
  7822. graphPath = graphPath.concat(segmentPath);
  7823. } else {
  7824. singlePoints.push(segment[0]);
  7825. }
  7826. // build the area
  7827. if (useArea) {
  7828. var areaSegmentPath = [],
  7829. i,
  7830. segLength = segmentPath.length;
  7831. for (i = 0; i < segLength; i++) {
  7832. areaSegmentPath.push(segmentPath[i]);
  7833. }
  7834. if (segLength == 3) { // for animation from 1 to two points
  7835. areaSegmentPath.push(L, segmentPath[1], segmentPath[2]);
  7836. }
  7837. if (options.stacking && series.type != 'areaspline') {
  7838. // follow stack back. Todo: implement areaspline
  7839. for (i = segment.length - 1; i >= 0; i--) {
  7840. areaSegmentPath.push(segment[i].plotX, segment[i].yBottom);
  7841. }
  7842. } else { // follow zero line back
  7843. areaSegmentPath.push(
  7844. L,
  7845. segment[segment.length - 1].plotX,
  7846. translatedThreshold,
  7847. L,
  7848. segment[0].plotX,
  7849. translatedThreshold
  7850. );
  7851. }
  7852. areaPath = areaPath.concat(areaSegmentPath);
  7853. }
  7854. });
  7855. // used in drawTracker:
  7856. series.graphPath = graphPath;
  7857. series.singlePoints = singlePoints;
  7858. // draw the area if area series or areaspline
  7859. if (useArea) {
  7860. fillColor = pick(
  7861. options.fillColor,
  7862. Color(series.color).setOpacity(options.fillOpacity || 0.75).get()
  7863. );
  7864. if (area) {
  7865. area.animate({ d: areaPath });
  7866. } else {
  7867. // draw the area
  7868. series.area = series.chart.renderer.path(areaPath)
  7869. .attr({
  7870. fill: fillColor
  7871. }).add(group);
  7872. }
  7873. }
  7874. // draw the graph
  7875. if (graph) {
  7876. //graph.animate({ d: graphPath.join(' ') });
  7877. graph.animate({ d: graphPath });
  7878. } else {
  7879. if (lineWidth) {
  7880. attribs = {
  7881. 'stroke': color,
  7882. 'stroke-width': lineWidth
  7883. };
  7884. if (dashStyle) {
  7885. attribs.dashstyle = dashStyle;
  7886. }
  7887. series.graph = renderer.path(graphPath)
  7888. .attr(attribs).add(group).shadow(options.shadow);
  7889. }
  7890. }
  7891. },
  7892. /**
  7893. * Render the graph and markers
  7894. */
  7895. render: function() {
  7896. var series = this,
  7897. chart = series.chart,
  7898. group,
  7899. setInvert,
  7900. options = series.options,
  7901. animation = options.animation,
  7902. doAnimation = animation && series.animate,
  7903. duration = doAnimation ? animation && animation.duration || 500 : 0,
  7904. clipRect = series.clipRect,
  7905. renderer = chart.renderer;
  7906. // Add plot area clipping rectangle. If this is before chart.hasRendered,
  7907. // create one shared clipRect.
  7908. if (!clipRect) {
  7909. clipRect = series.clipRect = !chart.hasRendered && chart.clipRect ?
  7910. chart.clipRect :
  7911. renderer.clipRect(0, 0, chart.plotSizeX, chart.plotSizeY);
  7912. if (!chart.clipRect) {
  7913. chart.clipRect = clipRect;
  7914. }
  7915. }
  7916. // the group
  7917. if (!series.group) {
  7918. group = series.group = renderer.g('series');
  7919. if (chart.inverted) {
  7920. setInvert = function() {
  7921. group.attr({
  7922. width: chart.plotWidth,
  7923. height: chart.plotHeight
  7924. }).invert();
  7925. };
  7926. setInvert(); // do it now
  7927. addEvent(chart, 'resize', setInvert); // do it on resize
  7928. }
  7929. group.clip(series.clipRect)
  7930. .attr({
  7931. visibility: series.visible ? VISIBLE : HIDDEN,
  7932. zIndex: options.zIndex
  7933. })
  7934. .translate(chart.plotLeft, chart.plotTop)
  7935. .add(chart.seriesGroup);
  7936. }
  7937. series.drawDataLabels();
  7938. // initiate the animation
  7939. if (doAnimation) {
  7940. series.animate(true);
  7941. }
  7942. // cache attributes for shapes
  7943. series.getAttribs();
  7944. // draw the graph if any
  7945. if (series.drawGraph) {
  7946. series.drawGraph();
  7947. }
  7948. // draw the points
  7949. series.drawPoints();
  7950. // draw the mouse tracking area
  7951. if (series.options.enableMouseTracking !== false) {
  7952. series.drawTracker();
  7953. }
  7954. // run the animation
  7955. if (doAnimation) {
  7956. series.animate();
  7957. }
  7958. // finish the individual clipRect
  7959. setTimeout(function() {
  7960. clipRect.isAnimating = false;
  7961. group = series.group; // can be destroyed during the timeout
  7962. if (group && clipRect != chart.clipRect && clipRect.renderer) {
  7963. group.clip((series.clipRect = chart.clipRect));
  7964. clipRect.destroy();
  7965. }
  7966. }, duration);
  7967. series.isDirty = false; // means data is in accordance with what you see
  7968. },
  7969. /**
  7970. * Redraw the series after an update in the axes.
  7971. */
  7972. redraw: function() {
  7973. var series = this,
  7974. chart = series.chart,
  7975. clipRect = series.clipRect,
  7976. group = series.group;
  7977. /*if (clipRect) {
  7978. stop(clipRect);
  7979. clipRect.animate({ // for chart resize
  7980. width: chart.plotSizeX,
  7981. height: chart.plotSizeY
  7982. });
  7983. }*/
  7984. // reposition on resize
  7985. if (group) {
  7986. if (chart.inverted) {
  7987. group.attr({
  7988. width: chart.plotWidth,
  7989. height: chart.plotHeight
  7990. });
  7991. }
  7992. group.animate({
  7993. translateX: chart.plotLeft,
  7994. translateY: chart.plotTop
  7995. });
  7996. }
  7997. series.translate();
  7998. series.setTooltipPoints(true);
  7999. series.render();
  8000. },
  8001. /**
  8002. * Set the state of the graph
  8003. */
  8004. setState: function(state) {
  8005. var series = this,
  8006. options = series.options,
  8007. graph = series.graph,
  8008. stateOptions = options.states,
  8009. lineWidth = options.lineWidth;
  8010. state = state || NORMAL_STATE;
  8011. if (series.state != state) {
  8012. series.state = state;
  8013. if (stateOptions[state] && stateOptions[state].enabled === false) {
  8014. return;
  8015. }
  8016. if (state) {
  8017. lineWidth = stateOptions[state].lineWidth || lineWidth + 1;
  8018. }
  8019. if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
  8020. graph.attr({ // use attr because animate will cause any other animation on the graph to stop
  8021. 'stroke-width': lineWidth
  8022. }, state ? 0 : 500);
  8023. }
  8024. }
  8025. },
  8026. /**
  8027. * Set the visibility of the graph
  8028. *
  8029. * @param vis {Boolean} True to show the series, false to hide. If UNDEFINED,
  8030. * the visibility is toggled.
  8031. */
  8032. setVisible: function(vis, redraw) {
  8033. var series = this,
  8034. chart = series.chart,
  8035. legendItem = series.legendItem,
  8036. seriesGroup = series.group,
  8037. seriesTracker = series.tracker,
  8038. dataLabelsGroup = series.dataLabelsGroup,
  8039. showOrHide,
  8040. i,
  8041. data = series.data,
  8042. point,
  8043. ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,
  8044. oldVisibility = series.visible;
  8045. // if called without an argument, toggle visibility
  8046. series.visible = vis = vis === UNDEFINED ? !oldVisibility : vis;
  8047. showOrHide = vis ? 'show' : 'hide';
  8048. // show or hide series
  8049. if (seriesGroup) { // pies don't have one
  8050. seriesGroup[showOrHide]();
  8051. }
  8052. // show or hide trackers
  8053. if (seriesTracker) {
  8054. seriesTracker[showOrHide]();
  8055. } else {
  8056. i = data.length;
  8057. while (i--) {
  8058. point = data[i];
  8059. if (point.tracker) {
  8060. point.tracker[showOrHide]();
  8061. }
  8062. }
  8063. }
  8064. if (dataLabelsGroup) {
  8065. dataLabelsGroup[showOrHide]();
  8066. }
  8067. if (legendItem) {
  8068. chart.legend.colorizeItem(series, vis);
  8069. }
  8070. // rescale or adapt to resized chart
  8071. series.isDirty = true;
  8072. // in a stack, all other series are affected
  8073. if (series.options.stacking) {
  8074. each(chart.series, function(otherSeries) {
  8075. if (otherSeries.options.stacking && otherSeries.visible) {
  8076. otherSeries.isDirty = true;
  8077. }
  8078. });
  8079. }
  8080. if (ignoreHiddenSeries) {
  8081. chart.isDirtyBox = true;
  8082. }
  8083. if (redraw !== false) {
  8084. chart.redraw();
  8085. }
  8086. fireEvent(series, showOrHide);
  8087. },
  8088. /**
  8089. * Show the graph
  8090. */
  8091. show: function() {
  8092. this.setVisible(true);
  8093. },
  8094. /**
  8095. * Hide the graph
  8096. */
  8097. hide: function() {
  8098. this.setVisible(false);
  8099. },
  8100. /**
  8101. * Set the selected state of the graph
  8102. *
  8103. * @param selected {Boolean} True to select the series, false to unselect. If
  8104. * UNDEFINED, the selection state is toggled.
  8105. */
  8106. select: function(selected) {
  8107. var series = this;
  8108. // if called without an argument, toggle
  8109. series.selected = selected = (selected === UNDEFINED) ? !series.selected : selected;
  8110. if (series.checkbox) {
  8111. series.checkbox.checked = selected;
  8112. }
  8113. fireEvent(series, selected ? 'select' : 'unselect');
  8114. },
  8115. /**
  8116. * Draw the tracker object that sits above all data labels and markers to
  8117. * track mouse events on the graph or points. For the line type charts
  8118. * the tracker uses the same graphPath, but with a greater stroke width
  8119. * for better control.
  8120. */
  8121. drawTracker: function() {
  8122. var series = this,
  8123. options = series.options,
  8124. trackerPath = [].concat(series.graphPath),
  8125. trackerPathLength = trackerPath.length,
  8126. chart = series.chart,
  8127. snap = chart.options.tooltip.snap,
  8128. tracker = series.tracker,
  8129. cursor = options.cursor,
  8130. css = cursor && { cursor: cursor },
  8131. singlePoints = series.singlePoints,
  8132. singlePoint,
  8133. i;
  8134. // Extend end points. A better way would be to use round linecaps,
  8135. // but those are not clickable in VML.
  8136. if (trackerPathLength) {
  8137. i = trackerPathLength + 1;
  8138. while (i--) {
  8139. if (trackerPath[i] == M) { // extend left side
  8140. trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L);
  8141. }
  8142. if ((i && trackerPath[i] == M) || i == trackerPathLength) { // extend right side
  8143. trackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]);
  8144. }
  8145. }
  8146. }
  8147. // handle single points
  8148. for (i = 0; i < singlePoints.length; i++) {
  8149. singlePoint = singlePoints[i];
  8150. trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY,
  8151. L, singlePoint.plotX + snap, singlePoint.plotY);
  8152. }
  8153. // draw the tracker
  8154. if (tracker) {
  8155. tracker.attr({ d: trackerPath });
  8156. } else { // create
  8157. series.tracker = chart.renderer.path(trackerPath)
  8158. .attr({
  8159. isTracker: true,
  8160. stroke: TRACKER_FILL,
  8161. fill: NONE,
  8162. 'stroke-width' : options.lineWidth + 2 * snap,
  8163. visibility: series.visible ? VISIBLE : HIDDEN,
  8164. zIndex: 1
  8165. })
  8166. .on(hasTouch ? 'touchstart' : 'mouseover', function() {
  8167. if (chart.hoverSeries != series) {
  8168. series.onMouseOver();
  8169. }
  8170. })
  8171. .on('mouseout', function() {
  8172. if (!options.stickyTracking) {
  8173. series.onMouseOut();
  8174. }
  8175. })
  8176. .css(css)
  8177. .add(chart.trackerGroup);
  8178. }
  8179. }
  8180. }; // end Series prototype
  8181. /**
  8182. * LineSeries object
  8183. */
  8184. var LineSeries = extendClass(Series);
  8185. seriesTypes.line = LineSeries;
  8186. /**
  8187. * AreaSeries object
  8188. */
  8189. var AreaSeries = extendClass(Series, {
  8190. type: 'area'
  8191. });
  8192. seriesTypes.area = AreaSeries;
  8193. /**
  8194. * SplineSeries object
  8195. */
  8196. var SplineSeries = extendClass( Series, {
  8197. type: 'spline',
  8198. /**
  8199. * Draw the actual graph
  8200. */
  8201. getPointSpline: function(segment, point, i) {
  8202. var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc
  8203. denom = smoothing + 1,
  8204. plotX = point.plotX,
  8205. plotY = point.plotY,
  8206. lastPoint = segment[i - 1],
  8207. nextPoint = segment[i + 1],
  8208. leftContX,
  8209. leftContY,
  8210. rightContX,
  8211. rightContY,
  8212. ret;
  8213. // find control points
  8214. if (i && i < segment.length - 1) {
  8215. var lastX = lastPoint.plotX,
  8216. lastY = lastPoint.plotY,
  8217. nextX = nextPoint.plotX,
  8218. nextY = nextPoint.plotY,
  8219. correction;
  8220. leftContX = (smoothing * plotX + lastX) / denom;
  8221. leftContY = (smoothing * plotY + lastY) / denom;
  8222. rightContX = (smoothing * plotX + nextX) / denom;
  8223. rightContY = (smoothing * plotY + nextY) / denom;
  8224. // have the two control points make a straight line through main point
  8225. correction = ((rightContY - leftContY) * (rightContX - plotX)) /
  8226. (rightContX - leftContX) + plotY - rightContY;
  8227. leftContY += correction;
  8228. rightContY += correction;
  8229. // to prevent false extremes, check that control points are between
  8230. // neighbouring points' y values
  8231. if (leftContY > lastY && leftContY > plotY) {
  8232. leftContY = mathMax(lastY, plotY);
  8233. rightContY = 2 * plotY - leftContY; // mirror of left control point
  8234. } else if (leftContY < lastY && leftContY < plotY) {
  8235. leftContY = mathMin(lastY, plotY);
  8236. rightContY = 2 * plotY - leftContY;
  8237. }
  8238. if (rightContY > nextY && rightContY > plotY) {
  8239. rightContY = mathMax(nextY, plotY);
  8240. leftContY = 2 * plotY - rightContY;
  8241. } else if (rightContY < nextY && rightContY < plotY) {
  8242. rightContY = mathMin(nextY, plotY);
  8243. leftContY = 2 * plotY - rightContY;
  8244. }
  8245. // record for drawing in next point
  8246. point.rightContX = rightContX;
  8247. point.rightContY = rightContY;
  8248. }
  8249. // moveTo or lineTo
  8250. if (!i) {
  8251. ret = [M, plotX, plotY];
  8252. }
  8253. // curve from last point to this
  8254. else {
  8255. ret = [
  8256. 'C',
  8257. lastPoint.rightContX || lastPoint.plotX,
  8258. lastPoint.rightContY || lastPoint.plotY,
  8259. leftContX || plotX,
  8260. leftContY || plotY,
  8261. plotX,
  8262. plotY
  8263. ];
  8264. lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
  8265. }
  8266. return ret;
  8267. }
  8268. });
  8269. seriesTypes.spline = SplineSeries;
  8270. /**
  8271. * AreaSplineSeries object
  8272. */
  8273. var AreaSplineSeries = extendClass(SplineSeries, {
  8274. type: 'areaspline'
  8275. });
  8276. seriesTypes.areaspline = AreaSplineSeries;
  8277. /**
  8278. * ColumnSeries object
  8279. */
  8280. var ColumnSeries = extendClass(Series, {
  8281. type: 'column',
  8282. pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
  8283. stroke: 'borderColor',
  8284. 'stroke-width': 'borderWidth',
  8285. fill: 'color',
  8286. r: 'borderRadius'
  8287. },
  8288. init: function() {
  8289. Series.prototype.init.apply(this, arguments);
  8290. var series = this,
  8291. chart = series.chart;
  8292. // flag the chart in order to pad the x axis
  8293. chart.hasColumn = true;
  8294. // if the series is added dynamically, force redraw of other
  8295. // series affected by a new column
  8296. if (chart.hasRendered) {
  8297. each(chart.series, function(otherSeries) {
  8298. if (otherSeries.type == series.type) {
  8299. otherSeries.isDirty = true;
  8300. }
  8301. });
  8302. }
  8303. },
  8304. /**
  8305. * Translate each point to the plot area coordinate system and find shape positions
  8306. */
  8307. translate: function() {
  8308. var series = this,
  8309. chart = series.chart,
  8310. columnCount = 0,
  8311. reversedXAxis = series.xAxis.reversed,
  8312. categories = series.xAxis.categories,
  8313. stackGroups = {},
  8314. stackKey,
  8315. columnIndex;
  8316. Series.prototype.translate.apply(series);
  8317. // Get the total number of column type series.
  8318. // This is called on every series. Consider moving this logic to a
  8319. // chart.orderStacks() function and call it on init, addSeries and removeSeries
  8320. each(chart.series, function(otherSeries) {
  8321. if (otherSeries.type == series.type) {
  8322. if (otherSeries.options.stacking) {
  8323. stackKey = otherSeries.stackKey;
  8324. if (stackGroups[stackKey] === UNDEFINED) {
  8325. stackGroups[stackKey] = columnCount++;
  8326. }
  8327. columnIndex = stackGroups[stackKey];
  8328. } else if (otherSeries.visible){
  8329. columnIndex = columnCount++;
  8330. }
  8331. otherSeries.columnIndex = columnIndex;
  8332. }
  8333. });
  8334. // calculate the width and position of each column based on
  8335. // the number of column series in the plot, the groupPadding
  8336. // and the pointPadding options
  8337. var options = series.options,
  8338. data = series.data,
  8339. closestPoints = series.closestPoints,
  8340. categoryWidth = mathAbs(
  8341. data[1] ? data[closestPoints].plotX - data[closestPoints - 1].plotX :
  8342. chart.plotSizeX / (categories ? categories.length : 1)
  8343. ),
  8344. groupPadding = categoryWidth * options.groupPadding,
  8345. groupWidth = categoryWidth - 2 * groupPadding,
  8346. pointOffsetWidth = groupWidth / columnCount,
  8347. optionPointWidth = options.pointWidth,
  8348. pointPadding = defined(optionPointWidth) ? (pointOffsetWidth - optionPointWidth) / 2 :
  8349. pointOffsetWidth * options.pointPadding,
  8350. pointWidth = mathMax(pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), 1),
  8351. colIndex = (reversedXAxis ? columnCount -
  8352. series.columnIndex : series.columnIndex) || 0,
  8353. pointXOffset = pointPadding + (groupPadding + colIndex *
  8354. pointOffsetWidth -(categoryWidth / 2)) *
  8355. (reversedXAxis ? -1 : 1),
  8356. threshold = options.threshold || 0,
  8357. translatedThreshold = series.yAxis.getThreshold(threshold),
  8358. minPointLength = pick(options.minPointLength, 5);
  8359. // record the new values
  8360. each(data, function(point) {
  8361. var plotY = point.plotY,
  8362. yBottom = point.yBottom || translatedThreshold,
  8363. barX = point.plotX + pointXOffset,
  8364. barY = mathCeil(mathMin(plotY, yBottom)),
  8365. barH = mathCeil(mathMax(plotY, yBottom) - barY),
  8366. trackerY;
  8367. // handle options.minPointLength and tracker for small points
  8368. if (mathAbs(barH) < minPointLength) {
  8369. if (minPointLength) {
  8370. barH = minPointLength;
  8371. barY =
  8372. mathAbs(barY - translatedThreshold) > minPointLength ? // stacked
  8373. yBottom - minPointLength : // keep position
  8374. translatedThreshold - (plotY <= translatedThreshold ? minPointLength : 0);
  8375. }
  8376. trackerY = barY - 3;
  8377. }
  8378. extend(point, {
  8379. barX: barX,
  8380. barY: barY,
  8381. barW: pointWidth,
  8382. barH: barH
  8383. });
  8384. point.shapeType = 'rect';
  8385. point.shapeArgs = {
  8386. x: barX,
  8387. y: barY,
  8388. width: pointWidth,
  8389. height: barH,
  8390. r: options.borderRadius
  8391. };
  8392. // make small columns responsive to mouse
  8393. point.trackerArgs = defined(trackerY) && merge(point.shapeArgs, {
  8394. height: mathMax(6, barH + 3),
  8395. y: trackerY
  8396. });
  8397. });
  8398. },
  8399. getSymbol: function(){
  8400. },
  8401. /**
  8402. * Columns have no graph
  8403. */
  8404. drawGraph: function() {},
  8405. /**
  8406. * Draw the columns. For bars, the series.group is rotated, so the same coordinates
  8407. * apply for columns and bars. This method is inherited by scatter series.
  8408. *
  8409. */
  8410. drawPoints: function() {
  8411. var series = this,
  8412. options = series.options,
  8413. renderer = series.chart.renderer,
  8414. graphic,
  8415. shapeArgs;
  8416. // draw the columns
  8417. each(series.data, function(point) {
  8418. var plotY = point.plotY;
  8419. if (plotY !== UNDEFINED && !isNaN(plotY)) {
  8420. graphic = point.graphic;
  8421. shapeArgs = point.shapeArgs;
  8422. if (graphic) { // update
  8423. stop(graphic);
  8424. graphic.animate(shapeArgs);
  8425. } else {
  8426. point.graphic = renderer[point.shapeType](shapeArgs)
  8427. .attr(point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE])
  8428. .add(series.group)
  8429. .shadow(options.shadow);
  8430. }
  8431. }
  8432. });
  8433. },
  8434. /**
  8435. * Draw the individual tracker elements.
  8436. * This method is inherited by scatter and pie charts too.
  8437. */
  8438. drawTracker: function() {
  8439. var series = this,
  8440. chart = series.chart,
  8441. renderer = chart.renderer,
  8442. shapeArgs,
  8443. tracker,
  8444. trackerLabel = +new Date(),
  8445. cursor = series.options.cursor,
  8446. css = cursor && { cursor: cursor },
  8447. rel;
  8448. each(series.data, function(point) {
  8449. tracker = point.tracker;
  8450. shapeArgs = point.trackerArgs || point.shapeArgs;
  8451. if (point.y !== null) {
  8452. if (tracker) {// update
  8453. tracker.attr(shapeArgs);
  8454. } else {
  8455. point.tracker =
  8456. renderer[point.shapeType](shapeArgs)
  8457. .attr({
  8458. isTracker: trackerLabel,
  8459. fill: TRACKER_FILL,
  8460. visibility: series.visible ? VISIBLE : HIDDEN,
  8461. zIndex: 1
  8462. })
  8463. .on(hasTouch ? 'touchstart' : 'mouseover', function(event) {
  8464. rel = event.relatedTarget || event.fromElement;
  8465. if (chart.hoverSeries != series && attr(rel, 'isTracker') != trackerLabel) {
  8466. series.onMouseOver();
  8467. }
  8468. point.onMouseOver();
  8469. })
  8470. .on('mouseout', function(event) {
  8471. if (!series.options.stickyTracking) {
  8472. rel = event.relatedTarget || event.toElement;
  8473. if (attr(rel, 'isTracker') != trackerLabel) {
  8474. series.onMouseOut();
  8475. }
  8476. }
  8477. })
  8478. .css(css)
  8479. .add(chart.trackerGroup);
  8480. }
  8481. }
  8482. });
  8483. },
  8484. /**
  8485. * Animate the column heights one by one from zero
  8486. * @param {Boolean} init Whether to initialize the animation or run it
  8487. */
  8488. animate: function(init) {
  8489. var series = this,
  8490. data = series.data;
  8491. if (!init) { // run the animation
  8492. /*
  8493. * Note: Ideally the animation should be initialized by calling
  8494. * series.group.hide(), and then calling series.group.show()
  8495. * after the animation was started. But this rendered the shadows
  8496. * invisible in IE8 standards mode. If the columns flicker on large
  8497. * datasets, this is the cause.
  8498. */
  8499. each(data, function(point) {
  8500. var graphic = point.graphic;
  8501. if (graphic) {
  8502. // start values
  8503. graphic.attr({
  8504. height: 0,
  8505. y: series.yAxis.translate(0, 0, 1)
  8506. });
  8507. // animate
  8508. graphic.animate({
  8509. height: point.barH,
  8510. y: point.barY
  8511. }, series.options.animation);
  8512. }
  8513. });
  8514. // delete this function to allow it only once
  8515. series.animate = null;
  8516. }
  8517. },
  8518. /**
  8519. * Remove this series from the chart
  8520. */
  8521. remove: function() {
  8522. var series = this,
  8523. chart = series.chart;
  8524. // column and bar series affects other series of the same type
  8525. // as they are either stacked or grouped
  8526. if (chart.hasRendered) {
  8527. each(chart.series, function(otherSeries) {
  8528. if (otherSeries.type == series.type) {
  8529. otherSeries.isDirty = true;
  8530. }
  8531. });
  8532. }
  8533. Series.prototype.remove.apply(series, arguments);
  8534. }
  8535. });
  8536. seriesTypes.column = ColumnSeries;
  8537. var BarSeries = extendClass(ColumnSeries, {
  8538. type: 'bar',
  8539. init: function(chart) {
  8540. chart.inverted = this.inverted = true;
  8541. ColumnSeries.prototype.init.apply(this, arguments);
  8542. }
  8543. });
  8544. seriesTypes.bar = BarSeries;
  8545. /**
  8546. * The scatter series class
  8547. */
  8548. var ScatterSeries = extendClass(Series, {
  8549. type: 'scatter',
  8550. /**
  8551. * Extend the base Series' translate method by adding shape type and
  8552. * arguments for the point trackers
  8553. */
  8554. translate: function() {
  8555. var series = this;
  8556. Series.prototype.translate.apply(series);
  8557. each(series.data, function(point) {
  8558. point.shapeType = 'circle';
  8559. point.shapeArgs = {
  8560. x: point.plotX,
  8561. y: point.plotY,
  8562. r: series.chart.options.tooltip.snap
  8563. };
  8564. });
  8565. },
  8566. /**
  8567. * Create individual tracker elements for each point
  8568. */
  8569. //drawTracker: ColumnSeries.prototype.drawTracker,
  8570. drawTracker: function() {
  8571. var series = this,
  8572. cursor = series.options.cursor,
  8573. css = cursor && { cursor: cursor },
  8574. graphic;
  8575. each(series.data, function(point) {
  8576. graphic = point.graphic;
  8577. if (graphic) { // doesn't exist for null points
  8578. graphic
  8579. .attr({ isTracker: true })
  8580. .on('mouseover', function(event) {
  8581. series.onMouseOver();
  8582. point.onMouseOver();
  8583. })
  8584. .on('mouseout', function(event) {
  8585. if (!series.options.stickyTracking) {
  8586. series.onMouseOut();
  8587. }
  8588. })
  8589. .css(css);
  8590. }
  8591. });
  8592. },
  8593. /**
  8594. * Cleaning the data is not necessary in a scatter plot
  8595. */
  8596. cleanData: function() {}
  8597. });
  8598. seriesTypes.scatter = ScatterSeries;
  8599. /**
  8600. * Extended point object for pies
  8601. */
  8602. var PiePoint = extendClass(Point, {
  8603. /**
  8604. * Initiate the pie slice
  8605. */
  8606. init: function () {
  8607. Point.prototype.init.apply(this, arguments);
  8608. var point = this,
  8609. toggleSlice;
  8610. //visible: options.visible !== false,
  8611. extend(point, {
  8612. visible: point.visible !== false,
  8613. name: pick(point.name, 'Slice')
  8614. });
  8615. // add event listener for select
  8616. toggleSlice = function() {
  8617. point.slice();
  8618. };
  8619. addEvent(point, 'select', toggleSlice);
  8620. addEvent(point, 'unselect', toggleSlice);
  8621. return point;
  8622. },
  8623. /**
  8624. * Toggle the visibility of the pie slice
  8625. * @param {Boolean} vis Whether to show the slice or not. If undefined, the
  8626. * visibility is toggled
  8627. */
  8628. setVisible: function(vis) {
  8629. var point = this,
  8630. chart = point.series.chart,
  8631. tracker = point.tracker,
  8632. dataLabel = point.dataLabel,
  8633. connector = point.connector,
  8634. method;
  8635. // if called without an argument, toggle visibility
  8636. point.visible = vis = vis === UNDEFINED ? !point.visible : vis;
  8637. method = vis ? 'show' : 'hide';
  8638. point.group[method]();
  8639. if (tracker) {
  8640. tracker[method]();
  8641. }
  8642. if (dataLabel) {
  8643. dataLabel[method]();
  8644. }
  8645. if (connector) {
  8646. connector[method]();
  8647. }
  8648. if (point.legendItem) {
  8649. chart.legend.colorizeItem(point, vis);
  8650. }
  8651. },
  8652. /**
  8653. * Set or toggle whether the slice is cut out from the pie
  8654. * @param {Boolean} sliced When undefined, the slice state is toggled
  8655. * @param {Boolean} redraw Whether to redraw the chart. True by default.
  8656. */
  8657. slice: function(sliced, redraw, animation) {
  8658. var point = this,
  8659. series = point.series,
  8660. chart = series.chart,
  8661. slicedTranslation = point.slicedTranslation;
  8662. setAnimation(animation, chart);
  8663. // redraw is true by default
  8664. redraw = pick(redraw, true);
  8665. // if called without an argument, toggle
  8666. sliced = point.sliced = defined(sliced) ? sliced : !point.sliced;
  8667. point.group.animate({
  8668. translateX: (sliced ? slicedTranslation[0] : chart.plotLeft),
  8669. translateY: (sliced ? slicedTranslation[1] : chart.plotTop)
  8670. });
  8671. }
  8672. });
  8673. /**
  8674. * The Pie series class
  8675. */
  8676. var PieSeries = extendClass(Series, {
  8677. type: 'pie',
  8678. isCartesian: false,
  8679. pointClass: PiePoint,
  8680. pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
  8681. stroke: 'borderColor',
  8682. 'stroke-width': 'borderWidth',
  8683. fill: 'color'
  8684. },
  8685. /**
  8686. * Pies have one color each point
  8687. */
  8688. getColor: function() {
  8689. // record first color for use in setData
  8690. this.initialColor = colorCounter;
  8691. },
  8692. /**
  8693. * Animate the column heights one by one from zero
  8694. * @param {Boolean} init Whether to initialize the animation or run it
  8695. */
  8696. animate: function(init) {
  8697. var series = this,
  8698. data = series.data;
  8699. each(data, function(point) {
  8700. var graphic = point.graphic,
  8701. args = point.shapeArgs,
  8702. up = -mathPI / 2;
  8703. if (graphic) {
  8704. // start values
  8705. graphic.attr({
  8706. r: 0,
  8707. start: up,
  8708. end: up
  8709. });
  8710. // animate
  8711. graphic.animate({
  8712. r: args.r,
  8713. start: args.start,
  8714. end: args.end
  8715. }, series.options.animation);
  8716. }
  8717. });
  8718. // delete this function to allow it only once
  8719. series.animate = null;
  8720. },
  8721. /**
  8722. * Do translation for pie slices
  8723. */
  8724. translate: function() {
  8725. var total = 0,
  8726. series = this,
  8727. cumulative = -0.25, // start at top
  8728. precision = 1000, // issue #172
  8729. options = series.options,
  8730. slicedOffset = options.slicedOffset,
  8731. connectorOffset = slicedOffset + options.borderWidth,
  8732. positions = options.center,
  8733. chart = series.chart,
  8734. plotWidth = chart.plotWidth,
  8735. plotHeight = chart.plotHeight,
  8736. start,
  8737. end,
  8738. angle,
  8739. data = series.data,
  8740. circ = 2 * mathPI,
  8741. fraction,
  8742. smallestSize = mathMin(plotWidth, plotHeight),
  8743. isPercent,
  8744. radiusX, // the x component of the radius vector for a given point
  8745. radiusY,
  8746. labelDistance = options.dataLabels.distance;
  8747. // get positions - either an integer or a percentage string must be given
  8748. positions.push(options.size, options.innerSize || 0);
  8749. positions = map(positions, function(length, i) {
  8750. isPercent = /%$/.test(length);
  8751. return isPercent ?
  8752. // i == 0: centerX, relative to width
  8753. // i == 1: centerY, relative to height
  8754. // i == 2: size, relative to smallestSize
  8755. [plotWidth, plotHeight, smallestSize, smallestSize][i] *
  8756. pInt(length) / 100:
  8757. length;
  8758. });
  8759. // utility for getting the x value from a given y, used for anticollision logic in data labels
  8760. series.getX = function(y, left) {
  8761. angle = math.asin((y - positions[1]) / (positions[2] / 2 + labelDistance));
  8762. return positions[0] +
  8763. (left ? -1 : 1) *
  8764. (mathCos(angle) * (positions[2] / 2 + labelDistance));
  8765. };
  8766. // set center for later use
  8767. series.center = positions;
  8768. // get the total sum
  8769. each(data, function(point) {
  8770. total += point.y;
  8771. });
  8772. each(data, function(point) {
  8773. // set start and end angle
  8774. fraction = total ? point.y / total : 0;
  8775. start = mathRound(cumulative * circ * precision) / precision;
  8776. cumulative += fraction;
  8777. end = mathRound(cumulative * circ * precision) / precision;
  8778. // set the shape
  8779. point.shapeType = 'arc';
  8780. point.shapeArgs = {
  8781. x: positions[0],
  8782. y: positions[1],
  8783. r: positions[2] / 2,
  8784. innerR: positions[3] / 2,
  8785. start: start,
  8786. end: end
  8787. };
  8788. // center for the sliced out slice
  8789. angle = (end + start) / 2;
  8790. point.slicedTranslation = map([
  8791. mathCos(angle) * slicedOffset + chart.plotLeft,
  8792. mathSin(angle) * slicedOffset + chart.plotTop
  8793. ], mathRound);
  8794. // set the anchor point for tooltips
  8795. radiusX = mathCos(angle) * positions[2] / 2;
  8796. radiusY = mathSin(angle) * positions[2] / 2;
  8797. point.tooltipPos = [
  8798. positions[0] + radiusX * 0.7,
  8799. positions[1] + radiusY * 0.7
  8800. ];
  8801. // set the anchor point for data labels
  8802. point.labelPos = [
  8803. positions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector
  8804. positions[1] + radiusY + mathSin(angle) * labelDistance, // a/a
  8805. positions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie
  8806. positions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a
  8807. positions[0] + radiusX, // landing point for connector
  8808. positions[1] + radiusY, // a/a
  8809. labelDistance < 0 ? // alignment
  8810. 'center' :
  8811. angle < circ / 4 ? 'left' : 'right', // alignment
  8812. angle // center angle
  8813. ];
  8814. // API properties
  8815. point.percentage = fraction * 100;
  8816. point.total = total;
  8817. });
  8818. this.setTooltipPoints();
  8819. },
  8820. /**
  8821. * Render the slices
  8822. */
  8823. render: function() {
  8824. var series = this;
  8825. // cache attributes for shapes
  8826. series.getAttribs();
  8827. this.drawPoints();
  8828. // draw the mouse tracking area
  8829. if (series.options.enableMouseTracking !== false) {
  8830. series.drawTracker();
  8831. }
  8832. this.drawDataLabels();
  8833. if (series.options.animation && series.animate) {
  8834. series.animate();
  8835. }
  8836. series.isDirty = false; // means data is in accordance with what you see
  8837. },
  8838. /**
  8839. * Draw the data points
  8840. */
  8841. drawPoints: function() {
  8842. var series = this,
  8843. chart = series.chart,
  8844. renderer = chart.renderer,
  8845. groupTranslation,
  8846. //center,
  8847. graphic,
  8848. group,
  8849. shapeArgs;
  8850. // draw the slices
  8851. each(series.data, function(point) {
  8852. graphic = point.graphic;
  8853. shapeArgs = point.shapeArgs;
  8854. group = point.group;
  8855. // create the group the first time
  8856. if (!group) {
  8857. group = point.group = renderer.g('point')
  8858. .attr({ zIndex: 5 })
  8859. .add();
  8860. }
  8861. // if the point is sliced, use special translation, else use plot area traslation
  8862. groupTranslation = point.sliced ? point.slicedTranslation : [chart.plotLeft, chart.plotTop];
  8863. group.translate(groupTranslation[0], groupTranslation[1])
  8864. // draw the slice
  8865. if (graphic) {
  8866. graphic.animate(shapeArgs);
  8867. } else {
  8868. point.graphic =
  8869. renderer.arc(shapeArgs)
  8870. .attr(extend(
  8871. point.pointAttr[NORMAL_STATE],
  8872. { 'stroke-linejoin': 'round' }
  8873. ))
  8874. .add(point.group);
  8875. }
  8876. // detect point specific visibility
  8877. if (point.visible === false) {
  8878. point.setVisible(false);
  8879. }
  8880. });
  8881. },
  8882. /**
  8883. * Override the base drawDataLabels method by pie specific functionality
  8884. */
  8885. drawDataLabels: function() {
  8886. var series = this,
  8887. data = series.data,
  8888. point,
  8889. chart = series.chart,
  8890. options = series.options.dataLabels,
  8891. connectorPadding = pick(options.connectorPadding, 10),
  8892. connectorWidth = pick(options.connectorWidth, 1),
  8893. connector,
  8894. connectorPath,
  8895. outside = options.distance > 0,
  8896. dataLabel,
  8897. labelPos,
  8898. labelHeight,
  8899. lastY,
  8900. centerY = series.center[1],
  8901. quarters = [// divide the points into quarters for anti collision
  8902. [], // top right
  8903. [], // bottom right
  8904. [], // bottom left
  8905. [] // top left
  8906. ],
  8907. x,
  8908. y,
  8909. visibility,
  8910. overlapping,
  8911. rankArr,
  8912. secondPass,
  8913. sign,
  8914. lowerHalf,
  8915. sort,
  8916. i = 4,
  8917. j;
  8918. // run parent method
  8919. Series.prototype.drawDataLabels.apply(series);
  8920. // arrange points for detection collision
  8921. each(data, function(point) {
  8922. var angle = point.labelPos[7],
  8923. quarter;
  8924. if (angle < 0) {
  8925. quarter = 0;
  8926. } else if (angle < mathPI / 2) {
  8927. quarter = 1;
  8928. } else if (angle < mathPI) {
  8929. quarter = 2;
  8930. } else {
  8931. quarter = 3;
  8932. }
  8933. quarters[quarter].push(point);
  8934. });
  8935. quarters[1].reverse();
  8936. quarters[3].reverse();
  8937. // define the sorting algorithm
  8938. sort = function(a,b) {
  8939. return a.y > b.y;
  8940. };
  8941. /* Loop over the points in each quartile, starting from the top and bottom
  8942. * of the pie to detect overlapping labels.
  8943. */
  8944. while (i--) {
  8945. overlapping = 0;
  8946. // create an array for sorting and ranking the points within each quarter
  8947. rankArr = [].concat(quarters[i]);
  8948. rankArr.sort(sort);
  8949. j = rankArr.length;
  8950. while (j--) {
  8951. rankArr[j].rank = j;
  8952. }
  8953. /* In the first pass, count the number of overlapping labels. In the second
  8954. * pass, remove the labels with lowest rank/values.
  8955. */
  8956. for (secondPass = 0; secondPass < 2; secondPass++) {
  8957. lowerHalf = i % 3;
  8958. lastY = lowerHalf ? 9999 : -9999;
  8959. sign = lowerHalf ? -1 : 1;
  8960. for (j = 0; j < quarters[i].length; j++) {
  8961. point = quarters[i][j];
  8962. if ((dataLabel = point.dataLabel)) {
  8963. labelPos = point.labelPos;
  8964. visibility = VISIBLE;
  8965. x = labelPos[0];
  8966. y = labelPos[1];
  8967. // assume all labels have equal height
  8968. if (!labelHeight) {
  8969. labelHeight = dataLabel && dataLabel.getBBox().height;
  8970. }
  8971. // anticollision
  8972. if (outside) {
  8973. if (secondPass && point.rank < overlapping) {
  8974. visibility = HIDDEN;
  8975. } else if ((!lowerHalf && y < lastY + labelHeight) ||
  8976. (lowerHalf && y > lastY - labelHeight)) {
  8977. y = lastY + sign * labelHeight;
  8978. x = series.getX(y, i > 1);
  8979. if ((!lowerHalf && y + labelHeight > centerY) ||
  8980. (lowerHalf && y -labelHeight < centerY)) {
  8981. if (secondPass) {
  8982. visibility = HIDDEN;
  8983. } else {
  8984. overlapping++;
  8985. }
  8986. }
  8987. }
  8988. }
  8989. if (point.visible === false) {
  8990. visibility = HIDDEN;
  8991. }
  8992. if (visibility == VISIBLE) {
  8993. lastY = y;
  8994. }
  8995. if (secondPass) {
  8996. // move or place the data label
  8997. dataLabel
  8998. .attr({
  8999. visibility: visibility,
  9000. align: labelPos[6]
  9001. })
  9002. [dataLabel.moved ? 'animate' : 'attr']({
  9003. x: x + options.x +
  9004. ({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0),
  9005. y: y + options.y
  9006. });
  9007. dataLabel.moved = true;
  9008. // draw the connector
  9009. if (outside && connectorWidth) {
  9010. connector = point.connector;
  9011. connectorPath = [
  9012. M,
  9013. x + (labelPos[6] == 'left' ? 5 : -5), y, // end of the string at the label
  9014. L,
  9015. x, y, // first break, next to the label
  9016. L,
  9017. labelPos[2], labelPos[3], // second break
  9018. L,
  9019. labelPos[4], labelPos[5] // base
  9020. ];
  9021. if (connector) {
  9022. connector.animate({ d: connectorPath });
  9023. connector.attr('visibility', visibility);
  9024. } else {
  9025. point.connector = connector = series.chart.renderer.path(connectorPath).attr({
  9026. 'stroke-width': connectorWidth,
  9027. stroke: options.connectorColor || '#606060',
  9028. visibility: visibility,
  9029. zIndex: 3
  9030. })
  9031. .translate(chart.plotLeft, chart.plotTop)
  9032. .add();
  9033. }
  9034. }
  9035. }
  9036. }
  9037. }
  9038. }
  9039. }
  9040. },
  9041. /**
  9042. * Draw point specific tracker objects. Inherit directly from column series.
  9043. */
  9044. drawTracker: ColumnSeries.prototype.drawTracker,
  9045. /**
  9046. * Pies don't have point marker symbols
  9047. */
  9048. getSymbol: function() {}
  9049. });
  9050. seriesTypes.pie = PieSeries;
  9051. // global variables
  9052. win.Highcharts = {
  9053. Chart: Chart,
  9054. dateFormat: dateFormat,
  9055. pathAnim: pathAnim,
  9056. getOptions: getOptions,
  9057. numberFormat: numberFormat,
  9058. Point: Point,
  9059. Color: Color,
  9060. Renderer: Renderer,
  9061. seriesTypes: seriesTypes,
  9062. setOptions: setOptions,
  9063. Series: Series,
  9064. // Expose utility funcitons for modules
  9065. addEvent: addEvent,
  9066. createElement: createElement,
  9067. discardElement: discardElement,
  9068. css: css,
  9069. each: each,
  9070. extend: extend,
  9071. map: map,
  9072. merge: merge,
  9073. pick: pick,
  9074. extendClass: extendClass,
  9075. version: '2.1.4'
  9076. };
  9077. })();