charts-debug.js 1.3 MB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976149771497814979149801498114982149831498414985149861498714988149891499014991149921499314994149951499614997149981499915000150011500215003150041500515006150071500815009150101501115012150131501415015150161501715018150191502015021150221502315024150251502615027150281502915030150311503215033150341503515036150371503815039150401504115042150431504415045150461504715048150491505015051150521505315054150551505615057150581505915060150611506215063150641506515066150671506815069150701507115072150731507415075150761507715078150791508015081150821508315084150851508615087150881508915090150911509215093150941509515096150971509815099151001510115102151031510415105151061510715108151091511015111151121511315114151151511615117151181511915120151211512215123151241512515126151271512815129151301513115132151331513415135151361513715138151391514015141151421514315144151451514615147151481514915150151511515215153151541515515156151571515815159151601516115162151631516415165151661516715168151691517015171151721517315174151751517615177151781517915180151811518215183151841518515186151871518815189151901519115192151931519415195151961519715198151991520015201152021520315204152051520615207152081520915210152111521215213152141521515216152171521815219152201522115222152231522415225152261522715228152291523015231152321523315234152351523615237152381523915240152411524215243152441524515246152471524815249152501525115252152531525415255152561525715258152591526015261152621526315264152651526615267152681526915270152711527215273152741527515276152771527815279152801528115282152831528415285152861528715288152891529015291152921529315294152951529615297152981529915300153011530215303153041530515306153071530815309153101531115312153131531415315153161531715318153191532015321153221532315324153251532615327153281532915330153311533215333153341533515336153371533815339153401534115342153431534415345153461534715348153491535015351153521535315354153551535615357153581535915360153611536215363153641536515366153671536815369153701537115372153731537415375153761537715378153791538015381153821538315384153851538615387153881538915390153911539215393153941539515396153971539815399154001540115402154031540415405154061540715408154091541015411154121541315414154151541615417154181541915420154211542215423154241542515426154271542815429154301543115432154331543415435154361543715438154391544015441154421544315444154451544615447154481544915450154511545215453154541545515456154571545815459154601546115462154631546415465154661546715468154691547015471154721547315474154751547615477154781547915480154811548215483154841548515486154871548815489154901549115492154931549415495154961549715498154991550015501155021550315504155051550615507155081550915510155111551215513155141551515516155171551815519155201552115522155231552415525155261552715528155291553015531155321553315534155351553615537155381553915540155411554215543155441554515546155471554815549155501555115552155531555415555155561555715558155591556015561155621556315564155651556615567155681556915570155711557215573155741557515576155771557815579155801558115582155831558415585155861558715588155891559015591155921559315594155951559615597155981559915600156011560215603156041560515606156071560815609156101561115612156131561415615156161561715618156191562015621156221562315624156251562615627156281562915630156311563215633156341563515636156371563815639156401564115642156431564415645156461564715648156491565015651156521565315654156551565615657156581565915660156611566215663156641566515666156671566815669156701567115672156731567415675156761567715678156791568015681156821568315684156851568615687156881568915690156911569215693156941569515696156971569815699157001570115702157031570415705157061570715708157091571015711157121571315714157151571615717157181571915720157211572215723157241572515726157271572815729157301573115732157331573415735157361573715738157391574015741157421574315744157451574615747157481574915750157511575215753157541575515756157571575815759157601576115762157631576415765157661576715768157691577015771157721577315774157751577615777157781577915780157811578215783157841578515786157871578815789157901579115792157931579415795157961579715798157991580015801158021580315804158051580615807158081580915810158111581215813158141581515816158171581815819158201582115822158231582415825158261582715828158291583015831158321583315834158351583615837158381583915840158411584215843158441584515846158471584815849158501585115852158531585415855158561585715858158591586015861158621586315864158651586615867158681586915870158711587215873158741587515876158771587815879158801588115882158831588415885158861588715888158891589015891158921589315894158951589615897158981589915900159011590215903159041590515906159071590815909159101591115912159131591415915159161591715918159191592015921159221592315924159251592615927159281592915930159311593215933159341593515936159371593815939159401594115942159431594415945159461594715948159491595015951159521595315954159551595615957159581595915960159611596215963159641596515966159671596815969159701597115972159731597415975159761597715978159791598015981159821598315984159851598615987159881598915990159911599215993159941599515996159971599815999160001600116002160031600416005160061600716008160091601016011160121601316014160151601616017160181601916020160211602216023160241602516026160271602816029160301603116032160331603416035160361603716038160391604016041160421604316044160451604616047160481604916050160511605216053160541605516056160571605816059160601606116062160631606416065160661606716068160691607016071160721607316074160751607616077160781607916080160811608216083160841608516086160871608816089160901609116092160931609416095160961609716098160991610016101161021610316104161051610616107161081610916110161111611216113161141611516116161171611816119161201612116122161231612416125161261612716128161291613016131161321613316134161351613616137161381613916140161411614216143161441614516146161471614816149161501615116152161531615416155161561615716158161591616016161161621616316164161651616616167161681616916170161711617216173161741617516176161771617816179161801618116182161831618416185161861618716188161891619016191161921619316194161951619616197161981619916200162011620216203162041620516206162071620816209162101621116212162131621416215162161621716218162191622016221162221622316224162251622616227162281622916230162311623216233162341623516236162371623816239162401624116242162431624416245162461624716248162491625016251162521625316254162551625616257162581625916260162611626216263162641626516266162671626816269162701627116272162731627416275162761627716278162791628016281162821628316284162851628616287162881628916290162911629216293162941629516296162971629816299163001630116302163031630416305163061630716308163091631016311163121631316314163151631616317163181631916320163211632216323163241632516326163271632816329163301633116332163331633416335163361633716338163391634016341163421634316344163451634616347163481634916350163511635216353163541635516356163571635816359163601636116362163631636416365163661636716368163691637016371163721637316374163751637616377163781637916380163811638216383163841638516386163871638816389163901639116392163931639416395163961639716398163991640016401164021640316404164051640616407164081640916410164111641216413164141641516416164171641816419164201642116422164231642416425164261642716428164291643016431164321643316434164351643616437164381643916440164411644216443164441644516446164471644816449164501645116452164531645416455164561645716458164591646016461164621646316464164651646616467164681646916470164711647216473164741647516476164771647816479164801648116482164831648416485164861648716488164891649016491164921649316494164951649616497164981649916500165011650216503165041650516506165071650816509165101651116512165131651416515165161651716518165191652016521165221652316524165251652616527165281652916530165311653216533165341653516536165371653816539165401654116542165431654416545165461654716548165491655016551165521655316554165551655616557165581655916560165611656216563165641656516566165671656816569165701657116572165731657416575165761657716578165791658016581165821658316584165851658616587165881658916590165911659216593165941659516596165971659816599166001660116602166031660416605166061660716608166091661016611166121661316614166151661616617166181661916620166211662216623166241662516626166271662816629166301663116632166331663416635166361663716638166391664016641166421664316644166451664616647166481664916650166511665216653166541665516656166571665816659166601666116662166631666416665166661666716668166691667016671166721667316674166751667616677166781667916680166811668216683166841668516686166871668816689166901669116692166931669416695166961669716698166991670016701167021670316704167051670616707167081670916710167111671216713167141671516716167171671816719167201672116722167231672416725167261672716728167291673016731167321673316734167351673616737167381673916740167411674216743167441674516746167471674816749167501675116752167531675416755167561675716758167591676016761167621676316764167651676616767167681676916770167711677216773167741677516776167771677816779167801678116782167831678416785167861678716788167891679016791167921679316794167951679616797167981679916800168011680216803168041680516806168071680816809168101681116812168131681416815168161681716818168191682016821168221682316824168251682616827168281682916830168311683216833168341683516836168371683816839168401684116842168431684416845168461684716848168491685016851168521685316854168551685616857168581685916860168611686216863168641686516866168671686816869168701687116872168731687416875168761687716878168791688016881168821688316884168851688616887168881688916890168911689216893168941689516896168971689816899169001690116902169031690416905169061690716908169091691016911169121691316914169151691616917169181691916920169211692216923169241692516926169271692816929169301693116932169331693416935169361693716938169391694016941169421694316944169451694616947169481694916950169511695216953169541695516956169571695816959169601696116962169631696416965169661696716968169691697016971169721697316974169751697616977169781697916980169811698216983169841698516986169871698816989169901699116992169931699416995169961699716998169991700017001170021700317004170051700617007170081700917010170111701217013170141701517016170171701817019170201702117022170231702417025170261702717028170291703017031170321703317034170351703617037170381703917040170411704217043170441704517046170471704817049170501705117052170531705417055170561705717058170591706017061170621706317064170651706617067170681706917070170711707217073170741707517076170771707817079170801708117082170831708417085170861708717088170891709017091170921709317094170951709617097170981709917100171011710217103171041710517106171071710817109171101711117112171131711417115171161711717118171191712017121171221712317124171251712617127171281712917130171311713217133171341713517136171371713817139171401714117142171431714417145171461714717148171491715017151171521715317154171551715617157171581715917160171611716217163171641716517166171671716817169171701717117172171731717417175171761717717178171791718017181171821718317184171851718617187171881718917190171911719217193171941719517196171971719817199172001720117202172031720417205172061720717208172091721017211172121721317214172151721617217172181721917220172211722217223172241722517226172271722817229172301723117232172331723417235172361723717238172391724017241172421724317244172451724617247172481724917250172511725217253172541725517256172571725817259172601726117262172631726417265172661726717268172691727017271172721727317274172751727617277172781727917280172811728217283172841728517286172871728817289172901729117292172931729417295172961729717298172991730017301173021730317304173051730617307173081730917310173111731217313173141731517316173171731817319173201732117322173231732417325173261732717328173291733017331173321733317334173351733617337173381733917340173411734217343173441734517346173471734817349173501735117352173531735417355173561735717358173591736017361173621736317364173651736617367173681736917370173711737217373173741737517376173771737817379173801738117382173831738417385173861738717388173891739017391173921739317394173951739617397173981739917400174011740217403174041740517406174071740817409174101741117412174131741417415174161741717418174191742017421174221742317424174251742617427174281742917430174311743217433174341743517436174371743817439174401744117442174431744417445174461744717448174491745017451174521745317454174551745617457174581745917460174611746217463174641746517466174671746817469174701747117472174731747417475174761747717478174791748017481174821748317484174851748617487174881748917490174911749217493174941749517496174971749817499175001750117502175031750417505175061750717508175091751017511175121751317514175151751617517175181751917520175211752217523175241752517526175271752817529175301753117532175331753417535175361753717538175391754017541175421754317544175451754617547175481754917550175511755217553175541755517556175571755817559175601756117562175631756417565175661756717568175691757017571175721757317574175751757617577175781757917580175811758217583175841758517586175871758817589175901759117592175931759417595175961759717598175991760017601176021760317604176051760617607176081760917610176111761217613176141761517616176171761817619176201762117622176231762417625176261762717628176291763017631176321763317634176351763617637176381763917640176411764217643176441764517646176471764817649176501765117652176531765417655176561765717658176591766017661176621766317664176651766617667176681766917670176711767217673176741767517676176771767817679176801768117682176831768417685176861768717688176891769017691176921769317694176951769617697176981769917700177011770217703177041770517706177071770817709177101771117712177131771417715177161771717718177191772017721177221772317724177251772617727177281772917730177311773217733177341773517736177371773817739177401774117742177431774417745177461774717748177491775017751177521775317754177551775617757177581775917760177611776217763177641776517766177671776817769177701777117772177731777417775177761777717778177791778017781177821778317784177851778617787177881778917790177911779217793177941779517796177971779817799178001780117802178031780417805178061780717808178091781017811178121781317814178151781617817178181781917820178211782217823178241782517826178271782817829178301783117832178331783417835178361783717838178391784017841178421784317844178451784617847178481784917850178511785217853178541785517856178571785817859178601786117862178631786417865178661786717868178691787017871178721787317874178751787617877178781787917880178811788217883178841788517886178871788817889178901789117892178931789417895178961789717898178991790017901179021790317904179051790617907179081790917910179111791217913179141791517916179171791817919179201792117922179231792417925179261792717928179291793017931179321793317934179351793617937179381793917940179411794217943179441794517946179471794817949179501795117952179531795417955179561795717958179591796017961179621796317964179651796617967179681796917970179711797217973179741797517976179771797817979179801798117982179831798417985179861798717988179891799017991179921799317994179951799617997179981799918000180011800218003180041800518006180071800818009180101801118012180131801418015180161801718018180191802018021180221802318024180251802618027180281802918030180311803218033180341803518036180371803818039180401804118042180431804418045180461804718048180491805018051180521805318054180551805618057180581805918060180611806218063180641806518066180671806818069180701807118072180731807418075180761807718078180791808018081180821808318084180851808618087180881808918090180911809218093180941809518096180971809818099181001810118102181031810418105181061810718108181091811018111181121811318114181151811618117181181811918120181211812218123181241812518126181271812818129181301813118132181331813418135181361813718138181391814018141181421814318144181451814618147181481814918150181511815218153181541815518156181571815818159181601816118162181631816418165181661816718168181691817018171181721817318174181751817618177181781817918180181811818218183181841818518186181871818818189181901819118192181931819418195181961819718198181991820018201182021820318204182051820618207182081820918210182111821218213182141821518216182171821818219182201822118222182231822418225182261822718228182291823018231182321823318234182351823618237182381823918240182411824218243182441824518246182471824818249182501825118252182531825418255182561825718258182591826018261182621826318264182651826618267182681826918270182711827218273182741827518276182771827818279182801828118282182831828418285182861828718288182891829018291182921829318294182951829618297182981829918300183011830218303183041830518306183071830818309183101831118312183131831418315183161831718318183191832018321183221832318324183251832618327183281832918330183311833218333183341833518336183371833818339183401834118342183431834418345183461834718348183491835018351183521835318354183551835618357183581835918360183611836218363183641836518366183671836818369183701837118372183731837418375183761837718378183791838018381183821838318384183851838618387183881838918390183911839218393183941839518396183971839818399184001840118402184031840418405184061840718408184091841018411184121841318414184151841618417184181841918420184211842218423184241842518426184271842818429184301843118432184331843418435184361843718438184391844018441184421844318444184451844618447184481844918450184511845218453184541845518456184571845818459184601846118462184631846418465184661846718468184691847018471184721847318474184751847618477184781847918480184811848218483184841848518486184871848818489184901849118492184931849418495184961849718498184991850018501185021850318504185051850618507185081850918510185111851218513185141851518516185171851818519185201852118522185231852418525185261852718528185291853018531185321853318534185351853618537185381853918540185411854218543185441854518546185471854818549185501855118552185531855418555185561855718558185591856018561185621856318564185651856618567185681856918570185711857218573185741857518576185771857818579185801858118582185831858418585185861858718588185891859018591185921859318594185951859618597185981859918600186011860218603186041860518606186071860818609186101861118612186131861418615186161861718618186191862018621186221862318624186251862618627186281862918630186311863218633186341863518636186371863818639186401864118642186431864418645186461864718648186491865018651186521865318654186551865618657186581865918660186611866218663186641866518666186671866818669186701867118672186731867418675186761867718678186791868018681186821868318684186851868618687186881868918690186911869218693186941869518696186971869818699187001870118702187031870418705187061870718708187091871018711187121871318714187151871618717187181871918720187211872218723187241872518726187271872818729187301873118732187331873418735187361873718738187391874018741187421874318744187451874618747187481874918750187511875218753187541875518756187571875818759187601876118762187631876418765187661876718768187691877018771187721877318774187751877618777187781877918780187811878218783187841878518786187871878818789187901879118792187931879418795187961879718798187991880018801188021880318804188051880618807188081880918810188111881218813188141881518816188171881818819188201882118822188231882418825188261882718828188291883018831188321883318834188351883618837188381883918840188411884218843188441884518846188471884818849188501885118852188531885418855188561885718858188591886018861188621886318864188651886618867188681886918870188711887218873188741887518876188771887818879188801888118882188831888418885188861888718888188891889018891188921889318894188951889618897188981889918900189011890218903189041890518906189071890818909189101891118912189131891418915189161891718918189191892018921189221892318924189251892618927189281892918930189311893218933189341893518936189371893818939189401894118942189431894418945189461894718948189491895018951189521895318954189551895618957189581895918960189611896218963189641896518966189671896818969189701897118972189731897418975189761897718978189791898018981189821898318984189851898618987189881898918990189911899218993189941899518996189971899818999190001900119002190031900419005190061900719008190091901019011190121901319014190151901619017190181901919020190211902219023190241902519026190271902819029190301903119032190331903419035190361903719038190391904019041190421904319044190451904619047190481904919050190511905219053190541905519056190571905819059190601906119062190631906419065190661906719068190691907019071190721907319074190751907619077190781907919080190811908219083190841908519086190871908819089190901909119092190931909419095190961909719098190991910019101191021910319104191051910619107191081910919110191111911219113191141911519116191171911819119191201912119122191231912419125191261912719128191291913019131191321913319134191351913619137191381913919140191411914219143191441914519146191471914819149191501915119152191531915419155191561915719158191591916019161191621916319164191651916619167191681916919170191711917219173191741917519176191771917819179191801918119182191831918419185191861918719188191891919019191191921919319194191951919619197191981919919200192011920219203192041920519206192071920819209192101921119212192131921419215192161921719218192191922019221192221922319224192251922619227192281922919230192311923219233192341923519236192371923819239192401924119242192431924419245192461924719248192491925019251192521925319254192551925619257192581925919260192611926219263192641926519266192671926819269192701927119272192731927419275192761927719278192791928019281192821928319284192851928619287192881928919290192911929219293192941929519296192971929819299193001930119302193031930419305193061930719308193091931019311193121931319314193151931619317193181931919320193211932219323193241932519326193271932819329193301933119332193331933419335193361933719338193391934019341193421934319344193451934619347193481934919350193511935219353193541935519356193571935819359193601936119362193631936419365193661936719368193691937019371193721937319374193751937619377193781937919380193811938219383193841938519386193871938819389193901939119392193931939419395193961939719398193991940019401194021940319404194051940619407194081940919410194111941219413194141941519416194171941819419194201942119422194231942419425194261942719428194291943019431194321943319434194351943619437194381943919440194411944219443194441944519446194471944819449194501945119452194531945419455194561945719458194591946019461194621946319464194651946619467194681946919470194711947219473194741947519476194771947819479194801948119482194831948419485194861948719488194891949019491194921949319494194951949619497194981949919500195011950219503195041950519506195071950819509195101951119512195131951419515195161951719518195191952019521195221952319524195251952619527195281952919530195311953219533195341953519536195371953819539195401954119542195431954419545195461954719548195491955019551195521955319554195551955619557195581955919560195611956219563195641956519566195671956819569195701957119572195731957419575195761957719578195791958019581195821958319584195851958619587195881958919590195911959219593195941959519596195971959819599196001960119602196031960419605196061960719608196091961019611196121961319614196151961619617196181961919620196211962219623196241962519626196271962819629196301963119632196331963419635196361963719638196391964019641196421964319644196451964619647196481964919650196511965219653196541965519656196571965819659196601966119662196631966419665196661966719668196691967019671196721967319674196751967619677196781967919680196811968219683196841968519686196871968819689196901969119692196931969419695196961969719698196991970019701197021970319704197051970619707197081970919710197111971219713197141971519716197171971819719197201972119722197231972419725197261972719728197291973019731197321973319734197351973619737197381973919740197411974219743197441974519746197471974819749197501975119752197531975419755197561975719758197591976019761197621976319764197651976619767197681976919770197711977219773197741977519776197771977819779197801978119782197831978419785197861978719788197891979019791197921979319794197951979619797197981979919800198011980219803198041980519806198071980819809198101981119812198131981419815198161981719818198191982019821198221982319824198251982619827198281982919830198311983219833198341983519836198371983819839198401984119842198431984419845198461984719848198491985019851198521985319854198551985619857198581985919860198611986219863198641986519866198671986819869198701987119872198731987419875198761987719878198791988019881198821988319884198851988619887198881988919890198911989219893198941989519896198971989819899199001990119902199031990419905199061990719908199091991019911199121991319914199151991619917199181991919920199211992219923199241992519926199271992819929199301993119932199331993419935199361993719938199391994019941199421994319944199451994619947199481994919950199511995219953199541995519956199571995819959199601996119962199631996419965199661996719968199691997019971199721997319974199751997619977199781997919980199811998219983199841998519986199871998819989199901999119992199931999419995199961999719998199992000020001200022000320004200052000620007200082000920010200112001220013200142001520016200172001820019200202002120022200232002420025200262002720028200292003020031200322003320034200352003620037200382003920040200412004220043200442004520046200472004820049200502005120052200532005420055200562005720058200592006020061200622006320064200652006620067200682006920070200712007220073200742007520076200772007820079200802008120082200832008420085200862008720088200892009020091200922009320094200952009620097200982009920100201012010220103201042010520106201072010820109201102011120112201132011420115201162011720118201192012020121201222012320124201252012620127201282012920130201312013220133201342013520136201372013820139201402014120142201432014420145201462014720148201492015020151201522015320154201552015620157201582015920160201612016220163201642016520166201672016820169201702017120172201732017420175201762017720178201792018020181201822018320184201852018620187201882018920190201912019220193201942019520196201972019820199202002020120202202032020420205202062020720208202092021020211202122021320214202152021620217202182021920220202212022220223202242022520226202272022820229202302023120232202332023420235202362023720238202392024020241202422024320244202452024620247202482024920250202512025220253202542025520256202572025820259202602026120262202632026420265202662026720268202692027020271202722027320274202752027620277202782027920280202812028220283202842028520286202872028820289202902029120292202932029420295202962029720298202992030020301203022030320304203052030620307203082030920310203112031220313203142031520316203172031820319203202032120322203232032420325203262032720328203292033020331203322033320334203352033620337203382033920340203412034220343203442034520346203472034820349203502035120352203532035420355203562035720358203592036020361203622036320364203652036620367203682036920370203712037220373203742037520376203772037820379203802038120382203832038420385203862038720388203892039020391203922039320394203952039620397203982039920400204012040220403204042040520406204072040820409204102041120412204132041420415204162041720418204192042020421204222042320424204252042620427204282042920430204312043220433204342043520436204372043820439204402044120442204432044420445204462044720448204492045020451204522045320454204552045620457204582045920460204612046220463204642046520466204672046820469204702047120472204732047420475204762047720478204792048020481204822048320484204852048620487204882048920490204912049220493204942049520496204972049820499205002050120502205032050420505205062050720508205092051020511205122051320514205152051620517205182051920520205212052220523205242052520526205272052820529205302053120532205332053420535205362053720538205392054020541205422054320544205452054620547205482054920550205512055220553205542055520556205572055820559205602056120562205632056420565205662056720568205692057020571205722057320574205752057620577205782057920580205812058220583205842058520586205872058820589205902059120592205932059420595205962059720598205992060020601206022060320604206052060620607206082060920610206112061220613206142061520616206172061820619206202062120622206232062420625206262062720628206292063020631206322063320634206352063620637206382063920640206412064220643206442064520646206472064820649206502065120652206532065420655206562065720658206592066020661206622066320664206652066620667206682066920670206712067220673206742067520676206772067820679206802068120682206832068420685206862068720688206892069020691206922069320694206952069620697206982069920700207012070220703207042070520706207072070820709207102071120712207132071420715207162071720718207192072020721207222072320724207252072620727207282072920730207312073220733207342073520736207372073820739207402074120742207432074420745207462074720748207492075020751207522075320754207552075620757207582075920760207612076220763207642076520766207672076820769207702077120772207732077420775207762077720778207792078020781207822078320784207852078620787207882078920790207912079220793207942079520796207972079820799208002080120802208032080420805208062080720808208092081020811208122081320814208152081620817208182081920820208212082220823208242082520826208272082820829208302083120832208332083420835208362083720838208392084020841208422084320844208452084620847208482084920850208512085220853208542085520856208572085820859208602086120862208632086420865208662086720868208692087020871208722087320874208752087620877208782087920880208812088220883208842088520886208872088820889208902089120892208932089420895208962089720898208992090020901209022090320904209052090620907209082090920910209112091220913209142091520916209172091820919209202092120922209232092420925209262092720928209292093020931209322093320934209352093620937209382093920940209412094220943209442094520946209472094820949209502095120952209532095420955209562095720958209592096020961209622096320964209652096620967209682096920970209712097220973209742097520976209772097820979209802098120982209832098420985209862098720988209892099020991209922099320994209952099620997209982099921000210012100221003210042100521006210072100821009210102101121012210132101421015210162101721018210192102021021210222102321024210252102621027210282102921030210312103221033210342103521036210372103821039210402104121042210432104421045210462104721048210492105021051210522105321054210552105621057210582105921060210612106221063210642106521066210672106821069210702107121072210732107421075210762107721078210792108021081210822108321084210852108621087210882108921090210912109221093210942109521096210972109821099211002110121102211032110421105211062110721108211092111021111211122111321114211152111621117211182111921120211212112221123211242112521126211272112821129211302113121132211332113421135211362113721138211392114021141211422114321144211452114621147211482114921150211512115221153211542115521156211572115821159211602116121162211632116421165211662116721168211692117021171211722117321174211752117621177211782117921180211812118221183211842118521186211872118821189211902119121192211932119421195211962119721198211992120021201212022120321204212052120621207212082120921210212112121221213212142121521216212172121821219212202122121222212232122421225212262122721228212292123021231212322123321234212352123621237212382123921240212412124221243212442124521246212472124821249212502125121252212532125421255212562125721258212592126021261212622126321264212652126621267212682126921270212712127221273212742127521276212772127821279212802128121282212832128421285212862128721288212892129021291212922129321294212952129621297212982129921300213012130221303213042130521306213072130821309213102131121312213132131421315213162131721318213192132021321213222132321324213252132621327213282132921330213312133221333213342133521336213372133821339213402134121342213432134421345213462134721348213492135021351213522135321354213552135621357213582135921360213612136221363213642136521366213672136821369213702137121372213732137421375213762137721378213792138021381213822138321384213852138621387213882138921390213912139221393213942139521396213972139821399214002140121402214032140421405214062140721408214092141021411214122141321414214152141621417214182141921420214212142221423214242142521426214272142821429214302143121432214332143421435214362143721438214392144021441214422144321444214452144621447214482144921450214512145221453214542145521456214572145821459214602146121462214632146421465214662146721468214692147021471214722147321474214752147621477214782147921480214812148221483214842148521486214872148821489214902149121492214932149421495214962149721498214992150021501215022150321504215052150621507215082150921510215112151221513215142151521516215172151821519215202152121522215232152421525215262152721528215292153021531215322153321534215352153621537215382153921540215412154221543215442154521546215472154821549215502155121552215532155421555215562155721558215592156021561215622156321564215652156621567215682156921570215712157221573215742157521576215772157821579215802158121582215832158421585215862158721588215892159021591215922159321594215952159621597215982159921600216012160221603216042160521606216072160821609216102161121612216132161421615216162161721618216192162021621216222162321624216252162621627216282162921630216312163221633216342163521636216372163821639216402164121642216432164421645216462164721648216492165021651216522165321654216552165621657216582165921660216612166221663216642166521666216672166821669216702167121672216732167421675216762167721678216792168021681216822168321684216852168621687216882168921690216912169221693216942169521696216972169821699217002170121702217032170421705217062170721708217092171021711217122171321714217152171621717217182171921720217212172221723217242172521726217272172821729217302173121732217332173421735217362173721738217392174021741217422174321744217452174621747217482174921750217512175221753217542175521756217572175821759217602176121762217632176421765217662176721768217692177021771217722177321774217752177621777217782177921780217812178221783217842178521786217872178821789217902179121792217932179421795217962179721798217992180021801218022180321804218052180621807218082180921810218112181221813218142181521816218172181821819218202182121822218232182421825218262182721828218292183021831218322183321834218352183621837218382183921840218412184221843218442184521846218472184821849218502185121852218532185421855218562185721858218592186021861218622186321864218652186621867218682186921870218712187221873218742187521876218772187821879218802188121882218832188421885218862188721888218892189021891218922189321894218952189621897218982189921900219012190221903219042190521906219072190821909219102191121912219132191421915219162191721918219192192021921219222192321924219252192621927219282192921930219312193221933219342193521936219372193821939219402194121942219432194421945219462194721948219492195021951219522195321954219552195621957219582195921960219612196221963219642196521966219672196821969219702197121972219732197421975219762197721978219792198021981219822198321984219852198621987219882198921990219912199221993219942199521996219972199821999220002200122002220032200422005220062200722008220092201022011220122201322014220152201622017220182201922020220212202222023220242202522026220272202822029220302203122032220332203422035220362203722038220392204022041220422204322044220452204622047220482204922050220512205222053220542205522056220572205822059220602206122062220632206422065220662206722068220692207022071220722207322074220752207622077220782207922080220812208222083220842208522086220872208822089220902209122092220932209422095220962209722098220992210022101221022210322104221052210622107221082210922110221112211222113221142211522116221172211822119221202212122122221232212422125221262212722128221292213022131221322213322134221352213622137221382213922140221412214222143221442214522146221472214822149221502215122152221532215422155221562215722158221592216022161221622216322164221652216622167221682216922170221712217222173221742217522176221772217822179221802218122182221832218422185221862218722188221892219022191221922219322194221952219622197221982219922200222012220222203222042220522206222072220822209222102221122212222132221422215222162221722218222192222022221222222222322224222252222622227222282222922230222312223222233222342223522236222372223822239222402224122242222432224422245222462224722248222492225022251222522225322254222552225622257222582225922260222612226222263222642226522266222672226822269222702227122272222732227422275222762227722278222792228022281222822228322284222852228622287222882228922290222912229222293222942229522296222972229822299223002230122302223032230422305223062230722308223092231022311223122231322314223152231622317223182231922320223212232222323223242232522326223272232822329223302233122332223332233422335223362233722338223392234022341223422234322344223452234622347223482234922350223512235222353223542235522356223572235822359223602236122362223632236422365223662236722368223692237022371223722237322374223752237622377223782237922380223812238222383223842238522386223872238822389223902239122392223932239422395223962239722398223992240022401224022240322404224052240622407224082240922410224112241222413224142241522416224172241822419224202242122422224232242422425224262242722428224292243022431224322243322434224352243622437224382243922440224412244222443224442244522446224472244822449224502245122452224532245422455224562245722458224592246022461224622246322464224652246622467224682246922470224712247222473224742247522476224772247822479224802248122482224832248422485224862248722488224892249022491224922249322494224952249622497224982249922500225012250222503225042250522506225072250822509225102251122512225132251422515225162251722518225192252022521225222252322524225252252622527225282252922530225312253222533225342253522536225372253822539225402254122542225432254422545225462254722548225492255022551225522255322554225552255622557225582255922560225612256222563225642256522566225672256822569225702257122572225732257422575225762257722578225792258022581225822258322584225852258622587225882258922590225912259222593225942259522596225972259822599226002260122602226032260422605226062260722608226092261022611226122261322614226152261622617226182261922620226212262222623226242262522626226272262822629226302263122632226332263422635226362263722638226392264022641226422264322644226452264622647226482264922650226512265222653226542265522656226572265822659226602266122662226632266422665226662266722668226692267022671226722267322674226752267622677226782267922680226812268222683226842268522686226872268822689226902269122692226932269422695226962269722698226992270022701227022270322704227052270622707227082270922710227112271222713227142271522716227172271822719227202272122722227232272422725227262272722728227292273022731227322273322734227352273622737227382273922740227412274222743227442274522746227472274822749227502275122752227532275422755227562275722758227592276022761227622276322764227652276622767227682276922770227712277222773227742277522776227772277822779227802278122782227832278422785227862278722788227892279022791227922279322794227952279622797227982279922800228012280222803228042280522806228072280822809228102281122812228132281422815228162281722818228192282022821228222282322824228252282622827228282282922830228312283222833228342283522836228372283822839228402284122842228432284422845228462284722848228492285022851228522285322854228552285622857228582285922860228612286222863228642286522866228672286822869228702287122872228732287422875228762287722878228792288022881228822288322884228852288622887228882288922890228912289222893228942289522896228972289822899229002290122902229032290422905229062290722908229092291022911229122291322914229152291622917229182291922920229212292222923229242292522926229272292822929229302293122932229332293422935229362293722938229392294022941229422294322944229452294622947229482294922950229512295222953229542295522956229572295822959229602296122962229632296422965229662296722968229692297022971229722297322974229752297622977229782297922980229812298222983229842298522986229872298822989229902299122992229932299422995229962299722998229992300023001230022300323004230052300623007230082300923010230112301223013230142301523016230172301823019230202302123022230232302423025230262302723028230292303023031230322303323034230352303623037230382303923040230412304223043230442304523046230472304823049230502305123052230532305423055230562305723058230592306023061230622306323064230652306623067230682306923070230712307223073230742307523076230772307823079230802308123082230832308423085230862308723088230892309023091230922309323094230952309623097230982309923100231012310223103231042310523106231072310823109231102311123112231132311423115231162311723118231192312023121231222312323124231252312623127231282312923130231312313223133231342313523136231372313823139231402314123142231432314423145231462314723148231492315023151231522315323154231552315623157231582315923160231612316223163231642316523166231672316823169231702317123172231732317423175231762317723178231792318023181231822318323184231852318623187231882318923190231912319223193231942319523196231972319823199232002320123202232032320423205232062320723208232092321023211232122321323214232152321623217232182321923220232212322223223232242322523226232272322823229232302323123232232332323423235232362323723238232392324023241232422324323244232452324623247232482324923250232512325223253232542325523256232572325823259232602326123262232632326423265232662326723268232692327023271232722327323274232752327623277232782327923280232812328223283232842328523286232872328823289232902329123292232932329423295232962329723298232992330023301233022330323304233052330623307233082330923310233112331223313233142331523316233172331823319233202332123322233232332423325233262332723328233292333023331233322333323334233352333623337233382333923340233412334223343233442334523346233472334823349233502335123352233532335423355233562335723358233592336023361233622336323364233652336623367233682336923370233712337223373233742337523376233772337823379233802338123382233832338423385233862338723388233892339023391233922339323394233952339623397233982339923400234012340223403234042340523406234072340823409234102341123412234132341423415234162341723418234192342023421234222342323424234252342623427234282342923430234312343223433234342343523436234372343823439234402344123442234432344423445234462344723448234492345023451234522345323454234552345623457234582345923460234612346223463234642346523466234672346823469234702347123472234732347423475234762347723478234792348023481234822348323484234852348623487234882348923490234912349223493234942349523496234972349823499235002350123502235032350423505235062350723508235092351023511235122351323514235152351623517235182351923520235212352223523235242352523526235272352823529235302353123532235332353423535235362353723538235392354023541235422354323544235452354623547235482354923550235512355223553235542355523556235572355823559235602356123562235632356423565235662356723568235692357023571235722357323574235752357623577235782357923580235812358223583235842358523586235872358823589235902359123592235932359423595235962359723598235992360023601236022360323604236052360623607236082360923610236112361223613236142361523616236172361823619236202362123622236232362423625236262362723628236292363023631236322363323634236352363623637236382363923640236412364223643236442364523646236472364823649236502365123652236532365423655236562365723658236592366023661236622366323664236652366623667236682366923670236712367223673236742367523676236772367823679236802368123682236832368423685236862368723688236892369023691236922369323694236952369623697236982369923700237012370223703237042370523706237072370823709237102371123712237132371423715237162371723718237192372023721237222372323724237252372623727237282372923730237312373223733237342373523736237372373823739237402374123742237432374423745237462374723748237492375023751237522375323754237552375623757237582375923760237612376223763237642376523766237672376823769237702377123772237732377423775237762377723778237792378023781237822378323784237852378623787237882378923790237912379223793237942379523796237972379823799238002380123802238032380423805238062380723808238092381023811238122381323814238152381623817238182381923820238212382223823238242382523826238272382823829238302383123832238332383423835238362383723838238392384023841238422384323844238452384623847238482384923850238512385223853238542385523856238572385823859238602386123862238632386423865238662386723868238692387023871238722387323874238752387623877238782387923880238812388223883238842388523886238872388823889238902389123892238932389423895238962389723898238992390023901239022390323904239052390623907239082390923910239112391223913239142391523916239172391823919239202392123922239232392423925239262392723928239292393023931239322393323934239352393623937239382393923940239412394223943239442394523946239472394823949239502395123952239532395423955239562395723958239592396023961239622396323964239652396623967239682396923970239712397223973239742397523976239772397823979239802398123982239832398423985239862398723988239892399023991239922399323994239952399623997239982399924000240012400224003240042400524006240072400824009240102401124012240132401424015240162401724018240192402024021240222402324024240252402624027240282402924030240312403224033240342403524036240372403824039240402404124042240432404424045240462404724048240492405024051240522405324054240552405624057240582405924060240612406224063240642406524066240672406824069240702407124072240732407424075240762407724078240792408024081240822408324084240852408624087240882408924090240912409224093240942409524096240972409824099241002410124102241032410424105241062410724108241092411024111241122411324114241152411624117241182411924120241212412224123241242412524126241272412824129241302413124132241332413424135241362413724138241392414024141241422414324144241452414624147241482414924150241512415224153241542415524156241572415824159241602416124162241632416424165241662416724168241692417024171241722417324174241752417624177241782417924180241812418224183241842418524186241872418824189241902419124192241932419424195241962419724198241992420024201242022420324204242052420624207242082420924210242112421224213242142421524216242172421824219242202422124222242232422424225242262422724228242292423024231242322423324234242352423624237242382423924240242412424224243242442424524246242472424824249242502425124252242532425424255242562425724258242592426024261242622426324264242652426624267242682426924270242712427224273242742427524276242772427824279242802428124282242832428424285242862428724288242892429024291242922429324294242952429624297242982429924300243012430224303243042430524306243072430824309243102431124312243132431424315243162431724318243192432024321243222432324324243252432624327243282432924330243312433224333243342433524336243372433824339243402434124342243432434424345243462434724348243492435024351243522435324354243552435624357243582435924360243612436224363243642436524366243672436824369243702437124372243732437424375243762437724378243792438024381243822438324384243852438624387243882438924390243912439224393243942439524396243972439824399244002440124402244032440424405244062440724408244092441024411244122441324414244152441624417244182441924420244212442224423244242442524426244272442824429244302443124432244332443424435244362443724438244392444024441244422444324444244452444624447244482444924450244512445224453244542445524456244572445824459244602446124462244632446424465244662446724468244692447024471244722447324474244752447624477244782447924480244812448224483244842448524486244872448824489244902449124492244932449424495244962449724498244992450024501245022450324504245052450624507245082450924510245112451224513245142451524516245172451824519245202452124522245232452424525245262452724528245292453024531245322453324534245352453624537245382453924540245412454224543245442454524546245472454824549245502455124552245532455424555245562455724558245592456024561245622456324564245652456624567245682456924570245712457224573245742457524576245772457824579245802458124582245832458424585245862458724588245892459024591245922459324594245952459624597245982459924600246012460224603246042460524606246072460824609246102461124612246132461424615246162461724618246192462024621246222462324624246252462624627246282462924630246312463224633246342463524636246372463824639246402464124642246432464424645246462464724648246492465024651246522465324654246552465624657246582465924660246612466224663246642466524666246672466824669246702467124672246732467424675246762467724678246792468024681246822468324684246852468624687246882468924690246912469224693246942469524696246972469824699247002470124702247032470424705247062470724708247092471024711247122471324714247152471624717247182471924720247212472224723247242472524726247272472824729247302473124732247332473424735247362473724738247392474024741247422474324744247452474624747247482474924750247512475224753247542475524756247572475824759247602476124762247632476424765247662476724768247692477024771247722477324774247752477624777247782477924780247812478224783247842478524786247872478824789247902479124792247932479424795247962479724798247992480024801248022480324804248052480624807248082480924810248112481224813248142481524816248172481824819248202482124822248232482424825248262482724828248292483024831248322483324834248352483624837248382483924840248412484224843248442484524846248472484824849248502485124852248532485424855248562485724858248592486024861248622486324864248652486624867248682486924870248712487224873248742487524876248772487824879248802488124882248832488424885248862488724888248892489024891248922489324894248952489624897248982489924900249012490224903249042490524906249072490824909249102491124912249132491424915249162491724918249192492024921249222492324924249252492624927249282492924930249312493224933249342493524936249372493824939249402494124942249432494424945249462494724948249492495024951249522495324954249552495624957249582495924960249612496224963249642496524966249672496824969249702497124972249732497424975249762497724978249792498024981249822498324984249852498624987249882498924990249912499224993249942499524996249972499824999250002500125002250032500425005250062500725008250092501025011250122501325014250152501625017250182501925020250212502225023250242502525026250272502825029250302503125032250332503425035250362503725038250392504025041250422504325044250452504625047250482504925050250512505225053250542505525056250572505825059250602506125062250632506425065250662506725068250692507025071250722507325074250752507625077250782507925080250812508225083250842508525086250872508825089250902509125092250932509425095250962509725098250992510025101251022510325104251052510625107251082510925110251112511225113251142511525116251172511825119251202512125122251232512425125251262512725128251292513025131251322513325134251352513625137251382513925140251412514225143251442514525146251472514825149251502515125152251532515425155251562515725158251592516025161251622516325164251652516625167251682516925170251712517225173251742517525176251772517825179251802518125182251832518425185251862518725188251892519025191251922519325194251952519625197251982519925200252012520225203252042520525206252072520825209252102521125212252132521425215252162521725218252192522025221252222522325224252252522625227252282522925230252312523225233252342523525236252372523825239252402524125242252432524425245252462524725248252492525025251252522525325254252552525625257252582525925260252612526225263252642526525266252672526825269252702527125272252732527425275252762527725278252792528025281252822528325284252852528625287252882528925290252912529225293252942529525296252972529825299253002530125302253032530425305253062530725308253092531025311253122531325314253152531625317253182531925320253212532225323253242532525326253272532825329253302533125332253332533425335253362533725338253392534025341253422534325344253452534625347253482534925350253512535225353253542535525356253572535825359253602536125362253632536425365253662536725368253692537025371253722537325374253752537625377253782537925380253812538225383253842538525386253872538825389253902539125392253932539425395253962539725398253992540025401254022540325404254052540625407254082540925410254112541225413254142541525416254172541825419254202542125422254232542425425254262542725428254292543025431254322543325434254352543625437254382543925440254412544225443254442544525446254472544825449254502545125452254532545425455254562545725458254592546025461254622546325464254652546625467254682546925470254712547225473254742547525476254772547825479254802548125482254832548425485254862548725488254892549025491254922549325494254952549625497254982549925500255012550225503255042550525506255072550825509255102551125512255132551425515255162551725518255192552025521255222552325524255252552625527255282552925530255312553225533255342553525536255372553825539255402554125542255432554425545255462554725548255492555025551255522555325554255552555625557255582555925560255612556225563255642556525566255672556825569255702557125572255732557425575255762557725578255792558025581255822558325584255852558625587255882558925590255912559225593255942559525596255972559825599256002560125602256032560425605256062560725608256092561025611256122561325614256152561625617256182561925620256212562225623256242562525626256272562825629256302563125632256332563425635256362563725638256392564025641256422564325644256452564625647256482564925650256512565225653256542565525656256572565825659256602566125662256632566425665256662566725668256692567025671256722567325674256752567625677256782567925680256812568225683256842568525686256872568825689256902569125692256932569425695256962569725698256992570025701257022570325704257052570625707257082570925710257112571225713257142571525716257172571825719257202572125722257232572425725257262572725728257292573025731257322573325734257352573625737257382573925740257412574225743257442574525746257472574825749257502575125752257532575425755257562575725758257592576025761257622576325764257652576625767257682576925770257712577225773257742577525776257772577825779257802578125782257832578425785257862578725788257892579025791257922579325794257952579625797257982579925800258012580225803258042580525806258072580825809258102581125812258132581425815258162581725818258192582025821258222582325824258252582625827258282582925830258312583225833258342583525836258372583825839258402584125842258432584425845258462584725848258492585025851258522585325854258552585625857258582585925860258612586225863258642586525866258672586825869258702587125872258732587425875258762587725878258792588025881258822588325884258852588625887258882588925890258912589225893258942589525896258972589825899259002590125902259032590425905259062590725908259092591025911259122591325914259152591625917259182591925920259212592225923259242592525926259272592825929259302593125932259332593425935259362593725938259392594025941259422594325944259452594625947259482594925950259512595225953259542595525956259572595825959259602596125962259632596425965259662596725968259692597025971259722597325974259752597625977259782597925980259812598225983259842598525986259872598825989259902599125992259932599425995259962599725998259992600026001260022600326004260052600626007260082600926010260112601226013260142601526016260172601826019260202602126022260232602426025260262602726028260292603026031260322603326034260352603626037260382603926040260412604226043260442604526046260472604826049260502605126052260532605426055260562605726058260592606026061260622606326064260652606626067260682606926070260712607226073260742607526076260772607826079260802608126082260832608426085260862608726088260892609026091260922609326094260952609626097260982609926100261012610226103261042610526106261072610826109261102611126112261132611426115261162611726118261192612026121261222612326124261252612626127261282612926130261312613226133261342613526136261372613826139261402614126142261432614426145261462614726148261492615026151261522615326154261552615626157261582615926160261612616226163261642616526166261672616826169261702617126172261732617426175261762617726178261792618026181261822618326184261852618626187261882618926190261912619226193261942619526196261972619826199262002620126202262032620426205262062620726208262092621026211262122621326214262152621626217262182621926220262212622226223262242622526226262272622826229262302623126232262332623426235262362623726238262392624026241262422624326244262452624626247262482624926250262512625226253262542625526256262572625826259262602626126262262632626426265262662626726268262692627026271262722627326274262752627626277262782627926280262812628226283262842628526286262872628826289262902629126292262932629426295262962629726298262992630026301263022630326304263052630626307263082630926310263112631226313263142631526316263172631826319263202632126322263232632426325263262632726328263292633026331263322633326334263352633626337263382633926340263412634226343263442634526346263472634826349263502635126352263532635426355263562635726358263592636026361263622636326364263652636626367263682636926370263712637226373263742637526376263772637826379263802638126382263832638426385263862638726388263892639026391263922639326394263952639626397263982639926400264012640226403264042640526406264072640826409264102641126412264132641426415264162641726418264192642026421264222642326424264252642626427264282642926430264312643226433264342643526436264372643826439264402644126442264432644426445264462644726448264492645026451264522645326454264552645626457264582645926460264612646226463264642646526466264672646826469264702647126472264732647426475264762647726478264792648026481264822648326484264852648626487264882648926490264912649226493264942649526496264972649826499265002650126502265032650426505265062650726508265092651026511265122651326514265152651626517265182651926520265212652226523265242652526526265272652826529265302653126532265332653426535265362653726538265392654026541265422654326544265452654626547265482654926550265512655226553265542655526556265572655826559265602656126562265632656426565265662656726568265692657026571265722657326574265752657626577265782657926580265812658226583265842658526586265872658826589265902659126592265932659426595265962659726598265992660026601266022660326604266052660626607266082660926610266112661226613266142661526616266172661826619266202662126622266232662426625266262662726628266292663026631266322663326634266352663626637266382663926640266412664226643266442664526646266472664826649266502665126652266532665426655266562665726658266592666026661266622666326664266652666626667266682666926670266712667226673266742667526676266772667826679266802668126682266832668426685266862668726688266892669026691266922669326694266952669626697266982669926700267012670226703267042670526706267072670826709267102671126712267132671426715267162671726718267192672026721267222672326724267252672626727267282672926730267312673226733267342673526736267372673826739267402674126742267432674426745267462674726748267492675026751267522675326754267552675626757267582675926760267612676226763267642676526766267672676826769267702677126772267732677426775267762677726778267792678026781267822678326784267852678626787267882678926790267912679226793267942679526796267972679826799268002680126802268032680426805268062680726808268092681026811268122681326814268152681626817268182681926820268212682226823268242682526826268272682826829268302683126832268332683426835268362683726838268392684026841268422684326844268452684626847268482684926850268512685226853268542685526856268572685826859268602686126862268632686426865268662686726868268692687026871268722687326874268752687626877268782687926880268812688226883268842688526886268872688826889268902689126892268932689426895268962689726898268992690026901269022690326904269052690626907269082690926910269112691226913269142691526916269172691826919269202692126922269232692426925269262692726928269292693026931269322693326934269352693626937269382693926940269412694226943269442694526946269472694826949269502695126952269532695426955269562695726958269592696026961269622696326964269652696626967269682696926970269712697226973269742697526976269772697826979269802698126982269832698426985269862698726988269892699026991269922699326994269952699626997269982699927000270012700227003270042700527006270072700827009270102701127012270132701427015270162701727018270192702027021270222702327024270252702627027270282702927030270312703227033270342703527036270372703827039270402704127042270432704427045270462704727048270492705027051270522705327054270552705627057270582705927060270612706227063270642706527066270672706827069270702707127072270732707427075270762707727078270792708027081270822708327084270852708627087270882708927090270912709227093270942709527096270972709827099271002710127102271032710427105271062710727108271092711027111271122711327114271152711627117271182711927120271212712227123271242712527126271272712827129271302713127132271332713427135271362713727138271392714027141271422714327144271452714627147271482714927150271512715227153271542715527156271572715827159271602716127162271632716427165271662716727168271692717027171271722717327174271752717627177271782717927180271812718227183271842718527186271872718827189271902719127192271932719427195271962719727198271992720027201272022720327204272052720627207272082720927210272112721227213272142721527216272172721827219272202722127222272232722427225272262722727228272292723027231272322723327234272352723627237272382723927240272412724227243272442724527246272472724827249272502725127252272532725427255272562725727258272592726027261272622726327264272652726627267272682726927270272712727227273272742727527276272772727827279272802728127282272832728427285272862728727288272892729027291272922729327294272952729627297272982729927300273012730227303273042730527306273072730827309273102731127312273132731427315273162731727318273192732027321273222732327324273252732627327273282732927330273312733227333273342733527336273372733827339273402734127342273432734427345273462734727348273492735027351273522735327354273552735627357273582735927360273612736227363273642736527366273672736827369273702737127372273732737427375273762737727378273792738027381273822738327384273852738627387273882738927390273912739227393273942739527396273972739827399274002740127402274032740427405274062740727408274092741027411274122741327414274152741627417274182741927420274212742227423274242742527426274272742827429274302743127432274332743427435274362743727438274392744027441274422744327444274452744627447274482744927450274512745227453274542745527456274572745827459274602746127462274632746427465274662746727468274692747027471274722747327474274752747627477274782747927480274812748227483274842748527486274872748827489274902749127492274932749427495274962749727498274992750027501275022750327504275052750627507275082750927510275112751227513275142751527516275172751827519275202752127522275232752427525275262752727528275292753027531275322753327534275352753627537275382753927540275412754227543275442754527546275472754827549275502755127552275532755427555275562755727558275592756027561275622756327564275652756627567275682756927570275712757227573275742757527576275772757827579275802758127582275832758427585275862758727588275892759027591275922759327594275952759627597275982759927600276012760227603276042760527606276072760827609276102761127612276132761427615276162761727618276192762027621276222762327624276252762627627276282762927630276312763227633276342763527636276372763827639276402764127642276432764427645276462764727648276492765027651276522765327654276552765627657276582765927660276612766227663276642766527666276672766827669276702767127672276732767427675276762767727678276792768027681276822768327684276852768627687276882768927690276912769227693276942769527696276972769827699277002770127702277032770427705277062770727708277092771027711277122771327714277152771627717277182771927720277212772227723277242772527726277272772827729277302773127732277332773427735277362773727738277392774027741277422774327744277452774627747277482774927750277512775227753277542775527756277572775827759277602776127762277632776427765277662776727768277692777027771277722777327774277752777627777277782777927780277812778227783277842778527786277872778827789277902779127792277932779427795277962779727798277992780027801278022780327804278052780627807278082780927810278112781227813278142781527816278172781827819278202782127822278232782427825278262782727828278292783027831278322783327834278352783627837278382783927840278412784227843278442784527846278472784827849278502785127852278532785427855278562785727858278592786027861278622786327864278652786627867278682786927870278712787227873278742787527876278772787827879278802788127882278832788427885278862788727888278892789027891278922789327894278952789627897278982789927900279012790227903279042790527906279072790827909279102791127912279132791427915279162791727918279192792027921279222792327924279252792627927279282792927930279312793227933279342793527936279372793827939279402794127942279432794427945279462794727948279492795027951279522795327954279552795627957279582795927960279612796227963279642796527966279672796827969279702797127972279732797427975279762797727978279792798027981279822798327984279852798627987279882798927990279912799227993279942799527996279972799827999280002800128002280032800428005280062800728008280092801028011280122801328014280152801628017280182801928020280212802228023280242802528026280272802828029280302803128032280332803428035280362803728038280392804028041280422804328044280452804628047280482804928050280512805228053280542805528056280572805828059280602806128062280632806428065280662806728068280692807028071280722807328074280752807628077280782807928080280812808228083280842808528086280872808828089280902809128092280932809428095280962809728098280992810028101281022810328104281052810628107281082810928110281112811228113281142811528116281172811828119281202812128122281232812428125281262812728128281292813028131281322813328134281352813628137281382813928140281412814228143281442814528146281472814828149281502815128152281532815428155281562815728158281592816028161281622816328164281652816628167281682816928170281712817228173281742817528176281772817828179281802818128182281832818428185281862818728188281892819028191281922819328194281952819628197281982819928200282012820228203282042820528206282072820828209282102821128212282132821428215282162821728218282192822028221282222822328224282252822628227282282822928230282312823228233282342823528236282372823828239282402824128242282432824428245282462824728248282492825028251282522825328254282552825628257282582825928260282612826228263282642826528266282672826828269282702827128272282732827428275282762827728278282792828028281282822828328284282852828628287282882828928290282912829228293282942829528296282972829828299283002830128302283032830428305283062830728308283092831028311283122831328314283152831628317283182831928320283212832228323283242832528326283272832828329283302833128332283332833428335283362833728338283392834028341283422834328344283452834628347283482834928350283512835228353283542835528356283572835828359283602836128362283632836428365283662836728368283692837028371283722837328374283752837628377283782837928380283812838228383283842838528386283872838828389283902839128392283932839428395283962839728398283992840028401284022840328404284052840628407284082840928410284112841228413284142841528416284172841828419284202842128422284232842428425284262842728428284292843028431284322843328434284352843628437284382843928440284412844228443284442844528446284472844828449284502845128452284532845428455284562845728458284592846028461284622846328464284652846628467284682846928470284712847228473284742847528476284772847828479284802848128482284832848428485284862848728488284892849028491284922849328494284952849628497284982849928500285012850228503285042850528506285072850828509285102851128512285132851428515285162851728518285192852028521285222852328524285252852628527285282852928530285312853228533285342853528536285372853828539285402854128542285432854428545285462854728548285492855028551285522855328554285552855628557285582855928560285612856228563285642856528566285672856828569285702857128572285732857428575285762857728578285792858028581285822858328584285852858628587285882858928590285912859228593285942859528596285972859828599286002860128602286032860428605286062860728608286092861028611286122861328614286152861628617286182861928620286212862228623286242862528626286272862828629286302863128632286332863428635286362863728638286392864028641286422864328644286452864628647286482864928650286512865228653286542865528656286572865828659286602866128662286632866428665286662866728668286692867028671286722867328674286752867628677286782867928680286812868228683286842868528686286872868828689286902869128692286932869428695286962869728698286992870028701287022870328704287052870628707287082870928710287112871228713287142871528716287172871828719287202872128722287232872428725287262872728728287292873028731287322873328734287352873628737287382873928740287412874228743287442874528746287472874828749287502875128752287532875428755287562875728758287592876028761287622876328764287652876628767287682876928770287712877228773287742877528776287772877828779287802878128782287832878428785287862878728788287892879028791287922879328794287952879628797287982879928800288012880228803288042880528806288072880828809288102881128812288132881428815288162881728818288192882028821288222882328824288252882628827288282882928830288312883228833288342883528836288372883828839288402884128842288432884428845288462884728848288492885028851288522885328854288552885628857288582885928860288612886228863288642886528866288672886828869288702887128872288732887428875288762887728878288792888028881288822888328884288852888628887288882888928890288912889228893288942889528896288972889828899289002890128902289032890428905289062890728908289092891028911289122891328914289152891628917289182891928920289212892228923289242892528926289272892828929289302893128932289332893428935289362893728938289392894028941289422894328944289452894628947289482894928950289512895228953289542895528956289572895828959289602896128962289632896428965289662896728968289692897028971289722897328974289752897628977289782897928980289812898228983289842898528986289872898828989289902899128992289932899428995289962899728998289992900029001290022900329004290052900629007290082900929010290112901229013290142901529016290172901829019290202902129022290232902429025290262902729028290292903029031290322903329034290352903629037290382903929040290412904229043290442904529046290472904829049290502905129052290532905429055290562905729058290592906029061290622906329064290652906629067290682906929070290712907229073290742907529076290772907829079290802908129082290832908429085290862908729088290892909029091290922909329094290952909629097290982909929100291012910229103291042910529106291072910829109291102911129112291132911429115291162911729118291192912029121291222912329124291252912629127291282912929130291312913229133291342913529136291372913829139291402914129142291432914429145291462914729148291492915029151291522915329154291552915629157291582915929160291612916229163291642916529166291672916829169291702917129172291732917429175291762917729178291792918029181291822918329184291852918629187291882918929190291912919229193291942919529196291972919829199292002920129202292032920429205292062920729208292092921029211292122921329214292152921629217292182921929220292212922229223292242922529226292272922829229292302923129232292332923429235292362923729238292392924029241292422924329244292452924629247292482924929250292512925229253292542925529256292572925829259292602926129262292632926429265292662926729268292692927029271292722927329274292752927629277292782927929280292812928229283292842928529286292872928829289292902929129292292932929429295292962929729298292992930029301293022930329304293052930629307293082930929310293112931229313293142931529316293172931829319293202932129322293232932429325293262932729328293292933029331293322933329334293352933629337293382933929340293412934229343293442934529346293472934829349293502935129352293532935429355293562935729358293592936029361293622936329364293652936629367293682936929370293712937229373293742937529376293772937829379293802938129382293832938429385293862938729388293892939029391293922939329394293952939629397293982939929400294012940229403294042940529406294072940829409294102941129412294132941429415294162941729418294192942029421294222942329424294252942629427294282942929430294312943229433294342943529436294372943829439294402944129442294432944429445294462944729448294492945029451294522945329454294552945629457294582945929460294612946229463294642946529466294672946829469294702947129472294732947429475294762947729478294792948029481294822948329484294852948629487294882948929490294912949229493294942949529496294972949829499295002950129502295032950429505295062950729508295092951029511295122951329514295152951629517295182951929520295212952229523295242952529526295272952829529295302953129532295332953429535295362953729538295392954029541295422954329544295452954629547295482954929550295512955229553295542955529556295572955829559295602956129562295632956429565295662956729568295692957029571295722957329574295752957629577295782957929580295812958229583295842958529586295872958829589295902959129592295932959429595295962959729598295992960029601296022960329604296052960629607296082960929610296112961229613296142961529616296172961829619296202962129622296232962429625296262962729628296292963029631296322963329634296352963629637296382963929640296412964229643296442964529646296472964829649296502965129652296532965429655296562965729658296592966029661296622966329664296652966629667296682966929670296712967229673296742967529676296772967829679296802968129682296832968429685296862968729688296892969029691296922969329694296952969629697296982969929700297012970229703297042970529706297072970829709297102971129712297132971429715297162971729718297192972029721297222972329724297252972629727297282972929730297312973229733297342973529736297372973829739297402974129742297432974429745297462974729748297492975029751297522975329754297552975629757297582975929760297612976229763297642976529766297672976829769297702977129772297732977429775297762977729778297792978029781297822978329784297852978629787297882978929790297912979229793297942979529796297972979829799298002980129802298032980429805298062980729808298092981029811298122981329814298152981629817298182981929820298212982229823298242982529826298272982829829298302983129832298332983429835298362983729838298392984029841298422984329844298452984629847298482984929850298512985229853298542985529856298572985829859298602986129862298632986429865298662986729868298692987029871298722987329874298752987629877298782987929880298812988229883298842988529886298872988829889298902989129892298932989429895298962989729898298992990029901299022990329904299052990629907299082990929910299112991229913299142991529916299172991829919299202992129922299232992429925299262992729928299292993029931299322993329934299352993629937299382993929940299412994229943299442994529946299472994829949299502995129952299532995429955299562995729958299592996029961299622996329964299652996629967299682996929970299712997229973299742997529976299772997829979299802998129982299832998429985299862998729988299892999029991299922999329994299952999629997299982999930000300013000230003300043000530006300073000830009300103001130012300133001430015300163001730018300193002030021300223002330024300253002630027300283002930030300313003230033300343003530036300373003830039300403004130042300433004430045300463004730048300493005030051300523005330054300553005630057300583005930060300613006230063300643006530066300673006830069300703007130072300733007430075300763007730078300793008030081300823008330084300853008630087300883008930090300913009230093300943009530096300973009830099301003010130102301033010430105301063010730108301093011030111301123011330114301153011630117301183011930120301213012230123301243012530126301273012830129301303013130132301333013430135301363013730138301393014030141301423014330144301453014630147301483014930150301513015230153301543015530156301573015830159301603016130162301633016430165301663016730168301693017030171301723017330174301753017630177301783017930180301813018230183301843018530186301873018830189301903019130192301933019430195301963019730198301993020030201302023020330204302053020630207302083020930210302113021230213302143021530216302173021830219302203022130222302233022430225302263022730228302293023030231302323023330234302353023630237302383023930240302413024230243302443024530246302473024830249302503025130252302533025430255302563025730258302593026030261302623026330264302653026630267302683026930270302713027230273302743027530276302773027830279302803028130282302833028430285302863028730288302893029030291302923029330294302953029630297302983029930300303013030230303303043030530306303073030830309303103031130312303133031430315303163031730318303193032030321303223032330324303253032630327303283032930330303313033230333303343033530336303373033830339303403034130342303433034430345303463034730348303493035030351303523035330354303553035630357303583035930360303613036230363303643036530366303673036830369303703037130372303733037430375303763037730378303793038030381303823038330384303853038630387303883038930390303913039230393303943039530396303973039830399304003040130402304033040430405304063040730408304093041030411304123041330414304153041630417304183041930420304213042230423304243042530426304273042830429304303043130432304333043430435304363043730438304393044030441304423044330444304453044630447304483044930450304513045230453304543045530456304573045830459304603046130462304633046430465304663046730468304693047030471304723047330474304753047630477304783047930480304813048230483304843048530486304873048830489304903049130492304933049430495304963049730498304993050030501305023050330504305053050630507305083050930510305113051230513305143051530516305173051830519305203052130522305233052430525305263052730528305293053030531305323053330534305353053630537305383053930540305413054230543305443054530546305473054830549305503055130552305533055430555305563055730558305593056030561305623056330564305653056630567305683056930570305713057230573305743057530576305773057830579305803058130582305833058430585305863058730588305893059030591305923059330594305953059630597305983059930600306013060230603306043060530606306073060830609306103061130612306133061430615306163061730618306193062030621306223062330624306253062630627306283062930630306313063230633306343063530636306373063830639306403064130642306433064430645306463064730648306493065030651306523065330654306553065630657306583065930660306613066230663306643066530666306673066830669306703067130672306733067430675306763067730678306793068030681306823068330684306853068630687306883068930690306913069230693306943069530696306973069830699307003070130702307033070430705307063070730708307093071030711307123071330714307153071630717307183071930720307213072230723307243072530726307273072830729307303073130732307333073430735307363073730738307393074030741307423074330744307453074630747307483074930750307513075230753307543075530756307573075830759307603076130762307633076430765307663076730768307693077030771307723077330774307753077630777307783077930780307813078230783307843078530786307873078830789307903079130792307933079430795307963079730798307993080030801308023080330804308053080630807308083080930810308113081230813308143081530816308173081830819308203082130822308233082430825308263082730828308293083030831308323083330834308353083630837308383083930840308413084230843308443084530846308473084830849308503085130852308533085430855308563085730858308593086030861308623086330864308653086630867308683086930870308713087230873308743087530876308773087830879308803088130882308833088430885308863088730888308893089030891308923089330894308953089630897308983089930900309013090230903309043090530906309073090830909309103091130912309133091430915309163091730918309193092030921309223092330924309253092630927309283092930930309313093230933309343093530936309373093830939309403094130942309433094430945309463094730948309493095030951309523095330954309553095630957309583095930960309613096230963309643096530966309673096830969309703097130972309733097430975309763097730978309793098030981309823098330984309853098630987309883098930990309913099230993309943099530996309973099830999310003100131002310033100431005310063100731008310093101031011310123101331014310153101631017310183101931020310213102231023310243102531026310273102831029310303103131032310333103431035310363103731038310393104031041310423104331044310453104631047310483104931050310513105231053310543105531056310573105831059310603106131062310633106431065310663106731068310693107031071310723107331074310753107631077310783107931080310813108231083310843108531086310873108831089310903109131092310933109431095310963109731098310993110031101311023110331104311053110631107311083110931110311113111231113311143111531116311173111831119311203112131122311233112431125311263112731128311293113031131311323113331134311353113631137311383113931140311413114231143311443114531146311473114831149311503115131152311533115431155311563115731158311593116031161311623116331164311653116631167311683116931170311713117231173311743117531176311773117831179311803118131182311833118431185311863118731188311893119031191311923119331194311953119631197311983119931200312013120231203312043120531206312073120831209312103121131212312133121431215312163121731218312193122031221312223122331224312253122631227312283122931230312313123231233312343123531236312373123831239312403124131242312433124431245312463124731248312493125031251312523125331254312553125631257312583125931260312613126231263312643126531266312673126831269312703127131272312733127431275312763127731278312793128031281312823128331284312853128631287312883128931290312913129231293312943129531296312973129831299313003130131302313033130431305313063130731308313093131031311313123131331314313153131631317313183131931320313213132231323313243132531326313273132831329313303133131332313333133431335313363133731338313393134031341313423134331344313453134631347313483134931350313513135231353313543135531356313573135831359313603136131362313633136431365313663136731368313693137031371313723137331374313753137631377313783137931380313813138231383313843138531386313873138831389313903139131392313933139431395313963139731398313993140031401314023140331404314053140631407314083140931410314113141231413314143141531416314173141831419314203142131422314233142431425314263142731428314293143031431314323143331434314353143631437314383143931440314413144231443314443144531446314473144831449314503145131452314533145431455314563145731458314593146031461314623146331464314653146631467314683146931470314713147231473314743147531476314773147831479314803148131482314833148431485314863148731488314893149031491314923149331494314953149631497314983149931500315013150231503315043150531506315073150831509315103151131512315133151431515315163151731518315193152031521315223152331524315253152631527315283152931530315313153231533315343153531536315373153831539315403154131542315433154431545315463154731548315493155031551315523155331554315553155631557315583155931560315613156231563315643156531566315673156831569315703157131572315733157431575315763157731578315793158031581315823158331584315853158631587315883158931590315913159231593315943159531596315973159831599316003160131602316033160431605316063160731608316093161031611316123161331614316153161631617316183161931620316213162231623316243162531626316273162831629316303163131632316333163431635316363163731638316393164031641316423164331644316453164631647316483164931650316513165231653316543165531656316573165831659316603166131662316633166431665316663166731668316693167031671316723167331674316753167631677316783167931680316813168231683316843168531686316873168831689316903169131692316933169431695316963169731698316993170031701317023170331704317053170631707317083170931710317113171231713317143171531716317173171831719317203172131722317233172431725317263172731728317293173031731317323173331734317353173631737317383173931740317413174231743317443174531746317473174831749317503175131752317533175431755317563175731758317593176031761317623176331764317653176631767317683176931770317713177231773317743177531776317773177831779317803178131782317833178431785317863178731788317893179031791317923179331794317953179631797317983179931800318013180231803318043180531806318073180831809318103181131812318133181431815318163181731818318193182031821318223182331824318253182631827318283182931830318313183231833318343183531836318373183831839318403184131842318433184431845318463184731848318493185031851318523185331854318553185631857318583185931860318613186231863318643186531866318673186831869318703187131872318733187431875318763187731878318793188031881318823188331884318853188631887318883188931890318913189231893318943189531896318973189831899319003190131902319033190431905319063190731908319093191031911319123191331914319153191631917319183191931920319213192231923319243192531926319273192831929319303193131932319333193431935319363193731938319393194031941319423194331944319453194631947319483194931950319513195231953319543195531956319573195831959319603196131962319633196431965319663196731968319693197031971319723197331974319753197631977319783197931980319813198231983319843198531986319873198831989319903199131992319933199431995319963199731998319993200032001320023200332004320053200632007320083200932010320113201232013320143201532016320173201832019320203202132022320233202432025320263202732028320293203032031320323203332034320353203632037320383203932040320413204232043320443204532046320473204832049320503205132052320533205432055320563205732058320593206032061320623206332064320653206632067320683206932070320713207232073320743207532076320773207832079320803208132082320833208432085320863208732088320893209032091320923209332094320953209632097320983209932100321013210232103321043210532106321073210832109321103211132112321133211432115321163211732118321193212032121321223212332124321253212632127321283212932130321313213232133321343213532136321373213832139321403214132142321433214432145321463214732148321493215032151321523215332154321553215632157321583215932160321613216232163321643216532166321673216832169321703217132172321733217432175321763217732178321793218032181321823218332184321853218632187321883218932190321913219232193321943219532196321973219832199322003220132202322033220432205322063220732208322093221032211322123221332214322153221632217322183221932220322213222232223322243222532226322273222832229322303223132232322333223432235322363223732238322393224032241322423224332244322453224632247322483224932250322513225232253322543225532256322573225832259322603226132262322633226432265322663226732268322693227032271322723227332274322753227632277322783227932280322813228232283322843228532286322873228832289322903229132292322933229432295322963229732298322993230032301323023230332304323053230632307323083230932310323113231232313323143231532316323173231832319323203232132322323233232432325323263232732328323293233032331323323233332334323353233632337323383233932340323413234232343323443234532346323473234832349323503235132352323533235432355323563235732358323593236032361323623236332364323653236632367323683236932370323713237232373323743237532376323773237832379323803238132382323833238432385323863238732388323893239032391323923239332394323953239632397323983239932400324013240232403324043240532406324073240832409324103241132412324133241432415324163241732418324193242032421324223242332424324253242632427324283242932430324313243232433324343243532436324373243832439324403244132442324433244432445324463244732448324493245032451324523245332454324553245632457324583245932460324613246232463324643246532466324673246832469324703247132472324733247432475324763247732478324793248032481324823248332484324853248632487324883248932490324913249232493324943249532496324973249832499325003250132502325033250432505325063250732508325093251032511325123251332514325153251632517325183251932520325213252232523325243252532526325273252832529325303253132532325333253432535325363253732538325393254032541325423254332544325453254632547325483254932550325513255232553325543255532556325573255832559325603256132562325633256432565325663256732568325693257032571325723257332574325753257632577325783257932580325813258232583325843258532586325873258832589325903259132592325933259432595325963259732598325993260032601326023260332604326053260632607326083260932610326113261232613326143261532616326173261832619326203262132622326233262432625326263262732628326293263032631326323263332634326353263632637326383263932640326413264232643326443264532646326473264832649326503265132652326533265432655326563265732658326593266032661326623266332664326653266632667326683266932670326713267232673326743267532676326773267832679326803268132682326833268432685326863268732688326893269032691326923269332694326953269632697326983269932700327013270232703327043270532706327073270832709327103271132712327133271432715327163271732718327193272032721327223272332724327253272632727327283272932730327313273232733327343273532736327373273832739327403274132742327433274432745327463274732748327493275032751327523275332754327553275632757327583275932760327613276232763327643276532766327673276832769327703277132772327733277432775327763277732778327793278032781327823278332784327853278632787327883278932790327913279232793327943279532796327973279832799328003280132802328033280432805328063280732808328093281032811328123281332814328153281632817328183281932820328213282232823328243282532826328273282832829328303283132832328333283432835328363283732838328393284032841328423284332844328453284632847328483284932850328513285232853328543285532856328573285832859328603286132862328633286432865328663286732868328693287032871328723287332874328753287632877328783287932880328813288232883328843288532886328873288832889328903289132892328933289432895328963289732898328993290032901329023290332904329053290632907329083290932910329113291232913329143291532916329173291832919329203292132922329233292432925329263292732928329293293032931329323293332934329353293632937329383293932940329413294232943329443294532946329473294832949329503295132952329533295432955329563295732958329593296032961329623296332964329653296632967329683296932970329713297232973329743297532976329773297832979329803298132982329833298432985329863298732988329893299032991329923299332994329953299632997329983299933000330013300233003330043300533006330073300833009330103301133012330133301433015330163301733018330193302033021330223302333024330253302633027330283302933030330313303233033330343303533036330373303833039330403304133042330433304433045330463304733048330493305033051330523305333054330553305633057330583305933060330613306233063330643306533066330673306833069330703307133072330733307433075330763307733078330793308033081330823308333084330853308633087330883308933090330913309233093330943309533096330973309833099331003310133102331033310433105331063310733108331093311033111331123311333114331153311633117331183311933120331213312233123331243312533126331273312833129331303313133132331333313433135331363313733138331393314033141331423314333144331453314633147331483314933150331513315233153331543315533156331573315833159331603316133162331633316433165331663316733168331693317033171331723317333174331753317633177331783317933180331813318233183331843318533186331873318833189331903319133192331933319433195331963319733198331993320033201332023320333204332053320633207332083320933210332113321233213332143321533216332173321833219332203322133222332233322433225332263322733228332293323033231332323323333234332353323633237332383323933240332413324233243332443324533246332473324833249332503325133252332533325433255332563325733258332593326033261332623326333264332653326633267332683326933270332713327233273332743327533276332773327833279332803328133282332833328433285332863328733288332893329033291332923329333294332953329633297332983329933300333013330233303333043330533306333073330833309333103331133312333133331433315333163331733318333193332033321333223332333324333253332633327333283332933330333313333233333333343333533336333373333833339333403334133342333433334433345333463334733348333493335033351333523335333354333553335633357333583335933360333613336233363333643336533366333673336833369333703337133372333733337433375333763337733378333793338033381333823338333384333853338633387333883338933390333913339233393333943339533396333973339833399334003340133402334033340433405334063340733408334093341033411334123341333414334153341633417334183341933420334213342233423334243342533426334273342833429334303343133432334333343433435334363343733438334393344033441334423344333444334453344633447334483344933450334513345233453334543345533456334573345833459334603346133462334633346433465334663346733468334693347033471334723347333474334753347633477334783347933480334813348233483334843348533486334873348833489334903349133492334933349433495334963349733498334993350033501335023350333504335053350633507335083350933510335113351233513335143351533516335173351833519335203352133522335233352433525335263352733528335293353033531335323353333534335353353633537335383353933540335413354233543335443354533546335473354833549335503355133552335533355433555335563355733558335593356033561335623356333564335653356633567335683356933570335713357233573335743357533576335773357833579335803358133582335833358433585335863358733588335893359033591335923359333594335953359633597335983359933600336013360233603336043360533606336073360833609336103361133612336133361433615336163361733618336193362033621336223362333624336253362633627336283362933630336313363233633336343363533636336373363833639336403364133642336433364433645336463364733648336493365033651336523365333654336553365633657336583365933660336613366233663336643366533666336673366833669336703367133672336733367433675336763367733678336793368033681336823368333684336853368633687336883368933690336913369233693336943369533696336973369833699337003370133702337033370433705337063370733708337093371033711337123371333714337153371633717337183371933720337213372233723337243372533726337273372833729337303373133732337333373433735337363373733738337393374033741337423374333744337453374633747337483374933750337513375233753337543375533756337573375833759337603376133762337633376433765337663376733768337693377033771337723377333774337753377633777337783377933780337813378233783337843378533786337873378833789337903379133792337933379433795337963379733798337993380033801338023380333804338053380633807338083380933810338113381233813338143381533816338173381833819338203382133822338233382433825338263382733828338293383033831338323383333834338353383633837338383383933840338413384233843338443384533846338473384833849338503385133852338533385433855338563385733858338593386033861338623386333864338653386633867338683386933870338713387233873338743387533876338773387833879338803388133882338833388433885338863388733888338893389033891338923389333894338953389633897338983389933900339013390233903339043390533906339073390833909339103391133912339133391433915339163391733918339193392033921339223392333924339253392633927339283392933930339313393233933339343393533936339373393833939339403394133942339433394433945339463394733948339493395033951339523395333954339553395633957339583395933960339613396233963339643396533966339673396833969339703397133972339733397433975339763397733978339793398033981339823398333984339853398633987339883398933990339913399233993339943399533996339973399833999340003400134002340033400434005340063400734008340093401034011340123401334014340153401634017340183401934020340213402234023340243402534026340273402834029340303403134032340333403434035340363403734038340393404034041340423404334044340453404634047340483404934050340513405234053340543405534056340573405834059340603406134062340633406434065340663406734068340693407034071340723407334074340753407634077340783407934080340813408234083340843408534086340873408834089340903409134092340933409434095340963409734098340993410034101341023410334104341053410634107341083410934110341113411234113341143411534116341173411834119341203412134122341233412434125341263412734128341293413034131341323413334134341353413634137341383413934140341413414234143341443414534146341473414834149341503415134152341533415434155341563415734158341593416034161341623416334164341653416634167341683416934170341713417234173341743417534176341773417834179341803418134182341833418434185341863418734188341893419034191341923419334194341953419634197341983419934200342013420234203342043420534206342073420834209342103421134212342133421434215342163421734218342193422034221342223422334224342253422634227342283422934230342313423234233342343423534236342373423834239342403424134242342433424434245342463424734248342493425034251342523425334254342553425634257342583425934260342613426234263342643426534266342673426834269342703427134272342733427434275342763427734278342793428034281342823428334284342853428634287342883428934290342913429234293342943429534296342973429834299343003430134302343033430434305343063430734308343093431034311343123431334314343153431634317343183431934320343213432234323343243432534326343273432834329343303433134332343333433434335343363433734338343393434034341343423434334344343453434634347343483434934350343513435234353343543435534356343573435834359343603436134362343633436434365343663436734368343693437034371343723437334374343753437634377343783437934380343813438234383343843438534386343873438834389343903439134392343933439434395343963439734398343993440034401344023440334404344053440634407344083440934410344113441234413344143441534416344173441834419344203442134422344233442434425344263442734428344293443034431344323443334434344353443634437344383443934440344413444234443344443444534446344473444834449344503445134452344533445434455344563445734458344593446034461344623446334464344653446634467344683446934470344713447234473344743447534476344773447834479344803448134482344833448434485344863448734488344893449034491344923449334494344953449634497344983449934500345013450234503345043450534506345073450834509345103451134512345133451434515345163451734518345193452034521345223452334524345253452634527345283452934530345313453234533345343453534536345373453834539345403454134542345433454434545345463454734548345493455034551345523455334554345553455634557345583455934560345613456234563345643456534566345673456834569345703457134572345733457434575345763457734578345793458034581345823458334584345853458634587345883458934590345913459234593345943459534596345973459834599346003460134602346033460434605346063460734608346093461034611346123461334614346153461634617346183461934620346213462234623346243462534626346273462834629346303463134632346333463434635346363463734638346393464034641346423464334644346453464634647346483464934650346513465234653346543465534656346573465834659346603466134662346633466434665346663466734668346693467034671346723467334674346753467634677346783467934680346813468234683346843468534686346873468834689346903469134692346933469434695346963469734698346993470034701347023470334704347053470634707347083470934710347113471234713347143471534716347173471834719347203472134722347233472434725347263472734728347293473034731347323473334734347353473634737347383473934740347413474234743347443474534746347473474834749347503475134752347533475434755347563475734758347593476034761347623476334764347653476634767347683476934770347713477234773347743477534776347773477834779347803478134782347833478434785347863478734788347893479034791347923479334794347953479634797347983479934800348013480234803348043480534806348073480834809348103481134812348133481434815348163481734818348193482034821348223482334824348253482634827348283482934830348313483234833348343483534836348373483834839348403484134842348433484434845348463484734848348493485034851348523485334854348553485634857348583485934860348613486234863348643486534866348673486834869348703487134872348733487434875348763487734878348793488034881348823488334884348853488634887348883488934890348913489234893348943489534896348973489834899349003490134902349033490434905349063490734908349093491034911349123491334914349153491634917349183491934920349213492234923349243492534926349273492834929349303493134932349333493434935349363493734938349393494034941349423494334944349453494634947349483494934950349513495234953349543495534956349573495834959349603496134962349633496434965349663496734968349693497034971349723497334974349753497634977349783497934980349813498234983349843498534986349873498834989349903499134992349933499434995349963499734998349993500035001350023500335004350053500635007350083500935010350113501235013350143501535016350173501835019350203502135022350233502435025350263502735028350293503035031350323503335034350353503635037350383503935040350413504235043350443504535046350473504835049350503505135052350533505435055350563505735058350593506035061350623506335064350653506635067350683506935070350713507235073350743507535076350773507835079350803508135082350833508435085350863508735088350893509035091350923509335094350953509635097350983509935100351013510235103351043510535106351073510835109351103511135112351133511435115351163511735118351193512035121351223512335124351253512635127351283512935130351313513235133351343513535136351373513835139351403514135142351433514435145351463514735148351493515035151351523515335154351553515635157351583515935160351613516235163351643516535166351673516835169351703517135172351733517435175351763517735178351793518035181351823518335184351853518635187351883518935190351913519235193351943519535196351973519835199352003520135202352033520435205352063520735208352093521035211352123521335214352153521635217352183521935220352213522235223352243522535226352273522835229352303523135232352333523435235352363523735238352393524035241352423524335244352453524635247352483524935250352513525235253352543525535256352573525835259352603526135262352633526435265352663526735268352693527035271352723527335274352753527635277352783527935280352813528235283352843528535286352873528835289352903529135292352933529435295352963529735298352993530035301353023530335304353053530635307353083530935310353113531235313353143531535316353173531835319353203532135322353233532435325353263532735328353293533035331353323533335334353353533635337353383533935340353413534235343353443534535346353473534835349353503535135352353533535435355353563535735358353593536035361353623536335364353653536635367353683536935370353713537235373353743537535376353773537835379353803538135382353833538435385353863538735388353893539035391353923539335394353953539635397353983539935400354013540235403354043540535406354073540835409354103541135412354133541435415354163541735418354193542035421354223542335424354253542635427354283542935430354313543235433354343543535436354373543835439354403544135442354433544435445354463544735448354493545035451354523545335454354553545635457354583545935460354613546235463354643546535466354673546835469354703547135472354733547435475354763547735478354793548035481354823548335484354853548635487354883548935490354913549235493354943549535496354973549835499355003550135502355033550435505355063550735508355093551035511355123551335514355153551635517355183551935520355213552235523355243552535526355273552835529355303553135532355333553435535355363553735538355393554035541355423554335544355453554635547355483554935550355513555235553355543555535556355573555835559355603556135562355633556435565355663556735568355693557035571355723557335574355753557635577355783557935580355813558235583355843558535586355873558835589355903559135592355933559435595355963559735598355993560035601356023560335604356053560635607356083560935610356113561235613356143561535616356173561835619356203562135622356233562435625356263562735628356293563035631356323563335634356353563635637356383563935640356413564235643356443564535646356473564835649356503565135652356533565435655356563565735658356593566035661356623566335664356653566635667356683566935670356713567235673356743567535676356773567835679356803568135682356833568435685356863568735688356893569035691356923569335694356953569635697356983569935700357013570235703357043570535706357073570835709357103571135712357133571435715357163571735718357193572035721357223572335724357253572635727357283572935730357313573235733357343573535736357373573835739357403574135742357433574435745357463574735748357493575035751357523575335754357553575635757357583575935760357613576235763357643576535766357673576835769357703577135772357733577435775357763577735778357793578035781357823578335784357853578635787357883578935790357913579235793357943579535796357973579835799358003580135802358033580435805358063580735808358093581035811358123581335814358153581635817358183581935820358213582235823358243582535826358273582835829358303583135832358333583435835358363583735838358393584035841358423584335844358453584635847358483584935850358513585235853358543585535856358573585835859358603586135862358633586435865358663586735868358693587035871358723587335874358753587635877358783587935880358813588235883358843588535886358873588835889358903589135892358933589435895358963589735898358993590035901359023590335904359053590635907359083590935910359113591235913359143591535916359173591835919359203592135922359233592435925359263592735928359293593035931359323593335934359353593635937359383593935940359413594235943359443594535946359473594835949359503595135952359533595435955359563595735958359593596035961359623596335964359653596635967359683596935970359713597235973359743597535976359773597835979359803598135982359833598435985359863598735988359893599035991359923599335994359953599635997359983599936000360013600236003360043600536006360073600836009360103601136012360133601436015360163601736018360193602036021360223602336024360253602636027360283602936030360313603236033360343603536036360373603836039360403604136042360433604436045360463604736048360493605036051360523605336054360553605636057360583605936060360613606236063360643606536066360673606836069360703607136072360733607436075360763607736078360793608036081360823608336084360853608636087360883608936090360913609236093360943609536096360973609836099361003610136102361033610436105361063610736108361093611036111361123611336114361153611636117361183611936120361213612236123361243612536126361273612836129361303613136132361333613436135361363613736138361393614036141361423614336144361453614636147361483614936150361513615236153361543615536156361573615836159361603616136162361633616436165361663616736168361693617036171361723617336174361753617636177361783617936180361813618236183361843618536186361873618836189361903619136192361933619436195361963619736198361993620036201362023620336204362053620636207362083620936210362113621236213362143621536216362173621836219362203622136222362233622436225362263622736228362293623036231362323623336234362353623636237362383623936240362413624236243362443624536246362473624836249362503625136252362533625436255362563625736258362593626036261362623626336264362653626636267362683626936270362713627236273362743627536276362773627836279362803628136282362833628436285362863628736288362893629036291362923629336294362953629636297362983629936300363013630236303363043630536306363073630836309363103631136312363133631436315363163631736318363193632036321363223632336324363253632636327363283632936330363313633236333363343633536336363373633836339363403634136342363433634436345363463634736348363493635036351363523635336354363553635636357363583635936360363613636236363363643636536366363673636836369363703637136372363733637436375363763637736378363793638036381363823638336384363853638636387363883638936390363913639236393363943639536396363973639836399364003640136402364033640436405364063640736408364093641036411364123641336414364153641636417364183641936420364213642236423364243642536426364273642836429364303643136432364333643436435364363643736438364393644036441364423644336444364453644636447364483644936450364513645236453364543645536456364573645836459364603646136462364633646436465364663646736468364693647036471364723647336474364753647636477364783647936480364813648236483364843648536486364873648836489364903649136492364933649436495364963649736498364993650036501365023650336504365053650636507365083650936510365113651236513365143651536516365173651836519365203652136522365233652436525365263652736528365293653036531365323653336534365353653636537365383653936540365413654236543365443654536546365473654836549365503655136552365533655436555365563655736558365593656036561365623656336564365653656636567365683656936570365713657236573365743657536576365773657836579365803658136582365833658436585365863658736588365893659036591365923659336594365953659636597365983659936600366013660236603366043660536606366073660836609366103661136612366133661436615366163661736618366193662036621366223662336624366253662636627366283662936630366313663236633366343663536636366373663836639366403664136642366433664436645366463664736648366493665036651366523665336654366553665636657366583665936660366613666236663366643666536666366673666836669366703667136672366733667436675366763667736678366793668036681366823668336684366853668636687366883668936690366913669236693366943669536696366973669836699367003670136702367033670436705367063670736708367093671036711367123671336714367153671636717367183671936720367213672236723367243672536726367273672836729367303673136732367333673436735367363673736738367393674036741367423674336744367453674636747367483674936750367513675236753367543675536756367573675836759367603676136762367633676436765367663676736768367693677036771367723677336774367753677636777367783677936780367813678236783367843678536786367873678836789367903679136792367933679436795367963679736798367993680036801368023680336804368053680636807368083680936810368113681236813368143681536816368173681836819368203682136822368233682436825368263682736828368293683036831368323683336834368353683636837368383683936840368413684236843368443684536846368473684836849368503685136852368533685436855368563685736858368593686036861368623686336864368653686636867368683686936870368713687236873368743687536876368773687836879368803688136882368833688436885368863688736888368893689036891368923689336894368953689636897368983689936900369013690236903369043690536906369073690836909369103691136912369133691436915369163691736918369193692036921369223692336924369253692636927369283692936930369313693236933369343693536936369373693836939369403694136942369433694436945369463694736948369493695036951369523695336954369553695636957369583695936960369613696236963369643696536966369673696836969369703697136972369733697436975369763697736978369793698036981369823698336984369853698636987369883698936990369913699236993369943699536996369973699836999370003700137002370033700437005370063700737008370093701037011370123701337014370153701637017370183701937020370213702237023370243702537026370273702837029370303703137032370333703437035370363703737038370393704037041370423704337044370453704637047370483704937050370513705237053370543705537056370573705837059370603706137062370633706437065370663706737068370693707037071370723707337074370753707637077370783707937080370813708237083370843708537086370873708837089370903709137092370933709437095370963709737098370993710037101371023710337104371053710637107371083710937110371113711237113371143711537116371173711837119371203712137122371233712437125371263712737128371293713037131371323713337134371353713637137371383713937140371413714237143371443714537146371473714837149371503715137152371533715437155371563715737158371593716037161371623716337164371653716637167371683716937170371713717237173371743717537176371773717837179371803718137182371833718437185371863718737188371893719037191371923719337194371953719637197371983719937200372013720237203372043720537206372073720837209372103721137212372133721437215372163721737218372193722037221372223722337224372253722637227372283722937230372313723237233372343723537236372373723837239372403724137242372433724437245372463724737248372493725037251372523725337254372553725637257372583725937260372613726237263372643726537266372673726837269372703727137272372733727437275372763727737278372793728037281372823728337284372853728637287372883728937290372913729237293372943729537296372973729837299373003730137302373033730437305373063730737308373093731037311373123731337314373153731637317373183731937320373213732237323373243732537326373273732837329373303733137332373333733437335373363733737338373393734037341373423734337344373453734637347373483734937350373513735237353373543735537356373573735837359373603736137362373633736437365373663736737368373693737037371373723737337374373753737637377373783737937380373813738237383373843738537386373873738837389373903739137392373933739437395373963739737398373993740037401374023740337404374053740637407374083740937410374113741237413374143741537416374173741837419374203742137422374233742437425374263742737428374293743037431374323743337434374353743637437374383743937440374413744237443374443744537446374473744837449374503745137452374533745437455374563745737458374593746037461374623746337464374653746637467374683746937470374713747237473374743747537476374773747837479374803748137482374833748437485374863748737488374893749037491374923749337494374953749637497374983749937500375013750237503375043750537506375073750837509375103751137512375133751437515375163751737518375193752037521375223752337524375253752637527375283752937530375313753237533375343753537536375373753837539375403754137542375433754437545375463754737548375493755037551375523755337554375553755637557375583755937560375613756237563375643756537566375673756837569375703757137572375733757437575375763757737578375793758037581375823758337584375853758637587375883758937590375913759237593375943759537596375973759837599376003760137602376033760437605376063760737608376093761037611376123761337614376153761637617376183761937620376213762237623376243762537626376273762837629376303763137632376333763437635376363763737638376393764037641376423764337644376453764637647376483764937650376513765237653376543765537656376573765837659376603766137662376633766437665376663766737668376693767037671376723767337674376753767637677376783767937680376813768237683376843768537686376873768837689376903769137692376933769437695376963769737698376993770037701377023770337704377053770637707377083770937710377113771237713377143771537716377173771837719377203772137722377233772437725377263772737728377293773037731377323773337734377353773637737377383773937740377413774237743377443774537746377473774837749377503775137752377533775437755377563775737758377593776037761377623776337764377653776637767377683776937770377713777237773377743777537776377773777837779377803778137782377833778437785377863778737788377893779037791377923779337794377953779637797377983779937800378013780237803378043780537806378073780837809378103781137812378133781437815378163781737818378193782037821378223782337824378253782637827378283782937830378313783237833378343783537836378373783837839378403784137842378433784437845378463784737848378493785037851378523785337854378553785637857378583785937860378613786237863378643786537866378673786837869378703787137872378733787437875378763787737878378793788037881378823788337884378853788637887378883788937890378913789237893378943789537896378973789837899379003790137902379033790437905379063790737908379093791037911379123791337914379153791637917379183791937920379213792237923379243792537926379273792837929379303793137932379333793437935379363793737938379393794037941379423794337944379453794637947379483794937950379513795237953379543795537956379573795837959379603796137962379633796437965379663796737968379693797037971379723797337974379753797637977379783797937980379813798237983379843798537986379873798837989379903799137992379933799437995379963799737998379993800038001380023800338004380053800638007380083800938010380113801238013380143801538016380173801838019380203802138022380233802438025380263802738028380293803038031380323803338034380353803638037380383803938040380413804238043380443804538046380473804838049380503805138052380533805438055380563805738058380593806038061380623806338064380653806638067380683806938070380713807238073380743807538076380773807838079380803808138082380833808438085380863808738088380893809038091380923809338094380953809638097380983809938100381013810238103381043810538106381073810838109381103811138112381133811438115381163811738118381193812038121381223812338124381253812638127381283812938130381313813238133381343813538136381373813838139381403814138142381433814438145381463814738148381493815038151381523815338154381553815638157381583815938160381613816238163381643816538166381673816838169381703817138172381733817438175381763817738178381793818038181381823818338184381853818638187381883818938190381913819238193381943819538196381973819838199382003820138202382033820438205382063820738208382093821038211382123821338214382153821638217382183821938220382213822238223382243822538226382273822838229382303823138232382333823438235382363823738238382393824038241382423824338244382453824638247382483824938250382513825238253382543825538256382573825838259382603826138262382633826438265382663826738268382693827038271382723827338274382753827638277382783827938280382813828238283382843828538286382873828838289382903829138292382933829438295382963829738298382993830038301383023830338304383053830638307383083830938310383113831238313383143831538316383173831838319383203832138322383233832438325383263832738328383293833038331383323833338334383353833638337383383833938340383413834238343383443834538346383473834838349383503835138352383533835438355383563835738358383593836038361383623836338364383653836638367383683836938370383713837238373383743837538376383773837838379383803838138382383833838438385383863838738388383893839038391383923839338394383953839638397383983839938400384013840238403384043840538406384073840838409384103841138412384133841438415384163841738418384193842038421384223842338424384253842638427384283842938430384313843238433384343843538436384373843838439384403844138442384433844438445384463844738448384493845038451384523845338454384553845638457384583845938460384613846238463384643846538466384673846838469384703847138472384733847438475384763847738478384793848038481384823848338484384853848638487384883848938490384913849238493384943849538496384973849838499385003850138502385033850438505385063850738508385093851038511385123851338514385153851638517385183851938520385213852238523385243852538526385273852838529385303853138532385333853438535385363853738538385393854038541385423854338544385453854638547385483854938550385513855238553385543855538556385573855838559385603856138562385633856438565385663856738568385693857038571385723857338574385753857638577385783857938580385813858238583385843858538586385873858838589385903859138592385933859438595385963859738598385993860038601386023860338604386053860638607386083860938610386113861238613386143861538616386173861838619386203862138622386233862438625386263862738628386293863038631386323863338634386353863638637386383863938640386413864238643386443864538646386473864838649386503865138652386533865438655386563865738658386593866038661386623866338664386653866638667386683866938670386713867238673386743867538676386773867838679386803868138682386833868438685386863868738688386893869038691386923869338694386953869638697386983869938700387013870238703387043870538706387073870838709387103871138712387133871438715387163871738718387193872038721387223872338724387253872638727387283872938730387313873238733387343873538736387373873838739387403874138742387433874438745387463874738748387493875038751387523875338754387553875638757387583875938760387613876238763387643876538766387673876838769387703877138772387733877438775
  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. // eslint-disable-next-line dot-notation
  100. ratio = image.type === 'svg' ? 1 : (window['devicePixelRatio'] || 1),
  101. size;
  102. if (!img.naturalWidth || !img.naturalHeight) {
  103. img.onload = function() {
  104. var width = img.naturalWidth,
  105. height = img.naturalHeight;
  106. me.setWidth(Math.floor(width / ratio));
  107. me.setHeight(Math.floor(height / ratio));
  108. };
  109. } else {
  110. size = me.getSize();
  111. me.setWidth(Math.floor(size.width / ratio));
  112. me.setHeight(Math.floor(size.height / ratio));
  113. }
  114. }
  115. }
  116. };
  117. }
  118. new Ext.window.Window({
  119. title: this.previewTitleText,
  120. closable: true,
  121. renderTo: Ext.getBody(),
  122. autoShow: true,
  123. maximizeable: true,
  124. maximized: true,
  125. border: true,
  126. layout: {
  127. type: 'hbox',
  128. pack: 'center',
  129. align: 'middle'
  130. },
  131. items: {
  132. xtype: 'container',
  133. items: items
  134. }
  135. });
  136. },
  137. privates: {
  138. getTargetEl: function() {
  139. return this.bodyElement;
  140. },
  141. reattachToBody: function() {
  142. // This is to ensure charts work properly as grid column widgets.
  143. var me = this;
  144. if (me.pendingDetachSize) {
  145. me.handleResize();
  146. }
  147. me.pendingDetachSize = false;
  148. me.callParent();
  149. }
  150. }
  151. });
  152. /**
  153. * @private
  154. * @class Ext.draw.SurfaceBase (Classic)
  155. */
  156. Ext.define('Ext.draw.SurfaceBase', {
  157. extend: 'Ext.Widget',
  158. getOwnerBody: function() {
  159. return this.ownerCt.body;
  160. }
  161. });
  162. /**
  163. * @private
  164. * @class Ext.draw.sprite.AnimationParser
  165. *
  166. * Computes an intermidiate value between two values of the same type for use in animations.
  167. * Can have pre- and post- processor functions if the values need to be processed
  168. * before an intermidiate value can be computed (parseInitial), or the computed value
  169. * needs to be processed before it can be used as a valid attribute value (serve).
  170. */
  171. Ext.define('Ext.draw.sprite.AnimationParser', function() {
  172. function compute(from, to, delta) {
  173. return from + (to - from) * delta;
  174. }
  175. return {
  176. singleton: true,
  177. attributeRe: /^url\(#([a-zA-Z-]+)\)$/,
  178. requires: [
  179. 'Ext.draw.Color'
  180. ],
  181. color: {
  182. parseInitial: function(color1, color2) {
  183. if (Ext.isString(color1)) {
  184. color1 = Ext.util.Color.create(color1);
  185. }
  186. if (Ext.isString(color2)) {
  187. color2 = Ext.util.Color.create(color2);
  188. }
  189. if ((color1 && color1.isColor) && (color2 && color2.isColor)) {
  190. return [
  191. [
  192. color1.r,
  193. color1.g,
  194. color1.b,
  195. color1.a
  196. ],
  197. [
  198. color2.r,
  199. color2.g,
  200. color2.b,
  201. color2.a
  202. ]
  203. ];
  204. } else {
  205. return [
  206. color1 || color2,
  207. color2 || color1
  208. ];
  209. }
  210. },
  211. compute: function(from, to, delta) {
  212. if (!Ext.isArray(from) || !Ext.isArray(to)) {
  213. return to || from;
  214. } else {
  215. return [
  216. compute(from[0], to[0], delta),
  217. compute(from[1], to[1], delta),
  218. compute(from[2], to[2], delta),
  219. compute(from[3], to[3], delta)
  220. ];
  221. }
  222. },
  223. serve: function(array) {
  224. var color = Ext.util.Color.fly(array[0], array[1], array[2], array[3]);
  225. return color.toString();
  226. }
  227. },
  228. number: {
  229. parse: function(n) {
  230. return n === null ? null : +n;
  231. },
  232. compute: function(from, to, delta) {
  233. if (!Ext.isNumber(from) || !Ext.isNumber(to)) {
  234. return to || from;
  235. } else {
  236. return compute(from, to, delta);
  237. }
  238. }
  239. },
  240. angle: {
  241. parseInitial: function(from, to) {
  242. if (to - from > Math.PI) {
  243. to -= Math.PI * 2;
  244. } else if (to - from < -Math.PI) {
  245. to += Math.PI * 2;
  246. }
  247. return [
  248. from,
  249. to
  250. ];
  251. },
  252. compute: function(from, to, delta) {
  253. if (!Ext.isNumber(from) || !Ext.isNumber(to)) {
  254. return to || from;
  255. } else {
  256. return compute(from, to, delta);
  257. }
  258. }
  259. },
  260. path: {
  261. parseInitial: function(from, to) {
  262. var fromStripes = from.toStripes(),
  263. toStripes = to.toStripes(),
  264. i, j,
  265. fromLength = fromStripes.length,
  266. toLength = toStripes.length,
  267. fromStripe, toStripe, length,
  268. lastStripe = toStripes[toLength - 1],
  269. endPoint = [
  270. lastStripe[lastStripe.length - 2],
  271. lastStripe[lastStripe.length - 1]
  272. ];
  273. for (i = fromLength; i < toLength; i++) {
  274. fromStripes.push(fromStripes[fromLength - 1].slice(0));
  275. }
  276. for (i = toLength; i < fromLength; i++) {
  277. toStripes.push(endPoint.slice(0));
  278. }
  279. length = fromStripes.length;
  280. toStripes.path = to;
  281. toStripes.temp = new Ext.draw.Path();
  282. for (i = 0; i < length; i++) {
  283. fromStripe = fromStripes[i];
  284. toStripe = toStripes[i];
  285. fromLength = fromStripe.length;
  286. toLength = toStripe.length;
  287. toStripes.temp.commands.push('M');
  288. for (j = toLength; j < fromLength; j += 6) {
  289. toStripe.push(endPoint[0], endPoint[1], endPoint[0], endPoint[1], endPoint[0], endPoint[1]);
  290. }
  291. lastStripe = toStripes[toStripes.length - 1];
  292. endPoint = [
  293. lastStripe[lastStripe.length - 2],
  294. lastStripe[lastStripe.length - 1]
  295. ];
  296. for (j = fromLength; j < toLength; j += 6) {
  297. fromStripe.push(endPoint[0], endPoint[1], endPoint[0], endPoint[1], endPoint[0], endPoint[1]);
  298. }
  299. for (i = 0; i < toStripe.length; i++) {
  300. toStripe[i] -= fromStripe[i];
  301. }
  302. for (i = 2; i < toStripe.length; i += 6) {
  303. toStripes.temp.commands.push('C');
  304. }
  305. }
  306. return [
  307. fromStripes,
  308. toStripes
  309. ];
  310. },
  311. compute: function(fromStripes, toStripes, delta) {
  312. if (delta >= 1) {
  313. return toStripes.path;
  314. }
  315. // eslint-disable-next-line vars-on-top
  316. var i = 0,
  317. ln = fromStripes.length,
  318. j = 0,
  319. ln2, from, to,
  320. temp = toStripes.temp.params,
  321. pos = 0;
  322. for (; i < ln; i++) {
  323. from = fromStripes[i];
  324. to = toStripes[i];
  325. ln2 = from.length;
  326. for (j = 0; j < ln2; j++) {
  327. temp[pos++] = to[j] * delta + from[j];
  328. }
  329. }
  330. return toStripes.temp;
  331. }
  332. },
  333. data: {
  334. compute: function(from, to, delta, target) {
  335. var iMaxFrom = from.length - 1,
  336. iMaxTo = to.length - 1,
  337. iMax = Math.max(iMaxFrom, iMaxTo),
  338. i, start, end;
  339. if (!target || target === from) {
  340. target = [];
  341. }
  342. target.length = iMax + 1;
  343. for (i = 0; i <= iMax; i++) {
  344. start = from[Math.min(i, iMaxFrom)];
  345. end = to[Math.min(i, iMaxTo)];
  346. if (Ext.isNumber(start)) {
  347. if (!Ext.isNumber(end)) {
  348. // This may not give the desired visual result during
  349. // animation (after all, we don't know what the target
  350. // value should be, if it wasn't given to us), but it's
  351. // better than spitting out a bunch of NaNs in the target
  352. // array, when transitioning from a non-empty to an empty
  353. // array.
  354. end = 0;
  355. }
  356. target[i] = start + (end - start) * delta;
  357. } else {
  358. target[i] = end;
  359. }
  360. }
  361. return target;
  362. }
  363. },
  364. text: {
  365. compute: function(from, to, delta) {
  366. return from.substr(0, Math.round(from.length * (1 - delta))) + to.substr(Math.round(to.length * (1 - delta)));
  367. }
  368. },
  369. limited: 'number',
  370. limited01: 'number'
  371. };
  372. });
  373. /* global Float32Array */
  374. /* eslint-disable indent */
  375. (function() {
  376. if (!Ext.global.Float32Array) {
  377. // Typed Array polyfill
  378. // eslint-disable-next-line vars-on-top
  379. var Float32Array = function(array) {
  380. var i, len;
  381. if (typeof array === 'number') {
  382. this.length = array;
  383. } else if ('length' in array) {
  384. this.length = array.length;
  385. for (i = 0 , len = array.length; i < len; i++) {
  386. this[i] = +array[i];
  387. }
  388. }
  389. };
  390. Float32Array.prototype = [];
  391. Ext.global.Float32Array = Float32Array;
  392. }
  393. })();
  394. /* eslint-enable indent */
  395. /**
  396. * Utility class providing mathematics functionalities through all the draw package.
  397. */
  398. Ext.define('Ext.draw.Draw', {
  399. singleton: true,
  400. radian: Math.PI / 180,
  401. pi2: Math.PI * 2,
  402. /**
  403. * @deprecated 6.5.0 Please use the {@link Ext#identityFn} instead.
  404. * Function that returns its first element.
  405. * @param {Mixed} a
  406. * @return {Mixed}
  407. */
  408. reflectFn: function(a) {
  409. return a;
  410. },
  411. /**
  412. * Converting degrees to radians.
  413. * @param {Number} degrees
  414. * @return {Number}
  415. */
  416. rad: function(degrees) {
  417. return (degrees % 360) * this.radian;
  418. },
  419. /**
  420. * Converting radians to degrees.
  421. * @param {Number} radian
  422. * @return {Number}
  423. */
  424. degrees: function(radian) {
  425. return (radian / this.radian) % 360;
  426. },
  427. /**
  428. *
  429. * @param {Object} bbox1
  430. * @param {Object} bbox2
  431. * @param {Number} [padding]
  432. * @return {Boolean}
  433. */
  434. isBBoxIntersect: function(bbox1, bbox2, padding) {
  435. padding = padding || 0;
  436. 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));
  437. },
  438. /**
  439. * Checks if a point is within a bounding box.
  440. * @param x
  441. * @param y
  442. * @param bbox
  443. * @return {Boolean}
  444. */
  445. isPointInBBox: function(x, y, bbox) {
  446. return !!bbox && x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height);
  447. },
  448. /**
  449. * Natural cubic spline interpolation.
  450. * This algorithm runs in linear time.
  451. *
  452. * @param {Array} points Array of numbers.
  453. */
  454. naturalSpline: function(points) {
  455. var i, j,
  456. ln = points.length,
  457. nd, d, y, ny,
  458. r = 0,
  459. zs = new Float32Array(points.length),
  460. result = new Float32Array(points.length * 3 - 2);
  461. zs[0] = 0;
  462. zs[ln - 1] = 0;
  463. for (i = 1; i < ln - 1; i++) {
  464. zs[i] = (points[i + 1] + points[i - 1] - 2 * points[i]) - zs[i - 1];
  465. r = 1 / (4 - r);
  466. zs[i] *= r;
  467. }
  468. for (i = ln - 2; i > 0; i--) {
  469. r = 3.732050807568877 + 48.248711305964385 / (-13.928203230275537 + Math.pow(0.07179676972449123, i));
  470. zs[i] -= zs[i + 1] * r;
  471. }
  472. ny = points[0];
  473. nd = ny - zs[0];
  474. for (i = 0 , j = 0; i < ln - 1; j += 3) {
  475. y = ny;
  476. d = nd;
  477. i++;
  478. ny = points[i];
  479. nd = ny - zs[i];
  480. result[j] = y;
  481. result[j + 1] = (nd + 2 * d) / 3;
  482. result[j + 2] = (nd * 2 + d) / 3;
  483. }
  484. result[j] = ny;
  485. return result;
  486. },
  487. /**
  488. * Shorthand for {@link #naturalSpline}
  489. */
  490. spline: function(points) {
  491. return this.naturalSpline(points);
  492. },
  493. /**
  494. * @private
  495. * Cardinal spline interpolation.
  496. * Goes from cardinal control points to cubic Bezier control points.
  497. */
  498. cardinalToBezier: function(P1, P2, P3, P4, tension) {
  499. return [
  500. P2,
  501. P2 + (P3 - P1) / 6 * tension,
  502. P3 - (P4 - P2) / 6 * tension,
  503. P3
  504. ];
  505. },
  506. /**
  507. * @private
  508. * @param {Number[]} P An array of n x- or y-coordinates.
  509. * @param {Number} tension
  510. * @return {Float32Array} An array of 3n - 2 Bezier control points.
  511. */
  512. cardinalSpline: function(P, tension) {
  513. var n = P.length,
  514. result = new Float32Array(n * 3 - 2),
  515. i, bezier;
  516. if (tension === undefined) {
  517. tension = 0.5;
  518. }
  519. bezier = this.cardinalToBezier(P[0], P[0], P[1], P[2], tension);
  520. result[0] = bezier[0];
  521. result[1] = bezier[1];
  522. result[2] = bezier[2];
  523. result[3] = bezier[3];
  524. for (i = 0; i < n - 3; i++) {
  525. bezier = this.cardinalToBezier(P[i], P[i + 1], P[i + 2], P[i + 3], tension);
  526. result[4 + i * 3] = bezier[1];
  527. result[4 + i * 3 + 1] = bezier[2];
  528. result[4 + i * 3 + 2] = bezier[3];
  529. }
  530. bezier = this.cardinalToBezier(P[n - 3], P[n - 2], P[n - 1], P[n - 1], tension);
  531. result[4 + i * 3] = bezier[1];
  532. result[4 + i * 3 + 1] = bezier[2];
  533. result[4 + i * 3 + 2] = bezier[3];
  534. return result;
  535. },
  536. /**
  537. * @private
  538. *
  539. * Calculates bezier curve control anchor points for a particular point in a path, with a
  540. * smoothing curve applied. The smoothness of the curve is controlled by the 'value' parameter.
  541. * Note that this algorithm assumes that the line being smoothed is normalized going from left
  542. * to right; it makes special adjustments assuming this orientation.
  543. *
  544. * @param {Number} prevX X coordinate of the previous point in the path
  545. * @param {Number} prevY Y coordinate of the previous point in the path
  546. * @param {Number} curX X coordinate of the current point in the path
  547. * @param {Number} curY Y coordinate of the current point in the path
  548. * @param {Number} nextX X coordinate of the next point in the path
  549. * @param {Number} nextY Y coordinate of the next point in the path
  550. * @param {Number} value A value to control the smoothness of the curve; this is used to
  551. * divide the distance between points, so a value of 2 corresponds to
  552. * half the distance between points (a very smooth line) while higher values
  553. * result in less smooth curves. Defaults to 4.
  554. * @return {Object} Object containing x1, y1, x2, y2 bezier control anchor points; x1 and y1
  555. * are the control point for the curve toward the previous path point, and
  556. * x2 and y2 are the control point for the curve toward the next path point.
  557. */
  558. getAnchors: function(prevX, prevY, curX, curY, nextX, nextY, value) {
  559. var PI = Math.PI,
  560. halfPI = PI / 2,
  561. abs = Math.abs,
  562. sin = Math.sin,
  563. cos = Math.cos,
  564. atan = Math.atan,
  565. control1Length, control2Length, control1Angle, control2Angle, control1X, control1Y, control2X, control2Y, alpha;
  566. value = value || 4;
  567. // Find the length of each control anchor line, by dividing the horizontal distance
  568. // between points by the value parameter.
  569. control1Length = (curX - prevX) / value;
  570. control2Length = (nextX - curX) / value;
  571. // Determine the angle of each control anchor line. If the middle point is a vertical
  572. // turnaround then we force it to a flat horizontal angle to prevent the curve from
  573. // dipping above or below the middle point. Otherwise we use an angle that points
  574. // toward the previous/next target point.
  575. if ((curY >= prevY && curY >= nextY) || (curY <= prevY && curY <= nextY)) {
  576. control1Angle = control2Angle = halfPI;
  577. } else {
  578. control1Angle = atan((curX - prevX) / abs(curY - prevY));
  579. if (prevY < curY) {
  580. control1Angle = PI - control1Angle;
  581. }
  582. control2Angle = atan((nextX - curX) / abs(curY - nextY));
  583. if (nextY < curY) {
  584. control2Angle = PI - control2Angle;
  585. }
  586. }
  587. // Adjust the calculated angles so they point away from each other on the same line
  588. alpha = halfPI - ((control1Angle + control2Angle) % (PI * 2)) / 2;
  589. if (alpha > halfPI) {
  590. alpha -= PI;
  591. }
  592. control1Angle += alpha;
  593. control2Angle += alpha;
  594. // Find the control anchor points from the angles and length
  595. control1X = curX - control1Length * sin(control1Angle);
  596. control1Y = curY + control1Length * cos(control1Angle);
  597. control2X = curX + control2Length * sin(control2Angle);
  598. control2Y = curY + control2Length * cos(control2Angle);
  599. // One last adjustment, make sure that no control anchor point extends vertically past
  600. // its target prev/next point, as that results in curves dipping above or below and
  601. // bending back strangely. If we find this happening we keep the control angle but
  602. // reduce the length of the control line so it stays within bounds.
  603. if ((curY > prevY && control1Y < prevY) || (curY < prevY && control1Y > prevY)) {
  604. control1X += abs(prevY - control1Y) * (control1X - curX) / (control1Y - curY);
  605. control1Y = prevY;
  606. }
  607. if ((curY > nextY && control2Y < nextY) || (curY < nextY && control2Y > nextY)) {
  608. control2X -= abs(nextY - control2Y) * (control2X - curX) / (control2Y - curY);
  609. control2Y = nextY;
  610. }
  611. return {
  612. x1: control1X,
  613. y1: control1Y,
  614. x2: control2X,
  615. y2: control2Y
  616. };
  617. },
  618. /**
  619. * Given coordinates of the points, calculates coordinates of a Bezier curve
  620. * that goes through them.
  621. * @param dataX x-coordinates of the points.
  622. * @param dataY y-coordinates of the points.
  623. * @param value A value to control the smoothness of the curve.
  624. * @return {Object} Object holding two arrays, for x and y coordinates of the curve.
  625. */
  626. smooth: function(dataX, dataY, value) {
  627. var ln = dataX.length,
  628. prevX, prevY, curX, curY, nextX, nextY, x, y,
  629. smoothX = [],
  630. smoothY = [],
  631. i, anchors;
  632. for (i = 0; i < ln - 1; i++) {
  633. prevX = dataX[i];
  634. prevY = dataY[i];
  635. if (i === 0) {
  636. x = prevX;
  637. y = prevY;
  638. smoothX.push(x);
  639. smoothY.push(y);
  640. if (ln === 1) {
  641. break;
  642. }
  643. }
  644. curX = dataX[i + 1];
  645. curY = dataY[i + 1];
  646. nextX = dataX[i + 2];
  647. nextY = dataY[i + 2];
  648. if (!(Ext.isNumber(nextX) && Ext.isNumber(nextY))) {
  649. smoothX.push(x, curX, curX);
  650. smoothY.push(y, curY, curY);
  651. break;
  652. }
  653. anchors = this.getAnchors(prevX, prevY, curX, curY, nextX, nextY, value);
  654. smoothX.push(x, anchors.x1, curX);
  655. smoothY.push(y, anchors.y1, curY);
  656. x = anchors.x2;
  657. y = anchors.y2;
  658. }
  659. return {
  660. smoothX: smoothX,
  661. smoothY: smoothY
  662. };
  663. },
  664. /**
  665. * @method
  666. * @private
  667. * Work around for iOS.
  668. * Nested 3d-transforms seems to prevent the redraw inside it until some event is fired.
  669. */
  670. beginUpdateIOS: Ext.os.is.iOS ? function() {
  671. this.iosUpdateEl = Ext.getBody().createChild({
  672. //<debug>
  673. 'data-sticky': true,
  674. //</debug>
  675. style: 'position: absolute; top: 0px; bottom: 0px; left: 0px; right: 0px; ' + 'background: rgba(0,0,0,0.001); z-index: 100000'
  676. });
  677. } : Ext.emptyFn,
  678. endUpdateIOS: function() {
  679. this.iosUpdateEl = Ext.destroy(this.iosUpdateEl);
  680. }
  681. });
  682. /**
  683. * @class Ext.draw.gradient.Gradient
  684. *
  685. * Creates a gradient.
  686. */
  687. Ext.define('Ext.draw.gradient.Gradient', {
  688. requires: [
  689. 'Ext.draw.Color'
  690. ],
  691. isGradient: true,
  692. config: {
  693. /**
  694. * @cfg {Object[]} stops
  695. * Defines the stops of the gradient.
  696. */
  697. stops: []
  698. },
  699. applyStops: function(newStops) {
  700. var stops = [],
  701. ln = newStops.length,
  702. i, stop, color;
  703. for (i = 0; i < ln; i++) {
  704. stop = newStops[i];
  705. color = stop.color;
  706. if (!(color && color.isColor)) {
  707. color = Ext.util.Color.fly(color || Ext.util.Color.NONE);
  708. }
  709. stops.push({
  710. // eslint-disable-next-line max-len
  711. offset: Math.min(1, Math.max(0, 'offset' in stop ? stop.offset : stop.position || 0)),
  712. color: color.toString()
  713. });
  714. }
  715. stops.sort(function(a, b) {
  716. return a.offset - b.offset;
  717. });
  718. return stops;
  719. },
  720. onClassExtended: function(subClass, member) {
  721. if (!member.alias && member.type) {
  722. member.alias = 'gradient.' + member.type;
  723. }
  724. },
  725. constructor: function(config) {
  726. this.initConfig(config);
  727. },
  728. /**
  729. * @method
  730. * @protected
  731. * Generates the gradient for the given context.
  732. * @param {Ext.draw.engine.SvgContext} ctx The context.
  733. * @param {Object} bbox
  734. * @return {CanvasGradient/Ext.draw.engine.SvgContext.Gradient/Ext.util.Color.NONE}
  735. */
  736. generateGradient: Ext.emptyFn
  737. });
  738. /**
  739. * @class Ext.draw.gradient.GradientDefinition
  740. *
  741. * A global map of all gradient configs.
  742. */
  743. Ext.define('Ext.draw.gradient.GradientDefinition', {
  744. singleton: true,
  745. urlStringRe: /^url\(#([\w-]+)\)$/,
  746. gradients: {},
  747. add: function(gradients) {
  748. var store = this.gradients,
  749. i, n, gradient;
  750. for (i = 0 , n = gradients.length; i < n; i++) {
  751. gradient = gradients[i];
  752. if (Ext.isString(gradient.id)) {
  753. store[gradient.id] = gradient;
  754. }
  755. }
  756. },
  757. get: function(str) {
  758. var store = this.gradients,
  759. match = str.match(this.urlStringRe),
  760. gradient;
  761. if (match && match[1] && (gradient = store[match[1]])) {
  762. return gradient || str;
  763. }
  764. return str;
  765. }
  766. });
  767. /* global Float32Array */
  768. /**
  769. * @private
  770. * @class Ext.draw.sprite.AttributeParser
  771. *
  772. * Parsers used for sprite attributes if they are
  773. * {@link Ext.draw.sprite.AttributeDefinition#normalize normalized} (default) when being
  774. * {@link Ext.draw.sprite.Sprite#setAttributes set}.
  775. *
  776. * Methods of the singleton correpond either to the processor functions themselves or processor
  777. * factories.
  778. */
  779. Ext.define('Ext.draw.sprite.AttributeParser', {
  780. singleton: true,
  781. attributeRe: /^url\(#([a-zA-Z-]+)\)$/,
  782. requires: [
  783. 'Ext.draw.Color',
  784. 'Ext.draw.gradient.GradientDefinition'
  785. ],
  786. 'default': Ext.identityFn,
  787. string: function(n) {
  788. return String(n);
  789. },
  790. number: function(n) {
  791. // Numbers as strings will be converted to numbers,
  792. // null will be converted to 0.
  793. if (Ext.isNumber(+n)) {
  794. return n;
  795. }
  796. },
  797. /**
  798. * Normalize angle to the [-180,180) interval.
  799. * @param n Angle in radians.
  800. * @return {Number/undefined} Normalized angle or undefined.
  801. */
  802. angle: function(n) {
  803. if (Ext.isNumber(n)) {
  804. n %= Math.PI * 2;
  805. if (n < -Math.PI) {
  806. n += Math.PI * 2;
  807. } else if (n >= Math.PI) {
  808. n -= Math.PI * 2;
  809. }
  810. return n;
  811. }
  812. },
  813. data: function(n) {
  814. if (Ext.isArray(n)) {
  815. return n.slice();
  816. } else if (n instanceof Float32Array) {
  817. return new Float32Array(n);
  818. }
  819. },
  820. bool: function(n) {
  821. return !!n;
  822. },
  823. color: function(n) {
  824. if (n && n.isColor) {
  825. return n.toString();
  826. } else if (n && n.isGradient) {
  827. return n;
  828. } else if (!n) {
  829. return Ext.util.Color.NONE;
  830. } else if (Ext.isString(n)) {
  831. if (n.substr(0, 3) === 'url') {
  832. n = Ext.draw.gradient.GradientDefinition.get(n);
  833. if (Ext.isString(n)) {
  834. return n;
  835. }
  836. } else {
  837. return Ext.util.Color.fly(n).toString();
  838. }
  839. }
  840. if (n.type === 'linear') {
  841. return Ext.create('Ext.draw.gradient.Linear', n);
  842. } else if (n.type === 'radial') {
  843. return Ext.create('Ext.draw.gradient.Radial', n);
  844. } else if (n.type === 'pattern') {
  845. return Ext.create('Ext.draw.gradient.Pattern', n);
  846. } else {
  847. return Ext.util.Color.NONE;
  848. }
  849. },
  850. limited: function(low, hi) {
  851. return function(n) {
  852. n = +n;
  853. return Ext.isNumber(n) ? Math.min(Math.max(n, low), hi) : undefined;
  854. };
  855. },
  856. limited01: function(n) {
  857. n = +n;
  858. return Ext.isNumber(n) ? Math.min(Math.max(n, 0), 1) : undefined;
  859. },
  860. /**
  861. * Generates a function that checks if a value matches
  862. * one of the given attributes.
  863. * @return {Function}
  864. */
  865. enums: function() {
  866. var enums = {},
  867. args = Array.prototype.slice.call(arguments, 0),
  868. i, ln;
  869. for (i = 0 , ln = args.length; i < ln; i++) {
  870. enums[args[i]] = true;
  871. }
  872. return function(n) {
  873. return n in enums ? n : undefined;
  874. };
  875. }
  876. });
  877. /**
  878. * @private
  879. * Flyweight object to process the attributes of a sprite.
  880. * A single instance of the AttributeDefinition is created per sprite class.
  881. * See `onClassCreated` and `onClassExtended` callbacks
  882. * of the {@link Ext.draw.sprite.Sprite} for more info.
  883. */
  884. Ext.define('Ext.draw.sprite.AttributeDefinition', {
  885. requires: [
  886. 'Ext.draw.sprite.AttributeParser',
  887. 'Ext.draw.sprite.AnimationParser'
  888. ],
  889. config: {
  890. /**
  891. * @cfg {Object} defaults Defines the default values of attributes.
  892. */
  893. defaults: {
  894. $value: {},
  895. lazy: true
  896. },
  897. /**
  898. * @cfg {Object} aliases Defines the alternative names for attributes.
  899. */
  900. aliases: {},
  901. /**
  902. * @cfg {Object} animationProcessors Defines the process used to animate between attributes.
  903. * One doesn't have to define animation processors for sprite attributes that use
  904. * predefined {@link #processors} from the {@link Ext.draw.sprite.AttributeParser}
  905. * singleton.
  906. * For such attributes matching animation processors from the
  907. * {@link Ext.draw.sprite.AnimationParser} singleton will be used automatically.
  908. * However, if you have a custom processor for an attribute that should support
  909. * animation, you must provide a corresponding animation processor for it here.
  910. * For more information on animation processors please see
  911. * {@link Ext.draw.sprite.AnimationParser} documentation.
  912. */
  913. animationProcessors: {},
  914. /**
  915. * @cfg {Object} processors Defines the preprocessing used on the attributes.
  916. * One can define a custom processor function here or use the name of a predefined
  917. * processor from the {@link Ext.draw.sprite.AttributeParser} singleton.
  918. */
  919. processors: {
  920. // A plus side of lazy initialization is that the 'processors' and 'defaults' will
  921. // only be applied for those sprite classes that are actually instantiated.
  922. $value: {},
  923. lazy: true
  924. },
  925. /**
  926. * @cfg {Object} dirtyTriggers
  927. * @deprecated 6.5.0 Use the {@link #triggers} config instead.
  928. */
  929. dirtyTriggers: {},
  930. /* eslint-disable max-len */
  931. /**
  932. * @cfg {Object} triggers Defines which updaters have to be called when an attribute is changed.
  933. * For example, the config below indicates that the 'size' updater
  934. * of a {@link Ext.draw.sprite.Square square} sprite has to be called
  935. * when the 'size' attribute changes.
  936. *
  937. * triggers: {
  938. * size: 'size' // Use comma-separated values here if multiple updaters have to be called.
  939. * } // Note that the order is _not_ guaranteed.
  940. *
  941. * If any of the updaters to be called (triggered by the {@link Ext.draw.sprite.Sprite#setAttributes} call)
  942. * set attributes themselves and those attributes have triggers defined for them,
  943. * then their updaters will be called after all current updaters finish execution.
  944. *
  945. * The updater functions themselves are defined in the {@link #updaters} config,
  946. * aside from the 'canvas' updater, which doesn't have to be defined and acts as a flag,
  947. * indicating that this attribute should be applied to a Canvas context (or whatever emulates it).
  948. * @since 5.1.0
  949. */
  950. triggers: {},
  951. /**
  952. * @cfg {Object} updaters Defines the postprocessing used by the attribute.
  953. * Inside the updater function 'this' refers to the sprite that the attributes belong to.
  954. * In case of an instancing sprite 'this' will refer to the instancing template.
  955. * The two parameters passed to the updater function are the attributes object
  956. * of the sprite or instance, and the names of attributes that triggered this updater call.
  957. *
  958. * The example below shows how the 'size' updater changes other attributes
  959. * of a {@link Ext.draw.sprite.Square square} sprite sprite when its 'size' attribute changes.
  960. *
  961. * updaters: {
  962. * size: function (attr) {
  963. * var size = attr.size;
  964. * this.setAttributes({ // Changes to these attributes will trigger the 'path' updater.
  965. * x: attr.x - size,
  966. * y: attr.y - size,
  967. * height: 2 * size,
  968. * width: 2 * size
  969. * });
  970. * }
  971. * }
  972. */
  973. updaters: {}
  974. },
  975. /* eslint-enable max-len */
  976. inheritableStatics: {
  977. /**
  978. * @private
  979. * Processor declaration in the form of 'processorFactory(argument1,argument2,...)'.
  980. * E.g.: {@link Ext.draw.sprite.AttributeParser#enums enums},
  981. * {@link Ext.draw.sprite.AttributeParser#limited limited}.
  982. */
  983. processorFactoryRe: /^(\w+)\(([\w\-,]*)\)$/
  984. },
  985. // The sprite class for which AttributeDefinition instance is created.
  986. spriteClass: null,
  987. constructor: function(config) {
  988. var me = this;
  989. me.initConfig(config);
  990. },
  991. applyDefaults: function(defaults, oldDefaults) {
  992. oldDefaults = Ext.apply(oldDefaults || {}, this.normalize(defaults));
  993. return oldDefaults;
  994. },
  995. applyAliases: function(aliases, oldAliases) {
  996. return Ext.apply(oldAliases || {}, aliases);
  997. },
  998. applyProcessors: function(processors, oldProcessors) {
  999. this.getAnimationProcessors();
  1000. // Apply custom animation processors first.
  1001. // eslint-disable-next-line vars-on-top
  1002. var result = oldProcessors || {},
  1003. defaultProcessor = Ext.draw.sprite.AttributeParser,
  1004. processorFactoryRe = this.self.processorFactoryRe,
  1005. animationProcessors = {},
  1006. anyAnimationProcessors, name, match, fn;
  1007. for (name in processors) {
  1008. fn = processors[name];
  1009. if (typeof fn === 'string') {
  1010. match = fn.match(processorFactoryRe);
  1011. if (match) {
  1012. // enums(... , limited(... or something of that nature.
  1013. fn = defaultProcessor[match[1]].apply(defaultProcessor, match[2].split(','));
  1014. } else if (defaultProcessor[fn]) {
  1015. // Names of animation parsers match the names of attribute parsers.
  1016. animationProcessors[name] = fn;
  1017. anyAnimationProcessors = true;
  1018. fn = defaultProcessor[fn];
  1019. }
  1020. }
  1021. //<debug>
  1022. if (!Ext.isFunction(fn)) {
  1023. Ext.raise(this.spriteClass.$className + ": processor '" + name + "' has not been found.");
  1024. }
  1025. //</debug>
  1026. result[name] = fn;
  1027. }
  1028. if (anyAnimationProcessors) {
  1029. this.setAnimationProcessors(animationProcessors);
  1030. }
  1031. return result;
  1032. },
  1033. applyAnimationProcessors: function(animationProcessors, oldAnimationProcessors) {
  1034. var parser = Ext.draw.sprite.AnimationParser,
  1035. name, item;
  1036. if (!oldAnimationProcessors) {
  1037. oldAnimationProcessors = {};
  1038. }
  1039. for (name in animationProcessors) {
  1040. item = animationProcessors[name];
  1041. if (item === 'none') {
  1042. oldAnimationProcessors[name] = null;
  1043. } else if (Ext.isString(item) && !(name in oldAnimationProcessors)) {
  1044. if (item in parser) {
  1045. // The while loop is used to resolve aliases, e.g. `num: 'number'`,
  1046. // where `number` maps to a parser object or is an alias too.
  1047. while (Ext.isString(parser[item])) {
  1048. item = parser[item];
  1049. }
  1050. oldAnimationProcessors[name] = parser[item];
  1051. }
  1052. } else if (Ext.isObject(item)) {
  1053. oldAnimationProcessors[name] = item;
  1054. }
  1055. }
  1056. return oldAnimationProcessors;
  1057. },
  1058. updateDirtyTriggers: function(dirtyTriggers) {
  1059. this.setTriggers(dirtyTriggers);
  1060. },
  1061. applyTriggers: function(triggers, oldTriggers) {
  1062. var name;
  1063. if (!oldTriggers) {
  1064. oldTriggers = {};
  1065. }
  1066. for (name in triggers) {
  1067. oldTriggers[name] = triggers[name].split(',');
  1068. }
  1069. return oldTriggers;
  1070. },
  1071. applyUpdaters: function(updaters, oldUpdaters) {
  1072. return Ext.apply(oldUpdaters || {}, updaters);
  1073. },
  1074. batchedNormalize: function(batchedChanges, keepUnrecognized) {
  1075. if (!batchedChanges) {
  1076. return {};
  1077. }
  1078. // eslint-disable-next-line vars-on-top
  1079. var processors = this.getProcessors(),
  1080. aliases = this.getAliases(),
  1081. translation = batchedChanges.translation || batchedChanges.translate,
  1082. normalized = {},
  1083. i, ln, name, val, rotation, scaling, matrix, subVal, split;
  1084. if ('rotation' in batchedChanges) {
  1085. rotation = batchedChanges.rotation;
  1086. } else {
  1087. rotation = ('rotate' in batchedChanges) ? batchedChanges.rotate : undefined;
  1088. }
  1089. if ('scaling' in batchedChanges) {
  1090. scaling = batchedChanges.scaling;
  1091. } else {
  1092. scaling = ('scale' in batchedChanges) ? batchedChanges.scale : undefined;
  1093. }
  1094. if (typeof scaling !== 'undefined') {
  1095. if (Ext.isNumber(scaling)) {
  1096. normalized.scalingX = scaling;
  1097. normalized.scalingY = scaling;
  1098. } else {
  1099. if ('x' in scaling) {
  1100. normalized.scalingX = scaling.x;
  1101. }
  1102. if ('y' in scaling) {
  1103. normalized.scalingY = scaling.y;
  1104. }
  1105. if ('centerX' in scaling) {
  1106. normalized.scalingCenterX = scaling.centerX;
  1107. }
  1108. if ('centerY' in scaling) {
  1109. normalized.scalingCenterY = scaling.centerY;
  1110. }
  1111. }
  1112. }
  1113. if (typeof rotation !== 'undefined') {
  1114. if (Ext.isNumber(rotation)) {
  1115. rotation = Ext.draw.Draw.rad(rotation);
  1116. normalized.rotationRads = rotation;
  1117. } else {
  1118. if ('rads' in rotation) {
  1119. normalized.rotationRads = rotation.rads;
  1120. } else if ('degrees' in rotation) {
  1121. if (Ext.isArray(rotation.degrees)) {
  1122. normalized.rotationRads = Ext.Array.map(rotation.degrees, function(deg) {
  1123. return Ext.draw.Draw.rad(deg);
  1124. });
  1125. } else {
  1126. normalized.rotationRads = Ext.draw.Draw.rad(rotation.degrees);
  1127. }
  1128. }
  1129. if ('centerX' in rotation) {
  1130. normalized.rotationCenterX = rotation.centerX;
  1131. }
  1132. if ('centerY' in rotation) {
  1133. normalized.rotationCenterY = rotation.centerY;
  1134. }
  1135. }
  1136. }
  1137. if (typeof translation !== 'undefined') {
  1138. if ('x' in translation) {
  1139. normalized.translationX = translation.x;
  1140. }
  1141. if ('y' in translation) {
  1142. normalized.translationY = translation.y;
  1143. }
  1144. }
  1145. if ('matrix' in batchedChanges) {
  1146. matrix = Ext.draw.Matrix.create(batchedChanges.matrix);
  1147. split = matrix.split();
  1148. normalized.matrix = matrix;
  1149. normalized.rotationRads = split.rotation;
  1150. normalized.rotationCenterX = 0;
  1151. normalized.rotationCenterY = 0;
  1152. normalized.scalingX = split.scaleX;
  1153. normalized.scalingY = split.scaleY;
  1154. normalized.scalingCenterX = 0;
  1155. normalized.scalingCenterY = 0;
  1156. normalized.translationX = split.translateX;
  1157. normalized.translationY = split.translateY;
  1158. }
  1159. for (name in batchedChanges) {
  1160. val = batchedChanges[name];
  1161. if (typeof val === 'undefined') {
  1162. continue;
  1163. } else if (Ext.isArray(val)) {
  1164. if (name in aliases) {
  1165. name = aliases[name];
  1166. }
  1167. if (name in processors) {
  1168. normalized[name] = [];
  1169. for (i = 0 , ln = val.length; i < ln; i++) {
  1170. subVal = processors[name].call(this, val[i]);
  1171. if (typeof subVal !== 'undefined') {
  1172. normalized[name][i] = subVal;
  1173. }
  1174. }
  1175. } else if (keepUnrecognized) {
  1176. normalized[name] = val;
  1177. }
  1178. } else {
  1179. if (name in aliases) {
  1180. name = aliases[name];
  1181. }
  1182. if (name in processors) {
  1183. val = processors[name].call(this, val);
  1184. if (typeof val !== 'undefined') {
  1185. normalized[name] = val;
  1186. }
  1187. } else if (keepUnrecognized) {
  1188. normalized[name] = val;
  1189. }
  1190. }
  1191. }
  1192. return normalized;
  1193. },
  1194. /**
  1195. * Normalizes the changes given via their processors before they are applied as attributes.
  1196. *
  1197. * @param {Object} changes The changes given.
  1198. * @param {Boolean} keepUnrecognized If 'true', unknown attributes will be passed through
  1199. * as normalized values.
  1200. * @return {Object} The normalized values.
  1201. */
  1202. normalize: function(changes, keepUnrecognized) {
  1203. if (!changes) {
  1204. return {};
  1205. }
  1206. // eslint-disable-next-line vars-on-top
  1207. var processors = this.getProcessors(),
  1208. aliases = this.getAliases(),
  1209. translation = changes.translation || changes.translate,
  1210. normalized = {},
  1211. name, val, rotation, scaling, matrix, split;
  1212. if ('rotation' in changes) {
  1213. rotation = changes.rotation;
  1214. } else {
  1215. rotation = ('rotate' in changes) ? changes.rotate : undefined;
  1216. }
  1217. if ('scaling' in changes) {
  1218. scaling = changes.scaling;
  1219. } else {
  1220. scaling = ('scale' in changes) ? changes.scale : undefined;
  1221. }
  1222. if (translation) {
  1223. if ('x' in translation) {
  1224. normalized.translationX = translation.x;
  1225. }
  1226. if ('y' in translation) {
  1227. normalized.translationY = translation.y;
  1228. }
  1229. }
  1230. if (typeof scaling !== 'undefined') {
  1231. if (Ext.isNumber(scaling)) {
  1232. normalized.scalingX = scaling;
  1233. normalized.scalingY = scaling;
  1234. } else {
  1235. if ('x' in scaling) {
  1236. normalized.scalingX = scaling.x;
  1237. }
  1238. if ('y' in scaling) {
  1239. normalized.scalingY = scaling.y;
  1240. }
  1241. if ('centerX' in scaling) {
  1242. normalized.scalingCenterX = scaling.centerX;
  1243. }
  1244. if ('centerY' in scaling) {
  1245. normalized.scalingCenterY = scaling.centerY;
  1246. }
  1247. }
  1248. }
  1249. if (typeof rotation !== 'undefined') {
  1250. if (Ext.isNumber(rotation)) {
  1251. rotation = Ext.draw.Draw.rad(rotation);
  1252. normalized.rotationRads = rotation;
  1253. } else {
  1254. if ('rads' in rotation) {
  1255. normalized.rotationRads = rotation.rads;
  1256. } else if ('degrees' in rotation) {
  1257. normalized.rotationRads = Ext.draw.Draw.rad(rotation.degrees);
  1258. }
  1259. if ('centerX' in rotation) {
  1260. normalized.rotationCenterX = rotation.centerX;
  1261. }
  1262. if ('centerY' in rotation) {
  1263. normalized.rotationCenterY = rotation.centerY;
  1264. }
  1265. }
  1266. }
  1267. if ('matrix' in changes) {
  1268. matrix = Ext.draw.Matrix.create(changes.matrix);
  1269. split = matrix.split();
  1270. // This will NOT update the transformation matrix of a sprite
  1271. // with the given elements. It will attempt to extract the
  1272. // individual transformation attributes from the transformation matrix
  1273. // elements provided. Then the extracted attributes will be used by
  1274. // the sprite's 'applyTransformations' method to calculate
  1275. // the transformation matrix of the sprite.
  1276. // It's not possible to recover all the information from the given
  1277. // transformation matrix elements. Shearing and centers of rotation
  1278. // and scaling are not recovered.
  1279. // Ideally, this should work like sprite.transform([elements], true),
  1280. // i.e. update the transformation matrix of a sprite directly,
  1281. // without attempting to update sprite's transformation attributes.
  1282. // But we are not changing the behavior (just yet) for compatibility
  1283. // reasons.
  1284. normalized.matrix = matrix;
  1285. normalized.rotationRads = split.rotation;
  1286. normalized.rotationCenterX = 0;
  1287. normalized.rotationCenterY = 0;
  1288. normalized.scalingX = split.scaleX;
  1289. normalized.scalingY = split.scaleY;
  1290. normalized.scalingCenterX = 0;
  1291. normalized.scalingCenterY = 0;
  1292. normalized.translationX = split.translateX;
  1293. normalized.translationY = split.translateY;
  1294. }
  1295. for (name in changes) {
  1296. val = changes[name];
  1297. if (typeof val === 'undefined') {
  1298. continue;
  1299. }
  1300. if (name in aliases) {
  1301. name = aliases[name];
  1302. }
  1303. if (name in processors) {
  1304. val = processors[name].call(this, val);
  1305. if (typeof val !== 'undefined') {
  1306. normalized[name] = val;
  1307. }
  1308. } else if (keepUnrecognized) {
  1309. normalized[name] = val;
  1310. }
  1311. }
  1312. return normalized;
  1313. },
  1314. setBypassingNormalization: function(attr, modifierStack, changes) {
  1315. return modifierStack.pushDown(attr, changes);
  1316. },
  1317. set: function(attr, modifierStack, changes) {
  1318. changes = this.normalize(changes);
  1319. return this.setBypassingNormalization(attr, modifierStack, changes);
  1320. }
  1321. });
  1322. /**
  1323. * Ext.draw.Matix is a utility class used to calculate
  1324. * [affine transformation](http://en.wikipedia.org/wiki/Affine_transformation) matrix.
  1325. * The matrix class is used to apply transformations to existing
  1326. * {@link Ext.draw.sprite.Sprite sprites} using a number of convenience transform
  1327. * methods.
  1328. *
  1329. * Transformations configured directly on a sprite are processed in the following order:
  1330. * scaling, rotation, and translation. The matrix class offers additional flexibility.
  1331. * Once a sprite is created, you can use the matrix class's transform methods as many
  1332. * times as needed and in any order you choose.
  1333. *
  1334. * To demonstrate, we'll start with a simple {@link Ext.draw.sprite.Rect rect} sprite
  1335. * with the intent of rotating it 180 degrees with the bottom right corner being the
  1336. * center of rotation. To begin, let's look at the initial, untransformed sprite:
  1337. *
  1338. * @example
  1339. * var drawContainer = new Ext.draw.Container({
  1340. * renderTo: Ext.getBody(),
  1341. * width: 380,
  1342. * height: 380,
  1343. * sprites: [{
  1344. * type: 'rect',
  1345. * width: 100,
  1346. * height: 100,
  1347. * fillStyle: 'red'
  1348. * }]
  1349. * });
  1350. *
  1351. * Next, we'll use the {@link #rotate} and {@link #translate} methods from our matrix
  1352. * class to position the rect sprite.
  1353. *
  1354. * @example
  1355. * var drawContainer = new Ext.draw.Container({
  1356. * renderTo: Ext.getBody(),
  1357. * width: 380,
  1358. * height: 380,
  1359. * sprites: [{
  1360. * type: 'rect',
  1361. * width: 100,
  1362. * height: 100,
  1363. * fillStyle: 'red'
  1364. * }]
  1365. * });
  1366. *
  1367. * var main = drawContainer.getSurface();
  1368. * var rect = main.getItems()[0];
  1369. *
  1370. * var m = new Ext.draw.Matrix().translate(100, 100).
  1371. * rotate(Math.PI).
  1372. * translate(-100, - 100);
  1373. *
  1374. * rect.setTransform(m);
  1375. * main.renderFrame();
  1376. *
  1377. * In the previous example we perform the following steps in order to achieve our
  1378. * desired rotated output:
  1379. *
  1380. * - translate the rect to the right and down by 100
  1381. * - rotate by 180 degrees
  1382. * - translate the rect to the right and down by 100
  1383. *
  1384. * **Note:** A couple of things to note at this stage; 1) the rotation center point is
  1385. * the upper left corner of the sprite by default and 2) with transformations, the
  1386. * sprite itself isn't transformed, but rather the entire coordinate plane of the sprite
  1387. * is transformed. The coordinate plane itself is translated by 100 and then rotated
  1388. * 180 degrees. And that is why in the third step we translate the sprite using
  1389. * negative values. Translating by -100 in the third step results in the sprite
  1390. * visually moving to the right and down within the draw container.
  1391. *
  1392. * Fortunately there is a shortcut we can apply using two optional params of the rotate
  1393. * method allowing us to specify the center point of rotation:
  1394. *
  1395. * @example
  1396. * var drawContainer = new Ext.draw.Container({
  1397. * renderTo: Ext.getBody(),
  1398. * width: 380,
  1399. * height: 380,
  1400. * sprites: [{
  1401. * type: 'rect',
  1402. * width: 100,
  1403. * height: 100,
  1404. * fillStyle: 'red'
  1405. * }]
  1406. * });
  1407. *
  1408. * var main = drawContainer.getSurface();
  1409. * var rect = main.getItems()[0];
  1410. *
  1411. * var m = new Ext.draw.Matrix().rotate(Math.PI, 100, 100);
  1412. *
  1413. * rect.setTransform(m);
  1414. * main.renderFrame();
  1415. *
  1416. *
  1417. * This class is compatible with
  1418. * [SVGMatrix](http://www.w3.org/TR/SVG11/coords.html#InterfaceSVGMatrix) except:
  1419. *
  1420. * 1. Ext.draw.Matrix is not read only
  1421. * 2. Using Number as its values rather than floats
  1422. *
  1423. * Using this class helps to reduce the severe numeric
  1424. * [problem with HTML Canvas and SVG transformation](http://stackoverflow.com/questions/8784405/large-numbers-in-html-canvas-translate-result-in-strange-behavior)
  1425. *
  1426. * Additionally, there's no way to get the current transformation matrix
  1427. * [in Canvas](http://stackoverflow.com/questions/7395813/html5-canvas-get-transform-matrix).
  1428. */
  1429. Ext.define('Ext.draw.Matrix', {
  1430. isMatrix: true,
  1431. statics: {
  1432. /**
  1433. * @static
  1434. * Return the affine matrix that transform two points (x0, y0) and (x1, y1) to (x0p, y0p)
  1435. * and (x1p, y1p)
  1436. * @param {Number} x0
  1437. * @param {Number} y0
  1438. * @param {Number} x1
  1439. * @param {Number} y1
  1440. * @param {Number} x0p
  1441. * @param {Number} y0p
  1442. * @param {Number} x1p
  1443. * @param {Number} y1p
  1444. */
  1445. createAffineMatrixFromTwoPair: function(x0, y0, x1, y1, x0p, y0p, x1p, y1p) {
  1446. var dx = x1 - x0,
  1447. dy = y1 - y0,
  1448. dxp = x1p - x0p,
  1449. dyp = y1p - y0p,
  1450. r = 1 / (dx * dx + dy * dy),
  1451. a = dx * dxp + dy * dyp,
  1452. b = dxp * dy - dx * dyp,
  1453. c = -a * x0 - b * y0,
  1454. f = b * x0 - a * y0;
  1455. return new this(a * r, -b * r, b * r, a * r, c * r + x0p, f * r + y0p);
  1456. },
  1457. /**
  1458. * @static
  1459. * Return the affine matrix that transform two points (x0, y0) and (x1, y1) to (x0p, y0p)
  1460. * and (x1p, y1p)
  1461. * @param {Number} x0
  1462. * @param {Number} y0
  1463. * @param {Number} x1
  1464. * @param {Number} y1
  1465. * @param {Number} x0p
  1466. * @param {Number} y0p
  1467. * @param {Number} x1p
  1468. * @param {Number} y1p
  1469. */
  1470. createPanZoomFromTwoPair: function(x0, y0, x1, y1, x0p, y0p, x1p, y1p) {
  1471. if (arguments.length === 2) {
  1472. return this.createPanZoomFromTwoPair.apply(this, x0.concat(y0));
  1473. }
  1474. // eslint-disable-next-line vars-on-top
  1475. var dx = x1 - x0,
  1476. dy = y1 - y0,
  1477. cx = (x0 + x1) * 0.5,
  1478. cy = (y0 + y1) * 0.5,
  1479. dxp = x1p - x0p,
  1480. dyp = y1p - y0p,
  1481. cxp = (x0p + x1p) * 0.5,
  1482. cyp = (y0p + y1p) * 0.5,
  1483. r = dx * dx + dy * dy,
  1484. rp = dxp * dxp + dyp * dyp,
  1485. scale = Math.sqrt(rp / r);
  1486. return new this(scale, 0, 0, scale, cxp - scale * cx, cyp - scale * cy);
  1487. },
  1488. /**
  1489. * @method
  1490. * @static
  1491. * Create a flyweight to wrap the given array.
  1492. * The flyweight will directly refer the object and the elements can be changed
  1493. * by other methods.
  1494. *
  1495. * Do not hold the instance of flyweight matrix.
  1496. *
  1497. * @param {Array} elements
  1498. * @return {Ext.draw.Matrix}
  1499. */
  1500. fly: (function() {
  1501. var flyMatrix = null,
  1502. simplefly = function(elements) {
  1503. flyMatrix.elements = elements;
  1504. return flyMatrix;
  1505. };
  1506. return function(elements) {
  1507. if (!flyMatrix) {
  1508. flyMatrix = new Ext.draw.Matrix();
  1509. }
  1510. flyMatrix.elements = elements;
  1511. Ext.draw.Matrix.fly = simplefly;
  1512. return flyMatrix;
  1513. };
  1514. })(),
  1515. /**
  1516. * @static
  1517. * Create a matrix from `mat`. If `mat` is already a matrix, returns it.
  1518. * @param {Mixed} mat
  1519. * @return {Ext.draw.Matrix}
  1520. */
  1521. create: function(mat) {
  1522. if (mat instanceof this) {
  1523. return mat;
  1524. }
  1525. return new this(mat);
  1526. }
  1527. },
  1528. /**
  1529. * Create an affine transform matrix.
  1530. *
  1531. * @param {Number} xx Coefficient from x to x
  1532. * @param {Number} xy Coefficient from x to y
  1533. * @param {Number} yx Coefficient from y to x
  1534. * @param {Number} yy Coefficient from y to y
  1535. * @param {Number} dx Offset of x
  1536. * @param {Number} dy Offset of y
  1537. */
  1538. constructor: function(xx, xy, yx, yy, dx, dy) {
  1539. if (xx && xx.length === 6) {
  1540. this.elements = xx.slice();
  1541. } else if (xx !== undefined) {
  1542. this.elements = [
  1543. xx,
  1544. xy,
  1545. yx,
  1546. yy,
  1547. dx,
  1548. dy
  1549. ];
  1550. } else {
  1551. this.elements = [
  1552. 1,
  1553. 0,
  1554. 0,
  1555. 1,
  1556. 0,
  1557. 0
  1558. ];
  1559. }
  1560. },
  1561. /**
  1562. * Prepend a matrix onto the current.
  1563. *
  1564. * __Note:__ The given transform will come after the current one.
  1565. *
  1566. * @param {Number} xx Coefficient from x to x.
  1567. * @param {Number} xy Coefficient from x to y.
  1568. * @param {Number} yx Coefficient from y to x.
  1569. * @param {Number} yy Coefficient from y to y.
  1570. * @param {Number} dx Offset of x.
  1571. * @param {Number} dy Offset of y.
  1572. * @return {Ext.draw.Matrix} this
  1573. */
  1574. prepend: function(xx, xy, yx, yy, dx, dy) {
  1575. var elements = this.elements,
  1576. xx0 = elements[0],
  1577. xy0 = elements[1],
  1578. yx0 = elements[2],
  1579. yy0 = elements[3],
  1580. dx0 = elements[4],
  1581. dy0 = elements[5];
  1582. elements[0] = xx * xx0 + yx * xy0;
  1583. elements[1] = xy * xx0 + yy * xy0;
  1584. elements[2] = xx * yx0 + yx * yy0;
  1585. elements[3] = xy * yx0 + yy * yy0;
  1586. elements[4] = xx * dx0 + yx * dy0 + dx;
  1587. elements[5] = xy * dx0 + yy * dy0 + dy;
  1588. return this;
  1589. },
  1590. /**
  1591. * Prepend a matrix onto the current.
  1592. *
  1593. * __Note:__ The given transform will come after the current one.
  1594. * @param {Ext.draw.Matrix} matrix
  1595. * @return {Ext.draw.Matrix} this
  1596. */
  1597. prependMatrix: function(matrix) {
  1598. return this.prepend.apply(this, matrix.elements);
  1599. },
  1600. /**
  1601. * Postpend a matrix onto the current.
  1602. *
  1603. * __Note:__ The given transform will come before the current one.
  1604. *
  1605. * @param {Number} xx Coefficient from x to x.
  1606. * @param {Number} xy Coefficient from x to y.
  1607. * @param {Number} yx Coefficient from y to x.
  1608. * @param {Number} yy Coefficient from y to y.
  1609. * @param {Number} dx Offset of x.
  1610. * @param {Number} dy Offset of y.
  1611. * @return {Ext.draw.Matrix} this
  1612. */
  1613. append: function(xx, xy, yx, yy, dx, dy) {
  1614. var elements = this.elements,
  1615. xx0 = elements[0],
  1616. xy0 = elements[1],
  1617. yx0 = elements[2],
  1618. yy0 = elements[3],
  1619. dx0 = elements[4],
  1620. dy0 = elements[5];
  1621. elements[0] = xx * xx0 + xy * yx0;
  1622. elements[1] = xx * xy0 + xy * yy0;
  1623. elements[2] = yx * xx0 + yy * yx0;
  1624. elements[3] = yx * xy0 + yy * yy0;
  1625. elements[4] = dx * xx0 + dy * yx0 + dx0;
  1626. elements[5] = dx * xy0 + dy * yy0 + dy0;
  1627. return this;
  1628. },
  1629. /**
  1630. * Postpend a matrix onto the current.
  1631. *
  1632. * __Note:__ The given transform will come before the current one.
  1633. *
  1634. * @param {Ext.draw.Matrix} matrix
  1635. * @return {Ext.draw.Matrix} this
  1636. */
  1637. appendMatrix: function(matrix) {
  1638. return this.append.apply(this, matrix.elements);
  1639. },
  1640. /**
  1641. * Set the elements of a Matrix
  1642. * @param {Number} xx
  1643. * @param {Number} xy
  1644. * @param {Number} yx
  1645. * @param {Number} yy
  1646. * @param {Number} dx
  1647. * @param {Number} dy
  1648. * @return {Ext.draw.Matrix} this
  1649. */
  1650. set: function(xx, xy, yx, yy, dx, dy) {
  1651. var elements = this.elements;
  1652. elements[0] = xx;
  1653. elements[1] = xy;
  1654. elements[2] = yx;
  1655. elements[3] = yy;
  1656. elements[4] = dx;
  1657. elements[5] = dy;
  1658. return this;
  1659. },
  1660. /**
  1661. * Return a new matrix represents the opposite transformation of the current one.
  1662. *
  1663. * @param {Ext.draw.Matrix} [target] A target matrix. If present, it will receive
  1664. * the result of inversion to avoid creating a new object.
  1665. *
  1666. * @return {Ext.draw.Matrix}
  1667. */
  1668. inverse: function(target) {
  1669. var elements = this.elements,
  1670. a = elements[0],
  1671. b = elements[1],
  1672. c = elements[2],
  1673. d = elements[3],
  1674. e = elements[4],
  1675. f = elements[5],
  1676. rDim = 1 / (a * d - b * c);
  1677. a *= rDim;
  1678. b *= rDim;
  1679. c *= rDim;
  1680. d *= rDim;
  1681. if (target) {
  1682. target.set(d, -b, -c, a, c * f - d * e, b * e - a * f);
  1683. return target;
  1684. } else {
  1685. return new Ext.draw.Matrix(d, -b, -c, a, c * f - d * e, b * e - a * f);
  1686. }
  1687. },
  1688. /**
  1689. * Translate the matrix.
  1690. *
  1691. * @param {Number} x
  1692. * @param {Number} y
  1693. * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
  1694. * @return {Ext.draw.Matrix} this
  1695. */
  1696. translate: function(x, y, prepend) {
  1697. if (prepend) {
  1698. return this.prepend(1, 0, 0, 1, x, y);
  1699. } else {
  1700. return this.append(1, 0, 0, 1, x, y);
  1701. }
  1702. },
  1703. /**
  1704. * Scale the matrix.
  1705. *
  1706. * @param {Number} sx
  1707. * @param {Number} sy
  1708. * @param {Number} scx
  1709. * @param {Number} scy
  1710. * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
  1711. * @return {Ext.draw.Matrix} this
  1712. */
  1713. scale: function(sx, sy, scx, scy, prepend) {
  1714. var me = this;
  1715. // null or undefined
  1716. if (sy == null) {
  1717. sy = sx;
  1718. }
  1719. if (scx === undefined) {
  1720. scx = 0;
  1721. }
  1722. if (scy === undefined) {
  1723. scy = 0;
  1724. }
  1725. if (prepend) {
  1726. return me.prepend(sx, 0, 0, sy, scx - scx * sx, scy - scy * sy);
  1727. } else {
  1728. return me.append(sx, 0, 0, sy, scx - scx * sx, scy - scy * sy);
  1729. }
  1730. },
  1731. /**
  1732. * Rotate the matrix.
  1733. *
  1734. * @param {Number} angle Radians to rotate
  1735. * @param {Number|null} rcx Center of rotation.
  1736. * @param {Number|null} rcy Center of rotation.
  1737. * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
  1738. * @return {Ext.draw.Matrix} this
  1739. */
  1740. rotate: function(angle, rcx, rcy, prepend) {
  1741. var me = this,
  1742. cos = Math.cos(angle),
  1743. sin = Math.sin(angle);
  1744. rcx = rcx || 0;
  1745. rcy = rcy || 0;
  1746. if (prepend) {
  1747. return me.prepend(cos, sin, -sin, cos, rcx - cos * rcx + rcy * sin, rcy - cos * rcy - rcx * sin);
  1748. } else {
  1749. return me.append(cos, sin, -sin, cos, rcx - cos * rcx + rcy * sin, rcy - cos * rcy - rcx * sin);
  1750. }
  1751. },
  1752. /**
  1753. * Rotate the matrix by the angle of a vector.
  1754. *
  1755. * @param {Number} x
  1756. * @param {Number} y
  1757. * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
  1758. * @return {Ext.draw.Matrix} this
  1759. */
  1760. rotateFromVector: function(x, y, prepend) {
  1761. var me = this,
  1762. d = Math.sqrt(x * x + y * y),
  1763. cos = x / d,
  1764. sin = y / d;
  1765. if (prepend) {
  1766. return me.prepend(cos, sin, -sin, cos, 0, 0);
  1767. } else {
  1768. return me.append(cos, sin, -sin, cos, 0, 0);
  1769. }
  1770. },
  1771. /**
  1772. * Clone this matrix.
  1773. * @return {Ext.draw.Matrix}
  1774. */
  1775. clone: function() {
  1776. return new Ext.draw.Matrix(this.elements);
  1777. },
  1778. /**
  1779. * Horizontally flip the matrix
  1780. * @return {Ext.draw.Matrix} this
  1781. */
  1782. flipX: function() {
  1783. return this.append(-1, 0, 0, 1, 0, 0);
  1784. },
  1785. /**
  1786. * Vertically flip the matrix
  1787. * @return {Ext.draw.Matrix} this
  1788. */
  1789. flipY: function() {
  1790. return this.append(1, 0, 0, -1, 0, 0);
  1791. },
  1792. /**
  1793. * Skew the matrix
  1794. * @param {Number} angle
  1795. * @return {Ext.draw.Matrix} this
  1796. */
  1797. skewX: function(angle) {
  1798. return this.append(1, 0, Math.tan(angle), 1, 0, 0);
  1799. },
  1800. /**
  1801. * Skew the matrix
  1802. * @param {Number} angle
  1803. * @return {Ext.draw.Matrix} this
  1804. */
  1805. skewY: function(angle) {
  1806. return this.append(1, Math.tan(angle), 0, 1, 0, 0);
  1807. },
  1808. /**
  1809. * Shear the matrix along the x-axis.
  1810. * @param factor The horizontal shear factor.
  1811. * @return {Ext.draw.Matrix} this
  1812. */
  1813. shearX: function(factor) {
  1814. return this.append(1, 0, factor, 1, 0, 0);
  1815. },
  1816. /**
  1817. * Shear the matrix along the y-axis.
  1818. * @param factor The vertical shear factor.
  1819. * @return {Ext.draw.Matrix} this
  1820. */
  1821. shearY: function(factor) {
  1822. return this.append(1, factor, 0, 1, 0, 0);
  1823. },
  1824. /**
  1825. * Reset the matrix to identical.
  1826. * @return {Ext.draw.Matrix} this
  1827. */
  1828. reset: function() {
  1829. return this.set(1, 0, 0, 1, 0, 0);
  1830. },
  1831. /* eslint-disable max-len */
  1832. /**
  1833. * @private
  1834. * Split Matrix to `{{devicePixelRatio,c,0},{b,devicePixelRatio,0},{0,0,1}}.{{xx,0,dx},{0,yy,dy},{0,0,1}}`
  1835. * @return {Object} Object with b,c,d=devicePixelRatio,xx,yy,dx,dy
  1836. */
  1837. precisionCompensate: function(devicePixelRatio, comp) {
  1838. /* eslint-enable max-len */
  1839. var elements = this.elements,
  1840. x2x = elements[0],
  1841. x2y = elements[1],
  1842. y2x = elements[2],
  1843. y2y = elements[3],
  1844. newDx = elements[4],
  1845. newDy = elements[5],
  1846. r = x2y * y2x - x2x * y2y;
  1847. comp.b = devicePixelRatio * x2y / x2x;
  1848. comp.c = devicePixelRatio * y2x / y2y;
  1849. comp.d = devicePixelRatio;
  1850. comp.xx = x2x / devicePixelRatio;
  1851. comp.yy = y2y / devicePixelRatio;
  1852. comp.dx = (newDy * x2x * y2x - newDx * x2x * y2y) / r / devicePixelRatio;
  1853. comp.dy = (newDx * x2y * y2y - newDy * x2x * y2y) / r / devicePixelRatio;
  1854. },
  1855. /**
  1856. * @private
  1857. * Split Matrix to `{{1,c,0},{b,d,0},{0,0,1}}.{{xx,0,dx},{0,xx,dy},{0,0,1}}`
  1858. * @return {Object} Object with b,c,d,xx,yy=xx,dx,dy
  1859. */
  1860. precisionCompensateRect: function(devicePixelRatio, comp) {
  1861. var elements = this.elements,
  1862. x2x = elements[0],
  1863. x2y = elements[1],
  1864. y2x = elements[2],
  1865. y2y = elements[3],
  1866. newDx = elements[4],
  1867. newDy = elements[5],
  1868. yxOnXx = y2x / x2x;
  1869. comp.b = devicePixelRatio * x2y / x2x;
  1870. comp.c = devicePixelRatio * yxOnXx;
  1871. comp.d = devicePixelRatio * y2y / x2x;
  1872. comp.xx = x2x / devicePixelRatio;
  1873. comp.yy = x2x / devicePixelRatio;
  1874. comp.dx = (newDy * y2x - newDx * y2y) / (x2y * yxOnXx - y2y) / devicePixelRatio;
  1875. comp.dy = -(newDy * x2x - newDx * x2y) / (x2y * yxOnXx - y2y) / devicePixelRatio;
  1876. },
  1877. /**
  1878. * Transform point returning the x component of the result.
  1879. * @param {Number} x
  1880. * @param {Number} y
  1881. * @return {Number} x component of the result.
  1882. */
  1883. x: function(x, y) {
  1884. var elements = this.elements;
  1885. return x * elements[0] + y * elements[2] + elements[4];
  1886. },
  1887. /**
  1888. * Transform point returning the y component of the result.
  1889. * @param {Number} x
  1890. * @param {Number} y
  1891. * @return {Number} y component of the result.
  1892. */
  1893. y: function(x, y) {
  1894. var elements = this.elements;
  1895. return x * elements[1] + y * elements[3] + elements[5];
  1896. },
  1897. /**
  1898. * @private
  1899. * @param {Number} i
  1900. * @param {Number} j
  1901. * @return {String}
  1902. */
  1903. get: function(i, j) {
  1904. return +this.elements[i + j * 2].toFixed(4);
  1905. },
  1906. /**
  1907. * Transform a point to a new array.
  1908. * @param {Array} point
  1909. * @return {Array}
  1910. */
  1911. transformPoint: function(point) {
  1912. var elements = this.elements,
  1913. x, y;
  1914. if (point.isPoint) {
  1915. x = point.x;
  1916. y = point.y;
  1917. } else {
  1918. x = point[0];
  1919. y = point[1];
  1920. }
  1921. return [
  1922. x * elements[0] + y * elements[2] + elements[4],
  1923. x * elements[1] + y * elements[3] + elements[5]
  1924. ];
  1925. },
  1926. /**
  1927. * @param {Object} bbox Given as `{x: Number, y: Number, width: Number, height: Number}`.
  1928. * @param {Number} [radius]
  1929. * @param {Object} [target] Optional target object to recieve the result.
  1930. * Recommended to use it for better gc.
  1931. *
  1932. * @return {Object} Object with x, y, width and height.
  1933. */
  1934. transformBBox: function(bbox, radius, target) {
  1935. var elements = this.elements,
  1936. l = bbox.x,
  1937. t = bbox.y,
  1938. w0 = bbox.width * 0.5,
  1939. h0 = bbox.height * 0.5,
  1940. xx = elements[0],
  1941. xy = elements[1],
  1942. yx = elements[2],
  1943. yy = elements[3],
  1944. cx = l + w0,
  1945. cy = t + h0,
  1946. w, h, scales;
  1947. if (radius) {
  1948. w0 -= radius;
  1949. h0 -= radius;
  1950. scales = [
  1951. Math.sqrt(elements[0] * elements[0] + elements[2] * elements[2]),
  1952. Math.sqrt(elements[1] * elements[1] + elements[3] * elements[3])
  1953. ];
  1954. w = Math.abs(w0 * xx) + Math.abs(h0 * yx) + Math.abs(scales[0] * radius);
  1955. h = Math.abs(w0 * xy) + Math.abs(h0 * yy) + Math.abs(scales[1] * radius);
  1956. } else {
  1957. w = Math.abs(w0 * xx) + Math.abs(h0 * yx);
  1958. h = Math.abs(w0 * xy) + Math.abs(h0 * yy);
  1959. }
  1960. if (!target) {
  1961. target = {};
  1962. }
  1963. target.x = cx * xx + cy * yx + elements[4] - w;
  1964. target.y = cx * xy + cy * yy + elements[5] - h;
  1965. target.width = w + w;
  1966. target.height = h + h;
  1967. return target;
  1968. },
  1969. /**
  1970. * Transform a list for points.
  1971. *
  1972. * __Note:__ will change the original list but not points inside it.
  1973. * @param {Array} list
  1974. * @return {Array} list
  1975. */
  1976. transformList: function(list) {
  1977. var elements = this.elements,
  1978. xx = elements[0],
  1979. yx = elements[2],
  1980. dx = elements[4],
  1981. xy = elements[1],
  1982. yy = elements[3],
  1983. dy = elements[5],
  1984. ln = list.length,
  1985. p, i;
  1986. for (i = 0; i < ln; i++) {
  1987. p = list[i];
  1988. list[i] = [
  1989. p[0] * xx + p[1] * yx + dx,
  1990. p[0] * xy + p[1] * yy + dy
  1991. ];
  1992. }
  1993. return list;
  1994. },
  1995. /**
  1996. * Determines whether this matrix is an identity matrix (no transform).
  1997. * @return {Boolean}
  1998. */
  1999. isIdentity: function() {
  2000. var elements = this.elements;
  2001. return elements[0] === 1 && elements[1] === 0 && elements[2] === 0 && elements[3] === 1 && elements[4] === 0 && elements[5] === 0;
  2002. },
  2003. /**
  2004. * Determines if this matrix has the same values as another matrix.
  2005. * @param {Ext.draw.Matrix} matrix A maxtrix or array of its elements.
  2006. * @return {Boolean}
  2007. */
  2008. isEqual: function(matrix) {
  2009. var elements = matrix && matrix.isMatrix ? matrix.elements : matrix,
  2010. myElements = this.elements;
  2011. 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];
  2012. },
  2013. /**
  2014. * @deprecated 6.0.1 This method is deprecated.
  2015. * Determines if this matrix has the same values as another matrix.
  2016. * @param {Ext.draw.Matrix} matrix
  2017. * @return {Boolean}
  2018. */
  2019. equals: function(matrix) {
  2020. return this.isEqual(matrix);
  2021. },
  2022. /**
  2023. * Create an array of elements by horizontal order (xx,yx,dx,yx,yy,dy).
  2024. * @return {Array}
  2025. */
  2026. toArray: function() {
  2027. var elements = this.elements;
  2028. return [
  2029. elements[0],
  2030. elements[2],
  2031. elements[4],
  2032. elements[1],
  2033. elements[3],
  2034. elements[5]
  2035. ];
  2036. },
  2037. /**
  2038. * Create an array of elements by vertical order (xx,xy,yx,yy,dx,dy).
  2039. * @return {Array|String}
  2040. */
  2041. toVerticalArray: function() {
  2042. return this.elements.slice();
  2043. },
  2044. /**
  2045. * Get an array of elements.
  2046. * The numbers are rounded to keep only 4 decimals.
  2047. * @return {Array}
  2048. */
  2049. toString: function() {
  2050. var me = this;
  2051. return [
  2052. me.get(0, 0),
  2053. me.get(0, 1),
  2054. me.get(1, 0),
  2055. me.get(1, 1),
  2056. me.get(2, 0),
  2057. me.get(2, 1)
  2058. ].join(',');
  2059. },
  2060. /**
  2061. * Apply the matrix to a drawing context.
  2062. * @param {Object} ctx
  2063. * @return {Ext.draw.Matrix} this
  2064. */
  2065. toContext: function(ctx) {
  2066. ctx.transform.apply(ctx, this.elements);
  2067. return this;
  2068. },
  2069. /**
  2070. * Return a string that can be used as transform attribute in SVG.
  2071. * @return {String}
  2072. */
  2073. toSvg: function() {
  2074. var elements = this.elements;
  2075. // The reason why we cannot use `.join` is the `1e5` form is not accepted in svg.
  2076. 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) + ")";
  2077. },
  2078. /**
  2079. * Get the x scale of the matrix.
  2080. * @return {Number}
  2081. */
  2082. getScaleX: function() {
  2083. var elements = this.elements;
  2084. return Math.sqrt(elements[0] * elements[0] + elements[2] * elements[2]);
  2085. },
  2086. /**
  2087. * Get the y scale of the matrix.
  2088. * @return {Number}
  2089. */
  2090. getScaleY: function() {
  2091. var elements = this.elements;
  2092. return Math.sqrt(elements[1] * elements[1] + elements[3] * elements[3]);
  2093. },
  2094. /**
  2095. * Get x-to-x component of the matrix
  2096. * @return {Number}
  2097. */
  2098. getXX: function() {
  2099. return this.elements[0];
  2100. },
  2101. /**
  2102. * Get x-to-y component of the matrix.
  2103. * @return {Number}
  2104. */
  2105. getXY: function() {
  2106. return this.elements[1];
  2107. },
  2108. /**
  2109. * Get y-to-x component of the matrix.
  2110. * @return {Number}
  2111. */
  2112. getYX: function() {
  2113. return this.elements[2];
  2114. },
  2115. /**
  2116. * Get y-to-y component of the matrix.
  2117. * @return {Number}
  2118. */
  2119. getYY: function() {
  2120. return this.elements[3];
  2121. },
  2122. /**
  2123. * Get offset x component of the matrix.
  2124. * @return {Number}
  2125. */
  2126. getDX: function() {
  2127. return this.elements[4];
  2128. },
  2129. /**
  2130. * Get offset y component of the matrix.
  2131. * @return {Number}
  2132. */
  2133. getDY: function() {
  2134. return this.elements[5];
  2135. },
  2136. /**
  2137. * Splits this transformation matrix into Scale, Rotate, Translate components,
  2138. * assuming it was produced by applying transformations in that order.
  2139. * @return {Object}
  2140. */
  2141. split: function() {
  2142. var el = this.elements,
  2143. xx = el[0],
  2144. xy = el[1],
  2145. yy = el[3],
  2146. out = {
  2147. translateX: el[4],
  2148. translateY: el[5]
  2149. };
  2150. out.rotate = out.rotation = Math.atan2(xy, xx);
  2151. out.scaleX = xx / Math.cos(out.rotate);
  2152. out.scaleY = yy / xx * out.scaleX;
  2153. return out;
  2154. }
  2155. }, function() {
  2156. function registerName(properties, name, i) {
  2157. properties[name] = {
  2158. get: function() {
  2159. return this.elements[i];
  2160. },
  2161. set: function(val) {
  2162. this.elements[i] = val;
  2163. }
  2164. };
  2165. }
  2166. // Compatibility with SVGMatrix.
  2167. if (Object.defineProperties) {
  2168. var properties = {};
  2169. // eslint-disable-line vars-on-top
  2170. /**
  2171. * @property {Number} a Get x-to-x component of the matrix. Avoid using it for performance
  2172. * consideration.
  2173. * Use {@link #getXX} instead.
  2174. */
  2175. registerName(properties, 'a', 0);
  2176. registerName(properties, 'b', 1);
  2177. registerName(properties, 'c', 2);
  2178. registerName(properties, 'd', 3);
  2179. registerName(properties, 'e', 4);
  2180. registerName(properties, 'f', 5);
  2181. Object.defineProperties(this.prototype, properties);
  2182. }
  2183. /**
  2184. * Performs matrix multiplication. This matrix is post-multiplied by another matrix.
  2185. *
  2186. * __Note:__ The given transform will come before the current one.
  2187. *
  2188. * @method
  2189. * @param {Ext.draw.Matrix} matrix
  2190. * @return {Ext.draw.Matrix} this
  2191. */
  2192. this.prototype.multiply = this.prototype.appendMatrix;
  2193. });
  2194. /**
  2195. * @class Ext.draw.modifier.Modifier
  2196. *
  2197. * Each sprite has a stack of modifiers. The resulting attributes of sprite is
  2198. * the content of the stack top. When setting attributes to a sprite,
  2199. * changes will be pushed-down though the stack of modifiers and pop-back the
  2200. * additive changes; When modifier is triggered to change the attribute of a
  2201. * sprite, it will pop-up the changes to the top.
  2202. */
  2203. Ext.define('Ext.draw.modifier.Modifier', {
  2204. isModifier: true,
  2205. mixins: {
  2206. observable: 'Ext.mixin.Observable'
  2207. },
  2208. config: {
  2209. /**
  2210. * @private
  2211. * @cfg {Ext.draw.modifier.Modifier} lower Modifier that receives the push-down changes.
  2212. */
  2213. lower: null,
  2214. /**
  2215. * @private
  2216. * @cfg {Ext.draw.modifier.Modifier} upper Modifier that receives the pop-up changes.
  2217. */
  2218. upper: null,
  2219. /**
  2220. * @cfg {Ext.draw.sprite.Sprite} sprite The sprite to which the modifier belongs.
  2221. */
  2222. sprite: null
  2223. },
  2224. constructor: function(config) {
  2225. this.mixins.observable.constructor.call(this, config);
  2226. },
  2227. updateUpper: function(upper) {
  2228. if (upper) {
  2229. upper.setLower(this);
  2230. }
  2231. },
  2232. updateLower: function(lower) {
  2233. if (lower) {
  2234. lower.setUpper(this);
  2235. }
  2236. },
  2237. /**
  2238. * @private
  2239. * Validate attribute set before use.
  2240. *
  2241. * @param {Object} attr The attribute to be validated. Note that it may be already initialized,
  2242. * so do not override properties that have already been used.
  2243. */
  2244. prepareAttributes: function(attr) {
  2245. if (this._lower) {
  2246. this._lower.prepareAttributes(attr);
  2247. }
  2248. },
  2249. /**
  2250. * @private
  2251. * Invoked when changes need to be popped up to the top.
  2252. * @param {Object} attr The source attributes.
  2253. * @param {Object} changes The changes to be popped up.
  2254. */
  2255. popUp: function(attr, changes) {
  2256. if (this._upper) {
  2257. this._upper.popUp(attr, changes);
  2258. } else {
  2259. Ext.apply(attr, changes);
  2260. }
  2261. },
  2262. /**
  2263. * @private
  2264. *
  2265. * This method will filter out the properties from the `changes` object, if they
  2266. * have the same values as in the `attr` object (sprite's attributes).
  2267. *
  2268. * If the `receiver` object is provided, the attributes with the new values will be
  2269. * copied from the `changes` object to the `receiver` object, and the `changes`
  2270. * object will be left unchanged.
  2271. *
  2272. * The method returns the `receiver` object, if it was provided, or the `changes`
  2273. * object otherwise.
  2274. *
  2275. * The method also handles a special case when a sprite attribute that is meant to be
  2276. * animated was set to a certain value (e.g. 5), that is different from the original
  2277. * value (e.g. 3) of the attribute, and immediately set to another value again, that
  2278. * is the same as the original value (3). In this case, the attribute's current
  2279. * value is still the original value, because the attribute hasn't started animating
  2280. * yet, so a comparison against the current value is not appropriate, and the target
  2281. * value (value at the end of animation, 5) should be used for comparison instead, so
  2282. * that 3 won't be filtered out.
  2283. */
  2284. filterChanges: function(attr, changes, receiver) {
  2285. var targets = attr.targets,
  2286. name, value;
  2287. if (receiver) {
  2288. for (name in changes) {
  2289. value = changes[name];
  2290. if (value !== attr[name] || (targets && value !== targets[name])) {
  2291. receiver[name] = value;
  2292. }
  2293. }
  2294. } else {
  2295. for (name in changes) {
  2296. value = changes[name];
  2297. if (value === attr[name] && (!targets || value === targets[name])) {
  2298. delete changes[name];
  2299. }
  2300. }
  2301. }
  2302. return receiver || changes;
  2303. },
  2304. /**
  2305. * @private
  2306. * Invoked when changes need to be pushed down to the sprite.
  2307. * @param {Object} attr The source attributes.
  2308. * @param {Object} changes The changes to make. This object might be changed unexpectedly
  2309. * inside the method.
  2310. * @return {Mixed}
  2311. */
  2312. pushDown: function(attr, changes) {
  2313. return this._lower ? this._lower.pushDown(attr, changes) : this.filterChanges(attr, changes);
  2314. }
  2315. });
  2316. /**
  2317. * @class Ext.draw.modifier.Target
  2318. * @extends Ext.draw.modifier.Modifier
  2319. *
  2320. * This is the destination (top) modifier that has to be put at
  2321. * the top of the modifier stack.
  2322. *
  2323. * The Target modifier figures out which updaters have to be called
  2324. * for the changed set of attributes and makes the sprite and its instances (if any)
  2325. * call them.
  2326. */
  2327. Ext.define('Ext.draw.modifier.Target', {
  2328. requires: [
  2329. 'Ext.draw.Matrix'
  2330. ],
  2331. extend: 'Ext.draw.modifier.Modifier',
  2332. alias: 'modifier.target',
  2333. statics: {
  2334. /**
  2335. * @private
  2336. */
  2337. uniqueId: 0
  2338. },
  2339. prepareAttributes: function(attr) {
  2340. if (this._lower) {
  2341. this._lower.prepareAttributes(attr);
  2342. }
  2343. attr.attributeId = 'attribute-' + Ext.draw.modifier.Target.uniqueId++;
  2344. if (!attr.hasOwnProperty('canvasAttributes')) {
  2345. attr.bbox = {
  2346. plain: {
  2347. dirty: true
  2348. },
  2349. transform: {
  2350. dirty: true
  2351. }
  2352. };
  2353. attr.dirty = true;
  2354. /*
  2355. Maps updaters that have to be called to the attributes that triggered the update.
  2356. It is basically a reversed `triggers` map (see Ext.draw.sprite.AttributeDefinition),
  2357. but only for those attributes that have changed.
  2358. Pending updaters are called by the Ext.draw.sprite.Sprite.callUpdaters method.
  2359. The 'canvas' updater is a special kind of updater that is not actually a function
  2360. but a flag indicating that the attribute should be applied directly to a canvas
  2361. context.
  2362. */
  2363. attr.pendingUpdaters = {};
  2364. /*
  2365. Holds the attributes that triggered the canvas update (attr.pendingUpdaters.canvas).
  2366. Canvas attributes are applied directly to a canvas context
  2367. by the sprite.useAttributes method.
  2368. */
  2369. attr.canvasAttributes = {};
  2370. attr.matrix = new Ext.draw.Matrix();
  2371. attr.inverseMatrix = new Ext.draw.Matrix();
  2372. }
  2373. },
  2374. /**
  2375. * @private
  2376. * Applies changes to sprite/instance attributes and determines which updaters
  2377. * have to be called as a result of attributes change.
  2378. * @param {Object} attr The source attributes.
  2379. * @param {Object} changes The modifier changes.
  2380. */
  2381. applyChanges: function(attr, changes) {
  2382. Ext.apply(attr, changes);
  2383. // eslint-disable-next-line vars-on-top
  2384. var sprite = this.getSprite(),
  2385. pendingUpdaters = attr.pendingUpdaters,
  2386. triggers = sprite.self.def.getTriggers(),
  2387. updaters, instances, instance, name, hasChanges, canvasAttributes, i, j, ln;
  2388. for (name in changes) {
  2389. hasChanges = true;
  2390. if ((updaters = triggers[name])) {
  2391. sprite.scheduleUpdaters(attr, updaters, [
  2392. name
  2393. ]);
  2394. }
  2395. if (attr.template && changes.removeFromInstance && changes.removeFromInstance[name]) {
  2396. delete attr[name];
  2397. }
  2398. }
  2399. if (!hasChanges) {
  2400. return;
  2401. }
  2402. // This can prevent sub objects to set duplicated attributes to context.
  2403. if (pendingUpdaters.canvas) {
  2404. canvasAttributes = pendingUpdaters.canvas;
  2405. delete pendingUpdaters.canvas;
  2406. for (i = 0 , ln = canvasAttributes.length; i < ln; i++) {
  2407. name = canvasAttributes[i];
  2408. attr.canvasAttributes[name] = attr[name];
  2409. }
  2410. }
  2411. // If the attributes of an instancing sprite template are being modified here,
  2412. // then spread the pending updaters to the instances (template's children).
  2413. if (attr.hasOwnProperty('children')) {
  2414. instances = attr.children;
  2415. for (i = 0 , ln = instances.length; i < ln; i++) {
  2416. instance = instances[i];
  2417. Ext.apply(instance.pendingUpdaters, pendingUpdaters);
  2418. if (canvasAttributes) {
  2419. for (j = 0; j < canvasAttributes.length; j++) {
  2420. name = canvasAttributes[j];
  2421. instance.canvasAttributes[name] = instance[name];
  2422. }
  2423. }
  2424. sprite.callUpdaters(instance);
  2425. }
  2426. }
  2427. sprite.setDirty(true);
  2428. sprite.callUpdaters(attr);
  2429. },
  2430. popUp: function(attr, changes) {
  2431. this.applyChanges(attr, changes);
  2432. },
  2433. pushDown: function(attr, changes) {
  2434. // Modifier chain looks like this:
  2435. // sprite.modifiers.target <---> postFx <---> sprite.modifiers.animation <---> preFx
  2436. // There can be any number of postFx and preFx modifiers, the difference between them
  2437. // is that:
  2438. // `preFx` modifier changes are animated.
  2439. // `postFx` modifier changes are not.
  2440. // preFx modifiers include Highlight (Draw) and Callout (Charts).
  2441. // There are no postFx modifiers at the moment.
  2442. if (this._lower) {
  2443. // Without any postFx modifiers, `lower` is going to be Animation.
  2444. changes = this._lower.pushDown(attr, changes);
  2445. }
  2446. this.applyChanges(attr, changes);
  2447. return changes;
  2448. }
  2449. });
  2450. /**
  2451. * @class Ext.draw.TimingFunctions
  2452. * @singleton
  2453. *
  2454. * Singleton that provides easing functions for use in sprite animations.
  2455. */
  2456. Ext.define('Ext.draw.TimingFunctions', function() {
  2457. var pow = Math.pow,
  2458. sin = Math.sin,
  2459. cos = Math.cos,
  2460. sqrt = Math.sqrt,
  2461. pi = Math.PI,
  2462. poly = [
  2463. 'quad',
  2464. 'cube',
  2465. 'quart',
  2466. 'quint'
  2467. ],
  2468. easings = {
  2469. pow: function(p, x) {
  2470. return pow(p, x || 6);
  2471. },
  2472. expo: function(p) {
  2473. return pow(2, 8 * (p - 1));
  2474. },
  2475. circ: function(p) {
  2476. return 1 - sqrt(1 - p * p);
  2477. },
  2478. sine: function(p) {
  2479. return 1 - sin((1 - p) * pi / 2);
  2480. },
  2481. back: function(p, n) {
  2482. n = n || 1.616;
  2483. return p * p * ((n + 1) * p - n);
  2484. },
  2485. bounce: function(p) {
  2486. var a, b;
  2487. // eslint-disable-next-line no-constant-condition
  2488. for (a = 0 , b = 1; 1; a += b , b /= 2) {
  2489. if (p >= (7 - 4 * a) / 11) {
  2490. return b * b - pow((11 - 6 * a - 11 * p) / 4, 2);
  2491. }
  2492. }
  2493. },
  2494. elastic: function(p, x) {
  2495. return pow(2, 10 * --p) * cos(20 * p * pi * (x || 1) / 3);
  2496. }
  2497. },
  2498. easingsMap = {},
  2499. name, len, i;
  2500. // Create polynomial easing equations.
  2501. function createPoly(times) {
  2502. return function(p) {
  2503. return pow(p, times);
  2504. };
  2505. }
  2506. function addEasing(name, easing) {
  2507. easingsMap[name + 'In'] = function(pos) {
  2508. return easing(pos);
  2509. };
  2510. easingsMap[name + 'Out'] = function(pos) {
  2511. return 1 - easing(1 - pos);
  2512. };
  2513. easingsMap[name + 'InOut'] = function(pos) {
  2514. return (pos <= 0.5) ? easing(2 * pos) / 2 : (2 - easing(2 * (1 - pos))) / 2;
  2515. };
  2516. }
  2517. for (i = 0 , len = poly.length; i < len; ++i) {
  2518. easings[poly[i]] = createPoly(i + 2);
  2519. }
  2520. for (name in easings) {
  2521. addEasing(name, easings[name]);
  2522. }
  2523. // Add linear interpolator.
  2524. easingsMap.linear = Ext.identityFn;
  2525. // Add aliases for quad easings.
  2526. easingsMap.easeIn = easingsMap.quadIn;
  2527. easingsMap.easeOut = easingsMap.quadOut;
  2528. easingsMap.easeInOut = easingsMap.quadInOut;
  2529. return {
  2530. singleton: true,
  2531. easingMap: easingsMap
  2532. };
  2533. }, function(Cls) {
  2534. Ext.apply(Cls, Cls.easingMap);
  2535. });
  2536. /**
  2537. * @class Ext.draw.Animator
  2538. *
  2539. * Singleton class that manages the animation pool.
  2540. */
  2541. Ext.define('Ext.draw.Animator', {
  2542. uses: [
  2543. 'Ext.draw.Draw'
  2544. ],
  2545. singleton: true,
  2546. frameCallbacks: {},
  2547. frameCallbackId: 0,
  2548. scheduled: 0,
  2549. frameStartTimeOffset: Ext.now(),
  2550. animations: [],
  2551. running: false,
  2552. /**
  2553. * Cross platform `animationTime` implementation.
  2554. * @return {Number}
  2555. */
  2556. animationTime: function() {
  2557. return Ext.AnimationQueue.frameStartTime - this.frameStartTimeOffset;
  2558. },
  2559. /**
  2560. * Adds an animated object to the animation pool.
  2561. *
  2562. * @param {Object} animation The animation descriptor to add to the pool.
  2563. */
  2564. add: function(animation) {
  2565. var me = this;
  2566. if (!me.contains(animation)) {
  2567. me.animations.push(animation);
  2568. me.ignite();
  2569. if ('fireEvent' in animation) {
  2570. animation.fireEvent('animationstart', animation);
  2571. }
  2572. }
  2573. },
  2574. /**
  2575. * Removes an animation from the pool.
  2576. * TODO: This is broken when called within `step` method.
  2577. * @param {Object} animation The animation to remove from the pool.
  2578. */
  2579. remove: function(animation) {
  2580. var me = this,
  2581. animations = me.animations,
  2582. i = 0,
  2583. l = animations.length;
  2584. for (; i < l; ++i) {
  2585. if (animations[i] === animation) {
  2586. animations.splice(i, 1);
  2587. if ('fireEvent' in animation) {
  2588. animation.fireEvent('animationend', animation);
  2589. }
  2590. return;
  2591. }
  2592. }
  2593. },
  2594. /**
  2595. * Returns `true` or `false` whether it contains the given animation or not.
  2596. *
  2597. * @param {Object} animation The animation to check for.
  2598. * @return {Boolean}
  2599. */
  2600. contains: function(animation) {
  2601. return Ext.Array.indexOf(this.animations, animation) > -1;
  2602. },
  2603. /**
  2604. * Returns `true` or `false` whether the pool is empty or not.
  2605. * @return {Boolean}
  2606. */
  2607. empty: function() {
  2608. return this.animations.length === 0;
  2609. },
  2610. idle: function() {
  2611. return this.scheduled === 0 && this.animations.length === 0;
  2612. },
  2613. /**
  2614. * Given a frame time it will filter out finished animations from the pool.
  2615. *
  2616. * @param {Number} frameTime The frame's start time, in milliseconds.
  2617. */
  2618. step: function(frameTime) {
  2619. var me = this,
  2620. animations = me.animations,
  2621. animation,
  2622. i = 0,
  2623. ln = animations.length;
  2624. for (; i < ln; i++) {
  2625. animation = animations[i];
  2626. animation.step(frameTime);
  2627. if (!animation.animating) {
  2628. animations.splice(i, 1);
  2629. i--;
  2630. ln--;
  2631. if (animation.fireEvent) {
  2632. animation.fireEvent('animationend', animation);
  2633. }
  2634. }
  2635. }
  2636. },
  2637. /**
  2638. * Register a one-time callback that will be called at the next frame.
  2639. * @param {Function/String} callback
  2640. * @param {Object} scope
  2641. * @return {String} The ID of the scheduled callback.
  2642. */
  2643. schedule: function(callback, scope) {
  2644. var id = 'frameCallback' + (this.frameCallbackId++);
  2645. scope = scope || this;
  2646. if (Ext.isString(callback)) {
  2647. callback = scope[callback];
  2648. }
  2649. Ext.draw.Animator.frameCallbacks[id] = {
  2650. fn: callback,
  2651. scope: scope,
  2652. once: true
  2653. };
  2654. this.scheduled++;
  2655. Ext.draw.Animator.ignite();
  2656. return id;
  2657. },
  2658. /**
  2659. * Register a one-time callback that will be called at the next frame,
  2660. * if that callback (with a matching function and scope) isn't already scheduled.
  2661. * @param {Function/String} callback
  2662. * @param {Object} scope
  2663. * @return {String/null} The ID of the scheduled callback or null, if that callback
  2664. * has already been scheduled.
  2665. */
  2666. scheduleIf: function(callback, scope) {
  2667. var frameCallbacks = Ext.draw.Animator.frameCallbacks,
  2668. cb, id;
  2669. scope = scope || this;
  2670. if (Ext.isString(callback)) {
  2671. callback = scope[callback];
  2672. }
  2673. for (id in frameCallbacks) {
  2674. cb = frameCallbacks[id];
  2675. if (cb.once && cb.fn === callback && cb.scope === scope) {
  2676. return null;
  2677. }
  2678. }
  2679. return this.schedule(callback, scope);
  2680. },
  2681. /**
  2682. * Cancel a registered one-time callback
  2683. * @param {String} id
  2684. */
  2685. cancel: function(id) {
  2686. if (Ext.draw.Animator.frameCallbacks[id] && Ext.draw.Animator.frameCallbacks[id].once) {
  2687. this.scheduled = Math.max(--this.scheduled, 0);
  2688. delete Ext.draw.Animator.frameCallbacks[id];
  2689. Ext.draw.Draw.endUpdateIOS();
  2690. }
  2691. if (this.idle()) {
  2692. this.extinguish();
  2693. }
  2694. },
  2695. clear: function() {
  2696. this.animations.length = 0;
  2697. Ext.draw.Animator.frameCallbacks = {};
  2698. this.extinguish();
  2699. },
  2700. /**
  2701. * Register a recursive callback that will be called at every frame.
  2702. *
  2703. * @param {Function} callback
  2704. * @param {Object} scope
  2705. * @return {String}
  2706. */
  2707. addFrameCallback: function(callback, scope) {
  2708. var id = 'frameCallback' + (this.frameCallbackId++);
  2709. scope = scope || this;
  2710. if (Ext.isString(callback)) {
  2711. callback = scope[callback];
  2712. }
  2713. Ext.draw.Animator.frameCallbacks[id] = {
  2714. fn: callback,
  2715. scope: scope
  2716. };
  2717. return id;
  2718. },
  2719. /**
  2720. * Unregister a recursive callback.
  2721. * @param {String} id
  2722. */
  2723. removeFrameCallback: function(id) {
  2724. delete Ext.draw.Animator.frameCallbacks[id];
  2725. if (this.idle()) {
  2726. this.extinguish();
  2727. }
  2728. },
  2729. /**
  2730. * @private
  2731. */
  2732. fireFrameCallbacks: function() {
  2733. var callbacks = this.frameCallbacks,
  2734. id, fn, cb;
  2735. for (id in callbacks) {
  2736. cb = callbacks[id];
  2737. fn = cb.fn;
  2738. if (Ext.isString(fn)) {
  2739. fn = cb.scope[fn];
  2740. }
  2741. fn.call(cb.scope);
  2742. if (callbacks[id] && cb.once) {
  2743. this.scheduled = Math.max(--this.scheduled, 0);
  2744. delete callbacks[id];
  2745. }
  2746. }
  2747. },
  2748. handleFrame: function() {
  2749. var me = this;
  2750. me.step(me.animationTime());
  2751. me.fireFrameCallbacks();
  2752. if (me.idle()) {
  2753. me.extinguish();
  2754. }
  2755. },
  2756. ignite: function() {
  2757. if (!this.running) {
  2758. this.running = true;
  2759. Ext.AnimationQueue.start(this.handleFrame, this);
  2760. Ext.draw.Draw.beginUpdateIOS();
  2761. }
  2762. },
  2763. extinguish: function() {
  2764. this.running = false;
  2765. Ext.AnimationQueue.stop(this.handleFrame, this);
  2766. Ext.draw.Draw.endUpdateIOS();
  2767. }
  2768. });
  2769. /**
  2770. * The Animation modifier.
  2771. *
  2772. * Sencha Charts allow users to use transitional animation on sprites. Simply set the duration
  2773. * and easing in the animation modifier, then all the changes to the sprites will be animated.
  2774. *
  2775. * @example
  2776. * var drawCt = Ext.create({
  2777. * xtype: 'draw',
  2778. * renderTo: document.body,
  2779. * width: 400,
  2780. * height: 400,
  2781. * sprites: [{
  2782. * type: 'rect',
  2783. * x: 50,
  2784. * y: 50,
  2785. * width: 100,
  2786. * height: 100,
  2787. * fillStyle: '#1F6D91'
  2788. * }]
  2789. * });
  2790. *
  2791. * var rect = drawCt.getSurface().getItems()[0];
  2792. *
  2793. * rect.setAnimation({
  2794. * duration: 1000,
  2795. * easing: 'elasticOut'
  2796. * });
  2797. *
  2798. * Ext.defer(function () {
  2799. * rect.setAttributes({
  2800. * width: 250
  2801. * });
  2802. * }, 500);
  2803. *
  2804. * Also, you can use different durations and easing functions on different attributes by using
  2805. * {@link #customDurations} and {@link #customEasings}.
  2806. *
  2807. * By default, an animation modifier will be created during the initialization of a sprite.
  2808. * You can get the animation modifier of a sprite via its
  2809. * {@link Ext.draw.sprite.Sprite#method-getAnimation getAnimation} method.
  2810. */
  2811. Ext.define('Ext.draw.modifier.Animation', {
  2812. extend: 'Ext.draw.modifier.Modifier',
  2813. alias: 'modifier.animation',
  2814. requires: [
  2815. 'Ext.draw.TimingFunctions',
  2816. 'Ext.draw.Animator'
  2817. ],
  2818. config: {
  2819. /**
  2820. * @cfg {Function} easing
  2821. * Default easing function.
  2822. */
  2823. easing: Ext.identityFn,
  2824. /**
  2825. * @cfg {Number} duration
  2826. * Default duration time (ms).
  2827. */
  2828. duration: 0,
  2829. /**
  2830. * @cfg {Object} customEasings Overrides the default easing function for defined attributes.
  2831. * E.g.:
  2832. *
  2833. * // Assuming the sprite the modifier is applied to is a 'circle'.
  2834. * customEasings: {
  2835. * r: 'easeOut',
  2836. * 'fillStyle,strokeStyle': 'linear',
  2837. * 'cx,cy': function (p, n) {
  2838. * p = 1 - p;
  2839. * n = n || 1.616;
  2840. * return 1 - p * p * ((n + 1) * p - n);
  2841. * }
  2842. * }
  2843. */
  2844. customEasings: {},
  2845. /**
  2846. * @cfg {Object} customDurations Overrides the default duration for defined attributes.
  2847. * E.g.:
  2848. *
  2849. * // Assuming the sprite the modifier is applied to is a 'circle'.
  2850. * customDurations: {
  2851. * r: 1000,
  2852. * 'fillStyle,strokeStyle': 2000,
  2853. * 'cx,cy': 1000
  2854. * }
  2855. */
  2856. customDurations: {}
  2857. },
  2858. constructor: function(config) {
  2859. var me = this;
  2860. me.anyAnimation = me.anySpecialAnimations = false;
  2861. me.animating = 0;
  2862. me.animatingPool = [];
  2863. me.callParent([
  2864. config
  2865. ]);
  2866. },
  2867. prepareAttributes: function(attr) {
  2868. if (!attr.hasOwnProperty('timers')) {
  2869. attr.animating = false;
  2870. attr.timers = {};
  2871. // The 'targets' object is used to hold the target values for the
  2872. // attributes while they are being animated from source to target values.
  2873. // The 'targets' is pushed down to the lower level modifiers,
  2874. // instead of the actual attr object, to hide the fact that the
  2875. // attributes are being animated.
  2876. attr.targets = Ext.Object.chain(attr);
  2877. attr.targets.prototype = attr;
  2878. }
  2879. if (this._lower) {
  2880. this._lower.prepareAttributes(attr.targets);
  2881. }
  2882. },
  2883. updateSprite: function(sprite) {
  2884. this.setConfig(sprite.config.animation);
  2885. },
  2886. updateDuration: function(duration) {
  2887. this.anyAnimation = duration > 0;
  2888. },
  2889. applyEasing: function(easing) {
  2890. if (typeof easing === 'string') {
  2891. easing = Ext.draw.TimingFunctions.easingMap[easing];
  2892. }
  2893. return easing;
  2894. },
  2895. applyCustomEasings: function(newEasings, oldEasings) {
  2896. var any, key, attrs, easing, i, ln;
  2897. oldEasings = oldEasings || {};
  2898. for (key in newEasings) {
  2899. any = true;
  2900. easing = newEasings[key];
  2901. attrs = key.split(',');
  2902. if (typeof easing === 'string') {
  2903. easing = Ext.draw.TimingFunctions.easingMap[easing];
  2904. }
  2905. for (i = 0 , ln = attrs.length; i < ln; i++) {
  2906. oldEasings[attrs[i]] = easing;
  2907. }
  2908. }
  2909. if (any) {
  2910. this.anySpecialAnimations = any;
  2911. }
  2912. return oldEasings;
  2913. },
  2914. /**
  2915. * Set special easings on the given attributes. E.g.:
  2916. *
  2917. * circleSprite.getAnimation().setEasingOn('r', 'elasticIn');
  2918. *
  2919. * @param {String/Array} attrs The source attribute(s).
  2920. * @param {String} easing The special easings.
  2921. */
  2922. setEasingOn: function(attrs, easing) {
  2923. var customEasings = {},
  2924. i, ln;
  2925. attrs = Ext.Array.from(attrs).slice();
  2926. for (i = 0 , ln = attrs.length; i < ln; i++) {
  2927. customEasings[attrs[i]] = easing;
  2928. }
  2929. this.setCustomEasings(customEasings);
  2930. },
  2931. /**
  2932. * Remove special easings on the given attributes.
  2933. * @param {String/Array} attrs The source attribute(s).
  2934. */
  2935. clearEasingOn: function(attrs) {
  2936. var i, ln;
  2937. attrs = Ext.Array.from(attrs, true);
  2938. for (i = 0 , ln = attrs.length; i < ln; i++) {
  2939. delete this._customEasings[attrs[i]];
  2940. }
  2941. },
  2942. applyCustomDurations: function(newDurations, oldDurations) {
  2943. var any, key, duration, attrs, i, ln;
  2944. oldDurations = oldDurations || {};
  2945. for (key in newDurations) {
  2946. any = true;
  2947. duration = newDurations[key];
  2948. attrs = key.split(',');
  2949. for (i = 0 , ln = attrs.length; i < ln; i++) {
  2950. oldDurations[attrs[i]] = duration;
  2951. }
  2952. }
  2953. if (any) {
  2954. this.anySpecialAnimations = any;
  2955. }
  2956. return oldDurations;
  2957. },
  2958. /**
  2959. * Set special duration on the given attributes. E.g.:
  2960. *
  2961. * rectSprite.getAnimation().setDurationOn('height', 2000);
  2962. *
  2963. * @param {String/Array} attrs The source attributes.
  2964. * @param {Number} duration The special duration.
  2965. */
  2966. setDurationOn: function(attrs, duration) {
  2967. var customDurations = {},
  2968. i, ln;
  2969. attrs = Ext.Array.from(attrs).slice();
  2970. for (i = 0 , ln = attrs.length; i < ln; i++) {
  2971. customDurations[attrs[i]] = duration;
  2972. }
  2973. this.setCustomDurations(customDurations);
  2974. },
  2975. /**
  2976. * Remove special easings on the given attributes.
  2977. * @param {Object} attrs The source attributes.
  2978. */
  2979. clearDurationOn: function(attrs) {
  2980. var i, ln;
  2981. attrs = Ext.Array.from(attrs, true);
  2982. for (i = 0 , ln = attrs.length; i < ln; i++) {
  2983. delete this._customDurations[attrs[i]];
  2984. }
  2985. },
  2986. /**
  2987. * @private
  2988. * Initializes Animator for the animation.
  2989. * @param {Object} attr The source attributes.
  2990. * @param {Boolean} animating The animating flag.
  2991. */
  2992. setAnimating: function(attr, animating) {
  2993. var me = this,
  2994. pool = me.animatingPool,
  2995. i;
  2996. if (attr.animating !== animating) {
  2997. attr.animating = animating;
  2998. if (animating) {
  2999. pool.push(attr);
  3000. if (me.animating === 0) {
  3001. Ext.draw.Animator.add(me);
  3002. }
  3003. me.animating++;
  3004. } else {
  3005. for (i = pool.length; i--; ) {
  3006. if (pool[i] === attr) {
  3007. pool.splice(i, 1);
  3008. }
  3009. }
  3010. me.animating = pool.length;
  3011. }
  3012. }
  3013. },
  3014. /**
  3015. * @private
  3016. * Set the attr with given easing and duration.
  3017. * @param {Object} attr The attributes collection.
  3018. * @param {Object} changes The changes that popped up from lower modifier.
  3019. * @return {Object} The changes to pop up.
  3020. */
  3021. setAttrs: function(attr, changes) {
  3022. var me = this,
  3023. timers = attr.timers,
  3024. parsers = me._sprite.self.def._animationProcessors,
  3025. defaultEasing = me._easing,
  3026. defaultDuration = me._duration,
  3027. customDurations = me._customDurations,
  3028. customEasings = me._customEasings,
  3029. anySpecial = me.anySpecialAnimations,
  3030. any = me.anyAnimation || anySpecial,
  3031. targets = attr.targets,
  3032. ignite = false,
  3033. timer, name, newValue, startValue, parser, easing, duration, initial;
  3034. if (!any) {
  3035. // If there is no animation enabled.
  3036. // When applying changes to attributes, simply stop current animation
  3037. // and set the value.
  3038. for (name in changes) {
  3039. if (attr[name] === changes[name]) {
  3040. delete changes[name];
  3041. } else {
  3042. attr[name] = changes[name];
  3043. }
  3044. delete targets[name];
  3045. delete timers[name];
  3046. }
  3047. return changes;
  3048. } else {
  3049. // If any animation.
  3050. for (name in changes) {
  3051. newValue = changes[name];
  3052. startValue = attr[name];
  3053. if (newValue !== startValue && startValue !== undefined && startValue !== null && (parser = parsers[name])) {
  3054. // If this property is animating.
  3055. // Figure out the desired duration and easing.
  3056. easing = defaultEasing;
  3057. duration = defaultDuration;
  3058. if (anySpecial) {
  3059. // Deducing the easing function and duration
  3060. if (name in customEasings) {
  3061. easing = customEasings[name];
  3062. }
  3063. if (name in customDurations) {
  3064. duration = customDurations[name];
  3065. }
  3066. }
  3067. // Transitions betweens color and gradient or between gradients
  3068. // are not supported.
  3069. if (startValue && startValue.isGradient || newValue && newValue.isGradient) {
  3070. duration = 0;
  3071. }
  3072. // If the property is animating
  3073. if (duration) {
  3074. if (!timers[name]) {
  3075. timers[name] = {};
  3076. }
  3077. timer = timers[name];
  3078. timer.start = 0;
  3079. timer.easing = easing;
  3080. timer.duration = duration;
  3081. timer.compute = parser.compute;
  3082. timer.serve = parser.serve || Ext.identityFn;
  3083. timer.remove = changes.removeFromInstance && changes.removeFromInstance[name];
  3084. if (parser.parseInitial) {
  3085. initial = parser.parseInitial(startValue, newValue);
  3086. timer.source = initial[0];
  3087. timer.target = initial[1];
  3088. } else if (parser.parse) {
  3089. timer.source = parser.parse(startValue);
  3090. timer.target = parser.parse(newValue);
  3091. } else {
  3092. timer.source = startValue;
  3093. timer.target = newValue;
  3094. }
  3095. // The animation started. Change to originalVal.
  3096. targets[name] = newValue;
  3097. delete changes[name];
  3098. ignite = true;
  3099. continue;
  3100. } else {
  3101. delete targets[name];
  3102. }
  3103. } else {
  3104. delete targets[name];
  3105. }
  3106. // If the property is not animating.
  3107. delete timers[name];
  3108. }
  3109. }
  3110. if (ignite && !attr.animating) {
  3111. me.setAnimating(attr, true);
  3112. }
  3113. return changes;
  3114. },
  3115. /**
  3116. * @private
  3117. *
  3118. * Update attributes to current value according to current animation time.
  3119. * This method will not affect the values of lower layers, but may delete a
  3120. * value from it.
  3121. * @param {Object} attr The source attributes.
  3122. * @return {Object} The changes to pop up or null.
  3123. */
  3124. updateAttributes: function(attr) {
  3125. if (!attr.animating) {
  3126. return {};
  3127. }
  3128. // eslint-disable-next-line vars-on-top
  3129. var changes = {},
  3130. any = false,
  3131. timers = attr.timers,
  3132. targets = attr.targets,
  3133. now = Ext.draw.Animator.animationTime(),
  3134. name, timer, delta;
  3135. // If updated in the same frame, return.
  3136. if (attr.lastUpdate === now) {
  3137. return null;
  3138. }
  3139. for (name in timers) {
  3140. timer = timers[name];
  3141. if (!timer.start) {
  3142. timer.start = now;
  3143. delta = 0;
  3144. } else {
  3145. delta = (now - timer.start) / timer.duration;
  3146. }
  3147. if (delta >= 1) {
  3148. changes[name] = targets[name];
  3149. delete targets[name];
  3150. if (timers[name].remove) {
  3151. changes.removeFromInstance = changes.removeFromInstance || {};
  3152. changes.removeFromInstance[name] = true;
  3153. }
  3154. delete timers[name];
  3155. } else {
  3156. changes[name] = timer.serve(timer.compute(timer.source, timer.target, timer.easing(delta), attr[name]));
  3157. any = true;
  3158. }
  3159. }
  3160. attr.lastUpdate = now;
  3161. this.setAnimating(attr, any);
  3162. return changes;
  3163. },
  3164. pushDown: function(attr, changes) {
  3165. changes = this.callParent([
  3166. attr.targets,
  3167. changes
  3168. ]);
  3169. return this.setAttrs(attr, changes);
  3170. },
  3171. popUp: function(attr, changes) {
  3172. attr = attr.prototype;
  3173. changes = this.setAttrs(attr, changes);
  3174. if (this._upper) {
  3175. return this._upper.popUp(attr, changes);
  3176. } else {
  3177. return Ext.apply(attr, changes);
  3178. }
  3179. },
  3180. /**
  3181. * @private
  3182. * This is called as an animated object in `Ext.draw.Animator`.
  3183. */
  3184. step: function(frameTime) {
  3185. var me = this,
  3186. pool = me.animatingPool.slice(),
  3187. ln = pool.length,
  3188. i = 0,
  3189. attr, changes;
  3190. for (; i < ln; i++) {
  3191. attr = pool[i];
  3192. changes = me.updateAttributes(attr);
  3193. if (changes && me._upper) {
  3194. me._upper.popUp(attr, changes);
  3195. }
  3196. }
  3197. },
  3198. /**
  3199. * Stop all animations affected by this modifier.
  3200. */
  3201. stop: function() {
  3202. var me = this,
  3203. pool = me.animatingPool,
  3204. i, ln;
  3205. this.step();
  3206. for (i = 0 , ln = pool.length; i < ln; i++) {
  3207. pool[i].animating = false;
  3208. }
  3209. me.animatingPool.length = 0;
  3210. me.animating = 0;
  3211. Ext.draw.Animator.remove(me);
  3212. },
  3213. destroy: function() {
  3214. Ext.draw.Animator.remove(this);
  3215. this.callParent();
  3216. }
  3217. });
  3218. /**
  3219. * @class Ext.draw.modifier.Highlight
  3220. * @extends Ext.draw.modifier.Modifier
  3221. *
  3222. * Highlight is a modifier that will override sprite attributes
  3223. * with {@link Ext.draw.modifier.Highlight#style style} attributes
  3224. * when sprite's `highlighted` attribute is true.
  3225. */
  3226. Ext.define('Ext.draw.modifier.Highlight', {
  3227. extend: 'Ext.draw.modifier.Modifier',
  3228. alias: 'modifier.highlight',
  3229. config: {
  3230. /**
  3231. * @cfg {Boolean} enabled 'true' if the highlight is applied.
  3232. */
  3233. enabled: false,
  3234. /**
  3235. * @cfg {Object} style The style attributes of the highlight modifier.
  3236. */
  3237. style: null
  3238. },
  3239. preFx: true,
  3240. applyStyle: function(style, oldStyle) {
  3241. oldStyle = oldStyle || {};
  3242. if (this.getSprite()) {
  3243. Ext.apply(oldStyle, this.getSprite().self.def.normalize(style));
  3244. } else {
  3245. Ext.apply(oldStyle, style);
  3246. }
  3247. return oldStyle;
  3248. },
  3249. prepareAttributes: function(attr) {
  3250. if (!attr.hasOwnProperty('highlightOriginal')) {
  3251. attr.highlighted = false;
  3252. attr.highlightOriginal = Ext.Object.chain(attr);
  3253. attr.highlightOriginal.prototype = attr;
  3254. // A list of attributes that should be removed from a sprite instance
  3255. // when it is unhighlighted.
  3256. attr.highlightOriginal.removeFromInstance = {};
  3257. }
  3258. if (this._lower) {
  3259. this._lower.prepareAttributes(attr.highlightOriginal);
  3260. }
  3261. },
  3262. updateSprite: function(sprite, oldSprite) {
  3263. var me = this,
  3264. style = me.getStyle(),
  3265. attributeDefinitions;
  3266. if (sprite) {
  3267. attributeDefinitions = sprite.self.def;
  3268. if (style) {
  3269. me._style = attributeDefinitions.normalize(style);
  3270. }
  3271. me.setStyle(sprite.config.highlight);
  3272. // Add highlight related attributes to sprite's attribute definition.
  3273. // This will affect all sprites of the same type, even those without
  3274. // the highlight modifier.
  3275. attributeDefinitions.setConfig({
  3276. defaults: {
  3277. highlighted: false
  3278. },
  3279. processors: {
  3280. highlighted: 'bool'
  3281. }
  3282. });
  3283. }
  3284. this.setSprite(sprite);
  3285. },
  3286. /**
  3287. * @private
  3288. * Filter out modifier changes that override highlight style or source attributes.
  3289. * @param {Object} attr The source attributes.
  3290. * @param {Object} changes The modifier changes.
  3291. * @return {*} The filtered changes.
  3292. */
  3293. filterChanges: function(attr, changes) {
  3294. var me = this,
  3295. highlightOriginal = attr.highlightOriginal,
  3296. style = me.getStyle(),
  3297. name;
  3298. if (attr.highlighted) {
  3299. for (name in changes) {
  3300. if (style.hasOwnProperty(name)) {
  3301. // If sprite is highlighted, then stash the changes
  3302. // to the `style` attributes made by lower level modifiers
  3303. // to apply them later when sprite is unhighlighted.
  3304. highlightOriginal[name] = changes[name];
  3305. delete changes[name];
  3306. }
  3307. }
  3308. }
  3309. return changes;
  3310. },
  3311. pushDown: function(attr, changes) {
  3312. var style = this.getStyle(),
  3313. highlightOriginal = attr.highlightOriginal,
  3314. removeFromInstance = highlightOriginal.removeFromInstance,
  3315. highlighted, name, tplAttr, timer;
  3316. if (changes.hasOwnProperty('highlighted')) {
  3317. highlighted = changes.highlighted;
  3318. // Hide `highlighted` and `style` from underlying modifiers.
  3319. delete changes.highlighted;
  3320. if (this._lower) {
  3321. changes = this._lower.pushDown(highlightOriginal, changes);
  3322. }
  3323. changes = this.filterChanges(attr, changes);
  3324. if (highlighted !== attr.highlighted) {
  3325. if (highlighted) {
  3326. // Switching ON.
  3327. // At this time, original should be empty.
  3328. for (name in style) {
  3329. // Remember the values of attributes to revert back to them on unhighlight.
  3330. if (name in changes) {
  3331. // Remember value set by lower level modifiers.
  3332. highlightOriginal[name] = changes[name];
  3333. } else {
  3334. // Remember the original value.
  3335. // If this is a sprite instance and it doesn't have its own
  3336. // 'name' attribute, (i.e. inherits template's attribute value)
  3337. // than we have to get the value for the 'name' attribute from
  3338. // the template's 'targets' object instead of its
  3339. // 'attr' object (which is the prototype of the instance),
  3340. // because the 'name' attribute of the template may be animating.
  3341. // Check out the prepareAttributes method of the Animation
  3342. // modifier for more details on the 'targets' object.
  3343. tplAttr = attr.template && attr.template.ownAttr;
  3344. if (tplAttr && !attr.prototype.hasOwnProperty(name)) {
  3345. removeFromInstance[name] = true;
  3346. highlightOriginal[name] = tplAttr.targets[name];
  3347. } else {
  3348. // Even if a sprite instance has its own property, it may
  3349. // still have to be removed from the instance after
  3350. // unhighlighting is done.
  3351. // Consider a situation where an instance doesn't originally
  3352. // have its own attribute (that is used for highlighting and
  3353. // unhighlighting). It will however have that attribute as
  3354. // its own when the highlight/unhighlight animation is in
  3355. // progress, until the attribute is removed from the instance
  3356. // when the unhighlighting is done.
  3357. // So in a scenario where the instance is highlighted, then
  3358. // unhighlighted (i.e. starts animating back to its original
  3359. // value) and then highlighted again before the unhighlight
  3360. // animation is done, we should still mark the attribute
  3361. // for removal from the instance, if it was our original
  3362. // intention. To tell if it was, we can check the timer
  3363. // for the attribute and see if the 'remove' flag is set.
  3364. timer = highlightOriginal.timers[name];
  3365. if (timer && timer.remove) {
  3366. removeFromInstance[name] = true;
  3367. }
  3368. highlightOriginal[name] = attr[name];
  3369. }
  3370. }
  3371. if (highlightOriginal[name] !== style[name]) {
  3372. changes[name] = style[name];
  3373. }
  3374. }
  3375. } else {
  3376. // Switching OFF.
  3377. for (name in style) {
  3378. if (!(name in changes)) {
  3379. changes[name] = highlightOriginal[name];
  3380. }
  3381. delete highlightOriginal[name];
  3382. }
  3383. changes.removeFromInstance = changes.removeFromInstance || {};
  3384. // Let the higher lever animation modifier know which attributes
  3385. // should be removed from instance when the animation is done.
  3386. Ext.apply(changes.removeFromInstance, removeFromInstance);
  3387. highlightOriginal.removeFromInstance = {};
  3388. }
  3389. changes.highlighted = highlighted;
  3390. }
  3391. } else {
  3392. if (this._lower) {
  3393. changes = this._lower.pushDown(highlightOriginal, changes);
  3394. }
  3395. changes = this.filterChanges(attr, changes);
  3396. }
  3397. return changes;
  3398. },
  3399. popUp: function(attr, changes) {
  3400. changes = this.filterChanges(attr, changes);
  3401. this.callParent([
  3402. attr,
  3403. changes
  3404. ]);
  3405. }
  3406. });
  3407. /**
  3408. * A sprite is a basic primitive from the charts package which represents a graphical
  3409. * object that can be drawn. Sprites are used extensively in the charts package to
  3410. * create the visual elements of each chart. You can also create a desired image by
  3411. * adding one or more sprites to a {@link Ext.draw.Container draw container}.
  3412. *
  3413. * The Sprite class itself is an abstract class and is not meant to be used directly.
  3414. * There are many different kinds of sprites available in the charts package that extend
  3415. * Ext.draw.sprite.Sprite. Each sprite type has various attributes that define how that
  3416. * sprite should look. For example, this is a {@link Ext.draw.sprite.Rect rect} sprite:
  3417. *
  3418. * @example
  3419. * Ext.create({
  3420. * xtype: 'draw',
  3421. * renderTo: document.body,
  3422. * width: 400,
  3423. * height: 400,
  3424. * sprites: [{
  3425. * type: 'rect',
  3426. * x: 50,
  3427. * y: 50,
  3428. * width: 100,
  3429. * height: 100,
  3430. * fillStyle: '#1F6D91'
  3431. * }]
  3432. * });
  3433. *
  3434. * By default, sprites are added to the default 'main' {@link Ext.draw.Surface surface}
  3435. * of the draw container. However, sprites may also be configured with a reference to a
  3436. * specific Ext.draw.Surface when set in the draw container's
  3437. * {@link Ext.draw.Container#cfg-sprites sprites} config. Specifying a surface
  3438. * other than 'main' will create a surface by that name if it does not already exist.
  3439. *
  3440. * @example
  3441. * Ext.create({
  3442. * xtype: 'draw',
  3443. * renderTo: document.body,
  3444. * width: 400,
  3445. * height: 400,
  3446. * sprites: [{
  3447. * type: 'rect',
  3448. * surface: 'anim', // a surface with id "anim" will be created automatically
  3449. * x: 50,
  3450. * y: 50,
  3451. * width: 100,
  3452. * height: 100,
  3453. * fillStyle: '#1F6D91'
  3454. * }]
  3455. * });
  3456. *
  3457. * The ability to have multiple surfaces is useful for performance (and battery life)
  3458. * reasons. Because changes to sprite attributes cause the whole surface (and all
  3459. * sprites in it) to re-render, it makes sense to group sprites by surface, so changes
  3460. * to one group of sprites will only trigger the surface they are in to re-render.
  3461. *
  3462. * You can add a sprite to an existing drawing by adding the sprite to a draw surface.
  3463. *
  3464. * @example
  3465. * var drawCt = Ext.create({
  3466. * xtype: 'draw',
  3467. * renderTo: document.body,
  3468. * width: 400,
  3469. * height: 400
  3470. * });
  3471. *
  3472. * // If the surface name is not specified then 'main' will be used
  3473. * var surface = drawCt.getSurface();
  3474. *
  3475. * surface.add({
  3476. * type: 'rect',
  3477. * x: 50,
  3478. * y: 50,
  3479. * width: 100,
  3480. * height: 100,
  3481. * fillStyle: '#1F6D91'
  3482. * });
  3483. *
  3484. * surface.renderFrame();
  3485. *
  3486. * **Note:** Changes to the sprites on a surface will be not be reflected in the DOM
  3487. * until you call the surface's {@link Ext.draw.Surface#method-renderFrame renderFrame}
  3488. * method. This must be done after adding, removing, or modifying sprites in order to
  3489. * see the changes on-screen.
  3490. *
  3491. * For information on configuring a sprite with an initial transformation see
  3492. * {@link #scaling}, {@link #rotation}, and {@link #translation}.
  3493. *
  3494. * For information on applying a transformation to an existing sprite see the
  3495. * Ext.draw.Matrix class.
  3496. */
  3497. Ext.define('Ext.draw.sprite.Sprite', {
  3498. alias: 'sprite.sprite',
  3499. mixins: {
  3500. observable: 'Ext.mixin.Observable'
  3501. },
  3502. requires: [
  3503. 'Ext.draw.Draw',
  3504. 'Ext.draw.gradient.Gradient',
  3505. 'Ext.draw.sprite.AttributeDefinition',
  3506. 'Ext.draw.modifier.Target',
  3507. 'Ext.draw.modifier.Animation',
  3508. 'Ext.draw.modifier.Highlight'
  3509. ],
  3510. isSprite: true,
  3511. $configStrict: false,
  3512. statics: {
  3513. //<debug>
  3514. /* eslint-disable max-len */
  3515. /**
  3516. * Debug rendering options:
  3517. *
  3518. * debug: {
  3519. * bbox: true, // renders the bounding box of the sprite
  3520. * xray: true // renders control points of the path (for Ext.draw.sprite.Path and descendants only)
  3521. * }
  3522. *
  3523. */
  3524. debug: false,
  3525. /* eslint-enable max-len */
  3526. //</debug>
  3527. defaultHitTestOptions: {
  3528. fill: true,
  3529. stroke: true
  3530. }
  3531. },
  3532. inheritableStatics: {
  3533. def: {
  3534. processors: {
  3535. //<debug>
  3536. debug: 'default',
  3537. //</debug>
  3538. /**
  3539. * @cfg {String} [strokeStyle="none"] The color of the stroke (a CSS color value).
  3540. */
  3541. strokeStyle: "color",
  3542. /**
  3543. * @cfg {String} [fillStyle="none"] The color of the shape (a CSS color value).
  3544. */
  3545. fillStyle: "color",
  3546. /**
  3547. * @cfg {Number} [strokeOpacity=1] The opacity of the stroke. Limited from 0 to 1.
  3548. */
  3549. strokeOpacity: "limited01",
  3550. /**
  3551. * @cfg {Number} [fillOpacity=1] The opacity of the fill. Limited from 0 to 1.
  3552. */
  3553. fillOpacity: "limited01",
  3554. /**
  3555. * @cfg {Number} [lineWidth=1] The width of the line stroke.
  3556. */
  3557. lineWidth: "number",
  3558. /**
  3559. * @cfg {String} [lineCap="butt"] The style of the line caps.
  3560. */
  3561. lineCap: "enums(butt,round,square)",
  3562. /**
  3563. * @cfg {String} [lineJoin="miter"] The style of the line join.
  3564. */
  3565. lineJoin: "enums(round,bevel,miter)",
  3566. /**
  3567. * @cfg {Array} [lineDash=[]]
  3568. * An even number of non-negative numbers specifying a dash/space sequence.
  3569. * Note that while this is supported in IE8 (VML engine), the behavior is
  3570. * different from Canvas and SVG. Please refer to this document for details:
  3571. * http://msdn.microsoft.com/en-us/library/bb264085(v=vs.85).aspx
  3572. * Although IE9 and IE10 have Canvas support, the 'lineDash'
  3573. * attribute is not supported in those browsers.
  3574. */
  3575. lineDash: "data",
  3576. /**
  3577. * @cfg {Number} [lineDashOffset=0]
  3578. * A number specifying how far into the line dash sequence drawing commences.
  3579. */
  3580. lineDashOffset: "number",
  3581. /**
  3582. * @cfg {Number} [miterLimit=10]
  3583. * Sets the distance between the inner corner and the outer corner
  3584. * where two lines meet.
  3585. */
  3586. miterLimit: "number",
  3587. /**
  3588. * @cfg {String} [shadowColor="none"] The color of the shadow (a CSS color value).
  3589. */
  3590. shadowColor: "color",
  3591. /**
  3592. * @cfg {Number} [shadowOffsetX=0] The offset of the sprite's shadow on the x-axis.
  3593. */
  3594. shadowOffsetX: "number",
  3595. /**
  3596. * @cfg {Number} [shadowOffsetY=0] The offset of the sprite's shadow on the y-axis.
  3597. */
  3598. shadowOffsetY: "number",
  3599. /**
  3600. * @cfg {Number} [shadowBlur=0] The amount blur used on the shadow.
  3601. */
  3602. shadowBlur: "number",
  3603. /**
  3604. * @cfg {Number} [globalAlpha=1] The opacity of the sprite. Limited from 0 to 1.
  3605. */
  3606. globalAlpha: "limited01",
  3607. /**
  3608. * @cfg {String} [globalCompositeOperation=source-over]
  3609. * Indicates how source images are drawn onto a destination image.
  3610. * globalCompositeOperation attribute is not supported by the SVG and VML
  3611. * (excanvas) engines.
  3612. */
  3613. // eslint-disable-next-line max-len
  3614. globalCompositeOperation: "enums(source-over,destination-over,source-in,destination-in,source-out,destination-out,source-atop,destination-atop,lighter,xor,copy)",
  3615. /**
  3616. * @cfg {Boolean} [hidden=false] Determines whether or not the sprite is hidden.
  3617. */
  3618. hidden: "bool",
  3619. /**
  3620. * @cfg {Boolean} [transformFillStroke=false]
  3621. * Determines whether the fill and stroke are affected by sprite transformations.
  3622. */
  3623. transformFillStroke: "bool",
  3624. /**
  3625. * @cfg {Number} [zIndex=0]
  3626. * The stacking order of the sprite.
  3627. */
  3628. zIndex: "number",
  3629. /**
  3630. * @cfg {Number} [translationX=0]
  3631. * The translation, position offset, of the sprite on the x-axis.
  3632. *
  3633. * **Note:** Transform configs are *always* performed in the following
  3634. * order:
  3635. *
  3636. * 1. Scaling
  3637. * 2. Rotation
  3638. * 3. Translation
  3639. *
  3640. * See also: {@link #translation} and {@link #translationY}
  3641. */
  3642. translationX: "number",
  3643. /**
  3644. * @cfg {Number} [translationY=0]
  3645. * The translation, position offset, of the sprite on the y-axis.
  3646. *
  3647. * **Note:** Transform configs are *always* performed in the following
  3648. * order:
  3649. *
  3650. * 1. Scaling
  3651. * 2. Rotation
  3652. * 3. Translation
  3653. *
  3654. * See also: {@link #translation} and {@link #translationX}
  3655. */
  3656. translationY: "number",
  3657. /**
  3658. * @cfg {Number} [rotationRads=0]
  3659. * The angle of rotation of the sprite in radians.
  3660. *
  3661. * **Note:** Transform configs are *always* performed in the following
  3662. * order:
  3663. *
  3664. * 1. Scaling
  3665. * 2. Rotation
  3666. * 3. Translation
  3667. *
  3668. * See also: {@link #rotation}, {@link #rotationCenterX}, and
  3669. * {@link #rotationCenterY}
  3670. */
  3671. rotationRads: "number",
  3672. /**
  3673. * @cfg {Number} [rotationCenterX=null]
  3674. * The central coordinate of the sprite's scale operation on the x-axis.
  3675. * Unless explicitly set, will default to the calculated center of the
  3676. * sprite along the x-axis.
  3677. *
  3678. * **Note:** Transform configs are *always* performed in the following
  3679. * order:
  3680. *
  3681. * 1. Scaling
  3682. * 2. Rotation
  3683. * 3. Translation
  3684. *
  3685. * See also: {@link #rotation}, {@link #rotationRads}, and
  3686. * {@link #rotationCenterY}
  3687. */
  3688. rotationCenterX: "number",
  3689. /**
  3690. * @cfg {Number} [rotationCenterY=null]
  3691. * The central coordinate of the sprite's rotate operation on the y-axis.
  3692. * Unless explicitly set, will default to the calculated center of the
  3693. * sprite along the y-axis.
  3694. *
  3695. * **Note:** Transform configs are *always* performed in the following
  3696. * order:
  3697. *
  3698. * 1. Scaling
  3699. * 2. Rotation
  3700. * 3. Translation
  3701. *
  3702. * See also: {@link #rotation}, {@link #rotationRads}, and
  3703. * {@link #rotationCenterX}
  3704. */
  3705. rotationCenterY: "number",
  3706. /**
  3707. * @cfg {Number} [scalingX=1] The scaling of the sprite on the x-axis.
  3708. * The number value represents a percentage by which to scale the
  3709. * sprite. **1** is equal to 100%, **2** would be 200%, etc.
  3710. *
  3711. * **Note:** Transform configs are *always* performed in the following
  3712. * order:
  3713. *
  3714. * 1. Scaling
  3715. * 2. Rotation
  3716. * 3. Translation
  3717. *
  3718. * See also: {@link #scaling}, {@link #scalingY},
  3719. * {@link #scalingCenterX}, and {@link #scalingCenterY}
  3720. */
  3721. scalingX: "number",
  3722. /**
  3723. * @cfg {Number} [scalingY=1] The scaling of the sprite on the y-axis.
  3724. * The number value represents a percentage by which to scale the
  3725. * sprite. **1** is equal to 100%, **2** would be 200%, etc.
  3726. *
  3727. * **Note:** Transform configs are *always* performed in the following
  3728. * order:
  3729. *
  3730. * 1. Scaling
  3731. * 2. Rotation
  3732. * 3. Translation
  3733. *
  3734. * See also: {@link #scaling}, {@link #scalingX},
  3735. * {@link #scalingCenterX}, and {@link #scalingCenterY}
  3736. */
  3737. scalingY: "number",
  3738. /**
  3739. * @cfg {Number} [scalingCenterX=null]
  3740. * The central coordinate of the sprite's scale operation on the x-axis.
  3741. *
  3742. * **Note:** Transform configs are *always* performed in the following
  3743. * order:
  3744. *
  3745. * 1. Scaling
  3746. * 2. Rotation
  3747. * 3. Translation
  3748. *
  3749. * See also: {@link #scaling}, {@link #scalingX},
  3750. * {@link #scalingY}, and {@link #scalingCenterY}
  3751. */
  3752. scalingCenterX: "number",
  3753. /**
  3754. * @cfg {Number} [scalingCenterY=null]
  3755. * The central coordinate of the sprite's scale operation on the y-axis.
  3756. *
  3757. * **Note:** Transform configs are *always* performed in the following
  3758. * order:
  3759. *
  3760. * 1. Scaling
  3761. * 2. Rotation
  3762. * 3. Translation
  3763. *
  3764. * See also: {@link #scaling}, {@link #scalingX},
  3765. * {@link #scalingY}, and {@link #scalingCenterX}
  3766. */
  3767. scalingCenterY: "number",
  3768. constrainGradients: "bool"
  3769. },
  3770. /**
  3771. * @cfg {Number/Object} rotation
  3772. * Applies an initial angle of rotation to the sprite. May be a number
  3773. * specifying the rotation in degrees. Or may be a config object using
  3774. * the below config options.
  3775. *
  3776. * **Note:** Rotation config options will be overridden by values set on
  3777. * the {@link #rotationRads}, {@link #rotationCenterX}, and
  3778. * {@link #rotationCenterY} configs.
  3779. *
  3780. * Ext.create({
  3781. * xtype: 'draw',
  3782. * renderTo: Ext.getBody(),
  3783. * width: 600,
  3784. * height: 400,
  3785. * sprites: [{
  3786. * type: 'rect',
  3787. * x: 50,
  3788. * y: 50,
  3789. * width: 100,
  3790. * height: 100,
  3791. * fillStyle: '#1F6D91',
  3792. * //rotation: 45
  3793. * rotation: {
  3794. * degrees: 45,
  3795. * //rads: Math.PI / 4,
  3796. * //centerX: 50,
  3797. * //centerY: 50
  3798. * }
  3799. * }]
  3800. * });
  3801. *
  3802. * **Note:** Transform configs are *always* performed in the following
  3803. * order:
  3804. *
  3805. * 1. Scaling
  3806. * 2. Rotation
  3807. * 3. Translation
  3808. *
  3809. * @cfg {Number} rotation.rads
  3810. * The angle in radians to rotate the sprite
  3811. *
  3812. * @cfg {Number} rotation.degrees
  3813. * The angle in degrees to rotate the sprite (is ignored if rads or
  3814. * {@link #rotationRads} is set
  3815. *
  3816. * @cfg {Number} rotation.centerX
  3817. * The central coordinate of the sprite's rotation on the x-axis.
  3818. * Unless explicitly set, will default to the calculated center of the
  3819. * sprite along the x-axis.
  3820. *
  3821. * @cfg {Number} rotation.centerY
  3822. * The central coordinate of the sprite's rotation on the y-axis.
  3823. * Unless explicitly set, will default to the calculated center of the
  3824. * sprite along the y-axis.
  3825. */
  3826. /**
  3827. * @cfg {Number/Object} scaling
  3828. * Applies initial scaling to the sprite. May be a number specifying
  3829. * the amount to scale both the x and y-axis. The number value
  3830. * represents a percentage by which to scale the sprite. **1** is equal
  3831. * to 100%, **2** would be 200%, etc. Or may be a config object using
  3832. * the below config options.
  3833. *
  3834. * **Note:** Scaling config options will be overridden by values set on
  3835. * the {@link #scalingX}, {@link #scalingY}, {@link #scalingCenterX},
  3836. * and {@link #scalingCenterY} configs.
  3837. *
  3838. * Ext.create({
  3839. * xtype: 'draw',
  3840. * renderTo: Ext.getBody(),
  3841. * width: 600,
  3842. * height: 400,
  3843. * sprites: [{
  3844. * type: 'rect',
  3845. * x: 50,
  3846. * y: 50,
  3847. * width: 100,
  3848. * height: 100,
  3849. * fillStyle: '#1F6D91',
  3850. * //scaling: 2,
  3851. * scaling: {
  3852. * x: 2,
  3853. * y: 2
  3854. * //centerX: 100,
  3855. * //centerY: 100
  3856. * }
  3857. * }]
  3858. * });
  3859. *
  3860. * **Note:** Transform configs are *always* performed in the following
  3861. * order:
  3862. *
  3863. * 1. Scaling
  3864. * 2. Rotation
  3865. * 3. Translation
  3866. *
  3867. * @cfg {Number} scaling.x
  3868. * The amount by which to scale the sprite along the x-axis. The number
  3869. * value represents a percentage by which to scale the sprite. **1** is
  3870. * equal to 100%, **2** would be 200%, etc.
  3871. *
  3872. * @cfg {Number} scaling.y
  3873. * The amount by which to scale the sprite along the y-axis. The number
  3874. * value represents a percentage by which to scale the sprite. **1** is
  3875. * equal to 100%, **2** would be 200%, etc.
  3876. *
  3877. * @cfg scaling.centerX
  3878. * The central coordinate of the sprite's scaling on the x-axis. Unless
  3879. * explicitly set, will default to the calculated center of the sprite
  3880. * along the x-axis.
  3881. *
  3882. * @cfg {Number} scaling.centerY
  3883. * The central coordinate of the sprite's scaling on the y-axis. Unless
  3884. * explicitly set, will default to the calculated center of the sprite
  3885. * along the y-axis.
  3886. */
  3887. /**
  3888. * @cfg {Object} translation
  3889. * Applies an initial translation, adjustment in x/y positioning, to the
  3890. * sprite.
  3891. *
  3892. * **Note:** Translation config options will be overridden by values set
  3893. * on the {@link #translationX} and {@link #translationY} configs.
  3894. *
  3895. * Ext.create({
  3896. * xtype: 'draw',
  3897. * renderTo: Ext.getBody(),
  3898. * width: 600,
  3899. * height: 400,
  3900. * sprites: [{
  3901. * type: 'rect',
  3902. * x: 50,
  3903. * y: 50,
  3904. * width: 100,
  3905. * height: 100,
  3906. * fillStyle: '#1F6D91',
  3907. * translation: {
  3908. * x: 50,
  3909. * y: 50
  3910. * }
  3911. * }]
  3912. * });
  3913. *
  3914. * **Note:** Transform configs are *always* performed in the following
  3915. * order:
  3916. *
  3917. * 1. Scaling
  3918. * 2. Rotation
  3919. * 3. Translation
  3920. *
  3921. * @cfg {Number} translation.x
  3922. * The amount to translate the sprite along the x-axis.
  3923. *
  3924. * @cfg {Number} translation.y
  3925. * The amount to translate the sprite along the y-axis.
  3926. */
  3927. aliases: {
  3928. "stroke": "strokeStyle",
  3929. "fill": "fillStyle",
  3930. "color": "fillStyle",
  3931. "stroke-width": "lineWidth",
  3932. "stroke-linecap": "lineCap",
  3933. "stroke-linejoin": "lineJoin",
  3934. "stroke-miterlimit": "miterLimit",
  3935. "text-anchor": "textAlign",
  3936. "opacity": "globalAlpha",
  3937. translateX: "translationX",
  3938. translateY: "translationY",
  3939. rotateRads: "rotationRads",
  3940. rotateCenterX: "rotationCenterX",
  3941. rotateCenterY: "rotationCenterY",
  3942. scaleX: "scalingX",
  3943. scaleY: "scalingY",
  3944. scaleCenterX: "scalingCenterX",
  3945. scaleCenterY: "scalingCenterY"
  3946. },
  3947. defaults: {
  3948. hidden: false,
  3949. zIndex: 0,
  3950. strokeStyle: "none",
  3951. fillStyle: "none",
  3952. lineWidth: 1,
  3953. lineDash: [],
  3954. lineDashOffset: 0,
  3955. lineCap: "butt",
  3956. lineJoin: "miter",
  3957. miterLimit: 10,
  3958. shadowColor: "none",
  3959. shadowOffsetX: 0,
  3960. shadowOffsetY: 0,
  3961. shadowBlur: 0,
  3962. globalAlpha: 1,
  3963. strokeOpacity: 1,
  3964. fillOpacity: 1,
  3965. transformFillStroke: false,
  3966. translationX: 0,
  3967. translationY: 0,
  3968. rotationRads: 0,
  3969. rotationCenterX: null,
  3970. rotationCenterY: null,
  3971. scalingX: 1,
  3972. scalingY: 1,
  3973. scalingCenterX: null,
  3974. scalingCenterY: null,
  3975. constrainGradients: false
  3976. },
  3977. triggers: {
  3978. zIndex: "zIndex",
  3979. globalAlpha: "canvas",
  3980. globalCompositeOperation: "canvas",
  3981. transformFillStroke: "canvas",
  3982. strokeStyle: "canvas",
  3983. fillStyle: "canvas",
  3984. strokeOpacity: "canvas",
  3985. fillOpacity: "canvas",
  3986. lineWidth: "canvas",
  3987. lineCap: "canvas",
  3988. lineJoin: "canvas",
  3989. lineDash: "canvas",
  3990. lineDashOffset: "canvas",
  3991. miterLimit: "canvas",
  3992. shadowColor: "canvas",
  3993. shadowOffsetX: "canvas",
  3994. shadowOffsetY: "canvas",
  3995. shadowBlur: "canvas",
  3996. translationX: "transform",
  3997. translationY: "transform",
  3998. rotationRads: "transform",
  3999. rotationCenterX: "transform",
  4000. rotationCenterY: "transform",
  4001. scalingX: "transform",
  4002. scalingY: "transform",
  4003. scalingCenterX: "transform",
  4004. scalingCenterY: "transform",
  4005. constrainGradients: "canvas"
  4006. },
  4007. updaters: {
  4008. // 'bbox' updater is meant to be called by subclasses when changes
  4009. // to attributes are expected to result in a change in sprite's dimensions.
  4010. bbox: 'bboxUpdater',
  4011. zIndex: function(attr) {
  4012. attr.dirtyZIndex = true;
  4013. },
  4014. transform: function(attr) {
  4015. attr.dirtyTransform = true;
  4016. attr.bbox.transform.dirty = true;
  4017. }
  4018. }
  4019. }
  4020. },
  4021. /**
  4022. * @property {Object} attr
  4023. * The visual attributes of the sprite, e.g. strokeStyle, fillStyle, lineWidth...
  4024. */
  4025. /**
  4026. * @cfg {Ext.draw.modifier.Animation} animation
  4027. * @accessor
  4028. */
  4029. config: {
  4030. /**
  4031. * @private
  4032. * @cfg {Ext.draw.Surface/Ext.draw.sprite.Instancing/Ext.draw.sprite.Composite} parent
  4033. * The immediate parent of the sprite. Not necessarily a surface.
  4034. */
  4035. parent: null,
  4036. /**
  4037. * @private
  4038. * @cfg {Ext.draw.Surface} surface
  4039. * The surface that this sprite is rendered into.
  4040. * This config is not meant to be used directly.
  4041. * Please use the {@link Ext.draw.Surface#add} method instead.
  4042. */
  4043. surface: null
  4044. },
  4045. onClassExtended: function(subClass, data) {
  4046. // The `def` here is no longer a config, but an instance
  4047. // of the AttributeDefinition class created with that config,
  4048. // which can now be retrieved from `initialConfig`.
  4049. var superclassCfg = subClass.superclass.self.def.initialConfig,
  4050. ownCfg = data.inheritableStatics && data.inheritableStatics.def,
  4051. cfg;
  4052. // If sprite defines attributes of its own, merge that with those of its parent.
  4053. if (ownCfg) {
  4054. cfg = Ext.Object.merge({}, superclassCfg, ownCfg);
  4055. subClass.def = new Ext.draw.sprite.AttributeDefinition(cfg);
  4056. delete data.inheritableStatics.def;
  4057. } else {
  4058. subClass.def = new Ext.draw.sprite.AttributeDefinition(superclassCfg);
  4059. }
  4060. subClass.def.spriteClass = subClass;
  4061. },
  4062. constructor: function(config) {
  4063. //<debug>
  4064. if (Ext.getClassName(this) === 'Ext.draw.sprite.Sprite') {
  4065. throw 'Ext.draw.sprite.Sprite is an abstract class';
  4066. }
  4067. //</debug>
  4068. // eslint-disable-next-line vars-on-top
  4069. var me = this,
  4070. attributeDefinition = me.self.def,
  4071. // It is important to get defaults (make sure
  4072. // 'defaults' config applier of the AttributeDefinition is called,
  4073. // since it is initialized lazily) before the attributes
  4074. // are initialized ('initializeAttributes' call).
  4075. defaults = attributeDefinition.getDefaults(),
  4076. processors = attributeDefinition.getProcessors(),
  4077. modifiers, name;
  4078. config = Ext.isObject(config) ? config : {};
  4079. me.id = config.id || Ext.id(null, 'ext-sprite-');
  4080. me.attr = {};
  4081. // Observable's constructor also calls the initConfig for us.
  4082. me.mixins.observable.constructor.apply(me, arguments);
  4083. modifiers = Ext.Array.from(config.modifiers, true);
  4084. me.createModifiers(modifiers);
  4085. me.initializeAttributes();
  4086. me.setAttributes(defaults, true);
  4087. //<debug>
  4088. for (name in config) {
  4089. if (name in processors && me['get' + name.charAt(0).toUpperCase() + name.substr(1)]) {
  4090. Ext.raise('The ' + me.$className + ' sprite has both a config and an attribute with the same name: ' + name + '.');
  4091. }
  4092. }
  4093. //</debug>
  4094. me.setAttributes(config);
  4095. },
  4096. updateSurface: function(surface, oldSurface) {
  4097. if (oldSurface) {
  4098. oldSurface.remove(this);
  4099. }
  4100. },
  4101. /**
  4102. * @private
  4103. * Current state of the sprite.
  4104. * Set to `true` if the sprite needs to be repainted.
  4105. * @cfg {Boolean} dirty
  4106. * @accessor
  4107. */
  4108. getDirty: function() {
  4109. return this.attr.dirty;
  4110. },
  4111. setDirty: function(dirty) {
  4112. var parent;
  4113. // This could have been a regular attribute.
  4114. // Instead, it's a hidden one, which is initialized inside in the
  4115. // Target's modifier `prepareAttributes` method and is exposed
  4116. // as a config. The idea is to skip the modifier chain when
  4117. // we simply need to change the sprite's state and notify
  4118. // the sprite's parent.
  4119. this.attr.dirty = dirty;
  4120. if (dirty) {
  4121. parent = this.getParent();
  4122. if (parent) {
  4123. parent.setDirty(true);
  4124. }
  4125. }
  4126. },
  4127. addModifier: function(modifier, reinitializeAttributes) {
  4128. var me = this,
  4129. mods = me.modifiers,
  4130. animation = mods.animation,
  4131. target = mods.target,
  4132. type;
  4133. if (!(modifier instanceof Ext.draw.modifier.Modifier)) {
  4134. type = typeof modifier === 'string' ? modifier : modifier.type;
  4135. if (type && !mods[type]) {
  4136. mods[type] = modifier = Ext.factory(modifier, null, null, 'modifier');
  4137. }
  4138. }
  4139. modifier.setSprite(me);
  4140. if (modifier.preFx || modifier.config && modifier.config.preFx) {
  4141. if (animation._lower) {
  4142. animation._lower.setUpper(modifier);
  4143. }
  4144. modifier.setUpper(animation);
  4145. } else {
  4146. target._lower.setUpper(modifier);
  4147. modifier.setUpper(target);
  4148. }
  4149. if (reinitializeAttributes) {
  4150. me.initializeAttributes();
  4151. }
  4152. return modifier;
  4153. },
  4154. createModifiers: function(modifiers) {
  4155. var me = this,
  4156. Modifier = Ext.draw.modifier,
  4157. animation = me.getInitialConfig().animation,
  4158. mods, i, ln;
  4159. // Create default modifiers.
  4160. me.modifiers = mods = {
  4161. target: new Modifier.Target({
  4162. sprite: me
  4163. }),
  4164. animation: new Modifier.Animation(Ext.apply({
  4165. sprite: me
  4166. }, animation))
  4167. };
  4168. // Link modifiers.
  4169. mods.animation.setUpper(mods.target);
  4170. for (i = 0 , ln = modifiers.length; i < ln; i++) {
  4171. me.addModifier(modifiers[i], false);
  4172. }
  4173. return mods;
  4174. },
  4175. /**
  4176. * Returns the current animation instance.
  4177. * return {Ext.draw.modifier.Animation} The animation modifier used to animate the
  4178. * sprite
  4179. */
  4180. getAnimation: function() {
  4181. return this.modifiers.animation;
  4182. },
  4183. /**
  4184. * Sets the animation config used by the sprite when animating the sprite's
  4185. * attributes and transformation properties.
  4186. *
  4187. * var drawCt = Ext.create({
  4188. * xtype: 'draw',
  4189. * renderTo: document.body,
  4190. * width: 400,
  4191. * height: 400,
  4192. * sprites: [{
  4193. * type: 'rect',
  4194. * x: 50,
  4195. * y: 50,
  4196. * width: 100,
  4197. * height: 100,
  4198. * fillStyle: '#1F6D91'
  4199. * }]
  4200. * });
  4201. *
  4202. * var rect = drawCt.getSurface().getItems()[0];
  4203. *
  4204. * rect.setAnimation({
  4205. * duration: 1000,
  4206. * easing: 'elasticOut'
  4207. * });
  4208. *
  4209. * Ext.defer(function () {
  4210. * rect.setAttributes({
  4211. * width: 250
  4212. * });
  4213. * }, 500);
  4214. *
  4215. * @param {Object} config The Ext.draw.modifier.Animation config for this sprite's
  4216. * animations.
  4217. */
  4218. setAnimation: function(config) {
  4219. if (!this.isConfiguring) {
  4220. this.modifiers.animation.setConfig(config || {
  4221. duration: 0
  4222. });
  4223. }
  4224. },
  4225. initializeAttributes: function() {
  4226. this.modifiers.target.prepareAttributes(this.attr);
  4227. },
  4228. /**
  4229. * @private
  4230. * Calls updaters triggered by changes to sprite attributes.
  4231. * @param attr The attributes of a sprite or its instance.
  4232. */
  4233. callUpdaters: function(attr) {
  4234. var me = this,
  4235. updaters = me.self.def.getUpdaters(),
  4236. any = false,
  4237. dirty = false,
  4238. pendingUpdaters, flags, updater, fn;
  4239. attr = attr || this.attr;
  4240. pendingUpdaters = attr.pendingUpdaters;
  4241. // If updaters set sprite attributes that trigger other updaters,
  4242. // those updaters are not called right away, but wait until all current
  4243. // updaters are called (till the next do/while loop iteration).
  4244. me.callUpdaters = Ext.emptyFn;
  4245. // Hide class method from the instance.
  4246. do {
  4247. any = false;
  4248. for (updater in pendingUpdaters) {
  4249. any = true;
  4250. flags = pendingUpdaters[updater];
  4251. delete pendingUpdaters[updater];
  4252. fn = updaters[updater];
  4253. if (typeof fn === 'string') {
  4254. fn = me[fn];
  4255. }
  4256. if (fn) {
  4257. fn.call(me, attr, flags);
  4258. }
  4259. }
  4260. dirty = dirty || any;
  4261. } while (any);
  4262. delete me.callUpdaters;
  4263. // Restore class method.
  4264. if (dirty) {
  4265. me.setDirty(true);
  4266. }
  4267. },
  4268. /**
  4269. * @private
  4270. */
  4271. callUpdater: function(attr, updater, triggers) {
  4272. this.scheduleUpdater(attr, updater, triggers);
  4273. this.callUpdaters(attr);
  4274. },
  4275. /**
  4276. * @private
  4277. * Schedules specified updaters to be called.
  4278. * Updaters are called implicitly as a result of a change to sprite attributes.
  4279. * But sometimes it may be required to call an updater without setting an attribute,
  4280. * and without messing up the updater call order (by calling the updater immediately).
  4281. * For example:
  4282. *
  4283. * updaters: {
  4284. * onDataX: function (attr) {
  4285. * this.processDataX();
  4286. * // Process data Y every time data X is processed.
  4287. * // Call the onDataY updater as if changes to dataY attribute itself
  4288. * // triggered the update.
  4289. * this.scheduleUpdaters(attr, {onDataY: ['dataY']});
  4290. * // Alternatively:
  4291. * // this.scheduleUpdaters(attr, ['onDataY'], ['dataY']);
  4292. * }
  4293. * }
  4294. *
  4295. * @param {Object} attr The attributes object (not necesseraly of a sprite,
  4296. * but of its instance).
  4297. * @param {Object/String[]} updaters A map of updaters to be called to attributes
  4298. * that triggered the update.
  4299. * @param {String[]} [triggers] Attributes that triggered the update. An optional parameter.
  4300. * If used, the `updaters` parameter will be treated as an array of updaters to be called.
  4301. */
  4302. scheduleUpdaters: function(attr, updaters, triggers) {
  4303. var updater, i, ln;
  4304. attr = attr || this.attr;
  4305. if (triggers) {
  4306. for (i = 0 , ln = updaters.length; i < ln; i++) {
  4307. updater = updaters[i];
  4308. this.scheduleUpdater(attr, updater, triggers);
  4309. }
  4310. } else {
  4311. for (updater in updaters) {
  4312. triggers = updaters[updater];
  4313. this.scheduleUpdater(attr, updater, triggers);
  4314. }
  4315. }
  4316. },
  4317. /**
  4318. * @private
  4319. * @param attr {Object} The attributes object (not necesseraly of a sprite,
  4320. * but of its instance).
  4321. * @param updater {String} Updater to be called.
  4322. * @param {String[]} [triggers] Attributes that triggered the update.
  4323. */
  4324. scheduleUpdater: function(attr, updater, triggers) {
  4325. var pendingUpdaters;
  4326. triggers = triggers || [];
  4327. attr = attr || this.attr;
  4328. pendingUpdaters = attr.pendingUpdaters;
  4329. if (updater in pendingUpdaters) {
  4330. if (triggers.length) {
  4331. pendingUpdaters[updater] = Ext.Array.merge(pendingUpdaters[updater], triggers);
  4332. }
  4333. } else {
  4334. pendingUpdaters[updater] = triggers;
  4335. }
  4336. },
  4337. /**
  4338. * Set attributes of the sprite.
  4339. * By default only the attributes that have processors will be set
  4340. * and all other attributes will be filtered out as a result of the
  4341. * normalization process.
  4342. * The normalization process can be skipped. In that case all the given
  4343. * attributes will be set unprocessed. This will result in better
  4344. * performance, but might also pollute the sprite's attributes with
  4345. * unwanted attributes or attributes with invalid values, if one is not
  4346. * careful. See also {@link #setAttributesBypassingNormalization}.
  4347. * If normalization is skipped, one may also chose to avoid copying
  4348. * the given object. This may result in even better performance, but
  4349. * only in cases where most of the attributes have values that are
  4350. * different from the old values, because copying additionally checks
  4351. * if the value has changed.
  4352. *
  4353. * @param {Object} changes The content of the change.
  4354. * @param {Boolean} [bypassNormalization] `true` to avoid normalization of the given changes.
  4355. * @param {Boolean} [avoidCopy] `true` to avoid copying the `changes` object.
  4356. * `bypassNormalization` should also be `true`. The content of object may be destroyed.
  4357. */
  4358. setAttributes: function(changes, bypassNormalization, avoidCopy) {
  4359. var me = this,
  4360. changesToPush;
  4361. //<debug>
  4362. if (me.destroyed) {
  4363. Ext.Error.raise("Setting attributes of a destroyed sprite.");
  4364. }
  4365. //</debug>
  4366. if (bypassNormalization) {
  4367. if (avoidCopy) {
  4368. changesToPush = changes;
  4369. } else {
  4370. changesToPush = Ext.apply({}, changes);
  4371. }
  4372. } else {
  4373. changesToPush = me.self.def.normalize(changes);
  4374. }
  4375. me.modifiers.target.pushDown(me.attr, changesToPush);
  4376. },
  4377. /**
  4378. * Set attributes of the sprite, assuming the names and values have already been
  4379. * normalized.
  4380. *
  4381. * @deprecated 6.5.0 Use setAttributes directly with bypassNormalization argument being `true`.
  4382. * @param {Object} changes The content of the change.
  4383. * @param {Boolean} [avoidCopy] `true` to avoid copying the `changes` object.
  4384. * The content of object may be destroyed.
  4385. */
  4386. setAttributesBypassingNormalization: function(changes, avoidCopy) {
  4387. return this.setAttributes(changes, true, avoidCopy);
  4388. },
  4389. /**
  4390. * @private
  4391. */
  4392. bboxUpdater: function(attr) {
  4393. var hasRotation = attr.rotationRads !== 0,
  4394. hasScaling = attr.scalingX !== 1 || attr.scalingY !== 1,
  4395. noRotationCenter = attr.rotationCenterX === null || attr.rotationCenterY === null,
  4396. noScalingCenter = attr.scalingCenterX === null || attr.scalingCenterY === null;
  4397. // 'bbox' is not a standard attribute (in the sense that it doesn't have
  4398. // a processor = not explicitly declared and cannot be set by a user)
  4399. // and is calculated automatically by the 'getBBox' method.
  4400. // The 'bbox' attribute is created by the 'prepareAttributes' method
  4401. // of the Target modifier at construction time.
  4402. // Both plain and tranformed bounding boxes need to be updated.
  4403. // Mark them as such below.
  4404. attr.bbox.plain.dirty = true;
  4405. // updated by the 'updatePlainBBox' method
  4406. // Before transformed bounding box can be updated,
  4407. // we must ensure that we have correct forward and inverse
  4408. // transformation matrices (which are also created by the Target modifier),
  4409. // so that they reflect the current state of the scaling, rotation
  4410. // and other transformation attributes.
  4411. // The 'applyTransformations' method does just that.
  4412. // The 'dirtyTransform' flag (another implicit attribute)
  4413. // is set to true when any of the transformation attributes change,
  4414. // to let us know that transformation matrices need to be updated.
  4415. attr.bbox.transform.dirty = true;
  4416. // updated by the 'updateTransformedBBox' method
  4417. if (hasRotation && noRotationCenter || hasScaling && noScalingCenter) {
  4418. this.scheduleUpdater(attr, 'transform');
  4419. }
  4420. },
  4421. /**
  4422. * Returns the bounding box for the given Sprite as calculated with the Canvas engine.
  4423. *
  4424. * @param {Boolean} [isWithoutTransform] Whether to calculate the bounding box
  4425. * with the current transforms or not.
  4426. */
  4427. getBBox: function(isWithoutTransform) {
  4428. var me = this,
  4429. attr = me.attr,
  4430. bbox = attr.bbox,
  4431. plain = bbox.plain,
  4432. transform = bbox.transform;
  4433. if (plain.dirty) {
  4434. me.updatePlainBBox(plain);
  4435. plain.dirty = false;
  4436. }
  4437. if (!isWithoutTransform) {
  4438. // If tranformations are to be applied ('dirtyTransform' is true),
  4439. // then this will itself call the 'getBBox' method
  4440. // to get the plain untransformed bbox and calculate its center.
  4441. me.applyTransformations();
  4442. if (transform.dirty) {
  4443. me.updateTransformedBBox(transform, plain);
  4444. transform.dirty = false;
  4445. }
  4446. return transform;
  4447. }
  4448. return plain;
  4449. },
  4450. /**
  4451. * @method
  4452. * @protected
  4453. * Subclass will fill the plain object with `x`, `y`, `width`, `height` information
  4454. * of the plain bounding box of this sprite.
  4455. *
  4456. * @param {Object} plain Target object.
  4457. */
  4458. updatePlainBBox: Ext.emptyFn,
  4459. /**
  4460. * @protected
  4461. * Subclass will fill the plain object with `x`, `y`, `width`, `height` information
  4462. * of the transformed bounding box of this sprite.
  4463. *
  4464. * @param {Object} transform Target object (transformed bounding box) to populate.
  4465. * @param {Object} plain Untransformed bounding box.
  4466. */
  4467. updateTransformedBBox: function(transform, plain) {
  4468. this.attr.matrix.transformBBox(plain, 0, transform);
  4469. },
  4470. /**
  4471. * Subclass can rewrite this function to gain better performance.
  4472. * @param {Boolean} isWithoutTransform
  4473. * @return {Array}
  4474. */
  4475. getBBoxCenter: function(isWithoutTransform) {
  4476. var bbox = this.getBBox(isWithoutTransform);
  4477. if (bbox) {
  4478. return [
  4479. bbox.x + bbox.width * 0.5,
  4480. bbox.y + bbox.height * 0.5
  4481. ];
  4482. } else {
  4483. return [
  4484. 0,
  4485. 0
  4486. ];
  4487. }
  4488. },
  4489. /**
  4490. * Hide the sprite.
  4491. * @return {Ext.draw.sprite.Sprite} this
  4492. * @chainable
  4493. */
  4494. hide: function() {
  4495. this.attr.hidden = true;
  4496. this.setDirty(true);
  4497. return this;
  4498. },
  4499. /**
  4500. * Show the sprite.
  4501. * @return {Ext.draw.sprite.Sprite} this
  4502. * @chainable
  4503. */
  4504. show: function() {
  4505. this.attr.hidden = false;
  4506. this.setDirty(true);
  4507. return this;
  4508. },
  4509. /**
  4510. * Applies sprite's attributes to the given context.
  4511. * @param {Object} ctx Context to apply sprite's attributes to.
  4512. * @param {Array} rect The rect of the context to be affected by gradients.
  4513. */
  4514. useAttributes: function(ctx, rect) {
  4515. // Always (force) apply transformation to sprite instances,
  4516. // even if their 'dirtyTransform' flag is false.
  4517. // The 'dirtyTransform' flag of an instance may never be set to 'true', as the
  4518. // 'transform' updater won't ever be called for sprite instances that have
  4519. // the same transform attributes as their template, because there's nothing to update
  4520. // (an instance is simply a prototype chained template's 'attr' object, that only
  4521. // has own properties for attributes whose values are different).
  4522. // Making the modifier recognize transform attributes set on sprite instances
  4523. // (see Ext.draw.modifier.Modifier's 'pushDown' method, where attributes with
  4524. // same values are removed from the 'changes' object) and making sure their 'dirtyTransform'
  4525. // flag is set to 'true' is not a correct solution here, because of the way instances
  4526. // are rendered (see Ext.draw.sprite.Instancing's 'render' method) - there is no way
  4527. // an instance wounldn't want its 'applyTransformations' method called.
  4528. this.applyTransformations(this.isSpriteInstance);
  4529. // eslint-disable-next-line vars-on-top
  4530. var attr = this.attr,
  4531. canvasAttributes = attr.canvasAttributes,
  4532. strokeStyle = canvasAttributes.strokeStyle,
  4533. fillStyle = canvasAttributes.fillStyle,
  4534. lineDash = canvasAttributes.lineDash,
  4535. lineDashOffset = canvasAttributes.lineDashOffset,
  4536. id;
  4537. if (strokeStyle) {
  4538. if (strokeStyle.isGradient) {
  4539. ctx.strokeStyle = 'black';
  4540. ctx.strokeGradient = strokeStyle;
  4541. } else {
  4542. ctx.strokeGradient = false;
  4543. }
  4544. }
  4545. if (fillStyle) {
  4546. if (fillStyle.isGradient) {
  4547. ctx.fillStyle = 'black';
  4548. ctx.fillGradient = fillStyle;
  4549. } else {
  4550. ctx.fillGradient = false;
  4551. }
  4552. }
  4553. if (lineDash) {
  4554. ctx.setLineDash(lineDash);
  4555. }
  4556. // Only set lineDashOffset to contexts that support the property (excludes VML).
  4557. if (Ext.isNumber(lineDashOffset) && Ext.isNumber(ctx.lineDashOffset)) {
  4558. ctx.lineDashOffset = lineDashOffset;
  4559. }
  4560. for (id in canvasAttributes) {
  4561. if (canvasAttributes[id] !== undefined && canvasAttributes[id] !== ctx[id]) {
  4562. ctx[id] = canvasAttributes[id];
  4563. }
  4564. }
  4565. this.setGradientBBox(ctx, rect);
  4566. },
  4567. setGradientBBox: function(ctx, rect) {
  4568. var attr = this.attr;
  4569. if (attr.constrainGradients) {
  4570. ctx.setGradientBBox({
  4571. x: rect[0],
  4572. y: rect[1],
  4573. width: rect[2],
  4574. height: rect[3]
  4575. });
  4576. } else {
  4577. ctx.setGradientBBox(this.getBBox(attr.transformFillStroke));
  4578. }
  4579. },
  4580. /**
  4581. * @private
  4582. *
  4583. * Calculates forward and inverse transform matrices from sprite's attributes.
  4584. * Transformations are applied in the following order: Scaling, Rotation, Translation.
  4585. * @param {Boolean} [force=false] Forces recalculation of transform matrices even when
  4586. * sprite's transform attributes supposedly haven't changed.
  4587. */
  4588. applyTransformations: function(force) {
  4589. if (!force && !this.attr.dirtyTransform) {
  4590. return;
  4591. }
  4592. // eslint-disable-next-line vars-on-top
  4593. var me = this,
  4594. attr = me.attr,
  4595. center = me.getBBoxCenter(true),
  4596. centerX = center[0],
  4597. centerY = center[1],
  4598. tx = attr.translationX,
  4599. ty = attr.translationY,
  4600. sx = attr.scalingX,
  4601. sy = attr.scalingY === null ? attr.scalingX : attr.scalingY,
  4602. scx = attr.scalingCenterX === null ? centerX : attr.scalingCenterX,
  4603. scy = attr.scalingCenterY === null ? centerY : attr.scalingCenterY,
  4604. rad = attr.rotationRads,
  4605. rcx = attr.rotationCenterX === null ? centerX : attr.rotationCenterX,
  4606. rcy = attr.rotationCenterY === null ? centerY : attr.rotationCenterY,
  4607. cos = Math.cos(rad),
  4608. sin = Math.sin(rad),
  4609. tx_4, ty_4;
  4610. if (sx === 1 && sy === 1) {
  4611. scx = 0;
  4612. scy = 0;
  4613. }
  4614. if (rad === 0) {
  4615. rcx = 0;
  4616. rcy = 0;
  4617. }
  4618. // Translation component after steps 1-4 (see below).
  4619. // Saving it here to prevent double calculation.
  4620. tx_4 = scx * (1 - sx) - rcx;
  4621. ty_4 = scy * (1 - sy) - rcy;
  4622. /* eslint-disable max-len */
  4623. // The matrix below is a result of:
  4624. // (7) (6) (5) (4) (3) (2) (1)
  4625. // | 1 0 tx | | 1 0 rcx | | cos -sin 0 | | 1 0 -rcx | | 1 0 scx | | sx 0 0 | | 1 0 -scx |
  4626. // | 0 1 ty | * | 0 1 rcy | * | sin cos 0 | * | 0 1 -rcy | * | 0 1 scy | * | 0 sy 0 | * | 0 1 -scy |
  4627. // | 0 0 1 | | 0 0 1 | | 0 0 1 | | 0 0 1 | | 0 0 1 | | 0 0 0 | | 0 0 1 |
  4628. /* eslint-enable max-len */
  4629. attr.matrix.elements = [
  4630. cos * sx,
  4631. sin * sx,
  4632. -sin * sy,
  4633. cos * sy,
  4634. cos * tx_4 - sin * ty_4 + rcx + tx,
  4635. sin * tx_4 + cos * ty_4 + rcy + ty
  4636. ];
  4637. attr.matrix.inverse(attr.inverseMatrix);
  4638. attr.dirtyTransform = false;
  4639. attr.bbox.transform.dirty = true;
  4640. },
  4641. /**
  4642. * Pre-multiplies the current transformation matrix of a sprite with the given matrix.
  4643. * If `isSplit` parameter is `true`, the resulting matrix is also split into
  4644. * individual components (scaling, rotation, translation) and corresponding sprite
  4645. * attributes are updated. The shearing component is not extracted.
  4646. * Note, that transformation attributes work as if transformations are applied to the
  4647. * local coordinate system of a sprite, while matrix transformations transform
  4648. * the global coordinate space or the surface grid.
  4649. * Since the `transform` method returns the sprite itself, calls to the method
  4650. * can be chained. And if updating sprite transformation attributes is desired,
  4651. * it can be achieved by setting the `isSplit` parameter of the last call to `true`.
  4652. * For example:
  4653. *
  4654. * sprite.transform(matrixA).transform(matrixB).transform(matrixC, true);
  4655. *
  4656. * See also: {@link #setTransform}
  4657. *
  4658. * @param {Ext.draw.Matrix/Number[]} matrix A transformation matrix or array of its elements.
  4659. * @param {Boolean} [isSplit=false] If 'true', transformation attributes are updated.
  4660. * @return {Ext.draw.sprite.Sprite} This sprite.
  4661. */
  4662. transform: function(matrix, isSplit) {
  4663. var attr = this.attr,
  4664. spriteMatrix = attr.matrix,
  4665. elements;
  4666. if (matrix && matrix.isMatrix) {
  4667. elements = matrix.elements;
  4668. } else {
  4669. elements = matrix;
  4670. }
  4671. //<debug>
  4672. if (!(Ext.isArray(elements) && elements.length === 6)) {
  4673. Ext.raise("An instance of Ext.draw.Matrix or an array of 6 numbers is expected.");
  4674. }
  4675. //</debug>
  4676. spriteMatrix.prepend.apply(spriteMatrix, elements.slice());
  4677. spriteMatrix.inverse(attr.inverseMatrix);
  4678. if (isSplit) {
  4679. this.updateTransformAttributes();
  4680. }
  4681. attr.dirtyTransform = false;
  4682. attr.bbox.transform.dirty = true;
  4683. this.setDirty(true);
  4684. return this;
  4685. },
  4686. /**
  4687. * @private
  4688. */
  4689. updateTransformAttributes: function() {
  4690. var attr = this.attr,
  4691. split = attr.matrix.split();
  4692. attr.rotationRads = split.rotate;
  4693. attr.rotationCenterX = 0;
  4694. attr.rotationCenterY = 0;
  4695. attr.scalingX = split.scaleX;
  4696. attr.scalingY = split.scaleY;
  4697. attr.scalingCenterX = 0;
  4698. attr.scalingCenterY = 0;
  4699. attr.translationX = split.translateX;
  4700. attr.translationY = split.translateY;
  4701. },
  4702. /**
  4703. * Resets current transformation matrix of a sprite to the identify matrix.
  4704. * @param {Boolean} [isSplit=false] If 'true', transformation attributes are updated.
  4705. * @return {Ext.draw.sprite.Sprite} This sprite.
  4706. */
  4707. resetTransform: function(isSplit) {
  4708. var attr = this.attr;
  4709. attr.matrix.reset();
  4710. attr.inverseMatrix.reset();
  4711. if (!isSplit) {
  4712. this.updateTransformAttributes();
  4713. }
  4714. attr.dirtyTransform = false;
  4715. attr.bbox.transform.dirty = true;
  4716. this.setDirty(true);
  4717. return this;
  4718. },
  4719. /**
  4720. * Resets current transformation matrix of a sprite to the identify matrix
  4721. * and pre-multiplies it with the given matrix.
  4722. * This is effectively the same as calling {@link #resetTransform},
  4723. * followed by {@link #transform} with the same arguments.
  4724. *
  4725. * See also: {@link #transform}
  4726. *
  4727. * var drawContainer = new Ext.draw.Container({
  4728. * renderTo: Ext.getBody(),
  4729. * width: 380,
  4730. * height: 380,
  4731. * sprites: [{
  4732. * type: 'rect',
  4733. * width: 100,
  4734. * height: 100,
  4735. * fillStyle: 'red'
  4736. * }]
  4737. * });
  4738. *
  4739. * var main = drawContainer.getSurface();
  4740. * var rect = main.getItems()[0];
  4741. *
  4742. * var m = new Ext.draw.Matrix().rotate(Math.PI, 100, 100);
  4743. *
  4744. * rect.setTransform(m);
  4745. * main.renderFrame();
  4746. *
  4747. * There may be times where the transformation you need to apply cannot easily be
  4748. * accomplished using the sprite’s convenience transform methods. Or, you may want
  4749. * to pass a matrix directly to the sprite in order to set a transformation. The
  4750. * `setTransform` method allows for this sort of advanced usage as well. The
  4751. * following tables show each transformation matrix used when applying
  4752. * transformations to a sprite.
  4753. *
  4754. * ### Translate
  4755. * <table style="text-align: center;">
  4756. * <tr>
  4757. * <td style="font-weight: normal;">1</td>
  4758. * <td style="font-weight: normal;">0</td>
  4759. * <td style="font-weight: normal;">tx</td>
  4760. * </tr>
  4761. * <tr>
  4762. * <td>0</td>
  4763. * <td>1</td>
  4764. * <td>ty</td>
  4765. * </tr>
  4766. * <tr>
  4767. * <td>0</td>
  4768. * <td>0</td>
  4769. * <td>1</td>
  4770. * </tr>
  4771. * </table>
  4772. *
  4773. * ### Rotate (θ is the angle of rotation)
  4774. * <table style="text-align: center;">
  4775. * <tr>
  4776. * <td style="font-weight: normal;">cos(θ)</td>
  4777. * <td style="font-weight: normal;">-sin(θ)</td>
  4778. * <td style="font-weight: normal;">0</td>
  4779. * </tr>
  4780. * <tr>
  4781. * <td>0</td>
  4782. * <td>cos(θ)</td>
  4783. * <td>0</td>
  4784. * </tr>
  4785. * <tr>
  4786. * <td>0</td>
  4787. * <td>0</td>
  4788. * <td>1</td>
  4789. * </tr>
  4790. * </table>
  4791. *
  4792. * ### Scale
  4793. * <table style="text-align: center;">
  4794. * <tr>
  4795. * <td style="font-weight: normal;">sx</td>
  4796. * <td style="font-weight: normal;">0</td>
  4797. * <td style="font-weight: normal;">0</td>
  4798. * </tr>
  4799. * <tr>
  4800. * <td>0</td>
  4801. * <td>cos(θ)</td>
  4802. * <td>0</td>
  4803. * </tr>
  4804. * <tr>
  4805. * <td>0</td>
  4806. * <td>0</td>
  4807. * <td>1</td>
  4808. * </tr>
  4809. * </table>
  4810. *
  4811. * ### Shear X _(λ is the distance on the x axis to shear by)_
  4812. * <table style="text-align: center;">
  4813. * <tr>
  4814. * <td style="font-weight: normal;">1</td>
  4815. * <td style="font-weight: normal;">λx</td>
  4816. * <td style="font-weight: normal;">0</td>
  4817. * </tr>
  4818. * <tr>
  4819. * <td>0</td>
  4820. * <td>1</td>
  4821. * <td>0</td>
  4822. * </tr>
  4823. * <tr>
  4824. * <td>0</td>
  4825. * <td>0</td>
  4826. * <td>1</td>
  4827. * </tr>
  4828. * </table>
  4829. *
  4830. * ### Shear Y (λ is the distance on the y axis to shear by)
  4831. * <table style="text-align: center;">
  4832. * <tr>
  4833. * <td style="font-weight: normal;">1</td>
  4834. * <td style="font-weight: normal;">0</td>
  4835. * <td style="font-weight: normal;">0</td>
  4836. * </tr>
  4837. * <tr>
  4838. * <td>λy</td>
  4839. * <td>1</td>
  4840. * <td>0</td>
  4841. * </tr>
  4842. * <tr>
  4843. * <td>0</td>
  4844. * <td>0</td>
  4845. * <td>1</td>
  4846. * </tr>
  4847. * </table>
  4848. *
  4849. * ### Skew X (θ is the angle to skew by)
  4850. * <table style="text-align: center;">
  4851. * <tr>
  4852. * <td style="font-weight: normal;">1</td>
  4853. * <td style="font-weight: normal;">tan(θ)</td>
  4854. * <td style="font-weight: normal;">0</td>
  4855. * </tr>
  4856. * <tr>
  4857. * <td>0</td>
  4858. * <td>1</td>
  4859. * <td>0</td>
  4860. * </tr>
  4861. * <tr>
  4862. * <td>0</td>
  4863. * <td>0</td>
  4864. * <td>1</td>
  4865. * </tr>
  4866. * </table>
  4867. *
  4868. * ### Skew Y (θ is the angle to skew by)
  4869. * <table style="text-align: center;">
  4870. * <tr>
  4871. * <td style="font-weight: normal;">1</td>
  4872. * <td style="font-weight: normal;">0</td>
  4873. * <td style="font-weight: normal;">0</td>
  4874. * </tr>
  4875. * <tr>
  4876. * <td>tan(θ)</td>
  4877. * <td>1</td>
  4878. * <td>0</td>
  4879. * </tr>
  4880. * <tr>
  4881. * <td>0</td>
  4882. * <td>0</td>
  4883. * <td>1</td>
  4884. * </tr>
  4885. * </table>
  4886. *
  4887. * Multiplying matrices for translation, rotation, scaling, and shearing / skewing
  4888. * any number of times in the desired order produces a single matrix for a composite
  4889. * transformation. You can use the product as a value for the `setTransform`method
  4890. * of a sprite:
  4891. *
  4892. * mySprite.setTransform([a, b, c, d, e, f]);
  4893. *
  4894. * Where `a`, `b`, `c`, `d`, `e`, `f` are numeric values that correspond to the
  4895. * following transformation matrix components:
  4896. *
  4897. * <table style="text-align: center;">
  4898. * <tr>
  4899. * <td style="font-weight: normal;">a</td>
  4900. * <td style="font-weight: normal;">c</td>
  4901. * <td style="font-weight: normal;">e</td>
  4902. * </tr>
  4903. * <tr>
  4904. * <td>b</td>
  4905. * <td>d</td>
  4906. * <td>f</td>
  4907. * </tr>
  4908. * <tr>
  4909. * <td>0</td>
  4910. * <td>0</td>
  4911. * <td>1</td>
  4912. * </tr>
  4913. * </table>
  4914. *
  4915. * @param {Ext.draw.Matrix/Number[]} matrix The transformation matrix to apply or its
  4916. * raw elements as an array.
  4917. * @param {Boolean} [isSplit=false] If `true`, transformation attributes are updated.
  4918. * @return {Ext.draw.sprite.Sprite} This sprite.
  4919. */
  4920. setTransform: function(matrix, isSplit) {
  4921. this.resetTransform(true);
  4922. this.transform.call(this, matrix, isSplit);
  4923. return this;
  4924. },
  4925. /**
  4926. * @method
  4927. * Called before rendering.
  4928. */
  4929. preRender: Ext.emptyFn,
  4930. /**
  4931. * @method
  4932. * This is where the actual sprite rendering happens by calling `ctx` methods.
  4933. * @param {Ext.draw.Surface} surface A draw container surface.
  4934. * @param {CanvasRenderingContext2D} ctx A context object that is API compatible with the native
  4935. * [CanvasRenderingContext2D](https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D).
  4936. * @param {Number[]} surfaceClipRect The clip rect: [left, top, width, height].
  4937. * Not to be confused with the `surface.getRect()`, which represents the location
  4938. * and size of the surface in a draw container, in draw container coordinates.
  4939. * The clip rect on the other hand represents the portion of the surface that is being
  4940. * rendered, in surface coordinates.
  4941. *
  4942. * @return {*} returns `false` to stop rendering in this frame.
  4943. * All the sprites that haven't been rendered will have their dirty flag untouched.
  4944. */
  4945. render: Ext.emptyFn,
  4946. //<debug>
  4947. /**
  4948. * @private
  4949. * Renders the bounding box of transformed sprite.
  4950. */
  4951. renderBBox: function(surface, ctx) {
  4952. var bbox = this.getBBox();
  4953. ctx.beginPath();
  4954. ctx.moveTo(bbox.x, bbox.y);
  4955. ctx.lineTo(bbox.x + bbox.width, bbox.y);
  4956. ctx.lineTo(bbox.x + bbox.width, bbox.y + bbox.height);
  4957. ctx.lineTo(bbox.x, bbox.y + bbox.height);
  4958. ctx.closePath();
  4959. ctx.strokeStyle = 'red';
  4960. ctx.strokeOpacity = 1;
  4961. ctx.lineWidth = 0.5;
  4962. ctx.stroke();
  4963. },
  4964. //</debug>
  4965. /**
  4966. * Performs a hit test on the sprite.
  4967. * @param {Array} point A two-item array containing x and y coordinates of the point.
  4968. * @param {Object} options Hit testing options.
  4969. * @return {Object} A hit result object that contains more information about what
  4970. * exactly was hit or null if nothing was hit.
  4971. */
  4972. hitTest: function(point, options) {
  4973. var x, y, bbox, isBBoxHit;
  4974. // Meant to be overridden in subclasses for more precise hit testing.
  4975. // This version doesn't take any options and simply hit tests sprite's
  4976. // bounding box, if the sprite is visible.
  4977. if (this.isVisible()) {
  4978. x = point[0];
  4979. y = point[1];
  4980. bbox = this.getBBox();
  4981. isBBoxHit = bbox && x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height);
  4982. if (isBBoxHit) {
  4983. return {
  4984. sprite: this
  4985. };
  4986. }
  4987. }
  4988. return null;
  4989. },
  4990. /**
  4991. * @private
  4992. * Checks if the sprite can be seen.
  4993. * This includes the `hidden` attribute check, alpha/opacity checks,
  4994. * fill/stroke color checks and surface/parent checks.
  4995. * The method doesn't check if the sprite is off-screen.
  4996. * @return {Boolean} Returns `true`, if the sprite can be seen.
  4997. */
  4998. isVisible: function() {
  4999. var attr = this.attr,
  5000. parent = this.getParent(),
  5001. hasParent = parent && (parent.isSurface || parent.isVisible()),
  5002. isSeen = hasParent && !attr.hidden && attr.globalAlpha,
  5003. none1 = Ext.util.Color.NONE,
  5004. none2 = Ext.util.Color.RGBA_NONE,
  5005. hasFill = attr.fillOpacity && attr.fillStyle !== none1 && attr.fillStyle !== none2,
  5006. hasStroke = attr.strokeOpacity && attr.strokeStyle !== none1 && attr.strokeStyle !== none2,
  5007. result = isSeen && (hasFill || hasStroke);
  5008. return !!result;
  5009. },
  5010. repaint: function() {
  5011. var surface = this.getSurface();
  5012. if (surface) {
  5013. surface.renderFrame();
  5014. }
  5015. },
  5016. /**
  5017. * Removes this sprite from its surface.
  5018. * The sprite itself is not destroyed.
  5019. * @return {Ext.draw.sprite.Sprite} Returns the removed sprite or `null` otherwise.
  5020. */
  5021. remove: function() {
  5022. var surface = this.getSurface();
  5023. if (surface && surface.isSurface) {
  5024. return surface.remove(this);
  5025. }
  5026. return null;
  5027. },
  5028. /**
  5029. * Removes the sprite and clears all listeners.
  5030. */
  5031. destroy: function() {
  5032. var me = this,
  5033. modifier = me.modifiers.target,
  5034. currentModifier;
  5035. while (modifier) {
  5036. currentModifier = modifier;
  5037. modifier = modifier._lower;
  5038. currentModifier.destroy();
  5039. }
  5040. delete me.attr;
  5041. me.remove();
  5042. if (me.fireEvent('beforedestroy', me) !== false) {
  5043. me.fireEvent('destroy', me);
  5044. }
  5045. me.callParent();
  5046. }
  5047. }, function() {
  5048. // onClassCreated
  5049. // Create one AttributeDefinition instance per sprite class when a class is created
  5050. // and replace the `def` config with the instance that was created using that config.
  5051. // Here we only create an AttributeDefinition instance for the base Sprite class,
  5052. // attribute definitions for subclasses are created inside onClassExtended method.
  5053. this.def = new Ext.draw.sprite.AttributeDefinition(this.def);
  5054. this.def.spriteClass = this;
  5055. });
  5056. /**
  5057. * Class representing a path.
  5058. * Designed to be compatible with [CanvasPathMethods](http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#canvaspathmethods)
  5059. * and will hopefully be replaced by the browsers' implementation of the Path object.
  5060. */
  5061. Ext.define('Ext.draw.Path', {
  5062. requires: [
  5063. 'Ext.draw.Draw'
  5064. ],
  5065. statics: {
  5066. pathRe: /,?([achlmqrstvxz]),?/gi,
  5067. pathRe2: /-/gi,
  5068. pathSplitRe: /\s|,/g
  5069. },
  5070. svgString: '',
  5071. /**
  5072. * Create a path from pathString.
  5073. * @constructor
  5074. * @param {String} pathString
  5075. */
  5076. constructor: function(pathString) {
  5077. var me = this;
  5078. me.commands = [];
  5079. // Stores command letters from the SVG path data ('d' attribute).
  5080. me.params = [];
  5081. // Stores command parameters from the SVG path data.
  5082. // All command parameters are actually point coordinates as the only commands used
  5083. // are the M, L, C, Z. This makes path transformations and hit testing easier.
  5084. // Arcs are approximated using cubic Bezier curves, H and S commands are translated
  5085. // to L commands and relative commands are translated to their absolute versions.
  5086. me.cursor = null;
  5087. me.startX = 0;
  5088. me.startY = 0;
  5089. if (pathString) {
  5090. me.fromSvgString(pathString);
  5091. }
  5092. },
  5093. /**
  5094. * Clear the path.
  5095. */
  5096. clear: function() {
  5097. var me = this;
  5098. me.params.length = 0;
  5099. me.commands.length = 0;
  5100. me.cursor = null;
  5101. me.startX = 0;
  5102. me.startY = 0;
  5103. me.dirt();
  5104. },
  5105. /**
  5106. * @private
  5107. */
  5108. dirt: function() {
  5109. this.svgString = '';
  5110. },
  5111. /**
  5112. * Move to a position.
  5113. * @param {Number} x
  5114. * @param {Number} y
  5115. */
  5116. moveTo: function(x, y) {
  5117. var me = this;
  5118. if (!me.cursor) {
  5119. me.cursor = [
  5120. x,
  5121. y
  5122. ];
  5123. }
  5124. me.params.push(x, y);
  5125. me.commands.push('M');
  5126. me.startX = x;
  5127. me.startY = y;
  5128. me.cursor[0] = x;
  5129. me.cursor[1] = y;
  5130. me.dirt();
  5131. },
  5132. /**
  5133. * A straight line to a position.
  5134. * @param {Number} x
  5135. * @param {Number} y
  5136. */
  5137. lineTo: function(x, y) {
  5138. var me = this;
  5139. if (!me.cursor) {
  5140. me.cursor = [
  5141. x,
  5142. y
  5143. ];
  5144. me.params.push(x, y);
  5145. me.commands.push('M');
  5146. } else {
  5147. me.params.push(x, y);
  5148. me.commands.push('L');
  5149. }
  5150. me.cursor[0] = x;
  5151. me.cursor[1] = y;
  5152. me.dirt();
  5153. },
  5154. /**
  5155. * A cubic bezier curve to a position.
  5156. * @param {Number} cx1
  5157. * @param {Number} cy1
  5158. * @param {Number} cx2
  5159. * @param {Number} cy2
  5160. * @param {Number} x
  5161. * @param {Number} y
  5162. */
  5163. bezierCurveTo: function(cx1, cy1, cx2, cy2, x, y) {
  5164. var me = this;
  5165. if (!me.cursor) {
  5166. me.moveTo(cx1, cy1);
  5167. }
  5168. me.params.push(cx1, cy1, cx2, cy2, x, y);
  5169. me.commands.push('C');
  5170. me.cursor[0] = x;
  5171. me.cursor[1] = y;
  5172. me.dirt();
  5173. },
  5174. /**
  5175. * A quadratic bezier curve to a position.
  5176. * @param {Number} cx
  5177. * @param {Number} cy
  5178. * @param {Number} x
  5179. * @param {Number} y
  5180. */
  5181. quadraticCurveTo: function(cx, cy, x, y) {
  5182. var me = this;
  5183. if (!me.cursor) {
  5184. me.moveTo(cx, cy);
  5185. }
  5186. me.bezierCurveTo((2 * cx + me.cursor[0]) / 3, (2 * cy + me.cursor[1]) / 3, (2 * cx + x) / 3, (2 * cy + y) / 3, x, y);
  5187. },
  5188. /**
  5189. * Close this path with a straight line.
  5190. */
  5191. closePath: function() {
  5192. var me = this;
  5193. if (me.cursor) {
  5194. me.cursor = null;
  5195. me.commands.push('Z');
  5196. me.dirt();
  5197. }
  5198. },
  5199. /**
  5200. * Create a elliptic arc curve compatible with SVG's arc to instruction.
  5201. *
  5202. * The curve start from (`x1`, `y1`) and ends at (`x2`, `y2`). The ellipse
  5203. * has radius `rx` and `ry` and a rotation of `rotation`.
  5204. * @param {Number} x1
  5205. * @param {Number} y1
  5206. * @param {Number} x2
  5207. * @param {Number} y2
  5208. * @param {Number} [rx]
  5209. * @param {Number} [ry]
  5210. * @param {Number} [rotation]
  5211. */
  5212. arcTo: function(x1, y1, x2, y2, rx, ry, rotation) {
  5213. var me = this;
  5214. if (ry === undefined) {
  5215. ry = rx;
  5216. }
  5217. if (rotation === undefined) {
  5218. rotation = 0;
  5219. }
  5220. if (!me.cursor) {
  5221. me.moveTo(x1, y1);
  5222. return;
  5223. }
  5224. if (rx === 0 || ry === 0) {
  5225. me.lineTo(x1, y1);
  5226. return;
  5227. }
  5228. x2 -= x1;
  5229. y2 -= y1;
  5230. // eslint-disable-next-line vars-on-top, one-var
  5231. var x0 = me.cursor[0] - x1,
  5232. y0 = me.cursor[1] - y1,
  5233. area = x2 * y0 - y2 * x0,
  5234. cos, sin, xx, yx, xy, yy,
  5235. l0 = Math.sqrt(x0 * x0 + y0 * y0),
  5236. l2 = Math.sqrt(x2 * x2 + y2 * y2),
  5237. dist, cx, cy, temp;
  5238. // cos rx, -sin ry , x1 - cos rx x1 + ry sin y1
  5239. // sin rx, cos ry, -rx sin x1 + y1 - cos ry y1
  5240. if (area === 0) {
  5241. me.lineTo(x1, y1);
  5242. return;
  5243. }
  5244. if (ry !== rx) {
  5245. cos = Math.cos(rotation);
  5246. sin = Math.sin(rotation);
  5247. xx = cos / rx;
  5248. yx = sin / ry;
  5249. xy = -sin / rx;
  5250. yy = cos / ry;
  5251. temp = xx * x0 + yx * y0;
  5252. y0 = xy * x0 + yy * y0;
  5253. x0 = temp;
  5254. temp = xx * x2 + yx * y2;
  5255. y2 = xy * x2 + yy * y2;
  5256. x2 = temp;
  5257. } else {
  5258. x0 /= rx;
  5259. y0 /= ry;
  5260. x2 /= rx;
  5261. y2 /= ry;
  5262. }
  5263. cx = x0 * l2 + x2 * l0;
  5264. cy = y0 * l2 + y2 * l0;
  5265. dist = 1 / (Math.sin(Math.asin(Math.abs(area) / (l0 * l2)) * 0.5) * Math.sqrt(cx * cx + cy * cy));
  5266. cx *= dist;
  5267. cy *= dist;
  5268. // eslint-disable-next-line vars-on-top, one-var
  5269. var k0 = (cx * x0 + cy * y0) / (x0 * x0 + y0 * y0),
  5270. k2 = (cx * x2 + cy * y2) / (x2 * x2 + y2 * y2),
  5271. cosStart = x0 * k0 - cx,
  5272. sinStart = y0 * k0 - cy,
  5273. cosEnd = x2 * k2 - cx,
  5274. sinEnd = y2 * k2 - cy,
  5275. startAngle = Math.atan2(sinStart, cosStart),
  5276. endAngle = Math.atan2(sinEnd, cosEnd);
  5277. if (area > 0) {
  5278. if (endAngle < startAngle) {
  5279. endAngle += Math.PI * 2;
  5280. }
  5281. } else {
  5282. if (startAngle < endAngle) {
  5283. startAngle += Math.PI * 2;
  5284. }
  5285. }
  5286. if (ry !== rx) {
  5287. cx = cos * cx * rx - sin * cy * ry + x1;
  5288. cy = sin * cy * ry + cos * cy * ry + y1;
  5289. me.lineTo(cos * rx * cosStart - sin * ry * sinStart + cx, sin * rx * cosStart + cos * ry * sinStart + cy);
  5290. me.ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, area < 0);
  5291. } else {
  5292. cx = cx * rx + x1;
  5293. cy = cy * ry + y1;
  5294. me.lineTo(rx * cosStart + cx, ry * sinStart + cy);
  5295. me.ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, area < 0);
  5296. }
  5297. },
  5298. /**
  5299. * Create an elliptic arc.
  5300. *
  5301. * See [the whatwg reference of ellipse](http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-ellipse).
  5302. *
  5303. * @param {Number} cx
  5304. * @param {Number} cy
  5305. * @param {Number} radiusX
  5306. * @param {Number} radiusY
  5307. * @param {Number} rotation
  5308. * @param {Number} startAngle
  5309. * @param {Number} endAngle
  5310. * @param {Number} anticlockwise
  5311. */
  5312. ellipse: function(cx, cy, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise) {
  5313. var me = this,
  5314. params = me.params,
  5315. start = params.length,
  5316. count, temp, i, j;
  5317. if (endAngle - startAngle >= Math.PI * 2) {
  5318. me.ellipse(cx, cy, radiusX, radiusY, rotation, startAngle, startAngle + Math.PI, anticlockwise);
  5319. me.ellipse(cx, cy, radiusX, radiusY, rotation, startAngle + Math.PI, endAngle, anticlockwise);
  5320. return;
  5321. }
  5322. if (!anticlockwise) {
  5323. if (endAngle < startAngle) {
  5324. endAngle += Math.PI * 2;
  5325. }
  5326. count = me.approximateArc(params, cx, cy, radiusX, radiusY, rotation, startAngle, endAngle);
  5327. } else {
  5328. if (startAngle < endAngle) {
  5329. startAngle += Math.PI * 2;
  5330. }
  5331. count = me.approximateArc(params, cx, cy, radiusX, radiusY, rotation, endAngle, startAngle);
  5332. for (i = start , j = params.length - 2; i < j; i += 2 , j -= 2) {
  5333. temp = params[i];
  5334. params[i] = params[j];
  5335. params[j] = temp;
  5336. temp = params[i + 1];
  5337. params[i + 1] = params[j + 1];
  5338. params[j + 1] = temp;
  5339. }
  5340. }
  5341. if (!me.cursor) {
  5342. me.cursor = [
  5343. params[params.length - 2],
  5344. params[params.length - 1]
  5345. ];
  5346. me.commands.push('M');
  5347. } else {
  5348. me.cursor[0] = params[params.length - 2];
  5349. me.cursor[1] = params[params.length - 1];
  5350. me.commands.push('L');
  5351. }
  5352. for (i = 2; i < count; i += 6) {
  5353. me.commands.push('C');
  5354. }
  5355. me.dirt();
  5356. },
  5357. /**
  5358. * Create an circular arc.
  5359. *
  5360. * @param {Number} x
  5361. * @param {Number} y
  5362. * @param {Number} radius
  5363. * @param {Number} startAngle
  5364. * @param {Number} endAngle
  5365. * @param {Number} anticlockwise
  5366. */
  5367. arc: function(x, y, radius, startAngle, endAngle, anticlockwise) {
  5368. this.ellipse(x, y, radius, radius, 0, startAngle, endAngle, anticlockwise);
  5369. },
  5370. /**
  5371. * Draw a rectangle and close it.
  5372. *
  5373. * @param {Number} x
  5374. * @param {Number} y
  5375. * @param {Number} width
  5376. * @param {Number} height
  5377. */
  5378. rect: function(x, y, width, height) {
  5379. var me = this;
  5380. if (width === 0 || height === 0) {
  5381. return;
  5382. }
  5383. me.moveTo(x, y);
  5384. me.lineTo(x + width, y);
  5385. me.lineTo(x + width, y + height);
  5386. me.lineTo(x, y + height);
  5387. me.closePath();
  5388. },
  5389. /**
  5390. * @private
  5391. * @param {Array} result
  5392. * @param {Number} cx
  5393. * @param {Number} cy
  5394. * @param {Number} rx
  5395. * @param {Number} ry
  5396. * @param {Number} phi
  5397. * @param {Number} theta1
  5398. * @param {Number} theta2
  5399. * @return {Number}
  5400. */
  5401. approximateArc: function(result, cx, cy, rx, ry, phi, theta1, theta2) {
  5402. var cosPhi = Math.cos(phi),
  5403. sinPhi = Math.sin(phi),
  5404. cosTheta1 = Math.cos(theta1),
  5405. sinTheta1 = Math.sin(theta1),
  5406. xx = cosPhi * cosTheta1 * rx - sinPhi * sinTheta1 * ry,
  5407. yx = -cosPhi * sinTheta1 * rx - sinPhi * cosTheta1 * ry,
  5408. xy = sinPhi * cosTheta1 * rx + cosPhi * sinTheta1 * ry,
  5409. yy = -sinPhi * sinTheta1 * rx + cosPhi * cosTheta1 * ry,
  5410. rightAngle = Math.PI / 2,
  5411. count = 2,
  5412. exx = xx,
  5413. eyx = yx,
  5414. exy = xy,
  5415. eyy = yy,
  5416. rho = 0.547443256150549,
  5417. temp, y1, x3, y3, x2, y2;
  5418. theta2 -= theta1;
  5419. if (theta2 < 0) {
  5420. theta2 += Math.PI * 2;
  5421. }
  5422. result.push(xx + cx, xy + cy);
  5423. while (theta2 >= rightAngle) {
  5424. result.push(exx + eyx * rho + cx, exy + eyy * rho + cy, exx * rho + eyx + cx, exy * rho + eyy + cy, eyx + cx, eyy + cy);
  5425. count += 6;
  5426. theta2 -= rightAngle;
  5427. temp = exx;
  5428. exx = eyx;
  5429. eyx = -temp;
  5430. temp = exy;
  5431. exy = eyy;
  5432. eyy = -temp;
  5433. }
  5434. if (theta2) {
  5435. y1 = (0.3294738052815987 + 0.012120855841304373 * theta2) * theta2;
  5436. x3 = Math.cos(theta2);
  5437. y3 = Math.sin(theta2);
  5438. x2 = x3 + y1 * y3;
  5439. y2 = y3 - y1 * x3;
  5440. 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);
  5441. count += 6;
  5442. }
  5443. return count;
  5444. },
  5445. /**
  5446. * [http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes](http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes)
  5447. * @param {Number} rx
  5448. * @param {Number} ry
  5449. * @param {Number} rotation Differ from svg spec, this is radian.
  5450. * @param {Number} fA
  5451. * @param {Number} fS
  5452. * @param {Number} x2
  5453. * @param {Number} y2
  5454. */
  5455. arcSvg: function(rx, ry, rotation, fA, fS, x2, y2) {
  5456. if (rx < 0) {
  5457. rx = -rx;
  5458. }
  5459. if (ry < 0) {
  5460. ry = -ry;
  5461. }
  5462. // eslint-disable-next-line vars-on-top
  5463. var me = this,
  5464. x1 = me.cursor[0],
  5465. y1 = me.cursor[1],
  5466. hdx = (x1 - x2) / 2,
  5467. hdy = (y1 - y2) / 2,
  5468. cosPhi = Math.cos(rotation),
  5469. sinPhi = Math.sin(rotation),
  5470. xp = hdx * cosPhi + hdy * sinPhi,
  5471. yp = -hdx * sinPhi + hdy * cosPhi,
  5472. ratX = xp / rx,
  5473. ratY = yp / ry,
  5474. lambda = ratX * ratX + ratY * ratY,
  5475. cx = (x1 + x2) * 0.5,
  5476. cy = (y1 + y2) * 0.5,
  5477. cpx = 0,
  5478. cpy = 0,
  5479. theta1, deltaTheta;
  5480. if (lambda >= 1) {
  5481. lambda = Math.sqrt(lambda);
  5482. rx *= lambda;
  5483. ry *= lambda;
  5484. } else // me gives lambda == cpx == cpy == 0;
  5485. {
  5486. lambda = Math.sqrt(1 / lambda - 1);
  5487. if (fA === fS) {
  5488. lambda = -lambda;
  5489. }
  5490. cpx = lambda * rx * ratY;
  5491. cpy = -lambda * ry * ratX;
  5492. cx += cosPhi * cpx - sinPhi * cpy;
  5493. cy += sinPhi * cpx + cosPhi * cpy;
  5494. }
  5495. theta1 = Math.atan2((yp - cpy) / ry, (xp - cpx) / rx);
  5496. deltaTheta = Math.atan2((-yp - cpy) / ry, (-xp - cpx) / rx) - theta1;
  5497. if (fS) {
  5498. if (deltaTheta <= 0) {
  5499. deltaTheta += Math.PI * 2;
  5500. }
  5501. } else {
  5502. if (deltaTheta >= 0) {
  5503. deltaTheta -= Math.PI * 2;
  5504. }
  5505. }
  5506. me.ellipse(cx, cy, rx, ry, rotation, theta1, theta1 + deltaTheta, 1 - fS);
  5507. },
  5508. /**
  5509. * Feed the path from svg path string.
  5510. * @param {String} pathString
  5511. */
  5512. fromSvgString: function(pathString) {
  5513. if (!pathString) {
  5514. return;
  5515. }
  5516. // eslint-disable-next-line vars-on-top
  5517. var me = this,
  5518. parts,
  5519. paramCounts = {
  5520. a: 7,
  5521. c: 6,
  5522. h: 1,
  5523. l: 2,
  5524. m: 2,
  5525. q: 4,
  5526. s: 4,
  5527. t: 2,
  5528. v: 1,
  5529. z: 0,
  5530. A: 7,
  5531. C: 6,
  5532. H: 1,
  5533. L: 2,
  5534. M: 2,
  5535. Q: 4,
  5536. S: 4,
  5537. T: 2,
  5538. V: 1,
  5539. Z: 0
  5540. },
  5541. lastCommand = '',
  5542. lastControlX, lastControlY,
  5543. lastX = 0,
  5544. lastY = 0,
  5545. part = false,
  5546. i, partLength;
  5547. // Split the string to items.
  5548. if (Ext.isString(pathString)) {
  5549. parts = pathString.replace(Ext.draw.Path.pathRe, " $1 ").replace(Ext.draw.Path.pathRe2, " -").split(Ext.draw.Path.pathSplitRe);
  5550. } else if (Ext.isArray(pathString)) {
  5551. parts = pathString.join(',').split(Ext.draw.Path.pathSplitRe);
  5552. }
  5553. // Remove empty entries
  5554. for (i = 0 , partLength = 0; i < parts.length; i++) {
  5555. if (parts[i] !== '') {
  5556. parts[partLength++] = parts[i];
  5557. }
  5558. }
  5559. parts.length = partLength;
  5560. me.clear();
  5561. for (i = 0; i < parts.length; ) {
  5562. lastCommand = part;
  5563. part = parts[i];
  5564. i++;
  5565. switch (part) {
  5566. case 'M':
  5567. me.moveTo(lastX = +parts[i], lastY = +parts[i + 1]);
  5568. i += 2;
  5569. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5570. me.lineTo(lastX = +parts[i], lastY = +parts[i + 1]);
  5571. i += 2;
  5572. };
  5573. break;
  5574. case 'L':
  5575. me.lineTo(lastX = +parts[i], lastY = +parts[i + 1]);
  5576. i += 2;
  5577. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5578. me.lineTo(lastX = +parts[i], lastY = +parts[i + 1]);
  5579. i += 2;
  5580. };
  5581. break;
  5582. case 'A':
  5583. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5584. 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]);
  5585. i += 7;
  5586. };
  5587. break;
  5588. case 'C':
  5589. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5590. me.bezierCurveTo(+parts[i], +parts[i + 1], lastControlX = +parts[i + 2], lastControlY = +parts[i + 3], lastX = +parts[i + 4], lastY = +parts[i + 5]);
  5591. i += 6;
  5592. };
  5593. break;
  5594. case 'Z':
  5595. me.closePath();
  5596. break;
  5597. case 'm':
  5598. me.moveTo(lastX += +parts[i], lastY += +parts[i + 1]);
  5599. i += 2;
  5600. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5601. me.lineTo(lastX += +parts[i], lastY += +parts[i + 1]);
  5602. i += 2;
  5603. };
  5604. break;
  5605. case 'l':
  5606. me.lineTo(lastX += +parts[i], lastY += +parts[i + 1]);
  5607. i += 2;
  5608. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5609. me.lineTo(lastX += +parts[i], lastY += +parts[i + 1]);
  5610. i += 2;
  5611. };
  5612. break;
  5613. case 'a':
  5614. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5615. 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]);
  5616. i += 7;
  5617. };
  5618. break;
  5619. case 'c':
  5620. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5621. 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]);
  5622. i += 6;
  5623. };
  5624. break;
  5625. case 'z':
  5626. me.closePath();
  5627. break;
  5628. case 's':
  5629. if (!(lastCommand === 'c' || lastCommand === 'C' || lastCommand === 's' || lastCommand === 'S')) {
  5630. lastControlX = lastX;
  5631. lastControlY = lastY;
  5632. };
  5633. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5634. 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]);
  5635. i += 4;
  5636. };
  5637. break;
  5638. case 'S':
  5639. if (!(lastCommand === 'c' || lastCommand === 'C' || lastCommand === 's' || lastCommand === 'S')) {
  5640. lastControlX = lastX;
  5641. lastControlY = lastY;
  5642. };
  5643. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5644. me.bezierCurveTo(lastX + lastX - lastControlX, lastY + lastY - lastControlY, lastControlX = +parts[i], lastControlY = +parts[i + 1], lastX = (+parts[i + 2]), lastY = (+parts[i + 3]));
  5645. i += 4;
  5646. };
  5647. break;
  5648. case 'q':
  5649. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5650. me.quadraticCurveTo(lastControlX = lastX + (+parts[i]), lastControlY = lastY + (+parts[i + 1]), lastX += +parts[i + 2], lastY += +parts[i + 3]);
  5651. i += 4;
  5652. };
  5653. break;
  5654. case 'Q':
  5655. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5656. me.quadraticCurveTo(lastControlX = +parts[i], lastControlY = +parts[i + 1], lastX = +parts[i + 2], lastY = +parts[i + 3]);
  5657. i += 4;
  5658. };
  5659. break;
  5660. case 't':
  5661. if (!(lastCommand === 'q' || lastCommand === 'Q' || lastCommand === 't' || lastCommand === 'T')) {
  5662. lastControlX = lastX;
  5663. lastControlY = lastY;
  5664. };
  5665. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5666. me.quadraticCurveTo(lastControlX = lastX + lastX - lastControlX, lastControlY = lastY + lastY - lastControlY, lastX += +parts[i + 1], lastY += +parts[i + 2]);
  5667. i += 2;
  5668. };
  5669. break;
  5670. case 'T':
  5671. if (!(lastCommand === 'q' || lastCommand === 'Q' || lastCommand === 't' || lastCommand === 'T')) {
  5672. lastControlX = lastX;
  5673. lastControlY = lastY;
  5674. };
  5675. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5676. me.quadraticCurveTo(lastControlX = lastX + lastX - lastControlX, lastControlY = lastY + lastY - lastControlY, lastX = (+parts[i + 1]), lastY = (+parts[i + 2]));
  5677. i += 2;
  5678. };
  5679. break;
  5680. case 'h':
  5681. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5682. me.lineTo(lastX += +parts[i], lastY);
  5683. i++;
  5684. };
  5685. break;
  5686. case 'H':
  5687. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5688. me.lineTo(lastX = +parts[i], lastY);
  5689. i++;
  5690. };
  5691. break;
  5692. case 'v':
  5693. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5694. me.lineTo(lastX, lastY += +parts[i]);
  5695. i++;
  5696. };
  5697. break;
  5698. case 'V':
  5699. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5700. me.lineTo(lastX, lastY = +parts[i]);
  5701. i++;
  5702. };
  5703. break;
  5704. }
  5705. }
  5706. },
  5707. /**
  5708. * Clone this path.
  5709. * @return {Ext.draw.Path}
  5710. */
  5711. clone: function() {
  5712. var me = this,
  5713. path = new Ext.draw.Path();
  5714. path.params = me.params.slice(0);
  5715. path.commands = me.commands.slice(0);
  5716. path.cursor = me.cursor ? me.cursor.slice(0) : null;
  5717. path.startX = me.startX;
  5718. path.startY = me.startY;
  5719. path.svgString = me.svgString;
  5720. return path;
  5721. },
  5722. /**
  5723. * Transform the current path by a matrix.
  5724. * @param {Ext.draw.Matrix} matrix
  5725. */
  5726. transform: function(matrix) {
  5727. if (matrix.isIdentity()) {
  5728. return;
  5729. }
  5730. // eslint-disable-next-line vars-on-top
  5731. var xx = matrix.getXX(),
  5732. yx = matrix.getYX(),
  5733. dx = matrix.getDX(),
  5734. xy = matrix.getXY(),
  5735. yy = matrix.getYY(),
  5736. dy = matrix.getDY(),
  5737. params = this.params,
  5738. i = 0,
  5739. ln = params.length,
  5740. x, y;
  5741. for (; i < ln; i += 2) {
  5742. x = params[i];
  5743. y = params[i + 1];
  5744. params[i] = x * xx + y * yx + dx;
  5745. params[i + 1] = x * xy + y * yy + dy;
  5746. }
  5747. this.dirt();
  5748. },
  5749. /**
  5750. * Get the bounding box of this matrix.
  5751. * @param {Object} [target] Optional object to receive the result.
  5752. *
  5753. * @return {Object} Object with x, y, width and height
  5754. */
  5755. getDimension: function(target) {
  5756. if (!target) {
  5757. target = {};
  5758. }
  5759. if (!this.commands || !this.commands.length) {
  5760. target.x = 0;
  5761. target.y = 0;
  5762. target.width = 0;
  5763. target.height = 0;
  5764. return target;
  5765. }
  5766. target.left = Infinity;
  5767. target.top = Infinity;
  5768. target.right = -Infinity;
  5769. target.bottom = -Infinity;
  5770. // eslint-disable-next-line vars-on-top
  5771. var i = 0,
  5772. j = 0,
  5773. commands = this.commands,
  5774. params = this.params,
  5775. ln = commands.length,
  5776. x, y;
  5777. for (; i < ln; i++) {
  5778. switch (commands[i]) {
  5779. case 'M':
  5780. case 'L':
  5781. x = params[j];
  5782. y = params[j + 1];
  5783. target.left = Math.min(x, target.left);
  5784. target.top = Math.min(y, target.top);
  5785. target.right = Math.max(x, target.right);
  5786. target.bottom = Math.max(y, target.bottom);
  5787. j += 2;
  5788. break;
  5789. case 'C':
  5790. this.expandDimension(target, x, y, params[j], params[j + 1], params[j + 2], params[j + 3], x = params[j + 4], y = params[j + 5]);
  5791. j += 6;
  5792. break;
  5793. }
  5794. }
  5795. target.x = target.left;
  5796. target.y = target.top;
  5797. target.width = target.right - target.left;
  5798. target.height = target.bottom - target.top;
  5799. return target;
  5800. },
  5801. /**
  5802. * Get the bounding box as if the path is transformed by a matrix.
  5803. *
  5804. * @param {Ext.draw.Matrix} matrix
  5805. * @param {Object} [target] Optional object to receive the result.
  5806. *
  5807. * @return {Object} An object with x, y, width and height.
  5808. */
  5809. getDimensionWithTransform: function(matrix, target) {
  5810. if (!this.commands || !this.commands.length) {
  5811. if (!target) {
  5812. target = {};
  5813. }
  5814. target.x = 0;
  5815. target.y = 0;
  5816. target.width = 0;
  5817. target.height = 0;
  5818. return target;
  5819. }
  5820. target.left = Infinity;
  5821. target.top = Infinity;
  5822. target.right = -Infinity;
  5823. target.bottom = -Infinity;
  5824. // eslint-disable-next-line vars-on-top
  5825. var xx = matrix.getXX(),
  5826. yx = matrix.getYX(),
  5827. dx = matrix.getDX(),
  5828. xy = matrix.getXY(),
  5829. yy = matrix.getYY(),
  5830. dy = matrix.getDY(),
  5831. i = 0,
  5832. j = 0,
  5833. commands = this.commands,
  5834. params = this.params,
  5835. ln = commands.length,
  5836. x, y;
  5837. for (; i < ln; i++) {
  5838. switch (commands[i]) {
  5839. case 'M':
  5840. case 'L':
  5841. x = params[j] * xx + params[j + 1] * yx + dx;
  5842. y = params[j] * xy + params[j + 1] * yy + dy;
  5843. target.left = Math.min(x, target.left);
  5844. target.top = Math.min(y, target.top);
  5845. target.right = Math.max(x, target.right);
  5846. target.bottom = Math.max(y, target.bottom);
  5847. j += 2;
  5848. break;
  5849. case 'C':
  5850. 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);
  5851. j += 6;
  5852. break;
  5853. }
  5854. }
  5855. if (!target) {
  5856. target = {};
  5857. }
  5858. target.x = target.left;
  5859. target.y = target.top;
  5860. target.width = target.right - target.left;
  5861. target.height = target.bottom - target.top;
  5862. return target;
  5863. },
  5864. /**
  5865. * @private
  5866. * Expand the rect by the bbox of a bezier curve.
  5867. *
  5868. * @param {Object} target
  5869. * @param {Number} x1
  5870. * @param {Number} y1
  5871. * @param {Number} cx1
  5872. * @param {Number} cy1
  5873. * @param {Number} cx2
  5874. * @param {Number} cy2
  5875. * @param {Number} x2
  5876. * @param {Number} y2
  5877. */
  5878. expandDimension: function(target, x1, y1, cx1, cy1, cx2, cy2, x2, y2) {
  5879. var me = this,
  5880. l = target.left,
  5881. r = target.right,
  5882. t = target.top,
  5883. b = target.bottom,
  5884. dim = me.dim || (me.dim = []);
  5885. me.curveDimension(x1, cx1, cx2, x2, dim);
  5886. l = Math.min(l, dim[0]);
  5887. r = Math.max(r, dim[1]);
  5888. me.curveDimension(y1, cy1, cy2, y2, dim);
  5889. t = Math.min(t, dim[0]);
  5890. b = Math.max(b, dim[1]);
  5891. target.left = l;
  5892. target.right = r;
  5893. target.top = t;
  5894. target.bottom = b;
  5895. },
  5896. /**
  5897. * @private
  5898. * Determine the curve
  5899. * @param {Number} a
  5900. * @param {Number} b
  5901. * @param {Number} c
  5902. * @param {Number} d
  5903. * @param {Number} dim
  5904. */
  5905. curveDimension: function(a, b, c, d, dim) {
  5906. var qa = 3 * (-a + 3 * (b - c) + d),
  5907. qb = 6 * (a - 2 * b + c),
  5908. qc = -3 * (a - b),
  5909. x, y,
  5910. min = Math.min(a, d),
  5911. max = Math.max(a, d),
  5912. delta;
  5913. if (qa === 0) {
  5914. if (qb === 0) {
  5915. dim[0] = min;
  5916. dim[1] = max;
  5917. return;
  5918. } else {
  5919. x = -qc / qb;
  5920. if (0 < x && x < 1) {
  5921. y = this.interpolate(a, b, c, d, x);
  5922. min = Math.min(min, y);
  5923. max = Math.max(max, y);
  5924. }
  5925. }
  5926. } else {
  5927. delta = qb * qb - 4 * qa * qc;
  5928. if (delta >= 0) {
  5929. delta = Math.sqrt(delta);
  5930. x = (delta - qb) / 2 / qa;
  5931. if (0 < x && x < 1) {
  5932. y = this.interpolate(a, b, c, d, x);
  5933. min = Math.min(min, y);
  5934. max = Math.max(max, y);
  5935. }
  5936. if (delta > 0) {
  5937. x -= delta / qa;
  5938. if (0 < x && x < 1) {
  5939. y = this.interpolate(a, b, c, d, x);
  5940. min = Math.min(min, y);
  5941. max = Math.max(max, y);
  5942. }
  5943. }
  5944. }
  5945. }
  5946. dim[0] = min;
  5947. dim[1] = max;
  5948. },
  5949. /**
  5950. * @private
  5951. *
  5952. * Returns `a * (1 - t) ^ 3 + 3 * b (1 - t) ^ 2 * t + 3 * c (1 - t) * t ^ 3 + d * t ^ 3`.
  5953. *
  5954. * @param {Number} a
  5955. * @param {Number} b
  5956. * @param {Number} c
  5957. * @param {Number} d
  5958. * @param {Number} t
  5959. * @return {Number}
  5960. */
  5961. interpolate: function(a, b, c, d, t) {
  5962. var rate;
  5963. if (t === 0) {
  5964. return a;
  5965. }
  5966. if (t === 1) {
  5967. return d;
  5968. }
  5969. rate = (1 - t) / t;
  5970. return t * t * t * (d + rate * (3 * c + rate * (3 * b + rate * a)));
  5971. },
  5972. /**
  5973. * Reconstruct path from cubic bezier curve stripes.
  5974. * @param {Array} stripes
  5975. */
  5976. fromStripes: function(stripes) {
  5977. var me = this,
  5978. i = 0,
  5979. ln = stripes.length,
  5980. j, ln2, stripe;
  5981. me.clear();
  5982. for (; i < ln; i++) {
  5983. stripe = stripes[i];
  5984. me.params.push.apply(me.params, stripe);
  5985. me.commands.push('M');
  5986. for (j = 2 , ln2 = stripe.length; j < ln2; j += 6) {
  5987. me.commands.push('C');
  5988. }
  5989. }
  5990. if (!me.cursor) {
  5991. me.cursor = [];
  5992. }
  5993. me.cursor[0] = me.params[me.params.length - 2];
  5994. me.cursor[1] = me.params[me.params.length - 1];
  5995. me.dirt();
  5996. },
  5997. /**
  5998. * Convert path to bezier curve stripes.
  5999. * @param {Array} [target] The optional array to receive the result.
  6000. * @return {Array}
  6001. */
  6002. toStripes: function(target) {
  6003. var stripes = target || [],
  6004. curr, x, y, lastX, lastY, startX, startY, i, j,
  6005. commands = this.commands,
  6006. params = this.params,
  6007. ln = commands.length;
  6008. for (i = 0 , j = 0; i < ln; i++) {
  6009. switch (commands[i]) {
  6010. case 'M':
  6011. curr = [
  6012. startX = lastX = params[j++],
  6013. startY = lastY = params[j++]
  6014. ];
  6015. stripes.push(curr);
  6016. break;
  6017. case 'L':
  6018. x = params[j++];
  6019. y = params[j++];
  6020. curr.push((lastX + lastX + x) / 3, (lastY + lastY + y) / 3, (lastX + x + x) / 3, (lastY + y + y) / 3, lastX = x, lastY = y);
  6021. break;
  6022. case 'C':
  6023. curr.push(params[j++], params[j++], params[j++], params[j++], lastX = params[j++], lastY = params[j++]);
  6024. break;
  6025. case 'Z':
  6026. x = startX;
  6027. y = startY;
  6028. curr.push((lastX + lastX + x) / 3, (lastY + lastY + y) / 3, (lastX + x + x) / 3, (lastY + y + y) / 3, lastX = x, lastY = y);
  6029. break;
  6030. }
  6031. }
  6032. return stripes;
  6033. },
  6034. /**
  6035. * @private
  6036. * Update cache for svg string of this path.
  6037. */
  6038. updateSvgString: function() {
  6039. var result = [],
  6040. commands = this.commands,
  6041. params = this.params,
  6042. ln = commands.length,
  6043. i = 0,
  6044. j = 0;
  6045. for (; i < ln; i++) {
  6046. switch (commands[i]) {
  6047. case 'M':
  6048. result.push('M' + params[j] + ',' + params[j + 1]);
  6049. j += 2;
  6050. break;
  6051. case 'L':
  6052. result.push('L' + params[j] + ',' + params[j + 1]);
  6053. j += 2;
  6054. break;
  6055. case 'C':
  6056. result.push('C' + params[j] + ',' + params[j + 1] + ' ' + params[j + 2] + ',' + params[j + 3] + ' ' + params[j + 4] + ',' + params[j + 5]);
  6057. j += 6;
  6058. break;
  6059. case 'Z':
  6060. result.push('Z');
  6061. break;
  6062. }
  6063. }
  6064. this.svgString = result.join('');
  6065. },
  6066. /**
  6067. * Return an svg path string for this path.
  6068. * @return {String}
  6069. */
  6070. toString: function() {
  6071. if (!this.svgString) {
  6072. this.updateSvgString();
  6073. }
  6074. return this.svgString;
  6075. }
  6076. });
  6077. /**
  6078. * @private
  6079. * Adds hit testing and path intersection points methods to the Ext.draw.Path.
  6080. * Included by the Ext.draw.PathUtil.
  6081. */
  6082. Ext.define('Ext.draw.overrides.hittest.Path', {
  6083. override: 'Ext.draw.Path',
  6084. // An arbitrary point outside the path used for hit testing with ray casting method.
  6085. rayOrigin: {
  6086. x: -10000,
  6087. y: -10000
  6088. },
  6089. /**
  6090. * Tests whether the given point is inside the path.
  6091. * @param {Number} x
  6092. * @param {Number} y
  6093. * @return {Boolean}
  6094. * @member Ext.draw.Path
  6095. */
  6096. isPointInPath: function(x, y) {
  6097. var me = this,
  6098. commands = me.commands,
  6099. solver = Ext.draw.PathUtil,
  6100. origin = me.rayOrigin,
  6101. params = me.params,
  6102. ln = commands.length,
  6103. firstX = null,
  6104. firstY = null,
  6105. lastX = 0,
  6106. lastY = 0,
  6107. count = 0,
  6108. i, j;
  6109. for (i = 0 , j = 0; i < ln; i++) {
  6110. switch (commands[i]) {
  6111. case 'M':
  6112. if (firstX !== null) {
  6113. // eslint-disable-next-line max-len
  6114. if (solver.linesIntersection(firstX, firstY, lastX, lastY, origin.x, origin.y, x, y)) {
  6115. count += 1;
  6116. }
  6117. };
  6118. firstX = lastX = params[j];
  6119. firstY = lastY = params[j + 1];
  6120. j += 2;
  6121. break;
  6122. case 'L':
  6123. // eslint-disable-next-line max-len
  6124. if (solver.linesIntersection(lastX, lastY, params[j], params[j + 1], origin.x, origin.y, x, y)) {
  6125. count += 1;
  6126. };
  6127. lastX = params[j];
  6128. lastY = params[j + 1];
  6129. j += 2;
  6130. break;
  6131. case 'C':
  6132. 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;
  6133. lastX = params[j + 4];
  6134. lastY = params[j + 5];
  6135. j += 6;
  6136. break;
  6137. case 'Z':
  6138. if (firstX !== null) {
  6139. // eslint-disable-next-line max-len
  6140. if (solver.linesIntersection(firstX, firstY, lastX, lastY, origin.x, origin.y, x, y)) {
  6141. count += 1;
  6142. }
  6143. };
  6144. break;
  6145. }
  6146. }
  6147. return count % 2 === 1;
  6148. },
  6149. /**
  6150. * Tests whether the given point is on the path.
  6151. * @param {Number} x
  6152. * @param {Number} y
  6153. * @return {Boolean}
  6154. * @member Ext.draw.Path
  6155. */
  6156. isPointOnPath: function(x, y) {
  6157. var me = this,
  6158. commands = me.commands,
  6159. solver = Ext.draw.PathUtil,
  6160. params = me.params,
  6161. ln = commands.length,
  6162. firstX = null,
  6163. firstY = null,
  6164. lastX = 0,
  6165. lastY = 0,
  6166. i, j;
  6167. for (i = 0 , j = 0; i < ln; i++) {
  6168. switch (commands[i]) {
  6169. case 'M':
  6170. if (firstX !== null) {
  6171. if (solver.pointOnLine(firstX, firstY, lastX, lastY, x, y)) {
  6172. return true;
  6173. }
  6174. };
  6175. firstX = lastX = params[j];
  6176. firstY = lastY = params[j + 1];
  6177. j += 2;
  6178. break;
  6179. case 'L':
  6180. if (solver.pointOnLine(lastX, lastY, params[j], params[j + 1], x, y)) {
  6181. return true;
  6182. };
  6183. lastX = params[j];
  6184. lastY = params[j + 1];
  6185. j += 2;
  6186. break;
  6187. case 'C':
  6188. if (solver.pointOnCubic(lastX, params[j], params[j + 2], params[j + 4], lastY, params[j + 1], params[j + 3], params[j + 5], x, y)) {
  6189. return true;
  6190. };
  6191. lastX = params[j + 4];
  6192. lastY = params[j + 5];
  6193. j += 6;
  6194. break;
  6195. case 'Z':
  6196. if (firstX !== null) {
  6197. if (solver.pointOnLine(firstX, firstY, lastX, lastY, x, y)) {
  6198. return true;
  6199. }
  6200. };
  6201. break;
  6202. }
  6203. }
  6204. return false;
  6205. },
  6206. /**
  6207. * Calculates the points where the given segment intersects the path.
  6208. * If four parameters are given then the segment is considered to be a line segment,
  6209. * where given parameters are the coordinates of the start and end points.
  6210. * If eight parameters are given then the segment is considered to be
  6211. * a cubic Bezier curve segment, where given parameters are the
  6212. * coordinates of its edge points and control points.
  6213. * @param x1
  6214. * @param y1
  6215. * @param x2
  6216. * @param y2
  6217. * @param x3
  6218. * @param y3
  6219. * @param x4
  6220. * @param y4
  6221. * @return {Array}
  6222. * @member Ext.draw.Path
  6223. */
  6224. getSegmentIntersections: function(x1, y1, x2, y2, x3, y3, x4, y4) {
  6225. var me = this,
  6226. count = arguments.length,
  6227. solver = Ext.draw.PathUtil,
  6228. commands = me.commands,
  6229. params = me.params,
  6230. ln = commands.length,
  6231. firstX = null,
  6232. firstY = null,
  6233. lastX = 0,
  6234. lastY = 0,
  6235. intersections = [],
  6236. i, j, points;
  6237. for (i = 0 , j = 0; i < ln; i++) {
  6238. switch (commands[i]) {
  6239. case 'M':
  6240. if (firstX !== null) {
  6241. switch (count) {
  6242. case 4:
  6243. points = solver.linesIntersection(firstX, firstY, lastX, lastY, x1, y1, x2, y2);
  6244. if (points) {
  6245. intersections.push(points);
  6246. };
  6247. break;
  6248. case 8:
  6249. points = solver.cubicLineIntersections(x1, x2, x3, x4, y1, y2, y3, y4, firstX, firstY, lastX, lastY);
  6250. intersections.push.apply(intersections, points);
  6251. break;
  6252. }
  6253. };
  6254. firstX = lastX = params[j];
  6255. firstY = lastY = params[j + 1];
  6256. j += 2;
  6257. break;
  6258. case 'L':
  6259. switch (count) {
  6260. case 4:
  6261. points = solver.linesIntersection(lastX, lastY, params[j], params[j + 1], x1, y1, x2, y2);
  6262. if (points) {
  6263. intersections.push(points);
  6264. };
  6265. break;
  6266. case 8:
  6267. points = solver.cubicLineIntersections(x1, x2, x3, x4, y1, y2, y3, y4, lastX, lastY, params[j], params[j + 1]);
  6268. intersections.push.apply(intersections, points);
  6269. break;
  6270. };
  6271. lastX = params[j];
  6272. lastY = params[j + 1];
  6273. j += 2;
  6274. break;
  6275. case 'C':
  6276. switch (count) {
  6277. case 4:
  6278. 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);
  6279. intersections.push.apply(intersections, points);
  6280. break;
  6281. case 8:
  6282. 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);
  6283. intersections.push.apply(intersections, points);
  6284. break;
  6285. };
  6286. lastX = params[j + 4];
  6287. lastY = params[j + 5];
  6288. j += 6;
  6289. break;
  6290. case 'Z':
  6291. if (firstX !== null) {
  6292. switch (count) {
  6293. case 4:
  6294. points = solver.linesIntersection(firstX, firstY, lastX, lastY, x1, y1, x2, y2);
  6295. if (points) {
  6296. intersections.push(points);
  6297. };
  6298. break;
  6299. case 8:
  6300. points = solver.cubicLineIntersections(x1, x2, x3, x4, y1, y2, y3, y4, firstX, firstY, lastX, lastY);
  6301. intersections.push.apply(intersections, points);
  6302. break;
  6303. }
  6304. };
  6305. break;
  6306. }
  6307. }
  6308. return intersections;
  6309. },
  6310. getIntersections: function(path) {
  6311. var me = this,
  6312. commands = me.commands,
  6313. params = me.params,
  6314. ln = commands.length,
  6315. firstX = null,
  6316. firstY = null,
  6317. lastX = 0,
  6318. lastY = 0,
  6319. intersections = [],
  6320. i, j, points;
  6321. for (i = 0 , j = 0; i < ln; i++) {
  6322. switch (commands[i]) {
  6323. case 'M':
  6324. if (firstX !== null) {
  6325. points = path.getSegmentIntersections.call(path, firstX, firstY, lastX, lastY);
  6326. intersections.push.apply(intersections, points);
  6327. };
  6328. firstX = lastX = params[j];
  6329. firstY = lastY = params[j + 1];
  6330. j += 2;
  6331. break;
  6332. case 'L':
  6333. points = path.getSegmentIntersections.call(path, lastX, lastY, params[j], params[j + 1]);
  6334. intersections.push.apply(intersections, points);
  6335. lastX = params[j];
  6336. lastY = params[j + 1];
  6337. j += 2;
  6338. break;
  6339. case 'C':
  6340. points = path.getSegmentIntersections.call(path, lastX, lastY, params[j], params[j + 1], params[j + 2], params[j + 3], params[j + 4], params[j + 5]);
  6341. intersections.push.apply(intersections, points);
  6342. lastX = params[j + 4];
  6343. lastY = params[j + 5];
  6344. j += 6;
  6345. break;
  6346. case 'Z':
  6347. if (firstX !== null) {
  6348. points = path.getSegmentIntersections.call(path, firstX, firstY, lastX, lastY);
  6349. intersections.push.apply(intersections, points);
  6350. };
  6351. break;
  6352. }
  6353. }
  6354. return intersections;
  6355. }
  6356. });
  6357. /**
  6358. * @class Ext.draw.sprite.Path
  6359. * @extends Ext.draw.sprite.Sprite
  6360. *
  6361. * A sprite that represents a path.
  6362. *
  6363. * @example
  6364. * Ext.create({
  6365. * xtype: 'draw',
  6366. * renderTo: document.body,
  6367. * width: 600,
  6368. * height: 400,
  6369. * sprites: [{
  6370. * type: 'path',
  6371. * path: 'M20,30 c0,-50 75,50 75,0 c0,-50 -75,50 -75,0',
  6372. * fillStyle: '#1F6D91'
  6373. * }]
  6374. * });
  6375. *
  6376. * ### Drawing with SVG Paths
  6377. * You may use special SVG Path syntax to "describe" the drawing path.
  6378. * Here are the SVG path commands:
  6379. *
  6380. * + M = moveto
  6381. * + L = lineto
  6382. * + H = horizontal lineto
  6383. * + V = vertical lineto
  6384. * + C = curveto
  6385. * + S = smooth curveto
  6386. * + Q = quadratic Bézier curve
  6387. * + T = smooth quadratic Bézier curveto
  6388. * + A = elliptical Arc
  6389. * + Z = closepath
  6390. *
  6391. * **Note:** Capital letters indicate that the item should be absolutely positioned.
  6392. * Use lower case letters for relative positioning.
  6393. */
  6394. Ext.define('Ext.draw.sprite.Path', {
  6395. extend: 'Ext.draw.sprite.Sprite',
  6396. requires: [
  6397. 'Ext.draw.Draw',
  6398. 'Ext.draw.Path'
  6399. ],
  6400. alias: [
  6401. 'sprite.path',
  6402. 'Ext.draw.Sprite'
  6403. ],
  6404. type: 'path',
  6405. isPath: true,
  6406. inheritableStatics: {
  6407. def: {
  6408. processors: {
  6409. /**
  6410. * @cfg {String} path The SVG based path string used by the sprite.
  6411. */
  6412. path: function(n, o) {
  6413. if (!(n instanceof Ext.draw.Path)) {
  6414. n = new Ext.draw.Path(n);
  6415. }
  6416. return n;
  6417. }
  6418. },
  6419. aliases: {
  6420. d: 'path'
  6421. },
  6422. triggers: {
  6423. path: 'bbox'
  6424. },
  6425. updaters: {
  6426. path: function(attr) {
  6427. var path = attr.path;
  6428. if (!path || path.bindAttr !== attr) {
  6429. path = new Ext.draw.Path();
  6430. path.bindAttr = attr;
  6431. attr.path = path;
  6432. }
  6433. path.clear();
  6434. this.updatePath(path, attr);
  6435. this.scheduleUpdater(attr, 'bbox', [
  6436. 'path'
  6437. ]);
  6438. }
  6439. }
  6440. }
  6441. },
  6442. updatePlainBBox: function(plain) {
  6443. if (this.attr.path) {
  6444. this.attr.path.getDimension(plain);
  6445. }
  6446. },
  6447. updateTransformedBBox: function(transform) {
  6448. if (this.attr.path) {
  6449. this.attr.path.getDimensionWithTransform(this.attr.matrix, transform);
  6450. }
  6451. },
  6452. render: function(surface, ctx) {
  6453. var mat = this.attr.matrix,
  6454. attr = this.attr;
  6455. if (!attr.path || attr.path.params.length === 0) {
  6456. return;
  6457. }
  6458. mat.toContext(ctx);
  6459. ctx.appendPath(attr.path);
  6460. ctx.fillStroke(attr);
  6461. //<debug>
  6462. // eslint-disable-next-line vars-on-top
  6463. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  6464. if (debug) {
  6465. if (debug.bbox) {
  6466. this.renderBBox(surface, ctx);
  6467. }
  6468. if (debug.xray) {
  6469. this.renderXRay(surface, ctx);
  6470. }
  6471. }
  6472. },
  6473. //</debug>
  6474. //<debug>
  6475. renderXRay: function(surface, ctx) {
  6476. var attr = this.attr,
  6477. mat = attr.matrix,
  6478. imat = attr.inverseMatrix,
  6479. path = attr.path,
  6480. commands = path.commands,
  6481. params = path.params,
  6482. ln = commands.length,
  6483. twoPi = Math.PI * 2,
  6484. size = 2,
  6485. i, j;
  6486. mat.toContext(ctx);
  6487. ctx.beginPath();
  6488. for (i = 0 , j = 0; i < ln; i++) {
  6489. switch (commands[i]) {
  6490. case 'M':
  6491. ctx.moveTo(params[j] - size, params[j + 1] - size);
  6492. ctx.rect(params[j] - size, params[j + 1] - size, size * 2, size * 2);
  6493. j += 2;
  6494. break;
  6495. case 'L':
  6496. ctx.moveTo(params[j] - size, params[j + 1] - size);
  6497. ctx.rect(params[j] - size, params[j + 1] - size, size * 2, size * 2);
  6498. j += 2;
  6499. break;
  6500. case 'C':
  6501. ctx.moveTo(params[j] + size, params[j + 1]);
  6502. ctx.arc(params[j], params[j + 1], size, 0, twoPi, true);
  6503. j += 2;
  6504. ctx.moveTo(params[j] + size, params[j + 1]);
  6505. ctx.arc(params[j], params[j + 1], size, 0, twoPi, true);
  6506. j += 2;
  6507. ctx.moveTo(params[j] + size * 2, params[j + 1]);
  6508. ctx.rect(params[j] - size, params[j + 1] - size, size * 2, size * 2);
  6509. j += 2;
  6510. break;
  6511. default:
  6512. }
  6513. }
  6514. imat.toContext(ctx);
  6515. ctx.strokeStyle = 'black';
  6516. ctx.strokeOpacity = 1;
  6517. ctx.lineWidth = 1;
  6518. ctx.stroke();
  6519. mat.toContext(ctx);
  6520. ctx.beginPath();
  6521. for (i = 0 , j = 0; i < ln; i++) {
  6522. switch (commands[i]) {
  6523. case 'M':
  6524. ctx.moveTo(params[j], params[j + 1]);
  6525. j += 2;
  6526. break;
  6527. case 'L':
  6528. ctx.moveTo(params[j], params[j + 1]);
  6529. j += 2;
  6530. break;
  6531. case 'C':
  6532. ctx.lineTo(params[j], params[j + 1]);
  6533. j += 2;
  6534. ctx.moveTo(params[j], params[j + 1]);
  6535. j += 2;
  6536. ctx.lineTo(params[j], params[j + 1]);
  6537. j += 2;
  6538. break;
  6539. default:
  6540. }
  6541. }
  6542. imat.toContext(ctx);
  6543. ctx.lineWidth = 0.5;
  6544. ctx.stroke();
  6545. },
  6546. //</debug>
  6547. /**
  6548. * Update the path.
  6549. * @param {Ext.draw.Path} path An empty path to draw on using path API.
  6550. * @param {Object} attr The attribute object. Note: DO NOT use the `sprite.attr` instead of this
  6551. * if you want to work with instancing.
  6552. */
  6553. updatePath: function(path, attr) {}
  6554. });
  6555. /**
  6556. * @private
  6557. * Adds hit testing methods to the Ext.draw.sprite.Path sprite.
  6558. * Included by the Ext.draw.PathUtil.
  6559. */
  6560. Ext.define('Ext.draw.overrides.hittest.sprite.Path', {
  6561. override: 'Ext.draw.sprite.Path',
  6562. requires: [
  6563. 'Ext.draw.Color'
  6564. ],
  6565. /**
  6566. * Tests whether the given point is inside the path.
  6567. * @param x
  6568. * @param y
  6569. * @return {Boolean}
  6570. * @member Ext.draw.sprite.Path
  6571. */
  6572. isPointInPath: function(x, y) {
  6573. var attr = this.attr,
  6574. path, matrix, params, result;
  6575. if (attr.fillStyle === Ext.util.Color.RGBA_NONE) {
  6576. return this.isPointOnPath(x, y);
  6577. }
  6578. path = attr.path;
  6579. matrix = attr.matrix;
  6580. if (!matrix.isIdentity()) {
  6581. params = path.params.slice(0);
  6582. path.transform(attr.matrix);
  6583. }
  6584. result = path.isPointInPath(x, y);
  6585. if (params) {
  6586. path.params = params;
  6587. }
  6588. return result;
  6589. },
  6590. /**
  6591. * Tests whether the given point is on the path.
  6592. * @param x
  6593. * @param y
  6594. * @return {Boolean}
  6595. * @member Ext.draw.sprite.Path
  6596. */
  6597. isPointOnPath: function(x, y) {
  6598. var attr = this.attr,
  6599. path = attr.path,
  6600. matrix = attr.matrix,
  6601. params, result;
  6602. if (!matrix.isIdentity()) {
  6603. params = path.params.slice(0);
  6604. path.transform(attr.matrix);
  6605. }
  6606. result = path.isPointOnPath(x, y);
  6607. if (params) {
  6608. path.params = params;
  6609. }
  6610. return result;
  6611. },
  6612. /**
  6613. * @method hitTest
  6614. * @inheritdoc Ext.draw.Surface#method-hitTest
  6615. */
  6616. hitTest: function(point, options) {
  6617. var me = this,
  6618. attr = me.attr,
  6619. path = attr.path,
  6620. matrix = attr.matrix,
  6621. x = point[0],
  6622. y = point[1],
  6623. parentResult = me.callParent([
  6624. point,
  6625. options
  6626. ]),
  6627. result = null,
  6628. params, isFilled;
  6629. if (!parentResult) {
  6630. // The sprite is not visible or bounding box wasn't hit.
  6631. return result;
  6632. }
  6633. options = options || Ext.draw.sprite.Sprite.defaultHitTestOptions;
  6634. if (!matrix.isIdentity()) {
  6635. params = path.params.slice(0);
  6636. path.transform(attr.matrix);
  6637. }
  6638. if (options.fill && options.stroke) {
  6639. isFilled = attr.fillStyle !== Ext.util.Color.NONE && attr.fillStyle !== Ext.util.Color.RGBA_NONE;
  6640. if (isFilled) {
  6641. if (path.isPointInPath(x, y)) {
  6642. result = {
  6643. sprite: me
  6644. };
  6645. }
  6646. } else {
  6647. if (path.isPointInPath(x, y) || path.isPointOnPath(x, y)) {
  6648. result = {
  6649. sprite: me
  6650. };
  6651. }
  6652. }
  6653. } else if (options.stroke && !options.fill) {
  6654. if (path.isPointOnPath(x, y)) {
  6655. result = {
  6656. sprite: me
  6657. };
  6658. }
  6659. } else if (options.fill && !options.stroke) {
  6660. if (path.isPointInPath(x, y)) {
  6661. result = {
  6662. sprite: me
  6663. };
  6664. }
  6665. }
  6666. if (params) {
  6667. path.params = params;
  6668. }
  6669. return result;
  6670. },
  6671. /**
  6672. * Returns all points where this sprite intersects the given sprite.
  6673. * The given sprite must be an instance of the {@link Ext.draw.sprite.Path} class
  6674. * or its subclass.
  6675. * @param path
  6676. * @return {Array}
  6677. * @member Ext.draw.sprite.Path
  6678. */
  6679. getIntersections: function(path) {
  6680. if (!(path.isSprite && path.isPath)) {
  6681. return [];
  6682. }
  6683. // eslint-disable-next-line vars-on-top
  6684. var aAttr = this.attr,
  6685. bAttr = path.attr,
  6686. aPath = aAttr.path,
  6687. bPath = bAttr.path,
  6688. aMatrix = aAttr.matrix,
  6689. bMatrix = bAttr.matrix,
  6690. aParams, bParams, intersections;
  6691. if (!aMatrix.isIdentity()) {
  6692. aParams = aPath.params.slice(0);
  6693. aPath.transform(aAttr.matrix);
  6694. }
  6695. if (!bMatrix.isIdentity()) {
  6696. bParams = bPath.params.slice(0);
  6697. bPath.transform(bAttr.matrix);
  6698. }
  6699. intersections = aPath.getIntersections(bPath);
  6700. if (aParams) {
  6701. aPath.params = aParams;
  6702. }
  6703. if (bParams) {
  6704. bPath.params = bParams;
  6705. }
  6706. return intersections;
  6707. }
  6708. });
  6709. /**
  6710. * @class Ext.draw.sprite.Circle
  6711. * @extends Ext.draw.sprite.Path
  6712. *
  6713. * A sprite that represents a circle.
  6714. *
  6715. * @example
  6716. * Ext.create({
  6717. * xtype: 'draw',
  6718. * renderTo: document.body,
  6719. * width: 600,
  6720. * height: 400,
  6721. * sprites: [{
  6722. * type: 'circle',
  6723. * cx: 100,
  6724. * cy: 100,
  6725. * r: 50,
  6726. * fillStyle: '#1F6D91'
  6727. * }]
  6728. * });
  6729. */
  6730. Ext.define('Ext.draw.sprite.Circle', {
  6731. extend: 'Ext.draw.sprite.Path',
  6732. alias: 'sprite.circle',
  6733. type: 'circle',
  6734. inheritableStatics: {
  6735. def: {
  6736. processors: {
  6737. /**
  6738. * @cfg {Number} [cx=0] The center coordinate of the sprite on the x-axis.
  6739. */
  6740. cx: 'number',
  6741. /**
  6742. * @cfg {Number} [cy=0] The center coordinate of the sprite on the y-axis.
  6743. */
  6744. cy: 'number',
  6745. /**
  6746. * @cfg {Number} [r=0] The radius of the sprite.
  6747. */
  6748. r: 'number'
  6749. },
  6750. aliases: {
  6751. radius: 'r',
  6752. x: 'cx',
  6753. y: 'cy',
  6754. centerX: 'cx',
  6755. centerY: 'cy'
  6756. },
  6757. defaults: {
  6758. cx: 0,
  6759. cy: 0,
  6760. r: 4
  6761. },
  6762. triggers: {
  6763. cx: 'path',
  6764. cy: 'path',
  6765. r: 'path'
  6766. }
  6767. }
  6768. },
  6769. updatePlainBBox: function(plain) {
  6770. var attr = this.attr,
  6771. cx = attr.cx,
  6772. cy = attr.cy,
  6773. r = attr.r;
  6774. plain.x = cx - r;
  6775. plain.y = cy - r;
  6776. plain.width = r + r;
  6777. plain.height = r + r;
  6778. },
  6779. updateTransformedBBox: function(transform) {
  6780. var attr = this.attr,
  6781. cx = attr.cx,
  6782. cy = attr.cy,
  6783. r = attr.r,
  6784. matrix = attr.matrix,
  6785. scaleX = matrix.getScaleX(),
  6786. scaleY = matrix.getScaleY(),
  6787. rx, ry;
  6788. rx = scaleX * r;
  6789. ry = scaleY * r;
  6790. transform.x = matrix.x(cx, cy) - rx;
  6791. transform.y = matrix.y(cx, cy) - ry;
  6792. transform.width = rx + rx;
  6793. transform.height = ry + ry;
  6794. },
  6795. updatePath: function(path, attr) {
  6796. path.arc(attr.cx, attr.cy, attr.r, 0, Math.PI * 2, false);
  6797. }
  6798. });
  6799. /**
  6800. * @class Ext.draw.sprite.Arc
  6801. * @extend Ext.draw.sprite.Circle
  6802. *
  6803. * A sprite that represents a circular arc.
  6804. *
  6805. * @example
  6806. * Ext.create({
  6807. * xtype: 'draw',
  6808. * renderTo: document.body,
  6809. * width: 600,
  6810. * height: 400,
  6811. * sprites: [{
  6812. * type: 'arc',
  6813. * cx: 100,
  6814. * cy: 100,
  6815. * r: 80,
  6816. * fillStyle: '#1F6D91',
  6817. * startAngle: 0,
  6818. * endAngle: Math.PI,
  6819. * anticlockwise: true
  6820. * }]
  6821. * });
  6822. */
  6823. Ext.define('Ext.draw.sprite.Arc', {
  6824. extend: 'Ext.draw.sprite.Circle',
  6825. alias: 'sprite.arc',
  6826. type: 'arc',
  6827. inheritableStatics: {
  6828. def: {
  6829. processors: {
  6830. /**
  6831. * @cfg {Number} [startAngle=0] The beginning angle of the arc.
  6832. */
  6833. startAngle: 'number',
  6834. /**
  6835. * @cfg {Number} [endAngle=Math.PI*2] The ending angle of the arc.
  6836. */
  6837. endAngle: 'number',
  6838. /**
  6839. * @cfg {Boolean} [anticlockwise=false] Determines whether or not the arc is drawn
  6840. * clockwise.
  6841. */
  6842. anticlockwise: 'bool'
  6843. },
  6844. aliases: {
  6845. from: 'startAngle',
  6846. to: 'endAngle',
  6847. start: 'startAngle',
  6848. end: 'endAngle'
  6849. },
  6850. defaults: {
  6851. startAngle: 0,
  6852. endAngle: Math.PI * 2,
  6853. anticlockwise: false
  6854. },
  6855. triggers: {
  6856. startAngle: 'path',
  6857. endAngle: 'path',
  6858. anticlockwise: 'path'
  6859. }
  6860. }
  6861. },
  6862. updatePath: function(path, attr) {
  6863. path.arc(attr.cx, attr.cy, attr.r, attr.startAngle, attr.endAngle, attr.anticlockwise);
  6864. }
  6865. });
  6866. /**
  6867. * A sprite that represents an arrow.
  6868. *
  6869. * @example
  6870. * Ext.create({
  6871. * xtype: 'draw',
  6872. * renderTo: document.body,
  6873. * width: 600,
  6874. * height: 400,
  6875. * sprites: [{
  6876. * type: 'arrow',
  6877. * translationX: 100,
  6878. * translationY: 100,
  6879. * size: 40,
  6880. * fillStyle: '#30BDA7'
  6881. * }]
  6882. * });
  6883. */
  6884. Ext.define('Ext.draw.sprite.Arrow', {
  6885. extend: 'Ext.draw.sprite.Path',
  6886. alias: 'sprite.arrow',
  6887. inheritableStatics: {
  6888. def: {
  6889. processors: {
  6890. x: 'number',
  6891. y: 'number',
  6892. /**
  6893. * @cfg {Number} [size=4] The size of the sprite.
  6894. * Meant to be comparable to the size of a circle sprite with the same radius.
  6895. */
  6896. size: 'number'
  6897. },
  6898. defaults: {
  6899. x: 0,
  6900. y: 0,
  6901. size: 4
  6902. },
  6903. triggers: {
  6904. x: 'path',
  6905. y: 'path',
  6906. size: 'path'
  6907. }
  6908. }
  6909. },
  6910. updatePath: function(path, attr) {
  6911. var s = attr.size * 1.5,
  6912. x = attr.x - attr.lineWidth / 2,
  6913. y = attr.y;
  6914. path.fromSvgString('M'.concat(x - s * 0.7, ',', y - s * 0.4, 'l', [
  6915. s * 0.6,
  6916. 0,
  6917. 0,
  6918. -s * 0.4,
  6919. s,
  6920. s * 0.8,
  6921. -s,
  6922. s * 0.8,
  6923. 0,
  6924. -s * 0.4,
  6925. -s * 0.6,
  6926. 0
  6927. ], 'z'));
  6928. }
  6929. });
  6930. /**
  6931. * @class Ext.draw.sprite.Composite
  6932. *
  6933. * Represents a group of sprites.
  6934. * Composite's sprites are rendered in the order they've been added to the Composite.
  6935. * The rendering order of composite sprites themselves is determined by the value of
  6936. * their zIndex attribute, just like with any other sprite.
  6937. * Every sprite that is added to the Composite is removed from whatever Surface/Composite
  6938. * it belongs to.
  6939. */
  6940. Ext.define('Ext.draw.sprite.Composite', {
  6941. extend: 'Ext.draw.sprite.Sprite',
  6942. alias: 'sprite.composite',
  6943. type: 'composite',
  6944. isComposite: true,
  6945. config: {
  6946. sprites: []
  6947. },
  6948. constructor: function(config) {
  6949. this.sprites = [];
  6950. this.map = {};
  6951. this.callParent([
  6952. config
  6953. ]);
  6954. },
  6955. /**
  6956. * Adds sprite(s) to the composite.
  6957. * @param {Ext.draw.sprite.Sprite/Ext.draw.sprite.Sprite[]/Object/Object[]} sprite
  6958. * @return {Ext.draw.sprite.Sprite/Ext.draw.sprite.Sprite[]}
  6959. */
  6960. addSprite: function(sprite) {
  6961. var i = 0,
  6962. attr, results, oldTransformations;
  6963. if (Ext.isArray(sprite)) {
  6964. results = [];
  6965. while (i < sprite.length) {
  6966. results.push(this.addSprite(sprite[i++]));
  6967. }
  6968. return results;
  6969. }
  6970. if (sprite && sprite.type && !sprite.isSprite) {
  6971. sprite = Ext.create('sprite.' + sprite.type, sprite);
  6972. }
  6973. if (!sprite || !sprite.isSprite || sprite.isComposite) {
  6974. return null;
  6975. }
  6976. sprite.setSurface(null);
  6977. sprite.setParent(this);
  6978. attr = this.attr;
  6979. oldTransformations = sprite.applyTransformations;
  6980. sprite.applyTransformations = function(force) {
  6981. if (sprite.attr.dirtyTransform) {
  6982. attr.dirtyTransform = true;
  6983. attr.bbox.plain.dirty = true;
  6984. attr.bbox.transform.dirty = true;
  6985. }
  6986. oldTransformations.call(sprite, force);
  6987. };
  6988. this.sprites.push(sprite);
  6989. this.map[sprite.id] = sprite.getId();
  6990. attr.bbox.plain.dirty = true;
  6991. attr.bbox.transform.dirty = true;
  6992. return sprite;
  6993. },
  6994. /**
  6995. * @deprecated 6.2.1 Use {@link #addSprite} instead.
  6996. */
  6997. add: function(sprite) {
  6998. return this.addSprite(sprite);
  6999. },
  7000. removeSprite: function(sprite, isDestroy) {
  7001. var me = this,
  7002. id, isOwnSprite;
  7003. if (sprite) {
  7004. if (sprite.charAt) {
  7005. // is String
  7006. sprite = me.map[sprite];
  7007. }
  7008. if (!sprite || !sprite.isSprite) {
  7009. return null;
  7010. }
  7011. if (sprite.destroyed || sprite.destroying) {
  7012. return sprite;
  7013. }
  7014. id = sprite.getId();
  7015. isOwnSprite = me.map[id];
  7016. delete me.map[id];
  7017. if (isDestroy) {
  7018. sprite.destroy();
  7019. }
  7020. if (!isOwnSprite) {
  7021. return sprite;
  7022. }
  7023. sprite.setParent(null);
  7024. // sprite.setSurface(null);
  7025. Ext.Array.remove(me.sprites, sprite);
  7026. me.dirtyZIndex = true;
  7027. me.setDirty(true);
  7028. }
  7029. return sprite || null;
  7030. },
  7031. /**
  7032. * @deprecated 6.2.1 Use {@link #addSprite} instead.
  7033. * Adds a list of sprites to the composite.
  7034. * @param {Ext.draw.sprite.Sprite[]|Object[]|Ext.draw.sprite.Sprite|Object} sprites
  7035. */
  7036. addAll: function(sprites) {
  7037. var i = 0;
  7038. if (sprites.isSprite || sprites.type) {
  7039. this.add(sprites);
  7040. } else if (Ext.isArray(sprites)) {
  7041. while (i < sprites.length) {
  7042. this.add(sprites[i++]);
  7043. }
  7044. }
  7045. },
  7046. /**
  7047. * Updates the bounding box of the composite, which contains the bounding box of all sprites
  7048. * in the composite.
  7049. */
  7050. updatePlainBBox: function(plain) {
  7051. var me = this,
  7052. left = Infinity,
  7053. right = -Infinity,
  7054. top = Infinity,
  7055. bottom = -Infinity,
  7056. sprite, bbox, i, ln;
  7057. for (i = 0 , ln = me.sprites.length; i < ln; i++) {
  7058. sprite = me.sprites[i];
  7059. sprite.applyTransformations();
  7060. bbox = sprite.getBBox();
  7061. if (left > bbox.x) {
  7062. left = bbox.x;
  7063. }
  7064. if (right < bbox.x + bbox.width) {
  7065. right = bbox.x + bbox.width;
  7066. }
  7067. if (top > bbox.y) {
  7068. top = bbox.y;
  7069. }
  7070. if (bottom < bbox.y + bbox.height) {
  7071. bottom = bbox.y + bbox.height;
  7072. }
  7073. }
  7074. plain.x = left;
  7075. plain.y = top;
  7076. plain.width = right - left;
  7077. plain.height = bottom - top;
  7078. },
  7079. isVisible: function() {
  7080. // Override the abstract Sprite's method.
  7081. // Composite uses a simpler check, because it has no fill or stroke
  7082. // style of its own, it just houses other sprites.
  7083. var attr = this.attr,
  7084. parent = this.getParent(),
  7085. hasParent = parent && (parent.isSurface || parent.isVisible()),
  7086. isSeen = hasParent && !attr.hidden && attr.globalAlpha;
  7087. return !!isSeen;
  7088. },
  7089. /**
  7090. * Renders all sprites contained in the composite to the surface.
  7091. */
  7092. render: function(surface, ctx, rect) {
  7093. var me = this,
  7094. attr = me.attr,
  7095. mat = me.attr.matrix,
  7096. sprites = me.sprites,
  7097. ln = sprites.length,
  7098. i = 0;
  7099. mat.toContext(ctx);
  7100. for (; i < ln; i++) {
  7101. surface.renderSprite(sprites[i], rect);
  7102. }
  7103. //<debug>
  7104. // eslint-disable-next-line vars-on-top
  7105. var debug = attr.debug || me.statics().debug || Ext.draw.sprite.Sprite.debug;
  7106. if (debug) {
  7107. attr.inverseMatrix.toContext(ctx);
  7108. if (debug.bbox) {
  7109. me.renderBBox(surface, ctx);
  7110. }
  7111. }
  7112. },
  7113. //</debug>
  7114. destroy: function() {
  7115. var me = this,
  7116. sprites = me.sprites,
  7117. ln = sprites.length,
  7118. i;
  7119. for (i = 0; i < ln; i++) {
  7120. sprites[i].destroy();
  7121. }
  7122. sprites.length = 0;
  7123. me.callParent();
  7124. }
  7125. });
  7126. /**
  7127. * A sprite that represents a cross.
  7128. *
  7129. * @example
  7130. * Ext.create({
  7131. * xtype: 'draw',
  7132. * renderTo: document.body,
  7133. * width: 600,
  7134. * height: 400,
  7135. * sprites: [{
  7136. * type: 'cross',
  7137. * translationX: 100,
  7138. * translationY: 100,
  7139. * size: 40,
  7140. * fillStyle: '#1F6D91'
  7141. * }]
  7142. * });
  7143. */
  7144. Ext.define('Ext.draw.sprite.Cross', {
  7145. extend: 'Ext.draw.sprite.Path',
  7146. alias: 'sprite.cross',
  7147. inheritableStatics: {
  7148. def: {
  7149. processors: {
  7150. x: 'number',
  7151. y: 'number',
  7152. /**
  7153. * @cfg {Number} [size=4] The size of the sprite.
  7154. * Meant to be comparable to the size of a circle sprite with the same radius.
  7155. */
  7156. size: 'number'
  7157. },
  7158. defaults: {
  7159. x: 0,
  7160. y: 0,
  7161. size: 4
  7162. },
  7163. triggers: {
  7164. x: 'path',
  7165. y: 'path',
  7166. size: 'path'
  7167. }
  7168. }
  7169. },
  7170. updatePath: function(path, attr) {
  7171. var s = attr.size / 1.7,
  7172. x = attr.x - attr.lineWidth / 2,
  7173. y = attr.y;
  7174. path.fromSvgString('M'.concat(x - s, ',', y, 'l', [
  7175. -s,
  7176. -s,
  7177. s,
  7178. -s,
  7179. s,
  7180. s,
  7181. s,
  7182. -s,
  7183. s,
  7184. s,
  7185. -s,
  7186. s,
  7187. s,
  7188. s,
  7189. -s,
  7190. s,
  7191. -s,
  7192. -s,
  7193. -s,
  7194. s,
  7195. -s,
  7196. -s,
  7197. 'z'
  7198. ]));
  7199. }
  7200. });
  7201. /**
  7202. * A sprite that represents a diamond.
  7203. *
  7204. * @example
  7205. * Ext.create({
  7206. * xtype: 'draw',
  7207. * renderTo: document.body,
  7208. * width: 600,
  7209. * height: 400,
  7210. * sprites: [{
  7211. * type: 'diamond',
  7212. * translationX: 100,
  7213. * translationY: 100,
  7214. * size: 40,
  7215. * fillStyle: '#1F6D91'
  7216. * }]
  7217. * });
  7218. */
  7219. Ext.define('Ext.draw.sprite.Diamond', {
  7220. extend: 'Ext.draw.sprite.Path',
  7221. alias: 'sprite.diamond',
  7222. inheritableStatics: {
  7223. def: {
  7224. processors: {
  7225. x: 'number',
  7226. y: 'number',
  7227. /**
  7228. * @cfg {Number} [size=4] The size of the sprite.
  7229. * Meant to be comparable to the size of a circle sprite with the same radius.
  7230. */
  7231. size: 'number'
  7232. },
  7233. defaults: {
  7234. x: 0,
  7235. y: 0,
  7236. size: 4
  7237. },
  7238. triggers: {
  7239. x: 'path',
  7240. y: 'path',
  7241. size: 'path'
  7242. }
  7243. }
  7244. },
  7245. updatePath: function(path, attr) {
  7246. var s = attr.size * 1.25,
  7247. x = attr.x - attr.lineWidth / 2,
  7248. y = attr.y;
  7249. path.fromSvgString([
  7250. 'M',
  7251. x,
  7252. y - s,
  7253. 'l',
  7254. s,
  7255. s,
  7256. -s,
  7257. s,
  7258. -s,
  7259. -s,
  7260. s,
  7261. -s,
  7262. 'z'
  7263. ]);
  7264. }
  7265. });
  7266. /**
  7267. * @class Ext.draw.sprite.Ellipse
  7268. * @extends Ext.draw.sprite.Path
  7269. *
  7270. * A sprite that represents an ellipse.
  7271. *
  7272. * @example
  7273. * Ext.create({
  7274. * xtype: 'draw',
  7275. * renderTo: document.body,
  7276. * width: 600,
  7277. * height: 400,
  7278. * sprites: [{
  7279. * type: 'ellipse',
  7280. * cx: 100,
  7281. * cy: 100,
  7282. * rx: 80,
  7283. * ry: 50,
  7284. * fillStyle: '#1F6D91'
  7285. * }]
  7286. * });
  7287. */
  7288. Ext.define("Ext.draw.sprite.Ellipse", {
  7289. extend: "Ext.draw.sprite.Path",
  7290. alias: 'sprite.ellipse',
  7291. type: 'ellipse',
  7292. inheritableStatics: {
  7293. def: {
  7294. processors: {
  7295. /**
  7296. * @cfg {Number} [cx=0] The center coordinate of the sprite on the x-axis.
  7297. */
  7298. cx: "number",
  7299. /**
  7300. * @cfg {Number} [cy=0] The center coordinate of the sprite on the y-axis.
  7301. */
  7302. cy: "number",
  7303. /**
  7304. * @cfg {Number} [rx=1] The radius of the sprite on the x-axis.
  7305. */
  7306. rx: "number",
  7307. /**
  7308. * @cfg {Number} [ry=1] The radius of the sprite on the y-axis.
  7309. */
  7310. ry: "number",
  7311. /**
  7312. * @cfg {Number} [axisRotation=0] The rotation of the sprite about its axis.
  7313. */
  7314. axisRotation: "number"
  7315. },
  7316. aliases: {
  7317. radius: "r",
  7318. x: "cx",
  7319. y: "cy",
  7320. centerX: "cx",
  7321. centerY: "cy",
  7322. radiusX: "rx",
  7323. radiusY: "ry"
  7324. },
  7325. defaults: {
  7326. cx: 0,
  7327. cy: 0,
  7328. rx: 1,
  7329. ry: 1,
  7330. axisRotation: 0
  7331. },
  7332. triggers: {
  7333. cx: 'path',
  7334. cy: 'path',
  7335. rx: 'path',
  7336. ry: 'path',
  7337. axisRotation: 'path'
  7338. }
  7339. }
  7340. },
  7341. updatePlainBBox: function(plain) {
  7342. var attr = this.attr,
  7343. cx = attr.cx,
  7344. cy = attr.cy,
  7345. rx = attr.rx,
  7346. ry = attr.ry;
  7347. plain.x = cx - rx;
  7348. plain.y = cy - ry;
  7349. plain.width = rx + rx;
  7350. plain.height = ry + ry;
  7351. },
  7352. updateTransformedBBox: function(transform) {
  7353. var attr = this.attr,
  7354. cx = attr.cx,
  7355. cy = attr.cy,
  7356. rx = attr.rx,
  7357. ry = attr.ry,
  7358. rxy = ry / rx,
  7359. matrix = attr.matrix.clone(),
  7360. xx, xy, yx, yy, dx, dy, w, h;
  7361. matrix.append(1, 0, 0, rxy, 0, cy * (1 - rxy));
  7362. xx = matrix.getXX();
  7363. yx = matrix.getYX();
  7364. dx = matrix.getDX();
  7365. xy = matrix.getXY();
  7366. yy = matrix.getYY();
  7367. dy = matrix.getDY();
  7368. w = Math.sqrt(xx * xx + yx * yx) * rx;
  7369. h = Math.sqrt(xy * xy + yy * yy) * rx;
  7370. transform.x = cx * xx + cy * yx + dx - w;
  7371. transform.y = cx * xy + cy * yy + dy - h;
  7372. transform.width = w + w;
  7373. transform.height = h + h;
  7374. },
  7375. updatePath: function(path, attr) {
  7376. path.ellipse(attr.cx, attr.cy, attr.rx, attr.ry, attr.axisRotation, 0, Math.PI * 2, false);
  7377. }
  7378. });
  7379. /**
  7380. * @class Ext.draw.sprite.EllipticalArc
  7381. * @extends Ext.draw.sprite.Ellipse
  7382. *
  7383. * A sprite that represents an elliptical arc.
  7384. *
  7385. * @example
  7386. * Ext.create({
  7387. * xtype: 'draw',
  7388. * renderTo: document.body,
  7389. * width: 600,
  7390. * height: 400,
  7391. * sprites: [{
  7392. * type: 'ellipticalArc',
  7393. * cx: 100,
  7394. * cy: 100,
  7395. * rx: 80,
  7396. * ry: 50,
  7397. * fillStyle: '#1F6D91',
  7398. * startAngle: 0,
  7399. * endAngle: Math.PI,
  7400. * anticlockwise: true
  7401. * }]
  7402. * });
  7403. */
  7404. Ext.define('Ext.draw.sprite.EllipticalArc', {
  7405. extend: 'Ext.draw.sprite.Ellipse',
  7406. alias: 'sprite.ellipticalArc',
  7407. type: 'ellipticalArc',
  7408. inheritableStatics: {
  7409. def: {
  7410. processors: {
  7411. /**
  7412. * @cfg {Number} [startAngle=0] The beginning angle of the arc.
  7413. */
  7414. startAngle: 'number',
  7415. /**
  7416. * @cfg {Number} [endAngle=Math.PI*2] The ending angle of the arc.
  7417. */
  7418. endAngle: 'number',
  7419. /**
  7420. * @cfg {Boolean} [anticlockwise=false] Determines whether or not the arc
  7421. * is drawn clockwise.
  7422. */
  7423. anticlockwise: 'bool'
  7424. },
  7425. aliases: {
  7426. from: 'startAngle',
  7427. to: 'endAngle',
  7428. start: 'startAngle',
  7429. end: 'endAngle'
  7430. },
  7431. defaults: {
  7432. startAngle: 0,
  7433. endAngle: Math.PI * 2,
  7434. anticlockwise: false
  7435. },
  7436. triggers: {
  7437. startAngle: 'path',
  7438. endAngle: 'path',
  7439. anticlockwise: 'path'
  7440. }
  7441. }
  7442. },
  7443. updatePath: function(path, attr) {
  7444. path.ellipse(attr.cx, attr.cy, attr.rx, attr.ry, attr.axisRotation, attr.startAngle, attr.endAngle, attr.anticlockwise);
  7445. }
  7446. });
  7447. /**
  7448. * @class Ext.draw.sprite.Rect
  7449. * @extends Ext.draw.sprite.Path
  7450. *
  7451. * A sprite that represents a rectangle.
  7452. *
  7453. * @example
  7454. * Ext.create({
  7455. * xtype: 'draw',
  7456. * renderTo: document.body,
  7457. * width: 600,
  7458. * height: 400,
  7459. * sprites: [{
  7460. * type: 'rect',
  7461. * x: 50,
  7462. * y: 50,
  7463. * width: 100,
  7464. * height: 100,
  7465. * fillStyle: '#1F6D91'
  7466. * }]
  7467. * });
  7468. */
  7469. Ext.define('Ext.draw.sprite.Rect', {
  7470. extend: 'Ext.draw.sprite.Path',
  7471. alias: 'sprite.rect',
  7472. type: 'rect',
  7473. inheritableStatics: {
  7474. def: {
  7475. processors: {
  7476. /**
  7477. * @cfg {Number} [x=0] The position of the sprite on the x-axis.
  7478. */
  7479. x: 'number',
  7480. /**
  7481. * @cfg {Number} [y=0] The position of the sprite on the y-axis.
  7482. */
  7483. y: 'number',
  7484. /**
  7485. * @cfg {Number} [width=8] The width of the sprite.
  7486. */
  7487. width: 'number',
  7488. /**
  7489. * @cfg {Number} [height=8] The height of the sprite.
  7490. */
  7491. height: 'number',
  7492. /**
  7493. * @cfg {Number} [radius=0] The radius of the rounded corners.
  7494. */
  7495. radius: 'number'
  7496. },
  7497. aliases: {},
  7498. triggers: {
  7499. x: 'path',
  7500. y: 'path',
  7501. width: 'path',
  7502. height: 'path',
  7503. radius: 'path'
  7504. },
  7505. defaults: {
  7506. x: 0,
  7507. y: 0,
  7508. width: 8,
  7509. height: 8,
  7510. radius: 0
  7511. }
  7512. }
  7513. },
  7514. updatePlainBBox: function(plain) {
  7515. var attr = this.attr;
  7516. plain.x = attr.x;
  7517. plain.y = attr.y;
  7518. plain.width = attr.width;
  7519. plain.height = attr.height;
  7520. },
  7521. updateTransformedBBox: function(transform, plain) {
  7522. this.attr.matrix.transformBBox(plain, this.attr.radius, transform);
  7523. },
  7524. updatePath: function(path, attr) {
  7525. var x = attr.x,
  7526. y = attr.y,
  7527. width = attr.width,
  7528. height = attr.height,
  7529. radius = Math.min(attr.radius, Math.abs(height) * 0.5, Math.abs(width) * 0.5);
  7530. if (radius === 0) {
  7531. path.rect(x, y, width, height);
  7532. } else {
  7533. path.moveTo(x + radius, y);
  7534. path.arcTo(x + width, y, x + width, y + height, radius);
  7535. path.arcTo(x + width, y + height, x, y + height, radius);
  7536. path.arcTo(x, y + height, x, y, radius);
  7537. path.arcTo(x, y, x + radius, y, radius);
  7538. path.closePath();
  7539. }
  7540. }
  7541. });
  7542. /**
  7543. * @class Ext.draw.sprite.Image
  7544. * @extends Ext.draw.sprite.Rect
  7545. *
  7546. * A sprite that represents an image.
  7547. */
  7548. Ext.define('Ext.draw.sprite.Image', {
  7549. extend: 'Ext.draw.sprite.Rect',
  7550. alias: 'sprite.image',
  7551. type: 'image',
  7552. statics: {
  7553. imageLoaders: {}
  7554. },
  7555. inheritableStatics: {
  7556. def: {
  7557. processors: {
  7558. /**
  7559. * @cfg {String} [src=''] The image source of the sprite.
  7560. */
  7561. src: 'string'
  7562. },
  7563. /**
  7564. * @private
  7565. * @cfg {Number} radius
  7566. */
  7567. triggers: {
  7568. src: 'src'
  7569. },
  7570. updaters: {
  7571. src: 'updateSource'
  7572. },
  7573. defaults: {
  7574. src: '',
  7575. /**
  7576. * @cfg {Number} [width=null] The width of the image.
  7577. * For consistent image size on all devices the width must be explicitly set.
  7578. * Otherwise the natural image width devided by the device pixel ratio
  7579. * (for a crisp looking image) will be used as the width of the sprite.
  7580. */
  7581. width: null,
  7582. /**
  7583. * @cfg {Number} [height=null] The height of the image.
  7584. * For consistent image size on all devices the height must be explicitly set.
  7585. * Otherwise the natural image height devided by the device pixel ratio
  7586. * (for a crisp looking image) will be used as the height of the sprite.
  7587. */
  7588. height: null
  7589. }
  7590. }
  7591. },
  7592. updateSurface: function(surface) {
  7593. if (surface) {
  7594. this.updateSource(this.attr);
  7595. }
  7596. },
  7597. updateSource: function(attr) {
  7598. var me = this,
  7599. src = attr.src,
  7600. surface = me.getSurface(),
  7601. loadingStub = Ext.draw.sprite.Image.imageLoaders[src],
  7602. width = attr.width,
  7603. height = attr.height,
  7604. imageLoader, i;
  7605. if (!surface) {
  7606. // First time this is called the sprite won't have a surface yet.
  7607. return;
  7608. }
  7609. if (!loadingStub) {
  7610. imageLoader = new Image();
  7611. loadingStub = Ext.draw.sprite.Image.imageLoaders[src] = {
  7612. image: imageLoader,
  7613. done: false,
  7614. pendingSprites: [
  7615. me
  7616. ],
  7617. pendingSurfaces: [
  7618. surface
  7619. ]
  7620. };
  7621. imageLoader.width = width;
  7622. imageLoader.height = height;
  7623. imageLoader.onload = function() {
  7624. var item;
  7625. if (!loadingStub.done) {
  7626. loadingStub.done = true;
  7627. for (i = 0; i < loadingStub.pendingSprites.length; i++) {
  7628. item = loadingStub.pendingSprites[i];
  7629. if (!item.destroyed) {
  7630. item.setDirty(true);
  7631. }
  7632. }
  7633. for (i = 0; i < loadingStub.pendingSurfaces.length; i++) {
  7634. item = loadingStub.pendingSurfaces[i];
  7635. if (!item.destroyed) {
  7636. item.renderFrame();
  7637. }
  7638. }
  7639. }
  7640. };
  7641. imageLoader.src = src;
  7642. } else {
  7643. Ext.Array.include(loadingStub.pendingSprites, me);
  7644. Ext.Array.include(loadingStub.pendingSurfaces, surface);
  7645. }
  7646. },
  7647. render: function(surface, ctx) {
  7648. var me = this,
  7649. attr = me.attr,
  7650. mat = attr.matrix,
  7651. src = attr.src,
  7652. x = attr.x,
  7653. y = attr.y,
  7654. width = attr.width,
  7655. height = attr.height,
  7656. loadingStub = Ext.draw.sprite.Image.imageLoaders[src],
  7657. image;
  7658. if (loadingStub && loadingStub.done) {
  7659. mat.toContext(ctx);
  7660. image = loadingStub.image;
  7661. ctx.drawImage(image, x, y, width || (image.naturalWidth || image.width) / surface.devicePixelRatio, height || (image.naturalHeight || image.height) / surface.devicePixelRatio);
  7662. }
  7663. //<debug>
  7664. // eslint-disable-next-line vars-on-top
  7665. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  7666. if (debug && debug.bbox) {
  7667. this.renderBBox(surface, ctx);
  7668. }
  7669. },
  7670. //</debug>
  7671. /**
  7672. * @private
  7673. */
  7674. isVisible: function() {
  7675. var attr = this.attr,
  7676. parent = this.getParent(),
  7677. hasParent = parent && (parent.isSurface || parent.isVisible()),
  7678. isSeen = hasParent && !attr.hidden && attr.globalAlpha;
  7679. return !!isSeen;
  7680. }
  7681. });
  7682. /**
  7683. * @class Ext.draw.sprite.Instancing
  7684. * @extends Ext.draw.sprite.Sprite
  7685. *
  7686. * Sprite that represents multiple instances based on the given template.
  7687. */
  7688. Ext.define('Ext.draw.sprite.Instancing', {
  7689. extend: 'Ext.draw.sprite.Sprite',
  7690. alias: 'sprite.instancing',
  7691. type: 'instancing',
  7692. isInstancing: true,
  7693. config: {
  7694. /**
  7695. * @cfg {Object} [template] The sprite template used by all instances.
  7696. */
  7697. template: null,
  7698. /**
  7699. * @cfg {Array} [instances]
  7700. * The instances of the {@link #template} sprite as configs of attributes.
  7701. */
  7702. instances: null
  7703. },
  7704. instances: null,
  7705. applyTemplate: function(template) {
  7706. var surface;
  7707. //<debug>
  7708. if (!Ext.isObject(template)) {
  7709. 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.");
  7710. } else if (template.isInstancing || template.isComposite) {
  7711. Ext.raise("Can't use an instancing or composite sprite " + "as a template for an instancing sprite.");
  7712. }
  7713. //</debug>
  7714. if (!template.isSprite) {
  7715. if (!template.xclass && !template.type) {
  7716. // For compatibility with legacy charts.
  7717. template.type = 'circle';
  7718. }
  7719. template = Ext.create(template.xclass || 'sprite.' + template.type, template);
  7720. }
  7721. surface = template.getSurface();
  7722. if (surface) {
  7723. surface.remove(template);
  7724. }
  7725. template.setParent(this);
  7726. return template;
  7727. },
  7728. updateTemplate: function(template, oldTemplate) {
  7729. if (oldTemplate) {
  7730. delete oldTemplate.ownAttr;
  7731. }
  7732. template.setSurface(this.getSurface());
  7733. // ownAttr is used to get a reference to the template's attributes
  7734. // when one of the instances is rendering, as at that moment the template's
  7735. // attributes (template.attr) are the instance's attributes.
  7736. template.ownAttr = template.attr;
  7737. this.clearAll();
  7738. this.setDirty(true);
  7739. },
  7740. updateInstances: function(instances) {
  7741. var i, ln;
  7742. this.clearAll();
  7743. if (Ext.isArray(instances)) {
  7744. for (i = 0 , ln = instances.length; i < ln; i++) {
  7745. this.add(instances[i]);
  7746. }
  7747. }
  7748. },
  7749. updateSurface: function(surface) {
  7750. var template = this.getTemplate();
  7751. if (template && !template.destroyed) {
  7752. template.setSurface(surface);
  7753. }
  7754. },
  7755. get: function(index) {
  7756. return this.instances[index];
  7757. },
  7758. getCount: function() {
  7759. return this.instances.length;
  7760. },
  7761. clearAll: function() {
  7762. var template = this.getTemplate();
  7763. template.attr.children = this.instances = [];
  7764. this.position = 0;
  7765. },
  7766. /**
  7767. * @deprecated 6.2.0
  7768. * Deprecated, use the {@link #add} method instead.
  7769. */
  7770. createInstance: function(config, bypassNormalization, avoidCopy) {
  7771. return this.add(config, bypassNormalization, avoidCopy);
  7772. },
  7773. /**
  7774. * Creates a new sprite instance.
  7775. *
  7776. * @param {Object} config The configuration of the instance.
  7777. * @param {Boolean} [bypassNormalization] 'true' to bypass attribute normalization.
  7778. * @param {Boolean} [avoidCopy] 'true' to avoid copying the `config` object.
  7779. * @return {Object} The attributes of the instance.
  7780. */
  7781. add: function(config, bypassNormalization, avoidCopy) {
  7782. var me = this,
  7783. template = me.getTemplate(),
  7784. originalAttr = template.attr,
  7785. attr = Ext.Object.chain(originalAttr);
  7786. template.modifiers.target.prepareAttributes(attr);
  7787. template.attr = attr;
  7788. template.setAttributes(config, bypassNormalization, avoidCopy);
  7789. attr.template = template;
  7790. me.instances.push(attr);
  7791. template.attr = originalAttr;
  7792. me.position++;
  7793. return attr;
  7794. },
  7795. /**
  7796. * Not supported.
  7797. *
  7798. * @return {null}
  7799. */
  7800. getBBox: function() {
  7801. return null;
  7802. },
  7803. /**
  7804. * Returns the bounding box for the instance at the given index.
  7805. *
  7806. * @param {Number} index The index of the instance.
  7807. * @param {Boolean} [isWithoutTransform] 'true' to not apply sprite transforms
  7808. * to the bounding box.
  7809. * @return {Object} The bounding box for the instance.
  7810. */
  7811. getBBoxFor: function(index, isWithoutTransform) {
  7812. var template = this.getTemplate(),
  7813. originalAttr = template.attr,
  7814. bbox;
  7815. template.attr = this.instances[index];
  7816. bbox = template.getBBox(isWithoutTransform);
  7817. template.attr = originalAttr;
  7818. return bbox;
  7819. },
  7820. /**
  7821. * @private
  7822. * Checks if the instancing sprite can be seen.
  7823. * @return {Boolean}
  7824. */
  7825. isVisible: function() {
  7826. var attr = this.attr,
  7827. parent = this.getParent(),
  7828. result;
  7829. result = parent && parent.isSurface && !attr.hidden && attr.globalAlpha;
  7830. return !!result;
  7831. },
  7832. /**
  7833. * @private
  7834. * Checks if the instance of an instancing sprite can be seen.
  7835. * @param {Number} index The index of the instance.
  7836. */
  7837. isInstanceVisible: function(index) {
  7838. var me = this,
  7839. template = me.getTemplate(),
  7840. originalAttr = template.attr,
  7841. instances = me.instances,
  7842. result = false;
  7843. if (!Ext.isNumber(index) || index < 0 || index >= instances.length || !me.isVisible()) {
  7844. return result;
  7845. }
  7846. template.attr = instances[index];
  7847. // TODO This is clearly a bug, fix it
  7848. // eslint-disable-next-line no-undef
  7849. result = template.isVisible(point, options);
  7850. template.attr = originalAttr;
  7851. return result;
  7852. },
  7853. render: function(surface, ctx, rect) {
  7854. //<debug>
  7855. if (!this.getTemplate()) {
  7856. Ext.raise('An instancing sprite must have a template.');
  7857. }
  7858. //</debug>
  7859. // eslint-disable-next-line vars-on-top
  7860. var me = this,
  7861. template = me.getTemplate(),
  7862. surfaceRect = surface.getRect(),
  7863. mat = me.attr.matrix,
  7864. originalAttr = template.attr,
  7865. instances = me.instances,
  7866. ln = me.position,
  7867. i;
  7868. mat.toContext(ctx);
  7869. template.preRender(surface, ctx, rect);
  7870. template.useAttributes(ctx, surfaceRect);
  7871. template.isSpriteInstance = true;
  7872. for (i = 0; i < ln; i++) {
  7873. if (instances[i].hidden) {
  7874. continue;
  7875. }
  7876. ctx.save();
  7877. template.attr = instances[i];
  7878. template.useAttributes(ctx, surfaceRect);
  7879. template.render(surface, ctx, rect);
  7880. ctx.restore();
  7881. }
  7882. template.isSpriteInstance = false;
  7883. template.attr = originalAttr;
  7884. },
  7885. /**
  7886. * Sets the attributes for the instance at the given index.
  7887. *
  7888. * @param {Number} index the index of the instance
  7889. * @param {Object} changes the attributes to change
  7890. * @param {Boolean} [bypassNormalization] 'true' to avoid attribute normalization
  7891. */
  7892. setAttributesFor: function(index, changes, bypassNormalization) {
  7893. var template = this.getTemplate(),
  7894. originalAttr = template.attr,
  7895. attr = this.instances[index];
  7896. if (!attr) {
  7897. return;
  7898. }
  7899. template.attr = attr;
  7900. if (bypassNormalization) {
  7901. changes = Ext.apply({}, changes);
  7902. } else {
  7903. changes = template.self.def.normalize(changes);
  7904. }
  7905. template.modifiers.target.pushDown(attr, changes);
  7906. template.attr = originalAttr;
  7907. },
  7908. destroy: function() {
  7909. var me = this,
  7910. template = me.getTemplate();
  7911. me.instances = null;
  7912. if (template) {
  7913. template.destroy();
  7914. }
  7915. me.callParent();
  7916. }
  7917. });
  7918. /**
  7919. * @private
  7920. * Adds hit testing methods to the Ext.draw.sprite.Instancing.
  7921. * Included by the Ext.draw.plugin.SpriteEvents.
  7922. */
  7923. Ext.define('Ext.draw.overrides.hittest.sprite.Instancing', {
  7924. override: 'Ext.draw.sprite.Instancing',
  7925. /**
  7926. * Performs a hit test on the instances of an instancing sprite.
  7927. * @param point A two-item array containing x and y coordinates of the point.
  7928. * @param options Hit testing options.
  7929. * @return {Object} A hit result object that contains more information about what
  7930. * exactly was hit or null if nothing was hit.
  7931. * @return {Boolean} return.isInstance `true` if an instance was hit.
  7932. * @return {Ext.draw.sprite.Instancing} return.sprite The instancing sprite.
  7933. * @return {Ext.draw.sprite.Sprite} return.template The template of the instancing sprite.
  7934. * @return {Object} return.instance The attributes of the instance.
  7935. * @return {Number} return.index The index of the instance.
  7936. */
  7937. hitTest: function(point, options) {
  7938. var me = this,
  7939. template = me.getTemplate(),
  7940. originalAttr = template.attr,
  7941. instances = me.instances,
  7942. ln = instances.length,
  7943. i = 0,
  7944. result = null;
  7945. if (!me.isVisible()) {
  7946. return result;
  7947. }
  7948. for (; i < ln; i++) {
  7949. template.attr = instances[i];
  7950. result = template.hitTest(point, options);
  7951. if (result) {
  7952. result.isInstance = true;
  7953. result.template = result.sprite;
  7954. result.sprite = this;
  7955. result.instance = instances[i];
  7956. result.index = i;
  7957. return result;
  7958. }
  7959. }
  7960. template.attr = originalAttr;
  7961. return result;
  7962. }
  7963. });
  7964. /**
  7965. * A sprite that represents a line.
  7966. *
  7967. * @example
  7968. * Ext.create({
  7969. * xtype: 'draw',
  7970. * renderTo: document.body,
  7971. * width: 600,
  7972. * height: 400,
  7973. * sprites: [{
  7974. * type: 'line',
  7975. * fromX: 20,
  7976. * fromY: 20,
  7977. * toX: 120,
  7978. * toY: 120,
  7979. * strokeStyle: '#1F6D91',
  7980. * lineWidth: 3
  7981. * }]
  7982. * });
  7983. */
  7984. Ext.define('Ext.draw.sprite.Line', {
  7985. extend: 'Ext.draw.sprite.Sprite',
  7986. alias: 'sprite.line',
  7987. type: 'line',
  7988. inheritableStatics: {
  7989. def: {
  7990. processors: {
  7991. fromX: 'number',
  7992. fromY: 'number',
  7993. toX: 'number',
  7994. toY: 'number',
  7995. crisp: 'bool'
  7996. },
  7997. defaults: {
  7998. fromX: 0,
  7999. fromY: 0,
  8000. toX: 1,
  8001. toY: 1,
  8002. crisp: false,
  8003. strokeStyle: 'black'
  8004. },
  8005. aliases: {
  8006. x1: 'fromX',
  8007. y1: 'fromY',
  8008. x2: 'toX',
  8009. y2: 'toY'
  8010. },
  8011. triggers: {
  8012. crisp: 'bbox'
  8013. }
  8014. }
  8015. },
  8016. updateLineBBox: function(bbox, isTransform, x1, y1, x2, y2) {
  8017. var attr = this.attr,
  8018. matrix = attr.matrix,
  8019. halfLineWidth = attr.lineWidth / 2,
  8020. fromX, fromY, toX, toY, p, angle, sin, cos, dx, dy;
  8021. if (attr.crisp) {
  8022. x1 = this.align(x1);
  8023. x2 = this.align(x2);
  8024. y1 = this.align(y1);
  8025. y2 = this.align(y2);
  8026. }
  8027. if (isTransform) {
  8028. p = matrix.transformPoint([
  8029. x1,
  8030. y1
  8031. ]);
  8032. x1 = p[0];
  8033. y1 = p[1];
  8034. p = matrix.transformPoint([
  8035. x2,
  8036. y2
  8037. ]);
  8038. x2 = p[0];
  8039. y2 = p[1];
  8040. }
  8041. fromX = Math.min(x1, x2);
  8042. toX = Math.max(x1, x2);
  8043. fromY = Math.min(y1, y2);
  8044. toY = Math.max(y1, y2);
  8045. angle = Math.atan2(toX - fromX, toY - fromY);
  8046. sin = Math.sin(angle);
  8047. cos = Math.cos(angle);
  8048. dx = halfLineWidth * cos;
  8049. dy = halfLineWidth * sin;
  8050. // Offset start and end points of the line by half its thickness,
  8051. // while accounting for line's angle.
  8052. fromX -= dx;
  8053. fromY -= dy;
  8054. toX += dx;
  8055. toY += dy;
  8056. bbox.x = fromX;
  8057. bbox.y = fromY;
  8058. bbox.width = toX - fromX;
  8059. bbox.height = toY - fromY;
  8060. },
  8061. updatePlainBBox: function(plain) {
  8062. var attr = this.attr;
  8063. this.updateLineBBox(plain, false, attr.fromX, attr.fromY, attr.toX, attr.toY);
  8064. },
  8065. updateTransformedBBox: function(transform, plain) {
  8066. var attr = this.attr;
  8067. this.updateLineBBox(transform, true, attr.fromX, attr.fromY, attr.toX, attr.toY);
  8068. },
  8069. align: function(x) {
  8070. return Math.round(x) - 0.5;
  8071. },
  8072. render: function(surface, ctx) {
  8073. var me = this,
  8074. attr = me.attr,
  8075. matrix = attr.matrix;
  8076. matrix.toContext(ctx);
  8077. ctx.beginPath();
  8078. if (attr.crisp) {
  8079. ctx.moveTo(me.align(attr.fromX), me.align(attr.fromY));
  8080. ctx.lineTo(me.align(attr.toX), me.align(attr.toY));
  8081. } else {
  8082. ctx.moveTo(attr.fromX, attr.fromY);
  8083. ctx.lineTo(attr.toX, attr.toY);
  8084. }
  8085. ctx.stroke();
  8086. //<debug>
  8087. // eslint-disable-next-line vars-on-top
  8088. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  8089. if (debug) {
  8090. // This assumes no part of the sprite is rendered after this call.
  8091. // If it is, we need to re-apply transformations.
  8092. // But the bounding box should always be rendered as is, untransformed.
  8093. this.attr.inverseMatrix.toContext(ctx);
  8094. if (debug.bbox) {
  8095. this.renderBBox(surface, ctx);
  8096. }
  8097. }
  8098. }
  8099. });
  8100. //</debug>
  8101. /**
  8102. * A sprite that represents a plus.
  8103. *
  8104. * @example
  8105. * Ext.create({
  8106. * xtype: 'draw',
  8107. * renderTo: document.body,
  8108. * width: 600,
  8109. * height: 400,
  8110. * sprites: [{
  8111. * type: 'plus',
  8112. * translationX: 100,
  8113. * translationY: 100,
  8114. * size: 40,
  8115. * fillStyle: '#1F6D91'
  8116. * }]
  8117. * });
  8118. */
  8119. Ext.define('Ext.draw.sprite.Plus', {
  8120. extend: 'Ext.draw.sprite.Path',
  8121. alias: 'sprite.plus',
  8122. inheritableStatics: {
  8123. def: {
  8124. processors: {
  8125. x: 'number',
  8126. y: 'number',
  8127. /**
  8128. * @cfg {Number} [size=4] The size of the sprite.
  8129. * Meant to be comparable to the size of a circle sprite with the same radius.
  8130. */
  8131. size: 'number'
  8132. },
  8133. defaults: {
  8134. x: 0,
  8135. y: 0,
  8136. size: 4
  8137. },
  8138. triggers: {
  8139. x: 'path',
  8140. y: 'path',
  8141. size: 'path'
  8142. }
  8143. }
  8144. },
  8145. updatePath: function(path, attr) {
  8146. var s = attr.size / 1.3,
  8147. x = attr.x - attr.lineWidth / 2,
  8148. y = attr.y;
  8149. path.fromSvgString('M'.concat(x - s / 2, ',', y - s / 2, 'l', [
  8150. 0,
  8151. -s,
  8152. s,
  8153. 0,
  8154. 0,
  8155. s,
  8156. s,
  8157. 0,
  8158. 0,
  8159. s,
  8160. -s,
  8161. 0,
  8162. 0,
  8163. s,
  8164. -s,
  8165. 0,
  8166. 0,
  8167. -s,
  8168. -s,
  8169. 0,
  8170. 0,
  8171. -s,
  8172. 'z'
  8173. ]));
  8174. }
  8175. });
  8176. /**
  8177. * @class Ext.draw.sprite.Sector
  8178. * @extends Ext.draw.sprite.Path
  8179. *
  8180. * A sprite representing a pie slice.
  8181. *
  8182. * @example
  8183. * Ext.create({
  8184. * xtype: 'draw',
  8185. * renderTo: document.body,
  8186. * width: 600,
  8187. * height: 400,
  8188. * sprites: [{
  8189. * type: 'sector',
  8190. * centerX: 100,
  8191. * centerY: 100,
  8192. * startAngle: -2.355,
  8193. * endAngle: -.785,
  8194. * endRho: 50,
  8195. * fillStyle: '#1F6D91'
  8196. * }]
  8197. * });
  8198. */
  8199. Ext.define('Ext.draw.sprite.Sector', {
  8200. extend: 'Ext.draw.sprite.Path',
  8201. alias: 'sprite.sector',
  8202. type: 'sector',
  8203. inheritableStatics: {
  8204. def: {
  8205. processors: {
  8206. /**
  8207. * @cfg {Number} [centerX=0] The center coordinate of the sprite on the x-axis.
  8208. */
  8209. centerX: 'number',
  8210. /**
  8211. * @cfg {Number} [centerY=0] The center coordinate of the sprite on the y-axis.
  8212. */
  8213. centerY: 'number',
  8214. /**
  8215. * @cfg {Number} [startAngle=0] The starting angle of the sprite.
  8216. */
  8217. startAngle: 'number',
  8218. /**
  8219. * @cfg {Number} [endAngle=0] The ending angle of the sprite.
  8220. */
  8221. endAngle: 'number',
  8222. /**
  8223. * @cfg {Number} [startRho=0] The starting point of the radius of the sprite.
  8224. */
  8225. startRho: 'number',
  8226. /**
  8227. * @cfg {Number} [endRho=150] The ending point of the radius of the sprite.
  8228. */
  8229. endRho: 'number',
  8230. /**
  8231. * @cfg {Number} [margin=0] The margin of the sprite from the center of pie.
  8232. */
  8233. margin: 'number'
  8234. },
  8235. aliases: {
  8236. rho: 'endRho'
  8237. },
  8238. triggers: {
  8239. centerX: 'path,bbox',
  8240. centerY: 'path,bbox',
  8241. startAngle: 'path,bbox',
  8242. endAngle: 'path,bbox',
  8243. startRho: 'path,bbox',
  8244. endRho: 'path,bbox',
  8245. margin: 'path,bbox'
  8246. },
  8247. defaults: {
  8248. centerX: 0,
  8249. centerY: 0,
  8250. startAngle: 0,
  8251. endAngle: 0,
  8252. startRho: 0,
  8253. endRho: 150,
  8254. margin: 0,
  8255. path: 'M 0,0'
  8256. }
  8257. }
  8258. },
  8259. getMidAngle: function() {
  8260. return this.midAngle || 0;
  8261. },
  8262. updatePath: function(path, attr) {
  8263. var startAngle = Math.min(attr.startAngle, attr.endAngle),
  8264. endAngle = Math.max(attr.startAngle, attr.endAngle),
  8265. midAngle = this.midAngle = (startAngle + endAngle) * 0.5,
  8266. fullPie = Ext.Number.isEqual(Math.abs(endAngle - startAngle), Ext.draw.Draw.pi2, 1.0E-10),
  8267. margin = attr.margin,
  8268. centerX = attr.centerX,
  8269. centerY = attr.centerY,
  8270. startRho = Math.min(attr.startRho, attr.endRho),
  8271. endRho = Math.max(attr.startRho, attr.endRho);
  8272. if (margin) {
  8273. centerX += margin * Math.cos(midAngle);
  8274. centerY += margin * Math.sin(midAngle);
  8275. }
  8276. if (!fullPie) {
  8277. path.moveTo(centerX + startRho * Math.cos(startAngle), centerY + startRho * Math.sin(startAngle));
  8278. path.lineTo(centerX + endRho * Math.cos(startAngle), centerY + endRho * Math.sin(startAngle));
  8279. }
  8280. path.arc(centerX, centerY, endRho, startAngle, endAngle, false);
  8281. path[fullPie ? 'moveTo' : 'lineTo'](centerX + startRho * Math.cos(endAngle), centerY + startRho * Math.sin(endAngle));
  8282. path.arc(centerX, centerY, startRho, endAngle, startAngle, true);
  8283. }
  8284. });
  8285. /**
  8286. * A sprite that represents a square.
  8287. *
  8288. * @example
  8289. * Ext.create({
  8290. * xtype: 'draw',
  8291. * renderTo: document.body,
  8292. * width: 600,
  8293. * height: 400,
  8294. * sprites: [{
  8295. * type: 'square',
  8296. * x: 100,
  8297. * y: 100,
  8298. * size: 50,
  8299. * fillStyle: '#1F6D91'
  8300. * }]
  8301. * });
  8302. */
  8303. Ext.define('Ext.draw.sprite.Square', {
  8304. extend: 'Ext.draw.sprite.Path',
  8305. alias: 'sprite.square',
  8306. inheritableStatics: {
  8307. def: {
  8308. processors: {
  8309. x: 'number',
  8310. y: 'number',
  8311. /**
  8312. * @cfg {Number} [size=4] The size of the sprite.
  8313. * Meant to be comparable to the size of a circle sprite with the same radius.
  8314. */
  8315. size: 'number'
  8316. },
  8317. defaults: {
  8318. x: 0,
  8319. y: 0,
  8320. size: 4
  8321. },
  8322. triggers: {
  8323. x: 'path',
  8324. y: 'path',
  8325. size: 'size'
  8326. }
  8327. }
  8328. },
  8329. updatePath: function(path, attr) {
  8330. var size = attr.size * 1.2,
  8331. s = size * 2,
  8332. x = attr.x - attr.lineWidth / 2,
  8333. y = attr.y;
  8334. path.fromSvgString('M'.concat(x - size, ',', y - size, 'l', [
  8335. s,
  8336. 0,
  8337. 0,
  8338. s,
  8339. -s,
  8340. 0,
  8341. 0,
  8342. -s,
  8343. 'z'
  8344. ]));
  8345. }
  8346. });
  8347. /**
  8348. * Utility class to provide a way to *approximately* measure the dimension of text
  8349. * without a drawing context.
  8350. */
  8351. Ext.define('Ext.draw.TextMeasurer', {
  8352. singleton: true,
  8353. requires: [
  8354. 'Ext.util.TextMetrics'
  8355. ],
  8356. measureDiv: null,
  8357. measureCache: {},
  8358. /**
  8359. * @cfg {Boolean} [precise=false]
  8360. * This singleton tries not to make use of the Ext.util.TextMetrics because it is
  8361. * several times slower than TextMeasurer's own solution. TextMetrics is more precise
  8362. * though, so if you have a case where the error is too big, you may want to set
  8363. * this config to `true` to get perfect results at the expense of performance.
  8364. * Note: defaults to `true` in IE8.
  8365. */
  8366. precise: Ext.isIE8,
  8367. measureDivTpl: {
  8368. id: 'ext-draw-text-measurer',
  8369. tag: 'div',
  8370. style: {
  8371. overflow: 'hidden',
  8372. position: 'relative',
  8373. // 'float' is a reserved word. Don't unquote, or it will break the CMD build.
  8374. 'float': 'left',
  8375. width: 0,
  8376. height: 0
  8377. },
  8378. //<debug>
  8379. // Tell the spec runner to ignore this element when checking if the dom is clean.
  8380. 'data-sticky': true,
  8381. //</debug>
  8382. children: {
  8383. tag: 'div',
  8384. style: {
  8385. display: 'block',
  8386. position: 'absolute',
  8387. x: -100000,
  8388. y: -100000,
  8389. padding: 0,
  8390. margin: 0,
  8391. 'z-index': -100000,
  8392. 'white-space': 'nowrap'
  8393. }
  8394. }
  8395. },
  8396. /**
  8397. * @private
  8398. * Measure the size of a text with specific font by using DOM to measure it.
  8399. * Could be very expensive therefore should be used lazily.
  8400. * @param {String} text
  8401. * @param {String} font
  8402. * @return {Object} An object with `width` and `height` properties.
  8403. * @return {Number} return.width
  8404. * @return {Number} return.height
  8405. */
  8406. actualMeasureText: function(text, font) {
  8407. var me = Ext.draw.TextMeasurer,
  8408. measureDiv = me.measureDiv,
  8409. FARAWAY = 100000,
  8410. size, parent;
  8411. if (!measureDiv) {
  8412. parent = Ext.Element.create({
  8413. //<debug>
  8414. // Tell the spec runner to ignore this element when checking if the dom is clean.
  8415. 'data-sticky': true,
  8416. //</debug>
  8417. style: {
  8418. "overflow": "hidden",
  8419. "position": "relative",
  8420. "float": "left",
  8421. // DO NOT REMOVE THE QUOTE OR IT WILL BREAK COMPRESSOR
  8422. "width": 0,
  8423. "height": 0
  8424. }
  8425. });
  8426. me.measureDiv = measureDiv = Ext.Element.create({
  8427. style: {
  8428. "position": 'absolute',
  8429. "x": FARAWAY,
  8430. "y": FARAWAY,
  8431. "z-index": -FARAWAY,
  8432. "white-space": "nowrap",
  8433. "display": 'block',
  8434. "padding": 0,
  8435. "margin": 0
  8436. }
  8437. });
  8438. Ext.getBody().appendChild(parent);
  8439. parent.appendChild(measureDiv);
  8440. }
  8441. if (font) {
  8442. measureDiv.setStyle({
  8443. font: font,
  8444. lineHeight: 'normal'
  8445. });
  8446. }
  8447. measureDiv.setText('(' + text + ')');
  8448. size = measureDiv.getSize();
  8449. measureDiv.setText('()');
  8450. size.width -= measureDiv.getSize().width;
  8451. return size;
  8452. },
  8453. /**
  8454. * Measure a single-line text with specific font.
  8455. * This will split the text into characters and add up their size.
  8456. * That may *not* be the exact size of the text as it is displayed.
  8457. * @param {String} text
  8458. * @param {String} font
  8459. * @return {Object} An object with `width` and `height` properties.
  8460. * @return {Number} return.width
  8461. * @return {Number} return.height
  8462. */
  8463. measureTextSingleLine: function(text, font) {
  8464. var width = 0,
  8465. height = 0,
  8466. cache, cachedItem, chars, charactor, i, ln, size;
  8467. if (this.precise) {
  8468. return this.preciseMeasureTextSingleLine(text, font);
  8469. }
  8470. text = text.toString();
  8471. cache = this.measureCache;
  8472. chars = text.split('');
  8473. if (!cache[font]) {
  8474. cache[font] = {};
  8475. }
  8476. cache = cache[font];
  8477. if (cache[text]) {
  8478. return cache[text];
  8479. }
  8480. for (i = 0 , ln = chars.length; i < ln; i++) {
  8481. charactor = chars[i];
  8482. if (!(cachedItem = cache[charactor])) {
  8483. size = this.actualMeasureText(charactor, font);
  8484. cachedItem = cache[charactor] = size;
  8485. }
  8486. width += cachedItem.width;
  8487. height = Math.max(height, cachedItem.height);
  8488. }
  8489. return cache[text] = {
  8490. width: width,
  8491. height: height
  8492. };
  8493. },
  8494. // A more precise but slower version of the measureTextSingleLine method.
  8495. preciseMeasureTextSingleLine: function(text, font) {
  8496. var measureDiv;
  8497. text = text.toString();
  8498. measureDiv = this.measureDiv || (this.measureDiv = Ext.getBody().createChild(this.measureDivTpl).down('div'));
  8499. measureDiv.setStyle({
  8500. font: font || ''
  8501. });
  8502. return Ext.util.TextMetrics.measure(measureDiv, text);
  8503. },
  8504. /**
  8505. * Measure a text with specific font.
  8506. * This will split the text to lines and add up their size.
  8507. * That may *not* be the exact size of the text as it is displayed.
  8508. * @param {String} text
  8509. * @param {String} font
  8510. * @return {Object} An object with `width`, `height` and `sizes` properties.
  8511. * @return {Number} return.width
  8512. * @return {Number} return.height
  8513. * @return {Object} return.sizes Results of individual line measurements, in case of multiline
  8514. * text.
  8515. */
  8516. measureText: function(text, font) {
  8517. var lines = text.split('\n'),
  8518. ln = lines.length,
  8519. height = 0,
  8520. width = 0,
  8521. line, i, sizes;
  8522. if (ln === 1) {
  8523. return this.measureTextSingleLine(text, font);
  8524. }
  8525. sizes = [];
  8526. for (i = 0; i < ln; i++) {
  8527. line = this.measureTextSingleLine(lines[i], font);
  8528. sizes.push(line);
  8529. height += line.height;
  8530. width = Math.max(width, line.width);
  8531. }
  8532. return {
  8533. width: width,
  8534. height: height,
  8535. sizes: sizes
  8536. };
  8537. }
  8538. });
  8539. /**
  8540. * @class Ext.draw.sprite.Text
  8541. * @extends Ext.draw.sprite.Sprite
  8542. *
  8543. * A sprite that represents text.
  8544. *
  8545. * @example
  8546. * Ext.create({
  8547. * xtype: 'draw',
  8548. * renderTo: document.body,
  8549. * width: 600,
  8550. * height: 400,
  8551. * sprites: [{
  8552. * type: 'text',
  8553. * x: 50,
  8554. * y: 50,
  8555. * text: 'Sencha',
  8556. * fontSize: 30,
  8557. * fillStyle: '#1F6D91'
  8558. * }]
  8559. * });
  8560. */
  8561. /* eslint-disable indent */
  8562. Ext.define('Ext.draw.sprite.Text', function() {
  8563. // Absolute font sizes.
  8564. var fontSizes = {
  8565. 'xx-small': true,
  8566. 'x-small': true,
  8567. 'small': true,
  8568. 'medium': true,
  8569. 'large': true,
  8570. 'x-large': true,
  8571. 'xx-large': true
  8572. },
  8573. fontWeights = {
  8574. normal: true,
  8575. bold: true,
  8576. bolder: true,
  8577. lighter: true,
  8578. 100: true,
  8579. 200: true,
  8580. 300: true,
  8581. 400: true,
  8582. 500: true,
  8583. 600: true,
  8584. 700: true,
  8585. 800: true,
  8586. 900: true
  8587. },
  8588. textAlignments = {
  8589. start: 'start',
  8590. left: 'start',
  8591. center: 'center',
  8592. middle: 'center',
  8593. end: 'end',
  8594. right: 'end'
  8595. },
  8596. textBaselines = {
  8597. top: 'top',
  8598. hanging: 'hanging',
  8599. middle: 'middle',
  8600. center: 'middle',
  8601. alphabetic: 'alphabetic',
  8602. ideographic: 'ideographic',
  8603. bottom: 'bottom'
  8604. };
  8605. return {
  8606. extend: 'Ext.draw.sprite.Sprite',
  8607. requires: [
  8608. 'Ext.draw.TextMeasurer',
  8609. 'Ext.draw.Color'
  8610. ],
  8611. alias: 'sprite.text',
  8612. type: 'text',
  8613. lineBreakRe: /\r?\n/g,
  8614. //<debug>
  8615. statics: {
  8616. /**
  8617. * Debug rendering options:
  8618. *
  8619. * debug: {
  8620. * bbox: true // renders the bounding box of the text sprite
  8621. * }
  8622. *
  8623. */
  8624. debug: false,
  8625. fontSizes: fontSizes,
  8626. fontWeights: fontWeights,
  8627. textAlignments: textAlignments,
  8628. textBaselines: textBaselines
  8629. },
  8630. //</debug>
  8631. inheritableStatics: {
  8632. def: {
  8633. animationProcessors: {
  8634. text: 'text'
  8635. },
  8636. processors: {
  8637. /**
  8638. * @cfg {Number} [x=0]
  8639. * The position of the sprite on the x-axis.
  8640. */
  8641. x: 'number',
  8642. /**
  8643. * @cfg {Number} [y=0]
  8644. * The position of the sprite on the y-axis.
  8645. */
  8646. y: 'number',
  8647. /**
  8648. * @cfg {String} [text='']
  8649. * The text represented in the sprite.
  8650. */
  8651. text: 'string',
  8652. /**
  8653. * @cfg {String/Number} [fontSize='10px']
  8654. * The size of the font displayed.
  8655. */
  8656. fontSize: function(n) {
  8657. // Numbers as strings will be converted to numbers,
  8658. // null will be converted to 0.
  8659. if (Ext.isNumber(+n)) {
  8660. return n + 'px';
  8661. } else if (n.match(Ext.dom.Element.unitRe)) {
  8662. return n;
  8663. } else if (n in fontSizes) {
  8664. return n;
  8665. }
  8666. },
  8667. /**
  8668. * @cfg {String} [fontStyle='']
  8669. * The style of the font displayed. {normal, italic, oblique}
  8670. */
  8671. fontStyle: 'enums(,italic,oblique)',
  8672. /**
  8673. * @cfg {String} [fontVariant='']
  8674. * The variant of the font displayed. {normal, small-caps}
  8675. */
  8676. fontVariant: 'enums(,small-caps)',
  8677. /**
  8678. * @cfg {String} [fontWeight='']
  8679. * The weight of the font displayed. {normal, bold, bolder, lighter}
  8680. */
  8681. fontWeight: function(n) {
  8682. if (n in fontWeights) {
  8683. return String(n);
  8684. } else {
  8685. return '';
  8686. }
  8687. },
  8688. /**
  8689. * @cfg {String} [fontFamily='sans-serif']
  8690. * The family of the font displayed.
  8691. */
  8692. fontFamily: 'string',
  8693. /**
  8694. * @cfg {"left"/"right"/"center"/"start"/"end"} [textAlign='start']
  8695. * The alignment of the text displayed.
  8696. */
  8697. textAlign: function(n) {
  8698. return textAlignments[n] || 'center';
  8699. },
  8700. /**
  8701. * @cfg {String} [textBaseline="alphabetic"]
  8702. * The baseline of the text displayed.
  8703. * {top, hanging, middle, alphabetic, ideographic, bottom}
  8704. */
  8705. textBaseline: function(n) {
  8706. return textBaselines[n] || 'alphabetic';
  8707. },
  8708. //<debug>
  8709. debug: 'default',
  8710. //</debug>
  8711. /**
  8712. * @cfg {String} [font='10px sans-serif']
  8713. * The font displayed.
  8714. */
  8715. font: 'string'
  8716. },
  8717. aliases: {
  8718. 'font-size': 'fontSize',
  8719. 'font-family': 'fontFamily',
  8720. 'font-weight': 'fontWeight',
  8721. 'font-variant': 'fontVariant',
  8722. 'text-anchor': 'textAlign',
  8723. 'dominant-baseline': 'textBaseline'
  8724. },
  8725. defaults: {
  8726. fontStyle: '',
  8727. fontVariant: '',
  8728. fontWeight: '',
  8729. fontSize: '10px',
  8730. fontFamily: 'sans-serif',
  8731. font: '10px sans-serif',
  8732. textBaseline: 'alphabetic',
  8733. textAlign: 'start',
  8734. strokeStyle: 'rgba(0, 0, 0, 0)',
  8735. fillStyle: '#000',
  8736. x: 0,
  8737. y: 0,
  8738. text: ''
  8739. },
  8740. triggers: {
  8741. fontStyle: 'fontX,bbox',
  8742. fontVariant: 'fontX,bbox',
  8743. fontWeight: 'fontX,bbox',
  8744. fontSize: 'fontX,bbox',
  8745. fontFamily: 'fontX,bbox',
  8746. font: 'font,bbox,canvas',
  8747. textBaseline: 'bbox',
  8748. textAlign: 'bbox',
  8749. x: 'bbox',
  8750. y: 'bbox',
  8751. text: 'bbox'
  8752. },
  8753. updaters: {
  8754. fontX: 'makeFontShorthand',
  8755. font: 'parseFontShorthand'
  8756. }
  8757. }
  8758. },
  8759. config: {
  8760. /**
  8761. * @private
  8762. * If the value is boolean, it overrides the TextMeasurer's 'precise' config
  8763. * (for the given sprite only).
  8764. */
  8765. preciseMeasurement: undefined
  8766. },
  8767. constructor: function(config) {
  8768. var key;
  8769. if (config && config.font) {
  8770. config = Ext.clone(config);
  8771. for (key in config) {
  8772. if (key !== 'font' && key.indexOf('font') === 0) {
  8773. delete config[key];
  8774. }
  8775. }
  8776. }
  8777. Ext.draw.sprite.Sprite.prototype.constructor.call(this, config);
  8778. },
  8779. // Maps values to font properties they belong to.
  8780. fontValuesMap: {
  8781. // Skip 'normal' and 'inherit' values, as the first one
  8782. // is the default and the second one has no meaning in Canvas.
  8783. 'italic': 'fontStyle',
  8784. 'oblique': 'fontStyle',
  8785. 'small-caps': 'fontVariant',
  8786. 'bold': 'fontWeight',
  8787. 'bolder': 'fontWeight',
  8788. 'lighter': 'fontWeight',
  8789. '100': 'fontWeight',
  8790. '200': 'fontWeight',
  8791. '300': 'fontWeight',
  8792. '400': 'fontWeight',
  8793. '500': 'fontWeight',
  8794. '600': 'fontWeight',
  8795. '700': 'fontWeight',
  8796. '800': 'fontWeight',
  8797. '900': 'fontWeight',
  8798. // Absolute font sizes.
  8799. 'xx-small': 'fontSize',
  8800. 'x-small': 'fontSize',
  8801. 'small': 'fontSize',
  8802. 'medium': 'fontSize',
  8803. 'large': 'fontSize',
  8804. 'x-large': 'fontSize',
  8805. 'xx-large': 'fontSize'
  8806. },
  8807. // Relative font sizes like 'smaller' and 'larger'
  8808. // have no meaning, and are not included.
  8809. makeFontShorthand: function(attr) {
  8810. var parts = [];
  8811. if (attr.fontStyle) {
  8812. parts.push(attr.fontStyle);
  8813. }
  8814. if (attr.fontVariant) {
  8815. parts.push(attr.fontVariant);
  8816. }
  8817. if (attr.fontWeight) {
  8818. parts.push(attr.fontWeight);
  8819. }
  8820. if (attr.fontSize) {
  8821. parts.push(attr.fontSize);
  8822. }
  8823. if (attr.fontFamily) {
  8824. parts.push(attr.fontFamily);
  8825. }
  8826. this.setAttributes({
  8827. font: parts.join(' ')
  8828. }, true);
  8829. },
  8830. // For more info see:
  8831. // http://www.w3.org/TR/CSS21/fonts.html#font-shorthand
  8832. parseFontShorthand: function(attr) {
  8833. var value = attr.font,
  8834. ln = value.length,
  8835. changes = {},
  8836. dispatcher = this.fontValuesMap,
  8837. start = 0,
  8838. end, slashIndex, part, fontProperty;
  8839. while (start < ln && end !== -1) {
  8840. end = value.indexOf(' ', start);
  8841. if (end < 0) {
  8842. part = value.substr(start);
  8843. } else if (end > start) {
  8844. part = value.substr(start, end - start);
  8845. } else {
  8846. continue;
  8847. }
  8848. // Since Canvas fillText doesn't support multi-line text,
  8849. // it is assumed that line height is never specified, i.e.
  8850. // in entries like these the part after slash is omitted:
  8851. // 12px/14px sans-serif
  8852. // x-large/110% "New Century Schoolbook", serif
  8853. slashIndex = part.indexOf('/');
  8854. if (slashIndex > 0) {
  8855. part = part.substr(0, slashIndex);
  8856. } else if (slashIndex === 0) {
  8857. continue;
  8858. }
  8859. // All optional font properties (fontStyle, fontVariant or fontWeight) can be 'normal'.
  8860. // They can go in any order. Which ones are 'normal' is determined by elimination.
  8861. // E.g. if only fontVariant is specified, then 'normal' applies to fontStyle
  8862. // and fontWeight.
  8863. // If none are explicitly mentioned, then all are 'normal'.
  8864. if (part !== 'normal' && part !== 'inherit') {
  8865. fontProperty = dispatcher[part];
  8866. if (fontProperty) {
  8867. changes[fontProperty] = part;
  8868. } else if (part.match(Ext.dom.Element.unitRe)) {
  8869. changes.fontSize = part;
  8870. } else {
  8871. // Assuming that font family always goes last in the font shorthand.
  8872. changes.fontFamily = value.substr(start);
  8873. break;
  8874. }
  8875. }
  8876. start = end + 1;
  8877. }
  8878. if (!changes.fontStyle) {
  8879. changes.fontStyle = '';
  8880. }
  8881. // same as 'normal'
  8882. if (!changes.fontVariant) {
  8883. changes.fontVariant = '';
  8884. }
  8885. // same as 'normal'
  8886. if (!changes.fontWeight) {
  8887. changes.fontWeight = '';
  8888. }
  8889. // same as 'normal'
  8890. this.setAttributes(changes, true);
  8891. },
  8892. fontProperties: {
  8893. fontStyle: true,
  8894. fontVariant: true,
  8895. fontWeight: true,
  8896. fontSize: true,
  8897. fontFamily: true
  8898. },
  8899. setAttributes: function(changes, bypassNormalization, avoidCopy) {
  8900. var key, obj;
  8901. // Discard individual font properties if 'font' shorthand was also provided.
  8902. // Example: a user provides a config for chart series labels, using the font
  8903. // shorthand, which is parsed into individual font properties and corresponding
  8904. // sprite attributes are set. Then a theme is applied to the chart, and
  8905. // individual font properties from the theme make up the new font shorthand
  8906. // that overrides the previous one. In other words, no matter what font
  8907. // the user has specified, theme font will be used.
  8908. // This workaround relies on the fact that the theme merges its own config with
  8909. // the user config (where user config values take over the same theme config
  8910. // values). So both user font shorthand and individual font properties from
  8911. // the theme are present in the resulting config (since there are no collisions),
  8912. // which ends up here as the 'changes' parameter.
  8913. // If the user wants their font config to merged with the the theme's font config,
  8914. // instead of taking over it, individual font properties should be used
  8915. // by the user as well.
  8916. if (changes && changes.font) {
  8917. obj = {};
  8918. for (key in changes) {
  8919. if (!(key in this.fontProperties)) {
  8920. obj[key] = changes[key];
  8921. }
  8922. }
  8923. changes = obj;
  8924. }
  8925. this.callParent([
  8926. changes,
  8927. bypassNormalization,
  8928. avoidCopy
  8929. ]);
  8930. },
  8931. // Overriding the getBBox method of the abstract sprite here to always
  8932. // recalculate the bounding box of the text in flipped RTL mode
  8933. // because in that case the position of the sprite depends not just on
  8934. // the value of its 'x' attribute, but also on the width of the surface
  8935. // the sprite belongs to.
  8936. getBBox: function(isWithoutTransform) {
  8937. var me = this,
  8938. plain = me.attr.bbox.plain,
  8939. surface = me.getSurface();
  8940. //<debug>
  8941. // The sprite's bounding box won't account for RTL if it doesn't
  8942. // belong to a surface.
  8943. // if (!surface) {
  8944. // Ext.raise("The sprite does not belong to a surface.");
  8945. // }
  8946. //</debug>
  8947. if (plain.dirty) {
  8948. me.updatePlainBBox(plain);
  8949. plain.dirty = false;
  8950. }
  8951. if (surface && surface.getInherited().rtl && surface.getFlipRtlText()) {
  8952. // Since sprite's attributes haven't actually changed at this point,
  8953. // and we just want to update the position of its bbox
  8954. // based on surface's width, there's no reason to perform
  8955. // expensive text measurement operation here,
  8956. // so we can use the result of the last measurement instead.
  8957. me.updatePlainBBox(plain, true);
  8958. }
  8959. return me.callParent([
  8960. isWithoutTransform
  8961. ]);
  8962. },
  8963. rtlAlignments: {
  8964. start: 'end',
  8965. center: 'center',
  8966. end: 'start'
  8967. },
  8968. updatePlainBBox: function(plain, useOldSize) {
  8969. var me = this,
  8970. attr = me.attr,
  8971. x = attr.x,
  8972. y = attr.y,
  8973. dx = [],
  8974. font = attr.font,
  8975. text = attr.text,
  8976. baseline = attr.textBaseline,
  8977. alignment = attr.textAlign,
  8978. precise = me.getPreciseMeasurement(),
  8979. size, textMeasurerPrecision;
  8980. if (useOldSize && me.oldSize) {
  8981. size = me.oldSize;
  8982. } else {
  8983. textMeasurerPrecision = Ext.draw.TextMeasurer.precise;
  8984. if (Ext.isBoolean(precise)) {
  8985. Ext.draw.TextMeasurer.precise = precise;
  8986. }
  8987. size = me.oldSize = Ext.draw.TextMeasurer.measureText(text, font);
  8988. Ext.draw.TextMeasurer.precise = textMeasurerPrecision;
  8989. }
  8990. // eslint-disable-next-line vars-on-top, one-var
  8991. var surface = me.getSurface(),
  8992. isRtl = (surface && surface.getInherited().rtl) || false,
  8993. flipRtlText = isRtl && surface.getFlipRtlText(),
  8994. sizes = size.sizes,
  8995. blockHeight = size.height,
  8996. blockWidth = size.width,
  8997. ln = sizes ? sizes.length : 0,
  8998. lineWidth, rect,
  8999. i = 0;
  9000. // To get consistent results in all browsers we don't apply textAlign
  9001. // and textBaseline attributes of the sprite to context, so text is always
  9002. // left aligned and has an alphabetic baseline.
  9003. //
  9004. // Instead we have to calculate the horizontal offset of each line
  9005. // based on sprite's textAlign, and the vertical offset of the bounding box
  9006. // based on sprite's textBaseline.
  9007. //
  9008. // These offsets are then used by the sprite's 'render' method
  9009. // to position text properly.
  9010. switch (baseline) {
  9011. case 'hanging':
  9012. case 'top':
  9013. break;
  9014. case 'ideographic':
  9015. case 'bottom':
  9016. y -= blockHeight;
  9017. break;
  9018. case 'alphabetic':
  9019. y -= blockHeight * 0.8;
  9020. break;
  9021. case 'middle':
  9022. y -= blockHeight * 0.5;
  9023. break;
  9024. }
  9025. if (flipRtlText) {
  9026. rect = surface.getRect();
  9027. x = rect[2] - rect[0] - x;
  9028. alignment = me.rtlAlignments[alignment];
  9029. }
  9030. switch (alignment) {
  9031. case 'start':
  9032. if (isRtl) {
  9033. for (; i < ln; i++) {
  9034. lineWidth = sizes[i].width;
  9035. dx.push(-(blockWidth - lineWidth));
  9036. }
  9037. };
  9038. break;
  9039. case 'end':
  9040. x -= blockWidth;
  9041. if (isRtl) {
  9042. break;
  9043. };
  9044. for (; i < ln; i++) {
  9045. lineWidth = sizes[i].width;
  9046. dx.push(blockWidth - lineWidth);
  9047. };
  9048. break;
  9049. case 'center':
  9050. x -= blockWidth * 0.5;
  9051. for (; i < ln; i++) {
  9052. lineWidth = sizes[i].width;
  9053. dx.push((isRtl ? -1 : 1) * (blockWidth - lineWidth) * 0.5);
  9054. };
  9055. break;
  9056. }
  9057. attr.textAlignOffsets = dx;
  9058. plain.x = x;
  9059. plain.y = y;
  9060. plain.width = blockWidth;
  9061. plain.height = blockHeight;
  9062. },
  9063. setText: function(text) {
  9064. this.setAttributes({
  9065. text: text
  9066. }, true);
  9067. },
  9068. render: function(surface, ctx, rect) {
  9069. var me = this,
  9070. attr = me.attr,
  9071. mat = Ext.draw.Matrix.fly(attr.matrix.elements.slice(0)),
  9072. bbox = me.getBBox(true),
  9073. dx = attr.textAlignOffsets,
  9074. none = Ext.util.Color.RGBA_NONE,
  9075. x, y, i, lines, lineHeight;
  9076. if (attr.text.length === 0) {
  9077. return;
  9078. }
  9079. lines = attr.text.split(me.lineBreakRe);
  9080. lineHeight = bbox.height / lines.length;
  9081. // Simulate textBaseline and textAlign.
  9082. x = attr.bbox.plain.x;
  9083. // lineHeight * 0.78 is the approximate distance between the top
  9084. // and the alphabetic baselines
  9085. y = attr.bbox.plain.y + lineHeight * 0.78;
  9086. mat.toContext(ctx);
  9087. if (surface.getInherited().rtl) {
  9088. // Canvas element in RTL mode automatically flips text alignment.
  9089. // Here we compensate for that change.
  9090. // So text is still positioned and aligned as in the LTR mode,
  9091. // but the direction of the text is RTL.
  9092. x += attr.bbox.plain.width;
  9093. }
  9094. for (i = 0; i < lines.length; i++) {
  9095. if (ctx.fillStyle !== none) {
  9096. ctx.fillText(lines[i], x + (dx[i] || 0), y + lineHeight * i);
  9097. }
  9098. if (ctx.strokeStyle !== none) {
  9099. ctx.strokeText(lines[i], x + (dx[i] || 0), y + lineHeight * i);
  9100. }
  9101. }
  9102. //<debug>
  9103. // eslint-disable-next-line vars-on-top
  9104. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  9105. if (debug) {
  9106. // This assumes no part of the sprite is rendered after this call.
  9107. // If it is, we need to re-apply transformations.
  9108. // But the bounding box is already transformed, so we remove the transformation.
  9109. this.attr.inverseMatrix.toContext(ctx);
  9110. if (debug.bbox) {
  9111. me.renderBBox(surface, ctx);
  9112. }
  9113. }
  9114. }
  9115. };
  9116. });
  9117. //</debug>
  9118. /**
  9119. * A veritical line sprite. The x and y configs set the center of the line with the size
  9120. * value determining the height of the line (the line will be twice the height of 'size'
  9121. * since 'size' is added to above and below 'y' to set the line endpoints).
  9122. *
  9123. * @example
  9124. * Ext.create({
  9125. * xtype: 'draw',
  9126. * renderTo: document.body,
  9127. * width: 600,
  9128. * height: 400,
  9129. * sprites: [{
  9130. * type: 'tick',
  9131. * x: 20,
  9132. * y: 40,
  9133. * size: 10,
  9134. * strokeStyle: '#388FAD',
  9135. * lineWidth: 2
  9136. * }]
  9137. * });
  9138. */
  9139. Ext.define('Ext.draw.sprite.Tick', {
  9140. extend: 'Ext.draw.sprite.Line',
  9141. alias: 'sprite.tick',
  9142. inheritableStatics: {
  9143. def: {
  9144. processors: {
  9145. /**
  9146. * @cfg {Object} x The position of the center of the sprite on the x-axis.
  9147. */
  9148. x: 'number',
  9149. /**
  9150. * @cfg {Object} y The position of the center of the sprite on the y-axis.
  9151. */
  9152. y: 'number',
  9153. /**
  9154. * @cfg {Number} [size=4] The size of the sprite.
  9155. * Meant to be comparable to the size of a circle sprite with the same radius.
  9156. */
  9157. size: 'number'
  9158. },
  9159. defaults: {
  9160. x: 0,
  9161. y: 0,
  9162. size: 4
  9163. },
  9164. triggers: {
  9165. x: 'tick',
  9166. y: 'tick',
  9167. size: 'tick'
  9168. },
  9169. updaters: {
  9170. tick: function(attr) {
  9171. var size = attr.size * 1.5,
  9172. halfLineWidth = attr.lineWidth / 2,
  9173. x = attr.x,
  9174. y = attr.y;
  9175. this.setAttributes({
  9176. fromX: x - halfLineWidth,
  9177. fromY: y - size,
  9178. toX: x - halfLineWidth,
  9179. toY: y + size
  9180. });
  9181. }
  9182. }
  9183. }
  9184. }
  9185. });
  9186. /**
  9187. * A sprite that represents a triangle.
  9188. *
  9189. * @example
  9190. * Ext.create({
  9191. * xtype: 'draw',
  9192. * renderTo: document.body,
  9193. * width: 600,
  9194. * height: 400,
  9195. * sprites: [{
  9196. * type: 'triangle',
  9197. * size: 50,
  9198. * translationX: 100,
  9199. * translationY: 100,
  9200. * fillStyle: '#1F6D91'
  9201. * }]
  9202. * });
  9203. *
  9204. */
  9205. Ext.define('Ext.draw.sprite.Triangle', {
  9206. extend: 'Ext.draw.sprite.Path',
  9207. alias: 'sprite.triangle',
  9208. inheritableStatics: {
  9209. def: {
  9210. processors: {
  9211. x: 'number',
  9212. y: 'number',
  9213. /**
  9214. * @cfg {Number} [size=4] The size of the sprite.
  9215. * Meant to be comparable to the size of a circle sprite with the same radius.
  9216. */
  9217. size: 'number'
  9218. },
  9219. defaults: {
  9220. x: 0,
  9221. y: 0,
  9222. size: 4
  9223. },
  9224. triggers: {
  9225. x: 'path',
  9226. y: 'path',
  9227. size: 'path'
  9228. }
  9229. }
  9230. },
  9231. updatePath: function(path, attr) {
  9232. var s = attr.size * 2.2,
  9233. x = attr.x,
  9234. y = attr.y;
  9235. path.fromSvgString('M'.concat(x, ',', y, 'm0-', s * 0.48, 'l', s * 0.5, ',', s * 0.87, '-', s, ',0z'));
  9236. }
  9237. });
  9238. /**
  9239. * Linear 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: 'linear',
  9254. * degrees: 180,
  9255. * stops: [{
  9256. * offset: 0,
  9257. * color: '#1F6D91'
  9258. * }, {
  9259. * offset: 1,
  9260. * color: '#90BCC9'
  9261. * }]
  9262. * }
  9263. * }]
  9264. * });
  9265. */
  9266. Ext.define('Ext.draw.gradient.Linear', {
  9267. extend: 'Ext.draw.gradient.Gradient',
  9268. requires: [
  9269. 'Ext.draw.Color'
  9270. ],
  9271. type: 'linear',
  9272. config: {
  9273. /**
  9274. * @cfg {Number} degrees
  9275. * The angle of rotation of the gradient in degrees.
  9276. */
  9277. degrees: 0,
  9278. /**
  9279. * @cfg {Number} radians
  9280. * The angle of rotation of the gradient in radians.
  9281. */
  9282. radians: 0
  9283. },
  9284. applyRadians: function(radians, oldRadians) {
  9285. if (Ext.isNumber(radians)) {
  9286. return radians;
  9287. }
  9288. return oldRadians;
  9289. },
  9290. applyDegrees: function(degrees, oldDegrees) {
  9291. if (Ext.isNumber(degrees)) {
  9292. return degrees;
  9293. }
  9294. return oldDegrees;
  9295. },
  9296. updateRadians: function(radians) {
  9297. this.setDegrees(Ext.draw.Draw.degrees(radians));
  9298. },
  9299. updateDegrees: function(degrees) {
  9300. this.setRadians(Ext.draw.Draw.rad(degrees));
  9301. },
  9302. /**
  9303. * @method generateGradient
  9304. * @inheritdoc
  9305. */
  9306. generateGradient: function(ctx, bbox) {
  9307. var angle = this.getRadians(),
  9308. cos = Math.cos(angle),
  9309. sin = Math.sin(angle),
  9310. w = bbox.width,
  9311. h = bbox.height,
  9312. cx = bbox.x + w * 0.5,
  9313. cy = bbox.y + h * 0.5,
  9314. stops = this.getStops(),
  9315. ln = stops.length,
  9316. gradient, l, i;
  9317. if (Ext.isNumber(cx) && Ext.isNumber(cy) && h > 0 && w > 0) {
  9318. l = (Math.sqrt(h * h + w * w) * Math.abs(Math.cos(angle - Math.atan(h / w)))) / 2;
  9319. gradient = ctx.createLinearGradient(cx + cos * l, cy + sin * l, cx - cos * l, cy - sin * l);
  9320. for (i = 0; i < ln; i++) {
  9321. gradient.addColorStop(stops[i].offset, stops[i].color);
  9322. }
  9323. return gradient;
  9324. }
  9325. return Ext.util.Color.NONE;
  9326. }
  9327. });
  9328. /**
  9329. * Radial gradient.
  9330. *
  9331. * @example
  9332. * Ext.create({
  9333. * xtype: 'draw',
  9334. * renderTo: document.body,
  9335. * width: 600,
  9336. * height: 400,
  9337. * sprites: [{
  9338. * type: 'circle',
  9339. * cx: 100,
  9340. * cy: 100,
  9341. * r: 100,
  9342. * fillStyle: {
  9343. * type: 'radial',
  9344. * start: {
  9345. * x: 0,
  9346. * y: 0,
  9347. * r: 0
  9348. * },
  9349. * end: {
  9350. * x: 0,
  9351. * y: 0,
  9352. * r: 1
  9353. * },
  9354. * stops: [{
  9355. * offset: 0,
  9356. * color: '#90BCC9'
  9357. * }, {
  9358. * offset: 1,
  9359. * color: '#1F6D91'
  9360. * }]
  9361. * }
  9362. * }]
  9363. * });
  9364. */
  9365. Ext.define('Ext.draw.gradient.Radial', {
  9366. extend: 'Ext.draw.gradient.Gradient',
  9367. type: 'radial',
  9368. config: {
  9369. /**
  9370. * @cfg {Object} start
  9371. * The starting circle of the gradient.
  9372. */
  9373. start: {
  9374. x: 0,
  9375. y: 0,
  9376. r: 0
  9377. },
  9378. /**
  9379. * @cfg {Object} end
  9380. * The ending circle of the gradient.
  9381. */
  9382. end: {
  9383. x: 0,
  9384. y: 0,
  9385. r: 1
  9386. }
  9387. },
  9388. applyStart: function(newStart, oldStart) {
  9389. var circle;
  9390. if (!oldStart) {
  9391. return newStart;
  9392. }
  9393. circle = {
  9394. x: oldStart.x,
  9395. y: oldStart.y,
  9396. r: oldStart.r
  9397. };
  9398. if ('x' in newStart) {
  9399. circle.x = newStart.x;
  9400. } else if ('centerX' in newStart) {
  9401. circle.x = newStart.centerX;
  9402. }
  9403. if ('y' in newStart) {
  9404. circle.y = newStart.y;
  9405. } else if ('centerY' in newStart) {
  9406. circle.y = newStart.centerY;
  9407. }
  9408. if ('r' in newStart) {
  9409. circle.r = newStart.r;
  9410. } else if ('radius' in newStart) {
  9411. circle.r = newStart.radius;
  9412. }
  9413. return circle;
  9414. },
  9415. applyEnd: function(newEnd, oldEnd) {
  9416. var circle;
  9417. if (!oldEnd) {
  9418. return newEnd;
  9419. }
  9420. circle = {
  9421. x: oldEnd.x,
  9422. y: oldEnd.y,
  9423. r: oldEnd.r
  9424. };
  9425. if ('x' in newEnd) {
  9426. circle.x = newEnd.x;
  9427. } else if ('centerX' in newEnd) {
  9428. circle.x = newEnd.centerX;
  9429. }
  9430. if ('y' in newEnd) {
  9431. circle.y = newEnd.y;
  9432. } else if ('centerY' in newEnd) {
  9433. circle.y = newEnd.centerY;
  9434. }
  9435. if ('r' in newEnd) {
  9436. circle.r = newEnd.r;
  9437. } else if ('radius' in newEnd) {
  9438. circle.r = newEnd.radius;
  9439. }
  9440. return circle;
  9441. },
  9442. /**
  9443. * @method generateGradient
  9444. * @inheritdoc
  9445. */
  9446. generateGradient: function(ctx, bbox) {
  9447. var start = this.getStart(),
  9448. end = this.getEnd(),
  9449. w = bbox.width * 0.5,
  9450. h = bbox.height * 0.5,
  9451. x = bbox.x + w,
  9452. y = bbox.y + h,
  9453. 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)),
  9454. stops = this.getStops(),
  9455. ln = stops.length,
  9456. i;
  9457. for (i = 0; i < ln; i++) {
  9458. gradient.addColorStop(stops[i].offset, stops[i].color);
  9459. }
  9460. return gradient;
  9461. }
  9462. });
  9463. /**
  9464. * A surface is an interface to render {@link Ext.draw.sprite.Sprite sprites} inside a
  9465. * {@link Ext.draw.Container draw container}. The surface API has methods to render
  9466. * sprites, get sprite bounding boxes (dimensions), add sprites to the underlying DOM,
  9467. * and more.
  9468. *
  9469. * A surface is automatically created when a draw container is created. By default,
  9470. * this will be a surface with an `id` of "main" and will manage all sprites in the draw
  9471. * container (unless the sprite configs specify a unique surface "id").
  9472. *
  9473. * @example
  9474. * Ext.create({
  9475. * xtype: 'draw',
  9476. * renderTo: document.body,
  9477. * width: 400,
  9478. * height: 400,
  9479. * sprites: [{
  9480. * type: 'rect',
  9481. * surface: 'anim', // a surface with id "anim" will be created automatically
  9482. * x: 50,
  9483. * y: 50,
  9484. * width: 100,
  9485. * height: 100,
  9486. * fillStyle: '#1F6D91'
  9487. * }]
  9488. * });
  9489. *
  9490. * The ability to have multiple surfaces is useful for performance (and battery life)
  9491. * reasons. Because changes to sprite attributes cause the whole surface (and all
  9492. * sprites in it) to re-render, it makes sense to group sprites by surface, so changes
  9493. * to one group of sprites will only trigger the surface they are in to re-render.
  9494. *
  9495. * One of the more useful methods is the {@link #add} method used to add sprites to the
  9496. * surface:
  9497. *
  9498. * @example
  9499. * var drawCt = Ext.create({
  9500. * xtype: 'draw',
  9501. * renderTo: document.body,
  9502. * width: 400,
  9503. * height: 400
  9504. * });
  9505. *
  9506. * // If the surface name is not specified then 'main' will be used
  9507. * var surface = drawCt.getSurface();
  9508. *
  9509. * surface.add({
  9510. * type: 'rect',
  9511. * x: 50,
  9512. * y: 50,
  9513. * width: 100,
  9514. * height: 100,
  9515. * fillStyle: '#1F6D91'
  9516. * });
  9517. *
  9518. * surface.renderFrame();
  9519. *
  9520. * **Note:** Changes to the sprites on a surface will be not be reflected in the DOM
  9521. * until you call the surface's {@link Ext.draw.Surface#method-renderFrame renderFrame}
  9522. * method. This must be done after adding, removing, or modifying sprites in order to
  9523. * see the changes on-screen.
  9524. */
  9525. Ext.define('Ext.draw.Surface', {
  9526. extend: 'Ext.draw.SurfaceBase',
  9527. xtype: 'surface',
  9528. requires: [
  9529. 'Ext.draw.sprite.*',
  9530. 'Ext.draw.gradient.*',
  9531. 'Ext.draw.sprite.AttributeDefinition',
  9532. 'Ext.draw.Matrix',
  9533. 'Ext.draw.Draw'
  9534. ],
  9535. uses: [
  9536. 'Ext.draw.engine.Canvas'
  9537. ],
  9538. /**
  9539. * The reported device pixel density.
  9540. * devicePixelRatio is only supported from IE11,
  9541. * so we use deviceXDPI and logicalXDPI that are supported from IE6.
  9542. */
  9543. devicePixelRatio: window.devicePixelRatio || window.screen.deviceXDPI / window.screen.logicalXDPI,
  9544. deprecated: {
  9545. '5.1.0': {
  9546. statics: {
  9547. methods: {
  9548. /**
  9549. * @deprecated 5.1.0
  9550. * Stably sort the list of sprites by their zIndex.
  9551. * Deprecated, use the {@link Ext.Array#sort} method instead.
  9552. * @param {Array} list
  9553. * @return {Array} Sorted array.
  9554. */
  9555. stableSort: function(list) {
  9556. return Ext.Array.sort(list, function(a, b) {
  9557. return a.attr.zIndex - b.attr.zIndex;
  9558. });
  9559. }
  9560. }
  9561. }
  9562. }
  9563. },
  9564. cls: Ext.baseCSSPrefix + 'surface',
  9565. config: {
  9566. /**
  9567. * @cfg {Array}
  9568. * The [x, y, width, height] rect of the surface related to its container.
  9569. */
  9570. rect: null,
  9571. /**
  9572. * @cfg {Object}
  9573. * Background sprite config of the surface.
  9574. */
  9575. background: null,
  9576. /**
  9577. * @cfg {Array}
  9578. * Array of sprite instances.
  9579. */
  9580. items: [],
  9581. /**
  9582. * @cfg {Boolean}
  9583. * Indicates whether the surface needs to redraw.
  9584. */
  9585. dirty: false,
  9586. /**
  9587. * @cfg {Boolean} flipRtlText
  9588. * If the surface is in the RTL mode, text will render with the RTL direction,
  9589. * but the alignment and position of the text won't change by default.
  9590. * Setting this config to 'true' will get text alignment and its position
  9591. * within a surface mirrored.
  9592. */
  9593. flipRtlText: false
  9594. },
  9595. isSurface: true,
  9596. /**
  9597. * @private
  9598. * This flag is used to indicate that `predecessors` surfaces that should render
  9599. * before this surface renders are dirty, and to call `renderFrame`
  9600. * when all `predecessors` have their `renderFrame` called (i.e. not dirty anymore).
  9601. * This flag indicates that current surface has surfaces that are yet to render
  9602. * before current surface can render. When all the `predecessors` surfaces
  9603. * have rendered, i.e. when `dirtyPredecessorCount` reaches zero,
  9604. */
  9605. isPendingRenderFrame: false,
  9606. dirtyPredecessorCount: 0,
  9607. emptyRect: [
  9608. 0,
  9609. 0,
  9610. 0,
  9611. 0
  9612. ],
  9613. constructor: function(config) {
  9614. var me = this;
  9615. me.predecessors = [];
  9616. me.successors = [];
  9617. me.map = {};
  9618. me.callParent([
  9619. config
  9620. ]);
  9621. me.matrix = new Ext.draw.Matrix();
  9622. me.inverseMatrix = me.matrix.inverse();
  9623. },
  9624. /**
  9625. * Round the number to align to the pixels on device.
  9626. * @param {Number} num The number to align.
  9627. * @return {Number} The resultant alignment.
  9628. */
  9629. roundPixel: function(num) {
  9630. return Math.round(this.devicePixelRatio * num) / this.devicePixelRatio;
  9631. },
  9632. /**
  9633. * Mark the surface to render after another surface is updated.
  9634. * @param {Ext.draw.Surface} surface The surface to wait for.
  9635. */
  9636. waitFor: function(surface) {
  9637. var me = this,
  9638. predecessors = me.predecessors;
  9639. if (!Ext.Array.contains(predecessors, surface)) {
  9640. predecessors.push(surface);
  9641. surface.successors.push(me);
  9642. if (surface.getDirty()) {
  9643. me.dirtyPredecessorCount++;
  9644. }
  9645. }
  9646. },
  9647. updateDirty: function(dirty) {
  9648. var successors = this.successors,
  9649. ln = successors.length,
  9650. i = 0,
  9651. successor;
  9652. for (; i < ln; i++) {
  9653. successor = successors[i];
  9654. if (dirty) {
  9655. successor.dirtyPredecessorCount++;
  9656. successor.setDirty(true);
  9657. } else {
  9658. successor.dirtyPredecessorCount--;
  9659. // Don't need to call `setDirty(false)` on a successor here,
  9660. // as this will be done by `renderFrame`.
  9661. if (successor.dirtyPredecessorCount === 0 && successor.isPendingRenderFrame) {
  9662. successor.renderFrame();
  9663. }
  9664. }
  9665. }
  9666. },
  9667. applyBackground: function(background, oldBackground) {
  9668. this.setDirty(true);
  9669. if (Ext.isString(background)) {
  9670. background = {
  9671. fillStyle: background
  9672. };
  9673. }
  9674. return Ext.factory(background, Ext.draw.sprite.Rect, oldBackground);
  9675. },
  9676. applyRect: function(rect, oldRect) {
  9677. if (oldRect && rect[0] === oldRect[0] && rect[1] === oldRect[1] && rect[2] === oldRect[2] && rect[3] === oldRect[3]) {
  9678. return oldRect;
  9679. }
  9680. if (Ext.isArray(rect)) {
  9681. return [
  9682. rect[0],
  9683. rect[1],
  9684. rect[2],
  9685. rect[3]
  9686. ];
  9687. } else if (Ext.isObject(rect)) {
  9688. return [
  9689. rect.x || rect.left,
  9690. rect.y || rect.top,
  9691. rect.width || (rect.right - rect.left),
  9692. rect.height || (rect.bottom - rect.top)
  9693. ];
  9694. }
  9695. },
  9696. updateRect: function(rect) {
  9697. var me = this,
  9698. l = rect[0],
  9699. t = rect[1],
  9700. r = l + rect[2],
  9701. b = t + rect[3],
  9702. background = me.getBackground(),
  9703. element = me.element;
  9704. element.setLocalXY(Math.floor(l), Math.floor(t));
  9705. element.setSize(Math.ceil(r - Math.floor(l)), Math.ceil(b - Math.floor(t)));
  9706. if (background) {
  9707. background.setAttributes({
  9708. x: 0,
  9709. y: 0,
  9710. width: Math.ceil(r - Math.floor(l)),
  9711. height: Math.ceil(b - Math.floor(t))
  9712. });
  9713. }
  9714. me.setDirty(true);
  9715. },
  9716. /**
  9717. * Reset the matrix of the surface.
  9718. */
  9719. resetTransform: function() {
  9720. this.matrix.set(1, 0, 0, 1, 0, 0);
  9721. this.inverseMatrix.set(1, 0, 0, 1, 0, 0);
  9722. this.setDirty(true);
  9723. },
  9724. /**
  9725. * Get the sprite by id or index.
  9726. * It will first try to find a sprite with the given id, otherwise will try to use the id
  9727. * as an index.
  9728. * @param {String|Number} id
  9729. * @return {Ext.draw.sprite.Sprite}
  9730. */
  9731. get: function(id) {
  9732. return this.map[id] || this.getItems()[id];
  9733. },
  9734. /**
  9735. * @method
  9736. * Add a Sprite to the surface.
  9737. * You can put any number of objects as the parameter.
  9738. * See {@link Ext.draw.sprite.Sprite} for the configuration object to be passed
  9739. * into this method.
  9740. *
  9741. * For example:
  9742. *
  9743. * drawContainer.getSurface().add({
  9744. * type: 'circle',
  9745. * fill: '#ffc',
  9746. * radius: 100,
  9747. * x: 100,
  9748. * y: 100
  9749. * });
  9750. * drawContainer.renderFrame();
  9751. *
  9752. * @param {Object/Object[]} sprite
  9753. * @return {Ext.draw.sprite.Sprite/Ext.draw.sprite.Sprite[]}
  9754. *
  9755. */
  9756. add: function() {
  9757. var me = this,
  9758. args = Array.prototype.slice.call(arguments),
  9759. argIsArray = Ext.isArray(args[0]),
  9760. map = me.map,
  9761. results = [],
  9762. items, item, sprite, oldSurface, i, ln;
  9763. items = Ext.Array.clean(argIsArray ? args[0] : args);
  9764. if (!items.length) {
  9765. return results;
  9766. }
  9767. for (i = 0 , ln = items.length; i < ln; i++) {
  9768. item = items[i];
  9769. if (!item || item.destroyed) {
  9770. continue;
  9771. }
  9772. sprite = null;
  9773. if (item.isSprite && !map[item.getId()]) {
  9774. sprite = item;
  9775. } else if (!map[item.id]) {
  9776. sprite = this.createItem(item);
  9777. }
  9778. if (sprite) {
  9779. map[sprite.getId()] = sprite;
  9780. results.push(sprite);
  9781. oldSurface = sprite.getSurface();
  9782. if (oldSurface && oldSurface.isSurface) {
  9783. oldSurface.remove(sprite);
  9784. }
  9785. sprite.setParent(me);
  9786. sprite.setSurface(me);
  9787. me.onAdd(sprite);
  9788. }
  9789. }
  9790. items = me.getItems();
  9791. if (items) {
  9792. items.push.apply(items, results);
  9793. }
  9794. me.dirtyZIndex = true;
  9795. me.setDirty(true);
  9796. if (!argIsArray && results.length === 1) {
  9797. return results[0];
  9798. } else {
  9799. return results;
  9800. }
  9801. },
  9802. /**
  9803. * @method
  9804. * @protected
  9805. * Invoked when a sprite is added to the surface.
  9806. * @param {Ext.draw.sprite.Sprite} sprite The sprite to be added.
  9807. */
  9808. onAdd: Ext.emptyFn,
  9809. /**
  9810. * Remove a given sprite from the surface,
  9811. * optionally destroying the sprite in the process.
  9812. * You can also call the sprite's own `remove` method.
  9813. *
  9814. * For example:
  9815. *
  9816. * drawContainer.surface.remove(sprite);
  9817. * // or...
  9818. * sprite.remove();
  9819. *
  9820. * @param {Ext.draw.sprite.Sprite/String} sprite A sprite instance or its ID.
  9821. * @param {Boolean} [isDestroy=false] If `true`, the sprite will be destroyed.
  9822. * @return {Ext.draw.sprite.Sprite} Returns the removed/destroyed sprite or `null` otherwise.
  9823. */
  9824. remove: function(sprite, isDestroy) {
  9825. var me = this,
  9826. destroying = me.clearing,
  9827. id, isOwnSprite;
  9828. if (sprite) {
  9829. if (sprite.charAt) {
  9830. // is String
  9831. sprite = me.map[sprite];
  9832. }
  9833. if (!sprite || !sprite.isSprite) {
  9834. return null;
  9835. }
  9836. id = sprite.id;
  9837. isOwnSprite = me.map[id];
  9838. delete me.map[id];
  9839. if (sprite.destroyed || sprite.destroying) {
  9840. if (isOwnSprite && !destroying) {
  9841. // Somehow this sprite was destroyed,
  9842. // but still belongs to the surface.
  9843. Ext.Array.remove(me.getItems(), sprite);
  9844. }
  9845. return sprite;
  9846. }
  9847. if (!isOwnSprite) {
  9848. if (isDestroy) {
  9849. sprite.destroy();
  9850. }
  9851. return sprite;
  9852. }
  9853. sprite.setParent(null);
  9854. sprite.setSurface(null);
  9855. if (isDestroy) {
  9856. sprite.destroy();
  9857. }
  9858. if (!destroying) {
  9859. Ext.Array.remove(me.getItems(), sprite);
  9860. me.dirtyZIndex = true;
  9861. me.setDirty(true);
  9862. }
  9863. }
  9864. return sprite || null;
  9865. },
  9866. /**
  9867. * Remove all sprites from the surface, optionally destroying the sprites in the process.
  9868. *
  9869. * For example:
  9870. *
  9871. * drawContainer.getSurface('main').removeAll();
  9872. *
  9873. * @param {Boolean} [isDestroy=false]
  9874. */
  9875. removeAll: function(isDestroy) {
  9876. var me = this,
  9877. items = me.getItems(),
  9878. item, i;
  9879. me.clearing = !!isDestroy;
  9880. for (i = items.length - 1; i >= 0; i--) {
  9881. item = items[i];
  9882. if (isDestroy) {
  9883. // Some sprites may destroy other sprites, however if we're destroying then
  9884. // we don't remove anything from the items array since we'll just clear it later.
  9885. // If a sprite is destroyed, the remove method will just drop out with no harm done.
  9886. item.destroy();
  9887. } else {
  9888. item.setParent(null);
  9889. item.setSurface(null);
  9890. }
  9891. }
  9892. me.clearing = false;
  9893. items.length = 0;
  9894. me.map = {};
  9895. me.dirtyZIndex = true;
  9896. if (!me.destroying) {
  9897. me.setDirty(true);
  9898. }
  9899. },
  9900. /**
  9901. * @private
  9902. */
  9903. applyItems: function(items) {
  9904. if (this.getItems()) {
  9905. this.removeAll(true);
  9906. }
  9907. return Ext.Array.from(this.add(items));
  9908. },
  9909. /**
  9910. * @private
  9911. * Creates an item and appends it to the surface. Called
  9912. * as an internal method when calling `add`.
  9913. */
  9914. createItem: function(config) {
  9915. return Ext.create(config.xclass || 'sprite.' + config.type, config);
  9916. },
  9917. /**
  9918. * Return the minimal bounding box that contains all the sprites bounding boxes
  9919. * in the given list of sprites.
  9920. * @param {Ext.draw.sprite.Sprite[]|Ext.draw.sprite.Sprite} sprites
  9921. * @param {Boolean} [isWithoutTransform=false]
  9922. * @return {{x: Number, y: Number, width: number, height: number}}
  9923. */
  9924. getBBox: function(sprites, isWithoutTransform) {
  9925. var left = Infinity,
  9926. right = -Infinity,
  9927. top = Infinity,
  9928. bottom = -Infinity,
  9929. sprite, bbox, i, ln;
  9930. sprites = Ext.Array.from(sprites);
  9931. for (i = 0 , ln = sprites.length; i < ln; i++) {
  9932. sprite = sprites[i];
  9933. bbox = sprite.getBBox(isWithoutTransform);
  9934. if (left > bbox.x) {
  9935. left = bbox.x;
  9936. }
  9937. if (right < bbox.x + bbox.width) {
  9938. right = bbox.x + bbox.width;
  9939. }
  9940. if (top > bbox.y) {
  9941. top = bbox.y;
  9942. }
  9943. if (bottom < bbox.y + bbox.height) {
  9944. bottom = bbox.y + bbox.height;
  9945. }
  9946. }
  9947. return {
  9948. x: left,
  9949. y: top,
  9950. width: right - left,
  9951. height: bottom - top
  9952. };
  9953. },
  9954. /**
  9955. * @private
  9956. * @method getOwnerBody
  9957. * The body element of the chart or the draw container
  9958. * (doesn't include docked items like a legend).
  9959. * Draw Container is a Panel in Classic (to allow for docked items)
  9960. * and a Container in Modern, so the body is retrieved differently.
  9961. * @return {Ext.dom.Element}
  9962. */
  9963. /**
  9964. * @private
  9965. * Converts event's page coordinates into surface coordinates.
  9966. * Note: surface's x-coordinates always go LTR, regardless of RTL mode.
  9967. */
  9968. getEventXY: function(e) {
  9969. var me = this,
  9970. isRtl = me.getInherited().rtl,
  9971. pageXY = e.getXY(),
  9972. // Event position in page coordinates.
  9973. // The body of the chart (doesn't include docked items like legend).
  9974. container = me.getOwnerBody(),
  9975. xy = container.getXY(),
  9976. // Surface container position in page coordinates.
  9977. // Surface position in surface container coordinates (LTR).
  9978. rect = me.getRect() || me.emptyRect,
  9979. result = [],
  9980. width;
  9981. if (isRtl) {
  9982. width = container.getWidth();
  9983. // The line below is actually a simplified form of
  9984. // rect[2] - (pageXY[0] - xy[0] - (width - (rect[0] + rect[2]))).
  9985. result[0] = xy[0] - pageXY[0] - rect[0] + width;
  9986. } else {
  9987. result[0] = pageXY[0] - xy[0] - rect[0];
  9988. }
  9989. result[1] = pageXY[1] - xy[1] - rect[1];
  9990. return result;
  9991. },
  9992. /**
  9993. * @method
  9994. * Empty the surface content (without touching the sprites.)
  9995. */
  9996. clear: Ext.emptyFn,
  9997. /**
  9998. * @private
  9999. * Order the items by their z-index if any of that has been changed since last sort.
  10000. */
  10001. orderByZIndex: function() {
  10002. var me = this,
  10003. items = me.getItems(),
  10004. dirtyZIndex = false,
  10005. i, ln;
  10006. if (me.getDirty()) {
  10007. for (i = 0 , ln = items.length; i < ln; i++) {
  10008. if (items[i].attr.dirtyZIndex) {
  10009. dirtyZIndex = true;
  10010. break;
  10011. }
  10012. }
  10013. if (dirtyZIndex) {
  10014. // sort by zIndex
  10015. Ext.Array.sort(items, function(a, b) {
  10016. return a.attr.zIndex - b.attr.zIndex;
  10017. });
  10018. this.setDirty(true);
  10019. }
  10020. for (i = 0 , ln = items.length; i < ln; i++) {
  10021. items[i].attr.dirtyZIndex = false;
  10022. }
  10023. }
  10024. },
  10025. /**
  10026. * Force the element to redraw.
  10027. */
  10028. repaint: function() {
  10029. var me = this;
  10030. me.repaint = Ext.emptyFn;
  10031. Ext.defer(function() {
  10032. delete me.repaint;
  10033. me.element.repaint();
  10034. }, 1);
  10035. },
  10036. /**
  10037. * Triggers the re-rendering of the canvas.
  10038. */
  10039. renderFrame: function() {
  10040. var me = this,
  10041. background, items, item, i, ln;
  10042. if (!(me.element && me.getDirty() && me.getRect())) {
  10043. return;
  10044. }
  10045. if (me.dirtyPredecessorCount > 0) {
  10046. me.isPendingRenderFrame = true;
  10047. return;
  10048. }
  10049. background = me.getBackground();
  10050. items = me.getItems();
  10051. // This will also check the dirty flags of the sprites.
  10052. me.orderByZIndex();
  10053. if (me.getDirty()) {
  10054. me.clear();
  10055. me.clearTransform();
  10056. if (background) {
  10057. me.renderSprite(background);
  10058. }
  10059. for (i = 0 , ln = items.length; i < ln; i++) {
  10060. item = items[i];
  10061. if (me.renderSprite(item) === false) {
  10062. return;
  10063. }
  10064. item.attr.textPositionCount = me.textPosition;
  10065. }
  10066. me.setDirty(false);
  10067. }
  10068. },
  10069. /**
  10070. * @method
  10071. * @private
  10072. * Renders a single sprite into the surface.
  10073. * Do not call it from outside `renderFrame` method.
  10074. *
  10075. * @param {Ext.draw.sprite.Sprite} sprite The Sprite to be rendered.
  10076. * @return {Boolean} returns `false` to stop the rendering to continue.
  10077. */
  10078. renderSprite: Ext.emptyFn,
  10079. /**
  10080. * @method flatten
  10081. * Flattens the given drawing surfaces into a single image
  10082. * and returns an object containing the data (in the DataURL format)
  10083. * and the type (e.g. 'png' or 'svg') of that image.
  10084. * @param {Object} size The size of the final image.
  10085. * @param {Number} size.width
  10086. * @param {Number} size.height
  10087. * @param {Ext.draw.Surface[]} surfaces The surfaces to flatten.
  10088. * @return {Object}
  10089. * @return {String} return.data The DataURL of the flattened image.
  10090. * @return {String} return.type The type of the image.
  10091. *
  10092. */
  10093. /**
  10094. * @method
  10095. * @private
  10096. * Clears the current transformation state on the surface.
  10097. */
  10098. clearTransform: Ext.emptyFn,
  10099. /**
  10100. * Destroys the surface. This is done by removing all components from it and
  10101. * also removing its reference to a DOM element.
  10102. *
  10103. * For example:
  10104. *
  10105. * drawContainer.surface.destroy();
  10106. */
  10107. destroy: function() {
  10108. var me = this;
  10109. me.destroying = true;
  10110. me.removeAll(true);
  10111. me.destroying = false;
  10112. me.predecessors = me.successors = null;
  10113. if (me.hasListeners.destroy) {
  10114. me.fireEvent('destroy', me);
  10115. }
  10116. me.callParent();
  10117. }
  10118. });
  10119. /**
  10120. * @private
  10121. * Adds hit testing methods to the Ext.draw.Surface.
  10122. * Included by the Ext.draw.plugin.SpriteEvents.
  10123. */
  10124. Ext.define('Ext.draw.overrides.hittest.Surface', {
  10125. override: 'Ext.draw.Surface',
  10126. /**
  10127. * Performs a hit test on all sprites in the surface, returning the first matching one.
  10128. * @param {Array} point A two-item array containing x and y coordinates of the point
  10129. * in surface coordinate system.
  10130. * @param {Object} options Hit testing options.
  10131. * @return {Object} A hit result object that contains more information about what
  10132. * exactly was hit or null if nothing was hit.
  10133. * @member Ext.draw.Surface
  10134. */
  10135. hitTest: function(point, options) {
  10136. var me = this,
  10137. sprites = me.getItems(),
  10138. i, sprite, result;
  10139. options = options || Ext.draw.sprite.Sprite.defaultHitTestOptions;
  10140. for (i = sprites.length - 1; i >= 0; i--) {
  10141. sprite = sprites[i];
  10142. if (sprite.hitTest) {
  10143. result = sprite.hitTest(point, options);
  10144. if (result) {
  10145. return result;
  10146. }
  10147. }
  10148. }
  10149. return null;
  10150. },
  10151. /**
  10152. * Performs a hit test on all sprites in the surface, returning the first matching one.
  10153. * Since hit testing is typically performed on mouse events, this convenience method
  10154. * converts event's page coordinates to surface coordinates before calling {@link #hitTest}.
  10155. * @param {Object} event An event object.
  10156. * @param {Object} options Hit testing options.
  10157. * @return {Object} A hit result object that contains more information about what
  10158. * exactly was hit or null if nothing was hit.
  10159. * @member Ext.draw.Surface
  10160. */
  10161. hitTestEvent: function(event, options) {
  10162. var xy = this.getEventXY(event);
  10163. return this.hitTest(xy, options);
  10164. }
  10165. });
  10166. /**
  10167. * @class Ext.draw.engine.SvgContext
  10168. *
  10169. * A class that imitates a canvas context but generates svg elements instead.
  10170. */
  10171. Ext.define('Ext.draw.engine.SvgContext', {
  10172. requires: [
  10173. 'Ext.draw.Color'
  10174. ],
  10175. /**
  10176. * @private
  10177. * Properties to be saved/restored in the `save` and `restore` methods.
  10178. */
  10179. toSave: [
  10180. 'strokeOpacity',
  10181. 'strokeStyle',
  10182. 'fillOpacity',
  10183. 'fillStyle',
  10184. 'globalAlpha',
  10185. 'lineWidth',
  10186. 'lineCap',
  10187. 'lineJoin',
  10188. 'lineDash',
  10189. 'lineDashOffset',
  10190. 'miterLimit',
  10191. 'shadowOffsetX',
  10192. 'shadowOffsetY',
  10193. 'shadowBlur',
  10194. 'shadowColor',
  10195. 'globalCompositeOperation',
  10196. 'position',
  10197. 'fillGradient',
  10198. 'strokeGradient'
  10199. ],
  10200. strokeOpacity: 1,
  10201. strokeStyle: 'none',
  10202. fillOpacity: 1,
  10203. fillStyle: 'none',
  10204. lineDas: [],
  10205. lineDashOffset: 0,
  10206. globalAlpha: 1,
  10207. lineWidth: 1,
  10208. lineCap: 'butt',
  10209. lineJoin: 'miter',
  10210. miterLimit: 10,
  10211. shadowOffsetX: 0,
  10212. shadowOffsetY: 0,
  10213. shadowBlur: 0,
  10214. shadowColor: 'none',
  10215. globalCompositeOperation: 'src',
  10216. urlStringRe: /^url\(#([\w-]+)\)$/,
  10217. constructor: function(SvgSurface) {
  10218. var me = this;
  10219. me.surface = SvgSurface;
  10220. // Stack of contexts.
  10221. me.state = [];
  10222. me.matrix = new Ext.draw.Matrix();
  10223. // Currently manipulated path.
  10224. me.path = null;
  10225. me.clear();
  10226. },
  10227. /**
  10228. * Clears the context.
  10229. */
  10230. clear: function() {
  10231. // Current group to put paths into.
  10232. this.group = this.surface.mainGroup;
  10233. // Position within the current group.
  10234. this.position = 0;
  10235. this.path = null;
  10236. },
  10237. /**
  10238. * @private
  10239. * @param {String} tag
  10240. * @return {*}
  10241. */
  10242. getElement: function(tag) {
  10243. return this.surface.getSvgElement(this.group, tag, this.position++);
  10244. },
  10245. /**
  10246. * Pushes the context state to the state stack.
  10247. */
  10248. save: function() {
  10249. var toSave = this.toSave,
  10250. obj = {},
  10251. group = this.getElement('g'),
  10252. key, i;
  10253. for (i = 0; i < toSave.length; i++) {
  10254. key = toSave[i];
  10255. if (key in this) {
  10256. obj[key] = this[key];
  10257. }
  10258. }
  10259. this.position = 0;
  10260. obj.matrix = this.matrix.clone();
  10261. this.state.push(obj);
  10262. this.group = group;
  10263. return group;
  10264. },
  10265. /**
  10266. * Pops the state stack and restores the state.
  10267. */
  10268. restore: function() {
  10269. var toSave = this.toSave,
  10270. obj = this.state.pop(),
  10271. group = this.group,
  10272. children = group.dom.childNodes,
  10273. key, i;
  10274. // Removing extra DOM elements that were not reused.
  10275. while (children.length > this.position) {
  10276. group.last().destroy();
  10277. }
  10278. for (i = 0; i < toSave.length; i++) {
  10279. key = toSave[i];
  10280. if (key in obj) {
  10281. this[key] = obj[key];
  10282. } else {
  10283. delete this[key];
  10284. }
  10285. }
  10286. this.setTransform.apply(this, obj.matrix.elements);
  10287. this.group = group.getParent();
  10288. },
  10289. /**
  10290. * Changes the transformation matrix to apply the matrix given by the arguments
  10291. * as described below.
  10292. * @param {Number} xx
  10293. * @param {Number} yx
  10294. * @param {Number} xy
  10295. * @param {Number} yy
  10296. * @param {Number} dx
  10297. * @param {Number} dy
  10298. */
  10299. transform: function(xx, yx, xy, yy, dx, dy) {
  10300. var inv;
  10301. if (this.path) {
  10302. inv = Ext.draw.Matrix.fly([
  10303. xx,
  10304. yx,
  10305. xy,
  10306. yy,
  10307. dx,
  10308. dy
  10309. ]).inverse();
  10310. this.path.transform(inv);
  10311. }
  10312. this.matrix.append(xx, yx, xy, yy, dx, dy);
  10313. },
  10314. /**
  10315. * Changes the transformation matrix to the matrix given by the arguments as described below.
  10316. * @param {Number} xx
  10317. * @param {Number} yx
  10318. * @param {Number} xy
  10319. * @param {Number} yy
  10320. * @param {Number} dx
  10321. * @param {Number} dy
  10322. */
  10323. setTransform: function(xx, yx, xy, yy, dx, dy) {
  10324. if (this.path) {
  10325. this.path.transform(this.matrix);
  10326. }
  10327. this.matrix.reset();
  10328. this.transform(xx, yx, xy, yy, dx, dy);
  10329. },
  10330. /**
  10331. * Scales the current context by the specified horizontal (x) and vertical (y) factors.
  10332. * @param {Number} x The horizontal scaling factor, where 1 equals unity or 100% scale.
  10333. * @param {Number} y The vertical scaling factor.
  10334. */
  10335. scale: function(x, y) {
  10336. this.transform(x, 0, 0, y, 0, 0);
  10337. },
  10338. /**
  10339. * Rotates the current context coordinates (that is, a transformation matrix).
  10340. * @param {Number} angle The rotation angle, in radians.
  10341. */
  10342. rotate: function(angle) {
  10343. var xx = Math.cos(angle),
  10344. yx = Math.sin(angle),
  10345. xy = -Math.sin(angle),
  10346. yy = Math.cos(angle);
  10347. this.transform(xx, yx, xy, yy, 0, 0);
  10348. },
  10349. /**
  10350. * Specifies values to move the origin point in a canvas.
  10351. * @param {Number} x The value to add to horizontal (or x) coordinates.
  10352. * @param {Number} y The value to add to vertical (or y) coordinates.
  10353. */
  10354. translate: function(x, y) {
  10355. this.transform(1, 0, 0, 1, x, y);
  10356. },
  10357. setGradientBBox: function(bbox) {
  10358. this.bbox = bbox;
  10359. },
  10360. /**
  10361. * Resets the current default path.
  10362. */
  10363. beginPath: function() {
  10364. this.path = new Ext.draw.Path();
  10365. },
  10366. /**
  10367. * Creates a new subpath with the given point.
  10368. * @param {Number} x
  10369. * @param {Number} y
  10370. */
  10371. moveTo: function(x, y) {
  10372. if (!this.path) {
  10373. this.beginPath();
  10374. }
  10375. this.path.moveTo(x, y);
  10376. this.path.element = null;
  10377. },
  10378. /**
  10379. * Adds the given point to the current subpath, connected to the previous one by a straight
  10380. * line.
  10381. * @param {Number} x
  10382. * @param {Number} y
  10383. */
  10384. lineTo: function(x, y) {
  10385. if (!this.path) {
  10386. this.beginPath();
  10387. }
  10388. this.path.lineTo(x, y);
  10389. this.path.element = null;
  10390. },
  10391. /**
  10392. * Adds a new closed subpath to the path, representing the given rectangle.
  10393. * @param {Number} x
  10394. * @param {Number} y
  10395. * @param {Number} width
  10396. * @param {Number} height
  10397. */
  10398. rect: function(x, y, width, height) {
  10399. this.moveTo(x, y);
  10400. this.lineTo(x + width, y);
  10401. this.lineTo(x + width, y + height);
  10402. this.lineTo(x, y + height);
  10403. this.closePath();
  10404. },
  10405. /**
  10406. * Paints the box that outlines the given rectangle onto the canvas, using the current
  10407. * stroke style.
  10408. * @param {Number} x
  10409. * @param {Number} y
  10410. * @param {Number} width
  10411. * @param {Number} height
  10412. */
  10413. strokeRect: function(x, y, width, height) {
  10414. this.beginPath();
  10415. this.rect(x, y, width, height);
  10416. this.stroke();
  10417. },
  10418. /**
  10419. * Paints the given rectangle onto the canvas, using the current fill style.
  10420. * @param {Number} x
  10421. * @param {Number} y
  10422. * @param {Number} width
  10423. * @param {Number} height
  10424. */
  10425. fillRect: function(x, y, width, height) {
  10426. this.beginPath();
  10427. this.rect(x, y, width, height);
  10428. this.fill();
  10429. },
  10430. /**
  10431. * Marks the current subpath as closed, and starts a new subpath with a point the same
  10432. * as the start and end of the newly closed subpath.
  10433. */
  10434. closePath: function() {
  10435. if (!this.path) {
  10436. this.beginPath();
  10437. }
  10438. this.path.closePath();
  10439. this.path.element = null;
  10440. },
  10441. /**
  10442. * Arc command using svg parameters.
  10443. * @param {Number} r1
  10444. * @param {Number} r2
  10445. * @param {Number} rotation
  10446. * @param {Number} large
  10447. * @param {Number} swipe
  10448. * @param {Number} x2
  10449. * @param {Number} y2
  10450. */
  10451. arcSvg: function(r1, r2, rotation, large, swipe, x2, y2) {
  10452. if (!this.path) {
  10453. this.beginPath();
  10454. }
  10455. this.path.arcSvg(r1, r2, rotation, large, swipe, x2, y2);
  10456. this.path.element = null;
  10457. },
  10458. /**
  10459. * Adds points to the subpath such that the arc described by the circumference of the circle
  10460. * described by the arguments, starting at the given start angle and ending at the given
  10461. * end angle, going in the given direction (defaulting to clockwise), is added to the path,
  10462. * connected to the previous point by a straight line.
  10463. * @param {Number} x
  10464. * @param {Number} y
  10465. * @param {Number} radius
  10466. * @param {Number} startAngle
  10467. * @param {Number} endAngle
  10468. * @param {Number} anticlockwise
  10469. */
  10470. arc: function(x, y, radius, startAngle, endAngle, anticlockwise) {
  10471. if (!this.path) {
  10472. this.beginPath();
  10473. }
  10474. this.path.arc(x, y, radius, startAngle, endAngle, anticlockwise);
  10475. this.path.element = null;
  10476. },
  10477. /**
  10478. * Adds points to the subpath such that the arc described by the circumference of the ellipse
  10479. * described by the arguments, starting at the given start angle and ending at the given
  10480. * end angle, going in the given direction (defaulting to clockwise), is added to the path,
  10481. * connected to the previous point by a straight line.
  10482. * @param {Number} x
  10483. * @param {Number} y
  10484. * @param {Number} radiusX
  10485. * @param {Number} radiusY
  10486. * @param {Number} rotation
  10487. * @param {Number} startAngle
  10488. * @param {Number} endAngle
  10489. * @param {Number} anticlockwise
  10490. */
  10491. ellipse: function(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise) {
  10492. if (!this.path) {
  10493. this.beginPath();
  10494. }
  10495. this.path.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise);
  10496. this.path.element = null;
  10497. },
  10498. /**
  10499. * Adds an arc with the given control points and radius to the current subpath, connected
  10500. * to the previous point by a straight line. If two radii are provided, the first controls
  10501. * the width of the arc's ellipse, and the second controls the height. If only one is provided,
  10502. * or if they are the same, the arc is from a circle. In the case of an ellipse, the rotation
  10503. * argument controls the clockwise inclination of the ellipse relative to the x-axis.
  10504. * @param {Number} x1
  10505. * @param {Number} y1
  10506. * @param {Number} x2
  10507. * @param {Number} y2
  10508. * @param {Number} radiusX
  10509. * @param {Number} radiusY
  10510. * @param {Number} rotation
  10511. */
  10512. arcTo: function(x1, y1, x2, y2, radiusX, radiusY, rotation) {
  10513. if (!this.path) {
  10514. this.beginPath();
  10515. }
  10516. this.path.arcTo(x1, y1, x2, y2, radiusX, radiusY, rotation);
  10517. this.path.element = null;
  10518. },
  10519. /**
  10520. * Adds the given point to the current subpath, connected to the previous one by a cubic Bézier
  10521. * curve with the given control points.
  10522. * @param {Number} x1
  10523. * @param {Number} y1
  10524. * @param {Number} x2
  10525. * @param {Number} y2
  10526. * @param {Number} x3
  10527. * @param {Number} y3
  10528. */
  10529. bezierCurveTo: function(x1, y1, x2, y2, x3, y3) {
  10530. if (!this.path) {
  10531. this.beginPath();
  10532. }
  10533. this.path.bezierCurveTo(x1, y1, x2, y2, x3, y3);
  10534. this.path.element = null;
  10535. },
  10536. /**
  10537. * Strokes the given text at the given position. If a maximum width is provided, the text
  10538. * will be scaled to fit that width if necessary.
  10539. * @param {String} text
  10540. * @param {Number} x
  10541. * @param {Number} y
  10542. */
  10543. strokeText: function(text, x, y) {
  10544. var element, tspan;
  10545. text = String(text);
  10546. if (this.strokeStyle) {
  10547. element = this.getElement('text');
  10548. tspan = this.surface.getSvgElement(element, 'tspan', 0);
  10549. this.surface.setElementAttributes(element, {
  10550. "x": x,
  10551. "y": y,
  10552. "transform": this.matrix.toSvg(),
  10553. "stroke": this.strokeStyle,
  10554. "fill": "none",
  10555. "opacity": this.globalAlpha,
  10556. "stroke-opacity": this.strokeOpacity,
  10557. "style": "font: " + this.font,
  10558. "stroke-dasharray": this.lineDash.join(','),
  10559. "stroke-dashoffset": this.lineDashOffset
  10560. });
  10561. if (this.lineDash.length) {
  10562. this.surface.setElementAttributes(element, {
  10563. "stroke-dasharray": this.lineDash.join(','),
  10564. "stroke-dashoffset": this.lineDashOffset
  10565. });
  10566. }
  10567. if (tspan.dom.firstChild) {
  10568. tspan.dom.removeChild(tspan.dom.firstChild);
  10569. }
  10570. this.surface.setElementAttributes(tspan, {
  10571. "alignment-baseline": "alphabetic"
  10572. });
  10573. tspan.dom.appendChild(document.createTextNode(Ext.String.htmlDecode(text)));
  10574. }
  10575. },
  10576. /**
  10577. * Fills the given text at the given position. If a maximum width is provided, the text
  10578. * will be scaled to fit that width if necessary.
  10579. * @param {String} text
  10580. * @param {Number} x
  10581. * @param {Number} y
  10582. */
  10583. fillText: function(text, x, y) {
  10584. var element, tspan;
  10585. text = String(text);
  10586. if (this.fillStyle) {
  10587. element = this.getElement('text');
  10588. tspan = this.surface.getSvgElement(element, 'tspan', 0);
  10589. this.surface.setElementAttributes(element, {
  10590. "x": x,
  10591. "y": y,
  10592. "transform": this.matrix.toSvg(),
  10593. "fill": this.fillStyle,
  10594. "opacity": this.globalAlpha,
  10595. "fill-opacity": this.fillOpacity,
  10596. "style": "font: " + this.font
  10597. });
  10598. if (tspan.dom.firstChild) {
  10599. tspan.dom.removeChild(tspan.dom.firstChild);
  10600. }
  10601. this.surface.setElementAttributes(tspan, {
  10602. "alignment-baseline": "alphabetic"
  10603. });
  10604. tspan.dom.appendChild(document.createTextNode(Ext.String.htmlDecode(text)));
  10605. }
  10606. },
  10607. /**
  10608. * Draws the given image onto the canvas.
  10609. * If the first argument isn't an img, canvas, or video element, throws a TypeMismatchError
  10610. * exception. If the image has no image data, throws an InvalidStateError exception.
  10611. * If the one of the source rectangle dimensions is zero, throws an IndexSizeError exception.
  10612. * If the image isn't yet fully decoded, then nothing is drawn.
  10613. * @param {HTMLElement} image
  10614. * @param {Number} sx
  10615. * @param {Number} sy
  10616. * @param {Number} sw
  10617. * @param {Number} sh
  10618. * @param {Number} dx
  10619. * @param {Number} dy
  10620. * @param {Number} dw
  10621. * @param {Number} dh
  10622. */
  10623. drawImage: function(image, sx, sy, sw, sh, dx, dy, dw, dh) {
  10624. var me = this,
  10625. element = me.getElement('image'),
  10626. x = sx,
  10627. y = sy,
  10628. width = typeof sw === 'undefined' ? image.width : sw,
  10629. height = typeof sh === 'undefined' ? image.height : sh,
  10630. viewBox = null;
  10631. if (typeof dh !== 'undefined') {
  10632. viewBox = sx + " " + sy + " " + sw + " " + sh;
  10633. x = dx;
  10634. y = dy;
  10635. width = dw;
  10636. height = dh;
  10637. }
  10638. element.dom.setAttributeNS("http:/" + "/www.w3.org/1999/xlink", "href", image.src);
  10639. me.surface.setElementAttributes(element, {
  10640. viewBox: viewBox,
  10641. x: x,
  10642. y: y,
  10643. width: width,
  10644. height: height,
  10645. opacity: me.globalAlpha,
  10646. transform: me.matrix.toSvg()
  10647. });
  10648. },
  10649. /**
  10650. * Fills the subpaths of the current default path or the given path with the current fill style.
  10651. */
  10652. fill: function() {
  10653. var me = this,
  10654. path, fillGradient, element, bbox, fill;
  10655. if (!me.path) {
  10656. return;
  10657. }
  10658. if (me.fillStyle) {
  10659. fillGradient = me.fillGradient;
  10660. element = me.path.element;
  10661. bbox = me.bbox;
  10662. if (!element) {
  10663. path = me.path.toString();
  10664. element = me.path.element = me.getElement('path');
  10665. me.surface.setElementAttributes(element, {
  10666. "d": path,
  10667. "transform": me.matrix.toSvg()
  10668. });
  10669. }
  10670. if (fillGradient && bbox) {
  10671. // This indirectly calls ctx.createLinearGradient or ctx.createRadialGradient,
  10672. // depending on the type of gradient, and returns an instance of
  10673. // Ext.draw.engine.SvgContext.Gradient.
  10674. fill = fillGradient.generateGradient(me, bbox);
  10675. } else {
  10676. fill = me.fillStyle;
  10677. }
  10678. me.surface.setElementAttributes(element, {
  10679. "fill": fill,
  10680. "fill-opacity": me.fillOpacity * me.globalAlpha
  10681. });
  10682. }
  10683. },
  10684. /**
  10685. * Strokes the subpaths of the current default path or the given path with the current
  10686. * stroke style.
  10687. */
  10688. stroke: function() {
  10689. var me = this,
  10690. path, strokeGradient, element, bbox, stroke;
  10691. if (!me.path) {
  10692. return;
  10693. }
  10694. if (me.strokeStyle) {
  10695. strokeGradient = me.strokeGradient;
  10696. element = me.path.element;
  10697. bbox = me.bbox;
  10698. if (!element || !me.path.svgString) {
  10699. path = me.path.toString();
  10700. if (!path) {
  10701. return;
  10702. }
  10703. element = me.path.element = me.getElement('path');
  10704. me.surface.setElementAttributes(element, {
  10705. "fill": "none",
  10706. "d": path,
  10707. "transform": me.matrix.toSvg()
  10708. });
  10709. }
  10710. if (strokeGradient && bbox) {
  10711. // This indirectly calls ctx.createLinearGradient or ctx.createRadialGradient,
  10712. // depending on the type of gradient, and returns an instance of
  10713. // Ext.draw.engine.SvgContext.Gradient.
  10714. stroke = strokeGradient.generateGradient(me, bbox);
  10715. } else {
  10716. stroke = me.strokeStyle;
  10717. }
  10718. me.surface.setElementAttributes(element, {
  10719. "stroke": stroke,
  10720. "stroke-linecap": me.lineCap,
  10721. "stroke-linejoin": me.lineJoin,
  10722. "stroke-width": me.lineWidth,
  10723. "stroke-opacity": me.strokeOpacity * me.globalAlpha,
  10724. "stroke-dasharray": me.lineDash.join(','),
  10725. "stroke-dashoffset": me.lineDashOffset
  10726. });
  10727. if (me.lineDash.length) {
  10728. me.surface.setElementAttributes(element, {
  10729. "stroke-dasharray": me.lineDash.join(','),
  10730. "stroke-dashoffset": me.lineDashOffset
  10731. });
  10732. }
  10733. }
  10734. },
  10735. /**
  10736. * @protected
  10737. *
  10738. * Note: After the method guarantees the transform matrix will be inverted.
  10739. * @param {Object} attr The attribute object
  10740. * @param {Boolean} [transformFillStroke] Indicate whether to transform fill and stroke.
  10741. * If this is not given, then uses `attr.transformFillStroke` instead.
  10742. */
  10743. fillStroke: function(attr, transformFillStroke) {
  10744. var ctx = this,
  10745. fillStyle = ctx.fillStyle,
  10746. strokeStyle = ctx.strokeStyle,
  10747. fillOpacity = ctx.fillOpacity,
  10748. strokeOpacity = ctx.strokeOpacity;
  10749. if (transformFillStroke === undefined) {
  10750. transformFillStroke = attr.transformFillStroke;
  10751. }
  10752. if (!transformFillStroke) {
  10753. attr.inverseMatrix.toContext(ctx);
  10754. }
  10755. if (fillStyle && fillOpacity !== 0) {
  10756. ctx.fill();
  10757. }
  10758. if (strokeStyle && strokeOpacity !== 0) {
  10759. ctx.stroke();
  10760. }
  10761. },
  10762. appendPath: function(path) {
  10763. this.path = path.clone();
  10764. },
  10765. setLineDash: function(lineDash) {
  10766. this.lineDash = lineDash;
  10767. },
  10768. getLineDash: function() {
  10769. return this.lineDash;
  10770. },
  10771. /**
  10772. * Returns an object that represents a linear gradient that paints along the line
  10773. * given by the coordinates represented by the arguments.
  10774. * @param {Number} x0
  10775. * @param {Number} y0
  10776. * @param {Number} x1
  10777. * @param {Number} y1
  10778. * @return {Ext.draw.engine.SvgContext.Gradient}
  10779. */
  10780. createLinearGradient: function(x0, y0, x1, y1) {
  10781. var me = this,
  10782. element = me.surface.getNextDef('linearGradient'),
  10783. gradient;
  10784. me.surface.setElementAttributes(element, {
  10785. "x1": x0,
  10786. "y1": y0,
  10787. "x2": x1,
  10788. "y2": y1,
  10789. "gradientUnits": "userSpaceOnUse"
  10790. });
  10791. gradient = new Ext.draw.engine.SvgContext.Gradient(me, me.surface, element);
  10792. return gradient;
  10793. },
  10794. /**
  10795. * Returns a CanvasGradient object that represents a radial gradient that paints
  10796. * along the cone given by the circles represented by the arguments.
  10797. * If either of the radii are negative, throws an IndexSizeError exception.
  10798. * @param {Number} x0
  10799. * @param {Number} y0
  10800. * @param {Number} r0
  10801. * @param {Number} x1
  10802. * @param {Number} y1
  10803. * @param {Number} r1
  10804. * @return {Ext.draw.engine.SvgContext.Gradient}
  10805. */
  10806. createRadialGradient: function(x0, y0, r0, x1, y1, r1) {
  10807. var me = this,
  10808. element = me.surface.getNextDef('radialGradient'),
  10809. gradient;
  10810. me.surface.setElementAttributes(element, {
  10811. fx: x0,
  10812. fy: y0,
  10813. cx: x1,
  10814. cy: y1,
  10815. r: r1,
  10816. gradientUnits: 'userSpaceOnUse'
  10817. });
  10818. gradient = new Ext.draw.engine.SvgContext.Gradient(me, me.surface, element, r0 / r1);
  10819. return gradient;
  10820. }
  10821. });
  10822. /**
  10823. * @class Ext.draw.engine.SvgContext.Gradient
  10824. *
  10825. * A class that implements native CanvasGradient interface
  10826. * (https://developer.mozilla.org/en/docs/Web/API/CanvasGradient)
  10827. * and a `toString` method that returns the ID of the gradient.
  10828. */
  10829. Ext.define('Ext.draw.engine.SvgContext.Gradient', {
  10830. // Gradients workflow in SVG engine:
  10831. //
  10832. // Inside the 'fill' & 'stroke' methods of the SVG Context
  10833. // we check if the 'ctx.fillGradient' or 'ctx.strokeGradient'
  10834. // objects exist.
  10835. // These objects are instances of Ext.draw.gradient.Gradient
  10836. // and are assigned to the ctx by the sprite's 'useAttributes' method,
  10837. // if the sprite has any gradients.
  10838. //
  10839. // Additionally, we check if the 'ctx.bbox' object exists - the bounding box
  10840. // for the gradients, set by the sprite's 'setGradientBBox' method.
  10841. //
  10842. // If we have both bbox and a valid instance of Ext.draw.gradient.Gradient,
  10843. // the 'generateGradient' method of the instance is called,
  10844. // which in turn calls 'ctx.createLinearGradient' or 'ctx.createRadialGradient'
  10845. // depending on the type of the gradient represented by the instance.
  10846. // These methods create a 'linearGradient' or 'radialGradient' SVG
  10847. // node and wrap it into a Ext.draw.engine.SvgContext.Gradient instance.
  10848. //
  10849. // The Ext.draw.engine.SvgContext.Gradient instance is then used internally
  10850. // by the Ext.draw.gradient.Gradient to add color 'stop' nodes
  10851. // to the gradient node, and by the SVG context when the 'fill' or
  10852. // 'stroke' attribute of a 'path' node is set to the Ext.draw.engine.SvgContext.Gradient
  10853. // instance, which is implicitly converted to a string - a 'url(#id)' reference
  10854. // to the gradient element wrapped by the instance.
  10855. isGradient: true,
  10856. constructor: function(ctx, surface, element, compression) {
  10857. var me = this;
  10858. me.ctx = ctx;
  10859. me.surface = surface;
  10860. me.element = element;
  10861. me.position = 0;
  10862. me.compression = compression || 0;
  10863. },
  10864. /**
  10865. * Adds a color stop with the given color to the gradient at the given offset. 0.0 is the offset
  10866. * at one end of the gradient, 1.0 is the offset at the other end.
  10867. * @param {Number} offset
  10868. * @param {String} color
  10869. */
  10870. addColorStop: function(offset, color) {
  10871. var me = this,
  10872. stop = me.surface.getSvgElement(me.element, 'stop', me.position++),
  10873. compression = me.compression;
  10874. me.surface.setElementAttributes(stop, {
  10875. "offset": (((1 - compression) * offset + compression) * 100).toFixed(2) + '%',
  10876. "stop-color": color,
  10877. "stop-opacity": Ext.util.Color.fly(color).a.toFixed(15)
  10878. });
  10879. },
  10880. toString: function() {
  10881. var children = this.element.dom.childNodes;
  10882. // Removing surplus stops in case existing gradient element with more stops was reused.
  10883. while (children.length > this.position) {
  10884. Ext.fly(children[children.length - 1]).destroy();
  10885. }
  10886. return 'url(#' + this.element.getId() + ')';
  10887. }
  10888. });
  10889. /**
  10890. * @class Ext.draw.engine.Svg
  10891. * @extends Ext.draw.Surface
  10892. *
  10893. * SVG engine.
  10894. */
  10895. Ext.define('Ext.draw.engine.Svg', {
  10896. extend: 'Ext.draw.Surface',
  10897. requires: [
  10898. 'Ext.draw.engine.SvgContext'
  10899. ],
  10900. isSVG: true,
  10901. config: {
  10902. /**
  10903. * @cfg {Boolean} highPrecision
  10904. * Nothing needs to be done in high precision mode.
  10905. */
  10906. highPrecision: false
  10907. },
  10908. getElementConfig: function() {
  10909. return {
  10910. reference: 'element',
  10911. style: {
  10912. position: 'absolute'
  10913. },
  10914. children: [
  10915. {
  10916. reference: 'bodyElement',
  10917. style: {
  10918. width: '100%',
  10919. height: '100%',
  10920. position: 'relative'
  10921. },
  10922. children: [
  10923. {
  10924. tag: 'svg',
  10925. reference: 'svgElement',
  10926. namespace: "http://www.w3.org/2000/svg",
  10927. width: '100%',
  10928. height: '100%',
  10929. version: 1.1
  10930. }
  10931. ]
  10932. }
  10933. ]
  10934. };
  10935. },
  10936. constructor: function(config) {
  10937. var me = this;
  10938. me.callParent([
  10939. config
  10940. ]);
  10941. me.mainGroup = me.createSvgNode("g");
  10942. me.defsElement = me.createSvgNode("defs");
  10943. // me.svgElement is assigned in element creation of Ext.Component.
  10944. me.svgElement.appendChild(me.mainGroup);
  10945. me.svgElement.appendChild(me.defsElement);
  10946. me.ctx = new Ext.draw.engine.SvgContext(me);
  10947. },
  10948. /**
  10949. * Creates a DOM element under the SVG namespace of the given type.
  10950. * @param {String} type The type of the SVG DOM element.
  10951. * @return {*} The created element.
  10952. */
  10953. createSvgNode: function(type) {
  10954. var node = document.createElementNS("http://www.w3.org/2000/svg", type);
  10955. return Ext.get(node);
  10956. },
  10957. /**
  10958. * @private
  10959. * Returns the SVG DOM element at the given position.
  10960. * If it does not already exist or is a different element tag,
  10961. * it will be created and inserted into the DOM.
  10962. * @param {Ext.dom.Element} group The parent DOM element.
  10963. * @param {String} tag The SVG element tag.
  10964. * @param {Number} position The position of the element in the DOM.
  10965. * @return {Ext.dom.Element} The SVG element.
  10966. */
  10967. getSvgElement: function(group, tag, position) {
  10968. var childNodes = group.dom.childNodes,
  10969. length = childNodes.length,
  10970. element;
  10971. if (position < length) {
  10972. element = childNodes[position];
  10973. if (element.tagName === tag) {
  10974. return Ext.get(element);
  10975. } else {
  10976. Ext.destroy(element);
  10977. }
  10978. } else if (position > length) {
  10979. Ext.raise("Invalid position.");
  10980. }
  10981. element = Ext.get(this.createSvgNode(tag));
  10982. if (position === 0) {
  10983. group.insertFirst(element);
  10984. } else {
  10985. element.insertAfter(Ext.fly(childNodes[position - 1]));
  10986. }
  10987. element.cache = {};
  10988. return element;
  10989. },
  10990. /**
  10991. * @private
  10992. * Applies attributes to the given element.
  10993. * @param {Ext.dom.Element} element The DOM element to be applied.
  10994. * @param {Object} attributes The attributes to apply to the element.
  10995. */
  10996. setElementAttributes: function(element, attributes) {
  10997. var dom = element.dom,
  10998. cache = element.cache,
  10999. name, value;
  11000. for (name in attributes) {
  11001. value = attributes[name];
  11002. if (cache[name] !== value) {
  11003. cache[name] = value;
  11004. dom.setAttribute(name, value);
  11005. }
  11006. }
  11007. },
  11008. /**
  11009. * @private
  11010. * Gets the next reference element under the SVG 'defs' tag.
  11011. * @param {String} tagName The type of reference element.
  11012. * @return {Ext.dom.Element} The reference element.
  11013. */
  11014. getNextDef: function(tagName) {
  11015. return this.getSvgElement(this.defsElement, tagName, this.defsPosition++);
  11016. },
  11017. /**
  11018. * @method clearTransform
  11019. * @inheritdoc
  11020. */
  11021. clearTransform: function() {
  11022. var me = this;
  11023. me.mainGroup.set({
  11024. transform: me.matrix.toSvg()
  11025. });
  11026. },
  11027. /**
  11028. * @method clear
  11029. * @inheritdoc
  11030. */
  11031. clear: function() {
  11032. this.ctx.clear();
  11033. this.removeSurplusDefs();
  11034. this.defsPosition = 0;
  11035. },
  11036. removeSurplusDefs: function() {
  11037. var defsElement = this.defsElement,
  11038. defs = defsElement.dom.childNodes,
  11039. ln = defs.length,
  11040. i;
  11041. for (i = ln - 1; i > this.defsPosition; i--) {
  11042. defsElement.removeChild(defs[i]);
  11043. }
  11044. },
  11045. /**
  11046. * @method renderSprite
  11047. * @inheritdoc
  11048. */
  11049. renderSprite: function(sprite) {
  11050. var me = this,
  11051. rect = me.getRect(),
  11052. ctx = me.ctx;
  11053. // This check is simplistic, but should result in a better performance
  11054. // compared to !sprite.isVisible() when most surface sprites are visible.
  11055. if (sprite.attr.hidden || sprite.attr.globalAlpha === 0) {
  11056. // Create an empty group for each hidden sprite,
  11057. // so that when these sprites do become visible,
  11058. // they don't need groups to be created and don't
  11059. // mess up the previous order of elements in the
  11060. // document, i.e. sprites rendered in the next
  11061. // frame reuse the same elements they used in the
  11062. // previous frame.
  11063. ctx.save();
  11064. ctx.restore();
  11065. return;
  11066. }
  11067. // Each sprite is rendered in its own group ('g' element),
  11068. // returned by the `ctx.save` method.
  11069. // Essentially, the group _is_ the sprite.
  11070. sprite.element = ctx.save();
  11071. sprite.preRender(this);
  11072. sprite.useAttributes(ctx, rect);
  11073. if (false === sprite.render(this, ctx, [
  11074. 0,
  11075. 0,
  11076. rect[2],
  11077. rect[3]
  11078. ])) {
  11079. return false;
  11080. }
  11081. sprite.setDirty(false);
  11082. ctx.restore();
  11083. },
  11084. /**
  11085. * @private
  11086. */
  11087. toSVG: function(size, surfaces) {
  11088. var className = Ext.getClassName(this),
  11089. svg, surface, rect, i;
  11090. svg = '<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg"' + ' width="' + size.width + '"' + ' height="' + size.height + '">';
  11091. for (i = 0; i < surfaces.length; i++) {
  11092. surface = surfaces[i];
  11093. if (Ext.getClassName(surface) !== className) {
  11094. continue;
  11095. }
  11096. rect = surface.getRect();
  11097. svg += '<g transform="translate(' + rect[0] + ',' + rect[1] + ')">';
  11098. svg += this.serializeNode(surface.svgElement.dom);
  11099. svg += '</g>';
  11100. }
  11101. svg += '</svg>';
  11102. return svg;
  11103. },
  11104. b64EncodeUnicode: function(str) {
  11105. // Since DOMStrings are 16-bit-encoded strings, in most browsers calling window.btoa
  11106. // on a Unicode string will cause a Character Out Of Range exception if a character
  11107. // exceeds the range of a 8-bit ASCII-encoded character. More information:
  11108. // https://developer.mozilla.org/en/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
  11109. return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
  11110. return String.fromCharCode('0x' + p1);
  11111. }));
  11112. },
  11113. flatten: function(size, surfaces) {
  11114. var svg = '<?xml version="1.0" standalone="yes"?>';
  11115. svg += this.toSVG(size, surfaces);
  11116. return {
  11117. data: 'data:image/svg+xml;base64,' + this.b64EncodeUnicode(svg),
  11118. type: 'svg'
  11119. };
  11120. },
  11121. /**
  11122. * @private
  11123. * Serializes an SVG DOM element and its children recursively into a string.
  11124. * @param {Object} node DOM element to serialize.
  11125. * @return {String}
  11126. */
  11127. serializeNode: function(node) {
  11128. var result = '',
  11129. i, n, attr, child;
  11130. if (node.nodeType === document.TEXT_NODE) {
  11131. return node.nodeValue;
  11132. }
  11133. result += '<' + node.nodeName;
  11134. if (node.attributes.length) {
  11135. for (i = 0 , n = node.attributes.length; i < n; i++) {
  11136. attr = node.attributes[i];
  11137. result += ' ' + attr.name + '="' + Ext.String.htmlEncode(attr.value) + '"';
  11138. }
  11139. }
  11140. result += '>';
  11141. if (node.childNodes && node.childNodes.length) {
  11142. for (i = 0 , n = node.childNodes.length; i < n; i++) {
  11143. child = node.childNodes[i];
  11144. result += this.serializeNode(child);
  11145. }
  11146. }
  11147. result += '</' + node.nodeName + '>';
  11148. return result;
  11149. },
  11150. /**
  11151. * Destroys the Canvas element and prepares it for Garbage Collection.
  11152. */
  11153. destroy: function() {
  11154. var me = this;
  11155. me.ctx.destroy();
  11156. me.mainGroup.destroy();
  11157. me.defsElement.destroy();
  11158. delete me.mainGroup;
  11159. delete me.defsElement;
  11160. delete me.ctx;
  11161. me.callParent();
  11162. },
  11163. remove: function(sprite, destroySprite) {
  11164. if (sprite && sprite.element) {
  11165. // If sprite has an associated SVG element, remove it from the surface.
  11166. sprite.element.destroy();
  11167. sprite.element = null;
  11168. }
  11169. this.callParent(arguments);
  11170. }
  11171. });
  11172. // @define Ext.draw.engine.excanvas
  11173. /**
  11174. * @private
  11175. */
  11176. if (!Ext.draw) {
  11177. Ext.draw = {};
  11178. }
  11179. if (!Ext.draw.engine) {
  11180. Ext.draw.engine = {};
  11181. }
  11182. Ext.draw.engine.excanvas = true;
  11183. // Copyright 2006 Google Inc.
  11184. //
  11185. // Licensed under the Apache License, Version 2.0 (the "License");
  11186. // you may not use this file except in compliance with the License.
  11187. // You may obtain a copy of the License at
  11188. //
  11189. // http://www.apache.org/licenses/LICENSE-2.0
  11190. //
  11191. // Unless required by applicable law or agreed to in writing, software
  11192. // distributed under the License is distributed on an "AS IS" BASIS,
  11193. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11194. // See the License for the specific language governing permissions and
  11195. // limitations under the License.
  11196. // Known Issues:
  11197. //
  11198. // * Patterns only support repeat.
  11199. // * Radial gradient are not implemented. The VML version of these look very
  11200. // different from the canvas one.
  11201. // * Clipping paths are not implemented.
  11202. // * Coordsize. The width and height attribute have higher priority than the
  11203. // width and height style values which isn't correct.
  11204. // * Painting mode isn't implemented.
  11205. // * Canvas width/height should is using content-box by default. IE in
  11206. // Quirks mode will draw the canvas using border-box. Either change your
  11207. // doctype to HTML5
  11208. // (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
  11209. // or use Box Sizing Behavior from WebFX
  11210. // (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
  11211. // * Non uniform scaling does not correctly scale strokes.
  11212. // * Optimize. There is always room for speed improvements.
  11213. /* eslint-disable */
  11214. // Only add this code if we do not already have a canvas implementation
  11215. if (!document.createElement('canvas').getContext) {
  11216. (function() {
  11217. // alias some functions to make (compiled) code shorter
  11218. var m = Math;
  11219. var mr = m.round;
  11220. var ms = m.sin;
  11221. var mc = m.cos;
  11222. var abs = m.abs;
  11223. var sqrt = m.sqrt;
  11224. // this is used for sub pixel precision
  11225. var Z = 10;
  11226. var Z2 = Z / 2;
  11227. var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];
  11228. /*
  11229. * @method getContext
  11230. * This function is assigned to the <canvas></canvas> elements as element.getContext().
  11231. * @return {CanvasRenderingContext2D_}
  11232. */
  11233. function getContext() {
  11234. return this.context_ || (this.context_ = new CanvasRenderingContext2D_(this));
  11235. }
  11236. var slice = Array.prototype.slice;
  11237. /*
  11238. * @method bind
  11239. * Binds a function to an object. The returned function will always use the
  11240. * passed in {@code obj} as {@code this}.
  11241. *
  11242. * Example:
  11243. *
  11244. * g = bind(f, obj, a, b)
  11245. * g(c, d) // will do f.call(obj, a, b, c, d)
  11246. *
  11247. * @param {Function} f The function to bind the object to
  11248. * @param {Object} obj The object that should act as this when the function
  11249. * is called
  11250. * @param {*} var_args Rest arguments that will be used as the initial
  11251. * arguments when the function is called
  11252. * @return {Function} A new function that has bound this
  11253. */
  11254. function bind(f, obj, var_args) {
  11255. var a = slice.call(arguments, 2);
  11256. return function() {
  11257. return f.apply(obj, a.concat(slice.call(arguments)));
  11258. };
  11259. }
  11260. function encodeHtmlAttribute(s) {
  11261. return String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
  11262. }
  11263. function addNamespace(doc, prefix, urn) {
  11264. Ext.onReady(function() {
  11265. if (!doc.namespaces[prefix]) {
  11266. doc.namespaces.add(prefix, urn, '#default#VML');
  11267. }
  11268. });
  11269. }
  11270. function addNamespacesAndStylesheet(doc) {
  11271. addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml');
  11272. addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office');
  11273. // Setup default CSS. Only add one style sheet per document
  11274. if (!doc.styleSheets['ex_canvas_']) {
  11275. var ss = doc.createStyleSheet();
  11276. ss.owningElement.id = 'ex_canvas_';
  11277. ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + // default size is 300x150 in Gecko and Opera
  11278. 'text-align:left;width:300px;height:150px}';
  11279. }
  11280. }
  11281. // Add namespaces and stylesheet at startup.
  11282. addNamespacesAndStylesheet(document);
  11283. var G_vmlCanvasManager_ = {
  11284. init: function(opt_doc) {
  11285. var doc = opt_doc || document;
  11286. // Create a dummy element so that IE will allow canvas elements to be
  11287. // recognized.
  11288. doc.createElement('canvas');
  11289. doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
  11290. },
  11291. init_: function(doc) {
  11292. // find all canvas elements
  11293. var els = doc.getElementsByTagName('canvas');
  11294. for (var i = 0; i < els.length; i++) {
  11295. this.initElement(els[i]);
  11296. }
  11297. },
  11298. /*
  11299. * Public initializes a canvas element so that it can be used as canvas
  11300. * element from now on. This is called automatically before the page is
  11301. * loaded but if you are creating elements using createElement you need to
  11302. * make sure this is called on the element.
  11303. * @param {HTMLElement} el The canvas element to initialize.
  11304. * @return {HTMLElement} the element that was created.
  11305. */
  11306. initElement: function(el) {
  11307. if (!el.getContext) {
  11308. el.getContext = getContext;
  11309. // Add namespaces and stylesheet to document of the element.
  11310. addNamespacesAndStylesheet(el.ownerDocument);
  11311. // Remove fallback content. There is no way to hide text nodes so we
  11312. // just remove all childNodes. We could hide all elements and remove
  11313. // text nodes but who really cares about the fallback content.
  11314. el.innerHTML = '';
  11315. // do not use inline function because that will leak memory
  11316. el.attachEvent('onpropertychange', onPropertyChange);
  11317. el.attachEvent('onresize', onResize);
  11318. var attrs = el.attributes;
  11319. if (attrs.width && attrs.width.specified) {
  11320. // TODO: use runtimeStyle and coordsize
  11321. // el.getContext().setWidth_(attrs.width.nodeValue);
  11322. el.style.width = attrs.width.nodeValue + 'px';
  11323. } else {
  11324. el.width = el.clientWidth;
  11325. }
  11326. if (attrs.height && attrs.height.specified) {
  11327. // TODO: use runtimeStyle and coordsize
  11328. // el.getContext().setHeight_(attrs.height.nodeValue);
  11329. el.style.height = attrs.height.nodeValue + 'px';
  11330. } else {
  11331. el.height = el.clientHeight;
  11332. }
  11333. }
  11334. //el.getContext().setCoordsize_()
  11335. return el;
  11336. }
  11337. };
  11338. function onPropertyChange(e) {
  11339. var el = e.srcElement;
  11340. switch (e.propertyName) {
  11341. case 'width':
  11342. el.getContext().clearRect();
  11343. el.style.width = el.attributes.width.nodeValue + 'px';
  11344. // In IE8 this does not trigger onresize.
  11345. el.firstChild.style.width = el.clientWidth + 'px';
  11346. break;
  11347. case 'height':
  11348. el.getContext().clearRect();
  11349. el.style.height = el.attributes.height.nodeValue + 'px';
  11350. el.firstChild.style.height = el.clientHeight + 'px';
  11351. break;
  11352. }
  11353. }
  11354. function onResize(e) {
  11355. var el = e.srcElement;
  11356. if (el.firstChild) {
  11357. el.firstChild.style.width = el.clientWidth + 'px';
  11358. el.firstChild.style.height = el.clientHeight + 'px';
  11359. }
  11360. }
  11361. G_vmlCanvasManager_.init();
  11362. // precompute "00" to "FF"
  11363. var decToHex = [];
  11364. for (var i = 0; i < 16; i++) {
  11365. for (var j = 0; j < 16; j++) {
  11366. decToHex[i * 16 + j] = i.toString(16) + j.toString(16);
  11367. }
  11368. }
  11369. function createMatrixIdentity() {
  11370. return [
  11371. [
  11372. 1,
  11373. 0,
  11374. 0
  11375. ],
  11376. [
  11377. 0,
  11378. 1,
  11379. 0
  11380. ],
  11381. [
  11382. 0,
  11383. 0,
  11384. 1
  11385. ]
  11386. ];
  11387. }
  11388. function matrixMultiply(m1, m2) {
  11389. var result = createMatrixIdentity();
  11390. for (var x = 0; x < 3; x++) {
  11391. for (var y = 0; y < 3; y++) {
  11392. var sum = 0;
  11393. for (var z = 0; z < 3; z++) {
  11394. sum += m1[x][z] * m2[z][y];
  11395. }
  11396. result[x][y] = sum;
  11397. }
  11398. }
  11399. return result;
  11400. }
  11401. function copyState(o1, o2) {
  11402. o2.fillStyle = o1.fillStyle;
  11403. o2.lineCap = o1.lineCap;
  11404. o2.lineJoin = o1.lineJoin;
  11405. o2.lineDash = o1.lineDash;
  11406. o2.lineWidth = o1.lineWidth;
  11407. o2.miterLimit = o1.miterLimit;
  11408. o2.shadowBlur = o1.shadowBlur;
  11409. o2.shadowColor = o1.shadowColor;
  11410. o2.shadowOffsetX = o1.shadowOffsetX;
  11411. o2.shadowOffsetY = o1.shadowOffsetY;
  11412. o2.strokeStyle = o1.strokeStyle;
  11413. o2.globalAlpha = o1.globalAlpha;
  11414. o2.font = o1.font;
  11415. o2.textAlign = o1.textAlign;
  11416. o2.textBaseline = o1.textBaseline;
  11417. o2.arcScaleX_ = o1.arcScaleX_;
  11418. o2.arcScaleY_ = o1.arcScaleY_;
  11419. o2.lineScale_ = o1.lineScale_;
  11420. }
  11421. var colorData = {
  11422. aliceblue: '#F0F8FF',
  11423. antiquewhite: '#FAEBD7',
  11424. aquamarine: '#7FFFD4',
  11425. azure: '#F0FFFF',
  11426. beige: '#F5F5DC',
  11427. bisque: '#FFE4C4',
  11428. black: '#000000',
  11429. blanchedalmond: '#FFEBCD',
  11430. blueviolet: '#8A2BE2',
  11431. brown: '#A52A2A',
  11432. burlywood: '#DEB887',
  11433. cadetblue: '#5F9EA0',
  11434. chartreuse: '#7FFF00',
  11435. chocolate: '#D2691E',
  11436. coral: '#FF7F50',
  11437. cornflowerblue: '#6495ED',
  11438. cornsilk: '#FFF8DC',
  11439. crimson: '#DC143C',
  11440. cyan: '#00FFFF',
  11441. darkblue: '#00008B',
  11442. darkcyan: '#008B8B',
  11443. darkgoldenrod: '#B8860B',
  11444. darkgray: '#A9A9A9',
  11445. darkgreen: '#006400',
  11446. darkgrey: '#A9A9A9',
  11447. darkkhaki: '#BDB76B',
  11448. darkmagenta: '#8B008B',
  11449. darkolivegreen: '#556B2F',
  11450. darkorange: '#FF8C00',
  11451. darkorchid: '#9932CC',
  11452. darkred: '#8B0000',
  11453. darksalmon: '#E9967A',
  11454. darkseagreen: '#8FBC8F',
  11455. darkslateblue: '#483D8B',
  11456. darkslategray: '#2F4F4F',
  11457. darkslategrey: '#2F4F4F',
  11458. darkturquoise: '#00CED1',
  11459. darkviolet: '#9400D3',
  11460. deeppink: '#FF1493',
  11461. deepskyblue: '#00BFFF',
  11462. dimgray: '#696969',
  11463. dimgrey: '#696969',
  11464. dodgerblue: '#1E90FF',
  11465. firebrick: '#B22222',
  11466. floralwhite: '#FFFAF0',
  11467. forestgreen: '#228B22',
  11468. gainsboro: '#DCDCDC',
  11469. ghostwhite: '#F8F8FF',
  11470. gold: '#FFD700',
  11471. goldenrod: '#DAA520',
  11472. grey: '#808080',
  11473. greenyellow: '#ADFF2F',
  11474. honeydew: '#F0FFF0',
  11475. hotpink: '#FF69B4',
  11476. indianred: '#CD5C5C',
  11477. indigo: '#4B0082',
  11478. ivory: '#FFFFF0',
  11479. khaki: '#F0E68C',
  11480. lavender: '#E6E6FA',
  11481. lavenderblush: '#FFF0F5',
  11482. lawngreen: '#7CFC00',
  11483. lemonchiffon: '#FFFACD',
  11484. lightblue: '#ADD8E6',
  11485. lightcoral: '#F08080',
  11486. lightcyan: '#E0FFFF',
  11487. lightgoldenrodyellow: '#FAFAD2',
  11488. lightgreen: '#90EE90',
  11489. lightgrey: '#D3D3D3',
  11490. lightpink: '#FFB6C1',
  11491. lightsalmon: '#FFA07A',
  11492. lightseagreen: '#20B2AA',
  11493. lightskyblue: '#87CEFA',
  11494. lightslategray: '#778899',
  11495. lightslategrey: '#778899',
  11496. lightsteelblue: '#B0C4DE',
  11497. lightyellow: '#FFFFE0',
  11498. limegreen: '#32CD32',
  11499. linen: '#FAF0E6',
  11500. magenta: '#FF00FF',
  11501. mediumaquamarine: '#66CDAA',
  11502. mediumblue: '#0000CD',
  11503. mediumorchid: '#BA55D3',
  11504. mediumpurple: '#9370DB',
  11505. mediumseagreen: '#3CB371',
  11506. mediumslateblue: '#7B68EE',
  11507. mediumspringgreen: '#00FA9A',
  11508. mediumturquoise: '#48D1CC',
  11509. mediumvioletred: '#C71585',
  11510. midnightblue: '#191970',
  11511. mintcream: '#F5FFFA',
  11512. mistyrose: '#FFE4E1',
  11513. moccasin: '#FFE4B5',
  11514. navajowhite: '#FFDEAD',
  11515. oldlace: '#FDF5E6',
  11516. olivedrab: '#6B8E23',
  11517. orange: '#FFA500',
  11518. orangered: '#FF4500',
  11519. orchid: '#DA70D6',
  11520. palegoldenrod: '#EEE8AA',
  11521. palegreen: '#98FB98',
  11522. paleturquoise: '#AFEEEE',
  11523. palevioletred: '#DB7093',
  11524. papayawhip: '#FFEFD5',
  11525. peachpuff: '#FFDAB9',
  11526. peru: '#CD853F',
  11527. pink: '#FFC0CB',
  11528. plum: '#DDA0DD',
  11529. powderblue: '#B0E0E6',
  11530. rosybrown: '#BC8F8F',
  11531. royalblue: '#4169E1',
  11532. saddlebrown: '#8B4513',
  11533. salmon: '#FA8072',
  11534. sandybrown: '#F4A460',
  11535. seagreen: '#2E8B57',
  11536. seashell: '#FFF5EE',
  11537. sienna: '#A0522D',
  11538. skyblue: '#87CEEB',
  11539. slateblue: '#6A5ACD',
  11540. slategray: '#708090',
  11541. slategrey: '#708090',
  11542. snow: '#FFFAFA',
  11543. springgreen: '#00FF7F',
  11544. steelblue: '#4682B4',
  11545. tan: '#D2B48C',
  11546. thistle: '#D8BFD8',
  11547. tomato: '#FF6347',
  11548. turquoise: '#40E0D0',
  11549. violet: '#EE82EE',
  11550. wheat: '#F5DEB3',
  11551. whitesmoke: '#F5F5F5',
  11552. yellowgreen: '#9ACD32'
  11553. };
  11554. function getRgbHslContent(styleString) {
  11555. var start = styleString.indexOf('(', 3);
  11556. var end = styleString.indexOf(')', start + 1);
  11557. var parts = styleString.substring(start + 1, end).split(',');
  11558. // add alpha if needed
  11559. if (parts.length != 4 || styleString.charAt(3) != 'a') {
  11560. parts[3] = 1;
  11561. }
  11562. return parts;
  11563. }
  11564. function percent(s) {
  11565. return parseFloat(s) / 100;
  11566. }
  11567. function clamp(v, min, max) {
  11568. return Math.min(max, Math.max(min, v));
  11569. }
  11570. function hslToRgb(parts) {
  11571. var r, g, b, h, s, l;
  11572. h = parseFloat(parts[0]) / 360 % 360;
  11573. if (h < 0) {
  11574. h++;
  11575. }
  11576. s = clamp(percent(parts[1]), 0, 1);
  11577. l = clamp(percent(parts[2]), 0, 1);
  11578. if (s == 0) {
  11579. r = g = b = l;
  11580. } else // achromatic
  11581. {
  11582. var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  11583. var p = 2 * l - q;
  11584. r = hueToRgb(p, q, h + 1 / 3);
  11585. g = hueToRgb(p, q, h);
  11586. b = hueToRgb(p, q, h - 1 / 3);
  11587. }
  11588. return '#' + decToHex[Math.floor(r * 255)] + decToHex[Math.floor(g * 255)] + decToHex[Math.floor(b * 255)];
  11589. }
  11590. function hueToRgb(m1, m2, h) {
  11591. if (h < 0) {
  11592. h++;
  11593. }
  11594. if (h > 1) {
  11595. h--;
  11596. }
  11597. if (6 * h < 1) {
  11598. return m1 + (m2 - m1) * 6 * h;
  11599. }
  11600. else if (2 * h < 1) {
  11601. return m2;
  11602. }
  11603. else if (3 * h < 2) {
  11604. return m1 + (m2 - m1) * (2 / 3 - h) * 6;
  11605. }
  11606. else {
  11607. return m1;
  11608. }
  11609. }
  11610. var processStyleCache = {};
  11611. function processStyle(styleString) {
  11612. if (styleString in processStyleCache) {
  11613. return processStyleCache[styleString];
  11614. }
  11615. var str,
  11616. alpha = 1;
  11617. styleString = String(styleString);
  11618. if (styleString.charAt(0) == '#') {
  11619. str = styleString;
  11620. } else if (/^rgb/.test(styleString)) {
  11621. var parts = getRgbHslContent(styleString);
  11622. var str = '#',
  11623. n;
  11624. for (var i = 0; i < 3; i++) {
  11625. if (parts[i].indexOf('%') != -1) {
  11626. n = Math.floor(percent(parts[i]) * 255);
  11627. } else {
  11628. n = +parts[i];
  11629. }
  11630. str += decToHex[clamp(n, 0, 255)];
  11631. }
  11632. alpha = +parts[3];
  11633. } else if (/^hsl/.test(styleString)) {
  11634. var parts = getRgbHslContent(styleString);
  11635. str = hslToRgb(parts);
  11636. alpha = parts[3];
  11637. } else {
  11638. str = colorData[styleString] || styleString;
  11639. }
  11640. return processStyleCache[styleString] = {
  11641. color: str,
  11642. alpha: alpha
  11643. };
  11644. }
  11645. var DEFAULT_STYLE = {
  11646. style: 'normal',
  11647. variant: 'normal',
  11648. weight: 'normal',
  11649. size: 10,
  11650. family: 'sans-serif'
  11651. };
  11652. // Internal text style cache
  11653. var fontStyleCache = {};
  11654. function processFontStyle(styleString) {
  11655. if (fontStyleCache[styleString]) {
  11656. return fontStyleCache[styleString];
  11657. }
  11658. var el = document.createElement('div');
  11659. var style = el.style;
  11660. try {
  11661. style.font = styleString;
  11662. } catch (ex) {}
  11663. // Ignore failures to set to invalid font.
  11664. return fontStyleCache[styleString] = {
  11665. style: style.fontStyle || DEFAULT_STYLE.style,
  11666. variant: style.fontVariant || DEFAULT_STYLE.variant,
  11667. weight: style.fontWeight || DEFAULT_STYLE.weight,
  11668. size: style.fontSize || DEFAULT_STYLE.size,
  11669. family: style.fontFamily || DEFAULT_STYLE.family
  11670. };
  11671. }
  11672. function getComputedStyle(style, element) {
  11673. var computedStyle = {};
  11674. for (var p in style) {
  11675. computedStyle[p] = style[p];
  11676. }
  11677. // Compute the size
  11678. var canvasFontSize = parseFloat(element.currentStyle.fontSize),
  11679. fontSize = parseFloat(style.size);
  11680. if (typeof style.size == 'number') {
  11681. computedStyle.size = style.size;
  11682. } else if (style.size.indexOf('px') != -1) {
  11683. computedStyle.size = fontSize;
  11684. } else if (style.size.indexOf('em') != -1) {
  11685. computedStyle.size = canvasFontSize * fontSize;
  11686. } else if (style.size.indexOf('%') != -1) {
  11687. computedStyle.size = (canvasFontSize / 100) * fontSize;
  11688. } else if (style.size.indexOf('pt') != -1) {
  11689. computedStyle.size = fontSize / 0.75;
  11690. } else {
  11691. computedStyle.size = canvasFontSize;
  11692. }
  11693. // Different scaling between normal text and VML text. This was found using
  11694. // trial and error to get the same size as non VML text.
  11695. computedStyle.size *= 0.981;
  11696. return computedStyle;
  11697. }
  11698. function buildStyle(style) {
  11699. return style.style + ' ' + style.variant + ' ' + style.weight + ' ' + style.size + 'px ' + style.family;
  11700. }
  11701. var lineCapMap = {
  11702. 'butt': 'flat',
  11703. 'round': 'round'
  11704. };
  11705. function processLineCap(lineCap) {
  11706. return lineCapMap[lineCap] || 'square';
  11707. }
  11708. /*
  11709. * This class implements CanvasRenderingContext2D interface as described by
  11710. * the WHATWG.
  11711. * @param {HTMLElement} canvasElement The element that the 2D context should
  11712. * be associated with
  11713. * @private
  11714. */
  11715. function CanvasRenderingContext2D_(canvasElement) {
  11716. this.m_ = createMatrixIdentity();
  11717. this.mStack_ = [];
  11718. this.aStack_ = [];
  11719. this.currentPath_ = [];
  11720. // Canvas context properties
  11721. this.strokeStyle = '#000';
  11722. this.fillStyle = '#000';
  11723. this.lineWidth = 1;
  11724. this.lineJoin = 'miter';
  11725. this.lineDash = [];
  11726. this.lineCap = 'butt';
  11727. this.miterLimit = Z * 1;
  11728. this.globalAlpha = 1;
  11729. this.font = '10px sans-serif';
  11730. this.textAlign = 'left';
  11731. this.textBaseline = 'alphabetic';
  11732. this.canvas = canvasElement;
  11733. var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' + canvasElement.clientHeight + 'px;overflow:hidden;position:absolute';
  11734. var el = canvasElement.ownerDocument.createElement('div');
  11735. el.style.cssText = cssText;
  11736. canvasElement.appendChild(el);
  11737. var overlayEl = el.cloneNode(false);
  11738. // Use a non transparent background.
  11739. overlayEl.style.backgroundColor = 'red';
  11740. overlayEl.style.filter = 'alpha(opacity=0)';
  11741. canvasElement.appendChild(overlayEl);
  11742. this.element_ = el;
  11743. this.arcScaleX_ = 1;
  11744. this.arcScaleY_ = 1;
  11745. this.lineScale_ = 1;
  11746. }
  11747. var contextPrototype = CanvasRenderingContext2D_.prototype;
  11748. contextPrototype.clearRect = function() {
  11749. if (this.textMeasureEl_) {
  11750. this.textMeasureEl_.removeNode(true);
  11751. this.textMeasureEl_ = null;
  11752. }
  11753. this.element_.innerHTML = '';
  11754. };
  11755. contextPrototype.beginPath = function() {
  11756. // TODO: Branch current matrix so that save/restore has no effect
  11757. // as per safari docs.
  11758. this.currentPath_ = [];
  11759. };
  11760. contextPrototype.moveTo = function(aX, aY) {
  11761. var p = getCoords(this, aX, aY);
  11762. this.currentPath_.push({
  11763. type: 'moveTo',
  11764. x: p.x,
  11765. y: p.y
  11766. });
  11767. this.currentX_ = p.x;
  11768. this.currentY_ = p.y;
  11769. };
  11770. contextPrototype.lineTo = function(aX, aY) {
  11771. var p = getCoords(this, aX, aY);
  11772. this.currentPath_.push({
  11773. type: 'lineTo',
  11774. x: p.x,
  11775. y: p.y
  11776. });
  11777. this.currentX_ = p.x;
  11778. this.currentY_ = p.y;
  11779. };
  11780. contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) {
  11781. var p = getCoords(this, aX, aY);
  11782. var cp1 = getCoords(this, aCP1x, aCP1y);
  11783. var cp2 = getCoords(this, aCP2x, aCP2y);
  11784. bezierCurveTo(this, cp1, cp2, p);
  11785. };
  11786. // Helper function that takes the already fixed cordinates.
  11787. function bezierCurveTo(self, cp1, cp2, p) {
  11788. self.currentPath_.push({
  11789. type: 'bezierCurveTo',
  11790. cp1x: cp1.x,
  11791. cp1y: cp1.y,
  11792. cp2x: cp2.x,
  11793. cp2y: cp2.y,
  11794. x: p.x,
  11795. y: p.y
  11796. });
  11797. self.currentX_ = p.x;
  11798. self.currentY_ = p.y;
  11799. }
  11800. contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
  11801. // the following is lifted almost directly from
  11802. // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
  11803. var cp = getCoords(this, aCPx, aCPy);
  11804. var p = getCoords(this, aX, aY);
  11805. var cp1 = {
  11806. x: this.currentX_ + 2 / 3 * (cp.x - this.currentX_),
  11807. y: this.currentY_ + 2 / 3 * (cp.y - this.currentY_)
  11808. };
  11809. var cp2 = {
  11810. x: cp1.x + (p.x - this.currentX_) / 3,
  11811. y: cp1.y + (p.y - this.currentY_) / 3
  11812. };
  11813. bezierCurveTo(this, cp1, cp2, p);
  11814. };
  11815. contextPrototype.arc = function(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) {
  11816. aRadius *= Z;
  11817. var arcType = aClockwise ? 'at' : 'wa';
  11818. var xStart = aX + mc(aStartAngle) * aRadius - Z2;
  11819. var yStart = aY + ms(aStartAngle) * aRadius - Z2;
  11820. var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
  11821. var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
  11822. // IE won't render arches drawn counter clockwise if xStart == xEnd.
  11823. if (xStart == xEnd && !aClockwise) {
  11824. xStart += 0.125;
  11825. }
  11826. // Offset xStart by 1/80 of a pixel. Use something
  11827. // that can be represented in binary
  11828. var p = getCoords(this, aX, aY);
  11829. var pStart = getCoords(this, xStart, yStart);
  11830. var pEnd = getCoords(this, xEnd, yEnd);
  11831. this.currentPath_.push({
  11832. type: arcType,
  11833. x: p.x,
  11834. y: p.y,
  11835. radius: aRadius,
  11836. xStart: pStart.x,
  11837. yStart: pStart.y,
  11838. xEnd: pEnd.x,
  11839. yEnd: pEnd.y
  11840. });
  11841. };
  11842. contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
  11843. this.moveTo(aX, aY);
  11844. this.lineTo(aX + aWidth, aY);
  11845. this.lineTo(aX + aWidth, aY + aHeight);
  11846. this.lineTo(aX, aY + aHeight);
  11847. this.closePath();
  11848. };
  11849. contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
  11850. var oldPath = this.currentPath_;
  11851. this.beginPath();
  11852. this.moveTo(aX, aY);
  11853. this.lineTo(aX + aWidth, aY);
  11854. this.lineTo(aX + aWidth, aY + aHeight);
  11855. this.lineTo(aX, aY + aHeight);
  11856. this.closePath();
  11857. this.stroke();
  11858. this.currentPath_ = oldPath;
  11859. };
  11860. contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
  11861. var oldPath = this.currentPath_;
  11862. this.beginPath();
  11863. this.moveTo(aX, aY);
  11864. this.lineTo(aX + aWidth, aY);
  11865. this.lineTo(aX + aWidth, aY + aHeight);
  11866. this.lineTo(aX, aY + aHeight);
  11867. this.closePath();
  11868. this.fill();
  11869. this.currentPath_ = oldPath;
  11870. };
  11871. contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
  11872. var gradient = new CanvasGradient_('gradient');
  11873. gradient.x0_ = aX0;
  11874. gradient.y0_ = aY0;
  11875. gradient.x1_ = aX1;
  11876. gradient.y1_ = aY1;
  11877. return gradient;
  11878. };
  11879. contextPrototype.createRadialGradient = function(aX0, aY0, aR0, aX1, aY1, aR1) {
  11880. var gradient = new CanvasGradient_('gradientradial');
  11881. gradient.x0_ = aX0;
  11882. gradient.y0_ = aY0;
  11883. gradient.r0_ = aR0;
  11884. gradient.x1_ = aX1;
  11885. gradient.y1_ = aY1;
  11886. gradient.r1_ = aR1;
  11887. return gradient;
  11888. };
  11889. contextPrototype.drawImage = function(image, var_args) {
  11890. var dx, dy, dw, dh, sx, sy, sw, sh;
  11891. // to find the original width we overide the width and height
  11892. var oldRuntimeWidth = image.runtimeStyle.width;
  11893. var oldRuntimeHeight = image.runtimeStyle.height;
  11894. image.runtimeStyle.width = 'auto';
  11895. image.runtimeStyle.height = 'auto';
  11896. // get the original size
  11897. var w = image.width;
  11898. var h = image.height;
  11899. // and remove overides
  11900. image.runtimeStyle.width = oldRuntimeWidth;
  11901. image.runtimeStyle.height = oldRuntimeHeight;
  11902. if (arguments.length == 3) {
  11903. dx = arguments[1];
  11904. dy = arguments[2];
  11905. sx = sy = 0;
  11906. sw = dw = w;
  11907. sh = dh = h;
  11908. } else if (arguments.length == 5) {
  11909. dx = arguments[1];
  11910. dy = arguments[2];
  11911. dw = arguments[3];
  11912. dh = arguments[4];
  11913. sx = sy = 0;
  11914. sw = w;
  11915. sh = h;
  11916. } else if (arguments.length == 9) {
  11917. sx = arguments[1];
  11918. sy = arguments[2];
  11919. sw = arguments[3];
  11920. sh = arguments[4];
  11921. dx = arguments[5];
  11922. dy = arguments[6];
  11923. dw = arguments[7];
  11924. dh = arguments[8];
  11925. } else {
  11926. throw Error('Invalid number of arguments');
  11927. }
  11928. var d = getCoords(this, dx, dy);
  11929. var vmlStr = [];
  11930. var W = 10;
  11931. var H = 10;
  11932. var m = this.m_;
  11933. 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), ';');
  11934. 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>');
  11935. this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
  11936. };
  11937. contextPrototype.setLineDash = function(lineDash) {
  11938. if (lineDash.length === 1) {
  11939. lineDash = lineDash.slice();
  11940. lineDash[1] = lineDash[0];
  11941. }
  11942. this.lineDash = lineDash;
  11943. };
  11944. contextPrototype.getLineDash = function() {
  11945. return this.lineDash;
  11946. };
  11947. contextPrototype.stroke = function(aFill) {
  11948. var lineStr = [];
  11949. var W = 10;
  11950. var H = 10;
  11951. 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="');
  11952. var min = {
  11953. x: null,
  11954. y: null
  11955. };
  11956. var max = {
  11957. x: null,
  11958. y: null
  11959. };
  11960. for (var i = 0; i < this.currentPath_.length; i++) {
  11961. var p = this.currentPath_[i];
  11962. var c;
  11963. switch (p.type) {
  11964. case 'moveTo':
  11965. c = p;
  11966. lineStr.push(' m ', mr(p.x), ',', mr(p.y));
  11967. break;
  11968. case 'lineTo':
  11969. lineStr.push(' l ', mr(p.x), ',', mr(p.y));
  11970. break;
  11971. case 'close':
  11972. lineStr.push(' x ');
  11973. p = null;
  11974. break;
  11975. case 'bezierCurveTo':
  11976. lineStr.push(' c ', mr(p.cp1x), ',', mr(p.cp1y), ',', mr(p.cp2x), ',', mr(p.cp2y), ',', mr(p.x), ',', mr(p.y));
  11977. break;
  11978. case 'at':
  11979. case 'wa':
  11980. 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));
  11981. break;
  11982. }
  11983. // TODO: Following is broken for curves due to
  11984. // move to proper paths.
  11985. // Figure out dimensions so we can do gradient fills
  11986. // properly
  11987. if (p) {
  11988. if (min.x == null || p.x < min.x) {
  11989. min.x = p.x;
  11990. }
  11991. if (max.x == null || p.x > max.x) {
  11992. max.x = p.x;
  11993. }
  11994. if (min.y == null || p.y < min.y) {
  11995. min.y = p.y;
  11996. }
  11997. if (max.y == null || p.y > max.y) {
  11998. max.y = p.y;
  11999. }
  12000. }
  12001. }
  12002. lineStr.push(' ">');
  12003. if (!aFill) {
  12004. appendStroke(this, lineStr);
  12005. } else {
  12006. appendFill(this, lineStr, min, max);
  12007. }
  12008. lineStr.push('</g_vml_:shape>');
  12009. this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
  12010. };
  12011. function appendStroke(ctx, lineStr) {
  12012. var a = processStyle(ctx.strokeStyle);
  12013. var color = a.color;
  12014. var opacity = a.alpha * ctx.globalAlpha;
  12015. var lineWidth = ctx.lineScale_ * ctx.lineWidth;
  12016. // VML cannot correctly render a line if the width is less than 1px.
  12017. // In that case, we dilute the color to make the line look thinner.
  12018. if (lineWidth < 1) {
  12019. opacity *= lineWidth;
  12020. }
  12021. 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, '" />');
  12022. }
  12023. function appendFill(ctx, lineStr, min, max) {
  12024. var fillStyle = ctx.fillStyle;
  12025. var arcScaleX = ctx.arcScaleX_;
  12026. var arcScaleY = ctx.arcScaleY_;
  12027. var width = max.x - min.x;
  12028. var height = max.y - min.y;
  12029. if (fillStyle instanceof CanvasGradient_) {
  12030. // TODO: Gradients transformed with the transformation matrix.
  12031. var angle = 0;
  12032. var focus = {
  12033. x: 0,
  12034. y: 0
  12035. };
  12036. // additional offset
  12037. var shift = 0;
  12038. // scale factor for offset
  12039. var expansion = 1;
  12040. if (fillStyle.type_ == 'gradient') {
  12041. var x0 = fillStyle.x0_ / arcScaleX;
  12042. var y0 = fillStyle.y0_ / arcScaleY;
  12043. var x1 = fillStyle.x1_ / arcScaleX;
  12044. var y1 = fillStyle.y1_ / arcScaleY;
  12045. var p0 = getCoords(ctx, x0, y0);
  12046. var p1 = getCoords(ctx, x1, y1);
  12047. var dx = p1.x - p0.x;
  12048. var dy = p1.y - p0.y;
  12049. angle = Math.atan2(dx, dy) * 180 / Math.PI;
  12050. // The angle should be a non-negative number.
  12051. if (angle < 0) {
  12052. angle += 360;
  12053. }
  12054. // Very small angles produce an unexpected result because they are
  12055. // converted to a scientific notation string.
  12056. if (angle < 1.0E-6) {
  12057. angle = 0;
  12058. }
  12059. } else {
  12060. var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_);
  12061. focus = {
  12062. x: (p0.x - min.x) / width,
  12063. y: (p0.y - min.y) / height
  12064. };
  12065. width /= arcScaleX * Z;
  12066. height /= arcScaleY * Z;
  12067. var dimension = m.max(width, height);
  12068. shift = 2 * fillStyle.r0_ / dimension;
  12069. expansion = 2 * fillStyle.r1_ / dimension - shift;
  12070. }
  12071. // We need to sort the color stops in ascending order by offset,
  12072. // otherwise IE won't interpret it correctly.
  12073. var stops = fillStyle.colors_;
  12074. stops.sort(function(cs1, cs2) {
  12075. return cs1.offset - cs2.offset;
  12076. });
  12077. var length = stops.length;
  12078. var color1 = stops[0].color;
  12079. var color2 = stops[length - 1].color;
  12080. var opacity1 = stops[0].alpha * ctx.globalAlpha;
  12081. var opacity2 = stops[length - 1].alpha * ctx.globalAlpha;
  12082. var colors = [];
  12083. for (var i = 0; i < length; i++) {
  12084. var stop = stops[i];
  12085. colors.push(stop.offset * expansion + shift + ' ' + stop.color);
  12086. }
  12087. // When colors attribute is used, the meanings of opacity and o:opacity2
  12088. // are reversed.
  12089. 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, '" />');
  12090. } else if (fillStyle instanceof CanvasPattern_) {
  12091. if (width && height) {
  12092. var deltaLeft = -min.x;
  12093. var deltaTop = -min.y;
  12094. 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.
  12095. //' size="', w, 'px ', h, 'px"',
  12096. ' src="', fillStyle.src_, '" />');
  12097. }
  12098. } else {
  12099. var a = processStyle(ctx.fillStyle);
  12100. var color = a.color;
  12101. var opacity = a.alpha * ctx.globalAlpha;
  12102. lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />');
  12103. }
  12104. }
  12105. contextPrototype.fill = function() {
  12106. // Calling `$stroke` here because otherwise we'd call not the native ctx.stroke,
  12107. // but our override from Ext.draw.engine.Canvas.statics.contextOverrides.
  12108. this.$stroke(true);
  12109. };
  12110. contextPrototype.closePath = function() {
  12111. this.currentPath_.push({
  12112. type: 'close'
  12113. });
  12114. };
  12115. function getCoords(ctx, aX, aY) {
  12116. var m = ctx.m_;
  12117. return {
  12118. x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
  12119. y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
  12120. };
  12121. }
  12122. contextPrototype.save = function() {
  12123. var o = {};
  12124. copyState(this, o);
  12125. this.aStack_.push(o);
  12126. this.mStack_.push(this.m_);
  12127. };
  12128. contextPrototype.restore = function() {
  12129. if (this.aStack_.length) {
  12130. copyState(this.aStack_.pop(), this);
  12131. this.m_ = this.mStack_.pop();
  12132. }
  12133. };
  12134. function matrixIsFinite(m) {
  12135. 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]);
  12136. }
  12137. function setM(ctx, m, updateLineScale) {
  12138. if (!matrixIsFinite(m)) {
  12139. return;
  12140. }
  12141. ctx.m_ = m;
  12142. if (updateLineScale) {
  12143. // Get the line scale.
  12144. // Determinant of this.m_ means how much the area is enlarged by the
  12145. // transformation. So its square root can be used as a scale factor
  12146. // for width.
  12147. var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
  12148. ctx.lineScale_ = sqrt(abs(det));
  12149. }
  12150. }
  12151. contextPrototype.translate = function(aX, aY) {
  12152. var m1 = [
  12153. [
  12154. 1,
  12155. 0,
  12156. 0
  12157. ],
  12158. [
  12159. 0,
  12160. 1,
  12161. 0
  12162. ],
  12163. [
  12164. aX,
  12165. aY,
  12166. 1
  12167. ]
  12168. ];
  12169. setM(this, matrixMultiply(m1, this.m_), false);
  12170. };
  12171. contextPrototype.rotate = function(aRot) {
  12172. var c = mc(aRot);
  12173. var s = ms(aRot);
  12174. var m1 = [
  12175. [
  12176. c,
  12177. s,
  12178. 0
  12179. ],
  12180. [
  12181. -s,
  12182. c,
  12183. 0
  12184. ],
  12185. [
  12186. 0,
  12187. 0,
  12188. 1
  12189. ]
  12190. ];
  12191. setM(this, matrixMultiply(m1, this.m_), false);
  12192. };
  12193. contextPrototype.scale = function(aX, aY) {
  12194. this.arcScaleX_ *= aX;
  12195. this.arcScaleY_ *= aY;
  12196. var m1 = [
  12197. [
  12198. aX,
  12199. 0,
  12200. 0
  12201. ],
  12202. [
  12203. 0,
  12204. aY,
  12205. 0
  12206. ],
  12207. [
  12208. 0,
  12209. 0,
  12210. 1
  12211. ]
  12212. ];
  12213. setM(this, matrixMultiply(m1, this.m_), true);
  12214. };
  12215. contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
  12216. var m1 = [
  12217. [
  12218. m11,
  12219. m12,
  12220. 0
  12221. ],
  12222. [
  12223. m21,
  12224. m22,
  12225. 0
  12226. ],
  12227. [
  12228. dx,
  12229. dy,
  12230. 1
  12231. ]
  12232. ];
  12233. setM(this, matrixMultiply(m1, this.m_), true);
  12234. };
  12235. contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
  12236. var m = [
  12237. [
  12238. m11,
  12239. m12,
  12240. 0
  12241. ],
  12242. [
  12243. m21,
  12244. m22,
  12245. 0
  12246. ],
  12247. [
  12248. dx,
  12249. dy,
  12250. 1
  12251. ]
  12252. ];
  12253. setM(this, m, true);
  12254. };
  12255. /*
  12256. * The text drawing function.
  12257. * The maxWidth argument isn't taken in account, since no browser supports
  12258. * it yet.
  12259. */
  12260. contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
  12261. var m = this.m_,
  12262. delta = 1000,
  12263. left = 0,
  12264. right = delta,
  12265. offset = {
  12266. x: 0,
  12267. y: 0
  12268. },
  12269. lineStr = [];
  12270. var fontStyle = getComputedStyle(processFontStyle(this.font), this.element_);
  12271. var fontStyleString = buildStyle(fontStyle);
  12272. var elementStyle = this.element_.currentStyle;
  12273. var textAlign = this.textAlign.toLowerCase();
  12274. switch (textAlign) {
  12275. case 'left':
  12276. case 'center':
  12277. case 'right':
  12278. break;
  12279. case 'end':
  12280. textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
  12281. break;
  12282. case 'start':
  12283. textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
  12284. break;
  12285. default:
  12286. textAlign = 'left';
  12287. }
  12288. // 1.75 is an arbitrary number, as there is no info about the text baseline
  12289. switch (this.textBaseline) {
  12290. case 'hanging':
  12291. case 'top':
  12292. offset.y = fontStyle.size / 1.75;
  12293. break;
  12294. case 'middle':
  12295. break;
  12296. default:
  12297. case null:
  12298. case 'alphabetic':
  12299. case 'ideographic':
  12300. case 'bottom':
  12301. offset.y = -fontStyle.size / 3;
  12302. break;
  12303. }
  12304. switch (textAlign) {
  12305. case 'right':
  12306. left = delta;
  12307. right = 0.05;
  12308. break;
  12309. case 'center':
  12310. left = right = delta / 2;
  12311. break;
  12312. }
  12313. var d = getCoords(this, x + offset.x, y + offset.y);
  12314. 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;">');
  12315. if (stroke) {
  12316. appendStroke(this, lineStr);
  12317. } else {
  12318. // TODO: Fix the min and max params.
  12319. appendFill(this, lineStr, {
  12320. x: -left,
  12321. y: 0
  12322. }, {
  12323. x: right,
  12324. y: fontStyle.size
  12325. });
  12326. }
  12327. var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' + m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0';
  12328. var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z);
  12329. 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>');
  12330. this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
  12331. };
  12332. contextPrototype.fillText = function(text, x, y, maxWidth) {
  12333. this.drawText_(text, x, y, maxWidth, false);
  12334. };
  12335. contextPrototype.strokeText = function(text, x, y, maxWidth) {
  12336. this.drawText_(text, x, y, maxWidth, true);
  12337. };
  12338. contextPrototype.measureText = function(text) {
  12339. if (!this.textMeasureEl_) {
  12340. var s = '<span style="position:absolute;' + 'top:-20000px;left:0;padding:0;margin:0;border:none;' + 'white-space:pre;"></span>';
  12341. this.element_.insertAdjacentHTML('beforeEnd', s);
  12342. this.textMeasureEl_ = this.element_.lastChild;
  12343. }
  12344. var doc = this.element_.ownerDocument;
  12345. this.textMeasureEl_.innerHTML = '';
  12346. this.textMeasureEl_.style.font = this.font;
  12347. // Don't use innerHTML or innerText because they allow markup/whitespace.
  12348. this.textMeasureEl_.appendChild(doc.createTextNode(text));
  12349. return {
  12350. width: this.textMeasureEl_.offsetWidth
  12351. };
  12352. };
  12353. /* STUBS */
  12354. contextPrototype.clip = function() {};
  12355. // TODO: Implement
  12356. contextPrototype.arcTo = function() {};
  12357. // TODO: Implement
  12358. contextPrototype.createPattern = function(image, repetition) {
  12359. return new CanvasPattern_(image, repetition);
  12360. };
  12361. // Gradient / Pattern Stubs
  12362. function CanvasGradient_(aType) {
  12363. this.type_ = aType;
  12364. this.x0_ = 0;
  12365. this.y0_ = 0;
  12366. this.r0_ = 0;
  12367. this.x1_ = 0;
  12368. this.y1_ = 0;
  12369. this.r1_ = 0;
  12370. this.colors_ = [];
  12371. }
  12372. CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
  12373. aColor = processStyle(aColor);
  12374. this.colors_.push({
  12375. offset: aOffset,
  12376. color: aColor.color,
  12377. alpha: aColor.alpha
  12378. });
  12379. };
  12380. function CanvasPattern_(image, repetition) {
  12381. assertImageIsValid(image);
  12382. switch (repetition) {
  12383. case 'repeat':
  12384. case null:
  12385. case '':
  12386. this.repetition_ = 'repeat';
  12387. break;
  12388. case 'repeat-x':
  12389. case 'repeat-y':
  12390. case 'no-repeat':
  12391. this.repetition_ = repetition;
  12392. break;
  12393. default:
  12394. throwException('SYNTAX_ERR');
  12395. }
  12396. this.src_ = image.src;
  12397. this.width_ = image.width;
  12398. this.height_ = image.height;
  12399. }
  12400. function throwException(s) {
  12401. throw new DOMException_(s);
  12402. }
  12403. function assertImageIsValid(img) {
  12404. if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
  12405. throwException('TYPE_MISMATCH_ERR');
  12406. }
  12407. if (img.readyState != 'complete') {
  12408. throwException('INVALID_STATE_ERR');
  12409. }
  12410. }
  12411. function DOMException_(s) {
  12412. this.code = this[s];
  12413. this.message = s + ': DOM Exception ' + this.code;
  12414. }
  12415. var p = DOMException_.prototype = new Error();
  12416. p.INDEX_SIZE_ERR = 1;
  12417. p.DOMSTRING_SIZE_ERR = 2;
  12418. p.HIERARCHY_REQUEST_ERR = 3;
  12419. p.WRONG_DOCUMENT_ERR = 4;
  12420. p.INVALID_CHARACTER_ERR = 5;
  12421. p.NO_DATA_ALLOWED_ERR = 6;
  12422. p.NO_MODIFICATION_ALLOWED_ERR = 7;
  12423. p.NOT_FOUND_ERR = 8;
  12424. p.NOT_SUPPORTED_ERR = 9;
  12425. p.INUSE_ATTRIBUTE_ERR = 10;
  12426. p.INVALID_STATE_ERR = 11;
  12427. p.SYNTAX_ERR = 12;
  12428. p.INVALID_MODIFICATION_ERR = 13;
  12429. p.NAMESPACE_ERR = 14;
  12430. p.INVALID_ACCESS_ERR = 15;
  12431. p.VALIDATION_ERR = 16;
  12432. p.TYPE_MISMATCH_ERR = 17;
  12433. // set up externs
  12434. G_vmlCanvasManager = G_vmlCanvasManager_;
  12435. CanvasRenderingContext2D = CanvasRenderingContext2D_;
  12436. CanvasGradient = CanvasGradient_;
  12437. CanvasPattern = CanvasPattern_;
  12438. DOMException = DOMException_;
  12439. })();
  12440. }
  12441. // if
  12442. /* global G_vmlCanvasManager */
  12443. /**
  12444. * Provides specific methods to draw with 2D Canvas element.
  12445. */
  12446. Ext.define('Ext.draw.engine.Canvas', {
  12447. extend: 'Ext.draw.Surface',
  12448. isCanvas: true,
  12449. requires: [
  12450. //<feature legacyBrowser>
  12451. 'Ext.draw.engine.excanvas',
  12452. //</feature>
  12453. 'Ext.draw.Animator',
  12454. 'Ext.draw.Color'
  12455. ],
  12456. config: {
  12457. /**
  12458. * @cfg {Boolean} highPrecision
  12459. * True to have the Canvas use JavaScript Number instead of single precision floating point
  12460. * for transforms.
  12461. *
  12462. * For example, when using data with big numbers to plot line series, the transformation
  12463. * matrix of the canvas will have big elements. Due to the implementation of the SVGMatrix,
  12464. * the elements are represented by 32-bits floats, which will work incorrectly.
  12465. * To compensate for that, we enable the canvas context to perform all the transformations
  12466. * in JavaScript.
  12467. *
  12468. * Do not use this if you are not encountering 32-bit floating point errors problem,
  12469. * since this will result in a performance penalty.
  12470. */
  12471. highPrecision: false
  12472. },
  12473. statics: {
  12474. contextOverrides: {
  12475. /**
  12476. * @ignore
  12477. */
  12478. setGradientBBox: function(bbox) {
  12479. this.bbox = bbox;
  12480. },
  12481. /**
  12482. * Fills the subpaths of the current default path or the given path with the current
  12483. * fill style.
  12484. * @ignore
  12485. */
  12486. fill: function() {
  12487. var fillStyle = this.fillStyle,
  12488. fillGradient = this.fillGradient,
  12489. fillOpacity = this.fillOpacity,
  12490. alpha = this.globalAlpha,
  12491. bbox = this.bbox;
  12492. if (fillStyle !== Ext.util.Color.RGBA_NONE && fillOpacity !== 0) {
  12493. if (fillGradient && bbox) {
  12494. this.fillStyle = fillGradient.generateGradient(this, bbox);
  12495. }
  12496. if (fillOpacity !== 1) {
  12497. this.globalAlpha = alpha * fillOpacity;
  12498. }
  12499. this.$fill();
  12500. if (fillOpacity !== 1) {
  12501. this.globalAlpha = alpha;
  12502. }
  12503. if (fillGradient && bbox) {
  12504. this.fillStyle = fillStyle;
  12505. }
  12506. }
  12507. },
  12508. /**
  12509. * Strokes the subpaths of the current default path or the given path with the current
  12510. * stroke style.
  12511. * @ignore
  12512. */
  12513. stroke: function() {
  12514. var strokeStyle = this.strokeStyle,
  12515. strokeGradient = this.strokeGradient,
  12516. strokeOpacity = this.strokeOpacity,
  12517. alpha = this.globalAlpha,
  12518. bbox = this.bbox;
  12519. if (strokeStyle !== Ext.util.Color.RGBA_NONE && strokeOpacity !== 0) {
  12520. if (strokeGradient && bbox) {
  12521. this.strokeStyle = strokeGradient.generateGradient(this, bbox);
  12522. }
  12523. if (strokeOpacity !== 1) {
  12524. this.globalAlpha = alpha * strokeOpacity;
  12525. }
  12526. this.$stroke();
  12527. if (strokeOpacity !== 1) {
  12528. this.globalAlpha = alpha;
  12529. }
  12530. if (strokeGradient && bbox) {
  12531. this.strokeStyle = strokeStyle;
  12532. }
  12533. }
  12534. },
  12535. /**
  12536. * @ignore
  12537. */
  12538. fillStroke: function(attr, transformFillStroke) {
  12539. var ctx = this,
  12540. fillStyle = this.fillStyle,
  12541. fillOpacity = this.fillOpacity,
  12542. strokeStyle = this.strokeStyle,
  12543. strokeOpacity = this.strokeOpacity,
  12544. shadowColor = ctx.shadowColor,
  12545. shadowBlur = ctx.shadowBlur,
  12546. none = Ext.util.Color.RGBA_NONE;
  12547. if (transformFillStroke === undefined) {
  12548. transformFillStroke = attr.transformFillStroke;
  12549. }
  12550. if (!transformFillStroke) {
  12551. attr.inverseMatrix.toContext(ctx);
  12552. }
  12553. if (fillStyle !== none && fillOpacity !== 0) {
  12554. ctx.fill();
  12555. ctx.shadowColor = none;
  12556. ctx.shadowBlur = 0;
  12557. }
  12558. if (strokeStyle !== none && strokeOpacity !== 0) {
  12559. ctx.stroke();
  12560. }
  12561. ctx.shadowColor = shadowColor;
  12562. ctx.shadowBlur = shadowBlur;
  12563. },
  12564. /**
  12565. * 2D Canvas context in IE (up to IE10, inclusive) doesn't support
  12566. * the setLineDash method and the lineDashOffset property.
  12567. * @param dashList An even number of non-negative numbers specifying a dash list.
  12568. */
  12569. setLineDash: function(dashList) {
  12570. if (this.$setLineDash) {
  12571. this.$setLineDash(dashList);
  12572. }
  12573. },
  12574. getLineDash: function() {
  12575. if (this.$getLineDash) {
  12576. return this.$getLineDash();
  12577. }
  12578. },
  12579. /**
  12580. * Adds points to the subpath such that the arc described by the circumference of the
  12581. * ellipse described by the arguments, starting at the given start angle and ending at
  12582. * the given end angle, going in the given direction (defaulting to clockwise), is added
  12583. * to the path, connected to the previous point by a straight line.
  12584. * @ignore
  12585. */
  12586. ellipse: function(cx, cy, rx, ry, rotation, start, end, anticlockwise) {
  12587. var cos = Math.cos(rotation),
  12588. sin = Math.sin(rotation);
  12589. this.transform(cos * rx, sin * rx, -sin * ry, cos * ry, cx, cy);
  12590. this.arc(0, 0, 1, start, end, anticlockwise);
  12591. this.transform(cos / rx, -sin / ry, sin / rx, cos / ry, -(cos * cx + sin * cy) / rx, (sin * cx - cos * cy) / ry);
  12592. },
  12593. /**
  12594. * Uses the given path commands to begin a new path on the canvas.
  12595. * @ignore
  12596. */
  12597. appendPath: function(path) {
  12598. var me = this,
  12599. i = 0,
  12600. j = 0,
  12601. commands = path.commands,
  12602. params = path.params,
  12603. ln = commands.length;
  12604. me.beginPath();
  12605. for (; i < ln; i++) {
  12606. switch (commands[i]) {
  12607. case 'M':
  12608. me.moveTo(params[j], params[j + 1]);
  12609. j += 2;
  12610. break;
  12611. case 'L':
  12612. me.lineTo(params[j], params[j + 1]);
  12613. j += 2;
  12614. break;
  12615. case 'C':
  12616. me.bezierCurveTo(params[j], params[j + 1], params[j + 2], params[j + 3], params[j + 4], params[j + 5]);
  12617. j += 6;
  12618. break;
  12619. case 'Z':
  12620. me.closePath();
  12621. break;
  12622. }
  12623. }
  12624. },
  12625. save: function() {
  12626. var toSave = this.toSave,
  12627. ln = toSave.length,
  12628. obj = ln && {},
  12629. // Don't allocate memory if we don't have to.
  12630. i = 0,
  12631. key;
  12632. for (; i < ln; i++) {
  12633. key = toSave[i];
  12634. if (key in this) {
  12635. obj[key] = this[key];
  12636. }
  12637. }
  12638. this.state.push(obj);
  12639. this.$save();
  12640. },
  12641. restore: function() {
  12642. var obj = this.state.pop(),
  12643. key;
  12644. if (obj) {
  12645. for (key in obj) {
  12646. this[key] = obj[key];
  12647. }
  12648. }
  12649. this.$restore();
  12650. }
  12651. }
  12652. },
  12653. splitThreshold: 3000,
  12654. /**
  12655. * @private
  12656. * Properties to be saved/restored in the `save` and `restore` methods.
  12657. */
  12658. toSave: [
  12659. 'fillGradient',
  12660. 'strokeGradient'
  12661. ],
  12662. /**
  12663. * @property element
  12664. * @inheritdoc
  12665. */
  12666. element: {
  12667. reference: 'element',
  12668. children: [
  12669. {
  12670. reference: 'bodyElement',
  12671. style: {
  12672. width: '100%',
  12673. height: '100%',
  12674. position: 'relative'
  12675. }
  12676. }
  12677. ]
  12678. },
  12679. /**
  12680. * @private
  12681. *
  12682. * Creates the canvas element.
  12683. */
  12684. createCanvas: function() {
  12685. var canvas, overrides, ctx, name;
  12686. canvas = Ext.Element.create({
  12687. tag: 'canvas',
  12688. cls: Ext.baseCSSPrefix + 'surface-canvas'
  12689. });
  12690. // Emulate Canvas in IE8 with VML.
  12691. if (window.G_vmlCanvasManager) {
  12692. G_vmlCanvasManager.initElement(canvas.dom);
  12693. this.isVML = true;
  12694. }
  12695. overrides = Ext.draw.engine.Canvas.contextOverrides;
  12696. ctx = canvas.dom.getContext('2d');
  12697. if (ctx.ellipse) {
  12698. delete overrides.ellipse;
  12699. }
  12700. ctx.state = [];
  12701. ctx.toSave = this.toSave;
  12702. // Saving references to the native Canvas context methods that we'll be overriding.
  12703. for (name in overrides) {
  12704. ctx['$' + name] = ctx[name];
  12705. }
  12706. Ext.apply(ctx, overrides);
  12707. if (this.getHighPrecision()) {
  12708. this.enablePrecisionCompensation(ctx);
  12709. } else {
  12710. this.disablePrecisionCompensation(ctx);
  12711. }
  12712. this.bodyElement.appendChild(canvas);
  12713. this.canvases.push(canvas);
  12714. this.contexts.push(ctx);
  12715. },
  12716. updateHighPrecision: function(highPrecision) {
  12717. var contexts = this.contexts,
  12718. ln = contexts.length,
  12719. i, context;
  12720. for (i = 0; i < ln; i++) {
  12721. context = contexts[i];
  12722. if (highPrecision) {
  12723. this.enablePrecisionCompensation(context);
  12724. } else {
  12725. this.disablePrecisionCompensation(context);
  12726. }
  12727. }
  12728. },
  12729. precisionNames: [
  12730. 'rect',
  12731. 'fillRect',
  12732. 'strokeRect',
  12733. 'clearRect',
  12734. 'moveTo',
  12735. 'lineTo',
  12736. 'arc',
  12737. 'arcTo',
  12738. 'save',
  12739. 'restore',
  12740. 'updatePrecisionCompensate',
  12741. 'setTransform',
  12742. 'transform',
  12743. 'scale',
  12744. 'translate',
  12745. 'rotate',
  12746. 'quadraticCurveTo',
  12747. 'bezierCurveTo',
  12748. 'createLinearGradient',
  12749. 'createRadialGradient',
  12750. 'fillText',
  12751. 'strokeText',
  12752. 'drawImage'
  12753. ],
  12754. /**
  12755. * @private
  12756. * Clears canvas of compensation for canvas' use of single precision floating point.
  12757. * @param {CanvasRenderingContext2D} ctx The canvas context.
  12758. */
  12759. disablePrecisionCompensation: function(ctx) {
  12760. var regularOverrides = Ext.draw.engine.Canvas.contextOverrides,
  12761. precisionOverrides = this.precisionNames,
  12762. ln = precisionOverrides.length,
  12763. i, name;
  12764. for (i = 0; i < ln; i++) {
  12765. name = precisionOverrides[i];
  12766. if (!(name in regularOverrides)) {
  12767. delete ctx[name];
  12768. }
  12769. }
  12770. this.setDirty(true);
  12771. },
  12772. /**
  12773. * @private
  12774. * Compensate for canvas' use of single precision floating point.
  12775. * @param {CanvasRenderingContext2D} ctx The canvas context.
  12776. */
  12777. enablePrecisionCompensation: function(ctx) {
  12778. var surface = this,
  12779. xx = 1,
  12780. yy = 1,
  12781. dx = 0,
  12782. dy = 0,
  12783. matrix = new Ext.draw.Matrix(),
  12784. transStack = [],
  12785. comp = {},
  12786. regularOverrides = Ext.draw.engine.Canvas.contextOverrides,
  12787. originalCtx = ctx.constructor.prototype,
  12788. /**
  12789. * @cfg {Object} precisionOverrides
  12790. * @ignore
  12791. */
  12792. precisionOverrides = {
  12793. toSave: surface.toSave,
  12794. /**
  12795. * Adds a new closed subpath to the path, representing the given rectangle.
  12796. * @return {*}
  12797. * @ignore
  12798. */
  12799. rect: function(x, y, w, h) {
  12800. return originalCtx.rect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
  12801. },
  12802. /**
  12803. * Paints the given rectangle onto the canvas, using the current fill style.
  12804. * @ignore
  12805. */
  12806. fillRect: function(x, y, w, h) {
  12807. this.updatePrecisionCompensateRect();
  12808. originalCtx.fillRect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
  12809. this.updatePrecisionCompensate();
  12810. },
  12811. /**
  12812. * Paints the box that outlines the given rectangle onto the canvas, using
  12813. * the current stroke style.
  12814. * @ignore
  12815. */
  12816. strokeRect: function(x, y, w, h) {
  12817. this.updatePrecisionCompensateRect();
  12818. originalCtx.strokeRect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
  12819. this.updatePrecisionCompensate();
  12820. },
  12821. /**
  12822. * Clears all pixels on the canvas in the given rectangle to transparent black.
  12823. * @ignore
  12824. */
  12825. clearRect: function(x, y, w, h) {
  12826. return originalCtx.clearRect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
  12827. },
  12828. /**
  12829. * Creates a new subpath with the given point.
  12830. * @ignore
  12831. */
  12832. moveTo: function(x, y) {
  12833. return originalCtx.moveTo.call(this, x * xx + dx, y * yy + dy);
  12834. },
  12835. /**
  12836. * Adds the given point to the current subpath, connected to the previous one
  12837. * by a straight line.
  12838. * @ignore
  12839. */
  12840. lineTo: function(x, y) {
  12841. return originalCtx.lineTo.call(this, x * xx + dx, y * yy + dy);
  12842. },
  12843. /**
  12844. * Adds points to the subpath such that the arc described by the circumference
  12845. * of the circle described by the arguments, starting at the given start angle
  12846. * and ending at the given end angle, going in the given direction (defaulting
  12847. * to clockwise), is added to the path, connected to the previous point
  12848. * by a straight line.
  12849. * @ignore
  12850. */
  12851. arc: function(x, y, radius, startAngle, endAngle, anticlockwise) {
  12852. this.updatePrecisionCompensateRect();
  12853. originalCtx.arc.call(this, x * xx + dx, y * xx + dy, radius * xx, startAngle, endAngle, anticlockwise);
  12854. this.updatePrecisionCompensate();
  12855. },
  12856. /**
  12857. * Adds an arc with the given control points and radius to the current subpath,
  12858. * connected to the previous point by a straight line. If two radii are provided,
  12859. * the first controls the width of the arc's ellipse, and the second controls
  12860. * the height. If only one is provided, or if they are the same, the arc is from
  12861. * a circle.
  12862. *
  12863. * In the case of an ellipse, the rotation argument controls the clockwise
  12864. * inclination of the ellipse relative to the x-axis.
  12865. * @ignore
  12866. */
  12867. arcTo: function(x1, y1, x2, y2, radius) {
  12868. this.updatePrecisionCompensateRect();
  12869. originalCtx.arcTo.call(this, x1 * xx + dx, y1 * yy + dy, x2 * xx + dx, y2 * yy + dy, radius * xx);
  12870. this.updatePrecisionCompensate();
  12871. },
  12872. /**
  12873. * Pushes the context state to the state stack.
  12874. * @ignore
  12875. */
  12876. save: function() {
  12877. transStack.push(matrix);
  12878. matrix = matrix.clone();
  12879. regularOverrides.save.call(this);
  12880. originalCtx.save.call(this);
  12881. },
  12882. /**
  12883. * Pops the state stack and restores the state.
  12884. * @ignore
  12885. */
  12886. restore: function() {
  12887. matrix = transStack.pop();
  12888. regularOverrides.restore.call(this);
  12889. originalCtx.restore.call(this);
  12890. this.updatePrecisionCompensate();
  12891. },
  12892. /**
  12893. * @ignore
  12894. */
  12895. updatePrecisionCompensate: function() {
  12896. matrix.precisionCompensate(surface.devicePixelRatio, comp);
  12897. xx = comp.xx;
  12898. yy = comp.yy;
  12899. dx = comp.dx;
  12900. dy = comp.dy;
  12901. originalCtx.setTransform.call(this, surface.devicePixelRatio, comp.b, comp.c, comp.d, 0, 0);
  12902. },
  12903. /**
  12904. * @ignore
  12905. */
  12906. updatePrecisionCompensateRect: function() {
  12907. matrix.precisionCompensateRect(surface.devicePixelRatio, comp);
  12908. xx = comp.xx;
  12909. yy = comp.yy;
  12910. dx = comp.dx;
  12911. dy = comp.dy;
  12912. originalCtx.setTransform.call(this, surface.devicePixelRatio, comp.b, comp.c, comp.d, 0, 0);
  12913. },
  12914. /**
  12915. * Changes the transformation matrix to the matrix given by the arguments
  12916. * as described below.
  12917. * @ignore
  12918. */
  12919. setTransform: function(x2x, x2y, y2x, y2y, newDx, newDy) {
  12920. matrix.set(x2x, x2y, y2x, y2y, newDx, newDy);
  12921. this.updatePrecisionCompensate();
  12922. },
  12923. /**
  12924. * Changes the transformation matrix to apply the matrix given by the arguments
  12925. * as described below.
  12926. * @ignore
  12927. */
  12928. transform: function(x2x, x2y, y2x, y2y, newDx, newDy) {
  12929. matrix.append(x2x, x2y, y2x, y2y, newDx, newDy);
  12930. this.updatePrecisionCompensate();
  12931. },
  12932. /**
  12933. * Scales the transformation matrix.
  12934. * @return {*}
  12935. * @ignore
  12936. */
  12937. scale: function(sx, sy) {
  12938. this.transform(sx, 0, 0, sy, 0, 0);
  12939. },
  12940. /**
  12941. * Translates the transformation matrix.
  12942. * @return {*}
  12943. * @ignore
  12944. */
  12945. translate: function(dx, dy) {
  12946. this.transform(1, 0, 0, 1, dx, dy);
  12947. },
  12948. /**
  12949. * Rotates the transformation matrix.
  12950. * @return {*}
  12951. * @ignore
  12952. */
  12953. rotate: function(radians) {
  12954. var cos = Math.cos(radians),
  12955. sin = Math.sin(radians);
  12956. this.transform(cos, sin, -sin, cos, 0, 0);
  12957. },
  12958. /**
  12959. * Adds the given point to the current subpath, connected to the previous one by a
  12960. * quadratic Bézier curve with the given control point.
  12961. * @return {*}
  12962. * @ignore
  12963. */
  12964. quadraticCurveTo: function(cx, cy, x, y) {
  12965. originalCtx.quadraticCurveTo.call(this, cx * xx + dx, cy * yy + dy, x * xx + dx, y * yy + dy);
  12966. },
  12967. /**
  12968. * Adds the given point to the current subpath, connected to the previous one
  12969. * by a cubic Bézier curve with the given control points.
  12970. * @return {*}
  12971. * @ignore
  12972. */
  12973. bezierCurveTo: function(c1x, c1y, c2x, c2y, x, y) {
  12974. originalCtx.bezierCurveTo.call(this, c1x * xx + dx, c1y * yy + dy, c2x * xx + dx, c2y * yy + dy, x * xx + dx, y * yy + dy);
  12975. },
  12976. /**
  12977. * Returns an object that represents a linear gradient that paints along the line
  12978. * given by the coordinates represented by the arguments.
  12979. * @return {*}
  12980. * @ignore
  12981. */
  12982. createLinearGradient: function(x0, y0, x1, y1) {
  12983. var grad;
  12984. this.updatePrecisionCompensateRect();
  12985. grad = originalCtx.createLinearGradient.call(this, x0 * xx + dx, y0 * yy + dy, x1 * xx + dx, y1 * yy + dy);
  12986. this.updatePrecisionCompensate();
  12987. return grad;
  12988. },
  12989. /**
  12990. * Returns a CanvasGradient object that represents a radial gradient that paints
  12991. * along the cone given by the circles represented by the arguments. If either
  12992. * of the radii are negative, throws an IndexSizeError exception.
  12993. * @return {*}
  12994. * @ignore
  12995. */
  12996. createRadialGradient: function(x0, y0, r0, x1, y1, r1) {
  12997. var grad;
  12998. this.updatePrecisionCompensateRect();
  12999. grad = originalCtx.createLinearGradient.call(this, x0 * xx + dx, y0 * xx + dy, r0 * xx, x1 * xx + dx, y1 * xx + dy, r1 * xx);
  13000. this.updatePrecisionCompensate();
  13001. return grad;
  13002. },
  13003. /**
  13004. * Fills the given text at the given position. If a maximum width is provided,
  13005. * the text will be scaled to fit that width if necessary.
  13006. * @ignore
  13007. */
  13008. fillText: function(text, x, y, maxWidth) {
  13009. originalCtx.setTransform.apply(this, matrix.elements);
  13010. if (typeof maxWidth === 'undefined') {
  13011. originalCtx.fillText.call(this, text, x, y);
  13012. } else {
  13013. originalCtx.fillText.call(this, text, x, y, maxWidth);
  13014. }
  13015. this.updatePrecisionCompensate();
  13016. },
  13017. /**
  13018. * Strokes the given text at the given position. If a
  13019. * maximum width is provided, the text will be scaled to
  13020. * fit that width if necessary.
  13021. * @ignore
  13022. */
  13023. strokeText: function(text, x, y, maxWidth) {
  13024. originalCtx.setTransform.apply(this, matrix.elements);
  13025. if (typeof maxWidth === 'undefined') {
  13026. originalCtx.strokeText.call(this, text, x, y);
  13027. } else {
  13028. originalCtx.strokeText.call(this, text, x, y, maxWidth);
  13029. }
  13030. this.updatePrecisionCompensate();
  13031. },
  13032. /**
  13033. * Fills the subpaths of the current default path or the given path with the current
  13034. * fill style.
  13035. * @ignore
  13036. */
  13037. fill: function() {
  13038. var fillGradient = this.fillGradient,
  13039. bbox = this.bbox;
  13040. this.updatePrecisionCompensateRect();
  13041. if (fillGradient && bbox) {
  13042. this.fillStyle = fillGradient.generateGradient(this, bbox);
  13043. }
  13044. originalCtx.fill.call(this);
  13045. this.updatePrecisionCompensate();
  13046. },
  13047. /**
  13048. * Strokes the subpaths of the current default path or the given path with the
  13049. * current stroke style.
  13050. * @ignore
  13051. */
  13052. stroke: function() {
  13053. var strokeGradient = this.strokeGradient,
  13054. bbox = this.bbox;
  13055. this.updatePrecisionCompensateRect();
  13056. if (strokeGradient && bbox) {
  13057. this.strokeStyle = strokeGradient.generateGradient(this, bbox);
  13058. }
  13059. originalCtx.stroke.call(this);
  13060. this.updatePrecisionCompensate();
  13061. },
  13062. /**
  13063. * Draws the given image onto the canvas. If the first argument isn't an img,
  13064. * canvas, or video element, throws a TypeMismatchError exception. If the image
  13065. * has no image data, throws an InvalidStateError exception. If the one of the
  13066. * source rectangle dimensions is zero, throws an IndexSizeError exception.
  13067. * If the image isn't yet fully decoded, then nothing is drawn.
  13068. * @return {*}
  13069. * @ignore
  13070. */
  13071. drawImage: function(img_elem, arg1, arg2, arg3, arg4, dst_x, dst_y, dw, dh) {
  13072. switch (arguments.length) {
  13073. case 3:
  13074. return originalCtx.drawImage.call(this, img_elem, arg1 * xx + dx, arg2 * yy + dy);
  13075. case 5:
  13076. return originalCtx.drawImage.call(this, img_elem, arg1 * xx + dx, arg2 * yy + dy, arg3 * xx, arg4 * yy);
  13077. case 9:
  13078. return originalCtx.drawImage.call(this, img_elem, arg1, arg2, arg3, arg4, dst_x * xx + dx, dst_y * yy * dy, dw * xx, dh * yy);
  13079. }
  13080. }
  13081. };
  13082. Ext.apply(ctx, precisionOverrides);
  13083. this.setDirty(true);
  13084. },
  13085. /**
  13086. * Normally, a surface will have a single canvas.
  13087. * However, on certain platforms/browsers there's a limit to how big a canvas can be.
  13088. * 'splitThreshold' is used to determine maximum width/height of a single canvas element.
  13089. * When a surface is wider/taller than the splitThreshold, extra canvas element(s)
  13090. * will be created and tiled inside the surface.
  13091. */
  13092. updateRect: function(rect) {
  13093. this.callParent([
  13094. rect
  13095. ]);
  13096. // eslint-disable-next-line vars-on-top
  13097. var me = this,
  13098. l = Math.floor(rect[0]),
  13099. t = Math.floor(rect[1]),
  13100. r = Math.ceil(rect[0] + rect[2]),
  13101. b = Math.ceil(rect[1] + rect[3]),
  13102. devicePixelRatio = me.devicePixelRatio,
  13103. canvases = me.canvases,
  13104. w = r - l,
  13105. h = b - t,
  13106. splitThreshold = Math.round(me.splitThreshold / devicePixelRatio),
  13107. xSplits = me.xSplits = Math.ceil(w / splitThreshold),
  13108. ySplits = me.ySplits = Math.ceil(h / splitThreshold),
  13109. i, j, k, offsetX, offsetY, dom, width, height;
  13110. for (j = 0 , offsetY = 0; j < ySplits; j++ , offsetY += splitThreshold) {
  13111. for (i = 0 , offsetX = 0; i < xSplits; i++ , offsetX += splitThreshold) {
  13112. k = j * xSplits + i;
  13113. if (k >= canvases.length) {
  13114. me.createCanvas();
  13115. }
  13116. dom = canvases[k].dom;
  13117. dom.style.left = offsetX + 'px';
  13118. dom.style.top = offsetY + 'px';
  13119. // The Canvas doesn't automatically support hi-DPI displays.
  13120. // We have to actually create a larger canvas (more pixels)
  13121. // while keeping its physical size the same.
  13122. height = Math.min(splitThreshold, h - offsetY);
  13123. if (height * devicePixelRatio !== dom.height) {
  13124. dom.height = height * devicePixelRatio;
  13125. dom.style.height = height + 'px';
  13126. }
  13127. width = Math.min(splitThreshold, w - offsetX);
  13128. if (width * devicePixelRatio !== dom.width) {
  13129. dom.width = width * devicePixelRatio;
  13130. dom.style.width = width + 'px';
  13131. }
  13132. me.applyDefaults(me.contexts[k]);
  13133. }
  13134. }
  13135. me.activeCanvases = k = xSplits * ySplits;
  13136. while (canvases.length > k) {
  13137. canvases.pop().destroy();
  13138. }
  13139. me.clear();
  13140. },
  13141. /**
  13142. * @method clearTransform
  13143. * @inheritdoc
  13144. */
  13145. clearTransform: function() {
  13146. var me = this,
  13147. xSplits = me.xSplits,
  13148. ySplits = me.ySplits,
  13149. contexts = me.contexts,
  13150. splitThreshold = me.splitThreshold,
  13151. devicePixelRatio = me.devicePixelRatio,
  13152. i, j, k, ctx;
  13153. for (i = 0; i < xSplits; i++) {
  13154. for (j = 0; j < ySplits; j++) {
  13155. k = j * xSplits + i;
  13156. ctx = contexts[k];
  13157. ctx.translate(-splitThreshold * i, -splitThreshold * j);
  13158. ctx.scale(devicePixelRatio, devicePixelRatio);
  13159. me.matrix.toContext(ctx);
  13160. }
  13161. }
  13162. },
  13163. /**
  13164. * @method renderSprite
  13165. * @inheritdoc
  13166. */
  13167. renderSprite: function(sprite) {
  13168. var me = this,
  13169. rect = me.getRect(),
  13170. surfaceMatrix = me.matrix,
  13171. parent = sprite.getParent(),
  13172. matrix = Ext.draw.Matrix.fly([
  13173. 1,
  13174. 0,
  13175. 0,
  13176. 1,
  13177. 0,
  13178. 0
  13179. ]),
  13180. splitThreshold = me.splitThreshold / me.devicePixelRatio,
  13181. xSplits = me.xSplits,
  13182. ySplits = me.ySplits,
  13183. offsetX, offsetY, ctx, bbox, width, height,
  13184. left = 0,
  13185. right,
  13186. top = 0,
  13187. bottom,
  13188. w = rect[2],
  13189. h = rect[3],
  13190. i, j, k;
  13191. while (parent && parent.isSprite) {
  13192. matrix.prependMatrix(parent.matrix || parent.attr && parent.attr.matrix);
  13193. parent = parent.getParent();
  13194. }
  13195. matrix.prependMatrix(surfaceMatrix);
  13196. bbox = sprite.getBBox();
  13197. if (bbox) {
  13198. bbox = matrix.transformBBox(bbox);
  13199. }
  13200. sprite.preRender(me);
  13201. if (sprite.attr.hidden || sprite.attr.globalAlpha === 0) {
  13202. sprite.setDirty(false);
  13203. return;
  13204. }
  13205. // Render this sprite on all Canvas elements it spans, skipping the rest.
  13206. for (j = 0 , offsetY = 0; j < ySplits; j++ , offsetY += splitThreshold) {
  13207. for (i = 0 , offsetX = 0; i < xSplits; i++ , offsetX += splitThreshold) {
  13208. k = j * xSplits + i;
  13209. ctx = me.contexts[k];
  13210. width = Math.min(splitThreshold, w - offsetX);
  13211. height = Math.min(splitThreshold, h - offsetY);
  13212. left = offsetX;
  13213. right = left + width;
  13214. top = offsetY;
  13215. bottom = top + height;
  13216. if (bbox) {
  13217. if (bbox.x > right || bbox.x + bbox.width < left || bbox.y > bottom || bbox.y + bbox.height < top) {
  13218. continue;
  13219. }
  13220. }
  13221. ctx.save();
  13222. sprite.useAttributes(ctx, rect);
  13223. if (false === sprite.render(me, ctx, [
  13224. left,
  13225. top,
  13226. width,
  13227. height
  13228. ])) {
  13229. return false;
  13230. }
  13231. ctx.restore();
  13232. }
  13233. }
  13234. sprite.setDirty(false);
  13235. },
  13236. flatten: function(size, surfaces) {
  13237. var targetCanvas = document.createElement('canvas'),
  13238. className = Ext.getClassName(this),
  13239. ratio = this.devicePixelRatio,
  13240. ctx = targetCanvas.getContext('2d'),
  13241. surface, canvas, rect, i, j, xy;
  13242. targetCanvas.width = Math.ceil(size.width * ratio);
  13243. targetCanvas.height = Math.ceil(size.height * ratio);
  13244. for (i = 0; i < surfaces.length; i++) {
  13245. surface = surfaces[i];
  13246. if (Ext.getClassName(surface) !== className) {
  13247. continue;
  13248. }
  13249. rect = surface.getRect();
  13250. for (j = 0; j < surface.canvases.length; j++) {
  13251. canvas = surface.canvases[j];
  13252. xy = canvas.getOffsetsTo(canvas.getParent());
  13253. ctx.drawImage(canvas.dom, (rect[0] + xy[0]) * ratio, (rect[1] + xy[1]) * ratio);
  13254. }
  13255. }
  13256. return {
  13257. data: targetCanvas.toDataURL(),
  13258. type: 'png'
  13259. };
  13260. },
  13261. applyDefaults: function(ctx) {
  13262. var none = Ext.util.Color.RGBA_NONE;
  13263. ctx.strokeStyle = none;
  13264. ctx.fillStyle = none;
  13265. ctx.textAlign = 'start';
  13266. ctx.textBaseline = 'alphabetic';
  13267. ctx.miterLimit = 1;
  13268. },
  13269. /**
  13270. * @method clear
  13271. * @inheritdoc
  13272. */
  13273. clear: function() {
  13274. var me = this,
  13275. activeCanvases = me.activeCanvases,
  13276. i, canvas, ctx;
  13277. for (i = 0; i < activeCanvases; i++) {
  13278. canvas = me.canvases[i].dom;
  13279. ctx = me.contexts[i];
  13280. ctx.setTransform(1, 0, 0, 1, 0, 0);
  13281. ctx.clearRect(0, 0, canvas.width, canvas.height);
  13282. }
  13283. me.setDirty(true);
  13284. },
  13285. /**
  13286. * Destroys the Canvas element and prepares it for Garbage Collection.
  13287. */
  13288. destroy: function() {
  13289. var me = this,
  13290. canvases = me.canvases,
  13291. ln = canvases.length,
  13292. i;
  13293. for (i = 0; i < ln; i++) {
  13294. me.contexts[i] = null;
  13295. canvases[i].destroy();
  13296. canvases[i] = null;
  13297. }
  13298. me.contexts = me.canvases = null;
  13299. me.callParent();
  13300. },
  13301. privates: {
  13302. initElement: function() {
  13303. var me = this;
  13304. me.callParent();
  13305. me.canvases = [];
  13306. me.contexts = [];
  13307. me.activeCanvases = me.xSplits = me.ySplits = 0;
  13308. }
  13309. }
  13310. }, function() {
  13311. var me = this,
  13312. proto = me.prototype,
  13313. splitThreshold = 1.0E10;
  13314. if (Ext.os.is.Android4 && Ext.browser.is.Chrome) {
  13315. splitThreshold = 3000;
  13316. } else if (Ext.is.iOS) {
  13317. splitThreshold = 2200;
  13318. }
  13319. proto.splitThreshold = splitThreshold;
  13320. });
  13321. /**
  13322. * The container that holds and manages instances of the {@link Ext.draw.Surface}
  13323. * in which {@link Ext.draw.sprite.Sprite sprites} are rendered. Draw containers are
  13324. * used as the foundation for all of the chart classes but may also be created directly
  13325. * in order to create custom drawings.
  13326. *
  13327. * @example
  13328. * var drawContainer = Ext.create('Ext.draw.Container', {
  13329. * renderTo: Ext.getBody(),
  13330. * width:200,
  13331. * height:200,
  13332. * sprites: [{
  13333. * type: 'circle',
  13334. * fillStyle: '#79BB3F',
  13335. * r: 100,
  13336. * x: 100,
  13337. * y: 100
  13338. * }]
  13339. * });
  13340. *
  13341. * // Uncomment to trigger a download of the painted circle.
  13342. * // drawContainer.download({
  13343. * // filename: 'Circle',
  13344. * // url: 'http://svg.sencha.io' // Default server the image data is sent to.
  13345. * // });
  13346. *
  13347. * In the previous example we created a draw container and configured it with a single
  13348. * sprite. The *type* of the sprite is {@link Ext.draw.sprite.Circle circle}, so if you
  13349. * run this code you'll see a green circle.
  13350. *
  13351. * You can attach sprite event listeners to the draw container with the help of the
  13352. * {@link Ext.draw.plugin.SpriteEvents} plugin.
  13353. *
  13354. * For more information on sprites, the core elements added to a draw container's
  13355. * surface, refer to the Ext.draw.sprite.Sprite documentation.
  13356. *
  13357. * For more information on surfaces, the interface owned by the draw container used to
  13358. * manage all sprites, see the Ext.draw.Surface documentation.
  13359. */
  13360. Ext.define('Ext.draw.Container', {
  13361. extend: 'Ext.draw.ContainerBase',
  13362. alternateClassName: 'Ext.draw.Component',
  13363. xtype: 'draw',
  13364. defaultType: 'surface',
  13365. isDrawContainer: true,
  13366. requires: [
  13367. 'Ext.draw.Surface',
  13368. 'Ext.draw.engine.Svg',
  13369. 'Ext.draw.engine.Canvas',
  13370. 'Ext.draw.gradient.GradientDefinition'
  13371. ],
  13372. /**
  13373. * @cfg {String} [engine="Ext.draw.engine.Canvas"]
  13374. * Defines the engine (type of surface) used to render draw container contents.
  13375. *
  13376. * The render engine is selected automatically depending on the platform used. Priority
  13377. * is given to the {@link Ext.draw.engine.Canvas} engine due to its performance advantage.
  13378. *
  13379. * You may also set the engine config to be `Ext.draw.engine.Svg` if so desired.
  13380. */
  13381. engine: 'Ext.draw.engine.Canvas',
  13382. /**
  13383. * @event spritemousemove
  13384. * Fires when the mouse is moved on a sprite.
  13385. * @param {Object} sprite
  13386. * @param {Event} event
  13387. */
  13388. /**
  13389. * @event spritemouseup
  13390. * Fires when a mouseup event occurs on a sprite.
  13391. * @param {Object} sprite
  13392. * @param {Event} event
  13393. */
  13394. /**
  13395. * @event spritemousedown
  13396. * Fires when a mousedown event occurs on a sprite.
  13397. * @param {Object} sprite
  13398. * @param {Event} event
  13399. */
  13400. /**
  13401. * @event spritemouseover
  13402. * Fires when the mouse enters a sprite.
  13403. * @param {Object} sprite
  13404. * @param {Event} event
  13405. */
  13406. /**
  13407. * @event spritemouseout
  13408. * Fires when the mouse exits a sprite.
  13409. * @param {Object} sprite
  13410. * @param {Event} event
  13411. */
  13412. /**
  13413. * @event spriteclick
  13414. * Fires when a click event occurs on a sprite.
  13415. * @param {Object} sprite
  13416. * @param {Event} event
  13417. */
  13418. /**
  13419. * @event spritedblclick
  13420. * Fires when a double click event occurs on a sprite.
  13421. * @param {Object} sprite
  13422. * @param {Event} event
  13423. */
  13424. /**
  13425. * @event spritetap
  13426. * Fires when a tap event occurs on a sprite.
  13427. * @param {Object} sprite
  13428. * @param {Event} event
  13429. */
  13430. /**
  13431. * @event bodyresize
  13432. * Fires when the size of the draw container body changes.
  13433. * @param {Object} size The object containing 'width' and 'height' of the draw container's body.
  13434. */
  13435. config: {
  13436. cls: [
  13437. Ext.baseCSSPrefix + 'draw-container',
  13438. Ext.baseCSSPrefix + 'unselectable'
  13439. ],
  13440. /**
  13441. * @cfg {Function} [resizeHandler]
  13442. * The resize function that can be configured to have a behavior,
  13443. * e.g. resize draw surfaces based on new draw container dimensions.
  13444. * The `resizeHandler` function takes a single parameter -
  13445. * the size object with `width` and `height` properties.
  13446. *
  13447. * **Note:** Since resize events trigger {@link #renderFrame} calls automatically,
  13448. * return `false` from the resize function, if it also calls `renderFrame`,
  13449. * to prevent double rendering.
  13450. */
  13451. resizeHandler: null,
  13452. /**
  13453. * @cfg {Object[]} sprites
  13454. * Defines a set of sprites to be added to the drawContainer surface.
  13455. *
  13456. * For example:
  13457. *
  13458. * sprites: [{
  13459. * type: 'circle',
  13460. * fillStyle: '#79BB3F',
  13461. * r: 100,
  13462. * x: 100,
  13463. * y: 100
  13464. * }]
  13465. *
  13466. */
  13467. sprites: null,
  13468. /**
  13469. * @cfg {Object[]} gradients
  13470. * Defines a set of gradients that can be used as color properties
  13471. * (fillStyle and strokeStyle, but not shadowColor) in sprites.
  13472. * The gradients array is an array of objects with the following properties:
  13473. * - **id** - string - The unique name of the gradient.
  13474. * - **type** - string, optional - The type of the gradient. Available types are: 'linear',
  13475. * 'radial'. Defaults to 'linear'.
  13476. * - **angle** - number, optional - The angle of the gradient in degrees.
  13477. * - **stops** - array - An array of objects with 'color' and 'offset' properties, where
  13478. * 'offset' is a real number from 0 to 1.
  13479. *
  13480. * For example:
  13481. *
  13482. * gradients: [{
  13483. * id: 'gradientId1',
  13484. * type: 'linear',
  13485. * angle: 45,
  13486. * stops: [{
  13487. * offset: 0,
  13488. * color: 'red'
  13489. * }, {
  13490. * offset: 1,
  13491. * color: 'yellow'
  13492. * }]
  13493. * }, {
  13494. * id: 'gradientId2',
  13495. * type: 'radial',
  13496. * stops: [{
  13497. * offset: 0,
  13498. * color: '#555',
  13499. * }, {
  13500. * offset: 1,
  13501. * color: '#ddd',
  13502. * }]
  13503. * }]
  13504. *
  13505. * Then the sprites can use 'gradientId1' and 'gradientId2' by setting the color attributes
  13506. * to those ids, for example:
  13507. *
  13508. * sprite.setAttributes({
  13509. * fillStyle: 'url(#gradientId1)',
  13510. * strokeStyle: 'url(#gradientId2)'
  13511. * });
  13512. */
  13513. gradients: [],
  13514. /**
  13515. * @cfg {String} downloadServerUrl
  13516. * The default URL used by the {@link #download} method.
  13517. */
  13518. downloadServerUrl: undefined,
  13519. touchAction: {
  13520. panX: false,
  13521. panY: false,
  13522. pinchZoom: false,
  13523. doubleTapZoom: false
  13524. },
  13525. /**
  13526. * @private
  13527. * @cfg {Object} surfaceZIndexes A map of surface type name to zIndex.
  13528. * The z-indexes to use for the various types of surfaces.
  13529. */
  13530. surfaceZIndexes: {
  13531. main: 1
  13532. }
  13533. },
  13534. /**
  13535. * @private
  13536. * @property {String} [defaultDownloadServerUrl="http://svg.sencha.io"]
  13537. * The default URL used by the {@link #download} method if the {@link #downloadServerUrl}
  13538. * config wasn't set.
  13539. * To override this globally, set the `Ext.draw.Container.prototype.defaultDownloadServerUrl`.
  13540. */
  13541. defaultDownloadServerUrl: 'http://svg.sencha.io',
  13542. /**
  13543. * @property {Array} [supportedFormats=["png", "pdf", "jpeg", "gif"]]
  13544. * A list of export types supported by the server.
  13545. * @private
  13546. */
  13547. supportedFormats: [
  13548. 'png',
  13549. 'pdf',
  13550. 'jpeg',
  13551. 'gif'
  13552. ],
  13553. supportedOptions: {
  13554. version: Ext.isNumber,
  13555. data: Ext.isString,
  13556. format: function(format) {
  13557. return Ext.Array.indexOf(this.supportedFormats, format) >= 0;
  13558. },
  13559. filename: Ext.isString,
  13560. width: Ext.isNumber,
  13561. height: Ext.isNumber,
  13562. scale: Ext.isNumber,
  13563. pdf: Ext.isObject,
  13564. jpeg: Ext.isObject
  13565. },
  13566. initAnimator: function() {
  13567. this.frameCallbackId = Ext.draw.Animator.addFrameCallback('renderFrame', this);
  13568. },
  13569. applyDownloadServerUrl: function(url) {
  13570. var defaultUrl = this.defaultDownloadServerUrl;
  13571. if (!url) {
  13572. url = defaultUrl;
  13573. //<debug>
  13574. // Skip this warning when unit testing.
  13575. if (!window.jasmine) {
  13576. 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() + ')');
  13577. }
  13578. }
  13579. //</debug>
  13580. return url;
  13581. },
  13582. applyGradients: function(gradients) {
  13583. var result = [],
  13584. i, n, gradient, offset;
  13585. if (!Ext.isArray(gradients)) {
  13586. return result;
  13587. }
  13588. for (i = 0 , n = gradients.length; i < n; i++) {
  13589. gradient = gradients[i];
  13590. if (!Ext.isObject(gradient)) {
  13591. continue;
  13592. }
  13593. // ExtJS only supported linear gradients, so we didn't have to specify their type
  13594. if (typeof gradient.type !== 'string') {
  13595. gradient.type = 'linear';
  13596. }
  13597. if (gradient.angle) {
  13598. gradient.degrees = gradient.angle;
  13599. delete gradient.angle;
  13600. }
  13601. // Convert ExtJS stops object to Touch stops array
  13602. if (Ext.isObject(gradient.stops)) {
  13603. gradient.stops = (function(stops) {
  13604. var result = [],
  13605. stop;
  13606. for (offset in stops) {
  13607. stop = stops[offset];
  13608. stop.offset = offset / 100;
  13609. result.push(stop);
  13610. }
  13611. return result;
  13612. })(gradient.stops);
  13613. }
  13614. result.push(gradient);
  13615. }
  13616. Ext.draw.gradient.GradientDefinition.add(result);
  13617. return result;
  13618. },
  13619. applySprites: function(sprites) {
  13620. var result, surface, sprite, i, ln;
  13621. // Never update.
  13622. if (!sprites) {
  13623. return;
  13624. }
  13625. sprites = Ext.Array.from(sprites);
  13626. result = [];
  13627. for (i = 0 , ln = sprites.length; i < ln; i++) {
  13628. sprite = sprites[i];
  13629. surface = sprite.surface;
  13630. if (!(surface && surface.isSurface)) {
  13631. if (Ext.isString(surface)) {
  13632. surface = this.getSurface(surface);
  13633. delete sprite.surface;
  13634. } else {
  13635. surface = this.getSurface('main');
  13636. }
  13637. }
  13638. sprite = surface.add(sprite);
  13639. result.push(sprite);
  13640. }
  13641. return result;
  13642. },
  13643. resizeDelay: 500,
  13644. // in milliseconds
  13645. resizeTimerId: 0,
  13646. lastResizeTime: null,
  13647. /**
  13648. * @private
  13649. * @property
  13650. * Last valid size.
  13651. */
  13652. size: null,
  13653. /**
  13654. * Triggers the {@link #resizeHandler} with the size of the draw container
  13655. * element as the parameter.
  13656. */
  13657. handleResize: function(size, instantly) {
  13658. // See the following:
  13659. // Classic: Ext.draw.ContainerBase.reattachToBody
  13660. // Modern: Ext.draw.ContainerBase.initialize
  13661. var me = this,
  13662. el = me.element,
  13663. resizeHandler = me.getResizeHandler() || me.defaultResizeHandler,
  13664. resizeDelay = me.resizeDelay,
  13665. lastResizeTime = me.lastResizeTime,
  13666. defer, result;
  13667. if (!el) {
  13668. return;
  13669. }
  13670. size = size || el.getSize();
  13671. if (!(size.width && size.height)) {
  13672. return;
  13673. }
  13674. me.size = size;
  13675. me.stopResizeTimer();
  13676. // Only want to defer when multiple resize events happen in quick succession.
  13677. // That way it doesn't feel luggy during an occasional resize, nor it's too straining
  13678. // when continuously resizing.
  13679. defer = !instantly && lastResizeTime && (Ext.Date.now() - lastResizeTime < resizeDelay);
  13680. if (defer) {
  13681. me.resizeTimerId = Ext.defer(me.handleResize, resizeDelay, me, [
  13682. size,
  13683. true
  13684. ]);
  13685. return;
  13686. }
  13687. me.fireEvent('bodyresize', me, size);
  13688. Ext.callback(resizeHandler, null, [
  13689. size
  13690. ], 0, me);
  13691. if (result !== false) {
  13692. me.renderFrame();
  13693. }
  13694. me.lastResizeTime = Ext.Date.now();
  13695. },
  13696. /**
  13697. * @private
  13698. */
  13699. stopResizeTimer: function() {
  13700. if (this.resizeTimerId) {
  13701. Ext.undefer(this.resizeTimerId);
  13702. this.resizeTimerId = 0;
  13703. }
  13704. },
  13705. defaultResizeHandler: function(size) {
  13706. this.getItems().each(function(surface) {
  13707. surface.setRect([
  13708. 0,
  13709. 0,
  13710. size.width,
  13711. size.height
  13712. ]);
  13713. });
  13714. },
  13715. /**
  13716. * Get a surface by the given id or create one if it doesn't exist.
  13717. * This will automatically call the {@link #resizeHandler}. Which
  13718. * means that, if no custom resize handler has been provided, the
  13719. * surface will be sized to match the container.
  13720. * If the {@link #method!add} method is used, it is the responsibility
  13721. * of the user to call the {@link #handleResize} method, to update
  13722. * the size of all added surfaces.
  13723. * @param {String} [id="main"]
  13724. * @param {String} type
  13725. * @return {Ext.draw.Surface}
  13726. */
  13727. getSurface: function(id, type) {
  13728. var me = this,
  13729. surfaces = me.getItems(),
  13730. oldCount = surfaces.getCount(),
  13731. zIndexes = me.getSurfaceZIndexes(),
  13732. surface;
  13733. id = id || 'main';
  13734. type = type || id;
  13735. surface = me.createSurface(id);
  13736. if (type in zIndexes) {
  13737. surface.element.setStyle('zIndex', zIndexes[type]);
  13738. }
  13739. if (surfaces.getCount() > oldCount) {
  13740. // Immediately call resize handler of the draw container,
  13741. // so that the newly created surface gets a size.
  13742. me.handleResize(null, true);
  13743. }
  13744. return surface;
  13745. },
  13746. createSurface: function(id) {
  13747. var me = this,
  13748. surfaces = me.getItems(),
  13749. surface;
  13750. id = this.getId() + '-' + (id || 'main');
  13751. surface = surfaces.get(id);
  13752. if (!surface) {
  13753. surface = me.add({
  13754. xclass: me.engine,
  13755. id: id
  13756. });
  13757. }
  13758. return surface;
  13759. },
  13760. /**
  13761. * Render all the surfaces in the container.
  13762. */
  13763. renderFrame: function() {
  13764. var me = this,
  13765. surfaces = me.getItems(),
  13766. i, ln, item;
  13767. for (i = 0 , ln = surfaces.length; i < ln; i++) {
  13768. item = surfaces.items[i];
  13769. if (item.isSurface) {
  13770. item.renderFrame();
  13771. }
  13772. }
  13773. },
  13774. /**
  13775. * @private
  13776. * Returns a slice of the surfaces (items) array of the draw container,
  13777. * optionally sorting them by zIndex.
  13778. * Overridden in subclasses.
  13779. */
  13780. getSurfaces: function(sort) {
  13781. var surfaces = Array.prototype.slice.call(this.items.items),
  13782. zIndexes = this.getSurfaceZIndexes(),
  13783. i, j, surface, zIndex;
  13784. if (sort) {
  13785. // Sort the surfaces by zIndex using insertion sort.
  13786. for (j = 1; j < surfaces.length; j++) {
  13787. surface = surfaces[j];
  13788. zIndex = zIndexes[surface.type];
  13789. i = j - 1;
  13790. while (i >= 0 && zIndexes[surfaces[i].type] > zIndex) {
  13791. surfaces[i + 1] = surfaces[i];
  13792. i--;
  13793. }
  13794. surfaces[i + 1] = surface;
  13795. }
  13796. }
  13797. return surfaces;
  13798. },
  13799. /**
  13800. * Produces an image of the chart / drawing.
  13801. * @param {String} [format] Possible options are 'image' (the method will return an
  13802. * Image object) and 'stream' (the method will return the image as a byte stream).
  13803. * If missing, the data URI of the drawing's (or chart's) image will be returned.
  13804. * Note: for an SVG based drawing/chart in IE/Edge browsers the method will always
  13805. * return SVG markup instead of a data URI, as 'img' elements won't accept a data
  13806. * URI anyway in those browsers.
  13807. * @return {Object}
  13808. * @return {String} return.data Image element, byte stream or DataURL.
  13809. * @return {String} return.type The type of the data (e.g. 'png' or 'svg').
  13810. */
  13811. getImage: function(format) {
  13812. var size = this.bodyElement.getSize(),
  13813. surfaces = this.getSurfaces(true),
  13814. surface = surfaces[0],
  13815. image, imageElement;
  13816. if ((Ext.isIE || Ext.isEdge) && surface.isSVG) {
  13817. // SVG data URLs don't work in IE/Edge as a source for an 'img' element,
  13818. // so we need to render SVG the usual way.
  13819. image = {
  13820. data: surface.toSVG(size, surfaces),
  13821. type: 'svg-markup'
  13822. };
  13823. } else {
  13824. image = surface.flatten(size, surfaces);
  13825. if (format === 'image') {
  13826. imageElement = new Image();
  13827. imageElement.src = image.data;
  13828. image.data = imageElement;
  13829. return image;
  13830. }
  13831. if (format === 'stream') {
  13832. image.data = image.data.replace(/^data:image\/[^;]+/, 'data:application/octet-stream');
  13833. return image;
  13834. }
  13835. }
  13836. return image;
  13837. },
  13838. /**
  13839. * Downloads an image or PDF of the chart / drawing or opens it in a separate
  13840. * browser tab/window if the download can't be triggered. The exact behavior is
  13841. * platform and browser specific. For more consistent results on mobile devices use
  13842. * the {@link #preview} method instead. This method doesn't work in IE8.
  13843. *
  13844. * Important: The default download mechanism sends image data to `http://svg.sencha.io`,
  13845. * which is a server operated by Sencha. This can be changed by setting
  13846. * the {@link #downloadServerUrl} config to the address of another server.
  13847. *
  13848. * You can deploy your own server by using the code from the `server` directory
  13849. * in the Charts package. The server is Node.js based and uses PhantomJS to
  13850. * generate images and PDFs from received data.
  13851. *
  13852. * The warning that the default download server is used can be suppressed
  13853. * by explicitly setting the value of the {@link #downloadServerUrl} config
  13854. * to `http://svg.sencha.io`.
  13855. *
  13856. * @param {Object} [config] The following config options are supported:
  13857. *
  13858. * @param {String} config.url The url to post the data to. Defaults to
  13859. * the value of the {@link #downloadServerUrl} config.
  13860. *
  13861. * @param {String} config.format The format of image to export. See the
  13862. * {@link #supportedFormats}. Defaults to 'png' on the Sencha IO server.
  13863. * Note that you can't export to 'svg' format if the {@link Ext.draw.engine.Canvas Canvas}
  13864. * {@link Ext.draw.Container#engine engine} is used.
  13865. *
  13866. * @param {Number} config.width A width to send to the server for
  13867. * configuring the image width. Defaults to natural image width on
  13868. * the Sencha IO server.
  13869. *
  13870. * @param {Number} config.height A height to send to the server for
  13871. * configuring the image height. Defaults to natural image height on
  13872. * the Sencha IO server.
  13873. *
  13874. * @param {String} config.filename The filename of the downloaded image.
  13875. * Defaults to 'chart' on the Sencha IO server. The config.format is used
  13876. * as a filename extension.
  13877. *
  13878. * @param {Number} config.scale The scaling of the downloaded image.
  13879. * Defaults to 1 on the Sencha IO server. The server will try to determine the natural
  13880. * size of the image unless the width/height configs have been set. If the
  13881. * {@link Ext.draw.engine.Canvas Canvas} {@link Ext.draw.Container#engine engine} is
  13882. * used the natural image size will depend on the value of the window.devicePixelRatio.
  13883. * For example, for devices with devicePixelRatio of 2 the produced image will be
  13884. * two times larger than for devices with devicePixelRatio of 1 for the same drawing.
  13885. * This is done so that the users with devices with HiDPI screens get a downloaded
  13886. * image that looks as crisp on their device as the original drawing.
  13887. * If you want image size to be consistent across devices with different device
  13888. * pixel ratios, you can set the value of this config to 1/devicePixelRatio.
  13889. * This parameter is ignored by the Sencha IO server if config.format is set to 'svg'.
  13890. *
  13891. * @param {Object} config.pdf PDF specific options.
  13892. * This config is only used if config.format is set to 'pdf'.
  13893. * The given object should be in either this format:
  13894. *
  13895. * {
  13896. * width: '200px',
  13897. * height: '300px',
  13898. * border: '0px'
  13899. * }
  13900. *
  13901. * or this format:
  13902. *
  13903. * {
  13904. * format: 'A4',
  13905. * orientation: 'portrait',
  13906. * border: '1cm'
  13907. * }
  13908. *
  13909. * Supported dimension units are: 'mm', 'cm', 'in', 'px'. No unit means 'px'.
  13910. * Supported formats are: 'A3', 'A4', 'A5', 'Legal', 'Letter', 'Tabloid'.
  13911. * Orientation ('portrait', 'landscape') is optional and defaults to 'portrait'.
  13912. *
  13913. * @param {Object} config.jpeg JPEG specific options.
  13914. * This config is only used if config.format is set to 'jpeg'.
  13915. * The given object should be in this format:
  13916. *
  13917. * {
  13918. * quality: 80
  13919. * }
  13920. *
  13921. * Where quality is an integer between 0 and 100.
  13922. *
  13923. * @return {Boolean} True if request was successfully sent to the server.
  13924. */
  13925. download: function(config) {
  13926. var me = this,
  13927. inputs = [],
  13928. markup, name, value;
  13929. if (Ext.isIE8) {
  13930. return false;
  13931. }
  13932. config = config || {};
  13933. config.version = 2;
  13934. if (!config.data) {
  13935. config.data = me.getImage().data;
  13936. }
  13937. for (name in config) {
  13938. if (config.hasOwnProperty(name)) {
  13939. value = config[name];
  13940. if (name in me.supportedOptions) {
  13941. if (me.supportedOptions[name].call(me, value)) {
  13942. inputs.push({
  13943. tag: 'input',
  13944. type: 'hidden',
  13945. name: name,
  13946. value: Ext.String.htmlEncode(Ext.isObject(value) ? Ext.JSON.encode(value) : value)
  13947. });
  13948. } else //<debug>
  13949. {
  13950. Ext.log.error('Invalid value for image download option "' + name + '": ' + value);
  13951. }
  13952. } else //</debug>
  13953. //<debug>
  13954. {
  13955. Ext.log.error('Invalid image download option: "' + name + '"');
  13956. }
  13957. }
  13958. }
  13959. //</debug>
  13960. markup = Ext.dom.Helper.markup({
  13961. tag: 'html',
  13962. children: [
  13963. {
  13964. tag: 'head'
  13965. },
  13966. {
  13967. tag: 'body',
  13968. children: [
  13969. {
  13970. tag: 'form',
  13971. method: 'POST',
  13972. action: config.url || me.getDownloadServerUrl(),
  13973. children: inputs
  13974. },
  13975. {
  13976. tag: 'script',
  13977. type: 'text/javascript',
  13978. children: 'document.getElementsByTagName("form")[0].submit();'
  13979. }
  13980. ]
  13981. }
  13982. ]
  13983. });
  13984. window.open('', 'ImageDownload_' + Date.now()).document.write(markup);
  13985. },
  13986. /**
  13987. * @method preview
  13988. * Displays an image of a Ext.draw.Container on screen.
  13989. * On mobile devices this lets users tap-and-hold to bring up the menu
  13990. * with image saving options.
  13991. * Notes:
  13992. * - some browsers won't save the preview image if it's SVG based
  13993. * (i.e. generated from a draw container that uses 'Ext.draw.engine.Svg' engine);
  13994. * - some platforms may not have the means of viewing successfully saved SVG images;
  13995. * - this method does not work on IE8.
  13996. */
  13997. doDestroy: function() {
  13998. var me = this,
  13999. callbackId = me.frameCallbackId;
  14000. if (callbackId) {
  14001. Ext.draw.Animator.removeFrameCallback(callbackId);
  14002. }
  14003. me.stopResizeTimer();
  14004. me.callParent();
  14005. }
  14006. }, function() {
  14007. if (location.search.match('svg')) {
  14008. Ext.draw.Container.prototype.engine = 'Ext.draw.engine.Svg';
  14009. } 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))) {
  14010. // http://code.google.com/p/android/issues/detail?id=37529
  14011. Ext.draw.Container.prototype.engine = 'Ext.draw.engine.Svg';
  14012. }
  14013. });
  14014. /**
  14015. *
  14016. */
  14017. Ext.define('Ext.chart.theme.BaseTheme', {
  14018. defaultsDivCls: 'x-component'
  14019. });
  14020. /**
  14021. * Abstract class that provides default styles for non-specified things.
  14022. * Should be sub-classed when creating new themes.
  14023. * For example:
  14024. *
  14025. * Ext.define('Ext.chart.theme.Custom', {
  14026. * extend: 'Ext.chart.theme.Base',
  14027. * singleton: true,
  14028. * alias: 'chart.theme.custom',
  14029. * config: {
  14030. * baseColor: '#ff9f00'
  14031. * }
  14032. * });
  14033. *
  14034. * Theme provided values will not override the values provided in an instance config.
  14035. * However, if a theme provided value is an object, it will be merged with the value
  14036. * from the instance config, unless the theme provided object has a '$default' key
  14037. * set to 'true'.
  14038. *
  14039. * Certain chart theme configs (e.g. 'fontSize') may use the 'default' value to indicate
  14040. * that they should inherit a value from the corresponding CSS style provided by
  14041. * a framework theme. Additionally, one can use basic binary operators like multiplication,
  14042. * addition and subtraction to derive from the default value, e.g. fontSize: 'default*1.3'.
  14043. *
  14044. * Important: the theme should not use the 'font' shorthand to specify the font of labels
  14045. * and other text elements of a chart. Instead, individual font properties should be used:
  14046. * 'fontStyle', 'fontVariant', 'fontWeight', 'fontSize' and 'fontFamily'.
  14047. */
  14048. Ext.define('Ext.chart.theme.Base', {
  14049. extend: 'Ext.chart.theme.BaseTheme',
  14050. mixins: {
  14051. factoryable: 'Ext.mixin.Factoryable'
  14052. },
  14053. requires: [
  14054. 'Ext.draw.Color'
  14055. ],
  14056. factoryConfig: {
  14057. type: 'chart.theme'
  14058. },
  14059. isTheme: true,
  14060. isBase: true,
  14061. config: {
  14062. /**
  14063. * @cfg {String/Ext.util.Color} baseColor
  14064. * The base color used to generate the {@link Ext.chart.AbstractChart#colors} of the theme.
  14065. */
  14066. baseColor: null,
  14067. /**
  14068. * @cfg {Array} colors
  14069. *
  14070. * Array of colors/gradients to be used by the theme.
  14071. * Defaults to {@link #colorDefaults}.
  14072. */
  14073. colors: undefined,
  14074. /**
  14075. * @cfg {Object} gradients
  14076. *
  14077. * The gradient config to be used by series' sprites. E.g.:
  14078. *
  14079. * {
  14080. * type: 'linear',
  14081. * degrees: 90
  14082. * }
  14083. *
  14084. * Please refer to the documentation for the {@link Ext.draw.gradient.Linear linear}
  14085. * and {@link Ext.draw.gradient.Radial radial} gradients for all possible options.
  14086. * The color {@link Ext.draw.gradient.Gradient#stops stops} for the gradients
  14087. * will be generated by the theme based on the {@link #colors} config.
  14088. */
  14089. gradients: null,
  14090. /**
  14091. * @cfg {Object} chart
  14092. * Theme defaults for the chart.
  14093. * Can apply to all charts or just a specific type of chart.
  14094. * For example:
  14095. *
  14096. * chart: {
  14097. * defaults: {
  14098. * background: 'lightgray'
  14099. * },
  14100. * polar: {
  14101. * background: 'green'
  14102. * }
  14103. * }
  14104. *
  14105. * The values from the chart.defaults and chart.*type* configs (where *type* is a valid
  14106. * chart xtype, e.g. '{@link Ext.chart.CartesianChart cartesian}' or
  14107. * '{@link Ext.chart.PolarChart polar}') will be applied to corresponding chart configs.
  14108. * E.g., the chart.defaults.background config will set the
  14109. * {@link Ext.chart.AbstractChart#background} config of all charts, where the
  14110. * chart.cartesian.flipXY config will only set the
  14111. * {@link Ext.chart.CartesianChart#flipXY} config of all cartesian charts.
  14112. */
  14113. chart: {
  14114. defaults: {
  14115. captions: {
  14116. title: {
  14117. docked: 'top',
  14118. padding: 5,
  14119. style: {
  14120. textAlign: 'center',
  14121. fontFamily: 'default',
  14122. fontWeight: '500',
  14123. fillStyle: 'black',
  14124. fontSize: 'default*1.6'
  14125. }
  14126. },
  14127. subtitle: {
  14128. docked: 'top',
  14129. style: {
  14130. textAlign: 'center',
  14131. fontFamily: 'default',
  14132. fontWeight: 'normal',
  14133. fillStyle: 'black',
  14134. fontSize: 'default*1.3'
  14135. }
  14136. },
  14137. credits: {
  14138. docked: 'bottom',
  14139. padding: 5,
  14140. style: {
  14141. textAlign: 'left',
  14142. fontFamily: 'default',
  14143. fontWeight: 'lighter',
  14144. fillStyle: 'black',
  14145. fontSize: 'default'
  14146. }
  14147. }
  14148. },
  14149. background: 'white'
  14150. }
  14151. },
  14152. /**
  14153. * @cfg {Object} axis
  14154. * Theme defaults for the axes.
  14155. * Can apply to all axes or only axes with a specific position.
  14156. * For example:
  14157. *
  14158. * axis: {
  14159. * defaults: {
  14160. * style: {strokeStyle: 'red'}
  14161. * },
  14162. * left: {
  14163. * title: {fillStyle: 'green'}
  14164. * }
  14165. * }
  14166. *
  14167. * The values from the axis.defaults and axis.*position* configs (where *position*
  14168. * is a valid axis {@link Ext.chart.axis.Axis#position}, e.g. 'bottom') will be
  14169. * applied to corresponding {@link Ext.chart.axis.Axis axis} configs.
  14170. * E.g., the axis.defaults.label config will apply to the {@link Ext.chart.axis.Axis#label}
  14171. * config of all axes, where the axis.left.titleMargin config will only apply to the
  14172. * {@link Ext.chart.axis.Axis#titleMargin} config of all axes positioned to the left.
  14173. */
  14174. axis: {
  14175. defaults: {
  14176. label: {
  14177. x: 0,
  14178. y: 0,
  14179. textBaseline: 'middle',
  14180. textAlign: 'center',
  14181. fontSize: 'default',
  14182. fontFamily: 'default',
  14183. fontWeight: 'default',
  14184. fillStyle: 'black'
  14185. },
  14186. title: {
  14187. fillStyle: 'black',
  14188. fontSize: 'default*1.23',
  14189. fontFamily: 'default',
  14190. fontWeight: 'default'
  14191. },
  14192. style: {
  14193. strokeStyle: 'black'
  14194. },
  14195. grid: {
  14196. strokeStyle: 'rgb(221, 221, 221)'
  14197. }
  14198. },
  14199. top: {
  14200. style: {
  14201. textPadding: 5
  14202. }
  14203. },
  14204. bottom: {
  14205. style: {
  14206. textPadding: 5
  14207. }
  14208. }
  14209. },
  14210. /**
  14211. * @cfg {Object} series
  14212. * Theme defaults for the series.
  14213. * Can apply to all series or just a specific type of series.
  14214. * For example:
  14215. *
  14216. * series: {
  14217. * defaults: {
  14218. * style: {
  14219. * lineWidth: 2
  14220. * }
  14221. * },
  14222. * bar: {
  14223. * animation: {
  14224. * easing: 'bounceOut',
  14225. * duration: 1000
  14226. * }
  14227. * }
  14228. * }
  14229. *
  14230. * The values from the series.defaults and series.*type* configs (where *type*
  14231. * is a valid series {@link Ext.chart.series.Series#type}, e.g. 'line') will be
  14232. * applied to corresponding series configs.
  14233. * E.g., the series.defaults.label config will apply to the
  14234. * {@link Ext.chart.series.Series#label} config of all series, where the series.line.step
  14235. * config will only apply to the
  14236. * {@link Ext.chart.series.Line#step} config of {@link Ext.chart.series.Line line} series.
  14237. */
  14238. series: {
  14239. defaults: {
  14240. label: {
  14241. fillStyle: 'black',
  14242. strokeStyle: 'none',
  14243. fontFamily: 'default',
  14244. fontWeight: 'default',
  14245. fontSize: 'default*1.077',
  14246. textBaseline: 'middle',
  14247. textAlign: 'center'
  14248. },
  14249. labelOverflowPadding: 5
  14250. }
  14251. },
  14252. /**
  14253. * @cfg {Object} sprites
  14254. * Default style for the custom chart sprites by type.
  14255. * For example:
  14256. *
  14257. * sprites: {
  14258. * text: {
  14259. * fontWeight: 300
  14260. * }
  14261. * }
  14262. *
  14263. * These sprite attribute overrides will apply to custom sprites of all charts
  14264. * specified using the {@link Ext.draw.Container#sprites} config.
  14265. * The overrides are specified by sprite type, e.g. sprites.text config
  14266. * tells to apply given attributes to all {@link Ext.draw.sprite.Text text} sprites.
  14267. */
  14268. sprites: {
  14269. text: {
  14270. fontSize: 'default',
  14271. fontWeight: 'default',
  14272. fontFamily: 'default',
  14273. fillStyle: 'black'
  14274. }
  14275. },
  14276. /**
  14277. * Style information for the {Ext.chart.legend.SpriteLegend sprite legend}.
  14278. * If the {@link Ext.chart.legend.Legend DOM} legend is used, this config is ignored.
  14279. * For additional details see {@link Ext.chart.AbstractChart#legend}.
  14280. * @cfg {Object} legend
  14281. * @cfg {Ext.chart.legend.sprite.Item} legend.item
  14282. * @cfg {Object} legend.border See {@link Ext.chart.legend.SpriteLegend#border}.
  14283. */
  14284. legend: {
  14285. label: {
  14286. fontSize: 14,
  14287. fontWeight: 'default',
  14288. fontFamily: 'default',
  14289. fillStyle: 'black'
  14290. },
  14291. border: {
  14292. lineWidth: 1,
  14293. radius: 4,
  14294. fillStyle: 'none',
  14295. strokeStyle: 'gray'
  14296. },
  14297. background: 'white'
  14298. },
  14299. /**
  14300. * @private
  14301. * An object with the following structure:
  14302. * {
  14303. * fillStyle: [color, color, ...],
  14304. * strokeStyle: [color, color, ...],
  14305. * ...
  14306. * }
  14307. * If missing, generated from the other configs: 'baseColor, 'gradients', 'colors'.
  14308. */
  14309. seriesThemes: undefined,
  14310. markerThemes: {
  14311. type: [
  14312. 'circle',
  14313. 'cross',
  14314. 'plus',
  14315. 'square',
  14316. 'triangle',
  14317. 'diamond'
  14318. ]
  14319. },
  14320. /**
  14321. * @deprecated 5.0.1 Use the {@link Ext.draw.Container#gradients} config instead.
  14322. */
  14323. useGradients: false,
  14324. /**
  14325. * @deprecated 5.0.1 Use the {@link Ext.chart.AbstractChart#background} config instead.
  14326. */
  14327. background: null
  14328. },
  14329. colorDefaults: [
  14330. '#94ae0a',
  14331. '#115fa6',
  14332. '#a61120',
  14333. '#ff8809',
  14334. '#ffd13e',
  14335. '#a61187',
  14336. '#24ad9a',
  14337. '#7c7474',
  14338. '#a66111'
  14339. ],
  14340. constructor: function(config) {
  14341. this.initConfig(config);
  14342. this.resolveDefaults();
  14343. },
  14344. defaultRegEx: /^default([+\-/*]\d+(?:\.\d+)?)?$/,
  14345. defaultOperators: {
  14346. '*': function(v1, v2) {
  14347. return v1 * v2;
  14348. },
  14349. '+': function(v1, v2) {
  14350. return v1 + v2;
  14351. },
  14352. '-': function(v1, v2) {
  14353. return v1 - v2;
  14354. }
  14355. },
  14356. resolveChartDefaults: function() {
  14357. var chart = Ext.clone(this.getChart()),
  14358. chartType, captionName, chartConfig, captionConfig;
  14359. for (chartType in chart) {
  14360. chartConfig = chart[chartType];
  14361. if ('captions' in chartConfig) {
  14362. for (captionName in chartConfig.captions) {
  14363. captionConfig = chartConfig.captions[captionName];
  14364. if (captionConfig) {
  14365. this.replaceDefaults(captionConfig.style);
  14366. }
  14367. }
  14368. }
  14369. }
  14370. this.setChart(chart);
  14371. },
  14372. resolveDefaults: function() {
  14373. var me = this;
  14374. Ext.onInternalReady(function() {
  14375. var sprites = Ext.clone(me.getSprites()),
  14376. legend = Ext.clone(me.getLegend()),
  14377. axis = Ext.clone(me.getAxis()),
  14378. series = Ext.clone(me.getSeries()),
  14379. div, key, config;
  14380. if (!me.superclass.defaults) {
  14381. div = Ext.getBody().createChild({
  14382. tag: 'div',
  14383. cls: me.defaultsDivCls
  14384. });
  14385. me.superclass.defaults = {
  14386. fontFamily: div.getStyle('fontFamily'),
  14387. fontWeight: div.getStyle('fontWeight'),
  14388. fontSize: parseFloat(div.getStyle('fontSize')),
  14389. fontVariant: div.getStyle('fontVariant'),
  14390. fontStyle: div.getStyle('fontStyle')
  14391. };
  14392. div.destroy();
  14393. }
  14394. me.resolveChartDefaults();
  14395. me.replaceDefaults(sprites.text);
  14396. me.setSprites(sprites);
  14397. me.replaceDefaults(legend.label);
  14398. me.setLegend(legend);
  14399. for (key in axis) {
  14400. config = axis[key];
  14401. me.replaceDefaults(config.label);
  14402. me.replaceDefaults(config.title);
  14403. }
  14404. me.setAxis(axis);
  14405. for (key in series) {
  14406. config = series[key];
  14407. me.replaceDefaults(config.label);
  14408. }
  14409. me.setSeries(series);
  14410. });
  14411. },
  14412. replaceDefaults: function(target) {
  14413. var me = this,
  14414. defaults = me.superclass.defaults,
  14415. defaultRegEx = me.defaultRegEx,
  14416. key, value, match, binaryFn;
  14417. if (Ext.isObject(target)) {
  14418. for (key in defaults) {
  14419. match = defaultRegEx.exec(target[key]);
  14420. if (match) {
  14421. value = defaults[key];
  14422. match = match[1];
  14423. if (match) {
  14424. binaryFn = me.defaultOperators[match.charAt(0)];
  14425. value = Math.round(binaryFn(value, parseFloat(match.substr(1))));
  14426. }
  14427. target[key] = value;
  14428. }
  14429. }
  14430. }
  14431. },
  14432. applyBaseColor: function(baseColor) {
  14433. var midColor, midL;
  14434. if (baseColor) {
  14435. midColor = baseColor.isColor ? baseColor : Ext.util.Color.fromString(baseColor);
  14436. midL = midColor.getHSL()[2];
  14437. if (midL < 0.15) {
  14438. midColor = midColor.createLighter(0.3);
  14439. } else if (midL < 0.3) {
  14440. midColor = midColor.createLighter(0.15);
  14441. } else if (midL > 0.85) {
  14442. midColor = midColor.createDarker(0.3);
  14443. } else if (midL > 0.7) {
  14444. midColor = midColor.createDarker(0.15);
  14445. }
  14446. this.setColors([
  14447. midColor.createDarker(0.3).toString(),
  14448. midColor.createDarker(0.15).toString(),
  14449. midColor.toString(),
  14450. midColor.createLighter(0.12).toString(),
  14451. midColor.createLighter(0.24).toString(),
  14452. midColor.createLighter(0.31).toString()
  14453. ]);
  14454. }
  14455. return baseColor;
  14456. },
  14457. applyColors: function(newColors) {
  14458. return newColors || this.colorDefaults;
  14459. },
  14460. updateUseGradients: function(useGradients) {
  14461. if (useGradients) {
  14462. this.updateGradients({
  14463. type: 'linear',
  14464. degrees: 90
  14465. });
  14466. }
  14467. },
  14468. updateBackground: function(background) {
  14469. var chart;
  14470. if (background) {
  14471. chart = this.getChart();
  14472. chart.defaults.background = background;
  14473. this.setChart(chart);
  14474. }
  14475. },
  14476. updateGradients: function(gradients) {
  14477. var colors = this.getColors(),
  14478. items = [],
  14479. gradient, midColor, color, i, ln;
  14480. if (Ext.isObject(gradients)) {
  14481. for (i = 0 , ln = colors && colors.length || 0; i < ln; i++) {
  14482. midColor = Ext.util.Color.fromString(colors[i]);
  14483. if (midColor) {
  14484. color = midColor.createLighter(0.15).toString();
  14485. gradient = Ext.apply(Ext.Object.chain(gradients), {
  14486. stops: [
  14487. {
  14488. offset: 1,
  14489. color: midColor.toString()
  14490. },
  14491. {
  14492. offset: 0,
  14493. color: color.toString()
  14494. }
  14495. ]
  14496. });
  14497. items.push(gradient);
  14498. }
  14499. }
  14500. this.setColors(items);
  14501. }
  14502. },
  14503. applySeriesThemes: function(newSeriesThemes) {
  14504. var colors, color;
  14505. // Init the 'colors' config with solid colors generated from the 'baseColor'.
  14506. this.getBaseColor();
  14507. // Init the 'gradients' config with a hardcoded value, if the legacy 'useGradients'
  14508. // config was set to 'true'. This in turn updates the 'colors' config.
  14509. this.getUseGradients();
  14510. // Init the 'gradients' config normally. This also updates the 'colors' config.
  14511. this.getGradients();
  14512. colors = this.getColors();
  14513. // Final colors.
  14514. if (!newSeriesThemes) {
  14515. newSeriesThemes = {
  14516. fillStyle: Ext.Array.clone(colors),
  14517. strokeStyle: Ext.Array.map(colors, function(value) {
  14518. color = Ext.util.Color.fromString(value.stops ? value.stops[0].color : value);
  14519. return color.createDarker(0.15).toString();
  14520. })
  14521. };
  14522. }
  14523. return newSeriesThemes;
  14524. }
  14525. });
  14526. /**
  14527. * @private
  14528. */
  14529. Ext.define('Ext.chart.theme.Default', {
  14530. extend: 'Ext.chart.theme.Base',
  14531. singleton: true,
  14532. alias: [
  14533. 'chart.theme.default',
  14534. 'chart.theme.Default',
  14535. 'chart.theme.Base'
  14536. ]
  14537. });
  14538. /**
  14539. * @private
  14540. */
  14541. Ext.define('Ext.chart.Util', {
  14542. singleton: true,
  14543. /**
  14544. * @private
  14545. * Given a `range` array of two (min/max) numbers and an arbitrary array of numbers
  14546. * (`data`), updates the given `range`, if the range of `data` exceeds it.
  14547. * Typically, one would start with the `[NaN, NaN]` range array instance, call the
  14548. * method on multiple datasets with that range instance, then validate it with
  14549. * {@link #validateRange}.
  14550. * @param {Number[]} range
  14551. * @param {Number[]} data
  14552. */
  14553. expandRange: function(range, data) {
  14554. var length = data.length,
  14555. min = range[0],
  14556. max = range[1],
  14557. i, value;
  14558. for (i = 0; i < length; i++) {
  14559. value = data[i];
  14560. // `null` is a "finite" number in JavaScript
  14561. // and greater than any negative number.
  14562. if (value == null || !isFinite(value)) {
  14563. continue;
  14564. }
  14565. if (value < min || !isFinite(min)) {
  14566. min = value;
  14567. }
  14568. if (value > max || !isFinite(max)) {
  14569. max = value;
  14570. }
  14571. }
  14572. range[0] = min;
  14573. range[1] = max;
  14574. },
  14575. defaultRange: [
  14576. 0,
  14577. 1
  14578. ],
  14579. /**
  14580. * @private
  14581. * Makes sure the range exists, is not zero, and its min/max values are finite numbers.
  14582. * If this is not the case, the values from the provided `defaultRange`
  14583. * are used.
  14584. *
  14585. * The range to validate. Never modified.
  14586. * @param {Number[]} range
  14587. * The default range to use, if the given range is not a valid data structure,
  14588. * if both values are infinities, or if both values are the same and dangerously
  14589. * close to either infinity (which makes expansion of the range by the value of
  14590. * `padding` impossible).
  14591. * If only a single value is infinity, the other value will be derived
  14592. * from the finite value by incrementing/decrementing it by the span
  14593. * of the default range towards the infinity.
  14594. * For example, if the `defaultRange` is `[0, 1]`, we have:
  14595. *
  14596. * [5, Infinity] --> [5, 6]
  14597. * [3, -Infinity] --> [2, 3]
  14598. * [-Infinity, -5] --> [-6, -5]
  14599. * [-3, -Infinity] --> [-4, -3]
  14600. *
  14601. * @param {Number[]} [defaultRange=[0, 1]]
  14602. * A non-negative padding to use in case of identical min/max.
  14603. * Note that the range span is not guaranteed to be `padding * 2` in this case,
  14604. * if min/max are close to MIN_SAFE_INTEGER/MAX_SAFE_INTEGER.
  14605. * @param {Number} [padding=0.5]
  14606. * @return {Number[]}
  14607. */
  14608. validateRange: function(range, defaultRange, padding) {
  14609. var isFin0, isFin1;
  14610. defaultRange = defaultRange || this.defaultRange.slice();
  14611. if (!(padding === 0 || padding > 0)) {
  14612. padding = 0.5;
  14613. }
  14614. if (!range || range.length !== 2) {
  14615. return defaultRange;
  14616. }
  14617. range = [
  14618. range[0],
  14619. range[1]
  14620. ];
  14621. if (!range[0]) {
  14622. range[0] = 0;
  14623. }
  14624. if (!range[1]) {
  14625. range[1] = 0;
  14626. }
  14627. if (padding && range[0] === range[1]) {
  14628. range = [
  14629. range[0] - padding,
  14630. range[0] + padding
  14631. ];
  14632. // In case the range values are at Infinity, the expansion above by the value
  14633. // of 'padding' won't do us much good, so we still have to fall back to the
  14634. // 'defaultRange'.
  14635. if (range[0] === range[1]) {
  14636. return defaultRange;
  14637. }
  14638. }
  14639. // Same sign infinities are ruled out at this point.
  14640. isFin0 = isFinite(range[0]);
  14641. isFin1 = isFinite(range[1]);
  14642. if (!isFin0 && !isFin1) {
  14643. return defaultRange;
  14644. }
  14645. // Different sign infinities are ruled out at this point.
  14646. if (isFin0 && !isFin1) {
  14647. range[1] = range[0] + Ext.Number.sign(range[1]) * (defaultRange[1] - defaultRange[0]);
  14648. } else if (isFin1 && !isFin0) {
  14649. range[0] = range[1] + Ext.Number.sign(range[0]) * (defaultRange[1] - defaultRange[0]);
  14650. }
  14651. // All infinities are ruled out at this point.
  14652. return [
  14653. Math.min(range[0], range[1]),
  14654. Math.max(range[0], range[1])
  14655. ];
  14656. },
  14657. applyAnimation: function(animation, oldAnimation) {
  14658. if (!animation) {
  14659. animation = {
  14660. duration: 0
  14661. };
  14662. } else if (animation === true) {
  14663. animation = {
  14664. easing: 'easeInOut',
  14665. duration: 500
  14666. };
  14667. }
  14668. return oldAnimation ? Ext.apply({}, animation, oldAnimation) : animation;
  14669. }
  14670. });
  14671. /**
  14672. * @class Ext.chart.Markers
  14673. * @extends Ext.draw.sprite.Instancing
  14674. *
  14675. * Marker sprite. A specialized version of instancing sprite that groups instances.
  14676. * Putting a marker is grouped by its category id. Clearing removes that category.
  14677. */
  14678. Ext.define('Ext.chart.Markers', {
  14679. extend: 'Ext.draw.sprite.Instancing',
  14680. isMarkers: true,
  14681. defaultCategory: 'default',
  14682. constructor: function() {
  14683. this.callParent(arguments);
  14684. // `categories` maps category names to a map that maps instance index in category to its
  14685. // global index: categoryName: {instanceIndexInCategory: globalInstanceIndex}
  14686. this.categories = {};
  14687. // The `revisions` map keeps revision numbers of instance categories.
  14688. // When a marker (instance) is put (created or updated), it gets the revision
  14689. // of the category. When a category is cleared, its revision is incremented,
  14690. // but its instances are not removed.
  14691. // An instance is only rendered if its revision matches category revision.
  14692. // In other words, a marker has to be put again after its category has been cleared
  14693. // or it won't render.
  14694. this.revisions = {};
  14695. },
  14696. destroy: function() {
  14697. this.categories = null;
  14698. this.revisions = null;
  14699. this.callParent();
  14700. },
  14701. getMarkerFor: function(category, index) {
  14702. var categoryInstances;
  14703. if (category in this.categories) {
  14704. categoryInstances = this.categories[category];
  14705. if (index in categoryInstances) {
  14706. return this.get(categoryInstances[index]);
  14707. }
  14708. }
  14709. },
  14710. /**
  14711. * Clears the markers in the category.
  14712. * @param {String} category
  14713. */
  14714. clear: function(category) {
  14715. category = category || this.defaultCategory;
  14716. if (!(category in this.revisions)) {
  14717. this.revisions[category] = 1;
  14718. } else {
  14719. this.revisions[category]++;
  14720. }
  14721. },
  14722. clearAll: function() {
  14723. this.callParent();
  14724. this.categories = {};
  14725. this.revisions = {};
  14726. },
  14727. /**
  14728. * Puts a marker in the category with additional attributes.
  14729. * @param {String} category
  14730. * @param {Object} attr
  14731. * @param {String|Number} index
  14732. * @param {Boolean} [bypassNormalization]
  14733. * @param {Boolean} [keepRevision]
  14734. */
  14735. putMarkerFor: function(category, attr, index, bypassNormalization, keepRevision) {
  14736. var me = this,
  14737. categoryInstances, instance;
  14738. category = category || this.defaultCategory;
  14739. categoryInstances = me.categories[category] || (me.categories[category] = {});
  14740. if (index in categoryInstances) {
  14741. me.setAttributesFor(categoryInstances[index], attr, bypassNormalization);
  14742. } else {
  14743. // get the index of the instance created on next line
  14744. categoryInstances[index] = me.getCount();
  14745. me.add(attr, bypassNormalization);
  14746. }
  14747. instance = me.get(categoryInstances[index]);
  14748. if (instance) {
  14749. instance.category = category;
  14750. if (!keepRevision) {
  14751. instance.revision = me.revisions[category] || (me.revisions[category] = 1);
  14752. }
  14753. }
  14754. },
  14755. /**
  14756. *
  14757. * @param {String} category
  14758. * @param {Mixed} index
  14759. * @param {Boolean} [isWithoutTransform]
  14760. */
  14761. getMarkerBBoxFor: function(category, index, isWithoutTransform) {
  14762. var categoryInstances;
  14763. if (category in this.categories) {
  14764. categoryInstances = this.categories[category];
  14765. if (index in categoryInstances) {
  14766. return this.getBBoxFor(categoryInstances[index], isWithoutTransform);
  14767. }
  14768. }
  14769. },
  14770. getBBox: function() {
  14771. return null;
  14772. },
  14773. render: function(surface, ctx, rect) {
  14774. var me = this,
  14775. surfaceRect = surface.getRect(),
  14776. revisions = me.revisions,
  14777. mat = me.attr.matrix,
  14778. template = me.getTemplate(),
  14779. templateAttr = template.attr,
  14780. ln = me.instances.length,
  14781. instance, i;
  14782. mat.toContext(ctx);
  14783. template.preRender(surface, ctx, rect);
  14784. template.useAttributes(ctx, surfaceRect);
  14785. for (i = 0; i < ln; i++) {
  14786. instance = me.get(i);
  14787. if (instance.hidden || instance.revision !== revisions[instance.category]) {
  14788. continue;
  14789. }
  14790. ctx.save();
  14791. template.attr = instance;
  14792. template.useAttributes(ctx, surfaceRect);
  14793. template.render(surface, ctx, rect);
  14794. ctx.restore();
  14795. }
  14796. template.attr = templateAttr;
  14797. }
  14798. });
  14799. /**
  14800. * This is a modifier to place labels and callouts by additional attributes.
  14801. */
  14802. Ext.define('Ext.chart.modifier.Callout', {
  14803. extend: 'Ext.draw.modifier.Modifier',
  14804. alternateClassName: 'Ext.chart.label.Callout',
  14805. prepareAttributes: function(attr) {
  14806. if (!attr.hasOwnProperty('calloutOriginal')) {
  14807. attr.calloutOriginal = Ext.Object.chain(attr);
  14808. // No __proto__, nor getPrototypeOf in IE8,
  14809. // so manually saving a reference to 'attr' after chaining.
  14810. attr.calloutOriginal.prototype = attr;
  14811. }
  14812. if (this._lower) {
  14813. this._lower.prepareAttributes(attr.calloutOriginal);
  14814. }
  14815. },
  14816. setAttrs: function(attr, changes) {
  14817. var callout = attr.callout,
  14818. origin = attr.calloutOriginal,
  14819. bbox = attr.bbox.plain,
  14820. width = (bbox.width || 0) + attr.labelOverflowPadding,
  14821. height = (bbox.height || 0) + attr.labelOverflowPadding,
  14822. dx, dy, rotationRads, x, y, calloutPlaceX, calloutPlaceY, calloutVertical, temp;
  14823. if ('callout' in changes) {
  14824. callout = changes.callout;
  14825. }
  14826. if ('callout' in changes || 'calloutPlaceX' in changes || 'calloutPlaceY' in changes || 'x' in changes || 'y' in changes) {
  14827. rotationRads = 'rotationRads' in changes ? origin.rotationRads = changes.rotationRads : origin.rotationRads;
  14828. x = 'x' in changes ? (origin.x = changes.x) : origin.x;
  14829. y = 'y' in changes ? (origin.y = changes.y) : origin.y;
  14830. calloutPlaceX = 'calloutPlaceX' in changes ? changes.calloutPlaceX : attr.calloutPlaceX;
  14831. calloutPlaceY = 'calloutPlaceY' in changes ? changes.calloutPlaceY : attr.calloutPlaceY;
  14832. calloutVertical = 'calloutVertical' in changes ? changes.calloutVertical : attr.calloutVertical;
  14833. // Normalize Rotations
  14834. rotationRads %= Math.PI * 2;
  14835. if (Math.cos(rotationRads) < 0) {
  14836. rotationRads = (rotationRads + Math.PI) % (Math.PI * 2);
  14837. }
  14838. if (rotationRads > Math.PI) {
  14839. rotationRads -= Math.PI * 2;
  14840. }
  14841. if (calloutVertical) {
  14842. rotationRads = rotationRads * (1 - callout) - Math.PI / 2 * callout;
  14843. temp = width;
  14844. width = height;
  14845. height = temp;
  14846. } else {
  14847. rotationRads = rotationRads * (1 - callout);
  14848. }
  14849. changes.rotationRads = rotationRads;
  14850. // Placing a label in the middle of a pie slice (x/y)
  14851. // if callout doesn't exists (callout=0),
  14852. // or outside the pie slice (calloutPlaceX/Y) if it does (callout=1).
  14853. changes.x = x * (1 - callout) + calloutPlaceX * callout;
  14854. changes.y = y * (1 - callout) + calloutPlaceY * callout;
  14855. dx = calloutPlaceX - x;
  14856. dy = calloutPlaceY - y;
  14857. // Finding where the callout line intersects the bbox of the label
  14858. // if it were to go to the center of the label,
  14859. // and make that intersection point the end of the callout line.
  14860. // Effectively, the end of the callout line traces label's bbox when chart is rotated.
  14861. if (Math.abs(dy * width) > Math.abs(dx * height)) {
  14862. // on top/bottom
  14863. if (dy > 0) {
  14864. changes.calloutEndX = changes.x - (height / 2) * (dx / dy) * callout;
  14865. changes.calloutEndY = changes.y - (height / 2) * callout;
  14866. } else {
  14867. changes.calloutEndX = changes.x + (height / 2) * (dx / dy) * callout;
  14868. changes.calloutEndY = changes.y + (height / 2) * callout;
  14869. }
  14870. } else {
  14871. // on left/right
  14872. if (dx > 0) {
  14873. changes.calloutEndX = changes.x - width / 2;
  14874. changes.calloutEndY = changes.y - (width / 2) * (dy / dx) * callout;
  14875. } else {
  14876. changes.calloutEndX = changes.x + width / 2;
  14877. changes.calloutEndY = changes.y + (width / 2) * (dy / dx) * callout;
  14878. }
  14879. }
  14880. // Since the length of the callout line is adjusted depending on the label's position
  14881. // and dimensions, we hide the callout line if the length becomes negative.
  14882. if (changes.calloutStartX && changes.calloutStartY) {
  14883. 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);
  14884. } else {
  14885. changes.calloutHasLine = true;
  14886. }
  14887. }
  14888. return changes;
  14889. },
  14890. pushDown: function(attr, changes) {
  14891. changes = this.callParent([
  14892. attr.calloutOriginal,
  14893. changes
  14894. ]);
  14895. return this.setAttrs(attr, changes);
  14896. },
  14897. popUp: function(attr, changes) {
  14898. attr = attr.prototype;
  14899. changes = this.setAttrs(attr, changes);
  14900. if (this._upper) {
  14901. return this._upper.popUp(attr, changes);
  14902. } else {
  14903. return Ext.apply(attr, changes);
  14904. }
  14905. }
  14906. });
  14907. /**
  14908. * @class Ext.chart.sprite.Label
  14909. * @extends Ext.draw.sprite.Text
  14910. *
  14911. * Sprite used to represent labels in series.
  14912. *
  14913. * Important: the actual default values are determined by the theme used.
  14914. * Please see the `label` config of the {@link Ext.chart.theme.Base#axis}.
  14915. */
  14916. Ext.define('Ext.chart.sprite.Label', {
  14917. extend: 'Ext.draw.sprite.Text',
  14918. alternateClassName: 'Ext.chart.label.Label',
  14919. requires: [
  14920. 'Ext.chart.modifier.Callout'
  14921. ],
  14922. inheritableStatics: {
  14923. def: {
  14924. processors: {
  14925. callout: 'limited01',
  14926. // Meant to be set by the Callout modifier only.
  14927. calloutHasLine: 'bool',
  14928. // The position where the callout would end, if not for the label:
  14929. // callout stops at the bounding box of the label,
  14930. // so the actual point where the callout ends - calloutEndX/Y -
  14931. // is calculated by the Callout modifier.
  14932. calloutPlaceX: 'number',
  14933. calloutPlaceY: 'number',
  14934. // The start/end points used to render the callout line.
  14935. calloutStartX: 'number',
  14936. calloutStartY: 'number',
  14937. calloutEndX: 'number',
  14938. calloutEndY: 'number',
  14939. calloutColor: 'color',
  14940. calloutWidth: 'number',
  14941. calloutVertical: 'bool',
  14942. labelOverflowPadding: 'number',
  14943. display: 'enums(none,under,over,rotate,insideStart,insideEnd,inside,outside)',
  14944. orientation: 'enums(horizontal,vertical)',
  14945. renderer: 'default'
  14946. },
  14947. defaults: {
  14948. callout: 0,
  14949. calloutHasLine: true,
  14950. calloutPlaceX: 0,
  14951. calloutPlaceY: 0,
  14952. calloutStartX: 0,
  14953. calloutStartY: 0,
  14954. calloutEndX: 0,
  14955. calloutEndY: 0,
  14956. calloutWidth: 1,
  14957. calloutVertical: false,
  14958. calloutColor: 'black',
  14959. labelOverflowPadding: 5,
  14960. display: 'none',
  14961. orientation: '',
  14962. renderer: null
  14963. },
  14964. triggers: {
  14965. callout: 'transform',
  14966. calloutPlaceX: 'transform',
  14967. calloutPlaceY: 'transform',
  14968. labelOverflowPadding: 'transform',
  14969. calloutRotation: 'transform',
  14970. display: 'hidden'
  14971. },
  14972. updaters: {
  14973. hidden: function(attr) {
  14974. attr.hidden = (attr.display === 'none');
  14975. }
  14976. }
  14977. }
  14978. },
  14979. config: {
  14980. /**
  14981. * @cfg {Object} fx Animation configuration.
  14982. */
  14983. animation: {
  14984. customDurations: {
  14985. callout: 200
  14986. }
  14987. },
  14988. /**
  14989. * @cfg {String} field The store record field used by the label sprite.
  14990. *
  14991. * Note: the label sprite is typically used indirectly (by a Ext.chart.MarkerHolder
  14992. * series sprite, via a Ext.chart.Markers sprite, where the latter is passed to the
  14993. * label renderer), so to get to the label field one has to do:
  14994. *
  14995. * renderer: function (text, sprite, config, data, index) {
  14996. * var field = sprite.getTemplate().getField();
  14997. * }
  14998. *
  14999. * To get the actual label sprite instance one can use:
  15000. *
  15001. * sprite.get(index)
  15002. *
  15003. */
  15004. field: null,
  15005. /**
  15006. * @cfg {Boolean|Object} calloutLine
  15007. *
  15008. * True to draw a line between the label and the chart with the default settings,
  15009. * or an Object that defines the 'color', 'width' and 'length' properties of the line.
  15010. * This config is only applicable when the label is displayed outside the chart.
  15011. *
  15012. * Default value: false.
  15013. */
  15014. calloutLine: true,
  15015. /**
  15016. * @cfg {Number} [hideLessThan=20]
  15017. * Hides labels for pie slices with segment length less than this value (in pixels).
  15018. */
  15019. hideLessThan: 20
  15020. },
  15021. applyCalloutLine: function(calloutLine) {
  15022. if (calloutLine) {
  15023. return Ext.apply({}, calloutLine);
  15024. }
  15025. return calloutLine;
  15026. },
  15027. createModifiers: function() {
  15028. var me = this,
  15029. mods = me.callParent(arguments);
  15030. mods.callout = new Ext.chart.modifier.Callout({
  15031. sprite: me
  15032. });
  15033. mods.animation.setUpper(mods.callout);
  15034. mods.callout.setUpper(mods.target);
  15035. },
  15036. render: function(surface, ctx) {
  15037. var me = this,
  15038. attr = me.attr,
  15039. calloutColor = attr.calloutColor;
  15040. ctx.save();
  15041. ctx.globalAlpha *= attr.callout;
  15042. if (ctx.globalAlpha > 0 && attr.calloutHasLine) {
  15043. if (calloutColor && calloutColor.isGradient) {
  15044. calloutColor = calloutColor.getStops()[0].color;
  15045. }
  15046. ctx.strokeStyle = calloutColor;
  15047. ctx.fillStyle = calloutColor;
  15048. ctx.lineWidth = attr.calloutWidth;
  15049. ctx.beginPath();
  15050. ctx.moveTo(me.attr.calloutStartX, me.attr.calloutStartY);
  15051. ctx.lineTo(me.attr.calloutEndX, me.attr.calloutEndY);
  15052. ctx.stroke();
  15053. ctx.beginPath();
  15054. ctx.arc(me.attr.calloutStartX, me.attr.calloutStartY, 1 * attr.calloutWidth, 0, 2 * Math.PI, true);
  15055. ctx.fill();
  15056. ctx.beginPath();
  15057. ctx.arc(me.attr.calloutEndX, me.attr.calloutEndY, 1 * attr.calloutWidth, 0, 2 * Math.PI, true);
  15058. ctx.fill();
  15059. }
  15060. ctx.restore();
  15061. Ext.draw.sprite.Text.prototype.render.apply(me, arguments);
  15062. }
  15063. });
  15064. /**
  15065. * Series is the abstract class containing the common logic to all chart series.
  15066. * Series includes methods from Labels, Highlights, and Callouts mixins. This class
  15067. * implements the logic of animating, hiding, showing all elements and returning the
  15068. * color of the series to be used as a legend item.
  15069. *
  15070. * ## Listeners
  15071. *
  15072. * The series class supports listeners via the Observable syntax.
  15073. *
  15074. * For example:
  15075. *
  15076. * Ext.create('Ext.chart.CartesianChart', {
  15077. * plugins: {
  15078. * chartitemevents: {
  15079. * moveEvents: true
  15080. * }
  15081. * },
  15082. * store: {
  15083. * fields: ['pet', 'households', 'total'],
  15084. * data: [
  15085. * {pet: 'Cats', households: 38, total: 93},
  15086. * {pet: 'Dogs', households: 45, total: 79},
  15087. * {pet: 'Fish', households: 13, total: 171}
  15088. * ]
  15089. * },
  15090. * axes: [{
  15091. * type: 'numeric',
  15092. * position: 'left'
  15093. * }, {
  15094. * type: 'category',
  15095. * position: 'bottom'
  15096. * }],
  15097. * series: [{
  15098. * type: 'bar',
  15099. * xField: 'pet',
  15100. * yField: 'households',
  15101. * listeners: {
  15102. * itemmousemove: function (series, item, event) {
  15103. * console.log('itemmousemove', item.category, item.field);
  15104. * }
  15105. * }
  15106. * }, {
  15107. * type: 'line',
  15108. * xField: 'pet',
  15109. * yField: 'total',
  15110. * marker: true
  15111. * }]
  15112. * });
  15113. *
  15114. */
  15115. Ext.define('Ext.chart.series.Series', {
  15116. requires: [
  15117. 'Ext.chart.Util',
  15118. 'Ext.chart.Markers',
  15119. 'Ext.chart.sprite.Label',
  15120. 'Ext.tip.ToolTip'
  15121. ],
  15122. mixins: [
  15123. 'Ext.mixin.Observable',
  15124. 'Ext.mixin.Bindable'
  15125. ],
  15126. isSeries: true,
  15127. defaultBindProperty: 'store',
  15128. /**
  15129. * @property {String} type
  15130. * The type of series. Set in subclasses.
  15131. * @protected
  15132. */
  15133. type: null,
  15134. /**
  15135. * @property {String} seriesType
  15136. * Default series sprite type.
  15137. */
  15138. seriesType: 'sprite',
  15139. identifiablePrefix: 'ext-line-',
  15140. observableType: 'series',
  15141. darkerStrokeRatio: 0.15,
  15142. /**
  15143. * @event itemmousemove
  15144. * Fires when the mouse is moved on a series item.
  15145. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15146. * plugin be added to the chart.
  15147. * @param {Ext.chart.series.Series} series
  15148. * @param {Object} item
  15149. * @param {Event} event
  15150. */
  15151. /**
  15152. * @event itemmouseup
  15153. * Fires when a mouseup event occurs on a series item.
  15154. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15155. * plugin be added to the chart.
  15156. * @param {Ext.chart.series.Series} series
  15157. * @param {Object} item
  15158. * @param {Event} event
  15159. */
  15160. /**
  15161. * @event itemmousedown
  15162. * Fires when a mousedown event occurs on a series item.
  15163. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15164. * plugin be added to the chart.
  15165. * @param {Ext.chart.series.Series} series
  15166. * @param {Object} item
  15167. * @param {Event} event
  15168. */
  15169. /**
  15170. * @event itemmouseover
  15171. * Fires when the mouse enters a series item.
  15172. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15173. * plugin be added to the chart.
  15174. * @param {Ext.chart.series.Series} series
  15175. * @param {Object} item
  15176. * @param {Event} event
  15177. */
  15178. /**
  15179. * @event itemmouseout
  15180. * Fires when the mouse exits a series item.
  15181. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15182. * plugin be added to the chart.
  15183. * @param {Ext.chart.series.Series} series
  15184. * @param {Object} item
  15185. * @param {Event} event
  15186. */
  15187. /**
  15188. * @event itemclick
  15189. * Fires when a click event occurs on a series item.
  15190. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15191. * plugin be added to the chart.
  15192. * @param {Ext.chart.series.Series} series
  15193. * @param {Object} item
  15194. * @param {Event} event
  15195. */
  15196. /**
  15197. * @event itemdblclick
  15198. * Fires when a double click event occurs on a series item.
  15199. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15200. * plugin be added to the chart.
  15201. * @param {Ext.chart.series.Series} series
  15202. * @param {Object} item
  15203. * @param {Event} event
  15204. */
  15205. /**
  15206. * @event itemtap
  15207. * Fires when a tap event occurs on a series item.
  15208. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15209. * plugin be added to the chart.
  15210. * @param {Ext.chart.series.Series} series
  15211. * @param {Object} item
  15212. * @param {Event} event
  15213. */
  15214. /**
  15215. * @event chartattached
  15216. * Fires when the {@link Ext.chart.AbstractChart} has been attached to this series.
  15217. * @param {Ext.chart.AbstractChart} chart
  15218. * @param {Ext.chart.series.Series} series
  15219. */
  15220. /**
  15221. * @event chartdetached
  15222. * Fires when the {@link Ext.chart.AbstractChart} has been detached from this series.
  15223. * @param {Ext.chart.AbstractChart} chart
  15224. * @param {Ext.chart.series.Series} series
  15225. */
  15226. /**
  15227. * @event storechange
  15228. * Fires when the store of the series changes.
  15229. * @param {Ext.chart.series.Series} series
  15230. * @param {Ext.data.Store} newStore
  15231. * @param {Ext.data.Store} oldStore
  15232. */
  15233. config: {
  15234. /**
  15235. * @private
  15236. * @cfg {Object} chart The chart that the series is bound.
  15237. */
  15238. chart: null,
  15239. /**
  15240. * @cfg {String|String[]} title
  15241. * The human-readable name of the series (displayed in the legend).
  15242. * If the series is stacked (has multiple components in it) this
  15243. * should be an array, where each string corresponds to a stacked component.
  15244. */
  15245. title: null,
  15246. /**
  15247. * @cfg {Function} renderer
  15248. * A function that can be provided to set custom styling properties to each
  15249. * rendered element. It receives `(sprite, config, rendererData, index)`
  15250. * as parameters.
  15251. *
  15252. * @param {Object} sprite The sprite affected by the renderer.
  15253. * The visual attributes are in `sprite.attr`.
  15254. * The data field is available in `sprite.getField()`.
  15255. * @param {Object} config The sprite configuration, which varies with the series
  15256. * and the type of sprite. For instance, a Line chart sprite might have just the
  15257. * `x` and `y` properties while a Bar chart sprite also has `width` and `height`.
  15258. * A `type` might be present too. For instance to draw each marker and each segment
  15259. * of a Line chart, the renderer is called with the `config.type` set to either
  15260. * `marker` or `line`.
  15261. * @param {Object} rendererData A record with different properties depending on
  15262. * the type of chart. The only guaranteed property is `rendererData.store`, the
  15263. * store used by the series. In some cases, a store may not exist: for instance
  15264. * a Gauge chart may read its value directly from its configuration; in this case
  15265. * rendererData.store is null and the value is available in rendererData.value.
  15266. * @param {Number} index The index of the sprite. It is usually the index of the
  15267. * store record associated with the sprite, in which case the record can be obtained
  15268. * with `store.getData().items[index]`. If the chart is not associated with a store,
  15269. * the index represents the index of the sprite within the series. For instance
  15270. * a Gauge chart may have as many sprites as there are sectors in the background of
  15271. * the gauge, plus one for the needle.
  15272. *
  15273. * @return {Object} The attributes that have been changed or added.
  15274. * Note: it is usually possible to add or modify the attributes directly into the
  15275. * `config` parameter and not return anything, but returning an object with only
  15276. * those attributes that have been changed may allow for optimizations in the
  15277. * rendering of some series. Example to draw every other marker in red:
  15278. *
  15279. * renderer: function (sprite, config, rendererData, index) {
  15280. * if (config.type === 'marker') {
  15281. * return { strokeStyle: (index % 2 === 0 ? 'red' : 'black') };
  15282. * }
  15283. * }
  15284. *
  15285. * @controllable
  15286. */
  15287. renderer: null,
  15288. /**
  15289. * @cfg {Boolean} showInLegend
  15290. * Whether to show this series in the legend.
  15291. */
  15292. showInLegend: true,
  15293. /**
  15294. * @private
  15295. * Trigger drawlistener flag
  15296. */
  15297. triggerAfterDraw: false,
  15298. /**
  15299. * @private
  15300. */
  15301. theme: null,
  15302. /**
  15303. * @cfg {Object} style Custom style configuration for the sprite used in the series.
  15304. * It overrides the style that is provided by the current theme.
  15305. */
  15306. style: {},
  15307. /**
  15308. * @cfg {Object} subStyle This is the cyclic used if the series has multiple sprites.
  15309. */
  15310. subStyle: {},
  15311. /**
  15312. * @private
  15313. * @cfg {Object} themeStyle Style configuration that is provided by the current theme.
  15314. * It is composed of five objects:
  15315. * @cfg {Object} themeStyle.style Properties common to all the series,
  15316. * for instance the 'lineWidth'.
  15317. * @cfg {Object} themeStyle.subStyle Cyclic used if the series has multiple sprites.
  15318. * @cfg {Object} themeStyle.label Sprite config for the labels,
  15319. * for instance the font and color.
  15320. * @cfg {Object} themeStyle.marker Sprite config for the markers,
  15321. * for instance the size and stroke color.
  15322. * @cfg {Object} themeStyle.markerSubStyle Cyclic used if series have multiple marker
  15323. * sprites.
  15324. */
  15325. themeStyle: {},
  15326. /**
  15327. * @cfg {Array} colors
  15328. * An array of color values which is used, in order of appearance, by the series.
  15329. * Each series can request one or more colors from the array. Radar, Scatter or Line charts
  15330. * require just one color each. Candlestick and OHLC require two
  15331. * (1 for drops + 1 for rises). Pie charts and Stacked charts (like Bar or Pie charts)
  15332. * require one color for each data category they represent, so one color for each slice
  15333. * of a Pie chart or each segment (not bar) of a Bar chart.
  15334. * It overrides the colors that are provided by the current theme.
  15335. */
  15336. colors: null,
  15337. /**
  15338. * @cfg {Boolean|Number} useDarkerStrokeColor
  15339. * Colors for the series can be set directly through the 'colors' config, or indirectly
  15340. * with the current theme or the 'colors' config that is set onto the chart. These colors
  15341. * are used as "fill color". Set this config to true, if you want a darker color for the
  15342. * strokes. Set it to false if you want to use the same color as the fill color.
  15343. * Alternatively, you can set it to a number between 0 and 1 to control how much darker
  15344. * the strokes should be.
  15345. * Note: this should be initial config and cannot be changed later on.
  15346. */
  15347. useDarkerStrokeColor: true,
  15348. /**
  15349. * @cfg {Object} store The store to use for this series. If not specified,
  15350. * the series will use the chart's {@link Ext.chart.AbstractChart#store store}.
  15351. */
  15352. store: null,
  15353. /**
  15354. * @cfg {Object} label
  15355. * Object with the following properties:
  15356. *
  15357. * @cfg {String} label.display
  15358. *
  15359. * Specifies the presence and position of the labels.
  15360. * The possible values depend on the series type.
  15361. * For Line and Scatter series: 'under' | 'over' | 'rotate'.
  15362. * For Bar and 3D Bar series: 'insideStart' | 'insideEnd' | 'outside'.
  15363. * For Pie series: 'inside' | 'outside' | 'rotate' | 'horizontal' | 'vertical'.
  15364. * Area, Radar and Candlestick series don't support labels.
  15365. * For Area and Radar series please consider using {@link #tooltip tooltips} instead.
  15366. * 3D Pie series currently always display labels 'outside'.
  15367. * For all series: 'none' hides the labels.
  15368. *
  15369. * Default value: 'none'.
  15370. *
  15371. * @cfg {String} label.color
  15372. *
  15373. * The color of the label text.
  15374. *
  15375. * Default value: '#000' (black).
  15376. *
  15377. * @cfg {String|String[]} label.field
  15378. *
  15379. * The name(s) of the field(s) to be displayed in the labels. If your chart has 3 series
  15380. * that correspond to the fields 'a', 'b', and 'c' of your model, and you only want to
  15381. * display labels for the series 'c', you must still provide an array `[null, null, 'c']`.
  15382. *
  15383. * Default value: null.
  15384. *
  15385. * @cfg {String} label.font
  15386. *
  15387. * The font used for the labels.
  15388. *
  15389. * Default value: '14px Helvetica'.
  15390. *
  15391. * @cfg {String} label.orientation
  15392. *
  15393. * Either 'horizontal' or 'vertical'. If not set (default), the orientation is inferred
  15394. * from the value of the flipXY property of the series.
  15395. *
  15396. * Default value: ''.
  15397. *
  15398. * @cfg {Function} label.renderer
  15399. *
  15400. * Optional function for formatting the label into a displayable value.
  15401. *
  15402. * The arguments to the method are:
  15403. *
  15404. * - *`text`*, *`sprite`*, *`config`*, *`rendererData`*, *`index`*
  15405. *
  15406. * Label's renderer is passed the same arguments as {@link #renderer}
  15407. * plus one extra 'text' argument which comes first.
  15408. *
  15409. * @return {Object|String} The attributes that have been changed or added,
  15410. * or the text for the label.
  15411. * Example to enclose every other label in parentheses:
  15412. *
  15413. * renderer: function (text) {
  15414. * if (index % 2 == 0) {
  15415. * return '(' + text + ')'
  15416. * }
  15417. * }
  15418. */
  15419. label: null,
  15420. /**
  15421. * @cfg {Number} labelOverflowPadding
  15422. * Extra distance value for which the labelOverflow listener is triggered.
  15423. */
  15424. labelOverflowPadding: null,
  15425. /**
  15426. * @cfg {Boolean} showMarkers
  15427. * Whether markers should be displayed at the data points along the line. If true,
  15428. * then the {@link #marker} config item will determine the markers' styling.
  15429. */
  15430. showMarkers: true,
  15431. /**
  15432. * @cfg {Object|Boolean} marker
  15433. * The sprite template used by marker instances on the series.
  15434. * If the value of the marker config is set to `true` or the type
  15435. * of the sprite instance is not specified, the {@link Ext.draw.sprite.Circle}
  15436. * sprite will be used.
  15437. *
  15438. * Examples:
  15439. *
  15440. * marker: true
  15441. *
  15442. * marker: {
  15443. * radius: 8
  15444. * }
  15445. *
  15446. * marker: {
  15447. * type: 'arrow',
  15448. * animation: {
  15449. * duration: 200,
  15450. * easing: 'backOut'
  15451. * }
  15452. * }
  15453. */
  15454. marker: null,
  15455. /**
  15456. * @cfg {Object} markerSubStyle
  15457. * This is cyclic used if series have multiple marker sprites.
  15458. */
  15459. markerSubStyle: null,
  15460. /**
  15461. * @protected
  15462. * @cfg {Object} itemInstancing
  15463. * The sprite template used to create sprite instances in the series.
  15464. */
  15465. itemInstancing: null,
  15466. /**
  15467. * @cfg {Object} background
  15468. * Sets the background of the surface the series is attached.
  15469. */
  15470. background: null,
  15471. /**
  15472. * @protected
  15473. * @cfg {Ext.draw.Surface} surface
  15474. * The chart surface used to render series sprites.
  15475. */
  15476. surface: null,
  15477. /**
  15478. * @protected
  15479. * @cfg {Object} overlaySurface
  15480. * The surface used to render series labels.
  15481. */
  15482. overlaySurface: null,
  15483. /**
  15484. * @cfg {Boolean|Array} hidden
  15485. */
  15486. hidden: false,
  15487. /**
  15488. * @cfg {Boolean/Object} highlight
  15489. * The sprite attributes that will be applied to the highlighted items in the series.
  15490. * If set to 'true', the default highlight style from {@link #highlightCfg} will be used.
  15491. * If the value of this config is an object, it will be merged with the
  15492. * {@link #highlightCfg}. In case merging of 'highlight' and 'highlightCfg' configs
  15493. * in not the desired behavior, provide the 'highlightCfg' instead.
  15494. */
  15495. highlight: false,
  15496. /**
  15497. * @protected
  15498. * @cfg {Object} highlightCfg
  15499. * The default style for the highlighted item.
  15500. * Used when {@link #highlight} config was simply set to 'true' instead of specifying
  15501. * a style.
  15502. */
  15503. highlightCfg: {
  15504. // Make custom highlightCfg's in subclasses replace this one.
  15505. merge: function(value) {
  15506. return value;
  15507. },
  15508. $value: {
  15509. fillStyle: 'yellow',
  15510. strokeStyle: 'red'
  15511. }
  15512. },
  15513. /**
  15514. * @cfg {Object} animation The series animation configuration.
  15515. * By default, the series is using the same animation the chart uses,
  15516. * if it's own animation is not explicitly configured.
  15517. */
  15518. animation: null,
  15519. /**
  15520. * @cfg {Object} tooltip
  15521. * Add tooltips to the visualization's markers. The config options for the
  15522. * tooltip are the same configuration used with {@link Ext.tip.ToolTip} plus a
  15523. * `renderer` config option and a `scope` for the renderer. For example:
  15524. *
  15525. * tooltip: {
  15526. * trackMouse: true,
  15527. * width: 140,
  15528. * height: 28,
  15529. * renderer: function (toolTip, record, ctx) {
  15530. * toolTip.setHtml(record.get('name') + ': ' + record.get('data1') + ' views');
  15531. * }
  15532. * }
  15533. *
  15534. * Note that tooltips are shown for series markers and won't work
  15535. * if the {@link #marker} is not configured.
  15536. *
  15537. * You can also configure
  15538. * {@link Ext.chart.interactions.ItemHighlight#multiTooltips}
  15539. * to display multiple tooltips for adjacent or overlapping Line series
  15540. * data points within {@link Ext.chart.series.Line#selectionTolerance} radius.
  15541. *
  15542. * @cfg {Object} tooltip.scope The scope to use when the renderer function is
  15543. * called. Defaults to the Series instance.
  15544. * @cfg {Function} tooltip.renderer An 'interceptor' method which can be used to
  15545. * modify the tooltip attributes before it is shown. The renderer function is
  15546. * passed the following params:
  15547. * @cfg {Ext.tip.ToolTip} tooltip.renderer.toolTip The tooltip instance
  15548. * @cfg {Ext.data.Model} tooltip.renderer.record The record instance for the
  15549. * chart item (sprite) currently targeted by the tooltip.
  15550. * @cfg {Object} tooltip.renderer.ctx A data object with values relating to the
  15551. * currently targeted chart sprite
  15552. * @cfg {String} tooltip.renderer.ctx.category The type of sprite passed to the
  15553. * renderer function (will be "items", "markers", or "labels" depending on the
  15554. * target sprite of the tooltip)
  15555. * @cfg {String} tooltip.renderer.ctx.field The {@link #yField} for the series
  15556. * @cfg {Number} tooltip.renderer.ctx.index The target sprite's index within the
  15557. * series' items
  15558. * @cfg {Ext.data.Model} tooltip.renderer.ctx.record The record instance for the
  15559. * chart item (sprite) currently targeted by the tooltip.
  15560. * @cfg {Ext.chart.series.Series} tooltip.renderer.ctx.series The series instance
  15561. * containing the tooltip's target sprite
  15562. * @cfg {Ext.draw.sprite.Sprite} tooltip.renderer.ctx.sprite The sprite (item)
  15563. * target of the tooltip
  15564. */
  15565. tooltip: null
  15566. },
  15567. directions: [],
  15568. sprites: null,
  15569. /**
  15570. * @private
  15571. * Returns the number of colors this series needs.
  15572. * A Pie chart needs one color per slice while a Stacked Bar chart needs one per segment.
  15573. * An OHLC chart needs 2 colors (one for drops, one for rises), and most other charts
  15574. * need just a single color.
  15575. */
  15576. themeColorCount: function() {
  15577. return 1;
  15578. },
  15579. /**
  15580. * @private
  15581. * @property
  15582. * Series, where the number of sprites (an so unique colors they require)
  15583. * depends on the number of records in the store should set this to 'true'.
  15584. */
  15585. isStoreDependantColorCount: false,
  15586. /**
  15587. * @private
  15588. * Returns the number of markers this series needs.
  15589. * Currently, only the Line, Scatter and Radar series use markers - and they need
  15590. * just one each.
  15591. */
  15592. themeMarkerCount: function() {
  15593. return 0;
  15594. },
  15595. /**
  15596. * @private
  15597. * Each series has configs that tell which store record fields to use as data
  15598. * for a certain dimension. For example, `xField`, `yField` for most cartesian series,
  15599. * `angleField`, `radiusField` for polar series, `openField`, ..., `closeField`
  15600. * for CandleStick series, etc. The field category is an array of capitalized config
  15601. * names, minus the 'Field' part, to use as data for a certain dimension.
  15602. * For example, for CandleStick series we have:
  15603. *
  15604. * fieldCategoryY: ['Open', 'High', 'Low', 'Close']
  15605. *
  15606. * While for generic Cartesian series it is simply:
  15607. *
  15608. * fieldCategoryY: ['Y']
  15609. *
  15610. * This method fetches the values of those configs, i.e. the actual record fields to use.
  15611. *
  15612. * The {@link #coordinate} method in turn will use the values from the `fieldCategory`
  15613. * array to set data attributes of the series sprite. E.g., in case of CandleStick series,
  15614. * the following attributes will be set based on the values in the `fieldCategoryY` array:
  15615. *
  15616. * `dataOpen`, `dataHigh`, `dataLow`, `dataClose`
  15617. *
  15618. * Where the value of each attribute is a coordinated array of data from the corresponding
  15619. * field.
  15620. *
  15621. * @param {String[]} fieldCategory
  15622. * @return {String[]}
  15623. */
  15624. getFields: function(fieldCategory) {
  15625. var me = this,
  15626. fields = [],
  15627. ln = fieldCategory.length,
  15628. i, field;
  15629. for (i = 0; i < ln; i++) {
  15630. field = me['get' + fieldCategory[i] + 'Field']();
  15631. if (Ext.isArray(field)) {
  15632. fields.push.apply(fields, field);
  15633. } else {
  15634. fields.push(field);
  15635. }
  15636. }
  15637. return fields;
  15638. },
  15639. applyAnimation: function(animation, oldAnimation) {
  15640. var chart = this.getChart();
  15641. if (!chart.isSettingSeriesAnimation) {
  15642. this.isUserAnimation = true;
  15643. }
  15644. return Ext.chart.Util.applyAnimation(animation, oldAnimation);
  15645. },
  15646. updateAnimation: function(animation) {
  15647. var sprites = this.getSprites(),
  15648. itemsMarker, markersMarker, i, ln, sprite;
  15649. for (i = 0 , ln = sprites.length; i < ln; i++) {
  15650. sprite = sprites[i];
  15651. if (sprite.isMarkerHolder) {
  15652. itemsMarker = sprite.getMarker('items');
  15653. if (itemsMarker) {
  15654. itemsMarker.getTemplate().setAnimation(animation);
  15655. }
  15656. markersMarker = sprite.getMarker('markers');
  15657. if (markersMarker) {
  15658. markersMarker.getTemplate().setAnimation(animation);
  15659. }
  15660. }
  15661. sprite.setAnimation(animation);
  15662. }
  15663. },
  15664. getAnimation: function() {
  15665. var chart = this.getChart(),
  15666. animation;
  15667. if (chart && chart.animationSuspendCount) {
  15668. animation = {
  15669. duration: 0
  15670. };
  15671. } else {
  15672. if (this.isUserAnimation) {
  15673. animation = this.callParent();
  15674. } else {
  15675. animation = chart.getAnimation();
  15676. }
  15677. }
  15678. return animation;
  15679. },
  15680. updateTitle: function() {
  15681. var me = this,
  15682. chart = me.getChart();
  15683. if (chart && !chart.isInitializing) {
  15684. chart.refreshLegendStore();
  15685. }
  15686. },
  15687. applyHighlight: function(highlight, oldHighlight) {
  15688. var me = this,
  15689. highlightCfg = me.getHighlightCfg();
  15690. if (Ext.isObject(highlight)) {
  15691. highlight = Ext.merge({}, highlightCfg, highlight);
  15692. } else if (highlight === true) {
  15693. highlight = highlightCfg;
  15694. }
  15695. if (highlight) {
  15696. highlight.type = 'highlight';
  15697. }
  15698. return highlight && Ext.merge({}, oldHighlight, highlight);
  15699. },
  15700. updateHighlight: function(highlight) {
  15701. var me = this,
  15702. sprites = me.sprites,
  15703. highlightCfg = me.getHighlightCfg(),
  15704. i, ln, sprite, items, markers;
  15705. me.getStyle();
  15706. // Make sure the 'markers' sprite has been created,
  15707. // so that we can set the 'style' config of its 'highlight' modifier here.
  15708. me.getMarker();
  15709. if (!Ext.Object.isEmpty(highlight)) {
  15710. me.addItemHighlight();
  15711. for (i = 0 , ln = sprites.length; i < ln; i++) {
  15712. sprite = sprites[i];
  15713. if (sprite.isMarkerHolder) {
  15714. items = sprite.getMarker('items');
  15715. if (items) {
  15716. items.getTemplate().modifiers.highlight.setStyle(highlight);
  15717. }
  15718. markers = sprite.getMarker('markers');
  15719. if (markers) {
  15720. markers.getTemplate().modifiers.highlight.setStyle(highlight);
  15721. }
  15722. }
  15723. }
  15724. } else if (!Ext.Object.equals(highlightCfg, this.defaultConfig.highlightCfg)) {
  15725. this.addItemHighlight();
  15726. }
  15727. },
  15728. updateHighlightCfg: function(highlightCfg) {
  15729. // Make sure to add the 'itemhighlight' interaction to the series, if the default
  15730. // highlight style changes, even if the 'highlight' config isn't set (defaults to false),
  15731. // since we probably want to use item highlighting now or later, if we are changing
  15732. // the default highlight style.
  15733. // This updater will be triggered by the 'highlight' applier, and the 'addItemHighlight'
  15734. // call here will in turn call 'getHighlight' down the call stack, which will return
  15735. // 'undefined' since the value hasn't been processed yet. So we don't call
  15736. // 'addItemHighlight' here during configuration and instead call it in the 'highlight'
  15737. // updater, if it hasn't already been called ('highlight' config is set to 'false').
  15738. if (!this.isConfiguring && !Ext.Object.equals(highlightCfg, this.defaultConfig.highlightCfg)) {
  15739. this.addItemHighlight();
  15740. }
  15741. },
  15742. applyItemInstancing: function(config, oldConfig) {
  15743. if (config && oldConfig && (!config.type || config.type === oldConfig.type)) {
  15744. // Have to merge to a new object, or the updater won't be called.
  15745. config = Ext.merge({}, oldConfig, config);
  15746. }
  15747. if (config && !config.type) {
  15748. config = null;
  15749. }
  15750. return config;
  15751. },
  15752. setAttributesForItem: function(item, change) {
  15753. var sprite = item && item.sprite,
  15754. i;
  15755. if (sprite) {
  15756. if (sprite.isMarkerHolder && item.category === 'items') {
  15757. sprite.putMarker(item.category, change, item.index, false, true);
  15758. }
  15759. if (sprite.isMarkerHolder && item.category === 'markers') {
  15760. sprite.putMarker(item.category, change, item.index, false, true);
  15761. } else if (sprite.isInstancing) {
  15762. sprite.setAttributesFor(item.index, change);
  15763. } else if (Ext.isArray(sprite)) {
  15764. // In some instances, like with the 3D pie series,
  15765. // an item can be composed of multiple sprites
  15766. // (e.g. 8 sprites are used to render a single 3D pie slice).
  15767. for (i = 0; i < sprite.length; i++) {
  15768. sprite[i].setAttributes(change);
  15769. }
  15770. } else {
  15771. sprite.setAttributes(change);
  15772. }
  15773. }
  15774. },
  15775. getBBoxForItem: function(item) {
  15776. var sprite = item && item.sprite,
  15777. result = null;
  15778. if (sprite) {
  15779. if (sprite.getMarker('items') && item.category === 'items') {
  15780. result = sprite.getMarkerBBox(item.category, item.index);
  15781. } else if (sprite instanceof Ext.draw.sprite.Instancing) {
  15782. result = sprite.getBBoxFor(item.index);
  15783. } else {
  15784. result = sprite.getBBox();
  15785. }
  15786. }
  15787. return result;
  15788. },
  15789. /**
  15790. * @private
  15791. * @property
  15792. * The range of "coordinated" data.
  15793. * Typically, for two directions ('X' and 'Y') the `dataRange` would look like this:
  15794. *
  15795. * dataRange[0] - minX
  15796. * dataRange[1] - minY
  15797. * dataRange[2] - maxX
  15798. * dataRange[3] - maxY
  15799. *
  15800. * And the series' {@link #coordinate} method would be called like this:
  15801. *
  15802. * coordinate('X', 0, 2)
  15803. * coordinate('Y', 1, 2)
  15804. *
  15805. * For numbers, coordinated data are numbers themselves.
  15806. * For categories - their indexes.
  15807. * For Date objects - their timestamps.
  15808. * In other words, whatever source data we have, it has to be converted to numbers
  15809. * before it can be plotted.
  15810. */
  15811. dataRange: null,
  15812. constructor: function(config) {
  15813. var me = this,
  15814. id;
  15815. config = config || {};
  15816. // Backward compatibility with Ext.
  15817. if (config.tips) {
  15818. config = Ext.apply({
  15819. tooltip: config.tips
  15820. }, config);
  15821. }
  15822. // Backward compatibility with Touch.
  15823. if (config.highlightCfg) {
  15824. config = Ext.apply({
  15825. highlight: config.highlightCfg
  15826. }, config);
  15827. }
  15828. if ('id' in config) {
  15829. id = config.id;
  15830. } else if ('id' in me.config) {
  15831. id = me.config.id;
  15832. } else {
  15833. id = me.getId();
  15834. }
  15835. me.setId(id);
  15836. me.sprites = [];
  15837. me.dataRange = [];
  15838. me.mixins.observable.constructor.call(me, config);
  15839. me.initBindable();
  15840. },
  15841. lookupViewModel: function(skipThis) {
  15842. // Override the Bindable's method to redirect view model
  15843. // lookup to the chart.
  15844. var chart = this.getChart();
  15845. return chart ? chart.lookupViewModel(skipThis) : null;
  15846. },
  15847. applyTooltip: function(tooltip, oldTooltip) {
  15848. var config = Ext.apply({
  15849. xtype: 'tooltip',
  15850. renderer: Ext.emptyFn,
  15851. constrainPosition: true,
  15852. shrinkWrapDock: true,
  15853. autoHide: true,
  15854. hideDelay: 200,
  15855. mouseOffset: [
  15856. 20,
  15857. 20
  15858. ],
  15859. trackMouse: true
  15860. }, tooltip);
  15861. return Ext.create(config);
  15862. },
  15863. updateTooltip: function() {
  15864. // Tooltips can't work without the 'itemhighlight' or the 'itemedit' interaction.
  15865. this.addItemHighlight();
  15866. },
  15867. // Adds the 'itemhighlight' interaction to the chart that owns the series.
  15868. addItemHighlight: function() {
  15869. var chart = this.getChart(),
  15870. interactions, interaction, hasRequiredInteraction, i;
  15871. if (!chart) {
  15872. return;
  15873. }
  15874. interactions = chart.getInteractions();
  15875. for (i = 0; i < interactions.length; i++) {
  15876. interaction = interactions[i];
  15877. if (interaction.isItemHighlight || interaction.isItemEdit) {
  15878. hasRequiredInteraction = true;
  15879. break;
  15880. }
  15881. }
  15882. if (!hasRequiredInteraction) {
  15883. interactions.push('itemhighlight');
  15884. chart.setInteractions(interactions);
  15885. }
  15886. },
  15887. showTooltip: function(item, event) {
  15888. var me = this,
  15889. tooltip = me.getTooltip();
  15890. if (!tooltip) {
  15891. return;
  15892. }
  15893. Ext.callback(tooltip.renderer, tooltip.scope, [
  15894. tooltip,
  15895. item.record,
  15896. item
  15897. ], 0, me);
  15898. tooltip.showBy(event);
  15899. },
  15900. showTooltipAt: function(item, x, y) {
  15901. var me = this,
  15902. tooltip = me.getTooltip(),
  15903. mouseOffset = tooltip.config.mouseOffset;
  15904. if (!tooltip || !tooltip.showAt) {
  15905. return;
  15906. }
  15907. if (mouseOffset) {
  15908. x += mouseOffset[0];
  15909. y += mouseOffset[1];
  15910. }
  15911. Ext.callback(tooltip.renderer, tooltip.scope, [
  15912. tooltip,
  15913. item.record,
  15914. item
  15915. ], 0, me);
  15916. tooltip.showAt([
  15917. x,
  15918. y
  15919. ]);
  15920. },
  15921. hideTooltip: function(item, immediate) {
  15922. var me = this,
  15923. tooltip = me.getTooltip();
  15924. if (!tooltip) {
  15925. return;
  15926. }
  15927. if (immediate) {
  15928. tooltip.hide();
  15929. } else {
  15930. tooltip.delayHide();
  15931. }
  15932. },
  15933. applyStore: function(store) {
  15934. return store && Ext.StoreManager.lookup(store);
  15935. },
  15936. getStore: function() {
  15937. return this._store || this.getChart() && this.getChart().getStore();
  15938. },
  15939. updateStore: function(newStore, oldStore) {
  15940. var me = this,
  15941. chart = me.getChart(),
  15942. chartStore = chart && chart.getStore(),
  15943. sprites, sprite, len, i;
  15944. oldStore = oldStore || chartStore;
  15945. if (oldStore && oldStore !== newStore) {
  15946. oldStore.un({
  15947. datachanged: 'onDataChanged',
  15948. update: 'onDataChanged',
  15949. scope: me
  15950. });
  15951. }
  15952. if (newStore) {
  15953. newStore.on({
  15954. datachanged: 'onDataChanged',
  15955. update: 'onDataChanged',
  15956. scope: me
  15957. });
  15958. sprites = me.getSprites();
  15959. for (i = 0 , len = sprites.length; i < len; i++) {
  15960. sprite = sprites[i];
  15961. if (sprite.setStore) {
  15962. sprite.setStore(newStore);
  15963. }
  15964. }
  15965. me.onDataChanged();
  15966. }
  15967. me.fireEvent('storechange', me, newStore, oldStore);
  15968. },
  15969. onStoreChange: function(chart, newStore, oldStore) {
  15970. if (!this._store) {
  15971. this.updateStore(newStore, oldStore);
  15972. }
  15973. },
  15974. defaultRange: [
  15975. 0,
  15976. 1
  15977. ],
  15978. /**
  15979. * @private
  15980. * @param direction {'X'/'Y'}
  15981. * @param directionOffset
  15982. * @param directionCount
  15983. */
  15984. coordinate: function(direction, directionOffset, directionCount) {
  15985. var me = this,
  15986. store = me.getStore(),
  15987. hidden = me.getHidden(),
  15988. items = store.getData().items,
  15989. axis = me['get' + direction + 'Axis'](),
  15990. dataRange = [
  15991. NaN,
  15992. NaN
  15993. ],
  15994. fieldCategory = me['fieldCategory' + direction] || [
  15995. direction
  15996. ],
  15997. fields = me.getFields(fieldCategory),
  15998. i, field, data,
  15999. style = {},
  16000. sprites = me.getSprites(),
  16001. axisRange;
  16002. if (sprites.length && !Ext.isBoolean(hidden) || !hidden) {
  16003. for (i = 0; i < fieldCategory.length; i++) {
  16004. field = fields[i];
  16005. data = me.coordinateData(items, field, axis);
  16006. Ext.chart.Util.expandRange(dataRange, data);
  16007. style['data' + fieldCategory[i]] = data;
  16008. }
  16009. // We don't want to expand the range that has a span of 0 here
  16010. // (e.g. [5, 5] that we'd get if all values for a field are 5).
  16011. // We only want to do this in the Axis, when we calculate the
  16012. // combined range.
  16013. // This is because, if we try to expand the range of values here,
  16014. // and we have multiple fields, the combined range for the axis
  16015. // may not represent the actual range of the data.
  16016. // E.g. if other fields have non-zero span ranges like [4.95, 5.03],
  16017. // [4.91, 5.08], and if the `padding` param to `validateRange` is 0.5,
  16018. // the range of the axis will end up being [4.5, 5.5], because the
  16019. // [5, 5] range of one of the series was expanded to [4.5, 5.5]
  16020. // which encompasses the rest of the ranges.
  16021. dataRange = Ext.chart.Util.validateRange(dataRange, me.defaultRange, 0);
  16022. // See `dataRange` docs.
  16023. me.dataRange[directionOffset] = dataRange[0];
  16024. me.dataRange[directionOffset + directionCount] = dataRange[1];
  16025. style['dataMin' + direction] = dataRange[0];
  16026. style['dataMax' + direction] = dataRange[1];
  16027. if (axis) {
  16028. axisRange = axis.getRange(true);
  16029. axis.setBoundSeriesRange(axisRange);
  16030. }
  16031. for (i = 0; i < sprites.length; i++) {
  16032. sprites[i].setAttributes(style);
  16033. }
  16034. }
  16035. },
  16036. /**
  16037. * @private
  16038. * This method will return an array containing data coordinated by a specific axis.
  16039. * @param {Array} items Store records.
  16040. * @param {String} field The field to fetch from each record.
  16041. * @param {Ext.chart.axis.Axis} axis The axis used to lay out the data.
  16042. * @return {Array}
  16043. */
  16044. coordinateData: function(items, field, axis) {
  16045. var data = [],
  16046. length = items.length,
  16047. layout = axis && axis.getLayout(),
  16048. i, x;
  16049. for (i = 0; i < length; i++) {
  16050. x = items[i].data[field];
  16051. // An empty string (a valid discrete axis value) will be coordinated
  16052. // by the axis layout (if axis is given), otherwise it will be converted
  16053. // to zero (via +'').
  16054. if (!Ext.isEmpty(x, true)) {
  16055. if (layout) {
  16056. data[i] = layout.getCoordFor(x, field, i, items);
  16057. } else {
  16058. x = +x;
  16059. // 'x' can be a category name here.
  16060. data[i] = Ext.isNumber(x) ? x : i;
  16061. }
  16062. } else {
  16063. data[i] = x;
  16064. }
  16065. }
  16066. return data;
  16067. },
  16068. updateLabelData: function() {
  16069. var label = this.getLabel();
  16070. if (!label) {
  16071. return;
  16072. }
  16073. // eslint-disable-next-line vars-on-top, one-var
  16074. var store = this.getStore(),
  16075. items = store.getData().items,
  16076. sprites = this.getSprites(),
  16077. labelTpl = label.getTemplate(),
  16078. labelFields = Ext.Array.from(labelTpl.getField()),
  16079. i, j, ln, labels, sprite, field;
  16080. if (!sprites.length || !labelFields.length) {
  16081. return;
  16082. }
  16083. for (i = 0; i < sprites.length; i++) {
  16084. sprite = sprites[i];
  16085. if (!sprite.getField) {
  16086. // The 'gauge' series is misnormer, its sprites
  16087. // do not extend from the base Series sprite and
  16088. // so do not have the 'field' config. They also
  16089. // don't support labels in the traditional sense.
  16090. continue;
  16091. }
  16092. labels = [];
  16093. field = sprite.getField();
  16094. if (Ext.Array.indexOf(labelFields, field) < 0) {
  16095. field = labelFields[i];
  16096. }
  16097. for (j = 0 , ln = items.length; j < ln; j++) {
  16098. labels.push(items[j].get(field));
  16099. }
  16100. sprite.setAttributes({
  16101. labels: labels
  16102. });
  16103. }
  16104. },
  16105. /**
  16106. * @private
  16107. *
  16108. * *** Data processing overview. ***
  16109. *
  16110. * The data is processed in the following order:
  16111. *
  16112. * 1) chart.processData() - calls `processData` of all series
  16113. * 2) series.processData() - calls `processData` of all bound axes,
  16114. * or jumps to (5) directly, if the series has no axis
  16115. * in this direction
  16116. * 3) axis.processData() - calls the `processData` of its own layout
  16117. * 4) axisLayout.processData() - calls `coordinateX/Y` of all bound series
  16118. * 5) series.coordinateX/Y - calls its own `coordinate` method in that direction
  16119. * 6) series.coordinate - calls its own `coordinateData` method using the right
  16120. * record fields and axes
  16121. * 7) series.coordinateData - calls `getCoordFor` of the axis layout for the given
  16122. * field
  16123. * 8) layout.getCoordFor - returns a numeric value for the given field value,
  16124. * whatever its type may be
  16125. *
  16126. * The `dataX`, `dataY` attributes of the series' sprites are set by the
  16127. * `series.coordinate` method using the data returned by the `coordinateData`.
  16128. * `series.coordinate` also calculates the range of said data (via `expandRange`)
  16129. * and sets the `dataMinX/Y`, `dataMaxX/Y` attributes of the series' sprites.
  16130. */
  16131. processData: function() {
  16132. var me = this,
  16133. directions = me.directions,
  16134. direction, axis, name, i, ln;
  16135. if (me.isProcessingData || !me.getStore()) {
  16136. return;
  16137. }
  16138. me.isProcessingData = true;
  16139. for (i = 0 , ln = directions.length; i < ln; i++) {
  16140. direction = directions[i];
  16141. axis = me['get' + direction + 'Axis']();
  16142. if (axis) {
  16143. axis.processData(me);
  16144. continue;
  16145. }
  16146. name = 'coordinate' + direction;
  16147. if (me[name]) {
  16148. me[name]();
  16149. }
  16150. }
  16151. me.updateLabelData();
  16152. me.isProcessingData = false;
  16153. },
  16154. applyBackground: function(background) {
  16155. var surface, result;
  16156. if (this.getChart()) {
  16157. surface = this.getSurface();
  16158. surface.setBackground(background);
  16159. result = surface.getBackground();
  16160. } else {
  16161. result = background;
  16162. }
  16163. return result;
  16164. },
  16165. updateChart: function(newChart, oldChart) {
  16166. var me = this,
  16167. store = me._store;
  16168. if (oldChart) {
  16169. oldChart.un('axeschange', 'onAxesChange', me);
  16170. me.clearSprites();
  16171. me.setSurface(null);
  16172. me.setOverlaySurface(null);
  16173. oldChart.unregister(me);
  16174. me.onChartDetached(oldChart);
  16175. if (!store) {
  16176. me.updateStore(null);
  16177. }
  16178. }
  16179. if (newChart) {
  16180. me.setSurface(newChart.getSurface('series'));
  16181. me.setOverlaySurface(newChart.getSurface('overlay'));
  16182. newChart.on('axeschange', 'onAxesChange', me);
  16183. // TODO: Gauge series should render correctly when chart's store is missing.
  16184. // TODO: When store is initially missing the getAxes will return null here,
  16185. // TODO: since applyAxes has actually triggered this series.updateChart call
  16186. // TODO: indirectly.
  16187. // TODO: Figure out why it doesn't go this route when a store is present.
  16188. if (newChart.getAxes()) {
  16189. me.onAxesChange(newChart);
  16190. }
  16191. me.onChartAttached(newChart);
  16192. newChart.register(me);
  16193. if (!store) {
  16194. me.updateStore(newChart.getStore());
  16195. }
  16196. }
  16197. },
  16198. onAxesChange: function(chart, force) {
  16199. if (chart.destroying || chart.destroyed) {
  16200. return;
  16201. }
  16202. // eslint-disable-next-line vars-on-top
  16203. var me = this,
  16204. axes = chart.getAxes(),
  16205. axis,
  16206. directionToAxesMap = {},
  16207. directionToFieldsMap = {},
  16208. needHighPrecision = false,
  16209. directions = this.directions,
  16210. direction, i, ln;
  16211. for (i = 0 , ln = directions.length; i < ln; i++) {
  16212. direction = directions[i];
  16213. directionToFieldsMap[direction] = me.getFields(me['fieldCategory' + direction]);
  16214. }
  16215. for (i = 0 , ln = axes.length; i < ln; i++) {
  16216. axis = axes[i];
  16217. direction = axis.getDirection();
  16218. if (!directionToAxesMap[direction]) {
  16219. directionToAxesMap[direction] = [
  16220. axis
  16221. ];
  16222. } else {
  16223. directionToAxesMap[direction].push(axis);
  16224. }
  16225. }
  16226. for (i = 0 , ln = directions.length; i < ln; i++) {
  16227. direction = directions[i];
  16228. if (!force && me['get' + direction + 'Axis']()) {
  16229. continue;
  16230. }
  16231. if (directionToAxesMap[direction]) {
  16232. axis = me.findMatchingAxis(directionToAxesMap[direction], directionToFieldsMap[direction]);
  16233. if (axis) {
  16234. me['set' + direction + 'Axis'](axis);
  16235. if (axis.getNeedHighPrecision()) {
  16236. needHighPrecision = true;
  16237. }
  16238. }
  16239. }
  16240. }
  16241. this.getSurface().setHighPrecision(needHighPrecision);
  16242. },
  16243. /**
  16244. * @private
  16245. * Given the list of axes in a certain direction and a list of series fields in that
  16246. * direction returns the first matching axis for the series in that direction,
  16247. * or undefined if a match wasn't found.
  16248. */
  16249. findMatchingAxis: function(directionAxes, directionFields) {
  16250. var axis, axisFields, i, j;
  16251. for (i = 0; i < directionAxes.length; i++) {
  16252. axis = directionAxes[i];
  16253. axisFields = axis.getFields();
  16254. if (!axisFields.length) {
  16255. return axis;
  16256. } else if (directionFields) {
  16257. for (j = 0; j < directionFields.length; j++) {
  16258. if (Ext.Array.indexOf(axisFields, directionFields[j]) >= 0) {
  16259. return axis;
  16260. }
  16261. }
  16262. }
  16263. }
  16264. },
  16265. onChartDetached: function(oldChart) {
  16266. var me = this;
  16267. me.fireEvent('chartdetached', oldChart, me);
  16268. oldChart.un('storechange', 'onStoreChange', me);
  16269. },
  16270. onChartAttached: function(chart) {
  16271. var me = this;
  16272. me.fireEvent('chartattached', chart, me);
  16273. chart.on('storechange', 'onStoreChange', me);
  16274. me.processData();
  16275. },
  16276. updateOverlaySurface: function(overlaySurface) {
  16277. var label = this.getLabel();
  16278. if (overlaySurface && label) {
  16279. overlaySurface.add(label);
  16280. }
  16281. },
  16282. getLabel: function() {
  16283. return this.labelMarker;
  16284. },
  16285. setLabel: function(label) {
  16286. var me = this,
  16287. chart = me.getChart(),
  16288. marker = me.labelMarker,
  16289. template;
  16290. // The label sprite is reused unless the value of 'label' is falsy,
  16291. // so that we can transition from one attribute set to another with an
  16292. // animation, which is important for example during theme switching.
  16293. if (!label && marker) {
  16294. marker.getTemplate().destroy();
  16295. marker.destroy();
  16296. me.labelMarker = marker = null;
  16297. }
  16298. if (label) {
  16299. if (!marker) {
  16300. marker = me.labelMarker = new Ext.chart.Markers({
  16301. zIndex: 10
  16302. });
  16303. marker.setTemplate(new Ext.chart.sprite.Label());
  16304. me.getOverlaySurface().add(marker);
  16305. }
  16306. template = marker.getTemplate();
  16307. template.setAttributes(label);
  16308. template.setConfig(label);
  16309. if (label.field) {
  16310. template.setField(label.field);
  16311. }
  16312. if (label.display) {
  16313. marker.setAttributes({
  16314. hidden: label.display === 'none'
  16315. });
  16316. }
  16317. marker.setDirty(true);
  16318. }
  16319. // Inform the label about the template change.
  16320. me.updateLabelData();
  16321. if (chart && !chart.isInitializing && !me.isConfiguring) {
  16322. chart.redraw();
  16323. }
  16324. },
  16325. createItemInstancingSprite: function(sprite, itemInstancing) {
  16326. var me = this,
  16327. markers = new Ext.chart.Markers(),
  16328. config = Ext.apply({
  16329. modifiers: 'highlight'
  16330. }, itemInstancing),
  16331. style = me.getStyle(),
  16332. template, animation;
  16333. markers.setAttributes({
  16334. zIndex: Number.MAX_VALUE
  16335. });
  16336. markers.setTemplate(config);
  16337. template = markers.getTemplate();
  16338. template.setAttributes(style);
  16339. animation = template.getAnimation();
  16340. animation.on('animationstart', 'onSpriteAnimationStart', this);
  16341. animation.on('animationend', 'onSpriteAnimationEnd', this);
  16342. sprite.bindMarker('items', markers);
  16343. me.getSurface().add(markers);
  16344. return markers;
  16345. },
  16346. getDefaultSpriteConfig: function() {
  16347. return {
  16348. type: this.seriesType,
  16349. renderer: this.getRenderer()
  16350. };
  16351. },
  16352. updateRenderer: function(renderer) {
  16353. var me = this,
  16354. chart = me.getChart();
  16355. if (chart && chart.isInitializing) {
  16356. return;
  16357. }
  16358. // We have to be careful and not call the 'getSprites' method here, as this
  16359. // method itself may have been called by the 'getSprites' method indirectly already.
  16360. if (me.sprites.length) {
  16361. me.sprites[0].setAttributes({
  16362. renderer: renderer || null
  16363. });
  16364. if (chart && !chart.isInitializing) {
  16365. chart.redraw();
  16366. }
  16367. }
  16368. },
  16369. updateShowMarkers: function(showMarkers) {
  16370. var sprite = this.getSprite(),
  16371. markers = sprite && sprite.getMarker('markers');
  16372. if (markers) {
  16373. markers.getTemplate().setAttributes({
  16374. hidden: !showMarkers
  16375. });
  16376. }
  16377. },
  16378. createSprite: function() {
  16379. var me = this,
  16380. surface = me.getSurface(),
  16381. itemInstancing = me.getItemInstancing(),
  16382. sprite = surface.add(me.getDefaultSpriteConfig()),
  16383. animation, label;
  16384. sprite.setAttributes(me.getStyle());
  16385. sprite.setSeries(me);
  16386. if (itemInstancing) {
  16387. me.createItemInstancingSprite(sprite, itemInstancing);
  16388. }
  16389. if (sprite.isMarkerHolder) {
  16390. label = me.getLabel();
  16391. if (label && label.getTemplate().getField()) {
  16392. sprite.bindMarker('labels', label);
  16393. }
  16394. }
  16395. if (sprite.setStore) {
  16396. sprite.setStore(me.getStore());
  16397. }
  16398. animation = sprite.getAnimation();
  16399. animation.on('animationstart', 'onSpriteAnimationStart', me);
  16400. animation.on('animationend', 'onSpriteAnimationEnd', me);
  16401. me.sprites.push(sprite);
  16402. return sprite;
  16403. },
  16404. /**
  16405. * @method
  16406. * Returns the read-only array of sprites the are used to draw this series.
  16407. */
  16408. getSprites: null,
  16409. /**
  16410. * @private
  16411. * Returns the first sprite. Convenience method for series that have
  16412. * a single markerholder sprite.
  16413. */
  16414. getSprite: function() {
  16415. var sprites = this.getSprites();
  16416. return sprites && sprites[0];
  16417. },
  16418. /**
  16419. * @private
  16420. */
  16421. withSprite: function(fn) {
  16422. var sprite = this.getSprite();
  16423. return sprite && fn(sprite) || undefined;
  16424. },
  16425. forEachSprite: function(fn) {
  16426. var sprites = this.getSprites(),
  16427. i, ln;
  16428. for (i = 0 , ln = sprites.length; i < ln; i++) {
  16429. fn(sprites[i]);
  16430. }
  16431. },
  16432. onDataChanged: function() {
  16433. var me = this,
  16434. chart = me.getChart(),
  16435. chartStore = chart && chart.getStore(),
  16436. seriesStore = me.getStore();
  16437. if (seriesStore !== chartStore) {
  16438. me.processData();
  16439. }
  16440. },
  16441. isXType: function(xtype) {
  16442. return xtype === 'series';
  16443. },
  16444. getItemId: function() {
  16445. return this.getId();
  16446. },
  16447. applyThemeStyle: function(theme, oldTheme) {
  16448. var me = this,
  16449. fill, stroke;
  16450. fill = theme && theme.subStyle && theme.subStyle.fillStyle;
  16451. stroke = fill && theme.subStyle.strokeStyle;
  16452. if (fill && !stroke) {
  16453. theme.subStyle.strokeStyle = me.getStrokeColorsFromFillColors(fill);
  16454. }
  16455. fill = theme && theme.markerSubStyle && theme.markerSubStyle.fillStyle;
  16456. stroke = fill && theme.markerSubStyle.strokeStyle;
  16457. if (fill && !stroke) {
  16458. theme.markerSubStyle.strokeStyle = me.getStrokeColorsFromFillColors(fill);
  16459. }
  16460. return Ext.apply(oldTheme || {}, theme);
  16461. },
  16462. applyStyle: function(style, oldStyle) {
  16463. return Ext.apply({}, style, oldStyle);
  16464. },
  16465. applySubStyle: function(subStyle, oldSubStyle) {
  16466. var name = Ext.ClassManager.getNameByAlias('sprite.' + this.seriesType),
  16467. cls = Ext.ClassManager.get(name);
  16468. if (cls && cls.def) {
  16469. subStyle = cls.def.batchedNormalize(subStyle, true);
  16470. }
  16471. return Ext.merge({}, oldSubStyle, subStyle);
  16472. },
  16473. applyMarker: function(marker, oldMarker) {
  16474. var type, cls;
  16475. if (marker) {
  16476. if (!Ext.isObject(marker)) {
  16477. marker = {};
  16478. }
  16479. type = marker.type || 'circle';
  16480. if (oldMarker && type === oldMarker.type) {
  16481. marker = Ext.merge({}, oldMarker, marker);
  16482. }
  16483. }
  16484. // Note: reusing the `oldMaker` like `Ext.merge(oldMarker, marker)`
  16485. // isn't possible because the `updateMarker` won't be called.
  16486. if (type) {
  16487. cls = Ext.ClassManager.get(Ext.ClassManager.getNameByAlias('sprite.' + type));
  16488. }
  16489. if (cls && cls.def) {
  16490. marker = cls.def.normalize(marker, true);
  16491. marker.type = type;
  16492. } else {
  16493. marker = null;
  16494. //<debug>
  16495. Ext.log.warn('Invalid series marker type: ' + type);
  16496. }
  16497. //</debug>
  16498. return marker;
  16499. },
  16500. updateMarker: function(marker) {
  16501. var me = this,
  16502. sprites = me.getSprites(),
  16503. seriesSprite, markerSprite, markerTplConfig, i, ln;
  16504. for (i = 0 , ln = sprites.length; i < ln; i++) {
  16505. seriesSprite = sprites[i];
  16506. if (!seriesSprite.isMarkerHolder) {
  16507. continue;
  16508. }
  16509. markerSprite = seriesSprite.getMarker('markers');
  16510. if (marker) {
  16511. if (!markerSprite) {
  16512. markerSprite = new Ext.chart.Markers();
  16513. seriesSprite.bindMarker('markers', markerSprite);
  16514. me.getOverlaySurface().add(markerSprite);
  16515. }
  16516. markerTplConfig = Ext.Object.merge({
  16517. modifiers: 'highlight'
  16518. }, marker);
  16519. markerSprite.setTemplate(markerTplConfig);
  16520. markerSprite.getTemplate().getAnimation().setCustomDurations({
  16521. translationX: 0,
  16522. translationY: 0
  16523. });
  16524. } else if (markerSprite) {
  16525. seriesSprite.releaseMarker('markers');
  16526. me.getOverlaySurface().remove(markerSprite, true);
  16527. }
  16528. seriesSprite.setDirty(true);
  16529. }
  16530. // If we call, for example, `series.setMarker({type: 'circle'})` on a series
  16531. // that has been already constructed, the newly added marker still has to be
  16532. // themed, and the 'style' config of its 'highlight' modifier has to be set.
  16533. if (!me.isConfiguring) {
  16534. me.doUpdateStyles();
  16535. me.updateHighlight(me.getHighlight());
  16536. }
  16537. },
  16538. applyMarkerSubStyle: function(marker, oldMarker) {
  16539. var type = (marker && marker.type) || (oldMarker && oldMarker.type) || 'circle',
  16540. cls = Ext.ClassManager.get(Ext.ClassManager.getNameByAlias('sprite.' + type));
  16541. if (cls && cls.def) {
  16542. marker = cls.def.batchedNormalize(marker, true);
  16543. }
  16544. return Ext.merge(oldMarker || {}, marker);
  16545. },
  16546. updateHidden: function(hidden) {
  16547. var me = this;
  16548. me.getColors();
  16549. me.getSubStyle();
  16550. me.setSubStyle({
  16551. hidden: hidden
  16552. });
  16553. me.processData();
  16554. me.doUpdateStyles();
  16555. if (!Ext.isArray(hidden)) {
  16556. me.updateLegendStore(hidden);
  16557. }
  16558. },
  16559. /**
  16560. * @private
  16561. * Updates chart's legend store when the value of the series' {@link #hidden} config
  16562. * changes or when the {@link #setHiddenByIndex} method is called.
  16563. * @param hidden Whether series (or its component) should be hidden or not.
  16564. * @param index Used for stacked series.
  16565. * If present, only the component with the specified index will change
  16566. * visibility.
  16567. */
  16568. updateLegendStore: function(hidden, index) {
  16569. var me = this,
  16570. chart = me.getChart(),
  16571. legendStore = chart && chart.getLegendStore(),
  16572. id = me.getId(),
  16573. record;
  16574. if (legendStore) {
  16575. if (arguments.length > 1) {
  16576. record = legendStore.findBy(function(rec) {
  16577. return rec.get('series') === id && rec.get('index') === index;
  16578. });
  16579. if (record !== -1) {
  16580. record = legendStore.getAt(record);
  16581. }
  16582. } else {
  16583. record = legendStore.findRecord('series', id);
  16584. }
  16585. if (record && record.get('disabled') !== hidden) {
  16586. record.set('disabled', hidden);
  16587. }
  16588. }
  16589. },
  16590. /**
  16591. *
  16592. * @param {Number} index
  16593. * @param {Boolean} value
  16594. */
  16595. setHiddenByIndex: function(index, value) {
  16596. var me = this;
  16597. if (Ext.isArray(me.getHidden())) {
  16598. // Multi-sprite series like Pie and StackedCartesian.
  16599. me.getHidden()[index] = value;
  16600. me.updateHidden(me.getHidden());
  16601. me.updateLegendStore(value, index);
  16602. } else {
  16603. me.setHidden(value);
  16604. }
  16605. },
  16606. getStrokeColorsFromFillColors: function(colors) {
  16607. var me = this,
  16608. darker = me.getUseDarkerStrokeColor(),
  16609. darkerRatio = (Ext.isNumber(darker) ? darker : me.darkerStrokeRatio),
  16610. strokeColors;
  16611. if (darker) {
  16612. strokeColors = Ext.Array.map(colors, function(color) {
  16613. color = Ext.isString(color) ? color : color.stops[0].color;
  16614. color = Ext.util.Color.fromString(color);
  16615. return color.createDarker(darkerRatio).toString();
  16616. });
  16617. } else {
  16618. strokeColors = Ext.Array.clone(colors);
  16619. }
  16620. return strokeColors;
  16621. },
  16622. updateThemeColors: function(colors) {
  16623. var me = this,
  16624. theme = me.getThemeStyle(),
  16625. fillColors = Ext.Array.clone(colors),
  16626. strokeColors = me.getStrokeColorsFromFillColors(colors),
  16627. newSubStyle = {
  16628. fillStyle: fillColors,
  16629. strokeStyle: strokeColors
  16630. };
  16631. theme.subStyle = Ext.apply(theme.subStyle || {}, newSubStyle);
  16632. theme.markerSubStyle = Ext.apply(theme.markerSubStyle || {}, newSubStyle);
  16633. me.doUpdateStyles();
  16634. if (!me.isConfiguring) {
  16635. me.getChart().refreshLegendStore();
  16636. }
  16637. },
  16638. themeOnlyIfConfigured: {},
  16639. updateTheme: function(theme) {
  16640. var me = this,
  16641. seriesTheme = theme.getSeries(),
  16642. initialConfig = me.getInitialConfig(),
  16643. defaultConfig = me.defaultConfig,
  16644. configs = me.self.getConfigurator().configs,
  16645. genericSeriesTheme = seriesTheme.defaults,
  16646. specificSeriesTheme = seriesTheme[me.type],
  16647. themeOnlyIfConfigured = me.themeOnlyIfConfigured,
  16648. key, value, isObjValue, isUnusedConfig, initialValue, cfg;
  16649. seriesTheme = Ext.merge({}, genericSeriesTheme, specificSeriesTheme);
  16650. for (key in seriesTheme) {
  16651. value = seriesTheme[key];
  16652. cfg = configs[key];
  16653. if (value !== null && value !== undefined && cfg) {
  16654. initialValue = initialConfig[key];
  16655. isObjValue = Ext.isObject(value);
  16656. isUnusedConfig = initialValue === defaultConfig[key];
  16657. if (isObjValue) {
  16658. if (isUnusedConfig && themeOnlyIfConfigured[key]) {
  16659. continue;
  16660. }
  16661. value = Ext.merge({}, value, initialValue);
  16662. }
  16663. if (isUnusedConfig || isObjValue) {
  16664. me[cfg.names.set](value);
  16665. }
  16666. }
  16667. }
  16668. },
  16669. /**
  16670. * @private
  16671. * When the chart's "colors" config changes, these colors are passed onto the series
  16672. * where they are used with the same priority as theme colors, i.e. they do not override
  16673. * the series' "colors" config, nor the series' "style" config, but they do override
  16674. * the colors from the theme's "seriesThemes" config.
  16675. */
  16676. updateChartColors: function(colors) {
  16677. var me = this;
  16678. if (!me.getColors()) {
  16679. me.updateThemeColors(colors);
  16680. }
  16681. },
  16682. updateColors: function(colors) {
  16683. var chart;
  16684. this.updateThemeColors(colors);
  16685. if (!this.isConfiguring) {
  16686. chart = this.getChart();
  16687. if (chart) {
  16688. chart.refreshLegendStore();
  16689. }
  16690. }
  16691. },
  16692. updateStyle: function() {
  16693. this.doUpdateStyles();
  16694. },
  16695. updateSubStyle: function() {
  16696. this.doUpdateStyles();
  16697. },
  16698. updateThemeStyle: function() {
  16699. this.doUpdateStyles();
  16700. },
  16701. doUpdateStyles: function() {
  16702. var me = this,
  16703. sprites = me.sprites,
  16704. itemInstancing = me.getItemInstancing(),
  16705. ln = sprites && sprites.length,
  16706. // 'showMarkers' updater calls 'series.getSprites()',
  16707. // which we don't want to call here.
  16708. showMarkers = me.getConfig('showMarkers', true),
  16709. // eslint-disable-line no-unused-vars
  16710. style, sprite, marker, i;
  16711. for (i = 0; i < ln; i++) {
  16712. sprite = sprites[i];
  16713. style = me.getStyleByIndex(i);
  16714. if (itemInstancing) {
  16715. sprite.getMarker('items').getTemplate().setAttributes(style);
  16716. }
  16717. sprite.setAttributes(style);
  16718. marker = sprite.isMarkerHolder && sprite.getMarker('markers');
  16719. if (marker) {
  16720. marker.getTemplate().setAttributes(me.getMarkerStyleByIndex(i));
  16721. }
  16722. }
  16723. },
  16724. getStyleWithTheme: function() {
  16725. var me = this,
  16726. theme = me.getThemeStyle(),
  16727. style = Ext.clone(me.getStyle());
  16728. if (theme && theme.style) {
  16729. Ext.applyIf(style, theme.style);
  16730. }
  16731. return style;
  16732. },
  16733. getSubStyleWithTheme: function() {
  16734. var me = this,
  16735. theme = me.getThemeStyle(),
  16736. subStyle = Ext.clone(me.getSubStyle());
  16737. if (theme && theme.subStyle) {
  16738. Ext.applyIf(subStyle, theme.subStyle);
  16739. }
  16740. return subStyle;
  16741. },
  16742. getStyleByIndex: function(i) {
  16743. var me = this,
  16744. theme = me.getThemeStyle(),
  16745. style, themeStyle, subStyle, themeSubStyle,
  16746. result = {};
  16747. style = me.getStyle();
  16748. themeStyle = (theme && theme.style) || {};
  16749. subStyle = me.styleDataForIndex(me.getSubStyle(), i);
  16750. themeSubStyle = me.styleDataForIndex((theme && theme.subStyle), i);
  16751. Ext.apply(result, themeStyle);
  16752. Ext.apply(result, themeSubStyle);
  16753. Ext.apply(result, style);
  16754. Ext.apply(result, subStyle);
  16755. return result;
  16756. },
  16757. getMarkerStyleByIndex: function(i) {
  16758. var me = this,
  16759. theme = me.getThemeStyle(),
  16760. style, themeStyle, subStyle, themeSubStyle, markerStyle, themeMarkerStyle, markerSubStyle, themeMarkerSubStyle,
  16761. result = {};
  16762. style = me.getStyle();
  16763. themeStyle = (theme && theme.style) || {};
  16764. // 'series.updateHidden()' will update 'series.subStyle.hidden' config
  16765. // with the value of the 'series.hidden' config.
  16766. // But we also need to account for 'series.showMarkers' config
  16767. // to determine whether the markers should be hidden or not.
  16768. subStyle = me.styleDataForIndex(me.getSubStyle(), i);
  16769. if (subStyle.hasOwnProperty('hidden')) {
  16770. subStyle.hidden = subStyle.hidden || !this.getConfig('showMarkers', true);
  16771. }
  16772. themeSubStyle = me.styleDataForIndex((theme && theme.subStyle), i);
  16773. markerStyle = me.getMarker();
  16774. themeMarkerStyle = (theme && theme.marker) || {};
  16775. markerSubStyle = me.getMarkerSubStyle();
  16776. themeMarkerSubStyle = me.styleDataForIndex((theme && theme.markerSubStyle), i);
  16777. Ext.apply(result, themeStyle);
  16778. Ext.apply(result, themeSubStyle);
  16779. Ext.apply(result, themeMarkerStyle);
  16780. Ext.apply(result, themeMarkerSubStyle);
  16781. Ext.apply(result, style);
  16782. Ext.apply(result, subStyle);
  16783. Ext.apply(result, markerStyle);
  16784. Ext.apply(result, markerSubStyle);
  16785. return result;
  16786. },
  16787. styleDataForIndex: function(style, i) {
  16788. var value, name,
  16789. result = {};
  16790. if (style) {
  16791. for (name in style) {
  16792. value = style[name];
  16793. if (Ext.isArray(value)) {
  16794. result[name] = value[i % value.length];
  16795. } else {
  16796. result[name] = value;
  16797. }
  16798. }
  16799. }
  16800. return result;
  16801. },
  16802. /**
  16803. * @method
  16804. * For a given x/y point relative to the main rect, find a corresponding item from this
  16805. * series, if any.
  16806. * @param {Number} x
  16807. * @param {Number} y
  16808. * @param {Object} [target] optional target to receive the result
  16809. * @return {Object} An object describing the item, or null if there is no matching item.
  16810. * The exact contents of this object will vary by series type, but should always contain
  16811. * at least the following:
  16812. *
  16813. * @return {Ext.data.Model} return.record the record of the item.
  16814. * @return {Array} return.point the x/y coordinates relative to the chart box
  16815. * of a single point for this data item, which can be used as e.g. a tooltip anchor
  16816. * point.
  16817. * @return {Ext.draw.sprite.Sprite} return.sprite the item's rendering Sprite.
  16818. * @return {Number} return.subSprite the index if sprite is an instancing sprite.
  16819. */
  16820. getItemForPoint: Ext.emptyFn,
  16821. /**
  16822. * Returns a series item by index and (optional) category.
  16823. * @param {Number} index The index of the item (matches store record index).
  16824. * @param {String} [category] The category of item, e.g.: 'items', 'markers', 'sprites'.
  16825. * @return {Object} item
  16826. */
  16827. getItemByIndex: function(index, category) {
  16828. var me = this,
  16829. sprites = me.getSprites(),
  16830. sprite = sprites && sprites[0],
  16831. item;
  16832. if (!sprite) {
  16833. return;
  16834. }
  16835. // 'category' is not defined, making our best guess here.
  16836. if (category === undefined && sprite.isMarkerHolder) {
  16837. category = me.getItemInstancing() ? 'items' : 'markers';
  16838. } else if (!category || category === '' || category === 'sprites') {
  16839. sprite = sprites[index];
  16840. }
  16841. if (sprite) {
  16842. item = {
  16843. series: me,
  16844. category: category,
  16845. index: index,
  16846. record: me.getStore().getData().items[index],
  16847. field: me.getYField(),
  16848. sprite: sprite
  16849. };
  16850. return item;
  16851. }
  16852. },
  16853. onSpriteAnimationStart: function(sprite) {
  16854. this.fireEvent('animationstart', this, sprite);
  16855. },
  16856. onSpriteAnimationEnd: function(sprite) {
  16857. this.fireEvent('animationend', this, sprite);
  16858. },
  16859. resolveListenerScope: function(defaultScope) {
  16860. // Override the Observable's method to redirect listener scope
  16861. // resolution to the chart.
  16862. var me = this,
  16863. namedScope = Ext._namedScopes[defaultScope],
  16864. chart = me.getChart(),
  16865. scope;
  16866. if (!namedScope) {
  16867. scope = chart ? chart.resolveListenerScope(defaultScope, false) : (defaultScope || me);
  16868. } else if (namedScope.isThis) {
  16869. scope = me;
  16870. } else if (namedScope.isController) {
  16871. scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
  16872. } else if (namedScope.isSelf) {
  16873. scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
  16874. // Class body listener. No chart controller, nor chart container controller.
  16875. if (scope === chart && !chart.getInheritedConfig('defaultListenerScope')) {
  16876. scope = me;
  16877. }
  16878. }
  16879. return scope;
  16880. },
  16881. /**
  16882. * Provide legend information to target array.
  16883. *
  16884. * @param {Array} target
  16885. *
  16886. * The information consists:
  16887. * @param {String} target.name
  16888. * @param {String} target.mark
  16889. * @param {Boolean} target.disabled
  16890. * @param {String} target.series
  16891. * @param {Number} target.index
  16892. */
  16893. provideLegendInfo: function(target) {
  16894. var me = this,
  16895. style = me.getSubStyleWithTheme(),
  16896. fill = style.fillStyle;
  16897. if (Ext.isArray(fill)) {
  16898. fill = fill[0];
  16899. }
  16900. target.push({
  16901. name: me.getTitle() || me.getYField() || me.getId(),
  16902. mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
  16903. disabled: me.getHidden(),
  16904. series: me.getId(),
  16905. index: 0
  16906. });
  16907. },
  16908. clearSprites: function() {
  16909. var sprites = this.sprites,
  16910. sprite, i, ln;
  16911. for (i = 0 , ln = sprites.length; i < ln; i++) {
  16912. sprite = sprites[i];
  16913. if (sprite && sprite.isSprite) {
  16914. sprite.destroy();
  16915. }
  16916. }
  16917. this.sprites = [];
  16918. },
  16919. destroy: function() {
  16920. var me = this,
  16921. store = me._store,
  16922. // Peek at the config so we don't create one just to destroy it
  16923. tooltip = me.getConfig('tooltip', true);
  16924. if (store && store.getAutoDestroy()) {
  16925. Ext.destroy(store);
  16926. }
  16927. me.setChart(null);
  16928. me.clearListeners();
  16929. if (tooltip) {
  16930. Ext.destroy(tooltip);
  16931. }
  16932. me.callParent();
  16933. }
  16934. });
  16935. /**
  16936. * @class Ext.chart.interactions.Abstract
  16937. *
  16938. * Defines a common abstract parent class for all interactions.
  16939. *
  16940. */
  16941. Ext.define('Ext.chart.interactions.Abstract', {
  16942. xtype: 'interaction',
  16943. mixins: {
  16944. observable: 'Ext.mixin.Observable'
  16945. },
  16946. config: {
  16947. /**
  16948. * @cfg {Object} gesture
  16949. * Maps gestures that should be used for starting/maintaining/ending the interaction
  16950. * to corresponding class methods.
  16951. * @private
  16952. */
  16953. gestures: {
  16954. tap: 'onGesture'
  16955. },
  16956. /**
  16957. * @cfg {Ext.chart.AbstractChart} chart The chart that the interaction is bound.
  16958. */
  16959. chart: null,
  16960. /**
  16961. * @cfg {Boolean} enabled 'true' if the interaction is enabled.
  16962. */
  16963. enabled: true
  16964. },
  16965. /**
  16966. * Android device is emerging too many events so if we re-render every frame it will
  16967. * take forever to finish a frame.
  16968. * This throttle technique will limit the timespan between two frames.
  16969. */
  16970. throttleGap: 0,
  16971. stopAnimationBeforeSync: false,
  16972. constructor: function(config) {
  16973. var me = this,
  16974. id;
  16975. config = config || {};
  16976. if ('id' in config) {
  16977. id = config.id;
  16978. } else if ('id' in me.config) {
  16979. id = me.config.id;
  16980. } else {
  16981. id = me.getId();
  16982. }
  16983. me.setId(id);
  16984. me.mixins.observable.constructor.call(me, config);
  16985. },
  16986. updateChart: function(newChart, oldChart) {
  16987. var me = this;
  16988. if (oldChart === newChart) {
  16989. return;
  16990. }
  16991. if (oldChart) {
  16992. oldChart.unregister(me);
  16993. me.removeChartListener(oldChart);
  16994. }
  16995. if (newChart) {
  16996. newChart.register(me);
  16997. me.addChartListener();
  16998. }
  16999. },
  17000. updateEnabled: function(enabled) {
  17001. var me = this,
  17002. chart = me.getChart();
  17003. if (chart) {
  17004. if (enabled) {
  17005. me.addChartListener();
  17006. } else {
  17007. me.removeChartListener(chart);
  17008. }
  17009. }
  17010. },
  17011. /**
  17012. * @method
  17013. * @protected
  17014. * Placeholder method.
  17015. */
  17016. onGesture: Ext.emptyFn,
  17017. /**
  17018. * @protected
  17019. * Find and return a single series item corresponding to the given event,
  17020. * or null if no matching item is found.
  17021. * @param {Event} e
  17022. * @return {Object} the item object or null if none found.
  17023. */
  17024. getItemForEvent: function(e) {
  17025. var me = this,
  17026. chart = me.getChart(),
  17027. chartXY = chart.getEventXY(e);
  17028. return chart.getItemForPoint(chartXY[0], chartXY[1]);
  17029. },
  17030. /**
  17031. * Find and return all series items corresponding to the given event.
  17032. * @param {Event} e
  17033. * @return {Array} array of matching item objects
  17034. * @private
  17035. * @deprecated 6.5.2 This method is deprecated
  17036. */
  17037. getItemsForEvent: function(e) {
  17038. var me = this,
  17039. chart = me.getChart(),
  17040. chartXY = chart.getEventXY(e);
  17041. return chart.getItemsForPoint(chartXY[0], chartXY[1]);
  17042. },
  17043. /**
  17044. * @private
  17045. */
  17046. addChartListener: function() {
  17047. var me = this,
  17048. chart = me.getChart(),
  17049. gestures = me.getGestures(),
  17050. gesture;
  17051. if (!me.getEnabled()) {
  17052. return;
  17053. }
  17054. function insertGesture(name, fn) {
  17055. chart.addElementListener(name, // wrap the handler so it does not fire if the event is locked
  17056. // by another interaction
  17057. me.listeners[name] = function(e) {
  17058. var locks = me.getLocks(),
  17059. result;
  17060. if (me.getEnabled() && (!(name in locks) || locks[name] === me)) {
  17061. result = (Ext.isFunction(fn) ? fn : me[fn]).apply(this, arguments);
  17062. if (result === false && e && e.stopPropagation) {
  17063. e.stopPropagation();
  17064. }
  17065. return result;
  17066. }
  17067. }, me);
  17068. }
  17069. me.listeners = me.listeners || {};
  17070. for (gesture in gestures) {
  17071. insertGesture(gesture, gestures[gesture]);
  17072. }
  17073. },
  17074. removeChartListener: function(chart) {
  17075. var me = this,
  17076. gestures = me.getGestures(),
  17077. gesture;
  17078. function removeGesture(name) {
  17079. var fn = me.listeners[name];
  17080. if (fn) {
  17081. chart.removeElementListener(name, fn);
  17082. delete me.listeners[name];
  17083. }
  17084. }
  17085. if (me.listeners) {
  17086. for (gesture in gestures) {
  17087. removeGesture(gesture);
  17088. }
  17089. }
  17090. },
  17091. lockEvents: function() {
  17092. var me = this,
  17093. locks = me.getLocks(),
  17094. args = Array.prototype.slice.call(arguments),
  17095. i = args.length;
  17096. while (i--) {
  17097. locks[args[i]] = me;
  17098. }
  17099. },
  17100. unlockEvents: function() {
  17101. var locks = this.getLocks(),
  17102. args = Array.prototype.slice.call(arguments),
  17103. i = args.length;
  17104. while (i--) {
  17105. delete locks[args[i]];
  17106. }
  17107. },
  17108. getLocks: function() {
  17109. var chart = this.getChart();
  17110. return chart.lockedEvents || (chart.lockedEvents = {});
  17111. },
  17112. doSync: function() {
  17113. var me = this,
  17114. chart = me.getChart();
  17115. if (me.syncTimer) {
  17116. Ext.undefer(me.syncTimer);
  17117. me.syncTimer = null;
  17118. }
  17119. if (me.stopAnimationBeforeSync) {
  17120. chart.animationSuspendCount++;
  17121. }
  17122. chart.redraw();
  17123. if (me.stopAnimationBeforeSync) {
  17124. chart.animationSuspendCount--;
  17125. }
  17126. me.syncThrottle = Date.now() + me.throttleGap;
  17127. },
  17128. sync: function() {
  17129. var me = this;
  17130. if (me.throttleGap && Ext.frameStartTime < me.syncThrottle) {
  17131. if (me.syncTimer) {
  17132. return;
  17133. }
  17134. me.syncTimer = Ext.defer(function() {
  17135. me.doSync();
  17136. }, me.throttleGap);
  17137. } else {
  17138. me.doSync();
  17139. }
  17140. },
  17141. getItemId: function() {
  17142. return this.getId();
  17143. },
  17144. isXType: function(xtype) {
  17145. return xtype === 'interaction';
  17146. },
  17147. destroy: function() {
  17148. var me = this;
  17149. me.setChart(null);
  17150. delete me.listeners;
  17151. me.callParent();
  17152. }
  17153. }, function() {
  17154. if (Ext.os.is.Android4) {
  17155. this.prototype.throttleGap = 40;
  17156. }
  17157. });
  17158. /**
  17159. * Mixin that provides the functionality to place markers.
  17160. */
  17161. Ext.define('Ext.chart.MarkerHolder', {
  17162. extend: 'Ext.Mixin',
  17163. requires: [
  17164. 'Ext.chart.Markers'
  17165. ],
  17166. mixinConfig: {
  17167. id: 'markerHolder',
  17168. after: {
  17169. constructor: 'constructor',
  17170. preRender: 'preRender'
  17171. },
  17172. before: {
  17173. destroy: 'destroy'
  17174. }
  17175. },
  17176. isMarkerHolder: true,
  17177. // The combined transformation applied to the sprite by its parents.
  17178. // Does not include the transformation matrix of the sprite itself.
  17179. surfaceMatrix: null,
  17180. // The inverse of the above transformation to go back to the original state.
  17181. inverseSurfaceMatrix: null,
  17182. deprecated: {
  17183. 6: {
  17184. methods: {
  17185. /**
  17186. * Returns the markers bound to the given name.
  17187. * @param {String} name The name of the marker (e.g., "items", "labels", etc.).
  17188. * @return {Ext.chart.Markers[]}
  17189. * @method getBoundMarker
  17190. * @deprecated 6.0 Use {@link #getMarker} instead.
  17191. */
  17192. getBoundMarker: {
  17193. message: "Please use the 'getMarker' method instead.",
  17194. fn: function(name) {
  17195. var marker = this.boundMarkers[name];
  17196. return marker ? [
  17197. marker
  17198. ] : marker;
  17199. }
  17200. }
  17201. }
  17202. }
  17203. },
  17204. constructor: function() {
  17205. this.boundMarkers = {};
  17206. this.cleanRedraw = false;
  17207. },
  17208. /**
  17209. * Registers the given marker with the marker holder under the specified name.
  17210. * @param {String} name The name of the marker (e.g., "items", "labels", etc.).
  17211. * @param {Ext.chart.Markers} marker
  17212. */
  17213. bindMarker: function(name, marker) {
  17214. var me = this,
  17215. markers = me.boundMarkers;
  17216. if (marker && marker.isMarkers) {
  17217. //<debug>
  17218. if (markers[name] && markers[name] === marker) {
  17219. Ext.log.warn(me.getId(), " (MarkerHolder): the Markers instance '", marker.getId(), "' is already bound under the name '", name, "'.");
  17220. }
  17221. //</debug>
  17222. me.releaseMarker(name);
  17223. markers[name] = marker;
  17224. marker.on('destroy', me.onMarkerDestroy, me);
  17225. }
  17226. },
  17227. onMarkerDestroy: function(marker) {
  17228. this.releaseMarker(marker);
  17229. },
  17230. /**
  17231. * Unregisters the given marker or a marker with the given name.
  17232. * Providing a name of the marker is more efficient as it avoids lookup.
  17233. * @param marker {String/Ext.chart.Markers}
  17234. * @return {Ext.chart.Markers} Released marker or null.
  17235. */
  17236. releaseMarker: function(marker) {
  17237. var markers = this.boundMarkers,
  17238. name;
  17239. if (marker && marker.isMarkers) {
  17240. for (name in markers) {
  17241. if (markers[name] === marker) {
  17242. delete markers[name];
  17243. break;
  17244. }
  17245. }
  17246. } else {
  17247. name = marker;
  17248. marker = markers[name];
  17249. delete markers[name];
  17250. }
  17251. return marker || null;
  17252. },
  17253. /**
  17254. * Returns the marker bound to the given name (or null). See {@link #bindMarker}.
  17255. * @param {String} name The name of the marker (e.g., "items", "labels", etc.).
  17256. * @return {Ext.chart.Markers}
  17257. */
  17258. getMarker: function(name) {
  17259. return this.boundMarkers[name] || null;
  17260. },
  17261. preRender: function(surface, ctx, rect) {
  17262. var me = this,
  17263. id = me.getId(),
  17264. boundMarkers = me.boundMarkers,
  17265. parent = me.getParent(),
  17266. name, marker, matrix;
  17267. if (me.surfaceMatrix) {
  17268. matrix = me.surfaceMatrix.set(1, 0, 0, 1, 0, 0);
  17269. } else {
  17270. matrix = me.surfaceMatrix = new Ext.draw.Matrix();
  17271. }
  17272. me.cleanRedraw = !me.attr.dirty;
  17273. if (!me.cleanRedraw) {
  17274. for (name in boundMarkers) {
  17275. marker = boundMarkers[name];
  17276. if (marker) {
  17277. marker.clear(id);
  17278. }
  17279. }
  17280. }
  17281. // Parent can be either a sprite (like a composite or instancing)
  17282. // or a surface. First, climb up and apply transformations of the
  17283. // parent sprites.
  17284. while (parent && parent.attr && parent.attr.matrix) {
  17285. matrix.prependMatrix(parent.attr.matrix);
  17286. parent = parent.getParent();
  17287. }
  17288. // Finally, apply the transformation used by the surface.
  17289. matrix.prependMatrix(parent.matrix);
  17290. me.surfaceMatrix = matrix;
  17291. me.inverseSurfaceMatrix = matrix.inverse(me.inverseSurfaceMatrix);
  17292. },
  17293. putMarker: function(name, attr, index, bypassNormalization, keepRevision) {
  17294. var marker = this.boundMarkers[name];
  17295. if (marker) {
  17296. marker.putMarkerFor(this.getId(), attr, index, bypassNormalization, keepRevision);
  17297. }
  17298. },
  17299. getMarkerBBox: function(name, index, isWithoutTransform) {
  17300. var marker = this.boundMarkers[name];
  17301. if (marker) {
  17302. return marker.getMarkerBBoxFor(this.getId(), index, isWithoutTransform);
  17303. }
  17304. },
  17305. destroy: function() {
  17306. var boundMarkers = this.boundMarkers,
  17307. name, marker;
  17308. for (name in boundMarkers) {
  17309. marker = boundMarkers[name];
  17310. marker.destroy();
  17311. }
  17312. }
  17313. });
  17314. /**
  17315. * @private
  17316. * @class Ext.chart.axis.sprite.Axis
  17317. * @extends Ext.draw.sprite.Sprite
  17318. *
  17319. * The axis sprite. Currently all types of the axis will be rendered with this sprite.
  17320. */
  17321. Ext.define('Ext.chart.axis.sprite.Axis', {
  17322. extend: 'Ext.draw.sprite.Sprite',
  17323. alias: 'sprite.axis',
  17324. type: 'axis',
  17325. mixins: {
  17326. markerHolder: 'Ext.chart.MarkerHolder'
  17327. },
  17328. requires: [
  17329. 'Ext.draw.sprite.Text'
  17330. ],
  17331. inheritableStatics: {
  17332. def: {
  17333. processors: {
  17334. /**
  17335. * @cfg {Boolean} grid 'true' if the axis has a grid.
  17336. */
  17337. grid: 'bool',
  17338. /**
  17339. * @cfg {Boolean} axisLine 'true' if the main line of the axis is drawn.
  17340. */
  17341. axisLine: 'bool',
  17342. /**
  17343. * @cfg {Boolean} minorTicks 'true' if the axis has sub ticks.
  17344. */
  17345. minorTicks: 'bool',
  17346. /**
  17347. * @cfg {Number} minorTickSize The length of the minor ticks.
  17348. */
  17349. minorTickSize: 'number',
  17350. /**
  17351. * @cfg {Boolean} majorTicks 'true' if the axis has major ticks.
  17352. */
  17353. majorTicks: 'bool',
  17354. /**
  17355. * @cfg {Number} majorTickSize The length of the major ticks.
  17356. */
  17357. majorTickSize: 'number',
  17358. /**
  17359. * @cfg {Number} length The total length of the axis.
  17360. */
  17361. length: 'number',
  17362. /**
  17363. * @private
  17364. * @cfg {Number} startGap Axis start determined by the chart inset padding.
  17365. */
  17366. startGap: 'number',
  17367. /**
  17368. * @private
  17369. * @cfg {Number} endGap Axis end determined by the chart inset padding.
  17370. */
  17371. endGap: 'number',
  17372. /**
  17373. * @cfg {Number} dataMin The minimum value of the axis data.
  17374. */
  17375. dataMin: 'number',
  17376. /**
  17377. * @cfg {Number} dataMax The maximum value of the axis data.
  17378. */
  17379. dataMax: 'number',
  17380. /**
  17381. * @cfg {Number} visibleMin The minimum value that is displayed.
  17382. */
  17383. visibleMin: 'number',
  17384. /**
  17385. * @cfg {Number} visibleMax The maximum value that is displayed.
  17386. */
  17387. visibleMax: 'number',
  17388. /**
  17389. * @cfg {String} position The position of the axis on the chart.
  17390. */
  17391. position: 'enums(left,right,top,bottom,angular,radial,gauge)',
  17392. /**
  17393. * @cfg {Number} minStepSize The minimum step size between ticks.
  17394. */
  17395. minStepSize: 'number',
  17396. /**
  17397. * @private
  17398. * @cfg {Number} estStepSize The estimated step size between ticks.
  17399. */
  17400. estStepSize: 'number',
  17401. /**
  17402. * @private
  17403. * Unused.
  17404. */
  17405. titleOffset: 'number',
  17406. /**
  17407. * @cfg {Number} [textPadding=0]
  17408. * The padding around axis labels to determine collision.
  17409. * The default is 0 for all axes except horizontal axes of cartesian charts,
  17410. * where the default is 5 to prevent axis labels from blending one into another.
  17411. * This default is defined in the {@link Ext.chart.theme.Base#axis axis} config
  17412. * of the {@link Ext.chart.theme.Base Base} theme.
  17413. * You may want to change this default to a smaller number or 0, if you have
  17414. * horizontal axis labels rotated, which allows for more text to fit in.
  17415. */
  17416. textPadding: 'number',
  17417. /**
  17418. * @cfg {Number} min The minimum value of the axis.
  17419. * `min` and {@link #max} attributes represent the effective range of the axis
  17420. * after segmentation, layout, and range reconciliation between axes.
  17421. */
  17422. min: 'number',
  17423. /**
  17424. * @cfg {Number} max The maximum value of the axis.
  17425. * {@link #min} and `max` attributes represent the effective range of the axis
  17426. * after segmentation, layout, and range reconciliation between axes.
  17427. */
  17428. max: 'number',
  17429. /**
  17430. * @cfg {Number} centerX The central point of the angular axis on the x-axis.
  17431. */
  17432. centerX: 'number',
  17433. /**
  17434. * @cfg {Number} centerY The central point of the angular axis on the y-axis.
  17435. */
  17436. centerY: 'number',
  17437. /**
  17438. * @private
  17439. * @cfg {Number} radius
  17440. * Unused.
  17441. */
  17442. radius: 'number',
  17443. /**
  17444. * @private
  17445. */
  17446. totalAngle: 'number',
  17447. /**
  17448. * @cfg {Number} baseRotation The starting rotation of the angular axis.
  17449. */
  17450. baseRotation: 'number',
  17451. /**
  17452. * @private
  17453. * Unused.
  17454. */
  17455. data: 'default',
  17456. /**
  17457. * @cfg {Boolean} 'true' if the estimated step size is adjusted by text size.
  17458. */
  17459. enlargeEstStepSizeByText: 'bool'
  17460. },
  17461. defaults: {
  17462. grid: false,
  17463. axisLine: true,
  17464. minorTicks: false,
  17465. minorTickSize: 3,
  17466. majorTicks: true,
  17467. majorTickSize: 5,
  17468. length: 0,
  17469. startGap: 0,
  17470. endGap: 0,
  17471. visibleMin: 0,
  17472. visibleMax: 1,
  17473. dataMin: 0,
  17474. dataMax: 1,
  17475. position: '',
  17476. minStepSize: 0,
  17477. estStepSize: 20,
  17478. min: 0,
  17479. max: 1,
  17480. centerX: 0,
  17481. centerY: 0,
  17482. radius: 1,
  17483. baseRotation: 0,
  17484. data: null,
  17485. titleOffset: 0,
  17486. textPadding: 0,
  17487. scalingCenterY: 0,
  17488. scalingCenterX: 0,
  17489. // Override default
  17490. strokeStyle: 'black',
  17491. enlargeEstStepSizeByText: false
  17492. },
  17493. triggers: {
  17494. minorTickSize: 'bbox',
  17495. majorTickSize: 'bbox',
  17496. position: 'bbox,layout',
  17497. axisLine: 'bbox,layout',
  17498. minorTicks: 'layout',
  17499. min: 'layout',
  17500. max: 'layout',
  17501. length: 'layout',
  17502. minStepSize: 'layout',
  17503. estStepSize: 'layout',
  17504. data: 'layout',
  17505. dataMin: 'layout',
  17506. dataMax: 'layout',
  17507. visibleMin: 'layout',
  17508. visibleMax: 'layout',
  17509. enlargeEstStepSizeByText: 'layout'
  17510. },
  17511. updaters: {
  17512. layout: 'layoutUpdater'
  17513. }
  17514. }
  17515. },
  17516. config: {
  17517. /**
  17518. * @cfg {Object} label
  17519. *
  17520. * The label configuration object for the Axis. This object may include style attributes
  17521. * like `spacing`, `padding`, `font` that receives a string or number and
  17522. * returns a new string with the modified values.
  17523. */
  17524. label: null,
  17525. /**
  17526. * @cfg {Number} labelOffset
  17527. * The distance between the label and the edge of a major tick.
  17528. * Only applicable for 'gauge' and 'angular' axes.
  17529. */
  17530. labelOffset: 10,
  17531. /**
  17532. * @cfg {Object|Ext.chart.axis.layout.Layout} layout The layout configuration used by
  17533. * the axis.
  17534. */
  17535. layout: null,
  17536. /**
  17537. * @cfg {Object|Ext.chart.axis.segmenter.Segmenter} segmenter The method of segmenter
  17538. * used by the axis.
  17539. */
  17540. segmenter: null,
  17541. /**
  17542. * @cfg {Function} renderer Allows direct customisation of rendered axis sprites.
  17543. */
  17544. renderer: null,
  17545. /**
  17546. * @private
  17547. * @cfg {Object} layoutContext Stores the context after calculating layout.
  17548. */
  17549. layoutContext: null,
  17550. /**
  17551. * @cfg {Ext.chart.axis.Axis} axis The axis represented by this sprite.
  17552. */
  17553. axis: null
  17554. },
  17555. thickness: 0,
  17556. stepSize: 0,
  17557. getBBox: function() {
  17558. return null;
  17559. },
  17560. defaultRenderer: function(v) {
  17561. // 'this' pointer in this case is a layoutContext
  17562. return this.segmenter.renderer(v, this);
  17563. },
  17564. layoutUpdater: function() {
  17565. var me = this,
  17566. chart = me.getAxis().getChart();
  17567. if (chart.isInitializing) {
  17568. return;
  17569. }
  17570. // eslint-disable-next-line vars-on-top, one-var
  17571. var attr = me.attr,
  17572. layout = me.getLayout(),
  17573. isRtl = chart.getInherited().rtl,
  17574. dataRange = attr.dataMax - attr.dataMin,
  17575. min = attr.dataMin + dataRange * attr.visibleMin,
  17576. max = attr.dataMin + dataRange * attr.visibleMax,
  17577. range = max - min,
  17578. position = attr.position,
  17579. context = {
  17580. attr: attr,
  17581. segmenter: me.getSegmenter(),
  17582. renderer: me.defaultRenderer
  17583. };
  17584. if (position === 'left' || position === 'right') {
  17585. attr.translationX = 0;
  17586. attr.translationY = max * attr.length / range;
  17587. attr.scalingX = 1;
  17588. attr.scalingY = -attr.length / range;
  17589. attr.scalingCenterY = 0;
  17590. attr.scalingCenterX = 0;
  17591. me.applyTransformations(true);
  17592. } else if (position === 'top' || position === 'bottom') {
  17593. if (isRtl) {
  17594. attr.translationX = attr.length + min * attr.length / range + 1;
  17595. } else {
  17596. attr.translationX = -min * attr.length / range;
  17597. }
  17598. attr.translationY = 0;
  17599. attr.scalingX = (isRtl ? -1 : 1) * attr.length / range;
  17600. attr.scalingY = 1;
  17601. attr.scalingCenterY = 0;
  17602. attr.scalingCenterX = 0;
  17603. me.applyTransformations(true);
  17604. }
  17605. if (layout) {
  17606. layout.calculateLayout(context);
  17607. me.setLayoutContext(context);
  17608. }
  17609. },
  17610. iterate: function(snaps, fn) {
  17611. var i, position, id, axis, floatingAxes, floatingValues,
  17612. some = Ext.Array.some,
  17613. abs = Math.abs,
  17614. threshold, isTickVisible;
  17615. if (snaps.getLabel) {
  17616. // Discrete layout.
  17617. if (snaps.min < snaps.from) {
  17618. fn.call(this, snaps.min, snaps.getLabel(snaps.min), -1, snaps);
  17619. }
  17620. for (i = 0; i <= snaps.steps; i++) {
  17621. fn.call(this, snaps.get(i), snaps.getLabel(i), i, snaps);
  17622. }
  17623. if (snaps.max > snaps.to) {
  17624. fn.call(this, snaps.max, snaps.getLabel(snaps.max), snaps.steps + 1, snaps);
  17625. }
  17626. } else {
  17627. axis = this.getAxis();
  17628. floatingAxes = axis.floatingAxes;
  17629. floatingValues = [];
  17630. threshold = (snaps.to - snaps.from) / (snaps.steps + 1);
  17631. if (axis.getFloating()) {
  17632. for (id in floatingAxes) {
  17633. floatingValues.push(floatingAxes[id]);
  17634. }
  17635. }
  17636. // Don't render ticks in axes intersection points.
  17637. isTickVisible = function(position) {
  17638. return !floatingValues.length || some(floatingValues, function(value) {
  17639. return abs(value - position) > threshold;
  17640. });
  17641. };
  17642. if (snaps.min < snaps.from && isTickVisible(snaps.min)) {
  17643. fn.call(this, snaps.min, snaps.min, -1, snaps);
  17644. }
  17645. for (i = 0; i <= snaps.steps; i++) {
  17646. position = snaps.get(i);
  17647. if (isTickVisible(position)) {
  17648. fn.call(this, position, position, i, snaps);
  17649. }
  17650. }
  17651. if (snaps.max > snaps.to && isTickVisible(snaps.max)) {
  17652. fn.call(this, snaps.max, snaps.max, snaps.steps + 1, snaps);
  17653. }
  17654. }
  17655. },
  17656. renderTicks: function(surface, ctx, layout, clipRect) {
  17657. var me = this,
  17658. attr = me.attr,
  17659. docked = attr.position,
  17660. matrix = attr.matrix,
  17661. halfLineWidth = 0.5 * attr.lineWidth,
  17662. xx = matrix.getXX(),
  17663. dx = matrix.getDX(),
  17664. yy = matrix.getYY(),
  17665. dy = matrix.getDY(),
  17666. majorTicks = layout.majorTicks,
  17667. majorTickSize = attr.majorTickSize,
  17668. minorTicks = layout.minorTicks,
  17669. minorTickSize = attr.minorTickSize,
  17670. gaugeAngles;
  17671. /* eslint-disable no-inner-declarations, no-case-declarations */
  17672. if (majorTicks) {
  17673. switch (docked) {
  17674. case 'right':
  17675. function getRightTickFn(size) {
  17676. return function(position, labelText, i) {
  17677. position = surface.roundPixel(position * yy + dy) + halfLineWidth;
  17678. ctx.moveTo(0, position);
  17679. ctx.lineTo(size, position);
  17680. };
  17681. };
  17682. me.iterate(majorTicks, getRightTickFn(majorTickSize));
  17683. if (minorTicks) {
  17684. me.iterate(minorTicks, getRightTickFn(minorTickSize));
  17685. };
  17686. break;
  17687. case 'left':
  17688. function getLeftTickFn(size) {
  17689. return function(position, labelText, i) {
  17690. position = surface.roundPixel(position * yy + dy) + halfLineWidth;
  17691. ctx.moveTo(clipRect[2] - size, position);
  17692. ctx.lineTo(clipRect[2], position);
  17693. };
  17694. };
  17695. me.iterate(majorTicks, getLeftTickFn(majorTickSize));
  17696. if (minorTicks) {
  17697. me.iterate(minorTicks, getLeftTickFn(minorTickSize));
  17698. };
  17699. break;
  17700. case 'bottom':
  17701. function getBottomTickFn(size) {
  17702. return function(position, labelText, i) {
  17703. position = surface.roundPixel(position * xx + dx) - halfLineWidth;
  17704. ctx.moveTo(position, 0);
  17705. ctx.lineTo(position, size);
  17706. };
  17707. };
  17708. me.iterate(majorTicks, getBottomTickFn(majorTickSize));
  17709. if (minorTicks) {
  17710. me.iterate(minorTicks, getBottomTickFn(minorTickSize));
  17711. };
  17712. break;
  17713. case 'top':
  17714. function getTopTickFn(size) {
  17715. return function(position, labelText, i) {
  17716. position = surface.roundPixel(position * xx + dx) - halfLineWidth;
  17717. ctx.moveTo(position, clipRect[3]);
  17718. ctx.lineTo(position, clipRect[3] - size);
  17719. };
  17720. };
  17721. me.iterate(majorTicks, getTopTickFn(majorTickSize));
  17722. if (minorTicks) {
  17723. me.iterate(minorTicks, getTopTickFn(minorTickSize));
  17724. };
  17725. break;
  17726. case 'angular':
  17727. me.iterate(majorTicks, function(position, labelText, i) {
  17728. position = position / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
  17729. ctx.moveTo(attr.centerX + (attr.length) * Math.cos(position), attr.centerY + (attr.length) * Math.sin(position));
  17730. ctx.lineTo(attr.centerX + (attr.length + majorTickSize) * Math.cos(position), attr.centerY + (attr.length + majorTickSize) * Math.sin(position));
  17731. });
  17732. break;
  17733. case 'gauge':
  17734. gaugeAngles = me.getGaugeAngles();
  17735. me.iterate(majorTicks, function(position, labelText, i) {
  17736. position = (position - attr.min) / (attr.max - attr.min) * attr.totalAngle - attr.totalAngle + gaugeAngles.start;
  17737. ctx.moveTo(attr.centerX + (attr.length) * Math.cos(position), attr.centerY + (attr.length) * Math.sin(position));
  17738. ctx.lineTo(attr.centerX + (attr.length + majorTickSize) * Math.cos(position), attr.centerY + (attr.length + majorTickSize) * Math.sin(position));
  17739. });
  17740. break;
  17741. }
  17742. }
  17743. },
  17744. /* eslint-enable no-inner-declarations, no-case-declarations */
  17745. renderLabels: function(surface, ctx, layoutContext, clipRect) {
  17746. var me = this,
  17747. attr = me.attr,
  17748. halfLineWidth = 0.5 * attr.lineWidth,
  17749. docked = attr.position,
  17750. matrix = attr.matrix,
  17751. textPadding = attr.textPadding,
  17752. xx = matrix.getXX(),
  17753. dx = matrix.getDX(),
  17754. yy = matrix.getYY(),
  17755. dy = matrix.getDY(),
  17756. thickness = 0,
  17757. majorTicks = layoutContext.majorTicks,
  17758. tickPadding = Math.max(attr.majorTickSize, attr.minorTickSize) + attr.lineWidth,
  17759. isBBoxIntersect = Ext.draw.Draw.isBBoxIntersect,
  17760. label = me.getLabel(),
  17761. font,
  17762. labelOffset = me.getLabelOffset(),
  17763. lastLabelText = null,
  17764. textSize = 0,
  17765. textCount = 0,
  17766. segmenter = layoutContext.segmenter,
  17767. renderer = me.getRenderer(),
  17768. axis = me.getAxis(),
  17769. title = axis.getTitle(),
  17770. titleBBox = title && title.attr.text !== '' && title.getBBox(),
  17771. labelInverseMatrix,
  17772. lastBBox = null,
  17773. bbox, fly, text, titlePadding, translation, gaugeAngles, angle;
  17774. if (majorTicks && label && !label.attr.hidden) {
  17775. font = label.attr.font;
  17776. if (ctx.font !== font) {
  17777. ctx.font = font;
  17778. }
  17779. // This can profoundly improve performance.
  17780. label.setAttributes({
  17781. translationX: 0,
  17782. translationY: 0
  17783. }, true);
  17784. label.applyTransformations();
  17785. labelInverseMatrix = label.attr.inverseMatrix.elements.slice(0);
  17786. switch (docked) {
  17787. case 'left':
  17788. titlePadding = titleBBox ? titleBBox.x + titleBBox.width : 0;
  17789. switch (label.attr.textAlign) {
  17790. case 'start':
  17791. translation = surface.roundPixel(titlePadding + dx) - halfLineWidth;
  17792. break;
  17793. case 'end':
  17794. translation = surface.roundPixel(clipRect[2] - tickPadding + dx) - halfLineWidth;
  17795. break;
  17796. default:
  17797. // 'center'
  17798. translation = surface.roundPixel(titlePadding + (clipRect[2] - titlePadding - tickPadding) / 2 + dx) - halfLineWidth;
  17799. };
  17800. label.setAttributes({
  17801. translationX: translation
  17802. }, true);
  17803. break;
  17804. case 'right':
  17805. titlePadding = titleBBox ? clipRect[2] - titleBBox.x : 0;
  17806. switch (label.attr.textAlign) {
  17807. case 'start':
  17808. translation = surface.roundPixel(tickPadding + dx) + halfLineWidth;
  17809. break;
  17810. case 'end':
  17811. translation = surface.roundPixel(clipRect[2] - titlePadding + dx) + halfLineWidth;
  17812. break;
  17813. default:
  17814. // 'center'
  17815. translation = surface.roundPixel(tickPadding + (clipRect[2] - tickPadding - titlePadding) / 2 + dx) + halfLineWidth;
  17816. };
  17817. label.setAttributes({
  17818. translationX: translation
  17819. }, true);
  17820. break;
  17821. case 'top':
  17822. titlePadding = titleBBox ? titleBBox.y + titleBBox.height : 0;
  17823. label.setAttributes({
  17824. translationY: surface.roundPixel(titlePadding + (clipRect[3] - titlePadding - tickPadding) / 2) - halfLineWidth
  17825. }, true);
  17826. break;
  17827. case 'bottom':
  17828. titlePadding = titleBBox ? clipRect[3] - titleBBox.y : 0;
  17829. label.setAttributes({
  17830. translationY: surface.roundPixel(tickPadding + (clipRect[3] - tickPadding - titlePadding) / 2) + halfLineWidth
  17831. }, true);
  17832. break;
  17833. case 'radial':
  17834. label.setAttributes({
  17835. translationX: attr.centerX
  17836. }, true);
  17837. break;
  17838. case 'angular':
  17839. label.setAttributes({
  17840. translationY: attr.centerY
  17841. }, true);
  17842. break;
  17843. case 'gauge':
  17844. label.setAttributes({
  17845. translationY: attr.centerY
  17846. }, true);
  17847. break;
  17848. }
  17849. // TODO: there are better ways to detect collision.
  17850. if (docked === 'left' || docked === 'right') {
  17851. me.iterate(majorTicks, function(position, labelText, i) {
  17852. if (labelText === undefined) {
  17853. return;
  17854. }
  17855. if (renderer) {
  17856. text = Ext.callback(renderer, null, [
  17857. axis,
  17858. labelText,
  17859. layoutContext,
  17860. lastLabelText
  17861. ], 0, axis);
  17862. } else {
  17863. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17864. }
  17865. lastLabelText = labelText;
  17866. label.setAttributes({
  17867. text: String(text),
  17868. translationY: surface.roundPixel(position * yy + dy)
  17869. }, true);
  17870. label.applyTransformations();
  17871. thickness = Math.max(thickness, label.getBBox().width + tickPadding);
  17872. fly = Ext.draw.Matrix.fly(label.attr.matrix.elements.slice(0));
  17873. bbox = fly.prepend.apply(fly, labelInverseMatrix).transformBBox(label.getBBox(true));
  17874. if (lastBBox && !isBBoxIntersect(bbox, lastBBox, textPadding)) {
  17875. return;
  17876. }
  17877. surface.renderSprite(label);
  17878. lastBBox = bbox;
  17879. textSize += bbox.height;
  17880. textCount++;
  17881. });
  17882. } else if (docked === 'top' || docked === 'bottom') {
  17883. me.iterate(majorTicks, function(position, labelText, i) {
  17884. if (labelText === undefined) {
  17885. return;
  17886. }
  17887. if (renderer) {
  17888. text = Ext.callback(renderer, null, [
  17889. axis,
  17890. labelText,
  17891. layoutContext,
  17892. lastLabelText
  17893. ], 0, axis);
  17894. } else {
  17895. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17896. }
  17897. lastLabelText = labelText;
  17898. label.setAttributes({
  17899. text: String(text),
  17900. translationX: surface.roundPixel(position * xx + dx)
  17901. }, true);
  17902. label.applyTransformations();
  17903. thickness = Math.max(thickness, label.getBBox().height + tickPadding);
  17904. fly = Ext.draw.Matrix.fly(label.attr.matrix.elements.slice(0));
  17905. bbox = fly.prepend.apply(fly, labelInverseMatrix).transformBBox(label.getBBox(true));
  17906. if (lastBBox && !isBBoxIntersect(bbox, lastBBox, textPadding)) {
  17907. return;
  17908. }
  17909. surface.renderSprite(label);
  17910. lastBBox = bbox;
  17911. textSize += bbox.width;
  17912. textCount++;
  17913. });
  17914. } else if (docked === 'radial') {
  17915. me.iterate(majorTicks, function(position, labelText, i) {
  17916. if (labelText === undefined) {
  17917. return;
  17918. }
  17919. if (renderer) {
  17920. text = Ext.callback(renderer, null, [
  17921. axis,
  17922. labelText,
  17923. layoutContext,
  17924. lastLabelText
  17925. ], 0, axis);
  17926. } else {
  17927. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17928. }
  17929. lastLabelText = labelText;
  17930. if (typeof text !== 'undefined') {
  17931. label.setAttributes({
  17932. text: String(text),
  17933. translationX: attr.centerX - surface.roundPixel(position) / attr.max * attr.length * Math.cos(attr.baseRotation + Math.PI / 2),
  17934. translationY: attr.centerY - surface.roundPixel(position) / attr.max * attr.length * Math.sin(attr.baseRotation + Math.PI / 2)
  17935. }, true);
  17936. label.applyTransformations();
  17937. bbox = label.attr.matrix.transformBBox(label.getBBox(true));
  17938. if (lastBBox && !isBBoxIntersect(bbox, lastBBox)) {
  17939. return;
  17940. }
  17941. surface.renderSprite(label);
  17942. lastBBox = bbox;
  17943. textSize += bbox.width;
  17944. textCount++;
  17945. }
  17946. });
  17947. } else if (docked === 'angular') {
  17948. labelOffset += attr.majorTickSize + attr.lineWidth * 0.5;
  17949. me.iterate(majorTicks, function(position, labelText, i) {
  17950. if (labelText === undefined) {
  17951. return;
  17952. }
  17953. if (renderer) {
  17954. text = Ext.callback(renderer, null, [
  17955. axis,
  17956. labelText,
  17957. layoutContext,
  17958. lastLabelText
  17959. ], 0, axis);
  17960. } else {
  17961. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17962. }
  17963. lastLabelText = labelText;
  17964. thickness = Math.max(thickness, Math.max(attr.majorTickSize, attr.minorTickSize) + (attr.lineCap !== 'butt' ? attr.lineWidth * 0.5 : 0));
  17965. if (typeof text !== 'undefined') {
  17966. angle = position / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
  17967. label.setAttributes({
  17968. text: String(text),
  17969. translationX: attr.centerX + (attr.length + labelOffset) * Math.cos(angle),
  17970. translationY: attr.centerY + (attr.length + labelOffset) * Math.sin(angle)
  17971. }, true);
  17972. label.applyTransformations();
  17973. bbox = label.attr.matrix.transformBBox(label.getBBox(true));
  17974. if (lastBBox && !isBBoxIntersect(bbox, lastBBox)) {
  17975. return;
  17976. }
  17977. surface.renderSprite(label);
  17978. lastBBox = bbox;
  17979. textSize += bbox.width;
  17980. textCount++;
  17981. }
  17982. });
  17983. } else if (docked === 'gauge') {
  17984. gaugeAngles = me.getGaugeAngles();
  17985. labelOffset += attr.majorTickSize + attr.lineWidth * 0.5;
  17986. me.iterate(majorTicks, function(position, labelText, i) {
  17987. if (labelText === undefined) {
  17988. return;
  17989. }
  17990. if (renderer) {
  17991. text = Ext.callback(renderer, null, [
  17992. axis,
  17993. labelText,
  17994. layoutContext,
  17995. lastLabelText
  17996. ], 0, axis);
  17997. } else {
  17998. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17999. }
  18000. lastLabelText = labelText;
  18001. if (typeof text !== 'undefined') {
  18002. angle = (position - attr.min) / (attr.max - attr.min) * attr.totalAngle - attr.totalAngle + gaugeAngles.start;
  18003. label.setAttributes({
  18004. text: String(text),
  18005. translationX: attr.centerX + (attr.length + labelOffset) * Math.cos(angle),
  18006. translationY: attr.centerY + (attr.length + labelOffset) * Math.sin(angle)
  18007. }, true);
  18008. label.applyTransformations();
  18009. bbox = label.attr.matrix.transformBBox(label.getBBox(true));
  18010. if (lastBBox && !isBBoxIntersect(bbox, lastBBox)) {
  18011. return;
  18012. }
  18013. surface.renderSprite(label);
  18014. lastBBox = bbox;
  18015. textSize += bbox.width;
  18016. textCount++;
  18017. }
  18018. });
  18019. }
  18020. if (attr.enlargeEstStepSizeByText && textCount) {
  18021. textSize /= textCount;
  18022. textSize += tickPadding;
  18023. textSize *= 2;
  18024. if (attr.estStepSize < textSize) {
  18025. attr.estStepSize = textSize;
  18026. }
  18027. }
  18028. if (Math.abs(me.thickness - thickness) > 1) {
  18029. me.thickness = thickness;
  18030. attr.bbox.plain.dirty = true;
  18031. attr.bbox.transform.dirty = true;
  18032. me.doThicknessChanged();
  18033. return false;
  18034. }
  18035. }
  18036. },
  18037. renderAxisLine: function(surface, ctx, layout, clipRect) {
  18038. var me = this,
  18039. attr = me.attr,
  18040. halfLineWidth = attr.lineWidth * 0.5,
  18041. docked = attr.position,
  18042. position, gaugeAngles;
  18043. if (attr.axisLine && attr.length) {
  18044. switch (docked) {
  18045. case 'left':
  18046. position = surface.roundPixel(clipRect[2]) - halfLineWidth;
  18047. ctx.moveTo(position, -attr.endGap);
  18048. ctx.lineTo(position, attr.length + attr.startGap + 1);
  18049. break;
  18050. case 'right':
  18051. ctx.moveTo(halfLineWidth, -attr.endGap);
  18052. ctx.lineTo(halfLineWidth, attr.length + attr.startGap + 1);
  18053. break;
  18054. case 'bottom':
  18055. ctx.moveTo(-attr.startGap, halfLineWidth);
  18056. ctx.lineTo(attr.length + attr.endGap, halfLineWidth);
  18057. break;
  18058. case 'top':
  18059. position = surface.roundPixel(clipRect[3]) - halfLineWidth;
  18060. ctx.moveTo(-attr.startGap, position);
  18061. ctx.lineTo(attr.length + attr.endGap, position);
  18062. break;
  18063. case 'angular':
  18064. ctx.moveTo(attr.centerX + attr.length, attr.centerY);
  18065. ctx.arc(attr.centerX, attr.centerY, attr.length, 0, Math.PI * 2, true);
  18066. break;
  18067. case 'gauge':
  18068. gaugeAngles = me.getGaugeAngles();
  18069. ctx.moveTo(attr.centerX + Math.cos(gaugeAngles.start) * attr.length, attr.centerY + Math.sin(gaugeAngles.start) * attr.length);
  18070. ctx.arc(attr.centerX, attr.centerY, attr.length, gaugeAngles.start, gaugeAngles.end, true);
  18071. break;
  18072. }
  18073. }
  18074. },
  18075. getGaugeAngles: function() {
  18076. var me = this,
  18077. angle = me.attr.totalAngle,
  18078. offset;
  18079. if (angle <= Math.PI) {
  18080. offset = (Math.PI - angle) * 0.5;
  18081. } else {
  18082. offset = -(Math.PI * 2 - angle) * 0.5;
  18083. }
  18084. offset = Math.PI * 2 - offset;
  18085. return {
  18086. start: offset,
  18087. end: offset - angle
  18088. };
  18089. },
  18090. renderGridLines: function(surface, ctx, layout, clipRect) {
  18091. var me = this,
  18092. axis = me.getAxis(),
  18093. attr = me.attr,
  18094. matrix = attr.matrix,
  18095. startGap = attr.startGap,
  18096. endGap = attr.endGap,
  18097. xx = matrix.getXX(),
  18098. yy = matrix.getYY(),
  18099. dx = matrix.getDX(),
  18100. dy = matrix.getDY(),
  18101. position = attr.position,
  18102. alignment = axis.getGridAlignment(),
  18103. majorTicks = layout.majorTicks,
  18104. anchor, j, lastAnchor;
  18105. if (attr.grid) {
  18106. if (majorTicks) {
  18107. if (position === 'left' || position === 'right') {
  18108. lastAnchor = attr.min * yy + dy + endGap + startGap;
  18109. me.iterate(majorTicks, function(position, labelText, i) {
  18110. anchor = position * yy + dy + endGap;
  18111. me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
  18112. y: anchor,
  18113. height: lastAnchor - anchor
  18114. }, j = i, true);
  18115. lastAnchor = anchor;
  18116. });
  18117. j++;
  18118. anchor = 0;
  18119. me.putMarker(alignment + '-' + (j % 2 ? 'odd' : 'even'), {
  18120. y: anchor,
  18121. height: lastAnchor - anchor
  18122. }, j, true);
  18123. } else if (position === 'top' || position === 'bottom') {
  18124. lastAnchor = attr.min * xx + dx + startGap;
  18125. if (startGap) {
  18126. me.putMarker(alignment + '-even', {
  18127. x: 0,
  18128. width: lastAnchor
  18129. }, -1, true);
  18130. }
  18131. me.iterate(majorTicks, function(position, labelText, i) {
  18132. anchor = position * xx + dx + startGap;
  18133. me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
  18134. x: anchor,
  18135. width: lastAnchor - anchor
  18136. }, j = i, true);
  18137. lastAnchor = anchor;
  18138. });
  18139. j++;
  18140. anchor = attr.length + attr.startGap + attr.endGap;
  18141. me.putMarker(alignment + '-' + (j % 2 ? 'odd' : 'even'), {
  18142. x: anchor,
  18143. width: lastAnchor - anchor
  18144. }, j, true);
  18145. } else if (position === 'radial') {
  18146. me.iterate(majorTicks, function(position, labelText, i) {
  18147. if (!position) {
  18148. return;
  18149. }
  18150. anchor = position / attr.max * attr.length;
  18151. me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
  18152. scalingX: anchor,
  18153. scalingY: anchor
  18154. }, i, true);
  18155. lastAnchor = anchor;
  18156. });
  18157. } else if (position === 'angular') {
  18158. me.iterate(majorTicks, function(position, labelText, i) {
  18159. if (!attr.length) {
  18160. return;
  18161. }
  18162. anchor = position / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
  18163. me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
  18164. rotationRads: anchor,
  18165. rotationCenterX: 0,
  18166. rotationCenterY: 0,
  18167. scalingX: attr.length,
  18168. scalingY: attr.length
  18169. }, i, true);
  18170. lastAnchor = anchor;
  18171. });
  18172. }
  18173. }
  18174. }
  18175. },
  18176. renderLimits: function(clipRect) {
  18177. var me = this,
  18178. attr = me.attr,
  18179. axis = me.getAxis(),
  18180. limits = Ext.Array.from(axis.getLimits());
  18181. if (!limits.length || attr.dataMin === attr.dataMax) {
  18182. if (axis.limits) {
  18183. axis.limits.titles.attr.hidden = true;
  18184. }
  18185. return;
  18186. }
  18187. // eslint-disable-next-line vars-on-top, one-var
  18188. var chart = axis.getChart(),
  18189. innerPadding = chart.getInnerPadding(),
  18190. limitsRect = axis.limits.surface.getRect(),
  18191. matrix = attr.matrix,
  18192. position = attr.position,
  18193. chain = Ext.Object.chain,
  18194. titles = axis.limits.titles,
  18195. titleBBox, titlePosition, titleFlip, limit, value, i, ln, x, y;
  18196. titles.attr.hidden = false;
  18197. titles.instances = [];
  18198. titles.position = 0;
  18199. if (position === 'left' || position === 'right') {
  18200. for (i = 0 , ln = limits.length; i < ln; i++) {
  18201. limit = chain(limits[i]);
  18202. if (!limit.line) {
  18203. limit.line = {};
  18204. }
  18205. value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
  18206. value = value * matrix.getYY() + matrix.getDY();
  18207. limit.line.y = value + innerPadding.top;
  18208. limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
  18209. me.putMarker('horizontal-limit-lines', limit.line, i, true);
  18210. if (limit.line.title) {
  18211. titles.add(limit.line.title);
  18212. titleBBox = titles.getBBoxFor(titles.position - 1);
  18213. titlePosition = limit.line.title.position || (position === 'left' ? 'start' : 'end');
  18214. switch (titlePosition) {
  18215. case 'start':
  18216. x = 10;
  18217. break;
  18218. case 'end':
  18219. x = limitsRect[2] - 10;
  18220. break;
  18221. case 'middle':
  18222. x = limitsRect[2] / 2;
  18223. break;
  18224. }
  18225. titles.setAttributesFor(titles.position - 1, {
  18226. x: x,
  18227. y: limit.line.y - titleBBox.height / 2,
  18228. textAlign: titlePosition,
  18229. fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle
  18230. });
  18231. }
  18232. }
  18233. } else if (position === 'top' || position === 'bottom') {
  18234. for (i = 0 , ln = limits.length; i < ln; i++) {
  18235. limit = chain(limits[i]);
  18236. if (!limit.line) {
  18237. limit.line = {};
  18238. }
  18239. value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
  18240. value = value * matrix.getXX() + matrix.getDX();
  18241. limit.line.x = value + innerPadding.left;
  18242. limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
  18243. me.putMarker('vertical-limit-lines', limit.line, i, true);
  18244. if (limit.line.title) {
  18245. titles.add(limit.line.title);
  18246. titleBBox = titles.getBBoxFor(titles.position - 1);
  18247. titlePosition = limit.line.title.position || (position === 'top' ? 'end' : 'start');
  18248. switch (titlePosition) {
  18249. case 'start':
  18250. y = limitsRect[3] - titleBBox.width / 2 - 10;
  18251. break;
  18252. case 'end':
  18253. y = titleBBox.width / 2 + 10;
  18254. break;
  18255. case 'middle':
  18256. y = limitsRect[3] / 2;
  18257. break;
  18258. }
  18259. titles.setAttributesFor(titles.position - 1, {
  18260. x: limit.line.x + titleBBox.height / 2,
  18261. y: y,
  18262. fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle,
  18263. rotationRads: Math.PI / 2
  18264. });
  18265. }
  18266. }
  18267. } else if (position === 'radial') {
  18268. for (i = 0 , ln = limits.length; i < ln; i++) {
  18269. limit = chain(limits[i]);
  18270. if (!limit.line) {
  18271. limit.line = {};
  18272. }
  18273. value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
  18274. if (value > attr.max) {
  18275. continue;
  18276. }
  18277. value = value / attr.max * attr.length;
  18278. limit.line.cx = attr.centerX;
  18279. limit.line.cy = attr.centerY;
  18280. limit.line.scalingX = value;
  18281. limit.line.scalingY = value;
  18282. limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
  18283. me.putMarker('circular-limit-lines', limit.line, i, true);
  18284. if (limit.line.title) {
  18285. titles.add(limit.line.title);
  18286. titleBBox = titles.getBBoxFor(titles.position - 1);
  18287. titles.setAttributesFor(titles.position - 1, {
  18288. x: attr.centerX,
  18289. y: attr.centerY - value - titleBBox.height / 2,
  18290. fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle
  18291. });
  18292. }
  18293. }
  18294. } else if (position === 'angular') {
  18295. for (i = 0 , ln = limits.length; i < ln; i++) {
  18296. limit = chain(limits[i]);
  18297. if (!limit.line) {
  18298. limit.line = {};
  18299. }
  18300. value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
  18301. value = value / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
  18302. limit.line.translationX = attr.centerX;
  18303. limit.line.translationY = attr.centerY;
  18304. limit.line.rotationRads = value;
  18305. limit.line.rotationCenterX = 0;
  18306. limit.line.rotationCenterY = 0;
  18307. limit.line.scalingX = attr.length;
  18308. limit.line.scalingY = attr.length;
  18309. limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
  18310. me.putMarker('radial-limit-lines', limit.line, i, true);
  18311. if (limit.line.title) {
  18312. titles.add(limit.line.title);
  18313. titleBBox = titles.getBBoxFor(titles.position - 1);
  18314. titleFlip = ((value > -0.5 * Math.PI && value < 0.5 * Math.PI) || (value > 1.5 * Math.PI && value < 2 * Math.PI)) ? 1 : -1;
  18315. titles.setAttributesFor(titles.position - 1, {
  18316. x: attr.centerX + 0.5 * attr.length * Math.cos(value) + titleFlip * titleBBox.height / 2 * Math.sin(value),
  18317. y: attr.centerY + 0.5 * attr.length * Math.sin(value) - titleFlip * titleBBox.height / 2 * Math.cos(value),
  18318. rotationRads: titleFlip === 1 ? value : value - Math.PI,
  18319. fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle
  18320. });
  18321. }
  18322. }
  18323. } else if (position === 'gauge') {}
  18324. },
  18325. // TODO
  18326. doThicknessChanged: function() {
  18327. var axis = this.getAxis();
  18328. if (axis) {
  18329. axis.onThicknessChanged();
  18330. }
  18331. },
  18332. render: function(surface, ctx, rect) {
  18333. var me = this,
  18334. layoutContext = me.getLayoutContext();
  18335. if (layoutContext) {
  18336. if (me.renderLabels(surface, ctx, layoutContext, rect) === false) {
  18337. return false;
  18338. }
  18339. ctx.beginPath();
  18340. me.renderTicks(surface, ctx, layoutContext, rect);
  18341. me.renderAxisLine(surface, ctx, layoutContext, rect);
  18342. me.renderGridLines(surface, ctx, layoutContext, rect);
  18343. me.renderLimits(rect);
  18344. ctx.stroke();
  18345. }
  18346. }
  18347. });
  18348. /**
  18349. * @abstract
  18350. * @class Ext.chart.axis.segmenter.Segmenter
  18351. *
  18352. * Interface for a segmenter in an Axis. A segmenter defines the operations you can do to a specific
  18353. * data type.
  18354. *
  18355. * See {@link Ext.chart.axis.Axis}.
  18356. *
  18357. */
  18358. Ext.define('Ext.chart.axis.segmenter.Segmenter', {
  18359. config: {
  18360. /**
  18361. * @cfg {Ext.chart.axis.Axis} axis The axis that the Segmenter is bound.
  18362. */
  18363. axis: null
  18364. },
  18365. constructor: function(config) {
  18366. this.initConfig(config);
  18367. },
  18368. /**
  18369. * This method formats the value.
  18370. *
  18371. * @param {*} value The value to format.
  18372. * @param {Object} context Axis layout context.
  18373. * @return {String}
  18374. */
  18375. renderer: function(value, context) {
  18376. return String(value);
  18377. },
  18378. /**
  18379. * Convert from any data into the target type.
  18380. * @param {*} value The value to convert from
  18381. * @return {*} The converted value.
  18382. */
  18383. from: function(value) {
  18384. return value;
  18385. },
  18386. /**
  18387. * @method
  18388. * Returns the difference between the min and max value based on the given unit scale.
  18389. *
  18390. * @param {*} min The smaller value.
  18391. * @param {*} max The larger value.
  18392. * @param {*} unit The unit scale. Unit can be any type.
  18393. * @return {Number} The number of `unit`s between min and max. It is the minimum n that
  18394. * min + n * unit >= max.
  18395. */
  18396. diff: Ext.emptyFn,
  18397. /**
  18398. * @method
  18399. * Align value with step of units.
  18400. * For example, for the date segmenter, if the unit is "Month" and step is 3, the value will be
  18401. * aligned by seasons.
  18402. *
  18403. * @param {*} value The value to be aligned.
  18404. * @param {Number} step The step of units.
  18405. * @param {*} unit The unit.
  18406. * @return {*} Aligned value.
  18407. */
  18408. align: Ext.emptyFn,
  18409. /**
  18410. * @method
  18411. * Add `step` `unit`s to the value.
  18412. * @param {*} value The value to be added.
  18413. * @param {Number} step The step of units. Negative value are allowed.
  18414. * @param {*} unit The unit.
  18415. */
  18416. add: Ext.emptyFn,
  18417. /**
  18418. * @method
  18419. * Given a start point and estimated step size of a range, determine the preferred step size.
  18420. *
  18421. * @param {*} start The start point of range.
  18422. * @param {*} estStepSize The estimated step size.
  18423. * @return {Object} Return the step size by an object of step x unit.
  18424. * @return {Number} return.step The step count of units.
  18425. * @return {Number|Object} return.unit The unit.
  18426. */
  18427. preferredStep: Ext.emptyFn
  18428. });
  18429. /**
  18430. * @class Ext.chart.axis.segmenter.Names
  18431. * @extends Ext.chart.axis.segmenter.Segmenter
  18432. *
  18433. * Names data type. Names will be calculated as their indices in the methods in this class.
  18434. * The `preferredStep` always return `{ unit: 1, step: 1 }` to indicate "show every item".
  18435. *
  18436. */
  18437. Ext.define('Ext.chart.axis.segmenter.Names', {
  18438. extend: 'Ext.chart.axis.segmenter.Segmenter',
  18439. alias: 'segmenter.names',
  18440. renderer: function(value, context) {
  18441. return value;
  18442. },
  18443. diff: function(min, max, unit) {
  18444. return Math.floor(max - min);
  18445. },
  18446. align: function(value, step, unit) {
  18447. return Math.floor(value);
  18448. },
  18449. add: function(value, step, unit) {
  18450. return value + step;
  18451. },
  18452. preferredStep: function(min, estStepSize, minIdx, data) {
  18453. return {
  18454. unit: 1,
  18455. step: 1
  18456. };
  18457. }
  18458. });
  18459. /**
  18460. * @class Ext.chart.axis.segmenter.Numeric
  18461. * @extends Ext.chart.axis.segmenter.Segmenter
  18462. *
  18463. * Numeric data type.
  18464. */
  18465. Ext.define('Ext.chart.axis.segmenter.Numeric', {
  18466. extend: 'Ext.chart.axis.segmenter.Segmenter',
  18467. alias: 'segmenter.numeric',
  18468. isNumeric: true,
  18469. renderer: function(value, context) {
  18470. return value.toFixed(Math.max(0, context.majorTicks.unit.fixes));
  18471. },
  18472. diff: function(min, max, unit) {
  18473. return Math.floor((max - min) / unit.scale);
  18474. },
  18475. align: function(value, step, unit) {
  18476. var scaledStep = unit.scale * step;
  18477. return Math.floor(value / scaledStep) * scaledStep;
  18478. },
  18479. add: function(value, step, unit) {
  18480. return value + step * unit.scale;
  18481. },
  18482. preferredStep: function(min, estStepSize) {
  18483. // Getting an order of magnitude of the estStepSize with a common logarithm.
  18484. var order = Math.floor(Math.log(estStepSize) * Math.LOG10E),
  18485. scale = Math.pow(10, order);
  18486. estStepSize /= scale;
  18487. if (estStepSize < 2) {
  18488. estStepSize = 2;
  18489. } else if (estStepSize < 5) {
  18490. estStepSize = 5;
  18491. } else if (estStepSize < 10) {
  18492. estStepSize = 10;
  18493. order++;
  18494. }
  18495. return {
  18496. unit: {
  18497. // When passed estStepSize is less than 1, its order of magnitude
  18498. // is equal to -number_of_leading_zeros in the estStepSize.
  18499. fixes: -order,
  18500. // Number of fractional digits.
  18501. scale: scale
  18502. },
  18503. step: estStepSize
  18504. };
  18505. },
  18506. leadingZeros: function(n) {
  18507. // For example:
  18508. // leadingZeros(0.2) is 1,
  18509. // leadingZeros(-0.01) is 2.
  18510. return -Math.floor(Ext.Number.log10(Math.abs(n)));
  18511. },
  18512. /**
  18513. * Wraps the provided estimated step size of a range without altering it into a step size
  18514. * object.
  18515. *
  18516. * @param {*} min The start point of range.
  18517. * @param {*} estStepSize The estimated step size.
  18518. * @return {Object} Return the step size by an object of step x unit.
  18519. * @return {Number} return.step The step count of units.
  18520. * @return {Object} return.unit The unit.
  18521. */
  18522. exactStep: function(min, estStepSize) {
  18523. var stepZeros = this.leadingZeros(estStepSize),
  18524. scale = Math.pow(10, stepZeros);
  18525. return {
  18526. unit: {
  18527. // add one decimal point if estStepSize is not a multiple of scale
  18528. fixes: stepZeros + (estStepSize % scale === 0 ? 0 : 1),
  18529. // Swap scale & step, if the estStepSize < 1,
  18530. // or 'diff' method will give us rounding errors.
  18531. scale: estStepSize < 1 ? estStepSize : 1
  18532. },
  18533. step: estStepSize < 1 ? 1 : estStepSize
  18534. };
  18535. },
  18536. adjustByMajorUnit: function(step, scale, range) {
  18537. var min = range[0],
  18538. max = range[1],
  18539. increment = step * scale,
  18540. remainder, multiplier;
  18541. multiplier = Math.max(1 / (min || 1), 1 / (increment || 1));
  18542. multiplier = multiplier > 1 ? multiplier : 1;
  18543. remainder = ((min * multiplier) % (increment * multiplier)) / multiplier;
  18544. if (remainder !== 0) {
  18545. range[0] = min - remainder + (min < 0 ? -increment : 0);
  18546. }
  18547. multiplier = Math.max(1 / (max || 1), 1 / (increment || 1));
  18548. multiplier = multiplier > 1 ? multiplier : 1;
  18549. remainder = ((max * multiplier) % (increment * multiplier)) / multiplier;
  18550. if (remainder !== 0) {
  18551. range[1] = max - remainder + (max > 0 ? increment : 0);
  18552. }
  18553. }
  18554. });
  18555. /**
  18556. * @class Ext.chart.axis.segmenter.Time
  18557. * @extends Ext.chart.axis.segmenter.Segmenter
  18558. *
  18559. * Time data type.
  18560. */
  18561. Ext.define('Ext.chart.axis.segmenter.Time', {
  18562. extend: 'Ext.chart.axis.segmenter.Segmenter',
  18563. alias: 'segmenter.time',
  18564. config: {
  18565. /**
  18566. * @cfg {Object} step
  18567. * @cfg {String} step.unit The unit of the step (Ext.Date.DAY, Ext.Date.MONTH, etc).
  18568. * @cfg {Number} step.step The number of units for the step (1, 2, etc).
  18569. * If specified, will override the result of {@link #preferredStep}.
  18570. * For example:
  18571. *
  18572. * step: {
  18573. * unit: Ext.Date.HOUR,
  18574. * step: 1
  18575. * }
  18576. */
  18577. step: null
  18578. },
  18579. renderer: function(value, context) {
  18580. var ExtDate = Ext.Date;
  18581. switch (context.majorTicks.unit) {
  18582. case 'y':
  18583. return ExtDate.format(value, 'Y');
  18584. case 'mo':
  18585. return ExtDate.format(value, 'Y-m');
  18586. case 'd':
  18587. return ExtDate.format(value, 'Y-m-d');
  18588. }
  18589. return ExtDate.format(value, 'Y-m-d\nH:i:s');
  18590. },
  18591. from: function(value) {
  18592. return new Date(value);
  18593. },
  18594. diff: function(min, max, unit) {
  18595. if (isFinite(min)) {
  18596. min = new Date(min);
  18597. }
  18598. if (isFinite(max)) {
  18599. max = new Date(max);
  18600. }
  18601. return Ext.Date.diff(min, max, unit);
  18602. },
  18603. updateStep: function() {
  18604. var axis = this.getAxis();
  18605. if (axis && !this.isConfiguring) {
  18606. axis.performLayout();
  18607. }
  18608. },
  18609. align: function(date, step, unit) {
  18610. if (unit === 'd' && step >= 7) {
  18611. date = Ext.Date.align(date, 'd', step);
  18612. date.setDate(date.getDate() - date.getDay() + 1);
  18613. return date;
  18614. } else {
  18615. return Ext.Date.align(date, unit, step);
  18616. }
  18617. },
  18618. add: function(value, step, unit) {
  18619. return Ext.Date.add(new Date(value), unit, step);
  18620. },
  18621. timeBuckets: [
  18622. {
  18623. unit: Ext.Date.YEAR,
  18624. steps: [
  18625. 1,
  18626. 2,
  18627. 5,
  18628. 10,
  18629. 20,
  18630. 50,
  18631. 100,
  18632. 200,
  18633. 500
  18634. ]
  18635. },
  18636. {
  18637. unit: Ext.Date.MONTH,
  18638. steps: [
  18639. 1,
  18640. 3,
  18641. 6
  18642. ]
  18643. },
  18644. {
  18645. unit: Ext.Date.DAY,
  18646. steps: [
  18647. 1,
  18648. 7,
  18649. 14
  18650. ]
  18651. },
  18652. {
  18653. unit: Ext.Date.HOUR,
  18654. steps: [
  18655. 1,
  18656. 6,
  18657. 12
  18658. ]
  18659. },
  18660. {
  18661. unit: Ext.Date.MINUTE,
  18662. steps: [
  18663. 1,
  18664. 5,
  18665. 15,
  18666. 30
  18667. ]
  18668. },
  18669. {
  18670. unit: Ext.Date.SECOND,
  18671. steps: [
  18672. 1,
  18673. 5,
  18674. 15,
  18675. 30
  18676. ]
  18677. },
  18678. {
  18679. unit: Ext.Date.MILLI,
  18680. steps: [
  18681. 1,
  18682. 2,
  18683. 5,
  18684. 10,
  18685. 20,
  18686. 50,
  18687. 100,
  18688. 200,
  18689. 500
  18690. ]
  18691. }
  18692. ],
  18693. /**
  18694. * @private
  18695. * Takes a time interval and figures out what is the smallest nice number of which
  18696. * units (years, months, days, etc.) that can fully encompass that interval.
  18697. * @param {Date} min
  18698. * @param {Date} max
  18699. * @return {Object}
  18700. * @return {String} return.unit The unit.
  18701. * @return {Number} return.step The number of units.
  18702. */
  18703. getTimeBucket: function(min, max) {
  18704. var buckets = this.timeBuckets,
  18705. unit, unitCount, steps, step, result, i, j;
  18706. for (i = 0; i < buckets.length; i++) {
  18707. unit = buckets[i].unit;
  18708. unitCount = this.diff(min, max, unit);
  18709. if (unitCount > 0) {
  18710. steps = buckets[i].steps;
  18711. for (j = 0; j < steps.length; j++) {
  18712. step = steps[j];
  18713. if (unitCount <= step) {
  18714. break;
  18715. }
  18716. }
  18717. result = {
  18718. unit: unit,
  18719. step: step
  18720. };
  18721. break;
  18722. }
  18723. }
  18724. // If the interval is smaller then one millisecond ...
  18725. if (!result) {
  18726. // ... we can't go smaller than one millisecond.
  18727. result = {
  18728. unit: Ext.Date.MILLI,
  18729. step: 1
  18730. };
  18731. }
  18732. return result;
  18733. },
  18734. preferredStep: function(min, estStepSize) {
  18735. var step = this.getStep();
  18736. return step ? step : this.getTimeBucket(new Date(+min), new Date(+min + Math.ceil(estStepSize)));
  18737. }
  18738. });
  18739. /**
  18740. * @abstract
  18741. * @class Ext.chart.axis.layout.Layout
  18742. *
  18743. * Interface used by Axis to process its data into a meaningful layout.
  18744. */
  18745. Ext.define('Ext.chart.axis.layout.Layout', {
  18746. mixins: {
  18747. observable: 'Ext.mixin.Observable'
  18748. },
  18749. config: {
  18750. /**
  18751. * @cfg {Ext.chart.axis.Axis} axis The axis that the Layout is bound.
  18752. */
  18753. axis: null
  18754. },
  18755. constructor: function(config) {
  18756. this.mixins.observable.constructor.call(this, config);
  18757. },
  18758. /**
  18759. * Processes the data of the series bound to the axis.
  18760. * @param {Ext.chart.series.Series} series The bound series.
  18761. */
  18762. processData: function(series) {
  18763. var me = this,
  18764. axis = me.getAxis(),
  18765. direction = axis.getDirection(),
  18766. boundSeries = axis.boundSeries,
  18767. i, ln;
  18768. if (series) {
  18769. series['coordinate' + direction]();
  18770. } else {
  18771. for (i = 0 , ln = boundSeries.length; i < ln; i++) {
  18772. boundSeries[i]['coordinate' + direction]();
  18773. }
  18774. }
  18775. },
  18776. /**
  18777. * Calculates the position of major ticks for the axis.
  18778. * @param {Object} context
  18779. */
  18780. calculateMajorTicks: function(context) {
  18781. var me = this,
  18782. attr = context.attr,
  18783. range = attr.max - attr.min,
  18784. zoom = range / Math.max(1, attr.length) * (attr.visibleMax - attr.visibleMin),
  18785. viewMin = attr.min + range * attr.visibleMin,
  18786. viewMax = attr.min + range * attr.visibleMax,
  18787. estStepSize = attr.estStepSize * zoom,
  18788. majorTicks = me.snapEnds(context, attr.min, attr.max, estStepSize);
  18789. if (majorTicks) {
  18790. me.trimByRange(context, majorTicks, viewMin, viewMax);
  18791. context.majorTicks = majorTicks;
  18792. }
  18793. },
  18794. /**
  18795. * Calculates the position of sub ticks for the axis.
  18796. * @param {Object} context
  18797. */
  18798. calculateMinorTicks: function(context) {
  18799. if (this.snapMinorEnds) {
  18800. context.minorTicks = this.snapMinorEnds(context);
  18801. }
  18802. },
  18803. /**
  18804. * Calculates the position of tick marks for the axis.
  18805. * @param {Object} context
  18806. * @return {*}
  18807. */
  18808. calculateLayout: function(context) {
  18809. var me = this,
  18810. attr = context.attr;
  18811. if (attr.length === 0) {
  18812. return null;
  18813. }
  18814. if (attr.majorTicks) {
  18815. me.calculateMajorTicks(context);
  18816. if (attr.minorTicks) {
  18817. me.calculateMinorTicks(context);
  18818. }
  18819. }
  18820. },
  18821. /**
  18822. * @method
  18823. * Snaps the data bound to the axis to meaningful tick marks.
  18824. * @param {Object} context
  18825. * @param {Number} min
  18826. * @param {Number} max
  18827. * @param {Number} estStepSize
  18828. */
  18829. snapEnds: Ext.emptyFn,
  18830. /**
  18831. * Trims the layout of the axis by the defined minimum and maximum.
  18832. * @param {Object} context
  18833. * @param {Object} ticks Ticks object (e.g. major ticks) to be modified.
  18834. * @param {Number} trimMin
  18835. * @param {Number} trimMax
  18836. */
  18837. trimByRange: function(context, ticks, trimMin, trimMax) {
  18838. var segmenter = context.segmenter,
  18839. unit = ticks.unit,
  18840. beginIdx = segmenter.diff(ticks.from, trimMin, unit),
  18841. endIdx = segmenter.diff(ticks.from, trimMax, unit),
  18842. begin = Math.max(0, Math.ceil(beginIdx / ticks.step)),
  18843. end = Math.min(ticks.steps, Math.floor(endIdx / ticks.step));
  18844. if (end < ticks.steps) {
  18845. ticks.to = segmenter.add(ticks.from, end * ticks.step, unit);
  18846. }
  18847. if (ticks.max > trimMax) {
  18848. ticks.max = ticks.to;
  18849. }
  18850. if (ticks.from < trimMin) {
  18851. ticks.from = segmenter.add(ticks.from, begin * ticks.step, unit);
  18852. while (ticks.from < trimMin) {
  18853. begin++;
  18854. ticks.from = segmenter.add(ticks.from, ticks.step, unit);
  18855. }
  18856. }
  18857. if (ticks.min < trimMin) {
  18858. ticks.min = ticks.from;
  18859. }
  18860. ticks.steps = end - begin;
  18861. }
  18862. });
  18863. /**
  18864. * @class Ext.chart.axis.layout.Discrete
  18865. * @extends Ext.chart.axis.layout.Layout
  18866. *
  18867. * Simple processor for data that cannot be interpolated.
  18868. */
  18869. Ext.define('Ext.chart.axis.layout.Discrete', {
  18870. extend: 'Ext.chart.axis.layout.Layout',
  18871. alias: 'axisLayout.discrete',
  18872. isDiscrete: true,
  18873. processData: function() {
  18874. var me = this,
  18875. axis = me.getAxis(),
  18876. seriesList = axis.boundSeries,
  18877. direction = axis.getDirection(),
  18878. i, ln, series;
  18879. me.labels = [];
  18880. me.labelMap = {};
  18881. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  18882. series = seriesList[i];
  18883. if (series['get' + direction + 'Axis']() === axis) {
  18884. series['coordinate' + direction]();
  18885. }
  18886. }
  18887. // About the labels on Category axes (aka. axes with a Discrete layout)...
  18888. //
  18889. // When the data set from the store changes, series.processData() is called, which does
  18890. // its thing at the series level and then calls series.updateLabelData() to update
  18891. // the labels in the sprites that belong to the series. At the same time,
  18892. // series.processData() calls axis.processData(), which also does its thing but at the axis
  18893. // level, and also needs to update the labels for the sprite(s) that belong to the axis.
  18894. // This is not that simple, however. So how are the axis labels rendered?
  18895. // First, axis.sprite.Axis.render() calls renderLabels() which obtains the majorTicks
  18896. // from the axis.layout and iterate() through them. The majorTicks are an object returned
  18897. // by snapEnds() below which provides a getLabel() function that returns the label
  18898. // from the axis.layoutContext.data array. So now the question is: how are the labels
  18899. // transferred from the axis.layout to the axis.layoutContext?
  18900. // The easy response is: it's in calculateLayout() below. The issue is to call
  18901. // calculateLayout() because it takes in an axis.layoutContext that can only be created
  18902. // in axis.sprite.Axis.layoutUpdater(), which is a private "updater" function that is
  18903. // called by all the sprite's "triggers". Of course, we don't want to call layoutUpdater()
  18904. // directly from here, so instead we update the sprite's data attribute, which sets
  18905. // the trigger which calls layoutUpdater() which calls calculateLayout() etc...
  18906. // Note that the sprite's data attribute could be set to any value and it would still result
  18907. // in the trigger we need. For consistency, however, it is set to the labels.
  18908. axis.getSprites()[0].setAttributes({
  18909. data: me.labels
  18910. });
  18911. me.fireEvent('datachange', me.labels);
  18912. },
  18913. /**
  18914. * @method calculateLayout
  18915. * @inheritdoc
  18916. */
  18917. calculateLayout: function(context) {
  18918. context.data = this.labels;
  18919. this.callParent([
  18920. context
  18921. ]);
  18922. },
  18923. /**
  18924. * @method calculateMajorTicks
  18925. * @inheritdoc
  18926. */
  18927. calculateMajorTicks: function(context) {
  18928. var me = this,
  18929. attr = context.attr,
  18930. data = context.data,
  18931. range = attr.max - attr.min,
  18932. viewMin = attr.min + range * attr.visibleMin,
  18933. viewMax = attr.min + range * attr.visibleMax,
  18934. out;
  18935. out = me.snapEnds(context, Math.max(0, attr.min), Math.min(attr.max, data.length - 1), 1);
  18936. if (out) {
  18937. me.trimByRange(context, out, viewMin, viewMax);
  18938. context.majorTicks = out;
  18939. }
  18940. },
  18941. /**
  18942. * @method snapEnds
  18943. * @inheritdoc
  18944. */
  18945. snapEnds: function(context, min, max, estStepSize) {
  18946. var data = context.data,
  18947. steps;
  18948. estStepSize = Math.ceil(estStepSize);
  18949. steps = Math.floor((max - min) / estStepSize);
  18950. return {
  18951. min: min,
  18952. max: max,
  18953. from: min,
  18954. to: steps * estStepSize + min,
  18955. step: estStepSize,
  18956. steps: steps,
  18957. unit: 1,
  18958. getLabel: function(currentStep) {
  18959. return data[this.from + this.step * currentStep];
  18960. },
  18961. get: function(currentStep) {
  18962. return this.from + this.step * currentStep;
  18963. }
  18964. };
  18965. },
  18966. /**
  18967. * @method trimByRange
  18968. * @inheritdoc
  18969. */
  18970. trimByRange: function(context, out, trimMin, trimMax) {
  18971. var unit = out.unit,
  18972. beginIdx = Math.ceil((trimMin - out.from) / unit) * unit,
  18973. endIdx = Math.floor((trimMax - out.from) / unit) * unit,
  18974. begin = Math.max(0, Math.ceil(beginIdx / out.step)),
  18975. end = Math.min(out.steps, Math.floor(endIdx / out.step));
  18976. if (end < out.steps) {
  18977. out.to = end;
  18978. }
  18979. if (out.max > trimMax) {
  18980. out.max = out.to;
  18981. }
  18982. if (out.from < trimMin && out.step > 0) {
  18983. out.from = out.from + begin * out.step * unit;
  18984. while (out.from < trimMin) {
  18985. begin++;
  18986. out.from += out.step * unit;
  18987. }
  18988. }
  18989. if (out.min < trimMin) {
  18990. out.min = out.from;
  18991. }
  18992. out.steps = end - begin;
  18993. },
  18994. getCoordFor: function(value, field, idx, items) {
  18995. this.labels.push(value);
  18996. return this.labels.length - 1;
  18997. }
  18998. });
  18999. /**
  19000. * Discrete layout that combines duplicate data points only if they have the same index.
  19001. * For example:
  19002. *
  19003. * @example
  19004. * Ext.create({
  19005. * xtype: 'cartesian',
  19006. * title: 'Weight vs Calories',
  19007. *
  19008. * renderTo: document.body,
  19009. * width: 400,
  19010. * height: 400,
  19011. *
  19012. * store: {
  19013. * fields: ['month', 'weight', 'calories'],
  19014. * data: [
  19015. * {
  19016. * month: 'Jan',
  19017. * weight: 185,
  19018. * calories: 2650
  19019. * },
  19020. * {
  19021. * month: 'Jan',
  19022. * weight: 188,
  19023. * calories: 2800
  19024. * },
  19025. * {
  19026. * month: 'Feb',
  19027. * weight: 188,
  19028. * calories: 2800
  19029. * },
  19030. * {
  19031. * month: 'Mar',
  19032. * weight: 191,
  19033. * calories: 2800
  19034. * },
  19035. * {
  19036. * month: 'Apr',
  19037. * weight: 189,
  19038. * calories: 1500
  19039. * },
  19040. * {
  19041. * month: 'May',
  19042. * weight: 187,
  19043. * calories: 1350
  19044. * }
  19045. * ]
  19046. * },
  19047. *
  19048. * axes: [{
  19049. * type: 'numeric',
  19050. * position: 'left',
  19051. * fields: ['weight'],
  19052. * minimum: 140
  19053. * }, {
  19054. * type: 'numeric',
  19055. * position: 'right',
  19056. * fields: ['calories'],
  19057. * minimum: 500,
  19058. * maximum: 3500
  19059. * }, {
  19060. * type: 'category',
  19061. * grid: true,
  19062. * layout: 'combineByIndex',
  19063. * fields: 'month',
  19064. * position: 'bottom',
  19065. * label: {
  19066. * rotate: {
  19067. * degrees: -45
  19068. * }
  19069. * }
  19070. * }],
  19071. *
  19072. * series: [{
  19073. * type: 'line',
  19074. * title: 'Weight',
  19075. * xField: 'month',
  19076. * yField: 'weight',
  19077. * smooth: true,
  19078. * marker: true
  19079. * }, {
  19080. * type: 'line',
  19081. * title: 'Calories',
  19082. * xField: 'month',
  19083. * yField: 'calories',
  19084. * smooth: true,
  19085. * marker: true
  19086. * }],
  19087. *
  19088. * legend: {
  19089. * docked: 'bottom'
  19090. * }
  19091. *
  19092. * });
  19093. *
  19094. * @since 6.5.0
  19095. */
  19096. Ext.define('Ext.chart.axis.layout.CombineByIndex', {
  19097. extend: 'Ext.chart.axis.layout.Discrete',
  19098. alias: 'axisLayout.combineByIndex',
  19099. getCoordFor: function(value, field, idx, items) {
  19100. var labels = this.labels,
  19101. result = idx;
  19102. if (labels[idx] !== value) {
  19103. result = labels.push(value) - 1;
  19104. }
  19105. return result;
  19106. }
  19107. });
  19108. /**
  19109. * Discrete processor that combines duplicate data points.
  19110. */
  19111. Ext.define('Ext.chart.axis.layout.CombineDuplicate', {
  19112. extend: 'Ext.chart.axis.layout.Discrete',
  19113. alias: 'axisLayout.combineDuplicate',
  19114. getCoordFor: function(value, field, idx, items) {
  19115. var result;
  19116. if (!(value in this.labelMap)) {
  19117. result = this.labelMap[value] = this.labels.length;
  19118. this.labels.push(value);
  19119. return result;
  19120. }
  19121. return this.labelMap[value];
  19122. }
  19123. });
  19124. /**
  19125. * @class Ext.chart.axis.layout.Continuous
  19126. * @extends Ext.chart.axis.layout.Layout
  19127. *
  19128. * Processor for axis data that can be interpolated.
  19129. */
  19130. Ext.define('Ext.chart.axis.layout.Continuous', {
  19131. extend: 'Ext.chart.axis.layout.Layout',
  19132. alias: 'axisLayout.continuous',
  19133. isContinuous: true,
  19134. config: {
  19135. adjustMinimumByMajorUnit: false,
  19136. adjustMaximumByMajorUnit: false
  19137. },
  19138. getCoordFor: function(value, field, idx, items) {
  19139. return +value;
  19140. },
  19141. /**
  19142. * @method snapEnds
  19143. * @inheritdoc
  19144. */
  19145. snapEnds: function(context, min, max, estStepSize) {
  19146. var segmenter = context.segmenter,
  19147. axis = this.getAxis(),
  19148. noAnimation = !axis.spriteAnimationCount,
  19149. majorTickSteps = axis.getMajorTickSteps(),
  19150. // if specific number of steps requested and the segmenter supports such segmentation
  19151. bucket = majorTickSteps && segmenter.exactStep ? segmenter.exactStep(min, (max - min) / majorTickSteps) : segmenter.preferredStep(min, estStepSize),
  19152. unit = bucket.unit,
  19153. step = bucket.step,
  19154. diffSteps = segmenter.diff(min, max, unit),
  19155. steps = (majorTickSteps || diffSteps) + 1,
  19156. from;
  19157. // If 'majorTickSteps' config of the axis is set (is not 0), it means that
  19158. // we want to split the range at that number of equal intervals (segmenter.exactStep),
  19159. // and don't care if the resulting ticks are at nice round values or not.
  19160. // So 'from' (aligned) step is equal to 'min' (unaligned step).
  19161. // And 'to' is equal to 'max'.
  19162. //
  19163. // Another case where this is possible, is when the range between 'min' and
  19164. // 'max' can be represented by n steps, where n is an integer.
  19165. // For example, if the data values are [7, 17, 27, 37, 47], the data step is 10
  19166. // and, if the calculated tick step (segmenter.preferredStep) is also 10,
  19167. // there is no need to segmenter.align the 'min' to 0, so that the ticks are at
  19168. // [0, 10, 20, 30, 40, 50], because the data points are already perfectly
  19169. // spaced, so the ticks can be exactly at the data points without runing the
  19170. // aesthetics.
  19171. //
  19172. // The 'noAnimation' check is required to prevent EXTJS-25413 from happening.
  19173. // The segmentation described above is ideal for a static chart, but produces
  19174. // unwanted effects during animation.
  19175. if (majorTickSteps || (noAnimation && +segmenter.add(min, diffSteps, unit) === max)) {
  19176. from = min;
  19177. } else {
  19178. from = segmenter.align(min, step, unit);
  19179. }
  19180. return {
  19181. // min/max are NOT aligned to step
  19182. min: segmenter.from(min),
  19183. max: segmenter.from(max),
  19184. // from/to are aligned to step
  19185. from: from,
  19186. to: segmenter.add(from, steps, unit),
  19187. step: step,
  19188. steps: steps,
  19189. unit: unit,
  19190. get: function(currentStep) {
  19191. return segmenter.add(this.from, this.step * currentStep, this.unit);
  19192. }
  19193. };
  19194. },
  19195. snapMinorEnds: function(context) {
  19196. var majorTicks = context.majorTicks,
  19197. minorTickSteps = this.getAxis().getMinorTickSteps(),
  19198. segmenter = context.segmenter,
  19199. min = majorTicks.min,
  19200. max = majorTicks.max,
  19201. from = majorTicks.from,
  19202. unit = majorTicks.unit,
  19203. step = majorTicks.step / minorTickSteps,
  19204. scaledStep = step * unit.scale,
  19205. fromMargin = from - min,
  19206. offset = Math.floor(fromMargin / scaledStep),
  19207. extraSteps = offset + Math.floor((max - majorTicks.to) / scaledStep) + 1,
  19208. steps = majorTicks.steps * minorTickSteps + extraSteps;
  19209. return {
  19210. min: min,
  19211. max: max,
  19212. from: min + fromMargin % scaledStep,
  19213. to: segmenter.add(from, steps * step, unit),
  19214. step: step,
  19215. steps: steps,
  19216. unit: unit,
  19217. get: function(current) {
  19218. // don't render minor tick in major tick position
  19219. return (current % minorTickSteps + offset + 1 !== 0) ? segmenter.add(this.from, this.step * current, unit) : null;
  19220. }
  19221. };
  19222. }
  19223. });
  19224. /**
  19225. * @class Ext.chart.axis.Axis
  19226. *
  19227. * Defines axis for charts.
  19228. *
  19229. * Using the current model, the type of axis can be easily extended. By default, Sencha Charts
  19230. * provide three different types of axis:
  19231. *
  19232. * * **numeric** - the data attached to this axis is numeric and continuous.
  19233. * * **time** - the data attached to this axis is (or gets converted into) a date/time value;
  19234. * it is continuous.
  19235. * * **category** - the data attached to this axis belongs to a finite set. The data points
  19236. * are evenly placed along the axis.
  19237. *
  19238. * The behavior of an axis can be easily changed by setting different types of axis layout and
  19239. * axis segmenter to the axis.
  19240. *
  19241. * Axis layout defines how the data points are placed. Using continuous layout, the data points
  19242. * will be distributed by the numeric value. Using discrete layout the data points will be spaced
  19243. * evenly. Furthermore, if you want to combine the data points with the duplicate values in a
  19244. * discrete layout, you should use combineDuplicate layout.
  19245. *
  19246. * Segmenter defines the way to segment data range. For example, if you have a Date-type data range
  19247. * from Jan 1, 1997 to Jan 1, 2017, the segmenter will segement the data range into years, months or
  19248. * days based on the current zooming level.
  19249. *
  19250. * It is possible to write custom axis layouts and segmenters to extends this behavior by simply
  19251. * implementing interfaces {@link Ext.chart.axis.layout.Layout} and
  19252. * {@link Ext.chart.axis.segmenter.Segmenter}.
  19253. *
  19254. * Here's an example for the axes part of a chart definition:
  19255. * An example of axis for a series (in this case for an area chart that has multiple layers of
  19256. * yFields) could be:
  19257. *
  19258. * axes: [{
  19259. * type: 'numeric',
  19260. * position: 'left',
  19261. * title: 'Number of Hits',
  19262. * grid: {
  19263. * odd: {
  19264. * opacity: 1,
  19265. * fill: '#ddd',
  19266. * stroke: '#bbb',
  19267. * lineWidth: 1
  19268. * }
  19269. * },
  19270. * minimum: 0
  19271. * }, {
  19272. * type: 'category',
  19273. * position: 'bottom',
  19274. * title: 'Month of the Year',
  19275. * grid: true,
  19276. * label: {
  19277. * rotate: {
  19278. * degrees: 315
  19279. * }
  19280. * }
  19281. * }]
  19282. *
  19283. * In this case we use a `numeric` axis for displaying the values of the Area series and a
  19284. * `category` axis for displaying the names of the store elements. The numeric axis is placed
  19285. * on the left of the screen, while the category axis is placed at the bottom of the chart.
  19286. * Both the category and numeric axes have `grid` set, which means that horizontal and vertical
  19287. * lines will cover the chart background. In the category axis the labels will be rotated so
  19288. * they can fit the space better.
  19289. */
  19290. Ext.define('Ext.chart.axis.Axis', {
  19291. xtype: 'axis',
  19292. mixins: {
  19293. observable: 'Ext.mixin.Observable'
  19294. },
  19295. requires: [
  19296. 'Ext.chart.axis.sprite.Axis',
  19297. 'Ext.chart.axis.segmenter.*',
  19298. 'Ext.chart.axis.layout.*',
  19299. 'Ext.chart.Util'
  19300. ],
  19301. isAxis: true,
  19302. /**
  19303. * @event rangechange
  19304. * Fires when the {@link Ext.chart.axis.Axis#range range} of the axis changes.
  19305. * @param {Ext.chart.axis.Axis} axis
  19306. * @param {Array} range
  19307. * @param {Array} oldRange
  19308. */
  19309. /**
  19310. * @event visiblerangechange
  19311. * Fires when the {@link #visibleRange} of the axis changes.
  19312. * @param {Ext.chart.axis.Axis} axis
  19313. * @param {Array} visibleRange
  19314. */
  19315. /**
  19316. * @cfg {String} id
  19317. * The **unique** id of this axis instance.
  19318. */
  19319. config: {
  19320. /**
  19321. * @cfg {String} position
  19322. * Where to set the axis. Available options are `left`, `bottom`, `right`, `top`, `radial`,
  19323. * and `angular`.
  19324. */
  19325. position: 'bottom',
  19326. /**
  19327. * @cfg {Array} fields
  19328. * An array containing the names of the record fields which should be mapped along the axis.
  19329. * This is optional if the binding between series and fields is clear.
  19330. */
  19331. fields: [],
  19332. /**
  19333. * @cfg {Object} label
  19334. *
  19335. * The label configuration object for the Axis. This object may include style attributes
  19336. * like `spacing`, `padding`, `font` that receives a string or number and
  19337. * returns a new string with the modified values.
  19338. *
  19339. * For more supported values, see the configurations for {@link Ext.chart.sprite.Label}.
  19340. */
  19341. label: undefined,
  19342. /**
  19343. * @cfg {Object} grid
  19344. * The grid configuration object for the Axis style. Can contain `stroke` or `fill`
  19345. * attributes. Also may contain an `odd` or `even` property in which you only style things
  19346. * on odd or even rows. For example:
  19347. *
  19348. *
  19349. * grid {
  19350. * odd: {
  19351. * stroke: '#555'
  19352. * },
  19353. * even: {
  19354. * stroke: '#ccc'
  19355. * }
  19356. * }
  19357. */
  19358. grid: false,
  19359. /**
  19360. * @cfg {Array|Object} limits
  19361. * The limit lines configuration for the axis.
  19362. * For example:
  19363. *
  19364. * limits: [{
  19365. * value: 50,
  19366. * line: {
  19367. * strokeStyle: 'red',
  19368. * lineDash: [6, 3],
  19369. * title: {
  19370. * text: 'Monthly minimum',
  19371. * fontSize: 14
  19372. * }
  19373. * }
  19374. * }]
  19375. */
  19376. limits: null,
  19377. /**
  19378. * @cfg {Function} renderer Allows to change the text shown next to the tick.
  19379. * @param {Ext.chart.axis.Axis} axis The axis.
  19380. * @param {String/Number} label The label.
  19381. * @param {Object} layoutContext The object that holds calculated positions
  19382. * of axis' ticks based on current layout, segmenter, axis length and configuration.
  19383. * @param {String/Number/null} lastLabel The last label (if any).
  19384. * @return {String} The label to display.
  19385. * @controllable
  19386. */
  19387. renderer: null,
  19388. /**
  19389. * @protected
  19390. * @cfg {Ext.chart.AbstractChart} chart The Chart that the Axis is bound.
  19391. */
  19392. chart: null,
  19393. /**
  19394. * @cfg {Object} style
  19395. * The style for the axis line and ticks.
  19396. * Refer to the {@link Ext.chart.axis.sprite.Axis}
  19397. */
  19398. style: null,
  19399. /**
  19400. * @cfg {Number} margin
  19401. * The margin of the axis. Used to control the spacing between axes in charts with multiple
  19402. * axes. Unlike CSS where the margin is added on all 4 sides of an element, the `margin`
  19403. * is the total space that is added horizontally for a vertical axis, vertically
  19404. * for a horizontal axis, and radially for an angular axis.
  19405. */
  19406. margin: 0,
  19407. /**
  19408. * @cfg {Number} [titleMargin=4]
  19409. * The margin around the axis title. Unlike CSS where the margin is added on all 4
  19410. * sides of an element, the `titleMargin` is the total space that is added horizontally
  19411. * for a vertical title and vertically for an horizontal title, with half the `titleMargin`
  19412. * being added on either side.
  19413. */
  19414. titleMargin: 4,
  19415. /**
  19416. * @cfg {Object} background
  19417. * The background config for the axis surface.
  19418. */
  19419. background: null,
  19420. /**
  19421. * @cfg {Number} minimum
  19422. * The minimum value drawn by the axis. If not set explicitly, the axis
  19423. * minimum will be calculated automatically.
  19424. */
  19425. minimum: NaN,
  19426. /**
  19427. * @cfg {Number} maximum
  19428. * The maximum value drawn by the axis. If not set explicitly, the axis
  19429. * maximum will be calculated automatically.
  19430. */
  19431. maximum: NaN,
  19432. /**
  19433. * @cfg {Boolean} reconcileRange
  19434. * If 'true' the range of the axis will be a union of ranges
  19435. * of all the axes with the same direction. Defaults to 'false'.
  19436. */
  19437. reconcileRange: false,
  19438. /**
  19439. * @cfg {Number} minZoom
  19440. * The minimum zooming level for axis.
  19441. */
  19442. minZoom: 1,
  19443. /**
  19444. * @cfg {Number} maxZoom
  19445. * The maximum zooming level for axis.
  19446. */
  19447. maxZoom: 10000,
  19448. /**
  19449. * @cfg {Object|Ext.chart.axis.layout.Layout} layout
  19450. * The axis layout config. See {@link Ext.chart.axis.layout.Layout}
  19451. */
  19452. layout: 'continuous',
  19453. /**
  19454. * @cfg {Object|Ext.chart.axis.segmenter.Segmenter} segmenter
  19455. * The segmenter config. See {@link Ext.chart.axis.segmenter.Segmenter}
  19456. */
  19457. segmenter: 'numeric',
  19458. /**
  19459. * @cfg {Boolean} hidden
  19460. * Indicate whether to hide the axis.
  19461. * If the axis is hidden, one of the axis line, ticks, labels or the title will be shown and
  19462. * no margin will be taken.
  19463. * The coordination mechanism works fine no matter if the axis is hidden.
  19464. */
  19465. hidden: false,
  19466. /**
  19467. * @cfg {Number} [majorTickSteps=0]
  19468. * Forces the number of major ticks to the specified value.
  19469. * Both {@link #minimum} and {@link #maximum} should be specified.
  19470. */
  19471. majorTickSteps: 0,
  19472. /**
  19473. * @cfg {Number} [minorTickSteps=0]
  19474. * The number of small ticks between two major ticks.
  19475. */
  19476. minorTickSteps: 0,
  19477. /**
  19478. * @cfg {Boolean} adjustByMajorUnit
  19479. * Whether to make the auto-calculated minimum and maximum of the axis
  19480. * a multiple of the interval between the major ticks of the axis.
  19481. * If {@link #majorTickSteps}, {@link #minimum} or {@link #maximum}
  19482. * configs have been set, this config will be ignored.
  19483. * Defaults to 'true'.
  19484. * Note: this config has no effect if the axis is {@link #hidden}.
  19485. */
  19486. adjustByMajorUnit: true,
  19487. /**
  19488. * @cfg {String|Object} title
  19489. * The title for the Axis.
  19490. * If given a String, the 'text' attribute of the title sprite will be set,
  19491. * otherwise the style will be set.
  19492. */
  19493. title: null,
  19494. /**
  19495. * @private
  19496. * @cfg {Number} [expandRangeBy=0]
  19497. */
  19498. expandRangeBy: 0,
  19499. /**
  19500. * @private
  19501. * @cfg {Number} length
  19502. * Length of the axis position. Equals to the size of inner rect on the docking side
  19503. * of this axis.
  19504. * WARNING: Meant to be set automatically by chart. Do not set it manually.
  19505. */
  19506. length: 0,
  19507. /**
  19508. * @private
  19509. * @cfg {Array} center
  19510. * Center of the polar axis.
  19511. * WARNING: Meant to be set automatically by chart. Do not set it manually.
  19512. */
  19513. center: null,
  19514. /**
  19515. * @private
  19516. * @cfg {Number} radius
  19517. * Radius of the polar axis.
  19518. * WARNING: Meant to be set automatically by chart. Do not set it manually.
  19519. */
  19520. radius: null,
  19521. /**
  19522. * @private
  19523. */
  19524. totalAngle: Math.PI,
  19525. /**
  19526. * @private
  19527. * @cfg {Number} rotation
  19528. * Rotation of the polar axis in radians.
  19529. * WARNING: Meant to be set automatically by chart. Do not set it manually.
  19530. */
  19531. rotation: null,
  19532. /**
  19533. * @cfg {Array} visibleRange
  19534. * Specify the proportion of the axis to be rendered. The series bound to
  19535. * this axis will be synchronized and transformed accordingly.
  19536. */
  19537. visibleRange: [
  19538. 0,
  19539. 1
  19540. ],
  19541. /**
  19542. * @cfg {Boolean} needHighPrecision
  19543. * Indicates that the axis needs high precision surface implementation.
  19544. * See {@link Ext.draw.engine.Canvas#highPrecision}
  19545. */
  19546. needHighPrecision: false,
  19547. /**
  19548. * @cfg {Ext.chart.axis.Axis|String|Number} linkedTo
  19549. * Axis (itself, its ID or index) that this axis is linked to.
  19550. * When an axis is linked to a master axis, it will use the same data as the master axis.
  19551. * It can be used to show additional info, or to ease reading the chart by duplicating
  19552. * the scales.
  19553. */
  19554. linkedTo: null,
  19555. /**
  19556. * @cfg {Number|Object}
  19557. * If `floating` is a number, then it's a percentage displacement of the axis from its
  19558. * initial {@link #position} in the direction opposite to the axis' direction. For instance,
  19559. * '{position:"left", floating:75}' displays a vertical axis at 3/4 of the chart, starting
  19560. * from the left. It is equivalent to '{position:"right", floating:25}'. If `floating` is
  19561. * an object, then `floating.value` is the position of this axis along another axis, defined
  19562. * by `floating.alongAxis`, where `alongAxis` is an ID, an
  19563. * {@link Ext.chart.AbstractChart#axes} config index, or the other axis itself. `alongAxis`
  19564. * must have an opposite {@link Ext.chart.axis.Axis#getAlignment alignment}.
  19565. * For example:
  19566. *
  19567. *
  19568. * axes: [
  19569. * {
  19570. * title: 'Average Temperature (F)',
  19571. * type: 'numeric',
  19572. * position: 'left',
  19573. * id: 'temperature-vertical-axis',
  19574. * minimum: -30,
  19575. * maximum: 130
  19576. * },
  19577. * {
  19578. * title: 'Month (2013)',
  19579. * type: 'category',
  19580. * position: 'bottom',
  19581. * floating: {
  19582. * value: 32,
  19583. * alongAxis: 'temperature-vertical-axis'
  19584. * }
  19585. * }
  19586. * ]
  19587. */
  19588. floating: null
  19589. },
  19590. titleOffset: 0,
  19591. spriteAnimationCount: 0,
  19592. boundSeries: [],
  19593. sprites: null,
  19594. surface: null,
  19595. /**
  19596. * @private
  19597. * @property {Array} range
  19598. * The full data range of the axis. Should not be set directly, Clear it to `null`
  19599. * and use `getRange` to update.
  19600. */
  19601. range: null,
  19602. defaultRange: [
  19603. 0,
  19604. 1
  19605. ],
  19606. rangePadding: 0.5,
  19607. xValues: [],
  19608. yValues: [],
  19609. masterAxis: null,
  19610. applyRotation: function(rotation) {
  19611. var twoPie = Math.PI * 2;
  19612. return (rotation % twoPie + Math.PI) % twoPie - Math.PI;
  19613. },
  19614. updateRotation: function(rotation) {
  19615. var sprites = this.getSprites(),
  19616. position = this.getPosition();
  19617. if (!this.getHidden() && position === 'angular' && sprites[0]) {
  19618. sprites[0].setAttributes({
  19619. baseRotation: rotation
  19620. });
  19621. }
  19622. },
  19623. applyTitle: function(title, oldTitle) {
  19624. var surface;
  19625. if (Ext.isString(title)) {
  19626. title = {
  19627. text: title
  19628. };
  19629. }
  19630. if (!oldTitle) {
  19631. oldTitle = Ext.create('sprite.text', title);
  19632. if ((surface = this.getSurface())) {
  19633. surface.add(oldTitle);
  19634. }
  19635. } else {
  19636. oldTitle.setAttributes(title);
  19637. }
  19638. return oldTitle;
  19639. },
  19640. getAdjustByMajorUnit: function() {
  19641. return !this.getHidden() && this.callParent();
  19642. },
  19643. applyFloating: function(floating, oldFloating) {
  19644. if (floating === null) {
  19645. floating = {
  19646. value: null,
  19647. alongAxis: null
  19648. };
  19649. } else if (Ext.isNumber(floating)) {
  19650. floating = {
  19651. value: floating,
  19652. alongAxis: null
  19653. };
  19654. }
  19655. if (Ext.isObject(floating)) {
  19656. if (oldFloating && oldFloating.alongAxis) {
  19657. delete this.getChart().getAxis(oldFloating.alongAxis).floatingAxes[this.getId()];
  19658. }
  19659. return floating;
  19660. }
  19661. return oldFloating;
  19662. },
  19663. constructor: function(config) {
  19664. var me = this,
  19665. id;
  19666. me.sprites = [];
  19667. me.labels = [];
  19668. // Maps IDs of the axes that float along this axis to their floating values.
  19669. me.floatingAxes = {};
  19670. config = config || {};
  19671. if (config.position === 'angular') {
  19672. config.style = config.style || {};
  19673. config.style.estStepSize = 1;
  19674. }
  19675. if ('id' in config) {
  19676. id = config.id;
  19677. } else if ('id' in me.config) {
  19678. id = me.config.id;
  19679. } else {
  19680. id = me.getId();
  19681. }
  19682. me.setId(id);
  19683. me.mixins.observable.constructor.apply(me, arguments);
  19684. },
  19685. /**
  19686. * @private
  19687. * @return {String}
  19688. */
  19689. getAlignment: function() {
  19690. switch (this.getPosition()) {
  19691. case 'left':
  19692. case 'right':
  19693. return 'vertical';
  19694. case 'top':
  19695. case 'bottom':
  19696. return 'horizontal';
  19697. case 'radial':
  19698. return 'radial';
  19699. case 'angular':
  19700. return 'angular';
  19701. }
  19702. },
  19703. /**
  19704. * @private
  19705. * @return {String}
  19706. */
  19707. getGridAlignment: function() {
  19708. switch (this.getPosition()) {
  19709. case 'left':
  19710. case 'right':
  19711. return 'horizontal';
  19712. case 'top':
  19713. case 'bottom':
  19714. return 'vertical';
  19715. case 'radial':
  19716. return 'circular';
  19717. case 'angular':
  19718. return 'radial';
  19719. }
  19720. },
  19721. /**
  19722. * @private
  19723. * Get the surface for drawing the series sprites
  19724. */
  19725. getSurface: function() {
  19726. var me = this,
  19727. chart = me.getChart(),
  19728. surface, gridSurface;
  19729. if (chart && !me.surface) {
  19730. surface = me.surface = chart.getSurface(me.getId(), 'axis');
  19731. gridSurface = me.gridSurface = chart.getSurface('main');
  19732. gridSurface.waitFor(surface);
  19733. me.getGrid();
  19734. me.createLimits();
  19735. }
  19736. return me.surface;
  19737. },
  19738. createLimits: function() {
  19739. var me = this,
  19740. chart = me.getChart(),
  19741. axisSprite = me.getSprites()[0],
  19742. gridAlignment = me.getGridAlignment(),
  19743. limits;
  19744. if (me.getLimits() && gridAlignment) {
  19745. gridAlignment = gridAlignment.replace('3d', '');
  19746. me.limits = limits = {
  19747. surface: chart.getSurface('overlay'),
  19748. lines: new Ext.chart.Markers(),
  19749. titles: new Ext.draw.sprite.Instancing()
  19750. };
  19751. limits.lines.setTemplate({
  19752. xclass: 'grid.' + gridAlignment
  19753. });
  19754. limits.lines.getTemplate().setAttributes({
  19755. strokeStyle: 'black'
  19756. }, true);
  19757. limits.surface.add(limits.lines);
  19758. axisSprite.bindMarker(gridAlignment + '-limit-lines', me.limits.lines);
  19759. me.limitTitleTpl = new Ext.draw.sprite.Text();
  19760. limits.titles.setTemplate(me.limitTitleTpl);
  19761. limits.surface.add(limits.titles);
  19762. }
  19763. },
  19764. applyGrid: function(grid) {
  19765. // Returning an empty object here if grid was set to 'true' so that
  19766. // config merging in the theme works properly.
  19767. if (grid === true) {
  19768. return {};
  19769. }
  19770. return grid;
  19771. },
  19772. updateGrid: function(grid) {
  19773. var me = this,
  19774. chart = me.getChart(),
  19775. gridSurface = me.gridSurface,
  19776. axisSprite, gridAlignment, gridSprite;
  19777. if (!chart) {
  19778. me.on({
  19779. chartattached: Ext.bind(me.updateGrid, me, [
  19780. grid
  19781. ]),
  19782. single: true
  19783. });
  19784. return;
  19785. }
  19786. axisSprite = me.getSprites()[0];
  19787. gridAlignment = me.getGridAlignment();
  19788. if (grid) {
  19789. gridSprite = me.gridSpriteEven;
  19790. if (!gridSprite) {
  19791. gridSprite = me.gridSpriteEven = new Ext.chart.Markers();
  19792. gridSprite.setTemplate({
  19793. xclass: 'grid.' + gridAlignment
  19794. });
  19795. gridSurface.add(gridSprite);
  19796. axisSprite.bindMarker(gridAlignment + '-even', gridSprite);
  19797. }
  19798. if (Ext.isObject(grid)) {
  19799. gridSprite.getTemplate().setAttributes(grid);
  19800. if (Ext.isObject(grid.even)) {
  19801. gridSprite.getTemplate().setAttributes(grid.even);
  19802. }
  19803. }
  19804. gridSprite = me.gridSpriteOdd;
  19805. if (!gridSprite) {
  19806. gridSprite = me.gridSpriteOdd = new Ext.chart.Markers();
  19807. gridSprite.setTemplate({
  19808. xclass: 'grid.' + gridAlignment
  19809. });
  19810. gridSurface.add(gridSprite);
  19811. axisSprite.bindMarker(gridAlignment + '-odd', gridSprite);
  19812. }
  19813. if (Ext.isObject(grid)) {
  19814. gridSprite.getTemplate().setAttributes(grid);
  19815. if (Ext.isObject(grid.odd)) {
  19816. gridSprite.getTemplate().setAttributes(grid.odd);
  19817. }
  19818. }
  19819. }
  19820. },
  19821. updateMinorTickSteps: function(minorTickSteps) {
  19822. var me = this,
  19823. sprites = me.getSprites(),
  19824. axisSprite = sprites && sprites[0],
  19825. surface;
  19826. if (axisSprite) {
  19827. axisSprite.setAttributes({
  19828. minorTicks: !!minorTickSteps
  19829. });
  19830. surface = me.getSurface();
  19831. if (!me.isConfiguring && surface) {
  19832. surface.renderFrame();
  19833. }
  19834. }
  19835. },
  19836. /**
  19837. *
  19838. * Mapping data value into coordinate.
  19839. *
  19840. * @param {*} value
  19841. * @param {String} field
  19842. * @param {Number} [idx]
  19843. * @param {Ext.util.MixedCollection} [items]
  19844. * @return {Number}
  19845. */
  19846. getCoordFor: function(value, field, idx, items) {
  19847. return this.getLayout().getCoordFor(value, field, idx, items);
  19848. },
  19849. applyPosition: function(pos) {
  19850. return pos.toLowerCase();
  19851. },
  19852. applyLength: function(length, oldLength) {
  19853. return length > 0 ? length : oldLength;
  19854. },
  19855. applyLabel: function(label, oldLabel) {
  19856. if (!oldLabel) {
  19857. oldLabel = new Ext.draw.sprite.Text({});
  19858. }
  19859. if (label) {
  19860. if (this.limitTitleTpl) {
  19861. this.limitTitleTpl.setAttributes(label);
  19862. }
  19863. oldLabel.setAttributes(label);
  19864. }
  19865. return oldLabel;
  19866. },
  19867. applyLayout: function(layout, oldLayout) {
  19868. layout = Ext.factory(layout, null, oldLayout, 'axisLayout');
  19869. layout.setAxis(this);
  19870. return layout;
  19871. },
  19872. applySegmenter: function(segmenter, oldSegmenter) {
  19873. segmenter = Ext.factory(segmenter, null, oldSegmenter, 'segmenter');
  19874. segmenter.setAxis(this);
  19875. return segmenter;
  19876. },
  19877. updateMinimum: function() {
  19878. this.range = null;
  19879. },
  19880. updateMaximum: function() {
  19881. this.range = null;
  19882. },
  19883. hideLabels: function() {
  19884. this.getSprites()[0].setDirty(true);
  19885. this.setLabel({
  19886. hidden: true
  19887. });
  19888. },
  19889. showLabels: function() {
  19890. this.getSprites()[0].setDirty(true);
  19891. this.setLabel({
  19892. hidden: false
  19893. });
  19894. },
  19895. /**
  19896. * Invokes renderFrame on this axis's surface(s)
  19897. */
  19898. renderFrame: function() {
  19899. this.getSurface().renderFrame();
  19900. },
  19901. updateChart: function(newChart, oldChart) {
  19902. var me = this,
  19903. surface;
  19904. if (oldChart) {
  19905. oldChart.unregister(me);
  19906. oldChart.un('serieschange', me.onSeriesChange, me);
  19907. me.linkAxis();
  19908. me.fireEvent('chartdetached', oldChart, me);
  19909. }
  19910. if (newChart) {
  19911. newChart.on('serieschange', me.onSeriesChange, me);
  19912. me.surface = null;
  19913. surface = me.getSurface();
  19914. me.getLabel().setSurface(surface);
  19915. surface.add(me.getSprites());
  19916. surface.add(me.getTitle());
  19917. newChart.register(me);
  19918. me.fireEvent('chartattached', newChart, me);
  19919. }
  19920. },
  19921. applyBackground: function(background) {
  19922. var rect = Ext.ClassManager.getByAlias('sprite.rect');
  19923. return rect.def.normalize(background);
  19924. },
  19925. /**
  19926. * @protected
  19927. * Invoked when data has changed.
  19928. */
  19929. processData: function() {
  19930. this.getLayout().processData();
  19931. this.range = null;
  19932. },
  19933. getDirection: function() {
  19934. return this.getChart().getDirectionForAxis(this.getPosition());
  19935. },
  19936. isSide: function() {
  19937. var position = this.getPosition();
  19938. return position === 'left' || position === 'right';
  19939. },
  19940. applyFields: function(fields) {
  19941. return Ext.Array.from(fields);
  19942. },
  19943. applyVisibleRange: function(visibleRange, oldVisibleRange) {
  19944. var temp;
  19945. this.getChart();
  19946. // If it is in reversed order swap them
  19947. if (visibleRange[0] > visibleRange[1]) {
  19948. temp = visibleRange[0];
  19949. visibleRange[0] = visibleRange[1];
  19950. visibleRange[0] = temp;
  19951. }
  19952. if (visibleRange[1] === visibleRange[0]) {
  19953. visibleRange[1] += 1 / this.getMaxZoom();
  19954. }
  19955. if (visibleRange[1] > visibleRange[0] + 1) {
  19956. visibleRange[0] = 0;
  19957. visibleRange[1] = 1;
  19958. } else if (visibleRange[0] < 0) {
  19959. visibleRange[1] -= visibleRange[0];
  19960. visibleRange[0] = 0;
  19961. } else if (visibleRange[1] > 1) {
  19962. visibleRange[0] -= visibleRange[1] - 1;
  19963. visibleRange[1] = 1;
  19964. }
  19965. if (oldVisibleRange && visibleRange[0] === oldVisibleRange[0] && visibleRange[1] === oldVisibleRange[1]) {
  19966. return undefined;
  19967. }
  19968. return visibleRange;
  19969. },
  19970. updateVisibleRange: function(visibleRange) {
  19971. this.fireEvent('visiblerangechange', this, visibleRange);
  19972. },
  19973. onSeriesChange: function(chart) {
  19974. var me = this,
  19975. series = chart.getSeries(),
  19976. boundSeries = [],
  19977. linkedTo, masterAxis, getAxisMethod, i, ln;
  19978. if (series) {
  19979. getAxisMethod = 'get' + me.getDirection() + 'Axis';
  19980. for (i = 0 , ln = series.length; i < ln; i++) {
  19981. if (this === series[i][getAxisMethod]()) {
  19982. boundSeries.push(series[i]);
  19983. }
  19984. }
  19985. }
  19986. me.boundSeries = boundSeries;
  19987. linkedTo = me.getLinkedTo();
  19988. masterAxis = !Ext.isEmpty(linkedTo) && chart.getAxis(linkedTo);
  19989. if (masterAxis) {
  19990. me.linkAxis(masterAxis);
  19991. } else {
  19992. me.getLayout().processData();
  19993. }
  19994. },
  19995. linkAxis: function(masterAxis) {
  19996. var me = this;
  19997. function link(action, slave, master) {
  19998. master.getLayout()[action]('datachange', 'onDataChange', slave);
  19999. master[action]('rangechange', 'onMasterAxisRangeChange', slave);
  20000. }
  20001. if (me.masterAxis) {
  20002. if (!me.masterAxis.destroyed) {
  20003. link('un', me, me.masterAxis);
  20004. }
  20005. me.masterAxis = null;
  20006. }
  20007. if (masterAxis) {
  20008. if (masterAxis.type !== this.type) {
  20009. Ext.Error.raise("Linked axes must be of the same type.");
  20010. }
  20011. link('on', me, masterAxis);
  20012. me.onDataChange(masterAxis.getLayout().labels);
  20013. me.onMasterAxisRangeChange(masterAxis, masterAxis.range);
  20014. me.setStyle(Ext.apply({}, me.config.style, masterAxis.config.style));
  20015. me.setTitle(Ext.apply({}, me.config.title, masterAxis.config.title));
  20016. me.setLabel(Ext.apply({}, me.config.label, masterAxis.config.label));
  20017. me.masterAxis = masterAxis;
  20018. }
  20019. },
  20020. onDataChange: function(data) {
  20021. this.getLayout().labels = data;
  20022. },
  20023. onMasterAxisRangeChange: function(masterAxis, range) {
  20024. this.range = range;
  20025. },
  20026. applyRange: function(newRange) {
  20027. if (!newRange) {
  20028. return this.dataRange.slice(0);
  20029. } else {
  20030. return [
  20031. newRange[0] === null ? this.dataRange[0] : newRange[0],
  20032. newRange[1] === null ? this.dataRange[1] : newRange[1]
  20033. ];
  20034. }
  20035. },
  20036. /**
  20037. * @private
  20038. */
  20039. setBoundSeriesRange: function(range) {
  20040. var boundSeries = this.boundSeries,
  20041. style = {},
  20042. series, i, sprites, j, ln;
  20043. style['range' + this.getDirection()] = range;
  20044. for (i = 0 , ln = boundSeries.length; i < ln; i++) {
  20045. series = boundSeries[i];
  20046. if (series.getHidden() === true) {
  20047. continue;
  20048. }
  20049. sprites = series.getSprites();
  20050. for (j = 0; j < sprites.length; j++) {
  20051. sprites[j].setAttributes(style);
  20052. }
  20053. }
  20054. },
  20055. /**
  20056. * Get the range derived from all the bound series.
  20057. * The range value is cached and returned the next time this method is called.
  20058. * Set `recalculate` to `true` to recalculate the range, if changes to the
  20059. * chart, its components or data are expected to affect the range.
  20060. * @param {Boolean} [recalculate]
  20061. * @return {Number[]}
  20062. */
  20063. getRange: function(recalculate) {
  20064. var me = this,
  20065. range = recalculate ? null : me.range,
  20066. oldRange = me.oldRange,
  20067. minimum, maximum;
  20068. if (!range) {
  20069. if (me.masterAxis) {
  20070. range = me.masterAxis.range;
  20071. } else {
  20072. minimum = me.getMinimum();
  20073. maximum = me.getMaximum();
  20074. if (Ext.isNumber(minimum) && Ext.isNumber(maximum)) {
  20075. range = [
  20076. minimum,
  20077. maximum
  20078. ];
  20079. } else {
  20080. range = me.calculateRange();
  20081. }
  20082. me.range = range;
  20083. }
  20084. }
  20085. if (range && (!oldRange || range[0] !== oldRange[0] || range[1] !== oldRange[1])) {
  20086. me.fireEvent('rangechange', me, range, oldRange);
  20087. me.oldRange = range;
  20088. }
  20089. return range;
  20090. },
  20091. isSingleDataPoint: function(range) {
  20092. return (range[0] + this.rangePadding) === 0 && (range[1] - this.rangePadding) === 0;
  20093. },
  20094. calculateRange: function() {
  20095. var me = this,
  20096. boundSeries = me.boundSeries,
  20097. layout = me.getLayout(),
  20098. segmenter = me.getSegmenter(),
  20099. minimum = me.getMinimum(),
  20100. maximum = me.getMaximum(),
  20101. visibleRange = me.getVisibleRange(),
  20102. getRangeMethod = 'get' + me.getDirection() + 'Range',
  20103. expandRangeBy = me.getExpandRangeBy(),
  20104. context, attr, majorTicks, series, i, ln, seriesRange,
  20105. range = [
  20106. NaN,
  20107. NaN
  20108. ];
  20109. // For each series bound to this axis, ask the series for its min/max values
  20110. // and use them to find the overall min/max.
  20111. for (i = 0 , ln = boundSeries.length; i < ln; i++) {
  20112. series = boundSeries[i];
  20113. if (series.getHidden() === true) {
  20114. continue;
  20115. }
  20116. seriesRange = series[getRangeMethod]();
  20117. if (seriesRange) {
  20118. Ext.chart.Util.expandRange(range, seriesRange);
  20119. }
  20120. }
  20121. range = Ext.chart.Util.validateRange(range, me.defaultRange, me.rangePadding);
  20122. // The second condition is there to account for a special case where we only have
  20123. // a single data point, so the effective range of coordinated data is 0 (whatever
  20124. // the actual value of that single data point is, it will be assigned an index of
  20125. // zero, as the first and only data point). Since zero range is invalid, the
  20126. // validateRange function above will expand the range by the value of the rangePadding,
  20127. // which makes further expansion by the value of expandRangeBy unnecessary.
  20128. if (expandRangeBy && (!me.isSingleDataPoint(range))) {
  20129. range[0] -= expandRangeBy;
  20130. range[1] += expandRangeBy;
  20131. }
  20132. if (isFinite(minimum)) {
  20133. range[0] = minimum;
  20134. }
  20135. if (isFinite(maximum)) {
  20136. range[1] = maximum;
  20137. }
  20138. // When series `fullStack` config is used, the values may add up to
  20139. // slightly more than the value of the `fullStackTotal` config
  20140. // because of a precision error.
  20141. range[0] = Ext.Number.correctFloat(range[0]);
  20142. range[1] = Ext.Number.correctFloat(range[1]);
  20143. me.range = range;
  20144. // It's important to call 'me.reconcileRange' after the 'range'
  20145. // has been assigned to avoid circular calls.
  20146. if (me.getReconcileRange()) {
  20147. me.reconcileRange();
  20148. }
  20149. // TODO: Find a better way to do this.
  20150. // TODO: The original design didn't take into account that the range of an axis
  20151. // TODO: will depend not just on the range of the data of the bound series in the
  20152. // TODO: direction of the axis, but also on the range of other axes with the
  20153. // TODO: same direction and on the segmentation of the axis (interval between
  20154. // TODO: major ticks).
  20155. // TODO: While the fist omission was possible to retrofit rather gracefully
  20156. // TODO: by adding the axis.reconcileRange method, the second one is harder to deal with.
  20157. // TODO: The issue is that the resulting axis segmentation, which is a part of
  20158. // TODO: the axis sprite layout has to be known before layout has begun.
  20159. // TODO: Example for the logic below:
  20160. // TODO: If we have a range of data of 0..34.5 the step will be 2 and we
  20161. // TODO: will round up the max to 36 based on that step, but when the range is 0..36,
  20162. // TODO: the step becomes 5, so we have to reconcile the range once again where max
  20163. // TODO: becomes 40.
  20164. if (range[0] !== range[1] && me.getAdjustByMajorUnit() && segmenter.adjustByMajorUnit && !me.getMajorTickSteps()) {
  20165. attr = Ext.Object.chain(me.getSprites()[0].attr);
  20166. attr.min = range[0];
  20167. attr.max = range[1];
  20168. attr.visibleMin = visibleRange[0];
  20169. attr.visibleMax = visibleRange[1];
  20170. context = {
  20171. attr: attr,
  20172. segmenter: segmenter
  20173. };
  20174. layout.calculateLayout(context);
  20175. majorTicks = context.majorTicks;
  20176. if (majorTicks) {
  20177. segmenter.adjustByMajorUnit(majorTicks.step, majorTicks.unit.scale, range);
  20178. attr.min = range[0];
  20179. attr.max = range[1];
  20180. context.majorTicks = null;
  20181. layout.calculateLayout(context);
  20182. majorTicks = context.majorTicks;
  20183. segmenter.adjustByMajorUnit(majorTicks.step, majorTicks.unit.scale, range);
  20184. } else if (!me.hasClearRangePending) {
  20185. // Axis hasn't been rendered yet.
  20186. me.hasClearRangePending = true;
  20187. me.getChart().on('layout', 'clearRange', me);
  20188. }
  20189. }
  20190. return range;
  20191. },
  20192. /**
  20193. * @private
  20194. */
  20195. clearRange: function() {
  20196. this.hasClearRangePending = null;
  20197. this.range = null;
  20198. },
  20199. /**
  20200. * Expands the range of the axis
  20201. * based on the range of other axes with the same direction (if any).
  20202. */
  20203. reconcileRange: function() {
  20204. var me = this,
  20205. axes = me.getChart().getAxes(),
  20206. direction = me.getDirection(),
  20207. i, ln, axis, range;
  20208. if (!axes) {
  20209. return;
  20210. }
  20211. for (i = 0 , ln = axes.length; i < ln; i++) {
  20212. axis = axes[i];
  20213. range = axis.getRange();
  20214. if (axis === me || axis.getDirection() !== direction || !range || !axis.getReconcileRange()) {
  20215. continue;
  20216. }
  20217. if (range[0] < me.range[0]) {
  20218. me.range[0] = range[0];
  20219. }
  20220. if (range[1] > me.range[1]) {
  20221. me.range[1] = range[1];
  20222. }
  20223. }
  20224. },
  20225. applyStyle: function(style, oldStyle) {
  20226. var cls = Ext.ClassManager.getByAlias('sprite.' + this.seriesType);
  20227. if (cls && cls.def) {
  20228. style = cls.def.normalize(style);
  20229. }
  20230. oldStyle = Ext.apply(oldStyle || {}, style);
  20231. return oldStyle;
  20232. },
  20233. themeOnlyIfConfigured: {
  20234. grid: true
  20235. },
  20236. updateTheme: function(theme) {
  20237. var me = this,
  20238. axisTheme = theme.getAxis(),
  20239. position = me.getPosition(),
  20240. initialConfig = me.getInitialConfig(),
  20241. defaultConfig = me.defaultConfig,
  20242. configs = me.self.getConfigurator().configs,
  20243. genericAxisTheme = axisTheme.defaults,
  20244. specificAxisTheme = axisTheme[position],
  20245. themeOnlyIfConfigured = me.themeOnlyIfConfigured,
  20246. key, value, isObjValue, isUnusedConfig, initialValue, cfg;
  20247. axisTheme = Ext.merge({}, genericAxisTheme, specificAxisTheme);
  20248. for (key in axisTheme) {
  20249. value = axisTheme[key];
  20250. cfg = configs[key];
  20251. if (value !== null && value !== undefined && cfg) {
  20252. initialValue = initialConfig[key];
  20253. isObjValue = Ext.isObject(value);
  20254. isUnusedConfig = initialValue === defaultConfig[key];
  20255. if (isObjValue) {
  20256. if (isUnusedConfig && themeOnlyIfConfigured[key]) {
  20257. continue;
  20258. }
  20259. value = Ext.merge({}, value, initialValue);
  20260. }
  20261. if (isUnusedConfig || isObjValue) {
  20262. me[cfg.names.set](value);
  20263. }
  20264. }
  20265. }
  20266. },
  20267. updateCenter: function(center) {
  20268. var me = this,
  20269. sprites = me.getSprites(),
  20270. axisSprite = sprites[0],
  20271. centerX = center[0],
  20272. centerY = center[1];
  20273. if (axisSprite) {
  20274. axisSprite.setAttributes({
  20275. centerX: centerX,
  20276. centerY: centerY
  20277. });
  20278. }
  20279. if (me.gridSpriteEven) {
  20280. me.gridSpriteEven.getTemplate().setAttributes({
  20281. translationX: centerX,
  20282. translationY: centerY,
  20283. rotationCenterX: centerX,
  20284. rotationCenterY: centerY
  20285. });
  20286. }
  20287. if (me.gridSpriteOdd) {
  20288. me.gridSpriteOdd.getTemplate().setAttributes({
  20289. translationX: centerX,
  20290. translationY: centerY,
  20291. rotationCenterX: centerX,
  20292. rotationCenterY: centerY
  20293. });
  20294. }
  20295. },
  20296. getSprites: function() {
  20297. if (!this.getChart()) {
  20298. return;
  20299. }
  20300. // eslint-disable-next-line vars-on-top
  20301. var me = this,
  20302. range = me.getRange(),
  20303. position = me.getPosition(),
  20304. chart = me.getChart(),
  20305. animation = chart.getAnimation(),
  20306. length = me.getLength(),
  20307. axisClass = me.superclass,
  20308. mainSprite, style, animationModifier;
  20309. // If animation is false, then stop animation.
  20310. if (animation === false) {
  20311. animation = {
  20312. duration: 0
  20313. };
  20314. }
  20315. style = Ext.applyIf({
  20316. position: position,
  20317. axis: me,
  20318. length: length,
  20319. grid: me.getGrid(),
  20320. hidden: me.getHidden(),
  20321. titleOffset: me.titleOffset,
  20322. layout: me.getLayout(),
  20323. segmenter: me.getSegmenter(),
  20324. totalAngle: me.getTotalAngle(),
  20325. label: me.getLabel()
  20326. }, me.getStyle());
  20327. if (range) {
  20328. style.min = range[0];
  20329. style.max = range[1];
  20330. }
  20331. // If the sprites are not created.
  20332. if (!me.sprites.length) {
  20333. while (!axisClass.xtype) {
  20334. axisClass = axisClass.superclass;
  20335. }
  20336. mainSprite = Ext.create('sprite.' + axisClass.xtype, style);
  20337. animationModifier = mainSprite.getAnimation();
  20338. animationModifier.setCustomDurations({
  20339. baseRotation: 0
  20340. });
  20341. animationModifier.on('animationstart', 'onAnimationStart', me);
  20342. animationModifier.on('animationend', 'onAnimationEnd', me);
  20343. mainSprite.setLayout(me.getLayout());
  20344. mainSprite.setSegmenter(me.getSegmenter());
  20345. mainSprite.setLabel(me.getLabel());
  20346. me.sprites.push(mainSprite);
  20347. me.updateTitleSprite();
  20348. } else {
  20349. mainSprite = me.sprites[0];
  20350. mainSprite.setAnimation(animation);
  20351. mainSprite.setAttributes(style);
  20352. }
  20353. if (me.getRenderer()) {
  20354. mainSprite.setRenderer(me.getRenderer());
  20355. }
  20356. return me.sprites;
  20357. },
  20358. /**
  20359. * @private
  20360. */
  20361. performLayout: function() {
  20362. if (this.isConfiguring) {
  20363. return;
  20364. }
  20365. // eslint-disable-next-line vars-on-top
  20366. var me = this,
  20367. sprites = me.getSprites(),
  20368. surface = me.getSurface(),
  20369. chart = me.getChart(),
  20370. sprite = sprites && sprites[0];
  20371. if (chart && surface && sprite) {
  20372. sprite.callUpdater(null, 'layout');
  20373. // recalculate axis ticks
  20374. chart.scheduleLayout();
  20375. }
  20376. },
  20377. updateTitleSprite: function() {
  20378. var me = this,
  20379. length = me.getLength(),
  20380. surface, thickness, title, position, margin, titleMargin, anchor;
  20381. if (!me.sprites[0] || !Ext.isNumber(length)) {
  20382. return;
  20383. }
  20384. thickness = this.sprites[0].thickness;
  20385. surface = me.getSurface();
  20386. title = me.getTitle();
  20387. position = me.getPosition();
  20388. margin = me.getMargin();
  20389. titleMargin = me.getTitleMargin();
  20390. anchor = surface.roundPixel(length / 2);
  20391. if (title) {
  20392. switch (position) {
  20393. case 'top':
  20394. title.setAttributes({
  20395. x: anchor,
  20396. y: margin + titleMargin / 2,
  20397. textBaseline: 'top',
  20398. textAlign: 'center'
  20399. }, true);
  20400. title.applyTransformations();
  20401. me.titleOffset = title.getBBox().height + titleMargin;
  20402. break;
  20403. case 'bottom':
  20404. title.setAttributes({
  20405. x: anchor,
  20406. y: thickness + titleMargin / 2,
  20407. textBaseline: 'top',
  20408. textAlign: 'center'
  20409. }, true);
  20410. title.applyTransformations();
  20411. me.titleOffset = title.getBBox().height + titleMargin;
  20412. break;
  20413. case 'left':
  20414. title.setAttributes({
  20415. x: margin + titleMargin / 2,
  20416. y: anchor,
  20417. textBaseline: 'top',
  20418. textAlign: 'center',
  20419. rotationCenterX: margin + titleMargin / 2,
  20420. rotationCenterY: anchor,
  20421. rotationRads: -Math.PI / 2
  20422. }, true);
  20423. title.applyTransformations();
  20424. me.titleOffset = title.getBBox().width + titleMargin;
  20425. break;
  20426. case 'right':
  20427. title.setAttributes({
  20428. x: thickness - margin + titleMargin / 2,
  20429. y: anchor,
  20430. textBaseline: 'bottom',
  20431. textAlign: 'center',
  20432. rotationCenterX: thickness + titleMargin / 2,
  20433. rotationCenterY: anchor,
  20434. rotationRads: Math.PI / 2
  20435. }, true);
  20436. title.applyTransformations();
  20437. me.titleOffset = title.getBBox().width + titleMargin;
  20438. break;
  20439. }
  20440. }
  20441. },
  20442. onThicknessChanged: function() {
  20443. this.getChart().onThicknessChanged();
  20444. },
  20445. getThickness: function() {
  20446. if (this.getHidden()) {
  20447. return 0;
  20448. }
  20449. return (this.sprites[0] && this.sprites[0].thickness || 1) + this.titleOffset + this.getMargin();
  20450. },
  20451. onAnimationStart: function() {
  20452. this.spriteAnimationCount++;
  20453. if (this.spriteAnimationCount === 1) {
  20454. this.fireEvent('animationstart', this);
  20455. }
  20456. },
  20457. onAnimationEnd: function() {
  20458. this.spriteAnimationCount--;
  20459. if (this.spriteAnimationCount === 0) {
  20460. this.fireEvent('animationend', this);
  20461. }
  20462. },
  20463. // Methods used in ComponentQuery and controller
  20464. getItemId: function() {
  20465. return this.getId();
  20466. },
  20467. getAncestorIds: function() {
  20468. return [
  20469. this.getChart().getId()
  20470. ];
  20471. },
  20472. isXType: function(xtype) {
  20473. return xtype === 'axis';
  20474. },
  20475. // Override the Observable's method to redirect listener scope
  20476. // resolution to the chart.
  20477. resolveListenerScope: function(defaultScope) {
  20478. var me = this,
  20479. namedScope = Ext._namedScopes[defaultScope],
  20480. chart = me.getChart(),
  20481. scope;
  20482. if (!namedScope) {
  20483. scope = chart ? chart.resolveListenerScope(defaultScope, false) : (defaultScope || me);
  20484. } else if (namedScope.isThis) {
  20485. scope = me;
  20486. } else if (namedScope.isController) {
  20487. scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
  20488. } else if (namedScope.isSelf) {
  20489. scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
  20490. // Class body listener. No chart controller, nor chart container controller.
  20491. if (scope === chart && !chart.getInheritedConfig('defaultListenerScope')) {
  20492. scope = me;
  20493. }
  20494. }
  20495. return scope;
  20496. },
  20497. destroy: function() {
  20498. var me = this;
  20499. me.setChart(null);
  20500. me.surface.destroy();
  20501. me.surface = null;
  20502. me.callParent();
  20503. }
  20504. });
  20505. /**
  20506. * The legend base class adapater for classic toolkit.
  20507. */
  20508. Ext.define('Ext.chart.legend.LegendBase', {
  20509. extend: 'Ext.view.View',
  20510. config: {
  20511. /* eslint-disable indent, max-len */
  20512. tpl: [
  20513. '<div class="',
  20514. Ext.baseCSSPrefix,
  20515. 'legend-inner">',
  20516. // for IE8 vertical centering
  20517. '<div class="',
  20518. Ext.baseCSSPrefix,
  20519. 'legend-container">',
  20520. '<tpl for=".">',
  20521. '<div class="',
  20522. Ext.baseCSSPrefix,
  20523. 'legend-item">',
  20524. '<span ',
  20525. 'class="',
  20526. Ext.baseCSSPrefix,
  20527. 'legend-item-marker {[ values.disabled ? Ext.baseCSSPrefix + \'legend-item-inactive\' : \'\' ]}" ',
  20528. 'style="background:{mark};">',
  20529. '</span>{name}',
  20530. '</div>',
  20531. '</tpl>',
  20532. '</div>',
  20533. '</div>'
  20534. ],
  20535. /* eslint-enable indent,max-len */
  20536. // element that contains rows (see AbstractView)
  20537. nodeContainerSelector: 'div.' + Ext.baseCSSPrefix + 'legend-inner',
  20538. // row element (see AbstractView)
  20539. itemSelector: 'div.' + Ext.baseCSSPrefix + 'legend-item',
  20540. /**
  20541. * @cfg {String} docked
  20542. * The dock position of this component in its container. Can be `left`, `top`, `right`,
  20543. * or `bottom`.
  20544. */
  20545. docked: 'bottom'
  20546. },
  20547. /**
  20548. * @cfg dock
  20549. * @hide
  20550. */
  20551. setDocked: function(docked) {
  20552. // If we call the method 'updateDocked' instead of 'setDocked', the following error
  20553. // is thrown: "Ext.Component#setDocked" is deprecated. Please use "setDock" instead.
  20554. var me = this,
  20555. panel = me.ownerCt;
  20556. me.docked = me.dock = docked;
  20557. switch (docked) {
  20558. case 'top':
  20559. case 'bottom':
  20560. me.addCls(me.horizontalCls);
  20561. me.removeCls(me.verticalCls);
  20562. break;
  20563. case 'left':
  20564. case 'right':
  20565. me.addCls(me.verticalCls);
  20566. me.removeCls(me.horizontalCls);
  20567. break;
  20568. }
  20569. if (panel) {
  20570. panel.setDock(docked);
  20571. }
  20572. },
  20573. setStore: function(store) {
  20574. this.bindStore(store);
  20575. },
  20576. clearViewEl: function() {
  20577. this.callParent(arguments);
  20578. // The legend-container div is not removed automatically.
  20579. Ext.removeNode(this.getNodeContainer());
  20580. },
  20581. onItemClick: function(record, item, index, e) {
  20582. this.callParent(arguments);
  20583. this.toggleItem(index);
  20584. }
  20585. });
  20586. /**
  20587. * This class provides a dataview-based chart legend.
  20588. */
  20589. Ext.define('Ext.chart.legend.Legend', {
  20590. extend: 'Ext.chart.legend.LegendBase',
  20591. alternateClassName: 'Ext.chart.Legend',
  20592. xtype: 'legend',
  20593. alias: 'legend.dom',
  20594. type: 'dom',
  20595. isLegend: true,
  20596. isDomLegend: true,
  20597. config: {
  20598. /**
  20599. * @cfg {Array}
  20600. * The rect of the legend relative to its container.
  20601. */
  20602. rect: null,
  20603. /**
  20604. * @cfg {Boolean} toggleable
  20605. * `true` to allow series items to have their visibility
  20606. * toggled by interaction with the legend items.
  20607. */
  20608. toggleable: true
  20609. },
  20610. /**
  20611. * @cfg {Ext.chart.legend.store.Store} store
  20612. * The {@link Ext.chart.legend.store.Store} to bind this legend to.
  20613. * @private
  20614. */
  20615. baseCls: Ext.baseCSSPrefix + 'legend',
  20616. horizontalCls: Ext.baseCSSPrefix + 'legend-horizontal',
  20617. verticalCls: Ext.baseCSSPrefix + 'legend-vertical',
  20618. toggleItem: function(index) {
  20619. var disabledCount = 0,
  20620. canToggle = true,
  20621. disabled, store, count, record, i;
  20622. if (!this.getToggleable()) {
  20623. return;
  20624. }
  20625. store = this.getStore();
  20626. if (store) {
  20627. count = store.getCount();
  20628. for (i = 0; i < count; i++) {
  20629. record = store.getAt(i);
  20630. if (record.get('disabled')) {
  20631. disabledCount++;
  20632. }
  20633. }
  20634. canToggle = count - disabledCount > 1;
  20635. record = store.getAt(index);
  20636. if (record) {
  20637. disabled = record.get('disabled');
  20638. if (disabled || canToggle) {
  20639. // This will trigger AbstractChart.onLegendStoreUpdate.
  20640. record.set('disabled', !disabled);
  20641. }
  20642. }
  20643. }
  20644. },
  20645. onResize: function(width, height, oldWidth, oldHeight) {
  20646. var me = this,
  20647. chart = me.chart;
  20648. if (!me.isConfiguring) {
  20649. if (chart) {
  20650. chart.scheduleLayout();
  20651. }
  20652. }
  20653. }
  20654. });
  20655. /**
  20656. * @private
  20657. */
  20658. Ext.define('Ext.chart.legend.sprite.Item', {
  20659. extend: 'Ext.draw.sprite.Composite',
  20660. alias: 'sprite.legenditem',
  20661. type: 'legenditem',
  20662. isLegendItem: true,
  20663. requires: [
  20664. 'Ext.draw.sprite.Text',
  20665. 'Ext.draw.sprite.Circle'
  20666. ],
  20667. inheritableStatics: {
  20668. def: {
  20669. processors: {
  20670. enabled: 'limited01',
  20671. markerLabelGap: 'number'
  20672. },
  20673. animationProcessors: {
  20674. enabled: null,
  20675. markerLabelGap: null
  20676. },
  20677. defaults: {
  20678. enabled: true,
  20679. markerLabelGap: 5
  20680. },
  20681. triggers: {
  20682. enabled: 'enabled',
  20683. markerLabelGap: 'layout'
  20684. },
  20685. updaters: {
  20686. layout: 'layoutUpdater',
  20687. enabled: 'enabledUpdater'
  20688. }
  20689. }
  20690. },
  20691. config: {
  20692. // Sprite's attributes are processed after initConfig.
  20693. // So we need to init below configs lazily, as otherwise
  20694. // adding sprites (created from those configs) to composite
  20695. // will result in an attempt to access attributes that
  20696. // composite doesn't have yet.
  20697. label: {
  20698. $value: {
  20699. type: 'text'
  20700. },
  20701. lazy: true
  20702. },
  20703. marker: {
  20704. $value: {
  20705. type: 'circle'
  20706. },
  20707. lazy: true
  20708. },
  20709. legend: null,
  20710. store: null,
  20711. record: null,
  20712. series: null
  20713. },
  20714. applyLabel: function(label, oldLabel) {
  20715. var sprite;
  20716. if (label) {
  20717. if (label.isSprite && label.type === 'text') {
  20718. sprite = label;
  20719. } else {
  20720. if (oldLabel && label.type === oldLabel.type) {
  20721. oldLabel.setConfig(label);
  20722. sprite = oldLabel;
  20723. this.scheduleUpdater(this.attr, 'layout');
  20724. } else {
  20725. sprite = new Ext.draw.sprite.Text(label);
  20726. }
  20727. }
  20728. }
  20729. return sprite;
  20730. },
  20731. defaultMarkerSize: 10,
  20732. updateLabel: function(label, oldLabel) {
  20733. var me = this;
  20734. me.removeSprite(oldLabel);
  20735. label.setAttributes({
  20736. textBaseline: 'middle'
  20737. });
  20738. me.addSprite(label);
  20739. me.scheduleUpdater(me.attr, 'layout');
  20740. },
  20741. applyMarker: function(config) {
  20742. var marker;
  20743. if (config) {
  20744. if (config.isSprite) {
  20745. marker = config;
  20746. } else {
  20747. marker = this.createMarker(config);
  20748. }
  20749. }
  20750. marker = this.resetMarker(marker, config);
  20751. return marker;
  20752. },
  20753. createMarker: function(config) {
  20754. var marker;
  20755. // If marker attributes are animated, the attributes change over
  20756. // time from default values to the values specified in the marker
  20757. // config. But the 'legenditem' sprite needs final values
  20758. // to properly layout its children.
  20759. delete config.animation;
  20760. if (config.type === 'image') {
  20761. delete config.width;
  20762. delete config.height;
  20763. }
  20764. marker = Ext.create('sprite.' + config.type, config);
  20765. return marker;
  20766. },
  20767. resetMarker: function(sprite, config) {
  20768. var size = config.size || this.defaultMarkerSize,
  20769. bbox, max, scale;
  20770. // Layout may not work properly,
  20771. // if the marker sprite is transformed to begin with.
  20772. sprite.setTransform([
  20773. 1,
  20774. 0,
  20775. 0,
  20776. 1,
  20777. 0,
  20778. 0
  20779. ], true);
  20780. if (config.type === 'image') {
  20781. sprite.setAttributes({
  20782. width: size,
  20783. height: size
  20784. });
  20785. } else {
  20786. // This should work with any sprite, irrespective of what attribute
  20787. // is used to control sprite's size ('size', 'r', or something else).
  20788. // However, the 'image' sprite above is a special case.
  20789. bbox = sprite.getBBox();
  20790. max = Math.max(bbox.width, bbox.height);
  20791. scale = size / max;
  20792. sprite.setAttributes({
  20793. scalingX: scale,
  20794. scalingY: scale
  20795. });
  20796. }
  20797. return sprite;
  20798. },
  20799. updateMarker: function(marker, oldMarker) {
  20800. var me = this;
  20801. me.removeSprite(oldMarker);
  20802. me.addSprite(marker);
  20803. me.scheduleUpdater(me.attr, 'layout');
  20804. },
  20805. updateSurface: function(surface, oldSurface) {
  20806. var me = this;
  20807. me.callParent([
  20808. surface,
  20809. oldSurface
  20810. ]);
  20811. if (surface) {
  20812. me.scheduleUpdater(me.attr, 'layout');
  20813. }
  20814. },
  20815. enabledUpdater: function(attr) {
  20816. var marker = this.getMarker();
  20817. if (marker) {
  20818. marker.setAttributes({
  20819. globalAlpha: attr.enabled ? 1 : 0.3
  20820. });
  20821. }
  20822. },
  20823. layoutUpdater: function() {
  20824. var me = this,
  20825. attr = me.attr,
  20826. label = me.getLabel(),
  20827. marker = me.getMarker(),
  20828. labelBBox, markerBBox, totalHeight;
  20829. // Measuring bounding boxes of transformed marker and label
  20830. // sprites and translating the sprites by required amount,
  20831. // makes layout virtually bullet-proof to unaccounted for
  20832. // changes in sprite attributes, whatever the sprite type may be.
  20833. markerBBox = marker.getBBox();
  20834. labelBBox = label.getBBox();
  20835. totalHeight = Math.max(markerBBox.height, labelBBox.height);
  20836. // Because we are getting an already transformed bounding box,
  20837. // we want to add to that transformation, not replace it,
  20838. // so setting translationX/Y attributes here would be inappropriate.
  20839. marker.transform([
  20840. 1,
  20841. 0,
  20842. 0,
  20843. 1,
  20844. -markerBBox.x,
  20845. -markerBBox.y + (totalHeight - markerBBox.height) / 2
  20846. ], true);
  20847. label.transform([
  20848. 1,
  20849. 0,
  20850. 0,
  20851. 1,
  20852. -labelBBox.x + markerBBox.width + attr.markerLabelGap,
  20853. -labelBBox.y + (totalHeight - labelBBox.height) / 2
  20854. ], true);
  20855. me.bboxUpdater(attr);
  20856. }
  20857. });
  20858. /**
  20859. * @private
  20860. */
  20861. Ext.define('Ext.chart.legend.sprite.Border', {
  20862. extend: 'Ext.draw.sprite.Rect',
  20863. alias: 'sprite.legendborder',
  20864. type: 'legendborder',
  20865. isLegendBorder: true
  20866. });
  20867. /**
  20868. * @private
  20869. * Singleton that provides methods used by the Ext.draw.Path
  20870. * for hit testing and finding path intersection points.
  20871. */
  20872. Ext.define('Ext.draw.PathUtil', function() {
  20873. var abs = Math.abs,
  20874. pow = Math.pow,
  20875. cos = Math.cos,
  20876. acos = Math.acos,
  20877. sqrt = Math.sqrt,
  20878. PI = Math.PI;
  20879. // For extra info see: http://pomax.github.io/bezierinfo/
  20880. return {
  20881. singleton: true,
  20882. requires: [
  20883. 'Ext.draw.overrides.hittest.Path',
  20884. 'Ext.draw.overrides.hittest.sprite.Path'
  20885. ],
  20886. /**
  20887. * @private
  20888. * Finds roots of a cubic equation in t, where t lies in the interval of [0,1].
  20889. * Based on http://www.particleincell.com/blog/2013/cubic-line-intersection/
  20890. * @param P {Number[]} Cubic equation coefficients.
  20891. * @return {Number[]} Returns an array of parametric intersection locations along the cubic,
  20892. * with -1 indicating an out-of-bounds intersection
  20893. * (before or after the end point or in the imaginary plane).
  20894. */
  20895. cubicRoots: function(P) {
  20896. var a = P[0],
  20897. b = P[1],
  20898. c = P[2],
  20899. d = P[3];
  20900. if (a === 0) {
  20901. return this.quadraticRoots(b, c, d);
  20902. }
  20903. // eslint-disable-next-line vars-on-top, one-var
  20904. var A = b / a,
  20905. B = c / a,
  20906. C = d / a,
  20907. Q = (3 * B - pow(A, 2)) / 9,
  20908. R = (9 * A * B - 27 * C - 2 * pow(A, 3)) / 54,
  20909. D = pow(Q, 3) + pow(R, 2),
  20910. // Polynomial discriminant.
  20911. t = [],
  20912. S, T, Im, th, i,
  20913. sign = Ext.Number.sign;
  20914. if (D >= 0) {
  20915. // Complex or duplicate roots.
  20916. S = sign(R + sqrt(D)) * pow(abs(R + sqrt(D)), 1 / 3);
  20917. T = sign(R - sqrt(D)) * pow(abs(R - sqrt(D)), 1 / 3);
  20918. t[0] = -A / 3 + (S + T);
  20919. // Real root.
  20920. t[1] = -A / 3 - (S + T) / 2;
  20921. // Real part of complex root.
  20922. t[2] = t[1];
  20923. // Real part of complex root.
  20924. Im = abs(sqrt(3) * (S - T) / 2);
  20925. // Complex part of root pair.
  20926. // Discard complex roots.
  20927. if (Im !== 0) {
  20928. t[1] = -1;
  20929. t[2] = -1;
  20930. }
  20931. } else {
  20932. // Distinct real roots.
  20933. th = acos(R / sqrt(-pow(Q, 3)));
  20934. t[0] = 2 * sqrt(-Q) * cos(th / 3) - A / 3;
  20935. t[1] = 2 * sqrt(-Q) * cos((th + 2 * PI) / 3) - A / 3;
  20936. t[2] = 2 * sqrt(-Q) * cos((th + 4 * PI) / 3) - A / 3;
  20937. }
  20938. // Discard out of spec roots.
  20939. for (i = 0; i < 3; i++) {
  20940. if (t[i] < 0 || t[i] > 1) {
  20941. t[i] = -1;
  20942. }
  20943. }
  20944. return t;
  20945. },
  20946. /**
  20947. * @private
  20948. * Finds roots of a quadratic equation in t, where t lies in the interval of [0,1].
  20949. * Takes three quadratic equation coefficients as parameters.
  20950. * @param a {Number}
  20951. * @param b {Number}
  20952. * @param c {Number}
  20953. * @return {Array}
  20954. */
  20955. quadraticRoots: function(a, b, c) {
  20956. var D, rD, t, i;
  20957. if (a === 0) {
  20958. return this.linearRoot(b, c);
  20959. }
  20960. D = b * b - 4 * a * c;
  20961. if (D === 0) {
  20962. // One real root.
  20963. t = [
  20964. -b / (2 * a)
  20965. ];
  20966. } else if (D > 0) {
  20967. // Distinct real roots.
  20968. rD = sqrt(D);
  20969. t = [
  20970. (-b - rD) / (2 * a),
  20971. (-b + rD) / (2 * a)
  20972. ];
  20973. } else {
  20974. // Complex roots.
  20975. return [];
  20976. }
  20977. for (i = 0; i < t.length; i++) {
  20978. if (t[i] < 0 || t[i] > 1) {
  20979. t[i] = -1;
  20980. }
  20981. }
  20982. return t;
  20983. },
  20984. /**
  20985. * @private
  20986. * Finds roots of a linear equation in t, where t lies in the interval of [0,1].
  20987. * Takes two linear equation coefficients as parameters.
  20988. * @param a {Number}
  20989. * @param b {Number}
  20990. * @return {Array}
  20991. */
  20992. linearRoot: function(a, b) {
  20993. var t = -b / a;
  20994. if (a === 0 || t < 0 || t > 1) {
  20995. return [];
  20996. }
  20997. return [
  20998. t
  20999. ];
  21000. },
  21001. /**
  21002. * @private
  21003. * Calculates the coefficients of a cubic function for the given coordinates.
  21004. * @param P0 {Number}
  21005. * @param P1 {Number}
  21006. * @param P2 {Number}
  21007. * @param P3 {Number}
  21008. * @return {Array}
  21009. */
  21010. bezierCoeffs: function(P0, P1, P2, P3) {
  21011. var Z = [];
  21012. Z[0] = -P0 + 3 * P1 - 3 * P2 + P3;
  21013. Z[1] = 3 * P0 - 6 * P1 + 3 * P2;
  21014. Z[2] = -3 * P0 + 3 * P1;
  21015. Z[3] = P0;
  21016. return Z;
  21017. },
  21018. /**
  21019. * @private
  21020. * Computes intersection points between a cubic spline and a line segment.
  21021. * Takes in x/y components of cubic control points and line segment start/end points
  21022. * as parameters.
  21023. * @param px1 {Number}
  21024. * @param px2 {Number}
  21025. * @param px3 {Number}
  21026. * @param px4 {Number}
  21027. * @param py1 {Number}
  21028. * @param py2 {Number}
  21029. * @param py3 {Number}
  21030. * @param py4 {Number}
  21031. * @param x1 {Number}
  21032. * @param y1 {Number}
  21033. * @param x2 {Number}
  21034. * @param y2 {Number}
  21035. * @return {Array} Array of intersection points, where each intersection point
  21036. * is itself a two-item array [x,y].
  21037. */
  21038. cubicLineIntersections: function(px1, px2, px3, px4, py1, py2, py3, py4, x1, y1, x2, y2) {
  21039. var P = [],
  21040. intersections = [],
  21041. // Finding line equation coefficients.
  21042. A = y1 - y2,
  21043. B = x2 - x1,
  21044. C = x1 * (y2 - y1) - y1 * (x2 - x1),
  21045. // Finding cubic Bezier curve equation coefficients.
  21046. bx = this.bezierCoeffs(px1, px2, px3, px4),
  21047. by = this.bezierCoeffs(py1, py2, py3, py4),
  21048. i, r, s, t, tt, ttt, cx, cy;
  21049. P[0] = A * bx[0] + B * by[0];
  21050. // t^3
  21051. P[1] = A * bx[1] + B * by[1];
  21052. // t^2
  21053. P[2] = A * bx[2] + B * by[2];
  21054. // t
  21055. P[3] = A * bx[3] + B * by[3] + C;
  21056. // 1
  21057. r = this.cubicRoots(P);
  21058. // Verify the roots are in bounds of the linear segment.
  21059. for (i = 0; i < r.length; i++) {
  21060. t = r[i];
  21061. if (t < 0 || t > 1) {
  21062. continue;
  21063. }
  21064. tt = t * t;
  21065. ttt = tt * t;
  21066. cx = bx[0] * ttt + bx[1] * tt + bx[2] * t + bx[3];
  21067. cy = by[0] * ttt + by[1] * tt + by[2] * t + by[3];
  21068. // Above is intersection point assuming infinitely long line segment,
  21069. // make sure we are also in bounds of the line.
  21070. if ((x2 - x1) !== 0) {
  21071. // If not vertical line
  21072. s = (cx - x1) / (x2 - x1);
  21073. } else {
  21074. s = (cy - y1) / (y2 - y1);
  21075. }
  21076. // In bounds?
  21077. if (!(s < 0 || s > 1)) {
  21078. intersections.push([
  21079. cx,
  21080. cy
  21081. ]);
  21082. }
  21083. }
  21084. return intersections;
  21085. },
  21086. /**
  21087. * @private
  21088. * Splits cubic Bezier curve into two cubic Bezier curves at point z,
  21089. * where z belongs to a range of [0, 1].
  21090. * Accepts cubic coefficients and point z as parameters.
  21091. * @param P1 {Number}
  21092. * @param P2 {Number}
  21093. * @param P3 {Number}
  21094. * @param P4 {Number}
  21095. * @param z Point to split the given curve at.
  21096. * @return {Array} Two-item array, where each item is itself an array
  21097. * of cubic coefficients.
  21098. */
  21099. splitCubic: function(P1, P2, P3, P4, z) {
  21100. var zz = z * z,
  21101. zzz = z * zz,
  21102. iz = z - 1,
  21103. izz = iz * iz,
  21104. izzz = iz * izz,
  21105. // Common point for both curves.
  21106. P = zzz * P4 - 3 * zz * iz * P3 + 3 * z * izz * P2 - izzz * P1;
  21107. return [
  21108. [
  21109. P1,
  21110. z * P2 - iz * P1,
  21111. zz * P3 - 2 * z * iz * P2 + izz * P1,
  21112. P
  21113. ],
  21114. [
  21115. P,
  21116. zz * P4 - 2 * z * iz * P3 + izz * P2,
  21117. z * P4 - iz * P3,
  21118. P4
  21119. ]
  21120. ];
  21121. },
  21122. /**
  21123. * @private
  21124. * Returns the dimension of a cubic Bezier curve in a single direction.
  21125. * @param a {Number}
  21126. * @param b {Number}
  21127. * @param c {Number}
  21128. * @param d {Number}
  21129. * @return {Array} Two-item array representing cubic's range in the given direction.
  21130. */
  21131. cubicDimension: function(a, b, c, d) {
  21132. var qa = 3 * (-a + 3 * (b - c) + d),
  21133. qb = 6 * (a - 2 * b + c),
  21134. qc = -3 * (a - b),
  21135. x, y,
  21136. min = Math.min(a, d),
  21137. max = Math.max(a, d),
  21138. delta;
  21139. if (qa === 0) {
  21140. if (qb === 0) {
  21141. return [
  21142. min,
  21143. max
  21144. ];
  21145. } else {
  21146. x = -qc / qb;
  21147. if (0 < x && x < 1) {
  21148. y = this.interpolateCubic(a, b, c, d, x);
  21149. min = Math.min(min, y);
  21150. max = Math.max(max, y);
  21151. }
  21152. }
  21153. } else {
  21154. delta = qb * qb - 4 * qa * qc;
  21155. if (delta >= 0) {
  21156. delta = sqrt(delta);
  21157. x = (delta - qb) / 2 / qa;
  21158. if (0 < x && x < 1) {
  21159. y = this.interpolateCubic(a, b, c, d, x);
  21160. min = Math.min(min, y);
  21161. max = Math.max(max, y);
  21162. }
  21163. if (delta > 0) {
  21164. x -= delta / qa;
  21165. if (0 < x && x < 1) {
  21166. y = this.interpolateCubic(a, b, c, d, x);
  21167. min = Math.min(min, y);
  21168. max = Math.max(max, y);
  21169. }
  21170. }
  21171. }
  21172. }
  21173. return [
  21174. min,
  21175. max
  21176. ];
  21177. },
  21178. /**
  21179. * @private
  21180. * Calculates a value of a cubic function at the given point t. In other words
  21181. * returns a * (1 - t) ^ 3 + 3 * b (1 - t) ^ 2 * t + 3 * c (1 - t) * t ^ 3 + d * t ^ 3
  21182. * for given a, b, c, d and t, where t belongs to an interval of [0, 1].
  21183. * @param a {Number}
  21184. * @param b {Number}
  21185. * @param c {Number}
  21186. * @param d {Number}
  21187. * @param t {Number}
  21188. * @return {Number}
  21189. */
  21190. interpolateCubic: function(a, b, c, d, t) {
  21191. var rate;
  21192. if (t === 0) {
  21193. return a;
  21194. }
  21195. if (t === 1) {
  21196. return d;
  21197. }
  21198. rate = (1 - t) / t;
  21199. return t * t * t * (d + rate * (3 * c + rate * (3 * b + rate * a)));
  21200. },
  21201. /**
  21202. * @private
  21203. * Computes intersection points between two cubic Bezier curve segments.
  21204. * Takes x/y components of control points for two Bezier curve segments.
  21205. * @param ax1 {Number}
  21206. * @param ax2 {Number}
  21207. * @param ax3 {Number}
  21208. * @param ax4 {Number}
  21209. * @param ay1 {Number}
  21210. * @param ay2 {Number}
  21211. * @param ay3 {Number}
  21212. * @param ay4 {Number}
  21213. * @param bx1 {Number}
  21214. * @param bx2 {Number}
  21215. * @param bx3 {Number}
  21216. * @param bx4 {Number}
  21217. * @param by1 {Number}
  21218. * @param by2 {Number}
  21219. * @param by3 {Number}
  21220. * @param by4 {Number}
  21221. * @return {Array} Array of intersection points, where each intersection point
  21222. * is itself a two-item array [x,y].
  21223. */
  21224. cubicsIntersections: function(ax1, ax2, ax3, ax4, ay1, ay2, ay3, ay4, bx1, bx2, bx3, bx4, by1, by2, by3, by4) {
  21225. /* eslint-disable max-len */
  21226. var me = this,
  21227. axDim = me.cubicDimension(ax1, ax2, ax3, ax4),
  21228. ayDim = me.cubicDimension(ay1, ay2, ay3, ay4),
  21229. bxDim = me.cubicDimension(bx1, bx2, bx3, bx4),
  21230. byDim = me.cubicDimension(by1, by2, by3, by4),
  21231. splitAx, splitAy, splitBx, splitBy,
  21232. points = [];
  21233. // Curves' bounding boxes don't intersect.
  21234. if (axDim[0] > bxDim[1] || axDim[1] < bxDim[0] || ayDim[0] > byDim[1] || ayDim[1] < byDim[0]) {
  21235. return [];
  21236. }
  21237. // Both curves occupy sub-pixel areas which is effectively their intersection point.
  21238. 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) {
  21239. return [
  21240. [
  21241. (ax1 + ax4) * 0.5,
  21242. (ay1 + ay2) * 0.5
  21243. ]
  21244. ];
  21245. }
  21246. splitAx = me.splitCubic(ax1, ax2, ax3, ax4, 0.5);
  21247. splitAy = me.splitCubic(ay1, ay2, ay3, ay4, 0.5);
  21248. splitBx = me.splitCubic(bx1, bx2, bx3, bx4, 0.5);
  21249. splitBy = me.splitCubic(by1, by2, by3, by4, 0.5);
  21250. points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[0].concat(splitAy[0], splitBx[0], splitBy[0])));
  21251. points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[0].concat(splitAy[0], splitBx[1], splitBy[1])));
  21252. points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[1].concat(splitAy[1], splitBx[0], splitBy[0])));
  21253. points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[1].concat(splitAy[1], splitBx[1], splitBy[1])));
  21254. return points;
  21255. },
  21256. /* eslint-enable max-len */
  21257. /**
  21258. * @private
  21259. * Returns the point [x,y] where two line segments intersect or null.
  21260. * Takes x/y components of the start and end point of the segments as parameters.
  21261. * Based on Paul Bourke's explanation:
  21262. * http://paulbourke.net/geometry/pointlineplane/
  21263. * @param x1 {Number}
  21264. * @param y1 {Number}
  21265. * @param x2 {Number}
  21266. * @param y2 {Number}
  21267. * @param x3 {Number}
  21268. * @param y3 {Number}
  21269. * @param x4 {Number}
  21270. * @param y4 {Number}
  21271. * @return {Number[]|null}
  21272. */
  21273. linesIntersection: function(x1, y1, x2, y2, x3, y3, x4, y4) {
  21274. var d = (x2 - x1) * (y4 - y3) - (y2 - y1) * (x4 - x3),
  21275. ua, ub;
  21276. if (d === 0) {
  21277. // Lines are parallel.
  21278. return null;
  21279. }
  21280. ua = ((x4 - x3) * (y1 - y3) - (x1 - x3) * (y4 - y3)) / d;
  21281. ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / d;
  21282. if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
  21283. return [
  21284. x1 + ua * (x2 - x1),
  21285. // x
  21286. y1 + ua * (y2 - y1)
  21287. ];
  21288. }
  21289. // y
  21290. return null;
  21291. },
  21292. // The intersection point is outside one or both segments.
  21293. /**
  21294. * @private
  21295. * Checks if a point belongs to a line segment.
  21296. * Takes x/y components of the start and end points of the segment and the point's
  21297. * coordinates as parameters.
  21298. * @param x1 {Number}
  21299. * @param y1 {Number}
  21300. * @param x2 {Number}
  21301. * @param y2 {Number}
  21302. * @param x {Number}
  21303. * @param y {Number}
  21304. * @return {Boolean}
  21305. */
  21306. pointOnLine: function(x1, y1, x2, y2, x, y) {
  21307. var t, _;
  21308. if (abs(x2 - x1) < abs(y2 - y1)) {
  21309. _ = x1;
  21310. x1 = y1;
  21311. y1 = _;
  21312. _ = x2;
  21313. x2 = y2;
  21314. y2 = _;
  21315. _ = x;
  21316. x = y;
  21317. y = _;
  21318. }
  21319. t = (x - x1) / (x2 - x1);
  21320. if (t < 0 || t > 1) {
  21321. return false;
  21322. }
  21323. return abs(y1 + t * (y2 - y1) - y) < 4;
  21324. },
  21325. /**
  21326. * @private
  21327. * Checks if a point belongs to a cubic Bezier curve segment.
  21328. * Takes x/y components of the control points of the segment and the point's
  21329. * coordinates as parameters.
  21330. * @param px1 {Number}
  21331. * @param px2 {Number}
  21332. * @param px3 {Number}
  21333. * @param px4 {Number}
  21334. * @param py1 {Number}
  21335. * @param py2 {Number}
  21336. * @param py3 {Number}
  21337. * @param py4 {Number}
  21338. * @param x {Number}
  21339. * @param y {Number}
  21340. * @return {Boolean}
  21341. */
  21342. pointOnCubic: function(px1, px2, px3, px4, py1, py2, py3, py4, x, y) {
  21343. // Finding cubic Bezier curve equation coefficients.
  21344. var me = this,
  21345. bx = me.bezierCoeffs(px1, px2, px3, px4),
  21346. by = me.bezierCoeffs(py1, py2, py3, py4),
  21347. i, j, rx, ry, t;
  21348. bx[3] -= x;
  21349. by[3] -= y;
  21350. rx = me.cubicRoots(bx);
  21351. ry = me.cubicRoots(by);
  21352. for (i = 0; i < rx.length; i++) {
  21353. t = rx[i];
  21354. for (j = 0; j < ry.length; j++) {
  21355. // TODO: for more accurate results tolerance should be dynamic
  21356. // TODO: based on the length and shape of the segment.
  21357. if (t >= 0 && t <= 1 && abs(t - ry[j]) < 0.05) {
  21358. return true;
  21359. }
  21360. }
  21361. }
  21362. return false;
  21363. }
  21364. };
  21365. });
  21366. Ext.define('Ext.draw.overrides.hittest.All', {
  21367. requires: [
  21368. 'Ext.draw.PathUtil',
  21369. 'Ext.draw.overrides.hittest.sprite.Instancing',
  21370. 'Ext.draw.overrides.hittest.Surface'
  21371. ]
  21372. });
  21373. /**
  21374. * This class uses `Ext.draw.sprite.Sprite` to render the chart legend.
  21375. *
  21376. * The DOM legend is essentially a data view docked inside a draw container, which a chart is.
  21377. * The sprite legend, on the other hand, is not a foreign entity in a draw container,
  21378. * and is rendered in a draw surface with sprites, just like series and axes.
  21379. *
  21380. * This means that:
  21381. *
  21382. * * it is styleable with chart themes
  21383. * * it shows up in chart preview and chart download
  21384. * * it renders markers exactly as they are in the series
  21385. * * it can't be styled with CSS
  21386. * * it doesn't scroll, instead the items are grouped into columns,
  21387. * and the legend grows in size as the number of items increases
  21388. *
  21389. */
  21390. Ext.define('Ext.chart.legend.SpriteLegend', {
  21391. alias: 'legend.sprite',
  21392. type: 'sprite',
  21393. isLegend: true,
  21394. isSpriteLegend: true,
  21395. mixins: [
  21396. 'Ext.mixin.Observable'
  21397. ],
  21398. requires: [
  21399. 'Ext.chart.legend.sprite.Item',
  21400. 'Ext.chart.legend.sprite.Border',
  21401. 'Ext.draw.overrides.hittest.All',
  21402. 'Ext.draw.Animator'
  21403. ],
  21404. config: {
  21405. /**
  21406. * @cfg {'top'/'left'/'right'/'bottom'} docked
  21407. * The position of the legend in the chart.
  21408. */
  21409. docked: 'bottom',
  21410. /**
  21411. * @cfg {Ext.chart.legend.store.Store} store
  21412. * The {@link Ext.chart.legend.store.Store} to bind this legend to.
  21413. * @private
  21414. */
  21415. store: null,
  21416. /**
  21417. * @cfg {Ext.chart.AbstractChart} chart
  21418. * The chart that the store belongs to.
  21419. */
  21420. chart: null,
  21421. /**
  21422. * @cfg {Ext.draw.Surface} surface
  21423. * The chart surface used to render legend sprites.
  21424. * @protected
  21425. */
  21426. surface: null,
  21427. /**
  21428. * @cfg {Object} size
  21429. * The size of the area occupied by the legend's sprites.
  21430. * This is set by the legend itself and then used during chart layout
  21431. * to make sure the 'legend' surface is big enough to accommodate
  21432. * legend sprites.
  21433. * @cfg {Number} size.width
  21434. * @cfg {Number} size.height
  21435. * @readonly
  21436. */
  21437. size: {
  21438. width: 0,
  21439. height: 0
  21440. },
  21441. /**
  21442. * @cfg {Boolean} toggleable
  21443. * `true` to allow series items to have their visibility
  21444. * toggled by interaction with the legend items.
  21445. */
  21446. toggleable: true,
  21447. /**
  21448. * @cfg {Number} padding
  21449. * The padding amount between legend items and legend border.
  21450. */
  21451. padding: 10,
  21452. label: {
  21453. preciseMeasurement: true
  21454. },
  21455. /**
  21456. * The sprite to use as a legend item marker. By default a corresponding series
  21457. * marker is used. If the series has no marker, the `circle` sprite
  21458. * is used as a legend item marker, where its `fillStyle`, `strokeStyle` and
  21459. * `lineWidth` match that of the series. The size of a legend item marker is
  21460. * controlled by the `size` property, which to defaults to `10` (pixels).
  21461. */
  21462. marker: {},
  21463. /**
  21464. * @cfg {Object} border
  21465. * The border that goes around legend item sprites.
  21466. * The type of the sprite is determined by this config,
  21467. * while the styling comes from a theme {@link Ext.chart.theme.Base #legend}.
  21468. * If both this config and the theme provide values for the
  21469. * same configs, the values from this config are used.
  21470. * The sprite class used a legend border should have the `isLegendBorder`
  21471. * property set to true on the prototype. The legend border sprite
  21472. * should also have the `x`, `y`, `width` and `height` attributes
  21473. * that determine it's position and dimensions.
  21474. */
  21475. border: {
  21476. $value: {
  21477. type: 'legendborder'
  21478. },
  21479. // The config should be processed at the time of the 'getSprites' call,
  21480. // when we already have the legend surface, otherwise the border sprite
  21481. // will not be added to the surface.
  21482. lazy: true
  21483. },
  21484. /**
  21485. * @cfg {Object} background
  21486. * Sets the legend background.
  21487. * This can be a gradient object, image, or color. This config works similarly
  21488. * to the {@link Ext.chart.AbstractChart#background} config.
  21489. */
  21490. background: null,
  21491. /**
  21492. * @cfg {Boolean} hidden Toggles the visibility of the legend.
  21493. */
  21494. hidden: false
  21495. },
  21496. sprites: null,
  21497. spriteZIndexes: {
  21498. background: 0,
  21499. border: 1,
  21500. // Item sprites should have a higher zIndex than border,
  21501. // or they won't react to clicks.
  21502. item: 2
  21503. },
  21504. dockedValues: {
  21505. left: true,
  21506. right: true,
  21507. top: true,
  21508. bottom: true
  21509. },
  21510. constructor: function(config) {
  21511. var me = this;
  21512. me.oldSize = {
  21513. width: 0,
  21514. height: 0
  21515. };
  21516. me.getId();
  21517. me.mixins.observable.constructor.call(me, config);
  21518. },
  21519. applyStore: function(store) {
  21520. return store && Ext.StoreManager.lookup(store);
  21521. },
  21522. updateStore: function(store, oldStore) {
  21523. var me = this;
  21524. if (oldStore) {
  21525. oldStore.un('datachanged', me.onDataChanged, me);
  21526. oldStore.un('update', me.onDataUpdate, me);
  21527. }
  21528. if (store) {
  21529. store.on('datachanged', me.onDataChanged, me);
  21530. store.on('update', me.onDataUpdate, me);
  21531. me.onDataChanged(store);
  21532. }
  21533. me.performLayout();
  21534. },
  21535. //<debug>
  21536. applyDocked: function(docked) {
  21537. if (!(docked in this.dockedValues)) {
  21538. Ext.raise("Invalid 'docked' config value.");
  21539. }
  21540. return docked;
  21541. },
  21542. //</debug>
  21543. updateDocked: function(docked) {
  21544. this.isTop = docked === 'top';
  21545. if (!this.isConfiguring) {
  21546. this.layoutChart();
  21547. }
  21548. },
  21549. updateHidden: function(hidden) {
  21550. var surface;
  21551. this.getChart();
  21552. // 'chart' updater will set the surface
  21553. surface = this.getSurface();
  21554. if (surface) {
  21555. surface.setHidden(hidden);
  21556. }
  21557. if (!this.isConfiguring) {
  21558. this.layoutChart();
  21559. }
  21560. },
  21561. /**
  21562. * @private
  21563. */
  21564. layoutChart: function() {
  21565. var chart;
  21566. if (!this.isConfiguring) {
  21567. chart = this.getChart();
  21568. if (chart) {
  21569. chart.scheduleLayout();
  21570. }
  21571. }
  21572. },
  21573. /**
  21574. * @private
  21575. * Calculates and returns the legend surface rect and adjusts the passed `chartRect`
  21576. * accordingly. The first time this is called, the `SpriteLegend` will have zero size
  21577. * (no width or height).
  21578. * @param {Number[]} chartRect [left, top, width, height] components as an array.
  21579. * @return {Number[]} [left, top, width, height] components as an array, or null.
  21580. */
  21581. computeRect: function(chartRect) {
  21582. var rect, docked, size, height, width;
  21583. if (this.getHidden()) {
  21584. return null;
  21585. }
  21586. rect = [
  21587. 0,
  21588. 0,
  21589. 0,
  21590. 0
  21591. ];
  21592. docked = this.getDocked();
  21593. size = this.getSize();
  21594. height = size.height;
  21595. width = size.width;
  21596. switch (docked) {
  21597. case 'top':
  21598. rect[1] = chartRect[1];
  21599. rect[2] = chartRect[2];
  21600. rect[3] = height;
  21601. chartRect[1] += height;
  21602. chartRect[3] -= height;
  21603. break;
  21604. case 'bottom':
  21605. chartRect[3] -= height;
  21606. rect[1] = chartRect[3];
  21607. rect[2] = chartRect[2];
  21608. rect[3] = height;
  21609. break;
  21610. case 'left':
  21611. chartRect[0] += width;
  21612. chartRect[2] -= width;
  21613. rect[2] = width;
  21614. rect[3] = chartRect[3];
  21615. break;
  21616. case 'right':
  21617. chartRect[2] -= width;
  21618. rect[0] = chartRect[2];
  21619. rect[2] = width;
  21620. rect[3] = chartRect[3];
  21621. break;
  21622. }
  21623. return rect;
  21624. },
  21625. applyBorder: function(config) {
  21626. var border;
  21627. if (config) {
  21628. if (config.isSprite) {
  21629. border = config;
  21630. } else {
  21631. border = Ext.create('sprite.' + config.type, config);
  21632. }
  21633. }
  21634. if (border) {
  21635. border.isLegendBorder = true;
  21636. border.setAttributes({
  21637. zIndex: this.spriteZIndexes.border
  21638. });
  21639. }
  21640. return border;
  21641. },
  21642. updateBorder: function(border, oldBorder) {
  21643. var surface = this.getSurface();
  21644. this.borderSprite = null;
  21645. if (surface) {
  21646. if (oldBorder) {
  21647. surface.remove(oldBorder);
  21648. }
  21649. if (border) {
  21650. this.borderSprite = surface.add(border);
  21651. }
  21652. }
  21653. },
  21654. scheduleLayout: function() {
  21655. if (!this.scheduledLayoutId) {
  21656. this.scheduledLayoutId = Ext.draw.Animator.schedule('performLayout', this);
  21657. }
  21658. },
  21659. cancelLayout: function() {
  21660. Ext.draw.Animator.cancel(this.scheduledLayoutId);
  21661. this.scheduledLayoutId = null;
  21662. },
  21663. performLayout: function() {
  21664. var me = this,
  21665. size = me.getSize(),
  21666. gap = me.getPadding(),
  21667. sprites = me.getSprites(),
  21668. surface = me.getSurface(),
  21669. background = me.getBackground(),
  21670. surfaceRect = surface.getRect(),
  21671. store = me.getStore(),
  21672. ln = (sprites && sprites.length) || 0,
  21673. i, sprite;
  21674. if (!surface || !surfaceRect || !store) {
  21675. return false;
  21676. }
  21677. me.cancelLayout();
  21678. // eslint-disable-next-line vars-on-top, one-var
  21679. var docked = me.getDocked(),
  21680. surfaceWidth = surfaceRect[2],
  21681. surfaceHeight = surfaceRect[3],
  21682. border = me.borderSprite,
  21683. bboxes = [],
  21684. startX, // Coordinates of the top-left corner.
  21685. startY, // of the first 'legenditem' sprite.
  21686. columnSize, // Number of items in a column.
  21687. columnCount, // Number of columns.
  21688. columnWidth, itemsWidth, itemsHeight, paddedItemsWidth, // The horizontal span of all 'legenditem' sprites.
  21689. paddedItemsHeight, // The vertical span of all 'legenditem' sprites.
  21690. paddedBorderWidth, paddedBorderHeight, // eslint-disable-line no-unused-vars
  21691. itemHeight, bbox, x, y;
  21692. for (i = 0; i < ln; i++) {
  21693. sprite = sprites[i];
  21694. bbox = sprite.getBBox();
  21695. bboxes.push(bbox);
  21696. }
  21697. if (bbox) {
  21698. itemHeight = bbox.height;
  21699. }
  21700. switch (docked) {
  21701. /*
  21702. Horizontal legend.
  21703. The outer box is the legend surface.
  21704. The inner box is the legend border.
  21705. There's a fixed amount of padding between all the items,
  21706. denoted by ##. This amount is controlled by the 'padding' config
  21707. of the legend.
  21708. |-------------------------------------------------------------|
  21709. | ## |
  21710. | |---------------------------------------------------| |
  21711. | | ## ## ## | |
  21712. | | -------- ----------- -------- | |
  21713. | ## | ## | Item 0 | ## | Item 2 | ## | Item 4 | ## | ## |
  21714. | | -------- ----------- -------- | |
  21715. | | ## ## ## | |
  21716. | | ---------- --------- | |
  21717. | | ## | Item 1 | ## | Item 3 | | |
  21718. | | ---------- --------- | |
  21719. | | ## ## | |
  21720. | |---------------------------------------------------| |
  21721. | ## |
  21722. |-------------------------------------------------------------|
  21723. */
  21724. case 'bottom':
  21725. case 'top':
  21726. // surface must have a width before we can proceed to layout top/bottom
  21727. // docked legend. width may be 0 if we are rendered into an inactive tab.
  21728. // see https://sencha.jira.com/browse/EXTJS-22454
  21729. if (!surfaceWidth) {
  21730. return false;
  21731. };
  21732. columnSize = 0;
  21733. // Split legend items into columns until the width is suitable.
  21734. do {
  21735. itemsWidth = 0;
  21736. columnWidth = 0;
  21737. columnCount = 0;
  21738. columnSize++;
  21739. for (i = 0; i < ln; i++) {
  21740. bbox = bboxes[i];
  21741. if (bbox.width > columnWidth) {
  21742. columnWidth = bbox.width;
  21743. columnWidth = Math.min(columnWidth, surfaceWidth - (gap * 4));
  21744. }
  21745. if ((i + 1) % columnSize === 0) {
  21746. itemsWidth += columnWidth;
  21747. columnWidth = 0;
  21748. columnCount++;
  21749. }
  21750. }
  21751. if (i % columnSize !== 0) {
  21752. itemsWidth += columnWidth;
  21753. columnCount++;
  21754. }
  21755. paddedItemsWidth = itemsWidth + (columnCount - 1) * gap;
  21756. paddedBorderWidth = paddedItemsWidth + gap * 4;
  21757. } while (// if the column width is greater than available surface width,
  21758. // set it to maximum available width
  21759. paddedBorderWidth > surfaceWidth);
  21760. paddedItemsHeight = itemHeight * columnSize + (columnSize - 1) * gap;
  21761. break;
  21762. /*
  21763. Vertical legend.
  21764. |-----------------------------------------------|
  21765. | ## |
  21766. | |-------------------------------------| |
  21767. | | ## ## | |
  21768. | | -------- ----------- | |
  21769. | | ## | Item 0 | ## | Item 1 | ## | |
  21770. | | -------- ----------- | |
  21771. | | ## ## | |
  21772. | | ---------- --------- | |
  21773. | ## | ## | Item 2 | ## | Item 3 | | ## |
  21774. | | ---------- --------- | |
  21775. | | ## | |
  21776. | | -------- | |
  21777. | | ## | Item 4 | | |
  21778. | | -------- | |
  21779. | | ## | |
  21780. | |-------------------------------------| |
  21781. | ## |
  21782. |-----------------------------------------------|
  21783. */
  21784. case 'right':
  21785. case 'left':
  21786. // surface must have a height before we can proceed to layout right/left
  21787. // docked legend. height may be 0 if we are rendered into an inactive tab.
  21788. // see https://sencha.jira.com/browse/EXTJS-22454
  21789. if (!surfaceHeight) {
  21790. return false;
  21791. };
  21792. columnSize = ln * 2;
  21793. // Split legend items into columns until the height is suitable.
  21794. do {
  21795. columnSize = (columnSize >> 1) + (columnSize % 2);
  21796. itemsWidth = 0;
  21797. itemsHeight = 0;
  21798. columnWidth = 0;
  21799. columnCount = 0;
  21800. for (i = 0; i < ln; i++) {
  21801. bbox = bboxes[i];
  21802. if (!columnCount) {
  21803. itemsHeight += bbox.height;
  21804. }
  21805. if (bbox.width > columnWidth) {
  21806. columnWidth = bbox.width;
  21807. }
  21808. if ((i + 1) % columnSize === 0) {
  21809. itemsWidth += columnWidth;
  21810. columnWidth = 0;
  21811. columnCount++;
  21812. }
  21813. }
  21814. if (i % columnSize !== 0) {
  21815. itemsWidth += columnWidth;
  21816. columnCount++;
  21817. }
  21818. paddedItemsWidth = itemsWidth + (columnCount - 1) * gap;
  21819. paddedItemsHeight = itemsHeight + (columnSize - 1) * gap;
  21820. paddedBorderWidth = paddedItemsWidth + gap * 4;
  21821. paddedBorderHeight = paddedItemsHeight + gap * 4;
  21822. } while (// Integer division by 2, plus remainder.
  21823. // itemsHeight is determined by the height of the first column.
  21824. paddedItemsHeight > surfaceHeight);
  21825. break;
  21826. }
  21827. startX = (surfaceWidth - paddedItemsWidth) / 2;
  21828. startY = (surfaceHeight - paddedItemsHeight) / 2;
  21829. x = 0;
  21830. y = 0;
  21831. columnWidth = 0;
  21832. for (i = 0; i < ln; i++) {
  21833. sprite = sprites[i];
  21834. bbox = bboxes[i];
  21835. sprite.setAttributes({
  21836. translationX: startX + x,
  21837. translationY: startY + y
  21838. });
  21839. if (bbox.width > columnWidth) {
  21840. columnWidth = bbox.width;
  21841. }
  21842. if ((i + 1) % columnSize === 0) {
  21843. x += columnWidth + gap;
  21844. y = 0;
  21845. columnWidth = 0;
  21846. } else {
  21847. y += bbox.height + gap;
  21848. }
  21849. }
  21850. if (border) {
  21851. border.setAttributes({
  21852. hidden: !ln,
  21853. x: startX - gap,
  21854. y: startY - gap,
  21855. width: paddedItemsWidth + gap * 2,
  21856. height: paddedItemsHeight + gap * 2
  21857. });
  21858. }
  21859. size.width = border.attr.width + gap * 2;
  21860. size.height = border.attr.height + gap * 2;
  21861. if (size.width !== me.oldSize.width || size.height !== me.oldSize.height) {
  21862. // Do not simply assign size to oldSize, as we want them to be
  21863. // separate objects.
  21864. Ext.apply(me.oldSize, size);
  21865. // Legend size has changed, so we return 'false' to cancel the current
  21866. // chart layout (this method is called by chart's 'performLayout' method)
  21867. // and manually start a new chart layout.
  21868. me.getChart().scheduleLayout();
  21869. return false;
  21870. }
  21871. if (background) {
  21872. me.resizeBackground(surface, background);
  21873. }
  21874. surface.renderFrame();
  21875. return true;
  21876. },
  21877. // Doesn't include the border sprite which also belongs to the 'legend'
  21878. // surface. To get it, use the 'getBorder' method.
  21879. getSprites: function() {
  21880. this.updateSprites();
  21881. return this.sprites;
  21882. },
  21883. /**
  21884. * @private
  21885. * Creates a 'legenditem' sprite in the given surface
  21886. * using the legend store record data provided.
  21887. * @param {Ext.draw.Surface} surface
  21888. * @param {Ext.chart.legend.store.Item} record
  21889. * @return {Ext.chart.legend.sprite.Item}
  21890. */
  21891. createSprite: function(surface, record) {
  21892. var me = this,
  21893. data = record.data,
  21894. chart = me.getChart(),
  21895. series = chart.get(data.series),
  21896. seriesMarker = series.getMarker(),
  21897. sprite = null,
  21898. markerConfig, labelConfig, legendItemConfig;
  21899. if (surface) {
  21900. markerConfig = series.getMarkerStyleByIndex(data.index);
  21901. markerConfig.fillStyle = data.mark;
  21902. markerConfig.hidden = false;
  21903. if (seriesMarker && seriesMarker.type) {
  21904. markerConfig.type = seriesMarker.type;
  21905. }
  21906. Ext.apply(markerConfig, me.getMarker());
  21907. markerConfig.surface = surface;
  21908. labelConfig = me.getLabel();
  21909. legendItemConfig = {
  21910. type: 'legenditem',
  21911. zIndex: me.spriteZIndexes.item,
  21912. text: data.name,
  21913. enabled: !data.disabled,
  21914. marker: markerConfig,
  21915. label: labelConfig,
  21916. series: data.series,
  21917. record: record
  21918. };
  21919. sprite = surface.add(legendItemConfig);
  21920. }
  21921. return sprite;
  21922. },
  21923. /**
  21924. * @private
  21925. * Creates legend item sprites and associates them with legend store records.
  21926. * Updates attributes of the sprites when legend store data changes.
  21927. */
  21928. updateSprites: function() {
  21929. var me = this,
  21930. chart = me.getChart(),
  21931. store = me.getStore(),
  21932. surface = me.getSurface(),
  21933. item, items, itemSprite, i, ln, sprites, unusedSprites, border;
  21934. if (!(chart && store && surface)) {
  21935. return;
  21936. }
  21937. me.sprites = sprites = me.sprites || [];
  21938. items = store.getData().items;
  21939. ln = items.length;
  21940. for (i = 0; i < ln; i++) {
  21941. item = items[i];
  21942. itemSprite = sprites[i];
  21943. if (itemSprite) {
  21944. me.updateSprite(itemSprite, item);
  21945. } else {
  21946. itemSprite = me.createSprite(surface, item);
  21947. surface.add(itemSprite);
  21948. sprites.push(itemSprite);
  21949. }
  21950. }
  21951. unusedSprites = Ext.Array.splice(sprites, i, sprites.length);
  21952. for (i = 0 , ln = unusedSprites.length; i < ln; i++) {
  21953. itemSprite = unusedSprites[i];
  21954. itemSprite.destroy();
  21955. }
  21956. border = me.getBorder();
  21957. if (border) {
  21958. me.borderSprite = border;
  21959. }
  21960. me.updateTheme(chart.getTheme());
  21961. },
  21962. /**
  21963. * @private
  21964. * Updates the given legend item sprite based on store record data.
  21965. * @param {Ext.chart.legend.sprite.Item} sprite
  21966. * @param {Ext.chart.legend.store.Item} record
  21967. */
  21968. updateSprite: function(sprite, record) {
  21969. var data = record.data,
  21970. chart = this.getChart(),
  21971. series = chart.get(data.series),
  21972. marker, label, markerConfig;
  21973. if (sprite) {
  21974. label = sprite.getLabel();
  21975. label.setAttributes({
  21976. text: data.name
  21977. });
  21978. sprite.setAttributes({
  21979. enabled: !data.disabled
  21980. });
  21981. sprite.setConfig({
  21982. series: data.series,
  21983. record: record
  21984. });
  21985. markerConfig = series.getMarkerStyleByIndex(data.index);
  21986. markerConfig.fillStyle = data.mark;
  21987. markerConfig.hidden = false;
  21988. Ext.apply(markerConfig, this.getMarker());
  21989. marker = sprite.getMarker();
  21990. marker.setAttributes({
  21991. fillStyle: markerConfig.fillStyle,
  21992. strokeStyle: markerConfig.strokeStyle
  21993. });
  21994. sprite.layoutUpdater(sprite.attr);
  21995. }
  21996. },
  21997. updateChart: function(newChart, oldChart) {
  21998. var me = this;
  21999. if (oldChart) {
  22000. me.setSurface(null);
  22001. }
  22002. if (newChart) {
  22003. me.setSurface(newChart.getSurface('legend'));
  22004. }
  22005. },
  22006. updateSurface: function(surface, oldSurface) {
  22007. if (oldSurface) {
  22008. oldSurface.el.un('click', 'onClick', this);
  22009. // The surface should not be destroyed here, just cleared.
  22010. // E.g. we may remove the sprite legend only to add another one.
  22011. oldSurface.removeAll(true);
  22012. }
  22013. if (surface) {
  22014. surface.isLegendSurface = true;
  22015. surface.el.on('click', 'onClick', this);
  22016. }
  22017. },
  22018. onClick: function(event) {
  22019. var chart = this.getChart(),
  22020. surface = this.getSurface(),
  22021. result, point;
  22022. if (chart && chart.hasFirstLayout && surface) {
  22023. point = surface.getEventXY(event);
  22024. result = surface.hitTest(point);
  22025. if (result && result.sprite) {
  22026. this.toggleItem(result.sprite);
  22027. }
  22028. }
  22029. },
  22030. applyBackground: function(newBackground, oldBackground) {
  22031. var me = this,
  22032. // It's important to get the `chart` first here,
  22033. // because the `surface` is set by the `chart` updater.
  22034. chart = me.getChart(),
  22035. surface = me.getSurface(),
  22036. background;
  22037. background = chart.refreshBackground(surface, newBackground, oldBackground);
  22038. if (background) {
  22039. background.setAttributes({
  22040. zIndex: me.spriteZIndexes.background
  22041. });
  22042. }
  22043. return background;
  22044. },
  22045. resizeBackground: function(surface, background) {
  22046. var width = background.attr.width,
  22047. height = background.attr.height,
  22048. surfaceRect = surface.getRect();
  22049. if (surfaceRect && (width !== surfaceRect[2] || height !== surfaceRect[3])) {
  22050. background.setAttributes({
  22051. width: surfaceRect[2],
  22052. height: surfaceRect[3]
  22053. });
  22054. }
  22055. },
  22056. themeableConfigs: {
  22057. background: true
  22058. },
  22059. updateTheme: function(theme) {
  22060. var me = this,
  22061. surface = me.getSurface(),
  22062. sprites = surface.getItems(),
  22063. legendTheme = theme.getLegend(),
  22064. labelConfig = me.getLabel(),
  22065. configs = me.self.getConfigurator().configs,
  22066. themeableConfigs = me.themeableConfigs,
  22067. themeOnlyIfConfigured = me.themeOnlyIfConfigured,
  22068. initialConfig = me.getInitialConfig(),
  22069. defaultConfig = me.defaultConfig,
  22070. value, cfg, isObjValue, isUnusedConfig, initialValue, sprite, style, labelSprite, key, attr, i, ln;
  22071. for (i = 0 , ln = sprites.length; i < ln; i++) {
  22072. sprite = sprites[i];
  22073. if (sprite.isLegendItem) {
  22074. style = legendTheme.label;
  22075. if (style) {
  22076. attr = null;
  22077. for (key in style) {
  22078. if (!(key in labelConfig)) {
  22079. attr = attr || {};
  22080. attr[key] = style[key];
  22081. }
  22082. }
  22083. if (attr) {
  22084. labelSprite = sprite.getLabel();
  22085. labelSprite.setAttributes(attr);
  22086. }
  22087. }
  22088. continue;
  22089. } else if (sprite.isLegendBorder) {
  22090. style = legendTheme.border;
  22091. } else {
  22092. continue;
  22093. }
  22094. if (style) {
  22095. attr = {};
  22096. for (key in style) {
  22097. if (!(key in sprite.config)) {
  22098. attr[key] = style[key];
  22099. }
  22100. }
  22101. sprite.setAttributes(attr);
  22102. }
  22103. }
  22104. value = legendTheme.background;
  22105. cfg = configs.background;
  22106. if (value !== null && value !== undefined && cfg) {}
  22107. // TODO
  22108. for (key in legendTheme) {
  22109. if (!(key in themeableConfigs)) {
  22110. continue;
  22111. }
  22112. value = legendTheme[key];
  22113. cfg = configs[key];
  22114. if (value !== null && value !== undefined && cfg) {
  22115. initialValue = initialConfig[key];
  22116. isObjValue = Ext.isObject(value);
  22117. isUnusedConfig = initialValue === defaultConfig[key];
  22118. if (isObjValue) {
  22119. if (isUnusedConfig && themeOnlyIfConfigured[key]) {
  22120. continue;
  22121. }
  22122. value = Ext.merge({}, value, initialValue);
  22123. }
  22124. if (isUnusedConfig || isObjValue) {
  22125. me[cfg.names.set](value);
  22126. }
  22127. }
  22128. }
  22129. },
  22130. onDataChanged: function(store) {
  22131. this.updateSprites();
  22132. this.scheduleLayout();
  22133. },
  22134. onDataUpdate: function(store, record) {
  22135. var me = this,
  22136. sprites = me.sprites,
  22137. ln = sprites.length,
  22138. i = 0,
  22139. sprite, spriteRecord, match;
  22140. for (; i < ln; i++) {
  22141. sprite = sprites[i];
  22142. spriteRecord = sprite.getRecord();
  22143. if (spriteRecord === record) {
  22144. match = sprite;
  22145. break;
  22146. }
  22147. }
  22148. if (match) {
  22149. me.updateSprite(match, record);
  22150. me.scheduleLayout();
  22151. }
  22152. },
  22153. toggleItem: function(sprite) {
  22154. var disabledCount = 0,
  22155. canToggle = true,
  22156. store, i, count, record, disabled;
  22157. if (!this.getToggleable() || !sprite.isLegendItem) {
  22158. return;
  22159. }
  22160. store = this.getStore();
  22161. if (store) {
  22162. count = store.getCount();
  22163. for (i = 0; i < count; i++) {
  22164. record = store.getAt(i);
  22165. if (record.get('disabled')) {
  22166. disabledCount++;
  22167. }
  22168. }
  22169. canToggle = count - disabledCount > 1;
  22170. record = sprite.getRecord();
  22171. if (record) {
  22172. disabled = record.get('disabled');
  22173. if (disabled || canToggle) {
  22174. // This will trigger AbstractChart.onLegendStoreUpdate.
  22175. record.set('disabled', !disabled);
  22176. sprite.setAttributes({
  22177. enabled: disabled
  22178. });
  22179. }
  22180. }
  22181. }
  22182. },
  22183. destroy: function() {
  22184. var me = this;
  22185. me.destroying = true;
  22186. me.cancelLayout();
  22187. me.setChart(null);
  22188. me.callParent();
  22189. }
  22190. });
  22191. /**
  22192. * Chart captions can be used to place titles, subtitles, credits and other captions
  22193. * inside a chart. Please see the chart's {@link Ext.chart.AbstractChart#captions}
  22194. * config documentation for the general description of the way captions work, and
  22195. * refer to the documentation of this class' configs for details.
  22196. */
  22197. Ext.define('Ext.chart.Caption', {
  22198. mixins: [
  22199. 'Ext.mixin.Observable',
  22200. 'Ext.mixin.Bindable'
  22201. ],
  22202. isCaption: true,
  22203. config: {
  22204. /**
  22205. * The weight controls the order in which the captions are created.
  22206. * Captions with lower weights are created first.
  22207. * This affects chart's layout. For example, if two captions are docked
  22208. * to the 'top', the one with the lower weight will end up on top
  22209. * of the other.
  22210. */
  22211. weight: 0,
  22212. /**
  22213. * @cfg {String} text
  22214. * The text displayed by the caption.
  22215. * Multi-line captions are allowed, e.g.:
  22216. *
  22217. * captions: {
  22218. * title: {
  22219. * text: 'India\'s tiger population\n'
  22220. * + 'from 1970 to 2015'
  22221. * }
  22222. * }
  22223. *
  22224. */
  22225. text: '',
  22226. /**
  22227. * @cfg {'left'/'center'/'right'} [align='center']
  22228. * Determines the horizontal alignment of the caption's text.
  22229. */
  22230. align: 'center',
  22231. /**
  22232. * @cfg {'series'/'chart'} [alignTo='series']
  22233. * Whether to align the caption to the 'series' (default) or the 'chart'.
  22234. */
  22235. alignTo: 'series',
  22236. /**
  22237. * @cfg {Number} padding
  22238. * The uniform padding applied to both top and bottom of the caption's text.
  22239. */
  22240. padding: 0,
  22241. /**
  22242. * @cfg {Boolean} [hidden=false]
  22243. * Controls the visibility of the caption.
  22244. */
  22245. hidden: false,
  22246. /**
  22247. * @cfg {'top'/'bottom'} [docked='top']
  22248. * The position of the caption in a chart.
  22249. */
  22250. docked: 'top',
  22251. /**
  22252. * @cfg {Object} style
  22253. * Style attributes for the caption's text.
  22254. * All attributes that are valid {@link Ext.draw.sprite.Text text sprite} attributes
  22255. * are valid here. However, only font attributes (such as `fontSize`, `fontFamily`, ...),
  22256. * color attributes (such as `fillStyle`) and `textAlign` attribute are guaranteed to
  22257. * produce correct behavior. For example, transform attributes are not officially supported.
  22258. */
  22259. style: {
  22260. fontSize: '14px',
  22261. fontWeight: 'bold',
  22262. fontFamily: 'Verdana, Aria, sans-serif'
  22263. },
  22264. /**
  22265. * @private
  22266. * @cfg {Ext.chart.AbstractChart} chart
  22267. * The chart the label belongs to.
  22268. */
  22269. chart: null,
  22270. /**
  22271. * @private
  22272. * The text sprite used to render caption's text.
  22273. */
  22274. sprite: {
  22275. type: 'text',
  22276. preciseMeasurement: true,
  22277. zIndex: 10
  22278. },
  22279. //<debug>
  22280. /**
  22281. * @private
  22282. * @cfg {Boolean} debug
  22283. * Whether to show the bounding boxes or not.
  22284. */
  22285. debug: false,
  22286. //</debug>
  22287. /**
  22288. * @private
  22289. * The logical rect of the caption in the `surfaceName` surface.
  22290. */
  22291. rect: null
  22292. },
  22293. surfaceName: 'caption',
  22294. constructor: function(config) {
  22295. var me = this,
  22296. id;
  22297. if ('id' in config) {
  22298. id = config.id;
  22299. } else if ('id' in me.config) {
  22300. id = me.config.id;
  22301. } else {
  22302. id = me.getId();
  22303. }
  22304. me.setId(id);
  22305. me.mixins.observable.constructor.call(me, config);
  22306. me.initBindable();
  22307. },
  22308. updateChart: function() {
  22309. if (!this.isConfiguring) {
  22310. // Re-create caption's sprite in another chart.
  22311. this.setSprite({
  22312. type: 'text'
  22313. });
  22314. }
  22315. },
  22316. applySprite: function(sprite) {
  22317. var me = this,
  22318. chart = me.getChart(),
  22319. surface = me.surface = chart.getSurface(me.surfaceName);
  22320. //<debug>
  22321. me.rectSprite = surface.add({
  22322. type: 'rect',
  22323. fillStyle: 'yellow',
  22324. strokeStyle: 'red'
  22325. });
  22326. //</debug>
  22327. return sprite && surface.add(sprite);
  22328. },
  22329. updateSprite: function(sprite, oldSprite) {
  22330. if (oldSprite) {
  22331. oldSprite.destroy();
  22332. }
  22333. },
  22334. updateText: function(text) {
  22335. this.getSprite().setAttributes({
  22336. text: text
  22337. });
  22338. },
  22339. updateStyle: function(style) {
  22340. this.getSprite().setAttributes(style);
  22341. },
  22342. //<debug>
  22343. updateDebug: function(debug) {
  22344. var me = this,
  22345. sprite = me.getSprite();
  22346. if (debug && !me.rectSprite) {
  22347. me.rectSprite = me.surface.add({
  22348. type: 'rect',
  22349. fillStyle: 'yellow',
  22350. strokeStyle: 'red'
  22351. });
  22352. }
  22353. if (sprite) {
  22354. sprite.setAttributes({
  22355. debug: debug ? {
  22356. bbox: true
  22357. } : null
  22358. });
  22359. }
  22360. if (me.rectSprite) {
  22361. me.rectSprite.setAttributes({
  22362. hidden: !debug
  22363. });
  22364. }
  22365. if (!me.isConfiguring) {
  22366. me.surface.renderFrame();
  22367. }
  22368. },
  22369. //</debug>
  22370. updateRect: function(rect) {
  22371. if (this.rectSprite) {
  22372. this.rectSprite.setAttributes({
  22373. x: rect[0],
  22374. y: rect[1],
  22375. width: rect[2],
  22376. height: rect[3]
  22377. });
  22378. }
  22379. },
  22380. updateDocked: function() {
  22381. var chart = this.getChart();
  22382. if (chart && !this.isConfiguring) {
  22383. chart.scheduleLayout();
  22384. }
  22385. },
  22386. /**
  22387. * @private
  22388. * Computes and sets the caption's rect.
  22389. * Shrinks the given chart rect to accomodate the caption.
  22390. * The chart rect is [top, left, width, height] in chart's
  22391. * body element coordinates.
  22392. * The shrink rect is {left, top, right, bottom} in `caption`
  22393. * surface coordinates.
  22394. */
  22395. computeRect: function(chartRect, shrinkRect) {
  22396. if (this.getHidden()) {
  22397. return null;
  22398. }
  22399. // eslint-disable-next-line vars-on-top
  22400. var rect = [
  22401. 0,
  22402. 0,
  22403. chartRect[2],
  22404. 0
  22405. ],
  22406. docked = this.getDocked(),
  22407. padding = this.getPadding(),
  22408. textSize = this.getSprite().getBBox(),
  22409. height = textSize.height + padding * 2;
  22410. switch (docked) {
  22411. case 'top':
  22412. rect[1] = shrinkRect.top;
  22413. rect[3] = height;
  22414. chartRect[1] += height;
  22415. chartRect[3] -= height;
  22416. shrinkRect.top += height;
  22417. break;
  22418. case 'bottom':
  22419. chartRect[3] -= height;
  22420. shrinkRect.bottom -= height;
  22421. rect[1] = shrinkRect.bottom;
  22422. rect[3] = height;
  22423. break;
  22424. }
  22425. this.setRect(rect);
  22426. },
  22427. alignRect: function(seriesRect) {
  22428. var surfaceRect = this.surface.getRect(),
  22429. rect = this.getRect();
  22430. rect[0] = seriesRect[0] - surfaceRect[0];
  22431. rect[2] = seriesRect[2];
  22432. // Slice to trigger the applier/updater.
  22433. this.setRect(rect.slice());
  22434. },
  22435. performLayout: function() {
  22436. var me = this,
  22437. rect = me.getRect(),
  22438. x = rect[0],
  22439. y = rect[1],
  22440. width = rect[2],
  22441. height = rect[3],
  22442. sprite = me.getSprite(),
  22443. tx = sprite.attr.translationX,
  22444. ty = sprite.attr.translationY,
  22445. bbox = sprite.getBBox(),
  22446. align = me.getAlign(),
  22447. dx, dy;
  22448. switch (align) {
  22449. case 'left':
  22450. dx = x - bbox.x;
  22451. break;
  22452. case 'right':
  22453. dx = (x + width) - (bbox.x + bbox.width);
  22454. break;
  22455. case 'center':
  22456. dx = x + (width - bbox.width) / 2 - bbox.x;
  22457. break;
  22458. }
  22459. dy = y + (height - bbox.height) / 2 - bbox.y;
  22460. sprite.setAttributes({
  22461. translationX: tx + dx,
  22462. translationY: ty + dy
  22463. });
  22464. },
  22465. destroy: function() {
  22466. var me = this;
  22467. //<debug>
  22468. if (me.rectSprite) {
  22469. me.rectSprite.destroy();
  22470. }
  22471. //</debug>
  22472. me.getSprite().destroy();
  22473. me.callParent();
  22474. }
  22475. });
  22476. /**
  22477. * The data model for legend items.
  22478. */
  22479. Ext.define('Ext.chart.legend.store.Item', {
  22480. extend: 'Ext.data.Model',
  22481. fields: [
  22482. 'id',
  22483. 'name',
  22484. // The series title.
  22485. 'mark',
  22486. // The color of the series.
  22487. 'disabled',
  22488. // The state of the series.
  22489. 'series',
  22490. // A reference to the series instance.
  22491. // A sprite index, e.g. for stacked or pie series.
  22492. // For such series an individual component of the series
  22493. // is hidden or shown when the legend item is toggled.
  22494. 'index'
  22495. ]
  22496. });
  22497. /**
  22498. * The store type used for legend items.
  22499. */
  22500. Ext.define('Ext.chart.legend.store.Store', {
  22501. extend: 'Ext.data.Store',
  22502. requires: [
  22503. 'Ext.chart.legend.store.Item'
  22504. ],
  22505. model: 'Ext.chart.legend.store.Item',
  22506. isLegendStore: true,
  22507. config: {
  22508. autoDestroy: true
  22509. }
  22510. });
  22511. /**
  22512. * The Ext.chart package provides the capability to visualize data.
  22513. * Each chart binds directly to a {@link Ext.data.Store store} enabling automatic
  22514. * updates of the chart. A chart configuration object has some overall styling
  22515. * options as well as an array of axes and series. A chart instance example could
  22516. * look like this:
  22517. *
  22518. * Ext.create('Ext.chart.CartesianChart', {
  22519. * width: 800,
  22520. * height: 600,
  22521. * animation: {
  22522. * easing: 'backOut',
  22523. * duration: 500
  22524. * },
  22525. * store: store1,
  22526. * legend: {
  22527. * position: 'right'
  22528. * },
  22529. * axes: [
  22530. * // ...some axes options...
  22531. * ],
  22532. * series: [
  22533. * // ...some series options...
  22534. * ]
  22535. * });
  22536. *
  22537. * In this example we set the `width` and `height` of a chart; We decide whether
  22538. * our series are animated or not and we select a store to be bound to the chart;
  22539. * We also set the legend to the right part of the chart.
  22540. *
  22541. * You can register certain interactions such as {@link Ext.chart.interactions.PanZoom}
  22542. * on the chart by specifying an array of names or more specific config objects.
  22543. * All the events will be wired automatically.
  22544. *
  22545. * You can also listen to series `itemXXX` events on both chart and series level.
  22546. *
  22547. * For example:
  22548. *
  22549. * Ext.create('Ext.chart.CartesianChart', {
  22550. * plugins: {
  22551. * chartitemevents: {
  22552. * moveEvents: true
  22553. * }
  22554. * },
  22555. * store: {
  22556. * fields: ['pet', 'households', 'total'],
  22557. * data: [
  22558. * {pet: 'Cats', households: 38, total: 93},
  22559. * {pet: 'Dogs', households: 45, total: 79},
  22560. * {pet: 'Fish', households: 13, total: 171}
  22561. * ]
  22562. * },
  22563. * axes: [{
  22564. * type: 'numeric',
  22565. * position: 'left'
  22566. * }, {
  22567. * type: 'category',
  22568. * position: 'bottom'
  22569. * }],
  22570. * series: [{
  22571. * type: 'bar',
  22572. * xField: 'pet',
  22573. * yField: 'households',
  22574. * listeners: {
  22575. * itemmousemove: function (series, item, event) {
  22576. * console.log('itemmousemove', item.category, item.field);
  22577. * }
  22578. * }
  22579. * }, {
  22580. * type: 'line',
  22581. * xField: 'pet',
  22582. * yField: 'total',
  22583. * marker: true
  22584. * }],
  22585. * listeners: { // Listen to itemclick events on all series.
  22586. * itemclick: function (chart, item, event) {
  22587. * console.log('itemclick', item.category, item.field);
  22588. * }
  22589. * }
  22590. * });
  22591. *
  22592. * Important! It's generally a poor design choice to put interactive charts
  22593. * inside scrollable views, in such cases it's not possible to tell
  22594. * which component should respond to the interaction.
  22595. * Since charts are typically interactive their default touch action config
  22596. * looks as follows: {@link Ext.draw.Container#touchAction}.
  22597. * If you do have a chart inside a scrollable view, even if it has no interactions,
  22598. * you have to set its `touchAction` config to the following:
  22599. *
  22600. * touchAction: {
  22601. * panX: true,
  22602. * panY: true
  22603. * }
  22604. *
  22605. * Otherwise, if a touch action started on a chart, a swipe will not scroll
  22606. * the view.
  22607. *
  22608. * For more information about the axes and series configurations please check
  22609. * the documentation of each series (Line, Bar, Pie, etc).
  22610. *
  22611. */
  22612. Ext.define('Ext.chart.AbstractChart', {
  22613. extend: 'Ext.draw.Container',
  22614. requires: [
  22615. 'Ext.chart.theme.Default',
  22616. 'Ext.chart.series.Series',
  22617. 'Ext.chart.interactions.Abstract',
  22618. 'Ext.chart.axis.Axis',
  22619. 'Ext.chart.Util',
  22620. 'Ext.data.StoreManager',
  22621. 'Ext.chart.legend.Legend',
  22622. 'Ext.chart.legend.SpriteLegend',
  22623. 'Ext.chart.Caption',
  22624. 'Ext.chart.legend.store.Store',
  22625. 'Ext.data.Store'
  22626. ],
  22627. isChart: true,
  22628. defaultBindProperty: 'store',
  22629. /**
  22630. * @event beforerefresh
  22631. * Fires before a refresh to the chart data is called. If the `beforerefresh`
  22632. * handler returns `false` the {@link #refresh} action will be canceled.
  22633. * @param {Ext.chart.AbstractChart} this
  22634. */
  22635. /**
  22636. * @event refresh
  22637. * Fires after the chart data has been refreshed.
  22638. * @param {Ext.chart.AbstractChart} this
  22639. */
  22640. /**
  22641. * @event redraw
  22642. * Fires after each {@link #event!redraw} call.
  22643. * @param {Ext.chart.AbstractChart} this
  22644. */
  22645. /**
  22646. * @private
  22647. * @event layout
  22648. * Fires after the final layout is done.
  22649. * (Two layouts may be required to fully render a chart.
  22650. * Typically for the initial render and every time thickness
  22651. * of the chart's axes changes.)
  22652. * @param {Ext.chart.AbstractChart} this
  22653. */
  22654. /**
  22655. * @event itemhighlight
  22656. * Fires when an item is highlighted.
  22657. * @param {Ext.chart.AbstractChart} this
  22658. * @param {Object} newItem The new highlight item.
  22659. * @param {Object} oldItem The old highlight item.
  22660. */
  22661. /**
  22662. * @event itemhighlightchange
  22663. * Fires when an item's highlight changes.
  22664. * @param this
  22665. * @param {Object} newItem The new highlight item.
  22666. * @param {Object} oldItem The old highlight item.
  22667. */
  22668. /**
  22669. * @event itemmousemove
  22670. * Fires when the mouse is moved on a series item.
  22671. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22672. * plugin be added to the chart.
  22673. * @param {Ext.chart.AbstractChart} chart
  22674. * @param {Object} item
  22675. * @param {Event} event
  22676. */
  22677. /**
  22678. * @event itemmouseup
  22679. * Fires when a mouseup event occurs on a series item.
  22680. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22681. * plugin be added to the chart.
  22682. * @param {Ext.chart.AbstractChart} chart
  22683. * @param {Object} item
  22684. * @param {Event} event
  22685. */
  22686. /**
  22687. * @event itemmousedown
  22688. * Fires when a mousedown event occurs on a series item.
  22689. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22690. * plugin be added to the chart.
  22691. * @param {Ext.chart.AbstractChart} chart
  22692. * @param {Object} item
  22693. * @param {Event} event
  22694. */
  22695. /**
  22696. * @event itemmouseover
  22697. * Fires when the mouse enters a series item.
  22698. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22699. * plugin be added to the chart.
  22700. * @param {Ext.chart.AbstractChart} chart
  22701. * @param {Object} item
  22702. * @param {Event} event
  22703. */
  22704. /**
  22705. * @event itemmouseout
  22706. * Fires when the mouse exits a series item.
  22707. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22708. * plugin be added to the chart.
  22709. * @param {Ext.chart.AbstractChart} chart
  22710. * @param {Object} item
  22711. * @param {Event} event
  22712. */
  22713. /**
  22714. * @event itemclick
  22715. * Fires when a click event occurs on a series item.
  22716. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22717. * plugin be added to the chart.
  22718. * @param {Ext.chart.AbstractChart} chart
  22719. * @param {Object} item
  22720. * @param {Event} event
  22721. */
  22722. /**
  22723. * @event itemdblclick
  22724. * Fires when a double click event occurs on a series item.
  22725. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22726. * plugin be added to the chart.
  22727. * @param {Ext.chart.AbstractChart} chart
  22728. * @param {Object} item
  22729. * @param {Event} event
  22730. */
  22731. /**
  22732. * @event itemtap
  22733. * Fires when a tap event occurs on a series item.
  22734. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22735. * plugin be added to the chart.
  22736. * @param {Ext.chart.AbstractChart} chart
  22737. * @param {Object} item
  22738. * @param {Event} event
  22739. */
  22740. /**
  22741. * @event storechange
  22742. * Fires when the store of the chart changes.
  22743. * @param {Ext.chart.AbstractChart} chart
  22744. * @param {Ext.data.Store} newStore
  22745. * @param {Ext.data.Store} oldStore
  22746. */
  22747. config: {
  22748. /**
  22749. * @cfg {Ext.data.Store/String/Object} store
  22750. * The data source to which the chart is bound.
  22751. * Acceptable values for this property are:
  22752. *
  22753. * - **any {@link Ext.data.Store Store} class / subclass**
  22754. * - **an {@link Ext.data.Store#storeId ID of a store}**
  22755. * - **a {@link Ext.data.Store Store} config object**. When passing a config you can
  22756. * specify the store type by alias. Passing a config object with a store type will
  22757. * dynamically create a new store of that type when the chart is instantiated.
  22758. *
  22759. * For example:
  22760. *
  22761. * Ext.define('MyApp.store.Customer', {
  22762. * extend: 'Ext.data.Store',
  22763. * alias: 'store.customerstore',
  22764. *
  22765. * fields: ['name', 'value']
  22766. * });
  22767. *
  22768. *
  22769. * Ext.create({
  22770. * xtype: 'cartesian',
  22771. * renderTo: document.body,
  22772. * height: 400,
  22773. * width: 400,
  22774. * store: {
  22775. * type: 'customerstore',
  22776. * data: [{
  22777. * name: 'metric one',
  22778. * value: 10
  22779. * }]
  22780. * },
  22781. * axes: [{
  22782. * type: 'numeric',
  22783. * position: 'left',
  22784. * title: {
  22785. * text: 'Sample Values',
  22786. * fontSize: 15
  22787. * },
  22788. * fields: 'value'
  22789. * }, {
  22790. * type: 'category',
  22791. * position: 'bottom',
  22792. * title: {
  22793. * text: 'Sample Values',
  22794. * fontSize: 15
  22795. * },
  22796. * fields: 'name'
  22797. * }],
  22798. * series: {
  22799. * type: 'bar',
  22800. * xField: 'name',
  22801. * yField: 'value'
  22802. * }
  22803. * });
  22804. */
  22805. store: 'ext-empty-store',
  22806. /**
  22807. * @cfg {String} [theme="default"]
  22808. * The name of the theme to be used. A theme defines the colors and styles
  22809. * used by the series, axes, markers and other chart components.
  22810. * Please see the documentation for the {@link Ext.chart.theme.Base} class
  22811. * for more information.
  22812. *
  22813. * Possible theme values are:
  22814. * - 'green', 'sky', 'red', 'purple', 'blue', 'yellow'
  22815. * - 'category1' to 'category6'
  22816. * - and the above theme names with the '-gradients' suffix, e.g. 'green-gradients'
  22817. *
  22818. * IMPORTANT: You should require the themes you use; for example, to use:
  22819. *
  22820. * theme: 'blue'
  22821. *
  22822. * the `Ext.chart.theme.Blue` class should be required:
  22823. *
  22824. * requires: 'Ext.chart.theme.Blue'
  22825. *
  22826. * To require all chart themes:
  22827. *
  22828. * requires: 'Ext.chart.theme.*'
  22829. */
  22830. theme: 'default',
  22831. /**
  22832. * Chart captions can be used to place titles, subtitles, credits and other captions
  22833. * inside a chart. For example:
  22834. *
  22835. * captions: {
  22836. * title: {
  22837. * text: 'Consumer Price Index'
  22838. * },
  22839. * subtitle: {
  22840. * text: 'from 2007 to 2017'
  22841. * },
  22842. * credits: {
  22843. * text: 'Source: 'bls.gov'
  22844. * }
  22845. * }
  22846. *
  22847. * One can use any names for properties in the `captions` config, but the `title`,
  22848. * `subtitle` and `credits` ones have a special meaning - they are automatically
  22849. * themeable. The `title` and `subtitle` are automatically docked to the top of
  22850. * a chart and the `credits` to the bottom. The `title` uses the largest and
  22851. * the heaviest font, while the `credits` - the smallest and the lightest.
  22852. *
  22853. * Other captions besides those three can be easily defined as well:
  22854. *
  22855. * captions: {
  22856. * myFancyCaption: {
  22857. * docked: 'bottom',
  22858. * align: 'left',
  22859. * style: {
  22860. * fontSize: 18,
  22861. * fontWeight: 'bold',
  22862. * fontFamily: 'Verdana'
  22863. * }
  22864. * }
  22865. * }
  22866. *
  22867. * If a caption config only specifies text, a shorthand syntax is also possible:
  22868. *
  22869. * captions: {
  22870. * title: 'Consumer Price Index'
  22871. * }
  22872. *
  22873. * @cfg {Object} captions
  22874. * @cfg {Ext.chart.Caption} captions.title
  22875. * @cfg {Ext.chart.Caption} captions.subtitle
  22876. * @cfg {Ext.chart.Caption} captions.credits
  22877. */
  22878. captions: null,
  22879. /**
  22880. * @cfg {Object} style
  22881. * The style for the chart component.
  22882. */
  22883. style: null,
  22884. /**
  22885. * @cfg {Boolean/Object} [animation=true]
  22886. * Defaults to `easeInOut` easing with a 500ms duration.
  22887. * See {@link Ext.draw.modifier.Animation} for possible configuration options.
  22888. */
  22889. animation: !Ext.isIE8,
  22890. /**
  22891. * @cfg {Ext.chart.series.Series/Array} series
  22892. * Array of {@link Ext.chart.series.Series Series} instances or config objects.
  22893. * For example:
  22894. *
  22895. * series: [{
  22896. * type: 'column',
  22897. * axis: 'left',
  22898. * listeners: {
  22899. * 'afterrender': function() {
  22900. * console.log('afterrender');
  22901. * }
  22902. * },
  22903. * xField: 'category',
  22904. * yField: 'data1'
  22905. * }]
  22906. */
  22907. series: [],
  22908. /**
  22909. * @cfg {Ext.chart.axis.Axis/Array/Object} axes
  22910. * Array of {@link Ext.chart.axis.Axis Axis} instances or config objects.
  22911. * For example:
  22912. *
  22913. * axes: [{
  22914. * type: 'numeric',
  22915. * position: 'left',
  22916. * title: 'Number of Hits',
  22917. * minimum: 0
  22918. * }, {
  22919. * type: 'category',
  22920. * position: 'bottom',
  22921. * title: 'Month of the Year'
  22922. * }]
  22923. */
  22924. axes: [],
  22925. /**
  22926. * @cfg {Ext.chart.legend.Legend/Ext.chart.legend.SpriteLegend/Boolean} legend
  22927. * The legend config for the chart. If specified, a legend block will be shown
  22928. * next to the chart.
  22929. * Each legend item displays the {@link Ext.chart.series.Series#title title}
  22930. * of the series, the color of the series and allows to toggle the visibility
  22931. * of the series (at least one series should remain visible).
  22932. *
  22933. * Sencha Charts support two types of legends: sprite based and DOM based.
  22934. *
  22935. * The sprite based legend can be shown in chart {@link Ext.draw.Container#preview preview}
  22936. * and is a part of the downloaded {@link Ext.draw.Container#download chart image}.
  22937. * The sprite based legend is always displayed in full and takes as much space as necessary,
  22938. * the legend items are split into columns to use the available space efficiently.
  22939. * The sprite based legend is styled via a {@link Ext.chart.theme.Base chart theme}.
  22940. *
  22941. * The DOM based legend supports RTL.
  22942. * It occupies a fixed width or height and scrolls when the content overflows.
  22943. * The DOM based legend is styled via CSS rules.
  22944. *
  22945. * By default the sprite legend is used. The type can be explicitly specified:
  22946. *
  22947. * legend: {
  22948. * type: 'dom', // 'sprite' is another possible value
  22949. * docked: 'top'
  22950. * }
  22951. *
  22952. * If the legend config is set to `true`, the sprite legend will be used
  22953. * docked to the bottom.
  22954. */
  22955. legend: null,
  22956. /**
  22957. * @cfg {Array} colors
  22958. * Array of colors/gradients to override the color of items and legends.
  22959. */
  22960. colors: null,
  22961. /**
  22962. * @cfg {Object/Number/String} insetPadding
  22963. * The amount of inset padding in pixels for the chart.
  22964. * Inset padding is the padding from the boundary of the chart to any
  22965. * of its contents.
  22966. */
  22967. insetPadding: {
  22968. top: 10,
  22969. left: 10,
  22970. right: 10,
  22971. bottom: 10
  22972. },
  22973. /**
  22974. * @cfg {Object} background Set the chart background.
  22975. * This can be a gradient object, image, or color.
  22976. *
  22977. * For example, if `background` were to be a color we could set the object as
  22978. *
  22979. * background: '#ccc'
  22980. *
  22981. * You can specify an image by using:
  22982. *
  22983. * background: {
  22984. * type: 'image',
  22985. * src: 'http://path.to.image/'
  22986. * }
  22987. *
  22988. * Also you can specify a gradient by using the gradient object syntax:
  22989. *
  22990. * background: {
  22991. * type: 'linear',
  22992. * degrees: 0,
  22993. * stops: [
  22994. * {
  22995. * offset: 0,
  22996. * color: 'white'
  22997. * },
  22998. * {
  22999. * offset: 1,
  23000. * color: 'blue'
  23001. * }
  23002. * ]
  23003. * }
  23004. */
  23005. background: null,
  23006. /**
  23007. * @cfg {Array} interactions
  23008. * Interactions are optional modules that can be plugged in to a chart
  23009. * to allow the user to interact with the chart and its data in special ways.
  23010. * The `interactions` config takes an Array of Object configurations,
  23011. * each one corresponding to a particular interaction class identified
  23012. * by a `type` property:
  23013. *
  23014. * new Ext.chart.AbstractChart({
  23015. * renderTo: Ext.getBody(),
  23016. * width: 800,
  23017. * height: 600,
  23018. * store: store1,
  23019. * axes: [
  23020. * // ...some axes options...
  23021. * ],
  23022. * series: [
  23023. * // ...some series options...
  23024. * ],
  23025. * interactions: [{
  23026. * type: 'interactiontype'
  23027. * // ...additional configs for the interaction...
  23028. * }]
  23029. * });
  23030. *
  23031. * When adding an interaction which uses only its default configuration
  23032. * (no extra properties other than `type`), you can alternately specify
  23033. * only the type as a String rather than the full Object:
  23034. *
  23035. * interactions: ['reset', 'rotate']
  23036. *
  23037. * The current supported interaction types include:
  23038. *
  23039. * - {@link Ext.chart.interactions.PanZoom panzoom} - allows pan and zoom of axes
  23040. * - {@link Ext.chart.interactions.ItemHighlight itemhighlight} - allows highlighting
  23041. * of series data points
  23042. * - {@link Ext.chart.interactions.ItemInfo iteminfo} - allows displaying details of
  23043. * a data point in a popup panel
  23044. * - {@link Ext.chart.interactions.Rotate rotate} - allows rotation of pie and radar series
  23045. *
  23046. * See the documentation for each of those interaction classes to see how they
  23047. * can be configured.
  23048. *
  23049. * Additional custom interactions can be registered using `'interactions.'` alias prefix.
  23050. */
  23051. interactions: [],
  23052. /**
  23053. * @private
  23054. * The main area of the chart where grid and series are drawn.
  23055. */
  23056. mainRect: null,
  23057. /**
  23058. * @private
  23059. * Override value.
  23060. */
  23061. resizeHandler: null,
  23062. /**
  23063. * @cfg {Object} highlightItem
  23064. * The current highlight item in the chart.
  23065. * The object must be the one that you get from item events.
  23066. *
  23067. * Note that series can also own highlight items.
  23068. * This notion is separate from this one and should not be used at the same time.
  23069. */
  23070. highlightItem: null,
  23071. /* eslint-disable indent */
  23072. surfaceZIndexes: {
  23073. background: 0,
  23074. // Contains the backround 'rect' sprite.
  23075. main: 1,
  23076. // Contains grid lines and CrossZoom overlay 'rect' sprite.
  23077. grid: 2,
  23078. // Reserved.
  23079. series: 3,
  23080. // Contains series sprites.
  23081. axis: 4,
  23082. // No actual `axis` surface is created, but this zIndex is used
  23083. // for all axis surfaces (one surface is created per axis).
  23084. chart: 5,
  23085. // Covers whole chart, minus the legend area.
  23086. // Contains sprites defined in the `sprites` config,
  23087. // title, subtitle and credits.
  23088. caption: 6,
  23089. // Contains title, subtitle and credits sprites.
  23090. overlay: 7,
  23091. // This surface will typically contain chart labels
  23092. // and interaction sprites like crosshair lines.
  23093. // With cartesian charts, equivalent in size to the `series` surface.
  23094. // With polar charts, equivalent in size to the `chart` surface.
  23095. legend: 8
  23096. }
  23097. },
  23098. // `SpriteLegend` surface.
  23099. /* eslint-enable indent */
  23100. /**
  23101. * @private
  23102. */
  23103. legendStore: null,
  23104. /**
  23105. * When this is non-zero, changes to sprite attributes apply instantly.
  23106. * See {@link #getAnimation}.
  23107. * @private
  23108. */
  23109. animationSuspendCount: 0,
  23110. /**
  23111. * @private
  23112. */
  23113. chartLayoutSuspendCount: 0,
  23114. /**
  23115. * @private
  23116. */
  23117. chartLayoutCount: 0,
  23118. /**
  23119. * @private
  23120. */
  23121. scheduledLayoutId: null,
  23122. /**
  23123. * @private
  23124. */
  23125. axisThicknessSuspendCount: 0,
  23126. /**
  23127. * @private
  23128. * Indicates that thickness of one or more axes has changed,
  23129. * at the time of {@link #performLayout} call. I.e. 'performLayout'
  23130. * should be called again when current layout is done.
  23131. */
  23132. isThicknessChanged: false,
  23133. constructor: function(config) {
  23134. var me = this;
  23135. me.itemListeners = {};
  23136. me.surfaceMap = {};
  23137. me.chartComponents = {};
  23138. me.isInitializing = true;
  23139. me.suspendChartLayout();
  23140. me.animationSuspendCount++;
  23141. me.callParent(arguments);
  23142. me.isInitializing = false;
  23143. me.getSurface('main');
  23144. me.getSurface('chart').setFlipRtlText(me.getInherited().rtl);
  23145. me.getSurface('overlay').waitFor(me.getSurface('series'));
  23146. me.animationSuspendCount--;
  23147. me.resumeChartLayout();
  23148. },
  23149. applyAnimation: function(animation, oldAnimation) {
  23150. return Ext.chart.Util.applyAnimation(animation, oldAnimation);
  23151. },
  23152. updateAnimation: function() {
  23153. if (this.isConfiguring) {
  23154. return;
  23155. }
  23156. // eslint-disable-next-line vars-on-top
  23157. var seriesList = this.getSeries(),
  23158. ln = seriesList.length,
  23159. i, series;
  23160. this.isSettingSeriesAnimation = true;
  23161. for (i = 0; i < ln; i++) {
  23162. series = seriesList[i];
  23163. // Don't update the series animation config, if it was set by
  23164. // a user, unless 'suspendAnimation' was called.
  23165. if (!series.isUserAnimation || this.animationSuspendCount) {
  23166. series.setAnimation(series.getAnimation());
  23167. }
  23168. }
  23169. this.isSettingSeriesAnimation = false;
  23170. },
  23171. getAnimation: function() {
  23172. var result;
  23173. if (this.animationSuspendCount) {
  23174. result = {
  23175. duration: 0
  23176. };
  23177. } else {
  23178. result = this.callParent();
  23179. }
  23180. return result;
  23181. },
  23182. suspendAnimation: function() {
  23183. this.animationSuspendCount++;
  23184. if (this.animationSuspendCount === 1) {
  23185. this.updateAnimation();
  23186. }
  23187. },
  23188. resumeAnimation: function() {
  23189. this.animationSuspendCount--;
  23190. if (this.animationSuspendCount === 0) {
  23191. this.updateAnimation();
  23192. }
  23193. },
  23194. applyInsetPadding: function(padding, oldPadding) {
  23195. var result;
  23196. if (!Ext.isObject(padding)) {
  23197. result = Ext.util.Format.parseBox(padding);
  23198. } else if (!oldPadding) {
  23199. result = padding;
  23200. } else {
  23201. result = Ext.apply(oldPadding, padding);
  23202. }
  23203. return result;
  23204. },
  23205. /**
  23206. * Suspends chart's layout.
  23207. */
  23208. suspendChartLayout: function() {
  23209. var me = this;
  23210. me.chartLayoutSuspendCount++;
  23211. if (me.chartLayoutSuspendCount === 1) {
  23212. if (me.scheduledLayoutId) {
  23213. me.layoutInSuspension = true;
  23214. me.cancelChartLayout();
  23215. } else {
  23216. me.layoutInSuspension = false;
  23217. }
  23218. }
  23219. },
  23220. /**
  23221. * Decrements chart's layout suspend count.
  23222. * When the suspend count is decremented to zero,
  23223. * a layout is scheduled.
  23224. */
  23225. resumeChartLayout: function() {
  23226. var me = this;
  23227. me.chartLayoutSuspendCount--;
  23228. if (me.chartLayoutSuspendCount === 0) {
  23229. if (me.layoutInSuspension) {
  23230. me.scheduleLayout();
  23231. }
  23232. }
  23233. },
  23234. /**
  23235. * Cancel a scheduled layout.
  23236. */
  23237. cancelChartLayout: function() {
  23238. if (this.scheduledLayoutId) {
  23239. Ext.draw.Animator.cancel(this.scheduledLayoutId);
  23240. this.scheduledLayoutId = null;
  23241. this.checkLayoutEnd();
  23242. }
  23243. },
  23244. /**
  23245. * Schedule a layout at next frame.
  23246. */
  23247. scheduleLayout: function() {
  23248. var me = this;
  23249. if (me.allowSchedule() && !me.scheduledLayoutId) {
  23250. me.scheduledLayoutId = Ext.draw.Animator.schedule('doScheduleLayout', me);
  23251. }
  23252. },
  23253. allowSchedule: function() {
  23254. return true;
  23255. },
  23256. doScheduleLayout: function() {
  23257. var me = this;
  23258. me.scheduledLayoutId = null;
  23259. if (me.chartLayoutSuspendCount) {
  23260. me.layoutInSuspension = true;
  23261. } else {
  23262. me.performLayout();
  23263. }
  23264. },
  23265. /**
  23266. * Prevent axes from triggering chart layout when their thickness changes.
  23267. * E.g. during an interaction that makes changes to the axes,
  23268. * or when chart layout was triggered by something else,
  23269. * for example a chart resize event.
  23270. */
  23271. suspendThicknessChanged: function() {
  23272. this.axisThicknessSuspendCount++;
  23273. },
  23274. /**
  23275. * Decrements axis thickness suspend count.
  23276. * When axis thickness suspend count is decremented to zero,
  23277. * chart layout is performed.
  23278. */
  23279. resumeThicknessChanged: function() {
  23280. if (this.axisThicknessSuspendCount > 0) {
  23281. this.axisThicknessSuspendCount--;
  23282. if (this.axisThicknessSuspendCount === 0 && this.isThicknessChanged) {
  23283. this.onThicknessChanged();
  23284. }
  23285. }
  23286. },
  23287. onThicknessChanged: function() {
  23288. if (this.axisThicknessSuspendCount === 0) {
  23289. this.isThicknessChanged = false;
  23290. this.performLayout();
  23291. } else {
  23292. this.isThicknessChanged = true;
  23293. }
  23294. },
  23295. applySprites: function(sprites) {
  23296. var surface = this.getSurface('chart');
  23297. sprites = Ext.Array.from(sprites);
  23298. surface.removeAll(true);
  23299. surface.add(sprites);
  23300. return sprites;
  23301. },
  23302. initItems: function() {
  23303. var items = this.items,
  23304. i, ln, item;
  23305. if (items && !items.isMixedCollection) {
  23306. this.items = [];
  23307. items = Ext.Array.from(items);
  23308. for (i = 0 , ln = items.length; i < ln; i++) {
  23309. item = items[i];
  23310. if (item.type) {
  23311. Ext.raise("To add custom sprites to the chart use the 'sprites' config.");
  23312. } else {
  23313. this.items.push(item);
  23314. }
  23315. }
  23316. }
  23317. // @noOptimize.callParent
  23318. this.callParent();
  23319. },
  23320. // noOptimize is needed because in the ext build we have a parent method to call,
  23321. // but in touch we do not so we need to suppress the cmd warning during optimized build
  23322. applyBackground: function(newBackground, oldBackground) {
  23323. var surface = this.getSurface('background');
  23324. return this.refreshBackground(surface, newBackground, oldBackground);
  23325. },
  23326. /**
  23327. * @private
  23328. * The background updater. Used by both the chart and the sprite legend.
  23329. * @param surface The surface to put the background in.
  23330. * @param newBackground
  23331. * @param oldBackground
  23332. * @return {Ext.draw.sprite.Rect/Ext.draw.sprite.Sprite}
  23333. */
  23334. refreshBackground: function(surface, newBackground, oldBackground) {
  23335. var width, height, isUpdateOld;
  23336. if (newBackground) {
  23337. if (oldBackground) {
  23338. width = oldBackground.attr.width;
  23339. height = oldBackground.attr.height;
  23340. isUpdateOld = oldBackground.type === (newBackground.type || 'rect');
  23341. }
  23342. if (newBackground.isSprite) {
  23343. oldBackground = newBackground;
  23344. } else if (newBackground.type === 'image' && Ext.isString(newBackground.src)) {
  23345. if (isUpdateOld) {
  23346. oldBackground.setAttributes({
  23347. src: newBackground.src
  23348. });
  23349. } else {
  23350. surface.remove(oldBackground, true);
  23351. oldBackground = surface.add(newBackground);
  23352. }
  23353. } else {
  23354. if (isUpdateOld) {
  23355. oldBackground.setAttributes({
  23356. fillStyle: newBackground
  23357. });
  23358. } else {
  23359. surface.remove(oldBackground, true);
  23360. oldBackground = surface.add({
  23361. type: 'rect',
  23362. fillStyle: newBackground,
  23363. animation: {
  23364. customDurations: {
  23365. x: 0,
  23366. y: 0,
  23367. width: 0,
  23368. height: 0
  23369. }
  23370. }
  23371. });
  23372. }
  23373. }
  23374. }
  23375. if (width && height) {
  23376. oldBackground.setAttributes({
  23377. width: width,
  23378. height: height
  23379. });
  23380. }
  23381. oldBackground.setAnimation(this.getAnimation());
  23382. return oldBackground;
  23383. },
  23384. defaultResizeHandler: function(size) {
  23385. this.scheduleLayout();
  23386. return false;
  23387. },
  23388. applyMainRect: function(newRect, rect) {
  23389. if (!rect) {
  23390. return newRect;
  23391. }
  23392. this.getSeries();
  23393. this.getAxes();
  23394. if (newRect[0] === rect[0] && newRect[1] === rect[1] && newRect[2] === rect[2] && newRect[3] === rect[3]) {
  23395. return rect;
  23396. } else {
  23397. return newRect;
  23398. }
  23399. },
  23400. register: function(component) {
  23401. var map = this.chartComponents,
  23402. id = component.getId();
  23403. //<debug>
  23404. if (id === undefined) {
  23405. Ext.raise('Chart component id is undefined. ' + 'Please ensure the component has an id.');
  23406. }
  23407. if (id in map) {
  23408. Ext.raise('Registering duplicate chart component id "' + id + '"');
  23409. }
  23410. //</debug>
  23411. map[id] = component;
  23412. },
  23413. unregister: function(component) {
  23414. var map = this.chartComponents,
  23415. id = component.getId();
  23416. delete map[id];
  23417. },
  23418. get: function(id) {
  23419. return this.chartComponents[id];
  23420. },
  23421. /**
  23422. * @method getAxis Returns an axis instance based on the type of data passed.
  23423. * @param {String/Number/Ext.chart.axis.Axis} axis You may request an axis by passing
  23424. * an id, the number of the array key returned by {@link #getAxes}, or an axis instance.
  23425. * @return {Ext.chart.axis.Axis} The axis requested.
  23426. */
  23427. getAxis: function(axis) {
  23428. if (axis instanceof Ext.chart.axis.Axis) {
  23429. return axis;
  23430. } else if (Ext.isNumber(axis)) {
  23431. return this.getAxes()[axis];
  23432. } else if (Ext.isString(axis)) {
  23433. return this.get(axis);
  23434. }
  23435. },
  23436. getSurface: function(id, type) {
  23437. var me = this,
  23438. map = me.surfaceMap,
  23439. surface;
  23440. id = id || 'main';
  23441. type = type || id;
  23442. surface = this.callParent([
  23443. id,
  23444. type
  23445. ]);
  23446. if (!map[type]) {
  23447. map[type] = [];
  23448. }
  23449. if (Ext.Array.indexOf(map[type], surface) < 0) {
  23450. surface.type = type;
  23451. map[type].push(surface);
  23452. surface.on('destroy', me.forgetSurface, me);
  23453. }
  23454. return surface;
  23455. },
  23456. forgetSurface: function(surface) {
  23457. var map = this.surfaceMap,
  23458. group, index;
  23459. if (!map || this.destroying) {
  23460. return;
  23461. }
  23462. group = map[surface.type];
  23463. index = group ? Ext.Array.indexOf(group, surface) : -1;
  23464. if (index >= 0) {
  23465. group.splice(index, 1);
  23466. }
  23467. },
  23468. applyAxes: function(newAxes, oldAxes) {
  23469. var me = this,
  23470. positions = {
  23471. left: 'right',
  23472. right: 'left'
  23473. },
  23474. result = [],
  23475. axis, oldAxis, linkedTo, id, oldMap, series, i, j, ln;
  23476. me.animationSuspendCount++;
  23477. me.getStore();
  23478. if (!oldAxes) {
  23479. oldAxes = [];
  23480. oldAxes.map = {};
  23481. }
  23482. oldMap = oldAxes.map;
  23483. result.map = {};
  23484. newAxes = Ext.Array.from(newAxes, true);
  23485. for (i = 0 , ln = newAxes.length; i < ln; i++) {
  23486. axis = newAxes[i];
  23487. if (!axis) {
  23488. continue;
  23489. }
  23490. if (axis instanceof Ext.chart.axis.Axis) {
  23491. oldAxis = oldMap[axis.getId()];
  23492. axis.setChart(me);
  23493. } else {
  23494. axis = Ext.Object.chain(axis);
  23495. linkedTo = axis.linkedTo;
  23496. id = axis.id;
  23497. if (Ext.isNumber(linkedTo)) {
  23498. axis = Ext.merge({}, newAxes[linkedTo], axis);
  23499. } else if (Ext.isString(linkedTo)) {
  23500. Ext.Array.each(newAxes, function(item) {
  23501. if (item.id === axis.linkedTo) {
  23502. axis = Ext.merge({}, item, axis);
  23503. return false;
  23504. }
  23505. });
  23506. }
  23507. axis.id = id;
  23508. axis.chart = me;
  23509. if (me.getInherited().rtl) {
  23510. axis.position = positions[axis.position] || axis.position;
  23511. }
  23512. id = axis.getId && axis.getId() || axis.id;
  23513. axis = Ext.factory(axis, null, oldAxis = oldMap[id], 'axis');
  23514. }
  23515. if (axis) {
  23516. result.push(axis);
  23517. result.map[axis.getId()] = axis;
  23518. }
  23519. }
  23520. me.axesChangeSeries = {};
  23521. for (i in oldMap) {
  23522. if (!result.map[i]) {
  23523. oldAxis = oldMap[i];
  23524. if (oldAxis && !oldAxis.destroyed) {
  23525. // At this point the series still have their `xAxis` and `yAxis` configs
  23526. // set to old axes. We need to update such series with new matching axes
  23527. // by calling their `onAxesChange` method.
  23528. for (j = 0 , ln = oldAxis.boundSeries.length; j < ln; j++) {
  23529. series = oldAxis.boundSeries[j];
  23530. me.axesChangeSeries[series.getId()] = series;
  23531. }
  23532. oldAxis.destroy();
  23533. }
  23534. }
  23535. }
  23536. me.animationSuspendCount--;
  23537. return result;
  23538. },
  23539. updateAxes: function(axes) {
  23540. var me = this,
  23541. seriesMap = me.axesChangeSeries,
  23542. series, id, i, ln, axis;
  23543. for (id in seriesMap) {
  23544. series = seriesMap[id];
  23545. // `true` to force set series' axes, even if they are already set
  23546. // (in this case to old axes that were just destroyed in the `axes` applier).
  23547. series.onAxesChange(me, true);
  23548. }
  23549. // If changes to the `axes` config are made post chart creation, without making any
  23550. // changes to the series afterwards, we need to figure out the new axes' `boundSeries`
  23551. // manually, as the 'serieschange' event won't be fired in this case.
  23552. for (i = 0 , ln = axes.length; i < ln; i++) {
  23553. axis = axes[i];
  23554. axis.onSeriesChange(me);
  23555. }
  23556. if (!me.isConfiguring && !me.destroying) {
  23557. me.scheduleLayout();
  23558. }
  23559. },
  23560. circularCopyArray: function(inArray, startIndex, count) {
  23561. var outArray = [],
  23562. i,
  23563. len = inArray && inArray.length;
  23564. if (len) {
  23565. for (i = 0; i < count; i++) {
  23566. outArray.push(inArray[(startIndex + i) % len]);
  23567. }
  23568. }
  23569. return outArray;
  23570. },
  23571. circularCopyObject: function(inObject, startIndex, count) {
  23572. var me = this,
  23573. name, value,
  23574. outObject = {};
  23575. if (count) {
  23576. for (name in inObject) {
  23577. if (inObject.hasOwnProperty(name)) {
  23578. value = inObject[name];
  23579. if (Ext.isArray(value)) {
  23580. outObject[name] = me.circularCopyArray(value, startIndex, count);
  23581. } else {
  23582. outObject[name] = value;
  23583. }
  23584. }
  23585. }
  23586. }
  23587. return outObject;
  23588. },
  23589. getColors: function() {
  23590. var me = this,
  23591. configColors = me.config.colors,
  23592. theme = me.getTheme();
  23593. if (Ext.isArray(configColors) && configColors.length > 0) {
  23594. configColors = me.applyColors(configColors);
  23595. }
  23596. return configColors || (theme && theme.getColors());
  23597. },
  23598. applyColors: function(newColors) {
  23599. newColors = Ext.Array.map(newColors, function(color) {
  23600. if (Ext.isString(color)) {
  23601. return color;
  23602. } else {
  23603. return color.toString();
  23604. }
  23605. });
  23606. return newColors;
  23607. },
  23608. updateColors: function(newColors) {
  23609. var me = this,
  23610. theme = me.getTheme(),
  23611. colors = newColors || (theme && theme.getColors()),
  23612. colorIndex = 0,
  23613. series = me.getSeries(),
  23614. seriesCount = series && series.length,
  23615. i, seriesItem, seriesColors, seriesColorCount;
  23616. if (colors.length) {
  23617. for (i = 0; i < seriesCount; i++) {
  23618. seriesItem = series[i];
  23619. seriesColorCount = seriesItem.themeColorCount();
  23620. seriesColors = me.circularCopyArray(colors, colorIndex, seriesColorCount);
  23621. colorIndex += seriesColorCount;
  23622. seriesItem.updateChartColors(seriesColors);
  23623. }
  23624. }
  23625. if (!me.isConfiguring) {
  23626. me.refreshLegendStore();
  23627. }
  23628. },
  23629. applyTheme: function(theme) {
  23630. if (theme && theme.isTheme) {
  23631. return theme;
  23632. }
  23633. return Ext.Factory.chartTheme(theme);
  23634. },
  23635. updateGradients: function(gradients) {
  23636. if (!Ext.isEmpty(gradients)) {
  23637. this.updateTheme(this.getTheme());
  23638. }
  23639. },
  23640. updateTheme: function(theme, oldTheme) {
  23641. var me = this,
  23642. axes = me.getAxes(),
  23643. series = me.getSeries(),
  23644. colors = me.getColors(),
  23645. i;
  23646. if (!series) {
  23647. return;
  23648. }
  23649. me.updateChartTheme(theme);
  23650. for (i = 0; i < axes.length; i++) {
  23651. axes[i].updateTheme(theme);
  23652. }
  23653. for (i = 0; i < series.length; i++) {
  23654. series[i].setTheme(theme);
  23655. }
  23656. me.updateSpriteTheme(theme);
  23657. me.updateColors(colors);
  23658. // It may be necessary to perform a layout here.
  23659. // But instead of the 'chart.scheduleLayout' call, we can call
  23660. // 'chart.redraw'. If after the redraw call the thickness
  23661. // of any axis changes, this will automatically trigger
  23662. // chart layout (see Ext.chart.axis.sprite.Axis.doThicknessChanged).
  23663. // Otherwise, no layout is necessary.
  23664. me.redraw();
  23665. me.fireEvent('themechange', me, theme, oldTheme);
  23666. },
  23667. themeOnlyIfConfigured: {
  23668. captions: true
  23669. },
  23670. updateChartTheme: function(theme) {
  23671. var me = this,
  23672. chartTheme = theme.getChart(),
  23673. initialConfig = me.getInitialConfig(),
  23674. defaultConfig = me.defaultConfig,
  23675. configs = me.self.getConfigurator().configs,
  23676. genericChartTheme = chartTheme.defaults,
  23677. specificChartTheme = chartTheme[me.xtype],
  23678. themeOnlyIfConfigured = me.themeOnlyIfConfigured,
  23679. key, value, isObjValue, isUnusedConfig, initialValue, cfg;
  23680. chartTheme = Ext.merge({}, genericChartTheme, specificChartTheme);
  23681. for (key in chartTheme) {
  23682. value = chartTheme[key];
  23683. cfg = configs[key];
  23684. if (value !== null && value !== undefined && cfg) {
  23685. initialValue = initialConfig[key];
  23686. isObjValue = Ext.isObject(value);
  23687. isUnusedConfig = initialValue === defaultConfig[key];
  23688. if (isObjValue) {
  23689. if (isUnusedConfig && themeOnlyIfConfigured[key]) {
  23690. continue;
  23691. }
  23692. value = Ext.merge({}, value, initialValue);
  23693. }
  23694. if (isUnusedConfig || isObjValue) {
  23695. me[cfg.names.set](value);
  23696. }
  23697. }
  23698. }
  23699. },
  23700. updateSpriteTheme: function(theme) {
  23701. var me = this,
  23702. chartSurface, sprites, styles, sprite, style, key, attr, isText, i, ln;
  23703. me.getSprites();
  23704. chartSurface = me.getSurface('chart');
  23705. sprites = chartSurface.getItems();
  23706. styles = theme.getSprites();
  23707. for (i = 0 , ln = sprites.length; i < ln; i++) {
  23708. sprite = sprites[i];
  23709. style = styles[sprite.type];
  23710. if (style) {
  23711. attr = {};
  23712. isText = sprite.type === 'text';
  23713. for (key in style) {
  23714. if (!(key in sprite.config)) {
  23715. // Setting individual font attributes will take over the 'font' shorthand
  23716. // attribute, but this behavior is undesireable for theming.
  23717. if (!(isText && key.indexOf('font') === 0 && sprite.config.font)) {
  23718. attr[key] = style[key];
  23719. }
  23720. }
  23721. }
  23722. sprite.setAttributes(attr);
  23723. }
  23724. }
  23725. },
  23726. /**
  23727. * Adds a {@link Ext.chart.series.Series Series} to this chart.
  23728. *
  23729. * The Series (or array) passed will be added to the existing series. If an `id` is specified
  23730. * in a new Series, any existing Series of that `id` will be updated.
  23731. *
  23732. * The chart will be redrawn in response to the change.
  23733. *
  23734. * @param {Object/Object[]/Ext.chart.series.Series/Ext.chart.series.Series[]} newSeries A
  23735. * config object describing the Series to add, or an instantiated Series object. Or an array
  23736. * of these.
  23737. */
  23738. addSeries: function(newSeries) {
  23739. var series = this.getSeries();
  23740. series = series.concat(Ext.Array.from(newSeries));
  23741. this.setSeries(series);
  23742. },
  23743. /**
  23744. * Remove a {@link Ext.chart.series.Series Series} from this chart.
  23745. * The Series (or array) passed will be removed from the existing series.
  23746. *
  23747. * The chart will be redrawn in response to the change.
  23748. *
  23749. * @param {Ext.chart.series.Series/String} series The Series or the `id` of the Series
  23750. * to remove. May be an array.
  23751. */
  23752. removeSeries: function(series) {
  23753. var existingSeries = this.getSeries(),
  23754. newSeries = [],
  23755. removeMap = {},
  23756. i, len, s;
  23757. series = Ext.Array.from(series);
  23758. // Build a map of the Series IDs that are to be removed
  23759. for (i = 0 , len = series.length; i < len; i++) {
  23760. s = series[i];
  23761. // If they passed a Series Object
  23762. if (typeof s !== 'string') {
  23763. s = s.getId();
  23764. }
  23765. removeMap[s] = true;
  23766. }
  23767. // Build a new Series array that excludes those Series scheduled for removal
  23768. for (i = 0 , len = existingSeries.length; i < len; i++) {
  23769. if (!removeMap[existingSeries[i].getId()]) {
  23770. newSeries.push(existingSeries[i]);
  23771. }
  23772. }
  23773. this.setSeries(newSeries);
  23774. },
  23775. applySeries: function(newSeries, oldSeries) {
  23776. var me = this,
  23777. result = [],
  23778. oldMap, oldSeriesItem, i, ln, series;
  23779. me.animationSuspendCount++;
  23780. me.getAxes();
  23781. if (oldSeries) {
  23782. oldMap = oldSeries.map;
  23783. } else {
  23784. oldSeries = [];
  23785. oldMap = oldSeries.map = {};
  23786. }
  23787. result.map = {};
  23788. newSeries = Ext.Array.from(newSeries, true);
  23789. for (i = 0 , ln = newSeries.length; i < ln; i++) {
  23790. series = newSeries[i];
  23791. if (!series) {
  23792. continue;
  23793. }
  23794. oldSeriesItem = oldMap[series.getId && series.getId() || series.id];
  23795. // New Series instance passed in
  23796. if (series instanceof Ext.chart.series.Series) {
  23797. // Replacing
  23798. if (oldSeriesItem && oldSeriesItem !== series) {
  23799. oldSeriesItem.destroy();
  23800. }
  23801. series.setChart(me);
  23802. }
  23803. // Series config object passed in
  23804. else if (Ext.isObject(series)) {
  23805. // Config object matched an existing Series item by id;
  23806. // update its configuration
  23807. if (oldSeriesItem) {
  23808. oldSeriesItem.setConfig(series);
  23809. series = oldSeriesItem;
  23810. } else // Create a new Series
  23811. {
  23812. if (Ext.isString(series)) {
  23813. series = {
  23814. type: series
  23815. };
  23816. }
  23817. series.chart = me;
  23818. series = Ext.create(series.xclass || ('series.' + series.type), series);
  23819. }
  23820. }
  23821. result.push(series);
  23822. result.map[series.getId()] = series;
  23823. }
  23824. for (i in oldMap) {
  23825. if (!result.map[oldMap[i].id]) {
  23826. oldMap[i].destroy();
  23827. }
  23828. }
  23829. me.animationSuspendCount--;
  23830. return result;
  23831. },
  23832. updateSeries: function(newSeries, oldSeries) {
  23833. var me = this;
  23834. if (me.destroying) {
  23835. return;
  23836. }
  23837. me.animationSuspendCount++;
  23838. me.fireEvent('serieschange', me, newSeries, oldSeries);
  23839. if (!Ext.isEmpty(newSeries)) {
  23840. me.updateTheme(me.getTheme());
  23841. }
  23842. me.refreshLegendStore();
  23843. if (!me.isConfiguring && !me.destroying) {
  23844. me.scheduleLayout();
  23845. }
  23846. me.animationSuspendCount--;
  23847. },
  23848. defaultLegendType: 'sprite',
  23849. applyLegend: function(legend, oldLegend) {
  23850. var me = this,
  23851. result = null,
  23852. alias;
  23853. if (oldLegend && !(oldLegend.destroyed || oldLegend.destroying)) {
  23854. if (me.legendStoreListeners) {
  23855. me.legendStoreListeners.destroy();
  23856. }
  23857. if (me.legendStore) {
  23858. me.legendStore.destroy();
  23859. }
  23860. oldLegend.destroy();
  23861. }
  23862. if (legend) {
  23863. if (Ext.isBoolean(legend)) {
  23864. result = Ext.create('legend.' + me.defaultLegendType, {
  23865. docked: 'bottom',
  23866. chart: me
  23867. });
  23868. } else {
  23869. legend.docked = legend.docked || 'bottom';
  23870. legend.chart = me;
  23871. alias = 'legend.' + (legend.type || me.defaultLegendType);
  23872. result = Ext.create(alias, legend);
  23873. }
  23874. }
  23875. return result;
  23876. },
  23877. updateLegend: function(legend) {
  23878. var me = this;
  23879. // Probably has been already destroyed with the old legend,
  23880. // but making sure.
  23881. me.destroyLegendStore();
  23882. if (legend) {
  23883. me.getItems();
  23884. legend.setStore(me.refreshLegendStore());
  23885. }
  23886. if (!me.isConfiguring) {
  23887. me.scheduleLayout();
  23888. }
  23889. },
  23890. captionApplier: function(caption, oldCaption) {
  23891. var me = this,
  23892. result;
  23893. if (oldCaption && !(oldCaption.destroyed || oldCaption.destroying)) {
  23894. oldCaption.destroy();
  23895. }
  23896. if (caption) {
  23897. caption.chart = me;
  23898. result = new Ext.chart.Caption(caption);
  23899. }
  23900. return result;
  23901. },
  23902. applyCaptions: function(captions, oldCaptions) {
  23903. var map = {},
  23904. caption, oldCaption, name, any;
  23905. for (name in captions) {
  23906. caption = captions[name];
  23907. if (caption && !caption.length && !(caption.text && caption.text.length)) {
  23908. caption = null;
  23909. } else if (typeof caption === 'string') {
  23910. caption = {
  23911. text: caption
  23912. };
  23913. // Initial config is used for proper theming (see `updateChartTheme`)
  23914. // and config merging, however, mergin won't work as expected, if
  23915. // the initial config value remains a string, so we modify it here.
  23916. this.getInitialConfig().captions[name] = caption;
  23917. }
  23918. oldCaption = oldCaptions && oldCaptions[name];
  23919. caption = this.captionApplier(caption, oldCaption);
  23920. if (caption) {
  23921. any = true;
  23922. map[name] = caption;
  23923. }
  23924. }
  23925. return any && map;
  23926. },
  23927. updateCaptions: function() {
  23928. var me = this;
  23929. if (!me.isConfiguring) {
  23930. me.scheduleLayout();
  23931. }
  23932. },
  23933. /**
  23934. * Return the legend store that contains all the legend information.
  23935. * This information is collected from all the series.
  23936. * @return {Ext.chart.legend.store.Store}
  23937. */
  23938. getLegendStore: function() {
  23939. var me = this,
  23940. store = me.legendStore;
  23941. if (!store) {
  23942. store = me.legendStore = new Ext.chart.legend.store.Store({
  23943. chart: me
  23944. });
  23945. me.legendStoreListeners = store.on({
  23946. scope: me,
  23947. update: 'onLegendStoreUpdate',
  23948. destroyable: true
  23949. });
  23950. }
  23951. return store;
  23952. },
  23953. destroyLegendStore: function() {
  23954. var store = this.legendStore;
  23955. if (store && !(store.destroyed || store.destroying)) {
  23956. store.destroy();
  23957. }
  23958. this.legendStore = null;
  23959. },
  23960. refreshLegendStore: function() {
  23961. var me = this,
  23962. legendStore = me.getLegendStore(),
  23963. series, seriesList, legendData, i, ln;
  23964. if (legendStore) {
  23965. seriesList = me.getSeries();
  23966. legendData = [];
  23967. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  23968. series = seriesList[i];
  23969. if (series.getShowInLegend()) {
  23970. series.provideLegendInfo(legendData);
  23971. }
  23972. }
  23973. legendStore.setData(legendData);
  23974. }
  23975. return legendStore;
  23976. },
  23977. onLegendStoreUpdate: function(store, record) {
  23978. var me = this,
  23979. series;
  23980. if (record) {
  23981. series = this.getSeries().map[record.get('series')];
  23982. if (series) {
  23983. series.setHiddenByIndex(record.get('index'), record.get('disabled'));
  23984. me.redraw();
  23985. }
  23986. }
  23987. },
  23988. applyInteractions: function(interactions, oldInteractions) {
  23989. var me = this,
  23990. result = [],
  23991. oldMap, interaction, i, ln;
  23992. interactions = Ext.Array.from(interactions, true);
  23993. if (!oldInteractions) {
  23994. oldInteractions = [];
  23995. oldInteractions.map = {};
  23996. }
  23997. oldMap = oldInteractions.map;
  23998. result.map = {};
  23999. for (i = 0 , ln = interactions.length; i < ln; i++) {
  24000. interaction = interactions[i];
  24001. if (!interaction) {
  24002. continue;
  24003. }
  24004. // eslint-disable-next-line max-len
  24005. interaction = Ext.factory(interaction, null, oldMap[interaction.getId && interaction.getId() || interaction.id], 'interaction');
  24006. if (interaction) {
  24007. interaction.setChart(me);
  24008. result.push(interaction);
  24009. result.map[interaction.getId()] = interaction;
  24010. }
  24011. }
  24012. for (i in oldMap) {
  24013. if (!result.map[i]) {
  24014. oldMap[i].destroy();
  24015. }
  24016. }
  24017. return result;
  24018. },
  24019. /**
  24020. * Get an interaction by type.
  24021. * @param {String} type The type of the interaction.
  24022. * @return {Ext.chart.interactions.Abstract} The interaction. `null`
  24023. * if not found.
  24024. */
  24025. getInteraction: function(type) {
  24026. var interactions = this.getInteractions(),
  24027. len = interactions && interactions.length,
  24028. out = null,
  24029. interaction, i;
  24030. if (len) {
  24031. for (i = 0; i < len; ++i) {
  24032. interaction = interactions[i];
  24033. if (interaction.type === type) {
  24034. out = interaction;
  24035. break;
  24036. }
  24037. }
  24038. }
  24039. return out;
  24040. },
  24041. applyStore: function(store) {
  24042. return store && Ext.StoreManager.lookup(store);
  24043. },
  24044. updateStore: function(newStore, oldStore) {
  24045. var me = this;
  24046. if (oldStore && !oldStore.destroyed) {
  24047. oldStore.un({
  24048. datachanged: 'onDataChanged',
  24049. update: 'onDataChanged',
  24050. scope: me,
  24051. order: 'after'
  24052. });
  24053. if (oldStore.autoDestroy) {
  24054. oldStore.destroy();
  24055. }
  24056. }
  24057. if (newStore) {
  24058. newStore.on({
  24059. datachanged: 'onDataChanged',
  24060. update: 'onDataChanged',
  24061. scope: me,
  24062. order: 'after'
  24063. });
  24064. }
  24065. me.fireEvent('storechange', me, newStore, oldStore);
  24066. me.onDataChanged();
  24067. },
  24068. /**
  24069. * Redraw the chart. If animations are set this will animate the chart too.
  24070. * Note: the actual redraw is performed in a subclass.
  24071. */
  24072. redraw: function() {
  24073. this.fireEvent('redraw', this);
  24074. },
  24075. /**
  24076. * @private
  24077. * Lays out chart components and triggers a {@link #event!redraw}.
  24078. * Note: the actual layout is performed in a subclass.
  24079. * A subclass should not perform a layout, if this parent method
  24080. * returns `false`.
  24081. * @return {Boolean}
  24082. */
  24083. performLayout: function() {
  24084. if (this.destroying || this.destroyed) {
  24085. //<debug>
  24086. Ext.raise('Attempting to lay out a dead chart: ' + this.getId());
  24087. //</debug>
  24088. return false;
  24089. }
  24090. // Cancel subclass layout.
  24091. // eslint-disable-next-line vars-on-top
  24092. var me = this,
  24093. legend = me.getLegend(),
  24094. chartRect = me.getChartRect(true),
  24095. background = me.getBackground(),
  24096. result = true,
  24097. legendRect;
  24098. me.cancelChartLayout();
  24099. //<debug>
  24100. // Unlike the 'layout' event that is called after all chart layouts are done
  24101. // and none are pending, this event fires before the start of each layout.
  24102. me.fireEvent('beforelayout', me);
  24103. //</debug>
  24104. if (background) {
  24105. me.getSurface('background').setRect(chartRect.slice());
  24106. background.setAttributes({
  24107. width: chartRect[2],
  24108. height: chartRect[3]
  24109. });
  24110. }
  24111. // The top docked legend is a special case and should be laid out after captions.
  24112. if (legend && legend.isSpriteLegend && !legend.isTop) {
  24113. legendRect = legend.computeRect(chartRect);
  24114. }
  24115. me.layoutCaptions(chartRect);
  24116. if (legend && legend.isSpriteLegend && legend.isTop) {
  24117. legendRect = legend.computeRect(chartRect);
  24118. }
  24119. if (legendRect) {
  24120. me.getSurface('legend').setRect(legendRect);
  24121. result = legend.performLayout();
  24122. }
  24123. me.getSurface('chart').setRect(chartRect);
  24124. if (result) {
  24125. me.hasFirstLayout = true;
  24126. }
  24127. return result;
  24128. },
  24129. layoutCaptions: function(chartRect) {
  24130. var captions = this.getCaptions(),
  24131. shrinkRect = {
  24132. left: 0,
  24133. top: 0,
  24134. right: chartRect[2],
  24135. bottom: chartRect[3]
  24136. },
  24137. caption, captionName, captionList, i, ln;
  24138. if (captions) {
  24139. captionList = [];
  24140. for (captionName in captions) {
  24141. captionList.push(captions[captionName]);
  24142. }
  24143. captionList.sort(function(a, b) {
  24144. return a.getWeight() - b.getWeight();
  24145. });
  24146. for (i = 0 , ln = captionList.length; i < ln; i++) {
  24147. caption = captionList[i];
  24148. if (!i) {
  24149. this.getSurface(caption.surfaceName).setRect(chartRect.slice());
  24150. }
  24151. caption.computeRect(chartRect, shrinkRect);
  24152. }
  24153. this.captionList = captionList;
  24154. }
  24155. },
  24156. /**
  24157. * @private
  24158. */
  24159. checkLayoutEnd: function() {
  24160. // not running not pending
  24161. if (!this.chartLayoutCount && !this.scheduledLayoutId) {
  24162. this.onLayoutEnd();
  24163. }
  24164. },
  24165. /**
  24166. * @private
  24167. */
  24168. onLayoutEnd: function() {
  24169. var me = this;
  24170. me.fireEvent('layout', me);
  24171. },
  24172. /**
  24173. * @private
  24174. * The area of the chart minus the legend, title, subtitle and credits.
  24175. * Cache chart rect as element.getSize() results in
  24176. * a relatively expensive call to the getComputedStyle().
  24177. */
  24178. getChartRect: function(isRecompute) {
  24179. var me = this,
  24180. chartRect, bodySize;
  24181. if (isRecompute) {
  24182. me.chartRect = null;
  24183. }
  24184. if (me.chartRect) {
  24185. chartRect = me.chartRect;
  24186. } else {
  24187. bodySize = me.bodyElement.getSize();
  24188. chartRect = me.chartRect = [
  24189. 0,
  24190. 0,
  24191. bodySize.width,
  24192. bodySize.height
  24193. ];
  24194. }
  24195. return chartRect;
  24196. },
  24197. /**
  24198. * @private
  24199. * Converts page coordinates into chart's 'series' surface coordinates.
  24200. */
  24201. getEventXY: function(e) {
  24202. return this.getSurface('series').getEventXY(e);
  24203. },
  24204. /**
  24205. * Given an x/y point relative to the chart, find and return the first series item that
  24206. * matches that point.
  24207. * @param {Number} x
  24208. * @param {Number} y
  24209. * @return {Object} An object with `series` and `item` properties, or `false` if no item found.
  24210. */
  24211. getItemForPoint: function(x, y) {
  24212. var me = this,
  24213. seriesList = me.getSeries(),
  24214. rect = me.getMainRect(),
  24215. ln = seriesList.length,
  24216. minDistance = Infinity,
  24217. result = null,
  24218. i, item;
  24219. // The x,y here are already converted to the 'main' surface coordinates.
  24220. // Series surface rect matches the main surface rect.
  24221. if (!(me.hasFirstLayout && rect && x >= 0 && x <= rect[2] && y >= 0 && y <= rect[3])) {
  24222. return null;
  24223. }
  24224. // Iterate in reverse order so that the series that render later (on top)
  24225. // get hit tested first.
  24226. for (i = ln - 1; i >= 0; i--) {
  24227. item = seriesList[i].getItemForPoint(x, y);
  24228. if (item) {
  24229. // Imagine a chart with multiple series, e.g. 'line', 'scatter' and 'bar'.
  24230. // For 'line' and 'scatter' series, the method will look for the nearest
  24231. // marker, but for 'bar' series, it will look for the first bar that
  24232. // contains the given point. For such series, the 'distance' information
  24233. // is absent and meaningless.
  24234. if (!item.distance) {
  24235. result = item;
  24236. break;
  24237. }
  24238. if (item.distance < minDistance) {
  24239. minDistance = item.distance;
  24240. result = item;
  24241. }
  24242. }
  24243. }
  24244. return result;
  24245. },
  24246. /**
  24247. * @private
  24248. * Given an x/y point relative to the chart, find and return all series items that match
  24249. * that point.
  24250. * @param {Number} x
  24251. * @param {Number} y
  24252. * @return {Array} An array of objects with `series` and `item` properties.
  24253. * @deprecated 6.5.2 This method is deprecated
  24254. */
  24255. getItemsForPoint: function(x, y) {
  24256. var me = this,
  24257. seriesList = me.getSeries(),
  24258. ln = seriesList.length,
  24259. // If we haven't drawn yet, don't attempt to find any items.
  24260. i = me.hasFirstLayout ? ln - 1 : -1,
  24261. items = [],
  24262. series, item;
  24263. // Iterate from the end so that the series that are drawn later get hit tested first.
  24264. for (; i >= 0; i--) {
  24265. series = seriesList[i];
  24266. item = series.getItemForPoint(x, y);
  24267. if (item && (item.category === 'items' || item.category === 'markers')) {
  24268. items.push(item);
  24269. }
  24270. }
  24271. return items;
  24272. },
  24273. /**
  24274. * @private
  24275. */
  24276. onDataChanged: function() {
  24277. var me = this,
  24278. rect, store, series, axes;
  24279. if (me.isInitializing) {
  24280. return;
  24281. }
  24282. rect = me.getMainRect();
  24283. store = me.getStore();
  24284. series = me.getSeries();
  24285. axes = me.getAxes();
  24286. if (!store || !axes || !series) {
  24287. return;
  24288. }
  24289. if (!rect) {
  24290. // The chart hasn't been rendered yet.
  24291. me.on({
  24292. redraw: me.onDataChanged,
  24293. scope: me,
  24294. single: true
  24295. });
  24296. return;
  24297. }
  24298. me.processData();
  24299. me.redraw();
  24300. },
  24301. /**
  24302. * @private
  24303. * The number of records in the chart's store last time the data was changed.
  24304. */
  24305. recordCount: 0,
  24306. /**
  24307. * @private
  24308. */
  24309. processData: function() {
  24310. var me = this,
  24311. recordCount = me.getStore().getCount(),
  24312. seriesList = me.getSeries(),
  24313. ln = seriesList.length,
  24314. isNeedUpdateColors = false,
  24315. i = 0,
  24316. series;
  24317. for (; i < ln; i++) {
  24318. series = seriesList[i];
  24319. series.processData();
  24320. if (!isNeedUpdateColors && series.isStoreDependantColorCount) {
  24321. isNeedUpdateColors = true;
  24322. }
  24323. }
  24324. if (isNeedUpdateColors && recordCount > me.recordCount) {
  24325. me.updateColors(me.getColors());
  24326. me.recordCount = recordCount;
  24327. }
  24328. // 'refreshLegendStore' will attemp to grab the 'series',
  24329. // which are still configuring at this point.
  24330. // The legend store will be refreshed inside the chart.series
  24331. // updater anyway.
  24332. if (!me.isConfiguring) {
  24333. me.refreshLegendStore();
  24334. }
  24335. },
  24336. /**
  24337. * Changes the data store bound to this chart and refreshes it.
  24338. * @param {Ext.data.Store} store The store to bind to this chart.
  24339. */
  24340. bindStore: function(store) {
  24341. this.setStore(store);
  24342. },
  24343. applyHighlightItem: function(newHighlightItem, oldHighlightItem) {
  24344. var i1, i2, s1, s2;
  24345. if (newHighlightItem === oldHighlightItem) {
  24346. return;
  24347. }
  24348. if (Ext.isObject(newHighlightItem) && Ext.isObject(oldHighlightItem)) {
  24349. i1 = newHighlightItem;
  24350. i2 = oldHighlightItem;
  24351. s1 = i1.sprite && (i1.sprite[0] || i1.sprite);
  24352. s2 = i2.sprite && (i2.sprite[0] || i2.sprite);
  24353. if (s1 === s2 && i1.index === i2.index) {
  24354. return;
  24355. }
  24356. }
  24357. return newHighlightItem;
  24358. },
  24359. updateHighlightItem: function(newHighlightItem, oldHighlightItem) {
  24360. var newHighlight, oldHighlight;
  24361. if (oldHighlightItem) {
  24362. oldHighlight = oldHighlightItem.series.getHighlight();
  24363. if (oldHighlight) {
  24364. oldHighlightItem.series.setAttributesForItem(oldHighlightItem, {
  24365. highlighted: false
  24366. });
  24367. }
  24368. }
  24369. if (newHighlightItem) {
  24370. newHighlight = newHighlightItem.series.getHighlight();
  24371. if (newHighlight) {
  24372. newHighlightItem.series.setAttributesForItem(newHighlightItem, {
  24373. highlighted: true
  24374. });
  24375. }
  24376. }
  24377. if (oldHighlight || newHighlight) {
  24378. this.fireEvent('itemhighlight', this, newHighlightItem, oldHighlightItem);
  24379. }
  24380. },
  24381. destroyChart: function() {
  24382. var me = this;
  24383. // The order is important here.
  24384. me.setInteractions(null);
  24385. me.setAxes(null);
  24386. me.setSeries(null);
  24387. me.setLegend(null);
  24388. me.setStore(null);
  24389. me.cancelChartLayout();
  24390. },
  24391. /* ---------------------------------
  24392. Methods needed for ComponentQuery
  24393. ---------------------------------- */
  24394. /**
  24395. * @private
  24396. * @param {Boolean} deep
  24397. * @return {Array}
  24398. */
  24399. getRefItems: function(deep) {
  24400. var me = this,
  24401. series = me.getSeries(),
  24402. axes = me.getAxes(),
  24403. interaction = me.getInteractions(),
  24404. legend = me.getLegend(),
  24405. ans = [],
  24406. i, ln;
  24407. for (i = 0 , ln = series.length; i < ln; i++) {
  24408. ans.push(series[i]);
  24409. if (series[i].getRefItems) {
  24410. ans.push.apply(ans, series[i].getRefItems(deep));
  24411. }
  24412. }
  24413. for (i = 0 , ln = axes.length; i < ln; i++) {
  24414. ans.push(axes[i]);
  24415. if (axes[i].getRefItems) {
  24416. ans.push.apply(ans, axes[i].getRefItems(deep));
  24417. }
  24418. }
  24419. for (i = 0 , ln = interaction.length; i < ln; i++) {
  24420. ans.push(interaction[i]);
  24421. if (interaction[i].getRefItems) {
  24422. ans.push.apply(ans, interaction[i].getRefItems(deep));
  24423. }
  24424. }
  24425. if (legend) {
  24426. ans.push(legend);
  24427. }
  24428. return ans;
  24429. }
  24430. });
  24431. /**
  24432. * @class Ext.chart.overrides.AbstractChart
  24433. */
  24434. Ext.define('Ext.chart.overrides.AbstractChart', {
  24435. override: 'Ext.chart.AbstractChart',
  24436. updateLegend: function(legend, oldLegend) {
  24437. this.callParent([
  24438. legend,
  24439. oldLegend
  24440. ]);
  24441. if (legend && legend.isDomLegend) {
  24442. this.addDocked(legend);
  24443. }
  24444. },
  24445. performLayout: function() {
  24446. if (this.isVisible(true)) {
  24447. return this.callParent();
  24448. }
  24449. this.cancelChartLayout();
  24450. return false;
  24451. },
  24452. afterComponentLayout: function(width, height, oldWidth, oldHeight) {
  24453. this.callParent([
  24454. width,
  24455. height,
  24456. oldWidth,
  24457. oldHeight
  24458. ]);
  24459. if (!this.hasFirstLayout) {
  24460. this.scheduleLayout();
  24461. }
  24462. },
  24463. allowSchedule: function() {
  24464. return this.rendered;
  24465. },
  24466. doDestroy: function() {
  24467. this.destroyChart();
  24468. this.callParent();
  24469. }
  24470. });
  24471. /**
  24472. * @class Ext.chart.grid.HorizontalGrid
  24473. * @extends Ext.draw.sprite.Sprite
  24474. *
  24475. * Horizontal Grid sprite. Used in Cartesian Charts.
  24476. */
  24477. Ext.define('Ext.chart.grid.HorizontalGrid', {
  24478. extend: 'Ext.draw.sprite.Sprite',
  24479. alias: 'grid.horizontal',
  24480. inheritableStatics: {
  24481. def: {
  24482. processors: {
  24483. x: 'number',
  24484. y: 'number',
  24485. width: 'number',
  24486. height: 'number'
  24487. },
  24488. defaults: {
  24489. x: 0,
  24490. y: 0,
  24491. width: 1,
  24492. height: 1,
  24493. strokeStyle: '#DDD'
  24494. }
  24495. }
  24496. },
  24497. render: function(surface, ctx, rect) {
  24498. var attr = this.attr,
  24499. y = surface.roundPixel(attr.y),
  24500. halfLineWidth = ctx.lineWidth * 0.5;
  24501. ctx.beginPath();
  24502. ctx.rect(rect[0] - surface.matrix.getDX(), y + halfLineWidth, +rect[2], attr.height);
  24503. ctx.fill();
  24504. ctx.beginPath();
  24505. ctx.moveTo(rect[0] - surface.matrix.getDX(), y + halfLineWidth);
  24506. ctx.lineTo(rect[0] + rect[2] - surface.matrix.getDX(), y + halfLineWidth);
  24507. ctx.stroke();
  24508. }
  24509. });
  24510. /**
  24511. * @class Ext.chart.grid.VerticalGrid
  24512. * @extends Ext.draw.sprite.Sprite
  24513. *
  24514. * Vertical Grid sprite. Used in Cartesian Charts.
  24515. */
  24516. Ext.define('Ext.chart.grid.VerticalGrid', {
  24517. extend: 'Ext.draw.sprite.Sprite',
  24518. alias: 'grid.vertical',
  24519. inheritableStatics: {
  24520. def: {
  24521. processors: {
  24522. x: 'number',
  24523. y: 'number',
  24524. width: 'number',
  24525. height: 'number'
  24526. },
  24527. defaults: {
  24528. x: 0,
  24529. y: 0,
  24530. width: 1,
  24531. height: 1,
  24532. strokeStyle: '#DDD'
  24533. }
  24534. }
  24535. },
  24536. render: function(surface, ctx, rect) {
  24537. var attr = this.attr,
  24538. x = surface.roundPixel(attr.x),
  24539. halfLineWidth = ctx.lineWidth * 0.5;
  24540. ctx.beginPath();
  24541. ctx.rect(x - halfLineWidth, rect[1] - surface.matrix.getDY(), attr.width, rect[3]);
  24542. ctx.fill();
  24543. ctx.beginPath();
  24544. ctx.moveTo(x - halfLineWidth, rect[1] - surface.matrix.getDY());
  24545. ctx.lineTo(x - halfLineWidth, rect[1] + rect[3] - surface.matrix.getDY());
  24546. ctx.stroke();
  24547. }
  24548. });
  24549. /**
  24550. * Represents a chart that uses cartesian coordinates.
  24551. * A cartesian chart has two directions, X direction and Y direction.
  24552. * The series and axes are coordinated along these directions.
  24553. * By default the x direction is horizontal and y direction is vertical,
  24554. * You can swap the direction by setting the {@link #flipXY} config to `true`.
  24555. *
  24556. * Cartesian series often treats x direction an y direction differently.
  24557. * In most cases, data on x direction are assumed to be monotonically increasing.
  24558. * Based on this property, cartesian series can be trimmed and summarized properly
  24559. * to gain a better performance.
  24560. *
  24561. * Please check out the summary for the {@link Ext.chart.AbstractChart} as well,
  24562. * for helpful tips and important details.
  24563. *
  24564. */
  24565. Ext.define('Ext.chart.CartesianChart', {
  24566. extend: 'Ext.chart.AbstractChart',
  24567. alternateClassName: 'Ext.chart.Chart',
  24568. requires: [
  24569. 'Ext.chart.grid.HorizontalGrid',
  24570. 'Ext.chart.grid.VerticalGrid'
  24571. ],
  24572. xtype: [
  24573. 'cartesian',
  24574. 'chart'
  24575. ],
  24576. isCartesian: true,
  24577. config: {
  24578. /**
  24579. * @cfg {Boolean} flipXY Flip the direction of X and Y axis.
  24580. * If flipXY is `true`, the X axes will be vertical and Y axes will be horizontal.
  24581. * Note that {@link Ext.chart.axis.Axis#position positions} of chart axes have
  24582. * to be updated accordingly: axes positioned to the `top` and `bottom` should
  24583. * be positioned to the `left` or `right` and vice versa.
  24584. */
  24585. flipXY: false,
  24586. /*
  24587. While it may seem tedious to change the position config of all axes every time
  24588. when the value of the flipXY config is changed, it's hard to predict the
  24589. expectaction of the user here, as illustrated below.
  24590. The 'num' and 'cat' here stand for the numeric and the category axis, respectively.
  24591. And the right column shows the expected (subjective) result of setting the flipXY
  24592. config of the chart to 'true'.
  24593. As one can see, there's no single rule (e.g. position swapping, clockwise 90° chart
  24594. rotation) that will produce a universally accepted result.
  24595. So we are letting the user decide, instead of doing it for them.
  24596. ---------------------------------------------
  24597. | flipXY: false | flipXY: true |
  24598. ---------------------------------------------
  24599. | ^ | ^ |
  24600. | | * | | * * * |
  24601. | num1 | * * | cat | * * |
  24602. | | * * * | | * |
  24603. | --------> | --------> |
  24604. | cat | num1 |
  24605. ---------------------------------------------
  24606. | | num1 |
  24607. | ^ ^ | ^-------> |
  24608. | | * | | | * * * |
  24609. | num1 | * * | num2 | cat | * * |
  24610. | | * * * | | | * |
  24611. | --------> | --------> |
  24612. | cat | num2 |
  24613. ---------------------------------------------
  24614. */
  24615. innerRect: [
  24616. 0,
  24617. 0,
  24618. 1,
  24619. 1
  24620. ],
  24621. /**
  24622. * @cfg {Object} innerPadding The amount of inner padding in pixels.
  24623. * Inner padding is the padding from the innermost axes to the series.
  24624. */
  24625. innerPadding: {
  24626. top: 0,
  24627. left: 0,
  24628. right: 0,
  24629. bottom: 0
  24630. }
  24631. },
  24632. applyInnerPadding: function(padding, oldPadding) {
  24633. if (!Ext.isObject(padding)) {
  24634. return Ext.util.Format.parseBox(padding);
  24635. } else if (!oldPadding) {
  24636. return padding;
  24637. } else {
  24638. return Ext.apply(oldPadding, padding);
  24639. }
  24640. },
  24641. getDirectionForAxis: function(position) {
  24642. var flipXY = this.getFlipXY(),
  24643. direction;
  24644. if (position === 'left' || position === 'right') {
  24645. direction = flipXY ? 'X' : 'Y';
  24646. } else {
  24647. direction = flipXY ? 'Y' : 'X';
  24648. }
  24649. return direction;
  24650. },
  24651. /**
  24652. * Layout the axes and series.
  24653. */
  24654. performLayout: function() {
  24655. var me = this;
  24656. if (me.callParent() === false) {
  24657. return;
  24658. }
  24659. me.chartLayoutCount++;
  24660. me.suspendAnimation();
  24661. // 'chart' surface rect is the size of the chart's inner element
  24662. // (see chart.getChartBox), i.e. the portion of the chart minus
  24663. // the legend area (whether DOM or sprite based).
  24664. // eslint-disable-next-line vars-on-top, one-var
  24665. var chartRect = me.getSurface('chart').getRect(),
  24666. left = chartRect[0],
  24667. top = chartRect[1],
  24668. width = chartRect[2],
  24669. height = chartRect[3],
  24670. captionList = me.captionList,
  24671. axes = me.getAxes(),
  24672. axis,
  24673. seriesList = me.getSeries(),
  24674. series, axisSurface, thickness,
  24675. insetPadding = me.getInsetPadding(),
  24676. innerPadding = me.getInnerPadding(),
  24677. surface, gridSurface,
  24678. // shrinkBox represents padding added on each side by
  24679. // innerPadding & insetPadding configs and the legend.
  24680. shrinkBox = Ext.apply({}, insetPadding),
  24681. mainRect, innerWidth, innerHeight, elements, floating, floatingValue, matrix, i, ln,
  24682. isRtl = me.getInherited().rtl,
  24683. flipXY = me.getFlipXY(),
  24684. caption;
  24685. if (width <= 0 || height <= 0) {
  24686. return;
  24687. }
  24688. me.suspendThicknessChanged();
  24689. for (i = 0; i < axes.length; i++) {
  24690. axis = axes[i];
  24691. axisSurface = axis.getSurface();
  24692. floating = axis.getFloating();
  24693. floatingValue = floating ? floating.value : null;
  24694. thickness = axis.getThickness();
  24695. switch (axis.getPosition()) {
  24696. case 'top':
  24697. axisSurface.setRect([
  24698. left,
  24699. top + shrinkBox.top + 1,
  24700. width,
  24701. thickness
  24702. ]);
  24703. break;
  24704. case 'bottom':
  24705. axisSurface.setRect([
  24706. left,
  24707. top + height - (shrinkBox.bottom + thickness),
  24708. width,
  24709. thickness
  24710. ]);
  24711. break;
  24712. case 'left':
  24713. axisSurface.setRect([
  24714. left + shrinkBox.left,
  24715. top,
  24716. thickness,
  24717. height
  24718. ]);
  24719. break;
  24720. case 'right':
  24721. axisSurface.setRect([
  24722. left + width - (shrinkBox.right + thickness),
  24723. top,
  24724. thickness,
  24725. height
  24726. ]);
  24727. break;
  24728. }
  24729. if (floatingValue === null) {
  24730. shrinkBox[axis.getPosition()] += thickness;
  24731. }
  24732. }
  24733. width -= shrinkBox.left + shrinkBox.right;
  24734. height -= shrinkBox.top + shrinkBox.bottom;
  24735. mainRect = [
  24736. left + shrinkBox.left,
  24737. top + shrinkBox.top,
  24738. width,
  24739. height
  24740. ];
  24741. shrinkBox.left += innerPadding.left;
  24742. shrinkBox.top += innerPadding.top;
  24743. shrinkBox.right += innerPadding.right;
  24744. shrinkBox.bottom += innerPadding.bottom;
  24745. innerWidth = width - innerPadding.left - innerPadding.right;
  24746. innerHeight = height - innerPadding.top - innerPadding.bottom;
  24747. me.setInnerRect([
  24748. shrinkBox.left,
  24749. shrinkBox.top,
  24750. innerWidth,
  24751. innerHeight
  24752. ]);
  24753. if (innerWidth <= 0 || innerHeight <= 0) {
  24754. return;
  24755. }
  24756. me.setMainRect(mainRect);
  24757. me.getSurface().setRect(mainRect);
  24758. for (i = 0 , ln = me.surfaceMap.grid && me.surfaceMap.grid.length; i < ln; i++) {
  24759. gridSurface = me.surfaceMap.grid[i];
  24760. gridSurface.setRect(mainRect);
  24761. gridSurface.matrix.set(1, 0, 0, 1, innerPadding.left, innerPadding.top);
  24762. gridSurface.matrix.inverse(gridSurface.inverseMatrix);
  24763. }
  24764. for (i = 0; i < axes.length; i++) {
  24765. axis = axes[i];
  24766. axis.getRange(true);
  24767. axisSurface = axis.getSurface();
  24768. matrix = axisSurface.matrix;
  24769. elements = matrix.elements;
  24770. switch (axis.getPosition()) {
  24771. case 'top':
  24772. case 'bottom':
  24773. elements[4] = shrinkBox.left;
  24774. axis.setLength(innerWidth);
  24775. break;
  24776. case 'left':
  24777. case 'right':
  24778. elements[5] = shrinkBox.top;
  24779. axis.setLength(innerHeight);
  24780. break;
  24781. }
  24782. axis.updateTitleSprite();
  24783. matrix.inverse(axisSurface.inverseMatrix);
  24784. }
  24785. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  24786. series = seriesList[i];
  24787. surface = series.getSurface();
  24788. surface.setRect(mainRect);
  24789. if (flipXY) {
  24790. if (isRtl) {
  24791. surface.matrix.set(0, -1, -1, 0, innerPadding.left + innerWidth, innerPadding.top + innerHeight);
  24792. } else {
  24793. surface.matrix.set(0, -1, 1, 0, innerPadding.left, innerPadding.top + innerHeight);
  24794. }
  24795. } else {
  24796. surface.matrix.set(1, 0, 0, -1, innerPadding.left, innerPadding.top + innerHeight);
  24797. }
  24798. surface.matrix.inverse(surface.inverseMatrix);
  24799. series.getOverlaySurface().setRect(mainRect);
  24800. }
  24801. if (captionList) {
  24802. for (i = 0 , ln = captionList.length; i < ln; i++) {
  24803. caption = captionList[i];
  24804. if (caption.getAlignTo() === 'series') {
  24805. caption.alignRect(mainRect);
  24806. }
  24807. caption.performLayout();
  24808. }
  24809. }
  24810. // In certain cases 'performLayout' override is not an option without major code duplication
  24811. // 'afterChartLayout' can be a cleaner solution in such cases (because of the timing
  24812. // of its call).
  24813. me.afterChartLayout();
  24814. // currently in cartesian charts only (used by Navigator)
  24815. me.redraw();
  24816. me.resumeAnimation();
  24817. // 'resumeThicknessChanged' may trigger another layout, if the 'redraw' call above
  24818. // resulted in a situation where an axis is no longer 'thick' enough to accommodate
  24819. // the new labels. E.g. the labels were: 'Bob', 'Ann', 'Joe' and now they are 'Jonathan',
  24820. // 'Rachael', 'Michael'. An axis has to be made thicker now, and another layout should be
  24821. // performed. This second layout is not scheduled, but performed immediately, which will
  24822. // increment the 'chartLayoutCount' again.
  24823. me.resumeThicknessChanged();
  24824. me.chartLayoutCount--;
  24825. // 'checkLayoutEnd' will check if another layout is already running or scheduled and,
  24826. // if neither is the case, will fire the 'layout' event, meaning we are totally done
  24827. // with layout at this point.
  24828. me.checkLayoutEnd();
  24829. },
  24830. afterChartLayout: Ext.emptyFn,
  24831. refloatAxes: function() {
  24832. var me = this,
  24833. axes = me.getAxes(),
  24834. axesCount = (axes && axes.length) || 0,
  24835. axis, axisSurface, axisRect, floating, value, alongAxis, matrix,
  24836. chartRect = me.getChartRect(),
  24837. inset = me.getInsetPadding(),
  24838. inner = me.getInnerPadding(),
  24839. width = chartRect[2] - inset.left - inset.right,
  24840. height = chartRect[3] - inset.top - inset.bottom,
  24841. isHorizontal, i;
  24842. for (i = 0; i < axesCount; i++) {
  24843. axis = axes[i];
  24844. floating = axis.getFloating();
  24845. value = floating ? floating.value : null;
  24846. if (value === null) {
  24847. axis.floatingAtCoord = null;
  24848. continue;
  24849. }
  24850. axisSurface = axis.getSurface();
  24851. axisRect = axisSurface.getRect();
  24852. if (!axisRect) {
  24853. continue;
  24854. }
  24855. axisRect = axisRect.slice();
  24856. alongAxis = me.getAxis(floating.alongAxis);
  24857. if (alongAxis) {
  24858. isHorizontal = alongAxis.getAlignment() === 'horizontal';
  24859. if (Ext.isString(value)) {
  24860. value = alongAxis.getCoordFor(value);
  24861. }
  24862. alongAxis.floatingAxes[axis.getId()] = value;
  24863. matrix = alongAxis.getSprites()[0].attr.matrix;
  24864. if (isHorizontal) {
  24865. value = value * matrix.getXX() + matrix.getDX();
  24866. axis.floatingAtCoord = value + inner.left + inner.right;
  24867. } else {
  24868. value = value * matrix.getYY() + matrix.getDY();
  24869. axis.floatingAtCoord = value + inner.top + inner.bottom;
  24870. }
  24871. } else {
  24872. isHorizontal = axis.getAlignment() === 'horizontal';
  24873. if (isHorizontal) {
  24874. axis.floatingAtCoord = value + inner.top + inner.bottom;
  24875. } else {
  24876. axis.floatingAtCoord = value + inner.left + inner.right;
  24877. }
  24878. value = axisSurface.roundPixel(0.01 * value * (isHorizontal ? height : width));
  24879. }
  24880. switch (axis.getPosition()) {
  24881. case 'top':
  24882. axisRect[1] = inset.top + inner.top + value - axisRect[3] + 1;
  24883. break;
  24884. case 'bottom':
  24885. axisRect[1] = inset.top + inner.top + (alongAxis ? value : height - value);
  24886. break;
  24887. case 'left':
  24888. axisRect[0] = inset.left + inner.left + value - axisRect[2];
  24889. break;
  24890. case 'right':
  24891. axisRect[0] = inset.left + inner.left + (alongAxis ? value : width - value) - 1;
  24892. break;
  24893. }
  24894. axisSurface.setRect(axisRect);
  24895. }
  24896. },
  24897. redraw: function() {
  24898. var me = this,
  24899. seriesList = me.getSeries(),
  24900. axes = me.getAxes(),
  24901. rect = me.getMainRect(),
  24902. innerWidth, innerHeight,
  24903. innerPadding = me.getInnerPadding(),
  24904. sprites, xRange, yRange, isSide, attr, i, j, ln, axis, axisX, axisY, range, visibleRange,
  24905. flipXY = me.getFlipXY(),
  24906. zBase = 1000,
  24907. zIndex, markersZIndex, series, sprite, markers;
  24908. if (!rect) {
  24909. return;
  24910. }
  24911. innerWidth = rect[2] - innerPadding.left - innerPadding.right;
  24912. innerHeight = rect[3] - innerPadding.top - innerPadding.bottom;
  24913. for (i = 0; i < seriesList.length; i++) {
  24914. series = seriesList[i];
  24915. axisX = series.getXAxis();
  24916. if (axisX) {
  24917. visibleRange = axisX.getVisibleRange();
  24918. xRange = axisX.getRange();
  24919. xRange = [
  24920. xRange[0] + (xRange[1] - xRange[0]) * visibleRange[0],
  24921. xRange[0] + (xRange[1] - xRange[0]) * visibleRange[1]
  24922. ];
  24923. } else {
  24924. xRange = series.getXRange();
  24925. }
  24926. axisY = series.getYAxis();
  24927. if (axisY) {
  24928. visibleRange = axisY.getVisibleRange();
  24929. yRange = axisY.getRange();
  24930. yRange = [
  24931. yRange[0] + (yRange[1] - yRange[0]) * visibleRange[0],
  24932. yRange[0] + (yRange[1] - yRange[0]) * visibleRange[1]
  24933. ];
  24934. } else {
  24935. yRange = series.getYRange();
  24936. }
  24937. attr = {
  24938. visibleMinX: xRange[0],
  24939. visibleMaxX: xRange[1],
  24940. visibleMinY: yRange[0],
  24941. visibleMaxY: yRange[1],
  24942. innerWidth: innerWidth,
  24943. innerHeight: innerHeight,
  24944. flipXY: flipXY
  24945. };
  24946. sprites = series.getSprites();
  24947. for (j = 0 , ln = sprites.length; j < ln; j++) {
  24948. // All the series now share the same surface, so we must assign
  24949. // the sprites a zIndex that depends on the index of their series.
  24950. sprite = sprites[j];
  24951. zIndex = sprite.attr.zIndex;
  24952. if (zIndex < zBase) {
  24953. // Set the sprite's zIndex
  24954. zIndex += (i + 1) * 100 + zBase;
  24955. sprite.attr.zIndex = zIndex;
  24956. // If the sprite is a MarkerHolder, set zIndex of the bound markers as well.
  24957. // Do this for the 'items' markers only, as those are the only ones
  24958. // that go into the 'series' surface. 'labels' and 'markers' markers
  24959. // go into the 'overlay' surface instead.
  24960. markers = sprite.getMarker('items');
  24961. if (markers) {
  24962. markersZIndex = markers.attr.zIndex;
  24963. if (markersZIndex === Number.MAX_VALUE) {
  24964. markers.attr.zIndex = zIndex;
  24965. } else if (markersZIndex < zBase) {
  24966. markers.attr.zIndex = zIndex + markersZIndex;
  24967. }
  24968. }
  24969. }
  24970. sprite.setAttributes(attr, true);
  24971. }
  24972. }
  24973. for (i = 0; i < axes.length; i++) {
  24974. axis = axes[i];
  24975. isSide = axis.isSide();
  24976. sprites = axis.getSprites();
  24977. range = axis.getRange();
  24978. visibleRange = axis.getVisibleRange();
  24979. attr = {
  24980. dataMin: range[0],
  24981. dataMax: range[1],
  24982. visibleMin: visibleRange[0],
  24983. visibleMax: visibleRange[1]
  24984. };
  24985. if (isSide) {
  24986. attr.length = innerHeight;
  24987. attr.startGap = innerPadding.bottom;
  24988. attr.endGap = innerPadding.top;
  24989. } else {
  24990. attr.length = innerWidth;
  24991. attr.startGap = innerPadding.left;
  24992. attr.endGap = innerPadding.right;
  24993. }
  24994. for (j = 0 , ln = sprites.length; j < ln; j++) {
  24995. sprites[j].setAttributes(attr, true);
  24996. }
  24997. }
  24998. me.renderFrame();
  24999. me.callParent();
  25000. },
  25001. renderFrame: function() {
  25002. this.refloatAxes();
  25003. this.callParent();
  25004. }
  25005. });
  25006. /**
  25007. * @class Ext.chart.grid.CircularGrid
  25008. * @extends Ext.draw.sprite.Circle
  25009. *
  25010. * Circular Grid sprite. Used by Radar chart to render a series of concentric circles.
  25011. */
  25012. Ext.define('Ext.chart.grid.CircularGrid', {
  25013. extend: 'Ext.draw.sprite.Circle',
  25014. alias: 'grid.circular',
  25015. inheritableStatics: {
  25016. def: {
  25017. defaults: {
  25018. r: 1,
  25019. strokeStyle: '#DDD'
  25020. }
  25021. }
  25022. }
  25023. });
  25024. /**
  25025. * @class Ext.chart.grid.RadialGrid
  25026. * @extends Ext.draw.sprite.Path
  25027. *
  25028. * Radial Grid sprite. Used by Radar chart to render a series of radial lines.
  25029. * Represents the scale of the radar chart on the yField.
  25030. */
  25031. Ext.define('Ext.chart.grid.RadialGrid', {
  25032. extend: 'Ext.draw.sprite.Path',
  25033. alias: 'grid.radial',
  25034. inheritableStatics: {
  25035. def: {
  25036. processors: {
  25037. startRadius: 'number',
  25038. endRadius: 'number'
  25039. },
  25040. defaults: {
  25041. startRadius: 0,
  25042. endRadius: 1,
  25043. scalingCenterX: 0,
  25044. scalingCenterY: 0,
  25045. strokeStyle: '#DDD'
  25046. },
  25047. triggers: {
  25048. startRadius: 'path,bbox',
  25049. endRadius: 'path,bbox'
  25050. }
  25051. }
  25052. },
  25053. render: function() {
  25054. this.callParent(arguments);
  25055. },
  25056. updatePath: function(path, attr) {
  25057. var startRadius = attr.startRadius,
  25058. endRadius = attr.endRadius;
  25059. path.moveTo(startRadius, 0);
  25060. path.lineTo(endRadius, 0);
  25061. }
  25062. });
  25063. /**
  25064. * @class Ext.chart.PolarChart
  25065. * @extends Ext.chart.AbstractChart
  25066. * @xtype polar
  25067. *
  25068. * Represent a chart that uses polar coordinates.
  25069. * A polar chart has two axes: an angular axis (which is a circle) and
  25070. * a radial axis (a straight line from the center to the edge of the circle).
  25071. * The angular axis is usually a Category axis while the radial axis is
  25072. * typically numerical.
  25073. *
  25074. * Pie charts and Radar charts are common examples of Polar charts.
  25075. *
  25076. * Please check out the summary for the {@link Ext.chart.AbstractChart} as well,
  25077. * for helpful tips and important details.
  25078. *
  25079. */
  25080. Ext.define('Ext.chart.PolarChart', {
  25081. extend: 'Ext.chart.AbstractChart',
  25082. requires: [
  25083. 'Ext.chart.grid.CircularGrid',
  25084. 'Ext.chart.grid.RadialGrid'
  25085. ],
  25086. xtype: 'polar',
  25087. isPolar: true,
  25088. config: {
  25089. /**
  25090. * @cfg {Array} center Determines the center of the polar chart.
  25091. * Updated when the chart performs layout.
  25092. */
  25093. center: [
  25094. 0,
  25095. 0
  25096. ],
  25097. /**
  25098. * @cfg {Number} radius Determines the radius of the polar chart.
  25099. * Updated when the chart performs layout.
  25100. */
  25101. radius: 0,
  25102. /**
  25103. * @cfg {Number} innerPadding The amount of inner padding in pixels.
  25104. * Inner padding is the padding from the outermost angular axis to the series.
  25105. */
  25106. innerPadding: 0
  25107. },
  25108. getDirectionForAxis: function(position) {
  25109. return position === 'radial' ? 'Y' : 'X';
  25110. },
  25111. updateCenter: function(center) {
  25112. var me = this,
  25113. axes = me.getAxes(),
  25114. series = me.getSeries(),
  25115. i, ln, axis, seriesItem;
  25116. for (i = 0 , ln = axes.length; i < ln; i++) {
  25117. axis = axes[i];
  25118. axis.setCenter(center);
  25119. }
  25120. for (i = 0 , ln = series.length; i < ln; i++) {
  25121. seriesItem = series[i];
  25122. seriesItem.setCenter(center);
  25123. }
  25124. },
  25125. applyInnerPadding: function(padding, oldPadding) {
  25126. return Ext.isNumber(padding) ? padding : oldPadding;
  25127. },
  25128. updateInnerPadding: function() {
  25129. if (!this.isConfiguring) {
  25130. this.performLayout();
  25131. }
  25132. },
  25133. doSetSurfaceRect: function(surface, rect) {
  25134. var mainRect = this.getMainRect();
  25135. surface.setRect(rect);
  25136. surface.matrix.set(1, 0, 0, 1, mainRect[0] - rect[0], mainRect[1] - rect[1]);
  25137. surface.inverseMatrix.set(1, 0, 0, 1, rect[0] - mainRect[0], rect[1] - mainRect[1]);
  25138. },
  25139. applyAxes: function(newAxes, oldAxes) {
  25140. var me = this,
  25141. firstSeries = Ext.Array.from(me.config.series)[0],
  25142. i, ln, axis, foundAngular;
  25143. if (firstSeries && firstSeries.type === 'radar' && newAxes && newAxes.length) {
  25144. // For compatibility with ExtJS: add a default angular axis if it's missing
  25145. for (i = 0 , ln = newAxes.length; i < ln; i++) {
  25146. axis = newAxes[i];
  25147. if (axis.position === 'angular') {
  25148. foundAngular = true;
  25149. break;
  25150. }
  25151. }
  25152. if (!foundAngular) {
  25153. newAxes.push({
  25154. type: 'category',
  25155. position: 'angular',
  25156. fields: firstSeries.xField || firstSeries.angleField,
  25157. style: {
  25158. estStepSize: 1
  25159. },
  25160. grid: true
  25161. });
  25162. }
  25163. }
  25164. return this.callParent([
  25165. newAxes,
  25166. oldAxes
  25167. ]);
  25168. },
  25169. performLayout: function() {
  25170. var me = this,
  25171. applyThickness = true;
  25172. try {
  25173. me.chartLayoutCount++;
  25174. me.suspendAnimation();
  25175. if (this.callParent() === false) {
  25176. applyThickness = false;
  25177. // Animation will be decremented in finally block
  25178. return;
  25179. }
  25180. me.suspendThicknessChanged();
  25181. // eslint-disable-next-line vars-on-top, one-var
  25182. var chartRect = me.getSurface('chart').getRect(),
  25183. inset = me.getInsetPadding(),
  25184. inner = me.getInnerPadding(),
  25185. shrinkBox = Ext.apply({}, inset),
  25186. width = Math.max(1, chartRect[2] - chartRect[0] - inset.left - inset.right),
  25187. height = Math.max(1, chartRect[3] - chartRect[1] - inset.top - inset.bottom),
  25188. mainRect = [
  25189. chartRect[0] + inset.left,
  25190. chartRect[1] + inset.top,
  25191. width + chartRect[0],
  25192. height + chartRect[1]
  25193. ],
  25194. seriesList = me.getSeries(),
  25195. innerWidth = width - inner * 2,
  25196. innerHeight = height - inner * 2,
  25197. center = [
  25198. (chartRect[0] + innerWidth) * 0.5 + inner,
  25199. (chartRect[1] + innerHeight) * 0.5 + inner
  25200. ],
  25201. radius = Math.min(innerWidth, innerHeight) * 0.5,
  25202. axes = me.getAxes(),
  25203. angularAxes = [],
  25204. radialAxes = [],
  25205. seriesRadius = radius - inner,
  25206. grid = me.surfaceMap.grid,
  25207. captionList = me.captionList,
  25208. i, ln, shrinkRadius, floating, floatingValue, // eslint-disable-line no-unused-vars
  25209. gaugeSeries, gaugeRadius, side, series, axis, thickness, halfLineWidth, caption;
  25210. me.setMainRect(mainRect);
  25211. me.doSetSurfaceRect(me.getSurface(), mainRect);
  25212. if (grid) {
  25213. for (i = 0 , ln = grid.length; i < ln; i++) {
  25214. me.doSetSurfaceRect(grid[i], chartRect);
  25215. }
  25216. }
  25217. for (i = 0 , ln = axes.length; i < ln; i++) {
  25218. axis = axes[i];
  25219. switch (axis.getPosition()) {
  25220. case 'angular':
  25221. angularAxes.push(axis);
  25222. break;
  25223. case 'radial':
  25224. radialAxes.push(axis);
  25225. break;
  25226. }
  25227. }
  25228. for (i = 0 , ln = angularAxes.length; i < ln; i++) {
  25229. axis = angularAxes[i];
  25230. floating = axis.getFloating();
  25231. floatingValue = floating ? floating.value : null;
  25232. me.doSetSurfaceRect(axis.getSurface(), chartRect);
  25233. thickness = axis.getThickness();
  25234. for (side in shrinkBox) {
  25235. shrinkBox[side] += thickness;
  25236. }
  25237. width = chartRect[2] - shrinkBox.left - shrinkBox.right;
  25238. height = chartRect[3] - shrinkBox.top - shrinkBox.bottom;
  25239. shrinkRadius = Math.min(width, height) * 0.5;
  25240. if (i === 0) {
  25241. seriesRadius = shrinkRadius - inner;
  25242. }
  25243. axis.setMinimum(0);
  25244. axis.setLength(shrinkRadius);
  25245. axis.getSprites();
  25246. halfLineWidth = axis.sprites[0].attr.lineWidth * 0.5;
  25247. for (side in shrinkBox) {
  25248. shrinkBox[side] += halfLineWidth;
  25249. }
  25250. }
  25251. for (i = 0 , ln = radialAxes.length; i < ln; i++) {
  25252. axis = radialAxes[i];
  25253. me.doSetSurfaceRect(axis.getSurface(), chartRect);
  25254. axis.setMinimum(0);
  25255. axis.setLength(seriesRadius);
  25256. axis.getSprites();
  25257. }
  25258. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  25259. series = seriesList[i];
  25260. if (series.type === 'gauge' && !gaugeSeries) {
  25261. gaugeSeries = series;
  25262. } else {
  25263. series.setRadius(seriesRadius);
  25264. }
  25265. me.doSetSurfaceRect(series.getSurface(), mainRect);
  25266. }
  25267. me.doSetSurfaceRect(me.getSurface('overlay'), chartRect);
  25268. if (gaugeSeries) {
  25269. gaugeSeries.setRect(mainRect);
  25270. gaugeRadius = gaugeSeries.getRadius() - inner;
  25271. me.setRadius(gaugeRadius);
  25272. me.setCenter(gaugeSeries.getCenter());
  25273. gaugeSeries.setRadius(gaugeRadius);
  25274. if (axes.length && axes[0].getPosition() === 'gauge') {
  25275. axis = axes[0];
  25276. me.doSetSurfaceRect(axis.getSurface(), chartRect);
  25277. axis.setTotalAngle(gaugeSeries.getTotalAngle());
  25278. axis.setLength(gaugeRadius);
  25279. }
  25280. } else {
  25281. me.setRadius(radius);
  25282. me.setCenter(center);
  25283. }
  25284. if (captionList) {
  25285. for (i = 0 , ln = captionList.length; i < ln; i++) {
  25286. caption = captionList[i];
  25287. if (caption.getAlignTo() === 'series') {
  25288. caption.alignRect(mainRect);
  25289. }
  25290. caption.performLayout();
  25291. }
  25292. }
  25293. me.redraw();
  25294. } finally {
  25295. me.resumeAnimation();
  25296. if (applyThickness) {
  25297. me.resumeThicknessChanged();
  25298. }
  25299. me.chartLayoutCount--;
  25300. me.checkLayoutEnd();
  25301. }
  25302. },
  25303. refloatAxes: function() {
  25304. var me = this,
  25305. axes = me.getAxes(),
  25306. mainRect = me.getMainRect(),
  25307. floating, value, alongAxis, i, n, axis, radius;
  25308. if (!mainRect) {
  25309. return;
  25310. }
  25311. radius = 0.5 * Math.min(mainRect[2], mainRect[3]);
  25312. for (i = 0 , n = axes.length; i < n; i++) {
  25313. axis = axes[i];
  25314. floating = axis.getFloating();
  25315. value = floating ? floating.value : null;
  25316. if (value !== null) {
  25317. alongAxis = me.getAxis(floating.alongAxis);
  25318. if (axis.getPosition() === 'angular') {
  25319. if (alongAxis) {
  25320. value = alongAxis.getLength() * value / alongAxis.getRange()[1];
  25321. } else {
  25322. value = 0.01 * value * radius;
  25323. }
  25324. axis.sprites[0].setAttributes({
  25325. length: value
  25326. }, true);
  25327. } else {
  25328. if (alongAxis) {
  25329. if (Ext.isString(value)) {
  25330. value = alongAxis.getCoordFor(value);
  25331. }
  25332. value = value / (alongAxis.getRange()[1] + 1) * Math.PI * 2 - Math.PI * 1.5 + axis.getRotation();
  25333. } else {
  25334. value = Ext.draw.Draw.rad(value);
  25335. }
  25336. axis.sprites[0].setAttributes({
  25337. baseRotation: value
  25338. }, true);
  25339. }
  25340. }
  25341. }
  25342. },
  25343. redraw: function() {
  25344. var me = this,
  25345. axes = me.getAxes(),
  25346. axis,
  25347. seriesList = me.getSeries(),
  25348. series, i, ln;
  25349. for (i = 0 , ln = axes.length; i < ln; i++) {
  25350. axis = axes[i];
  25351. axis.getSprites();
  25352. }
  25353. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  25354. series = seriesList[i];
  25355. series.getSprites();
  25356. }
  25357. me.renderFrame();
  25358. me.callParent();
  25359. },
  25360. renderFrame: function() {
  25361. this.refloatAxes();
  25362. this.callParent();
  25363. }
  25364. });
  25365. /**
  25366. * @class Ext.chart.SpaceFillingChart
  25367. * @extends Ext.chart.AbstractChart
  25368. *
  25369. * Creates a chart that fills the entire area of the chart.
  25370. * e.g. Gauge Charts
  25371. */
  25372. Ext.define('Ext.chart.SpaceFillingChart', {
  25373. extend: 'Ext.chart.AbstractChart',
  25374. xtype: 'spacefilling',
  25375. config: {},
  25376. performLayout: function() {
  25377. var me = this;
  25378. try {
  25379. me.chartLayoutCount++;
  25380. me.suspendAnimation();
  25381. if (me.callParent() === false) {
  25382. // animationSuspendCount will still be decremented
  25383. return;
  25384. }
  25385. // eslint-disable-next-line vars-on-top, one-var
  25386. var chartRect = me.getSurface('chart').getRect(),
  25387. padding = me.getInsetPadding(),
  25388. width = chartRect[2] - padding.left - padding.right,
  25389. height = chartRect[3] - padding.top - padding.bottom,
  25390. mainRect = [
  25391. padding.left,
  25392. padding.top,
  25393. width,
  25394. height
  25395. ],
  25396. seriesList = me.getSeries(),
  25397. series, i, ln;
  25398. me.getSurface().setRect(mainRect);
  25399. me.setMainRect(mainRect);
  25400. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  25401. series = seriesList[i];
  25402. series.getSurface().setRect(mainRect);
  25403. if (series.setRect) {
  25404. series.setRect(mainRect);
  25405. }
  25406. series.getOverlaySurface().setRect(chartRect);
  25407. }
  25408. me.redraw();
  25409. } finally {
  25410. me.resumeAnimation();
  25411. me.chartLayoutCount--;
  25412. me.checkLayoutEnd();
  25413. }
  25414. },
  25415. redraw: function() {
  25416. var me = this,
  25417. seriesList = me.getSeries(),
  25418. series, i, ln;
  25419. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  25420. series = seriesList[i];
  25421. series.getSprites();
  25422. }
  25423. me.renderFrame();
  25424. me.callParent();
  25425. }
  25426. });
  25427. /**
  25428. * @private
  25429. * @class Ext.chart.axis.sprite.Axis3D
  25430. * @extends Ext.chart.axis.sprite.Axis
  25431. *
  25432. * The {@link Ext.chart.axis.Axis3D 3D axis} sprite.
  25433. * Only 3D cartesian axes are rendered with this sprite.
  25434. */
  25435. Ext.define('Ext.chart.axis.sprite.Axis3D', {
  25436. extend: 'Ext.chart.axis.sprite.Axis',
  25437. alias: 'sprite.axis3d',
  25438. type: 'axis3d',
  25439. inheritableStatics: {
  25440. def: {
  25441. processors: {
  25442. depth: 'number'
  25443. },
  25444. defaults: {
  25445. depth: 0
  25446. },
  25447. triggers: {
  25448. depth: 'layout'
  25449. }
  25450. }
  25451. },
  25452. config: {
  25453. animation: {
  25454. customDurations: {
  25455. depth: 0
  25456. }
  25457. }
  25458. },
  25459. layoutUpdater: function() {
  25460. var me = this,
  25461. chart = me.getAxis().getChart();
  25462. if (chart.isInitializing) {
  25463. return;
  25464. }
  25465. // eslint-disable-next-line vars-on-top, one-var
  25466. var attr = me.attr,
  25467. layout = me.getLayout(),
  25468. depth = layout.isDiscrete ? 0 : attr.depth,
  25469. isRtl = chart.getInherited().rtl,
  25470. min = attr.dataMin + (attr.dataMax - attr.dataMin) * attr.visibleMin,
  25471. max = attr.dataMin + (attr.dataMax - attr.dataMin) * attr.visibleMax,
  25472. context = {
  25473. attr: attr,
  25474. segmenter: me.getSegmenter(),
  25475. renderer: me.defaultRenderer
  25476. };
  25477. if (attr.position === 'left' || attr.position === 'right') {
  25478. attr.translationX = 0;
  25479. attr.translationY = max * (attr.length - depth) / (max - min) + depth;
  25480. attr.scalingX = 1;
  25481. attr.scalingY = (-attr.length + depth) / (max - min);
  25482. attr.scalingCenterY = 0;
  25483. attr.scalingCenterX = 0;
  25484. me.applyTransformations(true);
  25485. } else if (attr.position === 'top' || attr.position === 'bottom') {
  25486. if (isRtl) {
  25487. attr.translationX = attr.length + min * attr.length / (max - min) + 1;
  25488. } else {
  25489. attr.translationX = -min * attr.length / (max - min);
  25490. }
  25491. attr.translationY = 0;
  25492. attr.scalingX = (isRtl ? -1 : 1) * (attr.length - depth) / (max - min);
  25493. attr.scalingY = 1;
  25494. attr.scalingCenterY = 0;
  25495. attr.scalingCenterX = 0;
  25496. me.applyTransformations(true);
  25497. }
  25498. if (layout) {
  25499. layout.calculateLayout(context);
  25500. me.setLayoutContext(context);
  25501. }
  25502. },
  25503. renderAxisLine: function(surface, ctx, layout, clipRect) {
  25504. var me = this,
  25505. attr = me.attr,
  25506. halfLineWidth = attr.lineWidth * 0.5,
  25507. layout = me.getLayout(),
  25508. // eslint-disable-line no-redeclare
  25509. depth = layout.isDiscrete ? 0 : attr.depth,
  25510. docked = attr.position,
  25511. position, gaugeAngles;
  25512. if (attr.axisLine && attr.length) {
  25513. switch (docked) {
  25514. case 'left':
  25515. position = surface.roundPixel(clipRect[2]) - halfLineWidth;
  25516. ctx.moveTo(position, -attr.endGap + depth);
  25517. ctx.lineTo(position, attr.length + attr.startGap);
  25518. break;
  25519. case 'right':
  25520. ctx.moveTo(halfLineWidth, -attr.endGap);
  25521. ctx.lineTo(halfLineWidth, attr.length + attr.startGap);
  25522. break;
  25523. case 'bottom':
  25524. ctx.moveTo(-attr.startGap, halfLineWidth);
  25525. ctx.lineTo(attr.length - depth + attr.endGap, halfLineWidth);
  25526. break;
  25527. case 'top':
  25528. position = surface.roundPixel(clipRect[3]) - halfLineWidth;
  25529. ctx.moveTo(-attr.startGap, position);
  25530. ctx.lineTo(attr.length + attr.endGap, position);
  25531. break;
  25532. case 'angular':
  25533. ctx.moveTo(attr.centerX + attr.length, attr.centerY);
  25534. ctx.arc(attr.centerX, attr.centerY, attr.length, 0, Math.PI * 2, true);
  25535. break;
  25536. case 'gauge':
  25537. gaugeAngles = me.getGaugeAngles();
  25538. ctx.moveTo(attr.centerX + Math.cos(gaugeAngles.start) * attr.length, attr.centerY + Math.sin(gaugeAngles.start) * attr.length);
  25539. ctx.arc(attr.centerX, attr.centerY, attr.length, gaugeAngles.start, gaugeAngles.end, true);
  25540. break;
  25541. }
  25542. }
  25543. }
  25544. });
  25545. /**
  25546. * @class Ext.chart.axis.Axis3D
  25547. * @extends Ext.chart.axis.Axis
  25548. * @xtype axis3d
  25549. *
  25550. * Defines a 3D axis for charts.
  25551. *
  25552. * A 3D axis has the same properties as the regular {@link Ext.chart.axis.Axis axis},
  25553. * plus a notion of depth. The depth of the 3D axis is determined automatically
  25554. * based on the depth of the bound series.
  25555. *
  25556. * This type of axis has the following limitations compared to the regular axis class:
  25557. * - supported {@link Ext.chart.axis.Axis#position positions} are 'left' and 'bottom' only;
  25558. * - floating axes are not supported.
  25559. *
  25560. * At the present moment only {@link Ext.chart.series.Bar3D} series can make use of the 3D axis.
  25561. */
  25562. Ext.define('Ext.chart.axis.Axis3D', {
  25563. extend: 'Ext.chart.axis.Axis',
  25564. xtype: 'axis3d',
  25565. requires: [
  25566. 'Ext.chart.axis.sprite.Axis3D'
  25567. ],
  25568. config: {
  25569. /**
  25570. * @private
  25571. * The depth of the axis. Determined automatically.
  25572. */
  25573. depth: 0
  25574. },
  25575. /**
  25576. * @cfg {String} position
  25577. * Where to set the axis. Available options are `left` and `bottom`.
  25578. */
  25579. onSeriesChange: function(chart) {
  25580. var me = this,
  25581. eventName = 'depthchange',
  25582. listenerName = 'onSeriesDepthChange',
  25583. i, series;
  25584. function toggle(action) {
  25585. var boundSeries = me.boundSeries;
  25586. for (i = 0; i < boundSeries.length; i++) {
  25587. series = boundSeries[i];
  25588. series[action](eventName, listenerName, me);
  25589. }
  25590. }
  25591. // Remove 'depthchange' listeners from old bound series, if any.
  25592. toggle('un');
  25593. me.callParent(arguments);
  25594. // Add 'depthchange' listeners to new bound series.
  25595. toggle('on');
  25596. },
  25597. onSeriesDepthChange: function(series, depth) {
  25598. var me = this,
  25599. maxDepth = depth,
  25600. boundSeries = me.boundSeries,
  25601. i, item;
  25602. if (depth > me.getDepth()) {
  25603. maxDepth = depth;
  25604. } else {
  25605. for (i = 0; i < boundSeries.length; i++) {
  25606. item = boundSeries[i];
  25607. if (item !== series && item.getDepth) {
  25608. depth = item.getDepth();
  25609. if (depth > maxDepth) {
  25610. maxDepth = depth;
  25611. }
  25612. }
  25613. }
  25614. }
  25615. me.setDepth(maxDepth);
  25616. },
  25617. updateDepth: function(depth) {
  25618. var me = this,
  25619. sprites = me.getSprites(),
  25620. attr = {
  25621. depth: depth
  25622. };
  25623. if (sprites && sprites.length) {
  25624. sprites[0].setAttributes(attr);
  25625. }
  25626. if (me.gridSpriteEven && me.gridSpriteOdd) {
  25627. me.gridSpriteEven.getTemplate().setAttributes(attr);
  25628. me.gridSpriteOdd.getTemplate().setAttributes(attr);
  25629. }
  25630. },
  25631. getGridAlignment: function() {
  25632. switch (this.getPosition()) {
  25633. case 'left':
  25634. case 'right':
  25635. return 'horizontal3d';
  25636. case 'top':
  25637. case 'bottom':
  25638. return 'vertical3d';
  25639. }
  25640. }
  25641. });
  25642. /**
  25643. * @class Ext.chart.axis.Category
  25644. * @extends Ext.chart.axis.Axis
  25645. *
  25646. * A type of axis that displays items in categories. This axis is generally used to
  25647. * display categorical information like names of items, month names, quarters, etc.
  25648. * but no quantitative values. For that other type of information
  25649. * {@link Ext.chart.axis.Numeric Numeric} axis are more suitable.
  25650. *
  25651. * As with other axis you can set the position of the axis and its title. For example:
  25652. *
  25653. * @example
  25654. * Ext.create({
  25655. * xtype: 'cartesian',
  25656. * renderTo: document.body,
  25657. * width: 600,
  25658. * height: 400,
  25659. * innerPadding: '0 40 0 40',
  25660. * store: {
  25661. * fields: ['name', 'data1', 'data2', 'data3'],
  25662. * data: [{
  25663. * 'name': 'metric one',
  25664. * 'data1': 10,
  25665. * 'data2': 12,
  25666. * 'data3': 14
  25667. * }, {
  25668. * 'name': 'metric two',
  25669. * 'data1': 7,
  25670. * 'data2': 8,
  25671. * 'data3': 16
  25672. * }, {
  25673. * 'name': 'metric three',
  25674. * 'data1': 5,
  25675. * 'data2': 2,
  25676. * 'data3': 14
  25677. * }, {
  25678. * 'name': 'metric four',
  25679. * 'data1': 2,
  25680. * 'data2': 14,
  25681. * 'data3': 6
  25682. * }, {
  25683. * 'name': 'metric five',
  25684. * 'data1': 27,
  25685. * 'data2': 38,
  25686. * 'data3': 36
  25687. * }]
  25688. * },
  25689. * axes: {
  25690. * type: 'category',
  25691. * position: 'bottom',
  25692. * fields: ['name'],
  25693. * title: {
  25694. * text: 'Sample Values',
  25695. * fontSize: 15
  25696. * }
  25697. * },
  25698. * series: {
  25699. * type: 'area',
  25700. * subStyle: {
  25701. * fill: ['#0A3F50', '#30BDA7', '#96D4C6']
  25702. * },
  25703. * xField: 'name',
  25704. * yField: ['data1', 'data2', 'data3']
  25705. * }
  25706. * });
  25707. *
  25708. * In this example with set the category axis to the bottom of the surface, bound the axis to
  25709. * the `name` property and set as title "Sample Values".
  25710. */
  25711. Ext.define('Ext.chart.axis.Category', {
  25712. requires: [
  25713. 'Ext.chart.axis.layout.CombineDuplicate',
  25714. 'Ext.chart.axis.segmenter.Names'
  25715. ],
  25716. extend: 'Ext.chart.axis.Axis',
  25717. alias: 'axis.category',
  25718. type: 'category',
  25719. isCategory: true,
  25720. config: {
  25721. layout: 'combineDuplicate',
  25722. segmenter: 'names'
  25723. }
  25724. });
  25725. /**
  25726. * Category 3D Axis
  25727. */
  25728. Ext.define('Ext.chart.axis.Category3D', {
  25729. requires: [
  25730. 'Ext.chart.axis.layout.CombineDuplicate',
  25731. 'Ext.chart.axis.segmenter.Names'
  25732. ],
  25733. extend: 'Ext.chart.axis.Axis3D',
  25734. alias: 'axis.category3d',
  25735. type: 'category3d',
  25736. config: {
  25737. layout: 'combineDuplicate',
  25738. segmenter: 'names'
  25739. }
  25740. });
  25741. /**
  25742. * @class Ext.chart.axis.Numeric
  25743. * @extends Ext.chart.axis.Axis
  25744. *
  25745. * An axis to handle numeric values. This axis is used for quantitative data as
  25746. * opposed to the category axis. You can set minimum and maximum values to the
  25747. * axis so that the values are bound to that. If no values are set, then the
  25748. * scale will auto-adjust to the values.
  25749. *
  25750. * @example
  25751. * Ext.create({
  25752. * xtype: 'cartesian',
  25753. * renderTo: document.body,
  25754. * width: 600,
  25755. * height: 400,
  25756. * store: {
  25757. * fields: ['name', 'data1', 'data2', 'data3'],
  25758. * data: [{
  25759. * 'name': 1,
  25760. * 'data1': 10,
  25761. * 'data2': 12,
  25762. * 'data3': 14
  25763. * }, {
  25764. * 'name': 2,
  25765. * 'data1': 7,
  25766. * 'data2': 8,
  25767. * 'data3': 16
  25768. * }, {
  25769. * 'name': 3,
  25770. * 'data1': 5,
  25771. * 'data2': 2,
  25772. * 'data3': 14
  25773. * }, {
  25774. * 'name': 4,
  25775. * 'data1': 2,
  25776. * 'data2': 14,
  25777. * 'data3': 6
  25778. * }, {
  25779. * 'name': 5,
  25780. * 'data1': 27,
  25781. * 'data2': 38,
  25782. * 'data3': 36
  25783. * }]
  25784. * },
  25785. * axes: {
  25786. * type: 'numeric',
  25787. * position: 'left',
  25788. * minimum: 0,
  25789. * fields: ['data1', 'data2', 'data3'],
  25790. * title: 'Sample Values',
  25791. * grid: {
  25792. * odd: {
  25793. * opacity: 1,
  25794. * fill: '#F2F2F2',
  25795. * stroke: '#DDD',
  25796. * 'lineWidth': 1
  25797. * }
  25798. * }
  25799. * },
  25800. * series: {
  25801. * type: 'area',
  25802. * subStyle: {
  25803. * fill: ['#0A3F50', '#30BDA7', '#96D4C6']
  25804. * },
  25805. * xField: 'name',
  25806. * yField: ['data1', 'data2', 'data3']
  25807. * }
  25808. * });
  25809. *
  25810. * In this example we create an axis of Numeric type. We set a minimum value so that
  25811. * even if all series have values greater than zero, the grid starts at zero. We bind
  25812. * the axis onto the left part of the surface by setting _position_ to _left_.
  25813. * We bind three different store fields to this axis by setting _fields_ to an array.
  25814. * We set the title of the axis to _Number of Hits_ by using the _title_ property.
  25815. * We use a _grid_ configuration to set odd background rows to a certain style and even rows
  25816. * to be transparent/ignored.
  25817. *
  25818. */
  25819. Ext.define('Ext.chart.axis.Numeric', {
  25820. extend: 'Ext.chart.axis.Axis',
  25821. type: 'numeric',
  25822. alias: [
  25823. 'axis.numeric',
  25824. 'axis.radial'
  25825. ],
  25826. // legacy charts compatibility
  25827. requires: [
  25828. 'Ext.chart.axis.layout.Continuous',
  25829. 'Ext.chart.axis.segmenter.Numeric'
  25830. ],
  25831. config: {
  25832. layout: 'continuous',
  25833. segmenter: 'numeric',
  25834. aggregator: 'double'
  25835. }
  25836. });
  25837. /**
  25838. * @class Ext.chart.axis.Numeric3D
  25839. */
  25840. Ext.define('Ext.chart.axis.Numeric3D', {
  25841. extend: 'Ext.chart.axis.Axis3D',
  25842. alias: [
  25843. 'axis.numeric3d'
  25844. ],
  25845. type: 'numeric3d',
  25846. requires: [
  25847. 'Ext.chart.axis.layout.Continuous',
  25848. 'Ext.chart.axis.segmenter.Numeric'
  25849. ],
  25850. config: {
  25851. layout: 'continuous',
  25852. segmenter: 'numeric',
  25853. aggregator: 'double'
  25854. }
  25855. });
  25856. /**
  25857. * @class Ext.chart.axis.Time
  25858. * @extends Ext.chart.axis.Numeric
  25859. *
  25860. * A type of axis whose units are measured in time values. Use this axis
  25861. * for listing dates that you will want to group or dynamically change.
  25862. * If you just want to display dates as categories then use the
  25863. * Category class for axis instead.
  25864. *
  25865. * @example
  25866. * Ext.create({
  25867. * xtype: 'cartesian',
  25868. * renderTo: document.body,
  25869. * width: 600,
  25870. * height: 400,
  25871. * store: {
  25872. * fields: ['time', 'open', 'high', 'low', 'close'],
  25873. * data: [{
  25874. * 'time': new Date('Jan 1 2010').getTime(),
  25875. * 'open': 600,
  25876. * 'high': 614,
  25877. * 'low': 578,
  25878. * 'close': 590
  25879. * }, {
  25880. * 'time': new Date('Jan 2 2010').getTime(),
  25881. * 'open': 590,
  25882. * 'high': 609,
  25883. * 'low': 580,
  25884. * 'close': 580
  25885. * }, {
  25886. * 'time': new Date('Jan 3 2010').getTime(),
  25887. * 'open': 580,
  25888. * 'high': 602,
  25889. * 'low': 578,
  25890. * 'close': 602
  25891. * }, {
  25892. * 'time': new Date('Jan 4 2010').getTime(),
  25893. * 'open': 602,
  25894. * 'high': 614,
  25895. * 'low': 586,
  25896. * 'close': 586
  25897. * }]
  25898. * },
  25899. * axes: [{
  25900. * type: 'numeric',
  25901. * position: 'left',
  25902. * fields: ['open', 'high', 'low', 'close'],
  25903. * title: {
  25904. * text: 'Sample Values',
  25905. * fontSize: 15
  25906. * },
  25907. * grid: true,
  25908. * minimum: 560,
  25909. * maximum: 640
  25910. * }, {
  25911. * type: 'time',
  25912. * position: 'bottom',
  25913. * fields: ['time'],
  25914. * fromDate: new Date('Dec 31 2009'),
  25915. * toDate: new Date('Jan 5 2010'),
  25916. * title: {
  25917. * text: 'Sample Values',
  25918. * fontSize: 15
  25919. * },
  25920. * style: {
  25921. * axisLine: false
  25922. * }
  25923. * }],
  25924. * series: {
  25925. * type: 'candlestick',
  25926. * xField: 'time',
  25927. * openField: 'open',
  25928. * highField: 'high',
  25929. * lowField: 'low',
  25930. * closeField: 'close',
  25931. * style: {
  25932. * ohlcType: 'ohlc',
  25933. * dropStyle: {
  25934. * fill: 'rgb(255, 128, 128)',
  25935. * stroke: 'rgb(255, 128, 128)',
  25936. * lineWidth: 3
  25937. * },
  25938. * raiseStyle: {
  25939. * fill: 'rgb(48, 189, 167)',
  25940. * stroke: 'rgb(48, 189, 167)',
  25941. * lineWidth: 3
  25942. * }
  25943. * }
  25944. * }
  25945. * });
  25946. */
  25947. Ext.define('Ext.chart.axis.Time', {
  25948. extend: 'Ext.chart.axis.Numeric',
  25949. alias: 'axis.time',
  25950. type: 'time',
  25951. requires: [
  25952. 'Ext.chart.axis.layout.Continuous',
  25953. 'Ext.chart.axis.segmenter.Time'
  25954. ],
  25955. config: {
  25956. /**
  25957. * @cfg {String} dateFormat
  25958. * Indicates the format the date will be rendered in.
  25959. * For example: 'M d' will render the dates as 'Jan 30'.
  25960. * This config works by setting the {@link #renderer} config
  25961. * to a function that uses {@link Ext.Date#format} to format the dates
  25962. * using the given `dateFormat`.
  25963. * If the {@link #renderer} config was set by the user, changes to this config
  25964. * won't replace the user set renderer (until the user removes the renderer by
  25965. * setting the `renderer` config to `null`). In this case the way the `dateFormat`
  25966. * is used (if at all) is up to the user.
  25967. */
  25968. dateFormat: null,
  25969. /**
  25970. * @cfg {Date} fromDate The starting date for the time axis.
  25971. */
  25972. fromDate: null,
  25973. /**
  25974. * @cfg {Date} toDate The ending date for the time axis.
  25975. */
  25976. toDate: null,
  25977. layout: 'continuous',
  25978. segmenter: 'time',
  25979. aggregator: 'time'
  25980. },
  25981. updateDateFormat: function(format) {
  25982. var renderer = this.getRenderer();
  25983. if (!renderer || renderer.isDefault) {
  25984. renderer = function(axis, date) {
  25985. return Ext.Date.format(new Date(date), format);
  25986. };
  25987. renderer.isDefault = true;
  25988. this.setRenderer(renderer);
  25989. this.performLayout();
  25990. }
  25991. },
  25992. updateRenderer: function(renderer) {
  25993. var dateFormat = this.getDateFormat();
  25994. if (renderer) {
  25995. this.performLayout();
  25996. } else if (dateFormat) {
  25997. // If the user removes custom `renderer` and `dateFormat` is set,
  25998. // set the `renderer` to the default one based on `dateFormat`.
  25999. this.updateDateFormat(dateFormat);
  26000. }
  26001. },
  26002. updateFromDate: function(date) {
  26003. this.setMinimum(+date);
  26004. },
  26005. updateToDate: function(date) {
  26006. this.setMaximum(+date);
  26007. },
  26008. getCoordFor: function(value) {
  26009. if (Ext.isString(value)) {
  26010. value = new Date(value);
  26011. }
  26012. return +value;
  26013. }
  26014. });
  26015. /**
  26016. * @class Ext.chart.axis.Time3D
  26017. */
  26018. Ext.define('Ext.chart.axis.Time3D', {
  26019. extend: 'Ext.chart.axis.Numeric3D',
  26020. alias: 'axis.time3d',
  26021. type: 'time3d',
  26022. requires: [
  26023. 'Ext.chart.axis.layout.Continuous',
  26024. 'Ext.chart.axis.segmenter.Time'
  26025. ],
  26026. config: {
  26027. /**
  26028. * @cfg {String/Boolean} dateFormat
  26029. * Indicates the format the date will be rendered on.
  26030. * For example: 'M d' will render the dates as 'Jan 30', etc.
  26031. */
  26032. dateFormat: null,
  26033. /**
  26034. * @cfg {Date} fromDate The starting date for the time axis.
  26035. */
  26036. fromDate: null,
  26037. /**
  26038. * @cfg {Date} toDate The ending date for the time axis.
  26039. */
  26040. toDate: null,
  26041. layout: 'continuous',
  26042. segmenter: 'time',
  26043. aggregator: 'time'
  26044. },
  26045. updateDateFormat: function(format) {
  26046. this.setRenderer(function(axis, date) {
  26047. return Ext.Date.format(new Date(date), format);
  26048. });
  26049. },
  26050. updateFromDate: function(date) {
  26051. this.setMinimum(+date);
  26052. },
  26053. updateToDate: function(date) {
  26054. this.setMaximum(+date);
  26055. },
  26056. getCoordFor: function(value) {
  26057. if (Ext.isString(value)) {
  26058. value = new Date(value);
  26059. }
  26060. return +value;
  26061. }
  26062. });
  26063. /**
  26064. * @class Ext.chart.grid.HorizontalGrid3D
  26065. * @extends Ext.chart.grid.HorizontalGrid
  26066. *
  26067. * Horizontal 3D Grid sprite. Used in 3D Cartesian Charts.
  26068. */
  26069. Ext.define('Ext.chart.grid.HorizontalGrid3D', {
  26070. extend: 'Ext.chart.grid.HorizontalGrid',
  26071. alias: 'grid.horizontal3d',
  26072. inheritableStatics: {
  26073. def: {
  26074. processors: {
  26075. depth: 'number'
  26076. },
  26077. defaults: {
  26078. depth: 0
  26079. }
  26080. }
  26081. },
  26082. render: function(surface, ctx, rect) {
  26083. var attr = this.attr,
  26084. x = surface.roundPixel(attr.x),
  26085. y = surface.roundPixel(attr.y),
  26086. dx = surface.matrix.getDX(),
  26087. halfLineWidth = ctx.lineWidth * 0.5,
  26088. height = attr.height,
  26089. depth = attr.depth,
  26090. left, top;
  26091. if (y <= rect[1]) {
  26092. return;
  26093. }
  26094. // Horizontal stripe.
  26095. left = rect[0] + depth - dx;
  26096. top = y + halfLineWidth - depth;
  26097. ctx.beginPath();
  26098. ctx.rect(left, top, rect[2], height);
  26099. ctx.fill();
  26100. // Horizontal line.
  26101. ctx.beginPath();
  26102. ctx.moveTo(left, top);
  26103. ctx.lineTo(left + rect[2], top);
  26104. ctx.stroke();
  26105. // Diagonal stripe.
  26106. left = rect[0] + x - dx;
  26107. top = y + halfLineWidth;
  26108. ctx.beginPath();
  26109. ctx.moveTo(left, top);
  26110. ctx.lineTo(left + depth, top - depth);
  26111. ctx.lineTo(left + depth, top - depth + height);
  26112. ctx.lineTo(left, top + height);
  26113. ctx.closePath();
  26114. ctx.fill();
  26115. // Diagonal line.
  26116. ctx.beginPath();
  26117. ctx.moveTo(left, top);
  26118. ctx.lineTo(left + depth, top - depth);
  26119. ctx.stroke();
  26120. }
  26121. });
  26122. /**
  26123. * @class Ext.chart.grid.VerticalGrid3D
  26124. * @extends Ext.chart.grid.VerticalGrid
  26125. *
  26126. * Vertical 3D Grid sprite. Used in 3D Cartesian Charts.
  26127. */
  26128. Ext.define('Ext.chart.grid.VerticalGrid3D', {
  26129. extend: 'Ext.chart.grid.VerticalGrid',
  26130. alias: 'grid.vertical3d',
  26131. inheritableStatics: {
  26132. def: {
  26133. processors: {
  26134. depth: 'number'
  26135. },
  26136. defaults: {
  26137. depth: 0
  26138. }
  26139. }
  26140. },
  26141. render: function(surface, ctx, clipRect) {
  26142. var attr = this.attr,
  26143. x = surface.roundPixel(attr.x),
  26144. dy = surface.matrix.getDY(),
  26145. halfLineWidth = ctx.lineWidth * 0.5,
  26146. width = attr.width,
  26147. depth = attr.depth,
  26148. left, top;
  26149. if (x >= clipRect[2]) {
  26150. return;
  26151. }
  26152. // Vertical stripe.
  26153. left = x - halfLineWidth + depth;
  26154. top = clipRect[1] - depth - dy;
  26155. ctx.beginPath();
  26156. ctx.rect(left, top, width, clipRect[3]);
  26157. ctx.fill();
  26158. // Vertical line.
  26159. ctx.beginPath();
  26160. ctx.moveTo(left, top);
  26161. ctx.lineTo(left, top + clipRect[3]);
  26162. ctx.stroke();
  26163. // Diagonal stripe.
  26164. left = x - halfLineWidth;
  26165. top = clipRect[3];
  26166. ctx.beginPath();
  26167. ctx.moveTo(left, top);
  26168. ctx.lineTo(left + depth, top - depth);
  26169. ctx.lineTo(left + depth + width, top - depth);
  26170. ctx.lineTo(left + width, top);
  26171. ctx.closePath();
  26172. ctx.fill();
  26173. // Diagonal line.
  26174. left = x - halfLineWidth;
  26175. top = clipRect[3];
  26176. ctx.beginPath();
  26177. ctx.moveTo(left, top);
  26178. ctx.lineTo(left + depth, top - depth);
  26179. ctx.stroke();
  26180. }
  26181. });
  26182. /**
  26183. * @class Ext.chart.interactions.CrossZoom
  26184. * @extends Ext.chart.interactions.Abstract
  26185. *
  26186. * The CrossZoom interaction allows the user to zoom in on a selected area of the chart.
  26187. *
  26188. * @example
  26189. * Ext.create({
  26190. * xtype: 'cartesian',
  26191. * renderTo: Ext.getBody(),
  26192. * width: 600,
  26193. * height: 400,
  26194. * insetPadding: 40,
  26195. * interactions: 'crosszoom',
  26196. * store: {
  26197. * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
  26198. * data: [{
  26199. * 'name': 'metric one',
  26200. * 'data1': 10,
  26201. * 'data2': 12,
  26202. * 'data3': 14,
  26203. * 'data4': 8,
  26204. * 'data5': 13
  26205. * }, {
  26206. * 'name': 'metric two',
  26207. * 'data1': 7,
  26208. * 'data2': 8,
  26209. * 'data3': 16,
  26210. * 'data4': 10,
  26211. * 'data5': 3
  26212. * }, {
  26213. * 'name': 'metric three',
  26214. * 'data1': 5,
  26215. * 'data2': 2,
  26216. * 'data3': 14,
  26217. * 'data4': 12,
  26218. * 'data5': 7
  26219. * }, {
  26220. * 'name': 'metric four',
  26221. * 'data1': 2,
  26222. * 'data2': 14,
  26223. * 'data3': 6,
  26224. * 'data4': 1,
  26225. * 'data5': 23
  26226. * }, {
  26227. * 'name': 'metric five',
  26228. * 'data1': 27,
  26229. * 'data2': 38,
  26230. * 'data3': 36,
  26231. * 'data4': 13,
  26232. * 'data5': 33
  26233. * }]
  26234. * },
  26235. * axes: [{
  26236. * type: 'numeric',
  26237. * position: 'left',
  26238. * fields: ['data1'],
  26239. * title: {
  26240. * text: 'Sample Values',
  26241. * fontSize: 15
  26242. * },
  26243. * grid: true,
  26244. * minimum: 0
  26245. * }, {
  26246. * type: 'category',
  26247. * position: 'bottom',
  26248. * fields: ['name'],
  26249. * title: {
  26250. * text: 'Sample Values',
  26251. * fontSize: 15
  26252. * }
  26253. * }],
  26254. * series: [{
  26255. * type: 'line',
  26256. * highlight: {
  26257. * size: 7,
  26258. * radius: 7
  26259. * },
  26260. * style: {
  26261. * stroke: 'rgb(143,203,203)'
  26262. * },
  26263. * xField: 'name',
  26264. * yField: 'data1',
  26265. * marker: {
  26266. * type: 'path',
  26267. * path: ['M', - 2, 0, 0, 2, 2, 0, 0, - 2, 'Z'],
  26268. * stroke: 'blue',
  26269. * lineWidth: 0
  26270. * }
  26271. * }, {
  26272. * type: 'line',
  26273. * highlight: {
  26274. * size: 7,
  26275. * radius: 7
  26276. * },
  26277. * fill: true,
  26278. * xField: 'name',
  26279. * yField: 'data3',
  26280. * marker: {
  26281. * type: 'circle',
  26282. * radius: 4,
  26283. * lineWidth: 0
  26284. * }
  26285. * }]
  26286. * });
  26287. */
  26288. Ext.define('Ext.chart.interactions.CrossZoom', {
  26289. extend: 'Ext.chart.interactions.Abstract',
  26290. type: 'crosszoom',
  26291. alias: 'interaction.crosszoom',
  26292. isCrossZoom: true,
  26293. config: {
  26294. /**
  26295. * @cfg {Object/Array} axes
  26296. * Specifies which axes should be made navigable. The config value can take the following
  26297. * formats:
  26298. *
  26299. * - An Object whose keys correspond to the {@link Ext.chart.axis.Axis#position position}
  26300. * of each axis that should be made navigable. Each key's value can either be an Object
  26301. * with further configuration options for each axis or simply `true` for a default
  26302. * set of options.
  26303. * {
  26304. * type: 'crosszoom',
  26305. * axes: {
  26306. * left: {
  26307. * maxZoom: 5,
  26308. * allowPan: false
  26309. * },
  26310. * bottom: true
  26311. * }
  26312. * }
  26313. *
  26314. * If using the full Object form, the following options can be specified for each axis:
  26315. *
  26316. * - minZoom (Number) A minimum zoom level for the axis. Defaults to `1` which is its
  26317. * natural size.
  26318. * - maxZoom (Number) A maximum zoom level for the axis. Defaults to `10`.
  26319. * - startZoom (Number) A starting zoom level for the axis. Defaults to `1`.
  26320. * - allowZoom (Boolean) Whether zooming is allowed for the axis. Defaults to `true`.
  26321. * - allowPan (Boolean) Whether panning is allowed for the axis. Defaults to `true`.
  26322. * - startPan (Boolean) A starting panning offset for the axis. Defaults to `0`.
  26323. *
  26324. * - An Array of strings, each one corresponding to the
  26325. * {@link Ext.chart.axis.Axis#position position} of an axis that should be made navigable.
  26326. * The default options will be used for each named axis.
  26327. *
  26328. * {
  26329. * type: 'crosszoom',
  26330. * axes: ['left', 'bottom']
  26331. * }
  26332. *
  26333. * If the `axes` config is not specified, it will default to making all axes navigable
  26334. * with the default axis options.
  26335. */
  26336. axes: true,
  26337. gestures: {
  26338. dragstart: 'onGestureStart',
  26339. drag: 'onGesture',
  26340. dragend: 'onGestureEnd',
  26341. dblclick: 'onDoubleTap'
  26342. },
  26343. undoButton: {}
  26344. },
  26345. stopAnimationBeforeSync: false,
  26346. zoomAnimationInProgress: false,
  26347. constructor: function() {
  26348. this.callParent(arguments);
  26349. this.zoomHistory = [];
  26350. },
  26351. applyAxes: function(axesConfig) {
  26352. var result = {};
  26353. if (axesConfig === true) {
  26354. return {
  26355. top: {},
  26356. right: {},
  26357. bottom: {},
  26358. left: {}
  26359. };
  26360. } else if (Ext.isArray(axesConfig)) {
  26361. // array of axis names - translate to full object form
  26362. result = {};
  26363. Ext.each(axesConfig, function(axis) {
  26364. result[axis] = {};
  26365. });
  26366. } else if (Ext.isObject(axesConfig)) {
  26367. Ext.iterate(axesConfig, function(key, val) {
  26368. // axis name with `true` value -> translate to object
  26369. if (val === true) {
  26370. result[key] = {};
  26371. } else if (val !== false) {
  26372. result[key] = val;
  26373. }
  26374. });
  26375. }
  26376. return result;
  26377. },
  26378. applyUndoButton: function(button, oldButton) {
  26379. var me = this;
  26380. if (oldButton) {
  26381. oldButton.destroy();
  26382. }
  26383. if (button) {
  26384. return Ext.create('Ext.Button', Ext.apply({
  26385. cls: [],
  26386. text: 'Undo Zoom',
  26387. disabled: true,
  26388. handler: function() {
  26389. me.undoZoom();
  26390. }
  26391. }, button));
  26392. }
  26393. },
  26394. getSurface: function() {
  26395. return this.getChart() && this.getChart().getSurface('overlay');
  26396. },
  26397. setSeriesOpacity: function(opacity) {
  26398. var surface = this.getChart() && this.getChart().getSurface('series');
  26399. if (surface) {
  26400. surface.element.setStyle('opacity', opacity);
  26401. }
  26402. },
  26403. onGestureStart: function(e) {
  26404. var me = this,
  26405. chart = me.getChart(),
  26406. surface = me.getSurface(),
  26407. rect = chart.getInnerRect(),
  26408. innerPadding = chart.getInnerPadding(),
  26409. minX = innerPadding.left,
  26410. maxX = minX + rect[2],
  26411. minY = innerPadding.top,
  26412. maxY = minY + rect[3],
  26413. xy = chart.getEventXY(e),
  26414. x = xy[0],
  26415. y = xy[1];
  26416. e.claimGesture();
  26417. if (me.zoomAnimationInProgress) {
  26418. return;
  26419. }
  26420. if (x > minX && x < maxX && y > minY && y < maxY) {
  26421. me.gestureEvent = 'drag';
  26422. me.lockEvents(me.gestureEvent);
  26423. me.startX = x;
  26424. me.startY = y;
  26425. me.selectionRect = surface.add({
  26426. type: 'rect',
  26427. globalAlpha: 0.5,
  26428. fillStyle: 'rgba(80,80,140,0.5)',
  26429. strokeStyle: 'rgba(80,80,140,1)',
  26430. lineWidth: 2,
  26431. x: x,
  26432. y: y,
  26433. width: 0,
  26434. height: 0,
  26435. zIndex: 10000
  26436. });
  26437. me.setSeriesOpacity(0.8);
  26438. return false;
  26439. }
  26440. },
  26441. onGesture: function(e) {
  26442. var me = this;
  26443. if (me.zoomAnimationInProgress) {
  26444. return;
  26445. }
  26446. if (me.getLocks()[me.gestureEvent] === me) {
  26447. // eslint-disable-next-line vars-on-top, one-var
  26448. var chart = me.getChart(),
  26449. surface = me.getSurface(),
  26450. rect = chart.getInnerRect(),
  26451. innerPadding = chart.getInnerPadding(),
  26452. minX = innerPadding.left,
  26453. maxX = minX + rect[2],
  26454. minY = innerPadding.top,
  26455. maxY = minY + rect[3],
  26456. xy = chart.getEventXY(e),
  26457. x = xy[0],
  26458. y = xy[1];
  26459. if (x < minX) {
  26460. x = minX;
  26461. } else if (x > maxX) {
  26462. x = maxX;
  26463. }
  26464. if (y < minY) {
  26465. y = minY;
  26466. } else if (y > maxY) {
  26467. y = maxY;
  26468. }
  26469. me.selectionRect.setAttributes({
  26470. width: x - me.startX,
  26471. height: y - me.startY
  26472. });
  26473. if (Math.abs(me.startX - x) < 11 || Math.abs(me.startY - y) < 11) {
  26474. me.selectionRect.setAttributes({
  26475. globalAlpha: 0.5
  26476. });
  26477. } else {
  26478. me.selectionRect.setAttributes({
  26479. globalAlpha: 1
  26480. });
  26481. }
  26482. surface.renderFrame();
  26483. return false;
  26484. }
  26485. },
  26486. onGestureEnd: function(e) {
  26487. var me = this;
  26488. if (me.zoomAnimationInProgress) {
  26489. return;
  26490. }
  26491. if (me.getLocks()[me.gestureEvent] === me) {
  26492. // eslint-disable-next-line vars-on-top, one-var
  26493. var chart = me.getChart(),
  26494. surface = me.getSurface(),
  26495. rect = chart.getInnerRect(),
  26496. innerPadding = chart.getInnerPadding(),
  26497. minX = innerPadding.left,
  26498. maxX = minX + rect[2],
  26499. minY = innerPadding.top,
  26500. maxY = minY + rect[3],
  26501. rectWidth = rect[2],
  26502. rectHeight = rect[3],
  26503. xy = chart.getEventXY(e),
  26504. x = xy[0],
  26505. y = xy[1];
  26506. if (x < minX) {
  26507. x = minX;
  26508. } else if (x > maxX) {
  26509. x = maxX;
  26510. }
  26511. if (y < minY) {
  26512. y = minY;
  26513. } else if (y > maxY) {
  26514. y = maxY;
  26515. }
  26516. if (Math.abs(me.startX - x) < 11 || Math.abs(me.startY - y) < 11) {
  26517. surface.remove(me.selectionRect);
  26518. } else {
  26519. me.zoomBy([
  26520. Math.min(me.startX, x) / rectWidth,
  26521. 1 - Math.max(me.startY, y) / rectHeight,
  26522. Math.max(me.startX, x) / rectWidth,
  26523. 1 - Math.min(me.startY, y) / rectHeight
  26524. ]);
  26525. me.selectionRect.setAttributes({
  26526. x: Math.min(me.startX, x),
  26527. y: Math.min(me.startY, y),
  26528. width: Math.abs(me.startX - x),
  26529. height: Math.abs(me.startY - y)
  26530. });
  26531. me.selectionRect.setAnimation(chart.getAnimation() || {
  26532. duration: 0
  26533. });
  26534. me.selectionRect.setAttributes({
  26535. globalAlpha: 0,
  26536. x: 0,
  26537. y: 0,
  26538. width: rectWidth,
  26539. height: rectHeight
  26540. });
  26541. me.zoomAnimationInProgress = true;
  26542. chart.suspendThicknessChanged();
  26543. me.selectionRect.getAnimation().on('animationend', function() {
  26544. chart.resumeThicknessChanged();
  26545. surface.remove(me.selectionRect);
  26546. me.selectionRect = null;
  26547. me.zoomAnimationInProgress = false;
  26548. });
  26549. }
  26550. surface.renderFrame();
  26551. me.sync();
  26552. me.unlockEvents(me.gestureEvent);
  26553. me.setSeriesOpacity(1);
  26554. if (!me.zoomAnimationInProgress) {
  26555. surface.remove(me.selectionRect);
  26556. me.selectionRect = null;
  26557. }
  26558. }
  26559. },
  26560. zoomBy: function(rect) {
  26561. var me = this,
  26562. axisConfigs = me.getAxes(),
  26563. chart = me.getChart(),
  26564. axes = chart.getAxes(),
  26565. isRtl = chart.getInherited().rtl,
  26566. zoomMap = {},
  26567. config, axis, x1, x2, isSide, oldRange, i;
  26568. if (isRtl) {
  26569. rect = rect.slice();
  26570. x1 = 1 - rect[0];
  26571. x2 = 1 - rect[2];
  26572. rect[0] = Math.min(x1, x2);
  26573. rect[2] = Math.max(x1, x2);
  26574. }
  26575. for (i = 0; i < axes.length; i++) {
  26576. axis = axes[i];
  26577. config = axisConfigs[axis.getPosition()];
  26578. if (config && config.allowZoom !== false) {
  26579. isSide = axis.isSide();
  26580. oldRange = axis.getVisibleRange();
  26581. zoomMap[axis.getId()] = oldRange.slice(0);
  26582. if (!isSide) {
  26583. axis.setVisibleRange([
  26584. (oldRange[1] - oldRange[0]) * rect[0] + oldRange[0],
  26585. (oldRange[1] - oldRange[0]) * rect[2] + oldRange[0]
  26586. ]);
  26587. } else {
  26588. axis.setVisibleRange([
  26589. (oldRange[1] - oldRange[0]) * rect[1] + oldRange[0],
  26590. (oldRange[1] - oldRange[0]) * rect[3] + oldRange[0]
  26591. ]);
  26592. }
  26593. }
  26594. }
  26595. me.zoomHistory.push(zoomMap);
  26596. me.getUndoButton().setDisabled(false);
  26597. },
  26598. undoZoom: function() {
  26599. var zoomMap = this.zoomHistory.pop(),
  26600. axes = this.getChart().getAxes(),
  26601. axis, i;
  26602. if (zoomMap) {
  26603. for (i = 0; i < axes.length; i++) {
  26604. axis = axes[i];
  26605. if (zoomMap[axis.getId()]) {
  26606. axis.setVisibleRange(zoomMap[axis.getId()]);
  26607. }
  26608. }
  26609. }
  26610. this.getUndoButton().setDisabled(this.zoomHistory.length === 0);
  26611. this.sync();
  26612. },
  26613. onDoubleTap: function(e) {
  26614. this.undoZoom();
  26615. },
  26616. destroy: function() {
  26617. this.setUndoButton(null);
  26618. this.callParent();
  26619. }
  26620. });
  26621. /**
  26622. * The Crosshair interaction allows the user to get precise values for a specific point
  26623. * on the chart. The values are obtained by single-touch dragging on the chart.
  26624. *
  26625. * @example
  26626. * Ext.create('Ext.Container', {
  26627. * renderTo: Ext.getBody(),
  26628. * width: 600,
  26629. * height: 400,
  26630. * layout: 'fit',
  26631. * items: {
  26632. * xtype: 'cartesian',
  26633. * innerPadding: 20,
  26634. * interactions: {
  26635. * type: 'crosshair',
  26636. * axes: {
  26637. * left: {
  26638. * label: {
  26639. * fillStyle: 'white'
  26640. * },
  26641. * rect: {
  26642. * fillStyle: 'brown',
  26643. * radius: 6
  26644. * }
  26645. * },
  26646. * bottom: {
  26647. * label: {
  26648. * fontSize: '14px',
  26649. * fontWeight: 'bold'
  26650. * }
  26651. * }
  26652. * },
  26653. * lines: {
  26654. * horizontal: {
  26655. * strokeStyle: 'brown',
  26656. * lineWidth: 2,
  26657. * lineDash: [20, 2, 2, 2, 2, 2, 2, 2]
  26658. * }
  26659. * }
  26660. * },
  26661. * store: {
  26662. * fields: ['name', 'data'],
  26663. * data: [
  26664. * {name: 'apple', data: 300},
  26665. * {name: 'orange', data: 900},
  26666. * {name: 'banana', data: 800},
  26667. * {name: 'pear', data: 400},
  26668. * {name: 'grape', data: 500}
  26669. * ]
  26670. * },
  26671. * axes: [{
  26672. * type: 'numeric',
  26673. * position: 'left',
  26674. * fields: ['data'],
  26675. * title: {
  26676. * text: 'Value',
  26677. * fontSize: 15
  26678. * },
  26679. * grid: true,
  26680. * label: {
  26681. * rotationRads: -Math.PI / 4
  26682. * }
  26683. * }, {
  26684. * type: 'category',
  26685. * position: 'bottom',
  26686. * fields: ['name'],
  26687. * title: {
  26688. * text: 'Category',
  26689. * fontSize: 15
  26690. * }
  26691. * }],
  26692. * series: {
  26693. * type: 'line',
  26694. * style: {
  26695. * strokeStyle: 'black'
  26696. * },
  26697. * xField: 'name',
  26698. * yField: 'data',
  26699. * marker: {
  26700. * type: 'circle',
  26701. * radius: 5,
  26702. * fillStyle: 'lightblue'
  26703. * }
  26704. * }
  26705. * }
  26706. * });
  26707. */
  26708. Ext.define('Ext.chart.interactions.Crosshair', {
  26709. extend: 'Ext.chart.interactions.Abstract',
  26710. requires: [
  26711. 'Ext.chart.grid.HorizontalGrid',
  26712. 'Ext.chart.grid.VerticalGrid',
  26713. 'Ext.chart.CartesianChart',
  26714. 'Ext.chart.axis.layout.Discrete'
  26715. ],
  26716. type: 'crosshair',
  26717. alias: 'interaction.crosshair',
  26718. config: {
  26719. /**
  26720. * @cfg {Object} axes
  26721. * Specifies label text and label rect configs on per axis basis or as a single config
  26722. * for all axes.
  26723. *
  26724. * {
  26725. * type: 'crosshair',
  26726. * axes: {
  26727. * label: { fillStyle: 'white' },
  26728. * rect: { fillStyle: 'maroon'}
  26729. * }
  26730. * }
  26731. *
  26732. * In case per axis configuration is used, an object with keys corresponding
  26733. * to the {@link Ext.chart.axis.Axis#position position} must be provided.
  26734. *
  26735. * {
  26736. * type: 'crosshair',
  26737. * axes: {
  26738. * left: {
  26739. * label: { fillStyle: 'white' },
  26740. * rect: {
  26741. * fillStyle: 'maroon',
  26742. * radius: 4
  26743. * }
  26744. * },
  26745. * bottom: {
  26746. * label: {
  26747. * fontSize: '14px',
  26748. * fontWeight: 'bold'
  26749. * },
  26750. * rect: { fillStyle: 'white' }
  26751. * }
  26752. * }
  26753. *
  26754. * If the `axes` config is not specified, the following defaults will be used:
  26755. * - `label` will use values from the {@link Ext.chart.axis.Axis#label label} config.
  26756. * - `rect` will use the 'white' fillStyle.
  26757. */
  26758. axes: {
  26759. top: {
  26760. label: {},
  26761. rect: {}
  26762. },
  26763. right: {
  26764. label: {},
  26765. rect: {}
  26766. },
  26767. bottom: {
  26768. label: {},
  26769. rect: {}
  26770. },
  26771. left: {
  26772. label: {},
  26773. rect: {}
  26774. }
  26775. },
  26776. /**
  26777. * @cfg {Object} lines
  26778. * Specifies attributes of horizontal and vertical lines that make up the crosshair.
  26779. * If this config is missing, black dashed lines will be used.
  26780. *
  26781. * {
  26782. * horizontal: {
  26783. * strokeStyle: 'red',
  26784. * lineDash: [] // solid line
  26785. * },
  26786. * vertical: {
  26787. * lineWidth: 2,
  26788. * lineDash: [15, 5, 5, 5]
  26789. * }
  26790. * }
  26791. */
  26792. lines: {
  26793. horizontal: {
  26794. strokeStyle: 'black',
  26795. lineDash: [
  26796. 5,
  26797. 5
  26798. ]
  26799. },
  26800. vertical: {
  26801. strokeStyle: 'black',
  26802. lineDash: [
  26803. 5,
  26804. 5
  26805. ]
  26806. }
  26807. },
  26808. /**
  26809. * @cfg {String} gesture
  26810. * Specifies which gesture should be used for starting/maintaining/ending the interaction.
  26811. */
  26812. gesture: 'drag'
  26813. },
  26814. applyAxes: function(axesConfig, oldAxesConfig) {
  26815. return Ext.merge(oldAxesConfig || {}, axesConfig);
  26816. },
  26817. applyLines: function(linesConfig, oldLinesConfig) {
  26818. return Ext.merge(oldLinesConfig || {}, linesConfig);
  26819. },
  26820. updateChart: function(chart) {
  26821. if (chart && !chart.isCartesian) {
  26822. Ext.raise("Crosshair interaction can only be used on cartesian charts.");
  26823. }
  26824. this.callParent(arguments);
  26825. },
  26826. getGestures: function() {
  26827. var me = this,
  26828. gestures = {},
  26829. gesture = me.getGesture();
  26830. gestures[gesture] = 'onGesture';
  26831. gestures[gesture + 'start'] = 'onGestureStart';
  26832. gestures[gesture + 'end'] = 'onGestureEnd';
  26833. gestures[gesture + 'cancel'] = 'onGestureCancel';
  26834. return gestures;
  26835. },
  26836. onGestureStart: function(e) {
  26837. var me = this,
  26838. chart = me.getChart(),
  26839. axesTheme = chart.getTheme().getAxis(),
  26840. axisTheme,
  26841. surface = chart.getSurface('overlay'),
  26842. rect = chart.getInnerRect(),
  26843. chartWidth = rect[2],
  26844. chartHeight = rect[3],
  26845. xy = chart.getEventXY(e),
  26846. x = xy[0],
  26847. y = xy[1],
  26848. axes = chart.getAxes(),
  26849. axesConfig = me.getAxes(),
  26850. linesConfig = me.getLines(),
  26851. axis, axisSurface, axisRect, axisWidth, axisHeight, axisPosition, axisAlignment, axisLabel, axisLabelConfig, crosshairLabelConfig, tickPadding, axisSprite, attr, lineWidth, halfLineWidth, title, titleBBox, horizontalLineCfg, verticalLineCfg, i;
  26852. e.claimGesture();
  26853. if (x > 0 && x < chartWidth && y > 0 && y < chartHeight) {
  26854. me.lockEvents(me.getGesture());
  26855. horizontalLineCfg = Ext.apply({
  26856. xclass: 'Ext.chart.grid.HorizontalGrid',
  26857. x: 0,
  26858. y: y,
  26859. width: chartWidth
  26860. }, linesConfig.horizontal);
  26861. verticalLineCfg = Ext.apply({
  26862. xclass: 'Ext.chart.grid.VerticalGrid',
  26863. x: x,
  26864. y: 0,
  26865. height: chartHeight
  26866. }, linesConfig.vertical);
  26867. me.axesLabels = me.axesLabels || {};
  26868. for (i = 0; i < axes.length; i++) {
  26869. axis = axes[i];
  26870. axisSurface = axis.getSurface();
  26871. axisRect = axisSurface.getRect();
  26872. axisSprite = axis.getSprites()[0];
  26873. axisWidth = axisRect[2];
  26874. axisHeight = axisRect[3];
  26875. axisPosition = axis.getPosition();
  26876. axisAlignment = axis.getAlignment();
  26877. title = axis.getTitle();
  26878. titleBBox = title && title.attr.text !== '' && title.getBBox();
  26879. attr = axisSprite.attr;
  26880. lineWidth = attr.axisLine ? attr.lineWidth : 0;
  26881. halfLineWidth = lineWidth / 2;
  26882. tickPadding = Math.max(attr.majorTickSize, attr.minorTickSize) + lineWidth;
  26883. axisLabel = me.axesLabels[axisPosition] = axisSurface.add({
  26884. type: 'composite'
  26885. });
  26886. axisLabel.labelRect = axisLabel.addSprite(Ext.apply({
  26887. type: 'rect',
  26888. fillStyle: 'white',
  26889. x: axisPosition === 'right' ? lineWidth : 0,
  26890. y: axisPosition === 'bottom' ? lineWidth : 0,
  26891. width: axisWidth - lineWidth - (axisAlignment === 'vertical' && titleBBox ? titleBBox.width : 0),
  26892. height: axisHeight - lineWidth - (axisAlignment === 'horizontal' && titleBBox ? titleBBox.height : 0),
  26893. translationX: axisPosition === 'left' && titleBBox ? titleBBox.width : 0,
  26894. translationY: axisPosition === 'top' && titleBBox ? titleBBox.height : 0
  26895. }, axesConfig.rect || axesConfig[axisPosition].rect));
  26896. if (axisAlignment === 'vertical' && !verticalLineCfg.strokeStyle) {
  26897. verticalLineCfg.strokeStyle = attr.strokeStyle;
  26898. }
  26899. if (axisAlignment === 'horizontal' && !horizontalLineCfg.strokeStyle) {
  26900. horizontalLineCfg.strokeStyle = attr.strokeStyle;
  26901. }
  26902. axisTheme = Ext.merge({}, axesTheme.defaults, axesTheme[axisPosition]);
  26903. axisLabelConfig = Ext.apply({}, axis.config.label, axisTheme.label);
  26904. crosshairLabelConfig = axesConfig.label || axesConfig[axisPosition].label;
  26905. axisLabel.labelText = axisLabel.addSprite(Ext.apply(axisLabelConfig, crosshairLabelConfig, {
  26906. type: 'text',
  26907. x: me.calculateLabelTextPoint(false, axisPosition, tickPadding, titleBBox, axisWidth, halfLineWidth),
  26908. y: me.calculateLabelTextPoint(true, axisPosition, tickPadding, titleBBox, axisHeight, halfLineWidth)
  26909. }));
  26910. }
  26911. me.horizontalLine = surface.add(horizontalLineCfg);
  26912. me.verticalLine = surface.add(verticalLineCfg);
  26913. return false;
  26914. }
  26915. },
  26916. onGesture: function(e) {
  26917. var me = this;
  26918. if (me.getLocks()[me.getGesture()] !== me) {
  26919. return;
  26920. }
  26921. // eslint-disable-next-line vars-on-top, one-var
  26922. var chart = me.getChart(),
  26923. surface = chart.getSurface('overlay'),
  26924. rect = Ext.Array.slice(chart.getInnerRect()),
  26925. padding = chart.getInnerPadding(),
  26926. px = padding.left,
  26927. py = padding.top,
  26928. chartWidth = rect[2],
  26929. chartHeight = rect[3],
  26930. xy = chart.getEventXY(e),
  26931. x = xy[0],
  26932. y = xy[1],
  26933. axes = chart.getAxes(),
  26934. axis, axisPosition, axisAlignment, axisSurface, axisSprite, axisMatrix, axisLayoutContext, axisSegmenter, axisLabel, labelBBox, textPadding, xx, yy, dx, dy, xValue, yValue, text, i;
  26935. if (x < 0) {
  26936. x = 0;
  26937. } else if (x > chartWidth) {
  26938. x = chartWidth;
  26939. }
  26940. if (y < 0) {
  26941. y = 0;
  26942. } else if (y > chartHeight) {
  26943. y = chartHeight;
  26944. }
  26945. x += px;
  26946. y += py;
  26947. for (i = 0; i < axes.length; i++) {
  26948. axis = axes[i];
  26949. axisPosition = axis.getPosition();
  26950. axisAlignment = axis.getAlignment();
  26951. axisSurface = axis.getSurface();
  26952. axisSprite = axis.getSprites()[0];
  26953. axisMatrix = axisSprite.attr.matrix;
  26954. textPadding = axisSprite.attr.textPadding * 2;
  26955. axisLabel = me.axesLabels[axisPosition];
  26956. axisLayoutContext = axisSprite.getLayoutContext();
  26957. axisSegmenter = axis.getSegmenter();
  26958. if (axisLabel) {
  26959. if (axisAlignment === 'vertical') {
  26960. yy = axisMatrix.getYY();
  26961. dy = axisMatrix.getDY();
  26962. yValue = (y - dy - py) / yy;
  26963. if (axis.getLayout() instanceof Ext.chart.axis.layout.Discrete) {
  26964. y = Math.round(yValue) * yy + dy + py;
  26965. yValue = axisSegmenter.from(Math.round(yValue));
  26966. yValue = axisSprite.attr.data[yValue];
  26967. } else {
  26968. yValue = axisSegmenter.from(yValue);
  26969. }
  26970. text = axisSegmenter.renderer(yValue, axisLayoutContext);
  26971. axisLabel.setAttributes({
  26972. translationY: y - py
  26973. });
  26974. axisLabel.labelText.setAttributes({
  26975. text: text
  26976. });
  26977. labelBBox = axisLabel.labelText.getBBox();
  26978. axisLabel.labelRect.setAttributes({
  26979. height: labelBBox.height + textPadding,
  26980. y: -(labelBBox.height + textPadding) / 2
  26981. });
  26982. axisSurface.renderFrame();
  26983. } else {
  26984. xx = axisMatrix.getXX();
  26985. dx = axisMatrix.getDX();
  26986. xValue = (x - dx - px) / xx;
  26987. if (axis.getLayout() instanceof Ext.chart.axis.layout.Discrete) {
  26988. x = Math.round(xValue) * xx + dx + px;
  26989. xValue = axisSegmenter.from(Math.round(xValue));
  26990. xValue = axisSprite.attr.data[xValue];
  26991. } else {
  26992. xValue = axisSegmenter.from(xValue);
  26993. }
  26994. text = axisSegmenter.renderer(xValue, axisLayoutContext);
  26995. axisLabel.setAttributes({
  26996. translationX: x - px
  26997. });
  26998. axisLabel.labelText.setAttributes({
  26999. text: text
  27000. });
  27001. labelBBox = axisLabel.labelText.getBBox();
  27002. axisLabel.labelRect.setAttributes({
  27003. width: labelBBox.width + textPadding,
  27004. x: -(labelBBox.width + textPadding) / 2
  27005. });
  27006. axisSurface.renderFrame();
  27007. }
  27008. }
  27009. }
  27010. me.horizontalLine.setAttributes({
  27011. y: y,
  27012. strokeStyle: axisSprite.attr.strokeStyle
  27013. });
  27014. me.verticalLine.setAttributes({
  27015. x: x,
  27016. strokeStyle: axisSprite.attr.strokeStyle
  27017. });
  27018. surface.renderFrame();
  27019. return false;
  27020. },
  27021. onGestureEnd: function(e) {
  27022. var me = this,
  27023. chart = me.getChart(),
  27024. surface = chart.getSurface('overlay'),
  27025. axes = chart.getAxes(),
  27026. axis, axisPosition, axisSurface, axisLabel, i;
  27027. surface.remove(me.verticalLine);
  27028. surface.remove(me.horizontalLine);
  27029. for (i = 0; i < axes.length; i++) {
  27030. axis = axes[i];
  27031. axisPosition = axis.getPosition();
  27032. axisSurface = axis.getSurface();
  27033. axisLabel = me.axesLabels[axisPosition];
  27034. if (axisLabel) {
  27035. delete me.axesLabels[axisPosition];
  27036. axisSurface.remove(axisLabel);
  27037. }
  27038. axisSurface.renderFrame();
  27039. }
  27040. surface.renderFrame();
  27041. me.unlockEvents(me.getGesture());
  27042. },
  27043. onGestureCancel: function(e) {
  27044. this.onGestureEnd(e);
  27045. },
  27046. privates: {
  27047. vertMap: {
  27048. top: 'start',
  27049. bottom: 'end'
  27050. },
  27051. horzMap: {
  27052. left: 'start',
  27053. right: 'end'
  27054. },
  27055. calculateLabelTextPoint: function(vertical, position, tickPadding, titleBBox, axisSize, halfLineWidth) {
  27056. var titlePadding, sizeProp, pointProp;
  27057. if (vertical) {
  27058. pointProp = 'y';
  27059. sizeProp = 'height';
  27060. position = this.vertMap[position];
  27061. } else {
  27062. pointProp = 'x';
  27063. sizeProp = 'width';
  27064. position = this.horzMap[position];
  27065. }
  27066. switch (position) {
  27067. case 'start':
  27068. titlePadding = titleBBox ? titleBBox[pointProp] + titleBBox[sizeProp] : 0;
  27069. return titlePadding + (axisSize - titlePadding - tickPadding) / 2 - halfLineWidth;
  27070. case 'end':
  27071. titlePadding = titleBBox ? axisSize - titleBBox[pointProp] : 0;
  27072. return tickPadding + (axisSize - tickPadding - titlePadding) / 2 + halfLineWidth;
  27073. default:
  27074. return 0;
  27075. }
  27076. }
  27077. }
  27078. });
  27079. /**
  27080. * @class Ext.chart.interactions.ItemHighlight
  27081. * @extends Ext.chart.interactions.Abstract
  27082. *
  27083. * The 'itemhighlight' interaction allows the user to highlight series items in the chart.
  27084. */
  27085. Ext.define('Ext.chart.interactions.ItemHighlight', {
  27086. extend: 'Ext.chart.interactions.Abstract',
  27087. type: 'itemhighlight',
  27088. alias: 'interaction.itemhighlight',
  27089. isItemHighlight: true,
  27090. config: {
  27091. gestures: {
  27092. tap: 'onTapGesture',
  27093. mousemove: 'onMouseMoveGesture',
  27094. mousedown: 'onMouseDownGesture',
  27095. mouseup: 'onMouseUpGesture',
  27096. mouseleave: 'onMouseUpGesture'
  27097. },
  27098. /**
  27099. * @cfg {Boolean} [sticky=false]
  27100. * Disables mouse tracking.
  27101. * Series items will only be highlighted/unhighlighted on mouse click.
  27102. * This config has no effect on touch devices.
  27103. */
  27104. sticky: false,
  27105. /**
  27106. * @cfg {Boolean} [multiTooltips=false]
  27107. * Enable displaying multiple tooltips for overlapping or adjacent series items within
  27108. * {@link Ext.chart.series.Line#selectionTolerance} radius.
  27109. * Default is to display a tooltip only for the last series item rendered.
  27110. * When multiple tooltips are displayed, they may overlap partially or completely;
  27111. * it is up to the developer to ensure tooltip positioning is satisfactory.
  27112. *
  27113. * @since 6.6.0
  27114. */
  27115. multiTooltips: false
  27116. },
  27117. constructor: function(config) {
  27118. this.callParent([
  27119. config
  27120. ]);
  27121. this.stickyHighlightItem = null;
  27122. this.tooltipItems = [];
  27123. },
  27124. destroy: function() {
  27125. this.stickyHighlightItem = this.tooltipItems = null;
  27126. this.callParent();
  27127. },
  27128. onMouseMoveGesture: function(e) {
  27129. var me = this,
  27130. tooltipItems = me.tooltipItems,
  27131. isMousePointer = e.pointerType === 'mouse',
  27132. tooltips = [],
  27133. item, oldItem, items, tooltip, oldTooltip, i, len, j, jLen;
  27134. if (me.getSticky()) {
  27135. return true;
  27136. }
  27137. if (isMousePointer && me.stickyHighlightItem) {
  27138. me.stickyHighlightItem = null;
  27139. me.highlight(null);
  27140. }
  27141. if (me.isDragging) {
  27142. if (tooltipItems.length && isMousePointer) {
  27143. me.hideTooltips(tooltipItems);
  27144. tooltipItems.length = 0;
  27145. }
  27146. } else if (!me.stickyHighlightItem) {
  27147. if (me.getMultiTooltips()) {
  27148. items = me.getItemsForEvent(e);
  27149. } else {
  27150. item = me.getItemForEvent(e);
  27151. items = item ? [
  27152. item
  27153. ] : [];
  27154. }
  27155. for (i = 0 , len = items.length; i < len; i++) {
  27156. item = items[i];
  27157. // Items are returned top to down, so first item is the top one.
  27158. // Chart can only have one highlighted item.
  27159. if (i === 0 && item !== me.getChart().getHighlightItem()) {
  27160. me.highlight(item);
  27161. me.sync();
  27162. }
  27163. tooltip = item.series.getTooltip();
  27164. if (tooltip) {
  27165. tooltips.push(tooltip);
  27166. }
  27167. }
  27168. if (isMousePointer) {
  27169. // If we detected a mouse hit, show/refresh the tooltip
  27170. if (items.length) {
  27171. for (i = 0 , len = items.length; i < len; i++) {
  27172. item = items[i];
  27173. tooltip = item.series.getTooltip();
  27174. if (tooltip) {
  27175. // If there were different previously active items
  27176. // that are not going to be included in current active items,
  27177. // ask them to hide their tooltips. Unless those are
  27178. // the same tooltip instances that we are about to show,
  27179. // in which case we are just going to reposition them.
  27180. for (j = 0 , jLen = tooltipItems.length; j < jLen; j++) {
  27181. oldItem = tooltipItems[j];
  27182. if (!Ext.Array.contains(items, oldItem)) {
  27183. oldTooltip = oldItem.series.getTooltip();
  27184. if (!Ext.Array.contains(tooltips, oldTooltip)) {
  27185. oldItem.series.hideTooltip(oldItem, true);
  27186. }
  27187. }
  27188. }
  27189. if (tooltip.getTrackMouse()) {
  27190. item.series.showTooltip(item, e);
  27191. } else {
  27192. me.showUntracked(item);
  27193. }
  27194. }
  27195. }
  27196. me.tooltipItems = items;
  27197. } else // No mouse hit - schedule a hide for hideDelay ms.
  27198. // If pointer enters another item within that time,
  27199. // there will be no flickery reshow.
  27200. {
  27201. me.hideTooltips(tooltipItems);
  27202. tooltipItems.length = 0;
  27203. }
  27204. }
  27205. return false;
  27206. }
  27207. },
  27208. highlight: function(item) {
  27209. // This is its own function to make it easier for subclasses
  27210. // to enhance the behavior. An alternative would be to listen
  27211. // for the chart's 'itemhighlight' event.
  27212. this.getChart().setHighlightItem(item);
  27213. },
  27214. showTooltip: function(e, item) {
  27215. item.series.showTooltip(item, e);
  27216. Ext.Array.include(this.tooltipItems, item);
  27217. },
  27218. showUntracked: function(item) {
  27219. var marker = item.sprite.getMarker(item.category),
  27220. surface, surfaceXY, isInverseY, itemBBox, matrix;
  27221. if (marker) {
  27222. surface = marker.getSurface();
  27223. isInverseY = surface.matrix.elements[3] < 0;
  27224. surfaceXY = surface.element.getXY();
  27225. itemBBox = Ext.clone(marker.getBBoxFor(item.index));
  27226. if (isInverseY) {
  27227. // The item.category for bar series will be 'items'.
  27228. // The item.category for line series will be 'markers'.
  27229. // 'items' are in the 'series' surface, which is flipped vertically
  27230. // for cartesian series.
  27231. // 'markers' are in the 'overlay' surface, which isn't flipped.
  27232. // So for 'markers' we already have the bbox in a coordinate system
  27233. // with the origin at the top-left of the surface, but for 'items'
  27234. // we need to do a conversion.
  27235. if (surface.getInherited().rtl) {
  27236. matrix = surface.inverseMatrix.clone().flipX().translate(item.sprite.attr.innerWidth, 0, true);
  27237. } else {
  27238. matrix = surface.inverseMatrix;
  27239. }
  27240. itemBBox = matrix.transformBBox(itemBBox);
  27241. }
  27242. itemBBox.x += surfaceXY[0];
  27243. itemBBox.y += surfaceXY[1];
  27244. item.series.showTooltipAt(item, itemBBox.x + itemBBox.width * 0.5, itemBBox.y + itemBBox.height * 0.5);
  27245. }
  27246. },
  27247. onMouseDownGesture: function() {
  27248. this.isDragging = true;
  27249. },
  27250. onMouseUpGesture: function() {
  27251. this.isDragging = false;
  27252. },
  27253. isSameItem: function(a, b) {
  27254. return a && b && a.series === b.series && a.field === b.field && a.index === b.index;
  27255. },
  27256. onTapGesture: function(e) {
  27257. var me = this,
  27258. item;
  27259. // A click/tap on an item makes its highlight sticky.
  27260. // It requires another click/tap to unhighlight.
  27261. if (e.pointerType === 'mouse' && !me.getSticky()) {
  27262. return;
  27263. }
  27264. item = me.getItemForEvent(e);
  27265. if (me.isSameItem(me.stickyHighlightItem, item)) {
  27266. item = null;
  27267. }
  27268. // toggle
  27269. me.stickyHighlightItem = item;
  27270. me.highlight(item);
  27271. },
  27272. privates: {
  27273. hideTooltips: function(items, force) {
  27274. var item, i, len;
  27275. items = Ext.isArray(items) ? items : [
  27276. items
  27277. ];
  27278. for (i = 0 , len = items.length; i < len; i++) {
  27279. item = items[i];
  27280. if (item && item.series && !item.series.destroyed) {
  27281. item.series.hideTooltip(item, force);
  27282. }
  27283. }
  27284. }
  27285. }
  27286. });
  27287. /**
  27288. * @class Ext.chart.interactions.ItemEdit
  27289. * @extends Ext.chart.interactions.ItemHighlight
  27290. *
  27291. * The 'itemedit' interaction allows the user to edit store data
  27292. * by dragging series items in the chart.
  27293. *
  27294. * The 'itemedit' interaction extends the
  27295. * {@link Ext.chart.interactions.ItemHighlight 'itemhighlight'} interaction,
  27296. * so it also acts like one. If you need both interactions in a single chart,
  27297. * 'itemedit' should be sufficient. Hovering/tapping will result in highlighting,
  27298. * and dragging will result in editing.
  27299. */
  27300. Ext.define('Ext.chart.interactions.ItemEdit', {
  27301. extend: 'Ext.chart.interactions.ItemHighlight',
  27302. requires: [
  27303. 'Ext.tip.ToolTip'
  27304. ],
  27305. type: 'itemedit',
  27306. alias: 'interaction.itemedit',
  27307. isItemEdit: true,
  27308. config: {
  27309. /**
  27310. * @cfg {Object} [style=null]
  27311. * The style that will be applied to the series item on dragging.
  27312. * By default, series item will have no fill,
  27313. * and will have a dashed stroke of the same color.
  27314. */
  27315. style: null,
  27316. /**
  27317. * @cfg {Function/String} [renderer=null]
  27318. * A function that returns style attributes for the item that's being dragged.
  27319. * This is useful if you want to give a visual feedback to the user when
  27320. * they dragged to a certain point.
  27321. *
  27322. * @param {Object} [data] The following properties are available:
  27323. *
  27324. * @param {Object} data.target The object containing the xField/xValue or/and
  27325. * yField/yValue properties, where the xField/yField specify the store records
  27326. * being edited and the xValue/yValue the target values to be set when
  27327. * the interaction ends. The object also contains the 'index' of the record
  27328. * being edited.
  27329. * @param {Object} data.style The style that is going to be used for the dragged item.
  27330. * The attributes returned by the renderer will be applied on top of this style.
  27331. * @param {Object} data.item The series item being dragged.
  27332. * This is actually the {@link Ext.chart.AbstractChart#highlightItem}.
  27333. *
  27334. * @return {Object} The style attributes to be set on the dragged item.
  27335. */
  27336. renderer: null,
  27337. /**
  27338. * @cfg {Object/Boolean} [tooltip=true]
  27339. */
  27340. tooltip: true,
  27341. gestures: {
  27342. dragstart: 'onDragStart',
  27343. drag: 'onDrag',
  27344. dragend: 'onDragEnd'
  27345. },
  27346. cursors: {
  27347. ewResize: 'ew-resize',
  27348. nsResize: 'ns-resize',
  27349. move: 'move'
  27350. }
  27351. },
  27352. /**
  27353. * @private
  27354. * @cfg {Boolean} [sticky=false]
  27355. */
  27356. /**
  27357. * @event beginitemedit
  27358. * Fires when item edit operation (dragging) begins.
  27359. * @param {Ext.chart.AbstractChart} chart The chart the interaction belongs to.
  27360. * @param {Ext.chart.interactions.ItemEdit} interaction The interaction.
  27361. * @param {Object} item The item that is about to be edited.
  27362. */
  27363. /**
  27364. * @event enditemedit
  27365. * Fires when item edit operation (dragging) ends.
  27366. * @param {Ext.chart.AbstractChart} chart The chart the interaction belongs to.
  27367. * @param {Ext.chart.interactions.ItemEdit} interaction The interaction.
  27368. * @param {Object} item The item that was edited.
  27369. * @param {Object} target The object containing target values the were used.
  27370. */
  27371. item: null,
  27372. // Item being edited.
  27373. applyTooltip: function(tooltip) {
  27374. var config;
  27375. if (tooltip) {
  27376. config = Ext.apply({}, tooltip, {
  27377. renderer: this.defaultTooltipRenderer,
  27378. constrainPosition: true,
  27379. shrinkWrapDock: true,
  27380. autoHide: true,
  27381. trackMouse: true,
  27382. mouseOffset: [
  27383. 20,
  27384. 20
  27385. ]
  27386. });
  27387. tooltip = new Ext.tip.ToolTip(config);
  27388. }
  27389. return tooltip;
  27390. },
  27391. defaultTooltipRenderer: function(tooltip, item, target, e) {
  27392. var parts = [];
  27393. if (target.xField) {
  27394. parts.push(target.xField + ': ' + target.xValue);
  27395. }
  27396. if (target.yField) {
  27397. parts.push(target.yField + ': ' + target.yValue);
  27398. }
  27399. tooltip.setHtml(parts.join('<br>'));
  27400. },
  27401. onDragStart: function(e) {
  27402. var me = this,
  27403. chart = me.getChart(),
  27404. item = chart.getHighlightItem();
  27405. e.claimGesture();
  27406. if (item) {
  27407. chart.fireEvent('beginitemedit', chart, me, me.item = item);
  27408. // If ItemEdit interaction comes before other interactions
  27409. // in the chart's 'interactions' config, this will
  27410. // prevent other interactions hijacking the 'dragstart'
  27411. // event. We only stop event propagation is there's
  27412. // an item to edit under cursor/finger, otherwise we
  27413. // let other interactions (e.g. 'panzoom') handle the event.
  27414. return false;
  27415. }
  27416. },
  27417. onDrag: function(e) {
  27418. var me = this,
  27419. chart = me.getChart(),
  27420. item = chart.getHighlightItem(),
  27421. type = item && item.sprite.type;
  27422. if (item) {
  27423. switch (type) {
  27424. case 'barSeries':
  27425. return me.onDragBar(e);
  27426. case 'scatterSeries':
  27427. return me.onDragScatter(e);
  27428. }
  27429. }
  27430. },
  27431. highlight: function(item) {
  27432. var me = this,
  27433. chart = me.getChart(),
  27434. flipXY = chart.getFlipXY(),
  27435. cursors = me.getCursors(),
  27436. type = item && item.sprite.type,
  27437. style = chart.el.dom.style;
  27438. me.callParent([
  27439. item
  27440. ]);
  27441. if (item) {
  27442. switch (type) {
  27443. case 'barSeries':
  27444. if (flipXY) {
  27445. style.cursor = cursors.ewResize;
  27446. } else {
  27447. style.cursor = cursors.nsResize;
  27448. };
  27449. break;
  27450. case 'scatterSeries':
  27451. style.cursor = cursors.move;
  27452. break;
  27453. }
  27454. } else {
  27455. chart.el.dom.style.cursor = 'default';
  27456. }
  27457. },
  27458. onDragBar: function(e) {
  27459. var me = this,
  27460. chart = me.getChart(),
  27461. isRtl = chart.getInherited().rtl,
  27462. flipXY = chart.isCartesian && chart.getFlipXY(),
  27463. item = chart.getHighlightItem(),
  27464. marker = item.sprite.getMarker('items'),
  27465. instance = marker.getMarkerFor(item.sprite.getId(), item.index),
  27466. surface = item.sprite.getSurface(),
  27467. surfaceRect = surface.getRect(),
  27468. xy = surface.getEventXY(e),
  27469. matrix = item.sprite.attr.matrix,
  27470. renderer = me.getRenderer(),
  27471. style, changes, params, positionY;
  27472. if (flipXY) {
  27473. positionY = isRtl ? surfaceRect[2] - xy[0] : xy[0];
  27474. } else {
  27475. positionY = surfaceRect[3] - xy[1];
  27476. }
  27477. style = {
  27478. x: instance.x,
  27479. y: positionY,
  27480. width: instance.width,
  27481. height: instance.height + (instance.y - positionY),
  27482. radius: instance.radius,
  27483. fillStyle: 'none',
  27484. lineDash: [
  27485. 4,
  27486. 4
  27487. ],
  27488. zIndex: 100
  27489. };
  27490. Ext.apply(style, me.getStyle());
  27491. if (Ext.isArray(item.series.getYField())) {
  27492. // stacked bars
  27493. positionY = positionY - instance.y - instance.height;
  27494. }
  27495. me.target = {
  27496. index: item.index,
  27497. yField: item.field,
  27498. yValue: (positionY - matrix.getDY()) / matrix.getYY()
  27499. };
  27500. params = [
  27501. chart,
  27502. {
  27503. target: me.target,
  27504. style: style,
  27505. item: item
  27506. }
  27507. ];
  27508. changes = Ext.callback(renderer, null, params, 0, chart);
  27509. if (changes) {
  27510. Ext.apply(style, changes);
  27511. }
  27512. // The interaction works by putting another series item instance
  27513. // under 'itemedit' ID with a slightly different style (default) or
  27514. // whatever style the user provided.
  27515. item.sprite.putMarker('items', style, 'itemedit');
  27516. me.showTooltip(e, me.target, item);
  27517. surface.renderFrame();
  27518. },
  27519. onDragScatter: function(e) {
  27520. var me = this,
  27521. chart = me.getChart(),
  27522. isRtl = chart.getInherited().rtl,
  27523. flipXY = chart.isCartesian && chart.getFlipXY(),
  27524. item = chart.getHighlightItem(),
  27525. marker = item.sprite.getMarker('markers'),
  27526. instance = marker.getMarkerFor(item.sprite.getId(), item.index),
  27527. surface = item.sprite.getSurface(),
  27528. surfaceRect = surface.getRect(),
  27529. xy = surface.getEventXY(e),
  27530. matrix = item.sprite.attr.matrix,
  27531. xAxis = item.series.getXAxis(),
  27532. isEditableX = xAxis && xAxis.getLayout().isContinuous,
  27533. renderer = me.getRenderer(),
  27534. style, changes, params, positionX, positionY, hintX, hintY;
  27535. if (flipXY) {
  27536. positionY = isRtl ? surfaceRect[2] - xy[0] : xy[0];
  27537. } else {
  27538. positionY = surfaceRect[3] - xy[1];
  27539. }
  27540. if (isEditableX) {
  27541. if (flipXY) {
  27542. positionX = surfaceRect[3] - xy[1];
  27543. } else {
  27544. positionX = xy[0];
  27545. }
  27546. } else {
  27547. positionX = instance.translationX;
  27548. }
  27549. if (isEditableX) {
  27550. hintX = xy[0];
  27551. hintY = xy[1];
  27552. } else {
  27553. if (flipXY) {
  27554. hintX = xy[0];
  27555. hintY = instance.translationY;
  27556. } else // no change
  27557. {
  27558. hintX = instance.translationX;
  27559. hintY = xy[1];
  27560. }
  27561. }
  27562. // no change
  27563. style = {
  27564. translationX: hintX,
  27565. translationY: hintY,
  27566. scalingX: instance.scalingX,
  27567. scalingY: instance.scalingY,
  27568. r: instance.r,
  27569. fillStyle: 'none',
  27570. lineDash: [
  27571. 4,
  27572. 4
  27573. ],
  27574. zIndex: 100
  27575. };
  27576. Ext.apply(style, me.getStyle());
  27577. me.target = {
  27578. index: item.index,
  27579. yField: item.field,
  27580. yValue: (positionY - matrix.getDY()) / matrix.getYY()
  27581. };
  27582. if (isEditableX) {
  27583. Ext.apply(me.target, {
  27584. xField: item.series.getXField(),
  27585. xValue: (positionX - matrix.getDX()) / matrix.getXX()
  27586. });
  27587. }
  27588. params = [
  27589. chart,
  27590. {
  27591. target: me.target,
  27592. style: style,
  27593. item: item
  27594. }
  27595. ];
  27596. changes = Ext.callback(renderer, null, params, 0, chart);
  27597. if (changes) {
  27598. Ext.apply(style, changes);
  27599. }
  27600. // This marker acts as a visual hint while dragging.
  27601. item.sprite.putMarker('markers', style, 'itemedit');
  27602. me.showTooltip(e, me.target, item);
  27603. surface.renderFrame();
  27604. },
  27605. showTooltip: function(e, target, item) {
  27606. var tooltip = this.getTooltip(),
  27607. config, chart;
  27608. if (tooltip && Ext.toolkit !== 'modern') {
  27609. config = tooltip.config;
  27610. chart = this.getChart();
  27611. Ext.callback(config.renderer, null, [
  27612. tooltip,
  27613. item,
  27614. target,
  27615. e
  27616. ], 0, chart);
  27617. // If trackMouse is set, a ToolTip shows by its pointerEvent
  27618. tooltip.pointerEvent = e;
  27619. if (tooltip.isVisible()) {
  27620. // After show handling repositions according
  27621. // to configuration. trackMouse uses the pointerEvent
  27622. // If aligning to an element, it uses a currentTarget
  27623. // flyweight which may be attached to any DOM element.
  27624. tooltip.realignToTarget();
  27625. } else {
  27626. tooltip.show();
  27627. }
  27628. }
  27629. },
  27630. hideTooltip: function() {
  27631. var tooltip = this.getTooltip();
  27632. if (tooltip && Ext.toolkit !== 'modern') {
  27633. tooltip.hide();
  27634. }
  27635. },
  27636. onDragEnd: function(e) {
  27637. var me = this,
  27638. target = me.target,
  27639. chart = me.getChart(),
  27640. store = chart.getStore(),
  27641. record;
  27642. if (target) {
  27643. record = store.getAt(target.index);
  27644. if (target.yField) {
  27645. record.set(target.yField, target.yValue, {
  27646. convert: false
  27647. });
  27648. }
  27649. if (target.xField) {
  27650. record.set(target.xField, target.xValue, {
  27651. convert: false
  27652. });
  27653. }
  27654. if (target.yField || target.xField) {
  27655. me.getChart().onDataChanged();
  27656. }
  27657. me.target = null;
  27658. }
  27659. me.hideTooltip();
  27660. if (me.item) {
  27661. chart.fireEvent('enditemedit', chart, me, me.item, target);
  27662. }
  27663. me.highlight(me.item = null);
  27664. },
  27665. destroy: function() {
  27666. // Peek at the config, so we don't create one just to destroy it,
  27667. // if a user has set 'tooltip' config to 'false'.
  27668. var tooltip = this.getConfig('tooltip', true);
  27669. Ext.destroy(tooltip);
  27670. this.callParent();
  27671. }
  27672. });
  27673. /**
  27674. * The PanZoom interaction allows the user to navigate the data for one or more chart
  27675. * axes by panning and/or zooming. Navigation can be limited to particular axes. Zooming is
  27676. * performed by pinching on the chart or axis area; panning is performed by single-touch dragging.
  27677. * The interaction only works with cartesian charts/series.
  27678. *
  27679. * For devices which do not support multiple-touch events, zooming can not be done via pinch
  27680. * gestures; in this case the interaction will allow the user to perform both zooming and panning
  27681. * using the same single-touch drag gesture.
  27682. * {@link #modeToggleButton} provides a button to indicate and toggle between two modes.
  27683. *
  27684. * @example
  27685. * Ext.create({
  27686. * renderTo: document.body,
  27687. * xtype: 'cartesian',
  27688. * width: 600,
  27689. * height: 400,
  27690. * insetPadding: 40,
  27691. * interactions: [{
  27692. * type: 'panzoom',
  27693. * zoomOnPan: true
  27694. * }],
  27695. * store: {
  27696. * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
  27697. * data: [{
  27698. * 'name': 'metric one',
  27699. * 'data1': 10,
  27700. * 'data2': 12,
  27701. * 'data3': 14,
  27702. * 'data4': 8,
  27703. * 'data5': 13
  27704. * }, {
  27705. * 'name': 'metric two',
  27706. * 'data1': 7,
  27707. * 'data2': 8,
  27708. * 'data3': 16,
  27709. * 'data4': 10,
  27710. * 'data5': 3
  27711. * }, {
  27712. * 'name': 'metric three',
  27713. * 'data1': 5,
  27714. * 'data2': 2,
  27715. * 'data3': 14,
  27716. * 'data4': 12,
  27717. * 'data5': 7
  27718. * }, {
  27719. * 'name': 'metric four',
  27720. * 'data1': 2,
  27721. * 'data2': 14,
  27722. * 'data3': 6,
  27723. * 'data4': 1,
  27724. * 'data5': 23
  27725. * }, {
  27726. * 'name': 'metric five',
  27727. * 'data1': 27,
  27728. * 'data2': 38,
  27729. * 'data3': 36,
  27730. * 'data4': 13,
  27731. * 'data5': 33
  27732. * }]
  27733. * },
  27734. * axes: [{
  27735. * type: 'numeric',
  27736. * position: 'left',
  27737. * fields: ['data1'],
  27738. * title: {
  27739. * text: 'Sample Values',
  27740. * fontSize: 15
  27741. * },
  27742. * grid: true,
  27743. * minimum: 0
  27744. * }, {
  27745. * type: 'category',
  27746. * position: 'bottom',
  27747. * fields: ['name'],
  27748. * title: {
  27749. * text: 'Sample Values',
  27750. * fontSize: 15
  27751. * }
  27752. * }],
  27753. * series: [{
  27754. * type: 'line',
  27755. * highlight: {
  27756. * size: 7,
  27757. * radius: 7
  27758. * },
  27759. * style: {
  27760. * stroke: 'rgb(143,203,203)'
  27761. * },
  27762. * xField: 'name',
  27763. * yField: 'data1',
  27764. * marker: {
  27765. * type: 'path',
  27766. * path: ['M', - 2, 0, 0, 2, 2, 0, 0, - 2, 'Z'],
  27767. * stroke: 'blue',
  27768. * lineWidth: 0
  27769. * }
  27770. * }, {
  27771. * type: 'line',
  27772. * highlight: {
  27773. * size: 7,
  27774. * radius: 7
  27775. * },
  27776. * fill: true,
  27777. * xField: 'name',
  27778. * yField: 'data3',
  27779. * marker: {
  27780. * type: 'circle',
  27781. * radius: 4,
  27782. * lineWidth: 0
  27783. * }
  27784. * }]
  27785. * });
  27786. *
  27787. * The configuration object for the `panzoom` interaction type should specify which axes
  27788. * will be made navigable via the `axes` config. See the {@link #axes} config documentation
  27789. * for details on the allowed formats. If the `axes` config is not specified, it will default
  27790. * to making all axes navigable with the default axis options.
  27791. *
  27792. */
  27793. Ext.define('Ext.chart.interactions.PanZoom', {
  27794. extend: 'Ext.chart.interactions.Abstract',
  27795. type: 'panzoom',
  27796. alias: 'interaction.panzoom',
  27797. requires: [
  27798. 'Ext.draw.Animator'
  27799. ],
  27800. config: {
  27801. /**
  27802. * @cfg {Object/Array} axes
  27803. * Specifies which axes should be made navigable. The config value can take the following
  27804. * formats:
  27805. *
  27806. * - An Object with keys corresponding to the {@link Ext.chart.axis.Axis#position position}
  27807. * of each axis that should be made navigable. Each key's value can either be an Object
  27808. * with further configuration options for each axis or simply `true` for a default set
  27809. * of options.
  27810. *
  27811. * {
  27812. * type: 'panzoom',
  27813. * axes: {
  27814. * left: {
  27815. * maxZoom: 5,
  27816. * allowPan: false
  27817. * },
  27818. * bottom: true
  27819. * }
  27820. * }
  27821. *
  27822. * If using the full Object form, the following options can be specified for each axis:
  27823. *
  27824. * - minZoom (Number) A minimum zoom level for the axis. Defaults to `1` which is its
  27825. * natural size.
  27826. * - maxZoom (Number) A maximum zoom level for the axis. Defaults to `10`.
  27827. * - startZoom (Number) A starting zoom level for the axis. Defaults to `1`.
  27828. * - allowZoom (Boolean) Whether zooming is allowed for the axis. Defaults to `true`.
  27829. * - allowPan (Boolean) Whether panning is allowed for the axis. Defaults to `true`.
  27830. * - startPan (Boolean) A starting panning offset for the axis. Defaults to `0`.
  27831. *
  27832. * - An Array of strings, each one corresponding to the {@link Ext.chart.axis.Axis#position
  27833. * position} of an axis that should be made navigable. The default options will be used
  27834. * for each named axis.
  27835. *
  27836. * {
  27837. * type: 'panzoom',
  27838. * axes: ['left', 'bottom']
  27839. * }
  27840. *
  27841. * If the `axes` config is not specified, it will default to making all axes navigable
  27842. * with the default axis options.
  27843. */
  27844. axes: {
  27845. top: {},
  27846. right: {},
  27847. bottom: {},
  27848. left: {}
  27849. },
  27850. minZoom: null,
  27851. maxZoom: null,
  27852. /**
  27853. * @cfg {Boolean} showOverflowArrows
  27854. * If `true`, arrows will be conditionally shown at either end of each axis to indicate that
  27855. * the axis is overflowing and can therefore be panned in that direction. Set this
  27856. * to `false` to prevent the arrows from being displayed.
  27857. */
  27858. showOverflowArrows: true,
  27859. /**
  27860. * @cfg {Object} overflowArrowOptions
  27861. * A set of optional overrides for the overflow arrow sprites' options. Only relevant when
  27862. * {@link #showOverflowArrows} is `true`.
  27863. */
  27864. /**
  27865. * @cfg {String} panGesture
  27866. * Defines the gesture that initiates panning.
  27867. * @private
  27868. */
  27869. panGesture: 'drag',
  27870. /**
  27871. * @cfg {String} zoomGesture
  27872. * Defines the gesture that initiates zooming.
  27873. * @private
  27874. */
  27875. zoomGesture: 'pinch',
  27876. /**
  27877. * @cfg {Boolean} zoomOnPanGesture
  27878. * @deprecated 6.2 Please use {@link #zoomOnPan} instead.
  27879. * If `true`, the pan gesture will zoom the chart.
  27880. */
  27881. zoomOnPanGesture: null,
  27882. /**
  27883. * @cfg {Boolean} zoomOnPan
  27884. * If `true`, the pan gesture will zoom the chart.
  27885. */
  27886. zoomOnPan: false,
  27887. /**
  27888. * @cfg {Boolean} [doubleTapReset=false]
  27889. * If `true`, the double tap on a chart will reset the current pan/zoom to show the whole
  27890. * chart.
  27891. */
  27892. doubleTapReset: false,
  27893. modeToggleButton: {
  27894. xtype: 'segmentedbutton',
  27895. width: 200,
  27896. defaults: {
  27897. ui: 'default-toolbar'
  27898. },
  27899. cls: Ext.baseCSSPrefix + 'panzoom-toggle',
  27900. items: [
  27901. {
  27902. text: 'Pan',
  27903. value: 'pan'
  27904. },
  27905. {
  27906. text: 'Zoom',
  27907. value: 'zoom'
  27908. }
  27909. ]
  27910. },
  27911. hideLabelInGesture: false
  27912. },
  27913. // Ext.os.is.Android
  27914. stopAnimationBeforeSync: true,
  27915. applyAxes: function(axesConfig, oldAxesConfig) {
  27916. return Ext.merge(oldAxesConfig || {}, axesConfig);
  27917. },
  27918. updateZoomOnPan: function(zoomOnPan) {
  27919. var button = this.getModeToggleButton();
  27920. button.setValue(zoomOnPan ? 'zoom' : 'pan');
  27921. },
  27922. updateZoomOnPanGesture: function(zoomOnPanGesture) {
  27923. this.setZoomOnPan(zoomOnPanGesture);
  27924. },
  27925. getZoomOnPanGesture: function() {
  27926. return this.getZoomOnPan();
  27927. },
  27928. applyModeToggleButton: function(button, oldButton) {
  27929. return Ext.factory(button, 'Ext.button.Segmented', oldButton);
  27930. },
  27931. updateModeToggleButton: function(button) {
  27932. if (button) {
  27933. button.on('change', 'onModeToggleChange', this);
  27934. }
  27935. },
  27936. onModeToggleChange: function(segmentedButton, value) {
  27937. this.setZoomOnPan(value === 'zoom');
  27938. },
  27939. getGestures: function() {
  27940. var me = this,
  27941. gestures = {},
  27942. pan = me.getPanGesture(),
  27943. zoom = me.getZoomGesture();
  27944. gestures[zoom] = 'onZoomGestureMove';
  27945. gestures[zoom + 'start'] = 'onZoomGestureStart';
  27946. gestures[zoom + 'end'] = 'onZoomGestureEnd';
  27947. gestures[pan] = 'onPanGestureMove';
  27948. gestures[pan + 'start'] = 'onPanGestureStart';
  27949. gestures[pan + 'end'] = 'onPanGestureEnd';
  27950. gestures.doubletap = 'onDoubleTap';
  27951. return gestures;
  27952. },
  27953. onDoubleTap: function(e) {
  27954. var me = this,
  27955. doubleTapReset = me.getDoubleTapReset(),
  27956. chart, axes, axis, i, ln;
  27957. if (doubleTapReset) {
  27958. chart = me.getChart();
  27959. axes = chart.getAxes();
  27960. for (i = 0 , ln = axes.length; i < ln; i++) {
  27961. axis = axes[i];
  27962. axis.setVisibleRange([
  27963. 0,
  27964. 1
  27965. ]);
  27966. }
  27967. chart.redraw();
  27968. }
  27969. },
  27970. onPanGestureStart: function(e) {
  27971. var me = this,
  27972. chart, rect, xy;
  27973. if (!e || !e.touches || e.touches.length < 2) {
  27974. // Limit drags to single touch
  27975. chart = me.getChart();
  27976. rect = chart.getInnerRect();
  27977. xy = chart.element.getXY();
  27978. e.claimGesture();
  27979. chart.suspendAnimation();
  27980. me.startX = e.getX() - xy[0] - rect[0];
  27981. me.startY = e.getY() - xy[1] - rect[1];
  27982. me.oldVisibleRanges = null;
  27983. me.hideLabels();
  27984. chart.suspendThicknessChanged();
  27985. me.lockEvents(me.getPanGesture());
  27986. return false;
  27987. }
  27988. },
  27989. onPanGestureMove: function(e) {
  27990. var me = this,
  27991. isMouse = e.pointerType === 'mouse',
  27992. isZoomOnPan = isMouse && me.getZoomOnPan(),
  27993. chart, rect, xy;
  27994. if (me.getLocks()[me.getPanGesture()] === me) {
  27995. // Limit drags to single touch.
  27996. chart = me.getChart();
  27997. rect = chart.getInnerRect();
  27998. xy = chart.element.getXY();
  27999. if (isZoomOnPan) {
  28000. me.transformAxesBy(me.getZoomableAxes(e), 0, 0, (e.getX() - xy[0] - rect[0]) / me.startX, me.startY / (e.getY() - xy[1] - rect[1]));
  28001. } else {
  28002. me.transformAxesBy(me.getPannableAxes(e), e.getX() - xy[0] - rect[0] - me.startX, e.getY() - xy[1] - rect[1] - me.startY, 1, 1);
  28003. }
  28004. me.sync();
  28005. return false;
  28006. }
  28007. },
  28008. onPanGestureEnd: function(e) {
  28009. var me = this,
  28010. pan = me.getPanGesture(),
  28011. chart;
  28012. if (me.getLocks()[pan] === me) {
  28013. chart = me.getChart();
  28014. chart.resumeThicknessChanged();
  28015. me.showLabels();
  28016. me.sync();
  28017. me.unlockEvents(pan);
  28018. chart.resumeAnimation();
  28019. return false;
  28020. }
  28021. },
  28022. onZoomGestureStart: function(e) {
  28023. if (e.touches && e.touches.length === 2) {
  28024. // eslint-disable-next-line vars-on-top
  28025. var me = this,
  28026. chart = me.getChart(),
  28027. xy = chart.element.getXY(),
  28028. rect = chart.getInnerRect(),
  28029. x = xy[0] + rect[0],
  28030. y = xy[1] + rect[1],
  28031. newPoints = [
  28032. e.touches[0].point.x - x,
  28033. e.touches[0].point.y - y,
  28034. e.touches[1].point.x - x,
  28035. e.touches[1].point.y - y
  28036. ],
  28037. xDistance = Math.max(44, Math.abs(newPoints[2] - newPoints[0])),
  28038. yDistance = Math.max(44, Math.abs(newPoints[3] - newPoints[1]));
  28039. e.claimGesture();
  28040. chart.suspendAnimation();
  28041. chart.suspendThicknessChanged();
  28042. me.lastZoomDistances = [
  28043. xDistance,
  28044. yDistance
  28045. ];
  28046. me.lastPoints = newPoints;
  28047. me.oldVisibleRanges = null;
  28048. me.hideLabels();
  28049. me.lockEvents(me.getZoomGesture());
  28050. return false;
  28051. }
  28052. },
  28053. onZoomGestureMove: function(e) {
  28054. var me = this;
  28055. if (me.getLocks()[me.getZoomGesture()] === me) {
  28056. // eslint-disable-next-line vars-on-top
  28057. var chart = me.getChart(),
  28058. rect = chart.getInnerRect(),
  28059. xy = chart.element.getXY(),
  28060. x = xy[0] + rect[0],
  28061. y = xy[1] + rect[1],
  28062. abs = Math.abs,
  28063. lastPoints = me.lastPoints,
  28064. newPoints = [
  28065. e.touches[0].point.x - x,
  28066. e.touches[0].point.y - y,
  28067. e.touches[1].point.x - x,
  28068. e.touches[1].point.y - y
  28069. ],
  28070. xDistance = Math.max(44, abs(newPoints[2] - newPoints[0])),
  28071. yDistance = Math.max(44, abs(newPoints[3] - newPoints[1])),
  28072. lastDistances = this.lastZoomDistances || [
  28073. xDistance,
  28074. yDistance
  28075. ],
  28076. zoomX = xDistance / lastDistances[0],
  28077. zoomY = yDistance / lastDistances[1];
  28078. 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);
  28079. me.sync();
  28080. return false;
  28081. }
  28082. },
  28083. onZoomGestureEnd: function(e) {
  28084. var me = this,
  28085. zoom = me.getZoomGesture(),
  28086. chart;
  28087. if (me.getLocks()[zoom] === me) {
  28088. chart = me.getChart();
  28089. chart.resumeThicknessChanged();
  28090. me.showLabels();
  28091. me.sync();
  28092. me.unlockEvents(zoom);
  28093. chart.resumeAnimation();
  28094. return false;
  28095. }
  28096. },
  28097. hideLabels: function() {
  28098. if (this.getHideLabelInGesture()) {
  28099. this.eachInteractiveAxes(function(axis) {
  28100. axis.hideLabels();
  28101. });
  28102. }
  28103. },
  28104. showLabels: function() {
  28105. if (this.getHideLabelInGesture()) {
  28106. this.eachInteractiveAxes(function(axis) {
  28107. axis.showLabels();
  28108. });
  28109. }
  28110. },
  28111. isEventOnAxis: function(e, axis) {
  28112. // TODO: right now this uses the current event position but really we want to only
  28113. // use the gesture's start event. Pinch does not give that to us though.
  28114. var rect = axis.getSurface().getRect();
  28115. return rect[0] <= e.getX() && e.getX() <= rect[0] + rect[2] && rect[1] <= e.getY() && e.getY() <= rect[1] + rect[3];
  28116. },
  28117. getPannableAxes: function(e) {
  28118. var me = this,
  28119. axisConfigs = me.getAxes(),
  28120. axes = me.getChart().getAxes(),
  28121. i,
  28122. ln = axes.length,
  28123. result = [],
  28124. isEventOnAxis = false,
  28125. config;
  28126. if (e) {
  28127. for (i = 0; i < ln; i++) {
  28128. if (this.isEventOnAxis(e, axes[i])) {
  28129. isEventOnAxis = true;
  28130. break;
  28131. }
  28132. }
  28133. }
  28134. for (i = 0; i < ln; i++) {
  28135. config = axisConfigs[axes[i].getPosition()];
  28136. if (config && config.allowPan !== false && (!isEventOnAxis || this.isEventOnAxis(e, axes[i]))) {
  28137. result.push(axes[i]);
  28138. }
  28139. }
  28140. return result;
  28141. },
  28142. getZoomableAxes: function(e) {
  28143. var me = this,
  28144. axisConfigs = me.getAxes(),
  28145. axes = me.getChart().getAxes(),
  28146. result = [],
  28147. i,
  28148. ln = axes.length,
  28149. axis,
  28150. isEventOnAxis = false,
  28151. config;
  28152. if (e) {
  28153. for (i = 0; i < ln; i++) {
  28154. if (this.isEventOnAxis(e, axes[i])) {
  28155. isEventOnAxis = true;
  28156. break;
  28157. }
  28158. }
  28159. }
  28160. for (i = 0; i < ln; i++) {
  28161. axis = axes[i];
  28162. config = axisConfigs[axis.getPosition()];
  28163. if (config && config.allowZoom !== false && (!isEventOnAxis || this.isEventOnAxis(e, axis))) {
  28164. result.push(axis);
  28165. }
  28166. }
  28167. return result;
  28168. },
  28169. eachInteractiveAxes: function(fn) {
  28170. var me = this,
  28171. axisConfigs = me.getAxes(),
  28172. axes = me.getChart().getAxes(),
  28173. i;
  28174. for (i = 0; i < axes.length; i++) {
  28175. if (axisConfigs[axes[i].getPosition()]) {
  28176. if (false === fn.call(this, axes[i])) {
  28177. return;
  28178. }
  28179. }
  28180. }
  28181. },
  28182. transformAxesBy: function(axes, panX, panY, sx, sy) {
  28183. var rect = this.getChart().getInnerRect(),
  28184. axesCfg = this.getAxes(),
  28185. oldVisibleRanges = this.oldVisibleRanges,
  28186. result = false,
  28187. axisCfg, i;
  28188. if (!oldVisibleRanges) {
  28189. this.oldVisibleRanges = oldVisibleRanges = {};
  28190. this.eachInteractiveAxes(function(axis) {
  28191. oldVisibleRanges[axis.getId()] = axis.getVisibleRange();
  28192. });
  28193. }
  28194. if (!rect) {
  28195. return;
  28196. }
  28197. for (i = 0; i < axes.length; i++) {
  28198. axisCfg = axesCfg[axes[i].getPosition()];
  28199. result = this.transformAxisBy(axes[i], oldVisibleRanges[axes[i].getId()], panX, panY, sx, sy, this.minZoom || axisCfg.minZoom, this.maxZoom || axisCfg.maxZoom) || result;
  28200. }
  28201. return result;
  28202. },
  28203. transformAxisBy: function(axis, oldVisibleRange, panX, panY, sx, sy, minZoom, maxZoom) {
  28204. var me = this,
  28205. visibleLength = oldVisibleRange[1] - oldVisibleRange[0],
  28206. visibleRange = axis.getVisibleRange(),
  28207. actualMinZoom = minZoom || me.getMinZoom() || axis.config.minZoom,
  28208. actualMaxZoom = maxZoom || me.getMaxZoom() || axis.config.maxZoom,
  28209. rect = me.getChart().getInnerRect(),
  28210. left, right, isSide, pan, length;
  28211. if (!rect) {
  28212. return;
  28213. }
  28214. isSide = axis.isSide();
  28215. length = isSide ? rect[3] : rect[2];
  28216. pan = isSide ? -panY : panX;
  28217. visibleLength /= isSide ? sy : sx;
  28218. if (visibleLength < 0) {
  28219. visibleLength = -visibleLength;
  28220. }
  28221. if (visibleLength * actualMinZoom > 1) {
  28222. visibleLength = 1;
  28223. }
  28224. if (visibleLength * actualMaxZoom < 1) {
  28225. visibleLength = 1 / actualMaxZoom;
  28226. }
  28227. left = oldVisibleRange[0];
  28228. right = oldVisibleRange[1];
  28229. visibleRange = visibleRange[1] - visibleRange[0];
  28230. if (visibleLength === visibleRange && visibleRange === 1) {
  28231. return;
  28232. }
  28233. axis.setVisibleRange([
  28234. (oldVisibleRange[0] + oldVisibleRange[1] - visibleLength) * 0.5 - pan / length * visibleLength,
  28235. (oldVisibleRange[0] + oldVisibleRange[1] + visibleLength) * 0.5 - pan / length * visibleLength
  28236. ]);
  28237. return Math.abs(left - axis.getVisibleRange()[0]) > 1.0E-10 || Math.abs(right - axis.getVisibleRange()[1]) > 1.0E-10;
  28238. },
  28239. destroy: function() {
  28240. this.setModeToggleButton(null);
  28241. this.callParent();
  28242. }
  28243. });
  28244. /* eslint-disable max-len */
  28245. /**
  28246. * @class Ext.chart.interactions.Rotate
  28247. * @extends Ext.chart.interactions.Abstract
  28248. *
  28249. * The Rotate interaction allows the user to rotate a polar chart about its central point.
  28250. *
  28251. * @example
  28252. * Ext.create('Ext.Container', {
  28253. * renderTo: Ext.getBody(),
  28254. * width: 600,
  28255. * height: 400,
  28256. * layout: 'fit',
  28257. * items: {
  28258. * xtype: 'polar',
  28259. * interactions: 'rotate',
  28260. * colors: ["#115fa6", "#94ae0a", "#a61120", "#ff8809", "#ffd13e"],
  28261. * store: {
  28262. * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
  28263. * data: [
  28264. * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
  28265. * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
  28266. * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
  28267. * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
  28268. * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
  28269. * ]
  28270. * },
  28271. * series: {
  28272. * type: 'pie',
  28273. * label: {
  28274. * field: 'name',
  28275. * display: 'rotate'
  28276. * },
  28277. * xField: 'data3',
  28278. * donut: 30
  28279. * }
  28280. * }
  28281. * });
  28282. */
  28283. /* eslint-enable max-len */
  28284. Ext.define('Ext.chart.interactions.Rotate', {
  28285. extend: 'Ext.chart.interactions.Abstract',
  28286. type: 'rotate',
  28287. alternateClassName: 'Ext.chart.interactions.RotatePie3D',
  28288. alias: [
  28289. 'interaction.rotate',
  28290. 'interaction.rotatePie3d'
  28291. ],
  28292. /**
  28293. * @event rotate
  28294. * Fires on every tick of the rotation.
  28295. * @param {Ext.chart.interactions.Rotate} this This interaction.
  28296. * @param {Number} angle The new current rotation angle.
  28297. */
  28298. /**
  28299. * @event rotatestart
  28300. * Fires when a user initiates the rotation.
  28301. * @param {Ext.chart.interactions.Rotate} this This interaction.
  28302. * @param {Number} angle The new current rotation angle.
  28303. */
  28304. /**
  28305. * @event rotateend
  28306. * Fires after a user finishes the rotation.
  28307. * @param {Ext.chart.interactions.Rotate} this This interaction.
  28308. * @param {Number} angle The new current rotation angle.
  28309. */
  28310. /**
  28311. * @deprecated 6.5.1 Use the 'rotateend' event instead.
  28312. * @event rotationEnd
  28313. * Fires after a user finishes the rotation
  28314. * @param {Ext.chart.interactions.Rotate} this This interaction.
  28315. * @param {Number} angle The new current rotation angle.
  28316. */
  28317. config: {
  28318. /**
  28319. * @cfg {String} gesture
  28320. * Defines the gesture type that will be used to rotate the chart. Currently only
  28321. * supports `pinch` for two-finger rotation and `drag` for single-finger rotation.
  28322. * @private
  28323. */
  28324. gesture: 'rotate',
  28325. gestures: {
  28326. dragstart: 'onGestureStart',
  28327. drag: 'onGesture',
  28328. dragend: 'onGestureEnd'
  28329. },
  28330. /**
  28331. * @cfg {Number} rotation
  28332. * Saves the current rotation of the series. Accepts negative values
  28333. * and values > 360 ( / 180 * Math.PI)
  28334. * @private
  28335. */
  28336. rotation: 0
  28337. },
  28338. oldRotations: null,
  28339. getAngle: function(e) {
  28340. var me = this,
  28341. chart = me.getChart(),
  28342. xy = chart.getEventXY(e),
  28343. center = chart.getCenter();
  28344. return Math.atan2(xy[1] - center[1], xy[0] - center[0]);
  28345. },
  28346. onGestureStart: function(e) {
  28347. var me = this;
  28348. e.claimGesture();
  28349. me.lockEvents('drag');
  28350. me.angle = me.getAngle(e);
  28351. me.oldRotations = {};
  28352. me.getChart().suspendAnimation();
  28353. me.fireEvent('rotatestart', me, me.getRotation());
  28354. return false;
  28355. },
  28356. onGesture: function(e) {
  28357. var me = this,
  28358. angle = me.getAngle(e) - me.angle;
  28359. if (me.getLocks().drag === me) {
  28360. me.doRotateTo(angle, true);
  28361. return false;
  28362. }
  28363. },
  28364. /**
  28365. * @private
  28366. */
  28367. doRotateTo: function(angle, relative) {
  28368. var me = this,
  28369. chart = me.getChart(),
  28370. axes = chart.getAxes(),
  28371. seriesList = chart.getSeries(),
  28372. oldRotations = me.oldRotations,
  28373. rotation, oldRotation, axis, series, id, i, ln;
  28374. for (i = 0 , ln = axes.length; i < ln; i++) {
  28375. axis = axes[i];
  28376. id = axis.getId();
  28377. oldRotation = oldRotations[id] || (oldRotations[id] = axis.getRotation());
  28378. rotation = angle + (relative ? oldRotation : 0);
  28379. axis.setRotation(rotation);
  28380. }
  28381. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  28382. series = seriesList[i];
  28383. id = series.getId();
  28384. oldRotation = oldRotations[id] || (oldRotations[id] = series.getRotation());
  28385. // Unline axis's 'rotation', Polar series' 'rotation' is a public config and in degrees.
  28386. rotation = Ext.draw.Draw.degrees(angle + (relative ? oldRotation : 0));
  28387. series.setRotation(rotation);
  28388. }
  28389. me.setRotation(rotation);
  28390. me.fireEvent('rotate', me, me.getRotation());
  28391. me.sync();
  28392. },
  28393. /**
  28394. * Rotates a polar chart about its center point to the specified angle.
  28395. * @param {Number} angle The angle to rotate to.
  28396. * @param {Boolean} [relative=false] Whether the rotation is relative to the current angle
  28397. * or not.
  28398. * @param {Boolean} [animate=false] Whether to animate the rotation or not.
  28399. */
  28400. rotateTo: function(angle, relative, animate) {
  28401. var me = this,
  28402. chart = me.getChart();
  28403. if (!animate) {
  28404. chart.suspendAnimation();
  28405. }
  28406. me.doRotateTo(angle, relative, animate);
  28407. me.oldRotations = {};
  28408. if (!animate) {
  28409. chart.resumeAnimation();
  28410. }
  28411. },
  28412. onGestureEnd: function(e) {
  28413. var me = this;
  28414. if (me.getLocks().drag === me) {
  28415. me.onGesture(e);
  28416. me.unlockEvents('drag');
  28417. me.getChart().resumeAnimation();
  28418. me.fireEvent('rotateend', me, me.getRotation());
  28419. me.fireEvent('rotationEnd', me, me.getRotation());
  28420. return false;
  28421. }
  28422. }
  28423. });
  28424. /**
  28425. *
  28426. */
  28427. Ext.define('Ext.chart.navigator.ContainerBase', {
  28428. extend: 'Ext.panel.Panel'
  28429. });
  28430. /**
  28431. *
  28432. */
  28433. Ext.define('Ext.chart.navigator.NavigatorBase', {
  28434. extend: 'Ext.chart.CartesianChart',
  28435. onRender: function() {
  28436. this.callParent();
  28437. this.setupEvents();
  28438. },
  28439. // Note: 'applyDock' and 'updateDock' won't ever be called in Classic.
  28440. // See the Classic Component's 'setDock' method, which is overridden here.
  28441. setDocked: function(docked) {
  28442. var me = this,
  28443. ownerCt = me.getNavigatorContainer();
  28444. if (!(docked === 'top' || docked === 'bottom')) {
  28445. Ext.raise("Can only dock to 'top' or 'bottom'.");
  28446. }
  28447. if (docked !== me.dock) {
  28448. if (ownerCt && ownerCt.moveDocked) {
  28449. ownerCt.moveDocked(me, docked);
  28450. } else {
  28451. me.dock = docked;
  28452. }
  28453. }
  28454. return me;
  28455. },
  28456. getDocked: function() {
  28457. return this.dock;
  28458. }
  28459. });
  28460. /**
  28461. * The overlay sprite used by the {@link Ext.chart.navigator.Navigator} component
  28462. * to render the selected visible range or a chart's horizontal axis.
  28463. */
  28464. Ext.define('Ext.chart.navigator.sprite.RangeMask', {
  28465. extend: 'Ext.draw.sprite.Sprite',
  28466. alias: 'sprite.rangemask',
  28467. inheritableStatics: {
  28468. def: {
  28469. processors: {
  28470. min: 'limited01',
  28471. max: 'limited01',
  28472. thumbOpacity: 'limited01'
  28473. },
  28474. defaults: {
  28475. min: 0,
  28476. max: 1,
  28477. lineWidth: 2,
  28478. miterLimit: 1,
  28479. strokeStyle: '#787878',
  28480. thumbOpacity: 1
  28481. }
  28482. }
  28483. },
  28484. getBBox: function(isWithoutTransform) {
  28485. var me = this,
  28486. attr = me.attr,
  28487. bbox = attr.bbox;
  28488. bbox.plain = {
  28489. x: 0,
  28490. y: 0,
  28491. width: 1,
  28492. height: 1
  28493. };
  28494. if (isWithoutTransform) {
  28495. return bbox.plain;
  28496. }
  28497. return bbox.transform || (bbox.transform = attr.matrix.transformBBox(bbox.plain));
  28498. },
  28499. renderThumb: function(surface, ctx, x, y) {
  28500. var me = this,
  28501. shapeSprite = me.shapeSprite,
  28502. textureSprite = me.textureSprite,
  28503. thumbOpacity = me.attr.thumbOpacity,
  28504. thumbAttributes = {
  28505. opacity: thumbOpacity,
  28506. translationX: x,
  28507. translationY: y
  28508. };
  28509. if (!shapeSprite) {
  28510. shapeSprite = me.shapeSprite = new Ext.draw.sprite.Rect({
  28511. x: -9.5,
  28512. y: -9.5,
  28513. width: 19,
  28514. height: 19,
  28515. radius: 4,
  28516. lineWidth: 1,
  28517. fillStyle: {
  28518. type: 'linear',
  28519. degrees: 90,
  28520. stops: [
  28521. {
  28522. offset: 0,
  28523. color: '#EEE'
  28524. },
  28525. {
  28526. offset: 1,
  28527. color: '#FFF'
  28528. }
  28529. ]
  28530. },
  28531. strokeStyle: '#999'
  28532. });
  28533. textureSprite = me.textureSprite = new Ext.draw.sprite.Path({
  28534. path: 'M -4, -5, -4, 5 M 0, -5, 0, 5 M 4, -5, 4, 5',
  28535. strokeStyle: {
  28536. type: 'linear',
  28537. degrees: 90,
  28538. stops: [
  28539. {
  28540. offset: 0,
  28541. color: '#CCC'
  28542. },
  28543. {
  28544. offset: 1,
  28545. color: '#BBB'
  28546. }
  28547. ]
  28548. },
  28549. lineWidth: 2
  28550. });
  28551. }
  28552. ctx.save();
  28553. shapeSprite.setAttributes(thumbAttributes);
  28554. shapeSprite.applyTransformations();
  28555. textureSprite.setAttributes(thumbAttributes);
  28556. textureSprite.applyTransformations();
  28557. shapeSprite.useAttributes(ctx);
  28558. shapeSprite.render(surface, ctx);
  28559. textureSprite.useAttributes(ctx);
  28560. textureSprite.render(surface, ctx);
  28561. ctx.restore();
  28562. },
  28563. render: function(surface, ctx) {
  28564. var me = this,
  28565. attr = me.attr,
  28566. matrix = attr.matrix.elements,
  28567. sx = matrix[0],
  28568. sy = matrix[3],
  28569. tx = matrix[4],
  28570. ty = matrix[5],
  28571. min = attr.min,
  28572. max = attr.max,
  28573. // s_min and s_max are range values in screen coordinates (scaled and translated)
  28574. s_min = min * sx + tx,
  28575. s_max = max * sx + tx,
  28576. s_y = Math.round(0.5 * sy + ty);
  28577. // thumb position in screen coordinates (mid-height)
  28578. ctx.beginPath();
  28579. // Rect that represents the whole range.
  28580. ctx.moveTo(tx, ty);
  28581. ctx.lineTo(sx + tx, ty);
  28582. ctx.lineTo(sx + tx, sy + ty);
  28583. ctx.lineTo(tx, sy + ty);
  28584. ctx.lineTo(tx, ty);
  28585. // Rect that represents the visible range.
  28586. ctx.moveTo(s_min, ty);
  28587. ctx.lineTo(s_min, sy + ty);
  28588. ctx.lineTo(s_max, sy + ty);
  28589. ctx.lineTo(s_max, ty);
  28590. ctx.lineTo(s_min, ty);
  28591. ctx.fillStroke(attr, true);
  28592. me.renderThumb(surface, ctx, Math.round(s_min), s_y);
  28593. me.renderThumb(surface, ctx, Math.round(s_max), s_y);
  28594. }
  28595. });
  28596. /**
  28597. * The Navigator component is used to visually set the visible range of the x-axis
  28598. * of a cartesian chart.
  28599. *
  28600. * This component is meant to be used with the Navigator Container
  28601. * via its {@link Ext.chart.navigator.Container#navigator} config.
  28602. *
  28603. * IMPORTANT: even though the Navigator component is a kind of chart, it should not be
  28604. * treated as such. Correct behavior is not guaranteed when using hidden/private configs.
  28605. */
  28606. Ext.define('Ext.chart.navigator.Navigator', {
  28607. extend: 'Ext.chart.navigator.NavigatorBase',
  28608. isNavigator: true,
  28609. requires: [
  28610. 'Ext.chart.navigator.sprite.RangeMask'
  28611. ],
  28612. config: {
  28613. /**
  28614. * @cfg {'bottom'/'top'} [docked='bottom']
  28615. */
  28616. docked: 'bottom',
  28617. /**
  28618. * @cfg {'series'/'chart'} [span='series']
  28619. * Whether the navigator should span the 'series' (default) or the whole 'chart'.
  28620. */
  28621. span: 'series',
  28622. insetPadding: 0,
  28623. innerPadding: 0,
  28624. /**
  28625. * @cfg {Ext.chart.navigator.Container} navigatorContainer
  28626. * 'parent' is reserved in Modern, 'container' is reserved in Classic,
  28627. * so we use 'navigatorContainer' as a config name.
  28628. * @private
  28629. */
  28630. navigatorContainer: null,
  28631. /**
  28632. * @cfg {String} axis (required)
  28633. * The ID of the {@link #chart chart's} axis to link to.
  28634. * The axis should be positioned to 'bottom' or 'top' in the chart.
  28635. */
  28636. axis: null,
  28637. /**
  28638. * @cfg {Number} [tolerance=20]
  28639. * The maximum horizontal delta between the pointer/finger and the center of a navigator
  28640. * thumb. Used for hit testing.
  28641. */
  28642. tolerance: 20,
  28643. /**
  28644. * @cfg {Number} [minimum=0.8]
  28645. * The start of the visible range, where the visible range is a [0, 1] interval.
  28646. */
  28647. minimum: 0.8,
  28648. /**
  28649. * @cfg {Number} [maximum=1]
  28650. * The end of the visible range, where the visible range is a [0, 1] interval.
  28651. */
  28652. maximum: 1,
  28653. /**
  28654. * @cfg {Number} [thumbGap=30]
  28655. * Minimum gap between navigator thumbs in pixels.
  28656. */
  28657. thumbGap: 30,
  28658. autoHideThumbs: true,
  28659. width: '100%',
  28660. /**
  28661. * @cfg {Number} [height=75]
  28662. * The height of the navigator component.
  28663. */
  28664. height: 75
  28665. },
  28666. /**
  28667. * @cfg flipXY
  28668. * @hide
  28669. */
  28670. /**
  28671. * @cfg series
  28672. * @hide
  28673. */
  28674. /**
  28675. * @cfg axes
  28676. * @hide
  28677. */
  28678. /**
  28679. * @cfg store
  28680. * @hide
  28681. */
  28682. /**
  28683. * @cfg legend
  28684. * @hide
  28685. */
  28686. /**
  28687. * @cfg interactions
  28688. * @hide
  28689. */
  28690. /**
  28691. * @cfg highlightItem
  28692. * @hide
  28693. */
  28694. /**
  28695. * @cfg theme
  28696. * @hide
  28697. */
  28698. /**
  28699. * @cfg innerPadding
  28700. * @hide
  28701. */
  28702. /**
  28703. * @cfg insetPadding
  28704. * @hide
  28705. */
  28706. dragType: null,
  28707. constructor: function(config) {
  28708. var me = this,
  28709. visibleRange, overlay;
  28710. config = config || {};
  28711. visibleRange = [
  28712. config.minimum || 0.8,
  28713. config.maximum || 1
  28714. ];
  28715. me.callParent([
  28716. config
  28717. ]);
  28718. overlay = me.overlaySurface;
  28719. overlay.element.setStyle({
  28720. zIndex: 100
  28721. });
  28722. me.rangeMask = overlay.add({
  28723. type: 'rangemask',
  28724. min: visibleRange[0],
  28725. max: visibleRange[1],
  28726. fillStyle: 'rgba(0, 0, 0, .25)'
  28727. });
  28728. me.onDragEnd();
  28729. // Set 'thumbOpacity' of the range mask sprite to 0, if needed,
  28730. // and apply animation modifier changes after that, so that the attribute is set
  28731. // instantly.
  28732. me.rangeMask.setAnimation({
  28733. duration: 500,
  28734. customDurations: {
  28735. min: 0,
  28736. max: 0,
  28737. translationX: 0,
  28738. translationY: 0,
  28739. scalingX: 0,
  28740. scalingY: 0,
  28741. scalingCenterX: 0,
  28742. scalingCenterY: 0,
  28743. fillStyle: 0,
  28744. strokeStyle: 0
  28745. }
  28746. });
  28747. me.setVisibleRange(visibleRange);
  28748. },
  28749. createSurface: function(id) {
  28750. var surface = this.callParent([
  28751. id
  28752. ]);
  28753. if (id === 'overlay') {
  28754. this.overlaySurface = surface;
  28755. }
  28756. return surface;
  28757. },
  28758. // Note: 'applyDock' and 'updateDock' won't ever be called in Classic.
  28759. // See Classic NavigatorBase.
  28760. applyAxis: function(axis) {
  28761. return this.getNavigatorContainer().getChart().getAxis(axis);
  28762. },
  28763. updateAxis: function(axis, oldAxis) {
  28764. var me = this,
  28765. eventName = 'visiblerangechange',
  28766. eventHandler = 'onAxisVisibleRangeChange';
  28767. if (oldAxis) {
  28768. oldAxis.un(eventName, eventHandler, me);
  28769. }
  28770. if (axis) {
  28771. axis.on(eventName, eventHandler, me);
  28772. }
  28773. me.axis = axis;
  28774. },
  28775. getAxis: function() {
  28776. // The superclass doesn't have the 'axis' config, but it has the same method,
  28777. // which we override here to act as a getter for the config. The user is not
  28778. // expected to use the original method in this subclass anyway.
  28779. return this.axis;
  28780. },
  28781. onAxisVisibleRangeChange: function(axis, visibleRange) {
  28782. this.setVisibleRange(visibleRange);
  28783. },
  28784. updateNavigatorContainer: function(navigatorContainer) {
  28785. var me = this,
  28786. oldChart = me.chart,
  28787. chart = me.chart = navigatorContainer && navigatorContainer.getChart(),
  28788. chartSeriesList = chart && chart.getSeries(),
  28789. // 'legendStore' already exists in the base class.
  28790. chartLegendStore = me.chartLegendStore,
  28791. navigatorSeriesList = [],
  28792. storeEventName = 'update',
  28793. // 'onLegendStoreUpdate' already exists in the base class.
  28794. storeEventHandler = 'onChartLegendStoreUpdate',
  28795. chartSeries, navigatorSeries, seriesConfig, i;
  28796. if (oldChart) {
  28797. oldChart.un('layout', 'afterBoundChartLayout', me);
  28798. oldChart.un('themechange', 'onChartThemeChange', me);
  28799. oldChart.un('storechange', 'onChartStoreChange', me);
  28800. }
  28801. chart.on('layout', 'afterBoundChartLayout', me);
  28802. for (i = 0; i < chartSeriesList.length; i++) {
  28803. chartSeries = chartSeriesList[i];
  28804. seriesConfig = me.getSeriesConfig(chartSeries);
  28805. navigatorSeries = Ext.create('series.' + seriesConfig.type, seriesConfig);
  28806. navigatorSeries.parentSeries = chartSeries;
  28807. chartSeries.navigatorSeries = navigatorSeries;
  28808. navigatorSeriesList.push(navigatorSeries);
  28809. }
  28810. if (chartLegendStore) {
  28811. chartLegendStore.un(storeEventName, storeEventHandler, me);
  28812. me.chartLegendStore = null;
  28813. }
  28814. if (chart) {
  28815. me.setStore(chart.getStore());
  28816. me.chartLegendStore = chartLegendStore = chart.getLegendStore();
  28817. if (chartLegendStore) {
  28818. chartLegendStore.on(storeEventName, storeEventHandler, me);
  28819. }
  28820. chart.on('themechange', 'onChartThemeChange', me);
  28821. chart.on('storechange', 'onChartStoreChange', me);
  28822. me.onChartThemeChange(chart, chart.getTheme());
  28823. }
  28824. me.setSeries(navigatorSeriesList);
  28825. },
  28826. onChartThemeChange: function(chart, theme) {
  28827. this.setTheme(theme);
  28828. },
  28829. onChartStoreChange: function(chart, store) {
  28830. this.setStore(store);
  28831. },
  28832. addCustomStyle: function(config, style, subStyle) {
  28833. var fillStyle, strokeStyle;
  28834. style = style || {};
  28835. subStyle = subStyle || {};
  28836. config.style = config.style || {};
  28837. config.subStyle = config.subStyle || {};
  28838. fillStyle = style && (style.fillStyle || style.fill);
  28839. strokeStyle = style && (style.strokeStyle || style.stroke);
  28840. if (fillStyle) {
  28841. config.style.fillStyle = fillStyle;
  28842. }
  28843. if (strokeStyle) {
  28844. config.style.strokeStyle = strokeStyle;
  28845. }
  28846. fillStyle = subStyle && (subStyle.fillStyle || subStyle.fill);
  28847. strokeStyle = subStyle && (subStyle.strokeStyle || subStyle.stroke);
  28848. if (fillStyle) {
  28849. config.subStyle.fillStyle = fillStyle;
  28850. }
  28851. if (strokeStyle) {
  28852. config.subStyle.strokeStyle = strokeStyle;
  28853. }
  28854. return config;
  28855. },
  28856. getSeriesConfig: function(chartSeries) {
  28857. var me = this,
  28858. style = chartSeries.getStyle(),
  28859. config;
  28860. if (chartSeries.isLine) {
  28861. config = me.addCustomStyle({
  28862. type: 'line',
  28863. fill: true,
  28864. xField: chartSeries.getXField(),
  28865. yField: chartSeries.getYField(),
  28866. smooth: chartSeries.getSmooth()
  28867. }, style);
  28868. } else if (chartSeries.isCandleStick) {
  28869. config = me.addCustomStyle({
  28870. type: 'line',
  28871. fill: true,
  28872. xField: chartSeries.getXField(),
  28873. yField: chartSeries.getCloseField()
  28874. }, style.raiseStyle);
  28875. } else if (chartSeries.isArea || chartSeries.isBar) {
  28876. config = me.addCustomStyle({
  28877. type: 'area',
  28878. xField: chartSeries.getXField(),
  28879. yField: chartSeries.getYField()
  28880. }, style, chartSeries.getSubStyle());
  28881. } else {
  28882. Ext.raise("Navigator only works with 'line', 'bar', 'candlestick' and 'area' series.");
  28883. }
  28884. config.style.fillOpacity = 0.2;
  28885. return config;
  28886. },
  28887. onChartLegendStoreUpdate: function(store, record) {
  28888. var me = this,
  28889. chart = me.chart,
  28890. series;
  28891. if (chart && record) {
  28892. series = chart.getSeries().map[record.get('series')];
  28893. if (series && series.navigatorSeries) {
  28894. series.navigatorSeries.setHiddenByIndex(record.get('index'), record.get('disabled'));
  28895. me.redraw();
  28896. }
  28897. }
  28898. },
  28899. setupEvents: function() {
  28900. // Called from NavigatorBase classes.
  28901. var me = this,
  28902. overlayEl = me.overlaySurface.element;
  28903. overlayEl.on({
  28904. scope: me,
  28905. drag: 'onDrag',
  28906. dragstart: 'onDragStart',
  28907. dragend: 'onDragEnd',
  28908. dragcancel: 'onDragEnd',
  28909. mousemove: 'onMouseMove'
  28910. });
  28911. },
  28912. onMouseMove: function(e) {
  28913. var me = this,
  28914. overlayEl = me.overlaySurface.element,
  28915. style = overlayEl.dom.style,
  28916. dragType = me.getDragType(e.pageX - overlayEl.getXY()[0]);
  28917. switch (dragType) {
  28918. case 'min':
  28919. case 'max':
  28920. style.cursor = 'ew-resize';
  28921. break;
  28922. case 'pan':
  28923. style.cursor = 'move';
  28924. break;
  28925. default:
  28926. style.cursor = 'default';
  28927. }
  28928. },
  28929. getDragType: function(x) {
  28930. var me = this,
  28931. t = me.getTolerance(),
  28932. width = me.overlaySurface.element.getSize().width,
  28933. rangeMask = me.rangeMask,
  28934. min = width * rangeMask.attr.min,
  28935. max = width * rangeMask.attr.max,
  28936. dragType;
  28937. if (x > min + t && x < max - t) {
  28938. dragType = 'pan';
  28939. } else if (x <= min + t && x > min - t) {
  28940. dragType = 'min';
  28941. } else if (x >= max - t && x < max + t) {
  28942. dragType = 'max';
  28943. }
  28944. return dragType;
  28945. },
  28946. onDragStart: function(e) {
  28947. var me = this,
  28948. x, dragType;
  28949. // Limit drags to single touch.
  28950. if (me.dragType || e && e.touches && e.touches.length > 1) {
  28951. return;
  28952. }
  28953. x = e.touches[0].pageX - me.overlaySurface.element.getXY()[0];
  28954. dragType = me.getDragType(x);
  28955. me.rangeMask.attr.thumbOpacity = 1;
  28956. if (dragType) {
  28957. me.dragType = dragType;
  28958. me.touchId = e.touches[0].identifier;
  28959. me.dragX = x;
  28960. }
  28961. },
  28962. onDrag: function(e) {
  28963. if (e.touch.identifier !== this.touchId) {
  28964. return;
  28965. }
  28966. // eslint-disable-next-line vars-on-top
  28967. var me = this,
  28968. overlayEl = me.overlaySurface.element,
  28969. width = overlayEl.getSize().width,
  28970. x = e.touches[0].pageX - overlayEl.getXY()[0],
  28971. thumbGap = me.getThumbGap() / width,
  28972. rangeMask = me.rangeMask,
  28973. min = rangeMask.attr.min,
  28974. max = rangeMask.attr.max,
  28975. delta = max - min,
  28976. dragType = me.dragType,
  28977. drag = me.dragX,
  28978. dx = (x - drag) / width;
  28979. if (dragType === 'pan') {
  28980. min += dx;
  28981. max += dx;
  28982. if (min < 0) {
  28983. min = 0;
  28984. max = delta;
  28985. }
  28986. if (max > 1) {
  28987. max = 1;
  28988. min = max - delta;
  28989. }
  28990. } else if (dragType === 'min') {
  28991. min += dx;
  28992. if (min < 0) {
  28993. min = 0;
  28994. }
  28995. if (min > max - thumbGap) {
  28996. min = max - thumbGap;
  28997. }
  28998. } else if (dragType === 'max') {
  28999. max += dx;
  29000. if (max > 1) {
  29001. max = 1;
  29002. }
  29003. if (max < min + thumbGap) {
  29004. max = min + thumbGap;
  29005. }
  29006. } else {
  29007. return;
  29008. }
  29009. me.dragX = x;
  29010. me.setVisibleRange([
  29011. min,
  29012. max
  29013. ]);
  29014. },
  29015. onDragEnd: function() {
  29016. var me = this,
  29017. autoHideThumbs = me.getAutoHideThumbs();
  29018. me.dragType = null;
  29019. if (autoHideThumbs) {
  29020. me.rangeMask.setAttributes({
  29021. thumbOpacity: 0
  29022. });
  29023. }
  29024. },
  29025. updateMinimum: function(mininum) {
  29026. if (!this.isConfiguring) {
  29027. this.setVisibleRange([
  29028. mininum,
  29029. this.getMaximum()
  29030. ]);
  29031. }
  29032. },
  29033. updateMaximum: function(maximum) {
  29034. if (!this.isConfiguring) {
  29035. this.setVisibleRange([
  29036. this.getMinimum(),
  29037. maximum
  29038. ]);
  29039. }
  29040. },
  29041. getMinimum: function() {
  29042. return this.rangeMask.attr.min;
  29043. },
  29044. getMaximum: function() {
  29045. return this.rangeMask.attr.max;
  29046. },
  29047. setVisibleRange: function(visibleRange) {
  29048. var me = this,
  29049. chart = me.chart;
  29050. me.axis.setVisibleRange(visibleRange);
  29051. me.rangeMask.setAttributes({
  29052. min: visibleRange[0],
  29053. max: visibleRange[1]
  29054. });
  29055. me.getSurface('overlay').renderFrame();
  29056. chart.suspendAnimation();
  29057. chart.redraw();
  29058. chart.resumeAnimation();
  29059. },
  29060. afterBoundChartLayout: function() {
  29061. var me = this,
  29062. spanSeries = me.getSpan() === 'series',
  29063. mainRect = me.chart.getMainRect(),
  29064. size = me.element.getSize();
  29065. if (mainRect && spanSeries) {
  29066. me.setInsetPadding({
  29067. left: mainRect[0],
  29068. right: size.width - mainRect[2] - mainRect[0],
  29069. top: 0,
  29070. bottom: 0
  29071. });
  29072. me.performLayout();
  29073. }
  29074. },
  29075. afterChartLayout: function() {
  29076. var me = this,
  29077. size = me.overlaySurface.element.getSize();
  29078. me.rangeMask.setAttributes({
  29079. scalingCenterX: 0,
  29080. scalingCenterY: 0,
  29081. scalingX: size.width,
  29082. scalingY: size.height
  29083. });
  29084. },
  29085. doDestroy: function() {
  29086. var chart = this.chart;
  29087. if (chart && !chart.destroyed) {
  29088. chart.un('layout', 'afterBoundChartLayout', this);
  29089. }
  29090. this.callParent();
  29091. }
  29092. });
  29093. /**
  29094. * The Navigator Container is a component used to lay out the chart and its
  29095. * {@link Ext.chart.navigator.Navigator navigator}, where the navigator is docked
  29096. * to the top/bottom, and the chart fills the rest of the container's space.
  29097. *
  29098. * For example:
  29099. *
  29100. * @example
  29101. * Ext.create({
  29102. * xtype: 'chartnavigator',
  29103. * renderTo: Ext.getBody(),
  29104. * width: 600,
  29105. * height: 400,
  29106. *
  29107. * chart: {
  29108. * xtype: 'cartesian',
  29109. *
  29110. * store: {
  29111. * data: (function () {
  29112. * var data = [];
  29113. * for (var i = 0; i < 360; i++) {
  29114. * data.push({
  29115. * x: i,
  29116. * y: Math.sin(i / 45 * Math.PI)
  29117. * });
  29118. * }
  29119. * return data;
  29120. * })()
  29121. * },
  29122. * axes: [
  29123. * {
  29124. * id: 'navigable-axis',
  29125. *
  29126. * type: 'numeric',
  29127. * position: 'bottom'
  29128. * },
  29129. * {
  29130. * type: 'numeric',
  29131. * position: 'left'
  29132. * }
  29133. * ],
  29134. * series: {
  29135. * type: 'line',
  29136. * xField: 'x',
  29137. * yField: 'y'
  29138. * }
  29139. * },
  29140. *
  29141. * navigator: {
  29142. * axis: 'navigable-axis'
  29143. * }
  29144. * });
  29145. *
  29146. */
  29147. Ext.define('Ext.chart.navigator.Container', {
  29148. // We are interested in the docking functionality that's available in
  29149. // the Container in Modern and in the Panel in Classic.
  29150. extend: 'Ext.chart.navigator.ContainerBase',
  29151. requires: [
  29152. 'Ext.chart.CartesianChart',
  29153. 'Ext.chart.navigator.Navigator'
  29154. ],
  29155. xtype: 'chartnavigator',
  29156. config: {
  29157. /**
  29158. * @cfg {Ext.chart.CartesianChart} chart
  29159. * The chart to make navigable.
  29160. */
  29161. chart: null,
  29162. /**
  29163. * @cfg {Ext.chart.navigator.Navigator} navigator
  29164. */
  29165. navigator: {}
  29166. },
  29167. layout: 'fit',
  29168. applyChart: function(chart, oldChart) {
  29169. if (oldChart) {
  29170. oldChart.destroy();
  29171. }
  29172. if (chart) {
  29173. if (chart.isCartesian) {
  29174. Ext.raise('Only cartesian charts are supported.');
  29175. }
  29176. if (!chart.isChart) {
  29177. chart.$initParent = this;
  29178. chart = new Ext.chart.CartesianChart(chart);
  29179. delete chart.$initParent;
  29180. }
  29181. }
  29182. return chart;
  29183. },
  29184. legendStore: null,
  29185. surfaceRects: null,
  29186. updateChart: function(chart, oldChart) {
  29187. var me = this;
  29188. if (chart) {
  29189. me.legendStore = chart.getLegendStore();
  29190. if (!me.items && me.initItems) {
  29191. me.initItems();
  29192. }
  29193. me.add(chart);
  29194. }
  29195. },
  29196. applyNavigator: function(navigator, oldNavigator) {
  29197. var instance;
  29198. if (oldNavigator) {
  29199. oldNavigator.destroy();
  29200. }
  29201. if (navigator) {
  29202. navigator.navigatorContainer = navigator.parent = this;
  29203. instance = new Ext.chart.navigator.Navigator(navigator);
  29204. }
  29205. return instance;
  29206. },
  29207. preview: function() {
  29208. this.getNavigator().preview(this.getImage());
  29209. },
  29210. download: function(config) {
  29211. config = config || {};
  29212. config.data = this.getImage().data;
  29213. this.getNavigator().download(config);
  29214. },
  29215. setVisibleRange: function(visibleRange) {
  29216. this.getNavigator().setVisibleRange(visibleRange);
  29217. },
  29218. getImage: function(format) {
  29219. var me = this,
  29220. chart = me.getChart(),
  29221. navigator = me.getNavigator(),
  29222. docked = navigator.getDocked(),
  29223. chartImageSize = chart.bodyElement.getSize(),
  29224. navigatorImageSize = navigator.bodyElement.getSize(),
  29225. chartSurfaces = chart.getSurfaces(true),
  29226. navigatorSurfaces = navigator.getSurfaces(true),
  29227. size = {
  29228. width: chartImageSize.width,
  29229. height: chartImageSize.height + navigatorImageSize.height
  29230. },
  29231. image, imageElement, surfaces, surface;
  29232. if (docked === 'top') {
  29233. me.shiftSurfaces(chartSurfaces, 0, navigatorImageSize.height);
  29234. } else {
  29235. me.shiftSurfaces(navigatorSurfaces, 0, chartImageSize.height);
  29236. }
  29237. surfaces = chartSurfaces.concat(navigatorSurfaces);
  29238. surface = surfaces[0];
  29239. if ((Ext.isIE || Ext.isEdge) && surface.isSVG) {
  29240. // SVG data URLs don't work in IE/Edge as a source for an 'img' element,
  29241. // so we need to render SVG the usual way.
  29242. image = {
  29243. data: surface.toSVG(size, surfaces),
  29244. type: 'svg-markup'
  29245. };
  29246. } else {
  29247. image = surface.flatten(size, surfaces);
  29248. if (format === 'image') {
  29249. imageElement = new Image();
  29250. imageElement.src = image.data;
  29251. image.data = imageElement;
  29252. return image;
  29253. }
  29254. if (format === 'stream') {
  29255. image.data = image.data.replace(/^data:image\/[^;]+/, 'data:application/octet-stream');
  29256. return image;
  29257. }
  29258. }
  29259. me.unshiftSurfaces(surfaces);
  29260. return image;
  29261. },
  29262. shiftSurfaces: function(surfaces, x, y) {
  29263. var ln = surfaces.length,
  29264. i = 0,
  29265. surface;
  29266. this.surfaceRects = {};
  29267. for (; i < ln; i++) {
  29268. surface = surfaces[i];
  29269. this.shiftSurface(surface, x, y);
  29270. }
  29271. },
  29272. shiftSurface: function(surface, x, y) {
  29273. var rect = surface.getRect();
  29274. this.surfaceRects[surface.getId()] = rect.slice();
  29275. rect[0] += x;
  29276. rect[1] += y;
  29277. },
  29278. unshiftSurfaces: function(surfaces) {
  29279. var rects = this.surfaceRects,
  29280. ln = surfaces.length,
  29281. i = 0,
  29282. surface, rect, oldRect;
  29283. if (rects) {
  29284. for (; i < ln; i++) {
  29285. surface = surfaces[i];
  29286. rect = surface.getRect();
  29287. oldRect = rects[surface.getId()];
  29288. if (oldRect) {
  29289. rect[0] = oldRect[0];
  29290. rect[1] = oldRect[1];
  29291. }
  29292. }
  29293. }
  29294. this.surfaceRects = null;
  29295. }
  29296. });
  29297. /**
  29298. * A chart {@link Ext.AbstractPlugin plugin} that adds ability to listen to chart series
  29299. * items events. Item event listeners are passed two parameters: the target item and the
  29300. * event itself. The item object has the following properties:
  29301. *
  29302. * * **category** - the category the item falls under: 'items' or 'markers'
  29303. * * **field** - the store field used by this series item
  29304. * * **index** - the index of the series item
  29305. * * **record** - the store record associated with this series item
  29306. * * **series** - the series the item belongs to
  29307. * * **sprite** - the sprite used to represents this series item
  29308. *
  29309. * For example:
  29310. *
  29311. * Ext.create('Ext.chart.CartesianChart', {
  29312. * plugins: {
  29313. * chartitemevents: {
  29314. * moveEvents: true
  29315. * }
  29316. * },
  29317. * store: {
  29318. * fields: ['pet', 'households', 'total'],
  29319. * data: [
  29320. * {pet: 'Cats', households: 38, total: 93},
  29321. * {pet: 'Dogs', households: 45, total: 79},
  29322. * {pet: 'Fish', households: 13, total: 171}
  29323. * ]
  29324. * },
  29325. * axes: [{
  29326. * type: 'numeric',
  29327. * position: 'left'
  29328. * }, {
  29329. * type: 'category',
  29330. * position: 'bottom'
  29331. * }],
  29332. * series: [{
  29333. * type: 'bar',
  29334. * xField: 'pet',
  29335. * yField: 'households',
  29336. * listeners: {
  29337. * itemmousemove: function (series, item, event) {
  29338. * console.log('itemmousemove', item.category, item.field);
  29339. * }
  29340. * }
  29341. * }, {
  29342. * type: 'line',
  29343. * xField: 'pet',
  29344. * yField: 'total',
  29345. * marker: true
  29346. * }],
  29347. * listeners: { // Listen to itemclick events on all series.
  29348. * itemclick: function (chart, item, event) {
  29349. * console.log('itemclick', item.category, item.field);
  29350. * }
  29351. * }
  29352. * });
  29353. *
  29354. */
  29355. Ext.define('Ext.chart.plugin.ItemEvents', {
  29356. extend: 'Ext.plugin.Abstract',
  29357. alias: 'plugin.chartitemevents',
  29358. /**
  29359. * @cfg {Boolean} [moveEvents=false]
  29360. * If `itemmousemove`, `itemmouseover` or `itemmouseout` event listeners are attached
  29361. * to the chart, the plugin will detect those and will hit test series items on
  29362. * every move. However, if the above item events are attached on the series level
  29363. * only, this config has to be set to true, as the plugin won't perform a similar
  29364. * detection on every series.
  29365. */
  29366. moveEvents: false,
  29367. mouseMoveEvents: {
  29368. mousemove: true,
  29369. mouseover: true,
  29370. mouseout: true
  29371. },
  29372. itemMouseMoveEvents: {
  29373. itemmousemove: true,
  29374. itemmouseover: true,
  29375. itemmouseout: true
  29376. },
  29377. init: function(chart) {
  29378. var handleEvent = 'handleEvent';
  29379. this.chart = chart;
  29380. chart.addElementListener({
  29381. click: handleEvent,
  29382. tap: handleEvent,
  29383. dblclick: handleEvent,
  29384. mousedown: handleEvent,
  29385. mousemove: handleEvent,
  29386. mouseup: handleEvent,
  29387. mouseover: handleEvent,
  29388. mouseout: handleEvent,
  29389. // run our handlers before user code
  29390. priority: 1001,
  29391. scope: this
  29392. });
  29393. },
  29394. hasItemMouseMoveListeners: function() {
  29395. var listeners = this.chart.hasListeners,
  29396. name;
  29397. for (name in this.itemMouseMoveEvents) {
  29398. if (name in listeners) {
  29399. return true;
  29400. }
  29401. }
  29402. return false;
  29403. },
  29404. handleEvent: function(e) {
  29405. var me = this,
  29406. chart = me.chart,
  29407. isMouseMoveEvent = e.type in me.mouseMoveEvents,
  29408. lastItem = me.lastItem,
  29409. chartXY, item;
  29410. if (isMouseMoveEvent && !me.hasItemMouseMoveListeners() && !me.moveEvents) {
  29411. return;
  29412. }
  29413. chartXY = chart.getEventXY(e);
  29414. item = chart.getItemForPoint(chartXY[0], chartXY[1]);
  29415. if (isMouseMoveEvent && !Ext.Object.equals(item, lastItem)) {
  29416. if (lastItem) {
  29417. chart.fireEvent('itemmouseout', chart, lastItem, e);
  29418. lastItem.series.fireEvent('itemmouseout', lastItem.series, lastItem, e);
  29419. }
  29420. if (item) {
  29421. chart.fireEvent('itemmouseover', chart, item, e);
  29422. item.series.fireEvent('itemmouseover', item.series, item, e);
  29423. }
  29424. }
  29425. if (item) {
  29426. chart.fireEvent('item' + e.type, chart, item, e);
  29427. item.series.fireEvent('item' + e.type, item.series, item, e);
  29428. }
  29429. me.lastItem = item;
  29430. }
  29431. });
  29432. /**
  29433. * @abstract
  29434. * @class Ext.chart.series.Cartesian
  29435. * @extends Ext.chart.series.Series
  29436. *
  29437. * Common base class for series implementations that plot values using cartesian coordinates.
  29438. *
  29439. * @constructor
  29440. */
  29441. Ext.define('Ext.chart.series.Cartesian', {
  29442. extend: 'Ext.chart.series.Series',
  29443. config: {
  29444. /**
  29445. * @cfg {String} xField
  29446. * The field used to access the x axis value from the items from the data source.
  29447. */
  29448. xField: null,
  29449. /**
  29450. * @cfg {String|String[]} yField
  29451. * The field(s) used to access the y-axis value(s) of the items from the data source.
  29452. */
  29453. yField: null,
  29454. /**
  29455. * @cfg {Ext.chart.axis.Axis|Number|String}
  29456. * xAxis The chart axis the series is bound to in the 'X' direction.
  29457. * Normally, this would be set automatically by the series.
  29458. * For charts with multiple x-axes, this defines which x-axis is used by the series.
  29459. * It refers to either axis' ID or the (zero-based) index of the axis
  29460. * in the chart's {@link Ext.chart.AbstractChart#axes axes} config.
  29461. */
  29462. xAxis: null,
  29463. /**
  29464. * @cfg {Ext.chart.axis.Axis|Number|String}
  29465. * yAxis The chart axis the series is bound to in the 'Y' direction.
  29466. * Normally, this would be set automatically by the series.
  29467. * For charts with multiple y-axes, this defines which y-axis is used by the series.
  29468. * It refers to either axis' ID or the (zero-based) index of the axis
  29469. * in the chart's {@link Ext.chart.AbstractChart#axes axes} config.
  29470. */
  29471. yAxis: null
  29472. },
  29473. directions: [
  29474. 'X',
  29475. 'Y'
  29476. ],
  29477. /**
  29478. * @private
  29479. *
  29480. * Tells which store record fields should be used for a specific axis direction. E.g. for
  29481. *
  29482. * fieldCategory<direction>: ['<fieldConfig1>', '<fieldConfig2>', ...]
  29483. *
  29484. * the field names from the following configs will be used:
  29485. *
  29486. * series.<fieldConfig1>Field, series.<fieldConfig2>Field, ...
  29487. *
  29488. * See {@link Ext.chart.series.StackedCartesian#getFields}.
  29489. *
  29490. */
  29491. fieldCategoryX: [
  29492. 'X'
  29493. ],
  29494. fieldCategoryY: [
  29495. 'Y'
  29496. ],
  29497. applyXAxis: function(newAxis, oldAxis) {
  29498. return this.getChart().getAxis(newAxis) || oldAxis;
  29499. },
  29500. applyYAxis: function(newAxis, oldAxis) {
  29501. return this.getChart().getAxis(newAxis) || oldAxis;
  29502. },
  29503. updateXAxis: function(axis) {
  29504. axis.processData(this);
  29505. },
  29506. updateYAxis: function(axis) {
  29507. axis.processData(this);
  29508. },
  29509. coordinateX: function() {
  29510. return this.coordinate('X', 0, 2);
  29511. },
  29512. coordinateY: function() {
  29513. return this.coordinate('Y', 1, 2);
  29514. },
  29515. getItemForPoint: function(x, y) {
  29516. var me = this,
  29517. sprite = me.getSprites()[0],
  29518. store = me.getStore(),
  29519. point;
  29520. if (sprite && !me.getHidden()) {
  29521. point = sprite.getNearestDataPoint(x, y);
  29522. }
  29523. return point ? {
  29524. series: me,
  29525. sprite: sprite,
  29526. category: me.getItemInstancing() ? 'items' : 'markers',
  29527. index: point.index,
  29528. record: store.getData().items[point.index],
  29529. field: me.getYField(),
  29530. distance: point.distance
  29531. } : null;
  29532. },
  29533. createSprite: function() {
  29534. var me = this,
  29535. sprite = me.callParent(),
  29536. chart = me.getChart(),
  29537. xAxis = me.getXAxis();
  29538. sprite.setAttributes({
  29539. flipXY: chart.getFlipXY(),
  29540. xAxis: xAxis
  29541. });
  29542. if (sprite.setAggregator && xAxis && xAxis.getAggregator) {
  29543. if (xAxis.getAggregator) {
  29544. sprite.setAggregator({
  29545. strategy: xAxis.getAggregator()
  29546. });
  29547. } else {
  29548. sprite.setAggregator({});
  29549. }
  29550. }
  29551. return sprite;
  29552. },
  29553. getSprites: function() {
  29554. var me = this,
  29555. chart = this.getChart(),
  29556. sprites = me.sprites;
  29557. if (!chart) {
  29558. return Ext.emptyArray;
  29559. }
  29560. if (!sprites.length) {
  29561. me.createSprite();
  29562. }
  29563. return sprites;
  29564. },
  29565. getXRange: function() {
  29566. return [
  29567. this.dataRange[0],
  29568. this.dataRange[2]
  29569. ];
  29570. },
  29571. getYRange: function() {
  29572. return [
  29573. this.dataRange[1],
  29574. this.dataRange[3]
  29575. ];
  29576. }
  29577. });
  29578. /**
  29579. * @abstract
  29580. * @extends Ext.chart.series.Cartesian
  29581. * Abstract class for all the stacked cartesian series including area series
  29582. * and bar series.
  29583. */
  29584. Ext.define('Ext.chart.series.StackedCartesian', {
  29585. extend: 'Ext.chart.series.Cartesian',
  29586. config: {
  29587. /**
  29588. * @cfg {Boolean} [stacked=true]
  29589. * `true` to display the series in its stacked configuration.
  29590. */
  29591. stacked: true,
  29592. /**
  29593. * @cfg {Boolean} [splitStacks=true]
  29594. * `true` to stack negative/positive values in respective y-axis directions.
  29595. */
  29596. splitStacks: true,
  29597. /**
  29598. * @cfg {Boolean} [fullStack=false]
  29599. * If `true`, the height of a stacked bar is always the full height of the chart,
  29600. * with individual components viewed as shares of the whole determined by the
  29601. * {@link #fullStackTotal} config.
  29602. */
  29603. fullStack: false,
  29604. /**
  29605. * @cfg {Boolean} [fullStackTotal=100]
  29606. * If the {@link #fullStack} config is set to `true`, this will determine
  29607. * the absolute total value of each stack.
  29608. */
  29609. fullStackTotal: 100,
  29610. /**
  29611. * @cfg {Array} hidden
  29612. */
  29613. hidden: []
  29614. },
  29615. /**
  29616. * @private
  29617. * @property
  29618. * If `true`, each subsequent sprite has a lower zIndex so that the stroke of previous
  29619. * sprite in the stack is not covered by the next sprite (which makes the very top
  29620. * segment look odd in flat bar and area series, especially when wide strokes are used).
  29621. */
  29622. reversedSpriteZOrder: true,
  29623. spriteAnimationCount: 0,
  29624. themeColorCount: function() {
  29625. var me = this,
  29626. yField = me.getYField();
  29627. return Ext.isArray(yField) ? yField.length : 1;
  29628. },
  29629. updateStacked: function() {
  29630. this.processData();
  29631. },
  29632. updateSplitStacks: function() {
  29633. this.processData();
  29634. },
  29635. coordinateY: function() {
  29636. return this.coordinateStacked('Y', 1, 2);
  29637. },
  29638. coordinateStacked: function(direction, directionOffset, directionCount) {
  29639. var me = this,
  29640. store = me.getStore(),
  29641. items = store.getData().items,
  29642. itemCount = items.length,
  29643. axis = me['get' + direction + 'Axis'](),
  29644. hidden = me.getHidden(),
  29645. splitStacks = me.getSplitStacks(),
  29646. fullStack = me.getFullStack(),
  29647. fullStackTotal = me.getFullStackTotal(),
  29648. range = [
  29649. 0,
  29650. 0
  29651. ],
  29652. directions = me['fieldCategory' + direction],
  29653. dataStart = [],
  29654. posDataStart = [],
  29655. negDataStart = [],
  29656. dataEnd,
  29657. stacked = me.getStacked(),
  29658. sprites = me.getSprites(),
  29659. coordinatedData = [],
  29660. i, j, k, fields, fieldCount, posTotals, negTotals, fieldCategoriesItem, data, attr;
  29661. if (!sprites.length) {
  29662. return;
  29663. }
  29664. for (i = 0; i < directions.length; i++) {
  29665. fieldCategoriesItem = directions[i];
  29666. fields = me.getFields([
  29667. fieldCategoriesItem
  29668. ]);
  29669. fieldCount = fields.length;
  29670. for (j = 0; j < itemCount; j++) {
  29671. dataStart[j] = 0;
  29672. posDataStart[j] = 0;
  29673. negDataStart[j] = 0;
  29674. }
  29675. for (j = 0; j < fieldCount; j++) {
  29676. if (!hidden[j]) {
  29677. coordinatedData[j] = me.coordinateData(items, fields[j], axis);
  29678. }
  29679. }
  29680. if (stacked && fullStack) {
  29681. posTotals = [];
  29682. if (splitStacks) {
  29683. negTotals = [];
  29684. }
  29685. for (j = 0; j < itemCount; j++) {
  29686. posTotals[j] = 0;
  29687. if (splitStacks) {
  29688. negTotals[j] = 0;
  29689. }
  29690. for (k = 0; k < fieldCount; k++) {
  29691. data = coordinatedData[k];
  29692. if (!data) {
  29693. // If the field is hidden there's no coordinated data for it.
  29694. continue;
  29695. }
  29696. data = data[j];
  29697. if (data >= 0 || !splitStacks) {
  29698. posTotals[j] += data;
  29699. } else if (data < 0) {
  29700. negTotals[j] += data;
  29701. }
  29702. }
  29703. }
  29704. }
  29705. // else not a valid number
  29706. for (j = 0; j < fieldCount; j++) {
  29707. attr = {};
  29708. if (hidden[j]) {
  29709. attr['dataStart' + fieldCategoriesItem] = dataStart;
  29710. attr['data' + fieldCategoriesItem] = dataStart;
  29711. sprites[j].setAttributes(attr);
  29712. continue;
  29713. }
  29714. data = coordinatedData[j];
  29715. if (stacked) {
  29716. dataEnd = [];
  29717. for (k = 0; k < itemCount; k++) {
  29718. if (!data[k]) {
  29719. data[k] = 0;
  29720. }
  29721. if (data[k] >= 0 || !splitStacks) {
  29722. if (fullStack && posTotals[k]) {
  29723. data[k] *= fullStackTotal / posTotals[k];
  29724. }
  29725. dataStart[k] = posDataStart[k];
  29726. posDataStart[k] += data[k];
  29727. dataEnd[k] = posDataStart[k];
  29728. } else {
  29729. if (fullStack && negTotals[k]) {
  29730. data[k] *= fullStackTotal / negTotals[k];
  29731. }
  29732. dataStart[k] = negDataStart[k];
  29733. negDataStart[k] += data[k];
  29734. dataEnd[k] = negDataStart[k];
  29735. }
  29736. }
  29737. attr['dataStart' + fieldCategoriesItem] = dataStart;
  29738. attr['data' + fieldCategoriesItem] = dataEnd;
  29739. Ext.chart.Util.expandRange(range, dataStart);
  29740. Ext.chart.Util.expandRange(range, dataEnd);
  29741. } else {
  29742. attr['dataStart' + fieldCategoriesItem] = dataStart;
  29743. attr['data' + fieldCategoriesItem] = data;
  29744. Ext.chart.Util.expandRange(range, data);
  29745. }
  29746. sprites[j].setAttributes(attr);
  29747. }
  29748. }
  29749. range = Ext.chart.Util.validateRange(range, me.defaultRange);
  29750. me.dataRange[directionOffset] = range[0];
  29751. me.dataRange[directionOffset + directionCount] = range[1];
  29752. attr = {};
  29753. attr['dataMin' + direction] = range[0];
  29754. attr['dataMax' + direction] = range[1];
  29755. for (i = 0; i < sprites.length; i++) {
  29756. sprites[i].setAttributes(attr);
  29757. }
  29758. },
  29759. getFields: function(fieldCategory) {
  29760. var me = this,
  29761. fields = [],
  29762. ln = fieldCategory.length,
  29763. i, fieldsItem;
  29764. for (i = 0; i < ln; i++) {
  29765. fieldsItem = me['get' + fieldCategory[i] + 'Field']();
  29766. if (Ext.isArray(fieldsItem)) {
  29767. fields.push.apply(fields, fieldsItem);
  29768. } else {
  29769. fields.push(fieldsItem);
  29770. }
  29771. }
  29772. return fields;
  29773. },
  29774. updateLabelOverflowPadding: function(labelOverflowPadding) {
  29775. var me = this,
  29776. label;
  29777. if (!me.isConfiguring) {
  29778. label = me.getLabel();
  29779. if (label) {
  29780. label.setAttributes({
  29781. labelOverflowPadding: labelOverflowPadding
  29782. });
  29783. }
  29784. }
  29785. },
  29786. updateLabelData: function() {
  29787. var me = this,
  29788. label = me.getLabel();
  29789. if (label) {
  29790. label.setAttributes({
  29791. labelOverflowPadding: me.getLabelOverflowPadding()
  29792. });
  29793. }
  29794. me.callParent();
  29795. },
  29796. getSprites: function() {
  29797. var me = this,
  29798. chart = me.getChart(),
  29799. fields = me.getFields(me.fieldCategoryY),
  29800. itemInstancing = me.getItemInstancing(),
  29801. sprites = me.sprites,
  29802. hidden = me.getHidden(),
  29803. spritesCreated = false,
  29804. fieldCount = fields.length,
  29805. i, sprite;
  29806. if (!chart) {
  29807. return [];
  29808. }
  29809. // Create one Ext.chart.series.sprite.StackedCartesian sprite per field.
  29810. for (i = 0; i < fieldCount; i++) {
  29811. sprite = sprites[i];
  29812. if (!sprite) {
  29813. sprite = me.createSprite();
  29814. sprite.setAttributes({
  29815. zIndex: (me.reversedSpriteZOrder ? -1 : 1) * i
  29816. });
  29817. sprite.setField(fields[i]);
  29818. spritesCreated = true;
  29819. hidden.push(false);
  29820. if (itemInstancing) {
  29821. sprite.getMarker('items').getTemplate().setAttributes(me.getStyleByIndex(i));
  29822. } else {
  29823. sprite.setAttributes(me.getStyleByIndex(i));
  29824. }
  29825. }
  29826. }
  29827. if (spritesCreated) {
  29828. me.updateHidden(hidden);
  29829. }
  29830. return sprites;
  29831. },
  29832. getItemForPoint: function(x, y) {
  29833. var me = this,
  29834. sprites = me.getSprites(),
  29835. store = me.getStore(),
  29836. hidden = me.getHidden(),
  29837. minDistance = Infinity,
  29838. item = null,
  29839. spriteIndex = -1,
  29840. pointIndex = -1,
  29841. point, yField, sprite, i, ln;
  29842. for (i = 0 , ln = sprites.length; i < ln; i++) {
  29843. if (hidden[i]) {
  29844. continue;
  29845. }
  29846. sprite = sprites[i];
  29847. point = sprite.getNearestDataPoint(x, y);
  29848. // Don't stop when the first matching point is found.
  29849. // Keep looking for the nearest point.
  29850. if (point) {
  29851. if (point.distance < minDistance) {
  29852. minDistance = point.distance;
  29853. pointIndex = point.index;
  29854. spriteIndex = i;
  29855. }
  29856. }
  29857. }
  29858. if (spriteIndex > -1) {
  29859. yField = me.getYField();
  29860. item = {
  29861. series: me,
  29862. sprite: sprites[spriteIndex],
  29863. category: me.getItemInstancing() ? 'items' : 'markers',
  29864. index: pointIndex,
  29865. record: store.getData().items[pointIndex],
  29866. // Handle the case where we're stacked but a single segment
  29867. field: typeof yField === 'string' ? yField : yField[spriteIndex],
  29868. distance: minDistance
  29869. };
  29870. }
  29871. return item;
  29872. },
  29873. provideLegendInfo: function(target) {
  29874. var me = this,
  29875. sprites = me.getSprites(),
  29876. title = me.getTitle(),
  29877. field = me.getYField(),
  29878. hidden = me.getHidden(),
  29879. single = sprites.length === 1,
  29880. style, fill, i, name;
  29881. for (i = 0; i < sprites.length; i++) {
  29882. style = me.getStyleByIndex(i);
  29883. fill = style.fillStyle;
  29884. if (title) {
  29885. if (Ext.isArray(title)) {
  29886. name = title[i];
  29887. } else if (single) {
  29888. name = title;
  29889. }
  29890. }
  29891. if (!title || !name) {
  29892. if (Ext.isArray(field)) {
  29893. name = field[i];
  29894. } else {
  29895. name = me.getId();
  29896. }
  29897. }
  29898. target.push({
  29899. name: name,
  29900. mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
  29901. disabled: hidden[i],
  29902. series: me.getId(),
  29903. index: i
  29904. });
  29905. }
  29906. },
  29907. onSpriteAnimationStart: function(sprite) {
  29908. this.spriteAnimationCount++;
  29909. if (this.spriteAnimationCount === 1) {
  29910. this.fireEvent('animationstart');
  29911. }
  29912. },
  29913. onSpriteAnimationEnd: function(sprite) {
  29914. this.spriteAnimationCount--;
  29915. if (this.spriteAnimationCount === 0) {
  29916. this.fireEvent('animationend');
  29917. }
  29918. }
  29919. });
  29920. /**
  29921. * Base class for all series sprites.
  29922. * Defines attributes common to all series sprites, like data in x/y directions and its
  29923. * min/max values, and configs, like the {@link Ext.chart.series.Series} instance that manages
  29924. * the sprite.
  29925. *
  29926. */
  29927. Ext.define('Ext.chart.series.sprite.Series', {
  29928. extend: 'Ext.draw.sprite.Sprite',
  29929. mixins: {
  29930. markerHolder: 'Ext.chart.MarkerHolder'
  29931. },
  29932. inheritableStatics: {
  29933. def: {
  29934. processors: {
  29935. /**
  29936. * @cfg {Number} [dataMinX=0] Data minimum on the x-axis.
  29937. */
  29938. dataMinX: 'number',
  29939. /**
  29940. * @cfg {Number} [dataMaxX=1] Data maximum on the x-axis.
  29941. */
  29942. dataMaxX: 'number',
  29943. /**
  29944. * @cfg {Number} [dataMinY=0] Data minimum on the y-axis.
  29945. */
  29946. dataMinY: 'number',
  29947. /**
  29948. * @cfg {Number} [dataMaxY=1] Data maximum on the y-axis.
  29949. */
  29950. dataMaxY: 'number',
  29951. /**
  29952. * @cfg {Array} [rangeX=null] Data range derived from all the series bound
  29953. * to the x-axis.
  29954. */
  29955. rangeX: 'data',
  29956. /**
  29957. * @cfg {Array} [rangeY=null] Data range derived from all the series bound
  29958. * to the y-axis.
  29959. */
  29960. rangeY: 'data',
  29961. /**
  29962. * @cfg {Object} [dataX=null] Data items on the x-axis.
  29963. */
  29964. dataX: 'data',
  29965. /**
  29966. * @cfg {Object} [dataY=null] Data items on the y-axis.
  29967. */
  29968. dataY: 'data',
  29969. /**
  29970. * @cfg {Object} [labels=null] Labels used in the series.
  29971. */
  29972. labels: 'default',
  29973. /**
  29974. * @cfg {Number} [labelOverflowPadding=10] Padding around labels to determine
  29975. * overlap.
  29976. */
  29977. labelOverflowPadding: 'number'
  29978. },
  29979. defaults: {
  29980. dataMinX: 0,
  29981. dataMaxX: 1,
  29982. dataMinY: 0,
  29983. dataMaxY: 1,
  29984. rangeX: null,
  29985. rangeY: null,
  29986. dataX: null,
  29987. dataY: null,
  29988. labels: null,
  29989. labelOverflowPadding: 10
  29990. },
  29991. triggers: {
  29992. dataX: 'bbox',
  29993. dataY: 'bbox',
  29994. dataMinX: 'bbox',
  29995. dataMaxX: 'bbox',
  29996. dataMinY: 'bbox',
  29997. dataMaxY: 'bbox'
  29998. }
  29999. }
  30000. },
  30001. config: {
  30002. /**
  30003. * @private
  30004. * @cfg {Object} store The store that is passed to the renderer.
  30005. */
  30006. store: null,
  30007. series: null,
  30008. /**
  30009. * @cfg {String} field The store field used by the series.
  30010. */
  30011. field: null
  30012. }
  30013. });
  30014. /**
  30015. * Cartesian sprite.
  30016. */
  30017. Ext.define('Ext.chart.series.sprite.Cartesian', {
  30018. extend: 'Ext.chart.series.sprite.Series',
  30019. inheritableStatics: {
  30020. def: {
  30021. processors: {
  30022. /**
  30023. * @cfg {Number} [selectionTolerance=20]
  30024. * The distance from the event position to the sprite's data points to trigger
  30025. * interactions (used for 'iteminfo', etc).
  30026. */
  30027. selectionTolerance: 'number',
  30028. /**
  30029. * @cfg {Boolean} flipXY If flipXY is 'true', the series is flipped.
  30030. */
  30031. flipXY: 'bool',
  30032. renderer: 'default',
  30033. // Visible range of data (pan/zoom) information.
  30034. visibleMinX: 'number',
  30035. visibleMinY: 'number',
  30036. visibleMaxX: 'number',
  30037. visibleMaxY: 'number',
  30038. innerWidth: 'number',
  30039. innerHeight: 'number'
  30040. },
  30041. defaults: {
  30042. selectionTolerance: 20,
  30043. flipXY: false,
  30044. renderer: null,
  30045. transformFillStroke: false,
  30046. visibleMinX: 0,
  30047. visibleMinY: 0,
  30048. visibleMaxX: 1,
  30049. visibleMaxY: 1,
  30050. innerWidth: 1,
  30051. innerHeight: 1
  30052. },
  30053. triggers: {
  30054. dataX: 'dataX,bbox',
  30055. dataY: 'dataY,bbox',
  30056. visibleMinX: 'panzoom',
  30057. visibleMinY: 'panzoom',
  30058. visibleMaxX: 'panzoom',
  30059. visibleMaxY: 'panzoom',
  30060. innerWidth: 'panzoom',
  30061. innerHeight: 'panzoom'
  30062. },
  30063. updaters: {
  30064. dataX: function(attr) {
  30065. this.processDataX();
  30066. this.scheduleUpdater(attr, 'dataY', [
  30067. 'dataY'
  30068. ]);
  30069. },
  30070. dataY: function() {
  30071. this.processDataY();
  30072. },
  30073. panzoom: function(attr) {
  30074. // dx, dy are deltas between min & max of coordinated data values.
  30075. var dx = attr.visibleMaxX - attr.visibleMinX,
  30076. dy = attr.visibleMaxY - attr.visibleMinY,
  30077. innerWidth = attr.flipXY ? attr.innerHeight : attr.innerWidth,
  30078. innerHeight = !attr.flipXY ? attr.innerHeight : attr.innerWidth,
  30079. surface = this.getSurface(),
  30080. isRtl = surface ? surface.getInherited().rtl : false;
  30081. attr.scalingCenterX = 0;
  30082. attr.scalingCenterY = 0;
  30083. attr.scalingX = innerWidth / dx;
  30084. attr.scalingY = innerHeight / dy;
  30085. // (attr.visibleMinY * attr.scalingY) will be the vertical position of
  30086. // our minimum data points, which we want to be at zero, so we offset
  30087. // by this amount.
  30088. attr.translationX = -(attr.visibleMinX * attr.scalingX);
  30089. attr.translationY = -(attr.visibleMinY * attr.scalingY);
  30090. if (isRtl && !attr.flipXY) {
  30091. attr.scalingX *= -1;
  30092. attr.translationX *= -1;
  30093. attr.translationX += innerWidth;
  30094. }
  30095. this.applyTransformations(true);
  30096. }
  30097. }
  30098. }
  30099. },
  30100. processDataY: Ext.emptyFn,
  30101. processDataX: Ext.emptyFn,
  30102. updatePlainBBox: function(plain) {
  30103. var attr = this.attr;
  30104. plain.x = attr.dataMinX;
  30105. plain.y = attr.dataMinY;
  30106. plain.width = attr.dataMaxX - attr.dataMinX;
  30107. plain.height = attr.dataMaxY - attr.dataMinY;
  30108. },
  30109. /**
  30110. * Does a binary search of the data on the x-axis using the given key.
  30111. * @param {String} key
  30112. * @return {*}
  30113. */
  30114. binarySearch: function(key) {
  30115. var dx = this.attr.dataX,
  30116. start = 0,
  30117. end = dx.length,
  30118. mid, val;
  30119. if (key <= dx[0]) {
  30120. return start;
  30121. }
  30122. if (key >= dx[end - 1]) {
  30123. return end - 1;
  30124. }
  30125. while (start + 1 < end) {
  30126. mid = (start + end) >> 1;
  30127. val = dx[mid];
  30128. if (val === key) {
  30129. return mid;
  30130. } else if (val < key) {
  30131. start = mid;
  30132. } else {
  30133. end = mid;
  30134. }
  30135. }
  30136. return start;
  30137. },
  30138. render: function(surface, ctx, surfaceClipRect) {
  30139. var me = this,
  30140. attr = me.attr,
  30141. margin = 1,
  30142. // TODO: why do we need it?
  30143. inverseMatrix = attr.inverseMatrix.clone(),
  30144. dataClipRect;
  30145. // The sprite's `attr.matrix` is stretching/shrinking data coordinates
  30146. // to surface coordinates.
  30147. // This matrix is set (indirectly) by the 'panzoom' updater.
  30148. // The sprite's `attr.inverseMatrix` does the opposite.
  30149. //
  30150. // The `surface.matrix` of the 'series' surface of a cartesian chart flips the
  30151. // surface content vertically, so that y=0 is at the bottom (look for
  30152. // `surface.matrix.set` call in the CartesianChart.performLayout method).
  30153. // This matrix is set in the 'performLayout' of the CartesianChart.
  30154. // The `surface.inverseMatrix` flips the content back.
  30155. //
  30156. // By combining the inverse matrices of the series surface and the series sprite,
  30157. // we essentially get a transformation that allows us to go from surface coordinates
  30158. // in a final flipped drawing back to data points.
  30159. //
  30160. // For example
  30161. //
  30162. // inverseMatrix.transformPoint([ 0, rect[3] ])
  30163. // inverseMatrix.transformPoint([ rect[2], 0 ])
  30164. //
  30165. // will return
  30166. //
  30167. // [attr.dataMinX, attr.dataMinY]
  30168. // [attr.dataMaxX, attr.dataMaxY]
  30169. //
  30170. // because left/bottom and top/right of the series surface is where the first smallest
  30171. // and last largest data points would be (given no pan/zoom), respectively.
  30172. //
  30173. // So the `dataClipRect` passed to the `renderClipped` call below is effectively
  30174. // the visible rect in data (not surface!) coordinates.
  30175. // It is important to note, that the all the scaling and translation is defined
  30176. // by the sprite's matrix, the 'series' surface matrix does not contain scaling
  30177. // or translation components, except for the vertical flipping.
  30178. // This is important because there is a common pattern in chart series sprites
  30179. // (MarkerHolders) - instead of using transform attributes for their Markers
  30180. // (e.g. instances of a 'rect' sprite in case of 'bar' series), the attributes
  30181. // that would position a sprite with no transformations are transformed.
  30182. // For example, to draw a rect with coordinates TL(10, 10), BR(20, 40),
  30183. // we could use the folling 'rect' sprite attributes:
  30184. //
  30185. // {
  30186. // x: 0,
  30187. // y: 0
  30188. // width: 10,
  30189. // height: 30
  30190. //
  30191. // translationX: 10,
  30192. // translationY: 10
  30193. //
  30194. // But the correct thing to do here is
  30195. //
  30196. // {
  30197. // x: 10,
  30198. // y: 10,
  30199. // width: 10,
  30200. // height: 30
  30201. // }
  30202. //
  30203. // Similarly, if the sprite was scaled, the 'x', 'y', 'width', 'height' attributes
  30204. // would have to account for that as well.
  30205. //
  30206. // This is done, so that the attribute values a marker gets by the time it renders,
  30207. // are the final values, and are not affected later by other transforms, such as
  30208. // surface matrix scaling, which could ruin the visual result, if the attributes
  30209. // values are doctored to make lines align to the pixel grid (which is typically
  30210. // the case).
  30211. inverseMatrix.appendMatrix(surface.inverseMatrix);
  30212. if (attr.dataX === null || attr.dataX === undefined) {
  30213. return;
  30214. }
  30215. if (attr.dataY === null || attr.dataY === undefined) {
  30216. return;
  30217. }
  30218. if (inverseMatrix.getXX() * inverseMatrix.getYX() || inverseMatrix.getXY() * inverseMatrix.getYY()) {
  30219. Ext.Logger.warn('Cartesian Series sprite does not support rotation/sheering');
  30220. return;
  30221. }
  30222. dataClipRect = inverseMatrix.transformList([
  30223. [
  30224. surfaceClipRect[0] - margin,
  30225. surfaceClipRect[3] + margin
  30226. ],
  30227. // (left, height)
  30228. [
  30229. surfaceClipRect[0] + surfaceClipRect[2] + margin,
  30230. -margin
  30231. ]
  30232. ]);
  30233. // (width, top)
  30234. dataClipRect = dataClipRect[0].concat(dataClipRect[1]);
  30235. // TODO: RTL improvements:
  30236. // TODO: produce such a dataClipRect here, so that we don't have to do:
  30237. // TODO: min = Math.min(dataClipRect[0], dataClipRect[2])
  30238. // TODO: max = Math.max(dataClipRect[0], dataClipRect[2])
  30239. // TODO: inside each 'renderClipped' call
  30240. me.renderClipped(surface, ctx, dataClipRect, surfaceClipRect);
  30241. },
  30242. /**
  30243. * Render the given visible clip range.
  30244. * @param {Ext.draw.Surface} surface A draw container surface.
  30245. * @param {CanvasRenderingContext2D} ctx A context object that is API compatible with the native
  30246. * [CanvasRenderingContext2D](https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D).
  30247. * @param {Number[]} dataClipRect The clip rect in data coordinates, roughly equivalent to
  30248. * [attr.dataMinX, attr.dataMinY, attr.dataMaxX, attr.dataMaxY] for an untranslated/unscaled
  30249. * surface/sprite.
  30250. * @param {Number[]} surfaceClipRect The clip rect in surface coordinates:
  30251. * [left, top, width, height].
  30252. * @method
  30253. */
  30254. renderClipped: Ext.emptyFn,
  30255. /**
  30256. * Get the nearest item index from point (x, y). -1 as not found.
  30257. * @param {Number} x
  30258. * @param {Number} y
  30259. * @return {Number} The index
  30260. * @deprecated 6.5.2 Use {@link #getNearestDataPoint} instead.
  30261. */
  30262. getIndexNearPoint: function(x, y) {
  30263. var result = this.getNearestDataPoint(x, y);
  30264. return result ? result.index : -1;
  30265. },
  30266. /**
  30267. * Given a point in 'series' surface element coordinates, returns the `index` of the
  30268. * sprite's data point that is nearest to that point, along with the `distance`
  30269. * between points.
  30270. * If the `selectionTolerance` attribute of the sprite is not zero, only the data points
  30271. * that are within that pixel distance from the given point will be checked.
  30272. * In the event no such data points exist or the data is empty, `null` is returned.
  30273. *
  30274. * Notes:
  30275. * 1) given a mouse/pointer event object, the surface coordinates of the event can be
  30276. * obtained with the `getEventXY` method of the chart;
  30277. * 2) using `selectionTolerance` of zero is useful for series with no visible markers,
  30278. * such as the Area series, where this attribute becomes meaningless.
  30279. *
  30280. * @param {Number} x
  30281. * @param {Number} y
  30282. * @return {Object}
  30283. */
  30284. getNearestDataPoint: function(x, y) {
  30285. var me = this,
  30286. attr = me.attr,
  30287. series = me.getSeries(),
  30288. surface = me.getSurface(),
  30289. items = me.boundMarkers.items,
  30290. matrix = attr.matrix,
  30291. dataX = attr.dataX,
  30292. dataY = attr.dataY,
  30293. selectionTolerance = attr.selectionTolerance,
  30294. minDistance = Infinity,
  30295. index = -1,
  30296. result = null,
  30297. distance, dx, dy, xy, i, ln, end, inc, bbox;
  30298. // Notes:
  30299. // Instead of converting the given point from surface coordinates to data coordinates
  30300. // and then measuring the distances between it and the data points, we have to
  30301. // convert all the data points to surface coordinates and measure the distances
  30302. // between them and the given point. This is because the data coordinates can use
  30303. // different scales, which makes distance measurement impossible.
  30304. // For example, if the x-axis is a `category` axis, the categories will be assigned
  30305. // indexes starting from 0, that's what the `attr.dataX` array will contain;
  30306. // and if the y-axis is a `numeric` axis, the `attr.dataY` array will simply contain
  30307. // the original values.
  30308. //
  30309. // Either 'items' or 'markers' will be highlighted. If a sprite has both (for example,
  30310. // 'bar' series with the 'marker' config, where the bars are 'items' and marker instances
  30311. // are 'markers'), only the 'items' (bars) will be highlighted.
  30312. if (items) {
  30313. ln = dataX.length;
  30314. if (series.reversedSpriteZOrder) {
  30315. i = ln - 1;
  30316. end = -1;
  30317. inc = -1;
  30318. } else {
  30319. i = 0;
  30320. end = ln;
  30321. inc = 1;
  30322. }
  30323. for (; i !== end; i += inc) {
  30324. bbox = me.getMarkerBBox('items', i);
  30325. // Transform the given surface element coordinates to logical coordinates
  30326. // of the surface (the ones the bbox uses).
  30327. xy = surface.inverseMatrix.transformPoint([
  30328. x,
  30329. y
  30330. ]);
  30331. if (Ext.draw.Draw.isPointInBBox(xy[0], xy[1], bbox)) {
  30332. index = i;
  30333. minDistance = 0;
  30334. // Return the first item that contains our touch point.
  30335. break;
  30336. }
  30337. }
  30338. } else {
  30339. // markers
  30340. for (i = 0 , ln = dataX.length; i < ln; i++) {
  30341. // Convert from data coordinates to coordinates within inner size rectangle.
  30342. // See `panzoom` method for more details.
  30343. xy = matrix.transformPoint([
  30344. dataX[i],
  30345. dataY[i]
  30346. ]);
  30347. // Flip back vertically and padding adjust (see `render` method comments).
  30348. xy = surface.matrix.transformPoint(xy);
  30349. // Essentially sprites go through the same two transformations when they render
  30350. // data points.
  30351. dx = x - xy[0];
  30352. dy = y - xy[1];
  30353. distance = Math.sqrt(dx * dx + dy * dy);
  30354. if (selectionTolerance && distance > selectionTolerance) {
  30355. continue;
  30356. }
  30357. if (distance < minDistance) {
  30358. minDistance = distance;
  30359. index = i;
  30360. }
  30361. }
  30362. }
  30363. // Keep looking for the nearest marker.
  30364. if (index > -1) {
  30365. result = {
  30366. index: index,
  30367. distance: minDistance
  30368. };
  30369. }
  30370. return result;
  30371. }
  30372. });
  30373. /**
  30374. * @class Ext.chart.series.sprite.StackedCartesian
  30375. * @extends Ext.chart.series.sprite.Cartesian
  30376. *
  30377. * Stacked cartesian sprite.
  30378. */
  30379. Ext.define('Ext.chart.series.sprite.StackedCartesian', {
  30380. extend: 'Ext.chart.series.sprite.Cartesian',
  30381. inheritableStatics: {
  30382. def: {
  30383. processors: {
  30384. /**
  30385. * @private
  30386. * @cfg {Number} [groupCount=1] The number of items (e.g. bars) in a group.
  30387. */
  30388. groupCount: 'number',
  30389. /**
  30390. * @private
  30391. * @cfg {Number} [groupOffset=0] The group index of the series sprite.
  30392. */
  30393. groupOffset: 'number',
  30394. /**
  30395. * @private
  30396. * @cfg {Object} [dataStartY=null] The starting point of the data
  30397. * used in the series.
  30398. */
  30399. dataStartY: 'data'
  30400. },
  30401. defaults: {
  30402. selectionTolerance: 20,
  30403. groupCount: 1,
  30404. groupOffset: 0,
  30405. dataStartY: null
  30406. },
  30407. triggers: {
  30408. dataStartY: 'dataY,bbox'
  30409. }
  30410. }
  30411. }
  30412. });
  30413. /**
  30414. * @class Ext.chart.series.sprite.Area
  30415. * @extends Ext.chart.series.sprite.StackedCartesian
  30416. *
  30417. * Area series sprite.
  30418. */
  30419. Ext.define('Ext.chart.series.sprite.Area', {
  30420. alias: 'sprite.areaSeries',
  30421. extend: 'Ext.chart.series.sprite.StackedCartesian',
  30422. inheritableStatics: {
  30423. def: {
  30424. processors: {
  30425. /**
  30426. * @cfg {Boolean} [step=false] 'true' if the area is represented with steps
  30427. * instead of lines.
  30428. */
  30429. step: 'bool'
  30430. },
  30431. defaults: {
  30432. selectionTolerance: 0,
  30433. step: false
  30434. }
  30435. }
  30436. },
  30437. renderClipped: function(surface, ctx, dataClipRect) {
  30438. var me = this,
  30439. store = me.getStore(),
  30440. series = me.getSeries(),
  30441. attr = me.attr,
  30442. dataX = attr.dataX,
  30443. dataY = attr.dataY,
  30444. dataStartY = attr.dataStartY,
  30445. matrix = attr.matrix,
  30446. x, y, i, lastX, lastY, startX, startY,
  30447. xx = matrix.elements[0],
  30448. dx = matrix.elements[4],
  30449. yy = matrix.elements[3],
  30450. dy = matrix.elements[5],
  30451. surfaceMatrix = me.surfaceMatrix,
  30452. markerCfg = {},
  30453. min = Math.min(dataClipRect[0], dataClipRect[2]),
  30454. max = Math.max(dataClipRect[0], dataClipRect[2]),
  30455. start = Math.max(0, this.binarySearch(min)),
  30456. end = Math.min(dataX.length - 1, this.binarySearch(max) + 1),
  30457. renderer = attr.renderer,
  30458. rendererData = {
  30459. store: store
  30460. },
  30461. rendererChanges;
  30462. ctx.beginPath();
  30463. startX = dataX[start] * xx + dx;
  30464. startY = dataY[start] * yy + dy;
  30465. ctx.moveTo(startX, startY);
  30466. if (attr.step) {
  30467. lastY = startY;
  30468. for (i = start; i <= end; i++) {
  30469. x = dataX[i] * xx + dx;
  30470. y = dataY[i] * yy + dy;
  30471. ctx.lineTo(x, lastY);
  30472. ctx.lineTo(x, lastY = y);
  30473. }
  30474. } else {
  30475. for (i = start; i <= end; i++) {
  30476. x = dataX[i] * xx + dx;
  30477. y = dataY[i] * yy + dy;
  30478. ctx.lineTo(x, y);
  30479. }
  30480. }
  30481. if (dataStartY) {
  30482. if (attr.step) {
  30483. lastX = dataX[end] * xx + dx;
  30484. for (i = end; i >= start; i--) {
  30485. x = dataX[i] * xx + dx;
  30486. y = dataStartY[i] * yy + dy;
  30487. ctx.lineTo(lastX, y);
  30488. ctx.lineTo(lastX = x, y);
  30489. }
  30490. } else {
  30491. for (i = end; i >= start; i--) {
  30492. x = dataX[i] * xx + dx;
  30493. y = dataStartY[i] * yy + dy;
  30494. ctx.lineTo(x, y);
  30495. }
  30496. }
  30497. } else {
  30498. ctx.lineTo(dataX[end] * xx + dx, y);
  30499. ctx.lineTo(dataX[end] * xx + dx, dy);
  30500. ctx.lineTo(startX, dy);
  30501. ctx.lineTo(startX, dataY[i] * yy + dy);
  30502. }
  30503. if (attr.transformFillStroke) {
  30504. attr.matrix.toContext(ctx);
  30505. }
  30506. ctx.fill();
  30507. if (attr.transformFillStroke) {
  30508. attr.inverseMatrix.toContext(ctx);
  30509. }
  30510. ctx.beginPath();
  30511. ctx.moveTo(startX, startY);
  30512. if (attr.step) {
  30513. for (i = start; i <= end; i++) {
  30514. x = dataX[i] * xx + dx;
  30515. y = dataY[i] * yy + dy;
  30516. ctx.lineTo(x, lastY);
  30517. ctx.lineTo(x, lastY = y);
  30518. markerCfg.translationX = surfaceMatrix.x(x, y);
  30519. markerCfg.translationY = surfaceMatrix.y(x, y);
  30520. if (renderer) {
  30521. // callback(fn, scope, args, delay, caller)
  30522. rendererChanges = Ext.callback(renderer, null, [
  30523. me,
  30524. markerCfg,
  30525. rendererData,
  30526. i
  30527. ], 0, series);
  30528. Ext.apply(markerCfg, rendererChanges);
  30529. }
  30530. me.putMarker('markers', markerCfg, i, !renderer);
  30531. }
  30532. } else {
  30533. for (i = start; i <= end; i++) {
  30534. x = dataX[i] * xx + dx;
  30535. y = dataY[i] * yy + dy;
  30536. ctx.lineTo(x, y);
  30537. markerCfg.translationX = surfaceMatrix.x(x, y);
  30538. markerCfg.translationY = surfaceMatrix.y(x, y);
  30539. if (renderer) {
  30540. rendererChanges = Ext.callback(renderer, null, [
  30541. me,
  30542. markerCfg,
  30543. rendererData,
  30544. i
  30545. ], 0, series);
  30546. Ext.apply(markerCfg, rendererChanges);
  30547. }
  30548. me.putMarker('markers', markerCfg, i, !renderer);
  30549. }
  30550. }
  30551. if (attr.transformFillStroke) {
  30552. attr.matrix.toContext(ctx);
  30553. }
  30554. ctx.stroke();
  30555. }
  30556. });
  30557. /**
  30558. * @class Ext.chart.series.Area
  30559. * @extends Ext.chart.series.StackedCartesian
  30560. *
  30561. * Creates an Area Chart.
  30562. *
  30563. * @example
  30564. * Ext.create({
  30565. * xtype: 'cartesian',
  30566. * renderTo: document.body,
  30567. * width: 600,
  30568. * height: 400,
  30569. * insetPadding: 40,
  30570. * store: {
  30571. * fields: ['name', 'data1', 'data2', 'data3'],
  30572. * data: [{
  30573. * name: 'metric one',
  30574. * data1: 10,
  30575. * data2: 12,
  30576. * data3: 14
  30577. * }, {
  30578. * name: 'metric two',
  30579. * data1: 7,
  30580. * data2: 8,
  30581. * data3: 16
  30582. * }, {
  30583. * name: 'metric three',
  30584. * data1: 5,
  30585. * data2: 2,
  30586. * data3: 14
  30587. * }, {
  30588. * name: 'metric four',
  30589. * data1: 2,
  30590. * data2: 14,
  30591. * data3: 6
  30592. * }, {
  30593. * name: 'metric five',
  30594. * data1: 27,
  30595. * data2: 38,
  30596. * data3: 36
  30597. * }]
  30598. * },
  30599. * axes: [{
  30600. * type: 'numeric',
  30601. * position: 'left',
  30602. * fields: ['data1'],
  30603. * grid: true,
  30604. * minimum: 0
  30605. * }, {
  30606. * type: 'category',
  30607. * position: 'bottom',
  30608. * fields: ['name']
  30609. * }],
  30610. * series: {
  30611. * type: 'area',
  30612. * subStyle: {
  30613. * fill: ['#0A3F50', '#30BDA7', '#96D4C6']
  30614. * },
  30615. * xField: 'name',
  30616. * yField: ['data1', 'data2', 'data3']
  30617. * }
  30618. * });
  30619. */
  30620. Ext.define('Ext.chart.series.Area', {
  30621. extend: 'Ext.chart.series.StackedCartesian',
  30622. alias: 'series.area',
  30623. type: 'area',
  30624. /**
  30625. * @property seriesType
  30626. * @inheritdoc
  30627. */
  30628. seriesType: 'areaSeries',
  30629. isArea: true,
  30630. requires: [
  30631. 'Ext.chart.series.sprite.Area'
  30632. ],
  30633. config: {
  30634. /**
  30635. * @cfg splitStacks
  30636. * @inheritdoc
  30637. */
  30638. splitStacks: false
  30639. }
  30640. });
  30641. /**
  30642. * @cfg renderer
  30643. * @inheritdoc
  30644. * Area series renderers only affect markers.
  30645. * For styling individual segments with a renderer it is possible to use
  30646. * the Line series with {@link Ext.chart.series.Line#fill} config set to `true`,
  30647. * which makes Line series look like Area series.
  30648. */
  30649. /**
  30650. * @class Ext.chart.series.sprite.Bar
  30651. * @extends Ext.chart.series.sprite.StackedCartesian
  30652. *
  30653. * Draws a sprite used in the bar series.
  30654. */
  30655. Ext.define('Ext.chart.series.sprite.Bar', {
  30656. alias: 'sprite.barSeries',
  30657. extend: 'Ext.chart.series.sprite.StackedCartesian',
  30658. inheritableStatics: {
  30659. def: {
  30660. processors: {
  30661. /**
  30662. * @cfg {Number} [minBarWidth=2] The minimum bar width.
  30663. */
  30664. minBarWidth: 'number',
  30665. /**
  30666. * @cfg {Number} [maxBarWidth=100] The maximum bar width.
  30667. */
  30668. maxBarWidth: 'number',
  30669. /**
  30670. * @cfg {Number} [minGapWidth=5] The minimum gap between bars.
  30671. */
  30672. minGapWidth: 'number',
  30673. /**
  30674. * @cfg {Number} [radius=0] The degree of rounding for rounded bars.
  30675. */
  30676. radius: 'number',
  30677. /**
  30678. * @cfg {Number} [inGroupGapWidth=3] The gap between grouped bars.
  30679. */
  30680. inGroupGapWidth: 'number'
  30681. },
  30682. defaults: {
  30683. minBarWidth: 2,
  30684. maxBarWidth: 100,
  30685. minGapWidth: 5,
  30686. inGroupGapWidth: 3,
  30687. radius: 0
  30688. }
  30689. }
  30690. },
  30691. drawLabel: function(text, dataX, dataStartY, dataY, labelId) {
  30692. var me = this,
  30693. attr = me.attr,
  30694. label = me.getMarker('labels'),
  30695. labelTpl = label.getTemplate(),
  30696. labelCfg = me.labelCfg || (me.labelCfg = {}),
  30697. surfaceMatrix = me.surfaceMatrix,
  30698. labelOverflowPadding = attr.labelOverflowPadding,
  30699. labelDisplay = labelTpl.attr.display,
  30700. labelOrientation = labelTpl.attr.orientation,
  30701. isVerticalText = (labelOrientation === 'horizontal' && attr.flipXY) || (labelOrientation === 'vertical' && !attr.flipXY) || !labelOrientation,
  30702. calloutLine = labelTpl.getCalloutLine(),
  30703. labelY, halfText, labelBBox, calloutLineLength, changes, hasPendingChanges, params;
  30704. // The coordinates below (data point converted to surface coordinates)
  30705. // are just for the renderer to give it a notion of where the label will be positioned.
  30706. // The actual position of the label will be different
  30707. // (unless the renderer returns x/y coordinates in the changes object)
  30708. // and depend on several things including the size of the text,
  30709. // which has to be measured after the renderer call,
  30710. // since text can be modified by the renderer.
  30711. labelCfg.x = surfaceMatrix.x(dataX, dataY);
  30712. labelCfg.y = surfaceMatrix.y(dataX, dataY);
  30713. if (calloutLine) {
  30714. calloutLineLength = calloutLine.length;
  30715. } else {
  30716. calloutLineLength = 0;
  30717. }
  30718. // Set defaults
  30719. if (!attr.flipXY) {
  30720. labelCfg.rotationRads = -Math.PI * 0.5;
  30721. } else {
  30722. labelCfg.rotationRads = 0;
  30723. }
  30724. labelCfg.calloutVertical = !attr.flipXY;
  30725. // Check if we have a specific orientation specified, if so, set
  30726. // the appropriate values.
  30727. switch (labelOrientation) {
  30728. case 'horizontal':
  30729. labelCfg.rotationRads = 0;
  30730. labelCfg.calloutVertical = false;
  30731. break;
  30732. case 'vertical':
  30733. labelCfg.rotationRads = -Math.PI * 0.5;
  30734. labelCfg.calloutVertical = true;
  30735. break;
  30736. }
  30737. labelCfg.text = text;
  30738. if (labelTpl.attr.renderer) {
  30739. // The label instance won't exist on first render before the renderer is called,
  30740. // it's only created later by `me.putMarker` after the renderer call. To make
  30741. // sure the renderer always can access the label instance, we make this check here.
  30742. if (!label.get(labelId)) {
  30743. label.putMarkerFor('labels', {}, labelId);
  30744. }
  30745. params = [
  30746. text,
  30747. label,
  30748. labelCfg,
  30749. {
  30750. store: me.getStore()
  30751. },
  30752. labelId
  30753. ];
  30754. changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
  30755. if (typeof changes === 'string') {
  30756. labelCfg.text = changes;
  30757. } else if (typeof changes === 'object') {
  30758. if ('text' in changes) {
  30759. labelCfg.text = changes.text;
  30760. }
  30761. hasPendingChanges = true;
  30762. }
  30763. }
  30764. labelBBox = me.getMarkerBBox('labels', labelId, true);
  30765. if (!labelBBox) {
  30766. me.putMarker('labels', labelCfg, labelId);
  30767. labelBBox = me.getMarkerBBox('labels', labelId, true);
  30768. }
  30769. if (calloutLineLength > 0) {
  30770. halfText = calloutLineLength;
  30771. } else if (calloutLineLength === 0) {
  30772. halfText = (isVerticalText ? labelBBox.width : labelBBox.height) / 2;
  30773. } else {
  30774. halfText = (isVerticalText ? labelBBox.width : labelBBox.height) / 2 + labelOverflowPadding;
  30775. }
  30776. if (dataStartY > dataY) {
  30777. halfText = -halfText;
  30778. }
  30779. if (isVerticalText) {
  30780. labelY = (labelDisplay === 'insideStart') ? dataStartY + halfText : dataY - halfText;
  30781. } else {
  30782. labelY = (labelDisplay === 'insideStart') ? dataStartY + labelOverflowPadding * 2 : dataY - labelOverflowPadding * 2;
  30783. }
  30784. labelCfg.x = surfaceMatrix.x(dataX, labelY);
  30785. labelCfg.y = surfaceMatrix.y(dataX, labelY);
  30786. labelY = (labelDisplay === 'insideStart') ? dataStartY : dataY;
  30787. labelCfg.calloutStartX = surfaceMatrix.x(dataX, labelY);
  30788. labelCfg.calloutStartY = surfaceMatrix.y(dataX, labelY);
  30789. labelY = (labelDisplay === 'insideStart') ? dataStartY - halfText : dataY + halfText;
  30790. labelCfg.calloutPlaceX = surfaceMatrix.x(dataX, labelY);
  30791. labelCfg.calloutPlaceY = surfaceMatrix.y(dataX, labelY);
  30792. labelCfg.calloutColor = (calloutLine && calloutLine.color) || me.attr.fillStyle;
  30793. if (calloutLine) {
  30794. if (calloutLine.width) {
  30795. labelCfg.calloutWidth = calloutLine.width;
  30796. }
  30797. } else {
  30798. labelCfg.calloutColor = 'none';
  30799. }
  30800. if (dataStartY > dataY) {
  30801. halfText = -halfText;
  30802. }
  30803. if (Math.abs(dataY - dataStartY) <= halfText * 2 || labelDisplay === 'outside') {
  30804. labelCfg.callout = 1;
  30805. } else {
  30806. labelCfg.callout = 0;
  30807. }
  30808. if (hasPendingChanges) {
  30809. Ext.apply(labelCfg, changes);
  30810. }
  30811. me.putMarker('labels', labelCfg, labelId);
  30812. },
  30813. drawBar: function(ctx, surface, rect, left, top, right, bottom, index) {
  30814. var me = this,
  30815. itemCfg = {},
  30816. renderer = me.attr.renderer,
  30817. changes;
  30818. itemCfg.x = left;
  30819. itemCfg.y = top;
  30820. itemCfg.width = right - left;
  30821. itemCfg.height = bottom - top;
  30822. itemCfg.radius = me.attr.radius;
  30823. if (renderer) {
  30824. changes = Ext.callback(renderer, null, [
  30825. me,
  30826. itemCfg,
  30827. {
  30828. store: me.getStore()
  30829. },
  30830. index
  30831. ], 0, me.getSeries());
  30832. Ext.apply(itemCfg, changes);
  30833. }
  30834. me.putMarker('items', itemCfg, index, !renderer);
  30835. },
  30836. renderClipped: function(surface, ctx, dataClipRect) {
  30837. if (this.cleanRedraw) {
  30838. return;
  30839. }
  30840. // eslint-disable-next-line vars-on-top
  30841. var me = this,
  30842. attr = me.attr,
  30843. dataX = attr.dataX,
  30844. dataY = attr.dataY,
  30845. dataText = attr.labels,
  30846. dataStartY = attr.dataStartY,
  30847. groupCount = attr.groupCount,
  30848. groupOffset = attr.groupOffset - (groupCount - 1) * 0.5,
  30849. inGroupGapWidth = attr.inGroupGapWidth,
  30850. lineWidth = ctx.lineWidth,
  30851. matrix = attr.matrix,
  30852. xx = matrix.elements[0],
  30853. yy = matrix.elements[3],
  30854. dx = matrix.elements[4],
  30855. dy = surface.roundPixel(matrix.elements[5]) - 1,
  30856. maxBarWidth = Math.abs(xx) - attr.minGapWidth,
  30857. minBarWidth = (Math.min(maxBarWidth, attr.maxBarWidth) - inGroupGapWidth * (groupCount - 1)) / groupCount,
  30858. barWidth = surface.roundPixel(Math.max(attr.minBarWidth, minBarWidth)),
  30859. surfaceMatrix = me.surfaceMatrix,
  30860. left, right, bottom, top, i, center,
  30861. halfLineWidth = 0.5 * attr.lineWidth,
  30862. // Finding min/max so that bars render properly in both LTR and RTL modes.
  30863. min = Math.min(dataClipRect[0], dataClipRect[2]),
  30864. max = Math.max(dataClipRect[0], dataClipRect[2]),
  30865. start = Math.max(0, Math.floor(min)),
  30866. end = Math.min(dataX.length - 1, Math.ceil(max)),
  30867. isDrawLabels = dataText && me.getMarker('labels'),
  30868. yLow, yHi;
  30869. // The scaling (xx) and translation (dx) here will already be such that the midpoints
  30870. // of the first and last bars are not at the surface edges (which would mean that
  30871. // bars are half-clipped), but padded, so that those bars are fully visible
  30872. // (assuming no pan/zoom).
  30873. for (i = start; i <= end; i++) {
  30874. yLow = dataStartY ? dataStartY[i] : 0;
  30875. yHi = dataY[i];
  30876. center = dataX[i] * xx + dx + groupOffset * (barWidth + inGroupGapWidth);
  30877. left = surface.roundPixel(center - barWidth / 2) + halfLineWidth;
  30878. top = surface.roundPixel(yHi * yy + dy + lineWidth);
  30879. right = surface.roundPixel(center + barWidth / 2) - halfLineWidth;
  30880. bottom = surface.roundPixel(yLow * yy + dy + lineWidth);
  30881. me.drawBar(ctx, surface, dataClipRect, left, top - halfLineWidth, right, bottom - halfLineWidth, i);
  30882. // We want 0 values to be passed to the renderer
  30883. if (isDrawLabels && dataText[i] != null) {
  30884. me.drawLabel(dataText[i], center, bottom, top, i);
  30885. }
  30886. me.putMarker('markers', {
  30887. translationX: surfaceMatrix.x(center, top),
  30888. translationY: surfaceMatrix.y(center, top)
  30889. }, i, true);
  30890. }
  30891. }
  30892. });
  30893. /**
  30894. * @class Ext.chart.series.Bar
  30895. * @extends Ext.chart.series.StackedCartesian
  30896. *
  30897. * Creates a Bar or Column Chart (depending on the value of the
  30898. * {@link Ext.chart.CartesianChart#flipXY flipXY} config).
  30899. *
  30900. * Note: 'bar' series is meant to be used with the
  30901. * {@link Ext.chart.axis.Category 'category'} axis as its x-axis.
  30902. *
  30903. * @example
  30904. * Ext.create({
  30905. * xtype: 'cartesian',
  30906. * renderTo: document.body,
  30907. * width: 600,
  30908. * height: 400,
  30909. * store: {
  30910. * fields: ['name', 'value'],
  30911. * data: [{
  30912. * name: 'metric one',
  30913. * value: 10
  30914. * }, {
  30915. * name: 'metric two',
  30916. * value: 7
  30917. * }, {
  30918. * name: 'metric three',
  30919. * value: 5
  30920. * }, {
  30921. * name: 'metric four',
  30922. * value: 2
  30923. * }, {
  30924. * name: 'metric five',
  30925. * value: 27
  30926. * }]
  30927. * },
  30928. * axes: [{
  30929. * type: 'numeric',
  30930. * position: 'left',
  30931. * title: {
  30932. * text: 'Sample Values',
  30933. * fontSize: 15
  30934. * },
  30935. * fields: 'value'
  30936. * }, {
  30937. * type: 'category',
  30938. * position: 'bottom',
  30939. * title: {
  30940. * text: 'Sample Values',
  30941. * fontSize: 15
  30942. * },
  30943. * fields: 'name'
  30944. * }],
  30945. * series: {
  30946. * type: 'bar',
  30947. * subStyle: {
  30948. * fill: ['#388FAD'],
  30949. * stroke: '#1F6D91'
  30950. * },
  30951. * xField: 'name',
  30952. * yField: 'value'
  30953. * }
  30954. * });
  30955. */
  30956. Ext.define('Ext.chart.series.Bar', {
  30957. extend: 'Ext.chart.series.StackedCartesian',
  30958. alias: 'series.bar',
  30959. type: 'bar',
  30960. seriesType: 'barSeries',
  30961. isBar: true,
  30962. requires: [
  30963. 'Ext.chart.series.sprite.Bar',
  30964. 'Ext.draw.sprite.Rect'
  30965. ],
  30966. config: {
  30967. /**
  30968. * @private
  30969. * @cfg {Object} itemInstancing Sprite template used for series.
  30970. */
  30971. itemInstancing: {
  30972. type: 'rect',
  30973. animation: {
  30974. customDurations: {
  30975. x: 0,
  30976. y: 0,
  30977. width: 0,
  30978. height: 0,
  30979. radius: 0
  30980. }
  30981. }
  30982. }
  30983. },
  30984. getItemForPoint: function(x, y) {
  30985. var chart, padding, isRtl;
  30986. if (this.getSprites().length) {
  30987. chart = this.getChart();
  30988. padding = chart.getInnerPadding();
  30989. isRtl = chart.getInherited().rtl;
  30990. // Convert the coordinates because the "items" sprites that draw
  30991. // the bars ignore the chart's InnerPadding.
  30992. arguments[0] = x + (isRtl ? padding.right : -padding.left);
  30993. arguments[1] = y + padding.bottom;
  30994. return this.callParent(arguments);
  30995. }
  30996. },
  30997. updateXAxis: function(xAxis) {
  30998. //<debug>
  30999. if (!this.is3D && !xAxis.isCategory) {
  31000. Ext.raise("'bar' series should be used with a 'category' axis. " + "Please refer to the bar series docs.");
  31001. }
  31002. //</debug>
  31003. xAxis.setExpandRangeBy(0.5);
  31004. this.callParent(arguments);
  31005. },
  31006. updateHidden: function(hidden) {
  31007. this.callParent(arguments);
  31008. this.updateStacked();
  31009. },
  31010. updateStacked: function(stacked) {
  31011. var me = this,
  31012. attributes = {},
  31013. sprites = me.getSprites(),
  31014. spriteCount = sprites.length,
  31015. visibleSprites = [],
  31016. visibleSpriteCount, i;
  31017. for (i = 0; i < spriteCount; i++) {
  31018. if (!sprites[i].attr.hidden) {
  31019. visibleSprites.push(sprites[i]);
  31020. }
  31021. }
  31022. visibleSpriteCount = visibleSprites.length;
  31023. if (me.getStacked()) {
  31024. attributes.groupCount = 1;
  31025. attributes.groupOffset = 0;
  31026. for (i = 0; i < visibleSpriteCount; i++) {
  31027. visibleSprites[i].setAttributes(attributes);
  31028. }
  31029. } else {
  31030. attributes.groupCount = visibleSpriteCount;
  31031. for (i = 0; i < visibleSpriteCount; i++) {
  31032. attributes.groupOffset = i;
  31033. visibleSprites[i].setAttributes(attributes);
  31034. }
  31035. }
  31036. me.callParent(arguments);
  31037. }
  31038. });
  31039. /**
  31040. * @class Ext.chart.series.sprite.Bar3D
  31041. * @extends Ext.chart.series.sprite.Bar
  31042. *
  31043. * Draws a sprite used in {@link Ext.chart.series.Bar3D} series.
  31044. */
  31045. Ext.define('Ext.chart.series.sprite.Bar3D', {
  31046. extend: 'Ext.chart.series.sprite.Bar',
  31047. alias: 'sprite.bar3dSeries',
  31048. requires: [
  31049. 'Ext.draw.gradient.Linear'
  31050. ],
  31051. inheritableStatics: {
  31052. def: {
  31053. processors: {
  31054. depthWidthRatio: 'number',
  31055. /**
  31056. * @cfg {Number} [saturationFactor=1]
  31057. * The factor applied to the saturation of the bars.
  31058. */
  31059. saturationFactor: 'number',
  31060. /**
  31061. * @cfg {Number} [brightnessFactor=1]
  31062. * The factor applied to the brightness of the bars.
  31063. */
  31064. brightnessFactor: 'number',
  31065. /**
  31066. * @cfg {Number} [colorSpread=1]
  31067. * An attribute used to control how flat the bar gradient looks.
  31068. * A value of 0 essentially means no gradient (flat color).
  31069. */
  31070. colorSpread: 'number'
  31071. },
  31072. defaults: {
  31073. depthWidthRatio: 1 / 3,
  31074. saturationFactor: 1,
  31075. brightnessFactor: 1,
  31076. colorSpread: 1,
  31077. transformFillStroke: true
  31078. },
  31079. triggers: {
  31080. groupCount: 'panzoom'
  31081. },
  31082. updaters: {
  31083. panzoom: function(attr) {
  31084. var me = this,
  31085. dx = attr.visibleMaxX - attr.visibleMinX,
  31086. dy = attr.visibleMaxY - attr.visibleMinY,
  31087. innerWidth = attr.flipXY ? attr.innerHeight : attr.innerWidth,
  31088. innerHeight = !attr.flipXY ? attr.innerHeight : attr.innerWidth,
  31089. surface = me.getSurface(),
  31090. isRtl = surface ? surface.getInherited().rtl : false;
  31091. if (isRtl && !attr.flipXY) {
  31092. attr.translationX = innerWidth + attr.visibleMinX * innerWidth / dx;
  31093. } else {
  31094. attr.translationX = -attr.visibleMinX * innerWidth / dx;
  31095. }
  31096. attr.translationY = -attr.visibleMinY * (innerHeight - me.depth) / dy;
  31097. attr.scalingX = (isRtl && !attr.flipXY ? -1 : 1) * innerWidth / dx;
  31098. attr.scalingY = (innerHeight - me.depth) / dy;
  31099. attr.scalingCenterX = 0;
  31100. attr.scalingCenterY = 0;
  31101. me.applyTransformations(true);
  31102. }
  31103. }
  31104. }
  31105. },
  31106. config: {
  31107. showStroke: false
  31108. },
  31109. depth: 0,
  31110. drawBar: function(ctx, surface, clip, left, top, right, bottom, index) {
  31111. var me = this,
  31112. attr = me.attr,
  31113. itemCfg = {},
  31114. renderer = attr.renderer,
  31115. changes, depth, series, params;
  31116. itemCfg.x = (left + right) * 0.5;
  31117. itemCfg.y = top;
  31118. itemCfg.width = (right - left) * 0.75;
  31119. itemCfg.height = bottom - top;
  31120. itemCfg.depth = depth = itemCfg.width * attr.depthWidthRatio;
  31121. itemCfg.orientation = attr.flipXY ? 'horizontal' : 'vertical';
  31122. itemCfg.saturationFactor = attr.saturationFactor;
  31123. itemCfg.brightnessFactor = attr.brightnessFactor;
  31124. itemCfg.colorSpread = attr.colorSpread;
  31125. if (depth !== me.depth) {
  31126. me.depth = depth;
  31127. series = me.getSeries();
  31128. series.fireEvent('depthchange', series, depth);
  31129. }
  31130. if (renderer) {
  31131. params = [
  31132. me,
  31133. itemCfg,
  31134. {
  31135. store: me.getStore()
  31136. },
  31137. index
  31138. ];
  31139. changes = Ext.callback(renderer, null, params, 0, me.getSeries());
  31140. Ext.apply(itemCfg, changes);
  31141. }
  31142. me.putMarker('items', itemCfg, index, !renderer);
  31143. }
  31144. });
  31145. /**
  31146. * @class Ext.chart.sprite.Bar3D
  31147. * @extends Ext.draw.sprite.Sprite
  31148. *
  31149. * A sprite that represents a 3D bar or column.
  31150. * Used as an item template by the {@link Ext.chart.series.sprite.Bar3D} marker holder.
  31151. *
  31152. */
  31153. Ext.define('Ext.chart.sprite.Bar3D', {
  31154. extend: 'Ext.draw.sprite.Sprite',
  31155. alias: 'sprite.bar3d',
  31156. type: 'bar3d',
  31157. inheritableStatics: {
  31158. def: {
  31159. processors: {
  31160. /**
  31161. * @cfg {Number} [x=0]
  31162. * The position of the sprite on the x-axis.
  31163. * Corresponds to the center of the front face of the box.
  31164. */
  31165. x: 'number',
  31166. /**
  31167. * @cfg {Number} [y=0]
  31168. * The position of the sprite on the y-axis.
  31169. * Corresponds to the top of the front face of the box.
  31170. */
  31171. y: 'number',
  31172. /**
  31173. * @cfg {Number} [width=8] The width of the box.
  31174. */
  31175. width: 'number',
  31176. /**
  31177. * @cfg {Number} [height=8] The height of the box.
  31178. */
  31179. height: 'number',
  31180. /**
  31181. * @cfg {Number} [depth=8] The depth of the box.
  31182. */
  31183. depth: 'number',
  31184. /**
  31185. * @cfg {String} [orientation='vertical'] The orientation of the box.
  31186. */
  31187. orientation: 'enums(vertical,horizontal)',
  31188. /**
  31189. * @cfg {Boolean} [showStroke=false]
  31190. * Whether to render the stroke or not.
  31191. */
  31192. showStroke: 'bool',
  31193. /**
  31194. * @cfg {Number} [saturationFactor=1]
  31195. * The factor applied to the saturation of the box.
  31196. */
  31197. saturationFactor: 'number',
  31198. /**
  31199. * @cfg {Number} [brightnessFactor=1]
  31200. * The factor applied to the brightness of the box.
  31201. */
  31202. brightnessFactor: 'number',
  31203. /**
  31204. * @cfg {Number} [colorSpread=1]
  31205. * An attribute used to control how flat the bar gradient looks.
  31206. * A value of 0 essentially means no gradient (flat color).
  31207. */
  31208. colorSpread: 'number'
  31209. },
  31210. triggers: {
  31211. x: 'bbox',
  31212. y: 'bbox',
  31213. width: 'bbox',
  31214. height: 'bbox',
  31215. depth: 'bbox',
  31216. orientation: 'bbox'
  31217. },
  31218. defaults: {
  31219. x: 0,
  31220. y: 0,
  31221. width: 8,
  31222. height: 8,
  31223. depth: 8,
  31224. orientation: 'vertical',
  31225. showStroke: false,
  31226. saturationFactor: 1,
  31227. brightnessFactor: 1,
  31228. colorSpread: 1,
  31229. lineJoin: 'bevel'
  31230. }
  31231. }
  31232. },
  31233. constructor: function(config) {
  31234. this.callParent([
  31235. config
  31236. ]);
  31237. this.topGradient = new Ext.draw.gradient.Linear({});
  31238. this.rightGradient = new Ext.draw.gradient.Linear({});
  31239. this.frontGradient = new Ext.draw.gradient.Linear({});
  31240. },
  31241. updatePlainBBox: function(plain) {
  31242. var attr = this.attr,
  31243. x = attr.x,
  31244. y = attr.y,
  31245. width = attr.width,
  31246. height = attr.height,
  31247. depth = attr.depth;
  31248. plain.x = x - width * 0.5;
  31249. plain.width = width + depth;
  31250. if (height > 0) {
  31251. plain.y = y;
  31252. plain.height = height + depth;
  31253. } else {
  31254. plain.y = y + depth;
  31255. plain.height = height - depth;
  31256. }
  31257. },
  31258. render: function(surface, ctx) {
  31259. var me = this,
  31260. attr = me.attr,
  31261. center = attr.x,
  31262. top = attr.y,
  31263. bottom = top + attr.height,
  31264. isNegative = top < bottom,
  31265. halfWidth = attr.width * 0.5,
  31266. depth = attr.depth,
  31267. isHorizontal = attr.orientation === 'horizontal',
  31268. isTransparent = attr.globalAlpha < 1,
  31269. fillStyle = attr.fillStyle,
  31270. color = Ext.util.Color.create(fillStyle.isGradient ? fillStyle.getStops()[0].color : fillStyle),
  31271. saturationFactor = attr.saturationFactor,
  31272. brightnessFactor = attr.brightnessFactor,
  31273. colorSpread = attr.colorSpread,
  31274. hsv = color.getHSV(),
  31275. bbox = {},
  31276. roundX, roundY, temp;
  31277. if (!attr.showStroke) {
  31278. ctx.strokeStyle = Ext.util.Color.RGBA_NONE;
  31279. }
  31280. if (isNegative) {
  31281. temp = top;
  31282. top = bottom;
  31283. bottom = temp;
  31284. }
  31285. // Refresh gradients based on sprite's fillStyle and other attributes.
  31286. me.topGradient.setDegrees(isHorizontal ? 0 : 80);
  31287. me.topGradient.setStops([
  31288. {
  31289. offset: 0,
  31290. 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))
  31291. },
  31292. {
  31293. offset: 1,
  31294. 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))
  31295. }
  31296. ]);
  31297. me.rightGradient.setDegrees(isHorizontal ? 45 : 90);
  31298. me.rightGradient.setStops([
  31299. {
  31300. offset: 0,
  31301. 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))
  31302. },
  31303. {
  31304. offset: 1,
  31305. 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))
  31306. }
  31307. ]);
  31308. if (isHorizontal) {
  31309. // 0° angle looks like 90° angle because the chart is flipped
  31310. me.frontGradient.setDegrees(0);
  31311. } else {
  31312. me.frontGradient.setRadians(Math.atan2(top - bottom, halfWidth * 2));
  31313. }
  31314. me.frontGradient.setStops([
  31315. {
  31316. offset: 0,
  31317. 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))
  31318. },
  31319. {
  31320. offset: 1,
  31321. 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))
  31322. }
  31323. ]);
  31324. if (isTransparent || isNegative) {
  31325. // Bottom side.
  31326. ctx.beginPath();
  31327. ctx.moveTo(center - halfWidth, bottom);
  31328. ctx.lineTo(center - halfWidth + depth, bottom + depth);
  31329. ctx.lineTo(center + halfWidth + depth, bottom + depth);
  31330. ctx.lineTo(center + halfWidth, bottom);
  31331. ctx.closePath();
  31332. bbox.x = center - halfWidth;
  31333. bbox.y = top;
  31334. bbox.width = halfWidth + depth;
  31335. bbox.height = depth;
  31336. // eslint-disable-next-line max-len
  31337. ctx.fillStyle = (isHorizontal ? me.rightGradient : me.topGradient).generateGradient(ctx, bbox);
  31338. ctx.fillStroke(attr);
  31339. }
  31340. if (isTransparent) {
  31341. // Left side.
  31342. ctx.beginPath();
  31343. ctx.moveTo(center - halfWidth, top);
  31344. ctx.lineTo(center - halfWidth + depth, top + depth);
  31345. ctx.lineTo(center - halfWidth + depth, bottom + depth);
  31346. ctx.lineTo(center - halfWidth, bottom);
  31347. ctx.closePath();
  31348. bbox.x = center + halfWidth;
  31349. bbox.y = bottom;
  31350. bbox.width = depth;
  31351. bbox.height = top + depth - bottom;
  31352. // eslint-disable-next-line max-len
  31353. ctx.fillStyle = (isHorizontal ? me.topGradient : me.rightGradient).generateGradient(ctx, bbox);
  31354. ctx.fillStroke(attr);
  31355. }
  31356. // Top side.
  31357. roundY = surface.roundPixel(top);
  31358. ctx.beginPath();
  31359. ctx.moveTo(center - halfWidth, roundY);
  31360. ctx.lineTo(center - halfWidth + depth, top + depth);
  31361. ctx.lineTo(center + halfWidth + depth, top + depth);
  31362. ctx.lineTo(center + halfWidth, roundY);
  31363. ctx.closePath();
  31364. bbox.x = center - halfWidth;
  31365. bbox.y = top;
  31366. bbox.width = halfWidth + depth;
  31367. bbox.height = depth;
  31368. // eslint-disable-next-line max-len
  31369. ctx.fillStyle = (isHorizontal ? me.rightGradient : me.topGradient).generateGradient(ctx, bbox);
  31370. ctx.fillStroke(attr);
  31371. // Right side.
  31372. roundX = surface.roundPixel(center + halfWidth);
  31373. ctx.beginPath();
  31374. ctx.moveTo(roundX, surface.roundPixel(top));
  31375. ctx.lineTo(center + halfWidth + depth, top + depth);
  31376. ctx.lineTo(center + halfWidth + depth, bottom + depth);
  31377. ctx.lineTo(roundX, bottom);
  31378. ctx.closePath();
  31379. bbox.x = center + halfWidth;
  31380. bbox.y = bottom;
  31381. bbox.width = depth;
  31382. bbox.height = top + depth - bottom;
  31383. // eslint-disable-next-line max-len
  31384. ctx.fillStyle = (isHorizontal ? me.topGradient : me.rightGradient).generateGradient(ctx, bbox);
  31385. ctx.fillStroke(attr);
  31386. // Front side.
  31387. roundX = surface.roundPixel(center + halfWidth);
  31388. roundY = surface.roundPixel(top);
  31389. ctx.beginPath();
  31390. ctx.moveTo(center - halfWidth, bottom);
  31391. ctx.lineTo(center - halfWidth, roundY);
  31392. ctx.lineTo(roundX, roundY);
  31393. ctx.lineTo(roundX, bottom);
  31394. ctx.closePath();
  31395. bbox.x = center - halfWidth;
  31396. bbox.y = bottom;
  31397. bbox.width = halfWidth * 2;
  31398. bbox.height = top - bottom;
  31399. ctx.fillStyle = me.frontGradient.generateGradient(ctx, bbox);
  31400. ctx.fillStroke(attr);
  31401. }
  31402. });
  31403. /**
  31404. * @class Ext.chart.series.Bar3D
  31405. * @extends Ext.chart.series.Bar
  31406. *
  31407. * Creates a 3D Bar or 3D Column Chart (depending on the value of the
  31408. * {@link Ext.chart.CartesianChart#flipXY flipXY} config).
  31409. *
  31410. * Note: 'bar3d' series is meant to be used with the
  31411. * {@link Ext.chart.axis.Category 'category3d'} axis as its x-axis.
  31412. *
  31413. * @example
  31414. * Ext.create({
  31415. * xtype: 'cartesian',
  31416. * renderTo: Ext.getBody(),
  31417. * width: 600,
  31418. * height: 400,
  31419. * innerPadding: '0 10 0 10',
  31420. * store: {
  31421. * fields: ['name', 'apples', 'oranges'],
  31422. * data: [{
  31423. * name: 'Eric',
  31424. * apples: 10,
  31425. * oranges: 3
  31426. * }, {
  31427. * name: 'Mary',
  31428. * apples: 7,
  31429. * oranges: 2
  31430. * }, {
  31431. * name: 'John',
  31432. * apples: 5,
  31433. * oranges: 2
  31434. * }, {
  31435. * name: 'Bob',
  31436. * apples: 2,
  31437. * oranges: 3
  31438. * }, {
  31439. * name: 'Joe',
  31440. * apples: 19,
  31441. * oranges: 1
  31442. * }, {
  31443. * name: 'Macy',
  31444. * apples: 13,
  31445. * oranges: 4
  31446. * }]
  31447. * },
  31448. * axes: [{
  31449. * type: 'numeric3d',
  31450. * position: 'left',
  31451. * fields: ['apples', 'oranges'],
  31452. * title: {
  31453. * text: 'Inventory',
  31454. * fontSize: 15
  31455. * },
  31456. * grid: {
  31457. * odd: {
  31458. * fillStyle: 'rgba(255, 255, 255, 0.06)'
  31459. * },
  31460. * even: {
  31461. * fillStyle: 'rgba(0, 0, 0, 0.03)'
  31462. * }
  31463. * }
  31464. * }, {
  31465. * type: 'category3d',
  31466. * position: 'bottom',
  31467. * title: {
  31468. * text: 'People',
  31469. * fontSize: 15
  31470. * },
  31471. * fields: 'name'
  31472. * }],
  31473. * series: {
  31474. * type: 'bar3d',
  31475. * xField: 'name',
  31476. * yField: ['apples', 'oranges']
  31477. * }
  31478. * });
  31479. */
  31480. Ext.define('Ext.chart.series.Bar3D', {
  31481. extend: 'Ext.chart.series.Bar',
  31482. requires: [
  31483. 'Ext.chart.series.sprite.Bar3D',
  31484. 'Ext.chart.sprite.Bar3D'
  31485. ],
  31486. alias: 'series.bar3d',
  31487. type: 'bar3d',
  31488. seriesType: 'bar3dSeries',
  31489. is3D: true,
  31490. config: {
  31491. itemInstancing: {
  31492. type: 'bar3d',
  31493. animation: {
  31494. customDurations: {
  31495. x: 0,
  31496. y: 0,
  31497. width: 0,
  31498. height: 0,
  31499. depth: 0
  31500. }
  31501. }
  31502. },
  31503. highlightCfg: {
  31504. opacity: 0.8
  31505. }
  31506. },
  31507. /**
  31508. * For 3D series, it's quite the opposite. It would be extremely odd,
  31509. * if top segments were rendered as if they were under the bottom ones.
  31510. */
  31511. reversedSpriteZOrder: false,
  31512. updateXAxis: function(xAxis, oldXAxis) {
  31513. //<debug>
  31514. if (xAxis.type !== 'category3d') {
  31515. Ext.raise("'bar3d' series should be used with a 'category3d' axis." + " Please refer to the 'bar3d' series docs.");
  31516. }
  31517. //</debug>
  31518. this.callParent([
  31519. xAxis,
  31520. oldXAxis
  31521. ]);
  31522. },
  31523. getDepth: function() {
  31524. var sprite = this.getSprites()[0];
  31525. return sprite ? (sprite.depth || 0) : 0;
  31526. }
  31527. });
  31528. /**
  31529. * BoxPlot series sprite that manages {@link Ext.chart.sprite.BoxPlot} instances.
  31530. */
  31531. Ext.define('Ext.chart.series.sprite.BoxPlot', {
  31532. alias: 'sprite.boxplotSeries',
  31533. extend: 'Ext.chart.series.sprite.Cartesian',
  31534. inheritableStatics: {
  31535. def: {
  31536. processors: {
  31537. /**
  31538. * @cfg {Number[]} [dataLow=null] Array of coordinated minimum values.
  31539. */
  31540. dataLow: 'data',
  31541. /**
  31542. * @cfg {Number[]} [dataQ1=null] Array of coordinated 1-st quartile values.
  31543. */
  31544. dataQ1: 'data',
  31545. /**
  31546. * @cfg {Number[]} [dataQ3=null] Array of coordinated 3-rd quartile values.
  31547. */
  31548. dataQ3: 'data',
  31549. /**
  31550. * @cfg {Number[]} [dataHigh=null] Array of coordinated maximum values.
  31551. */
  31552. dataHigh: 'data',
  31553. /**
  31554. * @cfg {Number} [minBoxWidth=2] The minimum box width.
  31555. */
  31556. minBoxWidth: 'number',
  31557. /**
  31558. * @cfg {Number} [maxBoxWidth=20] The maximum box width.
  31559. */
  31560. maxBoxWidth: 'number',
  31561. /**
  31562. * @cfg {Number} [minGapWidth=5] The minimum gap between boxes.
  31563. */
  31564. minGapWidth: 'number'
  31565. },
  31566. aliases: {
  31567. /**
  31568. * The `dataMedian` attribute can be used to set the value of
  31569. * the `dataY` attribute. E.g.:
  31570. *
  31571. * sprite.setAttributes({
  31572. * dataMedian: [...]
  31573. * });
  31574. *
  31575. * To fetch the value of the attribute one has to use
  31576. *
  31577. * sprite.attr.dataY // array of coordinated median values
  31578. *
  31579. * and not
  31580. *
  31581. * sprite.attr.dataMedian // WRONG!
  31582. *
  31583. * `dataY` attribute is defined by the `Ext.chart.series.sprite.Series`.
  31584. *
  31585. * @cfg {Number[]} [dataMedian=null] Array of coordinated median values.
  31586. */
  31587. dataMedian: 'dataY'
  31588. },
  31589. defaults: {
  31590. minBoxWidth: 2,
  31591. maxBoxWidth: 40,
  31592. minGapWidth: 5
  31593. }
  31594. }
  31595. },
  31596. renderClipped: function(surface, ctx, dataClipRect) {
  31597. if (this.cleanRedraw) {
  31598. return;
  31599. }
  31600. // eslint-disable-next-line vars-on-top
  31601. var me = this,
  31602. attr = me.attr,
  31603. series = me.getSeries(),
  31604. renderer = attr.renderer,
  31605. rendererData = {
  31606. store: me.getStore()
  31607. },
  31608. itemCfg = {},
  31609. dataX = attr.dataX,
  31610. dataLow = attr.dataLow,
  31611. dataQ1 = attr.dataQ1,
  31612. dataMedian = attr.dataY,
  31613. dataQ3 = attr.dataQ3,
  31614. dataHigh = attr.dataHigh,
  31615. min = Math.min(dataClipRect[0], dataClipRect[2]),
  31616. max = Math.max(dataClipRect[0], dataClipRect[2]),
  31617. start = Math.max(0, Math.floor(min)),
  31618. end = Math.min(dataX.length - 1, Math.ceil(max)),
  31619. // surfaceMatrix = me.surfaceMatrix,
  31620. matrix = attr.matrix,
  31621. xx = matrix.elements[0],
  31622. // horizontal scaling can be < 0, if RTL
  31623. yy = matrix.elements[3],
  31624. dx = matrix.elements[4],
  31625. dy = matrix.elements[5],
  31626. // `xx` essentially represents the distance between data points in surface coordinates.
  31627. maxBoxWidth = Math.abs(xx) - attr.minGapWidth,
  31628. minBoxWidth = Math.min(maxBoxWidth, attr.maxBoxWidth),
  31629. boxWidth = Math.round(Math.max(attr.minBoxWidth, minBoxWidth)),
  31630. x, low, q1, median, q3, high, rendererParams, changes, i;
  31631. if (renderer) {
  31632. rendererParams = [
  31633. me,
  31634. itemCfg,
  31635. rendererData
  31636. ];
  31637. }
  31638. for (i = start; i <= end; i++) {
  31639. x = dataX[i] * xx + dx;
  31640. low = dataLow[i] * yy + dy;
  31641. q1 = dataQ1[i] * yy + dy;
  31642. median = dataMedian[i] * yy + dy;
  31643. q3 = dataQ3[i] * yy + dy;
  31644. high = dataHigh[i] * yy + dy;
  31645. // --- Draw Box ---
  31646. // Reuse 'itemCfg' object and 'rendererParams' arrays for better performance.
  31647. itemCfg.x = x;
  31648. itemCfg.low = low;
  31649. itemCfg.q1 = q1;
  31650. itemCfg.median = median;
  31651. itemCfg.q3 = q3;
  31652. itemCfg.high = high;
  31653. itemCfg.boxWidth = boxWidth;
  31654. if (renderer) {
  31655. rendererParams[3] = i;
  31656. changes = Ext.callback(renderer, null, rendererParams, 0, series);
  31657. Ext.apply(itemCfg, changes);
  31658. }
  31659. me.putMarker('items', itemCfg, i, !renderer);
  31660. }
  31661. }
  31662. });
  31663. /**
  31664. * A sprite that represents an individual box with whiskers.
  31665. * This sprite is meant to be managed by the {@link Ext.chart.series.sprite.BoxPlot}
  31666. * {@link Ext.chart.MarkerHolder MarkerHolder}, but can also be used independently:
  31667. *
  31668. * @example
  31669. * new Ext.draw.Container({
  31670. * width: 100,
  31671. * height: 100,
  31672. * renderTo: Ext.getBody(),
  31673. * sprites: [{
  31674. * type: 'boxplot',
  31675. * translationX: 50,
  31676. * translationY: 50
  31677. * }]
  31678. * });
  31679. *
  31680. * IMPORTANT: the attributes that represent y-coordinates are in screen coordinates,
  31681. * just like with any other sprite. For this particular sprite this means that, if 'low'
  31682. * and 'high' attributes are 10 and 90, then the minimium whisker is rendered at the top
  31683. * of a draw container {@link Ext.draw.Surface surface} at y = 10, and the maximum whisker
  31684. * is rendered at the bottom at y = 90. But because the series surface is flipped vertically
  31685. * in cartesian charts, this means that there minimum is rendered at the bottom and maximum
  31686. * at the top, just as one would expect.
  31687. */
  31688. Ext.define('Ext.chart.sprite.BoxPlot', {
  31689. extend: 'Ext.draw.sprite.Sprite',
  31690. alias: 'sprite.boxplot',
  31691. type: 'boxplot',
  31692. inheritableStatics: {
  31693. def: {
  31694. processors: {
  31695. /**
  31696. * @cfg {Number} [x=0] The coordinate of the horizontal center of a boxplot.
  31697. */
  31698. x: 'number',
  31699. /**
  31700. * @cfg {Number} [low=-20] The y-coordinate of the whisker that represents
  31701. * the minimum.
  31702. */
  31703. low: 'number',
  31704. /**
  31705. * @cfg {Number} [q1=-10] The y-coordinate of the box edge that represents
  31706. * the 1-st quartile.
  31707. */
  31708. q1: 'number',
  31709. /**
  31710. * @cfg {Number} [median=0] The y-coordinate of the line that represents the median.
  31711. */
  31712. median: 'number',
  31713. /**
  31714. * @cfg {Number} [q3=10] The y-coordinate of the box edge that represents
  31715. * the 3-rd quartile.
  31716. */
  31717. q3: 'number',
  31718. /**
  31719. * @cfg {Number} [high=20] The y-coordinate of the whisker that represents
  31720. * the maximum.
  31721. */
  31722. high: 'number',
  31723. /**
  31724. * @cfg {Number} [boxWidth=12] The width of the box in pixels.
  31725. */
  31726. boxWidth: 'number',
  31727. /**
  31728. * @cfg {Number} [whiskerWidth=0.5] The length of the lines at the ends
  31729. * of the whiskers, as a ratio of `boxWidth`.
  31730. */
  31731. whiskerWidth: 'number',
  31732. /**
  31733. * @cfg {Boolean} [crisp=true] Whether to snap the rendered lines to the pixel grid
  31734. * of not. Generally, it's best to have this set to `true` (which is the default)
  31735. * for pixel perfect results (especially on non-HiDPI displays), but for boxplots
  31736. * with small `boxWidth` visible artifacts caused by pixel grid snapping may become
  31737. * noticeable, and setting this to `false` can be a remedy at the expense
  31738. * of clarity.
  31739. */
  31740. crisp: 'bool'
  31741. },
  31742. triggers: {
  31743. x: 'bbox',
  31744. low: 'bbox',
  31745. high: 'bbox',
  31746. boxWidth: 'bbox',
  31747. whiskerWidth: 'bbox',
  31748. crisp: 'bbox'
  31749. },
  31750. defaults: {
  31751. x: 0,
  31752. low: -20,
  31753. q1: -10,
  31754. median: 0,
  31755. q3: 10,
  31756. high: 20,
  31757. boxWidth: 12,
  31758. whiskerWidth: 0.5,
  31759. crisp: true,
  31760. fillStyle: '#ccc',
  31761. strokeStyle: '#000'
  31762. }
  31763. }
  31764. },
  31765. updatePlainBBox: function(plain) {
  31766. var me = this,
  31767. attr = me.attr,
  31768. halfLineWidth = attr.lineWidth / 2,
  31769. x = attr.x - attr.boxWidth / 2 - halfLineWidth,
  31770. y = attr.high - halfLineWidth,
  31771. width = attr.boxWidth + attr.lineWidth,
  31772. height = attr.low - attr.high + attr.lineWidth;
  31773. plain.x = x;
  31774. plain.y = y;
  31775. plain.width = width;
  31776. plain.height = height;
  31777. },
  31778. render: function(surface, ctx) {
  31779. var me = this,
  31780. attr = me.attr;
  31781. attr.matrix.toContext(ctx);
  31782. // enable sprite transformations
  31783. if (attr.crisp) {
  31784. me.crispRender(surface, ctx);
  31785. } else {
  31786. me.softRender(surface, ctx);
  31787. }
  31788. //<debug>
  31789. // eslint-disable-next-line vars-on-top, one-var
  31790. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  31791. if (debug) {
  31792. // This assumes no part of the sprite is rendered after this call.
  31793. // If it is, we need to re-apply transformations.
  31794. // But the bounding box should always be rendered as is, untransformed.
  31795. this.attr.inverseMatrix.toContext(ctx);
  31796. if (debug.bbox) {
  31797. this.renderBBox(surface, ctx);
  31798. }
  31799. }
  31800. },
  31801. //</debug>
  31802. /**
  31803. * @private
  31804. * Renders a single box with whiskers.
  31805. * Changes to this method have to be reflected in the {@link #crispRender} as well.
  31806. * @param surface
  31807. * @param ctx
  31808. */
  31809. softRender: function(surface, ctx) {
  31810. var me = this,
  31811. attr = me.attr,
  31812. x = attr.x,
  31813. low = attr.low,
  31814. q1 = attr.q1,
  31815. median = attr.median,
  31816. q3 = attr.q3,
  31817. high = attr.high,
  31818. halfBoxWidth = attr.boxWidth / 2,
  31819. halfWhiskerWidth = attr.boxWidth * attr.whiskerWidth / 2,
  31820. dash = ctx.getLineDash();
  31821. ctx.setLineDash([]);
  31822. // Only stem can be dashed.
  31823. // Box.
  31824. ctx.beginPath();
  31825. ctx.moveTo(x - halfBoxWidth, q3);
  31826. ctx.lineTo(x + halfBoxWidth, q3);
  31827. ctx.lineTo(x + halfBoxWidth, q1);
  31828. ctx.lineTo(x - halfBoxWidth, q1);
  31829. ctx.closePath();
  31830. ctx.fillStroke(attr, true);
  31831. // Stem.
  31832. ctx.setLineDash(dash);
  31833. ctx.beginPath();
  31834. ctx.moveTo(x, q3);
  31835. ctx.lineTo(x, high);
  31836. ctx.moveTo(x, q1);
  31837. ctx.lineTo(x, low);
  31838. ctx.stroke();
  31839. ctx.setLineDash([]);
  31840. // Whiskers.
  31841. ctx.beginPath();
  31842. ctx.moveTo(x - halfWhiskerWidth, low);
  31843. ctx.lineTo(x + halfWhiskerWidth, low);
  31844. ctx.moveTo(x - halfBoxWidth, median);
  31845. ctx.lineTo(x + halfBoxWidth, median);
  31846. ctx.moveTo(x - halfWhiskerWidth, high);
  31847. ctx.lineTo(x + halfWhiskerWidth, high);
  31848. ctx.stroke();
  31849. },
  31850. alignLine: function(x, lineWidth) {
  31851. lineWidth = lineWidth || this.attr.lineWidth;
  31852. x = Math.round(x);
  31853. if (lineWidth % 2 === 1) {
  31854. x -= 0.5;
  31855. }
  31856. return x;
  31857. },
  31858. /**
  31859. * @private
  31860. * Renders a pixel-perfect single box with whiskers by aligning to the pixel grid.
  31861. * Changes to this method have to be reflected in the {@link #softRender} as well.
  31862. *
  31863. * Note: crisp image is only guaranteed when `attr.lineWidth` is a whole number.
  31864. * @param surface
  31865. * @param ctx
  31866. */
  31867. crispRender: function(surface, ctx) {
  31868. var me = this,
  31869. attr = me.attr,
  31870. x = attr.x,
  31871. low = me.alignLine(attr.low),
  31872. q1 = me.alignLine(attr.q1),
  31873. median = me.alignLine(attr.median),
  31874. q3 = me.alignLine(attr.q3),
  31875. high = me.alignLine(attr.high),
  31876. halfBoxWidth = attr.boxWidth / 2,
  31877. halfWhiskerWidth = attr.boxWidth * attr.whiskerWidth / 2,
  31878. stemX = me.alignLine(x),
  31879. boxLeft = me.alignLine(x - halfBoxWidth),
  31880. boxRight = me.alignLine(x + halfBoxWidth),
  31881. whiskerLeft = stemX + Math.round(-halfWhiskerWidth),
  31882. whiskerRight = stemX + Math.round(halfWhiskerWidth),
  31883. dash = ctx.getLineDash();
  31884. ctx.setLineDash([]);
  31885. // Only stem can be dashed.
  31886. // Box.
  31887. ctx.beginPath();
  31888. ctx.moveTo(boxLeft, q3);
  31889. ctx.lineTo(boxRight, q3);
  31890. ctx.lineTo(boxRight, q1);
  31891. ctx.lineTo(boxLeft, q1);
  31892. ctx.closePath();
  31893. ctx.fillStroke(attr, true);
  31894. // Stem.
  31895. ctx.setLineDash(dash);
  31896. ctx.beginPath();
  31897. ctx.moveTo(stemX, q3);
  31898. ctx.lineTo(stemX, high);
  31899. ctx.moveTo(stemX, q1);
  31900. ctx.lineTo(stemX, low);
  31901. ctx.stroke();
  31902. ctx.setLineDash([]);
  31903. // Whiskers.
  31904. ctx.beginPath();
  31905. ctx.moveTo(whiskerLeft, low);
  31906. ctx.lineTo(whiskerRight, low);
  31907. ctx.moveTo(boxLeft, median);
  31908. ctx.lineTo(boxRight, median);
  31909. ctx.moveTo(whiskerLeft, high);
  31910. ctx.lineTo(whiskerRight, high);
  31911. ctx.stroke();
  31912. }
  31913. });
  31914. /**
  31915. * A box plot chart is a useful tool for visializing data distribution within datasets.
  31916. * For example, salary ranges for a set of occupations, or life expectancy for a set
  31917. * of countries. A single box with whiskers displays the following values for a dataset:
  31918. *
  31919. * * minimum
  31920. * * lower quartile (Q1)
  31921. * * median (Q2)
  31922. * * higher quartile (Q3)
  31923. * * maximum
  31924. *
  31925. * For example:
  31926. *
  31927. * @example
  31928. * Ext.create({
  31929. * xtype: 'cartesian',
  31930. * width: 400,
  31931. * height: 400,
  31932. * renderTo: Ext.getBody(),
  31933. * insetPadding: '20 20 10 10',
  31934. * store: {
  31935. * data: [{
  31936. * category: 'Engineer IV',
  31937. * low: 110, q1: 130, median: 175, q3: 200, high: 225
  31938. * }, {
  31939. * category: 'Market',
  31940. * low: 75, q1: 125, median: 210, q3: 230, high: 255
  31941. * }]
  31942. * },
  31943. * axes: [
  31944. * {
  31945. * type: 'numeric',
  31946. * position: 'left',
  31947. * renderer: function (axis, text) {
  31948. * return '$' + text + ' K'
  31949. * }
  31950. * },
  31951. * {
  31952. * type: 'category',
  31953. * position: 'bottom'
  31954. * }
  31955. * ],
  31956. * series: {
  31957. * type: 'boxplot',
  31958. * xField: 'category',
  31959. * style: {
  31960. * maxBoxWidth: 50,
  31961. * lineWidth: 2
  31962. * }
  31963. * }
  31964. * });
  31965. *
  31966. */
  31967. Ext.define('Ext.chart.series.BoxPlot', {
  31968. extend: 'Ext.chart.series.Cartesian',
  31969. alias: 'series.boxplot',
  31970. type: 'boxplot',
  31971. seriesType: 'boxplotSeries',
  31972. isBoxPlot: true,
  31973. requires: [
  31974. 'Ext.chart.series.sprite.BoxPlot',
  31975. 'Ext.chart.sprite.BoxPlot'
  31976. ],
  31977. config: {
  31978. itemInstancing: {
  31979. type: 'boxplot',
  31980. animation: {
  31981. // Setting the duration of these attributes to zero because
  31982. // the 'data' attributes of the series sprite (MarkerHolder)
  31983. // will be animated instead, and then changes applied to
  31984. // the attributes of 'boxplot' instances instantly.
  31985. customDurations: {
  31986. x: 0,
  31987. low: 0,
  31988. q1: 0,
  31989. median: 0,
  31990. q3: 0,
  31991. high: 0
  31992. }
  31993. }
  31994. },
  31995. /**
  31996. * @cfg {String} [lowField='low']
  31997. * The name of the store record field that represents the smallest value of a dataset.
  31998. */
  31999. lowField: 'low',
  32000. /**
  32001. * @cfg {String} [q1Field='q1']
  32002. * The name of the store record field that represents the lower (1-st) quartile
  32003. * value of a dataset.
  32004. */
  32005. q1Field: 'q1',
  32006. /**
  32007. * @cfg {String} [medianField='median']
  32008. * The name of the store record field that represents the median of a dataset.
  32009. */
  32010. medianField: 'median',
  32011. /**
  32012. * @cfg {String} [q3Field='q3']
  32013. * The name of the store record field that represents the upper (3-rd) quartile
  32014. * value of a dataset.
  32015. */
  32016. q3Field: 'q3',
  32017. /**
  32018. * @cfg {String} [highField='high']
  32019. * The name of the store record field that represents the largest value of a dataset.
  32020. */
  32021. highField: 'high'
  32022. },
  32023. fieldCategoryY: [
  32024. 'Low',
  32025. 'Q1',
  32026. 'Median',
  32027. 'Q3',
  32028. 'High'
  32029. ],
  32030. updateXAxis: function(xAxis) {
  32031. xAxis.setExpandRangeBy(0.5);
  32032. this.callParent(arguments);
  32033. }
  32034. });
  32035. /**
  32036. * Limited cache is a size limited cache container that stores limited number of objects.
  32037. *
  32038. * When {@link #get} is called, the container will try to find the object in the list.
  32039. * If failed it will call the {@link #feeder} to create that object. If there are too many
  32040. * objects in the container, the old ones are removed.
  32041. *
  32042. * __Note:__ This is not using a Least Recently Used policy due to simplicity and performance
  32043. * consideration.
  32044. * @private
  32045. */
  32046. Ext.define('Ext.draw.LimitedCache', {
  32047. config: {
  32048. /**
  32049. * @cfg {Number}
  32050. * The amount limit of the cache.
  32051. */
  32052. limit: 40,
  32053. /**
  32054. * @cfg {Function}
  32055. * Function that generates the object when look-up failed.
  32056. * @return {Number}
  32057. */
  32058. feeder: function() {
  32059. return 0;
  32060. },
  32061. /**
  32062. * @cfg {Object}
  32063. * The scope for {@link #feeder}
  32064. */
  32065. scope: null
  32066. },
  32067. cache: null,
  32068. constructor: function(config) {
  32069. this.cache = {};
  32070. this.cache.list = [];
  32071. this.cache.tail = 0;
  32072. this.initConfig(config);
  32073. },
  32074. /**
  32075. * Get a cached object.
  32076. * @param {String} id
  32077. * @return {Object}
  32078. */
  32079. get: function(id) {
  32080. // TODO: Implement cache hit optimization
  32081. var cache = this.cache,
  32082. limit = this.getLimit(),
  32083. feeder = this.getFeeder(),
  32084. scope = this.getScope() || this;
  32085. if (cache[id]) {
  32086. return cache[id].value;
  32087. }
  32088. if (cache.list[cache.tail]) {
  32089. delete cache[cache.list[cache.tail].cacheId];
  32090. }
  32091. cache[id] = cache.list[cache.tail] = {
  32092. value: feeder.apply(scope, Array.prototype.slice.call(arguments, 1)),
  32093. cacheId: id
  32094. };
  32095. cache.tail++;
  32096. if (cache.tail === limit) {
  32097. cache.tail = 0;
  32098. }
  32099. return cache[id].value;
  32100. },
  32101. /**
  32102. * Clear all the objects.
  32103. */
  32104. clear: function() {
  32105. this.cache = {};
  32106. this.cache.list = [];
  32107. this.cache.tail = 0;
  32108. }
  32109. });
  32110. /**
  32111. * This class we summarize the data and returns it when required.
  32112. */
  32113. Ext.define("Ext.draw.SegmentTree", {
  32114. config: {
  32115. strategy: "double"
  32116. },
  32117. /**
  32118. * @private
  32119. * @param {Object} result
  32120. * @param {Number} last
  32121. * @param {Number} dataX
  32122. * @param {Number} dataOpen
  32123. * @param {Number} dataHigh
  32124. * @param {Number} dataLow
  32125. * @param {Number} dataClose
  32126. */
  32127. time: function(result, last, dataX, dataOpen, dataHigh, dataLow, dataClose) {
  32128. var start = 0,
  32129. lastOffset, lastOffsetEnd,
  32130. minimum = new Date(dataX[result.startIdx[0]]),
  32131. maximum = new Date(dataX[result.endIdx[last - 1]]),
  32132. extDate = Ext.Date,
  32133. units = [
  32134. [
  32135. extDate.MILLI,
  32136. 1,
  32137. 'ms1',
  32138. null
  32139. ],
  32140. [
  32141. extDate.MILLI,
  32142. 2,
  32143. 'ms2',
  32144. 'ms1'
  32145. ],
  32146. [
  32147. extDate.MILLI,
  32148. 5,
  32149. 'ms5',
  32150. 'ms1'
  32151. ],
  32152. [
  32153. extDate.MILLI,
  32154. 10,
  32155. 'ms10',
  32156. 'ms5'
  32157. ],
  32158. [
  32159. extDate.MILLI,
  32160. 50,
  32161. 'ms50',
  32162. 'ms10'
  32163. ],
  32164. [
  32165. extDate.MILLI,
  32166. 100,
  32167. 'ms100',
  32168. 'ms50'
  32169. ],
  32170. [
  32171. extDate.MILLI,
  32172. 500,
  32173. 'ms500',
  32174. 'ms100'
  32175. ],
  32176. [
  32177. extDate.SECOND,
  32178. 1,
  32179. 's1',
  32180. 'ms500'
  32181. ],
  32182. [
  32183. extDate.SECOND,
  32184. 10,
  32185. 's10',
  32186. 's1'
  32187. ],
  32188. [
  32189. extDate.SECOND,
  32190. 30,
  32191. 's30',
  32192. 's10'
  32193. ],
  32194. [
  32195. extDate.MINUTE,
  32196. 1,
  32197. 'mi1',
  32198. 's10'
  32199. ],
  32200. [
  32201. extDate.MINUTE,
  32202. 5,
  32203. 'mi5',
  32204. 'mi1'
  32205. ],
  32206. [
  32207. extDate.MINUTE,
  32208. 10,
  32209. 'mi10',
  32210. 'mi5'
  32211. ],
  32212. [
  32213. extDate.MINUTE,
  32214. 30,
  32215. 'mi30',
  32216. 'mi10'
  32217. ],
  32218. [
  32219. extDate.HOUR,
  32220. 1,
  32221. 'h1',
  32222. 'mi30'
  32223. ],
  32224. [
  32225. extDate.HOUR,
  32226. 6,
  32227. 'h6',
  32228. 'h1'
  32229. ],
  32230. [
  32231. extDate.HOUR,
  32232. 12,
  32233. 'h12',
  32234. 'h6'
  32235. ],
  32236. [
  32237. extDate.DAY,
  32238. 1,
  32239. 'd1',
  32240. 'h12'
  32241. ],
  32242. [
  32243. extDate.DAY,
  32244. 7,
  32245. 'd7',
  32246. 'd1'
  32247. ],
  32248. [
  32249. extDate.MONTH,
  32250. 1,
  32251. 'mo1',
  32252. 'd1'
  32253. ],
  32254. [
  32255. extDate.MONTH,
  32256. 3,
  32257. 'mo3',
  32258. 'mo1'
  32259. ],
  32260. [
  32261. extDate.MONTH,
  32262. 6,
  32263. 'mo6',
  32264. 'mo3'
  32265. ],
  32266. [
  32267. extDate.YEAR,
  32268. 1,
  32269. 'y1',
  32270. 'mo3'
  32271. ],
  32272. [
  32273. extDate.YEAR,
  32274. 5,
  32275. 'y5',
  32276. 'y1'
  32277. ],
  32278. [
  32279. extDate.YEAR,
  32280. 10,
  32281. 'y10',
  32282. 'y5'
  32283. ],
  32284. [
  32285. extDate.YEAR,
  32286. 100,
  32287. 'y100',
  32288. 'y10'
  32289. ]
  32290. ],
  32291. unitIdx, currentUnit,
  32292. plainStart = start,
  32293. plainEnd = last,
  32294. startIdxs = result.startIdx,
  32295. endIdxs = result.endIdx,
  32296. minIdxs = result.minIdx,
  32297. maxIdxs = result.maxIdx,
  32298. opens = result.open,
  32299. closes = result.close,
  32300. minXs = result.minX,
  32301. minYs = result.minY,
  32302. maxXs = result.maxX,
  32303. maxYs = result.maxY,
  32304. i, current;
  32305. for (unitIdx = 0; last > start + 1 && unitIdx < units.length; unitIdx++) {
  32306. minimum = new Date(dataX[startIdxs[0]]);
  32307. currentUnit = units[unitIdx];
  32308. minimum = extDate.align(minimum, currentUnit[0], currentUnit[1]);
  32309. // eslint-disable-next-line max-len
  32310. if (extDate.diff(minimum, maximum, currentUnit[0]) > dataX.length * 2 * currentUnit[1]) {
  32311. continue;
  32312. }
  32313. if (currentUnit[3] && result.map['time_' + currentUnit[3]]) {
  32314. lastOffset = result.map['time_' + currentUnit[3]][0];
  32315. lastOffsetEnd = result.map['time_' + currentUnit[3]][1];
  32316. } else {
  32317. lastOffset = plainStart;
  32318. lastOffsetEnd = plainEnd;
  32319. }
  32320. start = last;
  32321. current = minimum;
  32322. startIdxs[last] = startIdxs[lastOffset];
  32323. endIdxs[last] = endIdxs[lastOffset];
  32324. minIdxs[last] = minIdxs[lastOffset];
  32325. maxIdxs[last] = maxIdxs[lastOffset];
  32326. opens[last] = opens[lastOffset];
  32327. closes[last] = closes[lastOffset];
  32328. minXs[last] = minXs[lastOffset];
  32329. minYs[last] = minYs[lastOffset];
  32330. maxXs[last] = maxXs[lastOffset];
  32331. maxYs[last] = maxYs[lastOffset];
  32332. current = Ext.Date.add(current, currentUnit[0], currentUnit[1]);
  32333. for (i = lastOffset + 1; i < lastOffsetEnd; i++) {
  32334. if (dataX[endIdxs[i]] < +current) {
  32335. endIdxs[last] = endIdxs[i];
  32336. closes[last] = closes[i];
  32337. if (maxYs[i] > maxYs[last]) {
  32338. maxYs[last] = maxYs[i];
  32339. maxXs[last] = maxXs[i];
  32340. maxIdxs[last] = maxIdxs[i];
  32341. }
  32342. if (minYs[i] < minYs[last]) {
  32343. minYs[last] = minYs[i];
  32344. minXs[last] = minXs[i];
  32345. minIdxs[last] = minIdxs[i];
  32346. }
  32347. } else {
  32348. last++;
  32349. startIdxs[last] = startIdxs[i];
  32350. endIdxs[last] = endIdxs[i];
  32351. minIdxs[last] = minIdxs[i];
  32352. maxIdxs[last] = maxIdxs[i];
  32353. opens[last] = opens[i];
  32354. closes[last] = closes[i];
  32355. minXs[last] = minXs[i];
  32356. minYs[last] = minYs[i];
  32357. maxXs[last] = maxXs[i];
  32358. maxYs[last] = maxYs[i];
  32359. current = Ext.Date.add(current, currentUnit[0], currentUnit[1]);
  32360. }
  32361. }
  32362. if (last > start) {
  32363. result.map['time_' + currentUnit[2]] = [
  32364. start,
  32365. last
  32366. ];
  32367. }
  32368. }
  32369. },
  32370. /**
  32371. * @private
  32372. * @param {Object} result
  32373. * @param {Number} position
  32374. * @param {Number} dataX
  32375. * @param {Number} dataOpen
  32376. * @param {Number} dataHigh
  32377. * @param {Number} dataLow
  32378. * @param {Number} dataClose
  32379. */
  32380. "double": function(result, position, dataX, dataOpen, dataHigh, dataLow, dataClose) {
  32381. var offset = 0,
  32382. lastOffset,
  32383. step = 1,
  32384. i, startIdx, endIdx, minIdx, maxIdx, open, close, minX, minY, maxX, maxY;
  32385. while (position > offset + 1) {
  32386. lastOffset = offset;
  32387. offset = position;
  32388. step += step;
  32389. for (i = lastOffset; i < offset; i += 2) {
  32390. if (i === offset - 1) {
  32391. startIdx = result.startIdx[i];
  32392. endIdx = result.endIdx[i];
  32393. minIdx = result.minIdx[i];
  32394. maxIdx = result.maxIdx[i];
  32395. open = result.open[i];
  32396. close = result.close[i];
  32397. minX = result.minX[i];
  32398. minY = result.minY[i];
  32399. maxX = result.maxX[i];
  32400. maxY = result.maxY[i];
  32401. } else {
  32402. startIdx = result.startIdx[i];
  32403. endIdx = result.endIdx[i + 1];
  32404. open = result.open[i];
  32405. close = result.close[i];
  32406. if (result.minY[i] <= result.minY[i + 1]) {
  32407. minIdx = result.minIdx[i];
  32408. minX = result.minX[i];
  32409. minY = result.minY[i];
  32410. } else {
  32411. minIdx = result.minIdx[i + 1];
  32412. minX = result.minX[i + 1];
  32413. minY = result.minY[i + 1];
  32414. }
  32415. if (result.maxY[i] >= result.maxY[i + 1]) {
  32416. maxIdx = result.maxIdx[i];
  32417. maxX = result.maxX[i];
  32418. maxY = result.maxY[i];
  32419. } else {
  32420. maxIdx = result.maxIdx[i + 1];
  32421. maxX = result.maxX[i + 1];
  32422. maxY = result.maxY[i + 1];
  32423. }
  32424. }
  32425. result.startIdx[position] = startIdx;
  32426. result.endIdx[position] = endIdx;
  32427. result.minIdx[position] = minIdx;
  32428. result.maxIdx[position] = maxIdx;
  32429. result.open[position] = open;
  32430. result.close[position] = close;
  32431. result.minX[position] = minX;
  32432. result.minY[position] = minY;
  32433. result.maxX[position] = maxX;
  32434. result.maxY[position] = maxY;
  32435. position++;
  32436. }
  32437. result.map['double_' + step] = [
  32438. offset,
  32439. position
  32440. ];
  32441. }
  32442. },
  32443. /**
  32444. * @method
  32445. * @private
  32446. */
  32447. none: Ext.emptyFn,
  32448. /**
  32449. * @private
  32450. *
  32451. * @param {Number} dataX
  32452. * @param {Number} dataOpen
  32453. * @param {Number} dataHigh
  32454. * @param {Number} dataLow
  32455. * @param {Number} dataClose
  32456. * @return {Object}
  32457. */
  32458. aggregateData: function(dataX, dataOpen, dataHigh, dataLow, dataClose) {
  32459. var length = dataX.length,
  32460. startIdx = [],
  32461. endIdx = [],
  32462. minIdx = [],
  32463. maxIdx = [],
  32464. open = [],
  32465. minX = [],
  32466. minY = [],
  32467. maxX = [],
  32468. maxY = [],
  32469. close = [],
  32470. result = {
  32471. startIdx: startIdx,
  32472. endIdx: endIdx,
  32473. minIdx: minIdx,
  32474. maxIdx: maxIdx,
  32475. open: open,
  32476. minX: minX,
  32477. minY: minY,
  32478. maxX: maxX,
  32479. maxY: maxY,
  32480. close: close
  32481. },
  32482. i;
  32483. for (i = 0; i < length; i++) {
  32484. startIdx[i] = i;
  32485. endIdx[i] = i;
  32486. minIdx[i] = i;
  32487. maxIdx[i] = i;
  32488. open[i] = dataOpen[i];
  32489. minX[i] = dataX[i];
  32490. minY[i] = dataLow[i];
  32491. maxX[i] = dataX[i];
  32492. maxY[i] = dataHigh[i];
  32493. close[i] = dataClose[i];
  32494. }
  32495. result.map = {
  32496. original: [
  32497. 0,
  32498. length
  32499. ]
  32500. };
  32501. if (length) {
  32502. this[this.getStrategy()](result, length, dataX, dataOpen, dataHigh, dataLow, dataClose);
  32503. }
  32504. return result;
  32505. },
  32506. /**
  32507. * @private
  32508. * @param {Object} items
  32509. * @param {Number} start
  32510. * @param {Number} end
  32511. * @param {Number} key
  32512. * @return {*}
  32513. */
  32514. binarySearchMin: function(items, start, end, key) {
  32515. var dx = this.dataX,
  32516. mid, val;
  32517. if (key <= dx[items.startIdx[0]]) {
  32518. return start;
  32519. }
  32520. if (key >= dx[items.startIdx[end - 1]]) {
  32521. return end - 1;
  32522. }
  32523. while (start + 1 < end) {
  32524. mid = (start + end) >> 1;
  32525. val = dx[items.startIdx[mid]];
  32526. if (val === key) {
  32527. return mid;
  32528. } else if (val < key) {
  32529. start = mid;
  32530. } else {
  32531. end = mid;
  32532. }
  32533. }
  32534. return start;
  32535. },
  32536. /**
  32537. * @private
  32538. * @param {Object} items
  32539. * @param {Number} start
  32540. * @param {Number} end
  32541. * @param {Number} key
  32542. * @return {*}
  32543. */
  32544. binarySearchMax: function(items, start, end, key) {
  32545. var dx = this.dataX,
  32546. mid, val;
  32547. if (key <= dx[items.endIdx[0]]) {
  32548. return start;
  32549. }
  32550. if (key >= dx[items.endIdx[end - 1]]) {
  32551. return end - 1;
  32552. }
  32553. while (start + 1 < end) {
  32554. mid = (start + end) >> 1;
  32555. val = dx[items.endIdx[mid]];
  32556. if (val === key) {
  32557. return mid;
  32558. } else if (val < key) {
  32559. start = mid;
  32560. } else {
  32561. end = mid;
  32562. }
  32563. }
  32564. return end;
  32565. },
  32566. constructor: function(config) {
  32567. this.initConfig(config);
  32568. },
  32569. /**
  32570. * Sets the data of the segment tree.
  32571. * @param {Number} dataX
  32572. * @param {Number} dataOpen
  32573. * @param {Number} dataHigh
  32574. * @param {Number} dataLow
  32575. * @param {Number} dataClose
  32576. */
  32577. setData: function(dataX, dataOpen, dataHigh, dataLow, dataClose) {
  32578. if (!dataHigh) {
  32579. dataClose = dataLow = dataHigh = dataOpen;
  32580. }
  32581. this.dataX = dataX;
  32582. this.dataOpen = dataOpen;
  32583. this.dataHigh = dataHigh;
  32584. this.dataLow = dataLow;
  32585. this.dataClose = dataClose;
  32586. if (dataX.length === dataHigh.length && dataX.length === dataLow.length) {
  32587. this.cache = this.aggregateData(dataX, dataOpen, dataHigh, dataLow, dataClose);
  32588. }
  32589. },
  32590. /**
  32591. * Returns the minimum range of data that fits the given range and step size.
  32592. *
  32593. * @param {Number} min
  32594. * @param {Number} max
  32595. * @param {Number} estStep
  32596. * @return {Object} The aggregation information.
  32597. * @return {Number} return.start
  32598. * @return {Number} return.end
  32599. * @return {Object} return.data The aggregated data
  32600. */
  32601. getAggregation: function(min, max, estStep) {
  32602. if (!this.cache) {
  32603. return null;
  32604. }
  32605. // eslint-disable-next-line vars-on-top
  32606. var minStep = Infinity,
  32607. range = this.dataX[this.dataX.length - 1] - this.dataX[0],
  32608. cacheMap = this.cache.map,
  32609. result = cacheMap.original,
  32610. name, positions, ln, step, minIdx, maxIdx;
  32611. for (name in cacheMap) {
  32612. positions = cacheMap[name];
  32613. ln = positions[1] - positions[0] - 1;
  32614. step = range / ln;
  32615. if (estStep <= step && step < minStep) {
  32616. result = positions;
  32617. minStep = step;
  32618. }
  32619. }
  32620. minIdx = Math.max(this.binarySearchMin(this.cache, result[0], result[1], min), result[0]);
  32621. maxIdx = Math.min(this.binarySearchMax(this.cache, result[0], result[1], max) + 1, result[1]);
  32622. return {
  32623. data: this.cache,
  32624. start: minIdx,
  32625. end: maxIdx
  32626. };
  32627. }
  32628. });
  32629. /**
  32630. *
  32631. */
  32632. Ext.define('Ext.chart.series.sprite.Aggregative', {
  32633. extend: 'Ext.chart.series.sprite.Cartesian',
  32634. requires: [
  32635. 'Ext.draw.LimitedCache',
  32636. 'Ext.draw.SegmentTree'
  32637. ],
  32638. inheritableStatics: {
  32639. def: {
  32640. processors: {
  32641. /**
  32642. * @cfg {Number[]} [dataHigh=null] Data items representing the high values
  32643. * of the aggregated data.
  32644. */
  32645. dataHigh: 'data',
  32646. /**
  32647. * @cfg {Number[]} [dataLow=null] Data items representing the low values
  32648. * of the aggregated data.
  32649. */
  32650. dataLow: 'data',
  32651. /**
  32652. * @cfg {Number[]} [dataClose=null] Data items representing the closing values
  32653. * of the aggregated data.
  32654. */
  32655. dataClose: 'data'
  32656. },
  32657. aliases: {
  32658. /**
  32659. * @cfg {Number[]} [dataOpen=null] Data items representing the opening values
  32660. * of the aggregated data.
  32661. */
  32662. dataOpen: 'dataY'
  32663. },
  32664. defaults: {
  32665. dataHigh: null,
  32666. dataLow: null,
  32667. dataClose: null
  32668. }
  32669. }
  32670. },
  32671. config: {
  32672. aggregator: {}
  32673. },
  32674. applyAggregator: function(aggregator, oldAggr) {
  32675. return Ext.factory(aggregator, Ext.draw.SegmentTree, oldAggr);
  32676. },
  32677. constructor: function() {
  32678. this.callParent(arguments);
  32679. },
  32680. processDataY: function() {
  32681. var me = this,
  32682. attr = me.attr,
  32683. high = attr.dataHigh,
  32684. low = attr.dataLow,
  32685. close = attr.dataClose,
  32686. open = attr.dataY,
  32687. aggregator;
  32688. me.callParent(arguments);
  32689. if (attr.dataX && open && open.length > 0) {
  32690. aggregator = me.getAggregator();
  32691. if (high) {
  32692. aggregator.setData(attr.dataX, attr.dataY, high, low, close);
  32693. } else {
  32694. aggregator.setData(attr.dataX, attr.dataY);
  32695. }
  32696. }
  32697. },
  32698. getGapWidth: function() {
  32699. return 1;
  32700. },
  32701. renderClipped: function(surface, ctx, dataClipRect, surfaceClipRect) {
  32702. var me = this,
  32703. min = Math.min(dataClipRect[0], dataClipRect[2]),
  32704. max = Math.max(dataClipRect[0], dataClipRect[2]),
  32705. aggregator = me.getAggregator(),
  32706. aggregates = aggregator && aggregator.getAggregation(min, max, (max - min) / surfaceClipRect[2] * me.getGapWidth());
  32707. if (aggregates) {
  32708. me.dataStart = aggregates.data.startIdx[aggregates.start];
  32709. me.dataEnd = aggregates.data.endIdx[aggregates.end - 1];
  32710. me.renderAggregates(aggregates.data, aggregates.start, aggregates.end, surface, ctx, dataClipRect, surfaceClipRect);
  32711. }
  32712. }
  32713. });
  32714. /**
  32715. * @class Ext.chart.series.sprite.CandleStick
  32716. * @extends Ext.chart.series.sprite.Aggregative
  32717. *
  32718. * CandleStick series sprite.
  32719. */
  32720. Ext.define('Ext.chart.series.sprite.CandleStick', {
  32721. alias: 'sprite.candlestickSeries',
  32722. extend: 'Ext.chart.series.sprite.Aggregative',
  32723. inheritableStatics: {
  32724. def: {
  32725. processors: {
  32726. raiseStyle: function(n, o) {
  32727. return Ext.merge({}, o || {}, n);
  32728. },
  32729. dropStyle: function(n, o) {
  32730. return Ext.merge({}, o || {}, n);
  32731. },
  32732. /**
  32733. * @cfg {Number} [barWidth=15] The bar width of the candles.
  32734. */
  32735. barWidth: 'number',
  32736. /**
  32737. * @cfg {Number} [padding=3] The amount of padding between candles.
  32738. */
  32739. padding: 'number',
  32740. /**
  32741. * @cfg {String} [ohlcType='candlestick'] Determines whether candlestick
  32742. * or ohlc is used.
  32743. */
  32744. ohlcType: 'enums(candlestick,ohlc)'
  32745. },
  32746. defaults: {
  32747. raiseStyle: {
  32748. strokeStyle: 'green',
  32749. fillStyle: 'green'
  32750. },
  32751. dropStyle: {
  32752. strokeStyle: 'red',
  32753. fillStyle: 'red'
  32754. },
  32755. barWidth: 15,
  32756. padding: 3,
  32757. lineJoin: 'miter',
  32758. miterLimit: 5,
  32759. ohlcType: 'candlestick'
  32760. },
  32761. triggers: {
  32762. raiseStyle: 'raiseStyle',
  32763. dropStyle: 'dropStyle'
  32764. },
  32765. updaters: {
  32766. raiseStyle: function() {
  32767. var me = this,
  32768. tpl = me.raiseTemplate;
  32769. if (tpl) {
  32770. tpl.setAttributes(me.attr.raiseStyle);
  32771. }
  32772. },
  32773. dropStyle: function() {
  32774. var me = this,
  32775. tpl = me.dropTemplate;
  32776. if (tpl) {
  32777. tpl.setAttributes(me.attr.dropStyle);
  32778. }
  32779. }
  32780. }
  32781. }
  32782. },
  32783. candlestick: function(ctx, open, high, low, close, mid, halfWidth) {
  32784. var minOC = Math.min(open, close),
  32785. maxOC = Math.max(open, close);
  32786. // lower stick
  32787. ctx.moveTo(mid, low);
  32788. ctx.lineTo(mid, minOC);
  32789. // body rect
  32790. ctx.moveTo(mid + halfWidth, maxOC);
  32791. ctx.lineTo(mid + halfWidth, minOC);
  32792. ctx.lineTo(mid - halfWidth, minOC);
  32793. ctx.lineTo(mid - halfWidth, maxOC);
  32794. ctx.closePath();
  32795. // upper stick
  32796. ctx.moveTo(mid, high);
  32797. ctx.lineTo(mid, maxOC);
  32798. },
  32799. ohlc: function(ctx, open, high, low, close, mid, halfWidth) {
  32800. ctx.moveTo(mid, high);
  32801. ctx.lineTo(mid, low);
  32802. ctx.moveTo(mid, open);
  32803. ctx.lineTo(mid - halfWidth, open);
  32804. ctx.moveTo(mid, close);
  32805. ctx.lineTo(mid + halfWidth, close);
  32806. },
  32807. constructor: function() {
  32808. var me = this,
  32809. Rect = Ext.draw.sprite.Rect;
  32810. me.callParent(arguments);
  32811. me.raiseTemplate = new Rect({
  32812. parent: me
  32813. });
  32814. me.dropTemplate = new Rect({
  32815. parent: me
  32816. });
  32817. },
  32818. getGapWidth: function() {
  32819. var attr = this.attr,
  32820. barWidth = attr.barWidth,
  32821. padding = attr.padding;
  32822. return barWidth + padding;
  32823. },
  32824. renderAggregates: function(aggregates, start, end, surface, ctx, clip) {
  32825. var me = this,
  32826. attr = me.attr,
  32827. ohlcType = attr.ohlcType,
  32828. series = me.getSeries(),
  32829. matrix = attr.matrix,
  32830. xx = matrix.getXX(),
  32831. yy = matrix.getYY(),
  32832. dx = matrix.getDX(),
  32833. dy = matrix.getDY(),
  32834. halfWidth = Math.round(attr.barWidth * 0.5),
  32835. dataX = attr.dataX,
  32836. opens = aggregates.open,
  32837. closes = aggregates.close,
  32838. maxYs = aggregates.maxY,
  32839. minYs = aggregates.minY,
  32840. startIdxs = aggregates.startIdx,
  32841. pixelAdjust = attr.lineWidth * surface.devicePixelRatio / 2,
  32842. renderer = attr.renderer,
  32843. rendererConfig = renderer && {},
  32844. rendererParams, rendererChanges, open, high, low, close, mid, i, template;
  32845. me.rendererData = me.rendererData || {
  32846. store: me.getStore()
  32847. };
  32848. pixelAdjust -= Math.floor(pixelAdjust);
  32849. // Render raises.
  32850. ctx.save();
  32851. template = me.raiseTemplate;
  32852. template.useAttributes(ctx, clip);
  32853. if (!renderer) {
  32854. ctx.beginPath();
  32855. }
  32856. for (i = start; i < end; i++) {
  32857. if (opens[i] <= closes[i]) {
  32858. open = Math.round(opens[i] * yy + dy) + pixelAdjust;
  32859. high = Math.round(maxYs[i] * yy + dy) + pixelAdjust;
  32860. low = Math.round(minYs[i] * yy + dy) + pixelAdjust;
  32861. close = Math.round(closes[i] * yy + dy) + pixelAdjust;
  32862. mid = Math.round(dataX[startIdxs[i]] * xx + dx) + pixelAdjust;
  32863. if (renderer) {
  32864. ctx.save();
  32865. ctx.beginPath();
  32866. rendererConfig.open = open;
  32867. rendererConfig.high = high;
  32868. rendererConfig.low = low;
  32869. rendererConfig.close = close;
  32870. rendererConfig.mid = mid;
  32871. rendererConfig.halfWidth = halfWidth;
  32872. rendererParams = [
  32873. me,
  32874. rendererConfig,
  32875. me.rendererData,
  32876. i
  32877. ];
  32878. rendererChanges = Ext.callback(renderer, null, rendererParams, 0, series);
  32879. Ext.apply(ctx, rendererChanges);
  32880. }
  32881. me[ohlcType](ctx, open, high, low, close, mid, halfWidth);
  32882. if (renderer) {
  32883. ctx.fillStroke(template.attr);
  32884. ctx.restore();
  32885. }
  32886. }
  32887. }
  32888. if (!renderer) {
  32889. ctx.fillStroke(template.attr);
  32890. }
  32891. ctx.restore();
  32892. // Render drops.
  32893. ctx.save();
  32894. template = me.dropTemplate;
  32895. template.useAttributes(ctx, clip);
  32896. if (!renderer) {
  32897. ctx.beginPath();
  32898. }
  32899. for (i = start; i < end; i++) {
  32900. if (opens[i] > closes[i]) {
  32901. open = Math.round(opens[i] * yy + dy) + pixelAdjust;
  32902. high = Math.round(maxYs[i] * yy + dy) + pixelAdjust;
  32903. low = Math.round(minYs[i] * yy + dy) + pixelAdjust;
  32904. close = Math.round(closes[i] * yy + dy) + pixelAdjust;
  32905. mid = Math.round(dataX[startIdxs[i]] * xx + dx) + pixelAdjust;
  32906. if (renderer) {
  32907. ctx.save();
  32908. ctx.beginPath();
  32909. rendererConfig.open = open;
  32910. rendererConfig.high = high;
  32911. rendererConfig.low = low;
  32912. rendererConfig.close = close;
  32913. rendererConfig.mid = mid;
  32914. rendererConfig.halfWidth = halfWidth;
  32915. rendererParams = [
  32916. me,
  32917. rendererConfig,
  32918. me.rendererData,
  32919. i
  32920. ];
  32921. rendererChanges = Ext.callback(renderer, null, rendererParams, 0, me.getSeries());
  32922. Ext.apply(ctx, rendererChanges);
  32923. }
  32924. me[ohlcType](ctx, open, high, low, close, mid, halfWidth);
  32925. if (renderer) {
  32926. ctx.fillStroke(template.attr);
  32927. ctx.restore();
  32928. }
  32929. }
  32930. }
  32931. if (!renderer) {
  32932. ctx.fillStroke(template.attr);
  32933. }
  32934. ctx.restore();
  32935. }
  32936. });
  32937. /**
  32938. * @class Ext.chart.series.CandleStick
  32939. * @extends Ext.chart.series.Cartesian
  32940. *
  32941. * Creates a candlestick or OHLC Chart.
  32942. *
  32943. * CandleStick series are typically used to plot price movements of a security on an exchange
  32944. * over time. The series can be used with the 'time' axis, but since exchanges often close
  32945. * for weekends, and the price data has gaps for those days, it's more practical to use this series
  32946. * with the 'category' axis to avoid rendering those data gaps. The 'category' axis has no notion
  32947. * of time (and thus gaps) and treats every Date object (value of the 'xField') as a unique
  32948. * category. However, it also means that it doesn't support the 'dateFormat' config,
  32949. * which can be easily remedied with a 'renderer' that formats a Date object for use
  32950. * as an axis label. For example:
  32951. *
  32952. * @example
  32953. * new Ext.chart.CartesianChart({
  32954. * xtype: 'cartesian',
  32955. * renderTo: document.body,
  32956. * width: 700,
  32957. * height: 500,
  32958. * insetPadding: 20,
  32959. * innerPadding: '0 20 0 20',
  32960. *
  32961. * store: {
  32962. * data: [
  32963. * {
  32964. * time: new Date('Nov 17 2016'),
  32965. * o: 52.40, h: 52.74, l: 52.18, c: 52.29
  32966. * },
  32967. * {
  32968. * time: new Date('Nov 18 2016'),
  32969. * o: 51.87, h: 52.22, l: 51.51, c: 52.04
  32970. * },
  32971. * {
  32972. * time: new Date('Nov 21 2016'),
  32973. * o: 53.02, h: 53.40, l: 53.02, c: 53.33
  32974. * },
  32975. * {
  32976. * time: new Date('Nov 22 2016'),
  32977. * o: 53.48, h: 53.80, l: 53.13, c: 53.70
  32978. * },
  32979. * {
  32980. * time: new Date('Nov 23 2016'),
  32981. * o: 52.85, h: 53.39, l: 52.76, c: 53.28
  32982. * },
  32983. * {
  32984. * time: new Date('Nov 25 2016'),
  32985. * o: 53.28, h: 53.45, l: 53.20, c: 53.40
  32986. * },
  32987. * {
  32988. * time: new Date('Nov 28 2016'),
  32989. * o: 52.51, h: 52.58, l: 51.96, c: 52.00
  32990. * },
  32991. * {
  32992. * time: new Date('Nov 29 2016'),
  32993. * o: 51.25, h: 51.98, l: 51.10, c: 51.79
  32994. * },
  32995. * {
  32996. * time: new Date('Nov 30 2016'),
  32997. * o: 53.65, h: 54.56, l: 53.60, c: 54.17
  32998. * },
  32999. * {
  33000. * time: new Date('Dec 01 2016'),
  33001. * o: 55.26, h: 55.75, l: 54.94, c: 55.13
  33002. * }
  33003. * ]
  33004. * },
  33005. * axes: [
  33006. * {
  33007. * type: 'numeric',
  33008. * position: 'left'
  33009. * },
  33010. * {
  33011. * type: 'category',
  33012. * position: 'bottom',
  33013. *
  33014. * renderer: function (axis, value) {
  33015. * return Ext.Date.format(value, 'M j\nY');
  33016. * }
  33017. * }
  33018. * ],
  33019. * series: {
  33020. * type: 'candlestick',
  33021. *
  33022. * xField: 'time',
  33023. *
  33024. * openField: 'o',
  33025. * highField: 'h',
  33026. * lowField: 'l',
  33027. * closeField: 'c',
  33028. *
  33029. * style: {
  33030. * barWidth: 10,
  33031. *
  33032. * dropStyle: {
  33033. * fill: 'rgb(222, 87, 87)',
  33034. * stroke: 'rgb(222, 87, 87)',
  33035. * lineWidth: 3
  33036. * },
  33037. * raiseStyle: {
  33038. * fill: 'rgb(48, 189, 167)',
  33039. * stroke: 'rgb(48, 189, 167)',
  33040. * lineWidth: 3
  33041. * }
  33042. * }
  33043. * }
  33044. * });
  33045. */
  33046. Ext.define('Ext.chart.series.CandleStick', {
  33047. extend: 'Ext.chart.series.Cartesian',
  33048. requires: [
  33049. 'Ext.chart.series.sprite.CandleStick'
  33050. ],
  33051. alias: 'series.candlestick',
  33052. type: 'candlestick',
  33053. seriesType: 'candlestickSeries',
  33054. isCandleStick: true,
  33055. config: {
  33056. /**
  33057. * @cfg {String} openField
  33058. * The store record field name that represents the opening value of the given period.
  33059. */
  33060. openField: null,
  33061. /**
  33062. * @cfg {String} highField
  33063. * The store record field name that represents the highest value of the time interval
  33064. * represented.
  33065. */
  33066. highField: null,
  33067. /**
  33068. * @cfg {String} lowField
  33069. * The store record field name that represents the lowest value of the time interval
  33070. * represented.
  33071. */
  33072. lowField: null,
  33073. /**
  33074. * @cfg {String} closeField
  33075. * The store record field name that represents the closing value of the given period.
  33076. */
  33077. closeField: null
  33078. },
  33079. fieldCategoryY: [
  33080. 'Open',
  33081. 'High',
  33082. 'Low',
  33083. 'Close'
  33084. ],
  33085. themeColorCount: function() {
  33086. return 2;
  33087. }
  33088. });
  33089. /**
  33090. * @abstract
  33091. * @class Ext.chart.series.Polar
  33092. * @extends Ext.chart.series.Series
  33093. *
  33094. * Common base class for series implementations that plot values using polar coordinates.
  33095. *
  33096. * Polar charts accept angles in radians. You can calculate radians with the following
  33097. * formula:
  33098. *
  33099. * radians = degrees x Π/180
  33100. */
  33101. Ext.define('Ext.chart.series.Polar', {
  33102. extend: 'Ext.chart.series.Series',
  33103. config: {
  33104. /**
  33105. * @cfg {Number} [rotation=0]
  33106. * The angle in radians at which the first polar series item should start.
  33107. */
  33108. rotation: 0,
  33109. /**
  33110. * @cfg {Number} radius
  33111. * @private
  33112. * Use {@link Ext.chart.series.Pie#cfg!radiusFactor radiusFactor} instead.
  33113. *
  33114. * The internally used radius of the polar series. Set to `null` will fit the
  33115. * polar series to the boundary.
  33116. */
  33117. radius: null,
  33118. /**
  33119. * @cfg {Array} center for the polar series.
  33120. */
  33121. center: [
  33122. 0,
  33123. 0
  33124. ],
  33125. /**
  33126. * @cfg {Number} [offsetX=0]
  33127. * The x-offset of center of the polar series related to the center of the boundary.
  33128. */
  33129. offsetX: 0,
  33130. /**
  33131. * @cfg {Number} [offsetY=0]
  33132. * The y-offset of center of the polar series related to the center of the boundary.
  33133. */
  33134. offsetY: 0,
  33135. /**
  33136. * @cfg {Boolean} [showInLegend=true]
  33137. * Whether to add the series elements as legend items.
  33138. */
  33139. showInLegend: true,
  33140. /**
  33141. * @private
  33142. * @cfg {String} xField
  33143. */
  33144. xField: null,
  33145. /**
  33146. * @private
  33147. * @cfg {String} yField
  33148. */
  33149. yField: null,
  33150. /**
  33151. * @cfg {String} angleField
  33152. * The store record field name for the angular axes in radar charts,
  33153. * or the size of the slices in pie charts.
  33154. */
  33155. angleField: null,
  33156. /**
  33157. * @cfg {String} radiusField
  33158. * The store record field name for the radial axes in radar charts,
  33159. * or the radius of the slices in pie charts.
  33160. */
  33161. radiusField: null,
  33162. xAxis: null,
  33163. yAxis: null
  33164. },
  33165. directions: [
  33166. 'X',
  33167. 'Y'
  33168. ],
  33169. fieldCategoryX: [
  33170. 'X'
  33171. ],
  33172. fieldCategoryY: [
  33173. 'Y'
  33174. ],
  33175. deprecatedConfigs: {
  33176. field: 'angleField',
  33177. lengthField: 'radiusField'
  33178. },
  33179. constructor: function(config) {
  33180. var me = this,
  33181. configurator = me.self.getConfigurator(),
  33182. configs = configurator.configs,
  33183. p;
  33184. if (config) {
  33185. for (p in me.deprecatedConfigs) {
  33186. if (p in config && !(config in configs)) {
  33187. Ext.raise("'" + p + "' config has been deprecated. Please use the '" + me.deprecatedConfigs[p] + "' config instead.");
  33188. }
  33189. }
  33190. }
  33191. me.callParent([
  33192. config
  33193. ]);
  33194. },
  33195. getXField: function() {
  33196. return this.getAngleField();
  33197. },
  33198. updateXField: function(value) {
  33199. this.setAngleField(value);
  33200. },
  33201. getYField: function() {
  33202. return this.getRadiusField();
  33203. },
  33204. updateYField: function(value) {
  33205. this.setRadiusField(value);
  33206. },
  33207. applyXAxis: function(newAxis, oldAxis) {
  33208. return this.getChart().getAxis(newAxis) || oldAxis;
  33209. },
  33210. applyYAxis: function(newAxis, oldAxis) {
  33211. return this.getChart().getAxis(newAxis) || oldAxis;
  33212. },
  33213. getXRange: function() {
  33214. return [
  33215. this.dataRange[0],
  33216. this.dataRange[2]
  33217. ];
  33218. },
  33219. getYRange: function() {
  33220. return [
  33221. this.dataRange[1],
  33222. this.dataRange[3]
  33223. ];
  33224. },
  33225. themeColorCount: function() {
  33226. var me = this,
  33227. store = me.getStore(),
  33228. count = store && store.getCount() || 0;
  33229. return count;
  33230. },
  33231. isStoreDependantColorCount: true,
  33232. getDefaultSpriteConfig: function() {
  33233. return {
  33234. type: this.seriesType,
  33235. renderer: this.getRenderer(),
  33236. centerX: 0,
  33237. centerY: 0,
  33238. rotationCenterX: 0,
  33239. rotationCenterY: 0
  33240. };
  33241. },
  33242. applyRotation: function(rotation) {
  33243. return Ext.draw.sprite.AttributeParser.angle(Ext.draw.Draw.rad(rotation));
  33244. },
  33245. updateRotation: function(rotation) {
  33246. var sprites = this.getSprites();
  33247. if (sprites && sprites[0]) {
  33248. sprites[0].setAttributes({
  33249. baseRotation: rotation
  33250. });
  33251. }
  33252. }
  33253. });
  33254. /**
  33255. * Displays a gauge chart.
  33256. *
  33257. * @example
  33258. * Ext.create({
  33259. * xtype: 'polar',
  33260. * renderTo: document.body,
  33261. * width: 600,
  33262. * height: 400,
  33263. * store: {
  33264. * fields: ['mph', 'fuel', 'temp', 'rpm'],
  33265. * data: [{
  33266. * mph: 65,
  33267. * fuel: 50,
  33268. * temp: 150,
  33269. * rpm: 6000
  33270. * }]
  33271. * },
  33272. * series: {
  33273. * type: 'gauge',
  33274. * colors: ['#1F6D91', '#90BCC9'],
  33275. * angleField: 'mph',
  33276. * needle: true,
  33277. * donut: 30
  33278. * }
  33279. * });
  33280. */
  33281. Ext.define('Ext.chart.series.Gauge', {
  33282. alias: 'series.gauge',
  33283. extend: 'Ext.chart.series.Polar',
  33284. type: 'gauge',
  33285. seriesType: 'pieslice',
  33286. requires: [
  33287. 'Ext.draw.sprite.Sector'
  33288. ],
  33289. config: {
  33290. /**
  33291. * @cfg {String} angleField
  33292. * The store record field name to be used for the gauge value.
  33293. * The values bound to this field name must be positive real numbers.
  33294. */
  33295. /**
  33296. * @cfg {Boolean} needle
  33297. * If true, display the gauge as a needle, otherwise as a sector.
  33298. */
  33299. needle: false,
  33300. /**
  33301. * @cfg {Number} needleLength
  33302. * Percentage of the length of needle compared to the radius of the entire disk.
  33303. */
  33304. needleLength: 90,
  33305. /**
  33306. * @cfg {Number} needleWidth
  33307. * Width of the needle in pixels.
  33308. */
  33309. needleWidth: 4,
  33310. /**
  33311. * @cfg {Number} donut
  33312. * Percentage of the radius of the donut hole compared to the entire disk.
  33313. */
  33314. donut: 30,
  33315. /**
  33316. * @cfg {Boolean} showInLegend
  33317. * Whether to add the gauge chart elements as legend items.
  33318. */
  33319. showInLegend: false,
  33320. /**
  33321. * @cfg {Number} value
  33322. * Directly sets the displayed value of the gauge.
  33323. * It is ignored if {@link #angleField} is provided.
  33324. */
  33325. value: null,
  33326. /**
  33327. * @cfg {Array} colors (required)
  33328. * An array of color values which is used for the needle and the `sectors`.
  33329. */
  33330. colors: null,
  33331. /**
  33332. * @cfg {Array} sectors
  33333. * Allows to paint sectors of different colors in the background of the gauge,
  33334. * with optional labels.
  33335. *
  33336. * It can be an array of numbers (each between `minimum` and `maximum`) that
  33337. * define the highest value of each sector. For N sectors, only (N-1) values are
  33338. * needed because it is assumed that the first sector starts at `minimum` and the
  33339. * last sector ends at `maximum`. Example: a water temperature gauge that is blue
  33340. * below 20C, red above 80C, gray in-between, and with an orange needle...
  33341. *
  33342. * minimum: 0,
  33343. * maximum: 100,
  33344. * sectors: [20, 80],
  33345. * colors: ['orange', 'blue', 'lightgray', 'red']
  33346. *
  33347. * It can be also an array of objects, each with the following properties:
  33348. *
  33349. * @cfg {Number} sectors.start The starting value of the sector. If omitted, it
  33350. * uses the previous sector's `end` value or the chart's `minimum`.
  33351. * @cfg {Number} sectors.end The ending value of the sector. If omitted, it uses
  33352. * the `maximum` defined for the chart.
  33353. * @cfg {String} sectors.label The label for this sector. Labels are styled using
  33354. * the series' {@link Ext.chart.series.Series#label label} config.
  33355. * @cfg {String} sectors.color The color of the sector. If omitted, it uses one
  33356. * of the `colors` defined for the series or for the chart.
  33357. * @cfg {Object} sectors.style An additional style object for the sector (for
  33358. * instance to set the opacity or to draw a line of a different color around the
  33359. * sector).
  33360. *
  33361. * minimum: 0,
  33362. * maximum: 100,
  33363. * sectors: [{
  33364. * end: 20,
  33365. * label: 'Cold',
  33366. * color: 'aqua'
  33367. * },
  33368. * {
  33369. * end: 80,
  33370. * label: 'Temp.',
  33371. * color: 'lightgray',
  33372. * style: { strokeStyle:'black', strokeOpacity:1, lineWidth:1 }
  33373. * },
  33374. * {
  33375. * label: 'Hot',
  33376. * color: 'tomato'
  33377. * }]
  33378. */
  33379. sectors: null,
  33380. /**
  33381. * @cfg {Number} minimum
  33382. * The minimum value of the gauge.
  33383. */
  33384. minimum: 0,
  33385. /**
  33386. * @cfg {Number} maximum
  33387. * The maximum value of the gauge.
  33388. */
  33389. maximum: 100,
  33390. rotation: 0,
  33391. /**
  33392. * @cfg {Number} totalAngle
  33393. * The size of the sector that the series will occupy.
  33394. */
  33395. totalAngle: Math.PI / 2,
  33396. rect: [
  33397. 0,
  33398. 0,
  33399. 1,
  33400. 1
  33401. ],
  33402. center: [
  33403. 0.5,
  33404. 0.75
  33405. ],
  33406. radius: 0.5,
  33407. /**
  33408. * @cfg {Boolean} wholeDisk Indicates whether to show the whole disk
  33409. * or only the marked part.
  33410. */
  33411. wholeDisk: false
  33412. },
  33413. coordinateX: function() {
  33414. return this.coordinate('X', 0, 2);
  33415. },
  33416. coordinateY: function() {
  33417. return this.coordinate('Y', 1, 2);
  33418. },
  33419. updateNeedle: function(needle) {
  33420. var me = this,
  33421. sprites = me.getSprites(),
  33422. angle = me.valueToAngle(me.getValue());
  33423. if (sprites && sprites.length) {
  33424. sprites[0].setAttributes({
  33425. startAngle: (needle ? angle : 0),
  33426. endAngle: angle,
  33427. strokeOpacity: (needle ? 1 : 0),
  33428. lineWidth: (needle ? me.getNeedleWidth() : 0)
  33429. });
  33430. me.doUpdateStyles();
  33431. }
  33432. },
  33433. themeColorCount: function() {
  33434. var me = this,
  33435. store = me.getStore(),
  33436. count = store && store.getCount() || 0;
  33437. return count + (me.getNeedle() ? 0 : 1);
  33438. },
  33439. updateColors: function(colors, oldColors) {
  33440. var me = this,
  33441. sectors = me.getSectors(),
  33442. sectorCount = sectors && sectors.length,
  33443. sprites = me.getSprites(),
  33444. newColors = Ext.Array.clone(colors),
  33445. colorCount = colors && colors.length,
  33446. i;
  33447. if (!colorCount || !colors[0]) {
  33448. return;
  33449. }
  33450. // Make sure the 'sectors' colors are not overridden.
  33451. for (i = 0; i < sectorCount; i++) {
  33452. newColors[i + 1] = sectors[i].color || newColors[i + 1] || colors[i % colorCount];
  33453. }
  33454. if (sprites.length) {
  33455. sprites[0].setAttributes({
  33456. strokeStyle: newColors[0]
  33457. });
  33458. }
  33459. this.setSubStyle({
  33460. fillStyle: newColors,
  33461. strokeStyle: newColors
  33462. });
  33463. this.doUpdateStyles();
  33464. },
  33465. updateRect: function(rect) {
  33466. var wholeDisk = this.getWholeDisk(),
  33467. halfTotalAngle = wholeDisk ? Math.PI : this.getTotalAngle() / 2,
  33468. donut = this.getDonut() / 100,
  33469. width, height, radius;
  33470. if (halfTotalAngle <= Math.PI / 2) {
  33471. width = 2 * Math.sin(halfTotalAngle);
  33472. height = 1 - donut * Math.cos(halfTotalAngle);
  33473. } else {
  33474. width = 2;
  33475. height = 1 - Math.cos(halfTotalAngle);
  33476. }
  33477. radius = Math.min(rect[2] / width, rect[3] / height);
  33478. this.setRadius(radius);
  33479. this.setCenter([
  33480. rect[2] / 2,
  33481. radius + (rect[3] - height * radius) / 2
  33482. ]);
  33483. },
  33484. updateCenter: function(center) {
  33485. this.setStyle({
  33486. centerX: center[0],
  33487. centerY: center[1],
  33488. rotationCenterX: center[0],
  33489. rotationCenterY: center[1]
  33490. });
  33491. this.doUpdateStyles();
  33492. },
  33493. updateRotation: function(rotation) {
  33494. this.setStyle({
  33495. rotationRads: rotation - (this.getTotalAngle() + Math.PI) / 2
  33496. });
  33497. this.doUpdateStyles();
  33498. },
  33499. doUpdateShape: function(radius, donut) {
  33500. var me = this,
  33501. sectors = me.getSectors(),
  33502. sectorCount = (sectors && sectors.length) || 0,
  33503. needleLength = me.getNeedleLength() / 100,
  33504. endRhoArray;
  33505. // Initialize an array that contains the endRho for each sprite.
  33506. // The first sprite is for the needle, the others for the gauge background sectors.
  33507. // Note: SubStyle arrays are handled in series.getStyleByIndex().
  33508. endRhoArray = [
  33509. radius * needleLength,
  33510. radius
  33511. ];
  33512. while (sectorCount--) {
  33513. endRhoArray.push(radius);
  33514. }
  33515. me.setSubStyle({
  33516. endRho: endRhoArray,
  33517. startRho: radius / 100 * donut
  33518. });
  33519. me.doUpdateStyles();
  33520. },
  33521. updateRadius: function(radius) {
  33522. var donut = this.getDonut();
  33523. this.doUpdateShape(radius, donut);
  33524. },
  33525. updateDonut: function(donut) {
  33526. var radius = this.getRadius();
  33527. this.doUpdateShape(radius, donut);
  33528. },
  33529. valueToAngle: function(value) {
  33530. value = this.applyValue(value);
  33531. return this.getTotalAngle() * (value - this.getMinimum()) / (this.getMaximum() - this.getMinimum());
  33532. },
  33533. applyValue: function(value) {
  33534. return Math.min(this.getMaximum(), Math.max(value, this.getMinimum()));
  33535. },
  33536. updateValue: function(value) {
  33537. var me = this,
  33538. needle = me.getNeedle(),
  33539. angle = me.valueToAngle(value),
  33540. sprites = me.getSprites();
  33541. sprites[0].getRendererData().value = value;
  33542. sprites[0].setAttributes({
  33543. startAngle: (needle ? angle : 0),
  33544. endAngle: angle
  33545. });
  33546. me.doUpdateStyles();
  33547. },
  33548. processData: function() {
  33549. var me = this,
  33550. store = me.getStore(),
  33551. record = store && store.first(),
  33552. animation, duration, axis, min, max, xField, value;
  33553. if (record) {
  33554. xField = me.getXField();
  33555. if (xField) {
  33556. value = record.get(xField);
  33557. }
  33558. }
  33559. axis = me.getXAxis();
  33560. if (axis) {
  33561. min = axis.getMinimum();
  33562. max = axis.getMaximum();
  33563. // Animating the axis here can lead to weird looking results.
  33564. animation = axis.getSprites()[0].getAnimation();
  33565. duration = animation.getDuration();
  33566. animation.setDuration(0);
  33567. if (Ext.isNumber(min)) {
  33568. me.setMinimum(min);
  33569. } else {
  33570. axis.setMinimum(me.getMinimum());
  33571. }
  33572. if (Ext.isNumber(max)) {
  33573. me.setMaximum(max);
  33574. } else {
  33575. axis.setMaximum(me.getMaximum());
  33576. }
  33577. animation.setDuration(duration);
  33578. }
  33579. if (!Ext.isNumber(value)) {
  33580. value = me.getMinimum();
  33581. }
  33582. me.setValue(value);
  33583. },
  33584. getDefaultSpriteConfig: function() {
  33585. return {
  33586. type: this.seriesType,
  33587. renderer: this.getRenderer(),
  33588. animation: {
  33589. customDurations: {
  33590. translationX: 0,
  33591. translationY: 0,
  33592. rotationCenterX: 0,
  33593. rotationCenterY: 0,
  33594. centerX: 0,
  33595. centerY: 0,
  33596. startRho: 0,
  33597. endRho: 0,
  33598. baseRotation: 0
  33599. }
  33600. }
  33601. };
  33602. },
  33603. normalizeSectors: function(sectors) {
  33604. // Make sure all the sectors in the array have a legit start and end.
  33605. // Note: the array is modified in-place.
  33606. var me = this,
  33607. sectorCount = (sectors && sectors.length) || 0,
  33608. i, value, start, end;
  33609. if (sectorCount) {
  33610. for (i = 0; i < sectorCount; i++) {
  33611. value = sectors[i];
  33612. if (typeof value === 'number') {
  33613. sectors[i] = {
  33614. start: (i > 0 ? sectors[i - 1].end : me.getMinimum()),
  33615. end: Math.min(value, me.getMaximum())
  33616. };
  33617. if (i === (sectorCount - 1) && sectors[i].end < me.getMaximum()) {
  33618. sectors[i + 1] = {
  33619. start: sectors[i].end,
  33620. end: me.getMaximum()
  33621. };
  33622. }
  33623. } else {
  33624. if (typeof value.start === 'number') {
  33625. start = Math.max(value.start, me.getMinimum());
  33626. } else {
  33627. start = (i > 0 ? sectors[i - 1].end : me.getMinimum());
  33628. }
  33629. if (typeof value.end === 'number') {
  33630. end = Math.min(value.end, me.getMaximum());
  33631. } else {
  33632. end = me.getMaximum();
  33633. }
  33634. sectors[i].start = start;
  33635. sectors[i].end = end;
  33636. }
  33637. }
  33638. } else {
  33639. sectors = [
  33640. {
  33641. start: me.getMinimum(),
  33642. end: me.getMaximum()
  33643. }
  33644. ];
  33645. }
  33646. return sectors;
  33647. },
  33648. getSprites: function() {
  33649. var me = this,
  33650. store = me.getStore(),
  33651. value = me.getValue(),
  33652. label = me.getLabel(),
  33653. i, ln;
  33654. // The store must be initialized, or the value must be set
  33655. if (!store && !Ext.isNumber(value)) {
  33656. return Ext.emptyArray;
  33657. }
  33658. // Return cached sprites
  33659. // eslint-disable-next-line vars-on-top, one-var
  33660. var chart = me.getChart(),
  33661. animation = me.getAnimation() || chart && chart.getAnimation(),
  33662. sprites = me.sprites,
  33663. spriteIndex = 0,
  33664. sprite, sectors, attr, rendererData,
  33665. // Hack to avoid having the lineWidths overwritten by the one specified in the theme.
  33666. // In fact, all the style properties from the needle and sectors should go
  33667. // to the series subStyle.
  33668. lineWidths = [];
  33669. if (sprites && sprites.length) {
  33670. sprites[0].setAnimation(animation);
  33671. return sprites;
  33672. }
  33673. rendererData = {
  33674. store: store,
  33675. field: me.getXField(),
  33676. // for backward compatibility only (deprecated in 5.5)
  33677. angleField: me.getXField(),
  33678. value: value,
  33679. series: me
  33680. };
  33681. // Create needle sprite
  33682. me.needleSprite = sprite = me.createSprite();
  33683. sprite.setAttributes({
  33684. zIndex: 10
  33685. }, true);
  33686. sprite.setRendererData(rendererData);
  33687. sprite.setRendererIndex(spriteIndex++);
  33688. lineWidths.push(me.getNeedleWidth());
  33689. if (label) {
  33690. label.getTemplate().setField(true);
  33691. }
  33692. // Enable labels
  33693. // Create background sprite(s)
  33694. sectors = me.normalizeSectors(me.getSectors());
  33695. for (i = 0 , ln = sectors.length; i < ln; i++) {
  33696. attr = {
  33697. startAngle: me.valueToAngle(sectors[i].start),
  33698. endAngle: me.valueToAngle(sectors[i].end),
  33699. label: sectors[i].label,
  33700. fillStyle: sectors[i].color,
  33701. strokeOpacity: 0,
  33702. doCallout: false,
  33703. // Show labels inside sectors.
  33704. labelOverflowPadding: -1
  33705. };
  33706. // Allow labels to overlap.
  33707. Ext.apply(attr, sectors[i].style);
  33708. sprite = me.createSprite();
  33709. sprite.setRendererData(rendererData);
  33710. sprite.setRendererIndex(spriteIndex++);
  33711. sprite.setAttributes(attr, true);
  33712. lineWidths.push(attr.lineWidth);
  33713. }
  33714. me.setSubStyle({
  33715. lineWidth: lineWidths
  33716. });
  33717. me.doUpdateStyles();
  33718. return sprites;
  33719. },
  33720. doUpdateStyles: function() {
  33721. var me = this;
  33722. me.callParent();
  33723. if (me.sprites.length) {
  33724. me.needleSprite.setAttributes({
  33725. startRho: me.getNeedle() ? 0 : (me.getRadius() / 100 * me.getDonut())
  33726. });
  33727. }
  33728. }
  33729. });
  33730. /**
  33731. * @class Ext.chart.series.sprite.Line
  33732. * @extends Ext.chart.series.sprite.Aggregative
  33733. *
  33734. * Line series sprite.
  33735. */
  33736. Ext.define('Ext.chart.series.sprite.Line', {
  33737. alias: 'sprite.lineSeries',
  33738. extend: 'Ext.chart.series.sprite.Aggregative',
  33739. inheritableStatics: {
  33740. def: {
  33741. processors: {
  33742. /**
  33743. * @cfg {Object} [curve={type: 'linear'}]
  33744. * The type of curve that connects the data points.
  33745. *
  33746. * For example:
  33747. *
  33748. * // The data points are connected by line segments.
  33749. * // This is the default setting.
  33750. * curve: {
  33751. * type: 'linear'
  33752. * }
  33753. *
  33754. * // Cardinal spline interpolation is used to produce the curve
  33755. * // that connects the data points. The `tension` parameter can
  33756. * // be used to control the smoothness of the curve. A tension
  33757. * // of 0 corresponds to infinite tension, which results in straight
  33758. * // lines between data points. A tension of 1 corresponds to
  33759. * // no tension, allowing the spline to take the path of least
  33760. * // total bend. With tension values greater than 1, the curve
  33761. * // behaves like a compressed spring, pushed to take a longer path.
  33762. * // A cardinal spline with a tension of 0.5 is a special case.
  33763. * // It is then called a Catmull-Rom spline. Catmull-Rom splines are
  33764. * // thought to be esthetically pleasing and are quite common.
  33765. * // Note: spline interpolation only works on gapless data.
  33766. * curve: {
  33767. * type: 'cardinal,
  33768. * tension: 0.5
  33769. * }
  33770. *
  33771. * // Produces a natural cubic spline with the second derivative
  33772. * // of the spline set to zero at the endpoints.
  33773. * curve: {
  33774. * type: 'natural'
  33775. * }
  33776. *
  33777. * // The data points are connected by alternating horizontal and
  33778. * // vertical lines. The y-value changes after the x-value.
  33779. * curve: {
  33780. * type: 'step-after'
  33781. * }
  33782. *
  33783. */
  33784. curve: 'default',
  33785. /**
  33786. * @cfg {Boolean} [fillArea=false]
  33787. * `true` if the sprite paints the area underneath the line.
  33788. */
  33789. fillArea: 'bool',
  33790. /**
  33791. * @cfg {"gap"/"connect"/"origin"} [nullStyle="gap"]
  33792. * Possible values:
  33793. * 'gap' - null points are rendered as gaps.
  33794. * 'connect' - non-null points are connected across null points, so that
  33795. * there is no gap, unless null points are at the beginning/end of the line.
  33796. * Only the visible data points are connected - if a visible data point
  33797. * is followed by a series of null points that go off screen and eventually
  33798. * terminate with a non-null point, the connection won't be made.
  33799. * 'origin' - null data points are rendered at the origin,
  33800. * which is the y-coordinate of a point where the x and y axes meet.
  33801. * This requires that at least the x-coordinate of a point is a valid value.
  33802. */
  33803. nullStyle: 'enums(gap,connect,origin)',
  33804. /**
  33805. * @cfg {Boolean} [preciseStroke=true]
  33806. * `true` if the line uses precise stroke.
  33807. */
  33808. preciseStroke: 'bool',
  33809. /**
  33810. * @private
  33811. * The x-axis associated with the Line series.
  33812. * We need to know the position of the x-axis to fill the area underneath
  33813. * the stroke properly.
  33814. */
  33815. xAxis: 'default',
  33816. /**
  33817. * @cfg {Number} [yCap=Math.pow(2, 20)]
  33818. * Absolute maximum y-value.
  33819. * Larger values will be capped to avoid rendering issues.
  33820. */
  33821. // The 'default' processor is used here as we don't want this attribute to animate.
  33822. yCap: 'default'
  33823. },
  33824. defaults: {
  33825. curve: {
  33826. type: 'linear'
  33827. },
  33828. nullStyle: 'connect',
  33829. fillArea: false,
  33830. preciseStroke: true,
  33831. xAxis: null,
  33832. yCap: Math.pow(2, 20),
  33833. yJump: 50
  33834. },
  33835. triggers: {
  33836. dataX: 'dataX,bbox,curve',
  33837. dataY: 'dataY,bbox,curve',
  33838. curve: 'curve'
  33839. },
  33840. updaters: {
  33841. curve: 'curveUpdater'
  33842. }
  33843. }
  33844. },
  33845. list: null,
  33846. curveUpdater: function(attr) {
  33847. var me = this,
  33848. dataX = attr.dataX,
  33849. dataY = attr.dataY,
  33850. curve = attr.curve,
  33851. smoothable = dataX && dataY && dataX.length > 2 && dataY.length > 2,
  33852. type = curve.type;
  33853. if (smoothable) {
  33854. if (type === 'natural') {
  33855. me.smoothX = Ext.draw.Draw.naturalSpline(dataX);
  33856. me.smoothY = Ext.draw.Draw.naturalSpline(dataY);
  33857. } else if (type === 'cardinal') {
  33858. me.smoothX = Ext.draw.Draw.cardinalSpline(dataX, curve.tension);
  33859. me.smoothY = Ext.draw.Draw.cardinalSpline(dataY, curve.tension);
  33860. } else {
  33861. smoothable = false;
  33862. }
  33863. }
  33864. if (!smoothable) {
  33865. delete me.smoothX;
  33866. delete me.smoothY;
  33867. }
  33868. },
  33869. updatePlainBBox: function(plain) {
  33870. var attr = this.attr,
  33871. ymin = Math.min(0, attr.dataMinY),
  33872. ymax = Math.max(0, attr.dataMaxY);
  33873. plain.x = attr.dataMinX;
  33874. plain.y = ymin;
  33875. plain.width = attr.dataMaxX - attr.dataMinX;
  33876. plain.height = ymax - ymin;
  33877. },
  33878. drawStrip: function(ctx, strip) {
  33879. var i, ln;
  33880. ctx.moveTo(strip[0], strip[1]);
  33881. for (i = 2 , ln = strip.length; i < ln; i += 2) {
  33882. ctx.lineTo(strip[i], strip[i + 1]);
  33883. }
  33884. },
  33885. drawStraightStroke: function(surface, ctx, start, end, list, xAxis) {
  33886. var me = this,
  33887. attr = me.attr,
  33888. nullStyle = attr.nullStyle,
  33889. isConnect = nullStyle === 'connect',
  33890. isOrigin = nullStyle === 'origin',
  33891. renderer = attr.renderer,
  33892. curve = attr.curve,
  33893. step = curve.type === 'step-after',
  33894. needMoveTo = true,
  33895. ln = list.length,
  33896. lineConfig = {
  33897. type: 'line',
  33898. smooth: false,
  33899. step: step
  33900. },
  33901. rendererChanges, params, stripStartX, isValidX0, isValidX, isValidX1, isValidPoint0, isValidPoint, isValidPoint1, isGap, lastValidPoint, px, py, x, y, x0, y0, x1, y1, i,
  33902. // 'strip' stores last continuous segment of the stroke,
  33903. // which we may need to re-build, if there's a fill as well.
  33904. // For example, if the renderer returned a style that needs
  33905. // to be applied to the current step, or we reached a null
  33906. // point in the data, where we have to fill the current continuous
  33907. // segment, we build and close a path that will be filled, then
  33908. // re-build the stroke path, using coordinates saved in the 'strip',
  33909. // and render the stroke on top of the fill.
  33910. strip = [];
  33911. ctx.beginPath();
  33912. for (i = 3; i < ln; i += 3) {
  33913. x0 = list[i - 3];
  33914. y0 = list[i - 2];
  33915. x = list[i];
  33916. y = list[i + 1];
  33917. x1 = list[i + 3];
  33918. y1 = list[i + 4];
  33919. isValidX0 = Ext.isNumber(x0);
  33920. isValidX = Ext.isNumber(x);
  33921. isValidX1 = Ext.isNumber(x1);
  33922. isValidPoint0 = isValidX0 && Ext.isNumber(y0);
  33923. isValidPoint = isValidX && Ext.isNumber(y);
  33924. isValidPoint1 = isValidX1 && Ext.isNumber(y1);
  33925. if (isOrigin) {
  33926. // If only the y-component isn't a valid number,
  33927. // we can 'fix' it by setting it to value of y-origin.
  33928. if (!isValidPoint0 && isValidX0) {
  33929. y0 = xAxis;
  33930. isValidPoint0 = true;
  33931. }
  33932. if (!isValidPoint && isValidX) {
  33933. y = xAxis;
  33934. isValidPoint = true;
  33935. }
  33936. if (!isValidPoint1 && isValidX1) {
  33937. y1 = xAxis;
  33938. isValidPoint1 = true;
  33939. }
  33940. }
  33941. if (renderer) {
  33942. lineConfig.x = x;
  33943. lineConfig.y = y;
  33944. lineConfig.x0 = x0;
  33945. lineConfig.y0 = y0;
  33946. params = [
  33947. me,
  33948. lineConfig,
  33949. me.rendererData,
  33950. start + i / 3
  33951. ];
  33952. // callback(fn, scope, args, delay, caller)
  33953. rendererChanges = Ext.callback(renderer, null, params, 0, me.getSeries());
  33954. }
  33955. if (isGap && isConnect && isValidPoint0 && lastValidPoint) {
  33956. px = lastValidPoint[0];
  33957. py = lastValidPoint[1];
  33958. if (needMoveTo) {
  33959. ctx.beginPath();
  33960. ctx.moveTo(px, py);
  33961. strip.push(px, py);
  33962. stripStartX = px;
  33963. needMoveTo = false;
  33964. }
  33965. if (step) {
  33966. ctx.lineTo(x0, py);
  33967. strip.push(x0, py);
  33968. }
  33969. ctx.lineTo(x0, y0);
  33970. strip.push(x0, y0);
  33971. lastValidPoint = [
  33972. x0,
  33973. y0
  33974. ];
  33975. isGap = false;
  33976. }
  33977. // Special case where we have an uninterrupted segment, followed
  33978. // by a gap, then a valid point, then another gap. The uninterrupted
  33979. // segment should be connenected with the dot situated between the gaps.
  33980. if (isConnect && lastValidPoint && isValidPoint && !isValidPoint0) {
  33981. x0 = lastValidPoint[0];
  33982. y0 = lastValidPoint[1];
  33983. isValidPoint0 = true;
  33984. }
  33985. // Remember last valid point to connect the gap
  33986. // when the next valid point is encountered.
  33987. if (isValidPoint) {
  33988. lastValidPoint = [
  33989. x,
  33990. y
  33991. ];
  33992. }
  33993. if (isValidPoint0 && isValidPoint) {
  33994. if (needMoveTo) {
  33995. ctx.beginPath();
  33996. ctx.moveTo(x0, y0);
  33997. strip.push(x0, y0);
  33998. stripStartX = x0;
  33999. needMoveTo = false;
  34000. }
  34001. } else {
  34002. isGap = true;
  34003. continue;
  34004. }
  34005. if (step) {
  34006. ctx.lineTo(x, y0);
  34007. strip.push(x, y0);
  34008. }
  34009. ctx.lineTo(x, y);
  34010. strip.push(x, y);
  34011. // If the next point is a gap, then we need to fill what
  34012. // has been already rendered so far. The same applies
  34013. // if the renderer returned some changes to apply to
  34014. // the current step.
  34015. if (rendererChanges || !isValidPoint1) {
  34016. ctx.save();
  34017. Ext.apply(ctx, rendererChanges);
  34018. rendererChanges = null;
  34019. if (attr.fillArea) {
  34020. ctx.lineTo(x, xAxis);
  34021. ctx.lineTo(stripStartX, xAxis);
  34022. ctx.closePath();
  34023. ctx.fill();
  34024. }
  34025. // Draw the line on top of the filled area.
  34026. ctx.beginPath();
  34027. me.drawStrip(ctx, strip);
  34028. strip = [];
  34029. ctx.stroke();
  34030. ctx.restore();
  34031. ctx.beginPath();
  34032. // Take note that the starting point of a path has been reset
  34033. // (as a result of filling a sub-path) and needs to be set again
  34034. // for the line to continue in a proper manner.
  34035. needMoveTo = true;
  34036. }
  34037. }
  34038. },
  34039. calculateScale: function(count, end) {
  34040. var power = 0,
  34041. n = count;
  34042. while (n < end && count > 0) {
  34043. power++;
  34044. n += count >> power;
  34045. }
  34046. return Math.pow(2, power > 0 ? power - 1 : power);
  34047. },
  34048. drawSmoothStroke: function(surface, ctx, start, end, list, xAxis) {
  34049. var me = this,
  34050. attr = me.attr,
  34051. step = attr.step,
  34052. matrix = attr.matrix,
  34053. renderer = attr.renderer,
  34054. xx = matrix.getXX(),
  34055. yy = matrix.getYY(),
  34056. dx = matrix.getDX(),
  34057. dy = matrix.getDY(),
  34058. smoothX = me.smoothX,
  34059. smoothY = me.smoothY,
  34060. scale = me.calculateScale(attr.dataX.length, end),
  34061. cx1, cy1, cx2, cy2, x, y, x0, y0, i, j, changes, params,
  34062. lineConfig = {
  34063. type: 'line',
  34064. smooth: true,
  34065. step: step
  34066. };
  34067. ctx.beginPath();
  34068. ctx.moveTo(smoothX[start * 3] * xx + dx, smoothY[start * 3] * yy + dy);
  34069. for (i = 0 , j = start * 3 + 1; i < list.length - 3; i += 3 , j += 3 * scale) {
  34070. cx1 = smoothX[j] * xx + dx;
  34071. cy1 = smoothY[j] * yy + dy;
  34072. cx2 = smoothX[j + 1] * xx + dx;
  34073. cy2 = smoothY[j + 1] * yy + dy;
  34074. x = surface.roundPixel(list[i + 3]);
  34075. y = list[i + 4];
  34076. x0 = surface.roundPixel(list[i]);
  34077. y0 = list[i + 1];
  34078. if (renderer) {
  34079. lineConfig.x0 = x0;
  34080. lineConfig.y0 = y0;
  34081. lineConfig.cx1 = cx1;
  34082. lineConfig.cy1 = cy1;
  34083. lineConfig.cx2 = cx2;
  34084. lineConfig.cy2 = cy2;
  34085. lineConfig.x = x;
  34086. lineConfig.y = y;
  34087. params = [
  34088. me,
  34089. lineConfig,
  34090. me.rendererData,
  34091. start + i / 3 + 1
  34092. ];
  34093. changes = Ext.callback(renderer, null, params, 0, me.getSeries());
  34094. ctx.save();
  34095. Ext.apply(ctx, changes);
  34096. }
  34097. if (attr.fillArea) {
  34098. ctx.moveTo(x0, y0);
  34099. ctx.bezierCurveTo(cx1, cy1, cx2, cy2, x, y);
  34100. ctx.lineTo(x, xAxis);
  34101. ctx.lineTo(x0, xAxis);
  34102. ctx.lineTo(x0, y0);
  34103. ctx.closePath();
  34104. ctx.fill();
  34105. ctx.beginPath();
  34106. }
  34107. // Draw the line on top of the filled area.
  34108. ctx.moveTo(x0, y0);
  34109. ctx.bezierCurveTo(cx1, cy1, cx2, cy2, x, y);
  34110. ctx.stroke();
  34111. ctx.moveTo(x0, y0);
  34112. ctx.closePath();
  34113. if (renderer) {
  34114. ctx.restore();
  34115. }
  34116. ctx.beginPath();
  34117. ctx.moveTo(x, y);
  34118. }
  34119. // Prevent the last visible segment from being stroked twice
  34120. // (second time by the ctx.fillStroke inside Path sprite 'render' method)
  34121. ctx.beginPath();
  34122. },
  34123. drawLabel: function(text, dataX, dataY, labelId, rect) {
  34124. var me = this,
  34125. attr = me.attr,
  34126. label = me.getMarker('labels'),
  34127. labelTpl = label.getTemplate(),
  34128. labelCfg = me.labelCfg || (me.labelCfg = {}),
  34129. surfaceMatrix = me.surfaceMatrix,
  34130. labelX, labelY,
  34131. labelOverflowPadding = attr.labelOverflowPadding,
  34132. halfHeight, labelBBox, changes, params, hasPendingChanges;
  34133. // The coordinates below (data point converted to surface coordinates)
  34134. // are just for the renderer to give it a notion of where the label will be positioned.
  34135. // The actual position of the label will be different
  34136. // (unless the renderer returns x/y coordinates in the changes object)
  34137. // and depend on several things including the size of the text,
  34138. // which has to be measured after the renderer call,
  34139. // since text can be modified by the renderer.
  34140. labelCfg.x = surfaceMatrix.x(dataX, dataY);
  34141. labelCfg.y = surfaceMatrix.y(dataX, dataY);
  34142. if (attr.flipXY) {
  34143. labelCfg.rotationRads = Math.PI * 0.5;
  34144. } else {
  34145. labelCfg.rotationRads = 0;
  34146. }
  34147. labelCfg.text = text;
  34148. if (labelTpl.attr.renderer) {
  34149. params = [
  34150. text,
  34151. label,
  34152. labelCfg,
  34153. me.rendererData,
  34154. labelId
  34155. ];
  34156. changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
  34157. if (typeof changes === 'string') {
  34158. labelCfg.text = changes;
  34159. } else if (typeof changes === 'object') {
  34160. if ('text' in changes) {
  34161. labelCfg.text = changes.text;
  34162. }
  34163. hasPendingChanges = true;
  34164. }
  34165. }
  34166. labelBBox = me.getMarkerBBox('labels', labelId, true);
  34167. if (!labelBBox) {
  34168. me.putMarker('labels', labelCfg, labelId);
  34169. labelBBox = me.getMarkerBBox('labels', labelId, true);
  34170. }
  34171. halfHeight = labelBBox.height / 2;
  34172. labelX = dataX;
  34173. switch (labelTpl.attr.display) {
  34174. case 'under':
  34175. labelY = dataY - halfHeight - labelOverflowPadding;
  34176. break;
  34177. case 'rotate':
  34178. labelX += labelOverflowPadding;
  34179. labelY = dataY - labelOverflowPadding;
  34180. labelCfg.rotationRads = -Math.PI / 4;
  34181. break;
  34182. default:
  34183. // 'over'
  34184. labelY = dataY + halfHeight + labelOverflowPadding;
  34185. }
  34186. labelCfg.x = surfaceMatrix.x(labelX, labelY);
  34187. labelCfg.y = surfaceMatrix.y(labelX, labelY);
  34188. if (hasPendingChanges) {
  34189. Ext.apply(labelCfg, changes);
  34190. }
  34191. me.putMarker('labels', labelCfg, labelId);
  34192. },
  34193. drawMarker: function(x, y, index) {
  34194. var me = this,
  34195. attr = me.attr,
  34196. renderer = attr.renderer,
  34197. surfaceMatrix = me.surfaceMatrix,
  34198. markerCfg = {},
  34199. changes, params;
  34200. if (renderer && me.getMarker('markers')) {
  34201. markerCfg.type = 'marker';
  34202. markerCfg.x = x;
  34203. markerCfg.y = y;
  34204. params = [
  34205. me,
  34206. markerCfg,
  34207. me.rendererData,
  34208. index
  34209. ];
  34210. changes = Ext.callback(renderer, null, params, 0, me.getSeries());
  34211. if (changes) {
  34212. Ext.apply(markerCfg, changes);
  34213. }
  34214. }
  34215. markerCfg.translationX = surfaceMatrix.x(x, y);
  34216. markerCfg.translationY = surfaceMatrix.y(x, y);
  34217. delete markerCfg.x;
  34218. delete markerCfg.y;
  34219. me.putMarker('markers', markerCfg, index, !renderer);
  34220. },
  34221. drawStroke: function(surface, ctx, start, end, list, xAxis) {
  34222. var me = this,
  34223. isSmooth = me.smoothX && me.smoothY;
  34224. if (isSmooth) {
  34225. me.drawSmoothStroke(surface, ctx, start, end, list, xAxis);
  34226. } else {
  34227. me.drawStraightStroke(surface, ctx, start, end, list, xAxis);
  34228. }
  34229. },
  34230. renderAggregates: function(aggregates, start, end, surface, ctx, clip, rect) {
  34231. var me = this,
  34232. attr = me.attr,
  34233. dataX = attr.dataX,
  34234. dataY = attr.dataY,
  34235. labels = attr.labels,
  34236. xAxis = attr.xAxis,
  34237. yCap = attr.yCap,
  34238. isSmooth = attr.smooth && me.smoothX && me.smoothY,
  34239. isDrawLabels = labels && me.getMarker('labels'),
  34240. isDrawMarkers = me.getMarker('markers'),
  34241. matrix = attr.matrix,
  34242. pixel = surface.devicePixelRatio,
  34243. xx = matrix.getXX(),
  34244. yy = matrix.getYY(),
  34245. dx = matrix.getDX(),
  34246. dy = matrix.getDY(),
  34247. list = me.list || (me.list = []),
  34248. minXs = aggregates.minX,
  34249. maxXs = aggregates.maxX,
  34250. minYs = aggregates.minY,
  34251. maxYs = aggregates.maxY,
  34252. idx = aggregates.startIdx,
  34253. isContinuousLine = true,
  34254. isValidMinX, isValidMaxX, isValidMinY, isValidMaxY, xAxisOrigin, isVerticalX, x, y, i, index, minX, maxX, minY, maxY, lastPointX, lastPointY, firstPointX, firstPointY;
  34255. me.rendererData = {
  34256. store: me.getStore()
  34257. };
  34258. list.length = 0;
  34259. // Say we have 7 y-items (attr.dataY): [20, 19, 17, 15, 11, 10, 14]
  34260. // and 7 x-items (attr.dataX): [0, 1, 2, 3, 4, 5, 6].
  34261. // Then aggregates.startIdx is an aggregated index,
  34262. // where every other item is skipped on each aggregation level:
  34263. // [0, 1, 2, 3, 4, 5, 6,
  34264. // 0, 2, 4, 6,
  34265. // 0, 4,
  34266. // 0]
  34267. // aggregates.minY
  34268. // [20, 19, 17, 15, 11, 10, 14,
  34269. // 19, 15, 10, 14,
  34270. // 15, 10,
  34271. // 10]
  34272. // aggregates.maxY
  34273. // [20, 19, 17, 15, 11, 10, 14,
  34274. // 20, 17, 11, 14,
  34275. // 20, 14,
  34276. // 20]
  34277. // aggregates.minX is
  34278. // [0, 1, 2, 3, 4, 5, 6,
  34279. // 1, 3, 5, 6, // TODO: why this order for min?
  34280. // 3, 5, // TODO: why this inconsistency?
  34281. // 5]
  34282. // aggregates.maxX is
  34283. // [0, 1, 2, 3, 4, 5, 6,
  34284. // 0, 2, 4, 6,
  34285. // 0, 6,
  34286. // 0]
  34287. // Create a list of the form [x0, y0, idx0, x1, y1, idx1, ...],
  34288. // where each x,y pair is a coordinate representing original data point
  34289. // at the idx position.
  34290. for (i = start; i < end; i++) {
  34291. minX = minXs[i];
  34292. maxX = maxXs[i];
  34293. minY = minYs[i];
  34294. maxY = maxYs[i];
  34295. isValidMinX = Ext.isNumber(minX);
  34296. isValidMinY = Ext.isNumber(minY);
  34297. isValidMaxX = Ext.isNumber(maxX);
  34298. isValidMaxY = Ext.isNumber(maxY);
  34299. if (minX < maxX) {
  34300. list.push(isValidMinX ? (minX * xx + dx) : null, isValidMinY ? (minY * yy + dy) : null, idx[i]);
  34301. list.push(isValidMaxX ? (maxX * xx + dx) : null, isValidMaxY ? (maxY * yy + dy) : null, idx[i]);
  34302. } else if (minX > maxX) {
  34303. list.push(isValidMaxX ? (maxX * xx + dx) : null, isValidMaxY ? (maxY * yy + dy) : null, idx[i]);
  34304. list.push(isValidMinX ? (minX * xx + dx) : null, isValidMinY ? (minY * yy + dy) : null, idx[i]);
  34305. } else {
  34306. list.push(isValidMaxX ? (maxX * xx + dx) : null, isValidMaxY ? (maxY * yy + dy) : null, idx[i]);
  34307. }
  34308. }
  34309. if (list.length) {
  34310. for (i = 0; i < list.length; i += 3) {
  34311. x = list[i];
  34312. y = list[i + 1];
  34313. if (Ext.isNumber(x) && Ext.isNumber(y)) {
  34314. if (y > yCap) {
  34315. y = yCap;
  34316. } else if (y < -yCap) {
  34317. y = -yCap;
  34318. }
  34319. list[i + 1] = y;
  34320. } else {
  34321. isContinuousLine = false;
  34322. continue;
  34323. }
  34324. index = list[i + 2];
  34325. if (isDrawMarkers) {
  34326. me.drawMarker(x, y, index);
  34327. }
  34328. if (isDrawLabels && labels[index]) {
  34329. me.drawLabel(labels[index], x, y, index, rect);
  34330. }
  34331. }
  34332. me.isContinuousLine = isContinuousLine;
  34333. if (isSmooth && !isContinuousLine) {
  34334. Ext.raise("Line smoothing in only supported for gapless data, " + "where all data points are finite numbers.");
  34335. }
  34336. if (xAxis) {
  34337. isVerticalX = xAxis.getAlignment() === 'vertical';
  34338. if (Ext.isNumber(xAxis.floatingAtCoord)) {
  34339. xAxisOrigin = (isVerticalX ? rect[2] : rect[3]) - xAxis.floatingAtCoord;
  34340. } else {
  34341. xAxisOrigin = isVerticalX ? rect[0] : rect[1];
  34342. }
  34343. } else {
  34344. xAxisOrigin = attr.flipXY ? rect[0] : rect[1];
  34345. }
  34346. if (attr.preciseStroke) {
  34347. if (attr.fillArea) {
  34348. ctx.fill();
  34349. }
  34350. if (attr.transformFillStroke) {
  34351. attr.inverseMatrix.toContext(ctx);
  34352. }
  34353. me.drawStroke(surface, ctx, start, end, list, xAxisOrigin);
  34354. if (attr.transformFillStroke) {
  34355. attr.matrix.toContext(ctx);
  34356. }
  34357. ctx.stroke();
  34358. } else {
  34359. me.drawStroke(surface, ctx, start, end, list, xAxisOrigin);
  34360. if (isContinuousLine && isSmooth && attr.fillArea && !attr.renderer) {
  34361. lastPointX = dataX[dataX.length - 1] * xx + dx + pixel;
  34362. lastPointY = dataY[dataY.length - 1] * yy + dy;
  34363. firstPointX = dataX[0] * xx + dx - pixel;
  34364. firstPointY = dataY[0] * yy + dy;
  34365. // Fill the area from the series to the xAxis in case there
  34366. // are no gaps and no renderer is used, in which case the
  34367. // area would be filled per uninterrupted segment or per
  34368. // step, instead of being filled a single pass.
  34369. ctx.lineTo(lastPointX, lastPointY);
  34370. ctx.lineTo(lastPointX, xAxisOrigin - attr.lineWidth);
  34371. ctx.lineTo(firstPointX, xAxisOrigin - attr.lineWidth);
  34372. ctx.lineTo(firstPointX, firstPointY);
  34373. }
  34374. if (attr.transformFillStroke) {
  34375. attr.matrix.toContext(ctx);
  34376. }
  34377. // Prevent the reverse transform to fix floating point error.
  34378. if (attr.fillArea) {
  34379. ctx.fillStroke(attr, true);
  34380. } else {
  34381. ctx.stroke(true);
  34382. }
  34383. }
  34384. }
  34385. }
  34386. });
  34387. /**
  34388. * @class Ext.chart.series.Line
  34389. * @extends Ext.chart.series.Cartesian
  34390. *
  34391. * Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative
  34392. * information for different categories or other real values (as opposed to the bar chart),
  34393. * that can show some progression (or regression) in the dataset.
  34394. * As with all other series, the Line Series must be appended in the *series* Chart array
  34395. * configuration. See the Chart documentation for more information. A typical configuration object
  34396. * for the line series could be:
  34397. *
  34398. * @example
  34399. * Ext.create({
  34400. * xtype: 'cartesian',
  34401. * renderTo: document.body,
  34402. * width: 600,
  34403. * height: 400,
  34404. * insetPadding: 40,
  34405. * store: {
  34406. * fields: ['name', 'data1', 'data2'],
  34407. * data: [{
  34408. * 'name': 'metric one',
  34409. * 'data1': 10,
  34410. * 'data2': 14
  34411. * }, {
  34412. * 'name': 'metric two',
  34413. * 'data1': 7,
  34414. * 'data2': 16
  34415. * }, {
  34416. * 'name': 'metric three',
  34417. * 'data1': 5,
  34418. * 'data2': 14
  34419. * }, {
  34420. * 'name': 'metric four',
  34421. * 'data1': 2,
  34422. * 'data2': 6
  34423. * }, {
  34424. * 'name': 'metric five',
  34425. * 'data1': 27,
  34426. * 'data2': 36
  34427. * }]
  34428. * },
  34429. * axes: [{
  34430. * type: 'numeric',
  34431. * position: 'left',
  34432. * fields: ['data1'],
  34433. * title: {
  34434. * text: 'Sample Values',
  34435. * fontSize: 15
  34436. * },
  34437. * grid: true,
  34438. * minimum: 0
  34439. * }, {
  34440. * type: 'category',
  34441. * position: 'bottom',
  34442. * fields: ['name'],
  34443. * title: {
  34444. * text: 'Sample Values',
  34445. * fontSize: 15
  34446. * }
  34447. * }],
  34448. * series: [{
  34449. * type: 'line',
  34450. * style: {
  34451. * stroke: '#30BDA7',
  34452. * lineWidth: 2
  34453. * },
  34454. * xField: 'name',
  34455. * yField: 'data1',
  34456. * marker: {
  34457. * type: 'path',
  34458. * path: ['M', - 4, 0, 0, 4, 4, 0, 0, - 4, 'Z'],
  34459. * stroke: '#30BDA7',
  34460. * lineWidth: 2,
  34461. * fill: 'white'
  34462. * }
  34463. * }, {
  34464. * type: 'line',
  34465. * fill: true,
  34466. * style: {
  34467. * fill: '#96D4C6',
  34468. * fillOpacity: .6,
  34469. * stroke: '#0A3F50',
  34470. * strokeOpacity: .6,
  34471. * },
  34472. * xField: 'name',
  34473. * yField: 'data2',
  34474. * marker: {
  34475. * type: 'circle',
  34476. * radius: 4,
  34477. * lineWidth: 2,
  34478. * fill: 'white'
  34479. * }
  34480. * }]
  34481. * });
  34482. *
  34483. * In this configuration we're adding two series (or lines), one bound to the `data1`
  34484. * property of the store and the other to `data3`. The type for both configurations is
  34485. * `line`. The `xField` for both series is the same, the `name` property of the store.
  34486. * Both line series share the same axis, the left axis. You can set particular marker
  34487. * configuration by adding properties onto the marker object. Both series have
  34488. * an object as highlight so that markers animate smoothly to the properties in highlight
  34489. * when hovered. The second series has `fill = true` which means that the line will also
  34490. * have an area below it of the same color.
  34491. *
  34492. * **Note:** In the series definition remember to explicitly set the axis to bind the
  34493. * values of the line series to. This can be done by using the `axis` configuration property.
  34494. */
  34495. Ext.define('Ext.chart.series.Line', {
  34496. extend: 'Ext.chart.series.Cartesian',
  34497. alias: 'series.line',
  34498. type: 'line',
  34499. seriesType: 'lineSeries',
  34500. isLine: true,
  34501. requires: [
  34502. 'Ext.chart.series.sprite.Line'
  34503. ],
  34504. config: {
  34505. /**
  34506. * @cfg {Number} selectionTolerance
  34507. * The offset distance from the cursor position to the line series to trigger events
  34508. * (then used for highlighting series, etc).
  34509. */
  34510. selectionTolerance: 20,
  34511. /**
  34512. * @cfg {Object} curve
  34513. * The type of curve that connects the data points.
  34514. * Please see {@link Ext.chart.series.sprite.Line#curve line sprite documentation}
  34515. * for the full description.
  34516. */
  34517. curve: {
  34518. type: 'linear'
  34519. },
  34520. /**
  34521. * @cfg {Object} style
  34522. * An object containing styles for the visualization lines. These styles will override
  34523. * the theme styles.
  34524. * Some options contained within the style object will are described next.
  34525. */
  34526. /**
  34527. * @cfg {Boolean} smooth
  34528. * `true` if the series' line should be smoothed.
  34529. * Line smoothing only works with gapless data.
  34530. * @deprecated 6.5.0 Use the {@link #curve} config instead.
  34531. */
  34532. smooth: null,
  34533. /**
  34534. * @cfg {Boolean} step
  34535. * If set to `true`, the line uses steps instead of straight lines to connect the dots.
  34536. * It is ignored if `smooth` is true.
  34537. * @deprecated 6.5.0 Use the {@link #curve} config instead.
  34538. */
  34539. step: null,
  34540. /**
  34541. * @cfg {"gap"/"connect"/"origin"} [nullStyle="gap"]
  34542. * Possible values:
  34543. * 'gap' - null points are rendered as gaps.
  34544. * 'connect' - non-null points are connected across null points, so that
  34545. * there is no gap, unless null points are at the beginning/end of the line.
  34546. * Only the visible data points are connected - if a visible data point
  34547. * is followed by a series of null points that go off screen and eventually
  34548. * terminate with a non-null point, the connection won't be made.
  34549. * 'origin' - null data points are rendered at the origin,
  34550. * which is the y-coordinate of a point where the x and y axes meet.
  34551. * This requires that at least the x-coordinate of a point is a valid value.
  34552. */
  34553. nullStyle: 'gap',
  34554. /**
  34555. * @cfg {Boolean} fill
  34556. * If set to `true`, the area underneath the line is filled with the color defined
  34557. * as follows, listed by priority:
  34558. * - The color that is configured for this series ({@link Ext.chart.series.Series#colors}).
  34559. * - The color that is configured for this chart ({@link Ext.chart.AbstractChart#colors}).
  34560. * - The fill color that is set in the {@link #style} config.
  34561. * - The stroke color that is set in the {@link #style} config, or the same color
  34562. * as the line.
  34563. *
  34564. * Note: Do not confuse `series.config.fill` (which is a boolean) with `series.style.fill'
  34565. * (which is an alias for the `fillStyle` property and contains a color). For compatibility
  34566. * with previous versions of the API, if `config.fill` is undefined but a `style.fill' color
  34567. * is provided, `config.fill` is considered true. So the default value below must be
  34568. * undefined, not false.
  34569. */
  34570. fill: undefined,
  34571. aggregator: {
  34572. strategy: 'double'
  34573. }
  34574. },
  34575. themeMarkerCount: function() {
  34576. return 1;
  34577. },
  34578. /**
  34579. * @private
  34580. * Override {@link Ext.chart.series.Series#getDefaultSpriteConfig}
  34581. */
  34582. getDefaultSpriteConfig: function() {
  34583. var me = this,
  34584. parentConfig = me.callParent(arguments),
  34585. style = Ext.apply({}, me.getStyle()),
  34586. styleWithTheme,
  34587. fillArea = false;
  34588. if (me.config.fill !== undefined) {
  34589. // If config.fill is present but there is no fillStyle, then use the
  34590. // strokeStyle to fill (and paint the area the same color as the line).
  34591. if (me.config.fill) {
  34592. fillArea = true;
  34593. if (style.fillStyle === undefined) {
  34594. if (style.strokeStyle === undefined) {
  34595. styleWithTheme = me.getStyleWithTheme();
  34596. style.fillStyle = styleWithTheme.fillStyle;
  34597. style.strokeStyle = styleWithTheme.strokeStyle;
  34598. } else {
  34599. style.fillStyle = style.strokeStyle;
  34600. }
  34601. }
  34602. }
  34603. } else {
  34604. // For compatibility with previous versions of the API, if config.fill
  34605. // is undefined but style.fillStyle is provided, we fill the area.
  34606. if (style.fillStyle) {
  34607. fillArea = true;
  34608. }
  34609. }
  34610. // If we don't fill, then delete the fillStyle because that's what is used by
  34611. // the Line sprite to fill below the line.
  34612. if (!fillArea) {
  34613. delete style.fillStyle;
  34614. }
  34615. style = Ext.apply(parentConfig || {}, style);
  34616. return Ext.apply(style, {
  34617. fillArea: fillArea,
  34618. selectionTolerance: me.config.selectionTolerance
  34619. });
  34620. },
  34621. updateFill: function(fill) {
  34622. this.withSprite(function(sprite) {
  34623. return sprite.setAttributes({
  34624. fillArea: fill
  34625. });
  34626. });
  34627. },
  34628. updateCurve: function(curve) {
  34629. this.withSprite(function(sprite) {
  34630. return sprite.setAttributes({
  34631. curve: curve
  34632. });
  34633. });
  34634. },
  34635. getCurve: function() {
  34636. return this.withSprite(function(sprite) {
  34637. return sprite.attr.curve;
  34638. });
  34639. },
  34640. updateNullStyle: function(nullStyle) {
  34641. this.withSprite(function(sprite) {
  34642. return sprite.setAttributes({
  34643. nullStyle: nullStyle
  34644. });
  34645. });
  34646. },
  34647. updateSmooth: function(smooth) {
  34648. this.setCurve({
  34649. type: smooth ? 'natural' : 'linear'
  34650. });
  34651. },
  34652. updateStep: function(step) {
  34653. this.setCurve({
  34654. type: step ? 'step-after' : 'linear'
  34655. });
  34656. }
  34657. });
  34658. /**
  34659. * @class Ext.chart.series.sprite.PieSlice
  34660. *
  34661. * Pie slice sprite.
  34662. */
  34663. Ext.define('Ext.chart.series.sprite.PieSlice', {
  34664. extend: 'Ext.draw.sprite.Sector',
  34665. mixins: {
  34666. markerHolder: 'Ext.chart.MarkerHolder'
  34667. },
  34668. alias: 'sprite.pieslice',
  34669. inheritableStatics: {
  34670. def: {
  34671. processors: {
  34672. /**
  34673. * @cfg {Boolean} [doCallout=true]
  34674. * 'true' if the pie series uses label callouts.
  34675. */
  34676. doCallout: 'bool',
  34677. /**
  34678. * @cfg {String} [label='']
  34679. * Label associated with the Pie sprite.
  34680. */
  34681. label: 'string',
  34682. // @deprecated Use series.label.orientation config instead.
  34683. // @since 5.0.1
  34684. rotateLabels: 'bool',
  34685. /**
  34686. * @cfg {Number} [labelOverflowPadding=10]
  34687. * Padding around labels to determine overlap.
  34688. * Any negative number allows the labels to overlap.
  34689. */
  34690. labelOverflowPadding: 'number',
  34691. renderer: 'default'
  34692. },
  34693. defaults: {
  34694. doCallout: true,
  34695. rotateLabels: true,
  34696. label: '',
  34697. labelOverflowPadding: 10,
  34698. renderer: null
  34699. }
  34700. }
  34701. },
  34702. config: {
  34703. /**
  34704. * @private
  34705. * @cfg {Object} rendererData The object that is passed to the renderer.
  34706. *
  34707. * For instance when the PieSlice sprite is used in a Gauge chart, the object
  34708. * contains the 'store' and 'angleField' properties, and the 'value' as well
  34709. * for that one PieSlice that is used to draw the needle of the Gauge.
  34710. */
  34711. rendererData: null,
  34712. rendererIndex: 0,
  34713. series: null
  34714. },
  34715. setGradientBBox: function(ctx, rect) {
  34716. var me = this,
  34717. attr = me.attr,
  34718. hasGradients = (attr.fillStyle && attr.fillStyle.isGradient) || (attr.strokeStyle && attr.strokeStyle.isGradient);
  34719. if (hasGradients && !attr.constrainGradients) {
  34720. // eslint-disable-next-line vars-on-top, one-var
  34721. var midAngle = me.getMidAngle(),
  34722. margin = attr.margin,
  34723. cx = attr.centerX,
  34724. cy = attr.centerY,
  34725. r = attr.endRho,
  34726. matrix = attr.matrix,
  34727. scaleX = matrix.getScaleX(),
  34728. scaleY = matrix.getScaleY(),
  34729. w = scaleX * r,
  34730. h = scaleY * r,
  34731. bbox = {
  34732. width: w + w,
  34733. height: h + h
  34734. };
  34735. if (margin) {
  34736. cx += margin * Math.cos(midAngle);
  34737. cy += margin * Math.sin(midAngle);
  34738. }
  34739. bbox.x = matrix.x(cx, cy) - w;
  34740. bbox.y = matrix.y(cx, cy) - h;
  34741. ctx.setGradientBBox(bbox);
  34742. } else {
  34743. me.callParent([
  34744. ctx,
  34745. rect
  34746. ]);
  34747. }
  34748. },
  34749. render: function(surface, ctx, rect) {
  34750. var me = this,
  34751. attr = me.attr,
  34752. itemCfg = {},
  34753. changes;
  34754. if (attr.renderer) {
  34755. itemCfg = {
  34756. type: 'sector',
  34757. centerX: attr.centerX,
  34758. centerY: attr.centerY,
  34759. margin: attr.margin,
  34760. startAngle: Math.min(attr.startAngle, attr.endAngle),
  34761. endAngle: Math.max(attr.startAngle, attr.endAngle),
  34762. startRho: Math.min(attr.startRho, attr.endRho),
  34763. endRho: Math.max(attr.startRho, attr.endRho)
  34764. };
  34765. changes = Ext.callback(attr.renderer, null, [
  34766. me,
  34767. itemCfg,
  34768. me.getRendererData(),
  34769. me.getRendererIndex()
  34770. ], 0, me.getSeries());
  34771. me.setAttributes(changes);
  34772. me.useAttributes(ctx, rect);
  34773. }
  34774. // Draw the sector
  34775. me.callParent([
  34776. surface,
  34777. ctx,
  34778. rect
  34779. ]);
  34780. // Draw the labels
  34781. if (attr.label && me.getMarker('labels')) {
  34782. me.placeLabel();
  34783. }
  34784. },
  34785. placeLabel: function() {
  34786. var me = this,
  34787. attr = me.attr,
  34788. attributeId = attr.attributeId,
  34789. startAngle = Math.min(attr.startAngle, attr.endAngle),
  34790. endAngle = Math.max(attr.startAngle, attr.endAngle),
  34791. midAngle = (startAngle + endAngle) * 0.5,
  34792. margin = attr.margin,
  34793. centerX = attr.centerX,
  34794. centerY = attr.centerY,
  34795. sinMidAngle = Math.sin(midAngle),
  34796. cosMidAngle = Math.cos(midAngle),
  34797. startRho = Math.min(attr.startRho, attr.endRho) + margin,
  34798. endRho = Math.max(attr.startRho, attr.endRho) + margin,
  34799. midRho = (startRho + endRho) * 0.5,
  34800. surfaceMatrix = me.surfaceMatrix,
  34801. labelCfg = me.labelCfg || (me.labelCfg = {}),
  34802. label = me.getMarker('labels'),
  34803. labelTpl = label.getTemplate(),
  34804. hideLessThan = labelTpl.getHideLessThan(),
  34805. calloutLine = labelTpl.getCalloutLine(),
  34806. labelBox, x, y, changes, params, calloutLineLength;
  34807. if (calloutLine) {
  34808. calloutLineLength = calloutLine.length || 40;
  34809. } else {
  34810. calloutLineLength = 0;
  34811. }
  34812. surfaceMatrix.appendMatrix(attr.matrix);
  34813. labelCfg.text = attr.label;
  34814. x = centerX + cosMidAngle * midRho;
  34815. y = centerY + sinMidAngle * midRho;
  34816. labelCfg.x = surfaceMatrix.x(x, y);
  34817. labelCfg.y = surfaceMatrix.y(x, y);
  34818. x = centerX + cosMidAngle * endRho;
  34819. y = centerY + sinMidAngle * endRho;
  34820. labelCfg.calloutStartX = surfaceMatrix.x(x, y);
  34821. labelCfg.calloutStartY = surfaceMatrix.y(x, y);
  34822. x = centerX + cosMidAngle * (endRho + calloutLineLength);
  34823. y = centerY + sinMidAngle * (endRho + calloutLineLength);
  34824. labelCfg.calloutPlaceX = surfaceMatrix.x(x, y);
  34825. labelCfg.calloutPlaceY = surfaceMatrix.y(x, y);
  34826. if (!attr.rotateLabels) {
  34827. labelCfg.rotationRads = 0;
  34828. //<debug>
  34829. Ext.log.warn("'series.style.rotateLabels' config is deprecated. " + "Use 'series.label.orientation' config instead.");
  34830. } else //</debug>
  34831. {
  34832. switch (labelTpl.attr.orientation) {
  34833. case 'horizontal':
  34834. labelCfg.rotationRads = midAngle + Math.atan2(surfaceMatrix.y(1, 0) - surfaceMatrix.y(0, 0), surfaceMatrix.x(1, 0) - surfaceMatrix.x(0, 0)) + Math.PI / 2;
  34835. break;
  34836. case 'vertical':
  34837. labelCfg.rotationRads = midAngle + Math.atan2(surfaceMatrix.y(1, 0) - surfaceMatrix.y(0, 0), surfaceMatrix.x(1, 0) - surfaceMatrix.x(0, 0));
  34838. break;
  34839. }
  34840. }
  34841. labelCfg.calloutColor = (calloutLine && calloutLine.color) || me.attr.fillStyle;
  34842. if (calloutLine) {
  34843. if (calloutLine.width) {
  34844. labelCfg.calloutWidth = calloutLine.width;
  34845. }
  34846. } else {
  34847. labelCfg.calloutColor = 'none';
  34848. }
  34849. labelCfg.globalAlpha = attr.globalAlpha * attr.fillOpacity;
  34850. // If a slice is empty, don't display the label.
  34851. // This behavior can be overridden by a renderer.
  34852. if (labelTpl.display !== 'none') {
  34853. // eslint-disable-next-line eqeqeq
  34854. labelCfg.hidden = (attr.startAngle == attr.endAngle);
  34855. }
  34856. if (labelTpl.attr.renderer) {
  34857. // Note: the labels are 'put' by the Ext.chart.series.Pie.updateLabelData, so we can
  34858. // be sure the label sprite instances will exist and can be accessed from the label
  34859. // renderer on first render. For example, with 'bar' series this isn't the case,
  34860. // so we make a check and create a label instance if necessary.
  34861. params = [
  34862. me.attr.label,
  34863. label,
  34864. labelCfg,
  34865. me.getRendererData(),
  34866. me.getRendererIndex()
  34867. ];
  34868. changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
  34869. if (typeof changes === 'string') {
  34870. labelCfg.text = changes;
  34871. } else {
  34872. Ext.apply(labelCfg, changes);
  34873. }
  34874. }
  34875. me.putMarker('labels', labelCfg, attributeId);
  34876. labelBox = me.getMarkerBBox('labels', attributeId, true);
  34877. if (labelBox) {
  34878. if (attr.doCallout && ((endAngle - startAngle) * endRho > hideLessThan || attr.highlighted)) {
  34879. if (labelTpl.attr.display === 'outside') {
  34880. me.putMarker('labels', {
  34881. callout: 1
  34882. }, attributeId);
  34883. } else if (labelTpl.attr.display === 'inside') {
  34884. me.putMarker('labels', {
  34885. callout: 0
  34886. }, attributeId);
  34887. } else {
  34888. me.putMarker('labels', {
  34889. callout: 1 - me.sliceContainsLabel(attr, labelBox)
  34890. }, attributeId);
  34891. }
  34892. } else {
  34893. me.putMarker('labels', {
  34894. globalAlpha: me.sliceContainsLabel(attr, labelBox)
  34895. }, attributeId);
  34896. }
  34897. }
  34898. },
  34899. sliceContainsLabel: function(attr, bbox) {
  34900. var padding = attr.labelOverflowPadding,
  34901. middle = (attr.endRho + attr.startRho) / 2,
  34902. outer = middle + (bbox.width + padding) / 2,
  34903. inner = middle - (bbox.width + padding) / 2,
  34904. sliceAngle, l1, l2, l3;
  34905. if (padding < 0) {
  34906. return 1;
  34907. }
  34908. if (bbox.width + padding * 2 > (attr.endRho - attr.startRho)) {
  34909. return 0;
  34910. }
  34911. l1 = Math.sqrt(attr.endRho * attr.endRho - outer * outer);
  34912. l2 = Math.sqrt(attr.endRho * attr.endRho - inner * inner);
  34913. sliceAngle = Math.abs(attr.endAngle - attr.startAngle);
  34914. l3 = (sliceAngle > Math.PI / 2 ? inner : Math.abs(Math.tan(sliceAngle / 2)) * inner);
  34915. if (bbox.height + padding * 2 > Math.min(l1, l2, l3) * 2) {
  34916. return 0;
  34917. }
  34918. return 1;
  34919. }
  34920. });
  34921. /**
  34922. * @class Ext.chart.series.Pie
  34923. * @extends Ext.chart.series.Polar
  34924. *
  34925. * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display
  34926. * quantitative information for different categories that also have a meaning as a whole.
  34927. * As with all other series, the Pie Series must be appended in the *series* Chart array
  34928. * configuration. See the Chart documentation for more information. A typical configuration
  34929. * object for the pie series could be:
  34930. *
  34931. * @example
  34932. * Ext.create({
  34933. * xtype: 'polar',
  34934. * renderTo: document.body,
  34935. * width: 400,
  34936. * height: 400,
  34937. * theme: 'green',
  34938. * interactions: ['rotate', 'itemhighlight'],
  34939. * store: {
  34940. * fields: ['name', 'data1'],
  34941. * data: [{
  34942. * name: 'metric one',
  34943. * data1: 14
  34944. * }, {
  34945. * name: 'metric two',
  34946. * data1: 16
  34947. * }, {
  34948. * name: 'metric three',
  34949. * data1: 14
  34950. * }, {
  34951. * name: 'metric four',
  34952. * data1: 6
  34953. * }, {
  34954. * name: 'metric five',
  34955. * data1: 36
  34956. * }]
  34957. * },
  34958. * series: {
  34959. * type: 'pie',
  34960. * highlight: true,
  34961. * angleField: 'data1',
  34962. * label: {
  34963. * field: 'name',
  34964. * display: 'rotate'
  34965. * },
  34966. * donut: 30
  34967. * }
  34968. * });
  34969. *
  34970. * In this configuration we set `pie` as the type for the series, then set the `highlight` config
  34971. * to `true` (we can also specify an object with specific style properties for highlighting options)
  34972. * which is triggered when hovering or tapping elements.
  34973. * We set `data1` as the value of the `angleField` to determine the angular span for each pie slice.
  34974. * We also set a label configuration object where we set the name of the store field
  34975. * to be rendered as text for the label. The labels will also be displayed rotated.
  34976. * And finally, we specify the donut hole radius for the pie series in percentages of the series
  34977. * radius.
  34978. *
  34979. */
  34980. Ext.define('Ext.chart.series.Pie', {
  34981. extend: 'Ext.chart.series.Polar',
  34982. requires: [
  34983. 'Ext.chart.series.sprite.PieSlice'
  34984. ],
  34985. type: 'pie',
  34986. alias: 'series.pie',
  34987. seriesType: 'pieslice',
  34988. isPie: true,
  34989. config: {
  34990. /**
  34991. * @cfg {String} radiusField
  34992. * The store record field name to be used for the pie slice lengths.
  34993. * The values bound to this field name must be positive real numbers.
  34994. */
  34995. /**
  34996. * @cfg {Number} donut Specifies the radius of the donut hole, as a percentage
  34997. * of the chart's radius.
  34998. * Defaults to 0 (no donut hole).
  34999. */
  35000. donut: 0,
  35001. /**
  35002. * @cfg {Number} rotation The starting angle of the pie slices.
  35003. */
  35004. rotation: 0,
  35005. /**
  35006. * @cfg {Boolean} clockwise
  35007. * Whether the pie slices are displayed clockwise. Default's true.
  35008. */
  35009. clockwise: true,
  35010. /**
  35011. * @cfg {Number} [totalAngle=2*PI] The total angle of the pie series.
  35012. */
  35013. totalAngle: 2 * Math.PI,
  35014. /**
  35015. * @cfg {Array} hidden Determines which pie slices are hidden.
  35016. */
  35017. hidden: [],
  35018. /**
  35019. * @cfg {Number} [radiusFactor=100] Allows adjustment of the radius
  35020. * by a specific percentage.
  35021. */
  35022. radiusFactor: 100,
  35023. /**
  35024. * @cfg {Ext.chart.series.sprite.PieSlice/Object} highlightCfg
  35025. * Default highlight config for the pie series.
  35026. * Slides highlighted pie sector outward by default.
  35027. *
  35028. * highlightCfg accepts as its value a config object (or array of configs) for a
  35029. * {@link Ext.chart.series.sprite.PieSlice pie sprite}.
  35030. *
  35031. *
  35032. * Example config:
  35033. *
  35034. * Ext.create('Ext.chart.PolarChart', {
  35035. * renderTo: document.body,
  35036. * width: 600,
  35037. * height: 400,
  35038. * innerPadding: 5,
  35039. * store: {
  35040. * fields: ['name', 'data1'],
  35041. * data: [{
  35042. * name: 'metric one',
  35043. * data1: 10
  35044. * }, {
  35045. * name: 'metric two',
  35046. * data1: 7
  35047. * }, {
  35048. * name: 'metric three',
  35049. * data1: 5
  35050. * }]
  35051. * },
  35052. * series: {
  35053. * type: 'pie',
  35054. * label: {
  35055. * field: 'name',
  35056. * display: 'rotate'
  35057. * },
  35058. * xField: 'data1',
  35059. * donut: 30,
  35060. * highlightCfg: {
  35061. * margin: 10,
  35062. * fillOpacity: .7
  35063. * }
  35064. * }
  35065. * });
  35066. */
  35067. highlightCfg: {
  35068. margin: 20
  35069. },
  35070. style: {}
  35071. },
  35072. directions: [
  35073. 'X'
  35074. ],
  35075. applyLabel: function(newLabel, oldLabel) {
  35076. if (Ext.isObject(newLabel) && !Ext.isString(newLabel.orientation)) {
  35077. // Override default label orientation from '' to 'vertical'.
  35078. Ext.apply(newLabel = Ext.Object.chain(newLabel), {
  35079. orientation: 'vertical'
  35080. });
  35081. }
  35082. return this.callParent([
  35083. newLabel,
  35084. oldLabel
  35085. ]);
  35086. },
  35087. updateLabelData: function() {
  35088. var me = this,
  35089. store = me.getStore(),
  35090. items = store.getData().items,
  35091. sprites = me.getSprites(),
  35092. label = me.getLabel(),
  35093. labelField = label && label.getTemplate().getField(),
  35094. hidden = me.getHidden(),
  35095. i, ln, labels, sprite;
  35096. if (sprites.length && labelField) {
  35097. labels = [];
  35098. for (i = 0 , ln = items.length; i < ln; i++) {
  35099. labels.push(items[i].get(labelField));
  35100. }
  35101. for (i = 0 , ln = sprites.length; i < ln; i++) {
  35102. sprite = sprites[i];
  35103. sprite.setAttributes({
  35104. label: labels[i]
  35105. });
  35106. sprite.putMarker('labels', {
  35107. hidden: hidden[i]
  35108. }, sprite.attr.attributeId);
  35109. }
  35110. }
  35111. },
  35112. coordinateX: function() {
  35113. var me = this,
  35114. store = me.getStore(),
  35115. records = store.getData().items,
  35116. recordCount = records.length,
  35117. xField = me.getXField(),
  35118. yField = me.getYField(),
  35119. x,
  35120. sumX = 0,
  35121. unit, y,
  35122. maxY = 0,
  35123. hidden = me.getHidden(),
  35124. summation = [],
  35125. i,
  35126. lastAngle = 0,
  35127. totalAngle = me.getTotalAngle(),
  35128. clockwise = me.getClockwise() ? 1 : -1,
  35129. sprites = me.getSprites(),
  35130. sprite, labels;
  35131. if (!sprites) {
  35132. return;
  35133. }
  35134. for (i = 0; i < recordCount; i++) {
  35135. x = Math.abs(Number(records[i].get(xField))) || 0;
  35136. y = yField && Math.abs(Number(records[i].get(yField))) || 0;
  35137. if (!hidden[i]) {
  35138. sumX += x;
  35139. if (y > maxY) {
  35140. maxY = y;
  35141. }
  35142. }
  35143. summation[i] = sumX;
  35144. if (i >= hidden.length) {
  35145. hidden[i] = false;
  35146. }
  35147. }
  35148. hidden.length = recordCount;
  35149. me.maxY = maxY;
  35150. if (sumX !== 0) {
  35151. unit = totalAngle / sumX;
  35152. }
  35153. for (i = 0; i < recordCount; i++) {
  35154. sprites[i].setAttributes({
  35155. startAngle: lastAngle,
  35156. endAngle: lastAngle = (unit ? clockwise * summation[i] * unit : 0),
  35157. globalAlpha: 1
  35158. });
  35159. }
  35160. if (recordCount < sprites.length) {
  35161. for (i = recordCount; i < sprites.length; i++) {
  35162. sprite = sprites[i];
  35163. labels = sprite.getMarker('labels');
  35164. if (labels) {
  35165. // Don't want the 'labels' Markers and its 'template' sprite to be destroyed
  35166. // with the PieSlice MarkerHolder, as it is also used by other pie slices.
  35167. // So we release 'labels' before destroying the PieSlice.
  35168. // But first, we have to clear the instances of the 'labels'
  35169. // Markers created by the PieSlice MarkerHolder.
  35170. labels.clear(sprite.getId());
  35171. sprite.releaseMarker('labels');
  35172. }
  35173. sprite.destroy();
  35174. }
  35175. sprites.length = recordCount;
  35176. }
  35177. for (i = recordCount; i < sprites.length; i++) {
  35178. sprites[i].setAttributes({
  35179. startAngle: totalAngle,
  35180. endAngle: totalAngle,
  35181. globalAlpha: 0
  35182. });
  35183. }
  35184. },
  35185. updateCenter: function(center) {
  35186. this.setStyle({
  35187. translationX: center[0] + this.getOffsetX(),
  35188. translationY: center[1] + this.getOffsetY()
  35189. });
  35190. this.doUpdateStyles();
  35191. },
  35192. updateRadius: function(radius) {
  35193. this.setStyle({
  35194. startRho: radius * this.getDonut() * 0.01,
  35195. endRho: radius * this.getRadiusFactor() * 0.01
  35196. });
  35197. this.doUpdateStyles();
  35198. },
  35199. getStyleByIndex: function(i) {
  35200. var me = this,
  35201. store = me.getStore(),
  35202. item = store.getAt(i),
  35203. yField = me.getYField(),
  35204. radius = me.getRadius(),
  35205. style = {},
  35206. startRho, endRho, y;
  35207. if (item) {
  35208. y = yField && Math.abs(Number(item.get(yField))) || 0;
  35209. startRho = radius * me.getDonut() * 0.01;
  35210. endRho = radius * me.getRadiusFactor() * 0.01;
  35211. style = me.callParent([
  35212. i
  35213. ]);
  35214. style.startRho = startRho;
  35215. style.endRho = me.maxY ? (startRho + (endRho - startRho) * y / me.maxY) : endRho;
  35216. }
  35217. return style;
  35218. },
  35219. updateDonut: function(donut) {
  35220. var radius = this.getRadius();
  35221. this.setStyle({
  35222. startRho: radius * donut * 0.01,
  35223. endRho: radius * this.getRadiusFactor() * 0.01
  35224. });
  35225. this.doUpdateStyles();
  35226. },
  35227. // Subtract 90 degrees from rotation, so that `rotation` config's default
  35228. // value of 0 makes first pie sector start at noon, rather than 3 o'clock.
  35229. rotationOffset: -Math.PI / 2,
  35230. updateRotation: function(rotation) {
  35231. this.setStyle({
  35232. rotationRads: rotation + this.rotationOffset
  35233. });
  35234. this.doUpdateStyles();
  35235. },
  35236. updateTotalAngle: function(totalAngle) {
  35237. this.processData();
  35238. },
  35239. getSprites: function() {
  35240. var me = this,
  35241. chart = me.getChart(),
  35242. store = me.getStore();
  35243. if (!chart || !store) {
  35244. return Ext.emptyArray;
  35245. }
  35246. me.getColors();
  35247. me.getSubStyle();
  35248. // eslint-disable-next-line vars-on-top, one-var
  35249. var items = store.getData().items,
  35250. length = items.length,
  35251. animation = me.getAnimation() || chart && chart.getAnimation(),
  35252. sprites = me.sprites,
  35253. sprite,
  35254. spriteCreated = false,
  35255. spriteIndex = 0,
  35256. label = me.getLabel(),
  35257. labelTpl = label && label.getTemplate(),
  35258. i, rendererData;
  35259. rendererData = {
  35260. store: store,
  35261. field: me.getXField(),
  35262. // for backward compatibility only (deprecated in 5.5)
  35263. angleField: me.getXField(),
  35264. radiusField: me.getYField(),
  35265. series: me
  35266. };
  35267. for (i = 0; i < length; i++) {
  35268. sprite = sprites[i];
  35269. if (!sprite) {
  35270. sprite = me.createSprite();
  35271. if (me.getHighlight()) {
  35272. sprite.config.highlight = me.getHighlight();
  35273. sprite.addModifier('highlight', true);
  35274. }
  35275. if (labelTpl && labelTpl.getField()) {
  35276. labelTpl.setAttributes({
  35277. labelOverflowPadding: me.getLabelOverflowPadding()
  35278. });
  35279. labelTpl.getAnimation().setCustomDurations({
  35280. 'callout': 200
  35281. });
  35282. }
  35283. sprite.setAttributes(me.getStyleByIndex(i));
  35284. sprite.setRendererData(rendererData);
  35285. spriteCreated = true;
  35286. }
  35287. sprite.setRendererIndex(spriteIndex++);
  35288. sprite.setAnimation(animation);
  35289. }
  35290. if (spriteCreated) {
  35291. me.doUpdateStyles();
  35292. }
  35293. return me.sprites;
  35294. },
  35295. betweenAngle: function(x, a, b) {
  35296. var pp = Math.PI * 2,
  35297. offset = this.rotationOffset;
  35298. if (a === b) {
  35299. return false;
  35300. }
  35301. if (!this.getClockwise()) {
  35302. x *= -1;
  35303. a *= -1;
  35304. b *= -1;
  35305. a -= offset;
  35306. b -= offset;
  35307. } else {
  35308. a += offset;
  35309. b += offset;
  35310. }
  35311. x -= a;
  35312. b -= a;
  35313. // Normalize, so that both x and b are in the [0,360) interval.
  35314. x %= pp;
  35315. b %= pp;
  35316. x += pp;
  35317. b += pp;
  35318. x %= pp;
  35319. b %= pp;
  35320. // Because 360 * n angles will be normalized to 0,
  35321. // we need to treat b ~= 0 as a special case.
  35322. return x < b || Ext.Number.isEqual(b, 0, 1.0E-8);
  35323. },
  35324. getItemByIndex: function(index, category) {
  35325. category = category || 'sprites';
  35326. return this.callParent([
  35327. index,
  35328. category
  35329. ]);
  35330. },
  35331. /**
  35332. * Returns the pie slice for a given angle
  35333. * @param {Number} angle The angle to search for the slice
  35334. * @return {Object} An object containing the reocord, sprite, scope etc.
  35335. */
  35336. getItemForAngle: function(angle) {
  35337. var me = this,
  35338. sprites = me.getSprites(),
  35339. attr, store, items, hidden, i, ln;
  35340. angle %= Math.PI * 2;
  35341. while (angle < 0) {
  35342. angle += Math.PI * 2;
  35343. }
  35344. if (sprites) {
  35345. store = me.getStore();
  35346. items = store.getData().items;
  35347. hidden = me.getHidden();
  35348. for (i = 0 , ln = store.getCount(); i < ln; i++) {
  35349. if (!hidden[i]) {
  35350. // Fortunately, item's id equals its index in the instances list.
  35351. attr = sprites[i].attr;
  35352. if (attr.startAngle <= angle && attr.endAngle >= angle) {
  35353. return {
  35354. series: me,
  35355. sprite: sprites[i],
  35356. index: i,
  35357. record: items[i],
  35358. field: me.getXField()
  35359. };
  35360. }
  35361. }
  35362. }
  35363. }
  35364. return null;
  35365. },
  35366. getItemForPoint: function(x, y) {
  35367. var me = this,
  35368. sprites = me.getSprites(),
  35369. center = me.getCenter(),
  35370. offsetX = me.getOffsetX(),
  35371. offsetY = me.getOffsetY(),
  35372. // Distance from the center of the series to the cursor.
  35373. dx = x - center[0] + offsetX,
  35374. dy = y - center[1] + offsetY,
  35375. store = me.getStore(),
  35376. donut = me.getDonut(),
  35377. records = store.getData().items,
  35378. direction = Math.atan2(dy, dx) - me.getRotation(),
  35379. radius = Math.sqrt(dx * dx + dy * dy),
  35380. startRadius = me.getRadius() * donut * 0.01,
  35381. hidden = me.getHidden(),
  35382. result = null,
  35383. i, ln, attr, sprite;
  35384. for (i = 0 , ln = records.length; i < ln; i++) {
  35385. if (hidden[i]) {
  35386. continue;
  35387. }
  35388. sprite = sprites[i];
  35389. if (!sprite) {
  35390. break;
  35391. }
  35392. attr = sprite.attr;
  35393. if (radius >= startRadius + attr.margin && radius <= attr.endRho + attr.margin && me.betweenAngle(direction, attr.startAngle, attr.endAngle)) {
  35394. result = {
  35395. series: me,
  35396. sprite: sprites[i],
  35397. index: i,
  35398. record: records[i],
  35399. field: me.getXField()
  35400. };
  35401. break;
  35402. }
  35403. }
  35404. return result;
  35405. },
  35406. provideLegendInfo: function(target) {
  35407. var me = this,
  35408. store = me.getStore(),
  35409. items, labelField, xField, hidden, i, style, fill;
  35410. if (store) {
  35411. items = store.getData().items;
  35412. labelField = me.getLabel().getTemplate().getField();
  35413. xField = me.getXField();
  35414. hidden = me.getHidden();
  35415. for (i = 0; i < items.length; i++) {
  35416. style = me.getStyleByIndex(i);
  35417. fill = style.fillStyle;
  35418. if (Ext.isObject(fill)) {
  35419. fill = fill.stops && fill.stops[0].color;
  35420. }
  35421. target.push({
  35422. name: labelField ? String(items[i].get(labelField)) : xField + ' ' + i,
  35423. mark: fill || style.strokeStyle || 'black',
  35424. disabled: hidden[i],
  35425. series: me.getId(),
  35426. index: i
  35427. });
  35428. }
  35429. }
  35430. }
  35431. });
  35432. /**
  35433. * @class Ext.chart.series.sprite.Pie3DPart
  35434. * @extends Ext.draw.sprite.Path
  35435. *
  35436. * Pie3D series sprite.
  35437. */
  35438. Ext.define('Ext.chart.series.sprite.Pie3DPart', {
  35439. extend: 'Ext.draw.sprite.Path',
  35440. mixins: {
  35441. markerHolder: 'Ext.chart.MarkerHolder'
  35442. },
  35443. alias: 'sprite.pie3dPart',
  35444. inheritableStatics: {
  35445. def: {
  35446. processors: {
  35447. /**
  35448. * @cfg {Number} [centerX=0]
  35449. * The central point of the series on the x-axis.
  35450. */
  35451. centerX: 'number',
  35452. /**
  35453. * @cfg {Number} [centerY=0]
  35454. * The central point of the series on the x-axis.
  35455. */
  35456. centerY: 'number',
  35457. /**
  35458. * @cfg {Number} [startAngle=0]
  35459. * The starting angle of the polar series.
  35460. */
  35461. startAngle: 'number',
  35462. /**
  35463. * @cfg {Number} [endAngle=Math.PI]
  35464. * The ending angle of the polar series.
  35465. */
  35466. endAngle: 'number',
  35467. /**
  35468. * @cfg {Number} [startRho=0]
  35469. * The starting radius of the polar series.
  35470. */
  35471. startRho: 'number',
  35472. /**
  35473. * @cfg {Number} [endRho=150]
  35474. * The ending radius of the polar series.
  35475. */
  35476. endRho: 'number',
  35477. /**
  35478. * @cfg {Number} [margin=0]
  35479. * Margin from the center of the pie. Used for donut.
  35480. */
  35481. margin: 'number',
  35482. /**
  35483. * @cfg {Number} [thickness=0]
  35484. * The thickness of the 3D pie part.
  35485. */
  35486. thickness: 'number',
  35487. /**
  35488. * @cfg {Number} [bevelWidth=5]
  35489. * The size of the 3D pie bevel.
  35490. */
  35491. bevelWidth: 'number',
  35492. /**
  35493. * @cfg {Number} [distortion=0]
  35494. * The distortion of the 3D pie part.
  35495. */
  35496. distortion: 'number',
  35497. /**
  35498. * @cfg {Object} [baseColor='white']
  35499. * The color of the 3D pie part before adding the 3D effect.
  35500. */
  35501. baseColor: 'color',
  35502. /**
  35503. * @cfg {Number} [colorSpread=0.7]
  35504. * An attribute used to control how flat the gradient of the sprite looks.
  35505. * A value of 0 essentially means no gradient (flat color).
  35506. */
  35507. colorSpread: 'number',
  35508. /**
  35509. * @cfg {Number} [baseRotation=0]
  35510. * The starting rotation of the polar series.
  35511. */
  35512. baseRotation: 'number',
  35513. /**
  35514. * @cfg {String} [part='top']
  35515. * The part of the 3D Pie represented by the sprite.
  35516. */
  35517. part: 'enums(top,bottom,start,end,innerFront,innerBack,outerFront,outerBack)',
  35518. /**
  35519. * @cfg {String} [label='']
  35520. * The label associated with the 'top' part of the sprite.
  35521. */
  35522. label: 'string'
  35523. },
  35524. aliases: {
  35525. rho: 'endRho'
  35526. },
  35527. triggers: {
  35528. centerX: 'path,bbox',
  35529. centerY: 'path,bbox',
  35530. startAngle: 'path,partZIndex',
  35531. endAngle: 'path,partZIndex',
  35532. startRho: 'path',
  35533. endRho: 'path,bbox',
  35534. margin: 'path,bbox',
  35535. thickness: 'path',
  35536. distortion: 'path',
  35537. baseRotation: 'path,partZIndex',
  35538. baseColor: 'partZIndex,partColor',
  35539. colorSpread: 'partColor',
  35540. part: 'path,partZIndex',
  35541. globalAlpha: 'canvas,alpha',
  35542. fillOpacity: 'canvas,alpha'
  35543. },
  35544. defaults: {
  35545. centerX: 0,
  35546. centerY: 0,
  35547. startAngle: Math.PI * 2,
  35548. endAngle: Math.PI * 2,
  35549. startRho: 0,
  35550. endRho: 150,
  35551. margin: 0,
  35552. thickness: 35,
  35553. distortion: 0.5,
  35554. baseRotation: 0,
  35555. baseColor: 'white',
  35556. colorSpread: 0.5,
  35557. miterLimit: 1,
  35558. bevelWidth: 5,
  35559. strokeOpacity: 0,
  35560. part: 'top',
  35561. label: ''
  35562. },
  35563. updaters: {
  35564. alpha: 'alphaUpdater',
  35565. partColor: 'partColorUpdater',
  35566. partZIndex: 'partZIndexUpdater'
  35567. }
  35568. }
  35569. },
  35570. config: {
  35571. renderer: null,
  35572. rendererData: null,
  35573. rendererIndex: 0,
  35574. series: null
  35575. },
  35576. bevelParams: [],
  35577. constructor: function(config) {
  35578. this.callParent([
  35579. config
  35580. ]);
  35581. this.bevelGradient = new Ext.draw.gradient.Linear({
  35582. stops: [
  35583. {
  35584. offset: 0,
  35585. color: 'rgba(255,255,255,0)'
  35586. },
  35587. {
  35588. offset: 0.7,
  35589. color: 'rgba(255,255,255,0.6)'
  35590. },
  35591. {
  35592. offset: 1,
  35593. color: 'rgba(255,255,255,0)'
  35594. }
  35595. ]
  35596. });
  35597. },
  35598. updateRenderer: function() {
  35599. this.setDirty(true);
  35600. },
  35601. updateRendererData: function() {
  35602. this.setDirty(true);
  35603. },
  35604. updateRendererIndex: function() {
  35605. this.setDirty(true);
  35606. },
  35607. alphaUpdater: function(attr) {
  35608. var me = this,
  35609. opacity = attr.globalAlpha,
  35610. fillOpacity = attr.fillOpacity,
  35611. oldOpacity = me.oldOpacity,
  35612. oldFillOpacity = me.oldFillOpacity;
  35613. // Update the path when the sprite becomes translucent or completely opaque.
  35614. if ((opacity !== oldOpacity && (opacity === 1 || oldOpacity === 1)) || (fillOpacity !== oldFillOpacity && (fillOpacity === 1 || oldFillOpacity === 1))) {
  35615. me.scheduleUpdater(attr, 'path', [
  35616. 'globalAlpha'
  35617. ]);
  35618. me.oldOpacity = opacity;
  35619. me.oldFillOpacity = fillOpacity;
  35620. }
  35621. },
  35622. partColorUpdater: function(attr) {
  35623. var color = Ext.util.Color.fly(attr.baseColor),
  35624. colorString = color.toString(),
  35625. colorSpread = attr.colorSpread,
  35626. fillStyle;
  35627. switch (attr.part) {
  35628. case 'top':
  35629. fillStyle = new Ext.draw.gradient.Radial({
  35630. start: {
  35631. x: 0,
  35632. y: 0,
  35633. r: 0
  35634. },
  35635. end: {
  35636. x: 0,
  35637. y: 0,
  35638. r: 1
  35639. },
  35640. stops: [
  35641. {
  35642. offset: 0,
  35643. color: color.createLighter(0.1 * colorSpread)
  35644. },
  35645. {
  35646. offset: 1,
  35647. color: color.createDarker(0.1 * colorSpread)
  35648. }
  35649. ]
  35650. });
  35651. break;
  35652. case 'bottom':
  35653. fillStyle = new Ext.draw.gradient.Radial({
  35654. start: {
  35655. x: 0,
  35656. y: 0,
  35657. r: 0
  35658. },
  35659. end: {
  35660. x: 0,
  35661. y: 0,
  35662. r: 1
  35663. },
  35664. stops: [
  35665. {
  35666. offset: 0,
  35667. color: color.createDarker(0.2 * colorSpread)
  35668. },
  35669. {
  35670. offset: 1,
  35671. color: color.toString()
  35672. }
  35673. ]
  35674. });
  35675. break;
  35676. case 'outerFront':
  35677. case 'outerBack':
  35678. fillStyle = new Ext.draw.gradient.Linear({
  35679. stops: [
  35680. {
  35681. offset: 0,
  35682. color: color.createDarker(0.15 * colorSpread).toString()
  35683. },
  35684. {
  35685. offset: 0.3,
  35686. color: colorString
  35687. },
  35688. {
  35689. offset: 0.8,
  35690. color: color.createLighter(0.2 * colorSpread).toString()
  35691. },
  35692. {
  35693. offset: 1,
  35694. color: color.createDarker(0.25 * colorSpread).toString()
  35695. }
  35696. ]
  35697. });
  35698. break;
  35699. case 'start':
  35700. fillStyle = new Ext.draw.gradient.Linear({
  35701. stops: [
  35702. {
  35703. offset: 0,
  35704. color: color.createDarker(0.1 * colorSpread).toString()
  35705. },
  35706. {
  35707. offset: 1,
  35708. color: color.createLighter(0.2 * colorSpread).toString()
  35709. }
  35710. ]
  35711. });
  35712. break;
  35713. case 'end':
  35714. fillStyle = new Ext.draw.gradient.Linear({
  35715. stops: [
  35716. {
  35717. offset: 0,
  35718. color: color.createDarker(0.1 * colorSpread).toString()
  35719. },
  35720. {
  35721. offset: 1,
  35722. color: color.createLighter(0.2 * colorSpread).toString()
  35723. }
  35724. ]
  35725. });
  35726. break;
  35727. case 'innerFront':
  35728. case 'innerBack':
  35729. fillStyle = new Ext.draw.gradient.Linear({
  35730. stops: [
  35731. {
  35732. offset: 0,
  35733. color: color.createDarker(0.1 * colorSpread).toString()
  35734. },
  35735. {
  35736. offset: 0.2,
  35737. color: color.createLighter(0.2 * colorSpread).toString()
  35738. },
  35739. {
  35740. offset: 0.7,
  35741. color: colorString
  35742. },
  35743. {
  35744. offset: 1,
  35745. color: color.createDarker(0.1 * colorSpread).toString()
  35746. }
  35747. ]
  35748. });
  35749. break;
  35750. }
  35751. attr.fillStyle = fillStyle;
  35752. attr.canvasAttributes.fillStyle = fillStyle;
  35753. },
  35754. partZIndexUpdater: function(attr) {
  35755. var normalize = Ext.draw.sprite.AttributeParser.angle,
  35756. rotation = attr.baseRotation,
  35757. startAngle = attr.startAngle,
  35758. endAngle = attr.endAngle,
  35759. depth;
  35760. switch (attr.part) {
  35761. case 'top':
  35762. attr.zIndex = 6;
  35763. break;
  35764. case 'outerFront':
  35765. startAngle = normalize(startAngle + rotation);
  35766. endAngle = normalize(endAngle + rotation);
  35767. if (startAngle >= 0 && endAngle < 0) {
  35768. depth = Math.sin(startAngle);
  35769. } else if (startAngle <= 0 && endAngle > 0) {
  35770. depth = Math.sin(endAngle);
  35771. } else if (startAngle >= 0 && endAngle > 0) {
  35772. if (startAngle > endAngle) {
  35773. depth = 0;
  35774. } else {
  35775. depth = Math.max(Math.sin(startAngle), Math.sin(endAngle));
  35776. }
  35777. } else {
  35778. depth = 1;
  35779. };
  35780. attr.zIndex = 4 + depth;
  35781. break;
  35782. case 'outerBack':
  35783. attr.zIndex = 1;
  35784. break;
  35785. case 'start':
  35786. attr.zIndex = 4 + Math.sin(normalize(startAngle + rotation));
  35787. break;
  35788. case 'end':
  35789. attr.zIndex = 4 + Math.sin(normalize(endAngle + rotation));
  35790. break;
  35791. case 'innerFront':
  35792. attr.zIndex = 2;
  35793. break;
  35794. case 'innerBack':
  35795. attr.zIndex = 4 + Math.sin(normalize((startAngle + endAngle) / 2 + rotation));
  35796. break;
  35797. case 'bottom':
  35798. attr.zIndex = 0;
  35799. break;
  35800. }
  35801. attr.dirtyZIndex = true;
  35802. },
  35803. updatePlainBBox: function(plain) {
  35804. var attr = this.attr,
  35805. part = attr.part,
  35806. baseRotation = attr.baseRotation,
  35807. centerX = attr.centerX,
  35808. centerY = attr.centerY,
  35809. rho, angle, x, y, sin, cos;
  35810. if (part === 'start') {
  35811. angle = attr.startAngle + baseRotation;
  35812. } else if (part === 'end') {
  35813. angle = attr.endAngle + baseRotation;
  35814. }
  35815. if (Ext.isNumber(angle)) {
  35816. sin = Math.sin(angle);
  35817. cos = Math.cos(angle);
  35818. x = Math.min(centerX + cos * attr.startRho, centerX + cos * attr.endRho);
  35819. y = centerY + sin * attr.startRho * attr.distortion;
  35820. plain.x = x;
  35821. plain.y = y;
  35822. plain.width = cos * (attr.endRho - attr.startRho);
  35823. plain.height = attr.thickness + sin * (attr.endRho - attr.startRho) * 2;
  35824. return;
  35825. }
  35826. if (part === 'innerFront' || part === 'innerBack') {
  35827. rho = attr.startRho;
  35828. } else {
  35829. rho = attr.endRho;
  35830. }
  35831. plain.width = rho * 2;
  35832. plain.height = rho * attr.distortion * 2 + attr.thickness;
  35833. plain.x = attr.centerX - rho;
  35834. plain.y = attr.centerY - rho * attr.distortion;
  35835. },
  35836. updateTransformedBBox: function(transform) {
  35837. if (this.attr.part === 'start' || this.attr.part === 'end') {
  35838. return this.callParent(arguments);
  35839. }
  35840. return this.updatePlainBBox(transform);
  35841. },
  35842. updatePath: function(path) {
  35843. if (!this.attr.globalAlpha) {
  35844. return;
  35845. }
  35846. if (this.attr.endAngle < this.attr.startAngle) {
  35847. return;
  35848. }
  35849. this[this.attr.part + 'Renderer'](path);
  35850. },
  35851. render: function(surface, ctx, rect) {
  35852. var me = this,
  35853. renderer = me.getRenderer(),
  35854. attr = me.attr,
  35855. part = attr.part,
  35856. itemCfg, changes;
  35857. if (!attr.globalAlpha || Ext.Number.isEqual(attr.startAngle, attr.endAngle, 1.0E-8)) {
  35858. return;
  35859. }
  35860. if (renderer) {
  35861. itemCfg = {
  35862. type: 'pie3dPart',
  35863. part: attr.part,
  35864. margin: attr.margin,
  35865. distortion: attr.distortion,
  35866. centerX: attr.centerX,
  35867. centerY: attr.centerY,
  35868. baseRotation: attr.baseRotation,
  35869. startAngle: attr.startAngle,
  35870. endAngle: attr.endAngle,
  35871. startRho: attr.startRho,
  35872. endRho: attr.endRho
  35873. };
  35874. changes = Ext.callback(renderer, null, [
  35875. me,
  35876. itemCfg,
  35877. me.getRendererData(),
  35878. me.getRendererIndex()
  35879. ], 0, me.getSeries());
  35880. if (changes) {
  35881. if (changes.part) {
  35882. // Can't let users change the nature of the sprite.
  35883. changes.part = part;
  35884. }
  35885. me.setAttributes(changes);
  35886. me.useAttributes(ctx, rect);
  35887. }
  35888. }
  35889. me.callParent([
  35890. surface,
  35891. ctx
  35892. ]);
  35893. me.bevelRenderer(surface, ctx);
  35894. // Only the top part will have the label attribute (set by the series).
  35895. if (attr.label && me.getMarker('labels')) {
  35896. me.placeLabel();
  35897. }
  35898. },
  35899. placeLabel: function() {
  35900. var me = this,
  35901. attr = me.attr,
  35902. attributeId = attr.attributeId,
  35903. margin = attr.margin,
  35904. distortion = attr.distortion,
  35905. centerX = attr.centerX,
  35906. centerY = attr.centerY,
  35907. baseRotation = attr.baseRotation,
  35908. startAngle = attr.startAngle + baseRotation,
  35909. endAngle = attr.endAngle + baseRotation,
  35910. midAngle = (startAngle + endAngle) / 2,
  35911. startRho = attr.startRho + margin,
  35912. endRho = attr.endRho + margin,
  35913. midRho = (startRho + endRho) / 2,
  35914. sin = Math.sin(midAngle),
  35915. cos = Math.cos(midAngle),
  35916. surfaceMatrix = me.surfaceMatrix,
  35917. label = me.getMarker('labels'),
  35918. labelTpl = label.getTemplate(),
  35919. calloutLine = labelTpl.getCalloutLine(),
  35920. calloutLineLength = calloutLine && calloutLine.length || 40,
  35921. labelCfg = {},
  35922. rendererParams, rendererChanges, x, y;
  35923. surfaceMatrix.appendMatrix(attr.matrix);
  35924. labelCfg.text = attr.label;
  35925. x = centerX + cos * midRho;
  35926. y = centerY + sin * midRho * distortion;
  35927. labelCfg.x = surfaceMatrix.x(x, y);
  35928. labelCfg.y = surfaceMatrix.y(x, y);
  35929. x = centerX + cos * endRho;
  35930. y = centerY + sin * endRho * distortion;
  35931. labelCfg.calloutStartX = surfaceMatrix.x(x, y);
  35932. labelCfg.calloutStartY = surfaceMatrix.y(x, y);
  35933. x = centerX + cos * (endRho + calloutLineLength);
  35934. y = centerY + sin * (endRho + calloutLineLength) * distortion;
  35935. labelCfg.calloutPlaceX = surfaceMatrix.x(x, y);
  35936. labelCfg.calloutPlaceY = surfaceMatrix.y(x, y);
  35937. labelCfg.calloutWidth = 2;
  35938. if (labelTpl.attr.renderer) {
  35939. rendererParams = [
  35940. me.attr.label,
  35941. label,
  35942. labelCfg,
  35943. me.getRendererData(),
  35944. me.getRendererIndex()
  35945. ];
  35946. rendererChanges = Ext.callback(labelTpl.attr.renderer, null, rendererParams, 0, me.getSeries());
  35947. if (typeof rendererChanges === 'string') {
  35948. labelCfg.text = rendererChanges;
  35949. } else {
  35950. Ext.apply(labelCfg, rendererChanges);
  35951. }
  35952. }
  35953. me.putMarker('labels', labelCfg, attributeId);
  35954. me.putMarker('labels', {
  35955. callout: 1
  35956. }, attributeId);
  35957. },
  35958. bevelRenderer: function(surface, ctx) {
  35959. var me = this,
  35960. attr = me.attr,
  35961. bevelWidth = attr.bevelWidth,
  35962. params = me.bevelParams,
  35963. i;
  35964. for (i = 0; i < params.length; i++) {
  35965. ctx.beginPath();
  35966. ctx.ellipse.apply(ctx, params[i]);
  35967. ctx.save();
  35968. ctx.lineWidth = bevelWidth;
  35969. ctx.strokeOpacity = bevelWidth ? 1 : 0;
  35970. ctx.strokeGradient = me.bevelGradient;
  35971. ctx.stroke(attr);
  35972. ctx.restore();
  35973. }
  35974. },
  35975. lidRenderer: function(path, thickness) {
  35976. var attr = this.attr,
  35977. margin = attr.margin,
  35978. distortion = attr.distortion,
  35979. centerX = attr.centerX,
  35980. centerY = attr.centerY,
  35981. baseRotation = attr.baseRotation,
  35982. startAngle = attr.startAngle + baseRotation,
  35983. endAngle = attr.endAngle + baseRotation,
  35984. midAngle = (startAngle + endAngle) / 2,
  35985. startRho = attr.startRho,
  35986. endRho = attr.endRho,
  35987. sinEnd = Math.sin(endAngle),
  35988. cosEnd = Math.cos(endAngle);
  35989. centerX += Math.cos(midAngle) * margin;
  35990. centerY += Math.sin(midAngle) * margin * distortion;
  35991. path.ellipse(centerX, centerY + thickness, startRho, startRho * distortion, 0, startAngle, endAngle, false);
  35992. path.lineTo(centerX + cosEnd * endRho, centerY + thickness + sinEnd * endRho * distortion);
  35993. path.ellipse(centerX, centerY + thickness, endRho, endRho * distortion, 0, endAngle, startAngle, true);
  35994. path.closePath();
  35995. },
  35996. topRenderer: function(path) {
  35997. this.lidRenderer(path, 0);
  35998. },
  35999. bottomRenderer: function(path) {
  36000. var attr = this.attr,
  36001. none = Ext.util.Color.RGBA_NONE;
  36002. if (attr.globalAlpha < 1 || attr.fillOpacity < 1 || attr.shadowColor !== none) {
  36003. this.lidRenderer(path, attr.thickness);
  36004. }
  36005. },
  36006. sideRenderer: function(path, position) {
  36007. var attr = this.attr,
  36008. margin = attr.margin,
  36009. centerX = attr.centerX,
  36010. centerY = attr.centerY,
  36011. distortion = attr.distortion,
  36012. baseRotation = attr.baseRotation,
  36013. startAngle = attr.startAngle + baseRotation,
  36014. endAngle = attr.endAngle + baseRotation,
  36015. // eslint-disable-next-line max-len
  36016. isFullPie = (!attr.startAngle && Ext.Number.isEqual(Math.PI * 2, attr.endAngle, 1.0E-7)),
  36017. thickness = attr.thickness,
  36018. startRho = attr.startRho,
  36019. endRho = attr.endRho,
  36020. angle = (position === 'start' && startAngle) || (position === 'end' && endAngle),
  36021. sin = Math.sin(angle),
  36022. cos = Math.cos(angle),
  36023. isTranslucent = attr.globalAlpha < 1,
  36024. isVisible = position === 'start' && cos < 0 || position === 'end' && cos > 0 || isTranslucent,
  36025. midAngle;
  36026. if (isVisible && !isFullPie) {
  36027. midAngle = (startAngle + endAngle) / 2;
  36028. centerX += Math.cos(midAngle) * margin;
  36029. centerY += Math.sin(midAngle) * margin * distortion;
  36030. path.moveTo(centerX + cos * startRho, centerY + sin * startRho * distortion);
  36031. path.lineTo(centerX + cos * endRho, centerY + sin * endRho * distortion);
  36032. path.lineTo(centerX + cos * endRho, centerY + sin * endRho * distortion + thickness);
  36033. path.lineTo(centerX + cos * startRho, centerY + sin * startRho * distortion + thickness);
  36034. path.closePath();
  36035. }
  36036. },
  36037. startRenderer: function(path) {
  36038. this.sideRenderer(path, 'start');
  36039. },
  36040. endRenderer: function(path) {
  36041. this.sideRenderer(path, 'end');
  36042. },
  36043. rimRenderer: function(path, radius, isDonut, isFront) {
  36044. var me = this,
  36045. attr = me.attr,
  36046. margin = attr.margin,
  36047. centerX = attr.centerX,
  36048. centerY = attr.centerY,
  36049. distortion = attr.distortion,
  36050. baseRotation = attr.baseRotation,
  36051. normalize = Ext.draw.sprite.AttributeParser.angle,
  36052. startAngle = attr.startAngle + baseRotation,
  36053. endAngle = attr.endAngle + baseRotation,
  36054. // It's critical to use non-normalized start and end angles
  36055. // for middle angle calculation. Consider a situation where the
  36056. // start angle is +170 degrees and the end engle is -170 degrees
  36057. // after normalization (the middle angle is 0 then, but it should be 180 degrees).
  36058. midAngle = normalize((startAngle + endAngle) / 2),
  36059. thickness = attr.thickness,
  36060. isTranslucent = attr.globalAlpha < 1,
  36061. isAllFront, isAllBack, params;
  36062. me.bevelParams = [];
  36063. startAngle = normalize(startAngle);
  36064. endAngle = normalize(endAngle);
  36065. centerX += Math.cos(midAngle) * margin;
  36066. centerY += Math.sin(midAngle) * margin * distortion;
  36067. isAllFront = startAngle >= 0 && endAngle >= 0;
  36068. isAllBack = startAngle <= 0 && endAngle <= 0;
  36069. function renderLeftFrontChunk() {
  36070. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, Math.PI, startAngle, true);
  36071. path.lineTo(centerX + Math.cos(startAngle) * radius, centerY + Math.sin(startAngle) * radius * distortion);
  36072. params = [
  36073. centerX,
  36074. centerY,
  36075. radius,
  36076. radius * distortion,
  36077. 0,
  36078. startAngle,
  36079. Math.PI,
  36080. false
  36081. ];
  36082. if (!isDonut) {
  36083. me.bevelParams.push(params);
  36084. }
  36085. path.ellipse.apply(path, params);
  36086. path.closePath();
  36087. }
  36088. function renderRightFrontChunk() {
  36089. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, 0, endAngle, false);
  36090. path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion);
  36091. params = [
  36092. centerX,
  36093. centerY,
  36094. radius,
  36095. radius * distortion,
  36096. 0,
  36097. endAngle,
  36098. 0,
  36099. true
  36100. ];
  36101. if (!isDonut) {
  36102. me.bevelParams.push(params);
  36103. }
  36104. path.ellipse.apply(path, params);
  36105. path.closePath();
  36106. }
  36107. function renderLeftBackChunk() {
  36108. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, Math.PI, endAngle, false);
  36109. path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion);
  36110. params = [
  36111. centerX,
  36112. centerY,
  36113. radius,
  36114. radius * distortion,
  36115. 0,
  36116. endAngle,
  36117. Math.PI,
  36118. true
  36119. ];
  36120. if (isDonut) {
  36121. me.bevelParams.push(params);
  36122. }
  36123. path.ellipse.apply(path, params);
  36124. path.closePath();
  36125. }
  36126. function renderRightBackChunk() {
  36127. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, startAngle, 0, false);
  36128. path.lineTo(centerX + radius, centerY);
  36129. params = [
  36130. centerX,
  36131. centerY,
  36132. radius,
  36133. radius * distortion,
  36134. 0,
  36135. 0,
  36136. startAngle,
  36137. true
  36138. ];
  36139. if (isDonut) {
  36140. me.bevelParams.push(params);
  36141. }
  36142. path.ellipse.apply(path, params);
  36143. path.closePath();
  36144. }
  36145. if (isFront) {
  36146. if (!isDonut || isTranslucent) {
  36147. if (startAngle >= 0 && endAngle < 0) {
  36148. renderLeftFrontChunk();
  36149. } else if (startAngle <= 0 && endAngle > 0) {
  36150. renderRightFrontChunk();
  36151. } else if (startAngle <= 0 && endAngle < 0) {
  36152. if (startAngle > endAngle) {
  36153. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, 0, Math.PI, false);
  36154. path.lineTo(centerX - radius, centerY);
  36155. params = [
  36156. centerX,
  36157. centerY,
  36158. radius,
  36159. radius * distortion,
  36160. 0,
  36161. Math.PI,
  36162. 0,
  36163. true
  36164. ];
  36165. if (!isDonut) {
  36166. me.bevelParams.push(params);
  36167. }
  36168. path.ellipse.apply(path, params);
  36169. path.closePath();
  36170. }
  36171. } else {
  36172. // startAngle >= 0 && endAngle > 0
  36173. // obtuse horseshoe-like slice with the gap facing forward
  36174. if (startAngle > endAngle) {
  36175. renderLeftFrontChunk();
  36176. renderRightFrontChunk();
  36177. } else {
  36178. // acute slice facing forward
  36179. params = [
  36180. centerX,
  36181. centerY,
  36182. radius,
  36183. radius * distortion,
  36184. 0,
  36185. startAngle,
  36186. endAngle,
  36187. false
  36188. ];
  36189. if (isAllFront && !isDonut || isAllBack && isDonut) {
  36190. me.bevelParams.push(params);
  36191. }
  36192. path.ellipse.apply(path, params);
  36193. path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion + thickness);
  36194. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, endAngle, startAngle, true);
  36195. path.closePath();
  36196. }
  36197. }
  36198. }
  36199. } else {
  36200. if (isDonut || isTranslucent) {
  36201. if (startAngle >= 0 && endAngle < 0) {
  36202. renderLeftBackChunk();
  36203. } else if (startAngle <= 0 && endAngle > 0) {
  36204. renderRightBackChunk();
  36205. } else if (startAngle <= 0 && endAngle < 0) {
  36206. if (startAngle > endAngle) {
  36207. renderLeftBackChunk();
  36208. renderRightBackChunk();
  36209. } else {
  36210. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, startAngle, endAngle, false);
  36211. path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion);
  36212. params = [
  36213. centerX,
  36214. centerY,
  36215. radius,
  36216. radius * distortion,
  36217. 0,
  36218. endAngle,
  36219. startAngle,
  36220. true
  36221. ];
  36222. if (isDonut) {
  36223. me.bevelParams.push(params);
  36224. }
  36225. path.ellipse.apply(path, params);
  36226. path.closePath();
  36227. }
  36228. } else {
  36229. // startAngle >= 0 && endAngle > 0
  36230. if (startAngle > endAngle) {
  36231. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, -Math.PI, 0, false);
  36232. path.lineTo(centerX + radius, centerY);
  36233. params = [
  36234. centerX,
  36235. centerY,
  36236. radius,
  36237. radius * distortion,
  36238. 0,
  36239. 0,
  36240. -Math.PI,
  36241. true
  36242. ];
  36243. if (isDonut) {
  36244. me.bevelParams.push(params);
  36245. }
  36246. path.ellipse.apply(path, params);
  36247. path.closePath();
  36248. }
  36249. }
  36250. }
  36251. }
  36252. },
  36253. innerFrontRenderer: function(path) {
  36254. this.rimRenderer(path, this.attr.startRho, true, true);
  36255. },
  36256. innerBackRenderer: function(path) {
  36257. this.rimRenderer(path, this.attr.startRho, true, false);
  36258. },
  36259. outerFrontRenderer: function(path) {
  36260. this.rimRenderer(path, this.attr.endRho, false, true);
  36261. },
  36262. outerBackRenderer: function(path) {
  36263. this.rimRenderer(path, this.attr.endRho, false, false);
  36264. }
  36265. });
  36266. /**
  36267. * @class Ext.chart.series.Pie3D
  36268. * @extends Ext.chart.series.Polar
  36269. *
  36270. * Creates a 3D Pie Chart.
  36271. *
  36272. * **Note:** Labels, legends, and lines are not currently available when using the
  36273. * 3D Pie chart series.
  36274. *
  36275. * @example
  36276. * Ext.create({
  36277. * xtype: 'polar',
  36278. * renderTo: document.body,
  36279. * width: 600,
  36280. * height: 400,
  36281. * theme: 'green',
  36282. * interactions: 'rotate',
  36283. * store: {
  36284. * fields: ['data3'],
  36285. * data: [{
  36286. * 'data3': 14
  36287. * }, {
  36288. * 'data3': 16
  36289. * }, {
  36290. * 'data3': 14
  36291. * }, {
  36292. * 'data3': 6
  36293. * }, {
  36294. * 'data3': 36
  36295. * }]
  36296. * },
  36297. * series: {
  36298. * type: 'pie3d',
  36299. * angleField: 'data3',
  36300. * donut: 30
  36301. * }
  36302. * });
  36303. */
  36304. Ext.define('Ext.chart.series.Pie3D', {
  36305. extend: 'Ext.chart.series.Polar',
  36306. requires: [
  36307. 'Ext.chart.series.sprite.Pie3DPart',
  36308. 'Ext.draw.PathUtil'
  36309. ],
  36310. type: 'pie3d',
  36311. seriesType: 'pie3d',
  36312. alias: 'series.pie3d',
  36313. is3D: true,
  36314. config: {
  36315. rect: [
  36316. 0,
  36317. 0,
  36318. 0,
  36319. 0
  36320. ],
  36321. thickness: 35,
  36322. distortion: 0.5,
  36323. /**
  36324. * @cfg {String} angleField (required)
  36325. * The store record field name to be used for the pie angles.
  36326. * The values bound to this field name must be positive real numbers.
  36327. */
  36328. /**
  36329. * @private
  36330. * @cfg {String} radiusField
  36331. * Not supported.
  36332. */
  36333. /**
  36334. * @cfg {Number} donut Specifies the radius of the donut hole, as a percentage
  36335. * of the chart's radius.
  36336. * Defaults to 0 (no donut hole).
  36337. */
  36338. donut: 0,
  36339. /**
  36340. * @cfg {Array} hidden Determines which pie slices are hidden.
  36341. */
  36342. hidden: [],
  36343. // Populated by the coordinateX method.
  36344. /**
  36345. * @cfg {Object} highlightCfg Default {@link #highlight} config for the 3D pie series.
  36346. * Slides highlighted pie sector outward.
  36347. */
  36348. highlightCfg: {
  36349. margin: 20
  36350. },
  36351. /**
  36352. * @cfg {Number} [rotation=0] The starting angle of the pie slices.
  36353. */
  36354. /**
  36355. * @private
  36356. * @cfg {Boolean/Object} [shadow=false]
  36357. */
  36358. shadow: false
  36359. },
  36360. // Subtract 90 degrees from rotation, so that `rotation` config's default
  36361. // zero value makes first pie sector start at noon, rather than 3 o'clock.
  36362. rotationOffset: -Math.PI / 2,
  36363. setField: function(value) {
  36364. return this.setXField(value);
  36365. },
  36366. getField: function() {
  36367. return this.getXField();
  36368. },
  36369. updateRotation: function(rotation) {
  36370. var attributes = {
  36371. baseRotation: rotation + this.rotationOffset
  36372. };
  36373. this.forEachSprite(function(sprite) {
  36374. sprite.setAttributes(attributes);
  36375. });
  36376. },
  36377. updateColors: function(colors) {
  36378. var chart;
  36379. this.setSubStyle({
  36380. baseColor: colors
  36381. });
  36382. if (!this.isConfiguring) {
  36383. chart = this.getChart();
  36384. if (chart) {
  36385. chart.refreshLegendStore();
  36386. }
  36387. }
  36388. },
  36389. applyShadow: function(shadow) {
  36390. if (shadow === true) {
  36391. shadow = {
  36392. shadowColor: 'rgba(0,0,0,0.8)',
  36393. shadowBlur: 30
  36394. };
  36395. } else if (!Ext.isObject(shadow)) {
  36396. shadow = {
  36397. shadowColor: Ext.util.Color.RGBA_NONE
  36398. };
  36399. }
  36400. return shadow;
  36401. },
  36402. updateShadow: function(shadow) {
  36403. var me = this,
  36404. sprites = me.getSprites(),
  36405. spritesPerSlice = me.spritesPerSlice,
  36406. ln = sprites && sprites.length,
  36407. i, sprite;
  36408. for (i = 1; i < ln; i += spritesPerSlice) {
  36409. sprite = sprites[i];
  36410. if (sprite.attr.part === 'bottom') {
  36411. sprite.setAttributes(shadow);
  36412. }
  36413. }
  36414. },
  36415. // This is a temporary solution until the Series.getStyleByIndex is fixed
  36416. // to give user styles the priority over theme ones. Also, for sprites of
  36417. // this particular series, the fillStyle shouldn't be set directly. Instead,
  36418. // the 'baseColor' attribute should be set, from which the stops of the
  36419. // gradient (used for fillStyle) will be calculated. Themes can't handle
  36420. // situations like that properly.
  36421. getStyleByIndex: function(i) {
  36422. var indexStyle = this.callParent([
  36423. i
  36424. ]),
  36425. style = this.getStyle(),
  36426. // 'fill' and 'color' are 'fillStyle' aliases
  36427. // (see Ext.draw.sprite.Sprite.inheritableStatics.def.aliases)
  36428. fillStyle = indexStyle.fillStyle || indexStyle.fill || indexStyle.color,
  36429. strokeStyle = style.strokeStyle || style.stroke;
  36430. if (fillStyle) {
  36431. indexStyle.baseColor = fillStyle;
  36432. delete indexStyle.fillStyle;
  36433. delete indexStyle.fill;
  36434. delete indexStyle.color;
  36435. }
  36436. if (strokeStyle) {
  36437. indexStyle.strokeStyle = strokeStyle;
  36438. }
  36439. return indexStyle;
  36440. },
  36441. doUpdateStyles: function() {
  36442. var me = this,
  36443. sprites = me.getSprites(),
  36444. spritesPerSlice = me.spritesPerSlice,
  36445. ln = sprites && sprites.length,
  36446. i = 0,
  36447. j = 0,
  36448. k, style;
  36449. for (; i < ln; i += spritesPerSlice , j++) {
  36450. style = me.getStyleByIndex(j);
  36451. for (k = 0; k < spritesPerSlice; k++) {
  36452. sprites[i + k].setAttributes(style);
  36453. }
  36454. }
  36455. },
  36456. coordinateX: function() {
  36457. var me = this,
  36458. store = me.getStore(),
  36459. records = store.getData().items,
  36460. recordCount = records.length,
  36461. xField = me.getXField(),
  36462. animation = me.getAnimation(),
  36463. rotation = me.getRotation(),
  36464. hidden = me.getHidden(),
  36465. sprites = me.getSprites(true),
  36466. spriteCount = sprites.length,
  36467. spritesPerSlice = me.spritesPerSlice,
  36468. center = me.getCenter(),
  36469. offsetX = me.getOffsetX(),
  36470. offsetY = me.getOffsetY(),
  36471. radius = me.getRadius(),
  36472. thickness = me.getThickness(),
  36473. distortion = me.getDistortion(),
  36474. renderer = me.getRenderer(),
  36475. rendererData = me.getRendererData(),
  36476. highlight = me.getHighlight(),
  36477. // eslint-disable-line no-unused-vars
  36478. lastAngle = 0,
  36479. twoPi = Math.PI * 2,
  36480. // To avoid adjacent start/end part blinking (z-index jitter)
  36481. // when rotating a translucent pie chart.
  36482. delta = 1.0E-10,
  36483. endAngles = [],
  36484. sum = 0,
  36485. value, unit, sprite, style, i, j;
  36486. for (i = 0; i < recordCount; i++) {
  36487. value = Math.abs(+records[i].get(xField)) || 0;
  36488. if (!hidden[i]) {
  36489. sum += value;
  36490. }
  36491. endAngles[i] = sum;
  36492. if (i >= hidden.length) {
  36493. hidden[i] = false;
  36494. }
  36495. }
  36496. if (sum === 0) {
  36497. return;
  36498. }
  36499. // Angular value of 1 in radians.
  36500. unit = 2 * Math.PI / sum;
  36501. for (i = 0; i < recordCount; i++) {
  36502. endAngles[i] *= unit;
  36503. }
  36504. for (i = 0; i < recordCount; i++) {
  36505. style = this.getStyleByIndex(i);
  36506. for (j = 0; j < spritesPerSlice; j++) {
  36507. sprite = sprites[i * spritesPerSlice + j];
  36508. sprite.setAnimation(animation);
  36509. sprite.setAttributes({
  36510. centerX: center[0] + offsetX,
  36511. centerY: center[1] + offsetY - thickness / 2,
  36512. endRho: radius,
  36513. startRho: radius * me.getDonut() / 100,
  36514. baseRotation: rotation + me.rotationOffset,
  36515. startAngle: lastAngle,
  36516. endAngle: endAngles[i] - delta,
  36517. thickness: thickness,
  36518. distortion: distortion,
  36519. globalAlpha: 1
  36520. });
  36521. sprite.setAttributes(style);
  36522. sprite.setConfig({
  36523. renderer: renderer,
  36524. rendererData: rendererData,
  36525. rendererIndex: i
  36526. });
  36527. }
  36528. // if (highlight) {
  36529. // if (!sprite.modifiers.highlight) {
  36530. // debugger
  36531. // sprite.addModifier(highlight, true);
  36532. // }
  36533. // // sprite.modifiers.highlight.setConfig(highlight);
  36534. // }
  36535. lastAngle = endAngles[i];
  36536. }
  36537. for (i *= spritesPerSlice; i < spriteCount; i++) {
  36538. sprite = sprites[i];
  36539. sprite.setAnimation(animation);
  36540. sprite.setAttributes({
  36541. startAngle: twoPi,
  36542. endAngle: twoPi,
  36543. globalAlpha: 0,
  36544. baseRotation: rotation + me.rotationOffset
  36545. });
  36546. }
  36547. },
  36548. updateHighlight: function(highlight, oldHighlight) {
  36549. this.callParent([
  36550. highlight,
  36551. oldHighlight
  36552. ]);
  36553. this.forEachSprite(function(sprite) {
  36554. if (highlight) {
  36555. if (sprite.modifiers.highlight) {
  36556. sprite.modifiers.highlight.setConfig(highlight);
  36557. } else {
  36558. sprite.config.highlight = highlight;
  36559. sprite.addModifier(highlight, true);
  36560. }
  36561. }
  36562. });
  36563. },
  36564. updateLabelData: function() {
  36565. var me = this,
  36566. store = me.getStore(),
  36567. items = store.getData().items,
  36568. sprites = me.getSprites(),
  36569. label = me.getLabel(),
  36570. labelField = label && label.getTemplate().getField(),
  36571. hidden = me.getHidden(),
  36572. spritesPerSlice = me.spritesPerSlice,
  36573. ln, labels, sprite,
  36574. name = 'labels',
  36575. i, // sprite index
  36576. j;
  36577. // record index
  36578. if (sprites.length) {
  36579. if (labelField) {
  36580. labels = [];
  36581. for (j = 0 , ln = items.length; j < ln; j++) {
  36582. labels.push(items[j].get(labelField));
  36583. }
  36584. }
  36585. // Only set labels for the sprites that compose the top lid of the pie.
  36586. for (i = 0 , j = 0 , ln = sprites.length; i < ln; i += spritesPerSlice , j++) {
  36587. sprite = sprites[i];
  36588. if (label) {
  36589. if (!sprite.getMarker(name)) {
  36590. sprite.bindMarker(name, label);
  36591. }
  36592. if (labels) {
  36593. sprite.setAttributes({
  36594. label: labels[j]
  36595. });
  36596. }
  36597. sprite.putMarker(name, {
  36598. hidden: hidden[j]
  36599. }, sprite.attr.attributeId);
  36600. } else {
  36601. sprite.releaseMarker(name);
  36602. }
  36603. }
  36604. }
  36605. },
  36606. // The radius here will normally be set by the PolarChart.performLayout,
  36607. // where it's half the width or height (whichever is smaller) of the chart's rect.
  36608. // But for 3D pie series we have to take the thickness of the pie and the
  36609. // distortion into account to calculate the proper radius.
  36610. // The passed value is never used (or derived from) since the radius config
  36611. // is not really meant to be used directly, as it will be reset by the next layout.
  36612. applyRadius: function() {
  36613. var me = this,
  36614. chart = me.getChart(),
  36615. padding = chart.getInnerPadding(),
  36616. rect = chart.getMainRect() || [
  36617. 0,
  36618. 0,
  36619. 1,
  36620. 1
  36621. ],
  36622. width = rect[2] - padding * 2,
  36623. height = rect[3] - padding * 2 - me.getThickness(),
  36624. horizontalRadius = width / 2,
  36625. verticalRadius = horizontalRadius * me.getDistortion(),
  36626. result;
  36627. if (verticalRadius > height / 2) {
  36628. result = height / (me.getDistortion() * 2);
  36629. } else {
  36630. result = horizontalRadius;
  36631. }
  36632. return Math.max(result, 0);
  36633. },
  36634. forEachSprite: function(fn) {
  36635. var sprites = this.sprites,
  36636. ln = sprites.length,
  36637. i;
  36638. for (i = 0; i < ln; i++) {
  36639. fn(sprites[i], Math.floor(i / this.spritesPerSlice));
  36640. }
  36641. },
  36642. updateRadius: function(radius) {
  36643. var donut;
  36644. // The side effects of the 'getChart' call will result
  36645. // in the 'coordinateX' method call, which we want to have called
  36646. // first, to coordinate the data and create sprites for pie slices,
  36647. // before we set their attributes here.
  36648. // updateChart -> onChartAttached -> processData -> coordinateX
  36649. this.getChart();
  36650. donut = this.getDonut();
  36651. this.forEachSprite(function(sprite) {
  36652. sprite.setAttributes({
  36653. endRho: radius,
  36654. startRho: radius * donut / 100
  36655. });
  36656. });
  36657. },
  36658. updateDonut: function(donut) {
  36659. var radius;
  36660. // See 'updateRadius' comments.
  36661. this.getChart();
  36662. radius = this.getRadius();
  36663. this.forEachSprite(function(sprite) {
  36664. sprite.setAttributes({
  36665. startRho: radius * donut / 100
  36666. });
  36667. });
  36668. },
  36669. updateCenter: function(center) {
  36670. var offsetX, offsetY, thickness;
  36671. // See 'updateRadius' comments.
  36672. this.getChart();
  36673. offsetX = this.getOffsetX();
  36674. offsetY = this.getOffsetY();
  36675. thickness = this.getThickness();
  36676. this.forEachSprite(function(sprite) {
  36677. sprite.setAttributes({
  36678. centerX: center[0] + offsetX,
  36679. centerY: center[1] + offsetY - thickness / 2
  36680. });
  36681. });
  36682. },
  36683. updateThickness: function(thickness) {
  36684. var center, offsetY;
  36685. // See 'updateRadius' comments.
  36686. this.getChart();
  36687. // Radius depends on thickness and distortion,
  36688. // this will trigger its recalculation in the applier.
  36689. this.setRadius();
  36690. center = this.getCenter();
  36691. offsetY = this.getOffsetY();
  36692. this.forEachSprite(function(sprite) {
  36693. sprite.setAttributes({
  36694. thickness: thickness,
  36695. centerY: center[1] + offsetY - thickness / 2
  36696. });
  36697. });
  36698. },
  36699. updateDistortion: function(distortion) {
  36700. // See 'updateRadius' comments.
  36701. this.getChart();
  36702. // Radius depends on thickness and distortion,
  36703. // this will trigger its recalculation in the applier.
  36704. this.setRadius();
  36705. this.forEachSprite(function(sprite) {
  36706. sprite.setAttributes({
  36707. distortion: distortion
  36708. });
  36709. });
  36710. },
  36711. updateOffsetX: function(offsetX) {
  36712. var center;
  36713. // See 'updateRadius' comments.
  36714. this.getChart();
  36715. center = this.getCenter();
  36716. this.forEachSprite(function(sprite) {
  36717. sprite.setAttributes({
  36718. centerX: center[0] + offsetX
  36719. });
  36720. });
  36721. },
  36722. updateOffsetY: function(offsetY) {
  36723. var center, thickness;
  36724. // See 'updateRadius' comments.
  36725. this.getChart();
  36726. center = this.getCenter();
  36727. thickness = this.getThickness();
  36728. this.forEachSprite(function(sprite) {
  36729. sprite.setAttributes({
  36730. centerY: center[1] + offsetY - thickness / 2
  36731. });
  36732. });
  36733. },
  36734. updateAnimation: function(animation) {
  36735. // See 'updateRadius' comments.
  36736. this.getChart();
  36737. this.forEachSprite(function(sprite) {
  36738. sprite.setAnimation(animation);
  36739. });
  36740. },
  36741. updateRenderer: function(renderer) {
  36742. var rendererData;
  36743. // See 'updateRadius' comments.
  36744. this.getChart();
  36745. rendererData = this.getRendererData();
  36746. this.forEachSprite(function(sprite, itemIndex) {
  36747. sprite.setConfig({
  36748. renderer: renderer,
  36749. rendererData: rendererData,
  36750. rendererIndex: itemIndex
  36751. });
  36752. });
  36753. },
  36754. getRendererData: function() {
  36755. return {
  36756. store: this.getStore(),
  36757. angleField: this.getXField(),
  36758. radiusField: this.getYField(),
  36759. series: this
  36760. };
  36761. },
  36762. getSprites: function(createMissing) {
  36763. var me = this,
  36764. store = me.getStore(),
  36765. sprites = me.sprites;
  36766. if (!store) {
  36767. return Ext.emptyArray;
  36768. }
  36769. if (sprites && !createMissing) {
  36770. return sprites;
  36771. }
  36772. // eslint-disable-next-line vars-on-top, one-var
  36773. var surface = me.getSurface(),
  36774. records = store.getData().items,
  36775. spritesPerSlice = me.spritesPerSlice,
  36776. partCount = me.partNames.length,
  36777. recordCount = records.length,
  36778. sprite, i, j;
  36779. for (i = 0; i < recordCount; i++) {
  36780. if (!sprites[i * spritesPerSlice]) {
  36781. for (j = 0; j < partCount; j++) {
  36782. sprite = surface.add({
  36783. type: 'pie3dPart',
  36784. part: me.partNames[j],
  36785. series: me
  36786. });
  36787. sprite.getAnimation().setDurationOn('baseRotation', 0);
  36788. sprites.push(sprite);
  36789. }
  36790. }
  36791. }
  36792. return sprites;
  36793. },
  36794. betweenAngle: function(x, a, b) {
  36795. var pp = Math.PI * 2,
  36796. offset = this.rotationOffset;
  36797. a += offset;
  36798. b += offset;
  36799. x -= a;
  36800. b -= a;
  36801. // Normalize, so that both x and b are in the [0,360) interval.
  36802. // Since 360 * n angles will be normalized to 0,
  36803. // we need to treat b === 0 as a special case.
  36804. x %= pp;
  36805. b %= pp;
  36806. x += pp;
  36807. b += pp;
  36808. x %= pp;
  36809. b %= pp;
  36810. return x < b || b === 0;
  36811. },
  36812. getItemForPoint: function(x, y) {
  36813. var me = this,
  36814. sprites = me.getSprites(),
  36815. spritesPerSlice = me.spritesPerSlice,
  36816. result = null,
  36817. store, records, hidden, i, ln, sprite, topPartIndex;
  36818. if (!sprites) {
  36819. return result;
  36820. }
  36821. store = me.getStore();
  36822. records = store.getData().items;
  36823. hidden = me.getHidden();
  36824. for (i = 0 , ln = records.length; i < ln; i++) {
  36825. if (hidden[i]) {
  36826. continue;
  36827. }
  36828. topPartIndex = i * spritesPerSlice;
  36829. sprite = sprites[topPartIndex];
  36830. // This is CPU intensive on mousemove (no visial slowdown
  36831. // on a fast machine, but some throttling might be desirable
  36832. // on slower machines).
  36833. // On touch devices performance/battery hit is negligible.
  36834. if (sprite.hitTest([
  36835. x,
  36836. y
  36837. ])) {
  36838. result = {
  36839. series: me,
  36840. sprite: sprites.slice(topPartIndex, topPartIndex + spritesPerSlice),
  36841. index: i,
  36842. record: records[i],
  36843. category: 'sprites',
  36844. field: me.getXField()
  36845. };
  36846. break;
  36847. }
  36848. }
  36849. return result;
  36850. },
  36851. provideLegendInfo: function(target) {
  36852. var me = this,
  36853. store = me.getStore(),
  36854. items, labelField, field, hidden, style, color, i;
  36855. if (store) {
  36856. items = store.getData().items;
  36857. labelField = me.getLabel().getTemplate().getField();
  36858. field = me.getField();
  36859. hidden = me.getHidden();
  36860. for (i = 0; i < items.length; i++) {
  36861. style = me.getStyleByIndex(i);
  36862. color = style.baseColor;
  36863. target.push({
  36864. name: labelField ? String(items[i].get(labelField)) : field + ' ' + i,
  36865. mark: color || 'black',
  36866. disabled: hidden[i],
  36867. series: me.getId(),
  36868. index: i
  36869. });
  36870. }
  36871. }
  36872. }
  36873. }, function() {
  36874. var proto = this.prototype,
  36875. definition = Ext.chart.series.sprite.Pie3DPart.def.getInitialConfig().processors.part;
  36876. proto.partNames = definition.replace(/^enums\(|\)/g, '').split(',');
  36877. proto.spritesPerSlice = proto.partNames.length;
  36878. });
  36879. /**
  36880. * Polar sprite.
  36881. */
  36882. Ext.define('Ext.chart.series.sprite.Polar', {
  36883. extend: 'Ext.chart.series.sprite.Series',
  36884. inheritableStatics: {
  36885. def: {
  36886. processors: {
  36887. /**
  36888. * @cfg {Number} [centerX=0] The central point of the series on the x-axis.
  36889. */
  36890. centerX: 'number',
  36891. /**
  36892. * @cfg {Number} [centerY=0] The central point of the series on the y-axis.
  36893. */
  36894. centerY: 'number',
  36895. /**
  36896. * @cfg {Number} [startAngle=0] The starting angle of the polar series.
  36897. */
  36898. startAngle: 'number',
  36899. /**
  36900. * @cfg {Number} [endAngle=Math.PI] The ending angle of the polar series.
  36901. */
  36902. endAngle: 'number',
  36903. /**
  36904. * @cfg {Number} [startRho=0] The starting radius of the polar series.
  36905. */
  36906. startRho: 'number',
  36907. /**
  36908. * @cfg {Number} [endRho=150] The ending radius of the polar series.
  36909. */
  36910. endRho: 'number',
  36911. /**
  36912. * @cfg {Number} [baseRotation=0] The starting rotation of the polar series.
  36913. */
  36914. baseRotation: 'number'
  36915. },
  36916. defaults: {
  36917. centerX: 0,
  36918. centerY: 0,
  36919. startAngle: 0,
  36920. endAngle: Math.PI,
  36921. startRho: 0,
  36922. endRho: 150,
  36923. baseRotation: 0
  36924. },
  36925. triggers: {
  36926. centerX: 'bbox',
  36927. centerY: 'bbox',
  36928. startAngle: 'bbox',
  36929. endAngle: 'bbox',
  36930. startRho: 'bbox',
  36931. endRho: 'bbox',
  36932. baseRotation: 'bbox'
  36933. }
  36934. }
  36935. },
  36936. updatePlainBBox: function(plain) {
  36937. var attr = this.attr;
  36938. plain.x = attr.centerX - attr.endRho;
  36939. plain.y = attr.centerY + attr.endRho;
  36940. plain.width = attr.endRho * 2;
  36941. plain.height = attr.endRho * 2;
  36942. }
  36943. });
  36944. /**
  36945. * @class Ext.chart.series.sprite.Radar
  36946. * @extends Ext.chart.series.sprite.Polar
  36947. *
  36948. * Radar series sprite.
  36949. */
  36950. Ext.define('Ext.chart.series.sprite.Radar', {
  36951. alias: 'sprite.radar',
  36952. extend: 'Ext.chart.series.sprite.Polar',
  36953. getDataPointXY: function(index) {
  36954. var me = this,
  36955. attr = me.attr,
  36956. centerX = attr.centerX,
  36957. centerY = attr.centerY,
  36958. matrix = attr.matrix,
  36959. minX = attr.dataMinX,
  36960. maxX = attr.dataMaxX,
  36961. dataX = attr.dataX,
  36962. dataY = attr.dataY,
  36963. endRho = attr.endRho,
  36964. startRho = attr.startRho,
  36965. baseRotation = attr.baseRotation,
  36966. x, y, r, th, ox, oy, maxY;
  36967. if (attr.rangeY) {
  36968. maxY = attr.rangeY[1];
  36969. } else {
  36970. maxY = attr.dataMaxY;
  36971. }
  36972. th = (dataX[index] - minX) / (maxX - minX + 1) * 2 * Math.PI + baseRotation;
  36973. r = dataY[index] / maxY * (endRho - startRho) + startRho;
  36974. // Original coordinates.
  36975. ox = centerX + Math.cos(th) * r;
  36976. oy = centerY + Math.sin(th) * r;
  36977. // Transformed coordinates.
  36978. x = matrix.x(ox, oy);
  36979. y = matrix.y(ox, oy);
  36980. return [
  36981. x,
  36982. y
  36983. ];
  36984. },
  36985. render: function(surface, ctx) {
  36986. var me = this,
  36987. attr = me.attr,
  36988. dataX = attr.dataX,
  36989. length = dataX.length,
  36990. surfaceMatrix = me.surfaceMatrix,
  36991. markerCfg = {},
  36992. i, x, y, xy;
  36993. ctx.beginPath();
  36994. for (i = 0; i < length; i++) {
  36995. xy = me.getDataPointXY(i);
  36996. x = xy[0];
  36997. y = xy[1];
  36998. if (i === 0) {
  36999. ctx.moveTo(x, y);
  37000. }
  37001. ctx.lineTo(x, y);
  37002. markerCfg.translationX = surfaceMatrix.x(x, y);
  37003. markerCfg.translationY = surfaceMatrix.y(x, y);
  37004. me.putMarker('markers', markerCfg, i, true);
  37005. }
  37006. ctx.closePath();
  37007. ctx.fillStroke(attr);
  37008. }
  37009. });
  37010. /**
  37011. * @class Ext.chart.series.Radar
  37012. * @extends Ext.chart.series.Polar
  37013. *
  37014. * Creates a Radar Chart. A Radar Chart is a useful visualization technique for comparing different
  37015. * quantitative values for a constrained number of categories.
  37016. * As with all other series, the Radar series must be appended in the *series* Chart array
  37017. * configuration. See the Chart documentation for more information. A typical configuration object
  37018. * for the radar series could be:
  37019. *
  37020. * @example
  37021. * Ext.create({
  37022. * xtype: 'polar',
  37023. * renderTo: document.body,
  37024. * width: 500,
  37025. * height: 400,
  37026. * interactions: 'rotate',
  37027. * store: {
  37028. * fields: ['name', 'data1'],
  37029. * data: [{
  37030. * 'name': 'metric one',
  37031. * 'data1': 8
  37032. * }, {
  37033. * 'name': 'metric two',
  37034. * 'data1': 10
  37035. * }, {
  37036. * 'name': 'metric three',
  37037. * 'data1': 12
  37038. * }, {
  37039. * 'name': 'metric four',
  37040. * 'data1': 1
  37041. * }, {
  37042. * 'name': 'metric five',
  37043. * 'data1': 13
  37044. * }]
  37045. * },
  37046. * series: {
  37047. * type: 'radar',
  37048. * angleField: 'name',
  37049. * radiusField: 'data1',
  37050. * style: {
  37051. * fillStyle: '#388FAD',
  37052. * fillOpacity: .1,
  37053. * strokeStyle: '#388FAD',
  37054. * strokeOpacity: .8,
  37055. * lineWidth: 1
  37056. * }
  37057. * },
  37058. * axes: [{
  37059. * type: 'numeric',
  37060. * position: 'radial',
  37061. * fields: 'data1',
  37062. * style: {
  37063. * estStepSize: 10
  37064. * },
  37065. * grid: true
  37066. * }, {
  37067. * type: 'category',
  37068. * position: 'angular',
  37069. * fields: 'name',
  37070. * style: {
  37071. * estStepSize: 1
  37072. * },
  37073. * grid: true
  37074. * }]
  37075. * });
  37076. *
  37077. */
  37078. Ext.define('Ext.chart.series.Radar', {
  37079. extend: 'Ext.chart.series.Polar',
  37080. type: 'radar',
  37081. seriesType: 'radar',
  37082. alias: 'series.radar',
  37083. requires: [
  37084. 'Ext.chart.series.sprite.Radar'
  37085. ],
  37086. themeColorCount: function() {
  37087. return 1;
  37088. },
  37089. isStoreDependantColorCount: false,
  37090. themeMarkerCount: function() {
  37091. return 1;
  37092. },
  37093. updateAngularAxis: function(axis) {
  37094. axis.processData(this);
  37095. },
  37096. updateRadialAxis: function(axis) {
  37097. axis.processData(this);
  37098. },
  37099. coordinateX: function() {
  37100. return this.coordinate('X', 0, 2);
  37101. },
  37102. coordinateY: function() {
  37103. return this.coordinate('Y', 1, 2);
  37104. },
  37105. updateCenter: function(center) {
  37106. this.setStyle({
  37107. translationX: center[0] + this.getOffsetX(),
  37108. translationY: center[1] + this.getOffsetY()
  37109. });
  37110. this.doUpdateStyles();
  37111. },
  37112. updateRadius: function(radius) {
  37113. this.setStyle({
  37114. endRho: radius
  37115. });
  37116. this.doUpdateStyles();
  37117. },
  37118. updateRotation: function(rotation) {
  37119. // Overrides base class method.
  37120. var me = this,
  37121. chart = me.getChart(),
  37122. axes = chart.getAxes(),
  37123. i, ln, axis;
  37124. for (i = 0 , ln = axes.length; i < ln; i++) {
  37125. axis = axes[i];
  37126. axis.setRotation(rotation);
  37127. }
  37128. me.setStyle({
  37129. rotationRads: rotation
  37130. });
  37131. me.doUpdateStyles();
  37132. },
  37133. updateTotalAngle: function(totalAngle) {
  37134. this.processData();
  37135. },
  37136. getItemForPoint: function(x, y) {
  37137. var me = this,
  37138. sprite = me.sprites && me.sprites[0],
  37139. attr = sprite.attr,
  37140. dataX = attr.dataX,
  37141. length = dataX.length,
  37142. store = me.getStore(),
  37143. marker = me.getMarker(),
  37144. threshhold, item, xy, i, bbox, markers;
  37145. if (me.getHidden()) {
  37146. return null;
  37147. }
  37148. if (sprite && marker) {
  37149. markers = sprite.getMarker('markers');
  37150. for (i = 0; i < length; i++) {
  37151. bbox = markers.getBBoxFor(i);
  37152. threshhold = (bbox.width + bbox.height) * 0.25;
  37153. xy = sprite.getDataPointXY(i);
  37154. if (Math.abs(xy[0] - x) < threshhold && Math.abs(xy[1] - y) < threshhold) {
  37155. item = {
  37156. series: me,
  37157. sprite: sprite,
  37158. index: i,
  37159. category: 'markers',
  37160. record: store.getData().items[i],
  37161. field: me.getYField()
  37162. };
  37163. return item;
  37164. }
  37165. }
  37166. }
  37167. return me.callParent(arguments);
  37168. },
  37169. getDefaultSpriteConfig: function() {
  37170. var config = this.callParent(),
  37171. animation = {
  37172. customDurations: {
  37173. translationX: 0,
  37174. translationY: 0,
  37175. rotationRads: 0,
  37176. // Prevent animation of 'dataMinX' and 'dataMaxX' attributes in order
  37177. // to react instantaniously to changes to the 'hidden' attribute.
  37178. dataMinX: 0,
  37179. dataMaxX: 0
  37180. }
  37181. };
  37182. if (config.animation) {
  37183. Ext.apply(config.animation, animation);
  37184. } else {
  37185. config.animation = animation;
  37186. }
  37187. return config;
  37188. },
  37189. getSprites: function() {
  37190. var me = this,
  37191. chart = me.getChart(),
  37192. sprites = me.sprites;
  37193. if (!chart) {
  37194. return Ext.emptyArray;
  37195. }
  37196. if (!sprites.length) {
  37197. me.createSprite();
  37198. }
  37199. return sprites;
  37200. },
  37201. provideLegendInfo: function(target) {
  37202. var me = this,
  37203. style = me.getSubStyleWithTheme(),
  37204. fill = style.fillStyle;
  37205. if (Ext.isArray(fill)) {
  37206. fill = fill[0];
  37207. }
  37208. target.push({
  37209. name: me.getTitle() || me.getYField() || me.getId(),
  37210. mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
  37211. disabled: me.getHidden(),
  37212. series: me.getId(),
  37213. index: 0
  37214. });
  37215. }
  37216. });
  37217. /**
  37218. * @class Ext.chart.series.sprite.Scatter
  37219. * @extends Ext.chart.series.sprite.Cartesian
  37220. *
  37221. * Scatter series sprite.
  37222. */
  37223. Ext.define('Ext.chart.series.sprite.Scatter', {
  37224. alias: 'sprite.scatterSeries',
  37225. extend: 'Ext.chart.series.sprite.Cartesian',
  37226. renderClipped: function(surface, ctx, dataClipRect, surfaceClipRect) {
  37227. if (this.cleanRedraw) {
  37228. return;
  37229. }
  37230. // eslint-disable-next-line vars-on-top
  37231. var me = this,
  37232. attr = me.attr,
  37233. dataX = attr.dataX,
  37234. dataY = attr.dataY,
  37235. labels = attr.labels,
  37236. series = me.getSeries(),
  37237. isDrawLabels = labels && me.getMarker('labels'),
  37238. surfaceMatrix = me.surfaceMatrix,
  37239. matrix = me.attr.matrix,
  37240. xx = matrix.getXX(),
  37241. yy = matrix.getYY(),
  37242. dx = matrix.getDX(),
  37243. dy = matrix.getDY(),
  37244. markerCfg = {},
  37245. changes, params,
  37246. xScalingDirection = surface.getInherited().rtl && !attr.flipXY ? -1 : 1,
  37247. left, right, top, bottom, x, y, i;
  37248. if (attr.flipXY) {
  37249. left = surfaceClipRect[1] - xx * xScalingDirection;
  37250. right = surfaceClipRect[1] + surfaceClipRect[3] + xx * xScalingDirection;
  37251. top = surfaceClipRect[0] - yy;
  37252. bottom = surfaceClipRect[0] + surfaceClipRect[2] + yy;
  37253. } else {
  37254. left = surfaceClipRect[0] - xx * xScalingDirection;
  37255. right = surfaceClipRect[0] + surfaceClipRect[2] + xx * xScalingDirection;
  37256. top = surfaceClipRect[1] - yy;
  37257. bottom = surfaceClipRect[1] + surfaceClipRect[3] + yy;
  37258. }
  37259. for (i = 0; i < dataX.length; i++) {
  37260. x = dataX[i];
  37261. y = dataY[i];
  37262. x = x * xx + dx;
  37263. y = y * yy + dy;
  37264. if (left <= x && x <= right && top <= y && y <= bottom) {
  37265. if (attr.renderer) {
  37266. markerCfg = {
  37267. type: 'markers',
  37268. translationX: surfaceMatrix.x(x, y),
  37269. translationY: surfaceMatrix.y(x, y)
  37270. };
  37271. params = [
  37272. me,
  37273. markerCfg,
  37274. {
  37275. store: me.getStore()
  37276. },
  37277. i
  37278. ];
  37279. changes = Ext.callback(attr.renderer, null, params, 0, series);
  37280. markerCfg = Ext.apply(markerCfg, changes);
  37281. } else {
  37282. markerCfg.translationX = surfaceMatrix.x(x, y);
  37283. markerCfg.translationY = surfaceMatrix.y(x, y);
  37284. }
  37285. me.putMarker('markers', markerCfg, i, !attr.renderer);
  37286. if (isDrawLabels && labels[i]) {
  37287. me.drawLabel(labels[i], x, y, i, surfaceClipRect);
  37288. }
  37289. }
  37290. }
  37291. },
  37292. drawLabel: function(text, dataX, dataY, labelId, rect) {
  37293. var me = this,
  37294. attr = me.attr,
  37295. label = me.getMarker('labels'),
  37296. labelTpl = label.getTemplate(),
  37297. labelCfg = me.labelCfg || (me.labelCfg = {}),
  37298. surfaceMatrix = me.surfaceMatrix,
  37299. labelX, labelY,
  37300. labelOverflowPadding = attr.labelOverflowPadding,
  37301. flipXY = attr.flipXY,
  37302. halfHeight, labelBox, changes, params;
  37303. labelCfg.text = text;
  37304. labelBox = me.getMarkerBBox('labels', labelId, true);
  37305. if (!labelBox) {
  37306. me.putMarker('labels', labelCfg, labelId);
  37307. labelBox = me.getMarkerBBox('labels', labelId, true);
  37308. }
  37309. if (flipXY) {
  37310. labelCfg.rotationRads = Math.PI * 0.5;
  37311. } else {
  37312. labelCfg.rotationRads = 0;
  37313. }
  37314. halfHeight = labelBox.height / 2;
  37315. labelX = dataX;
  37316. switch (labelTpl.attr.display) {
  37317. case 'under':
  37318. labelY = dataY - halfHeight - labelOverflowPadding;
  37319. break;
  37320. case 'rotate':
  37321. labelX += labelOverflowPadding;
  37322. labelY = dataY - labelOverflowPadding;
  37323. labelCfg.rotationRads = -Math.PI / 4;
  37324. break;
  37325. default:
  37326. // 'over'
  37327. labelY = dataY + halfHeight + labelOverflowPadding;
  37328. }
  37329. labelCfg.x = surfaceMatrix.x(labelX, labelY);
  37330. labelCfg.y = surfaceMatrix.y(labelX, labelY);
  37331. if (labelTpl.attr.renderer) {
  37332. params = [
  37333. text,
  37334. label,
  37335. labelCfg,
  37336. {
  37337. store: me.getStore()
  37338. },
  37339. labelId
  37340. ];
  37341. changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
  37342. if (typeof changes === 'string') {
  37343. labelCfg.text = changes;
  37344. } else {
  37345. Ext.apply(labelCfg, changes);
  37346. }
  37347. }
  37348. me.putMarker('labels', labelCfg, labelId);
  37349. }
  37350. });
  37351. /**
  37352. * @class Ext.chart.series.Scatter
  37353. * @extends Ext.chart.series.Cartesian
  37354. *
  37355. * Creates a Scatter Chart. The scatter plot is useful when trying to display more than
  37356. * two variables in the same visualization. These variables can be mapped into x, y coordinates
  37357. * and also to an element's radius/size, color, etc. As with all other series, the Scatter Series
  37358. * must be appended in the *series* Chart array configuration. See the Chart documentation for more
  37359. * information on creating charts. A typical configuration object for the scatter could be:
  37360. *
  37361. * @example
  37362. * Ext.create({
  37363. * xtype: 'cartesian',
  37364. * renderTo: document.body,
  37365. * width: 600,
  37366. * height: 400,
  37367. * insetPadding: 40,
  37368. * interactions: ['itemhighlight'],
  37369. * store: {
  37370. * fields: ['name', 'data1', 'data2'],
  37371. * data: [{
  37372. * 'name': 'metric one',
  37373. * 'data1': 10,
  37374. * 'data2': 14
  37375. * }, {
  37376. * 'name': 'metric two',
  37377. * 'data1': 7,
  37378. * 'data2': 16
  37379. * }, {
  37380. * 'name': 'metric three',
  37381. * 'data1': 5,
  37382. * 'data2': 14
  37383. * }, {
  37384. * 'name': 'metric four',
  37385. * 'data1': 2,
  37386. * 'data2': 6
  37387. * }, {
  37388. * 'name': 'metric five',
  37389. * 'data1': 27,
  37390. * 'data2': 36
  37391. * }]
  37392. * },
  37393. * axes: [{
  37394. * type: 'numeric',
  37395. * position: 'left',
  37396. * fields: ['data1'],
  37397. * title: {
  37398. * text: 'Sample Values',
  37399. * fontSize: 15
  37400. * },
  37401. * grid: true,
  37402. * minimum: 0
  37403. * }, {
  37404. * type: 'category',
  37405. * position: 'bottom',
  37406. * fields: ['name'],
  37407. * title: {
  37408. * text: 'Sample Values',
  37409. * fontSize: 15
  37410. * }
  37411. * }],
  37412. * series: {
  37413. * type: 'scatter',
  37414. * highlight: {
  37415. * size: 12,
  37416. * radius: 12,
  37417. * fill: '#96D4C6',
  37418. * stroke: '#30BDA7'
  37419. * },
  37420. * fill: true,
  37421. * xField: 'name',
  37422. * yField: 'data2',
  37423. * marker: {
  37424. * type: 'circle',
  37425. * fill: '#30BDA7',
  37426. * radius: 10,
  37427. * lineWidth: 0
  37428. * }
  37429. * }
  37430. * });
  37431. *
  37432. * In this configuration we add three different categories of scatter series. Each of them is bound
  37433. * to a different field of the same data store, `data1`, `data2` and `data3` respectively.
  37434. * All x-fields for the series must be the same field, in this case `name`. Each scatter series
  37435. * has a different styling configuration for markers, specified by the `marker` object. Finally
  37436. * we set the left axis as axis to show the current values of the elements.
  37437. *
  37438. */
  37439. Ext.define('Ext.chart.series.Scatter', {
  37440. extend: 'Ext.chart.series.Cartesian',
  37441. alias: 'series.scatter',
  37442. type: 'scatter',
  37443. seriesType: 'scatterSeries',
  37444. requires: [
  37445. 'Ext.chart.series.sprite.Scatter'
  37446. ],
  37447. config: {
  37448. itemInstancing: null,
  37449. marker: true
  37450. },
  37451. themeMarkerCount: function() {
  37452. return 1;
  37453. },
  37454. provideLegendInfo: function(target) {
  37455. var me = this,
  37456. style = me.getMarkerStyleByIndex(0),
  37457. fill = style.fillStyle;
  37458. target.push({
  37459. name: me.getTitle() || me.getYField() || me.getId(),
  37460. mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
  37461. disabled: me.getHidden(),
  37462. series: me.getId(),
  37463. index: 0
  37464. });
  37465. }
  37466. });
  37467. Ext.define('Ext.chart.theme.Blue', {
  37468. extend: 'Ext.chart.theme.Base',
  37469. singleton: true,
  37470. alias: [
  37471. 'chart.theme.blue',
  37472. 'chart.theme.Blue'
  37473. ],
  37474. config: {
  37475. baseColor: '#4d7fe6'
  37476. }
  37477. });
  37478. Ext.define('Ext.chart.theme.BlueGradients', {
  37479. extend: 'Ext.chart.theme.Base',
  37480. singleton: true,
  37481. alias: [
  37482. 'chart.theme.blue-gradients',
  37483. 'chart.theme.Blue:gradients'
  37484. ],
  37485. config: {
  37486. baseColor: '#4d7fe6',
  37487. gradients: {
  37488. type: 'linear',
  37489. degrees: 90
  37490. }
  37491. }
  37492. });
  37493. Ext.define('Ext.chart.theme.Category1', {
  37494. extend: 'Ext.chart.theme.Base',
  37495. singleton: true,
  37496. alias: [
  37497. 'chart.theme.category1',
  37498. 'chart.theme.Category1'
  37499. ],
  37500. config: {
  37501. colors: [
  37502. '#f0a50a',
  37503. '#c20024',
  37504. '#2044ba',
  37505. '#810065',
  37506. '#7eae29'
  37507. ]
  37508. }
  37509. });
  37510. Ext.define('Ext.chart.theme.Category1Gradients', {
  37511. extend: 'Ext.chart.theme.Base',
  37512. singleton: true,
  37513. alias: [
  37514. 'chart.theme.category1-gradients',
  37515. 'chart.theme.Category1:gradients'
  37516. ],
  37517. config: {
  37518. colors: [
  37519. '#f0a50a',
  37520. '#c20024',
  37521. '#2044ba',
  37522. '#810065',
  37523. '#7eae29'
  37524. ],
  37525. gradients: {
  37526. type: 'linear',
  37527. degrees: 90
  37528. }
  37529. }
  37530. });
  37531. Ext.define('Ext.chart.theme.Category2', {
  37532. extend: 'Ext.chart.theme.Base',
  37533. singleton: true,
  37534. alias: [
  37535. 'chart.theme.category2',
  37536. 'chart.theme.Category2'
  37537. ],
  37538. config: {
  37539. colors: [
  37540. '#6d9824',
  37541. '#87146e',
  37542. '#2a9196',
  37543. '#d39006',
  37544. '#1e40ac'
  37545. ]
  37546. }
  37547. });
  37548. Ext.define('Ext.chart.theme.Category2Gradients', {
  37549. extend: 'Ext.chart.theme.Base',
  37550. singleton: true,
  37551. alias: [
  37552. 'chart.theme.category2-gradients',
  37553. 'chart.theme.Category2:gradients'
  37554. ],
  37555. config: {
  37556. colors: [
  37557. '#6d9824',
  37558. '#87146e',
  37559. '#2a9196',
  37560. '#d39006',
  37561. '#1e40ac'
  37562. ],
  37563. gradients: {
  37564. type: 'linear',
  37565. degrees: 90
  37566. }
  37567. }
  37568. });
  37569. Ext.define('Ext.chart.theme.Category3', {
  37570. extend: 'Ext.chart.theme.Base',
  37571. singleton: true,
  37572. alias: [
  37573. 'chart.theme.category3',
  37574. 'chart.theme.Category3'
  37575. ],
  37576. config: {
  37577. colors: [
  37578. '#fbbc29',
  37579. '#ce2e4e',
  37580. '#7e0062',
  37581. '#158b90',
  37582. '#57880e'
  37583. ]
  37584. }
  37585. });
  37586. Ext.define('Ext.chart.theme.Category3Gradients', {
  37587. extend: 'Ext.chart.theme.Base',
  37588. singleton: true,
  37589. alias: [
  37590. 'chart.theme.category3-gradients',
  37591. 'chart.theme.Category3:gradients'
  37592. ],
  37593. config: {
  37594. colors: [
  37595. '#fbbc29',
  37596. '#ce2e4e',
  37597. '#7e0062',
  37598. '#158b90',
  37599. '#57880e'
  37600. ],
  37601. gradients: {
  37602. type: 'linear',
  37603. degrees: 90
  37604. }
  37605. }
  37606. });
  37607. Ext.define('Ext.chart.theme.Category4', {
  37608. extend: 'Ext.chart.theme.Base',
  37609. singleton: true,
  37610. alias: [
  37611. 'chart.theme.category4',
  37612. 'chart.theme.Category4'
  37613. ],
  37614. config: {
  37615. colors: [
  37616. '#ef5773',
  37617. '#fcbd2a',
  37618. '#4f770d',
  37619. '#1d3eaa',
  37620. '#9b001f'
  37621. ]
  37622. }
  37623. });
  37624. Ext.define('Ext.chart.theme.Category4Gradients', {
  37625. extend: 'Ext.chart.theme.Base',
  37626. singleton: true,
  37627. alias: [
  37628. 'chart.theme.category4-gradients',
  37629. 'chart.theme.Category4:gradients'
  37630. ],
  37631. config: {
  37632. colors: [
  37633. '#ef5773',
  37634. '#fcbd2a',
  37635. '#4f770d',
  37636. '#1d3eaa',
  37637. '#9b001f'
  37638. ],
  37639. gradients: {
  37640. type: 'linear',
  37641. degrees: 90
  37642. }
  37643. }
  37644. });
  37645. Ext.define('Ext.chart.theme.Category5', {
  37646. extend: 'Ext.chart.theme.Base',
  37647. singleton: true,
  37648. alias: [
  37649. 'chart.theme.category5',
  37650. 'chart.theme.Category5'
  37651. ],
  37652. config: {
  37653. colors: [
  37654. '#7eae29',
  37655. '#fdbe2a',
  37656. '#910019',
  37657. '#27b4bc',
  37658. '#d74dbc'
  37659. ]
  37660. }
  37661. });
  37662. Ext.define('Ext.chart.theme.Category5Gradients', {
  37663. extend: 'Ext.chart.theme.Base',
  37664. singleton: true,
  37665. alias: [
  37666. 'chart.theme.category5-gradients',
  37667. 'chart.theme.Category5:gradients'
  37668. ],
  37669. config: {
  37670. colors: [
  37671. '#7eae29',
  37672. '#fdbe2a',
  37673. '#910019',
  37674. '#27b4bc',
  37675. '#d74dbc'
  37676. ],
  37677. gradients: {
  37678. type: 'linear',
  37679. degrees: 90
  37680. }
  37681. }
  37682. });
  37683. Ext.define('Ext.chart.theme.Category6', {
  37684. extend: 'Ext.chart.theme.Base',
  37685. singleton: true,
  37686. alias: [
  37687. 'chart.theme.category6',
  37688. 'chart.theme.Category6'
  37689. ],
  37690. config: {
  37691. colors: [
  37692. '#44dce1',
  37693. '#0b2592',
  37694. '#996e05',
  37695. '#7fb325',
  37696. '#b821a1'
  37697. ]
  37698. }
  37699. });
  37700. Ext.define('Ext.chart.theme.Category6Gradients', {
  37701. extend: 'Ext.chart.theme.Base',
  37702. singleton: true,
  37703. alias: [
  37704. 'chart.theme.category6-gradients',
  37705. 'chart.theme.Category6:gradients'
  37706. ],
  37707. config: {
  37708. colors: [
  37709. '#44dce1',
  37710. '#0b2592',
  37711. '#996e05',
  37712. '#7fb325',
  37713. '#b821a1'
  37714. ],
  37715. gradients: {
  37716. type: 'linear',
  37717. degrees: 90
  37718. }
  37719. }
  37720. });
  37721. Ext.define('Ext.chart.theme.DefaultGradients', {
  37722. extend: 'Ext.chart.theme.Base',
  37723. singleton: true,
  37724. alias: [
  37725. 'chart.theme.default-gradients',
  37726. 'chart.theme.Base:gradients'
  37727. ],
  37728. config: {
  37729. gradients: {
  37730. type: 'linear',
  37731. degrees: 90
  37732. }
  37733. }
  37734. });
  37735. Ext.define('Ext.chart.theme.Green', {
  37736. extend: 'Ext.chart.theme.Base',
  37737. singleton: true,
  37738. alias: [
  37739. 'chart.theme.green',
  37740. 'chart.theme.Green'
  37741. ],
  37742. config: {
  37743. baseColor: '#b1da5a'
  37744. }
  37745. });
  37746. Ext.define('Ext.chart.theme.GreenGradients', {
  37747. extend: 'Ext.chart.theme.Base',
  37748. singleton: true,
  37749. alias: [
  37750. 'chart.theme.green-gradients',
  37751. 'chart.theme.Green:gradients'
  37752. ],
  37753. config: {
  37754. baseColor: '#b1da5a',
  37755. gradients: {
  37756. type: 'linear',
  37757. degrees: 90
  37758. }
  37759. }
  37760. });
  37761. Ext.define('Ext.chart.theme.Midnight', {
  37762. extend: 'Ext.chart.theme.Base',
  37763. singleton: true,
  37764. alias: [
  37765. 'chart.theme.midnight',
  37766. 'chart.theme.Midnight'
  37767. ],
  37768. config: {
  37769. colors: [
  37770. '#a837ff',
  37771. '#4ac0f2',
  37772. '#ff4d35',
  37773. '#ff8809',
  37774. '#61c102',
  37775. '#ff37ea'
  37776. ],
  37777. chart: {
  37778. defaults: {
  37779. captions: {
  37780. title: {
  37781. docked: 'top',
  37782. padding: 5,
  37783. style: {
  37784. textAlign: 'center',
  37785. fontFamily: 'default',
  37786. fontWeight: 'bold',
  37787. fillStyle: 'rgb(224, 224, 227)',
  37788. fontSize: 'default*1.6'
  37789. }
  37790. },
  37791. subtitle: {
  37792. docked: 'top',
  37793. style: {
  37794. textAlign: 'center',
  37795. fontFamily: 'default',
  37796. fontWeight: 'normal',
  37797. fillStyle: 'rgb(224, 224, 227)',
  37798. fontSize: 'default*1.3'
  37799. }
  37800. },
  37801. credits: {
  37802. docked: 'bottom',
  37803. padding: 5,
  37804. style: {
  37805. textAlign: 'left',
  37806. fontFamily: 'default',
  37807. fontWeight: 'lighter',
  37808. fillStyle: 'rgb(224, 224, 227)',
  37809. fontSize: 'default'
  37810. }
  37811. }
  37812. },
  37813. background: 'rgb(52, 52, 53)'
  37814. }
  37815. },
  37816. axis: {
  37817. defaults: {
  37818. style: {
  37819. strokeStyle: 'rgb(224, 224, 227)'
  37820. },
  37821. label: {
  37822. fillStyle: 'rgb(224, 224, 227)'
  37823. },
  37824. title: {
  37825. fillStyle: 'rgb(224, 224, 227)'
  37826. },
  37827. grid: {
  37828. strokeStyle: 'rgb(112, 112, 115)'
  37829. }
  37830. }
  37831. },
  37832. series: {
  37833. defaults: {
  37834. label: {
  37835. fillStyle: 'rgb(224, 224, 227)'
  37836. }
  37837. }
  37838. },
  37839. sprites: {
  37840. text: {
  37841. fillStyle: 'rgb(224, 224, 227)'
  37842. }
  37843. },
  37844. legend: {
  37845. label: {
  37846. fillStyle: 'white'
  37847. },
  37848. border: {
  37849. lineWidth: 2,
  37850. fillStyle: 'rgba(255, 255, 255, 0.3)',
  37851. strokeStyle: 'rgb(150, 150, 150)'
  37852. },
  37853. background: 'rgb(52, 52, 53)'
  37854. }
  37855. }
  37856. });
  37857. Ext.define('Ext.chart.theme.Muted', {
  37858. extend: 'Ext.chart.theme.Base',
  37859. singleton: true,
  37860. alias: [
  37861. 'chart.theme.muted',
  37862. 'chart.theme.Muted'
  37863. ],
  37864. config: {
  37865. colors: [
  37866. '#8ca640',
  37867. '#974144',
  37868. '#4091ba',
  37869. '#8e658e',
  37870. '#3b8d8b',
  37871. '#b86465',
  37872. '#d2af69',
  37873. '#6e8852',
  37874. '#3dcc7e',
  37875. '#a6bed1',
  37876. '#cbaa4b',
  37877. '#998baa'
  37878. ]
  37879. }
  37880. });
  37881. Ext.define('Ext.chart.theme.Purple', {
  37882. extend: 'Ext.chart.theme.Base',
  37883. singleton: true,
  37884. alias: [
  37885. 'chart.theme.purple',
  37886. 'chart.theme.Purple'
  37887. ],
  37888. config: {
  37889. baseColor: '#da5abd'
  37890. }
  37891. });
  37892. Ext.define('Ext.chart.theme.PurpleGradients', {
  37893. extend: 'Ext.chart.theme.Base',
  37894. singleton: true,
  37895. alias: [
  37896. 'chart.theme.purple-gradients',
  37897. 'chart.theme.Purple:gradients'
  37898. ],
  37899. config: {
  37900. baseColor: '#da5abd',
  37901. gradients: {
  37902. type: 'linear',
  37903. degrees: 90
  37904. }
  37905. }
  37906. });
  37907. Ext.define('Ext.chart.theme.Red', {
  37908. extend: 'Ext.chart.theme.Base',
  37909. singleton: true,
  37910. alias: [
  37911. 'chart.theme.red',
  37912. 'chart.theme.Red'
  37913. ],
  37914. config: {
  37915. baseColor: '#e84b67'
  37916. }
  37917. });
  37918. Ext.define('Ext.chart.theme.RedGradients', {
  37919. extend: 'Ext.chart.theme.Base',
  37920. singleton: true,
  37921. alias: [
  37922. 'chart.theme.red-gradients',
  37923. 'chart.theme.Red:gradients'
  37924. ],
  37925. config: {
  37926. baseColor: '#e84b67',
  37927. gradients: {
  37928. type: 'linear',
  37929. degrees: 90
  37930. }
  37931. }
  37932. });
  37933. Ext.define('Ext.chart.theme.Sky', {
  37934. extend: 'Ext.chart.theme.Base',
  37935. singleton: true,
  37936. alias: [
  37937. 'chart.theme.sky',
  37938. 'chart.theme.Sky'
  37939. ],
  37940. config: {
  37941. baseColor: '#4ce0e7'
  37942. }
  37943. });
  37944. Ext.define('Ext.chart.theme.SkyGradients', {
  37945. extend: 'Ext.chart.theme.Base',
  37946. singleton: true,
  37947. alias: [
  37948. 'chart.theme.sky-gradients',
  37949. 'chart.theme.Sky:gradients'
  37950. ],
  37951. config: {
  37952. baseColor: '#4ce0e7',
  37953. gradients: {
  37954. type: 'linear',
  37955. degrees: 90
  37956. }
  37957. }
  37958. });
  37959. Ext.define('Ext.chart.theme.Yellow', {
  37960. extend: 'Ext.chart.theme.Base',
  37961. singleton: true,
  37962. alias: [
  37963. 'chart.theme.yellow',
  37964. 'chart.theme.Yellow'
  37965. ],
  37966. config: {
  37967. baseColor: '#fec935'
  37968. }
  37969. });
  37970. Ext.define('Ext.chart.theme.YellowGradients', {
  37971. extend: 'Ext.chart.theme.Base',
  37972. singleton: true,
  37973. alias: [
  37974. 'chart.theme.yellow-gradients',
  37975. 'chart.theme.Yellow:gradients'
  37976. ],
  37977. config: {
  37978. baseColor: '#fec935',
  37979. gradients: {
  37980. type: 'linear',
  37981. degrees: 90
  37982. }
  37983. }
  37984. });
  37985. /**
  37986. * A helper class to facilitate common operations on points and vectors.
  37987. */
  37988. Ext.define('Ext.draw.Point', {
  37989. requires: [
  37990. 'Ext.draw.Draw',
  37991. 'Ext.draw.Matrix'
  37992. ],
  37993. isPoint: true,
  37994. x: 0,
  37995. y: 0,
  37996. length: 0,
  37997. angle: 0,
  37998. angleUnits: 'degrees',
  37999. statics: {
  38000. /**
  38001. * @method
  38002. * @static
  38003. * Creates a flyweight Ext.draw.Point instance.
  38004. * Takes the same parameters as the {@link Ext.draw.Point#method!constructor}.
  38005. * Do not hold the instance of the flyweight point.
  38006. *
  38007. * @param {Number/Number[]/Object/Ext.draw.Point} point
  38008. * @return {Ext.draw.Point}
  38009. */
  38010. fly: (function() {
  38011. var point = null;
  38012. return function(x, y) {
  38013. if (!point) {
  38014. point = new Ext.draw.Point();
  38015. }
  38016. point.constructor(x, y);
  38017. return point;
  38018. };
  38019. })()
  38020. },
  38021. /**
  38022. * Creates a point.
  38023. *
  38024. * new Ext.draw.Point(3, 4);
  38025. * new Ext.draw.Point(3); // both x and y equal 3
  38026. * new Ext.draw.Point([3, 4]);
  38027. * new Ext.draw.Point({x: 3, y: 4});
  38028. * new Ext.draw.Point(p); // where `p` is a Ext.draw.Point instance.
  38029. *
  38030. * @param {Number/Number[]/Object/Ext.draw.Point} x
  38031. * @param {Number/Number[]/Object/Ext.draw.Point} y
  38032. */
  38033. constructor: function(x, y) {
  38034. var me = this;
  38035. if (typeof x === 'number') {
  38036. me.x = x;
  38037. if (typeof y === 'number') {
  38038. me.y = y;
  38039. } else {
  38040. me.y = x;
  38041. }
  38042. } else if (Ext.isArray(x)) {
  38043. me.x = x[0];
  38044. me.y = x[1];
  38045. } else if (x) {
  38046. me.x = x.x;
  38047. me.y = x.y;
  38048. }
  38049. me.calculatePolar();
  38050. },
  38051. calculateCartesian: function() {
  38052. var me = this,
  38053. length = me.length,
  38054. angle = me.angle;
  38055. if (me.angleUnits === 'degrees') {
  38056. angle = Ext.draw.Draw.rad(angle);
  38057. }
  38058. me.x = Math.cos(angle) * length;
  38059. me.y = Math.sin(angle) * length;
  38060. },
  38061. calculatePolar: function() {
  38062. var me = this,
  38063. x = me.x,
  38064. y = me.y;
  38065. me.length = Math.sqrt(x * x + y * y);
  38066. me.angle = Math.atan2(y, x);
  38067. if (me.angleUnits === 'degrees') {
  38068. me.angle = Ext.draw.Draw.degrees(me.angle);
  38069. }
  38070. },
  38071. /**
  38072. * Sets the x-coordinate of the point.
  38073. * @param {Number} x
  38074. */
  38075. setX: function(x) {
  38076. this.x = x;
  38077. this.calculatePolar();
  38078. },
  38079. /**
  38080. * Sets the y-coordinate of the point.
  38081. * @param {Number} y
  38082. */
  38083. setY: function(y) {
  38084. this.y = y;
  38085. this.calculatePolar();
  38086. },
  38087. /**
  38088. * Sets coordinates of the point.
  38089. * Takes the same parameters as the {@link #method!constructor}.
  38090. * @param {Number/Number[]/Object/Ext.draw.Point} x
  38091. * @param {Number/Number[]/Object/Ext.draw.Point} y
  38092. */
  38093. set: function(x, y) {
  38094. this.constructor(x, y);
  38095. },
  38096. /**
  38097. * Sets the angle of the vector (measured from the x-axis to the vector)
  38098. * without changing its length.
  38099. * @param {Number} angle
  38100. */
  38101. setAngle: function(angle) {
  38102. this.angle = angle;
  38103. this.calculateCartesian();
  38104. },
  38105. /**
  38106. * Sets the length of the vector without changing its angle.
  38107. * @param {Number} length
  38108. */
  38109. setLength: function(length) {
  38110. this.length = length;
  38111. this.calculateCartesian();
  38112. },
  38113. /**
  38114. * Sets both the angle and the length of the vector.
  38115. * A point can be thought of as a vector pointing from the origin to the point's location.
  38116. * This can also be interpreted as setting coordinates of a point in the polar
  38117. * coordinate system.
  38118. * @param {Number} angle
  38119. * @param {Number} length
  38120. */
  38121. setPolar: function(angle, length) {
  38122. this.angle = angle;
  38123. this.length = length;
  38124. this.calculateCartesian();
  38125. },
  38126. /**
  38127. * Returns a copy of the point.
  38128. * @return {Ext.draw.Point}
  38129. */
  38130. clone: function() {
  38131. return new Ext.draw.Point(this.x, this.y);
  38132. },
  38133. /**
  38134. * Adds another vector to this one and returns the resulting vector
  38135. * without changing this vector.
  38136. * @param {Number/Number[]/Object/Ext.draw.Point} x
  38137. * @param {Number/Number[]/Object/Ext.draw.Point} y
  38138. * @return {Ext.draw.Point}
  38139. */
  38140. add: function(x, y) {
  38141. var fly = Ext.draw.Point.fly(x, y);
  38142. return new Ext.draw.Point(this.x + fly.x, this.y + fly.y);
  38143. },
  38144. /**
  38145. * Subtracts another vector from this one and returns the resulting vector
  38146. * without changing this vector.
  38147. * @param {Number/Number[]/Object/Ext.draw.Point} x
  38148. * @param {Number/Number[]/Object/Ext.draw.Point} y
  38149. * @return {Ext.draw.Point}
  38150. */
  38151. sub: function(x, y) {
  38152. var fly = Ext.draw.Point.fly(x, y);
  38153. return new Ext.draw.Point(this.x - fly.x, this.y - fly.y);
  38154. },
  38155. /**
  38156. * Returns the result of scalar multiplication of this vector by the given factor.
  38157. * This vector is not modified.
  38158. * @param {Number} n The factor.
  38159. * @return {Ext.draw.Point}
  38160. */
  38161. mul: function(n) {
  38162. return new Ext.draw.Point(this.x * n, this.y * n);
  38163. },
  38164. /**
  38165. * Returns a vector which coordinates are the result of division of this vector's
  38166. * coordinates by the given number. This vector is not modified.
  38167. * This vector is not modified.
  38168. * @param {Number} n The denominator.
  38169. * @return {Ext.draw.Point}
  38170. */
  38171. div: function(n) {
  38172. return new Ext.draw.Point(this.x / n, this.y / n);
  38173. },
  38174. /**
  38175. * Returns the dot product of this vector and the given vector.
  38176. * @param {Number/Number[]/Object/Ext.draw.Point} x
  38177. * @param {Number/Number[]/Object/Ext.draw.Point} y
  38178. * @return {Number}
  38179. */
  38180. dot: function(x, y) {
  38181. var fly = Ext.draw.Point.fly(x, y);
  38182. return this.x * fly.x + this.y * fly.y;
  38183. },
  38184. /**
  38185. * Checks whether coordinates of the point match those of the point provided.
  38186. * @param {Number/Number[]/Object/Ext.draw.Point} x
  38187. * @param {Number/Number[]/Object/Ext.draw.Point} y
  38188. * @return {Boolean}
  38189. */
  38190. equals: function(x, y) {
  38191. var fly = Ext.draw.Point.fly(x, y);
  38192. return this.x === fly.x && this.y === fly.y;
  38193. },
  38194. /**
  38195. * Rotates the point by the given angle. This point is not modified.
  38196. * @param {Number} angle The rotation angle.
  38197. * @param {Ext.draw.Point} [center] The center of rotation (optional). Defaults to origin.
  38198. * @return {Ext.draw.Point} The rotated point.
  38199. */
  38200. rotate: function(angle, center) {
  38201. var sin, cos, cx, cy, point;
  38202. if (this.angleUnits === 'degrees') {
  38203. angle = Ext.draw.Draw.rad(angle);
  38204. sin = Math.sin(angle);
  38205. cos = Math.cos(angle);
  38206. }
  38207. if (center) {
  38208. cx = center.x;
  38209. cy = center.y;
  38210. } else {
  38211. cx = 0;
  38212. cy = 0;
  38213. }
  38214. point = Ext.draw.Matrix.fly([
  38215. cos,
  38216. sin,
  38217. -sin,
  38218. cos,
  38219. cx - cos * cx + cy * sin,
  38220. cy - cos * cy + cx * -sin
  38221. ]).transformPoint(this);
  38222. return new Ext.draw.Point(point);
  38223. },
  38224. /**
  38225. * Transforms the point from one coordinate system to another
  38226. * using the transformation matrix provided. This point is not modified.
  38227. * @param {Ext.draw.Matrix/Number[]} matrix A trasformation matrix or its elements.
  38228. * @return {Ext.draw.Point}
  38229. */
  38230. transform: function(matrix) {
  38231. if (matrix && matrix.isMatrix) {
  38232. return new Ext.draw.Point(matrix.transformPoint(this));
  38233. } else if (arguments.length === 6) {
  38234. return new Ext.draw.Point(Ext.draw.Matrix.fly(arguments).transformPoint(this));
  38235. } else {
  38236. Ext.raise("Invalid parameters.");
  38237. }
  38238. },
  38239. /**
  38240. * Returns a new point with rounded x and y values. This point is not modified.
  38241. * @return {Ext.draw.Point}
  38242. */
  38243. round: function() {
  38244. return new Ext.draw.Point(Math.round(this.x), Math.round(this.y));
  38245. },
  38246. /**
  38247. * Returns a new point with ceiled x and y values. This point is not modified.
  38248. * @return {Ext.draw.Point}
  38249. */
  38250. ceil: function() {
  38251. return new Ext.draw.Point(Math.ceil(this.x), Math.ceil(this.y));
  38252. },
  38253. /**
  38254. * Returns a new point with floored x and y values. This point is not modified.
  38255. * @return {Ext.draw.Point}
  38256. */
  38257. floor: function() {
  38258. return new Ext.draw.Point(Math.floor(this.x), Math.floor(this.y));
  38259. },
  38260. /**
  38261. * Returns a new point with absolute values of the x and y values of this point.
  38262. * This point is not modified.
  38263. * @return {Ext.draw.Point}
  38264. */
  38265. abs: function(x, y) {
  38266. return new Ext.draw.Point(Math.abs(this.x), Math.abs(this.y));
  38267. },
  38268. /**
  38269. * Normalizes the vector by changing its length to 1 without changing its angle.
  38270. * The returned result is a normalized vector. This vector is not modified.
  38271. * @param {Number} [factor=1] Multiplication factor. Defaults to 1.
  38272. * @return {Ext.draw.Point}
  38273. */
  38274. normalize: function(factor) {
  38275. var x = this.x,
  38276. y = this.y,
  38277. k = (factor || 1) / Math.sqrt(x * x + y * y);
  38278. return new Ext.draw.Point(x * k, y * k);
  38279. },
  38280. /**
  38281. * Returns the vector from the point perpendicular to the line (shortest distance).
  38282. * Where line is specified using two points or the coordinates of those points.
  38283. * @param {Ext.draw.Point} p1
  38284. * @param {Ext.draw.Point} p2
  38285. * @return {Ext.draw.Point}
  38286. */
  38287. getDistanceToLine: function(p1, p2) {
  38288. var n, pp1;
  38289. if (arguments.length === 4) {
  38290. p1 = new Ext.draw.Point(arguments[0], arguments[1]);
  38291. p2 = new Ext.draw.Point(arguments[2], arguments[3]);
  38292. }
  38293. // See http://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Vector_formulation
  38294. n = p2.sub(p1).normalize();
  38295. pp1 = p1.sub(this);
  38296. return pp1.sub(n.mul(pp1.dot(n)));
  38297. },
  38298. /**
  38299. * Checks if both x and y coordinates of the point are zero.
  38300. * @return {Boolean}
  38301. */
  38302. isZero: function() {
  38303. return this.x === 0 && this.y === 0;
  38304. },
  38305. /**
  38306. * Checks if both x and y coordinates of the point are valid numbers.
  38307. * @return {Boolean}
  38308. */
  38309. isNumber: function() {
  38310. return Ext.isNumber(this.x) && Ext.isNumber(this.y);
  38311. }
  38312. });
  38313. /**
  38314. * A draw container {@link Ext.AbstractPlugin plugin} that adds ability to listen
  38315. * to sprite events. For example:
  38316. *
  38317. * var drawContainer = Ext.create('Ext.draw.Container', {
  38318. * plugins: {
  38319. * spriteevents: true
  38320. * },
  38321. * renderTo: Ext.getBody(),
  38322. * width: 200,
  38323. * height: 200,
  38324. * sprites: [{
  38325. * type: 'circle',
  38326. * fillStyle: '#79BB3F',
  38327. * r: 50,
  38328. * x: 100,
  38329. * y: 100
  38330. * }],
  38331. * listeners: {
  38332. * spriteclick: function (item, event) {
  38333. * var sprite = item && item.sprite;
  38334. * if (sprite) {
  38335. * sprite.setAttributes({fillStyle: 'red'});
  38336. sprite.getSurface().renderFrame();
  38337. * }
  38338. * }
  38339. * }
  38340. * });
  38341. */
  38342. Ext.define('Ext.draw.plugin.SpriteEvents', {
  38343. extend: 'Ext.plugin.Abstract',
  38344. alias: 'plugin.spriteevents',
  38345. requires: [
  38346. 'Ext.draw.overrides.hittest.All'
  38347. ],
  38348. /**
  38349. * @event spritemousemove
  38350. * Fires when the mouse is moved on a sprite.
  38351. * @param {Object} sprite
  38352. * @param {Event} event
  38353. */
  38354. /**
  38355. * @event spritemouseup
  38356. * Fires when a mouseup event occurs on a sprite.
  38357. * @param {Object} sprite
  38358. * @param {Event} event
  38359. */
  38360. /**
  38361. * @event spritemousedown
  38362. * Fires when a mousedown event occurs on a sprite.
  38363. * @param {Object} sprite
  38364. * @param {Event} event
  38365. */
  38366. /**
  38367. * @event spritemouseover
  38368. * Fires when the mouse enters a sprite.
  38369. * @param {Object} sprite
  38370. * @param {Event} event
  38371. */
  38372. /**
  38373. * @event spritemouseout
  38374. * Fires when the mouse exits a sprite.
  38375. * @param {Object} sprite
  38376. * @param {Event} event
  38377. */
  38378. /**
  38379. * @event spriteclick
  38380. * Fires when a click event occurs on a sprite.
  38381. * @param {Object} sprite
  38382. * @param {Event} event
  38383. */
  38384. /**
  38385. * @event spritedblclick
  38386. * Fires when a double click event occurs on a sprite.
  38387. * @param {Object} sprite
  38388. * @param {Event} event
  38389. */
  38390. /**
  38391. * @event spritetap
  38392. * Fires when a tap event occurs on a sprite.
  38393. * @param {Object} sprite
  38394. * @param {Event} event
  38395. */
  38396. mouseMoveEvents: {
  38397. mousemove: true,
  38398. mouseover: true,
  38399. mouseout: true
  38400. },
  38401. spriteMouseMoveEvents: {
  38402. spritemousemove: true,
  38403. spritemouseover: true,
  38404. spritemouseout: true
  38405. },
  38406. init: function(drawContainer) {
  38407. var handleEvent = 'handleEvent';
  38408. this.drawContainer = drawContainer;
  38409. drawContainer.addElementListener({
  38410. click: handleEvent,
  38411. dblclick: handleEvent,
  38412. mousedown: handleEvent,
  38413. mousemove: handleEvent,
  38414. mouseup: handleEvent,
  38415. mouseover: handleEvent,
  38416. mouseout: handleEvent,
  38417. // run our handlers before user code
  38418. priority: 1001,
  38419. scope: this
  38420. });
  38421. },
  38422. hasSpriteMouseMoveListeners: function() {
  38423. var listeners = this.drawContainer.hasListeners,
  38424. name;
  38425. for (name in this.spriteMouseMoveEvents) {
  38426. if (name in listeners) {
  38427. return true;
  38428. }
  38429. }
  38430. return false;
  38431. },
  38432. hitTestEvent: function(e) {
  38433. var items = this.drawContainer.getItems(),
  38434. surface, sprite, i;
  38435. for (i = items.length - 1; i >= 0; i--) {
  38436. surface = items.get(i);
  38437. sprite = surface.hitTestEvent(e);
  38438. if (sprite) {
  38439. return sprite;
  38440. }
  38441. }
  38442. return null;
  38443. },
  38444. handleEvent: function(e) {
  38445. var me = this,
  38446. drawContainer = me.drawContainer,
  38447. isMouseMoveEvent = e.type in me.mouseMoveEvents,
  38448. lastSprite = me.lastSprite,
  38449. sprite;
  38450. if (isMouseMoveEvent && !me.hasSpriteMouseMoveListeners()) {
  38451. return;
  38452. }
  38453. sprite = me.hitTestEvent(e);
  38454. if (isMouseMoveEvent && !Ext.Object.equals(sprite, lastSprite)) {
  38455. if (lastSprite) {
  38456. drawContainer.fireEvent('spritemouseout', lastSprite, e);
  38457. }
  38458. if (sprite) {
  38459. drawContainer.fireEvent('spritemouseover', sprite, e);
  38460. }
  38461. }
  38462. if (sprite) {
  38463. drawContainer.fireEvent('sprite' + e.type, sprite, e);
  38464. }
  38465. me.lastSprite = sprite;
  38466. }
  38467. });
  38468. /**
  38469. * The ItemInfo interaction allows displaying detailed information about a series data
  38470. * point in a popup panel.
  38471. *
  38472. * To attach this interaction to a chart, include an entry in the chart's
  38473. * {@link Ext.chart.AbstractChart#interactions interactions} config with the `iteminfo` type:
  38474. *
  38475. * new Ext.chart.AbstractChart({
  38476. * renderTo: Ext.getBody(),
  38477. * width: 800,
  38478. * height: 600,
  38479. * store: store1,
  38480. * axes: [ ...some axes options... ],
  38481. * series: [ ...some series options... ],
  38482. * interactions: [{
  38483. * type: 'iteminfo',
  38484. * listeners: {
  38485. * show: function(me, item, panel) {
  38486. * panel.setHtml('Stock Price: $' + item.record.get('price'));
  38487. * }
  38488. * }
  38489. * }]
  38490. * });
  38491. */
  38492. Ext.define('Ext.chart.interactions.ItemInfo', {
  38493. extend: 'Ext.chart.interactions.Abstract',
  38494. type: 'iteminfo',
  38495. alias: 'interaction.iteminfo',
  38496. /**
  38497. * @event show
  38498. * Fires when the info panel is shown.
  38499. * @param {Ext.chart.interactions.ItemInfo} this The interaction instance
  38500. * @param {Object} item The item whose info is being displayed
  38501. * @param {Ext.Panel} panel The panel for displaying the info
  38502. */
  38503. config: {
  38504. /**
  38505. * @cfg {Object} extjsGestures
  38506. * Defines the gestures that should trigger the item info panel to be displayed in ExtJS.
  38507. */
  38508. extjsGestures: {
  38509. 'start': {
  38510. event: 'click',
  38511. handler: 'onInfoGesture'
  38512. },
  38513. 'move': {
  38514. event: 'mousemove',
  38515. handler: 'onInfoGesture'
  38516. },
  38517. 'end': {
  38518. event: 'mouseleave',
  38519. handler: 'onInfoGesture'
  38520. }
  38521. }
  38522. },
  38523. // TODO:ps The trigger above should be 'itemclick', not 'click'.
  38524. item: null,
  38525. onInfoGesture: function(e, element) {
  38526. var me = this,
  38527. item = me.getItemForEvent(e),
  38528. tooltip = item && item.series.tooltip;
  38529. if (tooltip) {
  38530. tooltip.onMouseMove.call(tooltip, e);
  38531. }
  38532. if (item !== me.item) {
  38533. if (item) {
  38534. item.series.showTip(item);
  38535. } else {
  38536. me.item.series.hideTip(me.item);
  38537. }
  38538. me.item = item;
  38539. }
  38540. return false;
  38541. }
  38542. });