charts-debug.js 1.3 MB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737207382073920740207412074220743207442074520746207472074820749207502075120752207532075420755207562075720758207592076020761207622076320764207652076620767207682076920770207712077220773207742077520776207772077820779207802078120782207832078420785207862078720788207892079020791207922079320794207952079620797207982079920800208012080220803208042080520806208072080820809208102081120812208132081420815208162081720818208192082020821208222082320824208252082620827208282082920830208312083220833208342083520836208372083820839208402084120842208432084420845208462084720848208492085020851208522085320854208552085620857208582085920860208612086220863208642086520866208672086820869208702087120872208732087420875208762087720878208792088020881208822088320884208852088620887208882088920890208912089220893208942089520896208972089820899209002090120902209032090420905209062090720908209092091020911209122091320914209152091620917209182091920920209212092220923209242092520926209272092820929209302093120932209332093420935209362093720938209392094020941209422094320944209452094620947209482094920950209512095220953209542095520956209572095820959209602096120962209632096420965209662096720968209692097020971209722097320974209752097620977209782097920980209812098220983209842098520986209872098820989209902099120992209932099420995209962099720998209992100021001210022100321004210052100621007210082100921010210112101221013210142101521016210172101821019210202102121022210232102421025210262102721028210292103021031210322103321034210352103621037210382103921040210412104221043210442104521046210472104821049210502105121052210532105421055210562105721058210592106021061210622106321064210652106621067210682106921070210712107221073210742107521076210772107821079210802108121082210832108421085210862108721088210892109021091210922109321094210952109621097210982109921100211012110221103211042110521106211072110821109211102111121112211132111421115211162111721118211192112021121211222112321124211252112621127211282112921130211312113221133211342113521136211372113821139211402114121142211432114421145211462114721148211492115021151211522115321154211552115621157211582115921160211612116221163211642116521166211672116821169211702117121172211732117421175211762117721178211792118021181211822118321184211852118621187211882118921190211912119221193211942119521196211972119821199212002120121202212032120421205212062120721208212092121021211212122121321214212152121621217212182121921220212212122221223212242122521226212272122821229212302123121232212332123421235212362123721238212392124021241212422124321244212452124621247212482124921250212512125221253212542125521256212572125821259212602126121262212632126421265212662126721268212692127021271212722127321274212752127621277212782127921280212812128221283212842128521286212872128821289212902129121292212932129421295212962129721298212992130021301213022130321304213052130621307213082130921310213112131221313213142131521316213172131821319213202132121322213232132421325213262132721328213292133021331213322133321334213352133621337213382133921340213412134221343213442134521346213472134821349213502135121352213532135421355213562135721358213592136021361213622136321364213652136621367213682136921370213712137221373213742137521376213772137821379213802138121382213832138421385213862138721388213892139021391213922139321394213952139621397213982139921400214012140221403214042140521406214072140821409214102141121412214132141421415214162141721418214192142021421214222142321424214252142621427214282142921430214312143221433214342143521436214372143821439214402144121442214432144421445214462144721448214492145021451214522145321454214552145621457214582145921460214612146221463214642146521466214672146821469214702147121472214732147421475214762147721478214792148021481214822148321484214852148621487214882148921490214912149221493214942149521496214972149821499215002150121502215032150421505215062150721508215092151021511215122151321514215152151621517215182151921520215212152221523215242152521526215272152821529215302153121532215332153421535215362153721538215392154021541215422154321544215452154621547215482154921550215512155221553215542155521556215572155821559215602156121562215632156421565215662156721568215692157021571215722157321574215752157621577215782157921580215812158221583215842158521586215872158821589215902159121592215932159421595215962159721598215992160021601216022160321604216052160621607216082160921610216112161221613216142161521616216172161821619216202162121622216232162421625216262162721628216292163021631216322163321634216352163621637216382163921640216412164221643216442164521646216472164821649216502165121652216532165421655216562165721658216592166021661216622166321664216652166621667216682166921670216712167221673216742167521676216772167821679216802168121682216832168421685216862168721688216892169021691216922169321694216952169621697216982169921700217012170221703217042170521706217072170821709217102171121712217132171421715217162171721718217192172021721217222172321724217252172621727217282172921730217312173221733217342173521736217372173821739217402174121742217432174421745217462174721748217492175021751217522175321754217552175621757217582175921760217612176221763217642176521766217672176821769217702177121772217732177421775217762177721778217792178021781217822178321784217852178621787217882178921790217912179221793217942179521796217972179821799218002180121802218032180421805218062180721808218092181021811218122181321814218152181621817218182181921820218212182221823218242182521826218272182821829218302183121832218332183421835218362183721838218392184021841218422184321844218452184621847218482184921850218512185221853218542185521856218572185821859218602186121862218632186421865218662186721868218692187021871218722187321874218752187621877218782187921880218812188221883218842188521886218872188821889218902189121892218932189421895218962189721898218992190021901219022190321904219052190621907219082190921910219112191221913219142191521916219172191821919219202192121922219232192421925219262192721928219292193021931219322193321934219352193621937219382193921940219412194221943219442194521946219472194821949219502195121952219532195421955219562195721958219592196021961219622196321964219652196621967219682196921970219712197221973219742197521976219772197821979219802198121982219832198421985219862198721988219892199021991219922199321994219952199621997219982199922000220012200222003220042200522006220072200822009220102201122012220132201422015220162201722018220192202022021220222202322024220252202622027220282202922030220312203222033220342203522036220372203822039220402204122042220432204422045220462204722048220492205022051220522205322054220552205622057220582205922060220612206222063220642206522066220672206822069220702207122072220732207422075220762207722078220792208022081220822208322084220852208622087220882208922090220912209222093220942209522096220972209822099221002210122102221032210422105221062210722108221092211022111221122211322114221152211622117221182211922120221212212222123221242212522126221272212822129221302213122132221332213422135221362213722138221392214022141221422214322144221452214622147221482214922150221512215222153221542215522156221572215822159221602216122162221632216422165221662216722168221692217022171221722217322174221752217622177221782217922180221812218222183221842218522186221872218822189221902219122192221932219422195221962219722198221992220022201222022220322204222052220622207222082220922210222112221222213222142221522216222172221822219222202222122222222232222422225222262222722228222292223022231222322223322234222352223622237222382223922240222412224222243222442224522246222472224822249222502225122252222532225422255222562225722258222592226022261222622226322264222652226622267222682226922270222712227222273222742227522276222772227822279222802228122282222832228422285222862228722288222892229022291222922229322294222952229622297222982229922300223012230222303223042230522306223072230822309223102231122312223132231422315223162231722318223192232022321223222232322324223252232622327223282232922330223312233222333223342233522336223372233822339223402234122342223432234422345223462234722348223492235022351223522235322354223552235622357223582235922360223612236222363223642236522366223672236822369223702237122372223732237422375223762237722378223792238022381223822238322384223852238622387223882238922390223912239222393223942239522396223972239822399224002240122402224032240422405224062240722408224092241022411224122241322414224152241622417224182241922420224212242222423224242242522426224272242822429224302243122432224332243422435224362243722438224392244022441224422244322444224452244622447224482244922450224512245222453224542245522456224572245822459224602246122462224632246422465224662246722468224692247022471224722247322474224752247622477224782247922480224812248222483224842248522486224872248822489224902249122492224932249422495224962249722498224992250022501225022250322504225052250622507225082250922510225112251222513225142251522516225172251822519225202252122522225232252422525225262252722528225292253022531225322253322534225352253622537225382253922540225412254222543225442254522546225472254822549225502255122552225532255422555225562255722558225592256022561225622256322564225652256622567225682256922570225712257222573225742257522576225772257822579225802258122582225832258422585225862258722588225892259022591225922259322594225952259622597225982259922600226012260222603226042260522606226072260822609226102261122612226132261422615226162261722618226192262022621226222262322624226252262622627226282262922630226312263222633226342263522636226372263822639226402264122642226432264422645226462264722648226492265022651226522265322654226552265622657226582265922660226612266222663226642266522666226672266822669226702267122672226732267422675226762267722678226792268022681226822268322684226852268622687226882268922690226912269222693226942269522696226972269822699227002270122702227032270422705227062270722708227092271022711227122271322714227152271622717227182271922720227212272222723227242272522726227272272822729227302273122732227332273422735227362273722738227392274022741227422274322744227452274622747227482274922750227512275222753227542275522756227572275822759227602276122762227632276422765227662276722768227692277022771227722277322774227752277622777227782277922780227812278222783227842278522786227872278822789227902279122792227932279422795227962279722798227992280022801228022280322804228052280622807228082280922810228112281222813228142281522816228172281822819228202282122822228232282422825228262282722828228292283022831228322283322834228352283622837228382283922840228412284222843228442284522846228472284822849228502285122852228532285422855228562285722858228592286022861228622286322864228652286622867228682286922870228712287222873228742287522876228772287822879228802288122882228832288422885228862288722888228892289022891228922289322894228952289622897228982289922900229012290222903229042290522906229072290822909229102291122912229132291422915229162291722918229192292022921229222292322924229252292622927229282292922930229312293222933229342293522936229372293822939229402294122942229432294422945229462294722948229492295022951229522295322954229552295622957229582295922960229612296222963229642296522966229672296822969229702297122972229732297422975229762297722978229792298022981229822298322984229852298622987229882298922990229912299222993229942299522996229972299822999230002300123002230032300423005230062300723008230092301023011230122301323014230152301623017230182301923020230212302223023230242302523026230272302823029230302303123032230332303423035230362303723038230392304023041230422304323044230452304623047230482304923050230512305223053230542305523056230572305823059230602306123062230632306423065230662306723068230692307023071230722307323074230752307623077230782307923080230812308223083230842308523086230872308823089230902309123092230932309423095230962309723098230992310023101231022310323104231052310623107231082310923110231112311223113231142311523116231172311823119231202312123122231232312423125231262312723128231292313023131231322313323134231352313623137231382313923140231412314223143231442314523146231472314823149231502315123152231532315423155231562315723158231592316023161231622316323164231652316623167231682316923170231712317223173231742317523176231772317823179231802318123182231832318423185231862318723188231892319023191231922319323194231952319623197231982319923200232012320223203232042320523206232072320823209232102321123212232132321423215232162321723218232192322023221232222322323224232252322623227232282322923230232312323223233232342323523236232372323823239232402324123242232432324423245232462324723248232492325023251232522325323254232552325623257232582325923260232612326223263232642326523266232672326823269232702327123272232732327423275232762327723278232792328023281232822328323284232852328623287232882328923290232912329223293232942329523296232972329823299233002330123302233032330423305233062330723308233092331023311233122331323314233152331623317233182331923320233212332223323233242332523326233272332823329233302333123332233332333423335233362333723338233392334023341233422334323344233452334623347233482334923350233512335223353233542335523356233572335823359233602336123362233632336423365233662336723368233692337023371233722337323374233752337623377233782337923380233812338223383233842338523386233872338823389233902339123392233932339423395233962339723398233992340023401234022340323404234052340623407234082340923410234112341223413234142341523416234172341823419234202342123422234232342423425234262342723428234292343023431234322343323434234352343623437234382343923440234412344223443234442344523446234472344823449234502345123452234532345423455234562345723458234592346023461234622346323464234652346623467234682346923470234712347223473234742347523476234772347823479234802348123482234832348423485234862348723488234892349023491234922349323494234952349623497234982349923500235012350223503235042350523506235072350823509235102351123512235132351423515235162351723518235192352023521235222352323524235252352623527235282352923530235312353223533235342353523536235372353823539235402354123542235432354423545235462354723548235492355023551235522355323554235552355623557235582355923560235612356223563235642356523566235672356823569235702357123572235732357423575235762357723578235792358023581235822358323584235852358623587235882358923590235912359223593235942359523596235972359823599236002360123602236032360423605236062360723608236092361023611236122361323614236152361623617236182361923620236212362223623236242362523626236272362823629236302363123632236332363423635236362363723638236392364023641236422364323644236452364623647236482364923650236512365223653236542365523656236572365823659236602366123662236632366423665236662366723668236692367023671236722367323674236752367623677236782367923680236812368223683236842368523686236872368823689236902369123692236932369423695236962369723698236992370023701237022370323704237052370623707237082370923710237112371223713237142371523716237172371823719237202372123722237232372423725237262372723728237292373023731237322373323734237352373623737237382373923740237412374223743237442374523746237472374823749237502375123752237532375423755237562375723758237592376023761237622376323764237652376623767237682376923770237712377223773237742377523776237772377823779237802378123782237832378423785237862378723788237892379023791237922379323794237952379623797237982379923800238012380223803238042380523806238072380823809238102381123812238132381423815238162381723818238192382023821238222382323824238252382623827238282382923830238312383223833238342383523836238372383823839238402384123842238432384423845238462384723848238492385023851238522385323854238552385623857238582385923860238612386223863238642386523866238672386823869238702387123872238732387423875238762387723878238792388023881238822388323884238852388623887238882388923890238912389223893238942389523896238972389823899239002390123902239032390423905239062390723908239092391023911239122391323914239152391623917239182391923920239212392223923239242392523926239272392823929239302393123932239332393423935239362393723938239392394023941239422394323944239452394623947239482394923950239512395223953239542395523956239572395823959239602396123962239632396423965239662396723968239692397023971239722397323974239752397623977239782397923980239812398223983239842398523986239872398823989239902399123992239932399423995239962399723998239992400024001240022400324004240052400624007240082400924010240112401224013240142401524016240172401824019240202402124022240232402424025240262402724028240292403024031240322403324034240352403624037240382403924040240412404224043240442404524046240472404824049240502405124052240532405424055240562405724058240592406024061240622406324064240652406624067240682406924070240712407224073240742407524076240772407824079240802408124082240832408424085240862408724088240892409024091240922409324094240952409624097240982409924100241012410224103241042410524106241072410824109241102411124112241132411424115241162411724118241192412024121241222412324124241252412624127241282412924130241312413224133241342413524136241372413824139241402414124142241432414424145241462414724148241492415024151241522415324154241552415624157241582415924160241612416224163241642416524166241672416824169241702417124172241732417424175241762417724178241792418024181241822418324184241852418624187241882418924190241912419224193241942419524196241972419824199242002420124202242032420424205242062420724208242092421024211242122421324214242152421624217242182421924220242212422224223242242422524226242272422824229242302423124232242332423424235242362423724238242392424024241242422424324244242452424624247242482424924250242512425224253242542425524256242572425824259242602426124262242632426424265242662426724268242692427024271242722427324274242752427624277242782427924280242812428224283242842428524286242872428824289242902429124292242932429424295242962429724298242992430024301243022430324304243052430624307243082430924310243112431224313243142431524316243172431824319243202432124322243232432424325243262432724328243292433024331243322433324334243352433624337243382433924340243412434224343243442434524346243472434824349243502435124352243532435424355243562435724358243592436024361243622436324364243652436624367243682436924370243712437224373243742437524376243772437824379243802438124382243832438424385243862438724388243892439024391243922439324394243952439624397243982439924400244012440224403244042440524406244072440824409244102441124412244132441424415244162441724418244192442024421244222442324424244252442624427244282442924430244312443224433244342443524436244372443824439244402444124442244432444424445244462444724448244492445024451244522445324454244552445624457244582445924460244612446224463244642446524466244672446824469244702447124472244732447424475244762447724478244792448024481244822448324484244852448624487244882448924490244912449224493244942449524496244972449824499245002450124502245032450424505245062450724508245092451024511245122451324514245152451624517245182451924520245212452224523245242452524526245272452824529245302453124532245332453424535245362453724538245392454024541245422454324544245452454624547245482454924550245512455224553245542455524556245572455824559245602456124562245632456424565245662456724568245692457024571245722457324574245752457624577245782457924580245812458224583245842458524586245872458824589245902459124592245932459424595245962459724598245992460024601246022460324604246052460624607246082460924610246112461224613246142461524616246172461824619246202462124622246232462424625246262462724628246292463024631246322463324634246352463624637246382463924640246412464224643246442464524646246472464824649246502465124652246532465424655246562465724658246592466024661246622466324664246652466624667246682466924670246712467224673246742467524676246772467824679246802468124682246832468424685246862468724688246892469024691246922469324694246952469624697246982469924700247012470224703247042470524706247072470824709247102471124712247132471424715247162471724718247192472024721247222472324724247252472624727247282472924730247312473224733247342473524736247372473824739247402474124742247432474424745247462474724748247492475024751247522475324754247552475624757247582475924760247612476224763247642476524766247672476824769247702477124772247732477424775247762477724778247792478024781247822478324784247852478624787247882478924790247912479224793247942479524796247972479824799248002480124802248032480424805248062480724808248092481024811248122481324814248152481624817248182481924820248212482224823248242482524826248272482824829248302483124832248332483424835248362483724838248392484024841248422484324844248452484624847248482484924850248512485224853248542485524856248572485824859248602486124862248632486424865248662486724868248692487024871248722487324874248752487624877248782487924880248812488224883248842488524886248872488824889248902489124892248932489424895248962489724898248992490024901249022490324904249052490624907249082490924910249112491224913249142491524916249172491824919249202492124922249232492424925249262492724928249292493024931249322493324934249352493624937249382493924940249412494224943249442494524946249472494824949249502495124952249532495424955249562495724958249592496024961249622496324964249652496624967249682496924970249712497224973249742497524976249772497824979249802498124982249832498424985249862498724988249892499024991249922499324994249952499624997249982499925000250012500225003250042500525006250072500825009250102501125012250132501425015250162501725018250192502025021250222502325024250252502625027250282502925030250312503225033250342503525036250372503825039250402504125042250432504425045250462504725048250492505025051250522505325054250552505625057250582505925060250612506225063250642506525066250672506825069250702507125072250732507425075250762507725078250792508025081250822508325084250852508625087250882508925090250912509225093250942509525096250972509825099251002510125102251032510425105251062510725108251092511025111251122511325114251152511625117251182511925120251212512225123251242512525126251272512825129251302513125132251332513425135251362513725138251392514025141251422514325144251452514625147251482514925150251512515225153251542515525156251572515825159251602516125162251632516425165251662516725168251692517025171251722517325174251752517625177251782517925180251812518225183251842518525186251872518825189251902519125192251932519425195251962519725198251992520025201252022520325204252052520625207252082520925210252112521225213252142521525216252172521825219252202522125222252232522425225252262522725228252292523025231252322523325234252352523625237252382523925240252412524225243252442524525246252472524825249252502525125252252532525425255252562525725258252592526025261252622526325264252652526625267252682526925270252712527225273252742527525276252772527825279252802528125282252832528425285252862528725288252892529025291252922529325294252952529625297252982529925300253012530225303253042530525306253072530825309253102531125312253132531425315253162531725318253192532025321253222532325324253252532625327253282532925330253312533225333253342533525336253372533825339253402534125342253432534425345253462534725348253492535025351253522535325354253552535625357253582535925360253612536225363253642536525366253672536825369253702537125372253732537425375253762537725378253792538025381253822538325384253852538625387253882538925390253912539225393253942539525396253972539825399254002540125402254032540425405254062540725408254092541025411254122541325414254152541625417254182541925420254212542225423254242542525426254272542825429254302543125432254332543425435254362543725438254392544025441254422544325444254452544625447254482544925450254512545225453254542545525456254572545825459254602546125462254632546425465254662546725468254692547025471254722547325474254752547625477254782547925480254812548225483254842548525486254872548825489254902549125492254932549425495254962549725498254992550025501255022550325504255052550625507255082550925510255112551225513255142551525516255172551825519255202552125522255232552425525255262552725528255292553025531255322553325534255352553625537255382553925540255412554225543255442554525546255472554825549255502555125552255532555425555255562555725558255592556025561255622556325564255652556625567255682556925570255712557225573255742557525576255772557825579255802558125582255832558425585255862558725588255892559025591255922559325594255952559625597255982559925600256012560225603256042560525606256072560825609256102561125612256132561425615256162561725618256192562025621256222562325624256252562625627256282562925630256312563225633256342563525636256372563825639256402564125642256432564425645256462564725648256492565025651256522565325654256552565625657256582565925660256612566225663256642566525666256672566825669256702567125672256732567425675256762567725678256792568025681256822568325684256852568625687256882568925690256912569225693256942569525696256972569825699257002570125702257032570425705257062570725708257092571025711257122571325714257152571625717257182571925720257212572225723257242572525726257272572825729257302573125732257332573425735257362573725738257392574025741257422574325744257452574625747257482574925750257512575225753257542575525756257572575825759257602576125762257632576425765257662576725768257692577025771257722577325774257752577625777257782577925780257812578225783257842578525786257872578825789257902579125792257932579425795257962579725798257992580025801258022580325804258052580625807258082580925810258112581225813258142581525816258172581825819258202582125822258232582425825258262582725828258292583025831258322583325834258352583625837258382583925840258412584225843258442584525846258472584825849258502585125852258532585425855258562585725858258592586025861258622586325864258652586625867258682586925870258712587225873258742587525876258772587825879258802588125882258832588425885258862588725888258892589025891258922589325894258952589625897258982589925900259012590225903259042590525906259072590825909259102591125912259132591425915259162591725918259192592025921259222592325924259252592625927259282592925930259312593225933259342593525936259372593825939259402594125942259432594425945259462594725948259492595025951259522595325954259552595625957259582595925960259612596225963259642596525966259672596825969259702597125972259732597425975259762597725978259792598025981259822598325984259852598625987259882598925990259912599225993259942599525996259972599825999260002600126002260032600426005260062600726008260092601026011260122601326014260152601626017260182601926020260212602226023260242602526026260272602826029260302603126032260332603426035260362603726038260392604026041260422604326044260452604626047260482604926050260512605226053260542605526056260572605826059260602606126062260632606426065260662606726068260692607026071260722607326074260752607626077260782607926080260812608226083260842608526086260872608826089260902609126092260932609426095260962609726098260992610026101261022610326104261052610626107261082610926110261112611226113261142611526116261172611826119261202612126122261232612426125261262612726128261292613026131261322613326134261352613626137261382613926140261412614226143261442614526146261472614826149261502615126152261532615426155261562615726158261592616026161261622616326164261652616626167261682616926170261712617226173261742617526176261772617826179261802618126182261832618426185261862618726188261892619026191261922619326194261952619626197261982619926200262012620226203262042620526206262072620826209262102621126212262132621426215262162621726218262192622026221262222622326224262252622626227262282622926230262312623226233262342623526236262372623826239262402624126242262432624426245262462624726248262492625026251262522625326254262552625626257262582625926260262612626226263262642626526266262672626826269262702627126272262732627426275262762627726278262792628026281262822628326284262852628626287262882628926290262912629226293262942629526296262972629826299263002630126302263032630426305263062630726308263092631026311263122631326314263152631626317263182631926320263212632226323263242632526326263272632826329263302633126332263332633426335263362633726338263392634026341263422634326344263452634626347263482634926350263512635226353263542635526356263572635826359263602636126362263632636426365263662636726368263692637026371263722637326374263752637626377263782637926380263812638226383263842638526386263872638826389263902639126392263932639426395263962639726398263992640026401264022640326404264052640626407264082640926410264112641226413264142641526416264172641826419264202642126422264232642426425264262642726428264292643026431264322643326434264352643626437264382643926440264412644226443264442644526446264472644826449264502645126452264532645426455264562645726458264592646026461264622646326464264652646626467264682646926470264712647226473264742647526476264772647826479264802648126482264832648426485264862648726488264892649026491264922649326494264952649626497264982649926500265012650226503265042650526506265072650826509265102651126512265132651426515265162651726518265192652026521265222652326524265252652626527265282652926530265312653226533265342653526536265372653826539265402654126542265432654426545265462654726548265492655026551265522655326554265552655626557265582655926560265612656226563265642656526566265672656826569265702657126572265732657426575265762657726578265792658026581265822658326584265852658626587265882658926590265912659226593265942659526596265972659826599266002660126602266032660426605266062660726608266092661026611266122661326614266152661626617266182661926620266212662226623266242662526626266272662826629266302663126632266332663426635266362663726638266392664026641266422664326644266452664626647266482664926650266512665226653266542665526656266572665826659266602666126662266632666426665266662666726668266692667026671266722667326674266752667626677266782667926680266812668226683266842668526686266872668826689266902669126692266932669426695266962669726698266992670026701267022670326704267052670626707267082670926710267112671226713267142671526716267172671826719267202672126722267232672426725267262672726728267292673026731267322673326734267352673626737267382673926740267412674226743267442674526746267472674826749267502675126752267532675426755267562675726758267592676026761267622676326764267652676626767267682676926770267712677226773267742677526776267772677826779267802678126782267832678426785267862678726788267892679026791267922679326794267952679626797267982679926800268012680226803268042680526806268072680826809268102681126812268132681426815268162681726818268192682026821268222682326824268252682626827268282682926830268312683226833268342683526836268372683826839268402684126842268432684426845268462684726848268492685026851268522685326854268552685626857268582685926860268612686226863268642686526866268672686826869268702687126872268732687426875268762687726878268792688026881268822688326884268852688626887268882688926890268912689226893268942689526896268972689826899269002690126902269032690426905269062690726908269092691026911269122691326914269152691626917269182691926920269212692226923269242692526926269272692826929269302693126932269332693426935269362693726938269392694026941269422694326944269452694626947269482694926950269512695226953269542695526956269572695826959269602696126962269632696426965269662696726968269692697026971269722697326974269752697626977269782697926980269812698226983269842698526986269872698826989269902699126992269932699426995269962699726998269992700027001270022700327004270052700627007270082700927010270112701227013270142701527016270172701827019270202702127022270232702427025270262702727028270292703027031270322703327034270352703627037270382703927040270412704227043270442704527046270472704827049270502705127052270532705427055270562705727058270592706027061270622706327064270652706627067270682706927070270712707227073270742707527076270772707827079270802708127082270832708427085270862708727088270892709027091270922709327094270952709627097270982709927100271012710227103271042710527106271072710827109271102711127112271132711427115271162711727118271192712027121271222712327124271252712627127271282712927130271312713227133271342713527136271372713827139271402714127142271432714427145271462714727148271492715027151271522715327154271552715627157271582715927160271612716227163271642716527166271672716827169271702717127172271732717427175271762717727178271792718027181271822718327184271852718627187271882718927190271912719227193271942719527196271972719827199272002720127202272032720427205272062720727208272092721027211272122721327214272152721627217272182721927220272212722227223272242722527226272272722827229272302723127232272332723427235272362723727238272392724027241272422724327244272452724627247272482724927250272512725227253272542725527256272572725827259272602726127262272632726427265272662726727268272692727027271272722727327274272752727627277272782727927280272812728227283272842728527286272872728827289272902729127292272932729427295272962729727298272992730027301273022730327304273052730627307273082730927310273112731227313273142731527316273172731827319273202732127322273232732427325273262732727328273292733027331273322733327334273352733627337273382733927340273412734227343273442734527346273472734827349273502735127352273532735427355273562735727358273592736027361273622736327364273652736627367273682736927370273712737227373273742737527376273772737827379273802738127382273832738427385273862738727388273892739027391273922739327394273952739627397273982739927400274012740227403274042740527406274072740827409274102741127412274132741427415274162741727418274192742027421274222742327424274252742627427274282742927430274312743227433274342743527436274372743827439274402744127442274432744427445274462744727448274492745027451274522745327454274552745627457274582745927460274612746227463274642746527466274672746827469274702747127472274732747427475274762747727478274792748027481274822748327484274852748627487274882748927490274912749227493274942749527496274972749827499275002750127502275032750427505275062750727508275092751027511275122751327514275152751627517275182751927520275212752227523275242752527526275272752827529275302753127532275332753427535275362753727538275392754027541275422754327544275452754627547275482754927550275512755227553275542755527556275572755827559275602756127562275632756427565275662756727568275692757027571275722757327574275752757627577275782757927580275812758227583275842758527586275872758827589275902759127592275932759427595275962759727598275992760027601276022760327604276052760627607276082760927610276112761227613276142761527616276172761827619276202762127622276232762427625276262762727628276292763027631276322763327634276352763627637276382763927640276412764227643276442764527646276472764827649276502765127652276532765427655276562765727658276592766027661276622766327664276652766627667276682766927670276712767227673276742767527676276772767827679276802768127682276832768427685276862768727688276892769027691276922769327694276952769627697276982769927700277012770227703277042770527706277072770827709277102771127712277132771427715277162771727718277192772027721277222772327724277252772627727277282772927730277312773227733277342773527736277372773827739277402774127742277432774427745277462774727748277492775027751277522775327754277552775627757277582775927760277612776227763277642776527766277672776827769277702777127772277732777427775277762777727778277792778027781277822778327784277852778627787277882778927790277912779227793277942779527796277972779827799278002780127802278032780427805278062780727808278092781027811278122781327814278152781627817278182781927820278212782227823278242782527826278272782827829278302783127832278332783427835278362783727838278392784027841278422784327844278452784627847278482784927850278512785227853278542785527856278572785827859278602786127862278632786427865278662786727868278692787027871278722787327874278752787627877278782787927880278812788227883278842788527886278872788827889278902789127892278932789427895278962789727898278992790027901279022790327904279052790627907279082790927910279112791227913279142791527916279172791827919279202792127922279232792427925279262792727928279292793027931279322793327934279352793627937279382793927940279412794227943279442794527946279472794827949279502795127952279532795427955279562795727958279592796027961279622796327964279652796627967279682796927970279712797227973279742797527976279772797827979279802798127982279832798427985279862798727988279892799027991279922799327994279952799627997279982799928000280012800228003280042800528006280072800828009280102801128012280132801428015280162801728018280192802028021280222802328024280252802628027280282802928030280312803228033280342803528036280372803828039280402804128042280432804428045280462804728048280492805028051280522805328054280552805628057280582805928060280612806228063280642806528066280672806828069280702807128072280732807428075280762807728078280792808028081280822808328084280852808628087280882808928090280912809228093280942809528096280972809828099281002810128102281032810428105281062810728108281092811028111281122811328114281152811628117281182811928120281212812228123281242812528126281272812828129281302813128132281332813428135281362813728138281392814028141281422814328144281452814628147281482814928150281512815228153281542815528156281572815828159281602816128162281632816428165281662816728168281692817028171281722817328174281752817628177281782817928180281812818228183281842818528186281872818828189281902819128192281932819428195281962819728198281992820028201282022820328204282052820628207282082820928210282112821228213282142821528216282172821828219282202822128222282232822428225282262822728228282292823028231282322823328234282352823628237282382823928240282412824228243282442824528246282472824828249282502825128252282532825428255282562825728258282592826028261282622826328264282652826628267282682826928270282712827228273282742827528276282772827828279282802828128282282832828428285282862828728288282892829028291282922829328294282952829628297282982829928300283012830228303283042830528306283072830828309283102831128312283132831428315283162831728318283192832028321283222832328324283252832628327283282832928330283312833228333283342833528336283372833828339283402834128342283432834428345283462834728348283492835028351283522835328354283552835628357283582835928360283612836228363283642836528366283672836828369283702837128372283732837428375283762837728378283792838028381283822838328384283852838628387283882838928390283912839228393283942839528396283972839828399284002840128402284032840428405284062840728408284092841028411284122841328414284152841628417284182841928420284212842228423284242842528426284272842828429284302843128432284332843428435284362843728438284392844028441284422844328444284452844628447284482844928450284512845228453284542845528456284572845828459284602846128462284632846428465284662846728468284692847028471284722847328474284752847628477284782847928480284812848228483284842848528486284872848828489284902849128492284932849428495284962849728498284992850028501285022850328504285052850628507285082850928510285112851228513285142851528516285172851828519285202852128522285232852428525285262852728528285292853028531285322853328534285352853628537285382853928540285412854228543285442854528546285472854828549285502855128552285532855428555285562855728558285592856028561285622856328564285652856628567285682856928570285712857228573285742857528576285772857828579285802858128582285832858428585285862858728588285892859028591285922859328594285952859628597285982859928600286012860228603286042860528606286072860828609286102861128612286132861428615286162861728618286192862028621286222862328624286252862628627286282862928630286312863228633286342863528636286372863828639286402864128642286432864428645286462864728648286492865028651286522865328654286552865628657286582865928660286612866228663286642866528666286672866828669286702867128672286732867428675286762867728678286792868028681286822868328684286852868628687286882868928690286912869228693286942869528696286972869828699287002870128702287032870428705287062870728708287092871028711287122871328714287152871628717287182871928720287212872228723287242872528726287272872828729287302873128732287332873428735287362873728738287392874028741287422874328744287452874628747287482874928750287512875228753287542875528756287572875828759287602876128762287632876428765287662876728768287692877028771287722877328774287752877628777287782877928780287812878228783287842878528786287872878828789287902879128792287932879428795287962879728798287992880028801288022880328804288052880628807288082880928810288112881228813288142881528816288172881828819288202882128822288232882428825288262882728828288292883028831288322883328834288352883628837288382883928840288412884228843288442884528846288472884828849288502885128852288532885428855288562885728858288592886028861288622886328864288652886628867288682886928870288712887228873288742887528876288772887828879288802888128882288832888428885288862888728888288892889028891288922889328894288952889628897288982889928900289012890228903289042890528906289072890828909289102891128912289132891428915289162891728918289192892028921289222892328924289252892628927289282892928930289312893228933289342893528936289372893828939289402894128942289432894428945289462894728948289492895028951289522895328954289552895628957289582895928960289612896228963289642896528966289672896828969289702897128972289732897428975289762897728978289792898028981289822898328984289852898628987289882898928990289912899228993289942899528996289972899828999290002900129002290032900429005290062900729008290092901029011290122901329014290152901629017290182901929020290212902229023290242902529026290272902829029290302903129032290332903429035290362903729038290392904029041290422904329044290452904629047290482904929050290512905229053290542905529056290572905829059290602906129062290632906429065290662906729068290692907029071290722907329074290752907629077290782907929080290812908229083290842908529086290872908829089290902909129092290932909429095290962909729098290992910029101291022910329104291052910629107291082910929110291112911229113291142911529116291172911829119291202912129122291232912429125291262912729128291292913029131291322913329134291352913629137291382913929140291412914229143291442914529146291472914829149291502915129152291532915429155291562915729158291592916029161291622916329164291652916629167291682916929170291712917229173291742917529176291772917829179291802918129182291832918429185291862918729188291892919029191291922919329194291952919629197291982919929200292012920229203292042920529206292072920829209292102921129212292132921429215292162921729218292192922029221292222922329224292252922629227292282922929230292312923229233292342923529236292372923829239292402924129242292432924429245292462924729248292492925029251292522925329254292552925629257292582925929260292612926229263292642926529266292672926829269292702927129272292732927429275292762927729278292792928029281292822928329284292852928629287292882928929290292912929229293292942929529296292972929829299293002930129302293032930429305293062930729308293092931029311293122931329314293152931629317293182931929320293212932229323293242932529326293272932829329293302933129332293332933429335293362933729338293392934029341293422934329344293452934629347293482934929350293512935229353293542935529356293572935829359293602936129362293632936429365293662936729368293692937029371293722937329374293752937629377293782937929380293812938229383293842938529386293872938829389293902939129392293932939429395293962939729398293992940029401294022940329404294052940629407294082940929410294112941229413294142941529416294172941829419294202942129422294232942429425294262942729428294292943029431294322943329434294352943629437294382943929440294412944229443294442944529446294472944829449294502945129452294532945429455294562945729458294592946029461294622946329464294652946629467294682946929470294712947229473294742947529476294772947829479294802948129482294832948429485294862948729488294892949029491294922949329494294952949629497294982949929500295012950229503295042950529506295072950829509295102951129512295132951429515295162951729518295192952029521295222952329524295252952629527295282952929530295312953229533295342953529536295372953829539295402954129542295432954429545295462954729548295492955029551295522955329554295552955629557295582955929560295612956229563295642956529566295672956829569295702957129572295732957429575295762957729578295792958029581295822958329584295852958629587295882958929590295912959229593295942959529596295972959829599296002960129602296032960429605296062960729608296092961029611296122961329614296152961629617296182961929620296212962229623296242962529626296272962829629296302963129632296332963429635296362963729638296392964029641296422964329644296452964629647296482964929650296512965229653296542965529656296572965829659296602966129662296632966429665296662966729668296692967029671296722967329674296752967629677296782967929680296812968229683296842968529686296872968829689296902969129692296932969429695296962969729698296992970029701297022970329704297052970629707297082970929710297112971229713297142971529716297172971829719297202972129722297232972429725297262972729728297292973029731297322973329734297352973629737297382973929740297412974229743297442974529746297472974829749297502975129752297532975429755297562975729758297592976029761297622976329764297652976629767297682976929770297712977229773297742977529776297772977829779297802978129782297832978429785297862978729788297892979029791297922979329794297952979629797297982979929800298012980229803298042980529806298072980829809298102981129812298132981429815298162981729818298192982029821298222982329824298252982629827298282982929830298312983229833298342983529836298372983829839298402984129842298432984429845298462984729848298492985029851298522985329854298552985629857298582985929860298612986229863298642986529866298672986829869298702987129872298732987429875298762987729878298792988029881298822988329884298852988629887298882988929890298912989229893298942989529896298972989829899299002990129902299032990429905299062990729908299092991029911299122991329914299152991629917299182991929920299212992229923299242992529926299272992829929299302993129932299332993429935299362993729938299392994029941299422994329944299452994629947299482994929950299512995229953299542995529956299572995829959299602996129962299632996429965299662996729968299692997029971299722997329974299752997629977299782997929980299812998229983299842998529986299872998829989299902999129992299932999429995299962999729998299993000030001300023000330004300053000630007300083000930010300113001230013300143001530016300173001830019300203002130022300233002430025300263002730028300293003030031300323003330034300353003630037300383003930040300413004230043300443004530046300473004830049300503005130052300533005430055300563005730058300593006030061300623006330064300653006630067300683006930070300713007230073300743007530076300773007830079300803008130082300833008430085300863008730088300893009030091300923009330094300953009630097300983009930100301013010230103301043010530106301073010830109301103011130112301133011430115301163011730118301193012030121301223012330124301253012630127301283012930130301313013230133301343013530136301373013830139301403014130142301433014430145301463014730148301493015030151301523015330154301553015630157301583015930160301613016230163301643016530166301673016830169301703017130172301733017430175301763017730178301793018030181301823018330184301853018630187301883018930190301913019230193301943019530196301973019830199302003020130202302033020430205302063020730208302093021030211302123021330214302153021630217302183021930220302213022230223302243022530226302273022830229302303023130232302333023430235302363023730238302393024030241302423024330244302453024630247302483024930250302513025230253302543025530256302573025830259302603026130262302633026430265302663026730268302693027030271302723027330274302753027630277302783027930280302813028230283302843028530286302873028830289302903029130292302933029430295302963029730298302993030030301303023030330304303053030630307303083030930310303113031230313303143031530316303173031830319303203032130322303233032430325303263032730328303293033030331303323033330334303353033630337303383033930340303413034230343303443034530346303473034830349303503035130352303533035430355303563035730358303593036030361303623036330364303653036630367303683036930370303713037230373303743037530376303773037830379303803038130382303833038430385303863038730388303893039030391303923039330394303953039630397303983039930400304013040230403304043040530406304073040830409304103041130412304133041430415304163041730418304193042030421304223042330424304253042630427304283042930430304313043230433304343043530436304373043830439304403044130442304433044430445304463044730448304493045030451304523045330454304553045630457304583045930460304613046230463304643046530466304673046830469304703047130472304733047430475304763047730478304793048030481304823048330484304853048630487304883048930490304913049230493304943049530496304973049830499305003050130502305033050430505305063050730508305093051030511305123051330514305153051630517305183051930520305213052230523305243052530526305273052830529305303053130532305333053430535305363053730538305393054030541305423054330544305453054630547305483054930550305513055230553305543055530556305573055830559305603056130562305633056430565305663056730568305693057030571305723057330574305753057630577305783057930580305813058230583305843058530586305873058830589305903059130592305933059430595305963059730598305993060030601306023060330604306053060630607306083060930610306113061230613306143061530616306173061830619306203062130622306233062430625306263062730628306293063030631306323063330634306353063630637306383063930640306413064230643306443064530646306473064830649306503065130652306533065430655306563065730658306593066030661306623066330664306653066630667306683066930670306713067230673306743067530676306773067830679306803068130682306833068430685306863068730688306893069030691306923069330694306953069630697306983069930700307013070230703307043070530706307073070830709307103071130712307133071430715307163071730718307193072030721307223072330724307253072630727307283072930730307313073230733307343073530736307373073830739307403074130742307433074430745307463074730748307493075030751307523075330754307553075630757307583075930760307613076230763307643076530766307673076830769307703077130772307733077430775307763077730778307793078030781307823078330784307853078630787307883078930790307913079230793307943079530796307973079830799308003080130802308033080430805308063080730808308093081030811308123081330814308153081630817308183081930820308213082230823308243082530826308273082830829308303083130832308333083430835308363083730838308393084030841308423084330844308453084630847308483084930850308513085230853308543085530856308573085830859308603086130862308633086430865308663086730868308693087030871308723087330874308753087630877308783087930880308813088230883308843088530886308873088830889308903089130892308933089430895308963089730898308993090030901309023090330904309053090630907309083090930910309113091230913309143091530916309173091830919309203092130922309233092430925309263092730928309293093030931309323093330934309353093630937309383093930940309413094230943309443094530946309473094830949309503095130952309533095430955309563095730958309593096030961309623096330964309653096630967309683096930970309713097230973309743097530976309773097830979309803098130982309833098430985309863098730988309893099030991309923099330994309953099630997309983099931000310013100231003310043100531006310073100831009310103101131012310133101431015310163101731018310193102031021310223102331024310253102631027310283102931030310313103231033310343103531036310373103831039310403104131042310433104431045310463104731048310493105031051310523105331054310553105631057310583105931060310613106231063310643106531066310673106831069310703107131072310733107431075310763107731078310793108031081310823108331084310853108631087310883108931090310913109231093310943109531096310973109831099311003110131102311033110431105311063110731108311093111031111311123111331114311153111631117311183111931120311213112231123311243112531126311273112831129311303113131132311333113431135311363113731138311393114031141311423114331144311453114631147311483114931150311513115231153311543115531156311573115831159311603116131162311633116431165311663116731168311693117031171311723117331174311753117631177311783117931180311813118231183311843118531186311873118831189311903119131192311933119431195311963119731198311993120031201312023120331204312053120631207312083120931210312113121231213312143121531216312173121831219312203122131222312233122431225312263122731228312293123031231312323123331234312353123631237312383123931240312413124231243312443124531246312473124831249312503125131252312533125431255312563125731258312593126031261312623126331264312653126631267312683126931270312713127231273312743127531276312773127831279312803128131282312833128431285312863128731288312893129031291312923129331294312953129631297312983129931300313013130231303313043130531306313073130831309313103131131312313133131431315313163131731318313193132031321313223132331324313253132631327313283132931330313313133231333313343133531336313373133831339313403134131342313433134431345313463134731348313493135031351313523135331354313553135631357313583135931360313613136231363313643136531366313673136831369313703137131372313733137431375313763137731378313793138031381313823138331384313853138631387313883138931390313913139231393313943139531396313973139831399314003140131402314033140431405314063140731408314093141031411314123141331414314153141631417314183141931420314213142231423314243142531426314273142831429314303143131432314333143431435314363143731438314393144031441314423144331444314453144631447314483144931450314513145231453314543145531456314573145831459314603146131462314633146431465314663146731468314693147031471314723147331474314753147631477314783147931480314813148231483314843148531486314873148831489314903149131492314933149431495314963149731498314993150031501315023150331504315053150631507315083150931510315113151231513315143151531516315173151831519315203152131522315233152431525315263152731528315293153031531315323153331534315353153631537315383153931540315413154231543315443154531546315473154831549315503155131552315533155431555315563155731558315593156031561315623156331564315653156631567315683156931570315713157231573315743157531576315773157831579315803158131582315833158431585315863158731588315893159031591315923159331594315953159631597315983159931600316013160231603316043160531606316073160831609316103161131612316133161431615316163161731618316193162031621316223162331624316253162631627316283162931630316313163231633316343163531636316373163831639316403164131642316433164431645316463164731648316493165031651316523165331654316553165631657316583165931660316613166231663316643166531666316673166831669316703167131672316733167431675316763167731678316793168031681316823168331684316853168631687316883168931690316913169231693316943169531696316973169831699317003170131702317033170431705317063170731708317093171031711317123171331714317153171631717317183171931720317213172231723317243172531726317273172831729317303173131732317333173431735317363173731738317393174031741317423174331744317453174631747317483174931750317513175231753317543175531756317573175831759317603176131762317633176431765317663176731768317693177031771317723177331774317753177631777317783177931780317813178231783317843178531786317873178831789317903179131792317933179431795317963179731798317993180031801318023180331804318053180631807318083180931810318113181231813318143181531816318173181831819318203182131822318233182431825318263182731828318293183031831318323183331834318353183631837318383183931840318413184231843318443184531846318473184831849318503185131852318533185431855318563185731858318593186031861318623186331864318653186631867318683186931870318713187231873318743187531876318773187831879318803188131882318833188431885318863188731888318893189031891318923189331894318953189631897318983189931900319013190231903319043190531906319073190831909319103191131912319133191431915319163191731918319193192031921319223192331924319253192631927319283192931930319313193231933319343193531936319373193831939319403194131942319433194431945319463194731948319493195031951319523195331954319553195631957319583195931960319613196231963319643196531966319673196831969319703197131972319733197431975319763197731978319793198031981319823198331984319853198631987319883198931990319913199231993319943199531996319973199831999320003200132002320033200432005320063200732008320093201032011320123201332014320153201632017320183201932020320213202232023320243202532026320273202832029320303203132032320333203432035320363203732038320393204032041320423204332044320453204632047320483204932050320513205232053320543205532056320573205832059320603206132062320633206432065320663206732068320693207032071320723207332074320753207632077320783207932080320813208232083320843208532086320873208832089320903209132092320933209432095320963209732098320993210032101321023210332104321053210632107321083210932110321113211232113321143211532116321173211832119321203212132122321233212432125321263212732128321293213032131321323213332134321353213632137321383213932140321413214232143321443214532146321473214832149321503215132152321533215432155321563215732158321593216032161321623216332164321653216632167321683216932170321713217232173321743217532176321773217832179321803218132182321833218432185321863218732188321893219032191321923219332194321953219632197321983219932200322013220232203322043220532206322073220832209322103221132212322133221432215322163221732218322193222032221322223222332224322253222632227322283222932230322313223232233322343223532236322373223832239322403224132242322433224432245322463224732248322493225032251322523225332254322553225632257322583225932260322613226232263322643226532266322673226832269322703227132272322733227432275322763227732278322793228032281322823228332284322853228632287322883228932290322913229232293322943229532296322973229832299323003230132302323033230432305323063230732308323093231032311323123231332314323153231632317323183231932320323213232232323323243232532326323273232832329323303233132332323333233432335323363233732338323393234032341323423234332344323453234632347323483234932350323513235232353323543235532356323573235832359323603236132362323633236432365323663236732368323693237032371323723237332374323753237632377323783237932380323813238232383323843238532386323873238832389323903239132392323933239432395323963239732398323993240032401324023240332404324053240632407324083240932410324113241232413324143241532416324173241832419324203242132422324233242432425324263242732428324293243032431324323243332434324353243632437324383243932440324413244232443324443244532446324473244832449324503245132452324533245432455324563245732458324593246032461324623246332464324653246632467324683246932470324713247232473324743247532476324773247832479324803248132482324833248432485324863248732488324893249032491324923249332494324953249632497324983249932500325013250232503325043250532506325073250832509325103251132512325133251432515325163251732518325193252032521325223252332524325253252632527325283252932530325313253232533325343253532536325373253832539325403254132542325433254432545325463254732548325493255032551325523255332554325553255632557325583255932560325613256232563325643256532566325673256832569325703257132572325733257432575325763257732578325793258032581325823258332584325853258632587325883258932590325913259232593325943259532596325973259832599326003260132602326033260432605326063260732608326093261032611326123261332614326153261632617326183261932620326213262232623326243262532626326273262832629326303263132632326333263432635326363263732638326393264032641326423264332644326453264632647326483264932650326513265232653326543265532656326573265832659326603266132662326633266432665326663266732668326693267032671326723267332674326753267632677326783267932680326813268232683326843268532686326873268832689326903269132692326933269432695326963269732698326993270032701327023270332704327053270632707327083270932710327113271232713327143271532716327173271832719327203272132722327233272432725327263272732728327293273032731327323273332734327353273632737327383273932740327413274232743327443274532746327473274832749327503275132752327533275432755327563275732758327593276032761327623276332764327653276632767327683276932770327713277232773327743277532776327773277832779327803278132782327833278432785327863278732788327893279032791327923279332794327953279632797327983279932800328013280232803328043280532806328073280832809328103281132812328133281432815328163281732818328193282032821328223282332824328253282632827328283282932830328313283232833328343283532836328373283832839328403284132842328433284432845328463284732848328493285032851328523285332854328553285632857328583285932860328613286232863328643286532866328673286832869328703287132872328733287432875328763287732878328793288032881328823288332884328853288632887328883288932890328913289232893328943289532896328973289832899329003290132902329033290432905329063290732908329093291032911329123291332914329153291632917329183291932920329213292232923329243292532926329273292832929329303293132932329333293432935329363293732938329393294032941329423294332944329453294632947329483294932950329513295232953329543295532956329573295832959329603296132962329633296432965329663296732968329693297032971329723297332974329753297632977329783297932980329813298232983329843298532986329873298832989329903299132992329933299432995329963299732998329993300033001330023300333004330053300633007330083300933010330113301233013330143301533016330173301833019330203302133022330233302433025330263302733028330293303033031330323303333034330353303633037330383303933040330413304233043330443304533046330473304833049330503305133052330533305433055330563305733058330593306033061330623306333064330653306633067330683306933070330713307233073330743307533076330773307833079330803308133082330833308433085330863308733088330893309033091330923309333094330953309633097330983309933100331013310233103331043310533106331073310833109331103311133112331133311433115331163311733118331193312033121331223312333124331253312633127331283312933130331313313233133331343313533136331373313833139331403314133142331433314433145331463314733148331493315033151331523315333154331553315633157331583315933160331613316233163331643316533166331673316833169331703317133172331733317433175331763317733178331793318033181331823318333184331853318633187331883318933190331913319233193331943319533196331973319833199332003320133202332033320433205332063320733208332093321033211332123321333214332153321633217332183321933220332213322233223332243322533226332273322833229332303323133232332333323433235332363323733238332393324033241332423324333244332453324633247332483324933250332513325233253332543325533256332573325833259332603326133262332633326433265332663326733268332693327033271332723327333274332753327633277332783327933280332813328233283332843328533286332873328833289332903329133292332933329433295332963329733298332993330033301333023330333304333053330633307333083330933310333113331233313333143331533316333173331833319333203332133322333233332433325333263332733328333293333033331333323333333334333353333633337333383333933340333413334233343333443334533346333473334833349333503335133352333533335433355333563335733358333593336033361333623336333364333653336633367333683336933370333713337233373333743337533376333773337833379333803338133382333833338433385333863338733388333893339033391333923339333394333953339633397333983339933400334013340233403334043340533406334073340833409334103341133412334133341433415334163341733418334193342033421334223342333424334253342633427334283342933430334313343233433334343343533436334373343833439334403344133442334433344433445334463344733448334493345033451334523345333454334553345633457334583345933460334613346233463334643346533466334673346833469334703347133472334733347433475334763347733478334793348033481334823348333484334853348633487334883348933490334913349233493334943349533496334973349833499335003350133502335033350433505335063350733508335093351033511335123351333514335153351633517335183351933520335213352233523335243352533526335273352833529335303353133532335333353433535335363353733538335393354033541335423354333544335453354633547335483354933550335513355233553335543355533556335573355833559335603356133562335633356433565335663356733568335693357033571335723357333574335753357633577335783357933580335813358233583335843358533586335873358833589335903359133592335933359433595335963359733598335993360033601336023360333604336053360633607336083360933610336113361233613336143361533616336173361833619336203362133622336233362433625336263362733628336293363033631336323363333634336353363633637336383363933640336413364233643336443364533646336473364833649336503365133652336533365433655336563365733658336593366033661336623366333664336653366633667336683366933670336713367233673336743367533676336773367833679336803368133682336833368433685336863368733688336893369033691336923369333694336953369633697336983369933700337013370233703337043370533706337073370833709337103371133712337133371433715337163371733718337193372033721337223372333724337253372633727337283372933730337313373233733337343373533736337373373833739337403374133742337433374433745337463374733748337493375033751337523375333754337553375633757337583375933760337613376233763337643376533766337673376833769337703377133772337733377433775337763377733778337793378033781337823378333784337853378633787337883378933790337913379233793337943379533796337973379833799338003380133802338033380433805338063380733808338093381033811338123381333814338153381633817338183381933820338213382233823338243382533826338273382833829338303383133832338333383433835338363383733838338393384033841338423384333844338453384633847338483384933850338513385233853338543385533856338573385833859338603386133862338633386433865338663386733868338693387033871338723387333874338753387633877338783387933880338813388233883338843388533886338873388833889338903389133892338933389433895338963389733898338993390033901339023390333904339053390633907339083390933910339113391233913339143391533916339173391833919339203392133922339233392433925339263392733928339293393033931339323393333934339353393633937339383393933940339413394233943339443394533946339473394833949339503395133952339533395433955339563395733958339593396033961339623396333964339653396633967339683396933970339713397233973339743397533976339773397833979339803398133982339833398433985339863398733988339893399033991339923399333994339953399633997339983399934000340013400234003340043400534006340073400834009340103401134012340133401434015340163401734018340193402034021340223402334024340253402634027340283402934030340313403234033340343403534036340373403834039340403404134042340433404434045340463404734048340493405034051340523405334054340553405634057340583405934060340613406234063340643406534066340673406834069340703407134072340733407434075340763407734078340793408034081340823408334084340853408634087340883408934090340913409234093340943409534096340973409834099341003410134102341033410434105341063410734108341093411034111341123411334114341153411634117341183411934120341213412234123341243412534126341273412834129341303413134132341333413434135341363413734138341393414034141341423414334144341453414634147341483414934150341513415234153341543415534156341573415834159341603416134162341633416434165341663416734168341693417034171341723417334174341753417634177341783417934180341813418234183341843418534186341873418834189341903419134192341933419434195341963419734198341993420034201342023420334204342053420634207342083420934210342113421234213342143421534216342173421834219342203422134222342233422434225342263422734228342293423034231342323423334234342353423634237342383423934240342413424234243342443424534246342473424834249342503425134252342533425434255342563425734258342593426034261342623426334264342653426634267342683426934270342713427234273342743427534276342773427834279342803428134282342833428434285342863428734288342893429034291342923429334294342953429634297342983429934300343013430234303343043430534306343073430834309343103431134312343133431434315343163431734318343193432034321343223432334324343253432634327343283432934330343313433234333343343433534336343373433834339343403434134342343433434434345343463434734348343493435034351343523435334354343553435634357343583435934360343613436234363343643436534366343673436834369343703437134372343733437434375343763437734378343793438034381343823438334384343853438634387343883438934390343913439234393343943439534396343973439834399344003440134402344033440434405344063440734408344093441034411344123441334414344153441634417344183441934420344213442234423344243442534426344273442834429344303443134432344333443434435344363443734438344393444034441344423444334444344453444634447344483444934450344513445234453344543445534456344573445834459344603446134462344633446434465344663446734468344693447034471344723447334474344753447634477344783447934480344813448234483344843448534486344873448834489344903449134492344933449434495344963449734498344993450034501345023450334504345053450634507345083450934510345113451234513345143451534516345173451834519345203452134522345233452434525345263452734528345293453034531345323453334534345353453634537345383453934540345413454234543345443454534546345473454834549345503455134552345533455434555345563455734558345593456034561345623456334564345653456634567345683456934570345713457234573345743457534576345773457834579345803458134582345833458434585345863458734588345893459034591345923459334594345953459634597345983459934600346013460234603346043460534606346073460834609346103461134612346133461434615346163461734618346193462034621346223462334624346253462634627346283462934630346313463234633346343463534636346373463834639346403464134642346433464434645346463464734648346493465034651346523465334654346553465634657346583465934660346613466234663346643466534666346673466834669346703467134672346733467434675346763467734678346793468034681346823468334684346853468634687346883468934690346913469234693346943469534696346973469834699347003470134702347033470434705347063470734708347093471034711347123471334714347153471634717347183471934720347213472234723347243472534726347273472834729347303473134732347333473434735347363473734738347393474034741347423474334744347453474634747347483474934750347513475234753347543475534756347573475834759347603476134762347633476434765347663476734768347693477034771347723477334774347753477634777347783477934780347813478234783347843478534786347873478834789347903479134792347933479434795347963479734798347993480034801348023480334804348053480634807348083480934810348113481234813348143481534816348173481834819348203482134822348233482434825348263482734828348293483034831348323483334834348353483634837348383483934840348413484234843348443484534846348473484834849348503485134852348533485434855348563485734858348593486034861348623486334864348653486634867348683486934870348713487234873348743487534876348773487834879348803488134882348833488434885348863488734888348893489034891348923489334894348953489634897348983489934900349013490234903349043490534906349073490834909349103491134912349133491434915349163491734918349193492034921349223492334924349253492634927349283492934930349313493234933349343493534936349373493834939349403494134942349433494434945349463494734948349493495034951349523495334954349553495634957349583495934960349613496234963349643496534966349673496834969349703497134972349733497434975349763497734978349793498034981349823498334984349853498634987349883498934990349913499234993349943499534996349973499834999350003500135002350033500435005350063500735008350093501035011350123501335014350153501635017350183501935020350213502235023350243502535026350273502835029350303503135032350333503435035350363503735038350393504035041350423504335044350453504635047350483504935050350513505235053350543505535056350573505835059350603506135062350633506435065350663506735068350693507035071350723507335074350753507635077350783507935080350813508235083350843508535086350873508835089350903509135092350933509435095350963509735098350993510035101351023510335104351053510635107351083510935110351113511235113351143511535116351173511835119351203512135122351233512435125351263512735128351293513035131351323513335134351353513635137351383513935140351413514235143351443514535146351473514835149351503515135152351533515435155351563515735158351593516035161351623516335164351653516635167351683516935170351713517235173351743517535176351773517835179351803518135182351833518435185351863518735188351893519035191351923519335194351953519635197351983519935200352013520235203352043520535206352073520835209352103521135212352133521435215352163521735218352193522035221352223522335224352253522635227352283522935230352313523235233352343523535236352373523835239352403524135242352433524435245352463524735248352493525035251352523525335254352553525635257352583525935260352613526235263352643526535266352673526835269352703527135272352733527435275352763527735278352793528035281352823528335284352853528635287352883528935290352913529235293352943529535296352973529835299353003530135302353033530435305353063530735308353093531035311353123531335314353153531635317353183531935320353213532235323353243532535326353273532835329353303533135332353333533435335353363533735338353393534035341353423534335344353453534635347353483534935350353513535235353353543535535356353573535835359353603536135362353633536435365353663536735368353693537035371353723537335374353753537635377353783537935380353813538235383353843538535386353873538835389353903539135392353933539435395353963539735398353993540035401354023540335404354053540635407354083540935410354113541235413354143541535416354173541835419354203542135422354233542435425354263542735428354293543035431354323543335434354353543635437354383543935440354413544235443354443544535446354473544835449354503545135452354533545435455354563545735458354593546035461354623546335464354653546635467354683546935470354713547235473354743547535476354773547835479354803548135482354833548435485354863548735488354893549035491354923549335494354953549635497354983549935500355013550235503355043550535506355073550835509355103551135512355133551435515355163551735518355193552035521355223552335524355253552635527355283552935530355313553235533355343553535536355373553835539355403554135542355433554435545355463554735548355493555035551355523555335554355553555635557355583555935560355613556235563355643556535566355673556835569355703557135572355733557435575355763557735578355793558035581355823558335584355853558635587355883558935590355913559235593355943559535596355973559835599356003560135602356033560435605356063560735608356093561035611356123561335614356153561635617356183561935620356213562235623356243562535626356273562835629356303563135632356333563435635356363563735638356393564035641356423564335644356453564635647356483564935650356513565235653356543565535656356573565835659356603566135662356633566435665356663566735668356693567035671356723567335674356753567635677356783567935680356813568235683356843568535686356873568835689356903569135692356933569435695356963569735698356993570035701357023570335704357053570635707357083570935710357113571235713357143571535716357173571835719357203572135722357233572435725357263572735728357293573035731357323573335734357353573635737357383573935740357413574235743357443574535746357473574835749357503575135752357533575435755357563575735758357593576035761357623576335764357653576635767357683576935770357713577235773357743577535776357773577835779357803578135782357833578435785357863578735788357893579035791357923579335794357953579635797357983579935800358013580235803358043580535806358073580835809358103581135812358133581435815358163581735818358193582035821358223582335824358253582635827358283582935830358313583235833358343583535836358373583835839358403584135842358433584435845358463584735848358493585035851358523585335854358553585635857358583585935860358613586235863358643586535866358673586835869358703587135872358733587435875358763587735878358793588035881358823588335884358853588635887358883588935890358913589235893358943589535896358973589835899359003590135902359033590435905359063590735908359093591035911359123591335914359153591635917359183591935920359213592235923359243592535926359273592835929359303593135932359333593435935359363593735938359393594035941359423594335944359453594635947359483594935950359513595235953359543595535956359573595835959359603596135962359633596435965359663596735968359693597035971359723597335974359753597635977359783597935980359813598235983359843598535986359873598835989359903599135992359933599435995359963599735998359993600036001360023600336004360053600636007360083600936010360113601236013360143601536016360173601836019360203602136022360233602436025360263602736028360293603036031360323603336034360353603636037360383603936040360413604236043360443604536046360473604836049360503605136052360533605436055360563605736058360593606036061360623606336064360653606636067360683606936070360713607236073360743607536076360773607836079360803608136082360833608436085360863608736088360893609036091360923609336094360953609636097360983609936100361013610236103361043610536106361073610836109361103611136112361133611436115361163611736118361193612036121361223612336124361253612636127361283612936130361313613236133361343613536136361373613836139361403614136142361433614436145361463614736148361493615036151361523615336154361553615636157361583615936160361613616236163361643616536166361673616836169361703617136172361733617436175361763617736178361793618036181361823618336184361853618636187361883618936190361913619236193361943619536196361973619836199362003620136202362033620436205362063620736208362093621036211362123621336214362153621636217362183621936220362213622236223362243622536226362273622836229362303623136232362333623436235362363623736238362393624036241362423624336244362453624636247362483624936250362513625236253362543625536256362573625836259362603626136262362633626436265362663626736268362693627036271362723627336274362753627636277362783627936280362813628236283362843628536286362873628836289362903629136292362933629436295362963629736298362993630036301363023630336304363053630636307363083630936310363113631236313363143631536316363173631836319363203632136322363233632436325363263632736328363293633036331363323633336334363353633636337363383633936340363413634236343363443634536346363473634836349363503635136352363533635436355363563635736358363593636036361363623636336364363653636636367363683636936370363713637236373363743637536376363773637836379363803638136382363833638436385363863638736388363893639036391363923639336394363953639636397363983639936400364013640236403364043640536406364073640836409364103641136412364133641436415364163641736418364193642036421364223642336424364253642636427364283642936430364313643236433364343643536436364373643836439364403644136442364433644436445364463644736448364493645036451364523645336454364553645636457364583645936460364613646236463364643646536466364673646836469364703647136472364733647436475364763647736478364793648036481364823648336484364853648636487364883648936490364913649236493364943649536496364973649836499365003650136502365033650436505365063650736508365093651036511365123651336514365153651636517365183651936520365213652236523365243652536526365273652836529365303653136532365333653436535365363653736538365393654036541365423654336544365453654636547365483654936550365513655236553365543655536556365573655836559365603656136562365633656436565365663656736568365693657036571365723657336574365753657636577365783657936580365813658236583365843658536586365873658836589365903659136592365933659436595365963659736598365993660036601366023660336604366053660636607366083660936610366113661236613366143661536616366173661836619366203662136622366233662436625366263662736628366293663036631366323663336634366353663636637366383663936640366413664236643366443664536646366473664836649366503665136652366533665436655366563665736658366593666036661366623666336664366653666636667366683666936670366713667236673366743667536676366773667836679366803668136682366833668436685366863668736688366893669036691366923669336694366953669636697366983669936700367013670236703367043670536706367073670836709367103671136712367133671436715367163671736718367193672036721367223672336724367253672636727367283672936730367313673236733367343673536736367373673836739367403674136742367433674436745367463674736748367493675036751367523675336754367553675636757367583675936760367613676236763367643676536766367673676836769367703677136772367733677436775367763677736778367793678036781367823678336784367853678636787367883678936790367913679236793367943679536796367973679836799368003680136802368033680436805368063680736808368093681036811368123681336814368153681636817368183681936820368213682236823368243682536826368273682836829368303683136832368333683436835368363683736838368393684036841368423684336844368453684636847368483684936850368513685236853368543685536856368573685836859368603686136862368633686436865368663686736868368693687036871368723687336874368753687636877368783687936880368813688236883368843688536886368873688836889368903689136892368933689436895368963689736898368993690036901369023690336904369053690636907369083690936910369113691236913369143691536916369173691836919369203692136922369233692436925369263692736928369293693036931369323693336934369353693636937369383693936940369413694236943369443694536946369473694836949369503695136952369533695436955369563695736958369593696036961369623696336964369653696636967369683696936970369713697236973369743697536976369773697836979369803698136982369833698436985369863698736988369893699036991369923699336994369953699636997369983699937000370013700237003370043700537006370073700837009370103701137012370133701437015370163701737018370193702037021370223702337024370253702637027370283702937030370313703237033370343703537036370373703837039370403704137042370433704437045370463704737048370493705037051370523705337054370553705637057370583705937060370613706237063370643706537066370673706837069370703707137072370733707437075370763707737078370793708037081370823708337084370853708637087370883708937090370913709237093370943709537096370973709837099371003710137102371033710437105371063710737108371093711037111371123711337114371153711637117371183711937120371213712237123371243712537126371273712837129371303713137132371333713437135371363713737138371393714037141371423714337144371453714637147371483714937150371513715237153371543715537156371573715837159371603716137162371633716437165371663716737168371693717037171371723717337174371753717637177371783717937180371813718237183371843718537186371873718837189371903719137192371933719437195371963719737198371993720037201372023720337204372053720637207372083720937210372113721237213372143721537216372173721837219372203722137222372233722437225372263722737228372293723037231372323723337234372353723637237372383723937240372413724237243372443724537246372473724837249372503725137252372533725437255372563725737258372593726037261372623726337264372653726637267372683726937270372713727237273372743727537276372773727837279372803728137282372833728437285372863728737288372893729037291372923729337294372953729637297372983729937300373013730237303373043730537306373073730837309373103731137312373133731437315373163731737318373193732037321373223732337324373253732637327373283732937330373313733237333373343733537336373373733837339373403734137342373433734437345373463734737348373493735037351373523735337354373553735637357373583735937360373613736237363373643736537366373673736837369373703737137372373733737437375373763737737378373793738037381373823738337384373853738637387373883738937390373913739237393373943739537396373973739837399374003740137402374033740437405374063740737408374093741037411374123741337414374153741637417374183741937420374213742237423374243742537426374273742837429374303743137432374333743437435374363743737438374393744037441374423744337444374453744637447374483744937450374513745237453374543745537456374573745837459374603746137462374633746437465374663746737468374693747037471374723747337474374753747637477374783747937480374813748237483374843748537486374873748837489374903749137492374933749437495374963749737498374993750037501375023750337504375053750637507375083750937510375113751237513375143751537516375173751837519375203752137522375233752437525375263752737528375293753037531375323753337534375353753637537375383753937540375413754237543375443754537546375473754837549375503755137552375533755437555375563755737558375593756037561375623756337564375653756637567375683756937570375713757237573375743757537576375773757837579375803758137582375833758437585375863758737588375893759037591375923759337594375953759637597375983759937600376013760237603376043760537606376073760837609376103761137612376133761437615376163761737618376193762037621376223762337624376253762637627376283762937630376313763237633376343763537636376373763837639376403764137642376433764437645376463764737648376493765037651376523765337654376553765637657376583765937660376613766237663376643766537666376673766837669376703767137672376733767437675376763767737678376793768037681376823768337684376853768637687376883768937690376913769237693376943769537696376973769837699377003770137702377033770437705377063770737708377093771037711377123771337714377153771637717377183771937720377213772237723377243772537726377273772837729377303773137732377333773437735377363773737738377393774037741377423774337744377453774637747377483774937750377513775237753377543775537756377573775837759377603776137762377633776437765377663776737768377693777037771377723777337774377753777637777377783777937780377813778237783377843778537786377873778837789377903779137792377933779437795377963779737798377993780037801378023780337804378053780637807378083780937810378113781237813378143781537816378173781837819378203782137822378233782437825378263782737828378293783037831378323783337834378353783637837378383783937840378413784237843378443784537846378473784837849378503785137852378533785437855378563785737858378593786037861378623786337864378653786637867378683786937870378713787237873378743787537876378773787837879378803788137882378833788437885378863788737888378893789037891378923789337894378953789637897378983789937900379013790237903379043790537906379073790837909379103791137912379133791437915379163791737918379193792037921379223792337924379253792637927379283792937930379313793237933379343793537936379373793837939379403794137942379433794437945379463794737948379493795037951379523795337954379553795637957379583795937960379613796237963379643796537966379673796837969379703797137972379733797437975379763797737978379793798037981379823798337984379853798637987379883798937990379913799237993379943799537996379973799837999380003800138002380033800438005380063800738008380093801038011380123801338014380153801638017380183801938020380213802238023380243802538026380273802838029380303803138032380333803438035380363803738038380393804038041380423804338044380453804638047380483804938050380513805238053380543805538056380573805838059380603806138062380633806438065380663806738068380693807038071380723807338074380753807638077380783807938080380813808238083380843808538086380873808838089380903809138092380933809438095380963809738098380993810038101381023810338104381053810638107381083810938110381113811238113381143811538116381173811838119381203812138122381233812438125381263812738128381293813038131381323813338134381353813638137381383813938140381413814238143381443814538146381473814838149381503815138152381533815438155381563815738158381593816038161381623816338164381653816638167381683816938170381713817238173381743817538176381773817838179381803818138182381833818438185381863818738188381893819038191381923819338194381953819638197381983819938200382013820238203382043820538206382073820838209382103821138212382133821438215382163821738218382193822038221382223822338224382253822638227382283822938230382313823238233382343823538236382373823838239382403824138242382433824438245382463824738248382493825038251382523825338254382553825638257382583825938260382613826238263382643826538266382673826838269382703827138272382733827438275382763827738278382793828038281382823828338284382853828638287382883828938290382913829238293382943829538296382973829838299383003830138302383033830438305383063830738308383093831038311383123831338314383153831638317383183831938320383213832238323383243832538326383273832838329383303833138332383333833438335383363833738338383393834038341383423834338344383453834638347383483834938350383513835238353383543835538356383573835838359383603836138362383633836438365383663836738368383693837038371383723837338374383753837638377383783837938380383813838238383383843838538386383873838838389383903839138392383933839438395383963839738398383993840038401384023840338404384053840638407384083840938410384113841238413384143841538416384173841838419384203842138422384233842438425384263842738428384293843038431384323843338434384353843638437384383843938440384413844238443384443844538446384473844838449
  1. /**
  2. * @class Ext.draw.ContainerBase
  3. * @private
  4. */
  5. Ext.define('Ext.draw.ContainerBase', {
  6. extend: 'Ext.panel.Panel',
  7. requires: [
  8. 'Ext.window.Window'
  9. ],
  10. /**
  11. * @cfg {String} previewTitleText The text to place in Preview Chart window title.
  12. */
  13. previewTitleText: 'Chart Preview',
  14. /**
  15. * @cfg {String} previewAltText The text to place in the Preview image alt attribute.
  16. */
  17. previewAltText: 'Chart preview',
  18. layout: 'container',
  19. // Adds a listener to this draw container's element. If the element does not yet exist
  20. // addition of the listener will be deferred until onRender. Useful when listeners
  21. // need to be attached during initConfig.
  22. addElementListener: function() {
  23. var me = this,
  24. args = arguments;
  25. if (me.rendered) {
  26. me.el.on.apply(me.el, args);
  27. } else {
  28. me.on('render', function() {
  29. me.el.on.apply(me.el, args);
  30. });
  31. }
  32. },
  33. removeElementListener: function() {
  34. var me = this;
  35. if (me.rendered) {
  36. me.el.un.apply(me.el, arguments);
  37. }
  38. },
  39. afterRender: function() {
  40. this.callParent(arguments);
  41. this.initAnimator();
  42. },
  43. getItems: function() {
  44. var me = this,
  45. items = me.items;
  46. if (!items || !items.isMixedCollection) {
  47. // getItems may be called before initItems has run and created the items
  48. // collection, so we have to create it here just in case (this can happen
  49. // if getItems is called during initConfig)
  50. me.initItems();
  51. }
  52. return me.items;
  53. },
  54. onRender: function() {
  55. this.callParent(arguments);
  56. this.element = this.el;
  57. this.bodyElement = this.body;
  58. },
  59. setItems: function(items) {
  60. this.items = items;
  61. return items;
  62. },
  63. setSurfaceSize: function(width, height) {
  64. this.resizeHandler({
  65. width: width,
  66. height: height
  67. });
  68. this.renderFrame();
  69. },
  70. onResize: function(width, height, oldWidth, oldHeight) {
  71. this.handleResize({
  72. width: width,
  73. height: height
  74. }, !this.size);
  75. },
  76. // First resize should be performed without any delay.
  77. preview: function(image) {
  78. var items;
  79. if (Ext.isIE8) {
  80. return false;
  81. }
  82. image = image || this.getImage();
  83. if (image.type === 'svg-markup') {
  84. items = {
  85. xtype: 'container',
  86. html: image.data
  87. };
  88. } else {
  89. items = {
  90. xtype: 'image',
  91. mode: 'img',
  92. cls: Ext.baseCSSPrefix + 'chart-image',
  93. alt: this.previewAltText,
  94. src: image.data,
  95. listeners: {
  96. afterrender: function() {
  97. var me = this,
  98. img = me.imgEl.dom,
  99. ratio = image.type === 'svg' ? 1 : (window['devicePixelRatio'] || 1),
  100. size;
  101. if (!img.naturalWidth || !img.naturalHeight) {
  102. img.onload = function() {
  103. var width = img.naturalWidth,
  104. height = img.naturalHeight;
  105. me.setWidth(Math.floor(width / ratio));
  106. me.setHeight(Math.floor(height / ratio));
  107. };
  108. } else {
  109. size = me.getSize();
  110. me.setWidth(Math.floor(size.width / ratio));
  111. me.setHeight(Math.floor(size.height / ratio));
  112. }
  113. }
  114. }
  115. };
  116. }
  117. new Ext.window.Window({
  118. title: this.previewTitleText,
  119. closable: true,
  120. renderTo: Ext.getBody(),
  121. autoShow: true,
  122. maximizeable: true,
  123. maximized: true,
  124. border: true,
  125. layout: {
  126. type: 'hbox',
  127. pack: 'center',
  128. align: 'middle'
  129. },
  130. items: {
  131. xtype: 'container',
  132. items: items
  133. }
  134. });
  135. },
  136. privates: {
  137. getTargetEl: function() {
  138. return this.bodyElement;
  139. },
  140. reattachToBody: function() {
  141. // This is to ensure charts work properly as grid column widgets.
  142. var me = this;
  143. if (me.pendingDetachSize) {
  144. me.handleResize();
  145. }
  146. me.pendingDetachSize = false;
  147. me.callParent();
  148. }
  149. }
  150. });
  151. /**
  152. * @private
  153. * @class Ext.draw.SurfaceBase (Classic)
  154. */
  155. Ext.define('Ext.draw.SurfaceBase', {
  156. extend: 'Ext.Widget',
  157. getOwnerBody: function() {
  158. return this.ownerCt.body;
  159. }
  160. });
  161. /**
  162. * @private
  163. * @class Ext.draw.sprite.AnimationParser
  164. *
  165. * Computes an intermidiate value between two values of the same type for use in animations.
  166. * Can have pre- and post- processor functions if the values need to be processed
  167. * before an intermidiate value can be computed (parseInitial), or the computed value
  168. * needs to be processed before it can be used as a valid attribute value (serve).
  169. */
  170. Ext.define('Ext.draw.sprite.AnimationParser', function() {
  171. function compute(from, to, delta) {
  172. return from + (to - from) * delta;
  173. }
  174. return {
  175. singleton: true,
  176. attributeRe: /^url\(#([a-zA-Z\-]+)\)$/,
  177. requires: [
  178. 'Ext.draw.Color'
  179. ],
  180. color: {
  181. parseInitial: function(color1, color2) {
  182. if (Ext.isString(color1)) {
  183. color1 = Ext.util.Color.create(color1);
  184. }
  185. if (Ext.isString(color2)) {
  186. color2 = Ext.util.Color.create(color2);
  187. }
  188. if ((color1 && color1.isColor) && (color2 && color2.isColor)) {
  189. return [
  190. [
  191. color1.r,
  192. color1.g,
  193. color1.b,
  194. color1.a
  195. ],
  196. [
  197. color2.r,
  198. color2.g,
  199. color2.b,
  200. color2.a
  201. ]
  202. ];
  203. } else {
  204. return [
  205. color1 || color2,
  206. color2 || color1
  207. ];
  208. }
  209. },
  210. compute: function(from, to, delta) {
  211. if (!Ext.isArray(from) || !Ext.isArray(to)) {
  212. return to || from;
  213. } else {
  214. return [
  215. compute(from[0], to[0], delta),
  216. compute(from[1], to[1], delta),
  217. compute(from[2], to[2], delta),
  218. compute(from[3], to[3], delta)
  219. ];
  220. }
  221. },
  222. serve: function(array) {
  223. var color = Ext.util.Color.fly(array[0], array[1], array[2], array[3]);
  224. return color.toString();
  225. }
  226. },
  227. number: {
  228. parse: function(n) {
  229. return n === null ? null : +n;
  230. },
  231. compute: function(from, to, delta) {
  232. if (!Ext.isNumber(from) || !Ext.isNumber(to)) {
  233. return to || from;
  234. } else {
  235. return compute(from, to, delta);
  236. }
  237. }
  238. },
  239. angle: {
  240. parseInitial: function(from, to) {
  241. if (to - from > Math.PI) {
  242. to -= Math.PI * 2;
  243. } else if (to - from < -Math.PI) {
  244. to += Math.PI * 2;
  245. }
  246. return [
  247. from,
  248. to
  249. ];
  250. },
  251. compute: function(from, to, delta) {
  252. if (!Ext.isNumber(from) || !Ext.isNumber(to)) {
  253. return to || from;
  254. } else {
  255. return compute(from, to, delta);
  256. }
  257. }
  258. },
  259. path: {
  260. parseInitial: function(from, to) {
  261. var fromStripes = from.toStripes(),
  262. toStripes = to.toStripes(),
  263. i, j,
  264. fromLength = fromStripes.length,
  265. toLength = toStripes.length,
  266. fromStripe, toStripe, length,
  267. lastStripe = toStripes[toLength - 1],
  268. endPoint = [
  269. lastStripe[lastStripe.length - 2],
  270. lastStripe[lastStripe.length - 1]
  271. ];
  272. for (i = fromLength; i < toLength; i++) {
  273. fromStripes.push(fromStripes[fromLength - 1].slice(0));
  274. }
  275. for (i = toLength; i < fromLength; i++) {
  276. toStripes.push(endPoint.slice(0));
  277. }
  278. length = fromStripes.length;
  279. toStripes.path = to;
  280. toStripes.temp = new Ext.draw.Path();
  281. for (i = 0; i < length; i++) {
  282. fromStripe = fromStripes[i];
  283. toStripe = toStripes[i];
  284. fromLength = fromStripe.length;
  285. toLength = toStripe.length;
  286. toStripes.temp.commands.push('M');
  287. for (j = toLength; j < fromLength; j += 6) {
  288. toStripe.push(endPoint[0], endPoint[1], endPoint[0], endPoint[1], endPoint[0], endPoint[1]);
  289. }
  290. lastStripe = toStripes[toStripes.length - 1];
  291. endPoint = [
  292. lastStripe[lastStripe.length - 2],
  293. lastStripe[lastStripe.length - 1]
  294. ];
  295. for (j = fromLength; j < toLength; j += 6) {
  296. fromStripe.push(endPoint[0], endPoint[1], endPoint[0], endPoint[1], endPoint[0], endPoint[1]);
  297. }
  298. for (i = 0; i < toStripe.length; i++) {
  299. toStripe[i] -= fromStripe[i];
  300. }
  301. for (i = 2; i < toStripe.length; i += 6) {
  302. toStripes.temp.commands.push('C');
  303. }
  304. }
  305. return [
  306. fromStripes,
  307. toStripes
  308. ];
  309. },
  310. compute: function(fromStripes, toStripes, delta) {
  311. if (delta >= 1) {
  312. return toStripes.path;
  313. }
  314. var i = 0,
  315. ln = fromStripes.length,
  316. j = 0,
  317. ln2, from, to,
  318. temp = toStripes.temp.params,
  319. pos = 0;
  320. for (; i < ln; i++) {
  321. from = fromStripes[i];
  322. to = toStripes[i];
  323. ln2 = from.length;
  324. for (j = 0; j < ln2; j++) {
  325. temp[pos++] = to[j] * delta + from[j];
  326. }
  327. }
  328. return toStripes.temp;
  329. }
  330. },
  331. data: {
  332. compute: function(from, to, delta, target) {
  333. var iMaxFrom = from.length - 1,
  334. iMaxTo = to.length - 1,
  335. iMax = Math.max(iMaxFrom, iMaxTo),
  336. i, start, end;
  337. if (!target || target === from) {
  338. target = [];
  339. }
  340. target.length = iMax + 1;
  341. for (i = 0; i <= iMax; i++) {
  342. start = from[Math.min(i, iMaxFrom)];
  343. end = to[Math.min(i, iMaxTo)];
  344. if (Ext.isNumber(start)) {
  345. if (!Ext.isNumber(end)) {
  346. // This may not give the desired visual result during
  347. // animation (after all, we don't know what the target
  348. // value should be, if it wasn't given to us), but it's
  349. // better than spitting out a bunch of NaNs in the target
  350. // array, when transitioning from a non-empty to an empty
  351. // array.
  352. end = 0;
  353. }
  354. target[i] = start + (end - start) * delta;
  355. } else {
  356. target[i] = end;
  357. }
  358. }
  359. return target;
  360. }
  361. },
  362. text: {
  363. compute: function(from, to, delta) {
  364. return from.substr(0, Math.round(from.length * (1 - delta))) + to.substr(Math.round(to.length * (1 - delta)));
  365. }
  366. },
  367. limited: 'number',
  368. limited01: 'number'
  369. };
  370. });
  371. (function() {
  372. if (!Ext.global.Float32Array) {
  373. // Typed Array polyfill
  374. var Float32Array = function(array) {
  375. if (typeof array === 'number') {
  376. this.length = array;
  377. } else if ('length' in array) {
  378. this.length = array.length;
  379. for (var i = 0,
  380. len = array.length; i < len; i++) {
  381. this[i] = +array[i];
  382. }
  383. }
  384. };
  385. Float32Array.prototype = [];
  386. Ext.global.Float32Array = Float32Array;
  387. }
  388. })();
  389. /**
  390. * Utility class providing mathematics functionalities through all the draw package.
  391. */
  392. Ext.define('Ext.draw.Draw', {
  393. singleton: true,
  394. radian: Math.PI / 180,
  395. pi2: Math.PI * 2,
  396. /**
  397. * @deprecated 6.5.0 Please use the {@link Ext#identityFn} instead.
  398. * Function that returns its first element.
  399. * @param {Mixed} a
  400. * @return {Mixed}
  401. */
  402. reflectFn: function(a) {
  403. return a;
  404. },
  405. /**
  406. * Converting degrees to radians.
  407. * @param {Number} degrees
  408. * @return {Number}
  409. */
  410. rad: function(degrees) {
  411. return (degrees % 360) * this.radian;
  412. },
  413. /**
  414. * Converting radians to degrees.
  415. * @param {Number} radian
  416. * @return {Number}
  417. */
  418. degrees: function(radian) {
  419. return (radian / this.radian) % 360;
  420. },
  421. /**
  422. *
  423. * @param {Object} bbox1
  424. * @param {Object} bbox2
  425. * @param {Number} [padding]
  426. * @return {Boolean}
  427. */
  428. isBBoxIntersect: function(bbox1, bbox2, padding) {
  429. padding = padding || 0;
  430. return (Math.max(bbox1.x, bbox2.x) - padding > Math.min(bbox1.x + bbox1.width, bbox2.x + bbox2.width)) || (Math.max(bbox1.y, bbox2.y) - padding > Math.min(bbox1.y + bbox1.height, bbox2.y + bbox2.height));
  431. },
  432. /**
  433. * Checks if a point is within a bounding box.
  434. * @param x
  435. * @param y
  436. * @param bbox
  437. * @return {Boolean}
  438. */
  439. isPointInBBox: function(x, y, bbox) {
  440. return !!bbox && x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height);
  441. },
  442. /**
  443. * Natural cubic spline interpolation.
  444. * This algorithm runs in linear time.
  445. *
  446. * @param {Array} points Array of numbers.
  447. */
  448. naturalSpline: function(points) {
  449. var i, j,
  450. ln = points.length,
  451. nd, d, y, ny,
  452. r = 0,
  453. zs = new Float32Array(points.length),
  454. result = new Float32Array(points.length * 3 - 2);
  455. zs[0] = 0;
  456. zs[ln - 1] = 0;
  457. for (i = 1; i < ln - 1; i++) {
  458. zs[i] = (points[i + 1] + points[i - 1] - 2 * points[i]) - zs[i - 1];
  459. r = 1 / (4 - r);
  460. zs[i] *= r;
  461. }
  462. for (i = ln - 2; i > 0; i--) {
  463. r = 3.732050807568877 + 48.248711305964385 / (-13.928203230275537 + Math.pow(0.07179676972449123, i));
  464. zs[i] -= zs[i + 1] * r;
  465. }
  466. ny = points[0];
  467. nd = ny - zs[0];
  468. for (i = 0 , j = 0; i < ln - 1; j += 3) {
  469. y = ny;
  470. d = nd;
  471. i++;
  472. ny = points[i];
  473. nd = ny - zs[i];
  474. result[j] = y;
  475. result[j + 1] = (nd + 2 * d) / 3;
  476. result[j + 2] = (nd * 2 + d) / 3;
  477. }
  478. result[j] = ny;
  479. return result;
  480. },
  481. /**
  482. * Shorthand for {@link #naturalSpline}
  483. */
  484. spline: function(points) {
  485. return this.naturalSpline(points);
  486. },
  487. /**
  488. * @private
  489. * Cardinal spline interpolation.
  490. * Goes from cardinal control points to cubic Bezier control points.
  491. */
  492. cardinalToBezier: function(P1, P2, P3, P4, tension) {
  493. return [
  494. P2,
  495. P2 + (P3 - P1) / 6 * tension,
  496. P3 - (P4 - P2) / 6 * tension,
  497. P3
  498. ];
  499. },
  500. /**
  501. * @private
  502. * @param {Number[]} P An array of n x- or y-coordinates.
  503. * @param {Number} tension
  504. * @return {Float32Array} An array of 3n - 2 Bezier control points.
  505. */
  506. cardinalSpline: function(P, tension) {
  507. var n = P.length,
  508. result = new Float32Array(n * 3 - 2),
  509. i, bezier;
  510. if (tension === undefined) {
  511. tension = 0.5;
  512. }
  513. bezier = this.cardinalToBezier(P[0], P[0], P[1], P[2], tension);
  514. result[0] = bezier[0];
  515. result[1] = bezier[1];
  516. result[2] = bezier[2];
  517. result[3] = bezier[3];
  518. for (i = 0; i < n - 3; i++) {
  519. bezier = this.cardinalToBezier(P[i], P[i + 1], P[i + 2], P[i + 3], tension);
  520. result[4 + i * 3] = bezier[1];
  521. result[4 + i * 3 + 1] = bezier[2];
  522. result[4 + i * 3 + 2] = bezier[3];
  523. }
  524. bezier = this.cardinalToBezier(P[n - 3], P[n - 2], P[n - 1], P[n - 1], tension);
  525. result[4 + i * 3] = bezier[1];
  526. result[4 + i * 3 + 1] = bezier[2];
  527. result[4 + i * 3 + 2] = bezier[3];
  528. return result;
  529. },
  530. /**
  531. * @private
  532. *
  533. * Calculates bezier curve control anchor points for a particular point in a path, with a
  534. * smoothing curve applied. The smoothness of the curve is controlled by the 'value' parameter.
  535. * Note that this algorithm assumes that the line being smoothed is normalized going from left
  536. * to right; it makes special adjustments assuming this orientation.
  537. *
  538. * @param {Number} prevX X coordinate of the previous point in the path
  539. * @param {Number} prevY Y coordinate of the previous point in the path
  540. * @param {Number} curX X coordinate of the current point in the path
  541. * @param {Number} curY Y coordinate of the current point in the path
  542. * @param {Number} nextX X coordinate of the next point in the path
  543. * @param {Number} nextY Y coordinate of the next point in the path
  544. * @param {Number} value A value to control the smoothness of the curve; this is used to
  545. * divide the distance between points, so a value of 2 corresponds to
  546. * half the distance between points (a very smooth line) while higher values
  547. * result in less smooth curves. Defaults to 4.
  548. * @return {Object} Object containing x1, y1, x2, y2 bezier control anchor points; x1 and y1
  549. * are the control point for the curve toward the previous path point, and
  550. * x2 and y2 are the control point for the curve toward the next path point.
  551. */
  552. getAnchors: function(prevX, prevY, curX, curY, nextX, nextY, value) {
  553. value = value || 4;
  554. var PI = Math.PI,
  555. halfPI = PI / 2,
  556. abs = Math.abs,
  557. sin = Math.sin,
  558. cos = Math.cos,
  559. atan = Math.atan,
  560. control1Length, control2Length, control1Angle, control2Angle, control1X, control1Y, control2X, control2Y, alpha;
  561. // Find the length of each control anchor line, by dividing the horizontal distance
  562. // between points by the value parameter.
  563. control1Length = (curX - prevX) / value;
  564. control2Length = (nextX - curX) / value;
  565. // Determine the angle of each control anchor line. If the middle point is a vertical
  566. // turnaround then we force it to a flat horizontal angle to prevent the curve from
  567. // dipping above or below the middle point. Otherwise we use an angle that points
  568. // toward the previous/next target point.
  569. if ((curY >= prevY && curY >= nextY) || (curY <= prevY && curY <= nextY)) {
  570. control1Angle = control2Angle = halfPI;
  571. } else {
  572. control1Angle = atan((curX - prevX) / abs(curY - prevY));
  573. if (prevY < curY) {
  574. control1Angle = PI - control1Angle;
  575. }
  576. control2Angle = atan((nextX - curX) / abs(curY - nextY));
  577. if (nextY < curY) {
  578. control2Angle = PI - control2Angle;
  579. }
  580. }
  581. // Adjust the calculated angles so they point away from each other on the same line
  582. alpha = halfPI - ((control1Angle + control2Angle) % (PI * 2)) / 2;
  583. if (alpha > halfPI) {
  584. alpha -= PI;
  585. }
  586. control1Angle += alpha;
  587. control2Angle += alpha;
  588. // Find the control anchor points from the angles and length
  589. control1X = curX - control1Length * sin(control1Angle);
  590. control1Y = curY + control1Length * cos(control1Angle);
  591. control2X = curX + control2Length * sin(control2Angle);
  592. control2Y = curY + control2Length * cos(control2Angle);
  593. // One last adjustment, make sure that no control anchor point extends vertically past
  594. // its target prev/next point, as that results in curves dipping above or below and
  595. // bending back strangely. If we find this happening we keep the control angle but
  596. // reduce the length of the control line so it stays within bounds.
  597. if ((curY > prevY && control1Y < prevY) || (curY < prevY && control1Y > prevY)) {
  598. control1X += abs(prevY - control1Y) * (control1X - curX) / (control1Y - curY);
  599. control1Y = prevY;
  600. }
  601. if ((curY > nextY && control2Y < nextY) || (curY < nextY && control2Y > nextY)) {
  602. control2X -= abs(nextY - control2Y) * (control2X - curX) / (control2Y - curY);
  603. control2Y = nextY;
  604. }
  605. return {
  606. x1: control1X,
  607. y1: control1Y,
  608. x2: control2X,
  609. y2: control2Y
  610. };
  611. },
  612. /**
  613. * Given coordinates of the points, calculates coordinates of a Bezier curve that goes through them.
  614. * @param dataX x-coordinates of the points.
  615. * @param dataY y-coordinates of the points.
  616. * @param value A value to control the smoothness of the curve.
  617. * @return {Object} Object holding two arrays, for x and y coordinates of the curve.
  618. */
  619. smooth: function(dataX, dataY, value) {
  620. var ln = dataX.length,
  621. prevX, prevY, curX, curY, nextX, nextY, x, y,
  622. smoothX = [],
  623. smoothY = [],
  624. i, anchors;
  625. for (i = 0; i < ln - 1; i++) {
  626. prevX = dataX[i];
  627. prevY = dataY[i];
  628. if (i === 0) {
  629. x = prevX;
  630. y = prevY;
  631. smoothX.push(x);
  632. smoothY.push(y);
  633. if (ln === 1) {
  634. break;
  635. }
  636. }
  637. curX = dataX[i + 1];
  638. curY = dataY[i + 1];
  639. nextX = dataX[i + 2];
  640. nextY = dataY[i + 2];
  641. if (!(Ext.isNumber(nextX) && Ext.isNumber(nextY))) {
  642. smoothX.push(x, curX, curX);
  643. smoothY.push(y, curY, curY);
  644. break;
  645. }
  646. anchors = this.getAnchors(prevX, prevY, curX, curY, nextX, nextY, value);
  647. smoothX.push(x, anchors.x1, curX);
  648. smoothY.push(y, anchors.y1, curY);
  649. x = anchors.x2;
  650. y = anchors.y2;
  651. }
  652. return {
  653. smoothX: smoothX,
  654. smoothY: smoothY
  655. };
  656. },
  657. /**
  658. * @method
  659. * @private
  660. * Work around for iOS.
  661. * Nested 3d-transforms seems to prevent the redraw inside it until some event is fired.
  662. */
  663. beginUpdateIOS: Ext.os.is.iOS ? function() {
  664. this.iosUpdateEl = Ext.getBody().createChild({
  665. //<debug>
  666. 'data-sticky': true,
  667. //</debug>
  668. style: 'position: absolute; top: 0px; bottom: 0px; left: 0px; right: 0px; background: rgba(0,0,0,0.001); z-index: 100000'
  669. });
  670. } : Ext.emptyFn,
  671. endUpdateIOS: function() {
  672. this.iosUpdateEl = Ext.destroy(this.iosUpdateEl);
  673. }
  674. });
  675. /**
  676. * @class Ext.draw.gradient.Gradient
  677. *
  678. * Creates a gradient.
  679. */
  680. Ext.define('Ext.draw.gradient.Gradient', {
  681. requires: [
  682. 'Ext.draw.Color'
  683. ],
  684. isGradient: true,
  685. config: {
  686. /**
  687. * @cfg {Object[]} stops
  688. * Defines the stops of the gradient.
  689. */
  690. stops: []
  691. },
  692. applyStops: function(newStops) {
  693. var stops = [],
  694. ln = newStops.length,
  695. i, stop, color;
  696. for (i = 0; i < ln; i++) {
  697. stop = newStops[i];
  698. color = stop.color;
  699. if (!(color && color.isColor)) {
  700. color = Ext.util.Color.fly(color || Ext.util.Color.NONE);
  701. }
  702. stops.push({
  703. offset: Math.min(1, Math.max(0, 'offset' in stop ? stop.offset : stop.position || 0)),
  704. color: color.toString()
  705. });
  706. }
  707. stops.sort(function(a, b) {
  708. return a.offset - b.offset;
  709. });
  710. return stops;
  711. },
  712. onClassExtended: function(subClass, member) {
  713. if (!member.alias && member.type) {
  714. member.alias = 'gradient.' + member.type;
  715. }
  716. },
  717. constructor: function(config) {
  718. this.initConfig(config);
  719. },
  720. /**
  721. * @method
  722. * @protected
  723. * Generates the gradient for the given context.
  724. * @param {Ext.draw.engine.SvgContext} ctx The context.
  725. * @param {Object} bbox
  726. * @return {CanvasGradient/Ext.draw.engine.SvgContext.Gradient/Ext.util.Color.NONE}
  727. */
  728. generateGradient: Ext.emptyFn
  729. });
  730. /**
  731. * @class Ext.draw.gradient.GradientDefinition
  732. *
  733. * A global map of all gradient configs.
  734. */
  735. Ext.define('Ext.draw.gradient.GradientDefinition', {
  736. singleton: true,
  737. urlStringRe: /^url\(#([\w\-]+)\)$/,
  738. gradients: {},
  739. add: function(gradients) {
  740. var store = this.gradients,
  741. i, n, gradient;
  742. for (i = 0 , n = gradients.length; i < n; i++) {
  743. gradient = gradients[i];
  744. if (Ext.isString(gradient.id)) {
  745. store[gradient.id] = gradient;
  746. }
  747. }
  748. },
  749. get: function(str) {
  750. var store = this.gradients,
  751. match = str.match(this.urlStringRe),
  752. gradient;
  753. if (match && match[1] && (gradient = store[match[1]])) {
  754. return gradient || str;
  755. }
  756. return str;
  757. }
  758. });
  759. /**
  760. * @private
  761. * @class Ext.draw.sprite.AttributeParser
  762. *
  763. * Parsers used for sprite attributes if they are {@link Ext.draw.sprite.AttributeDefinition#normalize normalized}
  764. * (default) when being {@link Ext.draw.sprite.Sprite#setAttributes set}.
  765. *
  766. * Methods of the singleton correpond either to the processor functions themselves or processor factories.
  767. */
  768. Ext.define('Ext.draw.sprite.AttributeParser', {
  769. singleton: true,
  770. attributeRe: /^url\(#([a-zA-Z\-]+)\)$/,
  771. requires: [
  772. 'Ext.draw.Color',
  773. 'Ext.draw.gradient.GradientDefinition'
  774. ],
  775. 'default': Ext.identityFn,
  776. string: function(n) {
  777. return String(n);
  778. },
  779. number: function(n) {
  780. // Numbers as strings will be converted to numbers,
  781. // null will be converted to 0.
  782. if (Ext.isNumber(+n)) {
  783. return n;
  784. }
  785. },
  786. /**
  787. * Normalize angle to the [-180,180) interval.
  788. * @param n Angle in radians.
  789. * @return {Number/undefined} Normalized angle or undefined.
  790. */
  791. angle: function(n) {
  792. if (Ext.isNumber(n)) {
  793. n %= Math.PI * 2;
  794. if (n < -Math.PI) {
  795. n += Math.PI * 2;
  796. } else if (n >= Math.PI) {
  797. n -= Math.PI * 2;
  798. }
  799. return n;
  800. }
  801. },
  802. data: function(n) {
  803. if (Ext.isArray(n)) {
  804. return n.slice();
  805. } else if (n instanceof Float32Array) {
  806. return new Float32Array(n);
  807. }
  808. },
  809. bool: function(n) {
  810. return !!n;
  811. },
  812. color: function(n) {
  813. if (n && n.isColor) {
  814. return n.toString();
  815. } else if (n && n.isGradient) {
  816. return n;
  817. } else if (!n) {
  818. return Ext.util.Color.NONE;
  819. } else if (Ext.isString(n)) {
  820. if (n.substr(0, 3) === 'url') {
  821. n = Ext.draw.gradient.GradientDefinition.get(n);
  822. if (Ext.isString(n)) {
  823. return n;
  824. }
  825. } else {
  826. return Ext.util.Color.fly(n).toString();
  827. }
  828. }
  829. if (n.type === 'linear') {
  830. return Ext.create('Ext.draw.gradient.Linear', n);
  831. } else if (n.type === 'radial') {
  832. return Ext.create('Ext.draw.gradient.Radial', n);
  833. } else if (n.type === 'pattern') {
  834. return Ext.create('Ext.draw.gradient.Pattern', n);
  835. } else {
  836. return Ext.util.Color.NONE;
  837. }
  838. },
  839. limited: function(low, hi) {
  840. return function(n) {
  841. n = +n;
  842. return Ext.isNumber(n) ? Math.min(Math.max(n, low), hi) : undefined;
  843. };
  844. },
  845. limited01: function(n) {
  846. n = +n;
  847. return Ext.isNumber(n) ? Math.min(Math.max(n, 0), 1) : undefined;
  848. },
  849. /**
  850. * Generates a function that checks if a value matches
  851. * one of the given attributes.
  852. * @return {Function}
  853. */
  854. enums: function() {
  855. var enums = {},
  856. args = Array.prototype.slice.call(arguments, 0),
  857. i, ln;
  858. for (i = 0 , ln = args.length; i < ln; i++) {
  859. enums[args[i]] = true;
  860. }
  861. return function(n) {
  862. return n in enums ? n : undefined;
  863. };
  864. }
  865. });
  866. /**
  867. * @private
  868. * Flyweight object to process the attributes of a sprite.
  869. * A single instance of the AttributeDefinition is created per sprite class.
  870. * See `onClassCreated` and `onClassExtended` callbacks
  871. * of the {@link Ext.draw.sprite.Sprite} for more info.
  872. */
  873. Ext.define('Ext.draw.sprite.AttributeDefinition', {
  874. requires: [
  875. 'Ext.draw.sprite.AttributeParser',
  876. 'Ext.draw.sprite.AnimationParser'
  877. ],
  878. config: {
  879. /**
  880. * @cfg {Object} defaults Defines the default values of attributes.
  881. */
  882. defaults: {
  883. $value: {},
  884. lazy: true
  885. },
  886. /**
  887. * @cfg {Object} aliases Defines the alternative names for attributes.
  888. */
  889. aliases: {},
  890. /**
  891. * @cfg {Object} animationProcessors Defines the process used to animate between attributes.
  892. * One doesn't have to define animation processors for sprite attributes that use
  893. * predefined {@link #processors} from the {@link Ext.draw.sprite.AttributeParser} singleton.
  894. * For such attributes matching animation processors from the {@link Ext.draw.sprite.AnimationParser}
  895. * singleton will be used automatically.
  896. * However, if you have a custom processor for an attribute that should support
  897. * animation, you must provide a corresponding animation processor for it here.
  898. * For more information on animation processors please see {@link Ext.draw.sprite.AnimationParser}
  899. * documentation.
  900. */
  901. animationProcessors: {},
  902. /**
  903. * @cfg {Object} processors Defines the preprocessing used on the attributes.
  904. * One can define a custom processor function here or use the name of a predefined
  905. * processor from the {@link Ext.draw.sprite.AttributeParser} singleton.
  906. */
  907. processors: {
  908. // A plus side of lazy initialization is that the 'processors' and 'defaults' will
  909. // only be applied for those sprite classes that are actually instantiated.
  910. $value: {},
  911. lazy: true
  912. },
  913. /**
  914. * @cfg {Object} dirtyTriggers
  915. * @deprecated 6.5.0 Use the {@link #triggers} config instead.
  916. */
  917. dirtyTriggers: {},
  918. /**
  919. * @cfg {Object} triggers Defines which updaters have to be called when an attribute is changed.
  920. * For example, the config below indicates that the 'size' updater
  921. * of a {@link Ext.draw.sprite.Square square} sprite has to be called
  922. * when the 'size' attribute changes.
  923. *
  924. * triggers: {
  925. * size: 'size' // Use comma-separated values here if multiple updaters have to be called.
  926. * } // Note that the order is _not_ guaranteed.
  927. *
  928. * If any of the updaters to be called (triggered by the {@link Ext.draw.sprite.Sprite#setAttributes} call)
  929. * set attributes themselves and those attributes have triggers defined for them,
  930. * then their updaters will be called after all current updaters finish execution.
  931. *
  932. * The updater functions themselves are defined in the {@link #updaters} config,
  933. * aside from the 'canvas' updater, which doesn't have to be defined and acts as a flag,
  934. * indicating that this attribute should be applied to a Canvas context (or whatever emulates it).
  935. * @since 5.1.0
  936. */
  937. triggers: {},
  938. /**
  939. * @cfg {Object} updaters Defines the postprocessing used by the attribute.
  940. * Inside the updater function 'this' refers to the sprite that the attributes belong to.
  941. * In case of an instancing sprite 'this' will refer to the instancing template.
  942. * The two parameters passed to the updater function are the attributes object
  943. * of the sprite or instance, and the names of attributes that triggered this updater call.
  944. *
  945. * The example below shows how the 'size' updater changes other attributes
  946. * of a {@link Ext.draw.sprite.Square square} sprite sprite when its 'size' attribute changes.
  947. *
  948. * updaters: {
  949. * size: function (attr) {
  950. * var size = attr.size;
  951. * this.setAttributes({ // Changes to these attributes will trigger the 'path' updater.
  952. * x: attr.x - size,
  953. * y: attr.y - size,
  954. * height: 2 * size,
  955. * width: 2 * size
  956. * });
  957. * }
  958. * }
  959. */
  960. updaters: {}
  961. },
  962. inheritableStatics: {
  963. /**
  964. * @private
  965. * Processor declaration in the form of 'processorFactory(argument1,argument2,...)'.
  966. * E.g.: {@link Ext.draw.sprite.AttributeParser#enums enums},
  967. * {@link Ext.draw.sprite.AttributeParser#limited limited}.
  968. */
  969. processorFactoryRe: /^(\w+)\(([\w\-,]*)\)$/
  970. },
  971. // The sprite class for which AttributeDefinition instance is created.
  972. spriteClass: null,
  973. constructor: function(config) {
  974. var me = this;
  975. me.initConfig(config);
  976. },
  977. applyDefaults: function(defaults, oldDefaults) {
  978. oldDefaults = Ext.apply(oldDefaults || {}, this.normalize(defaults));
  979. return oldDefaults;
  980. },
  981. applyAliases: function(aliases, oldAliases) {
  982. return Ext.apply(oldAliases || {}, aliases);
  983. },
  984. applyProcessors: function(processors, oldProcessors) {
  985. this.getAnimationProcessors();
  986. // Apply custom animation processors first.
  987. var result = oldProcessors || {},
  988. defaultProcessor = Ext.draw.sprite.AttributeParser,
  989. processorFactoryRe = this.self.processorFactoryRe,
  990. animationProcessors = {},
  991. anyAnimationProcessors, name, match, fn;
  992. for (name in processors) {
  993. fn = processors[name];
  994. if (typeof fn === 'string') {
  995. match = fn.match(processorFactoryRe);
  996. if (match) {
  997. // enums(... , limited(... or something of that nature.
  998. fn = defaultProcessor[match[1]].apply(defaultProcessor, match[2].split(','));
  999. } else if (defaultProcessor[fn]) {
  1000. // Names of animation parsers match the names of attribute parsers.
  1001. animationProcessors[name] = fn;
  1002. anyAnimationProcessors = true;
  1003. fn = defaultProcessor[fn];
  1004. }
  1005. }
  1006. //<debug>
  1007. if (!Ext.isFunction(fn)) {
  1008. Ext.raise(this.spriteClass.$className + ": processor '" + name + "' has not been found.");
  1009. }
  1010. //</debug>
  1011. result[name] = fn;
  1012. }
  1013. if (anyAnimationProcessors) {
  1014. this.setAnimationProcessors(animationProcessors);
  1015. }
  1016. return result;
  1017. },
  1018. applyAnimationProcessors: function(animationProcessors, oldAnimationProcessors) {
  1019. var parser = Ext.draw.sprite.AnimationParser,
  1020. name, item;
  1021. if (!oldAnimationProcessors) {
  1022. oldAnimationProcessors = {};
  1023. }
  1024. for (name in animationProcessors) {
  1025. item = animationProcessors[name];
  1026. if (item === 'none') {
  1027. oldAnimationProcessors[name] = null;
  1028. } else if (Ext.isString(item) && !(name in oldAnimationProcessors)) {
  1029. if (item in parser) {
  1030. // The while loop is used to resolve aliases, e.g. `num: 'number'`,
  1031. // where `number` maps to a parser object or is an alias too.
  1032. while (Ext.isString(parser[item])) {
  1033. item = parser[item];
  1034. }
  1035. oldAnimationProcessors[name] = parser[item];
  1036. }
  1037. } else if (Ext.isObject(item)) {
  1038. oldAnimationProcessors[name] = item;
  1039. }
  1040. }
  1041. return oldAnimationProcessors;
  1042. },
  1043. updateDirtyTriggers: function(dirtyTriggers) {
  1044. this.setTriggers(dirtyTriggers);
  1045. },
  1046. applyTriggers: function(triggers, oldTriggers) {
  1047. if (!oldTriggers) {
  1048. oldTriggers = {};
  1049. }
  1050. for (var name in triggers) {
  1051. oldTriggers[name] = triggers[name].split(',');
  1052. }
  1053. return oldTriggers;
  1054. },
  1055. applyUpdaters: function(updaters, oldUpdaters) {
  1056. return Ext.apply(oldUpdaters || {}, updaters);
  1057. },
  1058. batchedNormalize: function(batchedChanges, keepUnrecognized) {
  1059. if (!batchedChanges) {
  1060. return {};
  1061. }
  1062. var processors = this.getProcessors(),
  1063. aliases = this.getAliases(),
  1064. translation = batchedChanges.translation || batchedChanges.translate,
  1065. normalized = {},
  1066. i, ln, name, val, rotation, scaling, matrix, subVal, split;
  1067. if ('rotation' in batchedChanges) {
  1068. rotation = batchedChanges.rotation;
  1069. } else {
  1070. rotation = ('rotate' in batchedChanges) ? batchedChanges.rotate : undefined;
  1071. }
  1072. if ('scaling' in batchedChanges) {
  1073. scaling = batchedChanges.scaling;
  1074. } else {
  1075. scaling = ('scale' in batchedChanges) ? batchedChanges.scale : undefined;
  1076. }
  1077. if (typeof scaling !== 'undefined') {
  1078. if (Ext.isNumber(scaling)) {
  1079. normalized.scalingX = scaling;
  1080. normalized.scalingY = scaling;
  1081. } else {
  1082. if ('x' in scaling) {
  1083. normalized.scalingX = scaling.x;
  1084. }
  1085. if ('y' in scaling) {
  1086. normalized.scalingY = scaling.y;
  1087. }
  1088. if ('centerX' in scaling) {
  1089. normalized.scalingCenterX = scaling.centerX;
  1090. }
  1091. if ('centerY' in scaling) {
  1092. normalized.scalingCenterY = scaling.centerY;
  1093. }
  1094. }
  1095. }
  1096. if (typeof rotation !== 'undefined') {
  1097. if (Ext.isNumber(rotation)) {
  1098. rotation = Ext.draw.Draw.rad(rotation);
  1099. normalized.rotationRads = rotation;
  1100. } else {
  1101. if ('rads' in rotation) {
  1102. normalized.rotationRads = rotation.rads;
  1103. } else if ('degrees' in rotation) {
  1104. if (Ext.isArray(rotation.degrees)) {
  1105. normalized.rotationRads = Ext.Array.map(rotation.degrees, function(deg) {
  1106. return Ext.draw.Draw.rad(deg);
  1107. });
  1108. } else {
  1109. normalized.rotationRads = Ext.draw.Draw.rad(rotation.degrees);
  1110. }
  1111. }
  1112. if ('centerX' in rotation) {
  1113. normalized.rotationCenterX = rotation.centerX;
  1114. }
  1115. if ('centerY' in rotation) {
  1116. normalized.rotationCenterY = rotation.centerY;
  1117. }
  1118. }
  1119. }
  1120. if (typeof translation !== 'undefined') {
  1121. if ('x' in translation) {
  1122. normalized.translationX = translation.x;
  1123. }
  1124. if ('y' in translation) {
  1125. normalized.translationY = translation.y;
  1126. }
  1127. }
  1128. if ('matrix' in batchedChanges) {
  1129. matrix = Ext.draw.Matrix.create(batchedChanges.matrix);
  1130. split = matrix.split();
  1131. normalized.matrix = matrix;
  1132. normalized.rotationRads = split.rotation;
  1133. normalized.rotationCenterX = 0;
  1134. normalized.rotationCenterY = 0;
  1135. normalized.scalingX = split.scaleX;
  1136. normalized.scalingY = split.scaleY;
  1137. normalized.scalingCenterX = 0;
  1138. normalized.scalingCenterY = 0;
  1139. normalized.translationX = split.translateX;
  1140. normalized.translationY = split.translateY;
  1141. }
  1142. for (name in batchedChanges) {
  1143. val = batchedChanges[name];
  1144. if (typeof val === 'undefined') {
  1145. continue;
  1146. } else if (Ext.isArray(val)) {
  1147. if (name in aliases) {
  1148. name = aliases[name];
  1149. }
  1150. if (name in processors) {
  1151. normalized[name] = [];
  1152. for (i = 0 , ln = val.length; i < ln; i++) {
  1153. subVal = processors[name].call(this, val[i]);
  1154. if (typeof subVal !== 'undefined') {
  1155. normalized[name][i] = subVal;
  1156. }
  1157. }
  1158. } else if (keepUnrecognized) {
  1159. normalized[name] = val;
  1160. }
  1161. } else {
  1162. if (name in aliases) {
  1163. name = aliases[name];
  1164. }
  1165. if (name in processors) {
  1166. val = processors[name].call(this, val);
  1167. if (typeof val !== 'undefined') {
  1168. normalized[name] = val;
  1169. }
  1170. } else if (keepUnrecognized) {
  1171. normalized[name] = val;
  1172. }
  1173. }
  1174. }
  1175. return normalized;
  1176. },
  1177. /**
  1178. * Normalizes the changes given via their processors before they are applied as attributes.
  1179. *
  1180. * @param {Object} changes The changes given.
  1181. * @param {Boolean} keepUnrecognized If 'true', unknown attributes will be passed through as normalized values.
  1182. * @return {Object} The normalized values.
  1183. */
  1184. normalize: function(changes, keepUnrecognized) {
  1185. if (!changes) {
  1186. return {};
  1187. }
  1188. var processors = this.getProcessors(),
  1189. aliases = this.getAliases(),
  1190. translation = changes.translation || changes.translate,
  1191. normalized = {},
  1192. name, val, rotation, scaling, matrix, split;
  1193. if ('rotation' in changes) {
  1194. rotation = changes.rotation;
  1195. } else {
  1196. rotation = ('rotate' in changes) ? changes.rotate : undefined;
  1197. }
  1198. if ('scaling' in changes) {
  1199. scaling = changes.scaling;
  1200. } else {
  1201. scaling = ('scale' in changes) ? changes.scale : undefined;
  1202. }
  1203. if (translation) {
  1204. if ('x' in translation) {
  1205. normalized.translationX = translation.x;
  1206. }
  1207. if ('y' in translation) {
  1208. normalized.translationY = translation.y;
  1209. }
  1210. }
  1211. if (typeof scaling !== 'undefined') {
  1212. if (Ext.isNumber(scaling)) {
  1213. normalized.scalingX = scaling;
  1214. normalized.scalingY = scaling;
  1215. } else {
  1216. if ('x' in scaling) {
  1217. normalized.scalingX = scaling.x;
  1218. }
  1219. if ('y' in scaling) {
  1220. normalized.scalingY = scaling.y;
  1221. }
  1222. if ('centerX' in scaling) {
  1223. normalized.scalingCenterX = scaling.centerX;
  1224. }
  1225. if ('centerY' in scaling) {
  1226. normalized.scalingCenterY = scaling.centerY;
  1227. }
  1228. }
  1229. }
  1230. if (typeof rotation !== 'undefined') {
  1231. if (Ext.isNumber(rotation)) {
  1232. rotation = Ext.draw.Draw.rad(rotation);
  1233. normalized.rotationRads = rotation;
  1234. } else {
  1235. if ('rads' in rotation) {
  1236. normalized.rotationRads = rotation.rads;
  1237. } else if ('degrees' in rotation) {
  1238. normalized.rotationRads = Ext.draw.Draw.rad(rotation.degrees);
  1239. }
  1240. if ('centerX' in rotation) {
  1241. normalized.rotationCenterX = rotation.centerX;
  1242. }
  1243. if ('centerY' in rotation) {
  1244. normalized.rotationCenterY = rotation.centerY;
  1245. }
  1246. }
  1247. }
  1248. if ('matrix' in changes) {
  1249. matrix = Ext.draw.Matrix.create(changes.matrix);
  1250. split = matrix.split();
  1251. // This will NOT update the transformation matrix of a sprite
  1252. // with the given elements. It will attempt to extract the
  1253. // individual transformation attributes from the transformation matrix
  1254. // elements provided. Then the extracted attributes will be used by
  1255. // the sprite's 'applyTransformations' method to calculate
  1256. // the transformation matrix of the sprite.
  1257. // It's not possible to recover all the information from the given
  1258. // transformation matrix elements. Shearing and centers of rotation
  1259. // and scaling are not recovered.
  1260. // Ideally, this should work like sprite.transform([elements], true),
  1261. // i.e. update the transformation matrix of a sprite directly,
  1262. // without attempting to update sprite's transformation attributes.
  1263. // But we are not changing the behavior (just yet) for compatibility
  1264. // reasons.
  1265. normalized.matrix = matrix;
  1266. normalized.rotationRads = split.rotation;
  1267. normalized.rotationCenterX = 0;
  1268. normalized.rotationCenterY = 0;
  1269. normalized.scalingX = split.scaleX;
  1270. normalized.scalingY = split.scaleY;
  1271. normalized.scalingCenterX = 0;
  1272. normalized.scalingCenterY = 0;
  1273. normalized.translationX = split.translateX;
  1274. normalized.translationY = split.translateY;
  1275. }
  1276. for (name in changes) {
  1277. val = changes[name];
  1278. if (typeof val === 'undefined') {
  1279. continue;
  1280. }
  1281. if (name in aliases) {
  1282. name = aliases[name];
  1283. }
  1284. if (name in processors) {
  1285. val = processors[name].call(this, val);
  1286. if (typeof val !== 'undefined') {
  1287. normalized[name] = val;
  1288. }
  1289. } else if (keepUnrecognized) {
  1290. normalized[name] = val;
  1291. }
  1292. }
  1293. return normalized;
  1294. },
  1295. setBypassingNormalization: function(attr, modifierStack, changes) {
  1296. return modifierStack.pushDown(attr, changes);
  1297. },
  1298. set: function(attr, modifierStack, changes) {
  1299. changes = this.normalize(changes);
  1300. return this.setBypassingNormalization(attr, modifierStack, changes);
  1301. }
  1302. });
  1303. /**
  1304. * Ext.draw.Matix is a utility class used to calculate
  1305. * [affine transformation](http://en.wikipedia.org/wiki/Affine_transformation) matrix.
  1306. * The matrix class is used to apply transformations to existing
  1307. * {@link Ext.draw.sprite.Sprite sprites} using a number of convenience transform
  1308. * methods.
  1309. *
  1310. * Transformations configured directly on a sprite are processed in the following order:
  1311. * scaling, rotation, and translation. The matrix class offers additional flexibility.
  1312. * Once a sprite is created, you can use the matrix class's transform methods as many
  1313. * times as needed and in any order you choose.
  1314. *
  1315. * To demonstrate, we'll start with a simple {@link Ext.draw.sprite.Rect rect} sprite
  1316. * with the intent of rotating it 180 degrees with the bottom right corner being the
  1317. * center of rotation. To begin, let's look at the initial, untransformed sprite:
  1318. *
  1319. * @example
  1320. * var drawContainer = new Ext.draw.Container({
  1321. * renderTo: Ext.getBody(),
  1322. * width: 380,
  1323. * height: 380,
  1324. * sprites: [{
  1325. * type: 'rect',
  1326. * width: 100,
  1327. * height: 100,
  1328. * fillStyle: 'red'
  1329. * }]
  1330. * });
  1331. *
  1332. * Next, we'll use the {@link #rotate} and {@link #translate} methods from our matrix
  1333. * class to position the rect sprite.
  1334. *
  1335. * @example
  1336. * var drawContainer = new Ext.draw.Container({
  1337. * renderTo: Ext.getBody(),
  1338. * width: 380,
  1339. * height: 380,
  1340. * sprites: [{
  1341. * type: 'rect',
  1342. * width: 100,
  1343. * height: 100,
  1344. * fillStyle: 'red'
  1345. * }]
  1346. * });
  1347. *
  1348. * var main = drawContainer.getSurface();
  1349. * var rect = main.getItems()[0];
  1350. *
  1351. * var m = new Ext.draw.Matrix().translate(100, 100).
  1352. * rotate(Math.PI).
  1353. * translate(-100, - 100);
  1354. *
  1355. * rect.setTransform(m);
  1356. * main.renderFrame();
  1357. *
  1358. * In the previous example we perform the following steps in order to achieve our
  1359. * desired rotated output:
  1360. *
  1361. * - translate the rect to the right and down by 100
  1362. * - rotate by 180 degrees
  1363. * - translate the rect to the right and down by 100
  1364. *
  1365. * **Note:** A couple of things to note at this stage; 1) the rotation center point is
  1366. * the upper left corner of the sprite by default and 2) with transformations, the
  1367. * sprite itself isn't transformed, but rather the entire coordinate plane of the sprite
  1368. * is transformed. The coordinate plane itself is translated by 100 and then rotated
  1369. * 180 degrees. And that is why in the third step we translate the sprite using
  1370. * negative values. Translating by -100 in the third step results in the sprite
  1371. * visually moving to the right and down within the draw container.
  1372. *
  1373. * Fortunately there is a shortcut we can apply using two optional params of the rotate
  1374. * method allowing us to specify the center point of rotation:
  1375. *
  1376. * @example
  1377. * var drawContainer = new Ext.draw.Container({
  1378. * renderTo: Ext.getBody(),
  1379. * width: 380,
  1380. * height: 380,
  1381. * sprites: [{
  1382. * type: 'rect',
  1383. * width: 100,
  1384. * height: 100,
  1385. * fillStyle: 'red'
  1386. * }]
  1387. * });
  1388. *
  1389. * var main = drawContainer.getSurface();
  1390. * var rect = main.getItems()[0];
  1391. *
  1392. * var m = new Ext.draw.Matrix().rotate(Math.PI, 100, 100);
  1393. *
  1394. * rect.setTransform(m);
  1395. * main.renderFrame();
  1396. *
  1397. *
  1398. * This class is compatible with
  1399. * [SVGMatrix](http://www.w3.org/TR/SVG11/coords.html#InterfaceSVGMatrix) except:
  1400. *
  1401. * 1. Ext.draw.Matrix is not read only
  1402. * 2. Using Number as its values rather than floats
  1403. *
  1404. * Using this class helps to reduce the severe numeric
  1405. * [problem with HTML Canvas and SVG transformation](http://stackoverflow.com/questions/8784405/large-numbers-in-html-canvas-translate-result-in-strange-behavior)
  1406. *
  1407. * Additionally, there's no way to get the current transformation matrix
  1408. * [in Canvas](http://stackoverflow.com/questions/7395813/html5-canvas-get-transform-matrix).
  1409. */
  1410. Ext.define('Ext.draw.Matrix', {
  1411. isMatrix: true,
  1412. statics: {
  1413. /**
  1414. * @static
  1415. * Return the affine matrix that transform two points (x0, y0) and (x1, y1) to (x0p, y0p) and (x1p, y1p)
  1416. * @param {Number} x0
  1417. * @param {Number} y0
  1418. * @param {Number} x1
  1419. * @param {Number} y1
  1420. * @param {Number} x0p
  1421. * @param {Number} y0p
  1422. * @param {Number} x1p
  1423. * @param {Number} y1p
  1424. */
  1425. createAffineMatrixFromTwoPair: function(x0, y0, x1, y1, x0p, y0p, x1p, y1p) {
  1426. var dx = x1 - x0,
  1427. dy = y1 - y0,
  1428. dxp = x1p - x0p,
  1429. dyp = y1p - y0p,
  1430. r = 1 / (dx * dx + dy * dy),
  1431. a = dx * dxp + dy * dyp,
  1432. b = dxp * dy - dx * dyp,
  1433. c = -a * x0 - b * y0,
  1434. f = b * x0 - a * y0;
  1435. return new this(a * r, -b * r, b * r, a * r, c * r + x0p, f * r + y0p);
  1436. },
  1437. /**
  1438. * @static
  1439. * Return the affine matrix that transform two points (x0, y0) and (x1, y1) to (x0p, y0p) and (x1p, y1p)
  1440. * @param {Number} x0
  1441. * @param {Number} y0
  1442. * @param {Number} x1
  1443. * @param {Number} y1
  1444. * @param {Number} x0p
  1445. * @param {Number} y0p
  1446. * @param {Number} x1p
  1447. * @param {Number} y1p
  1448. */
  1449. createPanZoomFromTwoPair: function(x0, y0, x1, y1, x0p, y0p, x1p, y1p) {
  1450. if (arguments.length === 2) {
  1451. return this.createPanZoomFromTwoPair.apply(this, x0.concat(y0));
  1452. }
  1453. var dx = x1 - x0,
  1454. dy = y1 - y0,
  1455. cx = (x0 + x1) * 0.5,
  1456. cy = (y0 + y1) * 0.5,
  1457. dxp = x1p - x0p,
  1458. dyp = y1p - y0p,
  1459. cxp = (x0p + x1p) * 0.5,
  1460. cyp = (y0p + y1p) * 0.5,
  1461. r = dx * dx + dy * dy,
  1462. rp = dxp * dxp + dyp * dyp,
  1463. scale = Math.sqrt(rp / r);
  1464. return new this(scale, 0, 0, scale, cxp - scale * cx, cyp - scale * cy);
  1465. },
  1466. /**
  1467. * @method
  1468. * @static
  1469. * Create a flyweight to wrap the given array.
  1470. * The flyweight will directly refer the object and the elements can be changed by other methods.
  1471. *
  1472. * Do not hold the instance of flyweight matrix.
  1473. *
  1474. * @param {Array} elements
  1475. * @return {Ext.draw.Matrix}
  1476. */
  1477. fly: (function() {
  1478. var flyMatrix = null,
  1479. simplefly = function(elements) {
  1480. flyMatrix.elements = elements;
  1481. return flyMatrix;
  1482. };
  1483. return function(elements) {
  1484. if (!flyMatrix) {
  1485. flyMatrix = new Ext.draw.Matrix();
  1486. }
  1487. flyMatrix.elements = elements;
  1488. Ext.draw.Matrix.fly = simplefly;
  1489. return flyMatrix;
  1490. };
  1491. })(),
  1492. /**
  1493. * @static
  1494. * Create a matrix from `mat`. If `mat` is already a matrix, returns it.
  1495. * @param {Mixed} mat
  1496. * @return {Ext.draw.Matrix}
  1497. */
  1498. create: function(mat) {
  1499. if (mat instanceof this) {
  1500. return mat;
  1501. }
  1502. return new this(mat);
  1503. }
  1504. },
  1505. /**
  1506. * Create an affine transform matrix.
  1507. *
  1508. * @param {Number} xx Coefficient from x to x
  1509. * @param {Number} xy Coefficient from x to y
  1510. * @param {Number} yx Coefficient from y to x
  1511. * @param {Number} yy Coefficient from y to y
  1512. * @param {Number} dx Offset of x
  1513. * @param {Number} dy Offset of y
  1514. */
  1515. constructor: function(xx, xy, yx, yy, dx, dy) {
  1516. if (xx && xx.length === 6) {
  1517. this.elements = xx.slice();
  1518. } else if (xx !== undefined) {
  1519. this.elements = [
  1520. xx,
  1521. xy,
  1522. yx,
  1523. yy,
  1524. dx,
  1525. dy
  1526. ];
  1527. } else {
  1528. this.elements = [
  1529. 1,
  1530. 0,
  1531. 0,
  1532. 1,
  1533. 0,
  1534. 0
  1535. ];
  1536. }
  1537. },
  1538. /**
  1539. * Prepend a matrix onto the current.
  1540. *
  1541. * __Note:__ The given transform will come after the current one.
  1542. *
  1543. * @param {Number} xx Coefficient from x to x.
  1544. * @param {Number} xy Coefficient from x to y.
  1545. * @param {Number} yx Coefficient from y to x.
  1546. * @param {Number} yy Coefficient from y to y.
  1547. * @param {Number} dx Offset of x.
  1548. * @param {Number} dy Offset of y.
  1549. * @return {Ext.draw.Matrix} this
  1550. */
  1551. prepend: function(xx, xy, yx, yy, dx, dy) {
  1552. var elements = this.elements,
  1553. xx0 = elements[0],
  1554. xy0 = elements[1],
  1555. yx0 = elements[2],
  1556. yy0 = elements[3],
  1557. dx0 = elements[4],
  1558. dy0 = elements[5];
  1559. elements[0] = xx * xx0 + yx * xy0;
  1560. elements[1] = xy * xx0 + yy * xy0;
  1561. elements[2] = xx * yx0 + yx * yy0;
  1562. elements[3] = xy * yx0 + yy * yy0;
  1563. elements[4] = xx * dx0 + yx * dy0 + dx;
  1564. elements[5] = xy * dx0 + yy * dy0 + dy;
  1565. return this;
  1566. },
  1567. /**
  1568. * Prepend a matrix onto the current.
  1569. *
  1570. * __Note:__ The given transform will come after the current one.
  1571. * @param {Ext.draw.Matrix} matrix
  1572. * @return {Ext.draw.Matrix} this
  1573. */
  1574. prependMatrix: function(matrix) {
  1575. return this.prepend.apply(this, matrix.elements);
  1576. },
  1577. /**
  1578. * Postpend a matrix onto the current.
  1579. *
  1580. * __Note:__ The given transform will come before the current one.
  1581. *
  1582. * @param {Number} xx Coefficient from x to x.
  1583. * @param {Number} xy Coefficient from x to y.
  1584. * @param {Number} yx Coefficient from y to x.
  1585. * @param {Number} yy Coefficient from y to y.
  1586. * @param {Number} dx Offset of x.
  1587. * @param {Number} dy Offset of y.
  1588. * @return {Ext.draw.Matrix} this
  1589. */
  1590. append: function(xx, xy, yx, yy, dx, dy) {
  1591. var elements = this.elements,
  1592. xx0 = elements[0],
  1593. xy0 = elements[1],
  1594. yx0 = elements[2],
  1595. yy0 = elements[3],
  1596. dx0 = elements[4],
  1597. dy0 = elements[5];
  1598. elements[0] = xx * xx0 + xy * yx0;
  1599. elements[1] = xx * xy0 + xy * yy0;
  1600. elements[2] = yx * xx0 + yy * yx0;
  1601. elements[3] = yx * xy0 + yy * yy0;
  1602. elements[4] = dx * xx0 + dy * yx0 + dx0;
  1603. elements[5] = dx * xy0 + dy * yy0 + dy0;
  1604. return this;
  1605. },
  1606. /**
  1607. * Postpend a matrix onto the current.
  1608. *
  1609. * __Note:__ The given transform will come before the current one.
  1610. *
  1611. * @param {Ext.draw.Matrix} matrix
  1612. * @return {Ext.draw.Matrix} this
  1613. */
  1614. appendMatrix: function(matrix) {
  1615. return this.append.apply(this, matrix.elements);
  1616. },
  1617. /**
  1618. * Set the elements of a Matrix
  1619. * @param {Number} xx
  1620. * @param {Number} xy
  1621. * @param {Number} yx
  1622. * @param {Number} yy
  1623. * @param {Number} dx
  1624. * @param {Number} dy
  1625. * @return {Ext.draw.Matrix} this
  1626. */
  1627. set: function(xx, xy, yx, yy, dx, dy) {
  1628. var elements = this.elements;
  1629. elements[0] = xx;
  1630. elements[1] = xy;
  1631. elements[2] = yx;
  1632. elements[3] = yy;
  1633. elements[4] = dx;
  1634. elements[5] = dy;
  1635. return this;
  1636. },
  1637. /**
  1638. * Return a new matrix represents the opposite transformation of the current one.
  1639. *
  1640. * @param {Ext.draw.Matrix} [target] A target matrix. If present, it will receive
  1641. * the result of inversion to avoid creating a new object.
  1642. *
  1643. * @return {Ext.draw.Matrix}
  1644. */
  1645. inverse: function(target) {
  1646. var elements = this.elements,
  1647. a = elements[0],
  1648. b = elements[1],
  1649. c = elements[2],
  1650. d = elements[3],
  1651. e = elements[4],
  1652. f = elements[5],
  1653. rDim = 1 / (a * d - b * c);
  1654. a *= rDim;
  1655. b *= rDim;
  1656. c *= rDim;
  1657. d *= rDim;
  1658. if (target) {
  1659. target.set(d, -b, -c, a, c * f - d * e, b * e - a * f);
  1660. return target;
  1661. } else {
  1662. return new Ext.draw.Matrix(d, -b, -c, a, c * f - d * e, b * e - a * f);
  1663. }
  1664. },
  1665. /**
  1666. * Translate the matrix.
  1667. *
  1668. * @param {Number} x
  1669. * @param {Number} y
  1670. * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
  1671. * @return {Ext.draw.Matrix} this
  1672. */
  1673. translate: function(x, y, prepend) {
  1674. if (prepend) {
  1675. return this.prepend(1, 0, 0, 1, x, y);
  1676. } else {
  1677. return this.append(1, 0, 0, 1, x, y);
  1678. }
  1679. },
  1680. /**
  1681. * Scale the matrix.
  1682. *
  1683. * @param {Number} sx
  1684. * @param {Number} sy
  1685. * @param {Number} scx
  1686. * @param {Number} scy
  1687. * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
  1688. * @return {Ext.draw.Matrix} this
  1689. */
  1690. scale: function(sx, sy, scx, scy, prepend) {
  1691. var me = this;
  1692. // null or undefined
  1693. if (sy == null) {
  1694. sy = sx;
  1695. }
  1696. if (scx === undefined) {
  1697. scx = 0;
  1698. }
  1699. if (scy === undefined) {
  1700. scy = 0;
  1701. }
  1702. if (prepend) {
  1703. return me.prepend(sx, 0, 0, sy, scx - scx * sx, scy - scy * sy);
  1704. } else {
  1705. return me.append(sx, 0, 0, sy, scx - scx * sx, scy - scy * sy);
  1706. }
  1707. },
  1708. /**
  1709. * Rotate the matrix.
  1710. *
  1711. * @param {Number} angle Radians to rotate
  1712. * @param {Number|null} rcx Center of rotation.
  1713. * @param {Number|null} rcy Center of rotation.
  1714. * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
  1715. * @return {Ext.draw.Matrix} this
  1716. */
  1717. rotate: function(angle, rcx, rcy, prepend) {
  1718. var me = this,
  1719. cos = Math.cos(angle),
  1720. sin = Math.sin(angle);
  1721. rcx = rcx || 0;
  1722. rcy = rcy || 0;
  1723. if (prepend) {
  1724. return me.prepend(cos, sin, -sin, cos, rcx - cos * rcx + rcy * sin, rcy - cos * rcy - rcx * sin);
  1725. } else {
  1726. return me.append(cos, sin, -sin, cos, rcx - cos * rcx + rcy * sin, rcy - cos * rcy - rcx * sin);
  1727. }
  1728. },
  1729. /**
  1730. * Rotate the matrix by the angle of a vector.
  1731. *
  1732. * @param {Number} x
  1733. * @param {Number} y
  1734. * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
  1735. * @return {Ext.draw.Matrix} this
  1736. */
  1737. rotateFromVector: function(x, y, prepend) {
  1738. var me = this,
  1739. d = Math.sqrt(x * x + y * y),
  1740. cos = x / d,
  1741. sin = y / d;
  1742. if (prepend) {
  1743. return me.prepend(cos, sin, -sin, cos, 0, 0);
  1744. } else {
  1745. return me.append(cos, sin, -sin, cos, 0, 0);
  1746. }
  1747. },
  1748. /**
  1749. * Clone this matrix.
  1750. * @return {Ext.draw.Matrix}
  1751. */
  1752. clone: function() {
  1753. return new Ext.draw.Matrix(this.elements);
  1754. },
  1755. /**
  1756. * Horizontally flip the matrix
  1757. * @return {Ext.draw.Matrix} this
  1758. */
  1759. flipX: function() {
  1760. return this.append(-1, 0, 0, 1, 0, 0);
  1761. },
  1762. /**
  1763. * Vertically flip the matrix
  1764. * @return {Ext.draw.Matrix} this
  1765. */
  1766. flipY: function() {
  1767. return this.append(1, 0, 0, -1, 0, 0);
  1768. },
  1769. /**
  1770. * Skew the matrix
  1771. * @param {Number} angle
  1772. * @return {Ext.draw.Matrix} this
  1773. */
  1774. skewX: function(angle) {
  1775. return this.append(1, 0, Math.tan(angle), 1, 0, 0);
  1776. },
  1777. /**
  1778. * Skew the matrix
  1779. * @param {Number} angle
  1780. * @return {Ext.draw.Matrix} this
  1781. */
  1782. skewY: function(angle) {
  1783. return this.append(1, Math.tan(angle), 0, 1, 0, 0);
  1784. },
  1785. /**
  1786. * Shear the matrix along the x-axis.
  1787. * @param factor The horizontal shear factor.
  1788. * @return {Ext.draw.Matrix} this
  1789. */
  1790. shearX: function(factor) {
  1791. return this.append(1, 0, factor, 1, 0, 0);
  1792. },
  1793. /**
  1794. * Shear the matrix along the y-axis.
  1795. * @param factor The vertical shear factor.
  1796. * @return {Ext.draw.Matrix} this
  1797. */
  1798. shearY: function(factor) {
  1799. return this.append(1, factor, 0, 1, 0, 0);
  1800. },
  1801. /**
  1802. * Reset the matrix to identical.
  1803. * @return {Ext.draw.Matrix} this
  1804. */
  1805. reset: function() {
  1806. return this.set(1, 0, 0, 1, 0, 0);
  1807. },
  1808. /**
  1809. * @private
  1810. * Split Matrix to `{{devicePixelRatio,c,0},{b,devicePixelRatio,0},{0,0,1}}.{{xx,0,dx},{0,yy,dy},{0,0,1}}`
  1811. * @return {Object} Object with b,c,d=devicePixelRatio,xx,yy,dx,dy
  1812. */
  1813. precisionCompensate: function(devicePixelRatio, comp) {
  1814. var elements = this.elements,
  1815. x2x = elements[0],
  1816. x2y = elements[1],
  1817. y2x = elements[2],
  1818. y2y = elements[3],
  1819. newDx = elements[4],
  1820. newDy = elements[5],
  1821. r = x2y * y2x - x2x * y2y;
  1822. comp.b = devicePixelRatio * x2y / x2x;
  1823. comp.c = devicePixelRatio * y2x / y2y;
  1824. comp.d = devicePixelRatio;
  1825. comp.xx = x2x / devicePixelRatio;
  1826. comp.yy = y2y / devicePixelRatio;
  1827. comp.dx = (newDy * x2x * y2x - newDx * x2x * y2y) / r / devicePixelRatio;
  1828. comp.dy = (newDx * x2y * y2y - newDy * x2x * y2y) / r / devicePixelRatio;
  1829. },
  1830. /**
  1831. * @private
  1832. * Split Matrix to `{{1,c,0},{b,d,0},{0,0,1}}.{{xx,0,dx},{0,xx,dy},{0,0,1}}`
  1833. * @return {Object} Object with b,c,d,xx,yy=xx,dx,dy
  1834. */
  1835. precisionCompensateRect: function(devicePixelRatio, comp) {
  1836. var elements = this.elements,
  1837. x2x = elements[0],
  1838. x2y = elements[1],
  1839. y2x = elements[2],
  1840. y2y = elements[3],
  1841. newDx = elements[4],
  1842. newDy = elements[5],
  1843. yxOnXx = y2x / x2x;
  1844. comp.b = devicePixelRatio * x2y / x2x;
  1845. comp.c = devicePixelRatio * yxOnXx;
  1846. comp.d = devicePixelRatio * y2y / x2x;
  1847. comp.xx = x2x / devicePixelRatio;
  1848. comp.yy = x2x / devicePixelRatio;
  1849. comp.dx = (newDy * y2x - newDx * y2y) / (x2y * yxOnXx - y2y) / devicePixelRatio;
  1850. comp.dy = -(newDy * x2x - newDx * x2y) / (x2y * yxOnXx - y2y) / devicePixelRatio;
  1851. },
  1852. /**
  1853. * Transform point returning the x component of the result.
  1854. * @param {Number} x
  1855. * @param {Number} y
  1856. * @return {Number} x component of the result.
  1857. */
  1858. x: function(x, y) {
  1859. var elements = this.elements;
  1860. return x * elements[0] + y * elements[2] + elements[4];
  1861. },
  1862. /**
  1863. * Transform point returning the y component of the result.
  1864. * @param {Number} x
  1865. * @param {Number} y
  1866. * @return {Number} y component of the result.
  1867. */
  1868. y: function(x, y) {
  1869. var elements = this.elements;
  1870. return x * elements[1] + y * elements[3] + elements[5];
  1871. },
  1872. /**
  1873. * @private
  1874. * @param {Number} i
  1875. * @param {Number} j
  1876. * @return {String}
  1877. */
  1878. get: function(i, j) {
  1879. return +this.elements[i + j * 2].toFixed(4);
  1880. },
  1881. /**
  1882. * Transform a point to a new array.
  1883. * @param {Array} point
  1884. * @return {Array}
  1885. */
  1886. transformPoint: function(point) {
  1887. var elements = this.elements,
  1888. x, y;
  1889. if (point.isPoint) {
  1890. x = point.x;
  1891. y = point.y;
  1892. } else {
  1893. x = point[0];
  1894. y = point[1];
  1895. }
  1896. return [
  1897. x * elements[0] + y * elements[2] + elements[4],
  1898. x * elements[1] + y * elements[3] + elements[5]
  1899. ];
  1900. },
  1901. /**
  1902. * @param {Object} bbox Given as `{x: Number, y: Number, width: Number, height: Number}`.
  1903. * @param {Number} [radius]
  1904. * @param {Object} [target] Optional target object to recieve the result.
  1905. * Recommended to use it for better gc.
  1906. *
  1907. * @return {Object} Object with x, y, width and height.
  1908. */
  1909. transformBBox: function(bbox, radius, target) {
  1910. var elements = this.elements,
  1911. l = bbox.x,
  1912. t = bbox.y,
  1913. w0 = bbox.width * 0.5,
  1914. h0 = bbox.height * 0.5,
  1915. xx = elements[0],
  1916. xy = elements[1],
  1917. yx = elements[2],
  1918. yy = elements[3],
  1919. cx = l + w0,
  1920. cy = t + h0,
  1921. w, h, scales;
  1922. if (radius) {
  1923. w0 -= radius;
  1924. h0 -= radius;
  1925. scales = [
  1926. Math.sqrt(elements[0] * elements[0] + elements[2] * elements[2]),
  1927. Math.sqrt(elements[1] * elements[1] + elements[3] * elements[3])
  1928. ];
  1929. w = Math.abs(w0 * xx) + Math.abs(h0 * yx) + Math.abs(scales[0] * radius);
  1930. h = Math.abs(w0 * xy) + Math.abs(h0 * yy) + Math.abs(scales[1] * radius);
  1931. } else {
  1932. w = Math.abs(w0 * xx) + Math.abs(h0 * yx);
  1933. h = Math.abs(w0 * xy) + Math.abs(h0 * yy);
  1934. }
  1935. if (!target) {
  1936. target = {};
  1937. }
  1938. target.x = cx * xx + cy * yx + elements[4] - w;
  1939. target.y = cx * xy + cy * yy + elements[5] - h;
  1940. target.width = w + w;
  1941. target.height = h + h;
  1942. return target;
  1943. },
  1944. /**
  1945. * Transform a list for points.
  1946. *
  1947. * __Note:__ will change the original list but not points inside it.
  1948. * @param {Array} list
  1949. * @return {Array} list
  1950. */
  1951. transformList: function(list) {
  1952. var elements = this.elements,
  1953. xx = elements[0],
  1954. yx = elements[2],
  1955. dx = elements[4],
  1956. xy = elements[1],
  1957. yy = elements[3],
  1958. dy = elements[5],
  1959. ln = list.length,
  1960. p, i;
  1961. for (i = 0; i < ln; i++) {
  1962. p = list[i];
  1963. list[i] = [
  1964. p[0] * xx + p[1] * yx + dx,
  1965. p[0] * xy + p[1] * yy + dy
  1966. ];
  1967. }
  1968. return list;
  1969. },
  1970. /**
  1971. * Determines whether this matrix is an identity matrix (no transform).
  1972. * @return {Boolean}
  1973. */
  1974. isIdentity: function() {
  1975. var elements = this.elements;
  1976. return elements[0] === 1 && elements[1] === 0 && elements[2] === 0 && elements[3] === 1 && elements[4] === 0 && elements[5] === 0;
  1977. },
  1978. /**
  1979. * Determines if this matrix has the same values as another matrix.
  1980. * @param {Ext.draw.Matrix} matrix A maxtrix or array of its elements.
  1981. * @return {Boolean}
  1982. */
  1983. isEqual: function(matrix) {
  1984. var elements = matrix && matrix.isMatrix ? matrix.elements : matrix,
  1985. myElements = this.elements;
  1986. return myElements[0] === elements[0] && myElements[1] === elements[1] && myElements[2] === elements[2] && myElements[3] === elements[3] && myElements[4] === elements[4] && myElements[5] === elements[5];
  1987. },
  1988. /**
  1989. * @deprecated 6.0.1 This method is deprecated.
  1990. * Determines if this matrix has the same values as another matrix.
  1991. * @param {Ext.draw.Matrix} matrix
  1992. * @return {Boolean}
  1993. */
  1994. equals: function(matrix) {
  1995. return this.isEqual(matrix);
  1996. },
  1997. /**
  1998. * Create an array of elements by horizontal order (xx,yx,dx,yx,yy,dy).
  1999. * @return {Array}
  2000. */
  2001. toArray: function() {
  2002. var elements = this.elements;
  2003. return [
  2004. elements[0],
  2005. elements[2],
  2006. elements[4],
  2007. elements[1],
  2008. elements[3],
  2009. elements[5]
  2010. ];
  2011. },
  2012. /**
  2013. * Create an array of elements by vertical order (xx,xy,yx,yy,dx,dy).
  2014. * @return {Array|String}
  2015. */
  2016. toVerticalArray: function() {
  2017. return this.elements.slice();
  2018. },
  2019. /**
  2020. * Get an array of elements.
  2021. * The numbers are rounded to keep only 4 decimals.
  2022. * @return {Array}
  2023. */
  2024. toString: function() {
  2025. var me = this;
  2026. return [
  2027. me.get(0, 0),
  2028. me.get(0, 1),
  2029. me.get(1, 0),
  2030. me.get(1, 1),
  2031. me.get(2, 0),
  2032. me.get(2, 1)
  2033. ].join(',');
  2034. },
  2035. /**
  2036. * Apply the matrix to a drawing context.
  2037. * @param {Object} ctx
  2038. * @return {Ext.draw.Matrix} this
  2039. */
  2040. toContext: function(ctx) {
  2041. ctx.transform.apply(ctx, this.elements);
  2042. return this;
  2043. },
  2044. /**
  2045. * Return a string that can be used as transform attribute in SVG.
  2046. * @return {String}
  2047. */
  2048. toSvg: function() {
  2049. var elements = this.elements;
  2050. // The reason why we cannot use `.join` is the `1e5` form is not accepted in svg.
  2051. return "matrix(" + elements[0].toFixed(9) + ',' + elements[1].toFixed(9) + ',' + elements[2].toFixed(9) + ',' + elements[3].toFixed(9) + ',' + elements[4].toFixed(9) + ',' + elements[5].toFixed(9) + ")";
  2052. },
  2053. /**
  2054. * Get the x scale of the matrix.
  2055. * @return {Number}
  2056. */
  2057. getScaleX: function() {
  2058. var elements = this.elements;
  2059. return Math.sqrt(elements[0] * elements[0] + elements[2] * elements[2]);
  2060. },
  2061. /**
  2062. * Get the y scale of the matrix.
  2063. * @return {Number}
  2064. */
  2065. getScaleY: function() {
  2066. var elements = this.elements;
  2067. return Math.sqrt(elements[1] * elements[1] + elements[3] * elements[3]);
  2068. },
  2069. /**
  2070. * Get x-to-x component of the matrix
  2071. * @return {Number}
  2072. */
  2073. getXX: function() {
  2074. return this.elements[0];
  2075. },
  2076. /**
  2077. * Get x-to-y component of the matrix.
  2078. * @return {Number}
  2079. */
  2080. getXY: function() {
  2081. return this.elements[1];
  2082. },
  2083. /**
  2084. * Get y-to-x component of the matrix.
  2085. * @return {Number}
  2086. */
  2087. getYX: function() {
  2088. return this.elements[2];
  2089. },
  2090. /**
  2091. * Get y-to-y component of the matrix.
  2092. * @return {Number}
  2093. */
  2094. getYY: function() {
  2095. return this.elements[3];
  2096. },
  2097. /**
  2098. * Get offset x component of the matrix.
  2099. * @return {Number}
  2100. */
  2101. getDX: function() {
  2102. return this.elements[4];
  2103. },
  2104. /**
  2105. * Get offset y component of the matrix.
  2106. * @return {Number}
  2107. */
  2108. getDY: function() {
  2109. return this.elements[5];
  2110. },
  2111. /**
  2112. * Splits this transformation matrix into Scale, Rotate, Translate components,
  2113. * assuming it was produced by applying transformations in that order.
  2114. * @return {Object}
  2115. */
  2116. split: function() {
  2117. var el = this.elements,
  2118. xx = el[0],
  2119. xy = el[1],
  2120. yy = el[3],
  2121. out = {
  2122. translateX: el[4],
  2123. translateY: el[5]
  2124. };
  2125. out.rotate = out.rotation = Math.atan2(xy, xx);
  2126. out.scaleX = xx / Math.cos(out.rotate);
  2127. out.scaleY = yy / xx * out.scaleX;
  2128. return out;
  2129. }
  2130. }, function() {
  2131. function registerName(properties, name, i) {
  2132. properties[name] = {
  2133. get: function() {
  2134. return this.elements[i];
  2135. },
  2136. set: function(val) {
  2137. this.elements[i] = val;
  2138. }
  2139. };
  2140. }
  2141. // Compatibility with SVGMatrix.
  2142. if (Object.defineProperties) {
  2143. var properties = {};
  2144. /**
  2145. * @property {Number} a Get x-to-x component of the matrix. Avoid using it for performance consideration.
  2146. * Use {@link #getXX} instead.
  2147. */
  2148. registerName(properties, 'a', 0);
  2149. registerName(properties, 'b', 1);
  2150. registerName(properties, 'c', 2);
  2151. registerName(properties, 'd', 3);
  2152. registerName(properties, 'e', 4);
  2153. registerName(properties, 'f', 5);
  2154. Object.defineProperties(this.prototype, properties);
  2155. }
  2156. /**
  2157. * Performs matrix multiplication. This matrix is post-multiplied by another matrix.
  2158. *
  2159. * __Note:__ The given transform will come before the current one.
  2160. *
  2161. * @method
  2162. * @param {Ext.draw.Matrix} matrix
  2163. * @return {Ext.draw.Matrix} this
  2164. */
  2165. this.prototype.multiply = this.prototype.appendMatrix;
  2166. });
  2167. /**
  2168. * @class Ext.draw.modifier.Modifier
  2169. *
  2170. * Each sprite has a stack of modifiers. The resulting attributes of sprite is
  2171. * the content of the stack top. When setting attributes to a sprite,
  2172. * changes will be pushed-down though the stack of modifiers and pop-back the
  2173. * additive changes; When modifier is triggered to change the attribute of a
  2174. * sprite, it will pop-up the changes to the top.
  2175. */
  2176. Ext.define('Ext.draw.modifier.Modifier', {
  2177. isModifier: true,
  2178. mixins: {
  2179. observable: 'Ext.mixin.Observable'
  2180. },
  2181. config: {
  2182. /**
  2183. * @private
  2184. * @cfg {Ext.draw.modifier.Modifier} lower Modifier that receives the push-down changes.
  2185. */
  2186. lower: null,
  2187. /**
  2188. * @private
  2189. * @cfg {Ext.draw.modifier.Modifier} upper Modifier that receives the pop-up changes.
  2190. */
  2191. upper: null,
  2192. /**
  2193. * @cfg {Ext.draw.sprite.Sprite} sprite The sprite to which the modifier belongs.
  2194. */
  2195. sprite: null
  2196. },
  2197. constructor: function(config) {
  2198. this.mixins.observable.constructor.call(this, config);
  2199. },
  2200. updateUpper: function(upper) {
  2201. if (upper) {
  2202. upper.setLower(this);
  2203. }
  2204. },
  2205. updateLower: function(lower) {
  2206. if (lower) {
  2207. lower.setUpper(this);
  2208. }
  2209. },
  2210. /**
  2211. * @private
  2212. * Validate attribute set before use.
  2213. *
  2214. * @param {Object} attr The attribute to be validated. Note that it may be already initialized, so do
  2215. * not override properties that have already been used.
  2216. */
  2217. prepareAttributes: function(attr) {
  2218. if (this._lower) {
  2219. this._lower.prepareAttributes(attr);
  2220. }
  2221. },
  2222. /**
  2223. * @private
  2224. * Invoked when changes need to be popped up to the top.
  2225. * @param {Object} attr The source attributes.
  2226. * @param {Object} changes The changes to be popped up.
  2227. */
  2228. popUp: function(attr, changes) {
  2229. if (this._upper) {
  2230. this._upper.popUp(attr, changes);
  2231. } else {
  2232. Ext.apply(attr, changes);
  2233. }
  2234. },
  2235. /**
  2236. * @private
  2237. *
  2238. * This method will filter out the properties from the `changes` object, if they
  2239. * have the same values as in the `attr` object (sprite's attributes).
  2240. *
  2241. * If the `receiver` object is provided, the attributes with the new values will be
  2242. * copied from the `changes` object to the `receiver` object, and the `changes`
  2243. * object will be left unchanged.
  2244. *
  2245. * The method returns the `receiver` object, if it was provided, or the `changes`
  2246. * object otherwise.
  2247. *
  2248. * The method also handles a special case when a sprite attribute that is meant to be
  2249. * animated was set to a certain value (e.g. 5), that is different from the original
  2250. * value (e.g. 3) of the attribute, and immediately set to another value again, that
  2251. * is the same as the original value (3). In this case, the attribute's current
  2252. * value is still the original value, because the attribute hasn't started animating
  2253. * yet, so a comparison against the current value is not appropriate, and the target
  2254. * value (value at the end of animation, 5) should be used for comparison instead, so
  2255. * that 3 won't be filtered out.
  2256. */
  2257. filterChanges: function(attr, changes, receiver) {
  2258. var targets = attr.targets,
  2259. name, value;
  2260. if (receiver) {
  2261. for (name in changes) {
  2262. value = changes[name];
  2263. if (value !== attr[name] || (targets && value !== targets[name])) {
  2264. receiver[name] = value;
  2265. }
  2266. }
  2267. } else {
  2268. for (name in changes) {
  2269. value = changes[name];
  2270. if (value === attr[name] && (!targets || value === targets[name])) {
  2271. delete changes[name];
  2272. }
  2273. }
  2274. }
  2275. return receiver || changes;
  2276. },
  2277. /**
  2278. * @private
  2279. * Invoked when changes need to be pushed down to the sprite.
  2280. * @param {Object} attr The source attributes.
  2281. * @param {Object} changes The changes to make. This object might be changed unexpectedly inside the method.
  2282. * @return {Mixed}
  2283. */
  2284. pushDown: function(attr, changes) {
  2285. return this._lower ? this._lower.pushDown(attr, changes) : this.filterChanges(attr, changes);
  2286. }
  2287. });
  2288. /**
  2289. * @class Ext.draw.modifier.Target
  2290. * @extends Ext.draw.modifier.Modifier
  2291. *
  2292. * This is the destination (top) modifier that has to be put at
  2293. * the top of the modifier stack.
  2294. *
  2295. * The Target modifier figures out which updaters have to be called
  2296. * for the changed set of attributes and makes the sprite and its instances (if any)
  2297. * call them.
  2298. */
  2299. Ext.define('Ext.draw.modifier.Target', {
  2300. requires: [
  2301. 'Ext.draw.Matrix'
  2302. ],
  2303. extend: 'Ext.draw.modifier.Modifier',
  2304. alias: 'modifier.target',
  2305. statics: {
  2306. /**
  2307. * @private
  2308. */
  2309. uniqueId: 0
  2310. },
  2311. prepareAttributes: function(attr) {
  2312. if (this._lower) {
  2313. this._lower.prepareAttributes(attr);
  2314. }
  2315. attr.attributeId = 'attribute-' + Ext.draw.modifier.Target.uniqueId++;
  2316. if (!attr.hasOwnProperty('canvasAttributes')) {
  2317. attr.bbox = {
  2318. plain: {
  2319. dirty: true
  2320. },
  2321. transform: {
  2322. dirty: true
  2323. }
  2324. };
  2325. attr.dirty = true;
  2326. /*
  2327. Maps updaters that have to be called to the attributes that triggered the update.
  2328. It is basically a reversed `triggers` map (see Ext.draw.sprite.AttributeDefinition),
  2329. but only for those attributes that have changed.
  2330. Pending updaters are called by the Ext.draw.sprite.Sprite.callUpdaters method.
  2331. The 'canvas' updater is a special kind of updater that is not actually a function
  2332. but a flag indicating that the attribute should be applied directly to a canvas
  2333. context.
  2334. */
  2335. attr.pendingUpdaters = {};
  2336. /*
  2337. Holds the attributes that triggered the canvas update (attr.pendingUpdaters.canvas).
  2338. Canvas attributes are applied directly to a canvas context
  2339. by the sprite.useAttributes method.
  2340. */
  2341. attr.canvasAttributes = {};
  2342. attr.matrix = new Ext.draw.Matrix();
  2343. attr.inverseMatrix = new Ext.draw.Matrix();
  2344. }
  2345. },
  2346. /**
  2347. * @private
  2348. * Applies changes to sprite/instance attributes and determines which updaters
  2349. * have to be called as a result of attributes change.
  2350. * @param {Object} attr The source attributes.
  2351. * @param {Object} changes The modifier changes.
  2352. */
  2353. applyChanges: function(attr, changes) {
  2354. Ext.apply(attr, changes);
  2355. var sprite = this.getSprite(),
  2356. pendingUpdaters = attr.pendingUpdaters,
  2357. triggers = sprite.self.def.getTriggers(),
  2358. updaters, instances, instance, name, hasChanges, canvasAttributes, i, j, ln;
  2359. for (name in changes) {
  2360. hasChanges = true;
  2361. if ((updaters = triggers[name])) {
  2362. sprite.scheduleUpdaters(attr, updaters, [
  2363. name
  2364. ]);
  2365. }
  2366. if (attr.template && changes.removeFromInstance && changes.removeFromInstance[name]) {
  2367. delete attr[name];
  2368. }
  2369. }
  2370. if (!hasChanges) {
  2371. return;
  2372. }
  2373. // This can prevent sub objects to set duplicated attributes to context.
  2374. if (pendingUpdaters.canvas) {
  2375. canvasAttributes = pendingUpdaters.canvas;
  2376. delete pendingUpdaters.canvas;
  2377. for (i = 0 , ln = canvasAttributes.length; i < ln; i++) {
  2378. name = canvasAttributes[i];
  2379. attr.canvasAttributes[name] = attr[name];
  2380. }
  2381. }
  2382. // If the attributes of an instancing sprite template are being modified here,
  2383. // then spread the pending updaters to the instances (template's children).
  2384. if (attr.hasOwnProperty('children')) {
  2385. instances = attr.children;
  2386. for (i = 0 , ln = instances.length; i < ln; i++) {
  2387. instance = instances[i];
  2388. Ext.apply(instance.pendingUpdaters, pendingUpdaters);
  2389. if (canvasAttributes) {
  2390. for (j = 0; j < canvasAttributes.length; j++) {
  2391. name = canvasAttributes[j];
  2392. instance.canvasAttributes[name] = instance[name];
  2393. }
  2394. }
  2395. sprite.callUpdaters(instance);
  2396. }
  2397. }
  2398. sprite.setDirty(true);
  2399. sprite.callUpdaters(attr);
  2400. },
  2401. popUp: function(attr, changes) {
  2402. this.applyChanges(attr, changes);
  2403. },
  2404. pushDown: function(attr, changes) {
  2405. // Modifier chain looks like this:
  2406. // sprite.modifiers.target <---> postFx <---> sprite.modifiers.animation <---> preFx
  2407. // There can be any number of postFx and preFx modifiers, the difference between them is that:
  2408. // `preFx` modifier changes are animated.
  2409. // `postFx` modifier changes are not.
  2410. // preFx modifiers include Highlight (Draw) and Callout (Charts).
  2411. // There are no postFx modifiers at the moment.
  2412. if (this._lower) {
  2413. // Without any postFx modifiers, `lower` is going to be Animation.
  2414. changes = this._lower.pushDown(attr, changes);
  2415. }
  2416. this.applyChanges(attr, changes);
  2417. return changes;
  2418. }
  2419. });
  2420. /**
  2421. * @class Ext.draw.TimingFunctions
  2422. * @singleton
  2423. *
  2424. * Singleton that provides easing functions for use in sprite animations.
  2425. */
  2426. Ext.define('Ext.draw.TimingFunctions', function() {
  2427. var pow = Math.pow,
  2428. sin = Math.sin,
  2429. cos = Math.cos,
  2430. sqrt = Math.sqrt,
  2431. pi = Math.PI,
  2432. poly = [
  2433. 'quad',
  2434. 'cube',
  2435. 'quart',
  2436. 'quint'
  2437. ],
  2438. easings = {
  2439. pow: function(p, x) {
  2440. return pow(p, x || 6);
  2441. },
  2442. expo: function(p) {
  2443. return pow(2, 8 * (p - 1));
  2444. },
  2445. circ: function(p) {
  2446. return 1 - sqrt(1 - p * p);
  2447. },
  2448. sine: function(p) {
  2449. return 1 - sin((1 - p) * pi / 2);
  2450. },
  2451. back: function(p, n) {
  2452. n = n || 1.616;
  2453. return p * p * ((n + 1) * p - n);
  2454. },
  2455. bounce: function(p) {
  2456. for (var a = 0,
  2457. b = 1; 1; a += b , b /= 2) {
  2458. if (p >= (7 - 4 * a) / 11) {
  2459. return b * b - pow((11 - 6 * a - 11 * p) / 4, 2);
  2460. }
  2461. }
  2462. },
  2463. elastic: function(p, x) {
  2464. return pow(2, 10 * --p) * cos(20 * p * pi * (x || 1) / 3);
  2465. }
  2466. },
  2467. easingsMap = {},
  2468. name, len, i;
  2469. // Create polynomial easing equations.
  2470. function createPoly(times) {
  2471. return function(p) {
  2472. return pow(p, times);
  2473. };
  2474. }
  2475. function addEasing(name, easing) {
  2476. easingsMap[name + 'In'] = function(pos) {
  2477. return easing(pos);
  2478. };
  2479. easingsMap[name + 'Out'] = function(pos) {
  2480. return 1 - easing(1 - pos);
  2481. };
  2482. easingsMap[name + 'InOut'] = function(pos) {
  2483. return (pos <= 0.5) ? easing(2 * pos) / 2 : (2 - easing(2 * (1 - pos))) / 2;
  2484. };
  2485. }
  2486. for (i = 0 , len = poly.length; i < len; ++i) {
  2487. easings[poly[i]] = createPoly(i + 2);
  2488. }
  2489. for (name in easings) {
  2490. addEasing(name, easings[name]);
  2491. }
  2492. // Add linear interpolator.
  2493. easingsMap.linear = Ext.identityFn;
  2494. // Add aliases for quad easings.
  2495. easingsMap.easeIn = easingsMap.quadIn;
  2496. easingsMap.easeOut = easingsMap.quadOut;
  2497. easingsMap.easeInOut = easingsMap.quadInOut;
  2498. return {
  2499. singleton: true,
  2500. easingMap: easingsMap
  2501. };
  2502. }, function(Cls) {
  2503. Ext.apply(Cls, Cls.easingMap);
  2504. });
  2505. /**
  2506. * @class Ext.draw.Animator
  2507. *
  2508. * Singleton class that manages the animation pool.
  2509. */
  2510. Ext.define('Ext.draw.Animator', {
  2511. uses: [
  2512. 'Ext.draw.Draw'
  2513. ],
  2514. singleton: true,
  2515. frameCallbacks: {},
  2516. frameCallbackId: 0,
  2517. scheduled: 0,
  2518. frameStartTimeOffset: Ext.now(),
  2519. animations: [],
  2520. running: false,
  2521. /**
  2522. * Cross platform `animationTime` implementation.
  2523. * @return {Number}
  2524. */
  2525. animationTime: function() {
  2526. return Ext.AnimationQueue.frameStartTime - this.frameStartTimeOffset;
  2527. },
  2528. /**
  2529. * Adds an animated object to the animation pool.
  2530. *
  2531. * @param {Object} animation The animation descriptor to add to the pool.
  2532. */
  2533. add: function(animation) {
  2534. var me = this;
  2535. if (!me.contains(animation)) {
  2536. me.animations.push(animation);
  2537. me.ignite();
  2538. if ('fireEvent' in animation) {
  2539. animation.fireEvent('animationstart', animation);
  2540. }
  2541. }
  2542. },
  2543. /**
  2544. * Removes an animation from the pool.
  2545. * TODO: This is broken when called within `step` method.
  2546. * @param {Object} animation The animation to remove from the pool.
  2547. */
  2548. remove: function(animation) {
  2549. var me = this,
  2550. animations = me.animations,
  2551. i = 0,
  2552. l = animations.length;
  2553. for (; i < l; ++i) {
  2554. if (animations[i] === animation) {
  2555. animations.splice(i, 1);
  2556. if ('fireEvent' in animation) {
  2557. animation.fireEvent('animationend', animation);
  2558. }
  2559. return;
  2560. }
  2561. }
  2562. },
  2563. /**
  2564. * Returns `true` or `false` whether it contains the given animation or not.
  2565. *
  2566. * @param {Object} animation The animation to check for.
  2567. * @return {Boolean}
  2568. */
  2569. contains: function(animation) {
  2570. return Ext.Array.indexOf(this.animations, animation) > -1;
  2571. },
  2572. /**
  2573. * Returns `true` or `false` whether the pool is empty or not.
  2574. * @return {Boolean}
  2575. */
  2576. empty: function() {
  2577. return this.animations.length === 0;
  2578. },
  2579. idle: function() {
  2580. return this.scheduled === 0 && this.animations.length === 0;
  2581. },
  2582. /**
  2583. * Given a frame time it will filter out finished animations from the pool.
  2584. *
  2585. * @param {Number} frameTime The frame's start time, in milliseconds.
  2586. */
  2587. step: function(frameTime) {
  2588. var me = this,
  2589. animations = me.animations,
  2590. animation,
  2591. i = 0,
  2592. ln = animations.length;
  2593. for (; i < ln; i++) {
  2594. animation = animations[i];
  2595. animation.step(frameTime);
  2596. if (!animation.animating) {
  2597. animations.splice(i, 1);
  2598. i--;
  2599. ln--;
  2600. if (animation.fireEvent) {
  2601. animation.fireEvent('animationend', animation);
  2602. }
  2603. }
  2604. }
  2605. },
  2606. /**
  2607. * Register a one-time callback that will be called at the next frame.
  2608. * @param {Function/String} callback
  2609. * @param {Object} scope
  2610. * @return {String} The ID of the scheduled callback.
  2611. */
  2612. schedule: function(callback, scope) {
  2613. scope = scope || this;
  2614. var id = 'frameCallback' + (this.frameCallbackId++);
  2615. if (Ext.isString(callback)) {
  2616. callback = scope[callback];
  2617. }
  2618. Ext.draw.Animator.frameCallbacks[id] = {
  2619. fn: callback,
  2620. scope: scope,
  2621. once: true
  2622. };
  2623. this.scheduled++;
  2624. Ext.draw.Animator.ignite();
  2625. return id;
  2626. },
  2627. /**
  2628. * Register a one-time callback that will be called at the next frame,
  2629. * if that callback (with a matching function and scope) isn't already scheduled.
  2630. * @param {Function/String} callback
  2631. * @param {Object} scope
  2632. * @return {String/null} The ID of the scheduled callback or null, if that callback has already been scheduled.
  2633. */
  2634. scheduleIf: function(callback, scope) {
  2635. scope = scope || this;
  2636. var frameCallbacks = Ext.draw.Animator.frameCallbacks,
  2637. cb, id;
  2638. if (Ext.isString(callback)) {
  2639. callback = scope[callback];
  2640. }
  2641. for (id in frameCallbacks) {
  2642. cb = frameCallbacks[id];
  2643. if (cb.once && cb.fn === callback && cb.scope === scope) {
  2644. return null;
  2645. }
  2646. }
  2647. return this.schedule(callback, scope);
  2648. },
  2649. /**
  2650. * Cancel a registered one-time callback
  2651. * @param {String} id
  2652. */
  2653. cancel: function(id) {
  2654. if (Ext.draw.Animator.frameCallbacks[id] && Ext.draw.Animator.frameCallbacks[id].once) {
  2655. this.scheduled = Math.max(--this.scheduled, 0);
  2656. delete Ext.draw.Animator.frameCallbacks[id];
  2657. Ext.draw.Draw.endUpdateIOS();
  2658. }
  2659. if (this.idle()) {
  2660. this.extinguish();
  2661. }
  2662. },
  2663. clear: function() {
  2664. this.animations.length = 0;
  2665. Ext.draw.Animator.frameCallbacks = {};
  2666. this.extinguish();
  2667. },
  2668. /**
  2669. * Register a recursive callback that will be called at every frame.
  2670. *
  2671. * @param {Function} callback
  2672. * @param {Object} scope
  2673. * @return {String}
  2674. */
  2675. addFrameCallback: function(callback, scope) {
  2676. scope = scope || this;
  2677. if (Ext.isString(callback)) {
  2678. callback = scope[callback];
  2679. }
  2680. var id = 'frameCallback' + (this.frameCallbackId++);
  2681. Ext.draw.Animator.frameCallbacks[id] = {
  2682. fn: callback,
  2683. scope: scope
  2684. };
  2685. return id;
  2686. },
  2687. /**
  2688. * Unregister a recursive callback.
  2689. * @param {String} id
  2690. */
  2691. removeFrameCallback: function(id) {
  2692. delete Ext.draw.Animator.frameCallbacks[id];
  2693. if (this.idle()) {
  2694. this.extinguish();
  2695. }
  2696. },
  2697. /**
  2698. * @private
  2699. */
  2700. fireFrameCallbacks: function() {
  2701. var callbacks = this.frameCallbacks,
  2702. id, fn, cb;
  2703. for (id in callbacks) {
  2704. cb = callbacks[id];
  2705. fn = cb.fn;
  2706. if (Ext.isString(fn)) {
  2707. fn = cb.scope[fn];
  2708. }
  2709. fn.call(cb.scope);
  2710. if (callbacks[id] && cb.once) {
  2711. this.scheduled = Math.max(--this.scheduled, 0);
  2712. delete callbacks[id];
  2713. }
  2714. }
  2715. },
  2716. handleFrame: function() {
  2717. var me = this;
  2718. me.step(me.animationTime());
  2719. me.fireFrameCallbacks();
  2720. if (me.idle()) {
  2721. me.extinguish();
  2722. }
  2723. },
  2724. ignite: function() {
  2725. if (!this.running) {
  2726. this.running = true;
  2727. Ext.AnimationQueue.start(this.handleFrame, this);
  2728. Ext.draw.Draw.beginUpdateIOS();
  2729. }
  2730. },
  2731. extinguish: function() {
  2732. this.running = false;
  2733. Ext.AnimationQueue.stop(this.handleFrame, this);
  2734. Ext.draw.Draw.endUpdateIOS();
  2735. }
  2736. });
  2737. /**
  2738. * The Animation modifier.
  2739. *
  2740. * Sencha Charts allow users to use transitional animation on sprites. Simply set the duration
  2741. * and easing in the animation modifier, then all the changes to the sprites will be animated.
  2742. *
  2743. * @example
  2744. * var drawCt = Ext.create({
  2745. * xtype: 'draw',
  2746. * renderTo: document.body,
  2747. * width: 400,
  2748. * height: 400,
  2749. * sprites: [{
  2750. * type: 'rect',
  2751. * x: 50,
  2752. * y: 50,
  2753. * width: 100,
  2754. * height: 100,
  2755. * fillStyle: '#1F6D91'
  2756. * }]
  2757. * });
  2758. *
  2759. * var rect = drawCt.getSurface().getItems()[0];
  2760. *
  2761. * rect.setAnimation({
  2762. * duration: 1000,
  2763. * easing: 'elasticOut'
  2764. * });
  2765. *
  2766. * Ext.defer(function () {
  2767. * rect.setAttributes({
  2768. * width: 250
  2769. * });
  2770. * }, 500);
  2771. *
  2772. * Also, you can use different durations and easing functions on different attributes by using
  2773. * {@link #customDurations} and {@link #customEasings}.
  2774. *
  2775. * By default, an animation modifier will be created during the initialization of a sprite.
  2776. * You can get the animation modifier of a sprite via its
  2777. * {@link Ext.draw.sprite.Sprite#method-getAnimation getAnimation} method.
  2778. */
  2779. Ext.define('Ext.draw.modifier.Animation', {
  2780. extend: 'Ext.draw.modifier.Modifier',
  2781. alias: 'modifier.animation',
  2782. requires: [
  2783. 'Ext.draw.TimingFunctions',
  2784. 'Ext.draw.Animator'
  2785. ],
  2786. config: {
  2787. /**
  2788. * @cfg {Function} easing
  2789. * Default easing function.
  2790. */
  2791. easing: Ext.identityFn,
  2792. /**
  2793. * @cfg {Number} duration
  2794. * Default duration time (ms).
  2795. */
  2796. duration: 0,
  2797. /**
  2798. * @cfg {Object} customEasings Overrides the default easing function for defined attributes. E.g.:
  2799. *
  2800. * // Assuming the sprite the modifier is applied to is a 'circle'.
  2801. * customEasings: {
  2802. * r: 'easeOut',
  2803. * 'fillStyle,strokeStyle': 'linear',
  2804. * 'cx,cy': function (p, n) {
  2805. * p = 1 - p;
  2806. * n = n || 1.616;
  2807. * return 1 - p * p * ((n + 1) * p - n);
  2808. * }
  2809. * }
  2810. */
  2811. customEasings: {},
  2812. /**
  2813. * @cfg {Object} customDurations Overrides the default duration for defined attributes. E.g.:
  2814. *
  2815. * // Assuming the sprite the modifier is applied to is a 'circle'.
  2816. * customDurations: {
  2817. * r: 1000,
  2818. * 'fillStyle,strokeStyle': 2000,
  2819. * 'cx,cy': 1000
  2820. * }
  2821. */
  2822. customDurations: {}
  2823. },
  2824. constructor: function(config) {
  2825. var me = this;
  2826. me.anyAnimation = me.anySpecialAnimations = false;
  2827. me.animating = 0;
  2828. me.animatingPool = [];
  2829. me.callParent([
  2830. config
  2831. ]);
  2832. },
  2833. prepareAttributes: function(attr) {
  2834. if (!attr.hasOwnProperty('timers')) {
  2835. attr.animating = false;
  2836. attr.timers = {};
  2837. // The 'targets' object is used to hold the target values for the
  2838. // attributes while they are being animated from source to target values.
  2839. // The 'targets' is pushed down to the lower level modifiers,
  2840. // instead of the actual attr object, to hide the fact that the
  2841. // attributes are being animated.
  2842. attr.targets = Ext.Object.chain(attr);
  2843. attr.targets.prototype = attr;
  2844. }
  2845. if (this._lower) {
  2846. this._lower.prepareAttributes(attr.targets);
  2847. }
  2848. },
  2849. updateSprite: function(sprite) {
  2850. this.setConfig(sprite.config.animation);
  2851. },
  2852. updateDuration: function(duration) {
  2853. this.anyAnimation = duration > 0;
  2854. },
  2855. applyEasing: function(easing) {
  2856. if (typeof easing === 'string') {
  2857. easing = Ext.draw.TimingFunctions.easingMap[easing];
  2858. }
  2859. return easing;
  2860. },
  2861. applyCustomEasings: function(newEasings, oldEasings) {
  2862. oldEasings = oldEasings || {};
  2863. var any, key, attrs, easing, i, ln;
  2864. for (key in newEasings) {
  2865. any = true;
  2866. easing = newEasings[key];
  2867. attrs = key.split(',');
  2868. if (typeof easing === 'string') {
  2869. easing = Ext.draw.TimingFunctions.easingMap[easing];
  2870. }
  2871. for (i = 0 , ln = attrs.length; i < ln; i++) {
  2872. oldEasings[attrs[i]] = easing;
  2873. }
  2874. }
  2875. if (any) {
  2876. this.anySpecialAnimations = any;
  2877. }
  2878. return oldEasings;
  2879. },
  2880. /**
  2881. * Set special easings on the given attributes. E.g.:
  2882. *
  2883. * circleSprite.getAnimation().setEasingOn('r', 'elasticIn');
  2884. *
  2885. * @param {String/Array} attrs The source attribute(s).
  2886. * @param {String} easing The special easings.
  2887. */
  2888. setEasingOn: function(attrs, easing) {
  2889. attrs = Ext.Array.from(attrs).slice();
  2890. var customEasings = {},
  2891. ln = attrs.length,
  2892. i = 0;
  2893. for (; i < ln; i++) {
  2894. customEasings[attrs[i]] = easing;
  2895. }
  2896. this.setCustomEasings(customEasings);
  2897. },
  2898. /**
  2899. * Remove special easings on the given attributes.
  2900. * @param {String/Array} attrs The source attribute(s).
  2901. */
  2902. clearEasingOn: function(attrs) {
  2903. attrs = Ext.Array.from(attrs, true);
  2904. var i = 0,
  2905. ln = attrs.length;
  2906. for (; i < ln; i++) {
  2907. delete this._customEasings[attrs[i]];
  2908. }
  2909. },
  2910. applyCustomDurations: function(newDurations, oldDurations) {
  2911. oldDurations = oldDurations || {};
  2912. var any, key, duration, attrs, i, ln;
  2913. for (key in newDurations) {
  2914. any = true;
  2915. duration = newDurations[key];
  2916. attrs = key.split(',');
  2917. for (i = 0 , ln = attrs.length; i < ln; i++) {
  2918. oldDurations[attrs[i]] = duration;
  2919. }
  2920. }
  2921. if (any) {
  2922. this.anySpecialAnimations = any;
  2923. }
  2924. return oldDurations;
  2925. },
  2926. /**
  2927. * Set special duration on the given attributes. E.g.:
  2928. *
  2929. * rectSprite.getAnimation().setDurationOn('height', 2000);
  2930. *
  2931. * @param {String/Array} attrs The source attributes.
  2932. * @param {Number} duration The special duration.
  2933. */
  2934. setDurationOn: function(attrs, duration) {
  2935. attrs = Ext.Array.from(attrs).slice();
  2936. var customDurations = {},
  2937. i = 0,
  2938. ln = attrs.length;
  2939. for (; i < ln; i++) {
  2940. customDurations[attrs[i]] = duration;
  2941. }
  2942. this.setCustomDurations(customDurations);
  2943. },
  2944. /**
  2945. * Remove special easings on the given attributes.
  2946. * @param {Object} attrs The source attributes.
  2947. */
  2948. clearDurationOn: function(attrs) {
  2949. attrs = Ext.Array.from(attrs, true);
  2950. for (var i = 0,
  2951. ln = attrs.length; i < ln; i++) {
  2952. delete this._customDurations[attrs[i]];
  2953. }
  2954. },
  2955. /**
  2956. * @private
  2957. * Initializes Animator for the animation.
  2958. * @param {Object} attr The source attributes.
  2959. * @param {Boolean} animating The animating flag.
  2960. */
  2961. setAnimating: function(attr, animating) {
  2962. var me = this,
  2963. pool = me.animatingPool;
  2964. if (attr.animating !== animating) {
  2965. attr.animating = animating;
  2966. if (animating) {
  2967. pool.push(attr);
  2968. if (me.animating === 0) {
  2969. Ext.draw.Animator.add(me);
  2970. }
  2971. me.animating++;
  2972. } else {
  2973. for (var i = pool.length; i--; ) {
  2974. if (pool[i] === attr) {
  2975. pool.splice(i, 1);
  2976. }
  2977. }
  2978. me.animating = pool.length;
  2979. }
  2980. }
  2981. },
  2982. /**
  2983. * @private
  2984. * Set the attr with given easing and duration.
  2985. * @param {Object} attr The attributes collection.
  2986. * @param {Object} changes The changes that popped up from lower modifier.
  2987. * @return {Object} The changes to pop up.
  2988. */
  2989. setAttrs: function(attr, changes) {
  2990. var me = this,
  2991. timers = attr.timers,
  2992. parsers = me._sprite.self.def._animationProcessors,
  2993. defaultEasing = me._easing,
  2994. defaultDuration = me._duration,
  2995. customDurations = me._customDurations,
  2996. customEasings = me._customEasings,
  2997. anySpecial = me.anySpecialAnimations,
  2998. any = me.anyAnimation || anySpecial,
  2999. targets = attr.targets,
  3000. ignite = false,
  3001. timer, name, newValue, startValue, parser, easing, duration;
  3002. if (!any) {
  3003. // If there is no animation enabled.
  3004. // When applying changes to attributes, simply stop current animation
  3005. // and set the value.
  3006. for (name in changes) {
  3007. if (attr[name] === changes[name]) {
  3008. delete changes[name];
  3009. } else {
  3010. attr[name] = changes[name];
  3011. }
  3012. delete targets[name];
  3013. delete timers[name];
  3014. }
  3015. return changes;
  3016. } else {
  3017. // If any animation.
  3018. for (name in changes) {
  3019. newValue = changes[name];
  3020. startValue = attr[name];
  3021. if (newValue !== startValue && startValue !== undefined && startValue !== null && (parser = parsers[name])) {
  3022. // If this property is animating.
  3023. // Figure out the desired duration and easing.
  3024. easing = defaultEasing;
  3025. duration = defaultDuration;
  3026. if (anySpecial) {
  3027. // Deducing the easing function and duration
  3028. if (name in customEasings) {
  3029. easing = customEasings[name];
  3030. }
  3031. if (name in customDurations) {
  3032. duration = customDurations[name];
  3033. }
  3034. }
  3035. // Transitions betweens color and gradient or between gradients are not supported.
  3036. if (startValue && startValue.isGradient || newValue && newValue.isGradient) {
  3037. duration = 0;
  3038. }
  3039. // If the property is animating
  3040. if (duration) {
  3041. if (!timers[name]) {
  3042. timers[name] = {};
  3043. }
  3044. timer = timers[name];
  3045. timer.start = 0;
  3046. timer.easing = easing;
  3047. timer.duration = duration;
  3048. timer.compute = parser.compute;
  3049. timer.serve = parser.serve || Ext.identityFn;
  3050. timer.remove = changes.removeFromInstance && changes.removeFromInstance[name];
  3051. if (parser.parseInitial) {
  3052. var initial = parser.parseInitial(startValue, newValue);
  3053. timer.source = initial[0];
  3054. timer.target = initial[1];
  3055. } else if (parser.parse) {
  3056. timer.source = parser.parse(startValue);
  3057. timer.target = parser.parse(newValue);
  3058. } else {
  3059. timer.source = startValue;
  3060. timer.target = newValue;
  3061. }
  3062. // The animation started. Change to originalVal.
  3063. targets[name] = newValue;
  3064. delete changes[name];
  3065. ignite = true;
  3066. continue;
  3067. } else {
  3068. delete targets[name];
  3069. }
  3070. } else {
  3071. delete targets[name];
  3072. }
  3073. // If the property is not animating.
  3074. delete timers[name];
  3075. }
  3076. }
  3077. if (ignite && !attr.animating) {
  3078. me.setAnimating(attr, true);
  3079. }
  3080. return changes;
  3081. },
  3082. /**
  3083. * @private
  3084. *
  3085. * Update attributes to current value according to current animation time.
  3086. * This method will not affect the values of lower layers, but may delete a
  3087. * value from it.
  3088. * @param {Object} attr The source attributes.
  3089. * @return {Object} The changes to pop up or null.
  3090. */
  3091. updateAttributes: function(attr) {
  3092. if (!attr.animating) {
  3093. return {};
  3094. }
  3095. var changes = {},
  3096. any = false,
  3097. timers = attr.timers,
  3098. targets = attr.targets,
  3099. now = Ext.draw.Animator.animationTime(),
  3100. name, timer, delta;
  3101. // If updated in the same frame, return.
  3102. if (attr.lastUpdate === now) {
  3103. return null;
  3104. }
  3105. for (name in timers) {
  3106. timer = timers[name];
  3107. if (!timer.start) {
  3108. timer.start = now;
  3109. delta = 0;
  3110. } else {
  3111. delta = (now - timer.start) / timer.duration;
  3112. }
  3113. if (delta >= 1) {
  3114. changes[name] = targets[name];
  3115. delete targets[name];
  3116. if (timers[name].remove) {
  3117. changes.removeFromInstance = changes.removeFromInstance || {};
  3118. changes.removeFromInstance[name] = true;
  3119. }
  3120. delete timers[name];
  3121. } else {
  3122. changes[name] = timer.serve(timer.compute(timer.source, timer.target, timer.easing(delta), attr[name]));
  3123. any = true;
  3124. }
  3125. }
  3126. attr.lastUpdate = now;
  3127. this.setAnimating(attr, any);
  3128. return changes;
  3129. },
  3130. pushDown: function(attr, changes) {
  3131. changes = this.callParent([
  3132. attr.targets,
  3133. changes
  3134. ]);
  3135. return this.setAttrs(attr, changes);
  3136. },
  3137. popUp: function(attr, changes) {
  3138. attr = attr.prototype;
  3139. changes = this.setAttrs(attr, changes);
  3140. if (this._upper) {
  3141. return this._upper.popUp(attr, changes);
  3142. } else {
  3143. return Ext.apply(attr, changes);
  3144. }
  3145. },
  3146. /**
  3147. * @private
  3148. * This is called as an animated object in `Ext.draw.Animator`.
  3149. */
  3150. step: function(frameTime) {
  3151. var me = this,
  3152. pool = me.animatingPool.slice(),
  3153. ln = pool.length,
  3154. i = 0,
  3155. attr, changes;
  3156. for (; i < ln; i++) {
  3157. attr = pool[i];
  3158. changes = me.updateAttributes(attr);
  3159. if (changes && me._upper) {
  3160. me._upper.popUp(attr, changes);
  3161. }
  3162. }
  3163. },
  3164. /**
  3165. * Stop all animations affected by this modifier.
  3166. */
  3167. stop: function() {
  3168. this.step();
  3169. var me = this,
  3170. pool = me.animatingPool,
  3171. i, ln;
  3172. for (i = 0 , ln = pool.length; i < ln; i++) {
  3173. pool[i].animating = false;
  3174. }
  3175. me.animatingPool.length = 0;
  3176. me.animating = 0;
  3177. Ext.draw.Animator.remove(me);
  3178. },
  3179. destroy: function() {
  3180. Ext.draw.Animator.remove(this);
  3181. this.callParent();
  3182. }
  3183. });
  3184. /**
  3185. * @class Ext.draw.modifier.Highlight
  3186. * @extends Ext.draw.modifier.Modifier
  3187. *
  3188. * Highlight is a modifier that will override sprite attributes
  3189. * with {@link Ext.draw.modifier.Highlight#style style} attributes
  3190. * when sprite's `highlighted` attribute is true.
  3191. */
  3192. Ext.define('Ext.draw.modifier.Highlight', {
  3193. extend: 'Ext.draw.modifier.Modifier',
  3194. alias: 'modifier.highlight',
  3195. config: {
  3196. /**
  3197. * @cfg {Boolean} enabled 'true' if the highlight is applied.
  3198. */
  3199. enabled: false,
  3200. /**
  3201. * @cfg {Object} style The style attributes of the highlight modifier.
  3202. */
  3203. style: null
  3204. },
  3205. preFx: true,
  3206. applyStyle: function(style, oldStyle) {
  3207. oldStyle = oldStyle || {};
  3208. if (this.getSprite()) {
  3209. Ext.apply(oldStyle, this.getSprite().self.def.normalize(style));
  3210. } else {
  3211. Ext.apply(oldStyle, style);
  3212. }
  3213. return oldStyle;
  3214. },
  3215. prepareAttributes: function(attr) {
  3216. if (!attr.hasOwnProperty('highlightOriginal')) {
  3217. attr.highlighted = false;
  3218. attr.highlightOriginal = Ext.Object.chain(attr);
  3219. attr.highlightOriginal.prototype = attr;
  3220. // A list of attributes that should be removed from a sprite instance
  3221. // when it is unhighlighted.
  3222. attr.highlightOriginal.removeFromInstance = {};
  3223. }
  3224. if (this._lower) {
  3225. this._lower.prepareAttributes(attr.highlightOriginal);
  3226. }
  3227. },
  3228. updateSprite: function(sprite, oldSprite) {
  3229. var me = this,
  3230. style = me.getStyle(),
  3231. attributeDefinitions;
  3232. if (sprite) {
  3233. attributeDefinitions = sprite.self.def;
  3234. if (style) {
  3235. me._style = attributeDefinitions.normalize(style);
  3236. }
  3237. me.setStyle(sprite.config.highlight);
  3238. // Add highlight related attributes to sprite's attribute definition.
  3239. // This will affect all sprites of the same type, even those without
  3240. // the highlight modifier.
  3241. attributeDefinitions.setConfig({
  3242. defaults: {
  3243. highlighted: false
  3244. },
  3245. processors: {
  3246. highlighted: 'bool'
  3247. }
  3248. });
  3249. }
  3250. this.setSprite(sprite);
  3251. },
  3252. /**
  3253. * @private
  3254. * Filter out modifier changes that override highlight style or source attributes.
  3255. * @param {Object} attr The source attributes.
  3256. * @param {Object} changes The modifier changes.
  3257. * @return {*} The filtered changes.
  3258. */
  3259. filterChanges: function(attr, changes) {
  3260. var me = this,
  3261. highlightOriginal = attr.highlightOriginal,
  3262. style = me.getStyle(),
  3263. name;
  3264. if (attr.highlighted) {
  3265. for (name in changes) {
  3266. if (style.hasOwnProperty(name)) {
  3267. // If sprite is highlighted, then stash the changes
  3268. // to the `style` attributes made by lower level modifiers
  3269. // to apply them later when sprite is unhighlighted.
  3270. highlightOriginal[name] = changes[name];
  3271. delete changes[name];
  3272. }
  3273. }
  3274. }
  3275. return changes;
  3276. },
  3277. pushDown: function(attr, changes) {
  3278. var style = this.getStyle(),
  3279. highlightOriginal = attr.highlightOriginal,
  3280. removeFromInstance = highlightOriginal.removeFromInstance,
  3281. highlighted, name, tplAttr, timer;
  3282. if (changes.hasOwnProperty('highlighted')) {
  3283. highlighted = changes.highlighted;
  3284. // Hide `highlighted` and `style` from underlying modifiers.
  3285. delete changes.highlighted;
  3286. if (this._lower) {
  3287. changes = this._lower.pushDown(highlightOriginal, changes);
  3288. }
  3289. changes = this.filterChanges(attr, changes);
  3290. if (highlighted !== attr.highlighted) {
  3291. if (highlighted) {
  3292. // Switching ON.
  3293. // At this time, original should be empty.
  3294. for (name in style) {
  3295. // Remember the values of attributes to revert back to them on unhighlight.
  3296. if (name in changes) {
  3297. // Remember value set by lower level modifiers.
  3298. highlightOriginal[name] = changes[name];
  3299. } else {
  3300. // Remember the original value.
  3301. // If this is a sprite instance and it doesn't have its own
  3302. // 'name' attribute, (i.e. inherits template's attribute value)
  3303. // than we have to get the value for the 'name' attribute from
  3304. // the template's 'targets' object instead of its
  3305. // 'attr' object (which is the prototype of the instance),
  3306. // because the 'name' attribute of the template may be animating.
  3307. // Check out the prepareAttributes method of the Animation
  3308. // modifier for more details on the 'targets' object.
  3309. tplAttr = attr.template && attr.template.ownAttr;
  3310. if (tplAttr && !attr.prototype.hasOwnProperty(name)) {
  3311. removeFromInstance[name] = true;
  3312. highlightOriginal[name] = tplAttr.targets[name];
  3313. } else {
  3314. // Even if a sprite instance has its own property, it may
  3315. // still have to be removed from the instance after
  3316. // unhighlighting is done.
  3317. // Consider a situation where an instance doesn't originally
  3318. // have its own attribute (that is used for highlighting and
  3319. // unhighlighting). It will however have that attribute as
  3320. // its own when the highlight/unhighlight animation is in
  3321. // progress, until the attribute is removed from the instance
  3322. // when the unhighlighting is done.
  3323. // So in a scenario where the instance is highlighted, then
  3324. // unhighlighted (i.e. starts animating back to its original
  3325. // value) and then highlighted again before the unhighlight
  3326. // animation is done, we should still mark the attribute
  3327. // for removal from the instance, if it was our original
  3328. // intention. To tell if it was, we can check the timer
  3329. // for the attribute and see if the 'remove' flag is set.
  3330. timer = highlightOriginal.timers[name];
  3331. if (timer && timer.remove) {
  3332. removeFromInstance[name] = true;
  3333. }
  3334. highlightOriginal[name] = attr[name];
  3335. }
  3336. }
  3337. if (highlightOriginal[name] !== style[name]) {
  3338. changes[name] = style[name];
  3339. }
  3340. }
  3341. } else {
  3342. // Switching OFF.
  3343. for (name in style) {
  3344. if (!(name in changes)) {
  3345. changes[name] = highlightOriginal[name];
  3346. }
  3347. delete highlightOriginal[name];
  3348. }
  3349. changes.removeFromInstance = changes.removeFromInstance || {};
  3350. // Let the higher lever animation modifier know which attributes
  3351. // should be removed from instance when the animation is done.
  3352. Ext.apply(changes.removeFromInstance, removeFromInstance);
  3353. highlightOriginal.removeFromInstance = {};
  3354. }
  3355. changes.highlighted = highlighted;
  3356. }
  3357. } else {
  3358. if (this._lower) {
  3359. changes = this._lower.pushDown(highlightOriginal, changes);
  3360. }
  3361. changes = this.filterChanges(attr, changes);
  3362. }
  3363. return changes;
  3364. },
  3365. popUp: function(attr, changes) {
  3366. changes = this.filterChanges(attr, changes);
  3367. this.callParent([
  3368. attr,
  3369. changes
  3370. ]);
  3371. }
  3372. });
  3373. /**
  3374. * A sprite is a basic primitive from the charts package which represents a graphical
  3375. * object that can be drawn. Sprites are used extensively in the charts package to
  3376. * create the visual elements of each chart. You can also create a desired image by
  3377. * adding one or more sprites to a {@link Ext.draw.Container draw container}.
  3378. *
  3379. * The Sprite class itself is an abstract class and is not meant to be used directly.
  3380. * There are many different kinds of sprites available in the charts package that extend
  3381. * Ext.draw.sprite.Sprite. Each sprite type has various attributes that define how that
  3382. * sprite should look. For example, this is a {@link Ext.draw.sprite.Rect rect} sprite:
  3383. *
  3384. * @example
  3385. * Ext.create({
  3386. * xtype: 'draw',
  3387. * renderTo: document.body,
  3388. * width: 400,
  3389. * height: 400,
  3390. * sprites: [{
  3391. * type: 'rect',
  3392. * x: 50,
  3393. * y: 50,
  3394. * width: 100,
  3395. * height: 100,
  3396. * fillStyle: '#1F6D91'
  3397. * }]
  3398. * });
  3399. *
  3400. * By default, sprites are added to the default 'main' {@link Ext.draw.Surface surface}
  3401. * of the draw container. However, sprites may also be configured with a reference to a
  3402. * specific Ext.draw.Surface when set in the draw container's
  3403. * {@link Ext.draw.Container#cfg-sprites sprites} config. Specifying a surface
  3404. * other than 'main' will create a surface by that name if it does not already exist.
  3405. *
  3406. * @example
  3407. * Ext.create({
  3408. * xtype: 'draw',
  3409. * renderTo: document.body,
  3410. * width: 400,
  3411. * height: 400,
  3412. * sprites: [{
  3413. * type: 'rect',
  3414. * surface: 'anim', // a surface with id "anim" will be created automatically
  3415. * x: 50,
  3416. * y: 50,
  3417. * width: 100,
  3418. * height: 100,
  3419. * fillStyle: '#1F6D91'
  3420. * }]
  3421. * });
  3422. *
  3423. * The ability to have multiple surfaces is useful for performance (and battery life)
  3424. * reasons. Because changes to sprite attributes cause the whole surface (and all
  3425. * sprites in it) to re-render, it makes sense to group sprites by surface, so changes
  3426. * to one group of sprites will only trigger the surface they are in to re-render.
  3427. *
  3428. * You can add a sprite to an existing drawing by adding the sprite to a draw surface.
  3429. *
  3430. * @example
  3431. * var drawCt = Ext.create({
  3432. * xtype: 'draw',
  3433. * renderTo: document.body,
  3434. * width: 400,
  3435. * height: 400
  3436. * });
  3437. *
  3438. * // If the surface name is not specified then 'main' will be used
  3439. * var surface = drawCt.getSurface();
  3440. *
  3441. * surface.add({
  3442. * type: 'rect',
  3443. * x: 50,
  3444. * y: 50,
  3445. * width: 100,
  3446. * height: 100,
  3447. * fillStyle: '#1F6D91'
  3448. * });
  3449. *
  3450. * surface.renderFrame();
  3451. *
  3452. * **Note:** Changes to the sprites on a surface will be not be reflected in the DOM
  3453. * until you call the surface's {@link Ext.draw.Surface#method-renderFrame renderFrame}
  3454. * method. This must be done after adding, removing, or modifying sprites in order to
  3455. * see the changes on-screen.
  3456. *
  3457. * For information on configuring a sprite with an initial transformation see
  3458. * {@link #scaling}, {@link #rotation}, and {@link #translation}.
  3459. *
  3460. * For information on applying a transformation to an existing sprite see the
  3461. * Ext.draw.Matrix class.
  3462. */
  3463. Ext.define('Ext.draw.sprite.Sprite', {
  3464. alias: 'sprite.sprite',
  3465. mixins: {
  3466. observable: 'Ext.mixin.Observable'
  3467. },
  3468. requires: [
  3469. 'Ext.draw.Draw',
  3470. 'Ext.draw.gradient.Gradient',
  3471. 'Ext.draw.sprite.AttributeDefinition',
  3472. 'Ext.draw.modifier.Target',
  3473. 'Ext.draw.modifier.Animation',
  3474. 'Ext.draw.modifier.Highlight'
  3475. ],
  3476. isSprite: true,
  3477. $configStrict: false,
  3478. statics: {
  3479. defaultHitTestOptions: {
  3480. fill: true,
  3481. stroke: true
  3482. },
  3483. //<debug>
  3484. /**
  3485. * Debug rendering options:
  3486. *
  3487. * debug: {
  3488. * bbox: true, // renders the bounding box of the sprite
  3489. * xray: true // renders control points of the path (for Ext.draw.sprite.Path and descendants only)
  3490. * }
  3491. *
  3492. */
  3493. debug: false
  3494. },
  3495. //</debug>
  3496. inheritableStatics: {
  3497. def: {
  3498. processors: {
  3499. //<debug>
  3500. debug: 'default',
  3501. //</debug>
  3502. /**
  3503. * @cfg {String} [strokeStyle="none"] The color of the stroke (a CSS color value).
  3504. */
  3505. strokeStyle: "color",
  3506. /**
  3507. * @cfg {String} [fillStyle="none"] The color of the shape (a CSS color value).
  3508. */
  3509. fillStyle: "color",
  3510. /**
  3511. * @cfg {Number} [strokeOpacity=1] The opacity of the stroke. Limited from 0 to 1.
  3512. */
  3513. strokeOpacity: "limited01",
  3514. /**
  3515. * @cfg {Number} [fillOpacity=1] The opacity of the fill. Limited from 0 to 1.
  3516. */
  3517. fillOpacity: "limited01",
  3518. /**
  3519. * @cfg {Number} [lineWidth=1] The width of the line stroke.
  3520. */
  3521. lineWidth: "number",
  3522. /**
  3523. * @cfg {String} [lineCap="butt"] The style of the line caps.
  3524. */
  3525. lineCap: "enums(butt,round,square)",
  3526. /**
  3527. * @cfg {String} [lineJoin="miter"] The style of the line join.
  3528. */
  3529. lineJoin: "enums(round,bevel,miter)",
  3530. /**
  3531. * @cfg {Array} [lineDash=[]]
  3532. * An even number of non-negative numbers specifying a dash/space sequence.
  3533. * Note that while this is supported in IE8 (VML engine), the behavior is
  3534. * different from Canvas and SVG. Please refer to this document for details:
  3535. * http://msdn.microsoft.com/en-us/library/bb264085(v=vs.85).aspx
  3536. * Although IE9 and IE10 have Canvas support, the 'lineDash'
  3537. * attribute is not supported in those browsers.
  3538. */
  3539. lineDash: "data",
  3540. /**
  3541. * @cfg {Number} [lineDashOffset=0]
  3542. * A number specifying how far into the line dash sequence drawing commences.
  3543. */
  3544. lineDashOffset: "number",
  3545. /**
  3546. * @cfg {Number} [miterLimit=10]
  3547. * Sets the distance between the inner corner and the outer corner where two lines meet.
  3548. */
  3549. miterLimit: "number",
  3550. /**
  3551. * @cfg {String} [shadowColor="none"] The color of the shadow (a CSS color value).
  3552. */
  3553. shadowColor: "color",
  3554. /**
  3555. * @cfg {Number} [shadowOffsetX=0] The offset of the sprite's shadow on the x-axis.
  3556. */
  3557. shadowOffsetX: "number",
  3558. /**
  3559. * @cfg {Number} [shadowOffsetY=0] The offset of the sprite's shadow on the y-axis.
  3560. */
  3561. shadowOffsetY: "number",
  3562. /**
  3563. * @cfg {Number} [shadowBlur=0] The amount blur used on the shadow.
  3564. */
  3565. shadowBlur: "number",
  3566. /**
  3567. * @cfg {Number} [globalAlpha=1] The opacity of the sprite. Limited from 0 to 1.
  3568. */
  3569. globalAlpha: "limited01",
  3570. /**
  3571. * @cfg {String} [globalCompositeOperation=source-over]
  3572. * Indicates how source images are drawn onto a destination image.
  3573. * globalCompositeOperation attribute is not supported by the SVG and VML (excanvas) engines.
  3574. */
  3575. globalCompositeOperation: "enums(source-over,destination-over,source-in,destination-in,source-out,destination-out,source-atop,destination-atop,lighter,xor,copy)",
  3576. /**
  3577. * @cfg {Boolean} [hidden=false] Determines whether or not the sprite is hidden.
  3578. */
  3579. hidden: "bool",
  3580. /**
  3581. * @cfg {Boolean} [transformFillStroke=false]
  3582. * Determines whether the fill and stroke are affected by sprite transformations.
  3583. */
  3584. transformFillStroke: "bool",
  3585. /**
  3586. * @cfg {Number} [zIndex=0]
  3587. * The stacking order of the sprite.
  3588. */
  3589. zIndex: "number",
  3590. /**
  3591. * @cfg {Number} [translationX=0]
  3592. * The translation, position offset, of the sprite on the x-axis.
  3593. *
  3594. * **Note:** Transform configs are *always* performed in the following
  3595. * order:
  3596. *
  3597. * 1. Scaling
  3598. * 2. Rotation
  3599. * 3. Translation
  3600. *
  3601. * See also: {@link #translation} and {@link #translationY}
  3602. */
  3603. translationX: "number",
  3604. /**
  3605. * @cfg {Number} [translationY=0]
  3606. * The translation, position offset, of the sprite on the y-axis.
  3607. *
  3608. * **Note:** Transform configs are *always* performed in the following
  3609. * order:
  3610. *
  3611. * 1. Scaling
  3612. * 2. Rotation
  3613. * 3. Translation
  3614. *
  3615. * See also: {@link #translation} and {@link #translationX}
  3616. */
  3617. translationY: "number",
  3618. /**
  3619. * @cfg {Number} [rotationRads=0]
  3620. * The angle of rotation of the sprite in radians.
  3621. *
  3622. * **Note:** Transform configs are *always* performed in the following
  3623. * order:
  3624. *
  3625. * 1. Scaling
  3626. * 2. Rotation
  3627. * 3. Translation
  3628. *
  3629. * See also: {@link #rotation}, {@link #rotationCenterX}, and
  3630. * {@link #rotationCenterY}
  3631. */
  3632. rotationRads: "number",
  3633. /**
  3634. * @cfg {Number} [rotationCenterX=null]
  3635. * The central coordinate of the sprite's scale operation on the x-axis.
  3636. * Unless explicitly set, will default to the calculated center of the
  3637. * sprite along the x-axis.
  3638. *
  3639. * **Note:** Transform configs are *always* performed in the following
  3640. * order:
  3641. *
  3642. * 1. Scaling
  3643. * 2. Rotation
  3644. * 3. Translation
  3645. *
  3646. * See also: {@link #rotation}, {@link #rotationRads}, and
  3647. * {@link #rotationCenterY}
  3648. */
  3649. rotationCenterX: "number",
  3650. /**
  3651. * @cfg {Number} [rotationCenterY=null]
  3652. * The central coordinate of the sprite's rotate operation on the y-axis.
  3653. * Unless explicitly set, will default to the calculated center of the
  3654. * sprite along the y-axis.
  3655. *
  3656. * **Note:** Transform configs are *always* performed in the following
  3657. * order:
  3658. *
  3659. * 1. Scaling
  3660. * 2. Rotation
  3661. * 3. Translation
  3662. *
  3663. * See also: {@link #rotation}, {@link #rotationRads}, and
  3664. * {@link #rotationCenterX}
  3665. */
  3666. rotationCenterY: "number",
  3667. /**
  3668. * @cfg {Number} [scalingX=1] The scaling of the sprite on the x-axis.
  3669. * The number value represents a percentage by which to scale the
  3670. * sprite. **1** is equal to 100%, **2** would be 200%, etc.
  3671. *
  3672. * **Note:** Transform configs are *always* performed in the following
  3673. * order:
  3674. *
  3675. * 1. Scaling
  3676. * 2. Rotation
  3677. * 3. Translation
  3678. *
  3679. * See also: {@link #scaling}, {@link #scalingY},
  3680. * {@link #scalingCenterX}, and {@link #scalingCenterY}
  3681. */
  3682. scalingX: "number",
  3683. /**
  3684. * @cfg {Number} [scalingY=1] The scaling of the sprite on the y-axis.
  3685. * The number value represents a percentage by which to scale the
  3686. * sprite. **1** is equal to 100%, **2** would be 200%, etc.
  3687. *
  3688. * **Note:** Transform configs are *always* performed in the following
  3689. * order:
  3690. *
  3691. * 1. Scaling
  3692. * 2. Rotation
  3693. * 3. Translation
  3694. *
  3695. * See also: {@link #scaling}, {@link #scalingX},
  3696. * {@link #scalingCenterX}, and {@link #scalingCenterY}
  3697. */
  3698. scalingY: "number",
  3699. /**
  3700. * @cfg {Number} [scalingCenterX=null]
  3701. * The central coordinate of the sprite's scale operation on the x-axis.
  3702. *
  3703. * **Note:** Transform configs are *always* performed in the following
  3704. * order:
  3705. *
  3706. * 1. Scaling
  3707. * 2. Rotation
  3708. * 3. Translation
  3709. *
  3710. * See also: {@link #scaling}, {@link #scalingX},
  3711. * {@link #scalingY}, and {@link #scalingCenterY}
  3712. */
  3713. scalingCenterX: "number",
  3714. /**
  3715. * @cfg {Number} [scalingCenterY=null]
  3716. * The central coordinate of the sprite's scale operation on the y-axis.
  3717. *
  3718. * **Note:** Transform configs are *always* performed in the following
  3719. * order:
  3720. *
  3721. * 1. Scaling
  3722. * 2. Rotation
  3723. * 3. Translation
  3724. *
  3725. * See also: {@link #scaling}, {@link #scalingX},
  3726. * {@link #scalingY}, and {@link #scalingCenterX}
  3727. */
  3728. scalingCenterY: "number",
  3729. constrainGradients: "bool"
  3730. },
  3731. /**
  3732. * @cfg {Number/Object} rotation
  3733. * Applies an initial angle of rotation to the sprite. May be a number
  3734. * specifying the rotation in degrees. Or may be a config object using
  3735. * the below config options.
  3736. *
  3737. * **Note:** Rotation config options will be overridden by values set on
  3738. * the {@link #rotationRads}, {@link #rotationCenterX}, and
  3739. * {@link #rotationCenterY} configs.
  3740. *
  3741. * Ext.create({
  3742. * xtype: 'draw',
  3743. * renderTo: Ext.getBody(),
  3744. * width: 600,
  3745. * height: 400,
  3746. * sprites: [{
  3747. * type: 'rect',
  3748. * x: 50,
  3749. * y: 50,
  3750. * width: 100,
  3751. * height: 100,
  3752. * fillStyle: '#1F6D91',
  3753. * //rotation: 45
  3754. * rotation: {
  3755. * degrees: 45,
  3756. * //rads: Math.PI / 4,
  3757. * //centerX: 50,
  3758. * //centerY: 50
  3759. * }
  3760. * }]
  3761. * });
  3762. *
  3763. * **Note:** Transform configs are *always* performed in the following
  3764. * order:
  3765. *
  3766. * 1. Scaling
  3767. * 2. Rotation
  3768. * 3. Translation
  3769. *
  3770. * @cfg {Number} rotation.rads
  3771. * The angle in radians to rotate the sprite
  3772. *
  3773. * @cfg {Number} rotation.degrees
  3774. * The angle in degrees to rotate the sprite (is ignored if rads or
  3775. * {@link #rotationRads} is set
  3776. *
  3777. * @cfg {Number} rotation.centerX
  3778. * The central coordinate of the sprite's rotation on the x-axis.
  3779. * Unless explicitly set, will default to the calculated center of the
  3780. * sprite along the x-axis.
  3781. *
  3782. * @cfg {Number} rotation.centerY
  3783. * The central coordinate of the sprite's rotation on the y-axis.
  3784. * Unless explicitly set, will default to the calculated center of the
  3785. * sprite along the y-axis.
  3786. */
  3787. /**
  3788. * @cfg {Number/Object} scaling
  3789. * Applies initial scaling to the sprite. May be a number specifying
  3790. * the amount to scale both the x and y-axis. The number value
  3791. * represents a percentage by which to scale the sprite. **1** is equal
  3792. * to 100%, **2** would be 200%, etc. Or may be a config object using
  3793. * the below config options.
  3794. *
  3795. * **Note:** Scaling config options will be overridden by values set on
  3796. * the {@link #scalingX}, {@link #scalingY}, {@link #scalingCenterX},
  3797. * and {@link #scalingCenterY} configs.
  3798. *
  3799. * Ext.create({
  3800. * xtype: 'draw',
  3801. * renderTo: Ext.getBody(),
  3802. * width: 600,
  3803. * height: 400,
  3804. * sprites: [{
  3805. * type: 'rect',
  3806. * x: 50,
  3807. * y: 50,
  3808. * width: 100,
  3809. * height: 100,
  3810. * fillStyle: '#1F6D91',
  3811. * //scaling: 2,
  3812. * scaling: {
  3813. * x: 2,
  3814. * y: 2
  3815. * //centerX: 100,
  3816. * //centerY: 100
  3817. * }
  3818. * }]
  3819. * });
  3820. *
  3821. * **Note:** Transform configs are *always* performed in the following
  3822. * order:
  3823. *
  3824. * 1. Scaling
  3825. * 2. Rotation
  3826. * 3. Translation
  3827. *
  3828. * @cfg {Number} scaling.x
  3829. * The amount by which to scale the sprite along the x-axis. The number
  3830. * value represents a percentage by which to scale the sprite. **1** is
  3831. * equal to 100%, **2** would be 200%, etc.
  3832. *
  3833. * @cfg {Number} scaling.y
  3834. * The amount by which to scale the sprite along the y-axis. The number
  3835. * value represents a percentage by which to scale the sprite. **1** is
  3836. * equal to 100%, **2** would be 200%, etc.
  3837. *
  3838. * @cfg scaling.centerX
  3839. * The central coordinate of the sprite's scaling on the x-axis. Unless
  3840. * explicitly set, will default to the calculated center of the sprite
  3841. * along the x-axis.
  3842. *
  3843. * @cfg {Number} scaling.centerY
  3844. * The central coordinate of the sprite's scaling on the y-axis. Unless
  3845. * explicitly set, will default to the calculated center of the sprite
  3846. * along the y-axis.
  3847. */
  3848. /**
  3849. * @cfg {Object} translation
  3850. * Applies an initial translation, adjustment in x/y positioning, to the
  3851. * sprite.
  3852. *
  3853. * **Note:** Translation config options will be overridden by values set
  3854. * on the {@link #translationX} and {@link #translationY} configs.
  3855. *
  3856. * Ext.create({
  3857. * xtype: 'draw',
  3858. * renderTo: Ext.getBody(),
  3859. * width: 600,
  3860. * height: 400,
  3861. * sprites: [{
  3862. * type: 'rect',
  3863. * x: 50,
  3864. * y: 50,
  3865. * width: 100,
  3866. * height: 100,
  3867. * fillStyle: '#1F6D91',
  3868. * translation: {
  3869. * x: 50,
  3870. * y: 50
  3871. * }
  3872. * }]
  3873. * });
  3874. *
  3875. * **Note:** Transform configs are *always* performed in the following
  3876. * order:
  3877. *
  3878. * 1. Scaling
  3879. * 2. Rotation
  3880. * 3. Translation
  3881. *
  3882. * @cfg {Number} translation.x
  3883. * The amount to translate the sprite along the x-axis.
  3884. *
  3885. * @cfg {Number} translation.y
  3886. * The amount to translate the sprite along the y-axis.
  3887. */
  3888. aliases: {
  3889. "stroke": "strokeStyle",
  3890. "fill": "fillStyle",
  3891. "color": "fillStyle",
  3892. "stroke-width": "lineWidth",
  3893. "stroke-linecap": "lineCap",
  3894. "stroke-linejoin": "lineJoin",
  3895. "stroke-miterlimit": "miterLimit",
  3896. "text-anchor": "textAlign",
  3897. "opacity": "globalAlpha",
  3898. translateX: "translationX",
  3899. translateY: "translationY",
  3900. rotateRads: "rotationRads",
  3901. rotateCenterX: "rotationCenterX",
  3902. rotateCenterY: "rotationCenterY",
  3903. scaleX: "scalingX",
  3904. scaleY: "scalingY",
  3905. scaleCenterX: "scalingCenterX",
  3906. scaleCenterY: "scalingCenterY"
  3907. },
  3908. defaults: {
  3909. hidden: false,
  3910. zIndex: 0,
  3911. strokeStyle: "none",
  3912. fillStyle: "none",
  3913. lineWidth: 1,
  3914. lineDash: [],
  3915. lineDashOffset: 0,
  3916. lineCap: "butt",
  3917. lineJoin: "miter",
  3918. miterLimit: 10,
  3919. shadowColor: "none",
  3920. shadowOffsetX: 0,
  3921. shadowOffsetY: 0,
  3922. shadowBlur: 0,
  3923. globalAlpha: 1,
  3924. strokeOpacity: 1,
  3925. fillOpacity: 1,
  3926. transformFillStroke: false,
  3927. translationX: 0,
  3928. translationY: 0,
  3929. rotationRads: 0,
  3930. rotationCenterX: null,
  3931. rotationCenterY: null,
  3932. scalingX: 1,
  3933. scalingY: 1,
  3934. scalingCenterX: null,
  3935. scalingCenterY: null,
  3936. constrainGradients: false
  3937. },
  3938. triggers: {
  3939. zIndex: "zIndex",
  3940. globalAlpha: "canvas",
  3941. globalCompositeOperation: "canvas",
  3942. transformFillStroke: "canvas",
  3943. strokeStyle: "canvas",
  3944. fillStyle: "canvas",
  3945. strokeOpacity: "canvas",
  3946. fillOpacity: "canvas",
  3947. lineWidth: "canvas",
  3948. lineCap: "canvas",
  3949. lineJoin: "canvas",
  3950. lineDash: "canvas",
  3951. lineDashOffset: "canvas",
  3952. miterLimit: "canvas",
  3953. shadowColor: "canvas",
  3954. shadowOffsetX: "canvas",
  3955. shadowOffsetY: "canvas",
  3956. shadowBlur: "canvas",
  3957. translationX: "transform",
  3958. translationY: "transform",
  3959. rotationRads: "transform",
  3960. rotationCenterX: "transform",
  3961. rotationCenterY: "transform",
  3962. scalingX: "transform",
  3963. scalingY: "transform",
  3964. scalingCenterX: "transform",
  3965. scalingCenterY: "transform",
  3966. constrainGradients: "canvas"
  3967. },
  3968. updaters: {
  3969. // 'bbox' updater is meant to be called by subclasses when changes
  3970. // to attributes are expected to result in a change in sprite's dimensions.
  3971. bbox: 'bboxUpdater',
  3972. zIndex: function(attr) {
  3973. attr.dirtyZIndex = true;
  3974. },
  3975. transform: function(attr) {
  3976. attr.dirtyTransform = true;
  3977. attr.bbox.transform.dirty = true;
  3978. }
  3979. }
  3980. }
  3981. },
  3982. /**
  3983. * @property {Object} attr
  3984. * The visual attributes of the sprite, e.g. strokeStyle, fillStyle, lineWidth...
  3985. */
  3986. /**
  3987. * @cfg {Ext.draw.modifier.Animation} animation
  3988. * @accessor
  3989. */
  3990. config: {
  3991. /**
  3992. * @private
  3993. * @cfg {Ext.draw.Surface/Ext.draw.sprite.Instancing/Ext.draw.sprite.Composite} parent
  3994. * The immediate parent of the sprite. Not necessarily a surface.
  3995. */
  3996. parent: null,
  3997. /**
  3998. * @private
  3999. * @cfg {Ext.draw.Surface} surface
  4000. * The surface that this sprite is rendered into.
  4001. * This config is not meant to be used directly.
  4002. * Please use the {@link Ext.draw.Surface#add} method instead.
  4003. */
  4004. surface: null
  4005. },
  4006. onClassExtended: function(subClass, data) {
  4007. // The `def` here is no longer a config, but an instance
  4008. // of the AttributeDefinition class created with that config,
  4009. // which can now be retrieved from `initialConfig`.
  4010. var superclassCfg = subClass.superclass.self.def.initialConfig,
  4011. ownCfg = data.inheritableStatics && data.inheritableStatics.def,
  4012. cfg;
  4013. // If sprite defines attributes of its own, merge that with those of its parent.
  4014. if (ownCfg) {
  4015. cfg = Ext.Object.merge({}, superclassCfg, ownCfg);
  4016. subClass.def = new Ext.draw.sprite.AttributeDefinition(cfg);
  4017. delete data.inheritableStatics.def;
  4018. } else {
  4019. subClass.def = new Ext.draw.sprite.AttributeDefinition(superclassCfg);
  4020. }
  4021. subClass.def.spriteClass = subClass;
  4022. },
  4023. constructor: function(config) {
  4024. //<debug>
  4025. if (Ext.getClassName(this) === 'Ext.draw.sprite.Sprite') {
  4026. throw 'Ext.draw.sprite.Sprite is an abstract class';
  4027. }
  4028. //</debug>
  4029. var me = this,
  4030. attributeDefinition = me.self.def,
  4031. // It is important to get defaults (make sure
  4032. // 'defaults' config applier of the AttributeDefinition is called,
  4033. // since it is initialized lazily) before the attributes
  4034. // are initialized ('initializeAttributes' call).
  4035. defaults = attributeDefinition.getDefaults(),
  4036. processors = attributeDefinition.getProcessors(),
  4037. modifiers, name;
  4038. config = Ext.isObject(config) ? config : {};
  4039. me.id = config.id || Ext.id(null, 'ext-sprite-');
  4040. me.attr = {};
  4041. // Observable's constructor also calls the initConfig for us.
  4042. me.mixins.observable.constructor.apply(me, arguments);
  4043. modifiers = Ext.Array.from(config.modifiers, true);
  4044. me.createModifiers(modifiers);
  4045. me.initializeAttributes();
  4046. me.setAttributes(defaults, true);
  4047. //<debug>
  4048. for (name in config) {
  4049. if (name in processors && me['get' + name.charAt(0).toUpperCase() + name.substr(1)]) {
  4050. Ext.raise('The ' + me.$className + ' sprite has both a config and an attribute with the same name: ' + name + '.');
  4051. }
  4052. }
  4053. //</debug>
  4054. me.setAttributes(config);
  4055. },
  4056. updateSurface: function(surface, oldSurface) {
  4057. if (oldSurface) {
  4058. oldSurface.remove(this);
  4059. }
  4060. },
  4061. /**
  4062. * @private
  4063. * Current state of the sprite.
  4064. * Set to `true` if the sprite needs to be repainted.
  4065. * @cfg {Boolean} dirty
  4066. * @accessor
  4067. */
  4068. getDirty: function() {
  4069. return this.attr.dirty;
  4070. },
  4071. setDirty: function(dirty) {
  4072. // This could have been a regular attribute.
  4073. // Instead, it's a hidden one, which is initialized inside in the
  4074. // Target's modifier `prepareAttributes` method and is exposed
  4075. // as a config. The idea is to skip the modifier chain when
  4076. // we simply need to change the sprite's state and notify
  4077. // the sprite's parent.
  4078. this.attr.dirty = dirty;
  4079. if (dirty) {
  4080. var parent = this.getParent();
  4081. if (parent) {
  4082. parent.setDirty(true);
  4083. }
  4084. }
  4085. },
  4086. addModifier: function(modifier, reinitializeAttributes) {
  4087. var me = this,
  4088. mods = me.modifiers,
  4089. animation = mods.animation,
  4090. target = mods.target,
  4091. type;
  4092. if (!(modifier instanceof Ext.draw.modifier.Modifier)) {
  4093. type = typeof modifier === 'string' ? modifier : modifier.type;
  4094. if (type && !mods[type]) {
  4095. mods[type] = modifier = Ext.factory(modifier, null, null, 'modifier');
  4096. }
  4097. }
  4098. modifier.setSprite(me);
  4099. if (modifier.preFx || modifier.config && modifier.config.preFx) {
  4100. if (animation._lower) {
  4101. animation._lower.setUpper(modifier);
  4102. }
  4103. modifier.setUpper(animation);
  4104. } else {
  4105. target._lower.setUpper(modifier);
  4106. modifier.setUpper(target);
  4107. }
  4108. if (reinitializeAttributes) {
  4109. me.initializeAttributes();
  4110. }
  4111. return modifier;
  4112. },
  4113. createModifiers: function(modifiers) {
  4114. var me = this,
  4115. Modifier = Ext.draw.modifier,
  4116. animation = me.getInitialConfig().animation,
  4117. mods, i, ln;
  4118. // Create default modifiers.
  4119. me.modifiers = mods = {
  4120. target: new Modifier.Target({
  4121. sprite: me
  4122. }),
  4123. animation: new Modifier.Animation(Ext.apply({
  4124. sprite: me
  4125. }, animation))
  4126. };
  4127. // Link modifiers.
  4128. mods.animation.setUpper(mods.target);
  4129. for (i = 0 , ln = modifiers.length; i < ln; i++) {
  4130. me.addModifier(modifiers[i], false);
  4131. }
  4132. return mods;
  4133. },
  4134. /**
  4135. * Returns the current animation instance.
  4136. * return {Ext.draw.modifier.Animation} The animation modifier used to animate the
  4137. * sprite
  4138. */
  4139. getAnimation: function() {
  4140. return this.modifiers.animation;
  4141. },
  4142. /**
  4143. * Sets the animation config used by the sprite when animating the sprite's
  4144. * attributes and transformation properties.
  4145. *
  4146. * var drawCt = Ext.create({
  4147. * xtype: 'draw',
  4148. * renderTo: document.body,
  4149. * width: 400,
  4150. * height: 400,
  4151. * sprites: [{
  4152. * type: 'rect',
  4153. * x: 50,
  4154. * y: 50,
  4155. * width: 100,
  4156. * height: 100,
  4157. * fillStyle: '#1F6D91'
  4158. * }]
  4159. * });
  4160. *
  4161. * var rect = drawCt.getSurface().getItems()[0];
  4162. *
  4163. * rect.setAnimation({
  4164. * duration: 1000,
  4165. * easing: 'elasticOut'
  4166. * });
  4167. *
  4168. * Ext.defer(function () {
  4169. * rect.setAttributes({
  4170. * width: 250
  4171. * });
  4172. * }, 500);
  4173. *
  4174. * @param {Object} config The Ext.draw.modifier.Animation config for this sprite's
  4175. * animations.
  4176. */
  4177. setAnimation: function(config) {
  4178. if (!this.isConfiguring) {
  4179. this.modifiers.animation.setConfig(config || {
  4180. duration: 0
  4181. });
  4182. }
  4183. },
  4184. initializeAttributes: function() {
  4185. this.modifiers.target.prepareAttributes(this.attr);
  4186. },
  4187. /**
  4188. * @private
  4189. * Calls updaters triggered by changes to sprite attributes.
  4190. * @param attr The attributes of a sprite or its instance.
  4191. */
  4192. callUpdaters: function(attr) {
  4193. attr = attr || this.attr;
  4194. var me = this,
  4195. pendingUpdaters = attr.pendingUpdaters,
  4196. updaters = me.self.def.getUpdaters(),
  4197. any = false,
  4198. dirty = false,
  4199. flags, updater, fn;
  4200. // If updaters set sprite attributes that trigger other updaters,
  4201. // those updaters are not called right away, but wait until all current
  4202. // updaters are called (till the next do/while loop iteration).
  4203. me.callUpdaters = Ext.emptyFn;
  4204. // Hide class method from the instance.
  4205. do {
  4206. any = false;
  4207. for (updater in pendingUpdaters) {
  4208. any = true;
  4209. flags = pendingUpdaters[updater];
  4210. delete pendingUpdaters[updater];
  4211. fn = updaters[updater];
  4212. if (typeof fn === 'string') {
  4213. fn = me[fn];
  4214. }
  4215. if (fn) {
  4216. fn.call(me, attr, flags);
  4217. }
  4218. }
  4219. dirty = dirty || any;
  4220. } while (any);
  4221. delete me.callUpdaters;
  4222. // Restore class method.
  4223. if (dirty) {
  4224. me.setDirty(true);
  4225. }
  4226. },
  4227. /**
  4228. * @private
  4229. */
  4230. callUpdater: function(attr, updater, triggers) {
  4231. this.scheduleUpdater(attr, updater, triggers);
  4232. this.callUpdaters(attr);
  4233. },
  4234. /**
  4235. * @private
  4236. * Schedules specified updaters to be called.
  4237. * Updaters are called implicitly as a result of a change to sprite attributes.
  4238. * But sometimes it may be required to call an updater without setting an attribute,
  4239. * and without messing up the updater call order (by calling the updater immediately).
  4240. * For example:
  4241. *
  4242. * updaters: {
  4243. * onDataX: function (attr) {
  4244. * this.processDataX();
  4245. * // Process data Y every time data X is processed.
  4246. * // Call the onDataY updater as if changes to dataY attribute itself
  4247. * // triggered the update.
  4248. * this.scheduleUpdaters(attr, {onDataY: ['dataY']});
  4249. * // Alternatively:
  4250. * // this.scheduleUpdaters(attr, ['onDataY'], ['dataY']);
  4251. * }
  4252. * }
  4253. *
  4254. * @param {Object} attr The attributes object (not necesseraly of a sprite, but of its instance).
  4255. * @param {Object/String[]} updaters A map of updaters to be called to attributes that triggered the update.
  4256. * @param {String[]} [triggers] Attributes that triggered the update. An optional parameter.
  4257. * If used, the `updaters` parameter will be treated as an array of updaters to be called.
  4258. */
  4259. scheduleUpdaters: function(attr, updaters, triggers) {
  4260. var updater;
  4261. attr = attr || this.attr;
  4262. if (triggers) {
  4263. for (var i = 0,
  4264. ln = updaters.length; i < ln; i++) {
  4265. updater = updaters[i];
  4266. this.scheduleUpdater(attr, updater, triggers);
  4267. }
  4268. } else {
  4269. for (updater in updaters) {
  4270. triggers = updaters[updater];
  4271. this.scheduleUpdater(attr, updater, triggers);
  4272. }
  4273. }
  4274. },
  4275. /**
  4276. * @private
  4277. * @param attr {Object} The attributes object (not necesseraly of a sprite, but of its instance).
  4278. * @param updater {String} Updater to be called.
  4279. * @param {String[]} [triggers] Attributes that triggered the update.
  4280. */
  4281. scheduleUpdater: function(attr, updater, triggers) {
  4282. triggers = triggers || [];
  4283. attr = attr || this.attr;
  4284. var pendingUpdaters = attr.pendingUpdaters;
  4285. if (updater in pendingUpdaters) {
  4286. if (triggers.length) {
  4287. pendingUpdaters[updater] = Ext.Array.merge(pendingUpdaters[updater], triggers);
  4288. }
  4289. } else {
  4290. pendingUpdaters[updater] = triggers;
  4291. }
  4292. },
  4293. /**
  4294. * Set attributes of the sprite.
  4295. * By default only the attributes that have processors will be set
  4296. * and all other attributes will be filtered out as a result of the
  4297. * normalization process.
  4298. * The normalization process can be skipped. In that case all the given
  4299. * attributes will be set unprocessed. This will result in better
  4300. * performance, but might also pollute the sprite's attributes with
  4301. * unwanted attributes or attributes with invalid values, if one is not
  4302. * careful. See also {@link #setAttributesBypassingNormalization}.
  4303. * If normalization is skipped, one may also chose to avoid copying
  4304. * the given object. This may result in even better performance, but
  4305. * only in cases where most of the attributes have values that are
  4306. * different from the old values, because copying additionally checks
  4307. * if the value has changed.
  4308. *
  4309. * @param {Object} changes The content of the change.
  4310. * @param {Boolean} [bypassNormalization] `true` to avoid normalization of the given changes.
  4311. * @param {Boolean} [avoidCopy] `true` to avoid copying the `changes` object.
  4312. * `bypassNormalization` should also be `true`. The content of object may be destroyed.
  4313. */
  4314. setAttributes: function(changes, bypassNormalization, avoidCopy) {
  4315. var me = this,
  4316. changesToPush;
  4317. //<debug>
  4318. if (me.destroyed) {
  4319. Ext.Error.raise("Setting attributes of a destroyed sprite.");
  4320. }
  4321. //</debug>
  4322. if (bypassNormalization) {
  4323. if (avoidCopy) {
  4324. changesToPush = changes;
  4325. } else {
  4326. changesToPush = Ext.apply({}, changes);
  4327. }
  4328. } else {
  4329. changesToPush = me.self.def.normalize(changes);
  4330. }
  4331. me.modifiers.target.pushDown(me.attr, changesToPush);
  4332. },
  4333. /**
  4334. * Set attributes of the sprite, assuming the names and values have already been
  4335. * normalized.
  4336. *
  4337. * @deprecated 6.5.0 Use setAttributes directly with bypassNormalization argument being `true`.
  4338. * @param {Object} changes The content of the change.
  4339. * @param {Boolean} [avoidCopy] `true` to avoid copying the `changes` object.
  4340. * The content of object may be destroyed.
  4341. */
  4342. setAttributesBypassingNormalization: function(changes, avoidCopy) {
  4343. return this.setAttributes(changes, true, avoidCopy);
  4344. },
  4345. /**
  4346. * @private
  4347. */
  4348. bboxUpdater: function(attr) {
  4349. var hasRotation = attr.rotationRads !== 0,
  4350. hasScaling = attr.scalingX !== 1 || attr.scalingY !== 1,
  4351. noRotationCenter = attr.rotationCenterX === null || attr.rotationCenterY === null,
  4352. noScalingCenter = attr.scalingCenterX === null || attr.scalingCenterY === null;
  4353. // 'bbox' is not a standard attribute (in the sense that it doesn't have
  4354. // a processor = not explicitly declared and cannot be set by a user)
  4355. // and is calculated automatically by the 'getBBox' method.
  4356. // The 'bbox' attribute is created by the 'prepareAttributes' method
  4357. // of the Target modifier at construction time.
  4358. // Both plain and tranformed bounding boxes need to be updated.
  4359. // Mark them as such below.
  4360. attr.bbox.plain.dirty = true;
  4361. // updated by the 'updatePlainBBox' method
  4362. // Before transformed bounding box can be updated,
  4363. // we must ensure that we have correct forward and inverse
  4364. // transformation matrices (which are also created by the Target modifier),
  4365. // so that they reflect the current state of the scaling, rotation
  4366. // and other transformation attributes.
  4367. // The 'applyTransformations' method does just that.
  4368. // The 'dirtyTransform' flag (another implicit attribute)
  4369. // is set to true when any of the transformation attributes change,
  4370. // to let us know that transformation matrices need to be updated.
  4371. attr.bbox.transform.dirty = true;
  4372. // updated by the 'updateTransformedBBox' method
  4373. if (hasRotation && noRotationCenter || hasScaling && noScalingCenter) {
  4374. this.scheduleUpdater(attr, 'transform');
  4375. }
  4376. },
  4377. /**
  4378. * Returns the bounding box for the given Sprite as calculated with the Canvas engine.
  4379. *
  4380. * @param {Boolean} [isWithoutTransform] Whether to calculate the bounding box with the current transforms or not.
  4381. */
  4382. getBBox: function(isWithoutTransform) {
  4383. var me = this,
  4384. attr = me.attr,
  4385. bbox = attr.bbox,
  4386. plain = bbox.plain,
  4387. transform = bbox.transform;
  4388. if (plain.dirty) {
  4389. me.updatePlainBBox(plain);
  4390. plain.dirty = false;
  4391. }
  4392. if (!isWithoutTransform) {
  4393. // If tranformations are to be applied ('dirtyTransform' is true),
  4394. // then this will itself call the 'getBBox' method
  4395. // to get the plain untransformed bbox and calculate its center.
  4396. me.applyTransformations();
  4397. if (transform.dirty) {
  4398. me.updateTransformedBBox(transform, plain);
  4399. transform.dirty = false;
  4400. }
  4401. return transform;
  4402. }
  4403. return plain;
  4404. },
  4405. /**
  4406. * @method
  4407. * @protected
  4408. * Subclass will fill the plain object with `x`, `y`, `width`, `height` information
  4409. * of the plain bounding box of this sprite.
  4410. *
  4411. * @param {Object} plain Target object.
  4412. */
  4413. updatePlainBBox: Ext.emptyFn,
  4414. /**
  4415. * @protected
  4416. * Subclass will fill the plain object with `x`, `y`, `width`, `height` information
  4417. * of the transformed bounding box of this sprite.
  4418. *
  4419. * @param {Object} transform Target object (transformed bounding box) to populate.
  4420. * @param {Object} plain Untransformed bounding box.
  4421. */
  4422. updateTransformedBBox: function(transform, plain) {
  4423. this.attr.matrix.transformBBox(plain, 0, transform);
  4424. },
  4425. /**
  4426. * Subclass can rewrite this function to gain better performance.
  4427. * @param {Boolean} isWithoutTransform
  4428. * @return {Array}
  4429. */
  4430. getBBoxCenter: function(isWithoutTransform) {
  4431. var bbox = this.getBBox(isWithoutTransform);
  4432. if (bbox) {
  4433. return [
  4434. bbox.x + bbox.width * 0.5,
  4435. bbox.y + bbox.height * 0.5
  4436. ];
  4437. } else {
  4438. return [
  4439. 0,
  4440. 0
  4441. ];
  4442. }
  4443. },
  4444. /**
  4445. * Hide the sprite.
  4446. * @return {Ext.draw.sprite.Sprite} this
  4447. * @chainable
  4448. */
  4449. hide: function() {
  4450. this.attr.hidden = true;
  4451. this.setDirty(true);
  4452. return this;
  4453. },
  4454. /**
  4455. * Show the sprite.
  4456. * @return {Ext.draw.sprite.Sprite} this
  4457. * @chainable
  4458. */
  4459. show: function() {
  4460. this.attr.hidden = false;
  4461. this.setDirty(true);
  4462. return this;
  4463. },
  4464. /**
  4465. * Applies sprite's attributes to the given context.
  4466. * @param {Object} ctx Context to apply sprite's attributes to.
  4467. * @param {Array} rect The rect of the context to be affected by gradients.
  4468. */
  4469. useAttributes: function(ctx, rect) {
  4470. // Always (force) apply transformation to sprite instances,
  4471. // even if their 'dirtyTransform' flag is false.
  4472. // The 'dirtyTransform' flag of an instance may never be set to 'true', as the
  4473. // 'transform' updater won't ever be called for sprite instances that have
  4474. // the same transform attributes as their template, because there's nothing to update
  4475. // (an instance is simply a prototype chained template's 'attr' object, that only
  4476. // has own properties for attributes whose values are different).
  4477. // Making the modifier recognize transform attributes set on sprite instances
  4478. // (see Ext.draw.modifier.Modifier's 'pushDown' method, where attributes with
  4479. // same values are removed from the 'changes' object) and making sure their 'dirtyTransform'
  4480. // flag is set to 'true' is not a correct solution here, because of the way instances
  4481. // are rendered (see Ext.draw.sprite.Instancing's 'render' method) - there is no way
  4482. // an instance wounldn't want its 'applyTransformations' method called.
  4483. this.applyTransformations(this.isSpriteInstance);
  4484. var attr = this.attr,
  4485. canvasAttributes = attr.canvasAttributes,
  4486. strokeStyle = canvasAttributes.strokeStyle,
  4487. fillStyle = canvasAttributes.fillStyle,
  4488. lineDash = canvasAttributes.lineDash,
  4489. lineDashOffset = canvasAttributes.lineDashOffset,
  4490. id;
  4491. if (strokeStyle) {
  4492. if (strokeStyle.isGradient) {
  4493. ctx.strokeStyle = 'black';
  4494. ctx.strokeGradient = strokeStyle;
  4495. } else {
  4496. ctx.strokeGradient = false;
  4497. }
  4498. }
  4499. if (fillStyle) {
  4500. if (fillStyle.isGradient) {
  4501. ctx.fillStyle = 'black';
  4502. ctx.fillGradient = fillStyle;
  4503. } else {
  4504. ctx.fillGradient = false;
  4505. }
  4506. }
  4507. if (lineDash) {
  4508. ctx.setLineDash(lineDash);
  4509. }
  4510. // Only set lineDashOffset to contexts that support the property (excludes VML).
  4511. if (Ext.isNumber(lineDashOffset) && Ext.isNumber(ctx.lineDashOffset)) {
  4512. ctx.lineDashOffset = lineDashOffset;
  4513. }
  4514. for (id in canvasAttributes) {
  4515. if (canvasAttributes[id] !== undefined && canvasAttributes[id] !== ctx[id]) {
  4516. ctx[id] = canvasAttributes[id];
  4517. }
  4518. }
  4519. this.setGradientBBox(ctx, rect);
  4520. },
  4521. setGradientBBox: function(ctx, rect) {
  4522. var attr = this.attr;
  4523. if (attr.constrainGradients) {
  4524. ctx.setGradientBBox({
  4525. x: rect[0],
  4526. y: rect[1],
  4527. width: rect[2],
  4528. height: rect[3]
  4529. });
  4530. } else {
  4531. ctx.setGradientBBox(this.getBBox(attr.transformFillStroke));
  4532. }
  4533. },
  4534. /**
  4535. * @private
  4536. *
  4537. * Calculates forward and inverse transform matrices from sprite's attributes.
  4538. * Transformations are applied in the following order: Scaling, Rotation, Translation.
  4539. * @param {Boolean} [force=false] Forces recalculation of transform matrices even when
  4540. * sprite's transform attributes supposedly haven't changed.
  4541. */
  4542. applyTransformations: function(force) {
  4543. if (!force && !this.attr.dirtyTransform) {
  4544. return;
  4545. }
  4546. var me = this,
  4547. attr = me.attr,
  4548. center = me.getBBoxCenter(true),
  4549. centerX = center[0],
  4550. centerY = center[1],
  4551. tx = attr.translationX,
  4552. ty = attr.translationY,
  4553. sx = attr.scalingX,
  4554. sy = attr.scalingY === null ? attr.scalingX : attr.scalingY,
  4555. scx = attr.scalingCenterX === null ? centerX : attr.scalingCenterX,
  4556. scy = attr.scalingCenterY === null ? centerY : attr.scalingCenterY,
  4557. rad = attr.rotationRads,
  4558. rcx = attr.rotationCenterX === null ? centerX : attr.rotationCenterX,
  4559. rcy = attr.rotationCenterY === null ? centerY : attr.rotationCenterY,
  4560. cos = Math.cos(rad),
  4561. sin = Math.sin(rad),
  4562. tx_4, ty_4;
  4563. if (sx === 1 && sy === 1) {
  4564. scx = 0;
  4565. scy = 0;
  4566. }
  4567. if (rad === 0) {
  4568. rcx = 0;
  4569. rcy = 0;
  4570. }
  4571. // Translation component after steps 1-4 (see below).
  4572. // Saving it here to prevent double calculation.
  4573. tx_4 = scx * (1 - sx) - rcx;
  4574. ty_4 = scy * (1 - sy) - rcy;
  4575. // The matrix below is a result of:
  4576. // (7) (6) (5) (4) (3) (2) (1)
  4577. // | 1 0 tx | | 1 0 rcx | | cos -sin 0 | | 1 0 -rcx | | 1 0 scx | | sx 0 0 | | 1 0 -scx |
  4578. // | 0 1 ty | * | 0 1 rcy | * | sin cos 0 | * | 0 1 -rcy | * | 0 1 scy | * | 0 sy 0 | * | 0 1 -scy |
  4579. // | 0 0 1 | | 0 0 1 | | 0 0 1 | | 0 0 1 | | 0 0 1 | | 0 0 0 | | 0 0 1 |
  4580. attr.matrix.elements = [
  4581. cos * sx,
  4582. sin * sx,
  4583. -sin * sy,
  4584. cos * sy,
  4585. cos * tx_4 - sin * ty_4 + rcx + tx,
  4586. sin * tx_4 + cos * ty_4 + rcy + ty
  4587. ];
  4588. attr.matrix.inverse(attr.inverseMatrix);
  4589. attr.dirtyTransform = false;
  4590. attr.bbox.transform.dirty = true;
  4591. },
  4592. /**
  4593. * Pre-multiplies the current transformation matrix of a sprite with the given matrix.
  4594. * If `isSplit` parameter is `true`, the resulting matrix is also split into
  4595. * individual components (scaling, rotation, translation) and corresponding sprite
  4596. * attributes are updated. The shearing component is not extracted.
  4597. * Note, that transformation attributes work as if transformations are applied to the
  4598. * local coordinate system of a sprite, while matrix transformations transform
  4599. * the global coordinate space or the surface grid.
  4600. * Since the `transform` method returns the sprite itself, calls to the method
  4601. * can be chained. And if updating sprite transformation attributes is desired,
  4602. * it can be achieved by setting the `isSplit` parameter of the last call to `true`.
  4603. * For example:
  4604. *
  4605. * sprite.transform(matrixA).transform(matrixB).transform(matrixC, true);
  4606. *
  4607. * See also: {@link #setTransform}
  4608. *
  4609. * @param {Ext.draw.Matrix/Number[]} matrix A transformation matrix or array of its elements.
  4610. * @param {Boolean} [isSplit=false] If 'true', transformation attributes are updated.
  4611. * @return {Ext.draw.sprite.Sprite} This sprite.
  4612. */
  4613. transform: function(matrix, isSplit) {
  4614. var attr = this.attr,
  4615. spriteMatrix = attr.matrix,
  4616. elements;
  4617. if (matrix && matrix.isMatrix) {
  4618. elements = matrix.elements;
  4619. } else {
  4620. elements = matrix;
  4621. }
  4622. //<debug>
  4623. if (!(Ext.isArray(elements) && elements.length === 6)) {
  4624. Ext.raise("An instance of Ext.draw.Matrix or an array of 6 numbers is expected.");
  4625. }
  4626. //</debug>
  4627. spriteMatrix.prepend.apply(spriteMatrix, elements.slice());
  4628. spriteMatrix.inverse(attr.inverseMatrix);
  4629. if (isSplit) {
  4630. this.updateTransformAttributes();
  4631. }
  4632. attr.dirtyTransform = false;
  4633. attr.bbox.transform.dirty = true;
  4634. this.setDirty(true);
  4635. return this;
  4636. },
  4637. /**
  4638. * @private
  4639. */
  4640. updateTransformAttributes: function() {
  4641. var attr = this.attr,
  4642. split = attr.matrix.split();
  4643. attr.rotationRads = split.rotate;
  4644. attr.rotationCenterX = 0;
  4645. attr.rotationCenterY = 0;
  4646. attr.scalingX = split.scaleX;
  4647. attr.scalingY = split.scaleY;
  4648. attr.scalingCenterX = 0;
  4649. attr.scalingCenterY = 0;
  4650. attr.translationX = split.translateX;
  4651. attr.translationY = split.translateY;
  4652. },
  4653. /**
  4654. * Resets current transformation matrix of a sprite to the identify matrix.
  4655. * @param {Boolean} [isSplit=false] If 'true', transformation attributes are updated.
  4656. * @return {Ext.draw.sprite.Sprite} This sprite.
  4657. */
  4658. resetTransform: function(isSplit) {
  4659. var attr = this.attr;
  4660. attr.matrix.reset();
  4661. attr.inverseMatrix.reset();
  4662. if (!isSplit) {
  4663. this.updateTransformAttributes();
  4664. }
  4665. attr.dirtyTransform = false;
  4666. attr.bbox.transform.dirty = true;
  4667. this.setDirty(true);
  4668. return this;
  4669. },
  4670. /**
  4671. * Resets current transformation matrix of a sprite to the identify matrix
  4672. * and pre-multiplies it with the given matrix.
  4673. * This is effectively the same as calling {@link #resetTransform},
  4674. * followed by {@link #transform} with the same arguments.
  4675. *
  4676. * See also: {@link #transform}
  4677. *
  4678. * var drawContainer = new Ext.draw.Container({
  4679. * renderTo: Ext.getBody(),
  4680. * width: 380,
  4681. * height: 380,
  4682. * sprites: [{
  4683. * type: 'rect',
  4684. * width: 100,
  4685. * height: 100,
  4686. * fillStyle: 'red'
  4687. * }]
  4688. * });
  4689. *
  4690. * var main = drawContainer.getSurface();
  4691. * var rect = main.getItems()[0];
  4692. *
  4693. * var m = new Ext.draw.Matrix().rotate(Math.PI, 100, 100);
  4694. *
  4695. * rect.setTransform(m);
  4696. * main.renderFrame();
  4697. *
  4698. * There may be times where the transformation you need to apply cannot easily be
  4699. * accomplished using the sprite’s convenience transform methods. Or, you may want
  4700. * to pass a matrix directly to the sprite in order to set a transformation. The
  4701. * `setTransform` method allows for this sort of advanced usage as well. The
  4702. * following tables show each transformation matrix used when applying
  4703. * transformations to a sprite.
  4704. *
  4705. * ### Translate
  4706. * <table style="text-align: center;">
  4707. * <tr>
  4708. * <td style="font-weight: normal;">1</td>
  4709. * <td style="font-weight: normal;">0</td>
  4710. * <td style="font-weight: normal;">tx</td>
  4711. * </tr>
  4712. * <tr>
  4713. * <td>0</td>
  4714. * <td>1</td>
  4715. * <td>ty</td>
  4716. * </tr>
  4717. * <tr>
  4718. * <td>0</td>
  4719. * <td>0</td>
  4720. * <td>1</td>
  4721. * </tr>
  4722. * </table>
  4723. *
  4724. * ### Rotate (θ is the angle of rotation)
  4725. * <table style="text-align: center;">
  4726. * <tr>
  4727. * <td style="font-weight: normal;">cos(θ)</td>
  4728. * <td style="font-weight: normal;">-sin(θ)</td>
  4729. * <td style="font-weight: normal;">0</td>
  4730. * </tr>
  4731. * <tr>
  4732. * <td>0</td>
  4733. * <td>cos(θ)</td>
  4734. * <td>0</td>
  4735. * </tr>
  4736. * <tr>
  4737. * <td>0</td>
  4738. * <td>0</td>
  4739. * <td>1</td>
  4740. * </tr>
  4741. * </table>
  4742. *
  4743. * ### Scale
  4744. * <table style="text-align: center;">
  4745. * <tr>
  4746. * <td style="font-weight: normal;">sx</td>
  4747. * <td style="font-weight: normal;">0</td>
  4748. * <td style="font-weight: normal;">0</td>
  4749. * </tr>
  4750. * <tr>
  4751. * <td>0</td>
  4752. * <td>cos(θ)</td>
  4753. * <td>0</td>
  4754. * </tr>
  4755. * <tr>
  4756. * <td>0</td>
  4757. * <td>0</td>
  4758. * <td>1</td>
  4759. * </tr>
  4760. * </table>
  4761. *
  4762. * ### Shear X _(λ is the distance on the x axis to shear by)_
  4763. * <table style="text-align: center;">
  4764. * <tr>
  4765. * <td style="font-weight: normal;">1</td>
  4766. * <td style="font-weight: normal;">λx</td>
  4767. * <td style="font-weight: normal;">0</td>
  4768. * </tr>
  4769. * <tr>
  4770. * <td>0</td>
  4771. * <td>1</td>
  4772. * <td>0</td>
  4773. * </tr>
  4774. * <tr>
  4775. * <td>0</td>
  4776. * <td>0</td>
  4777. * <td>1</td>
  4778. * </tr>
  4779. * </table>
  4780. *
  4781. * ### Shear Y (λ is the distance on the y axis to shear by)
  4782. * <table style="text-align: center;">
  4783. * <tr>
  4784. * <td style="font-weight: normal;">1</td>
  4785. * <td style="font-weight: normal;">0</td>
  4786. * <td style="font-weight: normal;">0</td>
  4787. * </tr>
  4788. * <tr>
  4789. * <td>λy</td>
  4790. * <td>1</td>
  4791. * <td>0</td>
  4792. * </tr>
  4793. * <tr>
  4794. * <td>0</td>
  4795. * <td>0</td>
  4796. * <td>1</td>
  4797. * </tr>
  4798. * </table>
  4799. *
  4800. * ### Skew X (θ is the angle to skew by)
  4801. * <table style="text-align: center;">
  4802. * <tr>
  4803. * <td style="font-weight: normal;">1</td>
  4804. * <td style="font-weight: normal;">tan(θ)</td>
  4805. * <td style="font-weight: normal;">0</td>
  4806. * </tr>
  4807. * <tr>
  4808. * <td>0</td>
  4809. * <td>1</td>
  4810. * <td>0</td>
  4811. * </tr>
  4812. * <tr>
  4813. * <td>0</td>
  4814. * <td>0</td>
  4815. * <td>1</td>
  4816. * </tr>
  4817. * </table>
  4818. *
  4819. * ### Skew Y (θ is the angle to skew by)
  4820. * <table style="text-align: center;">
  4821. * <tr>
  4822. * <td style="font-weight: normal;">1</td>
  4823. * <td style="font-weight: normal;">0</td>
  4824. * <td style="font-weight: normal;">0</td>
  4825. * </tr>
  4826. * <tr>
  4827. * <td>tan(θ)</td>
  4828. * <td>1</td>
  4829. * <td>0</td>
  4830. * </tr>
  4831. * <tr>
  4832. * <td>0</td>
  4833. * <td>0</td>
  4834. * <td>1</td>
  4835. * </tr>
  4836. * </table>
  4837. *
  4838. * Multiplying matrices for translation, rotation, scaling, and shearing / skewing
  4839. * any number of times in the desired order produces a single matrix for a composite
  4840. * transformation. You can use the product as a value for the `setTransform`method
  4841. * of a sprite:
  4842. *
  4843. * mySprite.setTransform([a, b, c, d, e, f]);
  4844. *
  4845. * Where `a`, `b`, `c`, `d`, `e`, `f` are numeric values that correspond to the
  4846. * following transformation matrix components:
  4847. *
  4848. * <table style="text-align: center;">
  4849. * <tr>
  4850. * <td style="font-weight: normal;">a</td>
  4851. * <td style="font-weight: normal;">c</td>
  4852. * <td style="font-weight: normal;">e</td>
  4853. * </tr>
  4854. * <tr>
  4855. * <td>b</td>
  4856. * <td>d</td>
  4857. * <td>f</td>
  4858. * </tr>
  4859. * <tr>
  4860. * <td>0</td>
  4861. * <td>0</td>
  4862. * <td>1</td>
  4863. * </tr>
  4864. * </table>
  4865. *
  4866. * @param {Ext.draw.Matrix/Number[]} matrix The transformation matrix to apply or its
  4867. * raw elements as an array.
  4868. * @param {Boolean} [isSplit=false] If `true`, transformation attributes are updated.
  4869. * @return {Ext.draw.sprite.Sprite} This sprite.
  4870. */
  4871. setTransform: function(matrix, isSplit) {
  4872. this.resetTransform(true);
  4873. this.transform.call(this, matrix, isSplit);
  4874. return this;
  4875. },
  4876. /**
  4877. * @method
  4878. * Called before rendering.
  4879. */
  4880. preRender: Ext.emptyFn,
  4881. /**
  4882. * @method
  4883. * This is where the actual sprite rendering happens by calling `ctx` methods.
  4884. * @param {Ext.draw.Surface} surface A draw container surface.
  4885. * @param {CanvasRenderingContext2D} ctx A context object that is API compatible with the native
  4886. * [CanvasRenderingContext2D](https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D).
  4887. * @param {Number[]} surfaceClipRect The clip rect: [left, top, width, height].
  4888. * Not to be confused with the `surface.getRect()`, which represents the location
  4889. * and size of the surface in a draw container, in draw container coordinates.
  4890. * The clip rect on the other hand represents the portion of the surface that is being
  4891. * rendered, in surface coordinates.
  4892. *
  4893. * @return {*} returns `false` to stop rendering in this frame.
  4894. * All the sprites that haven't been rendered will have their dirty flag untouched.
  4895. */
  4896. render: Ext.emptyFn,
  4897. //<debug>
  4898. /**
  4899. * @private
  4900. * Renders the bounding box of transformed sprite.
  4901. */
  4902. renderBBox: function(surface, ctx) {
  4903. var bbox = this.getBBox();
  4904. ctx.beginPath();
  4905. ctx.moveTo(bbox.x, bbox.y);
  4906. ctx.lineTo(bbox.x + bbox.width, bbox.y);
  4907. ctx.lineTo(bbox.x + bbox.width, bbox.y + bbox.height);
  4908. ctx.lineTo(bbox.x, bbox.y + bbox.height);
  4909. ctx.closePath();
  4910. ctx.strokeStyle = 'red';
  4911. ctx.strokeOpacity = 1;
  4912. ctx.lineWidth = 0.5;
  4913. ctx.stroke();
  4914. },
  4915. //</debug>
  4916. /**
  4917. * Performs a hit test on the sprite.
  4918. * @param {Array} point A two-item array containing x and y coordinates of the point.
  4919. * @param {Object} options Hit testing options.
  4920. * @return {Object} A hit result object that contains more information about what
  4921. * exactly was hit or null if nothing was hit.
  4922. */
  4923. hitTest: function(point, options) {
  4924. // Meant to be overridden in subclasses for more precise hit testing.
  4925. // This version doesn't take any options and simply hit tests sprite's
  4926. // bounding box, if the sprite is visible.
  4927. if (this.isVisible()) {
  4928. var x = point[0],
  4929. y = point[1],
  4930. bbox = this.getBBox(),
  4931. isBBoxHit = bbox && x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height);
  4932. if (isBBoxHit) {
  4933. return {
  4934. sprite: this
  4935. };
  4936. }
  4937. }
  4938. return null;
  4939. },
  4940. /**
  4941. * @private
  4942. * Checks if the sprite can be seen.
  4943. * This includes the `hidden` attribute check, alpha/opacity checks,
  4944. * fill/stroke color checks and surface/parent checks.
  4945. * The method doesn't check if the sprite is off-screen.
  4946. * @return {Boolean} Returns `true`, if the sprite can be seen.
  4947. */
  4948. isVisible: function() {
  4949. var attr = this.attr,
  4950. parent = this.getParent(),
  4951. hasParent = parent && (parent.isSurface || parent.isVisible()),
  4952. isSeen = hasParent && !attr.hidden && attr.globalAlpha,
  4953. none1 = Ext.util.Color.NONE,
  4954. none2 = Ext.util.Color.RGBA_NONE,
  4955. hasFill = attr.fillOpacity && attr.fillStyle !== none1 && attr.fillStyle !== none2,
  4956. hasStroke = attr.strokeOpacity && attr.strokeStyle !== none1 && attr.strokeStyle !== none2,
  4957. result = isSeen && (hasFill || hasStroke);
  4958. return !!result;
  4959. },
  4960. repaint: function() {
  4961. var surface = this.getSurface();
  4962. if (surface) {
  4963. surface.renderFrame();
  4964. }
  4965. },
  4966. /**
  4967. * Removes this sprite from its surface.
  4968. * The sprite itself is not destroyed.
  4969. * @return {Ext.draw.sprite.Sprite} Returns the removed sprite or `null` otherwise.
  4970. */
  4971. remove: function() {
  4972. var surface = this.getSurface();
  4973. if (surface && surface.isSurface) {
  4974. return surface.remove(this);
  4975. }
  4976. return null;
  4977. },
  4978. /**
  4979. * Removes the sprite and clears all listeners.
  4980. */
  4981. destroy: function() {
  4982. var me = this,
  4983. modifier = me.modifiers.target,
  4984. currentModifier;
  4985. while (modifier) {
  4986. currentModifier = modifier;
  4987. modifier = modifier._lower;
  4988. currentModifier.destroy();
  4989. }
  4990. delete me.attr;
  4991. me.remove();
  4992. if (me.fireEvent('beforedestroy', me) !== false) {
  4993. me.fireEvent('destroy', me);
  4994. }
  4995. me.callParent();
  4996. }
  4997. }, function() {
  4998. // onClassCreated
  4999. // Create one AttributeDefinition instance per sprite class when a class is created
  5000. // and replace the `def` config with the instance that was created using that config.
  5001. // Here we only create an AttributeDefinition instance for the base Sprite class,
  5002. // attribute definitions for subclasses are created inside onClassExtended method.
  5003. this.def = new Ext.draw.sprite.AttributeDefinition(this.def);
  5004. this.def.spriteClass = this;
  5005. });
  5006. /**
  5007. * Class representing a path.
  5008. * Designed to be compatible with [CanvasPathMethods](http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#canvaspathmethods)
  5009. * and will hopefully be replaced by the browsers' implementation of the Path object.
  5010. */
  5011. Ext.define('Ext.draw.Path', {
  5012. requires: [
  5013. 'Ext.draw.Draw'
  5014. ],
  5015. statics: {
  5016. pathRe: /,?([achlmqrstvxz]),?/gi,
  5017. pathRe2: /-/gi,
  5018. pathSplitRe: /\s|,/g
  5019. },
  5020. svgString: '',
  5021. /**
  5022. * Create a path from pathString.
  5023. * @constructor
  5024. * @param {String} pathString
  5025. */
  5026. constructor: function(pathString) {
  5027. var me = this;
  5028. me.commands = [];
  5029. // Stores command letters from the SVG path data ('d' attribute).
  5030. me.params = [];
  5031. // Stores command parameters from the SVG path data.
  5032. // All command parameters are actually point coordinates as the only commands used
  5033. // are the M, L, C, Z. This makes path transformations and hit testing easier.
  5034. // Arcs are approximated using cubic Bezier curves, H and S commands are translated
  5035. // to L commands and relative commands are translated to their absolute versions.
  5036. me.cursor = null;
  5037. me.startX = 0;
  5038. me.startY = 0;
  5039. if (pathString) {
  5040. me.fromSvgString(pathString);
  5041. }
  5042. },
  5043. /**
  5044. * Clear the path.
  5045. */
  5046. clear: function() {
  5047. var me = this;
  5048. me.params.length = 0;
  5049. me.commands.length = 0;
  5050. me.cursor = null;
  5051. me.startX = 0;
  5052. me.startY = 0;
  5053. me.dirt();
  5054. },
  5055. /**
  5056. * @private
  5057. */
  5058. dirt: function() {
  5059. this.svgString = '';
  5060. },
  5061. /**
  5062. * Move to a position.
  5063. * @param {Number} x
  5064. * @param {Number} y
  5065. */
  5066. moveTo: function(x, y) {
  5067. var me = this;
  5068. if (!me.cursor) {
  5069. me.cursor = [
  5070. x,
  5071. y
  5072. ];
  5073. }
  5074. me.params.push(x, y);
  5075. me.commands.push('M');
  5076. me.startX = x;
  5077. me.startY = y;
  5078. me.cursor[0] = x;
  5079. me.cursor[1] = y;
  5080. me.dirt();
  5081. },
  5082. /**
  5083. * A straight line to a position.
  5084. * @param {Number} x
  5085. * @param {Number} y
  5086. */
  5087. lineTo: function(x, y) {
  5088. var me = this;
  5089. if (!me.cursor) {
  5090. me.cursor = [
  5091. x,
  5092. y
  5093. ];
  5094. me.params.push(x, y);
  5095. me.commands.push('M');
  5096. } else {
  5097. me.params.push(x, y);
  5098. me.commands.push('L');
  5099. }
  5100. me.cursor[0] = x;
  5101. me.cursor[1] = y;
  5102. me.dirt();
  5103. },
  5104. /**
  5105. * A cubic bezier curve to a position.
  5106. * @param {Number} cx1
  5107. * @param {Number} cy1
  5108. * @param {Number} cx2
  5109. * @param {Number} cy2
  5110. * @param {Number} x
  5111. * @param {Number} y
  5112. */
  5113. bezierCurveTo: function(cx1, cy1, cx2, cy2, x, y) {
  5114. var me = this;
  5115. if (!me.cursor) {
  5116. me.moveTo(cx1, cy1);
  5117. }
  5118. me.params.push(cx1, cy1, cx2, cy2, x, y);
  5119. me.commands.push('C');
  5120. me.cursor[0] = x;
  5121. me.cursor[1] = y;
  5122. me.dirt();
  5123. },
  5124. /**
  5125. * A quadratic bezier curve to a position.
  5126. * @param {Number} cx
  5127. * @param {Number} cy
  5128. * @param {Number} x
  5129. * @param {Number} y
  5130. */
  5131. quadraticCurveTo: function(cx, cy, x, y) {
  5132. var me = this;
  5133. if (!me.cursor) {
  5134. me.moveTo(cx, cy);
  5135. }
  5136. me.bezierCurveTo((2 * cx + me.cursor[0]) / 3, (2 * cy + me.cursor[1]) / 3, (2 * cx + x) / 3, (2 * cy + y) / 3, x, y);
  5137. },
  5138. /**
  5139. * Close this path with a straight line.
  5140. */
  5141. closePath: function() {
  5142. var me = this;
  5143. if (me.cursor) {
  5144. me.cursor = null;
  5145. me.commands.push('Z');
  5146. me.dirt();
  5147. }
  5148. },
  5149. /**
  5150. * Create a elliptic arc curve compatible with SVG's arc to instruction.
  5151. *
  5152. * The curve start from (`x1`, `y1`) and ends at (`x2`, `y2`). The ellipse
  5153. * has radius `rx` and `ry` and a rotation of `rotation`.
  5154. * @param {Number} x1
  5155. * @param {Number} y1
  5156. * @param {Number} x2
  5157. * @param {Number} y2
  5158. * @param {Number} [rx]
  5159. * @param {Number} [ry]
  5160. * @param {Number} [rotation]
  5161. */
  5162. arcTo: function(x1, y1, x2, y2, rx, ry, rotation) {
  5163. var me = this;
  5164. if (ry === undefined) {
  5165. ry = rx;
  5166. }
  5167. if (rotation === undefined) {
  5168. rotation = 0;
  5169. }
  5170. if (!me.cursor) {
  5171. me.moveTo(x1, y1);
  5172. return;
  5173. }
  5174. if (rx === 0 || ry === 0) {
  5175. me.lineTo(x1, y1);
  5176. return;
  5177. }
  5178. x2 -= x1;
  5179. y2 -= y1;
  5180. var x0 = me.cursor[0] - x1,
  5181. y0 = me.cursor[1] - y1,
  5182. area = x2 * y0 - y2 * x0,
  5183. cos, sin, xx, yx, xy, yy,
  5184. l0 = Math.sqrt(x0 * x0 + y0 * y0),
  5185. l2 = Math.sqrt(x2 * x2 + y2 * y2),
  5186. dist, cx, cy;
  5187. // cos rx, -sin ry , x1 - cos rx x1 + ry sin y1
  5188. // sin rx, cos ry, -rx sin x1 + y1 - cos ry y1
  5189. if (area === 0) {
  5190. me.lineTo(x1, y1);
  5191. return;
  5192. }
  5193. if (ry !== rx) {
  5194. cos = Math.cos(rotation);
  5195. sin = Math.sin(rotation);
  5196. xx = cos / rx;
  5197. yx = sin / ry;
  5198. xy = -sin / rx;
  5199. yy = cos / ry;
  5200. var temp = xx * x0 + yx * y0;
  5201. y0 = xy * x0 + yy * y0;
  5202. x0 = temp;
  5203. temp = xx * x2 + yx * y2;
  5204. y2 = xy * x2 + yy * y2;
  5205. x2 = temp;
  5206. } else {
  5207. x0 /= rx;
  5208. y0 /= ry;
  5209. x2 /= rx;
  5210. y2 /= ry;
  5211. }
  5212. cx = x0 * l2 + x2 * l0;
  5213. cy = y0 * l2 + y2 * l0;
  5214. dist = 1 / (Math.sin(Math.asin(Math.abs(area) / (l0 * l2)) * 0.5) * Math.sqrt(cx * cx + cy * cy));
  5215. cx *= dist;
  5216. cy *= dist;
  5217. var k0 = (cx * x0 + cy * y0) / (x0 * x0 + y0 * y0),
  5218. k2 = (cx * x2 + cy * y2) / (x2 * x2 + y2 * y2);
  5219. var cosStart = x0 * k0 - cx,
  5220. sinStart = y0 * k0 - cy,
  5221. cosEnd = x2 * k2 - cx,
  5222. sinEnd = y2 * k2 - cy,
  5223. startAngle = Math.atan2(sinStart, cosStart),
  5224. endAngle = Math.atan2(sinEnd, cosEnd);
  5225. if (area > 0) {
  5226. if (endAngle < startAngle) {
  5227. endAngle += Math.PI * 2;
  5228. }
  5229. } else {
  5230. if (startAngle < endAngle) {
  5231. startAngle += Math.PI * 2;
  5232. }
  5233. }
  5234. if (ry !== rx) {
  5235. cx = cos * cx * rx - sin * cy * ry + x1;
  5236. cy = sin * cy * ry + cos * cy * ry + y1;
  5237. me.lineTo(cos * rx * cosStart - sin * ry * sinStart + cx, sin * rx * cosStart + cos * ry * sinStart + cy);
  5238. me.ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, area < 0);
  5239. } else {
  5240. cx = cx * rx + x1;
  5241. cy = cy * ry + y1;
  5242. me.lineTo(rx * cosStart + cx, ry * sinStart + cy);
  5243. me.ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, area < 0);
  5244. }
  5245. },
  5246. /**
  5247. * Create an elliptic arc.
  5248. *
  5249. * See [the whatwg reference of ellipse](http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-ellipse).
  5250. *
  5251. * @param {Number} cx
  5252. * @param {Number} cy
  5253. * @param {Number} radiusX
  5254. * @param {Number} radiusY
  5255. * @param {Number} rotation
  5256. * @param {Number} startAngle
  5257. * @param {Number} endAngle
  5258. * @param {Number} anticlockwise
  5259. */
  5260. ellipse: function(cx, cy, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise) {
  5261. var me = this,
  5262. params = me.params,
  5263. start = params.length,
  5264. count, i, j;
  5265. if (endAngle - startAngle >= Math.PI * 2) {
  5266. me.ellipse(cx, cy, radiusX, radiusY, rotation, startAngle, startAngle + Math.PI, anticlockwise);
  5267. me.ellipse(cx, cy, radiusX, radiusY, rotation, startAngle + Math.PI, endAngle, anticlockwise);
  5268. return;
  5269. }
  5270. if (!anticlockwise) {
  5271. if (endAngle < startAngle) {
  5272. endAngle += Math.PI * 2;
  5273. }
  5274. count = me.approximateArc(params, cx, cy, radiusX, radiusY, rotation, startAngle, endAngle);
  5275. } else {
  5276. if (startAngle < endAngle) {
  5277. startAngle += Math.PI * 2;
  5278. }
  5279. count = me.approximateArc(params, cx, cy, radiusX, radiusY, rotation, endAngle, startAngle);
  5280. for (i = start , j = params.length - 2; i < j; i += 2 , j -= 2) {
  5281. var temp = params[i];
  5282. params[i] = params[j];
  5283. params[j] = temp;
  5284. temp = params[i + 1];
  5285. params[i + 1] = params[j + 1];
  5286. params[j + 1] = temp;
  5287. }
  5288. }
  5289. if (!me.cursor) {
  5290. me.cursor = [
  5291. params[params.length - 2],
  5292. params[params.length - 1]
  5293. ];
  5294. me.commands.push('M');
  5295. } else {
  5296. me.cursor[0] = params[params.length - 2];
  5297. me.cursor[1] = params[params.length - 1];
  5298. me.commands.push('L');
  5299. }
  5300. for (i = 2; i < count; i += 6) {
  5301. me.commands.push('C');
  5302. }
  5303. me.dirt();
  5304. },
  5305. /**
  5306. * Create an circular arc.
  5307. *
  5308. * @param {Number} x
  5309. * @param {Number} y
  5310. * @param {Number} radius
  5311. * @param {Number} startAngle
  5312. * @param {Number} endAngle
  5313. * @param {Number} anticlockwise
  5314. */
  5315. arc: function(x, y, radius, startAngle, endAngle, anticlockwise) {
  5316. this.ellipse(x, y, radius, radius, 0, startAngle, endAngle, anticlockwise);
  5317. },
  5318. /**
  5319. * Draw a rectangle and close it.
  5320. *
  5321. * @param {Number} x
  5322. * @param {Number} y
  5323. * @param {Number} width
  5324. * @param {Number} height
  5325. */
  5326. rect: function(x, y, width, height) {
  5327. if (width == 0 || height == 0) {
  5328. return;
  5329. }
  5330. var me = this;
  5331. me.moveTo(x, y);
  5332. me.lineTo(x + width, y);
  5333. me.lineTo(x + width, y + height);
  5334. me.lineTo(x, y + height);
  5335. me.closePath();
  5336. },
  5337. /**
  5338. * @private
  5339. * @param {Array} result
  5340. * @param {Number} cx
  5341. * @param {Number} cy
  5342. * @param {Number} rx
  5343. * @param {Number} ry
  5344. * @param {Number} phi
  5345. * @param {Number} theta1
  5346. * @param {Number} theta2
  5347. * @return {Number}
  5348. */
  5349. approximateArc: function(result, cx, cy, rx, ry, phi, theta1, theta2) {
  5350. var cosPhi = Math.cos(phi),
  5351. sinPhi = Math.sin(phi),
  5352. cosTheta1 = Math.cos(theta1),
  5353. sinTheta1 = Math.sin(theta1),
  5354. xx = cosPhi * cosTheta1 * rx - sinPhi * sinTheta1 * ry,
  5355. yx = -cosPhi * sinTheta1 * rx - sinPhi * cosTheta1 * ry,
  5356. xy = sinPhi * cosTheta1 * rx + cosPhi * sinTheta1 * ry,
  5357. yy = -sinPhi * sinTheta1 * rx + cosPhi * cosTheta1 * ry,
  5358. rightAngle = Math.PI / 2,
  5359. count = 2,
  5360. exx = xx,
  5361. eyx = yx,
  5362. exy = xy,
  5363. eyy = yy,
  5364. rho = 0.547443256150549,
  5365. temp, y1, x3, y3, x2, y2;
  5366. theta2 -= theta1;
  5367. if (theta2 < 0) {
  5368. theta2 += Math.PI * 2;
  5369. }
  5370. result.push(xx + cx, xy + cy);
  5371. while (theta2 >= rightAngle) {
  5372. result.push(exx + eyx * rho + cx, exy + eyy * rho + cy, exx * rho + eyx + cx, exy * rho + eyy + cy, eyx + cx, eyy + cy);
  5373. count += 6;
  5374. theta2 -= rightAngle;
  5375. temp = exx;
  5376. exx = eyx;
  5377. eyx = -temp;
  5378. temp = exy;
  5379. exy = eyy;
  5380. eyy = -temp;
  5381. }
  5382. if (theta2) {
  5383. y1 = (0.3294738052815987 + 0.012120855841304373 * theta2) * theta2;
  5384. x3 = Math.cos(theta2);
  5385. y3 = Math.sin(theta2);
  5386. x2 = x3 + y1 * y3;
  5387. y2 = y3 - y1 * x3;
  5388. result.push(exx + eyx * y1 + cx, exy + eyy * y1 + cy, exx * x2 + eyx * y2 + cx, exy * x2 + eyy * y2 + cy, exx * x3 + eyx * y3 + cx, exy * x3 + eyy * y3 + cy);
  5389. count += 6;
  5390. }
  5391. return count;
  5392. },
  5393. /**
  5394. * [http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes](http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes)
  5395. * @param {Number} rx
  5396. * @param {Number} ry
  5397. * @param {Number} rotation Differ from svg spec, this is radian.
  5398. * @param {Number} fA
  5399. * @param {Number} fS
  5400. * @param {Number} x2
  5401. * @param {Number} y2
  5402. */
  5403. arcSvg: function(rx, ry, rotation, fA, fS, x2, y2) {
  5404. if (rx < 0) {
  5405. rx = -rx;
  5406. }
  5407. if (ry < 0) {
  5408. ry = -ry;
  5409. }
  5410. var me = this,
  5411. x1 = me.cursor[0],
  5412. y1 = me.cursor[1],
  5413. hdx = (x1 - x2) / 2,
  5414. hdy = (y1 - y2) / 2,
  5415. cosPhi = Math.cos(rotation),
  5416. sinPhi = Math.sin(rotation),
  5417. xp = hdx * cosPhi + hdy * sinPhi,
  5418. yp = -hdx * sinPhi + hdy * cosPhi,
  5419. ratX = xp / rx,
  5420. ratY = yp / ry,
  5421. lambda = ratX * ratX + ratY * ratY,
  5422. cx = (x1 + x2) * 0.5,
  5423. cy = (y1 + y2) * 0.5,
  5424. cpx = 0,
  5425. cpy = 0;
  5426. if (lambda >= 1) {
  5427. lambda = Math.sqrt(lambda);
  5428. rx *= lambda;
  5429. ry *= lambda;
  5430. } else // me gives lambda == cpx == cpy == 0;
  5431. {
  5432. lambda = Math.sqrt(1 / lambda - 1);
  5433. if (fA === fS) {
  5434. lambda = -lambda;
  5435. }
  5436. cpx = lambda * rx * ratY;
  5437. cpy = -lambda * ry * ratX;
  5438. cx += cosPhi * cpx - sinPhi * cpy;
  5439. cy += sinPhi * cpx + cosPhi * cpy;
  5440. }
  5441. var theta1 = Math.atan2((yp - cpy) / ry, (xp - cpx) / rx),
  5442. deltaTheta = Math.atan2((-yp - cpy) / ry, (-xp - cpx) / rx) - theta1;
  5443. if (fS) {
  5444. if (deltaTheta <= 0) {
  5445. deltaTheta += Math.PI * 2;
  5446. }
  5447. } else {
  5448. if (deltaTheta >= 0) {
  5449. deltaTheta -= Math.PI * 2;
  5450. }
  5451. }
  5452. me.ellipse(cx, cy, rx, ry, rotation, theta1, theta1 + deltaTheta, 1 - fS);
  5453. },
  5454. /**
  5455. * Feed the path from svg path string.
  5456. * @param {String} pathString
  5457. */
  5458. fromSvgString: function(pathString) {
  5459. if (!pathString) {
  5460. return;
  5461. }
  5462. var me = this,
  5463. parts,
  5464. paramCounts = {
  5465. a: 7,
  5466. c: 6,
  5467. h: 1,
  5468. l: 2,
  5469. m: 2,
  5470. q: 4,
  5471. s: 4,
  5472. t: 2,
  5473. v: 1,
  5474. z: 0,
  5475. A: 7,
  5476. C: 6,
  5477. H: 1,
  5478. L: 2,
  5479. M: 2,
  5480. Q: 4,
  5481. S: 4,
  5482. T: 2,
  5483. V: 1,
  5484. Z: 0
  5485. },
  5486. lastCommand = '',
  5487. lastControlX, lastControlY,
  5488. lastX = 0,
  5489. lastY = 0,
  5490. part = false,
  5491. i, partLength, relative;
  5492. // Split the string to items.
  5493. if (Ext.isString(pathString)) {
  5494. parts = pathString.replace(Ext.draw.Path.pathRe, " $1 ").replace(Ext.draw.Path.pathRe2, " -").split(Ext.draw.Path.pathSplitRe);
  5495. } else if (Ext.isArray(pathString)) {
  5496. parts = pathString.join(',').split(Ext.draw.Path.pathSplitRe);
  5497. }
  5498. // Remove empty entries
  5499. for (i = 0 , partLength = 0; i < parts.length; i++) {
  5500. if (parts[i] !== '') {
  5501. parts[partLength++] = parts[i];
  5502. }
  5503. }
  5504. parts.length = partLength;
  5505. me.clear();
  5506. for (i = 0; i < parts.length; ) {
  5507. lastCommand = part;
  5508. part = parts[i];
  5509. relative = (part.toUpperCase() !== part);
  5510. i++;
  5511. switch (part) {
  5512. case 'M':
  5513. me.moveTo(lastX = +parts[i], lastY = +parts[i + 1]);
  5514. i += 2;
  5515. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5516. me.lineTo(lastX = +parts[i], lastY = +parts[i + 1]);
  5517. i += 2;
  5518. };
  5519. break;
  5520. case 'L':
  5521. me.lineTo(lastX = +parts[i], lastY = +parts[i + 1]);
  5522. i += 2;
  5523. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5524. me.lineTo(lastX = +parts[i], lastY = +parts[i + 1]);
  5525. i += 2;
  5526. };
  5527. break;
  5528. case 'A':
  5529. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5530. me.arcSvg(+parts[i], +parts[i + 1], +parts[i + 2] * Math.PI / 180, +parts[i + 3], +parts[i + 4], lastX = +parts[i + 5], lastY = +parts[i + 6]);
  5531. i += 7;
  5532. };
  5533. break;
  5534. case 'C':
  5535. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5536. me.bezierCurveTo(+parts[i], +parts[i + 1], lastControlX = +parts[i + 2], lastControlY = +parts[i + 3], lastX = +parts[i + 4], lastY = +parts[i + 5]);
  5537. i += 6;
  5538. };
  5539. break;
  5540. case 'Z':
  5541. me.closePath();
  5542. break;
  5543. case 'm':
  5544. me.moveTo(lastX += +parts[i], lastY += +parts[i + 1]);
  5545. i += 2;
  5546. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5547. me.lineTo(lastX += +parts[i], lastY += +parts[i + 1]);
  5548. i += 2;
  5549. };
  5550. break;
  5551. case 'l':
  5552. me.lineTo(lastX += +parts[i], lastY += +parts[i + 1]);
  5553. i += 2;
  5554. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5555. me.lineTo(lastX += +parts[i], lastY += +parts[i + 1]);
  5556. i += 2;
  5557. };
  5558. break;
  5559. case 'a':
  5560. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5561. me.arcSvg(+parts[i], +parts[i + 1], +parts[i + 2] * Math.PI / 180, +parts[i + 3], +parts[i + 4], lastX += +parts[i + 5], lastY += +parts[i + 6]);
  5562. i += 7;
  5563. };
  5564. break;
  5565. case 'c':
  5566. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5567. me.bezierCurveTo(lastX + (+parts[i]), lastY + (+parts[i + 1]), lastControlX = lastX + (+parts[i + 2]), lastControlY = lastY + (+parts[i + 3]), lastX += +parts[i + 4], lastY += +parts[i + 5]);
  5568. i += 6;
  5569. };
  5570. break;
  5571. case 'z':
  5572. me.closePath();
  5573. break;
  5574. case 's':
  5575. if (!(lastCommand === 'c' || lastCommand === 'C' || lastCommand === 's' || lastCommand === 'S')) {
  5576. lastControlX = lastX;
  5577. lastControlY = lastY;
  5578. };
  5579. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5580. me.bezierCurveTo(lastX + lastX - lastControlX, lastY + lastY - lastControlY, lastControlX = lastX + (+parts[i]), lastControlY = lastY + (+parts[i + 1]), lastX += +parts[i + 2], lastY += +parts[i + 3]);
  5581. i += 4;
  5582. };
  5583. break;
  5584. case 'S':
  5585. if (!(lastCommand === 'c' || lastCommand === 'C' || lastCommand === 's' || lastCommand === 'S')) {
  5586. lastControlX = lastX;
  5587. lastControlY = lastY;
  5588. };
  5589. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5590. me.bezierCurveTo(lastX + lastX - lastControlX, lastY + lastY - lastControlY, lastControlX = +parts[i], lastControlY = +parts[i + 1], lastX = (+parts[i + 2]), lastY = (+parts[i + 3]));
  5591. i += 4;
  5592. };
  5593. break;
  5594. case 'q':
  5595. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5596. me.quadraticCurveTo(lastControlX = lastX + (+parts[i]), lastControlY = lastY + (+parts[i + 1]), lastX += +parts[i + 2], lastY += +parts[i + 3]);
  5597. i += 4;
  5598. };
  5599. break;
  5600. case 'Q':
  5601. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5602. me.quadraticCurveTo(lastControlX = +parts[i], lastControlY = +parts[i + 1], lastX = +parts[i + 2], lastY = +parts[i + 3]);
  5603. i += 4;
  5604. };
  5605. break;
  5606. case 't':
  5607. if (!(lastCommand === 'q' || lastCommand === 'Q' || lastCommand === 't' || lastCommand === 'T')) {
  5608. lastControlX = lastX;
  5609. lastControlY = lastY;
  5610. };
  5611. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5612. me.quadraticCurveTo(lastControlX = lastX + lastX - lastControlX, lastControlY = lastY + lastY - lastControlY, lastX += +parts[i + 1], lastY += +parts[i + 2]);
  5613. i += 2;
  5614. };
  5615. break;
  5616. case 'T':
  5617. if (!(lastCommand === 'q' || lastCommand === 'Q' || lastCommand === 't' || lastCommand === 'T')) {
  5618. lastControlX = lastX;
  5619. lastControlY = lastY;
  5620. };
  5621. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5622. me.quadraticCurveTo(lastControlX = lastX + lastX - lastControlX, lastControlY = lastY + lastY - lastControlY, lastX = (+parts[i + 1]), lastY = (+parts[i + 2]));
  5623. i += 2;
  5624. };
  5625. break;
  5626. case 'h':
  5627. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5628. me.lineTo(lastX += +parts[i], lastY);
  5629. i++;
  5630. };
  5631. break;
  5632. case 'H':
  5633. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5634. me.lineTo(lastX = +parts[i], lastY);
  5635. i++;
  5636. };
  5637. break;
  5638. case 'v':
  5639. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5640. me.lineTo(lastX, lastY += +parts[i]);
  5641. i++;
  5642. };
  5643. break;
  5644. case 'V':
  5645. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5646. me.lineTo(lastX, lastY = +parts[i]);
  5647. i++;
  5648. };
  5649. break;
  5650. }
  5651. }
  5652. },
  5653. /**
  5654. * Clone this path.
  5655. * @return {Ext.draw.Path}
  5656. */
  5657. clone: function() {
  5658. var me = this,
  5659. path = new Ext.draw.Path();
  5660. path.params = me.params.slice(0);
  5661. path.commands = me.commands.slice(0);
  5662. path.cursor = me.cursor ? me.cursor.slice(0) : null;
  5663. path.startX = me.startX;
  5664. path.startY = me.startY;
  5665. path.svgString = me.svgString;
  5666. return path;
  5667. },
  5668. /**
  5669. * Transform the current path by a matrix.
  5670. * @param {Ext.draw.Matrix} matrix
  5671. */
  5672. transform: function(matrix) {
  5673. if (matrix.isIdentity()) {
  5674. return;
  5675. }
  5676. var xx = matrix.getXX(),
  5677. yx = matrix.getYX(),
  5678. dx = matrix.getDX(),
  5679. xy = matrix.getXY(),
  5680. yy = matrix.getYY(),
  5681. dy = matrix.getDY(),
  5682. params = this.params,
  5683. i = 0,
  5684. ln = params.length,
  5685. x, y;
  5686. for (; i < ln; i += 2) {
  5687. x = params[i];
  5688. y = params[i + 1];
  5689. params[i] = x * xx + y * yx + dx;
  5690. params[i + 1] = x * xy + y * yy + dy;
  5691. }
  5692. this.dirt();
  5693. },
  5694. /**
  5695. * Get the bounding box of this matrix.
  5696. * @param {Object} [target] Optional object to receive the result.
  5697. *
  5698. * @return {Object} Object with x, y, width and height
  5699. */
  5700. getDimension: function(target) {
  5701. if (!target) {
  5702. target = {};
  5703. }
  5704. if (!this.commands || !this.commands.length) {
  5705. target.x = 0;
  5706. target.y = 0;
  5707. target.width = 0;
  5708. target.height = 0;
  5709. return target;
  5710. }
  5711. target.left = Infinity;
  5712. target.top = Infinity;
  5713. target.right = -Infinity;
  5714. target.bottom = -Infinity;
  5715. var i = 0,
  5716. j = 0,
  5717. commands = this.commands,
  5718. params = this.params,
  5719. ln = commands.length,
  5720. x, y;
  5721. for (; i < ln; i++) {
  5722. switch (commands[i]) {
  5723. case 'M':
  5724. case 'L':
  5725. x = params[j];
  5726. y = params[j + 1];
  5727. target.left = Math.min(x, target.left);
  5728. target.top = Math.min(y, target.top);
  5729. target.right = Math.max(x, target.right);
  5730. target.bottom = Math.max(y, target.bottom);
  5731. j += 2;
  5732. break;
  5733. case 'C':
  5734. this.expandDimension(target, x, y, params[j], params[j + 1], params[j + 2], params[j + 3], x = params[j + 4], y = params[j + 5]);
  5735. j += 6;
  5736. break;
  5737. }
  5738. }
  5739. target.x = target.left;
  5740. target.y = target.top;
  5741. target.width = target.right - target.left;
  5742. target.height = target.bottom - target.top;
  5743. return target;
  5744. },
  5745. /**
  5746. * Get the bounding box as if the path is transformed by a matrix.
  5747. *
  5748. * @param {Ext.draw.Matrix} matrix
  5749. * @param {Object} [target] Optional object to receive the result.
  5750. *
  5751. * @return {Object} An object with x, y, width and height.
  5752. */
  5753. getDimensionWithTransform: function(matrix, target) {
  5754. if (!this.commands || !this.commands.length) {
  5755. if (!target) {
  5756. target = {};
  5757. }
  5758. target.x = 0;
  5759. target.y = 0;
  5760. target.width = 0;
  5761. target.height = 0;
  5762. return target;
  5763. }
  5764. target.left = Infinity;
  5765. target.top = Infinity;
  5766. target.right = -Infinity;
  5767. target.bottom = -Infinity;
  5768. var xx = matrix.getXX(),
  5769. yx = matrix.getYX(),
  5770. dx = matrix.getDX(),
  5771. xy = matrix.getXY(),
  5772. yy = matrix.getYY(),
  5773. dy = matrix.getDY(),
  5774. i = 0,
  5775. j = 0,
  5776. commands = this.commands,
  5777. params = this.params,
  5778. ln = commands.length,
  5779. x, y;
  5780. for (; i < ln; i++) {
  5781. switch (commands[i]) {
  5782. case 'M':
  5783. case 'L':
  5784. x = params[j] * xx + params[j + 1] * yx + dx;
  5785. y = params[j] * xy + params[j + 1] * yy + dy;
  5786. target.left = Math.min(x, target.left);
  5787. target.top = Math.min(y, target.top);
  5788. target.right = Math.max(x, target.right);
  5789. target.bottom = Math.max(y, target.bottom);
  5790. j += 2;
  5791. break;
  5792. case 'C':
  5793. this.expandDimension(target, x, y, params[j] * xx + params[j + 1] * yx + dx, params[j] * xy + params[j + 1] * yy + dy, params[j + 2] * xx + params[j + 3] * yx + dx, params[j + 2] * xy + params[j + 3] * yy + dy, x = params[j + 4] * xx + params[j + 5] * yx + dx, y = params[j + 4] * xy + params[j + 5] * yy + dy);
  5794. j += 6;
  5795. break;
  5796. }
  5797. }
  5798. if (!target) {
  5799. target = {};
  5800. }
  5801. target.x = target.left;
  5802. target.y = target.top;
  5803. target.width = target.right - target.left;
  5804. target.height = target.bottom - target.top;
  5805. return target;
  5806. },
  5807. /**
  5808. * @private
  5809. * Expand the rect by the bbox of a bezier curve.
  5810. *
  5811. * @param {Object} target
  5812. * @param {Number} x1
  5813. * @param {Number} y1
  5814. * @param {Number} cx1
  5815. * @param {Number} cy1
  5816. * @param {Number} cx2
  5817. * @param {Number} cy2
  5818. * @param {Number} x2
  5819. * @param {Number} y2
  5820. */
  5821. expandDimension: function(target, x1, y1, cx1, cy1, cx2, cy2, x2, y2) {
  5822. var me = this,
  5823. l = target.left,
  5824. r = target.right,
  5825. t = target.top,
  5826. b = target.bottom,
  5827. dim = me.dim || (me.dim = []);
  5828. me.curveDimension(x1, cx1, cx2, x2, dim);
  5829. l = Math.min(l, dim[0]);
  5830. r = Math.max(r, dim[1]);
  5831. me.curveDimension(y1, cy1, cy2, y2, dim);
  5832. t = Math.min(t, dim[0]);
  5833. b = Math.max(b, dim[1]);
  5834. target.left = l;
  5835. target.right = r;
  5836. target.top = t;
  5837. target.bottom = b;
  5838. },
  5839. /**
  5840. * @private
  5841. * Determine the curve
  5842. * @param {Number} a
  5843. * @param {Number} b
  5844. * @param {Number} c
  5845. * @param {Number} d
  5846. * @param {Number} dim
  5847. */
  5848. curveDimension: function(a, b, c, d, dim) {
  5849. var qa = 3 * (-a + 3 * (b - c) + d),
  5850. qb = 6 * (a - 2 * b + c),
  5851. qc = -3 * (a - b),
  5852. x, y,
  5853. min = Math.min(a, d),
  5854. max = Math.max(a, d),
  5855. delta;
  5856. if (qa === 0) {
  5857. if (qb === 0) {
  5858. dim[0] = min;
  5859. dim[1] = max;
  5860. return;
  5861. } else {
  5862. x = -qc / qb;
  5863. if (0 < x && x < 1) {
  5864. y = this.interpolate(a, b, c, d, x);
  5865. min = Math.min(min, y);
  5866. max = Math.max(max, y);
  5867. }
  5868. }
  5869. } else {
  5870. delta = qb * qb - 4 * qa * qc;
  5871. if (delta >= 0) {
  5872. delta = Math.sqrt(delta);
  5873. x = (delta - qb) / 2 / qa;
  5874. if (0 < x && x < 1) {
  5875. y = this.interpolate(a, b, c, d, x);
  5876. min = Math.min(min, y);
  5877. max = Math.max(max, y);
  5878. }
  5879. if (delta > 0) {
  5880. x -= delta / qa;
  5881. if (0 < x && x < 1) {
  5882. y = this.interpolate(a, b, c, d, x);
  5883. min = Math.min(min, y);
  5884. max = Math.max(max, y);
  5885. }
  5886. }
  5887. }
  5888. }
  5889. dim[0] = min;
  5890. dim[1] = max;
  5891. },
  5892. /**
  5893. * @private
  5894. *
  5895. * Returns `a * (1 - t) ^ 3 + 3 * b (1 - t) ^ 2 * t + 3 * c (1 - t) * t ^ 3 + d * t ^ 3`.
  5896. *
  5897. * @param {Number} a
  5898. * @param {Number} b
  5899. * @param {Number} c
  5900. * @param {Number} d
  5901. * @param {Number} t
  5902. * @return {Number}
  5903. */
  5904. interpolate: function(a, b, c, d, t) {
  5905. if (t === 0) {
  5906. return a;
  5907. }
  5908. if (t === 1) {
  5909. return d;
  5910. }
  5911. var rate = (1 - t) / t;
  5912. return t * t * t * (d + rate * (3 * c + rate * (3 * b + rate * a)));
  5913. },
  5914. /**
  5915. * Reconstruct path from cubic bezier curve stripes.
  5916. * @param {Array} stripes
  5917. */
  5918. fromStripes: function(stripes) {
  5919. var me = this,
  5920. i = 0,
  5921. ln = stripes.length,
  5922. j, ln2, stripe;
  5923. me.clear();
  5924. for (; i < ln; i++) {
  5925. stripe = stripes[i];
  5926. me.params.push.apply(me.params, stripe);
  5927. me.commands.push('M');
  5928. for (j = 2 , ln2 = stripe.length; j < ln2; j += 6) {
  5929. me.commands.push('C');
  5930. }
  5931. }
  5932. if (!me.cursor) {
  5933. me.cursor = [];
  5934. }
  5935. me.cursor[0] = me.params[me.params.length - 2];
  5936. me.cursor[1] = me.params[me.params.length - 1];
  5937. me.dirt();
  5938. },
  5939. /**
  5940. * Convert path to bezier curve stripes.
  5941. * @param {Array} [target] The optional array to receive the result.
  5942. * @return {Array}
  5943. */
  5944. toStripes: function(target) {
  5945. var stripes = target || [],
  5946. curr, x, y, lastX, lastY, startX, startY, i, j,
  5947. commands = this.commands,
  5948. params = this.params,
  5949. ln = commands.length;
  5950. for (i = 0 , j = 0; i < ln; i++) {
  5951. switch (commands[i]) {
  5952. case 'M':
  5953. curr = [
  5954. startX = lastX = params[j++],
  5955. startY = lastY = params[j++]
  5956. ];
  5957. stripes.push(curr);
  5958. break;
  5959. case 'L':
  5960. x = params[j++];
  5961. y = params[j++];
  5962. curr.push((lastX + lastX + x) / 3, (lastY + lastY + y) / 3, (lastX + x + x) / 3, (lastY + y + y) / 3, lastX = x, lastY = y);
  5963. break;
  5964. case 'C':
  5965. curr.push(params[j++], params[j++], params[j++], params[j++], lastX = params[j++], lastY = params[j++]);
  5966. break;
  5967. case 'Z':
  5968. x = startX;
  5969. y = startY;
  5970. curr.push((lastX + lastX + x) / 3, (lastY + lastY + y) / 3, (lastX + x + x) / 3, (lastY + y + y) / 3, lastX = x, lastY = y);
  5971. break;
  5972. }
  5973. }
  5974. return stripes;
  5975. },
  5976. /**
  5977. * @private
  5978. * Update cache for svg string of this path.
  5979. */
  5980. updateSvgString: function() {
  5981. var result = [],
  5982. commands = this.commands,
  5983. params = this.params,
  5984. ln = commands.length,
  5985. i = 0,
  5986. j = 0;
  5987. for (; i < ln; i++) {
  5988. switch (commands[i]) {
  5989. case 'M':
  5990. result.push('M' + params[j] + ',' + params[j + 1]);
  5991. j += 2;
  5992. break;
  5993. case 'L':
  5994. result.push('L' + params[j] + ',' + params[j + 1]);
  5995. j += 2;
  5996. break;
  5997. case 'C':
  5998. result.push('C' + params[j] + ',' + params[j + 1] + ' ' + params[j + 2] + ',' + params[j + 3] + ' ' + params[j + 4] + ',' + params[j + 5]);
  5999. j += 6;
  6000. break;
  6001. case 'Z':
  6002. result.push('Z');
  6003. break;
  6004. }
  6005. }
  6006. this.svgString = result.join('');
  6007. },
  6008. /**
  6009. * Return an svg path string for this path.
  6010. * @return {String}
  6011. */
  6012. toString: function() {
  6013. if (!this.svgString) {
  6014. this.updateSvgString();
  6015. }
  6016. return this.svgString;
  6017. }
  6018. });
  6019. /**
  6020. * @private
  6021. * Adds hit testing and path intersection points methods to the Ext.draw.Path.
  6022. * Included by the Ext.draw.PathUtil.
  6023. */
  6024. Ext.define('Ext.draw.overrides.hittest.Path', {
  6025. override: 'Ext.draw.Path',
  6026. // An arbitrary point outside the path used for hit testing with ray casting method.
  6027. rayOrigin: {
  6028. x: -10000,
  6029. y: -10000
  6030. },
  6031. /**
  6032. * Tests whether the given point is inside the path.
  6033. * @param {Number} x
  6034. * @param {Number} y
  6035. * @return {Boolean}
  6036. * @member Ext.draw.Path
  6037. */
  6038. isPointInPath: function(x, y) {
  6039. var me = this,
  6040. commands = me.commands,
  6041. solver = Ext.draw.PathUtil,
  6042. origin = me.rayOrigin,
  6043. params = me.params,
  6044. ln = commands.length,
  6045. firstX = null,
  6046. firstY = null,
  6047. lastX = 0,
  6048. lastY = 0,
  6049. count = 0,
  6050. i, j;
  6051. for (i = 0 , j = 0; i < ln; i++) {
  6052. switch (commands[i]) {
  6053. case 'M':
  6054. if (firstX !== null) {
  6055. if (solver.linesIntersection(firstX, firstY, lastX, lastY, origin.x, origin.y, x, y)) {
  6056. count += 1;
  6057. }
  6058. };
  6059. firstX = lastX = params[j];
  6060. firstY = lastY = params[j + 1];
  6061. j += 2;
  6062. break;
  6063. case 'L':
  6064. if (solver.linesIntersection(lastX, lastY, params[j], params[j + 1], origin.x, origin.y, x, y)) {
  6065. count += 1;
  6066. };
  6067. lastX = params[j];
  6068. lastY = params[j + 1];
  6069. j += 2;
  6070. break;
  6071. case 'C':
  6072. count += solver.cubicLineIntersections(lastX, params[j], params[j + 2], params[j + 4], lastY, params[j + 1], params[j + 3], params[j + 5], origin.x, origin.y, x, y).length;
  6073. lastX = params[j + 4];
  6074. lastY = params[j + 5];
  6075. j += 6;
  6076. break;
  6077. case 'Z':
  6078. if (firstX !== null) {
  6079. if (solver.linesIntersection(firstX, firstY, lastX, lastY, origin.x, origin.y, x, y)) {
  6080. count += 1;
  6081. }
  6082. };
  6083. break;
  6084. }
  6085. }
  6086. return count % 2 === 1;
  6087. },
  6088. /**
  6089. * Tests whether the given point is on the path.
  6090. * @param {Number} x
  6091. * @param {Number} y
  6092. * @return {Boolean}
  6093. * @member Ext.draw.Path
  6094. */
  6095. isPointOnPath: function(x, y) {
  6096. var me = this,
  6097. commands = me.commands,
  6098. solver = Ext.draw.PathUtil,
  6099. params = me.params,
  6100. ln = commands.length,
  6101. firstX = null,
  6102. firstY = null,
  6103. lastX = 0,
  6104. lastY = 0,
  6105. i, j;
  6106. for (i = 0 , j = 0; i < ln; i++) {
  6107. switch (commands[i]) {
  6108. case 'M':
  6109. if (firstX !== null) {
  6110. if (solver.pointOnLine(firstX, firstY, lastX, lastY, x, y)) {
  6111. return true;
  6112. }
  6113. };
  6114. firstX = lastX = params[j];
  6115. firstY = lastY = params[j + 1];
  6116. j += 2;
  6117. break;
  6118. case 'L':
  6119. if (solver.pointOnLine(lastX, lastY, params[j], params[j + 1], x, y)) {
  6120. return true;
  6121. };
  6122. lastX = params[j];
  6123. lastY = params[j + 1];
  6124. j += 2;
  6125. break;
  6126. case 'C':
  6127. if (solver.pointOnCubic(lastX, params[j], params[j + 2], params[j + 4], lastY, params[j + 1], params[j + 3], params[j + 5], x, y)) {
  6128. return true;
  6129. };
  6130. lastX = params[j + 4];
  6131. lastY = params[j + 5];
  6132. j += 6;
  6133. break;
  6134. case 'Z':
  6135. if (firstX !== null) {
  6136. if (solver.pointOnLine(firstX, firstY, lastX, lastY, x, y)) {
  6137. return true;
  6138. }
  6139. };
  6140. break;
  6141. }
  6142. }
  6143. return false;
  6144. },
  6145. /**
  6146. * Calculates the points where the given segment intersects the path.
  6147. * If four parameters are given then the segment is considered to be a line segment,
  6148. * where given parameters are the coordinates of the start and end points.
  6149. * If eight parameters are given then the segment is considered to be
  6150. * a cubic Bezier curve segment, where given parameters are the
  6151. * coordinates of its edge points and control points.
  6152. * @param x1
  6153. * @param y1
  6154. * @param x2
  6155. * @param y2
  6156. * @param x3
  6157. * @param y3
  6158. * @param x4
  6159. * @param y4
  6160. * @return {Array}
  6161. * @member Ext.draw.Path
  6162. */
  6163. getSegmentIntersections: function(x1, y1, x2, y2, x3, y3, x4, y4) {
  6164. var me = this,
  6165. count = arguments.length,
  6166. solver = Ext.draw.PathUtil,
  6167. commands = me.commands,
  6168. params = me.params,
  6169. ln = commands.length,
  6170. firstX = null,
  6171. firstY = null,
  6172. lastX = 0,
  6173. lastY = 0,
  6174. intersections = [],
  6175. i, j, points;
  6176. for (i = 0 , j = 0; i < ln; i++) {
  6177. switch (commands[i]) {
  6178. case 'M':
  6179. if (firstX !== null) {
  6180. switch (count) {
  6181. case 4:
  6182. points = solver.linesIntersection(firstX, firstY, lastX, lastY, x1, y1, x2, y2);
  6183. if (points) {
  6184. intersections.push(points);
  6185. };
  6186. break;
  6187. case 8:
  6188. points = solver.cubicLineIntersections(x1, x2, x3, x4, y1, y2, y3, y4, firstX, firstY, lastX, lastY);
  6189. intersections.push.apply(intersections, points);
  6190. break;
  6191. }
  6192. };
  6193. firstX = lastX = params[j];
  6194. firstY = lastY = params[j + 1];
  6195. j += 2;
  6196. break;
  6197. case 'L':
  6198. switch (count) {
  6199. case 4:
  6200. points = solver.linesIntersection(lastX, lastY, params[j], params[j + 1], x1, y1, x2, y2);
  6201. if (points) {
  6202. intersections.push(points);
  6203. };
  6204. break;
  6205. case 8:
  6206. points = solver.cubicLineIntersections(x1, x2, x3, x4, y1, y2, y3, y4, lastX, lastY, params[j], params[j + 1]);
  6207. intersections.push.apply(intersections, points);
  6208. break;
  6209. };
  6210. lastX = params[j];
  6211. lastY = params[j + 1];
  6212. j += 2;
  6213. break;
  6214. case 'C':
  6215. switch (count) {
  6216. case 4:
  6217. points = solver.cubicLineIntersections(lastX, params[j], params[j + 2], params[j + 4], lastY, params[j + 1], params[j + 3], params[j + 5], x1, y1, x2, y2);
  6218. intersections.push.apply(intersections, points);
  6219. break;
  6220. case 8:
  6221. points = solver.cubicsIntersections(lastX, params[j], params[j + 2], params[j + 4], lastY, params[j + 1], params[j + 3], params[j + 5], x1, x2, x3, x4, y1, y2, y3, y4);
  6222. intersections.push.apply(intersections, points);
  6223. break;
  6224. };
  6225. lastX = params[j + 4];
  6226. lastY = params[j + 5];
  6227. j += 6;
  6228. break;
  6229. case 'Z':
  6230. if (firstX !== null) {
  6231. switch (count) {
  6232. case 4:
  6233. points = solver.linesIntersection(firstX, firstY, lastX, lastY, x1, y1, x2, y2);
  6234. if (points) {
  6235. intersections.push(points);
  6236. };
  6237. break;
  6238. case 8:
  6239. points = solver.cubicLineIntersections(x1, x2, x3, x4, y1, y2, y3, y4, firstX, firstY, lastX, lastY);
  6240. intersections.push.apply(intersections, points);
  6241. break;
  6242. }
  6243. };
  6244. break;
  6245. }
  6246. }
  6247. return intersections;
  6248. },
  6249. getIntersections: function(path) {
  6250. var me = this,
  6251. commands = me.commands,
  6252. params = me.params,
  6253. ln = commands.length,
  6254. firstX = null,
  6255. firstY = null,
  6256. lastX = 0,
  6257. lastY = 0,
  6258. intersections = [],
  6259. i, j, points;
  6260. for (i = 0 , j = 0; i < ln; i++) {
  6261. switch (commands[i]) {
  6262. case 'M':
  6263. if (firstX !== null) {
  6264. points = path.getSegmentIntersections.call(path, firstX, firstY, lastX, lastY);
  6265. intersections.push.apply(intersections, points);
  6266. };
  6267. firstX = lastX = params[j];
  6268. firstY = lastY = params[j + 1];
  6269. j += 2;
  6270. break;
  6271. case 'L':
  6272. points = path.getSegmentIntersections.call(path, lastX, lastY, params[j], params[j + 1]);
  6273. intersections.push.apply(intersections, points);
  6274. lastX = params[j];
  6275. lastY = params[j + 1];
  6276. j += 2;
  6277. break;
  6278. case 'C':
  6279. points = path.getSegmentIntersections.call(path, lastX, lastY, params[j], params[j + 1], params[j + 2], params[j + 3], params[j + 4], params[j + 5]);
  6280. intersections.push.apply(intersections, points);
  6281. lastX = params[j + 4];
  6282. lastY = params[j + 5];
  6283. j += 6;
  6284. break;
  6285. case 'Z':
  6286. if (firstX !== null) {
  6287. points = path.getSegmentIntersections.call(path, firstX, firstY, lastX, lastY);
  6288. intersections.push.apply(intersections, points);
  6289. };
  6290. break;
  6291. }
  6292. }
  6293. return intersections;
  6294. }
  6295. });
  6296. /**
  6297. * @class Ext.draw.sprite.Path
  6298. * @extends Ext.draw.sprite.Sprite
  6299. *
  6300. * A sprite that represents a path.
  6301. *
  6302. * @example
  6303. * Ext.create({
  6304. * xtype: 'draw',
  6305. * renderTo: document.body,
  6306. * width: 600,
  6307. * height: 400,
  6308. * sprites: [{
  6309. * type: 'path',
  6310. * path: 'M20,30 c0,-50 75,50 75,0 c0,-50 -75,50 -75,0',
  6311. * fillStyle: '#1F6D91'
  6312. * }]
  6313. * });
  6314. *
  6315. * ### Drawing with SVG Paths
  6316. * You may use special SVG Path syntax to "describe" the drawing path. Here are the SVG path commands:
  6317. *
  6318. * + M = moveto
  6319. * + L = lineto
  6320. * + H = horizontal lineto
  6321. * + V = vertical lineto
  6322. * + C = curveto
  6323. * + S = smooth curveto
  6324. * + Q = quadratic Bézier curve
  6325. * + T = smooth quadratic Bézier curveto
  6326. * + A = elliptical Arc
  6327. * + Z = closepath
  6328. *
  6329. * **Note:** Capital letters indicate that the item should be absolutely positioned.
  6330. * Use lower case letters for relative positioning.
  6331. */
  6332. Ext.define('Ext.draw.sprite.Path', {
  6333. extend: 'Ext.draw.sprite.Sprite',
  6334. requires: [
  6335. 'Ext.draw.Draw',
  6336. 'Ext.draw.Path'
  6337. ],
  6338. alias: [
  6339. 'sprite.path',
  6340. 'Ext.draw.Sprite'
  6341. ],
  6342. type: 'path',
  6343. isPath: true,
  6344. inheritableStatics: {
  6345. def: {
  6346. processors: {
  6347. /**
  6348. * @cfg {String} path The SVG based path string used by the sprite.
  6349. */
  6350. path: function(n, o) {
  6351. if (!(n instanceof Ext.draw.Path)) {
  6352. n = new Ext.draw.Path(n);
  6353. }
  6354. return n;
  6355. }
  6356. },
  6357. aliases: {
  6358. d: 'path'
  6359. },
  6360. triggers: {
  6361. path: 'bbox'
  6362. },
  6363. updaters: {
  6364. path: function(attr) {
  6365. var path = attr.path;
  6366. if (!path || path.bindAttr !== attr) {
  6367. path = new Ext.draw.Path();
  6368. path.bindAttr = attr;
  6369. attr.path = path;
  6370. }
  6371. path.clear();
  6372. this.updatePath(path, attr);
  6373. this.scheduleUpdater(attr, 'bbox', [
  6374. 'path'
  6375. ]);
  6376. }
  6377. }
  6378. }
  6379. },
  6380. updatePlainBBox: function(plain) {
  6381. if (this.attr.path) {
  6382. this.attr.path.getDimension(plain);
  6383. }
  6384. },
  6385. updateTransformedBBox: function(transform) {
  6386. if (this.attr.path) {
  6387. this.attr.path.getDimensionWithTransform(this.attr.matrix, transform);
  6388. }
  6389. },
  6390. render: function(surface, ctx) {
  6391. var mat = this.attr.matrix,
  6392. attr = this.attr;
  6393. if (!attr.path || attr.path.params.length === 0) {
  6394. return;
  6395. }
  6396. mat.toContext(ctx);
  6397. ctx.appendPath(attr.path);
  6398. ctx.fillStroke(attr);
  6399. //<debug>
  6400. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  6401. if (debug) {
  6402. debug.bbox && this.renderBBox(surface, ctx);
  6403. debug.xray && this.renderXRay(surface, ctx);
  6404. }
  6405. },
  6406. //</debug>
  6407. //<debug>
  6408. renderXRay: function(surface, ctx) {
  6409. var attr = this.attr,
  6410. mat = attr.matrix,
  6411. imat = attr.inverseMatrix,
  6412. path = attr.path,
  6413. commands = path.commands,
  6414. params = path.params,
  6415. ln = commands.length,
  6416. twoPi = Math.PI * 2,
  6417. size = 2,
  6418. i, j;
  6419. mat.toContext(ctx);
  6420. ctx.beginPath();
  6421. for (i = 0 , j = 0; i < ln; i++) {
  6422. switch (commands[i]) {
  6423. case 'M':
  6424. ctx.moveTo(params[j] - size, params[j + 1] - size);
  6425. ctx.rect(params[j] - size, params[j + 1] - size, size * 2, size * 2);
  6426. j += 2;
  6427. break;
  6428. case 'L':
  6429. ctx.moveTo(params[j] - size, params[j + 1] - size);
  6430. ctx.rect(params[j] - size, params[j + 1] - size, size * 2, size * 2);
  6431. j += 2;
  6432. break;
  6433. case 'C':
  6434. ctx.moveTo(params[j] + size, params[j + 1]);
  6435. ctx.arc(params[j], params[j + 1], size, 0, twoPi, true);
  6436. j += 2;
  6437. ctx.moveTo(params[j] + size, params[j + 1]);
  6438. ctx.arc(params[j], params[j + 1], size, 0, twoPi, true);
  6439. j += 2;
  6440. ctx.moveTo(params[j] + size * 2, params[j + 1]);
  6441. ctx.rect(params[j] - size, params[j + 1] - size, size * 2, size * 2);
  6442. j += 2;
  6443. break;
  6444. default:
  6445. }
  6446. }
  6447. imat.toContext(ctx);
  6448. ctx.strokeStyle = 'black';
  6449. ctx.strokeOpacity = 1;
  6450. ctx.lineWidth = 1;
  6451. ctx.stroke();
  6452. mat.toContext(ctx);
  6453. ctx.beginPath();
  6454. for (i = 0 , j = 0; i < ln; i++) {
  6455. switch (commands[i]) {
  6456. case 'M':
  6457. ctx.moveTo(params[j], params[j + 1]);
  6458. j += 2;
  6459. break;
  6460. case 'L':
  6461. ctx.moveTo(params[j], params[j + 1]);
  6462. j += 2;
  6463. break;
  6464. case 'C':
  6465. ctx.lineTo(params[j], params[j + 1]);
  6466. j += 2;
  6467. ctx.moveTo(params[j], params[j + 1]);
  6468. j += 2;
  6469. ctx.lineTo(params[j], params[j + 1]);
  6470. j += 2;
  6471. break;
  6472. default:
  6473. }
  6474. }
  6475. imat.toContext(ctx);
  6476. ctx.lineWidth = 0.5;
  6477. ctx.stroke();
  6478. },
  6479. //</debug>
  6480. /**
  6481. * Update the path.
  6482. * @param {Ext.draw.Path} path An empty path to draw on using path API.
  6483. * @param {Object} attr The attribute object. Note: DO NOT use the `sprite.attr` instead of this
  6484. * if you want to work with instancing.
  6485. */
  6486. updatePath: function(path, attr) {}
  6487. });
  6488. /**
  6489. * @private
  6490. * Adds hit testing methods to the Ext.draw.sprite.Path sprite.
  6491. * Included by the Ext.draw.PathUtil.
  6492. */
  6493. Ext.define('Ext.draw.overrides.hittest.sprite.Path', {
  6494. override: 'Ext.draw.sprite.Path',
  6495. requires: [
  6496. 'Ext.draw.Color'
  6497. ],
  6498. /**
  6499. * Tests whether the given point is inside the path.
  6500. * @param x
  6501. * @param y
  6502. * @return {Boolean}
  6503. * @member Ext.draw.sprite.Path
  6504. */
  6505. isPointInPath: function(x, y) {
  6506. var attr = this.attr;
  6507. if (attr.fillStyle === Ext.util.Color.RGBA_NONE) {
  6508. return this.isPointOnPath(x, y);
  6509. }
  6510. var path = attr.path,
  6511. matrix = attr.matrix,
  6512. params, result;
  6513. if (!matrix.isIdentity()) {
  6514. params = path.params.slice(0);
  6515. path.transform(attr.matrix);
  6516. }
  6517. result = path.isPointInPath(x, y);
  6518. if (params) {
  6519. path.params = params;
  6520. }
  6521. return result;
  6522. },
  6523. /**
  6524. * Tests whether the given point is on the path.
  6525. * @param x
  6526. * @param y
  6527. * @return {Boolean}
  6528. * @member Ext.draw.sprite.Path
  6529. */
  6530. isPointOnPath: function(x, y) {
  6531. var attr = this.attr,
  6532. path = attr.path,
  6533. matrix = attr.matrix,
  6534. params, result;
  6535. if (!matrix.isIdentity()) {
  6536. params = path.params.slice(0);
  6537. path.transform(attr.matrix);
  6538. }
  6539. result = path.isPointOnPath(x, y);
  6540. if (params) {
  6541. path.params = params;
  6542. }
  6543. return result;
  6544. },
  6545. /**
  6546. * @method hitTest
  6547. * @inheritdoc Ext.draw.Surface#method-hitTest
  6548. */
  6549. hitTest: function(point, options) {
  6550. var me = this,
  6551. attr = me.attr,
  6552. path = attr.path,
  6553. matrix = attr.matrix,
  6554. x = point[0],
  6555. y = point[1],
  6556. parentResult = me.callParent([
  6557. point,
  6558. options
  6559. ]),
  6560. result = null,
  6561. params, isFilled;
  6562. if (!parentResult) {
  6563. // The sprite is not visible or bounding box wasn't hit.
  6564. return result;
  6565. }
  6566. options = options || Ext.draw.sprite.Sprite.defaultHitTestOptions;
  6567. if (!matrix.isIdentity()) {
  6568. params = path.params.slice(0);
  6569. path.transform(attr.matrix);
  6570. }
  6571. if (options.fill && options.stroke) {
  6572. isFilled = attr.fillStyle !== Ext.util.Color.NONE && attr.fillStyle !== Ext.util.Color.RGBA_NONE;
  6573. if (isFilled) {
  6574. if (path.isPointInPath(x, y)) {
  6575. result = {
  6576. sprite: me
  6577. };
  6578. }
  6579. } else {
  6580. if (path.isPointInPath(x, y) || path.isPointOnPath(x, y)) {
  6581. result = {
  6582. sprite: me
  6583. };
  6584. }
  6585. }
  6586. } else if (options.stroke && !options.fill) {
  6587. if (path.isPointOnPath(x, y)) {
  6588. result = {
  6589. sprite: me
  6590. };
  6591. }
  6592. } else if (options.fill && !options.stroke) {
  6593. if (path.isPointInPath(x, y)) {
  6594. result = {
  6595. sprite: me
  6596. };
  6597. }
  6598. }
  6599. if (params) {
  6600. path.params = params;
  6601. }
  6602. return result;
  6603. },
  6604. /**
  6605. * Returns all points where this sprite intersects the given sprite.
  6606. * The given sprite must be an instance of the {@link Ext.draw.sprite.Path} class
  6607. * or its subclass.
  6608. * @param path
  6609. * @return {Array}
  6610. * @member Ext.draw.sprite.Path
  6611. */
  6612. getIntersections: function(path) {
  6613. if (!(path.isSprite && path.isPath)) {
  6614. return [];
  6615. }
  6616. var aAttr = this.attr,
  6617. bAttr = path.attr,
  6618. aPath = aAttr.path,
  6619. bPath = bAttr.path,
  6620. aMatrix = aAttr.matrix,
  6621. bMatrix = bAttr.matrix,
  6622. aParams, bParams, intersections;
  6623. if (!aMatrix.isIdentity()) {
  6624. aParams = aPath.params.slice(0);
  6625. aPath.transform(aAttr.matrix);
  6626. }
  6627. if (!bMatrix.isIdentity()) {
  6628. bParams = bPath.params.slice(0);
  6629. bPath.transform(bAttr.matrix);
  6630. }
  6631. intersections = aPath.getIntersections(bPath);
  6632. if (aParams) {
  6633. aPath.params = aParams;
  6634. }
  6635. if (bParams) {
  6636. bPath.params = bParams;
  6637. }
  6638. return intersections;
  6639. }
  6640. });
  6641. /**
  6642. * @class Ext.draw.sprite.Circle
  6643. * @extends Ext.draw.sprite.Path
  6644. *
  6645. * A sprite that represents a circle.
  6646. *
  6647. * @example
  6648. * Ext.create({
  6649. * xtype: 'draw',
  6650. * renderTo: document.body,
  6651. * width: 600,
  6652. * height: 400,
  6653. * sprites: [{
  6654. * type: 'circle',
  6655. * cx: 100,
  6656. * cy: 100,
  6657. * r: 50,
  6658. * fillStyle: '#1F6D91'
  6659. * }]
  6660. * });
  6661. */
  6662. Ext.define('Ext.draw.sprite.Circle', {
  6663. extend: 'Ext.draw.sprite.Path',
  6664. alias: 'sprite.circle',
  6665. type: 'circle',
  6666. inheritableStatics: {
  6667. def: {
  6668. processors: {
  6669. /**
  6670. * @cfg {Number} [cx=0] The center coordinate of the sprite on the x-axis.
  6671. */
  6672. cx: 'number',
  6673. /**
  6674. * @cfg {Number} [cy=0] The center coordinate of the sprite on the y-axis.
  6675. */
  6676. cy: 'number',
  6677. /**
  6678. * @cfg {Number} [r=0] The radius of the sprite.
  6679. */
  6680. r: 'number'
  6681. },
  6682. aliases: {
  6683. radius: 'r',
  6684. x: 'cx',
  6685. y: 'cy',
  6686. centerX: 'cx',
  6687. centerY: 'cy'
  6688. },
  6689. defaults: {
  6690. cx: 0,
  6691. cy: 0,
  6692. r: 4
  6693. },
  6694. triggers: {
  6695. cx: 'path',
  6696. cy: 'path',
  6697. r: 'path'
  6698. }
  6699. }
  6700. },
  6701. updatePlainBBox: function(plain) {
  6702. var attr = this.attr,
  6703. cx = attr.cx,
  6704. cy = attr.cy,
  6705. r = attr.r;
  6706. plain.x = cx - r;
  6707. plain.y = cy - r;
  6708. plain.width = r + r;
  6709. plain.height = r + r;
  6710. },
  6711. updateTransformedBBox: function(transform) {
  6712. var attr = this.attr,
  6713. cx = attr.cx,
  6714. cy = attr.cy,
  6715. r = attr.r,
  6716. matrix = attr.matrix,
  6717. scaleX = matrix.getScaleX(),
  6718. scaleY = matrix.getScaleY(),
  6719. rx, ry;
  6720. rx = scaleX * r;
  6721. ry = scaleY * r;
  6722. transform.x = matrix.x(cx, cy) - rx;
  6723. transform.y = matrix.y(cx, cy) - ry;
  6724. transform.width = rx + rx;
  6725. transform.height = ry + ry;
  6726. },
  6727. updatePath: function(path, attr) {
  6728. path.arc(attr.cx, attr.cy, attr.r, 0, Math.PI * 2, false);
  6729. }
  6730. });
  6731. /**
  6732. * @class Ext.draw.sprite.Arc
  6733. * @extend Ext.draw.sprite.Circle
  6734. *
  6735. * A sprite that represents a circular arc.
  6736. *
  6737. * @example
  6738. * Ext.create({
  6739. * xtype: 'draw',
  6740. * renderTo: document.body,
  6741. * width: 600,
  6742. * height: 400,
  6743. * sprites: [{
  6744. * type: 'arc',
  6745. * cx: 100,
  6746. * cy: 100,
  6747. * r: 80,
  6748. * fillStyle: '#1F6D91',
  6749. * startAngle: 0,
  6750. * endAngle: Math.PI,
  6751. * anticlockwise: true
  6752. * }]
  6753. * });
  6754. */
  6755. Ext.define('Ext.draw.sprite.Arc', {
  6756. extend: 'Ext.draw.sprite.Circle',
  6757. alias: 'sprite.arc',
  6758. type: 'arc',
  6759. inheritableStatics: {
  6760. def: {
  6761. processors: {
  6762. /**
  6763. * @cfg {Number} [startAngle=0] The beginning angle of the arc.
  6764. */
  6765. startAngle: 'number',
  6766. /**
  6767. * @cfg {Number} [endAngle=Math.PI*2] The ending angle of the arc.
  6768. */
  6769. endAngle: 'number',
  6770. /**
  6771. * @cfg {Boolean} [anticlockwise=false] Determines whether or not the arc is drawn clockwise.
  6772. */
  6773. anticlockwise: 'bool'
  6774. },
  6775. aliases: {
  6776. from: 'startAngle',
  6777. to: 'endAngle',
  6778. start: 'startAngle',
  6779. end: 'endAngle'
  6780. },
  6781. defaults: {
  6782. startAngle: 0,
  6783. endAngle: Math.PI * 2,
  6784. anticlockwise: false
  6785. },
  6786. triggers: {
  6787. startAngle: 'path',
  6788. endAngle: 'path',
  6789. anticlockwise: 'path'
  6790. }
  6791. }
  6792. },
  6793. updatePath: function(path, attr) {
  6794. path.arc(attr.cx, attr.cy, attr.r, attr.startAngle, attr.endAngle, attr.anticlockwise);
  6795. }
  6796. });
  6797. /**
  6798. * A sprite that represents an arrow.
  6799. *
  6800. * @example
  6801. * Ext.create({
  6802. * xtype: 'draw',
  6803. * renderTo: document.body,
  6804. * width: 600,
  6805. * height: 400,
  6806. * sprites: [{
  6807. * type: 'arrow',
  6808. * translationX: 100,
  6809. * translationY: 100,
  6810. * size: 40,
  6811. * fillStyle: '#30BDA7'
  6812. * }]
  6813. * });
  6814. */
  6815. Ext.define('Ext.draw.sprite.Arrow', {
  6816. extend: 'Ext.draw.sprite.Path',
  6817. alias: 'sprite.arrow',
  6818. inheritableStatics: {
  6819. def: {
  6820. processors: {
  6821. x: 'number',
  6822. y: 'number',
  6823. /**
  6824. * @cfg {Number} [size=4] The size of the sprite.
  6825. * Meant to be comparable to the size of a circle sprite with the same radius.
  6826. */
  6827. size: 'number'
  6828. },
  6829. defaults: {
  6830. x: 0,
  6831. y: 0,
  6832. size: 4
  6833. },
  6834. triggers: {
  6835. x: 'path',
  6836. y: 'path',
  6837. size: 'path'
  6838. }
  6839. }
  6840. },
  6841. updatePath: function(path, attr) {
  6842. var s = attr.size * 1.5,
  6843. x = attr.x - attr.lineWidth / 2,
  6844. y = attr.y;
  6845. path.fromSvgString('M'.concat(x - s * 0.7, ',', y - s * 0.4, 'l', [
  6846. s * 0.6,
  6847. 0,
  6848. 0,
  6849. -s * 0.4,
  6850. s,
  6851. s * 0.8,
  6852. -s,
  6853. s * 0.8,
  6854. 0,
  6855. -s * 0.4,
  6856. -s * 0.6,
  6857. 0
  6858. ], 'z'));
  6859. }
  6860. });
  6861. /**
  6862. * @class Ext.draw.sprite.Composite
  6863. *
  6864. * Represents a group of sprites.
  6865. * Composite's sprites are rendered in the order they've been added to the Composite.
  6866. * The rendering order of composite sprites themselves is determined by the value of
  6867. * their zIndex attribute, just like with any other sprite.
  6868. * Every sprite that is added to the Composite is removed from whatever Surface/Composite
  6869. * it belongs to.
  6870. */
  6871. Ext.define('Ext.draw.sprite.Composite', {
  6872. extend: 'Ext.draw.sprite.Sprite',
  6873. alias: 'sprite.composite',
  6874. type: 'composite',
  6875. isComposite: true,
  6876. config: {
  6877. sprites: []
  6878. },
  6879. constructor: function(config) {
  6880. this.sprites = [];
  6881. this.map = {};
  6882. this.callParent([
  6883. config
  6884. ]);
  6885. },
  6886. /**
  6887. * Adds sprite(s) to the composite.
  6888. * @param {Ext.draw.sprite.Sprite/Ext.draw.sprite.Sprite[]/Object/Object[]} sprite
  6889. * @return {Ext.draw.sprite.Sprite/Ext.draw.sprite.Sprite[]}
  6890. */
  6891. addSprite: function(sprite) {
  6892. var i = 0,
  6893. results;
  6894. if (Ext.isArray(sprite)) {
  6895. results = [];
  6896. while (i < sprite.length) {
  6897. results.push(this.addSprite(sprite[i++]));
  6898. }
  6899. return results;
  6900. }
  6901. if (sprite && sprite.type && !sprite.isSprite) {
  6902. sprite = Ext.create('sprite.' + sprite.type, sprite);
  6903. }
  6904. if (!sprite || !sprite.isSprite || sprite.isComposite) {
  6905. return null;
  6906. }
  6907. sprite.setSurface(null);
  6908. sprite.setParent(this);
  6909. var attr = this.attr,
  6910. oldTransformations = sprite.applyTransformations;
  6911. sprite.applyTransformations = function(force) {
  6912. if (sprite.attr.dirtyTransform) {
  6913. attr.dirtyTransform = true;
  6914. attr.bbox.plain.dirty = true;
  6915. attr.bbox.transform.dirty = true;
  6916. }
  6917. oldTransformations.call(sprite, force);
  6918. };
  6919. this.sprites.push(sprite);
  6920. this.map[sprite.id] = sprite.getId();
  6921. attr.bbox.plain.dirty = true;
  6922. attr.bbox.transform.dirty = true;
  6923. return sprite;
  6924. },
  6925. /**
  6926. * @deprecated 6.2.1 Use {@link #addSprite} instead.
  6927. */
  6928. add: function(sprite) {
  6929. return this.addSprite(sprite);
  6930. },
  6931. removeSprite: function(sprite, isDestroy) {
  6932. var me = this,
  6933. id, isOwnSprite;
  6934. if (sprite) {
  6935. if (sprite.charAt) {
  6936. // is String
  6937. sprite = me.map[sprite];
  6938. }
  6939. if (!sprite || !sprite.isSprite) {
  6940. return null;
  6941. }
  6942. if (sprite.destroyed || sprite.destroying) {
  6943. return sprite;
  6944. }
  6945. id = sprite.getId();
  6946. isOwnSprite = me.map[id];
  6947. delete me.map[id];
  6948. if (isDestroy) {
  6949. sprite.destroy();
  6950. }
  6951. if (!isOwnSprite) {
  6952. return sprite;
  6953. }
  6954. sprite.setParent(null);
  6955. // sprite.setSurface(null);
  6956. Ext.Array.remove(me.sprites, sprite);
  6957. me.dirtyZIndex = true;
  6958. me.setDirty(true);
  6959. }
  6960. return sprite || null;
  6961. },
  6962. /**
  6963. * @deprecated 6.2.1 Use {@link #addSprite} instead.
  6964. * Adds a list of sprites to the composite.
  6965. * @param {Ext.draw.sprite.Sprite[]|Object[]|Ext.draw.sprite.Sprite|Object} sprites
  6966. */
  6967. addAll: function(sprites) {
  6968. if (sprites.isSprite || sprites.type) {
  6969. this.add(sprites);
  6970. } else if (Ext.isArray(sprites)) {
  6971. var i = 0;
  6972. while (i < sprites.length) {
  6973. this.add(sprites[i++]);
  6974. }
  6975. }
  6976. },
  6977. /**
  6978. * Updates the bounding box of the composite, which contains the bounding box of all sprites in the composite.
  6979. */
  6980. updatePlainBBox: function(plain) {
  6981. var me = this,
  6982. left = Infinity,
  6983. right = -Infinity,
  6984. top = Infinity,
  6985. bottom = -Infinity,
  6986. sprite, bbox, i, ln;
  6987. for (i = 0 , ln = me.sprites.length; i < ln; i++) {
  6988. sprite = me.sprites[i];
  6989. sprite.applyTransformations();
  6990. bbox = sprite.getBBox();
  6991. if (left > bbox.x) {
  6992. left = bbox.x;
  6993. }
  6994. if (right < bbox.x + bbox.width) {
  6995. right = bbox.x + bbox.width;
  6996. }
  6997. if (top > bbox.y) {
  6998. top = bbox.y;
  6999. }
  7000. if (bottom < bbox.y + bbox.height) {
  7001. bottom = bbox.y + bbox.height;
  7002. }
  7003. }
  7004. plain.x = left;
  7005. plain.y = top;
  7006. plain.width = right - left;
  7007. plain.height = bottom - top;
  7008. },
  7009. isVisible: function() {
  7010. // Override the abstract Sprite's method.
  7011. // Composite uses a simpler check, because it has no fill or stroke
  7012. // style of its own, it just houses other sprites.
  7013. var attr = this.attr,
  7014. parent = this.getParent(),
  7015. hasParent = parent && (parent.isSurface || parent.isVisible()),
  7016. isSeen = hasParent && !attr.hidden && attr.globalAlpha;
  7017. return !!isSeen;
  7018. },
  7019. /**
  7020. * Renders all sprites contained in the composite to the surface.
  7021. */
  7022. render: function(surface, ctx, rect) {
  7023. var me = this,
  7024. attr = me.attr,
  7025. mat = me.attr.matrix,
  7026. sprites = me.sprites,
  7027. ln = sprites.length,
  7028. i = 0;
  7029. mat.toContext(ctx);
  7030. for (; i < ln; i++) {
  7031. surface.renderSprite(sprites[i], rect);
  7032. }
  7033. //<debug>
  7034. var debug = attr.debug || me.statics().debug || Ext.draw.sprite.Sprite.debug;
  7035. if (debug) {
  7036. attr.inverseMatrix.toContext(ctx);
  7037. if (debug.bbox) {
  7038. me.renderBBox(surface, ctx);
  7039. }
  7040. }
  7041. },
  7042. //</debug>
  7043. destroy: function() {
  7044. var me = this,
  7045. sprites = me.sprites,
  7046. ln = sprites.length,
  7047. i;
  7048. for (i = 0; i < ln; i++) {
  7049. sprites[i].destroy();
  7050. }
  7051. sprites.length = 0;
  7052. me.callParent();
  7053. }
  7054. });
  7055. /**
  7056. * A sprite that represents a cross.
  7057. *
  7058. * @example
  7059. * Ext.create({
  7060. * xtype: 'draw',
  7061. * renderTo: document.body,
  7062. * width: 600,
  7063. * height: 400,
  7064. * sprites: [{
  7065. * type: 'cross',
  7066. * translationX: 100,
  7067. * translationY: 100,
  7068. * size: 40,
  7069. * fillStyle: '#1F6D91'
  7070. * }]
  7071. * });
  7072. */
  7073. Ext.define('Ext.draw.sprite.Cross', {
  7074. extend: 'Ext.draw.sprite.Path',
  7075. alias: 'sprite.cross',
  7076. inheritableStatics: {
  7077. def: {
  7078. processors: {
  7079. x: 'number',
  7080. y: 'number',
  7081. /**
  7082. * @cfg {Number} [size=4] The size of the sprite.
  7083. * Meant to be comparable to the size of a circle sprite with the same radius.
  7084. */
  7085. size: 'number'
  7086. },
  7087. defaults: {
  7088. x: 0,
  7089. y: 0,
  7090. size: 4
  7091. },
  7092. triggers: {
  7093. x: 'path',
  7094. y: 'path',
  7095. size: 'path'
  7096. }
  7097. }
  7098. },
  7099. updatePath: function(path, attr) {
  7100. var s = attr.size / 1.7,
  7101. x = attr.x - attr.lineWidth / 2,
  7102. y = attr.y;
  7103. path.fromSvgString('M'.concat(x - s, ',', y, 'l', [
  7104. -s,
  7105. -s,
  7106. s,
  7107. -s,
  7108. s,
  7109. s,
  7110. s,
  7111. -s,
  7112. s,
  7113. s,
  7114. -s,
  7115. s,
  7116. s,
  7117. s,
  7118. -s,
  7119. s,
  7120. -s,
  7121. -s,
  7122. -s,
  7123. s,
  7124. -s,
  7125. -s,
  7126. 'z'
  7127. ]));
  7128. }
  7129. });
  7130. /**
  7131. * A sprite that represents a diamond.
  7132. *
  7133. * @example
  7134. * Ext.create({
  7135. * xtype: 'draw',
  7136. * renderTo: document.body,
  7137. * width: 600,
  7138. * height: 400,
  7139. * sprites: [{
  7140. * type: 'diamond',
  7141. * translationX: 100,
  7142. * translationY: 100,
  7143. * size: 40,
  7144. * fillStyle: '#1F6D91'
  7145. * }]
  7146. * });
  7147. */
  7148. Ext.define('Ext.draw.sprite.Diamond', {
  7149. extend: 'Ext.draw.sprite.Path',
  7150. alias: 'sprite.diamond',
  7151. inheritableStatics: {
  7152. def: {
  7153. processors: {
  7154. x: 'number',
  7155. y: 'number',
  7156. /**
  7157. * @cfg {Number} [size=4] The size of the sprite.
  7158. * Meant to be comparable to the size of a circle sprite with the same radius.
  7159. */
  7160. size: 'number'
  7161. },
  7162. defaults: {
  7163. x: 0,
  7164. y: 0,
  7165. size: 4
  7166. },
  7167. triggers: {
  7168. x: 'path',
  7169. y: 'path',
  7170. size: 'path'
  7171. }
  7172. }
  7173. },
  7174. updatePath: function(path, attr) {
  7175. var s = attr.size * 1.25,
  7176. x = attr.x - attr.lineWidth / 2,
  7177. y = attr.y;
  7178. path.fromSvgString([
  7179. 'M',
  7180. x,
  7181. y - s,
  7182. 'l',
  7183. s,
  7184. s,
  7185. -s,
  7186. s,
  7187. -s,
  7188. -s,
  7189. s,
  7190. -s,
  7191. 'z'
  7192. ]);
  7193. }
  7194. });
  7195. /**
  7196. * @class Ext.draw.sprite.Ellipse
  7197. * @extends Ext.draw.sprite.Path
  7198. *
  7199. * A sprite that represents an ellipse.
  7200. *
  7201. * @example
  7202. * Ext.create({
  7203. * xtype: 'draw',
  7204. * renderTo: document.body,
  7205. * width: 600,
  7206. * height: 400,
  7207. * sprites: [{
  7208. * type: 'ellipse',
  7209. * cx: 100,
  7210. * cy: 100,
  7211. * rx: 80,
  7212. * ry: 50,
  7213. * fillStyle: '#1F6D91'
  7214. * }]
  7215. * });
  7216. */
  7217. Ext.define("Ext.draw.sprite.Ellipse", {
  7218. extend: "Ext.draw.sprite.Path",
  7219. alias: 'sprite.ellipse',
  7220. type: 'ellipse',
  7221. inheritableStatics: {
  7222. def: {
  7223. processors: {
  7224. /**
  7225. * @cfg {Number} [cx=0] The center coordinate of the sprite on the x-axis.
  7226. */
  7227. cx: "number",
  7228. /**
  7229. * @cfg {Number} [cy=0] The center coordinate of the sprite on the y-axis.
  7230. */
  7231. cy: "number",
  7232. /**
  7233. * @cfg {Number} [rx=1] The radius of the sprite on the x-axis.
  7234. */
  7235. rx: "number",
  7236. /**
  7237. * @cfg {Number} [ry=1] The radius of the sprite on the y-axis.
  7238. */
  7239. ry: "number",
  7240. /**
  7241. * @cfg {Number} [axisRotation=0] The rotation of the sprite about its axis.
  7242. */
  7243. axisRotation: "number"
  7244. },
  7245. aliases: {
  7246. radius: "r",
  7247. x: "cx",
  7248. y: "cy",
  7249. centerX: "cx",
  7250. centerY: "cy",
  7251. radiusX: "rx",
  7252. radiusY: "ry"
  7253. },
  7254. defaults: {
  7255. cx: 0,
  7256. cy: 0,
  7257. rx: 1,
  7258. ry: 1,
  7259. axisRotation: 0
  7260. },
  7261. triggers: {
  7262. cx: 'path',
  7263. cy: 'path',
  7264. rx: 'path',
  7265. ry: 'path',
  7266. axisRotation: 'path'
  7267. }
  7268. }
  7269. },
  7270. updatePlainBBox: function(plain) {
  7271. var attr = this.attr,
  7272. cx = attr.cx,
  7273. cy = attr.cy,
  7274. rx = attr.rx,
  7275. ry = attr.ry;
  7276. plain.x = cx - rx;
  7277. plain.y = cy - ry;
  7278. plain.width = rx + rx;
  7279. plain.height = ry + ry;
  7280. },
  7281. updateTransformedBBox: function(transform) {
  7282. var attr = this.attr,
  7283. cx = attr.cx,
  7284. cy = attr.cy,
  7285. rx = attr.rx,
  7286. ry = attr.ry,
  7287. rxy = ry / rx,
  7288. matrix = attr.matrix.clone(),
  7289. xx, xy, yx, yy, dx, dy, w, h;
  7290. matrix.append(1, 0, 0, rxy, 0, cy * (1 - rxy));
  7291. xx = matrix.getXX();
  7292. yx = matrix.getYX();
  7293. dx = matrix.getDX();
  7294. xy = matrix.getXY();
  7295. yy = matrix.getYY();
  7296. dy = matrix.getDY();
  7297. w = Math.sqrt(xx * xx + yx * yx) * rx;
  7298. h = Math.sqrt(xy * xy + yy * yy) * rx;
  7299. transform.x = cx * xx + cy * yx + dx - w;
  7300. transform.y = cx * xy + cy * yy + dy - h;
  7301. transform.width = w + w;
  7302. transform.height = h + h;
  7303. },
  7304. updatePath: function(path, attr) {
  7305. path.ellipse(attr.cx, attr.cy, attr.rx, attr.ry, attr.axisRotation, 0, Math.PI * 2, false);
  7306. }
  7307. });
  7308. /**
  7309. * @class Ext.draw.sprite.EllipticalArc
  7310. * @extends Ext.draw.sprite.Ellipse
  7311. *
  7312. * A sprite that represents an elliptical arc.
  7313. *
  7314. * @example
  7315. * Ext.create({
  7316. * xtype: 'draw',
  7317. * renderTo: document.body,
  7318. * width: 600,
  7319. * height: 400,
  7320. * sprites: [{
  7321. * type: 'ellipticalArc',
  7322. * cx: 100,
  7323. * cy: 100,
  7324. * rx: 80,
  7325. * ry: 50,
  7326. * fillStyle: '#1F6D91',
  7327. * startAngle: 0,
  7328. * endAngle: Math.PI,
  7329. * anticlockwise: true
  7330. * }]
  7331. * });
  7332. */
  7333. Ext.define('Ext.draw.sprite.EllipticalArc', {
  7334. extend: 'Ext.draw.sprite.Ellipse',
  7335. alias: 'sprite.ellipticalArc',
  7336. type: 'ellipticalArc',
  7337. inheritableStatics: {
  7338. def: {
  7339. processors: {
  7340. /**
  7341. * @cfg {Number} [startAngle=0] The beginning angle of the arc.
  7342. */
  7343. startAngle: 'number',
  7344. /**
  7345. * @cfg {Number} [endAngle=Math.PI*2] The ending angle of the arc.
  7346. */
  7347. endAngle: 'number',
  7348. /**
  7349. * @cfg {Boolean} [anticlockwise=false] Determines whether or not the arc is drawn clockwise.
  7350. */
  7351. anticlockwise: 'bool'
  7352. },
  7353. aliases: {
  7354. from: 'startAngle',
  7355. to: 'endAngle',
  7356. start: 'startAngle',
  7357. end: 'endAngle'
  7358. },
  7359. defaults: {
  7360. startAngle: 0,
  7361. endAngle: Math.PI * 2,
  7362. anticlockwise: false
  7363. },
  7364. triggers: {
  7365. startAngle: 'path',
  7366. endAngle: 'path',
  7367. anticlockwise: 'path'
  7368. }
  7369. }
  7370. },
  7371. updatePath: function(path, attr) {
  7372. path.ellipse(attr.cx, attr.cy, attr.rx, attr.ry, attr.axisRotation, attr.startAngle, attr.endAngle, attr.anticlockwise);
  7373. }
  7374. });
  7375. /**
  7376. * @class Ext.draw.sprite.Rect
  7377. * @extends Ext.draw.sprite.Path
  7378. *
  7379. * A sprite that represents a rectangle.
  7380. *
  7381. * @example
  7382. * Ext.create({
  7383. * xtype: 'draw',
  7384. * renderTo: document.body,
  7385. * width: 600,
  7386. * height: 400,
  7387. * sprites: [{
  7388. * type: 'rect',
  7389. * x: 50,
  7390. * y: 50,
  7391. * width: 100,
  7392. * height: 100,
  7393. * fillStyle: '#1F6D91'
  7394. * }]
  7395. * });
  7396. */
  7397. Ext.define('Ext.draw.sprite.Rect', {
  7398. extend: 'Ext.draw.sprite.Path',
  7399. alias: 'sprite.rect',
  7400. type: 'rect',
  7401. inheritableStatics: {
  7402. def: {
  7403. processors: {
  7404. /**
  7405. * @cfg {Number} [x=0] The position of the sprite on the x-axis.
  7406. */
  7407. x: 'number',
  7408. /**
  7409. * @cfg {Number} [y=0] The position of the sprite on the y-axis.
  7410. */
  7411. y: 'number',
  7412. /**
  7413. * @cfg {Number} [width=8] The width of the sprite.
  7414. */
  7415. width: 'number',
  7416. /**
  7417. * @cfg {Number} [height=8] The height of the sprite.
  7418. */
  7419. height: 'number',
  7420. /**
  7421. * @cfg {Number} [radius=0] The radius of the rounded corners.
  7422. */
  7423. radius: 'number'
  7424. },
  7425. aliases: {},
  7426. triggers: {
  7427. x: 'path',
  7428. y: 'path',
  7429. width: 'path',
  7430. height: 'path',
  7431. radius: 'path'
  7432. },
  7433. defaults: {
  7434. x: 0,
  7435. y: 0,
  7436. width: 8,
  7437. height: 8,
  7438. radius: 0
  7439. }
  7440. }
  7441. },
  7442. updatePlainBBox: function(plain) {
  7443. var attr = this.attr;
  7444. plain.x = attr.x;
  7445. plain.y = attr.y;
  7446. plain.width = attr.width;
  7447. plain.height = attr.height;
  7448. },
  7449. updateTransformedBBox: function(transform, plain) {
  7450. this.attr.matrix.transformBBox(plain, this.attr.radius, transform);
  7451. },
  7452. updatePath: function(path, attr) {
  7453. var x = attr.x,
  7454. y = attr.y,
  7455. width = attr.width,
  7456. height = attr.height,
  7457. radius = Math.min(attr.radius, Math.abs(height) * 0.5, Math.abs(width) * 0.5);
  7458. if (radius === 0) {
  7459. path.rect(x, y, width, height);
  7460. } else {
  7461. path.moveTo(x + radius, y);
  7462. path.arcTo(x + width, y, x + width, y + height, radius);
  7463. path.arcTo(x + width, y + height, x, y + height, radius);
  7464. path.arcTo(x, y + height, x, y, radius);
  7465. path.arcTo(x, y, x + radius, y, radius);
  7466. path.closePath();
  7467. }
  7468. }
  7469. });
  7470. /**
  7471. * @class Ext.draw.sprite.Image
  7472. * @extends Ext.draw.sprite.Rect
  7473. *
  7474. * A sprite that represents an image.
  7475. */
  7476. Ext.define('Ext.draw.sprite.Image', {
  7477. extend: 'Ext.draw.sprite.Rect',
  7478. alias: 'sprite.image',
  7479. type: 'image',
  7480. statics: {
  7481. imageLoaders: {}
  7482. },
  7483. inheritableStatics: {
  7484. def: {
  7485. processors: {
  7486. /**
  7487. * @cfg {String} [src=''] The image source of the sprite.
  7488. */
  7489. src: 'string'
  7490. },
  7491. /**
  7492. * @private
  7493. * @cfg {Number} radius
  7494. */
  7495. triggers: {
  7496. src: 'src'
  7497. },
  7498. updaters: {
  7499. src: 'updateSource'
  7500. },
  7501. defaults: {
  7502. src: '',
  7503. /**
  7504. * @cfg {Number} [width=null] The width of the image.
  7505. * For consistent image size on all devices the width must be explicitly set.
  7506. * Otherwise the natural image width devided by the device pixel ratio
  7507. * (for a crisp looking image) will be used as the width of the sprite.
  7508. */
  7509. width: null,
  7510. /**
  7511. * @cfg {Number} [height=null] The height of the image.
  7512. * For consistent image size on all devices the height must be explicitly set.
  7513. * Otherwise the natural image height devided by the device pixel ratio
  7514. * (for a crisp looking image) will be used as the height of the sprite.
  7515. */
  7516. height: null
  7517. }
  7518. }
  7519. },
  7520. updateSurface: function(surface) {
  7521. if (surface) {
  7522. this.updateSource(this.attr);
  7523. }
  7524. },
  7525. updateSource: function(attr) {
  7526. var me = this,
  7527. src = attr.src,
  7528. surface = me.getSurface(),
  7529. loadingStub = Ext.draw.sprite.Image.imageLoaders[src],
  7530. width = attr.width,
  7531. height = attr.height,
  7532. imageLoader, i;
  7533. if (!surface) {
  7534. // First time this is called the sprite won't have a surface yet.
  7535. return;
  7536. }
  7537. if (!loadingStub) {
  7538. imageLoader = new Image();
  7539. loadingStub = Ext.draw.sprite.Image.imageLoaders[src] = {
  7540. image: imageLoader,
  7541. done: false,
  7542. pendingSprites: [
  7543. me
  7544. ],
  7545. pendingSurfaces: [
  7546. surface
  7547. ]
  7548. };
  7549. imageLoader.width = width;
  7550. imageLoader.height = height;
  7551. imageLoader.onload = function() {
  7552. var item;
  7553. if (!loadingStub.done) {
  7554. loadingStub.done = true;
  7555. for (i = 0; i < loadingStub.pendingSprites.length; i++) {
  7556. item = loadingStub.pendingSprites[i];
  7557. if (!item.destroyed) {
  7558. item.setDirty(true);
  7559. }
  7560. }
  7561. for (i = 0; i < loadingStub.pendingSurfaces.length; i++) {
  7562. item = loadingStub.pendingSurfaces[i];
  7563. if (!item.destroyed) {
  7564. item.renderFrame();
  7565. }
  7566. }
  7567. }
  7568. };
  7569. imageLoader.src = src;
  7570. } else {
  7571. Ext.Array.include(loadingStub.pendingSprites, me);
  7572. Ext.Array.include(loadingStub.pendingSurfaces, surface);
  7573. }
  7574. },
  7575. render: function(surface, ctx) {
  7576. var me = this,
  7577. attr = me.attr,
  7578. mat = attr.matrix,
  7579. src = attr.src,
  7580. x = attr.x,
  7581. y = attr.y,
  7582. width = attr.width,
  7583. height = attr.height,
  7584. loadingStub = Ext.draw.sprite.Image.imageLoaders[src],
  7585. image;
  7586. if (loadingStub && loadingStub.done) {
  7587. mat.toContext(ctx);
  7588. image = loadingStub.image;
  7589. ctx.drawImage(image, x, y, width || (image.naturalWidth || image.width) / surface.devicePixelRatio, height || (image.naturalHeight || image.height) / surface.devicePixelRatio);
  7590. }
  7591. //<debug>
  7592. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  7593. if (debug) {
  7594. debug.bbox && this.renderBBox(surface, ctx);
  7595. }
  7596. },
  7597. //</debug>
  7598. /**
  7599. * @private
  7600. */
  7601. isVisible: function() {
  7602. var attr = this.attr,
  7603. parent = this.getParent(),
  7604. hasParent = parent && (parent.isSurface || parent.isVisible()),
  7605. isSeen = hasParent && !attr.hidden && attr.globalAlpha;
  7606. return !!isSeen;
  7607. }
  7608. });
  7609. /**
  7610. * @class Ext.draw.sprite.Instancing
  7611. * @extends Ext.draw.sprite.Sprite
  7612. *
  7613. * Sprite that represents multiple instances based on the given template.
  7614. */
  7615. Ext.define('Ext.draw.sprite.Instancing', {
  7616. extend: 'Ext.draw.sprite.Sprite',
  7617. alias: 'sprite.instancing',
  7618. type: 'instancing',
  7619. isInstancing: true,
  7620. config: {
  7621. /**
  7622. * @cfg {Object} [template] The sprite template used by all instances.
  7623. */
  7624. template: null,
  7625. /**
  7626. * @cfg {Array} [instances]
  7627. * The instances of the {@link #template} sprite as configs of attributes.
  7628. */
  7629. instances: null
  7630. },
  7631. instances: null,
  7632. applyTemplate: function(template) {
  7633. //<debug>
  7634. if (!Ext.isObject(template)) {
  7635. Ext.raise("A template of an instancing sprite must either be " + "a sprite instance or a valid config object from which a template " + "sprite will be created.");
  7636. } else if (template.isInstancing || template.isComposite) {
  7637. Ext.raise("Can't use an instancing or composite sprite " + "as a template for an instancing sprite.");
  7638. }
  7639. //</debug>
  7640. if (!template.isSprite) {
  7641. if (!template.xclass && !template.type) {
  7642. // For compatibility with legacy charts.
  7643. template.type = 'circle';
  7644. }
  7645. template = Ext.create(template.xclass || 'sprite.' + template.type, template);
  7646. }
  7647. var surface = template.getSurface();
  7648. if (surface) {
  7649. surface.remove(template);
  7650. }
  7651. template.setParent(this);
  7652. return template;
  7653. },
  7654. updateTemplate: function(template, oldTemplate) {
  7655. if (oldTemplate) {
  7656. delete oldTemplate.ownAttr;
  7657. }
  7658. template.setSurface(this.getSurface());
  7659. // ownAttr is used to get a reference to the template's attributes
  7660. // when one of the instances is rendering, as at that moment the template's
  7661. // attributes (template.attr) are the instance's attributes.
  7662. template.ownAttr = template.attr;
  7663. this.clearAll();
  7664. this.setDirty(true);
  7665. },
  7666. updateInstances: function(instances) {
  7667. this.clearAll();
  7668. if (Ext.isArray(instances)) {
  7669. for (var i = 0,
  7670. ln = instances.length; i < ln; i++) {
  7671. this.add(instances[i]);
  7672. }
  7673. }
  7674. },
  7675. updateSurface: function(surface) {
  7676. var template = this.getTemplate();
  7677. if (template && !template.destroyed) {
  7678. template.setSurface(surface);
  7679. }
  7680. },
  7681. get: function(index) {
  7682. return this.instances[index];
  7683. },
  7684. getCount: function() {
  7685. return this.instances.length;
  7686. },
  7687. clearAll: function() {
  7688. var template = this.getTemplate();
  7689. template.attr.children = this.instances = [];
  7690. this.position = 0;
  7691. },
  7692. /**
  7693. * @deprecated 6.2.0
  7694. * Deprecated, use the {@link #add} method instead.
  7695. */
  7696. createInstance: function(config, bypassNormalization, avoidCopy) {
  7697. return this.add(config, bypassNormalization, avoidCopy);
  7698. },
  7699. /**
  7700. * Creates a new sprite instance.
  7701. *
  7702. * @param {Object} config The configuration of the instance.
  7703. * @param {Boolean} [bypassNormalization] 'true' to bypass attribute normalization.
  7704. * @param {Boolean} [avoidCopy] 'true' to avoid copying the `config` object.
  7705. * @return {Object} The attributes of the instance.
  7706. */
  7707. add: function(config, bypassNormalization, avoidCopy) {
  7708. var me = this,
  7709. template = me.getTemplate(),
  7710. originalAttr = template.attr,
  7711. attr = Ext.Object.chain(originalAttr);
  7712. template.modifiers.target.prepareAttributes(attr);
  7713. template.attr = attr;
  7714. template.setAttributes(config, bypassNormalization, avoidCopy);
  7715. attr.template = template;
  7716. me.instances.push(attr);
  7717. template.attr = originalAttr;
  7718. me.position++;
  7719. return attr;
  7720. },
  7721. /**
  7722. * Not supported.
  7723. *
  7724. * @return {null}
  7725. */
  7726. getBBox: function() {
  7727. return null;
  7728. },
  7729. /**
  7730. * Returns the bounding box for the instance at the given index.
  7731. *
  7732. * @param {Number} index The index of the instance.
  7733. * @param {Boolean} [isWithoutTransform] 'true' to not apply sprite transforms to the bounding box.
  7734. * @return {Object} The bounding box for the instance.
  7735. */
  7736. getBBoxFor: function(index, isWithoutTransform) {
  7737. var template = this.getTemplate(),
  7738. originalAttr = template.attr,
  7739. bbox;
  7740. template.attr = this.instances[index];
  7741. bbox = template.getBBox(isWithoutTransform);
  7742. template.attr = originalAttr;
  7743. return bbox;
  7744. },
  7745. /**
  7746. * @private
  7747. * Checks if the instancing sprite can be seen.
  7748. * @return {Boolean}
  7749. */
  7750. isVisible: function() {
  7751. var attr = this.attr,
  7752. parent = this.getParent(),
  7753. result;
  7754. result = parent && parent.isSurface && !attr.hidden && attr.globalAlpha;
  7755. return !!result;
  7756. },
  7757. /**
  7758. * @private
  7759. * Checks if the instance of an instancing sprite can be seen.
  7760. * @param {Number} index The index of the instance.
  7761. */
  7762. isInstanceVisible: function(index) {
  7763. var me = this,
  7764. template = me.getTemplate(),
  7765. originalAttr = template.attr,
  7766. instances = me.instances,
  7767. result = false;
  7768. if (!Ext.isNumber(index) || index < 0 || index >= instances.length || !me.isVisible()) {
  7769. return result;
  7770. }
  7771. template.attr = instances[index];
  7772. result = template.isVisible(point, options);
  7773. template.attr = originalAttr;
  7774. return result;
  7775. },
  7776. render: function(surface, ctx, rect) {
  7777. //<debug>
  7778. if (!this.getTemplate()) {
  7779. Ext.raise('An instancing sprite must have a template.');
  7780. }
  7781. //</debug>
  7782. var me = this,
  7783. template = me.getTemplate(),
  7784. surfaceRect = surface.getRect(),
  7785. mat = me.attr.matrix,
  7786. originalAttr = template.attr,
  7787. instances = me.instances,
  7788. ln = me.position,
  7789. i;
  7790. mat.toContext(ctx);
  7791. template.preRender(surface, ctx, rect);
  7792. template.useAttributes(ctx, surfaceRect);
  7793. template.isSpriteInstance = true;
  7794. for (i = 0; i < ln; i++) {
  7795. if (instances[i].hidden) {
  7796. continue;
  7797. }
  7798. ctx.save();
  7799. template.attr = instances[i];
  7800. template.useAttributes(ctx, surfaceRect);
  7801. template.render(surface, ctx, rect);
  7802. ctx.restore();
  7803. }
  7804. template.isSpriteInstance = false;
  7805. template.attr = originalAttr;
  7806. },
  7807. /**
  7808. * Sets the attributes for the instance at the given index.
  7809. *
  7810. * @param {Number} index the index of the instance
  7811. * @param {Object} changes the attributes to change
  7812. * @param {Boolean} [bypassNormalization] 'true' to avoid attribute normalization
  7813. */
  7814. setAttributesFor: function(index, changes, bypassNormalization) {
  7815. var template = this.getTemplate(),
  7816. originalAttr = template.attr,
  7817. attr = this.instances[index];
  7818. if (!attr) {
  7819. return;
  7820. }
  7821. template.attr = attr;
  7822. if (bypassNormalization) {
  7823. changes = Ext.apply({}, changes);
  7824. } else {
  7825. changes = template.self.def.normalize(changes);
  7826. }
  7827. template.modifiers.target.pushDown(attr, changes);
  7828. template.attr = originalAttr;
  7829. },
  7830. destroy: function() {
  7831. var me = this,
  7832. template = me.getTemplate();
  7833. me.instances = null;
  7834. if (template) {
  7835. template.destroy();
  7836. }
  7837. me.callParent();
  7838. }
  7839. });
  7840. /**
  7841. * @private
  7842. * Adds hit testing methods to the Ext.draw.sprite.Instancing.
  7843. * Included by the Ext.draw.plugin.SpriteEvents.
  7844. */
  7845. Ext.define('Ext.draw.overrides.hittest.sprite.Instancing', {
  7846. override: 'Ext.draw.sprite.Instancing',
  7847. /**
  7848. * Performs a hit test on the instances of an instancing sprite.
  7849. * @param point A two-item array containing x and y coordinates of the point.
  7850. * @param options Hit testing options.
  7851. * @return {Object} A hit result object that contains more information about what
  7852. * exactly was hit or null if nothing was hit.
  7853. * @return {Boolean} return.isInstance `true` if an instance was hit.
  7854. * @return {Ext.draw.sprite.Instancing} return.sprite The instancing sprite.
  7855. * @return {Ext.draw.sprite.Sprite} return.template The template of the instancing sprite.
  7856. * @return {Object} return.instance The attributes of the instance.
  7857. * @return {Number} return.index The index of the instance.
  7858. */
  7859. hitTest: function(point, options) {
  7860. var me = this,
  7861. template = me.getTemplate(),
  7862. originalAttr = template.attr,
  7863. instances = me.instances,
  7864. ln = instances.length,
  7865. i = 0,
  7866. result = null;
  7867. if (!me.isVisible()) {
  7868. return result;
  7869. }
  7870. for (; i < ln; i++) {
  7871. template.attr = instances[i];
  7872. result = template.hitTest(point, options);
  7873. if (result) {
  7874. result.isInstance = true;
  7875. result.template = result.sprite;
  7876. result.sprite = this;
  7877. result.instance = instances[i];
  7878. result.index = i;
  7879. return result;
  7880. }
  7881. }
  7882. template.attr = originalAttr;
  7883. return result;
  7884. }
  7885. });
  7886. /**
  7887. * A sprite that represents a line.
  7888. *
  7889. * @example
  7890. * Ext.create({
  7891. * xtype: 'draw',
  7892. * renderTo: document.body,
  7893. * width: 600,
  7894. * height: 400,
  7895. * sprites: [{
  7896. * type: 'line',
  7897. * fromX: 20,
  7898. * fromY: 20,
  7899. * toX: 120,
  7900. * toY: 120,
  7901. * strokeStyle: '#1F6D91',
  7902. * lineWidth: 3
  7903. * }]
  7904. * });
  7905. */
  7906. Ext.define('Ext.draw.sprite.Line', {
  7907. extend: 'Ext.draw.sprite.Sprite',
  7908. alias: 'sprite.line',
  7909. type: 'line',
  7910. inheritableStatics: {
  7911. def: {
  7912. processors: {
  7913. fromX: 'number',
  7914. fromY: 'number',
  7915. toX: 'number',
  7916. toY: 'number',
  7917. crisp: 'bool'
  7918. },
  7919. defaults: {
  7920. fromX: 0,
  7921. fromY: 0,
  7922. toX: 1,
  7923. toY: 1,
  7924. crisp: false,
  7925. strokeStyle: 'black'
  7926. },
  7927. aliases: {
  7928. x1: 'fromX',
  7929. y1: 'fromY',
  7930. x2: 'toX',
  7931. y2: 'toY'
  7932. },
  7933. triggers: {
  7934. crisp: 'bbox'
  7935. }
  7936. }
  7937. },
  7938. updateLineBBox: function(bbox, isTransform, x1, y1, x2, y2) {
  7939. var attr = this.attr,
  7940. matrix = attr.matrix,
  7941. halfLineWidth = attr.lineWidth / 2,
  7942. fromX, fromY, toX, toY, p;
  7943. if (attr.crisp) {
  7944. x1 = this.align(x1);
  7945. x2 = this.align(x2);
  7946. y1 = this.align(y1);
  7947. y2 = this.align(y2);
  7948. }
  7949. if (isTransform) {
  7950. p = matrix.transformPoint([
  7951. x1,
  7952. y1
  7953. ]);
  7954. x1 = p[0];
  7955. y1 = p[1];
  7956. p = matrix.transformPoint([
  7957. x2,
  7958. y2
  7959. ]);
  7960. x2 = p[0];
  7961. y2 = p[1];
  7962. }
  7963. fromX = Math.min(x1, x2);
  7964. toX = Math.max(x1, x2);
  7965. fromY = Math.min(y1, y2);
  7966. toY = Math.max(y1, y2);
  7967. var angle = Math.atan2(toX - fromX, toY - fromY),
  7968. sin = Math.sin(angle),
  7969. cos = Math.cos(angle),
  7970. dx = halfLineWidth * cos,
  7971. dy = halfLineWidth * sin;
  7972. // Offset start and end points of the line by half its thickness,
  7973. // while accounting for line's angle.
  7974. fromX -= dx;
  7975. fromY -= dy;
  7976. toX += dx;
  7977. toY += dy;
  7978. bbox.x = fromX;
  7979. bbox.y = fromY;
  7980. bbox.width = toX - fromX;
  7981. bbox.height = toY - fromY;
  7982. },
  7983. updatePlainBBox: function(plain) {
  7984. var attr = this.attr;
  7985. this.updateLineBBox(plain, false, attr.fromX, attr.fromY, attr.toX, attr.toY);
  7986. },
  7987. updateTransformedBBox: function(transform, plain) {
  7988. var attr = this.attr;
  7989. this.updateLineBBox(transform, true, attr.fromX, attr.fromY, attr.toX, attr.toY);
  7990. },
  7991. align: function(x) {
  7992. return Math.round(x) - 0.5;
  7993. },
  7994. render: function(surface, ctx) {
  7995. var me = this,
  7996. attr = me.attr,
  7997. matrix = attr.matrix;
  7998. matrix.toContext(ctx);
  7999. ctx.beginPath();
  8000. if (attr.crisp) {
  8001. ctx.moveTo(me.align(attr.fromX), me.align(attr.fromY));
  8002. ctx.lineTo(me.align(attr.toX), me.align(attr.toY));
  8003. } else {
  8004. ctx.moveTo(attr.fromX, attr.fromY);
  8005. ctx.lineTo(attr.toX, attr.toY);
  8006. }
  8007. ctx.stroke();
  8008. //<debug>
  8009. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  8010. if (debug) {
  8011. // This assumes no part of the sprite is rendered after this call.
  8012. // If it is, we need to re-apply transformations.
  8013. // But the bounding box should always be rendered as is, untransformed.
  8014. this.attr.inverseMatrix.toContext(ctx);
  8015. debug.bbox && this.renderBBox(surface, ctx);
  8016. }
  8017. }
  8018. });
  8019. //</debug>
  8020. /**
  8021. * A sprite that represents a plus.
  8022. *
  8023. * @example
  8024. * Ext.create({
  8025. * xtype: 'draw',
  8026. * renderTo: document.body,
  8027. * width: 600,
  8028. * height: 400,
  8029. * sprites: [{
  8030. * type: 'plus',
  8031. * translationX: 100,
  8032. * translationY: 100,
  8033. * size: 40,
  8034. * fillStyle: '#1F6D91'
  8035. * }]
  8036. * });
  8037. */
  8038. Ext.define('Ext.draw.sprite.Plus', {
  8039. extend: 'Ext.draw.sprite.Path',
  8040. alias: 'sprite.plus',
  8041. inheritableStatics: {
  8042. def: {
  8043. processors: {
  8044. x: 'number',
  8045. y: 'number',
  8046. /**
  8047. * @cfg {Number} [size=4] The size of the sprite.
  8048. * Meant to be comparable to the size of a circle sprite with the same radius.
  8049. */
  8050. size: 'number'
  8051. },
  8052. defaults: {
  8053. x: 0,
  8054. y: 0,
  8055. size: 4
  8056. },
  8057. triggers: {
  8058. x: 'path',
  8059. y: 'path',
  8060. size: 'path'
  8061. }
  8062. }
  8063. },
  8064. updatePath: function(path, attr) {
  8065. var s = attr.size / 1.3,
  8066. x = attr.x - attr.lineWidth / 2,
  8067. y = attr.y;
  8068. path.fromSvgString('M'.concat(x - s / 2, ',', y - s / 2, 'l', [
  8069. 0,
  8070. -s,
  8071. s,
  8072. 0,
  8073. 0,
  8074. s,
  8075. s,
  8076. 0,
  8077. 0,
  8078. s,
  8079. -s,
  8080. 0,
  8081. 0,
  8082. s,
  8083. -s,
  8084. 0,
  8085. 0,
  8086. -s,
  8087. -s,
  8088. 0,
  8089. 0,
  8090. -s,
  8091. 'z'
  8092. ]));
  8093. }
  8094. });
  8095. /**
  8096. * @class Ext.draw.sprite.Sector
  8097. * @extends Ext.draw.sprite.Path
  8098. *
  8099. * A sprite representing a pie slice.
  8100. *
  8101. * @example
  8102. * Ext.create({
  8103. * xtype: 'draw',
  8104. * renderTo: document.body,
  8105. * width: 600,
  8106. * height: 400,
  8107. * sprites: [{
  8108. * type: 'sector',
  8109. * centerX: 100,
  8110. * centerY: 100,
  8111. * startAngle: -2.355,
  8112. * endAngle: -.785,
  8113. * endRho: 50,
  8114. * fillStyle: '#1F6D91'
  8115. * }]
  8116. * });
  8117. */
  8118. Ext.define('Ext.draw.sprite.Sector', {
  8119. extend: 'Ext.draw.sprite.Path',
  8120. alias: 'sprite.sector',
  8121. type: 'sector',
  8122. inheritableStatics: {
  8123. def: {
  8124. processors: {
  8125. /**
  8126. * @cfg {Number} [centerX=0] The center coordinate of the sprite on the x-axis.
  8127. */
  8128. centerX: 'number',
  8129. /**
  8130. * @cfg {Number} [centerY=0] The center coordinate of the sprite on the y-axis.
  8131. */
  8132. centerY: 'number',
  8133. /**
  8134. * @cfg {Number} [startAngle=0] The starting angle of the sprite.
  8135. */
  8136. startAngle: 'number',
  8137. /**
  8138. * @cfg {Number} [endAngle=0] The ending angle of the sprite.
  8139. */
  8140. endAngle: 'number',
  8141. /**
  8142. * @cfg {Number} [startRho=0] The starting point of the radius of the sprite.
  8143. */
  8144. startRho: 'number',
  8145. /**
  8146. * @cfg {Number} [endRho=150] The ending point of the radius of the sprite.
  8147. */
  8148. endRho: 'number',
  8149. /**
  8150. * @cfg {Number} [margin=0] The margin of the sprite from the center of pie.
  8151. */
  8152. margin: 'number'
  8153. },
  8154. aliases: {
  8155. rho: 'endRho'
  8156. },
  8157. triggers: {
  8158. centerX: 'path,bbox',
  8159. centerY: 'path,bbox',
  8160. startAngle: 'path,bbox',
  8161. endAngle: 'path,bbox',
  8162. startRho: 'path,bbox',
  8163. endRho: 'path,bbox',
  8164. margin: 'path,bbox'
  8165. },
  8166. defaults: {
  8167. centerX: 0,
  8168. centerY: 0,
  8169. startAngle: 0,
  8170. endAngle: 0,
  8171. startRho: 0,
  8172. endRho: 150,
  8173. margin: 0,
  8174. path: 'M 0,0'
  8175. }
  8176. }
  8177. },
  8178. getMidAngle: function() {
  8179. return this.midAngle || 0;
  8180. },
  8181. updatePath: function(path, attr) {
  8182. var startAngle = Math.min(attr.startAngle, attr.endAngle),
  8183. endAngle = Math.max(attr.startAngle, attr.endAngle),
  8184. midAngle = this.midAngle = (startAngle + endAngle) * 0.5,
  8185. fullPie = Ext.Number.isEqual(Math.abs(endAngle - startAngle), Ext.draw.Draw.pi2, 1.0E-10),
  8186. margin = attr.margin,
  8187. centerX = attr.centerX,
  8188. centerY = attr.centerY,
  8189. startRho = Math.min(attr.startRho, attr.endRho),
  8190. endRho = Math.max(attr.startRho, attr.endRho);
  8191. if (margin) {
  8192. centerX += margin * Math.cos(midAngle);
  8193. centerY += margin * Math.sin(midAngle);
  8194. }
  8195. if (!fullPie) {
  8196. path.moveTo(centerX + startRho * Math.cos(startAngle), centerY + startRho * Math.sin(startAngle));
  8197. path.lineTo(centerX + endRho * Math.cos(startAngle), centerY + endRho * Math.sin(startAngle));
  8198. }
  8199. path.arc(centerX, centerY, endRho, startAngle, endAngle, false);
  8200. path[fullPie ? 'moveTo' : 'lineTo'](centerX + startRho * Math.cos(endAngle), centerY + startRho * Math.sin(endAngle));
  8201. path.arc(centerX, centerY, startRho, endAngle, startAngle, true);
  8202. }
  8203. });
  8204. /**
  8205. * A sprite that represents a square.
  8206. *
  8207. * @example
  8208. * Ext.create({
  8209. * xtype: 'draw',
  8210. * renderTo: document.body,
  8211. * width: 600,
  8212. * height: 400,
  8213. * sprites: [{
  8214. * type: 'square',
  8215. * x: 100,
  8216. * y: 100,
  8217. * size: 50,
  8218. * fillStyle: '#1F6D91'
  8219. * }]
  8220. * });
  8221. */
  8222. Ext.define('Ext.draw.sprite.Square', {
  8223. extend: 'Ext.draw.sprite.Path',
  8224. alias: 'sprite.square',
  8225. inheritableStatics: {
  8226. def: {
  8227. processors: {
  8228. x: 'number',
  8229. y: 'number',
  8230. /**
  8231. * @cfg {Number} [size=4] The size of the sprite.
  8232. * Meant to be comparable to the size of a circle sprite with the same radius.
  8233. */
  8234. size: 'number'
  8235. },
  8236. defaults: {
  8237. x: 0,
  8238. y: 0,
  8239. size: 4
  8240. },
  8241. triggers: {
  8242. x: 'path',
  8243. y: 'path',
  8244. size: 'size'
  8245. }
  8246. }
  8247. },
  8248. updatePath: function(path, attr) {
  8249. var size = attr.size * 1.2,
  8250. s = size * 2,
  8251. x = attr.x - attr.lineWidth / 2,
  8252. y = attr.y;
  8253. path.fromSvgString('M'.concat(x - size, ',', y - size, 'l', [
  8254. s,
  8255. 0,
  8256. 0,
  8257. s,
  8258. -s,
  8259. 0,
  8260. 0,
  8261. -s,
  8262. 'z'
  8263. ]));
  8264. }
  8265. });
  8266. /**
  8267. * Utility class to provide a way to *approximately* measure the dimension of text
  8268. * without a drawing context.
  8269. */
  8270. Ext.define('Ext.draw.TextMeasurer', {
  8271. singleton: true,
  8272. requires: [
  8273. 'Ext.util.TextMetrics'
  8274. ],
  8275. measureDiv: null,
  8276. measureCache: {},
  8277. /**
  8278. * @cfg {Boolean} [precise=false]
  8279. * This singleton tries not to make use of the Ext.util.TextMetrics because it is
  8280. * several times slower than TextMeasurer's own solution. TextMetrics is more precise
  8281. * though, so if you have a case where the error is too big, you may want to set
  8282. * this config to `true` to get perfect results at the expense of performance.
  8283. * Note: defaults to `true` in IE8.
  8284. */
  8285. precise: Ext.isIE8,
  8286. measureDivTpl: {
  8287. id: 'ext-draw-text-measurer',
  8288. tag: 'div',
  8289. style: {
  8290. overflow: 'hidden',
  8291. position: 'relative',
  8292. 'float': 'left',
  8293. // 'float' is a reserved word. Don't unquote, or it will break the CMD build.
  8294. width: 0,
  8295. height: 0
  8296. },
  8297. //<debug>
  8298. // Tell the spec runner to ignore this element when checking if the dom is clean.
  8299. 'data-sticky': true,
  8300. //</debug>
  8301. children: {
  8302. tag: 'div',
  8303. style: {
  8304. display: 'block',
  8305. position: 'absolute',
  8306. x: -100000,
  8307. y: -100000,
  8308. padding: 0,
  8309. margin: 0,
  8310. 'z-index': -100000,
  8311. 'white-space': 'nowrap'
  8312. }
  8313. }
  8314. },
  8315. /**
  8316. * @private
  8317. * Measure the size of a text with specific font by using DOM to measure it.
  8318. * Could be very expensive therefore should be used lazily.
  8319. * @param {String} text
  8320. * @param {String} font
  8321. * @return {Object} An object with `width` and `height` properties.
  8322. * @return {Number} return.width
  8323. * @return {Number} return.height
  8324. */
  8325. actualMeasureText: function(text, font) {
  8326. var me = Ext.draw.TextMeasurer,
  8327. measureDiv = me.measureDiv,
  8328. FARAWAY = 100000,
  8329. size;
  8330. if (!measureDiv) {
  8331. var parent = Ext.Element.create({
  8332. //<debug>
  8333. // Tell the spec runner to ignore this element when checking if the dom is clean.
  8334. 'data-sticky': true,
  8335. //</debug>
  8336. style: {
  8337. "overflow": "hidden",
  8338. "position": "relative",
  8339. "float": "left",
  8340. // DO NOT REMOVE THE QUOTE OR IT WILL BREAK COMPRESSOR
  8341. "width": 0,
  8342. "height": 0
  8343. }
  8344. });
  8345. me.measureDiv = measureDiv = Ext.Element.create({
  8346. style: {
  8347. "position": 'absolute',
  8348. "x": FARAWAY,
  8349. "y": FARAWAY,
  8350. "z-index": -FARAWAY,
  8351. "white-space": "nowrap",
  8352. "display": 'block',
  8353. "padding": 0,
  8354. "margin": 0
  8355. }
  8356. });
  8357. Ext.getBody().appendChild(parent);
  8358. parent.appendChild(measureDiv);
  8359. }
  8360. if (font) {
  8361. measureDiv.setStyle({
  8362. font: font,
  8363. lineHeight: 'normal'
  8364. });
  8365. }
  8366. measureDiv.setText('(' + text + ')');
  8367. size = measureDiv.getSize();
  8368. measureDiv.setText('()');
  8369. size.width -= measureDiv.getSize().width;
  8370. return size;
  8371. },
  8372. /**
  8373. * Measure a single-line text with specific font.
  8374. * This will split the text into characters and add up their size.
  8375. * That may *not* be the exact size of the text as it is displayed.
  8376. * @param {String} text
  8377. * @param {String} font
  8378. * @return {Object} An object with `width` and `height` properties.
  8379. * @return {Number} return.width
  8380. * @return {Number} return.height
  8381. */
  8382. measureTextSingleLine: function(text, font) {
  8383. if (this.precise) {
  8384. return this.preciseMeasureTextSingleLine(text, font);
  8385. }
  8386. text = text.toString();
  8387. var cache = this.measureCache,
  8388. chars = text.split(''),
  8389. width = 0,
  8390. height = 0,
  8391. cachedItem, charactor, i, ln, size;
  8392. if (!cache[font]) {
  8393. cache[font] = {};
  8394. }
  8395. cache = cache[font];
  8396. if (cache[text]) {
  8397. return cache[text];
  8398. }
  8399. for (i = 0 , ln = chars.length; i < ln; i++) {
  8400. charactor = chars[i];
  8401. if (!(cachedItem = cache[charactor])) {
  8402. size = this.actualMeasureText(charactor, font);
  8403. cachedItem = cache[charactor] = size;
  8404. }
  8405. width += cachedItem.width;
  8406. height = Math.max(height, cachedItem.height);
  8407. }
  8408. return cache[text] = {
  8409. width: width,
  8410. height: height
  8411. };
  8412. },
  8413. // A more precise but slower version of the measureTextSingleLine method.
  8414. preciseMeasureTextSingleLine: function(text, font) {
  8415. text = text.toString();
  8416. var measureDiv = this.measureDiv || (this.measureDiv = Ext.getBody().createChild(this.measureDivTpl).down('div'));
  8417. measureDiv.setStyle({
  8418. font: font || ''
  8419. });
  8420. return Ext.util.TextMetrics.measure(measureDiv, text);
  8421. },
  8422. /**
  8423. * Measure a text with specific font.
  8424. * This will split the text to lines and add up their size.
  8425. * That may *not* be the exact size of the text as it is displayed.
  8426. * @param {String} text
  8427. * @param {String} font
  8428. * @return {Object} An object with `width`, `height` and `sizes` properties.
  8429. * @return {Number} return.width
  8430. * @return {Number} return.height
  8431. * @return {Object} return.sizes Results of individual line measurements, in case of multiline text.
  8432. */
  8433. measureText: function(text, font) {
  8434. var lines = text.split('\n'),
  8435. ln = lines.length,
  8436. height = 0,
  8437. width = 0,
  8438. line, i, sizes;
  8439. if (ln === 1) {
  8440. return this.measureTextSingleLine(text, font);
  8441. }
  8442. sizes = [];
  8443. for (i = 0; i < ln; i++) {
  8444. line = this.measureTextSingleLine(lines[i], font);
  8445. sizes.push(line);
  8446. height += line.height;
  8447. width = Math.max(width, line.width);
  8448. }
  8449. return {
  8450. width: width,
  8451. height: height,
  8452. sizes: sizes
  8453. };
  8454. }
  8455. });
  8456. /**
  8457. * @class Ext.draw.sprite.Text
  8458. * @extends Ext.draw.sprite.Sprite
  8459. *
  8460. * A sprite that represents text.
  8461. *
  8462. * @example
  8463. * Ext.create({
  8464. * xtype: 'draw',
  8465. * renderTo: document.body,
  8466. * width: 600,
  8467. * height: 400,
  8468. * sprites: [{
  8469. * type: 'text',
  8470. * x: 50,
  8471. * y: 50,
  8472. * text: 'Sencha',
  8473. * fontSize: 30,
  8474. * fillStyle: '#1F6D91'
  8475. * }]
  8476. * });
  8477. */
  8478. Ext.define('Ext.draw.sprite.Text', function() {
  8479. // Absolute font sizes.
  8480. var fontSizes = {
  8481. 'xx-small': true,
  8482. 'x-small': true,
  8483. 'small': true,
  8484. 'medium': true,
  8485. 'large': true,
  8486. 'x-large': true,
  8487. 'xx-large': true
  8488. };
  8489. var fontWeights = {
  8490. normal: true,
  8491. bold: true,
  8492. bolder: true,
  8493. lighter: true,
  8494. 100: true,
  8495. 200: true,
  8496. 300: true,
  8497. 400: true,
  8498. 500: true,
  8499. 600: true,
  8500. 700: true,
  8501. 800: true,
  8502. 900: true
  8503. };
  8504. var textAlignments = {
  8505. start: 'start',
  8506. left: 'start',
  8507. center: 'center',
  8508. middle: 'center',
  8509. end: 'end',
  8510. right: 'end'
  8511. };
  8512. var textBaselines = {
  8513. top: 'top',
  8514. hanging: 'hanging',
  8515. middle: 'middle',
  8516. center: 'middle',
  8517. alphabetic: 'alphabetic',
  8518. ideographic: 'ideographic',
  8519. bottom: 'bottom'
  8520. };
  8521. return {
  8522. extend: 'Ext.draw.sprite.Sprite',
  8523. requires: [
  8524. 'Ext.draw.TextMeasurer',
  8525. 'Ext.draw.Color'
  8526. ],
  8527. alias: 'sprite.text',
  8528. type: 'text',
  8529. lineBreakRe: /\r?\n/g,
  8530. //<debug>
  8531. statics: {
  8532. /**
  8533. * Debug rendering options:
  8534. *
  8535. * debug: {
  8536. * bbox: true // renders the bounding box of the text sprite
  8537. * }
  8538. *
  8539. */
  8540. debug: false,
  8541. fontSizes: fontSizes,
  8542. fontWeights: fontWeights,
  8543. textAlignments: textAlignments,
  8544. textBaselines: textBaselines
  8545. },
  8546. //</debug>
  8547. inheritableStatics: {
  8548. def: {
  8549. animationProcessors: {
  8550. text: 'text'
  8551. },
  8552. processors: {
  8553. /**
  8554. * @cfg {Number} [x=0]
  8555. * The position of the sprite on the x-axis.
  8556. */
  8557. x: 'number',
  8558. /**
  8559. * @cfg {Number} [y=0]
  8560. * The position of the sprite on the y-axis.
  8561. */
  8562. y: 'number',
  8563. /**
  8564. * @cfg {String} [text='']
  8565. * The text represented in the sprite.
  8566. */
  8567. text: 'string',
  8568. /**
  8569. * @cfg {String/Number} [fontSize='10px']
  8570. * The size of the font displayed.
  8571. */
  8572. fontSize: function(n) {
  8573. // Numbers as strings will be converted to numbers,
  8574. // null will be converted to 0.
  8575. if (Ext.isNumber(+n)) {
  8576. return n + 'px';
  8577. } else if (n.match(Ext.dom.Element.unitRe)) {
  8578. return n;
  8579. } else if (n in fontSizes) {
  8580. return n;
  8581. }
  8582. },
  8583. /**
  8584. * @cfg {String} [fontStyle='']
  8585. * The style of the font displayed. {normal, italic, oblique}
  8586. */
  8587. fontStyle: 'enums(,italic,oblique)',
  8588. /**
  8589. * @cfg {String} [fontVariant='']
  8590. * The variant of the font displayed. {normal, small-caps}
  8591. */
  8592. fontVariant: 'enums(,small-caps)',
  8593. /**
  8594. * @cfg {String} [fontWeight='']
  8595. * The weight of the font displayed. {normal, bold, bolder, lighter}
  8596. */
  8597. fontWeight: function(n) {
  8598. if (n in fontWeights) {
  8599. return String(n);
  8600. } else {
  8601. return '';
  8602. }
  8603. },
  8604. /**
  8605. * @cfg {String} [fontFamily='sans-serif']
  8606. * The family of the font displayed.
  8607. */
  8608. fontFamily: 'string',
  8609. /**
  8610. * @cfg {String} [textAlign='start']
  8611. * The alignment of the text displayed.
  8612. * {left, right, center, start, end}
  8613. */
  8614. textAlign: function(n) {
  8615. return textAlignments[n] || 'center';
  8616. },
  8617. /**
  8618. * @cfg {String} [textBaseline="alphabetic"]
  8619. * The baseline of the text displayed.
  8620. * {top, hanging, middle, alphabetic, ideographic, bottom}
  8621. */
  8622. textBaseline: function(n) {
  8623. return textBaselines[n] || 'alphabetic';
  8624. },
  8625. /**
  8626. * @cfg {String} [font='10px sans-serif']
  8627. * The font displayed.
  8628. */
  8629. font: 'string',
  8630. //<debug>
  8631. debug: 'default'
  8632. },
  8633. //</debug>
  8634. aliases: {
  8635. 'font-size': 'fontSize',
  8636. 'font-family': 'fontFamily',
  8637. 'font-weight': 'fontWeight',
  8638. 'font-variant': 'fontVariant',
  8639. 'text-anchor': 'textAlign',
  8640. 'dominant-baseline': 'textBaseline'
  8641. },
  8642. defaults: {
  8643. fontStyle: '',
  8644. fontVariant: '',
  8645. fontWeight: '',
  8646. fontSize: '10px',
  8647. fontFamily: 'sans-serif',
  8648. font: '10px sans-serif',
  8649. textBaseline: 'alphabetic',
  8650. textAlign: 'start',
  8651. strokeStyle: 'rgba(0, 0, 0, 0)',
  8652. fillStyle: '#000',
  8653. x: 0,
  8654. y: 0,
  8655. text: ''
  8656. },
  8657. triggers: {
  8658. fontStyle: 'fontX,bbox',
  8659. fontVariant: 'fontX,bbox',
  8660. fontWeight: 'fontX,bbox',
  8661. fontSize: 'fontX,bbox',
  8662. fontFamily: 'fontX,bbox',
  8663. font: 'font,bbox,canvas',
  8664. textBaseline: 'bbox',
  8665. textAlign: 'bbox',
  8666. x: 'bbox',
  8667. y: 'bbox',
  8668. text: 'bbox'
  8669. },
  8670. updaters: {
  8671. fontX: 'makeFontShorthand',
  8672. font: 'parseFontShorthand'
  8673. }
  8674. }
  8675. },
  8676. config: {
  8677. /**
  8678. * @private
  8679. * If the value is boolean, it overrides the TextMeasurer's 'precise' config
  8680. * (for the given sprite only).
  8681. */
  8682. preciseMeasurement: undefined
  8683. },
  8684. constructor: function(config) {
  8685. if (config && config.font) {
  8686. config = Ext.clone(config);
  8687. for (var key in config) {
  8688. if (key !== 'font' && key.indexOf('font') === 0) {
  8689. delete config[key];
  8690. }
  8691. }
  8692. }
  8693. Ext.draw.sprite.Sprite.prototype.constructor.call(this, config);
  8694. },
  8695. // Maps values to font properties they belong to.
  8696. fontValuesMap: {
  8697. // Skip 'normal' and 'inherit' values, as the first one
  8698. // is the default and the second one has no meaning in Canvas.
  8699. 'italic': 'fontStyle',
  8700. 'oblique': 'fontStyle',
  8701. 'small-caps': 'fontVariant',
  8702. 'bold': 'fontWeight',
  8703. 'bolder': 'fontWeight',
  8704. 'lighter': 'fontWeight',
  8705. '100': 'fontWeight',
  8706. '200': 'fontWeight',
  8707. '300': 'fontWeight',
  8708. '400': 'fontWeight',
  8709. '500': 'fontWeight',
  8710. '600': 'fontWeight',
  8711. '700': 'fontWeight',
  8712. '800': 'fontWeight',
  8713. '900': 'fontWeight',
  8714. // Absolute font sizes.
  8715. 'xx-small': 'fontSize',
  8716. 'x-small': 'fontSize',
  8717. 'small': 'fontSize',
  8718. 'medium': 'fontSize',
  8719. 'large': 'fontSize',
  8720. 'x-large': 'fontSize',
  8721. 'xx-large': 'fontSize'
  8722. },
  8723. // Relative font sizes like 'smaller' and 'larger'
  8724. // have no meaning, and are not included.
  8725. makeFontShorthand: function(attr) {
  8726. var parts = [];
  8727. if (attr.fontStyle) {
  8728. parts.push(attr.fontStyle);
  8729. }
  8730. if (attr.fontVariant) {
  8731. parts.push(attr.fontVariant);
  8732. }
  8733. if (attr.fontWeight) {
  8734. parts.push(attr.fontWeight);
  8735. }
  8736. if (attr.fontSize) {
  8737. parts.push(attr.fontSize);
  8738. }
  8739. if (attr.fontFamily) {
  8740. parts.push(attr.fontFamily);
  8741. }
  8742. this.setAttributes({
  8743. font: parts.join(' ')
  8744. }, true);
  8745. },
  8746. // For more info see:
  8747. // http://www.w3.org/TR/CSS21/fonts.html#font-shorthand
  8748. parseFontShorthand: function(attr) {
  8749. var value = attr.font,
  8750. ln = value.length,
  8751. changes = {},
  8752. dispatcher = this.fontValuesMap,
  8753. start = 0,
  8754. end, slashIndex, part, fontProperty;
  8755. while (start < ln && end !== -1) {
  8756. end = value.indexOf(' ', start);
  8757. if (end < 0) {
  8758. part = value.substr(start);
  8759. } else if (end > start) {
  8760. part = value.substr(start, end - start);
  8761. } else {
  8762. continue;
  8763. }
  8764. // Since Canvas fillText doesn't support multi-line text,
  8765. // it is assumed that line height is never specified, i.e.
  8766. // in entries like these the part after slash is omitted:
  8767. // 12px/14px sans-serif
  8768. // x-large/110% "New Century Schoolbook", serif
  8769. slashIndex = part.indexOf('/');
  8770. if (slashIndex > 0) {
  8771. part = part.substr(0, slashIndex);
  8772. } else if (slashIndex === 0) {
  8773. continue;
  8774. }
  8775. // All optional font properties (fontStyle, fontVariant or fontWeight) can be 'normal'.
  8776. // They can go in any order. Which ones are 'normal' is determined by elimination.
  8777. // E.g. if only fontVariant is specified, then 'normal' applies to fontStyle and fontWeight.
  8778. // If none are explicitly mentioned, then all are 'normal'.
  8779. if (part !== 'normal' && part !== 'inherit') {
  8780. fontProperty = dispatcher[part];
  8781. if (fontProperty) {
  8782. changes[fontProperty] = part;
  8783. } else if (part.match(Ext.dom.Element.unitRe)) {
  8784. changes.fontSize = part;
  8785. } else {
  8786. // Assuming that font family always goes last in the font shorthand.
  8787. changes.fontFamily = value.substr(start);
  8788. break;
  8789. }
  8790. }
  8791. start = end + 1;
  8792. }
  8793. if (!changes.fontStyle) {
  8794. changes.fontStyle = '';
  8795. }
  8796. // same as 'normal'
  8797. if (!changes.fontVariant) {
  8798. changes.fontVariant = '';
  8799. }
  8800. // same as 'normal'
  8801. if (!changes.fontWeight) {
  8802. changes.fontWeight = '';
  8803. }
  8804. // same as 'normal'
  8805. this.setAttributes(changes, true);
  8806. },
  8807. fontProperties: {
  8808. fontStyle: true,
  8809. fontVariant: true,
  8810. fontWeight: true,
  8811. fontSize: true,
  8812. fontFamily: true
  8813. },
  8814. setAttributes: function(changes, bypassNormalization, avoidCopy) {
  8815. var key, obj;
  8816. // Discard individual font properties if 'font' shorthand was also provided.
  8817. // Example: a user provides a config for chart series labels, using the font
  8818. // shorthand, which is parsed into individual font properties and corresponding
  8819. // sprite attributes are set. Then a theme is applied to the chart, and
  8820. // individual font properties from the theme make up the new font shorthand
  8821. // that overrides the previous one. In other words, no matter what font
  8822. // the user has specified, theme font will be used.
  8823. // This workaround relies on the fact that the theme merges its own config with
  8824. // the user config (where user config values take over the same theme config
  8825. // values). So both user font shorthand and individual font properties from
  8826. // the theme are present in the resulting config (since there are no collisions),
  8827. // which ends up here as the 'changes' parameter.
  8828. // If the user wants their font config to merged with the the theme's font config,
  8829. // instead of taking over it, individual font properties should be used
  8830. // by the user as well.
  8831. if (changes && changes.font) {
  8832. obj = {};
  8833. for (key in changes) {
  8834. if (!(key in this.fontProperties)) {
  8835. obj[key] = changes[key];
  8836. }
  8837. }
  8838. changes = obj;
  8839. }
  8840. this.callParent([
  8841. changes,
  8842. bypassNormalization,
  8843. avoidCopy
  8844. ]);
  8845. },
  8846. // Overriding the getBBox method of the abstract sprite here to always
  8847. // recalculate the bounding box of the text in flipped RTL mode
  8848. // because in that case the position of the sprite depends not just on
  8849. // the value of its 'x' attribute, but also on the width of the surface
  8850. // the sprite belongs to.
  8851. getBBox: function(isWithoutTransform) {
  8852. var me = this,
  8853. plain = me.attr.bbox.plain,
  8854. surface = me.getSurface();
  8855. //<debug>
  8856. // The sprite's bounding box won't account for RTL if it doesn't
  8857. // belong to a surface.
  8858. //if (!surface) {
  8859. // Ext.raise("The sprite does not belong to a surface.");
  8860. //}
  8861. //</debug>
  8862. if (plain.dirty) {
  8863. me.updatePlainBBox(plain);
  8864. plain.dirty = false;
  8865. }
  8866. if (surface && surface.getInherited().rtl && surface.getFlipRtlText()) {
  8867. // Since sprite's attributes haven't actually changed at this point,
  8868. // and we just want to update the position of its bbox
  8869. // based on surface's width, there's no reason to perform
  8870. // expensive text measurement operation here,
  8871. // so we can use the result of the last measurement instead.
  8872. me.updatePlainBBox(plain, true);
  8873. }
  8874. return me.callParent([
  8875. isWithoutTransform
  8876. ]);
  8877. },
  8878. rtlAlignments: {
  8879. start: 'end',
  8880. center: 'center',
  8881. end: 'start'
  8882. },
  8883. updatePlainBBox: function(plain, useOldSize) {
  8884. var me = this,
  8885. attr = me.attr,
  8886. x = attr.x,
  8887. y = attr.y,
  8888. dx = [],
  8889. font = attr.font,
  8890. text = attr.text,
  8891. baseline = attr.textBaseline,
  8892. alignment = attr.textAlign,
  8893. precise = me.getPreciseMeasurement(),
  8894. size, textMeasurerPrecision;
  8895. if (useOldSize && me.oldSize) {
  8896. size = me.oldSize;
  8897. } else {
  8898. textMeasurerPrecision = Ext.draw.TextMeasurer.precise;
  8899. if (Ext.isBoolean(precise)) {
  8900. Ext.draw.TextMeasurer.precise = precise;
  8901. }
  8902. size = me.oldSize = Ext.draw.TextMeasurer.measureText(text, font);
  8903. Ext.draw.TextMeasurer.precise = textMeasurerPrecision;
  8904. }
  8905. var surface = me.getSurface(),
  8906. isRtl = (surface && surface.getInherited().rtl) || false,
  8907. flipRtlText = isRtl && surface.getFlipRtlText(),
  8908. sizes = size.sizes,
  8909. blockHeight = size.height,
  8910. blockWidth = size.width,
  8911. ln = sizes ? sizes.length : 0,
  8912. lineWidth, rect,
  8913. i = 0;
  8914. // To get consistent results in all browsers we don't apply textAlign
  8915. // and textBaseline attributes of the sprite to context, so text is always
  8916. // left aligned and has an alphabetic baseline.
  8917. //
  8918. // Instead we have to calculate the horizontal offset of each line
  8919. // based on sprite's textAlign, and the vertical offset of the bounding box
  8920. // based on sprite's textBaseline.
  8921. //
  8922. // These offsets are then used by the sprite's 'render' method
  8923. // to position text properly.
  8924. switch (baseline) {
  8925. case 'hanging':
  8926. case 'top':
  8927. break;
  8928. case 'ideographic':
  8929. case 'bottom':
  8930. y -= blockHeight;
  8931. break;
  8932. case 'alphabetic':
  8933. y -= blockHeight * 0.8;
  8934. break;
  8935. case 'middle':
  8936. y -= blockHeight * 0.5;
  8937. break;
  8938. }
  8939. if (flipRtlText) {
  8940. rect = surface.getRect();
  8941. x = rect[2] - rect[0] - x;
  8942. alignment = me.rtlAlignments[alignment];
  8943. }
  8944. switch (alignment) {
  8945. case 'start':
  8946. if (isRtl) {
  8947. for (; i < ln; i++) {
  8948. lineWidth = sizes[i].width;
  8949. dx.push(-(blockWidth - lineWidth));
  8950. }
  8951. };
  8952. break;
  8953. case 'end':
  8954. x -= blockWidth;
  8955. if (isRtl) {
  8956. break;
  8957. };
  8958. for (; i < ln; i++) {
  8959. lineWidth = sizes[i].width;
  8960. dx.push(blockWidth - lineWidth);
  8961. };
  8962. break;
  8963. case 'center':
  8964. x -= blockWidth * 0.5;
  8965. for (; i < ln; i++) {
  8966. lineWidth = sizes[i].width;
  8967. dx.push((isRtl ? -1 : 1) * (blockWidth - lineWidth) * 0.5);
  8968. };
  8969. break;
  8970. }
  8971. attr.textAlignOffsets = dx;
  8972. plain.x = x;
  8973. plain.y = y;
  8974. plain.width = blockWidth;
  8975. plain.height = blockHeight;
  8976. },
  8977. setText: function(text) {
  8978. this.setAttributes({
  8979. text: text
  8980. }, true);
  8981. },
  8982. render: function(surface, ctx, rect) {
  8983. var me = this,
  8984. attr = me.attr,
  8985. mat = Ext.draw.Matrix.fly(attr.matrix.elements.slice(0)),
  8986. bbox = me.getBBox(true),
  8987. dx = attr.textAlignOffsets,
  8988. none = Ext.util.Color.RGBA_NONE,
  8989. x, y, i, lines, lineHeight;
  8990. if (attr.text.length === 0) {
  8991. return;
  8992. }
  8993. lines = attr.text.split(me.lineBreakRe);
  8994. lineHeight = bbox.height / lines.length;
  8995. // Simulate textBaseline and textAlign.
  8996. x = attr.bbox.plain.x;
  8997. // lineHeight * 0.78 is the approximate distance between the top and the alphabetic baselines
  8998. y = attr.bbox.plain.y + lineHeight * 0.78;
  8999. mat.toContext(ctx);
  9000. if (surface.getInherited().rtl) {
  9001. // Canvas element in RTL mode automatically flips text alignment.
  9002. // Here we compensate for that change.
  9003. // So text is still positioned and aligned as in the LTR mode,
  9004. // but the direction of the text is RTL.
  9005. x += attr.bbox.plain.width;
  9006. }
  9007. for (i = 0; i < lines.length; i++) {
  9008. if (ctx.fillStyle !== none) {
  9009. ctx.fillText(lines[i], x + (dx[i] || 0), y + lineHeight * i);
  9010. }
  9011. if (ctx.strokeStyle !== none) {
  9012. ctx.strokeText(lines[i], x + (dx[i] || 0), y + lineHeight * i);
  9013. }
  9014. }
  9015. //<debug>
  9016. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  9017. if (debug) {
  9018. // This assumes no part of the sprite is rendered after this call.
  9019. // If it is, we need to re-apply transformations.
  9020. // But the bounding box is already transformed, so we remove the transformation.
  9021. this.attr.inverseMatrix.toContext(ctx);
  9022. debug.bbox && me.renderBBox(surface, ctx);
  9023. }
  9024. }
  9025. };
  9026. });
  9027. //</debug>
  9028. /**
  9029. * A veritical line sprite. The x and y configs set the center of the line with the size
  9030. * value determining the height of the line (the line will be twice the height of 'size'
  9031. * since 'size' is added to above and below 'y' to set the line endpoints).
  9032. *
  9033. * @example
  9034. * Ext.create({
  9035. * xtype: 'draw',
  9036. * renderTo: document.body,
  9037. * width: 600,
  9038. * height: 400,
  9039. * sprites: [{
  9040. * type: 'tick',
  9041. * x: 20,
  9042. * y: 40,
  9043. * size: 10,
  9044. * strokeStyle: '#388FAD',
  9045. * lineWidth: 2
  9046. * }]
  9047. * });
  9048. */
  9049. Ext.define('Ext.draw.sprite.Tick', {
  9050. extend: 'Ext.draw.sprite.Line',
  9051. alias: 'sprite.tick',
  9052. inheritableStatics: {
  9053. def: {
  9054. processors: {
  9055. /**
  9056. * @cfg {Object} x The position of the center of the sprite on the x-axis.
  9057. */
  9058. x: 'number',
  9059. /**
  9060. * @cfg {Object} y The position of the center of the sprite on the y-axis.
  9061. */
  9062. y: 'number',
  9063. /**
  9064. * @cfg {Number} [size=4] The size of the sprite.
  9065. * Meant to be comparable to the size of a circle sprite with the same radius.
  9066. */
  9067. size: 'number'
  9068. },
  9069. defaults: {
  9070. x: 0,
  9071. y: 0,
  9072. size: 4
  9073. },
  9074. triggers: {
  9075. x: 'tick',
  9076. y: 'tick',
  9077. size: 'tick'
  9078. },
  9079. updaters: {
  9080. tick: function(attr) {
  9081. var size = attr.size * 1.5,
  9082. halfLineWidth = attr.lineWidth / 2,
  9083. x = attr.x,
  9084. y = attr.y;
  9085. this.setAttributes({
  9086. fromX: x - halfLineWidth,
  9087. fromY: y - size,
  9088. toX: x - halfLineWidth,
  9089. toY: y + size
  9090. });
  9091. }
  9092. }
  9093. }
  9094. }
  9095. });
  9096. /**
  9097. * A sprite that represents a triangle.
  9098. *
  9099. * @example
  9100. * Ext.create({
  9101. * xtype: 'draw',
  9102. * renderTo: document.body,
  9103. * width: 600,
  9104. * height: 400,
  9105. * sprites: [{
  9106. * type: 'triangle',
  9107. * size: 50,
  9108. * translationX: 100,
  9109. * translationY: 100,
  9110. * fillStyle: '#1F6D91'
  9111. * }]
  9112. * });
  9113. *
  9114. */
  9115. Ext.define('Ext.draw.sprite.Triangle', {
  9116. extend: 'Ext.draw.sprite.Path',
  9117. alias: 'sprite.triangle',
  9118. inheritableStatics: {
  9119. def: {
  9120. processors: {
  9121. x: 'number',
  9122. y: 'number',
  9123. /**
  9124. * @cfg {Number} [size=4] The size of the sprite.
  9125. * Meant to be comparable to the size of a circle sprite with the same radius.
  9126. */
  9127. size: 'number'
  9128. },
  9129. defaults: {
  9130. x: 0,
  9131. y: 0,
  9132. size: 4
  9133. },
  9134. triggers: {
  9135. x: 'path',
  9136. y: 'path',
  9137. size: 'path'
  9138. }
  9139. }
  9140. },
  9141. updatePath: function(path, attr) {
  9142. var s = attr.size * 2.2,
  9143. x = attr.x,
  9144. y = attr.y;
  9145. path.fromSvgString('M'.concat(x, ',', y, 'm0-', s * 0.48, 'l', s * 0.5, ',', s * 0.87, '-', s, ',0z'));
  9146. }
  9147. });
  9148. /**
  9149. * Linear gradient.
  9150. *
  9151. * @example
  9152. * Ext.create({
  9153. * xtype: 'draw',
  9154. * renderTo: document.body,
  9155. * width: 600,
  9156. * height: 400,
  9157. * sprites: [{
  9158. * type: 'circle',
  9159. * cx: 100,
  9160. * cy: 100,
  9161. * r: 100,
  9162. * fillStyle: {
  9163. * type: 'linear',
  9164. * degrees: 180,
  9165. * stops: [{
  9166. * offset: 0,
  9167. * color: '#1F6D91'
  9168. * }, {
  9169. * offset: 1,
  9170. * color: '#90BCC9'
  9171. * }]
  9172. * }
  9173. * }]
  9174. * });
  9175. */
  9176. Ext.define('Ext.draw.gradient.Linear', {
  9177. extend: 'Ext.draw.gradient.Gradient',
  9178. requires: [
  9179. 'Ext.draw.Color'
  9180. ],
  9181. type: 'linear',
  9182. config: {
  9183. /**
  9184. * @cfg {Number} degrees
  9185. * The angle of rotation of the gradient in degrees.
  9186. */
  9187. degrees: 0,
  9188. /**
  9189. * @cfg {Number} radians
  9190. * The angle of rotation of the gradient in radians.
  9191. */
  9192. radians: 0
  9193. },
  9194. applyRadians: function(radians, oldRadians) {
  9195. if (Ext.isNumber(radians)) {
  9196. return radians;
  9197. }
  9198. return oldRadians;
  9199. },
  9200. applyDegrees: function(degrees, oldDegrees) {
  9201. if (Ext.isNumber(degrees)) {
  9202. return degrees;
  9203. }
  9204. return oldDegrees;
  9205. },
  9206. updateRadians: function(radians) {
  9207. this.setDegrees(Ext.draw.Draw.degrees(radians));
  9208. },
  9209. updateDegrees: function(degrees) {
  9210. this.setRadians(Ext.draw.Draw.rad(degrees));
  9211. },
  9212. /**
  9213. * @method generateGradient
  9214. * @inheritdoc
  9215. */
  9216. generateGradient: function(ctx, bbox) {
  9217. var angle = this.getRadians(),
  9218. cos = Math.cos(angle),
  9219. sin = Math.sin(angle),
  9220. w = bbox.width,
  9221. h = bbox.height,
  9222. cx = bbox.x + w * 0.5,
  9223. cy = bbox.y + h * 0.5,
  9224. stops = this.getStops(),
  9225. ln = stops.length,
  9226. gradient, l, i;
  9227. if (Ext.isNumber(cx) && Ext.isNumber(cy) && h > 0 && w > 0) {
  9228. l = (Math.sqrt(h * h + w * w) * Math.abs(Math.cos(angle - Math.atan(h / w)))) / 2;
  9229. gradient = ctx.createLinearGradient(cx + cos * l, cy + sin * l, cx - cos * l, cy - sin * l);
  9230. for (i = 0; i < ln; i++) {
  9231. gradient.addColorStop(stops[i].offset, stops[i].color);
  9232. }
  9233. return gradient;
  9234. }
  9235. return Ext.util.Color.NONE;
  9236. }
  9237. });
  9238. /**
  9239. * Radial gradient.
  9240. *
  9241. * @example
  9242. * Ext.create({
  9243. * xtype: 'draw',
  9244. * renderTo: document.body,
  9245. * width: 600,
  9246. * height: 400,
  9247. * sprites: [{
  9248. * type: 'circle',
  9249. * cx: 100,
  9250. * cy: 100,
  9251. * r: 100,
  9252. * fillStyle: {
  9253. * type: 'radial',
  9254. * start: {
  9255. * x: 0,
  9256. * y: 0,
  9257. * r: 0
  9258. * },
  9259. * end: {
  9260. * x: 0,
  9261. * y: 0,
  9262. * r: 1
  9263. * },
  9264. * stops: [{
  9265. * offset: 0,
  9266. * color: '#90BCC9'
  9267. * }, {
  9268. * offset: 1,
  9269. * color: '#1F6D91'
  9270. * }]
  9271. * }
  9272. * }]
  9273. * });
  9274. */
  9275. Ext.define('Ext.draw.gradient.Radial', {
  9276. extend: 'Ext.draw.gradient.Gradient',
  9277. type: 'radial',
  9278. config: {
  9279. /**
  9280. * @cfg {Object} start
  9281. * The starting circle of the gradient.
  9282. */
  9283. start: {
  9284. x: 0,
  9285. y: 0,
  9286. r: 0
  9287. },
  9288. /**
  9289. * @cfg {Object} end
  9290. * The ending circle of the gradient.
  9291. */
  9292. end: {
  9293. x: 0,
  9294. y: 0,
  9295. r: 1
  9296. }
  9297. },
  9298. applyStart: function(newStart, oldStart) {
  9299. if (!oldStart) {
  9300. return newStart;
  9301. }
  9302. var circle = {
  9303. x: oldStart.x,
  9304. y: oldStart.y,
  9305. r: oldStart.r
  9306. };
  9307. if ('x' in newStart) {
  9308. circle.x = newStart.x;
  9309. } else if ('centerX' in newStart) {
  9310. circle.x = newStart.centerX;
  9311. }
  9312. if ('y' in newStart) {
  9313. circle.y = newStart.y;
  9314. } else if ('centerY' in newStart) {
  9315. circle.y = newStart.centerY;
  9316. }
  9317. if ('r' in newStart) {
  9318. circle.r = newStart.r;
  9319. } else if ('radius' in newStart) {
  9320. circle.r = newStart.radius;
  9321. }
  9322. return circle;
  9323. },
  9324. applyEnd: function(newEnd, oldEnd) {
  9325. if (!oldEnd) {
  9326. return newEnd;
  9327. }
  9328. var circle = {
  9329. x: oldEnd.x,
  9330. y: oldEnd.y,
  9331. r: oldEnd.r
  9332. };
  9333. if ('x' in newEnd) {
  9334. circle.x = newEnd.x;
  9335. } else if ('centerX' in newEnd) {
  9336. circle.x = newEnd.centerX;
  9337. }
  9338. if ('y' in newEnd) {
  9339. circle.y = newEnd.y;
  9340. } else if ('centerY' in newEnd) {
  9341. circle.y = newEnd.centerY;
  9342. }
  9343. if ('r' in newEnd) {
  9344. circle.r = newEnd.r;
  9345. } else if ('radius' in newEnd) {
  9346. circle.r = newEnd.radius;
  9347. }
  9348. return circle;
  9349. },
  9350. /**
  9351. * @method generateGradient
  9352. * @inheritdoc
  9353. */
  9354. generateGradient: function(ctx, bbox) {
  9355. var start = this.getStart(),
  9356. end = this.getEnd(),
  9357. w = bbox.width * 0.5,
  9358. h = bbox.height * 0.5,
  9359. x = bbox.x + w,
  9360. y = bbox.y + h,
  9361. gradient = ctx.createRadialGradient(x + start.x * w, y + start.y * h, start.r * Math.max(w, h), x + end.x * w, y + end.y * h, end.r * Math.max(w, h)),
  9362. stops = this.getStops(),
  9363. ln = stops.length,
  9364. i;
  9365. for (i = 0; i < ln; i++) {
  9366. gradient.addColorStop(stops[i].offset, stops[i].color);
  9367. }
  9368. return gradient;
  9369. }
  9370. });
  9371. /**
  9372. * A surface is an interface to render {@link Ext.draw.sprite.Sprite sprites} inside a
  9373. * {@link Ext.draw.Container draw container}. The surface API has methods to render
  9374. * sprites, get sprite bounding boxes (dimensions), add sprites to the underlying DOM,
  9375. * and more.
  9376. *
  9377. * A surface is automatically created when a draw container is created. By default,
  9378. * this will be a surface with an `id` of "main" and will manage all sprites in the draw
  9379. * container (unless the sprite configs specify a unique surface "id").
  9380. *
  9381. * @example
  9382. * Ext.create({
  9383. * xtype: 'draw',
  9384. * renderTo: document.body,
  9385. * width: 400,
  9386. * height: 400,
  9387. * sprites: [{
  9388. * type: 'rect',
  9389. * surface: 'anim', // a surface with id "anim" will be created automatically
  9390. * x: 50,
  9391. * y: 50,
  9392. * width: 100,
  9393. * height: 100,
  9394. * fillStyle: '#1F6D91'
  9395. * }]
  9396. * });
  9397. *
  9398. * The ability to have multiple surfaces is useful for performance (and battery life)
  9399. * reasons. Because changes to sprite attributes cause the whole surface (and all
  9400. * sprites in it) to re-render, it makes sense to group sprites by surface, so changes
  9401. * to one group of sprites will only trigger the surface they are in to re-render.
  9402. *
  9403. * One of the more useful methods is the {@link #add} method used to add sprites to the
  9404. * surface:
  9405. *
  9406. * @example
  9407. * var drawCt = Ext.create({
  9408. * xtype: 'draw',
  9409. * renderTo: document.body,
  9410. * width: 400,
  9411. * height: 400
  9412. * });
  9413. *
  9414. * // If the surface name is not specified then 'main' will be used
  9415. * var surface = drawCt.getSurface();
  9416. *
  9417. * surface.add({
  9418. * type: 'rect',
  9419. * x: 50,
  9420. * y: 50,
  9421. * width: 100,
  9422. * height: 100,
  9423. * fillStyle: '#1F6D91'
  9424. * });
  9425. *
  9426. * surface.renderFrame();
  9427. *
  9428. * **Note:** Changes to the sprites on a surface will be not be reflected in the DOM
  9429. * until you call the surface's {@link Ext.draw.Surface#method-renderFrame renderFrame}
  9430. * method. This must be done after adding, removing, or modifying sprites in order to
  9431. * see the changes on-screen.
  9432. */
  9433. Ext.define('Ext.draw.Surface', {
  9434. extend: 'Ext.draw.SurfaceBase',
  9435. xtype: 'surface',
  9436. requires: [
  9437. 'Ext.draw.sprite.*',
  9438. 'Ext.draw.gradient.*',
  9439. 'Ext.draw.sprite.AttributeDefinition',
  9440. 'Ext.draw.Matrix',
  9441. 'Ext.draw.Draw'
  9442. ],
  9443. uses: [
  9444. 'Ext.draw.engine.Canvas'
  9445. ],
  9446. /**
  9447. * The reported device pixel density.
  9448. * devicePixelRatio is only supported from IE11,
  9449. * so we use deviceXDPI and logicalXDPI that are supported from IE6.
  9450. */
  9451. devicePixelRatio: window.devicePixelRatio || window.screen.deviceXDPI / window.screen.logicalXDPI,
  9452. deprecated: {
  9453. '5.1.0': {
  9454. statics: {
  9455. methods: {
  9456. /**
  9457. * @deprecated 5.1.0
  9458. * Stably sort the list of sprites by their zIndex.
  9459. * Deprecated, use the {@link Ext.Array#sort} method instead.
  9460. * @param {Array} list
  9461. * @return {Array} Sorted array.
  9462. */
  9463. stableSort: function(list) {
  9464. return Ext.Array.sort(list, function(a, b) {
  9465. return a.attr.zIndex - b.attr.zIndex;
  9466. });
  9467. }
  9468. }
  9469. }
  9470. }
  9471. },
  9472. cls: Ext.baseCSSPrefix + 'surface',
  9473. config: {
  9474. /**
  9475. * @cfg {Array}
  9476. * The [x, y, width, height] rect of the surface related to its container.
  9477. */
  9478. rect: null,
  9479. /**
  9480. * @cfg {Object}
  9481. * Background sprite config of the surface.
  9482. */
  9483. background: null,
  9484. /**
  9485. * @cfg {Array}
  9486. * Array of sprite instances.
  9487. */
  9488. items: [],
  9489. /**
  9490. * @cfg {Boolean}
  9491. * Indicates whether the surface needs to redraw.
  9492. */
  9493. dirty: false,
  9494. /**
  9495. * @cfg {Boolean} flipRtlText
  9496. * If the surface is in the RTL mode, text will render with the RTL direction,
  9497. * but the alignment and position of the text won't change by default.
  9498. * Setting this config to 'true' will get text alignment and its position
  9499. * within a surface mirrored.
  9500. */
  9501. flipRtlText: false
  9502. },
  9503. isSurface: true,
  9504. /**
  9505. * @private
  9506. * This flag is used to indicate that `predecessors` surfaces that should render
  9507. * before this surface renders are dirty, and to call `renderFrame`
  9508. * when all `predecessors` have their `renderFrame` called (i.e. not dirty anymore).
  9509. * This flag indicates that current surface has surfaces that are yet to render
  9510. * before current surface can render. When all the `predecessors` surfaces
  9511. * have rendered, i.e. when `dirtyPredecessorCount` reaches zero,
  9512. */
  9513. isPendingRenderFrame: false,
  9514. dirtyPredecessorCount: 0,
  9515. emptyRect: [
  9516. 0,
  9517. 0,
  9518. 0,
  9519. 0
  9520. ],
  9521. constructor: function(config) {
  9522. var me = this;
  9523. me.predecessors = [];
  9524. me.successors = [];
  9525. me.map = {};
  9526. me.callParent([
  9527. config
  9528. ]);
  9529. me.matrix = new Ext.draw.Matrix();
  9530. me.inverseMatrix = me.matrix.inverse();
  9531. },
  9532. /**
  9533. * Round the number to align to the pixels on device.
  9534. * @param {Number} num The number to align.
  9535. * @return {Number} The resultant alignment.
  9536. */
  9537. roundPixel: function(num) {
  9538. return Math.round(this.devicePixelRatio * num) / this.devicePixelRatio;
  9539. },
  9540. /**
  9541. * Mark the surface to render after another surface is updated.
  9542. * @param {Ext.draw.Surface} surface The surface to wait for.
  9543. */
  9544. waitFor: function(surface) {
  9545. var me = this,
  9546. predecessors = me.predecessors;
  9547. if (!Ext.Array.contains(predecessors, surface)) {
  9548. predecessors.push(surface);
  9549. surface.successors.push(me);
  9550. if (surface.getDirty()) {
  9551. me.dirtyPredecessorCount++;
  9552. }
  9553. }
  9554. },
  9555. updateDirty: function(dirty) {
  9556. var successors = this.successors,
  9557. ln = successors.length,
  9558. i = 0,
  9559. successor;
  9560. for (; i < ln; i++) {
  9561. successor = successors[i];
  9562. if (dirty) {
  9563. successor.dirtyPredecessorCount++;
  9564. successor.setDirty(true);
  9565. } else {
  9566. successor.dirtyPredecessorCount--;
  9567. // Don't need to call `setDirty(false)` on a successor here,
  9568. // as this will be done by `renderFrame`.
  9569. if (successor.dirtyPredecessorCount === 0 && successor.isPendingRenderFrame) {
  9570. successor.renderFrame();
  9571. }
  9572. }
  9573. }
  9574. },
  9575. applyBackground: function(background, oldBackground) {
  9576. this.setDirty(true);
  9577. if (Ext.isString(background)) {
  9578. background = {
  9579. fillStyle: background
  9580. };
  9581. }
  9582. return Ext.factory(background, Ext.draw.sprite.Rect, oldBackground);
  9583. },
  9584. applyRect: function(rect, oldRect) {
  9585. if (oldRect && rect[0] === oldRect[0] && rect[1] === oldRect[1] && rect[2] === oldRect[2] && rect[3] === oldRect[3]) {
  9586. return oldRect;
  9587. }
  9588. if (Ext.isArray(rect)) {
  9589. return [
  9590. rect[0],
  9591. rect[1],
  9592. rect[2],
  9593. rect[3]
  9594. ];
  9595. } else if (Ext.isObject(rect)) {
  9596. return [
  9597. rect.x || rect.left,
  9598. rect.y || rect.top,
  9599. rect.width || (rect.right - rect.left),
  9600. rect.height || (rect.bottom - rect.top)
  9601. ];
  9602. }
  9603. },
  9604. updateRect: function(rect) {
  9605. var me = this,
  9606. l = rect[0],
  9607. t = rect[1],
  9608. r = l + rect[2],
  9609. b = t + rect[3],
  9610. background = me.getBackground(),
  9611. element = me.element;
  9612. element.setLocalXY(Math.floor(l), Math.floor(t));
  9613. element.setSize(Math.ceil(r - Math.floor(l)), Math.ceil(b - Math.floor(t)));
  9614. if (background) {
  9615. background.setAttributes({
  9616. x: 0,
  9617. y: 0,
  9618. width: Math.ceil(r - Math.floor(l)),
  9619. height: Math.ceil(b - Math.floor(t))
  9620. });
  9621. }
  9622. me.setDirty(true);
  9623. },
  9624. /**
  9625. * Reset the matrix of the surface.
  9626. */
  9627. resetTransform: function() {
  9628. this.matrix.set(1, 0, 0, 1, 0, 0);
  9629. this.inverseMatrix.set(1, 0, 0, 1, 0, 0);
  9630. this.setDirty(true);
  9631. },
  9632. /**
  9633. * Get the sprite by id or index.
  9634. * It will first try to find a sprite with the given id, otherwise will try to use the id as an index.
  9635. * @param {String|Number} id
  9636. * @return {Ext.draw.sprite.Sprite}
  9637. */
  9638. get: function(id) {
  9639. return this.map[id] || this.getItems()[id];
  9640. },
  9641. /**
  9642. * @method
  9643. * Add a Sprite to the surface.
  9644. * You can put any number of objects as the parameter.
  9645. * See {@link Ext.draw.sprite.Sprite} for the configuration object to be passed into this method.
  9646. *
  9647. * For example:
  9648. *
  9649. * drawContainer.getSurface().add({
  9650. * type: 'circle',
  9651. * fill: '#ffc',
  9652. * radius: 100,
  9653. * x: 100,
  9654. * y: 100
  9655. * });
  9656. * drawContainer.renderFrame();
  9657. *
  9658. * @param {Object/Object[]} sprite
  9659. * @return {Ext.draw.sprite.Sprite/Ext.draw.sprite.Sprite[]}
  9660. *
  9661. */
  9662. add: function() {
  9663. var me = this,
  9664. args = Array.prototype.slice.call(arguments),
  9665. argIsArray = Ext.isArray(args[0]),
  9666. map = me.map,
  9667. results = [],
  9668. items, item, sprite, oldSurface, i, ln;
  9669. items = Ext.Array.clean(argIsArray ? args[0] : args);
  9670. if (!items.length) {
  9671. return results;
  9672. }
  9673. for (i = 0 , ln = items.length; i < ln; i++) {
  9674. item = items[i];
  9675. if (!item || item.destroyed) {
  9676. continue;
  9677. }
  9678. sprite = null;
  9679. if (item.isSprite && !map[item.getId()]) {
  9680. sprite = item;
  9681. } else if (!map[item.id]) {
  9682. sprite = this.createItem(item);
  9683. }
  9684. if (sprite) {
  9685. map[sprite.getId()] = sprite;
  9686. results.push(sprite);
  9687. oldSurface = sprite.getSurface();
  9688. if (oldSurface && oldSurface.isSurface) {
  9689. oldSurface.remove(sprite);
  9690. }
  9691. sprite.setParent(me);
  9692. sprite.setSurface(me);
  9693. me.onAdd(sprite);
  9694. }
  9695. }
  9696. items = me.getItems();
  9697. if (items) {
  9698. items.push.apply(items, results);
  9699. }
  9700. me.dirtyZIndex = true;
  9701. me.setDirty(true);
  9702. if (!argIsArray && results.length === 1) {
  9703. return results[0];
  9704. } else {
  9705. return results;
  9706. }
  9707. },
  9708. /**
  9709. * @method
  9710. * @protected
  9711. * Invoked when a sprite is added to the surface.
  9712. * @param {Ext.draw.sprite.Sprite} sprite The sprite to be added.
  9713. */
  9714. onAdd: Ext.emptyFn,
  9715. /**
  9716. * Remove a given sprite from the surface,
  9717. * optionally destroying the sprite in the process.
  9718. * You can also call the sprite's own `remove` method.
  9719. *
  9720. * For example:
  9721. *
  9722. * drawContainer.surface.remove(sprite);
  9723. * // or...
  9724. * sprite.remove();
  9725. *
  9726. * @param {Ext.draw.sprite.Sprite/String} sprite A sprite instance or its ID.
  9727. * @param {Boolean} [isDestroy=false] If `true`, the sprite will be destroyed.
  9728. * @return {Ext.draw.sprite.Sprite} Returns the removed/destroyed sprite or `null` otherwise.
  9729. */
  9730. remove: function(sprite, isDestroy) {
  9731. var me = this,
  9732. destroying = me.clearing,
  9733. id, isOwnSprite;
  9734. if (sprite) {
  9735. if (sprite.charAt) {
  9736. // is String
  9737. sprite = me.map[sprite];
  9738. }
  9739. if (!sprite || !sprite.isSprite) {
  9740. return null;
  9741. }
  9742. id = sprite.id;
  9743. isOwnSprite = me.map[id];
  9744. delete me.map[id];
  9745. if (sprite.destroyed || sprite.destroying) {
  9746. if (isOwnSprite && !destroying) {
  9747. // Somehow this sprite was destroyed,
  9748. // but still belongs to the surface.
  9749. Ext.Array.remove(me.getItems(), sprite);
  9750. }
  9751. return sprite;
  9752. }
  9753. if (!isOwnSprite) {
  9754. if (isDestroy) {
  9755. sprite.destroy();
  9756. }
  9757. return sprite;
  9758. }
  9759. sprite.setParent(null);
  9760. sprite.setSurface(null);
  9761. if (isDestroy) {
  9762. sprite.destroy();
  9763. }
  9764. if (!destroying) {
  9765. Ext.Array.remove(me.getItems(), sprite);
  9766. me.dirtyZIndex = true;
  9767. me.setDirty(true);
  9768. }
  9769. }
  9770. return sprite || null;
  9771. },
  9772. /**
  9773. * Remove all sprites from the surface, optionally destroying the sprites in the process.
  9774. *
  9775. * For example:
  9776. *
  9777. * drawContainer.getSurface('main').removeAll();
  9778. *
  9779. * @param {Boolean} [isDestroy=false]
  9780. */
  9781. removeAll: function(isDestroy) {
  9782. var me = this,
  9783. items = me.getItems(),
  9784. item, ln, i;
  9785. me.clearing = !!isDestroy;
  9786. for (i = items.length - 1; i >= 0; i--) {
  9787. item = items[i];
  9788. if (isDestroy) {
  9789. // Some sprites may destroy other sprites, however if we're destroying then
  9790. // we don't remove anything from the items array since we'll just clear it later.
  9791. // If a sprite is destroyed, the remove method will just drop out with no harm done.
  9792. item.destroy();
  9793. } else {
  9794. item.setParent(null);
  9795. item.setSurface(null);
  9796. }
  9797. }
  9798. me.clearing = false;
  9799. items.length = 0;
  9800. me.map = {};
  9801. me.dirtyZIndex = true;
  9802. if (!me.destroying) {
  9803. me.setDirty(true);
  9804. }
  9805. },
  9806. /**
  9807. * @private
  9808. */
  9809. applyItems: function(items) {
  9810. if (this.getItems()) {
  9811. this.removeAll(true);
  9812. }
  9813. return Ext.Array.from(this.add(items));
  9814. },
  9815. /**
  9816. * @private
  9817. * Creates an item and appends it to the surface. Called
  9818. * as an internal method when calling `add`.
  9819. */
  9820. createItem: function(config) {
  9821. return Ext.create(config.xclass || 'sprite.' + config.type, config);
  9822. },
  9823. /**
  9824. * Return the minimal bounding box that contains all the sprites bounding boxes in the given list of sprites.
  9825. * @param {Ext.draw.sprite.Sprite[]|Ext.draw.sprite.Sprite} sprites
  9826. * @param {Boolean} [isWithoutTransform=false]
  9827. * @return {{x: Number, y: Number, width: number, height: number}}
  9828. */
  9829. getBBox: function(sprites, isWithoutTransform) {
  9830. sprites = Ext.Array.from(sprites);
  9831. var left = Infinity,
  9832. right = -Infinity,
  9833. top = Infinity,
  9834. bottom = -Infinity,
  9835. ln = sprites.length,
  9836. sprite, bbox, i;
  9837. for (i = 0; i < ln; i++) {
  9838. sprite = sprites[i];
  9839. bbox = sprite.getBBox(isWithoutTransform);
  9840. if (left > bbox.x) {
  9841. left = bbox.x;
  9842. }
  9843. if (right < bbox.x + bbox.width) {
  9844. right = bbox.x + bbox.width;
  9845. }
  9846. if (top > bbox.y) {
  9847. top = bbox.y;
  9848. }
  9849. if (bottom < bbox.y + bbox.height) {
  9850. bottom = bbox.y + bbox.height;
  9851. }
  9852. }
  9853. return {
  9854. x: left,
  9855. y: top,
  9856. width: right - left,
  9857. height: bottom - top
  9858. };
  9859. },
  9860. /**
  9861. * @private
  9862. * @method getOwnerBody
  9863. * The body element of the chart or the draw container
  9864. * (doesn't include docked items like a legend).
  9865. * Draw Container is a Panel in Classic (to allow for docked items)
  9866. * and a Container in Modern, so the body is retrieved differently.
  9867. * @return {Ext.dom.Element}
  9868. */
  9869. /**
  9870. * @private
  9871. * Converts event's page coordinates into surface coordinates.
  9872. * Note: surface's x-coordinates always go LTR, regardless of RTL mode.
  9873. */
  9874. getEventXY: function(e) {
  9875. var me = this,
  9876. isRtl = me.getInherited().rtl,
  9877. pageXY = e.getXY(),
  9878. // Event position in page coordinates.
  9879. container = me.getOwnerBody(),
  9880. // The body of the chart (doesn't include docked items like legend).
  9881. xy = container.getXY(),
  9882. // Surface container position in page coordinates.
  9883. rect = me.getRect() || me.emptyRect,
  9884. // Surface position in surface container coordinates (LTR).
  9885. result = [],
  9886. width;
  9887. if (isRtl) {
  9888. width = container.getWidth();
  9889. // The line below is actually a simplified form of
  9890. // rect[2] - (pageXY[0] - xy[0] - (width - (rect[0] + rect[2]))).
  9891. result[0] = xy[0] - pageXY[0] - rect[0] + width;
  9892. } else {
  9893. result[0] = pageXY[0] - xy[0] - rect[0];
  9894. }
  9895. result[1] = pageXY[1] - xy[1] - rect[1];
  9896. return result;
  9897. },
  9898. /**
  9899. * @method
  9900. * Empty the surface content (without touching the sprites.)
  9901. */
  9902. clear: Ext.emptyFn,
  9903. /**
  9904. * @private
  9905. * Order the items by their z-index if any of that has been changed since last sort.
  9906. */
  9907. orderByZIndex: function() {
  9908. var me = this,
  9909. items = me.getItems(),
  9910. dirtyZIndex = false,
  9911. i, ln;
  9912. if (me.getDirty()) {
  9913. for (i = 0 , ln = items.length; i < ln; i++) {
  9914. if (items[i].attr.dirtyZIndex) {
  9915. dirtyZIndex = true;
  9916. break;
  9917. }
  9918. }
  9919. if (dirtyZIndex) {
  9920. // sort by zIndex
  9921. Ext.Array.sort(items, function(a, b) {
  9922. return a.attr.zIndex - b.attr.zIndex;
  9923. });
  9924. this.setDirty(true);
  9925. }
  9926. for (i = 0 , ln = items.length; i < ln; i++) {
  9927. items[i].attr.dirtyZIndex = false;
  9928. }
  9929. }
  9930. },
  9931. /**
  9932. * Force the element to redraw.
  9933. */
  9934. repaint: function() {
  9935. var me = this;
  9936. me.repaint = Ext.emptyFn;
  9937. Ext.defer(function() {
  9938. delete me.repaint;
  9939. me.element.repaint();
  9940. }, 1);
  9941. },
  9942. /**
  9943. * Triggers the re-rendering of the canvas.
  9944. */
  9945. renderFrame: function() {
  9946. var me = this;
  9947. if (!(me.element && me.getDirty() && me.getRect())) {
  9948. return;
  9949. }
  9950. if (me.dirtyPredecessorCount > 0) {
  9951. me.isPendingRenderFrame = true;
  9952. return;
  9953. }
  9954. var background = me.getBackground(),
  9955. items = me.getItems(),
  9956. item, i, ln;
  9957. // This will also check the dirty flags of the sprites.
  9958. me.orderByZIndex();
  9959. if (me.getDirty()) {
  9960. me.clear();
  9961. me.clearTransform();
  9962. if (background) {
  9963. me.renderSprite(background);
  9964. }
  9965. for (i = 0 , ln = items.length; i < ln; i++) {
  9966. item = items[i];
  9967. if (me.renderSprite(item) === false) {
  9968. return;
  9969. }
  9970. item.attr.textPositionCount = me.textPosition;
  9971. }
  9972. me.setDirty(false);
  9973. }
  9974. },
  9975. /**
  9976. * @method
  9977. * @private
  9978. * Renders a single sprite into the surface.
  9979. * Do not call it from outside `renderFrame` method.
  9980. *
  9981. * @param {Ext.draw.sprite.Sprite} sprite The Sprite to be rendered.
  9982. * @return {Boolean} returns `false` to stop the rendering to continue.
  9983. */
  9984. renderSprite: Ext.emptyFn,
  9985. /**
  9986. * @method flatten
  9987. * Flattens the given drawing surfaces into a single image
  9988. * and returns an object containing the data (in the DataURL format)
  9989. * and the type (e.g. 'png' or 'svg') of that image.
  9990. * @param {Object} size The size of the final image.
  9991. * @param {Number} size.width
  9992. * @param {Number} size.height
  9993. * @param {Ext.draw.Surface[]} surfaces The surfaces to flatten.
  9994. * @return {Object}
  9995. * @return {String} return.data The DataURL of the flattened image.
  9996. * @return {String} return.type The type of the image.
  9997. *
  9998. */
  9999. /**
  10000. * @method
  10001. * @private
  10002. * Clears the current transformation state on the surface.
  10003. */
  10004. clearTransform: Ext.emptyFn,
  10005. /**
  10006. * Destroys the surface. This is done by removing all components from it and
  10007. * also removing its reference to a DOM element.
  10008. *
  10009. * For example:
  10010. *
  10011. * drawContainer.surface.destroy();
  10012. */
  10013. destroy: function() {
  10014. var me = this;
  10015. me.destroying = true;
  10016. me.removeAll(true);
  10017. me.destroying = false;
  10018. me.predecessors = me.successors = null;
  10019. if (me.hasListeners.destroy) {
  10020. me.fireEvent('destroy', me);
  10021. }
  10022. me.callParent();
  10023. }
  10024. });
  10025. /**
  10026. * @private
  10027. * Adds hit testing methods to the Ext.draw.Surface.
  10028. * Included by the Ext.draw.plugin.SpriteEvents.
  10029. */
  10030. Ext.define('Ext.draw.overrides.hittest.Surface', {
  10031. override: 'Ext.draw.Surface',
  10032. /**
  10033. * Performs a hit test on all sprites in the surface, returning the first matching one.
  10034. * @param {Array} point A two-item array containing x and y coordinates of the point
  10035. * in surface coordinate system.
  10036. * @param {Object} options Hit testing options.
  10037. * @return {Object} A hit result object that contains more information about what
  10038. * exactly was hit or null if nothing was hit.
  10039. * @member Ext.draw.Surface
  10040. */
  10041. hitTest: function(point, options) {
  10042. var me = this,
  10043. sprites = me.getItems(),
  10044. i, sprite, result;
  10045. options = options || Ext.draw.sprite.Sprite.defaultHitTestOptions;
  10046. for (i = sprites.length - 1; i >= 0; i--) {
  10047. sprite = sprites[i];
  10048. if (sprite.hitTest) {
  10049. result = sprite.hitTest(point, options);
  10050. if (result) {
  10051. return result;
  10052. }
  10053. }
  10054. }
  10055. return null;
  10056. },
  10057. /**
  10058. * Performs a hit test on all sprites in the surface, returning the first matching one.
  10059. * Since hit testing is typically performed on mouse events, this convenience method
  10060. * converts event's page coordinates to surface coordinates before calling {@link #hitTest}.
  10061. * @param {Object} event An event object.
  10062. * @param {Object} options Hit testing options.
  10063. * @return {Object} A hit result object that contains more information about what
  10064. * exactly was hit or null if nothing was hit.
  10065. * @member Ext.draw.Surface
  10066. */
  10067. hitTestEvent: function(event, options) {
  10068. var xy = this.getEventXY(event);
  10069. return this.hitTest(xy, options);
  10070. }
  10071. });
  10072. /**
  10073. * @class Ext.draw.engine.SvgContext
  10074. *
  10075. * A class that imitates a canvas context but generates svg elements instead.
  10076. */
  10077. Ext.define('Ext.draw.engine.SvgContext', {
  10078. requires: [
  10079. 'Ext.draw.Color'
  10080. ],
  10081. /**
  10082. * @private
  10083. * Properties to be saved/restored in the `save` and `restore` methods.
  10084. */
  10085. toSave: [
  10086. 'strokeOpacity',
  10087. 'strokeStyle',
  10088. 'fillOpacity',
  10089. 'fillStyle',
  10090. 'globalAlpha',
  10091. 'lineWidth',
  10092. 'lineCap',
  10093. 'lineJoin',
  10094. 'lineDash',
  10095. 'lineDashOffset',
  10096. 'miterLimit',
  10097. 'shadowOffsetX',
  10098. 'shadowOffsetY',
  10099. 'shadowBlur',
  10100. 'shadowColor',
  10101. 'globalCompositeOperation',
  10102. 'position',
  10103. 'fillGradient',
  10104. 'strokeGradient'
  10105. ],
  10106. strokeOpacity: 1,
  10107. strokeStyle: 'none',
  10108. fillOpacity: 1,
  10109. fillStyle: 'none',
  10110. lineDas: [],
  10111. lineDashOffset: 0,
  10112. globalAlpha: 1,
  10113. lineWidth: 1,
  10114. lineCap: 'butt',
  10115. lineJoin: 'miter',
  10116. miterLimit: 10,
  10117. shadowOffsetX: 0,
  10118. shadowOffsetY: 0,
  10119. shadowBlur: 0,
  10120. shadowColor: 'none',
  10121. globalCompositeOperation: 'src',
  10122. urlStringRe: /^url\(#([\w\-]+)\)$/,
  10123. constructor: function(SvgSurface) {
  10124. var me = this;
  10125. me.surface = SvgSurface;
  10126. // Stack of contexts.
  10127. me.state = [];
  10128. me.matrix = new Ext.draw.Matrix();
  10129. // Currently manipulated path.
  10130. me.path = null;
  10131. me.clear();
  10132. },
  10133. /**
  10134. * Clears the context.
  10135. */
  10136. clear: function() {
  10137. // Current group to put paths into.
  10138. this.group = this.surface.mainGroup;
  10139. // Position within the current group.
  10140. this.position = 0;
  10141. this.path = null;
  10142. },
  10143. /**
  10144. * @private
  10145. * @param {String} tag
  10146. * @return {*}
  10147. */
  10148. getElement: function(tag) {
  10149. return this.surface.getSvgElement(this.group, tag, this.position++);
  10150. },
  10151. /**
  10152. * Pushes the context state to the state stack.
  10153. */
  10154. save: function() {
  10155. var toSave = this.toSave,
  10156. obj = {},
  10157. group = this.getElement('g'),
  10158. key, i;
  10159. for (i = 0; i < toSave.length; i++) {
  10160. key = toSave[i];
  10161. if (key in this) {
  10162. obj[key] = this[key];
  10163. }
  10164. }
  10165. this.position = 0;
  10166. obj.matrix = this.matrix.clone();
  10167. this.state.push(obj);
  10168. this.group = group;
  10169. return group;
  10170. },
  10171. /**
  10172. * Pops the state stack and restores the state.
  10173. */
  10174. restore: function() {
  10175. var toSave = this.toSave,
  10176. obj = this.state.pop(),
  10177. group = this.group,
  10178. children = group.dom.childNodes,
  10179. key, i;
  10180. // Removing extra DOM elements that were not reused.
  10181. while (children.length > this.position) {
  10182. group.last().destroy();
  10183. }
  10184. for (i = 0; i < toSave.length; i++) {
  10185. key = toSave[i];
  10186. if (key in obj) {
  10187. this[key] = obj[key];
  10188. } else {
  10189. delete this[key];
  10190. }
  10191. }
  10192. this.setTransform.apply(this, obj.matrix.elements);
  10193. this.group = group.getParent();
  10194. },
  10195. /**
  10196. * Changes the transformation matrix to apply the matrix given by the arguments as described below.
  10197. * @param {Number} xx
  10198. * @param {Number} yx
  10199. * @param {Number} xy
  10200. * @param {Number} yy
  10201. * @param {Number} dx
  10202. * @param {Number} dy
  10203. */
  10204. transform: function(xx, yx, xy, yy, dx, dy) {
  10205. if (this.path) {
  10206. var inv = Ext.draw.Matrix.fly([
  10207. xx,
  10208. yx,
  10209. xy,
  10210. yy,
  10211. dx,
  10212. dy
  10213. ]).inverse();
  10214. this.path.transform(inv);
  10215. }
  10216. this.matrix.append(xx, yx, xy, yy, dx, dy);
  10217. },
  10218. /**
  10219. * Changes the transformation matrix to the matrix given by the arguments as described below.
  10220. * @param {Number} xx
  10221. * @param {Number} yx
  10222. * @param {Number} xy
  10223. * @param {Number} yy
  10224. * @param {Number} dx
  10225. * @param {Number} dy
  10226. */
  10227. setTransform: function(xx, yx, xy, yy, dx, dy) {
  10228. if (this.path) {
  10229. this.path.transform(this.matrix);
  10230. }
  10231. this.matrix.reset();
  10232. this.transform(xx, yx, xy, yy, dx, dy);
  10233. },
  10234. /**
  10235. * Scales the current context by the specified horizontal (x) and vertical (y) factors.
  10236. * @param {Number} x The horizontal scaling factor, where 1 equals unity or 100% scale.
  10237. * @param {Number} y The vertical scaling factor.
  10238. */
  10239. scale: function(x, y) {
  10240. this.transform(x, 0, 0, y, 0, 0);
  10241. },
  10242. /**
  10243. * Rotates the current context coordinates (that is, a transformation matrix).
  10244. * @param {Number} angle The rotation angle, in radians.
  10245. */
  10246. rotate: function(angle) {
  10247. var xx = Math.cos(angle),
  10248. yx = Math.sin(angle),
  10249. xy = -Math.sin(angle),
  10250. yy = Math.cos(angle);
  10251. this.transform(xx, yx, xy, yy, 0, 0);
  10252. },
  10253. /**
  10254. * Specifies values to move the origin point in a canvas.
  10255. * @param {Number} x The value to add to horizontal (or x) coordinates.
  10256. * @param {Number} y The value to add to vertical (or y) coordinates.
  10257. */
  10258. translate: function(x, y) {
  10259. this.transform(1, 0, 0, 1, x, y);
  10260. },
  10261. setGradientBBox: function(bbox) {
  10262. this.bbox = bbox;
  10263. },
  10264. /**
  10265. * Resets the current default path.
  10266. */
  10267. beginPath: function() {
  10268. this.path = new Ext.draw.Path();
  10269. },
  10270. /**
  10271. * Creates a new subpath with the given point.
  10272. * @param {Number} x
  10273. * @param {Number} y
  10274. */
  10275. moveTo: function(x, y) {
  10276. if (!this.path) {
  10277. this.beginPath();
  10278. }
  10279. this.path.moveTo(x, y);
  10280. this.path.element = null;
  10281. },
  10282. /**
  10283. * Adds the given point to the current subpath, connected to the previous one by a straight line.
  10284. * @param {Number} x
  10285. * @param {Number} y
  10286. */
  10287. lineTo: function(x, y) {
  10288. if (!this.path) {
  10289. this.beginPath();
  10290. }
  10291. this.path.lineTo(x, y);
  10292. this.path.element = null;
  10293. },
  10294. /**
  10295. * Adds a new closed subpath to the path, representing the given rectangle.
  10296. * @param {Number} x
  10297. * @param {Number} y
  10298. * @param {Number} width
  10299. * @param {Number} height
  10300. */
  10301. rect: function(x, y, width, height) {
  10302. this.moveTo(x, y);
  10303. this.lineTo(x + width, y);
  10304. this.lineTo(x + width, y + height);
  10305. this.lineTo(x, y + height);
  10306. this.closePath();
  10307. },
  10308. /**
  10309. * Paints the box that outlines the given rectangle onto the canvas, using the current stroke style.
  10310. * @param {Number} x
  10311. * @param {Number} y
  10312. * @param {Number} width
  10313. * @param {Number} height
  10314. */
  10315. strokeRect: function(x, y, width, height) {
  10316. this.beginPath();
  10317. this.rect(x, y, width, height);
  10318. this.stroke();
  10319. },
  10320. /**
  10321. * Paints the given rectangle onto the canvas, using the current fill style.
  10322. * @param {Number} x
  10323. * @param {Number} y
  10324. * @param {Number} width
  10325. * @param {Number} height
  10326. */
  10327. fillRect: function(x, y, width, height) {
  10328. this.beginPath();
  10329. this.rect(x, y, width, height);
  10330. this.fill();
  10331. },
  10332. /**
  10333. * Marks the current subpath as closed, and starts a new subpath with a point the same as the start and end of the newly closed subpath.
  10334. */
  10335. closePath: function() {
  10336. if (!this.path) {
  10337. this.beginPath();
  10338. }
  10339. this.path.closePath();
  10340. this.path.element = null;
  10341. },
  10342. /**
  10343. * Arc command using svg parameters.
  10344. * @param {Number} r1
  10345. * @param {Number} r2
  10346. * @param {Number} rotation
  10347. * @param {Number} large
  10348. * @param {Number} swipe
  10349. * @param {Number} x2
  10350. * @param {Number} y2
  10351. */
  10352. arcSvg: function(r1, r2, rotation, large, swipe, x2, y2) {
  10353. if (!this.path) {
  10354. this.beginPath();
  10355. }
  10356. this.path.arcSvg(r1, r2, rotation, large, swipe, x2, y2);
  10357. this.path.element = null;
  10358. },
  10359. /**
  10360. * Adds points to the subpath such that the arc described by the circumference of the circle described by the arguments, starting at the given start angle and ending at the given end angle, going in the given direction (defaulting to clockwise), is added to the path, connected to the previous point by a straight line.
  10361. * @param {Number} x
  10362. * @param {Number} y
  10363. * @param {Number} radius
  10364. * @param {Number} startAngle
  10365. * @param {Number} endAngle
  10366. * @param {Number} anticlockwise
  10367. */
  10368. arc: function(x, y, radius, startAngle, endAngle, anticlockwise) {
  10369. if (!this.path) {
  10370. this.beginPath();
  10371. }
  10372. this.path.arc(x, y, radius, startAngle, endAngle, anticlockwise);
  10373. this.path.element = null;
  10374. },
  10375. /**
  10376. * Adds points to the subpath such that the arc described by the circumference of the ellipse described by the arguments, starting at the given start angle and ending at the given end angle, going in the given direction (defaulting to clockwise), is added to the path, connected to the previous point by a straight line.
  10377. * @param {Number} x
  10378. * @param {Number} y
  10379. * @param {Number} radiusX
  10380. * @param {Number} radiusY
  10381. * @param {Number} rotation
  10382. * @param {Number} startAngle
  10383. * @param {Number} endAngle
  10384. * @param {Number} anticlockwise
  10385. */
  10386. ellipse: function(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise) {
  10387. if (!this.path) {
  10388. this.beginPath();
  10389. }
  10390. this.path.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise);
  10391. this.path.element = null;
  10392. },
  10393. /**
  10394. * Adds an arc with the given control points and radius to the current subpath, connected to the previous point by a straight line.
  10395. * If two radii are provided, the first controls the width of the arc's ellipse, and the second controls the height. If only one is provided, or if they are the same, the arc is from a circle.
  10396. * In the case of an ellipse, the rotation argument controls the clockwise inclination of the ellipse relative to the x-axis.
  10397. * @param {Number} x1
  10398. * @param {Number} y1
  10399. * @param {Number} x2
  10400. * @param {Number} y2
  10401. * @param {Number} radiusX
  10402. * @param {Number} radiusY
  10403. * @param {Number} rotation
  10404. */
  10405. arcTo: function(x1, y1, x2, y2, radiusX, radiusY, rotation) {
  10406. if (!this.path) {
  10407. this.beginPath();
  10408. }
  10409. this.path.arcTo(x1, y1, x2, y2, radiusX, radiusY, rotation);
  10410. this.path.element = null;
  10411. },
  10412. /**
  10413. * Adds the given point to the current subpath, connected to the previous one by a cubic Bézier curve with the given control points.
  10414. * @param {Number} x1
  10415. * @param {Number} y1
  10416. * @param {Number} x2
  10417. * @param {Number} y2
  10418. * @param {Number} x3
  10419. * @param {Number} y3
  10420. */
  10421. bezierCurveTo: function(x1, y1, x2, y2, x3, y3) {
  10422. if (!this.path) {
  10423. this.beginPath();
  10424. }
  10425. this.path.bezierCurveTo(x1, y1, x2, y2, x3, y3);
  10426. this.path.element = null;
  10427. },
  10428. /**
  10429. * Strokes the given text at the given position. If a maximum width is provided, the text will be scaled to fit that width if necessary.
  10430. * @param {String} text
  10431. * @param {Number} x
  10432. * @param {Number} y
  10433. */
  10434. strokeText: function(text, x, y) {
  10435. text = String(text);
  10436. if (this.strokeStyle) {
  10437. var element = this.getElement('text'),
  10438. tspan = this.surface.getSvgElement(element, 'tspan', 0);
  10439. this.surface.setElementAttributes(element, {
  10440. "x": x,
  10441. "y": y,
  10442. "transform": this.matrix.toSvg(),
  10443. "stroke": this.strokeStyle,
  10444. "fill": "none",
  10445. "opacity": this.globalAlpha,
  10446. "stroke-opacity": this.strokeOpacity,
  10447. "style": "font: " + this.font,
  10448. "stroke-dasharray": this.lineDash.join(','),
  10449. "stroke-dashoffset": this.lineDashOffset
  10450. });
  10451. if (this.lineDash.length) {
  10452. this.surface.setElementAttributes(element, {
  10453. "stroke-dasharray": this.lineDash.join(','),
  10454. "stroke-dashoffset": this.lineDashOffset
  10455. });
  10456. }
  10457. if (tspan.dom.firstChild) {
  10458. tspan.dom.removeChild(tspan.dom.firstChild);
  10459. }
  10460. this.surface.setElementAttributes(tspan, {
  10461. "alignment-baseline": "alphabetic"
  10462. });
  10463. tspan.dom.appendChild(document.createTextNode(Ext.String.htmlDecode(text)));
  10464. }
  10465. },
  10466. /**
  10467. * Fills the given text at the given position. If a maximum width is provided, the text will be scaled to fit that width if necessary.
  10468. * @param {String} text
  10469. * @param {Number} x
  10470. * @param {Number} y
  10471. */
  10472. fillText: function(text, x, y) {
  10473. text = String(text);
  10474. if (this.fillStyle) {
  10475. var element = this.getElement('text'),
  10476. tspan = this.surface.getSvgElement(element, 'tspan', 0);
  10477. this.surface.setElementAttributes(element, {
  10478. "x": x,
  10479. "y": y,
  10480. "transform": this.matrix.toSvg(),
  10481. "fill": this.fillStyle,
  10482. "opacity": this.globalAlpha,
  10483. "fill-opacity": this.fillOpacity,
  10484. "style": "font: " + this.font
  10485. });
  10486. if (tspan.dom.firstChild) {
  10487. tspan.dom.removeChild(tspan.dom.firstChild);
  10488. }
  10489. this.surface.setElementAttributes(tspan, {
  10490. "alignment-baseline": "alphabetic"
  10491. });
  10492. tspan.dom.appendChild(document.createTextNode(Ext.String.htmlDecode(text)));
  10493. }
  10494. },
  10495. /**
  10496. * Draws the given image onto the canvas.
  10497. * If the first argument isn't an img, canvas, or video element, throws a TypeMismatchError exception. If the image has no image data, throws an InvalidStateError exception. If the one of the source rectangle dimensions is zero, throws an IndexSizeError exception. If the image isn't yet fully decoded, then nothing is drawn.
  10498. * @param {HTMLElement} image
  10499. * @param {Number} sx
  10500. * @param {Number} sy
  10501. * @param {Number} sw
  10502. * @param {Number} sh
  10503. * @param {Number} dx
  10504. * @param {Number} dy
  10505. * @param {Number} dw
  10506. * @param {Number} dh
  10507. */
  10508. drawImage: function(image, sx, sy, sw, sh, dx, dy, dw, dh) {
  10509. var me = this,
  10510. element = me.getElement('image'),
  10511. x = sx,
  10512. y = sy,
  10513. width = typeof sw === 'undefined' ? image.width : sw,
  10514. height = typeof sh === 'undefined' ? image.height : sh,
  10515. viewBox = null;
  10516. if (typeof dh !== 'undefined') {
  10517. viewBox = sx + " " + sy + " " + sw + " " + sh;
  10518. x = dx;
  10519. y = dy;
  10520. width = dw;
  10521. height = dh;
  10522. }
  10523. element.dom.setAttributeNS("http:/" + "/www.w3.org/1999/xlink", "href", image.src);
  10524. me.surface.setElementAttributes(element, {
  10525. viewBox: viewBox,
  10526. x: x,
  10527. y: y,
  10528. width: width,
  10529. height: height,
  10530. opacity: me.globalAlpha,
  10531. transform: me.matrix.toSvg()
  10532. });
  10533. },
  10534. /**
  10535. * Fills the subpaths of the current default path or the given path with the current fill style.
  10536. */
  10537. fill: function() {
  10538. var me = this;
  10539. if (!me.path) {
  10540. return;
  10541. }
  10542. if (me.fillStyle) {
  10543. var path,
  10544. fillGradient = me.fillGradient,
  10545. element = me.path.element,
  10546. bbox = me.bbox,
  10547. fill;
  10548. if (!element) {
  10549. path = me.path.toString();
  10550. element = me.path.element = me.getElement('path');
  10551. me.surface.setElementAttributes(element, {
  10552. "d": path,
  10553. "transform": me.matrix.toSvg()
  10554. });
  10555. }
  10556. if (fillGradient && bbox) {
  10557. // This indirectly calls ctx.createLinearGradient or ctx.createRadialGradient,
  10558. // depending on the type of gradient, and returns an instance of
  10559. // Ext.draw.engine.SvgContext.Gradient.
  10560. fill = fillGradient.generateGradient(me, bbox);
  10561. } else {
  10562. fill = me.fillStyle;
  10563. }
  10564. me.surface.setElementAttributes(element, {
  10565. "fill": fill,
  10566. "fill-opacity": me.fillOpacity * me.globalAlpha
  10567. });
  10568. }
  10569. },
  10570. /**
  10571. * Strokes the subpaths of the current default path or the given path with the current stroke style.
  10572. */
  10573. stroke: function() {
  10574. var me = this;
  10575. if (!me.path) {
  10576. return;
  10577. }
  10578. if (me.strokeStyle) {
  10579. var path,
  10580. strokeGradient = me.strokeGradient,
  10581. element = me.path.element,
  10582. bbox = me.bbox,
  10583. stroke;
  10584. if (!element || !me.path.svgString) {
  10585. path = me.path.toString();
  10586. if (!path) {
  10587. return;
  10588. }
  10589. element = me.path.element = me.getElement('path');
  10590. me.surface.setElementAttributes(element, {
  10591. "fill": "none",
  10592. "d": path,
  10593. "transform": me.matrix.toSvg()
  10594. });
  10595. }
  10596. if (strokeGradient && bbox) {
  10597. // This indirectly calls ctx.createLinearGradient or ctx.createRadialGradient,
  10598. // depending on the type of gradient, and returns an instance of
  10599. // Ext.draw.engine.SvgContext.Gradient.
  10600. stroke = strokeGradient.generateGradient(me, bbox);
  10601. } else {
  10602. stroke = me.strokeStyle;
  10603. }
  10604. me.surface.setElementAttributes(element, {
  10605. "stroke": stroke,
  10606. "stroke-linecap": me.lineCap,
  10607. "stroke-linejoin": me.lineJoin,
  10608. "stroke-width": me.lineWidth,
  10609. "stroke-opacity": me.strokeOpacity * me.globalAlpha,
  10610. "stroke-dasharray": me.lineDash.join(','),
  10611. "stroke-dashoffset": me.lineDashOffset
  10612. });
  10613. if (me.lineDash.length) {
  10614. me.surface.setElementAttributes(element, {
  10615. "stroke-dasharray": me.lineDash.join(','),
  10616. "stroke-dashoffset": me.lineDashOffset
  10617. });
  10618. }
  10619. }
  10620. },
  10621. /**
  10622. * @protected
  10623. *
  10624. * Note: After the method guarantees the transform matrix will be inverted.
  10625. * @param {Object} attr The attribute object
  10626. * @param {Boolean} [transformFillStroke] Indicate whether to transform fill and stroke. If this is not
  10627. * given, then uses `attr.transformFillStroke` instead.
  10628. */
  10629. fillStroke: function(attr, transformFillStroke) {
  10630. var ctx = this,
  10631. fillStyle = ctx.fillStyle,
  10632. strokeStyle = ctx.strokeStyle,
  10633. fillOpacity = ctx.fillOpacity,
  10634. strokeOpacity = ctx.strokeOpacity;
  10635. if (transformFillStroke === undefined) {
  10636. transformFillStroke = attr.transformFillStroke;
  10637. }
  10638. if (!transformFillStroke) {
  10639. attr.inverseMatrix.toContext(ctx);
  10640. }
  10641. if (fillStyle && fillOpacity !== 0) {
  10642. ctx.fill();
  10643. }
  10644. if (strokeStyle && strokeOpacity !== 0) {
  10645. ctx.stroke();
  10646. }
  10647. },
  10648. appendPath: function(path) {
  10649. this.path = path.clone();
  10650. },
  10651. setLineDash: function(lineDash) {
  10652. this.lineDash = lineDash;
  10653. },
  10654. getLineDash: function() {
  10655. return this.lineDash;
  10656. },
  10657. /**
  10658. * Returns an object that represents a linear gradient that paints along the line
  10659. * given by the coordinates represented by the arguments.
  10660. * @param {Number} x0
  10661. * @param {Number} y0
  10662. * @param {Number} x1
  10663. * @param {Number} y1
  10664. * @return {Ext.draw.engine.SvgContext.Gradient}
  10665. */
  10666. createLinearGradient: function(x0, y0, x1, y1) {
  10667. var me = this,
  10668. element = me.surface.getNextDef('linearGradient'),
  10669. gradient;
  10670. me.surface.setElementAttributes(element, {
  10671. "x1": x0,
  10672. "y1": y0,
  10673. "x2": x1,
  10674. "y2": y1,
  10675. "gradientUnits": "userSpaceOnUse"
  10676. });
  10677. gradient = new Ext.draw.engine.SvgContext.Gradient(me, me.surface, element);
  10678. return gradient;
  10679. },
  10680. /**
  10681. * Returns a CanvasGradient object that represents a radial gradient that paints
  10682. * along the cone given by the circles represented by the arguments.
  10683. * If either of the radii are negative, throws an IndexSizeError exception.
  10684. * @param {Number} x0
  10685. * @param {Number} y0
  10686. * @param {Number} r0
  10687. * @param {Number} x1
  10688. * @param {Number} y1
  10689. * @param {Number} r1
  10690. * @return {Ext.draw.engine.SvgContext.Gradient}
  10691. */
  10692. createRadialGradient: function(x0, y0, r0, x1, y1, r1) {
  10693. var me = this,
  10694. element = me.surface.getNextDef('radialGradient'),
  10695. gradient;
  10696. me.surface.setElementAttributes(element, {
  10697. fx: x0,
  10698. fy: y0,
  10699. cx: x1,
  10700. cy: y1,
  10701. r: r1,
  10702. gradientUnits: 'userSpaceOnUse'
  10703. });
  10704. gradient = new Ext.draw.engine.SvgContext.Gradient(me, me.surface, element, r0 / r1);
  10705. return gradient;
  10706. }
  10707. });
  10708. /**
  10709. * @class Ext.draw.engine.SvgContext.Gradient
  10710. *
  10711. * A class that implements native CanvasGradient interface
  10712. * (https://developer.mozilla.org/en/docs/Web/API/CanvasGradient)
  10713. * and a `toString` method that returns the ID of the gradient.
  10714. */
  10715. Ext.define('Ext.draw.engine.SvgContext.Gradient', {
  10716. // Gradients workflow in SVG engine:
  10717. //
  10718. // Inside the 'fill' & 'stroke' methods of the SVG Context
  10719. // we check if the 'ctx.fillGradient' or 'ctx.strokeGradient'
  10720. // objects exist.
  10721. // These objects are instances of Ext.draw.gradient.Gradient
  10722. // and are assigned to the ctx by the sprite's 'useAttributes' method,
  10723. // if the sprite has any gradients.
  10724. //
  10725. // Additionally, we check if the 'ctx.bbox' object exists - the bounding box
  10726. // for the gradients, set by the sprite's 'setGradientBBox' method.
  10727. //
  10728. // If we have both bbox and a valid instance of Ext.draw.gradient.Gradient,
  10729. // the 'generateGradient' method of the instance is called,
  10730. // which in turn calls 'ctx.createLinearGradient' or 'ctx.createRadialGradient'
  10731. // depending on the type of the gradient represented by the instance.
  10732. // These methods create a 'linearGradient' or 'radialGradient' SVG
  10733. // node and wrap it into a Ext.draw.engine.SvgContext.Gradient instance.
  10734. //
  10735. // The Ext.draw.engine.SvgContext.Gradient instance is then used internally
  10736. // by the Ext.draw.gradient.Gradient to add color 'stop' nodes
  10737. // to the gradient node, and by the SVG context when the 'fill' or
  10738. // 'stroke' attribute of a 'path' node is set to the Ext.draw.engine.SvgContext.Gradient
  10739. // instance, which is implicitly converted to a string - a 'url(#id)' reference
  10740. // to the gradient element wrapped by the instance.
  10741. isGradient: true,
  10742. constructor: function(ctx, surface, element, compression) {
  10743. var me = this;
  10744. me.ctx = ctx;
  10745. me.surface = surface;
  10746. me.element = element;
  10747. me.position = 0;
  10748. me.compression = compression || 0;
  10749. },
  10750. /**
  10751. * Adds a color stop with the given color to the gradient at the given offset. 0.0 is the offset at one end of the gradient, 1.0 is the offset at the other end.
  10752. * @param {Number} offset
  10753. * @param {String} color
  10754. */
  10755. addColorStop: function(offset, color) {
  10756. var me = this,
  10757. stop = me.surface.getSvgElement(me.element, 'stop', me.position++),
  10758. compression = me.compression;
  10759. me.surface.setElementAttributes(stop, {
  10760. "offset": (((1 - compression) * offset + compression) * 100).toFixed(2) + '%',
  10761. "stop-color": color,
  10762. "stop-opacity": Ext.util.Color.fly(color).a.toFixed(15)
  10763. });
  10764. },
  10765. toString: function() {
  10766. var children = this.element.dom.childNodes;
  10767. // Removing surplus stops in case existing gradient element with more stops was reused.
  10768. while (children.length > this.position) {
  10769. Ext.fly(children[children.length - 1]).destroy();
  10770. }
  10771. return 'url(#' + this.element.getId() + ')';
  10772. }
  10773. });
  10774. /**
  10775. * @class Ext.draw.engine.Svg
  10776. * @extends Ext.draw.Surface
  10777. *
  10778. * SVG engine.
  10779. */
  10780. Ext.define('Ext.draw.engine.Svg', {
  10781. extend: 'Ext.draw.Surface',
  10782. requires: [
  10783. 'Ext.draw.engine.SvgContext'
  10784. ],
  10785. isSVG: true,
  10786. config: {
  10787. /**
  10788. * @cfg {Boolean} highPrecision
  10789. * Nothing needs to be done in high precision mode.
  10790. */
  10791. highPrecision: false
  10792. },
  10793. getElementConfig: function() {
  10794. return {
  10795. reference: 'element',
  10796. style: {
  10797. position: 'absolute'
  10798. },
  10799. children: [
  10800. {
  10801. reference: 'bodyElement',
  10802. style: {
  10803. width: '100%',
  10804. height: '100%',
  10805. position: 'relative'
  10806. },
  10807. children: [
  10808. {
  10809. tag: 'svg',
  10810. reference: 'svgElement',
  10811. namespace: "http://www.w3.org/2000/svg",
  10812. width: '100%',
  10813. height: '100%',
  10814. version: 1.1
  10815. }
  10816. ]
  10817. }
  10818. ]
  10819. };
  10820. },
  10821. constructor: function(config) {
  10822. var me = this;
  10823. me.callParent([
  10824. config
  10825. ]);
  10826. me.mainGroup = me.createSvgNode("g");
  10827. me.defsElement = me.createSvgNode("defs");
  10828. // me.svgElement is assigned in element creation of Ext.Component.
  10829. me.svgElement.appendChild(me.mainGroup);
  10830. me.svgElement.appendChild(me.defsElement);
  10831. me.ctx = new Ext.draw.engine.SvgContext(me);
  10832. },
  10833. /**
  10834. * Creates a DOM element under the SVG namespace of the given type.
  10835. * @param {String} type The type of the SVG DOM element.
  10836. * @return {*} The created element.
  10837. */
  10838. createSvgNode: function(type) {
  10839. var node = document.createElementNS("http://www.w3.org/2000/svg", type);
  10840. return Ext.get(node);
  10841. },
  10842. /**
  10843. * @private
  10844. * Returns the SVG DOM element at the given position.
  10845. * If it does not already exist or is a different element tag,
  10846. * it will be created and inserted into the DOM.
  10847. * @param {Ext.dom.Element} group The parent DOM element.
  10848. * @param {String} tag The SVG element tag.
  10849. * @param {Number} position The position of the element in the DOM.
  10850. * @return {Ext.dom.Element} The SVG element.
  10851. */
  10852. getSvgElement: function(group, tag, position) {
  10853. var childNodes = group.dom.childNodes,
  10854. length = childNodes.length,
  10855. element;
  10856. if (position < length) {
  10857. element = childNodes[position];
  10858. if (element.tagName === tag) {
  10859. return Ext.get(element);
  10860. } else {
  10861. Ext.destroy(element);
  10862. }
  10863. } else if (position > length) {
  10864. Ext.raise("Invalid position.");
  10865. }
  10866. element = Ext.get(this.createSvgNode(tag));
  10867. if (position === 0) {
  10868. group.insertFirst(element);
  10869. } else {
  10870. element.insertAfter(Ext.fly(childNodes[position - 1]));
  10871. }
  10872. element.cache = {};
  10873. return element;
  10874. },
  10875. /**
  10876. * @private
  10877. * Applies attributes to the given element.
  10878. * @param {Ext.dom.Element} element The DOM element to be applied.
  10879. * @param {Object} attributes The attributes to apply to the element.
  10880. */
  10881. setElementAttributes: function(element, attributes) {
  10882. var dom = element.dom,
  10883. cache = element.cache,
  10884. name, value;
  10885. for (name in attributes) {
  10886. value = attributes[name];
  10887. if (cache[name] !== value) {
  10888. cache[name] = value;
  10889. dom.setAttribute(name, value);
  10890. }
  10891. }
  10892. },
  10893. /**
  10894. * @private
  10895. * Gets the next reference element under the SVG 'defs' tag.
  10896. * @param {String} tagName The type of reference element.
  10897. * @return {Ext.dom.Element} The reference element.
  10898. */
  10899. getNextDef: function(tagName) {
  10900. return this.getSvgElement(this.defsElement, tagName, this.defsPosition++);
  10901. },
  10902. /**
  10903. * @method clearTransform
  10904. * @inheritdoc
  10905. */
  10906. clearTransform: function() {
  10907. var me = this;
  10908. me.mainGroup.set({
  10909. transform: me.matrix.toSvg()
  10910. });
  10911. },
  10912. /**
  10913. * @method clear
  10914. * @inheritdoc
  10915. */
  10916. clear: function() {
  10917. this.ctx.clear();
  10918. this.removeSurplusDefs();
  10919. this.defsPosition = 0;
  10920. },
  10921. removeSurplusDefs: function() {
  10922. var defsElement = this.defsElement,
  10923. defs = defsElement.dom.childNodes,
  10924. ln = defs.length,
  10925. i;
  10926. for (i = ln - 1; i > this.defsPosition; i--) {
  10927. defsElement.removeChild(defs[i]);
  10928. }
  10929. },
  10930. /**
  10931. * @method renderSprite
  10932. * @inheritdoc
  10933. */
  10934. renderSprite: function(sprite) {
  10935. var me = this,
  10936. rect = me.getRect(),
  10937. ctx = me.ctx;
  10938. // This check is simplistic, but should result in a better performance
  10939. // compared to !sprite.isVisible() when most surface sprites are visible.
  10940. if (sprite.attr.hidden || sprite.attr.globalAlpha === 0) {
  10941. // Create an empty group for each hidden sprite,
  10942. // so that when these sprites do become visible,
  10943. // they don't need groups to be created and don't
  10944. // mess up the previous order of elements in the
  10945. // document, i.e. sprites rendered in the next
  10946. // frame reuse the same elements they used in the
  10947. // previous frame.
  10948. ctx.save();
  10949. ctx.restore();
  10950. return;
  10951. }
  10952. // Each sprite is rendered in its own group ('g' element),
  10953. // returned by the `ctx.save` method.
  10954. // Essentially, the group _is_ the sprite.
  10955. sprite.element = ctx.save();
  10956. sprite.preRender(this);
  10957. sprite.useAttributes(ctx, rect);
  10958. if (false === sprite.render(this, ctx, [
  10959. 0,
  10960. 0,
  10961. rect[2],
  10962. rect[3]
  10963. ])) {
  10964. return false;
  10965. }
  10966. sprite.setDirty(false);
  10967. ctx.restore();
  10968. },
  10969. /**
  10970. * @private
  10971. */
  10972. toSVG: function(size, surfaces) {
  10973. var className = Ext.getClassName(this),
  10974. svg, surface, rect, i;
  10975. svg = '<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg"' + ' width="' + size.width + '"' + ' height="' + size.height + '">';
  10976. for (i = 0; i < surfaces.length; i++) {
  10977. surface = surfaces[i];
  10978. if (Ext.getClassName(surface) !== className) {
  10979. continue;
  10980. }
  10981. rect = surface.getRect();
  10982. svg += '<g transform="translate(' + rect[0] + ',' + rect[1] + ')">';
  10983. svg += this.serializeNode(surface.svgElement.dom);
  10984. svg += '</g>';
  10985. }
  10986. svg += '</svg>';
  10987. return svg;
  10988. },
  10989. b64EncodeUnicode: function(str) {
  10990. // Since DOMStrings are 16-bit-encoded strings, in most browsers calling window.btoa
  10991. // on a Unicode string will cause a Character Out Of Range exception if a character
  10992. // exceeds the range of a 8-bit ASCII-encoded character. More information:
  10993. // https://developer.mozilla.org/en/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
  10994. return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
  10995. return String.fromCharCode('0x' + p1);
  10996. }));
  10997. },
  10998. flatten: function(size, surfaces) {
  10999. var svg = '<?xml version="1.0" standalone="yes"?>';
  11000. svg += this.toSVG(size, surfaces);
  11001. return {
  11002. data: 'data:image/svg+xml;base64,' + this.b64EncodeUnicode(svg),
  11003. type: 'svg'
  11004. };
  11005. },
  11006. /**
  11007. * @private
  11008. * Serializes an SVG DOM element and its children recursively into a string.
  11009. * @param {Object} node DOM element to serialize.
  11010. * @return {String}
  11011. */
  11012. serializeNode: function(node) {
  11013. var result = '',
  11014. i, n, attr, child;
  11015. if (node.nodeType === document.TEXT_NODE) {
  11016. return node.nodeValue;
  11017. }
  11018. result += '<' + node.nodeName;
  11019. if (node.attributes.length) {
  11020. for (i = 0 , n = node.attributes.length; i < n; i++) {
  11021. attr = node.attributes[i];
  11022. result += ' ' + attr.name + '="' + Ext.String.htmlEncode(attr.value) + '"';
  11023. }
  11024. }
  11025. result += '>';
  11026. if (node.childNodes && node.childNodes.length) {
  11027. for (i = 0 , n = node.childNodes.length; i < n; i++) {
  11028. child = node.childNodes[i];
  11029. result += this.serializeNode(child);
  11030. }
  11031. }
  11032. result += '</' + node.nodeName + '>';
  11033. return result;
  11034. },
  11035. /**
  11036. * Destroys the Canvas element and prepares it for Garbage Collection.
  11037. */
  11038. destroy: function() {
  11039. var me = this;
  11040. me.ctx.destroy();
  11041. me.mainGroup.destroy();
  11042. me.defsElement.destroy();
  11043. delete me.mainGroup;
  11044. delete me.defsElement;
  11045. delete me.ctx;
  11046. me.callParent();
  11047. },
  11048. remove: function(sprite, destroySprite) {
  11049. if (sprite && sprite.element) {
  11050. // If sprite has an associated SVG element, remove it from the surface.
  11051. sprite.element.destroy();
  11052. sprite.element = null;
  11053. }
  11054. this.callParent(arguments);
  11055. }
  11056. });
  11057. // @define Ext.draw.engine.excanvas
  11058. /**
  11059. * @private
  11060. */
  11061. Ext.draw || (Ext.draw = {});
  11062. Ext.draw.engine || (Ext.draw.engine = {});
  11063. Ext.draw.engine.excanvas = true;
  11064. // Copyright 2006 Google Inc.
  11065. //
  11066. // Licensed under the Apache License, Version 2.0 (the "License");
  11067. // you may not use this file except in compliance with the License.
  11068. // You may obtain a copy of the License at
  11069. //
  11070. // http://www.apache.org/licenses/LICENSE-2.0
  11071. //
  11072. // Unless required by applicable law or agreed to in writing, software
  11073. // distributed under the License is distributed on an "AS IS" BASIS,
  11074. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11075. // See the License for the specific language governing permissions and
  11076. // limitations under the License.
  11077. // Known Issues:
  11078. //
  11079. // * Patterns only support repeat.
  11080. // * Radial gradient are not implemented. The VML version of these look very
  11081. // different from the canvas one.
  11082. // * Clipping paths are not implemented.
  11083. // * Coordsize. The width and height attribute have higher priority than the
  11084. // width and height style values which isn't correct.
  11085. // * Painting mode isn't implemented.
  11086. // * Canvas width/height should is using content-box by default. IE in
  11087. // Quirks mode will draw the canvas using border-box. Either change your
  11088. // doctype to HTML5
  11089. // (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
  11090. // or use Box Sizing Behavior from WebFX
  11091. // (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
  11092. // * Non uniform scaling does not correctly scale strokes.
  11093. // * Optimize. There is always room for speed improvements.
  11094. // Only add this code if we do not already have a canvas implementation
  11095. if (!document.createElement('canvas').getContext) {
  11096. (function() {
  11097. // alias some functions to make (compiled) code shorter
  11098. var m = Math;
  11099. var mr = m.round;
  11100. var ms = m.sin;
  11101. var mc = m.cos;
  11102. var abs = m.abs;
  11103. var sqrt = m.sqrt;
  11104. // this is used for sub pixel precision
  11105. var Z = 10;
  11106. var Z2 = Z / 2;
  11107. var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];
  11108. /*
  11109. * @method getContext
  11110. * This function is assigned to the <canvas></canvas> elements as element.getContext().
  11111. * @return {CanvasRenderingContext2D_}
  11112. */
  11113. function getContext() {
  11114. return this.context_ || (this.context_ = new CanvasRenderingContext2D_(this));
  11115. }
  11116. var slice = Array.prototype.slice;
  11117. /*
  11118. * @method bind
  11119. * Binds a function to an object. The returned function will always use the
  11120. * passed in {@code obj} as {@code this}.
  11121. *
  11122. * Example:
  11123. *
  11124. * g = bind(f, obj, a, b)
  11125. * g(c, d) // will do f.call(obj, a, b, c, d)
  11126. *
  11127. * @param {Function} f The function to bind the object to
  11128. * @param {Object} obj The object that should act as this when the function
  11129. * is called
  11130. * @param {*} var_args Rest arguments that will be used as the initial
  11131. * arguments when the function is called
  11132. * @return {Function} A new function that has bound this
  11133. */
  11134. function bind(f, obj, var_args) {
  11135. var a = slice.call(arguments, 2);
  11136. return function() {
  11137. return f.apply(obj, a.concat(slice.call(arguments)));
  11138. };
  11139. }
  11140. function encodeHtmlAttribute(s) {
  11141. return String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
  11142. }
  11143. function addNamespace(doc, prefix, urn) {
  11144. Ext.onReady(function() {
  11145. if (!doc.namespaces[prefix]) {
  11146. doc.namespaces.add(prefix, urn, '#default#VML');
  11147. }
  11148. });
  11149. }
  11150. function addNamespacesAndStylesheet(doc) {
  11151. addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml');
  11152. addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office');
  11153. // Setup default CSS. Only add one style sheet per document
  11154. if (!doc.styleSheets['ex_canvas_']) {
  11155. var ss = doc.createStyleSheet();
  11156. ss.owningElement.id = 'ex_canvas_';
  11157. ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + // default size is 300x150 in Gecko and Opera
  11158. 'text-align:left;width:300px;height:150px}';
  11159. }
  11160. }
  11161. // Add namespaces and stylesheet at startup.
  11162. addNamespacesAndStylesheet(document);
  11163. var G_vmlCanvasManager_ = {
  11164. init: function(opt_doc) {
  11165. var doc = opt_doc || document;
  11166. // Create a dummy element so that IE will allow canvas elements to be
  11167. // recognized.
  11168. doc.createElement('canvas');
  11169. doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
  11170. },
  11171. init_: function(doc) {
  11172. // find all canvas elements
  11173. var els = doc.getElementsByTagName('canvas');
  11174. for (var i = 0; i < els.length; i++) {
  11175. this.initElement(els[i]);
  11176. }
  11177. },
  11178. /*
  11179. * Public initializes a canvas element so that it can be used as canvas
  11180. * element from now on. This is called automatically before the page is
  11181. * loaded but if you are creating elements using createElement you need to
  11182. * make sure this is called on the element.
  11183. * @param {HTMLElement} el The canvas element to initialize.
  11184. * @return {HTMLElement} the element that was created.
  11185. */
  11186. initElement: function(el) {
  11187. if (!el.getContext) {
  11188. el.getContext = getContext;
  11189. // Add namespaces and stylesheet to document of the element.
  11190. addNamespacesAndStylesheet(el.ownerDocument);
  11191. // Remove fallback content. There is no way to hide text nodes so we
  11192. // just remove all childNodes. We could hide all elements and remove
  11193. // text nodes but who really cares about the fallback content.
  11194. el.innerHTML = '';
  11195. // do not use inline function because that will leak memory
  11196. el.attachEvent('onpropertychange', onPropertyChange);
  11197. el.attachEvent('onresize', onResize);
  11198. var attrs = el.attributes;
  11199. if (attrs.width && attrs.width.specified) {
  11200. // TODO: use runtimeStyle and coordsize
  11201. // el.getContext().setWidth_(attrs.width.nodeValue);
  11202. el.style.width = attrs.width.nodeValue + 'px';
  11203. } else {
  11204. el.width = el.clientWidth;
  11205. }
  11206. if (attrs.height && attrs.height.specified) {
  11207. // TODO: use runtimeStyle and coordsize
  11208. // el.getContext().setHeight_(attrs.height.nodeValue);
  11209. el.style.height = attrs.height.nodeValue + 'px';
  11210. } else {
  11211. el.height = el.clientHeight;
  11212. }
  11213. }
  11214. //el.getContext().setCoordsize_()
  11215. return el;
  11216. }
  11217. };
  11218. function onPropertyChange(e) {
  11219. var el = e.srcElement;
  11220. switch (e.propertyName) {
  11221. case 'width':
  11222. el.getContext().clearRect();
  11223. el.style.width = el.attributes.width.nodeValue + 'px';
  11224. // In IE8 this does not trigger onresize.
  11225. el.firstChild.style.width = el.clientWidth + 'px';
  11226. break;
  11227. case 'height':
  11228. el.getContext().clearRect();
  11229. el.style.height = el.attributes.height.nodeValue + 'px';
  11230. el.firstChild.style.height = el.clientHeight + 'px';
  11231. break;
  11232. }
  11233. }
  11234. function onResize(e) {
  11235. var el = e.srcElement;
  11236. if (el.firstChild) {
  11237. el.firstChild.style.width = el.clientWidth + 'px';
  11238. el.firstChild.style.height = el.clientHeight + 'px';
  11239. }
  11240. }
  11241. G_vmlCanvasManager_.init();
  11242. // precompute "00" to "FF"
  11243. var decToHex = [];
  11244. for (var i = 0; i < 16; i++) {
  11245. for (var j = 0; j < 16; j++) {
  11246. decToHex[i * 16 + j] = i.toString(16) + j.toString(16);
  11247. }
  11248. }
  11249. function createMatrixIdentity() {
  11250. return [
  11251. [
  11252. 1,
  11253. 0,
  11254. 0
  11255. ],
  11256. [
  11257. 0,
  11258. 1,
  11259. 0
  11260. ],
  11261. [
  11262. 0,
  11263. 0,
  11264. 1
  11265. ]
  11266. ];
  11267. }
  11268. function matrixMultiply(m1, m2) {
  11269. var result = createMatrixIdentity();
  11270. for (var x = 0; x < 3; x++) {
  11271. for (var y = 0; y < 3; y++) {
  11272. var sum = 0;
  11273. for (var z = 0; z < 3; z++) {
  11274. sum += m1[x][z] * m2[z][y];
  11275. }
  11276. result[x][y] = sum;
  11277. }
  11278. }
  11279. return result;
  11280. }
  11281. function copyState(o1, o2) {
  11282. o2.fillStyle = o1.fillStyle;
  11283. o2.lineCap = o1.lineCap;
  11284. o2.lineJoin = o1.lineJoin;
  11285. o2.lineDash = o1.lineDash;
  11286. o2.lineWidth = o1.lineWidth;
  11287. o2.miterLimit = o1.miterLimit;
  11288. o2.shadowBlur = o1.shadowBlur;
  11289. o2.shadowColor = o1.shadowColor;
  11290. o2.shadowOffsetX = o1.shadowOffsetX;
  11291. o2.shadowOffsetY = o1.shadowOffsetY;
  11292. o2.strokeStyle = o1.strokeStyle;
  11293. o2.globalAlpha = o1.globalAlpha;
  11294. o2.font = o1.font;
  11295. o2.textAlign = o1.textAlign;
  11296. o2.textBaseline = o1.textBaseline;
  11297. o2.arcScaleX_ = o1.arcScaleX_;
  11298. o2.arcScaleY_ = o1.arcScaleY_;
  11299. o2.lineScale_ = o1.lineScale_;
  11300. }
  11301. var colorData = {
  11302. aliceblue: '#F0F8FF',
  11303. antiquewhite: '#FAEBD7',
  11304. aquamarine: '#7FFFD4',
  11305. azure: '#F0FFFF',
  11306. beige: '#F5F5DC',
  11307. bisque: '#FFE4C4',
  11308. black: '#000000',
  11309. blanchedalmond: '#FFEBCD',
  11310. blueviolet: '#8A2BE2',
  11311. brown: '#A52A2A',
  11312. burlywood: '#DEB887',
  11313. cadetblue: '#5F9EA0',
  11314. chartreuse: '#7FFF00',
  11315. chocolate: '#D2691E',
  11316. coral: '#FF7F50',
  11317. cornflowerblue: '#6495ED',
  11318. cornsilk: '#FFF8DC',
  11319. crimson: '#DC143C',
  11320. cyan: '#00FFFF',
  11321. darkblue: '#00008B',
  11322. darkcyan: '#008B8B',
  11323. darkgoldenrod: '#B8860B',
  11324. darkgray: '#A9A9A9',
  11325. darkgreen: '#006400',
  11326. darkgrey: '#A9A9A9',
  11327. darkkhaki: '#BDB76B',
  11328. darkmagenta: '#8B008B',
  11329. darkolivegreen: '#556B2F',
  11330. darkorange: '#FF8C00',
  11331. darkorchid: '#9932CC',
  11332. darkred: '#8B0000',
  11333. darksalmon: '#E9967A',
  11334. darkseagreen: '#8FBC8F',
  11335. darkslateblue: '#483D8B',
  11336. darkslategray: '#2F4F4F',
  11337. darkslategrey: '#2F4F4F',
  11338. darkturquoise: '#00CED1',
  11339. darkviolet: '#9400D3',
  11340. deeppink: '#FF1493',
  11341. deepskyblue: '#00BFFF',
  11342. dimgray: '#696969',
  11343. dimgrey: '#696969',
  11344. dodgerblue: '#1E90FF',
  11345. firebrick: '#B22222',
  11346. floralwhite: '#FFFAF0',
  11347. forestgreen: '#228B22',
  11348. gainsboro: '#DCDCDC',
  11349. ghostwhite: '#F8F8FF',
  11350. gold: '#FFD700',
  11351. goldenrod: '#DAA520',
  11352. grey: '#808080',
  11353. greenyellow: '#ADFF2F',
  11354. honeydew: '#F0FFF0',
  11355. hotpink: '#FF69B4',
  11356. indianred: '#CD5C5C',
  11357. indigo: '#4B0082',
  11358. ivory: '#FFFFF0',
  11359. khaki: '#F0E68C',
  11360. lavender: '#E6E6FA',
  11361. lavenderblush: '#FFF0F5',
  11362. lawngreen: '#7CFC00',
  11363. lemonchiffon: '#FFFACD',
  11364. lightblue: '#ADD8E6',
  11365. lightcoral: '#F08080',
  11366. lightcyan: '#E0FFFF',
  11367. lightgoldenrodyellow: '#FAFAD2',
  11368. lightgreen: '#90EE90',
  11369. lightgrey: '#D3D3D3',
  11370. lightpink: '#FFB6C1',
  11371. lightsalmon: '#FFA07A',
  11372. lightseagreen: '#20B2AA',
  11373. lightskyblue: '#87CEFA',
  11374. lightslategray: '#778899',
  11375. lightslategrey: '#778899',
  11376. lightsteelblue: '#B0C4DE',
  11377. lightyellow: '#FFFFE0',
  11378. limegreen: '#32CD32',
  11379. linen: '#FAF0E6',
  11380. magenta: '#FF00FF',
  11381. mediumaquamarine: '#66CDAA',
  11382. mediumblue: '#0000CD',
  11383. mediumorchid: '#BA55D3',
  11384. mediumpurple: '#9370DB',
  11385. mediumseagreen: '#3CB371',
  11386. mediumslateblue: '#7B68EE',
  11387. mediumspringgreen: '#00FA9A',
  11388. mediumturquoise: '#48D1CC',
  11389. mediumvioletred: '#C71585',
  11390. midnightblue: '#191970',
  11391. mintcream: '#F5FFFA',
  11392. mistyrose: '#FFE4E1',
  11393. moccasin: '#FFE4B5',
  11394. navajowhite: '#FFDEAD',
  11395. oldlace: '#FDF5E6',
  11396. olivedrab: '#6B8E23',
  11397. orange: '#FFA500',
  11398. orangered: '#FF4500',
  11399. orchid: '#DA70D6',
  11400. palegoldenrod: '#EEE8AA',
  11401. palegreen: '#98FB98',
  11402. paleturquoise: '#AFEEEE',
  11403. palevioletred: '#DB7093',
  11404. papayawhip: '#FFEFD5',
  11405. peachpuff: '#FFDAB9',
  11406. peru: '#CD853F',
  11407. pink: '#FFC0CB',
  11408. plum: '#DDA0DD',
  11409. powderblue: '#B0E0E6',
  11410. rosybrown: '#BC8F8F',
  11411. royalblue: '#4169E1',
  11412. saddlebrown: '#8B4513',
  11413. salmon: '#FA8072',
  11414. sandybrown: '#F4A460',
  11415. seagreen: '#2E8B57',
  11416. seashell: '#FFF5EE',
  11417. sienna: '#A0522D',
  11418. skyblue: '#87CEEB',
  11419. slateblue: '#6A5ACD',
  11420. slategray: '#708090',
  11421. slategrey: '#708090',
  11422. snow: '#FFFAFA',
  11423. springgreen: '#00FF7F',
  11424. steelblue: '#4682B4',
  11425. tan: '#D2B48C',
  11426. thistle: '#D8BFD8',
  11427. tomato: '#FF6347',
  11428. turquoise: '#40E0D0',
  11429. violet: '#EE82EE',
  11430. wheat: '#F5DEB3',
  11431. whitesmoke: '#F5F5F5',
  11432. yellowgreen: '#9ACD32'
  11433. };
  11434. function getRgbHslContent(styleString) {
  11435. var start = styleString.indexOf('(', 3);
  11436. var end = styleString.indexOf(')', start + 1);
  11437. var parts = styleString.substring(start + 1, end).split(',');
  11438. // add alpha if needed
  11439. if (parts.length != 4 || styleString.charAt(3) != 'a') {
  11440. parts[3] = 1;
  11441. }
  11442. return parts;
  11443. }
  11444. function percent(s) {
  11445. return parseFloat(s) / 100;
  11446. }
  11447. function clamp(v, min, max) {
  11448. return Math.min(max, Math.max(min, v));
  11449. }
  11450. function hslToRgb(parts) {
  11451. var r, g, b, h, s, l;
  11452. h = parseFloat(parts[0]) / 360 % 360;
  11453. if (h < 0) {
  11454. h++;
  11455. }
  11456. s = clamp(percent(parts[1]), 0, 1);
  11457. l = clamp(percent(parts[2]), 0, 1);
  11458. if (s == 0) {
  11459. r = g = b = l;
  11460. } else // achromatic
  11461. {
  11462. var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  11463. var p = 2 * l - q;
  11464. r = hueToRgb(p, q, h + 1 / 3);
  11465. g = hueToRgb(p, q, h);
  11466. b = hueToRgb(p, q, h - 1 / 3);
  11467. }
  11468. return '#' + decToHex[Math.floor(r * 255)] + decToHex[Math.floor(g * 255)] + decToHex[Math.floor(b * 255)];
  11469. }
  11470. function hueToRgb(m1, m2, h) {
  11471. if (h < 0) {
  11472. h++;
  11473. }
  11474. if (h > 1) {
  11475. h--;
  11476. }
  11477. if (6 * h < 1) {
  11478. return m1 + (m2 - m1) * 6 * h;
  11479. }
  11480. else if (2 * h < 1) {
  11481. return m2;
  11482. }
  11483. else if (3 * h < 2) {
  11484. return m1 + (m2 - m1) * (2 / 3 - h) * 6;
  11485. }
  11486. else {
  11487. return m1;
  11488. }
  11489. }
  11490. var processStyleCache = {};
  11491. function processStyle(styleString) {
  11492. if (styleString in processStyleCache) {
  11493. return processStyleCache[styleString];
  11494. }
  11495. var str,
  11496. alpha = 1;
  11497. styleString = String(styleString);
  11498. if (styleString.charAt(0) == '#') {
  11499. str = styleString;
  11500. } else if (/^rgb/.test(styleString)) {
  11501. var parts = getRgbHslContent(styleString);
  11502. var str = '#',
  11503. n;
  11504. for (var i = 0; i < 3; i++) {
  11505. if (parts[i].indexOf('%') != -1) {
  11506. n = Math.floor(percent(parts[i]) * 255);
  11507. } else {
  11508. n = +parts[i];
  11509. }
  11510. str += decToHex[clamp(n, 0, 255)];
  11511. }
  11512. alpha = +parts[3];
  11513. } else if (/^hsl/.test(styleString)) {
  11514. var parts = getRgbHslContent(styleString);
  11515. str = hslToRgb(parts);
  11516. alpha = parts[3];
  11517. } else {
  11518. str = colorData[styleString] || styleString;
  11519. }
  11520. return processStyleCache[styleString] = {
  11521. color: str,
  11522. alpha: alpha
  11523. };
  11524. }
  11525. var DEFAULT_STYLE = {
  11526. style: 'normal',
  11527. variant: 'normal',
  11528. weight: 'normal',
  11529. size: 10,
  11530. family: 'sans-serif'
  11531. };
  11532. // Internal text style cache
  11533. var fontStyleCache = {};
  11534. function processFontStyle(styleString) {
  11535. if (fontStyleCache[styleString]) {
  11536. return fontStyleCache[styleString];
  11537. }
  11538. var el = document.createElement('div');
  11539. var style = el.style;
  11540. try {
  11541. style.font = styleString;
  11542. } catch (ex) {}
  11543. // Ignore failures to set to invalid font.
  11544. return fontStyleCache[styleString] = {
  11545. style: style.fontStyle || DEFAULT_STYLE.style,
  11546. variant: style.fontVariant || DEFAULT_STYLE.variant,
  11547. weight: style.fontWeight || DEFAULT_STYLE.weight,
  11548. size: style.fontSize || DEFAULT_STYLE.size,
  11549. family: style.fontFamily || DEFAULT_STYLE.family
  11550. };
  11551. }
  11552. function getComputedStyle(style, element) {
  11553. var computedStyle = {};
  11554. for (var p in style) {
  11555. computedStyle[p] = style[p];
  11556. }
  11557. // Compute the size
  11558. var canvasFontSize = parseFloat(element.currentStyle.fontSize),
  11559. fontSize = parseFloat(style.size);
  11560. if (typeof style.size == 'number') {
  11561. computedStyle.size = style.size;
  11562. } else if (style.size.indexOf('px') != -1) {
  11563. computedStyle.size = fontSize;
  11564. } else if (style.size.indexOf('em') != -1) {
  11565. computedStyle.size = canvasFontSize * fontSize;
  11566. } else if (style.size.indexOf('%') != -1) {
  11567. computedStyle.size = (canvasFontSize / 100) * fontSize;
  11568. } else if (style.size.indexOf('pt') != -1) {
  11569. computedStyle.size = fontSize / 0.75;
  11570. } else {
  11571. computedStyle.size = canvasFontSize;
  11572. }
  11573. // Different scaling between normal text and VML text. This was found using
  11574. // trial and error to get the same size as non VML text.
  11575. computedStyle.size *= 0.981;
  11576. return computedStyle;
  11577. }
  11578. function buildStyle(style) {
  11579. return style.style + ' ' + style.variant + ' ' + style.weight + ' ' + style.size + 'px ' + style.family;
  11580. }
  11581. var lineCapMap = {
  11582. 'butt': 'flat',
  11583. 'round': 'round'
  11584. };
  11585. function processLineCap(lineCap) {
  11586. return lineCapMap[lineCap] || 'square';
  11587. }
  11588. /*
  11589. * This class implements CanvasRenderingContext2D interface as described by
  11590. * the WHATWG.
  11591. * @param {HTMLElement} canvasElement The element that the 2D context should
  11592. * be associated with
  11593. * @private
  11594. */
  11595. function CanvasRenderingContext2D_(canvasElement) {
  11596. this.m_ = createMatrixIdentity();
  11597. this.mStack_ = [];
  11598. this.aStack_ = [];
  11599. this.currentPath_ = [];
  11600. // Canvas context properties
  11601. this.strokeStyle = '#000';
  11602. this.fillStyle = '#000';
  11603. this.lineWidth = 1;
  11604. this.lineJoin = 'miter';
  11605. this.lineDash = [];
  11606. this.lineCap = 'butt';
  11607. this.miterLimit = Z * 1;
  11608. this.globalAlpha = 1;
  11609. this.font = '10px sans-serif';
  11610. this.textAlign = 'left';
  11611. this.textBaseline = 'alphabetic';
  11612. this.canvas = canvasElement;
  11613. var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' + canvasElement.clientHeight + 'px;overflow:hidden;position:absolute';
  11614. var el = canvasElement.ownerDocument.createElement('div');
  11615. el.style.cssText = cssText;
  11616. canvasElement.appendChild(el);
  11617. var overlayEl = el.cloneNode(false);
  11618. // Use a non transparent background.
  11619. overlayEl.style.backgroundColor = 'red';
  11620. overlayEl.style.filter = 'alpha(opacity=0)';
  11621. canvasElement.appendChild(overlayEl);
  11622. this.element_ = el;
  11623. this.arcScaleX_ = 1;
  11624. this.arcScaleY_ = 1;
  11625. this.lineScale_ = 1;
  11626. }
  11627. var contextPrototype = CanvasRenderingContext2D_.prototype;
  11628. contextPrototype.clearRect = function() {
  11629. if (this.textMeasureEl_) {
  11630. this.textMeasureEl_.removeNode(true);
  11631. this.textMeasureEl_ = null;
  11632. }
  11633. this.element_.innerHTML = '';
  11634. };
  11635. contextPrototype.beginPath = function() {
  11636. // TODO: Branch current matrix so that save/restore has no effect
  11637. // as per safari docs.
  11638. this.currentPath_ = [];
  11639. };
  11640. contextPrototype.moveTo = function(aX, aY) {
  11641. var p = getCoords(this, aX, aY);
  11642. this.currentPath_.push({
  11643. type: 'moveTo',
  11644. x: p.x,
  11645. y: p.y
  11646. });
  11647. this.currentX_ = p.x;
  11648. this.currentY_ = p.y;
  11649. };
  11650. contextPrototype.lineTo = function(aX, aY) {
  11651. var p = getCoords(this, aX, aY);
  11652. this.currentPath_.push({
  11653. type: 'lineTo',
  11654. x: p.x,
  11655. y: p.y
  11656. });
  11657. this.currentX_ = p.x;
  11658. this.currentY_ = p.y;
  11659. };
  11660. contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) {
  11661. var p = getCoords(this, aX, aY);
  11662. var cp1 = getCoords(this, aCP1x, aCP1y);
  11663. var cp2 = getCoords(this, aCP2x, aCP2y);
  11664. bezierCurveTo(this, cp1, cp2, p);
  11665. };
  11666. // Helper function that takes the already fixed cordinates.
  11667. function bezierCurveTo(self, cp1, cp2, p) {
  11668. self.currentPath_.push({
  11669. type: 'bezierCurveTo',
  11670. cp1x: cp1.x,
  11671. cp1y: cp1.y,
  11672. cp2x: cp2.x,
  11673. cp2y: cp2.y,
  11674. x: p.x,
  11675. y: p.y
  11676. });
  11677. self.currentX_ = p.x;
  11678. self.currentY_ = p.y;
  11679. }
  11680. contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
  11681. // the following is lifted almost directly from
  11682. // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
  11683. var cp = getCoords(this, aCPx, aCPy);
  11684. var p = getCoords(this, aX, aY);
  11685. var cp1 = {
  11686. x: this.currentX_ + 2 / 3 * (cp.x - this.currentX_),
  11687. y: this.currentY_ + 2 / 3 * (cp.y - this.currentY_)
  11688. };
  11689. var cp2 = {
  11690. x: cp1.x + (p.x - this.currentX_) / 3,
  11691. y: cp1.y + (p.y - this.currentY_) / 3
  11692. };
  11693. bezierCurveTo(this, cp1, cp2, p);
  11694. };
  11695. contextPrototype.arc = function(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) {
  11696. aRadius *= Z;
  11697. var arcType = aClockwise ? 'at' : 'wa';
  11698. var xStart = aX + mc(aStartAngle) * aRadius - Z2;
  11699. var yStart = aY + ms(aStartAngle) * aRadius - Z2;
  11700. var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
  11701. var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
  11702. // IE won't render arches drawn counter clockwise if xStart == xEnd.
  11703. if (xStart == xEnd && !aClockwise) {
  11704. xStart += 0.125;
  11705. }
  11706. // Offset xStart by 1/80 of a pixel. Use something
  11707. // that can be represented in binary
  11708. var p = getCoords(this, aX, aY);
  11709. var pStart = getCoords(this, xStart, yStart);
  11710. var pEnd = getCoords(this, xEnd, yEnd);
  11711. this.currentPath_.push({
  11712. type: arcType,
  11713. x: p.x,
  11714. y: p.y,
  11715. radius: aRadius,
  11716. xStart: pStart.x,
  11717. yStart: pStart.y,
  11718. xEnd: pEnd.x,
  11719. yEnd: pEnd.y
  11720. });
  11721. };
  11722. contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
  11723. this.moveTo(aX, aY);
  11724. this.lineTo(aX + aWidth, aY);
  11725. this.lineTo(aX + aWidth, aY + aHeight);
  11726. this.lineTo(aX, aY + aHeight);
  11727. this.closePath();
  11728. };
  11729. contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
  11730. var oldPath = this.currentPath_;
  11731. this.beginPath();
  11732. this.moveTo(aX, aY);
  11733. this.lineTo(aX + aWidth, aY);
  11734. this.lineTo(aX + aWidth, aY + aHeight);
  11735. this.lineTo(aX, aY + aHeight);
  11736. this.closePath();
  11737. this.stroke();
  11738. this.currentPath_ = oldPath;
  11739. };
  11740. contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
  11741. var oldPath = this.currentPath_;
  11742. this.beginPath();
  11743. this.moveTo(aX, aY);
  11744. this.lineTo(aX + aWidth, aY);
  11745. this.lineTo(aX + aWidth, aY + aHeight);
  11746. this.lineTo(aX, aY + aHeight);
  11747. this.closePath();
  11748. this.fill();
  11749. this.currentPath_ = oldPath;
  11750. };
  11751. contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
  11752. var gradient = new CanvasGradient_('gradient');
  11753. gradient.x0_ = aX0;
  11754. gradient.y0_ = aY0;
  11755. gradient.x1_ = aX1;
  11756. gradient.y1_ = aY1;
  11757. return gradient;
  11758. };
  11759. contextPrototype.createRadialGradient = function(aX0, aY0, aR0, aX1, aY1, aR1) {
  11760. var gradient = new CanvasGradient_('gradientradial');
  11761. gradient.x0_ = aX0;
  11762. gradient.y0_ = aY0;
  11763. gradient.r0_ = aR0;
  11764. gradient.x1_ = aX1;
  11765. gradient.y1_ = aY1;
  11766. gradient.r1_ = aR1;
  11767. return gradient;
  11768. };
  11769. contextPrototype.drawImage = function(image, var_args) {
  11770. var dx, dy, dw, dh, sx, sy, sw, sh;
  11771. // to find the original width we overide the width and height
  11772. var oldRuntimeWidth = image.runtimeStyle.width;
  11773. var oldRuntimeHeight = image.runtimeStyle.height;
  11774. image.runtimeStyle.width = 'auto';
  11775. image.runtimeStyle.height = 'auto';
  11776. // get the original size
  11777. var w = image.width;
  11778. var h = image.height;
  11779. // and remove overides
  11780. image.runtimeStyle.width = oldRuntimeWidth;
  11781. image.runtimeStyle.height = oldRuntimeHeight;
  11782. if (arguments.length == 3) {
  11783. dx = arguments[1];
  11784. dy = arguments[2];
  11785. sx = sy = 0;
  11786. sw = dw = w;
  11787. sh = dh = h;
  11788. } else if (arguments.length == 5) {
  11789. dx = arguments[1];
  11790. dy = arguments[2];
  11791. dw = arguments[3];
  11792. dh = arguments[4];
  11793. sx = sy = 0;
  11794. sw = w;
  11795. sh = h;
  11796. } else if (arguments.length == 9) {
  11797. sx = arguments[1];
  11798. sy = arguments[2];
  11799. sw = arguments[3];
  11800. sh = arguments[4];
  11801. dx = arguments[5];
  11802. dy = arguments[6];
  11803. dw = arguments[7];
  11804. dh = arguments[8];
  11805. } else {
  11806. throw Error('Invalid number of arguments');
  11807. }
  11808. var d = getCoords(this, dx, dy);
  11809. var vmlStr = [];
  11810. var W = 10;
  11811. var H = 10;
  11812. var m = this.m_;
  11813. vmlStr.push(' <g_vml_:group', ' coordsize="', Z * W, ',', Z * H, '"', ' coordorigin="0,0"', ' style="width:', mr(W * m[0][0]), 'px;height:', mr(H * m[1][1]), 'px;position:absolute;', 'top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px; rotation:', mr(Math.atan(m[0][1] / m[1][1]) * 180 / Math.PI), ';');
  11814. vmlStr.push('" >', '<g_vml_:image src="', image.src, '"', ' style="width:', Z * dw, 'px;', ' height:', Z * dh, 'px"', ' cropleft="', sx / w, '"', ' croptop="', sy / h, '"', ' cropright="', (w - sx - sw) / w, '"', ' cropbottom="', (h - sy - sh) / h, '"', ' />', '</g_vml_:group>');
  11815. this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
  11816. };
  11817. contextPrototype.setLineDash = function(lineDash) {
  11818. if (lineDash.length === 1) {
  11819. lineDash = lineDash.slice();
  11820. lineDash[1] = lineDash[0];
  11821. }
  11822. this.lineDash = lineDash;
  11823. };
  11824. contextPrototype.getLineDash = function() {
  11825. return this.lineDash;
  11826. };
  11827. contextPrototype.stroke = function(aFill) {
  11828. var lineStr = [];
  11829. var W = 10;
  11830. var H = 10;
  11831. lineStr.push('<g_vml_:shape', ' filled="', !!aFill, '"', ' style="position:absolute;width:', W, 'px;height:', H, 'px;left:0px;top:0px;"', ' coordorigin="0,0"', ' coordsize="', Z * W, ',', Z * H, '"', ' stroked="', !aFill, '"', ' path="');
  11832. var min = {
  11833. x: null,
  11834. y: null
  11835. };
  11836. var max = {
  11837. x: null,
  11838. y: null
  11839. };
  11840. for (var i = 0; i < this.currentPath_.length; i++) {
  11841. var p = this.currentPath_[i];
  11842. var c;
  11843. switch (p.type) {
  11844. case 'moveTo':
  11845. c = p;
  11846. lineStr.push(' m ', mr(p.x), ',', mr(p.y));
  11847. break;
  11848. case 'lineTo':
  11849. lineStr.push(' l ', mr(p.x), ',', mr(p.y));
  11850. break;
  11851. case 'close':
  11852. lineStr.push(' x ');
  11853. p = null;
  11854. break;
  11855. case 'bezierCurveTo':
  11856. lineStr.push(' c ', mr(p.cp1x), ',', mr(p.cp1y), ',', mr(p.cp2x), ',', mr(p.cp2y), ',', mr(p.x), ',', mr(p.y));
  11857. break;
  11858. case 'at':
  11859. case 'wa':
  11860. lineStr.push(' ', p.type, ' ', mr(p.x - this.arcScaleX_ * p.radius), ',', mr(p.y - this.arcScaleY_ * p.radius), ' ', mr(p.x + this.arcScaleX_ * p.radius), ',', mr(p.y + this.arcScaleY_ * p.radius), ' ', mr(p.xStart), ',', mr(p.yStart), ' ', mr(p.xEnd), ',', mr(p.yEnd));
  11861. break;
  11862. }
  11863. // TODO: Following is broken for curves due to
  11864. // move to proper paths.
  11865. // Figure out dimensions so we can do gradient fills
  11866. // properly
  11867. if (p) {
  11868. if (min.x == null || p.x < min.x) {
  11869. min.x = p.x;
  11870. }
  11871. if (max.x == null || p.x > max.x) {
  11872. max.x = p.x;
  11873. }
  11874. if (min.y == null || p.y < min.y) {
  11875. min.y = p.y;
  11876. }
  11877. if (max.y == null || p.y > max.y) {
  11878. max.y = p.y;
  11879. }
  11880. }
  11881. }
  11882. lineStr.push(' ">');
  11883. if (!aFill) {
  11884. appendStroke(this, lineStr);
  11885. } else {
  11886. appendFill(this, lineStr, min, max);
  11887. }
  11888. lineStr.push('</g_vml_:shape>');
  11889. this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
  11890. };
  11891. function appendStroke(ctx, lineStr) {
  11892. var a = processStyle(ctx.strokeStyle);
  11893. var color = a.color;
  11894. var opacity = a.alpha * ctx.globalAlpha;
  11895. var lineWidth = ctx.lineScale_ * ctx.lineWidth;
  11896. // VML cannot correctly render a line if the width is less than 1px.
  11897. // In that case, we dilute the color to make the line look thinner.
  11898. if (lineWidth < 1) {
  11899. opacity *= lineWidth;
  11900. }
  11901. lineStr.push('<g_vml_:stroke', ' opacity="', opacity, '"', ' joinstyle="', ctx.lineJoin, '"', ' dashstyle="', ctx.lineDash.join(' '), '"', ' miterlimit="', ctx.miterLimit, '"', ' endcap="', processLineCap(ctx.lineCap), '"', ' weight="', lineWidth, 'px"', ' color="', color, '" />');
  11902. }
  11903. function appendFill(ctx, lineStr, min, max) {
  11904. var fillStyle = ctx.fillStyle;
  11905. var arcScaleX = ctx.arcScaleX_;
  11906. var arcScaleY = ctx.arcScaleY_;
  11907. var width = max.x - min.x;
  11908. var height = max.y - min.y;
  11909. if (fillStyle instanceof CanvasGradient_) {
  11910. // TODO: Gradients transformed with the transformation matrix.
  11911. var angle = 0;
  11912. var focus = {
  11913. x: 0,
  11914. y: 0
  11915. };
  11916. // additional offset
  11917. var shift = 0;
  11918. // scale factor for offset
  11919. var expansion = 1;
  11920. if (fillStyle.type_ == 'gradient') {
  11921. var x0 = fillStyle.x0_ / arcScaleX;
  11922. var y0 = fillStyle.y0_ / arcScaleY;
  11923. var x1 = fillStyle.x1_ / arcScaleX;
  11924. var y1 = fillStyle.y1_ / arcScaleY;
  11925. var p0 = getCoords(ctx, x0, y0);
  11926. var p1 = getCoords(ctx, x1, y1);
  11927. var dx = p1.x - p0.x;
  11928. var dy = p1.y - p0.y;
  11929. angle = Math.atan2(dx, dy) * 180 / Math.PI;
  11930. // The angle should be a non-negative number.
  11931. if (angle < 0) {
  11932. angle += 360;
  11933. }
  11934. // Very small angles produce an unexpected result because they are
  11935. // converted to a scientific notation string.
  11936. if (angle < 1.0E-6) {
  11937. angle = 0;
  11938. }
  11939. } else {
  11940. var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_);
  11941. focus = {
  11942. x: (p0.x - min.x) / width,
  11943. y: (p0.y - min.y) / height
  11944. };
  11945. width /= arcScaleX * Z;
  11946. height /= arcScaleY * Z;
  11947. var dimension = m.max(width, height);
  11948. shift = 2 * fillStyle.r0_ / dimension;
  11949. expansion = 2 * fillStyle.r1_ / dimension - shift;
  11950. }
  11951. // We need to sort the color stops in ascending order by offset,
  11952. // otherwise IE won't interpret it correctly.
  11953. var stops = fillStyle.colors_;
  11954. stops.sort(function(cs1, cs2) {
  11955. return cs1.offset - cs2.offset;
  11956. });
  11957. var length = stops.length;
  11958. var color1 = stops[0].color;
  11959. var color2 = stops[length - 1].color;
  11960. var opacity1 = stops[0].alpha * ctx.globalAlpha;
  11961. var opacity2 = stops[length - 1].alpha * ctx.globalAlpha;
  11962. var colors = [];
  11963. for (var i = 0; i < length; i++) {
  11964. var stop = stops[i];
  11965. colors.push(stop.offset * expansion + shift + ' ' + stop.color);
  11966. }
  11967. // When colors attribute is used, the meanings of opacity and o:opacity2
  11968. // are reversed.
  11969. lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"', ' method="none" focus="100%"', ' color="', color1, '"', ' color2="', color2, '"', ' colors="', colors.join(','), '"', ' opacity="', opacity2, '"', ' g_o_:opacity2="', opacity1, '"', ' angle="', angle, '"', ' focusposition="', focus.x, ',', focus.y, '" />');
  11970. } else if (fillStyle instanceof CanvasPattern_) {
  11971. if (width && height) {
  11972. var deltaLeft = -min.x;
  11973. var deltaTop = -min.y;
  11974. lineStr.push('<g_vml_:fill', ' position="', deltaLeft / width * arcScaleX * arcScaleX, ',', deltaTop / height * arcScaleY * arcScaleY, '"', ' type="tile"', // TODO: Figure out the correct size to fit the scale.
  11975. //' size="', w, 'px ', h, 'px"',
  11976. ' src="', fillStyle.src_, '" />');
  11977. }
  11978. } else {
  11979. var a = processStyle(ctx.fillStyle);
  11980. var color = a.color;
  11981. var opacity = a.alpha * ctx.globalAlpha;
  11982. lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />');
  11983. }
  11984. }
  11985. contextPrototype.fill = function() {
  11986. // Calling `$stroke` here because otherwise we'd call not the native ctx.stroke,
  11987. // but our override from Ext.draw.engine.Canvas.statics.contextOverrides.
  11988. this.$stroke(true);
  11989. };
  11990. contextPrototype.closePath = function() {
  11991. this.currentPath_.push({
  11992. type: 'close'
  11993. });
  11994. };
  11995. function getCoords(ctx, aX, aY) {
  11996. var m = ctx.m_;
  11997. return {
  11998. x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
  11999. y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
  12000. };
  12001. }
  12002. contextPrototype.save = function() {
  12003. var o = {};
  12004. copyState(this, o);
  12005. this.aStack_.push(o);
  12006. this.mStack_.push(this.m_);
  12007. };
  12008. contextPrototype.restore = function() {
  12009. if (this.aStack_.length) {
  12010. copyState(this.aStack_.pop(), this);
  12011. this.m_ = this.mStack_.pop();
  12012. }
  12013. };
  12014. function matrixIsFinite(m) {
  12015. return isFinite(m[0][0]) && isFinite(m[0][1]) && isFinite(m[1][0]) && isFinite(m[1][1]) && isFinite(m[2][0]) && isFinite(m[2][1]);
  12016. }
  12017. function setM(ctx, m, updateLineScale) {
  12018. if (!matrixIsFinite(m)) {
  12019. return;
  12020. }
  12021. ctx.m_ = m;
  12022. if (updateLineScale) {
  12023. // Get the line scale.
  12024. // Determinant of this.m_ means how much the area is enlarged by the
  12025. // transformation. So its square root can be used as a scale factor
  12026. // for width.
  12027. var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
  12028. ctx.lineScale_ = sqrt(abs(det));
  12029. }
  12030. }
  12031. contextPrototype.translate = function(aX, aY) {
  12032. var m1 = [
  12033. [
  12034. 1,
  12035. 0,
  12036. 0
  12037. ],
  12038. [
  12039. 0,
  12040. 1,
  12041. 0
  12042. ],
  12043. [
  12044. aX,
  12045. aY,
  12046. 1
  12047. ]
  12048. ];
  12049. setM(this, matrixMultiply(m1, this.m_), false);
  12050. };
  12051. contextPrototype.rotate = function(aRot) {
  12052. var c = mc(aRot);
  12053. var s = ms(aRot);
  12054. var m1 = [
  12055. [
  12056. c,
  12057. s,
  12058. 0
  12059. ],
  12060. [
  12061. -s,
  12062. c,
  12063. 0
  12064. ],
  12065. [
  12066. 0,
  12067. 0,
  12068. 1
  12069. ]
  12070. ];
  12071. setM(this, matrixMultiply(m1, this.m_), false);
  12072. };
  12073. contextPrototype.scale = function(aX, aY) {
  12074. this.arcScaleX_ *= aX;
  12075. this.arcScaleY_ *= aY;
  12076. var m1 = [
  12077. [
  12078. aX,
  12079. 0,
  12080. 0
  12081. ],
  12082. [
  12083. 0,
  12084. aY,
  12085. 0
  12086. ],
  12087. [
  12088. 0,
  12089. 0,
  12090. 1
  12091. ]
  12092. ];
  12093. setM(this, matrixMultiply(m1, this.m_), true);
  12094. };
  12095. contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
  12096. var m1 = [
  12097. [
  12098. m11,
  12099. m12,
  12100. 0
  12101. ],
  12102. [
  12103. m21,
  12104. m22,
  12105. 0
  12106. ],
  12107. [
  12108. dx,
  12109. dy,
  12110. 1
  12111. ]
  12112. ];
  12113. setM(this, matrixMultiply(m1, this.m_), true);
  12114. };
  12115. contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
  12116. var m = [
  12117. [
  12118. m11,
  12119. m12,
  12120. 0
  12121. ],
  12122. [
  12123. m21,
  12124. m22,
  12125. 0
  12126. ],
  12127. [
  12128. dx,
  12129. dy,
  12130. 1
  12131. ]
  12132. ];
  12133. setM(this, m, true);
  12134. };
  12135. /*
  12136. * The text drawing function.
  12137. * The maxWidth argument isn't taken in account, since no browser supports
  12138. * it yet.
  12139. */
  12140. contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
  12141. var m = this.m_,
  12142. delta = 1000,
  12143. left = 0,
  12144. right = delta,
  12145. offset = {
  12146. x: 0,
  12147. y: 0
  12148. },
  12149. lineStr = [];
  12150. var fontStyle = getComputedStyle(processFontStyle(this.font), this.element_);
  12151. var fontStyleString = buildStyle(fontStyle);
  12152. var elementStyle = this.element_.currentStyle;
  12153. var textAlign = this.textAlign.toLowerCase();
  12154. switch (textAlign) {
  12155. case 'left':
  12156. case 'center':
  12157. case 'right':
  12158. break;
  12159. case 'end':
  12160. textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
  12161. break;
  12162. case 'start':
  12163. textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
  12164. break;
  12165. default:
  12166. textAlign = 'left';
  12167. }
  12168. // 1.75 is an arbitrary number, as there is no info about the text baseline
  12169. switch (this.textBaseline) {
  12170. case 'hanging':
  12171. case 'top':
  12172. offset.y = fontStyle.size / 1.75;
  12173. break;
  12174. case 'middle':
  12175. break;
  12176. default:
  12177. case null:
  12178. case 'alphabetic':
  12179. case 'ideographic':
  12180. case 'bottom':
  12181. offset.y = -fontStyle.size / 3;
  12182. break;
  12183. }
  12184. switch (textAlign) {
  12185. case 'right':
  12186. left = delta;
  12187. right = 0.05;
  12188. break;
  12189. case 'center':
  12190. left = right = delta / 2;
  12191. break;
  12192. }
  12193. var d = getCoords(this, x + offset.x, y + offset.y);
  12194. lineStr.push('<g_vml_:line from="', -left, ' 0" to="', right, ' 0.05" ', ' coordsize="100 100" coordorigin="0 0"', ' filled="', !stroke, '" stroked="', !!stroke, '" style="position:absolute;width:1px;height:1px;left:0px;top:0px;">');
  12195. if (stroke) {
  12196. appendStroke(this, lineStr);
  12197. } else {
  12198. // TODO: Fix the min and max params.
  12199. appendFill(this, lineStr, {
  12200. x: -left,
  12201. y: 0
  12202. }, {
  12203. x: right,
  12204. y: fontStyle.size
  12205. });
  12206. }
  12207. var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' + m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0';
  12208. var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z);
  12209. lineStr.push('<g_vml_:skew on="t" matrix="', skewM, '" ', ' offset="', skewOffset, '" origin="', left, ' 0" />', '<g_vml_:path textpathok="true" />', '<g_vml_:textpath on="true" string="', encodeHtmlAttribute(text), '" style="v-text-align:', textAlign, ';font:', encodeHtmlAttribute(fontStyleString), '" /></g_vml_:line>');
  12210. this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
  12211. };
  12212. contextPrototype.fillText = function(text, x, y, maxWidth) {
  12213. this.drawText_(text, x, y, maxWidth, false);
  12214. };
  12215. contextPrototype.strokeText = function(text, x, y, maxWidth) {
  12216. this.drawText_(text, x, y, maxWidth, true);
  12217. };
  12218. contextPrototype.measureText = function(text) {
  12219. if (!this.textMeasureEl_) {
  12220. var s = '<span style="position:absolute;' + 'top:-20000px;left:0;padding:0;margin:0;border:none;' + 'white-space:pre;"></span>';
  12221. this.element_.insertAdjacentHTML('beforeEnd', s);
  12222. this.textMeasureEl_ = this.element_.lastChild;
  12223. }
  12224. var doc = this.element_.ownerDocument;
  12225. this.textMeasureEl_.innerHTML = '';
  12226. this.textMeasureEl_.style.font = this.font;
  12227. // Don't use innerHTML or innerText because they allow markup/whitespace.
  12228. this.textMeasureEl_.appendChild(doc.createTextNode(text));
  12229. return {
  12230. width: this.textMeasureEl_.offsetWidth
  12231. };
  12232. };
  12233. /* STUBS */
  12234. contextPrototype.clip = function() {};
  12235. // TODO: Implement
  12236. contextPrototype.arcTo = function() {};
  12237. // TODO: Implement
  12238. contextPrototype.createPattern = function(image, repetition) {
  12239. return new CanvasPattern_(image, repetition);
  12240. };
  12241. // Gradient / Pattern Stubs
  12242. function CanvasGradient_(aType) {
  12243. this.type_ = aType;
  12244. this.x0_ = 0;
  12245. this.y0_ = 0;
  12246. this.r0_ = 0;
  12247. this.x1_ = 0;
  12248. this.y1_ = 0;
  12249. this.r1_ = 0;
  12250. this.colors_ = [];
  12251. }
  12252. CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
  12253. aColor = processStyle(aColor);
  12254. this.colors_.push({
  12255. offset: aOffset,
  12256. color: aColor.color,
  12257. alpha: aColor.alpha
  12258. });
  12259. };
  12260. function CanvasPattern_(image, repetition) {
  12261. assertImageIsValid(image);
  12262. switch (repetition) {
  12263. case 'repeat':
  12264. case null:
  12265. case '':
  12266. this.repetition_ = 'repeat';
  12267. break;
  12268. case 'repeat-x':
  12269. case 'repeat-y':
  12270. case 'no-repeat':
  12271. this.repetition_ = repetition;
  12272. break;
  12273. default:
  12274. throwException('SYNTAX_ERR');
  12275. }
  12276. this.src_ = image.src;
  12277. this.width_ = image.width;
  12278. this.height_ = image.height;
  12279. }
  12280. function throwException(s) {
  12281. throw new DOMException_(s);
  12282. }
  12283. function assertImageIsValid(img) {
  12284. if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
  12285. throwException('TYPE_MISMATCH_ERR');
  12286. }
  12287. if (img.readyState != 'complete') {
  12288. throwException('INVALID_STATE_ERR');
  12289. }
  12290. }
  12291. function DOMException_(s) {
  12292. this.code = this[s];
  12293. this.message = s + ': DOM Exception ' + this.code;
  12294. }
  12295. var p = DOMException_.prototype = new Error();
  12296. p.INDEX_SIZE_ERR = 1;
  12297. p.DOMSTRING_SIZE_ERR = 2;
  12298. p.HIERARCHY_REQUEST_ERR = 3;
  12299. p.WRONG_DOCUMENT_ERR = 4;
  12300. p.INVALID_CHARACTER_ERR = 5;
  12301. p.NO_DATA_ALLOWED_ERR = 6;
  12302. p.NO_MODIFICATION_ALLOWED_ERR = 7;
  12303. p.NOT_FOUND_ERR = 8;
  12304. p.NOT_SUPPORTED_ERR = 9;
  12305. p.INUSE_ATTRIBUTE_ERR = 10;
  12306. p.INVALID_STATE_ERR = 11;
  12307. p.SYNTAX_ERR = 12;
  12308. p.INVALID_MODIFICATION_ERR = 13;
  12309. p.NAMESPACE_ERR = 14;
  12310. p.INVALID_ACCESS_ERR = 15;
  12311. p.VALIDATION_ERR = 16;
  12312. p.TYPE_MISMATCH_ERR = 17;
  12313. // set up externs
  12314. G_vmlCanvasManager = G_vmlCanvasManager_;
  12315. CanvasRenderingContext2D = CanvasRenderingContext2D_;
  12316. CanvasGradient = CanvasGradient_;
  12317. CanvasPattern = CanvasPattern_;
  12318. DOMException = DOMException_;
  12319. })();
  12320. }
  12321. // if
  12322. /**
  12323. * Provides specific methods to draw with 2D Canvas element.
  12324. */
  12325. Ext.define('Ext.draw.engine.Canvas', {
  12326. extend: 'Ext.draw.Surface',
  12327. isCanvas: true,
  12328. requires: [
  12329. //<feature legacyBrowser>
  12330. 'Ext.draw.engine.excanvas',
  12331. //</feature>
  12332. 'Ext.draw.Animator',
  12333. 'Ext.draw.Color'
  12334. ],
  12335. config: {
  12336. /**
  12337. * @cfg {Boolean} highPrecision
  12338. * True to have the Canvas use JavaScript Number instead of single precision floating point for transforms.
  12339. *
  12340. * For example, when using data with big numbers to plot line series, the transformation
  12341. * matrix of the canvas will have big elements. Due to the implementation of the SVGMatrix,
  12342. * the elements are represented by 32-bits floats, which will work incorrectly.
  12343. * To compensate for that, we enable the canvas context to perform all the transformations
  12344. * in JavaScript.
  12345. *
  12346. * Do not use this if you are not encountering 32-bit floating point errors problem,
  12347. * since this will result in a performance penalty.
  12348. */
  12349. highPrecision: false
  12350. },
  12351. statics: {
  12352. contextOverrides: {
  12353. /**
  12354. * @ignore
  12355. */
  12356. setGradientBBox: function(bbox) {
  12357. this.bbox = bbox;
  12358. },
  12359. /**
  12360. * Fills the subpaths of the current default path or the given path with the current fill style.
  12361. * @ignore
  12362. */
  12363. fill: function() {
  12364. var fillStyle = this.fillStyle,
  12365. fillGradient = this.fillGradient,
  12366. fillOpacity = this.fillOpacity,
  12367. alpha = this.globalAlpha,
  12368. bbox = this.bbox;
  12369. if (fillStyle !== Ext.util.Color.RGBA_NONE && fillOpacity !== 0) {
  12370. if (fillGradient && bbox) {
  12371. this.fillStyle = fillGradient.generateGradient(this, bbox);
  12372. }
  12373. if (fillOpacity !== 1) {
  12374. this.globalAlpha = alpha * fillOpacity;
  12375. }
  12376. this.$fill();
  12377. if (fillOpacity !== 1) {
  12378. this.globalAlpha = alpha;
  12379. }
  12380. if (fillGradient && bbox) {
  12381. this.fillStyle = fillStyle;
  12382. }
  12383. }
  12384. },
  12385. /**
  12386. * Strokes the subpaths of the current default path or the given path with the current stroke style.
  12387. * @ignore
  12388. */
  12389. stroke: function() {
  12390. var strokeStyle = this.strokeStyle,
  12391. strokeGradient = this.strokeGradient,
  12392. strokeOpacity = this.strokeOpacity,
  12393. alpha = this.globalAlpha,
  12394. bbox = this.bbox;
  12395. if (strokeStyle !== Ext.util.Color.RGBA_NONE && strokeOpacity !== 0) {
  12396. if (strokeGradient && bbox) {
  12397. this.strokeStyle = strokeGradient.generateGradient(this, bbox);
  12398. }
  12399. if (strokeOpacity !== 1) {
  12400. this.globalAlpha = alpha * strokeOpacity;
  12401. }
  12402. this.$stroke();
  12403. if (strokeOpacity !== 1) {
  12404. this.globalAlpha = alpha;
  12405. }
  12406. if (strokeGradient && bbox) {
  12407. this.strokeStyle = strokeStyle;
  12408. }
  12409. }
  12410. },
  12411. /**
  12412. * @ignore
  12413. */
  12414. fillStroke: function(attr, transformFillStroke) {
  12415. var ctx = this,
  12416. fillStyle = this.fillStyle,
  12417. fillOpacity = this.fillOpacity,
  12418. strokeStyle = this.strokeStyle,
  12419. strokeOpacity = this.strokeOpacity,
  12420. shadowColor = ctx.shadowColor,
  12421. shadowBlur = ctx.shadowBlur,
  12422. none = Ext.util.Color.RGBA_NONE;
  12423. if (transformFillStroke === undefined) {
  12424. transformFillStroke = attr.transformFillStroke;
  12425. }
  12426. if (!transformFillStroke) {
  12427. attr.inverseMatrix.toContext(ctx);
  12428. }
  12429. if (fillStyle !== none && fillOpacity !== 0) {
  12430. ctx.fill();
  12431. ctx.shadowColor = none;
  12432. ctx.shadowBlur = 0;
  12433. }
  12434. if (strokeStyle !== none && strokeOpacity !== 0) {
  12435. ctx.stroke();
  12436. }
  12437. ctx.shadowColor = shadowColor;
  12438. ctx.shadowBlur = shadowBlur;
  12439. },
  12440. /**
  12441. * 2D Canvas context in IE (up to IE10, inclusive) doesn't support
  12442. * the setLineDash method and the lineDashOffset property.
  12443. * @param dashList An even number of non-negative numbers specifying a dash list.
  12444. */
  12445. setLineDash: function(dashList) {
  12446. if (this.$setLineDash) {
  12447. this.$setLineDash(dashList);
  12448. }
  12449. },
  12450. getLineDash: function() {
  12451. if (this.$getLineDash) {
  12452. return this.$getLineDash();
  12453. }
  12454. },
  12455. /**
  12456. * Adds points to the subpath such that the arc described by the circumference of the
  12457. * ellipse described by the arguments, starting at the given start angle and ending at
  12458. * the given end angle, going in the given direction (defaulting to clockwise), is added
  12459. * to the path, connected to the previous point by a straight line.
  12460. * @ignore
  12461. */
  12462. ellipse: function(cx, cy, rx, ry, rotation, start, end, anticlockwise) {
  12463. var cos = Math.cos(rotation),
  12464. sin = Math.sin(rotation);
  12465. this.transform(cos * rx, sin * rx, -sin * ry, cos * ry, cx, cy);
  12466. this.arc(0, 0, 1, start, end, anticlockwise);
  12467. this.transform(cos / rx, -sin / ry, sin / rx, cos / ry, -(cos * cx + sin * cy) / rx, (sin * cx - cos * cy) / ry);
  12468. },
  12469. /**
  12470. * Uses the given path commands to begin a new path on the canvas.
  12471. * @ignore
  12472. */
  12473. appendPath: function(path) {
  12474. var me = this,
  12475. i = 0,
  12476. j = 0,
  12477. commands = path.commands,
  12478. params = path.params,
  12479. ln = commands.length;
  12480. me.beginPath();
  12481. for (; i < ln; i++) {
  12482. switch (commands[i]) {
  12483. case 'M':
  12484. me.moveTo(params[j], params[j + 1]);
  12485. j += 2;
  12486. break;
  12487. case 'L':
  12488. me.lineTo(params[j], params[j + 1]);
  12489. j += 2;
  12490. break;
  12491. case 'C':
  12492. me.bezierCurveTo(params[j], params[j + 1], params[j + 2], params[j + 3], params[j + 4], params[j + 5]);
  12493. j += 6;
  12494. break;
  12495. case 'Z':
  12496. me.closePath();
  12497. break;
  12498. }
  12499. }
  12500. },
  12501. save: function() {
  12502. var toSave = this.toSave,
  12503. ln = toSave.length,
  12504. obj = ln && {},
  12505. // Don't allocate memory if we don't have to.
  12506. i = 0,
  12507. key;
  12508. for (; i < ln; i++) {
  12509. key = toSave[i];
  12510. if (key in this) {
  12511. obj[key] = this[key];
  12512. }
  12513. }
  12514. this.state.push(obj);
  12515. this.$save();
  12516. },
  12517. restore: function() {
  12518. var obj = this.state.pop(),
  12519. key;
  12520. if (obj) {
  12521. for (key in obj) {
  12522. this[key] = obj[key];
  12523. }
  12524. }
  12525. this.$restore();
  12526. }
  12527. }
  12528. },
  12529. splitThreshold: 3000,
  12530. /**
  12531. * @private
  12532. * Properties to be saved/restored in the `save` and `restore` methods.
  12533. */
  12534. toSave: [
  12535. 'fillGradient',
  12536. 'strokeGradient'
  12537. ],
  12538. /**
  12539. * @property element
  12540. * @inheritdoc
  12541. */
  12542. element: {
  12543. reference: 'element',
  12544. children: [
  12545. {
  12546. reference: 'bodyElement',
  12547. style: {
  12548. width: '100%',
  12549. height: '100%',
  12550. position: 'relative'
  12551. }
  12552. }
  12553. ]
  12554. },
  12555. /**
  12556. * @private
  12557. *
  12558. * Creates the canvas element.
  12559. */
  12560. createCanvas: function() {
  12561. var canvas = Ext.Element.create({
  12562. tag: 'canvas',
  12563. cls: Ext.baseCSSPrefix + 'surface-canvas'
  12564. });
  12565. // Emulate Canvas in IE8 with VML.
  12566. if (window['G_vmlCanvasManager']) {
  12567. G_vmlCanvasManager.initElement(canvas.dom);
  12568. this.isVML = true;
  12569. }
  12570. var overrides = Ext.draw.engine.Canvas.contextOverrides,
  12571. ctx = canvas.dom.getContext('2d'),
  12572. name;
  12573. if (ctx.ellipse) {
  12574. delete overrides.ellipse;
  12575. }
  12576. ctx.state = [];
  12577. ctx.toSave = this.toSave;
  12578. // Saving references to the native Canvas context methods that we'll be overriding.
  12579. for (name in overrides) {
  12580. ctx['$' + name] = ctx[name];
  12581. }
  12582. Ext.apply(ctx, overrides);
  12583. if (this.getHighPrecision()) {
  12584. this.enablePrecisionCompensation(ctx);
  12585. } else {
  12586. this.disablePrecisionCompensation(ctx);
  12587. }
  12588. this.bodyElement.appendChild(canvas);
  12589. this.canvases.push(canvas);
  12590. this.contexts.push(ctx);
  12591. },
  12592. updateHighPrecision: function(highPrecision) {
  12593. var contexts = this.contexts,
  12594. ln = contexts.length,
  12595. i, context;
  12596. for (i = 0; i < ln; i++) {
  12597. context = contexts[i];
  12598. if (highPrecision) {
  12599. this.enablePrecisionCompensation(context);
  12600. } else {
  12601. this.disablePrecisionCompensation(context);
  12602. }
  12603. }
  12604. },
  12605. precisionNames: [
  12606. 'rect',
  12607. 'fillRect',
  12608. 'strokeRect',
  12609. 'clearRect',
  12610. 'moveTo',
  12611. 'lineTo',
  12612. 'arc',
  12613. 'arcTo',
  12614. 'save',
  12615. 'restore',
  12616. 'updatePrecisionCompensate',
  12617. 'setTransform',
  12618. 'transform',
  12619. 'scale',
  12620. 'translate',
  12621. 'rotate',
  12622. 'quadraticCurveTo',
  12623. 'bezierCurveTo',
  12624. 'createLinearGradient',
  12625. 'createRadialGradient',
  12626. 'fillText',
  12627. 'strokeText',
  12628. 'drawImage'
  12629. ],
  12630. /**
  12631. * @private
  12632. * Clears canvas of compensation for canvas' use of single precision floating point.
  12633. * @param {CanvasRenderingContext2D} ctx The canvas context.
  12634. */
  12635. disablePrecisionCompensation: function(ctx) {
  12636. var regularOverrides = Ext.draw.engine.Canvas.contextOverrides,
  12637. precisionOverrides = this.precisionNames,
  12638. ln = precisionOverrides.length,
  12639. i, name;
  12640. for (i = 0; i < ln; i++) {
  12641. name = precisionOverrides[i];
  12642. if (!(name in regularOverrides)) {
  12643. delete ctx[name];
  12644. }
  12645. }
  12646. this.setDirty(true);
  12647. },
  12648. /**
  12649. * @private
  12650. * Compensate for canvas' use of single precision floating point.
  12651. * @param {CanvasRenderingContext2D} ctx The canvas context.
  12652. */
  12653. enablePrecisionCompensation: function(ctx) {
  12654. var surface = this,
  12655. xx = 1,
  12656. yy = 1,
  12657. dx = 0,
  12658. dy = 0,
  12659. matrix = new Ext.draw.Matrix(),
  12660. transStack = [],
  12661. comp = {},
  12662. regularOverrides = Ext.draw.engine.Canvas.contextOverrides,
  12663. originalCtx = ctx.constructor.prototype;
  12664. /**
  12665. * @cfg {Object} precisionOverrides
  12666. * @ignore
  12667. */
  12668. var precisionOverrides = {
  12669. toSave: surface.toSave,
  12670. /**
  12671. * Adds a new closed subpath to the path, representing the given rectangle.
  12672. * @return {*}
  12673. * @ignore
  12674. */
  12675. rect: function(x, y, w, h) {
  12676. return originalCtx.rect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
  12677. },
  12678. /**
  12679. * Paints the given rectangle onto the canvas, using the current fill style.
  12680. * @ignore
  12681. */
  12682. fillRect: function(x, y, w, h) {
  12683. this.updatePrecisionCompensateRect();
  12684. originalCtx.fillRect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
  12685. this.updatePrecisionCompensate();
  12686. },
  12687. /**
  12688. * Paints the box that outlines the given rectangle onto the canvas, using the current stroke style.
  12689. * @ignore
  12690. */
  12691. strokeRect: function(x, y, w, h) {
  12692. this.updatePrecisionCompensateRect();
  12693. originalCtx.strokeRect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
  12694. this.updatePrecisionCompensate();
  12695. },
  12696. /**
  12697. * Clears all pixels on the canvas in the given rectangle to transparent black.
  12698. * @ignore
  12699. */
  12700. clearRect: function(x, y, w, h) {
  12701. return originalCtx.clearRect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
  12702. },
  12703. /**
  12704. * Creates a new subpath with the given point.
  12705. * @ignore
  12706. */
  12707. moveTo: function(x, y) {
  12708. return originalCtx.moveTo.call(this, x * xx + dx, y * yy + dy);
  12709. },
  12710. /**
  12711. * Adds the given point to the current subpath, connected to the previous one by a straight line.
  12712. * @ignore
  12713. */
  12714. lineTo: function(x, y) {
  12715. return originalCtx.lineTo.call(this, x * xx + dx, y * yy + dy);
  12716. },
  12717. /**
  12718. * Adds points to the subpath such that the arc described by the circumference of the
  12719. * circle described by the arguments, starting at the given start angle and ending at
  12720. * the given end angle, going in the given direction (defaulting to clockwise), is added
  12721. * to the path, connected to the previous point by a straight line.
  12722. * @ignore
  12723. */
  12724. arc: function(x, y, radius, startAngle, endAngle, anticlockwise) {
  12725. this.updatePrecisionCompensateRect();
  12726. originalCtx.arc.call(this, x * xx + dx, y * xx + dy, radius * xx, startAngle, endAngle, anticlockwise);
  12727. this.updatePrecisionCompensate();
  12728. },
  12729. /**
  12730. * Adds an arc with the given control points and radius to the current subpath,
  12731. * connected to the previous point by a straight line. If two radii are provided, the
  12732. * first controls the width of the arc's ellipse, and the second controls the height. If
  12733. * only one is provided, or if they are the same, the arc is from a circle.
  12734. *
  12735. * In the case of an ellipse, the rotation argument controls the clockwise inclination
  12736. * of the ellipse relative to the x-axis.
  12737. * @ignore
  12738. */
  12739. arcTo: function(x1, y1, x2, y2, radius) {
  12740. this.updatePrecisionCompensateRect();
  12741. originalCtx.arcTo.call(this, x1 * xx + dx, y1 * yy + dy, x2 * xx + dx, y2 * yy + dy, radius * xx);
  12742. this.updatePrecisionCompensate();
  12743. },
  12744. /**
  12745. * Pushes the context state to the state stack.
  12746. * @ignore
  12747. */
  12748. save: function() {
  12749. transStack.push(matrix);
  12750. matrix = matrix.clone();
  12751. regularOverrides.save.call(this);
  12752. originalCtx.save.call(this);
  12753. },
  12754. /**
  12755. * Pops the state stack and restores the state.
  12756. * @ignore
  12757. */
  12758. restore: function() {
  12759. matrix = transStack.pop();
  12760. regularOverrides.restore.call(this);
  12761. originalCtx.restore.call(this);
  12762. this.updatePrecisionCompensate();
  12763. },
  12764. /**
  12765. * @ignore
  12766. */
  12767. updatePrecisionCompensate: function() {
  12768. matrix.precisionCompensate(surface.devicePixelRatio, comp);
  12769. xx = comp.xx;
  12770. yy = comp.yy;
  12771. dx = comp.dx;
  12772. dy = comp.dy;
  12773. originalCtx.setTransform.call(this, surface.devicePixelRatio, comp.b, comp.c, comp.d, 0, 0);
  12774. },
  12775. /**
  12776. * @ignore
  12777. */
  12778. updatePrecisionCompensateRect: function() {
  12779. matrix.precisionCompensateRect(surface.devicePixelRatio, comp);
  12780. xx = comp.xx;
  12781. yy = comp.yy;
  12782. dx = comp.dx;
  12783. dy = comp.dy;
  12784. originalCtx.setTransform.call(this, surface.devicePixelRatio, comp.b, comp.c, comp.d, 0, 0);
  12785. },
  12786. /**
  12787. * Changes the transformation matrix to the matrix given by the arguments as described below.
  12788. * @ignore
  12789. */
  12790. setTransform: function(x2x, x2y, y2x, y2y, newDx, newDy) {
  12791. matrix.set(x2x, x2y, y2x, y2y, newDx, newDy);
  12792. this.updatePrecisionCompensate();
  12793. },
  12794. /**
  12795. * Changes the transformation matrix to apply the matrix given by the arguments as described below.
  12796. * @ignore
  12797. */
  12798. transform: function(x2x, x2y, y2x, y2y, newDx, newDy) {
  12799. matrix.append(x2x, x2y, y2x, y2y, newDx, newDy);
  12800. this.updatePrecisionCompensate();
  12801. },
  12802. /**
  12803. * Scales the transformation matrix.
  12804. * @return {*}
  12805. * @ignore
  12806. */
  12807. scale: function(sx, sy) {
  12808. this.transform(sx, 0, 0, sy, 0, 0);
  12809. },
  12810. /**
  12811. * Translates the transformation matrix.
  12812. * @return {*}
  12813. * @ignore
  12814. */
  12815. translate: function(dx, dy) {
  12816. this.transform(1, 0, 0, 1, dx, dy);
  12817. },
  12818. /**
  12819. * Rotates the transformation matrix.
  12820. * @return {*}
  12821. * @ignore
  12822. */
  12823. rotate: function(radians) {
  12824. var cos = Math.cos(radians),
  12825. sin = Math.sin(radians);
  12826. this.transform(cos, sin, -sin, cos, 0, 0);
  12827. },
  12828. /**
  12829. * Adds the given point to the current subpath, connected to the previous one by a
  12830. * quadratic Bézier curve with the given control point.
  12831. * @return {*}
  12832. * @ignore
  12833. */
  12834. quadraticCurveTo: function(cx, cy, x, y) {
  12835. originalCtx.quadraticCurveTo.call(this, cx * xx + dx, cy * yy + dy, x * xx + dx, y * yy + dy);
  12836. },
  12837. /**
  12838. * Adds the given point to the current subpath, connected to the previous one by a cubic
  12839. * Bézier curve with the given control points.
  12840. * @return {*}
  12841. * @ignore
  12842. */
  12843. bezierCurveTo: function(c1x, c1y, c2x, c2y, x, y) {
  12844. originalCtx.bezierCurveTo.call(this, c1x * xx + dx, c1y * yy + dy, c2x * xx + dx, c2y * yy + dy, x * xx + dx, y * yy + dy);
  12845. },
  12846. /**
  12847. * Returns an object that represents a linear gradient that paints along the line given
  12848. * by the coordinates represented by the arguments.
  12849. * @return {*}
  12850. * @ignore
  12851. */
  12852. createLinearGradient: function(x0, y0, x1, y1) {
  12853. this.updatePrecisionCompensateRect();
  12854. var grad = originalCtx.createLinearGradient.call(this, x0 * xx + dx, y0 * yy + dy, x1 * xx + dx, y1 * yy + dy);
  12855. this.updatePrecisionCompensate();
  12856. return grad;
  12857. },
  12858. /**
  12859. * Returns a CanvasGradient object that represents a radial gradient that paints along
  12860. * the cone given by the circles represented by the arguments. If either of the radii
  12861. * are negative, throws an IndexSizeError exception.
  12862. * @return {*}
  12863. * @ignore
  12864. */
  12865. createRadialGradient: function(x0, y0, r0, x1, y1, r1) {
  12866. this.updatePrecisionCompensateRect();
  12867. var grad = originalCtx.createLinearGradient.call(this, x0 * xx + dx, y0 * xx + dy, r0 * xx, x1 * xx + dx, y1 * xx + dy, r1 * xx);
  12868. this.updatePrecisionCompensate();
  12869. return grad;
  12870. },
  12871. /**
  12872. * Fills the given text at the given position. If a maximum width is provided, the text
  12873. * will be scaled to fit that width if necessary.
  12874. * @ignore
  12875. */
  12876. fillText: function(text, x, y, maxWidth) {
  12877. originalCtx.setTransform.apply(this, matrix.elements);
  12878. if (typeof maxWidth === 'undefined') {
  12879. originalCtx.fillText.call(this, text, x, y);
  12880. } else {
  12881. originalCtx.fillText.call(this, text, x, y, maxWidth);
  12882. }
  12883. this.updatePrecisionCompensate();
  12884. },
  12885. /**
  12886. * Strokes the given text at the given position. If a
  12887. * maximum width is provided, the text will be scaled to
  12888. * fit that width if necessary.
  12889. * @ignore
  12890. */
  12891. strokeText: function(text, x, y, maxWidth) {
  12892. originalCtx.setTransform.apply(this, matrix.elements);
  12893. if (typeof maxWidth === 'undefined') {
  12894. originalCtx.strokeText.call(this, text, x, y);
  12895. } else {
  12896. originalCtx.strokeText.call(this, text, x, y, maxWidth);
  12897. }
  12898. this.updatePrecisionCompensate();
  12899. },
  12900. /**
  12901. * Fills the subpaths of the current default path or the given path with the current fill style.
  12902. * @ignore
  12903. */
  12904. fill: function() {
  12905. var fillGradient = this.fillGradient,
  12906. bbox = this.bbox;
  12907. this.updatePrecisionCompensateRect();
  12908. if (fillGradient && bbox) {
  12909. this.fillStyle = fillGradient.generateGradient(this, bbox);
  12910. }
  12911. originalCtx.fill.call(this);
  12912. this.updatePrecisionCompensate();
  12913. },
  12914. /**
  12915. * Strokes the subpaths of the current default path or the given path with the current stroke style.
  12916. * @ignore
  12917. */
  12918. stroke: function() {
  12919. var strokeGradient = this.strokeGradient,
  12920. bbox = this.bbox;
  12921. this.updatePrecisionCompensateRect();
  12922. if (strokeGradient && bbox) {
  12923. this.strokeStyle = strokeGradient.generateGradient(this, bbox);
  12924. }
  12925. originalCtx.stroke.call(this);
  12926. this.updatePrecisionCompensate();
  12927. },
  12928. /**
  12929. * Draws the given image onto the canvas. If the first argument isn't an img, canvas,
  12930. * or video element, throws a TypeMismatchError exception. If the image has no image
  12931. * data, throws an InvalidStateError exception. If the one of the source rectangle
  12932. * dimensions is zero, throws an IndexSizeError exception. If the image isn't yet fully
  12933. * decoded, then nothing is drawn.
  12934. * @return {*}
  12935. * @ignore
  12936. */
  12937. drawImage: function(img_elem, arg1, arg2, arg3, arg4, dst_x, dst_y, dw, dh) {
  12938. switch (arguments.length) {
  12939. case 3:
  12940. return originalCtx.drawImage.call(this, img_elem, arg1 * xx + dx, arg2 * yy + dy);
  12941. case 5:
  12942. return originalCtx.drawImage.call(this, img_elem, arg1 * xx + dx, arg2 * yy + dy, arg3 * xx, arg4 * yy);
  12943. case 9:
  12944. return originalCtx.drawImage.call(this, img_elem, arg1, arg2, arg3, arg4, dst_x * xx + dx, dst_y * yy * dy, dw * xx, dh * yy);
  12945. }
  12946. }
  12947. };
  12948. Ext.apply(ctx, precisionOverrides);
  12949. this.setDirty(true);
  12950. },
  12951. /**
  12952. * Normally, a surface will have a single canvas.
  12953. * However, on certain platforms/browsers there's a limit to how big a canvas can be.
  12954. * 'splitThreshold' is used to determine maximum width/height of a single canvas element.
  12955. * When a surface is wider/taller than the splitThreshold, extra canvas element(s)
  12956. * will be created and tiled inside the surface.
  12957. */
  12958. updateRect: function(rect) {
  12959. this.callParent([
  12960. rect
  12961. ]);
  12962. var me = this,
  12963. l = Math.floor(rect[0]),
  12964. t = Math.floor(rect[1]),
  12965. r = Math.ceil(rect[0] + rect[2]),
  12966. b = Math.ceil(rect[1] + rect[3]),
  12967. devicePixelRatio = me.devicePixelRatio,
  12968. canvases = me.canvases,
  12969. w = r - l,
  12970. h = b - t,
  12971. splitThreshold = Math.round(me.splitThreshold / devicePixelRatio),
  12972. xSplits = me.xSplits = Math.ceil(w / splitThreshold),
  12973. ySplits = me.ySplits = Math.ceil(h / splitThreshold),
  12974. i, j, k, offsetX, offsetY, dom, width, height;
  12975. for (j = 0 , offsetY = 0; j < ySplits; j++ , offsetY += splitThreshold) {
  12976. for (i = 0 , offsetX = 0; i < xSplits; i++ , offsetX += splitThreshold) {
  12977. k = j * xSplits + i;
  12978. if (k >= canvases.length) {
  12979. me.createCanvas();
  12980. }
  12981. dom = canvases[k].dom;
  12982. dom.style.left = offsetX + 'px';
  12983. dom.style.top = offsetY + 'px';
  12984. // The Canvas doesn't automatically support hi-DPI displays.
  12985. // We have to actually create a larger canvas (more pixels)
  12986. // while keeping its physical size the same.
  12987. height = Math.min(splitThreshold, h - offsetY);
  12988. if (height * devicePixelRatio !== dom.height) {
  12989. dom.height = height * devicePixelRatio;
  12990. dom.style.height = height + 'px';
  12991. }
  12992. width = Math.min(splitThreshold, w - offsetX);
  12993. if (width * devicePixelRatio !== dom.width) {
  12994. dom.width = width * devicePixelRatio;
  12995. dom.style.width = width + 'px';
  12996. }
  12997. me.applyDefaults(me.contexts[k]);
  12998. }
  12999. }
  13000. me.activeCanvases = k = xSplits * ySplits;
  13001. while (canvases.length > k) {
  13002. canvases.pop().destroy();
  13003. }
  13004. me.clear();
  13005. },
  13006. /**
  13007. * @method clearTransform
  13008. * @inheritdoc
  13009. */
  13010. clearTransform: function() {
  13011. var me = this,
  13012. xSplits = me.xSplits,
  13013. ySplits = me.ySplits,
  13014. contexts = me.contexts,
  13015. splitThreshold = me.splitThreshold,
  13016. devicePixelRatio = me.devicePixelRatio,
  13017. i, j, k, ctx;
  13018. for (i = 0; i < xSplits; i++) {
  13019. for (j = 0; j < ySplits; j++) {
  13020. k = j * xSplits + i;
  13021. ctx = contexts[k];
  13022. ctx.translate(-splitThreshold * i, -splitThreshold * j);
  13023. ctx.scale(devicePixelRatio, devicePixelRatio);
  13024. me.matrix.toContext(ctx);
  13025. }
  13026. }
  13027. },
  13028. /**
  13029. * @method renderSprite
  13030. * @inheritdoc
  13031. */
  13032. renderSprite: function(sprite) {
  13033. var me = this,
  13034. rect = me.getRect(),
  13035. surfaceMatrix = me.matrix,
  13036. parent = sprite.getParent(),
  13037. matrix = Ext.draw.Matrix.fly([
  13038. 1,
  13039. 0,
  13040. 0,
  13041. 1,
  13042. 0,
  13043. 0
  13044. ]),
  13045. splitThreshold = me.splitThreshold / me.devicePixelRatio,
  13046. xSplits = me.xSplits,
  13047. ySplits = me.ySplits,
  13048. offsetX, offsetY, ctx, bbox, width, height,
  13049. left = 0,
  13050. right,
  13051. top = 0,
  13052. bottom,
  13053. w = rect[2],
  13054. h = rect[3],
  13055. i, j, k;
  13056. while (parent && parent.isSprite) {
  13057. matrix.prependMatrix(parent.matrix || parent.attr && parent.attr.matrix);
  13058. parent = parent.getParent();
  13059. }
  13060. matrix.prependMatrix(surfaceMatrix);
  13061. bbox = sprite.getBBox();
  13062. if (bbox) {
  13063. bbox = matrix.transformBBox(bbox);
  13064. }
  13065. sprite.preRender(me);
  13066. if (sprite.attr.hidden || sprite.attr.globalAlpha === 0) {
  13067. sprite.setDirty(false);
  13068. return;
  13069. }
  13070. // Render this sprite on all Canvas elements it spans, skipping the rest.
  13071. for (j = 0 , offsetY = 0; j < ySplits; j++ , offsetY += splitThreshold) {
  13072. for (i = 0 , offsetX = 0; i < xSplits; i++ , offsetX += splitThreshold) {
  13073. k = j * xSplits + i;
  13074. ctx = me.contexts[k];
  13075. width = Math.min(splitThreshold, w - offsetX);
  13076. height = Math.min(splitThreshold, h - offsetY);
  13077. left = offsetX;
  13078. right = left + width;
  13079. top = offsetY;
  13080. bottom = top + height;
  13081. if (bbox) {
  13082. if (bbox.x > right || bbox.x + bbox.width < left || bbox.y > bottom || bbox.y + bbox.height < top) {
  13083. continue;
  13084. }
  13085. }
  13086. ctx.save();
  13087. sprite.useAttributes(ctx, rect);
  13088. if (false === sprite.render(me, ctx, [
  13089. left,
  13090. top,
  13091. width,
  13092. height
  13093. ])) {
  13094. return false;
  13095. }
  13096. ctx.restore();
  13097. }
  13098. }
  13099. sprite.setDirty(false);
  13100. },
  13101. flatten: function(size, surfaces) {
  13102. var targetCanvas = document.createElement('canvas'),
  13103. className = Ext.getClassName(this),
  13104. ratio = this.devicePixelRatio,
  13105. ctx = targetCanvas.getContext('2d'),
  13106. surface, canvas, rect, i, j, xy;
  13107. targetCanvas.width = Math.ceil(size.width * ratio);
  13108. targetCanvas.height = Math.ceil(size.height * ratio);
  13109. for (i = 0; i < surfaces.length; i++) {
  13110. surface = surfaces[i];
  13111. if (Ext.getClassName(surface) !== className) {
  13112. continue;
  13113. }
  13114. rect = surface.getRect();
  13115. for (j = 0; j < surface.canvases.length; j++) {
  13116. canvas = surface.canvases[j];
  13117. xy = canvas.getOffsetsTo(canvas.getParent());
  13118. ctx.drawImage(canvas.dom, (rect[0] + xy[0]) * ratio, (rect[1] + xy[1]) * ratio);
  13119. }
  13120. }
  13121. return {
  13122. data: targetCanvas.toDataURL(),
  13123. type: 'png'
  13124. };
  13125. },
  13126. applyDefaults: function(ctx) {
  13127. var none = Ext.util.Color.RGBA_NONE;
  13128. ctx.strokeStyle = none;
  13129. ctx.fillStyle = none;
  13130. ctx.textAlign = 'start';
  13131. ctx.textBaseline = 'alphabetic';
  13132. ctx.miterLimit = 1;
  13133. },
  13134. /**
  13135. * @method clear
  13136. * @inheritdoc
  13137. */
  13138. clear: function() {
  13139. var me = this,
  13140. activeCanvases = me.activeCanvases,
  13141. i, canvas, ctx;
  13142. for (i = 0; i < activeCanvases; i++) {
  13143. canvas = me.canvases[i].dom;
  13144. ctx = me.contexts[i];
  13145. ctx.setTransform(1, 0, 0, 1, 0, 0);
  13146. ctx.clearRect(0, 0, canvas.width, canvas.height);
  13147. }
  13148. me.setDirty(true);
  13149. },
  13150. /**
  13151. * Destroys the Canvas element and prepares it for Garbage Collection.
  13152. */
  13153. destroy: function() {
  13154. var me = this,
  13155. canvases = me.canvases,
  13156. ln = canvases.length,
  13157. i;
  13158. for (i = 0; i < ln; i++) {
  13159. me.contexts[i] = null;
  13160. canvases[i].destroy();
  13161. canvases[i] = null;
  13162. }
  13163. me.contexts = me.canvases = null;
  13164. me.callParent();
  13165. },
  13166. privates: {
  13167. initElement: function() {
  13168. var me = this;
  13169. me.callParent();
  13170. me.canvases = [];
  13171. me.contexts = [];
  13172. me.activeCanvases = me.xSplits = me.ySplits = 0;
  13173. }
  13174. }
  13175. }, function() {
  13176. var me = this,
  13177. proto = me.prototype,
  13178. splitThreshold = 1.0E10;
  13179. if (Ext.os.is.Android4 && Ext.browser.is.Chrome) {
  13180. splitThreshold = 3000;
  13181. } else if (Ext.is.iOS) {
  13182. splitThreshold = 2200;
  13183. }
  13184. proto.splitThreshold = splitThreshold;
  13185. });
  13186. /**
  13187. * The container that holds and manages instances of the {@link Ext.draw.Surface}
  13188. * in which {@link Ext.draw.sprite.Sprite sprites} are rendered. Draw containers are
  13189. * used as the foundation for all of the chart classes but may also be created directly
  13190. * in order to create custom drawings.
  13191. *
  13192. * @example
  13193. * var drawContainer = Ext.create('Ext.draw.Container', {
  13194. * renderTo: Ext.getBody(),
  13195. * width:200,
  13196. * height:200,
  13197. * sprites: [{
  13198. * type: 'circle',
  13199. * fillStyle: '#79BB3F',
  13200. * r: 100,
  13201. * x: 100,
  13202. * y: 100
  13203. * }]
  13204. * });
  13205. *
  13206. * // Uncomment to trigger a download of the painted circle.
  13207. * // drawContainer.download({
  13208. * // filename: 'Circle',
  13209. * // url: 'http://svg.sencha.io' // Default server the image data is sent to.
  13210. * // });
  13211. *
  13212. * In the previous example we created a draw container and configured it with a single
  13213. * sprite. The *type* of the sprite is {@link Ext.draw.sprite.Circle circle}, so if you
  13214. * run this code you'll see a green circle.
  13215. *
  13216. * You can attach sprite event listeners to the draw container with the help of the
  13217. * {@link Ext.draw.plugin.SpriteEvents} plugin.
  13218. *
  13219. * For more information on sprites, the core elements added to a draw container's
  13220. * surface, refer to the Ext.draw.sprite.Sprite documentation.
  13221. *
  13222. * For more information on surfaces, the interface owned by the draw container used to
  13223. * manage all sprites, see the Ext.draw.Surface documentation.
  13224. */
  13225. Ext.define('Ext.draw.Container', {
  13226. extend: 'Ext.draw.ContainerBase',
  13227. alternateClassName: 'Ext.draw.Component',
  13228. xtype: 'draw',
  13229. defaultType: 'surface',
  13230. isDrawContainer: true,
  13231. requires: [
  13232. 'Ext.draw.Surface',
  13233. 'Ext.draw.engine.Svg',
  13234. 'Ext.draw.engine.Canvas',
  13235. 'Ext.draw.gradient.GradientDefinition'
  13236. ],
  13237. /**
  13238. * @cfg {String} [engine="Ext.draw.engine.Canvas"]
  13239. * Defines the engine (type of surface) used to render draw container contents.
  13240. *
  13241. * The render engine is selected automatically depending on the platform used. Priority
  13242. * is given to the {@link Ext.draw.engine.Canvas} engine due to its performance advantage.
  13243. *
  13244. * You may also set the engine config to be `Ext.draw.engine.Svg` if so desired.
  13245. */
  13246. engine: 'Ext.draw.engine.Canvas',
  13247. /**
  13248. * @event spritemousemove
  13249. * Fires when the mouse is moved on a sprite.
  13250. * @param {Object} sprite
  13251. * @param {Event} event
  13252. */
  13253. /**
  13254. * @event spritemouseup
  13255. * Fires when a mouseup event occurs on a sprite.
  13256. * @param {Object} sprite
  13257. * @param {Event} event
  13258. */
  13259. /**
  13260. * @event spritemousedown
  13261. * Fires when a mousedown event occurs on a sprite.
  13262. * @param {Object} sprite
  13263. * @param {Event} event
  13264. */
  13265. /**
  13266. * @event spritemouseover
  13267. * Fires when the mouse enters a sprite.
  13268. * @param {Object} sprite
  13269. * @param {Event} event
  13270. */
  13271. /**
  13272. * @event spritemouseout
  13273. * Fires when the mouse exits a sprite.
  13274. * @param {Object} sprite
  13275. * @param {Event} event
  13276. */
  13277. /**
  13278. * @event spriteclick
  13279. * Fires when a click event occurs on a sprite.
  13280. * @param {Object} sprite
  13281. * @param {Event} event
  13282. */
  13283. /**
  13284. * @event spritedblclick
  13285. * Fires when a double click event occurs on a sprite.
  13286. * @param {Object} sprite
  13287. * @param {Event} event
  13288. */
  13289. /**
  13290. * @event spritetap
  13291. * Fires when a tap event occurs on a sprite.
  13292. * @param {Object} sprite
  13293. * @param {Event} event
  13294. */
  13295. /**
  13296. * @event bodyresize
  13297. * Fires when the size of the draw container body changes.
  13298. * @param {Object} size The object containing 'width' and 'height' of the draw container's body.
  13299. */
  13300. config: {
  13301. cls: [
  13302. Ext.baseCSSPrefix + 'draw-container',
  13303. Ext.baseCSSPrefix + 'unselectable'
  13304. ],
  13305. /**
  13306. * @cfg {Function} [resizeHandler]
  13307. * The resize function that can be configured to have a behavior,
  13308. * e.g. resize draw surfaces based on new draw container dimensions.
  13309. * The `resizeHandler` function takes a single parameter -
  13310. * the size object with `width` and `height` properties.
  13311. *
  13312. * **Note:** Since resize events trigger {@link #renderFrame} calls automatically,
  13313. * return `false` from the resize function, if it also calls `renderFrame`,
  13314. * to prevent double rendering.
  13315. */
  13316. resizeHandler: null,
  13317. /**
  13318. * @cfg {Object[]} sprites
  13319. * Defines a set of sprites to be added to the drawContainer surface.
  13320. *
  13321. * For example:
  13322. *
  13323. * sprites: [{
  13324. * type: 'circle',
  13325. * fillStyle: '#79BB3F',
  13326. * r: 100,
  13327. * x: 100,
  13328. * y: 100
  13329. * }]
  13330. *
  13331. */
  13332. sprites: null,
  13333. /**
  13334. * @cfg {Object[]} gradients
  13335. * Defines a set of gradients that can be used as color properties
  13336. * (fillStyle and strokeStyle, but not shadowColor) in sprites.
  13337. * The gradients array is an array of objects with the following properties:
  13338. * - **id** - string - The unique name of the gradient.
  13339. * - **type** - string, optional - The type of the gradient. Available types are: 'linear', 'radial'. Defaults to 'linear'.
  13340. * - **angle** - number, optional - The angle of the gradient in degrees.
  13341. * - **stops** - array - An array of objects with 'color' and 'offset' properties, where 'offset' is a real number from 0 to 1.
  13342. *
  13343. * For example:
  13344. *
  13345. * gradients: [{
  13346. * id: 'gradientId1',
  13347. * type: 'linear',
  13348. * angle: 45,
  13349. * stops: [{
  13350. * offset: 0,
  13351. * color: 'red'
  13352. * }, {
  13353. * offset: 1,
  13354. * color: 'yellow'
  13355. * }]
  13356. * }, {
  13357. * id: 'gradientId2',
  13358. * type: 'radial',
  13359. * stops: [{
  13360. * offset: 0,
  13361. * color: '#555',
  13362. * }, {
  13363. * offset: 1,
  13364. * color: '#ddd',
  13365. * }]
  13366. * }]
  13367. *
  13368. * Then the sprites can use 'gradientId1' and 'gradientId2' by setting the color attributes to those ids, for example:
  13369. *
  13370. * sprite.setAttributes({
  13371. * fillStyle: 'url(#gradientId1)',
  13372. * strokeStyle: 'url(#gradientId2)'
  13373. * });
  13374. */
  13375. gradients: [],
  13376. /**
  13377. * @cfg {String} downloadServerUrl
  13378. * The default URL used by the {@link #download} method.
  13379. */
  13380. downloadServerUrl: undefined,
  13381. touchAction: {
  13382. panX: false,
  13383. panY: false,
  13384. pinchZoom: false,
  13385. doubleTapZoom: false
  13386. },
  13387. /**
  13388. * @private
  13389. * @cfg {Object} surfaceZIndexes A map of surface type name to zIndex.
  13390. * The z-indexes to use for the various types of surfaces.
  13391. */
  13392. surfaceZIndexes: {
  13393. main: 1
  13394. }
  13395. },
  13396. /**
  13397. * @private
  13398. * @property {String} [defaultDownloadServerUrl="http://svg.sencha.io"]
  13399. * The default URL used by the {@link #download} method if the {@link #downloadServerUrl}
  13400. * config wasn't set.
  13401. * To override this globally, set the `Ext.draw.Container.prototype.defaultDownloadServerUrl`.
  13402. */
  13403. defaultDownloadServerUrl: 'http://svg.sencha.io',
  13404. /**
  13405. * @property {Array} [supportedFormats=["png", "pdf", "jpeg", "gif"]]
  13406. * A list of export types supported by the server.
  13407. * @private
  13408. */
  13409. supportedFormats: [
  13410. 'png',
  13411. 'pdf',
  13412. 'jpeg',
  13413. 'gif'
  13414. ],
  13415. supportedOptions: {
  13416. version: Ext.isNumber,
  13417. data: Ext.isString,
  13418. format: function(format) {
  13419. return Ext.Array.indexOf(this.supportedFormats, format) >= 0;
  13420. },
  13421. filename: Ext.isString,
  13422. width: Ext.isNumber,
  13423. height: Ext.isNumber,
  13424. scale: Ext.isNumber,
  13425. pdf: Ext.isObject,
  13426. jpeg: Ext.isObject
  13427. },
  13428. initAnimator: function() {
  13429. this.frameCallbackId = Ext.draw.Animator.addFrameCallback('renderFrame', this);
  13430. },
  13431. applyDownloadServerUrl: function(url) {
  13432. var defaultUrl = this.defaultDownloadServerUrl;
  13433. if (!url) {
  13434. url = defaultUrl;
  13435. //<debug>
  13436. // Skip this warning when unit testing.
  13437. if (!window.jasmine) {
  13438. Ext.log.warn('Using Sencha\'s download server could expose your data and pose a security risk. ' + 'Please see Ext.draw.Container#download method docs for more info. (component id=' + this.getId() + ')');
  13439. }
  13440. }
  13441. //</debug>
  13442. return url;
  13443. },
  13444. applyGradients: function(gradients) {
  13445. var result = [],
  13446. i, n, gradient, offset;
  13447. if (!Ext.isArray(gradients)) {
  13448. return result;
  13449. }
  13450. for (i = 0 , n = gradients.length; i < n; i++) {
  13451. gradient = gradients[i];
  13452. if (!Ext.isObject(gradient)) {
  13453. continue;
  13454. }
  13455. // ExtJS only supported linear gradients, so we didn't have to specify their type
  13456. if (typeof gradient.type !== 'string') {
  13457. gradient.type = 'linear';
  13458. }
  13459. if (gradient.angle) {
  13460. gradient.degrees = gradient.angle;
  13461. delete gradient.angle;
  13462. }
  13463. // Convert ExtJS stops object to Touch stops array
  13464. if (Ext.isObject(gradient.stops)) {
  13465. gradient.stops = (function(stops) {
  13466. var result = [],
  13467. stop;
  13468. for (offset in stops) {
  13469. stop = stops[offset];
  13470. stop.offset = offset / 100;
  13471. result.push(stop);
  13472. }
  13473. return result;
  13474. })(gradient.stops);
  13475. }
  13476. result.push(gradient);
  13477. }
  13478. Ext.draw.gradient.GradientDefinition.add(result);
  13479. return result;
  13480. },
  13481. applySprites: function(sprites) {
  13482. // Never update.
  13483. if (!sprites) {
  13484. return;
  13485. }
  13486. sprites = Ext.Array.from(sprites);
  13487. var ln = sprites.length,
  13488. result = [],
  13489. i, surface, sprite;
  13490. for (i = 0; i < ln; i++) {
  13491. sprite = sprites[i];
  13492. surface = sprite.surface;
  13493. if (!(surface && surface.isSurface)) {
  13494. if (Ext.isString(surface)) {
  13495. surface = this.getSurface(surface);
  13496. delete sprite.surface;
  13497. } else {
  13498. surface = this.getSurface('main');
  13499. }
  13500. }
  13501. sprite = surface.add(sprite);
  13502. result.push(sprite);
  13503. }
  13504. return result;
  13505. },
  13506. resizeDelay: 500,
  13507. // in milliseconds
  13508. resizeTimerId: 0,
  13509. lastResizeTime: null,
  13510. /**
  13511. * @private
  13512. * @property
  13513. * Last valid size.
  13514. */
  13515. size: null,
  13516. /**
  13517. * Triggers the {@link #resizeHandler} with the size of the draw container
  13518. * element as the parameter.
  13519. */
  13520. handleResize: function(size, instantly) {
  13521. // See the following:
  13522. // Classic: Ext.draw.ContainerBase.reattachToBody
  13523. // Modern: Ext.draw.ContainerBase.initialize
  13524. var me = this,
  13525. el = me.element,
  13526. resizeHandler = me.getResizeHandler() || me.defaultResizeHandler,
  13527. resizeDelay = me.resizeDelay,
  13528. lastResizeTime = me.lastResizeTime,
  13529. defer, result;
  13530. if (!el) {
  13531. return;
  13532. }
  13533. size = size || el.getSize();
  13534. if (!(size.width && size.height)) {
  13535. return;
  13536. }
  13537. me.size = size;
  13538. me.stopResizeTimer();
  13539. // Only want to defer when multiple resize events happen in quick succession.
  13540. // That way it doesn't feel luggy during an occasional resize, nor it's too straining
  13541. // when continuously resizing.
  13542. defer = !instantly && lastResizeTime && (Ext.Date.now() - lastResizeTime < resizeDelay);
  13543. if (defer) {
  13544. me.resizeTimerId = Ext.defer(me.handleResize, resizeDelay, me, [
  13545. size,
  13546. true
  13547. ]);
  13548. return;
  13549. }
  13550. me.fireEvent('bodyresize', me, size);
  13551. Ext.callback(resizeHandler, null, [
  13552. size
  13553. ], 0, me);
  13554. if (result !== false) {
  13555. me.renderFrame();
  13556. }
  13557. me.lastResizeTime = Ext.Date.now();
  13558. },
  13559. /**
  13560. * @private
  13561. */
  13562. stopResizeTimer: function() {
  13563. if (this.resizeTimerId) {
  13564. Ext.undefer(this.resizeTimerId);
  13565. this.resizeTimerId = 0;
  13566. }
  13567. },
  13568. defaultResizeHandler: function(size) {
  13569. this.getItems().each(function(surface) {
  13570. surface.setRect([
  13571. 0,
  13572. 0,
  13573. size.width,
  13574. size.height
  13575. ]);
  13576. });
  13577. },
  13578. /**
  13579. * Get a surface by the given id or create one if it doesn't exist.
  13580. * This will automatically call the {@link #resizeHandler}. Which
  13581. * means that, if no custom resize handler has been provided, the
  13582. * surface will be sized to match the container.
  13583. * If the {@link #method!add} method is used, it is the responsibility
  13584. * of the user to call the {@link #handleResize} method, to update
  13585. * the size of all added surfaces.
  13586. * @param {String} [id="main"]
  13587. * @param {String} type
  13588. * @return {Ext.draw.Surface}
  13589. */
  13590. getSurface: function(id, type) {
  13591. id = id || 'main';
  13592. type = type || id;
  13593. var me = this,
  13594. surfaces = me.getItems(),
  13595. oldCount = surfaces.getCount(),
  13596. zIndexes = me.getSurfaceZIndexes(),
  13597. surface;
  13598. surface = me.createSurface(id);
  13599. if (type in zIndexes) {
  13600. surface.element.setStyle('zIndex', zIndexes[type]);
  13601. }
  13602. if (surfaces.getCount() > oldCount) {
  13603. // Immediately call resize handler of the draw container,
  13604. // so that the newly created surface gets a size.
  13605. me.handleResize(null, true);
  13606. }
  13607. return surface;
  13608. },
  13609. createSurface: function(id) {
  13610. id = this.getId() + '-' + (id || 'main');
  13611. var me = this,
  13612. surfaces = me.getItems(),
  13613. surface = surfaces.get(id);
  13614. if (!surface) {
  13615. surface = me.add({
  13616. xclass: me.engine,
  13617. id: id
  13618. });
  13619. }
  13620. return surface;
  13621. },
  13622. /**
  13623. * Render all the surfaces in the container.
  13624. */
  13625. renderFrame: function() {
  13626. var me = this,
  13627. surfaces = me.getItems(),
  13628. i, ln, item;
  13629. for (i = 0 , ln = surfaces.length; i < ln; i++) {
  13630. item = surfaces.items[i];
  13631. if (item.isSurface) {
  13632. item.renderFrame();
  13633. }
  13634. }
  13635. },
  13636. /**
  13637. * @private
  13638. * Returns a slice of the surfaces (items) array of the draw container,
  13639. * optionally sorting them by zIndex.
  13640. * Overridden in subclasses.
  13641. */
  13642. getSurfaces: function(sort) {
  13643. var surfaces = Array.prototype.slice.call(this.items.items),
  13644. zIndexes = this.getSurfaceZIndexes(),
  13645. i, j, surface, zIndex;
  13646. if (sort) {
  13647. // Sort the surfaces by zIndex using insertion sort.
  13648. for (j = 1; j < surfaces.length; j++) {
  13649. surface = surfaces[j];
  13650. zIndex = zIndexes[surface.type];
  13651. i = j - 1;
  13652. while (i >= 0 && zIndexes[surfaces[i].type] > zIndex) {
  13653. surfaces[i + 1] = surfaces[i];
  13654. i--;
  13655. }
  13656. surfaces[i + 1] = surface;
  13657. }
  13658. }
  13659. return surfaces;
  13660. },
  13661. /**
  13662. * Produces an image of the chart / drawing.
  13663. * @param {String} [format] Possible options are 'image' (the method will return an
  13664. * Image object) and 'stream' (the method will return the image as a byte stream).
  13665. * If missing, the data URI of the drawing's (or chart's) image will be returned.
  13666. * Note: for an SVG based drawing/chart in IE/Edge browsers the method will always
  13667. * return SVG markup instead of a data URI, as 'img' elements won't accept a data
  13668. * URI anyway in those browsers.
  13669. * @return {Object}
  13670. * @return {String} return.data Image element, byte stream or DataURL.
  13671. * @return {String} return.type The type of the data (e.g. 'png' or 'svg').
  13672. */
  13673. getImage: function(format) {
  13674. var size = this.bodyElement.getSize(),
  13675. surfaces = this.getSurfaces(true),
  13676. surface = surfaces[0],
  13677. image, imageElement;
  13678. if ((Ext.isIE || Ext.isEdge) && surface.isSVG) {
  13679. // SVG data URLs don't work in IE/Edge as a source for an 'img' element,
  13680. // so we need to render SVG the usual way.
  13681. image = {
  13682. data: surface.toSVG(size, surfaces),
  13683. type: 'svg-markup'
  13684. };
  13685. } else {
  13686. image = surface.flatten(size, surfaces);
  13687. if (format === 'image') {
  13688. imageElement = new Image();
  13689. imageElement.src = image.data;
  13690. image.data = imageElement;
  13691. return image;
  13692. }
  13693. if (format === 'stream') {
  13694. image.data = image.data.replace(/^data:image\/[^;]+/, 'data:application/octet-stream');
  13695. return image;
  13696. }
  13697. }
  13698. return image;
  13699. },
  13700. /**
  13701. * Downloads an image or PDF of the chart / drawing or opens it in a separate
  13702. * browser tab/window if the download can't be triggered. The exact behavior is
  13703. * platform and browser specific. For more consistent results on mobile devices use
  13704. * the {@link #preview} method instead. This method doesn't work in IE8.
  13705. *
  13706. * Important: The default download mechanism sends image data to `http://svg.sencha.io`,
  13707. * which is a server operated by Sencha. This can be changed by setting
  13708. * the {@link #downloadServerUrl} config to the address of another server.
  13709. *
  13710. * You can deploy your own server by using the code from the `server` directory
  13711. * in the Charts package. The server is Node.js based and uses PhantomJS to
  13712. * generate images and PDFs from received data.
  13713. *
  13714. * The warning that the default download server is used can be suppressed
  13715. * by explicitly setting the value of the {@link #downloadServerUrl} config
  13716. * to `http://svg.sencha.io`.
  13717. *
  13718. * @param {Object} [config] The following config options are supported:
  13719. *
  13720. * @param {String} config.url The url to post the data to. Defaults to
  13721. * the value of the {@link #downloadServerUrl} config.
  13722. *
  13723. * @param {String} config.format The format of image to export. See the
  13724. * {@link #supportedFormats}. Defaults to 'png' on the Sencha IO server.
  13725. * Note that you can't export to 'svg' format if the {@link Ext.draw.engine.Canvas Canvas}
  13726. * {@link Ext.draw.Container#engine engine} is used.
  13727. *
  13728. * @param {Number} config.width A width to send to the server for
  13729. * configuring the image width. Defaults to natural image width on
  13730. * the Sencha IO server.
  13731. *
  13732. * @param {Number} config.height A height to send to the server for
  13733. * configuring the image height. Defaults to natural image height on
  13734. * the Sencha IO server.
  13735. *
  13736. * @param {String} config.filename The filename of the downloaded image.
  13737. * Defaults to 'chart' on the Sencha IO server. The config.format is used
  13738. * as a filename extension.
  13739. *
  13740. * @param {Number} config.scale The scaling of the downloaded image.
  13741. * Defaults to 1 on the Sencha IO server. The server will try to determine the natural
  13742. * size of the image unless the width/height configs have been set. If the
  13743. * {@link Ext.draw.engine.Canvas Canvas} {@link Ext.draw.Container#engine engine} is
  13744. * used the natural image size will depend on the value of the window.devicePixelRatio.
  13745. * For example, for devices with devicePixelRatio of 2 the produced image will be
  13746. * two times larger than for devices with devicePixelRatio of 1 for the same drawing.
  13747. * This is done so that the users with devices with HiDPI screens get a downloaded
  13748. * image that looks as crisp on their device as the original drawing.
  13749. * If you want image size to be consistent across devices with different device
  13750. * pixel ratios, you can set the value of this config to 1/devicePixelRatio.
  13751. * This parameter is ignored by the Sencha IO server if config.format is set to 'svg'.
  13752. *
  13753. * @param {Object} config.pdf PDF specific options.
  13754. * This config is only used if config.format is set to 'pdf'.
  13755. * The given object should be in either this format:
  13756. *
  13757. * {
  13758. * width: '200px',
  13759. * height: '300px',
  13760. * border: '0px'
  13761. * }
  13762. *
  13763. * or this format:
  13764. *
  13765. * {
  13766. * format: 'A4',
  13767. * orientation: 'portrait',
  13768. * border: '1cm'
  13769. * }
  13770. *
  13771. * Supported dimension units are: 'mm', 'cm', 'in', 'px'. No unit means 'px'.
  13772. * Supported formats are: 'A3', 'A4', 'A5', 'Legal', 'Letter', 'Tabloid'.
  13773. * Orientation ('portrait', 'landscape') is optional and defaults to 'portrait'.
  13774. *
  13775. * @param {Object} config.jpeg JPEG specific options.
  13776. * This config is only used if config.format is set to 'jpeg'.
  13777. * The given object should be in this format:
  13778. *
  13779. * {
  13780. * quality: 80
  13781. * }
  13782. *
  13783. * Where quality is an integer between 0 and 100.
  13784. *
  13785. * @return {Boolean} True if request was successfully sent to the server.
  13786. */
  13787. download: function(config) {
  13788. var me = this,
  13789. inputs = [],
  13790. markup, name, value;
  13791. if (Ext.isIE8) {
  13792. return false;
  13793. }
  13794. config = config || {};
  13795. config.version = 2;
  13796. if (!config.data) {
  13797. config.data = me.getImage().data;
  13798. }
  13799. for (name in config) {
  13800. if (config.hasOwnProperty(name)) {
  13801. value = config[name];
  13802. if (name in me.supportedOptions) {
  13803. if (me.supportedOptions[name].call(me, value)) {
  13804. inputs.push({
  13805. tag: 'input',
  13806. type: 'hidden',
  13807. name: name,
  13808. value: Ext.String.htmlEncode(Ext.isObject(value) ? Ext.JSON.encode(value) : value)
  13809. });
  13810. } else //<debug>
  13811. {
  13812. Ext.log.error('Invalid value for image download option "' + name + '": ' + value);
  13813. }
  13814. } else //</debug>
  13815. //<debug>
  13816. {
  13817. Ext.log.error('Invalid image download option: "' + name + '"');
  13818. }
  13819. }
  13820. }
  13821. //</debug>
  13822. markup = Ext.dom.Helper.markup({
  13823. tag: 'html',
  13824. children: [
  13825. {
  13826. tag: 'head'
  13827. },
  13828. {
  13829. tag: 'body',
  13830. children: [
  13831. {
  13832. tag: 'form',
  13833. method: 'POST',
  13834. action: config.url || me.getDownloadServerUrl(),
  13835. children: inputs
  13836. },
  13837. {
  13838. tag: 'script',
  13839. type: 'text/javascript',
  13840. children: 'document.getElementsByTagName("form")[0].submit();'
  13841. }
  13842. ]
  13843. }
  13844. ]
  13845. });
  13846. window.open('', 'ImageDownload_' + Date.now()).document.write(markup);
  13847. },
  13848. /**
  13849. * @method preview
  13850. * Displays an image of a Ext.draw.Container on screen.
  13851. * On mobile devices this lets users tap-and-hold to bring up the menu
  13852. * with image saving options.
  13853. * Notes:
  13854. * - some browsers won't save the preview image if it's SVG based
  13855. * (i.e. generated from a draw container that uses 'Ext.draw.engine.Svg' engine);
  13856. * - some platforms may not have the means of viewing successfully saved SVG images;
  13857. * - this method does not work on IE8.
  13858. */
  13859. doDestroy: function() {
  13860. var me = this,
  13861. callbackId = me.frameCallbackId;
  13862. if (callbackId) {
  13863. Ext.draw.Animator.removeFrameCallback(callbackId);
  13864. }
  13865. me.stopResizeTimer();
  13866. me.callParent();
  13867. }
  13868. }, function() {
  13869. if (location.search.match('svg')) {
  13870. Ext.draw.Container.prototype.engine = 'Ext.draw.engine.Svg';
  13871. } else if ((Ext.os.is.BlackBerry && Ext.os.version.getMajor() === 10) || (Ext.browser.is.AndroidStock4 && (Ext.os.version.getMinor() === 1 || Ext.os.version.getMinor() === 2 || Ext.os.version.getMinor() === 3))) {
  13872. // http://code.google.com/p/android/issues/detail?id=37529
  13873. Ext.draw.Container.prototype.engine = 'Ext.draw.engine.Svg';
  13874. }
  13875. });
  13876. /**
  13877. *
  13878. */
  13879. Ext.define('Ext.chart.theme.BaseTheme', {
  13880. defaultsDivCls: 'x-component'
  13881. });
  13882. /**
  13883. * Abstract class that provides default styles for non-specified things.
  13884. * Should be sub-classed when creating new themes.
  13885. * For example:
  13886. *
  13887. * Ext.define('Ext.chart.theme.Custom', {
  13888. * extend: 'Ext.chart.theme.Base',
  13889. * singleton: true,
  13890. * alias: 'chart.theme.custom',
  13891. * config: {
  13892. * baseColor: '#ff9f00'
  13893. * }
  13894. * });
  13895. *
  13896. * Theme provided values will not override the values provided in an instance config.
  13897. * However, if a theme provided value is an object, it will be merged with the value
  13898. * from the instance config, unless the theme provided object has a '$default' key
  13899. * set to 'true'.
  13900. *
  13901. * Certain chart theme configs (e.g. 'fontSize') may use the 'default' value to indicate
  13902. * that they should inherit a value from the corresponding CSS style provided by
  13903. * a framework theme. Additionally, one can use basic binary operators like multiplication,
  13904. * addition and subtraction to derive from the default value, e.g. fontSize: 'default*1.3'.
  13905. *
  13906. * Important: the theme should not use the 'font' shorthand to specify the font of labels
  13907. * and other text elements of a chart. Instead, individual font properties should be used:
  13908. * 'fontStyle', 'fontVariant', 'fontWeight', 'fontSize' and 'fontFamily'.
  13909. */
  13910. Ext.define('Ext.chart.theme.Base', {
  13911. extend: 'Ext.chart.theme.BaseTheme',
  13912. mixins: {
  13913. factoryable: 'Ext.mixin.Factoryable'
  13914. },
  13915. requires: [
  13916. 'Ext.draw.Color'
  13917. ],
  13918. factoryConfig: {
  13919. type: 'chart.theme'
  13920. },
  13921. isTheme: true,
  13922. isBase: true,
  13923. config: {
  13924. /**
  13925. * @cfg {String/Ext.util.Color} baseColor
  13926. * The base color used to generate the {@link Ext.chart.AbstractChart#colors} of the theme.
  13927. */
  13928. baseColor: null,
  13929. /**
  13930. * @cfg {Array} colors
  13931. *
  13932. * Array of colors/gradients to be used by the theme.
  13933. * Defaults to {@link #colorDefaults}.
  13934. */
  13935. colors: undefined,
  13936. /**
  13937. * @cfg {Object} gradients
  13938. *
  13939. * The gradient config to be used by series' sprites. E.g.:
  13940. *
  13941. * {
  13942. * type: 'linear',
  13943. * degrees: 90
  13944. * }
  13945. *
  13946. * Please refer to the documentation for the {@link Ext.draw.gradient.Linear linear}
  13947. * and {@link Ext.draw.gradient.Radial radial} gradients for all possible options.
  13948. * The color {@link Ext.draw.gradient.Gradient#stops stops} for the gradients
  13949. * will be generated by the theme based on the {@link #colors} config.
  13950. */
  13951. gradients: null,
  13952. /**
  13953. * @cfg {Object} chart
  13954. * Theme defaults for the chart.
  13955. * Can apply to all charts or just a specific type of chart.
  13956. * For example:
  13957. *
  13958. * chart: {
  13959. * defaults: {
  13960. * background: 'lightgray'
  13961. * },
  13962. * polar: {
  13963. * background: 'green'
  13964. * }
  13965. * }
  13966. *
  13967. * The values from the chart.defaults and chart.*type* configs (where *type* is a valid
  13968. * chart xtype, e.g. '{@link Ext.chart.CartesianChart cartesian}' or '{@link Ext.chart.PolarChart polar}')
  13969. * will be applied to corresponding chart configs.
  13970. * E.g., the chart.defaults.background config will set the {@link Ext.chart.AbstractChart#background}
  13971. * config of all charts, where the chart.cartesian.flipXY config will only set the
  13972. * {@link Ext.chart.CartesianChart#flipXY} config of all cartesian charts.
  13973. */
  13974. chart: {
  13975. defaults: {
  13976. captions: {
  13977. title: {
  13978. docked: 'top',
  13979. padding: 5,
  13980. style: {
  13981. textAlign: 'center',
  13982. fontFamily: 'default',
  13983. fontWeight: '500',
  13984. fillStyle: 'black',
  13985. fontSize: 'default*1.6'
  13986. }
  13987. },
  13988. subtitle: {
  13989. docked: 'top',
  13990. style: {
  13991. textAlign: 'center',
  13992. fontFamily: 'default',
  13993. fontWeight: 'normal',
  13994. fillStyle: 'black',
  13995. fontSize: 'default*1.3'
  13996. }
  13997. },
  13998. credits: {
  13999. docked: 'bottom',
  14000. padding: 5,
  14001. style: {
  14002. textAlign: 'left',
  14003. fontFamily: 'default',
  14004. fontWeight: 'lighter',
  14005. fillStyle: 'black',
  14006. fontSize: 'default'
  14007. }
  14008. }
  14009. },
  14010. background: 'white'
  14011. }
  14012. },
  14013. /**
  14014. * @cfg {Object} axis
  14015. * Theme defaults for the axes.
  14016. * Can apply to all axes or only axes with a specific position.
  14017. * For example:
  14018. *
  14019. * axis: {
  14020. * defaults: {
  14021. * style: {strokeStyle: 'red'}
  14022. * },
  14023. * left: {
  14024. * title: {fillStyle: 'green'}
  14025. * }
  14026. * }
  14027. *
  14028. * The values from the axis.defaults and axis.*position* configs (where *position*
  14029. * is a valid axis {@link Ext.chart.axis.Axis#position}, e.g. 'bottom') will be
  14030. * applied to corresponding {@link Ext.chart.axis.Axis axis} configs.
  14031. * E.g., the axis.defaults.label config will apply to the {@link Ext.chart.axis.Axis#label}
  14032. * config of all axes, where the axis.left.titleMargin config will only apply to the
  14033. * {@link Ext.chart.axis.Axis#titleMargin} config of all axes positioned to the left.
  14034. */
  14035. axis: {
  14036. defaults: {
  14037. label: {
  14038. x: 0,
  14039. y: 0,
  14040. textBaseline: 'middle',
  14041. textAlign: 'center',
  14042. fontSize: 'default',
  14043. fontFamily: 'default',
  14044. fontWeight: 'default',
  14045. fillStyle: 'black'
  14046. },
  14047. title: {
  14048. fillStyle: 'black',
  14049. fontSize: 'default*1.23',
  14050. fontFamily: 'default',
  14051. fontWeight: 'default'
  14052. },
  14053. style: {
  14054. strokeStyle: 'black'
  14055. },
  14056. grid: {
  14057. strokeStyle: 'rgb(221, 221, 221)'
  14058. }
  14059. },
  14060. top: {
  14061. style: {
  14062. textPadding: 5
  14063. }
  14064. },
  14065. bottom: {
  14066. style: {
  14067. textPadding: 5
  14068. }
  14069. }
  14070. },
  14071. /**
  14072. * @cfg {Object} series
  14073. * Theme defaults for the series.
  14074. * Can apply to all series or just a specific type of series.
  14075. * For example:
  14076. *
  14077. * series: {
  14078. * defaults: {
  14079. * style: {
  14080. * lineWidth: 2
  14081. * }
  14082. * },
  14083. * bar: {
  14084. * animation: {
  14085. * easing: 'bounceOut',
  14086. * duration: 1000
  14087. * }
  14088. * }
  14089. * }
  14090. *
  14091. * The values from the series.defaults and series.*type* configs (where *type*
  14092. * is a valid series {@link Ext.chart.series.Series#type}, e.g. 'line') will be
  14093. * applied to corresponding series configs.
  14094. * E.g., the series.defaults.label config will apply to the {@link Ext.chart.series.Series#label}
  14095. * config of all series, where the series.line.step config will only apply to the
  14096. * {@link Ext.chart.series.Line#step} config of {@link Ext.chart.series.Line line} series.
  14097. */
  14098. series: {
  14099. defaults: {
  14100. label: {
  14101. fillStyle: 'black',
  14102. strokeStyle: 'none',
  14103. fontFamily: 'default',
  14104. fontWeight: 'default',
  14105. fontSize: 'default*1.077',
  14106. textBaseline: 'middle',
  14107. textAlign: 'center'
  14108. },
  14109. labelOverflowPadding: 5
  14110. }
  14111. },
  14112. /**
  14113. * @cfg {Object} sprites
  14114. * Default style for the custom chart sprites by type.
  14115. * For example:
  14116. *
  14117. * sprites: {
  14118. * text: {
  14119. * fontWeight: 300
  14120. * }
  14121. * }
  14122. *
  14123. * These sprite attribute overrides will apply to custom sprites of all charts
  14124. * specified using the {@link Ext.draw.Container#sprites} config.
  14125. * The overrides are specified by sprite type, e.g. sprites.text config
  14126. * tells to apply given attributes to all {@link Ext.draw.sprite.Text text} sprites.
  14127. */
  14128. sprites: {
  14129. text: {
  14130. fontSize: 'default',
  14131. fontWeight: 'default',
  14132. fontFamily: 'default',
  14133. fillStyle: 'black'
  14134. }
  14135. },
  14136. /**
  14137. * Style information for the {Ext.chart.legend.SpriteLegend sprite legend}.
  14138. * If the {@link Ext.chart.legend.Legend DOM} legend is used, this config is ignored.
  14139. * For additional details see {@link Ext.chart.AbstractChart#legend}.
  14140. * @cfg {Object} legend
  14141. * @cfg {Ext.chart.legend.sprite.Item} legend.item
  14142. * @cfg {Object} legend.border See {@link Ext.chart.legend.SpriteLegend#border}.
  14143. */
  14144. legend: {
  14145. label: {
  14146. fontSize: 14,
  14147. fontWeight: 'default',
  14148. fontFamily: 'default',
  14149. fillStyle: 'black'
  14150. },
  14151. border: {
  14152. lineWidth: 1,
  14153. radius: 4,
  14154. fillStyle: 'none',
  14155. strokeStyle: 'gray'
  14156. },
  14157. background: 'white'
  14158. },
  14159. /**
  14160. * @private
  14161. * An object with the following structure:
  14162. * {
  14163. * fillStyle: [color, color, ...],
  14164. * strokeStyle: [color, color, ...],
  14165. * ...
  14166. * }
  14167. * If missing, generated from the other configs: 'baseColor, 'gradients', 'colors'.
  14168. */
  14169. seriesThemes: undefined,
  14170. markerThemes: {
  14171. type: [
  14172. 'circle',
  14173. 'cross',
  14174. 'plus',
  14175. 'square',
  14176. 'triangle',
  14177. 'diamond'
  14178. ]
  14179. },
  14180. /**
  14181. * @deprecated 5.0.1 Use the {@link Ext.draw.Container#gradients} config instead.
  14182. */
  14183. useGradients: false,
  14184. /**
  14185. * @deprecated 5.0.1 Use the {@link Ext.chart.AbstractChart#background} config instead.
  14186. */
  14187. background: null
  14188. },
  14189. colorDefaults: [
  14190. '#94ae0a',
  14191. '#115fa6',
  14192. '#a61120',
  14193. '#ff8809',
  14194. '#ffd13e',
  14195. '#a61187',
  14196. '#24ad9a',
  14197. '#7c7474',
  14198. '#a66111'
  14199. ],
  14200. constructor: function(config) {
  14201. this.initConfig(config);
  14202. this.resolveDefaults();
  14203. },
  14204. defaultRegEx: /^default([+\-/\*]\d+(?:\.\d+)?)?$/,
  14205. defaultOperators: {
  14206. '*': function(v1, v2) {
  14207. return v1 * v2;
  14208. },
  14209. '+': function(v1, v2) {
  14210. return v1 + v2;
  14211. },
  14212. '-': function(v1, v2) {
  14213. return v1 - v2;
  14214. }
  14215. },
  14216. resolveChartDefaults: function() {
  14217. var chart = Ext.clone(this.getChart()),
  14218. chartType, captionName, chartConfig, captionConfig;
  14219. for (chartType in chart) {
  14220. chartConfig = chart[chartType];
  14221. if ('captions' in chartConfig) {
  14222. for (captionName in chartConfig.captions) {
  14223. captionConfig = chartConfig.captions[captionName];
  14224. if (captionConfig) {
  14225. this.replaceDefaults(captionConfig.style);
  14226. }
  14227. }
  14228. }
  14229. }
  14230. this.setChart(chart);
  14231. },
  14232. resolveDefaults: function() {
  14233. var me = this;
  14234. Ext.onInternalReady(function() {
  14235. var sprites = Ext.clone(me.getSprites()),
  14236. legend = Ext.clone(me.getLegend()),
  14237. axis = Ext.clone(me.getAxis()),
  14238. series = Ext.clone(me.getSeries()),
  14239. div, key, config;
  14240. if (!me.superclass.defaults) {
  14241. div = Ext.getBody().createChild({
  14242. tag: 'div',
  14243. cls: me.defaultsDivCls
  14244. });
  14245. me.superclass.defaults = {
  14246. fontFamily: div.getStyle('fontFamily'),
  14247. fontWeight: div.getStyle('fontWeight'),
  14248. fontSize: parseFloat(div.getStyle('fontSize')),
  14249. fontVariant: div.getStyle('fontVariant'),
  14250. fontStyle: div.getStyle('fontStyle')
  14251. };
  14252. div.destroy();
  14253. }
  14254. me.resolveChartDefaults();
  14255. me.replaceDefaults(sprites.text);
  14256. me.setSprites(sprites);
  14257. me.replaceDefaults(legend.label);
  14258. me.setLegend(legend);
  14259. for (key in axis) {
  14260. config = axis[key];
  14261. me.replaceDefaults(config.label);
  14262. me.replaceDefaults(config.title);
  14263. }
  14264. me.setAxis(axis);
  14265. for (key in series) {
  14266. config = series[key];
  14267. me.replaceDefaults(config.label);
  14268. }
  14269. me.setSeries(series);
  14270. });
  14271. },
  14272. replaceDefaults: function(target) {
  14273. var me = this,
  14274. defaults = me.superclass.defaults,
  14275. defaultRegEx = me.defaultRegEx,
  14276. key, value, match, binaryFn;
  14277. if (Ext.isObject(target)) {
  14278. for (key in defaults) {
  14279. match = defaultRegEx.exec(target[key]);
  14280. if (match) {
  14281. value = defaults[key];
  14282. match = match[1];
  14283. if (match) {
  14284. binaryFn = me.defaultOperators[match.charAt(0)];
  14285. value = Math.round(binaryFn(value, parseFloat(match.substr(1))));
  14286. }
  14287. target[key] = value;
  14288. }
  14289. }
  14290. }
  14291. },
  14292. applyBaseColor: function(baseColor) {
  14293. var midColor, midL;
  14294. if (baseColor) {
  14295. midColor = baseColor.isColor ? baseColor : Ext.util.Color.fromString(baseColor);
  14296. midL = midColor.getHSL()[2];
  14297. if (midL < 0.15) {
  14298. midColor = midColor.createLighter(0.3);
  14299. } else if (midL < 0.3) {
  14300. midColor = midColor.createLighter(0.15);
  14301. } else if (midL > 0.85) {
  14302. midColor = midColor.createDarker(0.3);
  14303. } else if (midL > 0.7) {
  14304. midColor = midColor.createDarker(0.15);
  14305. }
  14306. this.setColors([
  14307. midColor.createDarker(0.3).toString(),
  14308. midColor.createDarker(0.15).toString(),
  14309. midColor.toString(),
  14310. midColor.createLighter(0.12).toString(),
  14311. midColor.createLighter(0.24).toString(),
  14312. midColor.createLighter(0.31).toString()
  14313. ]);
  14314. }
  14315. return baseColor;
  14316. },
  14317. applyColors: function(newColors) {
  14318. return newColors || this.colorDefaults;
  14319. },
  14320. updateUseGradients: function(useGradients) {
  14321. if (useGradients) {
  14322. this.updateGradients({
  14323. type: 'linear',
  14324. degrees: 90
  14325. });
  14326. }
  14327. },
  14328. updateBackground: function(background) {
  14329. if (background) {
  14330. var chart = this.getChart();
  14331. chart.defaults.background = background;
  14332. this.setChart(chart);
  14333. }
  14334. },
  14335. updateGradients: function(gradients) {
  14336. var colors = this.getColors(),
  14337. items = [],
  14338. gradient, midColor, color, i, ln;
  14339. if (Ext.isObject(gradients)) {
  14340. for (i = 0 , ln = colors && colors.length || 0; i < ln; i++) {
  14341. midColor = Ext.util.Color.fromString(colors[i]);
  14342. if (midColor) {
  14343. color = midColor.createLighter(0.15).toString();
  14344. gradient = Ext.apply(Ext.Object.chain(gradients), {
  14345. stops: [
  14346. {
  14347. offset: 1,
  14348. color: midColor.toString()
  14349. },
  14350. {
  14351. offset: 0,
  14352. color: color.toString()
  14353. }
  14354. ]
  14355. });
  14356. items.push(gradient);
  14357. }
  14358. }
  14359. this.setColors(items);
  14360. }
  14361. },
  14362. applySeriesThemes: function(newSeriesThemes) {
  14363. // Init the 'colors' config with solid colors generated from the 'baseColor'.
  14364. this.getBaseColor();
  14365. // Init the 'gradients' config with a hardcoded value, if the legacy 'useGradients'
  14366. // config was set to 'true'. This in turn updates the 'colors' config.
  14367. this.getUseGradients();
  14368. // Init the 'gradients' config normally. This also updates the 'colors' config.
  14369. this.getGradients();
  14370. var colors = this.getColors();
  14371. // Final colors.
  14372. if (!newSeriesThemes) {
  14373. newSeriesThemes = {
  14374. fillStyle: Ext.Array.clone(colors),
  14375. strokeStyle: Ext.Array.map(colors, function(value) {
  14376. var color = Ext.util.Color.fromString(value.stops ? value.stops[0].color : value);
  14377. return color.createDarker(0.15).toString();
  14378. })
  14379. };
  14380. }
  14381. return newSeriesThemes;
  14382. }
  14383. });
  14384. /**
  14385. * @private
  14386. */
  14387. Ext.define('Ext.chart.theme.Default', {
  14388. extend: 'Ext.chart.theme.Base',
  14389. singleton: true,
  14390. alias: [
  14391. 'chart.theme.default',
  14392. 'chart.theme.Default',
  14393. 'chart.theme.Base'
  14394. ]
  14395. });
  14396. /**
  14397. * @private
  14398. */
  14399. Ext.define('Ext.chart.Util', {
  14400. singleton: true,
  14401. /**
  14402. * @private
  14403. * Given a `range` array of two (min/max) numbers and an arbitrary array of numbers
  14404. * (`data`), updates the given `range`, if the range of `data` exceeds it.
  14405. * Typically, one would start with the `[NaN, NaN]` range array instance, call the
  14406. * method on multiple datasets with that range instance, then validate it with
  14407. * {@link #validateRange}.
  14408. * @param {Number[]} range
  14409. * @param {Number[]} data
  14410. */
  14411. expandRange: function(range, data) {
  14412. var length = data.length,
  14413. min = range[0],
  14414. max = range[1],
  14415. i, value;
  14416. for (i = 0; i < length; i++) {
  14417. value = data[i];
  14418. // `null` is a "finite" number in JavaScript
  14419. // and greater than any negative number.
  14420. if (value == null || !isFinite(value)) {
  14421. continue;
  14422. }
  14423. if (value < min || !isFinite(min)) {
  14424. min = value;
  14425. }
  14426. if (value > max || !isFinite(max)) {
  14427. max = value;
  14428. }
  14429. }
  14430. range[0] = min;
  14431. range[1] = max;
  14432. },
  14433. defaultRange: [
  14434. 0,
  14435. 1
  14436. ],
  14437. /**
  14438. * @private
  14439. * Makes sure the range exists, is not zero, and its min/max values are finite numbers.
  14440. * If this is not the case, the values from the provided `defaultRange`
  14441. * are used.
  14442. *
  14443. * The range to validate. Never modified.
  14444. * @param {Number[]} range
  14445. * The default range to use, if the given range is not a valid data structure,
  14446. * if both values are infinities, or if both values are the same and dangerously
  14447. * close to either infinity (which makes expansion of the range by the value of
  14448. * `padding` impossible).
  14449. * If only a single value is infinity, the other value will be derived
  14450. * from the finite value by incrementing/decrementing it by the span
  14451. * of the default range towards the infinity.
  14452. * For example, if the `defaultRange` is `[0, 1]`, we have:
  14453. *
  14454. * [5, Infinity] --> [5, 6]
  14455. * [3, -Infinity] --> [2, 3]
  14456. * [-Infinity, -5] --> [-6, -5]
  14457. * [-3, -Infinity] --> [-4, -3]
  14458. *
  14459. * @param {Number[]} [defaultRange=[0, 1]]
  14460. * A non-negative padding to use in case of identical min/max.
  14461. * Note that the range span is not guaranteed to be `padding * 2` in this case,
  14462. * if min/max are close to MIN_SAFE_INTEGER/MAX_SAFE_INTEGER.
  14463. * @param {Number} [padding=0.5]
  14464. * @return {Number[]}
  14465. */
  14466. validateRange: function(range, defaultRange, padding) {
  14467. defaultRange = defaultRange || this.defaultRange.slice();
  14468. if (!(padding === 0 || padding > 0)) {
  14469. padding = 0.5;
  14470. }
  14471. if (!range || range.length !== 2) {
  14472. return defaultRange;
  14473. }
  14474. range = [
  14475. range[0],
  14476. range[1]
  14477. ];
  14478. if (!range[0]) {
  14479. range[0] = 0;
  14480. }
  14481. if (!range[1]) {
  14482. range[1] = 0;
  14483. }
  14484. if (padding && range[0] === range[1]) {
  14485. range = [
  14486. range[0] - padding,
  14487. range[0] + padding
  14488. ];
  14489. // In case the range values are at Infinity, the expansion above by the value
  14490. // of 'padding' won't do us much good, so we still have to fall back to the
  14491. // 'defaultRange'.
  14492. if (range[0] === range[1]) {
  14493. return defaultRange;
  14494. }
  14495. }
  14496. // Same sign infinities are ruled out at this point.
  14497. var isFin0 = isFinite(range[0]);
  14498. var isFin1 = isFinite(range[1]);
  14499. if (!isFin0 && !isFin1) {
  14500. return defaultRange;
  14501. }
  14502. // Different sign infinities are ruled out at this point.
  14503. if (isFin0 && !isFin1) {
  14504. range[1] = range[0] + Ext.Number.sign(range[1]) * (defaultRange[1] - defaultRange[0]);
  14505. } else if (isFin1 && !isFin0) {
  14506. range[0] = range[1] + Ext.Number.sign(range[0]) * (defaultRange[1] - defaultRange[0]);
  14507. }
  14508. // All infinities are ruled out at this point.
  14509. return [
  14510. Math.min(range[0], range[1]),
  14511. Math.max(range[0], range[1])
  14512. ];
  14513. },
  14514. applyAnimation: function(animation, oldAnimation) {
  14515. if (!animation) {
  14516. animation = {
  14517. duration: 0
  14518. };
  14519. } else if (animation === true) {
  14520. animation = {
  14521. easing: 'easeInOut',
  14522. duration: 500
  14523. };
  14524. }
  14525. return oldAnimation ? Ext.apply({}, animation, oldAnimation) : animation;
  14526. }
  14527. });
  14528. /**
  14529. * @class Ext.chart.Markers
  14530. * @extends Ext.draw.sprite.Instancing
  14531. *
  14532. * Marker sprite. A specialized version of instancing sprite that groups instances.
  14533. * Putting a marker is grouped by its category id. Clearing removes that category.
  14534. */
  14535. Ext.define('Ext.chart.Markers', {
  14536. extend: 'Ext.draw.sprite.Instancing',
  14537. isMarkers: true,
  14538. defaultCategory: 'default',
  14539. constructor: function() {
  14540. this.callParent(arguments);
  14541. // `categories` maps category names to a map that maps instance index in category to its global index:
  14542. // categoryName: {instanceIndexInCategory: globalInstanceIndex}
  14543. this.categories = {};
  14544. // The `revisions` map keeps revision numbers of instance categories.
  14545. // When a marker (instance) is put (created or updated), it gets the revision
  14546. // of the category. When a category is cleared, its revision is incremented,
  14547. // but its instances are not removed.
  14548. // An instance is only rendered if its revision matches category revision.
  14549. // In other words, a marker has to be put again after its category has been cleared
  14550. // or it won't render.
  14551. this.revisions = {};
  14552. },
  14553. destroy: function() {
  14554. this.categories = null;
  14555. this.revisions = null;
  14556. this.callParent();
  14557. },
  14558. getMarkerFor: function(category, index) {
  14559. if (category in this.categories) {
  14560. var categoryInstances = this.categories[category];
  14561. if (index in categoryInstances) {
  14562. return this.get(categoryInstances[index]);
  14563. }
  14564. }
  14565. },
  14566. /**
  14567. * Clears the markers in the category.
  14568. * @param {String} category
  14569. */
  14570. clear: function(category) {
  14571. category = category || this.defaultCategory;
  14572. if (!(category in this.revisions)) {
  14573. this.revisions[category] = 1;
  14574. } else {
  14575. this.revisions[category]++;
  14576. }
  14577. },
  14578. clearAll: function() {
  14579. this.callParent();
  14580. this.categories = {};
  14581. this.revisions = {};
  14582. },
  14583. /**
  14584. * Puts a marker in the category with additional attributes.
  14585. * @param {String} category
  14586. * @param {Object} attr
  14587. * @param {String|Number} index
  14588. * @param {Boolean} [bypassNormalization]
  14589. * @param {Boolean} [keepRevision]
  14590. */
  14591. putMarkerFor: function(category, attr, index, bypassNormalization, keepRevision) {
  14592. category = category || this.defaultCategory;
  14593. var me = this,
  14594. categoryInstances = me.categories[category] || (me.categories[category] = {}),
  14595. instance;
  14596. if (index in categoryInstances) {
  14597. me.setAttributesFor(categoryInstances[index], attr, bypassNormalization);
  14598. } else {
  14599. categoryInstances[index] = me.getCount();
  14600. // get the index of the instance created on next line
  14601. me.add(attr, bypassNormalization);
  14602. }
  14603. instance = me.get(categoryInstances[index]);
  14604. if (instance) {
  14605. instance.category = category;
  14606. if (!keepRevision) {
  14607. instance.revision = me.revisions[category] || (me.revisions[category] = 1);
  14608. }
  14609. }
  14610. },
  14611. /**
  14612. *
  14613. * @param {String} category
  14614. * @param {Mixed} index
  14615. * @param {Boolean} [isWithoutTransform]
  14616. */
  14617. getMarkerBBoxFor: function(category, index, isWithoutTransform) {
  14618. if (category in this.categories) {
  14619. var categoryInstances = this.categories[category];
  14620. if (index in categoryInstances) {
  14621. return this.getBBoxFor(categoryInstances[index], isWithoutTransform);
  14622. }
  14623. }
  14624. },
  14625. getBBox: function() {
  14626. return null;
  14627. },
  14628. render: function(surface, ctx, rect) {
  14629. var me = this,
  14630. surfaceRect = surface.getRect(),
  14631. revisions = me.revisions,
  14632. mat = me.attr.matrix,
  14633. template = me.getTemplate(),
  14634. templateAttr = template.attr,
  14635. ln = me.instances.length,
  14636. instance, i;
  14637. mat.toContext(ctx);
  14638. template.preRender(surface, ctx, rect);
  14639. template.useAttributes(ctx, surfaceRect);
  14640. for (i = 0; i < ln; i++) {
  14641. instance = me.get(i);
  14642. if (instance.hidden || instance.revision !== revisions[instance.category]) {
  14643. continue;
  14644. }
  14645. ctx.save();
  14646. template.attr = instance;
  14647. template.useAttributes(ctx, surfaceRect);
  14648. template.render(surface, ctx, rect);
  14649. ctx.restore();
  14650. }
  14651. template.attr = templateAttr;
  14652. }
  14653. });
  14654. /**
  14655. * This is a modifier to place labels and callouts by additional attributes.
  14656. */
  14657. Ext.define('Ext.chart.modifier.Callout', {
  14658. extend: 'Ext.draw.modifier.Modifier',
  14659. alternateClassName: 'Ext.chart.label.Callout',
  14660. prepareAttributes: function(attr) {
  14661. if (!attr.hasOwnProperty('calloutOriginal')) {
  14662. attr.calloutOriginal = Ext.Object.chain(attr);
  14663. // No __proto__, nor getPrototypeOf in IE8,
  14664. // so manually saving a reference to 'attr' after chaining.
  14665. attr.calloutOriginal.prototype = attr;
  14666. }
  14667. if (this._lower) {
  14668. this._lower.prepareAttributes(attr.calloutOriginal);
  14669. }
  14670. },
  14671. setAttrs: function(attr, changes) {
  14672. var callout = attr.callout,
  14673. origin = attr.calloutOriginal,
  14674. bbox = attr.bbox.plain,
  14675. width = (bbox.width || 0) + attr.labelOverflowPadding,
  14676. height = (bbox.height || 0) + attr.labelOverflowPadding,
  14677. dx, dy;
  14678. if ('callout' in changes) {
  14679. callout = changes.callout;
  14680. }
  14681. if ('callout' in changes || 'calloutPlaceX' in changes || 'calloutPlaceY' in changes || 'x' in changes || 'y' in changes) {
  14682. var rotationRads = 'rotationRads' in changes ? origin.rotationRads = changes.rotationRads : origin.rotationRads,
  14683. x = 'x' in changes ? (origin.x = changes.x) : origin.x,
  14684. y = 'y' in changes ? (origin.y = changes.y) : origin.y,
  14685. calloutPlaceX = 'calloutPlaceX' in changes ? changes.calloutPlaceX : attr.calloutPlaceX,
  14686. calloutPlaceY = 'calloutPlaceY' in changes ? changes.calloutPlaceY : attr.calloutPlaceY,
  14687. calloutVertical = 'calloutVertical' in changes ? changes.calloutVertical : attr.calloutVertical,
  14688. temp;
  14689. // Normalize Rotations
  14690. rotationRads %= Math.PI * 2;
  14691. if (Math.cos(rotationRads) < 0) {
  14692. rotationRads = (rotationRads + Math.PI) % (Math.PI * 2);
  14693. }
  14694. if (rotationRads > Math.PI) {
  14695. rotationRads -= Math.PI * 2;
  14696. }
  14697. if (calloutVertical) {
  14698. rotationRads = rotationRads * (1 - callout) - Math.PI / 2 * callout;
  14699. temp = width;
  14700. width = height;
  14701. height = temp;
  14702. } else {
  14703. rotationRads = rotationRads * (1 - callout);
  14704. }
  14705. changes.rotationRads = rotationRads;
  14706. // Placing a label in the middle of a pie slice (x/y)
  14707. // if callout doesn't exists (callout=0),
  14708. // or outside the pie slice (calloutPlaceX/Y) if it does (callout=1).
  14709. changes.x = x * (1 - callout) + calloutPlaceX * callout;
  14710. changes.y = y * (1 - callout) + calloutPlaceY * callout;
  14711. dx = calloutPlaceX - x;
  14712. dy = calloutPlaceY - y;
  14713. // Finding where the callout line intersects the bbox of the label
  14714. // if it were to go to the center of the label,
  14715. // and make that intersection point the end of the callout line.
  14716. // Effectively, the end of the callout line traces label's bbox when chart is rotated.
  14717. if (Math.abs(dy * width) > Math.abs(dx * height)) {
  14718. // on top/bottom
  14719. if (dy > 0) {
  14720. changes.calloutEndX = changes.x - (height / 2) * (dx / dy) * callout;
  14721. changes.calloutEndY = changes.y - (height / 2) * callout;
  14722. } else {
  14723. changes.calloutEndX = changes.x + (height / 2) * (dx / dy) * callout;
  14724. changes.calloutEndY = changes.y + (height / 2) * callout;
  14725. }
  14726. } else {
  14727. // on left/right
  14728. if (dx > 0) {
  14729. changes.calloutEndX = changes.x - width / 2;
  14730. changes.calloutEndY = changes.y - (width / 2) * (dy / dx) * callout;
  14731. } else {
  14732. changes.calloutEndX = changes.x + width / 2;
  14733. changes.calloutEndY = changes.y + (width / 2) * (dy / dx) * callout;
  14734. }
  14735. }
  14736. // Since the length of the callout line is adjusted depending on the label's position
  14737. // and dimensions, we hide the callout line if the length becomes negative.
  14738. if (changes.calloutStartX && changes.calloutStartY) {
  14739. changes.calloutHasLine = (dx > 0 && changes.calloutStartX < changes.calloutEndX) || (dx <= 0 && changes.calloutStartX > changes.calloutEndX) || (dy > 0 && changes.calloutStartY < changes.calloutEndY) || (dy <= 0 && changes.calloutStartY > changes.calloutEndY);
  14740. } else {
  14741. changes.calloutHasLine = true;
  14742. }
  14743. }
  14744. return changes;
  14745. },
  14746. pushDown: function(attr, changes) {
  14747. changes = this.callParent([
  14748. attr.calloutOriginal,
  14749. changes
  14750. ]);
  14751. return this.setAttrs(attr, changes);
  14752. },
  14753. popUp: function(attr, changes) {
  14754. attr = attr.prototype;
  14755. changes = this.setAttrs(attr, changes);
  14756. if (this._upper) {
  14757. return this._upper.popUp(attr, changes);
  14758. } else {
  14759. return Ext.apply(attr, changes);
  14760. }
  14761. }
  14762. });
  14763. /**
  14764. * @class Ext.chart.sprite.Label
  14765. * @extends Ext.draw.sprite.Text
  14766. *
  14767. * Sprite used to represent labels in series.
  14768. *
  14769. * Important: the actual default values are determined by the theme used.
  14770. * Please see the `label` config of the {@link Ext.chart.theme.Base#axis}.
  14771. */
  14772. Ext.define('Ext.chart.sprite.Label', {
  14773. extend: 'Ext.draw.sprite.Text',
  14774. alternateClassName: 'Ext.chart.label.Label',
  14775. requires: [
  14776. 'Ext.chart.modifier.Callout'
  14777. ],
  14778. inheritableStatics: {
  14779. def: {
  14780. processors: {
  14781. callout: 'limited01',
  14782. // Meant to be set by the Callout modifier only.
  14783. calloutHasLine: 'bool',
  14784. // The position where the callout would end, if not for the label:
  14785. // callout stops at the bounding box of the label,
  14786. // so the actual point where the callout ends - calloutEndX/Y -
  14787. // is calculated by the Callout modifier.
  14788. calloutPlaceX: 'number',
  14789. calloutPlaceY: 'number',
  14790. // The start/end points used to render the callout line.
  14791. calloutStartX: 'number',
  14792. calloutStartY: 'number',
  14793. calloutEndX: 'number',
  14794. calloutEndY: 'number',
  14795. calloutColor: 'color',
  14796. calloutWidth: 'number',
  14797. calloutVertical: 'bool',
  14798. labelOverflowPadding: 'number',
  14799. display: 'enums(none,under,over,rotate,insideStart,insideEnd,inside,outside)',
  14800. orientation: 'enums(horizontal,vertical)',
  14801. renderer: 'default'
  14802. },
  14803. defaults: {
  14804. callout: 0,
  14805. calloutHasLine: true,
  14806. calloutPlaceX: 0,
  14807. calloutPlaceY: 0,
  14808. calloutStartX: 0,
  14809. calloutStartY: 0,
  14810. calloutEndX: 0,
  14811. calloutEndY: 0,
  14812. calloutWidth: 1,
  14813. calloutVertical: false,
  14814. calloutColor: 'black',
  14815. labelOverflowPadding: 5,
  14816. display: 'none',
  14817. orientation: '',
  14818. renderer: null
  14819. },
  14820. triggers: {
  14821. callout: 'transform',
  14822. calloutPlaceX: 'transform',
  14823. calloutPlaceY: 'transform',
  14824. labelOverflowPadding: 'transform',
  14825. calloutRotation: 'transform',
  14826. display: 'hidden'
  14827. },
  14828. updaters: {
  14829. hidden: function(attr) {
  14830. attr.hidden = (attr.display === 'none');
  14831. }
  14832. }
  14833. }
  14834. },
  14835. config: {
  14836. /**
  14837. * @cfg {Object} fx Animation configuration.
  14838. */
  14839. animation: {
  14840. customDurations: {
  14841. callout: 200
  14842. }
  14843. },
  14844. /**
  14845. * @cfg {String} field The store record field used by the label sprite.
  14846. *
  14847. * Note: the label sprite is typically used indirectly (by a Ext.chart.MarkerHolder
  14848. * series sprite, via a Ext.chart.Markers sprite, where the latter is passed to the
  14849. * label renderer), so to get to the label field one has to do:
  14850. *
  14851. * renderer: function (text, sprite, config, data, index) {
  14852. * var field = sprite.getTemplate().getField();
  14853. * }
  14854. *
  14855. * To get the actual label sprite instance one can use:
  14856. *
  14857. * sprite.get(index)
  14858. *
  14859. */
  14860. field: null,
  14861. /**
  14862. * @cfg {Boolean|Object} calloutLine
  14863. *
  14864. * True to draw a line between the label and the chart with the default settings,
  14865. * or an Object that defines the 'color', 'width' and 'length' properties of the line.
  14866. * This config is only applicable when the label is displayed outside the chart.
  14867. *
  14868. * Default value: false.
  14869. */
  14870. calloutLine: true,
  14871. /**
  14872. * @cfg {Number} [hideLessThan=20]
  14873. * Hides labels for pie slices with segment length less than this value (in pixels).
  14874. */
  14875. hideLessThan: 20
  14876. },
  14877. applyCalloutLine: function(calloutLine) {
  14878. if (calloutLine) {
  14879. return Ext.apply({}, calloutLine);
  14880. }
  14881. },
  14882. createModifiers: function() {
  14883. var me = this,
  14884. mods = me.callParent(arguments);
  14885. mods.callout = new Ext.chart.modifier.Callout({
  14886. sprite: me
  14887. });
  14888. mods.animation.setUpper(mods.callout);
  14889. mods.callout.setUpper(mods.target);
  14890. },
  14891. render: function(surface, ctx) {
  14892. var me = this,
  14893. attr = me.attr,
  14894. calloutColor = attr.calloutColor;
  14895. ctx.save();
  14896. ctx.globalAlpha *= attr.callout;
  14897. if (ctx.globalAlpha > 0 && attr.calloutHasLine) {
  14898. if (calloutColor && calloutColor.isGradient) {
  14899. calloutColor = calloutColor.getStops()[0].color;
  14900. }
  14901. ctx.strokeStyle = calloutColor;
  14902. ctx.fillStyle = calloutColor;
  14903. ctx.lineWidth = attr.calloutWidth;
  14904. ctx.beginPath();
  14905. ctx.moveTo(me.attr.calloutStartX, me.attr.calloutStartY);
  14906. ctx.lineTo(me.attr.calloutEndX, me.attr.calloutEndY);
  14907. ctx.stroke();
  14908. ctx.beginPath();
  14909. ctx.arc(me.attr.calloutStartX, me.attr.calloutStartY, 1 * attr.calloutWidth, 0, 2 * Math.PI, true);
  14910. ctx.fill();
  14911. ctx.beginPath();
  14912. ctx.arc(me.attr.calloutEndX, me.attr.calloutEndY, 1 * attr.calloutWidth, 0, 2 * Math.PI, true);
  14913. ctx.fill();
  14914. }
  14915. ctx.restore();
  14916. Ext.draw.sprite.Text.prototype.render.apply(me, arguments);
  14917. }
  14918. });
  14919. /**
  14920. * Series is the abstract class containing the common logic to all chart series.
  14921. * Series includes methods from Labels, Highlights, and Callouts mixins. This class
  14922. * implements the logic of animating, hiding, showing all elements and returning the
  14923. * color of the series to be used as a legend item.
  14924. *
  14925. * ## Listeners
  14926. *
  14927. * The series class supports listeners via the Observable syntax.
  14928. *
  14929. * For example:
  14930. *
  14931. * Ext.create('Ext.chart.CartesianChart', {
  14932. * plugins: {
  14933. * chartitemevents: {
  14934. * moveEvents: true
  14935. * }
  14936. * },
  14937. * store: {
  14938. * fields: ['pet', 'households', 'total'],
  14939. * data: [
  14940. * {pet: 'Cats', households: 38, total: 93},
  14941. * {pet: 'Dogs', households: 45, total: 79},
  14942. * {pet: 'Fish', households: 13, total: 171}
  14943. * ]
  14944. * },
  14945. * axes: [{
  14946. * type: 'numeric',
  14947. * position: 'left'
  14948. * }, {
  14949. * type: 'category',
  14950. * position: 'bottom'
  14951. * }],
  14952. * series: [{
  14953. * type: 'bar',
  14954. * xField: 'pet',
  14955. * yField: 'households',
  14956. * listeners: {
  14957. * itemmousemove: function (series, item, event) {
  14958. * console.log('itemmousemove', item.category, item.field);
  14959. * }
  14960. * }
  14961. * }, {
  14962. * type: 'line',
  14963. * xField: 'pet',
  14964. * yField: 'total',
  14965. * marker: true
  14966. * }]
  14967. * });
  14968. *
  14969. */
  14970. Ext.define('Ext.chart.series.Series', {
  14971. requires: [
  14972. 'Ext.chart.Util',
  14973. 'Ext.chart.Markers',
  14974. 'Ext.chart.sprite.Label',
  14975. 'Ext.tip.ToolTip'
  14976. ],
  14977. mixins: [
  14978. 'Ext.mixin.Observable',
  14979. 'Ext.mixin.Bindable'
  14980. ],
  14981. isSeries: true,
  14982. defaultBindProperty: 'store',
  14983. /**
  14984. * @property {String} type
  14985. * The type of series. Set in subclasses.
  14986. * @protected
  14987. */
  14988. type: null,
  14989. /**
  14990. * @property {String} seriesType
  14991. * Default series sprite type.
  14992. */
  14993. seriesType: 'sprite',
  14994. identifiablePrefix: 'ext-line-',
  14995. observableType: 'series',
  14996. darkerStrokeRatio: 0.15,
  14997. /**
  14998. * @event itemmousemove
  14999. * Fires when the mouse is moved on a series item.
  15000. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15001. * plugin be added to the chart.
  15002. * @param {Ext.chart.series.Series} series
  15003. * @param {Object} item
  15004. * @param {Event} event
  15005. */
  15006. /**
  15007. * @event itemmouseup
  15008. * Fires when a mouseup event occurs on a series item.
  15009. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15010. * plugin be added to the chart.
  15011. * @param {Ext.chart.series.Series} series
  15012. * @param {Object} item
  15013. * @param {Event} event
  15014. */
  15015. /**
  15016. * @event itemmousedown
  15017. * Fires when a mousedown event occurs on a series item.
  15018. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15019. * plugin be added to the chart.
  15020. * @param {Ext.chart.series.Series} series
  15021. * @param {Object} item
  15022. * @param {Event} event
  15023. */
  15024. /**
  15025. * @event itemmouseover
  15026. * Fires when the mouse enters a series item.
  15027. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15028. * plugin be added to the chart.
  15029. * @param {Ext.chart.series.Series} series
  15030. * @param {Object} item
  15031. * @param {Event} event
  15032. */
  15033. /**
  15034. * @event itemmouseout
  15035. * Fires when the mouse exits a series item.
  15036. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15037. * plugin be added to the chart.
  15038. * @param {Ext.chart.series.Series} series
  15039. * @param {Object} item
  15040. * @param {Event} event
  15041. */
  15042. /**
  15043. * @event itemclick
  15044. * Fires when a click event occurs on a series item.
  15045. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15046. * plugin be added to the chart.
  15047. * @param {Ext.chart.series.Series} series
  15048. * @param {Object} item
  15049. * @param {Event} event
  15050. */
  15051. /**
  15052. * @event itemdblclick
  15053. * Fires when a double click event occurs on a series item.
  15054. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15055. * plugin be added to the chart.
  15056. * @param {Ext.chart.series.Series} series
  15057. * @param {Object} item
  15058. * @param {Event} event
  15059. */
  15060. /**
  15061. * @event itemtap
  15062. * Fires when a tap event occurs on a series item.
  15063. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15064. * plugin be added to the chart.
  15065. * @param {Ext.chart.series.Series} series
  15066. * @param {Object} item
  15067. * @param {Event} event
  15068. */
  15069. /**
  15070. * @event chartattached
  15071. * Fires when the {@link Ext.chart.AbstractChart} has been attached to this series.
  15072. * @param {Ext.chart.AbstractChart} chart
  15073. * @param {Ext.chart.series.Series} series
  15074. */
  15075. /**
  15076. * @event chartdetached
  15077. * Fires when the {@link Ext.chart.AbstractChart} has been detached from this series.
  15078. * @param {Ext.chart.AbstractChart} chart
  15079. * @param {Ext.chart.series.Series} series
  15080. */
  15081. /**
  15082. * @event storechange
  15083. * Fires when the store of the series changes.
  15084. * @param {Ext.chart.series.Series} series
  15085. * @param {Ext.data.Store} newStore
  15086. * @param {Ext.data.Store} oldStore
  15087. */
  15088. config: {
  15089. /**
  15090. * @private
  15091. * @cfg {Object} chart The chart that the series is bound.
  15092. */
  15093. chart: null,
  15094. /**
  15095. * @cfg {String|String[]} title
  15096. * The human-readable name of the series (displayed in the legend).
  15097. * If the series is stacked (has multiple components in it) this
  15098. * should be an array, where each string corresponds to a stacked component.
  15099. */
  15100. title: null,
  15101. /**
  15102. * @cfg {Function} renderer
  15103. * A function that can be provided to set custom styling properties to each
  15104. * rendered element. It receives `(sprite, config, rendererData, index)`
  15105. * as parameters.
  15106. *
  15107. * @param {Object} sprite The sprite affected by the renderer.
  15108. * The visual attributes are in `sprite.attr`.
  15109. * The data field is available in `sprite.getField()`.
  15110. * @param {Object} config The sprite configuration, which varies with the series
  15111. * and the type of sprite. For instance, a Line chart sprite might have just the
  15112. * `x` and `y` properties while a Bar chart sprite also has `width` and `height`.
  15113. * A `type` might be present too. For instance to draw each marker and each segment
  15114. * of a Line chart, the renderer is called with the `config.type` set to either
  15115. * `marker` or `line`.
  15116. * @param {Object} rendererData A record with different properties depending on
  15117. * the type of chart. The only guaranteed property is `rendererData.store`, the
  15118. * store used by the series. In some cases, a store may not exist: for instance
  15119. * a Gauge chart may read its value directly from its configuration; in this case
  15120. * rendererData.store is null and the value is available in rendererData.value.
  15121. * @param {Number} index The index of the sprite. It is usually the index of the
  15122. * store record associated with the sprite, in which case the record can be obtained
  15123. * with `store.getData().items[index]`. If the chart is not associated with a store,
  15124. * the index represents the index of the sprite within the series. For instance
  15125. * a Gauge chart may have as many sprites as there are sectors in the background of
  15126. * the gauge, plus one for the needle.
  15127. *
  15128. * @return {Object} The attributes that have been changed or added.
  15129. * Note: it is usually possible to add or modify the attributes directly into the
  15130. * `config` parameter and not return anything, but returning an object with only
  15131. * those attributes that have been changed may allow for optimizations in the
  15132. * rendering of some series. Example to draw every other marker in red:
  15133. *
  15134. * renderer: function (sprite, config, rendererData, index) {
  15135. * if (config.type === 'marker') {
  15136. * return { strokeStyle: (index % 2 === 0 ? 'red' : 'black') };
  15137. * }
  15138. * }
  15139. *
  15140. * @controllable
  15141. */
  15142. renderer: null,
  15143. /**
  15144. * @cfg {Boolean} showInLegend
  15145. * Whether to show this series in the legend.
  15146. */
  15147. showInLegend: true,
  15148. /**
  15149. * @private
  15150. * Trigger drawlistener flag
  15151. */
  15152. triggerAfterDraw: false,
  15153. /**
  15154. * @private
  15155. */
  15156. theme: null,
  15157. /**
  15158. * @cfg {Object} style Custom style configuration for the sprite used in the series.
  15159. * It overrides the style that is provided by the current theme.
  15160. */
  15161. style: {},
  15162. /**
  15163. * @cfg {Object} subStyle This is the cyclic used if the series has multiple sprites.
  15164. */
  15165. subStyle: {},
  15166. /**
  15167. * @private
  15168. * @cfg {Object} themeStyle Style configuration that is provided by the current theme.
  15169. * It is composed of five objects:
  15170. * @cfg {Object} themeStyle.style Properties common to all the series,
  15171. * for instance the 'lineWidth'.
  15172. * @cfg {Object} themeStyle.subStyle Cyclic used if the series has multiple sprites.
  15173. * @cfg {Object} themeStyle.label Sprite config for the labels,
  15174. * for instance the font and color.
  15175. * @cfg {Object} themeStyle.marker Sprite config for the markers,
  15176. * for instance the size and stroke color.
  15177. * @cfg {Object} themeStyle.markerSubStyle Cyclic used if series have multiple marker sprites.
  15178. */
  15179. themeStyle: {},
  15180. /**
  15181. * @cfg {Array} colors
  15182. * An array of color values which is used, in order of appearance, by the series. Each series
  15183. * can request one or more colors from the array. Radar, Scatter or Line charts require just
  15184. * one color each. Candlestick and OHLC require two (1 for drops + 1 for rises). Pie charts
  15185. * and Stacked charts (like Bar or Pie charts) require one color for each data category
  15186. * they represent, so one color for each slice of a Pie chart or each segment (not bar) of
  15187. * a Bar chart.
  15188. * It overrides the colors that are provided by the current theme.
  15189. */
  15190. colors: null,
  15191. /**
  15192. * @cfg {Boolean|Number} useDarkerStrokeColor
  15193. * Colors for the series can be set directly through the 'colors' config, or indirectly
  15194. * with the current theme or the 'colors' config that is set onto the chart. These colors
  15195. * are used as "fill color". Set this config to true, if you want a darker color for the
  15196. * strokes. Set it to false if you want to use the same color as the fill color.
  15197. * Alternatively, you can set it to a number between 0 and 1 to control how much darker
  15198. * the strokes should be.
  15199. * Note: this should be initial config and cannot be changed later on.
  15200. */
  15201. useDarkerStrokeColor: true,
  15202. /**
  15203. * @cfg {Object} store The store to use for this series. If not specified,
  15204. * the series will use the chart's {@link Ext.chart.AbstractChart#store store}.
  15205. */
  15206. store: null,
  15207. /**
  15208. * @cfg {Object} label
  15209. * Object with the following properties:
  15210. *
  15211. * @cfg {String} label.display
  15212. *
  15213. * Specifies the presence and position of the labels.
  15214. * The possible values depend on the series type.
  15215. * For Line and Scatter series: 'under' | 'over' | 'rotate'.
  15216. * For Bar and 3D Bar series: 'insideStart' | 'insideEnd' | 'outside'.
  15217. * For Pie series: 'inside' | 'outside' | 'rotate' | 'horizontal' | 'vertical'.
  15218. * Area, Radar and Candlestick series don't support labels.
  15219. * For Area and Radar series please consider using {@link #tooltip tooltips} instead.
  15220. * 3D Pie series currently always display labels 'outside'.
  15221. * For all series: 'none' hides the labels.
  15222. *
  15223. * Default value: 'none'.
  15224. *
  15225. * @cfg {String} label.color
  15226. *
  15227. * The color of the label text.
  15228. *
  15229. * Default value: '#000' (black).
  15230. *
  15231. * @cfg {String|String[]} label.field
  15232. *
  15233. * The name(s) of the field(s) to be displayed in the labels. If your chart has 3 series
  15234. * that correspond to the fields 'a', 'b', and 'c' of your model, and you only want to
  15235. * display labels for the series 'c', you must still provide an array `[null, null, 'c']`.
  15236. *
  15237. * Default value: null.
  15238. *
  15239. * @cfg {String} label.font
  15240. *
  15241. * The font used for the labels.
  15242. *
  15243. * Default value: '14px Helvetica'.
  15244. *
  15245. * @cfg {String} label.orientation
  15246. *
  15247. * Either 'horizontal' or 'vertical'. If not set (default), the orientation is inferred
  15248. * from the value of the flipXY property of the series.
  15249. *
  15250. * Default value: ''.
  15251. *
  15252. * @cfg {Function} label.renderer
  15253. *
  15254. * Optional function for formatting the label into a displayable value.
  15255. *
  15256. * The arguments to the method are:
  15257. *
  15258. * - *`text`*, *`sprite`*, *`config`*, *`rendererData`*, *`index`*
  15259. *
  15260. * Label's renderer is passed the same arguments as {@link #renderer}
  15261. * plus one extra 'text' argument which comes first.
  15262. *
  15263. * @return {Object|String} The attributes that have been changed or added,
  15264. * or the text for the label.
  15265. * Example to enclose every other label in parentheses:
  15266. *
  15267. * renderer: function (text) {
  15268. * if (index % 2 == 0) {
  15269. * return '(' + text + ')'
  15270. * }
  15271. * }
  15272. */
  15273. label: null,
  15274. /**
  15275. * @cfg {Number} labelOverflowPadding
  15276. * Extra distance value for which the labelOverflow listener is triggered.
  15277. */
  15278. labelOverflowPadding: null,
  15279. /**
  15280. * @cfg {Boolean} showMarkers
  15281. * Whether markers should be displayed at the data points along the line. If true,
  15282. * then the {@link #marker} config item will determine the markers' styling.
  15283. */
  15284. showMarkers: true,
  15285. /**
  15286. * @cfg {Object|Boolean} marker
  15287. * The sprite template used by marker instances on the series.
  15288. * If the value of the marker config is set to `true` or the type
  15289. * of the sprite instance is not specified, the {@link Ext.draw.sprite.Circle}
  15290. * sprite will be used.
  15291. *
  15292. * Examples:
  15293. *
  15294. * marker: true
  15295. *
  15296. * marker: {
  15297. * radius: 8
  15298. * }
  15299. *
  15300. * marker: {
  15301. * type: 'arrow',
  15302. * animation: {
  15303. * duration: 200,
  15304. * easing: 'backOut'
  15305. * }
  15306. * }
  15307. */
  15308. marker: null,
  15309. /**
  15310. * @cfg {Object} markerSubStyle
  15311. * This is cyclic used if series have multiple marker sprites.
  15312. */
  15313. markerSubStyle: null,
  15314. /**
  15315. * @protected
  15316. * @cfg {Object} itemInstancing
  15317. * The sprite template used to create sprite instances in the series.
  15318. */
  15319. itemInstancing: null,
  15320. /**
  15321. * @cfg {Object} background
  15322. * Sets the background of the surface the series is attached.
  15323. */
  15324. background: null,
  15325. /**
  15326. * @protected
  15327. * @cfg {Ext.draw.Surface} surface
  15328. * The chart surface used to render series sprites.
  15329. */
  15330. surface: null,
  15331. /**
  15332. * @protected
  15333. * @cfg {Object} overlaySurface
  15334. * The surface used to render series labels.
  15335. */
  15336. overlaySurface: null,
  15337. /**
  15338. * @cfg {Boolean|Array} hidden
  15339. */
  15340. hidden: false,
  15341. /**
  15342. * @cfg {Boolean/Object} highlight
  15343. * The sprite attributes that will be applied to the highlighted items in the series.
  15344. * If set to 'true', the default highlight style from {@link #highlightCfg} will be used.
  15345. * If the value of this config is an object, it will be merged with the {@link #highlightCfg}.
  15346. * In case merging of 'highlight' and 'highlightCfg' configs in not the desired behavior,
  15347. * provide the 'highlightCfg' instead.
  15348. */
  15349. highlight: false,
  15350. /**
  15351. * @protected
  15352. * @cfg {Object} highlightCfg
  15353. * The default style for the highlighted item.
  15354. * Used when {@link #highlight} config was simply set to 'true' instead of specifying
  15355. * a style.
  15356. */
  15357. highlightCfg: {
  15358. // Make custom highlightCfg's in subclasses replace this one.
  15359. merge: function(value) {
  15360. return value;
  15361. },
  15362. $value: {
  15363. fillStyle: 'yellow',
  15364. strokeStyle: 'red'
  15365. }
  15366. },
  15367. /**
  15368. * @cfg {Object} animation The series animation configuration.
  15369. * By default, the series is using the same animation the chart uses,
  15370. * if it's own animation is not explicitly configured.
  15371. */
  15372. animation: null,
  15373. /**
  15374. * @cfg {Object} tooltip
  15375. * Add tooltips to the visualization's markers. The config options for the
  15376. * tooltip are the same configuration used with {@link Ext.tip.ToolTip} plus a
  15377. * `renderer` config option and a `scope` for the renderer. For example:
  15378. *
  15379. * tooltip: {
  15380. * trackMouse: true,
  15381. * width: 140,
  15382. * height: 28,
  15383. * renderer: function (toolTip, record, ctx) {
  15384. * toolTip.setHtml(record.get('name') + ': ' + record.get('data1') + ' views');
  15385. * }
  15386. * }
  15387. *
  15388. * Note that tooltips are shown for series markers and won't work
  15389. * if the {@link #marker} is not configured.
  15390. *
  15391. * You can also configure
  15392. * {@link Ext.chart.interactions.ItemHighlight#multiTooltips}
  15393. * to display multiple tooltips for adjacent or overlapping Line series
  15394. * data points within {@link Ext.chart.series.Line#selectionTolerance} radius.
  15395. *
  15396. * @cfg {Object} tooltip.scope The scope to use when the renderer function is
  15397. * called. Defaults to the Series instance.
  15398. * @cfg {Function} tooltip.renderer An 'interceptor' method which can be used to
  15399. * modify the tooltip attributes before it is shown. The renderer function is
  15400. * passed the following params:
  15401. * @cfg {Ext.tip.ToolTip} tooltip.renderer.toolTip The tooltip instance
  15402. * @cfg {Ext.data.Model} tooltip.renderer.record The record instance for the
  15403. * chart item (sprite) currently targeted by the tooltip.
  15404. * @cfg {Object} tooltip.renderer.ctx A data object with values relating to the
  15405. * currently targeted chart sprite
  15406. * @cfg {String} tooltip.renderer.ctx.category The type of sprite passed to the
  15407. * renderer function (will be "items", "markers", or "labels" depending on the
  15408. * target sprite of the tooltip)
  15409. * @cfg {String} tooltip.renderer.ctx.field The {@link #yField} for the series
  15410. * @cfg {Number} tooltip.renderer.ctx.index The target sprite's index within the
  15411. * series' items
  15412. * @cfg {Ext.data.Model} tooltip.renderer.ctx.record The record instance for the
  15413. * chart item (sprite) currently targeted by the tooltip.
  15414. * @cfg {Ext.chart.series.Series} tooltip.renderer.ctx.series The series instance
  15415. * containing the tooltip's target sprite
  15416. * @cfg {Ext.draw.sprite.Sprite} tooltip.renderer.ctx.sprite The sprite (item)
  15417. * target of the tooltip
  15418. */
  15419. tooltip: null
  15420. },
  15421. directions: [],
  15422. sprites: null,
  15423. /**
  15424. * @private
  15425. * Returns the number of colors this series needs.
  15426. * A Pie chart needs one color per slice while a Stacked Bar chart needs one per segment.
  15427. * An OHLC chart needs 2 colors (one for drops, one for rises), and most other charts
  15428. * need just a single color.
  15429. */
  15430. themeColorCount: function() {
  15431. return 1;
  15432. },
  15433. /**
  15434. * @private
  15435. * @property
  15436. * Series, where the number of sprites (an so unique colors they require)
  15437. * depends on the number of records in the store should set this to 'true'.
  15438. */
  15439. isStoreDependantColorCount: false,
  15440. /**
  15441. * @private
  15442. * Returns the number of markers this series needs.
  15443. * Currently, only the Line, Scatter and Radar series use markers - and they need
  15444. * just one each.
  15445. */
  15446. themeMarkerCount: function() {
  15447. return 0;
  15448. },
  15449. /**
  15450. * @private
  15451. * Each series has configs that tell which store record fields to use as data
  15452. * for a certain dimension. For example, `xField`, `yField` for most cartesian series,
  15453. * `angleField`, `radiusField` for polar series, `openField`, ..., `closeField`
  15454. * for CandleStick series, etc. The field category is an array of capitalized config
  15455. * names, minus the 'Field' part, to use as data for a certain dimension.
  15456. * For example, for CandleStick series we have:
  15457. *
  15458. * fieldCategoryY: ['Open', 'High', 'Low', 'Close']
  15459. *
  15460. * While for generic Cartesian series it is simply:
  15461. *
  15462. * fieldCategoryY: ['Y']
  15463. *
  15464. * This method fetches the values of those configs, i.e. the actual record fields to use.
  15465. *
  15466. * The {@link #coordinate} method in turn will use the values from the `fieldCategory`
  15467. * array to set data attributes of the series sprite. E.g., in case of CandleStick series,
  15468. * the following attributes will be set based on the values in the `fieldCategoryY` array:
  15469. *
  15470. * `dataOpen`, `dataHigh`, `dataLow`, `dataClose`
  15471. *
  15472. * Where the value of each attribute is a coordinated array of data from the corresponding
  15473. * field.
  15474. *
  15475. * @param {String[]} fieldCategory
  15476. * @return {String[]}
  15477. */
  15478. getFields: function(fieldCategory) {
  15479. var me = this,
  15480. fields = [],
  15481. ln = fieldCategory.length,
  15482. i, field;
  15483. for (i = 0; i < ln; i++) {
  15484. field = me['get' + fieldCategory[i] + 'Field']();
  15485. if (Ext.isArray(field)) {
  15486. fields.push.apply(fields, field);
  15487. } else {
  15488. fields.push(field);
  15489. }
  15490. }
  15491. return fields;
  15492. },
  15493. applyAnimation: function(animation, oldAnimation) {
  15494. var chart = this.getChart();
  15495. if (!chart.isSettingSeriesAnimation) {
  15496. this.isUserAnimation = true;
  15497. }
  15498. return Ext.chart.Util.applyAnimation(animation, oldAnimation);
  15499. },
  15500. updateAnimation: function(animation) {
  15501. var sprites = this.getSprites(),
  15502. itemsMarker, markersMarker, i, ln, sprite;
  15503. for (i = 0 , ln = sprites.length; i < ln; i++) {
  15504. sprite = sprites[i];
  15505. if (sprite.isMarkerHolder) {
  15506. itemsMarker = sprite.getMarker('items');
  15507. if (itemsMarker) {
  15508. itemsMarker.getTemplate().setAnimation(animation);
  15509. }
  15510. markersMarker = sprite.getMarker('markers');
  15511. if (markersMarker) {
  15512. markersMarker.getTemplate().setAnimation(animation);
  15513. }
  15514. }
  15515. sprite.setAnimation(animation);
  15516. }
  15517. },
  15518. getAnimation: function() {
  15519. var chart = this.getChart(),
  15520. animation;
  15521. if (chart && chart.animationSuspendCount) {
  15522. animation = {
  15523. duration: 0
  15524. };
  15525. } else {
  15526. if (this.isUserAnimation) {
  15527. animation = this.callParent();
  15528. } else {
  15529. animation = chart.getAnimation();
  15530. }
  15531. }
  15532. return animation;
  15533. },
  15534. updateTitle: function() {
  15535. var me = this,
  15536. chart = me.getChart();
  15537. if (chart && !chart.isInitializing) {
  15538. chart.refreshLegendStore();
  15539. }
  15540. },
  15541. applyHighlight: function(highlight, oldHighlight) {
  15542. var me = this,
  15543. highlightCfg = me.getHighlightCfg();
  15544. if (Ext.isObject(highlight)) {
  15545. highlight = Ext.merge({}, highlightCfg, highlight);
  15546. } else if (highlight === true) {
  15547. highlight = highlightCfg;
  15548. }
  15549. if (highlight) {
  15550. highlight.type = 'highlight';
  15551. }
  15552. return highlight && Ext.merge({}, oldHighlight, highlight);
  15553. },
  15554. updateHighlight: function(highlight) {
  15555. var me = this,
  15556. sprites = me.sprites,
  15557. highlightCfg = me.getHighlightCfg(),
  15558. i, ln, sprite, items, markers;
  15559. me.getStyle();
  15560. // Make sure the 'markers' sprite has been created,
  15561. // so that we can set the 'style' config of its 'highlight' modifier here.
  15562. me.getMarker();
  15563. if (!Ext.Object.isEmpty(highlight)) {
  15564. me.addItemHighlight();
  15565. for (i = 0 , ln = sprites.length; i < ln; i++) {
  15566. sprite = sprites[i];
  15567. if (sprite.isMarkerHolder) {
  15568. items = sprite.getMarker('items');
  15569. if (items) {
  15570. items.getTemplate().modifiers.highlight.setStyle(highlight);
  15571. }
  15572. markers = sprite.getMarker('markers');
  15573. if (markers) {
  15574. markers.getTemplate().modifiers.highlight.setStyle(highlight);
  15575. }
  15576. }
  15577. }
  15578. } else if (!Ext.Object.equals(highlightCfg, this.defaultConfig.highlightCfg)) {
  15579. this.addItemHighlight();
  15580. }
  15581. },
  15582. updateHighlightCfg: function(highlightCfg) {
  15583. // Make sure to add the 'itemhighlight' interaction to the series, if the default
  15584. // highlight style changes, even if the 'highlight' config isn't set (defaults to false),
  15585. // since we probably want to use item highlighting now or later, if we are changing
  15586. // the default highlight style.
  15587. // This updater will be triggered by the 'highlight' applier, and the 'addItemHighlight'
  15588. // call here will in turn call 'getHighlight' down the call stack, which will return
  15589. // 'undefined' since the value hasn't been processed yet. So we don't call 'addItemHighlight'
  15590. // here during configuration and instead call it in the 'highlight' updater, if it hasn't
  15591. // already been called ('highlight' config is set to 'false').
  15592. if (!this.isConfiguring && !Ext.Object.equals(highlightCfg, this.defaultConfig.highlightCfg)) {
  15593. this.addItemHighlight();
  15594. }
  15595. },
  15596. applyItemInstancing: function(config, oldConfig) {
  15597. if (config && oldConfig && (!config.type || config.type === oldConfig.type)) {
  15598. // Have to merge to a new object, or the updater won't be called.
  15599. config = Ext.merge({}, oldConfig, config);
  15600. }
  15601. if (config && !config.type) {
  15602. config = null;
  15603. }
  15604. return config;
  15605. },
  15606. setAttributesForItem: function(item, change) {
  15607. var sprite = item && item.sprite,
  15608. i;
  15609. if (sprite) {
  15610. if (sprite.isMarkerHolder && item.category === 'items') {
  15611. sprite.putMarker(item.category, change, item.index, false, true);
  15612. }
  15613. if (sprite.isMarkerHolder && item.category === 'markers') {
  15614. sprite.putMarker(item.category, change, item.index, false, true);
  15615. } else if (sprite.isInstancing) {
  15616. sprite.setAttributesFor(item.index, change);
  15617. } else if (Ext.isArray(sprite)) {
  15618. // In some instances, like with the 3D pie series,
  15619. // an item can be composed of multiple sprites
  15620. // (e.g. 8 sprites are used to render a single 3D pie slice).
  15621. for (i = 0; i < sprite.length; i++) {
  15622. sprite[i].setAttributes(change);
  15623. }
  15624. } else {
  15625. sprite.setAttributes(change);
  15626. }
  15627. }
  15628. },
  15629. getBBoxForItem: function(item) {
  15630. var sprite = item && item.sprite,
  15631. result = null;
  15632. if (sprite) {
  15633. if (sprite.getMarker('items') && item.category === 'items') {
  15634. result = sprite.getMarkerBBox(item.category, item.index);
  15635. } else if (sprite instanceof Ext.draw.sprite.Instancing) {
  15636. result = sprite.getBBoxFor(item.index);
  15637. } else {
  15638. result = sprite.getBBox();
  15639. }
  15640. }
  15641. return result;
  15642. },
  15643. /**
  15644. * @private
  15645. * @property
  15646. * The range of "coordinated" data.
  15647. * Typically, for two directions ('X' and 'Y') the `dataRange` would look like this:
  15648. *
  15649. * dataRange[0] - minX
  15650. * dataRange[1] - minY
  15651. * dataRange[2] - maxX
  15652. * dataRange[3] - maxY
  15653. *
  15654. * And the series' {@link #coordinate} method would be called like this:
  15655. *
  15656. * coordinate('X', 0, 2)
  15657. * coordinate('Y', 1, 2)
  15658. *
  15659. * For numbers, coordinated data are numbers themselves.
  15660. * For categories - their indexes.
  15661. * For Date objects - their timestamps.
  15662. * In other words, whatever source data we have, it has to be converted to numbers
  15663. * before it can be plotted.
  15664. */
  15665. dataRange: null,
  15666. constructor: function(config) {
  15667. var me = this,
  15668. id;
  15669. config = config || {};
  15670. // Backward compatibility with Ext.
  15671. if (config.tips) {
  15672. config = Ext.apply({
  15673. tooltip: config.tips
  15674. }, config);
  15675. }
  15676. // Backward compatibility with Touch.
  15677. if (config.highlightCfg) {
  15678. config = Ext.apply({
  15679. highlight: config.highlightCfg
  15680. }, config);
  15681. }
  15682. if ('id' in config) {
  15683. id = config.id;
  15684. } else if ('id' in me.config) {
  15685. id = me.config.id;
  15686. } else {
  15687. id = me.getId();
  15688. }
  15689. me.setId(id);
  15690. me.sprites = [];
  15691. me.dataRange = [];
  15692. me.mixins.observable.constructor.call(me, config);
  15693. me.initBindable();
  15694. },
  15695. lookupViewModel: function(skipThis) {
  15696. // Override the Bindable's method to redirect view model
  15697. // lookup to the chart.
  15698. var chart = this.getChart();
  15699. return chart ? chart.lookupViewModel(skipThis) : null;
  15700. },
  15701. applyTooltip: function(tooltip, oldTooltip) {
  15702. var config = Ext.apply({
  15703. xtype: 'tooltip',
  15704. renderer: Ext.emptyFn,
  15705. constrainPosition: true,
  15706. shrinkWrapDock: true,
  15707. autoHide: true,
  15708. hideDelay: 200,
  15709. mouseOffset: [
  15710. 20,
  15711. 20
  15712. ],
  15713. trackMouse: true
  15714. }, tooltip);
  15715. return Ext.create(config);
  15716. },
  15717. updateTooltip: function() {
  15718. // Tooltips can't work without the 'itemhighlight' or the 'itemedit' interaction.
  15719. this.addItemHighlight();
  15720. },
  15721. // Adds the 'itemhighlight' interaction to the chart that owns the series.
  15722. addItemHighlight: function() {
  15723. var chart = this.getChart();
  15724. if (!chart) {
  15725. return;
  15726. }
  15727. var interactions = chart.getInteractions(),
  15728. i, interaction, hasRequiredInteraction;
  15729. for (i = 0; i < interactions.length; i++) {
  15730. interaction = interactions[i];
  15731. if (interaction.isItemHighlight || interaction.isItemEdit) {
  15732. hasRequiredInteraction = true;
  15733. break;
  15734. }
  15735. }
  15736. if (!hasRequiredInteraction) {
  15737. interactions.push('itemhighlight');
  15738. chart.setInteractions(interactions);
  15739. }
  15740. },
  15741. showTooltip: function(item, event) {
  15742. var me = this,
  15743. tooltip = me.getTooltip();
  15744. if (!tooltip) {
  15745. return;
  15746. }
  15747. Ext.callback(tooltip.renderer, tooltip.scope, [
  15748. tooltip,
  15749. item.record,
  15750. item
  15751. ], 0, me);
  15752. tooltip.showBy(event);
  15753. },
  15754. showTooltipAt: function(item, x, y) {
  15755. var me = this,
  15756. tooltip = me.getTooltip(),
  15757. mouseOffset = tooltip.config.mouseOffset;
  15758. if (!tooltip || !tooltip.showAt) {
  15759. return;
  15760. }
  15761. if (mouseOffset) {
  15762. x += mouseOffset[0];
  15763. y += mouseOffset[1];
  15764. }
  15765. Ext.callback(tooltip.renderer, tooltip.scope, [
  15766. tooltip,
  15767. item.record,
  15768. item
  15769. ], 0, me);
  15770. tooltip.showAt([
  15771. x,
  15772. y
  15773. ]);
  15774. },
  15775. hideTooltip: function(item, immediate) {
  15776. var me = this,
  15777. tooltip = me.getTooltip();
  15778. if (!tooltip) {
  15779. return;
  15780. }
  15781. if (immediate) {
  15782. tooltip.hide();
  15783. } else {
  15784. tooltip.delayHide();
  15785. }
  15786. },
  15787. applyStore: function(store) {
  15788. return store && Ext.StoreManager.lookup(store);
  15789. },
  15790. getStore: function() {
  15791. return this._store || this.getChart() && this.getChart().getStore();
  15792. },
  15793. updateStore: function(newStore, oldStore) {
  15794. var me = this,
  15795. chart = me.getChart(),
  15796. chartStore = chart && chart.getStore(),
  15797. sprites, sprite, len, i;
  15798. oldStore = oldStore || chartStore;
  15799. if (oldStore && oldStore !== newStore) {
  15800. oldStore.un({
  15801. datachanged: 'onDataChanged',
  15802. update: 'onDataChanged',
  15803. scope: me
  15804. });
  15805. }
  15806. if (newStore) {
  15807. newStore.on({
  15808. datachanged: 'onDataChanged',
  15809. update: 'onDataChanged',
  15810. scope: me
  15811. });
  15812. sprites = me.getSprites();
  15813. for (i = 0 , len = sprites.length; i < len; i++) {
  15814. sprite = sprites[i];
  15815. if (sprite.setStore) {
  15816. sprite.setStore(newStore);
  15817. }
  15818. }
  15819. me.onDataChanged();
  15820. }
  15821. me.fireEvent('storechange', me, newStore, oldStore);
  15822. },
  15823. onStoreChange: function(chart, newStore, oldStore) {
  15824. if (!this._store) {
  15825. this.updateStore(newStore, oldStore);
  15826. }
  15827. },
  15828. defaultRange: [
  15829. 0,
  15830. 1
  15831. ],
  15832. /**
  15833. * @private
  15834. * @param direction {'X'/'Y'}
  15835. * @param directionOffset
  15836. * @param directionCount
  15837. */
  15838. coordinate: function(direction, directionOffset, directionCount) {
  15839. var me = this,
  15840. store = me.getStore(),
  15841. hidden = me.getHidden(),
  15842. items = store.getData().items,
  15843. axis = me['get' + direction + 'Axis'](),
  15844. dataRange = [
  15845. NaN,
  15846. NaN
  15847. ],
  15848. fieldCategory = me['fieldCategory' + direction] || [
  15849. direction
  15850. ],
  15851. fields = me.getFields(fieldCategory),
  15852. i, field, data,
  15853. style = {},
  15854. sprites = me.getSprites(),
  15855. axisRange;
  15856. if (sprites.length && !Ext.isBoolean(hidden) || !hidden) {
  15857. for (i = 0; i < fieldCategory.length; i++) {
  15858. field = fields[i];
  15859. data = me.coordinateData(items, field, axis);
  15860. Ext.chart.Util.expandRange(dataRange, data);
  15861. style['data' + fieldCategory[i]] = data;
  15862. }
  15863. // We don't want to expand the range that has a span of 0 here
  15864. // (e.g. [5, 5] that we'd get if all values for a field are 5).
  15865. // We only want to do this in the Axis, when we calculate the
  15866. // combined range.
  15867. // This is because, if we try to expand the range of values here,
  15868. // and we have multiple fields, the combined range for the axis
  15869. // may not represent the actual range of the data.
  15870. // E.g. if other fields have non-zero span ranges like [4.95, 5.03],
  15871. // [4.91, 5.08], and if the `padding` param to `validateRange` is 0.5,
  15872. // the range of the axis will end up being [4.5, 5.5], because the
  15873. // [5, 5] range of one of the series was expanded to [4.5, 5.5]
  15874. // which encompasses the rest of the ranges.
  15875. dataRange = Ext.chart.Util.validateRange(dataRange, me.defaultRange, 0);
  15876. // See `dataRange` docs.
  15877. me.dataRange[directionOffset] = dataRange[0];
  15878. me.dataRange[directionOffset + directionCount] = dataRange[1];
  15879. style['dataMin' + direction] = dataRange[0];
  15880. style['dataMax' + direction] = dataRange[1];
  15881. if (axis) {
  15882. axisRange = axis.getRange(true);
  15883. axis.setBoundSeriesRange(axisRange);
  15884. }
  15885. for (i = 0; i < sprites.length; i++) {
  15886. sprites[i].setAttributes(style);
  15887. }
  15888. }
  15889. },
  15890. /**
  15891. * @private
  15892. * This method will return an array containing data coordinated by a specific axis.
  15893. * @param {Array} items Store records.
  15894. * @param {String} field The field to fetch from each record.
  15895. * @param {Ext.chart.axis.Axis} axis The axis used to lay out the data.
  15896. * @return {Array}
  15897. */
  15898. coordinateData: function(items, field, axis) {
  15899. var data = [],
  15900. length = items.length,
  15901. layout = axis && axis.getLayout(),
  15902. i, x;
  15903. for (i = 0; i < length; i++) {
  15904. x = items[i].data[field];
  15905. // An empty string (a valid discrete axis value) will be coordinated
  15906. // by the axis layout (if axis is given), otherwise it will be converted
  15907. // to zero (via +'').
  15908. if (!Ext.isEmpty(x, true)) {
  15909. if (layout) {
  15910. data[i] = layout.getCoordFor(x, field, i, items);
  15911. } else {
  15912. x = +x;
  15913. // 'x' can be a category name here.
  15914. data[i] = Ext.isNumber(x) ? x : i;
  15915. }
  15916. } else {
  15917. data[i] = x;
  15918. }
  15919. }
  15920. return data;
  15921. },
  15922. updateLabelData: function() {
  15923. var label = this.getLabel();
  15924. if (!label) {
  15925. return;
  15926. }
  15927. var store = this.getStore(),
  15928. items = store.getData().items,
  15929. sprites = this.getSprites(),
  15930. labelTpl = label.getTemplate(),
  15931. labelFields = Ext.Array.from(labelTpl.getField()),
  15932. i, j, ln, labels, sprite, field;
  15933. if (!sprites.length || !labelFields.length) {
  15934. return;
  15935. }
  15936. for (i = 0; i < sprites.length; i++) {
  15937. sprite = sprites[i];
  15938. if (!sprite.getField) {
  15939. // The 'gauge' series is misnormer, its sprites
  15940. // do not extend from the base Series sprite and
  15941. // so do not have the 'field' config. They also
  15942. // don't support labels in the traditional sense.
  15943. continue;
  15944. }
  15945. labels = [];
  15946. field = sprite.getField();
  15947. if (Ext.Array.indexOf(labelFields, field) < 0) {
  15948. field = labelFields[i];
  15949. }
  15950. for (j = 0 , ln = items.length; j < ln; j++) {
  15951. labels.push(items[j].get(field));
  15952. }
  15953. sprite.setAttributes({
  15954. labels: labels
  15955. });
  15956. }
  15957. },
  15958. /**
  15959. * @private
  15960. *
  15961. * *** Data processing overview. ***
  15962. *
  15963. * The data is processed in the following order:
  15964. *
  15965. * 1) chart.processData() - calls `processData` of all series
  15966. * 2) series.processData() - calls `processData` of all bound axes,
  15967. * or jumps to (5) directly, if the series has no axis
  15968. * in this direction
  15969. * 3) axis.processData() - calls the `processData` of its own layout
  15970. * 4) axisLayout.processData() - calls `coordinateX/Y` of all bound series
  15971. * 5) series.coordinateX/Y - calls its own `coordinate` method in that direction
  15972. * 6) series.coordinate - calls its own `coordinateData` method using the right
  15973. * record fields and axes
  15974. * 7) series.coordinateData - calls `getCoordFor` of the axis layout for the given
  15975. * field
  15976. * 8) layout.getCoordFor - returns a numeric value for the given field value,
  15977. * whatever its type may be
  15978. *
  15979. * The `dataX`, `dataY` attributes of the series' sprites are set by the
  15980. * `series.coordinate` method using the data returned by the `coordinateData`.
  15981. * `series.coordinate` also calculates the range of said data (via `expandRange`)
  15982. * and sets the `dataMinX/Y`, `dataMaxX/Y` attributes of the series' sprites.
  15983. */
  15984. processData: function() {
  15985. var me = this;
  15986. if (me.isProcessingData || !me.getStore()) {
  15987. return;
  15988. }
  15989. var directions = this.directions,
  15990. i,
  15991. ln = directions.length,
  15992. direction, axis, name;
  15993. me.isProcessingData = true;
  15994. for (i = 0; i < ln; i++) {
  15995. direction = directions[i];
  15996. axis = me['get' + direction + 'Axis']();
  15997. if (axis) {
  15998. axis.processData(me);
  15999. continue;
  16000. }
  16001. name = 'coordinate' + direction;
  16002. if (me[name]) {
  16003. me[name]();
  16004. }
  16005. }
  16006. me.updateLabelData();
  16007. me.isProcessingData = false;
  16008. },
  16009. applyBackground: function(background) {
  16010. var surface, result;
  16011. if (this.getChart()) {
  16012. surface = this.getSurface();
  16013. surface.setBackground(background);
  16014. result = surface.getBackground();
  16015. } else {
  16016. result = background;
  16017. }
  16018. return result;
  16019. },
  16020. updateChart: function(newChart, oldChart) {
  16021. var me = this,
  16022. store = me._store;
  16023. if (oldChart) {
  16024. oldChart.un('axeschange', 'onAxesChange', me);
  16025. me.clearSprites();
  16026. me.setSurface(null);
  16027. me.setOverlaySurface(null);
  16028. oldChart.unregister(me);
  16029. me.onChartDetached(oldChart);
  16030. if (!store) {
  16031. me.updateStore(null);
  16032. }
  16033. }
  16034. if (newChart) {
  16035. me.setSurface(newChart.getSurface('series'));
  16036. me.setOverlaySurface(newChart.getSurface('overlay'));
  16037. newChart.on('axeschange', 'onAxesChange', me);
  16038. // TODO: Gauge series should render correctly when chart's store is missing.
  16039. // TODO: When store is initially missing the getAxes will return null here,
  16040. // TODO: since applyAxes has actually triggered this series.updateChart call
  16041. // TODO: indirectly.
  16042. // TODO: Figure out why it doesn't go this route when a store is present.
  16043. if (newChart.getAxes()) {
  16044. me.onAxesChange(newChart);
  16045. }
  16046. me.onChartAttached(newChart);
  16047. newChart.register(me);
  16048. if (!store) {
  16049. me.updateStore(newChart.getStore());
  16050. }
  16051. }
  16052. },
  16053. onAxesChange: function(chart, force) {
  16054. if (chart.destroying || chart.destroyed) {
  16055. return;
  16056. }
  16057. var me = this,
  16058. axes = chart.getAxes(),
  16059. axis,
  16060. directionToAxesMap = {},
  16061. directionToFieldsMap = {},
  16062. needHighPrecision = false,
  16063. directions = this.directions,
  16064. direction, i, ln;
  16065. for (i = 0 , ln = directions.length; i < ln; i++) {
  16066. direction = directions[i];
  16067. directionToFieldsMap[direction] = me.getFields(me['fieldCategory' + direction]);
  16068. }
  16069. for (i = 0 , ln = axes.length; i < ln; i++) {
  16070. axis = axes[i];
  16071. direction = axis.getDirection();
  16072. if (!directionToAxesMap[direction]) {
  16073. directionToAxesMap[direction] = [
  16074. axis
  16075. ];
  16076. } else {
  16077. directionToAxesMap[direction].push(axis);
  16078. }
  16079. }
  16080. for (i = 0 , ln = directions.length; i < ln; i++) {
  16081. direction = directions[i];
  16082. if (!force && me['get' + direction + 'Axis']()) {
  16083. continue;
  16084. }
  16085. if (directionToAxesMap[direction]) {
  16086. axis = me.findMatchingAxis(directionToAxesMap[direction], directionToFieldsMap[direction]);
  16087. if (axis) {
  16088. me['set' + direction + 'Axis'](axis);
  16089. if (axis.getNeedHighPrecision()) {
  16090. needHighPrecision = true;
  16091. }
  16092. }
  16093. }
  16094. }
  16095. this.getSurface().setHighPrecision(needHighPrecision);
  16096. },
  16097. /**
  16098. * @private
  16099. * Given the list of axes in a certain direction and a list of series fields in that
  16100. * direction returns the first matching axis for the series in that direction,
  16101. * or undefined if a match wasn't found.
  16102. */
  16103. findMatchingAxis: function(directionAxes, directionFields) {
  16104. var axis, axisFields, i, j;
  16105. for (i = 0; i < directionAxes.length; i++) {
  16106. axis = directionAxes[i];
  16107. axisFields = axis.getFields();
  16108. if (!axisFields.length) {
  16109. return axis;
  16110. } else if (directionFields) {
  16111. for (j = 0; j < directionFields.length; j++) {
  16112. if (Ext.Array.indexOf(axisFields, directionFields[j]) >= 0) {
  16113. return axis;
  16114. }
  16115. }
  16116. }
  16117. }
  16118. },
  16119. onChartDetached: function(oldChart) {
  16120. var me = this;
  16121. me.fireEvent('chartdetached', oldChart, me);
  16122. oldChart.un('storechange', 'onStoreChange', me);
  16123. },
  16124. onChartAttached: function(chart) {
  16125. var me = this;
  16126. me.fireEvent('chartattached', chart, me);
  16127. chart.on('storechange', 'onStoreChange', me);
  16128. me.processData();
  16129. },
  16130. updateOverlaySurface: function(overlaySurface) {
  16131. var label = this.getLabel();
  16132. if (overlaySurface && label) {
  16133. overlaySurface.add(label);
  16134. }
  16135. },
  16136. getLabel: function() {
  16137. return this.labelMarker;
  16138. },
  16139. setLabel: function(label) {
  16140. var me = this,
  16141. chart = me.getChart(),
  16142. marker = me.labelMarker,
  16143. template;
  16144. // The label sprite is reused unless the value of 'label' is falsy,
  16145. // so that we can transition from one attribute set to another with an
  16146. // animation, which is important for example during theme switching.
  16147. if (!label && marker) {
  16148. marker.getTemplate().destroy();
  16149. marker.destroy();
  16150. me.labelMarker = marker = null;
  16151. }
  16152. if (label) {
  16153. if (!marker) {
  16154. marker = me.labelMarker = new Ext.chart.Markers({
  16155. zIndex: 10
  16156. });
  16157. marker.setTemplate(new Ext.chart.sprite.Label());
  16158. me.getOverlaySurface().add(marker);
  16159. }
  16160. template = marker.getTemplate();
  16161. template.setAttributes(label);
  16162. template.setConfig(label);
  16163. if (label.field) {
  16164. template.setField(label.field);
  16165. }
  16166. if (label.display) {
  16167. marker.setAttributes({
  16168. hidden: label.display === 'none'
  16169. });
  16170. }
  16171. marker.setDirty(true);
  16172. }
  16173. // Inform the label about the template change.
  16174. me.updateLabelData();
  16175. if (chart && !chart.isInitializing && !me.isConfiguring) {
  16176. chart.redraw();
  16177. }
  16178. },
  16179. createItemInstancingSprite: function(sprite, itemInstancing) {
  16180. var me = this,
  16181. markers = new Ext.chart.Markers(),
  16182. config = Ext.apply({
  16183. modifiers: 'highlight'
  16184. }, itemInstancing),
  16185. style = me.getStyle(),
  16186. template, animation;
  16187. markers.setAttributes({
  16188. zIndex: Number.MAX_VALUE
  16189. });
  16190. markers.setTemplate(config);
  16191. template = markers.getTemplate();
  16192. template.setAttributes(style);
  16193. animation = template.getAnimation();
  16194. animation.on('animationstart', 'onSpriteAnimationStart', this);
  16195. animation.on('animationend', 'onSpriteAnimationEnd', this);
  16196. sprite.bindMarker('items', markers);
  16197. me.getSurface().add(markers);
  16198. return markers;
  16199. },
  16200. getDefaultSpriteConfig: function() {
  16201. return {
  16202. type: this.seriesType,
  16203. renderer: this.getRenderer()
  16204. };
  16205. },
  16206. updateRenderer: function(renderer) {
  16207. var me = this,
  16208. chart = me.getChart();
  16209. if (chart && chart.isInitializing) {
  16210. return;
  16211. }
  16212. // We have to be careful and not call the 'getSprites' method here, as this
  16213. // method itself may have been called by the 'getSprites' method indirectly already.
  16214. if (me.sprites.length) {
  16215. me.sprites[0].setAttributes({
  16216. renderer: renderer || null
  16217. });
  16218. if (chart && !chart.isInitializing) {
  16219. chart.redraw();
  16220. }
  16221. }
  16222. },
  16223. updateShowMarkers: function(showMarkers) {
  16224. var sprite = this.getSprite(),
  16225. markers = sprite && sprite.getMarker('markers');
  16226. if (markers) {
  16227. markers.getTemplate().setAttributes({
  16228. hidden: !showMarkers
  16229. });
  16230. }
  16231. },
  16232. createSprite: function() {
  16233. var me = this,
  16234. surface = me.getSurface(),
  16235. itemInstancing = me.getItemInstancing(),
  16236. sprite = surface.add(me.getDefaultSpriteConfig()),
  16237. animation, label;
  16238. sprite.setAttributes(me.getStyle());
  16239. sprite.setSeries(me);
  16240. if (itemInstancing) {
  16241. me.createItemInstancingSprite(sprite, itemInstancing);
  16242. }
  16243. if (sprite.isMarkerHolder) {
  16244. label = me.getLabel();
  16245. if (label && label.getTemplate().getField()) {
  16246. sprite.bindMarker('labels', label);
  16247. }
  16248. }
  16249. if (sprite.setStore) {
  16250. sprite.setStore(me.getStore());
  16251. }
  16252. animation = sprite.getAnimation();
  16253. animation.on('animationstart', 'onSpriteAnimationStart', me);
  16254. animation.on('animationend', 'onSpriteAnimationEnd', me);
  16255. me.sprites.push(sprite);
  16256. return sprite;
  16257. },
  16258. /**
  16259. * @method
  16260. * Returns the read-only array of sprites the are used to draw this series.
  16261. */
  16262. getSprites: null,
  16263. /**
  16264. * @private
  16265. * Returns the first sprite. Convenience method for series that have
  16266. * a single markerholder sprite.
  16267. */
  16268. getSprite: function() {
  16269. var sprites = this.getSprites();
  16270. return sprites && sprites[0];
  16271. },
  16272. /**
  16273. * @private
  16274. */
  16275. withSprite: function(fn) {
  16276. var sprite = this.getSprite();
  16277. return sprite && fn(sprite) || undefined;
  16278. },
  16279. forEachSprite: function(fn) {
  16280. var sprites = this.getSprites(),
  16281. i, ln;
  16282. for (i = 0 , ln = sprites.length; i < ln; i++) {
  16283. fn(sprites[i]);
  16284. }
  16285. },
  16286. onDataChanged: function() {
  16287. var me = this,
  16288. chart = me.getChart(),
  16289. chartStore = chart && chart.getStore(),
  16290. seriesStore = me.getStore();
  16291. if (seriesStore !== chartStore) {
  16292. me.processData();
  16293. }
  16294. },
  16295. isXType: function(xtype) {
  16296. return xtype === 'series';
  16297. },
  16298. getItemId: function() {
  16299. return this.getId();
  16300. },
  16301. applyThemeStyle: function(theme, oldTheme) {
  16302. var me = this,
  16303. fill, stroke;
  16304. fill = theme && theme.subStyle && theme.subStyle.fillStyle;
  16305. stroke = fill && theme.subStyle.strokeStyle;
  16306. if (fill && !stroke) {
  16307. theme.subStyle.strokeStyle = me.getStrokeColorsFromFillColors(fill);
  16308. }
  16309. fill = theme && theme.markerSubStyle && theme.markerSubStyle.fillStyle;
  16310. stroke = fill && theme.markerSubStyle.strokeStyle;
  16311. if (fill && !stroke) {
  16312. theme.markerSubStyle.strokeStyle = me.getStrokeColorsFromFillColors(fill);
  16313. }
  16314. return Ext.apply(oldTheme || {}, theme);
  16315. },
  16316. applyStyle: function(style, oldStyle) {
  16317. return Ext.apply({}, style, oldStyle);
  16318. },
  16319. applySubStyle: function(subStyle, oldSubStyle) {
  16320. var name = Ext.ClassManager.getNameByAlias('sprite.' + this.seriesType),
  16321. cls = Ext.ClassManager.get(name);
  16322. if (cls && cls.def) {
  16323. subStyle = cls.def.batchedNormalize(subStyle, true);
  16324. }
  16325. return Ext.merge({}, oldSubStyle, subStyle);
  16326. },
  16327. applyMarker: function(marker, oldMarker) {
  16328. var type, cls;
  16329. if (marker) {
  16330. if (!Ext.isObject(marker)) {
  16331. marker = {};
  16332. }
  16333. type = marker.type || 'circle';
  16334. if (oldMarker && type === oldMarker.type) {
  16335. marker = Ext.merge({}, oldMarker, marker);
  16336. }
  16337. }
  16338. // Note: reusing the `oldMaker` like `Ext.merge(oldMarker, marker)`
  16339. // isn't possible because the `updateMarker` won't be called.
  16340. if (type) {
  16341. cls = Ext.ClassManager.get(Ext.ClassManager.getNameByAlias('sprite.' + type));
  16342. }
  16343. if (cls && cls.def) {
  16344. marker = cls.def.normalize(marker, true);
  16345. marker.type = type;
  16346. } else {
  16347. marker = null;
  16348. //<debug>
  16349. Ext.log.warn('Invalid series marker type: ' + type);
  16350. }
  16351. //</debug>
  16352. return marker;
  16353. },
  16354. updateMarker: function(marker) {
  16355. var me = this,
  16356. sprites = me.getSprites(),
  16357. seriesSprite, markerSprite, markerTplConfig, i, ln;
  16358. for (i = 0 , ln = sprites.length; i < ln; i++) {
  16359. seriesSprite = sprites[i];
  16360. if (!seriesSprite.isMarkerHolder) {
  16361. continue;
  16362. }
  16363. markerSprite = seriesSprite.getMarker('markers');
  16364. if (marker) {
  16365. if (!markerSprite) {
  16366. markerSprite = new Ext.chart.Markers();
  16367. seriesSprite.bindMarker('markers', markerSprite);
  16368. me.getOverlaySurface().add(markerSprite);
  16369. }
  16370. markerTplConfig = Ext.Object.merge({
  16371. modifiers: 'highlight'
  16372. }, marker);
  16373. markerSprite.setTemplate(markerTplConfig);
  16374. markerSprite.getTemplate().getAnimation().setCustomDurations({
  16375. translationX: 0,
  16376. translationY: 0
  16377. });
  16378. } else if (markerSprite) {
  16379. seriesSprite.releaseMarker('markers');
  16380. me.getOverlaySurface().remove(markerSprite, true);
  16381. }
  16382. seriesSprite.setDirty(true);
  16383. }
  16384. // If we call, for example, `series.setMarker({type: 'circle'})` on a series
  16385. // that has been already constructed, the newly added marker still has to be
  16386. // themed, and the 'style' config of its 'highlight' modifier has to be set.
  16387. if (!me.isConfiguring) {
  16388. me.doUpdateStyles();
  16389. me.updateHighlight(me.getHighlight());
  16390. }
  16391. },
  16392. applyMarkerSubStyle: function(marker, oldMarker) {
  16393. var type = (marker && marker.type) || (oldMarker && oldMarker.type) || 'circle',
  16394. cls = Ext.ClassManager.get(Ext.ClassManager.getNameByAlias('sprite.' + type));
  16395. if (cls && cls.def) {
  16396. marker = cls.def.batchedNormalize(marker, true);
  16397. }
  16398. return Ext.merge(oldMarker || {}, marker);
  16399. },
  16400. updateHidden: function(hidden) {
  16401. var me = this;
  16402. me.getColors();
  16403. me.getSubStyle();
  16404. me.setSubStyle({
  16405. hidden: hidden
  16406. });
  16407. me.processData();
  16408. me.doUpdateStyles();
  16409. if (!Ext.isArray(hidden)) {
  16410. me.updateLegendStore(hidden);
  16411. }
  16412. },
  16413. /**
  16414. * @private
  16415. * Updates chart's legend store when the value of the series' {@link #hidden} config
  16416. * changes or when the {@link #setHiddenByIndex} method is called.
  16417. * @param hidden Whether series (or its component) should be hidden or not.
  16418. * @param index Used for stacked series.
  16419. * If present, only the component with the specified index will change
  16420. * visibility.
  16421. */
  16422. updateLegendStore: function(hidden, index) {
  16423. var me = this,
  16424. chart = me.getChart(),
  16425. legendStore = chart && chart.getLegendStore(),
  16426. id = me.getId(),
  16427. record;
  16428. if (legendStore) {
  16429. if (arguments.length > 1) {
  16430. record = legendStore.findBy(function(rec) {
  16431. return rec.get('series') === id && rec.get('index') === index;
  16432. });
  16433. if (record !== -1) {
  16434. record = legendStore.getAt(record);
  16435. }
  16436. } else {
  16437. record = legendStore.findRecord('series', id);
  16438. }
  16439. if (record && record.get('disabled') !== hidden) {
  16440. record.set('disabled', hidden);
  16441. }
  16442. }
  16443. },
  16444. /**
  16445. *
  16446. * @param {Number} index
  16447. * @param {Boolean} value
  16448. */
  16449. setHiddenByIndex: function(index, value) {
  16450. var me = this;
  16451. if (Ext.isArray(me.getHidden())) {
  16452. // Multi-sprite series like Pie and StackedCartesian.
  16453. me.getHidden()[index] = value;
  16454. me.updateHidden(me.getHidden());
  16455. me.updateLegendStore(value, index);
  16456. } else {
  16457. me.setHidden(value);
  16458. }
  16459. },
  16460. getStrokeColorsFromFillColors: function(colors) {
  16461. var me = this,
  16462. darker = me.getUseDarkerStrokeColor(),
  16463. darkerRatio = (Ext.isNumber(darker) ? darker : me.darkerStrokeRatio),
  16464. strokeColors;
  16465. if (darker) {
  16466. strokeColors = Ext.Array.map(colors, function(color) {
  16467. color = Ext.isString(color) ? color : color.stops[0].color;
  16468. color = Ext.util.Color.fromString(color);
  16469. return color.createDarker(darkerRatio).toString();
  16470. });
  16471. } else {
  16472. strokeColors = Ext.Array.clone(colors);
  16473. }
  16474. return strokeColors;
  16475. },
  16476. updateThemeColors: function(colors) {
  16477. var me = this,
  16478. theme = me.getThemeStyle(),
  16479. fillColors = Ext.Array.clone(colors),
  16480. strokeColors = me.getStrokeColorsFromFillColors(colors),
  16481. newSubStyle = {
  16482. fillStyle: fillColors,
  16483. strokeStyle: strokeColors
  16484. };
  16485. theme.subStyle = Ext.apply(theme.subStyle || {}, newSubStyle);
  16486. theme.markerSubStyle = Ext.apply(theme.markerSubStyle || {}, newSubStyle);
  16487. me.doUpdateStyles();
  16488. if (!me.isConfiguring) {
  16489. me.getChart().refreshLegendStore();
  16490. }
  16491. },
  16492. themeOnlyIfConfigured: {},
  16493. updateTheme: function(theme) {
  16494. var me = this,
  16495. seriesTheme = theme.getSeries(),
  16496. initialConfig = me.getInitialConfig(),
  16497. defaultConfig = me.defaultConfig,
  16498. configs = me.self.getConfigurator().configs,
  16499. genericSeriesTheme = seriesTheme.defaults,
  16500. specificSeriesTheme = seriesTheme[me.type],
  16501. themeOnlyIfConfigured = me.themeOnlyIfConfigured,
  16502. key, value, isObjValue, isUnusedConfig, initialValue, cfg;
  16503. seriesTheme = Ext.merge({}, genericSeriesTheme, specificSeriesTheme);
  16504. for (key in seriesTheme) {
  16505. value = seriesTheme[key];
  16506. cfg = configs[key];
  16507. if (value !== null && value !== undefined && cfg) {
  16508. initialValue = initialConfig[key];
  16509. isObjValue = Ext.isObject(value);
  16510. isUnusedConfig = initialValue === defaultConfig[key];
  16511. if (isObjValue) {
  16512. if (isUnusedConfig && themeOnlyIfConfigured[key]) {
  16513. continue;
  16514. }
  16515. value = Ext.merge({}, value, initialValue);
  16516. }
  16517. if (isUnusedConfig || isObjValue) {
  16518. me[cfg.names.set](value);
  16519. }
  16520. }
  16521. }
  16522. },
  16523. /**
  16524. * @private
  16525. * When the chart's "colors" config changes, these colors are passed onto the series
  16526. * where they are used with the same priority as theme colors, i.e. they do not override
  16527. * the series' "colors" config, nor the series' "style" config, but they do override
  16528. * the colors from the theme's "seriesThemes" config.
  16529. */
  16530. updateChartColors: function(colors) {
  16531. var me = this;
  16532. if (!me.getColors()) {
  16533. me.updateThemeColors(colors);
  16534. }
  16535. },
  16536. updateColors: function(colors) {
  16537. this.updateThemeColors(colors);
  16538. if (!this.isConfiguring) {
  16539. var chart = this.getChart();
  16540. if (chart) {
  16541. chart.refreshLegendStore();
  16542. }
  16543. }
  16544. },
  16545. updateStyle: function() {
  16546. this.doUpdateStyles();
  16547. },
  16548. updateSubStyle: function() {
  16549. this.doUpdateStyles();
  16550. },
  16551. updateThemeStyle: function() {
  16552. this.doUpdateStyles();
  16553. },
  16554. doUpdateStyles: function() {
  16555. var me = this,
  16556. sprites = me.sprites,
  16557. itemInstancing = me.getItemInstancing(),
  16558. ln = sprites && sprites.length,
  16559. // 'showMarkers' updater calls 'series.getSprites()',
  16560. // which we don't want to call here.
  16561. showMarkers = me.getConfig('showMarkers', true),
  16562. style, sprite, marker, i;
  16563. for (i = 0; i < ln; i++) {
  16564. sprite = sprites[i];
  16565. style = me.getStyleByIndex(i);
  16566. if (itemInstancing) {
  16567. sprite.getMarker('items').getTemplate().setAttributes(style);
  16568. }
  16569. sprite.setAttributes(style);
  16570. marker = sprite.isMarkerHolder && sprite.getMarker('markers');
  16571. if (marker) {
  16572. marker.getTemplate().setAttributes(me.getMarkerStyleByIndex(i));
  16573. }
  16574. }
  16575. },
  16576. getStyleWithTheme: function() {
  16577. var me = this,
  16578. theme = me.getThemeStyle(),
  16579. style = Ext.clone(me.getStyle());
  16580. if (theme && theme.style) {
  16581. Ext.applyIf(style, theme.style);
  16582. }
  16583. return style;
  16584. },
  16585. getSubStyleWithTheme: function() {
  16586. var me = this,
  16587. theme = me.getThemeStyle(),
  16588. subStyle = Ext.clone(me.getSubStyle());
  16589. if (theme && theme.subStyle) {
  16590. Ext.applyIf(subStyle, theme.subStyle);
  16591. }
  16592. return subStyle;
  16593. },
  16594. getStyleByIndex: function(i) {
  16595. var me = this,
  16596. theme = me.getThemeStyle(),
  16597. style, themeStyle, subStyle, themeSubStyle,
  16598. result = {};
  16599. style = me.getStyle();
  16600. themeStyle = (theme && theme.style) || {};
  16601. subStyle = me.styleDataForIndex(me.getSubStyle(), i);
  16602. themeSubStyle = me.styleDataForIndex((theme && theme.subStyle), i);
  16603. Ext.apply(result, themeStyle);
  16604. Ext.apply(result, themeSubStyle);
  16605. Ext.apply(result, style);
  16606. Ext.apply(result, subStyle);
  16607. return result;
  16608. },
  16609. getMarkerStyleByIndex: function(i) {
  16610. var me = this,
  16611. theme = me.getThemeStyle(),
  16612. style, themeStyle, subStyle, themeSubStyle, markerStyle, themeMarkerStyle, markerSubStyle, themeMarkerSubStyle,
  16613. result = {};
  16614. style = me.getStyle();
  16615. themeStyle = (theme && theme.style) || {};
  16616. // 'series.updateHidden()' will update 'series.subStyle.hidden' config
  16617. // with the value of the 'series.hidden' config.
  16618. // But we also need to account for 'series.showMarkers' config
  16619. // to determine whether the markers should be hidden or not.
  16620. subStyle = me.styleDataForIndex(me.getSubStyle(), i);
  16621. if (subStyle.hasOwnProperty('hidden')) {
  16622. subStyle.hidden = subStyle.hidden || !this.getConfig('showMarkers', true);
  16623. }
  16624. themeSubStyle = me.styleDataForIndex((theme && theme.subStyle), i);
  16625. markerStyle = me.getMarker();
  16626. themeMarkerStyle = (theme && theme.marker) || {};
  16627. markerSubStyle = me.getMarkerSubStyle();
  16628. themeMarkerSubStyle = me.styleDataForIndex((theme && theme.markerSubStyle), i);
  16629. Ext.apply(result, themeStyle);
  16630. Ext.apply(result, themeSubStyle);
  16631. Ext.apply(result, themeMarkerStyle);
  16632. Ext.apply(result, themeMarkerSubStyle);
  16633. Ext.apply(result, style);
  16634. Ext.apply(result, subStyle);
  16635. Ext.apply(result, markerStyle);
  16636. Ext.apply(result, markerSubStyle);
  16637. return result;
  16638. },
  16639. styleDataForIndex: function(style, i) {
  16640. var value, name,
  16641. result = {};
  16642. if (style) {
  16643. for (name in style) {
  16644. value = style[name];
  16645. if (Ext.isArray(value)) {
  16646. result[name] = value[i % value.length];
  16647. } else {
  16648. result[name] = value;
  16649. }
  16650. }
  16651. }
  16652. return result;
  16653. },
  16654. /**
  16655. * @method
  16656. * For a given x/y point relative to the main rect, find a corresponding item from this
  16657. * series, if any.
  16658. * @param {Number} x
  16659. * @param {Number} y
  16660. * @param {Object} [target] optional target to receive the result
  16661. * @return {Object} An object describing the item, or null if there is no matching item.
  16662. * The exact contents of this object will vary by series type, but should always contain
  16663. * at least the following:
  16664. *
  16665. * @return {Ext.data.Model} return.record the record of the item.
  16666. * @return {Array} return.point the x/y coordinates relative to the chart box
  16667. * of a single point for this data item, which can be used as e.g. a tooltip anchor
  16668. * point.
  16669. * @return {Ext.draw.sprite.Sprite} return.sprite the item's rendering Sprite.
  16670. * @return {Number} return.subSprite the index if sprite is an instancing sprite.
  16671. */
  16672. getItemForPoint: Ext.emptyFn,
  16673. /**
  16674. * Returns a series item by index and (optional) category.
  16675. * @param {Number} index The index of the item (matches store record index).
  16676. * @param {String} [category] The category of item, e.g.: 'items', 'markers', 'sprites'.
  16677. * @return {Object} item
  16678. */
  16679. getItemByIndex: function(index, category) {
  16680. var me = this,
  16681. sprites = me.getSprites(),
  16682. sprite = sprites && sprites[0],
  16683. item;
  16684. if (!sprite) {
  16685. return;
  16686. }
  16687. // 'category' is not defined, making our best guess here.
  16688. if (category === undefined && sprite.isMarkerHolder) {
  16689. category = me.getItemInstancing() ? 'items' : 'markers';
  16690. } else if (!category || category === '' || category === 'sprites') {
  16691. sprite = sprites[index];
  16692. }
  16693. if (sprite) {
  16694. item = {
  16695. series: me,
  16696. category: category,
  16697. index: index,
  16698. record: me.getStore().getData().items[index],
  16699. field: me.getYField(),
  16700. sprite: sprite
  16701. };
  16702. return item;
  16703. }
  16704. },
  16705. onSpriteAnimationStart: function(sprite) {
  16706. this.fireEvent('animationstart', this, sprite);
  16707. },
  16708. onSpriteAnimationEnd: function(sprite) {
  16709. this.fireEvent('animationend', this, sprite);
  16710. },
  16711. resolveListenerScope: function(defaultScope) {
  16712. // Override the Observable's method to redirect listener scope
  16713. // resolution to the chart.
  16714. var me = this,
  16715. namedScope = Ext._namedScopes[defaultScope],
  16716. chart = me.getChart(),
  16717. scope;
  16718. if (!namedScope) {
  16719. scope = chart ? chart.resolveListenerScope(defaultScope, false) : (defaultScope || me);
  16720. } else if (namedScope.isThis) {
  16721. scope = me;
  16722. } else if (namedScope.isController) {
  16723. scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
  16724. } else if (namedScope.isSelf) {
  16725. scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
  16726. // Class body listener. No chart controller, nor chart container controller.
  16727. if (scope === chart && !chart.getInheritedConfig('defaultListenerScope')) {
  16728. scope = me;
  16729. }
  16730. }
  16731. return scope;
  16732. },
  16733. /**
  16734. * Provide legend information to target array.
  16735. *
  16736. * @param {Array} target
  16737. *
  16738. * The information consists:
  16739. * @param {String} target.name
  16740. * @param {String} target.mark
  16741. * @param {Boolean} target.disabled
  16742. * @param {String} target.series
  16743. * @param {Number} target.index
  16744. */
  16745. provideLegendInfo: function(target) {
  16746. var me = this,
  16747. style = me.getSubStyleWithTheme(),
  16748. fill = style.fillStyle;
  16749. if (Ext.isArray(fill)) {
  16750. fill = fill[0];
  16751. }
  16752. target.push({
  16753. name: me.getTitle() || me.getYField() || me.getId(),
  16754. mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
  16755. disabled: me.getHidden(),
  16756. series: me.getId(),
  16757. index: 0
  16758. });
  16759. },
  16760. clearSprites: function() {
  16761. var sprites = this.sprites,
  16762. sprite, i, ln;
  16763. for (i = 0 , ln = sprites.length; i < ln; i++) {
  16764. sprite = sprites[i];
  16765. if (sprite && sprite.isSprite) {
  16766. sprite.destroy();
  16767. }
  16768. }
  16769. this.sprites = [];
  16770. },
  16771. destroy: function() {
  16772. var me = this,
  16773. store = me._store,
  16774. // Peek at the config so we don't create one just to destroy it
  16775. tooltip = me.getConfig('tooltip', true);
  16776. if (store && store.getAutoDestroy()) {
  16777. Ext.destroy(store);
  16778. }
  16779. me.setChart(null);
  16780. me.clearListeners();
  16781. if (tooltip) {
  16782. Ext.destroy(tooltip);
  16783. }
  16784. me.callParent();
  16785. }
  16786. });
  16787. /**
  16788. * @class Ext.chart.interactions.Abstract
  16789. *
  16790. * Defines a common abstract parent class for all interactions.
  16791. *
  16792. */
  16793. Ext.define('Ext.chart.interactions.Abstract', {
  16794. xtype: 'interaction',
  16795. mixins: {
  16796. observable: 'Ext.mixin.Observable'
  16797. },
  16798. config: {
  16799. /**
  16800. * @cfg {Object} gesture
  16801. * Maps gestures that should be used for starting/maintaining/ending the interaction
  16802. * to corresponding class methods.
  16803. * @private
  16804. */
  16805. gestures: {
  16806. tap: 'onGesture'
  16807. },
  16808. /**
  16809. * @cfg {Ext.chart.AbstractChart} chart The chart that the interaction is bound.
  16810. */
  16811. chart: null,
  16812. /**
  16813. * @cfg {Boolean} enabled 'true' if the interaction is enabled.
  16814. */
  16815. enabled: true
  16816. },
  16817. /**
  16818. * Android device is emerging too many events so if we re-render every frame it will take forever to finish a frame.
  16819. * This throttle technique will limit the timespan between two frames.
  16820. */
  16821. throttleGap: 0,
  16822. stopAnimationBeforeSync: false,
  16823. constructor: function(config) {
  16824. var me = this,
  16825. id;
  16826. config = config || {};
  16827. if ('id' in config) {
  16828. id = config.id;
  16829. } else if ('id' in me.config) {
  16830. id = me.config.id;
  16831. } else {
  16832. id = me.getId();
  16833. }
  16834. me.setId(id);
  16835. me.mixins.observable.constructor.call(me, config);
  16836. },
  16837. updateChart: function(newChart, oldChart) {
  16838. var me = this;
  16839. if (oldChart === newChart) {
  16840. return;
  16841. }
  16842. if (oldChart) {
  16843. oldChart.unregister(me);
  16844. me.removeChartListener(oldChart);
  16845. }
  16846. if (newChart) {
  16847. newChart.register(me);
  16848. me.addChartListener();
  16849. }
  16850. },
  16851. updateEnabled: function(enabled) {
  16852. var me = this,
  16853. chart = me.getChart();
  16854. if (chart) {
  16855. if (enabled) {
  16856. me.addChartListener();
  16857. } else {
  16858. me.removeChartListener(chart);
  16859. }
  16860. }
  16861. },
  16862. /**
  16863. * @method
  16864. * @protected
  16865. * Placeholder method.
  16866. */
  16867. onGesture: Ext.emptyFn,
  16868. /**
  16869. * @protected
  16870. * Find and return a single series item corresponding to the given event,
  16871. * or null if no matching item is found.
  16872. * @param {Event} e
  16873. * @return {Object} the item object or null if none found.
  16874. */
  16875. getItemForEvent: function(e) {
  16876. var me = this,
  16877. chart = me.getChart(),
  16878. chartXY = chart.getEventXY(e);
  16879. return chart.getItemForPoint(chartXY[0], chartXY[1]);
  16880. },
  16881. /**
  16882. * Find and return all series items corresponding to the given event.
  16883. * @param {Event} e
  16884. * @return {Array} array of matching item objects
  16885. * @private
  16886. * @deprecated 6.5.2 This method is deprecated
  16887. */
  16888. getItemsForEvent: function(e) {
  16889. var me = this,
  16890. chart = me.getChart(),
  16891. chartXY = chart.getEventXY(e);
  16892. return chart.getItemsForPoint(chartXY[0], chartXY[1]);
  16893. },
  16894. /**
  16895. * @private
  16896. */
  16897. addChartListener: function() {
  16898. var me = this,
  16899. chart = me.getChart(),
  16900. gestures = me.getGestures(),
  16901. gesture;
  16902. if (!me.getEnabled()) {
  16903. return;
  16904. }
  16905. function insertGesture(name, fn) {
  16906. chart.addElementListener(name, // wrap the handler so it does not fire if the event is locked by another interaction
  16907. me.listeners[name] = function(e) {
  16908. var locks = me.getLocks(),
  16909. result;
  16910. if (me.getEnabled() && (!(name in locks) || locks[name] === me)) {
  16911. result = (Ext.isFunction(fn) ? fn : me[fn]).apply(this, arguments);
  16912. if (result === false && e && e.stopPropagation) {
  16913. e.stopPropagation();
  16914. }
  16915. return result;
  16916. }
  16917. }, me);
  16918. }
  16919. me.listeners = me.listeners || {};
  16920. for (gesture in gestures) {
  16921. insertGesture(gesture, gestures[gesture]);
  16922. }
  16923. },
  16924. removeChartListener: function(chart) {
  16925. var me = this,
  16926. gestures = me.getGestures(),
  16927. gesture;
  16928. function removeGesture(name) {
  16929. var fn = me.listeners[name];
  16930. if (fn) {
  16931. chart.removeElementListener(name, fn);
  16932. delete me.listeners[name];
  16933. }
  16934. }
  16935. if (me.listeners) {
  16936. for (gesture in gestures) {
  16937. removeGesture(gesture);
  16938. }
  16939. }
  16940. },
  16941. lockEvents: function() {
  16942. var me = this,
  16943. locks = me.getLocks(),
  16944. args = Array.prototype.slice.call(arguments),
  16945. i = args.length;
  16946. while (i--) {
  16947. locks[args[i]] = me;
  16948. }
  16949. },
  16950. unlockEvents: function() {
  16951. var locks = this.getLocks(),
  16952. args = Array.prototype.slice.call(arguments),
  16953. i = args.length;
  16954. while (i--) {
  16955. delete locks[args[i]];
  16956. }
  16957. },
  16958. getLocks: function() {
  16959. var chart = this.getChart();
  16960. return chart.lockedEvents || (chart.lockedEvents = {});
  16961. },
  16962. doSync: function() {
  16963. var me = this,
  16964. chart = me.getChart();
  16965. if (me.syncTimer) {
  16966. Ext.undefer(me.syncTimer);
  16967. me.syncTimer = null;
  16968. }
  16969. if (me.stopAnimationBeforeSync) {
  16970. chart.animationSuspendCount++;
  16971. }
  16972. chart.redraw();
  16973. if (me.stopAnimationBeforeSync) {
  16974. chart.animationSuspendCount--;
  16975. }
  16976. me.syncThrottle = Date.now() + me.throttleGap;
  16977. },
  16978. sync: function() {
  16979. var me = this;
  16980. if (me.throttleGap && Ext.frameStartTime < me.syncThrottle) {
  16981. if (me.syncTimer) {
  16982. return;
  16983. }
  16984. me.syncTimer = Ext.defer(function() {
  16985. me.doSync();
  16986. }, me.throttleGap);
  16987. } else {
  16988. me.doSync();
  16989. }
  16990. },
  16991. getItemId: function() {
  16992. return this.getId();
  16993. },
  16994. isXType: function(xtype) {
  16995. return xtype === 'interaction';
  16996. },
  16997. destroy: function() {
  16998. var me = this;
  16999. me.setChart(null);
  17000. delete me.listeners;
  17001. me.callParent();
  17002. }
  17003. }, function() {
  17004. if (Ext.os.is.Android4) {
  17005. this.prototype.throttleGap = 40;
  17006. }
  17007. });
  17008. /**
  17009. * Mixin that provides the functionality to place markers.
  17010. */
  17011. Ext.define('Ext.chart.MarkerHolder', {
  17012. extend: 'Ext.Mixin',
  17013. requires: [
  17014. 'Ext.chart.Markers'
  17015. ],
  17016. mixinConfig: {
  17017. id: 'markerHolder',
  17018. after: {
  17019. constructor: 'constructor',
  17020. preRender: 'preRender'
  17021. },
  17022. before: {
  17023. destroy: 'destroy'
  17024. }
  17025. },
  17026. isMarkerHolder: true,
  17027. // The combined transformation applied to the sprite by its parents.
  17028. // Does not include the transformation matrix of the sprite itself.
  17029. surfaceMatrix: null,
  17030. // The inverse of the above transformation to go back to the original state.
  17031. inverseSurfaceMatrix: null,
  17032. deprecated: {
  17033. 6: {
  17034. methods: {
  17035. /**
  17036. * Returns the markers bound to the given name.
  17037. * @param {String} name The name of the marker (e.g., "items", "labels", etc.).
  17038. * @return {Ext.chart.Markers[]}
  17039. * @method getBoundMarker
  17040. * @deprecated 6.0 Use {@link #getMarker} instead.
  17041. */
  17042. getBoundMarker: {
  17043. message: "Please use the 'getMarker' method instead.",
  17044. fn: function(name) {
  17045. var marker = this.boundMarkers[name];
  17046. return marker ? [
  17047. marker
  17048. ] : marker;
  17049. }
  17050. }
  17051. }
  17052. }
  17053. },
  17054. constructor: function() {
  17055. this.boundMarkers = {};
  17056. this.cleanRedraw = false;
  17057. },
  17058. /**
  17059. * Registers the given marker with the marker holder under the specified name.
  17060. * @param {String} name The name of the marker (e.g., "items", "labels", etc.).
  17061. * @param {Ext.chart.Markers} marker
  17062. */
  17063. bindMarker: function(name, marker) {
  17064. var me = this,
  17065. markers = me.boundMarkers;
  17066. if (marker && marker.isMarkers) {
  17067. //<debug>
  17068. if (markers[name] && markers[name] === marker) {
  17069. Ext.log.warn(me.getId(), " (MarkerHolder): the Markers instance '", marker.getId(), "' is already bound under the name '", name, "'.");
  17070. }
  17071. //</debug>
  17072. me.releaseMarker(name);
  17073. markers[name] = marker;
  17074. marker.on('destroy', me.onMarkerDestroy, me);
  17075. }
  17076. },
  17077. onMarkerDestroy: function(marker) {
  17078. this.releaseMarker(marker);
  17079. },
  17080. /**
  17081. * Unregisters the given marker or a marker with the given name.
  17082. * Providing a name of the marker is more efficient as it avoids lookup.
  17083. * @param marker {String/Ext.chart.Markers}
  17084. * @return {Ext.chart.Markers} Released marker or null.
  17085. */
  17086. releaseMarker: function(marker) {
  17087. var markers = this.boundMarkers,
  17088. name;
  17089. if (marker && marker.isMarkers) {
  17090. for (name in markers) {
  17091. if (markers[name] === marker) {
  17092. delete markers[name];
  17093. break;
  17094. }
  17095. }
  17096. } else {
  17097. name = marker;
  17098. marker = markers[name];
  17099. delete markers[name];
  17100. }
  17101. return marker || null;
  17102. },
  17103. /**
  17104. * Returns the marker bound to the given name (or null). See {@link #bindMarker}.
  17105. * @param {String} name The name of the marker (e.g., "items", "labels", etc.).
  17106. * @return {Ext.chart.Markers}
  17107. */
  17108. getMarker: function(name) {
  17109. return this.boundMarkers[name] || null;
  17110. },
  17111. preRender: function(surface, ctx, rect) {
  17112. var me = this,
  17113. id = me.getId(),
  17114. boundMarkers = me.boundMarkers,
  17115. parent = me.getParent(),
  17116. name, marker, matrix;
  17117. if (me.surfaceMatrix) {
  17118. matrix = me.surfaceMatrix.set(1, 0, 0, 1, 0, 0);
  17119. } else {
  17120. matrix = me.surfaceMatrix = new Ext.draw.Matrix();
  17121. }
  17122. me.cleanRedraw = !me.attr.dirty;
  17123. if (!me.cleanRedraw) {
  17124. for (name in boundMarkers) {
  17125. marker = boundMarkers[name];
  17126. if (marker) {
  17127. marker.clear(id);
  17128. }
  17129. }
  17130. }
  17131. // Parent can be either a sprite (like a composite or instancing)
  17132. // or a surface. First, climb up and apply transformations of the
  17133. // parent sprites.
  17134. while (parent && parent.attr && parent.attr.matrix) {
  17135. matrix.prependMatrix(parent.attr.matrix);
  17136. parent = parent.getParent();
  17137. }
  17138. // Finally, apply the transformation used by the surface.
  17139. matrix.prependMatrix(parent.matrix);
  17140. me.surfaceMatrix = matrix;
  17141. me.inverseSurfaceMatrix = matrix.inverse(me.inverseSurfaceMatrix);
  17142. },
  17143. putMarker: function(name, attr, index, bypassNormalization, keepRevision) {
  17144. var marker = this.boundMarkers[name];
  17145. if (marker) {
  17146. marker.putMarkerFor(this.getId(), attr, index, bypassNormalization, keepRevision);
  17147. }
  17148. },
  17149. getMarkerBBox: function(name, index, isWithoutTransform) {
  17150. var marker = this.boundMarkers[name];
  17151. if (marker) {
  17152. return marker.getMarkerBBoxFor(this.getId(), index, isWithoutTransform);
  17153. }
  17154. },
  17155. destroy: function() {
  17156. var boundMarkers = this.boundMarkers,
  17157. name, marker;
  17158. for (name in boundMarkers) {
  17159. marker = boundMarkers[name];
  17160. marker.destroy();
  17161. }
  17162. }
  17163. });
  17164. /**
  17165. * @private
  17166. * @class Ext.chart.axis.sprite.Axis
  17167. * @extends Ext.draw.sprite.Sprite
  17168. *
  17169. * The axis sprite. Currently all types of the axis will be rendered with this sprite.
  17170. */
  17171. Ext.define('Ext.chart.axis.sprite.Axis', {
  17172. extend: 'Ext.draw.sprite.Sprite',
  17173. alias: 'sprite.axis',
  17174. type: 'axis',
  17175. mixins: {
  17176. markerHolder: 'Ext.chart.MarkerHolder'
  17177. },
  17178. requires: [
  17179. 'Ext.draw.sprite.Text'
  17180. ],
  17181. inheritableStatics: {
  17182. def: {
  17183. processors: {
  17184. /**
  17185. * @cfg {Boolean} grid 'true' if the axis has a grid.
  17186. */
  17187. grid: 'bool',
  17188. /**
  17189. * @cfg {Boolean} axisLine 'true' if the main line of the axis is drawn.
  17190. */
  17191. axisLine: 'bool',
  17192. /**
  17193. * @cfg {Boolean} minorTicks 'true' if the axis has sub ticks.
  17194. */
  17195. minorTicks: 'bool',
  17196. /**
  17197. * @cfg {Number} minorTickSize The length of the minor ticks.
  17198. */
  17199. minorTickSize: 'number',
  17200. /**
  17201. * @cfg {Boolean} majorTicks 'true' if the axis has major ticks.
  17202. */
  17203. majorTicks: 'bool',
  17204. /**
  17205. * @cfg {Number} majorTickSize The length of the major ticks.
  17206. */
  17207. majorTickSize: 'number',
  17208. /**
  17209. * @cfg {Number} length The total length of the axis.
  17210. */
  17211. length: 'number',
  17212. /**
  17213. * @private
  17214. * @cfg {Number} startGap Axis start determined by the chart inset padding.
  17215. */
  17216. startGap: 'number',
  17217. /**
  17218. * @private
  17219. * @cfg {Number} endGap Axis end determined by the chart inset padding.
  17220. */
  17221. endGap: 'number',
  17222. /**
  17223. * @cfg {Number} dataMin The minimum value of the axis data.
  17224. */
  17225. dataMin: 'number',
  17226. /**
  17227. * @cfg {Number} dataMax The maximum value of the axis data.
  17228. */
  17229. dataMax: 'number',
  17230. /**
  17231. * @cfg {Number} visibleMin The minimum value that is displayed.
  17232. */
  17233. visibleMin: 'number',
  17234. /**
  17235. * @cfg {Number} visibleMax The maximum value that is displayed.
  17236. */
  17237. visibleMax: 'number',
  17238. /**
  17239. * @cfg {String} position The position of the axis on the chart.
  17240. */
  17241. position: 'enums(left,right,top,bottom,angular,radial,gauge)',
  17242. /**
  17243. * @cfg {Number} minStepSize The minimum step size between ticks.
  17244. */
  17245. minStepSize: 'number',
  17246. /**
  17247. * @private
  17248. * @cfg {Number} estStepSize The estimated step size between ticks.
  17249. */
  17250. estStepSize: 'number',
  17251. /**
  17252. * @private
  17253. * Unused.
  17254. */
  17255. titleOffset: 'number',
  17256. /**
  17257. * @cfg {Number} [textPadding=0]
  17258. * The padding around axis labels to determine collision.
  17259. * The default is 0 for all axes except horizontal axes of cartesian charts,
  17260. * where the default is 5 to prevent axis labels from blending one into another.
  17261. * This default is defined in the {@link Ext.chart.theme.Base#axis axis} config
  17262. * of the {@link Ext.chart.theme.Base Base} theme.
  17263. * You may want to change this default to a smaller number or 0, if you have
  17264. * horizontal axis labels rotated, which allows for more text to fit in.
  17265. */
  17266. textPadding: 'number',
  17267. /**
  17268. * @cfg {Number} min The minimum value of the axis.
  17269. * `min` and {@link #max} attributes represent the effective range of the axis
  17270. * after segmentation, layout, and range reconciliation between axes.
  17271. */
  17272. min: 'number',
  17273. /**
  17274. * @cfg {Number} max The maximum value of the axis.
  17275. * {@link #min} and `max` attributes represent the effective range of the axis
  17276. * after segmentation, layout, and range reconciliation between axes.
  17277. */
  17278. max: 'number',
  17279. /**
  17280. * @cfg {Number} centerX The central point of the angular axis on the x-axis.
  17281. */
  17282. centerX: 'number',
  17283. /**
  17284. * @cfg {Number} centerY The central point of the angular axis on the y-axis.
  17285. */
  17286. centerY: 'number',
  17287. /**
  17288. * @private
  17289. * @cfg {Number} radius
  17290. * Unused.
  17291. */
  17292. radius: 'number',
  17293. /**
  17294. * @private
  17295. */
  17296. totalAngle: 'number',
  17297. /**
  17298. * @cfg {Number} baseRotation The starting rotation of the angular axis.
  17299. */
  17300. baseRotation: 'number',
  17301. /**
  17302. * @private
  17303. * Unused.
  17304. */
  17305. data: 'default',
  17306. /**
  17307. * @cfg {Boolean} 'true' if the estimated step size is adjusted by text size.
  17308. */
  17309. enlargeEstStepSizeByText: 'bool'
  17310. },
  17311. defaults: {
  17312. grid: false,
  17313. axisLine: true,
  17314. minorTicks: false,
  17315. minorTickSize: 3,
  17316. majorTicks: true,
  17317. majorTickSize: 5,
  17318. length: 0,
  17319. startGap: 0,
  17320. endGap: 0,
  17321. visibleMin: 0,
  17322. visibleMax: 1,
  17323. dataMin: 0,
  17324. dataMax: 1,
  17325. position: '',
  17326. minStepSize: 0,
  17327. estStepSize: 20,
  17328. min: 0,
  17329. max: 1,
  17330. centerX: 0,
  17331. centerY: 0,
  17332. radius: 1,
  17333. baseRotation: 0,
  17334. data: null,
  17335. titleOffset: 0,
  17336. textPadding: 0,
  17337. scalingCenterY: 0,
  17338. scalingCenterX: 0,
  17339. // Override default
  17340. strokeStyle: 'black',
  17341. enlargeEstStepSizeByText: false
  17342. },
  17343. triggers: {
  17344. minorTickSize: 'bbox',
  17345. majorTickSize: 'bbox',
  17346. position: 'bbox,layout',
  17347. axisLine: 'bbox,layout',
  17348. minorTicks: 'layout',
  17349. min: 'layout',
  17350. max: 'layout',
  17351. length: 'layout',
  17352. minStepSize: 'layout',
  17353. estStepSize: 'layout',
  17354. data: 'layout',
  17355. dataMin: 'layout',
  17356. dataMax: 'layout',
  17357. visibleMin: 'layout',
  17358. visibleMax: 'layout',
  17359. enlargeEstStepSizeByText: 'layout'
  17360. },
  17361. updaters: {
  17362. layout: 'layoutUpdater'
  17363. }
  17364. }
  17365. },
  17366. config: {
  17367. /**
  17368. * @cfg {Object} label
  17369. *
  17370. * The label configuration object for the Axis. This object may include style attributes
  17371. * like `spacing`, `padding`, `font` that receives a string or number and
  17372. * returns a new string with the modified values.
  17373. */
  17374. label: null,
  17375. /**
  17376. * @cfg {Number} labelOffset
  17377. * The distance between the label and the edge of a major tick.
  17378. * Only applicable for 'gauge' and 'angular' axes.
  17379. */
  17380. labelOffset: 10,
  17381. /**
  17382. * @cfg {Object|Ext.chart.axis.layout.Layout} layout The layout configuration used by the axis.
  17383. */
  17384. layout: null,
  17385. /**
  17386. * @cfg {Object|Ext.chart.axis.segmenter.Segmenter} segmenter The method of segmenter used by the axis.
  17387. */
  17388. segmenter: null,
  17389. /**
  17390. * @cfg {Function} renderer Allows direct customisation of rendered axis sprites.
  17391. */
  17392. renderer: null,
  17393. /**
  17394. * @private
  17395. * @cfg {Object} layoutContext Stores the context after calculating layout.
  17396. */
  17397. layoutContext: null,
  17398. /**
  17399. * @cfg {Ext.chart.axis.Axis} axis The axis represented by this sprite.
  17400. */
  17401. axis: null
  17402. },
  17403. thickness: 0,
  17404. stepSize: 0,
  17405. getBBox: function() {
  17406. return null;
  17407. },
  17408. defaultRenderer: function(v) {
  17409. // 'this' pointer in this case is a layoutContext
  17410. return this.segmenter.renderer(v, this);
  17411. },
  17412. layoutUpdater: function() {
  17413. var me = this,
  17414. chart = me.getAxis().getChart();
  17415. if (chart.isInitializing) {
  17416. return;
  17417. }
  17418. var attr = me.attr,
  17419. layout = me.getLayout(),
  17420. isRtl = chart.getInherited().rtl,
  17421. dataRange = attr.dataMax - attr.dataMin,
  17422. min = attr.dataMin + dataRange * attr.visibleMin,
  17423. max = attr.dataMin + dataRange * attr.visibleMax,
  17424. range = max - min,
  17425. position = attr.position,
  17426. context = {
  17427. attr: attr,
  17428. segmenter: me.getSegmenter(),
  17429. renderer: me.defaultRenderer
  17430. };
  17431. if (position === 'left' || position === 'right') {
  17432. attr.translationX = 0;
  17433. attr.translationY = max * attr.length / range;
  17434. attr.scalingX = 1;
  17435. attr.scalingY = -attr.length / range;
  17436. attr.scalingCenterY = 0;
  17437. attr.scalingCenterX = 0;
  17438. me.applyTransformations(true);
  17439. } else if (position === 'top' || position === 'bottom') {
  17440. if (isRtl) {
  17441. attr.translationX = attr.length + min * attr.length / range + 1;
  17442. } else {
  17443. attr.translationX = -min * attr.length / range;
  17444. }
  17445. attr.translationY = 0;
  17446. attr.scalingX = (isRtl ? -1 : 1) * attr.length / range;
  17447. attr.scalingY = 1;
  17448. attr.scalingCenterY = 0;
  17449. attr.scalingCenterX = 0;
  17450. me.applyTransformations(true);
  17451. }
  17452. if (layout) {
  17453. layout.calculateLayout(context);
  17454. me.setLayoutContext(context);
  17455. }
  17456. },
  17457. iterate: function(snaps, fn) {
  17458. var i, position, id, axis, floatingAxes, floatingValues,
  17459. some = Ext.Array.some,
  17460. abs = Math.abs,
  17461. threshold;
  17462. if (snaps.getLabel) {
  17463. // Discrete layout.
  17464. if (snaps.min < snaps.from) {
  17465. fn.call(this, snaps.min, snaps.getLabel(snaps.min), -1, snaps);
  17466. }
  17467. for (i = 0; i <= snaps.steps; i++) {
  17468. fn.call(this, snaps.get(i), snaps.getLabel(i), i, snaps);
  17469. }
  17470. if (snaps.max > snaps.to) {
  17471. fn.call(this, snaps.max, snaps.getLabel(snaps.max), snaps.steps + 1, snaps);
  17472. }
  17473. } else {
  17474. axis = this.getAxis();
  17475. floatingAxes = axis.floatingAxes;
  17476. floatingValues = [];
  17477. threshold = (snaps.to - snaps.from) / (snaps.steps + 1);
  17478. if (axis.getFloating()) {
  17479. for (id in floatingAxes) {
  17480. floatingValues.push(floatingAxes[id]);
  17481. }
  17482. }
  17483. // Don't render ticks in axes intersection points.
  17484. function isTickVisible(position) {
  17485. return !floatingValues.length || some(floatingValues, function(value) {
  17486. return abs(value - position) > threshold;
  17487. });
  17488. }
  17489. if (snaps.min < snaps.from && isTickVisible(snaps.min)) {
  17490. fn.call(this, snaps.min, snaps.min, -1, snaps);
  17491. }
  17492. for (i = 0; i <= snaps.steps; i++) {
  17493. position = snaps.get(i);
  17494. if (isTickVisible(position)) {
  17495. fn.call(this, position, position, i, snaps);
  17496. }
  17497. }
  17498. if (snaps.max > snaps.to && isTickVisible(snaps.max)) {
  17499. fn.call(this, snaps.max, snaps.max, snaps.steps + 1, snaps);
  17500. }
  17501. }
  17502. },
  17503. renderTicks: function(surface, ctx, layout, clipRect) {
  17504. var me = this,
  17505. attr = me.attr,
  17506. docked = attr.position,
  17507. matrix = attr.matrix,
  17508. halfLineWidth = 0.5 * attr.lineWidth,
  17509. xx = matrix.getXX(),
  17510. dx = matrix.getDX(),
  17511. yy = matrix.getYY(),
  17512. dy = matrix.getDY(),
  17513. majorTicks = layout.majorTicks,
  17514. majorTickSize = attr.majorTickSize,
  17515. minorTicks = layout.minorTicks,
  17516. minorTickSize = attr.minorTickSize;
  17517. if (majorTicks) {
  17518. switch (docked) {
  17519. case 'right':
  17520. function getRightTickFn(size) {
  17521. return function(position, labelText, i) {
  17522. position = surface.roundPixel(position * yy + dy) + halfLineWidth;
  17523. ctx.moveTo(0, position);
  17524. ctx.lineTo(size, position);
  17525. };
  17526. };
  17527. me.iterate(majorTicks, getRightTickFn(majorTickSize));
  17528. minorTicks && me.iterate(minorTicks, getRightTickFn(minorTickSize));
  17529. break;
  17530. case 'left':
  17531. function getLeftTickFn(size) {
  17532. return function(position, labelText, i) {
  17533. position = surface.roundPixel(position * yy + dy) + halfLineWidth;
  17534. ctx.moveTo(clipRect[2] - size, position);
  17535. ctx.lineTo(clipRect[2], position);
  17536. };
  17537. };
  17538. me.iterate(majorTicks, getLeftTickFn(majorTickSize));
  17539. minorTicks && me.iterate(minorTicks, getLeftTickFn(minorTickSize));
  17540. break;
  17541. case 'bottom':
  17542. function getBottomTickFn(size) {
  17543. return function(position, labelText, i) {
  17544. position = surface.roundPixel(position * xx + dx) - halfLineWidth;
  17545. ctx.moveTo(position, 0);
  17546. ctx.lineTo(position, size);
  17547. };
  17548. };
  17549. me.iterate(majorTicks, getBottomTickFn(majorTickSize));
  17550. minorTicks && me.iterate(minorTicks, getBottomTickFn(minorTickSize));
  17551. break;
  17552. case 'top':
  17553. function getTopTickFn(size) {
  17554. return function(position, labelText, i) {
  17555. position = surface.roundPixel(position * xx + dx) - halfLineWidth;
  17556. ctx.moveTo(position, clipRect[3]);
  17557. ctx.lineTo(position, clipRect[3] - size);
  17558. };
  17559. };
  17560. me.iterate(majorTicks, getTopTickFn(majorTickSize));
  17561. minorTicks && me.iterate(minorTicks, getTopTickFn(minorTickSize));
  17562. break;
  17563. case 'angular':
  17564. me.iterate(majorTicks, function(position, labelText, i) {
  17565. position = position / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
  17566. ctx.moveTo(attr.centerX + (attr.length) * Math.cos(position), attr.centerY + (attr.length) * Math.sin(position));
  17567. ctx.lineTo(attr.centerX + (attr.length + majorTickSize) * Math.cos(position), attr.centerY + (attr.length + majorTickSize) * Math.sin(position));
  17568. });
  17569. break;
  17570. case 'gauge':
  17571. var gaugeAngles = me.getGaugeAngles();
  17572. me.iterate(majorTicks, function(position, labelText, i) {
  17573. position = (position - attr.min) / (attr.max - attr.min) * attr.totalAngle - attr.totalAngle + gaugeAngles.start;
  17574. ctx.moveTo(attr.centerX + (attr.length) * Math.cos(position), attr.centerY + (attr.length) * Math.sin(position));
  17575. ctx.lineTo(attr.centerX + (attr.length + majorTickSize) * Math.cos(position), attr.centerY + (attr.length + majorTickSize) * Math.sin(position));
  17576. });
  17577. break;
  17578. }
  17579. }
  17580. },
  17581. renderLabels: function(surface, ctx, layoutContext, clipRect) {
  17582. var me = this,
  17583. attr = me.attr,
  17584. halfLineWidth = 0.5 * attr.lineWidth,
  17585. docked = attr.position,
  17586. matrix = attr.matrix,
  17587. textPadding = attr.textPadding,
  17588. xx = matrix.getXX(),
  17589. dx = matrix.getDX(),
  17590. yy = matrix.getYY(),
  17591. dy = matrix.getDY(),
  17592. thickness = 0,
  17593. majorTicks = layoutContext.majorTicks,
  17594. tickPadding = Math.max(attr.majorTickSize, attr.minorTickSize) + attr.lineWidth,
  17595. isBBoxIntersect = Ext.draw.Draw.isBBoxIntersect,
  17596. label = me.getLabel(),
  17597. font,
  17598. labelOffset = me.getLabelOffset(),
  17599. lastLabelText = null,
  17600. textSize = 0,
  17601. textCount = 0,
  17602. segmenter = layoutContext.segmenter,
  17603. renderer = me.getRenderer(),
  17604. axis = me.getAxis(),
  17605. title = axis.getTitle(),
  17606. titleBBox = title && title.attr.text !== '' && title.getBBox(),
  17607. labelInverseMatrix,
  17608. lastBBox = null,
  17609. bbox, fly, text, titlePadding, translation, gaugeAngles;
  17610. if (majorTicks && label && !label.attr.hidden) {
  17611. font = label.attr.font;
  17612. if (ctx.font !== font) {
  17613. ctx.font = font;
  17614. }
  17615. // This can profoundly improve performance.
  17616. label.setAttributes({
  17617. translationX: 0,
  17618. translationY: 0
  17619. }, true);
  17620. label.applyTransformations();
  17621. labelInverseMatrix = label.attr.inverseMatrix.elements.slice(0);
  17622. switch (docked) {
  17623. case 'left':
  17624. titlePadding = titleBBox ? titleBBox.x + titleBBox.width : 0;
  17625. switch (label.attr.textAlign) {
  17626. case 'start':
  17627. translation = surface.roundPixel(titlePadding + dx) - halfLineWidth;
  17628. break;
  17629. case 'end':
  17630. translation = surface.roundPixel(clipRect[2] - tickPadding + dx) - halfLineWidth;
  17631. break;
  17632. default:
  17633. // 'center'
  17634. translation = surface.roundPixel(titlePadding + (clipRect[2] - titlePadding - tickPadding) / 2 + dx) - halfLineWidth;
  17635. };
  17636. label.setAttributes({
  17637. translationX: translation
  17638. }, true);
  17639. break;
  17640. case 'right':
  17641. titlePadding = titleBBox ? clipRect[2] - titleBBox.x : 0;
  17642. switch (label.attr.textAlign) {
  17643. case 'start':
  17644. translation = surface.roundPixel(tickPadding + dx) + halfLineWidth;
  17645. break;
  17646. case 'end':
  17647. translation = surface.roundPixel(clipRect[2] - titlePadding + dx) + halfLineWidth;
  17648. break;
  17649. default:
  17650. // 'center'
  17651. translation = surface.roundPixel(tickPadding + (clipRect[2] - tickPadding - titlePadding) / 2 + dx) + halfLineWidth;
  17652. };
  17653. label.setAttributes({
  17654. translationX: translation
  17655. }, true);
  17656. break;
  17657. case 'top':
  17658. titlePadding = titleBBox ? titleBBox.y + titleBBox.height : 0;
  17659. label.setAttributes({
  17660. translationY: surface.roundPixel(titlePadding + (clipRect[3] - titlePadding - tickPadding) / 2) - halfLineWidth
  17661. }, true);
  17662. break;
  17663. case 'bottom':
  17664. titlePadding = titleBBox ? clipRect[3] - titleBBox.y : 0;
  17665. label.setAttributes({
  17666. translationY: surface.roundPixel(tickPadding + (clipRect[3] - tickPadding - titlePadding) / 2) + halfLineWidth
  17667. }, true);
  17668. break;
  17669. case 'radial':
  17670. label.setAttributes({
  17671. translationX: attr.centerX
  17672. }, true);
  17673. break;
  17674. case 'angular':
  17675. label.setAttributes({
  17676. translationY: attr.centerY
  17677. }, true);
  17678. break;
  17679. case 'gauge':
  17680. label.setAttributes({
  17681. translationY: attr.centerY
  17682. }, true);
  17683. break;
  17684. }
  17685. // TODO: there are better ways to detect collision.
  17686. if (docked === 'left' || docked === 'right') {
  17687. me.iterate(majorTicks, function(position, labelText, i) {
  17688. if (labelText === undefined) {
  17689. return;
  17690. }
  17691. if (renderer) {
  17692. text = Ext.callback(renderer, null, [
  17693. axis,
  17694. labelText,
  17695. layoutContext,
  17696. lastLabelText
  17697. ], 0, axis);
  17698. } else {
  17699. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17700. }
  17701. lastLabelText = labelText;
  17702. label.setAttributes({
  17703. text: String(text),
  17704. translationY: surface.roundPixel(position * yy + dy)
  17705. }, true);
  17706. label.applyTransformations();
  17707. thickness = Math.max(thickness, label.getBBox().width + tickPadding);
  17708. fly = Ext.draw.Matrix.fly(label.attr.matrix.elements.slice(0));
  17709. bbox = fly.prepend.apply(fly, labelInverseMatrix).transformBBox(label.getBBox(true));
  17710. if (lastBBox && !isBBoxIntersect(bbox, lastBBox, textPadding)) {
  17711. return;
  17712. }
  17713. surface.renderSprite(label);
  17714. lastBBox = bbox;
  17715. textSize += bbox.height;
  17716. textCount++;
  17717. });
  17718. } else if (docked === 'top' || docked === 'bottom') {
  17719. me.iterate(majorTicks, function(position, labelText, i) {
  17720. if (labelText === undefined) {
  17721. return;
  17722. }
  17723. if (renderer) {
  17724. text = Ext.callback(renderer, null, [
  17725. axis,
  17726. labelText,
  17727. layoutContext,
  17728. lastLabelText
  17729. ], 0, axis);
  17730. } else {
  17731. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17732. }
  17733. lastLabelText = labelText;
  17734. label.setAttributes({
  17735. text: String(text),
  17736. translationX: surface.roundPixel(position * xx + dx)
  17737. }, true);
  17738. label.applyTransformations();
  17739. thickness = Math.max(thickness, label.getBBox().height + tickPadding);
  17740. fly = Ext.draw.Matrix.fly(label.attr.matrix.elements.slice(0));
  17741. bbox = fly.prepend.apply(fly, labelInverseMatrix).transformBBox(label.getBBox(true));
  17742. if (lastBBox && !isBBoxIntersect(bbox, lastBBox, textPadding)) {
  17743. return;
  17744. }
  17745. surface.renderSprite(label);
  17746. lastBBox = bbox;
  17747. textSize += bbox.width;
  17748. textCount++;
  17749. });
  17750. } else if (docked === 'radial') {
  17751. me.iterate(majorTicks, function(position, labelText, i) {
  17752. if (labelText === undefined) {
  17753. return;
  17754. }
  17755. if (renderer) {
  17756. text = Ext.callback(renderer, null, [
  17757. axis,
  17758. labelText,
  17759. layoutContext,
  17760. lastLabelText
  17761. ], 0, axis);
  17762. } else {
  17763. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17764. }
  17765. lastLabelText = labelText;
  17766. if (typeof text !== 'undefined') {
  17767. label.setAttributes({
  17768. text: String(text),
  17769. translationX: attr.centerX - surface.roundPixel(position) / attr.max * attr.length * Math.cos(attr.baseRotation + Math.PI / 2),
  17770. translationY: attr.centerY - surface.roundPixel(position) / attr.max * attr.length * Math.sin(attr.baseRotation + Math.PI / 2)
  17771. }, true);
  17772. label.applyTransformations();
  17773. bbox = label.attr.matrix.transformBBox(label.getBBox(true));
  17774. if (lastBBox && !isBBoxIntersect(bbox, lastBBox)) {
  17775. return;
  17776. }
  17777. surface.renderSprite(label);
  17778. lastBBox = bbox;
  17779. textSize += bbox.width;
  17780. textCount++;
  17781. }
  17782. });
  17783. } else if (docked === 'angular') {
  17784. labelOffset += attr.majorTickSize + attr.lineWidth * 0.5;
  17785. me.iterate(majorTicks, function(position, labelText, i) {
  17786. if (labelText === undefined) {
  17787. return;
  17788. }
  17789. if (renderer) {
  17790. text = Ext.callback(renderer, null, [
  17791. axis,
  17792. labelText,
  17793. layoutContext,
  17794. lastLabelText
  17795. ], 0, axis);
  17796. } else {
  17797. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17798. }
  17799. lastLabelText = labelText;
  17800. thickness = Math.max(thickness, Math.max(attr.majorTickSize, attr.minorTickSize) + (attr.lineCap !== 'butt' ? attr.lineWidth * 0.5 : 0));
  17801. if (typeof text !== 'undefined') {
  17802. var angle = position / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
  17803. label.setAttributes({
  17804. text: String(text),
  17805. translationX: attr.centerX + (attr.length + labelOffset) * Math.cos(angle),
  17806. translationY: attr.centerY + (attr.length + labelOffset) * Math.sin(angle)
  17807. }, true);
  17808. label.applyTransformations();
  17809. bbox = label.attr.matrix.transformBBox(label.getBBox(true));
  17810. if (lastBBox && !isBBoxIntersect(bbox, lastBBox)) {
  17811. return;
  17812. }
  17813. surface.renderSprite(label);
  17814. lastBBox = bbox;
  17815. textSize += bbox.width;
  17816. textCount++;
  17817. }
  17818. });
  17819. } else if (docked === 'gauge') {
  17820. gaugeAngles = me.getGaugeAngles();
  17821. labelOffset += attr.majorTickSize + attr.lineWidth * 0.5;
  17822. me.iterate(majorTicks, function(position, labelText, i) {
  17823. if (labelText === undefined) {
  17824. return;
  17825. }
  17826. if (renderer) {
  17827. text = Ext.callback(renderer, null, [
  17828. axis,
  17829. labelText,
  17830. layoutContext,
  17831. lastLabelText
  17832. ], 0, axis);
  17833. } else {
  17834. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17835. }
  17836. lastLabelText = labelText;
  17837. if (typeof text !== 'undefined') {
  17838. var angle = (position - attr.min) / (attr.max - attr.min) * attr.totalAngle - attr.totalAngle + gaugeAngles.start;
  17839. label.setAttributes({
  17840. text: String(text),
  17841. translationX: attr.centerX + (attr.length + labelOffset) * Math.cos(angle),
  17842. translationY: attr.centerY + (attr.length + labelOffset) * Math.sin(angle)
  17843. }, true);
  17844. label.applyTransformations();
  17845. bbox = label.attr.matrix.transformBBox(label.getBBox(true));
  17846. if (lastBBox && !isBBoxIntersect(bbox, lastBBox)) {
  17847. return;
  17848. }
  17849. surface.renderSprite(label);
  17850. lastBBox = bbox;
  17851. textSize += bbox.width;
  17852. textCount++;
  17853. }
  17854. });
  17855. }
  17856. if (attr.enlargeEstStepSizeByText && textCount) {
  17857. textSize /= textCount;
  17858. textSize += tickPadding;
  17859. textSize *= 2;
  17860. if (attr.estStepSize < textSize) {
  17861. attr.estStepSize = textSize;
  17862. }
  17863. }
  17864. if (Math.abs(me.thickness - thickness) > 1) {
  17865. me.thickness = thickness;
  17866. attr.bbox.plain.dirty = true;
  17867. attr.bbox.transform.dirty = true;
  17868. me.doThicknessChanged();
  17869. return false;
  17870. }
  17871. }
  17872. },
  17873. renderAxisLine: function(surface, ctx, layout, clipRect) {
  17874. var me = this,
  17875. attr = me.attr,
  17876. halfLineWidth = attr.lineWidth * 0.5,
  17877. docked = attr.position,
  17878. position, gaugeAngles;
  17879. if (attr.axisLine && attr.length) {
  17880. switch (docked) {
  17881. case 'left':
  17882. position = surface.roundPixel(clipRect[2]) - halfLineWidth;
  17883. ctx.moveTo(position, -attr.endGap);
  17884. ctx.lineTo(position, attr.length + attr.startGap + 1);
  17885. break;
  17886. case 'right':
  17887. ctx.moveTo(halfLineWidth, -attr.endGap);
  17888. ctx.lineTo(halfLineWidth, attr.length + attr.startGap + 1);
  17889. break;
  17890. case 'bottom':
  17891. ctx.moveTo(-attr.startGap, halfLineWidth);
  17892. ctx.lineTo(attr.length + attr.endGap, halfLineWidth);
  17893. break;
  17894. case 'top':
  17895. position = surface.roundPixel(clipRect[3]) - halfLineWidth;
  17896. ctx.moveTo(-attr.startGap, position);
  17897. ctx.lineTo(attr.length + attr.endGap, position);
  17898. break;
  17899. case 'angular':
  17900. ctx.moveTo(attr.centerX + attr.length, attr.centerY);
  17901. ctx.arc(attr.centerX, attr.centerY, attr.length, 0, Math.PI * 2, true);
  17902. break;
  17903. case 'gauge':
  17904. gaugeAngles = me.getGaugeAngles();
  17905. ctx.moveTo(attr.centerX + Math.cos(gaugeAngles.start) * attr.length, attr.centerY + Math.sin(gaugeAngles.start) * attr.length);
  17906. ctx.arc(attr.centerX, attr.centerY, attr.length, gaugeAngles.start, gaugeAngles.end, true);
  17907. break;
  17908. }
  17909. }
  17910. },
  17911. getGaugeAngles: function() {
  17912. var me = this,
  17913. angle = me.attr.totalAngle,
  17914. offset;
  17915. if (angle <= Math.PI) {
  17916. offset = (Math.PI - angle) * 0.5;
  17917. } else {
  17918. offset = -(Math.PI * 2 - angle) * 0.5;
  17919. }
  17920. offset = Math.PI * 2 - offset;
  17921. return {
  17922. start: offset,
  17923. end: offset - angle
  17924. };
  17925. },
  17926. renderGridLines: function(surface, ctx, layout, clipRect) {
  17927. var me = this,
  17928. axis = me.getAxis(),
  17929. attr = me.attr,
  17930. matrix = attr.matrix,
  17931. startGap = attr.startGap,
  17932. endGap = attr.endGap,
  17933. xx = matrix.getXX(),
  17934. yy = matrix.getYY(),
  17935. dx = matrix.getDX(),
  17936. dy = matrix.getDY(),
  17937. position = attr.position,
  17938. alignment = axis.getGridAlignment(),
  17939. majorTicks = layout.majorTicks,
  17940. anchor, j, lastAnchor;
  17941. if (attr.grid) {
  17942. if (majorTicks) {
  17943. if (position === 'left' || position === 'right') {
  17944. lastAnchor = attr.min * yy + dy + endGap + startGap;
  17945. me.iterate(majorTicks, function(position, labelText, i) {
  17946. anchor = position * yy + dy + endGap;
  17947. me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
  17948. y: anchor,
  17949. height: lastAnchor - anchor
  17950. }, j = i, true);
  17951. lastAnchor = anchor;
  17952. });
  17953. j++;
  17954. anchor = 0;
  17955. me.putMarker(alignment + '-' + (j % 2 ? 'odd' : 'even'), {
  17956. y: anchor,
  17957. height: lastAnchor - anchor
  17958. }, j, true);
  17959. } else if (position === 'top' || position === 'bottom') {
  17960. lastAnchor = attr.min * xx + dx + startGap;
  17961. if (startGap) {
  17962. me.putMarker(alignment + '-even', {
  17963. x: 0,
  17964. width: lastAnchor
  17965. }, -1, true);
  17966. }
  17967. me.iterate(majorTicks, function(position, labelText, i) {
  17968. anchor = position * xx + dx + startGap;
  17969. me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
  17970. x: anchor,
  17971. width: lastAnchor - anchor
  17972. }, j = i, true);
  17973. lastAnchor = anchor;
  17974. });
  17975. j++;
  17976. anchor = attr.length + attr.startGap + attr.endGap;
  17977. me.putMarker(alignment + '-' + (j % 2 ? 'odd' : 'even'), {
  17978. x: anchor,
  17979. width: lastAnchor - anchor
  17980. }, j, true);
  17981. } else if (position === 'radial') {
  17982. me.iterate(majorTicks, function(position, labelText, i) {
  17983. if (!position) {
  17984. return;
  17985. }
  17986. anchor = position / attr.max * attr.length;
  17987. me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
  17988. scalingX: anchor,
  17989. scalingY: anchor
  17990. }, i, true);
  17991. lastAnchor = anchor;
  17992. });
  17993. } else if (position === 'angular') {
  17994. me.iterate(majorTicks, function(position, labelText, i) {
  17995. if (!attr.length) {
  17996. return;
  17997. }
  17998. anchor = position / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
  17999. me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
  18000. rotationRads: anchor,
  18001. rotationCenterX: 0,
  18002. rotationCenterY: 0,
  18003. scalingX: attr.length,
  18004. scalingY: attr.length
  18005. }, i, true);
  18006. lastAnchor = anchor;
  18007. });
  18008. }
  18009. }
  18010. }
  18011. },
  18012. renderLimits: function(clipRect) {
  18013. var me = this,
  18014. attr = me.attr,
  18015. axis = me.getAxis(),
  18016. limits = Ext.Array.from(axis.getLimits());
  18017. if (!limits.length || attr.dataMin === attr.dataMax) {
  18018. if (axis.limits) {
  18019. axis.limits.titles.attr.hidden = true;
  18020. }
  18021. return;
  18022. }
  18023. var chart = axis.getChart(),
  18024. innerPadding = chart.getInnerPadding(),
  18025. limitsRect = axis.limits.surface.getRect(),
  18026. matrix = attr.matrix,
  18027. position = attr.position,
  18028. chain = Ext.Object.chain,
  18029. titles = axis.limits.titles,
  18030. titleBBox, titlePosition, titleFlip, limit, value, i, ln, x, y;
  18031. titles.attr.hidden = false;
  18032. titles.instances = [];
  18033. titles.position = 0;
  18034. if (position === 'left' || position === 'right') {
  18035. for (i = 0 , ln = limits.length; i < ln; i++) {
  18036. limit = chain(limits[i]);
  18037. !limit.line && (limit.line = {});
  18038. value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
  18039. value = value * matrix.getYY() + matrix.getDY();
  18040. limit.line.y = value + innerPadding.top;
  18041. limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
  18042. me.putMarker('horizontal-limit-lines', limit.line, i, true);
  18043. if (limit.line.title) {
  18044. titles.add(limit.line.title);
  18045. titleBBox = titles.getBBoxFor(titles.position - 1);
  18046. titlePosition = limit.line.title.position || (position === 'left' ? 'start' : 'end');
  18047. switch (titlePosition) {
  18048. case 'start':
  18049. x = 10;
  18050. break;
  18051. case 'end':
  18052. x = limitsRect[2] - 10;
  18053. break;
  18054. case 'middle':
  18055. x = limitsRect[2] / 2;
  18056. break;
  18057. }
  18058. titles.setAttributesFor(titles.position - 1, {
  18059. x: x,
  18060. y: limit.line.y - titleBBox.height / 2,
  18061. textAlign: titlePosition,
  18062. fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle
  18063. });
  18064. }
  18065. }
  18066. } else if (position === 'top' || position === 'bottom') {
  18067. for (i = 0 , ln = limits.length; i < ln; i++) {
  18068. limit = chain(limits[i]);
  18069. !limit.line && (limit.line = {});
  18070. value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
  18071. value = value * matrix.getXX() + matrix.getDX();
  18072. limit.line.x = value + innerPadding.left;
  18073. limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
  18074. me.putMarker('vertical-limit-lines', limit.line, i, true);
  18075. if (limit.line.title) {
  18076. titles.add(limit.line.title);
  18077. titleBBox = titles.getBBoxFor(titles.position - 1);
  18078. titlePosition = limit.line.title.position || (position === 'top' ? 'end' : 'start');
  18079. switch (titlePosition) {
  18080. case 'start':
  18081. y = limitsRect[3] - titleBBox.width / 2 - 10;
  18082. break;
  18083. case 'end':
  18084. y = titleBBox.width / 2 + 10;
  18085. break;
  18086. case 'middle':
  18087. y = limitsRect[3] / 2;
  18088. break;
  18089. }
  18090. titles.setAttributesFor(titles.position - 1, {
  18091. x: limit.line.x + titleBBox.height / 2,
  18092. y: y,
  18093. fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle,
  18094. rotationRads: Math.PI / 2
  18095. });
  18096. }
  18097. }
  18098. } else if (position === 'radial') {
  18099. for (i = 0 , ln = limits.length; i < ln; i++) {
  18100. limit = chain(limits[i]);
  18101. !limit.line && (limit.line = {});
  18102. value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
  18103. if (value > attr.max) {
  18104. continue;
  18105. }
  18106. value = value / attr.max * attr.length;
  18107. limit.line.cx = attr.centerX;
  18108. limit.line.cy = attr.centerY;
  18109. limit.line.scalingX = value;
  18110. limit.line.scalingY = value;
  18111. limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
  18112. me.putMarker('circular-limit-lines', limit.line, i, true);
  18113. if (limit.line.title) {
  18114. titles.add(limit.line.title);
  18115. titleBBox = titles.getBBoxFor(titles.position - 1);
  18116. titles.setAttributesFor(titles.position - 1, {
  18117. x: attr.centerX,
  18118. y: attr.centerY - value - titleBBox.height / 2,
  18119. fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle
  18120. });
  18121. }
  18122. }
  18123. } else if (position === 'angular') {
  18124. for (i = 0 , ln = limits.length; i < ln; i++) {
  18125. limit = chain(limits[i]);
  18126. !limit.line && (limit.line = {});
  18127. value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
  18128. value = value / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
  18129. limit.line.translationX = attr.centerX;
  18130. limit.line.translationY = attr.centerY;
  18131. limit.line.rotationRads = value;
  18132. limit.line.rotationCenterX = 0;
  18133. limit.line.rotationCenterY = 0;
  18134. limit.line.scalingX = attr.length;
  18135. limit.line.scalingY = attr.length;
  18136. limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
  18137. me.putMarker('radial-limit-lines', limit.line, i, true);
  18138. if (limit.line.title) {
  18139. titles.add(limit.line.title);
  18140. titleBBox = titles.getBBoxFor(titles.position - 1);
  18141. titleFlip = ((value > -0.5 * Math.PI && value < 0.5 * Math.PI) || (value > 1.5 * Math.PI && value < 2 * Math.PI)) ? 1 : -1;
  18142. titles.setAttributesFor(titles.position - 1, {
  18143. x: attr.centerX + 0.5 * attr.length * Math.cos(value) + titleFlip * titleBBox.height / 2 * Math.sin(value),
  18144. y: attr.centerY + 0.5 * attr.length * Math.sin(value) - titleFlip * titleBBox.height / 2 * Math.cos(value),
  18145. rotationRads: titleFlip === 1 ? value : value - Math.PI,
  18146. fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle
  18147. });
  18148. }
  18149. }
  18150. } else if (position === 'gauge') {}
  18151. },
  18152. doThicknessChanged: function() {
  18153. var axis = this.getAxis();
  18154. if (axis) {
  18155. axis.onThicknessChanged();
  18156. }
  18157. },
  18158. render: function(surface, ctx, rect) {
  18159. var me = this,
  18160. layoutContext = me.getLayoutContext();
  18161. if (layoutContext) {
  18162. if (false === me.renderLabels(surface, ctx, layoutContext, rect)) {
  18163. return false;
  18164. }
  18165. ctx.beginPath();
  18166. me.renderTicks(surface, ctx, layoutContext, rect);
  18167. me.renderAxisLine(surface, ctx, layoutContext, rect);
  18168. me.renderGridLines(surface, ctx, layoutContext, rect);
  18169. me.renderLimits(rect);
  18170. ctx.stroke();
  18171. }
  18172. }
  18173. });
  18174. /*
  18175. Moved TODO comments to bottom
  18176. TODO(touch-2.2): Split different types of axis into different sprite classes.
  18177. */
  18178. /**
  18179. * @abstract
  18180. * @class Ext.chart.axis.segmenter.Segmenter
  18181. *
  18182. * Interface for a segmenter in an Axis. A segmenter defines the operations you can do to a specific
  18183. * data type.
  18184. *
  18185. * See {@link Ext.chart.axis.Axis}.
  18186. *
  18187. */
  18188. Ext.define('Ext.chart.axis.segmenter.Segmenter', {
  18189. config: {
  18190. /**
  18191. * @cfg {Ext.chart.axis.Axis} axis The axis that the Segmenter is bound.
  18192. */
  18193. axis: null
  18194. },
  18195. constructor: function(config) {
  18196. this.initConfig(config);
  18197. },
  18198. /**
  18199. * This method formats the value.
  18200. *
  18201. * @param {*} value The value to format.
  18202. * @param {Object} context Axis layout context.
  18203. * @return {String}
  18204. */
  18205. renderer: function(value, context) {
  18206. return String(value);
  18207. },
  18208. /**
  18209. * Convert from any data into the target type.
  18210. * @param {*} value The value to convert from
  18211. * @return {*} The converted value.
  18212. */
  18213. from: function(value) {
  18214. return value;
  18215. },
  18216. /**
  18217. * @method
  18218. * Returns the difference between the min and max value based on the given unit scale.
  18219. *
  18220. * @param {*} min The smaller value.
  18221. * @param {*} max The larger value.
  18222. * @param {*} unit The unit scale. Unit can be any type.
  18223. * @return {Number} The number of `unit`s between min and max. It is the minimum n that min + n * unit >= max.
  18224. */
  18225. diff: Ext.emptyFn,
  18226. /**
  18227. * @method
  18228. * Align value with step of units.
  18229. * For example, for the date segmenter, if the unit is "Month" and step is 3, the value will be aligned by
  18230. * seasons.
  18231. *
  18232. * @param {*} value The value to be aligned.
  18233. * @param {Number} step The step of units.
  18234. * @param {*} unit The unit.
  18235. * @return {*} Aligned value.
  18236. */
  18237. align: Ext.emptyFn,
  18238. /**
  18239. * @method
  18240. * Add `step` `unit`s to the value.
  18241. * @param {*} value The value to be added.
  18242. * @param {Number} step The step of units. Negative value are allowed.
  18243. * @param {*} unit The unit.
  18244. */
  18245. add: Ext.emptyFn,
  18246. /**
  18247. * @method
  18248. * Given a start point and estimated step size of a range, determine the preferred step size.
  18249. *
  18250. * @param {*} start The start point of range.
  18251. * @param {*} estStepSize The estimated step size.
  18252. * @return {Object} Return the step size by an object of step x unit.
  18253. * @return {Number} return.step The step count of units.
  18254. * @return {Number|Object} return.unit The unit.
  18255. */
  18256. preferredStep: Ext.emptyFn
  18257. });
  18258. /**
  18259. * @class Ext.chart.axis.segmenter.Names
  18260. * @extends Ext.chart.axis.segmenter.Segmenter
  18261. *
  18262. * Names data type. Names will be calculated as their indices in the methods in this class.
  18263. * The `preferredStep` always return `{ unit: 1, step: 1 }` to indicate "show every item".
  18264. *
  18265. */
  18266. Ext.define('Ext.chart.axis.segmenter.Names', {
  18267. extend: 'Ext.chart.axis.segmenter.Segmenter',
  18268. alias: 'segmenter.names',
  18269. renderer: function(value, context) {
  18270. return value;
  18271. },
  18272. diff: function(min, max, unit) {
  18273. return Math.floor(max - min);
  18274. },
  18275. align: function(value, step, unit) {
  18276. return Math.floor(value);
  18277. },
  18278. add: function(value, step, unit) {
  18279. return value + step;
  18280. },
  18281. preferredStep: function(min, estStepSize, minIdx, data) {
  18282. return {
  18283. unit: 1,
  18284. step: 1
  18285. };
  18286. }
  18287. });
  18288. /**
  18289. * @class Ext.chart.axis.segmenter.Numeric
  18290. * @extends Ext.chart.axis.segmenter.Segmenter
  18291. *
  18292. * Numeric data type.
  18293. */
  18294. Ext.define('Ext.chart.axis.segmenter.Numeric', {
  18295. extend: 'Ext.chart.axis.segmenter.Segmenter',
  18296. alias: 'segmenter.numeric',
  18297. isNumeric: true,
  18298. renderer: function(value, context) {
  18299. return value.toFixed(Math.max(0, context.majorTicks.unit.fixes));
  18300. },
  18301. diff: function(min, max, unit) {
  18302. return Math.floor((max - min) / unit.scale);
  18303. },
  18304. align: function(value, step, unit) {
  18305. var scaledStep = unit.scale * step;
  18306. return Math.floor(value / scaledStep) * scaledStep;
  18307. },
  18308. add: function(value, step, unit) {
  18309. return value + step * unit.scale;
  18310. },
  18311. preferredStep: function(min, estStepSize) {
  18312. // Getting an order of magnitude of the estStepSize with a common logarithm.
  18313. var order = Math.floor(Math.log(estStepSize) * Math.LOG10E),
  18314. scale = Math.pow(10, order);
  18315. estStepSize /= scale;
  18316. if (estStepSize < 2) {
  18317. estStepSize = 2;
  18318. } else if (estStepSize < 5) {
  18319. estStepSize = 5;
  18320. } else if (estStepSize < 10) {
  18321. estStepSize = 10;
  18322. order++;
  18323. }
  18324. return {
  18325. unit: {
  18326. // When passed estStepSize is less than 1, its order of magnitude
  18327. // is equal to -number_of_leading_zeros in the estStepSize.
  18328. fixes: -order,
  18329. // Number of fractional digits.
  18330. scale: scale
  18331. },
  18332. step: estStepSize
  18333. };
  18334. },
  18335. leadingZeros: function(n) {
  18336. // For example:
  18337. // leadingZeros(0.2) is 1,
  18338. // leadingZeros(-0.01) is 2.
  18339. return -Math.floor(Ext.Number.log10(Math.abs(n)));
  18340. },
  18341. /**
  18342. * Wraps the provided estimated step size of a range without altering it into a step size object.
  18343. *
  18344. * @param {*} min The start point of range.
  18345. * @param {*} estStepSize The estimated step size.
  18346. * @return {Object} Return the step size by an object of step x unit.
  18347. * @return {Number} return.step The step count of units.
  18348. * @return {Object} return.unit The unit.
  18349. */
  18350. exactStep: function(min, estStepSize) {
  18351. var stepZeros = this.leadingZeros(estStepSize),
  18352. scale = Math.pow(10, stepZeros);
  18353. return {
  18354. unit: {
  18355. // add one decimal point if estStepSize is not a multiple of scale
  18356. fixes: stepZeros + (estStepSize % scale === 0 ? 0 : 1),
  18357. // Swap scale & step, if the estStepSize < 1,
  18358. // or 'diff' method will give us rounding errors.
  18359. scale: estStepSize < 1 ? estStepSize : 1
  18360. },
  18361. step: estStepSize < 1 ? 1 : estStepSize
  18362. };
  18363. },
  18364. adjustByMajorUnit: function(step, scale, range) {
  18365. var min = range[0],
  18366. max = range[1],
  18367. increment = step * scale,
  18368. remainder, multiplier;
  18369. multiplier = Math.max(1 / (min || 1), 1 / (increment || 1));
  18370. multiplier = multiplier > 1 ? multiplier : 1;
  18371. remainder = ((min * multiplier) % (increment * multiplier)) / multiplier;
  18372. if (remainder !== 0) {
  18373. range[0] = min - remainder + (min < 0 ? -increment : 0);
  18374. }
  18375. multiplier = Math.max(1 / (max || 1), 1 / (increment || 1));
  18376. multiplier = multiplier > 1 ? multiplier : 1;
  18377. remainder = ((max * multiplier) % (increment * multiplier)) / multiplier;
  18378. if (remainder !== 0) {
  18379. range[1] = max - remainder + (max > 0 ? increment : 0);
  18380. }
  18381. }
  18382. });
  18383. /**
  18384. * @class Ext.chart.axis.segmenter.Time
  18385. * @extends Ext.chart.axis.segmenter.Segmenter
  18386. *
  18387. * Time data type.
  18388. */
  18389. Ext.define('Ext.chart.axis.segmenter.Time', {
  18390. extend: 'Ext.chart.axis.segmenter.Segmenter',
  18391. alias: 'segmenter.time',
  18392. config: {
  18393. /**
  18394. * @cfg {Object} step
  18395. * @cfg {String} step.unit The unit of the step (Ext.Date.DAY, Ext.Date.MONTH, etc).
  18396. * @cfg {Number} step.step The number of units for the step (1, 2, etc).
  18397. * If specified, will override the result of {@link #preferredStep}.
  18398. * For example:
  18399. *
  18400. * step: {
  18401. * unit: Ext.Date.HOUR,
  18402. * step: 1
  18403. * }
  18404. */
  18405. step: null
  18406. },
  18407. renderer: function(value, context) {
  18408. var ExtDate = Ext.Date;
  18409. switch (context.majorTicks.unit) {
  18410. case 'y':
  18411. return ExtDate.format(value, 'Y');
  18412. case 'mo':
  18413. return ExtDate.format(value, 'Y-m');
  18414. case 'd':
  18415. return ExtDate.format(value, 'Y-m-d');
  18416. }
  18417. return ExtDate.format(value, 'Y-m-d\nH:i:s');
  18418. },
  18419. from: function(value) {
  18420. return new Date(value);
  18421. },
  18422. diff: function(min, max, unit) {
  18423. if (isFinite(min)) {
  18424. min = new Date(min);
  18425. }
  18426. if (isFinite(max)) {
  18427. max = new Date(max);
  18428. }
  18429. return Ext.Date.diff(min, max, unit);
  18430. },
  18431. updateStep: function() {
  18432. var axis = this.getAxis();
  18433. if (axis && !this.isConfiguring) {
  18434. axis.performLayout();
  18435. }
  18436. },
  18437. align: function(date, step, unit) {
  18438. if (unit === 'd' && step >= 7) {
  18439. date = Ext.Date.align(date, 'd', step);
  18440. date.setDate(date.getDate() - date.getDay() + 1);
  18441. return date;
  18442. } else {
  18443. return Ext.Date.align(date, unit, step);
  18444. }
  18445. },
  18446. add: function(value, step, unit) {
  18447. return Ext.Date.add(new Date(value), unit, step);
  18448. },
  18449. timeBuckets: [
  18450. {
  18451. unit: Ext.Date.YEAR,
  18452. steps: [
  18453. 1,
  18454. 2,
  18455. 5,
  18456. 10,
  18457. 20,
  18458. 50,
  18459. 100,
  18460. 200,
  18461. 500
  18462. ]
  18463. },
  18464. {
  18465. unit: Ext.Date.MONTH,
  18466. steps: [
  18467. 1,
  18468. 3,
  18469. 6
  18470. ]
  18471. },
  18472. {
  18473. unit: Ext.Date.DAY,
  18474. steps: [
  18475. 1,
  18476. 7,
  18477. 14
  18478. ]
  18479. },
  18480. {
  18481. unit: Ext.Date.HOUR,
  18482. steps: [
  18483. 1,
  18484. 6,
  18485. 12
  18486. ]
  18487. },
  18488. {
  18489. unit: Ext.Date.MINUTE,
  18490. steps: [
  18491. 1,
  18492. 5,
  18493. 15,
  18494. 30
  18495. ]
  18496. },
  18497. {
  18498. unit: Ext.Date.SECOND,
  18499. steps: [
  18500. 1,
  18501. 5,
  18502. 15,
  18503. 30
  18504. ]
  18505. },
  18506. {
  18507. unit: Ext.Date.MILLI,
  18508. steps: [
  18509. 1,
  18510. 2,
  18511. 5,
  18512. 10,
  18513. 20,
  18514. 50,
  18515. 100,
  18516. 200,
  18517. 500
  18518. ]
  18519. }
  18520. ],
  18521. /**
  18522. * @private
  18523. * Takes a time interval and figures out what is the smallest nice number of which
  18524. * units (years, months, days, etc.) that can fully encompass that interval.
  18525. * @param {Date} min
  18526. * @param {Date} max
  18527. * @return {Object}
  18528. * @return {String} return.unit The unit.
  18529. * @return {Number} return.step The number of units.
  18530. */
  18531. getTimeBucket: function(min, max) {
  18532. var buckets = this.timeBuckets,
  18533. unit, unitCount, steps, step, result, i, j;
  18534. for (i = 0; i < buckets.length; i++) {
  18535. unit = buckets[i].unit;
  18536. unitCount = this.diff(min, max, unit);
  18537. if (unitCount > 0) {
  18538. steps = buckets[i].steps;
  18539. for (j = 0; j < steps.length; j++) {
  18540. step = steps[j];
  18541. if (unitCount <= step) {
  18542. break;
  18543. }
  18544. }
  18545. result = {
  18546. unit: unit,
  18547. step: step
  18548. };
  18549. break;
  18550. }
  18551. }
  18552. // If the interval is smaller then one millisecond ...
  18553. if (!result) {
  18554. // ... we can't go smaller than one millisecond.
  18555. result = {
  18556. unit: Ext.Date.MILLI,
  18557. step: 1
  18558. };
  18559. }
  18560. return result;
  18561. },
  18562. preferredStep: function(min, estStepSize) {
  18563. var step = this.getStep();
  18564. return step ? step : this.getTimeBucket(new Date(+min), new Date(+min + Math.ceil(estStepSize)));
  18565. }
  18566. });
  18567. /**
  18568. * @abstract
  18569. * @class Ext.chart.axis.layout.Layout
  18570. *
  18571. * Interface used by Axis to process its data into a meaningful layout.
  18572. */
  18573. Ext.define('Ext.chart.axis.layout.Layout', {
  18574. mixins: {
  18575. observable: 'Ext.mixin.Observable'
  18576. },
  18577. config: {
  18578. /**
  18579. * @cfg {Ext.chart.axis.Axis} axis The axis that the Layout is bound.
  18580. */
  18581. axis: null
  18582. },
  18583. constructor: function(config) {
  18584. this.mixins.observable.constructor.call(this, config);
  18585. },
  18586. /**
  18587. * Processes the data of the series bound to the axis.
  18588. * @param {Ext.chart.series.Series} series The bound series.
  18589. */
  18590. processData: function(series) {
  18591. var me = this,
  18592. axis = me.getAxis(),
  18593. direction = axis.getDirection(),
  18594. boundSeries = axis.boundSeries,
  18595. i, ln;
  18596. if (series) {
  18597. series['coordinate' + direction]();
  18598. } else {
  18599. for (i = 0 , ln = boundSeries.length; i < ln; i++) {
  18600. boundSeries[i]['coordinate' + direction]();
  18601. }
  18602. }
  18603. },
  18604. /**
  18605. * Calculates the position of major ticks for the axis.
  18606. * @param {Object} context
  18607. */
  18608. calculateMajorTicks: function(context) {
  18609. var me = this,
  18610. attr = context.attr,
  18611. range = attr.max - attr.min,
  18612. zoom = range / Math.max(1, attr.length) * (attr.visibleMax - attr.visibleMin),
  18613. viewMin = attr.min + range * attr.visibleMin,
  18614. viewMax = attr.min + range * attr.visibleMax,
  18615. estStepSize = attr.estStepSize * zoom,
  18616. majorTicks = me.snapEnds(context, attr.min, attr.max, estStepSize);
  18617. if (majorTicks) {
  18618. me.trimByRange(context, majorTicks, viewMin, viewMax);
  18619. context.majorTicks = majorTicks;
  18620. }
  18621. },
  18622. /**
  18623. * Calculates the position of sub ticks for the axis.
  18624. * @param {Object} context
  18625. */
  18626. calculateMinorTicks: function(context) {
  18627. if (this.snapMinorEnds) {
  18628. context.minorTicks = this.snapMinorEnds(context);
  18629. }
  18630. },
  18631. /**
  18632. * Calculates the position of tick marks for the axis.
  18633. * @param {Object} context
  18634. * @return {*}
  18635. */
  18636. calculateLayout: function(context) {
  18637. var me = this,
  18638. attr = context.attr;
  18639. if (attr.length === 0) {
  18640. return null;
  18641. }
  18642. if (attr.majorTicks) {
  18643. me.calculateMajorTicks(context);
  18644. if (attr.minorTicks) {
  18645. me.calculateMinorTicks(context);
  18646. }
  18647. }
  18648. },
  18649. /**
  18650. * @method
  18651. * Snaps the data bound to the axis to meaningful tick marks.
  18652. * @param {Object} context
  18653. * @param {Number} min
  18654. * @param {Number} max
  18655. * @param {Number} estStepSize
  18656. */
  18657. snapEnds: Ext.emptyFn,
  18658. /**
  18659. * Trims the layout of the axis by the defined minimum and maximum.
  18660. * @param {Object} context
  18661. * @param {Object} ticks Ticks object (e.g. major ticks) to be modified.
  18662. * @param {Number} trimMin
  18663. * @param {Number} trimMax
  18664. */
  18665. trimByRange: function(context, ticks, trimMin, trimMax) {
  18666. var segmenter = context.segmenter,
  18667. unit = ticks.unit,
  18668. beginIdx = segmenter.diff(ticks.from, trimMin, unit),
  18669. endIdx = segmenter.diff(ticks.from, trimMax, unit),
  18670. begin = Math.max(0, Math.ceil(beginIdx / ticks.step)),
  18671. end = Math.min(ticks.steps, Math.floor(endIdx / ticks.step));
  18672. if (end < ticks.steps) {
  18673. ticks.to = segmenter.add(ticks.from, end * ticks.step, unit);
  18674. }
  18675. if (ticks.max > trimMax) {
  18676. ticks.max = ticks.to;
  18677. }
  18678. if (ticks.from < trimMin) {
  18679. ticks.from = segmenter.add(ticks.from, begin * ticks.step, unit);
  18680. while (ticks.from < trimMin) {
  18681. begin++;
  18682. ticks.from = segmenter.add(ticks.from, ticks.step, unit);
  18683. }
  18684. }
  18685. if (ticks.min < trimMin) {
  18686. ticks.min = ticks.from;
  18687. }
  18688. ticks.steps = end - begin;
  18689. }
  18690. });
  18691. /**
  18692. * @class Ext.chart.axis.layout.Discrete
  18693. * @extends Ext.chart.axis.layout.Layout
  18694. *
  18695. * Simple processor for data that cannot be interpolated.
  18696. */
  18697. Ext.define('Ext.chart.axis.layout.Discrete', {
  18698. extend: 'Ext.chart.axis.layout.Layout',
  18699. alias: 'axisLayout.discrete',
  18700. isDiscrete: true,
  18701. processData: function() {
  18702. var me = this,
  18703. axis = me.getAxis(),
  18704. seriesList = axis.boundSeries,
  18705. direction = axis.getDirection(),
  18706. i, ln, series;
  18707. me.labels = [];
  18708. me.labelMap = {};
  18709. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  18710. series = seriesList[i];
  18711. if (series['get' + direction + 'Axis']() === axis) {
  18712. series['coordinate' + direction]();
  18713. }
  18714. }
  18715. // About the labels on Category axes (aka. axes with a Discrete layout)...
  18716. //
  18717. // When the data set from the store changes, series.processData() is called, which does its thing
  18718. // at the series level and then calls series.updateLabelData() to update the labels in the sprites
  18719. // that belong to the series. At the same time, series.processData() calls axis.processData(), which
  18720. // also does its thing but at the axis level, and also needs to update the labels for the sprite(s)
  18721. // that belong to the axis. This is not that simple, however. So how are the axis labels rendered?
  18722. // First, axis.sprite.Axis.render() calls renderLabels() which obtains the majorTicks from the
  18723. // axis.layout and iterate() through them. The majorTicks are an object returned by snapEnds() below
  18724. // which provides a getLabel() function that returns the label from the axis.layoutContext.data array.
  18725. // So now the question is: how are the labels transferred from the axis.layout to the axis.layoutContext?
  18726. // The easy response is: it's in calculateLayout() below. The issue is to call calculateLayout() because
  18727. // it takes in an axis.layoutContext that can only be created in axis.sprite.Axis.layoutUpdater(), which is
  18728. // a private "updater" function that is called by all the sprite's "triggers". Of course, we don't
  18729. // want to call layoutUpdater() directly from here, so instead we update the sprite's data attribute, which
  18730. // sets the trigger which calls layoutUpdater() which calls calculateLayout() etc...
  18731. // Note that the sprite's data attribute could be set to any value and it would still result in the
  18732. // trigger we need. For consistency, however, it is set to the labels.
  18733. axis.getSprites()[0].setAttributes({
  18734. data: me.labels
  18735. });
  18736. me.fireEvent('datachange', me.labels);
  18737. },
  18738. /**
  18739. * @method calculateLayout
  18740. * @inheritdoc
  18741. */
  18742. calculateLayout: function(context) {
  18743. context.data = this.labels;
  18744. this.callParent([
  18745. context
  18746. ]);
  18747. },
  18748. /**
  18749. * @method calculateMajorTicks
  18750. * @inheritdoc
  18751. */
  18752. calculateMajorTicks: function(context) {
  18753. var me = this,
  18754. attr = context.attr,
  18755. data = context.data,
  18756. range = attr.max - attr.min,
  18757. viewMin = attr.min + range * attr.visibleMin,
  18758. viewMax = attr.min + range * attr.visibleMax,
  18759. out;
  18760. out = me.snapEnds(context, Math.max(0, attr.min), Math.min(attr.max, data.length - 1), 1);
  18761. if (out) {
  18762. me.trimByRange(context, out, viewMin, viewMax);
  18763. context.majorTicks = out;
  18764. }
  18765. },
  18766. /**
  18767. * @method snapEnds
  18768. * @inheritdoc
  18769. */
  18770. snapEnds: function(context, min, max, estStepSize) {
  18771. estStepSize = Math.ceil(estStepSize);
  18772. var steps = Math.floor((max - min) / estStepSize),
  18773. data = context.data;
  18774. return {
  18775. min: min,
  18776. max: max,
  18777. from: min,
  18778. to: steps * estStepSize + min,
  18779. step: estStepSize,
  18780. steps: steps,
  18781. unit: 1,
  18782. getLabel: function(currentStep) {
  18783. return data[this.from + this.step * currentStep];
  18784. },
  18785. get: function(currentStep) {
  18786. return this.from + this.step * currentStep;
  18787. }
  18788. };
  18789. },
  18790. /**
  18791. * @method trimByRange
  18792. * @inheritdoc
  18793. */
  18794. trimByRange: function(context, out, trimMin, trimMax) {
  18795. var unit = out.unit,
  18796. beginIdx = Math.ceil((trimMin - out.from) / unit) * unit,
  18797. endIdx = Math.floor((trimMax - out.from) / unit) * unit,
  18798. begin = Math.max(0, Math.ceil(beginIdx / out.step)),
  18799. end = Math.min(out.steps, Math.floor(endIdx / out.step));
  18800. if (end < out.steps) {
  18801. out.to = end;
  18802. }
  18803. if (out.max > trimMax) {
  18804. out.max = out.to;
  18805. }
  18806. if (out.from < trimMin && out.step > 0) {
  18807. out.from = out.from + begin * out.step * unit;
  18808. while (out.from < trimMin) {
  18809. begin++;
  18810. out.from += out.step * unit;
  18811. }
  18812. }
  18813. if (out.min < trimMin) {
  18814. out.min = out.from;
  18815. }
  18816. out.steps = end - begin;
  18817. },
  18818. getCoordFor: function(value, field, idx, items) {
  18819. this.labels.push(value);
  18820. return this.labels.length - 1;
  18821. }
  18822. });
  18823. /**
  18824. * Discrete layout that combines duplicate data points only if they have the same index.
  18825. * For example:
  18826. *
  18827. * @example
  18828. * Ext.create({
  18829. * xtype: 'cartesian',
  18830. * title: 'Weight vs Calories',
  18831. *
  18832. * renderTo: document.body,
  18833. * width: 400,
  18834. * height: 400,
  18835. *
  18836. * store: {
  18837. * fields: ['month', 'weight', 'calories'],
  18838. * data: [
  18839. * {
  18840. * month: 'Jan',
  18841. * weight: 185,
  18842. * calories: 2650
  18843. * },
  18844. * {
  18845. * month: 'Jan',
  18846. * weight: 188,
  18847. * calories: 2800
  18848. * },
  18849. * {
  18850. * month: 'Feb',
  18851. * weight: 188,
  18852. * calories: 2800
  18853. * },
  18854. * {
  18855. * month: 'Mar',
  18856. * weight: 191,
  18857. * calories: 2800
  18858. * },
  18859. * {
  18860. * month: 'Apr',
  18861. * weight: 189,
  18862. * calories: 1500
  18863. * },
  18864. * {
  18865. * month: 'May',
  18866. * weight: 187,
  18867. * calories: 1350
  18868. * }
  18869. * ]
  18870. * },
  18871. *
  18872. * axes: [{
  18873. * type: 'numeric',
  18874. * position: 'left',
  18875. * fields: ['weight'],
  18876. * minimum: 140
  18877. * }, {
  18878. * type: 'numeric',
  18879. * position: 'right',
  18880. * fields: ['calories'],
  18881. * minimum: 500,
  18882. * maximum: 3500
  18883. * }, {
  18884. * type: 'category',
  18885. * grid: true,
  18886. * layout: 'combineByIndex',
  18887. * fields: 'month',
  18888. * position: 'bottom',
  18889. * label: {
  18890. * rotate: {
  18891. * degrees: -45
  18892. * }
  18893. * }
  18894. * }],
  18895. *
  18896. * series: [{
  18897. * type: 'line',
  18898. * title: 'Weight',
  18899. * xField: 'month',
  18900. * yField: 'weight',
  18901. * smooth: true,
  18902. * marker: true
  18903. * }, {
  18904. * type: 'line',
  18905. * title: 'Calories',
  18906. * xField: 'month',
  18907. * yField: 'calories',
  18908. * smooth: true,
  18909. * marker: true
  18910. * }],
  18911. *
  18912. * legend: {
  18913. * docked: 'bottom'
  18914. * }
  18915. *
  18916. * });
  18917. *
  18918. * @since 6.5.0
  18919. */
  18920. Ext.define('Ext.chart.axis.layout.CombineByIndex', {
  18921. extend: 'Ext.chart.axis.layout.Discrete',
  18922. alias: 'axisLayout.combineByIndex',
  18923. getCoordFor: function(value, field, idx, items) {
  18924. var labels = this.labels,
  18925. result = idx;
  18926. if (labels[idx] !== value) {
  18927. result = labels.push(value) - 1;
  18928. }
  18929. return result;
  18930. }
  18931. });
  18932. /**
  18933. * Discrete processor that combines duplicate data points.
  18934. */
  18935. Ext.define('Ext.chart.axis.layout.CombineDuplicate', {
  18936. extend: 'Ext.chart.axis.layout.Discrete',
  18937. alias: 'axisLayout.combineDuplicate',
  18938. getCoordFor: function(value, field, idx, items) {
  18939. if (!(value in this.labelMap)) {
  18940. var result = this.labelMap[value] = this.labels.length;
  18941. this.labels.push(value);
  18942. return result;
  18943. }
  18944. return this.labelMap[value];
  18945. }
  18946. });
  18947. /**
  18948. * @class Ext.chart.axis.layout.Continuous
  18949. * @extends Ext.chart.axis.layout.Layout
  18950. *
  18951. * Processor for axis data that can be interpolated.
  18952. */
  18953. Ext.define('Ext.chart.axis.layout.Continuous', {
  18954. extend: 'Ext.chart.axis.layout.Layout',
  18955. alias: 'axisLayout.continuous',
  18956. isContinuous: true,
  18957. config: {
  18958. adjustMinimumByMajorUnit: false,
  18959. adjustMaximumByMajorUnit: false
  18960. },
  18961. getCoordFor: function(value, field, idx, items) {
  18962. return +value;
  18963. },
  18964. /**
  18965. * @method snapEnds
  18966. * @inheritdoc
  18967. */
  18968. snapEnds: function(context, min, max, estStepSize) {
  18969. var segmenter = context.segmenter,
  18970. axis = this.getAxis(),
  18971. noAnimation = !axis.spriteAnimationCount,
  18972. majorTickSteps = axis.getMajorTickSteps(),
  18973. // if specific number of steps requested and the segmenter supports such segmentation
  18974. bucket = majorTickSteps && segmenter.exactStep ? segmenter.exactStep(min, (max - min) / majorTickSteps) : segmenter.preferredStep(min, estStepSize),
  18975. unit = bucket.unit,
  18976. step = bucket.step,
  18977. diffSteps = segmenter.diff(min, max, unit),
  18978. steps = (majorTickSteps || diffSteps) + 1,
  18979. from;
  18980. // If 'majorTickSteps' config of the axis is set (is not 0), it means that
  18981. // we want to split the range at that number of equal intervals (segmenter.exactStep),
  18982. // and don't care if the resulting ticks are at nice round values or not.
  18983. // So 'from' (aligned) step is equal to 'min' (unaligned step).
  18984. // And 'to' is equal to 'max'.
  18985. //
  18986. // Another case where this is possible, is when the range between 'min' and
  18987. // 'max' can be represented by n steps, where n is an integer.
  18988. // For example, if the data values are [7, 17, 27, 37, 47], the data step is 10
  18989. // and, if the calculated tick step (segmenter.preferredStep) is also 10,
  18990. // there is no need to segmenter.align the 'min' to 0, so that the ticks are at
  18991. // [0, 10, 20, 30, 40, 50], because the data points are already perfectly
  18992. // spaced, so the ticks can be exactly at the data points without runing the
  18993. // aesthetics.
  18994. //
  18995. // The 'noAnimation' check is required to prevent EXTJS-25413 from happening.
  18996. // The segmentation described above is ideal for a static chart, but produces
  18997. // unwanted effects during animation.
  18998. if (majorTickSteps || (noAnimation && +segmenter.add(min, diffSteps, unit) === max)) {
  18999. from = min;
  19000. } else {
  19001. from = segmenter.align(min, step, unit);
  19002. }
  19003. return {
  19004. // min/max are NOT aligned to step
  19005. min: segmenter.from(min),
  19006. max: segmenter.from(max),
  19007. // from/to are aligned to step
  19008. from: from,
  19009. to: segmenter.add(from, steps, unit),
  19010. step: step,
  19011. steps: steps,
  19012. unit: unit,
  19013. get: function(currentStep) {
  19014. return segmenter.add(this.from, this.step * currentStep, this.unit);
  19015. }
  19016. };
  19017. },
  19018. snapMinorEnds: function(context) {
  19019. var majorTicks = context.majorTicks,
  19020. minorTickSteps = this.getAxis().getMinorTickSteps(),
  19021. segmenter = context.segmenter,
  19022. min = majorTicks.min,
  19023. max = majorTicks.max,
  19024. from = majorTicks.from,
  19025. unit = majorTicks.unit,
  19026. step = majorTicks.step / minorTickSteps,
  19027. scaledStep = step * unit.scale,
  19028. fromMargin = from - min,
  19029. offset = Math.floor(fromMargin / scaledStep),
  19030. extraSteps = offset + Math.floor((max - majorTicks.to) / scaledStep) + 1,
  19031. steps = majorTicks.steps * minorTickSteps + extraSteps;
  19032. return {
  19033. min: min,
  19034. max: max,
  19035. from: min + fromMargin % scaledStep,
  19036. to: segmenter.add(from, steps * step, unit),
  19037. step: step,
  19038. steps: steps,
  19039. unit: unit,
  19040. get: function(current) {
  19041. return (current % minorTickSteps + offset + 1 !== 0) ? // don't render minor tick in major tick position
  19042. segmenter.add(this.from, this.step * current, unit) : null;
  19043. }
  19044. };
  19045. }
  19046. });
  19047. /**
  19048. * @class Ext.chart.axis.Axis
  19049. *
  19050. * Defines axis for charts.
  19051. *
  19052. * Using the current model, the type of axis can be easily extended. By default, Sencha Charts provide three different
  19053. * types of axis:
  19054. *
  19055. * * **numeric** - the data attached to this axis is numeric and continuous.
  19056. * * **time** - the data attached to this axis is (or gets converted into) a date/time value; it is continuous.
  19057. * * **category** - the data attached to this axis belongs to a finite set. The data points are evenly placed along the axis.
  19058. *
  19059. * The behavior of an axis can be easily changed by setting different types of axis layout and axis segmenter to the axis.
  19060. *
  19061. * Axis layout defines how the data points are placed. Using continuous layout, the data points will be distributed by
  19062. * the numeric value. Using discrete layout the data points will be spaced evenly. Furthermore, if you want to combine
  19063. * the data points with the duplicate values in a discrete layout, you should use combineDuplicate layout.
  19064. *
  19065. * Segmenter defines the way to segment data range. For example, if you have a Date-type data range from Jan 1, 1997 to
  19066. * Jan 1, 2017, the segmenter will segement the data range into years, months or days based on the current zooming
  19067. * level.
  19068. *
  19069. * It is possible to write custom axis layouts and segmenters to extends this behavior by simply implementing interfaces
  19070. * {@link Ext.chart.axis.layout.Layout} and {@link Ext.chart.axis.segmenter.Segmenter}.
  19071. *
  19072. * Here's an example for the axes part of a chart definition:
  19073. * An example of axis for a series (in this case for an area chart that has multiple layers of yFields) could be:
  19074. *
  19075. * axes: [{
  19076. * type: 'numeric',
  19077. * position: 'left',
  19078. * title: 'Number of Hits',
  19079. * grid: {
  19080. * odd: {
  19081. * opacity: 1,
  19082. * fill: '#ddd',
  19083. * stroke: '#bbb',
  19084. * lineWidth: 1
  19085. * }
  19086. * },
  19087. * minimum: 0
  19088. * }, {
  19089. * type: 'category',
  19090. * position: 'bottom',
  19091. * title: 'Month of the Year',
  19092. * grid: true,
  19093. * label: {
  19094. * rotate: {
  19095. * degrees: 315
  19096. * }
  19097. * }
  19098. * }]
  19099. *
  19100. * In this case we use a `numeric` axis for displaying the values of the Area series and a `category` axis for displaying the names of
  19101. * the store elements. The numeric axis is placed on the left of the screen, while the category axis is placed at the bottom of the chart.
  19102. * Both the category and numeric axes have `grid` set, which means that horizontal and vertical lines will cover the chart background. In the
  19103. * category axis the labels will be rotated so they can fit the space better.
  19104. */
  19105. Ext.define('Ext.chart.axis.Axis', {
  19106. xtype: 'axis',
  19107. mixins: {
  19108. observable: 'Ext.mixin.Observable'
  19109. },
  19110. requires: [
  19111. 'Ext.chart.axis.sprite.Axis',
  19112. 'Ext.chart.axis.segmenter.*',
  19113. 'Ext.chart.axis.layout.*',
  19114. 'Ext.chart.Util'
  19115. ],
  19116. isAxis: true,
  19117. /**
  19118. * @event rangechange
  19119. * Fires when the {@link Ext.chart.axis.Axis#range range} of the axis changes.
  19120. * @param {Ext.chart.axis.Axis} axis
  19121. * @param {Array} range
  19122. * @param {Array} oldRange
  19123. */
  19124. /**
  19125. * @event visiblerangechange
  19126. * Fires when the {@link #visibleRange} of the axis changes.
  19127. * @param {Ext.chart.axis.Axis} axis
  19128. * @param {Array} visibleRange
  19129. */
  19130. /**
  19131. * @cfg {String} id
  19132. * The **unique** id of this axis instance.
  19133. */
  19134. config: {
  19135. /**
  19136. * @cfg {String} position
  19137. * Where to set the axis. Available options are `left`, `bottom`, `right`, `top`, `radial` and `angular`.
  19138. */
  19139. position: 'bottom',
  19140. /**
  19141. * @cfg {Array} fields
  19142. * An array containing the names of the record fields which should be mapped along the axis.
  19143. * This is optional if the binding between series and fields is clear.
  19144. */
  19145. fields: [],
  19146. /**
  19147. * @cfg {Object} label
  19148. *
  19149. * The label configuration object for the Axis. This object may include style attributes
  19150. * like `spacing`, `padding`, `font` that receives a string or number and
  19151. * returns a new string with the modified values.
  19152. *
  19153. * For more supported values, see the configurations for {@link Ext.chart.sprite.Label}.
  19154. */
  19155. label: undefined,
  19156. /**
  19157. * @cfg {Object} grid
  19158. * The grid configuration object for the Axis style. Can contain `stroke` or `fill` attributes.
  19159. * Also may contain an `odd` or `even` property in which you only style things on odd or even rows.
  19160. * For example:
  19161. *
  19162. *
  19163. * grid {
  19164. * odd: {
  19165. * stroke: '#555'
  19166. * },
  19167. * even: {
  19168. * stroke: '#ccc'
  19169. * }
  19170. * }
  19171. */
  19172. grid: false,
  19173. /**
  19174. * @cfg {Array|Object} limits
  19175. * The limit lines configuration for the axis.
  19176. * For example:
  19177. *
  19178. * limits: [{
  19179. * value: 50,
  19180. * line: {
  19181. * strokeStyle: 'red',
  19182. * lineDash: [6, 3],
  19183. * title: {
  19184. * text: 'Monthly minimum',
  19185. * fontSize: 14
  19186. * }
  19187. * }
  19188. * }]
  19189. */
  19190. limits: null,
  19191. /**
  19192. * @cfg {Function} renderer Allows to change the text shown next to the tick.
  19193. * @param {Ext.chart.axis.Axis} axis The axis.
  19194. * @param {String/Number} label The label.
  19195. * @param {Object} layoutContext The object that holds calculated positions
  19196. * of axis' ticks based on current layout, segmenter, axis length and configuration.
  19197. * @param {String/Number/null} lastLabel The last label (if any).
  19198. * @return {String} The label to display.
  19199. * @controllable
  19200. */
  19201. renderer: null,
  19202. /**
  19203. * @protected
  19204. * @cfg {Ext.chart.AbstractChart} chart The Chart that the Axis is bound.
  19205. */
  19206. chart: null,
  19207. /**
  19208. * @cfg {Object} style
  19209. * The style for the axis line and ticks.
  19210. * Refer to the {@link Ext.chart.axis.sprite.Axis}
  19211. */
  19212. style: null,
  19213. /**
  19214. * @cfg {Number} margin
  19215. * The margin of the axis. Used to control the spacing between axes in charts with multiple axes.
  19216. * Unlike CSS where the margin is added on all 4 sides of an element, the `margin` is the total space
  19217. * that is added horizontally for a vertical axis, vertically for a horizontal axis,
  19218. * and radially for an angular axis.
  19219. */
  19220. margin: 0,
  19221. /**
  19222. * @cfg {Number} [titleMargin=4]
  19223. * The margin around the axis title. Unlike CSS where the margin is added on all 4
  19224. * sides of an element, the `titleMargin` is the total space that is added horizontally
  19225. * for a vertical title and vertically for an horizontal title, with half the `titleMargin`
  19226. * being added on either side.
  19227. */
  19228. titleMargin: 4,
  19229. /**
  19230. * @cfg {Object} background
  19231. * The background config for the axis surface.
  19232. */
  19233. background: null,
  19234. /**
  19235. * @cfg {Number} minimum
  19236. * The minimum value drawn by the axis. If not set explicitly, the axis
  19237. * minimum will be calculated automatically.
  19238. */
  19239. minimum: NaN,
  19240. /**
  19241. * @cfg {Number} maximum
  19242. * The maximum value drawn by the axis. If not set explicitly, the axis
  19243. * maximum will be calculated automatically.
  19244. */
  19245. maximum: NaN,
  19246. /**
  19247. * @cfg {Boolean} reconcileRange
  19248. * If 'true' the range of the axis will be a union of ranges
  19249. * of all the axes with the same direction. Defaults to 'false'.
  19250. */
  19251. reconcileRange: false,
  19252. /**
  19253. * @cfg {Number} minZoom
  19254. * The minimum zooming level for axis.
  19255. */
  19256. minZoom: 1,
  19257. /**
  19258. * @cfg {Number} maxZoom
  19259. * The maximum zooming level for axis.
  19260. */
  19261. maxZoom: 10000,
  19262. /**
  19263. * @cfg {Object|Ext.chart.axis.layout.Layout} layout
  19264. * The axis layout config. See {@link Ext.chart.axis.layout.Layout}
  19265. */
  19266. layout: 'continuous',
  19267. /**
  19268. * @cfg {Object|Ext.chart.axis.segmenter.Segmenter} segmenter
  19269. * The segmenter config. See {@link Ext.chart.axis.segmenter.Segmenter}
  19270. */
  19271. segmenter: 'numeric',
  19272. /**
  19273. * @cfg {Boolean} hidden
  19274. * Indicate whether to hide the axis.
  19275. * If the axis is hidden, one of the axis line, ticks, labels or the title will be shown and
  19276. * no margin will be taken.
  19277. * The coordination mechanism works fine no matter if the axis is hidden.
  19278. */
  19279. hidden: false,
  19280. /**
  19281. * @cfg {Number} [majorTickSteps=0]
  19282. * Forces the number of major ticks to the specified value.
  19283. * Both {@link #minimum} and {@link #maximum} should be specified.
  19284. */
  19285. majorTickSteps: 0,
  19286. /**
  19287. * @cfg {Number} [minorTickSteps=0]
  19288. * The number of small ticks between two major ticks.
  19289. */
  19290. minorTickSteps: 0,
  19291. /**
  19292. * @cfg {Boolean} adjustByMajorUnit
  19293. * Whether to make the auto-calculated minimum and maximum of the axis
  19294. * a multiple of the interval between the major ticks of the axis.
  19295. * If {@link #majorTickSteps}, {@link #minimum} or {@link #maximum}
  19296. * configs have been set, this config will be ignored.
  19297. * Defaults to 'true'.
  19298. * Note: this config has no effect if the axis is {@link #hidden}.
  19299. */
  19300. adjustByMajorUnit: true,
  19301. /**
  19302. * @cfg {String|Object} title
  19303. * The title for the Axis.
  19304. * If given a String, the 'text' attribute of the title sprite will be set,
  19305. * otherwise the style will be set.
  19306. */
  19307. title: null,
  19308. /**
  19309. * @private
  19310. * @cfg {Number} [expandRangeBy=0]
  19311. */
  19312. expandRangeBy: 0,
  19313. /**
  19314. * @private
  19315. * @cfg {Number} length
  19316. * Length of the axis position. Equals to the size of inner rect on the docking side of this axis.
  19317. * WARNING: Meant to be set automatically by chart. Do not set it manually.
  19318. */
  19319. length: 0,
  19320. /**
  19321. * @private
  19322. * @cfg {Array} center
  19323. * Center of the polar axis.
  19324. * WARNING: Meant to be set automatically by chart. Do not set it manually.
  19325. */
  19326. center: null,
  19327. /**
  19328. * @private
  19329. * @cfg {Number} radius
  19330. * Radius of the polar axis.
  19331. * WARNING: Meant to be set automatically by chart. Do not set it manually.
  19332. */
  19333. radius: null,
  19334. /**
  19335. * @private
  19336. */
  19337. totalAngle: Math.PI,
  19338. /**
  19339. * @private
  19340. * @cfg {Number} rotation
  19341. * Rotation of the polar axis in radians.
  19342. * WARNING: Meant to be set automatically by chart. Do not set it manually.
  19343. */
  19344. rotation: null,
  19345. /**
  19346. * @cfg {Array} visibleRange
  19347. * Specify the proportion of the axis to be rendered. The series bound to
  19348. * this axis will be synchronized and transformed accordingly.
  19349. */
  19350. visibleRange: [
  19351. 0,
  19352. 1
  19353. ],
  19354. /**
  19355. * @cfg {Boolean} needHighPrecision
  19356. * Indicates that the axis needs high precision surface implementation.
  19357. * See {@link Ext.draw.engine.Canvas#highPrecision}
  19358. */
  19359. needHighPrecision: false,
  19360. /**
  19361. * @cfg {Ext.chart.axis.Axis|String|Number} linkedTo
  19362. * Axis (itself, its ID or index) that this axis is linked to.
  19363. * When an axis is linked to a master axis, it will use the same data as the master axis.
  19364. * It can be used to show additional info, or to ease reading the chart by duplicating the scales.
  19365. */
  19366. linkedTo: null,
  19367. /**
  19368. * @cfg {Number|Object}
  19369. * If `floating` is a number, then it's a percentage displacement of the axis from its initial {@link #position}
  19370. * in the direction opposite to the axis' direction. For instance, '{position:"left", floating:75}' displays a vertical
  19371. * axis at 3/4 of the chart, starting from the left. It is equivalent to '{position:"right", floating:25}'.
  19372. * If `floating` is an object, then `floating.value` is the position of this axis along another axis,
  19373. * defined by `floating.alongAxis`, where `alongAxis` is an ID, an {@link Ext.chart.AbstractChart#axes} config index,
  19374. * or the other axis itself. `alongAxis` must have an opposite {@link Ext.chart.axis.Axis#getAlignment alignment}.
  19375. * For example:
  19376. *
  19377. *
  19378. * axes: [
  19379. * {
  19380. * title: 'Average Temperature (F)',
  19381. * type: 'numeric',
  19382. * position: 'left',
  19383. * id: 'temperature-vertical-axis',
  19384. * minimum: -30,
  19385. * maximum: 130
  19386. * },
  19387. * {
  19388. * title: 'Month (2013)',
  19389. * type: 'category',
  19390. * position: 'bottom',
  19391. * floating: {
  19392. * value: 32,
  19393. * alongAxis: 'temperature-vertical-axis'
  19394. * }
  19395. * }
  19396. * ]
  19397. */
  19398. floating: null
  19399. },
  19400. titleOffset: 0,
  19401. spriteAnimationCount: 0,
  19402. boundSeries: [],
  19403. sprites: null,
  19404. surface: null,
  19405. /**
  19406. * @private
  19407. * @property {Array} range
  19408. * The full data range of the axis. Should not be set directly, Clear it to `null`
  19409. * and use `getRange` to update.
  19410. */
  19411. range: null,
  19412. defaultRange: [
  19413. 0,
  19414. 1
  19415. ],
  19416. rangePadding: 0.5,
  19417. xValues: [],
  19418. yValues: [],
  19419. masterAxis: null,
  19420. applyRotation: function(rotation) {
  19421. var twoPie = Math.PI * 2;
  19422. return (rotation % twoPie + Math.PI) % twoPie - Math.PI;
  19423. },
  19424. updateRotation: function(rotation) {
  19425. var sprites = this.getSprites(),
  19426. position = this.getPosition();
  19427. if (!this.getHidden() && position === 'angular' && sprites[0]) {
  19428. sprites[0].setAttributes({
  19429. baseRotation: rotation
  19430. });
  19431. }
  19432. },
  19433. applyTitle: function(title, oldTitle) {
  19434. var surface;
  19435. if (Ext.isString(title)) {
  19436. title = {
  19437. text: title
  19438. };
  19439. }
  19440. if (!oldTitle) {
  19441. oldTitle = Ext.create('sprite.text', title);
  19442. if ((surface = this.getSurface())) {
  19443. surface.add(oldTitle);
  19444. }
  19445. } else {
  19446. oldTitle.setAttributes(title);
  19447. }
  19448. return oldTitle;
  19449. },
  19450. getAdjustByMajorUnit: function() {
  19451. return !this.getHidden() && this.callParent();
  19452. },
  19453. applyFloating: function(floating, oldFloating) {
  19454. if (floating === null) {
  19455. floating = {
  19456. value: null,
  19457. alongAxis: null
  19458. };
  19459. } else if (Ext.isNumber(floating)) {
  19460. floating = {
  19461. value: floating,
  19462. alongAxis: null
  19463. };
  19464. }
  19465. if (Ext.isObject(floating)) {
  19466. if (oldFloating && oldFloating.alongAxis) {
  19467. delete this.getChart().getAxis(oldFloating.alongAxis).floatingAxes[this.getId()];
  19468. }
  19469. return floating;
  19470. }
  19471. return oldFloating;
  19472. },
  19473. constructor: function(config) {
  19474. var me = this,
  19475. id;
  19476. me.sprites = [];
  19477. me.labels = [];
  19478. // Maps IDs of the axes that float along this axis to their floating values.
  19479. me.floatingAxes = {};
  19480. config = config || {};
  19481. if (config.position === 'angular') {
  19482. config.style = config.style || {};
  19483. config.style.estStepSize = 1;
  19484. }
  19485. if ('id' in config) {
  19486. id = config.id;
  19487. } else if ('id' in me.config) {
  19488. id = me.config.id;
  19489. } else {
  19490. id = me.getId();
  19491. }
  19492. me.setId(id);
  19493. me.mixins.observable.constructor.apply(me, arguments);
  19494. },
  19495. /**
  19496. * @private
  19497. * @return {String}
  19498. */
  19499. getAlignment: function() {
  19500. switch (this.getPosition()) {
  19501. case 'left':
  19502. case 'right':
  19503. return 'vertical';
  19504. case 'top':
  19505. case 'bottom':
  19506. return 'horizontal';
  19507. case 'radial':
  19508. return 'radial';
  19509. case 'angular':
  19510. return 'angular';
  19511. }
  19512. },
  19513. /**
  19514. * @private
  19515. * @return {String}
  19516. */
  19517. getGridAlignment: function() {
  19518. switch (this.getPosition()) {
  19519. case 'left':
  19520. case 'right':
  19521. return 'horizontal';
  19522. case 'top':
  19523. case 'bottom':
  19524. return 'vertical';
  19525. case 'radial':
  19526. return 'circular';
  19527. case 'angular':
  19528. return 'radial';
  19529. }
  19530. },
  19531. /**
  19532. * @private
  19533. * Get the surface for drawing the series sprites
  19534. */
  19535. getSurface: function() {
  19536. var me = this,
  19537. chart = me.getChart();
  19538. if (chart && !me.surface) {
  19539. var surface = me.surface = chart.getSurface(me.getId(), 'axis'),
  19540. gridSurface = me.gridSurface = chart.getSurface('main');
  19541. gridSurface.waitFor(surface);
  19542. me.getGrid();
  19543. me.createLimits();
  19544. }
  19545. return me.surface;
  19546. },
  19547. createLimits: function() {
  19548. var me = this,
  19549. chart = me.getChart(),
  19550. axisSprite = me.getSprites()[0],
  19551. gridAlignment = me.getGridAlignment(),
  19552. limits;
  19553. if (me.getLimits() && gridAlignment) {
  19554. gridAlignment = gridAlignment.replace('3d', '');
  19555. me.limits = limits = {
  19556. surface: chart.getSurface('overlay'),
  19557. lines: new Ext.chart.Markers(),
  19558. titles: new Ext.draw.sprite.Instancing()
  19559. };
  19560. limits.lines.setTemplate({
  19561. xclass: 'grid.' + gridAlignment
  19562. });
  19563. limits.lines.getTemplate().setAttributes({
  19564. strokeStyle: 'black'
  19565. }, true);
  19566. limits.surface.add(limits.lines);
  19567. axisSprite.bindMarker(gridAlignment + '-limit-lines', me.limits.lines);
  19568. me.limitTitleTpl = new Ext.draw.sprite.Text();
  19569. limits.titles.setTemplate(me.limitTitleTpl);
  19570. limits.surface.add(limits.titles);
  19571. }
  19572. },
  19573. applyGrid: function(grid) {
  19574. // Returning an empty object here if grid was set to 'true' so that
  19575. // config merging in the theme works properly.
  19576. if (grid === true) {
  19577. return {};
  19578. }
  19579. return grid;
  19580. },
  19581. updateGrid: function(grid) {
  19582. var me = this,
  19583. chart = me.getChart();
  19584. if (!chart) {
  19585. me.on({
  19586. chartattached: Ext.bind(me.updateGrid, me, [
  19587. grid
  19588. ]),
  19589. single: true
  19590. });
  19591. return;
  19592. }
  19593. var gridSurface = me.gridSurface,
  19594. axisSprite = me.getSprites()[0],
  19595. gridAlignment = me.getGridAlignment(),
  19596. gridSprite;
  19597. if (grid) {
  19598. gridSprite = me.gridSpriteEven;
  19599. if (!gridSprite) {
  19600. gridSprite = me.gridSpriteEven = new Ext.chart.Markers();
  19601. gridSprite.setTemplate({
  19602. xclass: 'grid.' + gridAlignment
  19603. });
  19604. gridSurface.add(gridSprite);
  19605. axisSprite.bindMarker(gridAlignment + '-even', gridSprite);
  19606. }
  19607. if (Ext.isObject(grid)) {
  19608. gridSprite.getTemplate().setAttributes(grid);
  19609. if (Ext.isObject(grid.even)) {
  19610. gridSprite.getTemplate().setAttributes(grid.even);
  19611. }
  19612. }
  19613. gridSprite = me.gridSpriteOdd;
  19614. if (!gridSprite) {
  19615. gridSprite = me.gridSpriteOdd = new Ext.chart.Markers();
  19616. gridSprite.setTemplate({
  19617. xclass: 'grid.' + gridAlignment
  19618. });
  19619. gridSurface.add(gridSprite);
  19620. axisSprite.bindMarker(gridAlignment + '-odd', gridSprite);
  19621. }
  19622. if (Ext.isObject(grid)) {
  19623. gridSprite.getTemplate().setAttributes(grid);
  19624. if (Ext.isObject(grid.odd)) {
  19625. gridSprite.getTemplate().setAttributes(grid.odd);
  19626. }
  19627. }
  19628. }
  19629. },
  19630. updateMinorTickSteps: function(minorTickSteps) {
  19631. var me = this,
  19632. sprites = me.getSprites(),
  19633. axisSprite = sprites && sprites[0],
  19634. surface;
  19635. if (axisSprite) {
  19636. axisSprite.setAttributes({
  19637. minorTicks: !!minorTickSteps
  19638. });
  19639. surface = me.getSurface();
  19640. if (!me.isConfiguring && surface) {
  19641. surface.renderFrame();
  19642. }
  19643. }
  19644. },
  19645. /**
  19646. *
  19647. * Mapping data value into coordinate.
  19648. *
  19649. * @param {*} value
  19650. * @param {String} field
  19651. * @param {Number} [idx]
  19652. * @param {Ext.util.MixedCollection} [items]
  19653. * @return {Number}
  19654. */
  19655. getCoordFor: function(value, field, idx, items) {
  19656. return this.getLayout().getCoordFor(value, field, idx, items);
  19657. },
  19658. applyPosition: function(pos) {
  19659. return pos.toLowerCase();
  19660. },
  19661. applyLength: function(length, oldLength) {
  19662. return length > 0 ? length : oldLength;
  19663. },
  19664. applyLabel: function(label, oldLabel) {
  19665. if (!oldLabel) {
  19666. oldLabel = new Ext.draw.sprite.Text({});
  19667. }
  19668. if (label) {
  19669. if (this.limitTitleTpl) {
  19670. this.limitTitleTpl.setAttributes(label);
  19671. }
  19672. oldLabel.setAttributes(label);
  19673. }
  19674. return oldLabel;
  19675. },
  19676. applyLayout: function(layout, oldLayout) {
  19677. layout = Ext.factory(layout, null, oldLayout, 'axisLayout');
  19678. layout.setAxis(this);
  19679. return layout;
  19680. },
  19681. applySegmenter: function(segmenter, oldSegmenter) {
  19682. segmenter = Ext.factory(segmenter, null, oldSegmenter, 'segmenter');
  19683. segmenter.setAxis(this);
  19684. return segmenter;
  19685. },
  19686. updateMinimum: function() {
  19687. this.range = null;
  19688. },
  19689. updateMaximum: function() {
  19690. this.range = null;
  19691. },
  19692. hideLabels: function() {
  19693. this.getSprites()[0].setDirty(true);
  19694. this.setLabel({
  19695. hidden: true
  19696. });
  19697. },
  19698. showLabels: function() {
  19699. this.getSprites()[0].setDirty(true);
  19700. this.setLabel({
  19701. hidden: false
  19702. });
  19703. },
  19704. /**
  19705. * Invokes renderFrame on this axis's surface(s)
  19706. */
  19707. renderFrame: function() {
  19708. this.getSurface().renderFrame();
  19709. },
  19710. updateChart: function(newChart, oldChart) {
  19711. var me = this,
  19712. surface;
  19713. if (oldChart) {
  19714. oldChart.unregister(me);
  19715. oldChart.un('serieschange', me.onSeriesChange, me);
  19716. me.linkAxis();
  19717. me.fireEvent('chartdetached', oldChart, me);
  19718. }
  19719. if (newChart) {
  19720. newChart.on('serieschange', me.onSeriesChange, me);
  19721. me.surface = null;
  19722. surface = me.getSurface();
  19723. me.getLabel().setSurface(surface);
  19724. surface.add(me.getSprites());
  19725. surface.add(me.getTitle());
  19726. newChart.register(me);
  19727. me.fireEvent('chartattached', newChart, me);
  19728. }
  19729. },
  19730. applyBackground: function(background) {
  19731. var rect = Ext.ClassManager.getByAlias('sprite.rect');
  19732. return rect.def.normalize(background);
  19733. },
  19734. /**
  19735. * @protected
  19736. * Invoked when data has changed.
  19737. */
  19738. processData: function() {
  19739. this.getLayout().processData();
  19740. this.range = null;
  19741. },
  19742. getDirection: function() {
  19743. return this.getChart().getDirectionForAxis(this.getPosition());
  19744. },
  19745. isSide: function() {
  19746. var position = this.getPosition();
  19747. return position === 'left' || position === 'right';
  19748. },
  19749. applyFields: function(fields) {
  19750. return Ext.Array.from(fields);
  19751. },
  19752. applyVisibleRange: function(visibleRange, oldVisibleRange) {
  19753. this.getChart();
  19754. // If it is in reversed order swap them
  19755. if (visibleRange[0] > visibleRange[1]) {
  19756. var temp = visibleRange[0];
  19757. visibleRange[0] = visibleRange[1];
  19758. visibleRange[0] = temp;
  19759. }
  19760. if (visibleRange[1] === visibleRange[0]) {
  19761. visibleRange[1] += 1 / this.getMaxZoom();
  19762. }
  19763. if (visibleRange[1] > visibleRange[0] + 1) {
  19764. visibleRange[0] = 0;
  19765. visibleRange[1] = 1;
  19766. } else if (visibleRange[0] < 0) {
  19767. visibleRange[1] -= visibleRange[0];
  19768. visibleRange[0] = 0;
  19769. } else if (visibleRange[1] > 1) {
  19770. visibleRange[0] -= visibleRange[1] - 1;
  19771. visibleRange[1] = 1;
  19772. }
  19773. if (oldVisibleRange && visibleRange[0] === oldVisibleRange[0] && visibleRange[1] === oldVisibleRange[1]) {
  19774. return undefined;
  19775. }
  19776. return visibleRange;
  19777. },
  19778. updateVisibleRange: function(visibleRange) {
  19779. this.fireEvent('visiblerangechange', this, visibleRange);
  19780. },
  19781. onSeriesChange: function(chart) {
  19782. var me = this,
  19783. series = chart.getSeries(),
  19784. boundSeries = [],
  19785. linkedTo, masterAxis, getAxisMethod, i, ln;
  19786. if (series) {
  19787. getAxisMethod = 'get' + me.getDirection() + 'Axis';
  19788. for (i = 0 , ln = series.length; i < ln; i++) {
  19789. if (this === series[i][getAxisMethod]()) {
  19790. boundSeries.push(series[i]);
  19791. }
  19792. }
  19793. }
  19794. me.boundSeries = boundSeries;
  19795. linkedTo = me.getLinkedTo();
  19796. masterAxis = !Ext.isEmpty(linkedTo) && chart.getAxis(linkedTo);
  19797. if (masterAxis) {
  19798. me.linkAxis(masterAxis);
  19799. } else {
  19800. me.getLayout().processData();
  19801. }
  19802. },
  19803. linkAxis: function(masterAxis) {
  19804. var me = this;
  19805. function link(action, slave, master) {
  19806. master.getLayout()[action]('datachange', 'onDataChange', slave);
  19807. master[action]('rangechange', 'onMasterAxisRangeChange', slave);
  19808. }
  19809. if (me.masterAxis) {
  19810. if (!me.masterAxis.destroyed) {
  19811. link('un', me, me.masterAxis);
  19812. }
  19813. me.masterAxis = null;
  19814. }
  19815. if (masterAxis) {
  19816. if (masterAxis.type !== this.type) {
  19817. Ext.Error.raise("Linked axes must be of the same type.");
  19818. }
  19819. link('on', me, masterAxis);
  19820. me.onDataChange(masterAxis.getLayout().labels);
  19821. me.onMasterAxisRangeChange(masterAxis, masterAxis.range);
  19822. me.setStyle(Ext.apply({}, me.config.style, masterAxis.config.style));
  19823. me.setTitle(Ext.apply({}, me.config.title, masterAxis.config.title));
  19824. me.setLabel(Ext.apply({}, me.config.label, masterAxis.config.label));
  19825. me.masterAxis = masterAxis;
  19826. }
  19827. },
  19828. onDataChange: function(data) {
  19829. this.getLayout().labels = data;
  19830. },
  19831. onMasterAxisRangeChange: function(masterAxis, range) {
  19832. this.range = range;
  19833. },
  19834. applyRange: function(newRange) {
  19835. if (!newRange) {
  19836. return this.dataRange.slice(0);
  19837. } else {
  19838. return [
  19839. newRange[0] === null ? this.dataRange[0] : newRange[0],
  19840. newRange[1] === null ? this.dataRange[1] : newRange[1]
  19841. ];
  19842. }
  19843. },
  19844. /**
  19845. * @private
  19846. */
  19847. setBoundSeriesRange: function(range) {
  19848. var boundSeries = this.boundSeries,
  19849. style = {},
  19850. series, i, sprites, j, ln;
  19851. style['range' + this.getDirection()] = range;
  19852. for (i = 0 , ln = boundSeries.length; i < ln; i++) {
  19853. series = boundSeries[i];
  19854. if (series.getHidden() === true) {
  19855. continue;
  19856. }
  19857. sprites = series.getSprites();
  19858. for (j = 0; j < sprites.length; j++) {
  19859. sprites[j].setAttributes(style);
  19860. }
  19861. }
  19862. },
  19863. /**
  19864. * Get the range derived from all the bound series.
  19865. * The range value is cached and returned the next time this method is called.
  19866. * Set `recalculate` to `true` to recalculate the range, if changes to the
  19867. * chart, its components or data are expected to affect the range.
  19868. * @param {Boolean} [recalculate]
  19869. * @return {Number[]}
  19870. */
  19871. getRange: function(recalculate) {
  19872. var me = this,
  19873. range = recalculate ? null : me.range,
  19874. oldRange = me.oldRange,
  19875. minimum, maximum;
  19876. if (!range) {
  19877. if (me.masterAxis) {
  19878. range = me.masterAxis.range;
  19879. } else {
  19880. minimum = me.getMinimum();
  19881. maximum = me.getMaximum();
  19882. if (Ext.isNumber(minimum) && Ext.isNumber(maximum)) {
  19883. range = [
  19884. minimum,
  19885. maximum
  19886. ];
  19887. } else {
  19888. range = me.calculateRange();
  19889. }
  19890. me.range = range;
  19891. }
  19892. }
  19893. if (range && (!oldRange || range[0] !== oldRange[0] || range[1] !== oldRange[1])) {
  19894. me.fireEvent('rangechange', me, range, oldRange);
  19895. me.oldRange = range;
  19896. }
  19897. return range;
  19898. },
  19899. isSingleDataPoint: function(range) {
  19900. return (range[0] + this.rangePadding) === 0 && (range[1] - this.rangePadding) === 0;
  19901. },
  19902. calculateRange: function() {
  19903. var me = this,
  19904. boundSeries = me.boundSeries,
  19905. layout = me.getLayout(),
  19906. segmenter = me.getSegmenter(),
  19907. minimum = me.getMinimum(),
  19908. maximum = me.getMaximum(),
  19909. visibleRange = me.getVisibleRange(),
  19910. getRangeMethod = 'get' + me.getDirection() + 'Range',
  19911. expandRangeBy = me.getExpandRangeBy(),
  19912. context, attr, majorTicks, series, i, ln, seriesRange,
  19913. range = [
  19914. NaN,
  19915. NaN
  19916. ];
  19917. // For each series bound to this axis, ask the series for its min/max values
  19918. // and use them to find the overall min/max.
  19919. for (i = 0 , ln = boundSeries.length; i < ln; i++) {
  19920. series = boundSeries[i];
  19921. if (series.getHidden() === true) {
  19922. continue;
  19923. }
  19924. seriesRange = series[getRangeMethod]();
  19925. if (seriesRange) {
  19926. Ext.chart.Util.expandRange(range, seriesRange);
  19927. }
  19928. }
  19929. range = Ext.chart.Util.validateRange(range, me.defaultRange, me.rangePadding);
  19930. // The second condition is there to account for a special case where we only have
  19931. // a single data point, so the effective range of coordinated data is 0 (whatever
  19932. // the actual value of that single data point is, it will be assigned an index of
  19933. // zero, as the first and only data point). Since zero range is invalid, the
  19934. // validateRange function above will expand the range by the value of the rangePadding,
  19935. // which makes further expansion by the value of expandRangeBy unnecessary.
  19936. if (expandRangeBy && (!me.isSingleDataPoint(range))) {
  19937. range[0] -= expandRangeBy;
  19938. range[1] += expandRangeBy;
  19939. }
  19940. if (isFinite(minimum)) {
  19941. range[0] = minimum;
  19942. }
  19943. if (isFinite(maximum)) {
  19944. range[1] = maximum;
  19945. }
  19946. // When series `fullStack` config is used, the values may add up to
  19947. // slightly more than the value of the `fullStackTotal` config
  19948. // because of a precision error.
  19949. range[0] = Ext.Number.correctFloat(range[0]);
  19950. range[1] = Ext.Number.correctFloat(range[1]);
  19951. me.range = range;
  19952. // It's important to call 'me.reconcileRange' after the 'range'
  19953. // has been assigned to avoid circular calls.
  19954. if (me.getReconcileRange()) {
  19955. me.reconcileRange();
  19956. }
  19957. // TODO: Find a better way to do this.
  19958. // TODO: The original design didn't take into account that the range of an axis
  19959. // TODO: will depend not just on the range of the data of the bound series in the
  19960. // TODO: direction of the axis, but also on the range of other axes with the
  19961. // TODO: same direction and on the segmentation of the axis (interval between
  19962. // TODO: major ticks).
  19963. // TODO: While the fist omission was possible to retrofit rather gracefully
  19964. // TODO: by adding the axis.reconcileRange method, the second one is harder to deal with.
  19965. // TODO: The issue is that the resulting axis segmentation, which is a part of
  19966. // TODO: the axis sprite layout has to be known before layout has begun.
  19967. // TODO: Example for the logic below:
  19968. // TODO: If we have a range of data of 0..34.5 the step will be 2 and we
  19969. // TODO: will round up the max to 36 based on that step, but when the range is 0..36,
  19970. // TODO: the step becomes 5, so we have to reconcile the range once again where max
  19971. // TODO: becomes 40.
  19972. if (range[0] !== range[1] && me.getAdjustByMajorUnit() && segmenter.adjustByMajorUnit && !me.getMajorTickSteps()) {
  19973. attr = Ext.Object.chain(me.getSprites()[0].attr);
  19974. attr.min = range[0];
  19975. attr.max = range[1];
  19976. attr.visibleMin = visibleRange[0];
  19977. attr.visibleMax = visibleRange[1];
  19978. context = {
  19979. attr: attr,
  19980. segmenter: segmenter
  19981. };
  19982. layout.calculateLayout(context);
  19983. majorTicks = context.majorTicks;
  19984. if (majorTicks) {
  19985. segmenter.adjustByMajorUnit(majorTicks.step, majorTicks.unit.scale, range);
  19986. attr.min = range[0];
  19987. attr.max = range[1];
  19988. context.majorTicks = null;
  19989. layout.calculateLayout(context);
  19990. majorTicks = context.majorTicks;
  19991. segmenter.adjustByMajorUnit(majorTicks.step, majorTicks.unit.scale, range);
  19992. } else if (!me.hasClearRangePending) {
  19993. // Axis hasn't been rendered yet.
  19994. me.hasClearRangePending = true;
  19995. me.getChart().on('layout', 'clearRange', me);
  19996. }
  19997. }
  19998. return range;
  19999. },
  20000. /**
  20001. * @private
  20002. */
  20003. clearRange: function() {
  20004. this.hasClearRangePending = null;
  20005. this.range = null;
  20006. },
  20007. /**
  20008. * Expands the range of the axis
  20009. * based on the range of other axes with the same direction (if any).
  20010. */
  20011. reconcileRange: function() {
  20012. var me = this,
  20013. axes = me.getChart().getAxes(),
  20014. direction = me.getDirection(),
  20015. i, ln, axis, range;
  20016. if (!axes) {
  20017. return;
  20018. }
  20019. for (i = 0 , ln = axes.length; i < ln; i++) {
  20020. axis = axes[i];
  20021. range = axis.getRange();
  20022. if (axis === me || axis.getDirection() !== direction || !range || !axis.getReconcileRange()) {
  20023. continue;
  20024. }
  20025. if (range[0] < me.range[0]) {
  20026. me.range[0] = range[0];
  20027. }
  20028. if (range[1] > me.range[1]) {
  20029. me.range[1] = range[1];
  20030. }
  20031. }
  20032. },
  20033. applyStyle: function(style, oldStyle) {
  20034. var cls = Ext.ClassManager.getByAlias('sprite.' + this.seriesType);
  20035. if (cls && cls.def) {
  20036. style = cls.def.normalize(style);
  20037. }
  20038. oldStyle = Ext.apply(oldStyle || {}, style);
  20039. return oldStyle;
  20040. },
  20041. themeOnlyIfConfigured: {
  20042. grid: true
  20043. },
  20044. updateTheme: function(theme) {
  20045. var me = this,
  20046. axisTheme = theme.getAxis(),
  20047. position = me.getPosition(),
  20048. initialConfig = me.getInitialConfig(),
  20049. defaultConfig = me.defaultConfig,
  20050. configs = me.self.getConfigurator().configs,
  20051. genericAxisTheme = axisTheme.defaults,
  20052. specificAxisTheme = axisTheme[position],
  20053. themeOnlyIfConfigured = me.themeOnlyIfConfigured,
  20054. key, value, isObjValue, isUnusedConfig, initialValue, cfg;
  20055. axisTheme = Ext.merge({}, genericAxisTheme, specificAxisTheme);
  20056. for (key in axisTheme) {
  20057. value = axisTheme[key];
  20058. cfg = configs[key];
  20059. if (value !== null && value !== undefined && cfg) {
  20060. initialValue = initialConfig[key];
  20061. isObjValue = Ext.isObject(value);
  20062. isUnusedConfig = initialValue === defaultConfig[key];
  20063. if (isObjValue) {
  20064. if (isUnusedConfig && themeOnlyIfConfigured[key]) {
  20065. continue;
  20066. }
  20067. value = Ext.merge({}, value, initialValue);
  20068. }
  20069. if (isUnusedConfig || isObjValue) {
  20070. me[cfg.names.set](value);
  20071. }
  20072. }
  20073. }
  20074. },
  20075. updateCenter: function(center) {
  20076. var me = this,
  20077. sprites = me.getSprites(),
  20078. axisSprite = sprites[0],
  20079. centerX = center[0],
  20080. centerY = center[1];
  20081. if (axisSprite) {
  20082. axisSprite.setAttributes({
  20083. centerX: centerX,
  20084. centerY: centerY
  20085. });
  20086. }
  20087. if (me.gridSpriteEven) {
  20088. me.gridSpriteEven.getTemplate().setAttributes({
  20089. translationX: centerX,
  20090. translationY: centerY,
  20091. rotationCenterX: centerX,
  20092. rotationCenterY: centerY
  20093. });
  20094. }
  20095. if (me.gridSpriteOdd) {
  20096. me.gridSpriteOdd.getTemplate().setAttributes({
  20097. translationX: centerX,
  20098. translationY: centerY,
  20099. rotationCenterX: centerX,
  20100. rotationCenterY: centerY
  20101. });
  20102. }
  20103. },
  20104. getSprites: function() {
  20105. if (!this.getChart()) {
  20106. return;
  20107. }
  20108. var me = this,
  20109. range = me.getRange(),
  20110. position = me.getPosition(),
  20111. chart = me.getChart(),
  20112. animation = chart.getAnimation(),
  20113. length = me.getLength(),
  20114. axisClass = me.superclass,
  20115. mainSprite, style, animationModifier;
  20116. // If animation is false, then stop animation.
  20117. if (animation === false) {
  20118. animation = {
  20119. duration: 0
  20120. };
  20121. }
  20122. style = Ext.applyIf({
  20123. position: position,
  20124. axis: me,
  20125. length: length,
  20126. grid: me.getGrid(),
  20127. hidden: me.getHidden(),
  20128. titleOffset: me.titleOffset,
  20129. layout: me.getLayout(),
  20130. segmenter: me.getSegmenter(),
  20131. totalAngle: me.getTotalAngle(),
  20132. label: me.getLabel()
  20133. }, me.getStyle());
  20134. if (range) {
  20135. style.min = range[0];
  20136. style.max = range[1];
  20137. }
  20138. // If the sprites are not created.
  20139. if (!me.sprites.length) {
  20140. while (!axisClass.xtype) {
  20141. axisClass = axisClass.superclass;
  20142. }
  20143. mainSprite = Ext.create('sprite.' + axisClass.xtype, style);
  20144. animationModifier = mainSprite.getAnimation();
  20145. animationModifier.setCustomDurations({
  20146. baseRotation: 0
  20147. });
  20148. animationModifier.on('animationstart', 'onAnimationStart', me);
  20149. animationModifier.on('animationend', 'onAnimationEnd', me);
  20150. mainSprite.setLayout(me.getLayout());
  20151. mainSprite.setSegmenter(me.getSegmenter());
  20152. mainSprite.setLabel(me.getLabel());
  20153. me.sprites.push(mainSprite);
  20154. me.updateTitleSprite();
  20155. } else {
  20156. mainSprite = me.sprites[0];
  20157. mainSprite.setAnimation(animation);
  20158. mainSprite.setAttributes(style);
  20159. }
  20160. if (me.getRenderer()) {
  20161. mainSprite.setRenderer(me.getRenderer());
  20162. }
  20163. return me.sprites;
  20164. },
  20165. /**
  20166. * @private
  20167. */
  20168. performLayout: function() {
  20169. if (this.isConfiguring) {
  20170. return;
  20171. }
  20172. var me = this,
  20173. sprites = me.getSprites(),
  20174. surface = me.getSurface(),
  20175. chart = me.getChart(),
  20176. sprite = sprites && sprites[0];
  20177. if (chart && surface && sprite) {
  20178. sprite.callUpdater(null, 'layout');
  20179. // recalculate axis ticks
  20180. chart.scheduleLayout();
  20181. }
  20182. },
  20183. updateTitleSprite: function() {
  20184. var me = this,
  20185. length = me.getLength();
  20186. if (!me.sprites[0] || !Ext.isNumber(length)) {
  20187. return;
  20188. }
  20189. var thickness = this.sprites[0].thickness,
  20190. surface = me.getSurface(),
  20191. title = me.getTitle(),
  20192. position = me.getPosition(),
  20193. margin = me.getMargin(),
  20194. titleMargin = me.getTitleMargin(),
  20195. anchor = surface.roundPixel(length / 2);
  20196. if (title) {
  20197. switch (position) {
  20198. case 'top':
  20199. title.setAttributes({
  20200. x: anchor,
  20201. y: margin + titleMargin / 2,
  20202. textBaseline: 'top',
  20203. textAlign: 'center'
  20204. }, true);
  20205. title.applyTransformations();
  20206. me.titleOffset = title.getBBox().height + titleMargin;
  20207. break;
  20208. case 'bottom':
  20209. title.setAttributes({
  20210. x: anchor,
  20211. y: thickness + titleMargin / 2,
  20212. textBaseline: 'top',
  20213. textAlign: 'center'
  20214. }, true);
  20215. title.applyTransformations();
  20216. me.titleOffset = title.getBBox().height + titleMargin;
  20217. break;
  20218. case 'left':
  20219. title.setAttributes({
  20220. x: margin + titleMargin / 2,
  20221. y: anchor,
  20222. textBaseline: 'top',
  20223. textAlign: 'center',
  20224. rotationCenterX: margin + titleMargin / 2,
  20225. rotationCenterY: anchor,
  20226. rotationRads: -Math.PI / 2
  20227. }, true);
  20228. title.applyTransformations();
  20229. me.titleOffset = title.getBBox().width + titleMargin;
  20230. break;
  20231. case 'right':
  20232. title.setAttributes({
  20233. x: thickness - margin + titleMargin / 2,
  20234. y: anchor,
  20235. textBaseline: 'bottom',
  20236. textAlign: 'center',
  20237. rotationCenterX: thickness + titleMargin / 2,
  20238. rotationCenterY: anchor,
  20239. rotationRads: Math.PI / 2
  20240. }, true);
  20241. title.applyTransformations();
  20242. me.titleOffset = title.getBBox().width + titleMargin;
  20243. break;
  20244. }
  20245. }
  20246. },
  20247. onThicknessChanged: function() {
  20248. this.getChart().onThicknessChanged();
  20249. },
  20250. getThickness: function() {
  20251. if (this.getHidden()) {
  20252. return 0;
  20253. }
  20254. return (this.sprites[0] && this.sprites[0].thickness || 1) + this.titleOffset + this.getMargin();
  20255. },
  20256. onAnimationStart: function() {
  20257. this.spriteAnimationCount++;
  20258. if (this.spriteAnimationCount === 1) {
  20259. this.fireEvent('animationstart', this);
  20260. }
  20261. },
  20262. onAnimationEnd: function() {
  20263. this.spriteAnimationCount--;
  20264. if (this.spriteAnimationCount === 0) {
  20265. this.fireEvent('animationend', this);
  20266. }
  20267. },
  20268. // Methods used in ComponentQuery and controller
  20269. getItemId: function() {
  20270. return this.getId();
  20271. },
  20272. getAncestorIds: function() {
  20273. return [
  20274. this.getChart().getId()
  20275. ];
  20276. },
  20277. isXType: function(xtype) {
  20278. return xtype === 'axis';
  20279. },
  20280. // Override the Observable's method to redirect listener scope
  20281. // resolution to the chart.
  20282. resolveListenerScope: function(defaultScope) {
  20283. var me = this,
  20284. namedScope = Ext._namedScopes[defaultScope],
  20285. chart = me.getChart(),
  20286. scope;
  20287. if (!namedScope) {
  20288. scope = chart ? chart.resolveListenerScope(defaultScope, false) : (defaultScope || me);
  20289. } else if (namedScope.isThis) {
  20290. scope = me;
  20291. } else if (namedScope.isController) {
  20292. scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
  20293. } else if (namedScope.isSelf) {
  20294. scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
  20295. // Class body listener. No chart controller, nor chart container controller.
  20296. if (scope === chart && !chart.getInheritedConfig('defaultListenerScope')) {
  20297. scope = me;
  20298. }
  20299. }
  20300. return scope;
  20301. },
  20302. destroy: function() {
  20303. var me = this;
  20304. me.setChart(null);
  20305. me.surface.destroy();
  20306. me.surface = null;
  20307. me.callParent();
  20308. }
  20309. });
  20310. /**
  20311. * The legend base class adapater for classic toolkit.
  20312. */
  20313. Ext.define('Ext.chart.legend.LegendBase', {
  20314. extend: 'Ext.view.View',
  20315. config: {
  20316. tpl: [
  20317. '<div class="',
  20318. Ext.baseCSSPrefix,
  20319. 'legend-inner">',
  20320. // for IE8 vertical centering
  20321. '<div class="',
  20322. Ext.baseCSSPrefix,
  20323. 'legend-container">',
  20324. '<tpl for=".">',
  20325. '<div class="',
  20326. Ext.baseCSSPrefix,
  20327. 'legend-item">',
  20328. '<span ',
  20329. 'class="',
  20330. Ext.baseCSSPrefix,
  20331. 'legend-item-marker {[ values.disabled ? Ext.baseCSSPrefix + \'legend-item-inactive\' : \'\' ]}" ',
  20332. 'style="background:{mark};">',
  20333. '</span>{name}',
  20334. '</div>',
  20335. '</tpl>',
  20336. '</div>',
  20337. '</div>'
  20338. ],
  20339. nodeContainerSelector: 'div.' + Ext.baseCSSPrefix + 'legend-inner',
  20340. // element that contains rows (see AbstractView)
  20341. itemSelector: 'div.' + Ext.baseCSSPrefix + 'legend-item',
  20342. // row element (see AbstractView)
  20343. /**
  20344. * @cfg {String} docked
  20345. * The dock position of this component in its container. Can be `left`, `top`, `right` or `bottom`.
  20346. */
  20347. docked: 'bottom'
  20348. },
  20349. /**
  20350. * @cfg dock
  20351. * @hide
  20352. */
  20353. setDocked: function(docked) {
  20354. // If we call the method 'updateDocked' instead of 'setDocked', the following error is thrown:
  20355. // "Ext.Component#setDocked" is deprecated. Please use "setDock" instead.
  20356. var me = this,
  20357. panel = me.ownerCt;
  20358. me.docked = me.dock = docked;
  20359. switch (docked) {
  20360. case 'top':
  20361. case 'bottom':
  20362. me.addCls(me.horizontalCls);
  20363. me.removeCls(me.verticalCls);
  20364. break;
  20365. case 'left':
  20366. case 'right':
  20367. me.addCls(me.verticalCls);
  20368. me.removeCls(me.horizontalCls);
  20369. break;
  20370. }
  20371. if (panel) {
  20372. panel.setDock(docked);
  20373. }
  20374. },
  20375. setStore: function(store) {
  20376. this.bindStore(store);
  20377. },
  20378. clearViewEl: function() {
  20379. this.callParent(arguments);
  20380. // The legend-container div is not removed automatically.
  20381. Ext.removeNode(this.getNodeContainer());
  20382. },
  20383. onItemClick: function(record, item, index, e) {
  20384. this.callParent(arguments);
  20385. this.toggleItem(index);
  20386. }
  20387. });
  20388. /**
  20389. * This class provides a dataview-based chart legend.
  20390. */
  20391. Ext.define('Ext.chart.legend.Legend', {
  20392. extend: 'Ext.chart.legend.LegendBase',
  20393. alternateClassName: 'Ext.chart.Legend',
  20394. xtype: 'legend',
  20395. alias: 'legend.dom',
  20396. type: 'dom',
  20397. isLegend: true,
  20398. isDomLegend: true,
  20399. config: {
  20400. /**
  20401. * @cfg {Array}
  20402. * The rect of the legend relative to its container.
  20403. */
  20404. rect: null,
  20405. /**
  20406. * @cfg {Boolean} toggleable
  20407. * `true` to allow series items to have their visibility
  20408. * toggled by interaction with the legend items.
  20409. */
  20410. toggleable: true
  20411. },
  20412. /**
  20413. * @cfg {Ext.chart.legend.store.Store} store
  20414. * The {@link Ext.chart.legend.store.Store} to bind this legend to.
  20415. * @private
  20416. */
  20417. baseCls: Ext.baseCSSPrefix + 'legend',
  20418. horizontalCls: Ext.baseCSSPrefix + 'legend-horizontal',
  20419. verticalCls: Ext.baseCSSPrefix + 'legend-vertical',
  20420. toggleItem: function(index) {
  20421. if (!this.getToggleable()) {
  20422. return;
  20423. }
  20424. var store = this.getStore(),
  20425. disabledCount = 0,
  20426. disabled,
  20427. canToggle = true,
  20428. i, count, record;
  20429. if (store) {
  20430. count = store.getCount();
  20431. for (i = 0; i < count; i++) {
  20432. record = store.getAt(i);
  20433. if (record.get('disabled')) {
  20434. disabledCount++;
  20435. }
  20436. }
  20437. canToggle = count - disabledCount > 1;
  20438. record = store.getAt(index);
  20439. if (record) {
  20440. disabled = record.get('disabled');
  20441. if (disabled || canToggle) {
  20442. // This will trigger AbstractChart.onLegendStoreUpdate.
  20443. record.set('disabled', !disabled);
  20444. }
  20445. }
  20446. }
  20447. },
  20448. onResize: function(width, height, oldWidth, oldHeight) {
  20449. var me = this,
  20450. chart = me.chart;
  20451. if (!me.isConfiguring) {
  20452. if (chart) {
  20453. chart.scheduleLayout();
  20454. }
  20455. }
  20456. }
  20457. });
  20458. /**
  20459. * @private
  20460. */
  20461. Ext.define('Ext.chart.legend.sprite.Item', {
  20462. extend: 'Ext.draw.sprite.Composite',
  20463. alias: 'sprite.legenditem',
  20464. type: 'legenditem',
  20465. isLegendItem: true,
  20466. requires: [
  20467. 'Ext.draw.sprite.Text',
  20468. 'Ext.draw.sprite.Circle'
  20469. ],
  20470. inheritableStatics: {
  20471. def: {
  20472. processors: {
  20473. enabled: 'limited01',
  20474. markerLabelGap: 'number'
  20475. },
  20476. animationProcessors: {
  20477. enabled: null,
  20478. markerLabelGap: null
  20479. },
  20480. defaults: {
  20481. enabled: true,
  20482. markerLabelGap: 5
  20483. },
  20484. triggers: {
  20485. enabled: 'enabled',
  20486. markerLabelGap: 'layout'
  20487. },
  20488. updaters: {
  20489. layout: 'layoutUpdater',
  20490. enabled: 'enabledUpdater'
  20491. }
  20492. }
  20493. },
  20494. config: {
  20495. // Sprite's attributes are processed after initConfig.
  20496. // So we need to init below configs lazily, as otherwise
  20497. // adding sprites (created from those configs) to composite
  20498. // will result in an attempt to access attributes that
  20499. // composite doesn't have yet.
  20500. label: {
  20501. $value: {
  20502. type: 'text'
  20503. },
  20504. lazy: true
  20505. },
  20506. marker: {
  20507. $value: {
  20508. type: 'circle'
  20509. },
  20510. lazy: true
  20511. },
  20512. legend: null,
  20513. store: null,
  20514. record: null,
  20515. series: null
  20516. },
  20517. applyLabel: function(label, oldLabel) {
  20518. var sprite;
  20519. if (label) {
  20520. if (label.isSprite && label.type === 'text') {
  20521. sprite = label;
  20522. } else {
  20523. if (oldLabel && label.type === oldLabel.type) {
  20524. oldLabel.setConfig(label);
  20525. sprite = oldLabel;
  20526. this.scheduleUpdater(this.attr, 'layout');
  20527. } else {
  20528. sprite = new Ext.draw.sprite.Text(label);
  20529. }
  20530. }
  20531. }
  20532. return sprite;
  20533. },
  20534. defaultMarkerSize: 10,
  20535. updateLabel: function(label, oldLabel) {
  20536. var me = this;
  20537. me.removeSprite(oldLabel);
  20538. label.setAttributes({
  20539. textBaseline: 'middle'
  20540. });
  20541. me.addSprite(label);
  20542. me.scheduleUpdater(me.attr, 'layout');
  20543. },
  20544. applyMarker: function(config) {
  20545. var marker;
  20546. if (config) {
  20547. if (config.isSprite) {
  20548. marker = config;
  20549. } else {
  20550. marker = this.createMarker(config);
  20551. }
  20552. }
  20553. marker = this.resetMarker(marker, config);
  20554. return marker;
  20555. },
  20556. createMarker: function(config) {
  20557. var marker;
  20558. // If marker attributes are animated, the attributes change over
  20559. // time from default values to the values specified in the marker
  20560. // config. But the 'legenditem' sprite needs final values
  20561. // to properly layout its children.
  20562. delete config.animation;
  20563. if (config.type === 'image') {
  20564. delete config.width;
  20565. delete config.height;
  20566. }
  20567. marker = Ext.create('sprite.' + config.type, config);
  20568. return marker;
  20569. },
  20570. resetMarker: function(sprite, config) {
  20571. var size = config.size || this.defaultMarkerSize,
  20572. bbox, max, scale;
  20573. // Layout may not work properly,
  20574. // if the marker sprite is transformed to begin with.
  20575. sprite.setTransform([
  20576. 1,
  20577. 0,
  20578. 0,
  20579. 1,
  20580. 0,
  20581. 0
  20582. ], true);
  20583. if (config.type === 'image') {
  20584. sprite.setAttributes({
  20585. width: size,
  20586. height: size
  20587. });
  20588. } else {
  20589. // This should work with any sprite, irrespective of what attribute
  20590. // is used to control sprite's size ('size', 'r', or something else).
  20591. // However, the 'image' sprite above is a special case.
  20592. bbox = sprite.getBBox();
  20593. max = Math.max(bbox.width, bbox.height);
  20594. scale = size / max;
  20595. sprite.setAttributes({
  20596. scalingX: scale,
  20597. scalingY: scale
  20598. });
  20599. }
  20600. return sprite;
  20601. },
  20602. updateMarker: function(marker, oldMarker) {
  20603. var me = this;
  20604. me.removeSprite(oldMarker);
  20605. me.addSprite(marker);
  20606. me.scheduleUpdater(me.attr, 'layout');
  20607. },
  20608. updateSurface: function(surface, oldSurface) {
  20609. var me = this;
  20610. me.callParent([
  20611. surface,
  20612. oldSurface
  20613. ]);
  20614. if (surface) {
  20615. me.scheduleUpdater(me.attr, 'layout');
  20616. }
  20617. },
  20618. enabledUpdater: function(attr) {
  20619. var marker = this.getMarker();
  20620. if (marker) {
  20621. marker.setAttributes({
  20622. globalAlpha: attr.enabled ? 1 : 0.3
  20623. });
  20624. }
  20625. },
  20626. layoutUpdater: function() {
  20627. var me = this,
  20628. attr = me.attr,
  20629. label = me.getLabel(),
  20630. marker = me.getMarker(),
  20631. labelBBox, markerBBox, totalHeight;
  20632. // Measuring bounding boxes of transformed marker and label
  20633. // sprites and translating the sprites by required amount,
  20634. // makes layout virtually bullet-proof to unaccounted for
  20635. // changes in sprite attributes, whatever the sprite type may be.
  20636. markerBBox = marker.getBBox();
  20637. labelBBox = label.getBBox();
  20638. totalHeight = Math.max(markerBBox.height, labelBBox.height);
  20639. // Because we are getting an already transformed bounding box,
  20640. // we want to add to that transformation, not replace it,
  20641. // so setting translationX/Y attributes here would be inappropriate.
  20642. marker.transform([
  20643. 1,
  20644. 0,
  20645. 0,
  20646. 1,
  20647. -markerBBox.x,
  20648. -markerBBox.y + (totalHeight - markerBBox.height) / 2
  20649. ], true);
  20650. label.transform([
  20651. 1,
  20652. 0,
  20653. 0,
  20654. 1,
  20655. -labelBBox.x + markerBBox.width + attr.markerLabelGap,
  20656. -labelBBox.y + (totalHeight - labelBBox.height) / 2
  20657. ], true);
  20658. me.bboxUpdater(attr);
  20659. }
  20660. });
  20661. /**
  20662. * @private
  20663. */
  20664. Ext.define('Ext.chart.legend.sprite.Border', {
  20665. extend: 'Ext.draw.sprite.Rect',
  20666. alias: 'sprite.legendborder',
  20667. type: 'legendborder',
  20668. isLegendBorder: true
  20669. });
  20670. /**
  20671. * @private
  20672. * Singleton that provides methods used by the Ext.draw.Path
  20673. * for hit testing and finding path intersection points.
  20674. */
  20675. Ext.define('Ext.draw.PathUtil', function() {
  20676. var abs = Math.abs,
  20677. pow = Math.pow,
  20678. cos = Math.cos,
  20679. acos = Math.acos,
  20680. sqrt = Math.sqrt,
  20681. PI = Math.PI;
  20682. // For extra info see: http://pomax.github.io/bezierinfo/
  20683. return {
  20684. singleton: true,
  20685. requires: [
  20686. 'Ext.draw.overrides.hittest.Path',
  20687. 'Ext.draw.overrides.hittest.sprite.Path'
  20688. ],
  20689. /**
  20690. * @private
  20691. * Finds roots of a cubic equation in t, where t lies in the interval of [0,1].
  20692. * Based on http://www.particleincell.com/blog/2013/cubic-line-intersection/
  20693. * @param P {Number[]} Cubic equation coefficients.
  20694. * @return {Number[]} Returns an array of parametric intersection locations along the cubic,
  20695. * with -1 indicating an out-of-bounds intersection
  20696. * (before or after the end point or in the imaginary plane).
  20697. */
  20698. cubicRoots: function(P) {
  20699. var a = P[0],
  20700. b = P[1],
  20701. c = P[2],
  20702. d = P[3];
  20703. if (a === 0) {
  20704. return this.quadraticRoots(b, c, d);
  20705. }
  20706. var A = b / a,
  20707. B = c / a,
  20708. C = d / a,
  20709. Q = (3 * B - pow(A, 2)) / 9,
  20710. R = (9 * A * B - 27 * C - 2 * pow(A, 3)) / 54,
  20711. D = pow(Q, 3) + pow(R, 2),
  20712. // Polynomial discriminant.
  20713. t = [],
  20714. S, T, Im, th, i,
  20715. sign = Ext.Number.sign;
  20716. if (D >= 0) {
  20717. // Complex or duplicate roots.
  20718. S = sign(R + sqrt(D)) * pow(abs(R + sqrt(D)), 1 / 3);
  20719. T = sign(R - sqrt(D)) * pow(abs(R - sqrt(D)), 1 / 3);
  20720. t[0] = -A / 3 + (S + T);
  20721. // Real root.
  20722. t[1] = -A / 3 - (S + T) / 2;
  20723. // Real part of complex root.
  20724. t[2] = t[1];
  20725. // Real part of complex root.
  20726. Im = abs(sqrt(3) * (S - T) / 2);
  20727. // Complex part of root pair.
  20728. // Discard complex roots.
  20729. if (Im !== 0) {
  20730. t[1] = -1;
  20731. t[2] = -1;
  20732. }
  20733. } else {
  20734. // Distinct real roots.
  20735. th = acos(R / sqrt(-pow(Q, 3)));
  20736. t[0] = 2 * sqrt(-Q) * cos(th / 3) - A / 3;
  20737. t[1] = 2 * sqrt(-Q) * cos((th + 2 * PI) / 3) - A / 3;
  20738. t[2] = 2 * sqrt(-Q) * cos((th + 4 * PI) / 3) - A / 3;
  20739. }
  20740. // Discard out of spec roots.
  20741. for (i = 0; i < 3; i++) {
  20742. if (t[i] < 0 || t[i] > 1) {
  20743. t[i] = -1;
  20744. }
  20745. }
  20746. return t;
  20747. },
  20748. /**
  20749. * @private
  20750. * Finds roots of a quadratic equation in t, where t lies in the interval of [0,1].
  20751. * Takes three quadratic equation coefficients as parameters.
  20752. * @param a {Number}
  20753. * @param b {Number}
  20754. * @param c {Number}
  20755. * @return {Array}
  20756. */
  20757. quadraticRoots: function(a, b, c) {
  20758. var D, rD, t, i;
  20759. if (a === 0) {
  20760. return this.linearRoot(b, c);
  20761. }
  20762. D = b * b - 4 * a * c;
  20763. if (D === 0) {
  20764. // One real root.
  20765. t = [
  20766. -b / (2 * a)
  20767. ];
  20768. } else if (D > 0) {
  20769. // Distinct real roots.
  20770. rD = sqrt(D);
  20771. t = [
  20772. (-b - rD) / (2 * a),
  20773. (-b + rD) / (2 * a)
  20774. ];
  20775. } else {
  20776. // Complex roots.
  20777. return [];
  20778. }
  20779. for (i = 0; i < t.length; i++) {
  20780. if (t[i] < 0 || t[i] > 1) {
  20781. t[i] = -1;
  20782. }
  20783. }
  20784. return t;
  20785. },
  20786. /**
  20787. * @private
  20788. * Finds roots of a linear equation in t, where t lies in the interval of [0,1].
  20789. * Takes two linear equation coefficients as parameters.
  20790. * @param a {Number}
  20791. * @param b {Number}
  20792. * @return {Array}
  20793. */
  20794. linearRoot: function(a, b) {
  20795. var t = -b / a;
  20796. if (a === 0 || t < 0 || t > 1) {
  20797. return [];
  20798. }
  20799. return [
  20800. t
  20801. ];
  20802. },
  20803. /**
  20804. * @private
  20805. * Calculates the coefficients of a cubic function for the given coordinates.
  20806. * @param P0 {Number}
  20807. * @param P1 {Number}
  20808. * @param P2 {Number}
  20809. * @param P3 {Number}
  20810. * @return {Array}
  20811. */
  20812. bezierCoeffs: function(P0, P1, P2, P3) {
  20813. var Z = [];
  20814. Z[0] = -P0 + 3 * P1 - 3 * P2 + P3;
  20815. Z[1] = 3 * P0 - 6 * P1 + 3 * P2;
  20816. Z[2] = -3 * P0 + 3 * P1;
  20817. Z[3] = P0;
  20818. return Z;
  20819. },
  20820. /**
  20821. * @private
  20822. * Computes intersection points between a cubic spline and a line segment.
  20823. * Takes in x/y components of cubic control points and line segment start/end points
  20824. * as parameters.
  20825. * @param px1 {Number}
  20826. * @param px2 {Number}
  20827. * @param px3 {Number}
  20828. * @param px4 {Number}
  20829. * @param py1 {Number}
  20830. * @param py2 {Number}
  20831. * @param py3 {Number}
  20832. * @param py4 {Number}
  20833. * @param x1 {Number}
  20834. * @param y1 {Number}
  20835. * @param x2 {Number}
  20836. * @param y2 {Number}
  20837. * @return {Array} Array of intersection points, where each intersection point
  20838. * is itself a two-item array [x,y].
  20839. */
  20840. cubicLineIntersections: function(px1, px2, px3, px4, py1, py2, py3, py4, x1, y1, x2, y2) {
  20841. var P = [],
  20842. intersections = [],
  20843. // Finding line equation coefficients.
  20844. A = y1 - y2,
  20845. B = x2 - x1,
  20846. C = x1 * (y2 - y1) - y1 * (x2 - x1),
  20847. // Finding cubic Bezier curve equation coefficients.
  20848. bx = this.bezierCoeffs(px1, px2, px3, px4),
  20849. by = this.bezierCoeffs(py1, py2, py3, py4),
  20850. i, r, s, t, tt, ttt, cx, cy;
  20851. P[0] = A * bx[0] + B * by[0];
  20852. // t^3
  20853. P[1] = A * bx[1] + B * by[1];
  20854. // t^2
  20855. P[2] = A * bx[2] + B * by[2];
  20856. // t
  20857. P[3] = A * bx[3] + B * by[3] + C;
  20858. // 1
  20859. r = this.cubicRoots(P);
  20860. // Verify the roots are in bounds of the linear segment.
  20861. for (i = 0; i < r.length; i++) {
  20862. t = r[i];
  20863. if (t < 0 || t > 1) {
  20864. continue;
  20865. }
  20866. tt = t * t;
  20867. ttt = tt * t;
  20868. cx = bx[0] * ttt + bx[1] * tt + bx[2] * t + bx[3];
  20869. cy = by[0] * ttt + by[1] * tt + by[2] * t + by[3];
  20870. // Above is intersection point assuming infinitely long line segment,
  20871. // make sure we are also in bounds of the line.
  20872. if ((x2 - x1) !== 0) {
  20873. // If not vertical line
  20874. s = (cx - x1) / (x2 - x1);
  20875. } else {
  20876. s = (cy - y1) / (y2 - y1);
  20877. }
  20878. // In bounds?
  20879. if (!(s < 0 || s > 1)) {
  20880. intersections.push([
  20881. cx,
  20882. cy
  20883. ]);
  20884. }
  20885. }
  20886. return intersections;
  20887. },
  20888. /**
  20889. * @private
  20890. * Splits cubic Bezier curve into two cubic Bezier curves at point z,
  20891. * where z belongs to a range of [0, 1].
  20892. * Accepts cubic coefficients and point z as parameters.
  20893. * @param P1 {Number}
  20894. * @param P2 {Number}
  20895. * @param P3 {Number}
  20896. * @param P4 {Number}
  20897. * @param z Point to split the given curve at.
  20898. * @return {Array} Two-item array, where each item is itself an array
  20899. * of cubic coefficients.
  20900. */
  20901. splitCubic: function(P1, P2, P3, P4, z) {
  20902. var zz = z * z,
  20903. zzz = z * zz,
  20904. iz = z - 1,
  20905. izz = iz * iz,
  20906. izzz = iz * izz,
  20907. // Common point for both curves.
  20908. P = zzz * P4 - 3 * zz * iz * P3 + 3 * z * izz * P2 - izzz * P1;
  20909. return [
  20910. [
  20911. P1,
  20912. z * P2 - iz * P1,
  20913. zz * P3 - 2 * z * iz * P2 + izz * P1,
  20914. P
  20915. ],
  20916. [
  20917. P,
  20918. zz * P4 - 2 * z * iz * P3 + izz * P2,
  20919. z * P4 - iz * P3,
  20920. P4
  20921. ]
  20922. ];
  20923. },
  20924. /**
  20925. * @private
  20926. * Returns the dimension of a cubic Bezier curve in a single direction.
  20927. * @param a {Number}
  20928. * @param b {Number}
  20929. * @param c {Number}
  20930. * @param d {Number}
  20931. * @return {Array} Two-item array representing cubic's range in the given direction.
  20932. */
  20933. cubicDimension: function(a, b, c, d) {
  20934. var qa = 3 * (-a + 3 * (b - c) + d),
  20935. qb = 6 * (a - 2 * b + c),
  20936. qc = -3 * (a - b),
  20937. x, y,
  20938. min = Math.min(a, d),
  20939. max = Math.max(a, d),
  20940. delta;
  20941. if (qa === 0) {
  20942. if (qb === 0) {
  20943. return [
  20944. min,
  20945. max
  20946. ];
  20947. } else {
  20948. x = -qc / qb;
  20949. if (0 < x && x < 1) {
  20950. y = this.interpolateCubic(a, b, c, d, x);
  20951. min = Math.min(min, y);
  20952. max = Math.max(max, y);
  20953. }
  20954. }
  20955. } else {
  20956. delta = qb * qb - 4 * qa * qc;
  20957. if (delta >= 0) {
  20958. delta = sqrt(delta);
  20959. x = (delta - qb) / 2 / qa;
  20960. if (0 < x && x < 1) {
  20961. y = this.interpolateCubic(a, b, c, d, x);
  20962. min = Math.min(min, y);
  20963. max = Math.max(max, y);
  20964. }
  20965. if (delta > 0) {
  20966. x -= delta / qa;
  20967. if (0 < x && x < 1) {
  20968. y = this.interpolateCubic(a, b, c, d, x);
  20969. min = Math.min(min, y);
  20970. max = Math.max(max, y);
  20971. }
  20972. }
  20973. }
  20974. }
  20975. return [
  20976. min,
  20977. max
  20978. ];
  20979. },
  20980. /**
  20981. * @private
  20982. * Calculates a value of a cubic function at the given point t. In other words
  20983. * returns a * (1 - t) ^ 3 + 3 * b (1 - t) ^ 2 * t + 3 * c (1 - t) * t ^ 3 + d * t ^ 3
  20984. * for given a, b, c, d and t, where t belongs to an interval of [0, 1].
  20985. * @param a {Number}
  20986. * @param b {Number}
  20987. * @param c {Number}
  20988. * @param d {Number}
  20989. * @param t {Number}
  20990. * @return {Number}
  20991. */
  20992. interpolateCubic: function(a, b, c, d, t) {
  20993. if (t === 0) {
  20994. return a;
  20995. }
  20996. if (t === 1) {
  20997. return d;
  20998. }
  20999. var rate = (1 - t) / t;
  21000. return t * t * t * (d + rate * (3 * c + rate * (3 * b + rate * a)));
  21001. },
  21002. /**
  21003. * @private
  21004. * Computes intersection points between two cubic Bezier curve segments.
  21005. * Takes x/y components of control points for two Bezier curve segments.
  21006. * @param ax1 {Number}
  21007. * @param ax2 {Number}
  21008. * @param ax3 {Number}
  21009. * @param ax4 {Number}
  21010. * @param ay1 {Number}
  21011. * @param ay2 {Number}
  21012. * @param ay3 {Number}
  21013. * @param ay4 {Number}
  21014. * @param bx1 {Number}
  21015. * @param bx2 {Number}
  21016. * @param bx3 {Number}
  21017. * @param bx4 {Number}
  21018. * @param by1 {Number}
  21019. * @param by2 {Number}
  21020. * @param by3 {Number}
  21021. * @param by4 {Number}
  21022. * @return {Array} Array of intersection points, where each intersection point
  21023. * is itself a two-item array [x,y].
  21024. */
  21025. cubicsIntersections: function(ax1, ax2, ax3, ax4, ay1, ay2, ay3, ay4, bx1, bx2, bx3, bx4, by1, by2, by3, by4) {
  21026. var me = this,
  21027. axDim = me.cubicDimension(ax1, ax2, ax3, ax4),
  21028. ayDim = me.cubicDimension(ay1, ay2, ay3, ay4),
  21029. bxDim = me.cubicDimension(bx1, bx2, bx3, bx4),
  21030. byDim = me.cubicDimension(by1, by2, by3, by4),
  21031. splitAx, splitAy, splitBx, splitBy,
  21032. points = [];
  21033. // Curves' bounding boxes don't intersect.
  21034. if (axDim[0] > bxDim[1] || axDim[1] < bxDim[0] || ayDim[0] > byDim[1] || ayDim[1] < byDim[0]) {
  21035. return [];
  21036. }
  21037. // Both curves occupy sub-pixel areas which is effectively their intersection point.
  21038. if (abs(ay1 - ay2) < 1 && abs(ay3 - ay4) < 1 && abs(ax1 - ax4) < 1 && abs(ax2 - ax3) < 1 && abs(by1 - by2) < 1 && abs(by3 - by4) < 1 && abs(bx1 - bx4) < 1 && abs(bx2 - bx3) < 1) {
  21039. return [
  21040. [
  21041. (ax1 + ax4) * 0.5,
  21042. (ay1 + ay2) * 0.5
  21043. ]
  21044. ];
  21045. }
  21046. splitAx = me.splitCubic(ax1, ax2, ax3, ax4, 0.5);
  21047. splitAy = me.splitCubic(ay1, ay2, ay3, ay4, 0.5);
  21048. splitBx = me.splitCubic(bx1, bx2, bx3, bx4, 0.5);
  21049. splitBy = me.splitCubic(by1, by2, by3, by4, 0.5);
  21050. points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[0].concat(splitAy[0], splitBx[0], splitBy[0])));
  21051. points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[0].concat(splitAy[0], splitBx[1], splitBy[1])));
  21052. points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[1].concat(splitAy[1], splitBx[0], splitBy[0])));
  21053. points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[1].concat(splitAy[1], splitBx[1], splitBy[1])));
  21054. return points;
  21055. },
  21056. /**
  21057. * @private
  21058. * Returns the point [x,y] where two line segments intersect or null.
  21059. * Takes x/y components of the start and end point of the segments as parameters.
  21060. * Based on Paul Bourke's explanation:
  21061. * http://paulbourke.net/geometry/pointlineplane/
  21062. * @param x1 {Number}
  21063. * @param y1 {Number}
  21064. * @param x2 {Number}
  21065. * @param y2 {Number}
  21066. * @param x3 {Number}
  21067. * @param y3 {Number}
  21068. * @param x4 {Number}
  21069. * @param y4 {Number}
  21070. * @return {Number[]|null}
  21071. */
  21072. linesIntersection: function(x1, y1, x2, y2, x3, y3, x4, y4) {
  21073. var d = (x2 - x1) * (y4 - y3) - (y2 - y1) * (x4 - x3),
  21074. ua, ub;
  21075. if (d === 0) {
  21076. // Lines are parallel.
  21077. return null;
  21078. }
  21079. ua = ((x4 - x3) * (y1 - y3) - (x1 - x3) * (y4 - y3)) / d;
  21080. ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / d;
  21081. if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
  21082. return [
  21083. x1 + ua * (x2 - x1),
  21084. // x
  21085. y1 + ua * (y2 - y1)
  21086. ];
  21087. }
  21088. // y
  21089. return null;
  21090. },
  21091. // The intersection point is outside one or both segments.
  21092. /**
  21093. * @private
  21094. * Checks if a point belongs to a line segment.
  21095. * Takes x/y components of the start and end points of the segment and the point's
  21096. * coordinates as parameters.
  21097. * @param x1 {Number}
  21098. * @param y1 {Number}
  21099. * @param x2 {Number}
  21100. * @param y2 {Number}
  21101. * @param x {Number}
  21102. * @param y {Number}
  21103. * @return {Boolean}
  21104. */
  21105. pointOnLine: function(x1, y1, x2, y2, x, y) {
  21106. var t, _;
  21107. if (abs(x2 - x1) < abs(y2 - y1)) {
  21108. _ = x1;
  21109. x1 = y1;
  21110. y1 = _;
  21111. _ = x2;
  21112. x2 = y2;
  21113. y2 = _;
  21114. _ = x;
  21115. x = y;
  21116. y = _;
  21117. }
  21118. t = (x - x1) / (x2 - x1);
  21119. if (t < 0 || t > 1) {
  21120. return false;
  21121. }
  21122. return abs(y1 + t * (y2 - y1) - y) < 4;
  21123. },
  21124. /**
  21125. * @private
  21126. * Checks if a point belongs to a cubic Bezier curve segment.
  21127. * Takes x/y components of the control points of the segment and the point's
  21128. * coordinates as parameters.
  21129. * @param px1 {Number}
  21130. * @param px2 {Number}
  21131. * @param px3 {Number}
  21132. * @param px4 {Number}
  21133. * @param py1 {Number}
  21134. * @param py2 {Number}
  21135. * @param py3 {Number}
  21136. * @param py4 {Number}
  21137. * @param x {Number}
  21138. * @param y {Number}
  21139. * @return {Boolean}
  21140. */
  21141. pointOnCubic: function(px1, px2, px3, px4, py1, py2, py3, py4, x, y) {
  21142. // Finding cubic Bezier curve equation coefficients.
  21143. var me = this,
  21144. bx = me.bezierCoeffs(px1, px2, px3, px4),
  21145. by = me.bezierCoeffs(py1, py2, py3, py4),
  21146. i, j, rx, ry, t;
  21147. bx[3] -= x;
  21148. by[3] -= y;
  21149. rx = me.cubicRoots(bx);
  21150. ry = me.cubicRoots(by);
  21151. for (i = 0; i < rx.length; i++) {
  21152. t = rx[i];
  21153. for (j = 0; j < ry.length; j++) {
  21154. // TODO: for more accurate results tolerance should be dynamic
  21155. // TODO: based on the length and shape of the segment.
  21156. if (t >= 0 && t <= 1 && abs(t - ry[j]) < 0.05) {
  21157. return true;
  21158. }
  21159. }
  21160. }
  21161. return false;
  21162. }
  21163. };
  21164. });
  21165. Ext.define('Ext.draw.overrides.hittest.All', {
  21166. requires: [
  21167. 'Ext.draw.PathUtil',
  21168. 'Ext.draw.overrides.hittest.sprite.Instancing',
  21169. 'Ext.draw.overrides.hittest.Surface'
  21170. ]
  21171. });
  21172. /**
  21173. * This class uses `Ext.draw.sprite.Sprite` to render the chart legend.
  21174. *
  21175. * The DOM legend is essentially a data view docked inside a draw container, which a chart is.
  21176. * The sprite legend, on the other hand, is not a foreign entity in a draw container,
  21177. * and is rendered in a draw surface with sprites, just like series and axes.
  21178. *
  21179. * This means that:
  21180. *
  21181. * * it is styleable with chart themes
  21182. * * it shows up in chart preview and chart download
  21183. * * it renders markers exactly as they are in the series
  21184. * * it can't be styled with CSS
  21185. * * it doesn't scroll, instead the items are grouped into columns,
  21186. * and the legend grows in size as the number of items increases
  21187. *
  21188. */
  21189. Ext.define('Ext.chart.legend.SpriteLegend', {
  21190. alias: 'legend.sprite',
  21191. type: 'sprite',
  21192. isLegend: true,
  21193. isSpriteLegend: true,
  21194. mixins: [
  21195. 'Ext.mixin.Observable'
  21196. ],
  21197. requires: [
  21198. 'Ext.chart.legend.sprite.Item',
  21199. 'Ext.chart.legend.sprite.Border',
  21200. 'Ext.draw.overrides.hittest.All',
  21201. 'Ext.draw.Animator'
  21202. ],
  21203. config: {
  21204. /**
  21205. * @cfg {'top'/'left'/'right'/'bottom'} docked
  21206. * The position of the legend in the chart.
  21207. */
  21208. docked: 'bottom',
  21209. /**
  21210. * @cfg {Ext.chart.legend.store.Store} store
  21211. * The {@link Ext.chart.legend.store.Store} to bind this legend to.
  21212. * @private
  21213. */
  21214. store: null,
  21215. /**
  21216. * @cfg {Ext.chart.AbstractChart} chart
  21217. * The chart that the store belongs to.
  21218. */
  21219. chart: null,
  21220. /**
  21221. * @cfg {Ext.draw.Surface} surface
  21222. * The chart surface used to render legend sprites.
  21223. * @protected
  21224. */
  21225. surface: null,
  21226. /**
  21227. * @cfg {Object} size
  21228. * The size of the area occupied by the legend's sprites.
  21229. * This is set by the legend itself and then used during chart layout
  21230. * to make sure the 'legend' surface is big enough to accommodate
  21231. * legend sprites.
  21232. * @cfg {Number} size.width
  21233. * @cfg {Number} size.height
  21234. * @readonly
  21235. */
  21236. size: {
  21237. width: 0,
  21238. height: 0
  21239. },
  21240. /**
  21241. * @cfg {Boolean} toggleable
  21242. * `true` to allow series items to have their visibility
  21243. * toggled by interaction with the legend items.
  21244. */
  21245. toggleable: true,
  21246. /**
  21247. * @cfg {Number} padding
  21248. * The padding amount between legend items and legend border.
  21249. */
  21250. padding: 10,
  21251. label: {
  21252. preciseMeasurement: true
  21253. },
  21254. /**
  21255. * The sprite to use as a legend item marker. By default a corresponding series
  21256. * marker is used. If the series has no marker, the `circle` sprite
  21257. * is used as a legend item marker, where its `fillStyle`, `strokeStyle` and
  21258. * `lineWidth` match that of the series. The size of a legend item marker is
  21259. * controlled by the `size` property, which to defaults to `10` (pixels).
  21260. */
  21261. marker: {},
  21262. /**
  21263. * @cfg {Object} border
  21264. * The border that goes around legend item sprites.
  21265. * The type of the sprite is determined by this config,
  21266. * while the styling comes from a theme {@link Ext.chart.theme.Base #legend}.
  21267. * If both this config and the theme provide values for the
  21268. * same configs, the values from this config are used.
  21269. * The sprite class used a legend border should have the `isLegendBorder`
  21270. * property set to true on the prototype. The legend border sprite
  21271. * should also have the `x`, `y`, `width` and `height` attributes
  21272. * that determine it's position and dimensions.
  21273. */
  21274. border: {
  21275. $value: {
  21276. type: 'legendborder'
  21277. },
  21278. // The config should be processed at the time of the 'getSprites' call,
  21279. // when we already have the legend surface, otherwise the border sprite
  21280. // will not be added to the surface.
  21281. lazy: true
  21282. },
  21283. /**
  21284. * @cfg {Object} background
  21285. * Sets the legend background.
  21286. * This can be a gradient object, image, or color. This config works similarly
  21287. * to the {@link Ext.chart.AbstractChart#background} config.
  21288. */
  21289. background: null,
  21290. /**
  21291. * @cfg {Boolean} hidden Toggles the visibility of the legend.
  21292. */
  21293. hidden: false
  21294. },
  21295. sprites: null,
  21296. spriteZIndexes: {
  21297. background: 0,
  21298. border: 1,
  21299. // Item sprites should have a higher zIndex than border,
  21300. // or they won't react to clicks.
  21301. item: 2
  21302. },
  21303. dockedValues: {
  21304. left: true,
  21305. right: true,
  21306. top: true,
  21307. bottom: true
  21308. },
  21309. constructor: function(config) {
  21310. var me = this;
  21311. me.oldSize = {
  21312. width: 0,
  21313. height: 0
  21314. };
  21315. me.getId();
  21316. me.mixins.observable.constructor.call(me, config);
  21317. },
  21318. applyStore: function(store) {
  21319. return store && Ext.StoreManager.lookup(store);
  21320. },
  21321. updateStore: function(store, oldStore) {
  21322. var me = this;
  21323. if (oldStore) {
  21324. oldStore.un('datachanged', me.onDataChanged, me);
  21325. oldStore.un('update', me.onDataUpdate, me);
  21326. }
  21327. if (store) {
  21328. store.on('datachanged', me.onDataChanged, me);
  21329. store.on('update', me.onDataUpdate, me);
  21330. me.onDataChanged(store);
  21331. }
  21332. me.performLayout();
  21333. },
  21334. //<debug>
  21335. applyDocked: function(docked) {
  21336. if (!(docked in this.dockedValues)) {
  21337. Ext.raise("Invalid 'docked' config value.");
  21338. }
  21339. return docked;
  21340. },
  21341. //</debug>
  21342. updateDocked: function(docked) {
  21343. this.isTop = docked === 'top';
  21344. if (!this.isConfiguring) {
  21345. this.layoutChart();
  21346. }
  21347. },
  21348. updateHidden: function(hidden) {
  21349. this.getChart();
  21350. // 'chart' updater will set the surface
  21351. var surface = this.getSurface();
  21352. if (surface) {
  21353. surface.setHidden(hidden);
  21354. }
  21355. if (!this.isConfiguring) {
  21356. this.layoutChart();
  21357. }
  21358. },
  21359. /**
  21360. * @private
  21361. */
  21362. layoutChart: function() {
  21363. if (!this.isConfiguring) {
  21364. var chart = this.getChart();
  21365. if (chart) {
  21366. chart.scheduleLayout();
  21367. }
  21368. }
  21369. },
  21370. /**
  21371. * @private
  21372. * Calculates and returns the legend surface rect and adjusts the passed `chartRect`
  21373. * accordingly. The first time this is called, the `SpriteLegend` will have zero size
  21374. * (no width or height).
  21375. * @param {Number[]} chartRect [left, top, width, height] components as an array.
  21376. * @return {Number[]} [left, top, width, height] components as an array, or null.
  21377. */
  21378. computeRect: function(chartRect) {
  21379. if (this.getHidden()) {
  21380. return null;
  21381. }
  21382. var rect = [
  21383. 0,
  21384. 0,
  21385. 0,
  21386. 0
  21387. ],
  21388. docked = this.getDocked(),
  21389. size = this.getSize(),
  21390. height = size.height,
  21391. width = size.width;
  21392. switch (docked) {
  21393. case 'top':
  21394. rect[1] = chartRect[1];
  21395. rect[2] = chartRect[2];
  21396. rect[3] = height;
  21397. chartRect[1] += height;
  21398. chartRect[3] -= height;
  21399. break;
  21400. case 'bottom':
  21401. chartRect[3] -= height;
  21402. rect[1] = chartRect[3];
  21403. rect[2] = chartRect[2];
  21404. rect[3] = height;
  21405. break;
  21406. case 'left':
  21407. chartRect[0] += width;
  21408. chartRect[2] -= width;
  21409. rect[2] = width;
  21410. rect[3] = chartRect[3];
  21411. break;
  21412. case 'right':
  21413. chartRect[2] -= width;
  21414. rect[0] = chartRect[2];
  21415. rect[2] = width;
  21416. rect[3] = chartRect[3];
  21417. break;
  21418. }
  21419. return rect;
  21420. },
  21421. applyBorder: function(config) {
  21422. var border;
  21423. if (config) {
  21424. if (config.isSprite) {
  21425. border = config;
  21426. } else {
  21427. border = Ext.create('sprite.' + config.type, config);
  21428. }
  21429. }
  21430. if (border) {
  21431. border.isLegendBorder = true;
  21432. border.setAttributes({
  21433. zIndex: this.spriteZIndexes.border
  21434. });
  21435. }
  21436. return border;
  21437. },
  21438. updateBorder: function(border, oldBorder) {
  21439. var surface = this.getSurface();
  21440. this.borderSprite = null;
  21441. if (surface) {
  21442. if (oldBorder) {
  21443. surface.remove(oldBorder);
  21444. }
  21445. if (border) {
  21446. this.borderSprite = surface.add(border);
  21447. }
  21448. }
  21449. },
  21450. scheduleLayout: function() {
  21451. if (!this.scheduledLayoutId) {
  21452. this.scheduledLayoutId = Ext.draw.Animator.schedule('performLayout', this);
  21453. }
  21454. },
  21455. cancelLayout: function() {
  21456. Ext.draw.Animator.cancel(this.scheduledLayoutId);
  21457. this.scheduledLayoutId = null;
  21458. },
  21459. performLayout: function() {
  21460. var me = this,
  21461. size = me.getSize(),
  21462. gap = me.getPadding(),
  21463. sprites = me.getSprites(),
  21464. surface = me.getSurface(),
  21465. background = me.getBackground(),
  21466. surfaceRect = surface.getRect(),
  21467. store = me.getStore(),
  21468. ln = (sprites && sprites.length) || 0,
  21469. i, sprite;
  21470. if (!surface || !surfaceRect || !store) {
  21471. return false;
  21472. }
  21473. me.cancelLayout();
  21474. var docked = me.getDocked(),
  21475. surfaceWidth = surfaceRect[2],
  21476. surfaceHeight = surfaceRect[3],
  21477. border = me.borderSprite,
  21478. bboxes = [],
  21479. startX, // Coordinates of the top-left corner.
  21480. startY, // of the first 'legenditem' sprite.
  21481. columnSize, // Number of items in a column.
  21482. columnCount, // Number of columns.
  21483. columnWidth, itemsWidth, itemsHeight, paddedItemsWidth, // The horizontal span of all 'legenditem' sprites.
  21484. paddedItemsHeight, // The vertical span of all 'legenditem' sprites.
  21485. paddedBorderWidth, paddedBorderHeight, itemHeight, bbox, x, y;
  21486. for (i = 0; i < ln; i++) {
  21487. sprite = sprites[i];
  21488. bbox = sprite.getBBox();
  21489. bboxes.push(bbox);
  21490. }
  21491. if (bbox) {
  21492. itemHeight = bbox.height;
  21493. }
  21494. switch (docked) {
  21495. /*
  21496. Horizontal legend.
  21497. The outer box is the legend surface.
  21498. The inner box is the legend border.
  21499. There's a fixed amount of padding between all the items,
  21500. denoted by ##. This amount is controlled by the 'padding' config
  21501. of the legend.
  21502. |-------------------------------------------------------------|
  21503. | ## |
  21504. | |---------------------------------------------------| |
  21505. | | ## ## ## | |
  21506. | | -------- ----------- -------- | |
  21507. | ## | ## | Item 0 | ## | Item 2 | ## | Item 4 | ## | ## |
  21508. | | -------- ----------- -------- | |
  21509. | | ## ## ## | |
  21510. | | ---------- --------- | |
  21511. | | ## | Item 1 | ## | Item 3 | | |
  21512. | | ---------- --------- | |
  21513. | | ## ## | |
  21514. | |---------------------------------------------------| |
  21515. | ## |
  21516. |-------------------------------------------------------------|
  21517. */
  21518. case 'bottom':
  21519. case 'top':
  21520. // surface must have a width before we can proceed to layout top/bottom
  21521. // docked legend. width may be 0 if we are rendered into an inactive tab.
  21522. // see https://sencha.jira.com/browse/EXTJS-22454
  21523. if (!surfaceWidth) {
  21524. return false;
  21525. };
  21526. columnSize = 0;
  21527. // Split legend items into columns until the width is suitable.
  21528. do {
  21529. itemsWidth = 0;
  21530. columnWidth = 0;
  21531. columnCount = 0;
  21532. columnSize++;
  21533. for (i = 0; i < ln; i++) {
  21534. bbox = bboxes[i];
  21535. if (bbox.width > columnWidth) {
  21536. columnWidth = bbox.width;
  21537. }
  21538. if ((i + 1) % columnSize === 0) {
  21539. itemsWidth += columnWidth;
  21540. columnWidth = 0;
  21541. columnCount++;
  21542. }
  21543. }
  21544. if (i % columnSize !== 0) {
  21545. itemsWidth += columnWidth;
  21546. columnCount++;
  21547. }
  21548. paddedItemsWidth = itemsWidth + (columnCount - 1) * gap;
  21549. paddedBorderWidth = paddedItemsWidth + gap * 4;
  21550. } while (paddedBorderWidth > surfaceWidth);
  21551. paddedItemsHeight = itemHeight * columnSize + (columnSize - 1) * gap;
  21552. break;
  21553. /*
  21554. Vertical legend.
  21555. |-----------------------------------------------|
  21556. | ## |
  21557. | |-------------------------------------| |
  21558. | | ## ## | |
  21559. | | -------- ----------- | |
  21560. | | ## | Item 0 | ## | Item 1 | ## | |
  21561. | | -------- ----------- | |
  21562. | | ## ## | |
  21563. | | ---------- --------- | |
  21564. | ## | ## | Item 2 | ## | Item 3 | | ## |
  21565. | | ---------- --------- | |
  21566. | | ## | |
  21567. | | -------- | |
  21568. | | ## | Item 4 | | |
  21569. | | -------- | |
  21570. | | ## | |
  21571. | |-------------------------------------| |
  21572. | ## |
  21573. |-----------------------------------------------|
  21574. */
  21575. case 'right':
  21576. case 'left':
  21577. // surface must have a height before we can proceed to layout right/left
  21578. // docked legend. height may be 0 if we are rendered into an inactive tab.
  21579. // see https://sencha.jira.com/browse/EXTJS-22454
  21580. if (!surfaceHeight) {
  21581. return false;
  21582. };
  21583. columnSize = ln * 2;
  21584. // Split legend items into columns until the height is suitable.
  21585. do {
  21586. columnSize = (columnSize >> 1) + (columnSize % 2);
  21587. itemsWidth = 0;
  21588. itemsHeight = 0;
  21589. columnWidth = 0;
  21590. columnCount = 0;
  21591. for (i = 0; i < ln; i++) {
  21592. bbox = bboxes[i];
  21593. if (!columnCount) {
  21594. itemsHeight += bbox.height;
  21595. }
  21596. if (bbox.width > columnWidth) {
  21597. columnWidth = bbox.width;
  21598. }
  21599. if ((i + 1) % columnSize === 0) {
  21600. itemsWidth += columnWidth;
  21601. columnWidth = 0;
  21602. columnCount++;
  21603. }
  21604. }
  21605. if (i % columnSize !== 0) {
  21606. itemsWidth += columnWidth;
  21607. columnCount++;
  21608. }
  21609. paddedItemsWidth = itemsWidth + (columnCount - 1) * gap;
  21610. paddedItemsHeight = itemsHeight + (columnSize - 1) * gap;
  21611. paddedBorderWidth = paddedItemsWidth + gap * 4;
  21612. paddedBorderHeight = paddedItemsHeight + gap * 4;
  21613. } while (// Integer division by 2, plus remainder.
  21614. // itemsHeight is determined by the height of the first column.
  21615. paddedItemsHeight > surfaceHeight);
  21616. break;
  21617. }
  21618. startX = (surfaceWidth - paddedItemsWidth) / 2;
  21619. startY = (surfaceHeight - paddedItemsHeight) / 2;
  21620. x = 0;
  21621. y = 0;
  21622. columnWidth = 0;
  21623. for (i = 0; i < ln; i++) {
  21624. sprite = sprites[i];
  21625. bbox = bboxes[i];
  21626. sprite.setAttributes({
  21627. translationX: startX + x,
  21628. translationY: startY + y
  21629. });
  21630. if (bbox.width > columnWidth) {
  21631. columnWidth = bbox.width;
  21632. }
  21633. if ((i + 1) % columnSize === 0) {
  21634. x += columnWidth + gap;
  21635. y = 0;
  21636. columnWidth = 0;
  21637. } else {
  21638. y += bbox.height + gap;
  21639. }
  21640. }
  21641. if (border) {
  21642. border.setAttributes({
  21643. hidden: !ln,
  21644. x: startX - gap,
  21645. y: startY - gap,
  21646. width: paddedItemsWidth + gap * 2,
  21647. height: paddedItemsHeight + gap * 2
  21648. });
  21649. }
  21650. size.width = border.attr.width + gap * 2;
  21651. size.height = border.attr.height + gap * 2;
  21652. if (size.width !== me.oldSize.width || size.height !== me.oldSize.height) {
  21653. // Do not simply assign size to oldSize, as we want them to be
  21654. // separate objects.
  21655. Ext.apply(me.oldSize, size);
  21656. // Legend size has changed, so we return 'false' to cancel the current
  21657. // chart layout (this method is called by chart's 'performLayout' method)
  21658. // and manually start a new chart layout.
  21659. me.getChart().scheduleLayout();
  21660. return false;
  21661. }
  21662. if (background) {
  21663. me.resizeBackground(surface, background);
  21664. }
  21665. surface.renderFrame();
  21666. return true;
  21667. },
  21668. // Doesn't include the border sprite which also belongs to the 'legend'
  21669. // surface. To get it, use the 'getBorder' method.
  21670. getSprites: function() {
  21671. this.updateSprites();
  21672. return this.sprites;
  21673. },
  21674. /**
  21675. * @private
  21676. * Creates a 'legenditem' sprite in the given surface
  21677. * using the legend store record data provided.
  21678. * @param {Ext.draw.Surface} surface
  21679. * @param {Ext.chart.legend.store.Item} record
  21680. * @return {Ext.chart.legend.sprite.Item}
  21681. */
  21682. createSprite: function(surface, record) {
  21683. var me = this,
  21684. data = record.data,
  21685. chart = me.getChart(),
  21686. series = chart.get(data.series),
  21687. seriesMarker = series.getMarker(),
  21688. sprite = null,
  21689. markerConfig, labelConfig, legendItemConfig;
  21690. if (surface) {
  21691. markerConfig = series.getMarkerStyleByIndex(data.index);
  21692. markerConfig.fillStyle = data.mark;
  21693. markerConfig.hidden = false;
  21694. if (seriesMarker && seriesMarker.type) {
  21695. markerConfig.type = seriesMarker.type;
  21696. }
  21697. Ext.apply(markerConfig, me.getMarker());
  21698. markerConfig.surface = surface;
  21699. labelConfig = me.getLabel();
  21700. legendItemConfig = {
  21701. type: 'legenditem',
  21702. zIndex: me.spriteZIndexes.item,
  21703. text: data.name,
  21704. enabled: !data.disabled,
  21705. marker: markerConfig,
  21706. label: labelConfig,
  21707. series: data.series,
  21708. record: record
  21709. };
  21710. sprite = surface.add(legendItemConfig);
  21711. }
  21712. return sprite;
  21713. },
  21714. /**
  21715. * @private
  21716. * Creates legend item sprites and associates them with legend store records.
  21717. * Updates attributes of the sprites when legend store data changes.
  21718. */
  21719. updateSprites: function() {
  21720. var me = this,
  21721. chart = me.getChart(),
  21722. store = me.getStore(),
  21723. surface = me.getSurface(),
  21724. item, items, itemSprite, i, ln, sprites, unusedSprites, border;
  21725. if (!(chart && store && surface)) {
  21726. return;
  21727. }
  21728. me.sprites = sprites = me.sprites || [];
  21729. items = store.getData().items;
  21730. ln = items.length;
  21731. for (i = 0; i < ln; i++) {
  21732. item = items[i];
  21733. itemSprite = sprites[i];
  21734. if (itemSprite) {
  21735. me.updateSprite(itemSprite, item);
  21736. } else {
  21737. itemSprite = me.createSprite(surface, item);
  21738. surface.add(itemSprite);
  21739. sprites.push(itemSprite);
  21740. }
  21741. }
  21742. unusedSprites = Ext.Array.splice(sprites, i, sprites.length);
  21743. for (i = 0 , ln = unusedSprites.length; i < ln; i++) {
  21744. itemSprite = unusedSprites[i];
  21745. itemSprite.destroy();
  21746. }
  21747. border = me.getBorder();
  21748. if (border) {
  21749. me.borderSprite = border;
  21750. }
  21751. me.updateTheme(chart.getTheme());
  21752. },
  21753. /**
  21754. * @private
  21755. * Updates the given legend item sprite based on store record data.
  21756. * @param {Ext.chart.legend.sprite.Item} sprite
  21757. * @param {Ext.chart.legend.store.Item} record
  21758. */
  21759. updateSprite: function(sprite, record) {
  21760. var data = record.data,
  21761. chart = this.getChart(),
  21762. series = chart.get(data.series),
  21763. marker, label, markerConfig;
  21764. if (sprite) {
  21765. label = sprite.getLabel();
  21766. label.setAttributes({
  21767. text: data.name
  21768. });
  21769. sprite.setAttributes({
  21770. enabled: !data.disabled
  21771. });
  21772. sprite.setConfig({
  21773. series: data.series,
  21774. record: record
  21775. });
  21776. markerConfig = series.getMarkerStyleByIndex(data.index);
  21777. markerConfig.fillStyle = data.mark;
  21778. markerConfig.hidden = false;
  21779. Ext.apply(markerConfig, this.getMarker());
  21780. marker = sprite.getMarker();
  21781. marker.setAttributes({
  21782. fillStyle: markerConfig.fillStyle,
  21783. strokeStyle: markerConfig.strokeStyle
  21784. });
  21785. sprite.layoutUpdater(sprite.attr);
  21786. }
  21787. },
  21788. updateChart: function(newChart, oldChart) {
  21789. var me = this;
  21790. if (oldChart) {
  21791. me.setSurface(null);
  21792. }
  21793. if (newChart) {
  21794. me.setSurface(newChart.getSurface('legend'));
  21795. }
  21796. },
  21797. updateSurface: function(surface, oldSurface) {
  21798. if (oldSurface) {
  21799. oldSurface.el.un('click', 'onClick', this);
  21800. // The surface should not be destroyed here, just cleared.
  21801. // E.g. we may remove the sprite legend only to add another one.
  21802. oldSurface.removeAll(true);
  21803. }
  21804. if (surface) {
  21805. surface.isLegendSurface = true;
  21806. surface.el.on('click', 'onClick', this);
  21807. }
  21808. },
  21809. onClick: function(event) {
  21810. var chart = this.getChart(),
  21811. surface = this.getSurface(),
  21812. result, point;
  21813. if (chart && chart.hasFirstLayout && surface) {
  21814. point = surface.getEventXY(event);
  21815. result = surface.hitTest(point);
  21816. if (result && result.sprite) {
  21817. this.toggleItem(result.sprite);
  21818. }
  21819. }
  21820. },
  21821. applyBackground: function(newBackground, oldBackground) {
  21822. var me = this,
  21823. // It's important to get the `chart` first here,
  21824. // because the `surface` is set by the `chart` updater.
  21825. chart = me.getChart(),
  21826. surface = me.getSurface(),
  21827. background;
  21828. background = chart.refreshBackground(surface, newBackground, oldBackground);
  21829. if (background) {
  21830. background.setAttributes({
  21831. zIndex: me.spriteZIndexes.background
  21832. });
  21833. }
  21834. return background;
  21835. },
  21836. resizeBackground: function(surface, background) {
  21837. var width = background.attr.width,
  21838. height = background.attr.height,
  21839. surfaceRect = surface.getRect();
  21840. if (surfaceRect && (width !== surfaceRect[2] || height !== surfaceRect[3])) {
  21841. background.setAttributes({
  21842. width: surfaceRect[2],
  21843. height: surfaceRect[3]
  21844. });
  21845. }
  21846. },
  21847. themeableConfigs: {
  21848. background: true
  21849. },
  21850. updateTheme: function(theme) {
  21851. var me = this,
  21852. surface = me.getSurface(),
  21853. sprites = surface.getItems(),
  21854. legendTheme = theme.getLegend(),
  21855. labelConfig = me.getLabel(),
  21856. configs = me.self.getConfigurator().configs,
  21857. themeableConfigs = me.themeableConfigs,
  21858. initialConfig = me.getInitialConfig(),
  21859. defaultConfig = me.defaultConfig,
  21860. value, cfg, isObjValue, isUnusedConfig, initialValue, sprite, style, labelSprite, key, attr, i, ln;
  21861. for (i = 0 , ln = sprites.length; i < ln; i++) {
  21862. sprite = sprites[i];
  21863. if (sprite.isLegendItem) {
  21864. style = legendTheme.label;
  21865. if (style) {
  21866. attr = null;
  21867. for (key in style) {
  21868. if (!(key in labelConfig)) {
  21869. attr = attr || {};
  21870. attr[key] = style[key];
  21871. }
  21872. }
  21873. if (attr) {
  21874. labelSprite = sprite.getLabel();
  21875. labelSprite.setAttributes(attr);
  21876. }
  21877. }
  21878. continue;
  21879. } else if (sprite.isLegendBorder) {
  21880. style = legendTheme.border;
  21881. } else {
  21882. continue;
  21883. }
  21884. if (style) {
  21885. attr = {};
  21886. for (key in style) {
  21887. if (!(key in sprite.config)) {
  21888. attr[key] = style[key];
  21889. }
  21890. }
  21891. sprite.setAttributes(attr);
  21892. }
  21893. }
  21894. value = legendTheme.background;
  21895. cfg = configs.background;
  21896. if (value !== null && value !== undefined && cfg) {}
  21897. for (key in legendTheme) {
  21898. if (!(key in themeableConfigs)) {
  21899. continue;
  21900. }
  21901. value = legendTheme[key];
  21902. cfg = configs[key];
  21903. if (value !== null && value !== undefined && cfg) {
  21904. initialValue = initialConfig[key];
  21905. isObjValue = Ext.isObject(value);
  21906. isUnusedConfig = initialValue === defaultConfig[key];
  21907. if (isObjValue) {
  21908. if (isUnusedConfig && themeOnlyIfConfigured[key]) {
  21909. continue;
  21910. }
  21911. value = Ext.merge({}, value, initialValue);
  21912. }
  21913. if (isUnusedConfig || isObjValue) {
  21914. me[cfg.names.set](value);
  21915. }
  21916. }
  21917. }
  21918. },
  21919. onDataChanged: function(store) {
  21920. this.updateSprites();
  21921. this.scheduleLayout();
  21922. },
  21923. onDataUpdate: function(store, record) {
  21924. var me = this,
  21925. sprites = me.sprites,
  21926. ln = sprites.length,
  21927. i = 0,
  21928. sprite, spriteRecord, match;
  21929. for (; i < ln; i++) {
  21930. sprite = sprites[i];
  21931. spriteRecord = sprite.getRecord();
  21932. if (spriteRecord === record) {
  21933. match = sprite;
  21934. break;
  21935. }
  21936. }
  21937. if (match) {
  21938. me.updateSprite(match, record);
  21939. me.scheduleLayout();
  21940. }
  21941. },
  21942. toggleItem: function(sprite) {
  21943. if (!this.getToggleable() || !sprite.isLegendItem) {
  21944. return;
  21945. }
  21946. var store = this.getStore(),
  21947. disabledCount = 0,
  21948. canToggle = true,
  21949. i, count, record, disabled;
  21950. if (store) {
  21951. count = store.getCount();
  21952. for (i = 0; i < count; i++) {
  21953. record = store.getAt(i);
  21954. if (record.get('disabled')) {
  21955. disabledCount++;
  21956. }
  21957. }
  21958. canToggle = count - disabledCount > 1;
  21959. record = sprite.getRecord();
  21960. if (record) {
  21961. disabled = record.get('disabled');
  21962. if (disabled || canToggle) {
  21963. // This will trigger AbstractChart.onLegendStoreUpdate.
  21964. record.set('disabled', !disabled);
  21965. sprite.setAttributes({
  21966. enabled: disabled
  21967. });
  21968. }
  21969. }
  21970. }
  21971. },
  21972. destroy: function() {
  21973. var me = this;
  21974. me.destroying = true;
  21975. me.cancelLayout();
  21976. me.setChart(null);
  21977. me.callParent();
  21978. }
  21979. });
  21980. /**
  21981. * Chart captions can be used to place titles, subtitles, credits and other captions
  21982. * inside a chart. Please see the chart's {@link Ext.chart.AbstractChart#captions}
  21983. * config documentation for the general description of the way captions work, and
  21984. * refer to the documentation of this class' configs for details.
  21985. */
  21986. Ext.define('Ext.chart.Caption', {
  21987. mixins: [
  21988. 'Ext.mixin.Observable',
  21989. 'Ext.mixin.Bindable'
  21990. ],
  21991. isCaption: true,
  21992. config: {
  21993. /**
  21994. * The weight controls the order in which the captions are created.
  21995. * Captions with lower weights are created first.
  21996. * This affects chart's layout. For example, if two captions are docked
  21997. * to the 'top', the one with the lower weight will end up on top
  21998. * of the other.
  21999. */
  22000. weight: 0,
  22001. /**
  22002. * @cfg {String} text
  22003. * The text displayed by the caption.
  22004. * Multi-line captions are allowed, e.g.:
  22005. *
  22006. * captions: {
  22007. * title: {
  22008. * text: 'India\'s tiger population\n'
  22009. * + 'from 1970 to 2015'
  22010. * }
  22011. * }
  22012. *
  22013. */
  22014. text: '',
  22015. /**
  22016. * @cfg {'left'/'center'/'right'} [align='center']
  22017. * Determines the horizontal alignment of the caption's text.
  22018. */
  22019. align: 'center',
  22020. /**
  22021. * @cfg {'series'/'chart'} [alignTo='series']
  22022. * Whether to align the caption to the 'series' (default) or the 'chart'.
  22023. */
  22024. alignTo: 'series',
  22025. /**
  22026. * @cfg {Number} padding
  22027. * The uniform padding applied to both top and bottom of the caption's text.
  22028. */
  22029. padding: 0,
  22030. /**
  22031. * @cfg {Boolean} [hidden=false]
  22032. * Controls the visibility of the caption.
  22033. */
  22034. hidden: false,
  22035. /**
  22036. * @cfg {'top'/'bottom'} [docked='top']
  22037. * The position of the caption in a chart.
  22038. */
  22039. docked: 'top',
  22040. /**
  22041. * @cfg {Object} style
  22042. * Style attributes for the caption's text.
  22043. * All attributes that are valid {@link Ext.draw.sprite.Text text sprite} attributes
  22044. * are valid here. However, only font attributes (such as `fontSize`, `fontFamily`, ...),
  22045. * color attributes (such as `fillStyle`) and `textAlign` attribute are guaranteed to
  22046. * produce correct behavior. For example, transform attributes are not officially supported.
  22047. */
  22048. style: {
  22049. fontSize: '14px',
  22050. fontWeight: 'bold',
  22051. fontFamily: 'Verdana, Aria, sans-serif'
  22052. },
  22053. /**
  22054. * @private
  22055. * @cfg {Ext.chart.AbstractChart} chart
  22056. * The chart the label belongs to.
  22057. */
  22058. chart: null,
  22059. /**
  22060. * @private
  22061. * The text sprite used to render caption's text.
  22062. */
  22063. sprite: {
  22064. type: 'text',
  22065. preciseMeasurement: true,
  22066. zIndex: 10
  22067. },
  22068. //<debug>
  22069. /**
  22070. * @private
  22071. * @cfg {Boolean} debug
  22072. * Whether to show the bounding boxes or not.
  22073. */
  22074. debug: false,
  22075. //</debug>
  22076. /**
  22077. * @private
  22078. * The logical rect of the caption in the `surfaceName` surface.
  22079. */
  22080. rect: null
  22081. },
  22082. surfaceName: 'caption',
  22083. constructor: function(config) {
  22084. var me = this,
  22085. id;
  22086. if ('id' in config) {
  22087. id = config.id;
  22088. } else if ('id' in me.config) {
  22089. id = me.config.id;
  22090. } else {
  22091. id = me.getId();
  22092. }
  22093. me.setId(id);
  22094. me.mixins.observable.constructor.call(me, config);
  22095. me.initBindable();
  22096. },
  22097. updateChart: function() {
  22098. if (!this.isConfiguring) {
  22099. // Re-create caption's sprite in another chart.
  22100. this.setSprite({
  22101. type: 'text'
  22102. });
  22103. }
  22104. },
  22105. applySprite: function(sprite) {
  22106. var me = this,
  22107. chart = me.getChart(),
  22108. surface = me.surface = chart.getSurface(me.surfaceName);
  22109. //<debug>
  22110. me.rectSprite = surface.add({
  22111. type: 'rect',
  22112. fillStyle: 'yellow',
  22113. strokeStyle: 'red'
  22114. });
  22115. //</debug>
  22116. return sprite && surface.add(sprite);
  22117. },
  22118. updateSprite: function(sprite, oldSprite) {
  22119. if (oldSprite) {
  22120. oldSprite.destroy();
  22121. }
  22122. },
  22123. updateText: function(text) {
  22124. this.getSprite().setAttributes({
  22125. text: text
  22126. });
  22127. },
  22128. updateStyle: function(style) {
  22129. this.getSprite().setAttributes(style);
  22130. },
  22131. //<debug>
  22132. updateDebug: function(debug) {
  22133. var me = this,
  22134. sprite = me.getSprite();
  22135. if (debug && !me.rectSprite) {
  22136. me.rectSprite = me.surface.add({
  22137. type: 'rect',
  22138. fillStyle: 'yellow',
  22139. strokeStyle: 'red'
  22140. });
  22141. }
  22142. if (sprite) {
  22143. sprite.setAttributes({
  22144. debug: debug ? {
  22145. bbox: true
  22146. } : null
  22147. });
  22148. }
  22149. if (me.rectSprite) {
  22150. me.rectSprite.setAttributes({
  22151. hidden: !debug
  22152. });
  22153. }
  22154. if (!me.isConfiguring) {
  22155. me.surface.renderFrame();
  22156. }
  22157. },
  22158. //</debug>
  22159. updateRect: function(rect) {
  22160. if (this.rectSprite) {
  22161. this.rectSprite.setAttributes({
  22162. x: rect[0],
  22163. y: rect[1],
  22164. width: rect[2],
  22165. height: rect[3]
  22166. });
  22167. }
  22168. },
  22169. updateDocked: function() {
  22170. var chart = this.getChart();
  22171. if (chart && !this.isConfiguring) {
  22172. chart.scheduleLayout();
  22173. }
  22174. },
  22175. /**
  22176. * @private
  22177. * Computes and sets the caption's rect.
  22178. * Shrinks the given chart rect to accomodate the caption.
  22179. * The chart rect is [top, left, width, height] in chart's
  22180. * body element coordinates.
  22181. * The shrink rect is {left, top, right, bottom} in `caption`
  22182. * surface coordinates.
  22183. */
  22184. computeRect: function(chartRect, shrinkRect) {
  22185. if (this.getHidden()) {
  22186. return null;
  22187. }
  22188. var rect = [
  22189. 0,
  22190. 0,
  22191. chartRect[2],
  22192. 0
  22193. ],
  22194. docked = this.getDocked(),
  22195. padding = this.getPadding(),
  22196. textSize = this.getSprite().getBBox(),
  22197. height = textSize.height + padding * 2;
  22198. switch (docked) {
  22199. case 'top':
  22200. rect[1] = shrinkRect.top;
  22201. rect[3] = height;
  22202. chartRect[1] += height;
  22203. chartRect[3] -= height;
  22204. shrinkRect.top += height;
  22205. break;
  22206. case 'bottom':
  22207. chartRect[3] -= height;
  22208. shrinkRect.bottom -= height;
  22209. rect[1] = shrinkRect.bottom;
  22210. rect[3] = height;
  22211. break;
  22212. }
  22213. this.setRect(rect);
  22214. },
  22215. alignRect: function(seriesRect) {
  22216. var surfaceRect = this.surface.getRect(),
  22217. rect = this.getRect();
  22218. rect[0] = seriesRect[0] - surfaceRect[0];
  22219. rect[2] = seriesRect[2];
  22220. // Slice to trigger the applier/updater.
  22221. this.setRect(rect.slice());
  22222. },
  22223. performLayout: function() {
  22224. var me = this,
  22225. rect = me.getRect(),
  22226. x = rect[0],
  22227. y = rect[1],
  22228. width = rect[2],
  22229. height = rect[3],
  22230. sprite = me.getSprite(),
  22231. tx = sprite.attr.translationX,
  22232. ty = sprite.attr.translationY,
  22233. bbox = sprite.getBBox(),
  22234. align = me.getAlign(),
  22235. dx, dy;
  22236. switch (align) {
  22237. case 'left':
  22238. dx = x - bbox.x;
  22239. break;
  22240. case 'right':
  22241. dx = (x + width) - (bbox.x + bbox.width);
  22242. break;
  22243. case 'center':
  22244. dx = x + (width - bbox.width) / 2 - bbox.x;
  22245. break;
  22246. }
  22247. dy = y + (height - bbox.height) / 2 - bbox.y;
  22248. sprite.setAttributes({
  22249. translationX: tx + dx,
  22250. translationY: ty + dy
  22251. });
  22252. },
  22253. destroy: function() {
  22254. var me = this;
  22255. //<debug>
  22256. if (me.rectSprite) {
  22257. me.rectSprite.destroy();
  22258. }
  22259. //</debug>
  22260. me.getSprite().destroy();
  22261. me.callParent();
  22262. }
  22263. });
  22264. /**
  22265. * The data model for legend items.
  22266. */
  22267. Ext.define('Ext.chart.legend.store.Item', {
  22268. extend: 'Ext.data.Model',
  22269. fields: [
  22270. 'id',
  22271. 'name',
  22272. // The series title.
  22273. 'mark',
  22274. // The color of the series.
  22275. 'disabled',
  22276. // The state of the series.
  22277. 'series',
  22278. // A reference to the series instance.
  22279. 'index'
  22280. ]
  22281. });
  22282. // A sprite index, e.g. for stacked or pie series.
  22283. // For such series an individual component of the series
  22284. // is hidden or shown when the legend item is toggled.
  22285. /**
  22286. * The store type used for legend items.
  22287. */
  22288. Ext.define('Ext.chart.legend.store.Store', {
  22289. extend: 'Ext.data.Store',
  22290. requires: [
  22291. 'Ext.chart.legend.store.Item'
  22292. ],
  22293. model: 'Ext.chart.legend.store.Item',
  22294. isLegendStore: true,
  22295. config: {
  22296. autoDestroy: true
  22297. }
  22298. });
  22299. /**
  22300. * The Ext.chart package provides the capability to visualize data.
  22301. * Each chart binds directly to a {@link Ext.data.Store store} enabling automatic
  22302. * updates of the chart. A chart configuration object has some overall styling
  22303. * options as well as an array of axes and series. A chart instance example could
  22304. * look like this:
  22305. *
  22306. * Ext.create('Ext.chart.CartesianChart', {
  22307. * width: 800,
  22308. * height: 600,
  22309. * animation: {
  22310. * easing: 'backOut',
  22311. * duration: 500
  22312. * },
  22313. * store: store1,
  22314. * legend: {
  22315. * position: 'right'
  22316. * },
  22317. * axes: [
  22318. * // ...some axes options...
  22319. * ],
  22320. * series: [
  22321. * // ...some series options...
  22322. * ]
  22323. * });
  22324. *
  22325. * In this example we set the `width` and `height` of a chart; We decide whether
  22326. * our series are animated or not and we select a store to be bound to the chart;
  22327. * We also set the legend to the right part of the chart.
  22328. *
  22329. * You can register certain interactions such as {@link Ext.chart.interactions.PanZoom}
  22330. * on the chart by specifying an array of names or more specific config objects.
  22331. * All the events will be wired automatically.
  22332. *
  22333. * You can also listen to series `itemXXX` events on both chart and series level.
  22334. *
  22335. * For example:
  22336. *
  22337. * Ext.create('Ext.chart.CartesianChart', {
  22338. * plugins: {
  22339. * chartitemevents: {
  22340. * moveEvents: true
  22341. * }
  22342. * },
  22343. * store: {
  22344. * fields: ['pet', 'households', 'total'],
  22345. * data: [
  22346. * {pet: 'Cats', households: 38, total: 93},
  22347. * {pet: 'Dogs', households: 45, total: 79},
  22348. * {pet: 'Fish', households: 13, total: 171}
  22349. * ]
  22350. * },
  22351. * axes: [{
  22352. * type: 'numeric',
  22353. * position: 'left'
  22354. * }, {
  22355. * type: 'category',
  22356. * position: 'bottom'
  22357. * }],
  22358. * series: [{
  22359. * type: 'bar',
  22360. * xField: 'pet',
  22361. * yField: 'households',
  22362. * listeners: {
  22363. * itemmousemove: function (series, item, event) {
  22364. * console.log('itemmousemove', item.category, item.field);
  22365. * }
  22366. * }
  22367. * }, {
  22368. * type: 'line',
  22369. * xField: 'pet',
  22370. * yField: 'total',
  22371. * marker: true
  22372. * }],
  22373. * listeners: { // Listen to itemclick events on all series.
  22374. * itemclick: function (chart, item, event) {
  22375. * console.log('itemclick', item.category, item.field);
  22376. * }
  22377. * }
  22378. * });
  22379. *
  22380. * Important! It's generally a poor design choice to put interactive charts
  22381. * inside scrollable views, in such cases it's not possible to tell
  22382. * which component should respond to the interaction.
  22383. * Since charts are typically interactive their default touch action config
  22384. * looks as follows: {@link Ext.draw.Container#touchAction}.
  22385. * If you do have a chart inside a scrollable view, even if it has no interactions,
  22386. * you have to set its `touchAction` config to the following:
  22387. *
  22388. * touchAction: {
  22389. * panX: true,
  22390. * panY: true
  22391. * }
  22392. *
  22393. * Otherwise, if a touch action started on a chart, a swipe will not scroll
  22394. * the view.
  22395. *
  22396. * For more information about the axes and series configurations please check
  22397. * the documentation of each series (Line, Bar, Pie, etc).
  22398. *
  22399. */
  22400. Ext.define('Ext.chart.AbstractChart', {
  22401. extend: 'Ext.draw.Container',
  22402. requires: [
  22403. 'Ext.chart.theme.Default',
  22404. 'Ext.chart.series.Series',
  22405. 'Ext.chart.interactions.Abstract',
  22406. 'Ext.chart.axis.Axis',
  22407. 'Ext.chart.Util',
  22408. 'Ext.data.StoreManager',
  22409. 'Ext.chart.legend.Legend',
  22410. 'Ext.chart.legend.SpriteLegend',
  22411. 'Ext.chart.Caption',
  22412. 'Ext.chart.legend.store.Store',
  22413. 'Ext.data.Store'
  22414. ],
  22415. isChart: true,
  22416. defaultBindProperty: 'store',
  22417. /**
  22418. * @event beforerefresh
  22419. * Fires before a refresh to the chart data is called. If the `beforerefresh`
  22420. * handler returns `false` the {@link #refresh} action will be canceled.
  22421. * @param {Ext.chart.AbstractChart} this
  22422. */
  22423. /**
  22424. * @event refresh
  22425. * Fires after the chart data has been refreshed.
  22426. * @param {Ext.chart.AbstractChart} this
  22427. */
  22428. /**
  22429. * @event redraw
  22430. * Fires after each {@link #event!redraw} call.
  22431. * @param {Ext.chart.AbstractChart} this
  22432. */
  22433. /**
  22434. * @private
  22435. * @event layout
  22436. * Fires after the final layout is done.
  22437. * (Two layouts may be required to fully render a chart.
  22438. * Typically for the initial render and every time thickness
  22439. * of the chart's axes changes.)
  22440. * @param {Ext.chart.AbstractChart} this
  22441. */
  22442. /**
  22443. * @event itemhighlight
  22444. * Fires when an item is highlighted.
  22445. * @param {Ext.chart.AbstractChart} this
  22446. * @param {Object} newItem The new highlight item.
  22447. * @param {Object} oldItem The old highlight item.
  22448. */
  22449. /**
  22450. * @event itemhighlightchange
  22451. * Fires when an item's highlight changes.
  22452. * @param this
  22453. * @param {Object} newItem The new highlight item.
  22454. * @param {Object} oldItem The old highlight item.
  22455. */
  22456. /**
  22457. * @event itemmousemove
  22458. * Fires when the mouse is moved on a series item.
  22459. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22460. * plugin be added to the chart.
  22461. * @param {Ext.chart.AbstractChart} chart
  22462. * @param {Object} item
  22463. * @param {Event} event
  22464. */
  22465. /**
  22466. * @event itemmouseup
  22467. * Fires when a mouseup event occurs on a series item.
  22468. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22469. * plugin be added to the chart.
  22470. * @param {Ext.chart.AbstractChart} chart
  22471. * @param {Object} item
  22472. * @param {Event} event
  22473. */
  22474. /**
  22475. * @event itemmousedown
  22476. * Fires when a mousedown event occurs on a series item.
  22477. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22478. * plugin be added to the chart.
  22479. * @param {Ext.chart.AbstractChart} chart
  22480. * @param {Object} item
  22481. * @param {Event} event
  22482. */
  22483. /**
  22484. * @event itemmouseover
  22485. * Fires when the mouse enters a series item.
  22486. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22487. * plugin be added to the chart.
  22488. * @param {Ext.chart.AbstractChart} chart
  22489. * @param {Object} item
  22490. * @param {Event} event
  22491. */
  22492. /**
  22493. * @event itemmouseout
  22494. * Fires when the mouse exits a series item.
  22495. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22496. * plugin be added to the chart.
  22497. * @param {Ext.chart.AbstractChart} chart
  22498. * @param {Object} item
  22499. * @param {Event} event
  22500. */
  22501. /**
  22502. * @event itemclick
  22503. * Fires when a click event occurs on a series item.
  22504. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22505. * plugin be added to the chart.
  22506. * @param {Ext.chart.AbstractChart} chart
  22507. * @param {Object} item
  22508. * @param {Event} event
  22509. */
  22510. /**
  22511. * @event itemdblclick
  22512. * Fires when a double click event occurs on a series item.
  22513. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22514. * plugin be added to the chart.
  22515. * @param {Ext.chart.AbstractChart} chart
  22516. * @param {Object} item
  22517. * @param {Event} event
  22518. */
  22519. /**
  22520. * @event itemtap
  22521. * Fires when a tap event occurs on a series item.
  22522. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22523. * plugin be added to the chart.
  22524. * @param {Ext.chart.AbstractChart} chart
  22525. * @param {Object} item
  22526. * @param {Event} event
  22527. */
  22528. /**
  22529. * @event storechange
  22530. * Fires when the store of the chart changes.
  22531. * @param {Ext.chart.AbstractChart} chart
  22532. * @param {Ext.data.Store} newStore
  22533. * @param {Ext.data.Store} oldStore
  22534. */
  22535. config: {
  22536. /**
  22537. * @cfg {Ext.data.Store/String/Object} store
  22538. * The data source to which the chart is bound.
  22539. * Acceptable values for this property are:
  22540. *
  22541. * - **any {@link Ext.data.Store Store} class / subclass**
  22542. * - **an {@link Ext.data.Store#storeId ID of a store}**
  22543. * - **a {@link Ext.data.Store Store} config object**. When passing a config you can
  22544. * specify the store type by alias. Passing a config object with a store type will
  22545. * dynamically create a new store of that type when the chart is instantiated.
  22546. *
  22547. * For example:
  22548. *
  22549. * Ext.define('MyApp.store.Customer', {
  22550. * extend: 'Ext.data.Store',
  22551. * alias: 'store.customerstore',
  22552. *
  22553. * fields: ['name', 'value']
  22554. * });
  22555. *
  22556. *
  22557. * Ext.create({
  22558. * xtype: 'cartesian',
  22559. * renderTo: document.body,
  22560. * height: 400,
  22561. * width: 400,
  22562. * store: {
  22563. * type: 'customerstore',
  22564. * data: [{
  22565. * name: 'metric one',
  22566. * value: 10
  22567. * }]
  22568. * },
  22569. * axes: [{
  22570. * type: 'numeric',
  22571. * position: 'left',
  22572. * title: {
  22573. * text: 'Sample Values',
  22574. * fontSize: 15
  22575. * },
  22576. * fields: 'value'
  22577. * }, {
  22578. * type: 'category',
  22579. * position: 'bottom',
  22580. * title: {
  22581. * text: 'Sample Values',
  22582. * fontSize: 15
  22583. * },
  22584. * fields: 'name'
  22585. * }],
  22586. * series: {
  22587. * type: 'bar',
  22588. * xField: 'name',
  22589. * yField: 'value'
  22590. * }
  22591. * });
  22592. */
  22593. store: 'ext-empty-store',
  22594. /**
  22595. * @cfg {String} [theme="default"]
  22596. * The name of the theme to be used. A theme defines the colors and styles
  22597. * used by the series, axes, markers and other chart components.
  22598. * Please see the documentation for the {@link Ext.chart.theme.Base} class
  22599. * for more information.
  22600. *
  22601. * Possible theme values are:
  22602. * - 'green', 'sky', 'red', 'purple', 'blue', 'yellow'
  22603. * - 'category1' to 'category6'
  22604. * - and the above theme names with the '-gradients' suffix, e.g. 'green-gradients'
  22605. *
  22606. * IMPORTANT: You should require the themes you use; for example, to use:
  22607. *
  22608. * theme: 'blue'
  22609. *
  22610. * the `Ext.chart.theme.Blue` class should be required:
  22611. *
  22612. * requires: 'Ext.chart.theme.Blue'
  22613. *
  22614. * To require all chart themes:
  22615. *
  22616. * requires: 'Ext.chart.theme.*'
  22617. */
  22618. theme: 'default',
  22619. /**
  22620. * Chart captions can be used to place titles, subtitles, credits and other captions
  22621. * inside a chart. For example:
  22622. *
  22623. * captions: {
  22624. * title: {
  22625. * text: 'Consumer Price Index'
  22626. * },
  22627. * subtitle: {
  22628. * text: 'from 2007 to 2017'
  22629. * },
  22630. * credits: {
  22631. * text: 'Source: 'bls.gov'
  22632. * }
  22633. * }
  22634. *
  22635. * One can use any names for properties in the `captions` config, but the `title`,
  22636. * `subtitle` and `credits` ones have a special meaning - they are automatically
  22637. * themeable. The `title` and `subtitle` are automatically docked to the top of
  22638. * a chart and the `credits` to the bottom. The `title` uses the largest and
  22639. * the heaviest font, while the `credits` - the smallest and the lightest.
  22640. *
  22641. * Other captions besides those three can be easily defined as well:
  22642. *
  22643. * captions: {
  22644. * myFancyCaption: {
  22645. * docked: 'bottom',
  22646. * align: 'left',
  22647. * style: {
  22648. * fontSize: 18,
  22649. * fontWeight: 'bold',
  22650. * fontFamily: 'Verdana'
  22651. * }
  22652. * }
  22653. * }
  22654. *
  22655. * If a caption config only specifies text, a shorthand syntax is also possible:
  22656. *
  22657. * captions: {
  22658. * title: 'Consumer Price Index'
  22659. * }
  22660. *
  22661. * @cfg {Object} captions
  22662. * @cfg {Ext.chart.Caption} captions.title
  22663. * @cfg {Ext.chart.Caption} captions.subtitle
  22664. * @cfg {Ext.chart.Caption} captions.credits
  22665. */
  22666. captions: null,
  22667. /**
  22668. * @cfg {Object} style
  22669. * The style for the chart component.
  22670. */
  22671. style: null,
  22672. /**
  22673. * @cfg {Boolean/Object} [animation=true]
  22674. * Defaults to `easeInOut` easing with a 500ms duration.
  22675. * See {@link Ext.draw.modifier.Animation} for possible configuration options.
  22676. */
  22677. animation: !Ext.isIE8,
  22678. /**
  22679. * @cfg {Ext.chart.series.Series/Array} series
  22680. * Array of {@link Ext.chart.series.Series Series} instances or config objects.
  22681. * For example:
  22682. *
  22683. * series: [{
  22684. * type: 'column',
  22685. * axis: 'left',
  22686. * listeners: {
  22687. * 'afterrender': function() {
  22688. * console.log('afterrender');
  22689. * }
  22690. * },
  22691. * xField: 'category',
  22692. * yField: 'data1'
  22693. * }]
  22694. */
  22695. series: [],
  22696. /**
  22697. * @cfg {Ext.chart.axis.Axis/Array/Object} axes
  22698. * Array of {@link Ext.chart.axis.Axis Axis} instances or config objects.
  22699. * For example:
  22700. *
  22701. * axes: [{
  22702. * type: 'numeric',
  22703. * position: 'left',
  22704. * title: 'Number of Hits',
  22705. * minimum: 0
  22706. * }, {
  22707. * type: 'category',
  22708. * position: 'bottom',
  22709. * title: 'Month of the Year'
  22710. * }]
  22711. */
  22712. axes: [],
  22713. /**
  22714. * @cfg {Ext.chart.legend.Legend/Ext.chart.legend.SpriteLegend/Boolean} legend
  22715. * The legend config for the chart. If specified, a legend block will be shown
  22716. * next to the chart.
  22717. * Each legend item displays the {@link Ext.chart.series.Series#title title}
  22718. * of the series, the color of the series and allows to toggle the visibility
  22719. * of the series (at least one series should remain visible).
  22720. *
  22721. * Sencha Charts support two types of legends: sprite based and DOM based.
  22722. *
  22723. * The sprite based legend can be shown in chart {@link Ext.draw.Container#preview preview}
  22724. * and is a part of the downloaded {@link Ext.draw.Container#download chart image}.
  22725. * The sprite based legend is always displayed in full and takes as much space as necessary,
  22726. * the legend items are split into columns to use the available space efficiently.
  22727. * The sprite based legend is styled via a {@link Ext.chart.theme.Base chart theme}.
  22728. *
  22729. * The DOM based legend supports RTL.
  22730. * It occupies a fixed width or height and scrolls when the content overflows.
  22731. * The DOM based legend is styled via CSS rules.
  22732. *
  22733. * By default the sprite legend is used. The type can be explicitly specified:
  22734. *
  22735. * legend: {
  22736. * type: 'dom', // 'sprite' is another possible value
  22737. * docked: 'top'
  22738. * }
  22739. *
  22740. * If the legend config is set to `true`, the sprite legend will be used
  22741. * docked to the bottom.
  22742. */
  22743. legend: null,
  22744. /**
  22745. * @cfg {Array} colors
  22746. * Array of colors/gradients to override the color of items and legends.
  22747. */
  22748. colors: null,
  22749. /**
  22750. * @cfg {Object/Number/String} insetPadding
  22751. * The amount of inset padding in pixels for the chart.
  22752. * Inset padding is the padding from the boundary of the chart to any
  22753. * of its contents.
  22754. */
  22755. insetPadding: {
  22756. top: 10,
  22757. left: 10,
  22758. right: 10,
  22759. bottom: 10
  22760. },
  22761. /**
  22762. * @cfg {Object} background Set the chart background.
  22763. * This can be a gradient object, image, or color.
  22764. *
  22765. * For example, if `background` were to be a color we could set the object as
  22766. *
  22767. * background: '#ccc'
  22768. *
  22769. * You can specify an image by using:
  22770. *
  22771. * background: {
  22772. * type: 'image',
  22773. * src: 'http://path.to.image/'
  22774. * }
  22775. *
  22776. * Also you can specify a gradient by using the gradient object syntax:
  22777. *
  22778. * background: {
  22779. * type: 'linear',
  22780. * degrees: 0,
  22781. * stops: [
  22782. * {
  22783. * offset: 0,
  22784. * color: 'white'
  22785. * },
  22786. * {
  22787. * offset: 1,
  22788. * color: 'blue'
  22789. * }
  22790. * ]
  22791. * }
  22792. */
  22793. background: null,
  22794. /**
  22795. * @cfg {Array} interactions
  22796. * Interactions are optional modules that can be plugged in to a chart
  22797. * to allow the user to interact with the chart and its data in special ways.
  22798. * The `interactions` config takes an Array of Object configurations,
  22799. * each one corresponding to a particular interaction class identified
  22800. * by a `type` property:
  22801. *
  22802. * new Ext.chart.AbstractChart({
  22803. * renderTo: Ext.getBody(),
  22804. * width: 800,
  22805. * height: 600,
  22806. * store: store1,
  22807. * axes: [
  22808. * // ...some axes options...
  22809. * ],
  22810. * series: [
  22811. * // ...some series options...
  22812. * ],
  22813. * interactions: [{
  22814. * type: 'interactiontype'
  22815. * // ...additional configs for the interaction...
  22816. * }]
  22817. * });
  22818. *
  22819. * When adding an interaction which uses only its default configuration
  22820. * (no extra properties other than `type`), you can alternately specify
  22821. * only the type as a String rather than the full Object:
  22822. *
  22823. * interactions: ['reset', 'rotate']
  22824. *
  22825. * The current supported interaction types include:
  22826. *
  22827. * - {@link Ext.chart.interactions.PanZoom panzoom} - allows pan and zoom of axes
  22828. * - {@link Ext.chart.interactions.ItemHighlight itemhighlight} - allows highlighting of series data points
  22829. * - {@link Ext.chart.interactions.ItemInfo iteminfo} - allows displaying details of a data point in a popup panel
  22830. * - {@link Ext.chart.interactions.Rotate rotate} - allows rotation of pie and radar series
  22831. *
  22832. * See the documentation for each of those interaction classes to see how they can be configured.
  22833. *
  22834. * Additional custom interactions can be registered using `'interactions.'` alias prefix.
  22835. */
  22836. interactions: [],
  22837. /**
  22838. * @private
  22839. * The main area of the chart where grid and series are drawn.
  22840. */
  22841. mainRect: null,
  22842. /**
  22843. * @private
  22844. * Override value.
  22845. */
  22846. resizeHandler: null,
  22847. /**
  22848. * @cfg {Object} highlightItem
  22849. * The current highlight item in the chart.
  22850. * The object must be the one that you get from item events.
  22851. *
  22852. * Note that series can also own highlight items.
  22853. * This notion is separate from this one and should not be used at the same time.
  22854. */
  22855. highlightItem: null,
  22856. surfaceZIndexes: {
  22857. background: 0,
  22858. // Contains the backround 'rect' sprite.
  22859. main: 1,
  22860. // Contains grid lines and CrossZoom overlay 'rect' sprite.
  22861. grid: 2,
  22862. // Reserved.
  22863. series: 3,
  22864. // Contains series sprites.
  22865. axis: 4,
  22866. // No actual `axis` surface is created, but this zIndex is used
  22867. // for all axis surfaces (one surface is created per axis).
  22868. chart: 5,
  22869. // Covers whole chart, minus the legend area.
  22870. // Contains sprites defined in the `sprites` config,
  22871. // title, subtitle and credits.
  22872. caption: 6,
  22873. // Contains title, subtitle and credits sprites.
  22874. overlay: 7,
  22875. // This surface will typically contain chart labels
  22876. // and interaction sprites like crosshair lines.
  22877. // With cartesian charts, equivalent in size to the `series` surface.
  22878. // With polar charts, equivalent in size to the `chart` surface.
  22879. legend: 8
  22880. }
  22881. },
  22882. // `SpriteLegend` surface.
  22883. /**
  22884. * @private
  22885. */
  22886. legendStore: null,
  22887. /**
  22888. * When this is non-zero, changes to sprite attributes apply instantly.
  22889. * See {@link #getAnimation}.
  22890. * @private
  22891. */
  22892. animationSuspendCount: 0,
  22893. /**
  22894. * @private
  22895. */
  22896. chartLayoutSuspendCount: 0,
  22897. /**
  22898. * @private
  22899. */
  22900. chartLayoutCount: 0,
  22901. /**
  22902. * @private
  22903. */
  22904. scheduledLayoutId: null,
  22905. /**
  22906. * @private
  22907. */
  22908. axisThicknessSuspendCount: 0,
  22909. /**
  22910. * @private
  22911. * Indicates that thickness of one or more axes has changed,
  22912. * at the time of {@link #performLayout} call. I.e. 'performLayout'
  22913. * should be called again when current layout is done.
  22914. */
  22915. isThicknessChanged: false,
  22916. constructor: function(config) {
  22917. var me = this;
  22918. me.itemListeners = {};
  22919. me.surfaceMap = {};
  22920. me.chartComponents = {};
  22921. me.isInitializing = true;
  22922. me.suspendChartLayout();
  22923. me.animationSuspendCount++;
  22924. me.callParent(arguments);
  22925. me.isInitializing = false;
  22926. me.getSurface('main');
  22927. me.getSurface('chart').setFlipRtlText(me.getInherited().rtl);
  22928. me.getSurface('overlay').waitFor(me.getSurface('series'));
  22929. me.animationSuspendCount--;
  22930. me.resumeChartLayout();
  22931. },
  22932. applyAnimation: function(animation, oldAnimation) {
  22933. return Ext.chart.Util.applyAnimation(animation, oldAnimation);
  22934. },
  22935. updateAnimation: function() {
  22936. if (this.isConfiguring) {
  22937. return;
  22938. }
  22939. var seriesList = this.getSeries(),
  22940. ln = seriesList.length,
  22941. i, series;
  22942. this.isSettingSeriesAnimation = true;
  22943. for (i = 0; i < ln; i++) {
  22944. series = seriesList[i];
  22945. // Don't update the series animation config, if it was set by
  22946. // a user, unless 'suspendAnimation' was called.
  22947. if (!series.isUserAnimation || this.animationSuspendCount) {
  22948. series.setAnimation(series.getAnimation());
  22949. }
  22950. }
  22951. this.isSettingSeriesAnimation = false;
  22952. },
  22953. getAnimation: function() {
  22954. var result;
  22955. if (this.animationSuspendCount) {
  22956. result = {
  22957. duration: 0
  22958. };
  22959. } else {
  22960. result = this.callParent();
  22961. }
  22962. return result;
  22963. },
  22964. suspendAnimation: function() {
  22965. this.animationSuspendCount++;
  22966. if (this.animationSuspendCount === 1) {
  22967. this.updateAnimation();
  22968. }
  22969. },
  22970. resumeAnimation: function() {
  22971. this.animationSuspendCount--;
  22972. if (this.animationSuspendCount === 0) {
  22973. this.updateAnimation();
  22974. }
  22975. },
  22976. applyInsetPadding: function(padding, oldPadding) {
  22977. var result;
  22978. if (!Ext.isObject(padding)) {
  22979. result = Ext.util.Format.parseBox(padding);
  22980. } else if (!oldPadding) {
  22981. result = padding;
  22982. } else {
  22983. result = Ext.apply(oldPadding, padding);
  22984. }
  22985. return result;
  22986. },
  22987. /**
  22988. * Suspends chart's layout.
  22989. */
  22990. suspendChartLayout: function() {
  22991. var me = this;
  22992. me.chartLayoutSuspendCount++;
  22993. if (me.chartLayoutSuspendCount === 1) {
  22994. if (me.scheduledLayoutId) {
  22995. me.layoutInSuspension = true;
  22996. me.cancelChartLayout();
  22997. } else {
  22998. me.layoutInSuspension = false;
  22999. }
  23000. }
  23001. },
  23002. /**
  23003. * Decrements chart's layout suspend count.
  23004. * When the suspend count is decremented to zero,
  23005. * a layout is scheduled.
  23006. */
  23007. resumeChartLayout: function() {
  23008. var me = this;
  23009. me.chartLayoutSuspendCount--;
  23010. if (me.chartLayoutSuspendCount === 0) {
  23011. if (me.layoutInSuspension) {
  23012. me.scheduleLayout();
  23013. }
  23014. }
  23015. },
  23016. /**
  23017. * Cancel a scheduled layout.
  23018. */
  23019. cancelChartLayout: function() {
  23020. if (this.scheduledLayoutId) {
  23021. Ext.draw.Animator.cancel(this.scheduledLayoutId);
  23022. this.scheduledLayoutId = null;
  23023. this.checkLayoutEnd();
  23024. }
  23025. },
  23026. /**
  23027. * Schedule a layout at next frame.
  23028. */
  23029. scheduleLayout: function() {
  23030. var me = this;
  23031. if (me.allowSchedule() && !me.scheduledLayoutId) {
  23032. me.scheduledLayoutId = Ext.draw.Animator.schedule('doScheduleLayout', me);
  23033. }
  23034. },
  23035. allowSchedule: function() {
  23036. return true;
  23037. },
  23038. doScheduleLayout: function() {
  23039. var me = this;
  23040. me.scheduledLayoutId = null;
  23041. if (me.chartLayoutSuspendCount) {
  23042. me.layoutInSuspension = true;
  23043. } else {
  23044. me.performLayout();
  23045. }
  23046. },
  23047. /**
  23048. * Prevent axes from triggering chart layout when their thickness changes.
  23049. * E.g. during an interaction that makes changes to the axes,
  23050. * or when chart layout was triggered by something else,
  23051. * for example a chart resize event.
  23052. */
  23053. suspendThicknessChanged: function() {
  23054. this.axisThicknessSuspendCount++;
  23055. },
  23056. /**
  23057. * Decrements axis thickness suspend count.
  23058. * When axis thickness suspend count is decremented to zero,
  23059. * chart layout is performed.
  23060. */
  23061. resumeThicknessChanged: function() {
  23062. if (this.axisThicknessSuspendCount > 0) {
  23063. this.axisThicknessSuspendCount--;
  23064. if (this.axisThicknessSuspendCount === 0 && this.isThicknessChanged) {
  23065. this.onThicknessChanged();
  23066. }
  23067. }
  23068. },
  23069. onThicknessChanged: function() {
  23070. if (this.axisThicknessSuspendCount === 0) {
  23071. this.isThicknessChanged = false;
  23072. this.performLayout();
  23073. } else {
  23074. this.isThicknessChanged = true;
  23075. }
  23076. },
  23077. applySprites: function(sprites) {
  23078. var surface = this.getSurface('chart');
  23079. sprites = Ext.Array.from(sprites);
  23080. surface.removeAll(true);
  23081. surface.add(sprites);
  23082. return sprites;
  23083. },
  23084. initItems: function() {
  23085. var items = this.items,
  23086. i, ln, item;
  23087. if (items && !items.isMixedCollection) {
  23088. this.items = [];
  23089. items = Ext.Array.from(items);
  23090. for (i = 0 , ln = items.length; i < ln; i++) {
  23091. item = items[i];
  23092. if (item.type) {
  23093. Ext.raise("To add custom sprites to the chart use the 'sprites' config.");
  23094. } else {
  23095. this.items.push(item);
  23096. }
  23097. }
  23098. }
  23099. // @noOptimize.callParent
  23100. this.callParent();
  23101. },
  23102. // noOptimize is needed because in the ext build we have a parent method to call,
  23103. // but in touch we do not so we need to suppress the cmd warning during optimized build
  23104. applyBackground: function(newBackground, oldBackground) {
  23105. var surface = this.getSurface('background');
  23106. return this.refreshBackground(surface, newBackground, oldBackground);
  23107. },
  23108. /**
  23109. * @private
  23110. * The background updater. Used by both the chart and the sprite legend.
  23111. * @param surface The surface to put the background in.
  23112. * @param newBackground
  23113. * @param oldBackground
  23114. * @return {Ext.draw.sprite.Rect/Ext.draw.sprite.Sprite}
  23115. */
  23116. refreshBackground: function(surface, newBackground, oldBackground) {
  23117. var width, height, isUpdateOld;
  23118. if (newBackground) {
  23119. if (oldBackground) {
  23120. width = oldBackground.attr.width;
  23121. height = oldBackground.attr.height;
  23122. isUpdateOld = oldBackground.type === (newBackground.type || 'rect');
  23123. }
  23124. if (newBackground.isSprite) {
  23125. oldBackground = newBackground;
  23126. } else if (newBackground.type === 'image' && Ext.isString(newBackground.src)) {
  23127. if (isUpdateOld) {
  23128. oldBackground.setAttributes({
  23129. src: newBackground.src
  23130. });
  23131. } else {
  23132. surface.remove(oldBackground, true);
  23133. oldBackground = surface.add(newBackground);
  23134. }
  23135. } else {
  23136. if (isUpdateOld) {
  23137. oldBackground.setAttributes({
  23138. fillStyle: newBackground
  23139. });
  23140. } else {
  23141. surface.remove(oldBackground, true);
  23142. oldBackground = surface.add({
  23143. type: 'rect',
  23144. fillStyle: newBackground,
  23145. animation: {
  23146. customDurations: {
  23147. x: 0,
  23148. y: 0,
  23149. width: 0,
  23150. height: 0
  23151. }
  23152. }
  23153. });
  23154. }
  23155. }
  23156. }
  23157. if (width && height) {
  23158. oldBackground.setAttributes({
  23159. width: width,
  23160. height: height
  23161. });
  23162. }
  23163. oldBackground.setAnimation(this.getAnimation());
  23164. return oldBackground;
  23165. },
  23166. defaultResizeHandler: function(size) {
  23167. this.scheduleLayout();
  23168. return false;
  23169. },
  23170. applyMainRect: function(newRect, rect) {
  23171. if (!rect) {
  23172. return newRect;
  23173. }
  23174. this.getSeries();
  23175. this.getAxes();
  23176. if (newRect[0] === rect[0] && newRect[1] === rect[1] && newRect[2] === rect[2] && newRect[3] === rect[3]) {
  23177. return rect;
  23178. } else {
  23179. return newRect;
  23180. }
  23181. },
  23182. register: function(component) {
  23183. var map = this.chartComponents,
  23184. id = component.getId();
  23185. //<debug>
  23186. if (id === undefined) {
  23187. Ext.raise('Chart component id is undefined. ' + 'Please ensure the component has an id.');
  23188. }
  23189. if (id in map) {
  23190. Ext.raise('Registering duplicate chart component id "' + id + '"');
  23191. }
  23192. //</debug>
  23193. map[id] = component;
  23194. },
  23195. unregister: function(component) {
  23196. var map = this.chartComponents,
  23197. id = component.getId();
  23198. delete map[id];
  23199. },
  23200. get: function(id) {
  23201. return this.chartComponents[id];
  23202. },
  23203. /**
  23204. * @method getAxis Returns an axis instance based on the type of data passed.
  23205. * @param {String/Number/Ext.chart.axis.Axis} axis You may request an axis by passing
  23206. * an id, the number of the array key returned by {@link #getAxes}, or an axis instance.
  23207. * @return {Ext.chart.axis.Axis} The axis requested.
  23208. */
  23209. getAxis: function(axis) {
  23210. if (axis instanceof Ext.chart.axis.Axis) {
  23211. return axis;
  23212. } else if (Ext.isNumber(axis)) {
  23213. return this.getAxes()[axis];
  23214. } else if (Ext.isString(axis)) {
  23215. return this.get(axis);
  23216. }
  23217. },
  23218. getSurface: function(id, type) {
  23219. id = id || 'main';
  23220. type = type || id;
  23221. var me = this,
  23222. surface = this.callParent([
  23223. id,
  23224. type
  23225. ]),
  23226. map = me.surfaceMap;
  23227. if (!map[type]) {
  23228. map[type] = [];
  23229. }
  23230. if (Ext.Array.indexOf(map[type], surface) < 0) {
  23231. surface.type = type;
  23232. map[type].push(surface);
  23233. surface.on('destroy', me.forgetSurface, me);
  23234. }
  23235. return surface;
  23236. },
  23237. forgetSurface: function(surface) {
  23238. var map = this.surfaceMap;
  23239. if (!map || this.destroying) {
  23240. return;
  23241. }
  23242. var group = map[surface.type],
  23243. index = group ? Ext.Array.indexOf(group, surface) : -1;
  23244. if (index >= 0) {
  23245. group.splice(index, 1);
  23246. }
  23247. },
  23248. applyAxes: function(newAxes, oldAxes) {
  23249. var me = this,
  23250. positions = {
  23251. left: 'right',
  23252. right: 'left'
  23253. },
  23254. result = [],
  23255. axis, oldAxis, linkedTo, id, i, j, ln, oldMap, series;
  23256. me.animationSuspendCount++;
  23257. me.getStore();
  23258. if (!oldAxes) {
  23259. oldAxes = [];
  23260. oldAxes.map = {};
  23261. }
  23262. oldMap = oldAxes.map;
  23263. result.map = {};
  23264. newAxes = Ext.Array.from(newAxes, true);
  23265. for (i = 0 , ln = newAxes.length; i < ln; i++) {
  23266. axis = newAxes[i];
  23267. if (!axis) {
  23268. continue;
  23269. }
  23270. if (axis instanceof Ext.chart.axis.Axis) {
  23271. oldAxis = oldMap[axis.getId()];
  23272. axis.setChart(me);
  23273. } else {
  23274. axis = Ext.Object.chain(axis);
  23275. linkedTo = axis.linkedTo;
  23276. id = axis.id;
  23277. if (Ext.isNumber(linkedTo)) {
  23278. axis = Ext.merge({}, newAxes[linkedTo], axis);
  23279. } else if (Ext.isString(linkedTo)) {
  23280. Ext.Array.each(newAxes, function(item) {
  23281. if (item.id === axis.linkedTo) {
  23282. axis = Ext.merge({}, item, axis);
  23283. return false;
  23284. }
  23285. });
  23286. }
  23287. axis.id = id;
  23288. axis.chart = me;
  23289. if (me.getInherited().rtl) {
  23290. axis.position = positions[axis.position] || axis.position;
  23291. }
  23292. id = axis.getId && axis.getId() || axis.id;
  23293. axis = Ext.factory(axis, null, oldAxis = oldMap[id], 'axis');
  23294. }
  23295. if (axis) {
  23296. result.push(axis);
  23297. result.map[axis.getId()] = axis;
  23298. }
  23299. }
  23300. me.axesChangeSeries = {};
  23301. for (i in oldMap) {
  23302. if (!result.map[i]) {
  23303. oldAxis = oldMap[i];
  23304. if (oldAxis && !oldAxis.destroyed) {
  23305. // At this point the series still have their `xAxis` and `yAxis` configs
  23306. // set to old axes. We need to update such series with new matching axes
  23307. // by calling their `onAxesChange` method.
  23308. for (j = 0 , ln = oldAxis.boundSeries.length; j < ln; j++) {
  23309. series = oldAxis.boundSeries[j];
  23310. me.axesChangeSeries[series.getId()] = series;
  23311. }
  23312. oldAxis.destroy();
  23313. }
  23314. }
  23315. }
  23316. me.animationSuspendCount--;
  23317. return result;
  23318. },
  23319. updateAxes: function(axes) {
  23320. var me = this,
  23321. seriesMap = me.axesChangeSeries,
  23322. series, id, i, ln, axis;
  23323. for (id in seriesMap) {
  23324. series = seriesMap[id];
  23325. // `true` to force set series' axes, even if they are already set
  23326. // (in this case to old axes that were just destroyed in the `axes` applier).
  23327. series.onAxesChange(me, true);
  23328. }
  23329. // If changes to the `axes` config are made post chart creation, without making any
  23330. // changes to the series afterwards, we need to figure out the new axes' `boundSeries`
  23331. // manually, as the 'serieschange' event won't be fired in this case.
  23332. for (i = 0 , ln = axes.length; i < ln; i++) {
  23333. axis = axes[i];
  23334. axis.onSeriesChange(me);
  23335. }
  23336. if (!me.isConfiguring && !me.destroying) {
  23337. me.scheduleLayout();
  23338. }
  23339. },
  23340. circularCopyArray: function(inArray, startIndex, count) {
  23341. var outArray = [],
  23342. i,
  23343. len = inArray && inArray.length;
  23344. if (len) {
  23345. for (i = 0; i < count; i++) {
  23346. outArray.push(inArray[(startIndex + i) % len]);
  23347. }
  23348. }
  23349. return outArray;
  23350. },
  23351. circularCopyObject: function(inObject, startIndex, count) {
  23352. var me = this,
  23353. name, value,
  23354. outObject = {};
  23355. if (count) {
  23356. for (name in inObject) {
  23357. if (inObject.hasOwnProperty(name)) {
  23358. value = inObject[name];
  23359. if (Ext.isArray(value)) {
  23360. outObject[name] = me.circularCopyArray(value, startIndex, count);
  23361. } else {
  23362. outObject[name] = value;
  23363. }
  23364. }
  23365. }
  23366. }
  23367. return outObject;
  23368. },
  23369. getColors: function() {
  23370. var me = this,
  23371. configColors = me.config.colors,
  23372. theme = me.getTheme();
  23373. if (Ext.isArray(configColors) && configColors.length > 0) {
  23374. configColors = me.applyColors(configColors);
  23375. }
  23376. return configColors || (theme && theme.getColors());
  23377. },
  23378. applyColors: function(newColors) {
  23379. newColors = Ext.Array.map(newColors, function(color) {
  23380. if (Ext.isString(color)) {
  23381. return color;
  23382. } else {
  23383. return color.toString();
  23384. }
  23385. });
  23386. return newColors;
  23387. },
  23388. updateColors: function(newColors) {
  23389. var me = this,
  23390. theme = me.getTheme(),
  23391. colors = newColors || (theme && theme.getColors()),
  23392. colorIndex = 0,
  23393. series = me.getSeries(),
  23394. seriesCount = series && series.length,
  23395. i, seriesItem, seriesColors, seriesColorCount;
  23396. if (colors.length) {
  23397. for (i = 0; i < seriesCount; i++) {
  23398. seriesItem = series[i];
  23399. seriesColorCount = seriesItem.themeColorCount();
  23400. seriesColors = me.circularCopyArray(colors, colorIndex, seriesColorCount);
  23401. colorIndex += seriesColorCount;
  23402. seriesItem.updateChartColors(seriesColors);
  23403. }
  23404. }
  23405. if (!me.isConfiguring) {
  23406. me.refreshLegendStore();
  23407. }
  23408. },
  23409. applyTheme: function(theme) {
  23410. if (theme && theme.isTheme) {
  23411. return theme;
  23412. }
  23413. return Ext.Factory.chartTheme(theme);
  23414. },
  23415. updateGradients: function(gradients) {
  23416. if (!Ext.isEmpty(gradients)) {
  23417. this.updateTheme(this.getTheme());
  23418. }
  23419. },
  23420. updateTheme: function(theme, oldTheme) {
  23421. var me = this,
  23422. axes = me.getAxes(),
  23423. series = me.getSeries(),
  23424. colors = me.getColors(),
  23425. i;
  23426. if (!series) {
  23427. return;
  23428. }
  23429. me.updateChartTheme(theme);
  23430. for (i = 0; i < axes.length; i++) {
  23431. axes[i].updateTheme(theme);
  23432. }
  23433. for (i = 0; i < series.length; i++) {
  23434. series[i].setTheme(theme);
  23435. }
  23436. me.updateSpriteTheme(theme);
  23437. me.updateColors(colors);
  23438. // It may be necessary to perform a layout here.
  23439. // But instead of the 'chart.scheduleLayout' call, we can call
  23440. // 'chart.redraw'. If after the redraw call the thickness
  23441. // of any axis changes, this will automatically trigger
  23442. // chart layout (see Ext.chart.axis.sprite.Axis.doThicknessChanged).
  23443. // Otherwise, no layout is necessary.
  23444. me.redraw();
  23445. me.fireEvent('themechange', me, theme, oldTheme);
  23446. },
  23447. themeOnlyIfConfigured: {
  23448. captions: true
  23449. },
  23450. updateChartTheme: function(theme) {
  23451. var me = this,
  23452. chartTheme = theme.getChart(),
  23453. initialConfig = me.getInitialConfig(),
  23454. defaultConfig = me.defaultConfig,
  23455. configs = me.self.getConfigurator().configs,
  23456. genericChartTheme = chartTheme.defaults,
  23457. specificChartTheme = chartTheme[me.xtype],
  23458. themeOnlyIfConfigured = me.themeOnlyIfConfigured,
  23459. key, value, isObjValue, isUnusedConfig, initialValue, cfg;
  23460. chartTheme = Ext.merge({}, genericChartTheme, specificChartTheme);
  23461. for (key in chartTheme) {
  23462. value = chartTheme[key];
  23463. cfg = configs[key];
  23464. if (value !== null && value !== undefined && cfg) {
  23465. initialValue = initialConfig[key];
  23466. isObjValue = Ext.isObject(value);
  23467. isUnusedConfig = initialValue === defaultConfig[key];
  23468. if (isObjValue) {
  23469. if (isUnusedConfig && themeOnlyIfConfigured[key]) {
  23470. continue;
  23471. }
  23472. value = Ext.merge({}, value, initialValue);
  23473. }
  23474. if (isUnusedConfig || isObjValue) {
  23475. me[cfg.names.set](value);
  23476. }
  23477. }
  23478. }
  23479. },
  23480. updateSpriteTheme: function(theme) {
  23481. this.getSprites();
  23482. var me = this,
  23483. chartSurface = me.getSurface('chart'),
  23484. sprites = chartSurface.getItems(),
  23485. styles = theme.getSprites(),
  23486. sprite, style, key, attr, isText, i, ln;
  23487. for (i = 0 , ln = sprites.length; i < ln; i++) {
  23488. sprite = sprites[i];
  23489. style = styles[sprite.type];
  23490. if (style) {
  23491. attr = {};
  23492. isText = sprite.type === 'text';
  23493. for (key in style) {
  23494. if (!(key in sprite.config)) {
  23495. // Setting individual font attributes will take over the 'font' shorthand
  23496. // attribute, but this behavior is undesireable for theming.
  23497. if (!(isText && key.indexOf('font') === 0 && sprite.config.font)) {
  23498. attr[key] = style[key];
  23499. }
  23500. }
  23501. }
  23502. sprite.setAttributes(attr);
  23503. }
  23504. }
  23505. },
  23506. /**
  23507. * Adds a {@link Ext.chart.series.Series Series} to this chart.
  23508. *
  23509. * The Series (or array) passed will be added to the existing series. If an `id` is specified
  23510. * in a new Series, any existing Series of that `id` will be updated.
  23511. *
  23512. * The chart will be redrawn in response to the change.
  23513. *
  23514. * @param {Object/Object[]/Ext.chart.series.Series/Ext.chart.series.Series[]} newSeries A config object
  23515. * describing the Series to add, or an instantiated Series object. Or an array of these.
  23516. */
  23517. addSeries: function(newSeries) {
  23518. var series = this.getSeries();
  23519. series = series.concat(Ext.Array.from(newSeries));
  23520. this.setSeries(series);
  23521. },
  23522. /**
  23523. * Remove a {@link Ext.chart.series.Series Series} from this chart.
  23524. * The Series (or array) passed will be removed from the existing series.
  23525. *
  23526. * The chart will be redrawn in response to the change.
  23527. *
  23528. * @param {Ext.chart.series.Series/String} series The Series or the `id` of the Series to remove. May be an array.
  23529. */
  23530. removeSeries: function(series) {
  23531. series = Ext.Array.from(series);
  23532. var existingSeries = this.getSeries(),
  23533. newSeries = [],
  23534. len = series.length,
  23535. removeMap = {},
  23536. i, s;
  23537. // Build a map of the Series IDs that are to be removed
  23538. for (i = 0; i < len; i++) {
  23539. s = series[i];
  23540. // If they passed a Series Object
  23541. if (typeof s !== 'string') {
  23542. s = s.getId();
  23543. }
  23544. removeMap[s] = true;
  23545. }
  23546. // Build a new Series array that excludes those Series scheduled for removal
  23547. for (i = 0 , len = existingSeries.length; i < len; i++) {
  23548. if (!removeMap[existingSeries[i].getId()]) {
  23549. newSeries.push(existingSeries[i]);
  23550. }
  23551. }
  23552. this.setSeries(newSeries);
  23553. },
  23554. applySeries: function(newSeries, oldSeries) {
  23555. var me = this,
  23556. result = [],
  23557. oldMap, oldSeriesItem, i, ln, series;
  23558. me.animationSuspendCount++;
  23559. me.getAxes();
  23560. if (oldSeries) {
  23561. oldMap = oldSeries.map;
  23562. } else {
  23563. oldSeries = [];
  23564. oldMap = oldSeries.map = {};
  23565. }
  23566. result.map = {};
  23567. newSeries = Ext.Array.from(newSeries, true);
  23568. for (i = 0 , ln = newSeries.length; i < ln; i++) {
  23569. series = newSeries[i];
  23570. if (!series) {
  23571. continue;
  23572. }
  23573. oldSeriesItem = oldMap[series.getId && series.getId() || series.id];
  23574. // New Series instance passed in
  23575. if (series instanceof Ext.chart.series.Series) {
  23576. // Replacing
  23577. if (oldSeriesItem && oldSeriesItem !== series) {
  23578. oldSeriesItem.destroy();
  23579. }
  23580. series.setChart(me);
  23581. }
  23582. // Series config object passed in
  23583. else if (Ext.isObject(series)) {
  23584. // Config object matched an existing Series item by id;
  23585. // update its configuration
  23586. if (oldSeriesItem) {
  23587. oldSeriesItem.setConfig(series);
  23588. series = oldSeriesItem;
  23589. } else // Create a new Series
  23590. {
  23591. if (Ext.isString(series)) {
  23592. series = {
  23593. type: series
  23594. };
  23595. }
  23596. series.chart = me;
  23597. series = Ext.create(series.xclass || ('series.' + series.type), series);
  23598. }
  23599. }
  23600. result.push(series);
  23601. result.map[series.getId()] = series;
  23602. }
  23603. for (i in oldMap) {
  23604. if (!result.map[oldMap[i].id]) {
  23605. oldMap[i].destroy();
  23606. }
  23607. }
  23608. me.animationSuspendCount--;
  23609. return result;
  23610. },
  23611. updateSeries: function(newSeries, oldSeries) {
  23612. var me = this;
  23613. if (me.destroying) {
  23614. return;
  23615. }
  23616. me.animationSuspendCount++;
  23617. me.fireEvent('serieschange', me, newSeries, oldSeries);
  23618. if (!Ext.isEmpty(newSeries)) {
  23619. me.updateTheme(me.getTheme());
  23620. }
  23621. me.refreshLegendStore();
  23622. if (!me.isConfiguring && !me.destroying) {
  23623. me.scheduleLayout();
  23624. }
  23625. me.animationSuspendCount--;
  23626. },
  23627. defaultLegendType: 'sprite',
  23628. applyLegend: function(legend, oldLegend) {
  23629. var me = this,
  23630. result = null,
  23631. alias;
  23632. if (oldLegend && !(oldLegend.destroyed || oldLegend.destroying)) {
  23633. if (me.legendStoreListeners) {
  23634. me.legendStoreListeners.destroy();
  23635. }
  23636. if (me.legendStore) {
  23637. me.legendStore.destroy();
  23638. }
  23639. oldLegend.destroy();
  23640. }
  23641. if (legend) {
  23642. if (Ext.isBoolean(legend)) {
  23643. result = Ext.create('legend.' + me.defaultLegendType, {
  23644. docked: 'bottom',
  23645. chart: me
  23646. });
  23647. } else {
  23648. legend.docked = legend.docked || 'bottom';
  23649. legend.chart = me;
  23650. alias = 'legend.' + (legend.type || me.defaultLegendType);
  23651. result = Ext.create(alias, legend);
  23652. }
  23653. }
  23654. return result;
  23655. },
  23656. updateLegend: function(legend) {
  23657. var me = this;
  23658. // Probably has been already destroyed with the old legend,
  23659. // but making sure.
  23660. me.destroyLegendStore();
  23661. if (legend) {
  23662. me.getItems();
  23663. legend.setStore(me.refreshLegendStore());
  23664. }
  23665. if (!me.isConfiguring) {
  23666. me.scheduleLayout();
  23667. }
  23668. },
  23669. captionApplier: function(caption, oldCaption) {
  23670. var me = this,
  23671. result;
  23672. if (oldCaption && !(oldCaption.destroyed || oldCaption.destroying)) {
  23673. oldCaption.destroy();
  23674. }
  23675. if (caption) {
  23676. caption.chart = me;
  23677. result = new Ext.chart.Caption(caption);
  23678. }
  23679. return result;
  23680. },
  23681. applyCaptions: function(captions, oldCaptions) {
  23682. var map = {},
  23683. caption, oldCaption, name, any;
  23684. for (name in captions) {
  23685. caption = captions[name];
  23686. if (caption && !caption.length && !(caption.text && caption.text.length)) {
  23687. caption = null;
  23688. } else if (typeof caption === 'string') {
  23689. caption = {
  23690. text: caption
  23691. };
  23692. // Initial config is used for proper theming (see `updateChartTheme`)
  23693. // and config merging, however, mergin won't work as expected, if
  23694. // the initial config value remains a string, so we modify it here.
  23695. this.getInitialConfig().captions[name] = caption;
  23696. }
  23697. oldCaption = oldCaptions && oldCaptions[name];
  23698. caption = this.captionApplier(caption, oldCaption);
  23699. if (caption) {
  23700. any = true;
  23701. map[name] = caption;
  23702. }
  23703. }
  23704. return any && map;
  23705. },
  23706. updateCaptions: function() {
  23707. var me = this;
  23708. if (!me.isConfiguring) {
  23709. me.scheduleLayout();
  23710. }
  23711. },
  23712. /**
  23713. * Return the legend store that contains all the legend information.
  23714. * This information is collected from all the series.
  23715. * @return {Ext.chart.legend.store.Store}
  23716. */
  23717. getLegendStore: function() {
  23718. var me = this,
  23719. store = me.legendStore;
  23720. if (!store) {
  23721. store = me.legendStore = new Ext.chart.legend.store.Store({
  23722. chart: me
  23723. });
  23724. me.legendStoreListeners = store.on({
  23725. scope: me,
  23726. update: 'onLegendStoreUpdate',
  23727. destroyable: true
  23728. });
  23729. }
  23730. return store;
  23731. },
  23732. destroyLegendStore: function() {
  23733. var store = this.legendStore;
  23734. if (store && !(store.destroyed || store.destroying)) {
  23735. store.destroy();
  23736. }
  23737. this.legendStore = null;
  23738. },
  23739. refreshLegendStore: function() {
  23740. var me = this,
  23741. legendStore = me.getLegendStore(),
  23742. series;
  23743. if (legendStore) {
  23744. var seriesList = me.getSeries(),
  23745. ln = seriesList.length,
  23746. legendData = [],
  23747. i = 0;
  23748. for (; i < ln; i++) {
  23749. series = seriesList[i];
  23750. if (series.getShowInLegend()) {
  23751. series.provideLegendInfo(legendData);
  23752. }
  23753. }
  23754. legendStore.setData(legendData);
  23755. }
  23756. return legendStore;
  23757. },
  23758. onLegendStoreUpdate: function(store, record) {
  23759. var me = this,
  23760. series;
  23761. if (record) {
  23762. series = this.getSeries().map[record.get('series')];
  23763. if (series) {
  23764. series.setHiddenByIndex(record.get('index'), record.get('disabled'));
  23765. me.redraw();
  23766. }
  23767. }
  23768. },
  23769. applyInteractions: function(interactions, oldInteractions) {
  23770. interactions = Ext.Array.from(interactions, true);
  23771. if (!oldInteractions) {
  23772. oldInteractions = [];
  23773. oldInteractions.map = {};
  23774. }
  23775. var me = this,
  23776. result = [],
  23777. oldMap = oldInteractions.map,
  23778. i, ln, interaction;
  23779. result.map = {};
  23780. for (i = 0 , ln = interactions.length; i < ln; i++) {
  23781. interaction = interactions[i];
  23782. if (!interaction) {
  23783. continue;
  23784. }
  23785. interaction = Ext.factory(interaction, null, oldMap[interaction.getId && interaction.getId() || interaction.id], 'interaction');
  23786. if (interaction) {
  23787. interaction.setChart(me);
  23788. result.push(interaction);
  23789. result.map[interaction.getId()] = interaction;
  23790. }
  23791. }
  23792. for (i in oldMap) {
  23793. if (!result.map[i]) {
  23794. oldMap[i].destroy();
  23795. }
  23796. }
  23797. return result;
  23798. },
  23799. /**
  23800. * Get an interaction by type.
  23801. * @param {String} type The type of the interaction.
  23802. * @return {Ext.chart.interactions.Abstract} The interaction. `null`
  23803. * if not found.
  23804. */
  23805. getInteraction: function(type) {
  23806. var interactions = this.getInteractions(),
  23807. len = interactions && interactions.length,
  23808. out = null,
  23809. interaction, i;
  23810. if (len) {
  23811. for (i = 0; i < len; ++i) {
  23812. interaction = interactions[i];
  23813. if (interaction.type === type) {
  23814. out = interaction;
  23815. break;
  23816. }
  23817. }
  23818. }
  23819. return out;
  23820. },
  23821. applyStore: function(store) {
  23822. return store && Ext.StoreManager.lookup(store);
  23823. },
  23824. updateStore: function(newStore, oldStore) {
  23825. var me = this;
  23826. if (oldStore && !oldStore.destroyed) {
  23827. oldStore.un({
  23828. datachanged: 'onDataChanged',
  23829. update: 'onDataChanged',
  23830. scope: me,
  23831. order: 'after'
  23832. });
  23833. if (oldStore.autoDestroy) {
  23834. oldStore.destroy();
  23835. }
  23836. }
  23837. if (newStore) {
  23838. newStore.on({
  23839. datachanged: 'onDataChanged',
  23840. update: 'onDataChanged',
  23841. scope: me,
  23842. order: 'after'
  23843. });
  23844. }
  23845. me.fireEvent('storechange', me, newStore, oldStore);
  23846. me.onDataChanged();
  23847. },
  23848. /**
  23849. * Redraw the chart. If animations are set this will animate the chart too.
  23850. * Note: the actual redraw is performed in a subclass.
  23851. */
  23852. redraw: function() {
  23853. this.fireEvent('redraw', this);
  23854. },
  23855. /**
  23856. * @private
  23857. * Lays out chart components and triggers a {@link #event!redraw}.
  23858. * Note: the actual layout is performed in a subclass.
  23859. * A subclass should not perform a layout, if this parent method
  23860. * returns `false`.
  23861. * @return {Boolean}
  23862. */
  23863. performLayout: function() {
  23864. if (this.destroying || this.destroyed) {
  23865. //<debug>
  23866. Ext.raise('Attempting to lay out a dead chart: ' + this.getId());
  23867. //</debug>
  23868. return false;
  23869. }
  23870. // Cancel subclass layout.
  23871. var me = this,
  23872. legend = me.getLegend(),
  23873. chartRect = me.getChartRect(true),
  23874. background = me.getBackground(),
  23875. result = true,
  23876. legendRect;
  23877. me.cancelChartLayout();
  23878. //<debug>
  23879. // Unlike the 'layout' event that is called after all chart layouts are done
  23880. // and none are pending, this event fires before the start of each layout.
  23881. me.fireEvent('beforelayout', me);
  23882. //</debug>
  23883. if (background) {
  23884. me.getSurface('background').setRect(chartRect.slice());
  23885. background.setAttributes({
  23886. width: chartRect[2],
  23887. height: chartRect[3]
  23888. });
  23889. }
  23890. // The top docked legend is a special case and should be laid out after captions.
  23891. if (legend && legend.isSpriteLegend && !legend.isTop) {
  23892. legendRect = legend.computeRect(chartRect);
  23893. }
  23894. me.layoutCaptions(chartRect);
  23895. if (legend && legend.isSpriteLegend && legend.isTop) {
  23896. legendRect = legend.computeRect(chartRect);
  23897. }
  23898. if (legendRect) {
  23899. me.getSurface('legend').setRect(legendRect);
  23900. result = legend.performLayout();
  23901. }
  23902. me.getSurface('chart').setRect(chartRect);
  23903. if (result) {
  23904. me.hasFirstLayout = true;
  23905. }
  23906. return result;
  23907. },
  23908. layoutCaptions: function(chartRect) {
  23909. var captions = this.getCaptions(),
  23910. shrinkRect = {
  23911. left: 0,
  23912. top: 0,
  23913. right: chartRect[2],
  23914. bottom: chartRect[3]
  23915. },
  23916. caption, captionName, captionList, i, ln;
  23917. if (captions) {
  23918. captionList = [];
  23919. for (captionName in captions) {
  23920. captionList.push(captions[captionName]);
  23921. }
  23922. captionList.sort(function(a, b) {
  23923. return a.getWeight() - b.getWeight();
  23924. });
  23925. for (i = 0 , ln = captionList.length; i < ln; i++) {
  23926. caption = captionList[i];
  23927. if (!i) {
  23928. this.getSurface(caption.surfaceName).setRect(chartRect.slice());
  23929. }
  23930. caption.computeRect(chartRect, shrinkRect);
  23931. }
  23932. this.captionList = captionList;
  23933. }
  23934. },
  23935. /**
  23936. * @private
  23937. */
  23938. checkLayoutEnd: function() {
  23939. // not running not pending
  23940. if (!this.chartLayoutCount && !this.scheduledLayoutId) {
  23941. this.onLayoutEnd();
  23942. }
  23943. },
  23944. /**
  23945. * @private
  23946. */
  23947. onLayoutEnd: function() {
  23948. var me = this;
  23949. me.fireEvent('layout', me);
  23950. },
  23951. /**
  23952. * @private
  23953. * The area of the chart minus the legend, title, subtitle and credits.
  23954. * Cache chart rect as element.getSize() results in
  23955. * a relatively expensive call to the getComputedStyle().
  23956. */
  23957. getChartRect: function(isRecompute) {
  23958. var me = this,
  23959. chartRect, bodySize;
  23960. if (isRecompute) {
  23961. me.chartRect = null;
  23962. }
  23963. if (me.chartRect) {
  23964. chartRect = me.chartRect;
  23965. } else {
  23966. bodySize = me.bodyElement.getSize();
  23967. chartRect = me.chartRect = [
  23968. 0,
  23969. 0,
  23970. bodySize.width,
  23971. bodySize.height
  23972. ];
  23973. }
  23974. return chartRect;
  23975. },
  23976. /**
  23977. * @private
  23978. * Converts page coordinates into chart's 'series' surface coordinates.
  23979. */
  23980. getEventXY: function(e) {
  23981. return this.getSurface('series').getEventXY(e);
  23982. },
  23983. /**
  23984. * Given an x/y point relative to the chart, find and return the first series item that
  23985. * matches that point.
  23986. * @param {Number} x
  23987. * @param {Number} y
  23988. * @return {Object} An object with `series` and `item` properties, or `false` if no item found.
  23989. */
  23990. getItemForPoint: function(x, y) {
  23991. var me = this,
  23992. seriesList = me.getSeries(),
  23993. rect = me.getMainRect(),
  23994. ln = seriesList.length,
  23995. minDistance = Infinity,
  23996. result = null,
  23997. i, item;
  23998. // The x,y here are already converted to the 'main' surface coordinates.
  23999. // Series surface rect matches the main surface rect.
  24000. if (!(me.hasFirstLayout && rect && x >= 0 && x <= rect[2] && y >= 0 && y <= rect[3])) {
  24001. return null;
  24002. }
  24003. // Iterate in reverse order so that the series that render later (on top)
  24004. // get hit tested first.
  24005. for (i = ln - 1; i >= 0; i--) {
  24006. item = seriesList[i].getItemForPoint(x, y);
  24007. if (item) {
  24008. // Imagine a chart with multiple series, e.g. 'line', 'scatter' and 'bar'.
  24009. // For 'line' and 'scatter' series, the method will look for the nearest
  24010. // marker, but for 'bar' series, it will look for the first bar that
  24011. // contains the given point. For such series, the 'distance' information
  24012. // is absent and meaningless.
  24013. if (!item.distance) {
  24014. result = item;
  24015. break;
  24016. }
  24017. if (item.distance < minDistance) {
  24018. minDistance = item.distance;
  24019. result = item;
  24020. }
  24021. }
  24022. }
  24023. return result;
  24024. },
  24025. /**
  24026. * @private
  24027. * Given an x/y point relative to the chart, find and return all series items that match that point.
  24028. * @param {Number} x
  24029. * @param {Number} y
  24030. * @return {Array} An array of objects with `series` and `item` properties.
  24031. * @deprecated 6.5.2 This method is deprecated
  24032. */
  24033. getItemsForPoint: function(x, y) {
  24034. var me = this,
  24035. seriesList = me.getSeries(),
  24036. ln = seriesList.length,
  24037. // If we haven't drawn yet, don't attempt to find any items.
  24038. i = me.hasFirstLayout ? ln - 1 : -1,
  24039. items = [],
  24040. series, item;
  24041. // Iterate from the end so that the series that are drawn later get hit tested first.
  24042. for (; i >= 0; i--) {
  24043. series = seriesList[i];
  24044. item = series.getItemForPoint(x, y);
  24045. if (item && (item.category === 'items' || item.category === 'markers')) {
  24046. items.push(item);
  24047. }
  24048. }
  24049. return items;
  24050. },
  24051. /**
  24052. * @private
  24053. */
  24054. onDataChanged: function() {
  24055. var me = this;
  24056. if (me.isInitializing) {
  24057. return;
  24058. }
  24059. var rect = me.getMainRect(),
  24060. store = me.getStore(),
  24061. series = me.getSeries(),
  24062. axes = me.getAxes();
  24063. if (!store || !axes || !series) {
  24064. return;
  24065. }
  24066. if (!rect) {
  24067. // The chart hasn't been rendered yet.
  24068. me.on({
  24069. redraw: me.onDataChanged,
  24070. scope: me,
  24071. single: true
  24072. });
  24073. return;
  24074. }
  24075. me.processData();
  24076. me.redraw();
  24077. },
  24078. /**
  24079. * @private
  24080. * The number of records in the chart's store last time the data was changed.
  24081. */
  24082. recordCount: 0,
  24083. /**
  24084. * @private
  24085. */
  24086. processData: function() {
  24087. var me = this,
  24088. recordCount = me.getStore().getCount(),
  24089. seriesList = me.getSeries(),
  24090. ln = seriesList.length,
  24091. isNeedUpdateColors = false,
  24092. i = 0,
  24093. series;
  24094. for (; i < ln; i++) {
  24095. series = seriesList[i];
  24096. series.processData();
  24097. if (!isNeedUpdateColors && series.isStoreDependantColorCount) {
  24098. isNeedUpdateColors = true;
  24099. }
  24100. }
  24101. if (isNeedUpdateColors && recordCount > me.recordCount) {
  24102. me.updateColors(me.getColors());
  24103. me.recordCount = recordCount;
  24104. }
  24105. // 'refreshLegendStore' will attemp to grab the 'series',
  24106. // which are still configuring at this point.
  24107. // The legend store will be refreshed inside the chart.series
  24108. // updater anyway.
  24109. if (!me.isConfiguring) {
  24110. me.refreshLegendStore();
  24111. }
  24112. },
  24113. /**
  24114. * Changes the data store bound to this chart and refreshes it.
  24115. * @param {Ext.data.Store} store The store to bind to this chart.
  24116. */
  24117. bindStore: function(store) {
  24118. this.setStore(store);
  24119. },
  24120. applyHighlightItem: function(newHighlightItem, oldHighlightItem) {
  24121. if (newHighlightItem === oldHighlightItem) {
  24122. return;
  24123. }
  24124. if (Ext.isObject(newHighlightItem) && Ext.isObject(oldHighlightItem)) {
  24125. var i1 = newHighlightItem,
  24126. i2 = oldHighlightItem,
  24127. s1 = i1.sprite && (i1.sprite[0] || i1.sprite),
  24128. s2 = i2.sprite && (i2.sprite[0] || i2.sprite);
  24129. if (s1 === s2 && i1.index === i2.index) {
  24130. return;
  24131. }
  24132. }
  24133. return newHighlightItem;
  24134. },
  24135. updateHighlightItem: function(newHighlightItem, oldHighlightItem) {
  24136. var newHighlight, oldHighlight;
  24137. if (oldHighlightItem) {
  24138. oldHighlight = oldHighlightItem.series.getHighlight();
  24139. if (oldHighlight) {
  24140. oldHighlightItem.series.setAttributesForItem(oldHighlightItem, {
  24141. highlighted: false
  24142. });
  24143. }
  24144. }
  24145. if (newHighlightItem) {
  24146. newHighlight = newHighlightItem.series.getHighlight();
  24147. if (newHighlight) {
  24148. newHighlightItem.series.setAttributesForItem(newHighlightItem, {
  24149. highlighted: true
  24150. });
  24151. }
  24152. }
  24153. if (oldHighlight || newHighlight) {
  24154. this.fireEvent('itemhighlight', this, newHighlightItem, oldHighlightItem);
  24155. }
  24156. },
  24157. destroyChart: function() {
  24158. var me = this;
  24159. // The order is important here.
  24160. me.setInteractions(null);
  24161. me.setAxes(null);
  24162. me.setSeries(null);
  24163. me.setLegend(null);
  24164. me.setStore(null);
  24165. me.cancelChartLayout();
  24166. },
  24167. /* ---------------------------------
  24168. Methods needed for ComponentQuery
  24169. ----------------------------------*/
  24170. /**
  24171. * @private
  24172. * @param {Boolean} deep
  24173. * @return {Array}
  24174. */
  24175. getRefItems: function(deep) {
  24176. var me = this,
  24177. series = me.getSeries(),
  24178. axes = me.getAxes(),
  24179. interaction = me.getInteractions(),
  24180. legend = me.getLegend(),
  24181. ans = [],
  24182. i, ln;
  24183. for (i = 0 , ln = series.length; i < ln; i++) {
  24184. ans.push(series[i]);
  24185. if (series[i].getRefItems) {
  24186. ans.push.apply(ans, series[i].getRefItems(deep));
  24187. }
  24188. }
  24189. for (i = 0 , ln = axes.length; i < ln; i++) {
  24190. ans.push(axes[i]);
  24191. if (axes[i].getRefItems) {
  24192. ans.push.apply(ans, axes[i].getRefItems(deep));
  24193. }
  24194. }
  24195. for (i = 0 , ln = interaction.length; i < ln; i++) {
  24196. ans.push(interaction[i]);
  24197. if (interaction[i].getRefItems) {
  24198. ans.push.apply(ans, interaction[i].getRefItems(deep));
  24199. }
  24200. }
  24201. if (legend) {
  24202. ans.push(legend);
  24203. }
  24204. return ans;
  24205. }
  24206. });
  24207. /**
  24208. * @class Ext.chart.overrides.AbstractChart
  24209. */
  24210. Ext.define('Ext.chart.overrides.AbstractChart', {
  24211. override: 'Ext.chart.AbstractChart',
  24212. updateLegend: function(legend, oldLegend) {
  24213. this.callParent([
  24214. legend,
  24215. oldLegend
  24216. ]);
  24217. if (legend && legend.isDomLegend) {
  24218. this.addDocked(legend);
  24219. }
  24220. },
  24221. performLayout: function() {
  24222. if (this.isVisible(true)) {
  24223. return this.callParent();
  24224. }
  24225. this.cancelChartLayout();
  24226. return false;
  24227. },
  24228. afterComponentLayout: function(width, height, oldWidth, oldHeight) {
  24229. this.callParent([
  24230. width,
  24231. height,
  24232. oldWidth,
  24233. oldHeight
  24234. ]);
  24235. if (!this.hasFirstLayout) {
  24236. this.scheduleLayout();
  24237. }
  24238. },
  24239. allowSchedule: function() {
  24240. return this.rendered;
  24241. },
  24242. doDestroy: function() {
  24243. this.destroyChart();
  24244. this.callParent();
  24245. }
  24246. });
  24247. /**
  24248. * @class Ext.chart.grid.HorizontalGrid
  24249. * @extends Ext.draw.sprite.Sprite
  24250. *
  24251. * Horizontal Grid sprite. Used in Cartesian Charts.
  24252. */
  24253. Ext.define('Ext.chart.grid.HorizontalGrid', {
  24254. extend: 'Ext.draw.sprite.Sprite',
  24255. alias: 'grid.horizontal',
  24256. inheritableStatics: {
  24257. def: {
  24258. processors: {
  24259. x: 'number',
  24260. y: 'number',
  24261. width: 'number',
  24262. height: 'number'
  24263. },
  24264. defaults: {
  24265. x: 0,
  24266. y: 0,
  24267. width: 1,
  24268. height: 1,
  24269. strokeStyle: '#DDD'
  24270. }
  24271. }
  24272. },
  24273. render: function(surface, ctx, rect) {
  24274. var attr = this.attr,
  24275. y = surface.roundPixel(attr.y),
  24276. halfLineWidth = ctx.lineWidth * 0.5;
  24277. ctx.beginPath();
  24278. ctx.rect(rect[0] - surface.matrix.getDX(), y + halfLineWidth, +rect[2], attr.height);
  24279. ctx.fill();
  24280. ctx.beginPath();
  24281. ctx.moveTo(rect[0] - surface.matrix.getDX(), y + halfLineWidth);
  24282. ctx.lineTo(rect[0] + rect[2] - surface.matrix.getDX(), y + halfLineWidth);
  24283. ctx.stroke();
  24284. }
  24285. });
  24286. /**
  24287. * @class Ext.chart.grid.VerticalGrid
  24288. * @extends Ext.draw.sprite.Sprite
  24289. *
  24290. * Vertical Grid sprite. Used in Cartesian Charts.
  24291. */
  24292. Ext.define('Ext.chart.grid.VerticalGrid', {
  24293. extend: 'Ext.draw.sprite.Sprite',
  24294. alias: 'grid.vertical',
  24295. inheritableStatics: {
  24296. def: {
  24297. processors: {
  24298. x: 'number',
  24299. y: 'number',
  24300. width: 'number',
  24301. height: 'number'
  24302. },
  24303. defaults: {
  24304. x: 0,
  24305. y: 0,
  24306. width: 1,
  24307. height: 1,
  24308. strokeStyle: '#DDD'
  24309. }
  24310. }
  24311. },
  24312. render: function(surface, ctx, rect) {
  24313. var attr = this.attr,
  24314. x = surface.roundPixel(attr.x),
  24315. halfLineWidth = ctx.lineWidth * 0.5;
  24316. ctx.beginPath();
  24317. ctx.rect(x - halfLineWidth, rect[1] - surface.matrix.getDY(), attr.width, rect[3]);
  24318. ctx.fill();
  24319. ctx.beginPath();
  24320. ctx.moveTo(x - halfLineWidth, rect[1] - surface.matrix.getDY());
  24321. ctx.lineTo(x - halfLineWidth, rect[1] + rect[3] - surface.matrix.getDY());
  24322. ctx.stroke();
  24323. }
  24324. });
  24325. /**
  24326. * Represents a chart that uses cartesian coordinates.
  24327. * A cartesian chart has two directions, X direction and Y direction.
  24328. * The series and axes are coordinated along these directions.
  24329. * By default the x direction is horizontal and y direction is vertical,
  24330. * You can swap the direction by setting the {@link #flipXY} config to `true`.
  24331. *
  24332. * Cartesian series often treats x direction an y direction differently.
  24333. * In most cases, data on x direction are assumed to be monotonically increasing.
  24334. * Based on this property, cartesian series can be trimmed and summarized properly
  24335. * to gain a better performance.
  24336. *
  24337. * Please check out the summary for the {@link Ext.chart.AbstractChart} as well,
  24338. * for helpful tips and important details.
  24339. *
  24340. */
  24341. Ext.define('Ext.chart.CartesianChart', {
  24342. extend: 'Ext.chart.AbstractChart',
  24343. alternateClassName: 'Ext.chart.Chart',
  24344. requires: [
  24345. 'Ext.chart.grid.HorizontalGrid',
  24346. 'Ext.chart.grid.VerticalGrid'
  24347. ],
  24348. xtype: [
  24349. 'cartesian',
  24350. 'chart'
  24351. ],
  24352. isCartesian: true,
  24353. config: {
  24354. /**
  24355. * @cfg {Boolean} flipXY Flip the direction of X and Y axis.
  24356. * If flipXY is `true`, the X axes will be vertical and Y axes will be horizontal.
  24357. * Note that {@link Ext.chart.axis.Axis#position positions} of chart axes have
  24358. * to be updated accordingly: axes positioned to the `top` and `bottom` should
  24359. * be positioned to the `left` or `right` and vice versa.
  24360. */
  24361. flipXY: false,
  24362. /*
  24363. While it may seem tedious to change the position config of all axes every time
  24364. when the value of the flipXY config is changed, it's hard to predict the
  24365. expectaction of the user here, as illustrated below.
  24366. The 'num' and 'cat' here stand for the numeric and the category axis, respectively.
  24367. And the right column shows the expected (subjective) result of setting the flipXY
  24368. config of the chart to 'true'.
  24369. As one can see, there's no single rule (e.g. position swapping, clockwise 90° chart
  24370. rotation) that will produce a universally accepted result.
  24371. So we are letting the user decide, instead of doing it for them.
  24372. ---------------------------------------------
  24373. | flipXY: false | flipXY: true |
  24374. ---------------------------------------------
  24375. | ^ | ^ |
  24376. | | * | | * * * |
  24377. | num1 | * * | cat | * * |
  24378. | | * * * | | * |
  24379. | --------> | --------> |
  24380. | cat | num1 |
  24381. ---------------------------------------------
  24382. | | num1 |
  24383. | ^ ^ | ^-------> |
  24384. | | * | | | * * * |
  24385. | num1 | * * | num2 | cat | * * |
  24386. | | * * * | | | * |
  24387. | --------> | --------> |
  24388. | cat | num2 |
  24389. ---------------------------------------------
  24390. */
  24391. innerRect: [
  24392. 0,
  24393. 0,
  24394. 1,
  24395. 1
  24396. ],
  24397. /**
  24398. * @cfg {Object} innerPadding The amount of inner padding in pixels.
  24399. * Inner padding is the padding from the innermost axes to the series.
  24400. */
  24401. innerPadding: {
  24402. top: 0,
  24403. left: 0,
  24404. right: 0,
  24405. bottom: 0
  24406. }
  24407. },
  24408. applyInnerPadding: function(padding, oldPadding) {
  24409. if (!Ext.isObject(padding)) {
  24410. return Ext.util.Format.parseBox(padding);
  24411. } else if (!oldPadding) {
  24412. return padding;
  24413. } else {
  24414. return Ext.apply(oldPadding, padding);
  24415. }
  24416. },
  24417. getDirectionForAxis: function(position) {
  24418. var flipXY = this.getFlipXY(),
  24419. direction;
  24420. if (position === 'left' || position === 'right') {
  24421. direction = flipXY ? 'X' : 'Y';
  24422. } else {
  24423. direction = flipXY ? 'Y' : 'X';
  24424. }
  24425. return direction;
  24426. },
  24427. /**
  24428. * Layout the axes and series.
  24429. */
  24430. performLayout: function() {
  24431. var me = this;
  24432. if (me.callParent() === false) {
  24433. return;
  24434. }
  24435. me.chartLayoutCount++;
  24436. me.suspendAnimation();
  24437. // 'chart' surface rect is the size of the chart's inner element
  24438. // (see chart.getChartBox), i.e. the portion of the chart minus
  24439. // the legend area (whether DOM or sprite based).
  24440. var chartRect = me.getSurface('chart').getRect(),
  24441. left = chartRect[0],
  24442. top = chartRect[1],
  24443. width = chartRect[2],
  24444. height = chartRect[3],
  24445. captionList = me.captionList,
  24446. axes = me.getAxes(),
  24447. axis,
  24448. seriesList = me.getSeries(),
  24449. series, axisSurface, thickness,
  24450. insetPadding = me.getInsetPadding(),
  24451. innerPadding = me.getInnerPadding(),
  24452. surface, gridSurface,
  24453. // shrinkBox represents padding added on each side by
  24454. // innerPadding & insetPadding configs and the legend.
  24455. shrinkBox = Ext.apply({}, insetPadding),
  24456. mainRect, innerWidth, innerHeight, elements, floating, floatingValue, matrix, i, ln,
  24457. isRtl = me.getInherited().rtl,
  24458. flipXY = me.getFlipXY(),
  24459. caption;
  24460. if (width <= 0 || height <= 0) {
  24461. return;
  24462. }
  24463. me.suspendThicknessChanged();
  24464. for (i = 0; i < axes.length; i++) {
  24465. axis = axes[i];
  24466. axisSurface = axis.getSurface();
  24467. floating = axis.getFloating();
  24468. floatingValue = floating ? floating.value : null;
  24469. thickness = axis.getThickness();
  24470. switch (axis.getPosition()) {
  24471. case 'top':
  24472. axisSurface.setRect([
  24473. left,
  24474. top + shrinkBox.top + 1,
  24475. width,
  24476. thickness
  24477. ]);
  24478. break;
  24479. case 'bottom':
  24480. axisSurface.setRect([
  24481. left,
  24482. top + height - (shrinkBox.bottom + thickness),
  24483. width,
  24484. thickness
  24485. ]);
  24486. break;
  24487. case 'left':
  24488. axisSurface.setRect([
  24489. left + shrinkBox.left,
  24490. top,
  24491. thickness,
  24492. height
  24493. ]);
  24494. break;
  24495. case 'right':
  24496. axisSurface.setRect([
  24497. left + width - (shrinkBox.right + thickness),
  24498. top,
  24499. thickness,
  24500. height
  24501. ]);
  24502. break;
  24503. }
  24504. if (floatingValue === null) {
  24505. shrinkBox[axis.getPosition()] += thickness;
  24506. }
  24507. }
  24508. width -= shrinkBox.left + shrinkBox.right;
  24509. height -= shrinkBox.top + shrinkBox.bottom;
  24510. mainRect = [
  24511. left + shrinkBox.left,
  24512. top + shrinkBox.top,
  24513. width,
  24514. height
  24515. ];
  24516. shrinkBox.left += innerPadding.left;
  24517. shrinkBox.top += innerPadding.top;
  24518. shrinkBox.right += innerPadding.right;
  24519. shrinkBox.bottom += innerPadding.bottom;
  24520. innerWidth = width - innerPadding.left - innerPadding.right;
  24521. innerHeight = height - innerPadding.top - innerPadding.bottom;
  24522. me.setInnerRect([
  24523. shrinkBox.left,
  24524. shrinkBox.top,
  24525. innerWidth,
  24526. innerHeight
  24527. ]);
  24528. if (innerWidth <= 0 || innerHeight <= 0) {
  24529. return;
  24530. }
  24531. me.setMainRect(mainRect);
  24532. me.getSurface().setRect(mainRect);
  24533. for (i = 0 , ln = me.surfaceMap.grid && me.surfaceMap.grid.length; i < ln; i++) {
  24534. gridSurface = me.surfaceMap.grid[i];
  24535. gridSurface.setRect(mainRect);
  24536. gridSurface.matrix.set(1, 0, 0, 1, innerPadding.left, innerPadding.top);
  24537. gridSurface.matrix.inverse(gridSurface.inverseMatrix);
  24538. }
  24539. for (i = 0; i < axes.length; i++) {
  24540. axis = axes[i];
  24541. axis.getRange(true);
  24542. axisSurface = axis.getSurface();
  24543. matrix = axisSurface.matrix;
  24544. elements = matrix.elements;
  24545. switch (axis.getPosition()) {
  24546. case 'top':
  24547. case 'bottom':
  24548. elements[4] = shrinkBox.left;
  24549. axis.setLength(innerWidth);
  24550. break;
  24551. case 'left':
  24552. case 'right':
  24553. elements[5] = shrinkBox.top;
  24554. axis.setLength(innerHeight);
  24555. break;
  24556. }
  24557. axis.updateTitleSprite();
  24558. matrix.inverse(axisSurface.inverseMatrix);
  24559. }
  24560. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  24561. series = seriesList[i];
  24562. surface = series.getSurface();
  24563. surface.setRect(mainRect);
  24564. if (flipXY) {
  24565. if (isRtl) {
  24566. surface.matrix.set(0, -1, -1, 0, innerPadding.left + innerWidth, innerPadding.top + innerHeight);
  24567. } else {
  24568. surface.matrix.set(0, -1, 1, 0, innerPadding.left, innerPadding.top + innerHeight);
  24569. }
  24570. } else {
  24571. surface.matrix.set(1, 0, 0, -1, innerPadding.left, innerPadding.top + innerHeight);
  24572. }
  24573. surface.matrix.inverse(surface.inverseMatrix);
  24574. series.getOverlaySurface().setRect(mainRect);
  24575. }
  24576. if (captionList) {
  24577. for (i = 0 , ln = captionList.length; i < ln; i++) {
  24578. caption = captionList[i];
  24579. if (caption.getAlignTo() === 'series') {
  24580. caption.alignRect(mainRect);
  24581. }
  24582. caption.performLayout();
  24583. }
  24584. }
  24585. // In certain cases 'performLayout' override is not an option without major code duplication.
  24586. // 'afterChartLayout' can be a cleaner solution in such cases (because of the timing of its call).
  24587. me.afterChartLayout();
  24588. // currently in cartesian charts only (used by Navigator)
  24589. me.redraw();
  24590. me.resumeAnimation();
  24591. // 'resumeThicknessChanged' may trigger another layout, if the 'redraw' call above
  24592. // resulted in a situation where an axis is no longer 'thick' enough to accommodate
  24593. // the new labels. E.g. the labels were: 'Bob', 'Ann', 'Joe' and now they are 'Jonathan',
  24594. // 'Rachael', 'Michael'. An axis has to be made thicker now, and another layout should be
  24595. // performed. This second layout is not scheduled, but performed immediately, which will
  24596. // increment the 'chartLayoutCount' again.
  24597. me.resumeThicknessChanged();
  24598. me.chartLayoutCount--;
  24599. // 'checkLayoutEnd' will check if another layout is already running or scheduled and,
  24600. // if neither is the case, will fire the 'layout' event, meaning we are totally done
  24601. // with layout at this point.
  24602. me.checkLayoutEnd();
  24603. },
  24604. afterChartLayout: Ext.emptyFn,
  24605. refloatAxes: function() {
  24606. var me = this,
  24607. axes = me.getAxes(),
  24608. axesCount = (axes && axes.length) || 0,
  24609. axis, axisSurface, axisRect, floating, value, alongAxis, matrix,
  24610. chartRect = me.getChartRect(),
  24611. inset = me.getInsetPadding(),
  24612. inner = me.getInnerPadding(),
  24613. width = chartRect[2] - inset.left - inset.right,
  24614. height = chartRect[3] - inset.top - inset.bottom,
  24615. isHorizontal, i;
  24616. for (i = 0; i < axesCount; i++) {
  24617. axis = axes[i];
  24618. floating = axis.getFloating();
  24619. value = floating ? floating.value : null;
  24620. if (value === null) {
  24621. axis.floatingAtCoord = null;
  24622. continue;
  24623. }
  24624. axisSurface = axis.getSurface();
  24625. axisRect = axisSurface.getRect();
  24626. if (!axisRect) {
  24627. continue;
  24628. }
  24629. axisRect = axisRect.slice();
  24630. alongAxis = me.getAxis(floating.alongAxis);
  24631. if (alongAxis) {
  24632. isHorizontal = alongAxis.getAlignment() === 'horizontal';
  24633. if (Ext.isString(value)) {
  24634. value = alongAxis.getCoordFor(value);
  24635. }
  24636. alongAxis.floatingAxes[axis.getId()] = value;
  24637. matrix = alongAxis.getSprites()[0].attr.matrix;
  24638. if (isHorizontal) {
  24639. value = value * matrix.getXX() + matrix.getDX();
  24640. axis.floatingAtCoord = value + inner.left + inner.right;
  24641. } else {
  24642. value = value * matrix.getYY() + matrix.getDY();
  24643. axis.floatingAtCoord = value + inner.top + inner.bottom;
  24644. }
  24645. } else {
  24646. isHorizontal = axis.getAlignment() === 'horizontal';
  24647. if (isHorizontal) {
  24648. axis.floatingAtCoord = value + inner.top + inner.bottom;
  24649. } else {
  24650. axis.floatingAtCoord = value + inner.left + inner.right;
  24651. }
  24652. value = axisSurface.roundPixel(0.01 * value * (isHorizontal ? height : width));
  24653. }
  24654. switch (axis.getPosition()) {
  24655. case 'top':
  24656. axisRect[1] = inset.top + inner.top + value - axisRect[3] + 1;
  24657. break;
  24658. case 'bottom':
  24659. axisRect[1] = inset.top + inner.top + (alongAxis ? value : height - value);
  24660. break;
  24661. case 'left':
  24662. axisRect[0] = inset.left + inner.left + value - axisRect[2];
  24663. break;
  24664. case 'right':
  24665. axisRect[0] = inset.left + inner.left + (alongAxis ? value : width - value) - 1;
  24666. break;
  24667. }
  24668. axisSurface.setRect(axisRect);
  24669. }
  24670. },
  24671. redraw: function() {
  24672. var me = this,
  24673. seriesList = me.getSeries(),
  24674. axes = me.getAxes(),
  24675. rect = me.getMainRect(),
  24676. innerWidth, innerHeight,
  24677. innerPadding = me.getInnerPadding(),
  24678. sprites, xRange, yRange, isSide, attr, i, j, ln, axis, axisX, axisY, range, visibleRange,
  24679. flipXY = me.getFlipXY(),
  24680. zBase = 1000,
  24681. zIndex, markersZIndex, series, sprite, markers;
  24682. if (!rect) {
  24683. return;
  24684. }
  24685. innerWidth = rect[2] - innerPadding.left - innerPadding.right;
  24686. innerHeight = rect[3] - innerPadding.top - innerPadding.bottom;
  24687. for (i = 0; i < seriesList.length; i++) {
  24688. series = seriesList[i];
  24689. axisX = series.getXAxis();
  24690. if (axisX) {
  24691. visibleRange = axisX.getVisibleRange();
  24692. xRange = axisX.getRange();
  24693. xRange = [
  24694. xRange[0] + (xRange[1] - xRange[0]) * visibleRange[0],
  24695. xRange[0] + (xRange[1] - xRange[0]) * visibleRange[1]
  24696. ];
  24697. } else {
  24698. xRange = series.getXRange();
  24699. }
  24700. axisY = series.getYAxis();
  24701. if (axisY) {
  24702. visibleRange = axisY.getVisibleRange();
  24703. yRange = axisY.getRange();
  24704. yRange = [
  24705. yRange[0] + (yRange[1] - yRange[0]) * visibleRange[0],
  24706. yRange[0] + (yRange[1] - yRange[0]) * visibleRange[1]
  24707. ];
  24708. } else {
  24709. yRange = series.getYRange();
  24710. }
  24711. attr = {
  24712. visibleMinX: xRange[0],
  24713. visibleMaxX: xRange[1],
  24714. visibleMinY: yRange[0],
  24715. visibleMaxY: yRange[1],
  24716. innerWidth: innerWidth,
  24717. innerHeight: innerHeight,
  24718. flipXY: flipXY
  24719. };
  24720. sprites = series.getSprites();
  24721. for (j = 0 , ln = sprites.length; j < ln; j++) {
  24722. // All the series now share the same surface, so we must assign
  24723. // the sprites a zIndex that depends on the index of their series.
  24724. sprite = sprites[j];
  24725. zIndex = sprite.attr.zIndex;
  24726. if (zIndex < zBase) {
  24727. // Set the sprite's zIndex
  24728. zIndex += (i + 1) * 100 + zBase;
  24729. sprite.attr.zIndex = zIndex;
  24730. // If the sprite is a MarkerHolder, set zIndex of the bound markers as well.
  24731. // Do this for the 'items' markers only, as those are the only ones
  24732. // that go into the 'series' surface. 'labels' and 'markers' markers
  24733. // go into the 'overlay' surface instead.
  24734. markers = sprite.getMarker('items');
  24735. if (markers) {
  24736. markersZIndex = markers.attr.zIndex;
  24737. if (markersZIndex === Number.MAX_VALUE) {
  24738. markers.attr.zIndex = zIndex;
  24739. } else if (markersZIndex < zBase) {
  24740. markers.attr.zIndex = zIndex + markersZIndex;
  24741. }
  24742. }
  24743. }
  24744. sprite.setAttributes(attr, true);
  24745. }
  24746. }
  24747. for (i = 0; i < axes.length; i++) {
  24748. axis = axes[i];
  24749. isSide = axis.isSide();
  24750. sprites = axis.getSprites();
  24751. range = axis.getRange();
  24752. visibleRange = axis.getVisibleRange();
  24753. attr = {
  24754. dataMin: range[0],
  24755. dataMax: range[1],
  24756. visibleMin: visibleRange[0],
  24757. visibleMax: visibleRange[1]
  24758. };
  24759. if (isSide) {
  24760. attr.length = innerHeight;
  24761. attr.startGap = innerPadding.bottom;
  24762. attr.endGap = innerPadding.top;
  24763. } else {
  24764. attr.length = innerWidth;
  24765. attr.startGap = innerPadding.left;
  24766. attr.endGap = innerPadding.right;
  24767. }
  24768. for (j = 0 , ln = sprites.length; j < ln; j++) {
  24769. sprites[j].setAttributes(attr, true);
  24770. }
  24771. }
  24772. me.renderFrame();
  24773. me.callParent();
  24774. },
  24775. renderFrame: function() {
  24776. this.refloatAxes();
  24777. this.callParent();
  24778. }
  24779. });
  24780. /**
  24781. * @class Ext.chart.grid.CircularGrid
  24782. * @extends Ext.draw.sprite.Circle
  24783. *
  24784. * Circular Grid sprite. Used by Radar chart to render a series of concentric circles.
  24785. */
  24786. Ext.define('Ext.chart.grid.CircularGrid', {
  24787. extend: 'Ext.draw.sprite.Circle',
  24788. alias: 'grid.circular',
  24789. inheritableStatics: {
  24790. def: {
  24791. defaults: {
  24792. r: 1,
  24793. strokeStyle: '#DDD'
  24794. }
  24795. }
  24796. }
  24797. });
  24798. /**
  24799. * @class Ext.chart.grid.RadialGrid
  24800. * @extends Ext.draw.sprite.Path
  24801. *
  24802. * Radial Grid sprite. Used by Radar chart to render a series of radial lines.
  24803. * Represents the scale of the radar chart on the yField.
  24804. */
  24805. Ext.define('Ext.chart.grid.RadialGrid', {
  24806. extend: 'Ext.draw.sprite.Path',
  24807. alias: 'grid.radial',
  24808. inheritableStatics: {
  24809. def: {
  24810. processors: {
  24811. startRadius: 'number',
  24812. endRadius: 'number'
  24813. },
  24814. defaults: {
  24815. startRadius: 0,
  24816. endRadius: 1,
  24817. scalingCenterX: 0,
  24818. scalingCenterY: 0,
  24819. strokeStyle: '#DDD'
  24820. },
  24821. triggers: {
  24822. startRadius: 'path,bbox',
  24823. endRadius: 'path,bbox'
  24824. }
  24825. }
  24826. },
  24827. render: function() {
  24828. this.callParent(arguments);
  24829. },
  24830. updatePath: function(path, attr) {
  24831. var startRadius = attr.startRadius,
  24832. endRadius = attr.endRadius;
  24833. path.moveTo(startRadius, 0);
  24834. path.lineTo(endRadius, 0);
  24835. }
  24836. });
  24837. /**
  24838. * @class Ext.chart.PolarChart
  24839. * @extends Ext.chart.AbstractChart
  24840. * @xtype polar
  24841. *
  24842. * Represent a chart that uses polar coordinates.
  24843. * A polar chart has two axes: an angular axis (which is a circle) and
  24844. * a radial axis (a straight line from the center to the edge of the circle).
  24845. * The angular axis is usually a Category axis while the radial axis is
  24846. * typically numerical.
  24847. *
  24848. * Pie charts and Radar charts are common examples of Polar charts.
  24849. *
  24850. * Please check out the summary for the {@link Ext.chart.AbstractChart} as well,
  24851. * for helpful tips and important details.
  24852. *
  24853. */
  24854. Ext.define('Ext.chart.PolarChart', {
  24855. extend: 'Ext.chart.AbstractChart',
  24856. requires: [
  24857. 'Ext.chart.grid.CircularGrid',
  24858. 'Ext.chart.grid.RadialGrid'
  24859. ],
  24860. xtype: 'polar',
  24861. isPolar: true,
  24862. config: {
  24863. /**
  24864. * @cfg {Array} center Determines the center of the polar chart.
  24865. * Updated when the chart performs layout.
  24866. */
  24867. center: [
  24868. 0,
  24869. 0
  24870. ],
  24871. /**
  24872. * @cfg {Number} radius Determines the radius of the polar chart.
  24873. * Updated when the chart performs layout.
  24874. */
  24875. radius: 0,
  24876. /**
  24877. * @cfg {Number} innerPadding The amount of inner padding in pixels.
  24878. * Inner padding is the padding from the outermost angular axis to the series.
  24879. */
  24880. innerPadding: 0
  24881. },
  24882. getDirectionForAxis: function(position) {
  24883. return position === 'radial' ? 'Y' : 'X';
  24884. },
  24885. updateCenter: function(center) {
  24886. var me = this,
  24887. axes = me.getAxes(),
  24888. series = me.getSeries(),
  24889. i, ln, axis, seriesItem;
  24890. for (i = 0 , ln = axes.length; i < ln; i++) {
  24891. axis = axes[i];
  24892. axis.setCenter(center);
  24893. }
  24894. for (i = 0 , ln = series.length; i < ln; i++) {
  24895. seriesItem = series[i];
  24896. seriesItem.setCenter(center);
  24897. }
  24898. },
  24899. applyInnerPadding: function(padding, oldPadding) {
  24900. return Ext.isNumber(padding) ? padding : oldPadding;
  24901. },
  24902. updateInnerPadding: function() {
  24903. if (!this.isConfiguring) {
  24904. this.performLayout();
  24905. }
  24906. },
  24907. doSetSurfaceRect: function(surface, rect) {
  24908. var mainRect = this.getMainRect();
  24909. surface.setRect(rect);
  24910. surface.matrix.set(1, 0, 0, 1, mainRect[0] - rect[0], mainRect[1] - rect[1]);
  24911. surface.inverseMatrix.set(1, 0, 0, 1, rect[0] - mainRect[0], rect[1] - mainRect[1]);
  24912. },
  24913. applyAxes: function(newAxes, oldAxes) {
  24914. var me = this,
  24915. firstSeries = Ext.Array.from(me.config.series)[0],
  24916. i, ln, axis, foundAngular;
  24917. if (firstSeries && firstSeries.type === 'radar' && newAxes && newAxes.length) {
  24918. // For compatibility with ExtJS: add a default angular axis if it's missing
  24919. for (i = 0 , ln = newAxes.length; i < ln; i++) {
  24920. axis = newAxes[i];
  24921. if (axis.position === 'angular') {
  24922. foundAngular = true;
  24923. break;
  24924. }
  24925. }
  24926. if (!foundAngular) {
  24927. newAxes.push({
  24928. type: 'category',
  24929. position: 'angular',
  24930. fields: firstSeries.xField || firstSeries.angleField,
  24931. style: {
  24932. estStepSize: 1
  24933. },
  24934. grid: true
  24935. });
  24936. }
  24937. }
  24938. return this.callParent([
  24939. newAxes,
  24940. oldAxes
  24941. ]);
  24942. },
  24943. performLayout: function() {
  24944. var me = this,
  24945. applyThickness = true;
  24946. try {
  24947. me.chartLayoutCount++;
  24948. me.suspendAnimation();
  24949. if (this.callParent() === false) {
  24950. applyThickness = false;
  24951. // Animation will be decremented in finally block
  24952. return;
  24953. }
  24954. me.suspendThicknessChanged();
  24955. var chartRect = me.getSurface('chart').getRect(),
  24956. inset = me.getInsetPadding(),
  24957. inner = me.getInnerPadding(),
  24958. shrinkBox = Ext.apply({}, inset),
  24959. width = Math.max(1, chartRect[2] - chartRect[0] - inset.left - inset.right),
  24960. height = Math.max(1, chartRect[3] - chartRect[1] - inset.top - inset.bottom),
  24961. mainRect = [
  24962. chartRect[0] + inset.left,
  24963. chartRect[1] + inset.top,
  24964. width + chartRect[0],
  24965. height + chartRect[1]
  24966. ],
  24967. seriesList = me.getSeries(),
  24968. innerWidth = width - inner * 2,
  24969. innerHeight = height - inner * 2,
  24970. center = [
  24971. (chartRect[0] + innerWidth) * 0.5 + inner,
  24972. (chartRect[1] + innerHeight) * 0.5 + inner
  24973. ],
  24974. radius = Math.min(innerWidth, innerHeight) * 0.5,
  24975. axes = me.getAxes(),
  24976. angularAxes = [],
  24977. radialAxes = [],
  24978. seriesRadius = radius - inner,
  24979. grid = me.surfaceMap.grid,
  24980. captionList = me.captionList,
  24981. i, ln, shrinkRadius, floating, floatingValue, gaugeSeries, gaugeRadius, side, series, axis, thickness, halfLineWidth, caption;
  24982. me.setMainRect(mainRect);
  24983. me.doSetSurfaceRect(me.getSurface(), mainRect);
  24984. if (grid) {
  24985. for (i = 0 , ln = grid.length; i < ln; i++) {
  24986. me.doSetSurfaceRect(grid[i], chartRect);
  24987. }
  24988. }
  24989. for (i = 0 , ln = axes.length; i < ln; i++) {
  24990. axis = axes[i];
  24991. switch (axis.getPosition()) {
  24992. case 'angular':
  24993. angularAxes.push(axis);
  24994. break;
  24995. case 'radial':
  24996. radialAxes.push(axis);
  24997. break;
  24998. }
  24999. }
  25000. for (i = 0 , ln = angularAxes.length; i < ln; i++) {
  25001. axis = angularAxes[i];
  25002. floating = axis.getFloating();
  25003. floatingValue = floating ? floating.value : null;
  25004. me.doSetSurfaceRect(axis.getSurface(), chartRect);
  25005. thickness = axis.getThickness();
  25006. for (side in shrinkBox) {
  25007. shrinkBox[side] += thickness;
  25008. }
  25009. width = chartRect[2] - shrinkBox.left - shrinkBox.right;
  25010. height = chartRect[3] - shrinkBox.top - shrinkBox.bottom;
  25011. shrinkRadius = Math.min(width, height) * 0.5;
  25012. if (i === 0) {
  25013. seriesRadius = shrinkRadius - inner;
  25014. }
  25015. axis.setMinimum(0);
  25016. axis.setLength(shrinkRadius);
  25017. axis.getSprites();
  25018. halfLineWidth = axis.sprites[0].attr.lineWidth * 0.5;
  25019. for (side in shrinkBox) {
  25020. shrinkBox[side] += halfLineWidth;
  25021. }
  25022. }
  25023. for (i = 0 , ln = radialAxes.length; i < ln; i++) {
  25024. axis = radialAxes[i];
  25025. me.doSetSurfaceRect(axis.getSurface(), chartRect);
  25026. axis.setMinimum(0);
  25027. axis.setLength(seriesRadius);
  25028. axis.getSprites();
  25029. }
  25030. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  25031. series = seriesList[i];
  25032. if (series.type === 'gauge' && !gaugeSeries) {
  25033. gaugeSeries = series;
  25034. } else {
  25035. series.setRadius(seriesRadius);
  25036. }
  25037. me.doSetSurfaceRect(series.getSurface(), mainRect);
  25038. }
  25039. me.doSetSurfaceRect(me.getSurface('overlay'), chartRect);
  25040. if (gaugeSeries) {
  25041. gaugeSeries.setRect(mainRect);
  25042. gaugeRadius = gaugeSeries.getRadius() - inner;
  25043. me.setRadius(gaugeRadius);
  25044. me.setCenter(gaugeSeries.getCenter());
  25045. gaugeSeries.setRadius(gaugeRadius);
  25046. if (axes.length && axes[0].getPosition() === 'gauge') {
  25047. axis = axes[0];
  25048. me.doSetSurfaceRect(axis.getSurface(), chartRect);
  25049. axis.setTotalAngle(gaugeSeries.getTotalAngle());
  25050. axis.setLength(gaugeRadius);
  25051. }
  25052. } else {
  25053. me.setRadius(radius);
  25054. me.setCenter(center);
  25055. }
  25056. if (captionList) {
  25057. for (i = 0 , ln = captionList.length; i < ln; i++) {
  25058. caption = captionList[i];
  25059. if (caption.getAlignTo() === 'series') {
  25060. caption.alignRect(mainRect);
  25061. }
  25062. caption.performLayout();
  25063. }
  25064. }
  25065. me.redraw();
  25066. } finally {
  25067. me.resumeAnimation();
  25068. if (applyThickness) {
  25069. me.resumeThicknessChanged();
  25070. }
  25071. me.chartLayoutCount--;
  25072. me.checkLayoutEnd();
  25073. }
  25074. },
  25075. refloatAxes: function() {
  25076. var me = this,
  25077. axes = me.getAxes(),
  25078. mainRect = me.getMainRect(),
  25079. floating, value, alongAxis, i, n, axis, radius;
  25080. if (!mainRect) {
  25081. return;
  25082. }
  25083. radius = 0.5 * Math.min(mainRect[2], mainRect[3]);
  25084. for (i = 0 , n = axes.length; i < n; i++) {
  25085. axis = axes[i];
  25086. floating = axis.getFloating();
  25087. value = floating ? floating.value : null;
  25088. if (value !== null) {
  25089. alongAxis = me.getAxis(floating.alongAxis);
  25090. if (axis.getPosition() === 'angular') {
  25091. if (alongAxis) {
  25092. value = alongAxis.getLength() * value / alongAxis.getRange()[1];
  25093. } else {
  25094. value = 0.01 * value * radius;
  25095. }
  25096. axis.sprites[0].setAttributes({
  25097. length: value
  25098. }, true);
  25099. } else {
  25100. if (alongAxis) {
  25101. if (Ext.isString(value)) {
  25102. value = alongAxis.getCoordFor(value);
  25103. }
  25104. value = value / (alongAxis.getRange()[1] + 1) * Math.PI * 2 - Math.PI * 1.5 + axis.getRotation();
  25105. } else {
  25106. value = Ext.draw.Draw.rad(value);
  25107. }
  25108. axis.sprites[0].setAttributes({
  25109. baseRotation: value
  25110. }, true);
  25111. }
  25112. }
  25113. }
  25114. },
  25115. redraw: function() {
  25116. var me = this,
  25117. axes = me.getAxes(),
  25118. axis,
  25119. seriesList = me.getSeries(),
  25120. series, i, ln;
  25121. for (i = 0 , ln = axes.length; i < ln; i++) {
  25122. axis = axes[i];
  25123. axis.getSprites();
  25124. }
  25125. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  25126. series = seriesList[i];
  25127. series.getSprites();
  25128. }
  25129. me.renderFrame();
  25130. me.callParent();
  25131. },
  25132. renderFrame: function() {
  25133. this.refloatAxes();
  25134. this.callParent();
  25135. }
  25136. });
  25137. /**
  25138. * @class Ext.chart.SpaceFillingChart
  25139. * @extends Ext.chart.AbstractChart
  25140. *
  25141. * Creates a chart that fills the entire area of the chart.
  25142. * e.g. Gauge Charts
  25143. */
  25144. Ext.define('Ext.chart.SpaceFillingChart', {
  25145. extend: 'Ext.chart.AbstractChart',
  25146. xtype: 'spacefilling',
  25147. config: {},
  25148. performLayout: function() {
  25149. var me = this;
  25150. try {
  25151. me.chartLayoutCount++;
  25152. me.suspendAnimation();
  25153. if (me.callParent() === false) {
  25154. // animationSuspendCount will still be decremented
  25155. return;
  25156. }
  25157. var chartRect = me.getSurface('chart').getRect(),
  25158. padding = me.getInsetPadding(),
  25159. width = chartRect[2] - padding.left - padding.right,
  25160. height = chartRect[3] - padding.top - padding.bottom,
  25161. mainRect = [
  25162. padding.left,
  25163. padding.top,
  25164. width,
  25165. height
  25166. ],
  25167. seriesList = me.getSeries(),
  25168. series, i, ln;
  25169. me.getSurface().setRect(mainRect);
  25170. me.setMainRect(mainRect);
  25171. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  25172. series = seriesList[i];
  25173. series.getSurface().setRect(mainRect);
  25174. if (series.setRect) {
  25175. series.setRect(mainRect);
  25176. }
  25177. series.getOverlaySurface().setRect(chartRect);
  25178. }
  25179. me.redraw();
  25180. } finally {
  25181. me.resumeAnimation();
  25182. me.chartLayoutCount--;
  25183. me.checkLayoutEnd();
  25184. }
  25185. },
  25186. redraw: function() {
  25187. var me = this,
  25188. seriesList = me.getSeries(),
  25189. series, i, ln;
  25190. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  25191. series = seriesList[i];
  25192. series.getSprites();
  25193. }
  25194. me.renderFrame();
  25195. me.callParent();
  25196. }
  25197. });
  25198. /**
  25199. * @private
  25200. * @class Ext.chart.axis.sprite.Axis3D
  25201. * @extends Ext.chart.axis.sprite.Axis
  25202. *
  25203. * The {@link Ext.chart.axis.Axis3D 3D axis} sprite.
  25204. * Only 3D cartesian axes are rendered with this sprite.
  25205. */
  25206. Ext.define('Ext.chart.axis.sprite.Axis3D', {
  25207. extend: 'Ext.chart.axis.sprite.Axis',
  25208. alias: 'sprite.axis3d',
  25209. type: 'axis3d',
  25210. inheritableStatics: {
  25211. def: {
  25212. processors: {
  25213. depth: 'number'
  25214. },
  25215. defaults: {
  25216. depth: 0
  25217. },
  25218. triggers: {
  25219. depth: 'layout'
  25220. }
  25221. }
  25222. },
  25223. config: {
  25224. animation: {
  25225. customDurations: {
  25226. depth: 0
  25227. }
  25228. }
  25229. },
  25230. layoutUpdater: function() {
  25231. var me = this,
  25232. chart = me.getAxis().getChart();
  25233. if (chart.isInitializing) {
  25234. return;
  25235. }
  25236. var attr = me.attr,
  25237. layout = me.getLayout(),
  25238. depth = layout.isDiscrete ? 0 : attr.depth,
  25239. isRtl = chart.getInherited().rtl,
  25240. min = attr.dataMin + (attr.dataMax - attr.dataMin) * attr.visibleMin,
  25241. max = attr.dataMin + (attr.dataMax - attr.dataMin) * attr.visibleMax,
  25242. context = {
  25243. attr: attr,
  25244. segmenter: me.getSegmenter(),
  25245. renderer: me.defaultRenderer
  25246. };
  25247. if (attr.position === 'left' || attr.position === 'right') {
  25248. attr.translationX = 0;
  25249. attr.translationY = max * (attr.length - depth) / (max - min) + depth;
  25250. attr.scalingX = 1;
  25251. attr.scalingY = (-attr.length + depth) / (max - min);
  25252. attr.scalingCenterY = 0;
  25253. attr.scalingCenterX = 0;
  25254. me.applyTransformations(true);
  25255. } else if (attr.position === 'top' || attr.position === 'bottom') {
  25256. if (isRtl) {
  25257. attr.translationX = attr.length + min * attr.length / (max - min) + 1;
  25258. } else {
  25259. attr.translationX = -min * attr.length / (max - min);
  25260. }
  25261. attr.translationY = 0;
  25262. attr.scalingX = (isRtl ? -1 : 1) * (attr.length - depth) / (max - min);
  25263. attr.scalingY = 1;
  25264. attr.scalingCenterY = 0;
  25265. attr.scalingCenterX = 0;
  25266. me.applyTransformations(true);
  25267. }
  25268. if (layout) {
  25269. layout.calculateLayout(context);
  25270. me.setLayoutContext(context);
  25271. }
  25272. },
  25273. renderAxisLine: function(surface, ctx, layout, clipRect) {
  25274. var me = this,
  25275. attr = me.attr,
  25276. halfLineWidth = attr.lineWidth * 0.5,
  25277. layout = me.getLayout(),
  25278. depth = layout.isDiscrete ? 0 : attr.depth,
  25279. docked = attr.position,
  25280. position, gaugeAngles;
  25281. if (attr.axisLine && attr.length) {
  25282. switch (docked) {
  25283. case 'left':
  25284. position = surface.roundPixel(clipRect[2]) - halfLineWidth;
  25285. ctx.moveTo(position, -attr.endGap + depth);
  25286. ctx.lineTo(position, attr.length + attr.startGap);
  25287. break;
  25288. case 'right':
  25289. ctx.moveTo(halfLineWidth, -attr.endGap);
  25290. ctx.lineTo(halfLineWidth, attr.length + attr.startGap);
  25291. break;
  25292. case 'bottom':
  25293. ctx.moveTo(-attr.startGap, halfLineWidth);
  25294. ctx.lineTo(attr.length - depth + attr.endGap, halfLineWidth);
  25295. break;
  25296. case 'top':
  25297. position = surface.roundPixel(clipRect[3]) - halfLineWidth;
  25298. ctx.moveTo(-attr.startGap, position);
  25299. ctx.lineTo(attr.length + attr.endGap, position);
  25300. break;
  25301. case 'angular':
  25302. ctx.moveTo(attr.centerX + attr.length, attr.centerY);
  25303. ctx.arc(attr.centerX, attr.centerY, attr.length, 0, Math.PI * 2, true);
  25304. break;
  25305. case 'gauge':
  25306. gaugeAngles = me.getGaugeAngles();
  25307. ctx.moveTo(attr.centerX + Math.cos(gaugeAngles.start) * attr.length, attr.centerY + Math.sin(gaugeAngles.start) * attr.length);
  25308. ctx.arc(attr.centerX, attr.centerY, attr.length, gaugeAngles.start, gaugeAngles.end, true);
  25309. break;
  25310. }
  25311. }
  25312. }
  25313. });
  25314. /**
  25315. * @class Ext.chart.axis.Axis3D
  25316. * @extends Ext.chart.axis.Axis
  25317. * @xtype axis3d
  25318. *
  25319. * Defines a 3D axis for charts.
  25320. *
  25321. * A 3D axis has the same properties as the regular {@link Ext.chart.axis.Axis axis},
  25322. * plus a notion of depth. The depth of the 3D axis is determined automatically
  25323. * based on the depth of the bound series.
  25324. *
  25325. * This type of axis has the following limitations compared to the regular axis class:
  25326. * - supported {@link Ext.chart.axis.Axis#position positions} are 'left' and 'bottom' only;
  25327. * - floating axes are not supported.
  25328. *
  25329. * At the present moment only {@link Ext.chart.series.Bar3D} series can make use of the 3D axis.
  25330. */
  25331. Ext.define('Ext.chart.axis.Axis3D', {
  25332. extend: 'Ext.chart.axis.Axis',
  25333. xtype: 'axis3d',
  25334. requires: [
  25335. 'Ext.chart.axis.sprite.Axis3D'
  25336. ],
  25337. config: {
  25338. /**
  25339. * @private
  25340. * The depth of the axis. Determined automatically.
  25341. */
  25342. depth: 0
  25343. },
  25344. /**
  25345. * @cfg {String} position
  25346. * Where to set the axis. Available options are `left` and `bottom`.
  25347. */
  25348. onSeriesChange: function(chart) {
  25349. var me = this,
  25350. eventName = 'depthchange',
  25351. listenerName = 'onSeriesDepthChange',
  25352. i, series;
  25353. function toggle(action) {
  25354. var boundSeries = me.boundSeries;
  25355. for (i = 0; i < boundSeries.length; i++) {
  25356. series = boundSeries[i];
  25357. series[action](eventName, listenerName, me);
  25358. }
  25359. }
  25360. // Remove 'depthchange' listeners from old bound series, if any.
  25361. toggle('un');
  25362. me.callParent(arguments);
  25363. // Add 'depthchange' listeners to new bound series.
  25364. toggle('on');
  25365. },
  25366. onSeriesDepthChange: function(series, depth) {
  25367. var me = this,
  25368. maxDepth = depth,
  25369. boundSeries = me.boundSeries,
  25370. i, item;
  25371. if (depth > me.getDepth()) {
  25372. maxDepth = depth;
  25373. } else {
  25374. for (i = 0; i < boundSeries.length; i++) {
  25375. item = boundSeries[i];
  25376. if (item !== series && item.getDepth) {
  25377. depth = item.getDepth();
  25378. if (depth > maxDepth) {
  25379. maxDepth = depth;
  25380. }
  25381. }
  25382. }
  25383. }
  25384. me.setDepth(maxDepth);
  25385. },
  25386. updateDepth: function(depth) {
  25387. var me = this,
  25388. sprites = me.getSprites(),
  25389. attr = {
  25390. depth: depth
  25391. };
  25392. if (sprites && sprites.length) {
  25393. sprites[0].setAttributes(attr);
  25394. }
  25395. if (me.gridSpriteEven && me.gridSpriteOdd) {
  25396. me.gridSpriteEven.getTemplate().setAttributes(attr);
  25397. me.gridSpriteOdd.getTemplate().setAttributes(attr);
  25398. }
  25399. },
  25400. getGridAlignment: function() {
  25401. switch (this.getPosition()) {
  25402. case 'left':
  25403. case 'right':
  25404. return 'horizontal3d';
  25405. case 'top':
  25406. case 'bottom':
  25407. return 'vertical3d';
  25408. }
  25409. }
  25410. });
  25411. /**
  25412. * @class Ext.chart.axis.Category
  25413. * @extends Ext.chart.axis.Axis
  25414. *
  25415. * A type of axis that displays items in categories. This axis is generally used to
  25416. * display categorical information like names of items, month names, quarters, etc.
  25417. * but no quantitative values. For that other type of information {@link Ext.chart.axis.Numeric Numeric}
  25418. * axis are more suitable.
  25419. *
  25420. * As with other axis you can set the position of the axis and its title. For example:
  25421. *
  25422. * @example
  25423. * Ext.create({
  25424. * xtype: 'cartesian',
  25425. * renderTo: document.body,
  25426. * width: 600,
  25427. * height: 400,
  25428. * innerPadding: '0 40 0 40',
  25429. * store: {
  25430. * fields: ['name', 'data1', 'data2', 'data3'],
  25431. * data: [{
  25432. * 'name': 'metric one',
  25433. * 'data1': 10,
  25434. * 'data2': 12,
  25435. * 'data3': 14
  25436. * }, {
  25437. * 'name': 'metric two',
  25438. * 'data1': 7,
  25439. * 'data2': 8,
  25440. * 'data3': 16
  25441. * }, {
  25442. * 'name': 'metric three',
  25443. * 'data1': 5,
  25444. * 'data2': 2,
  25445. * 'data3': 14
  25446. * }, {
  25447. * 'name': 'metric four',
  25448. * 'data1': 2,
  25449. * 'data2': 14,
  25450. * 'data3': 6
  25451. * }, {
  25452. * 'name': 'metric five',
  25453. * 'data1': 27,
  25454. * 'data2': 38,
  25455. * 'data3': 36
  25456. * }]
  25457. * },
  25458. * axes: {
  25459. * type: 'category',
  25460. * position: 'bottom',
  25461. * fields: ['name'],
  25462. * title: {
  25463. * text: 'Sample Values',
  25464. * fontSize: 15
  25465. * }
  25466. * },
  25467. * series: {
  25468. * type: 'area',
  25469. * subStyle: {
  25470. * fill: ['#0A3F50', '#30BDA7', '#96D4C6']
  25471. * },
  25472. * xField: 'name',
  25473. * yField: ['data1', 'data2', 'data3']
  25474. * }
  25475. * });
  25476. *
  25477. * In this example with set the category axis to the bottom of the surface, bound the axis to
  25478. * the `name` property and set as title "Sample Values".
  25479. */
  25480. Ext.define('Ext.chart.axis.Category', {
  25481. requires: [
  25482. 'Ext.chart.axis.layout.CombineDuplicate',
  25483. 'Ext.chart.axis.segmenter.Names'
  25484. ],
  25485. extend: 'Ext.chart.axis.Axis',
  25486. alias: 'axis.category',
  25487. type: 'category',
  25488. isCategory: true,
  25489. config: {
  25490. layout: 'combineDuplicate',
  25491. segmenter: 'names'
  25492. }
  25493. });
  25494. /**
  25495. * Category 3D Axis
  25496. */
  25497. Ext.define('Ext.chart.axis.Category3D', {
  25498. requires: [
  25499. 'Ext.chart.axis.layout.CombineDuplicate',
  25500. 'Ext.chart.axis.segmenter.Names'
  25501. ],
  25502. extend: 'Ext.chart.axis.Axis3D',
  25503. alias: 'axis.category3d',
  25504. type: 'category3d',
  25505. config: {
  25506. layout: 'combineDuplicate',
  25507. segmenter: 'names'
  25508. }
  25509. });
  25510. /**
  25511. * @class Ext.chart.axis.Numeric
  25512. * @extends Ext.chart.axis.Axis
  25513. *
  25514. * An axis to handle numeric values. This axis is used for quantitative data as
  25515. * opposed to the category axis. You can set minimum and maximum values to the
  25516. * axis so that the values are bound to that. If no values are set, then the
  25517. * scale will auto-adjust to the values.
  25518. *
  25519. * @example
  25520. * Ext.create({
  25521. * xtype: 'cartesian',
  25522. * renderTo: document.body,
  25523. * width: 600,
  25524. * height: 400,
  25525. * store: {
  25526. * fields: ['name', 'data1', 'data2', 'data3'],
  25527. * data: [{
  25528. * 'name': 1,
  25529. * 'data1': 10,
  25530. * 'data2': 12,
  25531. * 'data3': 14
  25532. * }, {
  25533. * 'name': 2,
  25534. * 'data1': 7,
  25535. * 'data2': 8,
  25536. * 'data3': 16
  25537. * }, {
  25538. * 'name': 3,
  25539. * 'data1': 5,
  25540. * 'data2': 2,
  25541. * 'data3': 14
  25542. * }, {
  25543. * 'name': 4,
  25544. * 'data1': 2,
  25545. * 'data2': 14,
  25546. * 'data3': 6
  25547. * }, {
  25548. * 'name': 5,
  25549. * 'data1': 27,
  25550. * 'data2': 38,
  25551. * 'data3': 36
  25552. * }]
  25553. * },
  25554. * axes: {
  25555. * type: 'numeric',
  25556. * position: 'left',
  25557. * minimum: 0,
  25558. * fields: ['data1', 'data2', 'data3'],
  25559. * title: 'Sample Values',
  25560. * grid: {
  25561. * odd: {
  25562. * opacity: 1,
  25563. * fill: '#F2F2F2',
  25564. * stroke: '#DDD',
  25565. * 'lineWidth': 1
  25566. * }
  25567. * }
  25568. * },
  25569. * series: {
  25570. * type: 'area',
  25571. * subStyle: {
  25572. * fill: ['#0A3F50', '#30BDA7', '#96D4C6']
  25573. * },
  25574. * xField: 'name',
  25575. * yField: ['data1', 'data2', 'data3']
  25576. * }
  25577. * });
  25578. *
  25579. * In this example we create an axis of Numeric type. We set a minimum value so that
  25580. * even if all series have values greater than zero, the grid starts at zero. We bind
  25581. * the axis onto the left part of the surface by setting _position_ to _left_.
  25582. * We bind three different store fields to this axis by setting _fields_ to an array.
  25583. * We set the title of the axis to _Number of Hits_ by using the _title_ property.
  25584. * We use a _grid_ configuration to set odd background rows to a certain style and even rows
  25585. * to be transparent/ignored.
  25586. *
  25587. */
  25588. Ext.define('Ext.chart.axis.Numeric', {
  25589. extend: 'Ext.chart.axis.Axis',
  25590. type: 'numeric',
  25591. alias: [
  25592. 'axis.numeric',
  25593. 'axis.radial'
  25594. ],
  25595. // legacy charts compatibility
  25596. requires: [
  25597. 'Ext.chart.axis.layout.Continuous',
  25598. 'Ext.chart.axis.segmenter.Numeric'
  25599. ],
  25600. config: {
  25601. layout: 'continuous',
  25602. segmenter: 'numeric',
  25603. aggregator: 'double'
  25604. }
  25605. });
  25606. /**
  25607. * @class Ext.chart.axis.Numeric3D
  25608. */
  25609. Ext.define('Ext.chart.axis.Numeric3D', {
  25610. extend: 'Ext.chart.axis.Axis3D',
  25611. alias: [
  25612. 'axis.numeric3d'
  25613. ],
  25614. type: 'numeric3d',
  25615. requires: [
  25616. 'Ext.chart.axis.layout.Continuous',
  25617. 'Ext.chart.axis.segmenter.Numeric'
  25618. ],
  25619. config: {
  25620. layout: 'continuous',
  25621. segmenter: 'numeric',
  25622. aggregator: 'double'
  25623. }
  25624. });
  25625. /**
  25626. * @class Ext.chart.axis.Time
  25627. * @extends Ext.chart.axis.Numeric
  25628. *
  25629. * A type of axis whose units are measured in time values. Use this axis
  25630. * for listing dates that you will want to group or dynamically change.
  25631. * If you just want to display dates as categories then use the
  25632. * Category class for axis instead.
  25633. *
  25634. * @example
  25635. * Ext.create({
  25636. * xtype: 'cartesian',
  25637. * renderTo: document.body,
  25638. * width: 600,
  25639. * height: 400,
  25640. * store: {
  25641. * fields: ['time', 'open', 'high', 'low', 'close'],
  25642. * data: [{
  25643. * 'time': new Date('Jan 1 2010').getTime(),
  25644. * 'open': 600,
  25645. * 'high': 614,
  25646. * 'low': 578,
  25647. * 'close': 590
  25648. * }, {
  25649. * 'time': new Date('Jan 2 2010').getTime(),
  25650. * 'open': 590,
  25651. * 'high': 609,
  25652. * 'low': 580,
  25653. * 'close': 580
  25654. * }, {
  25655. * 'time': new Date('Jan 3 2010').getTime(),
  25656. * 'open': 580,
  25657. * 'high': 602,
  25658. * 'low': 578,
  25659. * 'close': 602
  25660. * }, {
  25661. * 'time': new Date('Jan 4 2010').getTime(),
  25662. * 'open': 602,
  25663. * 'high': 614,
  25664. * 'low': 586,
  25665. * 'close': 586
  25666. * }]
  25667. * },
  25668. * axes: [{
  25669. * type: 'numeric',
  25670. * position: 'left',
  25671. * fields: ['open', 'high', 'low', 'close'],
  25672. * title: {
  25673. * text: 'Sample Values',
  25674. * fontSize: 15
  25675. * },
  25676. * grid: true,
  25677. * minimum: 560,
  25678. * maximum: 640
  25679. * }, {
  25680. * type: 'time',
  25681. * position: 'bottom',
  25682. * fields: ['time'],
  25683. * fromDate: new Date('Dec 31 2009'),
  25684. * toDate: new Date('Jan 5 2010'),
  25685. * title: {
  25686. * text: 'Sample Values',
  25687. * fontSize: 15
  25688. * },
  25689. * style: {
  25690. * axisLine: false
  25691. * }
  25692. * }],
  25693. * series: {
  25694. * type: 'candlestick',
  25695. * xField: 'time',
  25696. * openField: 'open',
  25697. * highField: 'high',
  25698. * lowField: 'low',
  25699. * closeField: 'close',
  25700. * style: {
  25701. * ohlcType: 'ohlc',
  25702. * dropStyle: {
  25703. * fill: 'rgb(255, 128, 128)',
  25704. * stroke: 'rgb(255, 128, 128)',
  25705. * lineWidth: 3
  25706. * },
  25707. * raiseStyle: {
  25708. * fill: 'rgb(48, 189, 167)',
  25709. * stroke: 'rgb(48, 189, 167)',
  25710. * lineWidth: 3
  25711. * }
  25712. * }
  25713. * }
  25714. * });
  25715. */
  25716. Ext.define('Ext.chart.axis.Time', {
  25717. extend: 'Ext.chart.axis.Numeric',
  25718. alias: 'axis.time',
  25719. type: 'time',
  25720. requires: [
  25721. 'Ext.chart.axis.layout.Continuous',
  25722. 'Ext.chart.axis.segmenter.Time'
  25723. ],
  25724. config: {
  25725. /**
  25726. * @cfg {String} dateFormat
  25727. * Indicates the format the date will be rendered in.
  25728. * For example: 'M d' will render the dates as 'Jan 30'.
  25729. * This config works by setting the {@link #renderer} config
  25730. * to a function that uses {@link Ext.Date#format} to format the dates
  25731. * using the given `dateFormat`.
  25732. * If the {@link #renderer} config was set by the user, changes to this config
  25733. * won't replace the user set renderer (until the user removes the renderer by
  25734. * setting the `renderer` config to `null`). In this case the way the `dateFormat`
  25735. * is used (if at all) is up to the user.
  25736. */
  25737. dateFormat: null,
  25738. /**
  25739. * @cfg {Date} fromDate The starting date for the time axis.
  25740. */
  25741. fromDate: null,
  25742. /**
  25743. * @cfg {Date} toDate The ending date for the time axis.
  25744. */
  25745. toDate: null,
  25746. layout: 'continuous',
  25747. segmenter: 'time',
  25748. aggregator: 'time'
  25749. },
  25750. updateDateFormat: function(format) {
  25751. var renderer = this.getRenderer();
  25752. if (!renderer || renderer.isDefault) {
  25753. renderer = function(axis, date) {
  25754. return Ext.Date.format(new Date(date), format);
  25755. };
  25756. renderer.isDefault = true;
  25757. this.setRenderer(renderer);
  25758. this.performLayout();
  25759. }
  25760. },
  25761. updateRenderer: function(renderer) {
  25762. var dateFormat = this.getDateFormat();
  25763. if (renderer) {
  25764. this.performLayout();
  25765. } else if (dateFormat) {
  25766. // If the user removes custom `renderer` and `dateFormat` is set,
  25767. // set the `renderer` to the default one based on `dateFormat`.
  25768. this.updateDateFormat(dateFormat);
  25769. }
  25770. },
  25771. updateFromDate: function(date) {
  25772. this.setMinimum(+date);
  25773. },
  25774. updateToDate: function(date) {
  25775. this.setMaximum(+date);
  25776. },
  25777. getCoordFor: function(value) {
  25778. if (Ext.isString(value)) {
  25779. value = new Date(value);
  25780. }
  25781. return +value;
  25782. }
  25783. });
  25784. /**
  25785. * @class Ext.chart.axis.Time3D
  25786. */
  25787. Ext.define('Ext.chart.axis.Time3D', {
  25788. extend: 'Ext.chart.axis.Numeric3D',
  25789. alias: 'axis.time3d',
  25790. type: 'time3d',
  25791. requires: [
  25792. 'Ext.chart.axis.layout.Continuous',
  25793. 'Ext.chart.axis.segmenter.Time'
  25794. ],
  25795. config: {
  25796. /**
  25797. * @cfg {String/Boolean} dateFormat
  25798. * Indicates the format the date will be rendered on.
  25799. * For example: 'M d' will render the dates as 'Jan 30', etc.
  25800. */
  25801. dateFormat: null,
  25802. /**
  25803. * @cfg {Date} fromDate The starting date for the time axis.
  25804. */
  25805. fromDate: null,
  25806. /**
  25807. * @cfg {Date} toDate The ending date for the time axis.
  25808. */
  25809. toDate: null,
  25810. layout: 'continuous',
  25811. segmenter: 'time',
  25812. aggregator: 'time'
  25813. },
  25814. updateDateFormat: function(format) {
  25815. this.setRenderer(function(axis, date) {
  25816. return Ext.Date.format(new Date(date), format);
  25817. });
  25818. },
  25819. updateFromDate: function(date) {
  25820. this.setMinimum(+date);
  25821. },
  25822. updateToDate: function(date) {
  25823. this.setMaximum(+date);
  25824. },
  25825. getCoordFor: function(value) {
  25826. if (Ext.isString(value)) {
  25827. value = new Date(value);
  25828. }
  25829. return +value;
  25830. }
  25831. });
  25832. /**
  25833. * @class Ext.chart.grid.HorizontalGrid3D
  25834. * @extends Ext.chart.grid.HorizontalGrid
  25835. *
  25836. * Horizontal 3D Grid sprite. Used in 3D Cartesian Charts.
  25837. */
  25838. Ext.define('Ext.chart.grid.HorizontalGrid3D', {
  25839. extend: 'Ext.chart.grid.HorizontalGrid',
  25840. alias: 'grid.horizontal3d',
  25841. inheritableStatics: {
  25842. def: {
  25843. processors: {
  25844. depth: 'number'
  25845. },
  25846. defaults: {
  25847. depth: 0
  25848. }
  25849. }
  25850. },
  25851. render: function(surface, ctx, rect) {
  25852. var attr = this.attr,
  25853. x = surface.roundPixel(attr.x),
  25854. y = surface.roundPixel(attr.y),
  25855. dx = surface.matrix.getDX(),
  25856. halfLineWidth = ctx.lineWidth * 0.5,
  25857. height = attr.height,
  25858. depth = attr.depth,
  25859. left, top;
  25860. if (y <= rect[1]) {
  25861. return;
  25862. }
  25863. // Horizontal stripe.
  25864. left = rect[0] + depth - dx;
  25865. top = y + halfLineWidth - depth;
  25866. ctx.beginPath();
  25867. ctx.rect(left, top, rect[2], height);
  25868. ctx.fill();
  25869. // Horizontal line.
  25870. ctx.beginPath();
  25871. ctx.moveTo(left, top);
  25872. ctx.lineTo(left + rect[2], top);
  25873. ctx.stroke();
  25874. // Diagonal stripe.
  25875. left = rect[0] + x - dx;
  25876. top = y + halfLineWidth;
  25877. ctx.beginPath();
  25878. ctx.moveTo(left, top);
  25879. ctx.lineTo(left + depth, top - depth);
  25880. ctx.lineTo(left + depth, top - depth + height);
  25881. ctx.lineTo(left, top + height);
  25882. ctx.closePath();
  25883. ctx.fill();
  25884. // Diagonal line.
  25885. ctx.beginPath();
  25886. ctx.moveTo(left, top);
  25887. ctx.lineTo(left + depth, top - depth);
  25888. ctx.stroke();
  25889. }
  25890. });
  25891. /**
  25892. * @class Ext.chart.grid.VerticalGrid3D
  25893. * @extends Ext.chart.grid.VerticalGrid
  25894. *
  25895. * Vertical 3D Grid sprite. Used in 3D Cartesian Charts.
  25896. */
  25897. Ext.define('Ext.chart.grid.VerticalGrid3D', {
  25898. extend: 'Ext.chart.grid.VerticalGrid',
  25899. alias: 'grid.vertical3d',
  25900. inheritableStatics: {
  25901. def: {
  25902. processors: {
  25903. depth: 'number'
  25904. },
  25905. defaults: {
  25906. depth: 0
  25907. }
  25908. }
  25909. },
  25910. render: function(surface, ctx, clipRect) {
  25911. var attr = this.attr,
  25912. x = surface.roundPixel(attr.x),
  25913. dy = surface.matrix.getDY(),
  25914. halfLineWidth = ctx.lineWidth * 0.5,
  25915. width = attr.width,
  25916. depth = attr.depth,
  25917. left, top;
  25918. if (x >= clipRect[2]) {
  25919. return;
  25920. }
  25921. // Vertical stripe.
  25922. left = x - halfLineWidth + depth;
  25923. top = clipRect[1] - depth - dy;
  25924. ctx.beginPath();
  25925. ctx.rect(left, top, width, clipRect[3]);
  25926. ctx.fill();
  25927. // Vertical line.
  25928. ctx.beginPath();
  25929. ctx.moveTo(left, top);
  25930. ctx.lineTo(left, top + clipRect[3]);
  25931. ctx.stroke();
  25932. // Diagonal stripe.
  25933. left = x - halfLineWidth;
  25934. top = clipRect[3];
  25935. ctx.beginPath();
  25936. ctx.moveTo(left, top);
  25937. ctx.lineTo(left + depth, top - depth);
  25938. ctx.lineTo(left + depth + width, top - depth);
  25939. ctx.lineTo(left + width, top);
  25940. ctx.closePath();
  25941. ctx.fill();
  25942. // Diagonal line.
  25943. left = x - halfLineWidth;
  25944. top = clipRect[3];
  25945. ctx.beginPath();
  25946. ctx.moveTo(left, top);
  25947. ctx.lineTo(left + depth, top - depth);
  25948. ctx.stroke();
  25949. }
  25950. });
  25951. /**
  25952. * @class Ext.chart.interactions.CrossZoom
  25953. * @extends Ext.chart.interactions.Abstract
  25954. *
  25955. * The CrossZoom interaction allows the user to zoom in on a selected area of the chart.
  25956. *
  25957. * @example
  25958. * Ext.create({
  25959. * xtype: 'cartesian',
  25960. * renderTo: Ext.getBody(),
  25961. * width: 600,
  25962. * height: 400,
  25963. * insetPadding: 40,
  25964. * interactions: 'crosszoom',
  25965. * store: {
  25966. * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
  25967. * data: [{
  25968. * 'name': 'metric one',
  25969. * 'data1': 10,
  25970. * 'data2': 12,
  25971. * 'data3': 14,
  25972. * 'data4': 8,
  25973. * 'data5': 13
  25974. * }, {
  25975. * 'name': 'metric two',
  25976. * 'data1': 7,
  25977. * 'data2': 8,
  25978. * 'data3': 16,
  25979. * 'data4': 10,
  25980. * 'data5': 3
  25981. * }, {
  25982. * 'name': 'metric three',
  25983. * 'data1': 5,
  25984. * 'data2': 2,
  25985. * 'data3': 14,
  25986. * 'data4': 12,
  25987. * 'data5': 7
  25988. * }, {
  25989. * 'name': 'metric four',
  25990. * 'data1': 2,
  25991. * 'data2': 14,
  25992. * 'data3': 6,
  25993. * 'data4': 1,
  25994. * 'data5': 23
  25995. * }, {
  25996. * 'name': 'metric five',
  25997. * 'data1': 27,
  25998. * 'data2': 38,
  25999. * 'data3': 36,
  26000. * 'data4': 13,
  26001. * 'data5': 33
  26002. * }]
  26003. * },
  26004. * axes: [{
  26005. * type: 'numeric',
  26006. * position: 'left',
  26007. * fields: ['data1'],
  26008. * title: {
  26009. * text: 'Sample Values',
  26010. * fontSize: 15
  26011. * },
  26012. * grid: true,
  26013. * minimum: 0
  26014. * }, {
  26015. * type: 'category',
  26016. * position: 'bottom',
  26017. * fields: ['name'],
  26018. * title: {
  26019. * text: 'Sample Values',
  26020. * fontSize: 15
  26021. * }
  26022. * }],
  26023. * series: [{
  26024. * type: 'line',
  26025. * highlight: {
  26026. * size: 7,
  26027. * radius: 7
  26028. * },
  26029. * style: {
  26030. * stroke: 'rgb(143,203,203)'
  26031. * },
  26032. * xField: 'name',
  26033. * yField: 'data1',
  26034. * marker: {
  26035. * type: 'path',
  26036. * path: ['M', - 2, 0, 0, 2, 2, 0, 0, - 2, 'Z'],
  26037. * stroke: 'blue',
  26038. * lineWidth: 0
  26039. * }
  26040. * }, {
  26041. * type: 'line',
  26042. * highlight: {
  26043. * size: 7,
  26044. * radius: 7
  26045. * },
  26046. * fill: true,
  26047. * xField: 'name',
  26048. * yField: 'data3',
  26049. * marker: {
  26050. * type: 'circle',
  26051. * radius: 4,
  26052. * lineWidth: 0
  26053. * }
  26054. * }]
  26055. * });
  26056. */
  26057. Ext.define('Ext.chart.interactions.CrossZoom', {
  26058. extend: 'Ext.chart.interactions.Abstract',
  26059. type: 'crosszoom',
  26060. alias: 'interaction.crosszoom',
  26061. isCrossZoom: true,
  26062. config: {
  26063. /**
  26064. * @cfg {Object/Array} axes
  26065. * Specifies which axes should be made navigable. The config value can take the following formats:
  26066. *
  26067. * - An Object whose keys correspond to the {@link Ext.chart.axis.Axis#position position} of each
  26068. * axis that should be made navigable. Each key's value can either be an Object with further
  26069. * configuration options for each axis or simply `true` for a default set of options.
  26070. * {
  26071. * type: 'crosszoom',
  26072. * axes: {
  26073. * left: {
  26074. * maxZoom: 5,
  26075. * allowPan: false
  26076. * },
  26077. * bottom: true
  26078. * }
  26079. * }
  26080. *
  26081. * If using the full Object form, the following options can be specified for each axis:
  26082. *
  26083. * - minZoom (Number) A minimum zoom level for the axis. Defaults to `1` which is its natural size.
  26084. * - maxZoom (Number) A maximum zoom level for the axis. Defaults to `10`.
  26085. * - startZoom (Number) A starting zoom level for the axis. Defaults to `1`.
  26086. * - allowZoom (Boolean) Whether zooming is allowed for the axis. Defaults to `true`.
  26087. * - allowPan (Boolean) Whether panning is allowed for the axis. Defaults to `true`.
  26088. * - startPan (Boolean) A starting panning offset for the axis. Defaults to `0`.
  26089. *
  26090. * - An Array of strings, each one corresponding to the {@link Ext.chart.axis.Axis#position position}
  26091. * of an axis that should be made navigable. The default options will be used for each named axis.
  26092. *
  26093. * {
  26094. * type: 'crosszoom',
  26095. * axes: ['left', 'bottom']
  26096. * }
  26097. *
  26098. * If the `axes` config is not specified, it will default to making all axes navigable with the
  26099. * default axis options.
  26100. */
  26101. axes: true,
  26102. gestures: {
  26103. dragstart: 'onGestureStart',
  26104. drag: 'onGesture',
  26105. dragend: 'onGestureEnd',
  26106. dblclick: 'onDoubleTap'
  26107. },
  26108. undoButton: {}
  26109. },
  26110. stopAnimationBeforeSync: false,
  26111. zoomAnimationInProgress: false,
  26112. constructor: function() {
  26113. this.callParent(arguments);
  26114. this.zoomHistory = [];
  26115. },
  26116. applyAxes: function(axesConfig) {
  26117. var result = {};
  26118. if (axesConfig === true) {
  26119. return {
  26120. top: {},
  26121. right: {},
  26122. bottom: {},
  26123. left: {}
  26124. };
  26125. } else if (Ext.isArray(axesConfig)) {
  26126. // array of axis names - translate to full object form
  26127. result = {};
  26128. Ext.each(axesConfig, function(axis) {
  26129. result[axis] = {};
  26130. });
  26131. } else if (Ext.isObject(axesConfig)) {
  26132. Ext.iterate(axesConfig, function(key, val) {
  26133. // axis name with `true` value -> translate to object
  26134. if (val === true) {
  26135. result[key] = {};
  26136. } else if (val !== false) {
  26137. result[key] = val;
  26138. }
  26139. });
  26140. }
  26141. return result;
  26142. },
  26143. applyUndoButton: function(button, oldButton) {
  26144. var me = this;
  26145. if (oldButton) {
  26146. oldButton.destroy();
  26147. }
  26148. if (button) {
  26149. return Ext.create('Ext.Button', Ext.apply({
  26150. cls: [],
  26151. text: 'Undo Zoom',
  26152. disabled: true,
  26153. handler: function() {
  26154. me.undoZoom();
  26155. }
  26156. }, button));
  26157. }
  26158. },
  26159. getSurface: function() {
  26160. return this.getChart() && this.getChart().getSurface('overlay');
  26161. },
  26162. setSeriesOpacity: function(opacity) {
  26163. var surface = this.getChart() && this.getChart().getSurface('series');
  26164. if (surface) {
  26165. surface.element.setStyle('opacity', opacity);
  26166. }
  26167. },
  26168. onGestureStart: function(e) {
  26169. var me = this,
  26170. chart = me.getChart(),
  26171. surface = me.getSurface(),
  26172. rect = chart.getInnerRect(),
  26173. innerPadding = chart.getInnerPadding(),
  26174. minX = innerPadding.left,
  26175. maxX = minX + rect[2],
  26176. minY = innerPadding.top,
  26177. maxY = minY + rect[3],
  26178. xy = chart.getEventXY(e),
  26179. x = xy[0],
  26180. y = xy[1];
  26181. e.claimGesture();
  26182. if (me.zoomAnimationInProgress) {
  26183. return;
  26184. }
  26185. if (x > minX && x < maxX && y > minY && y < maxY) {
  26186. me.gestureEvent = 'drag';
  26187. me.lockEvents(me.gestureEvent);
  26188. me.startX = x;
  26189. me.startY = y;
  26190. me.selectionRect = surface.add({
  26191. type: 'rect',
  26192. globalAlpha: 0.5,
  26193. fillStyle: 'rgba(80,80,140,0.5)',
  26194. strokeStyle: 'rgba(80,80,140,1)',
  26195. lineWidth: 2,
  26196. x: x,
  26197. y: y,
  26198. width: 0,
  26199. height: 0,
  26200. zIndex: 10000
  26201. });
  26202. me.setSeriesOpacity(0.8);
  26203. return false;
  26204. }
  26205. },
  26206. onGesture: function(e) {
  26207. var me = this;
  26208. if (me.zoomAnimationInProgress) {
  26209. return;
  26210. }
  26211. if (me.getLocks()[me.gestureEvent] === me) {
  26212. var chart = me.getChart(),
  26213. surface = me.getSurface(),
  26214. rect = chart.getInnerRect(),
  26215. innerPadding = chart.getInnerPadding(),
  26216. minX = innerPadding.left,
  26217. maxX = minX + rect[2],
  26218. minY = innerPadding.top,
  26219. maxY = minY + rect[3],
  26220. xy = chart.getEventXY(e),
  26221. x = xy[0],
  26222. y = xy[1];
  26223. if (x < minX) {
  26224. x = minX;
  26225. } else if (x > maxX) {
  26226. x = maxX;
  26227. }
  26228. if (y < minY) {
  26229. y = minY;
  26230. } else if (y > maxY) {
  26231. y = maxY;
  26232. }
  26233. me.selectionRect.setAttributes({
  26234. width: x - me.startX,
  26235. height: y - me.startY
  26236. });
  26237. if (Math.abs(me.startX - x) < 11 || Math.abs(me.startY - y) < 11) {
  26238. me.selectionRect.setAttributes({
  26239. globalAlpha: 0.5
  26240. });
  26241. } else {
  26242. me.selectionRect.setAttributes({
  26243. globalAlpha: 1
  26244. });
  26245. }
  26246. surface.renderFrame();
  26247. return false;
  26248. }
  26249. },
  26250. onGestureEnd: function(e) {
  26251. var me = this;
  26252. if (me.zoomAnimationInProgress) {
  26253. return;
  26254. }
  26255. if (me.getLocks()[me.gestureEvent] === me) {
  26256. var chart = me.getChart(),
  26257. surface = me.getSurface(),
  26258. rect = chart.getInnerRect(),
  26259. innerPadding = chart.getInnerPadding(),
  26260. minX = innerPadding.left,
  26261. maxX = minX + rect[2],
  26262. minY = innerPadding.top,
  26263. maxY = minY + rect[3],
  26264. rectWidth = rect[2],
  26265. rectHeight = rect[3],
  26266. xy = chart.getEventXY(e),
  26267. x = xy[0],
  26268. y = xy[1];
  26269. if (x < minX) {
  26270. x = minX;
  26271. } else if (x > maxX) {
  26272. x = maxX;
  26273. }
  26274. if (y < minY) {
  26275. y = minY;
  26276. } else if (y > maxY) {
  26277. y = maxY;
  26278. }
  26279. if (Math.abs(me.startX - x) < 11 || Math.abs(me.startY - y) < 11) {
  26280. surface.remove(me.selectionRect);
  26281. } else {
  26282. me.zoomBy([
  26283. Math.min(me.startX, x) / rectWidth,
  26284. 1 - Math.max(me.startY, y) / rectHeight,
  26285. Math.max(me.startX, x) / rectWidth,
  26286. 1 - Math.min(me.startY, y) / rectHeight
  26287. ]);
  26288. me.selectionRect.setAttributes({
  26289. x: Math.min(me.startX, x),
  26290. y: Math.min(me.startY, y),
  26291. width: Math.abs(me.startX - x),
  26292. height: Math.abs(me.startY - y)
  26293. });
  26294. me.selectionRect.setAnimation(chart.getAnimation() || {
  26295. duration: 0
  26296. });
  26297. me.selectionRect.setAttributes({
  26298. globalAlpha: 0,
  26299. x: 0,
  26300. y: 0,
  26301. width: rectWidth,
  26302. height: rectHeight
  26303. });
  26304. me.zoomAnimationInProgress = true;
  26305. chart.suspendThicknessChanged();
  26306. me.selectionRect.getAnimation().on('animationend', function() {
  26307. chart.resumeThicknessChanged();
  26308. surface.remove(me.selectionRect);
  26309. me.selectionRect = null;
  26310. me.zoomAnimationInProgress = false;
  26311. });
  26312. }
  26313. surface.renderFrame();
  26314. me.sync();
  26315. me.unlockEvents(me.gestureEvent);
  26316. me.setSeriesOpacity(1);
  26317. if (!me.zoomAnimationInProgress) {
  26318. surface.remove(me.selectionRect);
  26319. me.selectionRect = null;
  26320. }
  26321. }
  26322. },
  26323. zoomBy: function(rect) {
  26324. var me = this,
  26325. axisConfigs = me.getAxes(),
  26326. chart = me.getChart(),
  26327. axes = chart.getAxes(),
  26328. isRtl = chart.getInherited().rtl,
  26329. config,
  26330. zoomMap = {},
  26331. x1, x2;
  26332. if (isRtl) {
  26333. rect = rect.slice();
  26334. x1 = 1 - rect[0];
  26335. x2 = 1 - rect[2];
  26336. rect[0] = Math.min(x1, x2);
  26337. rect[2] = Math.max(x1, x2);
  26338. }
  26339. for (var i = 0; i < axes.length; i++) {
  26340. var axis = axes[i];
  26341. config = axisConfigs[axis.getPosition()];
  26342. if (config && config.allowZoom !== false) {
  26343. var isSide = axis.isSide(),
  26344. oldRange = axis.getVisibleRange();
  26345. zoomMap[axis.getId()] = oldRange.slice(0);
  26346. if (!isSide) {
  26347. axis.setVisibleRange([
  26348. (oldRange[1] - oldRange[0]) * rect[0] + oldRange[0],
  26349. (oldRange[1] - oldRange[0]) * rect[2] + oldRange[0]
  26350. ]);
  26351. } else {
  26352. axis.setVisibleRange([
  26353. (oldRange[1] - oldRange[0]) * rect[1] + oldRange[0],
  26354. (oldRange[1] - oldRange[0]) * rect[3] + oldRange[0]
  26355. ]);
  26356. }
  26357. }
  26358. }
  26359. me.zoomHistory.push(zoomMap);
  26360. me.getUndoButton().setDisabled(false);
  26361. },
  26362. undoZoom: function() {
  26363. var zoomMap = this.zoomHistory.pop(),
  26364. axes = this.getChart().getAxes();
  26365. if (zoomMap) {
  26366. for (var i = 0; i < axes.length; i++) {
  26367. var axis = axes[i];
  26368. if (zoomMap[axis.getId()]) {
  26369. axis.setVisibleRange(zoomMap[axis.getId()]);
  26370. }
  26371. }
  26372. }
  26373. this.getUndoButton().setDisabled(this.zoomHistory.length === 0);
  26374. this.sync();
  26375. },
  26376. onDoubleTap: function(e) {
  26377. this.undoZoom();
  26378. },
  26379. destroy: function() {
  26380. this.setUndoButton(null);
  26381. this.callParent();
  26382. }
  26383. });
  26384. /**
  26385. * The Crosshair interaction allows the user to get precise values for a specific point on the chart.
  26386. * The values are obtained by single-touch dragging on the chart.
  26387. *
  26388. * @example
  26389. * Ext.create('Ext.Container', {
  26390. * renderTo: Ext.getBody(),
  26391. * width: 600,
  26392. * height: 400,
  26393. * layout: 'fit',
  26394. * items: {
  26395. * xtype: 'cartesian',
  26396. * innerPadding: 20,
  26397. * interactions: {
  26398. * type: 'crosshair',
  26399. * axes: {
  26400. * left: {
  26401. * label: {
  26402. * fillStyle: 'white'
  26403. * },
  26404. * rect: {
  26405. * fillStyle: 'brown',
  26406. * radius: 6
  26407. * }
  26408. * },
  26409. * bottom: {
  26410. * label: {
  26411. * fontSize: '14px',
  26412. * fontWeight: 'bold'
  26413. * }
  26414. * }
  26415. * },
  26416. * lines: {
  26417. * horizontal: {
  26418. * strokeStyle: 'brown',
  26419. * lineWidth: 2,
  26420. * lineDash: [20, 2, 2, 2, 2, 2, 2, 2]
  26421. * }
  26422. * }
  26423. * },
  26424. * store: {
  26425. * fields: ['name', 'data'],
  26426. * data: [
  26427. * {name: 'apple', data: 300},
  26428. * {name: 'orange', data: 900},
  26429. * {name: 'banana', data: 800},
  26430. * {name: 'pear', data: 400},
  26431. * {name: 'grape', data: 500}
  26432. * ]
  26433. * },
  26434. * axes: [{
  26435. * type: 'numeric',
  26436. * position: 'left',
  26437. * fields: ['data'],
  26438. * title: {
  26439. * text: 'Value',
  26440. * fontSize: 15
  26441. * },
  26442. * grid: true,
  26443. * label: {
  26444. * rotationRads: -Math.PI / 4
  26445. * }
  26446. * }, {
  26447. * type: 'category',
  26448. * position: 'bottom',
  26449. * fields: ['name'],
  26450. * title: {
  26451. * text: 'Category',
  26452. * fontSize: 15
  26453. * }
  26454. * }],
  26455. * series: {
  26456. * type: 'line',
  26457. * style: {
  26458. * strokeStyle: 'black'
  26459. * },
  26460. * xField: 'name',
  26461. * yField: 'data',
  26462. * marker: {
  26463. * type: 'circle',
  26464. * radius: 5,
  26465. * fillStyle: 'lightblue'
  26466. * }
  26467. * }
  26468. * }
  26469. * });
  26470. */
  26471. Ext.define('Ext.chart.interactions.Crosshair', {
  26472. extend: 'Ext.chart.interactions.Abstract',
  26473. requires: [
  26474. 'Ext.chart.grid.HorizontalGrid',
  26475. 'Ext.chart.grid.VerticalGrid',
  26476. 'Ext.chart.CartesianChart',
  26477. 'Ext.chart.axis.layout.Discrete'
  26478. ],
  26479. type: 'crosshair',
  26480. alias: 'interaction.crosshair',
  26481. config: {
  26482. /**
  26483. * @cfg {Object} axes
  26484. * Specifies label text and label rect configs on per axis basis or as a single config for all axes.
  26485. *
  26486. * {
  26487. * type: 'crosshair',
  26488. * axes: {
  26489. * label: { fillStyle: 'white' },
  26490. * rect: { fillStyle: 'maroon'}
  26491. * }
  26492. * }
  26493. *
  26494. * In case per axis configuration is used, an object with keys corresponding
  26495. * to the {@link Ext.chart.axis.Axis#position position} must be provided.
  26496. *
  26497. * {
  26498. * type: 'crosshair',
  26499. * axes: {
  26500. * left: {
  26501. * label: { fillStyle: 'white' },
  26502. * rect: {
  26503. * fillStyle: 'maroon',
  26504. * radius: 4
  26505. * }
  26506. * },
  26507. * bottom: {
  26508. * label: {
  26509. * fontSize: '14px',
  26510. * fontWeight: 'bold'
  26511. * },
  26512. * rect: { fillStyle: 'white' }
  26513. * }
  26514. * }
  26515. *
  26516. * If the `axes` config is not specified, the following defaults will be used:
  26517. * - `label` will use values from the {@link Ext.chart.axis.Axis#label label} config.
  26518. * - `rect` will use the 'white' fillStyle.
  26519. */
  26520. axes: {
  26521. top: {
  26522. label: {},
  26523. rect: {}
  26524. },
  26525. right: {
  26526. label: {},
  26527. rect: {}
  26528. },
  26529. bottom: {
  26530. label: {},
  26531. rect: {}
  26532. },
  26533. left: {
  26534. label: {},
  26535. rect: {}
  26536. }
  26537. },
  26538. /**
  26539. * @cfg {Object} lines
  26540. * Specifies attributes of horizontal and vertical lines that make up the crosshair.
  26541. * If this config is missing, black dashed lines will be used.
  26542. *
  26543. * {
  26544. * horizontal: {
  26545. * strokeStyle: 'red',
  26546. * lineDash: [] // solid line
  26547. * },
  26548. * vertical: {
  26549. * lineWidth: 2,
  26550. * lineDash: [15, 5, 5, 5]
  26551. * }
  26552. * }
  26553. */
  26554. lines: {
  26555. horizontal: {
  26556. strokeStyle: 'black',
  26557. lineDash: [
  26558. 5,
  26559. 5
  26560. ]
  26561. },
  26562. vertical: {
  26563. strokeStyle: 'black',
  26564. lineDash: [
  26565. 5,
  26566. 5
  26567. ]
  26568. }
  26569. },
  26570. /**
  26571. * @cfg {String} gesture
  26572. * Specifies which gesture should be used for starting/maintaining/ending the interaction.
  26573. */
  26574. gesture: 'drag'
  26575. },
  26576. applyAxes: function(axesConfig, oldAxesConfig) {
  26577. return Ext.merge(oldAxesConfig || {}, axesConfig);
  26578. },
  26579. applyLines: function(linesConfig, oldLinesConfig) {
  26580. return Ext.merge(oldLinesConfig || {}, linesConfig);
  26581. },
  26582. updateChart: function(chart) {
  26583. if (chart && !chart.isCartesian) {
  26584. Ext.raise("Crosshair interaction can only be used on cartesian charts.");
  26585. }
  26586. this.callParent(arguments);
  26587. },
  26588. getGestures: function() {
  26589. var me = this,
  26590. gestures = {},
  26591. gesture = me.getGesture();
  26592. gestures[gesture] = 'onGesture';
  26593. gestures[gesture + 'start'] = 'onGestureStart';
  26594. gestures[gesture + 'end'] = 'onGestureEnd';
  26595. gestures[gesture + 'cancel'] = 'onGestureCancel';
  26596. return gestures;
  26597. },
  26598. onGestureStart: function(e) {
  26599. var me = this,
  26600. chart = me.getChart(),
  26601. axesTheme = chart.getTheme().getAxis(),
  26602. axisTheme,
  26603. surface = chart.getSurface('overlay'),
  26604. rect = chart.getInnerRect(),
  26605. chartWidth = rect[2],
  26606. chartHeight = rect[3],
  26607. xy = chart.getEventXY(e),
  26608. x = xy[0],
  26609. y = xy[1],
  26610. axes = chart.getAxes(),
  26611. axesConfig = me.getAxes(),
  26612. linesConfig = me.getLines(),
  26613. axis, axisSurface, axisRect, axisWidth, axisHeight, axisPosition, axisAlignment, axisLabel, axisLabelConfig, crosshairLabelConfig, tickPadding, axisSprite, attr, axisThickness, lineWidth, halfLineWidth, title, titleBBox, horizontalLineCfg, verticalLineCfg, i;
  26614. e.claimGesture();
  26615. if (x > 0 && x < chartWidth && y > 0 && y < chartHeight) {
  26616. me.lockEvents(me.getGesture());
  26617. horizontalLineCfg = Ext.apply({
  26618. xclass: 'Ext.chart.grid.HorizontalGrid',
  26619. x: 0,
  26620. y: y,
  26621. width: chartWidth
  26622. }, linesConfig.horizontal);
  26623. verticalLineCfg = Ext.apply({
  26624. xclass: 'Ext.chart.grid.VerticalGrid',
  26625. x: x,
  26626. y: 0,
  26627. height: chartHeight
  26628. }, linesConfig.vertical);
  26629. me.axesLabels = me.axesLabels || {};
  26630. for (i = 0; i < axes.length; i++) {
  26631. axis = axes[i];
  26632. axisSurface = axis.getSurface();
  26633. axisRect = axisSurface.getRect();
  26634. axisSprite = axis.getSprites()[0];
  26635. axisWidth = axisRect[2];
  26636. axisHeight = axisRect[3];
  26637. axisPosition = axis.getPosition();
  26638. axisAlignment = axis.getAlignment();
  26639. title = axis.getTitle();
  26640. titleBBox = title && title.attr.text !== '' && title.getBBox();
  26641. attr = axisSprite.attr;
  26642. axisThickness = axisSprite.thickness;
  26643. lineWidth = attr.axisLine ? attr.lineWidth : 0;
  26644. halfLineWidth = lineWidth / 2;
  26645. tickPadding = Math.max(attr.majorTickSize, attr.minorTickSize) + lineWidth;
  26646. axisLabel = me.axesLabels[axisPosition] = axisSurface.add({
  26647. type: 'composite'
  26648. });
  26649. axisLabel.labelRect = axisLabel.addSprite(Ext.apply({
  26650. type: 'rect',
  26651. fillStyle: 'white',
  26652. x: axisPosition === 'right' ? lineWidth : 0,
  26653. y: axisPosition === 'bottom' ? lineWidth : 0,
  26654. width: axisWidth - lineWidth - (axisAlignment === 'vertical' && titleBBox ? titleBBox.width : 0),
  26655. height: axisHeight - lineWidth - (axisAlignment === 'horizontal' && titleBBox ? titleBBox.height : 0),
  26656. translationX: axisPosition === 'left' && titleBBox ? titleBBox.width : 0,
  26657. translationY: axisPosition === 'top' && titleBBox ? titleBBox.height : 0
  26658. }, axesConfig.rect || axesConfig[axisPosition].rect));
  26659. if (axisAlignment === 'vertical' && !verticalLineCfg.strokeStyle) {
  26660. verticalLineCfg.strokeStyle = attr.strokeStyle;
  26661. }
  26662. if (axisAlignment === 'horizontal' && !horizontalLineCfg.strokeStyle) {
  26663. horizontalLineCfg.strokeStyle = attr.strokeStyle;
  26664. }
  26665. axisTheme = Ext.merge({}, axesTheme.defaults, axesTheme[axisPosition]);
  26666. axisLabelConfig = Ext.apply({}, axis.config.label, axisTheme.label);
  26667. crosshairLabelConfig = axesConfig.label || axesConfig[axisPosition].label;
  26668. axisLabel.labelText = axisLabel.addSprite(Ext.apply(axisLabelConfig, crosshairLabelConfig, {
  26669. type: 'text',
  26670. x: me.calculateLabelTextPoint(false, axisPosition, tickPadding, titleBBox, axisWidth, halfLineWidth),
  26671. y: me.calculateLabelTextPoint(true, axisPosition, tickPadding, titleBBox, axisHeight, halfLineWidth)
  26672. }));
  26673. }
  26674. me.horizontalLine = surface.add(horizontalLineCfg);
  26675. me.verticalLine = surface.add(verticalLineCfg);
  26676. return false;
  26677. }
  26678. },
  26679. onGesture: function(e) {
  26680. var me = this;
  26681. if (me.getLocks()[me.getGesture()] !== me) {
  26682. return;
  26683. }
  26684. var chart = me.getChart(),
  26685. surface = chart.getSurface('overlay'),
  26686. rect = Ext.Array.slice(chart.getInnerRect()),
  26687. padding = chart.getInnerPadding(),
  26688. px = padding.left,
  26689. py = padding.top,
  26690. chartWidth = rect[2],
  26691. chartHeight = rect[3],
  26692. xy = chart.getEventXY(e),
  26693. x = xy[0],
  26694. y = xy[1],
  26695. axes = chart.getAxes(),
  26696. axis, axisPosition, axisAlignment, axisSurface, axisSprite, axisMatrix, axisLayoutContext, axisSegmenter, axisLabel, labelBBox, textPadding, xx, yy, dx, dy, xValue, yValue, text, i;
  26697. if (x < 0) {
  26698. x = 0;
  26699. } else if (x > chartWidth) {
  26700. x = chartWidth;
  26701. }
  26702. if (y < 0) {
  26703. y = 0;
  26704. } else if (y > chartHeight) {
  26705. y = chartHeight;
  26706. }
  26707. x += px;
  26708. y += py;
  26709. for (i = 0; i < axes.length; i++) {
  26710. axis = axes[i];
  26711. axisPosition = axis.getPosition();
  26712. axisAlignment = axis.getAlignment();
  26713. axisSurface = axis.getSurface();
  26714. axisSprite = axis.getSprites()[0];
  26715. axisMatrix = axisSprite.attr.matrix;
  26716. textPadding = axisSprite.attr.textPadding * 2;
  26717. axisLabel = me.axesLabels[axisPosition];
  26718. axisLayoutContext = axisSprite.getLayoutContext();
  26719. axisSegmenter = axis.getSegmenter();
  26720. if (axisLabel) {
  26721. if (axisAlignment === 'vertical') {
  26722. yy = axisMatrix.getYY();
  26723. dy = axisMatrix.getDY();
  26724. yValue = (y - dy - py) / yy;
  26725. if (axis.getLayout() instanceof Ext.chart.axis.layout.Discrete) {
  26726. y = Math.round(yValue) * yy + dy + py;
  26727. yValue = axisSegmenter.from(Math.round(yValue));
  26728. yValue = axisSprite.attr.data[yValue];
  26729. } else {
  26730. yValue = axisSegmenter.from(yValue);
  26731. }
  26732. text = axisSegmenter.renderer(yValue, axisLayoutContext);
  26733. axisLabel.setAttributes({
  26734. translationY: y - py
  26735. });
  26736. axisLabel.labelText.setAttributes({
  26737. text: text
  26738. });
  26739. labelBBox = axisLabel.labelText.getBBox();
  26740. axisLabel.labelRect.setAttributes({
  26741. height: labelBBox.height + textPadding,
  26742. y: -(labelBBox.height + textPadding) / 2
  26743. });
  26744. axisSurface.renderFrame();
  26745. } else {
  26746. xx = axisMatrix.getXX();
  26747. dx = axisMatrix.getDX();
  26748. xValue = (x - dx - px) / xx;
  26749. if (axis.getLayout() instanceof Ext.chart.axis.layout.Discrete) {
  26750. x = Math.round(xValue) * xx + dx + px;
  26751. xValue = axisSegmenter.from(Math.round(xValue));
  26752. xValue = axisSprite.attr.data[xValue];
  26753. } else {
  26754. xValue = axisSegmenter.from(xValue);
  26755. }
  26756. text = axisSegmenter.renderer(xValue, axisLayoutContext);
  26757. axisLabel.setAttributes({
  26758. translationX: x - px
  26759. });
  26760. axisLabel.labelText.setAttributes({
  26761. text: text
  26762. });
  26763. labelBBox = axisLabel.labelText.getBBox();
  26764. axisLabel.labelRect.setAttributes({
  26765. width: labelBBox.width + textPadding,
  26766. x: -(labelBBox.width + textPadding) / 2
  26767. });
  26768. axisSurface.renderFrame();
  26769. }
  26770. }
  26771. }
  26772. me.horizontalLine.setAttributes({
  26773. y: y,
  26774. strokeStyle: axisSprite.attr.strokeStyle
  26775. });
  26776. me.verticalLine.setAttributes({
  26777. x: x,
  26778. strokeStyle: axisSprite.attr.strokeStyle
  26779. });
  26780. surface.renderFrame();
  26781. return false;
  26782. },
  26783. onGestureEnd: function(e) {
  26784. var me = this,
  26785. chart = me.getChart(),
  26786. surface = chart.getSurface('overlay'),
  26787. axes = chart.getAxes(),
  26788. axis, axisPosition, axisSurface, axisLabel, i;
  26789. surface.remove(me.verticalLine);
  26790. surface.remove(me.horizontalLine);
  26791. for (i = 0; i < axes.length; i++) {
  26792. axis = axes[i];
  26793. axisPosition = axis.getPosition();
  26794. axisSurface = axis.getSurface();
  26795. axisLabel = me.axesLabels[axisPosition];
  26796. if (axisLabel) {
  26797. delete me.axesLabels[axisPosition];
  26798. axisSurface.remove(axisLabel);
  26799. }
  26800. axisSurface.renderFrame();
  26801. }
  26802. surface.renderFrame();
  26803. me.unlockEvents(me.getGesture());
  26804. },
  26805. onGestureCancel: function(e) {
  26806. this.onGestureEnd(e);
  26807. },
  26808. privates: {
  26809. vertMap: {
  26810. top: 'start',
  26811. bottom: 'end'
  26812. },
  26813. horzMap: {
  26814. left: 'start',
  26815. right: 'end'
  26816. },
  26817. calculateLabelTextPoint: function(vertical, position, tickPadding, titleBBox, axisSize, halfLineWidth) {
  26818. var titlePadding, sizeProp, pointProp;
  26819. if (vertical) {
  26820. pointProp = 'y';
  26821. sizeProp = 'height';
  26822. position = this.vertMap[position];
  26823. } else {
  26824. pointProp = 'x';
  26825. sizeProp = 'width';
  26826. position = this.horzMap[position];
  26827. }
  26828. switch (position) {
  26829. case 'start':
  26830. titlePadding = titleBBox ? titleBBox[pointProp] + titleBBox[sizeProp] : 0;
  26831. return titlePadding + (axisSize - titlePadding - tickPadding) / 2 - halfLineWidth;
  26832. case 'end':
  26833. titlePadding = titleBBox ? axisSize - titleBBox[pointProp] : 0;
  26834. return tickPadding + (axisSize - tickPadding - titlePadding) / 2 + halfLineWidth;
  26835. default:
  26836. return 0;
  26837. }
  26838. }
  26839. }
  26840. });
  26841. /**
  26842. * @class Ext.chart.interactions.ItemHighlight
  26843. * @extends Ext.chart.interactions.Abstract
  26844. *
  26845. * The 'itemhighlight' interaction allows the user to highlight series items in the chart.
  26846. */
  26847. Ext.define('Ext.chart.interactions.ItemHighlight', {
  26848. extend: 'Ext.chart.interactions.Abstract',
  26849. type: 'itemhighlight',
  26850. alias: 'interaction.itemhighlight',
  26851. isItemHighlight: true,
  26852. config: {
  26853. gestures: {
  26854. tap: 'onTapGesture',
  26855. mousemove: 'onMouseMoveGesture',
  26856. mousedown: 'onMouseDownGesture',
  26857. mouseup: 'onMouseUpGesture',
  26858. mouseleave: 'onMouseUpGesture'
  26859. },
  26860. /**
  26861. * @cfg {Boolean} [sticky=false]
  26862. * Disables mouse tracking.
  26863. * Series items will only be highlighted/unhighlighted on mouse click.
  26864. * This config has no effect on touch devices.
  26865. */
  26866. sticky: false,
  26867. /**
  26868. * @cfg {Boolean} [multiTooltips=false]
  26869. * Enable displaying multiple tooltips for overlapping or adjacent series items within
  26870. * {@link Ext.chart.series.Line#selectionTolerance} radius.
  26871. * Default is to display a tooltip only for the last series item rendered.
  26872. * When multiple tooltips are displayed, they may overlap partially or completely;
  26873. * it is up to the developer to ensure tooltip positioning is satisfactory.
  26874. *
  26875. * @since 6.6.0
  26876. */
  26877. multiTooltips: false
  26878. },
  26879. constructor: function(config) {
  26880. this.callParent([
  26881. config
  26882. ]);
  26883. this.stickyHighlightItem = null;
  26884. this.tooltipItems = [];
  26885. },
  26886. destroy: function() {
  26887. this.stickyHighlightItem = this.tooltipItems = null;
  26888. this.callParent();
  26889. },
  26890. onMouseMoveGesture: function(e) {
  26891. var me = this,
  26892. tooltipItems = me.tooltipItems,
  26893. isMousePointer = e.pointerType === 'mouse',
  26894. tooltips = [],
  26895. item, oldItem, items, tooltip, oldTooltip, i, len, j, jLen;
  26896. if (me.getSticky()) {
  26897. return true;
  26898. }
  26899. if (isMousePointer && me.stickyHighlightItem) {
  26900. me.stickyHighlightItem = null;
  26901. me.highlight(null);
  26902. }
  26903. if (me.isDragging) {
  26904. if (tooltipItems.length && isMousePointer) {
  26905. me.hideTooltips(tooltipItems);
  26906. tooltipItems.length = 0;
  26907. }
  26908. } else if (!me.stickyHighlightItem) {
  26909. if (me.getMultiTooltips()) {
  26910. items = me.getItemsForEvent(e);
  26911. } else {
  26912. item = me.getItemForEvent(e);
  26913. items = item ? [
  26914. item
  26915. ] : [];
  26916. }
  26917. for (i = 0 , len = items.length; i < len; i++) {
  26918. item = items[i];
  26919. // Items are returned top to down, so first item is the top one.
  26920. // Chart can only have one highlighted item.
  26921. if (i === 0 && item !== me.getChart().getHighlightItem()) {
  26922. me.highlight(item);
  26923. me.sync();
  26924. }
  26925. tooltip = item.series.getTooltip();
  26926. if (tooltip) {
  26927. tooltips.push(tooltip);
  26928. }
  26929. }
  26930. if (isMousePointer) {
  26931. // If we detected a mouse hit, show/refresh the tooltip
  26932. if (items.length) {
  26933. for (i = 0 , len = items.length; i < len; i++) {
  26934. item = items[i];
  26935. tooltip = item.series.getTooltip();
  26936. if (tooltip) {
  26937. // If there were different previously active items
  26938. // that are not going to be included in current active items,
  26939. // ask them to hide their tooltips. Unless those are
  26940. // the same tooltip instances that we are about to show,
  26941. // in which case we are just going to reposition them.
  26942. for (j = 0 , jLen = tooltipItems.length; j < jLen; j++) {
  26943. oldItem = tooltipItems[j];
  26944. if (!Ext.Array.contains(items, oldItem)) {
  26945. oldTooltip = oldItem.series.getTooltip();
  26946. if (!Ext.Array.contains(tooltips, oldTooltip)) {
  26947. oldItem.series.hideTooltip(oldItem, true);
  26948. }
  26949. }
  26950. }
  26951. if (tooltip.getTrackMouse()) {
  26952. item.series.showTooltip(item, e);
  26953. } else {
  26954. me.showUntracked(item);
  26955. }
  26956. }
  26957. }
  26958. me.tooltipItems = items;
  26959. } else // No mouse hit - schedule a hide for hideDelay ms.
  26960. // If pointer enters another item within that time,
  26961. // there will be no flickery reshow.
  26962. {
  26963. me.hideTooltips(tooltipItems);
  26964. tooltipItems.length = 0;
  26965. }
  26966. }
  26967. return false;
  26968. }
  26969. },
  26970. highlight: function(item) {
  26971. // This is its own function to make it easier for subclasses
  26972. // to enhance the behavior. An alternative would be to listen
  26973. // for the chart's 'itemhighlight' event.
  26974. this.getChart().setHighlightItem(item);
  26975. },
  26976. showTooltip: function(e, item) {
  26977. item.series.showTooltip(item, e);
  26978. Ext.Array.include(this.tooltipItems, item);
  26979. },
  26980. showUntracked: function(item) {
  26981. var marker = item.sprite.getMarker(item.category),
  26982. surface, surfaceXY, isInverseY, itemBBox, matrix;
  26983. if (marker) {
  26984. surface = marker.getSurface();
  26985. isInverseY = surface.matrix.elements[3] < 0;
  26986. surfaceXY = surface.element.getXY();
  26987. itemBBox = Ext.clone(marker.getBBoxFor(item.index));
  26988. if (isInverseY) {
  26989. // The item.category for bar series will be 'items'.
  26990. // The item.category for line series will be 'markers'.
  26991. // 'items' are in the 'series' surface, which is flipped vertically
  26992. // for cartesian series.
  26993. // 'markers' are in the 'overlay' surface, which isn't flipped.
  26994. // So for 'markers' we already have the bbox in a coordinate system
  26995. // with the origin at the top-left of the surface, but for 'items'
  26996. // we need to do a conversion.
  26997. if (surface.getInherited().rtl) {
  26998. matrix = surface.inverseMatrix.clone().flipX().translate(item.sprite.attr.innerWidth, 0, true);
  26999. } else {
  27000. matrix = surface.inverseMatrix;
  27001. }
  27002. itemBBox = matrix.transformBBox(itemBBox);
  27003. }
  27004. itemBBox.x += surfaceXY[0];
  27005. itemBBox.y += surfaceXY[1];
  27006. item.series.showTooltipAt(item, itemBBox.x + itemBBox.width * 0.5, itemBBox.y + itemBBox.height * 0.5);
  27007. }
  27008. },
  27009. onMouseDownGesture: function() {
  27010. this.isDragging = true;
  27011. },
  27012. onMouseUpGesture: function() {
  27013. this.isDragging = false;
  27014. },
  27015. isSameItem: function(a, b) {
  27016. return a && b && a.series === b.series && a.field === b.field && a.index === b.index;
  27017. },
  27018. onTapGesture: function(e) {
  27019. var me = this;
  27020. // A click/tap on an item makes its highlight sticky.
  27021. // It requires another click/tap to unhighlight.
  27022. if (e.pointerType === 'mouse' && !me.getSticky()) {
  27023. return;
  27024. }
  27025. var item = me.getItemForEvent(e);
  27026. if (me.isSameItem(me.stickyHighlightItem, item)) {
  27027. item = null;
  27028. }
  27029. // toggle
  27030. me.stickyHighlightItem = item;
  27031. me.highlight(item);
  27032. },
  27033. privates: {
  27034. hideTooltips: function(items, force) {
  27035. var item, i, len;
  27036. items = Ext.isArray(items) ? items : [
  27037. items
  27038. ];
  27039. for (i = 0 , len = items.length; i < len; i++) {
  27040. item = items[i];
  27041. if (item && item.series && !item.series.destroyed) {
  27042. item.series.hideTooltip(item, force);
  27043. }
  27044. }
  27045. }
  27046. }
  27047. });
  27048. /**
  27049. * @class Ext.chart.interactions.ItemEdit
  27050. * @extends Ext.chart.interactions.ItemHighlight
  27051. *
  27052. * The 'itemedit' interaction allows the user to edit store data
  27053. * by dragging series items in the chart.
  27054. *
  27055. * The 'itemedit' interaction extends the
  27056. * {@link Ext.chart.interactions.ItemHighlight 'itemhighlight'} interaction,
  27057. * so it also acts like one. If you need both interactions in a single chart,
  27058. * 'itemedit' should be sufficient. Hovering/tapping will result in highlighting,
  27059. * and dragging will result in editing.
  27060. */
  27061. Ext.define('Ext.chart.interactions.ItemEdit', {
  27062. extend: 'Ext.chart.interactions.ItemHighlight',
  27063. requires: [
  27064. 'Ext.tip.ToolTip'
  27065. ],
  27066. type: 'itemedit',
  27067. alias: 'interaction.itemedit',
  27068. isItemEdit: true,
  27069. config: {
  27070. /**
  27071. * @cfg {Object} [style=null]
  27072. * The style that will be applied to the series item on dragging.
  27073. * By default, series item will have no fill,
  27074. * and will have a dashed stroke of the same color.
  27075. */
  27076. style: null,
  27077. /**
  27078. * @cfg {Function/String} [renderer=null]
  27079. * A function that returns style attributes for the item that's being dragged.
  27080. * This is useful if you want to give a visual feedback to the user when
  27081. * they dragged to a certain point.
  27082. *
  27083. * @param {Object} [data] The following properties are available:
  27084. *
  27085. * @param {Object} data.target The object containing the xField/xValue or/and
  27086. * yField/yValue properties, where the xField/yField specify the store records
  27087. * being edited and the xValue/yValue the target values to be set when
  27088. * the interaction ends. The object also contains the 'index' of the record
  27089. * being edited.
  27090. * @param {Object} data.style The style that is going to be used for the dragged item.
  27091. * The attributes returned by the renderer will be applied on top of this style.
  27092. * @param {Object} data.item The series item being dragged.
  27093. * This is actually the {@link Ext.chart.AbstractChart#highlightItem}.
  27094. *
  27095. * @return {Object} The style attributes to be set on the dragged item.
  27096. */
  27097. renderer: null,
  27098. /**
  27099. * @cfg {Object/Boolean} [tooltip=true]
  27100. */
  27101. tooltip: true,
  27102. gestures: {
  27103. dragstart: 'onDragStart',
  27104. drag: 'onDrag',
  27105. dragend: 'onDragEnd'
  27106. },
  27107. cursors: {
  27108. ewResize: 'ew-resize',
  27109. nsResize: 'ns-resize',
  27110. move: 'move'
  27111. }
  27112. },
  27113. /**
  27114. * @private
  27115. * @cfg {Boolean} [sticky=false]
  27116. */
  27117. /**
  27118. * @event beginitemedit
  27119. * Fires when item edit operation (dragging) begins.
  27120. * @param {Ext.chart.AbstractChart} chart The chart the interaction belongs to.
  27121. * @param {Ext.chart.interactions.ItemEdit} interaction The interaction.
  27122. * @param {Object} item The item that is about to be edited.
  27123. */
  27124. /**
  27125. * @event enditemedit
  27126. * Fires when item edit operation (dragging) ends.
  27127. * @param {Ext.chart.AbstractChart} chart The chart the interaction belongs to.
  27128. * @param {Ext.chart.interactions.ItemEdit} interaction The interaction.
  27129. * @param {Object} item The item that was edited.
  27130. * @param {Object} target The object containing target values the were used.
  27131. */
  27132. item: null,
  27133. // Item being edited.
  27134. applyTooltip: function(tooltip) {
  27135. if (tooltip) {
  27136. var config = Ext.apply({}, tooltip, {
  27137. renderer: this.defaultTooltipRenderer,
  27138. constrainPosition: true,
  27139. shrinkWrapDock: true,
  27140. autoHide: true,
  27141. trackMouse: true,
  27142. mouseOffset: [
  27143. 20,
  27144. 20
  27145. ]
  27146. });
  27147. tooltip = new Ext.tip.ToolTip(config);
  27148. }
  27149. return tooltip;
  27150. },
  27151. defaultTooltipRenderer: function(tooltip, item, target, e) {
  27152. var parts = [];
  27153. if (target.xField) {
  27154. parts.push(target.xField + ': ' + target.xValue);
  27155. }
  27156. if (target.yField) {
  27157. parts.push(target.yField + ': ' + target.yValue);
  27158. }
  27159. tooltip.setHtml(parts.join('<br>'));
  27160. },
  27161. onDragStart: function(e) {
  27162. var me = this,
  27163. chart = me.getChart(),
  27164. item = chart.getHighlightItem();
  27165. e.claimGesture();
  27166. if (item) {
  27167. chart.fireEvent('beginitemedit', chart, me, me.item = item);
  27168. // If ItemEdit interaction comes before other interactions
  27169. // in the chart's 'interactions' config, this will
  27170. // prevent other interactions hijacking the 'dragstart'
  27171. // event. We only stop event propagation is there's
  27172. // an item to edit under cursor/finger, otherwise we
  27173. // let other interactions (e.g. 'panzoom') handle the event.
  27174. return false;
  27175. }
  27176. },
  27177. onDrag: function(e) {
  27178. var me = this,
  27179. chart = me.getChart(),
  27180. item = chart.getHighlightItem(),
  27181. type = item && item.sprite.type;
  27182. if (item) {
  27183. switch (type) {
  27184. case 'barSeries':
  27185. return me.onDragBar(e);
  27186. case 'scatterSeries':
  27187. return me.onDragScatter(e);
  27188. }
  27189. }
  27190. },
  27191. highlight: function(item) {
  27192. var me = this,
  27193. chart = me.getChart(),
  27194. flipXY = chart.getFlipXY(),
  27195. cursors = me.getCursors(),
  27196. type = item && item.sprite.type,
  27197. style = chart.el.dom.style;
  27198. me.callParent([
  27199. item
  27200. ]);
  27201. if (item) {
  27202. switch (type) {
  27203. case 'barSeries':
  27204. if (flipXY) {
  27205. style.cursor = cursors.ewResize;
  27206. } else {
  27207. style.cursor = cursors.nsResize;
  27208. };
  27209. break;
  27210. case 'scatterSeries':
  27211. style.cursor = cursors.move;
  27212. break;
  27213. }
  27214. } else {
  27215. chart.el.dom.style.cursor = 'default';
  27216. }
  27217. },
  27218. onDragBar: function(e) {
  27219. var me = this,
  27220. chart = me.getChart(),
  27221. isRtl = chart.getInherited().rtl,
  27222. flipXY = chart.isCartesian && chart.getFlipXY(),
  27223. item = chart.getHighlightItem(),
  27224. marker = item.sprite.getMarker('items'),
  27225. instance = marker.getMarkerFor(item.sprite.getId(), item.index),
  27226. surface = item.sprite.getSurface(),
  27227. surfaceRect = surface.getRect(),
  27228. xy = surface.getEventXY(e),
  27229. matrix = item.sprite.attr.matrix,
  27230. renderer = me.getRenderer(),
  27231. style, changes, params, positionY;
  27232. if (flipXY) {
  27233. positionY = isRtl ? surfaceRect[2] - xy[0] : xy[0];
  27234. } else {
  27235. positionY = surfaceRect[3] - xy[1];
  27236. }
  27237. style = {
  27238. x: instance.x,
  27239. y: positionY,
  27240. width: instance.width,
  27241. height: instance.height + (instance.y - positionY),
  27242. radius: instance.radius,
  27243. fillStyle: 'none',
  27244. lineDash: [
  27245. 4,
  27246. 4
  27247. ],
  27248. zIndex: 100
  27249. };
  27250. Ext.apply(style, me.getStyle());
  27251. if (Ext.isArray(item.series.getYField())) {
  27252. // stacked bars
  27253. positionY = positionY - instance.y - instance.height;
  27254. }
  27255. me.target = {
  27256. index: item.index,
  27257. yField: item.field,
  27258. yValue: (positionY - matrix.getDY()) / matrix.getYY()
  27259. };
  27260. params = [
  27261. chart,
  27262. {
  27263. target: me.target,
  27264. style: style,
  27265. item: item
  27266. }
  27267. ];
  27268. changes = Ext.callback(renderer, null, params, 0, chart);
  27269. if (changes) {
  27270. Ext.apply(style, changes);
  27271. }
  27272. // The interaction works by putting another series item instance
  27273. // under 'itemedit' ID with a slightly different style (default) or
  27274. // whatever style the user provided.
  27275. item.sprite.putMarker('items', style, 'itemedit');
  27276. me.showTooltip(e, me.target, item);
  27277. surface.renderFrame();
  27278. },
  27279. onDragScatter: function(e) {
  27280. var me = this,
  27281. chart = me.getChart(),
  27282. isRtl = chart.getInherited().rtl,
  27283. flipXY = chart.isCartesian && chart.getFlipXY(),
  27284. item = chart.getHighlightItem(),
  27285. marker = item.sprite.getMarker('markers'),
  27286. instance = marker.getMarkerFor(item.sprite.getId(), item.index),
  27287. surface = item.sprite.getSurface(),
  27288. surfaceRect = surface.getRect(),
  27289. xy = surface.getEventXY(e),
  27290. matrix = item.sprite.attr.matrix,
  27291. xAxis = item.series.getXAxis(),
  27292. isEditableX = xAxis && xAxis.getLayout().isContinuous,
  27293. renderer = me.getRenderer(),
  27294. style, changes, params, positionX, positionY, hintX, hintY;
  27295. if (flipXY) {
  27296. positionY = isRtl ? surfaceRect[2] - xy[0] : xy[0];
  27297. } else {
  27298. positionY = surfaceRect[3] - xy[1];
  27299. }
  27300. if (isEditableX) {
  27301. if (flipXY) {
  27302. positionX = surfaceRect[3] - xy[1];
  27303. } else {
  27304. positionX = xy[0];
  27305. }
  27306. } else {
  27307. positionX = instance.translationX;
  27308. }
  27309. if (isEditableX) {
  27310. hintX = xy[0];
  27311. hintY = xy[1];
  27312. } else {
  27313. if (flipXY) {
  27314. hintX = xy[0];
  27315. hintY = instance.translationY;
  27316. } else // no change
  27317. {
  27318. hintX = instance.translationX;
  27319. hintY = xy[1];
  27320. }
  27321. }
  27322. // no change
  27323. style = {
  27324. translationX: hintX,
  27325. translationY: hintY,
  27326. scalingX: instance.scalingX,
  27327. scalingY: instance.scalingY,
  27328. r: instance.r,
  27329. fillStyle: 'none',
  27330. lineDash: [
  27331. 4,
  27332. 4
  27333. ],
  27334. zIndex: 100
  27335. };
  27336. Ext.apply(style, me.getStyle());
  27337. me.target = {
  27338. index: item.index,
  27339. yField: item.field,
  27340. yValue: (positionY - matrix.getDY()) / matrix.getYY()
  27341. };
  27342. if (isEditableX) {
  27343. Ext.apply(me.target, {
  27344. xField: item.series.getXField(),
  27345. xValue: (positionX - matrix.getDX()) / matrix.getXX()
  27346. });
  27347. }
  27348. params = [
  27349. chart,
  27350. {
  27351. target: me.target,
  27352. style: style,
  27353. item: item
  27354. }
  27355. ];
  27356. changes = Ext.callback(renderer, null, params, 0, chart);
  27357. if (changes) {
  27358. Ext.apply(style, changes);
  27359. }
  27360. // This marker acts as a visual hint while dragging.
  27361. item.sprite.putMarker('markers', style, 'itemedit');
  27362. me.showTooltip(e, me.target, item);
  27363. surface.renderFrame();
  27364. },
  27365. showTooltip: function(e, target, item) {
  27366. var tooltip = this.getTooltip(),
  27367. config, chart;
  27368. if (tooltip && Ext.toolkit !== 'modern') {
  27369. config = tooltip.config;
  27370. chart = this.getChart();
  27371. Ext.callback(config.renderer, null, [
  27372. tooltip,
  27373. item,
  27374. target,
  27375. e
  27376. ], 0, chart);
  27377. // If trackMouse is set, a ToolTip shows by its pointerEvent
  27378. tooltip.pointerEvent = e;
  27379. if (tooltip.isVisible()) {
  27380. // After show handling repositions according
  27381. // to configuration. trackMouse uses the pointerEvent
  27382. // If aligning to an element, it uses a currentTarget
  27383. // flyweight which may be attached to any DOM element.
  27384. tooltip.realignToTarget();
  27385. } else {
  27386. tooltip.show();
  27387. }
  27388. }
  27389. },
  27390. hideTooltip: function() {
  27391. var tooltip = this.getTooltip();
  27392. if (tooltip && Ext.toolkit !== 'modern') {
  27393. tooltip.hide();
  27394. }
  27395. },
  27396. onDragEnd: function(e) {
  27397. var me = this,
  27398. target = me.target,
  27399. chart = me.getChart(),
  27400. store = chart.getStore(),
  27401. record;
  27402. if (target) {
  27403. record = store.getAt(target.index);
  27404. if (target.yField) {
  27405. record.set(target.yField, target.yValue, {
  27406. convert: false
  27407. });
  27408. }
  27409. if (target.xField) {
  27410. record.set(target.xField, target.xValue, {
  27411. convert: false
  27412. });
  27413. }
  27414. if (target.yField || target.xField) {
  27415. me.getChart().onDataChanged();
  27416. }
  27417. me.target = null;
  27418. }
  27419. me.hideTooltip();
  27420. if (me.item) {
  27421. chart.fireEvent('enditemedit', chart, me, me.item, target);
  27422. }
  27423. me.highlight(me.item = null);
  27424. },
  27425. destroy: function() {
  27426. // Peek at the config, so we don't create one just to destroy it,
  27427. // if a user has set 'tooltip' config to 'false'.
  27428. var tooltip = this.getConfig('tooltip', true);
  27429. Ext.destroy(tooltip);
  27430. this.callParent();
  27431. }
  27432. });
  27433. /**
  27434. * The PanZoom interaction allows the user to navigate the data for one or more chart
  27435. * axes by panning and/or zooming. Navigation can be limited to particular axes. Zooming is
  27436. * performed by pinching on the chart or axis area; panning is performed by single-touch dragging.
  27437. * The interaction only works with cartesian charts/series.
  27438. *
  27439. * For devices which do not support multiple-touch events, zooming can not be done via pinch gestures; in this case the
  27440. * interaction will allow the user to perform both zooming and panning using the same single-touch drag gesture.
  27441. * {@link #modeToggleButton} provides a button to indicate and toggle between two modes.
  27442. *
  27443. * @example
  27444. * Ext.create({
  27445. * renderTo: document.body,
  27446. * xtype: 'cartesian',
  27447. * width: 600,
  27448. * height: 400,
  27449. * insetPadding: 40,
  27450. * interactions: [{
  27451. * type: 'panzoom',
  27452. * zoomOnPan: true
  27453. * }],
  27454. * store: {
  27455. * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
  27456. * data: [{
  27457. * 'name': 'metric one',
  27458. * 'data1': 10,
  27459. * 'data2': 12,
  27460. * 'data3': 14,
  27461. * 'data4': 8,
  27462. * 'data5': 13
  27463. * }, {
  27464. * 'name': 'metric two',
  27465. * 'data1': 7,
  27466. * 'data2': 8,
  27467. * 'data3': 16,
  27468. * 'data4': 10,
  27469. * 'data5': 3
  27470. * }, {
  27471. * 'name': 'metric three',
  27472. * 'data1': 5,
  27473. * 'data2': 2,
  27474. * 'data3': 14,
  27475. * 'data4': 12,
  27476. * 'data5': 7
  27477. * }, {
  27478. * 'name': 'metric four',
  27479. * 'data1': 2,
  27480. * 'data2': 14,
  27481. * 'data3': 6,
  27482. * 'data4': 1,
  27483. * 'data5': 23
  27484. * }, {
  27485. * 'name': 'metric five',
  27486. * 'data1': 27,
  27487. * 'data2': 38,
  27488. * 'data3': 36,
  27489. * 'data4': 13,
  27490. * 'data5': 33
  27491. * }]
  27492. * },
  27493. * axes: [{
  27494. * type: 'numeric',
  27495. * position: 'left',
  27496. * fields: ['data1'],
  27497. * title: {
  27498. * text: 'Sample Values',
  27499. * fontSize: 15
  27500. * },
  27501. * grid: true,
  27502. * minimum: 0
  27503. * }, {
  27504. * type: 'category',
  27505. * position: 'bottom',
  27506. * fields: ['name'],
  27507. * title: {
  27508. * text: 'Sample Values',
  27509. * fontSize: 15
  27510. * }
  27511. * }],
  27512. * series: [{
  27513. * type: 'line',
  27514. * highlight: {
  27515. * size: 7,
  27516. * radius: 7
  27517. * },
  27518. * style: {
  27519. * stroke: 'rgb(143,203,203)'
  27520. * },
  27521. * xField: 'name',
  27522. * yField: 'data1',
  27523. * marker: {
  27524. * type: 'path',
  27525. * path: ['M', - 2, 0, 0, 2, 2, 0, 0, - 2, 'Z'],
  27526. * stroke: 'blue',
  27527. * lineWidth: 0
  27528. * }
  27529. * }, {
  27530. * type: 'line',
  27531. * highlight: {
  27532. * size: 7,
  27533. * radius: 7
  27534. * },
  27535. * fill: true,
  27536. * xField: 'name',
  27537. * yField: 'data3',
  27538. * marker: {
  27539. * type: 'circle',
  27540. * radius: 4,
  27541. * lineWidth: 0
  27542. * }
  27543. * }]
  27544. * });
  27545. *
  27546. * The configuration object for the `panzoom` interaction type should specify which axes
  27547. * will be made navigable via the `axes` config. See the {@link #axes} config documentation
  27548. * for details on the allowed formats. If the `axes` config is not specified, it will default
  27549. * to making all axes navigable with the default axis options.
  27550. *
  27551. */
  27552. Ext.define('Ext.chart.interactions.PanZoom', {
  27553. extend: 'Ext.chart.interactions.Abstract',
  27554. type: 'panzoom',
  27555. alias: 'interaction.panzoom',
  27556. requires: [
  27557. 'Ext.draw.Animator'
  27558. ],
  27559. config: {
  27560. /**
  27561. * @cfg {Object/Array} axes
  27562. * Specifies which axes should be made navigable. The config value can take the following formats:
  27563. *
  27564. * - An Object with keys corresponding to the {@link Ext.chart.axis.Axis#position position} of each
  27565. * axis that should be made navigable. Each key's value can either be an Object with further
  27566. * configuration options for each axis or simply `true` for a default set of options.
  27567. *
  27568. * {
  27569. * type: 'panzoom',
  27570. * axes: {
  27571. * left: {
  27572. * maxZoom: 5,
  27573. * allowPan: false
  27574. * },
  27575. * bottom: true
  27576. * }
  27577. * }
  27578. *
  27579. * If using the full Object form, the following options can be specified for each axis:
  27580. *
  27581. * - minZoom (Number) A minimum zoom level for the axis. Defaults to `1` which is its natural size.
  27582. * - maxZoom (Number) A maximum zoom level for the axis. Defaults to `10`.
  27583. * - startZoom (Number) A starting zoom level for the axis. Defaults to `1`.
  27584. * - allowZoom (Boolean) Whether zooming is allowed for the axis. Defaults to `true`.
  27585. * - allowPan (Boolean) Whether panning is allowed for the axis. Defaults to `true`.
  27586. * - startPan (Boolean) A starting panning offset for the axis. Defaults to `0`.
  27587. *
  27588. * - An Array of strings, each one corresponding to the {@link Ext.chart.axis.Axis#position position}
  27589. * of an axis that should be made navigable. The default options will be used for each named axis.
  27590. *
  27591. * {
  27592. * type: 'panzoom',
  27593. * axes: ['left', 'bottom']
  27594. * }
  27595. *
  27596. * If the `axes` config is not specified, it will default to making all axes navigable with the
  27597. * default axis options.
  27598. */
  27599. axes: {
  27600. top: {},
  27601. right: {},
  27602. bottom: {},
  27603. left: {}
  27604. },
  27605. minZoom: null,
  27606. maxZoom: null,
  27607. /**
  27608. * @cfg {Boolean} showOverflowArrows
  27609. * If `true`, arrows will be conditionally shown at either end of each axis to indicate that the
  27610. * axis is overflowing and can therefore be panned in that direction. Set this to `false` to
  27611. * prevent the arrows from being displayed.
  27612. */
  27613. showOverflowArrows: true,
  27614. /**
  27615. * @cfg {Object} overflowArrowOptions
  27616. * A set of optional overrides for the overflow arrow sprites' options. Only relevant when
  27617. * {@link #showOverflowArrows} is `true`.
  27618. */
  27619. /**
  27620. * @cfg {String} panGesture
  27621. * Defines the gesture that initiates panning.
  27622. * @private
  27623. */
  27624. panGesture: 'drag',
  27625. /**
  27626. * @cfg {String} zoomGesture
  27627. * Defines the gesture that initiates zooming.
  27628. * @private
  27629. */
  27630. zoomGesture: 'pinch',
  27631. /**
  27632. * @cfg {Boolean} zoomOnPanGesture
  27633. * @deprecated 6.2 Please use {@link #zoomOnPan} instead.
  27634. * If `true`, the pan gesture will zoom the chart.
  27635. */
  27636. zoomOnPanGesture: null,
  27637. /**
  27638. * @cfg {Boolean} zoomOnPan
  27639. * If `true`, the pan gesture will zoom the chart.
  27640. */
  27641. zoomOnPan: false,
  27642. /**
  27643. * @cfg {Boolean} [doubleTapReset=false]
  27644. * If `true`, the double tap on a chart will reset the current pan/zoom to show the whole chart.
  27645. */
  27646. doubleTapReset: false,
  27647. modeToggleButton: {
  27648. xtype: 'segmentedbutton',
  27649. width: 200,
  27650. defaults: {
  27651. ui: 'default-toolbar'
  27652. },
  27653. cls: Ext.baseCSSPrefix + 'panzoom-toggle',
  27654. items: [
  27655. {
  27656. text: 'Pan',
  27657. value: 'pan'
  27658. },
  27659. {
  27660. text: 'Zoom',
  27661. value: 'zoom'
  27662. }
  27663. ]
  27664. },
  27665. hideLabelInGesture: false
  27666. },
  27667. // Ext.os.is.Android
  27668. stopAnimationBeforeSync: true,
  27669. applyAxes: function(axesConfig, oldAxesConfig) {
  27670. return Ext.merge(oldAxesConfig || {}, axesConfig);
  27671. },
  27672. updateZoomOnPan: function(zoomOnPan) {
  27673. var button = this.getModeToggleButton();
  27674. button.setValue(zoomOnPan ? 'zoom' : 'pan');
  27675. },
  27676. updateZoomOnPanGesture: function(zoomOnPanGesture) {
  27677. this.setZoomOnPan(zoomOnPanGesture);
  27678. },
  27679. getZoomOnPanGesture: function() {
  27680. return this.getZoomOnPan();
  27681. },
  27682. applyModeToggleButton: function(button, oldButton) {
  27683. return Ext.factory(button, 'Ext.button.Segmented', oldButton);
  27684. },
  27685. updateModeToggleButton: function(button) {
  27686. if (button) {
  27687. button.on('change', 'onModeToggleChange', this);
  27688. }
  27689. },
  27690. onModeToggleChange: function(segmentedButton, value) {
  27691. this.setZoomOnPan(value === 'zoom');
  27692. },
  27693. getGestures: function() {
  27694. var me = this,
  27695. gestures = {},
  27696. pan = me.getPanGesture(),
  27697. zoom = me.getZoomGesture();
  27698. gestures[zoom] = 'onZoomGestureMove';
  27699. gestures[zoom + 'start'] = 'onZoomGestureStart';
  27700. gestures[zoom + 'end'] = 'onZoomGestureEnd';
  27701. gestures[pan] = 'onPanGestureMove';
  27702. gestures[pan + 'start'] = 'onPanGestureStart';
  27703. gestures[pan + 'end'] = 'onPanGestureEnd';
  27704. gestures.doubletap = 'onDoubleTap';
  27705. return gestures;
  27706. },
  27707. onDoubleTap: function(e) {
  27708. var me = this,
  27709. doubleTapReset = me.getDoubleTapReset(),
  27710. chart, axes, axis, i, ln;
  27711. if (doubleTapReset) {
  27712. chart = me.getChart();
  27713. axes = chart.getAxes();
  27714. for (i = 0 , ln = axes.length; i < ln; i++) {
  27715. axis = axes[i];
  27716. axis.setVisibleRange([
  27717. 0,
  27718. 1
  27719. ]);
  27720. }
  27721. chart.redraw();
  27722. }
  27723. },
  27724. onPanGestureStart: function(e) {
  27725. if (!e || !e.touches || e.touches.length < 2) {
  27726. //Limit drags to single touch
  27727. var me = this,
  27728. chart = me.getChart(),
  27729. rect = chart.getInnerRect(),
  27730. xy = chart.element.getXY();
  27731. e.claimGesture();
  27732. chart.suspendAnimation();
  27733. me.startX = e.getX() - xy[0] - rect[0];
  27734. me.startY = e.getY() - xy[1] - rect[1];
  27735. me.oldVisibleRanges = null;
  27736. me.hideLabels();
  27737. chart.suspendThicknessChanged();
  27738. me.lockEvents(me.getPanGesture());
  27739. return false;
  27740. }
  27741. },
  27742. onPanGestureMove: function(e) {
  27743. var me = this,
  27744. isMouse = e.pointerType === 'mouse',
  27745. isZoomOnPan = isMouse && me.getZoomOnPan();
  27746. if (me.getLocks()[me.getPanGesture()] === me) {
  27747. // Limit drags to single touch.
  27748. var chart = me.getChart(),
  27749. rect = chart.getInnerRect(),
  27750. xy = chart.element.getXY();
  27751. if (isZoomOnPan) {
  27752. me.transformAxesBy(me.getZoomableAxes(e), 0, 0, (e.getX() - xy[0] - rect[0]) / me.startX, me.startY / (e.getY() - xy[1] - rect[1]));
  27753. } else {
  27754. me.transformAxesBy(me.getPannableAxes(e), e.getX() - xy[0] - rect[0] - me.startX, e.getY() - xy[1] - rect[1] - me.startY, 1, 1);
  27755. }
  27756. me.sync();
  27757. return false;
  27758. }
  27759. },
  27760. onPanGestureEnd: function(e) {
  27761. var me = this,
  27762. pan = me.getPanGesture(),
  27763. chart;
  27764. if (me.getLocks()[pan] === me) {
  27765. chart = me.getChart();
  27766. chart.resumeThicknessChanged();
  27767. me.showLabels();
  27768. me.sync();
  27769. me.unlockEvents(pan);
  27770. chart.resumeAnimation();
  27771. return false;
  27772. }
  27773. },
  27774. onZoomGestureStart: function(e) {
  27775. if (e.touches && e.touches.length === 2) {
  27776. var me = this,
  27777. chart = me.getChart(),
  27778. xy = chart.element.getXY(),
  27779. rect = chart.getInnerRect(),
  27780. x = xy[0] + rect[0],
  27781. y = xy[1] + rect[1],
  27782. newPoints = [
  27783. e.touches[0].point.x - x,
  27784. e.touches[0].point.y - y,
  27785. e.touches[1].point.x - x,
  27786. e.touches[1].point.y - y
  27787. ],
  27788. xDistance = Math.max(44, Math.abs(newPoints[2] - newPoints[0])),
  27789. yDistance = Math.max(44, Math.abs(newPoints[3] - newPoints[1]));
  27790. e.claimGesture();
  27791. chart.suspendAnimation();
  27792. chart.suspendThicknessChanged();
  27793. me.lastZoomDistances = [
  27794. xDistance,
  27795. yDistance
  27796. ];
  27797. me.lastPoints = newPoints;
  27798. me.oldVisibleRanges = null;
  27799. me.hideLabels();
  27800. me.lockEvents(me.getZoomGesture());
  27801. return false;
  27802. }
  27803. },
  27804. onZoomGestureMove: function(e) {
  27805. var me = this;
  27806. if (me.getLocks()[me.getZoomGesture()] === me) {
  27807. var chart = me.getChart(),
  27808. rect = chart.getInnerRect(),
  27809. xy = chart.element.getXY(),
  27810. x = xy[0] + rect[0],
  27811. y = xy[1] + rect[1],
  27812. abs = Math.abs,
  27813. lastPoints = me.lastPoints,
  27814. newPoints = [
  27815. e.touches[0].point.x - x,
  27816. e.touches[0].point.y - y,
  27817. e.touches[1].point.x - x,
  27818. e.touches[1].point.y - y
  27819. ],
  27820. xDistance = Math.max(44, abs(newPoints[2] - newPoints[0])),
  27821. yDistance = Math.max(44, abs(newPoints[3] - newPoints[1])),
  27822. lastDistances = this.lastZoomDistances || [
  27823. xDistance,
  27824. yDistance
  27825. ],
  27826. zoomX = xDistance / lastDistances[0],
  27827. zoomY = yDistance / lastDistances[1];
  27828. me.transformAxesBy(me.getZoomableAxes(e), rect[2] * (zoomX - 1) / 2 + newPoints[2] - lastPoints[2] * zoomX, rect[3] * (zoomY - 1) / 2 + newPoints[3] - lastPoints[3] * zoomY, zoomX, zoomY);
  27829. me.sync();
  27830. return false;
  27831. }
  27832. },
  27833. onZoomGestureEnd: function(e) {
  27834. var me = this,
  27835. zoom = me.getZoomGesture(),
  27836. chart;
  27837. if (me.getLocks()[zoom] === me) {
  27838. chart = me.getChart();
  27839. chart.resumeThicknessChanged();
  27840. me.showLabels();
  27841. me.sync();
  27842. me.unlockEvents(zoom);
  27843. chart.resumeAnimation();
  27844. return false;
  27845. }
  27846. },
  27847. hideLabels: function() {
  27848. if (this.getHideLabelInGesture()) {
  27849. this.eachInteractiveAxes(function(axis) {
  27850. axis.hideLabels();
  27851. });
  27852. }
  27853. },
  27854. showLabels: function() {
  27855. if (this.getHideLabelInGesture()) {
  27856. this.eachInteractiveAxes(function(axis) {
  27857. axis.showLabels();
  27858. });
  27859. }
  27860. },
  27861. isEventOnAxis: function(e, axis) {
  27862. // TODO: right now this uses the current event position but really we want to only
  27863. // use the gesture's start event. Pinch does not give that to us though.
  27864. var rect = axis.getSurface().getRect();
  27865. return rect[0] <= e.getX() && e.getX() <= rect[0] + rect[2] && rect[1] <= e.getY() && e.getY() <= rect[1] + rect[3];
  27866. },
  27867. getPannableAxes: function(e) {
  27868. var me = this,
  27869. axisConfigs = me.getAxes(),
  27870. axes = me.getChart().getAxes(),
  27871. i,
  27872. ln = axes.length,
  27873. result = [],
  27874. isEventOnAxis = false,
  27875. config;
  27876. if (e) {
  27877. for (i = 0; i < ln; i++) {
  27878. if (this.isEventOnAxis(e, axes[i])) {
  27879. isEventOnAxis = true;
  27880. break;
  27881. }
  27882. }
  27883. }
  27884. for (i = 0; i < ln; i++) {
  27885. config = axisConfigs[axes[i].getPosition()];
  27886. if (config && config.allowPan !== false && (!isEventOnAxis || this.isEventOnAxis(e, axes[i]))) {
  27887. result.push(axes[i]);
  27888. }
  27889. }
  27890. return result;
  27891. },
  27892. getZoomableAxes: function(e) {
  27893. var me = this,
  27894. axisConfigs = me.getAxes(),
  27895. axes = me.getChart().getAxes(),
  27896. result = [],
  27897. i,
  27898. ln = axes.length,
  27899. axis,
  27900. isEventOnAxis = false,
  27901. config;
  27902. if (e) {
  27903. for (i = 0; i < ln; i++) {
  27904. if (this.isEventOnAxis(e, axes[i])) {
  27905. isEventOnAxis = true;
  27906. break;
  27907. }
  27908. }
  27909. }
  27910. for (i = 0; i < ln; i++) {
  27911. axis = axes[i];
  27912. config = axisConfigs[axis.getPosition()];
  27913. if (config && config.allowZoom !== false && (!isEventOnAxis || this.isEventOnAxis(e, axis))) {
  27914. result.push(axis);
  27915. }
  27916. }
  27917. return result;
  27918. },
  27919. eachInteractiveAxes: function(fn) {
  27920. var me = this,
  27921. axisConfigs = me.getAxes(),
  27922. axes = me.getChart().getAxes();
  27923. for (var i = 0; i < axes.length; i++) {
  27924. if (axisConfigs[axes[i].getPosition()]) {
  27925. if (false === fn.call(this, axes[i])) {
  27926. return;
  27927. }
  27928. }
  27929. }
  27930. },
  27931. transformAxesBy: function(axes, panX, panY, sx, sy) {
  27932. var rect = this.getChart().getInnerRect(),
  27933. axesCfg = this.getAxes(),
  27934. axisCfg,
  27935. oldVisibleRanges = this.oldVisibleRanges,
  27936. result = false;
  27937. if (!oldVisibleRanges) {
  27938. this.oldVisibleRanges = oldVisibleRanges = {};
  27939. this.eachInteractiveAxes(function(axis) {
  27940. oldVisibleRanges[axis.getId()] = axis.getVisibleRange();
  27941. });
  27942. }
  27943. if (!rect) {
  27944. return;
  27945. }
  27946. for (var i = 0; i < axes.length; i++) {
  27947. axisCfg = axesCfg[axes[i].getPosition()];
  27948. result = this.transformAxisBy(axes[i], oldVisibleRanges[axes[i].getId()], panX, panY, sx, sy, this.minZoom || axisCfg.minZoom, this.maxZoom || axisCfg.maxZoom) || result;
  27949. }
  27950. return result;
  27951. },
  27952. transformAxisBy: function(axis, oldVisibleRange, panX, panY, sx, sy, minZoom, maxZoom) {
  27953. var me = this,
  27954. visibleLength = oldVisibleRange[1] - oldVisibleRange[0],
  27955. visibleRange = axis.getVisibleRange(),
  27956. actualMinZoom = minZoom || me.getMinZoom() || axis.config.minZoom,
  27957. actualMaxZoom = maxZoom || me.getMaxZoom() || axis.config.maxZoom,
  27958. rect = me.getChart().getInnerRect(),
  27959. left, right;
  27960. if (!rect) {
  27961. return;
  27962. }
  27963. var isSide = axis.isSide(),
  27964. length = isSide ? rect[3] : rect[2],
  27965. pan = isSide ? -panY : panX;
  27966. visibleLength /= isSide ? sy : sx;
  27967. if (visibleLength < 0) {
  27968. visibleLength = -visibleLength;
  27969. }
  27970. if (visibleLength * actualMinZoom > 1) {
  27971. visibleLength = 1;
  27972. }
  27973. if (visibleLength * actualMaxZoom < 1) {
  27974. visibleLength = 1 / actualMaxZoom;
  27975. }
  27976. left = oldVisibleRange[0];
  27977. right = oldVisibleRange[1];
  27978. visibleRange = visibleRange[1] - visibleRange[0];
  27979. if (visibleLength === visibleRange && visibleRange === 1) {
  27980. return;
  27981. }
  27982. axis.setVisibleRange([
  27983. (oldVisibleRange[0] + oldVisibleRange[1] - visibleLength) * 0.5 - pan / length * visibleLength,
  27984. (oldVisibleRange[0] + oldVisibleRange[1] + visibleLength) * 0.5 - pan / length * visibleLength
  27985. ]);
  27986. return Math.abs(left - axis.getVisibleRange()[0]) > 1.0E-10 || Math.abs(right - axis.getVisibleRange()[1]) > 1.0E-10;
  27987. },
  27988. destroy: function() {
  27989. this.setModeToggleButton(null);
  27990. this.callParent();
  27991. }
  27992. });
  27993. /**
  27994. * @class Ext.chart.interactions.Rotate
  27995. * @extends Ext.chart.interactions.Abstract
  27996. *
  27997. * The Rotate interaction allows the user to rotate a polar chart about its central point.
  27998. *
  27999. * @example
  28000. * Ext.create('Ext.Container', {
  28001. * renderTo: Ext.getBody(),
  28002. * width: 600,
  28003. * height: 400,
  28004. * layout: 'fit',
  28005. * items: {
  28006. * xtype: 'polar',
  28007. * interactions: 'rotate',
  28008. * colors: ["#115fa6", "#94ae0a", "#a61120", "#ff8809", "#ffd13e"],
  28009. * store: {
  28010. * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
  28011. * data: [
  28012. * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
  28013. * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
  28014. * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
  28015. * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
  28016. * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
  28017. * ]
  28018. * },
  28019. * series: {
  28020. * type: 'pie',
  28021. * label: {
  28022. * field: 'name',
  28023. * display: 'rotate'
  28024. * },
  28025. * xField: 'data3',
  28026. * donut: 30
  28027. * }
  28028. * }
  28029. * });
  28030. */
  28031. Ext.define('Ext.chart.interactions.Rotate', {
  28032. extend: 'Ext.chart.interactions.Abstract',
  28033. type: 'rotate',
  28034. alternateClassName: 'Ext.chart.interactions.RotatePie3D',
  28035. alias: [
  28036. 'interaction.rotate',
  28037. 'interaction.rotatePie3d'
  28038. ],
  28039. /**
  28040. * @event rotate
  28041. * Fires on every tick of the rotation.
  28042. * @param {Ext.chart.interactions.Rotate} this This interaction.
  28043. * @param {Number} angle The new current rotation angle.
  28044. */
  28045. /**
  28046. * @event rotatestart
  28047. * Fires when a user initiates the rotation.
  28048. * @param {Ext.chart.interactions.Rotate} this This interaction.
  28049. * @param {Number} angle The new current rotation angle.
  28050. */
  28051. /**
  28052. * @event rotateend
  28053. * Fires after a user finishes the rotation.
  28054. * @param {Ext.chart.interactions.Rotate} this This interaction.
  28055. * @param {Number} angle The new current rotation angle.
  28056. */
  28057. /**
  28058. * @deprecated 6.5.1 Use the 'rotateend' event instead.
  28059. * @event rotationEnd
  28060. * Fires after a user finishes the rotation
  28061. * @param {Ext.chart.interactions.Rotate} this This interaction.
  28062. * @param {Number} angle The new current rotation angle.
  28063. */
  28064. config: {
  28065. /**
  28066. * @cfg {String} gesture
  28067. * Defines the gesture type that will be used to rotate the chart. Currently only
  28068. * supports `pinch` for two-finger rotation and `drag` for single-finger rotation.
  28069. * @private
  28070. */
  28071. gesture: 'rotate',
  28072. gestures: {
  28073. dragstart: 'onGestureStart',
  28074. drag: 'onGesture',
  28075. dragend: 'onGestureEnd'
  28076. },
  28077. /**
  28078. * @cfg {Number} rotation
  28079. * Saves the current rotation of the series. Accepts negative values and values > 360 ( / 180 * Math.PI)
  28080. * @private
  28081. */
  28082. rotation: 0
  28083. },
  28084. oldRotations: null,
  28085. getAngle: function(e) {
  28086. var me = this,
  28087. chart = me.getChart(),
  28088. xy = chart.getEventXY(e),
  28089. center = chart.getCenter();
  28090. return Math.atan2(xy[1] - center[1], xy[0] - center[0]);
  28091. },
  28092. onGestureStart: function(e) {
  28093. var me = this;
  28094. e.claimGesture();
  28095. me.lockEvents('drag');
  28096. me.angle = me.getAngle(e);
  28097. me.oldRotations = {};
  28098. me.getChart().suspendAnimation();
  28099. me.fireEvent('rotatestart', me, me.getRotation());
  28100. return false;
  28101. },
  28102. onGesture: function(e) {
  28103. var me = this,
  28104. angle = me.getAngle(e) - me.angle;
  28105. if (me.getLocks().drag === me) {
  28106. me.doRotateTo(angle, true);
  28107. return false;
  28108. }
  28109. },
  28110. /**
  28111. * @private
  28112. */
  28113. doRotateTo: function(angle, relative) {
  28114. var me = this,
  28115. chart = me.getChart(),
  28116. axes = chart.getAxes(),
  28117. seriesList = chart.getSeries(),
  28118. oldRotations = me.oldRotations,
  28119. rotation, oldRotation, axis, series, id, i, ln;
  28120. for (i = 0 , ln = axes.length; i < ln; i++) {
  28121. axis = axes[i];
  28122. id = axis.getId();
  28123. oldRotation = oldRotations[id] || (oldRotations[id] = axis.getRotation());
  28124. rotation = angle + (relative ? oldRotation : 0);
  28125. axis.setRotation(rotation);
  28126. }
  28127. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  28128. series = seriesList[i];
  28129. id = series.getId();
  28130. oldRotation = oldRotations[id] || (oldRotations[id] = series.getRotation());
  28131. // Unline axis's 'rotation', Polar series' 'rotation' is a public config and in degrees.
  28132. rotation = Ext.draw.Draw.degrees(angle + (relative ? oldRotation : 0));
  28133. series.setRotation(rotation);
  28134. }
  28135. me.setRotation(rotation);
  28136. me.fireEvent('rotate', me, me.getRotation());
  28137. me.sync();
  28138. },
  28139. /**
  28140. * Rotates a polar chart about its center point to the specified angle.
  28141. * @param {Number} angle The angle to rotate to.
  28142. * @param {Boolean} [relative=false] Whether the rotation is relative to the current angle or not.
  28143. * @param {Boolean} [animate=false] Whether to animate the rotation or not.
  28144. */
  28145. rotateTo: function(angle, relative, animate) {
  28146. var me = this,
  28147. chart = me.getChart();
  28148. if (!animate) {
  28149. chart.suspendAnimation();
  28150. }
  28151. me.doRotateTo(angle, relative, animate);
  28152. me.oldRotations = {};
  28153. if (!animate) {
  28154. chart.resumeAnimation();
  28155. }
  28156. },
  28157. onGestureEnd: function(e) {
  28158. var me = this;
  28159. if (me.getLocks().drag === me) {
  28160. me.onGesture(e);
  28161. me.unlockEvents('drag');
  28162. me.getChart().resumeAnimation();
  28163. me.fireEvent('rotateend', me, me.getRotation());
  28164. me.fireEvent('rotationEnd', me, me.getRotation());
  28165. return false;
  28166. }
  28167. }
  28168. });
  28169. /**
  28170. *
  28171. */
  28172. Ext.define('Ext.chart.navigator.ContainerBase', {
  28173. extend: 'Ext.panel.Panel'
  28174. });
  28175. /**
  28176. *
  28177. */
  28178. Ext.define('Ext.chart.navigator.NavigatorBase', {
  28179. extend: 'Ext.chart.CartesianChart',
  28180. onRender: function() {
  28181. this.callParent();
  28182. this.setupEvents();
  28183. },
  28184. // Note: 'applyDock' and 'updateDock' won't ever be called in Classic.
  28185. // See the Classic Component's 'setDock' method, which is overridden here.
  28186. setDocked: function(docked) {
  28187. var me = this,
  28188. ownerCt = me.getNavigatorContainer();
  28189. if (!(docked === 'top' || docked === 'bottom')) {
  28190. Ext.raise("Can only dock to 'top' or 'bottom'.");
  28191. }
  28192. if (docked !== me.dock) {
  28193. if (ownerCt && ownerCt.moveDocked) {
  28194. ownerCt.moveDocked(me, docked);
  28195. } else {
  28196. me.dock = docked;
  28197. }
  28198. }
  28199. return me;
  28200. },
  28201. getDocked: function() {
  28202. return this.dock;
  28203. }
  28204. });
  28205. /**
  28206. * The overlay sprite used by the {@link Ext.chart.navigator.Navigator} component
  28207. * to render the selected visible range or a chart's horizontal axis.
  28208. */
  28209. Ext.define('Ext.chart.navigator.sprite.RangeMask', {
  28210. extend: 'Ext.draw.sprite.Sprite',
  28211. alias: 'sprite.rangemask',
  28212. inheritableStatics: {
  28213. def: {
  28214. processors: {
  28215. min: 'limited01',
  28216. max: 'limited01',
  28217. thumbOpacity: 'limited01'
  28218. },
  28219. defaults: {
  28220. min: 0,
  28221. max: 1,
  28222. lineWidth: 2,
  28223. miterLimit: 1,
  28224. strokeStyle: '#787878',
  28225. thumbOpacity: 1
  28226. }
  28227. }
  28228. },
  28229. getBBox: function(isWithoutTransform) {
  28230. var me = this,
  28231. attr = me.attr,
  28232. bbox = attr.bbox;
  28233. bbox.plain = {
  28234. x: 0,
  28235. y: 0,
  28236. width: 1,
  28237. height: 1
  28238. };
  28239. if (isWithoutTransform) {
  28240. return bbox.plain;
  28241. }
  28242. return bbox.transform || (bbox.transform = attr.matrix.transformBBox(bbox.plain));
  28243. },
  28244. renderThumb: function(surface, ctx, x, y) {
  28245. var me = this,
  28246. shapeSprite = me.shapeSprite,
  28247. textureSprite = me.textureSprite,
  28248. thumbOpacity = me.attr.thumbOpacity,
  28249. thumbAttributes = {
  28250. opacity: thumbOpacity,
  28251. translationX: x,
  28252. translationY: y
  28253. };
  28254. if (!shapeSprite) {
  28255. shapeSprite = me.shapeSprite = new Ext.draw.sprite.Rect({
  28256. x: -9.5,
  28257. y: -9.5,
  28258. width: 19,
  28259. height: 19,
  28260. radius: 4,
  28261. lineWidth: 1,
  28262. fillStyle: {
  28263. type: 'linear',
  28264. degrees: 90,
  28265. stops: [
  28266. {
  28267. offset: 0,
  28268. color: '#EEE'
  28269. },
  28270. {
  28271. offset: 1,
  28272. color: '#FFF'
  28273. }
  28274. ]
  28275. },
  28276. strokeStyle: '#999'
  28277. });
  28278. textureSprite = me.textureSprite = new Ext.draw.sprite.Path({
  28279. path: 'M -4, -5, -4, 5 M 0, -5, 0, 5 M 4, -5, 4, 5',
  28280. strokeStyle: {
  28281. type: 'linear',
  28282. degrees: 90,
  28283. stops: [
  28284. {
  28285. offset: 0,
  28286. color: '#CCC'
  28287. },
  28288. {
  28289. offset: 1,
  28290. color: '#BBB'
  28291. }
  28292. ]
  28293. },
  28294. lineWidth: 2
  28295. });
  28296. }
  28297. ctx.save();
  28298. shapeSprite.setAttributes(thumbAttributes);
  28299. shapeSprite.applyTransformations();
  28300. textureSprite.setAttributes(thumbAttributes);
  28301. textureSprite.applyTransformations();
  28302. shapeSprite.useAttributes(ctx);
  28303. shapeSprite.render(surface, ctx);
  28304. textureSprite.useAttributes(ctx);
  28305. textureSprite.render(surface, ctx);
  28306. ctx.restore();
  28307. },
  28308. render: function(surface, ctx) {
  28309. var me = this,
  28310. attr = me.attr,
  28311. matrix = attr.matrix.elements,
  28312. sx = matrix[0],
  28313. sy = matrix[3],
  28314. tx = matrix[4],
  28315. ty = matrix[5],
  28316. min = attr.min,
  28317. max = attr.max,
  28318. // s_min and s_max are range values in screen coordinates (scaled and translated)
  28319. s_min = min * sx + tx,
  28320. s_max = max * sx + tx,
  28321. s_y = Math.round(0.5 * sy + ty);
  28322. // thumb position in screen coordinates (mid-height)
  28323. ctx.beginPath();
  28324. // Rect that represents the whole range.
  28325. ctx.moveTo(tx, ty);
  28326. ctx.lineTo(sx + tx, ty);
  28327. ctx.lineTo(sx + tx, sy + ty);
  28328. ctx.lineTo(tx, sy + ty);
  28329. ctx.lineTo(tx, ty);
  28330. // Rect that represents the visible range.
  28331. ctx.moveTo(s_min, ty);
  28332. ctx.lineTo(s_min, sy + ty);
  28333. ctx.lineTo(s_max, sy + ty);
  28334. ctx.lineTo(s_max, ty);
  28335. ctx.lineTo(s_min, ty);
  28336. ctx.fillStroke(attr, true);
  28337. me.renderThumb(surface, ctx, Math.round(s_min), s_y);
  28338. me.renderThumb(surface, ctx, Math.round(s_max), s_y);
  28339. }
  28340. });
  28341. /**
  28342. * The Navigator component is used to visually set the visible range of the x-axis
  28343. * of a cartesian chart.
  28344. *
  28345. * This component is meant to be used with the Navigator Container
  28346. * via its {@link Ext.chart.navigator.Container#navigator} config.
  28347. *
  28348. * IMPORTANT: even though the Navigator component is a kind of chart, it should not be
  28349. * treated as such. Correct behavior is not guaranteed when using hidden/private configs.
  28350. */
  28351. Ext.define('Ext.chart.navigator.Navigator', {
  28352. extend: 'Ext.chart.navigator.NavigatorBase',
  28353. isNavigator: true,
  28354. requires: [
  28355. 'Ext.chart.navigator.sprite.RangeMask'
  28356. ],
  28357. config: {
  28358. /**
  28359. * @cfg {'bottom'/'top'} [docked='bottom']
  28360. */
  28361. docked: 'bottom',
  28362. /**
  28363. * @cfg {'series'/'chart'} [span='series']
  28364. * Whether the navigator should span the 'series' (default) or the whole 'chart'.
  28365. */
  28366. span: 'series',
  28367. insetPadding: 0,
  28368. innerPadding: 0,
  28369. /**
  28370. * @cfg {Ext.chart.navigator.Container} navigatorContainer
  28371. * 'parent' is reserved in Modern, 'container' is reserved in Classic,
  28372. * so we use 'navigatorContainer' as a config name.
  28373. * @private
  28374. */
  28375. navigatorContainer: null,
  28376. /**
  28377. * @cfg {String} axis (required)
  28378. * The ID of the {@link #chart chart's} axis to link to.
  28379. * The axis should be positioned to 'bottom' or 'top' in the chart.
  28380. */
  28381. axis: null,
  28382. /**
  28383. * @cfg {Number} [tolerance=20]
  28384. * The maximum horizontal delta between the pointer/finger and the center of a navigator thumb.
  28385. * Used for hit testing.
  28386. */
  28387. tolerance: 20,
  28388. /**
  28389. * @cfg {Number} [minimum=0.8]
  28390. * The start of the visible range, where the visible range is a [0, 1] interval.
  28391. */
  28392. minimum: 0.8,
  28393. /**
  28394. * @cfg {Number} [maximum=1]
  28395. * The end of the visible range, where the visible range is a [0, 1] interval.
  28396. */
  28397. maximum: 1,
  28398. /**
  28399. * @cfg {Number} [thumbGap=30]
  28400. * Minimum gap between navigator thumbs in pixels.
  28401. */
  28402. thumbGap: 30,
  28403. autoHideThumbs: true,
  28404. width: '100%',
  28405. /**
  28406. * @cfg {Number} [height=75]
  28407. * The height of the navigator component.
  28408. */
  28409. height: 75
  28410. },
  28411. /**
  28412. * @cfg flipXY
  28413. * @hide
  28414. */
  28415. /**
  28416. * @cfg series
  28417. * @hide
  28418. */
  28419. /**
  28420. * @cfg axes
  28421. * @hide
  28422. */
  28423. /**
  28424. * @cfg store
  28425. * @hide
  28426. */
  28427. /**
  28428. * @cfg legend
  28429. * @hide
  28430. */
  28431. /**
  28432. * @cfg interactions
  28433. * @hide
  28434. */
  28435. /**
  28436. * @cfg highlightItem
  28437. * @hide
  28438. */
  28439. /**
  28440. * @cfg theme
  28441. * @hide
  28442. */
  28443. /**
  28444. * @cfg innerPadding
  28445. * @hide
  28446. */
  28447. /**
  28448. * @cfg insetPadding
  28449. * @hide
  28450. */
  28451. dragType: null,
  28452. constructor: function(config) {
  28453. config = config || {};
  28454. var me = this,
  28455. visibleRange = [
  28456. config.minimum || 0.8,
  28457. config.maximum || 1
  28458. ],
  28459. overlay;
  28460. me.callParent([
  28461. config
  28462. ]);
  28463. overlay = me.overlaySurface;
  28464. overlay.element.setStyle({
  28465. zIndex: 100
  28466. });
  28467. me.rangeMask = overlay.add({
  28468. type: 'rangemask',
  28469. min: visibleRange[0],
  28470. max: visibleRange[1],
  28471. fillStyle: 'rgba(0, 0, 0, .25)'
  28472. });
  28473. me.onDragEnd();
  28474. // Set 'thumbOpacity' of the range mask sprite to 0, if needed,
  28475. // and apply animation modifier changes after that, so that the attribute is set
  28476. // instantly.
  28477. me.rangeMask.setAnimation({
  28478. duration: 500,
  28479. customDurations: {
  28480. min: 0,
  28481. max: 0,
  28482. translationX: 0,
  28483. translationY: 0,
  28484. scalingX: 0,
  28485. scalingY: 0,
  28486. scalingCenterX: 0,
  28487. scalingCenterY: 0,
  28488. fillStyle: 0,
  28489. strokeStyle: 0
  28490. }
  28491. });
  28492. me.setVisibleRange(visibleRange);
  28493. },
  28494. createSurface: function(id) {
  28495. var surface = this.callParent([
  28496. id
  28497. ]);
  28498. if (id === 'overlay') {
  28499. this.overlaySurface = surface;
  28500. }
  28501. return surface;
  28502. },
  28503. // Note: 'applyDock' and 'updateDock' won't ever be called in Classic.
  28504. // See Classic NavigatorBase.
  28505. applyAxis: function(axis) {
  28506. return this.getNavigatorContainer().getChart().getAxis(axis);
  28507. },
  28508. updateAxis: function(axis, oldAxis) {
  28509. var me = this,
  28510. eventName = 'visiblerangechange',
  28511. eventHandler = 'onAxisVisibleRangeChange';
  28512. if (oldAxis) {
  28513. oldAxis.un(eventName, eventHandler, me);
  28514. }
  28515. if (axis) {
  28516. axis.on(eventName, eventHandler, me);
  28517. }
  28518. me.axis = axis;
  28519. },
  28520. getAxis: function() {
  28521. // The superclass doesn't have the 'axis' config, but it has the same method,
  28522. // which we override here to act as a getter for the config. The user is not
  28523. // expected to use the original method in this subclass anyway.
  28524. return this.axis;
  28525. },
  28526. onAxisVisibleRangeChange: function(axis, visibleRange) {
  28527. this.setVisibleRange(visibleRange);
  28528. },
  28529. updateNavigatorContainer: function(navigatorContainer) {
  28530. var me = this,
  28531. oldChart = me.chart,
  28532. chart = me.chart = navigatorContainer && navigatorContainer.getChart(),
  28533. chartSeriesList = chart && chart.getSeries(),
  28534. // 'legendStore' already exists in the base class.
  28535. chartLegendStore = me.chartLegendStore,
  28536. navigatorSeriesList = [],
  28537. storeEventName = 'update',
  28538. // 'onLegendStoreUpdate' already exists in the base class.
  28539. storeEventHandler = 'onChartLegendStoreUpdate',
  28540. chartSeries, navigatorSeries, seriesConfig, i;
  28541. if (oldChart) {
  28542. oldChart.un('layout', 'afterBoundChartLayout', me);
  28543. oldChart.un('themechange', 'onChartThemeChange', me);
  28544. oldChart.un('storechange', 'onChartStoreChange', me);
  28545. }
  28546. chart.on('layout', 'afterBoundChartLayout', me);
  28547. for (i = 0; i < chartSeriesList.length; i++) {
  28548. chartSeries = chartSeriesList[i];
  28549. seriesConfig = me.getSeriesConfig(chartSeries);
  28550. navigatorSeries = Ext.create('series.' + seriesConfig.type, seriesConfig);
  28551. navigatorSeries.parentSeries = chartSeries;
  28552. chartSeries.navigatorSeries = navigatorSeries;
  28553. navigatorSeriesList.push(navigatorSeries);
  28554. }
  28555. if (chartLegendStore) {
  28556. chartLegendStore.un(storeEventName, storeEventHandler, me);
  28557. me.chartLegendStore = null;
  28558. }
  28559. if (chart) {
  28560. me.setStore(chart.getStore());
  28561. me.chartLegendStore = chartLegendStore = chart.getLegendStore();
  28562. if (chartLegendStore) {
  28563. chartLegendStore.on(storeEventName, storeEventHandler, me);
  28564. }
  28565. chart.on('themechange', 'onChartThemeChange', me);
  28566. chart.on('storechange', 'onChartStoreChange', me);
  28567. me.onChartThemeChange(chart, chart.getTheme());
  28568. }
  28569. me.setSeries(navigatorSeriesList);
  28570. },
  28571. onChartThemeChange: function(chart, theme) {
  28572. this.setTheme(theme);
  28573. },
  28574. onChartStoreChange: function(chart, store) {
  28575. this.setStore(store);
  28576. },
  28577. addCustomStyle: function(config, style, subStyle) {
  28578. var fillStyle, strokeStyle;
  28579. style = style || {};
  28580. subStyle = subStyle || {};
  28581. config.style = config.style || {};
  28582. config.subStyle = config.subStyle || {};
  28583. fillStyle = style && (style.fillStyle || style.fill);
  28584. strokeStyle = style && (style.strokeStyle || style.stroke);
  28585. if (fillStyle) {
  28586. config.style.fillStyle = fillStyle;
  28587. }
  28588. if (strokeStyle) {
  28589. config.style.strokeStyle = strokeStyle;
  28590. }
  28591. fillStyle = subStyle && (subStyle.fillStyle || subStyle.fill);
  28592. strokeStyle = subStyle && (subStyle.strokeStyle || subStyle.stroke);
  28593. if (fillStyle) {
  28594. config.subStyle.fillStyle = fillStyle;
  28595. }
  28596. if (strokeStyle) {
  28597. config.subStyle.strokeStyle = strokeStyle;
  28598. }
  28599. return config;
  28600. },
  28601. getSeriesConfig: function(chartSeries) {
  28602. var me = this,
  28603. style = chartSeries.getStyle(),
  28604. config;
  28605. if (chartSeries.isLine) {
  28606. config = me.addCustomStyle({
  28607. type: 'line',
  28608. fill: true,
  28609. xField: chartSeries.getXField(),
  28610. yField: chartSeries.getYField(),
  28611. smooth: chartSeries.getSmooth()
  28612. }, style);
  28613. } else if (chartSeries.isCandleStick) {
  28614. config = me.addCustomStyle({
  28615. type: 'line',
  28616. fill: true,
  28617. xField: chartSeries.getXField(),
  28618. yField: chartSeries.getCloseField()
  28619. }, style.raiseStyle);
  28620. } else if (chartSeries.isArea || chartSeries.isBar) {
  28621. config = me.addCustomStyle({
  28622. type: 'area',
  28623. xField: chartSeries.getXField(),
  28624. yField: chartSeries.getYField()
  28625. }, style, chartSeries.getSubStyle());
  28626. } else {
  28627. Ext.raise("Navigator only works with 'line', 'bar', 'candlestick' and 'area' series.");
  28628. }
  28629. config.style.fillOpacity = 0.2;
  28630. return config;
  28631. },
  28632. onChartLegendStoreUpdate: function(store, record) {
  28633. var me = this,
  28634. chart = me.chart,
  28635. series;
  28636. if (chart && record) {
  28637. series = chart.getSeries().map[record.get('series')];
  28638. if (series && series.navigatorSeries) {
  28639. series.navigatorSeries.setHiddenByIndex(record.get('index'), record.get('disabled'));
  28640. me.redraw();
  28641. }
  28642. }
  28643. },
  28644. setupEvents: function() {
  28645. // Called from NavigatorBase classes.
  28646. var me = this,
  28647. overlayEl = me.overlaySurface.element;
  28648. overlayEl.on({
  28649. scope: me,
  28650. drag: 'onDrag',
  28651. dragstart: 'onDragStart',
  28652. dragend: 'onDragEnd',
  28653. dragcancel: 'onDragEnd',
  28654. mousemove: 'onMouseMove'
  28655. });
  28656. },
  28657. onMouseMove: function(e) {
  28658. var me = this,
  28659. overlayEl = me.overlaySurface.element,
  28660. style = overlayEl.dom.style,
  28661. dragType = me.getDragType(e.pageX - overlayEl.getXY()[0]);
  28662. switch (dragType) {
  28663. case 'min':
  28664. case 'max':
  28665. style.cursor = 'ew-resize';
  28666. break;
  28667. case 'pan':
  28668. style.cursor = 'move';
  28669. break;
  28670. default:
  28671. style.cursor = 'default';
  28672. }
  28673. },
  28674. getDragType: function(x) {
  28675. var me = this,
  28676. t = me.getTolerance(),
  28677. width = me.overlaySurface.element.getSize().width,
  28678. rangeMask = me.rangeMask,
  28679. min = width * rangeMask.attr.min,
  28680. max = width * rangeMask.attr.max,
  28681. dragType;
  28682. if (x > min + t && x < max - t) {
  28683. dragType = 'pan';
  28684. } else if (x <= min + t && x > min - t) {
  28685. dragType = 'min';
  28686. } else if (x >= max - t && x < max + t) {
  28687. dragType = 'max';
  28688. }
  28689. return dragType;
  28690. },
  28691. onDragStart: function(e) {
  28692. // Limit drags to single touch.
  28693. if (this.dragType || e && e.touches && e.touches.length > 1) {
  28694. return;
  28695. }
  28696. var me = this,
  28697. x = e.touches[0].pageX - me.overlaySurface.element.getXY()[0],
  28698. dragType = me.getDragType(x);
  28699. me.rangeMask.attr.thumbOpacity = 1;
  28700. if (dragType) {
  28701. me.dragType = dragType;
  28702. me.touchId = e.touches[0].identifier;
  28703. me.dragX = x;
  28704. }
  28705. },
  28706. onDrag: function(e) {
  28707. if (e.touch.identifier !== this.touchId) {
  28708. return;
  28709. }
  28710. var me = this,
  28711. overlayEl = me.overlaySurface.element,
  28712. width = overlayEl.getSize().width,
  28713. x = e.touches[0].pageX - overlayEl.getXY()[0],
  28714. thumbGap = me.getThumbGap() / width,
  28715. rangeMask = me.rangeMask,
  28716. min = rangeMask.attr.min,
  28717. max = rangeMask.attr.max,
  28718. delta = max - min,
  28719. dragType = me.dragType,
  28720. drag = me.dragX,
  28721. dx = (x - drag) / width;
  28722. if (dragType === 'pan') {
  28723. min += dx;
  28724. max += dx;
  28725. if (min < 0) {
  28726. min = 0;
  28727. max = delta;
  28728. }
  28729. if (max > 1) {
  28730. max = 1;
  28731. min = max - delta;
  28732. }
  28733. } else if (dragType === 'min') {
  28734. min += dx;
  28735. if (min < 0) {
  28736. min = 0;
  28737. }
  28738. if (min > max - thumbGap) {
  28739. min = max - thumbGap;
  28740. }
  28741. } else if (dragType === 'max') {
  28742. max += dx;
  28743. if (max > 1) {
  28744. max = 1;
  28745. }
  28746. if (max < min + thumbGap) {
  28747. max = min + thumbGap;
  28748. }
  28749. } else {
  28750. return;
  28751. }
  28752. me.dragX = x;
  28753. me.setVisibleRange([
  28754. min,
  28755. max
  28756. ]);
  28757. },
  28758. onDragEnd: function() {
  28759. var me = this,
  28760. autoHideThumbs = me.getAutoHideThumbs();
  28761. me.dragType = null;
  28762. if (autoHideThumbs) {
  28763. me.rangeMask.setAttributes({
  28764. thumbOpacity: 0
  28765. });
  28766. }
  28767. },
  28768. updateMinimum: function(mininum) {
  28769. if (!this.isConfiguring) {
  28770. this.setVisibleRange([
  28771. mininum,
  28772. this.getMaximum()
  28773. ]);
  28774. }
  28775. },
  28776. updateMaximum: function(maximum) {
  28777. if (!this.isConfiguring) {
  28778. this.setVisibleRange([
  28779. this.getMinimum(),
  28780. maximum
  28781. ]);
  28782. }
  28783. },
  28784. getMinimum: function() {
  28785. return this.rangeMask.attr.min;
  28786. },
  28787. getMaximum: function() {
  28788. return this.rangeMask.attr.max;
  28789. },
  28790. setVisibleRange: function(visibleRange) {
  28791. var me = this,
  28792. chart = me.chart;
  28793. me.axis.setVisibleRange(visibleRange);
  28794. me.rangeMask.setAttributes({
  28795. min: visibleRange[0],
  28796. max: visibleRange[1]
  28797. });
  28798. me.getSurface('overlay').renderFrame();
  28799. chart.suspendAnimation();
  28800. chart.redraw();
  28801. chart.resumeAnimation();
  28802. },
  28803. afterBoundChartLayout: function() {
  28804. var me = this,
  28805. spanSeries = me.getSpan() === 'series',
  28806. mainRect = me.chart.getMainRect(),
  28807. size = me.element.getSize();
  28808. if (mainRect && spanSeries) {
  28809. me.setInsetPadding({
  28810. left: mainRect[0],
  28811. right: size.width - mainRect[2] - mainRect[0],
  28812. top: 0,
  28813. bottom: 0
  28814. });
  28815. me.performLayout();
  28816. }
  28817. },
  28818. afterChartLayout: function() {
  28819. var me = this,
  28820. size = me.overlaySurface.element.getSize();
  28821. me.rangeMask.setAttributes({
  28822. scalingCenterX: 0,
  28823. scalingCenterY: 0,
  28824. scalingX: size.width,
  28825. scalingY: size.height
  28826. });
  28827. },
  28828. doDestroy: function() {
  28829. var chart = this.chart;
  28830. if (chart && !chart.destroyed) {
  28831. chart.un('layout', 'afterBoundChartLayout', this);
  28832. }
  28833. this.callParent();
  28834. }
  28835. });
  28836. /**
  28837. * The Navigator Container is a component used to lay out the chart and its
  28838. * {@link Ext.chart.navigator.Navigator navigator}, where the navigator is docked
  28839. * to the top/bottom, and the chart fills the rest of the container's space.
  28840. *
  28841. * For example:
  28842. *
  28843. * @example
  28844. * Ext.create({
  28845. * xtype: 'chartnavigator',
  28846. * renderTo: Ext.getBody(),
  28847. * width: 600,
  28848. * height: 400,
  28849. *
  28850. * chart: {
  28851. * xtype: 'cartesian',
  28852. *
  28853. * store: {
  28854. * data: (function () {
  28855. * var data = [];
  28856. * for (var i = 0; i < 360; i++) {
  28857. * data.push({
  28858. * x: i,
  28859. * y: Math.sin(i / 45 * Math.PI)
  28860. * });
  28861. * }
  28862. * return data;
  28863. * })()
  28864. * },
  28865. * axes: [
  28866. * {
  28867. * id: 'navigable-axis',
  28868. *
  28869. * type: 'numeric',
  28870. * position: 'bottom'
  28871. * },
  28872. * {
  28873. * type: 'numeric',
  28874. * position: 'left'
  28875. * }
  28876. * ],
  28877. * series: {
  28878. * type: 'line',
  28879. * xField: 'x',
  28880. * yField: 'y'
  28881. * }
  28882. * },
  28883. *
  28884. * navigator: {
  28885. * axis: 'navigable-axis'
  28886. * }
  28887. * });
  28888. *
  28889. */
  28890. Ext.define('Ext.chart.navigator.Container', {
  28891. // We are interested in the docking functionality that's available in
  28892. // the Container in Modern and in the Panel in Classic.
  28893. extend: 'Ext.chart.navigator.ContainerBase',
  28894. requires: [
  28895. 'Ext.chart.CartesianChart',
  28896. 'Ext.chart.navigator.Navigator'
  28897. ],
  28898. xtype: 'chartnavigator',
  28899. config: {
  28900. /**
  28901. * @cfg {Ext.chart.CartesianChart} chart
  28902. * The chart to make navigable.
  28903. */
  28904. chart: null,
  28905. /**
  28906. * @cfg {Ext.chart.navigator.Navigator} navigator
  28907. */
  28908. navigator: {}
  28909. },
  28910. layout: 'fit',
  28911. applyChart: function(chart, oldChart) {
  28912. if (oldChart) {
  28913. oldChart.destroy();
  28914. }
  28915. if (chart) {
  28916. if (chart.isCartesian) {
  28917. Ext.raise('Only cartesian charts are supported.');
  28918. }
  28919. if (!chart.isChart) {
  28920. chart.$initParent = this;
  28921. chart = new Ext.chart.CartesianChart(chart);
  28922. delete chart.$initParent;
  28923. }
  28924. }
  28925. return chart;
  28926. },
  28927. legendStore: null,
  28928. surfaceRects: null,
  28929. updateChart: function(chart, oldChart) {
  28930. var me = this;
  28931. if (chart) {
  28932. me.legendStore = chart.getLegendStore();
  28933. if (!me.items && me.initItems) {
  28934. me.initItems();
  28935. }
  28936. me.add(chart);
  28937. }
  28938. },
  28939. applyNavigator: function(navigator, oldNavigator) {
  28940. var instance;
  28941. if (oldNavigator) {
  28942. oldNavigator.destroy();
  28943. }
  28944. if (navigator) {
  28945. navigator.navigatorContainer = navigator.parent = this;
  28946. instance = new Ext.chart.navigator.Navigator(navigator);
  28947. }
  28948. return instance;
  28949. },
  28950. preview: function() {
  28951. this.getNavigator().preview(this.getImage());
  28952. },
  28953. download: function(config) {
  28954. config = config || {};
  28955. config.data = this.getImage().data;
  28956. this.getNavigator().download(config);
  28957. },
  28958. setVisibleRange: function(visibleRange) {
  28959. this.getNavigator().setVisibleRange(visibleRange);
  28960. },
  28961. getImage: function(format) {
  28962. var me = this,
  28963. chart = me.getChart(),
  28964. navigator = me.getNavigator(),
  28965. docked = navigator.getDocked(),
  28966. chartImageSize = chart.bodyElement.getSize(),
  28967. navigatorImageSize = navigator.bodyElement.getSize(),
  28968. chartSurfaces = chart.getSurfaces(true),
  28969. navigatorSurfaces = navigator.getSurfaces(true),
  28970. size = {
  28971. width: chartImageSize.width,
  28972. height: chartImageSize.height + navigatorImageSize.height
  28973. },
  28974. image, imageElement, surfaces, surface;
  28975. if (docked === 'top') {
  28976. me.shiftSurfaces(chartSurfaces, 0, navigatorImageSize.height);
  28977. } else {
  28978. me.shiftSurfaces(navigatorSurfaces, 0, chartImageSize.height);
  28979. }
  28980. surfaces = chartSurfaces.concat(navigatorSurfaces);
  28981. surface = surfaces[0];
  28982. if ((Ext.isIE || Ext.isEdge) && surface.isSVG) {
  28983. // SVG data URLs don't work in IE/Edge as a source for an 'img' element,
  28984. // so we need to render SVG the usual way.
  28985. image = {
  28986. data: surface.toSVG(size, surfaces),
  28987. type: 'svg-markup'
  28988. };
  28989. } else {
  28990. image = surface.flatten(size, surfaces);
  28991. if (format === 'image') {
  28992. imageElement = new Image();
  28993. imageElement.src = image.data;
  28994. image.data = imageElement;
  28995. return image;
  28996. }
  28997. if (format === 'stream') {
  28998. image.data = image.data.replace(/^data:image\/[^;]+/, 'data:application/octet-stream');
  28999. return image;
  29000. }
  29001. }
  29002. me.unshiftSurfaces(surfaces);
  29003. return image;
  29004. },
  29005. shiftSurfaces: function(surfaces, x, y) {
  29006. var ln = surfaces.length,
  29007. i = 0,
  29008. surface;
  29009. this.surfaceRects = {};
  29010. for (; i < ln; i++) {
  29011. surface = surfaces[i];
  29012. this.shiftSurface(surface, x, y);
  29013. }
  29014. },
  29015. shiftSurface: function(surface, x, y) {
  29016. var rect = surface.getRect();
  29017. this.surfaceRects[surface.getId()] = rect.slice();
  29018. rect[0] += x;
  29019. rect[1] += y;
  29020. },
  29021. unshiftSurfaces: function(surfaces) {
  29022. var rects = this.surfaceRects,
  29023. ln = surfaces.length,
  29024. i = 0,
  29025. surface, rect, oldRect;
  29026. if (rects) {
  29027. for (; i < ln; i++) {
  29028. surface = surfaces[i];
  29029. rect = surface.getRect();
  29030. oldRect = rects[surface.getId()];
  29031. if (oldRect) {
  29032. rect[0] = oldRect[0];
  29033. rect[1] = oldRect[1];
  29034. }
  29035. }
  29036. }
  29037. this.surfaceRects = null;
  29038. }
  29039. });
  29040. /**
  29041. * A chart {@link Ext.AbstractPlugin plugin} that adds ability to listen to chart series
  29042. * items events. Item event listeners are passed two parameters: the target item and the
  29043. * event itself. The item object has the following properties:
  29044. *
  29045. * * **category** - the category the item falls under: 'items' or 'markers'
  29046. * * **field** - the store field used by this series item
  29047. * * **index** - the index of the series item
  29048. * * **record** - the store record associated with this series item
  29049. * * **series** - the series the item belongs to
  29050. * * **sprite** - the sprite used to represents this series item
  29051. *
  29052. * For example:
  29053. *
  29054. * Ext.create('Ext.chart.CartesianChart', {
  29055. * plugins: {
  29056. * chartitemevents: {
  29057. * moveEvents: true
  29058. * }
  29059. * },
  29060. * store: {
  29061. * fields: ['pet', 'households', 'total'],
  29062. * data: [
  29063. * {pet: 'Cats', households: 38, total: 93},
  29064. * {pet: 'Dogs', households: 45, total: 79},
  29065. * {pet: 'Fish', households: 13, total: 171}
  29066. * ]
  29067. * },
  29068. * axes: [{
  29069. * type: 'numeric',
  29070. * position: 'left'
  29071. * }, {
  29072. * type: 'category',
  29073. * position: 'bottom'
  29074. * }],
  29075. * series: [{
  29076. * type: 'bar',
  29077. * xField: 'pet',
  29078. * yField: 'households',
  29079. * listeners: {
  29080. * itemmousemove: function (series, item, event) {
  29081. * console.log('itemmousemove', item.category, item.field);
  29082. * }
  29083. * }
  29084. * }, {
  29085. * type: 'line',
  29086. * xField: 'pet',
  29087. * yField: 'total',
  29088. * marker: true
  29089. * }],
  29090. * listeners: { // Listen to itemclick events on all series.
  29091. * itemclick: function (chart, item, event) {
  29092. * console.log('itemclick', item.category, item.field);
  29093. * }
  29094. * }
  29095. * });
  29096. *
  29097. */
  29098. Ext.define('Ext.chart.plugin.ItemEvents', {
  29099. extend: 'Ext.plugin.Abstract',
  29100. alias: 'plugin.chartitemevents',
  29101. /**
  29102. * @cfg {Boolean} [moveEvents=false]
  29103. * If `itemmousemove`, `itemmouseover` or `itemmouseout` event listeners are attached
  29104. * to the chart, the plugin will detect those and will hit test series items on
  29105. * every move. However, if the above item events are attached on the series level
  29106. * only, this config has to be set to true, as the plugin won't perform a similar
  29107. * detection on every series.
  29108. */
  29109. moveEvents: false,
  29110. mouseMoveEvents: {
  29111. mousemove: true,
  29112. mouseover: true,
  29113. mouseout: true
  29114. },
  29115. itemMouseMoveEvents: {
  29116. itemmousemove: true,
  29117. itemmouseover: true,
  29118. itemmouseout: true
  29119. },
  29120. init: function(chart) {
  29121. var handleEvent = 'handleEvent';
  29122. this.chart = chart;
  29123. chart.addElementListener({
  29124. click: handleEvent,
  29125. tap: handleEvent,
  29126. dblclick: handleEvent,
  29127. mousedown: handleEvent,
  29128. mousemove: handleEvent,
  29129. mouseup: handleEvent,
  29130. mouseover: handleEvent,
  29131. mouseout: handleEvent,
  29132. // run our handlers before user code
  29133. priority: 1001,
  29134. scope: this
  29135. });
  29136. },
  29137. hasItemMouseMoveListeners: function() {
  29138. var listeners = this.chart.hasListeners,
  29139. name;
  29140. for (name in this.itemMouseMoveEvents) {
  29141. if (name in listeners) {
  29142. return true;
  29143. }
  29144. }
  29145. return false;
  29146. },
  29147. handleEvent: function(e) {
  29148. var me = this,
  29149. chart = me.chart,
  29150. isMouseMoveEvent = e.type in me.mouseMoveEvents,
  29151. lastItem = me.lastItem,
  29152. chartXY, item;
  29153. if (isMouseMoveEvent && !me.hasItemMouseMoveListeners() && !me.moveEvents) {
  29154. return;
  29155. }
  29156. chartXY = chart.getEventXY(e);
  29157. item = chart.getItemForPoint(chartXY[0], chartXY[1]);
  29158. if (isMouseMoveEvent && !Ext.Object.equals(item, lastItem)) {
  29159. if (lastItem) {
  29160. chart.fireEvent('itemmouseout', chart, lastItem, e);
  29161. lastItem.series.fireEvent('itemmouseout', lastItem.series, lastItem, e);
  29162. }
  29163. if (item) {
  29164. chart.fireEvent('itemmouseover', chart, item, e);
  29165. item.series.fireEvent('itemmouseover', item.series, item, e);
  29166. }
  29167. }
  29168. if (item) {
  29169. chart.fireEvent('item' + e.type, chart, item, e);
  29170. item.series.fireEvent('item' + e.type, item.series, item, e);
  29171. }
  29172. me.lastItem = item;
  29173. }
  29174. });
  29175. /**
  29176. * @abstract
  29177. * @class Ext.chart.series.Cartesian
  29178. * @extends Ext.chart.series.Series
  29179. *
  29180. * Common base class for series implementations that plot values using cartesian coordinates.
  29181. *
  29182. * @constructor
  29183. */
  29184. Ext.define('Ext.chart.series.Cartesian', {
  29185. extend: 'Ext.chart.series.Series',
  29186. config: {
  29187. /**
  29188. * @cfg {String} xField
  29189. * The field used to access the x axis value from the items from the data source.
  29190. */
  29191. xField: null,
  29192. /**
  29193. * @cfg {String|String[]} yField
  29194. * The field(s) used to access the y-axis value(s) of the items from the data source.
  29195. */
  29196. yField: null,
  29197. /**
  29198. * @cfg {Ext.chart.axis.Axis|Number|String}
  29199. * xAxis The chart axis the series is bound to in the 'X' direction.
  29200. * Normally, this would be set automatically by the series.
  29201. * For charts with multiple x-axes, this defines which x-axis is used by the series.
  29202. * It refers to either axis' ID or the (zero-based) index of the axis
  29203. * in the chart's {@link Ext.chart.AbstractChart#axes axes} config.
  29204. */
  29205. xAxis: null,
  29206. /**
  29207. * @cfg {Ext.chart.axis.Axis|Number|String}
  29208. * yAxis The chart axis the series is bound to in the 'Y' direction.
  29209. * Normally, this would be set automatically by the series.
  29210. * For charts with multiple y-axes, this defines which y-axis is used by the series.
  29211. * It refers to either axis' ID or the (zero-based) index of the axis
  29212. * in the chart's {@link Ext.chart.AbstractChart#axes axes} config.
  29213. */
  29214. yAxis: null
  29215. },
  29216. directions: [
  29217. 'X',
  29218. 'Y'
  29219. ],
  29220. /**
  29221. * @private
  29222. *
  29223. * Tells which store record fields should be used for a specific axis direction. E.g. for
  29224. *
  29225. * fieldCategory<direction>: ['<fieldConfig1>', '<fieldConfig2>', ...]
  29226. *
  29227. * the field names from the following configs will be used:
  29228. *
  29229. * series.<fieldConfig1>Field, series.<fieldConfig2>Field, ...
  29230. *
  29231. * See {@link Ext.chart.series.StackedCartesian#getFields}.
  29232. *
  29233. */
  29234. fieldCategoryX: [
  29235. 'X'
  29236. ],
  29237. fieldCategoryY: [
  29238. 'Y'
  29239. ],
  29240. applyXAxis: function(newAxis, oldAxis) {
  29241. return this.getChart().getAxis(newAxis) || oldAxis;
  29242. },
  29243. applyYAxis: function(newAxis, oldAxis) {
  29244. return this.getChart().getAxis(newAxis) || oldAxis;
  29245. },
  29246. updateXAxis: function(axis) {
  29247. axis.processData(this);
  29248. },
  29249. updateYAxis: function(axis) {
  29250. axis.processData(this);
  29251. },
  29252. coordinateX: function() {
  29253. return this.coordinate('X', 0, 2);
  29254. },
  29255. coordinateY: function() {
  29256. return this.coordinate('Y', 1, 2);
  29257. },
  29258. getItemForPoint: function(x, y) {
  29259. var me = this,
  29260. sprite = me.getSprites()[0],
  29261. store = me.getStore(),
  29262. point;
  29263. if (sprite && !me.getHidden()) {
  29264. point = sprite.getNearestDataPoint(x, y);
  29265. }
  29266. return point ? {
  29267. series: me,
  29268. sprite: sprite,
  29269. category: me.getItemInstancing() ? 'items' : 'markers',
  29270. index: point.index,
  29271. record: store.getData().items[point.index],
  29272. field: me.getYField(),
  29273. distance: point.distance
  29274. } : null;
  29275. },
  29276. createSprite: function() {
  29277. var me = this,
  29278. sprite = me.callParent(),
  29279. chart = me.getChart(),
  29280. xAxis = me.getXAxis();
  29281. sprite.setAttributes({
  29282. flipXY: chart.getFlipXY(),
  29283. xAxis: xAxis
  29284. });
  29285. if (sprite.setAggregator && xAxis && xAxis.getAggregator) {
  29286. if (xAxis.getAggregator) {
  29287. sprite.setAggregator({
  29288. strategy: xAxis.getAggregator()
  29289. });
  29290. } else {
  29291. sprite.setAggregator({});
  29292. }
  29293. }
  29294. return sprite;
  29295. },
  29296. getSprites: function() {
  29297. var me = this,
  29298. chart = this.getChart(),
  29299. sprites = me.sprites;
  29300. if (!chart) {
  29301. return Ext.emptyArray;
  29302. }
  29303. if (!sprites.length) {
  29304. me.createSprite();
  29305. }
  29306. return sprites;
  29307. },
  29308. getXRange: function() {
  29309. return [
  29310. this.dataRange[0],
  29311. this.dataRange[2]
  29312. ];
  29313. },
  29314. getYRange: function() {
  29315. return [
  29316. this.dataRange[1],
  29317. this.dataRange[3]
  29318. ];
  29319. }
  29320. });
  29321. /**
  29322. * @abstract
  29323. * @extends Ext.chart.series.Cartesian
  29324. * Abstract class for all the stacked cartesian series including area series
  29325. * and bar series.
  29326. */
  29327. Ext.define('Ext.chart.series.StackedCartesian', {
  29328. extend: 'Ext.chart.series.Cartesian',
  29329. config: {
  29330. /**
  29331. * @cfg {Boolean} [stacked=true]
  29332. * `true` to display the series in its stacked configuration.
  29333. */
  29334. stacked: true,
  29335. /**
  29336. * @cfg {Boolean} [splitStacks=true]
  29337. * `true` to stack negative/positive values in respective y-axis directions.
  29338. */
  29339. splitStacks: true,
  29340. /**
  29341. * @cfg {Boolean} [fullStack=false]
  29342. * If `true`, the height of a stacked bar is always the full height of the chart,
  29343. * with individual components viewed as shares of the whole determined by the
  29344. * {@link #fullStackTotal} config.
  29345. */
  29346. fullStack: false,
  29347. /**
  29348. * @cfg {Boolean} [fullStackTotal=100]
  29349. * If the {@link #fullStack} config is set to `true`, this will determine
  29350. * the absolute total value of each stack.
  29351. */
  29352. fullStackTotal: 100,
  29353. /**
  29354. * @cfg {Array} hidden
  29355. */
  29356. hidden: []
  29357. },
  29358. /**
  29359. * @private
  29360. * @property
  29361. * If `true`, each subsequent sprite has a lower zIndex so that the stroke of previous
  29362. * sprite in the stack is not covered by the next sprite (which makes the very top
  29363. * segment look odd in flat bar and area series, especially when wide strokes are used).
  29364. */
  29365. reversedSpriteZOrder: true,
  29366. spriteAnimationCount: 0,
  29367. themeColorCount: function() {
  29368. var me = this,
  29369. yField = me.getYField();
  29370. return Ext.isArray(yField) ? yField.length : 1;
  29371. },
  29372. updateStacked: function() {
  29373. this.processData();
  29374. },
  29375. updateSplitStacks: function() {
  29376. this.processData();
  29377. },
  29378. coordinateY: function() {
  29379. return this.coordinateStacked('Y', 1, 2);
  29380. },
  29381. coordinateStacked: function(direction, directionOffset, directionCount) {
  29382. var me = this,
  29383. store = me.getStore(),
  29384. items = store.getData().items,
  29385. itemCount = items.length,
  29386. axis = me['get' + direction + 'Axis'](),
  29387. hidden = me.getHidden(),
  29388. splitStacks = me.getSplitStacks(),
  29389. fullStack = me.getFullStack(),
  29390. fullStackTotal = me.getFullStackTotal(),
  29391. range = [
  29392. 0,
  29393. 0
  29394. ],
  29395. directions = me['fieldCategory' + direction],
  29396. dataStart = [],
  29397. posDataStart = [],
  29398. negDataStart = [],
  29399. dataEnd,
  29400. stacked = me.getStacked(),
  29401. sprites = me.getSprites(),
  29402. coordinatedData = [],
  29403. i, j, k, fields, fieldCount, posTotals, negTotals, fieldCategoriesItem, data, attr;
  29404. if (!sprites.length) {
  29405. return;
  29406. }
  29407. for (i = 0; i < directions.length; i++) {
  29408. fieldCategoriesItem = directions[i];
  29409. fields = me.getFields([
  29410. fieldCategoriesItem
  29411. ]);
  29412. fieldCount = fields.length;
  29413. for (j = 0; j < itemCount; j++) {
  29414. dataStart[j] = 0;
  29415. posDataStart[j] = 0;
  29416. negDataStart[j] = 0;
  29417. }
  29418. for (j = 0; j < fieldCount; j++) {
  29419. if (!hidden[j]) {
  29420. coordinatedData[j] = me.coordinateData(items, fields[j], axis);
  29421. }
  29422. }
  29423. if (stacked && fullStack) {
  29424. posTotals = [];
  29425. if (splitStacks) {
  29426. negTotals = [];
  29427. }
  29428. for (j = 0; j < itemCount; j++) {
  29429. posTotals[j] = 0;
  29430. if (splitStacks) {
  29431. negTotals[j] = 0;
  29432. }
  29433. for (k = 0; k < fieldCount; k++) {
  29434. data = coordinatedData[k];
  29435. if (!data) {
  29436. // If the field is hidden there's no coordinated data for it.
  29437. continue;
  29438. }
  29439. data = data[j];
  29440. if (data >= 0 || !splitStacks) {
  29441. posTotals[j] += data;
  29442. } else if (data < 0) {
  29443. negTotals[j] += data;
  29444. }
  29445. }
  29446. }
  29447. }
  29448. // else not a valid number
  29449. for (j = 0; j < fieldCount; j++) {
  29450. attr = {};
  29451. if (hidden[j]) {
  29452. attr['dataStart' + fieldCategoriesItem] = dataStart;
  29453. attr['data' + fieldCategoriesItem] = dataStart;
  29454. sprites[j].setAttributes(attr);
  29455. continue;
  29456. }
  29457. data = coordinatedData[j];
  29458. if (stacked) {
  29459. dataEnd = [];
  29460. for (k = 0; k < itemCount; k++) {
  29461. if (!data[k]) {
  29462. data[k] = 0;
  29463. }
  29464. if (data[k] >= 0 || !splitStacks) {
  29465. if (fullStack && posTotals[k]) {
  29466. data[k] *= fullStackTotal / posTotals[k];
  29467. }
  29468. dataStart[k] = posDataStart[k];
  29469. posDataStart[k] += data[k];
  29470. dataEnd[k] = posDataStart[k];
  29471. } else {
  29472. if (fullStack && negTotals[k]) {
  29473. data[k] *= fullStackTotal / negTotals[k];
  29474. }
  29475. dataStart[k] = negDataStart[k];
  29476. negDataStart[k] += data[k];
  29477. dataEnd[k] = negDataStart[k];
  29478. }
  29479. }
  29480. attr['dataStart' + fieldCategoriesItem] = dataStart;
  29481. attr['data' + fieldCategoriesItem] = dataEnd;
  29482. Ext.chart.Util.expandRange(range, dataStart);
  29483. Ext.chart.Util.expandRange(range, dataEnd);
  29484. } else {
  29485. attr['dataStart' + fieldCategoriesItem] = dataStart;
  29486. attr['data' + fieldCategoriesItem] = data;
  29487. Ext.chart.Util.expandRange(range, data);
  29488. }
  29489. sprites[j].setAttributes(attr);
  29490. }
  29491. }
  29492. range = Ext.chart.Util.validateRange(range, me.defaultRange);
  29493. me.dataRange[directionOffset] = range[0];
  29494. me.dataRange[directionOffset + directionCount] = range[1];
  29495. attr = {};
  29496. attr['dataMin' + direction] = range[0];
  29497. attr['dataMax' + direction] = range[1];
  29498. for (i = 0; i < sprites.length; i++) {
  29499. sprites[i].setAttributes(attr);
  29500. }
  29501. },
  29502. getFields: function(fieldCategory) {
  29503. var me = this,
  29504. fields = [],
  29505. ln = fieldCategory.length,
  29506. i, fieldsItem;
  29507. for (i = 0; i < ln; i++) {
  29508. fieldsItem = me['get' + fieldCategory[i] + 'Field']();
  29509. if (Ext.isArray(fieldsItem)) {
  29510. fields.push.apply(fields, fieldsItem);
  29511. } else {
  29512. fields.push(fieldsItem);
  29513. }
  29514. }
  29515. return fields;
  29516. },
  29517. updateLabelOverflowPadding: function(labelOverflowPadding) {
  29518. var me = this,
  29519. label;
  29520. if (!me.isConfiguring) {
  29521. label = me.getLabel();
  29522. if (label) {
  29523. label.setAttributes({
  29524. labelOverflowPadding: labelOverflowPadding
  29525. });
  29526. }
  29527. }
  29528. },
  29529. updateLabelData: function() {
  29530. var me = this,
  29531. label = me.getLabel();
  29532. if (label) {
  29533. label.setAttributes({
  29534. labelOverflowPadding: me.getLabelOverflowPadding()
  29535. });
  29536. }
  29537. me.callParent();
  29538. },
  29539. getSprites: function() {
  29540. var me = this,
  29541. chart = me.getChart(),
  29542. fields = me.getFields(me.fieldCategoryY),
  29543. itemInstancing = me.getItemInstancing(),
  29544. sprites = me.sprites,
  29545. hidden = me.getHidden(),
  29546. spritesCreated = false,
  29547. fieldCount = fields.length,
  29548. i, sprite;
  29549. if (!chart) {
  29550. return [];
  29551. }
  29552. // Create one Ext.chart.series.sprite.StackedCartesian sprite per field.
  29553. for (i = 0; i < fieldCount; i++) {
  29554. sprite = sprites[i];
  29555. if (!sprite) {
  29556. sprite = me.createSprite();
  29557. sprite.setAttributes({
  29558. zIndex: (me.reversedSpriteZOrder ? -1 : 1) * i
  29559. });
  29560. sprite.setField(fields[i]);
  29561. spritesCreated = true;
  29562. hidden.push(false);
  29563. if (itemInstancing) {
  29564. sprite.getMarker('items').getTemplate().setAttributes(me.getStyleByIndex(i));
  29565. } else {
  29566. sprite.setAttributes(me.getStyleByIndex(i));
  29567. }
  29568. }
  29569. }
  29570. if (spritesCreated) {
  29571. me.updateHidden(hidden);
  29572. }
  29573. return sprites;
  29574. },
  29575. getItemForPoint: function(x, y) {
  29576. var me = this,
  29577. sprites = me.getSprites(),
  29578. store = me.getStore(),
  29579. hidden = me.getHidden(),
  29580. minDistance = Infinity,
  29581. item = null,
  29582. spriteIndex = -1,
  29583. pointIndex = -1,
  29584. point, yField, sprite, i, ln;
  29585. for (i = 0 , ln = sprites.length; i < ln; i++) {
  29586. if (hidden[i]) {
  29587. continue;
  29588. }
  29589. sprite = sprites[i];
  29590. point = sprite.getNearestDataPoint(x, y);
  29591. // Don't stop when the first matching point is found.
  29592. // Keep looking for the nearest point.
  29593. if (point) {
  29594. if (point.distance < minDistance) {
  29595. minDistance = point.distance;
  29596. pointIndex = point.index;
  29597. spriteIndex = i;
  29598. }
  29599. }
  29600. }
  29601. if (spriteIndex > -1) {
  29602. yField = me.getYField();
  29603. item = {
  29604. series: me,
  29605. sprite: sprites[spriteIndex],
  29606. category: me.getItemInstancing() ? 'items' : 'markers',
  29607. index: pointIndex,
  29608. record: store.getData().items[pointIndex],
  29609. // Handle the case where we're stacked but a single segment
  29610. field: typeof yField === 'string' ? yField : yField[spriteIndex],
  29611. distance: minDistance
  29612. };
  29613. }
  29614. return item;
  29615. },
  29616. provideLegendInfo: function(target) {
  29617. var me = this,
  29618. sprites = me.getSprites(),
  29619. title = me.getTitle(),
  29620. field = me.getYField(),
  29621. hidden = me.getHidden(),
  29622. single = sprites.length === 1,
  29623. style, fill, i, name;
  29624. for (i = 0; i < sprites.length; i++) {
  29625. style = me.getStyleByIndex(i);
  29626. fill = style.fillStyle;
  29627. if (title) {
  29628. if (Ext.isArray(title)) {
  29629. name = title[i];
  29630. } else if (single) {
  29631. name = title;
  29632. }
  29633. }
  29634. if (!title || !name) {
  29635. if (Ext.isArray(field)) {
  29636. name = field[i];
  29637. } else {
  29638. name = me.getId();
  29639. }
  29640. }
  29641. target.push({
  29642. name: name,
  29643. mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
  29644. disabled: hidden[i],
  29645. series: me.getId(),
  29646. index: i
  29647. });
  29648. }
  29649. },
  29650. onSpriteAnimationStart: function(sprite) {
  29651. this.spriteAnimationCount++;
  29652. if (this.spriteAnimationCount === 1) {
  29653. this.fireEvent('animationstart');
  29654. }
  29655. },
  29656. onSpriteAnimationEnd: function(sprite) {
  29657. this.spriteAnimationCount--;
  29658. if (this.spriteAnimationCount === 0) {
  29659. this.fireEvent('animationend');
  29660. }
  29661. }
  29662. });
  29663. /**
  29664. * Base class for all series sprites.
  29665. * Defines attributes common to all series sprites, like data in x/y directions and its min/max values,
  29666. * and configs, like the {@link Ext.chart.series.Series} instance that manages the sprite.
  29667. *
  29668. */
  29669. Ext.define('Ext.chart.series.sprite.Series', {
  29670. extend: 'Ext.draw.sprite.Sprite',
  29671. mixins: {
  29672. markerHolder: 'Ext.chart.MarkerHolder'
  29673. },
  29674. inheritableStatics: {
  29675. def: {
  29676. processors: {
  29677. /**
  29678. * @cfg {Number} [dataMinX=0] Data minimum on the x-axis.
  29679. */
  29680. dataMinX: 'number',
  29681. /**
  29682. * @cfg {Number} [dataMaxX=1] Data maximum on the x-axis.
  29683. */
  29684. dataMaxX: 'number',
  29685. /**
  29686. * @cfg {Number} [dataMinY=0] Data minimum on the y-axis.
  29687. */
  29688. dataMinY: 'number',
  29689. /**
  29690. * @cfg {Number} [dataMaxY=1] Data maximum on the y-axis.
  29691. */
  29692. dataMaxY: 'number',
  29693. /**
  29694. * @cfg {Array} [rangeX=null] Data range derived from all the series bound to the x-axis.
  29695. */
  29696. rangeX: 'data',
  29697. /**
  29698. * @cfg {Array} [rangeY=null] Data range derived from all the series bound to the y-axis.
  29699. */
  29700. rangeY: 'data',
  29701. /**
  29702. * @cfg {Object} [dataX=null] Data items on the x-axis.
  29703. */
  29704. dataX: 'data',
  29705. /**
  29706. * @cfg {Object} [dataY=null] Data items on the y-axis.
  29707. */
  29708. dataY: 'data',
  29709. /**
  29710. * @cfg {Object} [labels=null] Labels used in the series.
  29711. */
  29712. labels: 'default',
  29713. /**
  29714. * @cfg {Number} [labelOverflowPadding=10] Padding around labels to determine overlap.
  29715. */
  29716. labelOverflowPadding: 'number'
  29717. },
  29718. defaults: {
  29719. dataMinX: 0,
  29720. dataMaxX: 1,
  29721. dataMinY: 0,
  29722. dataMaxY: 1,
  29723. rangeX: null,
  29724. rangeY: null,
  29725. dataX: null,
  29726. dataY: null,
  29727. labels: null,
  29728. labelOverflowPadding: 10
  29729. },
  29730. triggers: {
  29731. dataX: 'bbox',
  29732. dataY: 'bbox',
  29733. dataMinX: 'bbox',
  29734. dataMaxX: 'bbox',
  29735. dataMinY: 'bbox',
  29736. dataMaxY: 'bbox'
  29737. }
  29738. }
  29739. },
  29740. config: {
  29741. /**
  29742. * @private
  29743. * @cfg {Object} store The store that is passed to the renderer.
  29744. */
  29745. store: null,
  29746. series: null,
  29747. /**
  29748. * @cfg {String} field The store field used by the series.
  29749. */
  29750. field: null
  29751. }
  29752. });
  29753. /**
  29754. * Cartesian sprite.
  29755. */
  29756. Ext.define('Ext.chart.series.sprite.Cartesian', {
  29757. extend: 'Ext.chart.series.sprite.Series',
  29758. inheritableStatics: {
  29759. def: {
  29760. processors: {
  29761. /**
  29762. * @cfg {Number} [selectionTolerance=20]
  29763. * The distance from the event position to the sprite's data points to trigger interactions (used for 'iteminfo', etc).
  29764. */
  29765. selectionTolerance: 'number',
  29766. /**
  29767. * @cfg {Boolean} flipXY If flipXY is 'true', the series is flipped.
  29768. */
  29769. flipXY: 'bool',
  29770. renderer: 'default',
  29771. // Visible range of data (pan/zoom) information.
  29772. visibleMinX: 'number',
  29773. visibleMinY: 'number',
  29774. visibleMaxX: 'number',
  29775. visibleMaxY: 'number',
  29776. innerWidth: 'number',
  29777. innerHeight: 'number'
  29778. },
  29779. defaults: {
  29780. selectionTolerance: 20,
  29781. flipXY: false,
  29782. renderer: null,
  29783. transformFillStroke: false,
  29784. visibleMinX: 0,
  29785. visibleMinY: 0,
  29786. visibleMaxX: 1,
  29787. visibleMaxY: 1,
  29788. innerWidth: 1,
  29789. innerHeight: 1
  29790. },
  29791. triggers: {
  29792. dataX: 'dataX,bbox',
  29793. dataY: 'dataY,bbox',
  29794. visibleMinX: 'panzoom',
  29795. visibleMinY: 'panzoom',
  29796. visibleMaxX: 'panzoom',
  29797. visibleMaxY: 'panzoom',
  29798. innerWidth: 'panzoom',
  29799. innerHeight: 'panzoom'
  29800. },
  29801. updaters: {
  29802. dataX: function(attr) {
  29803. this.processDataX();
  29804. this.scheduleUpdater(attr, 'dataY', [
  29805. 'dataY'
  29806. ]);
  29807. },
  29808. dataY: function() {
  29809. this.processDataY();
  29810. },
  29811. panzoom: function(attr) {
  29812. // dx, dy are deltas between min & max of coordinated data values.
  29813. var dx = attr.visibleMaxX - attr.visibleMinX,
  29814. dy = attr.visibleMaxY - attr.visibleMinY,
  29815. innerWidth = attr.flipXY ? attr.innerHeight : attr.innerWidth,
  29816. innerHeight = !attr.flipXY ? attr.innerHeight : attr.innerWidth,
  29817. surface = this.getSurface(),
  29818. isRtl = surface ? surface.getInherited().rtl : false;
  29819. attr.scalingCenterX = 0;
  29820. attr.scalingCenterY = 0;
  29821. attr.scalingX = innerWidth / dx;
  29822. attr.scalingY = innerHeight / dy;
  29823. // (attr.visibleMinY * attr.scalingY) will be the vertical position of
  29824. // our minimum data points, which we want to be at zero, so we offset
  29825. // by this amount.
  29826. attr.translationX = -(attr.visibleMinX * attr.scalingX);
  29827. attr.translationY = -(attr.visibleMinY * attr.scalingY);
  29828. if (isRtl && !attr.flipXY) {
  29829. attr.scalingX *= -1;
  29830. attr.translationX *= -1;
  29831. attr.translationX += innerWidth;
  29832. }
  29833. this.applyTransformations(true);
  29834. }
  29835. }
  29836. }
  29837. },
  29838. processDataY: Ext.emptyFn,
  29839. processDataX: Ext.emptyFn,
  29840. updatePlainBBox: function(plain) {
  29841. var attr = this.attr;
  29842. plain.x = attr.dataMinX;
  29843. plain.y = attr.dataMinY;
  29844. plain.width = attr.dataMaxX - attr.dataMinX;
  29845. plain.height = attr.dataMaxY - attr.dataMinY;
  29846. },
  29847. /**
  29848. * Does a binary search of the data on the x-axis using the given key.
  29849. * @param {String} key
  29850. * @return {*}
  29851. */
  29852. binarySearch: function(key) {
  29853. var dx = this.attr.dataX,
  29854. start = 0,
  29855. end = dx.length;
  29856. if (key <= dx[0]) {
  29857. return start;
  29858. }
  29859. if (key >= dx[end - 1]) {
  29860. return end - 1;
  29861. }
  29862. while (start + 1 < end) {
  29863. var mid = (start + end) >> 1,
  29864. val = dx[mid];
  29865. if (val === key) {
  29866. return mid;
  29867. } else if (val < key) {
  29868. start = mid;
  29869. } else {
  29870. end = mid;
  29871. }
  29872. }
  29873. return start;
  29874. },
  29875. render: function(surface, ctx, surfaceClipRect) {
  29876. var me = this,
  29877. attr = me.attr,
  29878. margin = 1,
  29879. // TODO: why do we need it?
  29880. inverseMatrix = attr.inverseMatrix.clone();
  29881. // The sprite's `attr.matrix` is stretching/shrinking data coordinates
  29882. // to surface coordinates.
  29883. // This matrix is set (indirectly) by the 'panzoom' updater.
  29884. // The sprite's `attr.inverseMatrix` does the opposite.
  29885. //
  29886. // The `surface.matrix` of the 'series' surface of a cartesian chart flips the
  29887. // surface content vertically, so that y=0 is at the bottom (look for
  29888. // `surface.matrix.set` call in the CartesianChart.performLayout method).
  29889. // This matrix is set in the 'performLayout' of the CartesianChart.
  29890. // The `surface.inverseMatrix` flips the content back.
  29891. //
  29892. // By combining the inverse matrices of the series surface and the series sprite,
  29893. // we essentially get a transformation that allows us to go from surface coordinates
  29894. // in a final flipped drawing back to data points.
  29895. //
  29896. // For example
  29897. //
  29898. // inverseMatrix.transformPoint([ 0, rect[3] ])
  29899. // inverseMatrix.transformPoint([ rect[2], 0 ])
  29900. //
  29901. // will return
  29902. //
  29903. // [attr.dataMinX, attr.dataMinY]
  29904. // [attr.dataMaxX, attr.dataMaxY]
  29905. //
  29906. // because left/bottom and top/right of the series surface is where the first smallest
  29907. // and last largest data points would be (given no pan/zoom), respectively.
  29908. //
  29909. // So the `dataClipRect` passed to the `renderClipped` call below is effectively
  29910. // the visible rect in data (not surface!) coordinates.
  29911. // It is important to note, that the all the scaling and translation is defined
  29912. // by the sprite's matrix, the 'series' surface matrix does not contain scaling
  29913. // or translation components, except for the vertical flipping.
  29914. // This is important because there is a common pattern in chart series sprites
  29915. // (MarkerHolders) - instead of using transform attributes for their Markers
  29916. // (e.g. instances of a 'rect' sprite in case of 'bar' series), the attributes
  29917. // that would position a sprite with no transformations are transformed.
  29918. // For example, to draw a rect with coordinates TL(10, 10), BR(20, 40),
  29919. // we could use the folling 'rect' sprite attributes:
  29920. //
  29921. // {
  29922. // x: 0,
  29923. // y: 0
  29924. // width: 10,
  29925. // height: 30
  29926. //
  29927. // translationX: 10,
  29928. // translationY: 10
  29929. //
  29930. // But the correct thing to do here is
  29931. //
  29932. // {
  29933. // x: 10,
  29934. // y: 10,
  29935. // width: 10,
  29936. // height: 30
  29937. // }
  29938. //
  29939. // Similarly, if the sprite was scaled, the 'x', 'y', 'width', 'height' attributes
  29940. // would have to account for that as well.
  29941. //
  29942. // This is done, so that the attribute values a marker gets by the time it renders,
  29943. // are the final values, and are not affected later by other transforms, such as
  29944. // surface matrix scaling, which could ruin the visual result, if the attributes
  29945. // values are doctored to make lines align to the pixel grid (which is typically
  29946. // the case).
  29947. inverseMatrix.appendMatrix(surface.inverseMatrix);
  29948. if (attr.dataX === null || attr.dataX === undefined) {
  29949. return;
  29950. }
  29951. if (attr.dataY === null || attr.dataY === undefined) {
  29952. return;
  29953. }
  29954. if (inverseMatrix.getXX() * inverseMatrix.getYX() || inverseMatrix.getXY() * inverseMatrix.getYY()) {
  29955. Ext.Logger.warn('Cartesian Series sprite does not support rotation/sheering');
  29956. return;
  29957. }
  29958. var dataClipRect = inverseMatrix.transformList([
  29959. [
  29960. surfaceClipRect[0] - margin,
  29961. surfaceClipRect[3] + margin
  29962. ],
  29963. // (left, height)
  29964. [
  29965. surfaceClipRect[0] + surfaceClipRect[2] + margin,
  29966. -margin
  29967. ]
  29968. ]);
  29969. // (width, top)
  29970. dataClipRect = dataClipRect[0].concat(dataClipRect[1]);
  29971. // TODO: RTL improvements:
  29972. // TODO: produce such a dataClipRect here, so that we don't have to do:
  29973. // TODO: min = Math.min(dataClipRect[0], dataClipRect[2])
  29974. // TODO: max = Math.max(dataClipRect[0], dataClipRect[2])
  29975. // TODO: inside each 'renderClipped' call
  29976. me.renderClipped(surface, ctx, dataClipRect, surfaceClipRect);
  29977. },
  29978. /**
  29979. * Render the given visible clip range.
  29980. * @param {Ext.draw.Surface} surface A draw container surface.
  29981. * @param {CanvasRenderingContext2D} ctx A context object that is API compatible with the native
  29982. * [CanvasRenderingContext2D](https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D).
  29983. * @param {Number[]} dataClipRect The clip rect in data coordinates, roughly equivalent to
  29984. * [attr.dataMinX, attr.dataMinY, attr.dataMaxX, attr.dataMaxY] for an untranslated/unscaled surface/sprite.
  29985. * @param {Number[]} surfaceClipRect The clip rect in surface coordinates: [left, top, width, height].
  29986. * @method
  29987. */
  29988. renderClipped: Ext.emptyFn,
  29989. /**
  29990. * Get the nearest item index from point (x, y). -1 as not found.
  29991. * @param {Number} x
  29992. * @param {Number} y
  29993. * @return {Number} The index
  29994. * @deprecated 6.5.2 Use {@link #getNearestDataPoint} instead.
  29995. */
  29996. getIndexNearPoint: function(x, y) {
  29997. var result = this.getNearestDataPoint(x, y);
  29998. return result ? result.index : -1;
  29999. },
  30000. /**
  30001. * Given a point in 'series' surface element coordinates, returns the `index` of the
  30002. * sprite's data point that is nearest to that point, along with the `distance`
  30003. * between points.
  30004. * If the `selectionTolerance` attribute of the sprite is not zero, only the data points
  30005. * that are within that pixel distance from the given point will be checked.
  30006. * In the event no such data points exist or the data is empty, `null` is returned.
  30007. *
  30008. * Notes:
  30009. * 1) given a mouse/pointer event object, the surface coordinates of the event can be
  30010. * obtained with the `getEventXY` method of the chart;
  30011. * 2) using `selectionTolerance` of zero is useful for series with no visible markers,
  30012. * such as the Area series, where this attribute becomes meaningless.
  30013. *
  30014. * @param {Number} x
  30015. * @param {Number} y
  30016. * @return {Object}
  30017. */
  30018. getNearestDataPoint: function(x, y) {
  30019. var me = this,
  30020. attr = me.attr,
  30021. series = me.getSeries(),
  30022. surface = me.getSurface(),
  30023. items = me.boundMarkers.items,
  30024. matrix = attr.matrix,
  30025. dataX = attr.dataX,
  30026. dataY = attr.dataY,
  30027. selectionTolerance = attr.selectionTolerance,
  30028. minDistance = Infinity,
  30029. index = -1,
  30030. result = null,
  30031. distance, dx, dy, xy, i, ln, end, inc;
  30032. // Notes:
  30033. // Instead of converting the given point from surface coordinates to data coordinates
  30034. // and then measuring the distances between it and the data points, we have to
  30035. // convert all the data points to surface coordinates and measure the distances
  30036. // between them and the given point. This is because the data coordinates can use
  30037. // different scales, which makes distance measurement impossible.
  30038. // For example, if the x-axis is a `category` axis, the categories will be assigned
  30039. // indexes starting from 0, that's what the `attr.dataX` array will contain;
  30040. // and if the y-axis is a `numeric` axis, the `attr.dataY` array will simply contain
  30041. // the original values.
  30042. //
  30043. // Either 'items' or 'markers' will be highlighted. If a sprite has both (for example,
  30044. // 'bar' series with the 'marker' config, where the bars are 'items' and marker instances
  30045. // are 'markers'), only the 'items' (bars) will be highlighted.
  30046. if (items) {
  30047. ln = dataX.length;
  30048. if (series.reversedSpriteZOrder) {
  30049. i = ln - 1;
  30050. end = -1;
  30051. inc = -1;
  30052. } else {
  30053. i = 0;
  30054. end = ln;
  30055. inc = 1;
  30056. }
  30057. for (; i !== end; i += inc) {
  30058. var bbox = me.getMarkerBBox('items', i);
  30059. // Transform the given surface element coordinates to logical coordinates
  30060. // of the surface (the ones the bbox uses).
  30061. xy = surface.inverseMatrix.transformPoint([
  30062. x,
  30063. y
  30064. ]);
  30065. if (Ext.draw.Draw.isPointInBBox(xy[0], xy[1], bbox)) {
  30066. index = i;
  30067. minDistance = 0;
  30068. // Return the first item that contains our touch point.
  30069. break;
  30070. }
  30071. }
  30072. } else {
  30073. // markers
  30074. for (i = 0 , ln = dataX.length; i < ln; i++) {
  30075. // Convert from data coordinates to coordinates within inner size rectangle.
  30076. // See `panzoom` method for more details.
  30077. xy = matrix.transformPoint([
  30078. dataX[i],
  30079. dataY[i]
  30080. ]);
  30081. // Flip back vertically and padding adjust (see `render` method comments).
  30082. xy = surface.matrix.transformPoint(xy);
  30083. // Essentially sprites go through the same two transformations when they render
  30084. // data points.
  30085. dx = x - xy[0];
  30086. dy = y - xy[1];
  30087. distance = Math.sqrt(dx * dx + dy * dy);
  30088. if (selectionTolerance && distance > selectionTolerance) {
  30089. continue;
  30090. }
  30091. if (distance < minDistance) {
  30092. minDistance = distance;
  30093. index = i;
  30094. }
  30095. }
  30096. }
  30097. // Keep looking for the nearest marker.
  30098. if (index > -1) {
  30099. result = {
  30100. index: index,
  30101. distance: minDistance
  30102. };
  30103. }
  30104. return result;
  30105. }
  30106. });
  30107. /**
  30108. * @class Ext.chart.series.sprite.StackedCartesian
  30109. * @extends Ext.chart.series.sprite.Cartesian
  30110. *
  30111. * Stacked cartesian sprite.
  30112. */
  30113. Ext.define('Ext.chart.series.sprite.StackedCartesian', {
  30114. extend: 'Ext.chart.series.sprite.Cartesian',
  30115. inheritableStatics: {
  30116. def: {
  30117. processors: {
  30118. /**
  30119. * @private
  30120. * @cfg {Number} [groupCount=1] The number of items (e.g. bars) in a group.
  30121. */
  30122. groupCount: 'number',
  30123. /**
  30124. * @private
  30125. * @cfg {Number} [groupOffset=0] The group index of the series sprite.
  30126. */
  30127. groupOffset: 'number',
  30128. /**
  30129. * @private
  30130. * @cfg {Object} [dataStartY=null] The starting point of the data used in the series.
  30131. */
  30132. dataStartY: 'data'
  30133. },
  30134. defaults: {
  30135. selectionTolerance: 20,
  30136. groupCount: 1,
  30137. groupOffset: 0,
  30138. dataStartY: null
  30139. },
  30140. triggers: {
  30141. dataStartY: 'dataY,bbox'
  30142. }
  30143. }
  30144. }
  30145. });
  30146. /**
  30147. * @class Ext.chart.series.sprite.Area
  30148. * @extends Ext.chart.series.sprite.StackedCartesian
  30149. *
  30150. * Area series sprite.
  30151. */
  30152. Ext.define('Ext.chart.series.sprite.Area', {
  30153. alias: 'sprite.areaSeries',
  30154. extend: 'Ext.chart.series.sprite.StackedCartesian',
  30155. inheritableStatics: {
  30156. def: {
  30157. processors: {
  30158. /**
  30159. * @cfg {Boolean} [step=false] 'true' if the area is represented with steps instead of lines.
  30160. */
  30161. step: 'bool'
  30162. },
  30163. defaults: {
  30164. selectionTolerance: 0,
  30165. step: false
  30166. }
  30167. }
  30168. },
  30169. renderClipped: function(surface, ctx, dataClipRect) {
  30170. var me = this,
  30171. store = me.getStore(),
  30172. series = me.getSeries(),
  30173. attr = me.attr,
  30174. dataX = attr.dataX,
  30175. dataY = attr.dataY,
  30176. dataStartY = attr.dataStartY,
  30177. matrix = attr.matrix,
  30178. x, y, i, lastX, lastY, startX, startY,
  30179. xx = matrix.elements[0],
  30180. dx = matrix.elements[4],
  30181. yy = matrix.elements[3],
  30182. dy = matrix.elements[5],
  30183. surfaceMatrix = me.surfaceMatrix,
  30184. markerCfg = {},
  30185. min = Math.min(dataClipRect[0], dataClipRect[2]),
  30186. max = Math.max(dataClipRect[0], dataClipRect[2]),
  30187. start = Math.max(0, this.binarySearch(min)),
  30188. end = Math.min(dataX.length - 1, this.binarySearch(max) + 1),
  30189. renderer = attr.renderer,
  30190. rendererData = {
  30191. store: store
  30192. },
  30193. rendererChanges;
  30194. ctx.beginPath();
  30195. startX = dataX[start] * xx + dx;
  30196. startY = dataY[start] * yy + dy;
  30197. ctx.moveTo(startX, startY);
  30198. if (attr.step) {
  30199. lastY = startY;
  30200. for (i = start; i <= end; i++) {
  30201. x = dataX[i] * xx + dx;
  30202. y = dataY[i] * yy + dy;
  30203. ctx.lineTo(x, lastY);
  30204. ctx.lineTo(x, lastY = y);
  30205. }
  30206. } else {
  30207. for (i = start; i <= end; i++) {
  30208. x = dataX[i] * xx + dx;
  30209. y = dataY[i] * yy + dy;
  30210. ctx.lineTo(x, y);
  30211. }
  30212. }
  30213. if (dataStartY) {
  30214. if (attr.step) {
  30215. lastX = dataX[end] * xx + dx;
  30216. for (i = end; i >= start; i--) {
  30217. x = dataX[i] * xx + dx;
  30218. y = dataStartY[i] * yy + dy;
  30219. ctx.lineTo(lastX, y);
  30220. ctx.lineTo(lastX = x, y);
  30221. }
  30222. } else {
  30223. for (i = end; i >= start; i--) {
  30224. x = dataX[i] * xx + dx;
  30225. y = dataStartY[i] * yy + dy;
  30226. ctx.lineTo(x, y);
  30227. }
  30228. }
  30229. } else {
  30230. ctx.lineTo(dataX[end] * xx + dx, y);
  30231. ctx.lineTo(dataX[end] * xx + dx, dy);
  30232. ctx.lineTo(startX, dy);
  30233. ctx.lineTo(startX, dataY[i] * yy + dy);
  30234. }
  30235. if (attr.transformFillStroke) {
  30236. attr.matrix.toContext(ctx);
  30237. }
  30238. ctx.fill();
  30239. if (attr.transformFillStroke) {
  30240. attr.inverseMatrix.toContext(ctx);
  30241. }
  30242. ctx.beginPath();
  30243. ctx.moveTo(startX, startY);
  30244. if (attr.step) {
  30245. for (i = start; i <= end; i++) {
  30246. x = dataX[i] * xx + dx;
  30247. y = dataY[i] * yy + dy;
  30248. ctx.lineTo(x, lastY);
  30249. ctx.lineTo(x, lastY = y);
  30250. markerCfg.translationX = surfaceMatrix.x(x, y);
  30251. markerCfg.translationY = surfaceMatrix.y(x, y);
  30252. if (renderer) {
  30253. // callback(fn, scope, args, delay, caller)
  30254. rendererChanges = Ext.callback(renderer, null, [
  30255. me,
  30256. markerCfg,
  30257. rendererData,
  30258. i
  30259. ], 0, series);
  30260. Ext.apply(markerCfg, rendererChanges);
  30261. }
  30262. me.putMarker('markers', markerCfg, i, !renderer);
  30263. }
  30264. } else {
  30265. for (i = start; i <= end; i++) {
  30266. x = dataX[i] * xx + dx;
  30267. y = dataY[i] * yy + dy;
  30268. ctx.lineTo(x, y);
  30269. markerCfg.translationX = surfaceMatrix.x(x, y);
  30270. markerCfg.translationY = surfaceMatrix.y(x, y);
  30271. if (renderer) {
  30272. rendererChanges = Ext.callback(renderer, null, [
  30273. me,
  30274. markerCfg,
  30275. rendererData,
  30276. i
  30277. ], 0, series);
  30278. Ext.apply(markerCfg, rendererChanges);
  30279. }
  30280. me.putMarker('markers', markerCfg, i, !renderer);
  30281. }
  30282. }
  30283. if (attr.transformFillStroke) {
  30284. attr.matrix.toContext(ctx);
  30285. }
  30286. ctx.stroke();
  30287. }
  30288. });
  30289. /**
  30290. * @class Ext.chart.series.Area
  30291. * @extends Ext.chart.series.StackedCartesian
  30292. *
  30293. * Creates an Area Chart.
  30294. *
  30295. * @example
  30296. * Ext.create({
  30297. * xtype: 'cartesian',
  30298. * renderTo: document.body,
  30299. * width: 600,
  30300. * height: 400,
  30301. * insetPadding: 40,
  30302. * store: {
  30303. * fields: ['name', 'data1', 'data2', 'data3'],
  30304. * data: [{
  30305. * name: 'metric one',
  30306. * data1: 10,
  30307. * data2: 12,
  30308. * data3: 14
  30309. * }, {
  30310. * name: 'metric two',
  30311. * data1: 7,
  30312. * data2: 8,
  30313. * data3: 16
  30314. * }, {
  30315. * name: 'metric three',
  30316. * data1: 5,
  30317. * data2: 2,
  30318. * data3: 14
  30319. * }, {
  30320. * name: 'metric four',
  30321. * data1: 2,
  30322. * data2: 14,
  30323. * data3: 6
  30324. * }, {
  30325. * name: 'metric five',
  30326. * data1: 27,
  30327. * data2: 38,
  30328. * data3: 36
  30329. * }]
  30330. * },
  30331. * axes: [{
  30332. * type: 'numeric',
  30333. * position: 'left',
  30334. * fields: ['data1'],
  30335. * grid: true,
  30336. * minimum: 0
  30337. * }, {
  30338. * type: 'category',
  30339. * position: 'bottom',
  30340. * fields: ['name']
  30341. * }],
  30342. * series: {
  30343. * type: 'area',
  30344. * subStyle: {
  30345. * fill: ['#0A3F50', '#30BDA7', '#96D4C6']
  30346. * },
  30347. * xField: 'name',
  30348. * yField: ['data1', 'data2', 'data3']
  30349. * }
  30350. * });
  30351. */
  30352. Ext.define('Ext.chart.series.Area', {
  30353. extend: 'Ext.chart.series.StackedCartesian',
  30354. alias: 'series.area',
  30355. type: 'area',
  30356. /**
  30357. * @property seriesType
  30358. * @inheritdoc
  30359. */
  30360. seriesType: 'areaSeries',
  30361. isArea: true,
  30362. requires: [
  30363. 'Ext.chart.series.sprite.Area'
  30364. ],
  30365. config: {
  30366. /**
  30367. * @cfg splitStacks
  30368. * @inheritdoc
  30369. */
  30370. splitStacks: false
  30371. }
  30372. });
  30373. /**
  30374. * @cfg renderer
  30375. * @inheritdoc
  30376. * Area series renderers only affect markers.
  30377. * For styling individual segments with a renderer it is possible to use
  30378. * the Line series with {@link Ext.chart.series.Line#fill} config set to `true`,
  30379. * which makes Line series look like Area series.
  30380. */
  30381. /**
  30382. * @class Ext.chart.series.sprite.Bar
  30383. * @extends Ext.chart.series.sprite.StackedCartesian
  30384. *
  30385. * Draws a sprite used in the bar series.
  30386. */
  30387. Ext.define('Ext.chart.series.sprite.Bar', {
  30388. alias: 'sprite.barSeries',
  30389. extend: 'Ext.chart.series.sprite.StackedCartesian',
  30390. inheritableStatics: {
  30391. def: {
  30392. processors: {
  30393. /**
  30394. * @cfg {Number} [minBarWidth=2] The minimum bar width.
  30395. */
  30396. minBarWidth: 'number',
  30397. /**
  30398. * @cfg {Number} [maxBarWidth=100] The maximum bar width.
  30399. */
  30400. maxBarWidth: 'number',
  30401. /**
  30402. * @cfg {Number} [minGapWidth=5] The minimum gap between bars.
  30403. */
  30404. minGapWidth: 'number',
  30405. /**
  30406. * @cfg {Number} [radius=0] The degree of rounding for rounded bars.
  30407. */
  30408. radius: 'number',
  30409. /**
  30410. * @cfg {Number} [inGroupGapWidth=3] The gap between grouped bars.
  30411. */
  30412. inGroupGapWidth: 'number'
  30413. },
  30414. defaults: {
  30415. minBarWidth: 2,
  30416. maxBarWidth: 100,
  30417. minGapWidth: 5,
  30418. inGroupGapWidth: 3,
  30419. radius: 0
  30420. }
  30421. }
  30422. },
  30423. drawLabel: function(text, dataX, dataStartY, dataY, labelId) {
  30424. var me = this,
  30425. attr = me.attr,
  30426. label = me.getMarker('labels'),
  30427. labelTpl = label.getTemplate(),
  30428. labelCfg = me.labelCfg || (me.labelCfg = {}),
  30429. surfaceMatrix = me.surfaceMatrix,
  30430. labelOverflowPadding = attr.labelOverflowPadding,
  30431. labelDisplay = labelTpl.attr.display,
  30432. labelOrientation = labelTpl.attr.orientation,
  30433. isVerticalText = (labelOrientation === 'horizontal' && attr.flipXY) || (labelOrientation === 'vertical' && !attr.flipXY) || !labelOrientation,
  30434. calloutLine = labelTpl.getCalloutLine(),
  30435. labelY, halfText, labelBBox, calloutLineLength, changes, hasPendingChanges, params;
  30436. // The coordinates below (data point converted to surface coordinates)
  30437. // are just for the renderer to give it a notion of where the label will be positioned.
  30438. // The actual position of the label will be different
  30439. // (unless the renderer returns x/y coordinates in the changes object)
  30440. // and depend on several things including the size of the text,
  30441. // which has to be measured after the renderer call,
  30442. // since text can be modified by the renderer.
  30443. labelCfg.x = surfaceMatrix.x(dataX, dataY);
  30444. labelCfg.y = surfaceMatrix.y(dataX, dataY);
  30445. if (calloutLine) {
  30446. calloutLineLength = calloutLine.length;
  30447. } else {
  30448. calloutLineLength = 0;
  30449. }
  30450. // Set defaults
  30451. if (!attr.flipXY) {
  30452. labelCfg.rotationRads = -Math.PI * 0.5;
  30453. } else {
  30454. labelCfg.rotationRads = 0;
  30455. }
  30456. labelCfg.calloutVertical = !attr.flipXY;
  30457. // Check if we have a specific orientation specified, if so, set
  30458. // the appropriate values.
  30459. switch (labelOrientation) {
  30460. case 'horizontal':
  30461. labelCfg.rotationRads = 0;
  30462. labelCfg.calloutVertical = false;
  30463. break;
  30464. case 'vertical':
  30465. labelCfg.rotationRads = -Math.PI * 0.5;
  30466. labelCfg.calloutVertical = true;
  30467. break;
  30468. }
  30469. labelCfg.text = text;
  30470. if (labelTpl.attr.renderer) {
  30471. // The label instance won't exist on first render before the renderer is called,
  30472. // it's only created later by `me.putMarker` after the renderer call. To make
  30473. // sure the renderer always can access the label instance, we make this check here.
  30474. if (!label.get(labelId)) {
  30475. label.putMarkerFor('labels', {}, labelId);
  30476. }
  30477. params = [
  30478. text,
  30479. label,
  30480. labelCfg,
  30481. {
  30482. store: me.getStore()
  30483. },
  30484. labelId
  30485. ];
  30486. changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
  30487. if (typeof changes === 'string') {
  30488. labelCfg.text = changes;
  30489. } else if (typeof changes === 'object') {
  30490. if ('text' in changes) {
  30491. labelCfg.text = changes.text;
  30492. }
  30493. hasPendingChanges = true;
  30494. }
  30495. }
  30496. labelBBox = me.getMarkerBBox('labels', labelId, true);
  30497. if (!labelBBox) {
  30498. me.putMarker('labels', labelCfg, labelId);
  30499. labelBBox = me.getMarkerBBox('labels', labelId, true);
  30500. }
  30501. if (calloutLineLength > 0) {
  30502. halfText = calloutLineLength;
  30503. } else if (calloutLineLength === 0) {
  30504. halfText = (isVerticalText ? labelBBox.width : labelBBox.height) / 2;
  30505. } else {
  30506. halfText = (isVerticalText ? labelBBox.width : labelBBox.height) / 2 + labelOverflowPadding;
  30507. }
  30508. if (dataStartY > dataY) {
  30509. halfText = -halfText;
  30510. }
  30511. if (isVerticalText) {
  30512. labelY = (labelDisplay === 'insideStart') ? dataStartY + halfText : dataY - halfText;
  30513. } else {
  30514. labelY = (labelDisplay === 'insideStart') ? dataStartY + labelOverflowPadding * 2 : dataY - labelOverflowPadding * 2;
  30515. }
  30516. labelCfg.x = surfaceMatrix.x(dataX, labelY);
  30517. labelCfg.y = surfaceMatrix.y(dataX, labelY);
  30518. labelY = (labelDisplay === 'insideStart') ? dataStartY : dataY;
  30519. labelCfg.calloutStartX = surfaceMatrix.x(dataX, labelY);
  30520. labelCfg.calloutStartY = surfaceMatrix.y(dataX, labelY);
  30521. labelY = (labelDisplay === 'insideStart') ? dataStartY - halfText : dataY + halfText;
  30522. labelCfg.calloutPlaceX = surfaceMatrix.x(dataX, labelY);
  30523. labelCfg.calloutPlaceY = surfaceMatrix.y(dataX, labelY);
  30524. labelCfg.calloutColor = (calloutLine && calloutLine.color) || me.attr.fillStyle;
  30525. if (calloutLine) {
  30526. if (calloutLine.width) {
  30527. labelCfg.calloutWidth = calloutLine.width;
  30528. }
  30529. } else {
  30530. labelCfg.calloutColor = 'none';
  30531. }
  30532. if (dataStartY > dataY) {
  30533. halfText = -halfText;
  30534. }
  30535. if (Math.abs(dataY - dataStartY) <= halfText * 2 || labelDisplay === 'outside') {
  30536. labelCfg.callout = 1;
  30537. } else {
  30538. labelCfg.callout = 0;
  30539. }
  30540. if (hasPendingChanges) {
  30541. Ext.apply(labelCfg, changes);
  30542. }
  30543. me.putMarker('labels', labelCfg, labelId);
  30544. },
  30545. drawBar: function(ctx, surface, rect, left, top, right, bottom, index) {
  30546. var me = this,
  30547. itemCfg = {},
  30548. renderer = me.attr.renderer,
  30549. changes;
  30550. itemCfg.x = left;
  30551. itemCfg.y = top;
  30552. itemCfg.width = right - left;
  30553. itemCfg.height = bottom - top;
  30554. itemCfg.radius = me.attr.radius;
  30555. if (renderer) {
  30556. changes = Ext.callback(renderer, null, [
  30557. me,
  30558. itemCfg,
  30559. {
  30560. store: me.getStore()
  30561. },
  30562. index
  30563. ], 0, me.getSeries());
  30564. Ext.apply(itemCfg, changes);
  30565. }
  30566. me.putMarker('items', itemCfg, index, !renderer);
  30567. },
  30568. renderClipped: function(surface, ctx, dataClipRect) {
  30569. if (this.cleanRedraw) {
  30570. return;
  30571. }
  30572. var me = this,
  30573. attr = me.attr,
  30574. dataX = attr.dataX,
  30575. dataY = attr.dataY,
  30576. dataText = attr.labels,
  30577. dataStartY = attr.dataStartY,
  30578. groupCount = attr.groupCount,
  30579. groupOffset = attr.groupOffset - (groupCount - 1) * 0.5,
  30580. inGroupGapWidth = attr.inGroupGapWidth,
  30581. lineWidth = ctx.lineWidth,
  30582. matrix = attr.matrix,
  30583. xx = matrix.elements[0],
  30584. yy = matrix.elements[3],
  30585. dx = matrix.elements[4],
  30586. dy = surface.roundPixel(matrix.elements[5]) - 1,
  30587. maxBarWidth = Math.abs(xx) - attr.minGapWidth,
  30588. minBarWidth = (Math.min(maxBarWidth, attr.maxBarWidth) - inGroupGapWidth * (groupCount - 1)) / groupCount,
  30589. barWidth = surface.roundPixel(Math.max(attr.minBarWidth, minBarWidth)),
  30590. surfaceMatrix = me.surfaceMatrix,
  30591. left, right, bottom, top, i, center,
  30592. halfLineWidth = 0.5 * attr.lineWidth,
  30593. // Finding min/max so that bars render properly in both LTR and RTL modes.
  30594. min = Math.min(dataClipRect[0], dataClipRect[2]),
  30595. max = Math.max(dataClipRect[0], dataClipRect[2]),
  30596. start = Math.max(0, Math.floor(min)),
  30597. end = Math.min(dataX.length - 1, Math.ceil(max)),
  30598. isDrawLabels = dataText && me.getMarker('labels'),
  30599. yLow, yHi;
  30600. // The scaling (xx) and translation (dx) here will already be such that the midpoints
  30601. // of the first and last bars are not at the surface edges (which would mean that
  30602. // bars are half-clipped), but padded, so that those bars are fully visible (assuming no pan/zoom).
  30603. for (i = start; i <= end; i++) {
  30604. yLow = dataStartY ? dataStartY[i] : 0;
  30605. yHi = dataY[i];
  30606. center = dataX[i] * xx + dx + groupOffset * (barWidth + inGroupGapWidth);
  30607. left = surface.roundPixel(center - barWidth / 2) + halfLineWidth;
  30608. top = surface.roundPixel(yHi * yy + dy + lineWidth);
  30609. right = surface.roundPixel(center + barWidth / 2) - halfLineWidth;
  30610. bottom = surface.roundPixel(yLow * yy + dy + lineWidth);
  30611. me.drawBar(ctx, surface, dataClipRect, left, top - halfLineWidth, right, bottom - halfLineWidth, i);
  30612. // We want 0 values to be passed to the renderer
  30613. if (isDrawLabels && dataText[i] != null) {
  30614. me.drawLabel(dataText[i], center, bottom, top, i);
  30615. }
  30616. me.putMarker('markers', {
  30617. translationX: surfaceMatrix.x(center, top),
  30618. translationY: surfaceMatrix.y(center, top)
  30619. }, i, true);
  30620. }
  30621. }
  30622. });
  30623. /**
  30624. * @class Ext.chart.series.Bar
  30625. * @extends Ext.chart.series.StackedCartesian
  30626. *
  30627. * Creates a Bar or Column Chart (depending on the value of the
  30628. * {@link Ext.chart.CartesianChart#flipXY flipXY} config).
  30629. *
  30630. * Note: 'bar' series is meant to be used with the
  30631. * {@link Ext.chart.axis.Category 'category'} axis as its x-axis.
  30632. *
  30633. * @example
  30634. * Ext.create({
  30635. * xtype: 'cartesian',
  30636. * renderTo: document.body,
  30637. * width: 600,
  30638. * height: 400,
  30639. * store: {
  30640. * fields: ['name', 'value'],
  30641. * data: [{
  30642. * name: 'metric one',
  30643. * value: 10
  30644. * }, {
  30645. * name: 'metric two',
  30646. * value: 7
  30647. * }, {
  30648. * name: 'metric three',
  30649. * value: 5
  30650. * }, {
  30651. * name: 'metric four',
  30652. * value: 2
  30653. * }, {
  30654. * name: 'metric five',
  30655. * value: 27
  30656. * }]
  30657. * },
  30658. * axes: [{
  30659. * type: 'numeric',
  30660. * position: 'left',
  30661. * title: {
  30662. * text: 'Sample Values',
  30663. * fontSize: 15
  30664. * },
  30665. * fields: 'value'
  30666. * }, {
  30667. * type: 'category',
  30668. * position: 'bottom',
  30669. * title: {
  30670. * text: 'Sample Values',
  30671. * fontSize: 15
  30672. * },
  30673. * fields: 'name'
  30674. * }],
  30675. * series: {
  30676. * type: 'bar',
  30677. * subStyle: {
  30678. * fill: ['#388FAD'],
  30679. * stroke: '#1F6D91'
  30680. * },
  30681. * xField: 'name',
  30682. * yField: 'value'
  30683. * }
  30684. * });
  30685. */
  30686. Ext.define('Ext.chart.series.Bar', {
  30687. extend: 'Ext.chart.series.StackedCartesian',
  30688. alias: 'series.bar',
  30689. type: 'bar',
  30690. seriesType: 'barSeries',
  30691. isBar: true,
  30692. requires: [
  30693. 'Ext.chart.series.sprite.Bar',
  30694. 'Ext.draw.sprite.Rect'
  30695. ],
  30696. config: {
  30697. /**
  30698. * @private
  30699. * @cfg {Object} itemInstancing Sprite template used for series.
  30700. */
  30701. itemInstancing: {
  30702. type: 'rect',
  30703. animation: {
  30704. customDurations: {
  30705. x: 0,
  30706. y: 0,
  30707. width: 0,
  30708. height: 0,
  30709. radius: 0
  30710. }
  30711. }
  30712. }
  30713. },
  30714. getItemForPoint: function(x, y) {
  30715. if (this.getSprites().length) {
  30716. var chart = this.getChart(),
  30717. padding = chart.getInnerPadding(),
  30718. isRtl = chart.getInherited().rtl;
  30719. // Convert the coordinates because the "items" sprites that draw
  30720. // the bars ignore the chart's InnerPadding.
  30721. arguments[0] = x + (isRtl ? padding.right : -padding.left);
  30722. arguments[1] = y + padding.bottom;
  30723. return this.callParent(arguments);
  30724. }
  30725. },
  30726. updateXAxis: function(xAxis) {
  30727. //<debug>
  30728. if (!this.is3D && !xAxis.isCategory) {
  30729. Ext.raise("'bar' series should be used with a 'category' axis. Please refer to the bar series docs.");
  30730. }
  30731. //</debug>
  30732. xAxis.setExpandRangeBy(0.5);
  30733. this.callParent(arguments);
  30734. },
  30735. updateHidden: function(hidden) {
  30736. this.callParent(arguments);
  30737. this.updateStacked();
  30738. },
  30739. updateStacked: function(stacked) {
  30740. var me = this,
  30741. attributes = {},
  30742. sprites = me.getSprites(),
  30743. spriteCount = sprites.length,
  30744. visibleSprites = [],
  30745. visibleSpriteCount, i;
  30746. for (i = 0; i < spriteCount; i++) {
  30747. if (!sprites[i].attr.hidden) {
  30748. visibleSprites.push(sprites[i]);
  30749. }
  30750. }
  30751. visibleSpriteCount = visibleSprites.length;
  30752. if (me.getStacked()) {
  30753. attributes.groupCount = 1;
  30754. attributes.groupOffset = 0;
  30755. for (i = 0; i < visibleSpriteCount; i++) {
  30756. visibleSprites[i].setAttributes(attributes);
  30757. }
  30758. } else {
  30759. attributes.groupCount = visibleSpriteCount;
  30760. for (i = 0; i < visibleSpriteCount; i++) {
  30761. attributes.groupOffset = i;
  30762. visibleSprites[i].setAttributes(attributes);
  30763. }
  30764. }
  30765. me.callParent(arguments);
  30766. }
  30767. });
  30768. /**
  30769. * @class Ext.chart.series.sprite.Bar3D
  30770. * @extends Ext.chart.series.sprite.Bar
  30771. *
  30772. * Draws a sprite used in {@link Ext.chart.series.Bar3D} series.
  30773. */
  30774. Ext.define('Ext.chart.series.sprite.Bar3D', {
  30775. extend: 'Ext.chart.series.sprite.Bar',
  30776. alias: 'sprite.bar3dSeries',
  30777. requires: [
  30778. 'Ext.draw.gradient.Linear'
  30779. ],
  30780. inheritableStatics: {
  30781. def: {
  30782. processors: {
  30783. depthWidthRatio: 'number',
  30784. /**
  30785. * @cfg {Number} [saturationFactor=1]
  30786. * The factor applied to the saturation of the bars.
  30787. */
  30788. saturationFactor: 'number',
  30789. /**
  30790. * @cfg {Number} [brightnessFactor=1]
  30791. * The factor applied to the brightness of the bars.
  30792. */
  30793. brightnessFactor: 'number',
  30794. /**
  30795. * @cfg {Number} [colorSpread=1]
  30796. * An attribute used to control how flat the bar gradient looks.
  30797. * A value of 0 essentially means no gradient (flat color).
  30798. */
  30799. colorSpread: 'number'
  30800. },
  30801. defaults: {
  30802. depthWidthRatio: 1 / 3,
  30803. saturationFactor: 1,
  30804. brightnessFactor: 1,
  30805. colorSpread: 1,
  30806. transformFillStroke: true
  30807. },
  30808. triggers: {
  30809. groupCount: 'panzoom'
  30810. },
  30811. updaters: {
  30812. panzoom: function(attr) {
  30813. var me = this,
  30814. dx = attr.visibleMaxX - attr.visibleMinX,
  30815. dy = attr.visibleMaxY - attr.visibleMinY,
  30816. innerWidth = attr.flipXY ? attr.innerHeight : attr.innerWidth,
  30817. innerHeight = !attr.flipXY ? attr.innerHeight : attr.innerWidth,
  30818. surface = me.getSurface(),
  30819. isRtl = surface ? surface.getInherited().rtl : false;
  30820. if (isRtl && !attr.flipXY) {
  30821. attr.translationX = innerWidth + attr.visibleMinX * innerWidth / dx;
  30822. } else {
  30823. attr.translationX = -attr.visibleMinX * innerWidth / dx;
  30824. }
  30825. attr.translationY = -attr.visibleMinY * (innerHeight - me.depth) / dy;
  30826. attr.scalingX = (isRtl && !attr.flipXY ? -1 : 1) * innerWidth / dx;
  30827. attr.scalingY = (innerHeight - me.depth) / dy;
  30828. attr.scalingCenterX = 0;
  30829. attr.scalingCenterY = 0;
  30830. me.applyTransformations(true);
  30831. }
  30832. }
  30833. }
  30834. },
  30835. config: {
  30836. showStroke: false
  30837. },
  30838. depth: 0,
  30839. drawBar: function(ctx, surface, clip, left, top, right, bottom, index) {
  30840. var me = this,
  30841. attr = me.attr,
  30842. itemCfg = {},
  30843. renderer = attr.renderer,
  30844. changes, depth, series, params;
  30845. itemCfg.x = (left + right) * 0.5;
  30846. itemCfg.y = top;
  30847. itemCfg.width = (right - left) * 0.75;
  30848. itemCfg.height = bottom - top;
  30849. itemCfg.depth = depth = itemCfg.width * attr.depthWidthRatio;
  30850. itemCfg.orientation = attr.flipXY ? 'horizontal' : 'vertical';
  30851. itemCfg.saturationFactor = attr.saturationFactor;
  30852. itemCfg.brightnessFactor = attr.brightnessFactor;
  30853. itemCfg.colorSpread = attr.colorSpread;
  30854. if (depth !== me.depth) {
  30855. me.depth = depth;
  30856. series = me.getSeries();
  30857. series.fireEvent('depthchange', series, depth);
  30858. }
  30859. if (renderer) {
  30860. params = [
  30861. me,
  30862. itemCfg,
  30863. {
  30864. store: me.getStore()
  30865. },
  30866. index
  30867. ];
  30868. changes = Ext.callback(renderer, null, params, 0, me.getSeries());
  30869. Ext.apply(itemCfg, changes);
  30870. }
  30871. me.putMarker('items', itemCfg, index, !renderer);
  30872. }
  30873. });
  30874. /**
  30875. * @class Ext.chart.sprite.Bar3D
  30876. * @extends Ext.draw.sprite.Sprite
  30877. *
  30878. * A sprite that represents a 3D bar or column.
  30879. * Used as an item template by the {@link Ext.chart.series.sprite.Bar3D} marker holder.
  30880. *
  30881. */
  30882. Ext.define('Ext.chart.sprite.Bar3D', {
  30883. extend: 'Ext.draw.sprite.Sprite',
  30884. alias: 'sprite.bar3d',
  30885. type: 'bar3d',
  30886. inheritableStatics: {
  30887. def: {
  30888. processors: {
  30889. /**
  30890. * @cfg {Number} [x=0]
  30891. * The position of the sprite on the x-axis.
  30892. * Corresponds to the center of the front face of the box.
  30893. */
  30894. x: 'number',
  30895. /**
  30896. * @cfg {Number} [y=0]
  30897. * The position of the sprite on the y-axis.
  30898. * Corresponds to the top of the front face of the box.
  30899. */
  30900. y: 'number',
  30901. /**
  30902. * @cfg {Number} [width=8] The width of the box.
  30903. */
  30904. width: 'number',
  30905. /**
  30906. * @cfg {Number} [height=8] The height of the box.
  30907. */
  30908. height: 'number',
  30909. /**
  30910. * @cfg {Number} [depth=8] The depth of the box.
  30911. */
  30912. depth: 'number',
  30913. /**
  30914. * @cfg {String} [orientation='vertical'] The orientation of the box.
  30915. */
  30916. orientation: 'enums(vertical,horizontal)',
  30917. /**
  30918. * @cfg {Boolean} [showStroke=false]
  30919. * Whether to render the stroke or not.
  30920. */
  30921. showStroke: 'bool',
  30922. /**
  30923. * @cfg {Number} [saturationFactor=1]
  30924. * The factor applied to the saturation of the box.
  30925. */
  30926. saturationFactor: 'number',
  30927. /**
  30928. * @cfg {Number} [brightnessFactor=1]
  30929. * The factor applied to the brightness of the box.
  30930. */
  30931. brightnessFactor: 'number',
  30932. /**
  30933. * @cfg {Number} [colorSpread=1]
  30934. * An attribute used to control how flat the bar gradient looks.
  30935. * A value of 0 essentially means no gradient (flat color).
  30936. */
  30937. colorSpread: 'number'
  30938. },
  30939. triggers: {
  30940. x: 'bbox',
  30941. y: 'bbox',
  30942. width: 'bbox',
  30943. height: 'bbox',
  30944. depth: 'bbox',
  30945. orientation: 'bbox'
  30946. },
  30947. defaults: {
  30948. x: 0,
  30949. y: 0,
  30950. width: 8,
  30951. height: 8,
  30952. depth: 8,
  30953. orientation: 'vertical',
  30954. showStroke: false,
  30955. saturationFactor: 1,
  30956. brightnessFactor: 1,
  30957. colorSpread: 1,
  30958. lineJoin: 'bevel'
  30959. }
  30960. }
  30961. },
  30962. constructor: function(config) {
  30963. this.callParent([
  30964. config
  30965. ]);
  30966. this.topGradient = new Ext.draw.gradient.Linear({});
  30967. this.rightGradient = new Ext.draw.gradient.Linear({});
  30968. this.frontGradient = new Ext.draw.gradient.Linear({});
  30969. },
  30970. updatePlainBBox: function(plain) {
  30971. var attr = this.attr,
  30972. x = attr.x,
  30973. y = attr.y,
  30974. width = attr.width,
  30975. height = attr.height,
  30976. depth = attr.depth;
  30977. plain.x = x - width * 0.5;
  30978. plain.width = width + depth;
  30979. if (height > 0) {
  30980. plain.y = y;
  30981. plain.height = height + depth;
  30982. } else {
  30983. plain.y = y + depth;
  30984. plain.height = height - depth;
  30985. }
  30986. },
  30987. render: function(surface, ctx) {
  30988. var me = this,
  30989. attr = me.attr,
  30990. center = attr.x,
  30991. top = attr.y,
  30992. bottom = top + attr.height,
  30993. isNegative = top < bottom,
  30994. halfWidth = attr.width * 0.5,
  30995. depth = attr.depth,
  30996. isHorizontal = attr.orientation === 'horizontal',
  30997. isTransparent = attr.globalAlpha < 1,
  30998. fillStyle = attr.fillStyle,
  30999. color = Ext.util.Color.create(fillStyle.isGradient ? fillStyle.getStops()[0].color : fillStyle),
  31000. saturationFactor = attr.saturationFactor,
  31001. brightnessFactor = attr.brightnessFactor,
  31002. colorSpread = attr.colorSpread,
  31003. hsv = color.getHSV(),
  31004. bbox = {},
  31005. roundX, roundY, temp;
  31006. if (!attr.showStroke) {
  31007. ctx.strokeStyle = Ext.util.Color.RGBA_NONE;
  31008. }
  31009. if (isNegative) {
  31010. temp = top;
  31011. top = bottom;
  31012. bottom = temp;
  31013. }
  31014. // Refresh gradients based on sprite's fillStyle and other attributes.
  31015. me.topGradient.setDegrees(isHorizontal ? 0 : 80);
  31016. me.topGradient.setStops([
  31017. {
  31018. offset: 0,
  31019. color: Ext.util.Color.fromHSV(hsv[0], Ext.Number.constrain(hsv[1] * saturationFactor, 0, 1), Ext.Number.constrain((0.5 + colorSpread * 0.1) * brightnessFactor, 0, 1))
  31020. },
  31021. {
  31022. offset: 1,
  31023. color: Ext.util.Color.fromHSV(hsv[0], Ext.Number.constrain(hsv[1] * saturationFactor, 0, 1), Ext.Number.constrain((0.5 - colorSpread * 0.11) * brightnessFactor, 0, 1))
  31024. }
  31025. ]);
  31026. me.rightGradient.setDegrees(isHorizontal ? 45 : 90);
  31027. me.rightGradient.setStops([
  31028. {
  31029. offset: 0,
  31030. color: Ext.util.Color.fromHSV(hsv[0], Ext.Number.constrain(hsv[1] * saturationFactor, 0, 1), Ext.Number.constrain((0.5 - colorSpread * 0.14) * brightnessFactor, 0, 1))
  31031. },
  31032. {
  31033. offset: 1,
  31034. color: Ext.util.Color.fromHSV(hsv[0], Ext.Number.constrain(hsv[1] * (1 + colorSpread * 0.4) * saturationFactor, 0, 1), Ext.Number.constrain((0.5 - colorSpread * 0.32) * brightnessFactor, 0, 1))
  31035. }
  31036. ]);
  31037. if (isHorizontal) {
  31038. me.frontGradient.setDegrees(0);
  31039. } else // 0° angle looks like 90° angle because the chart is flipped
  31040. {
  31041. me.frontGradient.setRadians(Math.atan2(top - bottom, halfWidth * 2));
  31042. }
  31043. me.frontGradient.setStops([
  31044. {
  31045. offset: 0,
  31046. color: Ext.util.Color.fromHSV(hsv[0], Ext.Number.constrain(hsv[1] * (1 - colorSpread * 0.1) * saturationFactor, 0, 1), Ext.Number.constrain((0.5 + colorSpread * 0.1) * brightnessFactor, 0, 1))
  31047. },
  31048. {
  31049. offset: 1,
  31050. color: Ext.util.Color.fromHSV(hsv[0], Ext.Number.constrain(hsv[1] * (1 + colorSpread * 0.1) * saturationFactor, 0, 1), Ext.Number.constrain((0.5 - colorSpread * 0.23) * brightnessFactor, 0, 1))
  31051. }
  31052. ]);
  31053. if (isTransparent || isNegative) {
  31054. // Bottom side.
  31055. ctx.beginPath();
  31056. ctx.moveTo(center - halfWidth, bottom);
  31057. ctx.lineTo(center - halfWidth + depth, bottom + depth);
  31058. ctx.lineTo(center + halfWidth + depth, bottom + depth);
  31059. ctx.lineTo(center + halfWidth, bottom);
  31060. ctx.closePath();
  31061. bbox.x = center - halfWidth;
  31062. bbox.y = top;
  31063. bbox.width = halfWidth + depth;
  31064. bbox.height = depth;
  31065. ctx.fillStyle = (isHorizontal ? me.rightGradient : me.topGradient).generateGradient(ctx, bbox);
  31066. ctx.fillStroke(attr);
  31067. }
  31068. if (isTransparent) {
  31069. // Left side.
  31070. ctx.beginPath();
  31071. ctx.moveTo(center - halfWidth, top);
  31072. ctx.lineTo(center - halfWidth + depth, top + depth);
  31073. ctx.lineTo(center - halfWidth + depth, bottom + depth);
  31074. ctx.lineTo(center - halfWidth, bottom);
  31075. ctx.closePath();
  31076. bbox.x = center + halfWidth;
  31077. bbox.y = bottom;
  31078. bbox.width = depth;
  31079. bbox.height = top + depth - bottom;
  31080. ctx.fillStyle = (isHorizontal ? me.topGradient : me.rightGradient).generateGradient(ctx, bbox);
  31081. ctx.fillStroke(attr);
  31082. }
  31083. // Top side.
  31084. roundY = surface.roundPixel(top);
  31085. ctx.beginPath();
  31086. ctx.moveTo(center - halfWidth, roundY);
  31087. ctx.lineTo(center - halfWidth + depth, top + depth);
  31088. ctx.lineTo(center + halfWidth + depth, top + depth);
  31089. ctx.lineTo(center + halfWidth, roundY);
  31090. ctx.closePath();
  31091. bbox.x = center - halfWidth;
  31092. bbox.y = top;
  31093. bbox.width = halfWidth + depth;
  31094. bbox.height = depth;
  31095. ctx.fillStyle = (isHorizontal ? me.rightGradient : me.topGradient).generateGradient(ctx, bbox);
  31096. ctx.fillStroke(attr);
  31097. // Right side.
  31098. roundX = surface.roundPixel(center + halfWidth);
  31099. ctx.beginPath();
  31100. ctx.moveTo(roundX, surface.roundPixel(top));
  31101. ctx.lineTo(center + halfWidth + depth, top + depth);
  31102. ctx.lineTo(center + halfWidth + depth, bottom + depth);
  31103. ctx.lineTo(roundX, bottom);
  31104. ctx.closePath();
  31105. bbox.x = center + halfWidth;
  31106. bbox.y = bottom;
  31107. bbox.width = depth;
  31108. bbox.height = top + depth - bottom;
  31109. ctx.fillStyle = (isHorizontal ? me.topGradient : me.rightGradient).generateGradient(ctx, bbox);
  31110. ctx.fillStroke(attr);
  31111. // Front side.
  31112. roundX = surface.roundPixel(center + halfWidth);
  31113. roundY = surface.roundPixel(top);
  31114. ctx.beginPath();
  31115. ctx.moveTo(center - halfWidth, bottom);
  31116. ctx.lineTo(center - halfWidth, roundY);
  31117. ctx.lineTo(roundX, roundY);
  31118. ctx.lineTo(roundX, bottom);
  31119. ctx.closePath();
  31120. bbox.x = center - halfWidth;
  31121. bbox.y = bottom;
  31122. bbox.width = halfWidth * 2;
  31123. bbox.height = top - bottom;
  31124. ctx.fillStyle = me.frontGradient.generateGradient(ctx, bbox);
  31125. ctx.fillStroke(attr);
  31126. }
  31127. });
  31128. /**
  31129. * @class Ext.chart.series.Bar3D
  31130. * @extends Ext.chart.series.Bar
  31131. *
  31132. * Creates a 3D Bar or 3D Column Chart (depending on the value of the
  31133. * {@link Ext.chart.CartesianChart#flipXY flipXY} config).
  31134. *
  31135. * Note: 'bar3d' series is meant to be used with the
  31136. * {@link Ext.chart.axis.Category 'category3d'} axis as its x-axis.
  31137. *
  31138. * @example
  31139. * Ext.create({
  31140. * xtype: 'cartesian',
  31141. * renderTo: Ext.getBody(),
  31142. * width: 600,
  31143. * height: 400,
  31144. * innerPadding: '0 10 0 10',
  31145. * store: {
  31146. * fields: ['name', 'apples', 'oranges'],
  31147. * data: [{
  31148. * name: 'Eric',
  31149. * apples: 10,
  31150. * oranges: 3
  31151. * }, {
  31152. * name: 'Mary',
  31153. * apples: 7,
  31154. * oranges: 2
  31155. * }, {
  31156. * name: 'John',
  31157. * apples: 5,
  31158. * oranges: 2
  31159. * }, {
  31160. * name: 'Bob',
  31161. * apples: 2,
  31162. * oranges: 3
  31163. * }, {
  31164. * name: 'Joe',
  31165. * apples: 19,
  31166. * oranges: 1
  31167. * }, {
  31168. * name: 'Macy',
  31169. * apples: 13,
  31170. * oranges: 4
  31171. * }]
  31172. * },
  31173. * axes: [{
  31174. * type: 'numeric3d',
  31175. * position: 'left',
  31176. * fields: ['apples', 'oranges'],
  31177. * title: {
  31178. * text: 'Inventory',
  31179. * fontSize: 15
  31180. * },
  31181. * grid: {
  31182. * odd: {
  31183. * fillStyle: 'rgba(255, 255, 255, 0.06)'
  31184. * },
  31185. * even: {
  31186. * fillStyle: 'rgba(0, 0, 0, 0.03)'
  31187. * }
  31188. * }
  31189. * }, {
  31190. * type: 'category3d',
  31191. * position: 'bottom',
  31192. * title: {
  31193. * text: 'People',
  31194. * fontSize: 15
  31195. * },
  31196. * fields: 'name'
  31197. * }],
  31198. * series: {
  31199. * type: 'bar3d',
  31200. * xField: 'name',
  31201. * yField: ['apples', 'oranges']
  31202. * }
  31203. * });
  31204. */
  31205. Ext.define('Ext.chart.series.Bar3D', {
  31206. extend: 'Ext.chart.series.Bar',
  31207. requires: [
  31208. 'Ext.chart.series.sprite.Bar3D',
  31209. 'Ext.chart.sprite.Bar3D'
  31210. ],
  31211. alias: 'series.bar3d',
  31212. type: 'bar3d',
  31213. seriesType: 'bar3dSeries',
  31214. is3D: true,
  31215. config: {
  31216. itemInstancing: {
  31217. type: 'bar3d',
  31218. animation: {
  31219. customDurations: {
  31220. x: 0,
  31221. y: 0,
  31222. width: 0,
  31223. height: 0,
  31224. depth: 0
  31225. }
  31226. }
  31227. },
  31228. highlightCfg: {
  31229. opacity: 0.8
  31230. }
  31231. },
  31232. /**
  31233. * For 3D series, it's quite the opposite. It would be extremely odd,
  31234. * if top segments were rendered as if they were under the bottom ones.
  31235. */
  31236. reversedSpriteZOrder: false,
  31237. updateXAxis: function(xAxis, oldXAxis) {
  31238. //<debug>
  31239. if (xAxis.type !== 'category3d') {
  31240. Ext.raise("'bar3d' series should be used with a 'category3d' axis." + " Please refer to the 'bar3d' series docs.");
  31241. }
  31242. //</debug>
  31243. this.callParent([
  31244. xAxis,
  31245. oldXAxis
  31246. ]);
  31247. },
  31248. getDepth: function() {
  31249. var sprite = this.getSprites()[0];
  31250. return sprite ? (sprite.depth || 0) : 0;
  31251. }
  31252. });
  31253. /**
  31254. * BoxPlot series sprite that manages {@link Ext.chart.sprite.BoxPlot} instances.
  31255. */
  31256. Ext.define('Ext.chart.series.sprite.BoxPlot', {
  31257. alias: 'sprite.boxplotSeries',
  31258. extend: 'Ext.chart.series.sprite.Cartesian',
  31259. inheritableStatics: {
  31260. def: {
  31261. processors: {
  31262. /**
  31263. * @cfg {Number[]} [dataLow=null] Array of coordinated minimum values.
  31264. */
  31265. dataLow: 'data',
  31266. /**
  31267. * @cfg {Number[]} [dataQ1=null] Array of coordinated 1-st quartile values.
  31268. */
  31269. dataQ1: 'data',
  31270. /**
  31271. * @cfg {Number[]} [dataQ3=null] Array of coordinated 3-rd quartile values.
  31272. */
  31273. dataQ3: 'data',
  31274. /**
  31275. * @cfg {Number[]} [dataHigh=null] Array of coordinated maximum values.
  31276. */
  31277. dataHigh: 'data',
  31278. /**
  31279. * @cfg {Number} [minBoxWidth=2] The minimum box width.
  31280. */
  31281. minBoxWidth: 'number',
  31282. /**
  31283. * @cfg {Number} [maxBoxWidth=20] The maximum box width.
  31284. */
  31285. maxBoxWidth: 'number',
  31286. /**
  31287. * @cfg {Number} [minGapWidth=5] The minimum gap between boxes.
  31288. */
  31289. minGapWidth: 'number'
  31290. },
  31291. aliases: {
  31292. /**
  31293. * The `dataMedian` attribute can be used to set the value of
  31294. * the `dataY` attribute. E.g.:
  31295. *
  31296. * sprite.setAttributes({
  31297. * dataMedian: [...]
  31298. * });
  31299. *
  31300. * To fetch the value of the attribute one has to use
  31301. *
  31302. * sprite.attr.dataY // array of coordinated median values
  31303. *
  31304. * and not
  31305. *
  31306. * sprite.attr.dataMedian // WRONG!
  31307. *
  31308. * `dataY` attribute is defined by the `Ext.chart.series.sprite.Series`.
  31309. *
  31310. * @cfg {Number[]} [dataMedian=null] Array of coordinated median values.
  31311. */
  31312. dataMedian: 'dataY'
  31313. },
  31314. defaults: {
  31315. minBoxWidth: 2,
  31316. maxBoxWidth: 40,
  31317. minGapWidth: 5
  31318. }
  31319. }
  31320. },
  31321. renderClipped: function(surface, ctx, dataClipRect) {
  31322. if (this.cleanRedraw) {
  31323. return;
  31324. }
  31325. var me = this,
  31326. attr = me.attr,
  31327. series = me.getSeries(),
  31328. renderer = attr.renderer,
  31329. rendererData = {
  31330. store: me.getStore()
  31331. },
  31332. itemCfg = {},
  31333. dataX = attr.dataX,
  31334. dataLow = attr.dataLow,
  31335. dataQ1 = attr.dataQ1,
  31336. dataMedian = attr.dataY,
  31337. dataQ3 = attr.dataQ3,
  31338. dataHigh = attr.dataHigh,
  31339. min = Math.min(dataClipRect[0], dataClipRect[2]),
  31340. max = Math.max(dataClipRect[0], dataClipRect[2]),
  31341. start = Math.max(0, Math.floor(min)),
  31342. end = Math.min(dataX.length - 1, Math.ceil(max)),
  31343. // surfaceMatrix = me.surfaceMatrix,
  31344. matrix = attr.matrix,
  31345. xx = matrix.elements[0],
  31346. // horizontal scaling can be < 0, if RTL
  31347. yy = matrix.elements[3],
  31348. dx = matrix.elements[4],
  31349. dy = matrix.elements[5],
  31350. // `xx` essentially represents the distance between data points in surface coordinates.
  31351. maxBoxWidth = Math.abs(xx) - attr.minGapWidth,
  31352. minBoxWidth = Math.min(maxBoxWidth, attr.maxBoxWidth),
  31353. boxWidth = Math.round(Math.max(attr.minBoxWidth, minBoxWidth)),
  31354. x, low, q1, median, q3, high, rendererParams, changes, i;
  31355. if (renderer) {
  31356. rendererParams = [
  31357. me,
  31358. itemCfg,
  31359. rendererData
  31360. ];
  31361. }
  31362. for (i = start; i <= end; i++) {
  31363. x = dataX[i] * xx + dx;
  31364. low = dataLow[i] * yy + dy;
  31365. q1 = dataQ1[i] * yy + dy;
  31366. median = dataMedian[i] * yy + dy;
  31367. q3 = dataQ3[i] * yy + dy;
  31368. high = dataHigh[i] * yy + dy;
  31369. // --- Draw Box ---
  31370. // Reuse 'itemCfg' object and 'rendererParams' arrays for better performance.
  31371. itemCfg.x = x;
  31372. itemCfg.low = low;
  31373. itemCfg.q1 = q1;
  31374. itemCfg.median = median;
  31375. itemCfg.q3 = q3;
  31376. itemCfg.high = high;
  31377. itemCfg.boxWidth = boxWidth;
  31378. if (renderer) {
  31379. rendererParams[3] = i;
  31380. changes = Ext.callback(renderer, null, rendererParams, 0, series);
  31381. Ext.apply(itemCfg, changes);
  31382. }
  31383. me.putMarker('items', itemCfg, i, !renderer);
  31384. }
  31385. }
  31386. });
  31387. /**
  31388. * A sprite that represents an individual box with whiskers.
  31389. * This sprite is meant to be managed by the {@link Ext.chart.series.sprite.BoxPlot}
  31390. * {@link Ext.chart.MarkerHolder MarkerHolder}, but can also be used independently:
  31391. *
  31392. * @example
  31393. * new Ext.draw.Container({
  31394. * width: 100,
  31395. * height: 100,
  31396. * renderTo: Ext.getBody(),
  31397. * sprites: [{
  31398. * type: 'boxplot',
  31399. * translationX: 50,
  31400. * translationY: 50
  31401. * }]
  31402. * });
  31403. *
  31404. * IMPORTANT: the attributes that represent y-coordinates are in screen coordinates,
  31405. * just like with any other sprite. For this particular sprite this means that, if 'low'
  31406. * and 'high' attributes are 10 and 90, then the minimium whisker is rendered at the top
  31407. * of a draw container {@link Ext.draw.Surface surface} at y = 10, and the maximum whisker
  31408. * is rendered at the bottom at y = 90. But because the series surface is flipped vertically
  31409. * in cartesian charts, this means that there minimum is rendered at the bottom and maximum
  31410. * at the top, just as one would expect.
  31411. */
  31412. Ext.define('Ext.chart.sprite.BoxPlot', {
  31413. extend: 'Ext.draw.sprite.Sprite',
  31414. alias: 'sprite.boxplot',
  31415. type: 'boxplot',
  31416. inheritableStatics: {
  31417. def: {
  31418. processors: {
  31419. /**
  31420. * @cfg {Number} [x=0] The coordinate of the horizontal center of a boxplot.
  31421. */
  31422. x: 'number',
  31423. /**
  31424. * @cfg {Number} [low=-20] The y-coordinate of the whisker that represents the minimum.
  31425. */
  31426. low: 'number',
  31427. /**
  31428. * @cfg {Number} [q1=-10] The y-coordinate of the box edge that represents the 1-st quartile.
  31429. */
  31430. q1: 'number',
  31431. /**
  31432. * @cfg {Number} [median=0] The y-coordinate of the line that represents the median.
  31433. */
  31434. median: 'number',
  31435. /**
  31436. * @cfg {Number} [q3=10] The y-coordinate of the box edge that represents the 3-rd quartile.
  31437. */
  31438. q3: 'number',
  31439. /**
  31440. * @cfg {Number} [high=20] The y-coordinate of the whisker that represents the maximum.
  31441. */
  31442. high: 'number',
  31443. /**
  31444. * @cfg {Number} [boxWidth=12] The width of the box in pixels.
  31445. */
  31446. boxWidth: 'number',
  31447. /**
  31448. * @cfg {Number} [whiskerWidth=0.5] The length of the lines at the ends of the whiskers, as a ratio of `boxWidth`.
  31449. */
  31450. whiskerWidth: 'number',
  31451. /**
  31452. * @cfg {Boolean} [crisp=true] Whether to snap the rendered lines to the pixel grid of not.
  31453. * Generally, it's best to have this set to `true` (which is the default) fox pixel perfect
  31454. * results (especially on non-HiDPI displays), but for boxplots with small `boxWidth`
  31455. * visible artifacts caused by pixel grid snapping may become noticeable, and setting this
  31456. * to `false` can be a remedy at the expense of clarity.
  31457. */
  31458. crisp: 'bool'
  31459. },
  31460. triggers: {
  31461. x: 'bbox',
  31462. low: 'bbox',
  31463. high: 'bbox',
  31464. boxWidth: 'bbox',
  31465. whiskerWidth: 'bbox',
  31466. crisp: 'bbox'
  31467. },
  31468. defaults: {
  31469. x: 0,
  31470. low: -20,
  31471. q1: -10,
  31472. median: 0,
  31473. q3: 10,
  31474. high: 20,
  31475. boxWidth: 12,
  31476. whiskerWidth: 0.5,
  31477. crisp: true,
  31478. fillStyle: '#ccc',
  31479. strokeStyle: '#000'
  31480. }
  31481. }
  31482. },
  31483. updatePlainBBox: function(plain) {
  31484. var me = this,
  31485. attr = me.attr,
  31486. halfLineWidth = attr.lineWidth / 2,
  31487. x = attr.x - attr.boxWidth / 2 - halfLineWidth,
  31488. y = attr.high - halfLineWidth,
  31489. width = attr.boxWidth + attr.lineWidth,
  31490. height = attr.low - attr.high + attr.lineWidth;
  31491. plain.x = x;
  31492. plain.y = y;
  31493. plain.width = width;
  31494. plain.height = height;
  31495. },
  31496. render: function(surface, ctx) {
  31497. var me = this,
  31498. attr = me.attr;
  31499. attr.matrix.toContext(ctx);
  31500. // enable sprite transformations
  31501. if (attr.crisp) {
  31502. me.crispRender(surface, ctx);
  31503. } else {
  31504. me.softRender(surface, ctx);
  31505. }
  31506. //<debug>
  31507. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  31508. if (debug) {
  31509. // This assumes no part of the sprite is rendered after this call.
  31510. // If it is, we need to re-apply transformations.
  31511. // But the bounding box should always be rendered as is, untransformed.
  31512. this.attr.inverseMatrix.toContext(ctx);
  31513. debug.bbox && this.renderBBox(surface, ctx);
  31514. }
  31515. },
  31516. //</debug>
  31517. /**
  31518. * @private
  31519. * Renders a single box with whiskers.
  31520. * Changes to this method have to be reflected in the {@link #crispRender} as well.
  31521. * @param surface
  31522. * @param ctx
  31523. */
  31524. softRender: function(surface, ctx) {
  31525. var me = this,
  31526. attr = me.attr,
  31527. x = attr.x,
  31528. low = attr.low,
  31529. q1 = attr.q1,
  31530. median = attr.median,
  31531. q3 = attr.q3,
  31532. high = attr.high,
  31533. halfBoxWidth = attr.boxWidth / 2,
  31534. halfWhiskerWidth = attr.boxWidth * attr.whiskerWidth / 2,
  31535. dash = ctx.getLineDash();
  31536. ctx.setLineDash([]);
  31537. // Only stem can be dashed.
  31538. // Box.
  31539. ctx.beginPath();
  31540. ctx.moveTo(x - halfBoxWidth, q3);
  31541. ctx.lineTo(x + halfBoxWidth, q3);
  31542. ctx.lineTo(x + halfBoxWidth, q1);
  31543. ctx.lineTo(x - halfBoxWidth, q1);
  31544. ctx.closePath();
  31545. ctx.fillStroke(attr, true);
  31546. // Stem.
  31547. ctx.setLineDash(dash);
  31548. ctx.beginPath();
  31549. ctx.moveTo(x, q3);
  31550. ctx.lineTo(x, high);
  31551. ctx.moveTo(x, q1);
  31552. ctx.lineTo(x, low);
  31553. ctx.stroke();
  31554. ctx.setLineDash([]);
  31555. // Whiskers.
  31556. ctx.beginPath();
  31557. ctx.moveTo(x - halfWhiskerWidth, low);
  31558. ctx.lineTo(x + halfWhiskerWidth, low);
  31559. ctx.moveTo(x - halfBoxWidth, median);
  31560. ctx.lineTo(x + halfBoxWidth, median);
  31561. ctx.moveTo(x - halfWhiskerWidth, high);
  31562. ctx.lineTo(x + halfWhiskerWidth, high);
  31563. ctx.stroke();
  31564. },
  31565. alignLine: function(x, lineWidth) {
  31566. lineWidth = lineWidth || this.attr.lineWidth;
  31567. x = Math.round(x);
  31568. if (lineWidth % 2 === 1) {
  31569. x -= 0.5;
  31570. }
  31571. return x;
  31572. },
  31573. /**
  31574. * @private
  31575. * Renders a pixel-perfect single box with whiskers by aligning to the pixel grid.
  31576. * Changes to this method have to be reflected in the {@link #softRender} as well.
  31577. *
  31578. * Note: crisp image is only guaranteed when `attr.lineWidth` is a whole number.
  31579. * @param surface
  31580. * @param ctx
  31581. */
  31582. crispRender: function(surface, ctx) {
  31583. var me = this,
  31584. attr = me.attr,
  31585. x = attr.x,
  31586. low = me.alignLine(attr.low),
  31587. q1 = me.alignLine(attr.q1),
  31588. median = me.alignLine(attr.median),
  31589. q3 = me.alignLine(attr.q3),
  31590. high = me.alignLine(attr.high),
  31591. halfBoxWidth = attr.boxWidth / 2,
  31592. halfWhiskerWidth = attr.boxWidth * attr.whiskerWidth / 2,
  31593. stemX = me.alignLine(x),
  31594. boxLeft = me.alignLine(x - halfBoxWidth),
  31595. boxRight = me.alignLine(x + halfBoxWidth),
  31596. whiskerLeft = stemX + Math.round(-halfWhiskerWidth),
  31597. whiskerRight = stemX + Math.round(halfWhiskerWidth),
  31598. dash = ctx.getLineDash();
  31599. ctx.setLineDash([]);
  31600. // Only stem can be dashed.
  31601. // Box.
  31602. ctx.beginPath();
  31603. ctx.moveTo(boxLeft, q3);
  31604. ctx.lineTo(boxRight, q3);
  31605. ctx.lineTo(boxRight, q1);
  31606. ctx.lineTo(boxLeft, q1);
  31607. ctx.closePath();
  31608. ctx.fillStroke(attr, true);
  31609. // Stem.
  31610. ctx.setLineDash(dash);
  31611. ctx.beginPath();
  31612. ctx.moveTo(stemX, q3);
  31613. ctx.lineTo(stemX, high);
  31614. ctx.moveTo(stemX, q1);
  31615. ctx.lineTo(stemX, low);
  31616. ctx.stroke();
  31617. ctx.setLineDash([]);
  31618. // Whiskers.
  31619. ctx.beginPath();
  31620. ctx.moveTo(whiskerLeft, low);
  31621. ctx.lineTo(whiskerRight, low);
  31622. ctx.moveTo(boxLeft, median);
  31623. ctx.lineTo(boxRight, median);
  31624. ctx.moveTo(whiskerLeft, high);
  31625. ctx.lineTo(whiskerRight, high);
  31626. ctx.stroke();
  31627. }
  31628. });
  31629. /**
  31630. * A box plot chart is a useful tool for visializing data distribution within datasets.
  31631. * For example, salary ranges for a set of occupations, or life expectancy for a set
  31632. * of countries. A single box with whiskers displays the following values for a dataset:
  31633. *
  31634. * * minimum
  31635. * * lower quartile (Q1)
  31636. * * median (Q2)
  31637. * * higher quartile (Q3)
  31638. * * maximum
  31639. *
  31640. * For example:
  31641. *
  31642. * @example
  31643. * Ext.create({
  31644. * xtype: 'cartesian',
  31645. * width: 400,
  31646. * height: 400,
  31647. * renderTo: Ext.getBody(),
  31648. * insetPadding: '20 20 10 10',
  31649. * store: {
  31650. * data: [{
  31651. * category: 'Engineer IV',
  31652. * low: 110, q1: 130, median: 175, q3: 200, high: 225
  31653. * }, {
  31654. * category: 'Market',
  31655. * low: 75, q1: 125, median: 210, q3: 230, high: 255
  31656. * }]
  31657. * },
  31658. * axes: [
  31659. * {
  31660. * type: 'numeric',
  31661. * position: 'left',
  31662. * renderer: function (axis, text) {
  31663. * return '$' + text + ' K'
  31664. * }
  31665. * },
  31666. * {
  31667. * type: 'category',
  31668. * position: 'bottom'
  31669. * }
  31670. * ],
  31671. * series: {
  31672. * type: 'boxplot',
  31673. * xField: 'category',
  31674. * style: {
  31675. * maxBoxWidth: 50,
  31676. * lineWidth: 2
  31677. * }
  31678. * }
  31679. * });
  31680. *
  31681. */
  31682. Ext.define('Ext.chart.series.BoxPlot', {
  31683. extend: 'Ext.chart.series.Cartesian',
  31684. alias: 'series.boxplot',
  31685. type: 'boxplot',
  31686. seriesType: 'boxplotSeries',
  31687. isBoxPlot: true,
  31688. requires: [
  31689. 'Ext.chart.series.sprite.BoxPlot',
  31690. 'Ext.chart.sprite.BoxPlot'
  31691. ],
  31692. config: {
  31693. itemInstancing: {
  31694. type: 'boxplot',
  31695. animation: {
  31696. // Setting the duration of these attributes to zero because
  31697. // the 'data' attributes of the series sprite (MarkerHolder)
  31698. // will be animated instead, and then changes applied to
  31699. // the attributes of 'boxplot' instances instantly.
  31700. customDurations: {
  31701. x: 0,
  31702. low: 0,
  31703. q1: 0,
  31704. median: 0,
  31705. q3: 0,
  31706. high: 0
  31707. }
  31708. }
  31709. },
  31710. /**
  31711. * @cfg {String} [lowField='low']
  31712. * The name of the store record field that represents the smallest value of a dataset.
  31713. */
  31714. lowField: 'low',
  31715. /**
  31716. * @cfg {String} [q1Field='q1']
  31717. * The name of the store record field that represents the lower (1-st) quartile
  31718. * value of a dataset.
  31719. */
  31720. q1Field: 'q1',
  31721. /**
  31722. * @cfg {String} [medianField='median']
  31723. * The name of the store record field that represents the median of a dataset.
  31724. */
  31725. medianField: 'median',
  31726. /**
  31727. * @cfg {String} [q3Field='q3']
  31728. * The name of the store record field that represents the upper (3-rd) quartile
  31729. * value of a dataset.
  31730. */
  31731. q3Field: 'q3',
  31732. /**
  31733. * @cfg {String} [highField='high']
  31734. * The name of the store record field that represents the largest value of a dataset.
  31735. */
  31736. highField: 'high'
  31737. },
  31738. fieldCategoryY: [
  31739. 'Low',
  31740. 'Q1',
  31741. 'Median',
  31742. 'Q3',
  31743. 'High'
  31744. ],
  31745. updateXAxis: function(xAxis) {
  31746. xAxis.setExpandRangeBy(0.5);
  31747. this.callParent(arguments);
  31748. }
  31749. });
  31750. /**
  31751. * Limited cache is a size limited cache container that stores limited number of objects.
  31752. *
  31753. * When {@link #get} is called, the container will try to find the object in the list.
  31754. * If failed it will call the {@link #feeder} to create that object. If there are too many
  31755. * objects in the container, the old ones are removed.
  31756. *
  31757. * __Note:__ This is not using a Least Recently Used policy due to simplicity and performance consideration.
  31758. * @private
  31759. */
  31760. Ext.define('Ext.draw.LimitedCache', {
  31761. config: {
  31762. /**
  31763. * @cfg {Number}
  31764. * The amount limit of the cache.
  31765. */
  31766. limit: 40,
  31767. /**
  31768. * @cfg {Function}
  31769. * Function that generates the object when look-up failed.
  31770. * @return {Number}
  31771. */
  31772. feeder: function() {
  31773. return 0;
  31774. },
  31775. /**
  31776. * @cfg {Object}
  31777. * The scope for {@link #feeder}
  31778. */
  31779. scope: null
  31780. },
  31781. cache: null,
  31782. constructor: function(config) {
  31783. this.cache = {};
  31784. this.cache.list = [];
  31785. this.cache.tail = 0;
  31786. this.initConfig(config);
  31787. },
  31788. /**
  31789. * Get a cached object.
  31790. * @param {String} id
  31791. * @return {Object}
  31792. */
  31793. get: function(id) {
  31794. // TODO: Implement cache hit optimization
  31795. var cache = this.cache,
  31796. limit = this.getLimit(),
  31797. feeder = this.getFeeder(),
  31798. scope = this.getScope() || this;
  31799. if (cache[id]) {
  31800. return cache[id].value;
  31801. }
  31802. if (cache.list[cache.tail]) {
  31803. delete cache[cache.list[cache.tail].cacheId];
  31804. }
  31805. cache[id] = cache.list[cache.tail] = {
  31806. value: feeder.apply(scope, Array.prototype.slice.call(arguments, 1)),
  31807. cacheId: id
  31808. };
  31809. cache.tail++;
  31810. if (cache.tail === limit) {
  31811. cache.tail = 0;
  31812. }
  31813. return cache[id].value;
  31814. },
  31815. /**
  31816. * Clear all the objects.
  31817. */
  31818. clear: function() {
  31819. this.cache = {};
  31820. this.cache.list = [];
  31821. this.cache.tail = 0;
  31822. }
  31823. });
  31824. /**
  31825. * This class we summarize the data and returns it when required.
  31826. */
  31827. Ext.define("Ext.draw.SegmentTree", {
  31828. config: {
  31829. strategy: "double"
  31830. },
  31831. /**
  31832. * @private
  31833. * @param {Object} result
  31834. * @param {Number} last
  31835. * @param {Number} dataX
  31836. * @param {Number} dataOpen
  31837. * @param {Number} dataHigh
  31838. * @param {Number} dataLow
  31839. * @param {Number} dataClose
  31840. */
  31841. time: function(result, last, dataX, dataOpen, dataHigh, dataLow, dataClose) {
  31842. var start = 0,
  31843. lastOffset, lastOffsetEnd,
  31844. minimum = new Date(dataX[result.startIdx[0]]),
  31845. maximum = new Date(dataX[result.endIdx[last - 1]]),
  31846. extDate = Ext.Date,
  31847. units = [
  31848. [
  31849. extDate.MILLI,
  31850. 1,
  31851. 'ms1',
  31852. null
  31853. ],
  31854. [
  31855. extDate.MILLI,
  31856. 2,
  31857. 'ms2',
  31858. 'ms1'
  31859. ],
  31860. [
  31861. extDate.MILLI,
  31862. 5,
  31863. 'ms5',
  31864. 'ms1'
  31865. ],
  31866. [
  31867. extDate.MILLI,
  31868. 10,
  31869. 'ms10',
  31870. 'ms5'
  31871. ],
  31872. [
  31873. extDate.MILLI,
  31874. 50,
  31875. 'ms50',
  31876. 'ms10'
  31877. ],
  31878. [
  31879. extDate.MILLI,
  31880. 100,
  31881. 'ms100',
  31882. 'ms50'
  31883. ],
  31884. [
  31885. extDate.MILLI,
  31886. 500,
  31887. 'ms500',
  31888. 'ms100'
  31889. ],
  31890. [
  31891. extDate.SECOND,
  31892. 1,
  31893. 's1',
  31894. 'ms500'
  31895. ],
  31896. [
  31897. extDate.SECOND,
  31898. 10,
  31899. 's10',
  31900. 's1'
  31901. ],
  31902. [
  31903. extDate.SECOND,
  31904. 30,
  31905. 's30',
  31906. 's10'
  31907. ],
  31908. [
  31909. extDate.MINUTE,
  31910. 1,
  31911. 'mi1',
  31912. 's10'
  31913. ],
  31914. [
  31915. extDate.MINUTE,
  31916. 5,
  31917. 'mi5',
  31918. 'mi1'
  31919. ],
  31920. [
  31921. extDate.MINUTE,
  31922. 10,
  31923. 'mi10',
  31924. 'mi5'
  31925. ],
  31926. [
  31927. extDate.MINUTE,
  31928. 30,
  31929. 'mi30',
  31930. 'mi10'
  31931. ],
  31932. [
  31933. extDate.HOUR,
  31934. 1,
  31935. 'h1',
  31936. 'mi30'
  31937. ],
  31938. [
  31939. extDate.HOUR,
  31940. 6,
  31941. 'h6',
  31942. 'h1'
  31943. ],
  31944. [
  31945. extDate.HOUR,
  31946. 12,
  31947. 'h12',
  31948. 'h6'
  31949. ],
  31950. [
  31951. extDate.DAY,
  31952. 1,
  31953. 'd1',
  31954. 'h12'
  31955. ],
  31956. [
  31957. extDate.DAY,
  31958. 7,
  31959. 'd7',
  31960. 'd1'
  31961. ],
  31962. [
  31963. extDate.MONTH,
  31964. 1,
  31965. 'mo1',
  31966. 'd1'
  31967. ],
  31968. [
  31969. extDate.MONTH,
  31970. 3,
  31971. 'mo3',
  31972. 'mo1'
  31973. ],
  31974. [
  31975. extDate.MONTH,
  31976. 6,
  31977. 'mo6',
  31978. 'mo3'
  31979. ],
  31980. [
  31981. extDate.YEAR,
  31982. 1,
  31983. 'y1',
  31984. 'mo3'
  31985. ],
  31986. [
  31987. extDate.YEAR,
  31988. 5,
  31989. 'y5',
  31990. 'y1'
  31991. ],
  31992. [
  31993. extDate.YEAR,
  31994. 10,
  31995. 'y10',
  31996. 'y5'
  31997. ],
  31998. [
  31999. extDate.YEAR,
  32000. 100,
  32001. 'y100',
  32002. 'y10'
  32003. ]
  32004. ],
  32005. unitIdx, currentUnit,
  32006. plainStart = start,
  32007. plainEnd = last,
  32008. first = false,
  32009. startIdxs = result.startIdx,
  32010. endIdxs = result.endIdx,
  32011. minIdxs = result.minIdx,
  32012. maxIdxs = result.maxIdx,
  32013. opens = result.open,
  32014. closes = result.close,
  32015. minXs = result.minX,
  32016. minYs = result.minY,
  32017. maxXs = result.maxX,
  32018. maxYs = result.maxY,
  32019. i, current;
  32020. for (unitIdx = 0; last > start + 1 && unitIdx < units.length; unitIdx++) {
  32021. minimum = new Date(dataX[startIdxs[0]]);
  32022. currentUnit = units[unitIdx];
  32023. minimum = extDate.align(minimum, currentUnit[0], currentUnit[1]);
  32024. if (extDate.diff(minimum, maximum, currentUnit[0]) > dataX.length * 2 * currentUnit[1]) {
  32025. continue;
  32026. }
  32027. if (currentUnit[3] && result.map['time_' + currentUnit[3]]) {
  32028. lastOffset = result.map['time_' + currentUnit[3]][0];
  32029. lastOffsetEnd = result.map['time_' + currentUnit[3]][1];
  32030. } else {
  32031. lastOffset = plainStart;
  32032. lastOffsetEnd = plainEnd;
  32033. }
  32034. start = last;
  32035. current = minimum;
  32036. first = true;
  32037. startIdxs[last] = startIdxs[lastOffset];
  32038. endIdxs[last] = endIdxs[lastOffset];
  32039. minIdxs[last] = minIdxs[lastOffset];
  32040. maxIdxs[last] = maxIdxs[lastOffset];
  32041. opens[last] = opens[lastOffset];
  32042. closes[last] = closes[lastOffset];
  32043. minXs[last] = minXs[lastOffset];
  32044. minYs[last] = minYs[lastOffset];
  32045. maxXs[last] = maxXs[lastOffset];
  32046. maxYs[last] = maxYs[lastOffset];
  32047. current = Ext.Date.add(current, currentUnit[0], currentUnit[1]);
  32048. for (i = lastOffset + 1; i < lastOffsetEnd; i++) {
  32049. if (dataX[endIdxs[i]] < +current) {
  32050. endIdxs[last] = endIdxs[i];
  32051. closes[last] = closes[i];
  32052. if (maxYs[i] > maxYs[last]) {
  32053. maxYs[last] = maxYs[i];
  32054. maxXs[last] = maxXs[i];
  32055. maxIdxs[last] = maxIdxs[i];
  32056. }
  32057. if (minYs[i] < minYs[last]) {
  32058. minYs[last] = minYs[i];
  32059. minXs[last] = minXs[i];
  32060. minIdxs[last] = minIdxs[i];
  32061. }
  32062. } else {
  32063. last++;
  32064. startIdxs[last] = startIdxs[i];
  32065. endIdxs[last] = endIdxs[i];
  32066. minIdxs[last] = minIdxs[i];
  32067. maxIdxs[last] = maxIdxs[i];
  32068. opens[last] = opens[i];
  32069. closes[last] = closes[i];
  32070. minXs[last] = minXs[i];
  32071. minYs[last] = minYs[i];
  32072. maxXs[last] = maxXs[i];
  32073. maxYs[last] = maxYs[i];
  32074. current = Ext.Date.add(current, currentUnit[0], currentUnit[1]);
  32075. }
  32076. }
  32077. if (last > start) {
  32078. result.map['time_' + currentUnit[2]] = [
  32079. start,
  32080. last
  32081. ];
  32082. }
  32083. }
  32084. },
  32085. /**
  32086. * @private
  32087. * @param {Object} result
  32088. * @param {Number} position
  32089. * @param {Number} dataX
  32090. * @param {Number} dataOpen
  32091. * @param {Number} dataHigh
  32092. * @param {Number} dataLow
  32093. * @param {Number} dataClose
  32094. */
  32095. "double": function(result, position, dataX, dataOpen, dataHigh, dataLow, dataClose) {
  32096. var offset = 0,
  32097. lastOffset,
  32098. step = 1,
  32099. i, startIdx, endIdx, minIdx, maxIdx, open, close, minX, minY, maxX, maxY;
  32100. while (position > offset + 1) {
  32101. lastOffset = offset;
  32102. offset = position;
  32103. step += step;
  32104. for (i = lastOffset; i < offset; i += 2) {
  32105. if (i === offset - 1) {
  32106. startIdx = result.startIdx[i];
  32107. endIdx = result.endIdx[i];
  32108. minIdx = result.minIdx[i];
  32109. maxIdx = result.maxIdx[i];
  32110. open = result.open[i];
  32111. close = result.close[i];
  32112. minX = result.minX[i];
  32113. minY = result.minY[i];
  32114. maxX = result.maxX[i];
  32115. maxY = result.maxY[i];
  32116. } else {
  32117. startIdx = result.startIdx[i];
  32118. endIdx = result.endIdx[i + 1];
  32119. open = result.open[i];
  32120. close = result.close[i];
  32121. if (result.minY[i] <= result.minY[i + 1]) {
  32122. minIdx = result.minIdx[i];
  32123. minX = result.minX[i];
  32124. minY = result.minY[i];
  32125. } else {
  32126. minIdx = result.minIdx[i + 1];
  32127. minX = result.minX[i + 1];
  32128. minY = result.minY[i + 1];
  32129. }
  32130. if (result.maxY[i] >= result.maxY[i + 1]) {
  32131. maxIdx = result.maxIdx[i];
  32132. maxX = result.maxX[i];
  32133. maxY = result.maxY[i];
  32134. } else {
  32135. maxIdx = result.maxIdx[i + 1];
  32136. maxX = result.maxX[i + 1];
  32137. maxY = result.maxY[i + 1];
  32138. }
  32139. }
  32140. result.startIdx[position] = startIdx;
  32141. result.endIdx[position] = endIdx;
  32142. result.minIdx[position] = minIdx;
  32143. result.maxIdx[position] = maxIdx;
  32144. result.open[position] = open;
  32145. result.close[position] = close;
  32146. result.minX[position] = minX;
  32147. result.minY[position] = minY;
  32148. result.maxX[position] = maxX;
  32149. result.maxY[position] = maxY;
  32150. position++;
  32151. }
  32152. result.map['double_' + step] = [
  32153. offset,
  32154. position
  32155. ];
  32156. }
  32157. },
  32158. /**
  32159. * @method
  32160. * @private
  32161. */
  32162. none: Ext.emptyFn,
  32163. /**
  32164. * @private
  32165. *
  32166. * @param {Number} dataX
  32167. * @param {Number} dataOpen
  32168. * @param {Number} dataHigh
  32169. * @param {Number} dataLow
  32170. * @param {Number} dataClose
  32171. * @return {Object}
  32172. */
  32173. aggregateData: function(dataX, dataOpen, dataHigh, dataLow, dataClose) {
  32174. var length = dataX.length,
  32175. startIdx = [],
  32176. endIdx = [],
  32177. minIdx = [],
  32178. maxIdx = [],
  32179. open = [],
  32180. minX = [],
  32181. minY = [],
  32182. maxX = [],
  32183. maxY = [],
  32184. close = [],
  32185. result = {
  32186. startIdx: startIdx,
  32187. endIdx: endIdx,
  32188. minIdx: minIdx,
  32189. maxIdx: maxIdx,
  32190. open: open,
  32191. minX: minX,
  32192. minY: minY,
  32193. maxX: maxX,
  32194. maxY: maxY,
  32195. close: close
  32196. },
  32197. i;
  32198. for (i = 0; i < length; i++) {
  32199. startIdx[i] = i;
  32200. endIdx[i] = i;
  32201. minIdx[i] = i;
  32202. maxIdx[i] = i;
  32203. open[i] = dataOpen[i];
  32204. minX[i] = dataX[i];
  32205. minY[i] = dataLow[i];
  32206. maxX[i] = dataX[i];
  32207. maxY[i] = dataHigh[i];
  32208. close[i] = dataClose[i];
  32209. }
  32210. result.map = {
  32211. original: [
  32212. 0,
  32213. length
  32214. ]
  32215. };
  32216. if (length) {
  32217. this[this.getStrategy()](result, length, dataX, dataOpen, dataHigh, dataLow, dataClose);
  32218. }
  32219. return result;
  32220. },
  32221. /**
  32222. * @private
  32223. * @param {Object} items
  32224. * @param {Number} start
  32225. * @param {Number} end
  32226. * @param {Number} key
  32227. * @return {*}
  32228. */
  32229. binarySearchMin: function(items, start, end, key) {
  32230. var dx = this.dataX;
  32231. if (key <= dx[items.startIdx[0]]) {
  32232. return start;
  32233. }
  32234. if (key >= dx[items.startIdx[end - 1]]) {
  32235. return end - 1;
  32236. }
  32237. while (start + 1 < end) {
  32238. var mid = (start + end) >> 1,
  32239. val = dx[items.startIdx[mid]];
  32240. if (val === key) {
  32241. return mid;
  32242. } else if (val < key) {
  32243. start = mid;
  32244. } else {
  32245. end = mid;
  32246. }
  32247. }
  32248. return start;
  32249. },
  32250. /**
  32251. * @private
  32252. * @param {Object} items
  32253. * @param {Number} start
  32254. * @param {Number} end
  32255. * @param {Number} key
  32256. * @return {*}
  32257. */
  32258. binarySearchMax: function(items, start, end, key) {
  32259. var dx = this.dataX;
  32260. if (key <= dx[items.endIdx[0]]) {
  32261. return start;
  32262. }
  32263. if (key >= dx[items.endIdx[end - 1]]) {
  32264. return end - 1;
  32265. }
  32266. while (start + 1 < end) {
  32267. var mid = (start + end) >> 1,
  32268. val = dx[items.endIdx[mid]];
  32269. if (val === key) {
  32270. return mid;
  32271. } else if (val < key) {
  32272. start = mid;
  32273. } else {
  32274. end = mid;
  32275. }
  32276. }
  32277. return end;
  32278. },
  32279. constructor: function(config) {
  32280. this.initConfig(config);
  32281. },
  32282. /**
  32283. * Sets the data of the segment tree.
  32284. * @param {Number} dataX
  32285. * @param {Number} dataOpen
  32286. * @param {Number} dataHigh
  32287. * @param {Number} dataLow
  32288. * @param {Number} dataClose
  32289. */
  32290. setData: function(dataX, dataOpen, dataHigh, dataLow, dataClose) {
  32291. if (!dataHigh) {
  32292. dataClose = dataLow = dataHigh = dataOpen;
  32293. }
  32294. this.dataX = dataX;
  32295. this.dataOpen = dataOpen;
  32296. this.dataHigh = dataHigh;
  32297. this.dataLow = dataLow;
  32298. this.dataClose = dataClose;
  32299. if (dataX.length === dataHigh.length && dataX.length === dataLow.length) {
  32300. this.cache = this.aggregateData(dataX, dataOpen, dataHigh, dataLow, dataClose);
  32301. }
  32302. },
  32303. /**
  32304. * Returns the minimum range of data that fits the given range and step size.
  32305. *
  32306. * @param {Number} min
  32307. * @param {Number} max
  32308. * @param {Number} estStep
  32309. * @return {Object} The aggregation information.
  32310. * @return {Number} return.start
  32311. * @return {Number} return.end
  32312. * @return {Object} return.data The aggregated data
  32313. */
  32314. getAggregation: function(min, max, estStep) {
  32315. if (!this.cache) {
  32316. return null;
  32317. }
  32318. var minStep = Infinity,
  32319. range = this.dataX[this.dataX.length - 1] - this.dataX[0],
  32320. cacheMap = this.cache.map,
  32321. result = cacheMap.original,
  32322. name, positions, ln, step, minIdx, maxIdx;
  32323. for (name in cacheMap) {
  32324. positions = cacheMap[name];
  32325. ln = positions[1] - positions[0] - 1;
  32326. step = range / ln;
  32327. if (estStep <= step && step < minStep) {
  32328. result = positions;
  32329. minStep = step;
  32330. }
  32331. }
  32332. minIdx = Math.max(this.binarySearchMin(this.cache, result[0], result[1], min), result[0]);
  32333. maxIdx = Math.min(this.binarySearchMax(this.cache, result[0], result[1], max) + 1, result[1]);
  32334. return {
  32335. data: this.cache,
  32336. start: minIdx,
  32337. end: maxIdx
  32338. };
  32339. }
  32340. });
  32341. /**
  32342. *
  32343. */
  32344. Ext.define('Ext.chart.series.sprite.Aggregative', {
  32345. extend: 'Ext.chart.series.sprite.Cartesian',
  32346. requires: [
  32347. 'Ext.draw.LimitedCache',
  32348. 'Ext.draw.SegmentTree'
  32349. ],
  32350. inheritableStatics: {
  32351. def: {
  32352. processors: {
  32353. /**
  32354. * @cfg {Number[]} [dataHigh=null] Data items representing the high values of the aggregated data.
  32355. */
  32356. dataHigh: 'data',
  32357. /**
  32358. * @cfg {Number[]} [dataLow=null] Data items representing the low values of the aggregated data.
  32359. */
  32360. dataLow: 'data',
  32361. /**
  32362. * @cfg {Number[]} [dataClose=null] Data items representing the closing values of the aggregated data.
  32363. */
  32364. dataClose: 'data'
  32365. },
  32366. aliases: {
  32367. /**
  32368. * @cfg {Number[]} [dataOpen=null] Data items representing the opening values of the aggregated data.
  32369. */
  32370. dataOpen: 'dataY'
  32371. },
  32372. defaults: {
  32373. dataHigh: null,
  32374. dataLow: null,
  32375. dataClose: null
  32376. }
  32377. }
  32378. },
  32379. config: {
  32380. aggregator: {}
  32381. },
  32382. applyAggregator: function(aggregator, oldAggr) {
  32383. return Ext.factory(aggregator, Ext.draw.SegmentTree, oldAggr);
  32384. },
  32385. constructor: function() {
  32386. this.callParent(arguments);
  32387. },
  32388. processDataY: function() {
  32389. var me = this,
  32390. attr = me.attr,
  32391. high = attr.dataHigh,
  32392. low = attr.dataLow,
  32393. close = attr.dataClose,
  32394. open = attr.dataY,
  32395. aggregator;
  32396. me.callParent(arguments);
  32397. if (attr.dataX && open && open.length > 0) {
  32398. aggregator = me.getAggregator();
  32399. if (high) {
  32400. aggregator.setData(attr.dataX, attr.dataY, high, low, close);
  32401. } else {
  32402. aggregator.setData(attr.dataX, attr.dataY);
  32403. }
  32404. }
  32405. },
  32406. getGapWidth: function() {
  32407. return 1;
  32408. },
  32409. renderClipped: function(surface, ctx, dataClipRect, surfaceClipRect) {
  32410. var me = this,
  32411. min = Math.min(dataClipRect[0], dataClipRect[2]),
  32412. max = Math.max(dataClipRect[0], dataClipRect[2]),
  32413. aggregator = me.getAggregator(),
  32414. aggregates = aggregator && aggregator.getAggregation(min, max, (max - min) / surfaceClipRect[2] * me.getGapWidth());
  32415. if (aggregates) {
  32416. me.dataStart = aggregates.data.startIdx[aggregates.start];
  32417. me.dataEnd = aggregates.data.endIdx[aggregates.end - 1];
  32418. me.renderAggregates(aggregates.data, aggregates.start, aggregates.end, surface, ctx, dataClipRect, surfaceClipRect);
  32419. }
  32420. }
  32421. });
  32422. /**
  32423. * @class Ext.chart.series.sprite.CandleStick
  32424. * @extends Ext.chart.series.sprite.Aggregative
  32425. *
  32426. * CandleStick series sprite.
  32427. */
  32428. Ext.define('Ext.chart.series.sprite.CandleStick', {
  32429. alias: 'sprite.candlestickSeries',
  32430. extend: 'Ext.chart.series.sprite.Aggregative',
  32431. inheritableStatics: {
  32432. def: {
  32433. processors: {
  32434. raiseStyle: function(n, o) {
  32435. return Ext.merge({}, o || {}, n);
  32436. },
  32437. dropStyle: function(n, o) {
  32438. return Ext.merge({}, o || {}, n);
  32439. },
  32440. /**
  32441. * @cfg {Number} [barWidth=15] The bar width of the candles.
  32442. */
  32443. barWidth: 'number',
  32444. /**
  32445. * @cfg {Number} [padding=3] The amount of padding between candles.
  32446. */
  32447. padding: 'number',
  32448. /**
  32449. * @cfg {String} [ohlcType='candlestick'] Determines whether candlestick or ohlc is used.
  32450. */
  32451. ohlcType: 'enums(candlestick,ohlc)'
  32452. },
  32453. defaults: {
  32454. raiseStyle: {
  32455. strokeStyle: 'green',
  32456. fillStyle: 'green'
  32457. },
  32458. dropStyle: {
  32459. strokeStyle: 'red',
  32460. fillStyle: 'red'
  32461. },
  32462. barWidth: 15,
  32463. padding: 3,
  32464. lineJoin: 'miter',
  32465. miterLimit: 5,
  32466. ohlcType: 'candlestick'
  32467. },
  32468. triggers: {
  32469. raiseStyle: 'raiseStyle',
  32470. dropStyle: 'dropStyle'
  32471. },
  32472. updaters: {
  32473. raiseStyle: function() {
  32474. var me = this,
  32475. tpl = me.raiseTemplate;
  32476. if (tpl) {
  32477. tpl.setAttributes(me.attr.raiseStyle);
  32478. }
  32479. },
  32480. dropStyle: function() {
  32481. var me = this,
  32482. tpl = me.dropTemplate;
  32483. if (tpl) {
  32484. tpl.setAttributes(me.attr.dropStyle);
  32485. }
  32486. }
  32487. }
  32488. }
  32489. },
  32490. candlestick: function(ctx, open, high, low, close, mid, halfWidth) {
  32491. var minOC = Math.min(open, close),
  32492. maxOC = Math.max(open, close);
  32493. // lower stick
  32494. ctx.moveTo(mid, low);
  32495. ctx.lineTo(mid, minOC);
  32496. // body rect
  32497. ctx.moveTo(mid + halfWidth, maxOC);
  32498. ctx.lineTo(mid + halfWidth, minOC);
  32499. ctx.lineTo(mid - halfWidth, minOC);
  32500. ctx.lineTo(mid - halfWidth, maxOC);
  32501. ctx.closePath();
  32502. // upper stick
  32503. ctx.moveTo(mid, high);
  32504. ctx.lineTo(mid, maxOC);
  32505. },
  32506. ohlc: function(ctx, open, high, low, close, mid, halfWidth) {
  32507. ctx.moveTo(mid, high);
  32508. ctx.lineTo(mid, low);
  32509. ctx.moveTo(mid, open);
  32510. ctx.lineTo(mid - halfWidth, open);
  32511. ctx.moveTo(mid, close);
  32512. ctx.lineTo(mid + halfWidth, close);
  32513. },
  32514. constructor: function() {
  32515. var me = this,
  32516. Rect = Ext.draw.sprite.Rect;
  32517. me.callParent(arguments);
  32518. me.raiseTemplate = new Rect({
  32519. parent: me
  32520. });
  32521. me.dropTemplate = new Rect({
  32522. parent: me
  32523. });
  32524. },
  32525. getGapWidth: function() {
  32526. var attr = this.attr,
  32527. barWidth = attr.barWidth,
  32528. padding = attr.padding;
  32529. return barWidth + padding;
  32530. },
  32531. renderAggregates: function(aggregates, start, end, surface, ctx, clip) {
  32532. var me = this,
  32533. attr = me.attr,
  32534. ohlcType = attr.ohlcType,
  32535. series = me.getSeries(),
  32536. matrix = attr.matrix,
  32537. xx = matrix.getXX(),
  32538. yy = matrix.getYY(),
  32539. dx = matrix.getDX(),
  32540. dy = matrix.getDY(),
  32541. halfWidth = Math.round(attr.barWidth * 0.5),
  32542. dataX = attr.dataX,
  32543. opens = aggregates.open,
  32544. closes = aggregates.close,
  32545. maxYs = aggregates.maxY,
  32546. minYs = aggregates.minY,
  32547. startIdxs = aggregates.startIdx,
  32548. pixelAdjust = attr.lineWidth * surface.devicePixelRatio / 2,
  32549. renderer = attr.renderer,
  32550. rendererConfig = renderer && {},
  32551. rendererParams, rendererChanges, open, high, low, close, mid, i, template;
  32552. me.rendererData = me.rendererData || {
  32553. store: me.getStore()
  32554. };
  32555. pixelAdjust -= Math.floor(pixelAdjust);
  32556. // Render raises.
  32557. ctx.save();
  32558. template = me.raiseTemplate;
  32559. template.useAttributes(ctx, clip);
  32560. if (!renderer) {
  32561. ctx.beginPath();
  32562. }
  32563. for (i = start; i < end; i++) {
  32564. if (opens[i] <= closes[i]) {
  32565. open = Math.round(opens[i] * yy + dy) + pixelAdjust;
  32566. high = Math.round(maxYs[i] * yy + dy) + pixelAdjust;
  32567. low = Math.round(minYs[i] * yy + dy) + pixelAdjust;
  32568. close = Math.round(closes[i] * yy + dy) + pixelAdjust;
  32569. mid = Math.round(dataX[startIdxs[i]] * xx + dx) + pixelAdjust;
  32570. if (renderer) {
  32571. ctx.save();
  32572. ctx.beginPath();
  32573. rendererConfig.open = open;
  32574. rendererConfig.high = high;
  32575. rendererConfig.low = low;
  32576. rendererConfig.close = close;
  32577. rendererConfig.mid = mid;
  32578. rendererConfig.halfWidth = halfWidth;
  32579. rendererParams = [
  32580. me,
  32581. rendererConfig,
  32582. me.rendererData,
  32583. i
  32584. ];
  32585. rendererChanges = Ext.callback(renderer, null, rendererParams, 0, series);
  32586. Ext.apply(ctx, rendererChanges);
  32587. }
  32588. me[ohlcType](ctx, open, high, low, close, mid, halfWidth);
  32589. if (renderer) {
  32590. ctx.fillStroke(template.attr);
  32591. ctx.restore();
  32592. }
  32593. }
  32594. }
  32595. if (!renderer) {
  32596. ctx.fillStroke(template.attr);
  32597. }
  32598. ctx.restore();
  32599. // Render drops.
  32600. ctx.save();
  32601. template = me.dropTemplate;
  32602. template.useAttributes(ctx, clip);
  32603. if (!renderer) {
  32604. ctx.beginPath();
  32605. }
  32606. for (i = start; i < end; i++) {
  32607. if (opens[i] > closes[i]) {
  32608. open = Math.round(opens[i] * yy + dy) + pixelAdjust;
  32609. high = Math.round(maxYs[i] * yy + dy) + pixelAdjust;
  32610. low = Math.round(minYs[i] * yy + dy) + pixelAdjust;
  32611. close = Math.round(closes[i] * yy + dy) + pixelAdjust;
  32612. mid = Math.round(dataX[startIdxs[i]] * xx + dx) + pixelAdjust;
  32613. if (renderer) {
  32614. ctx.save();
  32615. ctx.beginPath();
  32616. rendererConfig.open = open;
  32617. rendererConfig.high = high;
  32618. rendererConfig.low = low;
  32619. rendererConfig.close = close;
  32620. rendererConfig.mid = mid;
  32621. rendererConfig.halfWidth = halfWidth;
  32622. rendererParams = [
  32623. me,
  32624. rendererConfig,
  32625. me.rendererData,
  32626. i
  32627. ];
  32628. rendererChanges = Ext.callback(renderer, null, rendererParams, 0, me.getSeries());
  32629. Ext.apply(ctx, rendererChanges);
  32630. }
  32631. me[ohlcType](ctx, open, high, low, close, mid, halfWidth);
  32632. if (renderer) {
  32633. ctx.fillStroke(template.attr);
  32634. ctx.restore();
  32635. }
  32636. }
  32637. }
  32638. if (!renderer) {
  32639. ctx.fillStroke(template.attr);
  32640. }
  32641. ctx.restore();
  32642. }
  32643. });
  32644. /**
  32645. * @class Ext.chart.series.CandleStick
  32646. * @extends Ext.chart.series.Cartesian
  32647. *
  32648. * Creates a candlestick or OHLC Chart.
  32649. *
  32650. * CandleStick series are typically used to plot price movements of a security on an exchange over time.
  32651. * The series can be used with the 'time' axis, but since exchanges often close for weekends,
  32652. * and the price data has gaps for those days, it's more practical to use this series with
  32653. * the 'category' axis to avoid rendering those data gaps. The 'category' axis has no notion
  32654. * of time (and thus gaps) and treats every Date object (value of the 'xField') as a unique
  32655. * category. However, it also means that it doesn't support the 'dateFormat' config,
  32656. * which can be easily remedied with a 'renderer' that formats a Date object for use
  32657. * as an axis label. For example:
  32658. *
  32659. * @example
  32660. * new Ext.chart.CartesianChart({
  32661. * xtype: 'cartesian',
  32662. * renderTo: document.body,
  32663. * width: 700,
  32664. * height: 500,
  32665. * insetPadding: 20,
  32666. * innerPadding: '0 20 0 20',
  32667. *
  32668. * store: {
  32669. * data: [
  32670. * {
  32671. * time: new Date('Nov 17 2016'),
  32672. * o: 52.40, h: 52.74, l: 52.18, c: 52.29
  32673. * },
  32674. * {
  32675. * time: new Date('Nov 18 2016'),
  32676. * o: 51.87, h: 52.22, l: 51.51, c: 52.04
  32677. * },
  32678. * {
  32679. * time: new Date('Nov 21 2016'),
  32680. * o: 53.02, h: 53.40, l: 53.02, c: 53.33
  32681. * },
  32682. * {
  32683. * time: new Date('Nov 22 2016'),
  32684. * o: 53.48, h: 53.80, l: 53.13, c: 53.70
  32685. * },
  32686. * {
  32687. * time: new Date('Nov 23 2016'),
  32688. * o: 52.85, h: 53.39, l: 52.76, c: 53.28
  32689. * },
  32690. * {
  32691. * time: new Date('Nov 25 2016'),
  32692. * o: 53.28, h: 53.45, l: 53.20, c: 53.40
  32693. * },
  32694. * {
  32695. * time: new Date('Nov 28 2016'),
  32696. * o: 52.51, h: 52.58, l: 51.96, c: 52.00
  32697. * },
  32698. * {
  32699. * time: new Date('Nov 29 2016'),
  32700. * o: 51.25, h: 51.98, l: 51.10, c: 51.79
  32701. * },
  32702. * {
  32703. * time: new Date('Nov 30 2016'),
  32704. * o: 53.65, h: 54.56, l: 53.60, c: 54.17
  32705. * },
  32706. * {
  32707. * time: new Date('Dec 01 2016'),
  32708. * o: 55.26, h: 55.75, l: 54.94, c: 55.13
  32709. * }
  32710. * ]
  32711. * },
  32712. * axes: [
  32713. * {
  32714. * type: 'numeric',
  32715. * position: 'left'
  32716. * },
  32717. * {
  32718. * type: 'category',
  32719. * position: 'bottom',
  32720. *
  32721. * renderer: function (axis, value) {
  32722. * return Ext.Date.format(value, 'M j\nY');
  32723. * }
  32724. * }
  32725. * ],
  32726. * series: {
  32727. * type: 'candlestick',
  32728. *
  32729. * xField: 'time',
  32730. *
  32731. * openField: 'o',
  32732. * highField: 'h',
  32733. * lowField: 'l',
  32734. * closeField: 'c',
  32735. *
  32736. * style: {
  32737. * barWidth: 10,
  32738. *
  32739. * dropStyle: {
  32740. * fill: 'rgb(222, 87, 87)',
  32741. * stroke: 'rgb(222, 87, 87)',
  32742. * lineWidth: 3
  32743. * },
  32744. * raiseStyle: {
  32745. * fill: 'rgb(48, 189, 167)',
  32746. * stroke: 'rgb(48, 189, 167)',
  32747. * lineWidth: 3
  32748. * }
  32749. * }
  32750. * }
  32751. * });
  32752. */
  32753. Ext.define('Ext.chart.series.CandleStick', {
  32754. extend: 'Ext.chart.series.Cartesian',
  32755. requires: [
  32756. 'Ext.chart.series.sprite.CandleStick'
  32757. ],
  32758. alias: 'series.candlestick',
  32759. type: 'candlestick',
  32760. seriesType: 'candlestickSeries',
  32761. isCandleStick: true,
  32762. config: {
  32763. /**
  32764. * @cfg {String} openField
  32765. * The store record field name that represents the opening value of the given period.
  32766. */
  32767. openField: null,
  32768. /**
  32769. * @cfg {String} highField
  32770. * The store record field name that represents the highest value of the time interval represented.
  32771. */
  32772. highField: null,
  32773. /**
  32774. * @cfg {String} lowField
  32775. * The store record field name that represents the lowest value of the time interval represented.
  32776. */
  32777. lowField: null,
  32778. /**
  32779. * @cfg {String} closeField
  32780. * The store record field name that represents the closing value of the given period.
  32781. */
  32782. closeField: null
  32783. },
  32784. fieldCategoryY: [
  32785. 'Open',
  32786. 'High',
  32787. 'Low',
  32788. 'Close'
  32789. ],
  32790. themeColorCount: function() {
  32791. return 2;
  32792. }
  32793. });
  32794. /**
  32795. * @abstract
  32796. * @class Ext.chart.series.Polar
  32797. * @extends Ext.chart.series.Series
  32798. *
  32799. * Common base class for series implementations that plot values using polar coordinates.
  32800. *
  32801. * Polar charts accept angles in radians. You can calculate radians with the following
  32802. * formula:
  32803. *
  32804. * radians = degrees x Π/180
  32805. */
  32806. Ext.define('Ext.chart.series.Polar', {
  32807. extend: 'Ext.chart.series.Series',
  32808. config: {
  32809. /**
  32810. * @cfg {Number} [rotation=0]
  32811. * The angle in radians at which the first polar series item should start.
  32812. */
  32813. rotation: 0,
  32814. /**
  32815. * @cfg {Number} radius
  32816. * @private
  32817. * Use {@link Ext.chart.series.Pie#cfg!radiusFactor radiusFactor} instead.
  32818. *
  32819. * The internally used radius of the polar series. Set to `null` will fit the
  32820. * polar series to the boundary.
  32821. */
  32822. radius: null,
  32823. /**
  32824. * @cfg {Array} center for the polar series.
  32825. */
  32826. center: [
  32827. 0,
  32828. 0
  32829. ],
  32830. /**
  32831. * @cfg {Number} [offsetX=0]
  32832. * The x-offset of center of the polar series related to the center of the boundary.
  32833. */
  32834. offsetX: 0,
  32835. /**
  32836. * @cfg {Number} [offsetY=0]
  32837. * The y-offset of center of the polar series related to the center of the boundary.
  32838. */
  32839. offsetY: 0,
  32840. /**
  32841. * @cfg {Boolean} [showInLegend=true]
  32842. * Whether to add the series elements as legend items.
  32843. */
  32844. showInLegend: true,
  32845. /**
  32846. * @private
  32847. * @cfg {String} xField
  32848. */
  32849. xField: null,
  32850. /**
  32851. * @private
  32852. * @cfg {String} yField
  32853. */
  32854. yField: null,
  32855. /**
  32856. * @cfg {String} angleField
  32857. * The store record field name for the angular axes in radar charts,
  32858. * or the size of the slices in pie charts.
  32859. */
  32860. angleField: null,
  32861. /**
  32862. * @cfg {String} radiusField
  32863. * The store record field name for the radial axes in radar charts,
  32864. * or the radius of the slices in pie charts.
  32865. */
  32866. radiusField: null,
  32867. xAxis: null,
  32868. yAxis: null
  32869. },
  32870. directions: [
  32871. 'X',
  32872. 'Y'
  32873. ],
  32874. fieldCategoryX: [
  32875. 'X'
  32876. ],
  32877. fieldCategoryY: [
  32878. 'Y'
  32879. ],
  32880. deprecatedConfigs: {
  32881. field: 'angleField',
  32882. lengthField: 'radiusField'
  32883. },
  32884. constructor: function(config) {
  32885. var me = this,
  32886. configurator = me.self.getConfigurator(),
  32887. configs = configurator.configs,
  32888. p;
  32889. if (config) {
  32890. for (p in me.deprecatedConfigs) {
  32891. if (p in config && !(config in configs)) {
  32892. Ext.raise("'" + p + "' config has been deprecated. Please use the '" + me.deprecatedConfigs[p] + "' config instead.");
  32893. }
  32894. }
  32895. }
  32896. me.callParent([
  32897. config
  32898. ]);
  32899. },
  32900. getXField: function() {
  32901. return this.getAngleField();
  32902. },
  32903. updateXField: function(value) {
  32904. this.setAngleField(value);
  32905. },
  32906. getYField: function() {
  32907. return this.getRadiusField();
  32908. },
  32909. updateYField: function(value) {
  32910. this.setRadiusField(value);
  32911. },
  32912. applyXAxis: function(newAxis, oldAxis) {
  32913. return this.getChart().getAxis(newAxis) || oldAxis;
  32914. },
  32915. applyYAxis: function(newAxis, oldAxis) {
  32916. return this.getChart().getAxis(newAxis) || oldAxis;
  32917. },
  32918. getXRange: function() {
  32919. return [
  32920. this.dataRange[0],
  32921. this.dataRange[2]
  32922. ];
  32923. },
  32924. getYRange: function() {
  32925. return [
  32926. this.dataRange[1],
  32927. this.dataRange[3]
  32928. ];
  32929. },
  32930. themeColorCount: function() {
  32931. var me = this,
  32932. store = me.getStore(),
  32933. count = store && store.getCount() || 0;
  32934. return count;
  32935. },
  32936. isStoreDependantColorCount: true,
  32937. getDefaultSpriteConfig: function() {
  32938. return {
  32939. type: this.seriesType,
  32940. renderer: this.getRenderer(),
  32941. centerX: 0,
  32942. centerY: 0,
  32943. rotationCenterX: 0,
  32944. rotationCenterY: 0
  32945. };
  32946. },
  32947. applyRotation: function(rotation) {
  32948. return Ext.draw.sprite.AttributeParser.angle(Ext.draw.Draw.rad(rotation));
  32949. },
  32950. updateRotation: function(rotation) {
  32951. var sprites = this.getSprites();
  32952. if (sprites && sprites[0]) {
  32953. sprites[0].setAttributes({
  32954. baseRotation: rotation
  32955. });
  32956. }
  32957. }
  32958. });
  32959. /**
  32960. * Displays a gauge chart.
  32961. *
  32962. * @example
  32963. * Ext.create({
  32964. * xtype: 'polar',
  32965. * renderTo: document.body,
  32966. * width: 600,
  32967. * height: 400,
  32968. * store: {
  32969. * fields: ['mph', 'fuel', 'temp', 'rpm'],
  32970. * data: [{
  32971. * mph: 65,
  32972. * fuel: 50,
  32973. * temp: 150,
  32974. * rpm: 6000
  32975. * }]
  32976. * },
  32977. * series: {
  32978. * type: 'gauge',
  32979. * colors: ['#1F6D91', '#90BCC9'],
  32980. * angleField: 'mph',
  32981. * needle: true,
  32982. * donut: 30
  32983. * }
  32984. * });
  32985. */
  32986. Ext.define('Ext.chart.series.Gauge', {
  32987. alias: 'series.gauge',
  32988. extend: 'Ext.chart.series.Polar',
  32989. type: 'gauge',
  32990. seriesType: 'pieslice',
  32991. requires: [
  32992. 'Ext.draw.sprite.Sector'
  32993. ],
  32994. config: {
  32995. /**
  32996. * @cfg {String} angleField
  32997. * The store record field name to be used for the gauge value.
  32998. * The values bound to this field name must be positive real numbers.
  32999. */
  33000. /**
  33001. * @cfg {Boolean} needle
  33002. * If true, display the gauge as a needle, otherwise as a sector.
  33003. */
  33004. needle: false,
  33005. /**
  33006. * @cfg {Number} needleLength
  33007. * Percentage of the length of needle compared to the radius of the entire disk.
  33008. */
  33009. needleLength: 90,
  33010. /**
  33011. * @cfg {Number} needleWidth
  33012. * Width of the needle in pixels.
  33013. */
  33014. needleWidth: 4,
  33015. /**
  33016. * @cfg {Number} donut
  33017. * Percentage of the radius of the donut hole compared to the entire disk.
  33018. */
  33019. donut: 30,
  33020. /**
  33021. * @cfg {Boolean} showInLegend
  33022. * Whether to add the gauge chart elements as legend items.
  33023. */
  33024. showInLegend: false,
  33025. /**
  33026. * @cfg {Number} value
  33027. * Directly sets the displayed value of the gauge.
  33028. * It is ignored if {@link #angleField} is provided.
  33029. */
  33030. value: null,
  33031. /**
  33032. * @cfg {Array} colors (required)
  33033. * An array of color values which is used for the needle and the `sectors`.
  33034. */
  33035. colors: null,
  33036. /**
  33037. * @cfg {Array} sectors
  33038. * Allows to paint sectors of different colors in the background of the gauge,
  33039. * with optional labels.
  33040. *
  33041. * It can be an array of numbers (each between `minimum` and `maximum`) that
  33042. * define the highest value of each sector. For N sectors, only (N-1) values are
  33043. * needed because it is assumed that the first sector starts at `minimum` and the
  33044. * last sector ends at `maximum`. Example: a water temperature gauge that is blue
  33045. * below 20C, red above 80C, gray in-between, and with an orange needle...
  33046. *
  33047. * minimum: 0,
  33048. * maximum: 100,
  33049. * sectors: [20, 80],
  33050. * colors: ['orange', 'blue', 'lightgray', 'red']
  33051. *
  33052. * It can be also an array of objects, each with the following properties:
  33053. *
  33054. * @cfg {Number} sectors.start The starting value of the sector. If omitted, it
  33055. * uses the previous sector's `end` value or the chart's `minimum`.
  33056. * @cfg {Number} sectors.end The ending value of the sector. If omitted, it uses
  33057. * the `maximum` defined for the chart.
  33058. * @cfg {String} sectors.label The label for this sector. Labels are styled using
  33059. * the series' {@link Ext.chart.series.Series#label label} config.
  33060. * @cfg {String} sectors.color The color of the sector. If omitted, it uses one
  33061. * of the `colors` defined for the series or for the chart.
  33062. * @cfg {Object} sectors.style An additional style object for the sector (for
  33063. * instance to set the opacity or to draw a line of a different color around the
  33064. * sector).
  33065. *
  33066. * minimum: 0,
  33067. * maximum: 100,
  33068. * sectors: [{
  33069. * end: 20,
  33070. * label: 'Cold',
  33071. * color: 'aqua'
  33072. * },
  33073. * {
  33074. * end: 80,
  33075. * label: 'Temp.',
  33076. * color: 'lightgray',
  33077. * style: { strokeStyle:'black', strokeOpacity:1, lineWidth:1 }
  33078. * },
  33079. * {
  33080. * label: 'Hot',
  33081. * color: 'tomato'
  33082. * }]
  33083. */
  33084. sectors: null,
  33085. /**
  33086. * @cfg {Number} minimum
  33087. * The minimum value of the gauge.
  33088. */
  33089. minimum: 0,
  33090. /**
  33091. * @cfg {Number} maximum
  33092. * The maximum value of the gauge.
  33093. */
  33094. maximum: 100,
  33095. rotation: 0,
  33096. /**
  33097. * @cfg {Number} totalAngle
  33098. * The size of the sector that the series will occupy.
  33099. */
  33100. totalAngle: Math.PI / 2,
  33101. rect: [
  33102. 0,
  33103. 0,
  33104. 1,
  33105. 1
  33106. ],
  33107. center: [
  33108. 0.5,
  33109. 0.75
  33110. ],
  33111. radius: 0.5,
  33112. /**
  33113. * @cfg {Boolean} wholeDisk Indicates whether to show the whole disk or only the marked part.
  33114. */
  33115. wholeDisk: false
  33116. },
  33117. coordinateX: function() {
  33118. return this.coordinate('X', 0, 2);
  33119. },
  33120. coordinateY: function() {
  33121. return this.coordinate('Y', 1, 2);
  33122. },
  33123. updateNeedle: function(needle) {
  33124. var me = this,
  33125. sprites = me.getSprites(),
  33126. angle = me.valueToAngle(me.getValue());
  33127. if (sprites && sprites.length) {
  33128. sprites[0].setAttributes({
  33129. startAngle: (needle ? angle : 0),
  33130. endAngle: angle,
  33131. strokeOpacity: (needle ? 1 : 0),
  33132. lineWidth: (needle ? me.getNeedleWidth() : 0)
  33133. });
  33134. me.doUpdateStyles();
  33135. }
  33136. },
  33137. themeColorCount: function() {
  33138. var me = this,
  33139. store = me.getStore(),
  33140. count = store && store.getCount() || 0;
  33141. return count + (me.getNeedle() ? 0 : 1);
  33142. },
  33143. updateColors: function(colors, oldColors) {
  33144. var me = this,
  33145. sectors = me.getSectors(),
  33146. sectorCount = sectors && sectors.length,
  33147. sprites = me.getSprites(),
  33148. newColors = Ext.Array.clone(colors),
  33149. colorCount = colors && colors.length,
  33150. i;
  33151. if (!colorCount || !colors[0]) {
  33152. return;
  33153. }
  33154. // Make sure the 'sectors' colors are not overridden.
  33155. for (i = 0; i < sectorCount; i++) {
  33156. newColors[i + 1] = sectors[i].color || newColors[i + 1] || colors[i % colorCount];
  33157. }
  33158. if (sprites.length) {
  33159. sprites[0].setAttributes({
  33160. strokeStyle: newColors[0]
  33161. });
  33162. }
  33163. this.setSubStyle({
  33164. fillStyle: newColors,
  33165. strokeStyle: newColors
  33166. });
  33167. this.doUpdateStyles();
  33168. },
  33169. updateRect: function(rect) {
  33170. var wholeDisk = this.getWholeDisk(),
  33171. halfTotalAngle = wholeDisk ? Math.PI : this.getTotalAngle() / 2,
  33172. donut = this.getDonut() / 100,
  33173. width, height, radius;
  33174. if (halfTotalAngle <= Math.PI / 2) {
  33175. width = 2 * Math.sin(halfTotalAngle);
  33176. height = 1 - donut * Math.cos(halfTotalAngle);
  33177. } else {
  33178. width = 2;
  33179. height = 1 - Math.cos(halfTotalAngle);
  33180. }
  33181. radius = Math.min(rect[2] / width, rect[3] / height);
  33182. this.setRadius(radius);
  33183. this.setCenter([
  33184. rect[2] / 2,
  33185. radius + (rect[3] - height * radius) / 2
  33186. ]);
  33187. },
  33188. updateCenter: function(center) {
  33189. this.setStyle({
  33190. centerX: center[0],
  33191. centerY: center[1],
  33192. rotationCenterX: center[0],
  33193. rotationCenterY: center[1]
  33194. });
  33195. this.doUpdateStyles();
  33196. },
  33197. updateRotation: function(rotation) {
  33198. this.setStyle({
  33199. rotationRads: rotation - (this.getTotalAngle() + Math.PI) / 2
  33200. });
  33201. this.doUpdateStyles();
  33202. },
  33203. doUpdateShape: function(radius, donut) {
  33204. var me = this,
  33205. sectors = me.getSectors(),
  33206. sectorCount = (sectors && sectors.length) || 0,
  33207. needleLength = me.getNeedleLength() / 100,
  33208. endRhoArray;
  33209. // Initialize an array that contains the endRho for each sprite.
  33210. // The first sprite is for the needle, the others for the gauge background sectors.
  33211. // Note: SubStyle arrays are handled in series.getStyleByIndex().
  33212. endRhoArray = [
  33213. radius * needleLength,
  33214. radius
  33215. ];
  33216. while (sectorCount--) {
  33217. endRhoArray.push(radius);
  33218. }
  33219. me.setSubStyle({
  33220. endRho: endRhoArray,
  33221. startRho: radius / 100 * donut
  33222. });
  33223. me.doUpdateStyles();
  33224. },
  33225. updateRadius: function(radius) {
  33226. var donut = this.getDonut();
  33227. this.doUpdateShape(radius, donut);
  33228. },
  33229. updateDonut: function(donut) {
  33230. var radius = this.getRadius();
  33231. this.doUpdateShape(radius, donut);
  33232. },
  33233. valueToAngle: function(value) {
  33234. value = this.applyValue(value);
  33235. return this.getTotalAngle() * (value - this.getMinimum()) / (this.getMaximum() - this.getMinimum());
  33236. },
  33237. applyValue: function(value) {
  33238. return Math.min(this.getMaximum(), Math.max(value, this.getMinimum()));
  33239. },
  33240. updateValue: function(value) {
  33241. var me = this,
  33242. needle = me.getNeedle(),
  33243. angle = me.valueToAngle(value),
  33244. sprites = me.getSprites();
  33245. sprites[0].getRendererData().value = value;
  33246. sprites[0].setAttributes({
  33247. startAngle: (needle ? angle : 0),
  33248. endAngle: angle
  33249. });
  33250. me.doUpdateStyles();
  33251. },
  33252. processData: function() {
  33253. var me = this,
  33254. store = me.getStore(),
  33255. record = store && store.first(),
  33256. animation, duration, axis, min, max, xField, value;
  33257. if (record) {
  33258. xField = me.getXField();
  33259. if (xField) {
  33260. value = record.get(xField);
  33261. }
  33262. }
  33263. if (axis = me.getXAxis()) {
  33264. min = axis.getMinimum();
  33265. max = axis.getMaximum();
  33266. // Animating the axis here can lead to weird looking results.
  33267. animation = axis.getSprites()[0].getAnimation();
  33268. duration = animation.getDuration();
  33269. animation.setDuration(0);
  33270. if (Ext.isNumber(min)) {
  33271. me.setMinimum(min);
  33272. } else {
  33273. axis.setMinimum(me.getMinimum());
  33274. }
  33275. if (Ext.isNumber(max)) {
  33276. me.setMaximum(max);
  33277. } else {
  33278. axis.setMaximum(me.getMaximum());
  33279. }
  33280. animation.setDuration(duration);
  33281. }
  33282. if (!Ext.isNumber(value)) {
  33283. value = me.getMinimum();
  33284. }
  33285. me.setValue(value);
  33286. },
  33287. getDefaultSpriteConfig: function() {
  33288. return {
  33289. type: this.seriesType,
  33290. renderer: this.getRenderer(),
  33291. animation: {
  33292. customDurations: {
  33293. translationX: 0,
  33294. translationY: 0,
  33295. rotationCenterX: 0,
  33296. rotationCenterY: 0,
  33297. centerX: 0,
  33298. centerY: 0,
  33299. startRho: 0,
  33300. endRho: 0,
  33301. baseRotation: 0
  33302. }
  33303. }
  33304. };
  33305. },
  33306. normalizeSectors: function(sectors) {
  33307. // Make sure all the sectors in the array have a legit start and end.
  33308. // Note: the array is modified in-place.
  33309. var me = this,
  33310. sectorCount = (sectors && sectors.length) || 0,
  33311. i, value, start, end;
  33312. if (sectorCount) {
  33313. for (i = 0; i < sectorCount; i++) {
  33314. value = sectors[i];
  33315. if (typeof value === 'number') {
  33316. sectors[i] = {
  33317. start: (i > 0 ? sectors[i - 1].end : me.getMinimum()),
  33318. end: Math.min(value, me.getMaximum())
  33319. };
  33320. if (i == (sectorCount - 1) && sectors[i].end < me.getMaximum()) {
  33321. sectors[i + 1] = {
  33322. start: sectors[i].end,
  33323. end: me.getMaximum()
  33324. };
  33325. }
  33326. } else {
  33327. if (typeof value.start === 'number') {
  33328. start = Math.max(value.start, me.getMinimum());
  33329. } else {
  33330. start = (i > 0 ? sectors[i - 1].end : me.getMinimum());
  33331. }
  33332. if (typeof value.end === 'number') {
  33333. end = Math.min(value.end, me.getMaximum());
  33334. } else {
  33335. end = me.getMaximum();
  33336. }
  33337. sectors[i].start = start;
  33338. sectors[i].end = end;
  33339. }
  33340. }
  33341. } else {
  33342. sectors = [
  33343. {
  33344. start: me.getMinimum(),
  33345. end: me.getMaximum()
  33346. }
  33347. ];
  33348. }
  33349. return sectors;
  33350. },
  33351. getSprites: function() {
  33352. var me = this,
  33353. store = me.getStore(),
  33354. value = me.getValue(),
  33355. label = me.getLabel(),
  33356. i, ln;
  33357. // The store must be initialized, or the value must be set
  33358. if (!store && !Ext.isNumber(value)) {
  33359. return Ext.emptyArray;
  33360. }
  33361. // Return cached sprites
  33362. var chart = me.getChart(),
  33363. animation = me.getAnimation() || chart && chart.getAnimation(),
  33364. sprites = me.sprites,
  33365. spriteIndex = 0,
  33366. sprite, sectors, attr, rendererData,
  33367. lineWidths = [];
  33368. // Hack to avoid having the lineWidths overwritten by the one specified in the theme.
  33369. // In fact, all the style properties from the needle and sectors should go to the series subStyle.
  33370. if (sprites && sprites.length) {
  33371. sprites[0].setAnimation(animation);
  33372. return sprites;
  33373. }
  33374. rendererData = {
  33375. store: store,
  33376. field: me.getXField(),
  33377. // for backward compatibility only (deprecated in 5.5)
  33378. angleField: me.getXField(),
  33379. value: value,
  33380. series: me
  33381. };
  33382. // Create needle sprite
  33383. me.needleSprite = sprite = me.createSprite();
  33384. sprite.setAttributes({
  33385. zIndex: 10
  33386. }, true);
  33387. sprite.setRendererData(rendererData);
  33388. sprite.setRendererIndex(spriteIndex++);
  33389. lineWidths.push(me.getNeedleWidth());
  33390. if (label) {
  33391. label.getTemplate().setField(true);
  33392. }
  33393. // Enable labels
  33394. // Create background sprite(s)
  33395. sectors = me.normalizeSectors(me.getSectors());
  33396. for (i = 0 , ln = sectors.length; i < ln; i++) {
  33397. attr = {
  33398. startAngle: me.valueToAngle(sectors[i].start),
  33399. endAngle: me.valueToAngle(sectors[i].end),
  33400. label: sectors[i].label,
  33401. fillStyle: sectors[i].color,
  33402. strokeOpacity: 0,
  33403. doCallout: false,
  33404. // Show labels inside sectors.
  33405. labelOverflowPadding: -1
  33406. };
  33407. // Allow labels to overlap.
  33408. Ext.apply(attr, sectors[i].style);
  33409. sprite = me.createSprite();
  33410. sprite.setRendererData(rendererData);
  33411. sprite.setRendererIndex(spriteIndex++);
  33412. sprite.setAttributes(attr, true);
  33413. lineWidths.push(attr.lineWidth);
  33414. }
  33415. me.setSubStyle({
  33416. lineWidth: lineWidths
  33417. });
  33418. me.doUpdateStyles();
  33419. return sprites;
  33420. },
  33421. doUpdateStyles: function() {
  33422. var me = this;
  33423. me.callParent();
  33424. if (me.sprites.length) {
  33425. me.needleSprite.setAttributes({
  33426. startRho: me.getNeedle() ? 0 : (me.getRadius() / 100 * me.getDonut())
  33427. });
  33428. }
  33429. }
  33430. });
  33431. /**
  33432. * @class Ext.chart.series.sprite.Line
  33433. * @extends Ext.chart.series.sprite.Aggregative
  33434. *
  33435. * Line series sprite.
  33436. */
  33437. Ext.define('Ext.chart.series.sprite.Line', {
  33438. alias: 'sprite.lineSeries',
  33439. extend: 'Ext.chart.series.sprite.Aggregative',
  33440. inheritableStatics: {
  33441. def: {
  33442. processors: {
  33443. /**
  33444. * @cfg {Object} [curve={type: 'linear'}]
  33445. * The type of curve that connects the data points.
  33446. *
  33447. * For example:
  33448. *
  33449. * // The data points are connected by line segments.
  33450. * // This is the default setting.
  33451. * curve: {
  33452. * type: 'linear'
  33453. * }
  33454. *
  33455. * // Cardinal spline interpolation is used to produce the curve
  33456. * // that connects the data points. The `tension` parameter can
  33457. * // be used to control the smoothness of the curve. A tension
  33458. * // of 0 corresponds to infinite tension, which results in straight
  33459. * // lines between data points. A tension of 1 corresponds to
  33460. * // no tension, allowing the spline to take the path of least
  33461. * // total bend. With tension values greater than 1, the curve
  33462. * // behaves like a compressed spring, pushed to take a longer path.
  33463. * // A cardinal spline with a tension of 0.5 is a special case.
  33464. * // It is then called a Catmull-Rom spline. Catmull-Rom splines are
  33465. * // thought to be esthetically pleasing and are quite common.
  33466. * // Note: spline interpolation only works on gapless data.
  33467. * curve: {
  33468. * type: 'cardinal,
  33469. * tension: 0.5
  33470. * }
  33471. *
  33472. * // Produces a natural cubic spline with the second derivative
  33473. * // of the spline set to zero at the endpoints.
  33474. * curve: {
  33475. * type: 'natural'
  33476. * }
  33477. *
  33478. * // The data points are connected by alternating horizontal and
  33479. * // vertical lines. The y-value changes after the x-value.
  33480. * curve: {
  33481. * type: 'step-after'
  33482. * }
  33483. *
  33484. */
  33485. curve: 'default',
  33486. /**
  33487. * @cfg {Boolean} [fillArea=false]
  33488. * `true` if the sprite paints the area underneath the line.
  33489. */
  33490. fillArea: 'bool',
  33491. /**
  33492. * @cfg {"gap"/"connect"/"origin"} [nullStyle="gap"]
  33493. * Possible values:
  33494. * 'gap' - null points are rendered as gaps.
  33495. * 'connect' - non-null points are connected across null points, so that
  33496. * there is no gap, unless null points are at the beginning/end of the line.
  33497. * Only the visible data points are connected - if a visible data point
  33498. * is followed by a series of null points that go off screen and eventually
  33499. * terminate with a non-null point, the connection won't be made.
  33500. * 'origin' - null data points are rendered at the origin,
  33501. * which is the y-coordinate of a point where the x and y axes meet.
  33502. * This requires that at least the x-coordinate of a point is a valid value.
  33503. */
  33504. nullStyle: 'enums(gap,connect,origin)',
  33505. /**
  33506. * @cfg {Boolean} [preciseStroke=true]
  33507. * `true` if the line uses precise stroke.
  33508. */
  33509. preciseStroke: 'bool',
  33510. /**
  33511. * @private
  33512. * The x-axis associated with the Line series.
  33513. * We need to know the position of the x-axis to fill the area underneath
  33514. * the stroke properly.
  33515. */
  33516. xAxis: 'default',
  33517. /**
  33518. * @cfg {Number} [yCap=Math.pow(2, 20)]
  33519. * Absolute maximum y-value.
  33520. * Larger values will be capped to avoid rendering issues.
  33521. */
  33522. yCap: 'default'
  33523. },
  33524. // The 'default' processor is used here as we don't want this attribute to animate.
  33525. defaults: {
  33526. curve: {
  33527. type: 'linear'
  33528. },
  33529. nullStyle: 'connect',
  33530. fillArea: false,
  33531. preciseStroke: true,
  33532. xAxis: null,
  33533. yCap: Math.pow(2, 20),
  33534. yJump: 50
  33535. },
  33536. triggers: {
  33537. dataX: 'dataX,bbox,curve',
  33538. dataY: 'dataY,bbox,curve',
  33539. curve: 'curve'
  33540. },
  33541. updaters: {
  33542. curve: 'curveUpdater'
  33543. }
  33544. }
  33545. },
  33546. list: null,
  33547. curveUpdater: function(attr) {
  33548. var me = this,
  33549. dataX = attr.dataX,
  33550. dataY = attr.dataY,
  33551. curve = attr.curve,
  33552. smoothable = dataX && dataY && dataX.length > 2 && dataY.length > 2,
  33553. type = curve.type;
  33554. if (smoothable) {
  33555. if (type === 'natural') {
  33556. me.smoothX = Ext.draw.Draw.naturalSpline(dataX);
  33557. me.smoothY = Ext.draw.Draw.naturalSpline(dataY);
  33558. } else if (type === 'cardinal') {
  33559. me.smoothX = Ext.draw.Draw.cardinalSpline(dataX, curve.tension);
  33560. me.smoothY = Ext.draw.Draw.cardinalSpline(dataY, curve.tension);
  33561. } else {
  33562. smoothable = false;
  33563. }
  33564. }
  33565. if (!smoothable) {
  33566. delete me.smoothX;
  33567. delete me.smoothY;
  33568. }
  33569. },
  33570. updatePlainBBox: function(plain) {
  33571. var attr = this.attr,
  33572. ymin = Math.min(0, attr.dataMinY),
  33573. ymax = Math.max(0, attr.dataMaxY);
  33574. plain.x = attr.dataMinX;
  33575. plain.y = ymin;
  33576. plain.width = attr.dataMaxX - attr.dataMinX;
  33577. plain.height = ymax - ymin;
  33578. },
  33579. drawStrip: function(ctx, strip) {
  33580. ctx.moveTo(strip[0], strip[1]);
  33581. for (var i = 2,
  33582. ln = strip.length; i < ln; i += 2) {
  33583. ctx.lineTo(strip[i], strip[i + 1]);
  33584. }
  33585. },
  33586. drawStraightStroke: function(surface, ctx, start, end, list, xAxis) {
  33587. var me = this,
  33588. attr = me.attr,
  33589. nullStyle = attr.nullStyle,
  33590. isConnect = nullStyle === 'connect',
  33591. isOrigin = nullStyle === 'origin',
  33592. renderer = attr.renderer,
  33593. curve = attr.curve,
  33594. step = curve.type === 'step-after',
  33595. needMoveTo = true,
  33596. ln = list.length,
  33597. lineConfig = {
  33598. type: 'line',
  33599. smooth: false,
  33600. step: step
  33601. };
  33602. var rendererChanges, params, stripStartX, isValidX0, isValidX, isValidX1, isValidPoint0, isValidPoint, isValidPoint1, isGap, lastValidPoint, px, py, x, y, x0, y0, x1, y1, i;
  33603. // 'strip' stores last continuous segment of the stroke,
  33604. // which we may need to re-build, if there's a fill as well.
  33605. // For example, if the renderer returned a style that needs
  33606. // to be applied to the current step, or we reached a null
  33607. // point in the data, where we have to fill the current continuous
  33608. // segment, we build and close a path that will be filled, then
  33609. // re-build the stroke path, using coordinates saved in the 'strip',
  33610. // and render the stroke on top of the fill.
  33611. var strip = [];
  33612. ctx.beginPath();
  33613. for (i = 3; i < ln; i += 3) {
  33614. x0 = list[i - 3];
  33615. y0 = list[i - 2];
  33616. x = list[i];
  33617. y = list[i + 1];
  33618. x1 = list[i + 3];
  33619. y1 = list[i + 4];
  33620. isValidX0 = Ext.isNumber(x0);
  33621. isValidX = Ext.isNumber(x);
  33622. isValidX1 = Ext.isNumber(x1);
  33623. isValidPoint0 = isValidX0 && Ext.isNumber(y0);
  33624. isValidPoint = isValidX && Ext.isNumber(y);
  33625. isValidPoint1 = isValidX1 && Ext.isNumber(y1);
  33626. if (isOrigin) {
  33627. // If only the y-component isn't a valid number,
  33628. // we can 'fix' it by setting it to value of y-origin.
  33629. if (!isValidPoint0 && isValidX0) {
  33630. y0 = xAxis;
  33631. isValidPoint0 = true;
  33632. }
  33633. if (!isValidPoint && isValidX) {
  33634. y = xAxis;
  33635. isValidPoint = true;
  33636. }
  33637. if (!isValidPoint1 && isValidX1) {
  33638. y1 = xAxis;
  33639. isValidPoint1 = true;
  33640. }
  33641. }
  33642. if (renderer) {
  33643. lineConfig.x = x;
  33644. lineConfig.y = y;
  33645. lineConfig.x0 = x0;
  33646. lineConfig.y0 = y0;
  33647. params = [
  33648. me,
  33649. lineConfig,
  33650. me.rendererData,
  33651. start + i / 3
  33652. ];
  33653. // callback(fn, scope, args, delay, caller)
  33654. rendererChanges = Ext.callback(renderer, null, params, 0, me.getSeries());
  33655. }
  33656. if (isGap && isConnect && isValidPoint0 && lastValidPoint) {
  33657. px = lastValidPoint[0];
  33658. py = lastValidPoint[1];
  33659. if (needMoveTo) {
  33660. ctx.beginPath();
  33661. ctx.moveTo(px, py);
  33662. strip.push(px, py);
  33663. stripStartX = px;
  33664. needMoveTo = false;
  33665. }
  33666. if (step) {
  33667. ctx.lineTo(x0, py);
  33668. strip.push(x0, py);
  33669. }
  33670. ctx.lineTo(x0, y0);
  33671. strip.push(x0, y0);
  33672. lastValidPoint = [
  33673. x0,
  33674. y0
  33675. ];
  33676. isGap = false;
  33677. }
  33678. // Special case where we have an uninterrupted segment, followed
  33679. // by a gap, then a valid point, then another gap. The uninterrupted
  33680. // segment should be connenected with the dot situated between the gaps.
  33681. if (isConnect && lastValidPoint && isValidPoint && !isValidPoint0) {
  33682. x0 = lastValidPoint[0];
  33683. y0 = lastValidPoint[1];
  33684. isValidPoint0 = true;
  33685. }
  33686. // Remember last valid point to connect the gap
  33687. // when the next valid point is encountered.
  33688. if (isValidPoint) {
  33689. lastValidPoint = [
  33690. x,
  33691. y
  33692. ];
  33693. }
  33694. if (isValidPoint0 && isValidPoint) {
  33695. if (needMoveTo) {
  33696. ctx.beginPath();
  33697. ctx.moveTo(x0, y0);
  33698. strip.push(x0, y0);
  33699. stripStartX = x0;
  33700. needMoveTo = false;
  33701. }
  33702. } else {
  33703. isGap = true;
  33704. continue;
  33705. }
  33706. if (step) {
  33707. ctx.lineTo(x, y0);
  33708. strip.push(x, y0);
  33709. }
  33710. ctx.lineTo(x, y);
  33711. strip.push(x, y);
  33712. // If the next point is a gap, then we need to fill what
  33713. // has been already rendered so far. The same applies
  33714. // if the renderer returned some changes to apply to
  33715. // the current step.
  33716. if (rendererChanges || !isValidPoint1) {
  33717. ctx.save();
  33718. Ext.apply(ctx, rendererChanges);
  33719. rendererChanges = null;
  33720. if (attr.fillArea) {
  33721. ctx.lineTo(x, xAxis);
  33722. ctx.lineTo(stripStartX, xAxis);
  33723. ctx.closePath();
  33724. ctx.fill();
  33725. }
  33726. // Draw the line on top of the filled area.
  33727. ctx.beginPath();
  33728. me.drawStrip(ctx, strip);
  33729. strip = [];
  33730. ctx.stroke();
  33731. ctx.restore();
  33732. ctx.beginPath();
  33733. // Take note that the starting point of a path has been reset
  33734. // (as a result of filling a sub-path) and needs to be set again
  33735. // for the line to continue in a proper manner.
  33736. needMoveTo = true;
  33737. }
  33738. }
  33739. },
  33740. calculateScale: function(count, end) {
  33741. var power = 0,
  33742. n = count;
  33743. while (n < end && count > 0) {
  33744. power++;
  33745. n += count >> power;
  33746. }
  33747. return Math.pow(2, power > 0 ? power - 1 : power);
  33748. },
  33749. drawSmoothStroke: function(surface, ctx, start, end, list, xAxis) {
  33750. var me = this,
  33751. attr = me.attr,
  33752. step = attr.step,
  33753. matrix = attr.matrix,
  33754. renderer = attr.renderer,
  33755. xx = matrix.getXX(),
  33756. yy = matrix.getYY(),
  33757. dx = matrix.getDX(),
  33758. dy = matrix.getDY(),
  33759. smoothX = me.smoothX,
  33760. smoothY = me.smoothY,
  33761. scale = me.calculateScale(attr.dataX.length, end),
  33762. cx1, cy1, cx2, cy2, x, y, x0, y0, i, j, changes, params,
  33763. lineConfig = {
  33764. type: 'line',
  33765. smooth: true,
  33766. step: step
  33767. };
  33768. ctx.beginPath();
  33769. ctx.moveTo(smoothX[start * 3] * xx + dx, smoothY[start * 3] * yy + dy);
  33770. for (i = 0 , j = start * 3 + 1; i < list.length - 3; i += 3 , j += 3 * scale) {
  33771. cx1 = smoothX[j] * xx + dx;
  33772. cy1 = smoothY[j] * yy + dy;
  33773. cx2 = smoothX[j + 1] * xx + dx;
  33774. cy2 = smoothY[j + 1] * yy + dy;
  33775. x = surface.roundPixel(list[i + 3]);
  33776. y = list[i + 4];
  33777. x0 = surface.roundPixel(list[i]);
  33778. y0 = list[i + 1];
  33779. if (renderer) {
  33780. lineConfig.x0 = x0;
  33781. lineConfig.y0 = y0;
  33782. lineConfig.cx1 = cx1;
  33783. lineConfig.cy1 = cy1;
  33784. lineConfig.cx2 = cx2;
  33785. lineConfig.cy2 = cy2;
  33786. lineConfig.x = x;
  33787. lineConfig.y = y;
  33788. params = [
  33789. me,
  33790. lineConfig,
  33791. me.rendererData,
  33792. start + i / 3 + 1
  33793. ];
  33794. changes = Ext.callback(renderer, null, params, 0, me.getSeries());
  33795. ctx.save();
  33796. Ext.apply(ctx, changes);
  33797. }
  33798. if (attr.fillArea) {
  33799. ctx.moveTo(x0, y0);
  33800. ctx.bezierCurveTo(cx1, cy1, cx2, cy2, x, y);
  33801. ctx.lineTo(x, xAxis);
  33802. ctx.lineTo(x0, xAxis);
  33803. ctx.lineTo(x0, y0);
  33804. ctx.closePath();
  33805. ctx.fill();
  33806. ctx.beginPath();
  33807. }
  33808. // Draw the line on top of the filled area.
  33809. ctx.moveTo(x0, y0);
  33810. ctx.bezierCurveTo(cx1, cy1, cx2, cy2, x, y);
  33811. ctx.stroke();
  33812. ctx.moveTo(x0, y0);
  33813. ctx.closePath();
  33814. if (renderer) {
  33815. ctx.restore();
  33816. }
  33817. ctx.beginPath();
  33818. ctx.moveTo(x, y);
  33819. }
  33820. // Prevent the last visible segment from being stroked twice
  33821. // (second time by the ctx.fillStroke inside Path sprite 'render' method)
  33822. ctx.beginPath();
  33823. },
  33824. drawLabel: function(text, dataX, dataY, labelId, rect) {
  33825. var me = this,
  33826. attr = me.attr,
  33827. label = me.getMarker('labels'),
  33828. labelTpl = label.getTemplate(),
  33829. labelCfg = me.labelCfg || (me.labelCfg = {}),
  33830. surfaceMatrix = me.surfaceMatrix,
  33831. labelX, labelY,
  33832. labelOverflowPadding = attr.labelOverflowPadding,
  33833. halfHeight, labelBBox, changes, params, hasPendingChanges;
  33834. // The coordinates below (data point converted to surface coordinates)
  33835. // are just for the renderer to give it a notion of where the label will be positioned.
  33836. // The actual position of the label will be different
  33837. // (unless the renderer returns x/y coordinates in the changes object)
  33838. // and depend on several things including the size of the text,
  33839. // which has to be measured after the renderer call,
  33840. // since text can be modified by the renderer.
  33841. labelCfg.x = surfaceMatrix.x(dataX, dataY);
  33842. labelCfg.y = surfaceMatrix.y(dataX, dataY);
  33843. if (attr.flipXY) {
  33844. labelCfg.rotationRads = Math.PI * 0.5;
  33845. } else {
  33846. labelCfg.rotationRads = 0;
  33847. }
  33848. labelCfg.text = text;
  33849. if (labelTpl.attr.renderer) {
  33850. params = [
  33851. text,
  33852. label,
  33853. labelCfg,
  33854. me.rendererData,
  33855. labelId
  33856. ];
  33857. changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
  33858. if (typeof changes === 'string') {
  33859. labelCfg.text = changes;
  33860. } else if (typeof changes === 'object') {
  33861. if ('text' in changes) {
  33862. labelCfg.text = changes.text;
  33863. }
  33864. hasPendingChanges = true;
  33865. }
  33866. }
  33867. labelBBox = me.getMarkerBBox('labels', labelId, true);
  33868. if (!labelBBox) {
  33869. me.putMarker('labels', labelCfg, labelId);
  33870. labelBBox = me.getMarkerBBox('labels', labelId, true);
  33871. }
  33872. halfHeight = labelBBox.height / 2;
  33873. labelX = dataX;
  33874. switch (labelTpl.attr.display) {
  33875. case 'under':
  33876. labelY = dataY - halfHeight - labelOverflowPadding;
  33877. break;
  33878. case 'rotate':
  33879. labelX += labelOverflowPadding;
  33880. labelY = dataY - labelOverflowPadding;
  33881. labelCfg.rotationRads = -Math.PI / 4;
  33882. break;
  33883. default:
  33884. // 'over'
  33885. labelY = dataY + halfHeight + labelOverflowPadding;
  33886. }
  33887. labelCfg.x = surfaceMatrix.x(labelX, labelY);
  33888. labelCfg.y = surfaceMatrix.y(labelX, labelY);
  33889. if (hasPendingChanges) {
  33890. Ext.apply(labelCfg, changes);
  33891. }
  33892. me.putMarker('labels', labelCfg, labelId);
  33893. },
  33894. drawMarker: function(x, y, index) {
  33895. var me = this,
  33896. attr = me.attr,
  33897. renderer = attr.renderer,
  33898. surfaceMatrix = me.surfaceMatrix,
  33899. markerCfg = {},
  33900. changes, params;
  33901. if (renderer && me.getMarker('markers')) {
  33902. markerCfg.type = 'marker';
  33903. markerCfg.x = x;
  33904. markerCfg.y = y;
  33905. params = [
  33906. me,
  33907. markerCfg,
  33908. me.rendererData,
  33909. index
  33910. ];
  33911. changes = Ext.callback(renderer, null, params, 0, me.getSeries());
  33912. if (changes) {
  33913. Ext.apply(markerCfg, changes);
  33914. }
  33915. }
  33916. markerCfg.translationX = surfaceMatrix.x(x, y);
  33917. markerCfg.translationY = surfaceMatrix.y(x, y);
  33918. delete markerCfg.x;
  33919. delete markerCfg.y;
  33920. me.putMarker('markers', markerCfg, index, !renderer);
  33921. },
  33922. drawStroke: function(surface, ctx, start, end, list, xAxis) {
  33923. var me = this,
  33924. isSmooth = me.smoothX && me.smoothY;
  33925. if (isSmooth) {
  33926. me.drawSmoothStroke(surface, ctx, start, end, list, xAxis);
  33927. } else {
  33928. me.drawStraightStroke(surface, ctx, start, end, list, xAxis);
  33929. }
  33930. },
  33931. renderAggregates: function(aggregates, start, end, surface, ctx, clip, rect) {
  33932. var me = this,
  33933. attr = me.attr,
  33934. dataX = attr.dataX,
  33935. dataY = attr.dataY,
  33936. labels = attr.labels,
  33937. xAxis = attr.xAxis,
  33938. yCap = attr.yCap,
  33939. isSmooth = attr.smooth && me.smoothX && me.smoothY,
  33940. isDrawLabels = labels && me.getMarker('labels'),
  33941. isDrawMarkers = me.getMarker('markers'),
  33942. matrix = attr.matrix,
  33943. pixel = surface.devicePixelRatio,
  33944. xx = matrix.getXX(),
  33945. yy = matrix.getYY(),
  33946. dx = matrix.getDX(),
  33947. dy = matrix.getDY(),
  33948. list = me.list || (me.list = []),
  33949. minXs = aggregates.minX,
  33950. maxXs = aggregates.maxX,
  33951. minYs = aggregates.minY,
  33952. maxYs = aggregates.maxY,
  33953. idx = aggregates.startIdx,
  33954. isContinuousLine = true,
  33955. isValidMinX, isValidMaxX, isValidMinY, isValidMaxY, xAxisOrigin, isVerticalX, x, y, i, index;
  33956. me.rendererData = {
  33957. store: me.getStore()
  33958. };
  33959. list.length = 0;
  33960. // Say we have 7 y-items (attr.dataY): [20, 19, 17, 15, 11, 10, 14]
  33961. // and 7 x-items (attr.dataX): [0, 1, 2, 3, 4, 5, 6].
  33962. // Then aggregates.startIdx is an aggregated index,
  33963. // where every other item is skipped on each aggregation level:
  33964. // [0, 1, 2, 3, 4, 5, 6,
  33965. // 0, 2, 4, 6,
  33966. // 0, 4,
  33967. // 0]
  33968. // aggregates.minY
  33969. // [20, 19, 17, 15, 11, 10, 14,
  33970. // 19, 15, 10, 14,
  33971. // 15, 10,
  33972. // 10]
  33973. // aggregates.maxY
  33974. // [20, 19, 17, 15, 11, 10, 14,
  33975. // 20, 17, 11, 14,
  33976. // 20, 14,
  33977. // 20]
  33978. // aggregates.minX is
  33979. // [0, 1, 2, 3, 4, 5, 6,
  33980. // 1, 3, 5, 6, // TODO: why this order for min?
  33981. // 3, 5, // TODO: why this inconsistency?
  33982. // 5]
  33983. // aggregates.maxX is
  33984. // [0, 1, 2, 3, 4, 5, 6,
  33985. // 0, 2, 4, 6,
  33986. // 0, 6,
  33987. // 0]
  33988. // Create a list of the form [x0, y0, idx0, x1, y1, idx1, ...],
  33989. // where each x,y pair is a coordinate representing original data point
  33990. // at the idx position.
  33991. for (i = start; i < end; i++) {
  33992. var minX = minXs[i],
  33993. maxX = maxXs[i],
  33994. minY = minYs[i],
  33995. maxY = maxYs[i];
  33996. isValidMinX = Ext.isNumber(minX);
  33997. isValidMinY = Ext.isNumber(minY);
  33998. isValidMaxX = Ext.isNumber(maxX);
  33999. isValidMaxY = Ext.isNumber(maxY);
  34000. if (minX < maxX) {
  34001. list.push(isValidMinX ? (minX * xx + dx) : null, isValidMinY ? (minY * yy + dy) : null, idx[i]);
  34002. list.push(isValidMaxX ? (maxX * xx + dx) : null, isValidMaxY ? (maxY * yy + dy) : null, idx[i]);
  34003. } else if (minX > maxX) {
  34004. list.push(isValidMaxX ? (maxX * xx + dx) : null, isValidMaxY ? (maxY * yy + dy) : null, idx[i]);
  34005. list.push(isValidMinX ? (minX * xx + dx) : null, isValidMinY ? (minY * yy + dy) : null, idx[i]);
  34006. } else {
  34007. list.push(isValidMaxX ? (maxX * xx + dx) : null, isValidMaxY ? (maxY * yy + dy) : null, idx[i]);
  34008. }
  34009. }
  34010. if (list.length) {
  34011. for (i = 0; i < list.length; i += 3) {
  34012. x = list[i];
  34013. y = list[i + 1];
  34014. if (Ext.isNumber(x) && Ext.isNumber(y)) {
  34015. if (y > yCap) {
  34016. y = yCap;
  34017. } else if (y < -yCap) {
  34018. y = -yCap;
  34019. }
  34020. list[i + 1] = y;
  34021. } else {
  34022. isContinuousLine = false;
  34023. continue;
  34024. }
  34025. index = list[i + 2];
  34026. if (isDrawMarkers) {
  34027. me.drawMarker(x, y, index);
  34028. }
  34029. if (isDrawLabels && labels[index]) {
  34030. me.drawLabel(labels[index], x, y, index, rect);
  34031. }
  34032. }
  34033. me.isContinuousLine = isContinuousLine;
  34034. if (isSmooth && !isContinuousLine) {
  34035. Ext.raise("Line smoothing in only supported for gapless data, " + "where all data points are finite numbers.");
  34036. }
  34037. if (xAxis) {
  34038. isVerticalX = xAxis.getAlignment() === 'vertical';
  34039. if (Ext.isNumber(xAxis.floatingAtCoord)) {
  34040. xAxisOrigin = (isVerticalX ? rect[2] : rect[3]) - xAxis.floatingAtCoord;
  34041. } else {
  34042. xAxisOrigin = isVerticalX ? rect[0] : rect[1];
  34043. }
  34044. } else {
  34045. xAxisOrigin = attr.flipXY ? rect[0] : rect[1];
  34046. }
  34047. if (attr.preciseStroke) {
  34048. if (attr.fillArea) {
  34049. ctx.fill();
  34050. }
  34051. if (attr.transformFillStroke) {
  34052. attr.inverseMatrix.toContext(ctx);
  34053. }
  34054. me.drawStroke(surface, ctx, start, end, list, xAxisOrigin);
  34055. if (attr.transformFillStroke) {
  34056. attr.matrix.toContext(ctx);
  34057. }
  34058. ctx.stroke();
  34059. } else {
  34060. me.drawStroke(surface, ctx, start, end, list, xAxisOrigin);
  34061. if (isContinuousLine && isSmooth && attr.fillArea && !attr.renderer) {
  34062. var lastPointX = dataX[dataX.length - 1] * xx + dx + pixel,
  34063. lastPointY = dataY[dataY.length - 1] * yy + dy,
  34064. firstPointX = dataX[0] * xx + dx - pixel,
  34065. firstPointY = dataY[0] * yy + dy;
  34066. // Fill the area from the series to the xAxis in case there
  34067. // are no gaps and no renderer is used, in which case the
  34068. // area would be filled per uninterrupted segment or per
  34069. // step, instead of being filled a single pass.
  34070. ctx.lineTo(lastPointX, lastPointY);
  34071. ctx.lineTo(lastPointX, xAxisOrigin - attr.lineWidth);
  34072. ctx.lineTo(firstPointX, xAxisOrigin - attr.lineWidth);
  34073. ctx.lineTo(firstPointX, firstPointY);
  34074. }
  34075. if (attr.transformFillStroke) {
  34076. attr.matrix.toContext(ctx);
  34077. }
  34078. // Prevent the reverse transform to fix floating point error.
  34079. if (attr.fillArea) {
  34080. ctx.fillStroke(attr, true);
  34081. } else {
  34082. ctx.stroke(true);
  34083. }
  34084. }
  34085. }
  34086. }
  34087. });
  34088. /**
  34089. * @class Ext.chart.series.Line
  34090. * @extends Ext.chart.series.Cartesian
  34091. *
  34092. * Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative information for different
  34093. * categories or other real values (as opposed to the bar chart), that can show some progression (or regression) in the dataset.
  34094. * As with all other series, the Line Series must be appended in the *series* Chart array configuration. See the Chart
  34095. * documentation for more information. A typical configuration object for the line series could be:
  34096. *
  34097. * @example
  34098. * Ext.create({
  34099. * xtype: 'cartesian',
  34100. * renderTo: document.body,
  34101. * width: 600,
  34102. * height: 400,
  34103. * insetPadding: 40,
  34104. * store: {
  34105. * fields: ['name', 'data1', 'data2'],
  34106. * data: [{
  34107. * 'name': 'metric one',
  34108. * 'data1': 10,
  34109. * 'data2': 14
  34110. * }, {
  34111. * 'name': 'metric two',
  34112. * 'data1': 7,
  34113. * 'data2': 16
  34114. * }, {
  34115. * 'name': 'metric three',
  34116. * 'data1': 5,
  34117. * 'data2': 14
  34118. * }, {
  34119. * 'name': 'metric four',
  34120. * 'data1': 2,
  34121. * 'data2': 6
  34122. * }, {
  34123. * 'name': 'metric five',
  34124. * 'data1': 27,
  34125. * 'data2': 36
  34126. * }]
  34127. * },
  34128. * axes: [{
  34129. * type: 'numeric',
  34130. * position: 'left',
  34131. * fields: ['data1'],
  34132. * title: {
  34133. * text: 'Sample Values',
  34134. * fontSize: 15
  34135. * },
  34136. * grid: true,
  34137. * minimum: 0
  34138. * }, {
  34139. * type: 'category',
  34140. * position: 'bottom',
  34141. * fields: ['name'],
  34142. * title: {
  34143. * text: 'Sample Values',
  34144. * fontSize: 15
  34145. * }
  34146. * }],
  34147. * series: [{
  34148. * type: 'line',
  34149. * style: {
  34150. * stroke: '#30BDA7',
  34151. * lineWidth: 2
  34152. * },
  34153. * xField: 'name',
  34154. * yField: 'data1',
  34155. * marker: {
  34156. * type: 'path',
  34157. * path: ['M', - 4, 0, 0, 4, 4, 0, 0, - 4, 'Z'],
  34158. * stroke: '#30BDA7',
  34159. * lineWidth: 2,
  34160. * fill: 'white'
  34161. * }
  34162. * }, {
  34163. * type: 'line',
  34164. * fill: true,
  34165. * style: {
  34166. * fill: '#96D4C6',
  34167. * fillOpacity: .6,
  34168. * stroke: '#0A3F50',
  34169. * strokeOpacity: .6,
  34170. * },
  34171. * xField: 'name',
  34172. * yField: 'data2',
  34173. * marker: {
  34174. * type: 'circle',
  34175. * radius: 4,
  34176. * lineWidth: 2,
  34177. * fill: 'white'
  34178. * }
  34179. * }]
  34180. * });
  34181. *
  34182. * In this configuration we're adding two series (or lines), one bound to the `data1`
  34183. * property of the store and the other to `data3`. The type for both configurations is
  34184. * `line`. The `xField` for both series is the same, the `name` property of the store.
  34185. * Both line series share the same axis, the left axis. You can set particular marker
  34186. * configuration by adding properties onto the marker object. Both series have
  34187. * an object as highlight so that markers animate smoothly to the properties in highlight
  34188. * when hovered. The second series has `fill = true` which means that the line will also
  34189. * have an area below it of the same color.
  34190. *
  34191. * **Note:** In the series definition remember to explicitly set the axis to bind the
  34192. * values of the line series to. This can be done by using the `axis` configuration property.
  34193. */
  34194. Ext.define('Ext.chart.series.Line', {
  34195. extend: 'Ext.chart.series.Cartesian',
  34196. alias: 'series.line',
  34197. type: 'line',
  34198. seriesType: 'lineSeries',
  34199. isLine: true,
  34200. requires: [
  34201. 'Ext.chart.series.sprite.Line'
  34202. ],
  34203. config: {
  34204. /**
  34205. * @cfg {Number} selectionTolerance
  34206. * The offset distance from the cursor position to the line series to trigger events (then used for highlighting series, etc).
  34207. */
  34208. selectionTolerance: 20,
  34209. /**
  34210. * @cfg {Object} curve
  34211. * The type of curve that connects the data points.
  34212. * Please see {@link Ext.chart.series.sprite.Line#curve line sprite documentation}
  34213. * for the full description.
  34214. */
  34215. curve: {
  34216. type: 'linear'
  34217. },
  34218. /**
  34219. * @cfg {Object} style
  34220. * An object containing styles for the visualization lines. These styles will override the theme styles.
  34221. * Some options contained within the style object will are described next.
  34222. */
  34223. /**
  34224. * @cfg {Boolean} smooth
  34225. * `true` if the series' line should be smoothed.
  34226. * Line smoothing only works with gapless data.
  34227. * @deprecated 6.5.0 Use the {@link #curve} config instead.
  34228. */
  34229. smooth: null,
  34230. /**
  34231. * @cfg {Boolean} step
  34232. * If set to `true`, the line uses steps instead of straight lines to connect the dots.
  34233. * It is ignored if `smooth` is true.
  34234. * @deprecated 6.5.0 Use the {@link #curve} config instead.
  34235. */
  34236. step: null,
  34237. /**
  34238. * @cfg {"gap"/"connect"/"origin"} [nullStyle="gap"]
  34239. * Possible values:
  34240. * 'gap' - null points are rendered as gaps.
  34241. * 'connect' - non-null points are connected across null points, so that
  34242. * there is no gap, unless null points are at the beginning/end of the line.
  34243. * Only the visible data points are connected - if a visible data point
  34244. * is followed by a series of null points that go off screen and eventually
  34245. * terminate with a non-null point, the connection won't be made.
  34246. * 'origin' - null data points are rendered at the origin,
  34247. * which is the y-coordinate of a point where the x and y axes meet.
  34248. * This requires that at least the x-coordinate of a point is a valid value.
  34249. */
  34250. nullStyle: 'gap',
  34251. /**
  34252. * @cfg {Boolean} fill
  34253. * If set to `true`, the area underneath the line is filled with the color defined as follows, listed by priority:
  34254. * - The color that is configured for this series ({@link Ext.chart.series.Series#colors}).
  34255. * - The color that is configured for this chart ({@link Ext.chart.AbstractChart#colors}).
  34256. * - The fill color that is set in the {@link #style} config.
  34257. * - The stroke color that is set in the {@link #style} config, or the same color as the line.
  34258. *
  34259. * Note: Do not confuse `series.config.fill` (which is a boolean) with `series.style.fill' (which is an alias
  34260. * for the `fillStyle` property and contains a color). For compatibility with previous versions of the API,
  34261. * if `config.fill` is undefined but a `style.fill' color is provided, `config.fill` is considered true.
  34262. * So the default value below must be undefined, not false.
  34263. */
  34264. fill: undefined,
  34265. aggregator: {
  34266. strategy: 'double'
  34267. }
  34268. },
  34269. themeMarkerCount: function() {
  34270. return 1;
  34271. },
  34272. /**
  34273. * @private
  34274. * Override {@link Ext.chart.series.Series#getDefaultSpriteConfig}
  34275. */
  34276. getDefaultSpriteConfig: function() {
  34277. var me = this,
  34278. parentConfig = me.callParent(arguments),
  34279. style = Ext.apply({}, me.getStyle()),
  34280. styleWithTheme,
  34281. fillArea = false;
  34282. if (me.config.fill !== undefined) {
  34283. // If config.fill is present but there is no fillStyle, then use the
  34284. // strokeStyle to fill (and paint the area the same color as the line).
  34285. if (me.config.fill) {
  34286. fillArea = true;
  34287. if (style.fillStyle === undefined) {
  34288. if (style.strokeStyle === undefined) {
  34289. styleWithTheme = me.getStyleWithTheme();
  34290. style.fillStyle = styleWithTheme.fillStyle;
  34291. style.strokeStyle = styleWithTheme.strokeStyle;
  34292. } else {
  34293. style.fillStyle = style.strokeStyle;
  34294. }
  34295. }
  34296. }
  34297. } else {
  34298. // For compatibility with previous versions of the API, if config.fill
  34299. // is undefined but style.fillStyle is provided, we fill the area.
  34300. if (style.fillStyle) {
  34301. fillArea = true;
  34302. }
  34303. }
  34304. // If we don't fill, then delete the fillStyle because that's what is used by
  34305. // the Line sprite to fill below the line.
  34306. if (!fillArea) {
  34307. delete style.fillStyle;
  34308. }
  34309. style = Ext.apply(parentConfig || {}, style);
  34310. return Ext.apply(style, {
  34311. fillArea: fillArea,
  34312. selectionTolerance: me.config.selectionTolerance
  34313. });
  34314. },
  34315. updateFill: function(fill) {
  34316. this.withSprite(function(sprite) {
  34317. return sprite.setAttributes({
  34318. fillArea: fill
  34319. });
  34320. });
  34321. },
  34322. updateCurve: function(curve) {
  34323. this.withSprite(function(sprite) {
  34324. return sprite.setAttributes({
  34325. curve: curve
  34326. });
  34327. });
  34328. },
  34329. getCurve: function() {
  34330. return this.withSprite(function(sprite) {
  34331. return sprite.attr.curve;
  34332. });
  34333. },
  34334. updateNullStyle: function(nullStyle) {
  34335. this.withSprite(function(sprite) {
  34336. return sprite.setAttributes({
  34337. nullStyle: nullStyle
  34338. });
  34339. });
  34340. },
  34341. updateSmooth: function(smooth) {
  34342. this.setCurve({
  34343. type: smooth ? 'natural' : 'linear'
  34344. });
  34345. },
  34346. updateStep: function(step) {
  34347. this.setCurve({
  34348. type: step ? 'step-after' : 'linear'
  34349. });
  34350. }
  34351. });
  34352. /**
  34353. * @class Ext.chart.series.sprite.PieSlice
  34354. *
  34355. * Pie slice sprite.
  34356. */
  34357. Ext.define('Ext.chart.series.sprite.PieSlice', {
  34358. extend: 'Ext.draw.sprite.Sector',
  34359. mixins: {
  34360. markerHolder: 'Ext.chart.MarkerHolder'
  34361. },
  34362. alias: 'sprite.pieslice',
  34363. inheritableStatics: {
  34364. def: {
  34365. processors: {
  34366. /**
  34367. * @cfg {Boolean} [doCallout=true]
  34368. * 'true' if the pie series uses label callouts.
  34369. */
  34370. doCallout: 'bool',
  34371. /**
  34372. * @cfg {String} [label='']
  34373. * Label associated with the Pie sprite.
  34374. */
  34375. label: 'string',
  34376. // @deprecated Use series.label.orientation config instead.
  34377. // @since 5.0.1
  34378. rotateLabels: 'bool',
  34379. /**
  34380. * @cfg {Number} [labelOverflowPadding=10]
  34381. * Padding around labels to determine overlap.
  34382. * Any negative number allows the labels to overlap.
  34383. */
  34384. labelOverflowPadding: 'number',
  34385. renderer: 'default'
  34386. },
  34387. defaults: {
  34388. doCallout: true,
  34389. rotateLabels: true,
  34390. label: '',
  34391. labelOverflowPadding: 10,
  34392. renderer: null
  34393. }
  34394. }
  34395. },
  34396. config: {
  34397. /**
  34398. * @private
  34399. * @cfg {Object} rendererData The object that is passed to the renderer.
  34400. *
  34401. * For instance when the PieSlice sprite is used in a Gauge chart, the object
  34402. * contains the 'store' and 'angleField' properties, and the 'value' as well
  34403. * for that one PieSlice that is used to draw the needle of the Gauge.
  34404. */
  34405. rendererData: null,
  34406. rendererIndex: 0,
  34407. series: null
  34408. },
  34409. setGradientBBox: function(ctx, rect) {
  34410. var me = this,
  34411. attr = me.attr,
  34412. hasGradients = (attr.fillStyle && attr.fillStyle.isGradient) || (attr.strokeStyle && attr.strokeStyle.isGradient);
  34413. if (hasGradients && !attr.constrainGradients) {
  34414. var midAngle = me.getMidAngle(),
  34415. margin = attr.margin,
  34416. cx = attr.centerX,
  34417. cy = attr.centerY,
  34418. r = attr.endRho,
  34419. matrix = attr.matrix,
  34420. scaleX = matrix.getScaleX(),
  34421. scaleY = matrix.getScaleY(),
  34422. w = scaleX * r,
  34423. h = scaleY * r,
  34424. bbox = {
  34425. width: w + w,
  34426. height: h + h
  34427. };
  34428. if (margin) {
  34429. cx += margin * Math.cos(midAngle);
  34430. cy += margin * Math.sin(midAngle);
  34431. }
  34432. bbox.x = matrix.x(cx, cy) - w;
  34433. bbox.y = matrix.y(cx, cy) - h;
  34434. ctx.setGradientBBox(bbox);
  34435. } else {
  34436. me.callParent([
  34437. ctx,
  34438. rect
  34439. ]);
  34440. }
  34441. },
  34442. render: function(surface, ctx, rect) {
  34443. var me = this,
  34444. attr = me.attr,
  34445. itemCfg = {},
  34446. changes;
  34447. if (attr.renderer) {
  34448. itemCfg = {
  34449. type: 'sector',
  34450. centerX: attr.centerX,
  34451. centerY: attr.centerY,
  34452. margin: attr.margin,
  34453. startAngle: Math.min(attr.startAngle, attr.endAngle),
  34454. endAngle: Math.max(attr.startAngle, attr.endAngle),
  34455. startRho: Math.min(attr.startRho, attr.endRho),
  34456. endRho: Math.max(attr.startRho, attr.endRho)
  34457. };
  34458. changes = Ext.callback(attr.renderer, null, [
  34459. me,
  34460. itemCfg,
  34461. me.getRendererData(),
  34462. me.getRendererIndex()
  34463. ], 0, me.getSeries());
  34464. me.setAttributes(changes);
  34465. me.useAttributes(ctx, rect);
  34466. }
  34467. // Draw the sector
  34468. me.callParent([
  34469. surface,
  34470. ctx,
  34471. rect
  34472. ]);
  34473. // Draw the labels
  34474. if (attr.label && me.getMarker('labels')) {
  34475. me.placeLabel();
  34476. }
  34477. },
  34478. placeLabel: function() {
  34479. var me = this,
  34480. attr = me.attr,
  34481. attributeId = attr.attributeId,
  34482. startAngle = Math.min(attr.startAngle, attr.endAngle),
  34483. endAngle = Math.max(attr.startAngle, attr.endAngle),
  34484. midAngle = (startAngle + endAngle) * 0.5,
  34485. margin = attr.margin,
  34486. centerX = attr.centerX,
  34487. centerY = attr.centerY,
  34488. sinMidAngle = Math.sin(midAngle),
  34489. cosMidAngle = Math.cos(midAngle),
  34490. startRho = Math.min(attr.startRho, attr.endRho) + margin,
  34491. endRho = Math.max(attr.startRho, attr.endRho) + margin,
  34492. midRho = (startRho + endRho) * 0.5,
  34493. surfaceMatrix = me.surfaceMatrix,
  34494. labelCfg = me.labelCfg || (me.labelCfg = {}),
  34495. label = me.getMarker('labels'),
  34496. labelTpl = label.getTemplate(),
  34497. hideLessThan = labelTpl.getHideLessThan(),
  34498. calloutLine = labelTpl.getCalloutLine(),
  34499. labelBox, x, y, changes, params, calloutLineLength;
  34500. if (calloutLine) {
  34501. calloutLineLength = calloutLine.length || 40;
  34502. } else {
  34503. calloutLineLength = 0;
  34504. }
  34505. surfaceMatrix.appendMatrix(attr.matrix);
  34506. labelCfg.text = attr.label;
  34507. x = centerX + cosMidAngle * midRho;
  34508. y = centerY + sinMidAngle * midRho;
  34509. labelCfg.x = surfaceMatrix.x(x, y);
  34510. labelCfg.y = surfaceMatrix.y(x, y);
  34511. x = centerX + cosMidAngle * endRho;
  34512. y = centerY + sinMidAngle * endRho;
  34513. labelCfg.calloutStartX = surfaceMatrix.x(x, y);
  34514. labelCfg.calloutStartY = surfaceMatrix.y(x, y);
  34515. x = centerX + cosMidAngle * (endRho + calloutLineLength);
  34516. y = centerY + sinMidAngle * (endRho + calloutLineLength);
  34517. labelCfg.calloutPlaceX = surfaceMatrix.x(x, y);
  34518. labelCfg.calloutPlaceY = surfaceMatrix.y(x, y);
  34519. if (!attr.rotateLabels) {
  34520. labelCfg.rotationRads = 0;
  34521. //<debug>
  34522. Ext.log.warn("'series.style.rotateLabels' config is deprecated. " + "Use 'series.label.orientation' config instead.");
  34523. } else //</debug>
  34524. {
  34525. switch (labelTpl.attr.orientation) {
  34526. case 'horizontal':
  34527. labelCfg.rotationRads = midAngle + Math.atan2(surfaceMatrix.y(1, 0) - surfaceMatrix.y(0, 0), surfaceMatrix.x(1, 0) - surfaceMatrix.x(0, 0)) + Math.PI / 2;
  34528. break;
  34529. case 'vertical':
  34530. labelCfg.rotationRads = midAngle + Math.atan2(surfaceMatrix.y(1, 0) - surfaceMatrix.y(0, 0), surfaceMatrix.x(1, 0) - surfaceMatrix.x(0, 0));
  34531. break;
  34532. }
  34533. }
  34534. labelCfg.calloutColor = (calloutLine && calloutLine.color) || me.attr.fillStyle;
  34535. if (calloutLine) {
  34536. if (calloutLine.width) {
  34537. labelCfg.calloutWidth = calloutLine.width;
  34538. }
  34539. } else {
  34540. labelCfg.calloutColor = 'none';
  34541. }
  34542. labelCfg.globalAlpha = attr.globalAlpha * attr.fillOpacity;
  34543. // If a slice is empty, don't display the label.
  34544. // This behavior can be overridden by a renderer.
  34545. if (labelTpl.display !== 'none') {
  34546. labelCfg.hidden = (attr.startAngle == attr.endAngle);
  34547. }
  34548. if (labelTpl.attr.renderer) {
  34549. // Note: the labels are 'put' by the Ext.chart.series.Pie.updateLabelData, so we can
  34550. // be sure the label sprite instances will exist and can be accessed from the label
  34551. // renderer on first render. For example, with 'bar' series this isn't the case,
  34552. // so we make a check and create a label instance if necessary.
  34553. params = [
  34554. me.attr.label,
  34555. label,
  34556. labelCfg,
  34557. me.getRendererData(),
  34558. me.getRendererIndex()
  34559. ];
  34560. changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
  34561. if (typeof changes === 'string') {
  34562. labelCfg.text = changes;
  34563. } else {
  34564. Ext.apply(labelCfg, changes);
  34565. }
  34566. }
  34567. me.putMarker('labels', labelCfg, attributeId);
  34568. labelBox = me.getMarkerBBox('labels', attributeId, true);
  34569. if (labelBox) {
  34570. if (attr.doCallout && ((endAngle - startAngle) * endRho > hideLessThan || attr.highlighted)) {
  34571. if (labelTpl.attr.display === 'outside') {
  34572. me.putMarker('labels', {
  34573. callout: 1
  34574. }, attributeId);
  34575. } else if (labelTpl.attr.display === 'inside') {
  34576. me.putMarker('labels', {
  34577. callout: 0
  34578. }, attributeId);
  34579. } else {
  34580. me.putMarker('labels', {
  34581. callout: 1 - me.sliceContainsLabel(attr, labelBox)
  34582. }, attributeId);
  34583. }
  34584. } else {
  34585. me.putMarker('labels', {
  34586. globalAlpha: me.sliceContainsLabel(attr, labelBox)
  34587. }, attributeId);
  34588. }
  34589. }
  34590. },
  34591. sliceContainsLabel: function(attr, bbox) {
  34592. var padding = attr.labelOverflowPadding,
  34593. middle = (attr.endRho + attr.startRho) / 2,
  34594. outer = middle + (bbox.width + padding) / 2,
  34595. inner = middle - (bbox.width + padding) / 2,
  34596. sliceAngle, l1, l2, l3;
  34597. if (padding < 0) {
  34598. return 1;
  34599. }
  34600. if (bbox.width + padding * 2 > (attr.endRho - attr.startRho)) {
  34601. return 0;
  34602. }
  34603. l1 = Math.sqrt(attr.endRho * attr.endRho - outer * outer);
  34604. l2 = Math.sqrt(attr.endRho * attr.endRho - inner * inner);
  34605. sliceAngle = Math.abs(attr.endAngle - attr.startAngle);
  34606. l3 = (sliceAngle > Math.PI / 2 ? inner : Math.abs(Math.tan(sliceAngle / 2)) * inner);
  34607. if (bbox.height + padding * 2 > Math.min(l1, l2, l3) * 2) {
  34608. return 0;
  34609. }
  34610. return 1;
  34611. }
  34612. });
  34613. /**
  34614. * @class Ext.chart.series.Pie
  34615. * @extends Ext.chart.series.Polar
  34616. *
  34617. * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display
  34618. * quantitative information for different categories that also have a meaning as a whole.
  34619. * As with all other series, the Pie Series must be appended in the *series* Chart array
  34620. * configuration. See the Chart documentation for more information. A typical configuration
  34621. * object for the pie series could be:
  34622. *
  34623. * @example
  34624. * Ext.create({
  34625. * xtype: 'polar',
  34626. * renderTo: document.body,
  34627. * width: 400,
  34628. * height: 400,
  34629. * theme: 'green',
  34630. * interactions: ['rotate', 'itemhighlight'],
  34631. * store: {
  34632. * fields: ['name', 'data1'],
  34633. * data: [{
  34634. * name: 'metric one',
  34635. * data1: 14
  34636. * }, {
  34637. * name: 'metric two',
  34638. * data1: 16
  34639. * }, {
  34640. * name: 'metric three',
  34641. * data1: 14
  34642. * }, {
  34643. * name: 'metric four',
  34644. * data1: 6
  34645. * }, {
  34646. * name: 'metric five',
  34647. * data1: 36
  34648. * }]
  34649. * },
  34650. * series: {
  34651. * type: 'pie',
  34652. * highlight: true,
  34653. * angleField: 'data1',
  34654. * label: {
  34655. * field: 'name',
  34656. * display: 'rotate'
  34657. * },
  34658. * donut: 30
  34659. * }
  34660. * });
  34661. *
  34662. * In this configuration we set `pie` as the type for the series, then set the `highlight` config
  34663. * to `true` (we can also specify an object with specific style properties for highlighting options)
  34664. * which is triggered when hovering or tapping elements.
  34665. * We set `data1` as the value of the `angleField` to determine the angular span for each pie slice.
  34666. * We also set a label configuration object where we set the name of the store field
  34667. * to be rendered as text for the label. The labels will also be displayed rotated.
  34668. * And finally, we specify the donut hole radius for the pie series in percentages of the series radius.
  34669. *
  34670. */
  34671. Ext.define('Ext.chart.series.Pie', {
  34672. extend: 'Ext.chart.series.Polar',
  34673. requires: [
  34674. 'Ext.chart.series.sprite.PieSlice'
  34675. ],
  34676. type: 'pie',
  34677. alias: 'series.pie',
  34678. seriesType: 'pieslice',
  34679. isPie: true,
  34680. config: {
  34681. /**
  34682. * @cfg {String} radiusField
  34683. * The store record field name to be used for the pie slice lengths.
  34684. * The values bound to this field name must be positive real numbers.
  34685. */
  34686. /**
  34687. * @cfg {Number} donut Specifies the radius of the donut hole, as a percentage of the chart's radius.
  34688. * Defaults to 0 (no donut hole).
  34689. */
  34690. donut: 0,
  34691. /**
  34692. * @cfg {Number} rotation The starting angle of the pie slices.
  34693. */
  34694. rotation: 0,
  34695. /**
  34696. * @cfg {Boolean} clockwise
  34697. * Whether the pie slices are displayed clockwise. Default's true.
  34698. */
  34699. clockwise: true,
  34700. /**
  34701. * @cfg {Number} [totalAngle=2*PI] The total angle of the pie series.
  34702. */
  34703. totalAngle: 2 * Math.PI,
  34704. /**
  34705. * @cfg {Array} hidden Determines which pie slices are hidden.
  34706. */
  34707. hidden: [],
  34708. /**
  34709. * @cfg {Number} [radiusFactor=100] Allows adjustment of the radius by a specific percentage.
  34710. */
  34711. radiusFactor: 100,
  34712. /**
  34713. * @cfg {Ext.chart.series.sprite.PieSlice/Object} highlightCfg
  34714. * Default highlight config for the pie series.
  34715. * Slides highlighted pie sector outward by default.
  34716. *
  34717. * highlightCfg accepts as its value a config object (or array of configs) for a
  34718. * {@link Ext.chart.series.sprite.PieSlice pie sprite}.
  34719. *
  34720. *
  34721. * Example config:
  34722. *
  34723. * Ext.create('Ext.chart.PolarChart', {
  34724. * renderTo: document.body,
  34725. * width: 600,
  34726. * height: 400,
  34727. * innerPadding: 5,
  34728. * store: {
  34729. * fields: ['name', 'data1'],
  34730. * data: [{
  34731. * name: 'metric one',
  34732. * data1: 10
  34733. * }, {
  34734. * name: 'metric two',
  34735. * data1: 7
  34736. * }, {
  34737. * name: 'metric three',
  34738. * data1: 5
  34739. * }]
  34740. * },
  34741. * series: {
  34742. * type: 'pie',
  34743. * label: {
  34744. * field: 'name',
  34745. * display: 'rotate'
  34746. * },
  34747. * xField: 'data1',
  34748. * donut: 30,
  34749. * highlightCfg: {
  34750. * margin: 10,
  34751. * fillOpacity: .7
  34752. * }
  34753. * }
  34754. * });
  34755. */
  34756. highlightCfg: {
  34757. margin: 20
  34758. },
  34759. style: {}
  34760. },
  34761. directions: [
  34762. 'X'
  34763. ],
  34764. applyLabel: function(newLabel, oldLabel) {
  34765. if (Ext.isObject(newLabel) && !Ext.isString(newLabel.orientation)) {
  34766. // Override default label orientation from '' to 'vertical'.
  34767. Ext.apply(newLabel = Ext.Object.chain(newLabel), {
  34768. orientation: 'vertical'
  34769. });
  34770. }
  34771. return this.callParent([
  34772. newLabel,
  34773. oldLabel
  34774. ]);
  34775. },
  34776. updateLabelData: function() {
  34777. var me = this,
  34778. store = me.getStore(),
  34779. items = store.getData().items,
  34780. sprites = me.getSprites(),
  34781. label = me.getLabel(),
  34782. labelField = label && label.getTemplate().getField(),
  34783. hidden = me.getHidden(),
  34784. i, ln, labels, sprite;
  34785. if (sprites.length && labelField) {
  34786. labels = [];
  34787. for (i = 0 , ln = items.length; i < ln; i++) {
  34788. labels.push(items[i].get(labelField));
  34789. }
  34790. for (i = 0 , ln = sprites.length; i < ln; i++) {
  34791. sprite = sprites[i];
  34792. sprite.setAttributes({
  34793. label: labels[i]
  34794. });
  34795. sprite.putMarker('labels', {
  34796. hidden: hidden[i]
  34797. }, sprite.attr.attributeId);
  34798. }
  34799. }
  34800. },
  34801. coordinateX: function() {
  34802. var me = this,
  34803. store = me.getStore(),
  34804. records = store.getData().items,
  34805. recordCount = records.length,
  34806. xField = me.getXField(),
  34807. yField = me.getYField(),
  34808. x,
  34809. sumX = 0,
  34810. unit, y,
  34811. maxY = 0,
  34812. hidden = me.getHidden(),
  34813. summation = [],
  34814. i,
  34815. lastAngle = 0,
  34816. totalAngle = me.getTotalAngle(),
  34817. clockwise = me.getClockwise() ? 1 : -1,
  34818. sprites = me.getSprites(),
  34819. sprite, labels;
  34820. if (!sprites) {
  34821. return;
  34822. }
  34823. for (i = 0; i < recordCount; i++) {
  34824. x = Math.abs(Number(records[i].get(xField))) || 0;
  34825. y = yField && Math.abs(Number(records[i].get(yField))) || 0;
  34826. if (!hidden[i]) {
  34827. sumX += x;
  34828. if (y > maxY) {
  34829. maxY = y;
  34830. }
  34831. }
  34832. summation[i] = sumX;
  34833. if (i >= hidden.length) {
  34834. hidden[i] = false;
  34835. }
  34836. }
  34837. hidden.length = recordCount;
  34838. me.maxY = maxY;
  34839. if (sumX !== 0) {
  34840. unit = totalAngle / sumX;
  34841. }
  34842. for (i = 0; i < recordCount; i++) {
  34843. sprites[i].setAttributes({
  34844. startAngle: lastAngle,
  34845. endAngle: lastAngle = (unit ? clockwise * summation[i] * unit : 0),
  34846. globalAlpha: 1
  34847. });
  34848. }
  34849. if (recordCount < sprites.length) {
  34850. for (i = recordCount; i < sprites.length; i++) {
  34851. sprite = sprites[i];
  34852. labels = sprite.getMarker('labels');
  34853. if (labels) {
  34854. // Don't want the 'labels' Markers and its 'template' sprite to be destroyed
  34855. // with the PieSlice MarkerHolder, as it is also used by other pie slices.
  34856. // So we release 'labels' before destroying the PieSlice.
  34857. // But first, we have to clear the instances of the 'labels'
  34858. // Markers created by the PieSlice MarkerHolder.
  34859. labels.clear(sprite.getId());
  34860. sprite.releaseMarker('labels');
  34861. }
  34862. sprite.destroy();
  34863. }
  34864. sprites.length = recordCount;
  34865. }
  34866. for (i = recordCount; i < sprites.length; i++) {
  34867. sprites[i].setAttributes({
  34868. startAngle: totalAngle,
  34869. endAngle: totalAngle,
  34870. globalAlpha: 0
  34871. });
  34872. }
  34873. },
  34874. updateCenter: function(center) {
  34875. this.setStyle({
  34876. translationX: center[0] + this.getOffsetX(),
  34877. translationY: center[1] + this.getOffsetY()
  34878. });
  34879. this.doUpdateStyles();
  34880. },
  34881. updateRadius: function(radius) {
  34882. this.setStyle({
  34883. startRho: radius * this.getDonut() * 0.01,
  34884. endRho: radius * this.getRadiusFactor() * 0.01
  34885. });
  34886. this.doUpdateStyles();
  34887. },
  34888. getStyleByIndex: function(i) {
  34889. var me = this,
  34890. store = me.getStore(),
  34891. item = store.getAt(i),
  34892. yField = me.getYField(),
  34893. radius = me.getRadius(),
  34894. style = {},
  34895. startRho, endRho, y;
  34896. if (item) {
  34897. y = yField && Math.abs(Number(item.get(yField))) || 0;
  34898. startRho = radius * me.getDonut() * 0.01;
  34899. endRho = radius * me.getRadiusFactor() * 0.01;
  34900. style = me.callParent([
  34901. i
  34902. ]);
  34903. style.startRho = startRho;
  34904. style.endRho = me.maxY ? (startRho + (endRho - startRho) * y / me.maxY) : endRho;
  34905. }
  34906. return style;
  34907. },
  34908. updateDonut: function(donut) {
  34909. var radius = this.getRadius();
  34910. this.setStyle({
  34911. startRho: radius * donut * 0.01,
  34912. endRho: radius * this.getRadiusFactor() * 0.01
  34913. });
  34914. this.doUpdateStyles();
  34915. },
  34916. // Subtract 90 degrees from rotation, so that `rotation` config's default
  34917. // value of 0 makes first pie sector start at noon, rather than 3 o'clock.
  34918. rotationOffset: -Math.PI / 2,
  34919. updateRotation: function(rotation) {
  34920. this.setStyle({
  34921. rotationRads: rotation + this.rotationOffset
  34922. });
  34923. this.doUpdateStyles();
  34924. },
  34925. updateTotalAngle: function(totalAngle) {
  34926. this.processData();
  34927. },
  34928. getSprites: function() {
  34929. var me = this,
  34930. chart = me.getChart(),
  34931. store = me.getStore();
  34932. if (!chart || !store) {
  34933. return Ext.emptyArray;
  34934. }
  34935. me.getColors();
  34936. me.getSubStyle();
  34937. var items = store.getData().items,
  34938. length = items.length,
  34939. animation = me.getAnimation() || chart && chart.getAnimation(),
  34940. sprites = me.sprites,
  34941. sprite,
  34942. spriteCreated = false,
  34943. spriteIndex = 0,
  34944. label = me.getLabel(),
  34945. labelTpl = label && label.getTemplate(),
  34946. i, rendererData;
  34947. rendererData = {
  34948. store: store,
  34949. field: me.getXField(),
  34950. // for backward compatibility only (deprecated in 5.5)
  34951. angleField: me.getXField(),
  34952. radiusField: me.getYField(),
  34953. series: me
  34954. };
  34955. for (i = 0; i < length; i++) {
  34956. sprite = sprites[i];
  34957. if (!sprite) {
  34958. sprite = me.createSprite();
  34959. if (me.getHighlight()) {
  34960. sprite.config.highlight = me.getHighlight();
  34961. sprite.addModifier('highlight', true);
  34962. }
  34963. if (labelTpl && labelTpl.getField()) {
  34964. labelTpl.setAttributes({
  34965. labelOverflowPadding: me.getLabelOverflowPadding()
  34966. });
  34967. labelTpl.getAnimation().setCustomDurations({
  34968. 'callout': 200
  34969. });
  34970. }
  34971. sprite.setAttributes(me.getStyleByIndex(i));
  34972. sprite.setRendererData(rendererData);
  34973. spriteCreated = true;
  34974. }
  34975. sprite.setRendererIndex(spriteIndex++);
  34976. sprite.setAnimation(animation);
  34977. }
  34978. if (spriteCreated) {
  34979. me.doUpdateStyles();
  34980. }
  34981. return me.sprites;
  34982. },
  34983. betweenAngle: function(x, a, b) {
  34984. var pp = Math.PI * 2,
  34985. offset = this.rotationOffset;
  34986. if (a === b) {
  34987. return false;
  34988. }
  34989. if (!this.getClockwise()) {
  34990. x *= -1;
  34991. a *= -1;
  34992. b *= -1;
  34993. a -= offset;
  34994. b -= offset;
  34995. } else {
  34996. a += offset;
  34997. b += offset;
  34998. }
  34999. x -= a;
  35000. b -= a;
  35001. // Normalize, so that both x and b are in the [0,360) interval.
  35002. x %= pp;
  35003. b %= pp;
  35004. x += pp;
  35005. b += pp;
  35006. x %= pp;
  35007. b %= pp;
  35008. // Because 360 * n angles will be normalized to 0,
  35009. // we need to treat b ~= 0 as a special case.
  35010. return x < b || Ext.Number.isEqual(b, 0, 1.0E-8);
  35011. },
  35012. getItemByIndex: function(index, category) {
  35013. category = category || 'sprites';
  35014. return this.callParent([
  35015. index,
  35016. category
  35017. ]);
  35018. },
  35019. /**
  35020. * Returns the pie slice for a given angle
  35021. * @param {Number} angle The angle to search for the slice
  35022. * @return {Object} An object containing the reocord, sprite, scope etc.
  35023. */
  35024. getItemForAngle: function(angle) {
  35025. var me = this,
  35026. sprites = me.getSprites(),
  35027. attr;
  35028. angle %= Math.PI * 2;
  35029. while (angle < 0) {
  35030. angle += Math.PI * 2;
  35031. }
  35032. if (sprites) {
  35033. var store = me.getStore(),
  35034. items = store.getData().items,
  35035. hidden = me.getHidden(),
  35036. i = 0,
  35037. ln = store.getCount();
  35038. for (; i < ln; i++) {
  35039. if (!hidden[i]) {
  35040. // Fortunately, item's id equals its index in the instances list.
  35041. attr = sprites[i].attr;
  35042. if (attr.startAngle <= angle && attr.endAngle >= angle) {
  35043. return {
  35044. series: me,
  35045. sprite: sprites[i],
  35046. index: i,
  35047. record: items[i],
  35048. field: me.getXField()
  35049. };
  35050. }
  35051. }
  35052. }
  35053. }
  35054. return null;
  35055. },
  35056. getItemForPoint: function(x, y) {
  35057. var me = this,
  35058. sprites = me.getSprites(),
  35059. center = me.getCenter(),
  35060. offsetX = me.getOffsetX(),
  35061. offsetY = me.getOffsetY(),
  35062. // Distance from the center of the series to the cursor.
  35063. dx = x - center[0] + offsetX,
  35064. dy = y - center[1] + offsetY,
  35065. store = me.getStore(),
  35066. donut = me.getDonut(),
  35067. records = store.getData().items,
  35068. direction = Math.atan2(dy, dx) - me.getRotation(),
  35069. radius = Math.sqrt(dx * dx + dy * dy),
  35070. startRadius = me.getRadius() * donut * 0.01,
  35071. hidden = me.getHidden(),
  35072. result = null,
  35073. i, ln, attr, sprite;
  35074. for (i = 0 , ln = records.length; i < ln; i++) {
  35075. if (hidden[i]) {
  35076. continue;
  35077. }
  35078. sprite = sprites[i];
  35079. if (!sprite) {
  35080. break;
  35081. }
  35082. attr = sprite.attr;
  35083. if (radius >= startRadius + attr.margin && radius <= attr.endRho + attr.margin && me.betweenAngle(direction, attr.startAngle, attr.endAngle)) {
  35084. result = {
  35085. series: me,
  35086. sprite: sprites[i],
  35087. index: i,
  35088. record: records[i],
  35089. field: me.getXField()
  35090. };
  35091. break;
  35092. }
  35093. }
  35094. return result;
  35095. },
  35096. provideLegendInfo: function(target) {
  35097. var me = this,
  35098. store = me.getStore();
  35099. if (store) {
  35100. var items = store.getData().items,
  35101. labelField = me.getLabel().getTemplate().getField(),
  35102. xField = me.getXField(),
  35103. hidden = me.getHidden(),
  35104. i, style, fill;
  35105. for (i = 0; i < items.length; i++) {
  35106. style = me.getStyleByIndex(i);
  35107. fill = style.fillStyle;
  35108. if (Ext.isObject(fill)) {
  35109. fill = fill.stops && fill.stops[0].color;
  35110. }
  35111. target.push({
  35112. name: labelField ? String(items[i].get(labelField)) : xField + ' ' + i,
  35113. mark: fill || style.strokeStyle || 'black',
  35114. disabled: hidden[i],
  35115. series: me.getId(),
  35116. index: i
  35117. });
  35118. }
  35119. }
  35120. }
  35121. });
  35122. /**
  35123. * @class Ext.chart.series.sprite.Pie3DPart
  35124. * @extends Ext.draw.sprite.Path
  35125. *
  35126. * Pie3D series sprite.
  35127. */
  35128. Ext.define('Ext.chart.series.sprite.Pie3DPart', {
  35129. extend: 'Ext.draw.sprite.Path',
  35130. mixins: {
  35131. markerHolder: 'Ext.chart.MarkerHolder'
  35132. },
  35133. alias: 'sprite.pie3dPart',
  35134. inheritableStatics: {
  35135. def: {
  35136. processors: {
  35137. /**
  35138. * @cfg {Number} [centerX=0]
  35139. * The central point of the series on the x-axis.
  35140. */
  35141. centerX: 'number',
  35142. /**
  35143. * @cfg {Number} [centerY=0]
  35144. * The central point of the series on the x-axis.
  35145. */
  35146. centerY: 'number',
  35147. /**
  35148. * @cfg {Number} [startAngle=0]
  35149. * The starting angle of the polar series.
  35150. */
  35151. startAngle: 'number',
  35152. /**
  35153. * @cfg {Number} [endAngle=Math.PI]
  35154. * The ending angle of the polar series.
  35155. */
  35156. endAngle: 'number',
  35157. /**
  35158. * @cfg {Number} [startRho=0]
  35159. * The starting radius of the polar series.
  35160. */
  35161. startRho: 'number',
  35162. /**
  35163. * @cfg {Number} [endRho=150]
  35164. * The ending radius of the polar series.
  35165. */
  35166. endRho: 'number',
  35167. /**
  35168. * @cfg {Number} [margin=0]
  35169. * Margin from the center of the pie. Used for donut.
  35170. */
  35171. margin: 'number',
  35172. /**
  35173. * @cfg {Number} [thickness=0]
  35174. * The thickness of the 3D pie part.
  35175. */
  35176. thickness: 'number',
  35177. /**
  35178. * @cfg {Number} [bevelWidth=5]
  35179. * The size of the 3D pie bevel.
  35180. */
  35181. bevelWidth: 'number',
  35182. /**
  35183. * @cfg {Number} [distortion=0]
  35184. * The distortion of the 3D pie part.
  35185. */
  35186. distortion: 'number',
  35187. /**
  35188. * @cfg {Object} [baseColor='white']
  35189. * The color of the 3D pie part before adding the 3D effect.
  35190. */
  35191. baseColor: 'color',
  35192. /**
  35193. * @cfg {Number} [colorSpread=0.7]
  35194. * An attribute used to control how flat the gradient of the sprite looks.
  35195. * A value of 0 essentially means no gradient (flat color).
  35196. */
  35197. colorSpread: 'number',
  35198. /**
  35199. * @cfg {Number} [baseRotation=0]
  35200. * The starting rotation of the polar series.
  35201. */
  35202. baseRotation: 'number',
  35203. /**
  35204. * @cfg {String} [part='top']
  35205. * The part of the 3D Pie represented by the sprite.
  35206. */
  35207. part: 'enums(top,bottom,start,end,innerFront,innerBack,outerFront,outerBack)',
  35208. /**
  35209. * @cfg {String} [label='']
  35210. * The label associated with the 'top' part of the sprite.
  35211. */
  35212. label: 'string'
  35213. },
  35214. aliases: {
  35215. rho: 'endRho'
  35216. },
  35217. triggers: {
  35218. centerX: 'path,bbox',
  35219. centerY: 'path,bbox',
  35220. startAngle: 'path,partZIndex',
  35221. endAngle: 'path,partZIndex',
  35222. startRho: 'path',
  35223. endRho: 'path,bbox',
  35224. margin: 'path,bbox',
  35225. thickness: 'path',
  35226. distortion: 'path',
  35227. baseRotation: 'path,partZIndex',
  35228. baseColor: 'partZIndex,partColor',
  35229. colorSpread: 'partColor',
  35230. part: 'path,partZIndex',
  35231. globalAlpha: 'canvas,alpha',
  35232. fillOpacity: 'canvas,alpha'
  35233. },
  35234. defaults: {
  35235. centerX: 0,
  35236. centerY: 0,
  35237. startAngle: Math.PI * 2,
  35238. endAngle: Math.PI * 2,
  35239. startRho: 0,
  35240. endRho: 150,
  35241. margin: 0,
  35242. thickness: 35,
  35243. distortion: 0.5,
  35244. baseRotation: 0,
  35245. baseColor: 'white',
  35246. colorSpread: 0.5,
  35247. miterLimit: 1,
  35248. bevelWidth: 5,
  35249. strokeOpacity: 0,
  35250. part: 'top',
  35251. label: ''
  35252. },
  35253. updaters: {
  35254. alpha: 'alphaUpdater',
  35255. partColor: 'partColorUpdater',
  35256. partZIndex: 'partZIndexUpdater'
  35257. }
  35258. }
  35259. },
  35260. config: {
  35261. renderer: null,
  35262. rendererData: null,
  35263. rendererIndex: 0,
  35264. series: null
  35265. },
  35266. bevelParams: [],
  35267. constructor: function(config) {
  35268. this.callParent([
  35269. config
  35270. ]);
  35271. this.bevelGradient = new Ext.draw.gradient.Linear({
  35272. stops: [
  35273. {
  35274. offset: 0,
  35275. color: 'rgba(255,255,255,0)'
  35276. },
  35277. {
  35278. offset: 0.7,
  35279. color: 'rgba(255,255,255,0.6)'
  35280. },
  35281. {
  35282. offset: 1,
  35283. color: 'rgba(255,255,255,0)'
  35284. }
  35285. ]
  35286. });
  35287. },
  35288. updateRenderer: function() {
  35289. this.setDirty(true);
  35290. },
  35291. updateRendererData: function() {
  35292. this.setDirty(true);
  35293. },
  35294. updateRendererIndex: function() {
  35295. this.setDirty(true);
  35296. },
  35297. alphaUpdater: function(attr) {
  35298. var me = this,
  35299. opacity = attr.globalAlpha,
  35300. fillOpacity = attr.fillOpacity,
  35301. oldOpacity = me.oldOpacity,
  35302. oldFillOpacity = me.oldFillOpacity;
  35303. // Update the path when the sprite becomes translucent or completely opaque.
  35304. if ((opacity !== oldOpacity && (opacity === 1 || oldOpacity === 1)) || (fillOpacity !== oldFillOpacity && (fillOpacity === 1 || oldFillOpacity === 1))) {
  35305. me.scheduleUpdater(attr, 'path', [
  35306. 'globalAlpha'
  35307. ]);
  35308. me.oldOpacity = opacity;
  35309. me.oldFillOpacity = fillOpacity;
  35310. }
  35311. },
  35312. partColorUpdater: function(attr) {
  35313. var color = Ext.util.Color.fly(attr.baseColor),
  35314. colorString = color.toString(),
  35315. colorSpread = attr.colorSpread,
  35316. fillStyle;
  35317. switch (attr.part) {
  35318. case 'top':
  35319. fillStyle = new Ext.draw.gradient.Radial({
  35320. start: {
  35321. x: 0,
  35322. y: 0,
  35323. r: 0
  35324. },
  35325. end: {
  35326. x: 0,
  35327. y: 0,
  35328. r: 1
  35329. },
  35330. stops: [
  35331. {
  35332. offset: 0,
  35333. color: color.createLighter(0.1 * colorSpread)
  35334. },
  35335. {
  35336. offset: 1,
  35337. color: color.createDarker(0.1 * colorSpread)
  35338. }
  35339. ]
  35340. });
  35341. break;
  35342. case 'bottom':
  35343. fillStyle = new Ext.draw.gradient.Radial({
  35344. start: {
  35345. x: 0,
  35346. y: 0,
  35347. r: 0
  35348. },
  35349. end: {
  35350. x: 0,
  35351. y: 0,
  35352. r: 1
  35353. },
  35354. stops: [
  35355. {
  35356. offset: 0,
  35357. color: color.createDarker(0.2 * colorSpread)
  35358. },
  35359. {
  35360. offset: 1,
  35361. color: color.toString()
  35362. }
  35363. ]
  35364. });
  35365. break;
  35366. case 'outerFront':
  35367. case 'outerBack':
  35368. fillStyle = new Ext.draw.gradient.Linear({
  35369. stops: [
  35370. {
  35371. offset: 0,
  35372. color: color.createDarker(0.15 * colorSpread).toString()
  35373. },
  35374. {
  35375. offset: 0.3,
  35376. color: colorString
  35377. },
  35378. {
  35379. offset: 0.8,
  35380. color: color.createLighter(0.2 * colorSpread).toString()
  35381. },
  35382. {
  35383. offset: 1,
  35384. color: color.createDarker(0.25 * colorSpread).toString()
  35385. }
  35386. ]
  35387. });
  35388. break;
  35389. case 'start':
  35390. fillStyle = new Ext.draw.gradient.Linear({
  35391. stops: [
  35392. {
  35393. offset: 0,
  35394. color: color.createDarker(0.1 * colorSpread).toString()
  35395. },
  35396. {
  35397. offset: 1,
  35398. color: color.createLighter(0.2 * colorSpread).toString()
  35399. }
  35400. ]
  35401. });
  35402. break;
  35403. case 'end':
  35404. fillStyle = new Ext.draw.gradient.Linear({
  35405. stops: [
  35406. {
  35407. offset: 0,
  35408. color: color.createDarker(0.1 * colorSpread).toString()
  35409. },
  35410. {
  35411. offset: 1,
  35412. color: color.createLighter(0.2 * colorSpread).toString()
  35413. }
  35414. ]
  35415. });
  35416. break;
  35417. case 'innerFront':
  35418. case 'innerBack':
  35419. fillStyle = new Ext.draw.gradient.Linear({
  35420. stops: [
  35421. {
  35422. offset: 0,
  35423. color: color.createDarker(0.1 * colorSpread).toString()
  35424. },
  35425. {
  35426. offset: 0.2,
  35427. color: color.createLighter(0.2 * colorSpread).toString()
  35428. },
  35429. {
  35430. offset: 0.7,
  35431. color: colorString
  35432. },
  35433. {
  35434. offset: 1,
  35435. color: color.createDarker(0.1 * colorSpread).toString()
  35436. }
  35437. ]
  35438. });
  35439. break;
  35440. }
  35441. attr.fillStyle = fillStyle;
  35442. attr.canvasAttributes.fillStyle = fillStyle;
  35443. },
  35444. partZIndexUpdater: function(attr) {
  35445. var normalize = Ext.draw.sprite.AttributeParser.angle,
  35446. rotation = attr.baseRotation,
  35447. startAngle = attr.startAngle,
  35448. endAngle = attr.endAngle,
  35449. depth;
  35450. switch (attr.part) {
  35451. case 'top':
  35452. attr.zIndex = 6;
  35453. break;
  35454. case 'outerFront':
  35455. startAngle = normalize(startAngle + rotation);
  35456. endAngle = normalize(endAngle + rotation);
  35457. if (startAngle >= 0 && endAngle < 0) {
  35458. depth = Math.sin(startAngle);
  35459. } else if (startAngle <= 0 && endAngle > 0) {
  35460. depth = Math.sin(endAngle);
  35461. } else if (startAngle >= 0 && endAngle > 0) {
  35462. if (startAngle > endAngle) {
  35463. depth = 0;
  35464. } else {
  35465. depth = Math.max(Math.sin(startAngle), Math.sin(endAngle));
  35466. }
  35467. } else {
  35468. depth = 1;
  35469. };
  35470. attr.zIndex = 4 + depth;
  35471. break;
  35472. case 'outerBack':
  35473. attr.zIndex = 1;
  35474. break;
  35475. case 'start':
  35476. attr.zIndex = 4 + Math.sin(normalize(startAngle + rotation));
  35477. break;
  35478. case 'end':
  35479. attr.zIndex = 4 + Math.sin(normalize(endAngle + rotation));
  35480. break;
  35481. case 'innerFront':
  35482. attr.zIndex = 2;
  35483. break;
  35484. case 'innerBack':
  35485. attr.zIndex = 4 + Math.sin(normalize((startAngle + endAngle) / 2 + rotation));
  35486. break;
  35487. case 'bottom':
  35488. attr.zIndex = 0;
  35489. break;
  35490. }
  35491. attr.dirtyZIndex = true;
  35492. },
  35493. updatePlainBBox: function(plain) {
  35494. var attr = this.attr,
  35495. part = attr.part,
  35496. baseRotation = attr.baseRotation,
  35497. centerX = attr.centerX,
  35498. centerY = attr.centerY,
  35499. rho, angle, x, y, sin, cos;
  35500. if (part === 'start') {
  35501. angle = attr.startAngle + baseRotation;
  35502. } else if (part === 'end') {
  35503. angle = attr.endAngle + baseRotation;
  35504. }
  35505. if (Ext.isNumber(angle)) {
  35506. sin = Math.sin(angle);
  35507. cos = Math.cos(angle);
  35508. x = Math.min(centerX + cos * attr.startRho, centerX + cos * attr.endRho);
  35509. y = centerY + sin * attr.startRho * attr.distortion;
  35510. plain.x = x;
  35511. plain.y = y;
  35512. plain.width = cos * (attr.endRho - attr.startRho);
  35513. plain.height = attr.thickness + sin * (attr.endRho - attr.startRho) * 2;
  35514. return;
  35515. }
  35516. if (part === 'innerFront' || part === 'innerBack') {
  35517. rho = attr.startRho;
  35518. } else {
  35519. rho = attr.endRho;
  35520. }
  35521. plain.width = rho * 2;
  35522. plain.height = rho * attr.distortion * 2 + attr.thickness;
  35523. plain.x = attr.centerX - rho;
  35524. plain.y = attr.centerY - rho * attr.distortion;
  35525. },
  35526. updateTransformedBBox: function(transform) {
  35527. if (this.attr.part === 'start' || this.attr.part === 'end') {
  35528. return this.callParent(arguments);
  35529. }
  35530. return this.updatePlainBBox(transform);
  35531. },
  35532. updatePath: function(path) {
  35533. if (!this.attr.globalAlpha) {
  35534. return;
  35535. }
  35536. if (this.attr.endAngle < this.attr.startAngle) {
  35537. return;
  35538. }
  35539. this[this.attr.part + 'Renderer'](path);
  35540. },
  35541. render: function(surface, ctx, rect) {
  35542. var me = this,
  35543. renderer = me.getRenderer(),
  35544. attr = me.attr,
  35545. part = attr.part,
  35546. itemCfg, changes;
  35547. if (!attr.globalAlpha || Ext.Number.isEqual(attr.startAngle, attr.endAngle, 1.0E-8)) {
  35548. return;
  35549. }
  35550. if (renderer) {
  35551. itemCfg = {
  35552. type: 'pie3dPart',
  35553. part: attr.part,
  35554. margin: attr.margin,
  35555. distortion: attr.distortion,
  35556. centerX: attr.centerX,
  35557. centerY: attr.centerY,
  35558. baseRotation: attr.baseRotation,
  35559. startAngle: attr.startAngle,
  35560. endAngle: attr.endAngle,
  35561. startRho: attr.startRho,
  35562. endRho: attr.endRho
  35563. };
  35564. changes = Ext.callback(renderer, null, [
  35565. me,
  35566. itemCfg,
  35567. me.getRendererData(),
  35568. me.getRendererIndex()
  35569. ], 0, me.getSeries());
  35570. if (changes) {
  35571. if (changes.part) {
  35572. // Can't let users change the nature of the sprite.
  35573. changes.part = part;
  35574. }
  35575. me.setAttributes(changes);
  35576. me.useAttributes(ctx, rect);
  35577. }
  35578. }
  35579. me.callParent([
  35580. surface,
  35581. ctx
  35582. ]);
  35583. me.bevelRenderer(surface, ctx);
  35584. // Only the top part will have the label attribute (set by the series).
  35585. if (attr.label && me.getMarker('labels')) {
  35586. me.placeLabel();
  35587. }
  35588. },
  35589. placeLabel: function() {
  35590. var me = this,
  35591. attr = me.attr,
  35592. attributeId = attr.attributeId,
  35593. margin = attr.margin,
  35594. distortion = attr.distortion,
  35595. centerX = attr.centerX,
  35596. centerY = attr.centerY,
  35597. baseRotation = attr.baseRotation,
  35598. startAngle = attr.startAngle + baseRotation,
  35599. endAngle = attr.endAngle + baseRotation,
  35600. midAngle = (startAngle + endAngle) / 2,
  35601. startRho = attr.startRho + margin,
  35602. endRho = attr.endRho + margin,
  35603. midRho = (startRho + endRho) / 2,
  35604. sin = Math.sin(midAngle),
  35605. cos = Math.cos(midAngle),
  35606. surfaceMatrix = me.surfaceMatrix,
  35607. label = me.getMarker('labels'),
  35608. labelTpl = label.getTemplate(),
  35609. calloutLine = labelTpl.getCalloutLine(),
  35610. calloutLineLength = calloutLine && calloutLine.length || 40,
  35611. labelCfg = {},
  35612. rendererParams, rendererChanges, x, y;
  35613. surfaceMatrix.appendMatrix(attr.matrix);
  35614. labelCfg.text = attr.label;
  35615. x = centerX + cos * midRho;
  35616. y = centerY + sin * midRho * distortion;
  35617. labelCfg.x = surfaceMatrix.x(x, y);
  35618. labelCfg.y = surfaceMatrix.y(x, y);
  35619. x = centerX + cos * endRho;
  35620. y = centerY + sin * endRho * distortion;
  35621. labelCfg.calloutStartX = surfaceMatrix.x(x, y);
  35622. labelCfg.calloutStartY = surfaceMatrix.y(x, y);
  35623. x = centerX + cos * (endRho + calloutLineLength);
  35624. y = centerY + sin * (endRho + calloutLineLength) * distortion;
  35625. labelCfg.calloutPlaceX = surfaceMatrix.x(x, y);
  35626. labelCfg.calloutPlaceY = surfaceMatrix.y(x, y);
  35627. labelCfg.calloutWidth = 2;
  35628. if (labelTpl.attr.renderer) {
  35629. rendererParams = [
  35630. me.attr.label,
  35631. label,
  35632. labelCfg,
  35633. me.getRendererData(),
  35634. me.getRendererIndex()
  35635. ];
  35636. rendererChanges = Ext.callback(labelTpl.attr.renderer, null, rendererParams, 0, me.getSeries());
  35637. if (typeof rendererChanges === 'string') {
  35638. labelCfg.text = rendererChanges;
  35639. } else {
  35640. Ext.apply(labelCfg, rendererChanges);
  35641. }
  35642. }
  35643. me.putMarker('labels', labelCfg, attributeId);
  35644. me.putMarker('labels', {
  35645. callout: 1
  35646. }, attributeId);
  35647. },
  35648. bevelRenderer: function(surface, ctx) {
  35649. var me = this,
  35650. attr = me.attr,
  35651. bevelWidth = attr.bevelWidth,
  35652. params = me.bevelParams,
  35653. i;
  35654. for (i = 0; i < params.length; i++) {
  35655. ctx.beginPath();
  35656. ctx.ellipse.apply(ctx, params[i]);
  35657. ctx.save();
  35658. ctx.lineWidth = bevelWidth;
  35659. ctx.strokeOpacity = bevelWidth ? 1 : 0;
  35660. ctx.strokeGradient = me.bevelGradient;
  35661. ctx.stroke(attr);
  35662. ctx.restore();
  35663. }
  35664. },
  35665. lidRenderer: function(path, thickness) {
  35666. var attr = this.attr,
  35667. margin = attr.margin,
  35668. distortion = attr.distortion,
  35669. centerX = attr.centerX,
  35670. centerY = attr.centerY,
  35671. baseRotation = attr.baseRotation,
  35672. startAngle = attr.startAngle + baseRotation,
  35673. endAngle = attr.endAngle + baseRotation,
  35674. midAngle = (startAngle + endAngle) / 2,
  35675. startRho = attr.startRho,
  35676. endRho = attr.endRho,
  35677. sinEnd = Math.sin(endAngle),
  35678. cosEnd = Math.cos(endAngle);
  35679. centerX += Math.cos(midAngle) * margin;
  35680. centerY += Math.sin(midAngle) * margin * distortion;
  35681. path.ellipse(centerX, centerY + thickness, startRho, startRho * distortion, 0, startAngle, endAngle, false);
  35682. path.lineTo(centerX + cosEnd * endRho, centerY + thickness + sinEnd * endRho * distortion);
  35683. path.ellipse(centerX, centerY + thickness, endRho, endRho * distortion, 0, endAngle, startAngle, true);
  35684. path.closePath();
  35685. },
  35686. topRenderer: function(path) {
  35687. this.lidRenderer(path, 0);
  35688. },
  35689. bottomRenderer: function(path) {
  35690. var attr = this.attr,
  35691. none = Ext.util.Color.RGBA_NONE;
  35692. if (attr.globalAlpha < 1 || attr.fillOpacity < 1 || attr.shadowColor !== none) {
  35693. this.lidRenderer(path, attr.thickness);
  35694. }
  35695. },
  35696. sideRenderer: function(path, position) {
  35697. var attr = this.attr,
  35698. margin = attr.margin,
  35699. centerX = attr.centerX,
  35700. centerY = attr.centerY,
  35701. distortion = attr.distortion,
  35702. baseRotation = attr.baseRotation,
  35703. startAngle = attr.startAngle + baseRotation,
  35704. endAngle = attr.endAngle + baseRotation,
  35705. isFullPie = (!attr.startAngle && Ext.Number.isEqual(Math.PI * 2, attr.endAngle, 1.0E-7)),
  35706. thickness = attr.thickness,
  35707. startRho = attr.startRho,
  35708. endRho = attr.endRho,
  35709. angle = (position === 'start' && startAngle) || (position === 'end' && endAngle),
  35710. sin = Math.sin(angle),
  35711. cos = Math.cos(angle),
  35712. isTranslucent = attr.globalAlpha < 1,
  35713. isVisible = position === 'start' && cos < 0 || position === 'end' && cos > 0 || isTranslucent,
  35714. midAngle;
  35715. if (isVisible && !isFullPie) {
  35716. midAngle = (startAngle + endAngle) / 2;
  35717. centerX += Math.cos(midAngle) * margin;
  35718. centerY += Math.sin(midAngle) * margin * distortion;
  35719. path.moveTo(centerX + cos * startRho, centerY + sin * startRho * distortion);
  35720. path.lineTo(centerX + cos * endRho, centerY + sin * endRho * distortion);
  35721. path.lineTo(centerX + cos * endRho, centerY + sin * endRho * distortion + thickness);
  35722. path.lineTo(centerX + cos * startRho, centerY + sin * startRho * distortion + thickness);
  35723. path.closePath();
  35724. }
  35725. },
  35726. startRenderer: function(path) {
  35727. this.sideRenderer(path, 'start');
  35728. },
  35729. endRenderer: function(path) {
  35730. this.sideRenderer(path, 'end');
  35731. },
  35732. rimRenderer: function(path, radius, isDonut, isFront) {
  35733. var me = this,
  35734. attr = me.attr,
  35735. margin = attr.margin,
  35736. centerX = attr.centerX,
  35737. centerY = attr.centerY,
  35738. distortion = attr.distortion,
  35739. baseRotation = attr.baseRotation,
  35740. normalize = Ext.draw.sprite.AttributeParser.angle,
  35741. startAngle = attr.startAngle + baseRotation,
  35742. endAngle = attr.endAngle + baseRotation,
  35743. // It's critical to use non-normalized start and end angles
  35744. // for middle angle calculation. Consider a situation where the
  35745. // start angle is +170 degrees and the end engle is -170 degrees
  35746. // after normalization (the middle angle is 0 then, but it should be 180 degrees).
  35747. midAngle = normalize((startAngle + endAngle) / 2),
  35748. thickness = attr.thickness,
  35749. isTranslucent = attr.globalAlpha < 1,
  35750. isAllFront, isAllBack, params;
  35751. me.bevelParams = [];
  35752. startAngle = normalize(startAngle);
  35753. endAngle = normalize(endAngle);
  35754. centerX += Math.cos(midAngle) * margin;
  35755. centerY += Math.sin(midAngle) * margin * distortion;
  35756. isAllFront = startAngle >= 0 && endAngle >= 0;
  35757. isAllBack = startAngle <= 0 && endAngle <= 0;
  35758. function renderLeftFrontChunk() {
  35759. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, Math.PI, startAngle, true);
  35760. path.lineTo(centerX + Math.cos(startAngle) * radius, centerY + Math.sin(startAngle) * radius * distortion);
  35761. params = [
  35762. centerX,
  35763. centerY,
  35764. radius,
  35765. radius * distortion,
  35766. 0,
  35767. startAngle,
  35768. Math.PI,
  35769. false
  35770. ];
  35771. if (!isDonut) {
  35772. me.bevelParams.push(params);
  35773. }
  35774. path.ellipse.apply(path, params);
  35775. path.closePath();
  35776. }
  35777. function renderRightFrontChunk() {
  35778. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, 0, endAngle, false);
  35779. path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion);
  35780. params = [
  35781. centerX,
  35782. centerY,
  35783. radius,
  35784. radius * distortion,
  35785. 0,
  35786. endAngle,
  35787. 0,
  35788. true
  35789. ];
  35790. if (!isDonut) {
  35791. me.bevelParams.push(params);
  35792. }
  35793. path.ellipse.apply(path, params);
  35794. path.closePath();
  35795. }
  35796. function renderLeftBackChunk() {
  35797. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, Math.PI, endAngle, false);
  35798. path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion);
  35799. params = [
  35800. centerX,
  35801. centerY,
  35802. radius,
  35803. radius * distortion,
  35804. 0,
  35805. endAngle,
  35806. Math.PI,
  35807. true
  35808. ];
  35809. if (isDonut) {
  35810. me.bevelParams.push(params);
  35811. }
  35812. path.ellipse.apply(path, params);
  35813. path.closePath();
  35814. }
  35815. function renderRightBackChunk() {
  35816. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, startAngle, 0, false);
  35817. path.lineTo(centerX + radius, centerY);
  35818. params = [
  35819. centerX,
  35820. centerY,
  35821. radius,
  35822. radius * distortion,
  35823. 0,
  35824. 0,
  35825. startAngle,
  35826. true
  35827. ];
  35828. if (isDonut) {
  35829. me.bevelParams.push(params);
  35830. }
  35831. path.ellipse.apply(path, params);
  35832. path.closePath();
  35833. }
  35834. if (isFront) {
  35835. if (!isDonut || isTranslucent) {
  35836. if (startAngle >= 0 && endAngle < 0) {
  35837. renderLeftFrontChunk();
  35838. } else if (startAngle <= 0 && endAngle > 0) {
  35839. renderRightFrontChunk();
  35840. } else if (startAngle <= 0 && endAngle < 0) {
  35841. if (startAngle > endAngle) {
  35842. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, 0, Math.PI, false);
  35843. path.lineTo(centerX - radius, centerY);
  35844. params = [
  35845. centerX,
  35846. centerY,
  35847. radius,
  35848. radius * distortion,
  35849. 0,
  35850. Math.PI,
  35851. 0,
  35852. true
  35853. ];
  35854. if (!isDonut) {
  35855. me.bevelParams.push(params);
  35856. }
  35857. path.ellipse.apply(path, params);
  35858. path.closePath();
  35859. }
  35860. } else {
  35861. // startAngle >= 0 && endAngle > 0
  35862. // obtuse horseshoe-like slice with the gap facing forward
  35863. if (startAngle > endAngle) {
  35864. renderLeftFrontChunk();
  35865. renderRightFrontChunk();
  35866. } else {
  35867. // acute slice facing forward
  35868. params = [
  35869. centerX,
  35870. centerY,
  35871. radius,
  35872. radius * distortion,
  35873. 0,
  35874. startAngle,
  35875. endAngle,
  35876. false
  35877. ];
  35878. if (isAllFront && !isDonut || isAllBack && isDonut) {
  35879. me.bevelParams.push(params);
  35880. }
  35881. path.ellipse.apply(path, params);
  35882. path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion + thickness);
  35883. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, endAngle, startAngle, true);
  35884. path.closePath();
  35885. }
  35886. }
  35887. }
  35888. } else {
  35889. if (isDonut || isTranslucent) {
  35890. if (startAngle >= 0 && endAngle < 0) {
  35891. renderLeftBackChunk();
  35892. } else if (startAngle <= 0 && endAngle > 0) {
  35893. renderRightBackChunk();
  35894. } else if (startAngle <= 0 && endAngle < 0) {
  35895. if (startAngle > endAngle) {
  35896. renderLeftBackChunk();
  35897. renderRightBackChunk();
  35898. } else {
  35899. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, startAngle, endAngle, false);
  35900. path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion);
  35901. params = [
  35902. centerX,
  35903. centerY,
  35904. radius,
  35905. radius * distortion,
  35906. 0,
  35907. endAngle,
  35908. startAngle,
  35909. true
  35910. ];
  35911. if (isDonut) {
  35912. me.bevelParams.push(params);
  35913. }
  35914. path.ellipse.apply(path, params);
  35915. path.closePath();
  35916. }
  35917. } else {
  35918. // startAngle >= 0 && endAngle > 0
  35919. if (startAngle > endAngle) {
  35920. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, -Math.PI, 0, false);
  35921. path.lineTo(centerX + radius, centerY);
  35922. params = [
  35923. centerX,
  35924. centerY,
  35925. radius,
  35926. radius * distortion,
  35927. 0,
  35928. 0,
  35929. -Math.PI,
  35930. true
  35931. ];
  35932. if (isDonut) {
  35933. me.bevelParams.push(params);
  35934. }
  35935. path.ellipse.apply(path, params);
  35936. path.closePath();
  35937. }
  35938. }
  35939. }
  35940. }
  35941. },
  35942. innerFrontRenderer: function(path) {
  35943. this.rimRenderer(path, this.attr.startRho, true, true);
  35944. },
  35945. innerBackRenderer: function(path) {
  35946. this.rimRenderer(path, this.attr.startRho, true, false);
  35947. },
  35948. outerFrontRenderer: function(path) {
  35949. this.rimRenderer(path, this.attr.endRho, false, true);
  35950. },
  35951. outerBackRenderer: function(path) {
  35952. this.rimRenderer(path, this.attr.endRho, false, false);
  35953. }
  35954. });
  35955. /**
  35956. * @class Ext.chart.series.Pie3D
  35957. * @extends Ext.chart.series.Polar
  35958. *
  35959. * Creates a 3D Pie Chart.
  35960. *
  35961. * **Note:** Labels, legends, and lines are not currently available when using the
  35962. * 3D Pie chart series.
  35963. *
  35964. * @example
  35965. * Ext.create({
  35966. * xtype: 'polar',
  35967. * renderTo: document.body,
  35968. * width: 600,
  35969. * height: 400,
  35970. * theme: 'green',
  35971. * interactions: 'rotate',
  35972. * store: {
  35973. * fields: ['data3'],
  35974. * data: [{
  35975. * 'data3': 14
  35976. * }, {
  35977. * 'data3': 16
  35978. * }, {
  35979. * 'data3': 14
  35980. * }, {
  35981. * 'data3': 6
  35982. * }, {
  35983. * 'data3': 36
  35984. * }]
  35985. * },
  35986. * series: {
  35987. * type: 'pie3d',
  35988. * angleField: 'data3',
  35989. * donut: 30
  35990. * }
  35991. * });
  35992. */
  35993. Ext.define('Ext.chart.series.Pie3D', {
  35994. extend: 'Ext.chart.series.Polar',
  35995. requires: [
  35996. 'Ext.chart.series.sprite.Pie3DPart',
  35997. 'Ext.draw.PathUtil'
  35998. ],
  35999. type: 'pie3d',
  36000. seriesType: 'pie3d',
  36001. alias: 'series.pie3d',
  36002. is3D: true,
  36003. config: {
  36004. rect: [
  36005. 0,
  36006. 0,
  36007. 0,
  36008. 0
  36009. ],
  36010. thickness: 35,
  36011. distortion: 0.5,
  36012. /**
  36013. * @cfg {String} angleField (required)
  36014. * The store record field name to be used for the pie angles.
  36015. * The values bound to this field name must be positive real numbers.
  36016. */
  36017. /**
  36018. * @private
  36019. * @cfg {String} radiusField
  36020. * Not supported.
  36021. */
  36022. /**
  36023. * @cfg {Number} donut Specifies the radius of the donut hole, as a percentage of the chart's radius.
  36024. * Defaults to 0 (no donut hole).
  36025. */
  36026. donut: 0,
  36027. /**
  36028. * @cfg {Array} hidden Determines which pie slices are hidden.
  36029. */
  36030. hidden: [],
  36031. // Populated by the coordinateX method.
  36032. /**
  36033. * @cfg {Object} highlightCfg Default {@link #highlight} config for the 3D pie series.
  36034. * Slides highlighted pie sector outward.
  36035. */
  36036. highlightCfg: {
  36037. margin: 20
  36038. },
  36039. /**
  36040. * @cfg {Number} [rotation=0] The starting angle of the pie slices.
  36041. */
  36042. /**
  36043. * @private
  36044. * @cfg {Boolean/Object} [shadow=false]
  36045. */
  36046. shadow: false
  36047. },
  36048. // Subtract 90 degrees from rotation, so that `rotation` config's default
  36049. // zero value makes first pie sector start at noon, rather than 3 o'clock.
  36050. rotationOffset: -Math.PI / 2,
  36051. setField: function(value) {
  36052. return this.setXField(value);
  36053. },
  36054. getField: function() {
  36055. return this.getXField();
  36056. },
  36057. updateRotation: function(rotation) {
  36058. var attributes = {
  36059. baseRotation: rotation + this.rotationOffset
  36060. };
  36061. this.forEachSprite(function(sprite) {
  36062. sprite.setAttributes(attributes);
  36063. });
  36064. },
  36065. updateColors: function(colors) {
  36066. this.setSubStyle({
  36067. baseColor: colors
  36068. });
  36069. if (!this.isConfiguring) {
  36070. var chart = this.getChart();
  36071. if (chart) {
  36072. chart.refreshLegendStore();
  36073. }
  36074. }
  36075. },
  36076. applyShadow: function(shadow) {
  36077. if (shadow === true) {
  36078. shadow = {
  36079. shadowColor: 'rgba(0,0,0,0.8)',
  36080. shadowBlur: 30
  36081. };
  36082. } else if (!Ext.isObject(shadow)) {
  36083. shadow = {
  36084. shadowColor: Ext.util.Color.RGBA_NONE
  36085. };
  36086. }
  36087. return shadow;
  36088. },
  36089. updateShadow: function(shadow) {
  36090. var me = this,
  36091. sprites = me.getSprites(),
  36092. spritesPerSlice = me.spritesPerSlice,
  36093. ln = sprites && sprites.length,
  36094. i, sprite;
  36095. for (i = 1; i < ln; i += spritesPerSlice) {
  36096. sprite = sprites[i];
  36097. if (sprite.attr.part = 'bottom') {
  36098. sprite.setAttributes(shadow);
  36099. }
  36100. }
  36101. },
  36102. // This is a temporary solution until the Series.getStyleByIndex is fixed
  36103. // to give user styles the priority over theme ones. Also, for sprites of
  36104. // this particular series, the fillStyle shouldn't be set directly. Instead,
  36105. // the 'baseColor' attribute should be set, from which the stops of the
  36106. // gradient (used for fillStyle) will be calculated. Themes can't handle
  36107. // situations like that properly.
  36108. getStyleByIndex: function(i) {
  36109. var indexStyle = this.callParent([
  36110. i
  36111. ]),
  36112. style = this.getStyle(),
  36113. // 'fill' and 'color' are 'fillStyle' aliases
  36114. // (see Ext.draw.sprite.Sprite.inheritableStatics.def.aliases)
  36115. fillStyle = indexStyle.fillStyle || indexStyle.fill || indexStyle.color,
  36116. strokeStyle = style.strokeStyle || style.stroke;
  36117. if (fillStyle) {
  36118. indexStyle.baseColor = fillStyle;
  36119. delete indexStyle.fillStyle;
  36120. delete indexStyle.fill;
  36121. delete indexStyle.color;
  36122. }
  36123. if (strokeStyle) {
  36124. indexStyle.strokeStyle = strokeStyle;
  36125. }
  36126. return indexStyle;
  36127. },
  36128. doUpdateStyles: function() {
  36129. var me = this,
  36130. sprites = me.getSprites(),
  36131. spritesPerSlice = me.spritesPerSlice,
  36132. ln = sprites && sprites.length,
  36133. i = 0,
  36134. j = 0,
  36135. k, style;
  36136. for (; i < ln; i += spritesPerSlice , j++) {
  36137. style = me.getStyleByIndex(j);
  36138. for (k = 0; k < spritesPerSlice; k++) {
  36139. sprites[i + k].setAttributes(style);
  36140. }
  36141. }
  36142. },
  36143. coordinateX: function() {
  36144. var me = this,
  36145. store = me.getStore(),
  36146. records = store.getData().items,
  36147. recordCount = records.length,
  36148. xField = me.getXField(),
  36149. animation = me.getAnimation(),
  36150. rotation = me.getRotation(),
  36151. hidden = me.getHidden(),
  36152. sprites = me.getSprites(true),
  36153. spriteCount = sprites.length,
  36154. spritesPerSlice = me.spritesPerSlice,
  36155. center = me.getCenter(),
  36156. offsetX = me.getOffsetX(),
  36157. offsetY = me.getOffsetY(),
  36158. radius = me.getRadius(),
  36159. thickness = me.getThickness(),
  36160. distortion = me.getDistortion(),
  36161. renderer = me.getRenderer(),
  36162. rendererData = me.getRendererData(),
  36163. highlight = me.getHighlight(),
  36164. lastAngle = 0,
  36165. twoPi = Math.PI * 2,
  36166. // To avoid adjacent start/end part blinking (z-index jitter)
  36167. // when rotating a translucent pie chart.
  36168. delta = 1.0E-10,
  36169. endAngles = [],
  36170. sum = 0,
  36171. value, unit, sprite, style, i, j;
  36172. for (i = 0; i < recordCount; i++) {
  36173. value = Math.abs(+records[i].get(xField)) || 0;
  36174. if (!hidden[i]) {
  36175. sum += value;
  36176. }
  36177. endAngles[i] = sum;
  36178. if (i >= hidden.length) {
  36179. hidden[i] = false;
  36180. }
  36181. }
  36182. if (sum === 0) {
  36183. return;
  36184. }
  36185. // Angular value of 1 in radians.
  36186. unit = 2 * Math.PI / sum;
  36187. for (i = 0; i < recordCount; i++) {
  36188. endAngles[i] *= unit;
  36189. }
  36190. for (i = 0; i < recordCount; i++) {
  36191. style = this.getStyleByIndex(i);
  36192. for (j = 0; j < spritesPerSlice; j++) {
  36193. sprite = sprites[i * spritesPerSlice + j];
  36194. sprite.setAnimation(animation);
  36195. sprite.setAttributes({
  36196. centerX: center[0] + offsetX,
  36197. centerY: center[1] + offsetY - thickness / 2,
  36198. endRho: radius,
  36199. startRho: radius * me.getDonut() / 100,
  36200. baseRotation: rotation + me.rotationOffset,
  36201. startAngle: lastAngle,
  36202. endAngle: endAngles[i] - delta,
  36203. thickness: thickness,
  36204. distortion: distortion,
  36205. globalAlpha: 1
  36206. });
  36207. sprite.setAttributes(style);
  36208. sprite.setConfig({
  36209. renderer: renderer,
  36210. rendererData: rendererData,
  36211. rendererIndex: i
  36212. });
  36213. }
  36214. // if (highlight) {
  36215. // if (!sprite.modifiers.highlight) {
  36216. // debugger
  36217. // sprite.addModifier(highlight, true);
  36218. // }
  36219. // // sprite.modifiers.highlight.setConfig(highlight);
  36220. // }
  36221. lastAngle = endAngles[i];
  36222. }
  36223. for (i *= spritesPerSlice; i < spriteCount; i++) {
  36224. sprite = sprites[i];
  36225. sprite.setAnimation(animation);
  36226. sprite.setAttributes({
  36227. startAngle: twoPi,
  36228. endAngle: twoPi,
  36229. globalAlpha: 0,
  36230. baseRotation: rotation + me.rotationOffset
  36231. });
  36232. }
  36233. },
  36234. updateHighlight: function(highlight, oldHighlight) {
  36235. this.callParent([
  36236. highlight,
  36237. oldHighlight
  36238. ]);
  36239. this.forEachSprite(function(sprite) {
  36240. if (highlight) {
  36241. if (sprite.modifiers.highlight) {
  36242. sprite.modifiers.highlight.setConfig(highlight);
  36243. } else {
  36244. sprite.config.highlight = highlight;
  36245. sprite.addModifier(highlight, true);
  36246. }
  36247. }
  36248. });
  36249. },
  36250. updateLabelData: function() {
  36251. var me = this,
  36252. store = me.getStore(),
  36253. items = store.getData().items,
  36254. sprites = me.getSprites(),
  36255. label = me.getLabel(),
  36256. labelField = label && label.getTemplate().getField(),
  36257. hidden = me.getHidden(),
  36258. spritesPerSlice = me.spritesPerSlice,
  36259. ln, labels, sprite,
  36260. name = 'labels',
  36261. i, // sprite index
  36262. j;
  36263. // record index
  36264. if (sprites.length) {
  36265. if (labelField) {
  36266. labels = [];
  36267. for (j = 0 , ln = items.length; j < ln; j++) {
  36268. labels.push(items[j].get(labelField));
  36269. }
  36270. }
  36271. // Only set labels for the sprites that compose the top lid of the pie.
  36272. for (i = 0 , j = 0 , ln = sprites.length; i < ln; i += spritesPerSlice , j++) {
  36273. sprite = sprites[i];
  36274. if (label) {
  36275. if (!sprite.getMarker(name)) {
  36276. sprite.bindMarker(name, label);
  36277. }
  36278. if (labels) {
  36279. sprite.setAttributes({
  36280. label: labels[j]
  36281. });
  36282. }
  36283. sprite.putMarker(name, {
  36284. hidden: hidden[j]
  36285. }, sprite.attr.attributeId);
  36286. } else {
  36287. sprite.releaseMarker(name);
  36288. }
  36289. }
  36290. }
  36291. },
  36292. // The radius here will normally be set by the PolarChart.performLayout,
  36293. // where it's half the width or height (whichever is smaller) of the chart's rect.
  36294. // But for 3D pie series we have to take the thickness of the pie and the
  36295. // distortion into account to calculate the proper radius.
  36296. // The passed value is never used (or derived from) since the radius config
  36297. // is not really meant to be used directly, as it will be reset by the next layout.
  36298. applyRadius: function() {
  36299. var me = this,
  36300. chart = me.getChart(),
  36301. padding = chart.getInnerPadding(),
  36302. rect = chart.getMainRect() || [
  36303. 0,
  36304. 0,
  36305. 1,
  36306. 1
  36307. ],
  36308. width = rect[2] - padding * 2,
  36309. height = rect[3] - padding * 2 - me.getThickness(),
  36310. horizontalRadius = width / 2,
  36311. verticalRadius = horizontalRadius * me.getDistortion(),
  36312. result;
  36313. if (verticalRadius > height / 2) {
  36314. result = height / (me.getDistortion() * 2);
  36315. } else {
  36316. result = horizontalRadius;
  36317. }
  36318. return Math.max(result, 0);
  36319. },
  36320. forEachSprite: function(fn) {
  36321. var sprites = this.sprites,
  36322. ln = sprites.length,
  36323. i;
  36324. for (i = 0; i < ln; i++) {
  36325. fn(sprites[i], Math.floor(i / this.spritesPerSlice));
  36326. }
  36327. },
  36328. updateRadius: function(radius) {
  36329. // The side effects of the 'getChart' call will result
  36330. // in the 'coordinateX' method call, which we want to have called
  36331. // first, to coordinate the data and create sprites for pie slices,
  36332. // before we set their attributes here.
  36333. // updateChart -> onChartAttached -> processData -> coordinateX
  36334. this.getChart();
  36335. var donut = this.getDonut();
  36336. this.forEachSprite(function(sprite) {
  36337. sprite.setAttributes({
  36338. endRho: radius,
  36339. startRho: radius * donut / 100
  36340. });
  36341. });
  36342. },
  36343. updateDonut: function(donut) {
  36344. // See 'updateRadius' comments.
  36345. this.getChart();
  36346. var radius = this.getRadius();
  36347. this.forEachSprite(function(sprite) {
  36348. sprite.setAttributes({
  36349. startRho: radius * donut / 100
  36350. });
  36351. });
  36352. },
  36353. updateCenter: function(center) {
  36354. // See 'updateRadius' comments.
  36355. this.getChart();
  36356. var offsetX = this.getOffsetX(),
  36357. offsetY = this.getOffsetY(),
  36358. thickness = this.getThickness();
  36359. this.forEachSprite(function(sprite) {
  36360. sprite.setAttributes({
  36361. centerX: center[0] + offsetX,
  36362. centerY: center[1] + offsetY - thickness / 2
  36363. });
  36364. });
  36365. },
  36366. updateThickness: function(thickness) {
  36367. // See 'updateRadius' comments.
  36368. this.getChart();
  36369. // Radius depends on thickness and distortion,
  36370. // this will trigger its recalculation in the applier.
  36371. this.setRadius();
  36372. var center = this.getCenter(),
  36373. offsetY = this.getOffsetY();
  36374. this.forEachSprite(function(sprite) {
  36375. sprite.setAttributes({
  36376. thickness: thickness,
  36377. centerY: center[1] + offsetY - thickness / 2
  36378. });
  36379. });
  36380. },
  36381. updateDistortion: function(distortion) {
  36382. // See 'updateRadius' comments.
  36383. this.getChart();
  36384. // Radius depends on thickness and distortion,
  36385. // this will trigger its recalculation in the applier.
  36386. this.setRadius();
  36387. this.forEachSprite(function(sprite) {
  36388. sprite.setAttributes({
  36389. distortion: distortion
  36390. });
  36391. });
  36392. },
  36393. updateOffsetX: function(offsetX) {
  36394. // See 'updateRadius' comments.
  36395. this.getChart();
  36396. var center = this.getCenter();
  36397. this.forEachSprite(function(sprite) {
  36398. sprite.setAttributes({
  36399. centerX: center[0] + offsetX
  36400. });
  36401. });
  36402. },
  36403. updateOffsetY: function(offsetY) {
  36404. // See 'updateRadius' comments.
  36405. this.getChart();
  36406. var center = this.getCenter(),
  36407. thickness = this.getThickness();
  36408. this.forEachSprite(function(sprite) {
  36409. sprite.setAttributes({
  36410. centerY: center[1] + offsetY - thickness / 2
  36411. });
  36412. });
  36413. },
  36414. updateAnimation: function(animation) {
  36415. // See 'updateRadius' comments.
  36416. this.getChart();
  36417. this.forEachSprite(function(sprite) {
  36418. sprite.setAnimation(animation);
  36419. });
  36420. },
  36421. updateRenderer: function(renderer) {
  36422. // See 'updateRadius' comments.
  36423. this.getChart();
  36424. var rendererData = this.getRendererData();
  36425. this.forEachSprite(function(sprite, itemIndex) {
  36426. sprite.setConfig({
  36427. renderer: renderer,
  36428. rendererData: rendererData,
  36429. rendererIndex: itemIndex
  36430. });
  36431. });
  36432. },
  36433. getRendererData: function() {
  36434. return {
  36435. store: this.getStore(),
  36436. angleField: this.getXField(),
  36437. radiusField: this.getYField(),
  36438. series: this
  36439. };
  36440. },
  36441. getSprites: function(createMissing) {
  36442. var me = this,
  36443. store = me.getStore(),
  36444. sprites = me.sprites;
  36445. if (!store) {
  36446. return Ext.emptyArray;
  36447. }
  36448. if (sprites && !createMissing) {
  36449. return sprites;
  36450. }
  36451. var surface = me.getSurface(),
  36452. records = store.getData().items,
  36453. spritesPerSlice = me.spritesPerSlice,
  36454. partCount = me.partNames.length,
  36455. recordCount = records.length,
  36456. sprite, i, j;
  36457. for (i = 0; i < recordCount; i++) {
  36458. if (!sprites[i * spritesPerSlice]) {
  36459. for (j = 0; j < partCount; j++) {
  36460. sprite = surface.add({
  36461. type: 'pie3dPart',
  36462. part: me.partNames[j],
  36463. series: me
  36464. });
  36465. sprite.getAnimation().setDurationOn('baseRotation', 0);
  36466. sprites.push(sprite);
  36467. }
  36468. }
  36469. }
  36470. return sprites;
  36471. },
  36472. betweenAngle: function(x, a, b) {
  36473. var pp = Math.PI * 2,
  36474. offset = this.rotationOffset;
  36475. a += offset;
  36476. b += offset;
  36477. x -= a;
  36478. b -= a;
  36479. // Normalize, so that both x and b are in the [0,360) interval.
  36480. // Since 360 * n angles will be normalized to 0,
  36481. // we need to treat b === 0 as a special case.
  36482. x %= pp;
  36483. b %= pp;
  36484. x += pp;
  36485. b += pp;
  36486. x %= pp;
  36487. b %= pp;
  36488. return x < b || b === 0;
  36489. },
  36490. getItemForPoint: function(x, y) {
  36491. var me = this,
  36492. sprites = me.getSprites(),
  36493. result = null;
  36494. if (!sprites) {
  36495. return result;
  36496. }
  36497. var store = me.getStore(),
  36498. records = store.getData().items,
  36499. spritesPerSlice = me.spritesPerSlice,
  36500. hidden = me.getHidden(),
  36501. i, ln, sprite, topPartIndex;
  36502. for (i = 0 , ln = records.length; i < ln; i++) {
  36503. if (hidden[i]) {
  36504. continue;
  36505. }
  36506. topPartIndex = i * spritesPerSlice;
  36507. sprite = sprites[topPartIndex];
  36508. // This is CPU intensive on mousemove (no visial slowdown
  36509. // on a fast machine, but some throttling might be desirable
  36510. // on slower machines).
  36511. // On touch devices performance/battery hit is negligible.
  36512. if (sprite.hitTest([
  36513. x,
  36514. y
  36515. ])) {
  36516. result = {
  36517. series: me,
  36518. sprite: sprites.slice(topPartIndex, topPartIndex + spritesPerSlice),
  36519. index: i,
  36520. record: records[i],
  36521. category: 'sprites',
  36522. field: me.getXField()
  36523. };
  36524. break;
  36525. }
  36526. }
  36527. return result;
  36528. },
  36529. provideLegendInfo: function(target) {
  36530. var me = this,
  36531. store = me.getStore();
  36532. if (store) {
  36533. var items = store.getData().items,
  36534. labelField = me.getLabel().getTemplate().getField(),
  36535. field = me.getField(),
  36536. hidden = me.getHidden(),
  36537. i, style, color;
  36538. for (i = 0; i < items.length; i++) {
  36539. style = me.getStyleByIndex(i);
  36540. color = style.baseColor;
  36541. target.push({
  36542. name: labelField ? String(items[i].get(labelField)) : field + ' ' + i,
  36543. mark: color || 'black',
  36544. disabled: hidden[i],
  36545. series: me.getId(),
  36546. index: i
  36547. });
  36548. }
  36549. }
  36550. }
  36551. }, function() {
  36552. var proto = this.prototype,
  36553. definition = Ext.chart.series.sprite.Pie3DPart.def.getInitialConfig().processors.part;
  36554. proto.partNames = definition.replace(/^enums\(|\)/g, '').split(',');
  36555. proto.spritesPerSlice = proto.partNames.length;
  36556. });
  36557. /**
  36558. * Polar sprite.
  36559. */
  36560. Ext.define('Ext.chart.series.sprite.Polar', {
  36561. extend: 'Ext.chart.series.sprite.Series',
  36562. inheritableStatics: {
  36563. def: {
  36564. processors: {
  36565. /**
  36566. * @cfg {Number} [centerX=0] The central point of the series on the x-axis.
  36567. */
  36568. centerX: 'number',
  36569. /**
  36570. * @cfg {Number} [centerY=0] The central point of the series on the y-axis.
  36571. */
  36572. centerY: 'number',
  36573. /**
  36574. * @cfg {Number} [startAngle=0] The starting angle of the polar series.
  36575. */
  36576. startAngle: 'number',
  36577. /**
  36578. * @cfg {Number} [endAngle=Math.PI] The ending angle of the polar series.
  36579. */
  36580. endAngle: 'number',
  36581. /**
  36582. * @cfg {Number} [startRho=0] The starting radius of the polar series.
  36583. */
  36584. startRho: 'number',
  36585. /**
  36586. * @cfg {Number} [endRho=150] The ending radius of the polar series.
  36587. */
  36588. endRho: 'number',
  36589. /**
  36590. * @cfg {Number} [baseRotation=0] The starting rotation of the polar series.
  36591. */
  36592. baseRotation: 'number'
  36593. },
  36594. defaults: {
  36595. centerX: 0,
  36596. centerY: 0,
  36597. startAngle: 0,
  36598. endAngle: Math.PI,
  36599. startRho: 0,
  36600. endRho: 150,
  36601. baseRotation: 0
  36602. },
  36603. triggers: {
  36604. centerX: 'bbox',
  36605. centerY: 'bbox',
  36606. startAngle: 'bbox',
  36607. endAngle: 'bbox',
  36608. startRho: 'bbox',
  36609. endRho: 'bbox',
  36610. baseRotation: 'bbox'
  36611. }
  36612. }
  36613. },
  36614. updatePlainBBox: function(plain) {
  36615. var attr = this.attr;
  36616. plain.x = attr.centerX - attr.endRho;
  36617. plain.y = attr.centerY + attr.endRho;
  36618. plain.width = attr.endRho * 2;
  36619. plain.height = attr.endRho * 2;
  36620. }
  36621. });
  36622. /**
  36623. * @class Ext.chart.series.sprite.Radar
  36624. * @extends Ext.chart.series.sprite.Polar
  36625. *
  36626. * Radar series sprite.
  36627. */
  36628. Ext.define('Ext.chart.series.sprite.Radar', {
  36629. alias: 'sprite.radar',
  36630. extend: 'Ext.chart.series.sprite.Polar',
  36631. getDataPointXY: function(index) {
  36632. var me = this,
  36633. attr = me.attr,
  36634. centerX = attr.centerX,
  36635. centerY = attr.centerY,
  36636. matrix = attr.matrix,
  36637. minX = attr.dataMinX,
  36638. maxX = attr.dataMaxX,
  36639. dataX = attr.dataX,
  36640. dataY = attr.dataY,
  36641. endRho = attr.endRho,
  36642. startRho = attr.startRho,
  36643. baseRotation = attr.baseRotation,
  36644. x, y, r, th, ox, oy, maxY;
  36645. if (attr.rangeY) {
  36646. maxY = attr.rangeY[1];
  36647. } else {
  36648. maxY = attr.dataMaxY;
  36649. }
  36650. th = (dataX[index] - minX) / (maxX - minX + 1) * 2 * Math.PI + baseRotation;
  36651. r = dataY[index] / maxY * (endRho - startRho) + startRho;
  36652. // Original coordinates.
  36653. ox = centerX + Math.cos(th) * r;
  36654. oy = centerY + Math.sin(th) * r;
  36655. // Transformed coordinates.
  36656. x = matrix.x(ox, oy);
  36657. y = matrix.y(ox, oy);
  36658. return [
  36659. x,
  36660. y
  36661. ];
  36662. },
  36663. render: function(surface, ctx) {
  36664. var me = this,
  36665. attr = me.attr,
  36666. dataX = attr.dataX,
  36667. length = dataX.length,
  36668. surfaceMatrix = me.surfaceMatrix,
  36669. markerCfg = {},
  36670. i, x, y, xy;
  36671. ctx.beginPath();
  36672. for (i = 0; i < length; i++) {
  36673. xy = me.getDataPointXY(i);
  36674. x = xy[0];
  36675. y = xy[1];
  36676. if (i === 0) {
  36677. ctx.moveTo(x, y);
  36678. }
  36679. ctx.lineTo(x, y);
  36680. markerCfg.translationX = surfaceMatrix.x(x, y);
  36681. markerCfg.translationY = surfaceMatrix.y(x, y);
  36682. me.putMarker('markers', markerCfg, i, true);
  36683. }
  36684. ctx.closePath();
  36685. ctx.fillStroke(attr);
  36686. }
  36687. });
  36688. /**
  36689. * @class Ext.chart.series.Radar
  36690. * @extends Ext.chart.series.Polar
  36691. *
  36692. * Creates a Radar Chart. A Radar Chart is a useful visualization technique for comparing different quantitative values for
  36693. * a constrained number of categories.
  36694. * As with all other series, the Radar series must be appended in the *series* Chart array configuration. See the Chart
  36695. * documentation for more information. A typical configuration object for the radar series could be:
  36696. *
  36697. * @example
  36698. * Ext.create({
  36699. * xtype: 'polar',
  36700. * renderTo: document.body,
  36701. * width: 500,
  36702. * height: 400,
  36703. * interactions: 'rotate',
  36704. * store: {
  36705. * fields: ['name', 'data1'],
  36706. * data: [{
  36707. * 'name': 'metric one',
  36708. * 'data1': 8
  36709. * }, {
  36710. * 'name': 'metric two',
  36711. * 'data1': 10
  36712. * }, {
  36713. * 'name': 'metric three',
  36714. * 'data1': 12
  36715. * }, {
  36716. * 'name': 'metric four',
  36717. * 'data1': 1
  36718. * }, {
  36719. * 'name': 'metric five',
  36720. * 'data1': 13
  36721. * }]
  36722. * },
  36723. * series: {
  36724. * type: 'radar',
  36725. * angleField: 'name',
  36726. * radiusField: 'data1',
  36727. * style: {
  36728. * fillStyle: '#388FAD',
  36729. * fillOpacity: .1,
  36730. * strokeStyle: '#388FAD',
  36731. * strokeOpacity: .8,
  36732. * lineWidth: 1
  36733. * }
  36734. * },
  36735. * axes: [{
  36736. * type: 'numeric',
  36737. * position: 'radial',
  36738. * fields: 'data1',
  36739. * style: {
  36740. * estStepSize: 10
  36741. * },
  36742. * grid: true
  36743. * }, {
  36744. * type: 'category',
  36745. * position: 'angular',
  36746. * fields: 'name',
  36747. * style: {
  36748. * estStepSize: 1
  36749. * },
  36750. * grid: true
  36751. * }]
  36752. * });
  36753. *
  36754. */
  36755. Ext.define('Ext.chart.series.Radar', {
  36756. extend: 'Ext.chart.series.Polar',
  36757. type: 'radar',
  36758. seriesType: 'radar',
  36759. alias: 'series.radar',
  36760. requires: [
  36761. 'Ext.chart.series.sprite.Radar'
  36762. ],
  36763. themeColorCount: function() {
  36764. return 1;
  36765. },
  36766. isStoreDependantColorCount: false,
  36767. themeMarkerCount: function() {
  36768. return 1;
  36769. },
  36770. updateAngularAxis: function(axis) {
  36771. axis.processData(this);
  36772. },
  36773. updateRadialAxis: function(axis) {
  36774. axis.processData(this);
  36775. },
  36776. coordinateX: function() {
  36777. return this.coordinate('X', 0, 2);
  36778. },
  36779. coordinateY: function() {
  36780. return this.coordinate('Y', 1, 2);
  36781. },
  36782. updateCenter: function(center) {
  36783. this.setStyle({
  36784. translationX: center[0] + this.getOffsetX(),
  36785. translationY: center[1] + this.getOffsetY()
  36786. });
  36787. this.doUpdateStyles();
  36788. },
  36789. updateRadius: function(radius) {
  36790. this.setStyle({
  36791. endRho: radius
  36792. });
  36793. this.doUpdateStyles();
  36794. },
  36795. updateRotation: function(rotation) {
  36796. // Overrides base class method.
  36797. var me = this,
  36798. chart = me.getChart(),
  36799. axes = chart.getAxes(),
  36800. i, ln, axis;
  36801. for (i = 0 , ln = axes.length; i < ln; i++) {
  36802. axis = axes[i];
  36803. axis.setRotation(rotation);
  36804. }
  36805. me.setStyle({
  36806. rotationRads: rotation
  36807. });
  36808. me.doUpdateStyles();
  36809. },
  36810. updateTotalAngle: function(totalAngle) {
  36811. this.processData();
  36812. },
  36813. getItemForPoint: function(x, y) {
  36814. var me = this,
  36815. sprite = me.sprites && me.sprites[0],
  36816. attr = sprite.attr,
  36817. dataX = attr.dataX,
  36818. length = dataX.length,
  36819. store = me.getStore(),
  36820. marker = me.getMarker(),
  36821. threshhold, item, xy, i, bbox, markers;
  36822. if (me.getHidden()) {
  36823. return null;
  36824. }
  36825. if (sprite && marker) {
  36826. markers = sprite.getMarker('markers');
  36827. for (i = 0; i < length; i++) {
  36828. bbox = markers.getBBoxFor(i);
  36829. threshhold = (bbox.width + bbox.height) * 0.25;
  36830. xy = sprite.getDataPointXY(i);
  36831. if (Math.abs(xy[0] - x) < threshhold && Math.abs(xy[1] - y) < threshhold) {
  36832. item = {
  36833. series: me,
  36834. sprite: sprite,
  36835. index: i,
  36836. category: 'markers',
  36837. record: store.getData().items[i],
  36838. field: me.getYField()
  36839. };
  36840. return item;
  36841. }
  36842. }
  36843. }
  36844. return me.callParent(arguments);
  36845. },
  36846. getDefaultSpriteConfig: function() {
  36847. var config = this.callParent(),
  36848. animation = {
  36849. customDurations: {
  36850. translationX: 0,
  36851. translationY: 0,
  36852. rotationRads: 0,
  36853. // Prevent animation of 'dataMinX' and 'dataMaxX' attributes in order
  36854. // to react instantaniously to changes to the 'hidden' attribute.
  36855. dataMinX: 0,
  36856. dataMaxX: 0
  36857. }
  36858. };
  36859. if (config.animation) {
  36860. Ext.apply(config.animation, animation);
  36861. } else {
  36862. config.animation = animation;
  36863. }
  36864. return config;
  36865. },
  36866. getSprites: function() {
  36867. var me = this,
  36868. chart = me.getChart(),
  36869. sprites = me.sprites;
  36870. if (!chart) {
  36871. return Ext.emptyArray;
  36872. }
  36873. if (!sprites.length) {
  36874. me.createSprite();
  36875. }
  36876. return sprites;
  36877. },
  36878. provideLegendInfo: function(target) {
  36879. var me = this,
  36880. style = me.getSubStyleWithTheme(),
  36881. fill = style.fillStyle;
  36882. if (Ext.isArray(fill)) {
  36883. fill = fill[0];
  36884. }
  36885. target.push({
  36886. name: me.getTitle() || me.getYField() || me.getId(),
  36887. mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
  36888. disabled: me.getHidden(),
  36889. series: me.getId(),
  36890. index: 0
  36891. });
  36892. }
  36893. });
  36894. /**
  36895. * @class Ext.chart.series.sprite.Scatter
  36896. * @extends Ext.chart.series.sprite.Cartesian
  36897. *
  36898. * Scatter series sprite.
  36899. */
  36900. Ext.define('Ext.chart.series.sprite.Scatter', {
  36901. alias: 'sprite.scatterSeries',
  36902. extend: 'Ext.chart.series.sprite.Cartesian',
  36903. renderClipped: function(surface, ctx, dataClipRect, surfaceClipRect) {
  36904. if (this.cleanRedraw) {
  36905. return;
  36906. }
  36907. var me = this,
  36908. attr = me.attr,
  36909. dataX = attr.dataX,
  36910. dataY = attr.dataY,
  36911. labels = attr.labels,
  36912. series = me.getSeries(),
  36913. isDrawLabels = labels && me.getMarker('labels'),
  36914. surfaceMatrix = me.surfaceMatrix,
  36915. matrix = me.attr.matrix,
  36916. xx = matrix.getXX(),
  36917. yy = matrix.getYY(),
  36918. dx = matrix.getDX(),
  36919. dy = matrix.getDY(),
  36920. markerCfg = {},
  36921. changes, params,
  36922. xScalingDirection = surface.getInherited().rtl && !attr.flipXY ? -1 : 1,
  36923. left, right, top, bottom, x, y, i;
  36924. if (attr.flipXY) {
  36925. left = surfaceClipRect[1] - xx * xScalingDirection;
  36926. right = surfaceClipRect[1] + surfaceClipRect[3] + xx * xScalingDirection;
  36927. top = surfaceClipRect[0] - yy;
  36928. bottom = surfaceClipRect[0] + surfaceClipRect[2] + yy;
  36929. } else {
  36930. left = surfaceClipRect[0] - xx * xScalingDirection;
  36931. right = surfaceClipRect[0] + surfaceClipRect[2] + xx * xScalingDirection;
  36932. top = surfaceClipRect[1] - yy;
  36933. bottom = surfaceClipRect[1] + surfaceClipRect[3] + yy;
  36934. }
  36935. for (i = 0; i < dataX.length; i++) {
  36936. x = dataX[i];
  36937. y = dataY[i];
  36938. x = x * xx + dx;
  36939. y = y * yy + dy;
  36940. if (left <= x && x <= right && top <= y && y <= bottom) {
  36941. if (attr.renderer) {
  36942. markerCfg = {
  36943. type: 'markers',
  36944. translationX: surfaceMatrix.x(x, y),
  36945. translationY: surfaceMatrix.y(x, y)
  36946. };
  36947. params = [
  36948. me,
  36949. markerCfg,
  36950. {
  36951. store: me.getStore()
  36952. },
  36953. i
  36954. ];
  36955. changes = Ext.callback(attr.renderer, null, params, 0, series);
  36956. markerCfg = Ext.apply(markerCfg, changes);
  36957. } else {
  36958. markerCfg.translationX = surfaceMatrix.x(x, y);
  36959. markerCfg.translationY = surfaceMatrix.y(x, y);
  36960. }
  36961. me.putMarker('markers', markerCfg, i, !attr.renderer);
  36962. if (isDrawLabels && labels[i]) {
  36963. me.drawLabel(labels[i], x, y, i, surfaceClipRect);
  36964. }
  36965. }
  36966. }
  36967. },
  36968. drawLabel: function(text, dataX, dataY, labelId, rect) {
  36969. var me = this,
  36970. attr = me.attr,
  36971. label = me.getMarker('labels'),
  36972. labelTpl = label.getTemplate(),
  36973. labelCfg = me.labelCfg || (me.labelCfg = {}),
  36974. surfaceMatrix = me.surfaceMatrix,
  36975. labelX, labelY,
  36976. labelOverflowPadding = attr.labelOverflowPadding,
  36977. flipXY = attr.flipXY,
  36978. halfHeight, labelBox, changes, params;
  36979. labelCfg.text = text;
  36980. labelBox = me.getMarkerBBox('labels', labelId, true);
  36981. if (!labelBox) {
  36982. me.putMarker('labels', labelCfg, labelId);
  36983. labelBox = me.getMarkerBBox('labels', labelId, true);
  36984. }
  36985. if (flipXY) {
  36986. labelCfg.rotationRads = Math.PI * 0.5;
  36987. } else {
  36988. labelCfg.rotationRads = 0;
  36989. }
  36990. halfHeight = labelBox.height / 2;
  36991. labelX = dataX;
  36992. switch (labelTpl.attr.display) {
  36993. case 'under':
  36994. labelY = dataY - halfHeight - labelOverflowPadding;
  36995. break;
  36996. case 'rotate':
  36997. labelX += labelOverflowPadding;
  36998. labelY = dataY - labelOverflowPadding;
  36999. labelCfg.rotationRads = -Math.PI / 4;
  37000. break;
  37001. default:
  37002. // 'over'
  37003. labelY = dataY + halfHeight + labelOverflowPadding;
  37004. }
  37005. labelCfg.x = surfaceMatrix.x(labelX, labelY);
  37006. labelCfg.y = surfaceMatrix.y(labelX, labelY);
  37007. if (labelTpl.attr.renderer) {
  37008. params = [
  37009. text,
  37010. label,
  37011. labelCfg,
  37012. {
  37013. store: me.getStore()
  37014. },
  37015. labelId
  37016. ];
  37017. changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
  37018. if (typeof changes === 'string') {
  37019. labelCfg.text = changes;
  37020. } else {
  37021. Ext.apply(labelCfg, changes);
  37022. }
  37023. }
  37024. me.putMarker('labels', labelCfg, labelId);
  37025. }
  37026. });
  37027. /**
  37028. * @class Ext.chart.series.Scatter
  37029. * @extends Ext.chart.series.Cartesian
  37030. *
  37031. * Creates a Scatter Chart. The scatter plot is useful when trying to display more than two variables in the same visualization.
  37032. * These variables can be mapped into x, y coordinates and also to an element's radius/size, color, etc.
  37033. * As with all other series, the Scatter Series must be appended in the *series* Chart array configuration. See the Chart
  37034. * documentation for more information on creating charts. A typical configuration object for the scatter could be:
  37035. *
  37036. * @example
  37037. * Ext.create({
  37038. * xtype: 'cartesian',
  37039. * renderTo: document.body,
  37040. * width: 600,
  37041. * height: 400,
  37042. * insetPadding: 40,
  37043. * interactions: ['itemhighlight'],
  37044. * store: {
  37045. * fields: ['name', 'data1', 'data2'],
  37046. * data: [{
  37047. * 'name': 'metric one',
  37048. * 'data1': 10,
  37049. * 'data2': 14
  37050. * }, {
  37051. * 'name': 'metric two',
  37052. * 'data1': 7,
  37053. * 'data2': 16
  37054. * }, {
  37055. * 'name': 'metric three',
  37056. * 'data1': 5,
  37057. * 'data2': 14
  37058. * }, {
  37059. * 'name': 'metric four',
  37060. * 'data1': 2,
  37061. * 'data2': 6
  37062. * }, {
  37063. * 'name': 'metric five',
  37064. * 'data1': 27,
  37065. * 'data2': 36
  37066. * }]
  37067. * },
  37068. * axes: [{
  37069. * type: 'numeric',
  37070. * position: 'left',
  37071. * fields: ['data1'],
  37072. * title: {
  37073. * text: 'Sample Values',
  37074. * fontSize: 15
  37075. * },
  37076. * grid: true,
  37077. * minimum: 0
  37078. * }, {
  37079. * type: 'category',
  37080. * position: 'bottom',
  37081. * fields: ['name'],
  37082. * title: {
  37083. * text: 'Sample Values',
  37084. * fontSize: 15
  37085. * }
  37086. * }],
  37087. * series: {
  37088. * type: 'scatter',
  37089. * highlight: {
  37090. * size: 12,
  37091. * radius: 12,
  37092. * fill: '#96D4C6',
  37093. * stroke: '#30BDA7'
  37094. * },
  37095. * fill: true,
  37096. * xField: 'name',
  37097. * yField: 'data2',
  37098. * marker: {
  37099. * type: 'circle',
  37100. * fill: '#30BDA7',
  37101. * radius: 10,
  37102. * lineWidth: 0
  37103. * }
  37104. * }
  37105. * });
  37106. *
  37107. * In this configuration we add three different categories of scatter series. Each of them is bound to a different field of the same data store,
  37108. * `data1`, `data2` and `data3` respectively. All x-fields for the series must be the same field, in this case `name`.
  37109. * Each scatter series has a different styling configuration for markers, specified by the `marker` object. Finally we set the left axis as
  37110. * axis to show the current values of the elements.
  37111. *
  37112. */
  37113. Ext.define('Ext.chart.series.Scatter', {
  37114. extend: 'Ext.chart.series.Cartesian',
  37115. alias: 'series.scatter',
  37116. type: 'scatter',
  37117. seriesType: 'scatterSeries',
  37118. requires: [
  37119. 'Ext.chart.series.sprite.Scatter'
  37120. ],
  37121. config: {
  37122. itemInstancing: null,
  37123. marker: true
  37124. },
  37125. themeMarkerCount: function() {
  37126. return 1;
  37127. },
  37128. provideLegendInfo: function(target) {
  37129. var me = this,
  37130. style = me.getMarkerStyleByIndex(0),
  37131. fill = style.fillStyle;
  37132. target.push({
  37133. name: me.getTitle() || me.getYField() || me.getId(),
  37134. mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
  37135. disabled: me.getHidden(),
  37136. series: me.getId(),
  37137. index: 0
  37138. });
  37139. }
  37140. });
  37141. Ext.define('Ext.chart.theme.Blue', {
  37142. extend: 'Ext.chart.theme.Base',
  37143. singleton: true,
  37144. alias: [
  37145. 'chart.theme.blue',
  37146. 'chart.theme.Blue'
  37147. ],
  37148. config: {
  37149. baseColor: '#4d7fe6'
  37150. }
  37151. });
  37152. Ext.define('Ext.chart.theme.BlueGradients', {
  37153. extend: 'Ext.chart.theme.Base',
  37154. singleton: true,
  37155. alias: [
  37156. 'chart.theme.blue-gradients',
  37157. 'chart.theme.Blue:gradients'
  37158. ],
  37159. config: {
  37160. baseColor: '#4d7fe6',
  37161. gradients: {
  37162. type: 'linear',
  37163. degrees: 90
  37164. }
  37165. }
  37166. });
  37167. Ext.define('Ext.chart.theme.Category1', {
  37168. extend: 'Ext.chart.theme.Base',
  37169. singleton: true,
  37170. alias: [
  37171. 'chart.theme.category1',
  37172. 'chart.theme.Category1'
  37173. ],
  37174. config: {
  37175. colors: [
  37176. '#f0a50a',
  37177. '#c20024',
  37178. '#2044ba',
  37179. '#810065',
  37180. '#7eae29'
  37181. ]
  37182. }
  37183. });
  37184. Ext.define('Ext.chart.theme.Category1Gradients', {
  37185. extend: 'Ext.chart.theme.Base',
  37186. singleton: true,
  37187. alias: [
  37188. 'chart.theme.category1-gradients',
  37189. 'chart.theme.Category1:gradients'
  37190. ],
  37191. config: {
  37192. colors: [
  37193. '#f0a50a',
  37194. '#c20024',
  37195. '#2044ba',
  37196. '#810065',
  37197. '#7eae29'
  37198. ],
  37199. gradients: {
  37200. type: 'linear',
  37201. degrees: 90
  37202. }
  37203. }
  37204. });
  37205. Ext.define('Ext.chart.theme.Category2', {
  37206. extend: 'Ext.chart.theme.Base',
  37207. singleton: true,
  37208. alias: [
  37209. 'chart.theme.category2',
  37210. 'chart.theme.Category2'
  37211. ],
  37212. config: {
  37213. colors: [
  37214. '#6d9824',
  37215. '#87146e',
  37216. '#2a9196',
  37217. '#d39006',
  37218. '#1e40ac'
  37219. ]
  37220. }
  37221. });
  37222. Ext.define('Ext.chart.theme.Category2Gradients', {
  37223. extend: 'Ext.chart.theme.Base',
  37224. singleton: true,
  37225. alias: [
  37226. 'chart.theme.category2-gradients',
  37227. 'chart.theme.Category2:gradients'
  37228. ],
  37229. config: {
  37230. colors: [
  37231. '#6d9824',
  37232. '#87146e',
  37233. '#2a9196',
  37234. '#d39006',
  37235. '#1e40ac'
  37236. ],
  37237. gradients: {
  37238. type: 'linear',
  37239. degrees: 90
  37240. }
  37241. }
  37242. });
  37243. Ext.define('Ext.chart.theme.Category3', {
  37244. extend: 'Ext.chart.theme.Base',
  37245. singleton: true,
  37246. alias: [
  37247. 'chart.theme.category3',
  37248. 'chart.theme.Category3'
  37249. ],
  37250. config: {
  37251. colors: [
  37252. '#fbbc29',
  37253. '#ce2e4e',
  37254. '#7e0062',
  37255. '#158b90',
  37256. '#57880e'
  37257. ]
  37258. }
  37259. });
  37260. Ext.define('Ext.chart.theme.Category3Gradients', {
  37261. extend: 'Ext.chart.theme.Base',
  37262. singleton: true,
  37263. alias: [
  37264. 'chart.theme.category3-gradients',
  37265. 'chart.theme.Category3:gradients'
  37266. ],
  37267. config: {
  37268. colors: [
  37269. '#fbbc29',
  37270. '#ce2e4e',
  37271. '#7e0062',
  37272. '#158b90',
  37273. '#57880e'
  37274. ],
  37275. gradients: {
  37276. type: 'linear',
  37277. degrees: 90
  37278. }
  37279. }
  37280. });
  37281. Ext.define('Ext.chart.theme.Category4', {
  37282. extend: 'Ext.chart.theme.Base',
  37283. singleton: true,
  37284. alias: [
  37285. 'chart.theme.category4',
  37286. 'chart.theme.Category4'
  37287. ],
  37288. config: {
  37289. colors: [
  37290. '#ef5773',
  37291. '#fcbd2a',
  37292. '#4f770d',
  37293. '#1d3eaa',
  37294. '#9b001f'
  37295. ]
  37296. }
  37297. });
  37298. Ext.define('Ext.chart.theme.Category4Gradients', {
  37299. extend: 'Ext.chart.theme.Base',
  37300. singleton: true,
  37301. alias: [
  37302. 'chart.theme.category4-gradients',
  37303. 'chart.theme.Category4:gradients'
  37304. ],
  37305. config: {
  37306. colors: [
  37307. '#ef5773',
  37308. '#fcbd2a',
  37309. '#4f770d',
  37310. '#1d3eaa',
  37311. '#9b001f'
  37312. ],
  37313. gradients: {
  37314. type: 'linear',
  37315. degrees: 90
  37316. }
  37317. }
  37318. });
  37319. Ext.define('Ext.chart.theme.Category5', {
  37320. extend: 'Ext.chart.theme.Base',
  37321. singleton: true,
  37322. alias: [
  37323. 'chart.theme.category5',
  37324. 'chart.theme.Category5'
  37325. ],
  37326. config: {
  37327. colors: [
  37328. '#7eae29',
  37329. '#fdbe2a',
  37330. '#910019',
  37331. '#27b4bc',
  37332. '#d74dbc'
  37333. ]
  37334. }
  37335. });
  37336. Ext.define('Ext.chart.theme.Category5Gradients', {
  37337. extend: 'Ext.chart.theme.Base',
  37338. singleton: true,
  37339. alias: [
  37340. 'chart.theme.category5-gradients',
  37341. 'chart.theme.Category5:gradients'
  37342. ],
  37343. config: {
  37344. colors: [
  37345. '#7eae29',
  37346. '#fdbe2a',
  37347. '#910019',
  37348. '#27b4bc',
  37349. '#d74dbc'
  37350. ],
  37351. gradients: {
  37352. type: 'linear',
  37353. degrees: 90
  37354. }
  37355. }
  37356. });
  37357. Ext.define('Ext.chart.theme.Category6', {
  37358. extend: 'Ext.chart.theme.Base',
  37359. singleton: true,
  37360. alias: [
  37361. 'chart.theme.category6',
  37362. 'chart.theme.Category6'
  37363. ],
  37364. config: {
  37365. colors: [
  37366. '#44dce1',
  37367. '#0b2592',
  37368. '#996e05',
  37369. '#7fb325',
  37370. '#b821a1'
  37371. ]
  37372. }
  37373. });
  37374. Ext.define('Ext.chart.theme.Category6Gradients', {
  37375. extend: 'Ext.chart.theme.Base',
  37376. singleton: true,
  37377. alias: [
  37378. 'chart.theme.category6-gradients',
  37379. 'chart.theme.Category6:gradients'
  37380. ],
  37381. config: {
  37382. colors: [
  37383. '#44dce1',
  37384. '#0b2592',
  37385. '#996e05',
  37386. '#7fb325',
  37387. '#b821a1'
  37388. ],
  37389. gradients: {
  37390. type: 'linear',
  37391. degrees: 90
  37392. }
  37393. }
  37394. });
  37395. Ext.define('Ext.chart.theme.DefaultGradients', {
  37396. extend: 'Ext.chart.theme.Base',
  37397. singleton: true,
  37398. alias: [
  37399. 'chart.theme.default-gradients',
  37400. 'chart.theme.Base:gradients'
  37401. ],
  37402. config: {
  37403. gradients: {
  37404. type: 'linear',
  37405. degrees: 90
  37406. }
  37407. }
  37408. });
  37409. Ext.define('Ext.chart.theme.Green', {
  37410. extend: 'Ext.chart.theme.Base',
  37411. singleton: true,
  37412. alias: [
  37413. 'chart.theme.green',
  37414. 'chart.theme.Green'
  37415. ],
  37416. config: {
  37417. baseColor: '#b1da5a'
  37418. }
  37419. });
  37420. Ext.define('Ext.chart.theme.GreenGradients', {
  37421. extend: 'Ext.chart.theme.Base',
  37422. singleton: true,
  37423. alias: [
  37424. 'chart.theme.green-gradients',
  37425. 'chart.theme.Green:gradients'
  37426. ],
  37427. config: {
  37428. baseColor: '#b1da5a',
  37429. gradients: {
  37430. type: 'linear',
  37431. degrees: 90
  37432. }
  37433. }
  37434. });
  37435. Ext.define('Ext.chart.theme.Midnight', {
  37436. extend: 'Ext.chart.theme.Base',
  37437. singleton: true,
  37438. alias: [
  37439. 'chart.theme.midnight',
  37440. 'chart.theme.Midnight'
  37441. ],
  37442. config: {
  37443. colors: [
  37444. '#a837ff',
  37445. '#4ac0f2',
  37446. '#ff4d35',
  37447. '#ff8809',
  37448. '#61c102',
  37449. '#ff37ea'
  37450. ],
  37451. chart: {
  37452. defaults: {
  37453. captions: {
  37454. title: {
  37455. docked: 'top',
  37456. padding: 5,
  37457. style: {
  37458. textAlign: 'center',
  37459. fontFamily: 'default',
  37460. fontWeight: 'bold',
  37461. fillStyle: 'rgb(224, 224, 227)',
  37462. fontSize: 'default*1.6'
  37463. }
  37464. },
  37465. subtitle: {
  37466. docked: 'top',
  37467. style: {
  37468. textAlign: 'center',
  37469. fontFamily: 'default',
  37470. fontWeight: 'normal',
  37471. fillStyle: 'rgb(224, 224, 227)',
  37472. fontSize: 'default*1.3'
  37473. }
  37474. },
  37475. credits: {
  37476. docked: 'bottom',
  37477. padding: 5,
  37478. style: {
  37479. textAlign: 'left',
  37480. fontFamily: 'default',
  37481. fontWeight: 'lighter',
  37482. fillStyle: 'rgb(224, 224, 227)',
  37483. fontSize: 'default'
  37484. }
  37485. }
  37486. },
  37487. background: 'rgb(52, 52, 53)'
  37488. }
  37489. },
  37490. axis: {
  37491. defaults: {
  37492. style: {
  37493. strokeStyle: 'rgb(224, 224, 227)'
  37494. },
  37495. label: {
  37496. fillStyle: 'rgb(224, 224, 227)'
  37497. },
  37498. title: {
  37499. fillStyle: 'rgb(224, 224, 227)'
  37500. },
  37501. grid: {
  37502. strokeStyle: 'rgb(112, 112, 115)'
  37503. }
  37504. }
  37505. },
  37506. series: {
  37507. defaults: {
  37508. label: {
  37509. fillStyle: 'rgb(224, 224, 227)'
  37510. }
  37511. }
  37512. },
  37513. sprites: {
  37514. text: {
  37515. fillStyle: 'rgb(224, 224, 227)'
  37516. }
  37517. },
  37518. legend: {
  37519. label: {
  37520. fillStyle: 'white'
  37521. },
  37522. border: {
  37523. lineWidth: 2,
  37524. fillStyle: 'rgba(255, 255, 255, 0.3)',
  37525. strokeStyle: 'rgb(150, 150, 150)'
  37526. },
  37527. background: 'rgb(52, 52, 53)'
  37528. }
  37529. }
  37530. });
  37531. Ext.define('Ext.chart.theme.Muted', {
  37532. extend: 'Ext.chart.theme.Base',
  37533. singleton: true,
  37534. alias: [
  37535. 'chart.theme.muted',
  37536. 'chart.theme.Muted'
  37537. ],
  37538. config: {
  37539. colors: [
  37540. '#8ca640',
  37541. '#974144',
  37542. '#4091ba',
  37543. '#8e658e',
  37544. '#3b8d8b',
  37545. '#b86465',
  37546. '#d2af69',
  37547. '#6e8852',
  37548. '#3dcc7e',
  37549. '#a6bed1',
  37550. '#cbaa4b',
  37551. '#998baa'
  37552. ]
  37553. }
  37554. });
  37555. Ext.define('Ext.chart.theme.Purple', {
  37556. extend: 'Ext.chart.theme.Base',
  37557. singleton: true,
  37558. alias: [
  37559. 'chart.theme.purple',
  37560. 'chart.theme.Purple'
  37561. ],
  37562. config: {
  37563. baseColor: '#da5abd'
  37564. }
  37565. });
  37566. Ext.define('Ext.chart.theme.PurpleGradients', {
  37567. extend: 'Ext.chart.theme.Base',
  37568. singleton: true,
  37569. alias: [
  37570. 'chart.theme.purple-gradients',
  37571. 'chart.theme.Purple:gradients'
  37572. ],
  37573. config: {
  37574. baseColor: '#da5abd',
  37575. gradients: {
  37576. type: 'linear',
  37577. degrees: 90
  37578. }
  37579. }
  37580. });
  37581. Ext.define('Ext.chart.theme.Red', {
  37582. extend: 'Ext.chart.theme.Base',
  37583. singleton: true,
  37584. alias: [
  37585. 'chart.theme.red',
  37586. 'chart.theme.Red'
  37587. ],
  37588. config: {
  37589. baseColor: '#e84b67'
  37590. }
  37591. });
  37592. Ext.define('Ext.chart.theme.RedGradients', {
  37593. extend: 'Ext.chart.theme.Base',
  37594. singleton: true,
  37595. alias: [
  37596. 'chart.theme.red-gradients',
  37597. 'chart.theme.Red:gradients'
  37598. ],
  37599. config: {
  37600. baseColor: '#e84b67',
  37601. gradients: {
  37602. type: 'linear',
  37603. degrees: 90
  37604. }
  37605. }
  37606. });
  37607. Ext.define('Ext.chart.theme.Sky', {
  37608. extend: 'Ext.chart.theme.Base',
  37609. singleton: true,
  37610. alias: [
  37611. 'chart.theme.sky',
  37612. 'chart.theme.Sky'
  37613. ],
  37614. config: {
  37615. baseColor: '#4ce0e7'
  37616. }
  37617. });
  37618. Ext.define('Ext.chart.theme.SkyGradients', {
  37619. extend: 'Ext.chart.theme.Base',
  37620. singleton: true,
  37621. alias: [
  37622. 'chart.theme.sky-gradients',
  37623. 'chart.theme.Sky:gradients'
  37624. ],
  37625. config: {
  37626. baseColor: '#4ce0e7',
  37627. gradients: {
  37628. type: 'linear',
  37629. degrees: 90
  37630. }
  37631. }
  37632. });
  37633. Ext.define('Ext.chart.theme.Yellow', {
  37634. extend: 'Ext.chart.theme.Base',
  37635. singleton: true,
  37636. alias: [
  37637. 'chart.theme.yellow',
  37638. 'chart.theme.Yellow'
  37639. ],
  37640. config: {
  37641. baseColor: '#fec935'
  37642. }
  37643. });
  37644. Ext.define('Ext.chart.theme.YellowGradients', {
  37645. extend: 'Ext.chart.theme.Base',
  37646. singleton: true,
  37647. alias: [
  37648. 'chart.theme.yellow-gradients',
  37649. 'chart.theme.Yellow:gradients'
  37650. ],
  37651. config: {
  37652. baseColor: '#fec935',
  37653. gradients: {
  37654. type: 'linear',
  37655. degrees: 90
  37656. }
  37657. }
  37658. });
  37659. /**
  37660. * A helper class to facilitate common operations on points and vectors.
  37661. */
  37662. Ext.define('Ext.draw.Point', {
  37663. requires: [
  37664. 'Ext.draw.Draw',
  37665. 'Ext.draw.Matrix'
  37666. ],
  37667. isPoint: true,
  37668. x: 0,
  37669. y: 0,
  37670. length: 0,
  37671. angle: 0,
  37672. angleUnits: 'degrees',
  37673. statics: {
  37674. /**
  37675. * @method
  37676. * @static
  37677. * Creates a flyweight Ext.draw.Point instance.
  37678. * Takes the same parameters as the {@link Ext.draw.Point#method!constructor}.
  37679. * Do not hold the instance of the flyweight point.
  37680. *
  37681. * @param {Number/Number[]/Object/Ext.draw.Point} point
  37682. * @return {Ext.draw.Point}
  37683. */
  37684. fly: (function() {
  37685. var point = null;
  37686. return function(x, y) {
  37687. if (!point) {
  37688. point = new Ext.draw.Point();
  37689. }
  37690. point.constructor(x, y);
  37691. return point;
  37692. };
  37693. })()
  37694. },
  37695. /**
  37696. * Creates a point.
  37697. *
  37698. * new Ext.draw.Point(3, 4);
  37699. * new Ext.draw.Point(3); // both x and y equal 3
  37700. * new Ext.draw.Point([3, 4]);
  37701. * new Ext.draw.Point({x: 3, y: 4});
  37702. * new Ext.draw.Point(p); // where `p` is a Ext.draw.Point instance.
  37703. *
  37704. * @param {Number/Number[]/Object/Ext.draw.Point} x
  37705. * @param {Number/Number[]/Object/Ext.draw.Point} y
  37706. */
  37707. constructor: function(x, y) {
  37708. var me = this;
  37709. if (typeof x === 'number') {
  37710. me.x = x;
  37711. if (typeof y === 'number') {
  37712. me.y = y;
  37713. } else {
  37714. me.y = x;
  37715. }
  37716. } else if (Ext.isArray(x)) {
  37717. me.x = x[0];
  37718. me.y = x[1];
  37719. } else if (x) {
  37720. me.x = x.x;
  37721. me.y = x.y;
  37722. }
  37723. me.calculatePolar();
  37724. },
  37725. calculateCartesian: function() {
  37726. var me = this,
  37727. length = me.length,
  37728. angle = me.angle;
  37729. if (me.angleUnits === 'degrees') {
  37730. angle = Ext.draw.Draw.rad(angle);
  37731. }
  37732. me.x = Math.cos(angle) * length;
  37733. me.y = Math.sin(angle) * length;
  37734. },
  37735. calculatePolar: function() {
  37736. var me = this,
  37737. x = me.x,
  37738. y = me.y;
  37739. me.length = Math.sqrt(x * x + y * y);
  37740. me.angle = Math.atan2(y, x);
  37741. if (me.angleUnits === 'degrees') {
  37742. me.angle = Ext.draw.Draw.degrees(me.angle);
  37743. }
  37744. },
  37745. /**
  37746. * Sets the x-coordinate of the point.
  37747. * @param {Number} x
  37748. */
  37749. setX: function(x) {
  37750. this.x = x;
  37751. this.calculatePolar();
  37752. },
  37753. /**
  37754. * Sets the y-coordinate of the point.
  37755. * @param {Number} y
  37756. */
  37757. setY: function(y) {
  37758. this.y = y;
  37759. this.calculatePolar();
  37760. },
  37761. /**
  37762. * Sets coordinates of the point.
  37763. * Takes the same parameters as the {@link #method!constructor}.
  37764. * @param {Number/Number[]/Object/Ext.draw.Point} x
  37765. * @param {Number/Number[]/Object/Ext.draw.Point} y
  37766. */
  37767. set: function(x, y) {
  37768. this.constructor(x, y);
  37769. },
  37770. /**
  37771. * Sets the angle of the vector (measured from the x-axis to the vector)
  37772. * without changing its length.
  37773. * @param {Number} angle
  37774. */
  37775. setAngle: function(angle) {
  37776. this.angle = angle;
  37777. this.calculateCartesian();
  37778. },
  37779. /**
  37780. * Sets the length of the vector without changing its angle.
  37781. * @param {Number} length
  37782. */
  37783. setLength: function(length) {
  37784. this.length = length;
  37785. this.calculateCartesian();
  37786. },
  37787. /**
  37788. * Sets both the angle and the length of the vector.
  37789. * A point can be thought of as a vector pointing from the origin to the point's location.
  37790. * This can also be interpreted as setting coordinates of a point in the polar
  37791. * coordinate system.
  37792. * @param {Number} angle
  37793. * @param {Number} length
  37794. */
  37795. setPolar: function(angle, length) {
  37796. this.angle = angle;
  37797. this.length = length;
  37798. this.calculateCartesian();
  37799. },
  37800. /**
  37801. * Returns a copy of the point.
  37802. * @return {Ext.draw.Point}
  37803. */
  37804. clone: function() {
  37805. return new Ext.draw.Point(this.x, this.y);
  37806. },
  37807. /**
  37808. * Adds another vector to this one and returns the resulting vector
  37809. * without changing this vector.
  37810. * @param {Number/Number[]/Object/Ext.draw.Point} x
  37811. * @param {Number/Number[]/Object/Ext.draw.Point} y
  37812. * @return {Ext.draw.Point}
  37813. */
  37814. add: function(x, y) {
  37815. var fly = Ext.draw.Point.fly(x, y);
  37816. return new Ext.draw.Point(this.x + fly.x, this.y + fly.y);
  37817. },
  37818. /**
  37819. * Subtracts another vector from this one and returns the resulting vector
  37820. * without changing this vector.
  37821. * @param {Number/Number[]/Object/Ext.draw.Point} x
  37822. * @param {Number/Number[]/Object/Ext.draw.Point} y
  37823. * @return {Ext.draw.Point}
  37824. */
  37825. sub: function(x, y) {
  37826. var fly = Ext.draw.Point.fly(x, y);
  37827. return new Ext.draw.Point(this.x - fly.x, this.y - fly.y);
  37828. },
  37829. /**
  37830. * Returns the result of scalar multiplication of this vector by the given factor.
  37831. * This vector is not modified.
  37832. * @param {Number} n The factor.
  37833. * @return {Ext.draw.Point}
  37834. */
  37835. mul: function(n) {
  37836. return new Ext.draw.Point(this.x * n, this.y * n);
  37837. },
  37838. /**
  37839. * Returns a vector which coordinates are the result of division of this vector's
  37840. * coordinates by the given number. This vector is not modified.
  37841. * This vector is not modified.
  37842. * @param {Number} n The denominator.
  37843. * @return {Ext.draw.Point}
  37844. */
  37845. div: function(n) {
  37846. return new Ext.draw.Point(this.x / n, this.y / n);
  37847. },
  37848. /**
  37849. * Returns the dot product of this vector and the given vector.
  37850. * @param {Number/Number[]/Object/Ext.draw.Point} x
  37851. * @param {Number/Number[]/Object/Ext.draw.Point} y
  37852. * @return {Number}
  37853. */
  37854. dot: function(x, y) {
  37855. var fly = Ext.draw.Point.fly(x, y);
  37856. return this.x * fly.x + this.y * fly.y;
  37857. },
  37858. /**
  37859. * Checks whether coordinates of the point match those of the point provided.
  37860. * @param {Number/Number[]/Object/Ext.draw.Point} x
  37861. * @param {Number/Number[]/Object/Ext.draw.Point} y
  37862. * @return {Boolean}
  37863. */
  37864. equals: function(x, y) {
  37865. var fly = Ext.draw.Point.fly(x, y);
  37866. return this.x === fly.x && this.y === fly.y;
  37867. },
  37868. /**
  37869. * Rotates the point by the given angle. This point is not modified.
  37870. * @param {Number} angle The rotation angle.
  37871. * @param {Ext.draw.Point} [center] The center of rotation (optional). Defaults to origin.
  37872. * @return {Ext.draw.Point} The rotated point.
  37873. */
  37874. rotate: function(angle, center) {
  37875. var sin, cos, cx, cy, point;
  37876. if (this.angleUnits === 'degrees') {
  37877. angle = Ext.draw.Draw.rad(angle);
  37878. sin = Math.sin(angle);
  37879. cos = Math.cos(angle);
  37880. }
  37881. if (center) {
  37882. cx = center.x;
  37883. cy = center.y;
  37884. } else {
  37885. cx = 0;
  37886. cy = 0;
  37887. }
  37888. point = Ext.draw.Matrix.fly([
  37889. cos,
  37890. sin,
  37891. -sin,
  37892. cos,
  37893. cx - cos * cx + cy * sin,
  37894. cy - cos * cy + cx * -sin
  37895. ]).transformPoint(this);
  37896. return new Ext.draw.Point(point);
  37897. },
  37898. /**
  37899. * Transforms the point from one coordinate system to another
  37900. * using the transformation matrix provided. This point is not modified.
  37901. * @param {Ext.draw.Matrix/Number[]} matrix A trasformation matrix or its elements.
  37902. * @return {Ext.draw.Point}
  37903. */
  37904. transform: function(matrix) {
  37905. if (matrix && matrix.isMatrix) {
  37906. return new Ext.draw.Point(matrix.transformPoint(this));
  37907. } else if (arguments.length === 6) {
  37908. return new Ext.draw.Point(Ext.draw.Matrix.fly(arguments).transformPoint(this));
  37909. } else {
  37910. Ext.raise("Invalid parameters.");
  37911. }
  37912. },
  37913. /**
  37914. * Returns a new point with rounded x and y values. This point is not modified.
  37915. * @return {Ext.draw.Point}
  37916. */
  37917. round: function() {
  37918. return new Ext.draw.Point(Math.round(this.x), Math.round(this.y));
  37919. },
  37920. /**
  37921. * Returns a new point with ceiled x and y values. This point is not modified.
  37922. * @return {Ext.draw.Point}
  37923. */
  37924. ceil: function() {
  37925. return new Ext.draw.Point(Math.ceil(this.x), Math.ceil(this.y));
  37926. },
  37927. /**
  37928. * Returns a new point with floored x and y values. This point is not modified.
  37929. * @return {Ext.draw.Point}
  37930. */
  37931. floor: function() {
  37932. return new Ext.draw.Point(Math.floor(this.x), Math.floor(this.y));
  37933. },
  37934. /**
  37935. * Returns a new point with absolute values of the x and y values of this point.
  37936. * This point is not modified.
  37937. * @return {Ext.draw.Point}
  37938. */
  37939. abs: function(x, y) {
  37940. return new Ext.draw.Point(Math.abs(this.x), Math.abs(this.y));
  37941. },
  37942. /**
  37943. * Normalizes the vector by changing its length to 1 without changing its angle.
  37944. * The returned result is a normalized vector. This vector is not modified.
  37945. * @param {Number} [factor=1] Multiplication factor. Defaults to 1.
  37946. * @return {Ext.draw.Point}
  37947. */
  37948. normalize: function(factor) {
  37949. var x = this.x,
  37950. y = this.y,
  37951. k = (factor || 1) / Math.sqrt(x * x + y * y);
  37952. return new Ext.draw.Point(x * k, y * k);
  37953. },
  37954. /**
  37955. * Returns the vector from the point perpendicular to the line (shortest distance).
  37956. * Where line is specified using two points or the coordinates of those points.
  37957. * @param {Ext.draw.Point} p1
  37958. * @param {Ext.draw.Point} p2
  37959. * @return {Ext.draw.Point}
  37960. */
  37961. getDistanceToLine: function(p1, p2) {
  37962. if (arguments.length === 4) {
  37963. p1 = new Ext.draw.Point(arguments[0], arguments[1]);
  37964. p2 = new Ext.draw.Point(arguments[2], arguments[3]);
  37965. }
  37966. // See http://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Vector_formulation
  37967. var n = p2.sub(p1).normalize(),
  37968. pp1 = p1.sub(this);
  37969. return pp1.sub(n.mul(pp1.dot(n)));
  37970. },
  37971. /**
  37972. * Checks if both x and y coordinates of the point are zero.
  37973. * @return {Boolean}
  37974. */
  37975. isZero: function() {
  37976. return this.x === 0 && this.y === 0;
  37977. },
  37978. /**
  37979. * Checks if both x and y coordinates of the point are valid numbers.
  37980. * @return {Boolean}
  37981. */
  37982. isNumber: function() {
  37983. return Ext.isNumber(this.x) && Ext.isNumber(this.y);
  37984. }
  37985. });
  37986. /**
  37987. * A draw container {@link Ext.AbstractPlugin plugin} that adds ability to listen
  37988. * to sprite events. For example:
  37989. *
  37990. * var drawContainer = Ext.create('Ext.draw.Container', {
  37991. * plugins: {
  37992. * spriteevents: true
  37993. * },
  37994. * renderTo: Ext.getBody(),
  37995. * width: 200,
  37996. * height: 200,
  37997. * sprites: [{
  37998. * type: 'circle',
  37999. * fillStyle: '#79BB3F',
  38000. * r: 50,
  38001. * x: 100,
  38002. * y: 100
  38003. * }],
  38004. * listeners: {
  38005. * spriteclick: function (item, event) {
  38006. * var sprite = item && item.sprite;
  38007. * if (sprite) {
  38008. * sprite.setAttributes({fillStyle: 'red'});
  38009. sprite.getSurface().renderFrame();
  38010. * }
  38011. * }
  38012. * }
  38013. * });
  38014. */
  38015. Ext.define('Ext.draw.plugin.SpriteEvents', {
  38016. extend: 'Ext.plugin.Abstract',
  38017. alias: 'plugin.spriteevents',
  38018. requires: [
  38019. 'Ext.draw.overrides.hittest.All'
  38020. ],
  38021. /**
  38022. * @event spritemousemove
  38023. * Fires when the mouse is moved on a sprite.
  38024. * @param {Object} sprite
  38025. * @param {Event} event
  38026. */
  38027. /**
  38028. * @event spritemouseup
  38029. * Fires when a mouseup event occurs on a sprite.
  38030. * @param {Object} sprite
  38031. * @param {Event} event
  38032. */
  38033. /**
  38034. * @event spritemousedown
  38035. * Fires when a mousedown event occurs on a sprite.
  38036. * @param {Object} sprite
  38037. * @param {Event} event
  38038. */
  38039. /**
  38040. * @event spritemouseover
  38041. * Fires when the mouse enters a sprite.
  38042. * @param {Object} sprite
  38043. * @param {Event} event
  38044. */
  38045. /**
  38046. * @event spritemouseout
  38047. * Fires when the mouse exits a sprite.
  38048. * @param {Object} sprite
  38049. * @param {Event} event
  38050. */
  38051. /**
  38052. * @event spriteclick
  38053. * Fires when a click event occurs on a sprite.
  38054. * @param {Object} sprite
  38055. * @param {Event} event
  38056. */
  38057. /**
  38058. * @event spritedblclick
  38059. * Fires when a double click event occurs on a sprite.
  38060. * @param {Object} sprite
  38061. * @param {Event} event
  38062. */
  38063. /**
  38064. * @event spritetap
  38065. * Fires when a tap event occurs on a sprite.
  38066. * @param {Object} sprite
  38067. * @param {Event} event
  38068. */
  38069. mouseMoveEvents: {
  38070. mousemove: true,
  38071. mouseover: true,
  38072. mouseout: true
  38073. },
  38074. spriteMouseMoveEvents: {
  38075. spritemousemove: true,
  38076. spritemouseover: true,
  38077. spritemouseout: true
  38078. },
  38079. init: function(drawContainer) {
  38080. var handleEvent = 'handleEvent';
  38081. this.drawContainer = drawContainer;
  38082. drawContainer.addElementListener({
  38083. click: handleEvent,
  38084. dblclick: handleEvent,
  38085. mousedown: handleEvent,
  38086. mousemove: handleEvent,
  38087. mouseup: handleEvent,
  38088. mouseover: handleEvent,
  38089. mouseout: handleEvent,
  38090. // run our handlers before user code
  38091. priority: 1001,
  38092. scope: this
  38093. });
  38094. },
  38095. hasSpriteMouseMoveListeners: function() {
  38096. var listeners = this.drawContainer.hasListeners,
  38097. name;
  38098. for (name in this.spriteMouseMoveEvents) {
  38099. if (name in listeners) {
  38100. return true;
  38101. }
  38102. }
  38103. return false;
  38104. },
  38105. hitTestEvent: function(e) {
  38106. var items = this.drawContainer.getItems(),
  38107. surface, sprite, i;
  38108. for (i = items.length - 1; i >= 0; i--) {
  38109. surface = items.get(i);
  38110. sprite = surface.hitTestEvent(e);
  38111. if (sprite) {
  38112. return sprite;
  38113. }
  38114. }
  38115. return null;
  38116. },
  38117. handleEvent: function(e) {
  38118. var me = this,
  38119. drawContainer = me.drawContainer,
  38120. isMouseMoveEvent = e.type in me.mouseMoveEvents,
  38121. lastSprite = me.lastSprite,
  38122. sprite;
  38123. if (isMouseMoveEvent && !me.hasSpriteMouseMoveListeners()) {
  38124. return;
  38125. }
  38126. sprite = me.hitTestEvent(e);
  38127. if (isMouseMoveEvent && !Ext.Object.equals(sprite, lastSprite)) {
  38128. if (lastSprite) {
  38129. drawContainer.fireEvent('spritemouseout', lastSprite, e);
  38130. }
  38131. if (sprite) {
  38132. drawContainer.fireEvent('spritemouseover', sprite, e);
  38133. }
  38134. }
  38135. if (sprite) {
  38136. drawContainer.fireEvent('sprite' + e.type, sprite, e);
  38137. }
  38138. me.lastSprite = sprite;
  38139. }
  38140. });
  38141. /**
  38142. * The ItemInfo interaction allows displaying detailed information about a series data
  38143. * point in a popup panel.
  38144. *
  38145. * To attach this interaction to a chart, include an entry in the chart's
  38146. * {@link Ext.chart.AbstractChart#interactions interactions} config with the `iteminfo` type:
  38147. *
  38148. * new Ext.chart.AbstractChart({
  38149. * renderTo: Ext.getBody(),
  38150. * width: 800,
  38151. * height: 600,
  38152. * store: store1,
  38153. * axes: [ ...some axes options... ],
  38154. * series: [ ...some series options... ],
  38155. * interactions: [{
  38156. * type: 'iteminfo',
  38157. * listeners: {
  38158. * show: function(me, item, panel) {
  38159. * panel.setHtml('Stock Price: $' + item.record.get('price'));
  38160. * }
  38161. * }
  38162. * }]
  38163. * });
  38164. */
  38165. Ext.define('Ext.chart.interactions.ItemInfo', {
  38166. extend: 'Ext.chart.interactions.Abstract',
  38167. type: 'iteminfo',
  38168. alias: 'interaction.iteminfo',
  38169. /**
  38170. * @event show
  38171. * Fires when the info panel is shown.
  38172. * @param {Ext.chart.interactions.ItemInfo} this The interaction instance
  38173. * @param {Object} item The item whose info is being displayed
  38174. * @param {Ext.Panel} panel The panel for displaying the info
  38175. */
  38176. config: {
  38177. /**
  38178. * @cfg {Object} extjsGestures
  38179. * Defines the gestures that should trigger the item info panel to be displayed in ExtJS.
  38180. */
  38181. extjsGestures: {
  38182. 'start': {
  38183. event: 'click',
  38184. handler: 'onInfoGesture'
  38185. },
  38186. 'move': {
  38187. event: 'mousemove',
  38188. handler: 'onInfoGesture'
  38189. },
  38190. 'end': {
  38191. event: 'mouseleave',
  38192. handler: 'onInfoGesture'
  38193. }
  38194. }
  38195. },
  38196. // TODO:ps The trigger above should be 'itemclick', not 'click'.
  38197. item: null,
  38198. onInfoGesture: function(e, element) {
  38199. var me = this,
  38200. item = me.getItemForEvent(e),
  38201. tooltip = item && item.series.tooltip;
  38202. if (tooltip) {
  38203. tooltip.onMouseMove.call(tooltip, e);
  38204. }
  38205. if (item !== me.item) {
  38206. if (item) {
  38207. item.series.showTip(item);
  38208. } else {
  38209. me.item.series.hideTip(me.item);
  38210. }
  38211. me.item = item;
  38212. }
  38213. return false;
  38214. }
  38215. });