highcharts.src.js 368 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681
  1. // ==ClosureCompiler==
  2. // @compilation_level SIMPLE_OPTIMIZATIONS
  3. /**
  4. * @license Highcharts JS v2.2.5 (2012-06-08)
  5. *
  6. * (c) 2009-2011 Torstein Hønsi
  7. *
  8. * License: www.highcharts.com/license
  9. */
  10. // JSLint options:
  11. /*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console */
  12. (function () {
  13. // encapsulated variables
  14. var UNDEFINED,
  15. doc = document,
  16. win = window,
  17. math = Math,
  18. mathRound = math.round,
  19. mathFloor = math.floor,
  20. mathCeil = math.ceil,
  21. mathMax = math.max,
  22. mathMin = math.min,
  23. mathAbs = math.abs,
  24. mathCos = math.cos,
  25. mathSin = math.sin,
  26. mathPI = math.PI,
  27. deg2rad = mathPI * 2 / 360,
  28. // some variables
  29. userAgent = navigator.userAgent,
  30. isIE = /msie/i.test(userAgent) && !win.opera,
  31. docMode8 = doc.documentMode === 8,
  32. isWebKit = /AppleWebKit/.test(userAgent),
  33. isFirefox = /Firefox/.test(userAgent),
  34. SVG_NS = 'http://www.w3.org/2000/svg',
  35. hasSVG = !!doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,
  36. hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38
  37. useCanVG = !hasSVG && !isIE && !!doc.createElement('canvas').getContext,
  38. Renderer,
  39. hasTouch = doc.documentElement.ontouchstart !== UNDEFINED,
  40. symbolSizes = {},
  41. idCounter = 0,
  42. garbageBin,
  43. defaultOptions,
  44. dateFormat, // function
  45. globalAnimation,
  46. pathAnim,
  47. timeUnits,
  48. noop = function () {},
  49. // some constants for frequently used strings
  50. DIV = 'div',
  51. ABSOLUTE = 'absolute',
  52. RELATIVE = 'relative',
  53. HIDDEN = 'hidden',
  54. PREFIX = 'highcharts-',
  55. VISIBLE = 'visible',
  56. PX = 'px',
  57. NONE = 'none',
  58. M = 'M',
  59. L = 'L',
  60. /*
  61. * Empirical lowest possible opacities for TRACKER_FILL
  62. * IE6: 0.002
  63. * IE7: 0.002
  64. * IE8: 0.002
  65. * IE9: 0.00000000001 (unlimited)
  66. * FF: 0.00000000001 (unlimited)
  67. * Chrome: 0.000001
  68. * Safari: 0.000001
  69. * Opera: 0.00000000001 (unlimited)
  70. */
  71. TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.000001 : 0.002) + ')', // invisible but clickable
  72. //TRACKER_FILL = 'rgba(192,192,192,0.5)',
  73. NORMAL_STATE = '',
  74. HOVER_STATE = 'hover',
  75. SELECT_STATE = 'select',
  76. MILLISECOND = 'millisecond',
  77. SECOND = 'second',
  78. MINUTE = 'minute',
  79. HOUR = 'hour',
  80. DAY = 'day',
  81. WEEK = 'week',
  82. MONTH = 'month',
  83. YEAR = 'year',
  84. // constants for attributes
  85. FILL = 'fill',
  86. LINEAR_GRADIENT = 'linearGradient',
  87. STOPS = 'stops',
  88. STROKE = 'stroke',
  89. STROKE_WIDTH = 'stroke-width',
  90. // time methods, changed based on whether or not UTC is used
  91. makeTime,
  92. getMinutes,
  93. getHours,
  94. getDay,
  95. getDate,
  96. getMonth,
  97. getFullYear,
  98. setMinutes,
  99. setHours,
  100. setDate,
  101. setMonth,
  102. setFullYear,
  103. // lookup over the types and the associated classes
  104. seriesTypes = {};
  105. // The Highcharts namespace
  106. win.Highcharts = {};
  107. /**
  108. * Extend an object with the members of another
  109. * @param {Object} a The object to be extended
  110. * @param {Object} b The object to add to the first one
  111. */
  112. function extend(a, b) {
  113. var n;
  114. if (!a) {
  115. a = {};
  116. }
  117. for (n in b) {
  118. a[n] = b[n];
  119. }
  120. return a;
  121. }
  122. /**
  123. * Take an array and turn into a hash with even number arguments as keys and odd numbers as
  124. * values. Allows creating constants for commonly used style properties, attributes etc.
  125. * Avoid it in performance critical situations like looping
  126. */
  127. function hash() {
  128. var i = 0,
  129. args = arguments,
  130. length = args.length,
  131. obj = {};
  132. for (; i < length; i++) {
  133. obj[args[i++]] = args[i];
  134. }
  135. return obj;
  136. }
  137. /**
  138. * Shortcut for parseInt
  139. * @param {Object} s
  140. * @param {Number} mag Magnitude
  141. */
  142. function pInt(s, mag) {
  143. return parseInt(s, mag || 10);
  144. }
  145. /**
  146. * Check for string
  147. * @param {Object} s
  148. */
  149. function isString(s) {
  150. return typeof s === 'string';
  151. }
  152. /**
  153. * Check for object
  154. * @param {Object} obj
  155. */
  156. function isObject(obj) {
  157. return typeof obj === 'object';
  158. }
  159. /**
  160. * Check for array
  161. * @param {Object} obj
  162. */
  163. function isArray(obj) {
  164. return Object.prototype.toString.call(obj) === '[object Array]';
  165. }
  166. /**
  167. * Check for number
  168. * @param {Object} n
  169. */
  170. function isNumber(n) {
  171. return typeof n === 'number';
  172. }
  173. function log2lin(num) {
  174. return math.log(num) / math.LN10;
  175. }
  176. function lin2log(num) {
  177. return math.pow(10, num);
  178. }
  179. /**
  180. * Remove last occurence of an item from an array
  181. * @param {Array} arr
  182. * @param {Mixed} item
  183. */
  184. function erase(arr, item) {
  185. var i = arr.length;
  186. while (i--) {
  187. if (arr[i] === item) {
  188. arr.splice(i, 1);
  189. break;
  190. }
  191. }
  192. //return arr;
  193. }
  194. /**
  195. * Returns true if the object is not null or undefined. Like MooTools' $.defined.
  196. * @param {Object} obj
  197. */
  198. function defined(obj) {
  199. return obj !== UNDEFINED && obj !== null;
  200. }
  201. /**
  202. * Set or get an attribute or an object of attributes. Can't use jQuery attr because
  203. * it attempts to set expando properties on the SVG element, which is not allowed.
  204. *
  205. * @param {Object} elem The DOM element to receive the attribute(s)
  206. * @param {String|Object} prop The property or an abject of key-value pairs
  207. * @param {String} value The value if a single property is set
  208. */
  209. function attr(elem, prop, value) {
  210. var key,
  211. setAttribute = 'setAttribute',
  212. ret;
  213. // if the prop is a string
  214. if (isString(prop)) {
  215. // set the value
  216. if (defined(value)) {
  217. elem[setAttribute](prop, value);
  218. // get the value
  219. } else if (elem && elem.getAttribute) { // elem not defined when printing pie demo...
  220. ret = elem.getAttribute(prop);
  221. }
  222. // else if prop is defined, it is a hash of key/value pairs
  223. } else if (defined(prop) && isObject(prop)) {
  224. for (key in prop) {
  225. elem[setAttribute](key, prop[key]);
  226. }
  227. }
  228. return ret;
  229. }
  230. /**
  231. * Check if an element is an array, and if not, make it into an array. Like
  232. * MooTools' $.splat.
  233. */
  234. function splat(obj) {
  235. return isArray(obj) ? obj : [obj];
  236. }
  237. /**
  238. * Return the first value that is defined. Like MooTools' $.pick.
  239. */
  240. function pick() {
  241. var args = arguments,
  242. i,
  243. arg,
  244. length = args.length;
  245. for (i = 0; i < length; i++) {
  246. arg = args[i];
  247. if (typeof arg !== 'undefined' && arg !== null) {
  248. return arg;
  249. }
  250. }
  251. }
  252. /**
  253. * Set CSS on a given element
  254. * @param {Object} el
  255. * @param {Object} styles Style object with camel case property names
  256. */
  257. function css(el, styles) {
  258. if (isIE) {
  259. if (styles && styles.opacity !== UNDEFINED) {
  260. styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')';
  261. }
  262. }
  263. extend(el.style, styles);
  264. }
  265. /**
  266. * Utility function to create element with attributes and styles
  267. * @param {Object} tag
  268. * @param {Object} attribs
  269. * @param {Object} styles
  270. * @param {Object} parent
  271. * @param {Object} nopad
  272. */
  273. function createElement(tag, attribs, styles, parent, nopad) {
  274. var el = doc.createElement(tag);
  275. if (attribs) {
  276. extend(el, attribs);
  277. }
  278. if (nopad) {
  279. css(el, {padding: 0, border: NONE, margin: 0});
  280. }
  281. if (styles) {
  282. css(el, styles);
  283. }
  284. if (parent) {
  285. parent.appendChild(el);
  286. }
  287. return el;
  288. }
  289. /**
  290. * Extend a prototyped class by new members
  291. * @param {Object} parent
  292. * @param {Object} members
  293. */
  294. function extendClass(parent, members) {
  295. var object = function () {};
  296. object.prototype = new parent();
  297. extend(object.prototype, members);
  298. return object;
  299. }
  300. /**
  301. * How many decimals are there in a number
  302. */
  303. function getDecimals(number) {
  304. number = (number || 0).toString();
  305. return number.indexOf('.') > -1 ?
  306. number.split('.')[1].length :
  307. 0;
  308. }
  309. /**
  310. * Format a number and return a string based on input settings
  311. * @param {Number} number The input number to format
  312. * @param {Number} decimals The amount of decimals
  313. * @param {String} decPoint The decimal point, defaults to the one given in the lang options
  314. * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options
  315. */
  316. function numberFormat(number, decimals, decPoint, thousandsSep) {
  317. var lang = defaultOptions.lang,
  318. // http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/
  319. n = number,
  320. c = decimals === -1 ?
  321. getDecimals(number) :
  322. (isNaN(decimals = mathAbs(decimals)) ? 2 : decimals),
  323. d = decPoint === undefined ? lang.decimalPoint : decPoint,
  324. t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep,
  325. s = n < 0 ? "-" : "",
  326. i = String(pInt(n = mathAbs(+n || 0).toFixed(c))),
  327. j = i.length > 3 ? i.length % 3 : 0;
  328. return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) +
  329. (c ? d + mathAbs(n - i).toFixed(c).slice(2) : "");
  330. }
  331. /**
  332. * Pad a string to a given length by adding 0 to the beginning
  333. * @param {Number} number
  334. * @param {Number} length
  335. */
  336. function pad(number, length) {
  337. // Create an array of the remaining length +1 and join it with 0's
  338. return new Array((length || 2) + 1 - String(number).length).join(0) + number;
  339. }
  340. /**
  341. * Based on http://www.php.net/manual/en/function.strftime.php
  342. * @param {String} format
  343. * @param {Number} timestamp
  344. * @param {Boolean} capitalize
  345. */
  346. dateFormat = function (format, timestamp, capitalize) {
  347. if (!defined(timestamp) || isNaN(timestamp)) {
  348. return 'Invalid date';
  349. }
  350. format = pick(format, '%Y-%m-%d %H:%M:%S');
  351. var date = new Date(timestamp),
  352. key, // used in for constuct below
  353. // get the basic time values
  354. hours = date[getHours](),
  355. day = date[getDay](),
  356. dayOfMonth = date[getDate](),
  357. month = date[getMonth](),
  358. fullYear = date[getFullYear](),
  359. lang = defaultOptions.lang,
  360. langWeekdays = lang.weekdays,
  361. /* // uncomment this and the 'W' format key below to enable week numbers
  362. weekNumber = function () {
  363. var clone = new Date(date.valueOf()),
  364. day = clone[getDay]() == 0 ? 7 : clone[getDay](),
  365. dayNumber;
  366. clone.setDate(clone[getDate]() + 4 - day);
  367. dayNumber = mathFloor((clone.getTime() - new Date(clone[getFullYear](), 0, 1, -6)) / 86400000);
  368. return 1 + mathFloor(dayNumber / 7);
  369. },
  370. */
  371. // list all format keys
  372. replacements = {
  373. // Day
  374. 'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon'
  375. 'A': langWeekdays[day], // Long weekday, like 'Monday'
  376. 'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31
  377. 'e': dayOfMonth, // Day of the month, 1 through 31
  378. // Week (none implemented)
  379. //'W': weekNumber(),
  380. // Month
  381. 'b': lang.shortMonths[month], // Short month, like 'Jan'
  382. 'B': lang.months[month], // Long month, like 'January'
  383. 'm': pad(month + 1), // Two digit month number, 01 through 12
  384. // Year
  385. 'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009
  386. 'Y': fullYear, // Four digits year, like 2009
  387. // Time
  388. 'H': pad(hours), // Two digits hours in 24h format, 00 through 23
  389. 'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11
  390. 'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12
  391. 'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59
  392. 'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM
  393. 'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM
  394. 'S': pad(date.getSeconds()), // Two digits seconds, 00 through 59
  395. 'L': pad(mathRound(timestamp % 1000), 3) // Milliseconds (naming from Ruby)
  396. };
  397. // do the replaces
  398. for (key in replacements) {
  399. format = format.replace('%' + key, replacements[key]);
  400. }
  401. // Optionally capitalize the string and return
  402. return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;
  403. };
  404. /**
  405. * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
  406. * @param {Number} interval
  407. * @param {Array} multiples
  408. * @param {Number} magnitude
  409. * @param {Object} options
  410. */
  411. function normalizeTickInterval(interval, multiples, magnitude, options) {
  412. var normalized, i;
  413. // round to a tenfold of 1, 2, 2.5 or 5
  414. magnitude = pick(magnitude, 1);
  415. normalized = interval / magnitude;
  416. // multiples for a linear scale
  417. if (!multiples) {
  418. multiples = [1, 2, 2.5, 5, 10];
  419. // the allowDecimals option
  420. if (options && options.allowDecimals === false) {
  421. if (magnitude === 1) {
  422. multiples = [1, 2, 5, 10];
  423. } else if (magnitude <= 0.1) {
  424. multiples = [1 / magnitude];
  425. }
  426. }
  427. }
  428. // normalize the interval to the nearest multiple
  429. for (i = 0; i < multiples.length; i++) {
  430. interval = multiples[i];
  431. if (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2) {
  432. break;
  433. }
  434. }
  435. // multiply back to the correct magnitude
  436. interval *= magnitude;
  437. return interval;
  438. }
  439. /**
  440. * Get a normalized tick interval for dates. Returns a configuration object with
  441. * unit range (interval), count and name. Used to prepare data for getTimeTicks.
  442. * Previously this logic was part of getTimeTicks, but as getTimeTicks now runs
  443. * of segments in stock charts, the normalizing logic was extracted in order to
  444. * prevent it for running over again for each segment having the same interval.
  445. * #662, #697.
  446. */
  447. function normalizeTimeTickInterval(tickInterval, unitsOption) {
  448. var units = unitsOption || [[
  449. MILLISECOND, // unit name
  450. [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
  451. ], [
  452. SECOND,
  453. [1, 2, 5, 10, 15, 30]
  454. ], [
  455. MINUTE,
  456. [1, 2, 5, 10, 15, 30]
  457. ], [
  458. HOUR,
  459. [1, 2, 3, 4, 6, 8, 12]
  460. ], [
  461. DAY,
  462. [1, 2]
  463. ], [
  464. WEEK,
  465. [1, 2]
  466. ], [
  467. MONTH,
  468. [1, 2, 3, 4, 6]
  469. ], [
  470. YEAR,
  471. null
  472. ]],
  473. unit = units[units.length - 1], // default unit is years
  474. interval = timeUnits[unit[0]],
  475. multiples = unit[1],
  476. count,
  477. i;
  478. // loop through the units to find the one that best fits the tickInterval
  479. for (i = 0; i < units.length; i++) {
  480. unit = units[i];
  481. interval = timeUnits[unit[0]];
  482. multiples = unit[1];
  483. if (units[i + 1]) {
  484. // lessThan is in the middle between the highest multiple and the next unit.
  485. var lessThan = (interval * multiples[multiples.length - 1] +
  486. timeUnits[units[i + 1][0]]) / 2;
  487. // break and keep the current unit
  488. if (tickInterval <= lessThan) {
  489. break;
  490. }
  491. }
  492. }
  493. // prevent 2.5 years intervals, though 25, 250 etc. are allowed
  494. if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) {
  495. multiples = [1, 2, 5];
  496. }
  497. // prevent 2.5 years intervals, though 25, 250 etc. are allowed
  498. if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) {
  499. multiples = [1, 2, 5];
  500. }
  501. // get the count
  502. count = normalizeTickInterval(tickInterval / interval, multiples);
  503. return {
  504. unitRange: interval,
  505. count: count,
  506. unitName: unit[0]
  507. };
  508. }
  509. /**
  510. * Set the tick positions to a time unit that makes sense, for example
  511. * on the first of each month or on every Monday. Return an array
  512. * with the time positions. Used in datetime axes as well as for grouping
  513. * data on a datetime axis.
  514. *
  515. * @param {Object} normalizedInterval The interval in axis values (ms) and the count
  516. * @param {Number} min The minimum in axis values
  517. * @param {Number} max The maximum in axis values
  518. * @param {Number} startOfWeek
  519. */
  520. function getTimeTicks(normalizedInterval, min, max, startOfWeek) {
  521. var tickPositions = [],
  522. i,
  523. higherRanks = {},
  524. useUTC = defaultOptions.global.useUTC,
  525. minYear, // used in months and years as a basis for Date.UTC()
  526. minDate = new Date(min),
  527. interval = normalizedInterval.unitRange,
  528. count = normalizedInterval.count;
  529. if (interval >= timeUnits[SECOND]) { // second
  530. minDate.setMilliseconds(0);
  531. minDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 :
  532. count * mathFloor(minDate.getSeconds() / count));
  533. }
  534. if (interval >= timeUnits[MINUTE]) { // minute
  535. minDate[setMinutes](interval >= timeUnits[HOUR] ? 0 :
  536. count * mathFloor(minDate[getMinutes]() / count));
  537. }
  538. if (interval >= timeUnits[HOUR]) { // hour
  539. minDate[setHours](interval >= timeUnits[DAY] ? 0 :
  540. count * mathFloor(minDate[getHours]() / count));
  541. }
  542. if (interval >= timeUnits[DAY]) { // day
  543. minDate[setDate](interval >= timeUnits[MONTH] ? 1 :
  544. count * mathFloor(minDate[getDate]() / count));
  545. }
  546. if (interval >= timeUnits[MONTH]) { // month
  547. minDate[setMonth](interval >= timeUnits[YEAR] ? 0 :
  548. count * mathFloor(minDate[getMonth]() / count));
  549. minYear = minDate[getFullYear]();
  550. }
  551. if (interval >= timeUnits[YEAR]) { // year
  552. minYear -= minYear % count;
  553. minDate[setFullYear](minYear);
  554. }
  555. // week is a special case that runs outside the hierarchy
  556. if (interval === timeUnits[WEEK]) {
  557. // get start of current week, independent of count
  558. minDate[setDate](minDate[getDate]() - minDate[getDay]() +
  559. pick(startOfWeek, 1));
  560. }
  561. // get tick positions
  562. i = 1;
  563. minYear = minDate[getFullYear]();
  564. var time = minDate.getTime(),
  565. minMonth = minDate[getMonth](),
  566. minDateDate = minDate[getDate](),
  567. timezoneOffset = useUTC ?
  568. 0 :
  569. (24 * 3600 * 1000 + minDate.getTimezoneOffset() * 60 * 1000) % (24 * 3600 * 1000); // #950
  570. // iterate and add tick positions at appropriate values
  571. while (time < max) {
  572. tickPositions.push(time);
  573. // if the interval is years, use Date.UTC to increase years
  574. if (interval === timeUnits[YEAR]) {
  575. time = makeTime(minYear + i * count, 0);
  576. // if the interval is months, use Date.UTC to increase months
  577. } else if (interval === timeUnits[MONTH]) {
  578. time = makeTime(minYear, minMonth + i * count);
  579. // if we're using global time, the interval is not fixed as it jumps
  580. // one hour at the DST crossover
  581. } else if (!useUTC && (interval === timeUnits[DAY] || interval === timeUnits[WEEK])) {
  582. time = makeTime(minYear, minMonth, minDateDate +
  583. i * count * (interval === timeUnits[DAY] ? 1 : 7));
  584. // else, the interval is fixed and we use simple addition
  585. } else {
  586. time += interval * count;
  587. // mark new days if the time is dividable by day
  588. if (interval <= timeUnits[HOUR] && time % timeUnits[DAY] === timezoneOffset) {
  589. higherRanks[time] = DAY;
  590. }
  591. }
  592. i++;
  593. }
  594. // push the last time
  595. tickPositions.push(time);
  596. // record information on the chosen unit - for dynamic label formatter
  597. tickPositions.info = extend(normalizedInterval, {
  598. higherRanks: higherRanks,
  599. totalRange: interval * count
  600. });
  601. return tickPositions;
  602. }
  603. /**
  604. * Helper class that contains variuos counters that are local to the chart.
  605. */
  606. function ChartCounters() {
  607. this.color = 0;
  608. this.symbol = 0;
  609. }
  610. ChartCounters.prototype = {
  611. /**
  612. * Wraps the color counter if it reaches the specified length.
  613. */
  614. wrapColor: function (length) {
  615. if (this.color >= length) {
  616. this.color = 0;
  617. }
  618. },
  619. /**
  620. * Wraps the symbol counter if it reaches the specified length.
  621. */
  622. wrapSymbol: function (length) {
  623. if (this.symbol >= length) {
  624. this.symbol = 0;
  625. }
  626. }
  627. };
  628. /**
  629. * Utility method that sorts an object array and keeping the order of equal items.
  630. * ECMA script standard does not specify the behaviour when items are equal.
  631. */
  632. function stableSort(arr, sortFunction) {
  633. var length = arr.length,
  634. sortValue,
  635. i;
  636. // Add index to each item
  637. for (i = 0; i < length; i++) {
  638. arr[i].ss_i = i; // stable sort index
  639. }
  640. arr.sort(function (a, b) {
  641. sortValue = sortFunction(a, b);
  642. return sortValue === 0 ? a.ss_i - b.ss_i : sortValue;
  643. });
  644. // Remove index from items
  645. for (i = 0; i < length; i++) {
  646. delete arr[i].ss_i; // stable sort index
  647. }
  648. }
  649. /**
  650. * Non-recursive method to find the lowest member of an array. Math.min raises a maximum
  651. * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This
  652. * method is slightly slower, but safe.
  653. */
  654. function arrayMin(data) {
  655. var i = data.length,
  656. min = data[0];
  657. while (i--) {
  658. if (data[i] < min) {
  659. min = data[i];
  660. }
  661. }
  662. return min;
  663. }
  664. /**
  665. * Non-recursive method to find the lowest member of an array. Math.min raises a maximum
  666. * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This
  667. * method is slightly slower, but safe.
  668. */
  669. function arrayMax(data) {
  670. var i = data.length,
  671. max = data[0];
  672. while (i--) {
  673. if (data[i] > max) {
  674. max = data[i];
  675. }
  676. }
  677. return max;
  678. }
  679. /**
  680. * Utility method that destroys any SVGElement or VMLElement that are properties on the given object.
  681. * It loops all properties and invokes destroy if there is a destroy method. The property is
  682. * then delete'ed.
  683. * @param {Object} The object to destroy properties on
  684. * @param {Object} Exception, do not destroy this property, only delete it.
  685. */
  686. function destroyObjectProperties(obj, except) {
  687. var n;
  688. for (n in obj) {
  689. // If the object is non-null and destroy is defined
  690. if (obj[n] && obj[n] !== except && obj[n].destroy) {
  691. // Invoke the destroy
  692. obj[n].destroy();
  693. }
  694. // Delete the property from the object.
  695. delete obj[n];
  696. }
  697. }
  698. /**
  699. * Discard an element by moving it to the bin and delete
  700. * @param {Object} The HTML node to discard
  701. */
  702. function discardElement(element) {
  703. // create a garbage bin element, not part of the DOM
  704. if (!garbageBin) {
  705. garbageBin = createElement(DIV);
  706. }
  707. // move the node and empty bin
  708. if (element) {
  709. garbageBin.appendChild(element);
  710. }
  711. garbageBin.innerHTML = '';
  712. }
  713. /**
  714. * Provide error messages for debugging, with links to online explanation
  715. */
  716. function error(code, stop) {
  717. var msg = 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code;
  718. if (stop) {
  719. throw msg;
  720. } else if (win.console) {
  721. console.log(msg);
  722. }
  723. }
  724. /**
  725. * Fix JS round off float errors
  726. * @param {Number} num
  727. */
  728. function correctFloat(num) {
  729. return parseFloat(
  730. num.toPrecision(14)
  731. );
  732. }
  733. /**
  734. * The time unit lookup
  735. */
  736. /*jslint white: true*/
  737. timeUnits = hash(
  738. MILLISECOND, 1,
  739. SECOND, 1000,
  740. MINUTE, 60000,
  741. HOUR, 3600000,
  742. DAY, 24 * 3600000,
  743. WEEK, 7 * 24 * 3600000,
  744. MONTH, 30 * 24 * 3600000,
  745. YEAR, 31556952000
  746. );
  747. /*jslint white: false*/
  748. /**
  749. * Path interpolation algorithm used across adapters
  750. */
  751. pathAnim = {
  752. /**
  753. * Prepare start and end values so that the path can be animated one to one
  754. */
  755. init: function (elem, fromD, toD) {
  756. fromD = fromD || '';
  757. var shift = elem.shift,
  758. bezier = fromD.indexOf('C') > -1,
  759. numParams = bezier ? 7 : 3,
  760. endLength,
  761. slice,
  762. i,
  763. start = fromD.split(' '),
  764. end = [].concat(toD), // copy
  765. startBaseLine,
  766. endBaseLine,
  767. sixify = function (arr) { // in splines make move points have six parameters like bezier curves
  768. i = arr.length;
  769. while (i--) {
  770. if (arr[i] === M) {
  771. arr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]);
  772. }
  773. }
  774. };
  775. if (bezier) {
  776. sixify(start);
  777. sixify(end);
  778. }
  779. // pull out the base lines before padding
  780. if (elem.isArea) {
  781. startBaseLine = start.splice(start.length - 6, 6);
  782. endBaseLine = end.splice(end.length - 6, 6);
  783. }
  784. // if shifting points, prepend a dummy point to the end path
  785. if (shift <= end.length / numParams) {
  786. while (shift--) {
  787. end = [].concat(end).splice(0, numParams).concat(end);
  788. }
  789. }
  790. elem.shift = 0; // reset for following animations
  791. // copy and append last point until the length matches the end length
  792. if (start.length) {
  793. endLength = end.length;
  794. while (start.length < endLength) {
  795. //bezier && sixify(start);
  796. slice = [].concat(start).splice(start.length - numParams, numParams);
  797. if (bezier) { // disable first control point
  798. slice[numParams - 6] = slice[numParams - 2];
  799. slice[numParams - 5] = slice[numParams - 1];
  800. }
  801. start = start.concat(slice);
  802. }
  803. }
  804. if (startBaseLine) { // append the base lines for areas
  805. start = start.concat(startBaseLine);
  806. end = end.concat(endBaseLine);
  807. }
  808. return [start, end];
  809. },
  810. /**
  811. * Interpolate each value of the path and return the array
  812. */
  813. step: function (start, end, pos, complete) {
  814. var ret = [],
  815. i = start.length,
  816. startVal;
  817. if (pos === 1) { // land on the final path without adjustment points appended in the ends
  818. ret = complete;
  819. } else if (i === end.length && pos < 1) {
  820. while (i--) {
  821. startVal = parseFloat(start[i]);
  822. ret[i] =
  823. isNaN(startVal) ? // a letter instruction like M or L
  824. start[i] :
  825. pos * (parseFloat(end[i] - startVal)) + startVal;
  826. }
  827. } else { // if animation is finished or length not matching, land on right value
  828. ret = end;
  829. }
  830. return ret;
  831. }
  832. };
  833. /**
  834. * Set the global animation to either a given value, or fall back to the
  835. * given chart's animation option
  836. * @param {Object} animation
  837. * @param {Object} chart
  838. */
  839. function setAnimation(animation, chart) {
  840. globalAnimation = pick(animation, chart.animation);
  841. }
  842. // check for a custom HighchartsAdapter defined prior to this file
  843. var globalAdapter = win.HighchartsAdapter,
  844. adapter = globalAdapter || {},
  845. // Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object
  846. // and all the utility functions will be null. In that case they are populated by the
  847. // default adapters below.
  848. adapterRun = adapter.adapterRun,
  849. getScript = adapter.getScript,
  850. each = adapter.each,
  851. grep = adapter.grep,
  852. offset = adapter.offset,
  853. map = adapter.map,
  854. merge = adapter.merge,
  855. addEvent = adapter.addEvent,
  856. removeEvent = adapter.removeEvent,
  857. fireEvent = adapter.fireEvent,
  858. washMouseEvent = adapter.washMouseEvent,
  859. animate = adapter.animate,
  860. stop = adapter.stop;
  861. /*
  862. * Define the adapter for frameworks. If an external adapter is not defined,
  863. * Highcharts reverts to the built-in jQuery adapter.
  864. */
  865. if (globalAdapter && globalAdapter.init) {
  866. // Initialize the adapter with the pathAnim object that takes care
  867. // of path animations.
  868. globalAdapter.init(pathAnim);
  869. }
  870. if (!globalAdapter && win.jQuery) {
  871. var jQ = jQuery;
  872. /**
  873. * Downloads a script and executes a callback when done.
  874. * @param {String} scriptLocation
  875. * @param {Function} callback
  876. */
  877. getScript = jQ.getScript;
  878. /**
  879. * A direct link to jQuery methods. MooTools and Prototype adapters must be implemented for each case of method.
  880. * @param {Object} elem The HTML element
  881. * @param {String} method Which method to run on the wrapped element
  882. */
  883. adapterRun = function (elem, method) {
  884. return jQ(elem)[method]();
  885. };
  886. /**
  887. * Utility for iterating over an array. Parameters are reversed compared to jQuery.
  888. * @param {Array} arr
  889. * @param {Function} fn
  890. */
  891. each = function (arr, fn) {
  892. var i = 0,
  893. len = arr.length;
  894. for (; i < len; i++) {
  895. if (fn.call(arr[i], arr[i], i, arr) === false) {
  896. return i;
  897. }
  898. }
  899. };
  900. /**
  901. * Filter an array
  902. */
  903. grep = jQ.grep;
  904. /**
  905. * Map an array
  906. * @param {Array} arr
  907. * @param {Function} fn
  908. */
  909. map = function (arr, fn) {
  910. //return jQuery.map(arr, fn);
  911. var results = [],
  912. i = 0,
  913. len = arr.length;
  914. for (; i < len; i++) {
  915. results[i] = fn.call(arr[i], arr[i], i, arr);
  916. }
  917. return results;
  918. };
  919. /**
  920. * Deep merge two objects and return a third object
  921. */
  922. merge = function () {
  923. var args = arguments;
  924. return jQ.extend(true, null, args[0], args[1], args[2], args[3]);
  925. };
  926. /**
  927. * Get the position of an element relative to the top left of the page
  928. */
  929. offset = function (el) {
  930. return jQ(el).offset();
  931. };
  932. /**
  933. * Add an event listener
  934. * @param {Object} el A HTML element or custom object
  935. * @param {String} event The event type
  936. * @param {Function} fn The event handler
  937. */
  938. addEvent = function (el, event, fn) {
  939. jQ(el).bind(event, fn);
  940. };
  941. /**
  942. * Remove event added with addEvent
  943. * @param {Object} el The object
  944. * @param {String} eventType The event type. Leave blank to remove all events.
  945. * @param {Function} handler The function to remove
  946. */
  947. removeEvent = function (el, eventType, handler) {
  948. // workaround for jQuery issue with unbinding custom events:
  949. // http://forum.jquery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jquery-1-4-2
  950. var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent';
  951. if (doc[func] && !el[func]) {
  952. el[func] = function () {};
  953. }
  954. jQ(el).unbind(eventType, handler);
  955. };
  956. /**
  957. * Fire an event on a custom object
  958. * @param {Object} el
  959. * @param {String} type
  960. * @param {Object} eventArguments
  961. * @param {Function} defaultFunction
  962. */
  963. fireEvent = function (el, type, eventArguments, defaultFunction) {
  964. var event = jQ.Event(type),
  965. detachedType = 'detached' + type,
  966. defaultPrevented;
  967. // Remove warnings in Chrome when accessing layerX and layerY. Although Highcharts
  968. // never uses these properties, Chrome includes them in the default click event and
  969. // raises the warning when they are copied over in the extend statement below.
  970. //
  971. // To avoid problems in IE (see #1010) where we cannot delete the properties and avoid
  972. // testing if they are there (warning in chrome) the only option is to test if running IE.
  973. if (!isIE && eventArguments) {
  974. delete eventArguments.layerX;
  975. delete eventArguments.layerY;
  976. }
  977. extend(event, eventArguments);
  978. // Prevent jQuery from triggering the object method that is named the
  979. // same as the event. For example, if the event is 'select', jQuery
  980. // attempts calling el.select and it goes into a loop.
  981. if (el[type]) {
  982. el[detachedType] = el[type];
  983. el[type] = null;
  984. }
  985. // Wrap preventDefault and stopPropagation in try/catch blocks in
  986. // order to prevent JS errors when cancelling events on non-DOM
  987. // objects. #615.
  988. each(['preventDefault', 'stopPropagation'], function (fn) {
  989. var base = event[fn];
  990. event[fn] = function () {
  991. try {
  992. base.call(event);
  993. } catch (e) {
  994. if (fn === 'preventDefault') {
  995. defaultPrevented = true;
  996. }
  997. }
  998. };
  999. });
  1000. // trigger it
  1001. jQ(el).trigger(event);
  1002. // attach the method
  1003. if (el[detachedType]) {
  1004. el[type] = el[detachedType];
  1005. el[detachedType] = null;
  1006. }
  1007. if (defaultFunction && !event.isDefaultPrevented() && !defaultPrevented) {
  1008. defaultFunction(event);
  1009. }
  1010. };
  1011. /**
  1012. * Extension method needed for MooTools
  1013. */
  1014. washMouseEvent = function (e) {
  1015. return e;
  1016. };
  1017. /**
  1018. * Animate a HTML element or SVG element wrapper
  1019. * @param {Object} el
  1020. * @param {Object} params
  1021. * @param {Object} options jQuery-like animation options: duration, easing, callback
  1022. */
  1023. animate = function (el, params, options) {
  1024. var $el = jQ(el);
  1025. if (params.d) {
  1026. el.toD = params.d; // keep the array form for paths, used in jQ.fx.step.d
  1027. params.d = 1; // because in jQuery, animating to an array has a different meaning
  1028. }
  1029. $el.stop();
  1030. $el.animate(params, options);
  1031. };
  1032. /**
  1033. * Stop running animation
  1034. */
  1035. stop = function (el) {
  1036. jQ(el).stop();
  1037. };
  1038. //=== Extend jQuery on init
  1039. /*jslint unparam: true*//* allow unused param x in this function */
  1040. jQ.extend(jQ.easing, {
  1041. easeOutQuad: function (x, t, b, c, d) {
  1042. return -c * (t /= d) * (t - 2) + b;
  1043. }
  1044. });
  1045. /*jslint unparam: false*/
  1046. // extend the animate function to allow SVG animations
  1047. var jFx = jQ.fx,
  1048. jStep = jFx.step;
  1049. // extend some methods to check for elem.attr, which means it is a Highcharts SVG object
  1050. each(['cur', '_default', 'width', 'height'], function (fn, i) {
  1051. var obj = jStep,
  1052. base,
  1053. elem;
  1054. // Handle different parent objects
  1055. if (fn === 'cur') {
  1056. obj = jFx.prototype; // 'cur', the getter, relates to jFx.prototype
  1057. } else if (fn === '_default' && jQ.Tween) { // jQuery 1.8 model
  1058. obj = jQ.Tween.propHooks[fn];
  1059. fn = 'set';
  1060. }
  1061. // Overwrite the method
  1062. base = obj[fn];
  1063. if (base) { // step.width and step.height don't exist in jQuery < 1.7
  1064. // create the extended function replacement
  1065. obj[fn] = function (fx) {
  1066. // jFx.prototype.cur does not use fx argument
  1067. fx = i ? fx : this;
  1068. // shortcut
  1069. elem = fx.elem;
  1070. // jFX.prototype.cur returns the current value. The other ones are setters
  1071. // and returning a value has no effect.
  1072. return elem.attr ? // is SVG element wrapper
  1073. elem.attr(fx.prop, fn === 'cur' ? UNDEFINED : fx.now) : // apply the SVG wrapper's method
  1074. base.apply(this, arguments); // use jQuery's built-in method
  1075. };
  1076. }
  1077. });
  1078. // animate paths
  1079. jStep.d = function (fx) {
  1080. var elem = fx.elem;
  1081. // Normally start and end should be set in state == 0, but sometimes,
  1082. // for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped
  1083. // in these cases
  1084. if (!fx.started) {
  1085. var ends = pathAnim.init(elem, elem.d, elem.toD);
  1086. fx.start = ends[0];
  1087. fx.end = ends[1];
  1088. fx.started = true;
  1089. }
  1090. // interpolate each value of the path
  1091. elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD));
  1092. };
  1093. }
  1094. /* ****************************************************************************
  1095. * Handle the options *
  1096. *****************************************************************************/
  1097. var
  1098. defaultLabelOptions = {
  1099. enabled: true,
  1100. // rotation: 0,
  1101. align: 'center',
  1102. x: 0,
  1103. y: 15,
  1104. /*formatter: function () {
  1105. return this.value;
  1106. },*/
  1107. style: {
  1108. color: '#666',
  1109. fontSize: '11px',
  1110. lineHeight: '14px'
  1111. }
  1112. };
  1113. defaultOptions = {
  1114. colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE',
  1115. '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'],
  1116. symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
  1117. lang: {
  1118. loading: 'Loading...',
  1119. months: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
  1120. 'August', 'September', 'October', 'November', 'December'],
  1121. shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
  1122. weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
  1123. decimalPoint: '.',
  1124. resetZoom: 'Reset zoom',
  1125. resetZoomTitle: 'Reset zoom level 1:1',
  1126. thousandsSep: ','
  1127. },
  1128. global: {
  1129. useUTC: true,
  1130. canvasToolsURL: 'http://code.highcharts.com/2.2.5/modules/canvas-tools.js'
  1131. },
  1132. chart: {
  1133. //animation: true,
  1134. //alignTicks: false,
  1135. //reflow: true,
  1136. //className: null,
  1137. //events: { load, selection },
  1138. //margin: [null],
  1139. //marginTop: null,
  1140. //marginRight: null,
  1141. //marginBottom: null,
  1142. //marginLeft: null,
  1143. borderColor: '#4572A7',
  1144. //borderWidth: 0,
  1145. borderRadius: 5,
  1146. defaultSeriesType: 'line',
  1147. ignoreHiddenSeries: true,
  1148. //inverted: false,
  1149. //shadow: false,
  1150. spacingTop: 10,
  1151. spacingRight: 10,
  1152. spacingBottom: 15,
  1153. spacingLeft: 10,
  1154. style: {
  1155. fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font
  1156. fontSize: '12px'
  1157. },
  1158. backgroundColor: '#FFFFFF',
  1159. //plotBackgroundColor: null,
  1160. plotBorderColor: '#C0C0C0',
  1161. //plotBorderWidth: 0,
  1162. //plotShadow: false,
  1163. //zoomType: ''
  1164. resetZoomButton: {
  1165. theme: {
  1166. zIndex: 20
  1167. },
  1168. position: {
  1169. align: 'right',
  1170. x: -10,
  1171. //verticalAlign: 'top',
  1172. y: 10
  1173. }
  1174. // relativeTo: 'plot'
  1175. }
  1176. },
  1177. title: {
  1178. text: 'Chart title',
  1179. align: 'center',
  1180. // floating: false,
  1181. // margin: 15,
  1182. // x: 0,
  1183. // verticalAlign: 'top',
  1184. y: 15,
  1185. style: {
  1186. color: '#3E576F',
  1187. fontSize: '16px'
  1188. }
  1189. },
  1190. subtitle: {
  1191. text: '',
  1192. align: 'center',
  1193. // floating: false
  1194. // x: 0,
  1195. // verticalAlign: 'top',
  1196. y: 30,
  1197. style: {
  1198. color: '#6D869F'
  1199. }
  1200. },
  1201. plotOptions: {
  1202. line: { // base series options
  1203. allowPointSelect: false,
  1204. showCheckbox: false,
  1205. animation: {
  1206. duration: 1000
  1207. },
  1208. //connectNulls: false,
  1209. //cursor: 'default',
  1210. //clip: true,
  1211. //dashStyle: null,
  1212. //enableMouseTracking: true,
  1213. events: {},
  1214. //legendIndex: 0,
  1215. lineWidth: 2,
  1216. shadow: true,
  1217. // stacking: null,
  1218. marker: {
  1219. enabled: true,
  1220. //symbol: null,
  1221. lineWidth: 0,
  1222. radius: 4,
  1223. lineColor: '#FFFFFF',
  1224. //fillColor: null,
  1225. states: { // states for a single point
  1226. hover: {
  1227. //radius: base + 2
  1228. },
  1229. select: {
  1230. fillColor: '#FFFFFF',
  1231. lineColor: '#000000',
  1232. lineWidth: 2
  1233. }
  1234. }
  1235. },
  1236. point: {
  1237. events: {}
  1238. },
  1239. dataLabels: merge(defaultLabelOptions, {
  1240. enabled: false,
  1241. y: -6,
  1242. formatter: function () {
  1243. return this.y;
  1244. }
  1245. // backgroundColor: undefined,
  1246. // borderColor: undefined,
  1247. // borderRadius: undefined,
  1248. // borderWidth: undefined,
  1249. // padding: 3,
  1250. // shadow: false
  1251. }),
  1252. cropThreshold: 300, // draw points outside the plot area when the number of points is less than this
  1253. pointRange: 0,
  1254. //pointStart: 0,
  1255. //pointInterval: 1,
  1256. showInLegend: true,
  1257. states: { // states for the entire series
  1258. hover: {
  1259. //enabled: false,
  1260. //lineWidth: base + 1,
  1261. marker: {
  1262. // lineWidth: base + 1,
  1263. // radius: base + 1
  1264. }
  1265. },
  1266. select: {
  1267. marker: {}
  1268. }
  1269. },
  1270. stickyTracking: true
  1271. //tooltip: {
  1272. //pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b>'
  1273. //valueDecimals: null,
  1274. //xDateFormat: '%A, %b %e, %Y',
  1275. //valuePrefix: '',
  1276. //ySuffix: ''
  1277. //}
  1278. // turboThreshold: 1000
  1279. // zIndex: null
  1280. }
  1281. },
  1282. labels: {
  1283. //items: [],
  1284. style: {
  1285. //font: defaultFont,
  1286. position: ABSOLUTE,
  1287. color: '#3E576F'
  1288. }
  1289. },
  1290. legend: {
  1291. enabled: true,
  1292. align: 'center',
  1293. //floating: false,
  1294. layout: 'horizontal',
  1295. labelFormatter: function () {
  1296. return this.name;
  1297. },
  1298. borderWidth: 1,
  1299. borderColor: '#909090',
  1300. borderRadius: 5,
  1301. navigation: {
  1302. // animation: true,
  1303. activeColor: '#3E576F',
  1304. // arrowSize: 12
  1305. inactiveColor: '#CCC'
  1306. // style: {} // text styles
  1307. },
  1308. // margin: 10,
  1309. // reversed: false,
  1310. shadow: false,
  1311. // backgroundColor: null,
  1312. /*style: {
  1313. padding: '5px'
  1314. },*/
  1315. itemStyle: {
  1316. cursor: 'pointer',
  1317. color: '#3E576F',
  1318. fontSize: '12px'
  1319. },
  1320. itemHoverStyle: {
  1321. //cursor: 'pointer', removed as of #601
  1322. color: '#000'
  1323. },
  1324. itemHiddenStyle: {
  1325. color: '#CCC'
  1326. },
  1327. itemCheckboxStyle: {
  1328. position: ABSOLUTE,
  1329. width: '13px', // for IE precision
  1330. height: '13px'
  1331. },
  1332. // itemWidth: undefined,
  1333. symbolWidth: 16,
  1334. symbolPadding: 5,
  1335. verticalAlign: 'bottom',
  1336. // width: undefined,
  1337. x: 0,
  1338. y: 0
  1339. },
  1340. loading: {
  1341. // hideDuration: 100,
  1342. labelStyle: {
  1343. fontWeight: 'bold',
  1344. position: RELATIVE,
  1345. top: '1em'
  1346. },
  1347. // showDuration: 0,
  1348. style: {
  1349. position: ABSOLUTE,
  1350. backgroundColor: 'white',
  1351. opacity: 0.5,
  1352. textAlign: 'center'
  1353. }
  1354. },
  1355. tooltip: {
  1356. enabled: true,
  1357. //crosshairs: null,
  1358. backgroundColor: 'rgba(255, 255, 255, .85)',
  1359. borderWidth: 2,
  1360. borderRadius: 5,
  1361. dateTimeLabelFormats: {
  1362. millisecond: '%A, %b %e, %H:%M:%S.%L',
  1363. second: '%A, %b %e, %H:%M:%S',
  1364. minute: '%A, %b %e, %H:%M',
  1365. hour: '%A, %b %e, %H:%M',
  1366. day: '%A, %b %e, %Y',
  1367. week: 'Week from %A, %b %e, %Y',
  1368. month: '%B %Y',
  1369. year: '%Y'
  1370. },
  1371. //formatter: defaultFormatter,
  1372. headerFormat: '<span style="font-size: 10px">{point.key}</span><br/>',
  1373. pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b><br/>',
  1374. shadow: true,
  1375. shared: useCanVG,
  1376. snap: hasTouch ? 25 : 10,
  1377. style: {
  1378. color: '#333333',
  1379. fontSize: '12px',
  1380. padding: '5px',
  1381. whiteSpace: 'nowrap'
  1382. }
  1383. //xDateFormat: '%A, %b %e, %Y',
  1384. //valueDecimals: null,
  1385. //valuePrefix: '',
  1386. //valueSuffix: ''
  1387. },
  1388. credits: {
  1389. enabled: true,
  1390. text: '',
  1391. href: '',
  1392. position: {
  1393. align: 'right',
  1394. x: -10,
  1395. verticalAlign: 'bottom',
  1396. y: -5
  1397. },
  1398. style: {
  1399. cursor: 'pointer',
  1400. color: '#909090',
  1401. fontSize: '10px'
  1402. }
  1403. }
  1404. };
  1405. // Series defaults
  1406. var defaultPlotOptions = defaultOptions.plotOptions,
  1407. defaultSeriesOptions = defaultPlotOptions.line;
  1408. // set the default time methods
  1409. setTimeMethods();
  1410. /**
  1411. * Set the time methods globally based on the useUTC option. Time method can be either
  1412. * local time or UTC (default).
  1413. */
  1414. function setTimeMethods() {
  1415. var useUTC = defaultOptions.global.useUTC,
  1416. GET = useUTC ? 'getUTC' : 'get',
  1417. SET = useUTC ? 'setUTC' : 'set';
  1418. makeTime = useUTC ? Date.UTC : function (year, month, date, hours, minutes, seconds) {
  1419. return new Date(
  1420. year,
  1421. month,
  1422. pick(date, 1),
  1423. pick(hours, 0),
  1424. pick(minutes, 0),
  1425. pick(seconds, 0)
  1426. ).getTime();
  1427. };
  1428. getMinutes = GET + 'Minutes';
  1429. getHours = GET + 'Hours';
  1430. getDay = GET + 'Day';
  1431. getDate = GET + 'Date';
  1432. getMonth = GET + 'Month';
  1433. getFullYear = GET + 'FullYear';
  1434. setMinutes = SET + 'Minutes';
  1435. setHours = SET + 'Hours';
  1436. setDate = SET + 'Date';
  1437. setMonth = SET + 'Month';
  1438. setFullYear = SET + 'FullYear';
  1439. }
  1440. /**
  1441. * Merge the default options with custom options and return the new options structure
  1442. * @param {Object} options The new custom options
  1443. */
  1444. function setOptions(options) {
  1445. // Pull out axis options and apply them to the respective default axis options
  1446. /*defaultXAxisOptions = merge(defaultXAxisOptions, options.xAxis);
  1447. defaultYAxisOptions = merge(defaultYAxisOptions, options.yAxis);
  1448. options.xAxis = options.yAxis = UNDEFINED;*/
  1449. // Merge in the default options
  1450. defaultOptions = merge(defaultOptions, options);
  1451. // Apply UTC
  1452. setTimeMethods();
  1453. return defaultOptions;
  1454. }
  1455. /**
  1456. * Get the updated default options. Merely exposing defaultOptions for outside modules
  1457. * isn't enough because the setOptions method creates a new object.
  1458. */
  1459. function getOptions() {
  1460. return defaultOptions;
  1461. }
  1462. /**
  1463. * Handle color operations. The object methods are chainable.
  1464. * @param {String} input The input color in either rbga or hex format
  1465. */
  1466. var Color = function (input) {
  1467. // declare variables
  1468. var rgba = [], result;
  1469. /**
  1470. * Parse the input color to rgba array
  1471. * @param {String} input
  1472. */
  1473. function init(input) {
  1474. // rgba
  1475. 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);
  1476. if (result) {
  1477. rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
  1478. } else { // hex
  1479. result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input);
  1480. if (result) {
  1481. rgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1];
  1482. }
  1483. }
  1484. }
  1485. /**
  1486. * Return the color a specified format
  1487. * @param {String} format
  1488. */
  1489. function get(format) {
  1490. var ret;
  1491. // it's NaN if gradient colors on a column chart
  1492. if (rgba && !isNaN(rgba[0])) {
  1493. if (format === 'rgb') {
  1494. ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')';
  1495. } else if (format === 'a') {
  1496. ret = rgba[3];
  1497. } else {
  1498. ret = 'rgba(' + rgba.join(',') + ')';
  1499. }
  1500. } else {
  1501. ret = input;
  1502. }
  1503. return ret;
  1504. }
  1505. /**
  1506. * Brighten the color
  1507. * @param {Number} alpha
  1508. */
  1509. function brighten(alpha) {
  1510. if (isNumber(alpha) && alpha !== 0) {
  1511. var i;
  1512. for (i = 0; i < 3; i++) {
  1513. rgba[i] += pInt(alpha * 255);
  1514. if (rgba[i] < 0) {
  1515. rgba[i] = 0;
  1516. }
  1517. if (rgba[i] > 255) {
  1518. rgba[i] = 255;
  1519. }
  1520. }
  1521. }
  1522. return this;
  1523. }
  1524. /**
  1525. * Set the color's opacity to a given alpha value
  1526. * @param {Number} alpha
  1527. */
  1528. function setOpacity(alpha) {
  1529. rgba[3] = alpha;
  1530. return this;
  1531. }
  1532. // initialize: parse the input
  1533. init(input);
  1534. // public methods
  1535. return {
  1536. get: get,
  1537. brighten: brighten,
  1538. setOpacity: setOpacity
  1539. };
  1540. };
  1541. /**
  1542. * A wrapper object for SVG elements
  1543. */
  1544. function SVGElement() {}
  1545. SVGElement.prototype = {
  1546. /**
  1547. * Initialize the SVG renderer
  1548. * @param {Object} renderer
  1549. * @param {String} nodeName
  1550. */
  1551. init: function (renderer, nodeName) {
  1552. var wrapper = this;
  1553. wrapper.element = nodeName === 'span' ?
  1554. createElement(nodeName) :
  1555. doc.createElementNS(SVG_NS, nodeName);
  1556. wrapper.renderer = renderer;
  1557. /**
  1558. * A collection of attribute setters. These methods, if defined, are called right before a certain
  1559. * attribute is set on an element wrapper. Returning false prevents the default attribute
  1560. * setter to run. Returning a value causes the default setter to set that value. Used in
  1561. * Renderer.label.
  1562. */
  1563. wrapper.attrSetters = {};
  1564. },
  1565. /**
  1566. * Animate a given attribute
  1567. * @param {Object} params
  1568. * @param {Number} options The same options as in jQuery animation
  1569. * @param {Function} complete Function to perform at the end of animation
  1570. */
  1571. animate: function (params, options, complete) {
  1572. var animOptions = pick(options, globalAnimation, true);
  1573. stop(this); // stop regardless of animation actually running, or reverting to .attr (#607)
  1574. if (animOptions) {
  1575. animOptions = merge(animOptions);
  1576. if (complete) { // allows using a callback with the global animation without overwriting it
  1577. animOptions.complete = complete;
  1578. }
  1579. animate(this, params, animOptions);
  1580. } else {
  1581. this.attr(params);
  1582. if (complete) {
  1583. complete();
  1584. }
  1585. }
  1586. },
  1587. /**
  1588. * Set or get a given attribute
  1589. * @param {Object|String} hash
  1590. * @param {Mixed|Undefined} val
  1591. */
  1592. attr: function (hash, val) {
  1593. var wrapper = this,
  1594. key,
  1595. value,
  1596. result,
  1597. i,
  1598. child,
  1599. element = wrapper.element,
  1600. nodeName = element.nodeName,
  1601. renderer = wrapper.renderer,
  1602. skipAttr,
  1603. titleNode,
  1604. attrSetters = wrapper.attrSetters,
  1605. shadows = wrapper.shadows,
  1606. hasSetSymbolSize,
  1607. doTransform,
  1608. ret = wrapper;
  1609. // single key-value pair
  1610. if (isString(hash) && defined(val)) {
  1611. key = hash;
  1612. hash = {};
  1613. hash[key] = val;
  1614. }
  1615. // used as a getter: first argument is a string, second is undefined
  1616. if (isString(hash)) {
  1617. key = hash;
  1618. if (nodeName === 'circle') {
  1619. key = { x: 'cx', y: 'cy' }[key] || key;
  1620. } else if (key === 'strokeWidth') {
  1621. key = 'stroke-width';
  1622. }
  1623. ret = attr(element, key) || wrapper[key] || 0;
  1624. if (key !== 'd' && key !== 'visibility') { // 'd' is string in animation step
  1625. ret = parseFloat(ret);
  1626. }
  1627. // setter
  1628. } else {
  1629. for (key in hash) {
  1630. skipAttr = false; // reset
  1631. value = hash[key];
  1632. // check for a specific attribute setter
  1633. result = attrSetters[key] && attrSetters[key](value, key);
  1634. if (result !== false) {
  1635. if (result !== UNDEFINED) {
  1636. value = result; // the attribute setter has returned a new value to set
  1637. }
  1638. // paths
  1639. if (key === 'd') {
  1640. if (value && value.join) { // join path
  1641. value = value.join(' ');
  1642. }
  1643. if (/(NaN| {2}|^$)/.test(value)) {
  1644. value = 'M 0 0';
  1645. }
  1646. //wrapper.d = value; // shortcut for animations
  1647. // update child tspans x values
  1648. } else if (key === 'x' && nodeName === 'text') {
  1649. for (i = 0; i < element.childNodes.length; i++) {
  1650. child = element.childNodes[i];
  1651. // if the x values are equal, the tspan represents a linebreak
  1652. if (attr(child, 'x') === attr(element, 'x')) {
  1653. //child.setAttribute('x', value);
  1654. attr(child, 'x', value);
  1655. }
  1656. }
  1657. if (wrapper.rotation) {
  1658. attr(element, 'transform', 'rotate(' + wrapper.rotation + ' ' + value + ' ' +
  1659. pInt(hash.y || attr(element, 'y')) + ')');
  1660. }
  1661. // apply gradients
  1662. } else if (key === 'fill') {
  1663. value = renderer.color(value, element, key);
  1664. // circle x and y
  1665. } else if (nodeName === 'circle' && (key === 'x' || key === 'y')) {
  1666. key = { x: 'cx', y: 'cy' }[key] || key;
  1667. // rectangle border radius
  1668. } else if (nodeName === 'rect' && key === 'r') {
  1669. attr(element, {
  1670. rx: value,
  1671. ry: value
  1672. });
  1673. skipAttr = true;
  1674. // translation and text rotation
  1675. } else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || key === 'verticalAlign') {
  1676. doTransform = true;
  1677. skipAttr = true;
  1678. // apply opacity as subnode (required by legacy WebKit and Batik)
  1679. } else if (key === 'stroke') {
  1680. value = renderer.color(value, element, key);
  1681. // emulate VML's dashstyle implementation
  1682. } else if (key === 'dashstyle') {
  1683. key = 'stroke-dasharray';
  1684. value = value && value.toLowerCase();
  1685. if (value === 'solid') {
  1686. value = NONE;
  1687. } else if (value) {
  1688. value = value
  1689. .replace('shortdashdotdot', '3,1,1,1,1,1,')
  1690. .replace('shortdashdot', '3,1,1,1')
  1691. .replace('shortdot', '1,1,')
  1692. .replace('shortdash', '3,1,')
  1693. .replace('longdash', '8,3,')
  1694. .replace(/dot/g, '1,3,')
  1695. .replace('dash', '4,3,')
  1696. .replace(/,$/, '')
  1697. .split(','); // ending comma
  1698. i = value.length;
  1699. while (i--) {
  1700. value[i] = pInt(value[i]) * hash['stroke-width'];
  1701. }
  1702. value = value.join(',');
  1703. }
  1704. // special
  1705. } else if (key === 'isTracker') {
  1706. wrapper[key] = value;
  1707. // IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2
  1708. // is unable to cast them. Test again with final IE9.
  1709. } else if (key === 'width') {
  1710. value = pInt(value);
  1711. // Text alignment
  1712. } else if (key === 'align') {
  1713. key = 'text-anchor';
  1714. value = { left: 'start', center: 'middle', right: 'end' }[value];
  1715. // Title requires a subnode, #431
  1716. } else if (key === 'title') {
  1717. titleNode = element.getElementsByTagName('title')[0];
  1718. if (!titleNode) {
  1719. titleNode = doc.createElementNS(SVG_NS, 'title');
  1720. element.appendChild(titleNode);
  1721. }
  1722. titleNode.textContent = value;
  1723. }
  1724. // jQuery animate changes case
  1725. if (key === 'strokeWidth') {
  1726. key = 'stroke-width';
  1727. }
  1728. // Chrome/Win < 6 bug (http://code.google.com/p/chromium/issues/detail?id=15461)
  1729. if (isWebKit && key === 'stroke-width' && value === 0) {
  1730. value = 0.000001;
  1731. }
  1732. // symbols
  1733. if (wrapper.symbolName && /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(key)) {
  1734. if (!hasSetSymbolSize) {
  1735. wrapper.symbolAttr(hash);
  1736. hasSetSymbolSize = true;
  1737. }
  1738. skipAttr = true;
  1739. }
  1740. // let the shadow follow the main element
  1741. if (shadows && /^(width|height|visibility|x|y|d|transform)$/.test(key)) {
  1742. i = shadows.length;
  1743. while (i--) {
  1744. attr(
  1745. shadows[i],
  1746. key,
  1747. key === 'height' ?
  1748. mathMax(value - (shadows[i].cutHeight || 0), 0) :
  1749. value
  1750. );
  1751. }
  1752. }
  1753. // validate heights
  1754. if ((key === 'width' || key === 'height') && nodeName === 'rect' && value < 0) {
  1755. value = 0;
  1756. }
  1757. // Record for animation and quick access without polling the DOM
  1758. wrapper[key] = value;
  1759. // Update transform
  1760. if (doTransform) {
  1761. wrapper.updateTransform();
  1762. }
  1763. if (key === 'text') {
  1764. // only one node allowed
  1765. wrapper.textStr = value;
  1766. if (wrapper.added) {
  1767. renderer.buildText(wrapper);
  1768. }
  1769. } else if (!skipAttr) {
  1770. attr(element, key, value);
  1771. }
  1772. }
  1773. }
  1774. }
  1775. // Workaround for our #732, WebKit's issue https://bugs.webkit.org/show_bug.cgi?id=78385
  1776. // TODO: If the WebKit team fix this bug before the final release of Chrome 18, remove the workaround.
  1777. if (isWebKit && /Chrome\/(18|19)/.test(userAgent)) {
  1778. if (nodeName === 'text' && (hash.x !== UNDEFINED || hash.y !== UNDEFINED)) {
  1779. var parent = element.parentNode,
  1780. next = element.nextSibling;
  1781. if (parent) {
  1782. parent.removeChild(element);
  1783. if (next) {
  1784. parent.insertBefore(element, next);
  1785. } else {
  1786. parent.appendChild(element);
  1787. }
  1788. }
  1789. }
  1790. }
  1791. // End of workaround for #732
  1792. return ret;
  1793. },
  1794. /**
  1795. * If one of the symbol size affecting parameters are changed,
  1796. * check all the others only once for each call to an element's
  1797. * .attr() method
  1798. * @param {Object} hash
  1799. */
  1800. symbolAttr: function (hash) {
  1801. var wrapper = this;
  1802. each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR', 'anchorX', 'anchorY'], function (key) {
  1803. wrapper[key] = pick(hash[key], wrapper[key]);
  1804. });
  1805. wrapper.attr({
  1806. d: wrapper.renderer.symbols[wrapper.symbolName](wrapper.x, wrapper.y, wrapper.width, wrapper.height, wrapper)
  1807. });
  1808. },
  1809. /**
  1810. * Apply a clipping path to this object
  1811. * @param {String} id
  1812. */
  1813. clip: function (clipRect) {
  1814. return this.attr('clip-path', 'url(' + this.renderer.url + '#' + clipRect.id + ')');
  1815. },
  1816. /**
  1817. * Calculate the coordinates needed for drawing a rectangle crisply and return the
  1818. * calculated attributes
  1819. * @param {Number} strokeWidth
  1820. * @param {Number} x
  1821. * @param {Number} y
  1822. * @param {Number} width
  1823. * @param {Number} height
  1824. */
  1825. crisp: function (strokeWidth, x, y, width, height) {
  1826. var wrapper = this,
  1827. key,
  1828. attribs = {},
  1829. values = {},
  1830. normalizer;
  1831. strokeWidth = strokeWidth || wrapper.strokeWidth || (wrapper.attr && wrapper.attr('stroke-width')) || 0;
  1832. normalizer = mathRound(strokeWidth) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors
  1833. // normalize for crisp edges
  1834. values.x = mathFloor(x || wrapper.x || 0) + normalizer;
  1835. values.y = mathFloor(y || wrapper.y || 0) + normalizer;
  1836. values.width = mathFloor((width || wrapper.width || 0) - 2 * normalizer);
  1837. values.height = mathFloor((height || wrapper.height || 0) - 2 * normalizer);
  1838. values.strokeWidth = strokeWidth;
  1839. for (key in values) {
  1840. if (wrapper[key] !== values[key]) { // only set attribute if changed
  1841. wrapper[key] = attribs[key] = values[key];
  1842. }
  1843. }
  1844. return attribs;
  1845. },
  1846. /**
  1847. * Set styles for the element
  1848. * @param {Object} styles
  1849. */
  1850. css: function (styles) {
  1851. /*jslint unparam: true*//* allow unused param a in the regexp function below */
  1852. var elemWrapper = this,
  1853. elem = elemWrapper.element,
  1854. textWidth = styles && styles.width && elem.nodeName === 'text',
  1855. n,
  1856. serializedCss = '',
  1857. hyphenate = function (a, b) { return '-' + b.toLowerCase(); };
  1858. /*jslint unparam: false*/
  1859. // convert legacy
  1860. if (styles && styles.color) {
  1861. styles.fill = styles.color;
  1862. }
  1863. // Merge the new styles with the old ones
  1864. styles = extend(
  1865. elemWrapper.styles,
  1866. styles
  1867. );
  1868. // store object
  1869. elemWrapper.styles = styles;
  1870. // serialize and set style attribute
  1871. if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute
  1872. if (textWidth) {
  1873. delete styles.width;
  1874. }
  1875. css(elemWrapper.element, styles);
  1876. } else {
  1877. for (n in styles) {
  1878. serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';';
  1879. }
  1880. elemWrapper.attr({
  1881. style: serializedCss
  1882. });
  1883. }
  1884. // re-build text
  1885. if (textWidth && elemWrapper.added) {
  1886. elemWrapper.renderer.buildText(elemWrapper);
  1887. }
  1888. return elemWrapper;
  1889. },
  1890. /**
  1891. * Add an event listener
  1892. * @param {String} eventType
  1893. * @param {Function} handler
  1894. */
  1895. on: function (eventType, handler) {
  1896. var fn = handler;
  1897. // touch
  1898. if (hasTouch && eventType === 'click') {
  1899. eventType = 'touchstart';
  1900. fn = function (e) {
  1901. e.preventDefault();
  1902. handler();
  1903. };
  1904. }
  1905. // simplest possible event model for internal use
  1906. this.element['on' + eventType] = fn;
  1907. return this;
  1908. },
  1909. /**
  1910. * Set the coordinates needed to draw a consistent radial gradient across
  1911. * pie slices regardless of positioning inside the chart. The format is
  1912. * [centerX, centerY, diameter] in pixels.
  1913. */
  1914. setRadialReference: function (coordinates) {
  1915. this.element.radialReference = coordinates;
  1916. return this;
  1917. },
  1918. /**
  1919. * Move an object and its children by x and y values
  1920. * @param {Number} x
  1921. * @param {Number} y
  1922. */
  1923. translate: function (x, y) {
  1924. return this.attr({
  1925. translateX: x,
  1926. translateY: y
  1927. });
  1928. },
  1929. /**
  1930. * Invert a group, rotate and flip
  1931. */
  1932. invert: function () {
  1933. var wrapper = this;
  1934. wrapper.inverted = true;
  1935. wrapper.updateTransform();
  1936. return wrapper;
  1937. },
  1938. /**
  1939. * Apply CSS to HTML elements. This is used in text within SVG rendering and
  1940. * by the VML renderer
  1941. */
  1942. htmlCss: function (styles) {
  1943. var wrapper = this,
  1944. element = wrapper.element,
  1945. textWidth = styles && element.tagName === 'SPAN' && styles.width;
  1946. if (textWidth) {
  1947. delete styles.width;
  1948. wrapper.textWidth = textWidth;
  1949. wrapper.updateTransform();
  1950. }
  1951. wrapper.styles = extend(wrapper.styles, styles);
  1952. css(wrapper.element, styles);
  1953. return wrapper;
  1954. },
  1955. /**
  1956. * VML and useHTML method for calculating the bounding box based on offsets
  1957. * @param {Boolean} refresh Whether to force a fresh value from the DOM or to
  1958. * use the cached value
  1959. *
  1960. * @return {Object} A hash containing values for x, y, width and height
  1961. */
  1962. htmlGetBBox: function (refresh) {
  1963. var wrapper = this,
  1964. element = wrapper.element,
  1965. bBox = wrapper.bBox;
  1966. // faking getBBox in exported SVG in legacy IE
  1967. if (!bBox || refresh) {
  1968. // faking getBBox in exported SVG in legacy IE
  1969. if (element.nodeName === 'text') {
  1970. element.style.position = ABSOLUTE;
  1971. }
  1972. bBox = wrapper.bBox = {
  1973. x: element.offsetLeft,
  1974. y: element.offsetTop,
  1975. width: element.offsetWidth,
  1976. height: element.offsetHeight
  1977. };
  1978. }
  1979. return bBox;
  1980. },
  1981. /**
  1982. * VML override private method to update elements based on internal
  1983. * properties based on SVG transform
  1984. */
  1985. htmlUpdateTransform: function () {
  1986. // aligning non added elements is expensive
  1987. if (!this.added) {
  1988. this.alignOnAdd = true;
  1989. return;
  1990. }
  1991. var wrapper = this,
  1992. renderer = wrapper.renderer,
  1993. elem = wrapper.element,
  1994. translateX = wrapper.translateX || 0,
  1995. translateY = wrapper.translateY || 0,
  1996. x = wrapper.x || 0,
  1997. y = wrapper.y || 0,
  1998. align = wrapper.textAlign || 'left',
  1999. alignCorrection = { left: 0, center: 0.5, right: 1 }[align],
  2000. nonLeft = align && align !== 'left',
  2001. shadows = wrapper.shadows;
  2002. // apply translate
  2003. if (translateX || translateY) {
  2004. css(elem, {
  2005. marginLeft: translateX,
  2006. marginTop: translateY
  2007. });
  2008. if (shadows) { // used in labels/tooltip
  2009. each(shadows, function (shadow) {
  2010. css(shadow, {
  2011. marginLeft: translateX + 1,
  2012. marginTop: translateY + 1
  2013. });
  2014. });
  2015. }
  2016. }
  2017. // apply inversion
  2018. if (wrapper.inverted) { // wrapper is a group
  2019. each(elem.childNodes, function (child) {
  2020. renderer.invertChild(child, elem);
  2021. });
  2022. }
  2023. if (elem.tagName === 'SPAN') {
  2024. var width, height,
  2025. rotation = wrapper.rotation,
  2026. baseline,
  2027. radians = 0,
  2028. costheta = 1,
  2029. sintheta = 0,
  2030. quad,
  2031. textWidth = pInt(wrapper.textWidth),
  2032. xCorr = wrapper.xCorr || 0,
  2033. yCorr = wrapper.yCorr || 0,
  2034. currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(',');
  2035. if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed
  2036. if (defined(rotation)) {
  2037. radians = rotation * deg2rad; // deg to rad
  2038. costheta = mathCos(radians);
  2039. sintheta = mathSin(radians);
  2040. // Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented
  2041. // but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+
  2042. // has support for CSS3 transform. The getBBox method also needs to be updated
  2043. // to compensate for the rotation, like it currently does for SVG.
  2044. // Test case: http://highcharts.com/tests/?file=text-rotation
  2045. css(elem, {
  2046. filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
  2047. ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
  2048. ', sizingMethod=\'auto expand\')'].join('') : NONE
  2049. });
  2050. }
  2051. width = pick(wrapper.elemWidth, elem.offsetWidth);
  2052. height = pick(wrapper.elemHeight, elem.offsetHeight);
  2053. // update textWidth
  2054. if (width > textWidth && /[ \-]/.test(elem.innerText)) { // #983
  2055. css(elem, {
  2056. width: textWidth + PX,
  2057. display: 'block',
  2058. whiteSpace: 'normal'
  2059. });
  2060. width = textWidth;
  2061. }
  2062. // correct x and y
  2063. baseline = renderer.fontMetrics(elem.style.fontSize).b;
  2064. xCorr = costheta < 0 && -width;
  2065. yCorr = sintheta < 0 && -height;
  2066. // correct for baseline and corners spilling out after rotation
  2067. quad = costheta * sintheta < 0;
  2068. xCorr += sintheta * baseline * (quad ? 1 - alignCorrection : alignCorrection);
  2069. yCorr -= costheta * baseline * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1);
  2070. // correct for the length/height of the text
  2071. if (nonLeft) {
  2072. xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1);
  2073. if (rotation) {
  2074. yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1);
  2075. }
  2076. css(elem, {
  2077. textAlign: align
  2078. });
  2079. }
  2080. // record correction
  2081. wrapper.xCorr = xCorr;
  2082. wrapper.yCorr = yCorr;
  2083. }
  2084. // apply position with correction
  2085. css(elem, {
  2086. left: (x + xCorr) + PX,
  2087. top: (y + yCorr) + PX
  2088. });
  2089. // record current text transform
  2090. wrapper.cTT = currentTextTransform;
  2091. }
  2092. },
  2093. /**
  2094. * Private method to update the transform attribute based on internal
  2095. * properties
  2096. */
  2097. updateTransform: function () {
  2098. var wrapper = this,
  2099. translateX = wrapper.translateX || 0,
  2100. translateY = wrapper.translateY || 0,
  2101. inverted = wrapper.inverted,
  2102. rotation = wrapper.rotation,
  2103. transform = [];
  2104. // flipping affects translate as adjustment for flipping around the group's axis
  2105. if (inverted) {
  2106. translateX += wrapper.attr('width');
  2107. translateY += wrapper.attr('height');
  2108. }
  2109. // apply translate
  2110. if (translateX || translateY) {
  2111. transform.push('translate(' + translateX + ',' + translateY + ')');
  2112. }
  2113. // apply rotation
  2114. if (inverted) {
  2115. transform.push('rotate(90) scale(-1,1)');
  2116. } else if (rotation) { // text rotation
  2117. transform.push('rotate(' + rotation + ' ' + (wrapper.x || 0) + ' ' + (wrapper.y || 0) + ')');
  2118. }
  2119. if (transform.length) {
  2120. attr(wrapper.element, 'transform', transform.join(' '));
  2121. }
  2122. },
  2123. /**
  2124. * Bring the element to the front
  2125. */
  2126. toFront: function () {
  2127. var element = this.element;
  2128. element.parentNode.appendChild(element);
  2129. return this;
  2130. },
  2131. /**
  2132. * Break down alignment options like align, verticalAlign, x and y
  2133. * to x and y relative to the chart.
  2134. *
  2135. * @param {Object} alignOptions
  2136. * @param {Boolean} alignByTranslate
  2137. * @param {Object} box The box to align to, needs a width and height
  2138. *
  2139. */
  2140. align: function (alignOptions, alignByTranslate, box) {
  2141. var elemWrapper = this;
  2142. if (!alignOptions) { // called on resize
  2143. alignOptions = elemWrapper.alignOptions;
  2144. alignByTranslate = elemWrapper.alignByTranslate;
  2145. } else { // first call on instanciate
  2146. elemWrapper.alignOptions = alignOptions;
  2147. elemWrapper.alignByTranslate = alignByTranslate;
  2148. if (!box) { // boxes other than renderer handle this internally
  2149. elemWrapper.renderer.alignedObjects.push(elemWrapper);
  2150. }
  2151. }
  2152. box = pick(box, elemWrapper.renderer);
  2153. var align = alignOptions.align,
  2154. vAlign = alignOptions.verticalAlign,
  2155. x = (box.x || 0) + (alignOptions.x || 0), // default: left align
  2156. y = (box.y || 0) + (alignOptions.y || 0), // default: top align
  2157. attribs = {};
  2158. // align
  2159. if (/^(right|center)$/.test(align)) {
  2160. x += (box.width - (alignOptions.width || 0)) /
  2161. { right: 1, center: 2 }[align];
  2162. }
  2163. attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x);
  2164. // vertical align
  2165. if (/^(bottom|middle)$/.test(vAlign)) {
  2166. y += (box.height - (alignOptions.height || 0)) /
  2167. ({ bottom: 1, middle: 2 }[vAlign] || 1);
  2168. }
  2169. attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y);
  2170. // animate only if already placed
  2171. elemWrapper[elemWrapper.placed ? 'animate' : 'attr'](attribs);
  2172. elemWrapper.placed = true;
  2173. elemWrapper.alignAttr = attribs;
  2174. return elemWrapper;
  2175. },
  2176. /**
  2177. * Get the bounding box (width, height, x and y) for the element
  2178. */
  2179. getBBox: function (refresh) {
  2180. var wrapper = this,
  2181. bBox,
  2182. width,
  2183. height,
  2184. rotation = wrapper.rotation,
  2185. element = wrapper.element,
  2186. rad = rotation * deg2rad;
  2187. // SVG elements
  2188. if (element.namespaceURI === SVG_NS || wrapper.renderer.forExport) {
  2189. try { // Fails in Firefox if the container has display: none.
  2190. bBox = element.getBBox ?
  2191. // SVG: use extend because IE9 is not allowed to change width and height in case
  2192. // of rotation (below)
  2193. extend({}, element.getBBox()) :
  2194. // Canvas renderer and legacy IE in export mode
  2195. {
  2196. width: element.offsetWidth,
  2197. height: element.offsetHeight
  2198. };
  2199. } catch (e) {}
  2200. // If the bBox is not set, the try-catch block above failed. The other condition
  2201. // is for Opera that returns a width of -Infinity on hidden elements.
  2202. if (!bBox || bBox.width < 0) {
  2203. bBox = { width: 0, height: 0 };
  2204. }
  2205. width = bBox.width;
  2206. height = bBox.height;
  2207. // adjust for rotated text
  2208. if (rotation) {
  2209. bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));
  2210. bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));
  2211. }
  2212. // VML Renderer or useHTML within SVG
  2213. } else {
  2214. bBox = wrapper.htmlGetBBox(refresh);
  2215. }
  2216. return bBox;
  2217. },
  2218. /**
  2219. * Show the element
  2220. */
  2221. show: function () {
  2222. return this.attr({ visibility: VISIBLE });
  2223. },
  2224. /**
  2225. * Hide the element
  2226. */
  2227. hide: function () {
  2228. return this.attr({ visibility: HIDDEN });
  2229. },
  2230. /**
  2231. * Add the element
  2232. * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined
  2233. * to append the element to the renderer.box.
  2234. */
  2235. add: function (parent) {
  2236. var renderer = this.renderer,
  2237. parentWrapper = parent || renderer,
  2238. parentNode = parentWrapper.element || renderer.box,
  2239. childNodes = parentNode.childNodes,
  2240. element = this.element,
  2241. zIndex = attr(element, 'zIndex'),
  2242. otherElement,
  2243. otherZIndex,
  2244. i,
  2245. inserted;
  2246. // mark as inverted
  2247. this.parentInverted = parent && parent.inverted;
  2248. // build formatted text
  2249. if (this.textStr !== undefined) {
  2250. renderer.buildText(this);
  2251. }
  2252. // mark the container as having z indexed children
  2253. if (zIndex) {
  2254. parentWrapper.handleZ = true;
  2255. zIndex = pInt(zIndex);
  2256. }
  2257. // insert according to this and other elements' zIndex
  2258. if (parentWrapper.handleZ) { // this element or any of its siblings has a z index
  2259. for (i = 0; i < childNodes.length; i++) {
  2260. otherElement = childNodes[i];
  2261. otherZIndex = attr(otherElement, 'zIndex');
  2262. if (otherElement !== element && (
  2263. // insert before the first element with a higher zIndex
  2264. pInt(otherZIndex) > zIndex ||
  2265. // if no zIndex given, insert before the first element with a zIndex
  2266. (!defined(zIndex) && defined(otherZIndex))
  2267. )) {
  2268. parentNode.insertBefore(element, otherElement);
  2269. inserted = true;
  2270. break;
  2271. }
  2272. }
  2273. }
  2274. // default: append at the end
  2275. if (!inserted) {
  2276. parentNode.appendChild(element);
  2277. }
  2278. // mark as added
  2279. this.added = true;
  2280. // fire an event for internal hooks
  2281. fireEvent(this, 'add');
  2282. return this;
  2283. },
  2284. /**
  2285. * Removes a child either by removeChild or move to garbageBin.
  2286. * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
  2287. */
  2288. safeRemoveChild: function (element) {
  2289. var parentNode = element.parentNode;
  2290. if (parentNode) {
  2291. parentNode.removeChild(element);
  2292. }
  2293. },
  2294. /**
  2295. * Destroy the element and element wrapper
  2296. */
  2297. destroy: function () {
  2298. var wrapper = this,
  2299. element = wrapper.element || {},
  2300. shadows = wrapper.shadows,
  2301. box = wrapper.box,
  2302. key,
  2303. i;
  2304. // remove events
  2305. element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = null;
  2306. stop(wrapper); // stop running animations
  2307. if (wrapper.clipPath) {
  2308. wrapper.clipPath = wrapper.clipPath.destroy();
  2309. }
  2310. // Destroy stops in case this is a gradient object
  2311. if (wrapper.stops) {
  2312. for (i = 0; i < wrapper.stops.length; i++) {
  2313. wrapper.stops[i] = wrapper.stops[i].destroy();
  2314. }
  2315. wrapper.stops = null;
  2316. }
  2317. // remove element
  2318. wrapper.safeRemoveChild(element);
  2319. // destroy shadows
  2320. if (shadows) {
  2321. each(shadows, function (shadow) {
  2322. wrapper.safeRemoveChild(shadow);
  2323. });
  2324. }
  2325. // destroy label box
  2326. if (box) {
  2327. box.destroy();
  2328. }
  2329. // remove from alignObjects
  2330. erase(wrapper.renderer.alignedObjects, wrapper);
  2331. for (key in wrapper) {
  2332. delete wrapper[key];
  2333. }
  2334. return null;
  2335. },
  2336. /**
  2337. * Empty a group element
  2338. */
  2339. empty: function () {
  2340. var element = this.element,
  2341. childNodes = element.childNodes,
  2342. i = childNodes.length;
  2343. while (i--) {
  2344. element.removeChild(childNodes[i]);
  2345. }
  2346. },
  2347. /**
  2348. * Add a shadow to the element. Must be done after the element is added to the DOM
  2349. * @param {Boolean} apply
  2350. */
  2351. shadow: function (apply, group, cutOff) {
  2352. var shadows = [],
  2353. i,
  2354. shadow,
  2355. element = this.element,
  2356. strokeWidth,
  2357. // compensate for inverted plot area
  2358. transform = this.parentInverted ? '(-1,-1)' : '(1,1)';
  2359. if (apply) {
  2360. for (i = 1; i <= 3; i++) {
  2361. shadow = element.cloneNode(0);
  2362. strokeWidth = 7 - 2 * i;
  2363. attr(shadow, {
  2364. 'isShadow': 'true',
  2365. 'stroke': 'rgb(0, 0, 0)',
  2366. 'stroke-opacity': 0.05 * i,
  2367. 'stroke-width': strokeWidth,
  2368. 'transform': 'translate' + transform,
  2369. 'fill': NONE
  2370. });
  2371. if (cutOff) {
  2372. attr(shadow, 'height', mathMax(attr(shadow, 'height') - strokeWidth, 0));
  2373. shadow.cutHeight = strokeWidth;
  2374. }
  2375. if (group) {
  2376. group.element.appendChild(shadow);
  2377. } else {
  2378. element.parentNode.insertBefore(shadow, element);
  2379. }
  2380. shadows.push(shadow);
  2381. }
  2382. this.shadows = shadows;
  2383. }
  2384. return this;
  2385. }
  2386. };
  2387. /**
  2388. * The default SVG renderer
  2389. */
  2390. var SVGRenderer = function () {
  2391. this.init.apply(this, arguments);
  2392. };
  2393. SVGRenderer.prototype = {
  2394. Element: SVGElement,
  2395. /**
  2396. * Initialize the SVGRenderer
  2397. * @param {Object} container
  2398. * @param {Number} width
  2399. * @param {Number} height
  2400. * @param {Boolean} forExport
  2401. */
  2402. init: function (container, width, height, forExport) {
  2403. var renderer = this,
  2404. loc = location,
  2405. boxWrapper;
  2406. boxWrapper = renderer.createElement('svg')
  2407. .attr({
  2408. xmlns: SVG_NS,
  2409. version: '1.1'
  2410. });
  2411. container.appendChild(boxWrapper.element);
  2412. // object properties
  2413. renderer.isSVG = true;
  2414. renderer.box = boxWrapper.element;
  2415. renderer.boxWrapper = boxWrapper;
  2416. renderer.alignedObjects = [];
  2417. renderer.url = isIE ? '' : loc.href.replace(/#.*?$/, '')
  2418. .replace(/([\('\)])/g, '\\$1'); // Page url used for internal references. #24, #672.
  2419. renderer.defs = this.createElement('defs').add();
  2420. renderer.forExport = forExport;
  2421. renderer.gradients = {}; // Object where gradient SvgElements are stored
  2422. renderer.setSize(width, height, false);
  2423. // Issue 110 workaround:
  2424. // In Firefox, if a div is positioned by percentage, its pixel position may land
  2425. // between pixels. The container itself doesn't display this, but an SVG element
  2426. // inside this container will be drawn at subpixel precision. In order to draw
  2427. // sharp lines, this must be compensated for. This doesn't seem to work inside
  2428. // iframes though (like in jsFiddle).
  2429. var subPixelFix, rect;
  2430. if (isFirefox && container.getBoundingClientRect) {
  2431. renderer.subPixelFix = subPixelFix = function () {
  2432. css(container, { left: 0, top: 0 });
  2433. rect = container.getBoundingClientRect();
  2434. css(container, {
  2435. left: (mathCeil(rect.left) - rect.left) + PX,
  2436. top: (mathCeil(rect.top) - rect.top) + PX
  2437. });
  2438. };
  2439. // run the fix now
  2440. subPixelFix();
  2441. // run it on resize
  2442. addEvent(win, 'resize', subPixelFix);
  2443. }
  2444. },
  2445. /**
  2446. * Detect whether the renderer is hidden. This happens when one of the parent elements
  2447. * has display: none. #608.
  2448. */
  2449. isHidden: function () {
  2450. return !this.boxWrapper.getBBox().width;
  2451. },
  2452. /**
  2453. * Destroys the renderer and its allocated members.
  2454. */
  2455. destroy: function () {
  2456. var renderer = this,
  2457. rendererDefs = renderer.defs;
  2458. renderer.box = null;
  2459. renderer.boxWrapper = renderer.boxWrapper.destroy();
  2460. // Call destroy on all gradient elements
  2461. destroyObjectProperties(renderer.gradients || {});
  2462. renderer.gradients = null;
  2463. // Defs are null in VMLRenderer
  2464. // Otherwise, destroy them here.
  2465. if (rendererDefs) {
  2466. renderer.defs = rendererDefs.destroy();
  2467. }
  2468. // Remove sub pixel fix handler
  2469. // We need to check that there is a handler, otherwise all functions that are registered for event 'resize' are removed
  2470. // See issue #982
  2471. if (renderer.subPixelFix) {
  2472. removeEvent(win, 'resize', renderer.subPixelFix);
  2473. }
  2474. renderer.alignedObjects = null;
  2475. return null;
  2476. },
  2477. /**
  2478. * Create a wrapper for an SVG element
  2479. * @param {Object} nodeName
  2480. */
  2481. createElement: function (nodeName) {
  2482. var wrapper = new this.Element();
  2483. wrapper.init(this, nodeName);
  2484. return wrapper;
  2485. },
  2486. /**
  2487. * Dummy function for use in canvas renderer
  2488. */
  2489. draw: function () {},
  2490. /**
  2491. * Parse a simple HTML string into SVG tspans
  2492. *
  2493. * @param {Object} textNode The parent text SVG node
  2494. */
  2495. buildText: function (wrapper) {
  2496. var textNode = wrapper.element,
  2497. lines = pick(wrapper.textStr, '').toString()
  2498. .replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
  2499. .replace(/<(i|em)>/g, '<span style="font-style:italic">')
  2500. .replace(/<a/g, '<span')
  2501. .replace(/<\/(b|strong|i|em|a)>/g, '</span>')
  2502. .split(/<br.*?>/g),
  2503. childNodes = textNode.childNodes,
  2504. styleRegex = /style="([^"]+)"/,
  2505. hrefRegex = /href="([^"]+)"/,
  2506. parentX = attr(textNode, 'x'),
  2507. textStyles = wrapper.styles,
  2508. width = textStyles && pInt(textStyles.width),
  2509. textLineHeight = textStyles && textStyles.lineHeight,
  2510. lastLine,
  2511. GET_COMPUTED_STYLE = 'getComputedStyle',
  2512. i = childNodes.length,
  2513. linePositions = [];
  2514. // Needed in IE9 because it doesn't report tspan's offsetHeight (#893)
  2515. function getLineHeightByBBox(lineNo) {
  2516. linePositions[lineNo] = textNode.getBBox().height;
  2517. return mathRound(linePositions[lineNo] - (linePositions[lineNo - 1] || 0));
  2518. }
  2519. // remove old text
  2520. while (i--) {
  2521. textNode.removeChild(childNodes[i]);
  2522. }
  2523. if (width && !wrapper.added) {
  2524. this.box.appendChild(textNode); // attach it to the DOM to read offset width
  2525. }
  2526. // remove empty line at end
  2527. if (lines[lines.length - 1] === '') {
  2528. lines.pop();
  2529. }
  2530. // build the lines
  2531. each(lines, function (line, lineNo) {
  2532. var spans, spanNo = 0, lineHeight;
  2533. line = line.replace(/<span/g, '|||<span').replace(/<\/span>/g, '</span>|||');
  2534. spans = line.split('|||');
  2535. each(spans, function (span) {
  2536. if (span !== '' || spans.length === 1) {
  2537. var attributes = {},
  2538. tspan = doc.createElementNS(SVG_NS, 'tspan');
  2539. if (styleRegex.test(span)) {
  2540. attr(
  2541. tspan,
  2542. 'style',
  2543. span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2')
  2544. );
  2545. }
  2546. if (hrefRegex.test(span)) {
  2547. attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"');
  2548. css(tspan, { cursor: 'pointer' });
  2549. }
  2550. span = (span.replace(/<(.|\n)*?>/g, '') || ' ')
  2551. .replace(/&lt;/g, '<')
  2552. .replace(/&gt;/g, '>');
  2553. // issue #38 workaround.
  2554. /*if (reverse) {
  2555. arr = [];
  2556. i = span.length;
  2557. while (i--) {
  2558. arr.push(span.charAt(i));
  2559. }
  2560. span = arr.join('');
  2561. }*/
  2562. // add the text node
  2563. tspan.appendChild(doc.createTextNode(span));
  2564. if (!spanNo) { // first span in a line, align it to the left
  2565. attributes.x = parentX;
  2566. } else {
  2567. // Firefox ignores spaces at the front or end of the tspan
  2568. attributes.dx = 3; // space
  2569. }
  2570. // first span on subsequent line, add the line height
  2571. if (!spanNo) {
  2572. if (lineNo) {
  2573. // allow getting the right offset height in exporting in IE
  2574. if (!hasSVG && wrapper.renderer.forExport) {
  2575. css(tspan, { display: 'block' });
  2576. }
  2577. // Webkit and opera sometimes return 'normal' as the line height. In that
  2578. // case, webkit uses offsetHeight, while Opera falls back to 18
  2579. lineHeight = win[GET_COMPUTED_STYLE] &&
  2580. pInt(win[GET_COMPUTED_STYLE](lastLine, null).getPropertyValue('line-height'));
  2581. if (!lineHeight || isNaN(lineHeight)) {
  2582. lineHeight = textLineHeight || lastLine.offsetHeight || getLineHeightByBBox(lineNo) || 18;
  2583. }
  2584. attr(tspan, 'dy', lineHeight);
  2585. }
  2586. lastLine = tspan; // record for use in next line
  2587. }
  2588. // add attributes
  2589. attr(tspan, attributes);
  2590. // append it
  2591. textNode.appendChild(tspan);
  2592. spanNo++;
  2593. // check width and apply soft breaks
  2594. if (width) {
  2595. var words = span.replace(/-/g, '- ').split(' '),
  2596. tooLong,
  2597. actualWidth,
  2598. rest = [];
  2599. while (words.length || rest.length) {
  2600. actualWidth = wrapper.getBBox().width;
  2601. tooLong = actualWidth > width;
  2602. if (!tooLong || words.length === 1) { // new line needed
  2603. words = rest;
  2604. rest = [];
  2605. if (words.length) {
  2606. tspan = doc.createElementNS(SVG_NS, 'tspan');
  2607. attr(tspan, {
  2608. dy: textLineHeight || 16,
  2609. x: parentX
  2610. });
  2611. textNode.appendChild(tspan);
  2612. if (actualWidth > width) { // a single word is pressing it out
  2613. width = actualWidth;
  2614. }
  2615. }
  2616. } else { // append to existing line tspan
  2617. tspan.removeChild(tspan.firstChild);
  2618. rest.unshift(words.pop());
  2619. }
  2620. if (words.length) {
  2621. tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
  2622. }
  2623. }
  2624. }
  2625. }
  2626. });
  2627. });
  2628. },
  2629. /**
  2630. * Create a button with preset states
  2631. * @param {String} text
  2632. * @param {Number} x
  2633. * @param {Number} y
  2634. * @param {Function} callback
  2635. * @param {Object} normalState
  2636. * @param {Object} hoverState
  2637. * @param {Object} pressedState
  2638. */
  2639. button: function (text, x, y, callback, normalState, hoverState, pressedState) {
  2640. var label = this.label(text, x, y),
  2641. curState = 0,
  2642. stateOptions,
  2643. stateStyle,
  2644. normalStyle,
  2645. hoverStyle,
  2646. pressedStyle,
  2647. STYLE = 'style',
  2648. verticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 };
  2649. // prepare the attributes
  2650. /*jslint white: true*/
  2651. normalState = merge(hash(
  2652. STROKE_WIDTH, 1,
  2653. STROKE, '#999',
  2654. FILL, hash(
  2655. LINEAR_GRADIENT, verticalGradient,
  2656. STOPS, [
  2657. [0, '#FFF'],
  2658. [1, '#DDD']
  2659. ]
  2660. ),
  2661. 'r', 3,
  2662. 'padding', 3,
  2663. STYLE, hash(
  2664. 'color', 'black'
  2665. )
  2666. ), normalState);
  2667. /*jslint white: false*/
  2668. normalStyle = normalState[STYLE];
  2669. delete normalState[STYLE];
  2670. /*jslint white: true*/
  2671. hoverState = merge(normalState, hash(
  2672. STROKE, '#68A',
  2673. FILL, hash(
  2674. LINEAR_GRADIENT, verticalGradient,
  2675. STOPS, [
  2676. [0, '#FFF'],
  2677. [1, '#ACF']
  2678. ]
  2679. )
  2680. ), hoverState);
  2681. /*jslint white: false*/
  2682. hoverStyle = hoverState[STYLE];
  2683. delete hoverState[STYLE];
  2684. /*jslint white: true*/
  2685. pressedState = merge(normalState, hash(
  2686. STROKE, '#68A',
  2687. FILL, hash(
  2688. LINEAR_GRADIENT, verticalGradient,
  2689. STOPS, [
  2690. [0, '#9BD'],
  2691. [1, '#CDF']
  2692. ]
  2693. )
  2694. ), pressedState);
  2695. /*jslint white: false*/
  2696. pressedStyle = pressedState[STYLE];
  2697. delete pressedState[STYLE];
  2698. // add the events
  2699. addEvent(label.element, 'mouseenter', function () {
  2700. label.attr(hoverState)
  2701. .css(hoverStyle);
  2702. });
  2703. addEvent(label.element, 'mouseleave', function () {
  2704. stateOptions = [normalState, hoverState, pressedState][curState];
  2705. stateStyle = [normalStyle, hoverStyle, pressedStyle][curState];
  2706. label.attr(stateOptions)
  2707. .css(stateStyle);
  2708. });
  2709. label.setState = function (state) {
  2710. curState = state;
  2711. if (!state) {
  2712. label.attr(normalState)
  2713. .css(normalStyle);
  2714. } else if (state === 2) {
  2715. label.attr(pressedState)
  2716. .css(pressedStyle);
  2717. }
  2718. };
  2719. return label
  2720. .on('click', function () {
  2721. callback.call(label);
  2722. })
  2723. .attr(normalState)
  2724. .css(extend({ cursor: 'default' }, normalStyle));
  2725. },
  2726. /**
  2727. * Make a straight line crisper by not spilling out to neighbour pixels
  2728. * @param {Array} points
  2729. * @param {Number} width
  2730. */
  2731. crispLine: function (points, width) {
  2732. // points format: [M, 0, 0, L, 100, 0]
  2733. // normalize to a crisp line
  2734. if (points[1] === points[4]) {
  2735. points[1] = points[4] = mathRound(points[1]) + (width % 2 / 2);
  2736. }
  2737. if (points[2] === points[5]) {
  2738. points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2);
  2739. }
  2740. return points;
  2741. },
  2742. /**
  2743. * Draw a path
  2744. * @param {Array} path An SVG path in array form
  2745. */
  2746. path: function (path) {
  2747. var attr = {
  2748. fill: NONE
  2749. };
  2750. if (isArray(path)) {
  2751. attr.d = path;
  2752. } else if (isObject(path)) { // attributes
  2753. extend(attr, path);
  2754. }
  2755. return this.createElement('path').attr(attr);
  2756. },
  2757. /**
  2758. * Draw and return an SVG circle
  2759. * @param {Number} x The x position
  2760. * @param {Number} y The y position
  2761. * @param {Number} r The radius
  2762. */
  2763. circle: function (x, y, r) {
  2764. var attr = isObject(x) ?
  2765. x :
  2766. {
  2767. x: x,
  2768. y: y,
  2769. r: r
  2770. };
  2771. return this.createElement('circle').attr(attr);
  2772. },
  2773. /**
  2774. * Draw and return an arc
  2775. * @param {Number} x X position
  2776. * @param {Number} y Y position
  2777. * @param {Number} r Radius
  2778. * @param {Number} innerR Inner radius like used in donut charts
  2779. * @param {Number} start Starting angle
  2780. * @param {Number} end Ending angle
  2781. */
  2782. arc: function (x, y, r, innerR, start, end) {
  2783. // arcs are defined as symbols for the ability to set
  2784. // attributes in attr and animate
  2785. if (isObject(x)) {
  2786. y = x.y;
  2787. r = x.r;
  2788. innerR = x.innerR;
  2789. start = x.start;
  2790. end = x.end;
  2791. x = x.x;
  2792. }
  2793. return this.symbol('arc', x || 0, y || 0, r || 0, r || 0, {
  2794. innerR: innerR || 0,
  2795. start: start || 0,
  2796. end: end || 0
  2797. });
  2798. },
  2799. /**
  2800. * Draw and return a rectangle
  2801. * @param {Number} x Left position
  2802. * @param {Number} y Top position
  2803. * @param {Number} width
  2804. * @param {Number} height
  2805. * @param {Number} r Border corner radius
  2806. * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing
  2807. */
  2808. rect: function (x, y, width, height, r, strokeWidth) {
  2809. r = isObject(x) ? x.r : r;
  2810. var wrapper = this.createElement('rect').attr({
  2811. rx: r,
  2812. ry: r,
  2813. fill: NONE
  2814. });
  2815. return wrapper.attr(
  2816. isObject(x) ?
  2817. x :
  2818. // do not crispify when an object is passed in (as in column charts)
  2819. wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0))
  2820. );
  2821. },
  2822. /**
  2823. * Resize the box and re-align all aligned elements
  2824. * @param {Object} width
  2825. * @param {Object} height
  2826. * @param {Boolean} animate
  2827. *
  2828. */
  2829. setSize: function (width, height, animate) {
  2830. var renderer = this,
  2831. alignedObjects = renderer.alignedObjects,
  2832. i = alignedObjects.length;
  2833. renderer.width = width;
  2834. renderer.height = height;
  2835. renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({
  2836. width: width,
  2837. height: height
  2838. });
  2839. while (i--) {
  2840. alignedObjects[i].align();
  2841. }
  2842. },
  2843. /**
  2844. * Create a group
  2845. * @param {String} name The group will be given a class name of 'highcharts-{name}'.
  2846. * This can be used for styling and scripting.
  2847. */
  2848. g: function (name) {
  2849. var elem = this.createElement('g');
  2850. return defined(name) ? elem.attr({ 'class': PREFIX + name }) : elem;
  2851. },
  2852. /**
  2853. * Display an image
  2854. * @param {String} src
  2855. * @param {Number} x
  2856. * @param {Number} y
  2857. * @param {Number} width
  2858. * @param {Number} height
  2859. */
  2860. image: function (src, x, y, width, height) {
  2861. var attribs = {
  2862. preserveAspectRatio: NONE
  2863. },
  2864. elemWrapper;
  2865. // optional properties
  2866. if (arguments.length > 1) {
  2867. extend(attribs, {
  2868. x: x,
  2869. y: y,
  2870. width: width,
  2871. height: height
  2872. });
  2873. }
  2874. elemWrapper = this.createElement('image').attr(attribs);
  2875. // set the href in the xlink namespace
  2876. if (elemWrapper.element.setAttributeNS) {
  2877. elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',
  2878. 'href', src);
  2879. } else {
  2880. // could be exporting in IE
  2881. // using href throws "not supported" in ie7 and under, requries regex shim to fix later
  2882. elemWrapper.element.setAttribute('hc-svg-href', src);
  2883. }
  2884. return elemWrapper;
  2885. },
  2886. /**
  2887. * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object.
  2888. *
  2889. * @param {Object} symbol
  2890. * @param {Object} x
  2891. * @param {Object} y
  2892. * @param {Object} radius
  2893. * @param {Object} options
  2894. */
  2895. symbol: function (symbol, x, y, width, height, options) {
  2896. var obj,
  2897. // get the symbol definition function
  2898. symbolFn = this.symbols[symbol],
  2899. // check if there's a path defined for this symbol
  2900. path = symbolFn && symbolFn(
  2901. mathRound(x),
  2902. mathRound(y),
  2903. width,
  2904. height,
  2905. options
  2906. ),
  2907. imageRegex = /^url\((.*?)\)$/,
  2908. imageSrc,
  2909. imageSize,
  2910. centerImage;
  2911. if (path) {
  2912. obj = this.path(path);
  2913. // expando properties for use in animate and attr
  2914. extend(obj, {
  2915. symbolName: symbol,
  2916. x: x,
  2917. y: y,
  2918. width: width,
  2919. height: height
  2920. });
  2921. if (options) {
  2922. extend(obj, options);
  2923. }
  2924. // image symbols
  2925. } else if (imageRegex.test(symbol)) {
  2926. // On image load, set the size and position
  2927. centerImage = function (img, size) {
  2928. img.attr({
  2929. width: size[0],
  2930. height: size[1]
  2931. });
  2932. if (!img.alignByTranslate) { // #185
  2933. img.translate(
  2934. -mathRound(size[0] / 2),
  2935. -mathRound(size[1] / 2)
  2936. );
  2937. }
  2938. };
  2939. imageSrc = symbol.match(imageRegex)[1];
  2940. imageSize = symbolSizes[imageSrc];
  2941. // create the image synchronously, add attribs async
  2942. obj = this.image(imageSrc)
  2943. .attr({
  2944. x: x,
  2945. y: y
  2946. });
  2947. if (imageSize) {
  2948. centerImage(obj, imageSize);
  2949. } else {
  2950. // initialize image to be 0 size so export will still function if there's no cached sizes
  2951. obj.attr({ width: 0, height: 0 });
  2952. // create a dummy JavaScript image to get the width and height
  2953. createElement('img', {
  2954. onload: function () {
  2955. var img = this;
  2956. centerImage(obj, symbolSizes[imageSrc] = [img.width, img.height]);
  2957. },
  2958. src: imageSrc
  2959. });
  2960. }
  2961. }
  2962. return obj;
  2963. },
  2964. /**
  2965. * An extendable collection of functions for defining symbol paths.
  2966. */
  2967. symbols: {
  2968. 'circle': function (x, y, w, h) {
  2969. var cpw = 0.166 * w;
  2970. return [
  2971. M, x + w / 2, y,
  2972. 'C', x + w + cpw, y, x + w + cpw, y + h, x + w / 2, y + h,
  2973. 'C', x - cpw, y + h, x - cpw, y, x + w / 2, y,
  2974. 'Z'
  2975. ];
  2976. },
  2977. 'square': function (x, y, w, h) {
  2978. return [
  2979. M, x, y,
  2980. L, x + w, y,
  2981. x + w, y + h,
  2982. x, y + h,
  2983. 'Z'
  2984. ];
  2985. },
  2986. 'triangle': function (x, y, w, h) {
  2987. return [
  2988. M, x + w / 2, y,
  2989. L, x + w, y + h,
  2990. x, y + h,
  2991. 'Z'
  2992. ];
  2993. },
  2994. 'triangle-down': function (x, y, w, h) {
  2995. return [
  2996. M, x, y,
  2997. L, x + w, y,
  2998. x + w / 2, y + h,
  2999. 'Z'
  3000. ];
  3001. },
  3002. 'diamond': function (x, y, w, h) {
  3003. return [
  3004. M, x + w / 2, y,
  3005. L, x + w, y + h / 2,
  3006. x + w / 2, y + h,
  3007. x, y + h / 2,
  3008. 'Z'
  3009. ];
  3010. },
  3011. 'arc': function (x, y, w, h, options) {
  3012. var start = options.start,
  3013. radius = options.r || w || h,
  3014. end = options.end - 0.000001, // to prevent cos and sin of start and end from becoming equal on 360 arcs
  3015. innerRadius = options.innerR,
  3016. open = options.open,
  3017. cosStart = mathCos(start),
  3018. sinStart = mathSin(start),
  3019. cosEnd = mathCos(end),
  3020. sinEnd = mathSin(end),
  3021. longArc = options.end - start < mathPI ? 0 : 1;
  3022. return [
  3023. M,
  3024. x + radius * cosStart,
  3025. y + radius * sinStart,
  3026. 'A', // arcTo
  3027. radius, // x radius
  3028. radius, // y radius
  3029. 0, // slanting
  3030. longArc, // long or short arc
  3031. 1, // clockwise
  3032. x + radius * cosEnd,
  3033. y + radius * sinEnd,
  3034. open ? M : L,
  3035. x + innerRadius * cosEnd,
  3036. y + innerRadius * sinEnd,
  3037. 'A', // arcTo
  3038. innerRadius, // x radius
  3039. innerRadius, // y radius
  3040. 0, // slanting
  3041. longArc, // long or short arc
  3042. 0, // clockwise
  3043. x + innerRadius * cosStart,
  3044. y + innerRadius * sinStart,
  3045. open ? '' : 'Z' // close
  3046. ];
  3047. }
  3048. },
  3049. /**
  3050. * Define a clipping rectangle
  3051. * @param {String} id
  3052. * @param {Number} x
  3053. * @param {Number} y
  3054. * @param {Number} width
  3055. * @param {Number} height
  3056. */
  3057. clipRect: function (x, y, width, height) {
  3058. var wrapper,
  3059. id = PREFIX + idCounter++,
  3060. clipPath = this.createElement('clipPath').attr({
  3061. id: id
  3062. }).add(this.defs);
  3063. wrapper = this.rect(x, y, width, height, 0).add(clipPath);
  3064. wrapper.id = id;
  3065. wrapper.clipPath = clipPath;
  3066. return wrapper;
  3067. },
  3068. /**
  3069. * Take a color and return it if it's a string, make it a gradient if it's a
  3070. * gradient configuration object. Prior to Highstock, an array was used to define
  3071. * a linear gradient with pixel positions relative to the SVG. In newer versions
  3072. * we change the coordinates to apply relative to the shape, using coordinates
  3073. * 0-1 within the shape. To preserve backwards compatibility, linearGradient
  3074. * in this definition is an object of x1, y1, x2 and y2.
  3075. *
  3076. * @param {Object} color The color or config object
  3077. */
  3078. color: function (color, elem, prop) {
  3079. var renderer = this,
  3080. colorObject,
  3081. regexRgba = /^rgba/,
  3082. gradName;
  3083. // Apply linear or radial gradients
  3084. if (color && color.linearGradient) {
  3085. gradName = 'linearGradient';
  3086. } else if (color && color.radialGradient) {
  3087. gradName = 'radialGradient';
  3088. }
  3089. if (gradName) {
  3090. var gradAttr = color[gradName],
  3091. gradients = renderer.gradients,
  3092. gradientObject,
  3093. stopColor,
  3094. stopOpacity,
  3095. radialReference = elem.radialReference;
  3096. // Check if a gradient object with the same config object is created within this renderer
  3097. if (!gradAttr.id || !gradients[gradAttr.id]) {
  3098. // Keep < 2.2 kompatibility
  3099. if (isArray(gradAttr)) {
  3100. color[gradName] = gradAttr = {
  3101. x1: gradAttr[0],
  3102. y1: gradAttr[1],
  3103. x2: gradAttr[2],
  3104. y2: gradAttr[3],
  3105. gradientUnits: 'userSpaceOnUse'
  3106. };
  3107. }
  3108. // Correct the radial gradient for the radial reference system
  3109. if (gradName === 'radialGradient' && radialReference && !defined(gradAttr.gradientUnits)) {
  3110. extend(gradAttr, {
  3111. cx: (radialReference[0] - radialReference[2] / 2) + gradAttr.cx * radialReference[2],
  3112. cy: (radialReference[1] - radialReference[2] / 2) + gradAttr.cy * radialReference[2],
  3113. r: gradAttr.r * radialReference[2],
  3114. gradientUnits: 'userSpaceOnUse'
  3115. });
  3116. }
  3117. // Set the id and create the element
  3118. gradAttr.id = PREFIX + idCounter++;
  3119. gradients[gradAttr.id] = gradientObject = renderer.createElement(gradName)
  3120. .attr(gradAttr)
  3121. .add(renderer.defs);
  3122. // The gradient needs to keep a list of stops to be able to destroy them
  3123. gradientObject.stops = [];
  3124. each(color.stops, function (stop) {
  3125. var stopObject;
  3126. if (regexRgba.test(stop[1])) {
  3127. colorObject = Color(stop[1]);
  3128. stopColor = colorObject.get('rgb');
  3129. stopOpacity = colorObject.get('a');
  3130. } else {
  3131. stopColor = stop[1];
  3132. stopOpacity = 1;
  3133. }
  3134. stopObject = renderer.createElement('stop').attr({
  3135. offset: stop[0],
  3136. 'stop-color': stopColor,
  3137. 'stop-opacity': stopOpacity
  3138. }).add(gradientObject);
  3139. // Add the stop element to the gradient
  3140. gradientObject.stops.push(stopObject);
  3141. });
  3142. }
  3143. // Return the reference to the gradient object
  3144. return 'url(' + renderer.url + '#' + gradAttr.id + ')';
  3145. // Webkit and Batik can't show rgba.
  3146. } else if (regexRgba.test(color)) {
  3147. colorObject = Color(color);
  3148. attr(elem, prop + '-opacity', colorObject.get('a'));
  3149. return colorObject.get('rgb');
  3150. } else {
  3151. // Remove the opacity attribute added above. Does not throw if the attribute is not there.
  3152. elem.removeAttribute(prop + '-opacity');
  3153. return color;
  3154. }
  3155. },
  3156. /**
  3157. * Add text to the SVG object
  3158. * @param {String} str
  3159. * @param {Number} x Left position
  3160. * @param {Number} y Top position
  3161. * @param {Boolean} useHTML Use HTML to render the text
  3162. */
  3163. text: function (str, x, y, useHTML) {
  3164. // declare variables
  3165. var renderer = this,
  3166. defaultChartStyle = defaultOptions.chart.style,
  3167. wrapper;
  3168. if (useHTML && !renderer.forExport) {
  3169. return renderer.html(str, x, y);
  3170. }
  3171. x = mathRound(pick(x, 0));
  3172. y = mathRound(pick(y, 0));
  3173. wrapper = renderer.createElement('text')
  3174. .attr({
  3175. x: x,
  3176. y: y,
  3177. text: str
  3178. })
  3179. .css({
  3180. fontFamily: defaultChartStyle.fontFamily,
  3181. fontSize: defaultChartStyle.fontSize
  3182. });
  3183. wrapper.x = x;
  3184. wrapper.y = y;
  3185. return wrapper;
  3186. },
  3187. /**
  3188. * Create HTML text node. This is used by the VML renderer as well as the SVG
  3189. * renderer through the useHTML option.
  3190. *
  3191. * @param {String} str
  3192. * @param {Number} x
  3193. * @param {Number} y
  3194. */
  3195. html: function (str, x, y) {
  3196. var defaultChartStyle = defaultOptions.chart.style,
  3197. wrapper = this.createElement('span'),
  3198. attrSetters = wrapper.attrSetters,
  3199. element = wrapper.element,
  3200. renderer = wrapper.renderer;
  3201. // Text setter
  3202. attrSetters.text = function (value) {
  3203. element.innerHTML = value;
  3204. return false;
  3205. };
  3206. // Various setters which rely on update transform
  3207. attrSetters.x = attrSetters.y = attrSetters.align = function (value, key) {
  3208. if (key === 'align') {
  3209. key = 'textAlign'; // Do not overwrite the SVGElement.align method. Same as VML.
  3210. }
  3211. wrapper[key] = value;
  3212. wrapper.htmlUpdateTransform();
  3213. return false;
  3214. };
  3215. // Set the default attributes
  3216. wrapper.attr({
  3217. text: str,
  3218. x: mathRound(x),
  3219. y: mathRound(y)
  3220. })
  3221. .css({
  3222. position: ABSOLUTE,
  3223. whiteSpace: 'nowrap',
  3224. fontFamily: defaultChartStyle.fontFamily,
  3225. fontSize: defaultChartStyle.fontSize
  3226. });
  3227. // Use the HTML specific .css method
  3228. wrapper.css = wrapper.htmlCss;
  3229. // This is specific for HTML within SVG
  3230. if (renderer.isSVG) {
  3231. wrapper.add = function (svgGroupWrapper) {
  3232. var htmlGroup,
  3233. htmlGroupStyle,
  3234. container = renderer.box.parentNode;
  3235. // Create a mock group to hold the HTML elements
  3236. if (svgGroupWrapper) {
  3237. htmlGroup = svgGroupWrapper.div;
  3238. if (!htmlGroup) {
  3239. htmlGroup = svgGroupWrapper.div = createElement(DIV, {
  3240. className: attr(svgGroupWrapper.element, 'class')
  3241. }, {
  3242. position: ABSOLUTE,
  3243. left: svgGroupWrapper.attr('translateX') + PX,
  3244. top: svgGroupWrapper.attr('translateY') + PX
  3245. }, container);
  3246. // Ensure dynamic updating position
  3247. htmlGroupStyle = htmlGroup.style;
  3248. extend(svgGroupWrapper.attrSetters, {
  3249. translateX: function (value) {
  3250. htmlGroupStyle.left = value + PX;
  3251. },
  3252. translateY: function (value) {
  3253. htmlGroupStyle.top = value + PX;
  3254. },
  3255. visibility: function (value, key) {
  3256. htmlGroupStyle[key] = value;
  3257. }
  3258. });
  3259. }
  3260. } else {
  3261. htmlGroup = container;
  3262. }
  3263. htmlGroup.appendChild(element);
  3264. // Shared with VML:
  3265. wrapper.added = true;
  3266. if (wrapper.alignOnAdd) {
  3267. wrapper.htmlUpdateTransform();
  3268. }
  3269. return wrapper;
  3270. };
  3271. }
  3272. return wrapper;
  3273. },
  3274. /**
  3275. * Utility to return the baseline offset and total line height from the font size
  3276. */
  3277. fontMetrics: function (fontSize) {
  3278. fontSize = pInt(fontSize || 11);
  3279. // Empirical values found by comparing font size and bounding box height.
  3280. // Applies to the default font family. http://jsfiddle.net/highcharts/7xvn7/
  3281. var lineHeight = fontSize < 24 ? fontSize + 4 : mathRound(fontSize * 1.2),
  3282. baseline = mathRound(lineHeight * 0.8);
  3283. return {
  3284. h: lineHeight,
  3285. b: baseline
  3286. };
  3287. },
  3288. /**
  3289. * Add a label, a text item that can hold a colored or gradient background
  3290. * as well as a border and shadow.
  3291. * @param {string} str
  3292. * @param {Number} x
  3293. * @param {Number} y
  3294. * @param {String} shape
  3295. * @param {Number} anchorX In case the shape has a pointer, like a flag, this is the
  3296. * coordinates it should be pinned to
  3297. * @param {Number} anchorY
  3298. * @param {Boolean} baseline Whether to position the label relative to the text baseline,
  3299. * like renderer.text, or to the upper border of the rectangle.
  3300. * @param {String} className Class name for the group
  3301. */
  3302. label: function (str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) {
  3303. var renderer = this,
  3304. wrapper = renderer.g(className),
  3305. text = renderer.text('', 0, 0, useHTML)
  3306. .attr({
  3307. zIndex: 1
  3308. })
  3309. .add(wrapper),
  3310. box,
  3311. bBox,
  3312. alignFactor = 0,
  3313. padding = 3,
  3314. width,
  3315. height,
  3316. wrapperX,
  3317. wrapperY,
  3318. crispAdjust = 0,
  3319. deferredAttr = {},
  3320. baselineOffset,
  3321. attrSetters = wrapper.attrSetters;
  3322. /**
  3323. * This function runs after the label is added to the DOM (when the bounding box is
  3324. * available), and after the text of the label is updated to detect the new bounding
  3325. * box and reflect it in the border box.
  3326. */
  3327. function updateBoxSize() {
  3328. var boxY,
  3329. style = text.element.style;
  3330. bBox = (width === undefined || height === undefined || wrapper.styles.textAlign) &&
  3331. text.getBBox(true);
  3332. wrapper.width = (width || bBox.width || 0) + 2 * padding;
  3333. wrapper.height = (height || bBox.height || 0) + 2 * padding;
  3334. // update the label-scoped y offset
  3335. baselineOffset = padding + renderer.fontMetrics(style && style.fontSize).b;
  3336. // create the border box if it is not already present
  3337. if (!box) {
  3338. boxY = baseline ? -baselineOffset : 0;
  3339. wrapper.box = box = shape ?
  3340. renderer.symbol(shape, -alignFactor * padding, boxY, wrapper.width, wrapper.height) :
  3341. renderer.rect(-alignFactor * padding, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]);
  3342. box.add(wrapper);
  3343. }
  3344. // apply the box attributes
  3345. box.attr(merge({
  3346. width: wrapper.width,
  3347. height: wrapper.height
  3348. }, deferredAttr));
  3349. deferredAttr = null;
  3350. }
  3351. /**
  3352. * This function runs after setting text or padding, but only if padding is changed
  3353. */
  3354. function updateTextPadding() {
  3355. var styles = wrapper.styles,
  3356. textAlign = styles && styles.textAlign,
  3357. x = padding * (1 - alignFactor),
  3358. y;
  3359. // determin y based on the baseline
  3360. y = baseline ? 0 : baselineOffset;
  3361. // compensate for alignment
  3362. if (defined(width) && (textAlign === 'center' || textAlign === 'right')) {
  3363. x += { center: 0.5, right: 1 }[textAlign] * (width - bBox.width);
  3364. }
  3365. // update if anything changed
  3366. if (x !== text.x || y !== text.y) {
  3367. text.attr({
  3368. x: x,
  3369. y: y
  3370. });
  3371. }
  3372. // record current values
  3373. text.x = x;
  3374. text.y = y;
  3375. }
  3376. /**
  3377. * Set a box attribute, or defer it if the box is not yet created
  3378. * @param {Object} key
  3379. * @param {Object} value
  3380. */
  3381. function boxAttr(key, value) {
  3382. if (box) {
  3383. box.attr(key, value);
  3384. } else {
  3385. deferredAttr[key] = value;
  3386. }
  3387. }
  3388. function getSizeAfterAdd() {
  3389. wrapper.attr({
  3390. text: str, // alignment is available now
  3391. x: x,
  3392. y: y
  3393. });
  3394. if (defined(anchorX)) {
  3395. wrapper.attr({
  3396. anchorX: anchorX,
  3397. anchorY: anchorY
  3398. });
  3399. }
  3400. }
  3401. /**
  3402. * After the text element is added, get the desired size of the border box
  3403. * and add it before the text in the DOM.
  3404. */
  3405. addEvent(wrapper, 'add', getSizeAfterAdd);
  3406. /*
  3407. * Add specific attribute setters.
  3408. */
  3409. // only change local variables
  3410. attrSetters.width = function (value) {
  3411. width = value;
  3412. return false;
  3413. };
  3414. attrSetters.height = function (value) {
  3415. height = value;
  3416. return false;
  3417. };
  3418. attrSetters.padding = function (value) {
  3419. if (defined(value) && value !== padding) {
  3420. padding = value;
  3421. updateTextPadding();
  3422. }
  3423. return false;
  3424. };
  3425. // change local variable and set attribue as well
  3426. attrSetters.align = function (value) {
  3427. alignFactor = { left: 0, center: 0.5, right: 1 }[value];
  3428. return false; // prevent setting text-anchor on the group
  3429. };
  3430. // apply these to the box and the text alike
  3431. attrSetters.text = function (value, key) {
  3432. text.attr(key, value);
  3433. updateBoxSize();
  3434. updateTextPadding();
  3435. return false;
  3436. };
  3437. // apply these to the box but not to the text
  3438. attrSetters[STROKE_WIDTH] = function (value, key) {
  3439. crispAdjust = value % 2 / 2;
  3440. boxAttr(key, value);
  3441. return false;
  3442. };
  3443. attrSetters.stroke = attrSetters.fill = attrSetters.r = function (value, key) {
  3444. boxAttr(key, value);
  3445. return false;
  3446. };
  3447. attrSetters.anchorX = function (value, key) {
  3448. anchorX = value;
  3449. boxAttr(key, value + crispAdjust - wrapperX);
  3450. return false;
  3451. };
  3452. attrSetters.anchorY = function (value, key) {
  3453. anchorY = value;
  3454. boxAttr(key, value - wrapperY);
  3455. return false;
  3456. };
  3457. // rename attributes
  3458. attrSetters.x = function (value) {
  3459. wrapper.x = value; // for animation getter
  3460. value -= alignFactor * ((width || bBox.width) + padding);
  3461. wrapperX = mathRound(value);
  3462. wrapper.attr('translateX', wrapperX);
  3463. return false;
  3464. };
  3465. attrSetters.y = function (value) {
  3466. wrapperY = wrapper.y = mathRound(value);
  3467. wrapper.attr('translateY', value);
  3468. return false;
  3469. };
  3470. // Redirect certain methods to either the box or the text
  3471. var baseCss = wrapper.css;
  3472. return extend(wrapper, {
  3473. /**
  3474. * Pick up some properties and apply them to the text instead of the wrapper
  3475. */
  3476. css: function (styles) {
  3477. if (styles) {
  3478. var textStyles = {};
  3479. styles = merge({}, styles); // create a copy to avoid altering the original object (#537)
  3480. each(['fontSize', 'fontWeight', 'fontFamily', 'color', 'lineHeight', 'width'], function (prop) {
  3481. if (styles[prop] !== UNDEFINED) {
  3482. textStyles[prop] = styles[prop];
  3483. delete styles[prop];
  3484. }
  3485. });
  3486. text.css(textStyles);
  3487. }
  3488. return baseCss.call(wrapper, styles);
  3489. },
  3490. /**
  3491. * Return the bounding box of the box, not the group
  3492. */
  3493. getBBox: function () {
  3494. return box.getBBox();
  3495. },
  3496. /**
  3497. * Apply the shadow to the box
  3498. */
  3499. shadow: function (b) {
  3500. box.shadow(b);
  3501. return wrapper;
  3502. },
  3503. /**
  3504. * Destroy and release memory.
  3505. */
  3506. destroy: function () {
  3507. removeEvent(wrapper, 'add', getSizeAfterAdd);
  3508. // Added by button implementation
  3509. removeEvent(wrapper.element, 'mouseenter');
  3510. removeEvent(wrapper.element, 'mouseleave');
  3511. if (text) {
  3512. // Destroy the text element
  3513. text = text.destroy();
  3514. }
  3515. // Call base implementation to destroy the rest
  3516. SVGElement.prototype.destroy.call(wrapper);
  3517. }
  3518. });
  3519. }
  3520. }; // end SVGRenderer
  3521. // general renderer
  3522. Renderer = SVGRenderer;
  3523. /* ****************************************************************************
  3524. * *
  3525. * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE *
  3526. * *
  3527. * For applications and websites that don't need IE support, like platform *
  3528. * targeted mobile apps and web apps, this code can be removed. *
  3529. * *
  3530. *****************************************************************************/
  3531. /**
  3532. * @constructor
  3533. */
  3534. var VMLRenderer;
  3535. if (!hasSVG && !useCanVG) {
  3536. /**
  3537. * The VML element wrapper.
  3538. */
  3539. var VMLElement = {
  3540. /**
  3541. * Initialize a new VML element wrapper. It builds the markup as a string
  3542. * to minimize DOM traffic.
  3543. * @param {Object} renderer
  3544. * @param {Object} nodeName
  3545. */
  3546. init: function (renderer, nodeName) {
  3547. var wrapper = this,
  3548. markup = ['<', nodeName, ' filled="f" stroked="f"'],
  3549. style = ['position: ', ABSOLUTE, ';'];
  3550. // divs and shapes need size
  3551. if (nodeName === 'shape' || nodeName === DIV) {
  3552. style.push('left:0;top:0;width:1px;height:1px;');
  3553. }
  3554. if (docMode8) {
  3555. style.push('visibility: ', nodeName === DIV ? HIDDEN : VISIBLE);
  3556. }
  3557. markup.push(' style="', style.join(''), '"/>');
  3558. // create element with default attributes and style
  3559. if (nodeName) {
  3560. markup = nodeName === DIV || nodeName === 'span' || nodeName === 'img' ?
  3561. markup.join('')
  3562. : renderer.prepVML(markup);
  3563. wrapper.element = createElement(markup);
  3564. }
  3565. wrapper.renderer = renderer;
  3566. wrapper.attrSetters = {};
  3567. },
  3568. /**
  3569. * Add the node to the given parent
  3570. * @param {Object} parent
  3571. */
  3572. add: function (parent) {
  3573. var wrapper = this,
  3574. renderer = wrapper.renderer,
  3575. element = wrapper.element,
  3576. box = renderer.box,
  3577. inverted = parent && parent.inverted,
  3578. // get the parent node
  3579. parentNode = parent ?
  3580. parent.element || parent :
  3581. box;
  3582. // if the parent group is inverted, apply inversion on all children
  3583. if (inverted) { // only on groups
  3584. renderer.invertChild(element, parentNode);
  3585. }
  3586. // issue #140 workaround - related to #61 and #74
  3587. if (docMode8 && parentNode.gVis === HIDDEN) {
  3588. css(element, { visibility: HIDDEN });
  3589. }
  3590. // append it
  3591. parentNode.appendChild(element);
  3592. // align text after adding to be able to read offset
  3593. wrapper.added = true;
  3594. if (wrapper.alignOnAdd && !wrapper.deferUpdateTransform) {
  3595. wrapper.updateTransform();
  3596. }
  3597. // fire an event for internal hooks
  3598. fireEvent(wrapper, 'add');
  3599. return wrapper;
  3600. },
  3601. /**
  3602. * In IE8 documentMode 8, we need to recursively set the visibility down in the DOM
  3603. * tree for nested groups. Related to #61, #586.
  3604. */
  3605. toggleChildren: function (element, visibility) {
  3606. var childNodes = element.childNodes,
  3607. i = childNodes.length;
  3608. while (i--) {
  3609. // apply the visibility
  3610. css(childNodes[i], { visibility: visibility });
  3611. // we have a nested group, apply it to its children again
  3612. if (childNodes[i].nodeName === 'DIV') {
  3613. this.toggleChildren(childNodes[i], visibility);
  3614. }
  3615. }
  3616. },
  3617. /**
  3618. * VML always uses htmlUpdateTransform
  3619. */
  3620. updateTransform: SVGElement.prototype.htmlUpdateTransform,
  3621. /**
  3622. * Get or set attributes
  3623. */
  3624. attr: function (hash, val) {
  3625. var wrapper = this,
  3626. key,
  3627. value,
  3628. i,
  3629. result,
  3630. element = wrapper.element || {},
  3631. elemStyle = element.style,
  3632. nodeName = element.nodeName,
  3633. renderer = wrapper.renderer,
  3634. symbolName = wrapper.symbolName,
  3635. hasSetSymbolSize,
  3636. shadows = wrapper.shadows,
  3637. skipAttr,
  3638. attrSetters = wrapper.attrSetters,
  3639. ret = wrapper;
  3640. // single key-value pair
  3641. if (isString(hash) && defined(val)) {
  3642. key = hash;
  3643. hash = {};
  3644. hash[key] = val;
  3645. }
  3646. // used as a getter, val is undefined
  3647. if (isString(hash)) {
  3648. key = hash;
  3649. if (key === 'strokeWidth' || key === 'stroke-width') {
  3650. ret = wrapper.strokeweight;
  3651. } else {
  3652. ret = wrapper[key];
  3653. }
  3654. // setter
  3655. } else {
  3656. for (key in hash) {
  3657. value = hash[key];
  3658. skipAttr = false;
  3659. // check for a specific attribute setter
  3660. result = attrSetters[key] && attrSetters[key](value, key);
  3661. if (result !== false && value !== null) { // #620
  3662. if (result !== UNDEFINED) {
  3663. value = result; // the attribute setter has returned a new value to set
  3664. }
  3665. // prepare paths
  3666. // symbols
  3667. if (symbolName && /^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(key)) {
  3668. // if one of the symbol size affecting parameters are changed,
  3669. // check all the others only once for each call to an element's
  3670. // .attr() method
  3671. if (!hasSetSymbolSize) {
  3672. wrapper.symbolAttr(hash);
  3673. hasSetSymbolSize = true;
  3674. }
  3675. skipAttr = true;
  3676. } else if (key === 'd') {
  3677. value = value || [];
  3678. wrapper.d = value.join(' '); // used in getter for animation
  3679. // convert paths
  3680. i = value.length;
  3681. var convertedPath = [];
  3682. while (i--) {
  3683. // Multiply by 10 to allow subpixel precision.
  3684. // Substracting half a pixel seems to make the coordinates
  3685. // align with SVG, but this hasn't been tested thoroughly
  3686. if (isNumber(value[i])) {
  3687. convertedPath[i] = mathRound(value[i] * 10) - 5;
  3688. } else if (value[i] === 'Z') { // close the path
  3689. convertedPath[i] = 'x';
  3690. } else {
  3691. convertedPath[i] = value[i];
  3692. }
  3693. }
  3694. value = convertedPath.join(' ') || 'x';
  3695. element.path = value;
  3696. // update shadows
  3697. if (shadows) {
  3698. i = shadows.length;
  3699. while (i--) {
  3700. shadows[i].path = shadows[i].cutOff ? this.cutOffPath(value, shadows[i].cutOff) : value;
  3701. }
  3702. }
  3703. skipAttr = true;
  3704. // directly mapped to css
  3705. } else if (key === 'zIndex' || key === 'visibility') {
  3706. // workaround for #61 and #586
  3707. if (docMode8 && key === 'visibility' && nodeName === 'DIV') {
  3708. element.gVis = value;
  3709. wrapper.toggleChildren(element, value);
  3710. if (value === VISIBLE) { // #74
  3711. value = null;
  3712. }
  3713. }
  3714. if (value) {
  3715. elemStyle[key] = value;
  3716. }
  3717. skipAttr = true;
  3718. // width and height
  3719. } else if (key === 'width' || key === 'height') {
  3720. value = mathMax(0, value); // don't set width or height below zero (#311)
  3721. this[key] = value; // used in getter
  3722. // clipping rectangle special
  3723. if (wrapper.updateClipping) {
  3724. wrapper[key] = value;
  3725. wrapper.updateClipping();
  3726. } else {
  3727. // normal
  3728. elemStyle[key] = value;
  3729. }
  3730. skipAttr = true;
  3731. // x and y
  3732. } else if (key === 'x' || key === 'y') {
  3733. wrapper[key] = value; // used in getter
  3734. elemStyle[{ x: 'left', y: 'top' }[key]] = value;
  3735. // class name
  3736. } else if (key === 'class') {
  3737. // IE8 Standards mode has problems retrieving the className
  3738. element.className = value;
  3739. // stroke
  3740. } else if (key === 'stroke') {
  3741. value = renderer.color(value, element, key);
  3742. key = 'strokecolor';
  3743. // stroke width
  3744. } else if (key === 'stroke-width' || key === 'strokeWidth') {
  3745. element.stroked = value ? true : false;
  3746. key = 'strokeweight';
  3747. wrapper[key] = value; // used in getter, issue #113
  3748. if (isNumber(value)) {
  3749. value += PX;
  3750. }
  3751. // dashStyle
  3752. } else if (key === 'dashstyle') {
  3753. var strokeElem = element.getElementsByTagName('stroke')[0] ||
  3754. createElement(renderer.prepVML(['<stroke/>']), null, null, element);
  3755. strokeElem[key] = value || 'solid';
  3756. wrapper.dashstyle = value; /* because changing stroke-width will change the dash length
  3757. and cause an epileptic effect */
  3758. skipAttr = true;
  3759. // fill
  3760. } else if (key === 'fill') {
  3761. if (nodeName === 'SPAN') { // text color
  3762. elemStyle.color = value;
  3763. } else {
  3764. element.filled = value !== NONE ? true : false;
  3765. value = renderer.color(value, element, key);
  3766. key = 'fillcolor';
  3767. }
  3768. // rotation on VML elements
  3769. } else if (nodeName === 'shape' && key === 'rotation') {
  3770. wrapper[key] = value;
  3771. // translation for animation
  3772. } else if (key === 'translateX' || key === 'translateY' || key === 'rotation') {
  3773. wrapper[key] = value;
  3774. wrapper.updateTransform();
  3775. skipAttr = true;
  3776. // text for rotated and non-rotated elements
  3777. } else if (key === 'text') {
  3778. this.bBox = null;
  3779. element.innerHTML = value;
  3780. skipAttr = true;
  3781. }
  3782. // let the shadow follow the main element
  3783. if (shadows && key === 'visibility') {
  3784. i = shadows.length;
  3785. while (i--) {
  3786. shadows[i].style[key] = value;
  3787. }
  3788. }
  3789. if (!skipAttr) {
  3790. if (docMode8) { // IE8 setAttribute bug
  3791. element[key] = value;
  3792. } else {
  3793. attr(element, key, value);
  3794. }
  3795. }
  3796. }
  3797. }
  3798. }
  3799. return ret;
  3800. },
  3801. /**
  3802. * Set the element's clipping to a predefined rectangle
  3803. *
  3804. * @param {String} id The id of the clip rectangle
  3805. */
  3806. clip: function (clipRect) {
  3807. var wrapper = this,
  3808. clipMembers = clipRect.members,
  3809. element = wrapper.element,
  3810. parentNode = element.parentNode;
  3811. clipMembers.push(wrapper);
  3812. wrapper.destroyClip = function () {
  3813. erase(clipMembers, wrapper);
  3814. };
  3815. // Issue #863 workaround - related to #140, #61, #74
  3816. if (parentNode && parentNode.className === 'highcharts-tracker' && !docMode8) {
  3817. css(element, { visibility: HIDDEN });
  3818. }
  3819. return wrapper.css(clipRect.getCSS(wrapper));
  3820. },
  3821. /**
  3822. * Set styles for the element
  3823. * @param {Object} styles
  3824. */
  3825. css: SVGElement.prototype.htmlCss,
  3826. /**
  3827. * Removes a child either by removeChild or move to garbageBin.
  3828. * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
  3829. */
  3830. safeRemoveChild: function (element) {
  3831. // discardElement will detach the node from its parent before attaching it
  3832. // to the garbage bin. Therefore it is important that the node is attached and have parent.
  3833. var parentNode = element.parentNode;
  3834. if (parentNode) {
  3835. discardElement(element);
  3836. }
  3837. },
  3838. /**
  3839. * Extend element.destroy by removing it from the clip members array
  3840. */
  3841. destroy: function () {
  3842. var wrapper = this;
  3843. if (wrapper.destroyClip) {
  3844. wrapper.destroyClip();
  3845. }
  3846. return SVGElement.prototype.destroy.apply(wrapper);
  3847. },
  3848. /**
  3849. * Remove all child nodes of a group, except the v:group element
  3850. */
  3851. empty: function () {
  3852. var element = this.element,
  3853. childNodes = element.childNodes,
  3854. i = childNodes.length,
  3855. node;
  3856. while (i--) {
  3857. node = childNodes[i];
  3858. node.parentNode.removeChild(node);
  3859. }
  3860. },
  3861. /**
  3862. * Add an event listener. VML override for normalizing event parameters.
  3863. * @param {String} eventType
  3864. * @param {Function} handler
  3865. */
  3866. on: function (eventType, handler) {
  3867. // simplest possible event model for internal use
  3868. this.element['on' + eventType] = function () {
  3869. var evt = win.event;
  3870. evt.target = evt.srcElement;
  3871. handler(evt);
  3872. };
  3873. return this;
  3874. },
  3875. /**
  3876. * In stacked columns, cut off the shadows so that they don't overlap
  3877. */
  3878. cutOffPath: function (path, length) {
  3879. var len;
  3880. path = path.split(/[ ,]/);
  3881. len = path.length;
  3882. if (len === 9 || len === 11) {
  3883. path[len - 4] = path[len - 2] = pInt(path[len - 2]) - 10 * length;
  3884. }
  3885. return path.join(' ');
  3886. },
  3887. /**
  3888. * Apply a drop shadow by copying elements and giving them different strokes
  3889. * @param {Boolean} apply
  3890. */
  3891. shadow: function (apply, group, cutOff) {
  3892. var shadows = [],
  3893. i,
  3894. element = this.element,
  3895. renderer = this.renderer,
  3896. shadow,
  3897. elemStyle = element.style,
  3898. markup,
  3899. path = element.path,
  3900. strokeWidth,
  3901. modifiedPath;
  3902. // some times empty paths are not strings
  3903. if (path && typeof path.value !== 'string') {
  3904. path = 'x';
  3905. }
  3906. modifiedPath = path;
  3907. if (apply) {
  3908. for (i = 1; i <= 3; i++) {
  3909. strokeWidth = 7 - 2 * i;
  3910. // Cut off shadows for stacked column items
  3911. if (cutOff) {
  3912. modifiedPath = this.cutOffPath(path.value, strokeWidth + 0.5);
  3913. }
  3914. markup = ['<shape isShadow="true" strokeweight="', (7 - 2 * i),
  3915. '" filled="false" path="', modifiedPath,
  3916. '" coordsize="10 10" style="', element.style.cssText, '" />'];
  3917. shadow = createElement(renderer.prepVML(markup),
  3918. null, {
  3919. left: pInt(elemStyle.left) + 1,
  3920. top: pInt(elemStyle.top) + 1
  3921. }
  3922. );
  3923. if (cutOff) {
  3924. shadow.cutOff = strokeWidth + 1;
  3925. }
  3926. // apply the opacity
  3927. markup = ['<stroke color="black" opacity="', (0.05 * i), '"/>'];
  3928. createElement(renderer.prepVML(markup), null, null, shadow);
  3929. // insert it
  3930. if (group) {
  3931. group.element.appendChild(shadow);
  3932. } else {
  3933. element.parentNode.insertBefore(shadow, element);
  3934. }
  3935. // record it
  3936. shadows.push(shadow);
  3937. }
  3938. this.shadows = shadows;
  3939. }
  3940. return this;
  3941. }
  3942. };
  3943. VMLElement = extendClass(SVGElement, VMLElement);
  3944. /**
  3945. * The VML renderer
  3946. */
  3947. var VMLRendererExtension = { // inherit SVGRenderer
  3948. Element: VMLElement,
  3949. isIE8: userAgent.indexOf('MSIE 8.0') > -1,
  3950. /**
  3951. * Initialize the VMLRenderer
  3952. * @param {Object} container
  3953. * @param {Number} width
  3954. * @param {Number} height
  3955. */
  3956. init: function (container, width, height) {
  3957. var renderer = this,
  3958. boxWrapper,
  3959. box;
  3960. renderer.alignedObjects = [];
  3961. boxWrapper = renderer.createElement(DIV);
  3962. box = boxWrapper.element;
  3963. box.style.position = RELATIVE; // for freeform drawing using renderer directly
  3964. container.appendChild(boxWrapper.element);
  3965. // generate the containing box
  3966. renderer.box = box;
  3967. renderer.boxWrapper = boxWrapper;
  3968. renderer.setSize(width, height, false);
  3969. // The only way to make IE6 and IE7 print is to use a global namespace. However,
  3970. // with IE8 the only way to make the dynamic shapes visible in screen and print mode
  3971. // seems to be to add the xmlns attribute and the behaviour style inline.
  3972. if (!doc.namespaces.hcv) {
  3973. doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml');
  3974. // setup default css
  3975. doc.createStyleSheet().cssText =
  3976. 'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke' +
  3977. '{ behavior:url(#default#VML); display: inline-block; } ';
  3978. }
  3979. },
  3980. /**
  3981. * Detect whether the renderer is hidden. This happens when one of the parent elements
  3982. * has display: none
  3983. */
  3984. isHidden: function () {
  3985. return !this.box.offsetWidth;
  3986. },
  3987. /**
  3988. * Define a clipping rectangle. In VML it is accomplished by storing the values
  3989. * for setting the CSS style to all associated members.
  3990. *
  3991. * @param {Number} x
  3992. * @param {Number} y
  3993. * @param {Number} width
  3994. * @param {Number} height
  3995. */
  3996. clipRect: function (x, y, width, height) {
  3997. // create a dummy element
  3998. var clipRect = this.createElement();
  3999. // mimic a rectangle with its style object for automatic updating in attr
  4000. return extend(clipRect, {
  4001. members: [],
  4002. left: x,
  4003. top: y,
  4004. width: width,
  4005. height: height,
  4006. getCSS: function (wrapper) {
  4007. var inverted = wrapper.inverted,
  4008. rect = this,
  4009. top = rect.top,
  4010. left = rect.left,
  4011. right = left + rect.width,
  4012. bottom = top + rect.height,
  4013. ret = {
  4014. clip: 'rect(' +
  4015. mathRound(inverted ? left : top) + 'px,' +
  4016. mathRound(inverted ? bottom : right) + 'px,' +
  4017. mathRound(inverted ? right : bottom) + 'px,' +
  4018. mathRound(inverted ? top : left) + 'px)'
  4019. };
  4020. // issue 74 workaround
  4021. if (!inverted && docMode8 && wrapper.element.nodeName !== 'IMG') {
  4022. extend(ret, {
  4023. width: right + PX,
  4024. height: bottom + PX
  4025. });
  4026. }
  4027. return ret;
  4028. },
  4029. // used in attr and animation to update the clipping of all members
  4030. updateClipping: function () {
  4031. each(clipRect.members, function (member) {
  4032. member.css(clipRect.getCSS(member));
  4033. });
  4034. }
  4035. });
  4036. },
  4037. /**
  4038. * Take a color and return it if it's a string, make it a gradient if it's a
  4039. * gradient configuration object, and apply opacity.
  4040. *
  4041. * @param {Object} color The color or config object
  4042. */
  4043. color: function (color, elem, prop) {
  4044. var colorObject,
  4045. regexRgba = /^rgba/,
  4046. markup,
  4047. fillType,
  4048. ret = NONE;
  4049. // Check for linear or radial gradient
  4050. if (color && color.linearGradient) {
  4051. fillType = 'gradient';
  4052. } else if (color && color.radialGradient) {
  4053. fillType = 'pattern';
  4054. }
  4055. if (fillType) {
  4056. var stopColor,
  4057. stopOpacity,
  4058. gradient = color.linearGradient || color.radialGradient,
  4059. x1,
  4060. y1,
  4061. x2,
  4062. y2,
  4063. angle,
  4064. opacity1,
  4065. opacity2,
  4066. color1,
  4067. color2,
  4068. fillAttr = '',
  4069. stops = color.stops,
  4070. firstStop,
  4071. lastStop,
  4072. colors = [];
  4073. // Extend from 0 to 1
  4074. firstStop = stops[0];
  4075. lastStop = stops[stops.length - 1];
  4076. if (firstStop[0] > 0) {
  4077. stops.unshift([
  4078. 0,
  4079. firstStop[1]
  4080. ]);
  4081. }
  4082. if (lastStop[0] < 1) {
  4083. stops.push([
  4084. 1,
  4085. lastStop[1]
  4086. ]);
  4087. }
  4088. // Compute the stops
  4089. each(stops, function (stop, i) {
  4090. if (regexRgba.test(stop[1])) {
  4091. colorObject = Color(stop[1]);
  4092. stopColor = colorObject.get('rgb');
  4093. stopOpacity = colorObject.get('a');
  4094. } else {
  4095. stopColor = stop[1];
  4096. stopOpacity = 1;
  4097. }
  4098. // Build the color attribute
  4099. colors.push((stop[0] * 100) + '% ' + stopColor);
  4100. // Only start and end opacities are allowed, so we use the first and the last
  4101. if (!i) {
  4102. opacity1 = stopOpacity;
  4103. color2 = stopColor;
  4104. } else {
  4105. opacity2 = stopOpacity;
  4106. color1 = stopColor;
  4107. }
  4108. });
  4109. // Handle linear gradient angle
  4110. if (fillType === 'gradient') {
  4111. x1 = gradient.x1 || gradient[0] || 0;
  4112. y1 = gradient.y1 || gradient[1] || 0;
  4113. x2 = gradient.x2 || gradient[2] || 0;
  4114. y2 = gradient.y2 || gradient[3] || 0;
  4115. angle = 90 - math.atan(
  4116. (y2 - y1) / // y vector
  4117. (x2 - x1) // x vector
  4118. ) * 180 / mathPI;
  4119. // Radial (circular) gradient
  4120. } else {
  4121. // pie: http://jsfiddle.net/highcharts/66g8H/
  4122. // reference: http://jsfiddle.net/highcharts/etznJ/
  4123. // http://jsfiddle.net/highcharts/XRbCc/
  4124. // http://jsfiddle.net/highcharts/F3fwR/
  4125. // TODO:
  4126. // - correct for radialRefeence
  4127. // - check whether gradient stops are supported
  4128. // - add global option for gradient image (must relate to version)
  4129. var r = gradient.r,
  4130. size = r * 2,
  4131. cx = gradient.cx,
  4132. cy = gradient.cy;
  4133. //radialReference = elem.radialReference;
  4134. //if (radialReference) {
  4135. // Try setting pixel size, or other way to adjust the gradient size to the bounding box
  4136. //}
  4137. fillAttr = 'src="http://code.highcharts.com/gfx/radial-gradient.png" ' +
  4138. 'size="' + size + ',' + size + '" ' +
  4139. 'origin="0.5,0.5" ' +
  4140. 'position="' + cx + ',' + cy + '" ' +
  4141. 'color2="' + color2 + '" ';
  4142. // The fill element's color attribute is broken in IE8 standards mode, so we
  4143. // need to set the parent shape's fillcolor attribute instead.
  4144. ret = color1;
  4145. }
  4146. // Apply the gradient to fills only.
  4147. if (prop === 'fill') {
  4148. // when colors attribute is used, the meanings of opacity and o:opacity2
  4149. // are reversed.
  4150. markup = ['<fill colors="' + colors.join(',') + '" angle="', angle,
  4151. '" opacity="', opacity2, '" o:opacity2="', opacity1,
  4152. '" type="', fillType, '" ', fillAttr, 'focus="100%" method="any" />'];
  4153. createElement(this.prepVML(markup), null, null, elem);
  4154. // Gradients are not supported for VML stroke, return the first color. #722.
  4155. } else {
  4156. ret = stopColor;
  4157. }
  4158. // if the color is an rgba color, split it and add a fill node
  4159. // to hold the opacity component
  4160. } else if (regexRgba.test(color) && elem.tagName !== 'IMG') {
  4161. colorObject = Color(color);
  4162. markup = ['<', prop, ' opacity="', colorObject.get('a'), '"/>'];
  4163. createElement(this.prepVML(markup), null, null, elem);
  4164. ret = colorObject.get('rgb');
  4165. } else {
  4166. var strokeNodes = elem.getElementsByTagName(prop);
  4167. if (strokeNodes.length) {
  4168. strokeNodes[0].opacity = 1;
  4169. }
  4170. ret = color;
  4171. }
  4172. return ret;
  4173. },
  4174. /**
  4175. * Take a VML string and prepare it for either IE8 or IE6/IE7.
  4176. * @param {Array} markup A string array of the VML markup to prepare
  4177. */
  4178. prepVML: function (markup) {
  4179. var vmlStyle = 'display:inline-block;behavior:url(#default#VML);',
  4180. isIE8 = this.isIE8;
  4181. markup = markup.join('');
  4182. if (isIE8) { // add xmlns and style inline
  4183. markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />');
  4184. if (markup.indexOf('style="') === -1) {
  4185. markup = markup.replace('/>', ' style="' + vmlStyle + '" />');
  4186. } else {
  4187. markup = markup.replace('style="', 'style="' + vmlStyle);
  4188. }
  4189. } else { // add namespace
  4190. markup = markup.replace('<', '<hcv:');
  4191. }
  4192. return markup;
  4193. },
  4194. /**
  4195. * Create rotated and aligned text
  4196. * @param {String} str
  4197. * @param {Number} x
  4198. * @param {Number} y
  4199. */
  4200. text: SVGRenderer.prototype.html,
  4201. /**
  4202. * Create and return a path element
  4203. * @param {Array} path
  4204. */
  4205. path: function (path) {
  4206. var attr = {
  4207. // subpixel precision down to 0.1 (width and height = 1px)
  4208. coordsize: '10 10'
  4209. };
  4210. if (isArray(path)) {
  4211. attr.d = path;
  4212. } else if (isObject(path)) { // attributes
  4213. extend(attr, path);
  4214. }
  4215. // create the shape
  4216. return this.createElement('shape').attr(attr);
  4217. },
  4218. /**
  4219. * Create and return a circle element. In VML circles are implemented as
  4220. * shapes, which is faster than v:oval
  4221. * @param {Number} x
  4222. * @param {Number} y
  4223. * @param {Number} r
  4224. */
  4225. circle: function (x, y, r) {
  4226. return this.symbol('circle').attr({ x: x - r, y: y - r, width: 2 * r, height: 2 * r });
  4227. },
  4228. /**
  4229. * Create a group using an outer div and an inner v:group to allow rotating
  4230. * and flipping. A simple v:group would have problems with positioning
  4231. * child HTML elements and CSS clip.
  4232. *
  4233. * @param {String} name The name of the group
  4234. */
  4235. g: function (name) {
  4236. var wrapper,
  4237. attribs;
  4238. // set the class name
  4239. if (name) {
  4240. attribs = { 'className': PREFIX + name, 'class': PREFIX + name };
  4241. }
  4242. // the div to hold HTML and clipping
  4243. wrapper = this.createElement(DIV).attr(attribs);
  4244. return wrapper;
  4245. },
  4246. /**
  4247. * VML override to create a regular HTML image
  4248. * @param {String} src
  4249. * @param {Number} x
  4250. * @param {Number} y
  4251. * @param {Number} width
  4252. * @param {Number} height
  4253. */
  4254. image: function (src, x, y, width, height) {
  4255. var obj = this.createElement('img')
  4256. .attr({ src: src });
  4257. if (arguments.length > 1) {
  4258. obj.css({
  4259. left: x,
  4260. top: y,
  4261. width: width,
  4262. height: height
  4263. });
  4264. }
  4265. return obj;
  4266. },
  4267. /**
  4268. * VML uses a shape for rect to overcome bugs and rotation problems
  4269. */
  4270. rect: function (x, y, width, height, r, strokeWidth) {
  4271. if (isObject(x)) {
  4272. y = x.y;
  4273. width = x.width;
  4274. height = x.height;
  4275. strokeWidth = x.strokeWidth;
  4276. x = x.x;
  4277. }
  4278. var wrapper = this.symbol('rect');
  4279. wrapper.r = r;
  4280. return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
  4281. },
  4282. /**
  4283. * In the VML renderer, each child of an inverted div (group) is inverted
  4284. * @param {Object} element
  4285. * @param {Object} parentNode
  4286. */
  4287. invertChild: function (element, parentNode) {
  4288. var parentStyle = parentNode.style;
  4289. css(element, {
  4290. flip: 'x',
  4291. left: pInt(parentStyle.width) - 1,
  4292. top: pInt(parentStyle.height) - 1,
  4293. rotation: -90
  4294. });
  4295. },
  4296. /**
  4297. * Symbol definitions that override the parent SVG renderer's symbols
  4298. *
  4299. */
  4300. symbols: {
  4301. // VML specific arc function
  4302. arc: function (x, y, w, h, options) {
  4303. var start = options.start,
  4304. end = options.end,
  4305. radius = options.r || w || h,
  4306. cosStart = mathCos(start),
  4307. sinStart = mathSin(start),
  4308. cosEnd = mathCos(end),
  4309. sinEnd = mathSin(end),
  4310. innerRadius = options.innerR,
  4311. circleCorrection = 0.08 / radius, // #760
  4312. innerCorrection = (innerRadius && 0.1 / innerRadius) || 0,
  4313. ret;
  4314. if (end - start === 0) { // no angle, don't show it.
  4315. return ['x'];
  4316. } else if (2 * mathPI - end + start < circleCorrection) { // full circle
  4317. // empirical correction found by trying out the limits for different radii
  4318. cosEnd = -circleCorrection;
  4319. } else if (end - start < innerCorrection) { // issue #186, another mysterious VML arc problem
  4320. cosEnd = mathCos(start + innerCorrection);
  4321. }
  4322. ret = [
  4323. 'wa', // clockwise arc to
  4324. x - radius, // left
  4325. y - radius, // top
  4326. x + radius, // right
  4327. y + radius, // bottom
  4328. x + radius * cosStart, // start x
  4329. y + radius * sinStart, // start y
  4330. x + radius * cosEnd, // end x
  4331. y + radius * sinEnd // end y
  4332. ];
  4333. if (options.open) {
  4334. ret.push(
  4335. M,
  4336. x - innerRadius,
  4337. y - innerRadius
  4338. );
  4339. }
  4340. ret.push(
  4341. 'at', // anti clockwise arc to
  4342. x - innerRadius, // left
  4343. y - innerRadius, // top
  4344. x + innerRadius, // right
  4345. y + innerRadius, // bottom
  4346. x + innerRadius * cosEnd, // start x
  4347. y + innerRadius * sinEnd, // start y
  4348. x + innerRadius * cosStart, // end x
  4349. y + innerRadius * sinStart, // end y
  4350. 'x', // finish path
  4351. 'e' // close
  4352. );
  4353. return ret;
  4354. },
  4355. // Add circle symbol path. This performs significantly faster than v:oval.
  4356. circle: function (x, y, w, h) {
  4357. return [
  4358. 'wa', // clockwisearcto
  4359. x, // left
  4360. y, // top
  4361. x + w, // right
  4362. y + h, // bottom
  4363. x + w, // start x
  4364. y + h / 2, // start y
  4365. x + w, // end x
  4366. y + h / 2, // end y
  4367. //'x', // finish path
  4368. 'e' // close
  4369. ];
  4370. },
  4371. /**
  4372. * Add rectangle symbol path which eases rotation and omits arcsize problems
  4373. * compared to the built-in VML roundrect shape
  4374. *
  4375. * @param {Number} left Left position
  4376. * @param {Number} top Top position
  4377. * @param {Number} r Border radius
  4378. * @param {Object} options Width and height
  4379. */
  4380. rect: function (left, top, width, height, options) {
  4381. var right = left + width,
  4382. bottom = top + height,
  4383. ret,
  4384. r;
  4385. // No radius, return the more lightweight square
  4386. if (!defined(options) || !options.r) {
  4387. ret = SVGRenderer.prototype.symbols.square.apply(0, arguments);
  4388. // Has radius add arcs for the corners
  4389. } else {
  4390. r = mathMin(options.r, width, height);
  4391. ret = [
  4392. M,
  4393. left + r, top,
  4394. L,
  4395. right - r, top,
  4396. 'wa',
  4397. right - 2 * r, top,
  4398. right, top + 2 * r,
  4399. right - r, top,
  4400. right, top + r,
  4401. L,
  4402. right, bottom - r,
  4403. 'wa',
  4404. right - 2 * r, bottom - 2 * r,
  4405. right, bottom,
  4406. right, bottom - r,
  4407. right - r, bottom,
  4408. L,
  4409. left + r, bottom,
  4410. 'wa',
  4411. left, bottom - 2 * r,
  4412. left + 2 * r, bottom,
  4413. left + r, bottom,
  4414. left, bottom - r,
  4415. L,
  4416. left, top + r,
  4417. 'wa',
  4418. left, top,
  4419. left + 2 * r, top + 2 * r,
  4420. left, top + r,
  4421. left + r, top,
  4422. 'x',
  4423. 'e'
  4424. ];
  4425. }
  4426. return ret;
  4427. }
  4428. }
  4429. };
  4430. VMLRenderer = function () {
  4431. this.init.apply(this, arguments);
  4432. };
  4433. VMLRenderer.prototype = merge(SVGRenderer.prototype, VMLRendererExtension);
  4434. // general renderer
  4435. Renderer = VMLRenderer;
  4436. }
  4437. /* ****************************************************************************
  4438. * *
  4439. * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE *
  4440. * *
  4441. *****************************************************************************/
  4442. /* ****************************************************************************
  4443. * *
  4444. * START OF ANDROID < 3 SPECIFIC CODE. THIS CAN BE REMOVED IF YOU'RE NOT *
  4445. * TARGETING THAT SYSTEM. *
  4446. * *
  4447. *****************************************************************************/
  4448. var CanVGRenderer,
  4449. CanVGController;
  4450. if (useCanVG) {
  4451. /**
  4452. * The CanVGRenderer is empty from start to keep the source footprint small.
  4453. * When requested, the CanVGController downloads the rest of the source packaged
  4454. * together with the canvg library.
  4455. */
  4456. CanVGRenderer = function () {
  4457. // Empty constructor
  4458. };
  4459. /**
  4460. * Start with an empty symbols object. This is needed when exporting is used (exporting.src.js will add a few symbols), but
  4461. * the implementation from SvgRenderer will not be merged in until first render.
  4462. */
  4463. CanVGRenderer.prototype.symbols = {};
  4464. /**
  4465. * Handles on demand download of canvg rendering support.
  4466. */
  4467. CanVGController = (function () {
  4468. // List of renderering calls
  4469. var deferredRenderCalls = [];
  4470. /**
  4471. * When downloaded, we are ready to draw deferred charts.
  4472. */
  4473. function drawDeferred() {
  4474. var callLength = deferredRenderCalls.length,
  4475. callIndex;
  4476. // Draw all pending render calls
  4477. for (callIndex = 0; callIndex < callLength; callIndex++) {
  4478. deferredRenderCalls[callIndex]();
  4479. }
  4480. // Clear the list
  4481. deferredRenderCalls = [];
  4482. }
  4483. return {
  4484. push: function (func, scriptLocation) {
  4485. // Only get the script once
  4486. if (deferredRenderCalls.length === 0) {
  4487. getScript(scriptLocation, drawDeferred);
  4488. }
  4489. // Register render call
  4490. deferredRenderCalls.push(func);
  4491. }
  4492. };
  4493. }());
  4494. } // end CanVGRenderer
  4495. /* ****************************************************************************
  4496. * *
  4497. * END OF ANDROID < 3 SPECIFIC CODE *
  4498. * *
  4499. *****************************************************************************/
  4500. /**
  4501. * General renderer
  4502. */
  4503. Renderer = VMLRenderer || CanVGRenderer || SVGRenderer;
  4504. /**
  4505. * The Tick class
  4506. */
  4507. function Tick(axis, pos, type) {
  4508. this.axis = axis;
  4509. this.pos = pos;
  4510. this.type = type || '';
  4511. this.isNew = true;
  4512. if (!type) {
  4513. this.addLabel();
  4514. }
  4515. }
  4516. Tick.prototype = {
  4517. /**
  4518. * Write the tick label
  4519. */
  4520. addLabel: function () {
  4521. var tick = this,
  4522. axis = tick.axis,
  4523. options = axis.options,
  4524. chart = axis.chart,
  4525. horiz = axis.horiz,
  4526. categories = axis.categories,
  4527. pos = tick.pos,
  4528. labelOptions = options.labels,
  4529. str,
  4530. tickPositions = axis.tickPositions,
  4531. width = (categories && horiz && categories.length &&
  4532. !labelOptions.step && !labelOptions.staggerLines &&
  4533. !labelOptions.rotation &&
  4534. chart.plotWidth / tickPositions.length) ||
  4535. (!horiz && chart.plotWidth / 2),
  4536. isFirst = pos === tickPositions[0],
  4537. isLast = pos === tickPositions[tickPositions.length - 1],
  4538. css,
  4539. attr,
  4540. value = categories && defined(categories[pos]) ? categories[pos] : pos,
  4541. label = tick.label,
  4542. tickPositionInfo = tickPositions.info,
  4543. dateTimeLabelFormat;
  4544. // Set the datetime label format. If a higher rank is set for this position, use that. If not,
  4545. // use the general format.
  4546. if (axis.isDatetimeAxis && tickPositionInfo) {
  4547. dateTimeLabelFormat = options.dateTimeLabelFormats[tickPositionInfo.higherRanks[pos] || tickPositionInfo.unitName];
  4548. }
  4549. // set properties for access in render method
  4550. tick.isFirst = isFirst;
  4551. tick.isLast = isLast;
  4552. // get the string
  4553. str = axis.labelFormatter.call({
  4554. axis: axis,
  4555. chart: chart,
  4556. isFirst: isFirst,
  4557. isLast: isLast,
  4558. dateTimeLabelFormat: dateTimeLabelFormat,
  4559. value: axis.isLog ? correctFloat(lin2log(value)) : value
  4560. });
  4561. // prepare CSS
  4562. css = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) + PX };
  4563. css = extend(css, labelOptions.style);
  4564. // first call
  4565. if (!defined(label)) {
  4566. attr = {
  4567. align: labelOptions.align
  4568. };
  4569. if (isNumber(labelOptions.rotation)) {
  4570. attr.rotation = labelOptions.rotation;
  4571. }
  4572. tick.label =
  4573. defined(str) && labelOptions.enabled ?
  4574. chart.renderer.text(
  4575. str,
  4576. 0,
  4577. 0,
  4578. labelOptions.useHTML
  4579. )
  4580. .attr(attr)
  4581. // without position absolute, IE export sometimes is wrong
  4582. .css(css)
  4583. .add(axis.axisGroup) :
  4584. null;
  4585. // update
  4586. } else if (label) {
  4587. label.attr({
  4588. text: str
  4589. })
  4590. .css(css);
  4591. }
  4592. },
  4593. /**
  4594. * Get the offset height or width of the label
  4595. */
  4596. getLabelSize: function () {
  4597. var label = this.label,
  4598. axis = this.axis;
  4599. return label ?
  4600. ((this.labelBBox = label.getBBox(true)))[axis.horiz ? 'height' : 'width'] :
  4601. 0;
  4602. },
  4603. /**
  4604. * Find how far the labels extend to the right and left of the tick's x position. Used for anti-collision
  4605. * detection with overflow logic.
  4606. */
  4607. getLabelSides: function () {
  4608. var bBox = this.labelBBox, // assume getLabelSize has run at this point
  4609. axis = this.axis,
  4610. options = axis.options,
  4611. labelOptions = options.labels,
  4612. width = bBox.width,
  4613. leftSide = width * { left: 0, center: 0.5, right: 1 }[labelOptions.align] - labelOptions.x;
  4614. return [-leftSide, width - leftSide];
  4615. },
  4616. /**
  4617. * Handle the label overflow by adjusting the labels to the left and right edge, or
  4618. * hide them if they collide into the neighbour label.
  4619. */
  4620. handleOverflow: function (index, xy) {
  4621. var show = true,
  4622. axis = this.axis,
  4623. chart = axis.chart,
  4624. isFirst = this.isFirst,
  4625. isLast = this.isLast,
  4626. x = xy.x,
  4627. reversed = axis.reversed,
  4628. tickPositions = axis.tickPositions;
  4629. if (isFirst || isLast) {
  4630. var sides = this.getLabelSides(),
  4631. leftSide = sides[0],
  4632. rightSide = sides[1],
  4633. plotLeft = chart.plotLeft,
  4634. plotRight = plotLeft + axis.len,
  4635. neighbour = axis.ticks[tickPositions[index + (isFirst ? 1 : -1)]],
  4636. neighbourEdge = neighbour && neighbour.label.xy.x + neighbour.getLabelSides()[isFirst ? 0 : 1];
  4637. if ((isFirst && !reversed) || (isLast && reversed)) {
  4638. // Is the label spilling out to the left of the plot area?
  4639. if (x + leftSide < plotLeft) {
  4640. // Align it to plot left
  4641. x = plotLeft - leftSide;
  4642. // Hide it if it now overlaps the neighbour label
  4643. if (neighbour && x + rightSide > neighbourEdge) {
  4644. show = false;
  4645. }
  4646. }
  4647. } else {
  4648. // Is the label spilling out to the right of the plot area?
  4649. if (x + rightSide > plotRight) {
  4650. // Align it to plot right
  4651. x = plotRight - rightSide;
  4652. // Hide it if it now overlaps the neighbour label
  4653. if (neighbour && x + leftSide < neighbourEdge) {
  4654. show = false;
  4655. }
  4656. }
  4657. }
  4658. // Set the modified x position of the label
  4659. xy.x = x;
  4660. }
  4661. return show;
  4662. },
  4663. /**
  4664. * Get the x and y position for ticks and labels
  4665. */
  4666. getPosition: function (horiz, pos, tickmarkOffset, old) {
  4667. var axis = this.axis,
  4668. chart = axis.chart,
  4669. cHeight = (old && chart.oldChartHeight) || chart.chartHeight;
  4670. return {
  4671. x: horiz ?
  4672. axis.translate(pos + tickmarkOffset, null, null, old) + axis.transB :
  4673. axis.left + axis.offset + (axis.opposite ? ((old && chart.oldChartWidth) || chart.chartWidth) - axis.right - axis.left : 0),
  4674. y: horiz ?
  4675. cHeight - axis.bottom + axis.offset - (axis.opposite ? axis.height : 0) :
  4676. cHeight - axis.translate(pos + tickmarkOffset, null, null, old) - axis.transB
  4677. };
  4678. },
  4679. /**
  4680. * Get the x, y position of the tick label
  4681. */
  4682. getLabelPosition: function (x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
  4683. var axis = this.axis,
  4684. transA = axis.transA,
  4685. reversed = axis.reversed,
  4686. staggerLines = axis.staggerLines;
  4687. x = x + labelOptions.x - (tickmarkOffset && horiz ?
  4688. tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
  4689. y = y + labelOptions.y - (tickmarkOffset && !horiz ?
  4690. tickmarkOffset * transA * (reversed ? 1 : -1) : 0);
  4691. // Vertically centered
  4692. if (!defined(labelOptions.y)) {
  4693. y += pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;
  4694. }
  4695. // Correct for staggered labels
  4696. if (staggerLines) {
  4697. y += (index / (step || 1) % staggerLines) * 16;
  4698. }
  4699. return {
  4700. x: x,
  4701. y: y
  4702. };
  4703. },
  4704. /**
  4705. * Extendible method to return the path of the marker
  4706. */
  4707. getMarkPath: function (x, y, tickLength, tickWidth, horiz, renderer) {
  4708. return renderer.crispLine([
  4709. M,
  4710. x,
  4711. y,
  4712. L,
  4713. x + (horiz ? 0 : -tickLength),
  4714. y + (horiz ? tickLength : 0)
  4715. ], tickWidth);
  4716. },
  4717. /**
  4718. * Put everything in place
  4719. *
  4720. * @param index {Number}
  4721. * @param old {Boolean} Use old coordinates to prepare an animation into new position
  4722. */
  4723. render: function (index, old) {
  4724. var tick = this,
  4725. axis = tick.axis,
  4726. options = axis.options,
  4727. chart = axis.chart,
  4728. renderer = chart.renderer,
  4729. horiz = axis.horiz,
  4730. type = tick.type,
  4731. label = tick.label,
  4732. pos = tick.pos,
  4733. labelOptions = options.labels,
  4734. gridLine = tick.gridLine,
  4735. gridPrefix = type ? type + 'Grid' : 'grid',
  4736. tickPrefix = type ? type + 'Tick' : 'tick',
  4737. gridLineWidth = options[gridPrefix + 'LineWidth'],
  4738. gridLineColor = options[gridPrefix + 'LineColor'],
  4739. dashStyle = options[gridPrefix + 'LineDashStyle'],
  4740. tickLength = options[tickPrefix + 'Length'],
  4741. tickWidth = options[tickPrefix + 'Width'] || 0,
  4742. tickColor = options[tickPrefix + 'Color'],
  4743. tickPosition = options[tickPrefix + 'Position'],
  4744. gridLinePath,
  4745. mark = tick.mark,
  4746. markPath,
  4747. step = labelOptions.step,
  4748. attribs,
  4749. show = true,
  4750. tickmarkOffset = (options.categories && options.tickmarkPlacement === 'between') ? 0.5 : 0,
  4751. xy = tick.getPosition(horiz, pos, tickmarkOffset, old),
  4752. x = xy.x,
  4753. y = xy.y,
  4754. staggerLines = axis.staggerLines;
  4755. // create the grid line
  4756. if (gridLineWidth) {
  4757. gridLinePath = axis.getPlotLinePath(pos + tickmarkOffset, gridLineWidth, old);
  4758. if (gridLine === UNDEFINED) {
  4759. attribs = {
  4760. stroke: gridLineColor,
  4761. 'stroke-width': gridLineWidth
  4762. };
  4763. if (dashStyle) {
  4764. attribs.dashstyle = dashStyle;
  4765. }
  4766. if (!type) {
  4767. attribs.zIndex = 1;
  4768. }
  4769. tick.gridLine = gridLine =
  4770. gridLineWidth ?
  4771. renderer.path(gridLinePath)
  4772. .attr(attribs).add(axis.gridGroup) :
  4773. null;
  4774. }
  4775. // If the parameter 'old' is set, the current call will be followed
  4776. // by another call, therefore do not do any animations this time
  4777. if (!old && gridLine && gridLinePath) {
  4778. gridLine[tick.isNew ? 'attr' : 'animate']({
  4779. d: gridLinePath
  4780. });
  4781. }
  4782. }
  4783. // create the tick mark
  4784. if (tickWidth) {
  4785. // negate the length
  4786. if (tickPosition === 'inside') {
  4787. tickLength = -tickLength;
  4788. }
  4789. if (axis.opposite) {
  4790. tickLength = -tickLength;
  4791. }
  4792. markPath = tick.getMarkPath(x, y, tickLength, tickWidth, horiz, renderer);
  4793. if (mark) { // updating
  4794. mark.animate({
  4795. d: markPath
  4796. });
  4797. } else { // first time
  4798. tick.mark = renderer.path(
  4799. markPath
  4800. ).attr({
  4801. stroke: tickColor,
  4802. 'stroke-width': tickWidth
  4803. }).add(axis.axisGroup);
  4804. }
  4805. }
  4806. // the label is created on init - now move it into place
  4807. if (label && !isNaN(x)) {
  4808. label.xy = xy = tick.getLabelPosition(x, y, label, horiz, labelOptions, tickmarkOffset, index, step);
  4809. // apply show first and show last
  4810. if ((tick.isFirst && !pick(options.showFirstLabel, 1)) ||
  4811. (tick.isLast && !pick(options.showLastLabel, 1))) {
  4812. show = false;
  4813. // Handle label overflow and show or hide accordingly
  4814. } else if (!staggerLines && horiz && labelOptions.overflow === 'justify' && !tick.handleOverflow(index, xy)) {
  4815. show = false;
  4816. }
  4817. // apply step
  4818. if (step && index % step) {
  4819. // show those indices dividable by step
  4820. show = false;
  4821. }
  4822. // Set the new position, and show or hide
  4823. if (show) {
  4824. label[tick.isNew ? 'attr' : 'animate'](xy);
  4825. label.show();
  4826. tick.isNew = false;
  4827. } else {
  4828. label.hide();
  4829. }
  4830. }
  4831. },
  4832. /**
  4833. * Destructor for the tick prototype
  4834. */
  4835. destroy: function () {
  4836. destroyObjectProperties(this, this.axis);
  4837. }
  4838. };
  4839. /**
  4840. * The object wrapper for plot lines and plot bands
  4841. * @param {Object} options
  4842. */
  4843. function PlotLineOrBand(axis, options) {
  4844. this.axis = axis;
  4845. if (options) {
  4846. this.options = options;
  4847. this.id = options.id;
  4848. }
  4849. //plotLine.render()
  4850. return this;
  4851. }
  4852. PlotLineOrBand.prototype = {
  4853. /**
  4854. * Render the plot line or plot band. If it is already existing,
  4855. * move it.
  4856. */
  4857. render: function () {
  4858. var plotLine = this,
  4859. axis = plotLine.axis,
  4860. horiz = axis.horiz,
  4861. halfPointRange = (axis.pointRange || 0) / 2,
  4862. options = plotLine.options,
  4863. optionsLabel = options.label,
  4864. label = plotLine.label,
  4865. width = options.width,
  4866. to = options.to,
  4867. from = options.from,
  4868. isBand = defined(from) && defined(to),
  4869. value = options.value,
  4870. dashStyle = options.dashStyle,
  4871. svgElem = plotLine.svgElem,
  4872. path = [],
  4873. addEvent,
  4874. eventType,
  4875. xs,
  4876. ys,
  4877. x,
  4878. y,
  4879. color = options.color,
  4880. zIndex = options.zIndex,
  4881. events = options.events,
  4882. attribs,
  4883. renderer = axis.chart.renderer;
  4884. // logarithmic conversion
  4885. if (axis.isLog) {
  4886. from = log2lin(from);
  4887. to = log2lin(to);
  4888. value = log2lin(value);
  4889. }
  4890. // plot line
  4891. if (width) {
  4892. path = axis.getPlotLinePath(value, width);
  4893. attribs = {
  4894. stroke: color,
  4895. 'stroke-width': width
  4896. };
  4897. if (dashStyle) {
  4898. attribs.dashstyle = dashStyle;
  4899. }
  4900. } else if (isBand) { // plot band
  4901. // keep within plot area
  4902. from = mathMax(from, axis.min - halfPointRange);
  4903. to = mathMin(to, axis.max + halfPointRange);
  4904. path = axis.getPlotBandPath(from, to, options);
  4905. attribs = {
  4906. fill: color
  4907. };
  4908. if (options.borderWidth) {
  4909. attribs.stroke = options.borderColor;
  4910. attribs['stroke-width'] = options.borderWidth;
  4911. }
  4912. } else {
  4913. return;
  4914. }
  4915. // zIndex
  4916. if (defined(zIndex)) {
  4917. attribs.zIndex = zIndex;
  4918. }
  4919. // common for lines and bands
  4920. if (svgElem) {
  4921. if (path) {
  4922. svgElem.animate({
  4923. d: path
  4924. }, null, svgElem.onGetPath);
  4925. } else {
  4926. svgElem.hide();
  4927. svgElem.onGetPath = function () {
  4928. svgElem.show();
  4929. };
  4930. }
  4931. } else if (path && path.length) {
  4932. plotLine.svgElem = svgElem = renderer.path(path)
  4933. .attr(attribs).add();
  4934. // events
  4935. if (events) {
  4936. addEvent = function (eventType) {
  4937. svgElem.on(eventType, function (e) {
  4938. events[eventType].apply(plotLine, [e]);
  4939. });
  4940. };
  4941. for (eventType in events) {
  4942. addEvent(eventType);
  4943. }
  4944. }
  4945. }
  4946. // the plot band/line label
  4947. if (optionsLabel && defined(optionsLabel.text) && path && path.length && axis.width > 0 && axis.height > 0) {
  4948. // apply defaults
  4949. optionsLabel = merge({
  4950. align: horiz && isBand && 'center',
  4951. x: horiz ? !isBand && 4 : 10,
  4952. verticalAlign : !horiz && isBand && 'middle',
  4953. y: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4,
  4954. rotation: horiz && !isBand && 90
  4955. }, optionsLabel);
  4956. // add the SVG element
  4957. if (!label) {
  4958. plotLine.label = label = renderer.text(
  4959. optionsLabel.text,
  4960. 0,
  4961. 0
  4962. )
  4963. .attr({
  4964. align: optionsLabel.textAlign || optionsLabel.align,
  4965. rotation: optionsLabel.rotation,
  4966. zIndex: zIndex
  4967. })
  4968. .css(optionsLabel.style)
  4969. .add();
  4970. }
  4971. // get the bounding box and align the label
  4972. xs = [path[1], path[4], pick(path[6], path[1])];
  4973. ys = [path[2], path[5], pick(path[7], path[2])];
  4974. x = arrayMin(xs);
  4975. y = arrayMin(ys);
  4976. label.align(optionsLabel, false, {
  4977. x: x,
  4978. y: y,
  4979. width: arrayMax(xs) - x,
  4980. height: arrayMax(ys) - y
  4981. });
  4982. label.show();
  4983. } else if (label) { // move out of sight
  4984. label.hide();
  4985. }
  4986. // chainable
  4987. return plotLine;
  4988. },
  4989. /**
  4990. * Remove the plot line or band
  4991. */
  4992. destroy: function () {
  4993. var plotLine = this,
  4994. axis = plotLine.axis;
  4995. // remove it from the lookup
  4996. erase(axis.plotLinesAndBands, plotLine);
  4997. destroyObjectProperties(plotLine, this.axis);
  4998. }
  4999. };
  5000. /**
  5001. * The class for stack items
  5002. */
  5003. function StackItem(axis, options, isNegative, x, stackOption) {
  5004. var inverted = axis.chart.inverted;
  5005. this.axis = axis;
  5006. // Tells if the stack is negative
  5007. this.isNegative = isNegative;
  5008. // Save the options to be able to style the label
  5009. this.options = options;
  5010. // Save the x value to be able to position the label later
  5011. this.x = x;
  5012. // Save the stack option on the series configuration object
  5013. this.stack = stackOption;
  5014. // The align options and text align varies on whether the stack is negative and
  5015. // if the chart is inverted or not.
  5016. // First test the user supplied value, then use the dynamic.
  5017. this.alignOptions = {
  5018. align: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'),
  5019. verticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')),
  5020. y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)),
  5021. x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0)
  5022. };
  5023. this.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center');
  5024. }
  5025. StackItem.prototype = {
  5026. destroy: function () {
  5027. destroyObjectProperties(this, this.axis);
  5028. },
  5029. /**
  5030. * Sets the total of this stack. Should be called when a serie is hidden or shown
  5031. * since that will affect the total of other stacks.
  5032. */
  5033. setTotal: function (total) {
  5034. this.total = total;
  5035. this.cum = total;
  5036. },
  5037. /**
  5038. * Renders the stack total label and adds it to the stack label group.
  5039. */
  5040. render: function (group) {
  5041. var str = this.options.formatter.call(this); // format the text in the label
  5042. // Change the text to reflect the new total and set visibility to hidden in case the serie is hidden
  5043. if (this.label) {
  5044. this.label.attr({text: str, visibility: HIDDEN});
  5045. // Create new label
  5046. } else {
  5047. this.label =
  5048. this.axis.chart.renderer.text(str, 0, 0) // dummy positions, actual position updated with setOffset method in columnseries
  5049. .css(this.options.style) // apply style
  5050. .attr({align: this.textAlign, // fix the text-anchor
  5051. rotation: this.options.rotation, // rotation
  5052. visibility: HIDDEN }) // hidden until setOffset is called
  5053. .add(group); // add to the labels-group
  5054. }
  5055. },
  5056. /**
  5057. * Sets the offset that the stack has from the x value and repositions the label.
  5058. */
  5059. setOffset: function (xOffset, xWidth) {
  5060. var stackItem = this,
  5061. axis = stackItem.axis,
  5062. chart = axis.chart,
  5063. inverted = chart.inverted,
  5064. neg = this.isNegative, // special treatment is needed for negative stacks
  5065. y = axis.translate(this.total, 0, 0, 0, 1), // stack value translated mapped to chart coordinates
  5066. yZero = axis.translate(0), // stack origin
  5067. h = mathAbs(y - yZero), // stack height
  5068. x = chart.xAxis[0].translate(this.x) + xOffset, // stack x position
  5069. plotHeight = chart.plotHeight,
  5070. stackBox = { // this is the box for the complete stack
  5071. x: inverted ? (neg ? y : y - h) : x,
  5072. y: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y),
  5073. width: inverted ? h : xWidth,
  5074. height: inverted ? xWidth : h
  5075. };
  5076. if (this.label) {
  5077. this.label
  5078. .align(this.alignOptions, null, stackBox) // align the label to the box
  5079. .attr({visibility: VISIBLE}); // set visibility
  5080. }
  5081. }
  5082. };
  5083. /**
  5084. * Create a new axis object
  5085. * @param {Object} chart
  5086. * @param {Object} options
  5087. */
  5088. function Axis() {
  5089. this.init.apply(this, arguments);
  5090. }
  5091. Axis.prototype = {
  5092. /**
  5093. * Default options for the X axis - the Y axis has extended defaults
  5094. */
  5095. defaultOptions: {
  5096. // allowDecimals: null,
  5097. // alternateGridColor: null,
  5098. // categories: [],
  5099. dateTimeLabelFormats: {
  5100. millisecond: '%H:%M:%S.%L',
  5101. second: '%H:%M:%S',
  5102. minute: '%H:%M',
  5103. hour: '%H:%M',
  5104. day: '%e. %b',
  5105. week: '%e. %b',
  5106. month: '%b \'%y',
  5107. year: '%Y'
  5108. },
  5109. endOnTick: false,
  5110. gridLineColor: '#C0C0C0',
  5111. // gridLineDashStyle: 'solid',
  5112. // gridLineWidth: 0,
  5113. // reversed: false,
  5114. labels: defaultLabelOptions,
  5115. // { step: null },
  5116. lineColor: '#C0D0E0',
  5117. lineWidth: 1,
  5118. //linkedTo: null,
  5119. //max: undefined,
  5120. //min: undefined,
  5121. minPadding: 0.01,
  5122. maxPadding: 0.01,
  5123. //minRange: null,
  5124. minorGridLineColor: '#E0E0E0',
  5125. // minorGridLineDashStyle: null,
  5126. minorGridLineWidth: 1,
  5127. minorTickColor: '#A0A0A0',
  5128. //minorTickInterval: null,
  5129. minorTickLength: 2,
  5130. minorTickPosition: 'outside', // inside or outside
  5131. //minorTickWidth: 0,
  5132. //opposite: false,
  5133. //offset: 0,
  5134. //plotBands: [{
  5135. // events: {},
  5136. // zIndex: 1,
  5137. // labels: { align, x, verticalAlign, y, style, rotation, textAlign }
  5138. //}],
  5139. //plotLines: [{
  5140. // events: {}
  5141. // dashStyle: {}
  5142. // zIndex:
  5143. // labels: { align, x, verticalAlign, y, style, rotation, textAlign }
  5144. //}],
  5145. //reversed: false,
  5146. // showFirstLabel: true,
  5147. // showLastLabel: true,
  5148. startOfWeek: 1,
  5149. startOnTick: false,
  5150. tickColor: '#C0D0E0',
  5151. //tickInterval: null,
  5152. tickLength: 5,
  5153. tickmarkPlacement: 'between', // on or between
  5154. tickPixelInterval: 100,
  5155. tickPosition: 'outside',
  5156. tickWidth: 1,
  5157. title: {
  5158. //text: null,
  5159. align: 'middle', // low, middle or high
  5160. //margin: 0 for horizontal, 10 for vertical axes,
  5161. //rotation: 0,
  5162. //side: 'outside',
  5163. style: {
  5164. color: '#6D869F',
  5165. //font: defaultFont.replace('normal', 'bold')
  5166. fontWeight: 'bold'
  5167. }
  5168. //x: 0,
  5169. //y: 0
  5170. },
  5171. type: 'linear' // linear, logarithmic or datetime
  5172. },
  5173. /**
  5174. * This options set extends the defaultOptions for Y axes
  5175. */
  5176. defaultYAxisOptions: {
  5177. endOnTick: true,
  5178. gridLineWidth: 1,
  5179. tickPixelInterval: 72,
  5180. showLastLabel: true,
  5181. labels: {
  5182. align: 'right',
  5183. x: -8,
  5184. y: 3
  5185. },
  5186. lineWidth: 0,
  5187. maxPadding: 0.05,
  5188. minPadding: 0.05,
  5189. startOnTick: true,
  5190. tickWidth: 0,
  5191. title: {
  5192. rotation: 270,
  5193. text: 'Y-values'
  5194. },
  5195. stackLabels: {
  5196. enabled: false,
  5197. //align: dynamic,
  5198. //y: dynamic,
  5199. //x: dynamic,
  5200. //verticalAlign: dynamic,
  5201. //textAlign: dynamic,
  5202. //rotation: 0,
  5203. formatter: function () {
  5204. return this.total;
  5205. },
  5206. style: defaultLabelOptions.style
  5207. }
  5208. },
  5209. /**
  5210. * These options extend the defaultOptions for left axes
  5211. */
  5212. defaultLeftAxisOptions: {
  5213. labels: {
  5214. align: 'right',
  5215. x: -8,
  5216. y: null
  5217. },
  5218. title: {
  5219. rotation: 270
  5220. }
  5221. },
  5222. /**
  5223. * These options extend the defaultOptions for right axes
  5224. */
  5225. defaultRightAxisOptions: {
  5226. labels: {
  5227. align: 'left',
  5228. x: 8,
  5229. y: null
  5230. },
  5231. title: {
  5232. rotation: 90
  5233. }
  5234. },
  5235. /**
  5236. * These options extend the defaultOptions for bottom axes
  5237. */
  5238. defaultBottomAxisOptions: {
  5239. labels: {
  5240. align: 'center',
  5241. x: 0,
  5242. y: 14
  5243. // overflow: undefined,
  5244. // staggerLines: null
  5245. },
  5246. title: {
  5247. rotation: 0
  5248. }
  5249. },
  5250. /**
  5251. * These options extend the defaultOptions for left axes
  5252. */
  5253. defaultTopAxisOptions: {
  5254. labels: {
  5255. align: 'center',
  5256. x: 0,
  5257. y: -5
  5258. // overflow: undefined
  5259. // staggerLines: null
  5260. },
  5261. title: {
  5262. rotation: 0
  5263. }
  5264. },
  5265. /**
  5266. * Initialize the axis
  5267. */
  5268. init: function (chart, userOptions) {
  5269. var isXAxis = userOptions.isX,
  5270. axis = this;
  5271. // Flag, is the axis horizontal
  5272. axis.horiz = chart.inverted ? !isXAxis : isXAxis;
  5273. // Flag, isXAxis
  5274. axis.isXAxis = isXAxis;
  5275. axis.xOrY = isXAxis ? 'x' : 'y';
  5276. axis.opposite = userOptions.opposite; // needed in setOptions
  5277. axis.side = axis.horiz ?
  5278. (axis.opposite ? 0 : 2) : // top : bottom
  5279. (axis.opposite ? 1 : 3); // right : left
  5280. axis.setOptions(userOptions);
  5281. var options = this.options,
  5282. type = options.type,
  5283. isDatetimeAxis = type === 'datetime';
  5284. axis.labelFormatter = options.labels.formatter || axis.defaultLabelFormatter; // can be overwritten by dynamic format
  5285. // Flag, stagger lines or not
  5286. axis.staggerLines = axis.horiz && options.labels.staggerLines;
  5287. axis.userOptions = userOptions;
  5288. //axis.axisTitleMargin = UNDEFINED,// = options.title.margin,
  5289. axis.minPixelPadding = 0;
  5290. //axis.ignoreMinPadding = UNDEFINED; // can be set to true by a column or bar series
  5291. //axis.ignoreMaxPadding = UNDEFINED;
  5292. axis.chart = chart;
  5293. axis.reversed = options.reversed;
  5294. // Initial categories
  5295. axis.categories = options.categories;
  5296. // Elements
  5297. //axis.axisGroup = UNDEFINED;
  5298. //axis.gridGroup = UNDEFINED;
  5299. //axis.axisTitle = UNDEFINED;
  5300. //axis.axisLine = UNDEFINED;
  5301. // Flag if type === logarithmic
  5302. axis.isLog = type === 'logarithmic';
  5303. // Flag, if axis is linked to another axis
  5304. axis.isLinked = defined(options.linkedTo);
  5305. // Linked axis.
  5306. //axis.linkedParent = UNDEFINED;
  5307. // Flag if type === datetime
  5308. axis.isDatetimeAxis = isDatetimeAxis;
  5309. // Flag if percentage mode
  5310. //axis.usePercentage = UNDEFINED;
  5311. // Tick positions
  5312. //axis.tickPositions = UNDEFINED; // array containing predefined positions
  5313. // Tick intervals
  5314. //axis.tickInterval = UNDEFINED;
  5315. //axis.minorTickInterval = UNDEFINED;
  5316. // Major ticks
  5317. axis.ticks = {};
  5318. // Minor ticks
  5319. axis.minorTicks = {};
  5320. //axis.tickAmount = UNDEFINED;
  5321. // List of plotLines/Bands
  5322. axis.plotLinesAndBands = [];
  5323. // Alternate bands
  5324. axis.alternateBands = {};
  5325. // Axis metrics
  5326. //axis.left = UNDEFINED;
  5327. //axis.top = UNDEFINED;
  5328. //axis.width = UNDEFINED;
  5329. //axis.height = UNDEFINED;
  5330. //axis.bottom = UNDEFINED;
  5331. //axis.right = UNDEFINED;
  5332. //axis.transA = UNDEFINED;
  5333. //axis.transB = UNDEFINED;
  5334. //axis.oldTransA = UNDEFINED;
  5335. axis.len = 0;
  5336. //axis.oldMin = UNDEFINED;
  5337. //axis.oldMax = UNDEFINED;
  5338. //axis.oldUserMin = UNDEFINED;
  5339. //axis.oldUserMax = UNDEFINED;
  5340. //axis.oldAxisLength = UNDEFINED;
  5341. axis.minRange = axis.userMinRange = options.minRange || options.maxZoom;
  5342. axis.range = options.range;
  5343. axis.offset = options.offset || 0;
  5344. // Dictionary for stacks
  5345. axis.stacks = {};
  5346. // Min and max in the data
  5347. //axis.dataMin = UNDEFINED,
  5348. //axis.dataMax = UNDEFINED,
  5349. // The axis range
  5350. axis.max = null;
  5351. axis.min = null;
  5352. // User set min and max
  5353. //axis.userMin = UNDEFINED,
  5354. //axis.userMax = UNDEFINED,
  5355. // Run Axis
  5356. var eventType,
  5357. events = axis.options.events;
  5358. // Register
  5359. chart.axes.push(axis);
  5360. chart[isXAxis ? 'xAxis' : 'yAxis'].push(axis);
  5361. axis.series = []; // populated by Series
  5362. // inverted charts have reversed xAxes as default
  5363. if (chart.inverted && isXAxis && axis.reversed === UNDEFINED) {
  5364. axis.reversed = true;
  5365. }
  5366. axis.removePlotBand = axis.removePlotBandOrLine;
  5367. axis.removePlotLine = axis.removePlotBandOrLine;
  5368. axis.addPlotBand = axis.addPlotBandOrLine;
  5369. axis.addPlotLine = axis.addPlotBandOrLine;
  5370. // register event listeners
  5371. for (eventType in events) {
  5372. addEvent(axis, eventType, events[eventType]);
  5373. }
  5374. // extend logarithmic axis
  5375. if (axis.isLog) {
  5376. axis.val2lin = log2lin;
  5377. axis.lin2val = lin2log;
  5378. }
  5379. },
  5380. /**
  5381. * Merge and set options
  5382. */
  5383. setOptions: function (userOptions) {
  5384. this.options = merge(
  5385. this.defaultOptions,
  5386. this.isXAxis ? {} : this.defaultYAxisOptions,
  5387. [this.defaultTopAxisOptions, this.defaultRightAxisOptions,
  5388. this.defaultBottomAxisOptions, this.defaultLeftAxisOptions][this.side],
  5389. userOptions
  5390. );
  5391. },
  5392. /**
  5393. * The default label formatter. The context is a special config object for the label.
  5394. */
  5395. defaultLabelFormatter: function () {
  5396. var axis = this.axis,
  5397. value = this.value,
  5398. categories = axis.categories,
  5399. tickInterval = axis.tickInterval,
  5400. dateTimeLabelFormat = this.dateTimeLabelFormat,
  5401. ret;
  5402. if (categories) {
  5403. ret = value;
  5404. } else if (dateTimeLabelFormat) { // datetime axis
  5405. ret = dateFormat(dateTimeLabelFormat, value);
  5406. } else if (tickInterval % 1000000 === 0) { // use M abbreviation
  5407. ret = (value / 1000000) + 'M';
  5408. } else if (tickInterval % 1000 === 0) { // use k abbreviation
  5409. ret = (value / 1000) + 'k';
  5410. } else if (value >= 1000) { // add thousands separators
  5411. ret = numberFormat(value, 0);
  5412. } else { // small numbers
  5413. ret = numberFormat(value, -1);
  5414. }
  5415. return ret;
  5416. },
  5417. /**
  5418. * Get the minimum and maximum for the series of each axis
  5419. */
  5420. getSeriesExtremes: function () {
  5421. var axis = this,
  5422. chart = axis.chart,
  5423. stacks = axis.stacks,
  5424. posStack = [],
  5425. negStack = [],
  5426. i;
  5427. // reset dataMin and dataMax in case we're redrawing
  5428. axis.dataMin = axis.dataMax = null;
  5429. // loop through this axis' series
  5430. each(axis.series, function (series) {
  5431. if (series.visible || !chart.options.chart.ignoreHiddenSeries) {
  5432. var seriesOptions = series.options,
  5433. stacking,
  5434. posPointStack,
  5435. negPointStack,
  5436. stackKey,
  5437. stackOption,
  5438. negKey,
  5439. xData,
  5440. yData,
  5441. x,
  5442. y,
  5443. threshold = seriesOptions.threshold,
  5444. yDataLength,
  5445. activeYData = [],
  5446. activeCounter = 0;
  5447. // Validate threshold in logarithmic axes
  5448. if (axis.isLog && threshold <= 0) {
  5449. threshold = seriesOptions.threshold = null;
  5450. }
  5451. // Get dataMin and dataMax for X axes
  5452. if (axis.isXAxis) {
  5453. xData = series.xData;
  5454. if (xData.length) {
  5455. axis.dataMin = mathMin(pick(axis.dataMin, xData[0]), arrayMin(xData));
  5456. axis.dataMax = mathMax(pick(axis.dataMax, xData[0]), arrayMax(xData));
  5457. }
  5458. // Get dataMin and dataMax for Y axes, as well as handle stacking and processed data
  5459. } else {
  5460. var isNegative,
  5461. pointStack,
  5462. key,
  5463. cropped = series.cropped,
  5464. xExtremes = series.xAxis.getExtremes(),
  5465. //findPointRange,
  5466. //pointRange,
  5467. j,
  5468. hasModifyValue = !!series.modifyValue;
  5469. // Handle stacking
  5470. stacking = seriesOptions.stacking;
  5471. axis.usePercentage = stacking === 'percent';
  5472. // create a stack for this particular series type
  5473. if (stacking) {
  5474. stackOption = seriesOptions.stack;
  5475. stackKey = series.type + pick(stackOption, '');
  5476. negKey = '-' + stackKey;
  5477. series.stackKey = stackKey; // used in translate
  5478. posPointStack = posStack[stackKey] || []; // contains the total values for each x
  5479. posStack[stackKey] = posPointStack;
  5480. negPointStack = negStack[negKey] || [];
  5481. negStack[negKey] = negPointStack;
  5482. }
  5483. if (axis.usePercentage) {
  5484. axis.dataMin = 0;
  5485. axis.dataMax = 99;
  5486. }
  5487. // processData can alter series.pointRange, so this goes after
  5488. //findPointRange = series.pointRange === null;
  5489. xData = series.processedXData;
  5490. yData = series.processedYData;
  5491. yDataLength = yData.length;
  5492. // loop over the non-null y values and read them into a local array
  5493. for (i = 0; i < yDataLength; i++) {
  5494. x = xData[i];
  5495. y = yData[i];
  5496. if (y !== null && y !== UNDEFINED) {
  5497. // read stacked values into a stack based on the x value,
  5498. // the sign of y and the stack key
  5499. if (stacking) {
  5500. isNegative = y < threshold;
  5501. pointStack = isNegative ? negPointStack : posPointStack;
  5502. key = isNegative ? negKey : stackKey;
  5503. y = pointStack[x] =
  5504. defined(pointStack[x]) ?
  5505. pointStack[x] + y : y;
  5506. // add the series
  5507. if (!stacks[key]) {
  5508. stacks[key] = {};
  5509. }
  5510. // If the StackItem is there, just update the values,
  5511. // if not, create one first
  5512. if (!stacks[key][x]) {
  5513. stacks[key][x] = new StackItem(axis, axis.options.stackLabels, isNegative, x, stackOption);
  5514. }
  5515. stacks[key][x].setTotal(y);
  5516. // general hook, used for Highstock compare values feature
  5517. } else if (hasModifyValue) {
  5518. y = series.modifyValue(y);
  5519. }
  5520. // get the smallest distance between points
  5521. /*if (i) {
  5522. distance = mathAbs(xData[i] - xData[i - 1]);
  5523. pointRange = pointRange === UNDEFINED ? distance : mathMin(distance, pointRange);
  5524. }*/
  5525. // for points within the visible range, including the first point outside the
  5526. // visible range, consider y extremes
  5527. if (cropped || ((xData[i + 1] || x) >= xExtremes.min && (xData[i - 1] || x) <= xExtremes.max)) {
  5528. j = y.length;
  5529. if (j) { // array, like ohlc or range data
  5530. while (j--) {
  5531. if (y[j] !== null) {
  5532. activeYData[activeCounter++] = y[j];
  5533. }
  5534. }
  5535. } else {
  5536. activeYData[activeCounter++] = y;
  5537. }
  5538. }
  5539. }
  5540. }
  5541. // record the least unit distance
  5542. /*if (findPointRange) {
  5543. series.pointRange = pointRange || 1;
  5544. }
  5545. series.closestPointRange = pointRange;*/
  5546. // Get the dataMin and dataMax so far. If percentage is used, the min and max are
  5547. // always 0 and 100. If the length of activeYData is 0, continue with null values.
  5548. if (!axis.usePercentage && activeYData.length) {
  5549. axis.dataMin = mathMin(pick(axis.dataMin, activeYData[0]), arrayMin(activeYData));
  5550. axis.dataMax = mathMax(pick(axis.dataMax, activeYData[0]), arrayMax(activeYData));
  5551. }
  5552. // Adjust to threshold
  5553. if (defined(threshold)) {
  5554. if (axis.dataMin >= threshold) {
  5555. axis.dataMin = threshold;
  5556. axis.ignoreMinPadding = true;
  5557. } else if (axis.dataMax < threshold) {
  5558. axis.dataMax = threshold;
  5559. axis.ignoreMaxPadding = true;
  5560. }
  5561. }
  5562. }
  5563. }
  5564. });
  5565. },
  5566. /**
  5567. * Translate from axis value to pixel position on the chart, or back
  5568. *
  5569. */
  5570. translate: function (val, backwards, cvsCoord, old, handleLog) {
  5571. var axis = this,
  5572. axisLength = axis.len,
  5573. sign = 1,
  5574. cvsOffset = 0,
  5575. localA = old ? axis.oldTransA : axis.transA,
  5576. localMin = old ? axis.oldMin : axis.min,
  5577. returnValue,
  5578. postTranslate = axis.options.ordinal || (axis.isLog && handleLog);
  5579. if (!localA) {
  5580. localA = axis.transA;
  5581. }
  5582. if (cvsCoord) {
  5583. sign *= -1; // canvas coordinates inverts the value
  5584. cvsOffset = axisLength;
  5585. }
  5586. if (axis.reversed) { // reversed axis
  5587. sign *= -1;
  5588. cvsOffset -= sign * axisLength;
  5589. }
  5590. if (backwards) { // reverse translation
  5591. if (axis.reversed) {
  5592. val = axisLength - val;
  5593. }
  5594. returnValue = val / localA + localMin; // from chart pixel to value
  5595. if (postTranslate) { // log and ordinal axes
  5596. returnValue = axis.lin2val(returnValue);
  5597. }
  5598. } else { // normal translation, from axis value to pixel, relative to plot
  5599. if (postTranslate) { // log and ordinal axes
  5600. val = axis.val2lin(val);
  5601. }
  5602. returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * axis.minPixelPadding);
  5603. }
  5604. return returnValue;
  5605. },
  5606. /**
  5607. * Create the path for a plot line that goes from the given value on
  5608. * this axis, across the plot to the opposite side
  5609. * @param {Number} value
  5610. * @param {Number} lineWidth Used for calculation crisp line
  5611. * @param {Number] old Use old coordinates (for resizing and rescaling)
  5612. */
  5613. getPlotLinePath: function (value, lineWidth, old) {
  5614. var axis = this,
  5615. chart = axis.chart,
  5616. axisLeft = axis.left,
  5617. axisTop = axis.top,
  5618. x1,
  5619. y1,
  5620. x2,
  5621. y2,
  5622. translatedValue = axis.translate(value, null, null, old),
  5623. cHeight = (old && chart.oldChartHeight) || chart.chartHeight,
  5624. cWidth = (old && chart.oldChartWidth) || chart.chartWidth,
  5625. skip,
  5626. transB = axis.transB;
  5627. x1 = x2 = mathRound(translatedValue + transB);
  5628. y1 = y2 = mathRound(cHeight - translatedValue - transB);
  5629. if (isNaN(translatedValue)) { // no min or max
  5630. skip = true;
  5631. } else if (axis.horiz) {
  5632. y1 = axisTop;
  5633. y2 = cHeight - axis.bottom;
  5634. if (x1 < axisLeft || x1 > axisLeft + axis.width) {
  5635. skip = true;
  5636. }
  5637. } else {
  5638. x1 = axisLeft;
  5639. x2 = cWidth - axis.right;
  5640. if (y1 < axisTop || y1 > axisTop + axis.height) {
  5641. skip = true;
  5642. }
  5643. }
  5644. return skip ?
  5645. null :
  5646. chart.renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 0);
  5647. },
  5648. /**
  5649. * Create the path for a plot band
  5650. */
  5651. getPlotBandPath: function (from, to) {
  5652. var toPath = this.getPlotLinePath(to),
  5653. path = this.getPlotLinePath(from);
  5654. if (path && toPath) {
  5655. path.push(
  5656. toPath[4],
  5657. toPath[5],
  5658. toPath[1],
  5659. toPath[2]
  5660. );
  5661. } else { // outside the axis area
  5662. path = null;
  5663. }
  5664. return path;
  5665. },
  5666. /**
  5667. * Set the tick positions of a linear axis to round values like whole tens or every five.
  5668. */
  5669. getLinearTickPositions: function (tickInterval, min, max) {
  5670. var pos,
  5671. lastPos,
  5672. roundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval),
  5673. roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval),
  5674. tickPositions = [];
  5675. // Populate the intermediate values
  5676. pos = roundedMin;
  5677. while (pos <= roundedMax) {
  5678. // Place the tick on the rounded value
  5679. tickPositions.push(pos);
  5680. // Always add the raw tickInterval, not the corrected one.
  5681. pos = correctFloat(pos + tickInterval);
  5682. // If the interval is not big enough in the current min - max range to actually increase
  5683. // the loop variable, we need to break out to prevent endless loop. Issue #619
  5684. if (pos === lastPos) {
  5685. break;
  5686. }
  5687. // Record the last value
  5688. lastPos = pos;
  5689. }
  5690. return tickPositions;
  5691. },
  5692. /**
  5693. * Set the tick positions of a logarithmic axis
  5694. */
  5695. getLogTickPositions: function (interval, min, max, minor) {
  5696. var axis = this,
  5697. options = axis.options,
  5698. axisLength = axis.len;
  5699. // Since we use this method for both major and minor ticks,
  5700. // use a local variable and return the result
  5701. var positions = [];
  5702. // Reset
  5703. if (!minor) {
  5704. axis._minorAutoInterval = null;
  5705. }
  5706. // First case: All ticks fall on whole logarithms: 1, 10, 100 etc.
  5707. if (interval >= 0.5) {
  5708. interval = mathRound(interval);
  5709. positions = axis.getLinearTickPositions(interval, min, max);
  5710. // Second case: We need intermediary ticks. For example
  5711. // 1, 2, 4, 6, 8, 10, 20, 40 etc.
  5712. } else if (interval >= 0.08) {
  5713. var roundedMin = mathFloor(min),
  5714. intermediate,
  5715. i,
  5716. j,
  5717. len,
  5718. pos,
  5719. lastPos,
  5720. break2;
  5721. if (interval > 0.3) {
  5722. intermediate = [1, 2, 4];
  5723. } else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc
  5724. intermediate = [1, 2, 4, 6, 8];
  5725. } else { // 0.1 equals ten minor ticks per 1, 10, 100 etc
  5726. intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
  5727. }
  5728. for (i = roundedMin; i < max + 1 && !break2; i++) {
  5729. len = intermediate.length;
  5730. for (j = 0; j < len && !break2; j++) {
  5731. pos = log2lin(lin2log(i) * intermediate[j]);
  5732. if (pos > min) {
  5733. positions.push(lastPos);
  5734. }
  5735. if (lastPos > max) {
  5736. break2 = true;
  5737. }
  5738. lastPos = pos;
  5739. }
  5740. }
  5741. // Third case: We are so deep in between whole logarithmic values that
  5742. // we might as well handle the tick positions like a linear axis. For
  5743. // example 1.01, 1.02, 1.03, 1.04.
  5744. } else {
  5745. var realMin = lin2log(min),
  5746. realMax = lin2log(max),
  5747. tickIntervalOption = options[minor ? 'minorTickInterval' : 'tickInterval'],
  5748. filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption,
  5749. tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1),
  5750. totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength;
  5751. interval = pick(
  5752. filteredTickIntervalOption,
  5753. axis._minorAutoInterval,
  5754. (realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1)
  5755. );
  5756. interval = normalizeTickInterval(
  5757. interval,
  5758. null,
  5759. math.pow(10, mathFloor(math.log(interval) / math.LN10))
  5760. );
  5761. positions = map(axis.getLinearTickPositions(
  5762. interval,
  5763. realMin,
  5764. realMax
  5765. ), log2lin);
  5766. if (!minor) {
  5767. axis._minorAutoInterval = interval / 5;
  5768. }
  5769. }
  5770. // Set the axis-level tickInterval variable
  5771. if (!minor) {
  5772. axis.tickInterval = interval;
  5773. }
  5774. return positions;
  5775. },
  5776. /**
  5777. * Return the minor tick positions. For logarithmic axes, reuse the same logic
  5778. * as for major ticks.
  5779. */
  5780. getMinorTickPositions: function () {
  5781. var axis = this,
  5782. tickPositions = axis.tickPositions,
  5783. minorTickInterval = axis.minorTickInterval;
  5784. var minorTickPositions = [],
  5785. pos,
  5786. i,
  5787. len;
  5788. if (axis.isLog) {
  5789. len = tickPositions.length;
  5790. for (i = 1; i < len; i++) {
  5791. minorTickPositions = minorTickPositions.concat(
  5792. axis.getLogTickPositions(minorTickInterval, tickPositions[i - 1], tickPositions[i], true)
  5793. );
  5794. }
  5795. } else {
  5796. for (pos = axis.min + (tickPositions[0] - axis.min) % minorTickInterval; pos <= axis.max; pos += minorTickInterval) {
  5797. minorTickPositions.push(pos);
  5798. }
  5799. }
  5800. return minorTickPositions;
  5801. },
  5802. /**
  5803. * Adjust the min and max for the minimum range. Keep in mind that the series data is
  5804. * not yet processed, so we don't have information on data cropping and grouping, or
  5805. * updated axis.pointRange or series.pointRange. The data can't be processed until
  5806. * we have finally established min and max.
  5807. */
  5808. adjustForMinRange: function () {
  5809. var axis = this,
  5810. options = axis.options,
  5811. min = axis.min,
  5812. max = axis.max,
  5813. zoomOffset,
  5814. spaceAvailable = axis.dataMax - axis.dataMin >= axis.minRange,
  5815. closestDataRange,
  5816. i,
  5817. distance,
  5818. xData,
  5819. loopLength,
  5820. minArgs,
  5821. maxArgs;
  5822. // Set the automatic minimum range based on the closest point distance
  5823. if (axis.isXAxis && axis.minRange === UNDEFINED && !axis.isLog) {
  5824. if (defined(options.min) || defined(options.max)) {
  5825. axis.minRange = null; // don't do this again
  5826. } else {
  5827. // Find the closest distance between raw data points, as opposed to
  5828. // closestPointRange that applies to processed points (cropped and grouped)
  5829. each(axis.series, function (series) {
  5830. xData = series.xData;
  5831. loopLength = series.xIncrement ? 1 : xData.length - 1;
  5832. for (i = loopLength; i > 0; i--) {
  5833. distance = xData[i] - xData[i - 1];
  5834. if (closestDataRange === UNDEFINED || distance < closestDataRange) {
  5835. closestDataRange = distance;
  5836. }
  5837. }
  5838. });
  5839. axis.minRange = mathMin(closestDataRange * 5, axis.dataMax - axis.dataMin);
  5840. }
  5841. }
  5842. // if minRange is exceeded, adjust
  5843. if (max - min < axis.minRange) {
  5844. var minRange = axis.minRange;
  5845. zoomOffset = (minRange - max + min) / 2;
  5846. // if min and max options have been set, don't go beyond it
  5847. minArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)];
  5848. if (spaceAvailable) { // if space is available, stay within the data range
  5849. minArgs[2] = axis.dataMin;
  5850. }
  5851. min = arrayMax(minArgs);
  5852. maxArgs = [min + minRange, pick(options.max, min + minRange)];
  5853. if (spaceAvailable) { // if space is availabe, stay within the data range
  5854. maxArgs[2] = axis.dataMax;
  5855. }
  5856. max = arrayMin(maxArgs);
  5857. // now if the max is adjusted, adjust the min back
  5858. if (max - min < minRange) {
  5859. minArgs[0] = max - minRange;
  5860. minArgs[1] = pick(options.min, max - minRange);
  5861. min = arrayMax(minArgs);
  5862. }
  5863. }
  5864. // Record modified extremes
  5865. axis.min = min;
  5866. axis.max = max;
  5867. },
  5868. /**
  5869. * Update translation information
  5870. */
  5871. setAxisTranslation: function () {
  5872. var axis = this,
  5873. range = axis.max - axis.min,
  5874. pointRange = 0,
  5875. closestPointRange,
  5876. seriesClosestPointRange,
  5877. transA = axis.transA;
  5878. // adjust translation for padding
  5879. if (axis.isXAxis) {
  5880. if (axis.isLinked) {
  5881. pointRange = axis.linkedParent.pointRange;
  5882. } else {
  5883. each(axis.series, function (series) {
  5884. pointRange = mathMax(pointRange, series.pointRange);
  5885. seriesClosestPointRange = series.closestPointRange;
  5886. if (!series.noSharedTooltip && defined(seriesClosestPointRange)) {
  5887. closestPointRange = defined(closestPointRange) ?
  5888. mathMin(closestPointRange, seriesClosestPointRange) :
  5889. seriesClosestPointRange;
  5890. }
  5891. });
  5892. }
  5893. // pointRange means the width reserved for each point, like in a column chart
  5894. axis.pointRange = pointRange;
  5895. // closestPointRange means the closest distance between points. In columns
  5896. // it is mostly equal to pointRange, but in lines pointRange is 0 while closestPointRange
  5897. // is some other value
  5898. axis.closestPointRange = closestPointRange;
  5899. }
  5900. // secondary values
  5901. axis.oldTransA = transA;
  5902. axis.translationSlope = axis.transA = transA = axis.len / ((range + pointRange) || 1);
  5903. axis.transB = axis.horiz ? axis.left : axis.bottom; // translation addend
  5904. axis.minPixelPadding = transA * (pointRange / 2);
  5905. },
  5906. /**
  5907. * Set the tick positions to round values and optionally extend the extremes
  5908. * to the nearest tick
  5909. */
  5910. setTickPositions: function (secondPass) {
  5911. var axis = this,
  5912. chart = axis.chart,
  5913. options = axis.options,
  5914. isLog = axis.isLog,
  5915. isDatetimeAxis = axis.isDatetimeAxis,
  5916. isXAxis = axis.isXAxis,
  5917. isLinked = axis.isLinked,
  5918. tickPositioner = axis.options.tickPositioner,
  5919. magnitude,
  5920. maxPadding = options.maxPadding,
  5921. minPadding = options.minPadding,
  5922. length,
  5923. linkedParentExtremes,
  5924. tickIntervalOption = options.tickInterval,
  5925. tickPixelIntervalOption = options.tickPixelInterval,
  5926. tickPositions,
  5927. categories = axis.categories;
  5928. // linked axis gets the extremes from the parent axis
  5929. if (isLinked) {
  5930. axis.linkedParent = chart[isXAxis ? 'xAxis' : 'yAxis'][options.linkedTo];
  5931. linkedParentExtremes = axis.linkedParent.getExtremes();
  5932. axis.min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin);
  5933. axis.max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax);
  5934. if (options.type !== axis.linkedParent.options.type) {
  5935. error(11, 1); // Can't link axes of different type
  5936. }
  5937. } else { // initial min and max from the extreme data values
  5938. axis.min = pick(axis.userMin, options.min, axis.dataMin);
  5939. axis.max = pick(axis.userMax, options.max, axis.dataMax);
  5940. }
  5941. if (isLog) {
  5942. if (!secondPass && mathMin(axis.min, pick(axis.dataMin, axis.min)) <= 0) { // #978
  5943. error(10, 1); // Can't plot negative values on log axis
  5944. }
  5945. axis.min = correctFloat(log2lin(axis.min)); // correctFloat cures #934
  5946. axis.max = correctFloat(log2lin(axis.max));
  5947. }
  5948. // handle zoomed range
  5949. if (axis.range) {
  5950. axis.userMin = axis.min = mathMax(axis.min, axis.max - axis.range); // #618
  5951. axis.userMax = axis.max;
  5952. if (secondPass) {
  5953. axis.range = null; // don't use it when running setExtremes
  5954. }
  5955. }
  5956. // adjust min and max for the minimum range
  5957. axis.adjustForMinRange();
  5958. // pad the values to get clear of the chart's edges
  5959. if (!categories && !axis.usePercentage && !isLinked && defined(axis.min) && defined(axis.max)) {
  5960. length = (axis.max - axis.min) || 1;
  5961. if (!defined(options.min) && !defined(axis.userMin) && minPadding && (axis.dataMin < 0 || !axis.ignoreMinPadding)) {
  5962. axis.min -= length * minPadding;
  5963. }
  5964. if (!defined(options.max) && !defined(axis.userMax) && maxPadding && (axis.dataMax > 0 || !axis.ignoreMaxPadding)) {
  5965. axis.max += length * maxPadding;
  5966. }
  5967. }
  5968. // get tickInterval
  5969. if (axis.min === axis.max || axis.min === undefined || axis.max === undefined) {
  5970. axis.tickInterval = 1;
  5971. } else if (isLinked && !tickIntervalOption &&
  5972. tickPixelIntervalOption === axis.linkedParent.options.tickPixelInterval) {
  5973. axis.tickInterval = axis.linkedParent.tickInterval;
  5974. } else {
  5975. axis.tickInterval = pick(
  5976. tickIntervalOption,
  5977. categories ? // for categoried axis, 1 is default, for linear axis use tickPix
  5978. 1 :
  5979. (axis.max - axis.min) * tickPixelIntervalOption / (axis.len || 1)
  5980. );
  5981. }
  5982. // Now we're finished detecting min and max, crop and group series data. This
  5983. // is in turn needed in order to find tick positions in ordinal axes.
  5984. if (isXAxis && !secondPass) {
  5985. each(axis.series, function (series) {
  5986. series.processData(axis.min !== axis.oldMin || axis.max !== axis.oldMax);
  5987. });
  5988. }
  5989. // set the translation factor used in translate function
  5990. axis.setAxisTranslation();
  5991. // hook for ordinal axes. To do: merge with below
  5992. if (axis.beforeSetTickPositions) {
  5993. axis.beforeSetTickPositions();
  5994. }
  5995. // hook for extensions, used in Highstock ordinal axes
  5996. if (axis.postProcessTickInterval) {
  5997. axis.tickInterval = axis.postProcessTickInterval(axis.tickInterval);
  5998. }
  5999. // for linear axes, get magnitude and normalize the interval
  6000. if (!isDatetimeAxis && !isLog) { // linear
  6001. magnitude = math.pow(10, mathFloor(math.log(axis.tickInterval) / math.LN10));
  6002. if (!defined(options.tickInterval)) {
  6003. axis.tickInterval = normalizeTickInterval(axis.tickInterval, null, magnitude, options);
  6004. }
  6005. }
  6006. // get minorTickInterval
  6007. axis.minorTickInterval = options.minorTickInterval === 'auto' && axis.tickInterval ?
  6008. axis.tickInterval / 5 : options.minorTickInterval;
  6009. // find the tick positions
  6010. axis.tickPositions = tickPositions = options.tickPositions || (tickPositioner && tickPositioner.apply(axis, [axis.min, axis.max]));
  6011. if (!tickPositions) {
  6012. if (isDatetimeAxis) {
  6013. tickPositions = (axis.getNonLinearTimeTicks || getTimeTicks)(
  6014. normalizeTimeTickInterval(axis.tickInterval, options.units),
  6015. axis.min,
  6016. axis.max,
  6017. options.startOfWeek,
  6018. axis.ordinalPositions,
  6019. axis.closestPointRange,
  6020. true
  6021. );
  6022. } else if (isLog) {
  6023. tickPositions = axis.getLogTickPositions(axis.tickInterval, axis.min, axis.max);
  6024. } else {
  6025. tickPositions = axis.getLinearTickPositions(axis.tickInterval, axis.min, axis.max);
  6026. }
  6027. axis.tickPositions = tickPositions;
  6028. }
  6029. if (!isLinked) {
  6030. // reset min/max or remove extremes based on start/end on tick
  6031. var roundedMin = tickPositions[0],
  6032. roundedMax = tickPositions[tickPositions.length - 1];
  6033. if (options.startOnTick) {
  6034. axis.min = roundedMin;
  6035. } else if (axis.min > roundedMin) {
  6036. tickPositions.shift();
  6037. }
  6038. if (options.endOnTick) {
  6039. axis.max = roundedMax;
  6040. } else if (axis.max < roundedMax) {
  6041. tickPositions.pop();
  6042. }
  6043. }
  6044. },
  6045. /**
  6046. * Set the max ticks of either the x and y axis collection
  6047. */
  6048. setMaxTicks: function () {
  6049. var chart = this.chart,
  6050. maxTicks = chart.maxTicks,
  6051. tickPositions = this.tickPositions,
  6052. xOrY = this.xOrY;
  6053. if (!maxTicks) { // first call, or maxTicks have been reset after a zoom operation
  6054. maxTicks = {
  6055. x: 0,
  6056. y: 0
  6057. };
  6058. }
  6059. if (!this.isLinked && !this.isDatetimeAxis && tickPositions.length > maxTicks[xOrY] && this.options.alignTicks !== false) {
  6060. maxTicks[xOrY] = tickPositions.length;
  6061. }
  6062. chart.maxTicks = maxTicks;
  6063. },
  6064. /**
  6065. * When using multiple axes, adjust the number of ticks to match the highest
  6066. * number of ticks in that group
  6067. */
  6068. adjustTickAmount: function () {
  6069. var axis = this,
  6070. chart = axis.chart,
  6071. xOrY = axis.xOrY,
  6072. tickPositions = axis.tickPositions,
  6073. maxTicks = chart.maxTicks;
  6074. if (maxTicks && maxTicks[xOrY] && !axis.isDatetimeAxis && !axis.categories && !axis.isLinked && axis.options.alignTicks !== false) { // only apply to linear scale
  6075. var oldTickAmount = axis.tickAmount,
  6076. calculatedTickAmount = tickPositions.length,
  6077. tickAmount;
  6078. // set the axis-level tickAmount to use below
  6079. axis.tickAmount = tickAmount = maxTicks[xOrY];
  6080. if (calculatedTickAmount < tickAmount) {
  6081. while (tickPositions.length < tickAmount) {
  6082. tickPositions.push(correctFloat(
  6083. tickPositions[tickPositions.length - 1] + axis.tickInterval
  6084. ));
  6085. }
  6086. axis.transA *= (calculatedTickAmount - 1) / (tickAmount - 1);
  6087. axis.max = tickPositions[tickPositions.length - 1];
  6088. }
  6089. if (defined(oldTickAmount) && tickAmount !== oldTickAmount) {
  6090. axis.isDirty = true;
  6091. }
  6092. }
  6093. },
  6094. /**
  6095. * Set the scale based on data min and max, user set min and max or options
  6096. *
  6097. */
  6098. setScale: function () {
  6099. var axis = this,
  6100. stacks = axis.stacks,
  6101. type,
  6102. i,
  6103. isDirtyData,
  6104. isDirtyAxisLength;
  6105. axis.oldMin = axis.min;
  6106. axis.oldMax = axis.max;
  6107. axis.oldAxisLength = axis.len;
  6108. // set the new axisLength
  6109. axis.setAxisSize();
  6110. //axisLength = horiz ? axisWidth : axisHeight;
  6111. isDirtyAxisLength = axis.len !== axis.oldAxisLength;
  6112. // is there new data?
  6113. each(axis.series, function (series) {
  6114. if (series.isDirtyData || series.isDirty ||
  6115. series.xAxis.isDirty) { // when x axis is dirty, we need new data extremes for y as well
  6116. isDirtyData = true;
  6117. }
  6118. });
  6119. // do we really need to go through all this?
  6120. if (isDirtyAxisLength || isDirtyData || axis.isLinked ||
  6121. axis.userMin !== axis.oldUserMin || axis.userMax !== axis.oldUserMax) {
  6122. // get data extremes if needed
  6123. axis.getSeriesExtremes();
  6124. // get fixed positions based on tickInterval
  6125. axis.setTickPositions();
  6126. // record old values to decide whether a rescale is necessary later on (#540)
  6127. axis.oldUserMin = axis.userMin;
  6128. axis.oldUserMax = axis.userMax;
  6129. // Mark as dirty if it is not already set to dirty and extremes have changed. #595.
  6130. if (!axis.isDirty) {
  6131. axis.isDirty = isDirtyAxisLength || axis.min !== axis.oldMin || axis.max !== axis.oldMax;
  6132. }
  6133. }
  6134. // reset stacks
  6135. if (!axis.isXAxis) {
  6136. for (type in stacks) {
  6137. for (i in stacks[type]) {
  6138. stacks[type][i].cum = stacks[type][i].total;
  6139. }
  6140. }
  6141. }
  6142. // Set the maximum tick amount
  6143. axis.setMaxTicks();
  6144. },
  6145. /**
  6146. * Set the extremes and optionally redraw
  6147. * @param {Number} newMin
  6148. * @param {Number} newMax
  6149. * @param {Boolean} redraw
  6150. * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
  6151. * configuration
  6152. * @param {Object} eventArguments
  6153. *
  6154. */
  6155. setExtremes: function (newMin, newMax, redraw, animation, eventArguments) {
  6156. var axis = this,
  6157. chart = axis.chart;
  6158. redraw = pick(redraw, true); // defaults to true
  6159. // Extend the arguments with min and max
  6160. eventArguments = extend(eventArguments, {
  6161. min: newMin,
  6162. max: newMax
  6163. });
  6164. // Fire the event
  6165. fireEvent(axis, 'setExtremes', eventArguments, function () { // the default event handler
  6166. axis.userMin = newMin;
  6167. axis.userMax = newMax;
  6168. // Mark for running afterSetExtremes
  6169. axis.isDirtyExtremes = true;
  6170. // redraw
  6171. if (redraw) {
  6172. chart.redraw(animation);
  6173. }
  6174. });
  6175. },
  6176. /**
  6177. * Update the axis metrics
  6178. */
  6179. setAxisSize: function () {
  6180. var axis = this,
  6181. chart = axis.chart,
  6182. options = axis.options;
  6183. var offsetLeft = options.offsetLeft || 0,
  6184. offsetRight = options.offsetRight || 0;
  6185. // basic values
  6186. // expose to use in Series object and navigator
  6187. axis.left = pick(options.left, chart.plotLeft + offsetLeft);
  6188. axis.top = pick(options.top, chart.plotTop);
  6189. axis.width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight);
  6190. axis.height = pick(options.height, chart.plotHeight);
  6191. axis.bottom = chart.chartHeight - axis.height - axis.top;
  6192. axis.right = chart.chartWidth - axis.width - axis.left;
  6193. axis.len = mathMax(axis.horiz ? axis.width : axis.height, 0); // mathMax fixes #905
  6194. },
  6195. /**
  6196. * Get the actual axis extremes
  6197. */
  6198. getExtremes: function () {
  6199. var axis = this,
  6200. isLog = axis.isLog;
  6201. return {
  6202. min: isLog ? correctFloat(lin2log(axis.min)) : axis.min,
  6203. max: isLog ? correctFloat(lin2log(axis.max)) : axis.max,
  6204. dataMin: axis.dataMin,
  6205. dataMax: axis.dataMax,
  6206. userMin: axis.userMin,
  6207. userMax: axis.userMax
  6208. };
  6209. },
  6210. /**
  6211. * Get the zero plane either based on zero or on the min or max value.
  6212. * Used in bar and area plots
  6213. */
  6214. getThreshold: function (threshold) {
  6215. var axis = this,
  6216. isLog = axis.isLog;
  6217. var realMin = isLog ? lin2log(axis.min) : axis.min,
  6218. realMax = isLog ? lin2log(axis.max) : axis.max;
  6219. if (realMin > threshold || threshold === null) {
  6220. threshold = realMin;
  6221. } else if (realMax < threshold) {
  6222. threshold = realMax;
  6223. }
  6224. return axis.translate(threshold, 0, 1, 0, 1);
  6225. },
  6226. /**
  6227. * Add a plot band or plot line after render time
  6228. *
  6229. * @param options {Object} The plotBand or plotLine configuration object
  6230. */
  6231. addPlotBandOrLine: function (options) {
  6232. var obj = new PlotLineOrBand(this, options).render();
  6233. this.plotLinesAndBands.push(obj);
  6234. return obj;
  6235. },
  6236. /**
  6237. * Render the tick labels to a preliminary position to get their sizes
  6238. */
  6239. getOffset: function () {
  6240. var axis = this,
  6241. chart = axis.chart,
  6242. renderer = chart.renderer,
  6243. options = axis.options,
  6244. tickPositions = axis.tickPositions,
  6245. ticks = axis.ticks,
  6246. horiz = axis.horiz,
  6247. side = axis.side,
  6248. hasData,
  6249. showAxis,
  6250. titleOffset = 0,
  6251. titleOffsetOption,
  6252. titleMargin = 0,
  6253. axisTitleOptions = options.title,
  6254. labelOptions = options.labels,
  6255. labelOffset = 0, // reset
  6256. axisOffset = chart.axisOffset,
  6257. directionFactor = [-1, 1, 1, -1][side],
  6258. n;
  6259. // For reuse in Axis.render
  6260. axis.hasData = hasData = axis.series.length && defined(axis.min) && defined(axis.max);
  6261. axis.showAxis = showAxis = hasData || pick(options.showEmpty, true);
  6262. // Create the axisGroup and gridGroup elements on first iteration
  6263. if (!axis.axisGroup) {
  6264. axis.axisGroup = renderer.g('axis')
  6265. .attr({ zIndex: options.zIndex || 7 })
  6266. .add();
  6267. axis.gridGroup = renderer.g('grid')
  6268. .attr({ zIndex: options.gridZIndex || 1 })
  6269. .add();
  6270. }
  6271. if (hasData || axis.isLinked) {
  6272. each(tickPositions, function (pos) {
  6273. if (!ticks[pos]) {
  6274. ticks[pos] = new Tick(axis, pos);
  6275. } else {
  6276. ticks[pos].addLabel(); // update labels depending on tick interval
  6277. }
  6278. });
  6279. each(tickPositions, function (pos) {
  6280. // left side must be align: right and right side must have align: left for labels
  6281. if (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === labelOptions.align) {
  6282. // get the highest offset
  6283. labelOffset = mathMax(
  6284. ticks[pos].getLabelSize(),
  6285. labelOffset
  6286. );
  6287. }
  6288. });
  6289. if (axis.staggerLines) {
  6290. labelOffset += (axis.staggerLines - 1) * 16;
  6291. }
  6292. } else { // doesn't have data
  6293. for (n in ticks) {
  6294. ticks[n].destroy();
  6295. delete ticks[n];
  6296. }
  6297. }
  6298. if (axisTitleOptions && axisTitleOptions.text) {
  6299. if (!axis.axisTitle) {
  6300. axis.axisTitle = renderer.text(
  6301. axisTitleOptions.text,
  6302. 0,
  6303. 0,
  6304. axisTitleOptions.useHTML
  6305. )
  6306. .attr({
  6307. zIndex: 7,
  6308. rotation: axisTitleOptions.rotation || 0,
  6309. align:
  6310. axisTitleOptions.textAlign ||
  6311. { low: 'left', middle: 'center', high: 'right' }[axisTitleOptions.align]
  6312. })
  6313. .css(axisTitleOptions.style)
  6314. .add(axis.axisGroup);
  6315. axis.axisTitle.isNew = true;
  6316. }
  6317. if (showAxis) {
  6318. titleOffset = axis.axisTitle.getBBox()[horiz ? 'height' : 'width'];
  6319. titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10);
  6320. titleOffsetOption = axisTitleOptions.offset;
  6321. }
  6322. // hide or show the title depending on whether showEmpty is set
  6323. axis.axisTitle[showAxis ? 'show' : 'hide']();
  6324. }
  6325. // handle automatic or user set offset
  6326. axis.offset = directionFactor * pick(options.offset, axisOffset[side]);
  6327. axis.axisTitleMargin =
  6328. pick(titleOffsetOption,
  6329. labelOffset + titleMargin +
  6330. (side !== 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x'])
  6331. );
  6332. axisOffset[side] = mathMax(
  6333. axisOffset[side],
  6334. axis.axisTitleMargin + titleOffset + directionFactor * axis.offset
  6335. );
  6336. },
  6337. /**
  6338. * Get the path for the axis line
  6339. */
  6340. getLinePath: function (lineWidth) {
  6341. var chart = this.chart,
  6342. opposite = this.opposite,
  6343. offset = this.offset,
  6344. horiz = this.horiz,
  6345. lineLeft = this.left + (opposite ? this.width : 0) + offset,
  6346. lineTop = chart.chartHeight - this.bottom - (opposite ? this.height : 0) + offset;
  6347. return chart.renderer.crispLine([
  6348. M,
  6349. horiz ?
  6350. this.left :
  6351. lineLeft,
  6352. horiz ?
  6353. lineTop :
  6354. this.top,
  6355. L,
  6356. horiz ?
  6357. chart.chartWidth - this.right :
  6358. lineLeft,
  6359. horiz ?
  6360. lineTop :
  6361. chart.chartHeight - this.bottom
  6362. ], lineWidth);
  6363. },
  6364. /**
  6365. * Position the title
  6366. */
  6367. getTitlePosition: function () {
  6368. // compute anchor points for each of the title align options
  6369. var horiz = this.horiz,
  6370. axisLeft = this.left,
  6371. axisTop = this.top,
  6372. axisLength = this.len,
  6373. axisTitleOptions = this.options.title,
  6374. margin = horiz ? axisLeft : axisTop,
  6375. opposite = this.opposite,
  6376. offset = this.offset,
  6377. fontSize = pInt(axisTitleOptions.style.fontSize || 12),
  6378. // the position in the length direction of the axis
  6379. alongAxis = {
  6380. low: margin + (horiz ? 0 : axisLength),
  6381. middle: margin + axisLength / 2,
  6382. high: margin + (horiz ? axisLength : 0)
  6383. }[axisTitleOptions.align],
  6384. // the position in the perpendicular direction of the axis
  6385. offAxis = (horiz ? axisTop + this.height : axisLeft) +
  6386. (horiz ? 1 : -1) * // horizontal axis reverses the margin
  6387. (opposite ? -1 : 1) * // so does opposite axes
  6388. this.axisTitleMargin +
  6389. (this.side === 2 ? fontSize : 0);
  6390. return {
  6391. x: horiz ?
  6392. alongAxis :
  6393. offAxis + (opposite ? this.width : 0) + offset +
  6394. (axisTitleOptions.x || 0), // x
  6395. y: horiz ?
  6396. offAxis - (opposite ? this.height : 0) + offset :
  6397. alongAxis + (axisTitleOptions.y || 0) // y
  6398. };
  6399. },
  6400. /**
  6401. * Render the axis
  6402. */
  6403. render: function () {
  6404. var axis = this,
  6405. chart = axis.chart,
  6406. renderer = chart.renderer,
  6407. options = axis.options,
  6408. isLog = axis.isLog,
  6409. isLinked = axis.isLinked,
  6410. tickPositions = axis.tickPositions,
  6411. axisTitle = axis.axisTitle,
  6412. stacks = axis.stacks,
  6413. ticks = axis.ticks,
  6414. minorTicks = axis.minorTicks,
  6415. alternateBands = axis.alternateBands,
  6416. stackLabelOptions = options.stackLabels,
  6417. alternateGridColor = options.alternateGridColor,
  6418. lineWidth = options.lineWidth,
  6419. linePath,
  6420. hasRendered = chart.hasRendered,
  6421. slideInTicks = hasRendered && defined(axis.oldMin) && !isNaN(axis.oldMin),
  6422. hasData = axis.hasData,
  6423. showAxis = axis.showAxis,
  6424. from,
  6425. to;
  6426. // If the series has data draw the ticks. Else only the line and title
  6427. if (hasData || isLinked) {
  6428. // minor ticks
  6429. if (axis.minorTickInterval && !axis.categories) {
  6430. each(axis.getMinorTickPositions(), function (pos) {
  6431. if (!minorTicks[pos]) {
  6432. minorTicks[pos] = new Tick(axis, pos, 'minor');
  6433. }
  6434. // render new ticks in old position
  6435. if (slideInTicks && minorTicks[pos].isNew) {
  6436. minorTicks[pos].render(null, true);
  6437. }
  6438. minorTicks[pos].isActive = true;
  6439. minorTicks[pos].render();
  6440. });
  6441. }
  6442. // Major ticks. Pull out the first item and render it last so that
  6443. // we can get the position of the neighbour label. #808.
  6444. each(tickPositions.slice(1).concat([tickPositions[0]]), function (pos, i) {
  6445. // Reorganize the indices
  6446. i = (i === tickPositions.length - 1) ? 0 : i + 1;
  6447. // linked axes need an extra check to find out if
  6448. if (!isLinked || (pos >= axis.min && pos <= axis.max)) {
  6449. if (!ticks[pos]) {
  6450. ticks[pos] = new Tick(axis, pos);
  6451. }
  6452. // render new ticks in old position
  6453. if (slideInTicks && ticks[pos].isNew) {
  6454. ticks[pos].render(i, true);
  6455. }
  6456. ticks[pos].isActive = true;
  6457. ticks[pos].render(i);
  6458. }
  6459. });
  6460. // alternate grid color
  6461. if (alternateGridColor) {
  6462. each(tickPositions, function (pos, i) {
  6463. if (i % 2 === 0 && pos < axis.max) {
  6464. if (!alternateBands[pos]) {
  6465. alternateBands[pos] = new PlotLineOrBand(axis);
  6466. }
  6467. from = pos;
  6468. to = tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] : axis.max;
  6469. alternateBands[pos].options = {
  6470. from: isLog ? lin2log(from) : from,
  6471. to: isLog ? lin2log(to) : to,
  6472. color: alternateGridColor
  6473. };
  6474. alternateBands[pos].render();
  6475. alternateBands[pos].isActive = true;
  6476. }
  6477. });
  6478. }
  6479. // custom plot lines and bands
  6480. if (!axis._addedPlotLB) { // only first time
  6481. each((options.plotLines || []).concat(options.plotBands || []), function (plotLineOptions) {
  6482. //plotLinesAndBands.push(new PlotLineOrBand(plotLineOptions).render());
  6483. axis.addPlotBandOrLine(plotLineOptions);
  6484. });
  6485. axis._addedPlotLB = true;
  6486. }
  6487. } // end if hasData
  6488. // remove inactive ticks
  6489. each([ticks, minorTicks, alternateBands], function (coll) {
  6490. var pos;
  6491. for (pos in coll) {
  6492. if (!coll[pos].isActive) {
  6493. coll[pos].destroy();
  6494. delete coll[pos];
  6495. } else {
  6496. coll[pos].isActive = false; // reset
  6497. }
  6498. }
  6499. });
  6500. // Static items. As the axis group is cleared on subsequent calls
  6501. // to render, these items are added outside the group.
  6502. // axis line
  6503. if (lineWidth) {
  6504. linePath = axis.getLinePath(lineWidth);
  6505. if (!axis.axisLine) {
  6506. axis.axisLine = renderer.path(linePath)
  6507. .attr({
  6508. stroke: options.lineColor,
  6509. 'stroke-width': lineWidth,
  6510. zIndex: 7
  6511. })
  6512. .add();
  6513. } else {
  6514. axis.axisLine.animate({ d: linePath });
  6515. }
  6516. // show or hide the line depending on options.showEmpty
  6517. axis.axisLine[showAxis ? 'show' : 'hide']();
  6518. }
  6519. if (axisTitle && showAxis) {
  6520. axisTitle[axisTitle.isNew ? 'attr' : 'animate'](
  6521. axis.getTitlePosition()
  6522. );
  6523. axisTitle.isNew = false;
  6524. }
  6525. // Stacked totals:
  6526. if (stackLabelOptions && stackLabelOptions.enabled) {
  6527. var stackKey, oneStack, stackCategory,
  6528. stackTotalGroup = axis.stackTotalGroup;
  6529. // Create a separate group for the stack total labels
  6530. if (!stackTotalGroup) {
  6531. axis.stackTotalGroup = stackTotalGroup =
  6532. renderer.g('stack-labels')
  6533. .attr({
  6534. visibility: VISIBLE,
  6535. zIndex: 6
  6536. })
  6537. .add();
  6538. }
  6539. // plotLeft/Top will change when y axis gets wider so we need to translate the
  6540. // stackTotalGroup at every render call. See bug #506 and #516
  6541. stackTotalGroup.translate(chart.plotLeft, chart.plotTop);
  6542. // Render each stack total
  6543. for (stackKey in stacks) {
  6544. oneStack = stacks[stackKey];
  6545. for (stackCategory in oneStack) {
  6546. oneStack[stackCategory].render(stackTotalGroup);
  6547. }
  6548. }
  6549. }
  6550. // End stacked totals
  6551. axis.isDirty = false;
  6552. },
  6553. /**
  6554. * Remove a plot band or plot line from the chart by id
  6555. * @param {Object} id
  6556. */
  6557. removePlotBandOrLine: function (id) {
  6558. var plotLinesAndBands = this.plotLinesAndBands,
  6559. i = plotLinesAndBands.length;
  6560. while (i--) {
  6561. if (plotLinesAndBands[i].id === id) {
  6562. plotLinesAndBands[i].destroy();
  6563. }
  6564. }
  6565. },
  6566. /**
  6567. * Update the axis title by options
  6568. */
  6569. setTitle: function (newTitleOptions, redraw) {
  6570. var axis = this,
  6571. chart = axis.chart,
  6572. options = axis.options,
  6573. axisTitle;
  6574. options.title = merge(options.title, newTitleOptions);
  6575. axis.axisTitle = axisTitle && axisTitle.destroy(); // #922
  6576. axis.isDirty = true;
  6577. if (pick(redraw, true)) {
  6578. chart.redraw();
  6579. }
  6580. },
  6581. /**
  6582. * Redraw the axis to reflect changes in the data or axis extremes
  6583. */
  6584. redraw: function () {
  6585. var axis = this,
  6586. chart = axis.chart;
  6587. // hide tooltip and hover states
  6588. if (chart.tracker.resetTracker) {
  6589. chart.tracker.resetTracker(true);
  6590. }
  6591. // render the axis
  6592. axis.render();
  6593. // move plot lines and bands
  6594. each(axis.plotLinesAndBands, function (plotLine) {
  6595. plotLine.render();
  6596. });
  6597. // mark associated series as dirty and ready for redraw
  6598. each(axis.series, function (series) {
  6599. series.isDirty = true;
  6600. });
  6601. },
  6602. /**
  6603. * Set new axis categories and optionally redraw
  6604. * @param {Array} newCategories
  6605. * @param {Boolean} doRedraw
  6606. */
  6607. setCategories: function (newCategories, doRedraw) {
  6608. var axis = this,
  6609. chart = axis.chart;
  6610. // set the categories
  6611. axis.categories = axis.userOptions.categories = newCategories;
  6612. // force reindexing tooltips
  6613. each(axis.series, function (series) {
  6614. series.translate();
  6615. series.setTooltipPoints(true);
  6616. });
  6617. // optionally redraw
  6618. axis.isDirty = true;
  6619. if (pick(doRedraw, true)) {
  6620. chart.redraw();
  6621. }
  6622. },
  6623. /**
  6624. * Destroys an Axis instance.
  6625. */
  6626. destroy: function () {
  6627. var axis = this,
  6628. stacks = axis.stacks,
  6629. stackKey;
  6630. // Remove the events
  6631. removeEvent(axis);
  6632. // Destroy each stack total
  6633. for (stackKey in stacks) {
  6634. destroyObjectProperties(stacks[stackKey]);
  6635. stacks[stackKey] = null;
  6636. }
  6637. // Destroy collections
  6638. each([axis.ticks, axis.minorTicks, axis.alternateBands, axis.plotLinesAndBands], function (coll) {
  6639. destroyObjectProperties(coll);
  6640. });
  6641. // Destroy local variables
  6642. each(['stackTotalGroup', 'axisLine', 'axisGroup', 'gridGroup', 'axisTitle'], function (prop) {
  6643. if (axis[prop]) {
  6644. axis[prop] = axis[prop].destroy();
  6645. }
  6646. });
  6647. }
  6648. }; // end Axis
  6649. /**
  6650. * The tooltip object
  6651. * @param {Object} chart The chart instance
  6652. * @param {Object} options Tooltip options
  6653. */
  6654. function Tooltip(chart, options) {
  6655. var borderWidth = options.borderWidth,
  6656. style = options.style,
  6657. shared = options.shared,
  6658. padding = pInt(style.padding);
  6659. // Save the chart and options
  6660. this.chart = chart;
  6661. this.options = options;
  6662. // remove padding CSS and apply padding on box instead
  6663. style.padding = 0;
  6664. // Keep track of the current series
  6665. //this.currentSeries = UNDEFINED;
  6666. // List of crosshairs
  6667. this.crosshairs = [];
  6668. // Current values of x and y when animating
  6669. this.currentX = 0;
  6670. this.currentY = 0;
  6671. // The tooltipTick function, initialized to nothing
  6672. //this.tooltipTick = UNDEFINED;
  6673. // The tooltip is initially hidden
  6674. this.tooltipIsHidden = true;
  6675. // create the label
  6676. this.label = chart.renderer.label('', 0, 0, null, null, null, options.useHTML, null, 'tooltip')
  6677. .attr({
  6678. padding: padding,
  6679. fill: options.backgroundColor,
  6680. 'stroke-width': borderWidth,
  6681. r: options.borderRadius,
  6682. zIndex: 8
  6683. })
  6684. .css(style)
  6685. .hide()
  6686. .add();
  6687. // When using canVG the shadow shows up as a gray circle
  6688. // even if the tooltip is hidden.
  6689. if (!useCanVG) {
  6690. this.label.shadow(options.shadow);
  6691. }
  6692. // Public property for getting the shared state.
  6693. this.shared = shared;
  6694. }
  6695. Tooltip.prototype = {
  6696. /**
  6697. * Destroy the tooltip and its elements.
  6698. */
  6699. destroy: function () {
  6700. each(this.crosshairs, function (crosshair) {
  6701. if (crosshair) {
  6702. crosshair.destroy();
  6703. }
  6704. });
  6705. // Destroy and clear local variables
  6706. if (this.label) {
  6707. this.label = this.label.destroy();
  6708. }
  6709. },
  6710. /**
  6711. * Provide a soft movement for the tooltip
  6712. *
  6713. * @param {Number} finalX
  6714. * @param {Number} finalY
  6715. * @private
  6716. */
  6717. move: function (finalX, finalY) {
  6718. var tooltip = this;
  6719. // get intermediate values for animation
  6720. tooltip.currentX = tooltip.tooltipIsHidden ? finalX : (2 * tooltip.currentX + finalX) / 3;
  6721. tooltip.currentY = tooltip.tooltipIsHidden ? finalY : (tooltip.currentY + finalY) / 2;
  6722. // move to the intermediate value
  6723. tooltip.label.attr({ x: tooltip.currentX, y: tooltip.currentY });
  6724. // run on next tick of the mouse tracker
  6725. if (mathAbs(finalX - tooltip.currentX) > 1 || mathAbs(finalY - tooltip.currentY) > 1) {
  6726. tooltip.tooltipTick = function () {
  6727. tooltip.move(finalX, finalY);
  6728. };
  6729. } else {
  6730. tooltip.tooltipTick = null;
  6731. }
  6732. },
  6733. /**
  6734. * Hide the tooltip
  6735. */
  6736. hide: function () {
  6737. if (!this.tooltipIsHidden) {
  6738. var hoverPoints = this.chart.hoverPoints;
  6739. this.label.hide();
  6740. // hide previous hoverPoints and set new
  6741. if (hoverPoints) {
  6742. each(hoverPoints, function (point) {
  6743. point.setState();
  6744. });
  6745. }
  6746. this.chart.hoverPoints = null;
  6747. this.tooltipIsHidden = true;
  6748. }
  6749. },
  6750. /**
  6751. * Hide the crosshairs
  6752. */
  6753. hideCrosshairs: function () {
  6754. each(this.crosshairs, function (crosshair) {
  6755. if (crosshair) {
  6756. crosshair.hide();
  6757. }
  6758. });
  6759. },
  6760. /**
  6761. * Extendable method to get the anchor position of the tooltip
  6762. * from a point or set of points
  6763. */
  6764. getAnchor: function (points, mouseEvent) {
  6765. var ret,
  6766. chart = this.chart,
  6767. inverted = chart.inverted,
  6768. plotX = 0,
  6769. plotY = 0;
  6770. points = splat(points);
  6771. // Pie uses a special tooltipPos
  6772. ret = points[0].tooltipPos;
  6773. // When shared, use the average position
  6774. if (!ret) {
  6775. each(points, function (point) {
  6776. plotX += point.plotX;
  6777. plotY += point.plotLow ? (point.plotLow + point.plotHigh) / 2 : point.plotY;
  6778. });
  6779. plotX /= points.length;
  6780. plotY /= points.length;
  6781. ret = [
  6782. inverted ? chart.plotWidth - plotY : plotX,
  6783. this.shared && !inverted && points.length > 1 && mouseEvent ?
  6784. mouseEvent.chartY - chart.plotTop : // place shared tooltip next to the mouse (#424)
  6785. inverted ? chart.plotHeight - plotX : plotY
  6786. ];
  6787. }
  6788. return map(ret, mathRound);
  6789. },
  6790. /**
  6791. * Place the tooltip in a chart without spilling over
  6792. * and not covering the point it self.
  6793. */
  6794. getPosition: function (boxWidth, boxHeight, point) {
  6795. // Set up the variables
  6796. var chart = this.chart,
  6797. plotLeft = chart.plotLeft,
  6798. plotTop = chart.plotTop,
  6799. plotWidth = chart.plotWidth,
  6800. plotHeight = chart.plotHeight,
  6801. distance = pick(this.options.distance, 12),
  6802. pointX = point.plotX,
  6803. pointY = point.plotY,
  6804. x = pointX + plotLeft + (chart.inverted ? distance : -boxWidth - distance),
  6805. y = pointY - boxHeight + plotTop + 15, // 15 means the point is 15 pixels up from the bottom of the tooltip
  6806. alignedRight;
  6807. // It is too far to the left, adjust it
  6808. if (x < 7) {
  6809. x = plotLeft + pointX + distance;
  6810. }
  6811. // Test to see if the tooltip is too far to the right,
  6812. // if it is, move it back to be inside and then up to not cover the point.
  6813. if ((x + boxWidth) > (plotLeft + plotWidth)) {
  6814. x -= (x + boxWidth) - (plotLeft + plotWidth);
  6815. y = pointY - boxHeight + plotTop - distance;
  6816. alignedRight = true;
  6817. }
  6818. // If it is now above the plot area, align it to the top of the plot area
  6819. if (y < plotTop + 5) {
  6820. y = plotTop + 5;
  6821. // If the tooltip is still covering the point, move it below instead
  6822. if (alignedRight && pointY >= y && pointY <= (y + boxHeight)) {
  6823. y = pointY + plotTop + distance; // below
  6824. }
  6825. }
  6826. // Now if the tooltip is below the chart, move it up. It's better to cover the
  6827. // point than to disappear outside the chart. #834.
  6828. if (y + boxHeight > plotTop + plotHeight) {
  6829. y = mathMax(plotTop, plotTop + plotHeight - boxHeight - distance); // below
  6830. }
  6831. return {x: x, y: y};
  6832. },
  6833. /**
  6834. * Refresh the tooltip's text and position.
  6835. * @param {Object} point
  6836. */
  6837. refresh: function (point, mouseEvent) {
  6838. var tooltip = this,
  6839. chart = tooltip.chart,
  6840. label = tooltip.label,
  6841. options = tooltip.options;
  6842. /**
  6843. * In case no user defined formatter is given, this will be used
  6844. */
  6845. function defaultFormatter() {
  6846. var pThis = this,
  6847. items = pThis.points || splat(pThis),
  6848. series = items[0].series,
  6849. s;
  6850. // build the header
  6851. s = [series.tooltipHeaderFormatter(items[0].key)];
  6852. // build the values
  6853. each(items, function (item) {
  6854. series = item.series;
  6855. s.push((series.tooltipFormatter && series.tooltipFormatter(item)) ||
  6856. item.point.tooltipFormatter(series.tooltipOptions.pointFormat));
  6857. });
  6858. // footer
  6859. s.push(options.footerFormat || '');
  6860. return s.join('');
  6861. }
  6862. var x,
  6863. y,
  6864. show,
  6865. anchor,
  6866. textConfig = {},
  6867. text,
  6868. pointConfig = [],
  6869. formatter = options.formatter || defaultFormatter,
  6870. hoverPoints = chart.hoverPoints,
  6871. placedTooltipPoint,
  6872. borderColor,
  6873. crosshairsOptions = options.crosshairs,
  6874. shared = tooltip.shared,
  6875. currentSeries;
  6876. // get the reference point coordinates (pie charts use tooltipPos)
  6877. anchor = tooltip.getAnchor(point, mouseEvent);
  6878. x = anchor[0];
  6879. y = anchor[1];
  6880. // shared tooltip, array is sent over
  6881. if (shared && !(point.series && point.series.noSharedTooltip)) {
  6882. // hide previous hoverPoints and set new
  6883. if (hoverPoints) {
  6884. each(hoverPoints, function (point) {
  6885. point.setState();
  6886. });
  6887. }
  6888. chart.hoverPoints = point;
  6889. each(point, function (item) {
  6890. item.setState(HOVER_STATE);
  6891. pointConfig.push(item.getLabelConfig());
  6892. });
  6893. textConfig = {
  6894. x: point[0].category,
  6895. y: point[0].y
  6896. };
  6897. textConfig.points = pointConfig;
  6898. point = point[0];
  6899. // single point tooltip
  6900. } else {
  6901. textConfig = point.getLabelConfig();
  6902. }
  6903. text = formatter.call(textConfig);
  6904. // register the current series
  6905. currentSeries = point.series;
  6906. // For line type series, hide tooltip if the point falls outside the plot
  6907. show = shared || !currentSeries.isCartesian || currentSeries.tooltipOutsidePlot || chart.isInsidePlot(x, y);
  6908. // update the inner HTML
  6909. if (text === false || !show) {
  6910. this.hide();
  6911. } else {
  6912. // show it
  6913. if (tooltip.tooltipIsHidden) {
  6914. label.show();
  6915. }
  6916. // update text
  6917. label.attr({
  6918. text: text
  6919. });
  6920. // set the stroke color of the box
  6921. borderColor = options.borderColor || point.color || currentSeries.color || '#606060';
  6922. label.attr({
  6923. stroke: borderColor
  6924. });
  6925. placedTooltipPoint = (options.positioner || tooltip.getPosition).call(
  6926. tooltip,
  6927. label.width,
  6928. label.height,
  6929. { plotX: x, plotY: y }
  6930. );
  6931. // do the move
  6932. tooltip.move(mathRound(placedTooltipPoint.x), mathRound(placedTooltipPoint.y));
  6933. tooltip.tooltipIsHidden = false;
  6934. }
  6935. // crosshairs
  6936. if (crosshairsOptions) {
  6937. crosshairsOptions = splat(crosshairsOptions); // [x, y]
  6938. var path,
  6939. i = crosshairsOptions.length,
  6940. attribs,
  6941. axis;
  6942. while (i--) {
  6943. axis = point.series[i ? 'yAxis' : 'xAxis'];
  6944. if (crosshairsOptions[i] && axis) {
  6945. path = axis.getPlotLinePath(
  6946. i ? pick(point.stackY, point.y) : point.x, // #814
  6947. 1
  6948. );
  6949. if (tooltip.crosshairs[i]) {
  6950. tooltip.crosshairs[i].attr({ d: path, visibility: VISIBLE });
  6951. } else {
  6952. attribs = {
  6953. 'stroke-width': crosshairsOptions[i].width || 1,
  6954. stroke: crosshairsOptions[i].color || '#C0C0C0',
  6955. zIndex: crosshairsOptions[i].zIndex || 2
  6956. };
  6957. if (crosshairsOptions[i].dashStyle) {
  6958. attribs.dashstyle = crosshairsOptions[i].dashStyle;
  6959. }
  6960. tooltip.crosshairs[i] = chart.renderer.path(path)
  6961. .attr(attribs)
  6962. .add();
  6963. }
  6964. }
  6965. }
  6966. }
  6967. fireEvent(chart, 'tooltipRefresh', {
  6968. text: text,
  6969. x: x + chart.plotLeft,
  6970. y: y + chart.plotTop,
  6971. borderColor: borderColor
  6972. });
  6973. },
  6974. /**
  6975. * Runs the tooltip animation one tick.
  6976. */
  6977. tick: function () {
  6978. if (this.tooltipTick) {
  6979. this.tooltipTick();
  6980. }
  6981. }
  6982. };
  6983. /**
  6984. * The mouse tracker object
  6985. * @param {Object} chart The Chart instance
  6986. * @param {Object} options The root options object
  6987. */
  6988. function MouseTracker(chart, options) {
  6989. var zoomType = useCanVG ? '' : options.chart.zoomType;
  6990. // Zoom status
  6991. this.zoomX = /x/.test(zoomType);
  6992. this.zoomY = /y/.test(zoomType);
  6993. // Store reference to options
  6994. this.options = options;
  6995. // Reference to the chart
  6996. this.chart = chart;
  6997. // The interval id
  6998. //this.tooltipInterval = UNDEFINED;
  6999. // The cached x hover position
  7000. //this.hoverX = UNDEFINED;
  7001. // The chart position
  7002. //this.chartPosition = UNDEFINED;
  7003. // The selection marker element
  7004. //this.selectionMarker = UNDEFINED;
  7005. // False or a value > 0 if a dragging operation
  7006. //this.mouseDownX = UNDEFINED;
  7007. //this.mouseDownY = UNDEFINED;
  7008. this.init(chart, options.tooltip);
  7009. }
  7010. MouseTracker.prototype = {
  7011. /**
  7012. * Add crossbrowser support for chartX and chartY
  7013. * @param {Object} e The event object in standard browsers
  7014. */
  7015. normalizeMouseEvent: function (e) {
  7016. var chartPosition,
  7017. chartX,
  7018. chartY,
  7019. ePos;
  7020. // common IE normalizing
  7021. e = e || win.event;
  7022. if (!e.target) {
  7023. e.target = e.srcElement;
  7024. }
  7025. // jQuery only copies over some properties. IE needs e.x and iOS needs touches.
  7026. if (e.originalEvent) {
  7027. e = e.originalEvent;
  7028. }
  7029. // The same for MooTools. It renames e.pageX to e.page.x. #445.
  7030. if (e.event) {
  7031. e = e.event;
  7032. }
  7033. // iOS
  7034. ePos = e.touches ? e.touches.item(0) : e;
  7035. // get mouse position
  7036. this.chartPosition = chartPosition = offset(this.chart.container);
  7037. // chartX and chartY
  7038. if (ePos.pageX === UNDEFINED) { // IE < 9. #886.
  7039. chartX = e.x;
  7040. chartY = e.y;
  7041. } else {
  7042. chartX = ePos.pageX - chartPosition.left;
  7043. chartY = ePos.pageY - chartPosition.top;
  7044. }
  7045. return extend(e, {
  7046. chartX: mathRound(chartX),
  7047. chartY: mathRound(chartY)
  7048. });
  7049. },
  7050. /**
  7051. * Get the click position in terms of axis values.
  7052. *
  7053. * @param {Object} e A mouse event
  7054. */
  7055. getMouseCoordinates: function (e) {
  7056. var coordinates = {
  7057. xAxis: [],
  7058. yAxis: []
  7059. },
  7060. chart = this.chart;
  7061. each(chart.axes, function (axis) {
  7062. var isXAxis = axis.isXAxis,
  7063. isHorizontal = chart.inverted ? !isXAxis : isXAxis;
  7064. coordinates[isXAxis ? 'xAxis' : 'yAxis'].push({
  7065. axis: axis,
  7066. value: axis.translate(
  7067. isHorizontal ?
  7068. e.chartX - chart.plotLeft :
  7069. chart.plotHeight - e.chartY + chart.plotTop,
  7070. true
  7071. )
  7072. });
  7073. });
  7074. return coordinates;
  7075. },
  7076. /**
  7077. * With line type charts with a single tracker, get the point closest to the mouse
  7078. */
  7079. onmousemove: function (e) {
  7080. var mouseTracker = this,
  7081. chart = mouseTracker.chart,
  7082. series = chart.series,
  7083. point,
  7084. points,
  7085. hoverPoint = chart.hoverPoint,
  7086. hoverSeries = chart.hoverSeries,
  7087. i,
  7088. j,
  7089. distance = chart.chartWidth,
  7090. // the index in the tooltipPoints array, corresponding to pixel position in plot area
  7091. index = chart.inverted ? chart.plotHeight + chart.plotTop - e.chartY : e.chartX - chart.plotLeft;
  7092. // shared tooltip
  7093. if (chart.tooltip && mouseTracker.options.tooltip.shared && !(hoverSeries && hoverSeries.noSharedTooltip)) {
  7094. points = [];
  7095. // loop over all series and find the ones with points closest to the mouse
  7096. i = series.length;
  7097. for (j = 0; j < i; j++) {
  7098. if (series[j].visible &&
  7099. series[j].options.enableMouseTracking !== false &&
  7100. !series[j].noSharedTooltip && series[j].tooltipPoints.length) {
  7101. point = series[j].tooltipPoints[index];
  7102. point._dist = mathAbs(index - point.plotX);
  7103. distance = mathMin(distance, point._dist);
  7104. points.push(point);
  7105. }
  7106. }
  7107. // remove furthest points
  7108. i = points.length;
  7109. while (i--) {
  7110. if (points[i]._dist > distance) {
  7111. points.splice(i, 1);
  7112. }
  7113. }
  7114. // refresh the tooltip if necessary
  7115. if (points.length && (points[0].plotX !== mouseTracker.hoverX)) {
  7116. chart.tooltip.refresh(points, e);
  7117. mouseTracker.hoverX = points[0].plotX;
  7118. }
  7119. }
  7120. // separate tooltip and general mouse events
  7121. if (hoverSeries && hoverSeries.tracker) { // only use for line-type series with common tracker
  7122. // get the point
  7123. point = hoverSeries.tooltipPoints[index];
  7124. // a new point is hovered, refresh the tooltip
  7125. if (point && point !== hoverPoint) {
  7126. // trigger the events
  7127. point.onMouseOver();
  7128. }
  7129. }
  7130. },
  7131. /**
  7132. * Reset the tracking by hiding the tooltip, the hover series state and the hover point
  7133. */
  7134. resetTracker: function (allowMove) {
  7135. var mouseTracker = this,
  7136. chart = mouseTracker.chart,
  7137. hoverSeries = chart.hoverSeries,
  7138. hoverPoint = chart.hoverPoint,
  7139. tooltipPoints = chart.hoverPoints || hoverPoint,
  7140. tooltip = chart.tooltip;
  7141. // Narrow in allowMove
  7142. allowMove = allowMove && tooltip && tooltipPoints;
  7143. // Check if the points have moved outside the plot area, #1003
  7144. if (allowMove && splat(tooltipPoints)[0].plotX === UNDEFINED) {
  7145. allowMove = false;
  7146. }
  7147. // Just move the tooltip, #349
  7148. if (allowMove) {
  7149. tooltip.refresh(tooltipPoints);
  7150. // Full reset
  7151. } else {
  7152. if (hoverPoint) {
  7153. hoverPoint.onMouseOut();
  7154. }
  7155. if (hoverSeries) {
  7156. hoverSeries.onMouseOut();
  7157. }
  7158. if (tooltip) {
  7159. tooltip.hide();
  7160. tooltip.hideCrosshairs();
  7161. }
  7162. mouseTracker.hoverX = null;
  7163. }
  7164. },
  7165. /**
  7166. * Set the JS events on the container element
  7167. */
  7168. setDOMEvents: function () {
  7169. var lastWasOutsidePlot = true,
  7170. mouseTracker = this,
  7171. chart = mouseTracker.chart,
  7172. container = chart.container,
  7173. hasDragged,
  7174. zoomHor = (mouseTracker.zoomX && !chart.inverted) || (mouseTracker.zoomY && chart.inverted),
  7175. zoomVert = (mouseTracker.zoomY && !chart.inverted) || (mouseTracker.zoomX && chart.inverted);
  7176. /**
  7177. * Mouse up or outside the plot area
  7178. */
  7179. function drop() {
  7180. if (mouseTracker.selectionMarker) {
  7181. var selectionData = {
  7182. xAxis: [],
  7183. yAxis: []
  7184. },
  7185. selectionBox = mouseTracker.selectionMarker.getBBox(),
  7186. selectionLeft = selectionBox.x - chart.plotLeft,
  7187. selectionTop = selectionBox.y - chart.plotTop,
  7188. runZoom;
  7189. // a selection has been made
  7190. if (hasDragged) {
  7191. // record each axis' min and max
  7192. each(chart.axes, function (axis) {
  7193. if (axis.options.zoomEnabled !== false) {
  7194. var isXAxis = axis.isXAxis,
  7195. isHorizontal = chart.inverted ? !isXAxis : isXAxis,
  7196. selectionMin = axis.translate(
  7197. isHorizontal ?
  7198. selectionLeft :
  7199. chart.plotHeight - selectionTop - selectionBox.height,
  7200. true,
  7201. 0,
  7202. 0,
  7203. 1
  7204. ),
  7205. selectionMax = axis.translate(
  7206. isHorizontal ?
  7207. selectionLeft + selectionBox.width :
  7208. chart.plotHeight - selectionTop,
  7209. true,
  7210. 0,
  7211. 0,
  7212. 1
  7213. );
  7214. if (!isNaN(selectionMin) && !isNaN(selectionMax)) { // #859
  7215. selectionData[isXAxis ? 'xAxis' : 'yAxis'].push({
  7216. axis: axis,
  7217. min: mathMin(selectionMin, selectionMax), // for reversed axes,
  7218. max: mathMax(selectionMin, selectionMax)
  7219. });
  7220. runZoom = true;
  7221. }
  7222. }
  7223. });
  7224. if (runZoom) {
  7225. fireEvent(chart, 'selection', selectionData, function (args) { chart.zoom(args); });
  7226. }
  7227. }
  7228. mouseTracker.selectionMarker = mouseTracker.selectionMarker.destroy();
  7229. }
  7230. if (chart) { // it may be destroyed on mouse up - #877
  7231. css(container, { cursor: 'auto' });
  7232. chart.cancelClick = hasDragged; // #370
  7233. chart.mouseIsDown = hasDragged = false;
  7234. }
  7235. removeEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
  7236. }
  7237. /**
  7238. * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea.
  7239. */
  7240. mouseTracker.hideTooltipOnMouseMove = function (e) {
  7241. // Get e.pageX and e.pageY back in MooTools
  7242. washMouseEvent(e);
  7243. // If we're outside, hide the tooltip
  7244. if (mouseTracker.chartPosition && chart.hoverSeries && chart.hoverSeries.isCartesian &&
  7245. !chart.isInsidePlot(e.pageX - mouseTracker.chartPosition.left - chart.plotLeft,
  7246. e.pageY - mouseTracker.chartPosition.top - chart.plotTop)) {
  7247. mouseTracker.resetTracker();
  7248. }
  7249. };
  7250. /**
  7251. * When mouse leaves the container, hide the tooltip.
  7252. */
  7253. mouseTracker.hideTooltipOnMouseLeave = function () {
  7254. mouseTracker.resetTracker();
  7255. mouseTracker.chartPosition = null; // also reset the chart position, used in #149 fix
  7256. };
  7257. /*
  7258. * Record the starting position of a dragoperation
  7259. */
  7260. container.onmousedown = function (e) {
  7261. e = mouseTracker.normalizeMouseEvent(e);
  7262. // issue #295, dragging not always working in Firefox
  7263. if (!hasTouch && e.preventDefault) {
  7264. e.preventDefault();
  7265. }
  7266. // record the start position
  7267. chart.mouseIsDown = true;
  7268. chart.cancelClick = false;
  7269. chart.mouseDownX = mouseTracker.mouseDownX = e.chartX;
  7270. mouseTracker.mouseDownY = e.chartY;
  7271. addEvent(doc, hasTouch ? 'touchend' : 'mouseup', drop);
  7272. };
  7273. // The mousemove, touchmove and touchstart event handler
  7274. var mouseMove = function (e) {
  7275. // let the system handle multitouch operations like two finger scroll
  7276. // and pinching
  7277. if (e && e.touches && e.touches.length > 1) {
  7278. return;
  7279. }
  7280. // normalize
  7281. e = mouseTracker.normalizeMouseEvent(e);
  7282. if (!hasTouch) { // not for touch devices
  7283. e.returnValue = false;
  7284. }
  7285. var chartX = e.chartX,
  7286. chartY = e.chartY,
  7287. isOutsidePlot = !chart.isInsidePlot(chartX - chart.plotLeft, chartY - chart.plotTop);
  7288. // on touch devices, only trigger click if a handler is defined
  7289. if (hasTouch && e.type === 'touchstart') {
  7290. if (attr(e.target, 'isTracker')) {
  7291. if (!chart.runTrackerClick) {
  7292. e.preventDefault();
  7293. }
  7294. } else if (!chart.runChartClick && !isOutsidePlot) {
  7295. e.preventDefault();
  7296. }
  7297. }
  7298. // cancel on mouse outside
  7299. if (isOutsidePlot) {
  7300. /*if (!lastWasOutsidePlot) {
  7301. // reset the tracker
  7302. resetTracker();
  7303. }*/
  7304. // drop the selection if any and reset mouseIsDown and hasDragged
  7305. //drop();
  7306. if (chartX < chart.plotLeft) {
  7307. chartX = chart.plotLeft;
  7308. } else if (chartX > chart.plotLeft + chart.plotWidth) {
  7309. chartX = chart.plotLeft + chart.plotWidth;
  7310. }
  7311. if (chartY < chart.plotTop) {
  7312. chartY = chart.plotTop;
  7313. } else if (chartY > chart.plotTop + chart.plotHeight) {
  7314. chartY = chart.plotTop + chart.plotHeight;
  7315. }
  7316. }
  7317. if (chart.mouseIsDown && e.type !== 'touchstart') { // make selection
  7318. // determine if the mouse has moved more than 10px
  7319. hasDragged = Math.sqrt(
  7320. Math.pow(mouseTracker.mouseDownX - chartX, 2) +
  7321. Math.pow(mouseTracker.mouseDownY - chartY, 2)
  7322. );
  7323. if (hasDragged > 10) {
  7324. var clickedInside = chart.isInsidePlot(mouseTracker.mouseDownX - chart.plotLeft, mouseTracker.mouseDownY - chart.plotTop);
  7325. // make a selection
  7326. if (chart.hasCartesianSeries && (mouseTracker.zoomX || mouseTracker.zoomY) && clickedInside) {
  7327. if (!mouseTracker.selectionMarker) {
  7328. mouseTracker.selectionMarker = chart.renderer.rect(
  7329. chart.plotLeft,
  7330. chart.plotTop,
  7331. zoomHor ? 1 : chart.plotWidth,
  7332. zoomVert ? 1 : chart.plotHeight,
  7333. 0
  7334. )
  7335. .attr({
  7336. fill: mouseTracker.options.chart.selectionMarkerFill || 'rgba(69,114,167,0.25)',
  7337. zIndex: 7
  7338. })
  7339. .add();
  7340. }
  7341. }
  7342. // adjust the width of the selection marker
  7343. if (mouseTracker.selectionMarker && zoomHor) {
  7344. var xSize = chartX - mouseTracker.mouseDownX;
  7345. mouseTracker.selectionMarker.attr({
  7346. width: mathAbs(xSize),
  7347. x: (xSize > 0 ? 0 : xSize) + mouseTracker.mouseDownX
  7348. });
  7349. }
  7350. // adjust the height of the selection marker
  7351. if (mouseTracker.selectionMarker && zoomVert) {
  7352. var ySize = chartY - mouseTracker.mouseDownY;
  7353. mouseTracker.selectionMarker.attr({
  7354. height: mathAbs(ySize),
  7355. y: (ySize > 0 ? 0 : ySize) + mouseTracker.mouseDownY
  7356. });
  7357. }
  7358. // panning
  7359. if (clickedInside && !mouseTracker.selectionMarker && mouseTracker.options.chart.panning) {
  7360. chart.pan(chartX);
  7361. }
  7362. }
  7363. } else if (!isOutsidePlot) {
  7364. // show the tooltip
  7365. mouseTracker.onmousemove(e);
  7366. }
  7367. lastWasOutsidePlot = isOutsidePlot;
  7368. // when outside plot, allow touch-drag by returning true
  7369. return isOutsidePlot || !chart.hasCartesianSeries;
  7370. };
  7371. /*
  7372. * When the mouse enters the container, run mouseMove
  7373. */
  7374. container.onmousemove = mouseMove;
  7375. /*
  7376. * When the mouse leaves the container, hide the tracking (tooltip).
  7377. */
  7378. addEvent(container, 'mouseleave', mouseTracker.hideTooltipOnMouseLeave);
  7379. // issue #149 workaround
  7380. // The mouseleave event above does not always fire. Whenever the mouse is moving
  7381. // outside the plotarea, hide the tooltip
  7382. addEvent(doc, 'mousemove', mouseTracker.hideTooltipOnMouseMove);
  7383. container.ontouchstart = function (e) {
  7384. // For touch devices, use touchmove to zoom
  7385. if (mouseTracker.zoomX || mouseTracker.zoomY) {
  7386. container.onmousedown(e);
  7387. }
  7388. // Show tooltip and prevent the lower mouse pseudo event
  7389. mouseMove(e);
  7390. };
  7391. /*
  7392. * Allow dragging the finger over the chart to read the values on touch
  7393. * devices
  7394. */
  7395. container.ontouchmove = mouseMove;
  7396. /*
  7397. * Allow dragging the finger over the chart to read the values on touch
  7398. * devices
  7399. */
  7400. container.ontouchend = function () {
  7401. if (hasDragged) {
  7402. mouseTracker.resetTracker();
  7403. }
  7404. };
  7405. // MooTools 1.2.3 doesn't fire this in IE when using addEvent
  7406. container.onclick = function (e) {
  7407. var hoverPoint = chart.hoverPoint,
  7408. plotX,
  7409. plotY;
  7410. e = mouseTracker.normalizeMouseEvent(e);
  7411. e.cancelBubble = true; // IE specific
  7412. if (!chart.cancelClick) {
  7413. // Detect clicks on trackers or tracker groups, #783
  7414. if (hoverPoint && (attr(e.target, 'isTracker') || attr(e.target.parentNode, 'isTracker'))) {
  7415. plotX = hoverPoint.plotX;
  7416. plotY = hoverPoint.plotY;
  7417. // add page position info
  7418. extend(hoverPoint, {
  7419. pageX: mouseTracker.chartPosition.left + chart.plotLeft +
  7420. (chart.inverted ? chart.plotWidth - plotY : plotX),
  7421. pageY: mouseTracker.chartPosition.top + chart.plotTop +
  7422. (chart.inverted ? chart.plotHeight - plotX : plotY)
  7423. });
  7424. // the series click event
  7425. fireEvent(hoverPoint.series, 'click', extend(e, {
  7426. point: hoverPoint
  7427. }));
  7428. // the point click event
  7429. hoverPoint.firePointEvent('click', e);
  7430. } else {
  7431. extend(e, mouseTracker.getMouseCoordinates(e));
  7432. // fire a click event in the chart
  7433. if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
  7434. fireEvent(chart, 'click', e);
  7435. }
  7436. }
  7437. }
  7438. };
  7439. },
  7440. /**
  7441. * Destroys the MouseTracker object and disconnects DOM events.
  7442. */
  7443. destroy: function () {
  7444. var mouseTracker = this,
  7445. chart = mouseTracker.chart,
  7446. container = chart.container;
  7447. // Destroy the tracker group element
  7448. if (chart.trackerGroup) {
  7449. chart.trackerGroup = chart.trackerGroup.destroy();
  7450. }
  7451. removeEvent(container, 'mouseleave', mouseTracker.hideTooltipOnMouseLeave);
  7452. removeEvent(doc, 'mousemove', mouseTracker.hideTooltipOnMouseMove);
  7453. container.onclick = container.onmousedown = container.onmousemove = container.ontouchstart = container.ontouchend = container.ontouchmove = null;
  7454. // memory and CPU leak
  7455. clearInterval(this.tooltipInterval);
  7456. },
  7457. // Run MouseTracker
  7458. init: function (chart, options) {
  7459. if (!chart.trackerGroup) {
  7460. chart.trackerGroup = chart.renderer.g('tracker')
  7461. .attr({ zIndex: 9 })
  7462. .add();
  7463. }
  7464. if (options.enabled) {
  7465. chart.tooltip = new Tooltip(chart, options);
  7466. // set the fixed interval ticking for the smooth tooltip
  7467. this.tooltipInterval = setInterval(function () { chart.tooltip.tick(); }, 32);
  7468. }
  7469. this.setDOMEvents();
  7470. }
  7471. };
  7472. /**
  7473. * The overview of the chart's series
  7474. */
  7475. function Legend(chart) {
  7476. this.init(chart);
  7477. }
  7478. Legend.prototype = {
  7479. /**
  7480. * Initialize the legend
  7481. */
  7482. init: function (chart) {
  7483. var legend = this,
  7484. options = legend.options = chart.options.legend;
  7485. if (!options.enabled) {
  7486. return;
  7487. }
  7488. var //style = options.style || {}, // deprecated
  7489. itemStyle = options.itemStyle,
  7490. padding = pick(options.padding, 8),
  7491. itemMarginTop = options.itemMarginTop || 0;
  7492. legend.baseline = pInt(itemStyle.fontSize) + 3 + itemMarginTop; // used in Series prototype
  7493. legend.itemStyle = itemStyle;
  7494. legend.itemHiddenStyle = merge(itemStyle, options.itemHiddenStyle);
  7495. legend.itemMarginTop = itemMarginTop;
  7496. legend.padding = padding;
  7497. legend.initialItemX = padding;
  7498. legend.initialItemY = padding - 5; // 5 is the number of pixels above the text
  7499. legend.maxItemWidth = 0;
  7500. legend.chart = chart;
  7501. //legend.allItems = UNDEFINED;
  7502. //legend.legendWidth = UNDEFINED;
  7503. //legend.legendHeight = UNDEFINED;
  7504. //legend.offsetWidth = UNDEFINED;
  7505. legend.itemHeight = 0;
  7506. legend.lastLineHeight = 0;
  7507. //legend.itemX = UNDEFINED;
  7508. //legend.itemY = UNDEFINED;
  7509. //legend.lastItemY = UNDEFINED;
  7510. // Elements
  7511. //legend.group = UNDEFINED;
  7512. //legend.box = UNDEFINED;
  7513. // run legend
  7514. legend.render();
  7515. // move checkboxes
  7516. addEvent(legend.chart, 'endResize', function () { legend.positionCheckboxes(); });
  7517. /* // expose
  7518. return {
  7519. colorizeItem: colorizeItem,
  7520. destroyItem: destroyItem,
  7521. render: render,
  7522. destroy: destroy,
  7523. getLegendWidth: getLegendWidth,
  7524. getLegendHeight: getLegendHeight
  7525. };*/
  7526. },
  7527. /**
  7528. * Set the colors for the legend item
  7529. * @param {Object} item A Series or Point instance
  7530. * @param {Object} visible Dimmed or colored
  7531. */
  7532. colorizeItem: function (item, visible) {
  7533. var legend = this,
  7534. options = legend.options,
  7535. legendItem = item.legendItem,
  7536. legendLine = item.legendLine,
  7537. legendSymbol = item.legendSymbol,
  7538. hiddenColor = legend.itemHiddenStyle.color,
  7539. textColor = visible ? options.itemStyle.color : hiddenColor,
  7540. symbolColor = visible ? item.color : hiddenColor;
  7541. if (legendItem) {
  7542. legendItem.css({ fill: textColor });
  7543. }
  7544. if (legendLine) {
  7545. legendLine.attr({ stroke: symbolColor });
  7546. }
  7547. if (legendSymbol) {
  7548. legendSymbol.attr({
  7549. stroke: symbolColor,
  7550. fill: symbolColor
  7551. });
  7552. }
  7553. },
  7554. /**
  7555. * Position the legend item
  7556. * @param {Object} item A Series or Point instance
  7557. */
  7558. positionItem: function (item) {
  7559. var legend = this,
  7560. options = legend.options,
  7561. symbolPadding = options.symbolPadding,
  7562. ltr = !options.rtl,
  7563. legendItemPos = item._legendItemPos,
  7564. itemX = legendItemPos[0],
  7565. itemY = legendItemPos[1],
  7566. checkbox = item.checkbox;
  7567. if (item.legendGroup) {
  7568. item.legendGroup.translate(
  7569. ltr ? itemX : legend.legendWidth - itemX - 2 * symbolPadding - 4,
  7570. itemY
  7571. );
  7572. }
  7573. if (checkbox) {
  7574. checkbox.x = itemX;
  7575. checkbox.y = itemY;
  7576. }
  7577. },
  7578. /**
  7579. * Destroy a single legend item
  7580. * @param {Object} item The series or point
  7581. */
  7582. destroyItem: function (item) {
  7583. var checkbox = item.checkbox;
  7584. // destroy SVG elements
  7585. each(['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'], function (key) {
  7586. if (item[key]) {
  7587. item[key].destroy();
  7588. }
  7589. });
  7590. if (checkbox) {
  7591. discardElement(item.checkbox);
  7592. }
  7593. },
  7594. /**
  7595. * Destroys the legend.
  7596. */
  7597. destroy: function () {
  7598. var legend = this,
  7599. legendGroup = legend.group,
  7600. box = legend.box;
  7601. if (box) {
  7602. legend.box = box.destroy();
  7603. }
  7604. if (legendGroup) {
  7605. legend.group = legendGroup.destroy();
  7606. }
  7607. },
  7608. /**
  7609. * Position the checkboxes after the width is determined
  7610. */
  7611. positionCheckboxes: function () {
  7612. var legend = this;
  7613. each(legend.allItems, function (item) {
  7614. var checkbox = item.checkbox,
  7615. alignAttr = legend.group.alignAttr;
  7616. if (checkbox) {
  7617. css(checkbox, {
  7618. left: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 20) + PX,
  7619. top: (alignAttr.translateY + checkbox.y + 3) + PX
  7620. });
  7621. }
  7622. });
  7623. },
  7624. /**
  7625. * Render a single specific legend item
  7626. * @param {Object} item A series or point
  7627. */
  7628. renderItem: function (item) {
  7629. var legend = this,
  7630. chart = legend.chart,
  7631. renderer = chart.renderer,
  7632. options = legend.options,
  7633. horizontal = options.layout === 'horizontal',
  7634. symbolWidth = options.symbolWidth,
  7635. symbolPadding = options.symbolPadding,
  7636. itemStyle = legend.itemStyle,
  7637. itemHiddenStyle = legend.itemHiddenStyle,
  7638. padding = legend.padding,
  7639. ltr = !options.rtl,
  7640. itemHeight,
  7641. widthOption = options.width,
  7642. itemMarginBottom = options.itemMarginBottom || 0,
  7643. itemMarginTop = legend.itemMarginTop,
  7644. initialItemX = legend.initialItemX,
  7645. bBox,
  7646. itemWidth,
  7647. li = item.legendItem,
  7648. series = item.series || item,
  7649. itemOptions = series.options,
  7650. showCheckbox = itemOptions.showCheckbox;
  7651. if (!li) { // generate it once, later move it
  7652. // Generate the group box
  7653. // A group to hold the symbol and text. Text is to be appended in Legend class.
  7654. item.legendGroup = renderer.g('legend-item')
  7655. .attr({ zIndex: 1 })
  7656. .add(legend.scrollGroup);
  7657. // Draw the legend symbol inside the group box
  7658. series.drawLegendSymbol(legend, item);
  7659. // Generate the list item text and add it to the group
  7660. item.legendItem = li = renderer.text(
  7661. options.labelFormatter.call(item),
  7662. ltr ? symbolWidth + symbolPadding : -symbolPadding,
  7663. legend.baseline,
  7664. options.useHTML
  7665. )
  7666. .css(merge(item.visible ? itemStyle : itemHiddenStyle)) // merge to prevent modifying original (#1021)
  7667. .attr({
  7668. align: ltr ? 'left' : 'right',
  7669. zIndex: 2
  7670. })
  7671. .add(item.legendGroup);
  7672. // Set the events on the item group
  7673. item.legendGroup.on('mouseover', function () {
  7674. item.setState(HOVER_STATE);
  7675. li.css(legend.options.itemHoverStyle);
  7676. })
  7677. .on('mouseout', function () {
  7678. li.css(item.visible ? itemStyle : itemHiddenStyle);
  7679. item.setState();
  7680. })
  7681. .on('click', function (event) {
  7682. var strLegendItemClick = 'legendItemClick',
  7683. fnLegendItemClick = function () {
  7684. item.setVisible();
  7685. };
  7686. // Pass over the click/touch event. #4.
  7687. event = {
  7688. browserEvent: event
  7689. };
  7690. // click the name or symbol
  7691. if (item.firePointEvent) { // point
  7692. item.firePointEvent(strLegendItemClick, event, fnLegendItemClick);
  7693. } else {
  7694. fireEvent(item, strLegendItemClick, event, fnLegendItemClick);
  7695. }
  7696. });
  7697. // Colorize the items
  7698. legend.colorizeItem(item, item.visible);
  7699. // add the HTML checkbox on top
  7700. if (itemOptions && showCheckbox) {
  7701. item.checkbox = createElement('input', {
  7702. type: 'checkbox',
  7703. checked: item.selected,
  7704. defaultChecked: item.selected // required by IE7
  7705. }, options.itemCheckboxStyle, chart.container);
  7706. addEvent(item.checkbox, 'click', function (event) {
  7707. var target = event.target;
  7708. fireEvent(item, 'checkboxClick', {
  7709. checked: target.checked
  7710. },
  7711. function () {
  7712. item.select();
  7713. }
  7714. );
  7715. });
  7716. }
  7717. }
  7718. // calculate the positions for the next line
  7719. bBox = li.getBBox();
  7720. itemWidth = item.legendItemWidth =
  7721. options.itemWidth || symbolWidth + symbolPadding + bBox.width + padding +
  7722. (showCheckbox ? 20 : 0);
  7723. legend.itemHeight = itemHeight = bBox.height;
  7724. // if the item exceeds the width, start a new line
  7725. if (horizontal && legend.itemX - initialItemX + itemWidth >
  7726. (widthOption || (chart.chartWidth - 2 * padding - initialItemX))) {
  7727. legend.itemX = initialItemX;
  7728. legend.itemY += itemMarginTop + legend.lastLineHeight + itemMarginBottom;
  7729. legend.lastLineHeight = 0; // reset for next line
  7730. }
  7731. // If the item exceeds the height, start a new column
  7732. /*if (!horizontal && legend.itemY + options.y + itemHeight > chart.chartHeight - spacingTop - spacingBottom) {
  7733. legend.itemY = legend.initialItemY;
  7734. legend.itemX += legend.maxItemWidth;
  7735. legend.maxItemWidth = 0;
  7736. }*/
  7737. // Set the edge positions
  7738. legend.maxItemWidth = mathMax(legend.maxItemWidth, itemWidth);
  7739. legend.lastItemY = itemMarginTop + legend.itemY + itemMarginBottom;
  7740. legend.lastLineHeight = mathMax(itemHeight, legend.lastLineHeight); // #915
  7741. // cache the position of the newly generated or reordered items
  7742. item._legendItemPos = [legend.itemX, legend.itemY];
  7743. // advance
  7744. if (horizontal) {
  7745. legend.itemX += itemWidth;
  7746. } else {
  7747. legend.itemY += itemMarginTop + itemHeight + itemMarginBottom;
  7748. legend.lastLineHeight = itemHeight;
  7749. }
  7750. // the width of the widest item
  7751. legend.offsetWidth = widthOption || mathMax(
  7752. horizontal ? legend.itemX - initialItemX : itemWidth,
  7753. legend.offsetWidth
  7754. );
  7755. },
  7756. /**
  7757. * Render the legend. This method can be called both before and after
  7758. * chart.render. If called after, it will only rearrange items instead
  7759. * of creating new ones.
  7760. */
  7761. render: function () {
  7762. var legend = this,
  7763. chart = legend.chart,
  7764. renderer = chart.renderer,
  7765. legendGroup = legend.group,
  7766. allItems,
  7767. display,
  7768. legendWidth,
  7769. legendHeight,
  7770. box = legend.box,
  7771. options = legend.options,
  7772. padding = legend.padding,
  7773. legendBorderWidth = options.borderWidth,
  7774. legendBackgroundColor = options.backgroundColor;
  7775. legend.itemX = legend.initialItemX;
  7776. legend.itemY = legend.initialItemY;
  7777. legend.offsetWidth = 0;
  7778. legend.lastItemY = 0;
  7779. if (!legendGroup) {
  7780. legend.group = legendGroup = renderer.g('legend')
  7781. // #414, #759. Trackers will be drawn above the legend, but we have
  7782. // to sacrifice that because tooltips need to be above the legend
  7783. // and trackers above tooltips
  7784. .attr({ zIndex: 7 })
  7785. .add();
  7786. legend.contentGroup = renderer.g()
  7787. .attr({ zIndex: 1 }) // above background
  7788. .add(legendGroup);
  7789. legend.scrollGroup = renderer.g()
  7790. .add(legend.contentGroup);
  7791. legend.clipRect = renderer.clipRect(0, 0, 9999, chart.chartHeight);
  7792. legend.contentGroup.clip(legend.clipRect);
  7793. }
  7794. // add each series or point
  7795. allItems = [];
  7796. each(chart.series, function (serie) {
  7797. var seriesOptions = serie.options;
  7798. if (!seriesOptions.showInLegend) {
  7799. return;
  7800. }
  7801. // use points or series for the legend item depending on legendType
  7802. allItems = allItems.concat(
  7803. serie.legendItems ||
  7804. (seriesOptions.legendType === 'point' ?
  7805. serie.data :
  7806. serie)
  7807. );
  7808. });
  7809. // sort by legendIndex
  7810. stableSort(allItems, function (a, b) {
  7811. return (a.options.legendIndex || 0) - (b.options.legendIndex || 0);
  7812. });
  7813. // reversed legend
  7814. if (options.reversed) {
  7815. allItems.reverse();
  7816. }
  7817. legend.allItems = allItems;
  7818. legend.display = display = !!allItems.length;
  7819. // render the items
  7820. each(allItems, function (item) {
  7821. legend.renderItem(item);
  7822. });
  7823. // Draw the border
  7824. legendWidth = options.width || legend.offsetWidth;
  7825. legendHeight = legend.lastItemY + legend.lastLineHeight;
  7826. legendHeight = legend.handleOverflow(legendHeight);
  7827. if (legendBorderWidth || legendBackgroundColor) {
  7828. legendWidth += padding;
  7829. legendHeight += padding;
  7830. if (!box) {
  7831. legend.box = box = renderer.rect(
  7832. 0,
  7833. 0,
  7834. legendWidth,
  7835. legendHeight,
  7836. options.borderRadius,
  7837. legendBorderWidth || 0
  7838. ).attr({
  7839. stroke: options.borderColor,
  7840. 'stroke-width': legendBorderWidth || 0,
  7841. fill: legendBackgroundColor || NONE
  7842. })
  7843. .add(legendGroup)
  7844. .shadow(options.shadow);
  7845. box.isNew = true;
  7846. } else if (legendWidth > 0 && legendHeight > 0) {
  7847. box[box.isNew ? 'attr' : 'animate'](
  7848. box.crisp(null, null, null, legendWidth, legendHeight)
  7849. );
  7850. box.isNew = false;
  7851. }
  7852. // hide the border if no items
  7853. box[display ? 'show' : 'hide']();
  7854. }
  7855. legend.legendWidth = legendWidth;
  7856. legend.legendHeight = legendHeight;
  7857. // Now that the legend width and height are established, put the items in the
  7858. // final position
  7859. each(allItems, function (item) {
  7860. legend.positionItem(item);
  7861. });
  7862. // 1.x compatibility: positioning based on style
  7863. /*var props = ['left', 'right', 'top', 'bottom'],
  7864. prop,
  7865. i = 4;
  7866. while (i--) {
  7867. prop = props[i];
  7868. if (options.style[prop] && options.style[prop] !== 'auto') {
  7869. options[i < 2 ? 'align' : 'verticalAlign'] = prop;
  7870. options[i < 2 ? 'x' : 'y'] = pInt(options.style[prop]) * (i % 2 ? -1 : 1);
  7871. }
  7872. }*/
  7873. if (display) {
  7874. legendGroup.align(extend({
  7875. width: legendWidth,
  7876. height: legendHeight
  7877. }, options), true, chart.spacingBox);
  7878. }
  7879. if (!chart.isResizing) {
  7880. this.positionCheckboxes();
  7881. }
  7882. },
  7883. /**
  7884. * Set up the overflow handling by adding navigation with up and down arrows below the
  7885. * legend.
  7886. */
  7887. handleOverflow: function (legendHeight) {
  7888. var legend = this,
  7889. chart = this.chart,
  7890. renderer = chart.renderer,
  7891. pageCount,
  7892. options = this.options,
  7893. optionsY = options.y,
  7894. alignTop = options.verticalAlign === 'top',
  7895. spaceHeight = chart.spacingBox.height + (alignTop ? -optionsY : optionsY) - this.padding,
  7896. maxHeight = options.maxHeight, // docs
  7897. clipHeight,
  7898. clipRect = this.clipRect,
  7899. navOptions = options.navigation,
  7900. animation = pick(navOptions.animation, true),
  7901. arrowSize = navOptions.arrowSize || 12,
  7902. nav = this.nav;
  7903. // Adjust the height
  7904. if (options.layout === 'horizontal') {
  7905. spaceHeight /= 2;
  7906. }
  7907. if (maxHeight) {
  7908. spaceHeight = mathMin(spaceHeight, maxHeight);
  7909. }
  7910. // Reset the legend height and adjust the clipping rectangle
  7911. if (legendHeight > spaceHeight) {
  7912. this.clipHeight = clipHeight = spaceHeight - 20;
  7913. this.pageCount = pageCount = mathCeil(legendHeight / clipHeight);
  7914. this.currentPage = pick(this.currentPage, 1);
  7915. this.fullHeight = legendHeight;
  7916. clipRect.attr({
  7917. height: clipHeight
  7918. });
  7919. // Add navigation elements
  7920. if (!nav) {
  7921. this.nav = nav = renderer.g().attr({ zIndex: 1 }).add(this.group);
  7922. this.up = renderer.symbol('triangle', 0, 0, arrowSize, arrowSize)
  7923. .on('click', function () {
  7924. legend.scroll(-1, animation);
  7925. })
  7926. .add(nav);
  7927. this.pager = renderer.text('', 15, 10)
  7928. .css(navOptions.style)
  7929. .add(nav);
  7930. this.down = renderer.symbol('triangle-down', 0, 0, arrowSize, arrowSize)
  7931. .on('click', function () {
  7932. legend.scroll(1, animation);
  7933. })
  7934. .add(nav);
  7935. }
  7936. // Set initial position
  7937. legend.scroll(0);
  7938. legendHeight = spaceHeight;
  7939. } else if (nav) {
  7940. clipRect.attr({
  7941. height: chart.chartHeight
  7942. });
  7943. nav.hide();
  7944. this.scrollGroup.attr({
  7945. translateY: 1
  7946. });
  7947. }
  7948. return legendHeight;
  7949. },
  7950. /**
  7951. * Scroll the legend by a number of pages
  7952. * @param {Object} scrollBy
  7953. * @param {Object} animation
  7954. */
  7955. scroll: function (scrollBy, animation) {
  7956. var pageCount = this.pageCount,
  7957. currentPage = this.currentPage + scrollBy,
  7958. clipHeight = this.clipHeight,
  7959. navOptions = this.options.navigation,
  7960. activeColor = navOptions.activeColor,
  7961. inactiveColor = navOptions.inactiveColor,
  7962. pager = this.pager,
  7963. padding = this.padding;
  7964. // When resizing while looking at the last page
  7965. if (currentPage > pageCount) {
  7966. currentPage = pageCount;
  7967. }
  7968. if (currentPage > 0) {
  7969. if (animation !== UNDEFINED) {
  7970. setAnimation(animation, this.chart);
  7971. }
  7972. this.nav.attr({
  7973. translateX: padding,
  7974. translateY: clipHeight + 7,
  7975. visibility: VISIBLE
  7976. });
  7977. this.up.attr({
  7978. fill: currentPage === 1 ? inactiveColor : activeColor
  7979. })
  7980. .css({
  7981. cursor: currentPage === 1 ? 'default' : 'pointer'
  7982. });
  7983. pager.attr({
  7984. text: currentPage + '/' + this.pageCount
  7985. });
  7986. this.down.attr({
  7987. x: 18 + this.pager.getBBox().width, // adjust to text width
  7988. fill: currentPage === pageCount ? inactiveColor : activeColor
  7989. })
  7990. .css({
  7991. cursor: currentPage === pageCount ? 'default' : 'pointer'
  7992. });
  7993. this.scrollGroup.animate({
  7994. translateY: -mathMin(clipHeight * (currentPage - 1), this.fullHeight - clipHeight + padding) + 1
  7995. });
  7996. pager.attr({
  7997. text: currentPage + '/' + pageCount
  7998. });
  7999. this.currentPage = currentPage;
  8000. }
  8001. }
  8002. };
  8003. /**
  8004. * The chart class
  8005. * @param {Object} options
  8006. * @param {Function} callback Function to run when the chart has loaded
  8007. */
  8008. function Chart(userOptions, callback) {
  8009. // Handle regular options
  8010. var options,
  8011. seriesOptions = userOptions.series; // skip merging data points to increase performance
  8012. userOptions.series = null;
  8013. options = merge(defaultOptions, userOptions); // do the merge
  8014. options.series = userOptions.series = seriesOptions; // set back the series data
  8015. var optionsChart = options.chart,
  8016. optionsMargin = optionsChart.margin,
  8017. margin = isObject(optionsMargin) ?
  8018. optionsMargin :
  8019. [optionsMargin, optionsMargin, optionsMargin, optionsMargin];
  8020. this.optionsMarginTop = pick(optionsChart.marginTop, margin[0]);
  8021. this.optionsMarginRight = pick(optionsChart.marginRight, margin[1]);
  8022. this.optionsMarginBottom = pick(optionsChart.marginBottom, margin[2]);
  8023. this.optionsMarginLeft = pick(optionsChart.marginLeft, margin[3]);
  8024. var chartEvents = optionsChart.events;
  8025. this.runChartClick = chartEvents && !!chartEvents.click;
  8026. this.callback = callback;
  8027. this.isResizing = 0;
  8028. this.options = options;
  8029. //chartTitleOptions = UNDEFINED;
  8030. //chartSubtitleOptions = UNDEFINED;
  8031. this.axes = [];
  8032. this.series = [];
  8033. this.hasCartesianSeries = optionsChart.showAxes;
  8034. //this.axisOffset = UNDEFINED;
  8035. //this.maxTicks = UNDEFINED; // handle the greatest amount of ticks on grouped axes
  8036. //this.inverted = UNDEFINED;
  8037. //this.loadingShown = UNDEFINED;
  8038. //this.container = UNDEFINED;
  8039. //this.chartWidth = UNDEFINED;
  8040. //this.chartHeight = UNDEFINED;
  8041. //this.marginRight = UNDEFINED;
  8042. //this.marginBottom = UNDEFINED;
  8043. //this.containerWidth = UNDEFINED;
  8044. //this.containerHeight = UNDEFINED;
  8045. //this.oldChartWidth = UNDEFINED;
  8046. //this.oldChartHeight = UNDEFINED;
  8047. //this.renderTo = UNDEFINED;
  8048. //this.renderToClone = UNDEFINED;
  8049. //this.tracker = UNDEFINED;
  8050. //this.spacingBox = UNDEFINED
  8051. //this.legend = UNDEFINED;
  8052. // Elements
  8053. //this.chartBackground = UNDEFINED;
  8054. //this.plotBackground = UNDEFINED;
  8055. //this.plotBGImage = UNDEFINED;
  8056. //this.plotBorder = UNDEFINED;
  8057. //this.loadingDiv = UNDEFINED;
  8058. //this.loadingSpan = UNDEFINED;
  8059. this.init(chartEvents);
  8060. }
  8061. Chart.prototype = {
  8062. /**
  8063. * Initialize an individual series, called internally before render time
  8064. */
  8065. initSeries: function (options) {
  8066. var chart = this,
  8067. optionsChart = chart.options.chart,
  8068. type = options.type || optionsChart.type || optionsChart.defaultSeriesType,
  8069. series = new seriesTypes[type]();
  8070. series.init(this, options);
  8071. return series;
  8072. },
  8073. /**
  8074. * Add a series dynamically after time
  8075. *
  8076. * @param {Object} options The config options
  8077. * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true.
  8078. * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
  8079. * configuration
  8080. *
  8081. * @return {Object} series The newly created series object
  8082. */
  8083. addSeries: function (options, redraw, animation) {
  8084. var series,
  8085. chart = this;
  8086. if (options) {
  8087. setAnimation(animation, chart);
  8088. redraw = pick(redraw, true); // defaults to true
  8089. fireEvent(chart, 'addSeries', { options: options }, function () {
  8090. chart.initSeries(options);
  8091. //series = chart.initSeries(options);
  8092. //series.isDirty = true;
  8093. chart.isDirtyLegend = true; // the series array is out of sync with the display
  8094. if (redraw) {
  8095. chart.redraw();
  8096. }
  8097. });
  8098. }
  8099. return series;
  8100. },
  8101. /**
  8102. * Check whether a given point is within the plot area
  8103. *
  8104. * @param {Number} x Pixel x relative to the plot area
  8105. * @param {Number} y Pixel y relative to the plot area
  8106. */
  8107. isInsidePlot: function (x, y) {
  8108. return x >= 0 &&
  8109. x <= this.plotWidth &&
  8110. y >= 0 &&
  8111. y <= this.plotHeight;
  8112. },
  8113. /**
  8114. * Adjust all axes tick amounts
  8115. */
  8116. adjustTickAmounts: function () {
  8117. if (this.options.chart.alignTicks !== false) {
  8118. each(this.axes, function (axis) {
  8119. axis.adjustTickAmount();
  8120. });
  8121. }
  8122. this.maxTicks = null;
  8123. },
  8124. /**
  8125. * Redraw legend, axes or series based on updated data
  8126. *
  8127. * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
  8128. * configuration
  8129. */
  8130. redraw: function (animation) {
  8131. var chart = this,
  8132. axes = chart.axes,
  8133. series = chart.series,
  8134. tracker = chart.tracker,
  8135. legend = chart.legend,
  8136. redrawLegend = chart.isDirtyLegend,
  8137. hasStackedSeries,
  8138. isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed?
  8139. seriesLength = series.length,
  8140. i = seriesLength,
  8141. clipRect = chart.clipRect,
  8142. serie,
  8143. renderer = chart.renderer,
  8144. isHiddenChart = renderer.isHidden();
  8145. setAnimation(animation, chart);
  8146. if (isHiddenChart) {
  8147. chart.cloneRenderTo();
  8148. }
  8149. // link stacked series
  8150. while (i--) {
  8151. serie = series[i];
  8152. if (serie.isDirty && serie.options.stacking) {
  8153. hasStackedSeries = true;
  8154. break;
  8155. }
  8156. }
  8157. if (hasStackedSeries) { // mark others as dirty
  8158. i = seriesLength;
  8159. while (i--) {
  8160. serie = series[i];
  8161. if (serie.options.stacking) {
  8162. serie.isDirty = true;
  8163. }
  8164. }
  8165. }
  8166. // handle updated data in the series
  8167. each(series, function (serie) {
  8168. if (serie.isDirty) { // prepare the data so axis can read it
  8169. if (serie.options.legendType === 'point') {
  8170. redrawLegend = true;
  8171. }
  8172. }
  8173. });
  8174. // handle added or removed series
  8175. if (redrawLegend && legend.options.enabled) { // series or pie points are added or removed
  8176. // draw legend graphics
  8177. legend.render();
  8178. chart.isDirtyLegend = false;
  8179. }
  8180. if (chart.hasCartesianSeries) {
  8181. if (!chart.isResizing) {
  8182. // reset maxTicks
  8183. chart.maxTicks = null;
  8184. // set axes scales
  8185. each(axes, function (axis) {
  8186. axis.setScale();
  8187. });
  8188. }
  8189. chart.adjustTickAmounts();
  8190. chart.getMargins();
  8191. // redraw axes
  8192. each(axes, function (axis) {
  8193. // Fire 'afterSetExtremes' only if extremes are set
  8194. if (axis.isDirtyExtremes) { // #821
  8195. axis.isDirtyExtremes = false;
  8196. fireEvent(axis, 'afterSetExtremes', axis.getExtremes()); // #747, #751
  8197. }
  8198. if (axis.isDirty || isDirtyBox || hasStackedSeries) {
  8199. axis.redraw();
  8200. isDirtyBox = true; // #792
  8201. }
  8202. });
  8203. }
  8204. // the plot areas size has changed
  8205. if (isDirtyBox) {
  8206. chart.drawChartBox();
  8207. // move clip rect
  8208. if (clipRect) {
  8209. stop(clipRect);
  8210. clipRect.animate({ // for chart resize
  8211. width: chart.plotSizeX,
  8212. height: chart.plotSizeY + 1
  8213. });
  8214. }
  8215. }
  8216. // redraw affected series
  8217. each(series, function (serie) {
  8218. if (serie.isDirty && serie.visible &&
  8219. (!serie.isCartesian || serie.xAxis)) { // issue #153
  8220. serie.redraw();
  8221. }
  8222. });
  8223. // move tooltip or reset
  8224. if (tracker && tracker.resetTracker) {
  8225. tracker.resetTracker(true);
  8226. }
  8227. // redraw if canvas
  8228. renderer.draw();
  8229. // fire the event
  8230. fireEvent(chart, 'redraw'); // jQuery breaks this when calling it from addEvent. Overwrites chart.redraw
  8231. if (isHiddenChart) {
  8232. chart.cloneRenderTo(true);
  8233. }
  8234. },
  8235. /**
  8236. * Dim the chart and show a loading text or symbol
  8237. * @param {String} str An optional text to show in the loading label instead of the default one
  8238. */
  8239. showLoading: function (str) {
  8240. var chart = this,
  8241. options = chart.options,
  8242. loadingDiv = chart.loadingDiv;
  8243. var loadingOptions = options.loading;
  8244. // create the layer at the first call
  8245. if (!loadingDiv) {
  8246. chart.loadingDiv = loadingDiv = createElement(DIV, {
  8247. className: PREFIX + 'loading'
  8248. }, extend(loadingOptions.style, {
  8249. left: chart.plotLeft + PX,
  8250. top: chart.plotTop + PX,
  8251. width: chart.plotWidth + PX,
  8252. height: chart.plotHeight + PX,
  8253. zIndex: 10,
  8254. display: NONE
  8255. }), chart.container);
  8256. chart.loadingSpan = createElement(
  8257. 'span',
  8258. null,
  8259. loadingOptions.labelStyle,
  8260. loadingDiv
  8261. );
  8262. }
  8263. // update text
  8264. chart.loadingSpan.innerHTML = str || options.lang.loading;
  8265. // show it
  8266. if (!chart.loadingShown) {
  8267. css(loadingDiv, { opacity: 0, display: '' });
  8268. animate(loadingDiv, {
  8269. opacity: loadingOptions.style.opacity
  8270. }, {
  8271. duration: loadingOptions.showDuration || 0
  8272. });
  8273. chart.loadingShown = true;
  8274. }
  8275. },
  8276. /**
  8277. * Hide the loading layer
  8278. */
  8279. hideLoading: function () {
  8280. var options = this.options,
  8281. loadingDiv = this.loadingDiv;
  8282. if (loadingDiv) {
  8283. animate(loadingDiv, {
  8284. opacity: 0
  8285. }, {
  8286. duration: options.loading.hideDuration || 100,
  8287. complete: function () {
  8288. css(loadingDiv, { display: NONE });
  8289. }
  8290. });
  8291. }
  8292. this.loadingShown = false;
  8293. },
  8294. /**
  8295. * Get an axis, series or point object by id.
  8296. * @param id {String} The id as given in the configuration options
  8297. */
  8298. get: function (id) {
  8299. var chart = this,
  8300. axes = chart.axes,
  8301. series = chart.series;
  8302. var i,
  8303. j,
  8304. points;
  8305. // search axes
  8306. for (i = 0; i < axes.length; i++) {
  8307. if (axes[i].options.id === id) {
  8308. return axes[i];
  8309. }
  8310. }
  8311. // search series
  8312. for (i = 0; i < series.length; i++) {
  8313. if (series[i].options.id === id) {
  8314. return series[i];
  8315. }
  8316. }
  8317. // search points
  8318. for (i = 0; i < series.length; i++) {
  8319. points = series[i].points || [];
  8320. for (j = 0; j < points.length; j++) {
  8321. if (points[j].id === id) {
  8322. return points[j];
  8323. }
  8324. }
  8325. }
  8326. return null;
  8327. },
  8328. /**
  8329. * Create the Axis instances based on the config options
  8330. */
  8331. getAxes: function () {
  8332. var chart = this,
  8333. options = this.options;
  8334. var xAxisOptions = options.xAxis || {},
  8335. yAxisOptions = options.yAxis || {},
  8336. optionsArray,
  8337. axis;
  8338. // make sure the options are arrays and add some members
  8339. xAxisOptions = splat(xAxisOptions);
  8340. each(xAxisOptions, function (axis, i) {
  8341. axis.index = i;
  8342. axis.isX = true;
  8343. });
  8344. yAxisOptions = splat(yAxisOptions);
  8345. each(yAxisOptions, function (axis, i) {
  8346. axis.index = i;
  8347. });
  8348. // concatenate all axis options into one array
  8349. optionsArray = xAxisOptions.concat(yAxisOptions);
  8350. each(optionsArray, function (axisOptions) {
  8351. axis = new Axis(chart, axisOptions);
  8352. });
  8353. chart.adjustTickAmounts();
  8354. },
  8355. /**
  8356. * Get the currently selected points from all series
  8357. */
  8358. getSelectedPoints: function () {
  8359. var points = [];
  8360. each(this.series, function (serie) {
  8361. points = points.concat(grep(serie.points, function (point) {
  8362. return point.selected;
  8363. }));
  8364. });
  8365. return points;
  8366. },
  8367. /**
  8368. * Get the currently selected series
  8369. */
  8370. getSelectedSeries: function () {
  8371. return grep(this.series, function (serie) {
  8372. return serie.selected;
  8373. });
  8374. },
  8375. /**
  8376. * Display the zoom button
  8377. */
  8378. showResetZoom: function () {
  8379. var chart = this,
  8380. lang = defaultOptions.lang,
  8381. btnOptions = chart.options.chart.resetZoomButton,
  8382. theme = btnOptions.theme,
  8383. states = theme.states,
  8384. box = btnOptions.relativeTo === 'chart' ? null : {
  8385. x: chart.plotLeft,
  8386. y: chart.plotTop,
  8387. width: chart.plotWidth,
  8388. height: chart.plotHeight
  8389. };
  8390. this.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, function () { chart.zoomOut(); }, theme, states && states.hover)
  8391. .attr({
  8392. align: btnOptions.position.align,
  8393. title: lang.resetZoomTitle
  8394. })
  8395. .add()
  8396. .align(btnOptions.position, false, box);
  8397. },
  8398. /**
  8399. * Zoom out to 1:1
  8400. */
  8401. zoomOut: function () {
  8402. var chart = this,
  8403. resetZoomButton = chart.resetZoomButton;
  8404. fireEvent(chart, 'selection', { resetSelection: true }, function () { chart.zoom(); });
  8405. if (resetZoomButton) {
  8406. chart.resetZoomButton = resetZoomButton.destroy();
  8407. }
  8408. },
  8409. /**
  8410. * Zoom into a given portion of the chart given by axis coordinates
  8411. * @param {Object} event
  8412. */
  8413. zoom: function (event) {
  8414. var chart = this,
  8415. optionsChart = chart.options.chart;
  8416. // add button to reset selection
  8417. var hasZoomed;
  8418. if (chart.resetZoomEnabled !== false && !chart.resetZoomButton) { // hook for Stock charts etc.
  8419. chart.showResetZoom();
  8420. }
  8421. // if zoom is called with no arguments, reset the axes
  8422. if (!event || event.resetSelection) {
  8423. each(chart.axes, function (axis) {
  8424. if (axis.options.zoomEnabled !== false) {
  8425. axis.setExtremes(null, null, false);
  8426. hasZoomed = true;
  8427. }
  8428. });
  8429. } else { // else, zoom in on all axes
  8430. each(event.xAxis.concat(event.yAxis), function (axisData) {
  8431. var axis = axisData.axis;
  8432. // don't zoom more than minRange
  8433. if (chart.tracker[axis.isXAxis ? 'zoomX' : 'zoomY']) {
  8434. axis.setExtremes(axisData.min, axisData.max, false);
  8435. hasZoomed = true;
  8436. }
  8437. });
  8438. }
  8439. // Redraw
  8440. if (hasZoomed) {
  8441. chart.redraw(
  8442. pick(optionsChart.animation, chart.pointCount < 100) // animation
  8443. );
  8444. }
  8445. },
  8446. /**
  8447. * Pan the chart by dragging the mouse across the pane. This function is called
  8448. * on mouse move, and the distance to pan is computed from chartX compared to
  8449. * the first chartX position in the dragging operation.
  8450. */
  8451. pan: function (chartX) {
  8452. var chart = this;
  8453. var xAxis = chart.xAxis[0],
  8454. mouseDownX = chart.mouseDownX,
  8455. halfPointRange = xAxis.pointRange / 2,
  8456. extremes = xAxis.getExtremes(),
  8457. newMin = xAxis.translate(mouseDownX - chartX, true) + halfPointRange,
  8458. newMax = xAxis.translate(mouseDownX + chart.plotWidth - chartX, true) - halfPointRange,
  8459. hoverPoints = chart.hoverPoints;
  8460. // remove active points for shared tooltip
  8461. if (hoverPoints) {
  8462. each(hoverPoints, function (point) {
  8463. point.setState();
  8464. });
  8465. }
  8466. if (xAxis.series.length && newMin > mathMin(extremes.dataMin, extremes.min) && newMax < mathMax(extremes.dataMax, extremes.max)) {
  8467. xAxis.setExtremes(newMin, newMax, true, false);
  8468. }
  8469. chart.mouseDownX = chartX; // set new reference for next run
  8470. css(chart.container, { cursor: 'move' });
  8471. },
  8472. /**
  8473. * Show the title and subtitle of the chart
  8474. *
  8475. * @param titleOptions {Object} New title options
  8476. * @param subtitleOptions {Object} New subtitle options
  8477. *
  8478. */
  8479. setTitle: function (titleOptions, subtitleOptions) {
  8480. var chart = this,
  8481. options = chart.options,
  8482. chartTitleOptions,
  8483. chartSubtitleOptions;
  8484. chart.chartTitleOptions = chartTitleOptions = merge(options.title, titleOptions);
  8485. chart.chartSubtitleOptions = chartSubtitleOptions = merge(options.subtitle, subtitleOptions);
  8486. // add title and subtitle
  8487. each([
  8488. ['title', titleOptions, chartTitleOptions],
  8489. ['subtitle', subtitleOptions, chartSubtitleOptions]
  8490. ], function (arr) {
  8491. var name = arr[0],
  8492. title = chart[name],
  8493. titleOptions = arr[1],
  8494. chartTitleOptions = arr[2];
  8495. if (title && titleOptions) {
  8496. title = title.destroy(); // remove old
  8497. }
  8498. if (chartTitleOptions && chartTitleOptions.text && !title) {
  8499. chart[name] = chart.renderer.text(
  8500. chartTitleOptions.text,
  8501. 0,
  8502. 0,
  8503. chartTitleOptions.useHTML
  8504. )
  8505. .attr({
  8506. align: chartTitleOptions.align,
  8507. 'class': PREFIX + name,
  8508. zIndex: chartTitleOptions.zIndex || 4
  8509. })
  8510. .css(chartTitleOptions.style)
  8511. .add()
  8512. .align(chartTitleOptions, false, chart.spacingBox);
  8513. }
  8514. });
  8515. },
  8516. /**
  8517. * Get chart width and height according to options and container size
  8518. */
  8519. getChartSize: function () {
  8520. var chart = this,
  8521. optionsChart = chart.options.chart,
  8522. renderTo = chart.renderToClone || chart.renderTo;
  8523. // get inner width and height from jQuery (#824)
  8524. chart.containerWidth = adapterRun(renderTo, 'width');
  8525. chart.containerHeight = adapterRun(renderTo, 'height');
  8526. chart.chartWidth = optionsChart.width || chart.containerWidth || 600;
  8527. chart.chartHeight = optionsChart.height ||
  8528. // the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7:
  8529. (chart.containerHeight > 19 ? chart.containerHeight : 400);
  8530. },
  8531. /**
  8532. * Create a clone of the chart's renderTo div and place it outside the viewport to allow
  8533. * size computation on chart.render and chart.redraw
  8534. */
  8535. cloneRenderTo: function (revert) {
  8536. var clone = this.renderToClone,
  8537. container = this.container;
  8538. // Destroy the clone and bring the container back to the real renderTo div
  8539. if (revert) {
  8540. if (clone) {
  8541. this.renderTo.appendChild(container);
  8542. discardElement(clone);
  8543. delete this.renderToClone;
  8544. }
  8545. // Set up the clone
  8546. } else {
  8547. if (container) {
  8548. this.renderTo.removeChild(container); // do not clone this
  8549. }
  8550. this.renderToClone = clone = this.renderTo.cloneNode(0);
  8551. css(clone, {
  8552. position: ABSOLUTE,
  8553. top: '-9999px',
  8554. display: 'block' // #833
  8555. });
  8556. doc.body.appendChild(clone);
  8557. if (container) {
  8558. clone.appendChild(container);
  8559. }
  8560. }
  8561. },
  8562. /**
  8563. * Get the containing element, determine the size and create the inner container
  8564. * div to hold the chart
  8565. */
  8566. getContainer: function () {
  8567. var chart = this,
  8568. container,
  8569. optionsChart = chart.options.chart,
  8570. chartWidth,
  8571. chartHeight,
  8572. renderTo,
  8573. containerId;
  8574. chart.renderTo = renderTo = optionsChart.renderTo;
  8575. containerId = PREFIX + idCounter++;
  8576. if (isString(renderTo)) {
  8577. chart.renderTo = renderTo = doc.getElementById(renderTo);
  8578. }
  8579. // Display an error if the renderTo is wrong
  8580. if (!renderTo) {
  8581. error(13, true);
  8582. }
  8583. // remove previous chart
  8584. renderTo.innerHTML = '';
  8585. // If the container doesn't have an offsetWidth, it has or is a child of a node
  8586. // that has display:none. We need to temporarily move it out to a visible
  8587. // state to determine the size, else the legend and tooltips won't render
  8588. // properly
  8589. if (!renderTo.offsetWidth) {
  8590. chart.cloneRenderTo();
  8591. }
  8592. // get the width and height
  8593. chart.getChartSize();
  8594. chartWidth = chart.chartWidth;
  8595. chartHeight = chart.chartHeight;
  8596. // create the inner container
  8597. chart.container = container = createElement(DIV, {
  8598. className: PREFIX + 'container' +
  8599. (optionsChart.className ? ' ' + optionsChart.className : ''),
  8600. id: containerId
  8601. }, extend({
  8602. position: RELATIVE,
  8603. overflow: HIDDEN, // needed for context menu (avoid scrollbars) and
  8604. // content overflow in IE
  8605. width: chartWidth + PX,
  8606. height: chartHeight + PX,
  8607. textAlign: 'left',
  8608. lineHeight: 'normal' // #427
  8609. }, optionsChart.style),
  8610. chart.renderToClone || renderTo
  8611. );
  8612. chart.renderer =
  8613. optionsChart.forExport ? // force SVG, used for SVG export
  8614. new SVGRenderer(container, chartWidth, chartHeight, true) :
  8615. new Renderer(container, chartWidth, chartHeight);
  8616. if (useCanVG) {
  8617. // If we need canvg library, extend and configure the renderer
  8618. // to get the tracker for translating mouse events
  8619. chart.renderer.create(chart, container, chartWidth, chartHeight);
  8620. }
  8621. },
  8622. /**
  8623. * Calculate margins by rendering axis labels in a preliminary position. Title,
  8624. * subtitle and legend have already been rendered at this stage, but will be
  8625. * moved into their final positions
  8626. */
  8627. getMargins: function () {
  8628. var chart = this,
  8629. optionsChart = chart.options.chart,
  8630. spacingTop = optionsChart.spacingTop,
  8631. spacingRight = optionsChart.spacingRight,
  8632. spacingBottom = optionsChart.spacingBottom,
  8633. spacingLeft = optionsChart.spacingLeft,
  8634. axisOffset,
  8635. legend = chart.legend,
  8636. optionsMarginTop = chart.optionsMarginTop,
  8637. optionsMarginLeft = chart.optionsMarginLeft,
  8638. optionsMarginRight = chart.optionsMarginRight,
  8639. optionsMarginBottom = chart.optionsMarginBottom,
  8640. chartTitleOptions = chart.chartTitleOptions,
  8641. chartSubtitleOptions = chart.chartSubtitleOptions,
  8642. legendOptions = chart.options.legend,
  8643. legendMargin = pick(legendOptions.margin, 10),
  8644. legendX = legendOptions.x,
  8645. legendY = legendOptions.y,
  8646. align = legendOptions.align,
  8647. verticalAlign = legendOptions.verticalAlign,
  8648. titleOffset;
  8649. chart.resetMargins();
  8650. axisOffset = chart.axisOffset;
  8651. // adjust for title and subtitle
  8652. if ((chart.title || chart.subtitle) && !defined(chart.optionsMarginTop)) {
  8653. titleOffset = mathMax(
  8654. (chart.title && !chartTitleOptions.floating && !chartTitleOptions.verticalAlign && chartTitleOptions.y) || 0,
  8655. (chart.subtitle && !chartSubtitleOptions.floating && !chartSubtitleOptions.verticalAlign && chartSubtitleOptions.y) || 0
  8656. );
  8657. if (titleOffset) {
  8658. chart.plotTop = mathMax(chart.plotTop, titleOffset + pick(chartTitleOptions.margin, 15) + spacingTop);
  8659. }
  8660. }
  8661. // adjust for legend
  8662. if (legend.display && !legendOptions.floating) {
  8663. if (align === 'right') { // horizontal alignment handled first
  8664. if (!defined(optionsMarginRight)) {
  8665. chart.marginRight = mathMax(
  8666. chart.marginRight,
  8667. legend.legendWidth - legendX + legendMargin + spacingRight
  8668. );
  8669. }
  8670. } else if (align === 'left') {
  8671. if (!defined(optionsMarginLeft)) {
  8672. chart.plotLeft = mathMax(
  8673. chart.plotLeft,
  8674. legend.legendWidth + legendX + legendMargin + spacingLeft
  8675. );
  8676. }
  8677. } else if (verticalAlign === 'top') {
  8678. if (!defined(optionsMarginTop)) {
  8679. chart.plotTop = mathMax(
  8680. chart.plotTop,
  8681. legend.legendHeight + legendY + legendMargin + spacingTop
  8682. );
  8683. }
  8684. } else if (verticalAlign === 'bottom') {
  8685. if (!defined(optionsMarginBottom)) {
  8686. chart.marginBottom = mathMax(
  8687. chart.marginBottom,
  8688. legend.legendHeight - legendY + legendMargin + spacingBottom
  8689. );
  8690. }
  8691. }
  8692. }
  8693. // adjust for scroller
  8694. if (chart.extraBottomMargin) {
  8695. chart.marginBottom += chart.extraBottomMargin;
  8696. }
  8697. if (chart.extraTopMargin) {
  8698. chart.plotTop += chart.extraTopMargin;
  8699. }
  8700. // pre-render axes to get labels offset width
  8701. if (chart.hasCartesianSeries) {
  8702. each(chart.axes, function (axis) {
  8703. axis.getOffset();
  8704. });
  8705. }
  8706. if (!defined(optionsMarginLeft)) {
  8707. chart.plotLeft += axisOffset[3];
  8708. }
  8709. if (!defined(optionsMarginTop)) {
  8710. chart.plotTop += axisOffset[0];
  8711. }
  8712. if (!defined(optionsMarginBottom)) {
  8713. chart.marginBottom += axisOffset[2];
  8714. }
  8715. if (!defined(optionsMarginRight)) {
  8716. chart.marginRight += axisOffset[1];
  8717. }
  8718. chart.setChartSize();
  8719. },
  8720. /**
  8721. * Add the event handlers necessary for auto resizing
  8722. *
  8723. */
  8724. initReflow: function () {
  8725. var chart = this,
  8726. optionsChart = chart.options.chart,
  8727. renderTo = chart.renderTo;
  8728. var reflowTimeout;
  8729. function reflow(e) {
  8730. var width = optionsChart.width || adapterRun(renderTo, 'width'),
  8731. height = optionsChart.height || adapterRun(renderTo, 'height'),
  8732. target = e ? e.target : win; // #805 - MooTools doesn't supply e
  8733. // Width and height checks for display:none. Target is doc in IE8 and Opera,
  8734. // win in Firefox, Chrome and IE9.
  8735. if (width && height && (target === win || target === doc)) {
  8736. if (width !== chart.containerWidth || height !== chart.containerHeight) {
  8737. clearTimeout(reflowTimeout);
  8738. reflowTimeout = setTimeout(function () {
  8739. chart.resize(width, height, false);
  8740. }, 100);
  8741. }
  8742. chart.containerWidth = width;
  8743. chart.containerHeight = height;
  8744. }
  8745. }
  8746. addEvent(win, 'resize', reflow);
  8747. addEvent(chart, 'destroy', function () {
  8748. removeEvent(win, 'resize', reflow);
  8749. });
  8750. },
  8751. /**
  8752. * Fires endResize event on chart instance.
  8753. */
  8754. fireEndResize: function () {
  8755. var chart = this;
  8756. if (chart) {
  8757. fireEvent(chart, 'endResize', null, function () {
  8758. chart.isResizing -= 1;
  8759. });
  8760. }
  8761. },
  8762. /**
  8763. * Resize the chart to a given width and height
  8764. * @param {Number} width
  8765. * @param {Number} height
  8766. * @param {Object|Boolean} animation
  8767. */
  8768. // TODO: This method is called setSize in the api
  8769. resize: function (width, height, animation) {
  8770. var chart = this,
  8771. chartWidth,
  8772. chartHeight,
  8773. spacingBox,
  8774. chartTitle = chart.title,
  8775. chartSubtitle = chart.subtitle;
  8776. chart.isResizing += 1;
  8777. // set the animation for the current process
  8778. setAnimation(animation, chart);
  8779. chart.oldChartHeight = chart.chartHeight;
  8780. chart.oldChartWidth = chart.chartWidth;
  8781. if (defined(width)) {
  8782. chart.chartWidth = chartWidth = mathRound(width);
  8783. }
  8784. if (defined(height)) {
  8785. chart.chartHeight = chartHeight = mathRound(height);
  8786. }
  8787. css(chart.container, {
  8788. width: chartWidth + PX,
  8789. height: chartHeight + PX
  8790. });
  8791. chart.renderer.setSize(chartWidth, chartHeight, animation);
  8792. // update axis lengths for more correct tick intervals:
  8793. chart.plotWidth = chartWidth - chart.plotLeft - chart.marginRight;
  8794. chart.plotHeight = chartHeight - chart.plotTop - chart.marginBottom;
  8795. // handle axes
  8796. chart.maxTicks = null;
  8797. each(chart.axes, function (axis) {
  8798. axis.isDirty = true;
  8799. axis.setScale();
  8800. });
  8801. // make sure non-cartesian series are also handled
  8802. each(chart.series, function (serie) {
  8803. serie.isDirty = true;
  8804. });
  8805. chart.isDirtyLegend = true; // force legend redraw
  8806. chart.isDirtyBox = true; // force redraw of plot and chart border
  8807. chart.getMargins();
  8808. // move titles
  8809. spacingBox = chart.spacingBox;
  8810. if (chartTitle) {
  8811. chartTitle.align(null, null, spacingBox);
  8812. }
  8813. if (chartSubtitle) {
  8814. chartSubtitle.align(null, null, spacingBox);
  8815. }
  8816. chart.redraw(animation);
  8817. chart.oldChartHeight = null;
  8818. fireEvent(chart, 'resize');
  8819. // fire endResize and set isResizing back
  8820. // If animation is disabled, fire without delay
  8821. if (globalAnimation === false) {
  8822. chart.fireEndResize();
  8823. } else { // else set a timeout with the animation duration
  8824. setTimeout(chart.fireEndResize, (globalAnimation && globalAnimation.duration) || 500);
  8825. }
  8826. },
  8827. /**
  8828. * Set the public chart properties. This is done before and after the pre-render
  8829. * to determine margin sizes
  8830. */
  8831. setChartSize: function () {
  8832. var chart = this,
  8833. inverted = chart.inverted,
  8834. chartWidth = chart.chartWidth,
  8835. chartHeight = chart.chartHeight,
  8836. optionsChart = chart.options.chart,
  8837. spacingTop = optionsChart.spacingTop,
  8838. spacingRight = optionsChart.spacingRight,
  8839. spacingBottom = optionsChart.spacingBottom,
  8840. spacingLeft = optionsChart.spacingLeft;
  8841. chart.plotLeft = mathRound(chart.plotLeft);
  8842. chart.plotTop = mathRound(chart.plotTop);
  8843. chart.plotWidth = mathRound(chartWidth - chart.plotLeft - chart.marginRight);
  8844. chart.plotHeight = mathRound(chartHeight - chart.plotTop - chart.marginBottom);
  8845. chart.plotSizeX = inverted ? chart.plotHeight : chart.plotWidth;
  8846. chart.plotSizeY = inverted ? chart.plotWidth : chart.plotHeight;
  8847. chart.spacingBox = {
  8848. x: spacingLeft,
  8849. y: spacingTop,
  8850. width: chartWidth - spacingLeft - spacingRight,
  8851. height: chartHeight - spacingTop - spacingBottom
  8852. };
  8853. each(chart.axes, function (axis) {
  8854. axis.setAxisSize();
  8855. axis.setAxisTranslation();
  8856. });
  8857. },
  8858. /**
  8859. * Initial margins before auto size margins are applied
  8860. */
  8861. resetMargins: function () {
  8862. var chart = this,
  8863. optionsChart = chart.options.chart,
  8864. spacingTop = optionsChart.spacingTop,
  8865. spacingRight = optionsChart.spacingRight,
  8866. spacingBottom = optionsChart.spacingBottom,
  8867. spacingLeft = optionsChart.spacingLeft;
  8868. chart.plotTop = pick(chart.optionsMarginTop, spacingTop);
  8869. chart.marginRight = pick(chart.optionsMarginRight, spacingRight);
  8870. chart.marginBottom = pick(chart.optionsMarginBottom, spacingBottom);
  8871. chart.plotLeft = pick(chart.optionsMarginLeft, spacingLeft);
  8872. chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
  8873. },
  8874. /**
  8875. * Draw the borders and backgrounds for chart and plot area
  8876. */
  8877. drawChartBox: function () {
  8878. var chart = this,
  8879. optionsChart = chart.options.chart,
  8880. renderer = chart.renderer,
  8881. chartWidth = chart.chartWidth,
  8882. chartHeight = chart.chartHeight,
  8883. chartBackground = chart.chartBackground,
  8884. plotBackground = chart.plotBackground,
  8885. plotBorder = chart.plotBorder,
  8886. plotBGImage = chart.plotBGImage,
  8887. chartBorderWidth = optionsChart.borderWidth || 0,
  8888. chartBackgroundColor = optionsChart.backgroundColor,
  8889. plotBackgroundColor = optionsChart.plotBackgroundColor,
  8890. plotBackgroundImage = optionsChart.plotBackgroundImage,
  8891. mgn,
  8892. bgAttr,
  8893. plotSize = {
  8894. x: chart.plotLeft,
  8895. y: chart.plotTop,
  8896. width: chart.plotWidth,
  8897. height: chart.plotHeight
  8898. };
  8899. // Chart area
  8900. mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);
  8901. if (chartBorderWidth || chartBackgroundColor) {
  8902. if (!chartBackground) {
  8903. bgAttr = {
  8904. fill: chartBackgroundColor || NONE
  8905. };
  8906. if (chartBorderWidth) { // #980
  8907. bgAttr.stroke = optionsChart.borderColor;
  8908. bgAttr['stroke-width'] = chartBorderWidth;
  8909. }
  8910. chart.chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn,
  8911. optionsChart.borderRadius, chartBorderWidth)
  8912. .attr(bgAttr)
  8913. .add()
  8914. .shadow(optionsChart.shadow);
  8915. } else { // resize
  8916. chartBackground.animate(
  8917. chartBackground.crisp(null, null, null, chartWidth - mgn, chartHeight - mgn)
  8918. );
  8919. }
  8920. }
  8921. // Plot background
  8922. if (plotBackgroundColor) {
  8923. if (!plotBackground) {
  8924. chart.plotBackground = renderer.rect(chart.plotLeft, chart.plotTop, chart.plotWidth, chart.plotHeight, 0)
  8925. .attr({
  8926. fill: plotBackgroundColor
  8927. })
  8928. .add()
  8929. .shadow(optionsChart.plotShadow);
  8930. } else {
  8931. plotBackground.animate(plotSize);
  8932. }
  8933. }
  8934. if (plotBackgroundImage) {
  8935. if (!plotBGImage) {
  8936. chart.plotBGImage = renderer.image(plotBackgroundImage, chart.plotLeft, chart.plotTop, chart.plotWidth, chart.plotHeight)
  8937. .add();
  8938. } else {
  8939. plotBGImage.animate(plotSize);
  8940. }
  8941. }
  8942. // Plot area border
  8943. if (optionsChart.plotBorderWidth) {
  8944. if (!plotBorder) {
  8945. chart.plotBorder = renderer.rect(chart.plotLeft, chart.plotTop, chart.plotWidth, chart.plotHeight, 0, optionsChart.plotBorderWidth)
  8946. .attr({
  8947. stroke: optionsChart.plotBorderColor,
  8948. 'stroke-width': optionsChart.plotBorderWidth,
  8949. zIndex: 4
  8950. })
  8951. .add();
  8952. } else {
  8953. plotBorder.animate(
  8954. plotBorder.crisp(null, chart.plotLeft, chart.plotTop, chart.plotWidth, chart.plotHeight)
  8955. );
  8956. }
  8957. }
  8958. // reset
  8959. chart.isDirtyBox = false;
  8960. },
  8961. /**
  8962. * Detect whether a certain chart property is needed based on inspecting its options
  8963. * and series. This mainly applies to the chart.invert property, and in extensions to
  8964. * the chart.angular and chart.polar properties.
  8965. */
  8966. propFromSeries: function () {
  8967. var chart = this,
  8968. optionsChart = chart.options.chart,
  8969. klass,
  8970. seriesOptions = chart.options.series,
  8971. i,
  8972. value;
  8973. each(['inverted', 'angular', 'polar'], function (key) {
  8974. // The default series type's class
  8975. klass = seriesTypes[optionsChart.type || optionsChart.defaultSeriesType];
  8976. // Get the value from available chart-wide properties
  8977. value = (
  8978. chart[key] || // 1. it is set before
  8979. optionsChart[key] || // 2. it is set in the options
  8980. (klass && klass.prototype[key]) // 3. it's default series class requires it
  8981. );
  8982. // 4. Check if any the chart's series require it
  8983. i = seriesOptions && seriesOptions.length;
  8984. while (!value && i--) {
  8985. klass = seriesTypes[seriesOptions[i].type];
  8986. if (klass && klass.prototype[key]) {
  8987. value = true;
  8988. }
  8989. }
  8990. // Set the chart property
  8991. chart[key] = value;
  8992. });
  8993. },
  8994. /**
  8995. * Render all graphics for the chart
  8996. */
  8997. render: function () {
  8998. var chart = this,
  8999. axes = chart.axes,
  9000. renderer = chart.renderer,
  9001. options = chart.options;
  9002. var labels = options.labels,
  9003. credits = options.credits,
  9004. creditsHref;
  9005. // Title
  9006. chart.setTitle();
  9007. // Legend
  9008. chart.legend = new Legend(chart);
  9009. // Get margins by pre-rendering axes
  9010. // set axes scales
  9011. each(axes, function (axis) {
  9012. axis.setScale();
  9013. });
  9014. chart.getMargins();
  9015. chart.maxTicks = null; // reset for second pass
  9016. each(axes, function (axis) {
  9017. axis.setTickPositions(true); // update to reflect the new margins
  9018. axis.setMaxTicks();
  9019. });
  9020. chart.adjustTickAmounts();
  9021. chart.getMargins(); // second pass to check for new labels
  9022. // Draw the borders and backgrounds
  9023. chart.drawChartBox();
  9024. // Axes
  9025. if (chart.hasCartesianSeries) {
  9026. each(axes, function (axis) {
  9027. axis.render();
  9028. });
  9029. }
  9030. // The series
  9031. if (!chart.seriesGroup) {
  9032. chart.seriesGroup = renderer.g('series-group')
  9033. .attr({ zIndex: 3 })
  9034. .add();
  9035. }
  9036. each(chart.series, function (serie) {
  9037. serie.translate();
  9038. serie.setTooltipPoints();
  9039. serie.render();
  9040. });
  9041. // Labels
  9042. if (labels.items) {
  9043. each(labels.items, function () {
  9044. var style = extend(labels.style, this.style),
  9045. x = pInt(style.left) + chart.plotLeft,
  9046. y = pInt(style.top) + chart.plotTop + 12;
  9047. // delete to prevent rewriting in IE
  9048. delete style.left;
  9049. delete style.top;
  9050. renderer.text(
  9051. this.html,
  9052. x,
  9053. y
  9054. )
  9055. .attr({ zIndex: 2 })
  9056. .css(style)
  9057. .add();
  9058. });
  9059. }
  9060. // Credits
  9061. if (credits.enabled && !chart.credits) {
  9062. creditsHref = credits.href;
  9063. chart.credits = renderer.text(
  9064. credits.text,
  9065. 0,
  9066. 0
  9067. )
  9068. .on('click', function () {
  9069. if (creditsHref) {
  9070. location.href = creditsHref;
  9071. }
  9072. })
  9073. .attr({
  9074. align: credits.position.align,
  9075. zIndex: 8
  9076. })
  9077. .css(credits.style)
  9078. .add()
  9079. .align(credits.position);
  9080. }
  9081. // Set flag
  9082. chart.hasRendered = true;
  9083. },
  9084. /**
  9085. * Clean up memory usage
  9086. */
  9087. destroy: function () {
  9088. var chart = this,
  9089. axes = chart.axes,
  9090. series = chart.series,
  9091. container = chart.container;
  9092. var i,
  9093. parentNode = container && container.parentNode;
  9094. // If the chart is destroyed already, do nothing.
  9095. // This will happen if if a script invokes chart.destroy and
  9096. // then it will be called again on win.unload
  9097. if (chart === null) {
  9098. return;
  9099. }
  9100. // fire the chart.destoy event
  9101. fireEvent(chart, 'destroy');
  9102. // remove events
  9103. removeEvent(chart);
  9104. // ==== Destroy collections:
  9105. // Destroy axes
  9106. i = axes.length;
  9107. while (i--) {
  9108. axes[i] = axes[i].destroy();
  9109. }
  9110. // Destroy each series
  9111. i = series.length;
  9112. while (i--) {
  9113. series[i] = series[i].destroy();
  9114. }
  9115. // ==== Destroy chart properties:
  9116. each(['title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage', 'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'tracker', 'scroller', 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer'], function (name) {
  9117. var prop = chart[name];
  9118. if (prop) {
  9119. chart[name] = prop.destroy();
  9120. }
  9121. });
  9122. // remove container and all SVG
  9123. if (container) { // can break in IE when destroyed before finished loading
  9124. container.innerHTML = '';
  9125. removeEvent(container);
  9126. if (parentNode) {
  9127. discardElement(container);
  9128. }
  9129. // IE6 leak
  9130. container = null;
  9131. }
  9132. // clean it all up
  9133. for (i in chart) {
  9134. delete chart[i];
  9135. }
  9136. chart.options = null;
  9137. chart = null;
  9138. },
  9139. /**
  9140. * Prepare for first rendering after all data are loaded
  9141. */
  9142. firstRender: function () {
  9143. var chart = this,
  9144. options = chart.options,
  9145. callback = chart.callback;
  9146. // VML namespaces can't be added until after complete. Listening
  9147. // for Perini's doScroll hack is not enough.
  9148. var ONREADYSTATECHANGE = 'onreadystatechange',
  9149. COMPLETE = 'complete';
  9150. // Note: in spite of JSLint's complaints, win == win.top is required
  9151. /*jslint eqeq: true*/
  9152. if ((!hasSVG && (win == win.top && doc.readyState !== COMPLETE)) || (useCanVG && !win.canvg)) {
  9153. /*jslint eqeq: false*/
  9154. if (useCanVG) {
  9155. // Delay rendering until canvg library is downloaded and ready
  9156. CanVGController.push(function () { chart.firstRender(); }, options.global.canvasToolsURL);
  9157. } else {
  9158. doc.attachEvent(ONREADYSTATECHANGE, function () {
  9159. doc.detachEvent(ONREADYSTATECHANGE, chart.firstRender);
  9160. if (doc.readyState === COMPLETE) {
  9161. chart.firstRender();
  9162. }
  9163. });
  9164. }
  9165. return;
  9166. }
  9167. // create the container
  9168. chart.getContainer();
  9169. // Run an early event after the container and renderer are established
  9170. fireEvent(chart, 'init');
  9171. // Initialize range selector for stock charts
  9172. if (Highcharts.RangeSelector && options.rangeSelector.enabled) {
  9173. chart.rangeSelector = new Highcharts.RangeSelector(chart);
  9174. }
  9175. chart.resetMargins();
  9176. chart.setChartSize();
  9177. // Set the common chart properties (mainly invert) from the given series
  9178. chart.propFromSeries();
  9179. // get axes
  9180. chart.getAxes();
  9181. // Initialize the series
  9182. each(options.series || [], function (serieOptions) {
  9183. chart.initSeries(serieOptions);
  9184. });
  9185. // Run an event where series and axes can be added
  9186. //fireEvent(chart, 'beforeRender');
  9187. // Initialize scroller for stock charts
  9188. if (Highcharts.Scroller && (options.navigator.enabled || options.scrollbar.enabled)) {
  9189. chart.scroller = new Highcharts.Scroller(chart);
  9190. }
  9191. // depends on inverted and on margins being set
  9192. chart.tracker = new MouseTracker(chart, options);
  9193. chart.render();
  9194. // add canvas
  9195. chart.renderer.draw();
  9196. // run callbacks
  9197. if (callback) {
  9198. callback.apply(chart, [chart]);
  9199. }
  9200. each(chart.callbacks, function (fn) {
  9201. fn.apply(chart, [chart]);
  9202. });
  9203. // If the chart was rendered outside the top container, put it back in
  9204. chart.cloneRenderTo(true);
  9205. fireEvent(chart, 'load');
  9206. },
  9207. init: function (chartEvents) {
  9208. var chart = this,
  9209. optionsChart = chart.options.chart,
  9210. eventType;
  9211. // Run chart
  9212. // Set up auto resize
  9213. if (optionsChart.reflow !== false) {
  9214. addEvent(chart, 'load', chart.initReflow);
  9215. }
  9216. // Chart event handlers
  9217. if (chartEvents) {
  9218. for (eventType in chartEvents) {
  9219. addEvent(chart, eventType, chartEvents[eventType]);
  9220. }
  9221. }
  9222. chart.xAxis = [];
  9223. chart.yAxis = [];
  9224. // Expose methods and variables
  9225. chart.animation = useCanVG ? false : pick(optionsChart.animation, true);
  9226. chart.setSize = chart.resize;
  9227. chart.pointCount = 0;
  9228. chart.counters = new ChartCounters();
  9229. /*
  9230. if ($) $(function () {
  9231. $container = $('#container');
  9232. var origChartWidth,
  9233. origChartHeight;
  9234. if ($container) {
  9235. $('<button>+</button>')
  9236. .insertBefore($container)
  9237. .click(function () {
  9238. if (origChartWidth === UNDEFINED) {
  9239. origChartWidth = chartWidth;
  9240. origChartHeight = chartHeight;
  9241. }
  9242. chart.resize(chartWidth *= 1.1, chartHeight *= 1.1);
  9243. });
  9244. $('<button>-</button>')
  9245. .insertBefore($container)
  9246. .click(function () {
  9247. if (origChartWidth === UNDEFINED) {
  9248. origChartWidth = chartWidth;
  9249. origChartHeight = chartHeight;
  9250. }
  9251. chart.resize(chartWidth *= 0.9, chartHeight *= 0.9);
  9252. });
  9253. $('<button>1:1</button>')
  9254. .insertBefore($container)
  9255. .click(function () {
  9256. if (origChartWidth === UNDEFINED) {
  9257. origChartWidth = chartWidth;
  9258. origChartHeight = chartHeight;
  9259. }
  9260. chart.resize(origChartWidth, origChartHeight);
  9261. });
  9262. }
  9263. })
  9264. */
  9265. chart.firstRender();
  9266. }
  9267. }; // end Chart
  9268. // Hook for exporting module
  9269. Chart.prototype.callbacks = [];
  9270. /**
  9271. * The Point object and prototype. Inheritable and used as base for PiePoint
  9272. */
  9273. var Point = function () {};
  9274. Point.prototype = {
  9275. /**
  9276. * Initialize the point
  9277. * @param {Object} series The series object containing this point
  9278. * @param {Object} options The data in either number, array or object format
  9279. */
  9280. init: function (series, options, x) {
  9281. var point = this,
  9282. counters = series.chart.counters,
  9283. defaultColors;
  9284. point.series = series;
  9285. point.applyOptions(options, x);
  9286. point.pointAttr = {};
  9287. if (series.options.colorByPoint) {
  9288. defaultColors = series.chart.options.colors;
  9289. if (!point.options) {
  9290. point.options = {};
  9291. }
  9292. point.color = point.options.color = point.color || defaultColors[counters.color++];
  9293. // loop back to zero
  9294. counters.wrapColor(defaultColors.length);
  9295. }
  9296. series.chart.pointCount++;
  9297. return point;
  9298. },
  9299. /**
  9300. * Apply the options containing the x and y data and possible some extra properties.
  9301. * This is called on point init or from point.update.
  9302. *
  9303. * @param {Object} options
  9304. */
  9305. applyOptions: function (options, x) {
  9306. var point = this,
  9307. series = point.series,
  9308. optionsType = typeof options;
  9309. point.config = options;
  9310. // onedimensional array input
  9311. if (optionsType === 'number' || options === null) {
  9312. point.y = options;
  9313. } else if (typeof options[0] === 'number') { // two-dimentional array
  9314. point.x = options[0];
  9315. point.y = options[1];
  9316. } else if (optionsType === 'object' && typeof options.length !== 'number') { // object input
  9317. // copy options directly to point
  9318. extend(point, options);
  9319. point.options = options;
  9320. // This is the fastest way to detect if there are individual point dataLabels that need
  9321. // to be considered in drawDataLabels. These can only occur in object configs.
  9322. if (options.dataLabels) {
  9323. series._hasPointLabels = true;
  9324. }
  9325. } else if (typeof options[0] === 'string') { // categorized data with name in first position
  9326. point.name = options[0];
  9327. point.y = options[1];
  9328. }
  9329. /*
  9330. * If no x is set by now, get auto incremented value. All points must have an
  9331. * x value, however the y value can be null to create a gap in the series
  9332. */
  9333. // todo: skip this? It is only used in applyOptions, in translate it should not be used
  9334. if (point.x === UNDEFINED) {
  9335. point.x = x === UNDEFINED ? series.autoIncrement() : x;
  9336. }
  9337. },
  9338. /**
  9339. * Destroy a point to clear memory. Its reference still stays in series.data.
  9340. */
  9341. destroy: function () {
  9342. var point = this,
  9343. series = point.series,
  9344. chart = series.chart,
  9345. hoverPoints = chart.hoverPoints,
  9346. prop;
  9347. chart.pointCount--;
  9348. if (hoverPoints) {
  9349. point.setState();
  9350. erase(hoverPoints, point);
  9351. if (!hoverPoints.length) {
  9352. chart.hoverPoints = null;
  9353. }
  9354. }
  9355. if (point === chart.hoverPoint) {
  9356. point.onMouseOut();
  9357. }
  9358. // remove all events
  9359. if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive
  9360. removeEvent(point);
  9361. point.destroyElements();
  9362. }
  9363. if (point.legendItem) { // pies have legend items
  9364. chart.legend.destroyItem(point);
  9365. }
  9366. for (prop in point) {
  9367. point[prop] = null;
  9368. }
  9369. },
  9370. /**
  9371. * Destroy SVG elements associated with the point
  9372. */
  9373. destroyElements: function () {
  9374. var point = this,
  9375. props = ['graphic', 'tracker', 'dataLabel', 'group', 'connector', 'shadowGroup'],
  9376. prop,
  9377. i = 6;
  9378. while (i--) {
  9379. prop = props[i];
  9380. if (point[prop]) {
  9381. point[prop] = point[prop].destroy();
  9382. }
  9383. }
  9384. },
  9385. /**
  9386. * Return the configuration hash needed for the data label and tooltip formatters
  9387. */
  9388. getLabelConfig: function () {
  9389. var point = this;
  9390. return {
  9391. x: point.category,
  9392. y: point.y,
  9393. key: point.name || point.category,
  9394. series: point.series,
  9395. point: point,
  9396. percentage: point.percentage,
  9397. total: point.total || point.stackTotal
  9398. };
  9399. },
  9400. /**
  9401. * Toggle the selection status of a point
  9402. * @param {Boolean} selected Whether to select or unselect the point.
  9403. * @param {Boolean} accumulate Whether to add to the previous selection. By default,
  9404. * this happens if the control key (Cmd on Mac) was pressed during clicking.
  9405. */
  9406. select: function (selected, accumulate) {
  9407. var point = this,
  9408. series = point.series,
  9409. chart = series.chart;
  9410. selected = pick(selected, !point.selected);
  9411. // fire the event with the defalut handler
  9412. point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () {
  9413. point.selected = selected;
  9414. point.setState(selected && SELECT_STATE);
  9415. // unselect all other points unless Ctrl or Cmd + click
  9416. if (!accumulate) {
  9417. each(chart.getSelectedPoints(), function (loopPoint) {
  9418. if (loopPoint.selected && loopPoint !== point) {
  9419. loopPoint.selected = false;
  9420. loopPoint.setState(NORMAL_STATE);
  9421. loopPoint.firePointEvent('unselect');
  9422. }
  9423. });
  9424. }
  9425. });
  9426. },
  9427. onMouseOver: function () {
  9428. var point = this,
  9429. series = point.series,
  9430. chart = series.chart,
  9431. tooltip = chart.tooltip,
  9432. hoverPoint = chart.hoverPoint;
  9433. // set normal state to previous series
  9434. if (hoverPoint && hoverPoint !== point) {
  9435. hoverPoint.onMouseOut();
  9436. }
  9437. // trigger the event
  9438. point.firePointEvent('mouseOver');
  9439. // update the tooltip
  9440. if (tooltip && (!tooltip.shared || series.noSharedTooltip)) {
  9441. tooltip.refresh(point);
  9442. }
  9443. // hover this
  9444. point.setState(HOVER_STATE);
  9445. chart.hoverPoint = point;
  9446. },
  9447. onMouseOut: function () {
  9448. var point = this;
  9449. point.firePointEvent('mouseOut');
  9450. point.setState();
  9451. point.series.chart.hoverPoint = null;
  9452. },
  9453. /**
  9454. * Extendable method for formatting each point's tooltip line
  9455. *
  9456. * @return {String} A string to be concatenated in to the common tooltip text
  9457. */
  9458. tooltipFormatter: function (pointFormat) {
  9459. var point = this,
  9460. series = point.series,
  9461. seriesTooltipOptions = series.tooltipOptions,
  9462. match = pointFormat.match(/\{(series|point)\.[a-zA-Z]+\}/g),
  9463. splitter = /[{\.}]/,
  9464. obj,
  9465. key,
  9466. replacement,
  9467. repOptionKey,
  9468. parts,
  9469. prop,
  9470. i,
  9471. cfg = { // docs: percentageDecimals, percentagePrefix, percentageSuffix, totalDecimals, totalPrefix, totalSuffix
  9472. y: 0, // 0: use 'value' for repOptionKey
  9473. open: 0,
  9474. high: 0,
  9475. low: 0,
  9476. close: 0,
  9477. percentage: 1, // 1: use the self name for repOptionKey
  9478. total: 1
  9479. };
  9480. // Backwards compatibility to y naming in early Highstock
  9481. seriesTooltipOptions.valuePrefix = seriesTooltipOptions.valuePrefix || seriesTooltipOptions.yPrefix;
  9482. seriesTooltipOptions.valueDecimals = seriesTooltipOptions.valueDecimals || seriesTooltipOptions.yDecimals;
  9483. seriesTooltipOptions.valueSuffix = seriesTooltipOptions.valueSuffix || seriesTooltipOptions.ySuffix;
  9484. // loop over the variables defined on the form {series.name}, {point.y} etc
  9485. for (i in match) {
  9486. key = match[i];
  9487. if (isString(key) && key !== pointFormat) { // IE matches more than just the variables
  9488. // Split it further into parts
  9489. parts = (' ' + key).split(splitter); // add empty string because IE and the rest handles it differently
  9490. obj = { 'point': point, 'series': series }[parts[1]];
  9491. prop = parts[2];
  9492. // Add some preformatting
  9493. if (obj === point && cfg.hasOwnProperty(prop)) {
  9494. repOptionKey = cfg[prop] ? prop : 'value';
  9495. replacement = (seriesTooltipOptions[repOptionKey + 'Prefix'] || '') +
  9496. numberFormat(point[prop], pick(seriesTooltipOptions[repOptionKey + 'Decimals'], -1)) +
  9497. (seriesTooltipOptions[repOptionKey + 'Suffix'] || '');
  9498. // Automatic replacement
  9499. } else {
  9500. replacement = obj[prop];
  9501. }
  9502. pointFormat = pointFormat.replace(key, replacement);
  9503. }
  9504. }
  9505. return pointFormat;
  9506. },
  9507. /**
  9508. * Update the point with new options (typically x/y data) and optionally redraw the series.
  9509. *
  9510. * @param {Object} options Point options as defined in the series.data array
  9511. * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
  9512. * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
  9513. * configuration
  9514. *
  9515. */
  9516. update: function (options, redraw, animation) {
  9517. var point = this,
  9518. series = point.series,
  9519. graphic = point.graphic,
  9520. i,
  9521. data = series.data,
  9522. dataLength = data.length,
  9523. chart = series.chart;
  9524. redraw = pick(redraw, true);
  9525. // fire the event with a default handler of doing the update
  9526. point.firePointEvent('update', { options: options }, function () {
  9527. point.applyOptions(options);
  9528. // update visuals
  9529. if (isObject(options)) {
  9530. series.getAttribs();
  9531. if (graphic) {
  9532. graphic.attr(point.pointAttr[series.state]);
  9533. }
  9534. }
  9535. // record changes in the parallel arrays
  9536. for (i = 0; i < dataLength; i++) {
  9537. if (data[i] === point) {
  9538. series.xData[i] = point.x;
  9539. series.yData[i] = point.y;
  9540. series.options.data[i] = options;
  9541. break;
  9542. }
  9543. }
  9544. // redraw
  9545. series.isDirty = true;
  9546. series.isDirtyData = true;
  9547. if (redraw) {
  9548. chart.redraw(animation);
  9549. }
  9550. });
  9551. },
  9552. /**
  9553. * Remove a point and optionally redraw the series and if necessary the axes
  9554. * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
  9555. * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
  9556. * configuration
  9557. */
  9558. remove: function (redraw, animation) {
  9559. var point = this,
  9560. series = point.series,
  9561. chart = series.chart,
  9562. i,
  9563. data = series.data,
  9564. dataLength = data.length;
  9565. setAnimation(animation, chart);
  9566. redraw = pick(redraw, true);
  9567. // fire the event with a default handler of removing the point
  9568. point.firePointEvent('remove', null, function () {
  9569. //erase(series.data, point);
  9570. for (i = 0; i < dataLength; i++) {
  9571. if (data[i] === point) {
  9572. // splice all the parallel arrays
  9573. data.splice(i, 1);
  9574. series.options.data.splice(i, 1);
  9575. series.xData.splice(i, 1);
  9576. series.yData.splice(i, 1);
  9577. break;
  9578. }
  9579. }
  9580. point.destroy();
  9581. // redraw
  9582. series.isDirty = true;
  9583. series.isDirtyData = true;
  9584. if (redraw) {
  9585. chart.redraw();
  9586. }
  9587. });
  9588. },
  9589. /**
  9590. * Fire an event on the Point object. Must not be renamed to fireEvent, as this
  9591. * causes a name clash in MooTools
  9592. * @param {String} eventType
  9593. * @param {Object} eventArgs Additional event arguments
  9594. * @param {Function} defaultFunction Default event handler
  9595. */
  9596. firePointEvent: function (eventType, eventArgs, defaultFunction) {
  9597. var point = this,
  9598. series = this.series,
  9599. seriesOptions = series.options;
  9600. // load event handlers on demand to save time on mouseover/out
  9601. if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) {
  9602. this.importEvents();
  9603. }
  9604. // add default handler if in selection mode
  9605. if (eventType === 'click' && seriesOptions.allowPointSelect) {
  9606. defaultFunction = function (event) {
  9607. // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
  9608. point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
  9609. };
  9610. }
  9611. fireEvent(this, eventType, eventArgs, defaultFunction);
  9612. },
  9613. /**
  9614. * Import events from the series' and point's options. Only do it on
  9615. * demand, to save processing time on hovering.
  9616. */
  9617. importEvents: function () {
  9618. if (!this.hasImportedEvents) {
  9619. var point = this,
  9620. options = merge(point.series.options.point, point.options),
  9621. events = options.events,
  9622. eventType;
  9623. point.events = events;
  9624. for (eventType in events) {
  9625. addEvent(point, eventType, events[eventType]);
  9626. }
  9627. this.hasImportedEvents = true;
  9628. }
  9629. },
  9630. /**
  9631. * Set the point's state
  9632. * @param {String} state
  9633. */
  9634. setState: function (state) {
  9635. var point = this,
  9636. plotX = point.plotX,
  9637. plotY = point.plotY,
  9638. series = point.series,
  9639. stateOptions = series.options.states,
  9640. markerOptions = defaultPlotOptions[series.type].marker && series.options.marker,
  9641. normalDisabled = markerOptions && !markerOptions.enabled,
  9642. markerStateOptions = markerOptions && markerOptions.states[state],
  9643. stateDisabled = markerStateOptions && markerStateOptions.enabled === false,
  9644. stateMarkerGraphic = series.stateMarkerGraphic,
  9645. chart = series.chart,
  9646. radius,
  9647. pointAttr = point.pointAttr;
  9648. state = state || NORMAL_STATE; // empty string
  9649. if (
  9650. // already has this state
  9651. state === point.state ||
  9652. // selected points don't respond to hover
  9653. (point.selected && state !== SELECT_STATE) ||
  9654. // series' state options is disabled
  9655. (stateOptions[state] && stateOptions[state].enabled === false) ||
  9656. // point marker's state options is disabled
  9657. (state && (stateDisabled || (normalDisabled && !markerStateOptions.enabled)))
  9658. ) {
  9659. return;
  9660. }
  9661. // apply hover styles to the existing point
  9662. if (point.graphic) {
  9663. radius = markerOptions && point.graphic.symbolName && pointAttr[state].r;
  9664. point.graphic.attr(merge(
  9665. pointAttr[state],
  9666. radius ? { // new symbol attributes (#507, #612)
  9667. x: plotX - radius,
  9668. y: plotY - radius,
  9669. width: 2 * radius,
  9670. height: 2 * radius
  9671. } : {}
  9672. ));
  9673. } else {
  9674. // if a graphic is not applied to each point in the normal state, create a shared
  9675. // graphic for the hover state
  9676. if (state && markerStateOptions) {
  9677. if (!stateMarkerGraphic) {
  9678. radius = markerStateOptions.radius;
  9679. series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(
  9680. series.symbol,
  9681. -radius,
  9682. -radius,
  9683. 2 * radius,
  9684. 2 * radius
  9685. )
  9686. .attr(pointAttr[state])
  9687. .add(series.group);
  9688. }
  9689. stateMarkerGraphic.translate(
  9690. plotX,
  9691. plotY
  9692. );
  9693. }
  9694. if (stateMarkerGraphic) {
  9695. stateMarkerGraphic[state ? 'show' : 'hide']();
  9696. }
  9697. }
  9698. point.state = state;
  9699. }
  9700. };
  9701. /**
  9702. * @classDescription The base function which all other series types inherit from. The data in the series is stored
  9703. * in various arrays.
  9704. *
  9705. * - First, series.options.data contains all the original config options for
  9706. * each point whether added by options or methods like series.addPoint.
  9707. * - Next, series.data contains those values converted to points, but in case the series data length
  9708. * exceeds the cropThreshold, or if the data is grouped, series.data doesn't contain all the points. It
  9709. * only contains the points that have been created on demand.
  9710. * - Then there's series.points that contains all currently visible point objects. In case of cropping,
  9711. * the cropped-away points are not part of this array. The series.points array starts at series.cropStart
  9712. * compared to series.data and series.options.data. If however the series data is grouped, these can't
  9713. * be correlated one to one.
  9714. * - series.xData and series.processedXData contain clean x values, equivalent to series.data and series.points.
  9715. * - series.yData and series.processedYData contain clean x values, equivalent to series.data and series.points.
  9716. *
  9717. * @param {Object} chart
  9718. * @param {Object} options
  9719. */
  9720. var Series = function () {};
  9721. Series.prototype = {
  9722. isCartesian: true,
  9723. type: 'line',
  9724. pointClass: Point,
  9725. sorted: true, // requires the data to be sorted
  9726. pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
  9727. stroke: 'lineColor',
  9728. 'stroke-width': 'lineWidth',
  9729. fill: 'fillColor',
  9730. r: 'radius'
  9731. },
  9732. init: function (chart, options) {
  9733. var series = this,
  9734. eventType,
  9735. events,
  9736. //pointEvent,
  9737. index = chart.series.length;
  9738. series.chart = chart;
  9739. series.options = options = series.setOptions(options); // merge with plotOptions
  9740. // bind the axes
  9741. series.bindAxes();
  9742. // set some variables
  9743. extend(series, {
  9744. index: index,
  9745. name: options.name || 'Series ' + (index + 1),
  9746. state: NORMAL_STATE,
  9747. pointAttr: {},
  9748. visible: options.visible !== false, // true by default
  9749. selected: options.selected === true // false by default
  9750. });
  9751. // special
  9752. if (useCanVG) {
  9753. options.animation = false;
  9754. }
  9755. // register event listeners
  9756. events = options.events;
  9757. for (eventType in events) {
  9758. addEvent(series, eventType, events[eventType]);
  9759. }
  9760. if (
  9761. (events && events.click) ||
  9762. (options.point && options.point.events && options.point.events.click) ||
  9763. options.allowPointSelect
  9764. ) {
  9765. chart.runTrackerClick = true;
  9766. }
  9767. series.getColor();
  9768. series.getSymbol();
  9769. // set the data
  9770. series.setData(options.data, false);
  9771. // Mark cartesian
  9772. if (series.isCartesian) {
  9773. chart.hasCartesianSeries = true;
  9774. }
  9775. // Register it in the chart
  9776. chart.series.push(series);
  9777. },
  9778. /**
  9779. * Set the xAxis and yAxis properties of cartesian series, and register the series
  9780. * in the axis.series array
  9781. */
  9782. bindAxes: function () {
  9783. var series = this,
  9784. seriesOptions = series.options,
  9785. chart = series.chart,
  9786. axisOptions;
  9787. if (series.isCartesian) {
  9788. each(['xAxis', 'yAxis'], function (AXIS) { // repeat for xAxis and yAxis
  9789. each(chart[AXIS], function (axis) { // loop through the chart's axis objects
  9790. axisOptions = axis.options;
  9791. // apply if the series xAxis or yAxis option mathches the number of the
  9792. // axis, or if undefined, use the first axis
  9793. if ((seriesOptions[AXIS] === axisOptions.index) ||
  9794. (seriesOptions[AXIS] === UNDEFINED && axisOptions.index === 0)) {
  9795. // register this series in the axis.series lookup
  9796. axis.series.push(series);
  9797. // set this series.xAxis or series.yAxis reference
  9798. series[AXIS] = axis;
  9799. // mark dirty for redraw
  9800. axis.isDirty = true;
  9801. }
  9802. });
  9803. });
  9804. }
  9805. },
  9806. /**
  9807. * Return an auto incremented x value based on the pointStart and pointInterval options.
  9808. * This is only used if an x value is not given for the point that calls autoIncrement.
  9809. */
  9810. autoIncrement: function () {
  9811. var series = this,
  9812. options = series.options,
  9813. xIncrement = series.xIncrement;
  9814. xIncrement = pick(xIncrement, options.pointStart, 0);
  9815. series.pointInterval = pick(series.pointInterval, options.pointInterval, 1);
  9816. series.xIncrement = xIncrement + series.pointInterval;
  9817. return xIncrement;
  9818. },
  9819. /**
  9820. * Divide the series data into segments divided by null values.
  9821. */
  9822. getSegments: function () {
  9823. var series = this,
  9824. lastNull = -1,
  9825. segments = [],
  9826. i,
  9827. points = series.points,
  9828. pointsLength = points.length;
  9829. if (pointsLength) { // no action required for []
  9830. // if connect nulls, just remove null points
  9831. if (series.options.connectNulls) {
  9832. i = pointsLength;
  9833. while (i--) {
  9834. if (points[i].y === null) {
  9835. points.splice(i, 1);
  9836. }
  9837. }
  9838. if (points.length) {
  9839. segments = [points];
  9840. }
  9841. // else, split on null points
  9842. } else {
  9843. each(points, function (point, i) {
  9844. if (point.y === null) {
  9845. if (i > lastNull + 1) {
  9846. segments.push(points.slice(lastNull + 1, i));
  9847. }
  9848. lastNull = i;
  9849. } else if (i === pointsLength - 1) { // last value
  9850. segments.push(points.slice(lastNull + 1, i + 1));
  9851. }
  9852. });
  9853. }
  9854. }
  9855. // register it
  9856. series.segments = segments;
  9857. },
  9858. /**
  9859. * Set the series options by merging from the options tree
  9860. * @param {Object} itemOptions
  9861. */
  9862. setOptions: function (itemOptions) {
  9863. var series = this,
  9864. chart = series.chart,
  9865. chartOptions = chart.options,
  9866. plotOptions = chartOptions.plotOptions,
  9867. data = itemOptions.data,
  9868. options;
  9869. itemOptions.data = null; // remove from merge to prevent looping over the data set
  9870. options = merge(
  9871. plotOptions[this.type],
  9872. plotOptions.series,
  9873. itemOptions
  9874. );
  9875. // Re-insert the data array to the options and the original config (#717)
  9876. options.data = itemOptions.data = data;
  9877. // the tooltip options are merged between global and series specific options
  9878. series.tooltipOptions = merge(chartOptions.tooltip, options.tooltip);
  9879. return options;
  9880. },
  9881. /**
  9882. * Get the series' color
  9883. */
  9884. getColor: function () {
  9885. var options = this.options,
  9886. defaultColors = this.chart.options.colors,
  9887. counters = this.chart.counters;
  9888. this.color = options.color ||
  9889. (!options.colorByPoint && defaultColors[counters.color++]) || 'gray';
  9890. counters.wrapColor(defaultColors.length);
  9891. },
  9892. /**
  9893. * Get the series' symbol
  9894. */
  9895. getSymbol: function () {
  9896. var series = this,
  9897. seriesMarkerOption = series.options.marker,
  9898. chart = series.chart,
  9899. defaultSymbols = chart.options.symbols,
  9900. counters = chart.counters;
  9901. series.symbol = seriesMarkerOption.symbol || defaultSymbols[counters.symbol++];
  9902. // don't substract radius in image symbols (#604)
  9903. if (/^url/.test(series.symbol)) {
  9904. seriesMarkerOption.radius = 0;
  9905. }
  9906. counters.wrapSymbol(defaultSymbols.length);
  9907. },
  9908. /**
  9909. * Get the series' symbol in the legend. This method should be overridable to create custom
  9910. * symbols through Highcharts.seriesTypes[type].prototype.drawLegendSymbols.
  9911. *
  9912. * @param {Object} legend The legend object
  9913. */
  9914. drawLegendSymbol: function (legend) {
  9915. var options = this.options,
  9916. markerOptions = options.marker,
  9917. radius,
  9918. legendOptions = legend.options,
  9919. legendSymbol,
  9920. symbolWidth = legendOptions.symbolWidth,
  9921. renderer = this.chart.renderer,
  9922. legendItemGroup = this.legendGroup,
  9923. baseline = legend.baseline,
  9924. attr;
  9925. // Draw the line
  9926. if (options.lineWidth) {
  9927. attr = {
  9928. 'stroke-width': options.lineWidth
  9929. };
  9930. if (options.dashStyle) {
  9931. attr.dashstyle = options.dashStyle;
  9932. }
  9933. this.legendLine = renderer.path([
  9934. M,
  9935. 0,
  9936. baseline - 4,
  9937. L,
  9938. symbolWidth,
  9939. baseline - 4
  9940. ])
  9941. .attr(attr)
  9942. .add(legendItemGroup);
  9943. }
  9944. // Draw the marker
  9945. if (markerOptions && markerOptions.enabled) {
  9946. radius = markerOptions.radius;
  9947. this.legendSymbol = legendSymbol = renderer.symbol(
  9948. this.symbol,
  9949. (symbolWidth / 2) - radius,
  9950. baseline - 4 - radius,
  9951. 2 * radius,
  9952. 2 * radius
  9953. )
  9954. .attr(this.pointAttr[NORMAL_STATE])
  9955. .add(legendItemGroup);
  9956. }
  9957. },
  9958. /**
  9959. * Add a point dynamically after chart load time
  9960. * @param {Object} options Point options as given in series.data
  9961. * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
  9962. * @param {Boolean} shift If shift is true, a point is shifted off the start
  9963. * of the series as one is appended to the end.
  9964. * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
  9965. * configuration
  9966. */
  9967. addPoint: function (options, redraw, shift, animation) {
  9968. var series = this,
  9969. data = series.data,
  9970. graph = series.graph,
  9971. area = series.area,
  9972. chart = series.chart,
  9973. xData = series.xData,
  9974. yData = series.yData,
  9975. currentShift = (graph && graph.shift) || 0,
  9976. dataOptions = series.options.data,
  9977. point;
  9978. //point = (new series.pointClass()).init(series, options);
  9979. setAnimation(animation, chart);
  9980. // Make graph animate sideways
  9981. if (graph && shift) {
  9982. graph.shift = currentShift + 1;
  9983. }
  9984. if (area) {
  9985. if (shift) { // #780
  9986. area.shift = currentShift + 1;
  9987. }
  9988. area.isArea = true; // needed in animation, both with and without shift
  9989. }
  9990. // Optional redraw, defaults to true
  9991. redraw = pick(redraw, true);
  9992. // Get options and push the point to xData, yData and series.options. In series.generatePoints
  9993. // the Point instance will be created on demand and pushed to the series.data array.
  9994. point = { series: series };
  9995. series.pointClass.prototype.applyOptions.apply(point, [options]);
  9996. xData.push(point.x);
  9997. yData.push(series.valueCount === 4 ? [point.open, point.high, point.low, point.close] : point.y);
  9998. dataOptions.push(options);
  9999. // Shift the first point off the parallel arrays
  10000. // todo: consider series.removePoint(i) method
  10001. if (shift) {
  10002. if (data[0] && data[0].remove) {
  10003. data[0].remove(false);
  10004. } else {
  10005. data.shift();
  10006. xData.shift();
  10007. yData.shift();
  10008. dataOptions.shift();
  10009. }
  10010. }
  10011. series.getAttribs();
  10012. // redraw
  10013. series.isDirty = true;
  10014. series.isDirtyData = true;
  10015. if (redraw) {
  10016. chart.redraw();
  10017. }
  10018. },
  10019. /**
  10020. * Replace the series data with a new set of data
  10021. * @param {Object} data
  10022. * @param {Object} redraw
  10023. */
  10024. setData: function (data, redraw) {
  10025. var series = this,
  10026. oldData = series.points,
  10027. options = series.options,
  10028. initialColor = series.initialColor,
  10029. chart = series.chart,
  10030. firstPoint = null,
  10031. xAxis = series.xAxis,
  10032. i,
  10033. pointProto = series.pointClass.prototype;
  10034. // reset properties
  10035. series.xIncrement = null;
  10036. series.pointRange = (xAxis && xAxis.categories && 1) || options.pointRange;
  10037. if (defined(initialColor)) { // reset colors for pie
  10038. chart.counters.color = initialColor;
  10039. }
  10040. // parallel arrays
  10041. var xData = [],
  10042. yData = [],
  10043. dataLength = data ? data.length : [],
  10044. turboThreshold = options.turboThreshold || 1000,
  10045. pt,
  10046. valueCount = series.valueCount;
  10047. // In turbo mode, only one- or twodimensional arrays of numbers are allowed. The
  10048. // first value is tested, and we assume that all the rest are defined the same
  10049. // way. Although the 'for' loops are similar, they are repeated inside each
  10050. // if-else conditional for max performance.
  10051. if (dataLength > turboThreshold) {
  10052. // find the first non-null point
  10053. i = 0;
  10054. while (firstPoint === null && i < dataLength) {
  10055. firstPoint = data[i];
  10056. i++;
  10057. }
  10058. if (isNumber(firstPoint)) { // assume all points are numbers
  10059. var x = pick(options.pointStart, 0),
  10060. pointInterval = pick(options.pointInterval, 1);
  10061. for (i = 0; i < dataLength; i++) {
  10062. xData[i] = x;
  10063. yData[i] = data[i];
  10064. x += pointInterval;
  10065. }
  10066. series.xIncrement = x;
  10067. } else if (isArray(firstPoint)) { // assume all points are arrays
  10068. if (valueCount) { // [x, low, high] or [x, o, h, l, c]
  10069. for (i = 0; i < dataLength; i++) {
  10070. pt = data[i];
  10071. xData[i] = pt[0];
  10072. yData[i] = pt.slice(1, valueCount + 1);
  10073. }
  10074. } else { // [x, y]
  10075. for (i = 0; i < dataLength; i++) {
  10076. pt = data[i];
  10077. xData[i] = pt[0];
  10078. yData[i] = pt[1];
  10079. }
  10080. }
  10081. } /* else {
  10082. error(12); // Highcharts expects configs to be numbers or arrays in turbo mode
  10083. }*/
  10084. } else {
  10085. for (i = 0; i < dataLength; i++) {
  10086. pt = { series: series };
  10087. pointProto.applyOptions.apply(pt, [data[i]]);
  10088. xData[i] = pt.x;
  10089. yData[i] = pointProto.toYData ? pointProto.toYData.apply(pt) : pt.y;
  10090. }
  10091. }
  10092. series.data = [];
  10093. series.options.data = data;
  10094. series.xData = xData;
  10095. series.yData = yData;
  10096. // destroy old points
  10097. i = (oldData && oldData.length) || 0;
  10098. while (i--) {
  10099. if (oldData[i] && oldData[i].destroy) {
  10100. oldData[i].destroy();
  10101. }
  10102. }
  10103. // reset minRange (#878)
  10104. if (xAxis) {
  10105. xAxis.minRange = xAxis.userMinRange;
  10106. }
  10107. // redraw
  10108. series.isDirty = series.isDirtyData = chart.isDirtyBox = true;
  10109. if (pick(redraw, true)) {
  10110. chart.redraw(false);
  10111. }
  10112. },
  10113. /**
  10114. * Remove a series and optionally redraw the chart
  10115. *
  10116. * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
  10117. * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
  10118. * configuration
  10119. */
  10120. remove: function (redraw, animation) {
  10121. var series = this,
  10122. chart = series.chart;
  10123. redraw = pick(redraw, true);
  10124. if (!series.isRemoving) { /* prevent triggering native event in jQuery
  10125. (calling the remove function from the remove event) */
  10126. series.isRemoving = true;
  10127. // fire the event with a default handler of removing the point
  10128. fireEvent(series, 'remove', null, function () {
  10129. // destroy elements
  10130. series.destroy();
  10131. // redraw
  10132. chart.isDirtyLegend = chart.isDirtyBox = true;
  10133. if (redraw) {
  10134. chart.redraw(animation);
  10135. }
  10136. });
  10137. }
  10138. series.isRemoving = false;
  10139. },
  10140. /**
  10141. * Process the data by cropping away unused data points if the series is longer
  10142. * than the crop threshold. This saves computing time for lage series.
  10143. */
  10144. processData: function (force) {
  10145. var series = this,
  10146. processedXData = series.xData, // copied during slice operation below
  10147. processedYData = series.yData,
  10148. dataLength = processedXData.length,
  10149. cropStart = 0,
  10150. cropEnd = dataLength,
  10151. cropped,
  10152. distance,
  10153. closestPointRange,
  10154. xAxis = series.xAxis,
  10155. i, // loop variable
  10156. options = series.options,
  10157. cropThreshold = options.cropThreshold,
  10158. isCartesian = series.isCartesian;
  10159. // If the series data or axes haven't changed, don't go through this. Return false to pass
  10160. // the message on to override methods like in data grouping.
  10161. if (isCartesian && !series.isDirty && !xAxis.isDirty && !series.yAxis.isDirty && !force) {
  10162. return false;
  10163. }
  10164. // optionally filter out points outside the plot area
  10165. if (isCartesian && series.sorted && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) {
  10166. var extremes = xAxis.getExtremes(),
  10167. min = extremes.min,
  10168. max = extremes.max;
  10169. // it's outside current extremes
  10170. if (processedXData[dataLength - 1] < min || processedXData[0] > max) {
  10171. processedXData = [];
  10172. processedYData = [];
  10173. // only crop if it's actually spilling out
  10174. } else if (processedXData[0] < min || processedXData[dataLength - 1] > max) {
  10175. // iterate up to find slice start
  10176. for (i = 0; i < dataLength; i++) {
  10177. if (processedXData[i] >= min) {
  10178. cropStart = mathMax(0, i - 1);
  10179. break;
  10180. }
  10181. }
  10182. // proceed to find slice end
  10183. for (; i < dataLength; i++) {
  10184. if (processedXData[i] > max) {
  10185. cropEnd = i + 1;
  10186. break;
  10187. }
  10188. }
  10189. processedXData = processedXData.slice(cropStart, cropEnd);
  10190. processedYData = processedYData.slice(cropStart, cropEnd);
  10191. cropped = true;
  10192. }
  10193. }
  10194. // Find the closest distance between processed points
  10195. for (i = processedXData.length - 1; i > 0; i--) {
  10196. distance = processedXData[i] - processedXData[i - 1];
  10197. if (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) {
  10198. closestPointRange = distance;
  10199. }
  10200. }
  10201. // Record the properties
  10202. series.cropped = cropped; // undefined or true
  10203. series.cropStart = cropStart;
  10204. series.processedXData = processedXData;
  10205. series.processedYData = processedYData;
  10206. if (options.pointRange === null) { // null means auto, as for columns, candlesticks and OHLC
  10207. series.pointRange = closestPointRange || 1;
  10208. }
  10209. series.closestPointRange = closestPointRange;
  10210. },
  10211. /**
  10212. * Generate the data point after the data has been processed by cropping away
  10213. * unused points and optionally grouped in Highcharts Stock.
  10214. */
  10215. generatePoints: function () {
  10216. var series = this,
  10217. options = series.options,
  10218. dataOptions = options.data,
  10219. data = series.data,
  10220. dataLength,
  10221. processedXData = series.processedXData,
  10222. processedYData = series.processedYData,
  10223. pointClass = series.pointClass,
  10224. processedDataLength = processedXData.length,
  10225. cropStart = series.cropStart || 0,
  10226. cursor,
  10227. hasGroupedData = series.hasGroupedData,
  10228. point,
  10229. points = [],
  10230. i;
  10231. if (!data && !hasGroupedData) {
  10232. var arr = [];
  10233. arr.length = dataOptions.length;
  10234. data = series.data = arr;
  10235. }
  10236. for (i = 0; i < processedDataLength; i++) {
  10237. cursor = cropStart + i;
  10238. if (!hasGroupedData) {
  10239. if (data[cursor]) {
  10240. point = data[cursor];
  10241. } else if (dataOptions[cursor] !== UNDEFINED) { // #970
  10242. data[cursor] = point = (new pointClass()).init(series, dataOptions[cursor], processedXData[i]);
  10243. }
  10244. points[i] = point;
  10245. } else {
  10246. // splat the y data in case of ohlc data array
  10247. points[i] = (new pointClass()).init(series, [processedXData[i]].concat(splat(processedYData[i])));
  10248. }
  10249. }
  10250. // Hide cropped-away points - this only runs when the number of points is above cropThreshold, or when
  10251. // swithching view from non-grouped data to grouped data (#637)
  10252. if (data && (processedDataLength !== (dataLength = data.length) || hasGroupedData)) {
  10253. for (i = 0; i < dataLength; i++) {
  10254. if (i === cropStart && !hasGroupedData) { // when has grouped data, clear all points
  10255. i += processedDataLength;
  10256. }
  10257. if (data[i]) {
  10258. data[i].destroyElements();
  10259. data[i].plotX = UNDEFINED; // #1003
  10260. }
  10261. }
  10262. }
  10263. series.data = data;
  10264. series.points = points;
  10265. },
  10266. /**
  10267. * Translate data points from raw data values to chart specific positioning data
  10268. * needed later in drawPoints, drawGraph and drawTracker.
  10269. */
  10270. translate: function () {
  10271. if (!this.processedXData) { // hidden series
  10272. this.processData();
  10273. }
  10274. this.generatePoints();
  10275. var series = this,
  10276. chart = series.chart,
  10277. options = series.options,
  10278. stacking = options.stacking,
  10279. xAxis = series.xAxis,
  10280. categories = xAxis.categories,
  10281. yAxis = series.yAxis,
  10282. points = series.points,
  10283. dataLength = points.length,
  10284. hasModifyValue = !!series.modifyValue,
  10285. isLastSeries,
  10286. allStackSeries = yAxis.series,
  10287. i = allStackSeries.length;
  10288. // Is it the last visible series?
  10289. while (i--) {
  10290. if (allStackSeries[i].visible) {
  10291. if (i === series.index) {
  10292. isLastSeries = true;
  10293. }
  10294. break;
  10295. }
  10296. }
  10297. // Translate each point
  10298. for (i = 0; i < dataLength; i++) {
  10299. var point = points[i],
  10300. xValue = point.x,
  10301. yValue = point.y,
  10302. yBottom = point.low,
  10303. stack = yAxis.stacks[(yValue < options.threshold ? '-' : '') + series.stackKey],
  10304. pointStack,
  10305. pointStackTotal;
  10306. // get the plotX translation
  10307. //point.plotX = mathRound(xAxis.translate(xValue, 0, 0, 0, 1) * 10) / 10; // Math.round fixes #591
  10308. point.plotX = xAxis.translate(xValue, 0, 0, 0, 1); // Math.round fixes #591
  10309. // calculate the bottom y value for stacked series
  10310. if (stacking && series.visible && stack && stack[xValue]) {
  10311. pointStack = stack[xValue];
  10312. pointStackTotal = pointStack.total;
  10313. pointStack.cum = yBottom = pointStack.cum - yValue; // start from top
  10314. yValue = yBottom + yValue;
  10315. if (isLastSeries) {
  10316. yBottom = options.threshold;
  10317. }
  10318. if (stacking === 'percent') {
  10319. yBottom = pointStackTotal ? yBottom * 100 / pointStackTotal : 0;
  10320. yValue = pointStackTotal ? yValue * 100 / pointStackTotal : 0;
  10321. }
  10322. point.percentage = pointStackTotal ? point.y * 100 / pointStackTotal : 0;
  10323. point.stackTotal = pointStackTotal;
  10324. point.stackY = yValue;
  10325. }
  10326. // Set translated yBottom or remove it
  10327. point.yBottom = defined(yBottom) ?
  10328. yAxis.translate(yBottom, 0, 1, 0, 1) :
  10329. null;
  10330. // general hook, used for Highstock compare mode
  10331. if (hasModifyValue) {
  10332. yValue = series.modifyValue(yValue, point);
  10333. }
  10334. // Set the the plotY value, reset it for redraws
  10335. point.plotY = (typeof yValue === 'number') ?
  10336. mathRound(yAxis.translate(yValue, 0, 1, 0, 1) * 10) / 10 : // Math.round fixes #591
  10337. UNDEFINED;
  10338. // set client related positions for mouse tracking
  10339. point.clientX = chart.inverted ?
  10340. chart.plotHeight - point.plotX :
  10341. point.plotX; // for mouse tracking
  10342. // some API data
  10343. point.category = categories && categories[point.x] !== UNDEFINED ?
  10344. categories[point.x] : point.x;
  10345. }
  10346. // now that we have the cropped data, build the segments
  10347. series.getSegments();
  10348. },
  10349. /**
  10350. * Memoize tooltip texts and positions
  10351. */
  10352. setTooltipPoints: function (renew) {
  10353. var series = this,
  10354. chart = series.chart,
  10355. points = [],
  10356. pointsLength,
  10357. plotSize = chart.plotSizeX,
  10358. low,
  10359. high,
  10360. xAxis = series.xAxis,
  10361. point,
  10362. i,
  10363. tooltipPoints = []; // a lookup array for each pixel in the x dimension
  10364. // don't waste resources if tracker is disabled
  10365. if (series.options.enableMouseTracking === false) {
  10366. return;
  10367. }
  10368. // renew
  10369. if (renew) {
  10370. series.tooltipPoints = null;
  10371. }
  10372. // concat segments to overcome null values
  10373. each(series.segments || series.points, function (segment) {
  10374. points = points.concat(segment);
  10375. });
  10376. // loop the concatenated points and apply each point to all the closest
  10377. // pixel positions
  10378. if (xAxis && xAxis.reversed) {
  10379. points = points.reverse();
  10380. }
  10381. // Assign each pixel position to the nearest point
  10382. pointsLength = points.length;
  10383. for (i = 0; i < pointsLength; i++) {
  10384. point = points[i];
  10385. low = points[i - 1] ? points[i - 1]._high + 1 : 0;
  10386. point._high = high = points[i + 1] ?
  10387. mathMax(0, mathFloor((point.plotX + (points[i + 1] ? points[i + 1].plotX : plotSize)) / 2)) :
  10388. plotSize;
  10389. while (low >= 0 && low <= high) {
  10390. tooltipPoints[low++] = point;
  10391. }
  10392. }
  10393. series.tooltipPoints = tooltipPoints;
  10394. },
  10395. /**
  10396. * Format the header of the tooltip
  10397. */
  10398. tooltipHeaderFormatter: function (key) {
  10399. var series = this,
  10400. tooltipOptions = series.tooltipOptions,
  10401. xDateFormat = tooltipOptions.xDateFormat,
  10402. xAxis = series.xAxis,
  10403. isDateTime = xAxis && xAxis.options.type === 'datetime',
  10404. n;
  10405. // Guess the best date format based on the closest point distance (#568)
  10406. if (isDateTime && !xDateFormat) {
  10407. for (n in timeUnits) {
  10408. if (timeUnits[n] >= xAxis.closestPointRange) {
  10409. xDateFormat = tooltipOptions.dateTimeLabelFormats[n];
  10410. break;
  10411. }
  10412. }
  10413. }
  10414. return tooltipOptions.headerFormat
  10415. .replace('{point.key}', isDateTime ? dateFormat(xDateFormat, key) : key)
  10416. .replace('{series.name}', series.name)
  10417. .replace('{series.color}', series.color);
  10418. },
  10419. /**
  10420. * Series mouse over handler
  10421. */
  10422. onMouseOver: function () {
  10423. var series = this,
  10424. chart = series.chart,
  10425. hoverSeries = chart.hoverSeries;
  10426. if (!hasTouch && chart.mouseIsDown) {
  10427. return;
  10428. }
  10429. // set normal state to previous series
  10430. if (hoverSeries && hoverSeries !== series) {
  10431. hoverSeries.onMouseOut();
  10432. }
  10433. // trigger the event, but to save processing time,
  10434. // only if defined
  10435. if (series.options.events.mouseOver) {
  10436. fireEvent(series, 'mouseOver');
  10437. }
  10438. // hover this
  10439. series.setState(HOVER_STATE);
  10440. chart.hoverSeries = series;
  10441. },
  10442. /**
  10443. * Series mouse out handler
  10444. */
  10445. onMouseOut: function () {
  10446. // trigger the event only if listeners exist
  10447. var series = this,
  10448. options = series.options,
  10449. chart = series.chart,
  10450. tooltip = chart.tooltip,
  10451. hoverPoint = chart.hoverPoint;
  10452. // trigger mouse out on the point, which must be in this series
  10453. if (hoverPoint) {
  10454. hoverPoint.onMouseOut();
  10455. }
  10456. // fire the mouse out event
  10457. if (series && options.events.mouseOut) {
  10458. fireEvent(series, 'mouseOut');
  10459. }
  10460. // hide the tooltip
  10461. if (tooltip && !options.stickyTracking && !tooltip.shared) {
  10462. tooltip.hide();
  10463. }
  10464. // set normal state
  10465. series.setState();
  10466. chart.hoverSeries = null;
  10467. },
  10468. /**
  10469. * Animate in the series
  10470. */
  10471. animate: function (init) {
  10472. var series = this,
  10473. chart = series.chart,
  10474. clipRect = series.clipRect,
  10475. animation = series.options.animation;
  10476. if (animation && !isObject(animation)) {
  10477. animation = {};
  10478. }
  10479. if (init) { // initialize the animation
  10480. if (!clipRect.isAnimating) { // apply it only for one of the series
  10481. clipRect.attr('width', 0);
  10482. clipRect.isAnimating = true;
  10483. }
  10484. } else { // run the animation
  10485. clipRect.animate({
  10486. width: chart.plotSizeX
  10487. }, animation);
  10488. // delete this function to allow it only once
  10489. this.animate = null;
  10490. }
  10491. },
  10492. /**
  10493. * Draw the markers
  10494. */
  10495. drawPoints: function () {
  10496. var series = this,
  10497. pointAttr,
  10498. points = series.points,
  10499. chart = series.chart,
  10500. plotX,
  10501. plotY,
  10502. i,
  10503. point,
  10504. radius,
  10505. symbol,
  10506. isImage,
  10507. graphic;
  10508. if (series.options.marker.enabled) {
  10509. i = points.length;
  10510. while (i--) {
  10511. point = points[i];
  10512. plotX = point.plotX;
  10513. plotY = point.plotY;
  10514. graphic = point.graphic;
  10515. // only draw the point if y is defined
  10516. if (plotY !== UNDEFINED && !isNaN(plotY)) {
  10517. // shortcuts
  10518. pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE];
  10519. radius = pointAttr.r;
  10520. symbol = pick(point.marker && point.marker.symbol, series.symbol);
  10521. isImage = symbol.indexOf('url') === 0;
  10522. if (graphic) { // update
  10523. graphic.animate(extend({
  10524. x: plotX - radius,
  10525. y: plotY - radius
  10526. }, graphic.symbolName ? { // don't apply to image symbols #507
  10527. width: 2 * radius,
  10528. height: 2 * radius
  10529. } : {}));
  10530. } else if (radius > 0 || isImage) {
  10531. point.graphic = chart.renderer.symbol(
  10532. symbol,
  10533. plotX - radius,
  10534. plotY - radius,
  10535. 2 * radius,
  10536. 2 * radius
  10537. )
  10538. .attr(pointAttr)
  10539. .add(series.group);
  10540. }
  10541. }
  10542. }
  10543. }
  10544. },
  10545. /**
  10546. * Convert state properties from API naming conventions to SVG attributes
  10547. *
  10548. * @param {Object} options API options object
  10549. * @param {Object} base1 SVG attribute object to inherit from
  10550. * @param {Object} base2 Second level SVG attribute object to inherit from
  10551. */
  10552. convertAttribs: function (options, base1, base2, base3) {
  10553. var conversion = this.pointAttrToOptions,
  10554. attr,
  10555. option,
  10556. obj = {};
  10557. options = options || {};
  10558. base1 = base1 || {};
  10559. base2 = base2 || {};
  10560. base3 = base3 || {};
  10561. for (attr in conversion) {
  10562. option = conversion[attr];
  10563. obj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]);
  10564. }
  10565. return obj;
  10566. },
  10567. /**
  10568. * Get the state attributes. Each series type has its own set of attributes
  10569. * that are allowed to change on a point's state change. Series wide attributes are stored for
  10570. * all series, and additionally point specific attributes are stored for all
  10571. * points with individual marker options. If such options are not defined for the point,
  10572. * a reference to the series wide attributes is stored in point.pointAttr.
  10573. */
  10574. getAttribs: function () {
  10575. var series = this,
  10576. normalOptions = defaultPlotOptions[series.type].marker ? series.options.marker : series.options,
  10577. stateOptions = normalOptions.states,
  10578. stateOptionsHover = stateOptions[HOVER_STATE],
  10579. pointStateOptionsHover,
  10580. seriesColor = series.color,
  10581. normalDefaults = {
  10582. stroke: seriesColor,
  10583. fill: seriesColor
  10584. },
  10585. points = series.points || [], // #927
  10586. i,
  10587. point,
  10588. seriesPointAttr = [],
  10589. pointAttr,
  10590. pointAttrToOptions = series.pointAttrToOptions,
  10591. hasPointSpecificOptions,
  10592. key;
  10593. // series type specific modifications
  10594. if (series.options.marker) { // line, spline, area, areaspline, scatter
  10595. // if no hover radius is given, default to normal radius + 2
  10596. stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2;
  10597. stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1;
  10598. } else { // column, bar, pie
  10599. // if no hover color is given, brighten the normal color
  10600. stateOptionsHover.color = stateOptionsHover.color ||
  10601. Color(stateOptionsHover.color || seriesColor)
  10602. .brighten(stateOptionsHover.brightness).get();
  10603. }
  10604. // general point attributes for the series normal state
  10605. seriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults);
  10606. // HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius
  10607. each([HOVER_STATE, SELECT_STATE], function (state) {
  10608. seriesPointAttr[state] =
  10609. series.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]);
  10610. });
  10611. // set it
  10612. series.pointAttr = seriesPointAttr;
  10613. // Generate the point-specific attribute collections if specific point
  10614. // options are given. If not, create a referance to the series wide point
  10615. // attributes
  10616. i = points.length;
  10617. while (i--) {
  10618. point = points[i];
  10619. normalOptions = (point.options && point.options.marker) || point.options;
  10620. if (normalOptions && normalOptions.enabled === false) {
  10621. normalOptions.radius = 0;
  10622. }
  10623. hasPointSpecificOptions = false;
  10624. // check if the point has specific visual options
  10625. if (point.options) {
  10626. for (key in pointAttrToOptions) {
  10627. if (defined(normalOptions[pointAttrToOptions[key]])) {
  10628. hasPointSpecificOptions = true;
  10629. }
  10630. }
  10631. }
  10632. // a specific marker config object is defined for the individual point:
  10633. // create it's own attribute collection
  10634. if (hasPointSpecificOptions) {
  10635. pointAttr = [];
  10636. stateOptions = normalOptions.states || {}; // reassign for individual point
  10637. pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {};
  10638. // if no hover color is given, brighten the normal color
  10639. if (!series.options.marker) { // column, bar, point
  10640. pointStateOptionsHover.color =
  10641. Color(pointStateOptionsHover.color || point.options.color)
  10642. .brighten(pointStateOptionsHover.brightness ||
  10643. stateOptionsHover.brightness).get();
  10644. }
  10645. // normal point state inherits series wide normal state
  10646. pointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, seriesPointAttr[NORMAL_STATE]);
  10647. // inherit from point normal and series hover
  10648. pointAttr[HOVER_STATE] = series.convertAttribs(
  10649. stateOptions[HOVER_STATE],
  10650. seriesPointAttr[HOVER_STATE],
  10651. pointAttr[NORMAL_STATE]
  10652. );
  10653. // inherit from point normal and series hover
  10654. pointAttr[SELECT_STATE] = series.convertAttribs(
  10655. stateOptions[SELECT_STATE],
  10656. seriesPointAttr[SELECT_STATE],
  10657. pointAttr[NORMAL_STATE]
  10658. );
  10659. // no marker config object is created: copy a reference to the series-wide
  10660. // attribute collection
  10661. } else {
  10662. pointAttr = seriesPointAttr;
  10663. }
  10664. point.pointAttr = pointAttr;
  10665. }
  10666. },
  10667. /**
  10668. * Clear DOM objects and free up memory
  10669. */
  10670. destroy: function () {
  10671. var series = this,
  10672. chart = series.chart,
  10673. seriesClipRect = series.clipRect,
  10674. issue134 = /AppleWebKit\/533/.test(userAgent),
  10675. destroy,
  10676. i,
  10677. data = series.data || [],
  10678. point,
  10679. prop,
  10680. axis;
  10681. // add event hook
  10682. fireEvent(series, 'destroy');
  10683. // remove all events
  10684. removeEvent(series);
  10685. // erase from axes
  10686. each(['xAxis', 'yAxis'], function (AXIS) {
  10687. axis = series[AXIS];
  10688. if (axis) {
  10689. erase(axis.series, series);
  10690. axis.isDirty = true;
  10691. }
  10692. });
  10693. // remove legend items
  10694. if (series.legendItem) {
  10695. series.chart.legend.destroyItem(series);
  10696. }
  10697. // destroy all points with their elements
  10698. i = data.length;
  10699. while (i--) {
  10700. point = data[i];
  10701. if (point && point.destroy) {
  10702. point.destroy();
  10703. }
  10704. }
  10705. series.points = null;
  10706. // If this series clipRect is not the global one (which is removed on chart.destroy) we
  10707. // destroy it here.
  10708. if (seriesClipRect && seriesClipRect !== chart.clipRect) {
  10709. series.clipRect = seriesClipRect.destroy();
  10710. }
  10711. // destroy all SVGElements associated to the series
  10712. each(['area', 'graph', 'dataLabelsGroup', 'group', 'tracker', 'trackerGroup'], function (prop) {
  10713. if (series[prop]) {
  10714. // issue 134 workaround
  10715. destroy = issue134 && prop === 'group' ?
  10716. 'hide' :
  10717. 'destroy';
  10718. series[prop][destroy]();
  10719. }
  10720. });
  10721. // remove from hoverSeries
  10722. if (chart.hoverSeries === series) {
  10723. chart.hoverSeries = null;
  10724. }
  10725. erase(chart.series, series);
  10726. // clear all members
  10727. for (prop in series) {
  10728. delete series[prop];
  10729. }
  10730. },
  10731. /**
  10732. * Draw the data labels
  10733. */
  10734. drawDataLabels: function () {
  10735. var series = this,
  10736. seriesOptions = series.options,
  10737. options = seriesOptions.dataLabels;
  10738. if (options.enabled || series._hasPointLabels) {
  10739. var x,
  10740. y,
  10741. points = series.points,
  10742. pointOptions,
  10743. generalOptions,
  10744. str,
  10745. dataLabelsGroup = series.dataLabelsGroup,
  10746. chart = series.chart,
  10747. xAxis = series.xAxis,
  10748. groupLeft = xAxis ? xAxis.left : chart.plotLeft,
  10749. yAxis = series.yAxis,
  10750. groupTop = yAxis ? yAxis.top : chart.plotTop,
  10751. renderer = chart.renderer,
  10752. inverted = chart.inverted,
  10753. seriesType = series.type,
  10754. stacking = seriesOptions.stacking,
  10755. isBarLike = seriesType === 'column' || seriesType === 'bar',
  10756. vAlignIsNull = options.verticalAlign === null,
  10757. yIsNull = options.y === null,
  10758. fontMetrics = renderer.fontMetrics(options.style.fontSize), // height and baseline
  10759. fontLineHeight = fontMetrics.h,
  10760. fontBaseline = fontMetrics.b,
  10761. dataLabel,
  10762. enabled;
  10763. if (isBarLike) {
  10764. var defaultYs = {
  10765. top: fontBaseline,
  10766. middle: fontBaseline - fontLineHeight / 2,
  10767. bottom: -fontLineHeight + fontBaseline
  10768. };
  10769. if (stacking) {
  10770. // In stacked series the default label placement is inside the bars
  10771. if (vAlignIsNull) {
  10772. options = merge(options, {verticalAlign: 'middle'});
  10773. }
  10774. // If no y delta is specified, try to create a good default
  10775. if (yIsNull) {
  10776. options = merge(options, { y: defaultYs[options.verticalAlign]});
  10777. }
  10778. } else {
  10779. // In non stacked series the default label placement is on top of the bars
  10780. if (vAlignIsNull) {
  10781. options = merge(options, {verticalAlign: 'top'});
  10782. // If no y delta is specified, try to create a good default (like default bar)
  10783. } else if (yIsNull) {
  10784. options = merge(options, { y: defaultYs[options.verticalAlign]});
  10785. }
  10786. }
  10787. }
  10788. // create a separate group for the data labels to avoid rotation
  10789. if (!dataLabelsGroup) {
  10790. dataLabelsGroup = series.dataLabelsGroup =
  10791. renderer.g('data-labels')
  10792. .attr({
  10793. visibility: series.visible ? VISIBLE : HIDDEN,
  10794. zIndex: 6
  10795. })
  10796. .translate(groupLeft, groupTop)
  10797. .add();
  10798. } else {
  10799. dataLabelsGroup.translate(groupLeft, groupTop);
  10800. }
  10801. // make the labels for each point
  10802. generalOptions = options;
  10803. each(points, function (point) {
  10804. dataLabel = point.dataLabel;
  10805. // Merge in individual options from point
  10806. options = generalOptions; // reset changes from previous points
  10807. pointOptions = point.options;
  10808. if (pointOptions && pointOptions.dataLabels) {
  10809. options = merge(options, pointOptions.dataLabels);
  10810. }
  10811. enabled = options.enabled;
  10812. // Get the positions
  10813. if (enabled) {
  10814. var plotX = (point.barX && point.barX + point.barW / 2) || pick(point.plotX, -999),
  10815. plotY = pick(point.plotY, -999),
  10816. // if options.y is null, which happens by default on column charts, set the position
  10817. // above or below the column depending on the threshold
  10818. individualYDelta = options.y === null ?
  10819. (point.y >= seriesOptions.threshold ?
  10820. -fontLineHeight + fontBaseline : // below the threshold
  10821. fontBaseline) : // above the threshold
  10822. options.y;
  10823. x = (inverted ? chart.plotWidth - plotY : plotX) + options.x;
  10824. y = mathRound((inverted ? chart.plotHeight - plotX : plotY) + individualYDelta);
  10825. }
  10826. // If the point is outside the plot area, destroy it. #678, #820
  10827. if (dataLabel && series.isCartesian && (!chart.isInsidePlot(x, y) || !enabled)) {
  10828. point.dataLabel = dataLabel.destroy();
  10829. // Individual labels are disabled if the are explicitly disabled
  10830. // in the point options, or if they fall outside the plot area.
  10831. } else if (enabled) {
  10832. var align = options.align,
  10833. attr,
  10834. name;
  10835. // Get the string
  10836. str = options.formatter.call(point.getLabelConfig(), options);
  10837. // in columns, align the string to the column
  10838. if (seriesType === 'column') {
  10839. x += { left: -1, right: 1 }[align] * point.barW / 2 || 0;
  10840. }
  10841. if (!stacking && inverted && point.y < 0) {
  10842. align = 'right';
  10843. x -= 10;
  10844. }
  10845. // Determine the color
  10846. options.style.color = pick(options.color, options.style.color, series.color, 'black');
  10847. // update existing label
  10848. if (dataLabel) {
  10849. // vertically centered
  10850. dataLabel
  10851. .attr({
  10852. text: str
  10853. }).animate({
  10854. x: x,
  10855. y: y
  10856. });
  10857. // create new label
  10858. } else if (defined(str)) {
  10859. attr = {
  10860. align: align,
  10861. fill: options.backgroundColor,
  10862. stroke: options.borderColor,
  10863. 'stroke-width': options.borderWidth,
  10864. r: options.borderRadius || 0,
  10865. rotation: options.rotation,
  10866. padding: options.padding,
  10867. zIndex: 1
  10868. };
  10869. // Remove unused attributes (#947)
  10870. for (name in attr) {
  10871. if (attr[name] === UNDEFINED) {
  10872. delete attr[name];
  10873. }
  10874. }
  10875. dataLabel = point.dataLabel = renderer[options.rotation ? 'text' : 'label']( // labels don't support rotation
  10876. str,
  10877. x,
  10878. y,
  10879. null,
  10880. null,
  10881. null,
  10882. options.useHTML,
  10883. true // baseline for backwards compat
  10884. )
  10885. .attr(attr)
  10886. .css(options.style)
  10887. .add(dataLabelsGroup)
  10888. .shadow(options.shadow);
  10889. }
  10890. if (isBarLike && seriesOptions.stacking && dataLabel) {
  10891. var barX = point.barX,
  10892. barY = point.barY,
  10893. barW = point.barW,
  10894. barH = point.barH;
  10895. dataLabel.align(options, null,
  10896. {
  10897. x: inverted ? chart.plotWidth - barY - barH : barX,
  10898. y: inverted ? chart.plotHeight - barX - barW : barY,
  10899. width: inverted ? barH : barW,
  10900. height: inverted ? barW : barH
  10901. });
  10902. }
  10903. }
  10904. });
  10905. }
  10906. },
  10907. /**
  10908. * Return the graph path of a segment
  10909. */
  10910. getSegmentPath: function (segment) {
  10911. var series = this,
  10912. segmentPath = [];
  10913. // build the segment line
  10914. each(segment, function (point, i) {
  10915. if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
  10916. segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i));
  10917. } else {
  10918. // moveTo or lineTo
  10919. segmentPath.push(i ? L : M);
  10920. // step line?
  10921. if (i && series.options.step) {
  10922. var lastPoint = segment[i - 1];
  10923. segmentPath.push(
  10924. point.plotX,
  10925. lastPoint.plotY
  10926. );
  10927. }
  10928. // normal line to next point
  10929. segmentPath.push(
  10930. point.plotX,
  10931. point.plotY
  10932. );
  10933. }
  10934. });
  10935. return segmentPath;
  10936. },
  10937. /**
  10938. * Draw the actual graph
  10939. */
  10940. drawGraph: function () {
  10941. var series = this,
  10942. options = series.options,
  10943. chart = series.chart,
  10944. graph = series.graph,
  10945. graphPath = [],
  10946. group = series.group,
  10947. color = options.lineColor || series.color,
  10948. lineWidth = options.lineWidth,
  10949. dashStyle = options.dashStyle,
  10950. segmentPath,
  10951. renderer = chart.renderer,
  10952. singlePoints = [], // used in drawTracker
  10953. attribs;
  10954. // divide into segments and build graph and area paths
  10955. each(series.segments, function (segment) {
  10956. segmentPath = series.getSegmentPath(segment);
  10957. // add the segment to the graph, or a single point for tracking
  10958. if (segment.length > 1) {
  10959. graphPath = graphPath.concat(segmentPath);
  10960. } else {
  10961. singlePoints.push(segment[0]);
  10962. }
  10963. });
  10964. // used in drawTracker:
  10965. series.graphPath = graphPath;
  10966. series.singlePoints = singlePoints;
  10967. // draw the graph
  10968. if (graph) {
  10969. stop(graph); // cancel running animations, #459
  10970. graph.animate({ d: graphPath });
  10971. } else {
  10972. if (lineWidth) {
  10973. attribs = {
  10974. stroke: color,
  10975. 'stroke-width': lineWidth
  10976. };
  10977. if (dashStyle) {
  10978. attribs.dashstyle = dashStyle;
  10979. }
  10980. series.graph = renderer.path(graphPath)
  10981. .attr(attribs).add(group).shadow(options.shadow);
  10982. }
  10983. }
  10984. },
  10985. /**
  10986. * Initialize and perform group inversion on series.group and series.trackerGroup
  10987. */
  10988. invertGroups: function () {
  10989. var series = this,
  10990. group = series.group,
  10991. trackerGroup = series.trackerGroup,
  10992. chart = series.chart;
  10993. // A fixed size is needed for inversion to work
  10994. function setInvert() {
  10995. var size = {
  10996. width: series.yAxis.len,
  10997. height: series.xAxis.len
  10998. };
  10999. // Set the series.group size
  11000. group.attr(size).invert();
  11001. // Set the tracker group size
  11002. if (trackerGroup) {
  11003. trackerGroup.attr(size).invert();
  11004. }
  11005. }
  11006. addEvent(chart, 'resize', setInvert); // do it on resize
  11007. addEvent(series, 'destroy', function () {
  11008. removeEvent(chart, 'resize', setInvert);
  11009. });
  11010. // Do it now
  11011. setInvert(); // do it now
  11012. // On subsequent render and redraw, just do setInvert without setting up events again
  11013. series.invertGroups = setInvert;
  11014. },
  11015. /**
  11016. * Create the series group
  11017. */
  11018. createGroup: function () {
  11019. var chart = this.chart,
  11020. group = this.group = chart.renderer.g('series');
  11021. group.attr({
  11022. visibility: this.visible ? VISIBLE : HIDDEN,
  11023. zIndex: this.options.zIndex
  11024. })
  11025. .translate(this.xAxis.left, this.yAxis.top)
  11026. .add(chart.seriesGroup);
  11027. // Only run this once
  11028. this.createGroup = noop;
  11029. },
  11030. /**
  11031. * Render the graph and markers
  11032. */
  11033. render: function () {
  11034. var series = this,
  11035. chart = series.chart,
  11036. group,
  11037. options = series.options,
  11038. doClip = options.clip !== false,
  11039. animation = options.animation,
  11040. doAnimation = animation && series.animate,
  11041. duration = doAnimation ? (animation && animation.duration) || 500 : 0,
  11042. clipRect = series.clipRect,
  11043. renderer = chart.renderer;
  11044. // Add plot area clipping rectangle. If this is before chart.hasRendered,
  11045. // create one shared clipRect.
  11046. // Todo: since creating the clip property, the clipRect is created but
  11047. // never used when clip is false. A better way would be that the animation
  11048. // would run, then the clipRect destroyed.
  11049. if (!clipRect) {
  11050. clipRect = series.clipRect = !chart.hasRendered && chart.clipRect ?
  11051. chart.clipRect :
  11052. renderer.clipRect(0, 0, chart.plotSizeX, chart.plotSizeY + 1);
  11053. if (!chart.clipRect) {
  11054. chart.clipRect = clipRect;
  11055. }
  11056. }
  11057. // the group
  11058. series.createGroup();
  11059. group = series.group;
  11060. series.drawDataLabels();
  11061. // initiate the animation
  11062. if (doAnimation) {
  11063. series.animate(true);
  11064. }
  11065. // cache attributes for shapes
  11066. series.getAttribs();
  11067. // draw the graph if any
  11068. if (series.drawGraph) {
  11069. series.drawGraph();
  11070. }
  11071. // draw the points
  11072. series.drawPoints();
  11073. // draw the mouse tracking area
  11074. if (series.options.enableMouseTracking !== false) {
  11075. series.drawTracker();
  11076. }
  11077. // Handle inverted series and tracker groups
  11078. if (chart.inverted) {
  11079. series.invertGroups();
  11080. }
  11081. // Do the initial clipping. This must be done after inverting for VML.
  11082. if (doClip && !series.hasRendered) {
  11083. group.clip(clipRect);
  11084. if (series.trackerGroup) {
  11085. series.trackerGroup.clip(chart.clipRect);
  11086. }
  11087. }
  11088. // run the animation
  11089. if (doAnimation) {
  11090. series.animate();
  11091. }
  11092. // finish the individual clipRect
  11093. setTimeout(function () {
  11094. clipRect.isAnimating = false;
  11095. group = series.group; // can be destroyed during the timeout
  11096. if (group && clipRect !== chart.clipRect && clipRect.renderer) {
  11097. if (doClip) {
  11098. group.clip((series.clipRect = chart.clipRect));
  11099. }
  11100. clipRect.destroy();
  11101. }
  11102. }, duration);
  11103. series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
  11104. // (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
  11105. series.hasRendered = true;
  11106. },
  11107. /**
  11108. * Redraw the series after an update in the axes.
  11109. */
  11110. redraw: function () {
  11111. var series = this,
  11112. chart = series.chart,
  11113. wasDirtyData = series.isDirtyData, // cache it here as it is set to false in render, but used after
  11114. group = series.group;
  11115. // reposition on resize
  11116. if (group) {
  11117. if (chart.inverted) {
  11118. group.attr({
  11119. width: chart.plotWidth,
  11120. height: chart.plotHeight
  11121. });
  11122. }
  11123. group.animate({
  11124. translateX: series.xAxis.left,
  11125. translateY: series.yAxis.top
  11126. });
  11127. }
  11128. series.translate();
  11129. series.setTooltipPoints(true);
  11130. series.render();
  11131. if (wasDirtyData) {
  11132. fireEvent(series, 'updatedData');
  11133. }
  11134. },
  11135. /**
  11136. * Set the state of the graph
  11137. */
  11138. setState: function (state) {
  11139. var series = this,
  11140. options = series.options,
  11141. graph = series.graph,
  11142. stateOptions = options.states,
  11143. lineWidth = options.lineWidth;
  11144. state = state || NORMAL_STATE;
  11145. if (series.state !== state) {
  11146. series.state = state;
  11147. if (stateOptions[state] && stateOptions[state].enabled === false) {
  11148. return;
  11149. }
  11150. if (state) {
  11151. lineWidth = stateOptions[state].lineWidth || lineWidth + 1;
  11152. }
  11153. if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
  11154. graph.attr({ // use attr because animate will cause any other animation on the graph to stop
  11155. 'stroke-width': lineWidth
  11156. }, state ? 0 : 500);
  11157. }
  11158. }
  11159. },
  11160. /**
  11161. * Set the visibility of the graph
  11162. *
  11163. * @param vis {Boolean} True to show the series, false to hide. If UNDEFINED,
  11164. * the visibility is toggled.
  11165. */
  11166. setVisible: function (vis, redraw) {
  11167. var series = this,
  11168. chart = series.chart,
  11169. legendItem = series.legendItem,
  11170. seriesGroup = series.group,
  11171. seriesTracker = series.tracker,
  11172. dataLabelsGroup = series.dataLabelsGroup,
  11173. showOrHide,
  11174. i,
  11175. points = series.points,
  11176. point,
  11177. ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,
  11178. oldVisibility = series.visible;
  11179. // if called without an argument, toggle visibility
  11180. series.visible = vis = vis === UNDEFINED ? !oldVisibility : vis;
  11181. showOrHide = vis ? 'show' : 'hide';
  11182. // show or hide series
  11183. if (seriesGroup) { // pies don't have one
  11184. seriesGroup[showOrHide]();
  11185. }
  11186. // show or hide trackers
  11187. if (seriesTracker) {
  11188. seriesTracker[showOrHide]();
  11189. } else if (points) {
  11190. i = points.length;
  11191. while (i--) {
  11192. point = points[i];
  11193. if (point.tracker) {
  11194. point.tracker[showOrHide]();
  11195. }
  11196. }
  11197. }
  11198. if (dataLabelsGroup) {
  11199. dataLabelsGroup[showOrHide]();
  11200. }
  11201. if (legendItem) {
  11202. chart.legend.colorizeItem(series, vis);
  11203. }
  11204. // rescale or adapt to resized chart
  11205. series.isDirty = true;
  11206. // in a stack, all other series are affected
  11207. if (series.options.stacking) {
  11208. each(chart.series, function (otherSeries) {
  11209. if (otherSeries.options.stacking && otherSeries.visible) {
  11210. otherSeries.isDirty = true;
  11211. }
  11212. });
  11213. }
  11214. if (ignoreHiddenSeries) {
  11215. chart.isDirtyBox = true;
  11216. }
  11217. if (redraw !== false) {
  11218. chart.redraw();
  11219. }
  11220. fireEvent(series, showOrHide);
  11221. },
  11222. /**
  11223. * Show the graph
  11224. */
  11225. show: function () {
  11226. this.setVisible(true);
  11227. },
  11228. /**
  11229. * Hide the graph
  11230. */
  11231. hide: function () {
  11232. this.setVisible(false);
  11233. },
  11234. /**
  11235. * Set the selected state of the graph
  11236. *
  11237. * @param selected {Boolean} True to select the series, false to unselect. If
  11238. * UNDEFINED, the selection state is toggled.
  11239. */
  11240. select: function (selected) {
  11241. var series = this;
  11242. // if called without an argument, toggle
  11243. series.selected = selected = (selected === UNDEFINED) ? !series.selected : selected;
  11244. if (series.checkbox) {
  11245. series.checkbox.checked = selected;
  11246. }
  11247. fireEvent(series, selected ? 'select' : 'unselect');
  11248. },
  11249. /**
  11250. * Create a group that holds the tracking object or objects. This allows for
  11251. * individual clipping and placement of each series tracker.
  11252. */
  11253. drawTrackerGroup: function () {
  11254. var trackerGroup = this.trackerGroup,
  11255. chart = this.chart;
  11256. if (this.isCartesian) {
  11257. // Generate it on first call
  11258. if (!trackerGroup) {
  11259. this.trackerGroup = trackerGroup = chart.renderer.g()
  11260. .attr({
  11261. zIndex: this.options.zIndex || 1
  11262. })
  11263. .add(chart.trackerGroup);
  11264. }
  11265. // Place it on first and subsequent (redraw) calls
  11266. trackerGroup.translate(this.xAxis.left, this.yAxis.top);
  11267. }
  11268. return trackerGroup;
  11269. },
  11270. /**
  11271. * Draw the tracker object that sits above all data labels and markers to
  11272. * track mouse events on the graph or points. For the line type charts
  11273. * the tracker uses the same graphPath, but with a greater stroke width
  11274. * for better control.
  11275. */
  11276. drawTracker: function () {
  11277. var series = this,
  11278. options = series.options,
  11279. trackByArea = options.trackByArea,
  11280. trackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath),
  11281. trackerPathLength = trackerPath.length,
  11282. chart = series.chart,
  11283. renderer = chart.renderer,
  11284. snap = chart.options.tooltip.snap,
  11285. tracker = series.tracker,
  11286. cursor = options.cursor,
  11287. css = cursor && { cursor: cursor },
  11288. singlePoints = series.singlePoints,
  11289. trackerGroup = series.drawTrackerGroup(),
  11290. singlePoint,
  11291. i;
  11292. // Extend end points. A better way would be to use round linecaps,
  11293. // but those are not clickable in VML.
  11294. if (trackerPathLength && !trackByArea) {
  11295. i = trackerPathLength + 1;
  11296. while (i--) {
  11297. if (trackerPath[i] === M) { // extend left side
  11298. trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L);
  11299. }
  11300. if ((i && trackerPath[i] === M) || i === trackerPathLength) { // extend right side
  11301. trackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]);
  11302. }
  11303. }
  11304. }
  11305. // handle single points
  11306. for (i = 0; i < singlePoints.length; i++) {
  11307. singlePoint = singlePoints[i];
  11308. trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY,
  11309. L, singlePoint.plotX + snap, singlePoint.plotY);
  11310. }
  11311. // draw the tracker
  11312. if (tracker) {
  11313. tracker.attr({ d: trackerPath });
  11314. } else { // create
  11315. series.tracker = renderer.path(trackerPath)
  11316. .attr({
  11317. isTracker: true,
  11318. 'stroke-linejoin': 'bevel',
  11319. visibility: series.visible ? VISIBLE : HIDDEN,
  11320. stroke: TRACKER_FILL,
  11321. fill: trackByArea ? TRACKER_FILL : NONE,
  11322. 'stroke-width' : options.lineWidth + (trackByArea ? 0 : 2 * snap)
  11323. })
  11324. .on(hasTouch ? 'touchstart' : 'mouseover', function () {
  11325. if (chart.hoverSeries !== series) {
  11326. series.onMouseOver();
  11327. }
  11328. })
  11329. .on('mouseout', function () {
  11330. if (!options.stickyTracking) {
  11331. series.onMouseOut();
  11332. }
  11333. })
  11334. .css(css)
  11335. .add(trackerGroup);
  11336. }
  11337. }
  11338. }; // end Series prototype
  11339. /**
  11340. * LineSeries object
  11341. */
  11342. var LineSeries = extendClass(Series);
  11343. seriesTypes.line = LineSeries;
  11344. /**
  11345. * Set the default options for area
  11346. */
  11347. defaultPlotOptions.area = merge(defaultSeriesOptions, {
  11348. threshold: 0
  11349. // trackByArea: false,
  11350. // lineColor: null, // overrides color, but lets fillColor be unaltered
  11351. // fillOpacity: 0.75,
  11352. // fillColor: null
  11353. });
  11354. /**
  11355. * AreaSeries object
  11356. */
  11357. var AreaSeries = extendClass(Series, {
  11358. type: 'area',
  11359. /**
  11360. * Extend the base Series getSegmentPath method by adding the path for the area.
  11361. * This path is pushed to the series.areaPath property.
  11362. */
  11363. getSegmentPath: function (segment) {
  11364. var segmentPath = Series.prototype.getSegmentPath.call(this, segment), // call base method
  11365. areaSegmentPath = [].concat(segmentPath), // work on a copy for the area path
  11366. i,
  11367. options = this.options,
  11368. segLength = segmentPath.length,
  11369. translatedThreshold = this.yAxis.getThreshold(options.threshold);
  11370. if (segLength === 3) { // for animation from 1 to two points
  11371. areaSegmentPath.push(L, segmentPath[1], segmentPath[2]);
  11372. }
  11373. if (options.stacking && this.type !== 'areaspline') {
  11374. // Follow stack back. Todo: implement areaspline. A general solution could be to
  11375. // reverse the entire graphPath of the previous series, though may be hard with
  11376. // splines and with series with different extremes
  11377. for (i = segment.length - 1; i >= 0; i--) {
  11378. // step line?
  11379. if (i < segment.length - 1 && options.step) {
  11380. areaSegmentPath.push(segment[i + 1].plotX, segment[i].yBottom);
  11381. }
  11382. areaSegmentPath.push(segment[i].plotX, segment[i].yBottom);
  11383. }
  11384. } else { // follow zero line back
  11385. areaSegmentPath.push(
  11386. L,
  11387. segment[segment.length - 1].plotX,
  11388. translatedThreshold,
  11389. L,
  11390. segment[0].plotX,
  11391. translatedThreshold
  11392. );
  11393. }
  11394. this.areaPath = this.areaPath.concat(areaSegmentPath);
  11395. return segmentPath;
  11396. },
  11397. /**
  11398. * Draw the graph and the underlying area. This method calls the Series base
  11399. * function and adds the area. The areaPath is calculated in the getSegmentPath
  11400. * method called from Series.prototype.drawGraph.
  11401. */
  11402. drawGraph: function () {
  11403. // Define or reset areaPath
  11404. this.areaPath = [];
  11405. // Call the base method
  11406. Series.prototype.drawGraph.apply(this);
  11407. // Define local variables
  11408. var areaPath = this.areaPath,
  11409. options = this.options,
  11410. area = this.area;
  11411. // Create or update the area
  11412. if (area) { // update
  11413. area.animate({ d: areaPath });
  11414. } else { // create
  11415. this.area = this.chart.renderer.path(areaPath)
  11416. .attr({
  11417. fill: pick(
  11418. options.fillColor,
  11419. Color(this.color).setOpacity(options.fillOpacity || 0.75).get()
  11420. )
  11421. }).add(this.group);
  11422. }
  11423. },
  11424. /**
  11425. * Get the series' symbol in the legend
  11426. *
  11427. * @param {Object} legend The legend object
  11428. * @param {Object} item The series (this) or point
  11429. */
  11430. drawLegendSymbol: function (legend, item) {
  11431. item.legendSymbol = this.chart.renderer.rect(
  11432. 0,
  11433. legend.baseline - 11,
  11434. legend.options.symbolWidth,
  11435. 12,
  11436. 2
  11437. ).attr({
  11438. zIndex: 3
  11439. }).add(item.legendGroup);
  11440. }
  11441. });
  11442. seriesTypes.area = AreaSeries;/**
  11443. * Set the default options for spline
  11444. */
  11445. defaultPlotOptions.spline = merge(defaultSeriesOptions);
  11446. /**
  11447. * SplineSeries object
  11448. */
  11449. var SplineSeries = extendClass(Series, {
  11450. type: 'spline',
  11451. /**
  11452. * Draw the actual graph
  11453. */
  11454. getPointSpline: function (segment, point, i) {
  11455. var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc
  11456. denom = smoothing + 1,
  11457. plotX = point.plotX,
  11458. plotY = point.plotY,
  11459. lastPoint = segment[i - 1],
  11460. nextPoint = segment[i + 1],
  11461. leftContX,
  11462. leftContY,
  11463. rightContX,
  11464. rightContY,
  11465. ret;
  11466. // find control points
  11467. if (i && i < segment.length - 1) {
  11468. var lastX = lastPoint.plotX,
  11469. lastY = lastPoint.plotY,
  11470. nextX = nextPoint.plotX,
  11471. nextY = nextPoint.plotY,
  11472. correction;
  11473. leftContX = (smoothing * plotX + lastX) / denom;
  11474. leftContY = (smoothing * plotY + lastY) / denom;
  11475. rightContX = (smoothing * plotX + nextX) / denom;
  11476. rightContY = (smoothing * plotY + nextY) / denom;
  11477. // have the two control points make a straight line through main point
  11478. correction = ((rightContY - leftContY) * (rightContX - plotX)) /
  11479. (rightContX - leftContX) + plotY - rightContY;
  11480. leftContY += correction;
  11481. rightContY += correction;
  11482. // to prevent false extremes, check that control points are between
  11483. // neighbouring points' y values
  11484. if (leftContY > lastY && leftContY > plotY) {
  11485. leftContY = mathMax(lastY, plotY);
  11486. rightContY = 2 * plotY - leftContY; // mirror of left control point
  11487. } else if (leftContY < lastY && leftContY < plotY) {
  11488. leftContY = mathMin(lastY, plotY);
  11489. rightContY = 2 * plotY - leftContY;
  11490. }
  11491. if (rightContY > nextY && rightContY > plotY) {
  11492. rightContY = mathMax(nextY, plotY);
  11493. leftContY = 2 * plotY - rightContY;
  11494. } else if (rightContY < nextY && rightContY < plotY) {
  11495. rightContY = mathMin(nextY, plotY);
  11496. leftContY = 2 * plotY - rightContY;
  11497. }
  11498. // record for drawing in next point
  11499. point.rightContX = rightContX;
  11500. point.rightContY = rightContY;
  11501. }
  11502. // moveTo or lineTo
  11503. if (!i) {
  11504. ret = [M, plotX, plotY];
  11505. } else { // curve from last point to this
  11506. ret = [
  11507. 'C',
  11508. lastPoint.rightContX || lastPoint.plotX,
  11509. lastPoint.rightContY || lastPoint.plotY,
  11510. leftContX || plotX,
  11511. leftContY || plotY,
  11512. plotX,
  11513. plotY
  11514. ];
  11515. lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
  11516. }
  11517. return ret;
  11518. }
  11519. });
  11520. seriesTypes.spline = SplineSeries;
  11521. /**
  11522. * Set the default options for areaspline
  11523. */
  11524. defaultPlotOptions.areaspline = merge(defaultPlotOptions.area);
  11525. /**
  11526. * AreaSplineSeries object
  11527. */
  11528. var areaProto = AreaSeries.prototype,
  11529. AreaSplineSeries = extendClass(SplineSeries, {
  11530. type: 'areaspline',
  11531. // Mix in methods from the area series
  11532. getSegmentPath: areaProto.getSegmentPath,
  11533. drawGraph: areaProto.drawGraph
  11534. });
  11535. seriesTypes.areaspline = AreaSplineSeries;
  11536. /**
  11537. * Set the default options for column
  11538. */
  11539. defaultPlotOptions.column = merge(defaultSeriesOptions, {
  11540. borderColor: '#FFFFFF',
  11541. borderWidth: 1,
  11542. borderRadius: 0,
  11543. //colorByPoint: undefined,
  11544. groupPadding: 0.2,
  11545. marker: null, // point options are specified in the base options
  11546. pointPadding: 0.1,
  11547. //pointWidth: null,
  11548. minPointLength: 0,
  11549. cropThreshold: 50, // when there are more points, they will not animate out of the chart on xAxis.setExtremes
  11550. pointRange: null, // null means auto, meaning 1 in a categorized axis and least distance between points if not categories
  11551. states: {
  11552. hover: {
  11553. brightness: 0.1,
  11554. shadow: false
  11555. },
  11556. select: {
  11557. color: '#C0C0C0',
  11558. borderColor: '#000000',
  11559. shadow: false
  11560. }
  11561. },
  11562. dataLabels: {
  11563. y: null,
  11564. verticalAlign: null
  11565. },
  11566. threshold: 0
  11567. });
  11568. /**
  11569. * ColumnSeries object
  11570. */
  11571. var ColumnSeries = extendClass(Series, {
  11572. type: 'column',
  11573. tooltipOutsidePlot: true,
  11574. pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
  11575. stroke: 'borderColor',
  11576. 'stroke-width': 'borderWidth',
  11577. fill: 'color',
  11578. r: 'borderRadius'
  11579. },
  11580. init: function () {
  11581. Series.prototype.init.apply(this, arguments);
  11582. var series = this,
  11583. chart = series.chart;
  11584. // if the series is added dynamically, force redraw of other
  11585. // series affected by a new column
  11586. if (chart.hasRendered) {
  11587. each(chart.series, function (otherSeries) {
  11588. if (otherSeries.type === series.type) {
  11589. otherSeries.isDirty = true;
  11590. }
  11591. });
  11592. }
  11593. },
  11594. /**
  11595. * Translate each point to the plot area coordinate system and find shape positions
  11596. */
  11597. translate: function () {
  11598. var series = this,
  11599. chart = series.chart,
  11600. options = series.options,
  11601. stacking = options.stacking,
  11602. borderWidth = options.borderWidth,
  11603. columnCount = 0,
  11604. xAxis = series.xAxis,
  11605. reversedXAxis = xAxis.reversed,
  11606. stackGroups = {},
  11607. stackKey,
  11608. columnIndex;
  11609. Series.prototype.translate.apply(series);
  11610. // Get the total number of column type series.
  11611. // This is called on every series. Consider moving this logic to a
  11612. // chart.orderStacks() function and call it on init, addSeries and removeSeries
  11613. each(chart.series, function (otherSeries) {
  11614. if (otherSeries.type === series.type && otherSeries.visible &&
  11615. series.options.group === otherSeries.options.group) { // used in Stock charts navigator series
  11616. if (otherSeries.options.stacking) {
  11617. stackKey = otherSeries.stackKey;
  11618. if (stackGroups[stackKey] === UNDEFINED) {
  11619. stackGroups[stackKey] = columnCount++;
  11620. }
  11621. columnIndex = stackGroups[stackKey];
  11622. } else {
  11623. columnIndex = columnCount++;
  11624. }
  11625. otherSeries.columnIndex = columnIndex;
  11626. }
  11627. });
  11628. // calculate the width and position of each column based on
  11629. // the number of column series in the plot, the groupPadding
  11630. // and the pointPadding options
  11631. var points = series.points,
  11632. categoryWidth = mathAbs(xAxis.transA) * (xAxis.ordinalSlope || options.pointRange || xAxis.closestPointRange || 1),
  11633. groupPadding = categoryWidth * options.groupPadding,
  11634. groupWidth = categoryWidth - 2 * groupPadding,
  11635. pointOffsetWidth = groupWidth / columnCount,
  11636. optionPointWidth = options.pointWidth,
  11637. pointPadding = defined(optionPointWidth) ? (pointOffsetWidth - optionPointWidth) / 2 :
  11638. pointOffsetWidth * options.pointPadding,
  11639. pointWidth = pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), // exact point width, used in polar charts
  11640. barW = mathCeil(mathMax(pointWidth, 1 + 2 * borderWidth)), // rounded and postprocessed for border width
  11641. colIndex = (reversedXAxis ? columnCount -
  11642. series.columnIndex : series.columnIndex) || 0,
  11643. pointXOffset = pointPadding + (groupPadding + colIndex *
  11644. pointOffsetWidth - (categoryWidth / 2)) *
  11645. (reversedXAxis ? -1 : 1),
  11646. threshold = options.threshold,
  11647. translatedThreshold = series.yAxis.getThreshold(threshold),
  11648. minPointLength = pick(options.minPointLength, 5);
  11649. // record the new values
  11650. each(points, function (point) {
  11651. var plotY = point.plotY,
  11652. yBottom = pick(point.yBottom, translatedThreshold),
  11653. barX = point.plotX + pointXOffset,
  11654. barY = mathCeil(mathMin(plotY, yBottom)),
  11655. barH = mathCeil(mathMax(plotY, yBottom) - barY),
  11656. stack = series.yAxis.stacks[(point.y < 0 ? '-' : '') + series.stackKey],
  11657. shapeArgs;
  11658. // Record the offset'ed position and width of the bar to be able to align the stacking total correctly
  11659. if (stacking && series.visible && stack && stack[point.x]) {
  11660. stack[point.x].setOffset(pointXOffset, barW);
  11661. }
  11662. // handle options.minPointLength
  11663. if (mathAbs(barH) < minPointLength) {
  11664. if (minPointLength) {
  11665. barH = minPointLength;
  11666. barY =
  11667. mathAbs(barY - translatedThreshold) > minPointLength ? // stacked
  11668. yBottom - minPointLength : // keep position
  11669. translatedThreshold - (plotY <= translatedThreshold ? minPointLength : 0);
  11670. }
  11671. }
  11672. extend(point, {
  11673. barX: barX,
  11674. barY: barY,
  11675. barW: barW,
  11676. barH: barH,
  11677. pointWidth: pointWidth
  11678. });
  11679. // create shape type and shape args that are reused in drawPoints and drawTracker
  11680. point.shapeType = 'rect';
  11681. point.shapeArgs = shapeArgs = chart.renderer.Element.prototype.crisp.call(0, borderWidth, barX, barY, barW, barH);
  11682. if (borderWidth % 2) { // correct for shorting in crisp method, visible in stacked columns with 1px border
  11683. shapeArgs.y -= 1;
  11684. shapeArgs.height += 1;
  11685. }
  11686. // make small columns responsive to mouse
  11687. point.trackerArgs = mathAbs(barH) < 3 && merge(point.shapeArgs, {
  11688. height: 6,
  11689. y: barY - 3
  11690. });
  11691. });
  11692. },
  11693. getSymbol: function () {
  11694. },
  11695. /**
  11696. * Use a solid rectangle like the area series types
  11697. */
  11698. drawLegendSymbol: AreaSeries.prototype.drawLegendSymbol,
  11699. /**
  11700. * Columns have no graph
  11701. */
  11702. drawGraph: function () {},
  11703. /**
  11704. * Draw the columns. For bars, the series.group is rotated, so the same coordinates
  11705. * apply for columns and bars. This method is inherited by scatter series.
  11706. *
  11707. */
  11708. drawPoints: function () {
  11709. var series = this,
  11710. options = series.options,
  11711. renderer = series.chart.renderer,
  11712. graphic,
  11713. shapeArgs;
  11714. // draw the columns
  11715. each(series.points, function (point) {
  11716. var plotY = point.plotY;
  11717. if (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {
  11718. graphic = point.graphic;
  11719. shapeArgs = point.shapeArgs;
  11720. if (graphic) { // update
  11721. stop(graphic);
  11722. graphic.animate(merge(shapeArgs));
  11723. } else {
  11724. point.graphic = graphic = renderer[point.shapeType](shapeArgs)
  11725. .attr(point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE])
  11726. .add(series.group)
  11727. .shadow(options.shadow, null, options.stacking && !options.borderRadius);
  11728. }
  11729. }
  11730. });
  11731. },
  11732. /**
  11733. * Draw the individual tracker elements.
  11734. * This method is inherited by scatter and pie charts too.
  11735. */
  11736. drawTracker: function () {
  11737. var series = this,
  11738. chart = series.chart,
  11739. renderer = chart.renderer,
  11740. shapeArgs,
  11741. tracker,
  11742. trackerLabel = +new Date(),
  11743. options = series.options,
  11744. cursor = options.cursor,
  11745. css = cursor && { cursor: cursor },
  11746. trackerGroup = series.drawTrackerGroup(),
  11747. rel,
  11748. plotY,
  11749. validPlotY;
  11750. each(series.points, function (point) {
  11751. tracker = point.tracker;
  11752. shapeArgs = point.trackerArgs || point.shapeArgs;
  11753. plotY = point.plotY;
  11754. validPlotY = !series.isCartesian || (plotY !== UNDEFINED && !isNaN(plotY));
  11755. delete shapeArgs.strokeWidth;
  11756. if (point.y !== null && validPlotY) {
  11757. if (tracker) {// update
  11758. tracker.attr(shapeArgs);
  11759. } else {
  11760. point.tracker =
  11761. renderer[point.shapeType](shapeArgs)
  11762. .attr({
  11763. isTracker: trackerLabel,
  11764. fill: TRACKER_FILL,
  11765. visibility: series.visible ? VISIBLE : HIDDEN
  11766. })
  11767. .on(hasTouch ? 'touchstart' : 'mouseover', function (event) {
  11768. rel = event.relatedTarget || event.fromElement;
  11769. if (chart.hoverSeries !== series && attr(rel, 'isTracker') !== trackerLabel) {
  11770. series.onMouseOver();
  11771. }
  11772. point.onMouseOver();
  11773. })
  11774. .on('mouseout', function (event) {
  11775. if (!options.stickyTracking) {
  11776. rel = event.relatedTarget || event.toElement;
  11777. if (attr(rel, 'isTracker') !== trackerLabel) {
  11778. series.onMouseOut();
  11779. }
  11780. }
  11781. })
  11782. .css(css)
  11783. .add(point.group || trackerGroup); // pies have point group - see issue #118
  11784. }
  11785. }
  11786. });
  11787. },
  11788. /**
  11789. * Animate the column heights one by one from zero
  11790. * @param {Boolean} init Whether to initialize the animation or run it
  11791. */
  11792. animate: function (init) {
  11793. var series = this,
  11794. points = series.points,
  11795. options = series.options;
  11796. if (!init) { // run the animation
  11797. /*
  11798. * Note: Ideally the animation should be initialized by calling
  11799. * series.group.hide(), and then calling series.group.show()
  11800. * after the animation was started. But this rendered the shadows
  11801. * invisible in IE8 standards mode. If the columns flicker on large
  11802. * datasets, this is the cause.
  11803. */
  11804. each(points, function (point) {
  11805. var graphic = point.graphic,
  11806. shapeArgs = point.shapeArgs,
  11807. yAxis = series.yAxis,
  11808. threshold = options.threshold;
  11809. if (graphic) {
  11810. // start values
  11811. graphic.attr({
  11812. height: 0,
  11813. y: defined(threshold) ?
  11814. yAxis.getThreshold(threshold) :
  11815. yAxis.translate(yAxis.getExtremes().min, 0, 1, 0, 1)
  11816. });
  11817. // animate
  11818. graphic.animate({
  11819. height: shapeArgs.height,
  11820. y: shapeArgs.y
  11821. }, options.animation);
  11822. }
  11823. });
  11824. // delete this function to allow it only once
  11825. series.animate = null;
  11826. }
  11827. },
  11828. /**
  11829. * Remove this series from the chart
  11830. */
  11831. remove: function () {
  11832. var series = this,
  11833. chart = series.chart;
  11834. // column and bar series affects other series of the same type
  11835. // as they are either stacked or grouped
  11836. if (chart.hasRendered) {
  11837. each(chart.series, function (otherSeries) {
  11838. if (otherSeries.type === series.type) {
  11839. otherSeries.isDirty = true;
  11840. }
  11841. });
  11842. }
  11843. Series.prototype.remove.apply(series, arguments);
  11844. }
  11845. });
  11846. seriesTypes.column = ColumnSeries;
  11847. /**
  11848. * Set the default options for bar
  11849. */
  11850. defaultPlotOptions.bar = merge(defaultPlotOptions.column, {
  11851. dataLabels: {
  11852. align: 'left',
  11853. x: 5,
  11854. y: null,
  11855. verticalAlign: 'middle'
  11856. }
  11857. });
  11858. /**
  11859. * The Bar series class
  11860. */
  11861. var BarSeries = extendClass(ColumnSeries, {
  11862. type: 'bar',
  11863. inverted: true
  11864. });
  11865. seriesTypes.bar = BarSeries;
  11866. /**
  11867. * Set the default options for scatter
  11868. */
  11869. defaultPlotOptions.scatter = merge(defaultSeriesOptions, {
  11870. lineWidth: 0,
  11871. states: {
  11872. hover: {
  11873. lineWidth: 0
  11874. }
  11875. },
  11876. tooltip: {
  11877. headerFormat: '<span style="font-size: 10px; color:{series.color}">{series.name}</span><br/>',
  11878. pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>'
  11879. }
  11880. });
  11881. /**
  11882. * The scatter series class
  11883. */
  11884. var ScatterSeries = extendClass(Series, {
  11885. type: 'scatter',
  11886. sorted: false,
  11887. /**
  11888. * Extend the base Series' translate method by adding shape type and
  11889. * arguments for the point trackers
  11890. */
  11891. translate: function () {
  11892. var series = this;
  11893. Series.prototype.translate.apply(series);
  11894. each(series.points, function (point) {
  11895. point.shapeType = 'circle';
  11896. point.shapeArgs = {
  11897. x: point.plotX,
  11898. y: point.plotY,
  11899. r: series.chart.options.tooltip.snap
  11900. };
  11901. });
  11902. },
  11903. /**
  11904. * Add tracking event listener to the series group, so the point graphics
  11905. * themselves act as trackers
  11906. */
  11907. drawTracker: function () {
  11908. var series = this,
  11909. cursor = series.options.cursor,
  11910. css = cursor && { cursor: cursor },
  11911. points = series.points,
  11912. i = points.length,
  11913. graphic;
  11914. // Set an expando property for the point index, used below
  11915. while (i--) {
  11916. graphic = points[i].graphic;
  11917. if (graphic) { // doesn't exist for null points
  11918. graphic.element._i = i;
  11919. }
  11920. }
  11921. // Add the event listeners, we need to do this only once
  11922. if (!series._hasTracking) {
  11923. series.group
  11924. .attr({
  11925. isTracker: true
  11926. })
  11927. .on(hasTouch ? 'touchstart' : 'mouseover', function (e) {
  11928. series.onMouseOver();
  11929. if (e.target._i !== UNDEFINED) { // undefined on graph in scatterchart
  11930. points[e.target._i].onMouseOver();
  11931. }
  11932. })
  11933. .on('mouseout', function () {
  11934. if (!series.options.stickyTracking) {
  11935. series.onMouseOut();
  11936. }
  11937. })
  11938. .css(css);
  11939. } else {
  11940. series._hasTracking = true;
  11941. }
  11942. }
  11943. });
  11944. seriesTypes.scatter = ScatterSeries;
  11945. /**
  11946. * Set the default options for pie
  11947. */
  11948. defaultPlotOptions.pie = merge(defaultSeriesOptions, {
  11949. borderColor: '#FFFFFF',
  11950. borderWidth: 1,
  11951. center: ['50%', '50%'],
  11952. colorByPoint: true, // always true for pies
  11953. dataLabels: {
  11954. // align: null,
  11955. // connectorWidth: 1,
  11956. // connectorColor: point.color,
  11957. // connectorPadding: 5,
  11958. distance: 30,
  11959. enabled: true,
  11960. formatter: function () {
  11961. return this.point.name;
  11962. },
  11963. // softConnector: true,
  11964. y: 5
  11965. },
  11966. //innerSize: 0,
  11967. legendType: 'point',
  11968. marker: null, // point options are specified in the base options
  11969. size: '75%',
  11970. showInLegend: false,
  11971. slicedOffset: 10,
  11972. states: {
  11973. hover: {
  11974. brightness: 0.1,
  11975. shadow: false
  11976. }
  11977. }
  11978. });
  11979. /**
  11980. * Extended point object for pies
  11981. */
  11982. var PiePoint = extendClass(Point, {
  11983. /**
  11984. * Initiate the pie slice
  11985. */
  11986. init: function () {
  11987. Point.prototype.init.apply(this, arguments);
  11988. var point = this,
  11989. toggleSlice;
  11990. //visible: options.visible !== false,
  11991. extend(point, {
  11992. visible: point.visible !== false,
  11993. name: pick(point.name, 'Slice')
  11994. });
  11995. // add event listener for select
  11996. toggleSlice = function () {
  11997. point.slice();
  11998. };
  11999. addEvent(point, 'select', toggleSlice);
  12000. addEvent(point, 'unselect', toggleSlice);
  12001. return point;
  12002. },
  12003. /**
  12004. * Toggle the visibility of the pie slice
  12005. * @param {Boolean} vis Whether to show the slice or not. If undefined, the
  12006. * visibility is toggled
  12007. */
  12008. setVisible: function (vis) {
  12009. var point = this,
  12010. chart = point.series.chart,
  12011. tracker = point.tracker,
  12012. dataLabel = point.dataLabel,
  12013. connector = point.connector,
  12014. shadowGroup = point.shadowGroup,
  12015. method;
  12016. // if called without an argument, toggle visibility
  12017. point.visible = vis = vis === UNDEFINED ? !point.visible : vis;
  12018. method = vis ? 'show' : 'hide';
  12019. point.group[method]();
  12020. if (tracker) {
  12021. tracker[method]();
  12022. }
  12023. if (dataLabel) {
  12024. dataLabel[method]();
  12025. }
  12026. if (connector) {
  12027. connector[method]();
  12028. }
  12029. if (shadowGroup) {
  12030. shadowGroup[method]();
  12031. }
  12032. if (point.legendItem) {
  12033. chart.legend.colorizeItem(point, vis);
  12034. }
  12035. },
  12036. /**
  12037. * Set or toggle whether the slice is cut out from the pie
  12038. * @param {Boolean} sliced When undefined, the slice state is toggled
  12039. * @param {Boolean} redraw Whether to redraw the chart. True by default.
  12040. */
  12041. slice: function (sliced, redraw, animation) {
  12042. var point = this,
  12043. series = point.series,
  12044. chart = series.chart,
  12045. slicedTranslation = point.slicedTranslation,
  12046. translation;
  12047. setAnimation(animation, chart);
  12048. // redraw is true by default
  12049. redraw = pick(redraw, true);
  12050. // if called without an argument, toggle
  12051. sliced = point.sliced = defined(sliced) ? sliced : !point.sliced;
  12052. translation = {
  12053. translateX: (sliced ? slicedTranslation[0] : chart.plotLeft),
  12054. translateY: (sliced ? slicedTranslation[1] : chart.plotTop)
  12055. };
  12056. point.group.animate(translation);
  12057. if (point.shadowGroup) {
  12058. point.shadowGroup.animate(translation);
  12059. }
  12060. }
  12061. });
  12062. /**
  12063. * The Pie series class
  12064. */
  12065. var PieSeries = {
  12066. type: 'pie',
  12067. isCartesian: false,
  12068. pointClass: PiePoint,
  12069. pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
  12070. stroke: 'borderColor',
  12071. 'stroke-width': 'borderWidth',
  12072. fill: 'color'
  12073. },
  12074. /**
  12075. * Pies have one color each point
  12076. */
  12077. getColor: function () {
  12078. // record first color for use in setData
  12079. this.initialColor = this.chart.counters.color;
  12080. },
  12081. /**
  12082. * Animate the pies in
  12083. */
  12084. animate: function () {
  12085. var series = this,
  12086. points = series.points;
  12087. each(points, function (point) {
  12088. var graphic = point.graphic,
  12089. args = point.shapeArgs,
  12090. up = -mathPI / 2;
  12091. if (graphic) {
  12092. // start values
  12093. graphic.attr({
  12094. r: 0,
  12095. start: up,
  12096. end: up
  12097. });
  12098. // animate
  12099. graphic.animate({
  12100. r: args.r,
  12101. start: args.start,
  12102. end: args.end
  12103. }, series.options.animation);
  12104. }
  12105. });
  12106. // delete this function to allow it only once
  12107. series.animate = null;
  12108. },
  12109. /**
  12110. * Extend the basic setData method by running processData and generatePoints immediately,
  12111. * in order to access the points from the legend.
  12112. */
  12113. setData: function (data, redraw) {
  12114. Series.prototype.setData.call(this, data, false);
  12115. this.processData();
  12116. this.generatePoints();
  12117. if (pick(redraw, true)) {
  12118. this.chart.redraw();
  12119. }
  12120. },
  12121. /**
  12122. * Get the center of the pie based on the size and center options relative to the
  12123. * plot area. Borrowed by the polar and gauge series types.
  12124. */
  12125. getCenter: function () {
  12126. var options = this.options,
  12127. chart = this.chart,
  12128. plotWidth = chart.plotWidth,
  12129. plotHeight = chart.plotHeight,
  12130. positions = options.center.concat([options.size, options.innerSize || 0]),
  12131. smallestSize = mathMin(plotWidth, plotHeight),
  12132. isPercent;
  12133. return map(positions, function (length, i) {
  12134. isPercent = /%$/.test(length);
  12135. return isPercent ?
  12136. // i == 0: centerX, relative to width
  12137. // i == 1: centerY, relative to height
  12138. // i == 2: size, relative to smallestSize
  12139. // i == 4: innerSize, relative to smallestSize
  12140. [plotWidth, plotHeight, smallestSize, smallestSize][i] *
  12141. pInt(length) / 100 :
  12142. length;
  12143. });
  12144. },
  12145. /**
  12146. * Do translation for pie slices
  12147. */
  12148. translate: function () {
  12149. this.generatePoints();
  12150. var total = 0,
  12151. series = this,
  12152. cumulative = -0.25, // start at top
  12153. precision = 1000, // issue #172
  12154. options = series.options,
  12155. slicedOffset = options.slicedOffset,
  12156. connectorOffset = slicedOffset + options.borderWidth,
  12157. positions,
  12158. chart = series.chart,
  12159. start,
  12160. end,
  12161. angle,
  12162. points = series.points,
  12163. circ = 2 * mathPI,
  12164. fraction,
  12165. radiusX, // the x component of the radius vector for a given point
  12166. radiusY,
  12167. labelDistance = options.dataLabels.distance;
  12168. // get positions - either an integer or a percentage string must be given
  12169. series.center = positions = series.getCenter();
  12170. // utility for getting the x value from a given y, used for anticollision logic in data labels
  12171. series.getX = function (y, left) {
  12172. angle = math.asin((y - positions[1]) / (positions[2] / 2 + labelDistance));
  12173. return positions[0] +
  12174. (left ? -1 : 1) *
  12175. (mathCos(angle) * (positions[2] / 2 + labelDistance));
  12176. };
  12177. // get the total sum
  12178. each(points, function (point) {
  12179. total += point.y;
  12180. });
  12181. each(points, function (point) {
  12182. // set start and end angle
  12183. fraction = total ? point.y / total : 0;
  12184. start = mathRound(cumulative * circ * precision) / precision;
  12185. cumulative += fraction;
  12186. end = mathRound(cumulative * circ * precision) / precision;
  12187. // set the shape
  12188. point.shapeType = 'arc';
  12189. point.shapeArgs = {
  12190. x: positions[0],
  12191. y: positions[1],
  12192. r: positions[2] / 2,
  12193. innerR: positions[3] / 2,
  12194. start: start,
  12195. end: end
  12196. };
  12197. // center for the sliced out slice
  12198. angle = (end + start) / 2;
  12199. point.slicedTranslation = map([
  12200. mathCos(angle) * slicedOffset + chart.plotLeft,
  12201. mathSin(angle) * slicedOffset + chart.plotTop
  12202. ], mathRound);
  12203. // set the anchor point for tooltips
  12204. radiusX = mathCos(angle) * positions[2] / 2;
  12205. radiusY = mathSin(angle) * positions[2] / 2;
  12206. point.tooltipPos = [
  12207. positions[0] + radiusX * 0.7,
  12208. positions[1] + radiusY * 0.7
  12209. ];
  12210. // set the anchor point for data labels
  12211. point.labelPos = [
  12212. positions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector
  12213. positions[1] + radiusY + mathSin(angle) * labelDistance, // a/a
  12214. positions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie
  12215. positions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a
  12216. positions[0] + radiusX, // landing point for connector
  12217. positions[1] + radiusY, // a/a
  12218. labelDistance < 0 ? // alignment
  12219. 'center' :
  12220. angle < circ / 4 ? 'left' : 'right', // alignment
  12221. angle // center angle
  12222. ];
  12223. // API properties
  12224. point.percentage = fraction * 100;
  12225. point.total = total;
  12226. });
  12227. this.setTooltipPoints();
  12228. },
  12229. /**
  12230. * Render the slices
  12231. */
  12232. render: function () {
  12233. var series = this;
  12234. // cache attributes for shapes
  12235. series.getAttribs();
  12236. this.drawPoints();
  12237. // draw the mouse tracking area
  12238. if (series.options.enableMouseTracking !== false) {
  12239. series.drawTracker();
  12240. }
  12241. this.drawDataLabels();
  12242. if (series.options.animation && series.animate) {
  12243. series.animate();
  12244. }
  12245. // (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
  12246. series.isDirty = false; // means data is in accordance with what you see
  12247. },
  12248. /**
  12249. * Draw the data points
  12250. */
  12251. drawPoints: function () {
  12252. var series = this,
  12253. chart = series.chart,
  12254. renderer = chart.renderer,
  12255. groupTranslation,
  12256. //center,
  12257. graphic,
  12258. group,
  12259. shadow = series.options.shadow,
  12260. shadowGroup,
  12261. shapeArgs;
  12262. // draw the slices
  12263. each(series.points, function (point) {
  12264. graphic = point.graphic;
  12265. shapeArgs = point.shapeArgs;
  12266. group = point.group;
  12267. shadowGroup = point.shadowGroup;
  12268. // put the shadow behind all points
  12269. if (shadow && !shadowGroup) {
  12270. shadowGroup = point.shadowGroup = renderer.g('shadow')
  12271. .attr({ zIndex: 4 })
  12272. .add();
  12273. }
  12274. // create the group the first time
  12275. if (!group) {
  12276. group = point.group = renderer.g('point')
  12277. .attr({ zIndex: 5 })
  12278. .add();
  12279. }
  12280. // if the point is sliced, use special translation, else use plot area traslation
  12281. groupTranslation = point.sliced ? point.slicedTranslation : [chart.plotLeft, chart.plotTop];
  12282. group.translate(groupTranslation[0], groupTranslation[1]);
  12283. if (shadowGroup) {
  12284. shadowGroup.translate(groupTranslation[0], groupTranslation[1]);
  12285. }
  12286. // draw the slice
  12287. if (graphic) {
  12288. graphic.animate(shapeArgs);
  12289. } else {
  12290. point.graphic = graphic = renderer.arc(shapeArgs)
  12291. .setRadialReference(series.center)
  12292. .attr(extend(
  12293. point.pointAttr[NORMAL_STATE],
  12294. { 'stroke-linejoin': 'round' }
  12295. ))
  12296. .add(point.group)
  12297. .shadow(shadow, shadowGroup);
  12298. }
  12299. // detect point specific visibility
  12300. if (point.visible === false) {
  12301. point.setVisible(false);
  12302. }
  12303. });
  12304. },
  12305. /**
  12306. * Override the base drawDataLabels method by pie specific functionality
  12307. */
  12308. drawDataLabels: function () {
  12309. var series = this,
  12310. data = series.data,
  12311. point,
  12312. chart = series.chart,
  12313. options = series.options.dataLabels,
  12314. connectorPadding = pick(options.connectorPadding, 10),
  12315. connectorWidth = pick(options.connectorWidth, 1),
  12316. connector,
  12317. connectorPath,
  12318. softConnector = pick(options.softConnector, true),
  12319. distanceOption = options.distance,
  12320. seriesCenter = series.center,
  12321. radius = seriesCenter[2] / 2,
  12322. centerY = seriesCenter[1],
  12323. outside = distanceOption > 0,
  12324. dataLabel,
  12325. labelPos,
  12326. labelHeight,
  12327. halves = [// divide the points into right and left halves for anti collision
  12328. [], // right
  12329. [] // left
  12330. ],
  12331. x,
  12332. y,
  12333. visibility,
  12334. rankArr,
  12335. sort,
  12336. i = 2,
  12337. j;
  12338. // get out if not enabled
  12339. if (!options.enabled) {
  12340. return;
  12341. }
  12342. // run parent method
  12343. Series.prototype.drawDataLabels.apply(series);
  12344. // arrange points for detection collision
  12345. each(data, function (point) {
  12346. if (point.dataLabel) { // it may have been cancelled in the base method (#407)
  12347. halves[
  12348. point.labelPos[7] < mathPI / 2 ? 0 : 1
  12349. ].push(point);
  12350. }
  12351. });
  12352. halves[1].reverse();
  12353. // define the sorting algorithm
  12354. sort = function (a, b) {
  12355. return b.y - a.y;
  12356. };
  12357. // assume equal label heights
  12358. labelHeight = halves[0][0] && halves[0][0].dataLabel && (halves[0][0].dataLabel.getBBox().height || 21); // 21 is for #968
  12359. /* Loop over the points in each half, starting from the top and bottom
  12360. * of the pie to detect overlapping labels.
  12361. */
  12362. while (i--) {
  12363. var slots = [],
  12364. slotsLength,
  12365. usedSlots = [],
  12366. points = halves[i],
  12367. pos,
  12368. length = points.length,
  12369. slotIndex;
  12370. // Only do anti-collision when we are outside the pie and have connectors (#856)
  12371. if (distanceOption > 0) {
  12372. // build the slots
  12373. for (pos = centerY - radius - distanceOption; pos <= centerY + radius + distanceOption; pos += labelHeight) {
  12374. slots.push(pos);
  12375. // visualize the slot
  12376. /*
  12377. var slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0),
  12378. slotY = pos + chart.plotTop;
  12379. if (!isNaN(slotX)) {
  12380. chart.renderer.rect(slotX, slotY - 7, 100, labelHeight)
  12381. .attr({
  12382. 'stroke-width': 1,
  12383. stroke: 'silver'
  12384. })
  12385. .add();
  12386. chart.renderer.text('Slot '+ (slots.length - 1), slotX, slotY + 4)
  12387. .attr({
  12388. fill: 'silver'
  12389. }).add();
  12390. }
  12391. // */
  12392. }
  12393. slotsLength = slots.length;
  12394. // if there are more values than available slots, remove lowest values
  12395. if (length > slotsLength) {
  12396. // create an array for sorting and ranking the points within each quarter
  12397. rankArr = [].concat(points);
  12398. rankArr.sort(sort);
  12399. j = length;
  12400. while (j--) {
  12401. rankArr[j].rank = j;
  12402. }
  12403. j = length;
  12404. while (j--) {
  12405. if (points[j].rank >= slotsLength) {
  12406. points.splice(j, 1);
  12407. }
  12408. }
  12409. length = points.length;
  12410. }
  12411. // The label goes to the nearest open slot, but not closer to the edge than
  12412. // the label's index.
  12413. for (j = 0; j < length; j++) {
  12414. point = points[j];
  12415. labelPos = point.labelPos;
  12416. var closest = 9999,
  12417. distance,
  12418. slotI;
  12419. // find the closest slot index
  12420. for (slotI = 0; slotI < slotsLength; slotI++) {
  12421. distance = mathAbs(slots[slotI] - labelPos[1]);
  12422. if (distance < closest) {
  12423. closest = distance;
  12424. slotIndex = slotI;
  12425. }
  12426. }
  12427. // if that slot index is closer to the edges of the slots, move it
  12428. // to the closest appropriate slot
  12429. if (slotIndex < j && slots[j] !== null) { // cluster at the top
  12430. slotIndex = j;
  12431. } else if (slotsLength < length - j + slotIndex && slots[j] !== null) { // cluster at the bottom
  12432. slotIndex = slotsLength - length + j;
  12433. while (slots[slotIndex] === null) { // make sure it is not taken
  12434. slotIndex++;
  12435. }
  12436. } else {
  12437. // Slot is taken, find next free slot below. In the next run, the next slice will find the
  12438. // slot above these, because it is the closest one
  12439. while (slots[slotIndex] === null) { // make sure it is not taken
  12440. slotIndex++;
  12441. }
  12442. }
  12443. usedSlots.push({ i: slotIndex, y: slots[slotIndex] });
  12444. slots[slotIndex] = null; // mark as taken
  12445. }
  12446. // sort them in order to fill in from the top
  12447. usedSlots.sort(sort);
  12448. }
  12449. // now the used slots are sorted, fill them up sequentially
  12450. for (j = 0; j < length; j++) {
  12451. var slot, naturalY;
  12452. point = points[j];
  12453. labelPos = point.labelPos;
  12454. dataLabel = point.dataLabel;
  12455. visibility = point.visible === false ? HIDDEN : VISIBLE;
  12456. naturalY = labelPos[1];
  12457. if (distanceOption > 0) {
  12458. slot = usedSlots.pop();
  12459. slotIndex = slot.i;
  12460. // if the slot next to currrent slot is free, the y value is allowed
  12461. // to fall back to the natural position
  12462. y = slot.y;
  12463. if ((naturalY > y && slots[slotIndex + 1] !== null) ||
  12464. (naturalY < y && slots[slotIndex - 1] !== null)) {
  12465. y = naturalY;
  12466. }
  12467. } else {
  12468. y = naturalY;
  12469. }
  12470. // get the x - use the natural x position for first and last slot, to prevent the top
  12471. // and botton slice connectors from touching each other on either side
  12472. x = options.justify ?
  12473. seriesCenter[0] + (i ? -1 : 1) * (radius + distanceOption) :
  12474. series.getX(slotIndex === 0 || slotIndex === slots.length - 1 ? naturalY : y, i);
  12475. // move or place the data label
  12476. dataLabel
  12477. .attr({
  12478. visibility: visibility,
  12479. align: labelPos[6]
  12480. })[dataLabel.moved ? 'animate' : 'attr']({
  12481. x: x + options.x +
  12482. ({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0),
  12483. y: y + options.y
  12484. });
  12485. dataLabel.moved = true;
  12486. // draw the connector
  12487. if (outside && connectorWidth) {
  12488. connector = point.connector;
  12489. connectorPath = softConnector ? [
  12490. M,
  12491. x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
  12492. 'C',
  12493. x, y, // first break, next to the label
  12494. 2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5],
  12495. labelPos[2], labelPos[3], // second break
  12496. L,
  12497. labelPos[4], labelPos[5] // base
  12498. ] : [
  12499. M,
  12500. x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
  12501. L,
  12502. labelPos[2], labelPos[3], // second break
  12503. L,
  12504. labelPos[4], labelPos[5] // base
  12505. ];
  12506. if (connector) {
  12507. connector.animate({ d: connectorPath });
  12508. connector.attr('visibility', visibility);
  12509. } else {
  12510. point.connector = connector = series.chart.renderer.path(connectorPath).attr({
  12511. 'stroke-width': connectorWidth,
  12512. stroke: options.connectorColor || point.color || '#606060',
  12513. visibility: visibility,
  12514. zIndex: 3
  12515. })
  12516. .translate(chart.plotLeft, chart.plotTop)
  12517. .add();
  12518. }
  12519. }
  12520. }
  12521. }
  12522. },
  12523. /**
  12524. * Draw point specific tracker objects. Inherit directly from column series.
  12525. */
  12526. drawTracker: ColumnSeries.prototype.drawTracker,
  12527. /**
  12528. * Use a simple symbol from column prototype
  12529. */
  12530. drawLegendSymbol: AreaSeries.prototype.drawLegendSymbol,
  12531. /**
  12532. * Pies don't have point marker symbols
  12533. */
  12534. getSymbol: function () {}
  12535. };
  12536. PieSeries = extendClass(Series, PieSeries);
  12537. seriesTypes.pie = PieSeries;
  12538. // global variables
  12539. extend(Highcharts, {
  12540. // Constructors
  12541. Axis: Axis,
  12542. CanVGRenderer: CanVGRenderer,
  12543. Chart: Chart,
  12544. Color: Color,
  12545. Legend: Legend,
  12546. Point: Point,
  12547. Tick: Tick,
  12548. Tooltip: Tooltip,
  12549. Renderer: Renderer,
  12550. Series: Series,
  12551. SVGRenderer: SVGRenderer,
  12552. VMLRenderer: VMLRenderer,
  12553. // Various
  12554. dateFormat: dateFormat,
  12555. pathAnim: pathAnim,
  12556. getOptions: getOptions,
  12557. hasBidiBug: hasBidiBug,
  12558. numberFormat: numberFormat,
  12559. seriesTypes: seriesTypes,
  12560. setOptions: setOptions,
  12561. addEvent: addEvent,
  12562. removeEvent: removeEvent,
  12563. createElement: createElement,
  12564. discardElement: discardElement,
  12565. css: css,
  12566. each: each,
  12567. extend: extend,
  12568. map: map,
  12569. merge: merge,
  12570. pick: pick,
  12571. splat: splat,
  12572. extendClass: extendClass,
  12573. pInt: pInt,
  12574. product: 'Highcharts',
  12575. version: '2.2.5'
  12576. });
  12577. }());