highcharts-android.js 250 KB

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