charts-debug.js 1.3 MB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737207382073920740207412074220743207442074520746207472074820749207502075120752207532075420755207562075720758207592076020761207622076320764207652076620767207682076920770207712077220773207742077520776207772077820779207802078120782207832078420785207862078720788207892079020791207922079320794207952079620797207982079920800208012080220803208042080520806208072080820809208102081120812208132081420815208162081720818208192082020821208222082320824208252082620827208282082920830208312083220833208342083520836208372083820839208402084120842208432084420845208462084720848208492085020851208522085320854208552085620857208582085920860208612086220863208642086520866208672086820869208702087120872208732087420875208762087720878208792088020881208822088320884208852088620887208882088920890208912089220893208942089520896208972089820899209002090120902209032090420905209062090720908209092091020911209122091320914209152091620917209182091920920209212092220923209242092520926209272092820929209302093120932209332093420935209362093720938209392094020941209422094320944209452094620947209482094920950209512095220953209542095520956209572095820959209602096120962209632096420965209662096720968209692097020971209722097320974209752097620977209782097920980209812098220983209842098520986209872098820989209902099120992209932099420995209962099720998209992100021001210022100321004210052100621007210082100921010210112101221013210142101521016210172101821019210202102121022210232102421025210262102721028210292103021031210322103321034210352103621037210382103921040210412104221043210442104521046210472104821049210502105121052210532105421055210562105721058210592106021061210622106321064210652106621067210682106921070210712107221073210742107521076210772107821079210802108121082210832108421085210862108721088210892109021091210922109321094210952109621097210982109921100211012110221103211042110521106211072110821109211102111121112211132111421115211162111721118211192112021121211222112321124211252112621127211282112921130211312113221133211342113521136211372113821139211402114121142211432114421145211462114721148211492115021151211522115321154211552115621157211582115921160211612116221163211642116521166211672116821169211702117121172211732117421175211762117721178211792118021181211822118321184211852118621187211882118921190211912119221193211942119521196211972119821199212002120121202212032120421205212062120721208212092121021211212122121321214212152121621217212182121921220212212122221223212242122521226212272122821229212302123121232212332123421235212362123721238212392124021241212422124321244212452124621247212482124921250212512125221253212542125521256212572125821259212602126121262212632126421265212662126721268212692127021271212722127321274212752127621277212782127921280212812128221283212842128521286212872128821289212902129121292212932129421295212962129721298212992130021301213022130321304213052130621307213082130921310213112131221313213142131521316213172131821319213202132121322213232132421325213262132721328213292133021331213322133321334213352133621337213382133921340213412134221343213442134521346213472134821349213502135121352213532135421355213562135721358213592136021361213622136321364213652136621367213682136921370213712137221373213742137521376213772137821379213802138121382213832138421385213862138721388213892139021391213922139321394213952139621397213982139921400214012140221403214042140521406214072140821409214102141121412214132141421415214162141721418214192142021421214222142321424214252142621427214282142921430214312143221433214342143521436214372143821439214402144121442214432144421445214462144721448214492145021451214522145321454214552145621457214582145921460214612146221463214642146521466214672146821469214702147121472214732147421475214762147721478214792148021481214822148321484214852148621487214882148921490214912149221493214942149521496214972149821499215002150121502215032150421505215062150721508215092151021511215122151321514215152151621517215182151921520215212152221523215242152521526215272152821529215302153121532215332153421535215362153721538215392154021541215422154321544215452154621547215482154921550215512155221553215542155521556215572155821559215602156121562215632156421565215662156721568215692157021571215722157321574215752157621577215782157921580215812158221583215842158521586215872158821589215902159121592215932159421595215962159721598215992160021601216022160321604216052160621607216082160921610216112161221613216142161521616216172161821619216202162121622216232162421625216262162721628216292163021631216322163321634216352163621637216382163921640216412164221643216442164521646216472164821649216502165121652216532165421655216562165721658216592166021661216622166321664216652166621667216682166921670216712167221673216742167521676216772167821679216802168121682216832168421685216862168721688216892169021691216922169321694216952169621697216982169921700217012170221703217042170521706217072170821709217102171121712217132171421715217162171721718217192172021721217222172321724217252172621727217282172921730217312173221733217342173521736217372173821739217402174121742217432174421745217462174721748217492175021751217522175321754217552175621757217582175921760217612176221763217642176521766217672176821769217702177121772217732177421775217762177721778217792178021781217822178321784217852178621787217882178921790217912179221793217942179521796217972179821799218002180121802218032180421805218062180721808218092181021811218122181321814218152181621817218182181921820218212182221823218242182521826218272182821829218302183121832218332183421835218362183721838218392184021841218422184321844218452184621847218482184921850218512185221853218542185521856218572185821859218602186121862218632186421865218662186721868218692187021871218722187321874218752187621877218782187921880218812188221883218842188521886218872188821889218902189121892218932189421895218962189721898218992190021901219022190321904219052190621907219082190921910219112191221913219142191521916219172191821919219202192121922219232192421925219262192721928219292193021931219322193321934219352193621937219382193921940219412194221943219442194521946219472194821949219502195121952219532195421955219562195721958219592196021961219622196321964219652196621967219682196921970219712197221973219742197521976219772197821979219802198121982219832198421985219862198721988219892199021991219922199321994219952199621997219982199922000220012200222003220042200522006220072200822009220102201122012220132201422015220162201722018220192202022021220222202322024220252202622027220282202922030220312203222033220342203522036220372203822039220402204122042220432204422045220462204722048220492205022051220522205322054220552205622057220582205922060220612206222063220642206522066220672206822069220702207122072220732207422075220762207722078220792208022081220822208322084220852208622087220882208922090220912209222093220942209522096220972209822099221002210122102221032210422105221062210722108221092211022111221122211322114221152211622117221182211922120221212212222123221242212522126221272212822129221302213122132221332213422135221362213722138221392214022141221422214322144221452214622147221482214922150221512215222153221542215522156221572215822159221602216122162221632216422165221662216722168221692217022171221722217322174221752217622177221782217922180221812218222183221842218522186221872218822189221902219122192221932219422195221962219722198221992220022201222022220322204222052220622207222082220922210222112221222213222142221522216222172221822219222202222122222222232222422225222262222722228222292223022231222322223322234222352223622237222382223922240222412224222243222442224522246222472224822249222502225122252222532225422255222562225722258222592226022261222622226322264222652226622267222682226922270222712227222273222742227522276222772227822279222802228122282222832228422285222862228722288222892229022291222922229322294222952229622297222982229922300223012230222303223042230522306223072230822309223102231122312223132231422315223162231722318223192232022321223222232322324223252232622327223282232922330223312233222333223342233522336223372233822339223402234122342223432234422345223462234722348223492235022351223522235322354223552235622357223582235922360223612236222363223642236522366223672236822369223702237122372223732237422375223762237722378223792238022381223822238322384223852238622387223882238922390223912239222393223942239522396223972239822399224002240122402224032240422405224062240722408224092241022411224122241322414224152241622417224182241922420224212242222423224242242522426224272242822429224302243122432224332243422435224362243722438224392244022441224422244322444224452244622447224482244922450224512245222453224542245522456224572245822459224602246122462224632246422465224662246722468224692247022471224722247322474224752247622477224782247922480224812248222483224842248522486224872248822489224902249122492224932249422495224962249722498224992250022501225022250322504225052250622507225082250922510225112251222513225142251522516225172251822519225202252122522225232252422525225262252722528225292253022531225322253322534225352253622537225382253922540225412254222543225442254522546225472254822549225502255122552225532255422555225562255722558225592256022561225622256322564225652256622567225682256922570225712257222573225742257522576225772257822579225802258122582225832258422585225862258722588225892259022591225922259322594225952259622597225982259922600226012260222603226042260522606226072260822609226102261122612226132261422615226162261722618226192262022621226222262322624226252262622627226282262922630226312263222633226342263522636226372263822639226402264122642226432264422645226462264722648226492265022651226522265322654226552265622657226582265922660226612266222663226642266522666226672266822669226702267122672226732267422675226762267722678226792268022681226822268322684226852268622687226882268922690226912269222693226942269522696226972269822699227002270122702227032270422705227062270722708227092271022711227122271322714227152271622717227182271922720227212272222723227242272522726227272272822729227302273122732227332273422735227362273722738227392274022741227422274322744227452274622747227482274922750227512275222753227542275522756227572275822759227602276122762227632276422765227662276722768227692277022771227722277322774227752277622777227782277922780227812278222783227842278522786227872278822789227902279122792227932279422795227962279722798227992280022801228022280322804228052280622807228082280922810228112281222813228142281522816228172281822819228202282122822228232282422825228262282722828228292283022831228322283322834228352283622837228382283922840228412284222843228442284522846228472284822849228502285122852228532285422855228562285722858228592286022861228622286322864228652286622867228682286922870228712287222873228742287522876228772287822879228802288122882228832288422885228862288722888228892289022891228922289322894228952289622897228982289922900229012290222903229042290522906229072290822909229102291122912229132291422915229162291722918229192292022921229222292322924229252292622927229282292922930229312293222933229342293522936229372293822939229402294122942229432294422945229462294722948229492295022951229522295322954229552295622957229582295922960229612296222963229642296522966229672296822969229702297122972229732297422975229762297722978229792298022981229822298322984229852298622987229882298922990229912299222993229942299522996229972299822999230002300123002230032300423005230062300723008230092301023011230122301323014230152301623017230182301923020230212302223023230242302523026230272302823029230302303123032230332303423035230362303723038230392304023041230422304323044230452304623047230482304923050230512305223053230542305523056230572305823059230602306123062230632306423065230662306723068230692307023071230722307323074230752307623077230782307923080230812308223083230842308523086230872308823089230902309123092230932309423095230962309723098230992310023101231022310323104231052310623107231082310923110231112311223113231142311523116231172311823119231202312123122231232312423125231262312723128231292313023131231322313323134231352313623137231382313923140231412314223143231442314523146231472314823149231502315123152231532315423155231562315723158231592316023161231622316323164231652316623167231682316923170231712317223173231742317523176231772317823179231802318123182231832318423185231862318723188231892319023191231922319323194231952319623197231982319923200232012320223203232042320523206232072320823209232102321123212232132321423215232162321723218232192322023221232222322323224232252322623227232282322923230232312323223233232342323523236232372323823239232402324123242232432324423245232462324723248232492325023251232522325323254232552325623257232582325923260232612326223263232642326523266232672326823269232702327123272232732327423275232762327723278232792328023281232822328323284232852328623287232882328923290232912329223293232942329523296232972329823299233002330123302233032330423305233062330723308233092331023311233122331323314233152331623317233182331923320233212332223323233242332523326233272332823329233302333123332233332333423335233362333723338233392334023341233422334323344233452334623347233482334923350233512335223353233542335523356233572335823359233602336123362233632336423365233662336723368233692337023371233722337323374233752337623377233782337923380233812338223383233842338523386233872338823389233902339123392233932339423395233962339723398233992340023401234022340323404234052340623407234082340923410234112341223413234142341523416234172341823419234202342123422234232342423425234262342723428234292343023431234322343323434234352343623437234382343923440234412344223443234442344523446234472344823449234502345123452234532345423455234562345723458234592346023461234622346323464234652346623467234682346923470234712347223473234742347523476234772347823479234802348123482234832348423485234862348723488234892349023491234922349323494234952349623497234982349923500235012350223503235042350523506235072350823509235102351123512235132351423515235162351723518235192352023521235222352323524235252352623527235282352923530235312353223533235342353523536235372353823539235402354123542235432354423545235462354723548235492355023551235522355323554235552355623557235582355923560235612356223563235642356523566235672356823569235702357123572235732357423575235762357723578235792358023581235822358323584235852358623587235882358923590235912359223593235942359523596235972359823599236002360123602236032360423605236062360723608236092361023611236122361323614236152361623617236182361923620236212362223623236242362523626236272362823629236302363123632236332363423635236362363723638236392364023641236422364323644236452364623647236482364923650236512365223653236542365523656236572365823659236602366123662236632366423665236662366723668236692367023671236722367323674236752367623677236782367923680236812368223683236842368523686236872368823689236902369123692236932369423695236962369723698236992370023701237022370323704237052370623707237082370923710237112371223713237142371523716237172371823719237202372123722237232372423725237262372723728237292373023731237322373323734237352373623737237382373923740237412374223743237442374523746237472374823749237502375123752237532375423755237562375723758237592376023761237622376323764237652376623767237682376923770237712377223773237742377523776237772377823779237802378123782237832378423785237862378723788237892379023791237922379323794237952379623797237982379923800238012380223803238042380523806238072380823809238102381123812238132381423815238162381723818238192382023821238222382323824238252382623827238282382923830238312383223833238342383523836238372383823839238402384123842238432384423845238462384723848238492385023851238522385323854238552385623857238582385923860238612386223863238642386523866238672386823869238702387123872238732387423875238762387723878238792388023881238822388323884238852388623887238882388923890238912389223893238942389523896238972389823899239002390123902239032390423905239062390723908239092391023911239122391323914239152391623917239182391923920239212392223923239242392523926239272392823929239302393123932239332393423935239362393723938239392394023941239422394323944239452394623947239482394923950239512395223953239542395523956239572395823959239602396123962239632396423965239662396723968239692397023971239722397323974239752397623977239782397923980239812398223983239842398523986239872398823989239902399123992239932399423995239962399723998239992400024001240022400324004240052400624007240082400924010240112401224013240142401524016240172401824019240202402124022240232402424025240262402724028240292403024031240322403324034240352403624037240382403924040240412404224043240442404524046240472404824049240502405124052240532405424055240562405724058240592406024061240622406324064240652406624067240682406924070240712407224073240742407524076240772407824079240802408124082240832408424085240862408724088240892409024091240922409324094240952409624097240982409924100241012410224103241042410524106241072410824109241102411124112241132411424115241162411724118241192412024121241222412324124241252412624127241282412924130241312413224133241342413524136241372413824139241402414124142241432414424145241462414724148241492415024151241522415324154241552415624157241582415924160241612416224163241642416524166241672416824169241702417124172241732417424175241762417724178241792418024181241822418324184241852418624187241882418924190241912419224193241942419524196241972419824199242002420124202242032420424205242062420724208242092421024211242122421324214242152421624217242182421924220242212422224223242242422524226242272422824229242302423124232242332423424235242362423724238242392424024241242422424324244242452424624247242482424924250242512425224253242542425524256242572425824259242602426124262242632426424265242662426724268242692427024271242722427324274242752427624277242782427924280242812428224283242842428524286242872428824289242902429124292242932429424295242962429724298242992430024301243022430324304243052430624307243082430924310243112431224313243142431524316243172431824319243202432124322243232432424325243262432724328243292433024331243322433324334243352433624337243382433924340243412434224343243442434524346243472434824349243502435124352243532435424355243562435724358243592436024361243622436324364243652436624367243682436924370243712437224373243742437524376243772437824379243802438124382243832438424385243862438724388243892439024391243922439324394243952439624397243982439924400244012440224403244042440524406244072440824409244102441124412244132441424415244162441724418244192442024421244222442324424244252442624427244282442924430244312443224433244342443524436244372443824439244402444124442244432444424445244462444724448244492445024451244522445324454244552445624457244582445924460244612446224463244642446524466244672446824469244702447124472244732447424475244762447724478244792448024481244822448324484244852448624487244882448924490244912449224493244942449524496244972449824499245002450124502245032450424505245062450724508245092451024511245122451324514245152451624517245182451924520245212452224523245242452524526245272452824529245302453124532245332453424535245362453724538245392454024541245422454324544245452454624547245482454924550245512455224553245542455524556245572455824559245602456124562245632456424565245662456724568245692457024571245722457324574245752457624577245782457924580245812458224583245842458524586245872458824589245902459124592245932459424595245962459724598245992460024601246022460324604246052460624607246082460924610246112461224613246142461524616246172461824619246202462124622246232462424625246262462724628246292463024631246322463324634246352463624637246382463924640246412464224643246442464524646246472464824649246502465124652246532465424655246562465724658246592466024661246622466324664246652466624667246682466924670246712467224673246742467524676246772467824679246802468124682246832468424685246862468724688246892469024691246922469324694246952469624697246982469924700247012470224703247042470524706247072470824709247102471124712247132471424715247162471724718247192472024721247222472324724247252472624727247282472924730247312473224733247342473524736247372473824739247402474124742247432474424745247462474724748247492475024751247522475324754247552475624757247582475924760247612476224763247642476524766247672476824769247702477124772247732477424775247762477724778247792478024781247822478324784247852478624787247882478924790247912479224793247942479524796247972479824799248002480124802248032480424805248062480724808248092481024811248122481324814248152481624817248182481924820248212482224823248242482524826248272482824829248302483124832248332483424835248362483724838248392484024841248422484324844248452484624847248482484924850248512485224853248542485524856248572485824859248602486124862248632486424865248662486724868248692487024871248722487324874248752487624877248782487924880248812488224883248842488524886248872488824889248902489124892248932489424895248962489724898248992490024901249022490324904249052490624907249082490924910249112491224913249142491524916249172491824919249202492124922249232492424925249262492724928249292493024931249322493324934249352493624937249382493924940249412494224943249442494524946249472494824949249502495124952249532495424955249562495724958249592496024961249622496324964249652496624967249682496924970249712497224973249742497524976249772497824979249802498124982249832498424985249862498724988249892499024991249922499324994249952499624997249982499925000250012500225003250042500525006250072500825009250102501125012250132501425015250162501725018250192502025021250222502325024250252502625027250282502925030250312503225033250342503525036250372503825039250402504125042250432504425045250462504725048250492505025051250522505325054250552505625057250582505925060250612506225063250642506525066250672506825069250702507125072250732507425075250762507725078250792508025081250822508325084250852508625087250882508925090250912509225093250942509525096250972509825099251002510125102251032510425105251062510725108251092511025111251122511325114251152511625117251182511925120251212512225123251242512525126251272512825129251302513125132251332513425135251362513725138251392514025141251422514325144251452514625147251482514925150251512515225153251542515525156251572515825159251602516125162251632516425165251662516725168251692517025171251722517325174251752517625177251782517925180251812518225183251842518525186251872518825189251902519125192251932519425195251962519725198251992520025201252022520325204252052520625207252082520925210252112521225213252142521525216252172521825219252202522125222252232522425225252262522725228252292523025231252322523325234252352523625237252382523925240252412524225243252442524525246252472524825249252502525125252252532525425255252562525725258252592526025261252622526325264252652526625267252682526925270252712527225273252742527525276252772527825279252802528125282252832528425285252862528725288252892529025291252922529325294252952529625297252982529925300253012530225303253042530525306253072530825309253102531125312253132531425315253162531725318253192532025321253222532325324253252532625327253282532925330253312533225333253342533525336253372533825339253402534125342253432534425345253462534725348253492535025351253522535325354253552535625357253582535925360253612536225363253642536525366253672536825369253702537125372253732537425375253762537725378253792538025381253822538325384253852538625387253882538925390253912539225393253942539525396253972539825399254002540125402254032540425405254062540725408254092541025411254122541325414254152541625417254182541925420254212542225423254242542525426254272542825429254302543125432254332543425435254362543725438254392544025441254422544325444254452544625447254482544925450254512545225453254542545525456254572545825459254602546125462254632546425465254662546725468254692547025471254722547325474254752547625477254782547925480254812548225483254842548525486254872548825489254902549125492254932549425495254962549725498254992550025501255022550325504255052550625507255082550925510255112551225513255142551525516255172551825519255202552125522255232552425525255262552725528255292553025531255322553325534255352553625537255382553925540255412554225543255442554525546255472554825549255502555125552255532555425555255562555725558255592556025561255622556325564255652556625567255682556925570255712557225573255742557525576255772557825579255802558125582255832558425585255862558725588255892559025591255922559325594255952559625597255982559925600256012560225603256042560525606256072560825609256102561125612256132561425615256162561725618256192562025621256222562325624256252562625627256282562925630256312563225633256342563525636256372563825639256402564125642256432564425645256462564725648256492565025651256522565325654256552565625657256582565925660256612566225663256642566525666256672566825669256702567125672256732567425675256762567725678256792568025681256822568325684256852568625687256882568925690256912569225693256942569525696256972569825699257002570125702257032570425705257062570725708257092571025711257122571325714257152571625717257182571925720257212572225723257242572525726257272572825729257302573125732257332573425735257362573725738257392574025741257422574325744257452574625747257482574925750257512575225753257542575525756257572575825759257602576125762257632576425765257662576725768257692577025771257722577325774257752577625777257782577925780257812578225783257842578525786257872578825789257902579125792257932579425795257962579725798257992580025801258022580325804258052580625807258082580925810258112581225813258142581525816258172581825819258202582125822258232582425825258262582725828258292583025831258322583325834258352583625837258382583925840258412584225843258442584525846258472584825849258502585125852258532585425855258562585725858258592586025861258622586325864258652586625867258682586925870258712587225873258742587525876258772587825879258802588125882258832588425885258862588725888258892589025891258922589325894258952589625897258982589925900259012590225903259042590525906259072590825909259102591125912259132591425915259162591725918259192592025921259222592325924259252592625927259282592925930259312593225933259342593525936259372593825939259402594125942259432594425945259462594725948259492595025951259522595325954259552595625957259582595925960259612596225963259642596525966259672596825969259702597125972259732597425975259762597725978259792598025981259822598325984259852598625987259882598925990259912599225993259942599525996259972599825999260002600126002260032600426005260062600726008260092601026011260122601326014260152601626017260182601926020260212602226023260242602526026260272602826029260302603126032260332603426035260362603726038260392604026041260422604326044260452604626047260482604926050260512605226053260542605526056260572605826059260602606126062260632606426065260662606726068260692607026071260722607326074260752607626077260782607926080260812608226083260842608526086260872608826089260902609126092260932609426095260962609726098260992610026101261022610326104261052610626107261082610926110261112611226113261142611526116261172611826119261202612126122261232612426125261262612726128261292613026131261322613326134261352613626137261382613926140261412614226143261442614526146261472614826149261502615126152261532615426155261562615726158261592616026161261622616326164261652616626167261682616926170261712617226173261742617526176261772617826179261802618126182261832618426185261862618726188261892619026191261922619326194261952619626197261982619926200262012620226203262042620526206262072620826209262102621126212262132621426215262162621726218262192622026221262222622326224262252622626227262282622926230262312623226233262342623526236262372623826239262402624126242262432624426245262462624726248262492625026251262522625326254262552625626257262582625926260262612626226263262642626526266262672626826269262702627126272262732627426275262762627726278262792628026281262822628326284262852628626287262882628926290262912629226293262942629526296262972629826299263002630126302263032630426305263062630726308263092631026311263122631326314263152631626317263182631926320263212632226323263242632526326263272632826329263302633126332263332633426335263362633726338263392634026341263422634326344263452634626347263482634926350263512635226353263542635526356263572635826359263602636126362263632636426365263662636726368263692637026371263722637326374263752637626377263782637926380263812638226383263842638526386263872638826389263902639126392263932639426395263962639726398263992640026401264022640326404264052640626407264082640926410264112641226413264142641526416264172641826419264202642126422264232642426425264262642726428264292643026431264322643326434264352643626437264382643926440264412644226443264442644526446264472644826449264502645126452264532645426455264562645726458264592646026461264622646326464264652646626467264682646926470264712647226473264742647526476264772647826479264802648126482264832648426485264862648726488264892649026491264922649326494264952649626497264982649926500265012650226503265042650526506265072650826509265102651126512265132651426515265162651726518265192652026521265222652326524265252652626527265282652926530265312653226533265342653526536265372653826539265402654126542265432654426545265462654726548265492655026551265522655326554265552655626557265582655926560265612656226563265642656526566265672656826569265702657126572265732657426575265762657726578265792658026581265822658326584265852658626587265882658926590265912659226593265942659526596265972659826599266002660126602266032660426605266062660726608266092661026611266122661326614266152661626617266182661926620266212662226623266242662526626266272662826629266302663126632266332663426635266362663726638266392664026641266422664326644266452664626647266482664926650266512665226653266542665526656266572665826659266602666126662266632666426665266662666726668266692667026671266722667326674266752667626677266782667926680266812668226683266842668526686266872668826689266902669126692266932669426695266962669726698266992670026701267022670326704267052670626707267082670926710267112671226713267142671526716267172671826719267202672126722267232672426725267262672726728267292673026731267322673326734267352673626737267382673926740267412674226743267442674526746267472674826749267502675126752267532675426755267562675726758267592676026761267622676326764267652676626767267682676926770267712677226773267742677526776267772677826779267802678126782267832678426785267862678726788267892679026791267922679326794267952679626797267982679926800268012680226803268042680526806268072680826809268102681126812268132681426815268162681726818268192682026821268222682326824268252682626827268282682926830268312683226833268342683526836268372683826839268402684126842268432684426845268462684726848268492685026851268522685326854268552685626857268582685926860268612686226863268642686526866268672686826869268702687126872268732687426875268762687726878268792688026881268822688326884268852688626887268882688926890268912689226893268942689526896268972689826899269002690126902269032690426905269062690726908269092691026911269122691326914269152691626917269182691926920269212692226923269242692526926269272692826929269302693126932269332693426935269362693726938269392694026941269422694326944269452694626947269482694926950269512695226953269542695526956269572695826959269602696126962269632696426965269662696726968269692697026971269722697326974269752697626977269782697926980269812698226983269842698526986269872698826989269902699126992269932699426995269962699726998269992700027001270022700327004270052700627007270082700927010270112701227013270142701527016270172701827019270202702127022270232702427025270262702727028270292703027031270322703327034270352703627037270382703927040270412704227043270442704527046270472704827049270502705127052270532705427055270562705727058270592706027061270622706327064270652706627067270682706927070270712707227073270742707527076270772707827079270802708127082270832708427085270862708727088270892709027091270922709327094270952709627097270982709927100271012710227103271042710527106271072710827109271102711127112271132711427115271162711727118271192712027121271222712327124271252712627127271282712927130271312713227133271342713527136271372713827139271402714127142271432714427145271462714727148271492715027151271522715327154271552715627157271582715927160271612716227163271642716527166271672716827169271702717127172271732717427175271762717727178271792718027181271822718327184271852718627187271882718927190271912719227193271942719527196271972719827199272002720127202272032720427205272062720727208272092721027211272122721327214272152721627217272182721927220272212722227223272242722527226272272722827229272302723127232272332723427235272362723727238272392724027241272422724327244272452724627247272482724927250272512725227253272542725527256272572725827259272602726127262272632726427265272662726727268272692727027271272722727327274272752727627277272782727927280272812728227283272842728527286272872728827289272902729127292272932729427295272962729727298272992730027301273022730327304273052730627307273082730927310273112731227313273142731527316273172731827319273202732127322273232732427325273262732727328273292733027331273322733327334273352733627337273382733927340273412734227343273442734527346273472734827349273502735127352273532735427355273562735727358273592736027361273622736327364273652736627367273682736927370273712737227373273742737527376273772737827379273802738127382273832738427385273862738727388273892739027391273922739327394273952739627397273982739927400274012740227403274042740527406274072740827409274102741127412274132741427415274162741727418274192742027421274222742327424274252742627427274282742927430274312743227433274342743527436274372743827439274402744127442274432744427445274462744727448274492745027451274522745327454274552745627457274582745927460274612746227463274642746527466274672746827469274702747127472274732747427475274762747727478274792748027481274822748327484274852748627487274882748927490274912749227493274942749527496274972749827499275002750127502275032750427505275062750727508275092751027511275122751327514275152751627517275182751927520275212752227523275242752527526275272752827529275302753127532275332753427535275362753727538275392754027541275422754327544275452754627547275482754927550275512755227553275542755527556275572755827559275602756127562275632756427565275662756727568275692757027571275722757327574275752757627577275782757927580275812758227583275842758527586275872758827589275902759127592275932759427595275962759727598275992760027601276022760327604276052760627607276082760927610276112761227613276142761527616276172761827619276202762127622276232762427625276262762727628276292763027631276322763327634276352763627637276382763927640276412764227643276442764527646276472764827649276502765127652276532765427655276562765727658276592766027661276622766327664276652766627667276682766927670276712767227673276742767527676276772767827679276802768127682276832768427685276862768727688276892769027691276922769327694276952769627697276982769927700277012770227703277042770527706277072770827709277102771127712277132771427715277162771727718277192772027721277222772327724277252772627727277282772927730277312773227733277342773527736277372773827739277402774127742277432774427745277462774727748277492775027751277522775327754277552775627757277582775927760277612776227763277642776527766277672776827769277702777127772277732777427775277762777727778277792778027781277822778327784277852778627787277882778927790277912779227793277942779527796277972779827799278002780127802278032780427805278062780727808278092781027811278122781327814278152781627817278182781927820278212782227823278242782527826278272782827829278302783127832278332783427835278362783727838278392784027841278422784327844278452784627847278482784927850278512785227853278542785527856278572785827859278602786127862278632786427865278662786727868278692787027871278722787327874278752787627877278782787927880278812788227883278842788527886278872788827889278902789127892278932789427895278962789727898278992790027901279022790327904279052790627907279082790927910279112791227913279142791527916279172791827919279202792127922279232792427925279262792727928279292793027931279322793327934279352793627937279382793927940279412794227943279442794527946279472794827949279502795127952279532795427955279562795727958279592796027961279622796327964279652796627967279682796927970279712797227973279742797527976279772797827979279802798127982279832798427985279862798727988279892799027991279922799327994279952799627997279982799928000280012800228003280042800528006280072800828009280102801128012280132801428015280162801728018280192802028021280222802328024280252802628027280282802928030280312803228033280342803528036280372803828039280402804128042280432804428045280462804728048280492805028051280522805328054280552805628057280582805928060280612806228063280642806528066280672806828069280702807128072280732807428075280762807728078280792808028081280822808328084280852808628087280882808928090280912809228093280942809528096280972809828099281002810128102281032810428105281062810728108281092811028111281122811328114281152811628117281182811928120281212812228123281242812528126281272812828129281302813128132281332813428135281362813728138281392814028141281422814328144281452814628147281482814928150281512815228153281542815528156281572815828159281602816128162281632816428165281662816728168281692817028171281722817328174281752817628177281782817928180281812818228183281842818528186281872818828189281902819128192281932819428195281962819728198281992820028201282022820328204282052820628207282082820928210282112821228213282142821528216282172821828219282202822128222282232822428225282262822728228282292823028231282322823328234282352823628237282382823928240282412824228243282442824528246282472824828249282502825128252282532825428255282562825728258282592826028261282622826328264282652826628267282682826928270282712827228273282742827528276282772827828279282802828128282282832828428285282862828728288282892829028291282922829328294282952829628297282982829928300283012830228303283042830528306283072830828309283102831128312283132831428315283162831728318283192832028321283222832328324283252832628327283282832928330283312833228333283342833528336283372833828339283402834128342283432834428345283462834728348283492835028351283522835328354283552835628357283582835928360283612836228363283642836528366283672836828369283702837128372283732837428375283762837728378283792838028381283822838328384283852838628387283882838928390283912839228393283942839528396283972839828399284002840128402284032840428405284062840728408284092841028411284122841328414284152841628417284182841928420284212842228423284242842528426284272842828429284302843128432284332843428435284362843728438284392844028441284422844328444284452844628447284482844928450284512845228453284542845528456284572845828459284602846128462284632846428465284662846728468284692847028471284722847328474284752847628477284782847928480284812848228483284842848528486284872848828489284902849128492284932849428495284962849728498284992850028501285022850328504285052850628507285082850928510285112851228513285142851528516285172851828519285202852128522285232852428525285262852728528285292853028531285322853328534285352853628537285382853928540285412854228543285442854528546285472854828549285502855128552285532855428555285562855728558285592856028561285622856328564285652856628567285682856928570285712857228573285742857528576285772857828579285802858128582285832858428585285862858728588285892859028591285922859328594285952859628597285982859928600286012860228603286042860528606286072860828609286102861128612286132861428615286162861728618286192862028621286222862328624286252862628627286282862928630286312863228633286342863528636286372863828639286402864128642286432864428645286462864728648286492865028651286522865328654286552865628657286582865928660286612866228663286642866528666286672866828669286702867128672286732867428675286762867728678286792868028681286822868328684286852868628687286882868928690286912869228693286942869528696286972869828699287002870128702287032870428705287062870728708287092871028711287122871328714287152871628717287182871928720287212872228723287242872528726287272872828729287302873128732287332873428735287362873728738287392874028741287422874328744287452874628747287482874928750287512875228753287542875528756287572875828759287602876128762287632876428765287662876728768287692877028771287722877328774287752877628777287782877928780287812878228783287842878528786287872878828789287902879128792287932879428795287962879728798287992880028801288022880328804288052880628807288082880928810288112881228813288142881528816288172881828819288202882128822288232882428825288262882728828288292883028831288322883328834288352883628837288382883928840288412884228843288442884528846288472884828849288502885128852288532885428855288562885728858288592886028861288622886328864288652886628867288682886928870288712887228873288742887528876288772887828879288802888128882288832888428885288862888728888288892889028891288922889328894288952889628897288982889928900289012890228903289042890528906289072890828909289102891128912289132891428915289162891728918289192892028921289222892328924289252892628927289282892928930289312893228933289342893528936289372893828939289402894128942289432894428945289462894728948289492895028951289522895328954289552895628957289582895928960289612896228963289642896528966289672896828969289702897128972289732897428975289762897728978289792898028981289822898328984289852898628987289882898928990289912899228993289942899528996289972899828999290002900129002290032900429005290062900729008290092901029011290122901329014290152901629017290182901929020290212902229023290242902529026290272902829029290302903129032290332903429035290362903729038290392904029041290422904329044290452904629047290482904929050290512905229053290542905529056290572905829059290602906129062290632906429065290662906729068290692907029071290722907329074290752907629077290782907929080290812908229083290842908529086290872908829089290902909129092290932909429095290962909729098290992910029101291022910329104291052910629107291082910929110291112911229113291142911529116291172911829119291202912129122291232912429125291262912729128291292913029131291322913329134291352913629137291382913929140291412914229143291442914529146291472914829149291502915129152291532915429155291562915729158291592916029161291622916329164291652916629167291682916929170291712917229173291742917529176291772917829179291802918129182291832918429185291862918729188291892919029191291922919329194291952919629197291982919929200292012920229203292042920529206292072920829209292102921129212292132921429215292162921729218292192922029221292222922329224292252922629227292282922929230292312923229233292342923529236292372923829239292402924129242292432924429245292462924729248292492925029251292522925329254292552925629257292582925929260292612926229263292642926529266292672926829269292702927129272292732927429275292762927729278292792928029281292822928329284292852928629287292882928929290292912929229293292942929529296292972929829299293002930129302293032930429305293062930729308293092931029311293122931329314293152931629317293182931929320293212932229323293242932529326293272932829329293302933129332293332933429335293362933729338293392934029341293422934329344293452934629347293482934929350293512935229353293542935529356293572935829359293602936129362293632936429365293662936729368293692937029371293722937329374293752937629377293782937929380293812938229383293842938529386293872938829389293902939129392293932939429395293962939729398293992940029401294022940329404294052940629407294082940929410294112941229413294142941529416294172941829419294202942129422294232942429425294262942729428294292943029431294322943329434294352943629437294382943929440294412944229443294442944529446294472944829449294502945129452294532945429455294562945729458294592946029461294622946329464294652946629467294682946929470294712947229473294742947529476294772947829479294802948129482294832948429485294862948729488294892949029491294922949329494294952949629497294982949929500295012950229503295042950529506295072950829509295102951129512295132951429515295162951729518295192952029521295222952329524295252952629527295282952929530295312953229533295342953529536295372953829539295402954129542295432954429545295462954729548295492955029551295522955329554295552955629557295582955929560295612956229563295642956529566295672956829569295702957129572295732957429575295762957729578295792958029581295822958329584295852958629587295882958929590295912959229593295942959529596295972959829599296002960129602296032960429605296062960729608296092961029611296122961329614296152961629617296182961929620296212962229623296242962529626296272962829629296302963129632296332963429635296362963729638296392964029641296422964329644296452964629647296482964929650296512965229653296542965529656296572965829659296602966129662296632966429665296662966729668296692967029671296722967329674296752967629677296782967929680296812968229683296842968529686296872968829689296902969129692296932969429695296962969729698296992970029701297022970329704297052970629707297082970929710297112971229713297142971529716297172971829719297202972129722297232972429725297262972729728297292973029731297322973329734297352973629737297382973929740297412974229743297442974529746297472974829749297502975129752297532975429755297562975729758297592976029761297622976329764297652976629767297682976929770297712977229773297742977529776297772977829779297802978129782297832978429785297862978729788297892979029791297922979329794297952979629797297982979929800298012980229803298042980529806298072980829809298102981129812298132981429815298162981729818298192982029821298222982329824298252982629827298282982929830298312983229833298342983529836298372983829839298402984129842298432984429845298462984729848298492985029851298522985329854298552985629857298582985929860298612986229863298642986529866298672986829869298702987129872298732987429875298762987729878298792988029881298822988329884298852988629887298882988929890298912989229893298942989529896298972989829899299002990129902299032990429905299062990729908299092991029911299122991329914299152991629917299182991929920299212992229923299242992529926299272992829929299302993129932299332993429935299362993729938299392994029941299422994329944299452994629947299482994929950299512995229953299542995529956299572995829959299602996129962299632996429965299662996729968299692997029971299722997329974299752997629977299782997929980299812998229983299842998529986299872998829989299902999129992299932999429995299962999729998299993000030001300023000330004300053000630007300083000930010300113001230013300143001530016300173001830019300203002130022300233002430025300263002730028300293003030031300323003330034300353003630037300383003930040300413004230043300443004530046300473004830049300503005130052300533005430055300563005730058300593006030061300623006330064300653006630067300683006930070300713007230073300743007530076300773007830079300803008130082300833008430085300863008730088300893009030091300923009330094300953009630097300983009930100301013010230103301043010530106301073010830109301103011130112301133011430115301163011730118301193012030121301223012330124301253012630127301283012930130301313013230133301343013530136301373013830139301403014130142301433014430145301463014730148301493015030151301523015330154301553015630157301583015930160301613016230163301643016530166301673016830169301703017130172301733017430175301763017730178301793018030181301823018330184301853018630187301883018930190301913019230193301943019530196301973019830199302003020130202302033020430205302063020730208302093021030211302123021330214302153021630217302183021930220302213022230223302243022530226302273022830229302303023130232302333023430235302363023730238302393024030241302423024330244302453024630247302483024930250302513025230253302543025530256302573025830259302603026130262302633026430265302663026730268302693027030271302723027330274302753027630277302783027930280302813028230283302843028530286302873028830289302903029130292302933029430295302963029730298302993030030301303023030330304303053030630307303083030930310303113031230313303143031530316303173031830319303203032130322303233032430325303263032730328303293033030331303323033330334303353033630337303383033930340303413034230343303443034530346303473034830349303503035130352303533035430355303563035730358303593036030361303623036330364303653036630367303683036930370303713037230373303743037530376303773037830379303803038130382303833038430385303863038730388303893039030391303923039330394303953039630397303983039930400304013040230403304043040530406304073040830409304103041130412304133041430415304163041730418304193042030421304223042330424304253042630427304283042930430304313043230433304343043530436304373043830439304403044130442304433044430445304463044730448304493045030451304523045330454304553045630457304583045930460304613046230463304643046530466304673046830469304703047130472304733047430475304763047730478304793048030481304823048330484304853048630487304883048930490304913049230493304943049530496304973049830499305003050130502305033050430505305063050730508305093051030511305123051330514305153051630517305183051930520305213052230523305243052530526305273052830529305303053130532305333053430535305363053730538305393054030541305423054330544305453054630547305483054930550305513055230553305543055530556305573055830559305603056130562305633056430565305663056730568305693057030571305723057330574305753057630577305783057930580305813058230583305843058530586305873058830589305903059130592305933059430595305963059730598305993060030601306023060330604306053060630607306083060930610306113061230613306143061530616306173061830619306203062130622306233062430625306263062730628306293063030631306323063330634306353063630637306383063930640306413064230643306443064530646306473064830649306503065130652306533065430655306563065730658306593066030661306623066330664306653066630667306683066930670306713067230673306743067530676306773067830679306803068130682306833068430685306863068730688306893069030691306923069330694306953069630697306983069930700307013070230703307043070530706307073070830709307103071130712307133071430715307163071730718307193072030721307223072330724307253072630727307283072930730307313073230733307343073530736307373073830739307403074130742307433074430745307463074730748307493075030751307523075330754307553075630757307583075930760307613076230763307643076530766307673076830769307703077130772307733077430775307763077730778307793078030781307823078330784307853078630787307883078930790307913079230793307943079530796307973079830799308003080130802308033080430805308063080730808308093081030811308123081330814308153081630817308183081930820308213082230823308243082530826308273082830829308303083130832308333083430835308363083730838308393084030841308423084330844308453084630847308483084930850308513085230853308543085530856308573085830859308603086130862308633086430865308663086730868308693087030871308723087330874308753087630877308783087930880308813088230883308843088530886308873088830889308903089130892308933089430895308963089730898308993090030901309023090330904309053090630907309083090930910309113091230913309143091530916309173091830919309203092130922309233092430925309263092730928309293093030931309323093330934309353093630937309383093930940309413094230943309443094530946309473094830949309503095130952309533095430955309563095730958309593096030961309623096330964309653096630967309683096930970309713097230973309743097530976309773097830979309803098130982309833098430985309863098730988309893099030991309923099330994309953099630997309983099931000310013100231003310043100531006310073100831009310103101131012310133101431015310163101731018310193102031021310223102331024310253102631027310283102931030310313103231033310343103531036310373103831039310403104131042310433104431045310463104731048310493105031051310523105331054310553105631057310583105931060310613106231063310643106531066310673106831069310703107131072310733107431075310763107731078310793108031081310823108331084310853108631087310883108931090310913109231093310943109531096310973109831099311003110131102311033110431105311063110731108311093111031111311123111331114311153111631117311183111931120311213112231123311243112531126311273112831129311303113131132311333113431135311363113731138311393114031141311423114331144311453114631147311483114931150311513115231153311543115531156311573115831159311603116131162311633116431165311663116731168311693117031171311723117331174311753117631177311783117931180311813118231183311843118531186311873118831189311903119131192311933119431195311963119731198311993120031201312023120331204312053120631207312083120931210312113121231213312143121531216312173121831219312203122131222312233122431225312263122731228312293123031231312323123331234312353123631237312383123931240312413124231243312443124531246312473124831249312503125131252312533125431255312563125731258312593126031261312623126331264312653126631267312683126931270312713127231273312743127531276312773127831279312803128131282312833128431285312863128731288312893129031291312923129331294312953129631297312983129931300313013130231303313043130531306313073130831309313103131131312313133131431315313163131731318313193132031321313223132331324313253132631327313283132931330313313133231333313343133531336313373133831339313403134131342313433134431345313463134731348313493135031351313523135331354313553135631357313583135931360313613136231363313643136531366313673136831369313703137131372313733137431375313763137731378313793138031381313823138331384313853138631387313883138931390313913139231393313943139531396313973139831399314003140131402314033140431405314063140731408314093141031411314123141331414314153141631417314183141931420314213142231423314243142531426314273142831429314303143131432314333143431435314363143731438314393144031441314423144331444314453144631447314483144931450314513145231453314543145531456314573145831459314603146131462314633146431465314663146731468314693147031471314723147331474314753147631477314783147931480314813148231483314843148531486314873148831489314903149131492314933149431495314963149731498314993150031501315023150331504315053150631507315083150931510315113151231513315143151531516315173151831519315203152131522315233152431525315263152731528315293153031531315323153331534315353153631537315383153931540315413154231543315443154531546315473154831549315503155131552315533155431555315563155731558315593156031561315623156331564315653156631567315683156931570315713157231573315743157531576315773157831579315803158131582315833158431585315863158731588315893159031591315923159331594315953159631597315983159931600316013160231603316043160531606316073160831609316103161131612316133161431615316163161731618316193162031621316223162331624316253162631627316283162931630316313163231633316343163531636316373163831639316403164131642316433164431645316463164731648316493165031651316523165331654316553165631657316583165931660316613166231663316643166531666316673166831669316703167131672316733167431675316763167731678316793168031681316823168331684316853168631687316883168931690316913169231693316943169531696316973169831699317003170131702317033170431705317063170731708317093171031711317123171331714317153171631717317183171931720317213172231723317243172531726317273172831729317303173131732317333173431735317363173731738317393174031741317423174331744317453174631747317483174931750317513175231753317543175531756317573175831759317603176131762317633176431765317663176731768317693177031771317723177331774317753177631777317783177931780317813178231783317843178531786317873178831789317903179131792317933179431795317963179731798317993180031801318023180331804318053180631807318083180931810318113181231813318143181531816318173181831819318203182131822318233182431825318263182731828318293183031831318323183331834318353183631837318383183931840318413184231843318443184531846318473184831849318503185131852318533185431855318563185731858318593186031861318623186331864318653186631867318683186931870318713187231873318743187531876318773187831879318803188131882318833188431885318863188731888318893189031891318923189331894318953189631897318983189931900319013190231903319043190531906319073190831909319103191131912319133191431915319163191731918319193192031921319223192331924319253192631927319283192931930319313193231933319343193531936319373193831939319403194131942319433194431945319463194731948319493195031951319523195331954319553195631957319583195931960319613196231963319643196531966319673196831969319703197131972319733197431975319763197731978319793198031981319823198331984319853198631987319883198931990319913199231993319943199531996319973199831999320003200132002320033200432005320063200732008320093201032011320123201332014320153201632017320183201932020320213202232023320243202532026320273202832029320303203132032320333203432035320363203732038320393204032041320423204332044320453204632047320483204932050320513205232053320543205532056320573205832059320603206132062320633206432065320663206732068320693207032071320723207332074320753207632077320783207932080320813208232083320843208532086320873208832089320903209132092320933209432095320963209732098320993210032101321023210332104321053210632107321083210932110321113211232113321143211532116321173211832119321203212132122321233212432125321263212732128321293213032131321323213332134321353213632137321383213932140321413214232143321443214532146321473214832149321503215132152321533215432155321563215732158321593216032161321623216332164321653216632167321683216932170321713217232173321743217532176321773217832179321803218132182321833218432185321863218732188321893219032191321923219332194321953219632197321983219932200322013220232203322043220532206322073220832209322103221132212322133221432215322163221732218322193222032221322223222332224322253222632227322283222932230322313223232233322343223532236322373223832239322403224132242322433224432245322463224732248322493225032251322523225332254322553225632257322583225932260322613226232263322643226532266322673226832269322703227132272322733227432275322763227732278322793228032281322823228332284322853228632287322883228932290322913229232293322943229532296322973229832299323003230132302323033230432305323063230732308323093231032311323123231332314323153231632317323183231932320323213232232323323243232532326323273232832329323303233132332323333233432335323363233732338323393234032341323423234332344323453234632347323483234932350323513235232353323543235532356323573235832359323603236132362323633236432365323663236732368323693237032371323723237332374323753237632377323783237932380323813238232383323843238532386323873238832389323903239132392323933239432395323963239732398323993240032401324023240332404324053240632407324083240932410324113241232413324143241532416324173241832419324203242132422324233242432425324263242732428324293243032431324323243332434324353243632437324383243932440324413244232443324443244532446324473244832449324503245132452324533245432455324563245732458324593246032461324623246332464324653246632467324683246932470324713247232473324743247532476324773247832479324803248132482324833248432485324863248732488324893249032491324923249332494324953249632497324983249932500325013250232503325043250532506325073250832509325103251132512325133251432515325163251732518325193252032521325223252332524325253252632527325283252932530325313253232533325343253532536325373253832539325403254132542325433254432545325463254732548325493255032551325523255332554325553255632557325583255932560325613256232563325643256532566325673256832569325703257132572325733257432575325763257732578325793258032581325823258332584325853258632587325883258932590325913259232593325943259532596325973259832599326003260132602326033260432605326063260732608326093261032611326123261332614326153261632617326183261932620326213262232623326243262532626326273262832629326303263132632326333263432635326363263732638326393264032641326423264332644326453264632647326483264932650326513265232653326543265532656326573265832659326603266132662326633266432665326663266732668326693267032671326723267332674326753267632677326783267932680326813268232683326843268532686326873268832689326903269132692326933269432695326963269732698326993270032701327023270332704327053270632707327083270932710327113271232713327143271532716327173271832719327203272132722327233272432725327263272732728327293273032731327323273332734327353273632737327383273932740327413274232743327443274532746327473274832749327503275132752327533275432755327563275732758327593276032761327623276332764327653276632767327683276932770327713277232773327743277532776327773277832779327803278132782327833278432785327863278732788327893279032791327923279332794327953279632797327983279932800328013280232803328043280532806328073280832809328103281132812328133281432815328163281732818328193282032821328223282332824328253282632827328283282932830328313283232833328343283532836328373283832839328403284132842328433284432845328463284732848328493285032851328523285332854328553285632857328583285932860328613286232863328643286532866328673286832869328703287132872328733287432875328763287732878328793288032881328823288332884328853288632887328883288932890328913289232893328943289532896328973289832899329003290132902329033290432905329063290732908329093291032911329123291332914329153291632917329183291932920329213292232923329243292532926329273292832929329303293132932329333293432935329363293732938329393294032941329423294332944329453294632947329483294932950329513295232953329543295532956329573295832959329603296132962329633296432965329663296732968329693297032971329723297332974329753297632977329783297932980329813298232983329843298532986329873298832989329903299132992329933299432995329963299732998329993300033001330023300333004330053300633007330083300933010330113301233013330143301533016330173301833019330203302133022330233302433025330263302733028330293303033031330323303333034330353303633037330383303933040330413304233043330443304533046330473304833049330503305133052330533305433055330563305733058330593306033061330623306333064330653306633067330683306933070330713307233073330743307533076330773307833079330803308133082330833308433085330863308733088330893309033091330923309333094330953309633097330983309933100331013310233103331043310533106331073310833109331103311133112331133311433115331163311733118331193312033121331223312333124331253312633127331283312933130331313313233133331343313533136331373313833139331403314133142331433314433145331463314733148331493315033151331523315333154331553315633157331583315933160331613316233163331643316533166331673316833169331703317133172331733317433175331763317733178331793318033181331823318333184331853318633187331883318933190331913319233193331943319533196331973319833199332003320133202332033320433205332063320733208332093321033211332123321333214332153321633217332183321933220332213322233223332243322533226332273322833229332303323133232332333323433235332363323733238332393324033241332423324333244332453324633247332483324933250332513325233253332543325533256332573325833259332603326133262332633326433265332663326733268332693327033271332723327333274332753327633277332783327933280332813328233283332843328533286332873328833289332903329133292332933329433295332963329733298332993330033301333023330333304333053330633307333083330933310333113331233313333143331533316333173331833319333203332133322333233332433325333263332733328333293333033331333323333333334333353333633337333383333933340333413334233343333443334533346333473334833349333503335133352333533335433355333563335733358333593336033361333623336333364333653336633367333683336933370333713337233373333743337533376333773337833379333803338133382333833338433385333863338733388333893339033391333923339333394333953339633397333983339933400334013340233403334043340533406334073340833409334103341133412334133341433415334163341733418334193342033421334223342333424334253342633427334283342933430334313343233433334343343533436334373343833439334403344133442334433344433445334463344733448334493345033451334523345333454334553345633457334583345933460334613346233463334643346533466334673346833469334703347133472334733347433475334763347733478334793348033481334823348333484334853348633487334883348933490334913349233493334943349533496334973349833499335003350133502335033350433505335063350733508335093351033511335123351333514335153351633517335183351933520335213352233523335243352533526335273352833529335303353133532335333353433535335363353733538335393354033541335423354333544335453354633547335483354933550335513355233553335543355533556335573355833559335603356133562335633356433565335663356733568335693357033571335723357333574335753357633577335783357933580335813358233583335843358533586335873358833589335903359133592335933359433595335963359733598335993360033601336023360333604336053360633607336083360933610336113361233613336143361533616336173361833619336203362133622336233362433625336263362733628336293363033631336323363333634336353363633637336383363933640336413364233643336443364533646336473364833649336503365133652336533365433655336563365733658336593366033661336623366333664336653366633667336683366933670336713367233673336743367533676336773367833679336803368133682336833368433685336863368733688336893369033691336923369333694336953369633697336983369933700337013370233703337043370533706337073370833709337103371133712337133371433715337163371733718337193372033721337223372333724337253372633727337283372933730337313373233733337343373533736337373373833739337403374133742337433374433745337463374733748337493375033751337523375333754337553375633757337583375933760337613376233763337643376533766337673376833769337703377133772337733377433775337763377733778337793378033781337823378333784337853378633787337883378933790337913379233793337943379533796337973379833799338003380133802338033380433805338063380733808338093381033811338123381333814338153381633817338183381933820338213382233823338243382533826338273382833829338303383133832338333383433835338363383733838338393384033841338423384333844338453384633847338483384933850338513385233853338543385533856338573385833859338603386133862338633386433865338663386733868338693387033871338723387333874338753387633877338783387933880338813388233883338843388533886338873388833889338903389133892338933389433895338963389733898338993390033901339023390333904339053390633907339083390933910339113391233913339143391533916339173391833919339203392133922339233392433925339263392733928339293393033931339323393333934339353393633937339383393933940339413394233943339443394533946339473394833949339503395133952339533395433955339563395733958339593396033961339623396333964339653396633967339683396933970339713397233973339743397533976339773397833979339803398133982339833398433985339863398733988339893399033991339923399333994339953399633997339983399934000340013400234003340043400534006340073400834009340103401134012340133401434015340163401734018340193402034021340223402334024340253402634027340283402934030340313403234033340343403534036340373403834039340403404134042340433404434045340463404734048340493405034051340523405334054340553405634057340583405934060340613406234063340643406534066340673406834069340703407134072340733407434075340763407734078340793408034081340823408334084340853408634087340883408934090340913409234093340943409534096340973409834099341003410134102341033410434105341063410734108341093411034111341123411334114341153411634117341183411934120341213412234123341243412534126341273412834129341303413134132341333413434135341363413734138341393414034141341423414334144341453414634147341483414934150341513415234153341543415534156341573415834159341603416134162341633416434165341663416734168341693417034171341723417334174341753417634177341783417934180341813418234183341843418534186341873418834189341903419134192341933419434195341963419734198341993420034201342023420334204342053420634207342083420934210342113421234213342143421534216342173421834219342203422134222342233422434225342263422734228342293423034231342323423334234342353423634237342383423934240342413424234243342443424534246342473424834249342503425134252342533425434255342563425734258342593426034261342623426334264342653426634267342683426934270342713427234273342743427534276342773427834279342803428134282342833428434285342863428734288342893429034291342923429334294342953429634297342983429934300343013430234303343043430534306343073430834309343103431134312343133431434315343163431734318343193432034321343223432334324343253432634327343283432934330343313433234333343343433534336343373433834339343403434134342343433434434345343463434734348343493435034351343523435334354343553435634357343583435934360343613436234363343643436534366343673436834369343703437134372343733437434375343763437734378343793438034381343823438334384343853438634387343883438934390343913439234393343943439534396343973439834399344003440134402344033440434405344063440734408344093441034411344123441334414344153441634417344183441934420344213442234423344243442534426344273442834429344303443134432344333443434435344363443734438344393444034441344423444334444344453444634447344483444934450344513445234453344543445534456344573445834459344603446134462344633446434465344663446734468344693447034471344723447334474344753447634477344783447934480344813448234483344843448534486344873448834489344903449134492344933449434495344963449734498344993450034501345023450334504345053450634507345083450934510345113451234513345143451534516345173451834519345203452134522345233452434525345263452734528345293453034531345323453334534345353453634537345383453934540345413454234543345443454534546345473454834549345503455134552345533455434555345563455734558345593456034561345623456334564345653456634567345683456934570345713457234573345743457534576345773457834579345803458134582345833458434585345863458734588345893459034591345923459334594345953459634597345983459934600346013460234603346043460534606346073460834609346103461134612346133461434615346163461734618346193462034621346223462334624346253462634627346283462934630346313463234633346343463534636346373463834639346403464134642346433464434645346463464734648346493465034651346523465334654346553465634657346583465934660346613466234663346643466534666346673466834669346703467134672346733467434675346763467734678346793468034681346823468334684346853468634687346883468934690346913469234693346943469534696346973469834699347003470134702347033470434705347063470734708347093471034711347123471334714347153471634717347183471934720347213472234723347243472534726347273472834729347303473134732347333473434735347363473734738347393474034741347423474334744347453474634747347483474934750347513475234753347543475534756347573475834759347603476134762347633476434765347663476734768347693477034771347723477334774347753477634777347783477934780347813478234783347843478534786347873478834789347903479134792347933479434795347963479734798347993480034801348023480334804348053480634807348083480934810348113481234813348143481534816348173481834819348203482134822348233482434825348263482734828348293483034831348323483334834348353483634837348383483934840348413484234843348443484534846348473484834849348503485134852348533485434855348563485734858348593486034861348623486334864348653486634867348683486934870348713487234873348743487534876348773487834879348803488134882348833488434885348863488734888348893489034891348923489334894348953489634897348983489934900349013490234903349043490534906349073490834909349103491134912349133491434915349163491734918349193492034921349223492334924349253492634927349283492934930349313493234933349343493534936349373493834939349403494134942349433494434945349463494734948349493495034951349523495334954349553495634957349583495934960349613496234963349643496534966349673496834969349703497134972349733497434975349763497734978349793498034981349823498334984349853498634987349883498934990349913499234993349943499534996349973499834999350003500135002350033500435005350063500735008350093501035011350123501335014350153501635017350183501935020350213502235023350243502535026350273502835029350303503135032350333503435035350363503735038350393504035041350423504335044350453504635047350483504935050350513505235053350543505535056350573505835059350603506135062350633506435065350663506735068350693507035071350723507335074350753507635077350783507935080350813508235083350843508535086350873508835089350903509135092350933509435095350963509735098350993510035101351023510335104351053510635107351083510935110351113511235113351143511535116351173511835119351203512135122351233512435125351263512735128351293513035131351323513335134351353513635137351383513935140351413514235143351443514535146351473514835149351503515135152351533515435155351563515735158351593516035161351623516335164351653516635167351683516935170351713517235173351743517535176351773517835179351803518135182351833518435185351863518735188351893519035191351923519335194351953519635197351983519935200352013520235203352043520535206352073520835209352103521135212352133521435215352163521735218352193522035221352223522335224352253522635227352283522935230352313523235233352343523535236352373523835239352403524135242352433524435245352463524735248352493525035251352523525335254352553525635257352583525935260352613526235263352643526535266352673526835269352703527135272352733527435275352763527735278352793528035281352823528335284352853528635287352883528935290352913529235293352943529535296352973529835299353003530135302353033530435305353063530735308353093531035311353123531335314353153531635317353183531935320353213532235323353243532535326353273532835329353303533135332353333533435335353363533735338353393534035341353423534335344353453534635347353483534935350353513535235353353543535535356353573535835359353603536135362353633536435365353663536735368353693537035371353723537335374353753537635377353783537935380353813538235383353843538535386353873538835389353903539135392353933539435395353963539735398353993540035401354023540335404354053540635407354083540935410354113541235413354143541535416354173541835419354203542135422354233542435425354263542735428354293543035431354323543335434354353543635437354383543935440354413544235443354443544535446354473544835449354503545135452354533545435455354563545735458354593546035461354623546335464354653546635467354683546935470354713547235473354743547535476354773547835479354803548135482354833548435485354863548735488354893549035491354923549335494354953549635497354983549935500355013550235503355043550535506355073550835509355103551135512355133551435515355163551735518355193552035521355223552335524355253552635527355283552935530355313553235533355343553535536355373553835539355403554135542355433554435545355463554735548355493555035551355523555335554355553555635557355583555935560355613556235563355643556535566355673556835569355703557135572355733557435575355763557735578355793558035581355823558335584355853558635587355883558935590355913559235593355943559535596355973559835599356003560135602356033560435605356063560735608356093561035611356123561335614356153561635617356183561935620356213562235623356243562535626356273562835629356303563135632356333563435635356363563735638356393564035641356423564335644356453564635647356483564935650356513565235653356543565535656356573565835659356603566135662356633566435665356663566735668356693567035671356723567335674356753567635677356783567935680356813568235683356843568535686356873568835689356903569135692356933569435695356963569735698356993570035701357023570335704357053570635707357083570935710357113571235713357143571535716357173571835719357203572135722357233572435725357263572735728357293573035731357323573335734357353573635737357383573935740357413574235743357443574535746357473574835749357503575135752357533575435755357563575735758357593576035761357623576335764357653576635767357683576935770357713577235773357743577535776357773577835779357803578135782357833578435785357863578735788357893579035791357923579335794357953579635797357983579935800358013580235803358043580535806358073580835809358103581135812358133581435815358163581735818358193582035821358223582335824358253582635827358283582935830358313583235833358343583535836358373583835839358403584135842358433584435845358463584735848358493585035851358523585335854358553585635857358583585935860358613586235863358643586535866358673586835869358703587135872358733587435875358763587735878358793588035881358823588335884358853588635887358883588935890358913589235893358943589535896358973589835899359003590135902359033590435905359063590735908359093591035911359123591335914359153591635917359183591935920359213592235923359243592535926359273592835929359303593135932359333593435935359363593735938359393594035941359423594335944359453594635947359483594935950359513595235953359543595535956359573595835959359603596135962359633596435965359663596735968359693597035971359723597335974359753597635977359783597935980359813598235983359843598535986359873598835989359903599135992359933599435995359963599735998359993600036001360023600336004360053600636007360083600936010360113601236013360143601536016360173601836019360203602136022360233602436025360263602736028360293603036031360323603336034360353603636037360383603936040360413604236043360443604536046360473604836049360503605136052360533605436055360563605736058360593606036061360623606336064360653606636067360683606936070360713607236073360743607536076360773607836079360803608136082360833608436085360863608736088360893609036091360923609336094360953609636097360983609936100361013610236103361043610536106361073610836109361103611136112361133611436115361163611736118361193612036121361223612336124361253612636127361283612936130361313613236133361343613536136361373613836139361403614136142361433614436145361463614736148361493615036151361523615336154361553615636157361583615936160361613616236163361643616536166361673616836169361703617136172361733617436175361763617736178361793618036181361823618336184361853618636187361883618936190361913619236193361943619536196361973619836199362003620136202362033620436205362063620736208362093621036211362123621336214362153621636217362183621936220362213622236223362243622536226362273622836229362303623136232362333623436235362363623736238362393624036241362423624336244362453624636247362483624936250362513625236253362543625536256362573625836259362603626136262362633626436265362663626736268362693627036271362723627336274362753627636277362783627936280362813628236283362843628536286362873628836289362903629136292362933629436295362963629736298362993630036301363023630336304363053630636307363083630936310363113631236313363143631536316363173631836319363203632136322363233632436325363263632736328363293633036331363323633336334363353633636337363383633936340363413634236343363443634536346363473634836349363503635136352363533635436355363563635736358363593636036361363623636336364363653636636367363683636936370363713637236373363743637536376363773637836379363803638136382363833638436385363863638736388363893639036391363923639336394363953639636397363983639936400364013640236403364043640536406364073640836409364103641136412364133641436415364163641736418364193642036421364223642336424364253642636427364283642936430364313643236433364343643536436364373643836439364403644136442364433644436445364463644736448364493645036451364523645336454364553645636457364583645936460364613646236463364643646536466364673646836469364703647136472364733647436475364763647736478364793648036481364823648336484364853648636487364883648936490364913649236493364943649536496364973649836499365003650136502365033650436505365063650736508365093651036511365123651336514365153651636517365183651936520365213652236523365243652536526365273652836529365303653136532365333653436535365363653736538365393654036541365423654336544365453654636547365483654936550365513655236553365543655536556365573655836559365603656136562365633656436565365663656736568365693657036571365723657336574365753657636577365783657936580365813658236583365843658536586365873658836589365903659136592365933659436595365963659736598365993660036601366023660336604366053660636607366083660936610366113661236613366143661536616366173661836619366203662136622366233662436625366263662736628366293663036631366323663336634366353663636637366383663936640366413664236643366443664536646366473664836649366503665136652366533665436655366563665736658366593666036661366623666336664366653666636667366683666936670366713667236673366743667536676366773667836679366803668136682366833668436685366863668736688366893669036691366923669336694366953669636697366983669936700367013670236703367043670536706367073670836709367103671136712367133671436715367163671736718367193672036721367223672336724367253672636727367283672936730367313673236733367343673536736367373673836739367403674136742367433674436745367463674736748367493675036751367523675336754367553675636757367583675936760367613676236763367643676536766367673676836769367703677136772367733677436775367763677736778367793678036781367823678336784367853678636787367883678936790367913679236793367943679536796367973679836799368003680136802368033680436805368063680736808368093681036811368123681336814368153681636817368183681936820368213682236823368243682536826368273682836829368303683136832368333683436835368363683736838368393684036841368423684336844368453684636847368483684936850368513685236853368543685536856368573685836859368603686136862368633686436865368663686736868368693687036871368723687336874368753687636877368783687936880368813688236883368843688536886368873688836889368903689136892368933689436895368963689736898368993690036901369023690336904369053690636907369083690936910369113691236913369143691536916369173691836919369203692136922369233692436925369263692736928369293693036931369323693336934369353693636937369383693936940369413694236943369443694536946369473694836949369503695136952369533695436955369563695736958369593696036961369623696336964369653696636967369683696936970369713697236973369743697536976369773697836979369803698136982369833698436985369863698736988369893699036991369923699336994369953699636997369983699937000370013700237003370043700537006370073700837009370103701137012370133701437015370163701737018370193702037021370223702337024370253702637027370283702937030370313703237033370343703537036370373703837039370403704137042370433704437045370463704737048370493705037051370523705337054370553705637057370583705937060370613706237063370643706537066370673706837069370703707137072370733707437075370763707737078370793708037081370823708337084370853708637087370883708937090370913709237093370943709537096370973709837099371003710137102371033710437105371063710737108371093711037111371123711337114371153711637117371183711937120371213712237123371243712537126371273712837129371303713137132371333713437135371363713737138371393714037141371423714337144371453714637147371483714937150371513715237153371543715537156371573715837159371603716137162371633716437165371663716737168371693717037171371723717337174371753717637177371783717937180371813718237183371843718537186371873718837189371903719137192371933719437195371963719737198371993720037201372023720337204372053720637207372083720937210372113721237213372143721537216372173721837219372203722137222372233722437225372263722737228372293723037231372323723337234372353723637237372383723937240372413724237243372443724537246372473724837249372503725137252372533725437255372563725737258372593726037261372623726337264372653726637267372683726937270372713727237273372743727537276372773727837279372803728137282372833728437285372863728737288372893729037291372923729337294372953729637297372983729937300373013730237303373043730537306373073730837309373103731137312373133731437315373163731737318373193732037321373223732337324373253732637327373283732937330373313733237333373343733537336373373733837339373403734137342373433734437345373463734737348373493735037351373523735337354373553735637357373583735937360373613736237363373643736537366373673736837369373703737137372373733737437375373763737737378373793738037381373823738337384373853738637387373883738937390373913739237393373943739537396373973739837399374003740137402374033740437405374063740737408374093741037411374123741337414374153741637417374183741937420374213742237423374243742537426374273742837429374303743137432374333743437435374363743737438374393744037441374423744337444374453744637447374483744937450374513745237453374543745537456374573745837459374603746137462374633746437465374663746737468374693747037471374723747337474374753747637477374783747937480374813748237483374843748537486374873748837489374903749137492374933749437495374963749737498374993750037501375023750337504375053750637507375083750937510375113751237513375143751537516375173751837519375203752137522375233752437525375263752737528375293753037531375323753337534375353753637537375383753937540375413754237543375443754537546375473754837549375503755137552375533755437555375563755737558375593756037561375623756337564375653756637567375683756937570375713757237573375743757537576375773757837579375803758137582375833758437585375863758737588375893759037591375923759337594375953759637597375983759937600376013760237603376043760537606376073760837609376103761137612376133761437615376163761737618376193762037621376223762337624376253762637627376283762937630376313763237633376343763537636376373763837639376403764137642376433764437645376463764737648376493765037651376523765337654376553765637657376583765937660376613766237663376643766537666376673766837669376703767137672376733767437675376763767737678376793768037681376823768337684376853768637687376883768937690376913769237693376943769537696376973769837699377003770137702377033770437705377063770737708377093771037711377123771337714377153771637717377183771937720377213772237723377243772537726377273772837729377303773137732377333773437735377363773737738377393774037741377423774337744377453774637747377483774937750377513775237753377543775537756377573775837759377603776137762377633776437765377663776737768377693777037771377723777337774377753777637777377783777937780377813778237783377843778537786377873778837789377903779137792377933779437795377963779737798377993780037801378023780337804378053780637807378083780937810378113781237813378143781537816378173781837819378203782137822378233782437825378263782737828378293783037831378323783337834378353783637837378383783937840378413784237843378443784537846378473784837849378503785137852378533785437855378563785737858378593786037861378623786337864378653786637867378683786937870378713787237873378743787537876378773787837879378803788137882378833788437885378863788737888378893789037891378923789337894378953789637897378983789937900379013790237903379043790537906379073790837909379103791137912379133791437915379163791737918379193792037921379223792337924379253792637927379283792937930379313793237933379343793537936379373793837939379403794137942379433794437945379463794737948379493795037951379523795337954379553795637957379583795937960379613796237963379643796537966379673796837969379703797137972379733797437975379763797737978379793798037981379823798337984379853798637987379883798937990379913799237993379943799537996379973799837999380003800138002380033800438005380063800738008380093801038011380123801338014380153801638017380183801938020380213802238023380243802538026380273802838029380303803138032380333803438035380363803738038380393804038041380423804338044380453804638047380483804938050380513805238053380543805538056380573805838059380603806138062380633806438065380663806738068380693807038071380723807338074380753807638077380783807938080380813808238083380843808538086380873808838089380903809138092380933809438095380963809738098380993810038101381023810338104381053810638107381083810938110381113811238113381143811538116381173811838119381203812138122381233812438125381263812738128381293813038131381323813338134381353813638137381383813938140381413814238143381443814538146381473814838149381503815138152381533815438155381563815738158381593816038161381623816338164381653816638167381683816938170381713817238173381743817538176381773817838179381803818138182381833818438185381863818738188381893819038191381923819338194381953819638197381983819938200382013820238203382043820538206382073820838209382103821138212382133821438215382163821738218382193822038221382223822338224382253822638227382283822938230382313823238233382343823538236382373823838239382403824138242382433824438245382463824738248382493825038251382523825338254382553825638257382583825938260382613826238263382643826538266382673826838269382703827138272382733827438275382763827738278382793828038281382823828338284382853828638287382883828938290382913829238293382943829538296382973829838299383003830138302383033830438305383063830738308383093831038311383123831338314383153831638317383183831938320383213832238323383243832538326383273832838329383303833138332383333833438335383363833738338383393834038341383423834338344383453834638347383483834938350383513835238353383543835538356383573835838359383603836138362383633836438365383663836738368383693837038371383723837338374383753837638377383783837938380383813838238383383843838538386383873838838389383903839138392383933839438395383963839738398383993840038401384023840338404384053840638407384083840938410384113841238413384143841538416384173841838419384203842138422384233842438425384263842738428384293843038431384323843338434384353843638437384383843938440384413844238443384443844538446384473844838449384503845138452384533845438455384563845738458384593846038461384623846338464384653846638467384683846938470384713847238473384743847538476384773847838479384803848138482384833848438485384863848738488384893849038491384923849338494384953849638497384983849938500385013850238503385043850538506385073850838509385103851138512385133851438515385163851738518385193852038521385223852338524385253852638527385283852938530385313853238533385343853538536385373853838539385403854138542385433854438545385463854738548385493855038551385523855338554385553855638557385583855938560385613856238563385643856538566385673856838569385703857138572385733857438575385763857738578385793858038581385823858338584385853858638587385883858938590385913859238593385943859538596385973859838599386003860138602386033860438605386063860738608386093861038611386123861338614386153861638617386183861938620386213862238623386243862538626386273862838629386303863138632386333863438635386363863738638386393864038641386423864338644386453864638647386483864938650386513865238653386543865538656386573865838659386603866138662386633866438665386663866738668386693867038671386723867338674386753867638677386783867938680
  1. /**
  2. * @class Ext.draw.ContainerBase
  3. * @private
  4. */
  5. Ext.define('Ext.draw.ContainerBase', {
  6. extend: 'Ext.Container',
  7. constructor: function(config) {
  8. this.callParent([
  9. config
  10. ]);
  11. this.initAnimator();
  12. },
  13. onResize: function(width, height, oldWidth, oldHeight) {
  14. this.handleResize({
  15. width: width,
  16. height: height
  17. }, true);
  18. },
  19. addElementListener: function() {
  20. var el = this.element;
  21. el.on.apply(el, arguments);
  22. },
  23. removeElementListener: function() {
  24. var el = this.element;
  25. el.un.apply(el, arguments);
  26. },
  27. preview: function(image) {
  28. var item;
  29. image = image || this.getImage();
  30. if (image.type === 'svg-markup') {
  31. item = {
  32. xtype: 'container',
  33. html: image.data
  34. };
  35. } else {
  36. item = {
  37. xtype: 'image',
  38. mode: 'img',
  39. imageCls: '',
  40. cls: Ext.baseCSSPrefix + 'chart-preview',
  41. src: image.data
  42. };
  43. }
  44. Ext.Viewport.add({
  45. xtype: 'panel',
  46. layout: 'fit',
  47. modal: true,
  48. border: 1,
  49. shadow: true,
  50. width: '90%',
  51. height: '90%',
  52. hideOnMaskTap: true,
  53. centered: true,
  54. floated: true,
  55. scrollable: false,
  56. closable: true,
  57. // Use 'hide' so that hiding via close button/mask tap go through
  58. // the same code path
  59. closeAction: 'hide',
  60. items: [
  61. item
  62. ],
  63. listeners: {
  64. hide: function() {
  65. this.destroy();
  66. }
  67. }
  68. }).show();
  69. }
  70. });
  71. /**
  72. * @private
  73. * @class Ext.draw.SurfaceBase
  74. */
  75. Ext.define('Ext.draw.SurfaceBase', {
  76. extend: 'Ext.Widget',
  77. getOwnerBody: function() {
  78. return this.getRefOwner().bodyElement;
  79. }
  80. });
  81. /**
  82. * @private
  83. * @class Ext.draw.sprite.AnimationParser
  84. *
  85. * Computes an intermidiate value between two values of the same type for use in animations.
  86. * Can have pre- and post- processor functions if the values need to be processed
  87. * before an intermidiate value can be computed (parseInitial), or the computed value
  88. * needs to be processed before it can be used as a valid attribute value (serve).
  89. */
  90. Ext.define('Ext.draw.sprite.AnimationParser', function() {
  91. function compute(from, to, delta) {
  92. return from + (to - from) * delta;
  93. }
  94. return {
  95. singleton: true,
  96. attributeRe: /^url\(#([a-zA-Z-]+)\)$/,
  97. requires: [
  98. 'Ext.draw.Color'
  99. ],
  100. color: {
  101. parseInitial: function(color1, color2) {
  102. if (Ext.isString(color1)) {
  103. color1 = Ext.util.Color.create(color1);
  104. }
  105. if (Ext.isString(color2)) {
  106. color2 = Ext.util.Color.create(color2);
  107. }
  108. if ((color1 && color1.isColor) && (color2 && color2.isColor)) {
  109. return [
  110. [
  111. color1.r,
  112. color1.g,
  113. color1.b,
  114. color1.a
  115. ],
  116. [
  117. color2.r,
  118. color2.g,
  119. color2.b,
  120. color2.a
  121. ]
  122. ];
  123. } else {
  124. return [
  125. color1 || color2,
  126. color2 || color1
  127. ];
  128. }
  129. },
  130. compute: function(from, to, delta) {
  131. if (!Ext.isArray(from) || !Ext.isArray(to)) {
  132. return to || from;
  133. } else {
  134. return [
  135. compute(from[0], to[0], delta),
  136. compute(from[1], to[1], delta),
  137. compute(from[2], to[2], delta),
  138. compute(from[3], to[3], delta)
  139. ];
  140. }
  141. },
  142. serve: function(array) {
  143. var color = Ext.util.Color.fly(array[0], array[1], array[2], array[3]);
  144. return color.toString();
  145. }
  146. },
  147. number: {
  148. parse: function(n) {
  149. return n === null ? null : +n;
  150. },
  151. compute: function(from, to, delta) {
  152. if (!Ext.isNumber(from) || !Ext.isNumber(to)) {
  153. return to || from;
  154. } else {
  155. return compute(from, to, delta);
  156. }
  157. }
  158. },
  159. angle: {
  160. parseInitial: function(from, to) {
  161. if (to - from > Math.PI) {
  162. to -= Math.PI * 2;
  163. } else if (to - from < -Math.PI) {
  164. to += Math.PI * 2;
  165. }
  166. return [
  167. from,
  168. to
  169. ];
  170. },
  171. compute: function(from, to, delta) {
  172. if (!Ext.isNumber(from) || !Ext.isNumber(to)) {
  173. return to || from;
  174. } else {
  175. return compute(from, to, delta);
  176. }
  177. }
  178. },
  179. path: {
  180. parseInitial: function(from, to) {
  181. var fromStripes = from.toStripes(),
  182. toStripes = to.toStripes(),
  183. i, j,
  184. fromLength = fromStripes.length,
  185. toLength = toStripes.length,
  186. fromStripe, toStripe, length,
  187. lastStripe = toStripes[toLength - 1],
  188. endPoint = [
  189. lastStripe[lastStripe.length - 2],
  190. lastStripe[lastStripe.length - 1]
  191. ];
  192. for (i = fromLength; i < toLength; i++) {
  193. fromStripes.push(fromStripes[fromLength - 1].slice(0));
  194. }
  195. for (i = toLength; i < fromLength; i++) {
  196. toStripes.push(endPoint.slice(0));
  197. }
  198. length = fromStripes.length;
  199. toStripes.path = to;
  200. toStripes.temp = new Ext.draw.Path();
  201. for (i = 0; i < length; i++) {
  202. fromStripe = fromStripes[i];
  203. toStripe = toStripes[i];
  204. fromLength = fromStripe.length;
  205. toLength = toStripe.length;
  206. toStripes.temp.commands.push('M');
  207. for (j = toLength; j < fromLength; j += 6) {
  208. toStripe.push(endPoint[0], endPoint[1], endPoint[0], endPoint[1], endPoint[0], endPoint[1]);
  209. }
  210. lastStripe = toStripes[toStripes.length - 1];
  211. endPoint = [
  212. lastStripe[lastStripe.length - 2],
  213. lastStripe[lastStripe.length - 1]
  214. ];
  215. for (j = fromLength; j < toLength; j += 6) {
  216. fromStripe.push(endPoint[0], endPoint[1], endPoint[0], endPoint[1], endPoint[0], endPoint[1]);
  217. }
  218. for (i = 0; i < toStripe.length; i++) {
  219. toStripe[i] -= fromStripe[i];
  220. }
  221. for (i = 2; i < toStripe.length; i += 6) {
  222. toStripes.temp.commands.push('C');
  223. }
  224. }
  225. return [
  226. fromStripes,
  227. toStripes
  228. ];
  229. },
  230. compute: function(fromStripes, toStripes, delta) {
  231. if (delta >= 1) {
  232. return toStripes.path;
  233. }
  234. // eslint-disable-next-line vars-on-top
  235. var i = 0,
  236. ln = fromStripes.length,
  237. j = 0,
  238. ln2, from, to,
  239. temp = toStripes.temp.params,
  240. pos = 0;
  241. for (; i < ln; i++) {
  242. from = fromStripes[i];
  243. to = toStripes[i];
  244. ln2 = from.length;
  245. for (j = 0; j < ln2; j++) {
  246. temp[pos++] = to[j] * delta + from[j];
  247. }
  248. }
  249. return toStripes.temp;
  250. }
  251. },
  252. data: {
  253. compute: function(from, to, delta, target) {
  254. var iMaxFrom = from.length - 1,
  255. iMaxTo = to.length - 1,
  256. iMax = Math.max(iMaxFrom, iMaxTo),
  257. i, start, end;
  258. if (!target || target === from) {
  259. target = [];
  260. }
  261. target.length = iMax + 1;
  262. for (i = 0; i <= iMax; i++) {
  263. start = from[Math.min(i, iMaxFrom)];
  264. end = to[Math.min(i, iMaxTo)];
  265. if (Ext.isNumber(start)) {
  266. if (!Ext.isNumber(end)) {
  267. // This may not give the desired visual result during
  268. // animation (after all, we don't know what the target
  269. // value should be, if it wasn't given to us), but it's
  270. // better than spitting out a bunch of NaNs in the target
  271. // array, when transitioning from a non-empty to an empty
  272. // array.
  273. end = 0;
  274. }
  275. target[i] = start + (end - start) * delta;
  276. } else {
  277. target[i] = end;
  278. }
  279. }
  280. return target;
  281. }
  282. },
  283. text: {
  284. compute: function(from, to, delta) {
  285. return from.substr(0, Math.round(from.length * (1 - delta))) + to.substr(Math.round(to.length * (1 - delta)));
  286. }
  287. },
  288. limited: 'number',
  289. limited01: 'number'
  290. };
  291. });
  292. /* global Float32Array */
  293. /* eslint-disable indent */
  294. (function() {
  295. if (!Ext.global.Float32Array) {
  296. // Typed Array polyfill
  297. // eslint-disable-next-line vars-on-top
  298. var Float32Array = function(array) {
  299. var i, len;
  300. if (typeof array === 'number') {
  301. this.length = array;
  302. } else if ('length' in array) {
  303. this.length = array.length;
  304. for (i = 0 , len = array.length; i < len; i++) {
  305. this[i] = +array[i];
  306. }
  307. }
  308. };
  309. Float32Array.prototype = [];
  310. Ext.global.Float32Array = Float32Array;
  311. }
  312. })();
  313. /* eslint-enable indent */
  314. /**
  315. * Utility class providing mathematics functionalities through all the draw package.
  316. */
  317. Ext.define('Ext.draw.Draw', {
  318. singleton: true,
  319. radian: Math.PI / 180,
  320. pi2: Math.PI * 2,
  321. /**
  322. * @deprecated 6.5.0 Please use the {@link Ext#identityFn} instead.
  323. * Function that returns its first element.
  324. * @param {Mixed} a
  325. * @return {Mixed}
  326. */
  327. reflectFn: function(a) {
  328. return a;
  329. },
  330. /**
  331. * Converting degrees to radians.
  332. * @param {Number} degrees
  333. * @return {Number}
  334. */
  335. rad: function(degrees) {
  336. return (degrees % 360) * this.radian;
  337. },
  338. /**
  339. * Converting radians to degrees.
  340. * @param {Number} radian
  341. * @return {Number}
  342. */
  343. degrees: function(radian) {
  344. return (radian / this.radian) % 360;
  345. },
  346. /**
  347. *
  348. * @param {Object} bbox1
  349. * @param {Object} bbox2
  350. * @param {Number} [padding]
  351. * @return {Boolean}
  352. */
  353. isBBoxIntersect: function(bbox1, bbox2, padding) {
  354. padding = padding || 0;
  355. 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));
  356. },
  357. /**
  358. * Checks if a point is within a bounding box.
  359. * @param x
  360. * @param y
  361. * @param bbox
  362. * @return {Boolean}
  363. */
  364. isPointInBBox: function(x, y, bbox) {
  365. return !!bbox && x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height);
  366. },
  367. /**
  368. * Natural cubic spline interpolation.
  369. * This algorithm runs in linear time.
  370. *
  371. * @param {Array} points Array of numbers.
  372. */
  373. naturalSpline: function(points) {
  374. var i, j,
  375. ln = points.length,
  376. nd, d, y, ny,
  377. r = 0,
  378. zs = new Float32Array(points.length),
  379. result = new Float32Array(points.length * 3 - 2);
  380. zs[0] = 0;
  381. zs[ln - 1] = 0;
  382. for (i = 1; i < ln - 1; i++) {
  383. zs[i] = (points[i + 1] + points[i - 1] - 2 * points[i]) - zs[i - 1];
  384. r = 1 / (4 - r);
  385. zs[i] *= r;
  386. }
  387. for (i = ln - 2; i > 0; i--) {
  388. r = 3.732050807568877 + 48.248711305964385 / (-13.928203230275537 + Math.pow(0.07179676972449123, i));
  389. zs[i] -= zs[i + 1] * r;
  390. }
  391. ny = points[0];
  392. nd = ny - zs[0];
  393. for (i = 0 , j = 0; i < ln - 1; j += 3) {
  394. y = ny;
  395. d = nd;
  396. i++;
  397. ny = points[i];
  398. nd = ny - zs[i];
  399. result[j] = y;
  400. result[j + 1] = (nd + 2 * d) / 3;
  401. result[j + 2] = (nd * 2 + d) / 3;
  402. }
  403. result[j] = ny;
  404. return result;
  405. },
  406. /**
  407. * Shorthand for {@link #naturalSpline}
  408. */
  409. spline: function(points) {
  410. return this.naturalSpline(points);
  411. },
  412. /**
  413. * @private
  414. * Cardinal spline interpolation.
  415. * Goes from cardinal control points to cubic Bezier control points.
  416. */
  417. cardinalToBezier: function(P1, P2, P3, P4, tension) {
  418. return [
  419. P2,
  420. P2 + (P3 - P1) / 6 * tension,
  421. P3 - (P4 - P2) / 6 * tension,
  422. P3
  423. ];
  424. },
  425. /**
  426. * @private
  427. * @param {Number[]} P An array of n x- or y-coordinates.
  428. * @param {Number} tension
  429. * @return {Float32Array} An array of 3n - 2 Bezier control points.
  430. */
  431. cardinalSpline: function(P, tension) {
  432. var n = P.length,
  433. result = new Float32Array(n * 3 - 2),
  434. i, bezier;
  435. if (tension === undefined) {
  436. tension = 0.5;
  437. }
  438. bezier = this.cardinalToBezier(P[0], P[0], P[1], P[2], tension);
  439. result[0] = bezier[0];
  440. result[1] = bezier[1];
  441. result[2] = bezier[2];
  442. result[3] = bezier[3];
  443. for (i = 0; i < n - 3; i++) {
  444. bezier = this.cardinalToBezier(P[i], P[i + 1], P[i + 2], P[i + 3], tension);
  445. result[4 + i * 3] = bezier[1];
  446. result[4 + i * 3 + 1] = bezier[2];
  447. result[4 + i * 3 + 2] = bezier[3];
  448. }
  449. bezier = this.cardinalToBezier(P[n - 3], P[n - 2], P[n - 1], P[n - 1], tension);
  450. result[4 + i * 3] = bezier[1];
  451. result[4 + i * 3 + 1] = bezier[2];
  452. result[4 + i * 3 + 2] = bezier[3];
  453. return result;
  454. },
  455. /**
  456. * @private
  457. *
  458. * Calculates bezier curve control anchor points for a particular point in a path, with a
  459. * smoothing curve applied. The smoothness of the curve is controlled by the 'value' parameter.
  460. * Note that this algorithm assumes that the line being smoothed is normalized going from left
  461. * to right; it makes special adjustments assuming this orientation.
  462. *
  463. * @param {Number} prevX X coordinate of the previous point in the path
  464. * @param {Number} prevY Y coordinate of the previous point in the path
  465. * @param {Number} curX X coordinate of the current point in the path
  466. * @param {Number} curY Y coordinate of the current point in the path
  467. * @param {Number} nextX X coordinate of the next point in the path
  468. * @param {Number} nextY Y coordinate of the next point in the path
  469. * @param {Number} value A value to control the smoothness of the curve; this is used to
  470. * divide the distance between points, so a value of 2 corresponds to
  471. * half the distance between points (a very smooth line) while higher values
  472. * result in less smooth curves. Defaults to 4.
  473. * @return {Object} Object containing x1, y1, x2, y2 bezier control anchor points; x1 and y1
  474. * are the control point for the curve toward the previous path point, and
  475. * x2 and y2 are the control point for the curve toward the next path point.
  476. */
  477. getAnchors: function(prevX, prevY, curX, curY, nextX, nextY, value) {
  478. var PI = Math.PI,
  479. halfPI = PI / 2,
  480. abs = Math.abs,
  481. sin = Math.sin,
  482. cos = Math.cos,
  483. atan = Math.atan,
  484. control1Length, control2Length, control1Angle, control2Angle, control1X, control1Y, control2X, control2Y, alpha;
  485. value = value || 4;
  486. // Find the length of each control anchor line, by dividing the horizontal distance
  487. // between points by the value parameter.
  488. control1Length = (curX - prevX) / value;
  489. control2Length = (nextX - curX) / value;
  490. // Determine the angle of each control anchor line. If the middle point is a vertical
  491. // turnaround then we force it to a flat horizontal angle to prevent the curve from
  492. // dipping above or below the middle point. Otherwise we use an angle that points
  493. // toward the previous/next target point.
  494. if ((curY >= prevY && curY >= nextY) || (curY <= prevY && curY <= nextY)) {
  495. control1Angle = control2Angle = halfPI;
  496. } else {
  497. control1Angle = atan((curX - prevX) / abs(curY - prevY));
  498. if (prevY < curY) {
  499. control1Angle = PI - control1Angle;
  500. }
  501. control2Angle = atan((nextX - curX) / abs(curY - nextY));
  502. if (nextY < curY) {
  503. control2Angle = PI - control2Angle;
  504. }
  505. }
  506. // Adjust the calculated angles so they point away from each other on the same line
  507. alpha = halfPI - ((control1Angle + control2Angle) % (PI * 2)) / 2;
  508. if (alpha > halfPI) {
  509. alpha -= PI;
  510. }
  511. control1Angle += alpha;
  512. control2Angle += alpha;
  513. // Find the control anchor points from the angles and length
  514. control1X = curX - control1Length * sin(control1Angle);
  515. control1Y = curY + control1Length * cos(control1Angle);
  516. control2X = curX + control2Length * sin(control2Angle);
  517. control2Y = curY + control2Length * cos(control2Angle);
  518. // One last adjustment, make sure that no control anchor point extends vertically past
  519. // its target prev/next point, as that results in curves dipping above or below and
  520. // bending back strangely. If we find this happening we keep the control angle but
  521. // reduce the length of the control line so it stays within bounds.
  522. if ((curY > prevY && control1Y < prevY) || (curY < prevY && control1Y > prevY)) {
  523. control1X += abs(prevY - control1Y) * (control1X - curX) / (control1Y - curY);
  524. control1Y = prevY;
  525. }
  526. if ((curY > nextY && control2Y < nextY) || (curY < nextY && control2Y > nextY)) {
  527. control2X -= abs(nextY - control2Y) * (control2X - curX) / (control2Y - curY);
  528. control2Y = nextY;
  529. }
  530. return {
  531. x1: control1X,
  532. y1: control1Y,
  533. x2: control2X,
  534. y2: control2Y
  535. };
  536. },
  537. /**
  538. * Given coordinates of the points, calculates coordinates of a Bezier curve
  539. * that goes through them.
  540. * @param dataX x-coordinates of the points.
  541. * @param dataY y-coordinates of the points.
  542. * @param value A value to control the smoothness of the curve.
  543. * @return {Object} Object holding two arrays, for x and y coordinates of the curve.
  544. */
  545. smooth: function(dataX, dataY, value) {
  546. var ln = dataX.length,
  547. prevX, prevY, curX, curY, nextX, nextY, x, y,
  548. smoothX = [],
  549. smoothY = [],
  550. i, anchors;
  551. for (i = 0; i < ln - 1; i++) {
  552. prevX = dataX[i];
  553. prevY = dataY[i];
  554. if (i === 0) {
  555. x = prevX;
  556. y = prevY;
  557. smoothX.push(x);
  558. smoothY.push(y);
  559. if (ln === 1) {
  560. break;
  561. }
  562. }
  563. curX = dataX[i + 1];
  564. curY = dataY[i + 1];
  565. nextX = dataX[i + 2];
  566. nextY = dataY[i + 2];
  567. if (!(Ext.isNumber(nextX) && Ext.isNumber(nextY))) {
  568. smoothX.push(x, curX, curX);
  569. smoothY.push(y, curY, curY);
  570. break;
  571. }
  572. anchors = this.getAnchors(prevX, prevY, curX, curY, nextX, nextY, value);
  573. smoothX.push(x, anchors.x1, curX);
  574. smoothY.push(y, anchors.y1, curY);
  575. x = anchors.x2;
  576. y = anchors.y2;
  577. }
  578. return {
  579. smoothX: smoothX,
  580. smoothY: smoothY
  581. };
  582. },
  583. /**
  584. * @method
  585. * @private
  586. * Work around for iOS.
  587. * Nested 3d-transforms seems to prevent the redraw inside it until some event is fired.
  588. */
  589. beginUpdateIOS: Ext.os.is.iOS ? function() {
  590. this.iosUpdateEl = Ext.getBody().createChild({
  591. //<debug>
  592. 'data-sticky': true,
  593. //</debug>
  594. style: 'position: absolute; top: 0px; bottom: 0px; left: 0px; right: 0px; ' + 'background: rgba(0,0,0,0.001); z-index: 100000'
  595. });
  596. } : Ext.emptyFn,
  597. endUpdateIOS: function() {
  598. this.iosUpdateEl = Ext.destroy(this.iosUpdateEl);
  599. }
  600. });
  601. /**
  602. * @class Ext.draw.gradient.Gradient
  603. *
  604. * Creates a gradient.
  605. */
  606. Ext.define('Ext.draw.gradient.Gradient', {
  607. requires: [
  608. 'Ext.draw.Color'
  609. ],
  610. isGradient: true,
  611. config: {
  612. /**
  613. * @cfg {Object[]} stops
  614. * Defines the stops of the gradient.
  615. */
  616. stops: []
  617. },
  618. applyStops: function(newStops) {
  619. var stops = [],
  620. ln = newStops.length,
  621. i, stop, color;
  622. for (i = 0; i < ln; i++) {
  623. stop = newStops[i];
  624. color = stop.color;
  625. if (!(color && color.isColor)) {
  626. color = Ext.util.Color.fly(color || Ext.util.Color.NONE);
  627. }
  628. stops.push({
  629. // eslint-disable-next-line max-len
  630. offset: Math.min(1, Math.max(0, 'offset' in stop ? stop.offset : stop.position || 0)),
  631. color: color.toString()
  632. });
  633. }
  634. stops.sort(function(a, b) {
  635. return a.offset - b.offset;
  636. });
  637. return stops;
  638. },
  639. onClassExtended: function(subClass, member) {
  640. if (!member.alias && member.type) {
  641. member.alias = 'gradient.' + member.type;
  642. }
  643. },
  644. constructor: function(config) {
  645. this.initConfig(config);
  646. },
  647. /**
  648. * @method
  649. * @protected
  650. * Generates the gradient for the given context.
  651. * @param {Ext.draw.engine.SvgContext} ctx The context.
  652. * @param {Object} bbox
  653. * @return {CanvasGradient/Ext.draw.engine.SvgContext.Gradient/Ext.util.Color.NONE}
  654. */
  655. generateGradient: Ext.emptyFn
  656. });
  657. /**
  658. * @class Ext.draw.gradient.GradientDefinition
  659. *
  660. * A global map of all gradient configs.
  661. */
  662. Ext.define('Ext.draw.gradient.GradientDefinition', {
  663. singleton: true,
  664. urlStringRe: /^url\(#([\w-]+)\)$/,
  665. gradients: {},
  666. add: function(gradients) {
  667. var store = this.gradients,
  668. i, n, gradient;
  669. for (i = 0 , n = gradients.length; i < n; i++) {
  670. gradient = gradients[i];
  671. if (Ext.isString(gradient.id)) {
  672. store[gradient.id] = gradient;
  673. }
  674. }
  675. },
  676. get: function(str) {
  677. var store = this.gradients,
  678. match = str.match(this.urlStringRe),
  679. gradient;
  680. if (match && match[1] && (gradient = store[match[1]])) {
  681. return gradient || str;
  682. }
  683. return str;
  684. }
  685. });
  686. /* global Float32Array */
  687. /**
  688. * @private
  689. * @class Ext.draw.sprite.AttributeParser
  690. *
  691. * Parsers used for sprite attributes if they are
  692. * {@link Ext.draw.sprite.AttributeDefinition#normalize normalized} (default) when being
  693. * {@link Ext.draw.sprite.Sprite#setAttributes set}.
  694. *
  695. * Methods of the singleton correpond either to the processor functions themselves or processor
  696. * factories.
  697. */
  698. Ext.define('Ext.draw.sprite.AttributeParser', {
  699. singleton: true,
  700. attributeRe: /^url\(#([a-zA-Z-]+)\)$/,
  701. requires: [
  702. 'Ext.draw.Color',
  703. 'Ext.draw.gradient.GradientDefinition'
  704. ],
  705. 'default': Ext.identityFn,
  706. string: function(n) {
  707. return String(n);
  708. },
  709. number: function(n) {
  710. // Numbers as strings will be converted to numbers,
  711. // null will be converted to 0.
  712. if (Ext.isNumber(+n)) {
  713. return n;
  714. }
  715. },
  716. /**
  717. * Normalize angle to the [-180,180) interval.
  718. * @param n Angle in radians.
  719. * @return {Number/undefined} Normalized angle or undefined.
  720. */
  721. angle: function(n) {
  722. if (Ext.isNumber(n)) {
  723. n %= Math.PI * 2;
  724. if (n < -Math.PI) {
  725. n += Math.PI * 2;
  726. } else if (n >= Math.PI) {
  727. n -= Math.PI * 2;
  728. }
  729. return n;
  730. }
  731. },
  732. data: function(n) {
  733. if (Ext.isArray(n)) {
  734. return n.slice();
  735. } else if (n instanceof Float32Array) {
  736. return new Float32Array(n);
  737. }
  738. },
  739. bool: function(n) {
  740. return !!n;
  741. },
  742. color: function(n) {
  743. if (n && n.isColor) {
  744. return n.toString();
  745. } else if (n && n.isGradient) {
  746. return n;
  747. } else if (!n) {
  748. return Ext.util.Color.NONE;
  749. } else if (Ext.isString(n)) {
  750. if (n.substr(0, 3) === 'url') {
  751. n = Ext.draw.gradient.GradientDefinition.get(n);
  752. if (Ext.isString(n)) {
  753. return n;
  754. }
  755. } else {
  756. return Ext.util.Color.fly(n).toString();
  757. }
  758. }
  759. if (n.type === 'linear') {
  760. return Ext.create('Ext.draw.gradient.Linear', n);
  761. } else if (n.type === 'radial') {
  762. return Ext.create('Ext.draw.gradient.Radial', n);
  763. } else if (n.type === 'pattern') {
  764. return Ext.create('Ext.draw.gradient.Pattern', n);
  765. } else {
  766. return Ext.util.Color.NONE;
  767. }
  768. },
  769. limited: function(low, hi) {
  770. return function(n) {
  771. n = +n;
  772. return Ext.isNumber(n) ? Math.min(Math.max(n, low), hi) : undefined;
  773. };
  774. },
  775. limited01: function(n) {
  776. n = +n;
  777. return Ext.isNumber(n) ? Math.min(Math.max(n, 0), 1) : undefined;
  778. },
  779. /**
  780. * Generates a function that checks if a value matches
  781. * one of the given attributes.
  782. * @return {Function}
  783. */
  784. enums: function() {
  785. var enums = {},
  786. args = Array.prototype.slice.call(arguments, 0),
  787. i, ln;
  788. for (i = 0 , ln = args.length; i < ln; i++) {
  789. enums[args[i]] = true;
  790. }
  791. return function(n) {
  792. return n in enums ? n : undefined;
  793. };
  794. }
  795. });
  796. /**
  797. * @private
  798. * Flyweight object to process the attributes of a sprite.
  799. * A single instance of the AttributeDefinition is created per sprite class.
  800. * See `onClassCreated` and `onClassExtended` callbacks
  801. * of the {@link Ext.draw.sprite.Sprite} for more info.
  802. */
  803. Ext.define('Ext.draw.sprite.AttributeDefinition', {
  804. requires: [
  805. 'Ext.draw.sprite.AttributeParser',
  806. 'Ext.draw.sprite.AnimationParser'
  807. ],
  808. config: {
  809. /**
  810. * @cfg {Object} defaults Defines the default values of attributes.
  811. */
  812. defaults: {
  813. $value: {},
  814. lazy: true
  815. },
  816. /**
  817. * @cfg {Object} aliases Defines the alternative names for attributes.
  818. */
  819. aliases: {},
  820. /**
  821. * @cfg {Object} animationProcessors Defines the process used to animate between attributes.
  822. * One doesn't have to define animation processors for sprite attributes that use
  823. * predefined {@link #processors} from the {@link Ext.draw.sprite.AttributeParser}
  824. * singleton.
  825. * For such attributes matching animation processors from the
  826. * {@link Ext.draw.sprite.AnimationParser} singleton will be used automatically.
  827. * However, if you have a custom processor for an attribute that should support
  828. * animation, you must provide a corresponding animation processor for it here.
  829. * For more information on animation processors please see
  830. * {@link Ext.draw.sprite.AnimationParser} documentation.
  831. */
  832. animationProcessors: {},
  833. /**
  834. * @cfg {Object} processors Defines the preprocessing used on the attributes.
  835. * One can define a custom processor function here or use the name of a predefined
  836. * processor from the {@link Ext.draw.sprite.AttributeParser} singleton.
  837. */
  838. processors: {
  839. // A plus side of lazy initialization is that the 'processors' and 'defaults' will
  840. // only be applied for those sprite classes that are actually instantiated.
  841. $value: {},
  842. lazy: true
  843. },
  844. /**
  845. * @cfg {Object} dirtyTriggers
  846. * @deprecated 6.5.0 Use the {@link #triggers} config instead.
  847. */
  848. dirtyTriggers: {},
  849. /* eslint-disable max-len */
  850. /**
  851. * @cfg {Object} triggers Defines which updaters have to be called when an attribute is changed.
  852. * For example, the config below indicates that the 'size' updater
  853. * of a {@link Ext.draw.sprite.Square square} sprite has to be called
  854. * when the 'size' attribute changes.
  855. *
  856. * triggers: {
  857. * size: 'size' // Use comma-separated values here if multiple updaters have to be called.
  858. * } // Note that the order is _not_ guaranteed.
  859. *
  860. * If any of the updaters to be called (triggered by the {@link Ext.draw.sprite.Sprite#setAttributes} call)
  861. * set attributes themselves and those attributes have triggers defined for them,
  862. * then their updaters will be called after all current updaters finish execution.
  863. *
  864. * The updater functions themselves are defined in the {@link #updaters} config,
  865. * aside from the 'canvas' updater, which doesn't have to be defined and acts as a flag,
  866. * indicating that this attribute should be applied to a Canvas context (or whatever emulates it).
  867. * @since 5.1.0
  868. */
  869. triggers: {},
  870. /**
  871. * @cfg {Object} updaters Defines the postprocessing used by the attribute.
  872. * Inside the updater function 'this' refers to the sprite that the attributes belong to.
  873. * In case of an instancing sprite 'this' will refer to the instancing template.
  874. * The two parameters passed to the updater function are the attributes object
  875. * of the sprite or instance, and the names of attributes that triggered this updater call.
  876. *
  877. * The example below shows how the 'size' updater changes other attributes
  878. * of a {@link Ext.draw.sprite.Square square} sprite sprite when its 'size' attribute changes.
  879. *
  880. * updaters: {
  881. * size: function (attr) {
  882. * var size = attr.size;
  883. * this.setAttributes({ // Changes to these attributes will trigger the 'path' updater.
  884. * x: attr.x - size,
  885. * y: attr.y - size,
  886. * height: 2 * size,
  887. * width: 2 * size
  888. * });
  889. * }
  890. * }
  891. */
  892. updaters: {}
  893. },
  894. /* eslint-enable max-len */
  895. inheritableStatics: {
  896. /**
  897. * @private
  898. * Processor declaration in the form of 'processorFactory(argument1,argument2,...)'.
  899. * E.g.: {@link Ext.draw.sprite.AttributeParser#enums enums},
  900. * {@link Ext.draw.sprite.AttributeParser#limited limited}.
  901. */
  902. processorFactoryRe: /^(\w+)\(([\w\-,]*)\)$/
  903. },
  904. // The sprite class for which AttributeDefinition instance is created.
  905. spriteClass: null,
  906. constructor: function(config) {
  907. var me = this;
  908. me.initConfig(config);
  909. },
  910. applyDefaults: function(defaults, oldDefaults) {
  911. oldDefaults = Ext.apply(oldDefaults || {}, this.normalize(defaults));
  912. return oldDefaults;
  913. },
  914. applyAliases: function(aliases, oldAliases) {
  915. return Ext.apply(oldAliases || {}, aliases);
  916. },
  917. applyProcessors: function(processors, oldProcessors) {
  918. this.getAnimationProcessors();
  919. // Apply custom animation processors first.
  920. // eslint-disable-next-line vars-on-top
  921. var result = oldProcessors || {},
  922. defaultProcessor = Ext.draw.sprite.AttributeParser,
  923. processorFactoryRe = this.self.processorFactoryRe,
  924. animationProcessors = {},
  925. anyAnimationProcessors, name, match, fn;
  926. for (name in processors) {
  927. fn = processors[name];
  928. if (typeof fn === 'string') {
  929. match = fn.match(processorFactoryRe);
  930. if (match) {
  931. // enums(... , limited(... or something of that nature.
  932. fn = defaultProcessor[match[1]].apply(defaultProcessor, match[2].split(','));
  933. } else if (defaultProcessor[fn]) {
  934. // Names of animation parsers match the names of attribute parsers.
  935. animationProcessors[name] = fn;
  936. anyAnimationProcessors = true;
  937. fn = defaultProcessor[fn];
  938. }
  939. }
  940. //<debug>
  941. if (!Ext.isFunction(fn)) {
  942. Ext.raise(this.spriteClass.$className + ": processor '" + name + "' has not been found.");
  943. }
  944. //</debug>
  945. result[name] = fn;
  946. }
  947. if (anyAnimationProcessors) {
  948. this.setAnimationProcessors(animationProcessors);
  949. }
  950. return result;
  951. },
  952. applyAnimationProcessors: function(animationProcessors, oldAnimationProcessors) {
  953. var parser = Ext.draw.sprite.AnimationParser,
  954. name, item;
  955. if (!oldAnimationProcessors) {
  956. oldAnimationProcessors = {};
  957. }
  958. for (name in animationProcessors) {
  959. item = animationProcessors[name];
  960. if (item === 'none') {
  961. oldAnimationProcessors[name] = null;
  962. } else if (Ext.isString(item) && !(name in oldAnimationProcessors)) {
  963. if (item in parser) {
  964. // The while loop is used to resolve aliases, e.g. `num: 'number'`,
  965. // where `number` maps to a parser object or is an alias too.
  966. while (Ext.isString(parser[item])) {
  967. item = parser[item];
  968. }
  969. oldAnimationProcessors[name] = parser[item];
  970. }
  971. } else if (Ext.isObject(item)) {
  972. oldAnimationProcessors[name] = item;
  973. }
  974. }
  975. return oldAnimationProcessors;
  976. },
  977. updateDirtyTriggers: function(dirtyTriggers) {
  978. this.setTriggers(dirtyTriggers);
  979. },
  980. applyTriggers: function(triggers, oldTriggers) {
  981. var name;
  982. if (!oldTriggers) {
  983. oldTriggers = {};
  984. }
  985. for (name in triggers) {
  986. oldTriggers[name] = triggers[name].split(',');
  987. }
  988. return oldTriggers;
  989. },
  990. applyUpdaters: function(updaters, oldUpdaters) {
  991. return Ext.apply(oldUpdaters || {}, updaters);
  992. },
  993. batchedNormalize: function(batchedChanges, keepUnrecognized) {
  994. if (!batchedChanges) {
  995. return {};
  996. }
  997. // eslint-disable-next-line vars-on-top
  998. var processors = this.getProcessors(),
  999. aliases = this.getAliases(),
  1000. translation = batchedChanges.translation || batchedChanges.translate,
  1001. normalized = {},
  1002. i, ln, name, val, rotation, scaling, matrix, subVal, split;
  1003. if ('rotation' in batchedChanges) {
  1004. rotation = batchedChanges.rotation;
  1005. } else {
  1006. rotation = ('rotate' in batchedChanges) ? batchedChanges.rotate : undefined;
  1007. }
  1008. if ('scaling' in batchedChanges) {
  1009. scaling = batchedChanges.scaling;
  1010. } else {
  1011. scaling = ('scale' in batchedChanges) ? batchedChanges.scale : undefined;
  1012. }
  1013. if (typeof scaling !== 'undefined') {
  1014. if (Ext.isNumber(scaling)) {
  1015. normalized.scalingX = scaling;
  1016. normalized.scalingY = scaling;
  1017. } else {
  1018. if ('x' in scaling) {
  1019. normalized.scalingX = scaling.x;
  1020. }
  1021. if ('y' in scaling) {
  1022. normalized.scalingY = scaling.y;
  1023. }
  1024. if ('centerX' in scaling) {
  1025. normalized.scalingCenterX = scaling.centerX;
  1026. }
  1027. if ('centerY' in scaling) {
  1028. normalized.scalingCenterY = scaling.centerY;
  1029. }
  1030. }
  1031. }
  1032. if (typeof rotation !== 'undefined') {
  1033. if (Ext.isNumber(rotation)) {
  1034. rotation = Ext.draw.Draw.rad(rotation);
  1035. normalized.rotationRads = rotation;
  1036. } else {
  1037. if ('rads' in rotation) {
  1038. normalized.rotationRads = rotation.rads;
  1039. } else if ('degrees' in rotation) {
  1040. if (Ext.isArray(rotation.degrees)) {
  1041. normalized.rotationRads = Ext.Array.map(rotation.degrees, function(deg) {
  1042. return Ext.draw.Draw.rad(deg);
  1043. });
  1044. } else {
  1045. normalized.rotationRads = Ext.draw.Draw.rad(rotation.degrees);
  1046. }
  1047. }
  1048. if ('centerX' in rotation) {
  1049. normalized.rotationCenterX = rotation.centerX;
  1050. }
  1051. if ('centerY' in rotation) {
  1052. normalized.rotationCenterY = rotation.centerY;
  1053. }
  1054. }
  1055. }
  1056. if (typeof translation !== 'undefined') {
  1057. if ('x' in translation) {
  1058. normalized.translationX = translation.x;
  1059. }
  1060. if ('y' in translation) {
  1061. normalized.translationY = translation.y;
  1062. }
  1063. }
  1064. if ('matrix' in batchedChanges) {
  1065. matrix = Ext.draw.Matrix.create(batchedChanges.matrix);
  1066. split = matrix.split();
  1067. normalized.matrix = matrix;
  1068. normalized.rotationRads = split.rotation;
  1069. normalized.rotationCenterX = 0;
  1070. normalized.rotationCenterY = 0;
  1071. normalized.scalingX = split.scaleX;
  1072. normalized.scalingY = split.scaleY;
  1073. normalized.scalingCenterX = 0;
  1074. normalized.scalingCenterY = 0;
  1075. normalized.translationX = split.translateX;
  1076. normalized.translationY = split.translateY;
  1077. }
  1078. for (name in batchedChanges) {
  1079. val = batchedChanges[name];
  1080. if (typeof val === 'undefined') {
  1081. continue;
  1082. } else if (Ext.isArray(val)) {
  1083. if (name in aliases) {
  1084. name = aliases[name];
  1085. }
  1086. if (name in processors) {
  1087. normalized[name] = [];
  1088. for (i = 0 , ln = val.length; i < ln; i++) {
  1089. subVal = processors[name].call(this, val[i]);
  1090. if (typeof subVal !== 'undefined') {
  1091. normalized[name][i] = subVal;
  1092. }
  1093. }
  1094. } else if (keepUnrecognized) {
  1095. normalized[name] = val;
  1096. }
  1097. } else {
  1098. if (name in aliases) {
  1099. name = aliases[name];
  1100. }
  1101. if (name in processors) {
  1102. val = processors[name].call(this, val);
  1103. if (typeof val !== 'undefined') {
  1104. normalized[name] = val;
  1105. }
  1106. } else if (keepUnrecognized) {
  1107. normalized[name] = val;
  1108. }
  1109. }
  1110. }
  1111. return normalized;
  1112. },
  1113. /**
  1114. * Normalizes the changes given via their processors before they are applied as attributes.
  1115. *
  1116. * @param {Object} changes The changes given.
  1117. * @param {Boolean} keepUnrecognized If 'true', unknown attributes will be passed through
  1118. * as normalized values.
  1119. * @return {Object} The normalized values.
  1120. */
  1121. normalize: function(changes, keepUnrecognized) {
  1122. if (!changes) {
  1123. return {};
  1124. }
  1125. // eslint-disable-next-line vars-on-top
  1126. var processors = this.getProcessors(),
  1127. aliases = this.getAliases(),
  1128. translation = changes.translation || changes.translate,
  1129. normalized = {},
  1130. name, val, rotation, scaling, matrix, split;
  1131. if ('rotation' in changes) {
  1132. rotation = changes.rotation;
  1133. } else {
  1134. rotation = ('rotate' in changes) ? changes.rotate : undefined;
  1135. }
  1136. if ('scaling' in changes) {
  1137. scaling = changes.scaling;
  1138. } else {
  1139. scaling = ('scale' in changes) ? changes.scale : undefined;
  1140. }
  1141. if (translation) {
  1142. if ('x' in translation) {
  1143. normalized.translationX = translation.x;
  1144. }
  1145. if ('y' in translation) {
  1146. normalized.translationY = translation.y;
  1147. }
  1148. }
  1149. if (typeof scaling !== 'undefined') {
  1150. if (Ext.isNumber(scaling)) {
  1151. normalized.scalingX = scaling;
  1152. normalized.scalingY = scaling;
  1153. } else {
  1154. if ('x' in scaling) {
  1155. normalized.scalingX = scaling.x;
  1156. }
  1157. if ('y' in scaling) {
  1158. normalized.scalingY = scaling.y;
  1159. }
  1160. if ('centerX' in scaling) {
  1161. normalized.scalingCenterX = scaling.centerX;
  1162. }
  1163. if ('centerY' in scaling) {
  1164. normalized.scalingCenterY = scaling.centerY;
  1165. }
  1166. }
  1167. }
  1168. if (typeof rotation !== 'undefined') {
  1169. if (Ext.isNumber(rotation)) {
  1170. rotation = Ext.draw.Draw.rad(rotation);
  1171. normalized.rotationRads = rotation;
  1172. } else {
  1173. if ('rads' in rotation) {
  1174. normalized.rotationRads = rotation.rads;
  1175. } else if ('degrees' in rotation) {
  1176. normalized.rotationRads = Ext.draw.Draw.rad(rotation.degrees);
  1177. }
  1178. if ('centerX' in rotation) {
  1179. normalized.rotationCenterX = rotation.centerX;
  1180. }
  1181. if ('centerY' in rotation) {
  1182. normalized.rotationCenterY = rotation.centerY;
  1183. }
  1184. }
  1185. }
  1186. if ('matrix' in changes) {
  1187. matrix = Ext.draw.Matrix.create(changes.matrix);
  1188. split = matrix.split();
  1189. // This will NOT update the transformation matrix of a sprite
  1190. // with the given elements. It will attempt to extract the
  1191. // individual transformation attributes from the transformation matrix
  1192. // elements provided. Then the extracted attributes will be used by
  1193. // the sprite's 'applyTransformations' method to calculate
  1194. // the transformation matrix of the sprite.
  1195. // It's not possible to recover all the information from the given
  1196. // transformation matrix elements. Shearing and centers of rotation
  1197. // and scaling are not recovered.
  1198. // Ideally, this should work like sprite.transform([elements], true),
  1199. // i.e. update the transformation matrix of a sprite directly,
  1200. // without attempting to update sprite's transformation attributes.
  1201. // But we are not changing the behavior (just yet) for compatibility
  1202. // reasons.
  1203. normalized.matrix = matrix;
  1204. normalized.rotationRads = split.rotation;
  1205. normalized.rotationCenterX = 0;
  1206. normalized.rotationCenterY = 0;
  1207. normalized.scalingX = split.scaleX;
  1208. normalized.scalingY = split.scaleY;
  1209. normalized.scalingCenterX = 0;
  1210. normalized.scalingCenterY = 0;
  1211. normalized.translationX = split.translateX;
  1212. normalized.translationY = split.translateY;
  1213. }
  1214. for (name in changes) {
  1215. val = changes[name];
  1216. if (typeof val === 'undefined') {
  1217. continue;
  1218. }
  1219. if (name in aliases) {
  1220. name = aliases[name];
  1221. }
  1222. if (name in processors) {
  1223. val = processors[name].call(this, val);
  1224. if (typeof val !== 'undefined') {
  1225. normalized[name] = val;
  1226. }
  1227. } else if (keepUnrecognized) {
  1228. normalized[name] = val;
  1229. }
  1230. }
  1231. return normalized;
  1232. },
  1233. setBypassingNormalization: function(attr, modifierStack, changes) {
  1234. return modifierStack.pushDown(attr, changes);
  1235. },
  1236. set: function(attr, modifierStack, changes) {
  1237. changes = this.normalize(changes);
  1238. return this.setBypassingNormalization(attr, modifierStack, changes);
  1239. }
  1240. });
  1241. /**
  1242. * Ext.draw.Matix is a utility class used to calculate
  1243. * [affine transformation](http://en.wikipedia.org/wiki/Affine_transformation) matrix.
  1244. * The matrix class is used to apply transformations to existing
  1245. * {@link Ext.draw.sprite.Sprite sprites} using a number of convenience transform
  1246. * methods.
  1247. *
  1248. * Transformations configured directly on a sprite are processed in the following order:
  1249. * scaling, rotation, and translation. The matrix class offers additional flexibility.
  1250. * Once a sprite is created, you can use the matrix class's transform methods as many
  1251. * times as needed and in any order you choose.
  1252. *
  1253. * To demonstrate, we'll start with a simple {@link Ext.draw.sprite.Rect rect} sprite
  1254. * with the intent of rotating it 180 degrees with the bottom right corner being the
  1255. * center of rotation. To begin, let's look at the initial, untransformed sprite:
  1256. *
  1257. * @example
  1258. * var drawContainer = new Ext.draw.Container({
  1259. * renderTo: Ext.getBody(),
  1260. * width: 380,
  1261. * height: 380,
  1262. * sprites: [{
  1263. * type: 'rect',
  1264. * width: 100,
  1265. * height: 100,
  1266. * fillStyle: 'red'
  1267. * }]
  1268. * });
  1269. *
  1270. * Next, we'll use the {@link #rotate} and {@link #translate} methods from our matrix
  1271. * class to position the rect sprite.
  1272. *
  1273. * @example
  1274. * var drawContainer = new Ext.draw.Container({
  1275. * renderTo: Ext.getBody(),
  1276. * width: 380,
  1277. * height: 380,
  1278. * sprites: [{
  1279. * type: 'rect',
  1280. * width: 100,
  1281. * height: 100,
  1282. * fillStyle: 'red'
  1283. * }]
  1284. * });
  1285. *
  1286. * var main = drawContainer.getSurface();
  1287. * var rect = main.getItems()[0];
  1288. *
  1289. * var m = new Ext.draw.Matrix().translate(100, 100).
  1290. * rotate(Math.PI).
  1291. * translate(-100, - 100);
  1292. *
  1293. * rect.setTransform(m);
  1294. * main.renderFrame();
  1295. *
  1296. * In the previous example we perform the following steps in order to achieve our
  1297. * desired rotated output:
  1298. *
  1299. * - translate the rect to the right and down by 100
  1300. * - rotate by 180 degrees
  1301. * - translate the rect to the right and down by 100
  1302. *
  1303. * **Note:** A couple of things to note at this stage; 1) the rotation center point is
  1304. * the upper left corner of the sprite by default and 2) with transformations, the
  1305. * sprite itself isn't transformed, but rather the entire coordinate plane of the sprite
  1306. * is transformed. The coordinate plane itself is translated by 100 and then rotated
  1307. * 180 degrees. And that is why in the third step we translate the sprite using
  1308. * negative values. Translating by -100 in the third step results in the sprite
  1309. * visually moving to the right and down within the draw container.
  1310. *
  1311. * Fortunately there is a shortcut we can apply using two optional params of the rotate
  1312. * method allowing us to specify the center point of rotation:
  1313. *
  1314. * @example
  1315. * var drawContainer = new Ext.draw.Container({
  1316. * renderTo: Ext.getBody(),
  1317. * width: 380,
  1318. * height: 380,
  1319. * sprites: [{
  1320. * type: 'rect',
  1321. * width: 100,
  1322. * height: 100,
  1323. * fillStyle: 'red'
  1324. * }]
  1325. * });
  1326. *
  1327. * var main = drawContainer.getSurface();
  1328. * var rect = main.getItems()[0];
  1329. *
  1330. * var m = new Ext.draw.Matrix().rotate(Math.PI, 100, 100);
  1331. *
  1332. * rect.setTransform(m);
  1333. * main.renderFrame();
  1334. *
  1335. *
  1336. * This class is compatible with
  1337. * [SVGMatrix](http://www.w3.org/TR/SVG11/coords.html#InterfaceSVGMatrix) except:
  1338. *
  1339. * 1. Ext.draw.Matrix is not read only
  1340. * 2. Using Number as its values rather than floats
  1341. *
  1342. * Using this class helps to reduce the severe numeric
  1343. * [problem with HTML Canvas and SVG transformation](http://stackoverflow.com/questions/8784405/large-numbers-in-html-canvas-translate-result-in-strange-behavior)
  1344. *
  1345. * Additionally, there's no way to get the current transformation matrix
  1346. * [in Canvas](http://stackoverflow.com/questions/7395813/html5-canvas-get-transform-matrix).
  1347. */
  1348. Ext.define('Ext.draw.Matrix', {
  1349. isMatrix: true,
  1350. statics: {
  1351. /**
  1352. * @static
  1353. * Return the affine matrix that transform two points (x0, y0) and (x1, y1) to (x0p, y0p)
  1354. * and (x1p, y1p)
  1355. * @param {Number} x0
  1356. * @param {Number} y0
  1357. * @param {Number} x1
  1358. * @param {Number} y1
  1359. * @param {Number} x0p
  1360. * @param {Number} y0p
  1361. * @param {Number} x1p
  1362. * @param {Number} y1p
  1363. */
  1364. createAffineMatrixFromTwoPair: function(x0, y0, x1, y1, x0p, y0p, x1p, y1p) {
  1365. var dx = x1 - x0,
  1366. dy = y1 - y0,
  1367. dxp = x1p - x0p,
  1368. dyp = y1p - y0p,
  1369. r = 1 / (dx * dx + dy * dy),
  1370. a = dx * dxp + dy * dyp,
  1371. b = dxp * dy - dx * dyp,
  1372. c = -a * x0 - b * y0,
  1373. f = b * x0 - a * y0;
  1374. return new this(a * r, -b * r, b * r, a * r, c * r + x0p, f * r + y0p);
  1375. },
  1376. /**
  1377. * @static
  1378. * Return the affine matrix that transform two points (x0, y0) and (x1, y1) to (x0p, y0p)
  1379. * and (x1p, y1p)
  1380. * @param {Number} x0
  1381. * @param {Number} y0
  1382. * @param {Number} x1
  1383. * @param {Number} y1
  1384. * @param {Number} x0p
  1385. * @param {Number} y0p
  1386. * @param {Number} x1p
  1387. * @param {Number} y1p
  1388. */
  1389. createPanZoomFromTwoPair: function(x0, y0, x1, y1, x0p, y0p, x1p, y1p) {
  1390. if (arguments.length === 2) {
  1391. return this.createPanZoomFromTwoPair.apply(this, x0.concat(y0));
  1392. }
  1393. // eslint-disable-next-line vars-on-top
  1394. var dx = x1 - x0,
  1395. dy = y1 - y0,
  1396. cx = (x0 + x1) * 0.5,
  1397. cy = (y0 + y1) * 0.5,
  1398. dxp = x1p - x0p,
  1399. dyp = y1p - y0p,
  1400. cxp = (x0p + x1p) * 0.5,
  1401. cyp = (y0p + y1p) * 0.5,
  1402. r = dx * dx + dy * dy,
  1403. rp = dxp * dxp + dyp * dyp,
  1404. scale = Math.sqrt(rp / r);
  1405. return new this(scale, 0, 0, scale, cxp - scale * cx, cyp - scale * cy);
  1406. },
  1407. /**
  1408. * @method
  1409. * @static
  1410. * Create a flyweight to wrap the given array.
  1411. * The flyweight will directly refer the object and the elements can be changed
  1412. * by other methods.
  1413. *
  1414. * Do not hold the instance of flyweight matrix.
  1415. *
  1416. * @param {Array} elements
  1417. * @return {Ext.draw.Matrix}
  1418. */
  1419. fly: (function() {
  1420. var flyMatrix = null,
  1421. simplefly = function(elements) {
  1422. flyMatrix.elements = elements;
  1423. return flyMatrix;
  1424. };
  1425. return function(elements) {
  1426. if (!flyMatrix) {
  1427. flyMatrix = new Ext.draw.Matrix();
  1428. }
  1429. flyMatrix.elements = elements;
  1430. Ext.draw.Matrix.fly = simplefly;
  1431. return flyMatrix;
  1432. };
  1433. })(),
  1434. /**
  1435. * @static
  1436. * Create a matrix from `mat`. If `mat` is already a matrix, returns it.
  1437. * @param {Mixed} mat
  1438. * @return {Ext.draw.Matrix}
  1439. */
  1440. create: function(mat) {
  1441. if (mat instanceof this) {
  1442. return mat;
  1443. }
  1444. return new this(mat);
  1445. }
  1446. },
  1447. /**
  1448. * Create an affine transform matrix.
  1449. *
  1450. * @param {Number} xx Coefficient from x to x
  1451. * @param {Number} xy Coefficient from x to y
  1452. * @param {Number} yx Coefficient from y to x
  1453. * @param {Number} yy Coefficient from y to y
  1454. * @param {Number} dx Offset of x
  1455. * @param {Number} dy Offset of y
  1456. */
  1457. constructor: function(xx, xy, yx, yy, dx, dy) {
  1458. if (xx && xx.length === 6) {
  1459. this.elements = xx.slice();
  1460. } else if (xx !== undefined) {
  1461. this.elements = [
  1462. xx,
  1463. xy,
  1464. yx,
  1465. yy,
  1466. dx,
  1467. dy
  1468. ];
  1469. } else {
  1470. this.elements = [
  1471. 1,
  1472. 0,
  1473. 0,
  1474. 1,
  1475. 0,
  1476. 0
  1477. ];
  1478. }
  1479. },
  1480. /**
  1481. * Prepend a matrix onto the current.
  1482. *
  1483. * __Note:__ The given transform will come after the current one.
  1484. *
  1485. * @param {Number} xx Coefficient from x to x.
  1486. * @param {Number} xy Coefficient from x to y.
  1487. * @param {Number} yx Coefficient from y to x.
  1488. * @param {Number} yy Coefficient from y to y.
  1489. * @param {Number} dx Offset of x.
  1490. * @param {Number} dy Offset of y.
  1491. * @return {Ext.draw.Matrix} this
  1492. */
  1493. prepend: function(xx, xy, yx, yy, dx, dy) {
  1494. var elements = this.elements,
  1495. xx0 = elements[0],
  1496. xy0 = elements[1],
  1497. yx0 = elements[2],
  1498. yy0 = elements[3],
  1499. dx0 = elements[4],
  1500. dy0 = elements[5];
  1501. elements[0] = xx * xx0 + yx * xy0;
  1502. elements[1] = xy * xx0 + yy * xy0;
  1503. elements[2] = xx * yx0 + yx * yy0;
  1504. elements[3] = xy * yx0 + yy * yy0;
  1505. elements[4] = xx * dx0 + yx * dy0 + dx;
  1506. elements[5] = xy * dx0 + yy * dy0 + dy;
  1507. return this;
  1508. },
  1509. /**
  1510. * Prepend a matrix onto the current.
  1511. *
  1512. * __Note:__ The given transform will come after the current one.
  1513. * @param {Ext.draw.Matrix} matrix
  1514. * @return {Ext.draw.Matrix} this
  1515. */
  1516. prependMatrix: function(matrix) {
  1517. return this.prepend.apply(this, matrix.elements);
  1518. },
  1519. /**
  1520. * Postpend a matrix onto the current.
  1521. *
  1522. * __Note:__ The given transform will come before the current one.
  1523. *
  1524. * @param {Number} xx Coefficient from x to x.
  1525. * @param {Number} xy Coefficient from x to y.
  1526. * @param {Number} yx Coefficient from y to x.
  1527. * @param {Number} yy Coefficient from y to y.
  1528. * @param {Number} dx Offset of x.
  1529. * @param {Number} dy Offset of y.
  1530. * @return {Ext.draw.Matrix} this
  1531. */
  1532. append: function(xx, xy, yx, yy, dx, dy) {
  1533. var elements = this.elements,
  1534. xx0 = elements[0],
  1535. xy0 = elements[1],
  1536. yx0 = elements[2],
  1537. yy0 = elements[3],
  1538. dx0 = elements[4],
  1539. dy0 = elements[5];
  1540. elements[0] = xx * xx0 + xy * yx0;
  1541. elements[1] = xx * xy0 + xy * yy0;
  1542. elements[2] = yx * xx0 + yy * yx0;
  1543. elements[3] = yx * xy0 + yy * yy0;
  1544. elements[4] = dx * xx0 + dy * yx0 + dx0;
  1545. elements[5] = dx * xy0 + dy * yy0 + dy0;
  1546. return this;
  1547. },
  1548. /**
  1549. * Postpend a matrix onto the current.
  1550. *
  1551. * __Note:__ The given transform will come before the current one.
  1552. *
  1553. * @param {Ext.draw.Matrix} matrix
  1554. * @return {Ext.draw.Matrix} this
  1555. */
  1556. appendMatrix: function(matrix) {
  1557. return this.append.apply(this, matrix.elements);
  1558. },
  1559. /**
  1560. * Set the elements of a Matrix
  1561. * @param {Number} xx
  1562. * @param {Number} xy
  1563. * @param {Number} yx
  1564. * @param {Number} yy
  1565. * @param {Number} dx
  1566. * @param {Number} dy
  1567. * @return {Ext.draw.Matrix} this
  1568. */
  1569. set: function(xx, xy, yx, yy, dx, dy) {
  1570. var elements = this.elements;
  1571. elements[0] = xx;
  1572. elements[1] = xy;
  1573. elements[2] = yx;
  1574. elements[3] = yy;
  1575. elements[4] = dx;
  1576. elements[5] = dy;
  1577. return this;
  1578. },
  1579. /**
  1580. * Return a new matrix represents the opposite transformation of the current one.
  1581. *
  1582. * @param {Ext.draw.Matrix} [target] A target matrix. If present, it will receive
  1583. * the result of inversion to avoid creating a new object.
  1584. *
  1585. * @return {Ext.draw.Matrix}
  1586. */
  1587. inverse: function(target) {
  1588. var elements = this.elements,
  1589. a = elements[0],
  1590. b = elements[1],
  1591. c = elements[2],
  1592. d = elements[3],
  1593. e = elements[4],
  1594. f = elements[5],
  1595. rDim = 1 / (a * d - b * c);
  1596. a *= rDim;
  1597. b *= rDim;
  1598. c *= rDim;
  1599. d *= rDim;
  1600. if (target) {
  1601. target.set(d, -b, -c, a, c * f - d * e, b * e - a * f);
  1602. return target;
  1603. } else {
  1604. return new Ext.draw.Matrix(d, -b, -c, a, c * f - d * e, b * e - a * f);
  1605. }
  1606. },
  1607. /**
  1608. * Translate the matrix.
  1609. *
  1610. * @param {Number} x
  1611. * @param {Number} y
  1612. * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
  1613. * @return {Ext.draw.Matrix} this
  1614. */
  1615. translate: function(x, y, prepend) {
  1616. if (prepend) {
  1617. return this.prepend(1, 0, 0, 1, x, y);
  1618. } else {
  1619. return this.append(1, 0, 0, 1, x, y);
  1620. }
  1621. },
  1622. /**
  1623. * Scale the matrix.
  1624. *
  1625. * @param {Number} sx
  1626. * @param {Number} sy
  1627. * @param {Number} scx
  1628. * @param {Number} scy
  1629. * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
  1630. * @return {Ext.draw.Matrix} this
  1631. */
  1632. scale: function(sx, sy, scx, scy, prepend) {
  1633. var me = this;
  1634. // null or undefined
  1635. if (sy == null) {
  1636. sy = sx;
  1637. }
  1638. if (scx === undefined) {
  1639. scx = 0;
  1640. }
  1641. if (scy === undefined) {
  1642. scy = 0;
  1643. }
  1644. if (prepend) {
  1645. return me.prepend(sx, 0, 0, sy, scx - scx * sx, scy - scy * sy);
  1646. } else {
  1647. return me.append(sx, 0, 0, sy, scx - scx * sx, scy - scy * sy);
  1648. }
  1649. },
  1650. /**
  1651. * Rotate the matrix.
  1652. *
  1653. * @param {Number} angle Radians to rotate
  1654. * @param {Number|null} rcx Center of rotation.
  1655. * @param {Number|null} rcy Center of rotation.
  1656. * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
  1657. * @return {Ext.draw.Matrix} this
  1658. */
  1659. rotate: function(angle, rcx, rcy, prepend) {
  1660. var me = this,
  1661. cos = Math.cos(angle),
  1662. sin = Math.sin(angle);
  1663. rcx = rcx || 0;
  1664. rcy = rcy || 0;
  1665. if (prepend) {
  1666. return me.prepend(cos, sin, -sin, cos, rcx - cos * rcx + rcy * sin, rcy - cos * rcy - rcx * sin);
  1667. } else {
  1668. return me.append(cos, sin, -sin, cos, rcx - cos * rcx + rcy * sin, rcy - cos * rcy - rcx * sin);
  1669. }
  1670. },
  1671. /**
  1672. * Rotate the matrix by the angle of a vector.
  1673. *
  1674. * @param {Number} x
  1675. * @param {Number} y
  1676. * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
  1677. * @return {Ext.draw.Matrix} this
  1678. */
  1679. rotateFromVector: function(x, y, prepend) {
  1680. var me = this,
  1681. d = Math.sqrt(x * x + y * y),
  1682. cos = x / d,
  1683. sin = y / d;
  1684. if (prepend) {
  1685. return me.prepend(cos, sin, -sin, cos, 0, 0);
  1686. } else {
  1687. return me.append(cos, sin, -sin, cos, 0, 0);
  1688. }
  1689. },
  1690. /**
  1691. * Clone this matrix.
  1692. * @return {Ext.draw.Matrix}
  1693. */
  1694. clone: function() {
  1695. return new Ext.draw.Matrix(this.elements);
  1696. },
  1697. /**
  1698. * Horizontally flip the matrix
  1699. * @return {Ext.draw.Matrix} this
  1700. */
  1701. flipX: function() {
  1702. return this.append(-1, 0, 0, 1, 0, 0);
  1703. },
  1704. /**
  1705. * Vertically flip the matrix
  1706. * @return {Ext.draw.Matrix} this
  1707. */
  1708. flipY: function() {
  1709. return this.append(1, 0, 0, -1, 0, 0);
  1710. },
  1711. /**
  1712. * Skew the matrix
  1713. * @param {Number} angle
  1714. * @return {Ext.draw.Matrix} this
  1715. */
  1716. skewX: function(angle) {
  1717. return this.append(1, 0, Math.tan(angle), 1, 0, 0);
  1718. },
  1719. /**
  1720. * Skew the matrix
  1721. * @param {Number} angle
  1722. * @return {Ext.draw.Matrix} this
  1723. */
  1724. skewY: function(angle) {
  1725. return this.append(1, Math.tan(angle), 0, 1, 0, 0);
  1726. },
  1727. /**
  1728. * Shear the matrix along the x-axis.
  1729. * @param factor The horizontal shear factor.
  1730. * @return {Ext.draw.Matrix} this
  1731. */
  1732. shearX: function(factor) {
  1733. return this.append(1, 0, factor, 1, 0, 0);
  1734. },
  1735. /**
  1736. * Shear the matrix along the y-axis.
  1737. * @param factor The vertical shear factor.
  1738. * @return {Ext.draw.Matrix} this
  1739. */
  1740. shearY: function(factor) {
  1741. return this.append(1, factor, 0, 1, 0, 0);
  1742. },
  1743. /**
  1744. * Reset the matrix to identical.
  1745. * @return {Ext.draw.Matrix} this
  1746. */
  1747. reset: function() {
  1748. return this.set(1, 0, 0, 1, 0, 0);
  1749. },
  1750. /* eslint-disable max-len */
  1751. /**
  1752. * @private
  1753. * Split Matrix to `{{devicePixelRatio,c,0},{b,devicePixelRatio,0},{0,0,1}}.{{xx,0,dx},{0,yy,dy},{0,0,1}}`
  1754. * @return {Object} Object with b,c,d=devicePixelRatio,xx,yy,dx,dy
  1755. */
  1756. precisionCompensate: function(devicePixelRatio, comp) {
  1757. /* eslint-enable max-len */
  1758. var elements = this.elements,
  1759. x2x = elements[0],
  1760. x2y = elements[1],
  1761. y2x = elements[2],
  1762. y2y = elements[3],
  1763. newDx = elements[4],
  1764. newDy = elements[5],
  1765. r = x2y * y2x - x2x * y2y;
  1766. comp.b = devicePixelRatio * x2y / x2x;
  1767. comp.c = devicePixelRatio * y2x / y2y;
  1768. comp.d = devicePixelRatio;
  1769. comp.xx = x2x / devicePixelRatio;
  1770. comp.yy = y2y / devicePixelRatio;
  1771. comp.dx = (newDy * x2x * y2x - newDx * x2x * y2y) / r / devicePixelRatio;
  1772. comp.dy = (newDx * x2y * y2y - newDy * x2x * y2y) / r / devicePixelRatio;
  1773. },
  1774. /**
  1775. * @private
  1776. * Split Matrix to `{{1,c,0},{b,d,0},{0,0,1}}.{{xx,0,dx},{0,xx,dy},{0,0,1}}`
  1777. * @return {Object} Object with b,c,d,xx,yy=xx,dx,dy
  1778. */
  1779. precisionCompensateRect: function(devicePixelRatio, comp) {
  1780. var elements = this.elements,
  1781. x2x = elements[0],
  1782. x2y = elements[1],
  1783. y2x = elements[2],
  1784. y2y = elements[3],
  1785. newDx = elements[4],
  1786. newDy = elements[5],
  1787. yxOnXx = y2x / x2x;
  1788. comp.b = devicePixelRatio * x2y / x2x;
  1789. comp.c = devicePixelRatio * yxOnXx;
  1790. comp.d = devicePixelRatio * y2y / x2x;
  1791. comp.xx = x2x / devicePixelRatio;
  1792. comp.yy = x2x / devicePixelRatio;
  1793. comp.dx = (newDy * y2x - newDx * y2y) / (x2y * yxOnXx - y2y) / devicePixelRatio;
  1794. comp.dy = -(newDy * x2x - newDx * x2y) / (x2y * yxOnXx - y2y) / devicePixelRatio;
  1795. },
  1796. /**
  1797. * Transform point returning the x component of the result.
  1798. * @param {Number} x
  1799. * @param {Number} y
  1800. * @return {Number} x component of the result.
  1801. */
  1802. x: function(x, y) {
  1803. var elements = this.elements;
  1804. return x * elements[0] + y * elements[2] + elements[4];
  1805. },
  1806. /**
  1807. * Transform point returning the y component of the result.
  1808. * @param {Number} x
  1809. * @param {Number} y
  1810. * @return {Number} y component of the result.
  1811. */
  1812. y: function(x, y) {
  1813. var elements = this.elements;
  1814. return x * elements[1] + y * elements[3] + elements[5];
  1815. },
  1816. /**
  1817. * @private
  1818. * @param {Number} i
  1819. * @param {Number} j
  1820. * @return {String}
  1821. */
  1822. get: function(i, j) {
  1823. return +this.elements[i + j * 2].toFixed(4);
  1824. },
  1825. /**
  1826. * Transform a point to a new array.
  1827. * @param {Array} point
  1828. * @return {Array}
  1829. */
  1830. transformPoint: function(point) {
  1831. var elements = this.elements,
  1832. x, y;
  1833. if (point.isPoint) {
  1834. x = point.x;
  1835. y = point.y;
  1836. } else {
  1837. x = point[0];
  1838. y = point[1];
  1839. }
  1840. return [
  1841. x * elements[0] + y * elements[2] + elements[4],
  1842. x * elements[1] + y * elements[3] + elements[5]
  1843. ];
  1844. },
  1845. /**
  1846. * @param {Object} bbox Given as `{x: Number, y: Number, width: Number, height: Number}`.
  1847. * @param {Number} [radius]
  1848. * @param {Object} [target] Optional target object to recieve the result.
  1849. * Recommended to use it for better gc.
  1850. *
  1851. * @return {Object} Object with x, y, width and height.
  1852. */
  1853. transformBBox: function(bbox, radius, target) {
  1854. var elements = this.elements,
  1855. l = bbox.x,
  1856. t = bbox.y,
  1857. w0 = bbox.width * 0.5,
  1858. h0 = bbox.height * 0.5,
  1859. xx = elements[0],
  1860. xy = elements[1],
  1861. yx = elements[2],
  1862. yy = elements[3],
  1863. cx = l + w0,
  1864. cy = t + h0,
  1865. w, h, scales;
  1866. if (radius) {
  1867. w0 -= radius;
  1868. h0 -= radius;
  1869. scales = [
  1870. Math.sqrt(elements[0] * elements[0] + elements[2] * elements[2]),
  1871. Math.sqrt(elements[1] * elements[1] + elements[3] * elements[3])
  1872. ];
  1873. w = Math.abs(w0 * xx) + Math.abs(h0 * yx) + Math.abs(scales[0] * radius);
  1874. h = Math.abs(w0 * xy) + Math.abs(h0 * yy) + Math.abs(scales[1] * radius);
  1875. } else {
  1876. w = Math.abs(w0 * xx) + Math.abs(h0 * yx);
  1877. h = Math.abs(w0 * xy) + Math.abs(h0 * yy);
  1878. }
  1879. if (!target) {
  1880. target = {};
  1881. }
  1882. target.x = cx * xx + cy * yx + elements[4] - w;
  1883. target.y = cx * xy + cy * yy + elements[5] - h;
  1884. target.width = w + w;
  1885. target.height = h + h;
  1886. return target;
  1887. },
  1888. /**
  1889. * Transform a list for points.
  1890. *
  1891. * __Note:__ will change the original list but not points inside it.
  1892. * @param {Array} list
  1893. * @return {Array} list
  1894. */
  1895. transformList: function(list) {
  1896. var elements = this.elements,
  1897. xx = elements[0],
  1898. yx = elements[2],
  1899. dx = elements[4],
  1900. xy = elements[1],
  1901. yy = elements[3],
  1902. dy = elements[5],
  1903. ln = list.length,
  1904. p, i;
  1905. for (i = 0; i < ln; i++) {
  1906. p = list[i];
  1907. list[i] = [
  1908. p[0] * xx + p[1] * yx + dx,
  1909. p[0] * xy + p[1] * yy + dy
  1910. ];
  1911. }
  1912. return list;
  1913. },
  1914. /**
  1915. * Determines whether this matrix is an identity matrix (no transform).
  1916. * @return {Boolean}
  1917. */
  1918. isIdentity: function() {
  1919. var elements = this.elements;
  1920. return elements[0] === 1 && elements[1] === 0 && elements[2] === 0 && elements[3] === 1 && elements[4] === 0 && elements[5] === 0;
  1921. },
  1922. /**
  1923. * Determines if this matrix has the same values as another matrix.
  1924. * @param {Ext.draw.Matrix} matrix A maxtrix or array of its elements.
  1925. * @return {Boolean}
  1926. */
  1927. isEqual: function(matrix) {
  1928. var elements = matrix && matrix.isMatrix ? matrix.elements : matrix,
  1929. myElements = this.elements;
  1930. 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];
  1931. },
  1932. /**
  1933. * @deprecated 6.0.1 This method is deprecated.
  1934. * Determines if this matrix has the same values as another matrix.
  1935. * @param {Ext.draw.Matrix} matrix
  1936. * @return {Boolean}
  1937. */
  1938. equals: function(matrix) {
  1939. return this.isEqual(matrix);
  1940. },
  1941. /**
  1942. * Create an array of elements by horizontal order (xx,yx,dx,yx,yy,dy).
  1943. * @return {Array}
  1944. */
  1945. toArray: function() {
  1946. var elements = this.elements;
  1947. return [
  1948. elements[0],
  1949. elements[2],
  1950. elements[4],
  1951. elements[1],
  1952. elements[3],
  1953. elements[5]
  1954. ];
  1955. },
  1956. /**
  1957. * Create an array of elements by vertical order (xx,xy,yx,yy,dx,dy).
  1958. * @return {Array|String}
  1959. */
  1960. toVerticalArray: function() {
  1961. return this.elements.slice();
  1962. },
  1963. /**
  1964. * Get an array of elements.
  1965. * The numbers are rounded to keep only 4 decimals.
  1966. * @return {Array}
  1967. */
  1968. toString: function() {
  1969. var me = this;
  1970. return [
  1971. me.get(0, 0),
  1972. me.get(0, 1),
  1973. me.get(1, 0),
  1974. me.get(1, 1),
  1975. me.get(2, 0),
  1976. me.get(2, 1)
  1977. ].join(',');
  1978. },
  1979. /**
  1980. * Apply the matrix to a drawing context.
  1981. * @param {Object} ctx
  1982. * @return {Ext.draw.Matrix} this
  1983. */
  1984. toContext: function(ctx) {
  1985. ctx.transform.apply(ctx, this.elements);
  1986. return this;
  1987. },
  1988. /**
  1989. * Return a string that can be used as transform attribute in SVG.
  1990. * @return {String}
  1991. */
  1992. toSvg: function() {
  1993. var elements = this.elements;
  1994. // The reason why we cannot use `.join` is the `1e5` form is not accepted in svg.
  1995. 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) + ")";
  1996. },
  1997. /**
  1998. * Get the x scale of the matrix.
  1999. * @return {Number}
  2000. */
  2001. getScaleX: function() {
  2002. var elements = this.elements;
  2003. return Math.sqrt(elements[0] * elements[0] + elements[2] * elements[2]);
  2004. },
  2005. /**
  2006. * Get the y scale of the matrix.
  2007. * @return {Number}
  2008. */
  2009. getScaleY: function() {
  2010. var elements = this.elements;
  2011. return Math.sqrt(elements[1] * elements[1] + elements[3] * elements[3]);
  2012. },
  2013. /**
  2014. * Get x-to-x component of the matrix
  2015. * @return {Number}
  2016. */
  2017. getXX: function() {
  2018. return this.elements[0];
  2019. },
  2020. /**
  2021. * Get x-to-y component of the matrix.
  2022. * @return {Number}
  2023. */
  2024. getXY: function() {
  2025. return this.elements[1];
  2026. },
  2027. /**
  2028. * Get y-to-x component of the matrix.
  2029. * @return {Number}
  2030. */
  2031. getYX: function() {
  2032. return this.elements[2];
  2033. },
  2034. /**
  2035. * Get y-to-y component of the matrix.
  2036. * @return {Number}
  2037. */
  2038. getYY: function() {
  2039. return this.elements[3];
  2040. },
  2041. /**
  2042. * Get offset x component of the matrix.
  2043. * @return {Number}
  2044. */
  2045. getDX: function() {
  2046. return this.elements[4];
  2047. },
  2048. /**
  2049. * Get offset y component of the matrix.
  2050. * @return {Number}
  2051. */
  2052. getDY: function() {
  2053. return this.elements[5];
  2054. },
  2055. /**
  2056. * Splits this transformation matrix into Scale, Rotate, Translate components,
  2057. * assuming it was produced by applying transformations in that order.
  2058. * @return {Object}
  2059. */
  2060. split: function() {
  2061. var el = this.elements,
  2062. xx = el[0],
  2063. xy = el[1],
  2064. yy = el[3],
  2065. out = {
  2066. translateX: el[4],
  2067. translateY: el[5]
  2068. };
  2069. out.rotate = out.rotation = Math.atan2(xy, xx);
  2070. out.scaleX = xx / Math.cos(out.rotate);
  2071. out.scaleY = yy / xx * out.scaleX;
  2072. return out;
  2073. }
  2074. }, function() {
  2075. function registerName(properties, name, i) {
  2076. properties[name] = {
  2077. get: function() {
  2078. return this.elements[i];
  2079. },
  2080. set: function(val) {
  2081. this.elements[i] = val;
  2082. }
  2083. };
  2084. }
  2085. // Compatibility with SVGMatrix.
  2086. if (Object.defineProperties) {
  2087. var properties = {};
  2088. // eslint-disable-line vars-on-top
  2089. /**
  2090. * @property {Number} a Get x-to-x component of the matrix. Avoid using it for performance
  2091. * consideration.
  2092. * Use {@link #getXX} instead.
  2093. */
  2094. registerName(properties, 'a', 0);
  2095. registerName(properties, 'b', 1);
  2096. registerName(properties, 'c', 2);
  2097. registerName(properties, 'd', 3);
  2098. registerName(properties, 'e', 4);
  2099. registerName(properties, 'f', 5);
  2100. Object.defineProperties(this.prototype, properties);
  2101. }
  2102. /**
  2103. * Performs matrix multiplication. This matrix is post-multiplied by another matrix.
  2104. *
  2105. * __Note:__ The given transform will come before the current one.
  2106. *
  2107. * @method
  2108. * @param {Ext.draw.Matrix} matrix
  2109. * @return {Ext.draw.Matrix} this
  2110. */
  2111. this.prototype.multiply = this.prototype.appendMatrix;
  2112. });
  2113. /**
  2114. * @class Ext.draw.modifier.Modifier
  2115. *
  2116. * Each sprite has a stack of modifiers. The resulting attributes of sprite is
  2117. * the content of the stack top. When setting attributes to a sprite,
  2118. * changes will be pushed-down though the stack of modifiers and pop-back the
  2119. * additive changes; When modifier is triggered to change the attribute of a
  2120. * sprite, it will pop-up the changes to the top.
  2121. */
  2122. Ext.define('Ext.draw.modifier.Modifier', {
  2123. isModifier: true,
  2124. mixins: {
  2125. observable: 'Ext.mixin.Observable'
  2126. },
  2127. config: {
  2128. /**
  2129. * @private
  2130. * @cfg {Ext.draw.modifier.Modifier} lower Modifier that receives the push-down changes.
  2131. */
  2132. lower: null,
  2133. /**
  2134. * @private
  2135. * @cfg {Ext.draw.modifier.Modifier} upper Modifier that receives the pop-up changes.
  2136. */
  2137. upper: null,
  2138. /**
  2139. * @cfg {Ext.draw.sprite.Sprite} sprite The sprite to which the modifier belongs.
  2140. */
  2141. sprite: null
  2142. },
  2143. constructor: function(config) {
  2144. this.mixins.observable.constructor.call(this, config);
  2145. },
  2146. updateUpper: function(upper) {
  2147. if (upper) {
  2148. upper.setLower(this);
  2149. }
  2150. },
  2151. updateLower: function(lower) {
  2152. if (lower) {
  2153. lower.setUpper(this);
  2154. }
  2155. },
  2156. /**
  2157. * @private
  2158. * Validate attribute set before use.
  2159. *
  2160. * @param {Object} attr The attribute to be validated. Note that it may be already initialized,
  2161. * so do not override properties that have already been used.
  2162. */
  2163. prepareAttributes: function(attr) {
  2164. if (this._lower) {
  2165. this._lower.prepareAttributes(attr);
  2166. }
  2167. },
  2168. /**
  2169. * @private
  2170. * Invoked when changes need to be popped up to the top.
  2171. * @param {Object} attr The source attributes.
  2172. * @param {Object} changes The changes to be popped up.
  2173. */
  2174. popUp: function(attr, changes) {
  2175. if (this._upper) {
  2176. this._upper.popUp(attr, changes);
  2177. } else {
  2178. Ext.apply(attr, changes);
  2179. }
  2180. },
  2181. /**
  2182. * @private
  2183. *
  2184. * This method will filter out the properties from the `changes` object, if they
  2185. * have the same values as in the `attr` object (sprite's attributes).
  2186. *
  2187. * If the `receiver` object is provided, the attributes with the new values will be
  2188. * copied from the `changes` object to the `receiver` object, and the `changes`
  2189. * object will be left unchanged.
  2190. *
  2191. * The method returns the `receiver` object, if it was provided, or the `changes`
  2192. * object otherwise.
  2193. *
  2194. * The method also handles a special case when a sprite attribute that is meant to be
  2195. * animated was set to a certain value (e.g. 5), that is different from the original
  2196. * value (e.g. 3) of the attribute, and immediately set to another value again, that
  2197. * is the same as the original value (3). In this case, the attribute's current
  2198. * value is still the original value, because the attribute hasn't started animating
  2199. * yet, so a comparison against the current value is not appropriate, and the target
  2200. * value (value at the end of animation, 5) should be used for comparison instead, so
  2201. * that 3 won't be filtered out.
  2202. */
  2203. filterChanges: function(attr, changes, receiver) {
  2204. var targets = attr.targets,
  2205. name, value;
  2206. if (receiver) {
  2207. for (name in changes) {
  2208. value = changes[name];
  2209. if (value !== attr[name] || (targets && value !== targets[name])) {
  2210. receiver[name] = value;
  2211. }
  2212. }
  2213. } else {
  2214. for (name in changes) {
  2215. value = changes[name];
  2216. if (value === attr[name] && (!targets || value === targets[name])) {
  2217. delete changes[name];
  2218. }
  2219. }
  2220. }
  2221. return receiver || changes;
  2222. },
  2223. /**
  2224. * @private
  2225. * Invoked when changes need to be pushed down to the sprite.
  2226. * @param {Object} attr The source attributes.
  2227. * @param {Object} changes The changes to make. This object might be changed unexpectedly
  2228. * inside the method.
  2229. * @return {Mixed}
  2230. */
  2231. pushDown: function(attr, changes) {
  2232. return this._lower ? this._lower.pushDown(attr, changes) : this.filterChanges(attr, changes);
  2233. }
  2234. });
  2235. /**
  2236. * @class Ext.draw.modifier.Target
  2237. * @extends Ext.draw.modifier.Modifier
  2238. *
  2239. * This is the destination (top) modifier that has to be put at
  2240. * the top of the modifier stack.
  2241. *
  2242. * The Target modifier figures out which updaters have to be called
  2243. * for the changed set of attributes and makes the sprite and its instances (if any)
  2244. * call them.
  2245. */
  2246. Ext.define('Ext.draw.modifier.Target', {
  2247. requires: [
  2248. 'Ext.draw.Matrix'
  2249. ],
  2250. extend: 'Ext.draw.modifier.Modifier',
  2251. alias: 'modifier.target',
  2252. statics: {
  2253. /**
  2254. * @private
  2255. */
  2256. uniqueId: 0
  2257. },
  2258. prepareAttributes: function(attr) {
  2259. if (this._lower) {
  2260. this._lower.prepareAttributes(attr);
  2261. }
  2262. attr.attributeId = 'attribute-' + Ext.draw.modifier.Target.uniqueId++;
  2263. if (!attr.hasOwnProperty('canvasAttributes')) {
  2264. attr.bbox = {
  2265. plain: {
  2266. dirty: true
  2267. },
  2268. transform: {
  2269. dirty: true
  2270. }
  2271. };
  2272. attr.dirty = true;
  2273. /*
  2274. Maps updaters that have to be called to the attributes that triggered the update.
  2275. It is basically a reversed `triggers` map (see Ext.draw.sprite.AttributeDefinition),
  2276. but only for those attributes that have changed.
  2277. Pending updaters are called by the Ext.draw.sprite.Sprite.callUpdaters method.
  2278. The 'canvas' updater is a special kind of updater that is not actually a function
  2279. but a flag indicating that the attribute should be applied directly to a canvas
  2280. context.
  2281. */
  2282. attr.pendingUpdaters = {};
  2283. /*
  2284. Holds the attributes that triggered the canvas update (attr.pendingUpdaters.canvas).
  2285. Canvas attributes are applied directly to a canvas context
  2286. by the sprite.useAttributes method.
  2287. */
  2288. attr.canvasAttributes = {};
  2289. attr.matrix = new Ext.draw.Matrix();
  2290. attr.inverseMatrix = new Ext.draw.Matrix();
  2291. }
  2292. },
  2293. /**
  2294. * @private
  2295. * Applies changes to sprite/instance attributes and determines which updaters
  2296. * have to be called as a result of attributes change.
  2297. * @param {Object} attr The source attributes.
  2298. * @param {Object} changes The modifier changes.
  2299. */
  2300. applyChanges: function(attr, changes) {
  2301. Ext.apply(attr, changes);
  2302. // eslint-disable-next-line vars-on-top
  2303. var sprite = this.getSprite(),
  2304. pendingUpdaters = attr.pendingUpdaters,
  2305. triggers = sprite.self.def.getTriggers(),
  2306. updaters, instances, instance, name, hasChanges, canvasAttributes, i, j, ln;
  2307. for (name in changes) {
  2308. hasChanges = true;
  2309. if ((updaters = triggers[name])) {
  2310. sprite.scheduleUpdaters(attr, updaters, [
  2311. name
  2312. ]);
  2313. }
  2314. if (attr.template && changes.removeFromInstance && changes.removeFromInstance[name]) {
  2315. delete attr[name];
  2316. }
  2317. }
  2318. if (!hasChanges) {
  2319. return;
  2320. }
  2321. // This can prevent sub objects to set duplicated attributes to context.
  2322. if (pendingUpdaters.canvas) {
  2323. canvasAttributes = pendingUpdaters.canvas;
  2324. delete pendingUpdaters.canvas;
  2325. for (i = 0 , ln = canvasAttributes.length; i < ln; i++) {
  2326. name = canvasAttributes[i];
  2327. attr.canvasAttributes[name] = attr[name];
  2328. }
  2329. }
  2330. // If the attributes of an instancing sprite template are being modified here,
  2331. // then spread the pending updaters to the instances (template's children).
  2332. if (attr.hasOwnProperty('children')) {
  2333. instances = attr.children;
  2334. for (i = 0 , ln = instances.length; i < ln; i++) {
  2335. instance = instances[i];
  2336. Ext.apply(instance.pendingUpdaters, pendingUpdaters);
  2337. if (canvasAttributes) {
  2338. for (j = 0; j < canvasAttributes.length; j++) {
  2339. name = canvasAttributes[j];
  2340. instance.canvasAttributes[name] = instance[name];
  2341. }
  2342. }
  2343. sprite.callUpdaters(instance);
  2344. }
  2345. }
  2346. sprite.setDirty(true);
  2347. sprite.callUpdaters(attr);
  2348. },
  2349. popUp: function(attr, changes) {
  2350. this.applyChanges(attr, changes);
  2351. },
  2352. pushDown: function(attr, changes) {
  2353. // Modifier chain looks like this:
  2354. // sprite.modifiers.target <---> postFx <---> sprite.modifiers.animation <---> preFx
  2355. // There can be any number of postFx and preFx modifiers, the difference between them
  2356. // is that:
  2357. // `preFx` modifier changes are animated.
  2358. // `postFx` modifier changes are not.
  2359. // preFx modifiers include Highlight (Draw) and Callout (Charts).
  2360. // There are no postFx modifiers at the moment.
  2361. if (this._lower) {
  2362. // Without any postFx modifiers, `lower` is going to be Animation.
  2363. changes = this._lower.pushDown(attr, changes);
  2364. }
  2365. this.applyChanges(attr, changes);
  2366. return changes;
  2367. }
  2368. });
  2369. /**
  2370. * @class Ext.draw.TimingFunctions
  2371. * @singleton
  2372. *
  2373. * Singleton that provides easing functions for use in sprite animations.
  2374. */
  2375. Ext.define('Ext.draw.TimingFunctions', function() {
  2376. var pow = Math.pow,
  2377. sin = Math.sin,
  2378. cos = Math.cos,
  2379. sqrt = Math.sqrt,
  2380. pi = Math.PI,
  2381. poly = [
  2382. 'quad',
  2383. 'cube',
  2384. 'quart',
  2385. 'quint'
  2386. ],
  2387. easings = {
  2388. pow: function(p, x) {
  2389. return pow(p, x || 6);
  2390. },
  2391. expo: function(p) {
  2392. return pow(2, 8 * (p - 1));
  2393. },
  2394. circ: function(p) {
  2395. return 1 - sqrt(1 - p * p);
  2396. },
  2397. sine: function(p) {
  2398. return 1 - sin((1 - p) * pi / 2);
  2399. },
  2400. back: function(p, n) {
  2401. n = n || 1.616;
  2402. return p * p * ((n + 1) * p - n);
  2403. },
  2404. bounce: function(p) {
  2405. var a, b;
  2406. // eslint-disable-next-line no-constant-condition
  2407. for (a = 0 , b = 1; 1; a += b , b /= 2) {
  2408. if (p >= (7 - 4 * a) / 11) {
  2409. return b * b - pow((11 - 6 * a - 11 * p) / 4, 2);
  2410. }
  2411. }
  2412. },
  2413. elastic: function(p, x) {
  2414. return pow(2, 10 * --p) * cos(20 * p * pi * (x || 1) / 3);
  2415. }
  2416. },
  2417. easingsMap = {},
  2418. name, len, i;
  2419. // Create polynomial easing equations.
  2420. function createPoly(times) {
  2421. return function(p) {
  2422. return pow(p, times);
  2423. };
  2424. }
  2425. function addEasing(name, easing) {
  2426. easingsMap[name + 'In'] = function(pos) {
  2427. return easing(pos);
  2428. };
  2429. easingsMap[name + 'Out'] = function(pos) {
  2430. return 1 - easing(1 - pos);
  2431. };
  2432. easingsMap[name + 'InOut'] = function(pos) {
  2433. return (pos <= 0.5) ? easing(2 * pos) / 2 : (2 - easing(2 * (1 - pos))) / 2;
  2434. };
  2435. }
  2436. for (i = 0 , len = poly.length; i < len; ++i) {
  2437. easings[poly[i]] = createPoly(i + 2);
  2438. }
  2439. for (name in easings) {
  2440. addEasing(name, easings[name]);
  2441. }
  2442. // Add linear interpolator.
  2443. easingsMap.linear = Ext.identityFn;
  2444. // Add aliases for quad easings.
  2445. easingsMap.easeIn = easingsMap.quadIn;
  2446. easingsMap.easeOut = easingsMap.quadOut;
  2447. easingsMap.easeInOut = easingsMap.quadInOut;
  2448. return {
  2449. singleton: true,
  2450. easingMap: easingsMap
  2451. };
  2452. }, function(Cls) {
  2453. Ext.apply(Cls, Cls.easingMap);
  2454. });
  2455. /**
  2456. * @class Ext.draw.Animator
  2457. *
  2458. * Singleton class that manages the animation pool.
  2459. */
  2460. Ext.define('Ext.draw.Animator', {
  2461. uses: [
  2462. 'Ext.draw.Draw'
  2463. ],
  2464. singleton: true,
  2465. frameCallbacks: {},
  2466. frameCallbackId: 0,
  2467. scheduled: 0,
  2468. frameStartTimeOffset: Ext.now(),
  2469. animations: [],
  2470. running: false,
  2471. /**
  2472. * Cross platform `animationTime` implementation.
  2473. * @return {Number}
  2474. */
  2475. animationTime: function() {
  2476. return Ext.AnimationQueue.frameStartTime - this.frameStartTimeOffset;
  2477. },
  2478. /**
  2479. * Adds an animated object to the animation pool.
  2480. *
  2481. * @param {Object} animation The animation descriptor to add to the pool.
  2482. */
  2483. add: function(animation) {
  2484. var me = this;
  2485. if (!me.contains(animation)) {
  2486. me.animations.push(animation);
  2487. me.ignite();
  2488. if ('fireEvent' in animation) {
  2489. animation.fireEvent('animationstart', animation);
  2490. }
  2491. }
  2492. },
  2493. /**
  2494. * Removes an animation from the pool.
  2495. * TODO: This is broken when called within `step` method.
  2496. * @param {Object} animation The animation to remove from the pool.
  2497. */
  2498. remove: function(animation) {
  2499. var me = this,
  2500. animations = me.animations,
  2501. i = 0,
  2502. l = animations.length;
  2503. for (; i < l; ++i) {
  2504. if (animations[i] === animation) {
  2505. animations.splice(i, 1);
  2506. if ('fireEvent' in animation) {
  2507. animation.fireEvent('animationend', animation);
  2508. }
  2509. return;
  2510. }
  2511. }
  2512. },
  2513. /**
  2514. * Returns `true` or `false` whether it contains the given animation or not.
  2515. *
  2516. * @param {Object} animation The animation to check for.
  2517. * @return {Boolean}
  2518. */
  2519. contains: function(animation) {
  2520. return Ext.Array.indexOf(this.animations, animation) > -1;
  2521. },
  2522. /**
  2523. * Returns `true` or `false` whether the pool is empty or not.
  2524. * @return {Boolean}
  2525. */
  2526. empty: function() {
  2527. return this.animations.length === 0;
  2528. },
  2529. idle: function() {
  2530. return this.scheduled === 0 && this.animations.length === 0;
  2531. },
  2532. /**
  2533. * Given a frame time it will filter out finished animations from the pool.
  2534. *
  2535. * @param {Number} frameTime The frame's start time, in milliseconds.
  2536. */
  2537. step: function(frameTime) {
  2538. var me = this,
  2539. animations = me.animations,
  2540. animation,
  2541. i = 0,
  2542. ln = animations.length;
  2543. for (; i < ln; i++) {
  2544. animation = animations[i];
  2545. animation.step(frameTime);
  2546. if (!animation.animating) {
  2547. animations.splice(i, 1);
  2548. i--;
  2549. ln--;
  2550. if (animation.fireEvent) {
  2551. animation.fireEvent('animationend', animation);
  2552. }
  2553. }
  2554. }
  2555. },
  2556. /**
  2557. * Register a one-time callback that will be called at the next frame.
  2558. * @param {Function/String} callback
  2559. * @param {Object} scope
  2560. * @return {String} The ID of the scheduled callback.
  2561. */
  2562. schedule: function(callback, scope) {
  2563. var id = 'frameCallback' + (this.frameCallbackId++);
  2564. scope = scope || this;
  2565. if (Ext.isString(callback)) {
  2566. callback = scope[callback];
  2567. }
  2568. Ext.draw.Animator.frameCallbacks[id] = {
  2569. fn: callback,
  2570. scope: scope,
  2571. once: true
  2572. };
  2573. this.scheduled++;
  2574. Ext.draw.Animator.ignite();
  2575. return id;
  2576. },
  2577. /**
  2578. * Register a one-time callback that will be called at the next frame,
  2579. * if that callback (with a matching function and scope) isn't already scheduled.
  2580. * @param {Function/String} callback
  2581. * @param {Object} scope
  2582. * @return {String/null} The ID of the scheduled callback or null, if that callback
  2583. * has already been scheduled.
  2584. */
  2585. scheduleIf: function(callback, scope) {
  2586. var frameCallbacks = Ext.draw.Animator.frameCallbacks,
  2587. cb, id;
  2588. scope = scope || this;
  2589. if (Ext.isString(callback)) {
  2590. callback = scope[callback];
  2591. }
  2592. for (id in frameCallbacks) {
  2593. cb = frameCallbacks[id];
  2594. if (cb.once && cb.fn === callback && cb.scope === scope) {
  2595. return null;
  2596. }
  2597. }
  2598. return this.schedule(callback, scope);
  2599. },
  2600. /**
  2601. * Cancel a registered one-time callback
  2602. * @param {String} id
  2603. */
  2604. cancel: function(id) {
  2605. if (Ext.draw.Animator.frameCallbacks[id] && Ext.draw.Animator.frameCallbacks[id].once) {
  2606. this.scheduled = Math.max(--this.scheduled, 0);
  2607. delete Ext.draw.Animator.frameCallbacks[id];
  2608. Ext.draw.Draw.endUpdateIOS();
  2609. }
  2610. if (this.idle()) {
  2611. this.extinguish();
  2612. }
  2613. },
  2614. clear: function() {
  2615. this.animations.length = 0;
  2616. Ext.draw.Animator.frameCallbacks = {};
  2617. this.extinguish();
  2618. },
  2619. /**
  2620. * Register a recursive callback that will be called at every frame.
  2621. *
  2622. * @param {Function} callback
  2623. * @param {Object} scope
  2624. * @return {String}
  2625. */
  2626. addFrameCallback: function(callback, scope) {
  2627. var id = 'frameCallback' + (this.frameCallbackId++);
  2628. scope = scope || this;
  2629. if (Ext.isString(callback)) {
  2630. callback = scope[callback];
  2631. }
  2632. Ext.draw.Animator.frameCallbacks[id] = {
  2633. fn: callback,
  2634. scope: scope
  2635. };
  2636. return id;
  2637. },
  2638. /**
  2639. * Unregister a recursive callback.
  2640. * @param {String} id
  2641. */
  2642. removeFrameCallback: function(id) {
  2643. delete Ext.draw.Animator.frameCallbacks[id];
  2644. if (this.idle()) {
  2645. this.extinguish();
  2646. }
  2647. },
  2648. /**
  2649. * @private
  2650. */
  2651. fireFrameCallbacks: function() {
  2652. var callbacks = this.frameCallbacks,
  2653. id, fn, cb;
  2654. for (id in callbacks) {
  2655. cb = callbacks[id];
  2656. fn = cb.fn;
  2657. if (Ext.isString(fn)) {
  2658. fn = cb.scope[fn];
  2659. }
  2660. fn.call(cb.scope);
  2661. if (callbacks[id] && cb.once) {
  2662. this.scheduled = Math.max(--this.scheduled, 0);
  2663. delete callbacks[id];
  2664. }
  2665. }
  2666. },
  2667. handleFrame: function() {
  2668. var me = this;
  2669. me.step(me.animationTime());
  2670. me.fireFrameCallbacks();
  2671. if (me.idle()) {
  2672. me.extinguish();
  2673. }
  2674. },
  2675. ignite: function() {
  2676. if (!this.running) {
  2677. this.running = true;
  2678. Ext.AnimationQueue.start(this.handleFrame, this);
  2679. Ext.draw.Draw.beginUpdateIOS();
  2680. }
  2681. },
  2682. extinguish: function() {
  2683. this.running = false;
  2684. Ext.AnimationQueue.stop(this.handleFrame, this);
  2685. Ext.draw.Draw.endUpdateIOS();
  2686. }
  2687. });
  2688. /**
  2689. * The Animation modifier.
  2690. *
  2691. * Sencha Charts allow users to use transitional animation on sprites. Simply set the duration
  2692. * and easing in the animation modifier, then all the changes to the sprites will be animated.
  2693. *
  2694. * @example
  2695. * var drawCt = Ext.create({
  2696. * xtype: 'draw',
  2697. * renderTo: document.body,
  2698. * width: 400,
  2699. * height: 400,
  2700. * sprites: [{
  2701. * type: 'rect',
  2702. * x: 50,
  2703. * y: 50,
  2704. * width: 100,
  2705. * height: 100,
  2706. * fillStyle: '#1F6D91'
  2707. * }]
  2708. * });
  2709. *
  2710. * var rect = drawCt.getSurface().getItems()[0];
  2711. *
  2712. * rect.setAnimation({
  2713. * duration: 1000,
  2714. * easing: 'elasticOut'
  2715. * });
  2716. *
  2717. * Ext.defer(function () {
  2718. * rect.setAttributes({
  2719. * width: 250
  2720. * });
  2721. * }, 500);
  2722. *
  2723. * Also, you can use different durations and easing functions on different attributes by using
  2724. * {@link #customDurations} and {@link #customEasings}.
  2725. *
  2726. * By default, an animation modifier will be created during the initialization of a sprite.
  2727. * You can get the animation modifier of a sprite via its
  2728. * {@link Ext.draw.sprite.Sprite#method-getAnimation getAnimation} method.
  2729. */
  2730. Ext.define('Ext.draw.modifier.Animation', {
  2731. extend: 'Ext.draw.modifier.Modifier',
  2732. alias: 'modifier.animation',
  2733. requires: [
  2734. 'Ext.draw.TimingFunctions',
  2735. 'Ext.draw.Animator'
  2736. ],
  2737. config: {
  2738. /**
  2739. * @cfg {Function} easing
  2740. * Default easing function.
  2741. */
  2742. easing: Ext.identityFn,
  2743. /**
  2744. * @cfg {Number} duration
  2745. * Default duration time (ms).
  2746. */
  2747. duration: 0,
  2748. /**
  2749. * @cfg {Object} customEasings Overrides the default easing function for defined attributes.
  2750. * E.g.:
  2751. *
  2752. * // Assuming the sprite the modifier is applied to is a 'circle'.
  2753. * customEasings: {
  2754. * r: 'easeOut',
  2755. * 'fillStyle,strokeStyle': 'linear',
  2756. * 'cx,cy': function (p, n) {
  2757. * p = 1 - p;
  2758. * n = n || 1.616;
  2759. * return 1 - p * p * ((n + 1) * p - n);
  2760. * }
  2761. * }
  2762. */
  2763. customEasings: {},
  2764. /**
  2765. * @cfg {Object} customDurations Overrides the default duration for defined attributes.
  2766. * E.g.:
  2767. *
  2768. * // Assuming the sprite the modifier is applied to is a 'circle'.
  2769. * customDurations: {
  2770. * r: 1000,
  2771. * 'fillStyle,strokeStyle': 2000,
  2772. * 'cx,cy': 1000
  2773. * }
  2774. */
  2775. customDurations: {}
  2776. },
  2777. constructor: function(config) {
  2778. var me = this;
  2779. me.anyAnimation = me.anySpecialAnimations = false;
  2780. me.animating = 0;
  2781. me.animatingPool = [];
  2782. me.callParent([
  2783. config
  2784. ]);
  2785. },
  2786. prepareAttributes: function(attr) {
  2787. if (!attr.hasOwnProperty('timers')) {
  2788. attr.animating = false;
  2789. attr.timers = {};
  2790. // The 'targets' object is used to hold the target values for the
  2791. // attributes while they are being animated from source to target values.
  2792. // The 'targets' is pushed down to the lower level modifiers,
  2793. // instead of the actual attr object, to hide the fact that the
  2794. // attributes are being animated.
  2795. attr.targets = Ext.Object.chain(attr);
  2796. attr.targets.prototype = attr;
  2797. }
  2798. if (this._lower) {
  2799. this._lower.prepareAttributes(attr.targets);
  2800. }
  2801. },
  2802. updateSprite: function(sprite) {
  2803. this.setConfig(sprite.config.animation);
  2804. },
  2805. updateDuration: function(duration) {
  2806. this.anyAnimation = duration > 0;
  2807. },
  2808. applyEasing: function(easing) {
  2809. if (typeof easing === 'string') {
  2810. easing = Ext.draw.TimingFunctions.easingMap[easing];
  2811. }
  2812. return easing;
  2813. },
  2814. applyCustomEasings: function(newEasings, oldEasings) {
  2815. var any, key, attrs, easing, i, ln;
  2816. oldEasings = oldEasings || {};
  2817. for (key in newEasings) {
  2818. any = true;
  2819. easing = newEasings[key];
  2820. attrs = key.split(',');
  2821. if (typeof easing === 'string') {
  2822. easing = Ext.draw.TimingFunctions.easingMap[easing];
  2823. }
  2824. for (i = 0 , ln = attrs.length; i < ln; i++) {
  2825. oldEasings[attrs[i]] = easing;
  2826. }
  2827. }
  2828. if (any) {
  2829. this.anySpecialAnimations = any;
  2830. }
  2831. return oldEasings;
  2832. },
  2833. /**
  2834. * Set special easings on the given attributes. E.g.:
  2835. *
  2836. * circleSprite.getAnimation().setEasingOn('r', 'elasticIn');
  2837. *
  2838. * @param {String/Array} attrs The source attribute(s).
  2839. * @param {String} easing The special easings.
  2840. */
  2841. setEasingOn: function(attrs, easing) {
  2842. var customEasings = {},
  2843. i, ln;
  2844. attrs = Ext.Array.from(attrs).slice();
  2845. for (i = 0 , ln = attrs.length; i < ln; i++) {
  2846. customEasings[attrs[i]] = easing;
  2847. }
  2848. this.setCustomEasings(customEasings);
  2849. },
  2850. /**
  2851. * Remove special easings on the given attributes.
  2852. * @param {String/Array} attrs The source attribute(s).
  2853. */
  2854. clearEasingOn: function(attrs) {
  2855. var i, ln;
  2856. attrs = Ext.Array.from(attrs, true);
  2857. for (i = 0 , ln = attrs.length; i < ln; i++) {
  2858. delete this._customEasings[attrs[i]];
  2859. }
  2860. },
  2861. applyCustomDurations: function(newDurations, oldDurations) {
  2862. var any, key, duration, attrs, i, ln;
  2863. oldDurations = oldDurations || {};
  2864. for (key in newDurations) {
  2865. any = true;
  2866. duration = newDurations[key];
  2867. attrs = key.split(',');
  2868. for (i = 0 , ln = attrs.length; i < ln; i++) {
  2869. oldDurations[attrs[i]] = duration;
  2870. }
  2871. }
  2872. if (any) {
  2873. this.anySpecialAnimations = any;
  2874. }
  2875. return oldDurations;
  2876. },
  2877. /**
  2878. * Set special duration on the given attributes. E.g.:
  2879. *
  2880. * rectSprite.getAnimation().setDurationOn('height', 2000);
  2881. *
  2882. * @param {String/Array} attrs The source attributes.
  2883. * @param {Number} duration The special duration.
  2884. */
  2885. setDurationOn: function(attrs, duration) {
  2886. var customDurations = {},
  2887. i, ln;
  2888. attrs = Ext.Array.from(attrs).slice();
  2889. for (i = 0 , ln = attrs.length; i < ln; i++) {
  2890. customDurations[attrs[i]] = duration;
  2891. }
  2892. this.setCustomDurations(customDurations);
  2893. },
  2894. /**
  2895. * Remove special easings on the given attributes.
  2896. * @param {Object} attrs The source attributes.
  2897. */
  2898. clearDurationOn: function(attrs) {
  2899. var i, ln;
  2900. attrs = Ext.Array.from(attrs, true);
  2901. for (i = 0 , ln = attrs.length; i < ln; i++) {
  2902. delete this._customDurations[attrs[i]];
  2903. }
  2904. },
  2905. /**
  2906. * @private
  2907. * Initializes Animator for the animation.
  2908. * @param {Object} attr The source attributes.
  2909. * @param {Boolean} animating The animating flag.
  2910. */
  2911. setAnimating: function(attr, animating) {
  2912. var me = this,
  2913. pool = me.animatingPool,
  2914. i;
  2915. if (attr.animating !== animating) {
  2916. attr.animating = animating;
  2917. if (animating) {
  2918. pool.push(attr);
  2919. if (me.animating === 0) {
  2920. Ext.draw.Animator.add(me);
  2921. }
  2922. me.animating++;
  2923. } else {
  2924. for (i = pool.length; i--; ) {
  2925. if (pool[i] === attr) {
  2926. pool.splice(i, 1);
  2927. }
  2928. }
  2929. me.animating = pool.length;
  2930. }
  2931. }
  2932. },
  2933. /**
  2934. * @private
  2935. * Set the attr with given easing and duration.
  2936. * @param {Object} attr The attributes collection.
  2937. * @param {Object} changes The changes that popped up from lower modifier.
  2938. * @return {Object} The changes to pop up.
  2939. */
  2940. setAttrs: function(attr, changes) {
  2941. var me = this,
  2942. timers = attr.timers,
  2943. parsers = me._sprite.self.def._animationProcessors,
  2944. defaultEasing = me._easing,
  2945. defaultDuration = me._duration,
  2946. customDurations = me._customDurations,
  2947. customEasings = me._customEasings,
  2948. anySpecial = me.anySpecialAnimations,
  2949. any = me.anyAnimation || anySpecial,
  2950. targets = attr.targets,
  2951. ignite = false,
  2952. timer, name, newValue, startValue, parser, easing, duration, initial;
  2953. if (!any) {
  2954. // If there is no animation enabled.
  2955. // When applying changes to attributes, simply stop current animation
  2956. // and set the value.
  2957. for (name in changes) {
  2958. if (attr[name] === changes[name]) {
  2959. delete changes[name];
  2960. } else {
  2961. attr[name] = changes[name];
  2962. }
  2963. delete targets[name];
  2964. delete timers[name];
  2965. }
  2966. return changes;
  2967. } else {
  2968. // If any animation.
  2969. for (name in changes) {
  2970. newValue = changes[name];
  2971. startValue = attr[name];
  2972. if (newValue !== startValue && startValue !== undefined && startValue !== null && (parser = parsers[name])) {
  2973. // If this property is animating.
  2974. // Figure out the desired duration and easing.
  2975. easing = defaultEasing;
  2976. duration = defaultDuration;
  2977. if (anySpecial) {
  2978. // Deducing the easing function and duration
  2979. if (name in customEasings) {
  2980. easing = customEasings[name];
  2981. }
  2982. if (name in customDurations) {
  2983. duration = customDurations[name];
  2984. }
  2985. }
  2986. // Transitions betweens color and gradient or between gradients
  2987. // are not supported.
  2988. if (startValue && startValue.isGradient || newValue && newValue.isGradient) {
  2989. duration = 0;
  2990. }
  2991. // If the property is animating
  2992. if (duration) {
  2993. if (!timers[name]) {
  2994. timers[name] = {};
  2995. }
  2996. timer = timers[name];
  2997. timer.start = 0;
  2998. timer.easing = easing;
  2999. timer.duration = duration;
  3000. timer.compute = parser.compute;
  3001. timer.serve = parser.serve || Ext.identityFn;
  3002. timer.remove = changes.removeFromInstance && changes.removeFromInstance[name];
  3003. if (parser.parseInitial) {
  3004. initial = parser.parseInitial(startValue, newValue);
  3005. timer.source = initial[0];
  3006. timer.target = initial[1];
  3007. } else if (parser.parse) {
  3008. timer.source = parser.parse(startValue);
  3009. timer.target = parser.parse(newValue);
  3010. } else {
  3011. timer.source = startValue;
  3012. timer.target = newValue;
  3013. }
  3014. // The animation started. Change to originalVal.
  3015. targets[name] = newValue;
  3016. delete changes[name];
  3017. ignite = true;
  3018. continue;
  3019. } else {
  3020. delete targets[name];
  3021. }
  3022. } else {
  3023. delete targets[name];
  3024. }
  3025. // If the property is not animating.
  3026. delete timers[name];
  3027. }
  3028. }
  3029. if (ignite && !attr.animating) {
  3030. me.setAnimating(attr, true);
  3031. }
  3032. return changes;
  3033. },
  3034. /**
  3035. * @private
  3036. *
  3037. * Update attributes to current value according to current animation time.
  3038. * This method will not affect the values of lower layers, but may delete a
  3039. * value from it.
  3040. * @param {Object} attr The source attributes.
  3041. * @return {Object} The changes to pop up or null.
  3042. */
  3043. updateAttributes: function(attr) {
  3044. if (!attr.animating) {
  3045. return {};
  3046. }
  3047. // eslint-disable-next-line vars-on-top
  3048. var changes = {},
  3049. any = false,
  3050. timers = attr.timers,
  3051. targets = attr.targets,
  3052. now = Ext.draw.Animator.animationTime(),
  3053. name, timer, delta;
  3054. // If updated in the same frame, return.
  3055. if (attr.lastUpdate === now) {
  3056. return null;
  3057. }
  3058. for (name in timers) {
  3059. timer = timers[name];
  3060. if (!timer.start) {
  3061. timer.start = now;
  3062. delta = 0;
  3063. } else {
  3064. delta = (now - timer.start) / timer.duration;
  3065. }
  3066. if (delta >= 1) {
  3067. changes[name] = targets[name];
  3068. delete targets[name];
  3069. if (timers[name].remove) {
  3070. changes.removeFromInstance = changes.removeFromInstance || {};
  3071. changes.removeFromInstance[name] = true;
  3072. }
  3073. delete timers[name];
  3074. } else {
  3075. changes[name] = timer.serve(timer.compute(timer.source, timer.target, timer.easing(delta), attr[name]));
  3076. any = true;
  3077. }
  3078. }
  3079. attr.lastUpdate = now;
  3080. this.setAnimating(attr, any);
  3081. return changes;
  3082. },
  3083. pushDown: function(attr, changes) {
  3084. changes = this.callParent([
  3085. attr.targets,
  3086. changes
  3087. ]);
  3088. return this.setAttrs(attr, changes);
  3089. },
  3090. popUp: function(attr, changes) {
  3091. attr = attr.prototype;
  3092. changes = this.setAttrs(attr, changes);
  3093. if (this._upper) {
  3094. return this._upper.popUp(attr, changes);
  3095. } else {
  3096. return Ext.apply(attr, changes);
  3097. }
  3098. },
  3099. /**
  3100. * @private
  3101. * This is called as an animated object in `Ext.draw.Animator`.
  3102. */
  3103. step: function(frameTime) {
  3104. var me = this,
  3105. pool = me.animatingPool.slice(),
  3106. ln = pool.length,
  3107. i = 0,
  3108. attr, changes;
  3109. for (; i < ln; i++) {
  3110. attr = pool[i];
  3111. changes = me.updateAttributes(attr);
  3112. if (changes && me._upper) {
  3113. me._upper.popUp(attr, changes);
  3114. }
  3115. }
  3116. },
  3117. /**
  3118. * Stop all animations affected by this modifier.
  3119. */
  3120. stop: function() {
  3121. var me = this,
  3122. pool = me.animatingPool,
  3123. i, ln;
  3124. this.step();
  3125. for (i = 0 , ln = pool.length; i < ln; i++) {
  3126. pool[i].animating = false;
  3127. }
  3128. me.animatingPool.length = 0;
  3129. me.animating = 0;
  3130. Ext.draw.Animator.remove(me);
  3131. },
  3132. destroy: function() {
  3133. Ext.draw.Animator.remove(this);
  3134. this.callParent();
  3135. }
  3136. });
  3137. /**
  3138. * @class Ext.draw.modifier.Highlight
  3139. * @extends Ext.draw.modifier.Modifier
  3140. *
  3141. * Highlight is a modifier that will override sprite attributes
  3142. * with {@link Ext.draw.modifier.Highlight#style style} attributes
  3143. * when sprite's `highlighted` attribute is true.
  3144. */
  3145. Ext.define('Ext.draw.modifier.Highlight', {
  3146. extend: 'Ext.draw.modifier.Modifier',
  3147. alias: 'modifier.highlight',
  3148. config: {
  3149. /**
  3150. * @cfg {Boolean} enabled 'true' if the highlight is applied.
  3151. */
  3152. enabled: false,
  3153. /**
  3154. * @cfg {Object} style The style attributes of the highlight modifier.
  3155. */
  3156. style: null
  3157. },
  3158. preFx: true,
  3159. applyStyle: function(style, oldStyle) {
  3160. oldStyle = oldStyle || {};
  3161. if (this.getSprite()) {
  3162. Ext.apply(oldStyle, this.getSprite().self.def.normalize(style));
  3163. } else {
  3164. Ext.apply(oldStyle, style);
  3165. }
  3166. return oldStyle;
  3167. },
  3168. prepareAttributes: function(attr) {
  3169. if (!attr.hasOwnProperty('highlightOriginal')) {
  3170. attr.highlighted = false;
  3171. attr.highlightOriginal = Ext.Object.chain(attr);
  3172. attr.highlightOriginal.prototype = attr;
  3173. // A list of attributes that should be removed from a sprite instance
  3174. // when it is unhighlighted.
  3175. attr.highlightOriginal.removeFromInstance = {};
  3176. }
  3177. if (this._lower) {
  3178. this._lower.prepareAttributes(attr.highlightOriginal);
  3179. }
  3180. },
  3181. updateSprite: function(sprite, oldSprite) {
  3182. var me = this,
  3183. style = me.getStyle(),
  3184. attributeDefinitions;
  3185. if (sprite) {
  3186. attributeDefinitions = sprite.self.def;
  3187. if (style) {
  3188. me._style = attributeDefinitions.normalize(style);
  3189. }
  3190. me.setStyle(sprite.config.highlight);
  3191. // Add highlight related attributes to sprite's attribute definition.
  3192. // This will affect all sprites of the same type, even those without
  3193. // the highlight modifier.
  3194. attributeDefinitions.setConfig({
  3195. defaults: {
  3196. highlighted: false
  3197. },
  3198. processors: {
  3199. highlighted: 'bool'
  3200. }
  3201. });
  3202. }
  3203. this.setSprite(sprite);
  3204. },
  3205. /**
  3206. * @private
  3207. * Filter out modifier changes that override highlight style or source attributes.
  3208. * @param {Object} attr The source attributes.
  3209. * @param {Object} changes The modifier changes.
  3210. * @return {*} The filtered changes.
  3211. */
  3212. filterChanges: function(attr, changes) {
  3213. var me = this,
  3214. highlightOriginal = attr.highlightOriginal,
  3215. style = me.getStyle(),
  3216. name;
  3217. if (attr.highlighted) {
  3218. for (name in changes) {
  3219. if (style.hasOwnProperty(name)) {
  3220. // If sprite is highlighted, then stash the changes
  3221. // to the `style` attributes made by lower level modifiers
  3222. // to apply them later when sprite is unhighlighted.
  3223. highlightOriginal[name] = changes[name];
  3224. delete changes[name];
  3225. }
  3226. }
  3227. }
  3228. return changes;
  3229. },
  3230. pushDown: function(attr, changes) {
  3231. var style = this.getStyle(),
  3232. highlightOriginal = attr.highlightOriginal,
  3233. removeFromInstance = highlightOriginal.removeFromInstance,
  3234. highlighted, name, tplAttr, timer;
  3235. if (changes.hasOwnProperty('highlighted')) {
  3236. highlighted = changes.highlighted;
  3237. // Hide `highlighted` and `style` from underlying modifiers.
  3238. delete changes.highlighted;
  3239. if (this._lower) {
  3240. changes = this._lower.pushDown(highlightOriginal, changes);
  3241. }
  3242. changes = this.filterChanges(attr, changes);
  3243. if (highlighted !== attr.highlighted) {
  3244. if (highlighted) {
  3245. // Switching ON.
  3246. // At this time, original should be empty.
  3247. for (name in style) {
  3248. // Remember the values of attributes to revert back to them on unhighlight.
  3249. if (name in changes) {
  3250. // Remember value set by lower level modifiers.
  3251. highlightOriginal[name] = changes[name];
  3252. } else {
  3253. // Remember the original value.
  3254. // If this is a sprite instance and it doesn't have its own
  3255. // 'name' attribute, (i.e. inherits template's attribute value)
  3256. // than we have to get the value for the 'name' attribute from
  3257. // the template's 'targets' object instead of its
  3258. // 'attr' object (which is the prototype of the instance),
  3259. // because the 'name' attribute of the template may be animating.
  3260. // Check out the prepareAttributes method of the Animation
  3261. // modifier for more details on the 'targets' object.
  3262. tplAttr = attr.template && attr.template.ownAttr;
  3263. if (tplAttr && !attr.prototype.hasOwnProperty(name)) {
  3264. removeFromInstance[name] = true;
  3265. highlightOriginal[name] = tplAttr.targets[name];
  3266. } else {
  3267. // Even if a sprite instance has its own property, it may
  3268. // still have to be removed from the instance after
  3269. // unhighlighting is done.
  3270. // Consider a situation where an instance doesn't originally
  3271. // have its own attribute (that is used for highlighting and
  3272. // unhighlighting). It will however have that attribute as
  3273. // its own when the highlight/unhighlight animation is in
  3274. // progress, until the attribute is removed from the instance
  3275. // when the unhighlighting is done.
  3276. // So in a scenario where the instance is highlighted, then
  3277. // unhighlighted (i.e. starts animating back to its original
  3278. // value) and then highlighted again before the unhighlight
  3279. // animation is done, we should still mark the attribute
  3280. // for removal from the instance, if it was our original
  3281. // intention. To tell if it was, we can check the timer
  3282. // for the attribute and see if the 'remove' flag is set.
  3283. timer = highlightOriginal.timers[name];
  3284. if (timer && timer.remove) {
  3285. removeFromInstance[name] = true;
  3286. }
  3287. highlightOriginal[name] = attr[name];
  3288. }
  3289. }
  3290. if (highlightOriginal[name] !== style[name]) {
  3291. changes[name] = style[name];
  3292. }
  3293. }
  3294. } else {
  3295. // Switching OFF.
  3296. for (name in style) {
  3297. if (!(name in changes)) {
  3298. changes[name] = highlightOriginal[name];
  3299. }
  3300. delete highlightOriginal[name];
  3301. }
  3302. changes.removeFromInstance = changes.removeFromInstance || {};
  3303. // Let the higher lever animation modifier know which attributes
  3304. // should be removed from instance when the animation is done.
  3305. Ext.apply(changes.removeFromInstance, removeFromInstance);
  3306. highlightOriginal.removeFromInstance = {};
  3307. }
  3308. changes.highlighted = highlighted;
  3309. }
  3310. } else {
  3311. if (this._lower) {
  3312. changes = this._lower.pushDown(highlightOriginal, changes);
  3313. }
  3314. changes = this.filterChanges(attr, changes);
  3315. }
  3316. return changes;
  3317. },
  3318. popUp: function(attr, changes) {
  3319. changes = this.filterChanges(attr, changes);
  3320. this.callParent([
  3321. attr,
  3322. changes
  3323. ]);
  3324. }
  3325. });
  3326. /**
  3327. * A sprite is a basic primitive from the charts package which represents a graphical
  3328. * object that can be drawn. Sprites are used extensively in the charts package to
  3329. * create the visual elements of each chart. You can also create a desired image by
  3330. * adding one or more sprites to a {@link Ext.draw.Container draw container}.
  3331. *
  3332. * The Sprite class itself is an abstract class and is not meant to be used directly.
  3333. * There are many different kinds of sprites available in the charts package that extend
  3334. * Ext.draw.sprite.Sprite. Each sprite type has various attributes that define how that
  3335. * sprite should look. For example, this is a {@link Ext.draw.sprite.Rect rect} sprite:
  3336. *
  3337. * @example
  3338. * Ext.create({
  3339. * xtype: 'draw',
  3340. * renderTo: document.body,
  3341. * width: 400,
  3342. * height: 400,
  3343. * sprites: [{
  3344. * type: 'rect',
  3345. * x: 50,
  3346. * y: 50,
  3347. * width: 100,
  3348. * height: 100,
  3349. * fillStyle: '#1F6D91'
  3350. * }]
  3351. * });
  3352. *
  3353. * By default, sprites are added to the default 'main' {@link Ext.draw.Surface surface}
  3354. * of the draw container. However, sprites may also be configured with a reference to a
  3355. * specific Ext.draw.Surface when set in the draw container's
  3356. * {@link Ext.draw.Container#cfg-sprites sprites} config. Specifying a surface
  3357. * other than 'main' will create a surface by that name if it does not already exist.
  3358. *
  3359. * @example
  3360. * Ext.create({
  3361. * xtype: 'draw',
  3362. * renderTo: document.body,
  3363. * width: 400,
  3364. * height: 400,
  3365. * sprites: [{
  3366. * type: 'rect',
  3367. * surface: 'anim', // a surface with id "anim" will be created automatically
  3368. * x: 50,
  3369. * y: 50,
  3370. * width: 100,
  3371. * height: 100,
  3372. * fillStyle: '#1F6D91'
  3373. * }]
  3374. * });
  3375. *
  3376. * The ability to have multiple surfaces is useful for performance (and battery life)
  3377. * reasons. Because changes to sprite attributes cause the whole surface (and all
  3378. * sprites in it) to re-render, it makes sense to group sprites by surface, so changes
  3379. * to one group of sprites will only trigger the surface they are in to re-render.
  3380. *
  3381. * You can add a sprite to an existing drawing by adding the sprite to a draw surface.
  3382. *
  3383. * @example
  3384. * var drawCt = Ext.create({
  3385. * xtype: 'draw',
  3386. * renderTo: document.body,
  3387. * width: 400,
  3388. * height: 400
  3389. * });
  3390. *
  3391. * // If the surface name is not specified then 'main' will be used
  3392. * var surface = drawCt.getSurface();
  3393. *
  3394. * surface.add({
  3395. * type: 'rect',
  3396. * x: 50,
  3397. * y: 50,
  3398. * width: 100,
  3399. * height: 100,
  3400. * fillStyle: '#1F6D91'
  3401. * });
  3402. *
  3403. * surface.renderFrame();
  3404. *
  3405. * **Note:** Changes to the sprites on a surface will be not be reflected in the DOM
  3406. * until you call the surface's {@link Ext.draw.Surface#method-renderFrame renderFrame}
  3407. * method. This must be done after adding, removing, or modifying sprites in order to
  3408. * see the changes on-screen.
  3409. *
  3410. * For information on configuring a sprite with an initial transformation see
  3411. * {@link #scaling}, {@link #rotation}, and {@link #translation}.
  3412. *
  3413. * For information on applying a transformation to an existing sprite see the
  3414. * Ext.draw.Matrix class.
  3415. */
  3416. Ext.define('Ext.draw.sprite.Sprite', {
  3417. alias: 'sprite.sprite',
  3418. mixins: {
  3419. observable: 'Ext.mixin.Observable'
  3420. },
  3421. requires: [
  3422. 'Ext.draw.Draw',
  3423. 'Ext.draw.gradient.Gradient',
  3424. 'Ext.draw.sprite.AttributeDefinition',
  3425. 'Ext.draw.modifier.Target',
  3426. 'Ext.draw.modifier.Animation',
  3427. 'Ext.draw.modifier.Highlight'
  3428. ],
  3429. isSprite: true,
  3430. $configStrict: false,
  3431. statics: {
  3432. //<debug>
  3433. /* eslint-disable max-len */
  3434. /**
  3435. * Debug rendering options:
  3436. *
  3437. * debug: {
  3438. * bbox: true, // renders the bounding box of the sprite
  3439. * xray: true // renders control points of the path (for Ext.draw.sprite.Path and descendants only)
  3440. * }
  3441. *
  3442. */
  3443. debug: false,
  3444. /* eslint-enable max-len */
  3445. //</debug>
  3446. defaultHitTestOptions: {
  3447. fill: true,
  3448. stroke: true
  3449. }
  3450. },
  3451. inheritableStatics: {
  3452. def: {
  3453. processors: {
  3454. //<debug>
  3455. debug: 'default',
  3456. //</debug>
  3457. /**
  3458. * @cfg {String} [strokeStyle="none"] The color of the stroke (a CSS color value).
  3459. */
  3460. strokeStyle: "color",
  3461. /**
  3462. * @cfg {String} [fillStyle="none"] The color of the shape (a CSS color value).
  3463. */
  3464. fillStyle: "color",
  3465. /**
  3466. * @cfg {Number} [strokeOpacity=1] The opacity of the stroke. Limited from 0 to 1.
  3467. */
  3468. strokeOpacity: "limited01",
  3469. /**
  3470. * @cfg {Number} [fillOpacity=1] The opacity of the fill. Limited from 0 to 1.
  3471. */
  3472. fillOpacity: "limited01",
  3473. /**
  3474. * @cfg {Number} [lineWidth=1] The width of the line stroke.
  3475. */
  3476. lineWidth: "number",
  3477. /**
  3478. * @cfg {String} [lineCap="butt"] The style of the line caps.
  3479. */
  3480. lineCap: "enums(butt,round,square)",
  3481. /**
  3482. * @cfg {String} [lineJoin="miter"] The style of the line join.
  3483. */
  3484. lineJoin: "enums(round,bevel,miter)",
  3485. /**
  3486. * @cfg {Array} [lineDash=[]]
  3487. * An even number of non-negative numbers specifying a dash/space sequence.
  3488. * Note that while this is supported in IE8 (VML engine), the behavior is
  3489. * different from Canvas and SVG. Please refer to this document for details:
  3490. * http://msdn.microsoft.com/en-us/library/bb264085(v=vs.85).aspx
  3491. * Although IE9 and IE10 have Canvas support, the 'lineDash'
  3492. * attribute is not supported in those browsers.
  3493. */
  3494. lineDash: "data",
  3495. /**
  3496. * @cfg {Number} [lineDashOffset=0]
  3497. * A number specifying how far into the line dash sequence drawing commences.
  3498. */
  3499. lineDashOffset: "number",
  3500. /**
  3501. * @cfg {Number} [miterLimit=10]
  3502. * Sets the distance between the inner corner and the outer corner
  3503. * where two lines meet.
  3504. */
  3505. miterLimit: "number",
  3506. /**
  3507. * @cfg {String} [shadowColor="none"] The color of the shadow (a CSS color value).
  3508. */
  3509. shadowColor: "color",
  3510. /**
  3511. * @cfg {Number} [shadowOffsetX=0] The offset of the sprite's shadow on the x-axis.
  3512. */
  3513. shadowOffsetX: "number",
  3514. /**
  3515. * @cfg {Number} [shadowOffsetY=0] The offset of the sprite's shadow on the y-axis.
  3516. */
  3517. shadowOffsetY: "number",
  3518. /**
  3519. * @cfg {Number} [shadowBlur=0] The amount blur used on the shadow.
  3520. */
  3521. shadowBlur: "number",
  3522. /**
  3523. * @cfg {Number} [globalAlpha=1] The opacity of the sprite. Limited from 0 to 1.
  3524. */
  3525. globalAlpha: "limited01",
  3526. /**
  3527. * @cfg {String} [globalCompositeOperation=source-over]
  3528. * Indicates how source images are drawn onto a destination image.
  3529. * globalCompositeOperation attribute is not supported by the SVG and VML
  3530. * (excanvas) engines.
  3531. */
  3532. // eslint-disable-next-line max-len
  3533. globalCompositeOperation: "enums(source-over,destination-over,source-in,destination-in,source-out,destination-out,source-atop,destination-atop,lighter,xor,copy)",
  3534. /**
  3535. * @cfg {Boolean} [hidden=false] Determines whether or not the sprite is hidden.
  3536. */
  3537. hidden: "bool",
  3538. /**
  3539. * @cfg {Boolean} [transformFillStroke=false]
  3540. * Determines whether the fill and stroke are affected by sprite transformations.
  3541. */
  3542. transformFillStroke: "bool",
  3543. /**
  3544. * @cfg {Number} [zIndex=0]
  3545. * The stacking order of the sprite.
  3546. */
  3547. zIndex: "number",
  3548. /**
  3549. * @cfg {Number} [translationX=0]
  3550. * The translation, position offset, of the sprite on the x-axis.
  3551. *
  3552. * **Note:** Transform configs are *always* performed in the following
  3553. * order:
  3554. *
  3555. * 1. Scaling
  3556. * 2. Rotation
  3557. * 3. Translation
  3558. *
  3559. * See also: {@link #translation} and {@link #translationY}
  3560. */
  3561. translationX: "number",
  3562. /**
  3563. * @cfg {Number} [translationY=0]
  3564. * The translation, position offset, of the sprite on the y-axis.
  3565. *
  3566. * **Note:** Transform configs are *always* performed in the following
  3567. * order:
  3568. *
  3569. * 1. Scaling
  3570. * 2. Rotation
  3571. * 3. Translation
  3572. *
  3573. * See also: {@link #translation} and {@link #translationX}
  3574. */
  3575. translationY: "number",
  3576. /**
  3577. * @cfg {Number} [rotationRads=0]
  3578. * The angle of rotation of the sprite in radians.
  3579. *
  3580. * **Note:** Transform configs are *always* performed in the following
  3581. * order:
  3582. *
  3583. * 1. Scaling
  3584. * 2. Rotation
  3585. * 3. Translation
  3586. *
  3587. * See also: {@link #rotation}, {@link #rotationCenterX}, and
  3588. * {@link #rotationCenterY}
  3589. */
  3590. rotationRads: "number",
  3591. /**
  3592. * @cfg {Number} [rotationCenterX=null]
  3593. * The central coordinate of the sprite's scale operation on the x-axis.
  3594. * Unless explicitly set, will default to the calculated center of the
  3595. * sprite along the x-axis.
  3596. *
  3597. * **Note:** Transform configs are *always* performed in the following
  3598. * order:
  3599. *
  3600. * 1. Scaling
  3601. * 2. Rotation
  3602. * 3. Translation
  3603. *
  3604. * See also: {@link #rotation}, {@link #rotationRads}, and
  3605. * {@link #rotationCenterY}
  3606. */
  3607. rotationCenterX: "number",
  3608. /**
  3609. * @cfg {Number} [rotationCenterY=null]
  3610. * The central coordinate of the sprite's rotate operation on the y-axis.
  3611. * Unless explicitly set, will default to the calculated center of the
  3612. * sprite along the y-axis.
  3613. *
  3614. * **Note:** Transform configs are *always* performed in the following
  3615. * order:
  3616. *
  3617. * 1. Scaling
  3618. * 2. Rotation
  3619. * 3. Translation
  3620. *
  3621. * See also: {@link #rotation}, {@link #rotationRads}, and
  3622. * {@link #rotationCenterX}
  3623. */
  3624. rotationCenterY: "number",
  3625. /**
  3626. * @cfg {Number} [scalingX=1] The scaling of the sprite on the x-axis.
  3627. * The number value represents a percentage by which to scale the
  3628. * sprite. **1** is equal to 100%, **2** would be 200%, etc.
  3629. *
  3630. * **Note:** Transform configs are *always* performed in the following
  3631. * order:
  3632. *
  3633. * 1. Scaling
  3634. * 2. Rotation
  3635. * 3. Translation
  3636. *
  3637. * See also: {@link #scaling}, {@link #scalingY},
  3638. * {@link #scalingCenterX}, and {@link #scalingCenterY}
  3639. */
  3640. scalingX: "number",
  3641. /**
  3642. * @cfg {Number} [scalingY=1] The scaling of the sprite on the y-axis.
  3643. * The number value represents a percentage by which to scale the
  3644. * sprite. **1** is equal to 100%, **2** would be 200%, etc.
  3645. *
  3646. * **Note:** Transform configs are *always* performed in the following
  3647. * order:
  3648. *
  3649. * 1. Scaling
  3650. * 2. Rotation
  3651. * 3. Translation
  3652. *
  3653. * See also: {@link #scaling}, {@link #scalingX},
  3654. * {@link #scalingCenterX}, and {@link #scalingCenterY}
  3655. */
  3656. scalingY: "number",
  3657. /**
  3658. * @cfg {Number} [scalingCenterX=null]
  3659. * The central coordinate of the sprite's scale operation on the x-axis.
  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 #scaling}, {@link #scalingX},
  3669. * {@link #scalingY}, and {@link #scalingCenterY}
  3670. */
  3671. scalingCenterX: "number",
  3672. /**
  3673. * @cfg {Number} [scalingCenterY=null]
  3674. * The central coordinate of the sprite's scale operation on the y-axis.
  3675. *
  3676. * **Note:** Transform configs are *always* performed in the following
  3677. * order:
  3678. *
  3679. * 1. Scaling
  3680. * 2. Rotation
  3681. * 3. Translation
  3682. *
  3683. * See also: {@link #scaling}, {@link #scalingX},
  3684. * {@link #scalingY}, and {@link #scalingCenterX}
  3685. */
  3686. scalingCenterY: "number",
  3687. constrainGradients: "bool"
  3688. },
  3689. /**
  3690. * @cfg {Number/Object} rotation
  3691. * Applies an initial angle of rotation to the sprite. May be a number
  3692. * specifying the rotation in degrees. Or may be a config object using
  3693. * the below config options.
  3694. *
  3695. * **Note:** Rotation config options will be overridden by values set on
  3696. * the {@link #rotationRads}, {@link #rotationCenterX}, and
  3697. * {@link #rotationCenterY} configs.
  3698. *
  3699. * Ext.create({
  3700. * xtype: 'draw',
  3701. * renderTo: Ext.getBody(),
  3702. * width: 600,
  3703. * height: 400,
  3704. * sprites: [{
  3705. * type: 'rect',
  3706. * x: 50,
  3707. * y: 50,
  3708. * width: 100,
  3709. * height: 100,
  3710. * fillStyle: '#1F6D91',
  3711. * //rotation: 45
  3712. * rotation: {
  3713. * degrees: 45,
  3714. * //rads: Math.PI / 4,
  3715. * //centerX: 50,
  3716. * //centerY: 50
  3717. * }
  3718. * }]
  3719. * });
  3720. *
  3721. * **Note:** Transform configs are *always* performed in the following
  3722. * order:
  3723. *
  3724. * 1. Scaling
  3725. * 2. Rotation
  3726. * 3. Translation
  3727. *
  3728. * @cfg {Number} rotation.rads
  3729. * The angle in radians to rotate the sprite
  3730. *
  3731. * @cfg {Number} rotation.degrees
  3732. * The angle in degrees to rotate the sprite (is ignored if rads or
  3733. * {@link #rotationRads} is set
  3734. *
  3735. * @cfg {Number} rotation.centerX
  3736. * The central coordinate of the sprite's rotation on the x-axis.
  3737. * Unless explicitly set, will default to the calculated center of the
  3738. * sprite along the x-axis.
  3739. *
  3740. * @cfg {Number} rotation.centerY
  3741. * The central coordinate of the sprite's rotation on the y-axis.
  3742. * Unless explicitly set, will default to the calculated center of the
  3743. * sprite along the y-axis.
  3744. */
  3745. /**
  3746. * @cfg {Number/Object} scaling
  3747. * Applies initial scaling to the sprite. May be a number specifying
  3748. * the amount to scale both the x and y-axis. The number value
  3749. * represents a percentage by which to scale the sprite. **1** is equal
  3750. * to 100%, **2** would be 200%, etc. Or may be a config object using
  3751. * the below config options.
  3752. *
  3753. * **Note:** Scaling config options will be overridden by values set on
  3754. * the {@link #scalingX}, {@link #scalingY}, {@link #scalingCenterX},
  3755. * and {@link #scalingCenterY} configs.
  3756. *
  3757. * Ext.create({
  3758. * xtype: 'draw',
  3759. * renderTo: Ext.getBody(),
  3760. * width: 600,
  3761. * height: 400,
  3762. * sprites: [{
  3763. * type: 'rect',
  3764. * x: 50,
  3765. * y: 50,
  3766. * width: 100,
  3767. * height: 100,
  3768. * fillStyle: '#1F6D91',
  3769. * //scaling: 2,
  3770. * scaling: {
  3771. * x: 2,
  3772. * y: 2
  3773. * //centerX: 100,
  3774. * //centerY: 100
  3775. * }
  3776. * }]
  3777. * });
  3778. *
  3779. * **Note:** Transform configs are *always* performed in the following
  3780. * order:
  3781. *
  3782. * 1. Scaling
  3783. * 2. Rotation
  3784. * 3. Translation
  3785. *
  3786. * @cfg {Number} scaling.x
  3787. * The amount by which to scale the sprite along the x-axis. The number
  3788. * value represents a percentage by which to scale the sprite. **1** is
  3789. * equal to 100%, **2** would be 200%, etc.
  3790. *
  3791. * @cfg {Number} scaling.y
  3792. * The amount by which to scale the sprite along the y-axis. The number
  3793. * value represents a percentage by which to scale the sprite. **1** is
  3794. * equal to 100%, **2** would be 200%, etc.
  3795. *
  3796. * @cfg scaling.centerX
  3797. * The central coordinate of the sprite's scaling on the x-axis. Unless
  3798. * explicitly set, will default to the calculated center of the sprite
  3799. * along the x-axis.
  3800. *
  3801. * @cfg {Number} scaling.centerY
  3802. * The central coordinate of the sprite's scaling on the y-axis. Unless
  3803. * explicitly set, will default to the calculated center of the sprite
  3804. * along the y-axis.
  3805. */
  3806. /**
  3807. * @cfg {Object} translation
  3808. * Applies an initial translation, adjustment in x/y positioning, to the
  3809. * sprite.
  3810. *
  3811. * **Note:** Translation config options will be overridden by values set
  3812. * on the {@link #translationX} and {@link #translationY} configs.
  3813. *
  3814. * Ext.create({
  3815. * xtype: 'draw',
  3816. * renderTo: Ext.getBody(),
  3817. * width: 600,
  3818. * height: 400,
  3819. * sprites: [{
  3820. * type: 'rect',
  3821. * x: 50,
  3822. * y: 50,
  3823. * width: 100,
  3824. * height: 100,
  3825. * fillStyle: '#1F6D91',
  3826. * translation: {
  3827. * x: 50,
  3828. * y: 50
  3829. * }
  3830. * }]
  3831. * });
  3832. *
  3833. * **Note:** Transform configs are *always* performed in the following
  3834. * order:
  3835. *
  3836. * 1. Scaling
  3837. * 2. Rotation
  3838. * 3. Translation
  3839. *
  3840. * @cfg {Number} translation.x
  3841. * The amount to translate the sprite along the x-axis.
  3842. *
  3843. * @cfg {Number} translation.y
  3844. * The amount to translate the sprite along the y-axis.
  3845. */
  3846. aliases: {
  3847. "stroke": "strokeStyle",
  3848. "fill": "fillStyle",
  3849. "color": "fillStyle",
  3850. "stroke-width": "lineWidth",
  3851. "stroke-linecap": "lineCap",
  3852. "stroke-linejoin": "lineJoin",
  3853. "stroke-miterlimit": "miterLimit",
  3854. "text-anchor": "textAlign",
  3855. "opacity": "globalAlpha",
  3856. translateX: "translationX",
  3857. translateY: "translationY",
  3858. rotateRads: "rotationRads",
  3859. rotateCenterX: "rotationCenterX",
  3860. rotateCenterY: "rotationCenterY",
  3861. scaleX: "scalingX",
  3862. scaleY: "scalingY",
  3863. scaleCenterX: "scalingCenterX",
  3864. scaleCenterY: "scalingCenterY"
  3865. },
  3866. defaults: {
  3867. hidden: false,
  3868. zIndex: 0,
  3869. strokeStyle: "none",
  3870. fillStyle: "none",
  3871. lineWidth: 1,
  3872. lineDash: [],
  3873. lineDashOffset: 0,
  3874. lineCap: "butt",
  3875. lineJoin: "miter",
  3876. miterLimit: 10,
  3877. shadowColor: "none",
  3878. shadowOffsetX: 0,
  3879. shadowOffsetY: 0,
  3880. shadowBlur: 0,
  3881. globalAlpha: 1,
  3882. strokeOpacity: 1,
  3883. fillOpacity: 1,
  3884. transformFillStroke: false,
  3885. translationX: 0,
  3886. translationY: 0,
  3887. rotationRads: 0,
  3888. rotationCenterX: null,
  3889. rotationCenterY: null,
  3890. scalingX: 1,
  3891. scalingY: 1,
  3892. scalingCenterX: null,
  3893. scalingCenterY: null,
  3894. constrainGradients: false
  3895. },
  3896. triggers: {
  3897. zIndex: "zIndex",
  3898. globalAlpha: "canvas",
  3899. globalCompositeOperation: "canvas",
  3900. transformFillStroke: "canvas",
  3901. strokeStyle: "canvas",
  3902. fillStyle: "canvas",
  3903. strokeOpacity: "canvas",
  3904. fillOpacity: "canvas",
  3905. lineWidth: "canvas",
  3906. lineCap: "canvas",
  3907. lineJoin: "canvas",
  3908. lineDash: "canvas",
  3909. lineDashOffset: "canvas",
  3910. miterLimit: "canvas",
  3911. shadowColor: "canvas",
  3912. shadowOffsetX: "canvas",
  3913. shadowOffsetY: "canvas",
  3914. shadowBlur: "canvas",
  3915. translationX: "transform",
  3916. translationY: "transform",
  3917. rotationRads: "transform",
  3918. rotationCenterX: "transform",
  3919. rotationCenterY: "transform",
  3920. scalingX: "transform",
  3921. scalingY: "transform",
  3922. scalingCenterX: "transform",
  3923. scalingCenterY: "transform",
  3924. constrainGradients: "canvas"
  3925. },
  3926. updaters: {
  3927. // 'bbox' updater is meant to be called by subclasses when changes
  3928. // to attributes are expected to result in a change in sprite's dimensions.
  3929. bbox: 'bboxUpdater',
  3930. zIndex: function(attr) {
  3931. attr.dirtyZIndex = true;
  3932. },
  3933. transform: function(attr) {
  3934. attr.dirtyTransform = true;
  3935. attr.bbox.transform.dirty = true;
  3936. }
  3937. }
  3938. }
  3939. },
  3940. /**
  3941. * @property {Object} attr
  3942. * The visual attributes of the sprite, e.g. strokeStyle, fillStyle, lineWidth...
  3943. */
  3944. /**
  3945. * @cfg {Ext.draw.modifier.Animation} animation
  3946. * @accessor
  3947. */
  3948. config: {
  3949. /**
  3950. * @private
  3951. * @cfg {Ext.draw.Surface/Ext.draw.sprite.Instancing/Ext.draw.sprite.Composite} parent
  3952. * The immediate parent of the sprite. Not necessarily a surface.
  3953. */
  3954. parent: null,
  3955. /**
  3956. * @private
  3957. * @cfg {Ext.draw.Surface} surface
  3958. * The surface that this sprite is rendered into.
  3959. * This config is not meant to be used directly.
  3960. * Please use the {@link Ext.draw.Surface#add} method instead.
  3961. */
  3962. surface: null
  3963. },
  3964. onClassExtended: function(subClass, data) {
  3965. // The `def` here is no longer a config, but an instance
  3966. // of the AttributeDefinition class created with that config,
  3967. // which can now be retrieved from `initialConfig`.
  3968. var superclassCfg = subClass.superclass.self.def.initialConfig,
  3969. ownCfg = data.inheritableStatics && data.inheritableStatics.def,
  3970. cfg;
  3971. // If sprite defines attributes of its own, merge that with those of its parent.
  3972. if (ownCfg) {
  3973. cfg = Ext.Object.merge({}, superclassCfg, ownCfg);
  3974. subClass.def = new Ext.draw.sprite.AttributeDefinition(cfg);
  3975. delete data.inheritableStatics.def;
  3976. } else {
  3977. subClass.def = new Ext.draw.sprite.AttributeDefinition(superclassCfg);
  3978. }
  3979. subClass.def.spriteClass = subClass;
  3980. },
  3981. constructor: function(config) {
  3982. //<debug>
  3983. if (Ext.getClassName(this) === 'Ext.draw.sprite.Sprite') {
  3984. throw 'Ext.draw.sprite.Sprite is an abstract class';
  3985. }
  3986. //</debug>
  3987. // eslint-disable-next-line vars-on-top
  3988. var me = this,
  3989. attributeDefinition = me.self.def,
  3990. // It is important to get defaults (make sure
  3991. // 'defaults' config applier of the AttributeDefinition is called,
  3992. // since it is initialized lazily) before the attributes
  3993. // are initialized ('initializeAttributes' call).
  3994. defaults = attributeDefinition.getDefaults(),
  3995. processors = attributeDefinition.getProcessors(),
  3996. modifiers, name;
  3997. config = Ext.isObject(config) ? config : {};
  3998. me.id = config.id || Ext.id(null, 'ext-sprite-');
  3999. me.attr = {};
  4000. // Observable's constructor also calls the initConfig for us.
  4001. me.mixins.observable.constructor.apply(me, arguments);
  4002. modifiers = Ext.Array.from(config.modifiers, true);
  4003. me.createModifiers(modifiers);
  4004. me.initializeAttributes();
  4005. me.setAttributes(defaults, true);
  4006. //<debug>
  4007. for (name in config) {
  4008. if (name in processors && me['get' + name.charAt(0).toUpperCase() + name.substr(1)]) {
  4009. Ext.raise('The ' + me.$className + ' sprite has both a config and an attribute with the same name: ' + name + '.');
  4010. }
  4011. }
  4012. //</debug>
  4013. me.setAttributes(config);
  4014. },
  4015. updateSurface: function(surface, oldSurface) {
  4016. if (oldSurface) {
  4017. oldSurface.remove(this);
  4018. }
  4019. },
  4020. /**
  4021. * @private
  4022. * Current state of the sprite.
  4023. * Set to `true` if the sprite needs to be repainted.
  4024. * @cfg {Boolean} dirty
  4025. * @accessor
  4026. */
  4027. getDirty: function() {
  4028. return this.attr.dirty;
  4029. },
  4030. setDirty: function(dirty) {
  4031. var parent;
  4032. // This could have been a regular attribute.
  4033. // Instead, it's a hidden one, which is initialized inside in the
  4034. // Target's modifier `prepareAttributes` method and is exposed
  4035. // as a config. The idea is to skip the modifier chain when
  4036. // we simply need to change the sprite's state and notify
  4037. // the sprite's parent.
  4038. this.attr.dirty = dirty;
  4039. if (dirty) {
  4040. parent = this.getParent();
  4041. if (parent) {
  4042. parent.setDirty(true);
  4043. }
  4044. }
  4045. },
  4046. addModifier: function(modifier, reinitializeAttributes) {
  4047. var me = this,
  4048. mods = me.modifiers,
  4049. animation = mods.animation,
  4050. target = mods.target,
  4051. type;
  4052. if (!(modifier instanceof Ext.draw.modifier.Modifier)) {
  4053. type = typeof modifier === 'string' ? modifier : modifier.type;
  4054. if (type && !mods[type]) {
  4055. mods[type] = modifier = Ext.factory(modifier, null, null, 'modifier');
  4056. }
  4057. }
  4058. modifier.setSprite(me);
  4059. if (modifier.preFx || modifier.config && modifier.config.preFx) {
  4060. if (animation._lower) {
  4061. animation._lower.setUpper(modifier);
  4062. }
  4063. modifier.setUpper(animation);
  4064. } else {
  4065. target._lower.setUpper(modifier);
  4066. modifier.setUpper(target);
  4067. }
  4068. if (reinitializeAttributes) {
  4069. me.initializeAttributes();
  4070. }
  4071. return modifier;
  4072. },
  4073. createModifiers: function(modifiers) {
  4074. var me = this,
  4075. Modifier = Ext.draw.modifier,
  4076. animation = me.getInitialConfig().animation,
  4077. mods, i, ln;
  4078. // Create default modifiers.
  4079. me.modifiers = mods = {
  4080. target: new Modifier.Target({
  4081. sprite: me
  4082. }),
  4083. animation: new Modifier.Animation(Ext.apply({
  4084. sprite: me
  4085. }, animation))
  4086. };
  4087. // Link modifiers.
  4088. mods.animation.setUpper(mods.target);
  4089. for (i = 0 , ln = modifiers.length; i < ln; i++) {
  4090. me.addModifier(modifiers[i], false);
  4091. }
  4092. return mods;
  4093. },
  4094. /**
  4095. * Returns the current animation instance.
  4096. * return {Ext.draw.modifier.Animation} The animation modifier used to animate the
  4097. * sprite
  4098. */
  4099. getAnimation: function() {
  4100. return this.modifiers.animation;
  4101. },
  4102. /**
  4103. * Sets the animation config used by the sprite when animating the sprite's
  4104. * attributes and transformation properties.
  4105. *
  4106. * var drawCt = Ext.create({
  4107. * xtype: 'draw',
  4108. * renderTo: document.body,
  4109. * width: 400,
  4110. * height: 400,
  4111. * sprites: [{
  4112. * type: 'rect',
  4113. * x: 50,
  4114. * y: 50,
  4115. * width: 100,
  4116. * height: 100,
  4117. * fillStyle: '#1F6D91'
  4118. * }]
  4119. * });
  4120. *
  4121. * var rect = drawCt.getSurface().getItems()[0];
  4122. *
  4123. * rect.setAnimation({
  4124. * duration: 1000,
  4125. * easing: 'elasticOut'
  4126. * });
  4127. *
  4128. * Ext.defer(function () {
  4129. * rect.setAttributes({
  4130. * width: 250
  4131. * });
  4132. * }, 500);
  4133. *
  4134. * @param {Object} config The Ext.draw.modifier.Animation config for this sprite's
  4135. * animations.
  4136. */
  4137. setAnimation: function(config) {
  4138. if (!this.isConfiguring) {
  4139. this.modifiers.animation.setConfig(config || {
  4140. duration: 0
  4141. });
  4142. }
  4143. },
  4144. initializeAttributes: function() {
  4145. this.modifiers.target.prepareAttributes(this.attr);
  4146. },
  4147. /**
  4148. * @private
  4149. * Calls updaters triggered by changes to sprite attributes.
  4150. * @param attr The attributes of a sprite or its instance.
  4151. */
  4152. callUpdaters: function(attr) {
  4153. var me = this,
  4154. updaters = me.self.def.getUpdaters(),
  4155. any = false,
  4156. dirty = false,
  4157. pendingUpdaters, flags, updater, fn;
  4158. attr = attr || this.attr;
  4159. pendingUpdaters = attr.pendingUpdaters;
  4160. // If updaters set sprite attributes that trigger other updaters,
  4161. // those updaters are not called right away, but wait until all current
  4162. // updaters are called (till the next do/while loop iteration).
  4163. me.callUpdaters = Ext.emptyFn;
  4164. // Hide class method from the instance.
  4165. do {
  4166. any = false;
  4167. for (updater in pendingUpdaters) {
  4168. any = true;
  4169. flags = pendingUpdaters[updater];
  4170. delete pendingUpdaters[updater];
  4171. fn = updaters[updater];
  4172. if (typeof fn === 'string') {
  4173. fn = me[fn];
  4174. }
  4175. if (fn) {
  4176. fn.call(me, attr, flags);
  4177. }
  4178. }
  4179. dirty = dirty || any;
  4180. } while (any);
  4181. delete me.callUpdaters;
  4182. // Restore class method.
  4183. if (dirty) {
  4184. me.setDirty(true);
  4185. }
  4186. },
  4187. /**
  4188. * @private
  4189. */
  4190. callUpdater: function(attr, updater, triggers) {
  4191. this.scheduleUpdater(attr, updater, triggers);
  4192. this.callUpdaters(attr);
  4193. },
  4194. /**
  4195. * @private
  4196. * Schedules specified updaters to be called.
  4197. * Updaters are called implicitly as a result of a change to sprite attributes.
  4198. * But sometimes it may be required to call an updater without setting an attribute,
  4199. * and without messing up the updater call order (by calling the updater immediately).
  4200. * For example:
  4201. *
  4202. * updaters: {
  4203. * onDataX: function (attr) {
  4204. * this.processDataX();
  4205. * // Process data Y every time data X is processed.
  4206. * // Call the onDataY updater as if changes to dataY attribute itself
  4207. * // triggered the update.
  4208. * this.scheduleUpdaters(attr, {onDataY: ['dataY']});
  4209. * // Alternatively:
  4210. * // this.scheduleUpdaters(attr, ['onDataY'], ['dataY']);
  4211. * }
  4212. * }
  4213. *
  4214. * @param {Object} attr The attributes object (not necesseraly of a sprite,
  4215. * but of its instance).
  4216. * @param {Object/String[]} updaters A map of updaters to be called to attributes
  4217. * that triggered the update.
  4218. * @param {String[]} [triggers] Attributes that triggered the update. An optional parameter.
  4219. * If used, the `updaters` parameter will be treated as an array of updaters to be called.
  4220. */
  4221. scheduleUpdaters: function(attr, updaters, triggers) {
  4222. var updater, i, ln;
  4223. attr = attr || this.attr;
  4224. if (triggers) {
  4225. for (i = 0 , ln = updaters.length; i < ln; i++) {
  4226. updater = updaters[i];
  4227. this.scheduleUpdater(attr, updater, triggers);
  4228. }
  4229. } else {
  4230. for (updater in updaters) {
  4231. triggers = updaters[updater];
  4232. this.scheduleUpdater(attr, updater, triggers);
  4233. }
  4234. }
  4235. },
  4236. /**
  4237. * @private
  4238. * @param attr {Object} The attributes object (not necesseraly of a sprite,
  4239. * but of its instance).
  4240. * @param updater {String} Updater to be called.
  4241. * @param {String[]} [triggers] Attributes that triggered the update.
  4242. */
  4243. scheduleUpdater: function(attr, updater, triggers) {
  4244. var pendingUpdaters;
  4245. triggers = triggers || [];
  4246. attr = attr || this.attr;
  4247. pendingUpdaters = attr.pendingUpdaters;
  4248. if (updater in pendingUpdaters) {
  4249. if (triggers.length) {
  4250. pendingUpdaters[updater] = Ext.Array.merge(pendingUpdaters[updater], triggers);
  4251. }
  4252. } else {
  4253. pendingUpdaters[updater] = triggers;
  4254. }
  4255. },
  4256. /**
  4257. * Set attributes of the sprite.
  4258. * By default only the attributes that have processors will be set
  4259. * and all other attributes will be filtered out as a result of the
  4260. * normalization process.
  4261. * The normalization process can be skipped. In that case all the given
  4262. * attributes will be set unprocessed. This will result in better
  4263. * performance, but might also pollute the sprite's attributes with
  4264. * unwanted attributes or attributes with invalid values, if one is not
  4265. * careful. See also {@link #setAttributesBypassingNormalization}.
  4266. * If normalization is skipped, one may also chose to avoid copying
  4267. * the given object. This may result in even better performance, but
  4268. * only in cases where most of the attributes have values that are
  4269. * different from the old values, because copying additionally checks
  4270. * if the value has changed.
  4271. *
  4272. * @param {Object} changes The content of the change.
  4273. * @param {Boolean} [bypassNormalization] `true` to avoid normalization of the given changes.
  4274. * @param {Boolean} [avoidCopy] `true` to avoid copying the `changes` object.
  4275. * `bypassNormalization` should also be `true`. The content of object may be destroyed.
  4276. */
  4277. setAttributes: function(changes, bypassNormalization, avoidCopy) {
  4278. var me = this,
  4279. changesToPush;
  4280. //<debug>
  4281. if (me.destroyed) {
  4282. Ext.Error.raise("Setting attributes of a destroyed sprite.");
  4283. }
  4284. //</debug>
  4285. if (bypassNormalization) {
  4286. if (avoidCopy) {
  4287. changesToPush = changes;
  4288. } else {
  4289. changesToPush = Ext.apply({}, changes);
  4290. }
  4291. } else {
  4292. changesToPush = me.self.def.normalize(changes);
  4293. }
  4294. me.modifiers.target.pushDown(me.attr, changesToPush);
  4295. },
  4296. /**
  4297. * Set attributes of the sprite, assuming the names and values have already been
  4298. * normalized.
  4299. *
  4300. * @deprecated 6.5.0 Use setAttributes directly with bypassNormalization argument being `true`.
  4301. * @param {Object} changes The content of the change.
  4302. * @param {Boolean} [avoidCopy] `true` to avoid copying the `changes` object.
  4303. * The content of object may be destroyed.
  4304. */
  4305. setAttributesBypassingNormalization: function(changes, avoidCopy) {
  4306. return this.setAttributes(changes, true, avoidCopy);
  4307. },
  4308. /**
  4309. * @private
  4310. */
  4311. bboxUpdater: function(attr) {
  4312. var hasRotation = attr.rotationRads !== 0,
  4313. hasScaling = attr.scalingX !== 1 || attr.scalingY !== 1,
  4314. noRotationCenter = attr.rotationCenterX === null || attr.rotationCenterY === null,
  4315. noScalingCenter = attr.scalingCenterX === null || attr.scalingCenterY === null;
  4316. // 'bbox' is not a standard attribute (in the sense that it doesn't have
  4317. // a processor = not explicitly declared and cannot be set by a user)
  4318. // and is calculated automatically by the 'getBBox' method.
  4319. // The 'bbox' attribute is created by the 'prepareAttributes' method
  4320. // of the Target modifier at construction time.
  4321. // Both plain and tranformed bounding boxes need to be updated.
  4322. // Mark them as such below.
  4323. attr.bbox.plain.dirty = true;
  4324. // updated by the 'updatePlainBBox' method
  4325. // Before transformed bounding box can be updated,
  4326. // we must ensure that we have correct forward and inverse
  4327. // transformation matrices (which are also created by the Target modifier),
  4328. // so that they reflect the current state of the scaling, rotation
  4329. // and other transformation attributes.
  4330. // The 'applyTransformations' method does just that.
  4331. // The 'dirtyTransform' flag (another implicit attribute)
  4332. // is set to true when any of the transformation attributes change,
  4333. // to let us know that transformation matrices need to be updated.
  4334. attr.bbox.transform.dirty = true;
  4335. // updated by the 'updateTransformedBBox' method
  4336. if (hasRotation && noRotationCenter || hasScaling && noScalingCenter) {
  4337. this.scheduleUpdater(attr, 'transform');
  4338. }
  4339. },
  4340. /**
  4341. * Returns the bounding box for the given Sprite as calculated with the Canvas engine.
  4342. *
  4343. * @param {Boolean} [isWithoutTransform] Whether to calculate the bounding box
  4344. * with the current transforms or not.
  4345. */
  4346. getBBox: function(isWithoutTransform) {
  4347. var me = this,
  4348. attr = me.attr,
  4349. bbox = attr.bbox,
  4350. plain = bbox.plain,
  4351. transform = bbox.transform;
  4352. if (plain.dirty) {
  4353. me.updatePlainBBox(plain);
  4354. plain.dirty = false;
  4355. }
  4356. if (!isWithoutTransform) {
  4357. // If tranformations are to be applied ('dirtyTransform' is true),
  4358. // then this will itself call the 'getBBox' method
  4359. // to get the plain untransformed bbox and calculate its center.
  4360. me.applyTransformations();
  4361. if (transform.dirty) {
  4362. me.updateTransformedBBox(transform, plain);
  4363. transform.dirty = false;
  4364. }
  4365. return transform;
  4366. }
  4367. return plain;
  4368. },
  4369. /**
  4370. * @method
  4371. * @protected
  4372. * Subclass will fill the plain object with `x`, `y`, `width`, `height` information
  4373. * of the plain bounding box of this sprite.
  4374. *
  4375. * @param {Object} plain Target object.
  4376. */
  4377. updatePlainBBox: Ext.emptyFn,
  4378. /**
  4379. * @protected
  4380. * Subclass will fill the plain object with `x`, `y`, `width`, `height` information
  4381. * of the transformed bounding box of this sprite.
  4382. *
  4383. * @param {Object} transform Target object (transformed bounding box) to populate.
  4384. * @param {Object} plain Untransformed bounding box.
  4385. */
  4386. updateTransformedBBox: function(transform, plain) {
  4387. this.attr.matrix.transformBBox(plain, 0, transform);
  4388. },
  4389. /**
  4390. * Subclass can rewrite this function to gain better performance.
  4391. * @param {Boolean} isWithoutTransform
  4392. * @return {Array}
  4393. */
  4394. getBBoxCenter: function(isWithoutTransform) {
  4395. var bbox = this.getBBox(isWithoutTransform);
  4396. if (bbox) {
  4397. return [
  4398. bbox.x + bbox.width * 0.5,
  4399. bbox.y + bbox.height * 0.5
  4400. ];
  4401. } else {
  4402. return [
  4403. 0,
  4404. 0
  4405. ];
  4406. }
  4407. },
  4408. /**
  4409. * Hide the sprite.
  4410. * @return {Ext.draw.sprite.Sprite} this
  4411. * @chainable
  4412. */
  4413. hide: function() {
  4414. this.attr.hidden = true;
  4415. this.setDirty(true);
  4416. return this;
  4417. },
  4418. /**
  4419. * Show the sprite.
  4420. * @return {Ext.draw.sprite.Sprite} this
  4421. * @chainable
  4422. */
  4423. show: function() {
  4424. this.attr.hidden = false;
  4425. this.setDirty(true);
  4426. return this;
  4427. },
  4428. /**
  4429. * Applies sprite's attributes to the given context.
  4430. * @param {Object} ctx Context to apply sprite's attributes to.
  4431. * @param {Array} rect The rect of the context to be affected by gradients.
  4432. */
  4433. useAttributes: function(ctx, rect) {
  4434. // Always (force) apply transformation to sprite instances,
  4435. // even if their 'dirtyTransform' flag is false.
  4436. // The 'dirtyTransform' flag of an instance may never be set to 'true', as the
  4437. // 'transform' updater won't ever be called for sprite instances that have
  4438. // the same transform attributes as their template, because there's nothing to update
  4439. // (an instance is simply a prototype chained template's 'attr' object, that only
  4440. // has own properties for attributes whose values are different).
  4441. // Making the modifier recognize transform attributes set on sprite instances
  4442. // (see Ext.draw.modifier.Modifier's 'pushDown' method, where attributes with
  4443. // same values are removed from the 'changes' object) and making sure their 'dirtyTransform'
  4444. // flag is set to 'true' is not a correct solution here, because of the way instances
  4445. // are rendered (see Ext.draw.sprite.Instancing's 'render' method) - there is no way
  4446. // an instance wounldn't want its 'applyTransformations' method called.
  4447. this.applyTransformations(this.isSpriteInstance);
  4448. // eslint-disable-next-line vars-on-top
  4449. var attr = this.attr,
  4450. canvasAttributes = attr.canvasAttributes,
  4451. strokeStyle = canvasAttributes.strokeStyle,
  4452. fillStyle = canvasAttributes.fillStyle,
  4453. lineDash = canvasAttributes.lineDash,
  4454. lineDashOffset = canvasAttributes.lineDashOffset,
  4455. id;
  4456. if (strokeStyle) {
  4457. if (strokeStyle.isGradient) {
  4458. ctx.strokeStyle = 'black';
  4459. ctx.strokeGradient = strokeStyle;
  4460. } else {
  4461. ctx.strokeGradient = false;
  4462. }
  4463. }
  4464. if (fillStyle) {
  4465. if (fillStyle.isGradient) {
  4466. ctx.fillStyle = 'black';
  4467. ctx.fillGradient = fillStyle;
  4468. } else {
  4469. ctx.fillGradient = false;
  4470. }
  4471. }
  4472. if (lineDash) {
  4473. ctx.setLineDash(lineDash);
  4474. }
  4475. // Only set lineDashOffset to contexts that support the property (excludes VML).
  4476. if (Ext.isNumber(lineDashOffset) && Ext.isNumber(ctx.lineDashOffset)) {
  4477. ctx.lineDashOffset = lineDashOffset;
  4478. }
  4479. for (id in canvasAttributes) {
  4480. if (canvasAttributes[id] !== undefined && canvasAttributes[id] !== ctx[id]) {
  4481. ctx[id] = canvasAttributes[id];
  4482. }
  4483. }
  4484. this.setGradientBBox(ctx, rect);
  4485. },
  4486. setGradientBBox: function(ctx, rect) {
  4487. var attr = this.attr;
  4488. if (attr.constrainGradients) {
  4489. ctx.setGradientBBox({
  4490. x: rect[0],
  4491. y: rect[1],
  4492. width: rect[2],
  4493. height: rect[3]
  4494. });
  4495. } else {
  4496. ctx.setGradientBBox(this.getBBox(attr.transformFillStroke));
  4497. }
  4498. },
  4499. /**
  4500. * @private
  4501. *
  4502. * Calculates forward and inverse transform matrices from sprite's attributes.
  4503. * Transformations are applied in the following order: Scaling, Rotation, Translation.
  4504. * @param {Boolean} [force=false] Forces recalculation of transform matrices even when
  4505. * sprite's transform attributes supposedly haven't changed.
  4506. */
  4507. applyTransformations: function(force) {
  4508. if (!force && !this.attr.dirtyTransform) {
  4509. return;
  4510. }
  4511. // eslint-disable-next-line vars-on-top
  4512. var me = this,
  4513. attr = me.attr,
  4514. center = me.getBBoxCenter(true),
  4515. centerX = center[0],
  4516. centerY = center[1],
  4517. tx = attr.translationX,
  4518. ty = attr.translationY,
  4519. sx = attr.scalingX,
  4520. sy = attr.scalingY === null ? attr.scalingX : attr.scalingY,
  4521. scx = attr.scalingCenterX === null ? centerX : attr.scalingCenterX,
  4522. scy = attr.scalingCenterY === null ? centerY : attr.scalingCenterY,
  4523. rad = attr.rotationRads,
  4524. rcx = attr.rotationCenterX === null ? centerX : attr.rotationCenterX,
  4525. rcy = attr.rotationCenterY === null ? centerY : attr.rotationCenterY,
  4526. cos = Math.cos(rad),
  4527. sin = Math.sin(rad),
  4528. tx_4, ty_4;
  4529. if (sx === 1 && sy === 1) {
  4530. scx = 0;
  4531. scy = 0;
  4532. }
  4533. if (rad === 0) {
  4534. rcx = 0;
  4535. rcy = 0;
  4536. }
  4537. // Translation component after steps 1-4 (see below).
  4538. // Saving it here to prevent double calculation.
  4539. tx_4 = scx * (1 - sx) - rcx;
  4540. ty_4 = scy * (1 - sy) - rcy;
  4541. /* eslint-disable max-len */
  4542. // The matrix below is a result of:
  4543. // (7) (6) (5) (4) (3) (2) (1)
  4544. // | 1 0 tx | | 1 0 rcx | | cos -sin 0 | | 1 0 -rcx | | 1 0 scx | | sx 0 0 | | 1 0 -scx |
  4545. // | 0 1 ty | * | 0 1 rcy | * | sin cos 0 | * | 0 1 -rcy | * | 0 1 scy | * | 0 sy 0 | * | 0 1 -scy |
  4546. // | 0 0 1 | | 0 0 1 | | 0 0 1 | | 0 0 1 | | 0 0 1 | | 0 0 0 | | 0 0 1 |
  4547. /* eslint-enable max-len */
  4548. attr.matrix.elements = [
  4549. cos * sx,
  4550. sin * sx,
  4551. -sin * sy,
  4552. cos * sy,
  4553. cos * tx_4 - sin * ty_4 + rcx + tx,
  4554. sin * tx_4 + cos * ty_4 + rcy + ty
  4555. ];
  4556. attr.matrix.inverse(attr.inverseMatrix);
  4557. attr.dirtyTransform = false;
  4558. attr.bbox.transform.dirty = true;
  4559. },
  4560. /**
  4561. * Pre-multiplies the current transformation matrix of a sprite with the given matrix.
  4562. * If `isSplit` parameter is `true`, the resulting matrix is also split into
  4563. * individual components (scaling, rotation, translation) and corresponding sprite
  4564. * attributes are updated. The shearing component is not extracted.
  4565. * Note, that transformation attributes work as if transformations are applied to the
  4566. * local coordinate system of a sprite, while matrix transformations transform
  4567. * the global coordinate space or the surface grid.
  4568. * Since the `transform` method returns the sprite itself, calls to the method
  4569. * can be chained. And if updating sprite transformation attributes is desired,
  4570. * it can be achieved by setting the `isSplit` parameter of the last call to `true`.
  4571. * For example:
  4572. *
  4573. * sprite.transform(matrixA).transform(matrixB).transform(matrixC, true);
  4574. *
  4575. * See also: {@link #setTransform}
  4576. *
  4577. * @param {Ext.draw.Matrix/Number[]} matrix A transformation matrix or array of its elements.
  4578. * @param {Boolean} [isSplit=false] If 'true', transformation attributes are updated.
  4579. * @return {Ext.draw.sprite.Sprite} This sprite.
  4580. */
  4581. transform: function(matrix, isSplit) {
  4582. var attr = this.attr,
  4583. spriteMatrix = attr.matrix,
  4584. elements;
  4585. if (matrix && matrix.isMatrix) {
  4586. elements = matrix.elements;
  4587. } else {
  4588. elements = matrix;
  4589. }
  4590. //<debug>
  4591. if (!(Ext.isArray(elements) && elements.length === 6)) {
  4592. Ext.raise("An instance of Ext.draw.Matrix or an array of 6 numbers is expected.");
  4593. }
  4594. //</debug>
  4595. spriteMatrix.prepend.apply(spriteMatrix, elements.slice());
  4596. spriteMatrix.inverse(attr.inverseMatrix);
  4597. if (isSplit) {
  4598. this.updateTransformAttributes();
  4599. }
  4600. attr.dirtyTransform = false;
  4601. attr.bbox.transform.dirty = true;
  4602. this.setDirty(true);
  4603. return this;
  4604. },
  4605. /**
  4606. * @private
  4607. */
  4608. updateTransformAttributes: function() {
  4609. var attr = this.attr,
  4610. split = attr.matrix.split();
  4611. attr.rotationRads = split.rotate;
  4612. attr.rotationCenterX = 0;
  4613. attr.rotationCenterY = 0;
  4614. attr.scalingX = split.scaleX;
  4615. attr.scalingY = split.scaleY;
  4616. attr.scalingCenterX = 0;
  4617. attr.scalingCenterY = 0;
  4618. attr.translationX = split.translateX;
  4619. attr.translationY = split.translateY;
  4620. },
  4621. /**
  4622. * Resets current transformation matrix of a sprite to the identify matrix.
  4623. * @param {Boolean} [isSplit=false] If 'true', transformation attributes are updated.
  4624. * @return {Ext.draw.sprite.Sprite} This sprite.
  4625. */
  4626. resetTransform: function(isSplit) {
  4627. var attr = this.attr;
  4628. attr.matrix.reset();
  4629. attr.inverseMatrix.reset();
  4630. if (!isSplit) {
  4631. this.updateTransformAttributes();
  4632. }
  4633. attr.dirtyTransform = false;
  4634. attr.bbox.transform.dirty = true;
  4635. this.setDirty(true);
  4636. return this;
  4637. },
  4638. /**
  4639. * Resets current transformation matrix of a sprite to the identify matrix
  4640. * and pre-multiplies it with the given matrix.
  4641. * This is effectively the same as calling {@link #resetTransform},
  4642. * followed by {@link #transform} with the same arguments.
  4643. *
  4644. * See also: {@link #transform}
  4645. *
  4646. * var drawContainer = new Ext.draw.Container({
  4647. * renderTo: Ext.getBody(),
  4648. * width: 380,
  4649. * height: 380,
  4650. * sprites: [{
  4651. * type: 'rect',
  4652. * width: 100,
  4653. * height: 100,
  4654. * fillStyle: 'red'
  4655. * }]
  4656. * });
  4657. *
  4658. * var main = drawContainer.getSurface();
  4659. * var rect = main.getItems()[0];
  4660. *
  4661. * var m = new Ext.draw.Matrix().rotate(Math.PI, 100, 100);
  4662. *
  4663. * rect.setTransform(m);
  4664. * main.renderFrame();
  4665. *
  4666. * There may be times where the transformation you need to apply cannot easily be
  4667. * accomplished using the sprite’s convenience transform methods. Or, you may want
  4668. * to pass a matrix directly to the sprite in order to set a transformation. The
  4669. * `setTransform` method allows for this sort of advanced usage as well. The
  4670. * following tables show each transformation matrix used when applying
  4671. * transformations to a sprite.
  4672. *
  4673. * ### Translate
  4674. * <table style="text-align: center;">
  4675. * <tr>
  4676. * <td style="font-weight: normal;">1</td>
  4677. * <td style="font-weight: normal;">0</td>
  4678. * <td style="font-weight: normal;">tx</td>
  4679. * </tr>
  4680. * <tr>
  4681. * <td>0</td>
  4682. * <td>1</td>
  4683. * <td>ty</td>
  4684. * </tr>
  4685. * <tr>
  4686. * <td>0</td>
  4687. * <td>0</td>
  4688. * <td>1</td>
  4689. * </tr>
  4690. * </table>
  4691. *
  4692. * ### Rotate (θ is the angle of rotation)
  4693. * <table style="text-align: center;">
  4694. * <tr>
  4695. * <td style="font-weight: normal;">cos(θ)</td>
  4696. * <td style="font-weight: normal;">-sin(θ)</td>
  4697. * <td style="font-weight: normal;">0</td>
  4698. * </tr>
  4699. * <tr>
  4700. * <td>0</td>
  4701. * <td>cos(θ)</td>
  4702. * <td>0</td>
  4703. * </tr>
  4704. * <tr>
  4705. * <td>0</td>
  4706. * <td>0</td>
  4707. * <td>1</td>
  4708. * </tr>
  4709. * </table>
  4710. *
  4711. * ### Scale
  4712. * <table style="text-align: center;">
  4713. * <tr>
  4714. * <td style="font-weight: normal;">sx</td>
  4715. * <td style="font-weight: normal;">0</td>
  4716. * <td style="font-weight: normal;">0</td>
  4717. * </tr>
  4718. * <tr>
  4719. * <td>0</td>
  4720. * <td>cos(θ)</td>
  4721. * <td>0</td>
  4722. * </tr>
  4723. * <tr>
  4724. * <td>0</td>
  4725. * <td>0</td>
  4726. * <td>1</td>
  4727. * </tr>
  4728. * </table>
  4729. *
  4730. * ### Shear X _(λ is the distance on the x axis to shear by)_
  4731. * <table style="text-align: center;">
  4732. * <tr>
  4733. * <td style="font-weight: normal;">1</td>
  4734. * <td style="font-weight: normal;">λx</td>
  4735. * <td style="font-weight: normal;">0</td>
  4736. * </tr>
  4737. * <tr>
  4738. * <td>0</td>
  4739. * <td>1</td>
  4740. * <td>0</td>
  4741. * </tr>
  4742. * <tr>
  4743. * <td>0</td>
  4744. * <td>0</td>
  4745. * <td>1</td>
  4746. * </tr>
  4747. * </table>
  4748. *
  4749. * ### Shear Y (λ is the distance on the y axis to shear by)
  4750. * <table style="text-align: center;">
  4751. * <tr>
  4752. * <td style="font-weight: normal;">1</td>
  4753. * <td style="font-weight: normal;">0</td>
  4754. * <td style="font-weight: normal;">0</td>
  4755. * </tr>
  4756. * <tr>
  4757. * <td>λy</td>
  4758. * <td>1</td>
  4759. * <td>0</td>
  4760. * </tr>
  4761. * <tr>
  4762. * <td>0</td>
  4763. * <td>0</td>
  4764. * <td>1</td>
  4765. * </tr>
  4766. * </table>
  4767. *
  4768. * ### Skew X (θ is the angle to skew by)
  4769. * <table style="text-align: center;">
  4770. * <tr>
  4771. * <td style="font-weight: normal;">1</td>
  4772. * <td style="font-weight: normal;">tan(θ)</td>
  4773. * <td style="font-weight: normal;">0</td>
  4774. * </tr>
  4775. * <tr>
  4776. * <td>0</td>
  4777. * <td>1</td>
  4778. * <td>0</td>
  4779. * </tr>
  4780. * <tr>
  4781. * <td>0</td>
  4782. * <td>0</td>
  4783. * <td>1</td>
  4784. * </tr>
  4785. * </table>
  4786. *
  4787. * ### Skew Y (θ is the angle to skew by)
  4788. * <table style="text-align: center;">
  4789. * <tr>
  4790. * <td style="font-weight: normal;">1</td>
  4791. * <td style="font-weight: normal;">0</td>
  4792. * <td style="font-weight: normal;">0</td>
  4793. * </tr>
  4794. * <tr>
  4795. * <td>tan(θ)</td>
  4796. * <td>1</td>
  4797. * <td>0</td>
  4798. * </tr>
  4799. * <tr>
  4800. * <td>0</td>
  4801. * <td>0</td>
  4802. * <td>1</td>
  4803. * </tr>
  4804. * </table>
  4805. *
  4806. * Multiplying matrices for translation, rotation, scaling, and shearing / skewing
  4807. * any number of times in the desired order produces a single matrix for a composite
  4808. * transformation. You can use the product as a value for the `setTransform`method
  4809. * of a sprite:
  4810. *
  4811. * mySprite.setTransform([a, b, c, d, e, f]);
  4812. *
  4813. * Where `a`, `b`, `c`, `d`, `e`, `f` are numeric values that correspond to the
  4814. * following transformation matrix components:
  4815. *
  4816. * <table style="text-align: center;">
  4817. * <tr>
  4818. * <td style="font-weight: normal;">a</td>
  4819. * <td style="font-weight: normal;">c</td>
  4820. * <td style="font-weight: normal;">e</td>
  4821. * </tr>
  4822. * <tr>
  4823. * <td>b</td>
  4824. * <td>d</td>
  4825. * <td>f</td>
  4826. * </tr>
  4827. * <tr>
  4828. * <td>0</td>
  4829. * <td>0</td>
  4830. * <td>1</td>
  4831. * </tr>
  4832. * </table>
  4833. *
  4834. * @param {Ext.draw.Matrix/Number[]} matrix The transformation matrix to apply or its
  4835. * raw elements as an array.
  4836. * @param {Boolean} [isSplit=false] If `true`, transformation attributes are updated.
  4837. * @return {Ext.draw.sprite.Sprite} This sprite.
  4838. */
  4839. setTransform: function(matrix, isSplit) {
  4840. this.resetTransform(true);
  4841. this.transform.call(this, matrix, isSplit);
  4842. return this;
  4843. },
  4844. /**
  4845. * @method
  4846. * Called before rendering.
  4847. */
  4848. preRender: Ext.emptyFn,
  4849. /**
  4850. * @method
  4851. * This is where the actual sprite rendering happens by calling `ctx` methods.
  4852. * @param {Ext.draw.Surface} surface A draw container surface.
  4853. * @param {CanvasRenderingContext2D} ctx A context object that is API compatible with the native
  4854. * [CanvasRenderingContext2D](https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D).
  4855. * @param {Number[]} surfaceClipRect The clip rect: [left, top, width, height].
  4856. * Not to be confused with the `surface.getRect()`, which represents the location
  4857. * and size of the surface in a draw container, in draw container coordinates.
  4858. * The clip rect on the other hand represents the portion of the surface that is being
  4859. * rendered, in surface coordinates.
  4860. *
  4861. * @return {*} returns `false` to stop rendering in this frame.
  4862. * All the sprites that haven't been rendered will have their dirty flag untouched.
  4863. */
  4864. render: Ext.emptyFn,
  4865. //<debug>
  4866. /**
  4867. * @private
  4868. * Renders the bounding box of transformed sprite.
  4869. */
  4870. renderBBox: function(surface, ctx) {
  4871. var bbox = this.getBBox();
  4872. ctx.beginPath();
  4873. ctx.moveTo(bbox.x, bbox.y);
  4874. ctx.lineTo(bbox.x + bbox.width, bbox.y);
  4875. ctx.lineTo(bbox.x + bbox.width, bbox.y + bbox.height);
  4876. ctx.lineTo(bbox.x, bbox.y + bbox.height);
  4877. ctx.closePath();
  4878. ctx.strokeStyle = 'red';
  4879. ctx.strokeOpacity = 1;
  4880. ctx.lineWidth = 0.5;
  4881. ctx.stroke();
  4882. },
  4883. //</debug>
  4884. /**
  4885. * Performs a hit test on the sprite.
  4886. * @param {Array} point A two-item array containing x and y coordinates of the point.
  4887. * @param {Object} options Hit testing options.
  4888. * @return {Object} A hit result object that contains more information about what
  4889. * exactly was hit or null if nothing was hit.
  4890. */
  4891. hitTest: function(point, options) {
  4892. var x, y, bbox, isBBoxHit;
  4893. // Meant to be overridden in subclasses for more precise hit testing.
  4894. // This version doesn't take any options and simply hit tests sprite's
  4895. // bounding box, if the sprite is visible.
  4896. if (this.isVisible()) {
  4897. x = point[0];
  4898. y = point[1];
  4899. bbox = this.getBBox();
  4900. isBBoxHit = bbox && x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height);
  4901. if (isBBoxHit) {
  4902. return {
  4903. sprite: this
  4904. };
  4905. }
  4906. }
  4907. return null;
  4908. },
  4909. /**
  4910. * @private
  4911. * Checks if the sprite can be seen.
  4912. * This includes the `hidden` attribute check, alpha/opacity checks,
  4913. * fill/stroke color checks and surface/parent checks.
  4914. * The method doesn't check if the sprite is off-screen.
  4915. * @return {Boolean} Returns `true`, if the sprite can be seen.
  4916. */
  4917. isVisible: function() {
  4918. var attr = this.attr,
  4919. parent = this.getParent(),
  4920. hasParent = parent && (parent.isSurface || parent.isVisible()),
  4921. isSeen = hasParent && !attr.hidden && attr.globalAlpha,
  4922. none1 = Ext.util.Color.NONE,
  4923. none2 = Ext.util.Color.RGBA_NONE,
  4924. hasFill = attr.fillOpacity && attr.fillStyle !== none1 && attr.fillStyle !== none2,
  4925. hasStroke = attr.strokeOpacity && attr.strokeStyle !== none1 && attr.strokeStyle !== none2,
  4926. result = isSeen && (hasFill || hasStroke);
  4927. return !!result;
  4928. },
  4929. repaint: function() {
  4930. var surface = this.getSurface();
  4931. if (surface) {
  4932. surface.renderFrame();
  4933. }
  4934. },
  4935. /**
  4936. * Removes this sprite from its surface.
  4937. * The sprite itself is not destroyed.
  4938. * @return {Ext.draw.sprite.Sprite} Returns the removed sprite or `null` otherwise.
  4939. */
  4940. remove: function() {
  4941. var surface = this.getSurface();
  4942. if (surface && surface.isSurface) {
  4943. return surface.remove(this);
  4944. }
  4945. return null;
  4946. },
  4947. /**
  4948. * Removes the sprite and clears all listeners.
  4949. */
  4950. destroy: function() {
  4951. var me = this,
  4952. modifier = me.modifiers.target,
  4953. currentModifier;
  4954. while (modifier) {
  4955. currentModifier = modifier;
  4956. modifier = modifier._lower;
  4957. currentModifier.destroy();
  4958. }
  4959. delete me.attr;
  4960. me.remove();
  4961. if (me.fireEvent('beforedestroy', me) !== false) {
  4962. me.fireEvent('destroy', me);
  4963. }
  4964. me.callParent();
  4965. }
  4966. }, function() {
  4967. // onClassCreated
  4968. // Create one AttributeDefinition instance per sprite class when a class is created
  4969. // and replace the `def` config with the instance that was created using that config.
  4970. // Here we only create an AttributeDefinition instance for the base Sprite class,
  4971. // attribute definitions for subclasses are created inside onClassExtended method.
  4972. this.def = new Ext.draw.sprite.AttributeDefinition(this.def);
  4973. this.def.spriteClass = this;
  4974. });
  4975. /**
  4976. * Class representing a path.
  4977. * Designed to be compatible with [CanvasPathMethods](http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#canvaspathmethods)
  4978. * and will hopefully be replaced by the browsers' implementation of the Path object.
  4979. */
  4980. Ext.define('Ext.draw.Path', {
  4981. requires: [
  4982. 'Ext.draw.Draw'
  4983. ],
  4984. statics: {
  4985. pathRe: /,?([achlmqrstvxz]),?/gi,
  4986. pathRe2: /-/gi,
  4987. pathSplitRe: /\s|,/g
  4988. },
  4989. svgString: '',
  4990. /**
  4991. * Create a path from pathString.
  4992. * @constructor
  4993. * @param {String} pathString
  4994. */
  4995. constructor: function(pathString) {
  4996. var me = this;
  4997. me.commands = [];
  4998. // Stores command letters from the SVG path data ('d' attribute).
  4999. me.params = [];
  5000. // Stores command parameters from the SVG path data.
  5001. // All command parameters are actually point coordinates as the only commands used
  5002. // are the M, L, C, Z. This makes path transformations and hit testing easier.
  5003. // Arcs are approximated using cubic Bezier curves, H and S commands are translated
  5004. // to L commands and relative commands are translated to their absolute versions.
  5005. me.cursor = null;
  5006. me.startX = 0;
  5007. me.startY = 0;
  5008. if (pathString) {
  5009. me.fromSvgString(pathString);
  5010. }
  5011. },
  5012. /**
  5013. * Clear the path.
  5014. */
  5015. clear: function() {
  5016. var me = this;
  5017. me.params.length = 0;
  5018. me.commands.length = 0;
  5019. me.cursor = null;
  5020. me.startX = 0;
  5021. me.startY = 0;
  5022. me.dirt();
  5023. },
  5024. /**
  5025. * @private
  5026. */
  5027. dirt: function() {
  5028. this.svgString = '';
  5029. },
  5030. /**
  5031. * Move to a position.
  5032. * @param {Number} x
  5033. * @param {Number} y
  5034. */
  5035. moveTo: function(x, y) {
  5036. var me = this;
  5037. if (!me.cursor) {
  5038. me.cursor = [
  5039. x,
  5040. y
  5041. ];
  5042. }
  5043. me.params.push(x, y);
  5044. me.commands.push('M');
  5045. me.startX = x;
  5046. me.startY = y;
  5047. me.cursor[0] = x;
  5048. me.cursor[1] = y;
  5049. me.dirt();
  5050. },
  5051. /**
  5052. * A straight line to a position.
  5053. * @param {Number} x
  5054. * @param {Number} y
  5055. */
  5056. lineTo: function(x, y) {
  5057. var me = this;
  5058. if (!me.cursor) {
  5059. me.cursor = [
  5060. x,
  5061. y
  5062. ];
  5063. me.params.push(x, y);
  5064. me.commands.push('M');
  5065. } else {
  5066. me.params.push(x, y);
  5067. me.commands.push('L');
  5068. }
  5069. me.cursor[0] = x;
  5070. me.cursor[1] = y;
  5071. me.dirt();
  5072. },
  5073. /**
  5074. * A cubic bezier curve to a position.
  5075. * @param {Number} cx1
  5076. * @param {Number} cy1
  5077. * @param {Number} cx2
  5078. * @param {Number} cy2
  5079. * @param {Number} x
  5080. * @param {Number} y
  5081. */
  5082. bezierCurveTo: function(cx1, cy1, cx2, cy2, x, y) {
  5083. var me = this;
  5084. if (!me.cursor) {
  5085. me.moveTo(cx1, cy1);
  5086. }
  5087. me.params.push(cx1, cy1, cx2, cy2, x, y);
  5088. me.commands.push('C');
  5089. me.cursor[0] = x;
  5090. me.cursor[1] = y;
  5091. me.dirt();
  5092. },
  5093. /**
  5094. * A quadratic bezier curve to a position.
  5095. * @param {Number} cx
  5096. * @param {Number} cy
  5097. * @param {Number} x
  5098. * @param {Number} y
  5099. */
  5100. quadraticCurveTo: function(cx, cy, x, y) {
  5101. var me = this;
  5102. if (!me.cursor) {
  5103. me.moveTo(cx, cy);
  5104. }
  5105. me.bezierCurveTo((2 * cx + me.cursor[0]) / 3, (2 * cy + me.cursor[1]) / 3, (2 * cx + x) / 3, (2 * cy + y) / 3, x, y);
  5106. },
  5107. /**
  5108. * Close this path with a straight line.
  5109. */
  5110. closePath: function() {
  5111. var me = this;
  5112. if (me.cursor) {
  5113. me.cursor = null;
  5114. me.commands.push('Z');
  5115. me.dirt();
  5116. }
  5117. },
  5118. /**
  5119. * Create a elliptic arc curve compatible with SVG's arc to instruction.
  5120. *
  5121. * The curve start from (`x1`, `y1`) and ends at (`x2`, `y2`). The ellipse
  5122. * has radius `rx` and `ry` and a rotation of `rotation`.
  5123. * @param {Number} x1
  5124. * @param {Number} y1
  5125. * @param {Number} x2
  5126. * @param {Number} y2
  5127. * @param {Number} [rx]
  5128. * @param {Number} [ry]
  5129. * @param {Number} [rotation]
  5130. */
  5131. arcTo: function(x1, y1, x2, y2, rx, ry, rotation) {
  5132. var me = this;
  5133. if (ry === undefined) {
  5134. ry = rx;
  5135. }
  5136. if (rotation === undefined) {
  5137. rotation = 0;
  5138. }
  5139. if (!me.cursor) {
  5140. me.moveTo(x1, y1);
  5141. return;
  5142. }
  5143. if (rx === 0 || ry === 0) {
  5144. me.lineTo(x1, y1);
  5145. return;
  5146. }
  5147. x2 -= x1;
  5148. y2 -= y1;
  5149. // eslint-disable-next-line vars-on-top, one-var
  5150. var x0 = me.cursor[0] - x1,
  5151. y0 = me.cursor[1] - y1,
  5152. area = x2 * y0 - y2 * x0,
  5153. cos, sin, xx, yx, xy, yy,
  5154. l0 = Math.sqrt(x0 * x0 + y0 * y0),
  5155. l2 = Math.sqrt(x2 * x2 + y2 * y2),
  5156. dist, cx, cy, temp;
  5157. // cos rx, -sin ry , x1 - cos rx x1 + ry sin y1
  5158. // sin rx, cos ry, -rx sin x1 + y1 - cos ry y1
  5159. if (area === 0) {
  5160. me.lineTo(x1, y1);
  5161. return;
  5162. }
  5163. if (ry !== rx) {
  5164. cos = Math.cos(rotation);
  5165. sin = Math.sin(rotation);
  5166. xx = cos / rx;
  5167. yx = sin / ry;
  5168. xy = -sin / rx;
  5169. yy = cos / ry;
  5170. temp = xx * x0 + yx * y0;
  5171. y0 = xy * x0 + yy * y0;
  5172. x0 = temp;
  5173. temp = xx * x2 + yx * y2;
  5174. y2 = xy * x2 + yy * y2;
  5175. x2 = temp;
  5176. } else {
  5177. x0 /= rx;
  5178. y0 /= ry;
  5179. x2 /= rx;
  5180. y2 /= ry;
  5181. }
  5182. cx = x0 * l2 + x2 * l0;
  5183. cy = y0 * l2 + y2 * l0;
  5184. dist = 1 / (Math.sin(Math.asin(Math.abs(area) / (l0 * l2)) * 0.5) * Math.sqrt(cx * cx + cy * cy));
  5185. cx *= dist;
  5186. cy *= dist;
  5187. // eslint-disable-next-line vars-on-top, one-var
  5188. var k0 = (cx * x0 + cy * y0) / (x0 * x0 + y0 * y0),
  5189. k2 = (cx * x2 + cy * y2) / (x2 * x2 + y2 * y2),
  5190. cosStart = x0 * k0 - cx,
  5191. sinStart = y0 * k0 - cy,
  5192. cosEnd = x2 * k2 - cx,
  5193. sinEnd = y2 * k2 - cy,
  5194. startAngle = Math.atan2(sinStart, cosStart),
  5195. endAngle = Math.atan2(sinEnd, cosEnd);
  5196. if (area > 0) {
  5197. if (endAngle < startAngle) {
  5198. endAngle += Math.PI * 2;
  5199. }
  5200. } else {
  5201. if (startAngle < endAngle) {
  5202. startAngle += Math.PI * 2;
  5203. }
  5204. }
  5205. if (ry !== rx) {
  5206. cx = cos * cx * rx - sin * cy * ry + x1;
  5207. cy = sin * cy * ry + cos * cy * ry + y1;
  5208. me.lineTo(cos * rx * cosStart - sin * ry * sinStart + cx, sin * rx * cosStart + cos * ry * sinStart + cy);
  5209. me.ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, area < 0);
  5210. } else {
  5211. cx = cx * rx + x1;
  5212. cy = cy * ry + y1;
  5213. me.lineTo(rx * cosStart + cx, ry * sinStart + cy);
  5214. me.ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, area < 0);
  5215. }
  5216. },
  5217. /**
  5218. * Create an elliptic arc.
  5219. *
  5220. * See [the whatwg reference of ellipse](http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-ellipse).
  5221. *
  5222. * @param {Number} cx
  5223. * @param {Number} cy
  5224. * @param {Number} radiusX
  5225. * @param {Number} radiusY
  5226. * @param {Number} rotation
  5227. * @param {Number} startAngle
  5228. * @param {Number} endAngle
  5229. * @param {Number} anticlockwise
  5230. */
  5231. ellipse: function(cx, cy, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise) {
  5232. var me = this,
  5233. params = me.params,
  5234. start = params.length,
  5235. count, temp, i, j;
  5236. if (endAngle - startAngle >= Math.PI * 2) {
  5237. me.ellipse(cx, cy, radiusX, radiusY, rotation, startAngle, startAngle + Math.PI, anticlockwise);
  5238. me.ellipse(cx, cy, radiusX, radiusY, rotation, startAngle + Math.PI, endAngle, anticlockwise);
  5239. return;
  5240. }
  5241. if (!anticlockwise) {
  5242. if (endAngle < startAngle) {
  5243. endAngle += Math.PI * 2;
  5244. }
  5245. count = me.approximateArc(params, cx, cy, radiusX, radiusY, rotation, startAngle, endAngle);
  5246. } else {
  5247. if (startAngle < endAngle) {
  5248. startAngle += Math.PI * 2;
  5249. }
  5250. count = me.approximateArc(params, cx, cy, radiusX, radiusY, rotation, endAngle, startAngle);
  5251. for (i = start , j = params.length - 2; i < j; i += 2 , j -= 2) {
  5252. temp = params[i];
  5253. params[i] = params[j];
  5254. params[j] = temp;
  5255. temp = params[i + 1];
  5256. params[i + 1] = params[j + 1];
  5257. params[j + 1] = temp;
  5258. }
  5259. }
  5260. if (!me.cursor) {
  5261. me.cursor = [
  5262. params[params.length - 2],
  5263. params[params.length - 1]
  5264. ];
  5265. me.commands.push('M');
  5266. } else {
  5267. me.cursor[0] = params[params.length - 2];
  5268. me.cursor[1] = params[params.length - 1];
  5269. me.commands.push('L');
  5270. }
  5271. for (i = 2; i < count; i += 6) {
  5272. me.commands.push('C');
  5273. }
  5274. me.dirt();
  5275. },
  5276. /**
  5277. * Create an circular arc.
  5278. *
  5279. * @param {Number} x
  5280. * @param {Number} y
  5281. * @param {Number} radius
  5282. * @param {Number} startAngle
  5283. * @param {Number} endAngle
  5284. * @param {Number} anticlockwise
  5285. */
  5286. arc: function(x, y, radius, startAngle, endAngle, anticlockwise) {
  5287. this.ellipse(x, y, radius, radius, 0, startAngle, endAngle, anticlockwise);
  5288. },
  5289. /**
  5290. * Draw a rectangle and close it.
  5291. *
  5292. * @param {Number} x
  5293. * @param {Number} y
  5294. * @param {Number} width
  5295. * @param {Number} height
  5296. */
  5297. rect: function(x, y, width, height) {
  5298. var me = this;
  5299. if (width === 0 || height === 0) {
  5300. return;
  5301. }
  5302. me.moveTo(x, y);
  5303. me.lineTo(x + width, y);
  5304. me.lineTo(x + width, y + height);
  5305. me.lineTo(x, y + height);
  5306. me.closePath();
  5307. },
  5308. /**
  5309. * @private
  5310. * @param {Array} result
  5311. * @param {Number} cx
  5312. * @param {Number} cy
  5313. * @param {Number} rx
  5314. * @param {Number} ry
  5315. * @param {Number} phi
  5316. * @param {Number} theta1
  5317. * @param {Number} theta2
  5318. * @return {Number}
  5319. */
  5320. approximateArc: function(result, cx, cy, rx, ry, phi, theta1, theta2) {
  5321. var cosPhi = Math.cos(phi),
  5322. sinPhi = Math.sin(phi),
  5323. cosTheta1 = Math.cos(theta1),
  5324. sinTheta1 = Math.sin(theta1),
  5325. xx = cosPhi * cosTheta1 * rx - sinPhi * sinTheta1 * ry,
  5326. yx = -cosPhi * sinTheta1 * rx - sinPhi * cosTheta1 * ry,
  5327. xy = sinPhi * cosTheta1 * rx + cosPhi * sinTheta1 * ry,
  5328. yy = -sinPhi * sinTheta1 * rx + cosPhi * cosTheta1 * ry,
  5329. rightAngle = Math.PI / 2,
  5330. count = 2,
  5331. exx = xx,
  5332. eyx = yx,
  5333. exy = xy,
  5334. eyy = yy,
  5335. rho = 0.547443256150549,
  5336. temp, y1, x3, y3, x2, y2;
  5337. theta2 -= theta1;
  5338. if (theta2 < 0) {
  5339. theta2 += Math.PI * 2;
  5340. }
  5341. result.push(xx + cx, xy + cy);
  5342. while (theta2 >= rightAngle) {
  5343. result.push(exx + eyx * rho + cx, exy + eyy * rho + cy, exx * rho + eyx + cx, exy * rho + eyy + cy, eyx + cx, eyy + cy);
  5344. count += 6;
  5345. theta2 -= rightAngle;
  5346. temp = exx;
  5347. exx = eyx;
  5348. eyx = -temp;
  5349. temp = exy;
  5350. exy = eyy;
  5351. eyy = -temp;
  5352. }
  5353. if (theta2) {
  5354. y1 = (0.3294738052815987 + 0.012120855841304373 * theta2) * theta2;
  5355. x3 = Math.cos(theta2);
  5356. y3 = Math.sin(theta2);
  5357. x2 = x3 + y1 * y3;
  5358. y2 = y3 - y1 * x3;
  5359. 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);
  5360. count += 6;
  5361. }
  5362. return count;
  5363. },
  5364. /**
  5365. * [http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes](http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes)
  5366. * @param {Number} rx
  5367. * @param {Number} ry
  5368. * @param {Number} rotation Differ from svg spec, this is radian.
  5369. * @param {Number} fA
  5370. * @param {Number} fS
  5371. * @param {Number} x2
  5372. * @param {Number} y2
  5373. */
  5374. arcSvg: function(rx, ry, rotation, fA, fS, x2, y2) {
  5375. if (rx < 0) {
  5376. rx = -rx;
  5377. }
  5378. if (ry < 0) {
  5379. ry = -ry;
  5380. }
  5381. // eslint-disable-next-line vars-on-top
  5382. var me = this,
  5383. x1 = me.cursor[0],
  5384. y1 = me.cursor[1],
  5385. hdx = (x1 - x2) / 2,
  5386. hdy = (y1 - y2) / 2,
  5387. cosPhi = Math.cos(rotation),
  5388. sinPhi = Math.sin(rotation),
  5389. xp = hdx * cosPhi + hdy * sinPhi,
  5390. yp = -hdx * sinPhi + hdy * cosPhi,
  5391. ratX = xp / rx,
  5392. ratY = yp / ry,
  5393. lambda = ratX * ratX + ratY * ratY,
  5394. cx = (x1 + x2) * 0.5,
  5395. cy = (y1 + y2) * 0.5,
  5396. cpx = 0,
  5397. cpy = 0,
  5398. theta1, deltaTheta;
  5399. if (lambda >= 1) {
  5400. lambda = Math.sqrt(lambda);
  5401. rx *= lambda;
  5402. ry *= lambda;
  5403. } else // me gives lambda == cpx == cpy == 0;
  5404. {
  5405. lambda = Math.sqrt(1 / lambda - 1);
  5406. if (fA === fS) {
  5407. lambda = -lambda;
  5408. }
  5409. cpx = lambda * rx * ratY;
  5410. cpy = -lambda * ry * ratX;
  5411. cx += cosPhi * cpx - sinPhi * cpy;
  5412. cy += sinPhi * cpx + cosPhi * cpy;
  5413. }
  5414. theta1 = Math.atan2((yp - cpy) / ry, (xp - cpx) / rx);
  5415. deltaTheta = Math.atan2((-yp - cpy) / ry, (-xp - cpx) / rx) - theta1;
  5416. if (fS) {
  5417. if (deltaTheta <= 0) {
  5418. deltaTheta += Math.PI * 2;
  5419. }
  5420. } else {
  5421. if (deltaTheta >= 0) {
  5422. deltaTheta -= Math.PI * 2;
  5423. }
  5424. }
  5425. me.ellipse(cx, cy, rx, ry, rotation, theta1, theta1 + deltaTheta, 1 - fS);
  5426. },
  5427. /**
  5428. * Feed the path from svg path string.
  5429. * @param {String} pathString
  5430. */
  5431. fromSvgString: function(pathString) {
  5432. if (!pathString) {
  5433. return;
  5434. }
  5435. // eslint-disable-next-line vars-on-top
  5436. var me = this,
  5437. parts,
  5438. paramCounts = {
  5439. a: 7,
  5440. c: 6,
  5441. h: 1,
  5442. l: 2,
  5443. m: 2,
  5444. q: 4,
  5445. s: 4,
  5446. t: 2,
  5447. v: 1,
  5448. z: 0,
  5449. A: 7,
  5450. C: 6,
  5451. H: 1,
  5452. L: 2,
  5453. M: 2,
  5454. Q: 4,
  5455. S: 4,
  5456. T: 2,
  5457. V: 1,
  5458. Z: 0
  5459. },
  5460. lastCommand = '',
  5461. lastControlX, lastControlY,
  5462. lastX = 0,
  5463. lastY = 0,
  5464. part = false,
  5465. i, partLength;
  5466. // Split the string to items.
  5467. if (Ext.isString(pathString)) {
  5468. parts = pathString.replace(Ext.draw.Path.pathRe, " $1 ").replace(Ext.draw.Path.pathRe2, " -").split(Ext.draw.Path.pathSplitRe);
  5469. } else if (Ext.isArray(pathString)) {
  5470. parts = pathString.join(',').split(Ext.draw.Path.pathSplitRe);
  5471. }
  5472. // Remove empty entries
  5473. for (i = 0 , partLength = 0; i < parts.length; i++) {
  5474. if (parts[i] !== '') {
  5475. parts[partLength++] = parts[i];
  5476. }
  5477. }
  5478. parts.length = partLength;
  5479. me.clear();
  5480. for (i = 0; i < parts.length; ) {
  5481. lastCommand = part;
  5482. part = parts[i];
  5483. i++;
  5484. switch (part) {
  5485. case 'M':
  5486. me.moveTo(lastX = +parts[i], lastY = +parts[i + 1]);
  5487. i += 2;
  5488. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5489. me.lineTo(lastX = +parts[i], lastY = +parts[i + 1]);
  5490. i += 2;
  5491. };
  5492. break;
  5493. case 'L':
  5494. me.lineTo(lastX = +parts[i], lastY = +parts[i + 1]);
  5495. i += 2;
  5496. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5497. me.lineTo(lastX = +parts[i], lastY = +parts[i + 1]);
  5498. i += 2;
  5499. };
  5500. break;
  5501. case 'A':
  5502. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5503. 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]);
  5504. i += 7;
  5505. };
  5506. break;
  5507. case 'C':
  5508. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5509. me.bezierCurveTo(+parts[i], +parts[i + 1], lastControlX = +parts[i + 2], lastControlY = +parts[i + 3], lastX = +parts[i + 4], lastY = +parts[i + 5]);
  5510. i += 6;
  5511. };
  5512. break;
  5513. case 'Z':
  5514. me.closePath();
  5515. break;
  5516. case 'm':
  5517. me.moveTo(lastX += +parts[i], lastY += +parts[i + 1]);
  5518. i += 2;
  5519. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5520. me.lineTo(lastX += +parts[i], lastY += +parts[i + 1]);
  5521. i += 2;
  5522. };
  5523. break;
  5524. case 'l':
  5525. me.lineTo(lastX += +parts[i], lastY += +parts[i + 1]);
  5526. i += 2;
  5527. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5528. me.lineTo(lastX += +parts[i], lastY += +parts[i + 1]);
  5529. i += 2;
  5530. };
  5531. break;
  5532. case 'a':
  5533. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5534. 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]);
  5535. i += 7;
  5536. };
  5537. break;
  5538. case 'c':
  5539. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5540. 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]);
  5541. i += 6;
  5542. };
  5543. break;
  5544. case 'z':
  5545. me.closePath();
  5546. break;
  5547. case 's':
  5548. if (!(lastCommand === 'c' || lastCommand === 'C' || lastCommand === 's' || lastCommand === 'S')) {
  5549. lastControlX = lastX;
  5550. lastControlY = lastY;
  5551. };
  5552. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5553. 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]);
  5554. i += 4;
  5555. };
  5556. break;
  5557. case 'S':
  5558. if (!(lastCommand === 'c' || lastCommand === 'C' || lastCommand === 's' || lastCommand === 'S')) {
  5559. lastControlX = lastX;
  5560. lastControlY = lastY;
  5561. };
  5562. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5563. me.bezierCurveTo(lastX + lastX - lastControlX, lastY + lastY - lastControlY, lastControlX = +parts[i], lastControlY = +parts[i + 1], lastX = (+parts[i + 2]), lastY = (+parts[i + 3]));
  5564. i += 4;
  5565. };
  5566. break;
  5567. case 'q':
  5568. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5569. me.quadraticCurveTo(lastControlX = lastX + (+parts[i]), lastControlY = lastY + (+parts[i + 1]), lastX += +parts[i + 2], lastY += +parts[i + 3]);
  5570. i += 4;
  5571. };
  5572. break;
  5573. case 'Q':
  5574. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5575. me.quadraticCurveTo(lastControlX = +parts[i], lastControlY = +parts[i + 1], lastX = +parts[i + 2], lastY = +parts[i + 3]);
  5576. i += 4;
  5577. };
  5578. break;
  5579. case 't':
  5580. if (!(lastCommand === 'q' || lastCommand === 'Q' || lastCommand === 't' || lastCommand === 'T')) {
  5581. lastControlX = lastX;
  5582. lastControlY = lastY;
  5583. };
  5584. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5585. me.quadraticCurveTo(lastControlX = lastX + lastX - lastControlX, lastControlY = lastY + lastY - lastControlY, lastX += +parts[i + 1], lastY += +parts[i + 2]);
  5586. i += 2;
  5587. };
  5588. break;
  5589. case 'T':
  5590. if (!(lastCommand === 'q' || lastCommand === 'Q' || lastCommand === 't' || lastCommand === 'T')) {
  5591. lastControlX = lastX;
  5592. lastControlY = lastY;
  5593. };
  5594. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5595. me.quadraticCurveTo(lastControlX = lastX + lastX - lastControlX, lastControlY = lastY + lastY - lastControlY, lastX = (+parts[i + 1]), lastY = (+parts[i + 2]));
  5596. i += 2;
  5597. };
  5598. break;
  5599. case 'h':
  5600. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5601. me.lineTo(lastX += +parts[i], lastY);
  5602. i++;
  5603. };
  5604. break;
  5605. case 'H':
  5606. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5607. me.lineTo(lastX = +parts[i], lastY);
  5608. i++;
  5609. };
  5610. break;
  5611. case 'v':
  5612. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5613. me.lineTo(lastX, lastY += +parts[i]);
  5614. i++;
  5615. };
  5616. break;
  5617. case 'V':
  5618. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5619. me.lineTo(lastX, lastY = +parts[i]);
  5620. i++;
  5621. };
  5622. break;
  5623. }
  5624. }
  5625. },
  5626. /**
  5627. * Clone this path.
  5628. * @return {Ext.draw.Path}
  5629. */
  5630. clone: function() {
  5631. var me = this,
  5632. path = new Ext.draw.Path();
  5633. path.params = me.params.slice(0);
  5634. path.commands = me.commands.slice(0);
  5635. path.cursor = me.cursor ? me.cursor.slice(0) : null;
  5636. path.startX = me.startX;
  5637. path.startY = me.startY;
  5638. path.svgString = me.svgString;
  5639. return path;
  5640. },
  5641. /**
  5642. * Transform the current path by a matrix.
  5643. * @param {Ext.draw.Matrix} matrix
  5644. */
  5645. transform: function(matrix) {
  5646. if (matrix.isIdentity()) {
  5647. return;
  5648. }
  5649. // eslint-disable-next-line vars-on-top
  5650. var xx = matrix.getXX(),
  5651. yx = matrix.getYX(),
  5652. dx = matrix.getDX(),
  5653. xy = matrix.getXY(),
  5654. yy = matrix.getYY(),
  5655. dy = matrix.getDY(),
  5656. params = this.params,
  5657. i = 0,
  5658. ln = params.length,
  5659. x, y;
  5660. for (; i < ln; i += 2) {
  5661. x = params[i];
  5662. y = params[i + 1];
  5663. params[i] = x * xx + y * yx + dx;
  5664. params[i + 1] = x * xy + y * yy + dy;
  5665. }
  5666. this.dirt();
  5667. },
  5668. /**
  5669. * Get the bounding box of this matrix.
  5670. * @param {Object} [target] Optional object to receive the result.
  5671. *
  5672. * @return {Object} Object with x, y, width and height
  5673. */
  5674. getDimension: function(target) {
  5675. if (!target) {
  5676. target = {};
  5677. }
  5678. if (!this.commands || !this.commands.length) {
  5679. target.x = 0;
  5680. target.y = 0;
  5681. target.width = 0;
  5682. target.height = 0;
  5683. return target;
  5684. }
  5685. target.left = Infinity;
  5686. target.top = Infinity;
  5687. target.right = -Infinity;
  5688. target.bottom = -Infinity;
  5689. // eslint-disable-next-line vars-on-top
  5690. var i = 0,
  5691. j = 0,
  5692. commands = this.commands,
  5693. params = this.params,
  5694. ln = commands.length,
  5695. x, y;
  5696. for (; i < ln; i++) {
  5697. switch (commands[i]) {
  5698. case 'M':
  5699. case 'L':
  5700. x = params[j];
  5701. y = params[j + 1];
  5702. target.left = Math.min(x, target.left);
  5703. target.top = Math.min(y, target.top);
  5704. target.right = Math.max(x, target.right);
  5705. target.bottom = Math.max(y, target.bottom);
  5706. j += 2;
  5707. break;
  5708. case 'C':
  5709. this.expandDimension(target, x, y, params[j], params[j + 1], params[j + 2], params[j + 3], x = params[j + 4], y = params[j + 5]);
  5710. j += 6;
  5711. break;
  5712. }
  5713. }
  5714. target.x = target.left;
  5715. target.y = target.top;
  5716. target.width = target.right - target.left;
  5717. target.height = target.bottom - target.top;
  5718. return target;
  5719. },
  5720. /**
  5721. * Get the bounding box as if the path is transformed by a matrix.
  5722. *
  5723. * @param {Ext.draw.Matrix} matrix
  5724. * @param {Object} [target] Optional object to receive the result.
  5725. *
  5726. * @return {Object} An object with x, y, width and height.
  5727. */
  5728. getDimensionWithTransform: function(matrix, target) {
  5729. if (!this.commands || !this.commands.length) {
  5730. if (!target) {
  5731. target = {};
  5732. }
  5733. target.x = 0;
  5734. target.y = 0;
  5735. target.width = 0;
  5736. target.height = 0;
  5737. return target;
  5738. }
  5739. target.left = Infinity;
  5740. target.top = Infinity;
  5741. target.right = -Infinity;
  5742. target.bottom = -Infinity;
  5743. // eslint-disable-next-line vars-on-top
  5744. var xx = matrix.getXX(),
  5745. yx = matrix.getYX(),
  5746. dx = matrix.getDX(),
  5747. xy = matrix.getXY(),
  5748. yy = matrix.getYY(),
  5749. dy = matrix.getDY(),
  5750. i = 0,
  5751. j = 0,
  5752. commands = this.commands,
  5753. params = this.params,
  5754. ln = commands.length,
  5755. x, y;
  5756. for (; i < ln; i++) {
  5757. switch (commands[i]) {
  5758. case 'M':
  5759. case 'L':
  5760. x = params[j] * xx + params[j + 1] * yx + dx;
  5761. y = params[j] * xy + params[j + 1] * yy + dy;
  5762. target.left = Math.min(x, target.left);
  5763. target.top = Math.min(y, target.top);
  5764. target.right = Math.max(x, target.right);
  5765. target.bottom = Math.max(y, target.bottom);
  5766. j += 2;
  5767. break;
  5768. case 'C':
  5769. 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);
  5770. j += 6;
  5771. break;
  5772. }
  5773. }
  5774. if (!target) {
  5775. target = {};
  5776. }
  5777. target.x = target.left;
  5778. target.y = target.top;
  5779. target.width = target.right - target.left;
  5780. target.height = target.bottom - target.top;
  5781. return target;
  5782. },
  5783. /**
  5784. * @private
  5785. * Expand the rect by the bbox of a bezier curve.
  5786. *
  5787. * @param {Object} target
  5788. * @param {Number} x1
  5789. * @param {Number} y1
  5790. * @param {Number} cx1
  5791. * @param {Number} cy1
  5792. * @param {Number} cx2
  5793. * @param {Number} cy2
  5794. * @param {Number} x2
  5795. * @param {Number} y2
  5796. */
  5797. expandDimension: function(target, x1, y1, cx1, cy1, cx2, cy2, x2, y2) {
  5798. var me = this,
  5799. l = target.left,
  5800. r = target.right,
  5801. t = target.top,
  5802. b = target.bottom,
  5803. dim = me.dim || (me.dim = []);
  5804. me.curveDimension(x1, cx1, cx2, x2, dim);
  5805. l = Math.min(l, dim[0]);
  5806. r = Math.max(r, dim[1]);
  5807. me.curveDimension(y1, cy1, cy2, y2, dim);
  5808. t = Math.min(t, dim[0]);
  5809. b = Math.max(b, dim[1]);
  5810. target.left = l;
  5811. target.right = r;
  5812. target.top = t;
  5813. target.bottom = b;
  5814. },
  5815. /**
  5816. * @private
  5817. * Determine the curve
  5818. * @param {Number} a
  5819. * @param {Number} b
  5820. * @param {Number} c
  5821. * @param {Number} d
  5822. * @param {Number} dim
  5823. */
  5824. curveDimension: function(a, b, c, d, dim) {
  5825. var qa = 3 * (-a + 3 * (b - c) + d),
  5826. qb = 6 * (a - 2 * b + c),
  5827. qc = -3 * (a - b),
  5828. x, y,
  5829. min = Math.min(a, d),
  5830. max = Math.max(a, d),
  5831. delta;
  5832. if (qa === 0) {
  5833. if (qb === 0) {
  5834. dim[0] = min;
  5835. dim[1] = max;
  5836. return;
  5837. } else {
  5838. x = -qc / qb;
  5839. if (0 < x && x < 1) {
  5840. y = this.interpolate(a, b, c, d, x);
  5841. min = Math.min(min, y);
  5842. max = Math.max(max, y);
  5843. }
  5844. }
  5845. } else {
  5846. delta = qb * qb - 4 * qa * qc;
  5847. if (delta >= 0) {
  5848. delta = Math.sqrt(delta);
  5849. x = (delta - qb) / 2 / qa;
  5850. if (0 < x && x < 1) {
  5851. y = this.interpolate(a, b, c, d, x);
  5852. min = Math.min(min, y);
  5853. max = Math.max(max, y);
  5854. }
  5855. if (delta > 0) {
  5856. x -= delta / qa;
  5857. if (0 < x && x < 1) {
  5858. y = this.interpolate(a, b, c, d, x);
  5859. min = Math.min(min, y);
  5860. max = Math.max(max, y);
  5861. }
  5862. }
  5863. }
  5864. }
  5865. dim[0] = min;
  5866. dim[1] = max;
  5867. },
  5868. /**
  5869. * @private
  5870. *
  5871. * Returns `a * (1 - t) ^ 3 + 3 * b (1 - t) ^ 2 * t + 3 * c (1 - t) * t ^ 3 + d * t ^ 3`.
  5872. *
  5873. * @param {Number} a
  5874. * @param {Number} b
  5875. * @param {Number} c
  5876. * @param {Number} d
  5877. * @param {Number} t
  5878. * @return {Number}
  5879. */
  5880. interpolate: function(a, b, c, d, t) {
  5881. var rate;
  5882. if (t === 0) {
  5883. return a;
  5884. }
  5885. if (t === 1) {
  5886. return d;
  5887. }
  5888. rate = (1 - t) / t;
  5889. return t * t * t * (d + rate * (3 * c + rate * (3 * b + rate * a)));
  5890. },
  5891. /**
  5892. * Reconstruct path from cubic bezier curve stripes.
  5893. * @param {Array} stripes
  5894. */
  5895. fromStripes: function(stripes) {
  5896. var me = this,
  5897. i = 0,
  5898. ln = stripes.length,
  5899. j, ln2, stripe;
  5900. me.clear();
  5901. for (; i < ln; i++) {
  5902. stripe = stripes[i];
  5903. me.params.push.apply(me.params, stripe);
  5904. me.commands.push('M');
  5905. for (j = 2 , ln2 = stripe.length; j < ln2; j += 6) {
  5906. me.commands.push('C');
  5907. }
  5908. }
  5909. if (!me.cursor) {
  5910. me.cursor = [];
  5911. }
  5912. me.cursor[0] = me.params[me.params.length - 2];
  5913. me.cursor[1] = me.params[me.params.length - 1];
  5914. me.dirt();
  5915. },
  5916. /**
  5917. * Convert path to bezier curve stripes.
  5918. * @param {Array} [target] The optional array to receive the result.
  5919. * @return {Array}
  5920. */
  5921. toStripes: function(target) {
  5922. var stripes = target || [],
  5923. curr, x, y, lastX, lastY, startX, startY, i, j,
  5924. commands = this.commands,
  5925. params = this.params,
  5926. ln = commands.length;
  5927. for (i = 0 , j = 0; i < ln; i++) {
  5928. switch (commands[i]) {
  5929. case 'M':
  5930. curr = [
  5931. startX = lastX = params[j++],
  5932. startY = lastY = params[j++]
  5933. ];
  5934. stripes.push(curr);
  5935. break;
  5936. case 'L':
  5937. x = params[j++];
  5938. y = params[j++];
  5939. curr.push((lastX + lastX + x) / 3, (lastY + lastY + y) / 3, (lastX + x + x) / 3, (lastY + y + y) / 3, lastX = x, lastY = y);
  5940. break;
  5941. case 'C':
  5942. curr.push(params[j++], params[j++], params[j++], params[j++], lastX = params[j++], lastY = params[j++]);
  5943. break;
  5944. case 'Z':
  5945. x = startX;
  5946. y = startY;
  5947. curr.push((lastX + lastX + x) / 3, (lastY + lastY + y) / 3, (lastX + x + x) / 3, (lastY + y + y) / 3, lastX = x, lastY = y);
  5948. break;
  5949. }
  5950. }
  5951. return stripes;
  5952. },
  5953. /**
  5954. * @private
  5955. * Update cache for svg string of this path.
  5956. */
  5957. updateSvgString: function() {
  5958. var result = [],
  5959. commands = this.commands,
  5960. params = this.params,
  5961. ln = commands.length,
  5962. i = 0,
  5963. j = 0;
  5964. for (; i < ln; i++) {
  5965. switch (commands[i]) {
  5966. case 'M':
  5967. result.push('M' + params[j] + ',' + params[j + 1]);
  5968. j += 2;
  5969. break;
  5970. case 'L':
  5971. result.push('L' + params[j] + ',' + params[j + 1]);
  5972. j += 2;
  5973. break;
  5974. case 'C':
  5975. result.push('C' + params[j] + ',' + params[j + 1] + ' ' + params[j + 2] + ',' + params[j + 3] + ' ' + params[j + 4] + ',' + params[j + 5]);
  5976. j += 6;
  5977. break;
  5978. case 'Z':
  5979. result.push('Z');
  5980. break;
  5981. }
  5982. }
  5983. this.svgString = result.join('');
  5984. },
  5985. /**
  5986. * Return an svg path string for this path.
  5987. * @return {String}
  5988. */
  5989. toString: function() {
  5990. if (!this.svgString) {
  5991. this.updateSvgString();
  5992. }
  5993. return this.svgString;
  5994. }
  5995. });
  5996. /**
  5997. * @private
  5998. * Adds hit testing and path intersection points methods to the Ext.draw.Path.
  5999. * Included by the Ext.draw.PathUtil.
  6000. */
  6001. Ext.define('Ext.draw.overrides.hittest.Path', {
  6002. override: 'Ext.draw.Path',
  6003. // An arbitrary point outside the path used for hit testing with ray casting method.
  6004. rayOrigin: {
  6005. x: -10000,
  6006. y: -10000
  6007. },
  6008. /**
  6009. * Tests whether the given point is inside the path.
  6010. * @param {Number} x
  6011. * @param {Number} y
  6012. * @return {Boolean}
  6013. * @member Ext.draw.Path
  6014. */
  6015. isPointInPath: function(x, y) {
  6016. var me = this,
  6017. commands = me.commands,
  6018. solver = Ext.draw.PathUtil,
  6019. origin = me.rayOrigin,
  6020. params = me.params,
  6021. ln = commands.length,
  6022. firstX = null,
  6023. firstY = null,
  6024. lastX = 0,
  6025. lastY = 0,
  6026. count = 0,
  6027. i, j;
  6028. for (i = 0 , j = 0; i < ln; i++) {
  6029. switch (commands[i]) {
  6030. case 'M':
  6031. if (firstX !== null) {
  6032. // eslint-disable-next-line max-len
  6033. if (solver.linesIntersection(firstX, firstY, lastX, lastY, origin.x, origin.y, x, y)) {
  6034. count += 1;
  6035. }
  6036. };
  6037. firstX = lastX = params[j];
  6038. firstY = lastY = params[j + 1];
  6039. j += 2;
  6040. break;
  6041. case 'L':
  6042. // eslint-disable-next-line max-len
  6043. if (solver.linesIntersection(lastX, lastY, params[j], params[j + 1], origin.x, origin.y, x, y)) {
  6044. count += 1;
  6045. };
  6046. lastX = params[j];
  6047. lastY = params[j + 1];
  6048. j += 2;
  6049. break;
  6050. case 'C':
  6051. 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;
  6052. lastX = params[j + 4];
  6053. lastY = params[j + 5];
  6054. j += 6;
  6055. break;
  6056. case 'Z':
  6057. if (firstX !== null) {
  6058. // eslint-disable-next-line max-len
  6059. if (solver.linesIntersection(firstX, firstY, lastX, lastY, origin.x, origin.y, x, y)) {
  6060. count += 1;
  6061. }
  6062. };
  6063. break;
  6064. }
  6065. }
  6066. return count % 2 === 1;
  6067. },
  6068. /**
  6069. * Tests whether the given point is on the path.
  6070. * @param {Number} x
  6071. * @param {Number} y
  6072. * @return {Boolean}
  6073. * @member Ext.draw.Path
  6074. */
  6075. isPointOnPath: function(x, y) {
  6076. var me = this,
  6077. commands = me.commands,
  6078. solver = Ext.draw.PathUtil,
  6079. params = me.params,
  6080. ln = commands.length,
  6081. firstX = null,
  6082. firstY = null,
  6083. lastX = 0,
  6084. lastY = 0,
  6085. i, j;
  6086. for (i = 0 , j = 0; i < ln; i++) {
  6087. switch (commands[i]) {
  6088. case 'M':
  6089. if (firstX !== null) {
  6090. if (solver.pointOnLine(firstX, firstY, lastX, lastY, x, y)) {
  6091. return true;
  6092. }
  6093. };
  6094. firstX = lastX = params[j];
  6095. firstY = lastY = params[j + 1];
  6096. j += 2;
  6097. break;
  6098. case 'L':
  6099. if (solver.pointOnLine(lastX, lastY, params[j], params[j + 1], x, y)) {
  6100. return true;
  6101. };
  6102. lastX = params[j];
  6103. lastY = params[j + 1];
  6104. j += 2;
  6105. break;
  6106. case 'C':
  6107. if (solver.pointOnCubic(lastX, params[j], params[j + 2], params[j + 4], lastY, params[j + 1], params[j + 3], params[j + 5], x, y)) {
  6108. return true;
  6109. };
  6110. lastX = params[j + 4];
  6111. lastY = params[j + 5];
  6112. j += 6;
  6113. break;
  6114. case 'Z':
  6115. if (firstX !== null) {
  6116. if (solver.pointOnLine(firstX, firstY, lastX, lastY, x, y)) {
  6117. return true;
  6118. }
  6119. };
  6120. break;
  6121. }
  6122. }
  6123. return false;
  6124. },
  6125. /**
  6126. * Calculates the points where the given segment intersects the path.
  6127. * If four parameters are given then the segment is considered to be a line segment,
  6128. * where given parameters are the coordinates of the start and end points.
  6129. * If eight parameters are given then the segment is considered to be
  6130. * a cubic Bezier curve segment, where given parameters are the
  6131. * coordinates of its edge points and control points.
  6132. * @param x1
  6133. * @param y1
  6134. * @param x2
  6135. * @param y2
  6136. * @param x3
  6137. * @param y3
  6138. * @param x4
  6139. * @param y4
  6140. * @return {Array}
  6141. * @member Ext.draw.Path
  6142. */
  6143. getSegmentIntersections: function(x1, y1, x2, y2, x3, y3, x4, y4) {
  6144. var me = this,
  6145. count = arguments.length,
  6146. solver = Ext.draw.PathUtil,
  6147. commands = me.commands,
  6148. params = me.params,
  6149. ln = commands.length,
  6150. firstX = null,
  6151. firstY = null,
  6152. lastX = 0,
  6153. lastY = 0,
  6154. intersections = [],
  6155. i, j, points;
  6156. for (i = 0 , j = 0; i < ln; i++) {
  6157. switch (commands[i]) {
  6158. case 'M':
  6159. if (firstX !== null) {
  6160. switch (count) {
  6161. case 4:
  6162. points = solver.linesIntersection(firstX, firstY, lastX, lastY, x1, y1, x2, y2);
  6163. if (points) {
  6164. intersections.push(points);
  6165. };
  6166. break;
  6167. case 8:
  6168. points = solver.cubicLineIntersections(x1, x2, x3, x4, y1, y2, y3, y4, firstX, firstY, lastX, lastY);
  6169. intersections.push.apply(intersections, points);
  6170. break;
  6171. }
  6172. };
  6173. firstX = lastX = params[j];
  6174. firstY = lastY = params[j + 1];
  6175. j += 2;
  6176. break;
  6177. case 'L':
  6178. switch (count) {
  6179. case 4:
  6180. points = solver.linesIntersection(lastX, lastY, params[j], params[j + 1], x1, y1, x2, y2);
  6181. if (points) {
  6182. intersections.push(points);
  6183. };
  6184. break;
  6185. case 8:
  6186. points = solver.cubicLineIntersections(x1, x2, x3, x4, y1, y2, y3, y4, lastX, lastY, params[j], params[j + 1]);
  6187. intersections.push.apply(intersections, points);
  6188. break;
  6189. };
  6190. lastX = params[j];
  6191. lastY = params[j + 1];
  6192. j += 2;
  6193. break;
  6194. case 'C':
  6195. switch (count) {
  6196. case 4:
  6197. 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);
  6198. intersections.push.apply(intersections, points);
  6199. break;
  6200. case 8:
  6201. 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);
  6202. intersections.push.apply(intersections, points);
  6203. break;
  6204. };
  6205. lastX = params[j + 4];
  6206. lastY = params[j + 5];
  6207. j += 6;
  6208. break;
  6209. case 'Z':
  6210. if (firstX !== null) {
  6211. switch (count) {
  6212. case 4:
  6213. points = solver.linesIntersection(firstX, firstY, lastX, lastY, x1, y1, x2, y2);
  6214. if (points) {
  6215. intersections.push(points);
  6216. };
  6217. break;
  6218. case 8:
  6219. points = solver.cubicLineIntersections(x1, x2, x3, x4, y1, y2, y3, y4, firstX, firstY, lastX, lastY);
  6220. intersections.push.apply(intersections, points);
  6221. break;
  6222. }
  6223. };
  6224. break;
  6225. }
  6226. }
  6227. return intersections;
  6228. },
  6229. getIntersections: function(path) {
  6230. var me = this,
  6231. commands = me.commands,
  6232. params = me.params,
  6233. ln = commands.length,
  6234. firstX = null,
  6235. firstY = null,
  6236. lastX = 0,
  6237. lastY = 0,
  6238. intersections = [],
  6239. i, j, points;
  6240. for (i = 0 , j = 0; i < ln; i++) {
  6241. switch (commands[i]) {
  6242. case 'M':
  6243. if (firstX !== null) {
  6244. points = path.getSegmentIntersections.call(path, firstX, firstY, lastX, lastY);
  6245. intersections.push.apply(intersections, points);
  6246. };
  6247. firstX = lastX = params[j];
  6248. firstY = lastY = params[j + 1];
  6249. j += 2;
  6250. break;
  6251. case 'L':
  6252. points = path.getSegmentIntersections.call(path, lastX, lastY, params[j], params[j + 1]);
  6253. intersections.push.apply(intersections, points);
  6254. lastX = params[j];
  6255. lastY = params[j + 1];
  6256. j += 2;
  6257. break;
  6258. case 'C':
  6259. points = path.getSegmentIntersections.call(path, lastX, lastY, params[j], params[j + 1], params[j + 2], params[j + 3], params[j + 4], params[j + 5]);
  6260. intersections.push.apply(intersections, points);
  6261. lastX = params[j + 4];
  6262. lastY = params[j + 5];
  6263. j += 6;
  6264. break;
  6265. case 'Z':
  6266. if (firstX !== null) {
  6267. points = path.getSegmentIntersections.call(path, firstX, firstY, lastX, lastY);
  6268. intersections.push.apply(intersections, points);
  6269. };
  6270. break;
  6271. }
  6272. }
  6273. return intersections;
  6274. }
  6275. });
  6276. /**
  6277. * @class Ext.draw.sprite.Path
  6278. * @extends Ext.draw.sprite.Sprite
  6279. *
  6280. * A sprite that represents a path.
  6281. *
  6282. * @example
  6283. * Ext.create({
  6284. * xtype: 'draw',
  6285. * renderTo: document.body,
  6286. * width: 600,
  6287. * height: 400,
  6288. * sprites: [{
  6289. * type: 'path',
  6290. * path: 'M20,30 c0,-50 75,50 75,0 c0,-50 -75,50 -75,0',
  6291. * fillStyle: '#1F6D91'
  6292. * }]
  6293. * });
  6294. *
  6295. * ### Drawing with SVG Paths
  6296. * You may use special SVG Path syntax to "describe" the drawing path.
  6297. * Here are the SVG path commands:
  6298. *
  6299. * + M = moveto
  6300. * + L = lineto
  6301. * + H = horizontal lineto
  6302. * + V = vertical lineto
  6303. * + C = curveto
  6304. * + S = smooth curveto
  6305. * + Q = quadratic Bézier curve
  6306. * + T = smooth quadratic Bézier curveto
  6307. * + A = elliptical Arc
  6308. * + Z = closepath
  6309. *
  6310. * **Note:** Capital letters indicate that the item should be absolutely positioned.
  6311. * Use lower case letters for relative positioning.
  6312. */
  6313. Ext.define('Ext.draw.sprite.Path', {
  6314. extend: 'Ext.draw.sprite.Sprite',
  6315. requires: [
  6316. 'Ext.draw.Draw',
  6317. 'Ext.draw.Path'
  6318. ],
  6319. alias: [
  6320. 'sprite.path',
  6321. 'Ext.draw.Sprite'
  6322. ],
  6323. type: 'path',
  6324. isPath: true,
  6325. inheritableStatics: {
  6326. def: {
  6327. processors: {
  6328. /**
  6329. * @cfg {String} path The SVG based path string used by the sprite.
  6330. */
  6331. path: function(n, o) {
  6332. if (!(n instanceof Ext.draw.Path)) {
  6333. n = new Ext.draw.Path(n);
  6334. }
  6335. return n;
  6336. }
  6337. },
  6338. aliases: {
  6339. d: 'path'
  6340. },
  6341. triggers: {
  6342. path: 'bbox'
  6343. },
  6344. updaters: {
  6345. path: function(attr) {
  6346. var path = attr.path;
  6347. if (!path || path.bindAttr !== attr) {
  6348. path = new Ext.draw.Path();
  6349. path.bindAttr = attr;
  6350. attr.path = path;
  6351. }
  6352. path.clear();
  6353. this.updatePath(path, attr);
  6354. this.scheduleUpdater(attr, 'bbox', [
  6355. 'path'
  6356. ]);
  6357. }
  6358. }
  6359. }
  6360. },
  6361. updatePlainBBox: function(plain) {
  6362. if (this.attr.path) {
  6363. this.attr.path.getDimension(plain);
  6364. }
  6365. },
  6366. updateTransformedBBox: function(transform) {
  6367. if (this.attr.path) {
  6368. this.attr.path.getDimensionWithTransform(this.attr.matrix, transform);
  6369. }
  6370. },
  6371. render: function(surface, ctx) {
  6372. var mat = this.attr.matrix,
  6373. attr = this.attr;
  6374. if (!attr.path || attr.path.params.length === 0) {
  6375. return;
  6376. }
  6377. mat.toContext(ctx);
  6378. ctx.appendPath(attr.path);
  6379. ctx.fillStroke(attr);
  6380. //<debug>
  6381. // eslint-disable-next-line vars-on-top
  6382. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  6383. if (debug) {
  6384. if (debug.bbox) {
  6385. this.renderBBox(surface, ctx);
  6386. }
  6387. if (debug.xray) {
  6388. this.renderXRay(surface, ctx);
  6389. }
  6390. }
  6391. },
  6392. //</debug>
  6393. //<debug>
  6394. renderXRay: function(surface, ctx) {
  6395. var attr = this.attr,
  6396. mat = attr.matrix,
  6397. imat = attr.inverseMatrix,
  6398. path = attr.path,
  6399. commands = path.commands,
  6400. params = path.params,
  6401. ln = commands.length,
  6402. twoPi = Math.PI * 2,
  6403. size = 2,
  6404. i, j;
  6405. mat.toContext(ctx);
  6406. ctx.beginPath();
  6407. for (i = 0 , j = 0; i < ln; i++) {
  6408. switch (commands[i]) {
  6409. case 'M':
  6410. ctx.moveTo(params[j] - size, params[j + 1] - size);
  6411. ctx.rect(params[j] - size, params[j + 1] - size, size * 2, size * 2);
  6412. j += 2;
  6413. break;
  6414. case 'L':
  6415. ctx.moveTo(params[j] - size, params[j + 1] - size);
  6416. ctx.rect(params[j] - size, params[j + 1] - size, size * 2, size * 2);
  6417. j += 2;
  6418. break;
  6419. case 'C':
  6420. ctx.moveTo(params[j] + size, params[j + 1]);
  6421. ctx.arc(params[j], params[j + 1], size, 0, twoPi, true);
  6422. j += 2;
  6423. ctx.moveTo(params[j] + size, params[j + 1]);
  6424. ctx.arc(params[j], params[j + 1], size, 0, twoPi, true);
  6425. j += 2;
  6426. ctx.moveTo(params[j] + size * 2, params[j + 1]);
  6427. ctx.rect(params[j] - size, params[j + 1] - size, size * 2, size * 2);
  6428. j += 2;
  6429. break;
  6430. default:
  6431. }
  6432. }
  6433. imat.toContext(ctx);
  6434. ctx.strokeStyle = 'black';
  6435. ctx.strokeOpacity = 1;
  6436. ctx.lineWidth = 1;
  6437. ctx.stroke();
  6438. mat.toContext(ctx);
  6439. ctx.beginPath();
  6440. for (i = 0 , j = 0; i < ln; i++) {
  6441. switch (commands[i]) {
  6442. case 'M':
  6443. ctx.moveTo(params[j], params[j + 1]);
  6444. j += 2;
  6445. break;
  6446. case 'L':
  6447. ctx.moveTo(params[j], params[j + 1]);
  6448. j += 2;
  6449. break;
  6450. case 'C':
  6451. ctx.lineTo(params[j], params[j + 1]);
  6452. j += 2;
  6453. ctx.moveTo(params[j], params[j + 1]);
  6454. j += 2;
  6455. ctx.lineTo(params[j], params[j + 1]);
  6456. j += 2;
  6457. break;
  6458. default:
  6459. }
  6460. }
  6461. imat.toContext(ctx);
  6462. ctx.lineWidth = 0.5;
  6463. ctx.stroke();
  6464. },
  6465. //</debug>
  6466. /**
  6467. * Update the path.
  6468. * @param {Ext.draw.Path} path An empty path to draw on using path API.
  6469. * @param {Object} attr The attribute object. Note: DO NOT use the `sprite.attr` instead of this
  6470. * if you want to work with instancing.
  6471. */
  6472. updatePath: function(path, attr) {}
  6473. });
  6474. /**
  6475. * @private
  6476. * Adds hit testing methods to the Ext.draw.sprite.Path sprite.
  6477. * Included by the Ext.draw.PathUtil.
  6478. */
  6479. Ext.define('Ext.draw.overrides.hittest.sprite.Path', {
  6480. override: 'Ext.draw.sprite.Path',
  6481. requires: [
  6482. 'Ext.draw.Color'
  6483. ],
  6484. /**
  6485. * Tests whether the given point is inside the path.
  6486. * @param x
  6487. * @param y
  6488. * @return {Boolean}
  6489. * @member Ext.draw.sprite.Path
  6490. */
  6491. isPointInPath: function(x, y) {
  6492. var attr = this.attr,
  6493. path, matrix, params, result;
  6494. if (attr.fillStyle === Ext.util.Color.RGBA_NONE) {
  6495. return this.isPointOnPath(x, y);
  6496. }
  6497. path = attr.path;
  6498. matrix = attr.matrix;
  6499. if (!matrix.isIdentity()) {
  6500. params = path.params.slice(0);
  6501. path.transform(attr.matrix);
  6502. }
  6503. result = path.isPointInPath(x, y);
  6504. if (params) {
  6505. path.params = params;
  6506. }
  6507. return result;
  6508. },
  6509. /**
  6510. * Tests whether the given point is on the path.
  6511. * @param x
  6512. * @param y
  6513. * @return {Boolean}
  6514. * @member Ext.draw.sprite.Path
  6515. */
  6516. isPointOnPath: function(x, y) {
  6517. var attr = this.attr,
  6518. path = attr.path,
  6519. matrix = attr.matrix,
  6520. params, result;
  6521. if (!matrix.isIdentity()) {
  6522. params = path.params.slice(0);
  6523. path.transform(attr.matrix);
  6524. }
  6525. result = path.isPointOnPath(x, y);
  6526. if (params) {
  6527. path.params = params;
  6528. }
  6529. return result;
  6530. },
  6531. /**
  6532. * @method hitTest
  6533. * @inheritdoc Ext.draw.Surface#method-hitTest
  6534. */
  6535. hitTest: function(point, options) {
  6536. var me = this,
  6537. attr = me.attr,
  6538. path = attr.path,
  6539. matrix = attr.matrix,
  6540. x = point[0],
  6541. y = point[1],
  6542. parentResult = me.callParent([
  6543. point,
  6544. options
  6545. ]),
  6546. result = null,
  6547. params, isFilled;
  6548. if (!parentResult) {
  6549. // The sprite is not visible or bounding box wasn't hit.
  6550. return result;
  6551. }
  6552. options = options || Ext.draw.sprite.Sprite.defaultHitTestOptions;
  6553. if (!matrix.isIdentity()) {
  6554. params = path.params.slice(0);
  6555. path.transform(attr.matrix);
  6556. }
  6557. if (options.fill && options.stroke) {
  6558. isFilled = attr.fillStyle !== Ext.util.Color.NONE && attr.fillStyle !== Ext.util.Color.RGBA_NONE;
  6559. if (isFilled) {
  6560. if (path.isPointInPath(x, y)) {
  6561. result = {
  6562. sprite: me
  6563. };
  6564. }
  6565. } else {
  6566. if (path.isPointInPath(x, y) || path.isPointOnPath(x, y)) {
  6567. result = {
  6568. sprite: me
  6569. };
  6570. }
  6571. }
  6572. } else if (options.stroke && !options.fill) {
  6573. if (path.isPointOnPath(x, y)) {
  6574. result = {
  6575. sprite: me
  6576. };
  6577. }
  6578. } else if (options.fill && !options.stroke) {
  6579. if (path.isPointInPath(x, y)) {
  6580. result = {
  6581. sprite: me
  6582. };
  6583. }
  6584. }
  6585. if (params) {
  6586. path.params = params;
  6587. }
  6588. return result;
  6589. },
  6590. /**
  6591. * Returns all points where this sprite intersects the given sprite.
  6592. * The given sprite must be an instance of the {@link Ext.draw.sprite.Path} class
  6593. * or its subclass.
  6594. * @param path
  6595. * @return {Array}
  6596. * @member Ext.draw.sprite.Path
  6597. */
  6598. getIntersections: function(path) {
  6599. if (!(path.isSprite && path.isPath)) {
  6600. return [];
  6601. }
  6602. // eslint-disable-next-line vars-on-top
  6603. var aAttr = this.attr,
  6604. bAttr = path.attr,
  6605. aPath = aAttr.path,
  6606. bPath = bAttr.path,
  6607. aMatrix = aAttr.matrix,
  6608. bMatrix = bAttr.matrix,
  6609. aParams, bParams, intersections;
  6610. if (!aMatrix.isIdentity()) {
  6611. aParams = aPath.params.slice(0);
  6612. aPath.transform(aAttr.matrix);
  6613. }
  6614. if (!bMatrix.isIdentity()) {
  6615. bParams = bPath.params.slice(0);
  6616. bPath.transform(bAttr.matrix);
  6617. }
  6618. intersections = aPath.getIntersections(bPath);
  6619. if (aParams) {
  6620. aPath.params = aParams;
  6621. }
  6622. if (bParams) {
  6623. bPath.params = bParams;
  6624. }
  6625. return intersections;
  6626. }
  6627. });
  6628. /**
  6629. * @class Ext.draw.sprite.Circle
  6630. * @extends Ext.draw.sprite.Path
  6631. *
  6632. * A sprite that represents a circle.
  6633. *
  6634. * @example
  6635. * Ext.create({
  6636. * xtype: 'draw',
  6637. * renderTo: document.body,
  6638. * width: 600,
  6639. * height: 400,
  6640. * sprites: [{
  6641. * type: 'circle',
  6642. * cx: 100,
  6643. * cy: 100,
  6644. * r: 50,
  6645. * fillStyle: '#1F6D91'
  6646. * }]
  6647. * });
  6648. */
  6649. Ext.define('Ext.draw.sprite.Circle', {
  6650. extend: 'Ext.draw.sprite.Path',
  6651. alias: 'sprite.circle',
  6652. type: 'circle',
  6653. inheritableStatics: {
  6654. def: {
  6655. processors: {
  6656. /**
  6657. * @cfg {Number} [cx=0] The center coordinate of the sprite on the x-axis.
  6658. */
  6659. cx: 'number',
  6660. /**
  6661. * @cfg {Number} [cy=0] The center coordinate of the sprite on the y-axis.
  6662. */
  6663. cy: 'number',
  6664. /**
  6665. * @cfg {Number} [r=0] The radius of the sprite.
  6666. */
  6667. r: 'number'
  6668. },
  6669. aliases: {
  6670. radius: 'r',
  6671. x: 'cx',
  6672. y: 'cy',
  6673. centerX: 'cx',
  6674. centerY: 'cy'
  6675. },
  6676. defaults: {
  6677. cx: 0,
  6678. cy: 0,
  6679. r: 4
  6680. },
  6681. triggers: {
  6682. cx: 'path',
  6683. cy: 'path',
  6684. r: 'path'
  6685. }
  6686. }
  6687. },
  6688. updatePlainBBox: function(plain) {
  6689. var attr = this.attr,
  6690. cx = attr.cx,
  6691. cy = attr.cy,
  6692. r = attr.r;
  6693. plain.x = cx - r;
  6694. plain.y = cy - r;
  6695. plain.width = r + r;
  6696. plain.height = r + r;
  6697. },
  6698. updateTransformedBBox: function(transform) {
  6699. var attr = this.attr,
  6700. cx = attr.cx,
  6701. cy = attr.cy,
  6702. r = attr.r,
  6703. matrix = attr.matrix,
  6704. scaleX = matrix.getScaleX(),
  6705. scaleY = matrix.getScaleY(),
  6706. rx, ry;
  6707. rx = scaleX * r;
  6708. ry = scaleY * r;
  6709. transform.x = matrix.x(cx, cy) - rx;
  6710. transform.y = matrix.y(cx, cy) - ry;
  6711. transform.width = rx + rx;
  6712. transform.height = ry + ry;
  6713. },
  6714. updatePath: function(path, attr) {
  6715. path.arc(attr.cx, attr.cy, attr.r, 0, Math.PI * 2, false);
  6716. }
  6717. });
  6718. /**
  6719. * @class Ext.draw.sprite.Arc
  6720. * @extend Ext.draw.sprite.Circle
  6721. *
  6722. * A sprite that represents a circular arc.
  6723. *
  6724. * @example
  6725. * Ext.create({
  6726. * xtype: 'draw',
  6727. * renderTo: document.body,
  6728. * width: 600,
  6729. * height: 400,
  6730. * sprites: [{
  6731. * type: 'arc',
  6732. * cx: 100,
  6733. * cy: 100,
  6734. * r: 80,
  6735. * fillStyle: '#1F6D91',
  6736. * startAngle: 0,
  6737. * endAngle: Math.PI,
  6738. * anticlockwise: true
  6739. * }]
  6740. * });
  6741. */
  6742. Ext.define('Ext.draw.sprite.Arc', {
  6743. extend: 'Ext.draw.sprite.Circle',
  6744. alias: 'sprite.arc',
  6745. type: 'arc',
  6746. inheritableStatics: {
  6747. def: {
  6748. processors: {
  6749. /**
  6750. * @cfg {Number} [startAngle=0] The beginning angle of the arc.
  6751. */
  6752. startAngle: 'number',
  6753. /**
  6754. * @cfg {Number} [endAngle=Math.PI*2] The ending angle of the arc.
  6755. */
  6756. endAngle: 'number',
  6757. /**
  6758. * @cfg {Boolean} [anticlockwise=false] Determines whether or not the arc is drawn
  6759. * clockwise.
  6760. */
  6761. anticlockwise: 'bool'
  6762. },
  6763. aliases: {
  6764. from: 'startAngle',
  6765. to: 'endAngle',
  6766. start: 'startAngle',
  6767. end: 'endAngle'
  6768. },
  6769. defaults: {
  6770. startAngle: 0,
  6771. endAngle: Math.PI * 2,
  6772. anticlockwise: false
  6773. },
  6774. triggers: {
  6775. startAngle: 'path',
  6776. endAngle: 'path',
  6777. anticlockwise: 'path'
  6778. }
  6779. }
  6780. },
  6781. updatePath: function(path, attr) {
  6782. path.arc(attr.cx, attr.cy, attr.r, attr.startAngle, attr.endAngle, attr.anticlockwise);
  6783. }
  6784. });
  6785. /**
  6786. * A sprite that represents an arrow.
  6787. *
  6788. * @example
  6789. * Ext.create({
  6790. * xtype: 'draw',
  6791. * renderTo: document.body,
  6792. * width: 600,
  6793. * height: 400,
  6794. * sprites: [{
  6795. * type: 'arrow',
  6796. * translationX: 100,
  6797. * translationY: 100,
  6798. * size: 40,
  6799. * fillStyle: '#30BDA7'
  6800. * }]
  6801. * });
  6802. */
  6803. Ext.define('Ext.draw.sprite.Arrow', {
  6804. extend: 'Ext.draw.sprite.Path',
  6805. alias: 'sprite.arrow',
  6806. inheritableStatics: {
  6807. def: {
  6808. processors: {
  6809. x: 'number',
  6810. y: 'number',
  6811. /**
  6812. * @cfg {Number} [size=4] The size of the sprite.
  6813. * Meant to be comparable to the size of a circle sprite with the same radius.
  6814. */
  6815. size: 'number'
  6816. },
  6817. defaults: {
  6818. x: 0,
  6819. y: 0,
  6820. size: 4
  6821. },
  6822. triggers: {
  6823. x: 'path',
  6824. y: 'path',
  6825. size: 'path'
  6826. }
  6827. }
  6828. },
  6829. updatePath: function(path, attr) {
  6830. var s = attr.size * 1.5,
  6831. x = attr.x - attr.lineWidth / 2,
  6832. y = attr.y;
  6833. path.fromSvgString('M'.concat(x - s * 0.7, ',', y - s * 0.4, 'l', [
  6834. s * 0.6,
  6835. 0,
  6836. 0,
  6837. -s * 0.4,
  6838. s,
  6839. s * 0.8,
  6840. -s,
  6841. s * 0.8,
  6842. 0,
  6843. -s * 0.4,
  6844. -s * 0.6,
  6845. 0
  6846. ], 'z'));
  6847. }
  6848. });
  6849. /**
  6850. * @class Ext.draw.sprite.Composite
  6851. *
  6852. * Represents a group of sprites.
  6853. * Composite's sprites are rendered in the order they've been added to the Composite.
  6854. * The rendering order of composite sprites themselves is determined by the value of
  6855. * their zIndex attribute, just like with any other sprite.
  6856. * Every sprite that is added to the Composite is removed from whatever Surface/Composite
  6857. * it belongs to.
  6858. */
  6859. Ext.define('Ext.draw.sprite.Composite', {
  6860. extend: 'Ext.draw.sprite.Sprite',
  6861. alias: 'sprite.composite',
  6862. type: 'composite',
  6863. isComposite: true,
  6864. config: {
  6865. sprites: []
  6866. },
  6867. constructor: function(config) {
  6868. this.sprites = [];
  6869. this.map = {};
  6870. this.callParent([
  6871. config
  6872. ]);
  6873. },
  6874. /**
  6875. * Adds sprite(s) to the composite.
  6876. * @param {Ext.draw.sprite.Sprite/Ext.draw.sprite.Sprite[]/Object/Object[]} sprite
  6877. * @return {Ext.draw.sprite.Sprite/Ext.draw.sprite.Sprite[]}
  6878. */
  6879. addSprite: function(sprite) {
  6880. var i = 0,
  6881. attr, results, oldTransformations;
  6882. if (Ext.isArray(sprite)) {
  6883. results = [];
  6884. while (i < sprite.length) {
  6885. results.push(this.addSprite(sprite[i++]));
  6886. }
  6887. return results;
  6888. }
  6889. if (sprite && sprite.type && !sprite.isSprite) {
  6890. sprite = Ext.create('sprite.' + sprite.type, sprite);
  6891. }
  6892. if (!sprite || !sprite.isSprite || sprite.isComposite) {
  6893. return null;
  6894. }
  6895. sprite.setSurface(null);
  6896. sprite.setParent(this);
  6897. attr = this.attr;
  6898. oldTransformations = sprite.applyTransformations;
  6899. sprite.applyTransformations = function(force) {
  6900. if (sprite.attr.dirtyTransform) {
  6901. attr.dirtyTransform = true;
  6902. attr.bbox.plain.dirty = true;
  6903. attr.bbox.transform.dirty = true;
  6904. }
  6905. oldTransformations.call(sprite, force);
  6906. };
  6907. this.sprites.push(sprite);
  6908. this.map[sprite.id] = sprite.getId();
  6909. attr.bbox.plain.dirty = true;
  6910. attr.bbox.transform.dirty = true;
  6911. return sprite;
  6912. },
  6913. /**
  6914. * @deprecated 6.2.1 Use {@link #addSprite} instead.
  6915. */
  6916. add: function(sprite) {
  6917. return this.addSprite(sprite);
  6918. },
  6919. removeSprite: function(sprite, isDestroy) {
  6920. var me = this,
  6921. id, isOwnSprite;
  6922. if (sprite) {
  6923. if (sprite.charAt) {
  6924. // is String
  6925. sprite = me.map[sprite];
  6926. }
  6927. if (!sprite || !sprite.isSprite) {
  6928. return null;
  6929. }
  6930. if (sprite.destroyed || sprite.destroying) {
  6931. return sprite;
  6932. }
  6933. id = sprite.getId();
  6934. isOwnSprite = me.map[id];
  6935. delete me.map[id];
  6936. if (isDestroy) {
  6937. sprite.destroy();
  6938. }
  6939. if (!isOwnSprite) {
  6940. return sprite;
  6941. }
  6942. sprite.setParent(null);
  6943. // sprite.setSurface(null);
  6944. Ext.Array.remove(me.sprites, sprite);
  6945. me.dirtyZIndex = true;
  6946. me.setDirty(true);
  6947. }
  6948. return sprite || null;
  6949. },
  6950. /**
  6951. * @deprecated 6.2.1 Use {@link #addSprite} instead.
  6952. * Adds a list of sprites to the composite.
  6953. * @param {Ext.draw.sprite.Sprite[]|Object[]|Ext.draw.sprite.Sprite|Object} sprites
  6954. */
  6955. addAll: function(sprites) {
  6956. var i = 0;
  6957. if (sprites.isSprite || sprites.type) {
  6958. this.add(sprites);
  6959. } else if (Ext.isArray(sprites)) {
  6960. while (i < sprites.length) {
  6961. this.add(sprites[i++]);
  6962. }
  6963. }
  6964. },
  6965. /**
  6966. * Updates the bounding box of the composite, which contains the bounding box of all sprites
  6967. * in the composite.
  6968. */
  6969. updatePlainBBox: function(plain) {
  6970. var me = this,
  6971. left = Infinity,
  6972. right = -Infinity,
  6973. top = Infinity,
  6974. bottom = -Infinity,
  6975. sprite, bbox, i, ln;
  6976. for (i = 0 , ln = me.sprites.length; i < ln; i++) {
  6977. sprite = me.sprites[i];
  6978. sprite.applyTransformations();
  6979. bbox = sprite.getBBox();
  6980. if (left > bbox.x) {
  6981. left = bbox.x;
  6982. }
  6983. if (right < bbox.x + bbox.width) {
  6984. right = bbox.x + bbox.width;
  6985. }
  6986. if (top > bbox.y) {
  6987. top = bbox.y;
  6988. }
  6989. if (bottom < bbox.y + bbox.height) {
  6990. bottom = bbox.y + bbox.height;
  6991. }
  6992. }
  6993. plain.x = left;
  6994. plain.y = top;
  6995. plain.width = right - left;
  6996. plain.height = bottom - top;
  6997. },
  6998. isVisible: function() {
  6999. // Override the abstract Sprite's method.
  7000. // Composite uses a simpler check, because it has no fill or stroke
  7001. // style of its own, it just houses other sprites.
  7002. var attr = this.attr,
  7003. parent = this.getParent(),
  7004. hasParent = parent && (parent.isSurface || parent.isVisible()),
  7005. isSeen = hasParent && !attr.hidden && attr.globalAlpha;
  7006. return !!isSeen;
  7007. },
  7008. /**
  7009. * Renders all sprites contained in the composite to the surface.
  7010. */
  7011. render: function(surface, ctx, rect) {
  7012. var me = this,
  7013. attr = me.attr,
  7014. mat = me.attr.matrix,
  7015. sprites = me.sprites,
  7016. ln = sprites.length,
  7017. i = 0;
  7018. mat.toContext(ctx);
  7019. for (; i < ln; i++) {
  7020. surface.renderSprite(sprites[i], rect);
  7021. }
  7022. //<debug>
  7023. // eslint-disable-next-line vars-on-top
  7024. var debug = attr.debug || me.statics().debug || Ext.draw.sprite.Sprite.debug;
  7025. if (debug) {
  7026. attr.inverseMatrix.toContext(ctx);
  7027. if (debug.bbox) {
  7028. me.renderBBox(surface, ctx);
  7029. }
  7030. }
  7031. },
  7032. //</debug>
  7033. destroy: function() {
  7034. var me = this,
  7035. sprites = me.sprites,
  7036. ln = sprites.length,
  7037. i;
  7038. for (i = 0; i < ln; i++) {
  7039. sprites[i].destroy();
  7040. }
  7041. sprites.length = 0;
  7042. me.callParent();
  7043. }
  7044. });
  7045. /**
  7046. * A sprite that represents a cross.
  7047. *
  7048. * @example
  7049. * Ext.create({
  7050. * xtype: 'draw',
  7051. * renderTo: document.body,
  7052. * width: 600,
  7053. * height: 400,
  7054. * sprites: [{
  7055. * type: 'cross',
  7056. * translationX: 100,
  7057. * translationY: 100,
  7058. * size: 40,
  7059. * fillStyle: '#1F6D91'
  7060. * }]
  7061. * });
  7062. */
  7063. Ext.define('Ext.draw.sprite.Cross', {
  7064. extend: 'Ext.draw.sprite.Path',
  7065. alias: 'sprite.cross',
  7066. inheritableStatics: {
  7067. def: {
  7068. processors: {
  7069. x: 'number',
  7070. y: 'number',
  7071. /**
  7072. * @cfg {Number} [size=4] The size of the sprite.
  7073. * Meant to be comparable to the size of a circle sprite with the same radius.
  7074. */
  7075. size: 'number'
  7076. },
  7077. defaults: {
  7078. x: 0,
  7079. y: 0,
  7080. size: 4
  7081. },
  7082. triggers: {
  7083. x: 'path',
  7084. y: 'path',
  7085. size: 'path'
  7086. }
  7087. }
  7088. },
  7089. updatePath: function(path, attr) {
  7090. var s = attr.size / 1.7,
  7091. x = attr.x - attr.lineWidth / 2,
  7092. y = attr.y;
  7093. path.fromSvgString('M'.concat(x - s, ',', y, 'l', [
  7094. -s,
  7095. -s,
  7096. s,
  7097. -s,
  7098. s,
  7099. s,
  7100. s,
  7101. -s,
  7102. s,
  7103. s,
  7104. -s,
  7105. s,
  7106. s,
  7107. s,
  7108. -s,
  7109. s,
  7110. -s,
  7111. -s,
  7112. -s,
  7113. s,
  7114. -s,
  7115. -s,
  7116. 'z'
  7117. ]));
  7118. }
  7119. });
  7120. /**
  7121. * A sprite that represents a diamond.
  7122. *
  7123. * @example
  7124. * Ext.create({
  7125. * xtype: 'draw',
  7126. * renderTo: document.body,
  7127. * width: 600,
  7128. * height: 400,
  7129. * sprites: [{
  7130. * type: 'diamond',
  7131. * translationX: 100,
  7132. * translationY: 100,
  7133. * size: 40,
  7134. * fillStyle: '#1F6D91'
  7135. * }]
  7136. * });
  7137. */
  7138. Ext.define('Ext.draw.sprite.Diamond', {
  7139. extend: 'Ext.draw.sprite.Path',
  7140. alias: 'sprite.diamond',
  7141. inheritableStatics: {
  7142. def: {
  7143. processors: {
  7144. x: 'number',
  7145. y: 'number',
  7146. /**
  7147. * @cfg {Number} [size=4] The size of the sprite.
  7148. * Meant to be comparable to the size of a circle sprite with the same radius.
  7149. */
  7150. size: 'number'
  7151. },
  7152. defaults: {
  7153. x: 0,
  7154. y: 0,
  7155. size: 4
  7156. },
  7157. triggers: {
  7158. x: 'path',
  7159. y: 'path',
  7160. size: 'path'
  7161. }
  7162. }
  7163. },
  7164. updatePath: function(path, attr) {
  7165. var s = attr.size * 1.25,
  7166. x = attr.x - attr.lineWidth / 2,
  7167. y = attr.y;
  7168. path.fromSvgString([
  7169. 'M',
  7170. x,
  7171. y - s,
  7172. 'l',
  7173. s,
  7174. s,
  7175. -s,
  7176. s,
  7177. -s,
  7178. -s,
  7179. s,
  7180. -s,
  7181. 'z'
  7182. ]);
  7183. }
  7184. });
  7185. /**
  7186. * @class Ext.draw.sprite.Ellipse
  7187. * @extends Ext.draw.sprite.Path
  7188. *
  7189. * A sprite that represents an ellipse.
  7190. *
  7191. * @example
  7192. * Ext.create({
  7193. * xtype: 'draw',
  7194. * renderTo: document.body,
  7195. * width: 600,
  7196. * height: 400,
  7197. * sprites: [{
  7198. * type: 'ellipse',
  7199. * cx: 100,
  7200. * cy: 100,
  7201. * rx: 80,
  7202. * ry: 50,
  7203. * fillStyle: '#1F6D91'
  7204. * }]
  7205. * });
  7206. */
  7207. Ext.define("Ext.draw.sprite.Ellipse", {
  7208. extend: "Ext.draw.sprite.Path",
  7209. alias: 'sprite.ellipse',
  7210. type: 'ellipse',
  7211. inheritableStatics: {
  7212. def: {
  7213. processors: {
  7214. /**
  7215. * @cfg {Number} [cx=0] The center coordinate of the sprite on the x-axis.
  7216. */
  7217. cx: "number",
  7218. /**
  7219. * @cfg {Number} [cy=0] The center coordinate of the sprite on the y-axis.
  7220. */
  7221. cy: "number",
  7222. /**
  7223. * @cfg {Number} [rx=1] The radius of the sprite on the x-axis.
  7224. */
  7225. rx: "number",
  7226. /**
  7227. * @cfg {Number} [ry=1] The radius of the sprite on the y-axis.
  7228. */
  7229. ry: "number",
  7230. /**
  7231. * @cfg {Number} [axisRotation=0] The rotation of the sprite about its axis.
  7232. */
  7233. axisRotation: "number"
  7234. },
  7235. aliases: {
  7236. radius: "r",
  7237. x: "cx",
  7238. y: "cy",
  7239. centerX: "cx",
  7240. centerY: "cy",
  7241. radiusX: "rx",
  7242. radiusY: "ry"
  7243. },
  7244. defaults: {
  7245. cx: 0,
  7246. cy: 0,
  7247. rx: 1,
  7248. ry: 1,
  7249. axisRotation: 0
  7250. },
  7251. triggers: {
  7252. cx: 'path',
  7253. cy: 'path',
  7254. rx: 'path',
  7255. ry: 'path',
  7256. axisRotation: 'path'
  7257. }
  7258. }
  7259. },
  7260. updatePlainBBox: function(plain) {
  7261. var attr = this.attr,
  7262. cx = attr.cx,
  7263. cy = attr.cy,
  7264. rx = attr.rx,
  7265. ry = attr.ry;
  7266. plain.x = cx - rx;
  7267. plain.y = cy - ry;
  7268. plain.width = rx + rx;
  7269. plain.height = ry + ry;
  7270. },
  7271. updateTransformedBBox: function(transform) {
  7272. var attr = this.attr,
  7273. cx = attr.cx,
  7274. cy = attr.cy,
  7275. rx = attr.rx,
  7276. ry = attr.ry,
  7277. rxy = ry / rx,
  7278. matrix = attr.matrix.clone(),
  7279. xx, xy, yx, yy, dx, dy, w, h;
  7280. matrix.append(1, 0, 0, rxy, 0, cy * (1 - rxy));
  7281. xx = matrix.getXX();
  7282. yx = matrix.getYX();
  7283. dx = matrix.getDX();
  7284. xy = matrix.getXY();
  7285. yy = matrix.getYY();
  7286. dy = matrix.getDY();
  7287. w = Math.sqrt(xx * xx + yx * yx) * rx;
  7288. h = Math.sqrt(xy * xy + yy * yy) * rx;
  7289. transform.x = cx * xx + cy * yx + dx - w;
  7290. transform.y = cx * xy + cy * yy + dy - h;
  7291. transform.width = w + w;
  7292. transform.height = h + h;
  7293. },
  7294. updatePath: function(path, attr) {
  7295. path.ellipse(attr.cx, attr.cy, attr.rx, attr.ry, attr.axisRotation, 0, Math.PI * 2, false);
  7296. }
  7297. });
  7298. /**
  7299. * @class Ext.draw.sprite.EllipticalArc
  7300. * @extends Ext.draw.sprite.Ellipse
  7301. *
  7302. * A sprite that represents an elliptical arc.
  7303. *
  7304. * @example
  7305. * Ext.create({
  7306. * xtype: 'draw',
  7307. * renderTo: document.body,
  7308. * width: 600,
  7309. * height: 400,
  7310. * sprites: [{
  7311. * type: 'ellipticalArc',
  7312. * cx: 100,
  7313. * cy: 100,
  7314. * rx: 80,
  7315. * ry: 50,
  7316. * fillStyle: '#1F6D91',
  7317. * startAngle: 0,
  7318. * endAngle: Math.PI,
  7319. * anticlockwise: true
  7320. * }]
  7321. * });
  7322. */
  7323. Ext.define('Ext.draw.sprite.EllipticalArc', {
  7324. extend: 'Ext.draw.sprite.Ellipse',
  7325. alias: 'sprite.ellipticalArc',
  7326. type: 'ellipticalArc',
  7327. inheritableStatics: {
  7328. def: {
  7329. processors: {
  7330. /**
  7331. * @cfg {Number} [startAngle=0] The beginning angle of the arc.
  7332. */
  7333. startAngle: 'number',
  7334. /**
  7335. * @cfg {Number} [endAngle=Math.PI*2] The ending angle of the arc.
  7336. */
  7337. endAngle: 'number',
  7338. /**
  7339. * @cfg {Boolean} [anticlockwise=false] Determines whether or not the arc
  7340. * is drawn clockwise.
  7341. */
  7342. anticlockwise: 'bool'
  7343. },
  7344. aliases: {
  7345. from: 'startAngle',
  7346. to: 'endAngle',
  7347. start: 'startAngle',
  7348. end: 'endAngle'
  7349. },
  7350. defaults: {
  7351. startAngle: 0,
  7352. endAngle: Math.PI * 2,
  7353. anticlockwise: false
  7354. },
  7355. triggers: {
  7356. startAngle: 'path',
  7357. endAngle: 'path',
  7358. anticlockwise: 'path'
  7359. }
  7360. }
  7361. },
  7362. updatePath: function(path, attr) {
  7363. path.ellipse(attr.cx, attr.cy, attr.rx, attr.ry, attr.axisRotation, attr.startAngle, attr.endAngle, attr.anticlockwise);
  7364. }
  7365. });
  7366. /**
  7367. * @class Ext.draw.sprite.Rect
  7368. * @extends Ext.draw.sprite.Path
  7369. *
  7370. * A sprite that represents a rectangle.
  7371. *
  7372. * @example
  7373. * Ext.create({
  7374. * xtype: 'draw',
  7375. * renderTo: document.body,
  7376. * width: 600,
  7377. * height: 400,
  7378. * sprites: [{
  7379. * type: 'rect',
  7380. * x: 50,
  7381. * y: 50,
  7382. * width: 100,
  7383. * height: 100,
  7384. * fillStyle: '#1F6D91'
  7385. * }]
  7386. * });
  7387. */
  7388. Ext.define('Ext.draw.sprite.Rect', {
  7389. extend: 'Ext.draw.sprite.Path',
  7390. alias: 'sprite.rect',
  7391. type: 'rect',
  7392. inheritableStatics: {
  7393. def: {
  7394. processors: {
  7395. /**
  7396. * @cfg {Number} [x=0] The position of the sprite on the x-axis.
  7397. */
  7398. x: 'number',
  7399. /**
  7400. * @cfg {Number} [y=0] The position of the sprite on the y-axis.
  7401. */
  7402. y: 'number',
  7403. /**
  7404. * @cfg {Number} [width=8] The width of the sprite.
  7405. */
  7406. width: 'number',
  7407. /**
  7408. * @cfg {Number} [height=8] The height of the sprite.
  7409. */
  7410. height: 'number',
  7411. /**
  7412. * @cfg {Number} [radius=0] The radius of the rounded corners.
  7413. */
  7414. radius: 'number'
  7415. },
  7416. aliases: {},
  7417. triggers: {
  7418. x: 'path',
  7419. y: 'path',
  7420. width: 'path',
  7421. height: 'path',
  7422. radius: 'path'
  7423. },
  7424. defaults: {
  7425. x: 0,
  7426. y: 0,
  7427. width: 8,
  7428. height: 8,
  7429. radius: 0
  7430. }
  7431. }
  7432. },
  7433. updatePlainBBox: function(plain) {
  7434. var attr = this.attr;
  7435. plain.x = attr.x;
  7436. plain.y = attr.y;
  7437. plain.width = attr.width;
  7438. plain.height = attr.height;
  7439. },
  7440. updateTransformedBBox: function(transform, plain) {
  7441. this.attr.matrix.transformBBox(plain, this.attr.radius, transform);
  7442. },
  7443. updatePath: function(path, attr) {
  7444. var x = attr.x,
  7445. y = attr.y,
  7446. width = attr.width,
  7447. height = attr.height,
  7448. radius = Math.min(attr.radius, Math.abs(height) * 0.5, Math.abs(width) * 0.5);
  7449. if (radius === 0) {
  7450. path.rect(x, y, width, height);
  7451. } else {
  7452. path.moveTo(x + radius, y);
  7453. path.arcTo(x + width, y, x + width, y + height, radius);
  7454. path.arcTo(x + width, y + height, x, y + height, radius);
  7455. path.arcTo(x, y + height, x, y, radius);
  7456. path.arcTo(x, y, x + radius, y, radius);
  7457. path.closePath();
  7458. }
  7459. }
  7460. });
  7461. /**
  7462. * @class Ext.draw.sprite.Image
  7463. * @extends Ext.draw.sprite.Rect
  7464. *
  7465. * A sprite that represents an image.
  7466. */
  7467. Ext.define('Ext.draw.sprite.Image', {
  7468. extend: 'Ext.draw.sprite.Rect',
  7469. alias: 'sprite.image',
  7470. type: 'image',
  7471. statics: {
  7472. imageLoaders: {}
  7473. },
  7474. inheritableStatics: {
  7475. def: {
  7476. processors: {
  7477. /**
  7478. * @cfg {String} [src=''] The image source of the sprite.
  7479. */
  7480. src: 'string'
  7481. },
  7482. /**
  7483. * @private
  7484. * @cfg {Number} radius
  7485. */
  7486. triggers: {
  7487. src: 'src'
  7488. },
  7489. updaters: {
  7490. src: 'updateSource'
  7491. },
  7492. defaults: {
  7493. src: '',
  7494. /**
  7495. * @cfg {Number} [width=null] The width of the image.
  7496. * For consistent image size on all devices the width must be explicitly set.
  7497. * Otherwise the natural image width devided by the device pixel ratio
  7498. * (for a crisp looking image) will be used as the width of the sprite.
  7499. */
  7500. width: null,
  7501. /**
  7502. * @cfg {Number} [height=null] The height of the image.
  7503. * For consistent image size on all devices the height must be explicitly set.
  7504. * Otherwise the natural image height devided by the device pixel ratio
  7505. * (for a crisp looking image) will be used as the height of the sprite.
  7506. */
  7507. height: null
  7508. }
  7509. }
  7510. },
  7511. updateSurface: function(surface) {
  7512. if (surface) {
  7513. this.updateSource(this.attr);
  7514. }
  7515. },
  7516. updateSource: function(attr) {
  7517. var me = this,
  7518. src = attr.src,
  7519. surface = me.getSurface(),
  7520. loadingStub = Ext.draw.sprite.Image.imageLoaders[src],
  7521. width = attr.width,
  7522. height = attr.height,
  7523. imageLoader, i;
  7524. if (!surface) {
  7525. // First time this is called the sprite won't have a surface yet.
  7526. return;
  7527. }
  7528. if (!loadingStub) {
  7529. imageLoader = new Image();
  7530. loadingStub = Ext.draw.sprite.Image.imageLoaders[src] = {
  7531. image: imageLoader,
  7532. done: false,
  7533. pendingSprites: [
  7534. me
  7535. ],
  7536. pendingSurfaces: [
  7537. surface
  7538. ]
  7539. };
  7540. imageLoader.width = width;
  7541. imageLoader.height = height;
  7542. imageLoader.onload = function() {
  7543. var item;
  7544. if (!loadingStub.done) {
  7545. loadingStub.done = true;
  7546. for (i = 0; i < loadingStub.pendingSprites.length; i++) {
  7547. item = loadingStub.pendingSprites[i];
  7548. if (!item.destroyed) {
  7549. item.setDirty(true);
  7550. }
  7551. }
  7552. for (i = 0; i < loadingStub.pendingSurfaces.length; i++) {
  7553. item = loadingStub.pendingSurfaces[i];
  7554. if (!item.destroyed) {
  7555. item.renderFrame();
  7556. }
  7557. }
  7558. }
  7559. };
  7560. imageLoader.src = src;
  7561. } else {
  7562. Ext.Array.include(loadingStub.pendingSprites, me);
  7563. Ext.Array.include(loadingStub.pendingSurfaces, surface);
  7564. }
  7565. },
  7566. render: function(surface, ctx) {
  7567. var me = this,
  7568. attr = me.attr,
  7569. mat = attr.matrix,
  7570. src = attr.src,
  7571. x = attr.x,
  7572. y = attr.y,
  7573. width = attr.width,
  7574. height = attr.height,
  7575. loadingStub = Ext.draw.sprite.Image.imageLoaders[src],
  7576. image;
  7577. if (loadingStub && loadingStub.done) {
  7578. mat.toContext(ctx);
  7579. image = loadingStub.image;
  7580. ctx.drawImage(image, x, y, width || (image.naturalWidth || image.width) / surface.devicePixelRatio, height || (image.naturalHeight || image.height) / surface.devicePixelRatio);
  7581. }
  7582. //<debug>
  7583. // eslint-disable-next-line vars-on-top
  7584. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  7585. if (debug && debug.bbox) {
  7586. this.renderBBox(surface, ctx);
  7587. }
  7588. },
  7589. //</debug>
  7590. /**
  7591. * @private
  7592. */
  7593. isVisible: function() {
  7594. var attr = this.attr,
  7595. parent = this.getParent(),
  7596. hasParent = parent && (parent.isSurface || parent.isVisible()),
  7597. isSeen = hasParent && !attr.hidden && attr.globalAlpha;
  7598. return !!isSeen;
  7599. }
  7600. });
  7601. /**
  7602. * @class Ext.draw.sprite.Instancing
  7603. * @extends Ext.draw.sprite.Sprite
  7604. *
  7605. * Sprite that represents multiple instances based on the given template.
  7606. */
  7607. Ext.define('Ext.draw.sprite.Instancing', {
  7608. extend: 'Ext.draw.sprite.Sprite',
  7609. alias: 'sprite.instancing',
  7610. type: 'instancing',
  7611. isInstancing: true,
  7612. config: {
  7613. /**
  7614. * @cfg {Object} [template] The sprite template used by all instances.
  7615. */
  7616. template: null,
  7617. /**
  7618. * @cfg {Array} [instances]
  7619. * The instances of the {@link #template} sprite as configs of attributes.
  7620. */
  7621. instances: null
  7622. },
  7623. instances: null,
  7624. applyTemplate: function(template) {
  7625. var surface;
  7626. //<debug>
  7627. if (!Ext.isObject(template)) {
  7628. 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.");
  7629. } else if (template.isInstancing || template.isComposite) {
  7630. Ext.raise("Can't use an instancing or composite sprite " + "as a template for an instancing sprite.");
  7631. }
  7632. //</debug>
  7633. if (!template.isSprite) {
  7634. if (!template.xclass && !template.type) {
  7635. // For compatibility with legacy charts.
  7636. template.type = 'circle';
  7637. }
  7638. template = Ext.create(template.xclass || 'sprite.' + template.type, template);
  7639. }
  7640. surface = template.getSurface();
  7641. if (surface) {
  7642. surface.remove(template);
  7643. }
  7644. template.setParent(this);
  7645. return template;
  7646. },
  7647. updateTemplate: function(template, oldTemplate) {
  7648. if (oldTemplate) {
  7649. delete oldTemplate.ownAttr;
  7650. }
  7651. template.setSurface(this.getSurface());
  7652. // ownAttr is used to get a reference to the template's attributes
  7653. // when one of the instances is rendering, as at that moment the template's
  7654. // attributes (template.attr) are the instance's attributes.
  7655. template.ownAttr = template.attr;
  7656. this.clearAll();
  7657. this.setDirty(true);
  7658. },
  7659. updateInstances: function(instances) {
  7660. var i, ln;
  7661. this.clearAll();
  7662. if (Ext.isArray(instances)) {
  7663. for (i = 0 , ln = instances.length; i < ln; i++) {
  7664. this.add(instances[i]);
  7665. }
  7666. }
  7667. },
  7668. updateSurface: function(surface) {
  7669. var template = this.getTemplate();
  7670. if (template && !template.destroyed) {
  7671. template.setSurface(surface);
  7672. }
  7673. },
  7674. get: function(index) {
  7675. return this.instances[index];
  7676. },
  7677. getCount: function() {
  7678. return this.instances.length;
  7679. },
  7680. clearAll: function() {
  7681. var template = this.getTemplate();
  7682. template.attr.children = this.instances = [];
  7683. this.position = 0;
  7684. },
  7685. /**
  7686. * @deprecated 6.2.0
  7687. * Deprecated, use the {@link #add} method instead.
  7688. */
  7689. createInstance: function(config, bypassNormalization, avoidCopy) {
  7690. return this.add(config, bypassNormalization, avoidCopy);
  7691. },
  7692. /**
  7693. * Creates a new sprite instance.
  7694. *
  7695. * @param {Object} config The configuration of the instance.
  7696. * @param {Boolean} [bypassNormalization] 'true' to bypass attribute normalization.
  7697. * @param {Boolean} [avoidCopy] 'true' to avoid copying the `config` object.
  7698. * @return {Object} The attributes of the instance.
  7699. */
  7700. add: function(config, bypassNormalization, avoidCopy) {
  7701. var me = this,
  7702. template = me.getTemplate(),
  7703. originalAttr = template.attr,
  7704. attr = Ext.Object.chain(originalAttr);
  7705. template.modifiers.target.prepareAttributes(attr);
  7706. template.attr = attr;
  7707. template.setAttributes(config, bypassNormalization, avoidCopy);
  7708. attr.template = template;
  7709. me.instances.push(attr);
  7710. template.attr = originalAttr;
  7711. me.position++;
  7712. return attr;
  7713. },
  7714. /**
  7715. * Not supported.
  7716. *
  7717. * @return {null}
  7718. */
  7719. getBBox: function() {
  7720. return null;
  7721. },
  7722. /**
  7723. * Returns the bounding box for the instance at the given index.
  7724. *
  7725. * @param {Number} index The index of the instance.
  7726. * @param {Boolean} [isWithoutTransform] 'true' to not apply sprite transforms
  7727. * to the bounding box.
  7728. * @return {Object} The bounding box for the instance.
  7729. */
  7730. getBBoxFor: function(index, isWithoutTransform) {
  7731. var template = this.getTemplate(),
  7732. originalAttr = template.attr,
  7733. bbox;
  7734. template.attr = this.instances[index];
  7735. bbox = template.getBBox(isWithoutTransform);
  7736. template.attr = originalAttr;
  7737. return bbox;
  7738. },
  7739. /**
  7740. * @private
  7741. * Checks if the instancing sprite can be seen.
  7742. * @return {Boolean}
  7743. */
  7744. isVisible: function() {
  7745. var attr = this.attr,
  7746. parent = this.getParent(),
  7747. result;
  7748. result = parent && parent.isSurface && !attr.hidden && attr.globalAlpha;
  7749. return !!result;
  7750. },
  7751. /**
  7752. * @private
  7753. * Checks if the instance of an instancing sprite can be seen.
  7754. * @param {Number} index The index of the instance.
  7755. */
  7756. isInstanceVisible: function(index) {
  7757. var me = this,
  7758. template = me.getTemplate(),
  7759. originalAttr = template.attr,
  7760. instances = me.instances,
  7761. result = false;
  7762. if (!Ext.isNumber(index) || index < 0 || index >= instances.length || !me.isVisible()) {
  7763. return result;
  7764. }
  7765. template.attr = instances[index];
  7766. // TODO This is clearly a bug, fix it
  7767. // eslint-disable-next-line no-undef
  7768. result = template.isVisible(point, options);
  7769. template.attr = originalAttr;
  7770. return result;
  7771. },
  7772. render: function(surface, ctx, rect) {
  7773. //<debug>
  7774. if (!this.getTemplate()) {
  7775. Ext.raise('An instancing sprite must have a template.');
  7776. }
  7777. //</debug>
  7778. // eslint-disable-next-line vars-on-top
  7779. var me = this,
  7780. template = me.getTemplate(),
  7781. surfaceRect = surface.getRect(),
  7782. mat = me.attr.matrix,
  7783. originalAttr = template.attr,
  7784. instances = me.instances,
  7785. ln = me.position,
  7786. i;
  7787. mat.toContext(ctx);
  7788. template.preRender(surface, ctx, rect);
  7789. template.useAttributes(ctx, surfaceRect);
  7790. template.isSpriteInstance = true;
  7791. for (i = 0; i < ln; i++) {
  7792. if (instances[i].hidden) {
  7793. continue;
  7794. }
  7795. ctx.save();
  7796. template.attr = instances[i];
  7797. template.useAttributes(ctx, surfaceRect);
  7798. template.render(surface, ctx, rect);
  7799. ctx.restore();
  7800. }
  7801. template.isSpriteInstance = false;
  7802. template.attr = originalAttr;
  7803. },
  7804. /**
  7805. * Sets the attributes for the instance at the given index.
  7806. *
  7807. * @param {Number} index the index of the instance
  7808. * @param {Object} changes the attributes to change
  7809. * @param {Boolean} [bypassNormalization] 'true' to avoid attribute normalization
  7810. */
  7811. setAttributesFor: function(index, changes, bypassNormalization) {
  7812. var template = this.getTemplate(),
  7813. originalAttr = template.attr,
  7814. attr = this.instances[index];
  7815. if (!attr) {
  7816. return;
  7817. }
  7818. template.attr = attr;
  7819. if (bypassNormalization) {
  7820. changes = Ext.apply({}, changes);
  7821. } else {
  7822. changes = template.self.def.normalize(changes);
  7823. }
  7824. template.modifiers.target.pushDown(attr, changes);
  7825. template.attr = originalAttr;
  7826. },
  7827. destroy: function() {
  7828. var me = this,
  7829. template = me.getTemplate();
  7830. me.instances = null;
  7831. if (template) {
  7832. template.destroy();
  7833. }
  7834. me.callParent();
  7835. }
  7836. });
  7837. /**
  7838. * @private
  7839. * Adds hit testing methods to the Ext.draw.sprite.Instancing.
  7840. * Included by the Ext.draw.plugin.SpriteEvents.
  7841. */
  7842. Ext.define('Ext.draw.overrides.hittest.sprite.Instancing', {
  7843. override: 'Ext.draw.sprite.Instancing',
  7844. /**
  7845. * Performs a hit test on the instances of an instancing sprite.
  7846. * @param point A two-item array containing x and y coordinates of the point.
  7847. * @param options Hit testing options.
  7848. * @return {Object} A hit result object that contains more information about what
  7849. * exactly was hit or null if nothing was hit.
  7850. * @return {Boolean} return.isInstance `true` if an instance was hit.
  7851. * @return {Ext.draw.sprite.Instancing} return.sprite The instancing sprite.
  7852. * @return {Ext.draw.sprite.Sprite} return.template The template of the instancing sprite.
  7853. * @return {Object} return.instance The attributes of the instance.
  7854. * @return {Number} return.index The index of the instance.
  7855. */
  7856. hitTest: function(point, options) {
  7857. var me = this,
  7858. template = me.getTemplate(),
  7859. originalAttr = template.attr,
  7860. instances = me.instances,
  7861. ln = instances.length,
  7862. i = 0,
  7863. result = null;
  7864. if (!me.isVisible()) {
  7865. return result;
  7866. }
  7867. for (; i < ln; i++) {
  7868. template.attr = instances[i];
  7869. result = template.hitTest(point, options);
  7870. if (result) {
  7871. result.isInstance = true;
  7872. result.template = result.sprite;
  7873. result.sprite = this;
  7874. result.instance = instances[i];
  7875. result.index = i;
  7876. return result;
  7877. }
  7878. }
  7879. template.attr = originalAttr;
  7880. return result;
  7881. }
  7882. });
  7883. /**
  7884. * A sprite that represents a line.
  7885. *
  7886. * @example
  7887. * Ext.create({
  7888. * xtype: 'draw',
  7889. * renderTo: document.body,
  7890. * width: 600,
  7891. * height: 400,
  7892. * sprites: [{
  7893. * type: 'line',
  7894. * fromX: 20,
  7895. * fromY: 20,
  7896. * toX: 120,
  7897. * toY: 120,
  7898. * strokeStyle: '#1F6D91',
  7899. * lineWidth: 3
  7900. * }]
  7901. * });
  7902. */
  7903. Ext.define('Ext.draw.sprite.Line', {
  7904. extend: 'Ext.draw.sprite.Sprite',
  7905. alias: 'sprite.line',
  7906. type: 'line',
  7907. inheritableStatics: {
  7908. def: {
  7909. processors: {
  7910. fromX: 'number',
  7911. fromY: 'number',
  7912. toX: 'number',
  7913. toY: 'number',
  7914. crisp: 'bool'
  7915. },
  7916. defaults: {
  7917. fromX: 0,
  7918. fromY: 0,
  7919. toX: 1,
  7920. toY: 1,
  7921. crisp: false,
  7922. strokeStyle: 'black'
  7923. },
  7924. aliases: {
  7925. x1: 'fromX',
  7926. y1: 'fromY',
  7927. x2: 'toX',
  7928. y2: 'toY'
  7929. },
  7930. triggers: {
  7931. crisp: 'bbox'
  7932. }
  7933. }
  7934. },
  7935. updateLineBBox: function(bbox, isTransform, x1, y1, x2, y2) {
  7936. var attr = this.attr,
  7937. matrix = attr.matrix,
  7938. halfLineWidth = attr.lineWidth / 2,
  7939. fromX, fromY, toX, toY, p, angle, sin, cos, dx, dy;
  7940. if (attr.crisp) {
  7941. x1 = this.align(x1);
  7942. x2 = this.align(x2);
  7943. y1 = this.align(y1);
  7944. y2 = this.align(y2);
  7945. }
  7946. if (isTransform) {
  7947. p = matrix.transformPoint([
  7948. x1,
  7949. y1
  7950. ]);
  7951. x1 = p[0];
  7952. y1 = p[1];
  7953. p = matrix.transformPoint([
  7954. x2,
  7955. y2
  7956. ]);
  7957. x2 = p[0];
  7958. y2 = p[1];
  7959. }
  7960. fromX = Math.min(x1, x2);
  7961. toX = Math.max(x1, x2);
  7962. fromY = Math.min(y1, y2);
  7963. toY = Math.max(y1, y2);
  7964. angle = Math.atan2(toX - fromX, toY - fromY);
  7965. sin = Math.sin(angle);
  7966. cos = Math.cos(angle);
  7967. dx = halfLineWidth * cos;
  7968. dy = halfLineWidth * sin;
  7969. // Offset start and end points of the line by half its thickness,
  7970. // while accounting for line's angle.
  7971. fromX -= dx;
  7972. fromY -= dy;
  7973. toX += dx;
  7974. toY += dy;
  7975. bbox.x = fromX;
  7976. bbox.y = fromY;
  7977. bbox.width = toX - fromX;
  7978. bbox.height = toY - fromY;
  7979. },
  7980. updatePlainBBox: function(plain) {
  7981. var attr = this.attr;
  7982. this.updateLineBBox(plain, false, attr.fromX, attr.fromY, attr.toX, attr.toY);
  7983. },
  7984. updateTransformedBBox: function(transform, plain) {
  7985. var attr = this.attr;
  7986. this.updateLineBBox(transform, true, attr.fromX, attr.fromY, attr.toX, attr.toY);
  7987. },
  7988. align: function(x) {
  7989. return Math.round(x) - 0.5;
  7990. },
  7991. render: function(surface, ctx) {
  7992. var me = this,
  7993. attr = me.attr,
  7994. matrix = attr.matrix;
  7995. matrix.toContext(ctx);
  7996. ctx.beginPath();
  7997. if (attr.crisp) {
  7998. ctx.moveTo(me.align(attr.fromX), me.align(attr.fromY));
  7999. ctx.lineTo(me.align(attr.toX), me.align(attr.toY));
  8000. } else {
  8001. ctx.moveTo(attr.fromX, attr.fromY);
  8002. ctx.lineTo(attr.toX, attr.toY);
  8003. }
  8004. ctx.stroke();
  8005. //<debug>
  8006. // eslint-disable-next-line vars-on-top
  8007. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  8008. if (debug) {
  8009. // This assumes no part of the sprite is rendered after this call.
  8010. // If it is, we need to re-apply transformations.
  8011. // But the bounding box should always be rendered as is, untransformed.
  8012. this.attr.inverseMatrix.toContext(ctx);
  8013. if (debug.bbox) {
  8014. this.renderBBox(surface, ctx);
  8015. }
  8016. }
  8017. }
  8018. });
  8019. //</debug>
  8020. /**
  8021. * A sprite that represents a plus.
  8022. *
  8023. * @example
  8024. * Ext.create({
  8025. * xtype: 'draw',
  8026. * renderTo: document.body,
  8027. * width: 600,
  8028. * height: 400,
  8029. * sprites: [{
  8030. * type: 'plus',
  8031. * translationX: 100,
  8032. * translationY: 100,
  8033. * size: 40,
  8034. * fillStyle: '#1F6D91'
  8035. * }]
  8036. * });
  8037. */
  8038. Ext.define('Ext.draw.sprite.Plus', {
  8039. extend: 'Ext.draw.sprite.Path',
  8040. alias: 'sprite.plus',
  8041. inheritableStatics: {
  8042. def: {
  8043. processors: {
  8044. x: 'number',
  8045. y: 'number',
  8046. /**
  8047. * @cfg {Number} [size=4] The size of the sprite.
  8048. * Meant to be comparable to the size of a circle sprite with the same radius.
  8049. */
  8050. size: 'number'
  8051. },
  8052. defaults: {
  8053. x: 0,
  8054. y: 0,
  8055. size: 4
  8056. },
  8057. triggers: {
  8058. x: 'path',
  8059. y: 'path',
  8060. size: 'path'
  8061. }
  8062. }
  8063. },
  8064. updatePath: function(path, attr) {
  8065. var s = attr.size / 1.3,
  8066. x = attr.x - attr.lineWidth / 2,
  8067. y = attr.y;
  8068. path.fromSvgString('M'.concat(x - s / 2, ',', y - s / 2, 'l', [
  8069. 0,
  8070. -s,
  8071. s,
  8072. 0,
  8073. 0,
  8074. s,
  8075. s,
  8076. 0,
  8077. 0,
  8078. s,
  8079. -s,
  8080. 0,
  8081. 0,
  8082. s,
  8083. -s,
  8084. 0,
  8085. 0,
  8086. -s,
  8087. -s,
  8088. 0,
  8089. 0,
  8090. -s,
  8091. 'z'
  8092. ]));
  8093. }
  8094. });
  8095. /**
  8096. * @class Ext.draw.sprite.Sector
  8097. * @extends Ext.draw.sprite.Path
  8098. *
  8099. * A sprite representing a pie slice.
  8100. *
  8101. * @example
  8102. * Ext.create({
  8103. * xtype: 'draw',
  8104. * renderTo: document.body,
  8105. * width: 600,
  8106. * height: 400,
  8107. * sprites: [{
  8108. * type: 'sector',
  8109. * centerX: 100,
  8110. * centerY: 100,
  8111. * startAngle: -2.355,
  8112. * endAngle: -.785,
  8113. * endRho: 50,
  8114. * fillStyle: '#1F6D91'
  8115. * }]
  8116. * });
  8117. */
  8118. Ext.define('Ext.draw.sprite.Sector', {
  8119. extend: 'Ext.draw.sprite.Path',
  8120. alias: 'sprite.sector',
  8121. type: 'sector',
  8122. inheritableStatics: {
  8123. def: {
  8124. processors: {
  8125. /**
  8126. * @cfg {Number} [centerX=0] The center coordinate of the sprite on the x-axis.
  8127. */
  8128. centerX: 'number',
  8129. /**
  8130. * @cfg {Number} [centerY=0] The center coordinate of the sprite on the y-axis.
  8131. */
  8132. centerY: 'number',
  8133. /**
  8134. * @cfg {Number} [startAngle=0] The starting angle of the sprite.
  8135. */
  8136. startAngle: 'number',
  8137. /**
  8138. * @cfg {Number} [endAngle=0] The ending angle of the sprite.
  8139. */
  8140. endAngle: 'number',
  8141. /**
  8142. * @cfg {Number} [startRho=0] The starting point of the radius of the sprite.
  8143. */
  8144. startRho: 'number',
  8145. /**
  8146. * @cfg {Number} [endRho=150] The ending point of the radius of the sprite.
  8147. */
  8148. endRho: 'number',
  8149. /**
  8150. * @cfg {Number} [margin=0] The margin of the sprite from the center of pie.
  8151. */
  8152. margin: 'number'
  8153. },
  8154. aliases: {
  8155. rho: 'endRho'
  8156. },
  8157. triggers: {
  8158. centerX: 'path,bbox',
  8159. centerY: 'path,bbox',
  8160. startAngle: 'path,bbox',
  8161. endAngle: 'path,bbox',
  8162. startRho: 'path,bbox',
  8163. endRho: 'path,bbox',
  8164. margin: 'path,bbox'
  8165. },
  8166. defaults: {
  8167. centerX: 0,
  8168. centerY: 0,
  8169. startAngle: 0,
  8170. endAngle: 0,
  8171. startRho: 0,
  8172. endRho: 150,
  8173. margin: 0,
  8174. path: 'M 0,0'
  8175. }
  8176. }
  8177. },
  8178. getMidAngle: function() {
  8179. return this.midAngle || 0;
  8180. },
  8181. updatePath: function(path, attr) {
  8182. var startAngle = Math.min(attr.startAngle, attr.endAngle),
  8183. endAngle = Math.max(attr.startAngle, attr.endAngle),
  8184. midAngle = this.midAngle = (startAngle + endAngle) * 0.5,
  8185. fullPie = Ext.Number.isEqual(Math.abs(endAngle - startAngle), Ext.draw.Draw.pi2, 1.0E-10),
  8186. margin = attr.margin,
  8187. centerX = attr.centerX,
  8188. centerY = attr.centerY,
  8189. startRho = Math.min(attr.startRho, attr.endRho),
  8190. endRho = Math.max(attr.startRho, attr.endRho);
  8191. if (margin) {
  8192. centerX += margin * Math.cos(midAngle);
  8193. centerY += margin * Math.sin(midAngle);
  8194. }
  8195. if (!fullPie) {
  8196. path.moveTo(centerX + startRho * Math.cos(startAngle), centerY + startRho * Math.sin(startAngle));
  8197. path.lineTo(centerX + endRho * Math.cos(startAngle), centerY + endRho * Math.sin(startAngle));
  8198. }
  8199. path.arc(centerX, centerY, endRho, startAngle, endAngle, false);
  8200. path[fullPie ? 'moveTo' : 'lineTo'](centerX + startRho * Math.cos(endAngle), centerY + startRho * Math.sin(endAngle));
  8201. path.arc(centerX, centerY, startRho, endAngle, startAngle, true);
  8202. }
  8203. });
  8204. /**
  8205. * A sprite that represents a square.
  8206. *
  8207. * @example
  8208. * Ext.create({
  8209. * xtype: 'draw',
  8210. * renderTo: document.body,
  8211. * width: 600,
  8212. * height: 400,
  8213. * sprites: [{
  8214. * type: 'square',
  8215. * x: 100,
  8216. * y: 100,
  8217. * size: 50,
  8218. * fillStyle: '#1F6D91'
  8219. * }]
  8220. * });
  8221. */
  8222. Ext.define('Ext.draw.sprite.Square', {
  8223. extend: 'Ext.draw.sprite.Path',
  8224. alias: 'sprite.square',
  8225. inheritableStatics: {
  8226. def: {
  8227. processors: {
  8228. x: 'number',
  8229. y: 'number',
  8230. /**
  8231. * @cfg {Number} [size=4] The size of the sprite.
  8232. * Meant to be comparable to the size of a circle sprite with the same radius.
  8233. */
  8234. size: 'number'
  8235. },
  8236. defaults: {
  8237. x: 0,
  8238. y: 0,
  8239. size: 4
  8240. },
  8241. triggers: {
  8242. x: 'path',
  8243. y: 'path',
  8244. size: 'size'
  8245. }
  8246. }
  8247. },
  8248. updatePath: function(path, attr) {
  8249. var size = attr.size * 1.2,
  8250. s = size * 2,
  8251. x = attr.x - attr.lineWidth / 2,
  8252. y = attr.y;
  8253. path.fromSvgString('M'.concat(x - size, ',', y - size, 'l', [
  8254. s,
  8255. 0,
  8256. 0,
  8257. s,
  8258. -s,
  8259. 0,
  8260. 0,
  8261. -s,
  8262. 'z'
  8263. ]));
  8264. }
  8265. });
  8266. /**
  8267. * Utility class to provide a way to *approximately* measure the dimension of text
  8268. * without a drawing context.
  8269. */
  8270. Ext.define('Ext.draw.TextMeasurer', {
  8271. singleton: true,
  8272. requires: [
  8273. 'Ext.util.TextMetrics'
  8274. ],
  8275. measureDiv: null,
  8276. measureCache: {},
  8277. /**
  8278. * @cfg {Boolean} [precise=false]
  8279. * This singleton tries not to make use of the Ext.util.TextMetrics because it is
  8280. * several times slower than TextMeasurer's own solution. TextMetrics is more precise
  8281. * though, so if you have a case where the error is too big, you may want to set
  8282. * this config to `true` to get perfect results at the expense of performance.
  8283. * Note: defaults to `true` in IE8.
  8284. */
  8285. precise: Ext.isIE8,
  8286. measureDivTpl: {
  8287. id: 'ext-draw-text-measurer',
  8288. tag: 'div',
  8289. style: {
  8290. overflow: 'hidden',
  8291. position: 'relative',
  8292. // 'float' is a reserved word. Don't unquote, or it will break the CMD build.
  8293. 'float': 'left',
  8294. width: 0,
  8295. height: 0
  8296. },
  8297. //<debug>
  8298. // Tell the spec runner to ignore this element when checking if the dom is clean.
  8299. 'data-sticky': true,
  8300. //</debug>
  8301. children: {
  8302. tag: 'div',
  8303. style: {
  8304. display: 'block',
  8305. position: 'absolute',
  8306. x: -100000,
  8307. y: -100000,
  8308. padding: 0,
  8309. margin: 0,
  8310. 'z-index': -100000,
  8311. 'white-space': 'nowrap'
  8312. }
  8313. }
  8314. },
  8315. /**
  8316. * @private
  8317. * Measure the size of a text with specific font by using DOM to measure it.
  8318. * Could be very expensive therefore should be used lazily.
  8319. * @param {String} text
  8320. * @param {String} font
  8321. * @return {Object} An object with `width` and `height` properties.
  8322. * @return {Number} return.width
  8323. * @return {Number} return.height
  8324. */
  8325. actualMeasureText: function(text, font) {
  8326. var me = Ext.draw.TextMeasurer,
  8327. measureDiv = me.measureDiv,
  8328. FARAWAY = 100000,
  8329. size, parent;
  8330. if (!measureDiv) {
  8331. parent = Ext.Element.create({
  8332. //<debug>
  8333. // Tell the spec runner to ignore this element when checking if the dom is clean.
  8334. 'data-sticky': true,
  8335. //</debug>
  8336. style: {
  8337. "overflow": "hidden",
  8338. "position": "relative",
  8339. "float": "left",
  8340. // DO NOT REMOVE THE QUOTE OR IT WILL BREAK COMPRESSOR
  8341. "width": 0,
  8342. "height": 0
  8343. }
  8344. });
  8345. me.measureDiv = measureDiv = Ext.Element.create({
  8346. style: {
  8347. "position": 'absolute',
  8348. "x": FARAWAY,
  8349. "y": FARAWAY,
  8350. "z-index": -FARAWAY,
  8351. "white-space": "nowrap",
  8352. "display": 'block',
  8353. "padding": 0,
  8354. "margin": 0
  8355. }
  8356. });
  8357. Ext.getBody().appendChild(parent);
  8358. parent.appendChild(measureDiv);
  8359. }
  8360. if (font) {
  8361. measureDiv.setStyle({
  8362. font: font,
  8363. lineHeight: 'normal'
  8364. });
  8365. }
  8366. measureDiv.setText('(' + text + ')');
  8367. size = measureDiv.getSize();
  8368. measureDiv.setText('()');
  8369. size.width -= measureDiv.getSize().width;
  8370. return size;
  8371. },
  8372. /**
  8373. * Measure a single-line text with specific font.
  8374. * This will split the text into characters and add up their size.
  8375. * That may *not* be the exact size of the text as it is displayed.
  8376. * @param {String} text
  8377. * @param {String} font
  8378. * @return {Object} An object with `width` and `height` properties.
  8379. * @return {Number} return.width
  8380. * @return {Number} return.height
  8381. */
  8382. measureTextSingleLine: function(text, font) {
  8383. var width = 0,
  8384. height = 0,
  8385. cache, cachedItem, chars, charactor, i, ln, size;
  8386. if (this.precise) {
  8387. return this.preciseMeasureTextSingleLine(text, font);
  8388. }
  8389. text = text.toString();
  8390. cache = this.measureCache;
  8391. chars = text.split('');
  8392. if (!cache[font]) {
  8393. cache[font] = {};
  8394. }
  8395. cache = cache[font];
  8396. if (cache[text]) {
  8397. return cache[text];
  8398. }
  8399. for (i = 0 , ln = chars.length; i < ln; i++) {
  8400. charactor = chars[i];
  8401. if (!(cachedItem = cache[charactor])) {
  8402. size = this.actualMeasureText(charactor, font);
  8403. cachedItem = cache[charactor] = size;
  8404. }
  8405. width += cachedItem.width;
  8406. height = Math.max(height, cachedItem.height);
  8407. }
  8408. return cache[text] = {
  8409. width: width,
  8410. height: height
  8411. };
  8412. },
  8413. // A more precise but slower version of the measureTextSingleLine method.
  8414. preciseMeasureTextSingleLine: function(text, font) {
  8415. var measureDiv;
  8416. text = text.toString();
  8417. measureDiv = this.measureDiv || (this.measureDiv = Ext.getBody().createChild(this.measureDivTpl).down('div'));
  8418. measureDiv.setStyle({
  8419. font: font || ''
  8420. });
  8421. return Ext.util.TextMetrics.measure(measureDiv, text);
  8422. },
  8423. /**
  8424. * Measure a text with specific font.
  8425. * This will split the text to lines and add up their size.
  8426. * That may *not* be the exact size of the text as it is displayed.
  8427. * @param {String} text
  8428. * @param {String} font
  8429. * @return {Object} An object with `width`, `height` and `sizes` properties.
  8430. * @return {Number} return.width
  8431. * @return {Number} return.height
  8432. * @return {Object} return.sizes Results of individual line measurements, in case of multiline
  8433. * text.
  8434. */
  8435. measureText: function(text, font) {
  8436. var lines = text.split('\n'),
  8437. ln = lines.length,
  8438. height = 0,
  8439. width = 0,
  8440. line, i, sizes;
  8441. if (ln === 1) {
  8442. return this.measureTextSingleLine(text, font);
  8443. }
  8444. sizes = [];
  8445. for (i = 0; i < ln; i++) {
  8446. line = this.measureTextSingleLine(lines[i], font);
  8447. sizes.push(line);
  8448. height += line.height;
  8449. width = Math.max(width, line.width);
  8450. }
  8451. return {
  8452. width: width,
  8453. height: height,
  8454. sizes: sizes
  8455. };
  8456. }
  8457. });
  8458. /**
  8459. * @class Ext.draw.sprite.Text
  8460. * @extends Ext.draw.sprite.Sprite
  8461. *
  8462. * A sprite that represents text.
  8463. *
  8464. * @example
  8465. * Ext.create({
  8466. * xtype: 'draw',
  8467. * renderTo: document.body,
  8468. * width: 600,
  8469. * height: 400,
  8470. * sprites: [{
  8471. * type: 'text',
  8472. * x: 50,
  8473. * y: 50,
  8474. * text: 'Sencha',
  8475. * fontSize: 30,
  8476. * fillStyle: '#1F6D91'
  8477. * }]
  8478. * });
  8479. */
  8480. /* eslint-disable indent */
  8481. Ext.define('Ext.draw.sprite.Text', function() {
  8482. // Absolute font sizes.
  8483. var fontSizes = {
  8484. 'xx-small': true,
  8485. 'x-small': true,
  8486. 'small': true,
  8487. 'medium': true,
  8488. 'large': true,
  8489. 'x-large': true,
  8490. 'xx-large': true
  8491. },
  8492. fontWeights = {
  8493. normal: true,
  8494. bold: true,
  8495. bolder: true,
  8496. lighter: true,
  8497. 100: true,
  8498. 200: true,
  8499. 300: true,
  8500. 400: true,
  8501. 500: true,
  8502. 600: true,
  8503. 700: true,
  8504. 800: true,
  8505. 900: true
  8506. },
  8507. textAlignments = {
  8508. start: 'start',
  8509. left: 'start',
  8510. center: 'center',
  8511. middle: 'center',
  8512. end: 'end',
  8513. right: 'end'
  8514. },
  8515. textBaselines = {
  8516. top: 'top',
  8517. hanging: 'hanging',
  8518. middle: 'middle',
  8519. center: 'middle',
  8520. alphabetic: 'alphabetic',
  8521. ideographic: 'ideographic',
  8522. bottom: 'bottom'
  8523. };
  8524. return {
  8525. extend: 'Ext.draw.sprite.Sprite',
  8526. requires: [
  8527. 'Ext.draw.TextMeasurer',
  8528. 'Ext.draw.Color'
  8529. ],
  8530. alias: 'sprite.text',
  8531. type: 'text',
  8532. lineBreakRe: /\r?\n/g,
  8533. //<debug>
  8534. statics: {
  8535. /**
  8536. * Debug rendering options:
  8537. *
  8538. * debug: {
  8539. * bbox: true // renders the bounding box of the text sprite
  8540. * }
  8541. *
  8542. */
  8543. debug: false,
  8544. fontSizes: fontSizes,
  8545. fontWeights: fontWeights,
  8546. textAlignments: textAlignments,
  8547. textBaselines: textBaselines
  8548. },
  8549. //</debug>
  8550. inheritableStatics: {
  8551. def: {
  8552. animationProcessors: {
  8553. text: 'text'
  8554. },
  8555. processors: {
  8556. /**
  8557. * @cfg {Number} [x=0]
  8558. * The position of the sprite on the x-axis.
  8559. */
  8560. x: 'number',
  8561. /**
  8562. * @cfg {Number} [y=0]
  8563. * The position of the sprite on the y-axis.
  8564. */
  8565. y: 'number',
  8566. /**
  8567. * @cfg {String} [text='']
  8568. * The text represented in the sprite.
  8569. */
  8570. text: 'string',
  8571. /**
  8572. * @cfg {String/Number} [fontSize='10px']
  8573. * The size of the font displayed.
  8574. */
  8575. fontSize: function(n) {
  8576. // Numbers as strings will be converted to numbers,
  8577. // null will be converted to 0.
  8578. if (Ext.isNumber(+n)) {
  8579. return n + 'px';
  8580. } else if (n.match(Ext.dom.Element.unitRe)) {
  8581. return n;
  8582. } else if (n in fontSizes) {
  8583. return n;
  8584. }
  8585. },
  8586. /**
  8587. * @cfg {String} [fontStyle='']
  8588. * The style of the font displayed. {normal, italic, oblique}
  8589. */
  8590. fontStyle: 'enums(,italic,oblique)',
  8591. /**
  8592. * @cfg {String} [fontVariant='']
  8593. * The variant of the font displayed. {normal, small-caps}
  8594. */
  8595. fontVariant: 'enums(,small-caps)',
  8596. /**
  8597. * @cfg {String} [fontWeight='']
  8598. * The weight of the font displayed. {normal, bold, bolder, lighter}
  8599. */
  8600. fontWeight: function(n) {
  8601. if (n in fontWeights) {
  8602. return String(n);
  8603. } else {
  8604. return '';
  8605. }
  8606. },
  8607. /**
  8608. * @cfg {String} [fontFamily='sans-serif']
  8609. * The family of the font displayed.
  8610. */
  8611. fontFamily: 'string',
  8612. /**
  8613. * @cfg {"left"/"right"/"center"/"start"/"end"} [textAlign='start']
  8614. * The alignment of the text displayed.
  8615. */
  8616. textAlign: function(n) {
  8617. return textAlignments[n] || 'center';
  8618. },
  8619. /**
  8620. * @cfg {String} [textBaseline="alphabetic"]
  8621. * The baseline of the text displayed.
  8622. * {top, hanging, middle, alphabetic, ideographic, bottom}
  8623. */
  8624. textBaseline: function(n) {
  8625. return textBaselines[n] || 'alphabetic';
  8626. },
  8627. //<debug>
  8628. debug: 'default',
  8629. //</debug>
  8630. /**
  8631. * @cfg {String} [font='10px sans-serif']
  8632. * The font displayed.
  8633. */
  8634. font: 'string'
  8635. },
  8636. aliases: {
  8637. 'font-size': 'fontSize',
  8638. 'font-family': 'fontFamily',
  8639. 'font-weight': 'fontWeight',
  8640. 'font-variant': 'fontVariant',
  8641. 'text-anchor': 'textAlign',
  8642. 'dominant-baseline': 'textBaseline'
  8643. },
  8644. defaults: {
  8645. fontStyle: '',
  8646. fontVariant: '',
  8647. fontWeight: '',
  8648. fontSize: '10px',
  8649. fontFamily: 'sans-serif',
  8650. font: '10px sans-serif',
  8651. textBaseline: 'alphabetic',
  8652. textAlign: 'start',
  8653. strokeStyle: 'rgba(0, 0, 0, 0)',
  8654. fillStyle: '#000',
  8655. x: 0,
  8656. y: 0,
  8657. text: ''
  8658. },
  8659. triggers: {
  8660. fontStyle: 'fontX,bbox',
  8661. fontVariant: 'fontX,bbox',
  8662. fontWeight: 'fontX,bbox',
  8663. fontSize: 'fontX,bbox',
  8664. fontFamily: 'fontX,bbox',
  8665. font: 'font,bbox,canvas',
  8666. textBaseline: 'bbox',
  8667. textAlign: 'bbox',
  8668. x: 'bbox',
  8669. y: 'bbox',
  8670. text: 'bbox'
  8671. },
  8672. updaters: {
  8673. fontX: 'makeFontShorthand',
  8674. font: 'parseFontShorthand'
  8675. }
  8676. }
  8677. },
  8678. config: {
  8679. /**
  8680. * @private
  8681. * If the value is boolean, it overrides the TextMeasurer's 'precise' config
  8682. * (for the given sprite only).
  8683. */
  8684. preciseMeasurement: undefined
  8685. },
  8686. constructor: function(config) {
  8687. var key;
  8688. if (config && config.font) {
  8689. config = Ext.clone(config);
  8690. for (key in config) {
  8691. if (key !== 'font' && key.indexOf('font') === 0) {
  8692. delete config[key];
  8693. }
  8694. }
  8695. }
  8696. Ext.draw.sprite.Sprite.prototype.constructor.call(this, config);
  8697. },
  8698. // Maps values to font properties they belong to.
  8699. fontValuesMap: {
  8700. // Skip 'normal' and 'inherit' values, as the first one
  8701. // is the default and the second one has no meaning in Canvas.
  8702. 'italic': 'fontStyle',
  8703. 'oblique': 'fontStyle',
  8704. 'small-caps': 'fontVariant',
  8705. 'bold': 'fontWeight',
  8706. 'bolder': 'fontWeight',
  8707. 'lighter': 'fontWeight',
  8708. '100': 'fontWeight',
  8709. '200': 'fontWeight',
  8710. '300': 'fontWeight',
  8711. '400': 'fontWeight',
  8712. '500': 'fontWeight',
  8713. '600': 'fontWeight',
  8714. '700': 'fontWeight',
  8715. '800': 'fontWeight',
  8716. '900': 'fontWeight',
  8717. // Absolute font sizes.
  8718. 'xx-small': 'fontSize',
  8719. 'x-small': 'fontSize',
  8720. 'small': 'fontSize',
  8721. 'medium': 'fontSize',
  8722. 'large': 'fontSize',
  8723. 'x-large': 'fontSize',
  8724. 'xx-large': 'fontSize'
  8725. },
  8726. // Relative font sizes like 'smaller' and 'larger'
  8727. // have no meaning, and are not included.
  8728. makeFontShorthand: function(attr) {
  8729. var parts = [];
  8730. if (attr.fontStyle) {
  8731. parts.push(attr.fontStyle);
  8732. }
  8733. if (attr.fontVariant) {
  8734. parts.push(attr.fontVariant);
  8735. }
  8736. if (attr.fontWeight) {
  8737. parts.push(attr.fontWeight);
  8738. }
  8739. if (attr.fontSize) {
  8740. parts.push(attr.fontSize);
  8741. }
  8742. if (attr.fontFamily) {
  8743. parts.push(attr.fontFamily);
  8744. }
  8745. this.setAttributes({
  8746. font: parts.join(' ')
  8747. }, true);
  8748. },
  8749. // For more info see:
  8750. // http://www.w3.org/TR/CSS21/fonts.html#font-shorthand
  8751. parseFontShorthand: function(attr) {
  8752. var value = attr.font,
  8753. ln = value.length,
  8754. changes = {},
  8755. dispatcher = this.fontValuesMap,
  8756. start = 0,
  8757. end, slashIndex, part, fontProperty;
  8758. while (start < ln && end !== -1) {
  8759. end = value.indexOf(' ', start);
  8760. if (end < 0) {
  8761. part = value.substr(start);
  8762. } else if (end > start) {
  8763. part = value.substr(start, end - start);
  8764. } else {
  8765. continue;
  8766. }
  8767. // Since Canvas fillText doesn't support multi-line text,
  8768. // it is assumed that line height is never specified, i.e.
  8769. // in entries like these the part after slash is omitted:
  8770. // 12px/14px sans-serif
  8771. // x-large/110% "New Century Schoolbook", serif
  8772. slashIndex = part.indexOf('/');
  8773. if (slashIndex > 0) {
  8774. part = part.substr(0, slashIndex);
  8775. } else if (slashIndex === 0) {
  8776. continue;
  8777. }
  8778. // All optional font properties (fontStyle, fontVariant or fontWeight) can be 'normal'.
  8779. // They can go in any order. Which ones are 'normal' is determined by elimination.
  8780. // E.g. if only fontVariant is specified, then 'normal' applies to fontStyle
  8781. // and fontWeight.
  8782. // If none are explicitly mentioned, then all are 'normal'.
  8783. if (part !== 'normal' && part !== 'inherit') {
  8784. fontProperty = dispatcher[part];
  8785. if (fontProperty) {
  8786. changes[fontProperty] = part;
  8787. } else if (part.match(Ext.dom.Element.unitRe)) {
  8788. changes.fontSize = part;
  8789. } else {
  8790. // Assuming that font family always goes last in the font shorthand.
  8791. changes.fontFamily = value.substr(start);
  8792. break;
  8793. }
  8794. }
  8795. start = end + 1;
  8796. }
  8797. if (!changes.fontStyle) {
  8798. changes.fontStyle = '';
  8799. }
  8800. // same as 'normal'
  8801. if (!changes.fontVariant) {
  8802. changes.fontVariant = '';
  8803. }
  8804. // same as 'normal'
  8805. if (!changes.fontWeight) {
  8806. changes.fontWeight = '';
  8807. }
  8808. // same as 'normal'
  8809. this.setAttributes(changes, true);
  8810. },
  8811. fontProperties: {
  8812. fontStyle: true,
  8813. fontVariant: true,
  8814. fontWeight: true,
  8815. fontSize: true,
  8816. fontFamily: true
  8817. },
  8818. setAttributes: function(changes, bypassNormalization, avoidCopy) {
  8819. var key, obj;
  8820. // Discard individual font properties if 'font' shorthand was also provided.
  8821. // Example: a user provides a config for chart series labels, using the font
  8822. // shorthand, which is parsed into individual font properties and corresponding
  8823. // sprite attributes are set. Then a theme is applied to the chart, and
  8824. // individual font properties from the theme make up the new font shorthand
  8825. // that overrides the previous one. In other words, no matter what font
  8826. // the user has specified, theme font will be used.
  8827. // This workaround relies on the fact that the theme merges its own config with
  8828. // the user config (where user config values take over the same theme config
  8829. // values). So both user font shorthand and individual font properties from
  8830. // the theme are present in the resulting config (since there are no collisions),
  8831. // which ends up here as the 'changes' parameter.
  8832. // If the user wants their font config to merged with the the theme's font config,
  8833. // instead of taking over it, individual font properties should be used
  8834. // by the user as well.
  8835. if (changes && changes.font) {
  8836. obj = {};
  8837. for (key in changes) {
  8838. if (!(key in this.fontProperties)) {
  8839. obj[key] = changes[key];
  8840. }
  8841. }
  8842. changes = obj;
  8843. }
  8844. this.callParent([
  8845. changes,
  8846. bypassNormalization,
  8847. avoidCopy
  8848. ]);
  8849. },
  8850. // Overriding the getBBox method of the abstract sprite here to always
  8851. // recalculate the bounding box of the text in flipped RTL mode
  8852. // because in that case the position of the sprite depends not just on
  8853. // the value of its 'x' attribute, but also on the width of the surface
  8854. // the sprite belongs to.
  8855. getBBox: function(isWithoutTransform) {
  8856. var me = this,
  8857. plain = me.attr.bbox.plain,
  8858. surface = me.getSurface();
  8859. //<debug>
  8860. // The sprite's bounding box won't account for RTL if it doesn't
  8861. // belong to a surface.
  8862. // if (!surface) {
  8863. // Ext.raise("The sprite does not belong to a surface.");
  8864. // }
  8865. //</debug>
  8866. if (plain.dirty) {
  8867. me.updatePlainBBox(plain);
  8868. plain.dirty = false;
  8869. }
  8870. if (surface && surface.getInherited().rtl && surface.getFlipRtlText()) {
  8871. // Since sprite's attributes haven't actually changed at this point,
  8872. // and we just want to update the position of its bbox
  8873. // based on surface's width, there's no reason to perform
  8874. // expensive text measurement operation here,
  8875. // so we can use the result of the last measurement instead.
  8876. me.updatePlainBBox(plain, true);
  8877. }
  8878. return me.callParent([
  8879. isWithoutTransform
  8880. ]);
  8881. },
  8882. rtlAlignments: {
  8883. start: 'end',
  8884. center: 'center',
  8885. end: 'start'
  8886. },
  8887. updatePlainBBox: function(plain, useOldSize) {
  8888. var me = this,
  8889. attr = me.attr,
  8890. x = attr.x,
  8891. y = attr.y,
  8892. dx = [],
  8893. font = attr.font,
  8894. text = attr.text,
  8895. baseline = attr.textBaseline,
  8896. alignment = attr.textAlign,
  8897. precise = me.getPreciseMeasurement(),
  8898. size, textMeasurerPrecision;
  8899. if (useOldSize && me.oldSize) {
  8900. size = me.oldSize;
  8901. } else {
  8902. textMeasurerPrecision = Ext.draw.TextMeasurer.precise;
  8903. if (Ext.isBoolean(precise)) {
  8904. Ext.draw.TextMeasurer.precise = precise;
  8905. }
  8906. size = me.oldSize = Ext.draw.TextMeasurer.measureText(text, font);
  8907. Ext.draw.TextMeasurer.precise = textMeasurerPrecision;
  8908. }
  8909. // eslint-disable-next-line vars-on-top, one-var
  8910. var surface = me.getSurface(),
  8911. isRtl = (surface && surface.getInherited().rtl) || false,
  8912. flipRtlText = isRtl && surface.getFlipRtlText(),
  8913. sizes = size.sizes,
  8914. blockHeight = size.height,
  8915. blockWidth = size.width,
  8916. ln = sizes ? sizes.length : 0,
  8917. lineWidth, rect,
  8918. i = 0;
  8919. // To get consistent results in all browsers we don't apply textAlign
  8920. // and textBaseline attributes of the sprite to context, so text is always
  8921. // left aligned and has an alphabetic baseline.
  8922. //
  8923. // Instead we have to calculate the horizontal offset of each line
  8924. // based on sprite's textAlign, and the vertical offset of the bounding box
  8925. // based on sprite's textBaseline.
  8926. //
  8927. // These offsets are then used by the sprite's 'render' method
  8928. // to position text properly.
  8929. switch (baseline) {
  8930. case 'hanging':
  8931. case 'top':
  8932. break;
  8933. case 'ideographic':
  8934. case 'bottom':
  8935. y -= blockHeight;
  8936. break;
  8937. case 'alphabetic':
  8938. y -= blockHeight * 0.8;
  8939. break;
  8940. case 'middle':
  8941. y -= blockHeight * 0.5;
  8942. break;
  8943. }
  8944. if (flipRtlText) {
  8945. rect = surface.getRect();
  8946. x = rect[2] - rect[0] - x;
  8947. alignment = me.rtlAlignments[alignment];
  8948. }
  8949. switch (alignment) {
  8950. case 'start':
  8951. if (isRtl) {
  8952. for (; i < ln; i++) {
  8953. lineWidth = sizes[i].width;
  8954. dx.push(-(blockWidth - lineWidth));
  8955. }
  8956. };
  8957. break;
  8958. case 'end':
  8959. x -= blockWidth;
  8960. if (isRtl) {
  8961. break;
  8962. };
  8963. for (; i < ln; i++) {
  8964. lineWidth = sizes[i].width;
  8965. dx.push(blockWidth - lineWidth);
  8966. };
  8967. break;
  8968. case 'center':
  8969. x -= blockWidth * 0.5;
  8970. for (; i < ln; i++) {
  8971. lineWidth = sizes[i].width;
  8972. dx.push((isRtl ? -1 : 1) * (blockWidth - lineWidth) * 0.5);
  8973. };
  8974. break;
  8975. }
  8976. attr.textAlignOffsets = dx;
  8977. plain.x = x;
  8978. plain.y = y;
  8979. plain.width = blockWidth;
  8980. plain.height = blockHeight;
  8981. },
  8982. setText: function(text) {
  8983. this.setAttributes({
  8984. text: text
  8985. }, true);
  8986. },
  8987. render: function(surface, ctx, rect) {
  8988. var me = this,
  8989. attr = me.attr,
  8990. mat = Ext.draw.Matrix.fly(attr.matrix.elements.slice(0)),
  8991. bbox = me.getBBox(true),
  8992. dx = attr.textAlignOffsets,
  8993. none = Ext.util.Color.RGBA_NONE,
  8994. x, y, i, lines, lineHeight;
  8995. if (attr.text.length === 0) {
  8996. return;
  8997. }
  8998. lines = attr.text.split(me.lineBreakRe);
  8999. lineHeight = bbox.height / lines.length;
  9000. // Simulate textBaseline and textAlign.
  9001. x = attr.bbox.plain.x;
  9002. // lineHeight * 0.78 is the approximate distance between the top
  9003. // and the alphabetic baselines
  9004. y = attr.bbox.plain.y + lineHeight * 0.78;
  9005. mat.toContext(ctx);
  9006. if (surface.getInherited().rtl) {
  9007. // Canvas element in RTL mode automatically flips text alignment.
  9008. // Here we compensate for that change.
  9009. // So text is still positioned and aligned as in the LTR mode,
  9010. // but the direction of the text is RTL.
  9011. x += attr.bbox.plain.width;
  9012. }
  9013. for (i = 0; i < lines.length; i++) {
  9014. if (ctx.fillStyle !== none) {
  9015. ctx.fillText(lines[i], x + (dx[i] || 0), y + lineHeight * i);
  9016. }
  9017. if (ctx.strokeStyle !== none) {
  9018. ctx.strokeText(lines[i], x + (dx[i] || 0), y + lineHeight * i);
  9019. }
  9020. }
  9021. //<debug>
  9022. // eslint-disable-next-line vars-on-top
  9023. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  9024. if (debug) {
  9025. // This assumes no part of the sprite is rendered after this call.
  9026. // If it is, we need to re-apply transformations.
  9027. // But the bounding box is already transformed, so we remove the transformation.
  9028. this.attr.inverseMatrix.toContext(ctx);
  9029. if (debug.bbox) {
  9030. me.renderBBox(surface, ctx);
  9031. }
  9032. }
  9033. }
  9034. };
  9035. });
  9036. //</debug>
  9037. /**
  9038. * A veritical line sprite. The x and y configs set the center of the line with the size
  9039. * value determining the height of the line (the line will be twice the height of 'size'
  9040. * since 'size' is added to above and below 'y' to set the line endpoints).
  9041. *
  9042. * @example
  9043. * Ext.create({
  9044. * xtype: 'draw',
  9045. * renderTo: document.body,
  9046. * width: 600,
  9047. * height: 400,
  9048. * sprites: [{
  9049. * type: 'tick',
  9050. * x: 20,
  9051. * y: 40,
  9052. * size: 10,
  9053. * strokeStyle: '#388FAD',
  9054. * lineWidth: 2
  9055. * }]
  9056. * });
  9057. */
  9058. Ext.define('Ext.draw.sprite.Tick', {
  9059. extend: 'Ext.draw.sprite.Line',
  9060. alias: 'sprite.tick',
  9061. inheritableStatics: {
  9062. def: {
  9063. processors: {
  9064. /**
  9065. * @cfg {Object} x The position of the center of the sprite on the x-axis.
  9066. */
  9067. x: 'number',
  9068. /**
  9069. * @cfg {Object} y The position of the center of the sprite on the y-axis.
  9070. */
  9071. y: 'number',
  9072. /**
  9073. * @cfg {Number} [size=4] The size of the sprite.
  9074. * Meant to be comparable to the size of a circle sprite with the same radius.
  9075. */
  9076. size: 'number'
  9077. },
  9078. defaults: {
  9079. x: 0,
  9080. y: 0,
  9081. size: 4
  9082. },
  9083. triggers: {
  9084. x: 'tick',
  9085. y: 'tick',
  9086. size: 'tick'
  9087. },
  9088. updaters: {
  9089. tick: function(attr) {
  9090. var size = attr.size * 1.5,
  9091. halfLineWidth = attr.lineWidth / 2,
  9092. x = attr.x,
  9093. y = attr.y;
  9094. this.setAttributes({
  9095. fromX: x - halfLineWidth,
  9096. fromY: y - size,
  9097. toX: x - halfLineWidth,
  9098. toY: y + size
  9099. });
  9100. }
  9101. }
  9102. }
  9103. }
  9104. });
  9105. /**
  9106. * A sprite that represents a triangle.
  9107. *
  9108. * @example
  9109. * Ext.create({
  9110. * xtype: 'draw',
  9111. * renderTo: document.body,
  9112. * width: 600,
  9113. * height: 400,
  9114. * sprites: [{
  9115. * type: 'triangle',
  9116. * size: 50,
  9117. * translationX: 100,
  9118. * translationY: 100,
  9119. * fillStyle: '#1F6D91'
  9120. * }]
  9121. * });
  9122. *
  9123. */
  9124. Ext.define('Ext.draw.sprite.Triangle', {
  9125. extend: 'Ext.draw.sprite.Path',
  9126. alias: 'sprite.triangle',
  9127. inheritableStatics: {
  9128. def: {
  9129. processors: {
  9130. x: 'number',
  9131. y: 'number',
  9132. /**
  9133. * @cfg {Number} [size=4] The size of the sprite.
  9134. * Meant to be comparable to the size of a circle sprite with the same radius.
  9135. */
  9136. size: 'number'
  9137. },
  9138. defaults: {
  9139. x: 0,
  9140. y: 0,
  9141. size: 4
  9142. },
  9143. triggers: {
  9144. x: 'path',
  9145. y: 'path',
  9146. size: 'path'
  9147. }
  9148. }
  9149. },
  9150. updatePath: function(path, attr) {
  9151. var s = attr.size * 2.2,
  9152. x = attr.x,
  9153. y = attr.y;
  9154. path.fromSvgString('M'.concat(x, ',', y, 'm0-', s * 0.48, 'l', s * 0.5, ',', s * 0.87, '-', s, ',0z'));
  9155. }
  9156. });
  9157. /**
  9158. * Linear gradient.
  9159. *
  9160. * @example
  9161. * Ext.create({
  9162. * xtype: 'draw',
  9163. * renderTo: document.body,
  9164. * width: 600,
  9165. * height: 400,
  9166. * sprites: [{
  9167. * type: 'circle',
  9168. * cx: 100,
  9169. * cy: 100,
  9170. * r: 100,
  9171. * fillStyle: {
  9172. * type: 'linear',
  9173. * degrees: 180,
  9174. * stops: [{
  9175. * offset: 0,
  9176. * color: '#1F6D91'
  9177. * }, {
  9178. * offset: 1,
  9179. * color: '#90BCC9'
  9180. * }]
  9181. * }
  9182. * }]
  9183. * });
  9184. */
  9185. Ext.define('Ext.draw.gradient.Linear', {
  9186. extend: 'Ext.draw.gradient.Gradient',
  9187. requires: [
  9188. 'Ext.draw.Color'
  9189. ],
  9190. type: 'linear',
  9191. config: {
  9192. /**
  9193. * @cfg {Number} degrees
  9194. * The angle of rotation of the gradient in degrees.
  9195. */
  9196. degrees: 0,
  9197. /**
  9198. * @cfg {Number} radians
  9199. * The angle of rotation of the gradient in radians.
  9200. */
  9201. radians: 0
  9202. },
  9203. applyRadians: function(radians, oldRadians) {
  9204. if (Ext.isNumber(radians)) {
  9205. return radians;
  9206. }
  9207. return oldRadians;
  9208. },
  9209. applyDegrees: function(degrees, oldDegrees) {
  9210. if (Ext.isNumber(degrees)) {
  9211. return degrees;
  9212. }
  9213. return oldDegrees;
  9214. },
  9215. updateRadians: function(radians) {
  9216. this.setDegrees(Ext.draw.Draw.degrees(radians));
  9217. },
  9218. updateDegrees: function(degrees) {
  9219. this.setRadians(Ext.draw.Draw.rad(degrees));
  9220. },
  9221. /**
  9222. * @method generateGradient
  9223. * @inheritdoc
  9224. */
  9225. generateGradient: function(ctx, bbox) {
  9226. var angle = this.getRadians(),
  9227. cos = Math.cos(angle),
  9228. sin = Math.sin(angle),
  9229. w = bbox.width,
  9230. h = bbox.height,
  9231. cx = bbox.x + w * 0.5,
  9232. cy = bbox.y + h * 0.5,
  9233. stops = this.getStops(),
  9234. ln = stops.length,
  9235. gradient, l, i;
  9236. if (Ext.isNumber(cx) && Ext.isNumber(cy) && h > 0 && w > 0) {
  9237. l = (Math.sqrt(h * h + w * w) * Math.abs(Math.cos(angle - Math.atan(h / w)))) / 2;
  9238. gradient = ctx.createLinearGradient(cx + cos * l, cy + sin * l, cx - cos * l, cy - sin * l);
  9239. for (i = 0; i < ln; i++) {
  9240. gradient.addColorStop(stops[i].offset, stops[i].color);
  9241. }
  9242. return gradient;
  9243. }
  9244. return Ext.util.Color.NONE;
  9245. }
  9246. });
  9247. /**
  9248. * Radial gradient.
  9249. *
  9250. * @example
  9251. * Ext.create({
  9252. * xtype: 'draw',
  9253. * renderTo: document.body,
  9254. * width: 600,
  9255. * height: 400,
  9256. * sprites: [{
  9257. * type: 'circle',
  9258. * cx: 100,
  9259. * cy: 100,
  9260. * r: 100,
  9261. * fillStyle: {
  9262. * type: 'radial',
  9263. * start: {
  9264. * x: 0,
  9265. * y: 0,
  9266. * r: 0
  9267. * },
  9268. * end: {
  9269. * x: 0,
  9270. * y: 0,
  9271. * r: 1
  9272. * },
  9273. * stops: [{
  9274. * offset: 0,
  9275. * color: '#90BCC9'
  9276. * }, {
  9277. * offset: 1,
  9278. * color: '#1F6D91'
  9279. * }]
  9280. * }
  9281. * }]
  9282. * });
  9283. */
  9284. Ext.define('Ext.draw.gradient.Radial', {
  9285. extend: 'Ext.draw.gradient.Gradient',
  9286. type: 'radial',
  9287. config: {
  9288. /**
  9289. * @cfg {Object} start
  9290. * The starting circle of the gradient.
  9291. */
  9292. start: {
  9293. x: 0,
  9294. y: 0,
  9295. r: 0
  9296. },
  9297. /**
  9298. * @cfg {Object} end
  9299. * The ending circle of the gradient.
  9300. */
  9301. end: {
  9302. x: 0,
  9303. y: 0,
  9304. r: 1
  9305. }
  9306. },
  9307. applyStart: function(newStart, oldStart) {
  9308. var circle;
  9309. if (!oldStart) {
  9310. return newStart;
  9311. }
  9312. circle = {
  9313. x: oldStart.x,
  9314. y: oldStart.y,
  9315. r: oldStart.r
  9316. };
  9317. if ('x' in newStart) {
  9318. circle.x = newStart.x;
  9319. } else if ('centerX' in newStart) {
  9320. circle.x = newStart.centerX;
  9321. }
  9322. if ('y' in newStart) {
  9323. circle.y = newStart.y;
  9324. } else if ('centerY' in newStart) {
  9325. circle.y = newStart.centerY;
  9326. }
  9327. if ('r' in newStart) {
  9328. circle.r = newStart.r;
  9329. } else if ('radius' in newStart) {
  9330. circle.r = newStart.radius;
  9331. }
  9332. return circle;
  9333. },
  9334. applyEnd: function(newEnd, oldEnd) {
  9335. var circle;
  9336. if (!oldEnd) {
  9337. return newEnd;
  9338. }
  9339. circle = {
  9340. x: oldEnd.x,
  9341. y: oldEnd.y,
  9342. r: oldEnd.r
  9343. };
  9344. if ('x' in newEnd) {
  9345. circle.x = newEnd.x;
  9346. } else if ('centerX' in newEnd) {
  9347. circle.x = newEnd.centerX;
  9348. }
  9349. if ('y' in newEnd) {
  9350. circle.y = newEnd.y;
  9351. } else if ('centerY' in newEnd) {
  9352. circle.y = newEnd.centerY;
  9353. }
  9354. if ('r' in newEnd) {
  9355. circle.r = newEnd.r;
  9356. } else if ('radius' in newEnd) {
  9357. circle.r = newEnd.radius;
  9358. }
  9359. return circle;
  9360. },
  9361. /**
  9362. * @method generateGradient
  9363. * @inheritdoc
  9364. */
  9365. generateGradient: function(ctx, bbox) {
  9366. var start = this.getStart(),
  9367. end = this.getEnd(),
  9368. w = bbox.width * 0.5,
  9369. h = bbox.height * 0.5,
  9370. x = bbox.x + w,
  9371. y = bbox.y + h,
  9372. 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)),
  9373. stops = this.getStops(),
  9374. ln = stops.length,
  9375. i;
  9376. for (i = 0; i < ln; i++) {
  9377. gradient.addColorStop(stops[i].offset, stops[i].color);
  9378. }
  9379. return gradient;
  9380. }
  9381. });
  9382. /**
  9383. * A surface is an interface to render {@link Ext.draw.sprite.Sprite sprites} inside a
  9384. * {@link Ext.draw.Container draw container}. The surface API has methods to render
  9385. * sprites, get sprite bounding boxes (dimensions), add sprites to the underlying DOM,
  9386. * and more.
  9387. *
  9388. * A surface is automatically created when a draw container is created. By default,
  9389. * this will be a surface with an `id` of "main" and will manage all sprites in the draw
  9390. * container (unless the sprite configs specify a unique surface "id").
  9391. *
  9392. * @example
  9393. * Ext.create({
  9394. * xtype: 'draw',
  9395. * renderTo: document.body,
  9396. * width: 400,
  9397. * height: 400,
  9398. * sprites: [{
  9399. * type: 'rect',
  9400. * surface: 'anim', // a surface with id "anim" will be created automatically
  9401. * x: 50,
  9402. * y: 50,
  9403. * width: 100,
  9404. * height: 100,
  9405. * fillStyle: '#1F6D91'
  9406. * }]
  9407. * });
  9408. *
  9409. * The ability to have multiple surfaces is useful for performance (and battery life)
  9410. * reasons. Because changes to sprite attributes cause the whole surface (and all
  9411. * sprites in it) to re-render, it makes sense to group sprites by surface, so changes
  9412. * to one group of sprites will only trigger the surface they are in to re-render.
  9413. *
  9414. * One of the more useful methods is the {@link #add} method used to add sprites to the
  9415. * surface:
  9416. *
  9417. * @example
  9418. * var drawCt = Ext.create({
  9419. * xtype: 'draw',
  9420. * renderTo: document.body,
  9421. * width: 400,
  9422. * height: 400
  9423. * });
  9424. *
  9425. * // If the surface name is not specified then 'main' will be used
  9426. * var surface = drawCt.getSurface();
  9427. *
  9428. * surface.add({
  9429. * type: 'rect',
  9430. * x: 50,
  9431. * y: 50,
  9432. * width: 100,
  9433. * height: 100,
  9434. * fillStyle: '#1F6D91'
  9435. * });
  9436. *
  9437. * surface.renderFrame();
  9438. *
  9439. * **Note:** Changes to the sprites on a surface will be not be reflected in the DOM
  9440. * until you call the surface's {@link Ext.draw.Surface#method-renderFrame renderFrame}
  9441. * method. This must be done after adding, removing, or modifying sprites in order to
  9442. * see the changes on-screen.
  9443. */
  9444. Ext.define('Ext.draw.Surface', {
  9445. extend: 'Ext.draw.SurfaceBase',
  9446. xtype: 'surface',
  9447. requires: [
  9448. 'Ext.draw.sprite.*',
  9449. 'Ext.draw.gradient.*',
  9450. 'Ext.draw.sprite.AttributeDefinition',
  9451. 'Ext.draw.Matrix',
  9452. 'Ext.draw.Draw'
  9453. ],
  9454. uses: [
  9455. 'Ext.draw.engine.Canvas'
  9456. ],
  9457. /**
  9458. * The reported device pixel density.
  9459. * devicePixelRatio is only supported from IE11,
  9460. * so we use deviceXDPI and logicalXDPI that are supported from IE6.
  9461. */
  9462. devicePixelRatio: window.devicePixelRatio || window.screen.deviceXDPI / window.screen.logicalXDPI,
  9463. deprecated: {
  9464. '5.1.0': {
  9465. statics: {
  9466. methods: {
  9467. /**
  9468. * @deprecated 5.1.0
  9469. * Stably sort the list of sprites by their zIndex.
  9470. * Deprecated, use the {@link Ext.Array#sort} method instead.
  9471. * @param {Array} list
  9472. * @return {Array} Sorted array.
  9473. */
  9474. stableSort: function(list) {
  9475. return Ext.Array.sort(list, function(a, b) {
  9476. return a.attr.zIndex - b.attr.zIndex;
  9477. });
  9478. }
  9479. }
  9480. }
  9481. }
  9482. },
  9483. cls: Ext.baseCSSPrefix + 'surface',
  9484. config: {
  9485. /**
  9486. * @cfg {Array}
  9487. * The [x, y, width, height] rect of the surface related to its container.
  9488. */
  9489. rect: null,
  9490. /**
  9491. * @cfg {Object}
  9492. * Background sprite config of the surface.
  9493. */
  9494. background: null,
  9495. /**
  9496. * @cfg {Array}
  9497. * Array of sprite instances.
  9498. */
  9499. items: [],
  9500. /**
  9501. * @cfg {Boolean}
  9502. * Indicates whether the surface needs to redraw.
  9503. */
  9504. dirty: false,
  9505. /**
  9506. * @cfg {Boolean} flipRtlText
  9507. * If the surface is in the RTL mode, text will render with the RTL direction,
  9508. * but the alignment and position of the text won't change by default.
  9509. * Setting this config to 'true' will get text alignment and its position
  9510. * within a surface mirrored.
  9511. */
  9512. flipRtlText: false
  9513. },
  9514. isSurface: true,
  9515. /**
  9516. * @private
  9517. * This flag is used to indicate that `predecessors` surfaces that should render
  9518. * before this surface renders are dirty, and to call `renderFrame`
  9519. * when all `predecessors` have their `renderFrame` called (i.e. not dirty anymore).
  9520. * This flag indicates that current surface has surfaces that are yet to render
  9521. * before current surface can render. When all the `predecessors` surfaces
  9522. * have rendered, i.e. when `dirtyPredecessorCount` reaches zero,
  9523. */
  9524. isPendingRenderFrame: false,
  9525. dirtyPredecessorCount: 0,
  9526. emptyRect: [
  9527. 0,
  9528. 0,
  9529. 0,
  9530. 0
  9531. ],
  9532. constructor: function(config) {
  9533. var me = this;
  9534. me.predecessors = [];
  9535. me.successors = [];
  9536. me.map = {};
  9537. me.callParent([
  9538. config
  9539. ]);
  9540. me.matrix = new Ext.draw.Matrix();
  9541. me.inverseMatrix = me.matrix.inverse();
  9542. },
  9543. /**
  9544. * Round the number to align to the pixels on device.
  9545. * @param {Number} num The number to align.
  9546. * @return {Number} The resultant alignment.
  9547. */
  9548. roundPixel: function(num) {
  9549. return Math.round(this.devicePixelRatio * num) / this.devicePixelRatio;
  9550. },
  9551. /**
  9552. * Mark the surface to render after another surface is updated.
  9553. * @param {Ext.draw.Surface} surface The surface to wait for.
  9554. */
  9555. waitFor: function(surface) {
  9556. var me = this,
  9557. predecessors = me.predecessors;
  9558. if (!Ext.Array.contains(predecessors, surface)) {
  9559. predecessors.push(surface);
  9560. surface.successors.push(me);
  9561. if (surface.getDirty()) {
  9562. me.dirtyPredecessorCount++;
  9563. }
  9564. }
  9565. },
  9566. updateDirty: function(dirty) {
  9567. var successors = this.successors,
  9568. ln = successors.length,
  9569. i = 0,
  9570. successor;
  9571. for (; i < ln; i++) {
  9572. successor = successors[i];
  9573. if (dirty) {
  9574. successor.dirtyPredecessorCount++;
  9575. successor.setDirty(true);
  9576. } else {
  9577. successor.dirtyPredecessorCount--;
  9578. // Don't need to call `setDirty(false)` on a successor here,
  9579. // as this will be done by `renderFrame`.
  9580. if (successor.dirtyPredecessorCount === 0 && successor.isPendingRenderFrame) {
  9581. successor.renderFrame();
  9582. }
  9583. }
  9584. }
  9585. },
  9586. applyBackground: function(background, oldBackground) {
  9587. this.setDirty(true);
  9588. if (Ext.isString(background)) {
  9589. background = {
  9590. fillStyle: background
  9591. };
  9592. }
  9593. return Ext.factory(background, Ext.draw.sprite.Rect, oldBackground);
  9594. },
  9595. applyRect: function(rect, oldRect) {
  9596. if (oldRect && rect[0] === oldRect[0] && rect[1] === oldRect[1] && rect[2] === oldRect[2] && rect[3] === oldRect[3]) {
  9597. return oldRect;
  9598. }
  9599. if (Ext.isArray(rect)) {
  9600. return [
  9601. rect[0],
  9602. rect[1],
  9603. rect[2],
  9604. rect[3]
  9605. ];
  9606. } else if (Ext.isObject(rect)) {
  9607. return [
  9608. rect.x || rect.left,
  9609. rect.y || rect.top,
  9610. rect.width || (rect.right - rect.left),
  9611. rect.height || (rect.bottom - rect.top)
  9612. ];
  9613. }
  9614. },
  9615. updateRect: function(rect) {
  9616. var me = this,
  9617. l = rect[0],
  9618. t = rect[1],
  9619. r = l + rect[2],
  9620. b = t + rect[3],
  9621. background = me.getBackground(),
  9622. element = me.element;
  9623. element.setLocalXY(Math.floor(l), Math.floor(t));
  9624. element.setSize(Math.ceil(r - Math.floor(l)), Math.ceil(b - Math.floor(t)));
  9625. if (background) {
  9626. background.setAttributes({
  9627. x: 0,
  9628. y: 0,
  9629. width: Math.ceil(r - Math.floor(l)),
  9630. height: Math.ceil(b - Math.floor(t))
  9631. });
  9632. }
  9633. me.setDirty(true);
  9634. },
  9635. /**
  9636. * Reset the matrix of the surface.
  9637. */
  9638. resetTransform: function() {
  9639. this.matrix.set(1, 0, 0, 1, 0, 0);
  9640. this.inverseMatrix.set(1, 0, 0, 1, 0, 0);
  9641. this.setDirty(true);
  9642. },
  9643. /**
  9644. * Get the sprite by id or index.
  9645. * It will first try to find a sprite with the given id, otherwise will try to use the id
  9646. * as an index.
  9647. * @param {String|Number} id
  9648. * @return {Ext.draw.sprite.Sprite}
  9649. */
  9650. get: function(id) {
  9651. return this.map[id] || this.getItems()[id];
  9652. },
  9653. /**
  9654. * @method
  9655. * Add a Sprite to the surface.
  9656. * You can put any number of objects as the parameter.
  9657. * See {@link Ext.draw.sprite.Sprite} for the configuration object to be passed
  9658. * into this method.
  9659. *
  9660. * For example:
  9661. *
  9662. * drawContainer.getSurface().add({
  9663. * type: 'circle',
  9664. * fill: '#ffc',
  9665. * radius: 100,
  9666. * x: 100,
  9667. * y: 100
  9668. * });
  9669. * drawContainer.renderFrame();
  9670. *
  9671. * @param {Object/Object[]} sprite
  9672. * @return {Ext.draw.sprite.Sprite/Ext.draw.sprite.Sprite[]}
  9673. *
  9674. */
  9675. add: function() {
  9676. var me = this,
  9677. args = Array.prototype.slice.call(arguments),
  9678. argIsArray = Ext.isArray(args[0]),
  9679. map = me.map,
  9680. results = [],
  9681. items, item, sprite, oldSurface, i, ln;
  9682. items = Ext.Array.clean(argIsArray ? args[0] : args);
  9683. if (!items.length) {
  9684. return results;
  9685. }
  9686. for (i = 0 , ln = items.length; i < ln; i++) {
  9687. item = items[i];
  9688. if (!item || item.destroyed) {
  9689. continue;
  9690. }
  9691. sprite = null;
  9692. if (item.isSprite && !map[item.getId()]) {
  9693. sprite = item;
  9694. } else if (!map[item.id]) {
  9695. sprite = this.createItem(item);
  9696. }
  9697. if (sprite) {
  9698. map[sprite.getId()] = sprite;
  9699. results.push(sprite);
  9700. oldSurface = sprite.getSurface();
  9701. if (oldSurface && oldSurface.isSurface) {
  9702. oldSurface.remove(sprite);
  9703. }
  9704. sprite.setParent(me);
  9705. sprite.setSurface(me);
  9706. me.onAdd(sprite);
  9707. }
  9708. }
  9709. items = me.getItems();
  9710. if (items) {
  9711. items.push.apply(items, results);
  9712. }
  9713. me.dirtyZIndex = true;
  9714. me.setDirty(true);
  9715. if (!argIsArray && results.length === 1) {
  9716. return results[0];
  9717. } else {
  9718. return results;
  9719. }
  9720. },
  9721. /**
  9722. * @method
  9723. * @protected
  9724. * Invoked when a sprite is added to the surface.
  9725. * @param {Ext.draw.sprite.Sprite} sprite The sprite to be added.
  9726. */
  9727. onAdd: Ext.emptyFn,
  9728. /**
  9729. * Remove a given sprite from the surface,
  9730. * optionally destroying the sprite in the process.
  9731. * You can also call the sprite's own `remove` method.
  9732. *
  9733. * For example:
  9734. *
  9735. * drawContainer.surface.remove(sprite);
  9736. * // or...
  9737. * sprite.remove();
  9738. *
  9739. * @param {Ext.draw.sprite.Sprite/String} sprite A sprite instance or its ID.
  9740. * @param {Boolean} [isDestroy=false] If `true`, the sprite will be destroyed.
  9741. * @return {Ext.draw.sprite.Sprite} Returns the removed/destroyed sprite or `null` otherwise.
  9742. */
  9743. remove: function(sprite, isDestroy) {
  9744. var me = this,
  9745. destroying = me.clearing,
  9746. id, isOwnSprite;
  9747. if (sprite) {
  9748. if (sprite.charAt) {
  9749. // is String
  9750. sprite = me.map[sprite];
  9751. }
  9752. if (!sprite || !sprite.isSprite) {
  9753. return null;
  9754. }
  9755. id = sprite.id;
  9756. isOwnSprite = me.map[id];
  9757. delete me.map[id];
  9758. if (sprite.destroyed || sprite.destroying) {
  9759. if (isOwnSprite && !destroying) {
  9760. // Somehow this sprite was destroyed,
  9761. // but still belongs to the surface.
  9762. Ext.Array.remove(me.getItems(), sprite);
  9763. }
  9764. return sprite;
  9765. }
  9766. if (!isOwnSprite) {
  9767. if (isDestroy) {
  9768. sprite.destroy();
  9769. }
  9770. return sprite;
  9771. }
  9772. sprite.setParent(null);
  9773. sprite.setSurface(null);
  9774. if (isDestroy) {
  9775. sprite.destroy();
  9776. }
  9777. if (!destroying) {
  9778. Ext.Array.remove(me.getItems(), sprite);
  9779. me.dirtyZIndex = true;
  9780. me.setDirty(true);
  9781. }
  9782. }
  9783. return sprite || null;
  9784. },
  9785. /**
  9786. * Remove all sprites from the surface, optionally destroying the sprites in the process.
  9787. *
  9788. * For example:
  9789. *
  9790. * drawContainer.getSurface('main').removeAll();
  9791. *
  9792. * @param {Boolean} [isDestroy=false]
  9793. */
  9794. removeAll: function(isDestroy) {
  9795. var me = this,
  9796. items = me.getItems(),
  9797. item, i;
  9798. me.clearing = !!isDestroy;
  9799. for (i = items.length - 1; i >= 0; i--) {
  9800. item = items[i];
  9801. if (isDestroy) {
  9802. // Some sprites may destroy other sprites, however if we're destroying then
  9803. // we don't remove anything from the items array since we'll just clear it later.
  9804. // If a sprite is destroyed, the remove method will just drop out with no harm done.
  9805. item.destroy();
  9806. } else {
  9807. item.setParent(null);
  9808. item.setSurface(null);
  9809. }
  9810. }
  9811. me.clearing = false;
  9812. items.length = 0;
  9813. me.map = {};
  9814. me.dirtyZIndex = true;
  9815. if (!me.destroying) {
  9816. me.setDirty(true);
  9817. }
  9818. },
  9819. /**
  9820. * @private
  9821. */
  9822. applyItems: function(items) {
  9823. if (this.getItems()) {
  9824. this.removeAll(true);
  9825. }
  9826. return Ext.Array.from(this.add(items));
  9827. },
  9828. /**
  9829. * @private
  9830. * Creates an item and appends it to the surface. Called
  9831. * as an internal method when calling `add`.
  9832. */
  9833. createItem: function(config) {
  9834. return Ext.create(config.xclass || 'sprite.' + config.type, config);
  9835. },
  9836. /**
  9837. * Return the minimal bounding box that contains all the sprites bounding boxes
  9838. * in the given list of sprites.
  9839. * @param {Ext.draw.sprite.Sprite[]|Ext.draw.sprite.Sprite} sprites
  9840. * @param {Boolean} [isWithoutTransform=false]
  9841. * @return {{x: Number, y: Number, width: number, height: number}}
  9842. */
  9843. getBBox: function(sprites, isWithoutTransform) {
  9844. var left = Infinity,
  9845. right = -Infinity,
  9846. top = Infinity,
  9847. bottom = -Infinity,
  9848. sprite, bbox, i, ln;
  9849. sprites = Ext.Array.from(sprites);
  9850. for (i = 0 , ln = sprites.length; i < ln; i++) {
  9851. sprite = sprites[i];
  9852. bbox = sprite.getBBox(isWithoutTransform);
  9853. if (left > bbox.x) {
  9854. left = bbox.x;
  9855. }
  9856. if (right < bbox.x + bbox.width) {
  9857. right = bbox.x + bbox.width;
  9858. }
  9859. if (top > bbox.y) {
  9860. top = bbox.y;
  9861. }
  9862. if (bottom < bbox.y + bbox.height) {
  9863. bottom = bbox.y + bbox.height;
  9864. }
  9865. }
  9866. return {
  9867. x: left,
  9868. y: top,
  9869. width: right - left,
  9870. height: bottom - top
  9871. };
  9872. },
  9873. /**
  9874. * @private
  9875. * @method getOwnerBody
  9876. * The body element of the chart or the draw container
  9877. * (doesn't include docked items like a legend).
  9878. * Draw Container is a Panel in Classic (to allow for docked items)
  9879. * and a Container in Modern, so the body is retrieved differently.
  9880. * @return {Ext.dom.Element}
  9881. */
  9882. /**
  9883. * @private
  9884. * Converts event's page coordinates into surface coordinates.
  9885. * Note: surface's x-coordinates always go LTR, regardless of RTL mode.
  9886. */
  9887. getEventXY: function(e) {
  9888. var me = this,
  9889. isRtl = me.getInherited().rtl,
  9890. pageXY = e.getXY(),
  9891. // Event position in page coordinates.
  9892. // The body of the chart (doesn't include docked items like legend).
  9893. container = me.getOwnerBody(),
  9894. xy = container.getXY(),
  9895. // Surface container position in page coordinates.
  9896. // Surface position in surface container coordinates (LTR).
  9897. rect = me.getRect() || me.emptyRect,
  9898. result = [],
  9899. width;
  9900. if (isRtl) {
  9901. width = container.getWidth();
  9902. // The line below is actually a simplified form of
  9903. // rect[2] - (pageXY[0] - xy[0] - (width - (rect[0] + rect[2]))).
  9904. result[0] = xy[0] - pageXY[0] - rect[0] + width;
  9905. } else {
  9906. result[0] = pageXY[0] - xy[0] - rect[0];
  9907. }
  9908. result[1] = pageXY[1] - xy[1] - rect[1];
  9909. return result;
  9910. },
  9911. /**
  9912. * @method
  9913. * Empty the surface content (without touching the sprites.)
  9914. */
  9915. clear: Ext.emptyFn,
  9916. /**
  9917. * @private
  9918. * Order the items by their z-index if any of that has been changed since last sort.
  9919. */
  9920. orderByZIndex: function() {
  9921. var me = this,
  9922. items = me.getItems(),
  9923. dirtyZIndex = false,
  9924. i, ln;
  9925. if (me.getDirty()) {
  9926. for (i = 0 , ln = items.length; i < ln; i++) {
  9927. if (items[i].attr.dirtyZIndex) {
  9928. dirtyZIndex = true;
  9929. break;
  9930. }
  9931. }
  9932. if (dirtyZIndex) {
  9933. // sort by zIndex
  9934. Ext.Array.sort(items, function(a, b) {
  9935. return a.attr.zIndex - b.attr.zIndex;
  9936. });
  9937. this.setDirty(true);
  9938. }
  9939. for (i = 0 , ln = items.length; i < ln; i++) {
  9940. items[i].attr.dirtyZIndex = false;
  9941. }
  9942. }
  9943. },
  9944. /**
  9945. * Force the element to redraw.
  9946. */
  9947. repaint: function() {
  9948. var me = this;
  9949. me.repaint = Ext.emptyFn;
  9950. Ext.defer(function() {
  9951. delete me.repaint;
  9952. me.element.repaint();
  9953. }, 1);
  9954. },
  9955. /**
  9956. * Triggers the re-rendering of the canvas.
  9957. */
  9958. renderFrame: function() {
  9959. var me = this,
  9960. background, items, item, i, ln;
  9961. if (!(me.element && me.getDirty() && me.getRect())) {
  9962. return;
  9963. }
  9964. if (me.dirtyPredecessorCount > 0) {
  9965. me.isPendingRenderFrame = true;
  9966. return;
  9967. }
  9968. background = me.getBackground();
  9969. items = me.getItems();
  9970. // This will also check the dirty flags of the sprites.
  9971. me.orderByZIndex();
  9972. if (me.getDirty()) {
  9973. me.clear();
  9974. me.clearTransform();
  9975. if (background) {
  9976. me.renderSprite(background);
  9977. }
  9978. for (i = 0 , ln = items.length; i < ln; i++) {
  9979. item = items[i];
  9980. if (me.renderSprite(item) === false) {
  9981. return;
  9982. }
  9983. item.attr.textPositionCount = me.textPosition;
  9984. }
  9985. me.setDirty(false);
  9986. }
  9987. },
  9988. /**
  9989. * @method
  9990. * @private
  9991. * Renders a single sprite into the surface.
  9992. * Do not call it from outside `renderFrame` method.
  9993. *
  9994. * @param {Ext.draw.sprite.Sprite} sprite The Sprite to be rendered.
  9995. * @return {Boolean} returns `false` to stop the rendering to continue.
  9996. */
  9997. renderSprite: Ext.emptyFn,
  9998. /**
  9999. * @method flatten
  10000. * Flattens the given drawing surfaces into a single image
  10001. * and returns an object containing the data (in the DataURL format)
  10002. * and the type (e.g. 'png' or 'svg') of that image.
  10003. * @param {Object} size The size of the final image.
  10004. * @param {Number} size.width
  10005. * @param {Number} size.height
  10006. * @param {Ext.draw.Surface[]} surfaces The surfaces to flatten.
  10007. * @return {Object}
  10008. * @return {String} return.data The DataURL of the flattened image.
  10009. * @return {String} return.type The type of the image.
  10010. *
  10011. */
  10012. /**
  10013. * @method
  10014. * @private
  10015. * Clears the current transformation state on the surface.
  10016. */
  10017. clearTransform: Ext.emptyFn,
  10018. /**
  10019. * Destroys the surface. This is done by removing all components from it and
  10020. * also removing its reference to a DOM element.
  10021. *
  10022. * For example:
  10023. *
  10024. * drawContainer.surface.destroy();
  10025. */
  10026. destroy: function() {
  10027. var me = this;
  10028. me.destroying = true;
  10029. me.removeAll(true);
  10030. me.destroying = false;
  10031. me.predecessors = me.successors = null;
  10032. if (me.hasListeners.destroy) {
  10033. me.fireEvent('destroy', me);
  10034. }
  10035. me.callParent();
  10036. }
  10037. });
  10038. /**
  10039. * @private
  10040. * Adds hit testing methods to the Ext.draw.Surface.
  10041. * Included by the Ext.draw.plugin.SpriteEvents.
  10042. */
  10043. Ext.define('Ext.draw.overrides.hittest.Surface', {
  10044. override: 'Ext.draw.Surface',
  10045. /**
  10046. * Performs a hit test on all sprites in the surface, returning the first matching one.
  10047. * @param {Array} point A two-item array containing x and y coordinates of the point
  10048. * in surface coordinate system.
  10049. * @param {Object} options Hit testing options.
  10050. * @return {Object} A hit result object that contains more information about what
  10051. * exactly was hit or null if nothing was hit.
  10052. * @member Ext.draw.Surface
  10053. */
  10054. hitTest: function(point, options) {
  10055. var me = this,
  10056. sprites = me.getItems(),
  10057. i, sprite, result;
  10058. options = options || Ext.draw.sprite.Sprite.defaultHitTestOptions;
  10059. for (i = sprites.length - 1; i >= 0; i--) {
  10060. sprite = sprites[i];
  10061. if (sprite.hitTest) {
  10062. result = sprite.hitTest(point, options);
  10063. if (result) {
  10064. return result;
  10065. }
  10066. }
  10067. }
  10068. return null;
  10069. },
  10070. /**
  10071. * Performs a hit test on all sprites in the surface, returning the first matching one.
  10072. * Since hit testing is typically performed on mouse events, this convenience method
  10073. * converts event's page coordinates to surface coordinates before calling {@link #hitTest}.
  10074. * @param {Object} event An event object.
  10075. * @param {Object} options Hit testing options.
  10076. * @return {Object} A hit result object that contains more information about what
  10077. * exactly was hit or null if nothing was hit.
  10078. * @member Ext.draw.Surface
  10079. */
  10080. hitTestEvent: function(event, options) {
  10081. var xy = this.getEventXY(event);
  10082. return this.hitTest(xy, options);
  10083. }
  10084. });
  10085. /**
  10086. * @class Ext.draw.engine.SvgContext
  10087. *
  10088. * A class that imitates a canvas context but generates svg elements instead.
  10089. */
  10090. Ext.define('Ext.draw.engine.SvgContext', {
  10091. requires: [
  10092. 'Ext.draw.Color'
  10093. ],
  10094. /**
  10095. * @private
  10096. * Properties to be saved/restored in the `save` and `restore` methods.
  10097. */
  10098. toSave: [
  10099. 'strokeOpacity',
  10100. 'strokeStyle',
  10101. 'fillOpacity',
  10102. 'fillStyle',
  10103. 'globalAlpha',
  10104. 'lineWidth',
  10105. 'lineCap',
  10106. 'lineJoin',
  10107. 'lineDash',
  10108. 'lineDashOffset',
  10109. 'miterLimit',
  10110. 'shadowOffsetX',
  10111. 'shadowOffsetY',
  10112. 'shadowBlur',
  10113. 'shadowColor',
  10114. 'globalCompositeOperation',
  10115. 'position',
  10116. 'fillGradient',
  10117. 'strokeGradient'
  10118. ],
  10119. strokeOpacity: 1,
  10120. strokeStyle: 'none',
  10121. fillOpacity: 1,
  10122. fillStyle: 'none',
  10123. lineDas: [],
  10124. lineDashOffset: 0,
  10125. globalAlpha: 1,
  10126. lineWidth: 1,
  10127. lineCap: 'butt',
  10128. lineJoin: 'miter',
  10129. miterLimit: 10,
  10130. shadowOffsetX: 0,
  10131. shadowOffsetY: 0,
  10132. shadowBlur: 0,
  10133. shadowColor: 'none',
  10134. globalCompositeOperation: 'src',
  10135. urlStringRe: /^url\(#([\w-]+)\)$/,
  10136. constructor: function(SvgSurface) {
  10137. var me = this;
  10138. me.surface = SvgSurface;
  10139. // Stack of contexts.
  10140. me.state = [];
  10141. me.matrix = new Ext.draw.Matrix();
  10142. // Currently manipulated path.
  10143. me.path = null;
  10144. me.clear();
  10145. },
  10146. /**
  10147. * Clears the context.
  10148. */
  10149. clear: function() {
  10150. // Current group to put paths into.
  10151. this.group = this.surface.mainGroup;
  10152. // Position within the current group.
  10153. this.position = 0;
  10154. this.path = null;
  10155. },
  10156. /**
  10157. * @private
  10158. * @param {String} tag
  10159. * @return {*}
  10160. */
  10161. getElement: function(tag) {
  10162. return this.surface.getSvgElement(this.group, tag, this.position++);
  10163. },
  10164. /**
  10165. * Pushes the context state to the state stack.
  10166. */
  10167. save: function() {
  10168. var toSave = this.toSave,
  10169. obj = {},
  10170. group = this.getElement('g'),
  10171. key, i;
  10172. for (i = 0; i < toSave.length; i++) {
  10173. key = toSave[i];
  10174. if (key in this) {
  10175. obj[key] = this[key];
  10176. }
  10177. }
  10178. this.position = 0;
  10179. obj.matrix = this.matrix.clone();
  10180. this.state.push(obj);
  10181. this.group = group;
  10182. return group;
  10183. },
  10184. /**
  10185. * Pops the state stack and restores the state.
  10186. */
  10187. restore: function() {
  10188. var toSave = this.toSave,
  10189. obj = this.state.pop(),
  10190. group = this.group,
  10191. children = group.dom.childNodes,
  10192. key, i;
  10193. // Removing extra DOM elements that were not reused.
  10194. while (children.length > this.position) {
  10195. group.last().destroy();
  10196. }
  10197. for (i = 0; i < toSave.length; i++) {
  10198. key = toSave[i];
  10199. if (key in obj) {
  10200. this[key] = obj[key];
  10201. } else {
  10202. delete this[key];
  10203. }
  10204. }
  10205. this.setTransform.apply(this, obj.matrix.elements);
  10206. this.group = group.getParent();
  10207. },
  10208. /**
  10209. * Changes the transformation matrix to apply the matrix given by the arguments
  10210. * as described below.
  10211. * @param {Number} xx
  10212. * @param {Number} yx
  10213. * @param {Number} xy
  10214. * @param {Number} yy
  10215. * @param {Number} dx
  10216. * @param {Number} dy
  10217. */
  10218. transform: function(xx, yx, xy, yy, dx, dy) {
  10219. var inv;
  10220. if (this.path) {
  10221. inv = Ext.draw.Matrix.fly([
  10222. xx,
  10223. yx,
  10224. xy,
  10225. yy,
  10226. dx,
  10227. dy
  10228. ]).inverse();
  10229. this.path.transform(inv);
  10230. }
  10231. this.matrix.append(xx, yx, xy, yy, dx, dy);
  10232. },
  10233. /**
  10234. * Changes the transformation matrix to the matrix given by the arguments as described below.
  10235. * @param {Number} xx
  10236. * @param {Number} yx
  10237. * @param {Number} xy
  10238. * @param {Number} yy
  10239. * @param {Number} dx
  10240. * @param {Number} dy
  10241. */
  10242. setTransform: function(xx, yx, xy, yy, dx, dy) {
  10243. if (this.path) {
  10244. this.path.transform(this.matrix);
  10245. }
  10246. this.matrix.reset();
  10247. this.transform(xx, yx, xy, yy, dx, dy);
  10248. },
  10249. /**
  10250. * Scales the current context by the specified horizontal (x) and vertical (y) factors.
  10251. * @param {Number} x The horizontal scaling factor, where 1 equals unity or 100% scale.
  10252. * @param {Number} y The vertical scaling factor.
  10253. */
  10254. scale: function(x, y) {
  10255. this.transform(x, 0, 0, y, 0, 0);
  10256. },
  10257. /**
  10258. * Rotates the current context coordinates (that is, a transformation matrix).
  10259. * @param {Number} angle The rotation angle, in radians.
  10260. */
  10261. rotate: function(angle) {
  10262. var xx = Math.cos(angle),
  10263. yx = Math.sin(angle),
  10264. xy = -Math.sin(angle),
  10265. yy = Math.cos(angle);
  10266. this.transform(xx, yx, xy, yy, 0, 0);
  10267. },
  10268. /**
  10269. * Specifies values to move the origin point in a canvas.
  10270. * @param {Number} x The value to add to horizontal (or x) coordinates.
  10271. * @param {Number} y The value to add to vertical (or y) coordinates.
  10272. */
  10273. translate: function(x, y) {
  10274. this.transform(1, 0, 0, 1, x, y);
  10275. },
  10276. setGradientBBox: function(bbox) {
  10277. this.bbox = bbox;
  10278. },
  10279. /**
  10280. * Resets the current default path.
  10281. */
  10282. beginPath: function() {
  10283. this.path = new Ext.draw.Path();
  10284. },
  10285. /**
  10286. * Creates a new subpath with the given point.
  10287. * @param {Number} x
  10288. * @param {Number} y
  10289. */
  10290. moveTo: function(x, y) {
  10291. if (!this.path) {
  10292. this.beginPath();
  10293. }
  10294. this.path.moveTo(x, y);
  10295. this.path.element = null;
  10296. },
  10297. /**
  10298. * Adds the given point to the current subpath, connected to the previous one by a straight
  10299. * line.
  10300. * @param {Number} x
  10301. * @param {Number} y
  10302. */
  10303. lineTo: function(x, y) {
  10304. if (!this.path) {
  10305. this.beginPath();
  10306. }
  10307. this.path.lineTo(x, y);
  10308. this.path.element = null;
  10309. },
  10310. /**
  10311. * Adds a new closed subpath to the path, representing the given rectangle.
  10312. * @param {Number} x
  10313. * @param {Number} y
  10314. * @param {Number} width
  10315. * @param {Number} height
  10316. */
  10317. rect: function(x, y, width, height) {
  10318. this.moveTo(x, y);
  10319. this.lineTo(x + width, y);
  10320. this.lineTo(x + width, y + height);
  10321. this.lineTo(x, y + height);
  10322. this.closePath();
  10323. },
  10324. /**
  10325. * Paints the box that outlines the given rectangle onto the canvas, using the current
  10326. * stroke style.
  10327. * @param {Number} x
  10328. * @param {Number} y
  10329. * @param {Number} width
  10330. * @param {Number} height
  10331. */
  10332. strokeRect: function(x, y, width, height) {
  10333. this.beginPath();
  10334. this.rect(x, y, width, height);
  10335. this.stroke();
  10336. },
  10337. /**
  10338. * Paints the given rectangle onto the canvas, using the current fill style.
  10339. * @param {Number} x
  10340. * @param {Number} y
  10341. * @param {Number} width
  10342. * @param {Number} height
  10343. */
  10344. fillRect: function(x, y, width, height) {
  10345. this.beginPath();
  10346. this.rect(x, y, width, height);
  10347. this.fill();
  10348. },
  10349. /**
  10350. * Marks the current subpath as closed, and starts a new subpath with a point the same
  10351. * as the start and end of the newly closed subpath.
  10352. */
  10353. closePath: function() {
  10354. if (!this.path) {
  10355. this.beginPath();
  10356. }
  10357. this.path.closePath();
  10358. this.path.element = null;
  10359. },
  10360. /**
  10361. * Arc command using svg parameters.
  10362. * @param {Number} r1
  10363. * @param {Number} r2
  10364. * @param {Number} rotation
  10365. * @param {Number} large
  10366. * @param {Number} swipe
  10367. * @param {Number} x2
  10368. * @param {Number} y2
  10369. */
  10370. arcSvg: function(r1, r2, rotation, large, swipe, x2, y2) {
  10371. if (!this.path) {
  10372. this.beginPath();
  10373. }
  10374. this.path.arcSvg(r1, r2, rotation, large, swipe, x2, y2);
  10375. this.path.element = null;
  10376. },
  10377. /**
  10378. * Adds points to the subpath such that the arc described by the circumference of the circle
  10379. * described by the arguments, starting at the given start angle and ending at the given
  10380. * end angle, going in the given direction (defaulting to clockwise), is added to the path,
  10381. * connected to the previous point by a straight line.
  10382. * @param {Number} x
  10383. * @param {Number} y
  10384. * @param {Number} radius
  10385. * @param {Number} startAngle
  10386. * @param {Number} endAngle
  10387. * @param {Number} anticlockwise
  10388. */
  10389. arc: function(x, y, radius, startAngle, endAngle, anticlockwise) {
  10390. if (!this.path) {
  10391. this.beginPath();
  10392. }
  10393. this.path.arc(x, y, radius, startAngle, endAngle, anticlockwise);
  10394. this.path.element = null;
  10395. },
  10396. /**
  10397. * Adds points to the subpath such that the arc described by the circumference of the ellipse
  10398. * described by the arguments, starting at the given start angle and ending at the given
  10399. * end angle, going in the given direction (defaulting to clockwise), is added to the path,
  10400. * connected to the previous point by a straight line.
  10401. * @param {Number} x
  10402. * @param {Number} y
  10403. * @param {Number} radiusX
  10404. * @param {Number} radiusY
  10405. * @param {Number} rotation
  10406. * @param {Number} startAngle
  10407. * @param {Number} endAngle
  10408. * @param {Number} anticlockwise
  10409. */
  10410. ellipse: function(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise) {
  10411. if (!this.path) {
  10412. this.beginPath();
  10413. }
  10414. this.path.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise);
  10415. this.path.element = null;
  10416. },
  10417. /**
  10418. * Adds an arc with the given control points and radius to the current subpath, connected
  10419. * to the previous point by a straight line. If two radii are provided, the first controls
  10420. * the width of the arc's ellipse, and the second controls the height. If only one is provided,
  10421. * or if they are the same, the arc is from a circle. In the case of an ellipse, the rotation
  10422. * argument controls the clockwise inclination of the ellipse relative to the x-axis.
  10423. * @param {Number} x1
  10424. * @param {Number} y1
  10425. * @param {Number} x2
  10426. * @param {Number} y2
  10427. * @param {Number} radiusX
  10428. * @param {Number} radiusY
  10429. * @param {Number} rotation
  10430. */
  10431. arcTo: function(x1, y1, x2, y2, radiusX, radiusY, rotation) {
  10432. if (!this.path) {
  10433. this.beginPath();
  10434. }
  10435. this.path.arcTo(x1, y1, x2, y2, radiusX, radiusY, rotation);
  10436. this.path.element = null;
  10437. },
  10438. /**
  10439. * Adds the given point to the current subpath, connected to the previous one by a cubic Bézier
  10440. * curve with the given control points.
  10441. * @param {Number} x1
  10442. * @param {Number} y1
  10443. * @param {Number} x2
  10444. * @param {Number} y2
  10445. * @param {Number} x3
  10446. * @param {Number} y3
  10447. */
  10448. bezierCurveTo: function(x1, y1, x2, y2, x3, y3) {
  10449. if (!this.path) {
  10450. this.beginPath();
  10451. }
  10452. this.path.bezierCurveTo(x1, y1, x2, y2, x3, y3);
  10453. this.path.element = null;
  10454. },
  10455. /**
  10456. * Strokes the given text at the given position. If a maximum width is provided, the text
  10457. * will be scaled to fit that width if necessary.
  10458. * @param {String} text
  10459. * @param {Number} x
  10460. * @param {Number} y
  10461. */
  10462. strokeText: function(text, x, y) {
  10463. var element, tspan;
  10464. text = String(text);
  10465. if (this.strokeStyle) {
  10466. element = this.getElement('text');
  10467. tspan = this.surface.getSvgElement(element, 'tspan', 0);
  10468. this.surface.setElementAttributes(element, {
  10469. "x": x,
  10470. "y": y,
  10471. "transform": this.matrix.toSvg(),
  10472. "stroke": this.strokeStyle,
  10473. "fill": "none",
  10474. "opacity": this.globalAlpha,
  10475. "stroke-opacity": this.strokeOpacity,
  10476. "style": "font: " + this.font,
  10477. "stroke-dasharray": this.lineDash.join(','),
  10478. "stroke-dashoffset": this.lineDashOffset
  10479. });
  10480. if (this.lineDash.length) {
  10481. this.surface.setElementAttributes(element, {
  10482. "stroke-dasharray": this.lineDash.join(','),
  10483. "stroke-dashoffset": this.lineDashOffset
  10484. });
  10485. }
  10486. if (tspan.dom.firstChild) {
  10487. tspan.dom.removeChild(tspan.dom.firstChild);
  10488. }
  10489. this.surface.setElementAttributes(tspan, {
  10490. "alignment-baseline": "alphabetic"
  10491. });
  10492. tspan.dom.appendChild(document.createTextNode(Ext.String.htmlDecode(text)));
  10493. }
  10494. },
  10495. /**
  10496. * Fills the given text at the given position. If a maximum width is provided, the text
  10497. * will be scaled to fit that width if necessary.
  10498. * @param {String} text
  10499. * @param {Number} x
  10500. * @param {Number} y
  10501. */
  10502. fillText: function(text, x, y) {
  10503. var element, tspan;
  10504. text = String(text);
  10505. if (this.fillStyle) {
  10506. element = this.getElement('text');
  10507. tspan = this.surface.getSvgElement(element, 'tspan', 0);
  10508. this.surface.setElementAttributes(element, {
  10509. "x": x,
  10510. "y": y,
  10511. "transform": this.matrix.toSvg(),
  10512. "fill": this.fillStyle,
  10513. "opacity": this.globalAlpha,
  10514. "fill-opacity": this.fillOpacity,
  10515. "style": "font: " + this.font
  10516. });
  10517. if (tspan.dom.firstChild) {
  10518. tspan.dom.removeChild(tspan.dom.firstChild);
  10519. }
  10520. this.surface.setElementAttributes(tspan, {
  10521. "alignment-baseline": "alphabetic"
  10522. });
  10523. tspan.dom.appendChild(document.createTextNode(Ext.String.htmlDecode(text)));
  10524. }
  10525. },
  10526. /**
  10527. * Draws the given image onto the canvas.
  10528. * If the first argument isn't an img, canvas, or video element, throws a TypeMismatchError
  10529. * exception. If the image has no image data, throws an InvalidStateError exception.
  10530. * If the one of the source rectangle dimensions is zero, throws an IndexSizeError exception.
  10531. * If the image isn't yet fully decoded, then nothing is drawn.
  10532. * @param {HTMLElement} image
  10533. * @param {Number} sx
  10534. * @param {Number} sy
  10535. * @param {Number} sw
  10536. * @param {Number} sh
  10537. * @param {Number} dx
  10538. * @param {Number} dy
  10539. * @param {Number} dw
  10540. * @param {Number} dh
  10541. */
  10542. drawImage: function(image, sx, sy, sw, sh, dx, dy, dw, dh) {
  10543. var me = this,
  10544. element = me.getElement('image'),
  10545. x = sx,
  10546. y = sy,
  10547. width = typeof sw === 'undefined' ? image.width : sw,
  10548. height = typeof sh === 'undefined' ? image.height : sh,
  10549. viewBox = null;
  10550. if (typeof dh !== 'undefined') {
  10551. viewBox = sx + " " + sy + " " + sw + " " + sh;
  10552. x = dx;
  10553. y = dy;
  10554. width = dw;
  10555. height = dh;
  10556. }
  10557. element.dom.setAttributeNS("http:/" + "/www.w3.org/1999/xlink", "href", image.src);
  10558. me.surface.setElementAttributes(element, {
  10559. viewBox: viewBox,
  10560. x: x,
  10561. y: y,
  10562. width: width,
  10563. height: height,
  10564. opacity: me.globalAlpha,
  10565. transform: me.matrix.toSvg()
  10566. });
  10567. },
  10568. /**
  10569. * Fills the subpaths of the current default path or the given path with the current fill style.
  10570. */
  10571. fill: function() {
  10572. var me = this,
  10573. path, fillGradient, element, bbox, fill;
  10574. if (!me.path) {
  10575. return;
  10576. }
  10577. if (me.fillStyle) {
  10578. fillGradient = me.fillGradient;
  10579. element = me.path.element;
  10580. bbox = me.bbox;
  10581. if (!element) {
  10582. path = me.path.toString();
  10583. element = me.path.element = me.getElement('path');
  10584. me.surface.setElementAttributes(element, {
  10585. "d": path,
  10586. "transform": me.matrix.toSvg()
  10587. });
  10588. }
  10589. if (fillGradient && bbox) {
  10590. // This indirectly calls ctx.createLinearGradient or ctx.createRadialGradient,
  10591. // depending on the type of gradient, and returns an instance of
  10592. // Ext.draw.engine.SvgContext.Gradient.
  10593. fill = fillGradient.generateGradient(me, bbox);
  10594. } else {
  10595. fill = me.fillStyle;
  10596. }
  10597. me.surface.setElementAttributes(element, {
  10598. "fill": fill,
  10599. "fill-opacity": me.fillOpacity * me.globalAlpha
  10600. });
  10601. }
  10602. },
  10603. /**
  10604. * Strokes the subpaths of the current default path or the given path with the current
  10605. * stroke style.
  10606. */
  10607. stroke: function() {
  10608. var me = this,
  10609. path, strokeGradient, element, bbox, stroke;
  10610. if (!me.path) {
  10611. return;
  10612. }
  10613. if (me.strokeStyle) {
  10614. strokeGradient = me.strokeGradient;
  10615. element = me.path.element;
  10616. bbox = me.bbox;
  10617. if (!element || !me.path.svgString) {
  10618. path = me.path.toString();
  10619. if (!path) {
  10620. return;
  10621. }
  10622. element = me.path.element = me.getElement('path');
  10623. me.surface.setElementAttributes(element, {
  10624. "fill": "none",
  10625. "d": path,
  10626. "transform": me.matrix.toSvg()
  10627. });
  10628. }
  10629. if (strokeGradient && bbox) {
  10630. // This indirectly calls ctx.createLinearGradient or ctx.createRadialGradient,
  10631. // depending on the type of gradient, and returns an instance of
  10632. // Ext.draw.engine.SvgContext.Gradient.
  10633. stroke = strokeGradient.generateGradient(me, bbox);
  10634. } else {
  10635. stroke = me.strokeStyle;
  10636. }
  10637. me.surface.setElementAttributes(element, {
  10638. "stroke": stroke,
  10639. "stroke-linecap": me.lineCap,
  10640. "stroke-linejoin": me.lineJoin,
  10641. "stroke-width": me.lineWidth,
  10642. "stroke-opacity": me.strokeOpacity * me.globalAlpha,
  10643. "stroke-dasharray": me.lineDash.join(','),
  10644. "stroke-dashoffset": me.lineDashOffset
  10645. });
  10646. if (me.lineDash.length) {
  10647. me.surface.setElementAttributes(element, {
  10648. "stroke-dasharray": me.lineDash.join(','),
  10649. "stroke-dashoffset": me.lineDashOffset
  10650. });
  10651. }
  10652. }
  10653. },
  10654. /**
  10655. * @protected
  10656. *
  10657. * Note: After the method guarantees the transform matrix will be inverted.
  10658. * @param {Object} attr The attribute object
  10659. * @param {Boolean} [transformFillStroke] Indicate whether to transform fill and stroke.
  10660. * If this is not given, then uses `attr.transformFillStroke` instead.
  10661. */
  10662. fillStroke: function(attr, transformFillStroke) {
  10663. var ctx = this,
  10664. fillStyle = ctx.fillStyle,
  10665. strokeStyle = ctx.strokeStyle,
  10666. fillOpacity = ctx.fillOpacity,
  10667. strokeOpacity = ctx.strokeOpacity;
  10668. if (transformFillStroke === undefined) {
  10669. transformFillStroke = attr.transformFillStroke;
  10670. }
  10671. if (!transformFillStroke) {
  10672. attr.inverseMatrix.toContext(ctx);
  10673. }
  10674. if (fillStyle && fillOpacity !== 0) {
  10675. ctx.fill();
  10676. }
  10677. if (strokeStyle && strokeOpacity !== 0) {
  10678. ctx.stroke();
  10679. }
  10680. },
  10681. appendPath: function(path) {
  10682. this.path = path.clone();
  10683. },
  10684. setLineDash: function(lineDash) {
  10685. this.lineDash = lineDash;
  10686. },
  10687. getLineDash: function() {
  10688. return this.lineDash;
  10689. },
  10690. /**
  10691. * Returns an object that represents a linear gradient that paints along the line
  10692. * given by the coordinates represented by the arguments.
  10693. * @param {Number} x0
  10694. * @param {Number} y0
  10695. * @param {Number} x1
  10696. * @param {Number} y1
  10697. * @return {Ext.draw.engine.SvgContext.Gradient}
  10698. */
  10699. createLinearGradient: function(x0, y0, x1, y1) {
  10700. var me = this,
  10701. element = me.surface.getNextDef('linearGradient'),
  10702. gradient;
  10703. me.surface.setElementAttributes(element, {
  10704. "x1": x0,
  10705. "y1": y0,
  10706. "x2": x1,
  10707. "y2": y1,
  10708. "gradientUnits": "userSpaceOnUse"
  10709. });
  10710. gradient = new Ext.draw.engine.SvgContext.Gradient(me, me.surface, element);
  10711. return gradient;
  10712. },
  10713. /**
  10714. * Returns a CanvasGradient object that represents a radial gradient that paints
  10715. * along the cone given by the circles represented by the arguments.
  10716. * If either of the radii are negative, throws an IndexSizeError exception.
  10717. * @param {Number} x0
  10718. * @param {Number} y0
  10719. * @param {Number} r0
  10720. * @param {Number} x1
  10721. * @param {Number} y1
  10722. * @param {Number} r1
  10723. * @return {Ext.draw.engine.SvgContext.Gradient}
  10724. */
  10725. createRadialGradient: function(x0, y0, r0, x1, y1, r1) {
  10726. var me = this,
  10727. element = me.surface.getNextDef('radialGradient'),
  10728. gradient;
  10729. me.surface.setElementAttributes(element, {
  10730. fx: x0,
  10731. fy: y0,
  10732. cx: x1,
  10733. cy: y1,
  10734. r: r1,
  10735. gradientUnits: 'userSpaceOnUse'
  10736. });
  10737. gradient = new Ext.draw.engine.SvgContext.Gradient(me, me.surface, element, r0 / r1);
  10738. return gradient;
  10739. }
  10740. });
  10741. /**
  10742. * @class Ext.draw.engine.SvgContext.Gradient
  10743. *
  10744. * A class that implements native CanvasGradient interface
  10745. * (https://developer.mozilla.org/en/docs/Web/API/CanvasGradient)
  10746. * and a `toString` method that returns the ID of the gradient.
  10747. */
  10748. Ext.define('Ext.draw.engine.SvgContext.Gradient', {
  10749. // Gradients workflow in SVG engine:
  10750. //
  10751. // Inside the 'fill' & 'stroke' methods of the SVG Context
  10752. // we check if the 'ctx.fillGradient' or 'ctx.strokeGradient'
  10753. // objects exist.
  10754. // These objects are instances of Ext.draw.gradient.Gradient
  10755. // and are assigned to the ctx by the sprite's 'useAttributes' method,
  10756. // if the sprite has any gradients.
  10757. //
  10758. // Additionally, we check if the 'ctx.bbox' object exists - the bounding box
  10759. // for the gradients, set by the sprite's 'setGradientBBox' method.
  10760. //
  10761. // If we have both bbox and a valid instance of Ext.draw.gradient.Gradient,
  10762. // the 'generateGradient' method of the instance is called,
  10763. // which in turn calls 'ctx.createLinearGradient' or 'ctx.createRadialGradient'
  10764. // depending on the type of the gradient represented by the instance.
  10765. // These methods create a 'linearGradient' or 'radialGradient' SVG
  10766. // node and wrap it into a Ext.draw.engine.SvgContext.Gradient instance.
  10767. //
  10768. // The Ext.draw.engine.SvgContext.Gradient instance is then used internally
  10769. // by the Ext.draw.gradient.Gradient to add color 'stop' nodes
  10770. // to the gradient node, and by the SVG context when the 'fill' or
  10771. // 'stroke' attribute of a 'path' node is set to the Ext.draw.engine.SvgContext.Gradient
  10772. // instance, which is implicitly converted to a string - a 'url(#id)' reference
  10773. // to the gradient element wrapped by the instance.
  10774. isGradient: true,
  10775. constructor: function(ctx, surface, element, compression) {
  10776. var me = this;
  10777. me.ctx = ctx;
  10778. me.surface = surface;
  10779. me.element = element;
  10780. me.position = 0;
  10781. me.compression = compression || 0;
  10782. },
  10783. /**
  10784. * Adds a color stop with the given color to the gradient at the given offset. 0.0 is the offset
  10785. * at one end of the gradient, 1.0 is the offset at the other end.
  10786. * @param {Number} offset
  10787. * @param {String} color
  10788. */
  10789. addColorStop: function(offset, color) {
  10790. var me = this,
  10791. stop = me.surface.getSvgElement(me.element, 'stop', me.position++),
  10792. compression = me.compression;
  10793. me.surface.setElementAttributes(stop, {
  10794. "offset": (((1 - compression) * offset + compression) * 100).toFixed(2) + '%',
  10795. "stop-color": color,
  10796. "stop-opacity": Ext.util.Color.fly(color).a.toFixed(15)
  10797. });
  10798. },
  10799. toString: function() {
  10800. var children = this.element.dom.childNodes;
  10801. // Removing surplus stops in case existing gradient element with more stops was reused.
  10802. while (children.length > this.position) {
  10803. Ext.fly(children[children.length - 1]).destroy();
  10804. }
  10805. return 'url(#' + this.element.getId() + ')';
  10806. }
  10807. });
  10808. /**
  10809. * @class Ext.draw.engine.Svg
  10810. * @extends Ext.draw.Surface
  10811. *
  10812. * SVG engine.
  10813. */
  10814. Ext.define('Ext.draw.engine.Svg', {
  10815. extend: 'Ext.draw.Surface',
  10816. requires: [
  10817. 'Ext.draw.engine.SvgContext'
  10818. ],
  10819. isSVG: true,
  10820. config: {
  10821. /**
  10822. * @cfg {Boolean} highPrecision
  10823. * Nothing needs to be done in high precision mode.
  10824. */
  10825. highPrecision: false
  10826. },
  10827. getElementConfig: function() {
  10828. return {
  10829. reference: 'element',
  10830. style: {
  10831. position: 'absolute'
  10832. },
  10833. children: [
  10834. {
  10835. reference: 'bodyElement',
  10836. style: {
  10837. width: '100%',
  10838. height: '100%',
  10839. position: 'relative'
  10840. },
  10841. children: [
  10842. {
  10843. tag: 'svg',
  10844. reference: 'svgElement',
  10845. namespace: "http://www.w3.org/2000/svg",
  10846. width: '100%',
  10847. height: '100%',
  10848. version: 1.1
  10849. }
  10850. ]
  10851. }
  10852. ]
  10853. };
  10854. },
  10855. constructor: function(config) {
  10856. var me = this;
  10857. me.callParent([
  10858. config
  10859. ]);
  10860. me.mainGroup = me.createSvgNode("g");
  10861. me.defsElement = me.createSvgNode("defs");
  10862. // me.svgElement is assigned in element creation of Ext.Component.
  10863. me.svgElement.appendChild(me.mainGroup);
  10864. me.svgElement.appendChild(me.defsElement);
  10865. me.ctx = new Ext.draw.engine.SvgContext(me);
  10866. },
  10867. /**
  10868. * Creates a DOM element under the SVG namespace of the given type.
  10869. * @param {String} type The type of the SVG DOM element.
  10870. * @return {*} The created element.
  10871. */
  10872. createSvgNode: function(type) {
  10873. var node = document.createElementNS("http://www.w3.org/2000/svg", type);
  10874. return Ext.get(node);
  10875. },
  10876. /**
  10877. * @private
  10878. * Returns the SVG DOM element at the given position.
  10879. * If it does not already exist or is a different element tag,
  10880. * it will be created and inserted into the DOM.
  10881. * @param {Ext.dom.Element} group The parent DOM element.
  10882. * @param {String} tag The SVG element tag.
  10883. * @param {Number} position The position of the element in the DOM.
  10884. * @return {Ext.dom.Element} The SVG element.
  10885. */
  10886. getSvgElement: function(group, tag, position) {
  10887. var childNodes = group.dom.childNodes,
  10888. length = childNodes.length,
  10889. element;
  10890. if (position < length) {
  10891. element = childNodes[position];
  10892. if (element.tagName === tag) {
  10893. return Ext.get(element);
  10894. } else {
  10895. Ext.destroy(element);
  10896. }
  10897. } else if (position > length) {
  10898. Ext.raise("Invalid position.");
  10899. }
  10900. element = Ext.get(this.createSvgNode(tag));
  10901. if (position === 0) {
  10902. group.insertFirst(element);
  10903. } else {
  10904. element.insertAfter(Ext.fly(childNodes[position - 1]));
  10905. }
  10906. element.cache = {};
  10907. return element;
  10908. },
  10909. /**
  10910. * @private
  10911. * Applies attributes to the given element.
  10912. * @param {Ext.dom.Element} element The DOM element to be applied.
  10913. * @param {Object} attributes The attributes to apply to the element.
  10914. */
  10915. setElementAttributes: function(element, attributes) {
  10916. var dom = element.dom,
  10917. cache = element.cache,
  10918. name, value;
  10919. for (name in attributes) {
  10920. value = attributes[name];
  10921. if (cache[name] !== value) {
  10922. cache[name] = value;
  10923. dom.setAttribute(name, value);
  10924. }
  10925. }
  10926. },
  10927. /**
  10928. * @private
  10929. * Gets the next reference element under the SVG 'defs' tag.
  10930. * @param {String} tagName The type of reference element.
  10931. * @return {Ext.dom.Element} The reference element.
  10932. */
  10933. getNextDef: function(tagName) {
  10934. return this.getSvgElement(this.defsElement, tagName, this.defsPosition++);
  10935. },
  10936. /**
  10937. * @method clearTransform
  10938. * @inheritdoc
  10939. */
  10940. clearTransform: function() {
  10941. var me = this;
  10942. me.mainGroup.set({
  10943. transform: me.matrix.toSvg()
  10944. });
  10945. },
  10946. /**
  10947. * @method clear
  10948. * @inheritdoc
  10949. */
  10950. clear: function() {
  10951. this.ctx.clear();
  10952. this.removeSurplusDefs();
  10953. this.defsPosition = 0;
  10954. },
  10955. removeSurplusDefs: function() {
  10956. var defsElement = this.defsElement,
  10957. defs = defsElement.dom.childNodes,
  10958. ln = defs.length,
  10959. i;
  10960. for (i = ln - 1; i > this.defsPosition; i--) {
  10961. defsElement.removeChild(defs[i]);
  10962. }
  10963. },
  10964. /**
  10965. * @method renderSprite
  10966. * @inheritdoc
  10967. */
  10968. renderSprite: function(sprite) {
  10969. var me = this,
  10970. rect = me.getRect(),
  10971. ctx = me.ctx;
  10972. // This check is simplistic, but should result in a better performance
  10973. // compared to !sprite.isVisible() when most surface sprites are visible.
  10974. if (sprite.attr.hidden || sprite.attr.globalAlpha === 0) {
  10975. // Create an empty group for each hidden sprite,
  10976. // so that when these sprites do become visible,
  10977. // they don't need groups to be created and don't
  10978. // mess up the previous order of elements in the
  10979. // document, i.e. sprites rendered in the next
  10980. // frame reuse the same elements they used in the
  10981. // previous frame.
  10982. ctx.save();
  10983. ctx.restore();
  10984. return;
  10985. }
  10986. // Each sprite is rendered in its own group ('g' element),
  10987. // returned by the `ctx.save` method.
  10988. // Essentially, the group _is_ the sprite.
  10989. sprite.element = ctx.save();
  10990. sprite.preRender(this);
  10991. sprite.useAttributes(ctx, rect);
  10992. if (false === sprite.render(this, ctx, [
  10993. 0,
  10994. 0,
  10995. rect[2],
  10996. rect[3]
  10997. ])) {
  10998. return false;
  10999. }
  11000. sprite.setDirty(false);
  11001. ctx.restore();
  11002. },
  11003. /**
  11004. * @private
  11005. */
  11006. toSVG: function(size, surfaces) {
  11007. var className = Ext.getClassName(this),
  11008. svg, surface, rect, i;
  11009. svg = '<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg"' + ' width="' + size.width + '"' + ' height="' + size.height + '">';
  11010. for (i = 0; i < surfaces.length; i++) {
  11011. surface = surfaces[i];
  11012. if (Ext.getClassName(surface) !== className) {
  11013. continue;
  11014. }
  11015. rect = surface.getRect();
  11016. svg += '<g transform="translate(' + rect[0] + ',' + rect[1] + ')">';
  11017. svg += this.serializeNode(surface.svgElement.dom);
  11018. svg += '</g>';
  11019. }
  11020. svg += '</svg>';
  11021. return svg;
  11022. },
  11023. b64EncodeUnicode: function(str) {
  11024. // Since DOMStrings are 16-bit-encoded strings, in most browsers calling window.btoa
  11025. // on a Unicode string will cause a Character Out Of Range exception if a character
  11026. // exceeds the range of a 8-bit ASCII-encoded character. More information:
  11027. // https://developer.mozilla.org/en/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
  11028. return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
  11029. return String.fromCharCode('0x' + p1);
  11030. }));
  11031. },
  11032. flatten: function(size, surfaces) {
  11033. var svg = '<?xml version="1.0" standalone="yes"?>';
  11034. svg += this.toSVG(size, surfaces);
  11035. return {
  11036. data: 'data:image/svg+xml;base64,' + this.b64EncodeUnicode(svg),
  11037. type: 'svg'
  11038. };
  11039. },
  11040. /**
  11041. * @private
  11042. * Serializes an SVG DOM element and its children recursively into a string.
  11043. * @param {Object} node DOM element to serialize.
  11044. * @return {String}
  11045. */
  11046. serializeNode: function(node) {
  11047. var result = '',
  11048. i, n, attr, child;
  11049. if (node.nodeType === document.TEXT_NODE) {
  11050. return node.nodeValue;
  11051. }
  11052. result += '<' + node.nodeName;
  11053. if (node.attributes.length) {
  11054. for (i = 0 , n = node.attributes.length; i < n; i++) {
  11055. attr = node.attributes[i];
  11056. result += ' ' + attr.name + '="' + Ext.String.htmlEncode(attr.value) + '"';
  11057. }
  11058. }
  11059. result += '>';
  11060. if (node.childNodes && node.childNodes.length) {
  11061. for (i = 0 , n = node.childNodes.length; i < n; i++) {
  11062. child = node.childNodes[i];
  11063. result += this.serializeNode(child);
  11064. }
  11065. }
  11066. result += '</' + node.nodeName + '>';
  11067. return result;
  11068. },
  11069. /**
  11070. * Destroys the Canvas element and prepares it for Garbage Collection.
  11071. */
  11072. destroy: function() {
  11073. var me = this;
  11074. me.ctx.destroy();
  11075. me.mainGroup.destroy();
  11076. me.defsElement.destroy();
  11077. delete me.mainGroup;
  11078. delete me.defsElement;
  11079. delete me.ctx;
  11080. me.callParent();
  11081. },
  11082. remove: function(sprite, destroySprite) {
  11083. if (sprite && sprite.element) {
  11084. // If sprite has an associated SVG element, remove it from the surface.
  11085. sprite.element.destroy();
  11086. sprite.element = null;
  11087. }
  11088. this.callParent(arguments);
  11089. }
  11090. });
  11091. // @define Ext.draw.engine.excanvas
  11092. /**
  11093. * @private
  11094. */
  11095. if (!Ext.draw) {
  11096. Ext.draw = {};
  11097. }
  11098. if (!Ext.draw.engine) {
  11099. Ext.draw.engine = {};
  11100. }
  11101. Ext.draw.engine.excanvas = true;
  11102. // Copyright 2006 Google Inc.
  11103. //
  11104. // Licensed under the Apache License, Version 2.0 (the "License");
  11105. // you may not use this file except in compliance with the License.
  11106. // You may obtain a copy of the License at
  11107. //
  11108. // http://www.apache.org/licenses/LICENSE-2.0
  11109. //
  11110. // Unless required by applicable law or agreed to in writing, software
  11111. // distributed under the License is distributed on an "AS IS" BASIS,
  11112. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11113. // See the License for the specific language governing permissions and
  11114. // limitations under the License.
  11115. // Known Issues:
  11116. //
  11117. // * Patterns only support repeat.
  11118. // * Radial gradient are not implemented. The VML version of these look very
  11119. // different from the canvas one.
  11120. // * Clipping paths are not implemented.
  11121. // * Coordsize. The width and height attribute have higher priority than the
  11122. // width and height style values which isn't correct.
  11123. // * Painting mode isn't implemented.
  11124. // * Canvas width/height should is using content-box by default. IE in
  11125. // Quirks mode will draw the canvas using border-box. Either change your
  11126. // doctype to HTML5
  11127. // (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
  11128. // or use Box Sizing Behavior from WebFX
  11129. // (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
  11130. // * Non uniform scaling does not correctly scale strokes.
  11131. // * Optimize. There is always room for speed improvements.
  11132. /* eslint-disable */
  11133. // Only add this code if we do not already have a canvas implementation
  11134. if (!document.createElement('canvas').getContext) {
  11135. (function() {
  11136. // alias some functions to make (compiled) code shorter
  11137. var m = Math;
  11138. var mr = m.round;
  11139. var ms = m.sin;
  11140. var mc = m.cos;
  11141. var abs = m.abs;
  11142. var sqrt = m.sqrt;
  11143. // this is used for sub pixel precision
  11144. var Z = 10;
  11145. var Z2 = Z / 2;
  11146. var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];
  11147. /*
  11148. * @method getContext
  11149. * This function is assigned to the <canvas></canvas> elements as element.getContext().
  11150. * @return {CanvasRenderingContext2D_}
  11151. */
  11152. function getContext() {
  11153. return this.context_ || (this.context_ = new CanvasRenderingContext2D_(this));
  11154. }
  11155. var slice = Array.prototype.slice;
  11156. /*
  11157. * @method bind
  11158. * Binds a function to an object. The returned function will always use the
  11159. * passed in {@code obj} as {@code this}.
  11160. *
  11161. * Example:
  11162. *
  11163. * g = bind(f, obj, a, b)
  11164. * g(c, d) // will do f.call(obj, a, b, c, d)
  11165. *
  11166. * @param {Function} f The function to bind the object to
  11167. * @param {Object} obj The object that should act as this when the function
  11168. * is called
  11169. * @param {*} var_args Rest arguments that will be used as the initial
  11170. * arguments when the function is called
  11171. * @return {Function} A new function that has bound this
  11172. */
  11173. function bind(f, obj, var_args) {
  11174. var a = slice.call(arguments, 2);
  11175. return function() {
  11176. return f.apply(obj, a.concat(slice.call(arguments)));
  11177. };
  11178. }
  11179. function encodeHtmlAttribute(s) {
  11180. return String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
  11181. }
  11182. function addNamespace(doc, prefix, urn) {
  11183. Ext.onReady(function() {
  11184. if (!doc.namespaces[prefix]) {
  11185. doc.namespaces.add(prefix, urn, '#default#VML');
  11186. }
  11187. });
  11188. }
  11189. function addNamespacesAndStylesheet(doc) {
  11190. addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml');
  11191. addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office');
  11192. // Setup default CSS. Only add one style sheet per document
  11193. if (!doc.styleSheets['ex_canvas_']) {
  11194. var ss = doc.createStyleSheet();
  11195. ss.owningElement.id = 'ex_canvas_';
  11196. ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + // default size is 300x150 in Gecko and Opera
  11197. 'text-align:left;width:300px;height:150px}';
  11198. }
  11199. }
  11200. // Add namespaces and stylesheet at startup.
  11201. addNamespacesAndStylesheet(document);
  11202. var G_vmlCanvasManager_ = {
  11203. init: function(opt_doc) {
  11204. var doc = opt_doc || document;
  11205. // Create a dummy element so that IE will allow canvas elements to be
  11206. // recognized.
  11207. doc.createElement('canvas');
  11208. doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
  11209. },
  11210. init_: function(doc) {
  11211. // find all canvas elements
  11212. var els = doc.getElementsByTagName('canvas');
  11213. for (var i = 0; i < els.length; i++) {
  11214. this.initElement(els[i]);
  11215. }
  11216. },
  11217. /*
  11218. * Public initializes a canvas element so that it can be used as canvas
  11219. * element from now on. This is called automatically before the page is
  11220. * loaded but if you are creating elements using createElement you need to
  11221. * make sure this is called on the element.
  11222. * @param {HTMLElement} el The canvas element to initialize.
  11223. * @return {HTMLElement} the element that was created.
  11224. */
  11225. initElement: function(el) {
  11226. if (!el.getContext) {
  11227. el.getContext = getContext;
  11228. // Add namespaces and stylesheet to document of the element.
  11229. addNamespacesAndStylesheet(el.ownerDocument);
  11230. // Remove fallback content. There is no way to hide text nodes so we
  11231. // just remove all childNodes. We could hide all elements and remove
  11232. // text nodes but who really cares about the fallback content.
  11233. el.innerHTML = '';
  11234. // do not use inline function because that will leak memory
  11235. el.attachEvent('onpropertychange', onPropertyChange);
  11236. el.attachEvent('onresize', onResize);
  11237. var attrs = el.attributes;
  11238. if (attrs.width && attrs.width.specified) {
  11239. // TODO: use runtimeStyle and coordsize
  11240. // el.getContext().setWidth_(attrs.width.nodeValue);
  11241. el.style.width = attrs.width.nodeValue + 'px';
  11242. } else {
  11243. el.width = el.clientWidth;
  11244. }
  11245. if (attrs.height && attrs.height.specified) {
  11246. // TODO: use runtimeStyle and coordsize
  11247. // el.getContext().setHeight_(attrs.height.nodeValue);
  11248. el.style.height = attrs.height.nodeValue + 'px';
  11249. } else {
  11250. el.height = el.clientHeight;
  11251. }
  11252. }
  11253. //el.getContext().setCoordsize_()
  11254. return el;
  11255. }
  11256. };
  11257. function onPropertyChange(e) {
  11258. var el = e.srcElement;
  11259. switch (e.propertyName) {
  11260. case 'width':
  11261. el.getContext().clearRect();
  11262. el.style.width = el.attributes.width.nodeValue + 'px';
  11263. // In IE8 this does not trigger onresize.
  11264. el.firstChild.style.width = el.clientWidth + 'px';
  11265. break;
  11266. case 'height':
  11267. el.getContext().clearRect();
  11268. el.style.height = el.attributes.height.nodeValue + 'px';
  11269. el.firstChild.style.height = el.clientHeight + 'px';
  11270. break;
  11271. }
  11272. }
  11273. function onResize(e) {
  11274. var el = e.srcElement;
  11275. if (el.firstChild) {
  11276. el.firstChild.style.width = el.clientWidth + 'px';
  11277. el.firstChild.style.height = el.clientHeight + 'px';
  11278. }
  11279. }
  11280. G_vmlCanvasManager_.init();
  11281. // precompute "00" to "FF"
  11282. var decToHex = [];
  11283. for (var i = 0; i < 16; i++) {
  11284. for (var j = 0; j < 16; j++) {
  11285. decToHex[i * 16 + j] = i.toString(16) + j.toString(16);
  11286. }
  11287. }
  11288. function createMatrixIdentity() {
  11289. return [
  11290. [
  11291. 1,
  11292. 0,
  11293. 0
  11294. ],
  11295. [
  11296. 0,
  11297. 1,
  11298. 0
  11299. ],
  11300. [
  11301. 0,
  11302. 0,
  11303. 1
  11304. ]
  11305. ];
  11306. }
  11307. function matrixMultiply(m1, m2) {
  11308. var result = createMatrixIdentity();
  11309. for (var x = 0; x < 3; x++) {
  11310. for (var y = 0; y < 3; y++) {
  11311. var sum = 0;
  11312. for (var z = 0; z < 3; z++) {
  11313. sum += m1[x][z] * m2[z][y];
  11314. }
  11315. result[x][y] = sum;
  11316. }
  11317. }
  11318. return result;
  11319. }
  11320. function copyState(o1, o2) {
  11321. o2.fillStyle = o1.fillStyle;
  11322. o2.lineCap = o1.lineCap;
  11323. o2.lineJoin = o1.lineJoin;
  11324. o2.lineDash = o1.lineDash;
  11325. o2.lineWidth = o1.lineWidth;
  11326. o2.miterLimit = o1.miterLimit;
  11327. o2.shadowBlur = o1.shadowBlur;
  11328. o2.shadowColor = o1.shadowColor;
  11329. o2.shadowOffsetX = o1.shadowOffsetX;
  11330. o2.shadowOffsetY = o1.shadowOffsetY;
  11331. o2.strokeStyle = o1.strokeStyle;
  11332. o2.globalAlpha = o1.globalAlpha;
  11333. o2.font = o1.font;
  11334. o2.textAlign = o1.textAlign;
  11335. o2.textBaseline = o1.textBaseline;
  11336. o2.arcScaleX_ = o1.arcScaleX_;
  11337. o2.arcScaleY_ = o1.arcScaleY_;
  11338. o2.lineScale_ = o1.lineScale_;
  11339. }
  11340. var colorData = {
  11341. aliceblue: '#F0F8FF',
  11342. antiquewhite: '#FAEBD7',
  11343. aquamarine: '#7FFFD4',
  11344. azure: '#F0FFFF',
  11345. beige: '#F5F5DC',
  11346. bisque: '#FFE4C4',
  11347. black: '#000000',
  11348. blanchedalmond: '#FFEBCD',
  11349. blueviolet: '#8A2BE2',
  11350. brown: '#A52A2A',
  11351. burlywood: '#DEB887',
  11352. cadetblue: '#5F9EA0',
  11353. chartreuse: '#7FFF00',
  11354. chocolate: '#D2691E',
  11355. coral: '#FF7F50',
  11356. cornflowerblue: '#6495ED',
  11357. cornsilk: '#FFF8DC',
  11358. crimson: '#DC143C',
  11359. cyan: '#00FFFF',
  11360. darkblue: '#00008B',
  11361. darkcyan: '#008B8B',
  11362. darkgoldenrod: '#B8860B',
  11363. darkgray: '#A9A9A9',
  11364. darkgreen: '#006400',
  11365. darkgrey: '#A9A9A9',
  11366. darkkhaki: '#BDB76B',
  11367. darkmagenta: '#8B008B',
  11368. darkolivegreen: '#556B2F',
  11369. darkorange: '#FF8C00',
  11370. darkorchid: '#9932CC',
  11371. darkred: '#8B0000',
  11372. darksalmon: '#E9967A',
  11373. darkseagreen: '#8FBC8F',
  11374. darkslateblue: '#483D8B',
  11375. darkslategray: '#2F4F4F',
  11376. darkslategrey: '#2F4F4F',
  11377. darkturquoise: '#00CED1',
  11378. darkviolet: '#9400D3',
  11379. deeppink: '#FF1493',
  11380. deepskyblue: '#00BFFF',
  11381. dimgray: '#696969',
  11382. dimgrey: '#696969',
  11383. dodgerblue: '#1E90FF',
  11384. firebrick: '#B22222',
  11385. floralwhite: '#FFFAF0',
  11386. forestgreen: '#228B22',
  11387. gainsboro: '#DCDCDC',
  11388. ghostwhite: '#F8F8FF',
  11389. gold: '#FFD700',
  11390. goldenrod: '#DAA520',
  11391. grey: '#808080',
  11392. greenyellow: '#ADFF2F',
  11393. honeydew: '#F0FFF0',
  11394. hotpink: '#FF69B4',
  11395. indianred: '#CD5C5C',
  11396. indigo: '#4B0082',
  11397. ivory: '#FFFFF0',
  11398. khaki: '#F0E68C',
  11399. lavender: '#E6E6FA',
  11400. lavenderblush: '#FFF0F5',
  11401. lawngreen: '#7CFC00',
  11402. lemonchiffon: '#FFFACD',
  11403. lightblue: '#ADD8E6',
  11404. lightcoral: '#F08080',
  11405. lightcyan: '#E0FFFF',
  11406. lightgoldenrodyellow: '#FAFAD2',
  11407. lightgreen: '#90EE90',
  11408. lightgrey: '#D3D3D3',
  11409. lightpink: '#FFB6C1',
  11410. lightsalmon: '#FFA07A',
  11411. lightseagreen: '#20B2AA',
  11412. lightskyblue: '#87CEFA',
  11413. lightslategray: '#778899',
  11414. lightslategrey: '#778899',
  11415. lightsteelblue: '#B0C4DE',
  11416. lightyellow: '#FFFFE0',
  11417. limegreen: '#32CD32',
  11418. linen: '#FAF0E6',
  11419. magenta: '#FF00FF',
  11420. mediumaquamarine: '#66CDAA',
  11421. mediumblue: '#0000CD',
  11422. mediumorchid: '#BA55D3',
  11423. mediumpurple: '#9370DB',
  11424. mediumseagreen: '#3CB371',
  11425. mediumslateblue: '#7B68EE',
  11426. mediumspringgreen: '#00FA9A',
  11427. mediumturquoise: '#48D1CC',
  11428. mediumvioletred: '#C71585',
  11429. midnightblue: '#191970',
  11430. mintcream: '#F5FFFA',
  11431. mistyrose: '#FFE4E1',
  11432. moccasin: '#FFE4B5',
  11433. navajowhite: '#FFDEAD',
  11434. oldlace: '#FDF5E6',
  11435. olivedrab: '#6B8E23',
  11436. orange: '#FFA500',
  11437. orangered: '#FF4500',
  11438. orchid: '#DA70D6',
  11439. palegoldenrod: '#EEE8AA',
  11440. palegreen: '#98FB98',
  11441. paleturquoise: '#AFEEEE',
  11442. palevioletred: '#DB7093',
  11443. papayawhip: '#FFEFD5',
  11444. peachpuff: '#FFDAB9',
  11445. peru: '#CD853F',
  11446. pink: '#FFC0CB',
  11447. plum: '#DDA0DD',
  11448. powderblue: '#B0E0E6',
  11449. rosybrown: '#BC8F8F',
  11450. royalblue: '#4169E1',
  11451. saddlebrown: '#8B4513',
  11452. salmon: '#FA8072',
  11453. sandybrown: '#F4A460',
  11454. seagreen: '#2E8B57',
  11455. seashell: '#FFF5EE',
  11456. sienna: '#A0522D',
  11457. skyblue: '#87CEEB',
  11458. slateblue: '#6A5ACD',
  11459. slategray: '#708090',
  11460. slategrey: '#708090',
  11461. snow: '#FFFAFA',
  11462. springgreen: '#00FF7F',
  11463. steelblue: '#4682B4',
  11464. tan: '#D2B48C',
  11465. thistle: '#D8BFD8',
  11466. tomato: '#FF6347',
  11467. turquoise: '#40E0D0',
  11468. violet: '#EE82EE',
  11469. wheat: '#F5DEB3',
  11470. whitesmoke: '#F5F5F5',
  11471. yellowgreen: '#9ACD32'
  11472. };
  11473. function getRgbHslContent(styleString) {
  11474. var start = styleString.indexOf('(', 3);
  11475. var end = styleString.indexOf(')', start + 1);
  11476. var parts = styleString.substring(start + 1, end).split(',');
  11477. // add alpha if needed
  11478. if (parts.length != 4 || styleString.charAt(3) != 'a') {
  11479. parts[3] = 1;
  11480. }
  11481. return parts;
  11482. }
  11483. function percent(s) {
  11484. return parseFloat(s) / 100;
  11485. }
  11486. function clamp(v, min, max) {
  11487. return Math.min(max, Math.max(min, v));
  11488. }
  11489. function hslToRgb(parts) {
  11490. var r, g, b, h, s, l;
  11491. h = parseFloat(parts[0]) / 360 % 360;
  11492. if (h < 0) {
  11493. h++;
  11494. }
  11495. s = clamp(percent(parts[1]), 0, 1);
  11496. l = clamp(percent(parts[2]), 0, 1);
  11497. if (s == 0) {
  11498. r = g = b = l;
  11499. } else // achromatic
  11500. {
  11501. var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  11502. var p = 2 * l - q;
  11503. r = hueToRgb(p, q, h + 1 / 3);
  11504. g = hueToRgb(p, q, h);
  11505. b = hueToRgb(p, q, h - 1 / 3);
  11506. }
  11507. return '#' + decToHex[Math.floor(r * 255)] + decToHex[Math.floor(g * 255)] + decToHex[Math.floor(b * 255)];
  11508. }
  11509. function hueToRgb(m1, m2, h) {
  11510. if (h < 0) {
  11511. h++;
  11512. }
  11513. if (h > 1) {
  11514. h--;
  11515. }
  11516. if (6 * h < 1) {
  11517. return m1 + (m2 - m1) * 6 * h;
  11518. }
  11519. else if (2 * h < 1) {
  11520. return m2;
  11521. }
  11522. else if (3 * h < 2) {
  11523. return m1 + (m2 - m1) * (2 / 3 - h) * 6;
  11524. }
  11525. else {
  11526. return m1;
  11527. }
  11528. }
  11529. var processStyleCache = {};
  11530. function processStyle(styleString) {
  11531. if (styleString in processStyleCache) {
  11532. return processStyleCache[styleString];
  11533. }
  11534. var str,
  11535. alpha = 1;
  11536. styleString = String(styleString);
  11537. if (styleString.charAt(0) == '#') {
  11538. str = styleString;
  11539. } else if (/^rgb/.test(styleString)) {
  11540. var parts = getRgbHslContent(styleString);
  11541. var str = '#',
  11542. n;
  11543. for (var i = 0; i < 3; i++) {
  11544. if (parts[i].indexOf('%') != -1) {
  11545. n = Math.floor(percent(parts[i]) * 255);
  11546. } else {
  11547. n = +parts[i];
  11548. }
  11549. str += decToHex[clamp(n, 0, 255)];
  11550. }
  11551. alpha = +parts[3];
  11552. } else if (/^hsl/.test(styleString)) {
  11553. var parts = getRgbHslContent(styleString);
  11554. str = hslToRgb(parts);
  11555. alpha = parts[3];
  11556. } else {
  11557. str = colorData[styleString] || styleString;
  11558. }
  11559. return processStyleCache[styleString] = {
  11560. color: str,
  11561. alpha: alpha
  11562. };
  11563. }
  11564. var DEFAULT_STYLE = {
  11565. style: 'normal',
  11566. variant: 'normal',
  11567. weight: 'normal',
  11568. size: 10,
  11569. family: 'sans-serif'
  11570. };
  11571. // Internal text style cache
  11572. var fontStyleCache = {};
  11573. function processFontStyle(styleString) {
  11574. if (fontStyleCache[styleString]) {
  11575. return fontStyleCache[styleString];
  11576. }
  11577. var el = document.createElement('div');
  11578. var style = el.style;
  11579. try {
  11580. style.font = styleString;
  11581. } catch (ex) {}
  11582. // Ignore failures to set to invalid font.
  11583. return fontStyleCache[styleString] = {
  11584. style: style.fontStyle || DEFAULT_STYLE.style,
  11585. variant: style.fontVariant || DEFAULT_STYLE.variant,
  11586. weight: style.fontWeight || DEFAULT_STYLE.weight,
  11587. size: style.fontSize || DEFAULT_STYLE.size,
  11588. family: style.fontFamily || DEFAULT_STYLE.family
  11589. };
  11590. }
  11591. function getComputedStyle(style, element) {
  11592. var computedStyle = {};
  11593. for (var p in style) {
  11594. computedStyle[p] = style[p];
  11595. }
  11596. // Compute the size
  11597. var canvasFontSize = parseFloat(element.currentStyle.fontSize),
  11598. fontSize = parseFloat(style.size);
  11599. if (typeof style.size == 'number') {
  11600. computedStyle.size = style.size;
  11601. } else if (style.size.indexOf('px') != -1) {
  11602. computedStyle.size = fontSize;
  11603. } else if (style.size.indexOf('em') != -1) {
  11604. computedStyle.size = canvasFontSize * fontSize;
  11605. } else if (style.size.indexOf('%') != -1) {
  11606. computedStyle.size = (canvasFontSize / 100) * fontSize;
  11607. } else if (style.size.indexOf('pt') != -1) {
  11608. computedStyle.size = fontSize / 0.75;
  11609. } else {
  11610. computedStyle.size = canvasFontSize;
  11611. }
  11612. // Different scaling between normal text and VML text. This was found using
  11613. // trial and error to get the same size as non VML text.
  11614. computedStyle.size *= 0.981;
  11615. return computedStyle;
  11616. }
  11617. function buildStyle(style) {
  11618. return style.style + ' ' + style.variant + ' ' + style.weight + ' ' + style.size + 'px ' + style.family;
  11619. }
  11620. var lineCapMap = {
  11621. 'butt': 'flat',
  11622. 'round': 'round'
  11623. };
  11624. function processLineCap(lineCap) {
  11625. return lineCapMap[lineCap] || 'square';
  11626. }
  11627. /*
  11628. * This class implements CanvasRenderingContext2D interface as described by
  11629. * the WHATWG.
  11630. * @param {HTMLElement} canvasElement The element that the 2D context should
  11631. * be associated with
  11632. * @private
  11633. */
  11634. function CanvasRenderingContext2D_(canvasElement) {
  11635. this.m_ = createMatrixIdentity();
  11636. this.mStack_ = [];
  11637. this.aStack_ = [];
  11638. this.currentPath_ = [];
  11639. // Canvas context properties
  11640. this.strokeStyle = '#000';
  11641. this.fillStyle = '#000';
  11642. this.lineWidth = 1;
  11643. this.lineJoin = 'miter';
  11644. this.lineDash = [];
  11645. this.lineCap = 'butt';
  11646. this.miterLimit = Z * 1;
  11647. this.globalAlpha = 1;
  11648. this.font = '10px sans-serif';
  11649. this.textAlign = 'left';
  11650. this.textBaseline = 'alphabetic';
  11651. this.canvas = canvasElement;
  11652. var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' + canvasElement.clientHeight + 'px;overflow:hidden;position:absolute';
  11653. var el = canvasElement.ownerDocument.createElement('div');
  11654. el.style.cssText = cssText;
  11655. canvasElement.appendChild(el);
  11656. var overlayEl = el.cloneNode(false);
  11657. // Use a non transparent background.
  11658. overlayEl.style.backgroundColor = 'red';
  11659. overlayEl.style.filter = 'alpha(opacity=0)';
  11660. canvasElement.appendChild(overlayEl);
  11661. this.element_ = el;
  11662. this.arcScaleX_ = 1;
  11663. this.arcScaleY_ = 1;
  11664. this.lineScale_ = 1;
  11665. }
  11666. var contextPrototype = CanvasRenderingContext2D_.prototype;
  11667. contextPrototype.clearRect = function() {
  11668. if (this.textMeasureEl_) {
  11669. this.textMeasureEl_.removeNode(true);
  11670. this.textMeasureEl_ = null;
  11671. }
  11672. this.element_.innerHTML = '';
  11673. };
  11674. contextPrototype.beginPath = function() {
  11675. // TODO: Branch current matrix so that save/restore has no effect
  11676. // as per safari docs.
  11677. this.currentPath_ = [];
  11678. };
  11679. contextPrototype.moveTo = function(aX, aY) {
  11680. var p = getCoords(this, aX, aY);
  11681. this.currentPath_.push({
  11682. type: 'moveTo',
  11683. x: p.x,
  11684. y: p.y
  11685. });
  11686. this.currentX_ = p.x;
  11687. this.currentY_ = p.y;
  11688. };
  11689. contextPrototype.lineTo = function(aX, aY) {
  11690. var p = getCoords(this, aX, aY);
  11691. this.currentPath_.push({
  11692. type: 'lineTo',
  11693. x: p.x,
  11694. y: p.y
  11695. });
  11696. this.currentX_ = p.x;
  11697. this.currentY_ = p.y;
  11698. };
  11699. contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) {
  11700. var p = getCoords(this, aX, aY);
  11701. var cp1 = getCoords(this, aCP1x, aCP1y);
  11702. var cp2 = getCoords(this, aCP2x, aCP2y);
  11703. bezierCurveTo(this, cp1, cp2, p);
  11704. };
  11705. // Helper function that takes the already fixed cordinates.
  11706. function bezierCurveTo(self, cp1, cp2, p) {
  11707. self.currentPath_.push({
  11708. type: 'bezierCurveTo',
  11709. cp1x: cp1.x,
  11710. cp1y: cp1.y,
  11711. cp2x: cp2.x,
  11712. cp2y: cp2.y,
  11713. x: p.x,
  11714. y: p.y
  11715. });
  11716. self.currentX_ = p.x;
  11717. self.currentY_ = p.y;
  11718. }
  11719. contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
  11720. // the following is lifted almost directly from
  11721. // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
  11722. var cp = getCoords(this, aCPx, aCPy);
  11723. var p = getCoords(this, aX, aY);
  11724. var cp1 = {
  11725. x: this.currentX_ + 2 / 3 * (cp.x - this.currentX_),
  11726. y: this.currentY_ + 2 / 3 * (cp.y - this.currentY_)
  11727. };
  11728. var cp2 = {
  11729. x: cp1.x + (p.x - this.currentX_) / 3,
  11730. y: cp1.y + (p.y - this.currentY_) / 3
  11731. };
  11732. bezierCurveTo(this, cp1, cp2, p);
  11733. };
  11734. contextPrototype.arc = function(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) {
  11735. aRadius *= Z;
  11736. var arcType = aClockwise ? 'at' : 'wa';
  11737. var xStart = aX + mc(aStartAngle) * aRadius - Z2;
  11738. var yStart = aY + ms(aStartAngle) * aRadius - Z2;
  11739. var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
  11740. var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
  11741. // IE won't render arches drawn counter clockwise if xStart == xEnd.
  11742. if (xStart == xEnd && !aClockwise) {
  11743. xStart += 0.125;
  11744. }
  11745. // Offset xStart by 1/80 of a pixel. Use something
  11746. // that can be represented in binary
  11747. var p = getCoords(this, aX, aY);
  11748. var pStart = getCoords(this, xStart, yStart);
  11749. var pEnd = getCoords(this, xEnd, yEnd);
  11750. this.currentPath_.push({
  11751. type: arcType,
  11752. x: p.x,
  11753. y: p.y,
  11754. radius: aRadius,
  11755. xStart: pStart.x,
  11756. yStart: pStart.y,
  11757. xEnd: pEnd.x,
  11758. yEnd: pEnd.y
  11759. });
  11760. };
  11761. contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
  11762. this.moveTo(aX, aY);
  11763. this.lineTo(aX + aWidth, aY);
  11764. this.lineTo(aX + aWidth, aY + aHeight);
  11765. this.lineTo(aX, aY + aHeight);
  11766. this.closePath();
  11767. };
  11768. contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
  11769. var oldPath = this.currentPath_;
  11770. this.beginPath();
  11771. this.moveTo(aX, aY);
  11772. this.lineTo(aX + aWidth, aY);
  11773. this.lineTo(aX + aWidth, aY + aHeight);
  11774. this.lineTo(aX, aY + aHeight);
  11775. this.closePath();
  11776. this.stroke();
  11777. this.currentPath_ = oldPath;
  11778. };
  11779. contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
  11780. var oldPath = this.currentPath_;
  11781. this.beginPath();
  11782. this.moveTo(aX, aY);
  11783. this.lineTo(aX + aWidth, aY);
  11784. this.lineTo(aX + aWidth, aY + aHeight);
  11785. this.lineTo(aX, aY + aHeight);
  11786. this.closePath();
  11787. this.fill();
  11788. this.currentPath_ = oldPath;
  11789. };
  11790. contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
  11791. var gradient = new CanvasGradient_('gradient');
  11792. gradient.x0_ = aX0;
  11793. gradient.y0_ = aY0;
  11794. gradient.x1_ = aX1;
  11795. gradient.y1_ = aY1;
  11796. return gradient;
  11797. };
  11798. contextPrototype.createRadialGradient = function(aX0, aY0, aR0, aX1, aY1, aR1) {
  11799. var gradient = new CanvasGradient_('gradientradial');
  11800. gradient.x0_ = aX0;
  11801. gradient.y0_ = aY0;
  11802. gradient.r0_ = aR0;
  11803. gradient.x1_ = aX1;
  11804. gradient.y1_ = aY1;
  11805. gradient.r1_ = aR1;
  11806. return gradient;
  11807. };
  11808. contextPrototype.drawImage = function(image, var_args) {
  11809. var dx, dy, dw, dh, sx, sy, sw, sh;
  11810. // to find the original width we overide the width and height
  11811. var oldRuntimeWidth = image.runtimeStyle.width;
  11812. var oldRuntimeHeight = image.runtimeStyle.height;
  11813. image.runtimeStyle.width = 'auto';
  11814. image.runtimeStyle.height = 'auto';
  11815. // get the original size
  11816. var w = image.width;
  11817. var h = image.height;
  11818. // and remove overides
  11819. image.runtimeStyle.width = oldRuntimeWidth;
  11820. image.runtimeStyle.height = oldRuntimeHeight;
  11821. if (arguments.length == 3) {
  11822. dx = arguments[1];
  11823. dy = arguments[2];
  11824. sx = sy = 0;
  11825. sw = dw = w;
  11826. sh = dh = h;
  11827. } else if (arguments.length == 5) {
  11828. dx = arguments[1];
  11829. dy = arguments[2];
  11830. dw = arguments[3];
  11831. dh = arguments[4];
  11832. sx = sy = 0;
  11833. sw = w;
  11834. sh = h;
  11835. } else if (arguments.length == 9) {
  11836. sx = arguments[1];
  11837. sy = arguments[2];
  11838. sw = arguments[3];
  11839. sh = arguments[4];
  11840. dx = arguments[5];
  11841. dy = arguments[6];
  11842. dw = arguments[7];
  11843. dh = arguments[8];
  11844. } else {
  11845. throw Error('Invalid number of arguments');
  11846. }
  11847. var d = getCoords(this, dx, dy);
  11848. var vmlStr = [];
  11849. var W = 10;
  11850. var H = 10;
  11851. var m = this.m_;
  11852. 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), ';');
  11853. 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>');
  11854. this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
  11855. };
  11856. contextPrototype.setLineDash = function(lineDash) {
  11857. if (lineDash.length === 1) {
  11858. lineDash = lineDash.slice();
  11859. lineDash[1] = lineDash[0];
  11860. }
  11861. this.lineDash = lineDash;
  11862. };
  11863. contextPrototype.getLineDash = function() {
  11864. return this.lineDash;
  11865. };
  11866. contextPrototype.stroke = function(aFill) {
  11867. var lineStr = [];
  11868. var W = 10;
  11869. var H = 10;
  11870. 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="');
  11871. var min = {
  11872. x: null,
  11873. y: null
  11874. };
  11875. var max = {
  11876. x: null,
  11877. y: null
  11878. };
  11879. for (var i = 0; i < this.currentPath_.length; i++) {
  11880. var p = this.currentPath_[i];
  11881. var c;
  11882. switch (p.type) {
  11883. case 'moveTo':
  11884. c = p;
  11885. lineStr.push(' m ', mr(p.x), ',', mr(p.y));
  11886. break;
  11887. case 'lineTo':
  11888. lineStr.push(' l ', mr(p.x), ',', mr(p.y));
  11889. break;
  11890. case 'close':
  11891. lineStr.push(' x ');
  11892. p = null;
  11893. break;
  11894. case 'bezierCurveTo':
  11895. lineStr.push(' c ', mr(p.cp1x), ',', mr(p.cp1y), ',', mr(p.cp2x), ',', mr(p.cp2y), ',', mr(p.x), ',', mr(p.y));
  11896. break;
  11897. case 'at':
  11898. case 'wa':
  11899. 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));
  11900. break;
  11901. }
  11902. // TODO: Following is broken for curves due to
  11903. // move to proper paths.
  11904. // Figure out dimensions so we can do gradient fills
  11905. // properly
  11906. if (p) {
  11907. if (min.x == null || p.x < min.x) {
  11908. min.x = p.x;
  11909. }
  11910. if (max.x == null || p.x > max.x) {
  11911. max.x = p.x;
  11912. }
  11913. if (min.y == null || p.y < min.y) {
  11914. min.y = p.y;
  11915. }
  11916. if (max.y == null || p.y > max.y) {
  11917. max.y = p.y;
  11918. }
  11919. }
  11920. }
  11921. lineStr.push(' ">');
  11922. if (!aFill) {
  11923. appendStroke(this, lineStr);
  11924. } else {
  11925. appendFill(this, lineStr, min, max);
  11926. }
  11927. lineStr.push('</g_vml_:shape>');
  11928. this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
  11929. };
  11930. function appendStroke(ctx, lineStr) {
  11931. var a = processStyle(ctx.strokeStyle);
  11932. var color = a.color;
  11933. var opacity = a.alpha * ctx.globalAlpha;
  11934. var lineWidth = ctx.lineScale_ * ctx.lineWidth;
  11935. // VML cannot correctly render a line if the width is less than 1px.
  11936. // In that case, we dilute the color to make the line look thinner.
  11937. if (lineWidth < 1) {
  11938. opacity *= lineWidth;
  11939. }
  11940. 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, '" />');
  11941. }
  11942. function appendFill(ctx, lineStr, min, max) {
  11943. var fillStyle = ctx.fillStyle;
  11944. var arcScaleX = ctx.arcScaleX_;
  11945. var arcScaleY = ctx.arcScaleY_;
  11946. var width = max.x - min.x;
  11947. var height = max.y - min.y;
  11948. if (fillStyle instanceof CanvasGradient_) {
  11949. // TODO: Gradients transformed with the transformation matrix.
  11950. var angle = 0;
  11951. var focus = {
  11952. x: 0,
  11953. y: 0
  11954. };
  11955. // additional offset
  11956. var shift = 0;
  11957. // scale factor for offset
  11958. var expansion = 1;
  11959. if (fillStyle.type_ == 'gradient') {
  11960. var x0 = fillStyle.x0_ / arcScaleX;
  11961. var y0 = fillStyle.y0_ / arcScaleY;
  11962. var x1 = fillStyle.x1_ / arcScaleX;
  11963. var y1 = fillStyle.y1_ / arcScaleY;
  11964. var p0 = getCoords(ctx, x0, y0);
  11965. var p1 = getCoords(ctx, x1, y1);
  11966. var dx = p1.x - p0.x;
  11967. var dy = p1.y - p0.y;
  11968. angle = Math.atan2(dx, dy) * 180 / Math.PI;
  11969. // The angle should be a non-negative number.
  11970. if (angle < 0) {
  11971. angle += 360;
  11972. }
  11973. // Very small angles produce an unexpected result because they are
  11974. // converted to a scientific notation string.
  11975. if (angle < 1.0E-6) {
  11976. angle = 0;
  11977. }
  11978. } else {
  11979. var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_);
  11980. focus = {
  11981. x: (p0.x - min.x) / width,
  11982. y: (p0.y - min.y) / height
  11983. };
  11984. width /= arcScaleX * Z;
  11985. height /= arcScaleY * Z;
  11986. var dimension = m.max(width, height);
  11987. shift = 2 * fillStyle.r0_ / dimension;
  11988. expansion = 2 * fillStyle.r1_ / dimension - shift;
  11989. }
  11990. // We need to sort the color stops in ascending order by offset,
  11991. // otherwise IE won't interpret it correctly.
  11992. var stops = fillStyle.colors_;
  11993. stops.sort(function(cs1, cs2) {
  11994. return cs1.offset - cs2.offset;
  11995. });
  11996. var length = stops.length;
  11997. var color1 = stops[0].color;
  11998. var color2 = stops[length - 1].color;
  11999. var opacity1 = stops[0].alpha * ctx.globalAlpha;
  12000. var opacity2 = stops[length - 1].alpha * ctx.globalAlpha;
  12001. var colors = [];
  12002. for (var i = 0; i < length; i++) {
  12003. var stop = stops[i];
  12004. colors.push(stop.offset * expansion + shift + ' ' + stop.color);
  12005. }
  12006. // When colors attribute is used, the meanings of opacity and o:opacity2
  12007. // are reversed.
  12008. 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, '" />');
  12009. } else if (fillStyle instanceof CanvasPattern_) {
  12010. if (width && height) {
  12011. var deltaLeft = -min.x;
  12012. var deltaTop = -min.y;
  12013. 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.
  12014. //' size="', w, 'px ', h, 'px"',
  12015. ' src="', fillStyle.src_, '" />');
  12016. }
  12017. } else {
  12018. var a = processStyle(ctx.fillStyle);
  12019. var color = a.color;
  12020. var opacity = a.alpha * ctx.globalAlpha;
  12021. lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />');
  12022. }
  12023. }
  12024. contextPrototype.fill = function() {
  12025. // Calling `$stroke` here because otherwise we'd call not the native ctx.stroke,
  12026. // but our override from Ext.draw.engine.Canvas.statics.contextOverrides.
  12027. this.$stroke(true);
  12028. };
  12029. contextPrototype.closePath = function() {
  12030. this.currentPath_.push({
  12031. type: 'close'
  12032. });
  12033. };
  12034. function getCoords(ctx, aX, aY) {
  12035. var m = ctx.m_;
  12036. return {
  12037. x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
  12038. y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
  12039. };
  12040. }
  12041. contextPrototype.save = function() {
  12042. var o = {};
  12043. copyState(this, o);
  12044. this.aStack_.push(o);
  12045. this.mStack_.push(this.m_);
  12046. };
  12047. contextPrototype.restore = function() {
  12048. if (this.aStack_.length) {
  12049. copyState(this.aStack_.pop(), this);
  12050. this.m_ = this.mStack_.pop();
  12051. }
  12052. };
  12053. function matrixIsFinite(m) {
  12054. 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]);
  12055. }
  12056. function setM(ctx, m, updateLineScale) {
  12057. if (!matrixIsFinite(m)) {
  12058. return;
  12059. }
  12060. ctx.m_ = m;
  12061. if (updateLineScale) {
  12062. // Get the line scale.
  12063. // Determinant of this.m_ means how much the area is enlarged by the
  12064. // transformation. So its square root can be used as a scale factor
  12065. // for width.
  12066. var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
  12067. ctx.lineScale_ = sqrt(abs(det));
  12068. }
  12069. }
  12070. contextPrototype.translate = function(aX, aY) {
  12071. var m1 = [
  12072. [
  12073. 1,
  12074. 0,
  12075. 0
  12076. ],
  12077. [
  12078. 0,
  12079. 1,
  12080. 0
  12081. ],
  12082. [
  12083. aX,
  12084. aY,
  12085. 1
  12086. ]
  12087. ];
  12088. setM(this, matrixMultiply(m1, this.m_), false);
  12089. };
  12090. contextPrototype.rotate = function(aRot) {
  12091. var c = mc(aRot);
  12092. var s = ms(aRot);
  12093. var m1 = [
  12094. [
  12095. c,
  12096. s,
  12097. 0
  12098. ],
  12099. [
  12100. -s,
  12101. c,
  12102. 0
  12103. ],
  12104. [
  12105. 0,
  12106. 0,
  12107. 1
  12108. ]
  12109. ];
  12110. setM(this, matrixMultiply(m1, this.m_), false);
  12111. };
  12112. contextPrototype.scale = function(aX, aY) {
  12113. this.arcScaleX_ *= aX;
  12114. this.arcScaleY_ *= aY;
  12115. var m1 = [
  12116. [
  12117. aX,
  12118. 0,
  12119. 0
  12120. ],
  12121. [
  12122. 0,
  12123. aY,
  12124. 0
  12125. ],
  12126. [
  12127. 0,
  12128. 0,
  12129. 1
  12130. ]
  12131. ];
  12132. setM(this, matrixMultiply(m1, this.m_), true);
  12133. };
  12134. contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
  12135. var m1 = [
  12136. [
  12137. m11,
  12138. m12,
  12139. 0
  12140. ],
  12141. [
  12142. m21,
  12143. m22,
  12144. 0
  12145. ],
  12146. [
  12147. dx,
  12148. dy,
  12149. 1
  12150. ]
  12151. ];
  12152. setM(this, matrixMultiply(m1, this.m_), true);
  12153. };
  12154. contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
  12155. var m = [
  12156. [
  12157. m11,
  12158. m12,
  12159. 0
  12160. ],
  12161. [
  12162. m21,
  12163. m22,
  12164. 0
  12165. ],
  12166. [
  12167. dx,
  12168. dy,
  12169. 1
  12170. ]
  12171. ];
  12172. setM(this, m, true);
  12173. };
  12174. /*
  12175. * The text drawing function.
  12176. * The maxWidth argument isn't taken in account, since no browser supports
  12177. * it yet.
  12178. */
  12179. contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
  12180. var m = this.m_,
  12181. delta = 1000,
  12182. left = 0,
  12183. right = delta,
  12184. offset = {
  12185. x: 0,
  12186. y: 0
  12187. },
  12188. lineStr = [];
  12189. var fontStyle = getComputedStyle(processFontStyle(this.font), this.element_);
  12190. var fontStyleString = buildStyle(fontStyle);
  12191. var elementStyle = this.element_.currentStyle;
  12192. var textAlign = this.textAlign.toLowerCase();
  12193. switch (textAlign) {
  12194. case 'left':
  12195. case 'center':
  12196. case 'right':
  12197. break;
  12198. case 'end':
  12199. textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
  12200. break;
  12201. case 'start':
  12202. textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
  12203. break;
  12204. default:
  12205. textAlign = 'left';
  12206. }
  12207. // 1.75 is an arbitrary number, as there is no info about the text baseline
  12208. switch (this.textBaseline) {
  12209. case 'hanging':
  12210. case 'top':
  12211. offset.y = fontStyle.size / 1.75;
  12212. break;
  12213. case 'middle':
  12214. break;
  12215. default:
  12216. case null:
  12217. case 'alphabetic':
  12218. case 'ideographic':
  12219. case 'bottom':
  12220. offset.y = -fontStyle.size / 3;
  12221. break;
  12222. }
  12223. switch (textAlign) {
  12224. case 'right':
  12225. left = delta;
  12226. right = 0.05;
  12227. break;
  12228. case 'center':
  12229. left = right = delta / 2;
  12230. break;
  12231. }
  12232. var d = getCoords(this, x + offset.x, y + offset.y);
  12233. 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;">');
  12234. if (stroke) {
  12235. appendStroke(this, lineStr);
  12236. } else {
  12237. // TODO: Fix the min and max params.
  12238. appendFill(this, lineStr, {
  12239. x: -left,
  12240. y: 0
  12241. }, {
  12242. x: right,
  12243. y: fontStyle.size
  12244. });
  12245. }
  12246. var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' + m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0';
  12247. var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z);
  12248. 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>');
  12249. this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
  12250. };
  12251. contextPrototype.fillText = function(text, x, y, maxWidth) {
  12252. this.drawText_(text, x, y, maxWidth, false);
  12253. };
  12254. contextPrototype.strokeText = function(text, x, y, maxWidth) {
  12255. this.drawText_(text, x, y, maxWidth, true);
  12256. };
  12257. contextPrototype.measureText = function(text) {
  12258. if (!this.textMeasureEl_) {
  12259. var s = '<span style="position:absolute;' + 'top:-20000px;left:0;padding:0;margin:0;border:none;' + 'white-space:pre;"></span>';
  12260. this.element_.insertAdjacentHTML('beforeEnd', s);
  12261. this.textMeasureEl_ = this.element_.lastChild;
  12262. }
  12263. var doc = this.element_.ownerDocument;
  12264. this.textMeasureEl_.innerHTML = '';
  12265. this.textMeasureEl_.style.font = this.font;
  12266. // Don't use innerHTML or innerText because they allow markup/whitespace.
  12267. this.textMeasureEl_.appendChild(doc.createTextNode(text));
  12268. return {
  12269. width: this.textMeasureEl_.offsetWidth
  12270. };
  12271. };
  12272. /* STUBS */
  12273. contextPrototype.clip = function() {};
  12274. // TODO: Implement
  12275. contextPrototype.arcTo = function() {};
  12276. // TODO: Implement
  12277. contextPrototype.createPattern = function(image, repetition) {
  12278. return new CanvasPattern_(image, repetition);
  12279. };
  12280. // Gradient / Pattern Stubs
  12281. function CanvasGradient_(aType) {
  12282. this.type_ = aType;
  12283. this.x0_ = 0;
  12284. this.y0_ = 0;
  12285. this.r0_ = 0;
  12286. this.x1_ = 0;
  12287. this.y1_ = 0;
  12288. this.r1_ = 0;
  12289. this.colors_ = [];
  12290. }
  12291. CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
  12292. aColor = processStyle(aColor);
  12293. this.colors_.push({
  12294. offset: aOffset,
  12295. color: aColor.color,
  12296. alpha: aColor.alpha
  12297. });
  12298. };
  12299. function CanvasPattern_(image, repetition) {
  12300. assertImageIsValid(image);
  12301. switch (repetition) {
  12302. case 'repeat':
  12303. case null:
  12304. case '':
  12305. this.repetition_ = 'repeat';
  12306. break;
  12307. case 'repeat-x':
  12308. case 'repeat-y':
  12309. case 'no-repeat':
  12310. this.repetition_ = repetition;
  12311. break;
  12312. default:
  12313. throwException('SYNTAX_ERR');
  12314. }
  12315. this.src_ = image.src;
  12316. this.width_ = image.width;
  12317. this.height_ = image.height;
  12318. }
  12319. function throwException(s) {
  12320. throw new DOMException_(s);
  12321. }
  12322. function assertImageIsValid(img) {
  12323. if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
  12324. throwException('TYPE_MISMATCH_ERR');
  12325. }
  12326. if (img.readyState != 'complete') {
  12327. throwException('INVALID_STATE_ERR');
  12328. }
  12329. }
  12330. function DOMException_(s) {
  12331. this.code = this[s];
  12332. this.message = s + ': DOM Exception ' + this.code;
  12333. }
  12334. var p = DOMException_.prototype = new Error();
  12335. p.INDEX_SIZE_ERR = 1;
  12336. p.DOMSTRING_SIZE_ERR = 2;
  12337. p.HIERARCHY_REQUEST_ERR = 3;
  12338. p.WRONG_DOCUMENT_ERR = 4;
  12339. p.INVALID_CHARACTER_ERR = 5;
  12340. p.NO_DATA_ALLOWED_ERR = 6;
  12341. p.NO_MODIFICATION_ALLOWED_ERR = 7;
  12342. p.NOT_FOUND_ERR = 8;
  12343. p.NOT_SUPPORTED_ERR = 9;
  12344. p.INUSE_ATTRIBUTE_ERR = 10;
  12345. p.INVALID_STATE_ERR = 11;
  12346. p.SYNTAX_ERR = 12;
  12347. p.INVALID_MODIFICATION_ERR = 13;
  12348. p.NAMESPACE_ERR = 14;
  12349. p.INVALID_ACCESS_ERR = 15;
  12350. p.VALIDATION_ERR = 16;
  12351. p.TYPE_MISMATCH_ERR = 17;
  12352. // set up externs
  12353. G_vmlCanvasManager = G_vmlCanvasManager_;
  12354. CanvasRenderingContext2D = CanvasRenderingContext2D_;
  12355. CanvasGradient = CanvasGradient_;
  12356. CanvasPattern = CanvasPattern_;
  12357. DOMException = DOMException_;
  12358. })();
  12359. }
  12360. // if
  12361. /* global G_vmlCanvasManager */
  12362. /**
  12363. * Provides specific methods to draw with 2D Canvas element.
  12364. */
  12365. Ext.define('Ext.draw.engine.Canvas', {
  12366. extend: 'Ext.draw.Surface',
  12367. isCanvas: true,
  12368. requires: [
  12369. //<feature legacyBrowser>
  12370. 'Ext.draw.engine.excanvas',
  12371. //</feature>
  12372. 'Ext.draw.Animator',
  12373. 'Ext.draw.Color'
  12374. ],
  12375. config: {
  12376. /**
  12377. * @cfg {Boolean} highPrecision
  12378. * True to have the Canvas use JavaScript Number instead of single precision floating point
  12379. * for transforms.
  12380. *
  12381. * For example, when using data with big numbers to plot line series, the transformation
  12382. * matrix of the canvas will have big elements. Due to the implementation of the SVGMatrix,
  12383. * the elements are represented by 32-bits floats, which will work incorrectly.
  12384. * To compensate for that, we enable the canvas context to perform all the transformations
  12385. * in JavaScript.
  12386. *
  12387. * Do not use this if you are not encountering 32-bit floating point errors problem,
  12388. * since this will result in a performance penalty.
  12389. */
  12390. highPrecision: false
  12391. },
  12392. statics: {
  12393. contextOverrides: {
  12394. /**
  12395. * @ignore
  12396. */
  12397. setGradientBBox: function(bbox) {
  12398. this.bbox = bbox;
  12399. },
  12400. /**
  12401. * Fills the subpaths of the current default path or the given path with the current
  12402. * fill style.
  12403. * @ignore
  12404. */
  12405. fill: function() {
  12406. var fillStyle = this.fillStyle,
  12407. fillGradient = this.fillGradient,
  12408. fillOpacity = this.fillOpacity,
  12409. alpha = this.globalAlpha,
  12410. bbox = this.bbox;
  12411. if (fillStyle !== Ext.util.Color.RGBA_NONE && fillOpacity !== 0) {
  12412. if (fillGradient && bbox) {
  12413. this.fillStyle = fillGradient.generateGradient(this, bbox);
  12414. }
  12415. if (fillOpacity !== 1) {
  12416. this.globalAlpha = alpha * fillOpacity;
  12417. }
  12418. this.$fill();
  12419. if (fillOpacity !== 1) {
  12420. this.globalAlpha = alpha;
  12421. }
  12422. if (fillGradient && bbox) {
  12423. this.fillStyle = fillStyle;
  12424. }
  12425. }
  12426. },
  12427. /**
  12428. * Strokes the subpaths of the current default path or the given path with the current
  12429. * stroke style.
  12430. * @ignore
  12431. */
  12432. stroke: function() {
  12433. var strokeStyle = this.strokeStyle,
  12434. strokeGradient = this.strokeGradient,
  12435. strokeOpacity = this.strokeOpacity,
  12436. alpha = this.globalAlpha,
  12437. bbox = this.bbox;
  12438. if (strokeStyle !== Ext.util.Color.RGBA_NONE && strokeOpacity !== 0) {
  12439. if (strokeGradient && bbox) {
  12440. this.strokeStyle = strokeGradient.generateGradient(this, bbox);
  12441. }
  12442. if (strokeOpacity !== 1) {
  12443. this.globalAlpha = alpha * strokeOpacity;
  12444. }
  12445. this.$stroke();
  12446. if (strokeOpacity !== 1) {
  12447. this.globalAlpha = alpha;
  12448. }
  12449. if (strokeGradient && bbox) {
  12450. this.strokeStyle = strokeStyle;
  12451. }
  12452. }
  12453. },
  12454. /**
  12455. * @ignore
  12456. */
  12457. fillStroke: function(attr, transformFillStroke) {
  12458. var ctx = this,
  12459. fillStyle = this.fillStyle,
  12460. fillOpacity = this.fillOpacity,
  12461. strokeStyle = this.strokeStyle,
  12462. strokeOpacity = this.strokeOpacity,
  12463. shadowColor = ctx.shadowColor,
  12464. shadowBlur = ctx.shadowBlur,
  12465. none = Ext.util.Color.RGBA_NONE;
  12466. if (transformFillStroke === undefined) {
  12467. transformFillStroke = attr.transformFillStroke;
  12468. }
  12469. if (!transformFillStroke) {
  12470. attr.inverseMatrix.toContext(ctx);
  12471. }
  12472. if (fillStyle !== none && fillOpacity !== 0) {
  12473. ctx.fill();
  12474. ctx.shadowColor = none;
  12475. ctx.shadowBlur = 0;
  12476. }
  12477. if (strokeStyle !== none && strokeOpacity !== 0) {
  12478. ctx.stroke();
  12479. }
  12480. ctx.shadowColor = shadowColor;
  12481. ctx.shadowBlur = shadowBlur;
  12482. },
  12483. /**
  12484. * 2D Canvas context in IE (up to IE10, inclusive) doesn't support
  12485. * the setLineDash method and the lineDashOffset property.
  12486. * @param dashList An even number of non-negative numbers specifying a dash list.
  12487. */
  12488. setLineDash: function(dashList) {
  12489. if (this.$setLineDash) {
  12490. this.$setLineDash(dashList);
  12491. }
  12492. },
  12493. getLineDash: function() {
  12494. if (this.$getLineDash) {
  12495. return this.$getLineDash();
  12496. }
  12497. },
  12498. /**
  12499. * Adds points to the subpath such that the arc described by the circumference of the
  12500. * ellipse described by the arguments, starting at the given start angle and ending at
  12501. * the given end angle, going in the given direction (defaulting to clockwise), is added
  12502. * to the path, connected to the previous point by a straight line.
  12503. * @ignore
  12504. */
  12505. ellipse: function(cx, cy, rx, ry, rotation, start, end, anticlockwise) {
  12506. var cos = Math.cos(rotation),
  12507. sin = Math.sin(rotation);
  12508. this.transform(cos * rx, sin * rx, -sin * ry, cos * ry, cx, cy);
  12509. this.arc(0, 0, 1, start, end, anticlockwise);
  12510. this.transform(cos / rx, -sin / ry, sin / rx, cos / ry, -(cos * cx + sin * cy) / rx, (sin * cx - cos * cy) / ry);
  12511. },
  12512. /**
  12513. * Uses the given path commands to begin a new path on the canvas.
  12514. * @ignore
  12515. */
  12516. appendPath: function(path) {
  12517. var me = this,
  12518. i = 0,
  12519. j = 0,
  12520. commands = path.commands,
  12521. params = path.params,
  12522. ln = commands.length;
  12523. me.beginPath();
  12524. for (; i < ln; i++) {
  12525. switch (commands[i]) {
  12526. case 'M':
  12527. me.moveTo(params[j], params[j + 1]);
  12528. j += 2;
  12529. break;
  12530. case 'L':
  12531. me.lineTo(params[j], params[j + 1]);
  12532. j += 2;
  12533. break;
  12534. case 'C':
  12535. me.bezierCurveTo(params[j], params[j + 1], params[j + 2], params[j + 3], params[j + 4], params[j + 5]);
  12536. j += 6;
  12537. break;
  12538. case 'Z':
  12539. me.closePath();
  12540. break;
  12541. }
  12542. }
  12543. },
  12544. save: function() {
  12545. var toSave = this.toSave,
  12546. ln = toSave.length,
  12547. obj = ln && {},
  12548. // Don't allocate memory if we don't have to.
  12549. i = 0,
  12550. key;
  12551. for (; i < ln; i++) {
  12552. key = toSave[i];
  12553. if (key in this) {
  12554. obj[key] = this[key];
  12555. }
  12556. }
  12557. this.state.push(obj);
  12558. this.$save();
  12559. },
  12560. restore: function() {
  12561. var obj = this.state.pop(),
  12562. key;
  12563. if (obj) {
  12564. for (key in obj) {
  12565. this[key] = obj[key];
  12566. }
  12567. }
  12568. this.$restore();
  12569. }
  12570. }
  12571. },
  12572. splitThreshold: 3000,
  12573. /**
  12574. * @private
  12575. * Properties to be saved/restored in the `save` and `restore` methods.
  12576. */
  12577. toSave: [
  12578. 'fillGradient',
  12579. 'strokeGradient'
  12580. ],
  12581. /**
  12582. * @property element
  12583. * @inheritdoc
  12584. */
  12585. element: {
  12586. reference: 'element',
  12587. children: [
  12588. {
  12589. reference: 'bodyElement',
  12590. style: {
  12591. width: '100%',
  12592. height: '100%',
  12593. position: 'relative'
  12594. }
  12595. }
  12596. ]
  12597. },
  12598. /**
  12599. * @private
  12600. *
  12601. * Creates the canvas element.
  12602. */
  12603. createCanvas: function() {
  12604. var canvas, overrides, ctx, name;
  12605. canvas = Ext.Element.create({
  12606. tag: 'canvas',
  12607. cls: Ext.baseCSSPrefix + 'surface-canvas'
  12608. });
  12609. // Emulate Canvas in IE8 with VML.
  12610. if (window.G_vmlCanvasManager) {
  12611. G_vmlCanvasManager.initElement(canvas.dom);
  12612. this.isVML = true;
  12613. }
  12614. overrides = Ext.draw.engine.Canvas.contextOverrides;
  12615. ctx = canvas.dom.getContext('2d');
  12616. if (ctx.ellipse) {
  12617. delete overrides.ellipse;
  12618. }
  12619. ctx.state = [];
  12620. ctx.toSave = this.toSave;
  12621. // Saving references to the native Canvas context methods that we'll be overriding.
  12622. for (name in overrides) {
  12623. ctx['$' + name] = ctx[name];
  12624. }
  12625. Ext.apply(ctx, overrides);
  12626. if (this.getHighPrecision()) {
  12627. this.enablePrecisionCompensation(ctx);
  12628. } else {
  12629. this.disablePrecisionCompensation(ctx);
  12630. }
  12631. this.bodyElement.appendChild(canvas);
  12632. this.canvases.push(canvas);
  12633. this.contexts.push(ctx);
  12634. },
  12635. updateHighPrecision: function(highPrecision) {
  12636. var contexts = this.contexts,
  12637. ln = contexts.length,
  12638. i, context;
  12639. for (i = 0; i < ln; i++) {
  12640. context = contexts[i];
  12641. if (highPrecision) {
  12642. this.enablePrecisionCompensation(context);
  12643. } else {
  12644. this.disablePrecisionCompensation(context);
  12645. }
  12646. }
  12647. },
  12648. precisionNames: [
  12649. 'rect',
  12650. 'fillRect',
  12651. 'strokeRect',
  12652. 'clearRect',
  12653. 'moveTo',
  12654. 'lineTo',
  12655. 'arc',
  12656. 'arcTo',
  12657. 'save',
  12658. 'restore',
  12659. 'updatePrecisionCompensate',
  12660. 'setTransform',
  12661. 'transform',
  12662. 'scale',
  12663. 'translate',
  12664. 'rotate',
  12665. 'quadraticCurveTo',
  12666. 'bezierCurveTo',
  12667. 'createLinearGradient',
  12668. 'createRadialGradient',
  12669. 'fillText',
  12670. 'strokeText',
  12671. 'drawImage'
  12672. ],
  12673. /**
  12674. * @private
  12675. * Clears canvas of compensation for canvas' use of single precision floating point.
  12676. * @param {CanvasRenderingContext2D} ctx The canvas context.
  12677. */
  12678. disablePrecisionCompensation: function(ctx) {
  12679. var regularOverrides = Ext.draw.engine.Canvas.contextOverrides,
  12680. precisionOverrides = this.precisionNames,
  12681. ln = precisionOverrides.length,
  12682. i, name;
  12683. for (i = 0; i < ln; i++) {
  12684. name = precisionOverrides[i];
  12685. if (!(name in regularOverrides)) {
  12686. delete ctx[name];
  12687. }
  12688. }
  12689. this.setDirty(true);
  12690. },
  12691. /**
  12692. * @private
  12693. * Compensate for canvas' use of single precision floating point.
  12694. * @param {CanvasRenderingContext2D} ctx The canvas context.
  12695. */
  12696. enablePrecisionCompensation: function(ctx) {
  12697. var surface = this,
  12698. xx = 1,
  12699. yy = 1,
  12700. dx = 0,
  12701. dy = 0,
  12702. matrix = new Ext.draw.Matrix(),
  12703. transStack = [],
  12704. comp = {},
  12705. regularOverrides = Ext.draw.engine.Canvas.contextOverrides,
  12706. originalCtx = ctx.constructor.prototype,
  12707. /**
  12708. * @cfg {Object} precisionOverrides
  12709. * @ignore
  12710. */
  12711. precisionOverrides = {
  12712. toSave: surface.toSave,
  12713. /**
  12714. * Adds a new closed subpath to the path, representing the given rectangle.
  12715. * @return {*}
  12716. * @ignore
  12717. */
  12718. rect: function(x, y, w, h) {
  12719. return originalCtx.rect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
  12720. },
  12721. /**
  12722. * Paints the given rectangle onto the canvas, using the current fill style.
  12723. * @ignore
  12724. */
  12725. fillRect: function(x, y, w, h) {
  12726. this.updatePrecisionCompensateRect();
  12727. originalCtx.fillRect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
  12728. this.updatePrecisionCompensate();
  12729. },
  12730. /**
  12731. * Paints the box that outlines the given rectangle onto the canvas, using
  12732. * the current stroke style.
  12733. * @ignore
  12734. */
  12735. strokeRect: function(x, y, w, h) {
  12736. this.updatePrecisionCompensateRect();
  12737. originalCtx.strokeRect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
  12738. this.updatePrecisionCompensate();
  12739. },
  12740. /**
  12741. * Clears all pixels on the canvas in the given rectangle to transparent black.
  12742. * @ignore
  12743. */
  12744. clearRect: function(x, y, w, h) {
  12745. return originalCtx.clearRect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
  12746. },
  12747. /**
  12748. * Creates a new subpath with the given point.
  12749. * @ignore
  12750. */
  12751. moveTo: function(x, y) {
  12752. return originalCtx.moveTo.call(this, x * xx + dx, y * yy + dy);
  12753. },
  12754. /**
  12755. * Adds the given point to the current subpath, connected to the previous one
  12756. * by a straight line.
  12757. * @ignore
  12758. */
  12759. lineTo: function(x, y) {
  12760. return originalCtx.lineTo.call(this, x * xx + dx, y * yy + dy);
  12761. },
  12762. /**
  12763. * Adds points to the subpath such that the arc described by the circumference
  12764. * of the circle described by the arguments, starting at the given start angle
  12765. * and ending at the given end angle, going in the given direction (defaulting
  12766. * to clockwise), is added to the path, connected to the previous point
  12767. * by a straight line.
  12768. * @ignore
  12769. */
  12770. arc: function(x, y, radius, startAngle, endAngle, anticlockwise) {
  12771. this.updatePrecisionCompensateRect();
  12772. originalCtx.arc.call(this, x * xx + dx, y * xx + dy, radius * xx, startAngle, endAngle, anticlockwise);
  12773. this.updatePrecisionCompensate();
  12774. },
  12775. /**
  12776. * Adds an arc with the given control points and radius to the current subpath,
  12777. * connected to the previous point by a straight line. If two radii are provided,
  12778. * the first controls the width of the arc's ellipse, and the second controls
  12779. * the height. If only one is provided, or if they are the same, the arc is from
  12780. * a circle.
  12781. *
  12782. * In the case of an ellipse, the rotation argument controls the clockwise
  12783. * inclination of the ellipse relative to the x-axis.
  12784. * @ignore
  12785. */
  12786. arcTo: function(x1, y1, x2, y2, radius) {
  12787. this.updatePrecisionCompensateRect();
  12788. originalCtx.arcTo.call(this, x1 * xx + dx, y1 * yy + dy, x2 * xx + dx, y2 * yy + dy, radius * xx);
  12789. this.updatePrecisionCompensate();
  12790. },
  12791. /**
  12792. * Pushes the context state to the state stack.
  12793. * @ignore
  12794. */
  12795. save: function() {
  12796. transStack.push(matrix);
  12797. matrix = matrix.clone();
  12798. regularOverrides.save.call(this);
  12799. originalCtx.save.call(this);
  12800. },
  12801. /**
  12802. * Pops the state stack and restores the state.
  12803. * @ignore
  12804. */
  12805. restore: function() {
  12806. matrix = transStack.pop();
  12807. regularOverrides.restore.call(this);
  12808. originalCtx.restore.call(this);
  12809. this.updatePrecisionCompensate();
  12810. },
  12811. /**
  12812. * @ignore
  12813. */
  12814. updatePrecisionCompensate: function() {
  12815. matrix.precisionCompensate(surface.devicePixelRatio, comp);
  12816. xx = comp.xx;
  12817. yy = comp.yy;
  12818. dx = comp.dx;
  12819. dy = comp.dy;
  12820. originalCtx.setTransform.call(this, surface.devicePixelRatio, comp.b, comp.c, comp.d, 0, 0);
  12821. },
  12822. /**
  12823. * @ignore
  12824. */
  12825. updatePrecisionCompensateRect: function() {
  12826. matrix.precisionCompensateRect(surface.devicePixelRatio, comp);
  12827. xx = comp.xx;
  12828. yy = comp.yy;
  12829. dx = comp.dx;
  12830. dy = comp.dy;
  12831. originalCtx.setTransform.call(this, surface.devicePixelRatio, comp.b, comp.c, comp.d, 0, 0);
  12832. },
  12833. /**
  12834. * Changes the transformation matrix to the matrix given by the arguments
  12835. * as described below.
  12836. * @ignore
  12837. */
  12838. setTransform: function(x2x, x2y, y2x, y2y, newDx, newDy) {
  12839. matrix.set(x2x, x2y, y2x, y2y, newDx, newDy);
  12840. this.updatePrecisionCompensate();
  12841. },
  12842. /**
  12843. * Changes the transformation matrix to apply the matrix given by the arguments
  12844. * as described below.
  12845. * @ignore
  12846. */
  12847. transform: function(x2x, x2y, y2x, y2y, newDx, newDy) {
  12848. matrix.append(x2x, x2y, y2x, y2y, newDx, newDy);
  12849. this.updatePrecisionCompensate();
  12850. },
  12851. /**
  12852. * Scales the transformation matrix.
  12853. * @return {*}
  12854. * @ignore
  12855. */
  12856. scale: function(sx, sy) {
  12857. this.transform(sx, 0, 0, sy, 0, 0);
  12858. },
  12859. /**
  12860. * Translates the transformation matrix.
  12861. * @return {*}
  12862. * @ignore
  12863. */
  12864. translate: function(dx, dy) {
  12865. this.transform(1, 0, 0, 1, dx, dy);
  12866. },
  12867. /**
  12868. * Rotates the transformation matrix.
  12869. * @return {*}
  12870. * @ignore
  12871. */
  12872. rotate: function(radians) {
  12873. var cos = Math.cos(radians),
  12874. sin = Math.sin(radians);
  12875. this.transform(cos, sin, -sin, cos, 0, 0);
  12876. },
  12877. /**
  12878. * Adds the given point to the current subpath, connected to the previous one by a
  12879. * quadratic Bézier curve with the given control point.
  12880. * @return {*}
  12881. * @ignore
  12882. */
  12883. quadraticCurveTo: function(cx, cy, x, y) {
  12884. originalCtx.quadraticCurveTo.call(this, cx * xx + dx, cy * yy + dy, x * xx + dx, y * yy + dy);
  12885. },
  12886. /**
  12887. * Adds the given point to the current subpath, connected to the previous one
  12888. * by a cubic Bézier curve with the given control points.
  12889. * @return {*}
  12890. * @ignore
  12891. */
  12892. bezierCurveTo: function(c1x, c1y, c2x, c2y, x, y) {
  12893. originalCtx.bezierCurveTo.call(this, c1x * xx + dx, c1y * yy + dy, c2x * xx + dx, c2y * yy + dy, x * xx + dx, y * yy + dy);
  12894. },
  12895. /**
  12896. * Returns an object that represents a linear gradient that paints along the line
  12897. * given by the coordinates represented by the arguments.
  12898. * @return {*}
  12899. * @ignore
  12900. */
  12901. createLinearGradient: function(x0, y0, x1, y1) {
  12902. var grad;
  12903. this.updatePrecisionCompensateRect();
  12904. grad = originalCtx.createLinearGradient.call(this, x0 * xx + dx, y0 * yy + dy, x1 * xx + dx, y1 * yy + dy);
  12905. this.updatePrecisionCompensate();
  12906. return grad;
  12907. },
  12908. /**
  12909. * Returns a CanvasGradient object that represents a radial gradient that paints
  12910. * along the cone given by the circles represented by the arguments. If either
  12911. * of the radii are negative, throws an IndexSizeError exception.
  12912. * @return {*}
  12913. * @ignore
  12914. */
  12915. createRadialGradient: function(x0, y0, r0, x1, y1, r1) {
  12916. var grad;
  12917. this.updatePrecisionCompensateRect();
  12918. grad = originalCtx.createLinearGradient.call(this, x0 * xx + dx, y0 * xx + dy, r0 * xx, x1 * xx + dx, y1 * xx + dy, r1 * xx);
  12919. this.updatePrecisionCompensate();
  12920. return grad;
  12921. },
  12922. /**
  12923. * Fills the given text at the given position. If a maximum width is provided,
  12924. * the text will be scaled to fit that width if necessary.
  12925. * @ignore
  12926. */
  12927. fillText: function(text, x, y, maxWidth) {
  12928. originalCtx.setTransform.apply(this, matrix.elements);
  12929. if (typeof maxWidth === 'undefined') {
  12930. originalCtx.fillText.call(this, text, x, y);
  12931. } else {
  12932. originalCtx.fillText.call(this, text, x, y, maxWidth);
  12933. }
  12934. this.updatePrecisionCompensate();
  12935. },
  12936. /**
  12937. * Strokes the given text at the given position. If a
  12938. * maximum width is provided, the text will be scaled to
  12939. * fit that width if necessary.
  12940. * @ignore
  12941. */
  12942. strokeText: function(text, x, y, maxWidth) {
  12943. originalCtx.setTransform.apply(this, matrix.elements);
  12944. if (typeof maxWidth === 'undefined') {
  12945. originalCtx.strokeText.call(this, text, x, y);
  12946. } else {
  12947. originalCtx.strokeText.call(this, text, x, y, maxWidth);
  12948. }
  12949. this.updatePrecisionCompensate();
  12950. },
  12951. /**
  12952. * Fills the subpaths of the current default path or the given path with the current
  12953. * fill style.
  12954. * @ignore
  12955. */
  12956. fill: function() {
  12957. var fillGradient = this.fillGradient,
  12958. bbox = this.bbox;
  12959. this.updatePrecisionCompensateRect();
  12960. if (fillGradient && bbox) {
  12961. this.fillStyle = fillGradient.generateGradient(this, bbox);
  12962. }
  12963. originalCtx.fill.call(this);
  12964. this.updatePrecisionCompensate();
  12965. },
  12966. /**
  12967. * Strokes the subpaths of the current default path or the given path with the
  12968. * current stroke style.
  12969. * @ignore
  12970. */
  12971. stroke: function() {
  12972. var strokeGradient = this.strokeGradient,
  12973. bbox = this.bbox;
  12974. this.updatePrecisionCompensateRect();
  12975. if (strokeGradient && bbox) {
  12976. this.strokeStyle = strokeGradient.generateGradient(this, bbox);
  12977. }
  12978. originalCtx.stroke.call(this);
  12979. this.updatePrecisionCompensate();
  12980. },
  12981. /**
  12982. * Draws the given image onto the canvas. If the first argument isn't an img,
  12983. * canvas, or video element, throws a TypeMismatchError exception. If the image
  12984. * has no image data, throws an InvalidStateError exception. If the one of the
  12985. * source rectangle dimensions is zero, throws an IndexSizeError exception.
  12986. * If the image isn't yet fully decoded, then nothing is drawn.
  12987. * @return {*}
  12988. * @ignore
  12989. */
  12990. drawImage: function(img_elem, arg1, arg2, arg3, arg4, dst_x, dst_y, dw, dh) {
  12991. switch (arguments.length) {
  12992. case 3:
  12993. return originalCtx.drawImage.call(this, img_elem, arg1 * xx + dx, arg2 * yy + dy);
  12994. case 5:
  12995. return originalCtx.drawImage.call(this, img_elem, arg1 * xx + dx, arg2 * yy + dy, arg3 * xx, arg4 * yy);
  12996. case 9:
  12997. return originalCtx.drawImage.call(this, img_elem, arg1, arg2, arg3, arg4, dst_x * xx + dx, dst_y * yy * dy, dw * xx, dh * yy);
  12998. }
  12999. }
  13000. };
  13001. Ext.apply(ctx, precisionOverrides);
  13002. this.setDirty(true);
  13003. },
  13004. /**
  13005. * Normally, a surface will have a single canvas.
  13006. * However, on certain platforms/browsers there's a limit to how big a canvas can be.
  13007. * 'splitThreshold' is used to determine maximum width/height of a single canvas element.
  13008. * When a surface is wider/taller than the splitThreshold, extra canvas element(s)
  13009. * will be created and tiled inside the surface.
  13010. */
  13011. updateRect: function(rect) {
  13012. this.callParent([
  13013. rect
  13014. ]);
  13015. // eslint-disable-next-line vars-on-top
  13016. var me = this,
  13017. l = Math.floor(rect[0]),
  13018. t = Math.floor(rect[1]),
  13019. r = Math.ceil(rect[0] + rect[2]),
  13020. b = Math.ceil(rect[1] + rect[3]),
  13021. devicePixelRatio = me.devicePixelRatio,
  13022. canvases = me.canvases,
  13023. w = r - l,
  13024. h = b - t,
  13025. splitThreshold = Math.round(me.splitThreshold / devicePixelRatio),
  13026. xSplits = me.xSplits = Math.ceil(w / splitThreshold),
  13027. ySplits = me.ySplits = Math.ceil(h / splitThreshold),
  13028. i, j, k, offsetX, offsetY, dom, width, height;
  13029. for (j = 0 , offsetY = 0; j < ySplits; j++ , offsetY += splitThreshold) {
  13030. for (i = 0 , offsetX = 0; i < xSplits; i++ , offsetX += splitThreshold) {
  13031. k = j * xSplits + i;
  13032. if (k >= canvases.length) {
  13033. me.createCanvas();
  13034. }
  13035. dom = canvases[k].dom;
  13036. dom.style.left = offsetX + 'px';
  13037. dom.style.top = offsetY + 'px';
  13038. // The Canvas doesn't automatically support hi-DPI displays.
  13039. // We have to actually create a larger canvas (more pixels)
  13040. // while keeping its physical size the same.
  13041. height = Math.min(splitThreshold, h - offsetY);
  13042. if (height * devicePixelRatio !== dom.height) {
  13043. dom.height = height * devicePixelRatio;
  13044. dom.style.height = height + 'px';
  13045. }
  13046. width = Math.min(splitThreshold, w - offsetX);
  13047. if (width * devicePixelRatio !== dom.width) {
  13048. dom.width = width * devicePixelRatio;
  13049. dom.style.width = width + 'px';
  13050. }
  13051. me.applyDefaults(me.contexts[k]);
  13052. }
  13053. }
  13054. me.activeCanvases = k = xSplits * ySplits;
  13055. while (canvases.length > k) {
  13056. canvases.pop().destroy();
  13057. }
  13058. me.clear();
  13059. },
  13060. /**
  13061. * @method clearTransform
  13062. * @inheritdoc
  13063. */
  13064. clearTransform: function() {
  13065. var me = this,
  13066. xSplits = me.xSplits,
  13067. ySplits = me.ySplits,
  13068. contexts = me.contexts,
  13069. splitThreshold = me.splitThreshold,
  13070. devicePixelRatio = me.devicePixelRatio,
  13071. i, j, k, ctx;
  13072. for (i = 0; i < xSplits; i++) {
  13073. for (j = 0; j < ySplits; j++) {
  13074. k = j * xSplits + i;
  13075. ctx = contexts[k];
  13076. ctx.translate(-splitThreshold * i, -splitThreshold * j);
  13077. ctx.scale(devicePixelRatio, devicePixelRatio);
  13078. me.matrix.toContext(ctx);
  13079. }
  13080. }
  13081. },
  13082. /**
  13083. * @method renderSprite
  13084. * @inheritdoc
  13085. */
  13086. renderSprite: function(sprite) {
  13087. var me = this,
  13088. rect = me.getRect(),
  13089. surfaceMatrix = me.matrix,
  13090. parent = sprite.getParent(),
  13091. matrix = Ext.draw.Matrix.fly([
  13092. 1,
  13093. 0,
  13094. 0,
  13095. 1,
  13096. 0,
  13097. 0
  13098. ]),
  13099. splitThreshold = me.splitThreshold / me.devicePixelRatio,
  13100. xSplits = me.xSplits,
  13101. ySplits = me.ySplits,
  13102. offsetX, offsetY, ctx, bbox, width, height,
  13103. left = 0,
  13104. right,
  13105. top = 0,
  13106. bottom,
  13107. w = rect[2],
  13108. h = rect[3],
  13109. i, j, k;
  13110. while (parent && parent.isSprite) {
  13111. matrix.prependMatrix(parent.matrix || parent.attr && parent.attr.matrix);
  13112. parent = parent.getParent();
  13113. }
  13114. matrix.prependMatrix(surfaceMatrix);
  13115. bbox = sprite.getBBox();
  13116. if (bbox) {
  13117. bbox = matrix.transformBBox(bbox);
  13118. }
  13119. sprite.preRender(me);
  13120. if (sprite.attr.hidden || sprite.attr.globalAlpha === 0) {
  13121. sprite.setDirty(false);
  13122. return;
  13123. }
  13124. // Render this sprite on all Canvas elements it spans, skipping the rest.
  13125. for (j = 0 , offsetY = 0; j < ySplits; j++ , offsetY += splitThreshold) {
  13126. for (i = 0 , offsetX = 0; i < xSplits; i++ , offsetX += splitThreshold) {
  13127. k = j * xSplits + i;
  13128. ctx = me.contexts[k];
  13129. width = Math.min(splitThreshold, w - offsetX);
  13130. height = Math.min(splitThreshold, h - offsetY);
  13131. left = offsetX;
  13132. right = left + width;
  13133. top = offsetY;
  13134. bottom = top + height;
  13135. if (bbox) {
  13136. if (bbox.x > right || bbox.x + bbox.width < left || bbox.y > bottom || bbox.y + bbox.height < top) {
  13137. continue;
  13138. }
  13139. }
  13140. ctx.save();
  13141. sprite.useAttributes(ctx, rect);
  13142. if (false === sprite.render(me, ctx, [
  13143. left,
  13144. top,
  13145. width,
  13146. height
  13147. ])) {
  13148. return false;
  13149. }
  13150. ctx.restore();
  13151. }
  13152. }
  13153. sprite.setDirty(false);
  13154. },
  13155. flatten: function(size, surfaces) {
  13156. var targetCanvas = document.createElement('canvas'),
  13157. className = Ext.getClassName(this),
  13158. ratio = this.devicePixelRatio,
  13159. ctx = targetCanvas.getContext('2d'),
  13160. surface, canvas, rect, i, j, xy;
  13161. targetCanvas.width = Math.ceil(size.width * ratio);
  13162. targetCanvas.height = Math.ceil(size.height * ratio);
  13163. for (i = 0; i < surfaces.length; i++) {
  13164. surface = surfaces[i];
  13165. if (Ext.getClassName(surface) !== className) {
  13166. continue;
  13167. }
  13168. rect = surface.getRect();
  13169. for (j = 0; j < surface.canvases.length; j++) {
  13170. canvas = surface.canvases[j];
  13171. xy = canvas.getOffsetsTo(canvas.getParent());
  13172. ctx.drawImage(canvas.dom, (rect[0] + xy[0]) * ratio, (rect[1] + xy[1]) * ratio);
  13173. }
  13174. }
  13175. return {
  13176. data: targetCanvas.toDataURL(),
  13177. type: 'png'
  13178. };
  13179. },
  13180. applyDefaults: function(ctx) {
  13181. var none = Ext.util.Color.RGBA_NONE;
  13182. ctx.strokeStyle = none;
  13183. ctx.fillStyle = none;
  13184. ctx.textAlign = 'start';
  13185. ctx.textBaseline = 'alphabetic';
  13186. ctx.miterLimit = 1;
  13187. },
  13188. /**
  13189. * @method clear
  13190. * @inheritdoc
  13191. */
  13192. clear: function() {
  13193. var me = this,
  13194. activeCanvases = me.activeCanvases,
  13195. i, canvas, ctx;
  13196. for (i = 0; i < activeCanvases; i++) {
  13197. canvas = me.canvases[i].dom;
  13198. ctx = me.contexts[i];
  13199. ctx.setTransform(1, 0, 0, 1, 0, 0);
  13200. ctx.clearRect(0, 0, canvas.width, canvas.height);
  13201. }
  13202. me.setDirty(true);
  13203. },
  13204. /**
  13205. * Destroys the Canvas element and prepares it for Garbage Collection.
  13206. */
  13207. destroy: function() {
  13208. var me = this,
  13209. canvases = me.canvases,
  13210. ln = canvases.length,
  13211. i;
  13212. for (i = 0; i < ln; i++) {
  13213. me.contexts[i] = null;
  13214. canvases[i].destroy();
  13215. canvases[i] = null;
  13216. }
  13217. me.contexts = me.canvases = null;
  13218. me.callParent();
  13219. },
  13220. privates: {
  13221. initElement: function() {
  13222. var me = this;
  13223. me.callParent();
  13224. me.canvases = [];
  13225. me.contexts = [];
  13226. me.activeCanvases = me.xSplits = me.ySplits = 0;
  13227. }
  13228. }
  13229. }, function() {
  13230. var me = this,
  13231. proto = me.prototype,
  13232. splitThreshold = 1.0E10;
  13233. if (Ext.os.is.Android4 && Ext.browser.is.Chrome) {
  13234. splitThreshold = 3000;
  13235. } else if (Ext.is.iOS) {
  13236. splitThreshold = 2200;
  13237. }
  13238. proto.splitThreshold = splitThreshold;
  13239. });
  13240. /**
  13241. * The container that holds and manages instances of the {@link Ext.draw.Surface}
  13242. * in which {@link Ext.draw.sprite.Sprite sprites} are rendered. Draw containers are
  13243. * used as the foundation for all of the chart classes but may also be created directly
  13244. * in order to create custom drawings.
  13245. *
  13246. * @example
  13247. * var drawContainer = Ext.create('Ext.draw.Container', {
  13248. * renderTo: Ext.getBody(),
  13249. * width:200,
  13250. * height:200,
  13251. * sprites: [{
  13252. * type: 'circle',
  13253. * fillStyle: '#79BB3F',
  13254. * r: 100,
  13255. * x: 100,
  13256. * y: 100
  13257. * }]
  13258. * });
  13259. *
  13260. * // Uncomment to trigger a download of the painted circle.
  13261. * // drawContainer.download({
  13262. * // filename: 'Circle',
  13263. * // url: 'http://svg.sencha.io' // Default server the image data is sent to.
  13264. * // });
  13265. *
  13266. * In the previous example we created a draw container and configured it with a single
  13267. * sprite. The *type* of the sprite is {@link Ext.draw.sprite.Circle circle}, so if you
  13268. * run this code you'll see a green circle.
  13269. *
  13270. * You can attach sprite event listeners to the draw container with the help of the
  13271. * {@link Ext.draw.plugin.SpriteEvents} plugin.
  13272. *
  13273. * For more information on sprites, the core elements added to a draw container's
  13274. * surface, refer to the Ext.draw.sprite.Sprite documentation.
  13275. *
  13276. * For more information on surfaces, the interface owned by the draw container used to
  13277. * manage all sprites, see the Ext.draw.Surface documentation.
  13278. */
  13279. Ext.define('Ext.draw.Container', {
  13280. extend: 'Ext.draw.ContainerBase',
  13281. alternateClassName: 'Ext.draw.Component',
  13282. xtype: 'draw',
  13283. defaultType: 'surface',
  13284. isDrawContainer: true,
  13285. requires: [
  13286. 'Ext.draw.Surface',
  13287. 'Ext.draw.engine.Svg',
  13288. 'Ext.draw.engine.Canvas',
  13289. 'Ext.draw.gradient.GradientDefinition'
  13290. ],
  13291. /**
  13292. * @cfg {String} [engine="Ext.draw.engine.Canvas"]
  13293. * Defines the engine (type of surface) used to render draw container contents.
  13294. *
  13295. * The render engine is selected automatically depending on the platform used. Priority
  13296. * is given to the {@link Ext.draw.engine.Canvas} engine due to its performance advantage.
  13297. *
  13298. * You may also set the engine config to be `Ext.draw.engine.Svg` if so desired.
  13299. */
  13300. engine: 'Ext.draw.engine.Canvas',
  13301. /**
  13302. * @event spritemousemove
  13303. * Fires when the mouse is moved on a sprite.
  13304. * @param {Object} sprite
  13305. * @param {Event} event
  13306. */
  13307. /**
  13308. * @event spritemouseup
  13309. * Fires when a mouseup event occurs on a sprite.
  13310. * @param {Object} sprite
  13311. * @param {Event} event
  13312. */
  13313. /**
  13314. * @event spritemousedown
  13315. * Fires when a mousedown event occurs on a sprite.
  13316. * @param {Object} sprite
  13317. * @param {Event} event
  13318. */
  13319. /**
  13320. * @event spritemouseover
  13321. * Fires when the mouse enters a sprite.
  13322. * @param {Object} sprite
  13323. * @param {Event} event
  13324. */
  13325. /**
  13326. * @event spritemouseout
  13327. * Fires when the mouse exits a sprite.
  13328. * @param {Object} sprite
  13329. * @param {Event} event
  13330. */
  13331. /**
  13332. * @event spriteclick
  13333. * Fires when a click event occurs on a sprite.
  13334. * @param {Object} sprite
  13335. * @param {Event} event
  13336. */
  13337. /**
  13338. * @event spritedblclick
  13339. * Fires when a double click event occurs on a sprite.
  13340. * @param {Object} sprite
  13341. * @param {Event} event
  13342. */
  13343. /**
  13344. * @event spritetap
  13345. * Fires when a tap event occurs on a sprite.
  13346. * @param {Object} sprite
  13347. * @param {Event} event
  13348. */
  13349. /**
  13350. * @event bodyresize
  13351. * Fires when the size of the draw container body changes.
  13352. * @param {Object} size The object containing 'width' and 'height' of the draw container's body.
  13353. */
  13354. config: {
  13355. cls: [
  13356. Ext.baseCSSPrefix + 'draw-container',
  13357. Ext.baseCSSPrefix + 'unselectable'
  13358. ],
  13359. /**
  13360. * @cfg {Function} [resizeHandler]
  13361. * The resize function that can be configured to have a behavior,
  13362. * e.g. resize draw surfaces based on new draw container dimensions.
  13363. * The `resizeHandler` function takes a single parameter -
  13364. * the size object with `width` and `height` properties.
  13365. *
  13366. * **Note:** Since resize events trigger {@link #renderFrame} calls automatically,
  13367. * return `false` from the resize function, if it also calls `renderFrame`,
  13368. * to prevent double rendering.
  13369. */
  13370. resizeHandler: null,
  13371. /**
  13372. * @cfg {Object[]} sprites
  13373. * Defines a set of sprites to be added to the drawContainer surface.
  13374. *
  13375. * For example:
  13376. *
  13377. * sprites: [{
  13378. * type: 'circle',
  13379. * fillStyle: '#79BB3F',
  13380. * r: 100,
  13381. * x: 100,
  13382. * y: 100
  13383. * }]
  13384. *
  13385. */
  13386. sprites: null,
  13387. /**
  13388. * @cfg {Object[]} gradients
  13389. * Defines a set of gradients that can be used as color properties
  13390. * (fillStyle and strokeStyle, but not shadowColor) in sprites.
  13391. * The gradients array is an array of objects with the following properties:
  13392. * - **id** - string - The unique name of the gradient.
  13393. * - **type** - string, optional - The type of the gradient. Available types are: 'linear',
  13394. * 'radial'. Defaults to 'linear'.
  13395. * - **angle** - number, optional - The angle of the gradient in degrees.
  13396. * - **stops** - array - An array of objects with 'color' and 'offset' properties, where
  13397. * 'offset' is a real number from 0 to 1.
  13398. *
  13399. * For example:
  13400. *
  13401. * gradients: [{
  13402. * id: 'gradientId1',
  13403. * type: 'linear',
  13404. * angle: 45,
  13405. * stops: [{
  13406. * offset: 0,
  13407. * color: 'red'
  13408. * }, {
  13409. * offset: 1,
  13410. * color: 'yellow'
  13411. * }]
  13412. * }, {
  13413. * id: 'gradientId2',
  13414. * type: 'radial',
  13415. * stops: [{
  13416. * offset: 0,
  13417. * color: '#555',
  13418. * }, {
  13419. * offset: 1,
  13420. * color: '#ddd',
  13421. * }]
  13422. * }]
  13423. *
  13424. * Then the sprites can use 'gradientId1' and 'gradientId2' by setting the color attributes
  13425. * to those ids, for example:
  13426. *
  13427. * sprite.setAttributes({
  13428. * fillStyle: 'url(#gradientId1)',
  13429. * strokeStyle: 'url(#gradientId2)'
  13430. * });
  13431. */
  13432. gradients: [],
  13433. /**
  13434. * @cfg {String} downloadServerUrl
  13435. * The default URL used by the {@link #download} method.
  13436. */
  13437. downloadServerUrl: undefined,
  13438. touchAction: {
  13439. panX: false,
  13440. panY: false,
  13441. pinchZoom: false,
  13442. doubleTapZoom: false
  13443. },
  13444. /**
  13445. * @private
  13446. * @cfg {Object} surfaceZIndexes A map of surface type name to zIndex.
  13447. * The z-indexes to use for the various types of surfaces.
  13448. */
  13449. surfaceZIndexes: {
  13450. main: 1
  13451. }
  13452. },
  13453. /**
  13454. * @private
  13455. * @property {String} [defaultDownloadServerUrl="http://svg.sencha.io"]
  13456. * The default URL used by the {@link #download} method if the {@link #downloadServerUrl}
  13457. * config wasn't set.
  13458. * To override this globally, set the `Ext.draw.Container.prototype.defaultDownloadServerUrl`.
  13459. */
  13460. defaultDownloadServerUrl: 'http://svg.sencha.io',
  13461. /**
  13462. * @property {Array} [supportedFormats=["png", "pdf", "jpeg", "gif"]]
  13463. * A list of export types supported by the server.
  13464. * @private
  13465. */
  13466. supportedFormats: [
  13467. 'png',
  13468. 'pdf',
  13469. 'jpeg',
  13470. 'gif'
  13471. ],
  13472. supportedOptions: {
  13473. version: Ext.isNumber,
  13474. data: Ext.isString,
  13475. format: function(format) {
  13476. return Ext.Array.indexOf(this.supportedFormats, format) >= 0;
  13477. },
  13478. filename: Ext.isString,
  13479. width: Ext.isNumber,
  13480. height: Ext.isNumber,
  13481. scale: Ext.isNumber,
  13482. pdf: Ext.isObject,
  13483. jpeg: Ext.isObject
  13484. },
  13485. initAnimator: function() {
  13486. this.frameCallbackId = Ext.draw.Animator.addFrameCallback('renderFrame', this);
  13487. },
  13488. applyDownloadServerUrl: function(url) {
  13489. var defaultUrl = this.defaultDownloadServerUrl;
  13490. if (!url) {
  13491. url = defaultUrl;
  13492. //<debug>
  13493. // Skip this warning when unit testing.
  13494. if (!window.jasmine) {
  13495. 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() + ')');
  13496. }
  13497. }
  13498. //</debug>
  13499. return url;
  13500. },
  13501. applyGradients: function(gradients) {
  13502. var result = [],
  13503. i, n, gradient, offset;
  13504. if (!Ext.isArray(gradients)) {
  13505. return result;
  13506. }
  13507. for (i = 0 , n = gradients.length; i < n; i++) {
  13508. gradient = gradients[i];
  13509. if (!Ext.isObject(gradient)) {
  13510. continue;
  13511. }
  13512. // ExtJS only supported linear gradients, so we didn't have to specify their type
  13513. if (typeof gradient.type !== 'string') {
  13514. gradient.type = 'linear';
  13515. }
  13516. if (gradient.angle) {
  13517. gradient.degrees = gradient.angle;
  13518. delete gradient.angle;
  13519. }
  13520. // Convert ExtJS stops object to Touch stops array
  13521. if (Ext.isObject(gradient.stops)) {
  13522. gradient.stops = (function(stops) {
  13523. var result = [],
  13524. stop;
  13525. for (offset in stops) {
  13526. stop = stops[offset];
  13527. stop.offset = offset / 100;
  13528. result.push(stop);
  13529. }
  13530. return result;
  13531. })(gradient.stops);
  13532. }
  13533. result.push(gradient);
  13534. }
  13535. Ext.draw.gradient.GradientDefinition.add(result);
  13536. return result;
  13537. },
  13538. applySprites: function(sprites) {
  13539. var result, surface, sprite, i, ln;
  13540. // Never update.
  13541. if (!sprites) {
  13542. return;
  13543. }
  13544. sprites = Ext.Array.from(sprites);
  13545. result = [];
  13546. for (i = 0 , ln = sprites.length; i < ln; i++) {
  13547. sprite = sprites[i];
  13548. surface = sprite.surface;
  13549. if (!(surface && surface.isSurface)) {
  13550. if (Ext.isString(surface)) {
  13551. surface = this.getSurface(surface);
  13552. delete sprite.surface;
  13553. } else {
  13554. surface = this.getSurface('main');
  13555. }
  13556. }
  13557. sprite = surface.add(sprite);
  13558. result.push(sprite);
  13559. }
  13560. return result;
  13561. },
  13562. resizeDelay: 500,
  13563. // in milliseconds
  13564. resizeTimerId: 0,
  13565. lastResizeTime: null,
  13566. /**
  13567. * @private
  13568. * @property
  13569. * Last valid size.
  13570. */
  13571. size: null,
  13572. /**
  13573. * Triggers the {@link #resizeHandler} with the size of the draw container
  13574. * element as the parameter.
  13575. */
  13576. handleResize: function(size, instantly) {
  13577. // See the following:
  13578. // Classic: Ext.draw.ContainerBase.reattachToBody
  13579. // Modern: Ext.draw.ContainerBase.initialize
  13580. var me = this,
  13581. el = me.element,
  13582. resizeHandler = me.getResizeHandler() || me.defaultResizeHandler,
  13583. resizeDelay = me.resizeDelay,
  13584. lastResizeTime = me.lastResizeTime,
  13585. defer, result;
  13586. if (!el) {
  13587. return;
  13588. }
  13589. size = size || el.getSize();
  13590. if (!(size.width && size.height)) {
  13591. return;
  13592. }
  13593. me.size = size;
  13594. me.stopResizeTimer();
  13595. // Only want to defer when multiple resize events happen in quick succession.
  13596. // That way it doesn't feel luggy during an occasional resize, nor it's too straining
  13597. // when continuously resizing.
  13598. defer = !instantly && lastResizeTime && (Ext.Date.now() - lastResizeTime < resizeDelay);
  13599. if (defer) {
  13600. me.resizeTimerId = Ext.defer(me.handleResize, resizeDelay, me, [
  13601. size,
  13602. true
  13603. ]);
  13604. return;
  13605. }
  13606. me.fireEvent('bodyresize', me, size);
  13607. Ext.callback(resizeHandler, null, [
  13608. size
  13609. ], 0, me);
  13610. if (result !== false) {
  13611. me.renderFrame();
  13612. }
  13613. me.lastResizeTime = Ext.Date.now();
  13614. },
  13615. /**
  13616. * @private
  13617. */
  13618. stopResizeTimer: function() {
  13619. if (this.resizeTimerId) {
  13620. Ext.undefer(this.resizeTimerId);
  13621. this.resizeTimerId = 0;
  13622. }
  13623. },
  13624. defaultResizeHandler: function(size) {
  13625. this.getItems().each(function(surface) {
  13626. surface.setRect([
  13627. 0,
  13628. 0,
  13629. size.width,
  13630. size.height
  13631. ]);
  13632. });
  13633. },
  13634. /**
  13635. * Get a surface by the given id or create one if it doesn't exist.
  13636. * This will automatically call the {@link #resizeHandler}. Which
  13637. * means that, if no custom resize handler has been provided, the
  13638. * surface will be sized to match the container.
  13639. * If the {@link #method!add} method is used, it is the responsibility
  13640. * of the user to call the {@link #handleResize} method, to update
  13641. * the size of all added surfaces.
  13642. * @param {String} [id="main"]
  13643. * @param {String} type
  13644. * @return {Ext.draw.Surface}
  13645. */
  13646. getSurface: function(id, type) {
  13647. var me = this,
  13648. surfaces = me.getItems(),
  13649. oldCount = surfaces.getCount(),
  13650. zIndexes = me.getSurfaceZIndexes(),
  13651. surface;
  13652. id = id || 'main';
  13653. type = type || id;
  13654. surface = me.createSurface(id);
  13655. if (type in zIndexes) {
  13656. surface.element.setStyle('zIndex', zIndexes[type]);
  13657. }
  13658. if (surfaces.getCount() > oldCount) {
  13659. // Immediately call resize handler of the draw container,
  13660. // so that the newly created surface gets a size.
  13661. me.handleResize(null, true);
  13662. }
  13663. return surface;
  13664. },
  13665. createSurface: function(id) {
  13666. var me = this,
  13667. surfaces = me.getItems(),
  13668. surface;
  13669. id = this.getId() + '-' + (id || 'main');
  13670. surface = surfaces.get(id);
  13671. if (!surface) {
  13672. surface = me.add({
  13673. xclass: me.engine,
  13674. id: id
  13675. });
  13676. }
  13677. return surface;
  13678. },
  13679. /**
  13680. * Render all the surfaces in the container.
  13681. */
  13682. renderFrame: function() {
  13683. var me = this,
  13684. surfaces = me.getItems(),
  13685. i, ln, item;
  13686. for (i = 0 , ln = surfaces.length; i < ln; i++) {
  13687. item = surfaces.items[i];
  13688. if (item.isSurface) {
  13689. item.renderFrame();
  13690. }
  13691. }
  13692. },
  13693. /**
  13694. * @private
  13695. * Returns a slice of the surfaces (items) array of the draw container,
  13696. * optionally sorting them by zIndex.
  13697. * Overridden in subclasses.
  13698. */
  13699. getSurfaces: function(sort) {
  13700. var surfaces = Array.prototype.slice.call(this.items.items),
  13701. zIndexes = this.getSurfaceZIndexes(),
  13702. i, j, surface, zIndex;
  13703. if (sort) {
  13704. // Sort the surfaces by zIndex using insertion sort.
  13705. for (j = 1; j < surfaces.length; j++) {
  13706. surface = surfaces[j];
  13707. zIndex = zIndexes[surface.type];
  13708. i = j - 1;
  13709. while (i >= 0 && zIndexes[surfaces[i].type] > zIndex) {
  13710. surfaces[i + 1] = surfaces[i];
  13711. i--;
  13712. }
  13713. surfaces[i + 1] = surface;
  13714. }
  13715. }
  13716. return surfaces;
  13717. },
  13718. /**
  13719. * Produces an image of the chart / drawing.
  13720. * @param {String} [format] Possible options are 'image' (the method will return an
  13721. * Image object) and 'stream' (the method will return the image as a byte stream).
  13722. * If missing, the data URI of the drawing's (or chart's) image will be returned.
  13723. * Note: for an SVG based drawing/chart in IE/Edge browsers the method will always
  13724. * return SVG markup instead of a data URI, as 'img' elements won't accept a data
  13725. * URI anyway in those browsers.
  13726. * @return {Object}
  13727. * @return {String} return.data Image element, byte stream or DataURL.
  13728. * @return {String} return.type The type of the data (e.g. 'png' or 'svg').
  13729. */
  13730. getImage: function(format) {
  13731. var size = this.bodyElement.getSize(),
  13732. surfaces = this.getSurfaces(true),
  13733. surface = surfaces[0],
  13734. image, imageElement;
  13735. if ((Ext.isIE || Ext.isEdge) && surface.isSVG) {
  13736. // SVG data URLs don't work in IE/Edge as a source for an 'img' element,
  13737. // so we need to render SVG the usual way.
  13738. image = {
  13739. data: surface.toSVG(size, surfaces),
  13740. type: 'svg-markup'
  13741. };
  13742. } else {
  13743. image = surface.flatten(size, surfaces);
  13744. if (format === 'image') {
  13745. imageElement = new Image();
  13746. imageElement.src = image.data;
  13747. image.data = imageElement;
  13748. return image;
  13749. }
  13750. if (format === 'stream') {
  13751. image.data = image.data.replace(/^data:image\/[^;]+/, 'data:application/octet-stream');
  13752. return image;
  13753. }
  13754. }
  13755. return image;
  13756. },
  13757. /**
  13758. * Downloads an image or PDF of the chart / drawing or opens it in a separate
  13759. * browser tab/window if the download can't be triggered. The exact behavior is
  13760. * platform and browser specific. For more consistent results on mobile devices use
  13761. * the {@link #preview} method instead. This method doesn't work in IE8.
  13762. *
  13763. * Important: The default download mechanism sends image data to `http://svg.sencha.io`,
  13764. * which is a server operated by Sencha. This can be changed by setting
  13765. * the {@link #downloadServerUrl} config to the address of another server.
  13766. *
  13767. * You can deploy your own server by using the code from the `server` directory
  13768. * in the Charts package. The server is Node.js based and uses PhantomJS to
  13769. * generate images and PDFs from received data.
  13770. *
  13771. * The warning that the default download server is used can be suppressed
  13772. * by explicitly setting the value of the {@link #downloadServerUrl} config
  13773. * to `http://svg.sencha.io`.
  13774. *
  13775. * @param {Object} [config] The following config options are supported:
  13776. *
  13777. * @param {String} config.url The url to post the data to. Defaults to
  13778. * the value of the {@link #downloadServerUrl} config.
  13779. *
  13780. * @param {String} config.format The format of image to export. See the
  13781. * {@link #supportedFormats}. Defaults to 'png' on the Sencha IO server.
  13782. * Note that you can't export to 'svg' format if the {@link Ext.draw.engine.Canvas Canvas}
  13783. * {@link Ext.draw.Container#engine engine} is used.
  13784. *
  13785. * @param {Number} config.width A width to send to the server for
  13786. * configuring the image width. Defaults to natural image width on
  13787. * the Sencha IO server.
  13788. *
  13789. * @param {Number} config.height A height to send to the server for
  13790. * configuring the image height. Defaults to natural image height on
  13791. * the Sencha IO server.
  13792. *
  13793. * @param {String} config.filename The filename of the downloaded image.
  13794. * Defaults to 'chart' on the Sencha IO server. The config.format is used
  13795. * as a filename extension.
  13796. *
  13797. * @param {Number} config.scale The scaling of the downloaded image.
  13798. * Defaults to 1 on the Sencha IO server. The server will try to determine the natural
  13799. * size of the image unless the width/height configs have been set. If the
  13800. * {@link Ext.draw.engine.Canvas Canvas} {@link Ext.draw.Container#engine engine} is
  13801. * used the natural image size will depend on the value of the window.devicePixelRatio.
  13802. * For example, for devices with devicePixelRatio of 2 the produced image will be
  13803. * two times larger than for devices with devicePixelRatio of 1 for the same drawing.
  13804. * This is done so that the users with devices with HiDPI screens get a downloaded
  13805. * image that looks as crisp on their device as the original drawing.
  13806. * If you want image size to be consistent across devices with different device
  13807. * pixel ratios, you can set the value of this config to 1/devicePixelRatio.
  13808. * This parameter is ignored by the Sencha IO server if config.format is set to 'svg'.
  13809. *
  13810. * @param {Object} config.pdf PDF specific options.
  13811. * This config is only used if config.format is set to 'pdf'.
  13812. * The given object should be in either this format:
  13813. *
  13814. * {
  13815. * width: '200px',
  13816. * height: '300px',
  13817. * border: '0px'
  13818. * }
  13819. *
  13820. * or this format:
  13821. *
  13822. * {
  13823. * format: 'A4',
  13824. * orientation: 'portrait',
  13825. * border: '1cm'
  13826. * }
  13827. *
  13828. * Supported dimension units are: 'mm', 'cm', 'in', 'px'. No unit means 'px'.
  13829. * Supported formats are: 'A3', 'A4', 'A5', 'Legal', 'Letter', 'Tabloid'.
  13830. * Orientation ('portrait', 'landscape') is optional and defaults to 'portrait'.
  13831. *
  13832. * @param {Object} config.jpeg JPEG specific options.
  13833. * This config is only used if config.format is set to 'jpeg'.
  13834. * The given object should be in this format:
  13835. *
  13836. * {
  13837. * quality: 80
  13838. * }
  13839. *
  13840. * Where quality is an integer between 0 and 100.
  13841. *
  13842. * @return {Boolean} True if request was successfully sent to the server.
  13843. */
  13844. download: function(config) {
  13845. var me = this,
  13846. inputs = [],
  13847. markup, name, value;
  13848. if (Ext.isIE8) {
  13849. return false;
  13850. }
  13851. config = config || {};
  13852. config.version = 2;
  13853. if (!config.data) {
  13854. config.data = me.getImage().data;
  13855. }
  13856. for (name in config) {
  13857. if (config.hasOwnProperty(name)) {
  13858. value = config[name];
  13859. if (name in me.supportedOptions) {
  13860. if (me.supportedOptions[name].call(me, value)) {
  13861. inputs.push({
  13862. tag: 'input',
  13863. type: 'hidden',
  13864. name: name,
  13865. value: Ext.String.htmlEncode(Ext.isObject(value) ? Ext.JSON.encode(value) : value)
  13866. });
  13867. } else //<debug>
  13868. {
  13869. Ext.log.error('Invalid value for image download option "' + name + '": ' + value);
  13870. }
  13871. } else //</debug>
  13872. //<debug>
  13873. {
  13874. Ext.log.error('Invalid image download option: "' + name + '"');
  13875. }
  13876. }
  13877. }
  13878. //</debug>
  13879. markup = Ext.dom.Helper.markup({
  13880. tag: 'html',
  13881. children: [
  13882. {
  13883. tag: 'head'
  13884. },
  13885. {
  13886. tag: 'body',
  13887. children: [
  13888. {
  13889. tag: 'form',
  13890. method: 'POST',
  13891. action: config.url || me.getDownloadServerUrl(),
  13892. children: inputs
  13893. },
  13894. {
  13895. tag: 'script',
  13896. type: 'text/javascript',
  13897. children: 'document.getElementsByTagName("form")[0].submit();'
  13898. }
  13899. ]
  13900. }
  13901. ]
  13902. });
  13903. window.open('', 'ImageDownload_' + Date.now()).document.write(markup);
  13904. },
  13905. /**
  13906. * @method preview
  13907. * Displays an image of a Ext.draw.Container on screen.
  13908. * On mobile devices this lets users tap-and-hold to bring up the menu
  13909. * with image saving options.
  13910. * Notes:
  13911. * - some browsers won't save the preview image if it's SVG based
  13912. * (i.e. generated from a draw container that uses 'Ext.draw.engine.Svg' engine);
  13913. * - some platforms may not have the means of viewing successfully saved SVG images;
  13914. * - this method does not work on IE8.
  13915. */
  13916. doDestroy: function() {
  13917. var me = this,
  13918. callbackId = me.frameCallbackId;
  13919. if (callbackId) {
  13920. Ext.draw.Animator.removeFrameCallback(callbackId);
  13921. }
  13922. me.stopResizeTimer();
  13923. me.callParent();
  13924. }
  13925. }, function() {
  13926. if (location.search.match('svg')) {
  13927. Ext.draw.Container.prototype.engine = 'Ext.draw.engine.Svg';
  13928. } 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))) {
  13929. // http://code.google.com/p/android/issues/detail?id=37529
  13930. Ext.draw.Container.prototype.engine = 'Ext.draw.engine.Svg';
  13931. }
  13932. });
  13933. /**
  13934. *
  13935. */
  13936. Ext.define('Ext.chart.theme.BaseTheme', {
  13937. defaultsDivCls: 'x-root'
  13938. });
  13939. /**
  13940. * Abstract class that provides default styles for non-specified things.
  13941. * Should be sub-classed when creating new themes.
  13942. * For example:
  13943. *
  13944. * Ext.define('Ext.chart.theme.Custom', {
  13945. * extend: 'Ext.chart.theme.Base',
  13946. * singleton: true,
  13947. * alias: 'chart.theme.custom',
  13948. * config: {
  13949. * baseColor: '#ff9f00'
  13950. * }
  13951. * });
  13952. *
  13953. * Theme provided values will not override the values provided in an instance config.
  13954. * However, if a theme provided value is an object, it will be merged with the value
  13955. * from the instance config, unless the theme provided object has a '$default' key
  13956. * set to 'true'.
  13957. *
  13958. * Certain chart theme configs (e.g. 'fontSize') may use the 'default' value to indicate
  13959. * that they should inherit a value from the corresponding CSS style provided by
  13960. * a framework theme. Additionally, one can use basic binary operators like multiplication,
  13961. * addition and subtraction to derive from the default value, e.g. fontSize: 'default*1.3'.
  13962. *
  13963. * Important: the theme should not use the 'font' shorthand to specify the font of labels
  13964. * and other text elements of a chart. Instead, individual font properties should be used:
  13965. * 'fontStyle', 'fontVariant', 'fontWeight', 'fontSize' and 'fontFamily'.
  13966. */
  13967. Ext.define('Ext.chart.theme.Base', {
  13968. extend: 'Ext.chart.theme.BaseTheme',
  13969. mixins: {
  13970. factoryable: 'Ext.mixin.Factoryable'
  13971. },
  13972. requires: [
  13973. 'Ext.draw.Color'
  13974. ],
  13975. factoryConfig: {
  13976. type: 'chart.theme'
  13977. },
  13978. isTheme: true,
  13979. isBase: true,
  13980. config: {
  13981. /**
  13982. * @cfg {String/Ext.util.Color} baseColor
  13983. * The base color used to generate the {@link Ext.chart.AbstractChart#colors} of the theme.
  13984. */
  13985. baseColor: null,
  13986. /**
  13987. * @cfg {Array} colors
  13988. *
  13989. * Array of colors/gradients to be used by the theme.
  13990. * Defaults to {@link #colorDefaults}.
  13991. */
  13992. colors: undefined,
  13993. /**
  13994. * @cfg {Object} gradients
  13995. *
  13996. * The gradient config to be used by series' sprites. E.g.:
  13997. *
  13998. * {
  13999. * type: 'linear',
  14000. * degrees: 90
  14001. * }
  14002. *
  14003. * Please refer to the documentation for the {@link Ext.draw.gradient.Linear linear}
  14004. * and {@link Ext.draw.gradient.Radial radial} gradients for all possible options.
  14005. * The color {@link Ext.draw.gradient.Gradient#stops stops} for the gradients
  14006. * will be generated by the theme based on the {@link #colors} config.
  14007. */
  14008. gradients: null,
  14009. /**
  14010. * @cfg {Object} chart
  14011. * Theme defaults for the chart.
  14012. * Can apply to all charts or just a specific type of chart.
  14013. * For example:
  14014. *
  14015. * chart: {
  14016. * defaults: {
  14017. * background: 'lightgray'
  14018. * },
  14019. * polar: {
  14020. * background: 'green'
  14021. * }
  14022. * }
  14023. *
  14024. * The values from the chart.defaults and chart.*type* configs (where *type* is a valid
  14025. * chart xtype, e.g. '{@link Ext.chart.CartesianChart cartesian}' or
  14026. * '{@link Ext.chart.PolarChart polar}') will be applied to corresponding chart configs.
  14027. * E.g., the chart.defaults.background config will set the
  14028. * {@link Ext.chart.AbstractChart#background} config of all charts, where the
  14029. * chart.cartesian.flipXY config will only set the
  14030. * {@link Ext.chart.CartesianChart#flipXY} config of all cartesian charts.
  14031. */
  14032. chart: {
  14033. defaults: {
  14034. captions: {
  14035. title: {
  14036. docked: 'top',
  14037. padding: 5,
  14038. style: {
  14039. textAlign: 'center',
  14040. fontFamily: 'default',
  14041. fontWeight: '500',
  14042. fillStyle: 'black',
  14043. fontSize: 'default*1.6'
  14044. }
  14045. },
  14046. subtitle: {
  14047. docked: 'top',
  14048. style: {
  14049. textAlign: 'center',
  14050. fontFamily: 'default',
  14051. fontWeight: 'normal',
  14052. fillStyle: 'black',
  14053. fontSize: 'default*1.3'
  14054. }
  14055. },
  14056. credits: {
  14057. docked: 'bottom',
  14058. padding: 5,
  14059. style: {
  14060. textAlign: 'left',
  14061. fontFamily: 'default',
  14062. fontWeight: 'lighter',
  14063. fillStyle: 'black',
  14064. fontSize: 'default'
  14065. }
  14066. }
  14067. },
  14068. background: 'white'
  14069. }
  14070. },
  14071. /**
  14072. * @cfg {Object} axis
  14073. * Theme defaults for the axes.
  14074. * Can apply to all axes or only axes with a specific position.
  14075. * For example:
  14076. *
  14077. * axis: {
  14078. * defaults: {
  14079. * style: {strokeStyle: 'red'}
  14080. * },
  14081. * left: {
  14082. * title: {fillStyle: 'green'}
  14083. * }
  14084. * }
  14085. *
  14086. * The values from the axis.defaults and axis.*position* configs (where *position*
  14087. * is a valid axis {@link Ext.chart.axis.Axis#position}, e.g. 'bottom') will be
  14088. * applied to corresponding {@link Ext.chart.axis.Axis axis} configs.
  14089. * E.g., the axis.defaults.label config will apply to the {@link Ext.chart.axis.Axis#label}
  14090. * config of all axes, where the axis.left.titleMargin config will only apply to the
  14091. * {@link Ext.chart.axis.Axis#titleMargin} config of all axes positioned to the left.
  14092. */
  14093. axis: {
  14094. defaults: {
  14095. label: {
  14096. x: 0,
  14097. y: 0,
  14098. textBaseline: 'middle',
  14099. textAlign: 'center',
  14100. fontSize: 'default',
  14101. fontFamily: 'default',
  14102. fontWeight: 'default',
  14103. fillStyle: 'black'
  14104. },
  14105. title: {
  14106. fillStyle: 'black',
  14107. fontSize: 'default*1.23',
  14108. fontFamily: 'default',
  14109. fontWeight: 'default'
  14110. },
  14111. style: {
  14112. strokeStyle: 'black'
  14113. },
  14114. grid: {
  14115. strokeStyle: 'rgb(221, 221, 221)'
  14116. }
  14117. },
  14118. top: {
  14119. style: {
  14120. textPadding: 5
  14121. }
  14122. },
  14123. bottom: {
  14124. style: {
  14125. textPadding: 5
  14126. }
  14127. }
  14128. },
  14129. /**
  14130. * @cfg {Object} series
  14131. * Theme defaults for the series.
  14132. * Can apply to all series or just a specific type of series.
  14133. * For example:
  14134. *
  14135. * series: {
  14136. * defaults: {
  14137. * style: {
  14138. * lineWidth: 2
  14139. * }
  14140. * },
  14141. * bar: {
  14142. * animation: {
  14143. * easing: 'bounceOut',
  14144. * duration: 1000
  14145. * }
  14146. * }
  14147. * }
  14148. *
  14149. * The values from the series.defaults and series.*type* configs (where *type*
  14150. * is a valid series {@link Ext.chart.series.Series#type}, e.g. 'line') will be
  14151. * applied to corresponding series configs.
  14152. * E.g., the series.defaults.label config will apply to the
  14153. * {@link Ext.chart.series.Series#label} config of all series, where the series.line.step
  14154. * config will only apply to the
  14155. * {@link Ext.chart.series.Line#step} config of {@link Ext.chart.series.Line line} series.
  14156. */
  14157. series: {
  14158. defaults: {
  14159. label: {
  14160. fillStyle: 'black',
  14161. strokeStyle: 'none',
  14162. fontFamily: 'default',
  14163. fontWeight: 'default',
  14164. fontSize: 'default*1.077',
  14165. textBaseline: 'middle',
  14166. textAlign: 'center'
  14167. },
  14168. labelOverflowPadding: 5
  14169. }
  14170. },
  14171. /**
  14172. * @cfg {Object} sprites
  14173. * Default style for the custom chart sprites by type.
  14174. * For example:
  14175. *
  14176. * sprites: {
  14177. * text: {
  14178. * fontWeight: 300
  14179. * }
  14180. * }
  14181. *
  14182. * These sprite attribute overrides will apply to custom sprites of all charts
  14183. * specified using the {@link Ext.draw.Container#sprites} config.
  14184. * The overrides are specified by sprite type, e.g. sprites.text config
  14185. * tells to apply given attributes to all {@link Ext.draw.sprite.Text text} sprites.
  14186. */
  14187. sprites: {
  14188. text: {
  14189. fontSize: 'default',
  14190. fontWeight: 'default',
  14191. fontFamily: 'default',
  14192. fillStyle: 'black'
  14193. }
  14194. },
  14195. /**
  14196. * Style information for the {Ext.chart.legend.SpriteLegend sprite legend}.
  14197. * If the {@link Ext.chart.legend.Legend DOM} legend is used, this config is ignored.
  14198. * For additional details see {@link Ext.chart.AbstractChart#legend}.
  14199. * @cfg {Object} legend
  14200. * @cfg {Ext.chart.legend.sprite.Item} legend.item
  14201. * @cfg {Object} legend.border See {@link Ext.chart.legend.SpriteLegend#border}.
  14202. */
  14203. legend: {
  14204. label: {
  14205. fontSize: 14,
  14206. fontWeight: 'default',
  14207. fontFamily: 'default',
  14208. fillStyle: 'black'
  14209. },
  14210. border: {
  14211. lineWidth: 1,
  14212. radius: 4,
  14213. fillStyle: 'none',
  14214. strokeStyle: 'gray'
  14215. },
  14216. background: 'white'
  14217. },
  14218. /**
  14219. * @private
  14220. * An object with the following structure:
  14221. * {
  14222. * fillStyle: [color, color, ...],
  14223. * strokeStyle: [color, color, ...],
  14224. * ...
  14225. * }
  14226. * If missing, generated from the other configs: 'baseColor, 'gradients', 'colors'.
  14227. */
  14228. seriesThemes: undefined,
  14229. markerThemes: {
  14230. type: [
  14231. 'circle',
  14232. 'cross',
  14233. 'plus',
  14234. 'square',
  14235. 'triangle',
  14236. 'diamond'
  14237. ]
  14238. },
  14239. /**
  14240. * @deprecated 5.0.1 Use the {@link Ext.draw.Container#gradients} config instead.
  14241. */
  14242. useGradients: false,
  14243. /**
  14244. * @deprecated 5.0.1 Use the {@link Ext.chart.AbstractChart#background} config instead.
  14245. */
  14246. background: null
  14247. },
  14248. colorDefaults: [
  14249. '#94ae0a',
  14250. '#115fa6',
  14251. '#a61120',
  14252. '#ff8809',
  14253. '#ffd13e',
  14254. '#a61187',
  14255. '#24ad9a',
  14256. '#7c7474',
  14257. '#a66111'
  14258. ],
  14259. constructor: function(config) {
  14260. this.initConfig(config);
  14261. this.resolveDefaults();
  14262. },
  14263. defaultRegEx: /^default([+\-/*]\d+(?:\.\d+)?)?$/,
  14264. defaultOperators: {
  14265. '*': function(v1, v2) {
  14266. return v1 * v2;
  14267. },
  14268. '+': function(v1, v2) {
  14269. return v1 + v2;
  14270. },
  14271. '-': function(v1, v2) {
  14272. return v1 - v2;
  14273. }
  14274. },
  14275. resolveChartDefaults: function() {
  14276. var chart = Ext.clone(this.getChart()),
  14277. chartType, captionName, chartConfig, captionConfig;
  14278. for (chartType in chart) {
  14279. chartConfig = chart[chartType];
  14280. if ('captions' in chartConfig) {
  14281. for (captionName in chartConfig.captions) {
  14282. captionConfig = chartConfig.captions[captionName];
  14283. if (captionConfig) {
  14284. this.replaceDefaults(captionConfig.style);
  14285. }
  14286. }
  14287. }
  14288. }
  14289. this.setChart(chart);
  14290. },
  14291. resolveDefaults: function() {
  14292. var me = this;
  14293. Ext.onInternalReady(function() {
  14294. var sprites = Ext.clone(me.getSprites()),
  14295. legend = Ext.clone(me.getLegend()),
  14296. axis = Ext.clone(me.getAxis()),
  14297. series = Ext.clone(me.getSeries()),
  14298. div, key, config;
  14299. if (!me.superclass.defaults) {
  14300. div = Ext.getBody().createChild({
  14301. tag: 'div',
  14302. cls: me.defaultsDivCls
  14303. });
  14304. me.superclass.defaults = {
  14305. fontFamily: div.getStyle('fontFamily'),
  14306. fontWeight: div.getStyle('fontWeight'),
  14307. fontSize: parseFloat(div.getStyle('fontSize')),
  14308. fontVariant: div.getStyle('fontVariant'),
  14309. fontStyle: div.getStyle('fontStyle')
  14310. };
  14311. div.destroy();
  14312. }
  14313. me.resolveChartDefaults();
  14314. me.replaceDefaults(sprites.text);
  14315. me.setSprites(sprites);
  14316. me.replaceDefaults(legend.label);
  14317. me.setLegend(legend);
  14318. for (key in axis) {
  14319. config = axis[key];
  14320. me.replaceDefaults(config.label);
  14321. me.replaceDefaults(config.title);
  14322. }
  14323. me.setAxis(axis);
  14324. for (key in series) {
  14325. config = series[key];
  14326. me.replaceDefaults(config.label);
  14327. }
  14328. me.setSeries(series);
  14329. });
  14330. },
  14331. replaceDefaults: function(target) {
  14332. var me = this,
  14333. defaults = me.superclass.defaults,
  14334. defaultRegEx = me.defaultRegEx,
  14335. key, value, match, binaryFn;
  14336. if (Ext.isObject(target)) {
  14337. for (key in defaults) {
  14338. match = defaultRegEx.exec(target[key]);
  14339. if (match) {
  14340. value = defaults[key];
  14341. match = match[1];
  14342. if (match) {
  14343. binaryFn = me.defaultOperators[match.charAt(0)];
  14344. value = Math.round(binaryFn(value, parseFloat(match.substr(1))));
  14345. }
  14346. target[key] = value;
  14347. }
  14348. }
  14349. }
  14350. },
  14351. applyBaseColor: function(baseColor) {
  14352. var midColor, midL;
  14353. if (baseColor) {
  14354. midColor = baseColor.isColor ? baseColor : Ext.util.Color.fromString(baseColor);
  14355. midL = midColor.getHSL()[2];
  14356. if (midL < 0.15) {
  14357. midColor = midColor.createLighter(0.3);
  14358. } else if (midL < 0.3) {
  14359. midColor = midColor.createLighter(0.15);
  14360. } else if (midL > 0.85) {
  14361. midColor = midColor.createDarker(0.3);
  14362. } else if (midL > 0.7) {
  14363. midColor = midColor.createDarker(0.15);
  14364. }
  14365. this.setColors([
  14366. midColor.createDarker(0.3).toString(),
  14367. midColor.createDarker(0.15).toString(),
  14368. midColor.toString(),
  14369. midColor.createLighter(0.12).toString(),
  14370. midColor.createLighter(0.24).toString(),
  14371. midColor.createLighter(0.31).toString()
  14372. ]);
  14373. }
  14374. return baseColor;
  14375. },
  14376. applyColors: function(newColors) {
  14377. return newColors || this.colorDefaults;
  14378. },
  14379. updateUseGradients: function(useGradients) {
  14380. if (useGradients) {
  14381. this.updateGradients({
  14382. type: 'linear',
  14383. degrees: 90
  14384. });
  14385. }
  14386. },
  14387. updateBackground: function(background) {
  14388. var chart;
  14389. if (background) {
  14390. chart = this.getChart();
  14391. chart.defaults.background = background;
  14392. this.setChart(chart);
  14393. }
  14394. },
  14395. updateGradients: function(gradients) {
  14396. var colors = this.getColors(),
  14397. items = [],
  14398. gradient, midColor, color, i, ln;
  14399. if (Ext.isObject(gradients)) {
  14400. for (i = 0 , ln = colors && colors.length || 0; i < ln; i++) {
  14401. midColor = Ext.util.Color.fromString(colors[i]);
  14402. if (midColor) {
  14403. color = midColor.createLighter(0.15).toString();
  14404. gradient = Ext.apply(Ext.Object.chain(gradients), {
  14405. stops: [
  14406. {
  14407. offset: 1,
  14408. color: midColor.toString()
  14409. },
  14410. {
  14411. offset: 0,
  14412. color: color.toString()
  14413. }
  14414. ]
  14415. });
  14416. items.push(gradient);
  14417. }
  14418. }
  14419. this.setColors(items);
  14420. }
  14421. },
  14422. applySeriesThemes: function(newSeriesThemes) {
  14423. var colors, color;
  14424. // Init the 'colors' config with solid colors generated from the 'baseColor'.
  14425. this.getBaseColor();
  14426. // Init the 'gradients' config with a hardcoded value, if the legacy 'useGradients'
  14427. // config was set to 'true'. This in turn updates the 'colors' config.
  14428. this.getUseGradients();
  14429. // Init the 'gradients' config normally. This also updates the 'colors' config.
  14430. this.getGradients();
  14431. colors = this.getColors();
  14432. // Final colors.
  14433. if (!newSeriesThemes) {
  14434. newSeriesThemes = {
  14435. fillStyle: Ext.Array.clone(colors),
  14436. strokeStyle: Ext.Array.map(colors, function(value) {
  14437. color = Ext.util.Color.fromString(value.stops ? value.stops[0].color : value);
  14438. return color.createDarker(0.15).toString();
  14439. })
  14440. };
  14441. }
  14442. return newSeriesThemes;
  14443. }
  14444. });
  14445. /**
  14446. * @private
  14447. */
  14448. Ext.define('Ext.chart.theme.Default', {
  14449. extend: 'Ext.chart.theme.Base',
  14450. singleton: true,
  14451. alias: [
  14452. 'chart.theme.default',
  14453. 'chart.theme.Default',
  14454. 'chart.theme.Base'
  14455. ]
  14456. });
  14457. /**
  14458. * @private
  14459. */
  14460. Ext.define('Ext.chart.Util', {
  14461. singleton: true,
  14462. /**
  14463. * @private
  14464. * Given a `range` array of two (min/max) numbers and an arbitrary array of numbers
  14465. * (`data`), updates the given `range`, if the range of `data` exceeds it.
  14466. * Typically, one would start with the `[NaN, NaN]` range array instance, call the
  14467. * method on multiple datasets with that range instance, then validate it with
  14468. * {@link #validateRange}.
  14469. * @param {Number[]} range
  14470. * @param {Number[]} data
  14471. */
  14472. expandRange: function(range, data) {
  14473. var length = data.length,
  14474. min = range[0],
  14475. max = range[1],
  14476. i, value;
  14477. for (i = 0; i < length; i++) {
  14478. value = data[i];
  14479. // `null` is a "finite" number in JavaScript
  14480. // and greater than any negative number.
  14481. if (value == null || !isFinite(value)) {
  14482. continue;
  14483. }
  14484. if (value < min || !isFinite(min)) {
  14485. min = value;
  14486. }
  14487. if (value > max || !isFinite(max)) {
  14488. max = value;
  14489. }
  14490. }
  14491. range[0] = min;
  14492. range[1] = max;
  14493. },
  14494. defaultRange: [
  14495. 0,
  14496. 1
  14497. ],
  14498. /**
  14499. * @private
  14500. * Makes sure the range exists, is not zero, and its min/max values are finite numbers.
  14501. * If this is not the case, the values from the provided `defaultRange`
  14502. * are used.
  14503. *
  14504. * The range to validate. Never modified.
  14505. * @param {Number[]} range
  14506. * The default range to use, if the given range is not a valid data structure,
  14507. * if both values are infinities, or if both values are the same and dangerously
  14508. * close to either infinity (which makes expansion of the range by the value of
  14509. * `padding` impossible).
  14510. * If only a single value is infinity, the other value will be derived
  14511. * from the finite value by incrementing/decrementing it by the span
  14512. * of the default range towards the infinity.
  14513. * For example, if the `defaultRange` is `[0, 1]`, we have:
  14514. *
  14515. * [5, Infinity] --> [5, 6]
  14516. * [3, -Infinity] --> [2, 3]
  14517. * [-Infinity, -5] --> [-6, -5]
  14518. * [-3, -Infinity] --> [-4, -3]
  14519. *
  14520. * @param {Number[]} [defaultRange=[0, 1]]
  14521. * A non-negative padding to use in case of identical min/max.
  14522. * Note that the range span is not guaranteed to be `padding * 2` in this case,
  14523. * if min/max are close to MIN_SAFE_INTEGER/MAX_SAFE_INTEGER.
  14524. * @param {Number} [padding=0.5]
  14525. * @return {Number[]}
  14526. */
  14527. validateRange: function(range, defaultRange, padding) {
  14528. var isFin0, isFin1;
  14529. defaultRange = defaultRange || this.defaultRange.slice();
  14530. if (!(padding === 0 || padding > 0)) {
  14531. padding = 0.5;
  14532. }
  14533. if (!range || range.length !== 2) {
  14534. return defaultRange;
  14535. }
  14536. range = [
  14537. range[0],
  14538. range[1]
  14539. ];
  14540. if (!range[0]) {
  14541. range[0] = 0;
  14542. }
  14543. if (!range[1]) {
  14544. range[1] = 0;
  14545. }
  14546. if (padding && range[0] === range[1]) {
  14547. range = [
  14548. range[0] - padding,
  14549. range[0] + padding
  14550. ];
  14551. // In case the range values are at Infinity, the expansion above by the value
  14552. // of 'padding' won't do us much good, so we still have to fall back to the
  14553. // 'defaultRange'.
  14554. if (range[0] === range[1]) {
  14555. return defaultRange;
  14556. }
  14557. }
  14558. // Same sign infinities are ruled out at this point.
  14559. isFin0 = isFinite(range[0]);
  14560. isFin1 = isFinite(range[1]);
  14561. if (!isFin0 && !isFin1) {
  14562. return defaultRange;
  14563. }
  14564. // Different sign infinities are ruled out at this point.
  14565. if (isFin0 && !isFin1) {
  14566. range[1] = range[0] + Ext.Number.sign(range[1]) * (defaultRange[1] - defaultRange[0]);
  14567. } else if (isFin1 && !isFin0) {
  14568. range[0] = range[1] + Ext.Number.sign(range[0]) * (defaultRange[1] - defaultRange[0]);
  14569. }
  14570. // All infinities are ruled out at this point.
  14571. return [
  14572. Math.min(range[0], range[1]),
  14573. Math.max(range[0], range[1])
  14574. ];
  14575. },
  14576. applyAnimation: function(animation, oldAnimation) {
  14577. if (!animation) {
  14578. animation = {
  14579. duration: 0
  14580. };
  14581. } else if (animation === true) {
  14582. animation = {
  14583. easing: 'easeInOut',
  14584. duration: 500
  14585. };
  14586. }
  14587. return oldAnimation ? Ext.apply({}, animation, oldAnimation) : animation;
  14588. }
  14589. });
  14590. /**
  14591. * @class Ext.chart.Markers
  14592. * @extends Ext.draw.sprite.Instancing
  14593. *
  14594. * Marker sprite. A specialized version of instancing sprite that groups instances.
  14595. * Putting a marker is grouped by its category id. Clearing removes that category.
  14596. */
  14597. Ext.define('Ext.chart.Markers', {
  14598. extend: 'Ext.draw.sprite.Instancing',
  14599. isMarkers: true,
  14600. defaultCategory: 'default',
  14601. constructor: function() {
  14602. this.callParent(arguments);
  14603. // `categories` maps category names to a map that maps instance index in category to its
  14604. // global index: categoryName: {instanceIndexInCategory: globalInstanceIndex}
  14605. this.categories = {};
  14606. // The `revisions` map keeps revision numbers of instance categories.
  14607. // When a marker (instance) is put (created or updated), it gets the revision
  14608. // of the category. When a category is cleared, its revision is incremented,
  14609. // but its instances are not removed.
  14610. // An instance is only rendered if its revision matches category revision.
  14611. // In other words, a marker has to be put again after its category has been cleared
  14612. // or it won't render.
  14613. this.revisions = {};
  14614. },
  14615. destroy: function() {
  14616. this.categories = null;
  14617. this.revisions = null;
  14618. this.callParent();
  14619. },
  14620. getMarkerFor: function(category, index) {
  14621. var categoryInstances;
  14622. if (category in this.categories) {
  14623. categoryInstances = this.categories[category];
  14624. if (index in categoryInstances) {
  14625. return this.get(categoryInstances[index]);
  14626. }
  14627. }
  14628. },
  14629. /**
  14630. * Clears the markers in the category.
  14631. * @param {String} category
  14632. */
  14633. clear: function(category) {
  14634. category = category || this.defaultCategory;
  14635. if (!(category in this.revisions)) {
  14636. this.revisions[category] = 1;
  14637. } else {
  14638. this.revisions[category]++;
  14639. }
  14640. },
  14641. clearAll: function() {
  14642. this.callParent();
  14643. this.categories = {};
  14644. this.revisions = {};
  14645. },
  14646. /**
  14647. * Puts a marker in the category with additional attributes.
  14648. * @param {String} category
  14649. * @param {Object} attr
  14650. * @param {String|Number} index
  14651. * @param {Boolean} [bypassNormalization]
  14652. * @param {Boolean} [keepRevision]
  14653. */
  14654. putMarkerFor: function(category, attr, index, bypassNormalization, keepRevision) {
  14655. var me = this,
  14656. categoryInstances, instance;
  14657. category = category || this.defaultCategory;
  14658. categoryInstances = me.categories[category] || (me.categories[category] = {});
  14659. if (index in categoryInstances) {
  14660. me.setAttributesFor(categoryInstances[index], attr, bypassNormalization);
  14661. } else {
  14662. // get the index of the instance created on next line
  14663. categoryInstances[index] = me.getCount();
  14664. me.add(attr, bypassNormalization);
  14665. }
  14666. instance = me.get(categoryInstances[index]);
  14667. if (instance) {
  14668. instance.category = category;
  14669. if (!keepRevision) {
  14670. instance.revision = me.revisions[category] || (me.revisions[category] = 1);
  14671. }
  14672. }
  14673. },
  14674. /**
  14675. *
  14676. * @param {String} category
  14677. * @param {Mixed} index
  14678. * @param {Boolean} [isWithoutTransform]
  14679. */
  14680. getMarkerBBoxFor: function(category, index, isWithoutTransform) {
  14681. var categoryInstances;
  14682. if (category in this.categories) {
  14683. categoryInstances = this.categories[category];
  14684. if (index in categoryInstances) {
  14685. return this.getBBoxFor(categoryInstances[index], isWithoutTransform);
  14686. }
  14687. }
  14688. },
  14689. getBBox: function() {
  14690. return null;
  14691. },
  14692. render: function(surface, ctx, rect) {
  14693. var me = this,
  14694. surfaceRect = surface.getRect(),
  14695. revisions = me.revisions,
  14696. mat = me.attr.matrix,
  14697. template = me.getTemplate(),
  14698. templateAttr = template.attr,
  14699. ln = me.instances.length,
  14700. instance, i;
  14701. mat.toContext(ctx);
  14702. template.preRender(surface, ctx, rect);
  14703. template.useAttributes(ctx, surfaceRect);
  14704. for (i = 0; i < ln; i++) {
  14705. instance = me.get(i);
  14706. if (instance.hidden || instance.revision !== revisions[instance.category]) {
  14707. continue;
  14708. }
  14709. ctx.save();
  14710. template.attr = instance;
  14711. template.useAttributes(ctx, surfaceRect);
  14712. template.render(surface, ctx, rect);
  14713. ctx.restore();
  14714. }
  14715. template.attr = templateAttr;
  14716. }
  14717. });
  14718. /**
  14719. * This is a modifier to place labels and callouts by additional attributes.
  14720. */
  14721. Ext.define('Ext.chart.modifier.Callout', {
  14722. extend: 'Ext.draw.modifier.Modifier',
  14723. alternateClassName: 'Ext.chart.label.Callout',
  14724. prepareAttributes: function(attr) {
  14725. if (!attr.hasOwnProperty('calloutOriginal')) {
  14726. attr.calloutOriginal = Ext.Object.chain(attr);
  14727. // No __proto__, nor getPrototypeOf in IE8,
  14728. // so manually saving a reference to 'attr' after chaining.
  14729. attr.calloutOriginal.prototype = attr;
  14730. }
  14731. if (this._lower) {
  14732. this._lower.prepareAttributes(attr.calloutOriginal);
  14733. }
  14734. },
  14735. setAttrs: function(attr, changes) {
  14736. var callout = attr.callout,
  14737. origin = attr.calloutOriginal,
  14738. bbox = attr.bbox.plain,
  14739. width = (bbox.width || 0) + attr.labelOverflowPadding,
  14740. height = (bbox.height || 0) + attr.labelOverflowPadding,
  14741. dx, dy, rotationRads, x, y, calloutPlaceX, calloutPlaceY, calloutVertical, temp;
  14742. if ('callout' in changes) {
  14743. callout = changes.callout;
  14744. }
  14745. if ('callout' in changes || 'calloutPlaceX' in changes || 'calloutPlaceY' in changes || 'x' in changes || 'y' in changes) {
  14746. rotationRads = 'rotationRads' in changes ? origin.rotationRads = changes.rotationRads : origin.rotationRads;
  14747. x = 'x' in changes ? (origin.x = changes.x) : origin.x;
  14748. y = 'y' in changes ? (origin.y = changes.y) : origin.y;
  14749. calloutPlaceX = 'calloutPlaceX' in changes ? changes.calloutPlaceX : attr.calloutPlaceX;
  14750. calloutPlaceY = 'calloutPlaceY' in changes ? changes.calloutPlaceY : attr.calloutPlaceY;
  14751. calloutVertical = 'calloutVertical' in changes ? changes.calloutVertical : attr.calloutVertical;
  14752. // Normalize Rotations
  14753. rotationRads %= Math.PI * 2;
  14754. if (Math.cos(rotationRads) < 0) {
  14755. rotationRads = (rotationRads + Math.PI) % (Math.PI * 2);
  14756. }
  14757. if (rotationRads > Math.PI) {
  14758. rotationRads -= Math.PI * 2;
  14759. }
  14760. if (calloutVertical) {
  14761. rotationRads = rotationRads * (1 - callout) - Math.PI / 2 * callout;
  14762. temp = width;
  14763. width = height;
  14764. height = temp;
  14765. } else {
  14766. rotationRads = rotationRads * (1 - callout);
  14767. }
  14768. changes.rotationRads = rotationRads;
  14769. // Placing a label in the middle of a pie slice (x/y)
  14770. // if callout doesn't exists (callout=0),
  14771. // or outside the pie slice (calloutPlaceX/Y) if it does (callout=1).
  14772. changes.x = x * (1 - callout) + calloutPlaceX * callout;
  14773. changes.y = y * (1 - callout) + calloutPlaceY * callout;
  14774. dx = calloutPlaceX - x;
  14775. dy = calloutPlaceY - y;
  14776. // Finding where the callout line intersects the bbox of the label
  14777. // if it were to go to the center of the label,
  14778. // and make that intersection point the end of the callout line.
  14779. // Effectively, the end of the callout line traces label's bbox when chart is rotated.
  14780. if (Math.abs(dy * width) > Math.abs(dx * height)) {
  14781. // on top/bottom
  14782. if (dy > 0) {
  14783. changes.calloutEndX = changes.x - (height / 2) * (dx / dy) * callout;
  14784. changes.calloutEndY = changes.y - (height / 2) * callout;
  14785. } else {
  14786. changes.calloutEndX = changes.x + (height / 2) * (dx / dy) * callout;
  14787. changes.calloutEndY = changes.y + (height / 2) * callout;
  14788. }
  14789. } else {
  14790. // on left/right
  14791. if (dx > 0) {
  14792. changes.calloutEndX = changes.x - width / 2;
  14793. changes.calloutEndY = changes.y - (width / 2) * (dy / dx) * callout;
  14794. } else {
  14795. changes.calloutEndX = changes.x + width / 2;
  14796. changes.calloutEndY = changes.y + (width / 2) * (dy / dx) * callout;
  14797. }
  14798. }
  14799. // Since the length of the callout line is adjusted depending on the label's position
  14800. // and dimensions, we hide the callout line if the length becomes negative.
  14801. if (changes.calloutStartX && changes.calloutStartY) {
  14802. 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);
  14803. } else {
  14804. changes.calloutHasLine = true;
  14805. }
  14806. }
  14807. return changes;
  14808. },
  14809. pushDown: function(attr, changes) {
  14810. changes = this.callParent([
  14811. attr.calloutOriginal,
  14812. changes
  14813. ]);
  14814. return this.setAttrs(attr, changes);
  14815. },
  14816. popUp: function(attr, changes) {
  14817. attr = attr.prototype;
  14818. changes = this.setAttrs(attr, changes);
  14819. if (this._upper) {
  14820. return this._upper.popUp(attr, changes);
  14821. } else {
  14822. return Ext.apply(attr, changes);
  14823. }
  14824. }
  14825. });
  14826. /**
  14827. * @class Ext.chart.sprite.Label
  14828. * @extends Ext.draw.sprite.Text
  14829. *
  14830. * Sprite used to represent labels in series.
  14831. *
  14832. * Important: the actual default values are determined by the theme used.
  14833. * Please see the `label` config of the {@link Ext.chart.theme.Base#axis}.
  14834. */
  14835. Ext.define('Ext.chart.sprite.Label', {
  14836. extend: 'Ext.draw.sprite.Text',
  14837. alternateClassName: 'Ext.chart.label.Label',
  14838. requires: [
  14839. 'Ext.chart.modifier.Callout'
  14840. ],
  14841. inheritableStatics: {
  14842. def: {
  14843. processors: {
  14844. callout: 'limited01',
  14845. // Meant to be set by the Callout modifier only.
  14846. calloutHasLine: 'bool',
  14847. // The position where the callout would end, if not for the label:
  14848. // callout stops at the bounding box of the label,
  14849. // so the actual point where the callout ends - calloutEndX/Y -
  14850. // is calculated by the Callout modifier.
  14851. calloutPlaceX: 'number',
  14852. calloutPlaceY: 'number',
  14853. // The start/end points used to render the callout line.
  14854. calloutStartX: 'number',
  14855. calloutStartY: 'number',
  14856. calloutEndX: 'number',
  14857. calloutEndY: 'number',
  14858. calloutColor: 'color',
  14859. calloutWidth: 'number',
  14860. calloutVertical: 'bool',
  14861. labelOverflowPadding: 'number',
  14862. display: 'enums(none,under,over,rotate,insideStart,insideEnd,inside,outside)',
  14863. orientation: 'enums(horizontal,vertical)',
  14864. renderer: 'default'
  14865. },
  14866. defaults: {
  14867. callout: 0,
  14868. calloutHasLine: true,
  14869. calloutPlaceX: 0,
  14870. calloutPlaceY: 0,
  14871. calloutStartX: 0,
  14872. calloutStartY: 0,
  14873. calloutEndX: 0,
  14874. calloutEndY: 0,
  14875. calloutWidth: 1,
  14876. calloutVertical: false,
  14877. calloutColor: 'black',
  14878. labelOverflowPadding: 5,
  14879. display: 'none',
  14880. orientation: '',
  14881. renderer: null
  14882. },
  14883. triggers: {
  14884. callout: 'transform',
  14885. calloutPlaceX: 'transform',
  14886. calloutPlaceY: 'transform',
  14887. labelOverflowPadding: 'transform',
  14888. calloutRotation: 'transform',
  14889. display: 'hidden'
  14890. },
  14891. updaters: {
  14892. hidden: function(attr) {
  14893. attr.hidden = (attr.display === 'none');
  14894. }
  14895. }
  14896. }
  14897. },
  14898. config: {
  14899. /**
  14900. * @cfg {Object} fx Animation configuration.
  14901. */
  14902. animation: {
  14903. customDurations: {
  14904. callout: 200
  14905. }
  14906. },
  14907. /**
  14908. * @cfg {String} field The store record field used by the label sprite.
  14909. *
  14910. * Note: the label sprite is typically used indirectly (by a Ext.chart.MarkerHolder
  14911. * series sprite, via a Ext.chart.Markers sprite, where the latter is passed to the
  14912. * label renderer), so to get to the label field one has to do:
  14913. *
  14914. * renderer: function (text, sprite, config, data, index) {
  14915. * var field = sprite.getTemplate().getField();
  14916. * }
  14917. *
  14918. * To get the actual label sprite instance one can use:
  14919. *
  14920. * sprite.get(index)
  14921. *
  14922. */
  14923. field: null,
  14924. /**
  14925. * @cfg {Boolean|Object} calloutLine
  14926. *
  14927. * True to draw a line between the label and the chart with the default settings,
  14928. * or an Object that defines the 'color', 'width' and 'length' properties of the line.
  14929. * This config is only applicable when the label is displayed outside the chart.
  14930. *
  14931. * Default value: false.
  14932. */
  14933. calloutLine: true,
  14934. /**
  14935. * @cfg {Number} [hideLessThan=20]
  14936. * Hides labels for pie slices with segment length less than this value (in pixels).
  14937. */
  14938. hideLessThan: 20
  14939. },
  14940. applyCalloutLine: function(calloutLine) {
  14941. if (calloutLine) {
  14942. return Ext.apply({}, calloutLine);
  14943. }
  14944. return calloutLine;
  14945. },
  14946. createModifiers: function() {
  14947. var me = this,
  14948. mods = me.callParent(arguments);
  14949. mods.callout = new Ext.chart.modifier.Callout({
  14950. sprite: me
  14951. });
  14952. mods.animation.setUpper(mods.callout);
  14953. mods.callout.setUpper(mods.target);
  14954. },
  14955. render: function(surface, ctx) {
  14956. var me = this,
  14957. attr = me.attr,
  14958. calloutColor = attr.calloutColor;
  14959. ctx.save();
  14960. ctx.globalAlpha *= attr.callout;
  14961. if (ctx.globalAlpha > 0 && attr.calloutHasLine) {
  14962. if (calloutColor && calloutColor.isGradient) {
  14963. calloutColor = calloutColor.getStops()[0].color;
  14964. }
  14965. ctx.strokeStyle = calloutColor;
  14966. ctx.fillStyle = calloutColor;
  14967. ctx.lineWidth = attr.calloutWidth;
  14968. ctx.beginPath();
  14969. ctx.moveTo(me.attr.calloutStartX, me.attr.calloutStartY);
  14970. ctx.lineTo(me.attr.calloutEndX, me.attr.calloutEndY);
  14971. ctx.stroke();
  14972. ctx.beginPath();
  14973. ctx.arc(me.attr.calloutStartX, me.attr.calloutStartY, 1 * attr.calloutWidth, 0, 2 * Math.PI, true);
  14974. ctx.fill();
  14975. ctx.beginPath();
  14976. ctx.arc(me.attr.calloutEndX, me.attr.calloutEndY, 1 * attr.calloutWidth, 0, 2 * Math.PI, true);
  14977. ctx.fill();
  14978. }
  14979. ctx.restore();
  14980. Ext.draw.sprite.Text.prototype.render.apply(me, arguments);
  14981. }
  14982. });
  14983. /**
  14984. * Series is the abstract class containing the common logic to all chart series.
  14985. * Series includes methods from Labels, Highlights, and Callouts mixins. This class
  14986. * implements the logic of animating, hiding, showing all elements and returning the
  14987. * color of the series to be used as a legend item.
  14988. *
  14989. * ## Listeners
  14990. *
  14991. * The series class supports listeners via the Observable syntax.
  14992. *
  14993. * For example:
  14994. *
  14995. * Ext.create('Ext.chart.CartesianChart', {
  14996. * plugins: {
  14997. * chartitemevents: {
  14998. * moveEvents: true
  14999. * }
  15000. * },
  15001. * store: {
  15002. * fields: ['pet', 'households', 'total'],
  15003. * data: [
  15004. * {pet: 'Cats', households: 38, total: 93},
  15005. * {pet: 'Dogs', households: 45, total: 79},
  15006. * {pet: 'Fish', households: 13, total: 171}
  15007. * ]
  15008. * },
  15009. * axes: [{
  15010. * type: 'numeric',
  15011. * position: 'left'
  15012. * }, {
  15013. * type: 'category',
  15014. * position: 'bottom'
  15015. * }],
  15016. * series: [{
  15017. * type: 'bar',
  15018. * xField: 'pet',
  15019. * yField: 'households',
  15020. * listeners: {
  15021. * itemmousemove: function (series, item, event) {
  15022. * console.log('itemmousemove', item.category, item.field);
  15023. * }
  15024. * }
  15025. * }, {
  15026. * type: 'line',
  15027. * xField: 'pet',
  15028. * yField: 'total',
  15029. * marker: true
  15030. * }]
  15031. * });
  15032. *
  15033. */
  15034. Ext.define('Ext.chart.series.Series', {
  15035. requires: [
  15036. 'Ext.chart.Util',
  15037. 'Ext.chart.Markers',
  15038. 'Ext.chart.sprite.Label',
  15039. 'Ext.tip.ToolTip'
  15040. ],
  15041. mixins: [
  15042. 'Ext.mixin.Observable',
  15043. 'Ext.mixin.Bindable'
  15044. ],
  15045. isSeries: true,
  15046. defaultBindProperty: 'store',
  15047. /**
  15048. * @property {String} type
  15049. * The type of series. Set in subclasses.
  15050. * @protected
  15051. */
  15052. type: null,
  15053. /**
  15054. * @property {String} seriesType
  15055. * Default series sprite type.
  15056. */
  15057. seriesType: 'sprite',
  15058. identifiablePrefix: 'ext-line-',
  15059. observableType: 'series',
  15060. darkerStrokeRatio: 0.15,
  15061. /**
  15062. * @event itemmousemove
  15063. * Fires when the mouse is moved on a series item.
  15064. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15065. * plugin be added to the chart.
  15066. * @param {Ext.chart.series.Series} series
  15067. * @param {Object} item
  15068. * @param {Event} event
  15069. */
  15070. /**
  15071. * @event itemmouseup
  15072. * Fires when a mouseup event occurs on a series item.
  15073. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15074. * plugin be added to the chart.
  15075. * @param {Ext.chart.series.Series} series
  15076. * @param {Object} item
  15077. * @param {Event} event
  15078. */
  15079. /**
  15080. * @event itemmousedown
  15081. * Fires when a mousedown event occurs on a series item.
  15082. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15083. * plugin be added to the chart.
  15084. * @param {Ext.chart.series.Series} series
  15085. * @param {Object} item
  15086. * @param {Event} event
  15087. */
  15088. /**
  15089. * @event itemmouseover
  15090. * Fires when the mouse enters a series item.
  15091. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15092. * plugin be added to the chart.
  15093. * @param {Ext.chart.series.Series} series
  15094. * @param {Object} item
  15095. * @param {Event} event
  15096. */
  15097. /**
  15098. * @event itemmouseout
  15099. * Fires when the mouse exits a series item.
  15100. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15101. * plugin be added to the chart.
  15102. * @param {Ext.chart.series.Series} series
  15103. * @param {Object} item
  15104. * @param {Event} event
  15105. */
  15106. /**
  15107. * @event itemclick
  15108. * Fires when a click event occurs on a series item.
  15109. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15110. * plugin be added to the chart.
  15111. * @param {Ext.chart.series.Series} series
  15112. * @param {Object} item
  15113. * @param {Event} event
  15114. */
  15115. /**
  15116. * @event itemdblclick
  15117. * Fires when a double click event occurs on a series item.
  15118. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15119. * plugin be added to the chart.
  15120. * @param {Ext.chart.series.Series} series
  15121. * @param {Object} item
  15122. * @param {Event} event
  15123. */
  15124. /**
  15125. * @event itemtap
  15126. * Fires when a tap event occurs on a series item.
  15127. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  15128. * plugin be added to the chart.
  15129. * @param {Ext.chart.series.Series} series
  15130. * @param {Object} item
  15131. * @param {Event} event
  15132. */
  15133. /**
  15134. * @event chartattached
  15135. * Fires when the {@link Ext.chart.AbstractChart} has been attached to this series.
  15136. * @param {Ext.chart.AbstractChart} chart
  15137. * @param {Ext.chart.series.Series} series
  15138. */
  15139. /**
  15140. * @event chartdetached
  15141. * Fires when the {@link Ext.chart.AbstractChart} has been detached from this series.
  15142. * @param {Ext.chart.AbstractChart} chart
  15143. * @param {Ext.chart.series.Series} series
  15144. */
  15145. /**
  15146. * @event storechange
  15147. * Fires when the store of the series changes.
  15148. * @param {Ext.chart.series.Series} series
  15149. * @param {Ext.data.Store} newStore
  15150. * @param {Ext.data.Store} oldStore
  15151. */
  15152. config: {
  15153. /**
  15154. * @private
  15155. * @cfg {Object} chart The chart that the series is bound.
  15156. */
  15157. chart: null,
  15158. /**
  15159. * @cfg {String|String[]} title
  15160. * The human-readable name of the series (displayed in the legend).
  15161. * If the series is stacked (has multiple components in it) this
  15162. * should be an array, where each string corresponds to a stacked component.
  15163. */
  15164. title: null,
  15165. /**
  15166. * @cfg {Function} renderer
  15167. * A function that can be provided to set custom styling properties to each
  15168. * rendered element. It receives `(sprite, config, rendererData, index)`
  15169. * as parameters.
  15170. *
  15171. * @param {Object} sprite The sprite affected by the renderer.
  15172. * The visual attributes are in `sprite.attr`.
  15173. * The data field is available in `sprite.getField()`.
  15174. * @param {Object} config The sprite configuration, which varies with the series
  15175. * and the type of sprite. For instance, a Line chart sprite might have just the
  15176. * `x` and `y` properties while a Bar chart sprite also has `width` and `height`.
  15177. * A `type` might be present too. For instance to draw each marker and each segment
  15178. * of a Line chart, the renderer is called with the `config.type` set to either
  15179. * `marker` or `line`.
  15180. * @param {Object} rendererData A record with different properties depending on
  15181. * the type of chart. The only guaranteed property is `rendererData.store`, the
  15182. * store used by the series. In some cases, a store may not exist: for instance
  15183. * a Gauge chart may read its value directly from its configuration; in this case
  15184. * rendererData.store is null and the value is available in rendererData.value.
  15185. * @param {Number} index The index of the sprite. It is usually the index of the
  15186. * store record associated with the sprite, in which case the record can be obtained
  15187. * with `store.getData().items[index]`. If the chart is not associated with a store,
  15188. * the index represents the index of the sprite within the series. For instance
  15189. * a Gauge chart may have as many sprites as there are sectors in the background of
  15190. * the gauge, plus one for the needle.
  15191. *
  15192. * @return {Object} The attributes that have been changed or added.
  15193. * Note: it is usually possible to add or modify the attributes directly into the
  15194. * `config` parameter and not return anything, but returning an object with only
  15195. * those attributes that have been changed may allow for optimizations in the
  15196. * rendering of some series. Example to draw every other marker in red:
  15197. *
  15198. * renderer: function (sprite, config, rendererData, index) {
  15199. * if (config.type === 'marker') {
  15200. * return { strokeStyle: (index % 2 === 0 ? 'red' : 'black') };
  15201. * }
  15202. * }
  15203. *
  15204. * @controllable
  15205. */
  15206. renderer: null,
  15207. /**
  15208. * @cfg {Boolean} showInLegend
  15209. * Whether to show this series in the legend.
  15210. */
  15211. showInLegend: true,
  15212. /**
  15213. * @private
  15214. * Trigger drawlistener flag
  15215. */
  15216. triggerAfterDraw: false,
  15217. /**
  15218. * @private
  15219. */
  15220. theme: null,
  15221. /**
  15222. * @cfg {Object} style Custom style configuration for the sprite used in the series.
  15223. * It overrides the style that is provided by the current theme.
  15224. */
  15225. style: {},
  15226. /**
  15227. * @cfg {Object} subStyle This is the cyclic used if the series has multiple sprites.
  15228. */
  15229. subStyle: {},
  15230. /**
  15231. * @private
  15232. * @cfg {Object} themeStyle Style configuration that is provided by the current theme.
  15233. * It is composed of five objects:
  15234. * @cfg {Object} themeStyle.style Properties common to all the series,
  15235. * for instance the 'lineWidth'.
  15236. * @cfg {Object} themeStyle.subStyle Cyclic used if the series has multiple sprites.
  15237. * @cfg {Object} themeStyle.label Sprite config for the labels,
  15238. * for instance the font and color.
  15239. * @cfg {Object} themeStyle.marker Sprite config for the markers,
  15240. * for instance the size and stroke color.
  15241. * @cfg {Object} themeStyle.markerSubStyle Cyclic used if series have multiple marker
  15242. * sprites.
  15243. */
  15244. themeStyle: {},
  15245. /**
  15246. * @cfg {Array} colors
  15247. * An array of color values which is used, in order of appearance, by the series.
  15248. * Each series can request one or more colors from the array. Radar, Scatter or Line charts
  15249. * require just one color each. Candlestick and OHLC require two
  15250. * (1 for drops + 1 for rises). Pie charts and Stacked charts (like Bar or Pie charts)
  15251. * require one color for each data category they represent, so one color for each slice
  15252. * of a Pie chart or each segment (not bar) of a Bar chart.
  15253. * It overrides the colors that are provided by the current theme.
  15254. */
  15255. colors: null,
  15256. /**
  15257. * @cfg {Boolean|Number} useDarkerStrokeColor
  15258. * Colors for the series can be set directly through the 'colors' config, or indirectly
  15259. * with the current theme or the 'colors' config that is set onto the chart. These colors
  15260. * are used as "fill color". Set this config to true, if you want a darker color for the
  15261. * strokes. Set it to false if you want to use the same color as the fill color.
  15262. * Alternatively, you can set it to a number between 0 and 1 to control how much darker
  15263. * the strokes should be.
  15264. * Note: this should be initial config and cannot be changed later on.
  15265. */
  15266. useDarkerStrokeColor: true,
  15267. /**
  15268. * @cfg {Object} store The store to use for this series. If not specified,
  15269. * the series will use the chart's {@link Ext.chart.AbstractChart#store store}.
  15270. */
  15271. store: null,
  15272. /**
  15273. * @cfg {Object} label
  15274. * Object with the following properties:
  15275. *
  15276. * @cfg {String} label.display
  15277. *
  15278. * Specifies the presence and position of the labels.
  15279. * The possible values depend on the series type.
  15280. * For Line and Scatter series: 'under' | 'over' | 'rotate'.
  15281. * For Bar and 3D Bar series: 'insideStart' | 'insideEnd' | 'outside'.
  15282. * For Pie series: 'inside' | 'outside' | 'rotate' | 'horizontal' | 'vertical'.
  15283. * Area, Radar and Candlestick series don't support labels.
  15284. * For Area and Radar series please consider using {@link #tooltip tooltips} instead.
  15285. * 3D Pie series currently always display labels 'outside'.
  15286. * For all series: 'none' hides the labels.
  15287. *
  15288. * Default value: 'none'.
  15289. *
  15290. * @cfg {String} label.color
  15291. *
  15292. * The color of the label text.
  15293. *
  15294. * Default value: '#000' (black).
  15295. *
  15296. * @cfg {String|String[]} label.field
  15297. *
  15298. * The name(s) of the field(s) to be displayed in the labels. If your chart has 3 series
  15299. * that correspond to the fields 'a', 'b', and 'c' of your model, and you only want to
  15300. * display labels for the series 'c', you must still provide an array `[null, null, 'c']`.
  15301. *
  15302. * Default value: null.
  15303. *
  15304. * @cfg {String} label.font
  15305. *
  15306. * The font used for the labels.
  15307. *
  15308. * Default value: '14px Helvetica'.
  15309. *
  15310. * @cfg {String} label.orientation
  15311. *
  15312. * Either 'horizontal' or 'vertical'. If not set (default), the orientation is inferred
  15313. * from the value of the flipXY property of the series.
  15314. *
  15315. * Default value: ''.
  15316. *
  15317. * @cfg {Function} label.renderer
  15318. *
  15319. * Optional function for formatting the label into a displayable value.
  15320. *
  15321. * The arguments to the method are:
  15322. *
  15323. * - *`text`*, *`sprite`*, *`config`*, *`rendererData`*, *`index`*
  15324. *
  15325. * Label's renderer is passed the same arguments as {@link #renderer}
  15326. * plus one extra 'text' argument which comes first.
  15327. *
  15328. * @return {Object|String} The attributes that have been changed or added,
  15329. * or the text for the label.
  15330. * Example to enclose every other label in parentheses:
  15331. *
  15332. * renderer: function (text) {
  15333. * if (index % 2 == 0) {
  15334. * return '(' + text + ')'
  15335. * }
  15336. * }
  15337. */
  15338. label: null,
  15339. /**
  15340. * @cfg {Number} labelOverflowPadding
  15341. * Extra distance value for which the labelOverflow listener is triggered.
  15342. */
  15343. labelOverflowPadding: null,
  15344. /**
  15345. * @cfg {Boolean} showMarkers
  15346. * Whether markers should be displayed at the data points along the line. If true,
  15347. * then the {@link #marker} config item will determine the markers' styling.
  15348. */
  15349. showMarkers: true,
  15350. /**
  15351. * @cfg {Object|Boolean} marker
  15352. * The sprite template used by marker instances on the series.
  15353. * If the value of the marker config is set to `true` or the type
  15354. * of the sprite instance is not specified, the {@link Ext.draw.sprite.Circle}
  15355. * sprite will be used.
  15356. *
  15357. * Examples:
  15358. *
  15359. * marker: true
  15360. *
  15361. * marker: {
  15362. * radius: 8
  15363. * }
  15364. *
  15365. * marker: {
  15366. * type: 'arrow',
  15367. * animation: {
  15368. * duration: 200,
  15369. * easing: 'backOut'
  15370. * }
  15371. * }
  15372. */
  15373. marker: null,
  15374. /**
  15375. * @cfg {Object} markerSubStyle
  15376. * This is cyclic used if series have multiple marker sprites.
  15377. */
  15378. markerSubStyle: null,
  15379. /**
  15380. * @protected
  15381. * @cfg {Object} itemInstancing
  15382. * The sprite template used to create sprite instances in the series.
  15383. */
  15384. itemInstancing: null,
  15385. /**
  15386. * @cfg {Object} background
  15387. * Sets the background of the surface the series is attached.
  15388. */
  15389. background: null,
  15390. /**
  15391. * @protected
  15392. * @cfg {Ext.draw.Surface} surface
  15393. * The chart surface used to render series sprites.
  15394. */
  15395. surface: null,
  15396. /**
  15397. * @protected
  15398. * @cfg {Object} overlaySurface
  15399. * The surface used to render series labels.
  15400. */
  15401. overlaySurface: null,
  15402. /**
  15403. * @cfg {Boolean|Array} hidden
  15404. */
  15405. hidden: false,
  15406. /**
  15407. * @cfg {Boolean/Object} highlight
  15408. * The sprite attributes that will be applied to the highlighted items in the series.
  15409. * If set to 'true', the default highlight style from {@link #highlightCfg} will be used.
  15410. * If the value of this config is an object, it will be merged with the
  15411. * {@link #highlightCfg}. In case merging of 'highlight' and 'highlightCfg' configs
  15412. * in not the desired behavior, provide the 'highlightCfg' instead.
  15413. */
  15414. highlight: false,
  15415. /**
  15416. * @protected
  15417. * @cfg {Object} highlightCfg
  15418. * The default style for the highlighted item.
  15419. * Used when {@link #highlight} config was simply set to 'true' instead of specifying
  15420. * a style.
  15421. */
  15422. highlightCfg: {
  15423. // Make custom highlightCfg's in subclasses replace this one.
  15424. merge: function(value) {
  15425. return value;
  15426. },
  15427. $value: {
  15428. fillStyle: 'yellow',
  15429. strokeStyle: 'red'
  15430. }
  15431. },
  15432. /**
  15433. * @cfg {Object} animation The series animation configuration.
  15434. * By default, the series is using the same animation the chart uses,
  15435. * if it's own animation is not explicitly configured.
  15436. */
  15437. animation: null,
  15438. /**
  15439. * @cfg {Object} tooltip
  15440. * Add tooltips to the visualization's markers. The config options for the
  15441. * tooltip are the same configuration used with {@link Ext.tip.ToolTip} plus a
  15442. * `renderer` config option and a `scope` for the renderer. For example:
  15443. *
  15444. * tooltip: {
  15445. * trackMouse: true,
  15446. * width: 140,
  15447. * height: 28,
  15448. * renderer: function (toolTip, record, ctx) {
  15449. * toolTip.setHtml(record.get('name') + ': ' + record.get('data1') + ' views');
  15450. * }
  15451. * }
  15452. *
  15453. * Note that tooltips are shown for series markers and won't work
  15454. * if the {@link #marker} is not configured.
  15455. *
  15456. * You can also configure
  15457. * {@link Ext.chart.interactions.ItemHighlight#multiTooltips}
  15458. * to display multiple tooltips for adjacent or overlapping Line series
  15459. * data points within {@link Ext.chart.series.Line#selectionTolerance} radius.
  15460. *
  15461. * @cfg {Object} tooltip.scope The scope to use when the renderer function is
  15462. * called. Defaults to the Series instance.
  15463. * @cfg {Function} tooltip.renderer An 'interceptor' method which can be used to
  15464. * modify the tooltip attributes before it is shown. The renderer function is
  15465. * passed the following params:
  15466. * @cfg {Ext.tip.ToolTip} tooltip.renderer.toolTip The tooltip instance
  15467. * @cfg {Ext.data.Model} tooltip.renderer.record The record instance for the
  15468. * chart item (sprite) currently targeted by the tooltip.
  15469. * @cfg {Object} tooltip.renderer.ctx A data object with values relating to the
  15470. * currently targeted chart sprite
  15471. * @cfg {String} tooltip.renderer.ctx.category The type of sprite passed to the
  15472. * renderer function (will be "items", "markers", or "labels" depending on the
  15473. * target sprite of the tooltip)
  15474. * @cfg {String} tooltip.renderer.ctx.field The {@link #yField} for the series
  15475. * @cfg {Number} tooltip.renderer.ctx.index The target sprite's index within the
  15476. * series' items
  15477. * @cfg {Ext.data.Model} tooltip.renderer.ctx.record The record instance for the
  15478. * chart item (sprite) currently targeted by the tooltip.
  15479. * @cfg {Ext.chart.series.Series} tooltip.renderer.ctx.series The series instance
  15480. * containing the tooltip's target sprite
  15481. * @cfg {Ext.draw.sprite.Sprite} tooltip.renderer.ctx.sprite The sprite (item)
  15482. * target of the tooltip
  15483. */
  15484. tooltip: null
  15485. },
  15486. directions: [],
  15487. sprites: null,
  15488. /**
  15489. * @private
  15490. * Returns the number of colors this series needs.
  15491. * A Pie chart needs one color per slice while a Stacked Bar chart needs one per segment.
  15492. * An OHLC chart needs 2 colors (one for drops, one for rises), and most other charts
  15493. * need just a single color.
  15494. */
  15495. themeColorCount: function() {
  15496. return 1;
  15497. },
  15498. /**
  15499. * @private
  15500. * @property
  15501. * Series, where the number of sprites (an so unique colors they require)
  15502. * depends on the number of records in the store should set this to 'true'.
  15503. */
  15504. isStoreDependantColorCount: false,
  15505. /**
  15506. * @private
  15507. * Returns the number of markers this series needs.
  15508. * Currently, only the Line, Scatter and Radar series use markers - and they need
  15509. * just one each.
  15510. */
  15511. themeMarkerCount: function() {
  15512. return 0;
  15513. },
  15514. /**
  15515. * @private
  15516. * Each series has configs that tell which store record fields to use as data
  15517. * for a certain dimension. For example, `xField`, `yField` for most cartesian series,
  15518. * `angleField`, `radiusField` for polar series, `openField`, ..., `closeField`
  15519. * for CandleStick series, etc. The field category is an array of capitalized config
  15520. * names, minus the 'Field' part, to use as data for a certain dimension.
  15521. * For example, for CandleStick series we have:
  15522. *
  15523. * fieldCategoryY: ['Open', 'High', 'Low', 'Close']
  15524. *
  15525. * While for generic Cartesian series it is simply:
  15526. *
  15527. * fieldCategoryY: ['Y']
  15528. *
  15529. * This method fetches the values of those configs, i.e. the actual record fields to use.
  15530. *
  15531. * The {@link #coordinate} method in turn will use the values from the `fieldCategory`
  15532. * array to set data attributes of the series sprite. E.g., in case of CandleStick series,
  15533. * the following attributes will be set based on the values in the `fieldCategoryY` array:
  15534. *
  15535. * `dataOpen`, `dataHigh`, `dataLow`, `dataClose`
  15536. *
  15537. * Where the value of each attribute is a coordinated array of data from the corresponding
  15538. * field.
  15539. *
  15540. * @param {String[]} fieldCategory
  15541. * @return {String[]}
  15542. */
  15543. getFields: function(fieldCategory) {
  15544. var me = this,
  15545. fields = [],
  15546. ln = fieldCategory.length,
  15547. i, field;
  15548. for (i = 0; i < ln; i++) {
  15549. field = me['get' + fieldCategory[i] + 'Field']();
  15550. if (Ext.isArray(field)) {
  15551. fields.push.apply(fields, field);
  15552. } else {
  15553. fields.push(field);
  15554. }
  15555. }
  15556. return fields;
  15557. },
  15558. applyAnimation: function(animation, oldAnimation) {
  15559. var chart = this.getChart();
  15560. if (!chart.isSettingSeriesAnimation) {
  15561. this.isUserAnimation = true;
  15562. }
  15563. return Ext.chart.Util.applyAnimation(animation, oldAnimation);
  15564. },
  15565. updateAnimation: function(animation) {
  15566. var sprites = this.getSprites(),
  15567. itemsMarker, markersMarker, i, ln, sprite;
  15568. for (i = 0 , ln = sprites.length; i < ln; i++) {
  15569. sprite = sprites[i];
  15570. if (sprite.isMarkerHolder) {
  15571. itemsMarker = sprite.getMarker('items');
  15572. if (itemsMarker) {
  15573. itemsMarker.getTemplate().setAnimation(animation);
  15574. }
  15575. markersMarker = sprite.getMarker('markers');
  15576. if (markersMarker) {
  15577. markersMarker.getTemplate().setAnimation(animation);
  15578. }
  15579. }
  15580. sprite.setAnimation(animation);
  15581. }
  15582. },
  15583. getAnimation: function() {
  15584. var chart = this.getChart(),
  15585. animation;
  15586. if (chart && chart.animationSuspendCount) {
  15587. animation = {
  15588. duration: 0
  15589. };
  15590. } else {
  15591. if (this.isUserAnimation) {
  15592. animation = this.callParent();
  15593. } else {
  15594. animation = chart.getAnimation();
  15595. }
  15596. }
  15597. return animation;
  15598. },
  15599. updateTitle: function() {
  15600. var me = this,
  15601. chart = me.getChart();
  15602. if (chart && !chart.isInitializing) {
  15603. chart.refreshLegendStore();
  15604. }
  15605. },
  15606. applyHighlight: function(highlight, oldHighlight) {
  15607. var me = this,
  15608. highlightCfg = me.getHighlightCfg();
  15609. if (Ext.isObject(highlight)) {
  15610. highlight = Ext.merge({}, highlightCfg, highlight);
  15611. } else if (highlight === true) {
  15612. highlight = highlightCfg;
  15613. }
  15614. if (highlight) {
  15615. highlight.type = 'highlight';
  15616. }
  15617. return highlight && Ext.merge({}, oldHighlight, highlight);
  15618. },
  15619. updateHighlight: function(highlight) {
  15620. var me = this,
  15621. sprites = me.sprites,
  15622. highlightCfg = me.getHighlightCfg(),
  15623. i, ln, sprite, items, markers;
  15624. me.getStyle();
  15625. // Make sure the 'markers' sprite has been created,
  15626. // so that we can set the 'style' config of its 'highlight' modifier here.
  15627. me.getMarker();
  15628. if (!Ext.Object.isEmpty(highlight)) {
  15629. me.addItemHighlight();
  15630. for (i = 0 , ln = sprites.length; i < ln; i++) {
  15631. sprite = sprites[i];
  15632. if (sprite.isMarkerHolder) {
  15633. items = sprite.getMarker('items');
  15634. if (items) {
  15635. items.getTemplate().modifiers.highlight.setStyle(highlight);
  15636. }
  15637. markers = sprite.getMarker('markers');
  15638. if (markers) {
  15639. markers.getTemplate().modifiers.highlight.setStyle(highlight);
  15640. }
  15641. }
  15642. }
  15643. } else if (!Ext.Object.equals(highlightCfg, this.defaultConfig.highlightCfg)) {
  15644. this.addItemHighlight();
  15645. }
  15646. },
  15647. updateHighlightCfg: function(highlightCfg) {
  15648. // Make sure to add the 'itemhighlight' interaction to the series, if the default
  15649. // highlight style changes, even if the 'highlight' config isn't set (defaults to false),
  15650. // since we probably want to use item highlighting now or later, if we are changing
  15651. // the default highlight style.
  15652. // This updater will be triggered by the 'highlight' applier, and the 'addItemHighlight'
  15653. // call here will in turn call 'getHighlight' down the call stack, which will return
  15654. // 'undefined' since the value hasn't been processed yet. So we don't call
  15655. // 'addItemHighlight' here during configuration and instead call it in the 'highlight'
  15656. // updater, if it hasn't already been called ('highlight' config is set to 'false').
  15657. if (!this.isConfiguring && !Ext.Object.equals(highlightCfg, this.defaultConfig.highlightCfg)) {
  15658. this.addItemHighlight();
  15659. }
  15660. },
  15661. applyItemInstancing: function(config, oldConfig) {
  15662. if (config && oldConfig && (!config.type || config.type === oldConfig.type)) {
  15663. // Have to merge to a new object, or the updater won't be called.
  15664. config = Ext.merge({}, oldConfig, config);
  15665. }
  15666. if (config && !config.type) {
  15667. config = null;
  15668. }
  15669. return config;
  15670. },
  15671. setAttributesForItem: function(item, change) {
  15672. var sprite = item && item.sprite,
  15673. i;
  15674. if (sprite) {
  15675. if (sprite.isMarkerHolder && item.category === 'items') {
  15676. sprite.putMarker(item.category, change, item.index, false, true);
  15677. }
  15678. if (sprite.isMarkerHolder && item.category === 'markers') {
  15679. sprite.putMarker(item.category, change, item.index, false, true);
  15680. } else if (sprite.isInstancing) {
  15681. sprite.setAttributesFor(item.index, change);
  15682. } else if (Ext.isArray(sprite)) {
  15683. // In some instances, like with the 3D pie series,
  15684. // an item can be composed of multiple sprites
  15685. // (e.g. 8 sprites are used to render a single 3D pie slice).
  15686. for (i = 0; i < sprite.length; i++) {
  15687. sprite[i].setAttributes(change);
  15688. }
  15689. } else {
  15690. sprite.setAttributes(change);
  15691. }
  15692. }
  15693. },
  15694. getBBoxForItem: function(item) {
  15695. var sprite = item && item.sprite,
  15696. result = null;
  15697. if (sprite) {
  15698. if (sprite.getMarker('items') && item.category === 'items') {
  15699. result = sprite.getMarkerBBox(item.category, item.index);
  15700. } else if (sprite instanceof Ext.draw.sprite.Instancing) {
  15701. result = sprite.getBBoxFor(item.index);
  15702. } else {
  15703. result = sprite.getBBox();
  15704. }
  15705. }
  15706. return result;
  15707. },
  15708. /**
  15709. * @private
  15710. * @property
  15711. * The range of "coordinated" data.
  15712. * Typically, for two directions ('X' and 'Y') the `dataRange` would look like this:
  15713. *
  15714. * dataRange[0] - minX
  15715. * dataRange[1] - minY
  15716. * dataRange[2] - maxX
  15717. * dataRange[3] - maxY
  15718. *
  15719. * And the series' {@link #coordinate} method would be called like this:
  15720. *
  15721. * coordinate('X', 0, 2)
  15722. * coordinate('Y', 1, 2)
  15723. *
  15724. * For numbers, coordinated data are numbers themselves.
  15725. * For categories - their indexes.
  15726. * For Date objects - their timestamps.
  15727. * In other words, whatever source data we have, it has to be converted to numbers
  15728. * before it can be plotted.
  15729. */
  15730. dataRange: null,
  15731. constructor: function(config) {
  15732. var me = this,
  15733. id;
  15734. config = config || {};
  15735. // Backward compatibility with Ext.
  15736. if (config.tips) {
  15737. config = Ext.apply({
  15738. tooltip: config.tips
  15739. }, config);
  15740. }
  15741. // Backward compatibility with Touch.
  15742. if (config.highlightCfg) {
  15743. config = Ext.apply({
  15744. highlight: config.highlightCfg
  15745. }, config);
  15746. }
  15747. if ('id' in config) {
  15748. id = config.id;
  15749. } else if ('id' in me.config) {
  15750. id = me.config.id;
  15751. } else {
  15752. id = me.getId();
  15753. }
  15754. me.setId(id);
  15755. me.sprites = [];
  15756. me.dataRange = [];
  15757. me.mixins.observable.constructor.call(me, config);
  15758. me.initBindable();
  15759. },
  15760. lookupViewModel: function(skipThis) {
  15761. // Override the Bindable's method to redirect view model
  15762. // lookup to the chart.
  15763. var chart = this.getChart();
  15764. return chart ? chart.lookupViewModel(skipThis) : null;
  15765. },
  15766. applyTooltip: function(tooltip, oldTooltip) {
  15767. var config = Ext.apply({
  15768. xtype: 'tooltip',
  15769. renderer: Ext.emptyFn,
  15770. constrainPosition: true,
  15771. shrinkWrapDock: true,
  15772. autoHide: true,
  15773. hideDelay: 200,
  15774. mouseOffset: [
  15775. 20,
  15776. 20
  15777. ],
  15778. trackMouse: true
  15779. }, tooltip);
  15780. return Ext.create(config);
  15781. },
  15782. updateTooltip: function() {
  15783. // Tooltips can't work without the 'itemhighlight' or the 'itemedit' interaction.
  15784. this.addItemHighlight();
  15785. },
  15786. // Adds the 'itemhighlight' interaction to the chart that owns the series.
  15787. addItemHighlight: function() {
  15788. var chart = this.getChart(),
  15789. interactions, interaction, hasRequiredInteraction, i;
  15790. if (!chart) {
  15791. return;
  15792. }
  15793. interactions = chart.getInteractions();
  15794. for (i = 0; i < interactions.length; i++) {
  15795. interaction = interactions[i];
  15796. if (interaction.isItemHighlight || interaction.isItemEdit) {
  15797. hasRequiredInteraction = true;
  15798. break;
  15799. }
  15800. }
  15801. if (!hasRequiredInteraction) {
  15802. interactions.push('itemhighlight');
  15803. chart.setInteractions(interactions);
  15804. }
  15805. },
  15806. showTooltip: function(item, event) {
  15807. var me = this,
  15808. tooltip = me.getTooltip();
  15809. if (!tooltip) {
  15810. return;
  15811. }
  15812. Ext.callback(tooltip.renderer, tooltip.scope, [
  15813. tooltip,
  15814. item.record,
  15815. item
  15816. ], 0, me);
  15817. tooltip.showBy(event);
  15818. },
  15819. showTooltipAt: function(item, x, y) {
  15820. var me = this,
  15821. tooltip = me.getTooltip(),
  15822. mouseOffset = tooltip.config.mouseOffset;
  15823. if (!tooltip || !tooltip.showAt) {
  15824. return;
  15825. }
  15826. if (mouseOffset) {
  15827. x += mouseOffset[0];
  15828. y += mouseOffset[1];
  15829. }
  15830. Ext.callback(tooltip.renderer, tooltip.scope, [
  15831. tooltip,
  15832. item.record,
  15833. item
  15834. ], 0, me);
  15835. tooltip.showAt([
  15836. x,
  15837. y
  15838. ]);
  15839. },
  15840. hideTooltip: function(item, immediate) {
  15841. var me = this,
  15842. tooltip = me.getTooltip();
  15843. if (!tooltip) {
  15844. return;
  15845. }
  15846. if (immediate) {
  15847. tooltip.hide();
  15848. } else {
  15849. tooltip.delayHide();
  15850. }
  15851. },
  15852. applyStore: function(store) {
  15853. return store && Ext.StoreManager.lookup(store);
  15854. },
  15855. getStore: function() {
  15856. return this._store || this.getChart() && this.getChart().getStore();
  15857. },
  15858. updateStore: function(newStore, oldStore) {
  15859. var me = this,
  15860. chart = me.getChart(),
  15861. chartStore = chart && chart.getStore(),
  15862. sprites, sprite, len, i;
  15863. oldStore = oldStore || chartStore;
  15864. if (oldStore && oldStore !== newStore) {
  15865. oldStore.un({
  15866. datachanged: 'onDataChanged',
  15867. update: 'onDataChanged',
  15868. scope: me
  15869. });
  15870. }
  15871. if (newStore) {
  15872. newStore.on({
  15873. datachanged: 'onDataChanged',
  15874. update: 'onDataChanged',
  15875. scope: me
  15876. });
  15877. sprites = me.getSprites();
  15878. for (i = 0 , len = sprites.length; i < len; i++) {
  15879. sprite = sprites[i];
  15880. if (sprite.setStore) {
  15881. sprite.setStore(newStore);
  15882. }
  15883. }
  15884. me.onDataChanged();
  15885. }
  15886. me.fireEvent('storechange', me, newStore, oldStore);
  15887. },
  15888. onStoreChange: function(chart, newStore, oldStore) {
  15889. if (!this._store) {
  15890. this.updateStore(newStore, oldStore);
  15891. }
  15892. },
  15893. defaultRange: [
  15894. 0,
  15895. 1
  15896. ],
  15897. /**
  15898. * @private
  15899. * @param direction {'X'/'Y'}
  15900. * @param directionOffset
  15901. * @param directionCount
  15902. */
  15903. coordinate: function(direction, directionOffset, directionCount) {
  15904. var me = this,
  15905. store = me.getStore(),
  15906. hidden = me.getHidden(),
  15907. items = store.getData().items,
  15908. axis = me['get' + direction + 'Axis'](),
  15909. dataRange = [
  15910. NaN,
  15911. NaN
  15912. ],
  15913. fieldCategory = me['fieldCategory' + direction] || [
  15914. direction
  15915. ],
  15916. fields = me.getFields(fieldCategory),
  15917. i, field, data,
  15918. style = {},
  15919. sprites = me.getSprites(),
  15920. axisRange;
  15921. if (sprites.length && !Ext.isBoolean(hidden) || !hidden) {
  15922. for (i = 0; i < fieldCategory.length; i++) {
  15923. field = fields[i];
  15924. data = me.coordinateData(items, field, axis);
  15925. Ext.chart.Util.expandRange(dataRange, data);
  15926. style['data' + fieldCategory[i]] = data;
  15927. }
  15928. // We don't want to expand the range that has a span of 0 here
  15929. // (e.g. [5, 5] that we'd get if all values for a field are 5).
  15930. // We only want to do this in the Axis, when we calculate the
  15931. // combined range.
  15932. // This is because, if we try to expand the range of values here,
  15933. // and we have multiple fields, the combined range for the axis
  15934. // may not represent the actual range of the data.
  15935. // E.g. if other fields have non-zero span ranges like [4.95, 5.03],
  15936. // [4.91, 5.08], and if the `padding` param to `validateRange` is 0.5,
  15937. // the range of the axis will end up being [4.5, 5.5], because the
  15938. // [5, 5] range of one of the series was expanded to [4.5, 5.5]
  15939. // which encompasses the rest of the ranges.
  15940. dataRange = Ext.chart.Util.validateRange(dataRange, me.defaultRange, 0);
  15941. // See `dataRange` docs.
  15942. me.dataRange[directionOffset] = dataRange[0];
  15943. me.dataRange[directionOffset + directionCount] = dataRange[1];
  15944. style['dataMin' + direction] = dataRange[0];
  15945. style['dataMax' + direction] = dataRange[1];
  15946. if (axis) {
  15947. axisRange = axis.getRange(true);
  15948. axis.setBoundSeriesRange(axisRange);
  15949. }
  15950. for (i = 0; i < sprites.length; i++) {
  15951. sprites[i].setAttributes(style);
  15952. }
  15953. }
  15954. },
  15955. /**
  15956. * @private
  15957. * This method will return an array containing data coordinated by a specific axis.
  15958. * @param {Array} items Store records.
  15959. * @param {String} field The field to fetch from each record.
  15960. * @param {Ext.chart.axis.Axis} axis The axis used to lay out the data.
  15961. * @return {Array}
  15962. */
  15963. coordinateData: function(items, field, axis) {
  15964. var data = [],
  15965. length = items.length,
  15966. layout = axis && axis.getLayout(),
  15967. i, x;
  15968. for (i = 0; i < length; i++) {
  15969. x = items[i].data[field];
  15970. // An empty string (a valid discrete axis value) will be coordinated
  15971. // by the axis layout (if axis is given), otherwise it will be converted
  15972. // to zero (via +'').
  15973. if (!Ext.isEmpty(x, true)) {
  15974. if (layout) {
  15975. data[i] = layout.getCoordFor(x, field, i, items);
  15976. } else {
  15977. x = +x;
  15978. // 'x' can be a category name here.
  15979. data[i] = Ext.isNumber(x) ? x : i;
  15980. }
  15981. } else {
  15982. data[i] = x;
  15983. }
  15984. }
  15985. return data;
  15986. },
  15987. updateLabelData: function() {
  15988. var label = this.getLabel();
  15989. if (!label) {
  15990. return;
  15991. }
  15992. // eslint-disable-next-line vars-on-top, one-var
  15993. var store = this.getStore(),
  15994. items = store.getData().items,
  15995. sprites = this.getSprites(),
  15996. labelTpl = label.getTemplate(),
  15997. labelFields = Ext.Array.from(labelTpl.getField()),
  15998. i, j, ln, labels, sprite, field;
  15999. if (!sprites.length || !labelFields.length) {
  16000. return;
  16001. }
  16002. for (i = 0; i < sprites.length; i++) {
  16003. sprite = sprites[i];
  16004. if (!sprite.getField) {
  16005. // The 'gauge' series is misnormer, its sprites
  16006. // do not extend from the base Series sprite and
  16007. // so do not have the 'field' config. They also
  16008. // don't support labels in the traditional sense.
  16009. continue;
  16010. }
  16011. labels = [];
  16012. field = sprite.getField();
  16013. if (Ext.Array.indexOf(labelFields, field) < 0) {
  16014. field = labelFields[i];
  16015. }
  16016. for (j = 0 , ln = items.length; j < ln; j++) {
  16017. labels.push(items[j].get(field));
  16018. }
  16019. sprite.setAttributes({
  16020. labels: labels
  16021. });
  16022. }
  16023. },
  16024. /**
  16025. * @private
  16026. *
  16027. * *** Data processing overview. ***
  16028. *
  16029. * The data is processed in the following order:
  16030. *
  16031. * 1) chart.processData() - calls `processData` of all series
  16032. * 2) series.processData() - calls `processData` of all bound axes,
  16033. * or jumps to (5) directly, if the series has no axis
  16034. * in this direction
  16035. * 3) axis.processData() - calls the `processData` of its own layout
  16036. * 4) axisLayout.processData() - calls `coordinateX/Y` of all bound series
  16037. * 5) series.coordinateX/Y - calls its own `coordinate` method in that direction
  16038. * 6) series.coordinate - calls its own `coordinateData` method using the right
  16039. * record fields and axes
  16040. * 7) series.coordinateData - calls `getCoordFor` of the axis layout for the given
  16041. * field
  16042. * 8) layout.getCoordFor - returns a numeric value for the given field value,
  16043. * whatever its type may be
  16044. *
  16045. * The `dataX`, `dataY` attributes of the series' sprites are set by the
  16046. * `series.coordinate` method using the data returned by the `coordinateData`.
  16047. * `series.coordinate` also calculates the range of said data (via `expandRange`)
  16048. * and sets the `dataMinX/Y`, `dataMaxX/Y` attributes of the series' sprites.
  16049. */
  16050. processData: function() {
  16051. var me = this,
  16052. directions = me.directions,
  16053. direction, axis, name, i, ln;
  16054. if (me.isProcessingData || !me.getStore()) {
  16055. return;
  16056. }
  16057. me.isProcessingData = true;
  16058. for (i = 0 , ln = directions.length; i < ln; i++) {
  16059. direction = directions[i];
  16060. axis = me['get' + direction + 'Axis']();
  16061. if (axis) {
  16062. axis.processData(me);
  16063. continue;
  16064. }
  16065. name = 'coordinate' + direction;
  16066. if (me[name]) {
  16067. me[name]();
  16068. }
  16069. }
  16070. me.updateLabelData();
  16071. me.isProcessingData = false;
  16072. },
  16073. applyBackground: function(background) {
  16074. var surface, result;
  16075. if (this.getChart()) {
  16076. surface = this.getSurface();
  16077. surface.setBackground(background);
  16078. result = surface.getBackground();
  16079. } else {
  16080. result = background;
  16081. }
  16082. return result;
  16083. },
  16084. updateChart: function(newChart, oldChart) {
  16085. var me = this,
  16086. store = me._store;
  16087. if (oldChart) {
  16088. oldChart.un('axeschange', 'onAxesChange', me);
  16089. me.clearSprites();
  16090. me.setSurface(null);
  16091. me.setOverlaySurface(null);
  16092. oldChart.unregister(me);
  16093. me.onChartDetached(oldChart);
  16094. if (!store) {
  16095. me.updateStore(null);
  16096. }
  16097. }
  16098. if (newChart) {
  16099. me.setSurface(newChart.getSurface('series'));
  16100. me.setOverlaySurface(newChart.getSurface('overlay'));
  16101. newChart.on('axeschange', 'onAxesChange', me);
  16102. // TODO: Gauge series should render correctly when chart's store is missing.
  16103. // TODO: When store is initially missing the getAxes will return null here,
  16104. // TODO: since applyAxes has actually triggered this series.updateChart call
  16105. // TODO: indirectly.
  16106. // TODO: Figure out why it doesn't go this route when a store is present.
  16107. if (newChart.getAxes()) {
  16108. me.onAxesChange(newChart);
  16109. }
  16110. me.onChartAttached(newChart);
  16111. newChart.register(me);
  16112. if (!store) {
  16113. me.updateStore(newChart.getStore());
  16114. }
  16115. }
  16116. },
  16117. onAxesChange: function(chart, force) {
  16118. if (chart.destroying || chart.destroyed) {
  16119. return;
  16120. }
  16121. // eslint-disable-next-line vars-on-top
  16122. var me = this,
  16123. axes = chart.getAxes(),
  16124. axis,
  16125. directionToAxesMap = {},
  16126. directionToFieldsMap = {},
  16127. needHighPrecision = false,
  16128. directions = this.directions,
  16129. direction, i, ln;
  16130. for (i = 0 , ln = directions.length; i < ln; i++) {
  16131. direction = directions[i];
  16132. directionToFieldsMap[direction] = me.getFields(me['fieldCategory' + direction]);
  16133. }
  16134. for (i = 0 , ln = axes.length; i < ln; i++) {
  16135. axis = axes[i];
  16136. direction = axis.getDirection();
  16137. if (!directionToAxesMap[direction]) {
  16138. directionToAxesMap[direction] = [
  16139. axis
  16140. ];
  16141. } else {
  16142. directionToAxesMap[direction].push(axis);
  16143. }
  16144. }
  16145. for (i = 0 , ln = directions.length; i < ln; i++) {
  16146. direction = directions[i];
  16147. if (!force && me['get' + direction + 'Axis']()) {
  16148. continue;
  16149. }
  16150. if (directionToAxesMap[direction]) {
  16151. axis = me.findMatchingAxis(directionToAxesMap[direction], directionToFieldsMap[direction]);
  16152. if (axis) {
  16153. me['set' + direction + 'Axis'](axis);
  16154. if (axis.getNeedHighPrecision()) {
  16155. needHighPrecision = true;
  16156. }
  16157. }
  16158. }
  16159. }
  16160. this.getSurface().setHighPrecision(needHighPrecision);
  16161. },
  16162. /**
  16163. * @private
  16164. * Given the list of axes in a certain direction and a list of series fields in that
  16165. * direction returns the first matching axis for the series in that direction,
  16166. * or undefined if a match wasn't found.
  16167. */
  16168. findMatchingAxis: function(directionAxes, directionFields) {
  16169. var axis, axisFields, i, j;
  16170. for (i = 0; i < directionAxes.length; i++) {
  16171. axis = directionAxes[i];
  16172. axisFields = axis.getFields();
  16173. if (!axisFields.length) {
  16174. return axis;
  16175. } else if (directionFields) {
  16176. for (j = 0; j < directionFields.length; j++) {
  16177. if (Ext.Array.indexOf(axisFields, directionFields[j]) >= 0) {
  16178. return axis;
  16179. }
  16180. }
  16181. }
  16182. }
  16183. },
  16184. onChartDetached: function(oldChart) {
  16185. var me = this;
  16186. me.fireEvent('chartdetached', oldChart, me);
  16187. oldChart.un('storechange', 'onStoreChange', me);
  16188. },
  16189. onChartAttached: function(chart) {
  16190. var me = this;
  16191. me.fireEvent('chartattached', chart, me);
  16192. chart.on('storechange', 'onStoreChange', me);
  16193. me.processData();
  16194. },
  16195. updateOverlaySurface: function(overlaySurface) {
  16196. var label = this.getLabel();
  16197. if (overlaySurface && label) {
  16198. overlaySurface.add(label);
  16199. }
  16200. },
  16201. getLabel: function() {
  16202. return this.labelMarker;
  16203. },
  16204. setLabel: function(label) {
  16205. var me = this,
  16206. chart = me.getChart(),
  16207. marker = me.labelMarker,
  16208. template;
  16209. // The label sprite is reused unless the value of 'label' is falsy,
  16210. // so that we can transition from one attribute set to another with an
  16211. // animation, which is important for example during theme switching.
  16212. if (!label && marker) {
  16213. marker.getTemplate().destroy();
  16214. marker.destroy();
  16215. me.labelMarker = marker = null;
  16216. }
  16217. if (label) {
  16218. if (!marker) {
  16219. marker = me.labelMarker = new Ext.chart.Markers({
  16220. zIndex: 10
  16221. });
  16222. marker.setTemplate(new Ext.chart.sprite.Label());
  16223. me.getOverlaySurface().add(marker);
  16224. }
  16225. template = marker.getTemplate();
  16226. template.setAttributes(label);
  16227. template.setConfig(label);
  16228. if (label.field) {
  16229. template.setField(label.field);
  16230. }
  16231. if (label.display) {
  16232. marker.setAttributes({
  16233. hidden: label.display === 'none'
  16234. });
  16235. }
  16236. marker.setDirty(true);
  16237. }
  16238. // Inform the label about the template change.
  16239. me.updateLabelData();
  16240. if (chart && !chart.isInitializing && !me.isConfiguring) {
  16241. chart.redraw();
  16242. }
  16243. },
  16244. createItemInstancingSprite: function(sprite, itemInstancing) {
  16245. var me = this,
  16246. markers = new Ext.chart.Markers(),
  16247. config = Ext.apply({
  16248. modifiers: 'highlight'
  16249. }, itemInstancing),
  16250. style = me.getStyle(),
  16251. template, animation;
  16252. markers.setAttributes({
  16253. zIndex: Number.MAX_VALUE
  16254. });
  16255. markers.setTemplate(config);
  16256. template = markers.getTemplate();
  16257. template.setAttributes(style);
  16258. animation = template.getAnimation();
  16259. animation.on('animationstart', 'onSpriteAnimationStart', this);
  16260. animation.on('animationend', 'onSpriteAnimationEnd', this);
  16261. sprite.bindMarker('items', markers);
  16262. me.getSurface().add(markers);
  16263. return markers;
  16264. },
  16265. getDefaultSpriteConfig: function() {
  16266. return {
  16267. type: this.seriesType,
  16268. renderer: this.getRenderer()
  16269. };
  16270. },
  16271. updateRenderer: function(renderer) {
  16272. var me = this,
  16273. chart = me.getChart();
  16274. if (chart && chart.isInitializing) {
  16275. return;
  16276. }
  16277. // We have to be careful and not call the 'getSprites' method here, as this
  16278. // method itself may have been called by the 'getSprites' method indirectly already.
  16279. if (me.sprites.length) {
  16280. me.sprites[0].setAttributes({
  16281. renderer: renderer || null
  16282. });
  16283. if (chart && !chart.isInitializing) {
  16284. chart.redraw();
  16285. }
  16286. }
  16287. },
  16288. updateShowMarkers: function(showMarkers) {
  16289. var sprite = this.getSprite(),
  16290. markers = sprite && sprite.getMarker('markers');
  16291. if (markers) {
  16292. markers.getTemplate().setAttributes({
  16293. hidden: !showMarkers
  16294. });
  16295. }
  16296. },
  16297. createSprite: function() {
  16298. var me = this,
  16299. surface = me.getSurface(),
  16300. itemInstancing = me.getItemInstancing(),
  16301. sprite = surface.add(me.getDefaultSpriteConfig()),
  16302. animation, label;
  16303. sprite.setAttributes(me.getStyle());
  16304. sprite.setSeries(me);
  16305. if (itemInstancing) {
  16306. me.createItemInstancingSprite(sprite, itemInstancing);
  16307. }
  16308. if (sprite.isMarkerHolder) {
  16309. label = me.getLabel();
  16310. if (label && label.getTemplate().getField()) {
  16311. sprite.bindMarker('labels', label);
  16312. }
  16313. }
  16314. if (sprite.setStore) {
  16315. sprite.setStore(me.getStore());
  16316. }
  16317. animation = sprite.getAnimation();
  16318. animation.on('animationstart', 'onSpriteAnimationStart', me);
  16319. animation.on('animationend', 'onSpriteAnimationEnd', me);
  16320. me.sprites.push(sprite);
  16321. return sprite;
  16322. },
  16323. /**
  16324. * @method
  16325. * Returns the read-only array of sprites the are used to draw this series.
  16326. */
  16327. getSprites: null,
  16328. /**
  16329. * @private
  16330. * Returns the first sprite. Convenience method for series that have
  16331. * a single markerholder sprite.
  16332. */
  16333. getSprite: function() {
  16334. var sprites = this.getSprites();
  16335. return sprites && sprites[0];
  16336. },
  16337. /**
  16338. * @private
  16339. */
  16340. withSprite: function(fn) {
  16341. var sprite = this.getSprite();
  16342. return sprite && fn(sprite) || undefined;
  16343. },
  16344. forEachSprite: function(fn) {
  16345. var sprites = this.getSprites(),
  16346. i, ln;
  16347. for (i = 0 , ln = sprites.length; i < ln; i++) {
  16348. fn(sprites[i]);
  16349. }
  16350. },
  16351. onDataChanged: function() {
  16352. var me = this,
  16353. chart = me.getChart(),
  16354. chartStore = chart && chart.getStore(),
  16355. seriesStore = me.getStore();
  16356. if (seriesStore !== chartStore) {
  16357. me.processData();
  16358. }
  16359. },
  16360. isXType: function(xtype) {
  16361. return xtype === 'series';
  16362. },
  16363. getItemId: function() {
  16364. return this.getId();
  16365. },
  16366. applyThemeStyle: function(theme, oldTheme) {
  16367. var me = this,
  16368. fill, stroke;
  16369. fill = theme && theme.subStyle && theme.subStyle.fillStyle;
  16370. stroke = fill && theme.subStyle.strokeStyle;
  16371. if (fill && !stroke) {
  16372. theme.subStyle.strokeStyle = me.getStrokeColorsFromFillColors(fill);
  16373. }
  16374. fill = theme && theme.markerSubStyle && theme.markerSubStyle.fillStyle;
  16375. stroke = fill && theme.markerSubStyle.strokeStyle;
  16376. if (fill && !stroke) {
  16377. theme.markerSubStyle.strokeStyle = me.getStrokeColorsFromFillColors(fill);
  16378. }
  16379. return Ext.apply(oldTheme || {}, theme);
  16380. },
  16381. applyStyle: function(style, oldStyle) {
  16382. return Ext.apply({}, style, oldStyle);
  16383. },
  16384. applySubStyle: function(subStyle, oldSubStyle) {
  16385. var name = Ext.ClassManager.getNameByAlias('sprite.' + this.seriesType),
  16386. cls = Ext.ClassManager.get(name);
  16387. if (cls && cls.def) {
  16388. subStyle = cls.def.batchedNormalize(subStyle, true);
  16389. }
  16390. return Ext.merge({}, oldSubStyle, subStyle);
  16391. },
  16392. applyMarker: function(marker, oldMarker) {
  16393. var type, cls;
  16394. if (marker) {
  16395. if (!Ext.isObject(marker)) {
  16396. marker = {};
  16397. }
  16398. type = marker.type || 'circle';
  16399. if (oldMarker && type === oldMarker.type) {
  16400. marker = Ext.merge({}, oldMarker, marker);
  16401. }
  16402. }
  16403. // Note: reusing the `oldMaker` like `Ext.merge(oldMarker, marker)`
  16404. // isn't possible because the `updateMarker` won't be called.
  16405. if (type) {
  16406. cls = Ext.ClassManager.get(Ext.ClassManager.getNameByAlias('sprite.' + type));
  16407. }
  16408. if (cls && cls.def) {
  16409. marker = cls.def.normalize(marker, true);
  16410. marker.type = type;
  16411. } else {
  16412. marker = null;
  16413. //<debug>
  16414. Ext.log.warn('Invalid series marker type: ' + type);
  16415. }
  16416. //</debug>
  16417. return marker;
  16418. },
  16419. updateMarker: function(marker) {
  16420. var me = this,
  16421. sprites = me.getSprites(),
  16422. seriesSprite, markerSprite, markerTplConfig, i, ln;
  16423. for (i = 0 , ln = sprites.length; i < ln; i++) {
  16424. seriesSprite = sprites[i];
  16425. if (!seriesSprite.isMarkerHolder) {
  16426. continue;
  16427. }
  16428. markerSprite = seriesSprite.getMarker('markers');
  16429. if (marker) {
  16430. if (!markerSprite) {
  16431. markerSprite = new Ext.chart.Markers();
  16432. seriesSprite.bindMarker('markers', markerSprite);
  16433. me.getOverlaySurface().add(markerSprite);
  16434. }
  16435. markerTplConfig = Ext.Object.merge({
  16436. modifiers: 'highlight'
  16437. }, marker);
  16438. markerSprite.setTemplate(markerTplConfig);
  16439. markerSprite.getTemplate().getAnimation().setCustomDurations({
  16440. translationX: 0,
  16441. translationY: 0
  16442. });
  16443. } else if (markerSprite) {
  16444. seriesSprite.releaseMarker('markers');
  16445. me.getOverlaySurface().remove(markerSprite, true);
  16446. }
  16447. seriesSprite.setDirty(true);
  16448. }
  16449. // If we call, for example, `series.setMarker({type: 'circle'})` on a series
  16450. // that has been already constructed, the newly added marker still has to be
  16451. // themed, and the 'style' config of its 'highlight' modifier has to be set.
  16452. if (!me.isConfiguring) {
  16453. me.doUpdateStyles();
  16454. me.updateHighlight(me.getHighlight());
  16455. }
  16456. },
  16457. applyMarkerSubStyle: function(marker, oldMarker) {
  16458. var type = (marker && marker.type) || (oldMarker && oldMarker.type) || 'circle',
  16459. cls = Ext.ClassManager.get(Ext.ClassManager.getNameByAlias('sprite.' + type));
  16460. if (cls && cls.def) {
  16461. marker = cls.def.batchedNormalize(marker, true);
  16462. }
  16463. return Ext.merge(oldMarker || {}, marker);
  16464. },
  16465. updateHidden: function(hidden) {
  16466. var me = this;
  16467. me.getColors();
  16468. me.getSubStyle();
  16469. me.setSubStyle({
  16470. hidden: hidden
  16471. });
  16472. me.processData();
  16473. me.doUpdateStyles();
  16474. if (!Ext.isArray(hidden)) {
  16475. me.updateLegendStore(hidden);
  16476. }
  16477. },
  16478. /**
  16479. * @private
  16480. * Updates chart's legend store when the value of the series' {@link #hidden} config
  16481. * changes or when the {@link #setHiddenByIndex} method is called.
  16482. * @param hidden Whether series (or its component) should be hidden or not.
  16483. * @param index Used for stacked series.
  16484. * If present, only the component with the specified index will change
  16485. * visibility.
  16486. */
  16487. updateLegendStore: function(hidden, index) {
  16488. var me = this,
  16489. chart = me.getChart(),
  16490. legendStore = chart && chart.getLegendStore(),
  16491. id = me.getId(),
  16492. record;
  16493. if (legendStore) {
  16494. if (arguments.length > 1) {
  16495. record = legendStore.findBy(function(rec) {
  16496. return rec.get('series') === id && rec.get('index') === index;
  16497. });
  16498. if (record !== -1) {
  16499. record = legendStore.getAt(record);
  16500. }
  16501. } else {
  16502. record = legendStore.findRecord('series', id);
  16503. }
  16504. if (record && record.get('disabled') !== hidden) {
  16505. record.set('disabled', hidden);
  16506. }
  16507. }
  16508. },
  16509. /**
  16510. *
  16511. * @param {Number} index
  16512. * @param {Boolean} value
  16513. */
  16514. setHiddenByIndex: function(index, value) {
  16515. var me = this;
  16516. if (Ext.isArray(me.getHidden())) {
  16517. // Multi-sprite series like Pie and StackedCartesian.
  16518. me.getHidden()[index] = value;
  16519. me.updateHidden(me.getHidden());
  16520. me.updateLegendStore(value, index);
  16521. } else {
  16522. me.setHidden(value);
  16523. }
  16524. },
  16525. getStrokeColorsFromFillColors: function(colors) {
  16526. var me = this,
  16527. darker = me.getUseDarkerStrokeColor(),
  16528. darkerRatio = (Ext.isNumber(darker) ? darker : me.darkerStrokeRatio),
  16529. strokeColors;
  16530. if (darker) {
  16531. strokeColors = Ext.Array.map(colors, function(color) {
  16532. color = Ext.isString(color) ? color : color.stops[0].color;
  16533. color = Ext.util.Color.fromString(color);
  16534. return color.createDarker(darkerRatio).toString();
  16535. });
  16536. } else {
  16537. strokeColors = Ext.Array.clone(colors);
  16538. }
  16539. return strokeColors;
  16540. },
  16541. updateThemeColors: function(colors) {
  16542. var me = this,
  16543. theme = me.getThemeStyle(),
  16544. fillColors = Ext.Array.clone(colors),
  16545. strokeColors = me.getStrokeColorsFromFillColors(colors),
  16546. newSubStyle = {
  16547. fillStyle: fillColors,
  16548. strokeStyle: strokeColors
  16549. };
  16550. theme.subStyle = Ext.apply(theme.subStyle || {}, newSubStyle);
  16551. theme.markerSubStyle = Ext.apply(theme.markerSubStyle || {}, newSubStyle);
  16552. me.doUpdateStyles();
  16553. if (!me.isConfiguring) {
  16554. me.getChart().refreshLegendStore();
  16555. }
  16556. },
  16557. themeOnlyIfConfigured: {},
  16558. updateTheme: function(theme) {
  16559. var me = this,
  16560. seriesTheme = theme.getSeries(),
  16561. initialConfig = me.getInitialConfig(),
  16562. defaultConfig = me.defaultConfig,
  16563. configs = me.self.getConfigurator().configs,
  16564. genericSeriesTheme = seriesTheme.defaults,
  16565. specificSeriesTheme = seriesTheme[me.type],
  16566. themeOnlyIfConfigured = me.themeOnlyIfConfigured,
  16567. key, value, isObjValue, isUnusedConfig, initialValue, cfg;
  16568. seriesTheme = Ext.merge({}, genericSeriesTheme, specificSeriesTheme);
  16569. for (key in seriesTheme) {
  16570. value = seriesTheme[key];
  16571. cfg = configs[key];
  16572. if (value !== null && value !== undefined && cfg) {
  16573. initialValue = initialConfig[key];
  16574. isObjValue = Ext.isObject(value);
  16575. isUnusedConfig = initialValue === defaultConfig[key];
  16576. if (isObjValue) {
  16577. if (isUnusedConfig && themeOnlyIfConfigured[key]) {
  16578. continue;
  16579. }
  16580. value = Ext.merge({}, value, initialValue);
  16581. }
  16582. if (isUnusedConfig || isObjValue) {
  16583. me[cfg.names.set](value);
  16584. }
  16585. }
  16586. }
  16587. },
  16588. /**
  16589. * @private
  16590. * When the chart's "colors" config changes, these colors are passed onto the series
  16591. * where they are used with the same priority as theme colors, i.e. they do not override
  16592. * the series' "colors" config, nor the series' "style" config, but they do override
  16593. * the colors from the theme's "seriesThemes" config.
  16594. */
  16595. updateChartColors: function(colors) {
  16596. var me = this;
  16597. if (!me.getColors()) {
  16598. me.updateThemeColors(colors);
  16599. }
  16600. },
  16601. updateColors: function(colors) {
  16602. var chart;
  16603. this.updateThemeColors(colors);
  16604. if (!this.isConfiguring) {
  16605. chart = this.getChart();
  16606. if (chart) {
  16607. chart.refreshLegendStore();
  16608. }
  16609. }
  16610. },
  16611. updateStyle: function() {
  16612. this.doUpdateStyles();
  16613. },
  16614. updateSubStyle: function() {
  16615. this.doUpdateStyles();
  16616. },
  16617. updateThemeStyle: function() {
  16618. this.doUpdateStyles();
  16619. },
  16620. doUpdateStyles: function() {
  16621. var me = this,
  16622. sprites = me.sprites,
  16623. itemInstancing = me.getItemInstancing(),
  16624. ln = sprites && sprites.length,
  16625. // 'showMarkers' updater calls 'series.getSprites()',
  16626. // which we don't want to call here.
  16627. showMarkers = me.getConfig('showMarkers', true),
  16628. // eslint-disable-line no-unused-vars
  16629. style, sprite, marker, i;
  16630. for (i = 0; i < ln; i++) {
  16631. sprite = sprites[i];
  16632. style = me.getStyleByIndex(i);
  16633. if (itemInstancing) {
  16634. sprite.getMarker('items').getTemplate().setAttributes(style);
  16635. }
  16636. sprite.setAttributes(style);
  16637. marker = sprite.isMarkerHolder && sprite.getMarker('markers');
  16638. if (marker) {
  16639. marker.getTemplate().setAttributes(me.getMarkerStyleByIndex(i));
  16640. }
  16641. }
  16642. },
  16643. getStyleWithTheme: function() {
  16644. var me = this,
  16645. theme = me.getThemeStyle(),
  16646. style = Ext.clone(me.getStyle());
  16647. if (theme && theme.style) {
  16648. Ext.applyIf(style, theme.style);
  16649. }
  16650. return style;
  16651. },
  16652. getSubStyleWithTheme: function() {
  16653. var me = this,
  16654. theme = me.getThemeStyle(),
  16655. subStyle = Ext.clone(me.getSubStyle());
  16656. if (theme && theme.subStyle) {
  16657. Ext.applyIf(subStyle, theme.subStyle);
  16658. }
  16659. return subStyle;
  16660. },
  16661. getStyleByIndex: function(i) {
  16662. var me = this,
  16663. theme = me.getThemeStyle(),
  16664. style, themeStyle, subStyle, themeSubStyle,
  16665. result = {};
  16666. style = me.getStyle();
  16667. themeStyle = (theme && theme.style) || {};
  16668. subStyle = me.styleDataForIndex(me.getSubStyle(), i);
  16669. themeSubStyle = me.styleDataForIndex((theme && theme.subStyle), i);
  16670. Ext.apply(result, themeStyle);
  16671. Ext.apply(result, themeSubStyle);
  16672. Ext.apply(result, style);
  16673. Ext.apply(result, subStyle);
  16674. return result;
  16675. },
  16676. getMarkerStyleByIndex: function(i) {
  16677. var me = this,
  16678. theme = me.getThemeStyle(),
  16679. style, themeStyle, subStyle, themeSubStyle, markerStyle, themeMarkerStyle, markerSubStyle, themeMarkerSubStyle,
  16680. result = {};
  16681. style = me.getStyle();
  16682. themeStyle = (theme && theme.style) || {};
  16683. // 'series.updateHidden()' will update 'series.subStyle.hidden' config
  16684. // with the value of the 'series.hidden' config.
  16685. // But we also need to account for 'series.showMarkers' config
  16686. // to determine whether the markers should be hidden or not.
  16687. subStyle = me.styleDataForIndex(me.getSubStyle(), i);
  16688. if (subStyle.hasOwnProperty('hidden')) {
  16689. subStyle.hidden = subStyle.hidden || !this.getConfig('showMarkers', true);
  16690. }
  16691. themeSubStyle = me.styleDataForIndex((theme && theme.subStyle), i);
  16692. markerStyle = me.getMarker();
  16693. themeMarkerStyle = (theme && theme.marker) || {};
  16694. markerSubStyle = me.getMarkerSubStyle();
  16695. themeMarkerSubStyle = me.styleDataForIndex((theme && theme.markerSubStyle), i);
  16696. Ext.apply(result, themeStyle);
  16697. Ext.apply(result, themeSubStyle);
  16698. Ext.apply(result, themeMarkerStyle);
  16699. Ext.apply(result, themeMarkerSubStyle);
  16700. Ext.apply(result, style);
  16701. Ext.apply(result, subStyle);
  16702. Ext.apply(result, markerStyle);
  16703. Ext.apply(result, markerSubStyle);
  16704. return result;
  16705. },
  16706. styleDataForIndex: function(style, i) {
  16707. var value, name,
  16708. result = {};
  16709. if (style) {
  16710. for (name in style) {
  16711. value = style[name];
  16712. if (Ext.isArray(value)) {
  16713. result[name] = value[i % value.length];
  16714. } else {
  16715. result[name] = value;
  16716. }
  16717. }
  16718. }
  16719. return result;
  16720. },
  16721. /**
  16722. * @method
  16723. * For a given x/y point relative to the main rect, find a corresponding item from this
  16724. * series, if any.
  16725. * @param {Number} x
  16726. * @param {Number} y
  16727. * @param {Object} [target] optional target to receive the result
  16728. * @return {Object} An object describing the item, or null if there is no matching item.
  16729. * The exact contents of this object will vary by series type, but should always contain
  16730. * at least the following:
  16731. *
  16732. * @return {Ext.data.Model} return.record the record of the item.
  16733. * @return {Array} return.point the x/y coordinates relative to the chart box
  16734. * of a single point for this data item, which can be used as e.g. a tooltip anchor
  16735. * point.
  16736. * @return {Ext.draw.sprite.Sprite} return.sprite the item's rendering Sprite.
  16737. * @return {Number} return.subSprite the index if sprite is an instancing sprite.
  16738. */
  16739. getItemForPoint: Ext.emptyFn,
  16740. /**
  16741. * Returns a series item by index and (optional) category.
  16742. * @param {Number} index The index of the item (matches store record index).
  16743. * @param {String} [category] The category of item, e.g.: 'items', 'markers', 'sprites'.
  16744. * @return {Object} item
  16745. */
  16746. getItemByIndex: function(index, category) {
  16747. var me = this,
  16748. sprites = me.getSprites(),
  16749. sprite = sprites && sprites[0],
  16750. item;
  16751. if (!sprite) {
  16752. return;
  16753. }
  16754. // 'category' is not defined, making our best guess here.
  16755. if (category === undefined && sprite.isMarkerHolder) {
  16756. category = me.getItemInstancing() ? 'items' : 'markers';
  16757. } else if (!category || category === '' || category === 'sprites') {
  16758. sprite = sprites[index];
  16759. }
  16760. if (sprite) {
  16761. item = {
  16762. series: me,
  16763. category: category,
  16764. index: index,
  16765. record: me.getStore().getData().items[index],
  16766. field: me.getYField(),
  16767. sprite: sprite
  16768. };
  16769. return item;
  16770. }
  16771. },
  16772. onSpriteAnimationStart: function(sprite) {
  16773. this.fireEvent('animationstart', this, sprite);
  16774. },
  16775. onSpriteAnimationEnd: function(sprite) {
  16776. this.fireEvent('animationend', this, sprite);
  16777. },
  16778. resolveListenerScope: function(defaultScope) {
  16779. // Override the Observable's method to redirect listener scope
  16780. // resolution to the chart.
  16781. var me = this,
  16782. namedScope = Ext._namedScopes[defaultScope],
  16783. chart = me.getChart(),
  16784. scope;
  16785. if (!namedScope) {
  16786. scope = chart ? chart.resolveListenerScope(defaultScope, false) : (defaultScope || me);
  16787. } else if (namedScope.isThis) {
  16788. scope = me;
  16789. } else if (namedScope.isController) {
  16790. scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
  16791. } else if (namedScope.isSelf) {
  16792. scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
  16793. // Class body listener. No chart controller, nor chart container controller.
  16794. if (scope === chart && !chart.getInheritedConfig('defaultListenerScope')) {
  16795. scope = me;
  16796. }
  16797. }
  16798. return scope;
  16799. },
  16800. /**
  16801. * Provide legend information to target array.
  16802. *
  16803. * @param {Array} target
  16804. *
  16805. * The information consists:
  16806. * @param {String} target.name
  16807. * @param {String} target.mark
  16808. * @param {Boolean} target.disabled
  16809. * @param {String} target.series
  16810. * @param {Number} target.index
  16811. */
  16812. provideLegendInfo: function(target) {
  16813. var me = this,
  16814. style = me.getSubStyleWithTheme(),
  16815. fill = style.fillStyle;
  16816. if (Ext.isArray(fill)) {
  16817. fill = fill[0];
  16818. }
  16819. target.push({
  16820. name: me.getTitle() || me.getYField() || me.getId(),
  16821. mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
  16822. disabled: me.getHidden(),
  16823. series: me.getId(),
  16824. index: 0
  16825. });
  16826. },
  16827. clearSprites: function() {
  16828. var sprites = this.sprites,
  16829. sprite, i, ln;
  16830. for (i = 0 , ln = sprites.length; i < ln; i++) {
  16831. sprite = sprites[i];
  16832. if (sprite && sprite.isSprite) {
  16833. sprite.destroy();
  16834. }
  16835. }
  16836. this.sprites = [];
  16837. },
  16838. destroy: function() {
  16839. var me = this,
  16840. store = me._store,
  16841. // Peek at the config so we don't create one just to destroy it
  16842. tooltip = me.getConfig('tooltip', true);
  16843. if (store && store.getAutoDestroy()) {
  16844. Ext.destroy(store);
  16845. }
  16846. me.setChart(null);
  16847. me.clearListeners();
  16848. if (tooltip) {
  16849. Ext.destroy(tooltip);
  16850. }
  16851. me.callParent();
  16852. }
  16853. });
  16854. /**
  16855. * @class Ext.chart.interactions.Abstract
  16856. *
  16857. * Defines a common abstract parent class for all interactions.
  16858. *
  16859. */
  16860. Ext.define('Ext.chart.interactions.Abstract', {
  16861. xtype: 'interaction',
  16862. mixins: {
  16863. observable: 'Ext.mixin.Observable'
  16864. },
  16865. config: {
  16866. /**
  16867. * @cfg {Object} gesture
  16868. * Maps gestures that should be used for starting/maintaining/ending the interaction
  16869. * to corresponding class methods.
  16870. * @private
  16871. */
  16872. gestures: {
  16873. tap: 'onGesture'
  16874. },
  16875. /**
  16876. * @cfg {Ext.chart.AbstractChart} chart The chart that the interaction is bound.
  16877. */
  16878. chart: null,
  16879. /**
  16880. * @cfg {Boolean} enabled 'true' if the interaction is enabled.
  16881. */
  16882. enabled: true
  16883. },
  16884. /**
  16885. * Android device is emerging too many events so if we re-render every frame it will
  16886. * take forever to finish a frame.
  16887. * This throttle technique will limit the timespan between two frames.
  16888. */
  16889. throttleGap: 0,
  16890. stopAnimationBeforeSync: false,
  16891. constructor: function(config) {
  16892. var me = this,
  16893. id;
  16894. config = config || {};
  16895. if ('id' in config) {
  16896. id = config.id;
  16897. } else if ('id' in me.config) {
  16898. id = me.config.id;
  16899. } else {
  16900. id = me.getId();
  16901. }
  16902. me.setId(id);
  16903. me.mixins.observable.constructor.call(me, config);
  16904. },
  16905. updateChart: function(newChart, oldChart) {
  16906. var me = this;
  16907. if (oldChart === newChart) {
  16908. return;
  16909. }
  16910. if (oldChart) {
  16911. oldChart.unregister(me);
  16912. me.removeChartListener(oldChart);
  16913. }
  16914. if (newChart) {
  16915. newChart.register(me);
  16916. me.addChartListener();
  16917. }
  16918. },
  16919. updateEnabled: function(enabled) {
  16920. var me = this,
  16921. chart = me.getChart();
  16922. if (chart) {
  16923. if (enabled) {
  16924. me.addChartListener();
  16925. } else {
  16926. me.removeChartListener(chart);
  16927. }
  16928. }
  16929. },
  16930. /**
  16931. * @method
  16932. * @protected
  16933. * Placeholder method.
  16934. */
  16935. onGesture: Ext.emptyFn,
  16936. /**
  16937. * @protected
  16938. * Find and return a single series item corresponding to the given event,
  16939. * or null if no matching item is found.
  16940. * @param {Event} e
  16941. * @return {Object} the item object or null if none found.
  16942. */
  16943. getItemForEvent: function(e) {
  16944. var me = this,
  16945. chart = me.getChart(),
  16946. chartXY = chart.getEventXY(e);
  16947. return chart.getItemForPoint(chartXY[0], chartXY[1]);
  16948. },
  16949. /**
  16950. * Find and return all series items corresponding to the given event.
  16951. * @param {Event} e
  16952. * @return {Array} array of matching item objects
  16953. * @private
  16954. * @deprecated 6.5.2 This method is deprecated
  16955. */
  16956. getItemsForEvent: function(e) {
  16957. var me = this,
  16958. chart = me.getChart(),
  16959. chartXY = chart.getEventXY(e);
  16960. return chart.getItemsForPoint(chartXY[0], chartXY[1]);
  16961. },
  16962. /**
  16963. * @private
  16964. */
  16965. addChartListener: function() {
  16966. var me = this,
  16967. chart = me.getChart(),
  16968. gestures = me.getGestures(),
  16969. gesture;
  16970. if (!me.getEnabled()) {
  16971. return;
  16972. }
  16973. function insertGesture(name, fn) {
  16974. chart.addElementListener(name, // wrap the handler so it does not fire if the event is locked
  16975. // by another interaction
  16976. me.listeners[name] = function(e) {
  16977. var locks = me.getLocks(),
  16978. result;
  16979. if (me.getEnabled() && (!(name in locks) || locks[name] === me)) {
  16980. result = (Ext.isFunction(fn) ? fn : me[fn]).apply(this, arguments);
  16981. if (result === false && e && e.stopPropagation) {
  16982. e.stopPropagation();
  16983. }
  16984. return result;
  16985. }
  16986. }, me);
  16987. }
  16988. me.listeners = me.listeners || {};
  16989. for (gesture in gestures) {
  16990. insertGesture(gesture, gestures[gesture]);
  16991. }
  16992. },
  16993. removeChartListener: function(chart) {
  16994. var me = this,
  16995. gestures = me.getGestures(),
  16996. gesture;
  16997. function removeGesture(name) {
  16998. var fn = me.listeners[name];
  16999. if (fn) {
  17000. chart.removeElementListener(name, fn);
  17001. delete me.listeners[name];
  17002. }
  17003. }
  17004. if (me.listeners) {
  17005. for (gesture in gestures) {
  17006. removeGesture(gesture);
  17007. }
  17008. }
  17009. },
  17010. lockEvents: function() {
  17011. var me = this,
  17012. locks = me.getLocks(),
  17013. args = Array.prototype.slice.call(arguments),
  17014. i = args.length;
  17015. while (i--) {
  17016. locks[args[i]] = me;
  17017. }
  17018. },
  17019. unlockEvents: function() {
  17020. var locks = this.getLocks(),
  17021. args = Array.prototype.slice.call(arguments),
  17022. i = args.length;
  17023. while (i--) {
  17024. delete locks[args[i]];
  17025. }
  17026. },
  17027. getLocks: function() {
  17028. var chart = this.getChart();
  17029. return chart.lockedEvents || (chart.lockedEvents = {});
  17030. },
  17031. doSync: function() {
  17032. var me = this,
  17033. chart = me.getChart();
  17034. if (me.syncTimer) {
  17035. Ext.undefer(me.syncTimer);
  17036. me.syncTimer = null;
  17037. }
  17038. if (me.stopAnimationBeforeSync) {
  17039. chart.animationSuspendCount++;
  17040. }
  17041. chart.redraw();
  17042. if (me.stopAnimationBeforeSync) {
  17043. chart.animationSuspendCount--;
  17044. }
  17045. me.syncThrottle = Date.now() + me.throttleGap;
  17046. },
  17047. sync: function() {
  17048. var me = this;
  17049. if (me.throttleGap && Ext.frameStartTime < me.syncThrottle) {
  17050. if (me.syncTimer) {
  17051. return;
  17052. }
  17053. me.syncTimer = Ext.defer(function() {
  17054. me.doSync();
  17055. }, me.throttleGap);
  17056. } else {
  17057. me.doSync();
  17058. }
  17059. },
  17060. getItemId: function() {
  17061. return this.getId();
  17062. },
  17063. isXType: function(xtype) {
  17064. return xtype === 'interaction';
  17065. },
  17066. destroy: function() {
  17067. var me = this;
  17068. me.setChart(null);
  17069. delete me.listeners;
  17070. me.callParent();
  17071. }
  17072. }, function() {
  17073. if (Ext.os.is.Android4) {
  17074. this.prototype.throttleGap = 40;
  17075. }
  17076. });
  17077. /**
  17078. * Mixin that provides the functionality to place markers.
  17079. */
  17080. Ext.define('Ext.chart.MarkerHolder', {
  17081. extend: 'Ext.Mixin',
  17082. requires: [
  17083. 'Ext.chart.Markers'
  17084. ],
  17085. mixinConfig: {
  17086. id: 'markerHolder',
  17087. after: {
  17088. constructor: 'constructor',
  17089. preRender: 'preRender'
  17090. },
  17091. before: {
  17092. destroy: 'destroy'
  17093. }
  17094. },
  17095. isMarkerHolder: true,
  17096. // The combined transformation applied to the sprite by its parents.
  17097. // Does not include the transformation matrix of the sprite itself.
  17098. surfaceMatrix: null,
  17099. // The inverse of the above transformation to go back to the original state.
  17100. inverseSurfaceMatrix: null,
  17101. deprecated: {
  17102. 6: {
  17103. methods: {
  17104. /**
  17105. * Returns the markers bound to the given name.
  17106. * @param {String} name The name of the marker (e.g., "items", "labels", etc.).
  17107. * @return {Ext.chart.Markers[]}
  17108. * @method getBoundMarker
  17109. * @deprecated 6.0 Use {@link #getMarker} instead.
  17110. */
  17111. getBoundMarker: {
  17112. message: "Please use the 'getMarker' method instead.",
  17113. fn: function(name) {
  17114. var marker = this.boundMarkers[name];
  17115. return marker ? [
  17116. marker
  17117. ] : marker;
  17118. }
  17119. }
  17120. }
  17121. }
  17122. },
  17123. constructor: function() {
  17124. this.boundMarkers = {};
  17125. this.cleanRedraw = false;
  17126. },
  17127. /**
  17128. * Registers the given marker with the marker holder under the specified name.
  17129. * @param {String} name The name of the marker (e.g., "items", "labels", etc.).
  17130. * @param {Ext.chart.Markers} marker
  17131. */
  17132. bindMarker: function(name, marker) {
  17133. var me = this,
  17134. markers = me.boundMarkers;
  17135. if (marker && marker.isMarkers) {
  17136. //<debug>
  17137. if (markers[name] && markers[name] === marker) {
  17138. Ext.log.warn(me.getId(), " (MarkerHolder): the Markers instance '", marker.getId(), "' is already bound under the name '", name, "'.");
  17139. }
  17140. //</debug>
  17141. me.releaseMarker(name);
  17142. markers[name] = marker;
  17143. marker.on('destroy', me.onMarkerDestroy, me);
  17144. }
  17145. },
  17146. onMarkerDestroy: function(marker) {
  17147. this.releaseMarker(marker);
  17148. },
  17149. /**
  17150. * Unregisters the given marker or a marker with the given name.
  17151. * Providing a name of the marker is more efficient as it avoids lookup.
  17152. * @param marker {String/Ext.chart.Markers}
  17153. * @return {Ext.chart.Markers} Released marker or null.
  17154. */
  17155. releaseMarker: function(marker) {
  17156. var markers = this.boundMarkers,
  17157. name;
  17158. if (marker && marker.isMarkers) {
  17159. for (name in markers) {
  17160. if (markers[name] === marker) {
  17161. delete markers[name];
  17162. break;
  17163. }
  17164. }
  17165. } else {
  17166. name = marker;
  17167. marker = markers[name];
  17168. delete markers[name];
  17169. }
  17170. return marker || null;
  17171. },
  17172. /**
  17173. * Returns the marker bound to the given name (or null). See {@link #bindMarker}.
  17174. * @param {String} name The name of the marker (e.g., "items", "labels", etc.).
  17175. * @return {Ext.chart.Markers}
  17176. */
  17177. getMarker: function(name) {
  17178. return this.boundMarkers[name] || null;
  17179. },
  17180. preRender: function(surface, ctx, rect) {
  17181. var me = this,
  17182. id = me.getId(),
  17183. boundMarkers = me.boundMarkers,
  17184. parent = me.getParent(),
  17185. name, marker, matrix;
  17186. if (me.surfaceMatrix) {
  17187. matrix = me.surfaceMatrix.set(1, 0, 0, 1, 0, 0);
  17188. } else {
  17189. matrix = me.surfaceMatrix = new Ext.draw.Matrix();
  17190. }
  17191. me.cleanRedraw = !me.attr.dirty;
  17192. if (!me.cleanRedraw) {
  17193. for (name in boundMarkers) {
  17194. marker = boundMarkers[name];
  17195. if (marker) {
  17196. marker.clear(id);
  17197. }
  17198. }
  17199. }
  17200. // Parent can be either a sprite (like a composite or instancing)
  17201. // or a surface. First, climb up and apply transformations of the
  17202. // parent sprites.
  17203. while (parent && parent.attr && parent.attr.matrix) {
  17204. matrix.prependMatrix(parent.attr.matrix);
  17205. parent = parent.getParent();
  17206. }
  17207. // Finally, apply the transformation used by the surface.
  17208. matrix.prependMatrix(parent.matrix);
  17209. me.surfaceMatrix = matrix;
  17210. me.inverseSurfaceMatrix = matrix.inverse(me.inverseSurfaceMatrix);
  17211. },
  17212. putMarker: function(name, attr, index, bypassNormalization, keepRevision) {
  17213. var marker = this.boundMarkers[name];
  17214. if (marker) {
  17215. marker.putMarkerFor(this.getId(), attr, index, bypassNormalization, keepRevision);
  17216. }
  17217. },
  17218. getMarkerBBox: function(name, index, isWithoutTransform) {
  17219. var marker = this.boundMarkers[name];
  17220. if (marker) {
  17221. return marker.getMarkerBBoxFor(this.getId(), index, isWithoutTransform);
  17222. }
  17223. },
  17224. destroy: function() {
  17225. var boundMarkers = this.boundMarkers,
  17226. name, marker;
  17227. for (name in boundMarkers) {
  17228. marker = boundMarkers[name];
  17229. marker.destroy();
  17230. }
  17231. }
  17232. });
  17233. /**
  17234. * @private
  17235. * @class Ext.chart.axis.sprite.Axis
  17236. * @extends Ext.draw.sprite.Sprite
  17237. *
  17238. * The axis sprite. Currently all types of the axis will be rendered with this sprite.
  17239. */
  17240. Ext.define('Ext.chart.axis.sprite.Axis', {
  17241. extend: 'Ext.draw.sprite.Sprite',
  17242. alias: 'sprite.axis',
  17243. type: 'axis',
  17244. mixins: {
  17245. markerHolder: 'Ext.chart.MarkerHolder'
  17246. },
  17247. requires: [
  17248. 'Ext.draw.sprite.Text'
  17249. ],
  17250. inheritableStatics: {
  17251. def: {
  17252. processors: {
  17253. /**
  17254. * @cfg {Boolean} grid 'true' if the axis has a grid.
  17255. */
  17256. grid: 'bool',
  17257. /**
  17258. * @cfg {Boolean} axisLine 'true' if the main line of the axis is drawn.
  17259. */
  17260. axisLine: 'bool',
  17261. /**
  17262. * @cfg {Boolean} minorTicks 'true' if the axis has sub ticks.
  17263. */
  17264. minorTicks: 'bool',
  17265. /**
  17266. * @cfg {Number} minorTickSize The length of the minor ticks.
  17267. */
  17268. minorTickSize: 'number',
  17269. /**
  17270. * @cfg {Boolean} majorTicks 'true' if the axis has major ticks.
  17271. */
  17272. majorTicks: 'bool',
  17273. /**
  17274. * @cfg {Number} majorTickSize The length of the major ticks.
  17275. */
  17276. majorTickSize: 'number',
  17277. /**
  17278. * @cfg {Number} length The total length of the axis.
  17279. */
  17280. length: 'number',
  17281. /**
  17282. * @private
  17283. * @cfg {Number} startGap Axis start determined by the chart inset padding.
  17284. */
  17285. startGap: 'number',
  17286. /**
  17287. * @private
  17288. * @cfg {Number} endGap Axis end determined by the chart inset padding.
  17289. */
  17290. endGap: 'number',
  17291. /**
  17292. * @cfg {Number} dataMin The minimum value of the axis data.
  17293. */
  17294. dataMin: 'number',
  17295. /**
  17296. * @cfg {Number} dataMax The maximum value of the axis data.
  17297. */
  17298. dataMax: 'number',
  17299. /**
  17300. * @cfg {Number} visibleMin The minimum value that is displayed.
  17301. */
  17302. visibleMin: 'number',
  17303. /**
  17304. * @cfg {Number} visibleMax The maximum value that is displayed.
  17305. */
  17306. visibleMax: 'number',
  17307. /**
  17308. * @cfg {String} position The position of the axis on the chart.
  17309. */
  17310. position: 'enums(left,right,top,bottom,angular,radial,gauge)',
  17311. /**
  17312. * @cfg {Number} minStepSize The minimum step size between ticks.
  17313. */
  17314. minStepSize: 'number',
  17315. /**
  17316. * @private
  17317. * @cfg {Number} estStepSize The estimated step size between ticks.
  17318. */
  17319. estStepSize: 'number',
  17320. /**
  17321. * @private
  17322. * Unused.
  17323. */
  17324. titleOffset: 'number',
  17325. /**
  17326. * @cfg {Number} [textPadding=0]
  17327. * The padding around axis labels to determine collision.
  17328. * The default is 0 for all axes except horizontal axes of cartesian charts,
  17329. * where the default is 5 to prevent axis labels from blending one into another.
  17330. * This default is defined in the {@link Ext.chart.theme.Base#axis axis} config
  17331. * of the {@link Ext.chart.theme.Base Base} theme.
  17332. * You may want to change this default to a smaller number or 0, if you have
  17333. * horizontal axis labels rotated, which allows for more text to fit in.
  17334. */
  17335. textPadding: 'number',
  17336. /**
  17337. * @cfg {Number} min The minimum value of the axis.
  17338. * `min` and {@link #max} attributes represent the effective range of the axis
  17339. * after segmentation, layout, and range reconciliation between axes.
  17340. */
  17341. min: 'number',
  17342. /**
  17343. * @cfg {Number} max The maximum value of the axis.
  17344. * {@link #min} and `max` attributes represent the effective range of the axis
  17345. * after segmentation, layout, and range reconciliation between axes.
  17346. */
  17347. max: 'number',
  17348. /**
  17349. * @cfg {Number} centerX The central point of the angular axis on the x-axis.
  17350. */
  17351. centerX: 'number',
  17352. /**
  17353. * @cfg {Number} centerY The central point of the angular axis on the y-axis.
  17354. */
  17355. centerY: 'number',
  17356. /**
  17357. * @private
  17358. * @cfg {Number} radius
  17359. * Unused.
  17360. */
  17361. radius: 'number',
  17362. /**
  17363. * @private
  17364. */
  17365. totalAngle: 'number',
  17366. /**
  17367. * @cfg {Number} baseRotation The starting rotation of the angular axis.
  17368. */
  17369. baseRotation: 'number',
  17370. /**
  17371. * @private
  17372. * Unused.
  17373. */
  17374. data: 'default',
  17375. /**
  17376. * @cfg {Boolean} 'true' if the estimated step size is adjusted by text size.
  17377. */
  17378. enlargeEstStepSizeByText: 'bool'
  17379. },
  17380. defaults: {
  17381. grid: false,
  17382. axisLine: true,
  17383. minorTicks: false,
  17384. minorTickSize: 3,
  17385. majorTicks: true,
  17386. majorTickSize: 5,
  17387. length: 0,
  17388. startGap: 0,
  17389. endGap: 0,
  17390. visibleMin: 0,
  17391. visibleMax: 1,
  17392. dataMin: 0,
  17393. dataMax: 1,
  17394. position: '',
  17395. minStepSize: 0,
  17396. estStepSize: 20,
  17397. min: 0,
  17398. max: 1,
  17399. centerX: 0,
  17400. centerY: 0,
  17401. radius: 1,
  17402. baseRotation: 0,
  17403. data: null,
  17404. titleOffset: 0,
  17405. textPadding: 0,
  17406. scalingCenterY: 0,
  17407. scalingCenterX: 0,
  17408. // Override default
  17409. strokeStyle: 'black',
  17410. enlargeEstStepSizeByText: false
  17411. },
  17412. triggers: {
  17413. minorTickSize: 'bbox',
  17414. majorTickSize: 'bbox',
  17415. position: 'bbox,layout',
  17416. axisLine: 'bbox,layout',
  17417. minorTicks: 'layout',
  17418. min: 'layout',
  17419. max: 'layout',
  17420. length: 'layout',
  17421. minStepSize: 'layout',
  17422. estStepSize: 'layout',
  17423. data: 'layout',
  17424. dataMin: 'layout',
  17425. dataMax: 'layout',
  17426. visibleMin: 'layout',
  17427. visibleMax: 'layout',
  17428. enlargeEstStepSizeByText: 'layout'
  17429. },
  17430. updaters: {
  17431. layout: 'layoutUpdater'
  17432. }
  17433. }
  17434. },
  17435. config: {
  17436. /**
  17437. * @cfg {Object} label
  17438. *
  17439. * The label configuration object for the Axis. This object may include style attributes
  17440. * like `spacing`, `padding`, `font` that receives a string or number and
  17441. * returns a new string with the modified values.
  17442. */
  17443. label: null,
  17444. /**
  17445. * @cfg {Number} labelOffset
  17446. * The distance between the label and the edge of a major tick.
  17447. * Only applicable for 'gauge' and 'angular' axes.
  17448. */
  17449. labelOffset: 10,
  17450. /**
  17451. * @cfg {Object|Ext.chart.axis.layout.Layout} layout The layout configuration used by
  17452. * the axis.
  17453. */
  17454. layout: null,
  17455. /**
  17456. * @cfg {Object|Ext.chart.axis.segmenter.Segmenter} segmenter The method of segmenter
  17457. * used by the axis.
  17458. */
  17459. segmenter: null,
  17460. /**
  17461. * @cfg {Function} renderer Allows direct customisation of rendered axis sprites.
  17462. */
  17463. renderer: null,
  17464. /**
  17465. * @private
  17466. * @cfg {Object} layoutContext Stores the context after calculating layout.
  17467. */
  17468. layoutContext: null,
  17469. /**
  17470. * @cfg {Ext.chart.axis.Axis} axis The axis represented by this sprite.
  17471. */
  17472. axis: null
  17473. },
  17474. thickness: 0,
  17475. stepSize: 0,
  17476. getBBox: function() {
  17477. return null;
  17478. },
  17479. defaultRenderer: function(v) {
  17480. // 'this' pointer in this case is a layoutContext
  17481. return this.segmenter.renderer(v, this);
  17482. },
  17483. layoutUpdater: function() {
  17484. var me = this,
  17485. chart = me.getAxis().getChart();
  17486. if (chart.isInitializing) {
  17487. return;
  17488. }
  17489. // eslint-disable-next-line vars-on-top, one-var
  17490. var attr = me.attr,
  17491. layout = me.getLayout(),
  17492. isRtl = chart.getInherited().rtl,
  17493. dataRange = attr.dataMax - attr.dataMin,
  17494. min = attr.dataMin + dataRange * attr.visibleMin,
  17495. max = attr.dataMin + dataRange * attr.visibleMax,
  17496. range = max - min,
  17497. position = attr.position,
  17498. context = {
  17499. attr: attr,
  17500. segmenter: me.getSegmenter(),
  17501. renderer: me.defaultRenderer
  17502. };
  17503. if (position === 'left' || position === 'right') {
  17504. attr.translationX = 0;
  17505. attr.translationY = max * attr.length / range;
  17506. attr.scalingX = 1;
  17507. attr.scalingY = -attr.length / range;
  17508. attr.scalingCenterY = 0;
  17509. attr.scalingCenterX = 0;
  17510. me.applyTransformations(true);
  17511. } else if (position === 'top' || position === 'bottom') {
  17512. if (isRtl) {
  17513. attr.translationX = attr.length + min * attr.length / range + 1;
  17514. } else {
  17515. attr.translationX = -min * attr.length / range;
  17516. }
  17517. attr.translationY = 0;
  17518. attr.scalingX = (isRtl ? -1 : 1) * attr.length / range;
  17519. attr.scalingY = 1;
  17520. attr.scalingCenterY = 0;
  17521. attr.scalingCenterX = 0;
  17522. me.applyTransformations(true);
  17523. }
  17524. if (layout) {
  17525. layout.calculateLayout(context);
  17526. me.setLayoutContext(context);
  17527. }
  17528. },
  17529. iterate: function(snaps, fn) {
  17530. var i, position, id, axis, floatingAxes, floatingValues,
  17531. some = Ext.Array.some,
  17532. abs = Math.abs,
  17533. threshold, isTickVisible;
  17534. if (snaps.getLabel) {
  17535. // Discrete layout.
  17536. if (snaps.min < snaps.from) {
  17537. fn.call(this, snaps.min, snaps.getLabel(snaps.min), -1, snaps);
  17538. }
  17539. for (i = 0; i <= snaps.steps; i++) {
  17540. fn.call(this, snaps.get(i), snaps.getLabel(i), i, snaps);
  17541. }
  17542. if (snaps.max > snaps.to) {
  17543. fn.call(this, snaps.max, snaps.getLabel(snaps.max), snaps.steps + 1, snaps);
  17544. }
  17545. } else {
  17546. axis = this.getAxis();
  17547. floatingAxes = axis.floatingAxes;
  17548. floatingValues = [];
  17549. threshold = (snaps.to - snaps.from) / (snaps.steps + 1);
  17550. if (axis.getFloating()) {
  17551. for (id in floatingAxes) {
  17552. floatingValues.push(floatingAxes[id]);
  17553. }
  17554. }
  17555. // Don't render ticks in axes intersection points.
  17556. isTickVisible = function(position) {
  17557. return !floatingValues.length || some(floatingValues, function(value) {
  17558. return abs(value - position) > threshold;
  17559. });
  17560. };
  17561. if (snaps.min < snaps.from && isTickVisible(snaps.min)) {
  17562. fn.call(this, snaps.min, snaps.min, -1, snaps);
  17563. }
  17564. for (i = 0; i <= snaps.steps; i++) {
  17565. position = snaps.get(i);
  17566. if (isTickVisible(position)) {
  17567. fn.call(this, position, position, i, snaps);
  17568. }
  17569. }
  17570. if (snaps.max > snaps.to && isTickVisible(snaps.max)) {
  17571. fn.call(this, snaps.max, snaps.max, snaps.steps + 1, snaps);
  17572. }
  17573. }
  17574. },
  17575. renderTicks: function(surface, ctx, layout, clipRect) {
  17576. var me = this,
  17577. attr = me.attr,
  17578. docked = attr.position,
  17579. matrix = attr.matrix,
  17580. halfLineWidth = 0.5 * attr.lineWidth,
  17581. xx = matrix.getXX(),
  17582. dx = matrix.getDX(),
  17583. yy = matrix.getYY(),
  17584. dy = matrix.getDY(),
  17585. majorTicks = layout.majorTicks,
  17586. majorTickSize = attr.majorTickSize,
  17587. minorTicks = layout.minorTicks,
  17588. minorTickSize = attr.minorTickSize,
  17589. gaugeAngles;
  17590. /* eslint-disable no-inner-declarations, no-case-declarations */
  17591. if (majorTicks) {
  17592. switch (docked) {
  17593. case 'right':
  17594. function getRightTickFn(size) {
  17595. return function(position, labelText, i) {
  17596. position = surface.roundPixel(position * yy + dy) + halfLineWidth;
  17597. ctx.moveTo(0, position);
  17598. ctx.lineTo(size, position);
  17599. };
  17600. };
  17601. me.iterate(majorTicks, getRightTickFn(majorTickSize));
  17602. if (minorTicks) {
  17603. me.iterate(minorTicks, getRightTickFn(minorTickSize));
  17604. };
  17605. break;
  17606. case 'left':
  17607. function getLeftTickFn(size) {
  17608. return function(position, labelText, i) {
  17609. position = surface.roundPixel(position * yy + dy) + halfLineWidth;
  17610. ctx.moveTo(clipRect[2] - size, position);
  17611. ctx.lineTo(clipRect[2], position);
  17612. };
  17613. };
  17614. me.iterate(majorTicks, getLeftTickFn(majorTickSize));
  17615. if (minorTicks) {
  17616. me.iterate(minorTicks, getLeftTickFn(minorTickSize));
  17617. };
  17618. break;
  17619. case 'bottom':
  17620. function getBottomTickFn(size) {
  17621. return function(position, labelText, i) {
  17622. position = surface.roundPixel(position * xx + dx) - halfLineWidth;
  17623. ctx.moveTo(position, 0);
  17624. ctx.lineTo(position, size);
  17625. };
  17626. };
  17627. me.iterate(majorTicks, getBottomTickFn(majorTickSize));
  17628. if (minorTicks) {
  17629. me.iterate(minorTicks, getBottomTickFn(minorTickSize));
  17630. };
  17631. break;
  17632. case 'top':
  17633. function getTopTickFn(size) {
  17634. return function(position, labelText, i) {
  17635. position = surface.roundPixel(position * xx + dx) - halfLineWidth;
  17636. ctx.moveTo(position, clipRect[3]);
  17637. ctx.lineTo(position, clipRect[3] - size);
  17638. };
  17639. };
  17640. me.iterate(majorTicks, getTopTickFn(majorTickSize));
  17641. if (minorTicks) {
  17642. me.iterate(minorTicks, getTopTickFn(minorTickSize));
  17643. };
  17644. break;
  17645. case 'angular':
  17646. me.iterate(majorTicks, function(position, labelText, i) {
  17647. position = position / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
  17648. ctx.moveTo(attr.centerX + (attr.length) * Math.cos(position), attr.centerY + (attr.length) * Math.sin(position));
  17649. ctx.lineTo(attr.centerX + (attr.length + majorTickSize) * Math.cos(position), attr.centerY + (attr.length + majorTickSize) * Math.sin(position));
  17650. });
  17651. break;
  17652. case 'gauge':
  17653. gaugeAngles = me.getGaugeAngles();
  17654. me.iterate(majorTicks, function(position, labelText, i) {
  17655. position = (position - attr.min) / (attr.max - attr.min) * attr.totalAngle - attr.totalAngle + gaugeAngles.start;
  17656. ctx.moveTo(attr.centerX + (attr.length) * Math.cos(position), attr.centerY + (attr.length) * Math.sin(position));
  17657. ctx.lineTo(attr.centerX + (attr.length + majorTickSize) * Math.cos(position), attr.centerY + (attr.length + majorTickSize) * Math.sin(position));
  17658. });
  17659. break;
  17660. }
  17661. }
  17662. },
  17663. /* eslint-enable no-inner-declarations, no-case-declarations */
  17664. renderLabels: function(surface, ctx, layoutContext, clipRect) {
  17665. var me = this,
  17666. attr = me.attr,
  17667. halfLineWidth = 0.5 * attr.lineWidth,
  17668. docked = attr.position,
  17669. matrix = attr.matrix,
  17670. textPadding = attr.textPadding,
  17671. xx = matrix.getXX(),
  17672. dx = matrix.getDX(),
  17673. yy = matrix.getYY(),
  17674. dy = matrix.getDY(),
  17675. thickness = 0,
  17676. majorTicks = layoutContext.majorTicks,
  17677. tickPadding = Math.max(attr.majorTickSize, attr.minorTickSize) + attr.lineWidth,
  17678. isBBoxIntersect = Ext.draw.Draw.isBBoxIntersect,
  17679. label = me.getLabel(),
  17680. font,
  17681. labelOffset = me.getLabelOffset(),
  17682. lastLabelText = null,
  17683. textSize = 0,
  17684. textCount = 0,
  17685. segmenter = layoutContext.segmenter,
  17686. renderer = me.getRenderer(),
  17687. axis = me.getAxis(),
  17688. title = axis.getTitle(),
  17689. titleBBox = title && title.attr.text !== '' && title.getBBox(),
  17690. labelInverseMatrix,
  17691. lastBBox = null,
  17692. bbox, fly, text, titlePadding, translation, gaugeAngles, angle;
  17693. if (majorTicks && label && !label.attr.hidden) {
  17694. font = label.attr.font;
  17695. if (ctx.font !== font) {
  17696. ctx.font = font;
  17697. }
  17698. // This can profoundly improve performance.
  17699. label.setAttributes({
  17700. translationX: 0,
  17701. translationY: 0
  17702. }, true);
  17703. label.applyTransformations();
  17704. labelInverseMatrix = label.attr.inverseMatrix.elements.slice(0);
  17705. switch (docked) {
  17706. case 'left':
  17707. titlePadding = titleBBox ? titleBBox.x + titleBBox.width : 0;
  17708. switch (label.attr.textAlign) {
  17709. case 'start':
  17710. translation = surface.roundPixel(titlePadding + dx) - halfLineWidth;
  17711. break;
  17712. case 'end':
  17713. translation = surface.roundPixel(clipRect[2] - tickPadding + dx) - halfLineWidth;
  17714. break;
  17715. default:
  17716. // 'center'
  17717. translation = surface.roundPixel(titlePadding + (clipRect[2] - titlePadding - tickPadding) / 2 + dx) - halfLineWidth;
  17718. };
  17719. label.setAttributes({
  17720. translationX: translation
  17721. }, true);
  17722. break;
  17723. case 'right':
  17724. titlePadding = titleBBox ? clipRect[2] - titleBBox.x : 0;
  17725. switch (label.attr.textAlign) {
  17726. case 'start':
  17727. translation = surface.roundPixel(tickPadding + dx) + halfLineWidth;
  17728. break;
  17729. case 'end':
  17730. translation = surface.roundPixel(clipRect[2] - titlePadding + dx) + halfLineWidth;
  17731. break;
  17732. default:
  17733. // 'center'
  17734. translation = surface.roundPixel(tickPadding + (clipRect[2] - tickPadding - titlePadding) / 2 + dx) + halfLineWidth;
  17735. };
  17736. label.setAttributes({
  17737. translationX: translation
  17738. }, true);
  17739. break;
  17740. case 'top':
  17741. titlePadding = titleBBox ? titleBBox.y + titleBBox.height : 0;
  17742. label.setAttributes({
  17743. translationY: surface.roundPixel(titlePadding + (clipRect[3] - titlePadding - tickPadding) / 2) - halfLineWidth
  17744. }, true);
  17745. break;
  17746. case 'bottom':
  17747. titlePadding = titleBBox ? clipRect[3] - titleBBox.y : 0;
  17748. label.setAttributes({
  17749. translationY: surface.roundPixel(tickPadding + (clipRect[3] - tickPadding - titlePadding) / 2) + halfLineWidth
  17750. }, true);
  17751. break;
  17752. case 'radial':
  17753. label.setAttributes({
  17754. translationX: attr.centerX
  17755. }, true);
  17756. break;
  17757. case 'angular':
  17758. label.setAttributes({
  17759. translationY: attr.centerY
  17760. }, true);
  17761. break;
  17762. case 'gauge':
  17763. label.setAttributes({
  17764. translationY: attr.centerY
  17765. }, true);
  17766. break;
  17767. }
  17768. // TODO: there are better ways to detect collision.
  17769. if (docked === 'left' || docked === 'right') {
  17770. me.iterate(majorTicks, function(position, labelText, i) {
  17771. if (labelText === undefined) {
  17772. return;
  17773. }
  17774. if (renderer) {
  17775. text = Ext.callback(renderer, null, [
  17776. axis,
  17777. labelText,
  17778. layoutContext,
  17779. lastLabelText
  17780. ], 0, axis);
  17781. } else {
  17782. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17783. }
  17784. lastLabelText = labelText;
  17785. label.setAttributes({
  17786. text: String(text),
  17787. translationY: surface.roundPixel(position * yy + dy)
  17788. }, true);
  17789. label.applyTransformations();
  17790. thickness = Math.max(thickness, label.getBBox().width + tickPadding);
  17791. fly = Ext.draw.Matrix.fly(label.attr.matrix.elements.slice(0));
  17792. bbox = fly.prepend.apply(fly, labelInverseMatrix).transformBBox(label.getBBox(true));
  17793. if (lastBBox && !isBBoxIntersect(bbox, lastBBox, textPadding)) {
  17794. return;
  17795. }
  17796. surface.renderSprite(label);
  17797. lastBBox = bbox;
  17798. textSize += bbox.height;
  17799. textCount++;
  17800. });
  17801. } else if (docked === 'top' || docked === 'bottom') {
  17802. me.iterate(majorTicks, function(position, labelText, i) {
  17803. if (labelText === undefined) {
  17804. return;
  17805. }
  17806. if (renderer) {
  17807. text = Ext.callback(renderer, null, [
  17808. axis,
  17809. labelText,
  17810. layoutContext,
  17811. lastLabelText
  17812. ], 0, axis);
  17813. } else {
  17814. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17815. }
  17816. lastLabelText = labelText;
  17817. label.setAttributes({
  17818. text: String(text),
  17819. translationX: surface.roundPixel(position * xx + dx)
  17820. }, true);
  17821. label.applyTransformations();
  17822. thickness = Math.max(thickness, label.getBBox().height + tickPadding);
  17823. fly = Ext.draw.Matrix.fly(label.attr.matrix.elements.slice(0));
  17824. bbox = fly.prepend.apply(fly, labelInverseMatrix).transformBBox(label.getBBox(true));
  17825. if (lastBBox && !isBBoxIntersect(bbox, lastBBox, textPadding)) {
  17826. return;
  17827. }
  17828. surface.renderSprite(label);
  17829. lastBBox = bbox;
  17830. textSize += bbox.width;
  17831. textCount++;
  17832. });
  17833. } else if (docked === 'radial') {
  17834. me.iterate(majorTicks, function(position, labelText, i) {
  17835. if (labelText === undefined) {
  17836. return;
  17837. }
  17838. if (renderer) {
  17839. text = Ext.callback(renderer, null, [
  17840. axis,
  17841. labelText,
  17842. layoutContext,
  17843. lastLabelText
  17844. ], 0, axis);
  17845. } else {
  17846. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17847. }
  17848. lastLabelText = labelText;
  17849. if (typeof text !== 'undefined') {
  17850. label.setAttributes({
  17851. text: String(text),
  17852. translationX: attr.centerX - surface.roundPixel(position) / attr.max * attr.length * Math.cos(attr.baseRotation + Math.PI / 2),
  17853. translationY: attr.centerY - surface.roundPixel(position) / attr.max * attr.length * Math.sin(attr.baseRotation + Math.PI / 2)
  17854. }, true);
  17855. label.applyTransformations();
  17856. bbox = label.attr.matrix.transformBBox(label.getBBox(true));
  17857. if (lastBBox && !isBBoxIntersect(bbox, lastBBox)) {
  17858. return;
  17859. }
  17860. surface.renderSprite(label);
  17861. lastBBox = bbox;
  17862. textSize += bbox.width;
  17863. textCount++;
  17864. }
  17865. });
  17866. } else if (docked === 'angular') {
  17867. labelOffset += attr.majorTickSize + attr.lineWidth * 0.5;
  17868. me.iterate(majorTicks, function(position, labelText, i) {
  17869. if (labelText === undefined) {
  17870. return;
  17871. }
  17872. if (renderer) {
  17873. text = Ext.callback(renderer, null, [
  17874. axis,
  17875. labelText,
  17876. layoutContext,
  17877. lastLabelText
  17878. ], 0, axis);
  17879. } else {
  17880. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17881. }
  17882. lastLabelText = labelText;
  17883. thickness = Math.max(thickness, Math.max(attr.majorTickSize, attr.minorTickSize) + (attr.lineCap !== 'butt' ? attr.lineWidth * 0.5 : 0));
  17884. if (typeof text !== 'undefined') {
  17885. angle = position / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
  17886. label.setAttributes({
  17887. text: String(text),
  17888. translationX: attr.centerX + (attr.length + labelOffset) * Math.cos(angle),
  17889. translationY: attr.centerY + (attr.length + labelOffset) * Math.sin(angle)
  17890. }, true);
  17891. label.applyTransformations();
  17892. bbox = label.attr.matrix.transformBBox(label.getBBox(true));
  17893. if (lastBBox && !isBBoxIntersect(bbox, lastBBox)) {
  17894. return;
  17895. }
  17896. surface.renderSprite(label);
  17897. lastBBox = bbox;
  17898. textSize += bbox.width;
  17899. textCount++;
  17900. }
  17901. });
  17902. } else if (docked === 'gauge') {
  17903. gaugeAngles = me.getGaugeAngles();
  17904. labelOffset += attr.majorTickSize + attr.lineWidth * 0.5;
  17905. me.iterate(majorTicks, function(position, labelText, i) {
  17906. if (labelText === undefined) {
  17907. return;
  17908. }
  17909. if (renderer) {
  17910. text = Ext.callback(renderer, null, [
  17911. axis,
  17912. labelText,
  17913. layoutContext,
  17914. lastLabelText
  17915. ], 0, axis);
  17916. } else {
  17917. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17918. }
  17919. lastLabelText = labelText;
  17920. if (typeof text !== 'undefined') {
  17921. angle = (position - attr.min) / (attr.max - attr.min) * attr.totalAngle - attr.totalAngle + gaugeAngles.start;
  17922. label.setAttributes({
  17923. text: String(text),
  17924. translationX: attr.centerX + (attr.length + labelOffset) * Math.cos(angle),
  17925. translationY: attr.centerY + (attr.length + labelOffset) * Math.sin(angle)
  17926. }, true);
  17927. label.applyTransformations();
  17928. bbox = label.attr.matrix.transformBBox(label.getBBox(true));
  17929. if (lastBBox && !isBBoxIntersect(bbox, lastBBox)) {
  17930. return;
  17931. }
  17932. surface.renderSprite(label);
  17933. lastBBox = bbox;
  17934. textSize += bbox.width;
  17935. textCount++;
  17936. }
  17937. });
  17938. }
  17939. if (attr.enlargeEstStepSizeByText && textCount) {
  17940. textSize /= textCount;
  17941. textSize += tickPadding;
  17942. textSize *= 2;
  17943. if (attr.estStepSize < textSize) {
  17944. attr.estStepSize = textSize;
  17945. }
  17946. }
  17947. if (Math.abs(me.thickness - thickness) > 1) {
  17948. me.thickness = thickness;
  17949. attr.bbox.plain.dirty = true;
  17950. attr.bbox.transform.dirty = true;
  17951. me.doThicknessChanged();
  17952. return false;
  17953. }
  17954. }
  17955. },
  17956. renderAxisLine: function(surface, ctx, layout, clipRect) {
  17957. var me = this,
  17958. attr = me.attr,
  17959. halfLineWidth = attr.lineWidth * 0.5,
  17960. docked = attr.position,
  17961. position, gaugeAngles;
  17962. if (attr.axisLine && attr.length) {
  17963. switch (docked) {
  17964. case 'left':
  17965. position = surface.roundPixel(clipRect[2]) - halfLineWidth;
  17966. ctx.moveTo(position, -attr.endGap);
  17967. ctx.lineTo(position, attr.length + attr.startGap + 1);
  17968. break;
  17969. case 'right':
  17970. ctx.moveTo(halfLineWidth, -attr.endGap);
  17971. ctx.lineTo(halfLineWidth, attr.length + attr.startGap + 1);
  17972. break;
  17973. case 'bottom':
  17974. ctx.moveTo(-attr.startGap, halfLineWidth);
  17975. ctx.lineTo(attr.length + attr.endGap, halfLineWidth);
  17976. break;
  17977. case 'top':
  17978. position = surface.roundPixel(clipRect[3]) - halfLineWidth;
  17979. ctx.moveTo(-attr.startGap, position);
  17980. ctx.lineTo(attr.length + attr.endGap, position);
  17981. break;
  17982. case 'angular':
  17983. ctx.moveTo(attr.centerX + attr.length, attr.centerY);
  17984. ctx.arc(attr.centerX, attr.centerY, attr.length, 0, Math.PI * 2, true);
  17985. break;
  17986. case 'gauge':
  17987. gaugeAngles = me.getGaugeAngles();
  17988. ctx.moveTo(attr.centerX + Math.cos(gaugeAngles.start) * attr.length, attr.centerY + Math.sin(gaugeAngles.start) * attr.length);
  17989. ctx.arc(attr.centerX, attr.centerY, attr.length, gaugeAngles.start, gaugeAngles.end, true);
  17990. break;
  17991. }
  17992. }
  17993. },
  17994. getGaugeAngles: function() {
  17995. var me = this,
  17996. angle = me.attr.totalAngle,
  17997. offset;
  17998. if (angle <= Math.PI) {
  17999. offset = (Math.PI - angle) * 0.5;
  18000. } else {
  18001. offset = -(Math.PI * 2 - angle) * 0.5;
  18002. }
  18003. offset = Math.PI * 2 - offset;
  18004. return {
  18005. start: offset,
  18006. end: offset - angle
  18007. };
  18008. },
  18009. renderGridLines: function(surface, ctx, layout, clipRect) {
  18010. var me = this,
  18011. axis = me.getAxis(),
  18012. attr = me.attr,
  18013. matrix = attr.matrix,
  18014. startGap = attr.startGap,
  18015. endGap = attr.endGap,
  18016. xx = matrix.getXX(),
  18017. yy = matrix.getYY(),
  18018. dx = matrix.getDX(),
  18019. dy = matrix.getDY(),
  18020. position = attr.position,
  18021. alignment = axis.getGridAlignment(),
  18022. majorTicks = layout.majorTicks,
  18023. anchor, j, lastAnchor;
  18024. if (attr.grid) {
  18025. if (majorTicks) {
  18026. if (position === 'left' || position === 'right') {
  18027. lastAnchor = attr.min * yy + dy + endGap + startGap;
  18028. me.iterate(majorTicks, function(position, labelText, i) {
  18029. anchor = position * yy + dy + endGap;
  18030. me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
  18031. y: anchor,
  18032. height: lastAnchor - anchor
  18033. }, j = i, true);
  18034. lastAnchor = anchor;
  18035. });
  18036. j++;
  18037. anchor = 0;
  18038. me.putMarker(alignment + '-' + (j % 2 ? 'odd' : 'even'), {
  18039. y: anchor,
  18040. height: lastAnchor - anchor
  18041. }, j, true);
  18042. } else if (position === 'top' || position === 'bottom') {
  18043. lastAnchor = attr.min * xx + dx + startGap;
  18044. if (startGap) {
  18045. me.putMarker(alignment + '-even', {
  18046. x: 0,
  18047. width: lastAnchor
  18048. }, -1, true);
  18049. }
  18050. me.iterate(majorTicks, function(position, labelText, i) {
  18051. anchor = position * xx + dx + startGap;
  18052. me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
  18053. x: anchor,
  18054. width: lastAnchor - anchor
  18055. }, j = i, true);
  18056. lastAnchor = anchor;
  18057. });
  18058. j++;
  18059. anchor = attr.length + attr.startGap + attr.endGap;
  18060. me.putMarker(alignment + '-' + (j % 2 ? 'odd' : 'even'), {
  18061. x: anchor,
  18062. width: lastAnchor - anchor
  18063. }, j, true);
  18064. } else if (position === 'radial') {
  18065. me.iterate(majorTicks, function(position, labelText, i) {
  18066. if (!position) {
  18067. return;
  18068. }
  18069. anchor = position / attr.max * attr.length;
  18070. me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
  18071. scalingX: anchor,
  18072. scalingY: anchor
  18073. }, i, true);
  18074. lastAnchor = anchor;
  18075. });
  18076. } else if (position === 'angular') {
  18077. me.iterate(majorTicks, function(position, labelText, i) {
  18078. if (!attr.length) {
  18079. return;
  18080. }
  18081. anchor = position / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
  18082. me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
  18083. rotationRads: anchor,
  18084. rotationCenterX: 0,
  18085. rotationCenterY: 0,
  18086. scalingX: attr.length,
  18087. scalingY: attr.length
  18088. }, i, true);
  18089. lastAnchor = anchor;
  18090. });
  18091. }
  18092. }
  18093. }
  18094. },
  18095. renderLimits: function(clipRect) {
  18096. var me = this,
  18097. attr = me.attr,
  18098. axis = me.getAxis(),
  18099. limits = Ext.Array.from(axis.getLimits());
  18100. if (!limits.length || attr.dataMin === attr.dataMax) {
  18101. if (axis.limits) {
  18102. axis.limits.titles.attr.hidden = true;
  18103. }
  18104. return;
  18105. }
  18106. // eslint-disable-next-line vars-on-top, one-var
  18107. var chart = axis.getChart(),
  18108. innerPadding = chart.getInnerPadding(),
  18109. limitsRect = axis.limits.surface.getRect(),
  18110. matrix = attr.matrix,
  18111. position = attr.position,
  18112. chain = Ext.Object.chain,
  18113. titles = axis.limits.titles,
  18114. titleBBox, titlePosition, titleFlip, limit, value, i, ln, x, y;
  18115. titles.attr.hidden = false;
  18116. titles.instances = [];
  18117. titles.position = 0;
  18118. if (position === 'left' || position === 'right') {
  18119. for (i = 0 , ln = limits.length; i < ln; i++) {
  18120. limit = chain(limits[i]);
  18121. if (!limit.line) {
  18122. limit.line = {};
  18123. }
  18124. value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
  18125. value = value * matrix.getYY() + matrix.getDY();
  18126. limit.line.y = value + innerPadding.top;
  18127. limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
  18128. me.putMarker('horizontal-limit-lines', limit.line, i, true);
  18129. if (limit.line.title) {
  18130. titles.add(limit.line.title);
  18131. titleBBox = titles.getBBoxFor(titles.position - 1);
  18132. titlePosition = limit.line.title.position || (position === 'left' ? 'start' : 'end');
  18133. switch (titlePosition) {
  18134. case 'start':
  18135. x = 10;
  18136. break;
  18137. case 'end':
  18138. x = limitsRect[2] - 10;
  18139. break;
  18140. case 'middle':
  18141. x = limitsRect[2] / 2;
  18142. break;
  18143. }
  18144. titles.setAttributesFor(titles.position - 1, {
  18145. x: x,
  18146. y: limit.line.y - titleBBox.height / 2,
  18147. textAlign: titlePosition,
  18148. fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle
  18149. });
  18150. }
  18151. }
  18152. } else if (position === 'top' || position === 'bottom') {
  18153. for (i = 0 , ln = limits.length; i < ln; i++) {
  18154. limit = chain(limits[i]);
  18155. if (!limit.line) {
  18156. limit.line = {};
  18157. }
  18158. value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
  18159. value = value * matrix.getXX() + matrix.getDX();
  18160. limit.line.x = value + innerPadding.left;
  18161. limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
  18162. me.putMarker('vertical-limit-lines', limit.line, i, true);
  18163. if (limit.line.title) {
  18164. titles.add(limit.line.title);
  18165. titleBBox = titles.getBBoxFor(titles.position - 1);
  18166. titlePosition = limit.line.title.position || (position === 'top' ? 'end' : 'start');
  18167. switch (titlePosition) {
  18168. case 'start':
  18169. y = limitsRect[3] - titleBBox.width / 2 - 10;
  18170. break;
  18171. case 'end':
  18172. y = titleBBox.width / 2 + 10;
  18173. break;
  18174. case 'middle':
  18175. y = limitsRect[3] / 2;
  18176. break;
  18177. }
  18178. titles.setAttributesFor(titles.position - 1, {
  18179. x: limit.line.x + titleBBox.height / 2,
  18180. y: y,
  18181. fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle,
  18182. rotationRads: Math.PI / 2
  18183. });
  18184. }
  18185. }
  18186. } else if (position === 'radial') {
  18187. for (i = 0 , ln = limits.length; i < ln; i++) {
  18188. limit = chain(limits[i]);
  18189. if (!limit.line) {
  18190. limit.line = {};
  18191. }
  18192. value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
  18193. if (value > attr.max) {
  18194. continue;
  18195. }
  18196. value = value / attr.max * attr.length;
  18197. limit.line.cx = attr.centerX;
  18198. limit.line.cy = attr.centerY;
  18199. limit.line.scalingX = value;
  18200. limit.line.scalingY = value;
  18201. limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
  18202. me.putMarker('circular-limit-lines', limit.line, i, true);
  18203. if (limit.line.title) {
  18204. titles.add(limit.line.title);
  18205. titleBBox = titles.getBBoxFor(titles.position - 1);
  18206. titles.setAttributesFor(titles.position - 1, {
  18207. x: attr.centerX,
  18208. y: attr.centerY - value - titleBBox.height / 2,
  18209. fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle
  18210. });
  18211. }
  18212. }
  18213. } else if (position === 'angular') {
  18214. for (i = 0 , ln = limits.length; i < ln; i++) {
  18215. limit = chain(limits[i]);
  18216. if (!limit.line) {
  18217. limit.line = {};
  18218. }
  18219. value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
  18220. value = value / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
  18221. limit.line.translationX = attr.centerX;
  18222. limit.line.translationY = attr.centerY;
  18223. limit.line.rotationRads = value;
  18224. limit.line.rotationCenterX = 0;
  18225. limit.line.rotationCenterY = 0;
  18226. limit.line.scalingX = attr.length;
  18227. limit.line.scalingY = attr.length;
  18228. limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
  18229. me.putMarker('radial-limit-lines', limit.line, i, true);
  18230. if (limit.line.title) {
  18231. titles.add(limit.line.title);
  18232. titleBBox = titles.getBBoxFor(titles.position - 1);
  18233. titleFlip = ((value > -0.5 * Math.PI && value < 0.5 * Math.PI) || (value > 1.5 * Math.PI && value < 2 * Math.PI)) ? 1 : -1;
  18234. titles.setAttributesFor(titles.position - 1, {
  18235. x: attr.centerX + 0.5 * attr.length * Math.cos(value) + titleFlip * titleBBox.height / 2 * Math.sin(value),
  18236. y: attr.centerY + 0.5 * attr.length * Math.sin(value) - titleFlip * titleBBox.height / 2 * Math.cos(value),
  18237. rotationRads: titleFlip === 1 ? value : value - Math.PI,
  18238. fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle
  18239. });
  18240. }
  18241. }
  18242. } else if (position === 'gauge') {}
  18243. },
  18244. // TODO
  18245. doThicknessChanged: function() {
  18246. var axis = this.getAxis();
  18247. if (axis) {
  18248. axis.onThicknessChanged();
  18249. }
  18250. },
  18251. render: function(surface, ctx, rect) {
  18252. var me = this,
  18253. layoutContext = me.getLayoutContext();
  18254. if (layoutContext) {
  18255. if (me.renderLabels(surface, ctx, layoutContext, rect) === false) {
  18256. return false;
  18257. }
  18258. ctx.beginPath();
  18259. me.renderTicks(surface, ctx, layoutContext, rect);
  18260. me.renderAxisLine(surface, ctx, layoutContext, rect);
  18261. me.renderGridLines(surface, ctx, layoutContext, rect);
  18262. me.renderLimits(rect);
  18263. ctx.stroke();
  18264. }
  18265. }
  18266. });
  18267. /**
  18268. * @abstract
  18269. * @class Ext.chart.axis.segmenter.Segmenter
  18270. *
  18271. * Interface for a segmenter in an Axis. A segmenter defines the operations you can do to a specific
  18272. * data type.
  18273. *
  18274. * See {@link Ext.chart.axis.Axis}.
  18275. *
  18276. */
  18277. Ext.define('Ext.chart.axis.segmenter.Segmenter', {
  18278. config: {
  18279. /**
  18280. * @cfg {Ext.chart.axis.Axis} axis The axis that the Segmenter is bound.
  18281. */
  18282. axis: null
  18283. },
  18284. constructor: function(config) {
  18285. this.initConfig(config);
  18286. },
  18287. /**
  18288. * This method formats the value.
  18289. *
  18290. * @param {*} value The value to format.
  18291. * @param {Object} context Axis layout context.
  18292. * @return {String}
  18293. */
  18294. renderer: function(value, context) {
  18295. return String(value);
  18296. },
  18297. /**
  18298. * Convert from any data into the target type.
  18299. * @param {*} value The value to convert from
  18300. * @return {*} The converted value.
  18301. */
  18302. from: function(value) {
  18303. return value;
  18304. },
  18305. /**
  18306. * @method
  18307. * Returns the difference between the min and max value based on the given unit scale.
  18308. *
  18309. * @param {*} min The smaller value.
  18310. * @param {*} max The larger value.
  18311. * @param {*} unit The unit scale. Unit can be any type.
  18312. * @return {Number} The number of `unit`s between min and max. It is the minimum n that
  18313. * min + n * unit >= max.
  18314. */
  18315. diff: Ext.emptyFn,
  18316. /**
  18317. * @method
  18318. * Align value with step of units.
  18319. * For example, for the date segmenter, if the unit is "Month" and step is 3, the value will be
  18320. * aligned by seasons.
  18321. *
  18322. * @param {*} value The value to be aligned.
  18323. * @param {Number} step The step of units.
  18324. * @param {*} unit The unit.
  18325. * @return {*} Aligned value.
  18326. */
  18327. align: Ext.emptyFn,
  18328. /**
  18329. * @method
  18330. * Add `step` `unit`s to the value.
  18331. * @param {*} value The value to be added.
  18332. * @param {Number} step The step of units. Negative value are allowed.
  18333. * @param {*} unit The unit.
  18334. */
  18335. add: Ext.emptyFn,
  18336. /**
  18337. * @method
  18338. * Given a start point and estimated step size of a range, determine the preferred step size.
  18339. *
  18340. * @param {*} start The start point of range.
  18341. * @param {*} estStepSize The estimated step size.
  18342. * @return {Object} Return the step size by an object of step x unit.
  18343. * @return {Number} return.step The step count of units.
  18344. * @return {Number|Object} return.unit The unit.
  18345. */
  18346. preferredStep: Ext.emptyFn
  18347. });
  18348. /**
  18349. * @class Ext.chart.axis.segmenter.Names
  18350. * @extends Ext.chart.axis.segmenter.Segmenter
  18351. *
  18352. * Names data type. Names will be calculated as their indices in the methods in this class.
  18353. * The `preferredStep` always return `{ unit: 1, step: 1 }` to indicate "show every item".
  18354. *
  18355. */
  18356. Ext.define('Ext.chart.axis.segmenter.Names', {
  18357. extend: 'Ext.chart.axis.segmenter.Segmenter',
  18358. alias: 'segmenter.names',
  18359. renderer: function(value, context) {
  18360. return value;
  18361. },
  18362. diff: function(min, max, unit) {
  18363. return Math.floor(max - min);
  18364. },
  18365. align: function(value, step, unit) {
  18366. return Math.floor(value);
  18367. },
  18368. add: function(value, step, unit) {
  18369. return value + step;
  18370. },
  18371. preferredStep: function(min, estStepSize, minIdx, data) {
  18372. return {
  18373. unit: 1,
  18374. step: 1
  18375. };
  18376. }
  18377. });
  18378. /**
  18379. * @class Ext.chart.axis.segmenter.Numeric
  18380. * @extends Ext.chart.axis.segmenter.Segmenter
  18381. *
  18382. * Numeric data type.
  18383. */
  18384. Ext.define('Ext.chart.axis.segmenter.Numeric', {
  18385. extend: 'Ext.chart.axis.segmenter.Segmenter',
  18386. alias: 'segmenter.numeric',
  18387. isNumeric: true,
  18388. renderer: function(value, context) {
  18389. return value.toFixed(Math.max(0, context.majorTicks.unit.fixes));
  18390. },
  18391. diff: function(min, max, unit) {
  18392. return Math.floor((max - min) / unit.scale);
  18393. },
  18394. align: function(value, step, unit) {
  18395. var scaledStep = unit.scale * step;
  18396. return Math.floor(value / scaledStep) * scaledStep;
  18397. },
  18398. add: function(value, step, unit) {
  18399. return value + step * unit.scale;
  18400. },
  18401. preferredStep: function(min, estStepSize) {
  18402. // Getting an order of magnitude of the estStepSize with a common logarithm.
  18403. var order = Math.floor(Math.log(estStepSize) * Math.LOG10E),
  18404. scale = Math.pow(10, order);
  18405. estStepSize /= scale;
  18406. if (estStepSize < 2) {
  18407. estStepSize = 2;
  18408. } else if (estStepSize < 5) {
  18409. estStepSize = 5;
  18410. } else if (estStepSize < 10) {
  18411. estStepSize = 10;
  18412. order++;
  18413. }
  18414. return {
  18415. unit: {
  18416. // When passed estStepSize is less than 1, its order of magnitude
  18417. // is equal to -number_of_leading_zeros in the estStepSize.
  18418. fixes: -order,
  18419. // Number of fractional digits.
  18420. scale: scale
  18421. },
  18422. step: estStepSize
  18423. };
  18424. },
  18425. leadingZeros: function(n) {
  18426. // For example:
  18427. // leadingZeros(0.2) is 1,
  18428. // leadingZeros(-0.01) is 2.
  18429. return -Math.floor(Ext.Number.log10(Math.abs(n)));
  18430. },
  18431. /**
  18432. * Wraps the provided estimated step size of a range without altering it into a step size
  18433. * object.
  18434. *
  18435. * @param {*} min The start point of range.
  18436. * @param {*} estStepSize The estimated step size.
  18437. * @return {Object} Return the step size by an object of step x unit.
  18438. * @return {Number} return.step The step count of units.
  18439. * @return {Object} return.unit The unit.
  18440. */
  18441. exactStep: function(min, estStepSize) {
  18442. var stepZeros = this.leadingZeros(estStepSize),
  18443. scale = Math.pow(10, stepZeros);
  18444. return {
  18445. unit: {
  18446. // add one decimal point if estStepSize is not a multiple of scale
  18447. fixes: stepZeros + (estStepSize % scale === 0 ? 0 : 1),
  18448. // Swap scale & step, if the estStepSize < 1,
  18449. // or 'diff' method will give us rounding errors.
  18450. scale: estStepSize < 1 ? estStepSize : 1
  18451. },
  18452. step: estStepSize < 1 ? 1 : estStepSize
  18453. };
  18454. },
  18455. adjustByMajorUnit: function(step, scale, range) {
  18456. var min = range[0],
  18457. max = range[1],
  18458. increment = step * scale,
  18459. remainder, multiplier;
  18460. multiplier = Math.max(1 / (min || 1), 1 / (increment || 1));
  18461. multiplier = multiplier > 1 ? multiplier : 1;
  18462. remainder = ((min * multiplier) % (increment * multiplier)) / multiplier;
  18463. if (remainder !== 0) {
  18464. range[0] = min - remainder + (min < 0 ? -increment : 0);
  18465. }
  18466. multiplier = Math.max(1 / (max || 1), 1 / (increment || 1));
  18467. multiplier = multiplier > 1 ? multiplier : 1;
  18468. remainder = ((max * multiplier) % (increment * multiplier)) / multiplier;
  18469. if (remainder !== 0) {
  18470. range[1] = max - remainder + (max > 0 ? increment : 0);
  18471. }
  18472. }
  18473. });
  18474. /**
  18475. * @class Ext.chart.axis.segmenter.Time
  18476. * @extends Ext.chart.axis.segmenter.Segmenter
  18477. *
  18478. * Time data type.
  18479. */
  18480. Ext.define('Ext.chart.axis.segmenter.Time', {
  18481. extend: 'Ext.chart.axis.segmenter.Segmenter',
  18482. alias: 'segmenter.time',
  18483. config: {
  18484. /**
  18485. * @cfg {Object} step
  18486. * @cfg {String} step.unit The unit of the step (Ext.Date.DAY, Ext.Date.MONTH, etc).
  18487. * @cfg {Number} step.step The number of units for the step (1, 2, etc).
  18488. * If specified, will override the result of {@link #preferredStep}.
  18489. * For example:
  18490. *
  18491. * step: {
  18492. * unit: Ext.Date.HOUR,
  18493. * step: 1
  18494. * }
  18495. */
  18496. step: null
  18497. },
  18498. renderer: function(value, context) {
  18499. var ExtDate = Ext.Date;
  18500. switch (context.majorTicks.unit) {
  18501. case 'y':
  18502. return ExtDate.format(value, 'Y');
  18503. case 'mo':
  18504. return ExtDate.format(value, 'Y-m');
  18505. case 'd':
  18506. return ExtDate.format(value, 'Y-m-d');
  18507. }
  18508. return ExtDate.format(value, 'Y-m-d\nH:i:s');
  18509. },
  18510. from: function(value) {
  18511. return new Date(value);
  18512. },
  18513. diff: function(min, max, unit) {
  18514. if (isFinite(min)) {
  18515. min = new Date(min);
  18516. }
  18517. if (isFinite(max)) {
  18518. max = new Date(max);
  18519. }
  18520. return Ext.Date.diff(min, max, unit);
  18521. },
  18522. updateStep: function() {
  18523. var axis = this.getAxis();
  18524. if (axis && !this.isConfiguring) {
  18525. axis.performLayout();
  18526. }
  18527. },
  18528. align: function(date, step, unit) {
  18529. if (unit === 'd' && step >= 7) {
  18530. date = Ext.Date.align(date, 'd', step);
  18531. date.setDate(date.getDate() - date.getDay() + 1);
  18532. return date;
  18533. } else {
  18534. return Ext.Date.align(date, unit, step);
  18535. }
  18536. },
  18537. add: function(value, step, unit) {
  18538. return Ext.Date.add(new Date(value), unit, step);
  18539. },
  18540. timeBuckets: [
  18541. {
  18542. unit: Ext.Date.YEAR,
  18543. steps: [
  18544. 1,
  18545. 2,
  18546. 5,
  18547. 10,
  18548. 20,
  18549. 50,
  18550. 100,
  18551. 200,
  18552. 500
  18553. ]
  18554. },
  18555. {
  18556. unit: Ext.Date.MONTH,
  18557. steps: [
  18558. 1,
  18559. 3,
  18560. 6
  18561. ]
  18562. },
  18563. {
  18564. unit: Ext.Date.DAY,
  18565. steps: [
  18566. 1,
  18567. 7,
  18568. 14
  18569. ]
  18570. },
  18571. {
  18572. unit: Ext.Date.HOUR,
  18573. steps: [
  18574. 1,
  18575. 6,
  18576. 12
  18577. ]
  18578. },
  18579. {
  18580. unit: Ext.Date.MINUTE,
  18581. steps: [
  18582. 1,
  18583. 5,
  18584. 15,
  18585. 30
  18586. ]
  18587. },
  18588. {
  18589. unit: Ext.Date.SECOND,
  18590. steps: [
  18591. 1,
  18592. 5,
  18593. 15,
  18594. 30
  18595. ]
  18596. },
  18597. {
  18598. unit: Ext.Date.MILLI,
  18599. steps: [
  18600. 1,
  18601. 2,
  18602. 5,
  18603. 10,
  18604. 20,
  18605. 50,
  18606. 100,
  18607. 200,
  18608. 500
  18609. ]
  18610. }
  18611. ],
  18612. /**
  18613. * @private
  18614. * Takes a time interval and figures out what is the smallest nice number of which
  18615. * units (years, months, days, etc.) that can fully encompass that interval.
  18616. * @param {Date} min
  18617. * @param {Date} max
  18618. * @return {Object}
  18619. * @return {String} return.unit The unit.
  18620. * @return {Number} return.step The number of units.
  18621. */
  18622. getTimeBucket: function(min, max) {
  18623. var buckets = this.timeBuckets,
  18624. unit, unitCount, steps, step, result, i, j;
  18625. for (i = 0; i < buckets.length; i++) {
  18626. unit = buckets[i].unit;
  18627. unitCount = this.diff(min, max, unit);
  18628. if (unitCount > 0) {
  18629. steps = buckets[i].steps;
  18630. for (j = 0; j < steps.length; j++) {
  18631. step = steps[j];
  18632. if (unitCount <= step) {
  18633. break;
  18634. }
  18635. }
  18636. result = {
  18637. unit: unit,
  18638. step: step
  18639. };
  18640. break;
  18641. }
  18642. }
  18643. // If the interval is smaller then one millisecond ...
  18644. if (!result) {
  18645. // ... we can't go smaller than one millisecond.
  18646. result = {
  18647. unit: Ext.Date.MILLI,
  18648. step: 1
  18649. };
  18650. }
  18651. return result;
  18652. },
  18653. preferredStep: function(min, estStepSize) {
  18654. var step = this.getStep();
  18655. return step ? step : this.getTimeBucket(new Date(+min), new Date(+min + Math.ceil(estStepSize)));
  18656. }
  18657. });
  18658. /**
  18659. * @abstract
  18660. * @class Ext.chart.axis.layout.Layout
  18661. *
  18662. * Interface used by Axis to process its data into a meaningful layout.
  18663. */
  18664. Ext.define('Ext.chart.axis.layout.Layout', {
  18665. mixins: {
  18666. observable: 'Ext.mixin.Observable'
  18667. },
  18668. config: {
  18669. /**
  18670. * @cfg {Ext.chart.axis.Axis} axis The axis that the Layout is bound.
  18671. */
  18672. axis: null
  18673. },
  18674. constructor: function(config) {
  18675. this.mixins.observable.constructor.call(this, config);
  18676. },
  18677. /**
  18678. * Processes the data of the series bound to the axis.
  18679. * @param {Ext.chart.series.Series} series The bound series.
  18680. */
  18681. processData: function(series) {
  18682. var me = this,
  18683. axis = me.getAxis(),
  18684. direction = axis.getDirection(),
  18685. boundSeries = axis.boundSeries,
  18686. i, ln;
  18687. if (series) {
  18688. series['coordinate' + direction]();
  18689. } else {
  18690. for (i = 0 , ln = boundSeries.length; i < ln; i++) {
  18691. boundSeries[i]['coordinate' + direction]();
  18692. }
  18693. }
  18694. },
  18695. /**
  18696. * Calculates the position of major ticks for the axis.
  18697. * @param {Object} context
  18698. */
  18699. calculateMajorTicks: function(context) {
  18700. var me = this,
  18701. attr = context.attr,
  18702. range = attr.max - attr.min,
  18703. zoom = range / Math.max(1, attr.length) * (attr.visibleMax - attr.visibleMin),
  18704. viewMin = attr.min + range * attr.visibleMin,
  18705. viewMax = attr.min + range * attr.visibleMax,
  18706. estStepSize = attr.estStepSize * zoom,
  18707. majorTicks = me.snapEnds(context, attr.min, attr.max, estStepSize);
  18708. if (majorTicks) {
  18709. me.trimByRange(context, majorTicks, viewMin, viewMax);
  18710. context.majorTicks = majorTicks;
  18711. }
  18712. },
  18713. /**
  18714. * Calculates the position of sub ticks for the axis.
  18715. * @param {Object} context
  18716. */
  18717. calculateMinorTicks: function(context) {
  18718. if (this.snapMinorEnds) {
  18719. context.minorTicks = this.snapMinorEnds(context);
  18720. }
  18721. },
  18722. /**
  18723. * Calculates the position of tick marks for the axis.
  18724. * @param {Object} context
  18725. * @return {*}
  18726. */
  18727. calculateLayout: function(context) {
  18728. var me = this,
  18729. attr = context.attr;
  18730. if (attr.length === 0) {
  18731. return null;
  18732. }
  18733. if (attr.majorTicks) {
  18734. me.calculateMajorTicks(context);
  18735. if (attr.minorTicks) {
  18736. me.calculateMinorTicks(context);
  18737. }
  18738. }
  18739. },
  18740. /**
  18741. * @method
  18742. * Snaps the data bound to the axis to meaningful tick marks.
  18743. * @param {Object} context
  18744. * @param {Number} min
  18745. * @param {Number} max
  18746. * @param {Number} estStepSize
  18747. */
  18748. snapEnds: Ext.emptyFn,
  18749. /**
  18750. * Trims the layout of the axis by the defined minimum and maximum.
  18751. * @param {Object} context
  18752. * @param {Object} ticks Ticks object (e.g. major ticks) to be modified.
  18753. * @param {Number} trimMin
  18754. * @param {Number} trimMax
  18755. */
  18756. trimByRange: function(context, ticks, trimMin, trimMax) {
  18757. var segmenter = context.segmenter,
  18758. unit = ticks.unit,
  18759. beginIdx = segmenter.diff(ticks.from, trimMin, unit),
  18760. endIdx = segmenter.diff(ticks.from, trimMax, unit),
  18761. begin = Math.max(0, Math.ceil(beginIdx / ticks.step)),
  18762. end = Math.min(ticks.steps, Math.floor(endIdx / ticks.step));
  18763. if (end < ticks.steps) {
  18764. ticks.to = segmenter.add(ticks.from, end * ticks.step, unit);
  18765. }
  18766. if (ticks.max > trimMax) {
  18767. ticks.max = ticks.to;
  18768. }
  18769. if (ticks.from < trimMin) {
  18770. ticks.from = segmenter.add(ticks.from, begin * ticks.step, unit);
  18771. while (ticks.from < trimMin) {
  18772. begin++;
  18773. ticks.from = segmenter.add(ticks.from, ticks.step, unit);
  18774. }
  18775. }
  18776. if (ticks.min < trimMin) {
  18777. ticks.min = ticks.from;
  18778. }
  18779. ticks.steps = end - begin;
  18780. }
  18781. });
  18782. /**
  18783. * @class Ext.chart.axis.layout.Discrete
  18784. * @extends Ext.chart.axis.layout.Layout
  18785. *
  18786. * Simple processor for data that cannot be interpolated.
  18787. */
  18788. Ext.define('Ext.chart.axis.layout.Discrete', {
  18789. extend: 'Ext.chart.axis.layout.Layout',
  18790. alias: 'axisLayout.discrete',
  18791. isDiscrete: true,
  18792. processData: function() {
  18793. var me = this,
  18794. axis = me.getAxis(),
  18795. seriesList = axis.boundSeries,
  18796. direction = axis.getDirection(),
  18797. i, ln, series;
  18798. me.labels = [];
  18799. me.labelMap = {};
  18800. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  18801. series = seriesList[i];
  18802. if (series['get' + direction + 'Axis']() === axis) {
  18803. series['coordinate' + direction]();
  18804. }
  18805. }
  18806. // About the labels on Category axes (aka. axes with a Discrete layout)...
  18807. //
  18808. // When the data set from the store changes, series.processData() is called, which does
  18809. // its thing at the series level and then calls series.updateLabelData() to update
  18810. // the labels in the sprites that belong to the series. At the same time,
  18811. // series.processData() calls axis.processData(), which also does its thing but at the axis
  18812. // level, and also needs to update the labels for the sprite(s) that belong to the axis.
  18813. // This is not that simple, however. So how are the axis labels rendered?
  18814. // First, axis.sprite.Axis.render() calls renderLabels() which obtains the majorTicks
  18815. // from the axis.layout and iterate() through them. The majorTicks are an object returned
  18816. // by snapEnds() below which provides a getLabel() function that returns the label
  18817. // from the axis.layoutContext.data array. So now the question is: how are the labels
  18818. // transferred from the axis.layout to the axis.layoutContext?
  18819. // The easy response is: it's in calculateLayout() below. The issue is to call
  18820. // calculateLayout() because it takes in an axis.layoutContext that can only be created
  18821. // in axis.sprite.Axis.layoutUpdater(), which is a private "updater" function that is
  18822. // called by all the sprite's "triggers". Of course, we don't want to call layoutUpdater()
  18823. // directly from here, so instead we update the sprite's data attribute, which sets
  18824. // the trigger which calls layoutUpdater() which calls calculateLayout() etc...
  18825. // Note that the sprite's data attribute could be set to any value and it would still result
  18826. // in the trigger we need. For consistency, however, it is set to the labels.
  18827. axis.getSprites()[0].setAttributes({
  18828. data: me.labels
  18829. });
  18830. me.fireEvent('datachange', me.labels);
  18831. },
  18832. /**
  18833. * @method calculateLayout
  18834. * @inheritdoc
  18835. */
  18836. calculateLayout: function(context) {
  18837. context.data = this.labels;
  18838. this.callParent([
  18839. context
  18840. ]);
  18841. },
  18842. /**
  18843. * @method calculateMajorTicks
  18844. * @inheritdoc
  18845. */
  18846. calculateMajorTicks: function(context) {
  18847. var me = this,
  18848. attr = context.attr,
  18849. data = context.data,
  18850. range = attr.max - attr.min,
  18851. viewMin = attr.min + range * attr.visibleMin,
  18852. viewMax = attr.min + range * attr.visibleMax,
  18853. out;
  18854. out = me.snapEnds(context, Math.max(0, attr.min), Math.min(attr.max, data.length - 1), 1);
  18855. if (out) {
  18856. me.trimByRange(context, out, viewMin, viewMax);
  18857. context.majorTicks = out;
  18858. }
  18859. },
  18860. /**
  18861. * @method snapEnds
  18862. * @inheritdoc
  18863. */
  18864. snapEnds: function(context, min, max, estStepSize) {
  18865. var data = context.data,
  18866. steps;
  18867. estStepSize = Math.ceil(estStepSize);
  18868. steps = Math.floor((max - min) / estStepSize);
  18869. return {
  18870. min: min,
  18871. max: max,
  18872. from: min,
  18873. to: steps * estStepSize + min,
  18874. step: estStepSize,
  18875. steps: steps,
  18876. unit: 1,
  18877. getLabel: function(currentStep) {
  18878. return data[this.from + this.step * currentStep];
  18879. },
  18880. get: function(currentStep) {
  18881. return this.from + this.step * currentStep;
  18882. }
  18883. };
  18884. },
  18885. /**
  18886. * @method trimByRange
  18887. * @inheritdoc
  18888. */
  18889. trimByRange: function(context, out, trimMin, trimMax) {
  18890. var unit = out.unit,
  18891. beginIdx = Math.ceil((trimMin - out.from) / unit) * unit,
  18892. endIdx = Math.floor((trimMax - out.from) / unit) * unit,
  18893. begin = Math.max(0, Math.ceil(beginIdx / out.step)),
  18894. end = Math.min(out.steps, Math.floor(endIdx / out.step));
  18895. if (end < out.steps) {
  18896. out.to = end;
  18897. }
  18898. if (out.max > trimMax) {
  18899. out.max = out.to;
  18900. }
  18901. if (out.from < trimMin && out.step > 0) {
  18902. out.from = out.from + begin * out.step * unit;
  18903. while (out.from < trimMin) {
  18904. begin++;
  18905. out.from += out.step * unit;
  18906. }
  18907. }
  18908. if (out.min < trimMin) {
  18909. out.min = out.from;
  18910. }
  18911. out.steps = end - begin;
  18912. },
  18913. getCoordFor: function(value, field, idx, items) {
  18914. this.labels.push(value);
  18915. return this.labels.length - 1;
  18916. }
  18917. });
  18918. /**
  18919. * Discrete layout that combines duplicate data points only if they have the same index.
  18920. * For example:
  18921. *
  18922. * @example
  18923. * Ext.create({
  18924. * xtype: 'cartesian',
  18925. * title: 'Weight vs Calories',
  18926. *
  18927. * renderTo: document.body,
  18928. * width: 400,
  18929. * height: 400,
  18930. *
  18931. * store: {
  18932. * fields: ['month', 'weight', 'calories'],
  18933. * data: [
  18934. * {
  18935. * month: 'Jan',
  18936. * weight: 185,
  18937. * calories: 2650
  18938. * },
  18939. * {
  18940. * month: 'Jan',
  18941. * weight: 188,
  18942. * calories: 2800
  18943. * },
  18944. * {
  18945. * month: 'Feb',
  18946. * weight: 188,
  18947. * calories: 2800
  18948. * },
  18949. * {
  18950. * month: 'Mar',
  18951. * weight: 191,
  18952. * calories: 2800
  18953. * },
  18954. * {
  18955. * month: 'Apr',
  18956. * weight: 189,
  18957. * calories: 1500
  18958. * },
  18959. * {
  18960. * month: 'May',
  18961. * weight: 187,
  18962. * calories: 1350
  18963. * }
  18964. * ]
  18965. * },
  18966. *
  18967. * axes: [{
  18968. * type: 'numeric',
  18969. * position: 'left',
  18970. * fields: ['weight'],
  18971. * minimum: 140
  18972. * }, {
  18973. * type: 'numeric',
  18974. * position: 'right',
  18975. * fields: ['calories'],
  18976. * minimum: 500,
  18977. * maximum: 3500
  18978. * }, {
  18979. * type: 'category',
  18980. * grid: true,
  18981. * layout: 'combineByIndex',
  18982. * fields: 'month',
  18983. * position: 'bottom',
  18984. * label: {
  18985. * rotate: {
  18986. * degrees: -45
  18987. * }
  18988. * }
  18989. * }],
  18990. *
  18991. * series: [{
  18992. * type: 'line',
  18993. * title: 'Weight',
  18994. * xField: 'month',
  18995. * yField: 'weight',
  18996. * smooth: true,
  18997. * marker: true
  18998. * }, {
  18999. * type: 'line',
  19000. * title: 'Calories',
  19001. * xField: 'month',
  19002. * yField: 'calories',
  19003. * smooth: true,
  19004. * marker: true
  19005. * }],
  19006. *
  19007. * legend: {
  19008. * docked: 'bottom'
  19009. * }
  19010. *
  19011. * });
  19012. *
  19013. * @since 6.5.0
  19014. */
  19015. Ext.define('Ext.chart.axis.layout.CombineByIndex', {
  19016. extend: 'Ext.chart.axis.layout.Discrete',
  19017. alias: 'axisLayout.combineByIndex',
  19018. getCoordFor: function(value, field, idx, items) {
  19019. var labels = this.labels,
  19020. result = idx;
  19021. if (labels[idx] !== value) {
  19022. result = labels.push(value) - 1;
  19023. }
  19024. return result;
  19025. }
  19026. });
  19027. /**
  19028. * Discrete processor that combines duplicate data points.
  19029. */
  19030. Ext.define('Ext.chart.axis.layout.CombineDuplicate', {
  19031. extend: 'Ext.chart.axis.layout.Discrete',
  19032. alias: 'axisLayout.combineDuplicate',
  19033. getCoordFor: function(value, field, idx, items) {
  19034. var result;
  19035. if (!(value in this.labelMap)) {
  19036. result = this.labelMap[value] = this.labels.length;
  19037. this.labels.push(value);
  19038. return result;
  19039. }
  19040. return this.labelMap[value];
  19041. }
  19042. });
  19043. /**
  19044. * @class Ext.chart.axis.layout.Continuous
  19045. * @extends Ext.chart.axis.layout.Layout
  19046. *
  19047. * Processor for axis data that can be interpolated.
  19048. */
  19049. Ext.define('Ext.chart.axis.layout.Continuous', {
  19050. extend: 'Ext.chart.axis.layout.Layout',
  19051. alias: 'axisLayout.continuous',
  19052. isContinuous: true,
  19053. config: {
  19054. adjustMinimumByMajorUnit: false,
  19055. adjustMaximumByMajorUnit: false
  19056. },
  19057. getCoordFor: function(value, field, idx, items) {
  19058. return +value;
  19059. },
  19060. /**
  19061. * @method snapEnds
  19062. * @inheritdoc
  19063. */
  19064. snapEnds: function(context, min, max, estStepSize) {
  19065. var segmenter = context.segmenter,
  19066. axis = this.getAxis(),
  19067. noAnimation = !axis.spriteAnimationCount,
  19068. majorTickSteps = axis.getMajorTickSteps(),
  19069. // if specific number of steps requested and the segmenter supports such segmentation
  19070. bucket = majorTickSteps && segmenter.exactStep ? segmenter.exactStep(min, (max - min) / majorTickSteps) : segmenter.preferredStep(min, estStepSize),
  19071. unit = bucket.unit,
  19072. step = bucket.step,
  19073. diffSteps = segmenter.diff(min, max, unit),
  19074. steps = (majorTickSteps || diffSteps) + 1,
  19075. from;
  19076. // If 'majorTickSteps' config of the axis is set (is not 0), it means that
  19077. // we want to split the range at that number of equal intervals (segmenter.exactStep),
  19078. // and don't care if the resulting ticks are at nice round values or not.
  19079. // So 'from' (aligned) step is equal to 'min' (unaligned step).
  19080. // And 'to' is equal to 'max'.
  19081. //
  19082. // Another case where this is possible, is when the range between 'min' and
  19083. // 'max' can be represented by n steps, where n is an integer.
  19084. // For example, if the data values are [7, 17, 27, 37, 47], the data step is 10
  19085. // and, if the calculated tick step (segmenter.preferredStep) is also 10,
  19086. // there is no need to segmenter.align the 'min' to 0, so that the ticks are at
  19087. // [0, 10, 20, 30, 40, 50], because the data points are already perfectly
  19088. // spaced, so the ticks can be exactly at the data points without runing the
  19089. // aesthetics.
  19090. //
  19091. // The 'noAnimation' check is required to prevent EXTJS-25413 from happening.
  19092. // The segmentation described above is ideal for a static chart, but produces
  19093. // unwanted effects during animation.
  19094. if (majorTickSteps || (noAnimation && +segmenter.add(min, diffSteps, unit) === max)) {
  19095. from = min;
  19096. } else {
  19097. from = segmenter.align(min, step, unit);
  19098. }
  19099. return {
  19100. // min/max are NOT aligned to step
  19101. min: segmenter.from(min),
  19102. max: segmenter.from(max),
  19103. // from/to are aligned to step
  19104. from: from,
  19105. to: segmenter.add(from, steps, unit),
  19106. step: step,
  19107. steps: steps,
  19108. unit: unit,
  19109. get: function(currentStep) {
  19110. return segmenter.add(this.from, this.step * currentStep, this.unit);
  19111. }
  19112. };
  19113. },
  19114. snapMinorEnds: function(context) {
  19115. var majorTicks = context.majorTicks,
  19116. minorTickSteps = this.getAxis().getMinorTickSteps(),
  19117. segmenter = context.segmenter,
  19118. min = majorTicks.min,
  19119. max = majorTicks.max,
  19120. from = majorTicks.from,
  19121. unit = majorTicks.unit,
  19122. step = majorTicks.step / minorTickSteps,
  19123. scaledStep = step * unit.scale,
  19124. fromMargin = from - min,
  19125. offset = Math.floor(fromMargin / scaledStep),
  19126. extraSteps = offset + Math.floor((max - majorTicks.to) / scaledStep) + 1,
  19127. steps = majorTicks.steps * minorTickSteps + extraSteps;
  19128. return {
  19129. min: min,
  19130. max: max,
  19131. from: min + fromMargin % scaledStep,
  19132. to: segmenter.add(from, steps * step, unit),
  19133. step: step,
  19134. steps: steps,
  19135. unit: unit,
  19136. get: function(current) {
  19137. // don't render minor tick in major tick position
  19138. return (current % minorTickSteps + offset + 1 !== 0) ? segmenter.add(this.from, this.step * current, unit) : null;
  19139. }
  19140. };
  19141. }
  19142. });
  19143. /**
  19144. * @class Ext.chart.axis.Axis
  19145. *
  19146. * Defines axis for charts.
  19147. *
  19148. * Using the current model, the type of axis can be easily extended. By default, Sencha Charts
  19149. * provide three different types of axis:
  19150. *
  19151. * * **numeric** - the data attached to this axis is numeric and continuous.
  19152. * * **time** - the data attached to this axis is (or gets converted into) a date/time value;
  19153. * it is continuous.
  19154. * * **category** - the data attached to this axis belongs to a finite set. The data points
  19155. * are evenly placed along the axis.
  19156. *
  19157. * The behavior of an axis can be easily changed by setting different types of axis layout and
  19158. * axis segmenter to the axis.
  19159. *
  19160. * Axis layout defines how the data points are placed. Using continuous layout, the data points
  19161. * will be distributed by the numeric value. Using discrete layout the data points will be spaced
  19162. * evenly. Furthermore, if you want to combine the data points with the duplicate values in a
  19163. * discrete layout, you should use combineDuplicate layout.
  19164. *
  19165. * Segmenter defines the way to segment data range. For example, if you have a Date-type data range
  19166. * from Jan 1, 1997 to Jan 1, 2017, the segmenter will segement the data range into years, months or
  19167. * days based on the current zooming level.
  19168. *
  19169. * It is possible to write custom axis layouts and segmenters to extends this behavior by simply
  19170. * implementing interfaces {@link Ext.chart.axis.layout.Layout} and
  19171. * {@link Ext.chart.axis.segmenter.Segmenter}.
  19172. *
  19173. * Here's an example for the axes part of a chart definition:
  19174. * An example of axis for a series (in this case for an area chart that has multiple layers of
  19175. * yFields) could be:
  19176. *
  19177. * axes: [{
  19178. * type: 'numeric',
  19179. * position: 'left',
  19180. * title: 'Number of Hits',
  19181. * grid: {
  19182. * odd: {
  19183. * opacity: 1,
  19184. * fill: '#ddd',
  19185. * stroke: '#bbb',
  19186. * lineWidth: 1
  19187. * }
  19188. * },
  19189. * minimum: 0
  19190. * }, {
  19191. * type: 'category',
  19192. * position: 'bottom',
  19193. * title: 'Month of the Year',
  19194. * grid: true,
  19195. * label: {
  19196. * rotate: {
  19197. * degrees: 315
  19198. * }
  19199. * }
  19200. * }]
  19201. *
  19202. * In this case we use a `numeric` axis for displaying the values of the Area series and a
  19203. * `category` axis for displaying the names of the store elements. The numeric axis is placed
  19204. * on the left of the screen, while the category axis is placed at the bottom of the chart.
  19205. * Both the category and numeric axes have `grid` set, which means that horizontal and vertical
  19206. * lines will cover the chart background. In the category axis the labels will be rotated so
  19207. * they can fit the space better.
  19208. */
  19209. Ext.define('Ext.chart.axis.Axis', {
  19210. xtype: 'axis',
  19211. mixins: {
  19212. observable: 'Ext.mixin.Observable'
  19213. },
  19214. requires: [
  19215. 'Ext.chart.axis.sprite.Axis',
  19216. 'Ext.chart.axis.segmenter.*',
  19217. 'Ext.chart.axis.layout.*',
  19218. 'Ext.chart.Util'
  19219. ],
  19220. isAxis: true,
  19221. /**
  19222. * @event rangechange
  19223. * Fires when the {@link Ext.chart.axis.Axis#range range} of the axis changes.
  19224. * @param {Ext.chart.axis.Axis} axis
  19225. * @param {Array} range
  19226. * @param {Array} oldRange
  19227. */
  19228. /**
  19229. * @event visiblerangechange
  19230. * Fires when the {@link #visibleRange} of the axis changes.
  19231. * @param {Ext.chart.axis.Axis} axis
  19232. * @param {Array} visibleRange
  19233. */
  19234. /**
  19235. * @cfg {String} id
  19236. * The **unique** id of this axis instance.
  19237. */
  19238. config: {
  19239. /**
  19240. * @cfg {String} position
  19241. * Where to set the axis. Available options are `left`, `bottom`, `right`, `top`, `radial`,
  19242. * and `angular`.
  19243. */
  19244. position: 'bottom',
  19245. /**
  19246. * @cfg {Array} fields
  19247. * An array containing the names of the record fields which should be mapped along the axis.
  19248. * This is optional if the binding between series and fields is clear.
  19249. */
  19250. fields: [],
  19251. /**
  19252. * @cfg {Object} label
  19253. *
  19254. * The label configuration object for the Axis. This object may include style attributes
  19255. * like `spacing`, `padding`, `font` that receives a string or number and
  19256. * returns a new string with the modified values.
  19257. *
  19258. * For more supported values, see the configurations for {@link Ext.chart.sprite.Label}.
  19259. */
  19260. label: undefined,
  19261. /**
  19262. * @cfg {Object} grid
  19263. * The grid configuration object for the Axis style. Can contain `stroke` or `fill`
  19264. * attributes. Also may contain an `odd` or `even` property in which you only style things
  19265. * on odd or even rows. For example:
  19266. *
  19267. *
  19268. * grid {
  19269. * odd: {
  19270. * stroke: '#555'
  19271. * },
  19272. * even: {
  19273. * stroke: '#ccc'
  19274. * }
  19275. * }
  19276. */
  19277. grid: false,
  19278. /**
  19279. * @cfg {Array|Object} limits
  19280. * The limit lines configuration for the axis.
  19281. * For example:
  19282. *
  19283. * limits: [{
  19284. * value: 50,
  19285. * line: {
  19286. * strokeStyle: 'red',
  19287. * lineDash: [6, 3],
  19288. * title: {
  19289. * text: 'Monthly minimum',
  19290. * fontSize: 14
  19291. * }
  19292. * }
  19293. * }]
  19294. */
  19295. limits: null,
  19296. /**
  19297. * @cfg {Function} renderer Allows to change the text shown next to the tick.
  19298. * @param {Ext.chart.axis.Axis} axis The axis.
  19299. * @param {String/Number} label The label.
  19300. * @param {Object} layoutContext The object that holds calculated positions
  19301. * of axis' ticks based on current layout, segmenter, axis length and configuration.
  19302. * @param {String/Number/null} lastLabel The last label (if any).
  19303. * @return {String} The label to display.
  19304. * @controllable
  19305. */
  19306. renderer: null,
  19307. /**
  19308. * @protected
  19309. * @cfg {Ext.chart.AbstractChart} chart The Chart that the Axis is bound.
  19310. */
  19311. chart: null,
  19312. /**
  19313. * @cfg {Object} style
  19314. * The style for the axis line and ticks.
  19315. * Refer to the {@link Ext.chart.axis.sprite.Axis}
  19316. */
  19317. style: null,
  19318. /**
  19319. * @cfg {Number} margin
  19320. * The margin of the axis. Used to control the spacing between axes in charts with multiple
  19321. * axes. Unlike CSS where the margin is added on all 4 sides of an element, the `margin`
  19322. * is the total space that is added horizontally for a vertical axis, vertically
  19323. * for a horizontal axis, and radially for an angular axis.
  19324. */
  19325. margin: 0,
  19326. /**
  19327. * @cfg {Number} [titleMargin=4]
  19328. * The margin around the axis title. Unlike CSS where the margin is added on all 4
  19329. * sides of an element, the `titleMargin` is the total space that is added horizontally
  19330. * for a vertical title and vertically for an horizontal title, with half the `titleMargin`
  19331. * being added on either side.
  19332. */
  19333. titleMargin: 4,
  19334. /**
  19335. * @cfg {Object} background
  19336. * The background config for the axis surface.
  19337. */
  19338. background: null,
  19339. /**
  19340. * @cfg {Number} minimum
  19341. * The minimum value drawn by the axis. If not set explicitly, the axis
  19342. * minimum will be calculated automatically.
  19343. */
  19344. minimum: NaN,
  19345. /**
  19346. * @cfg {Number} maximum
  19347. * The maximum value drawn by the axis. If not set explicitly, the axis
  19348. * maximum will be calculated automatically.
  19349. */
  19350. maximum: NaN,
  19351. /**
  19352. * @cfg {Boolean} reconcileRange
  19353. * If 'true' the range of the axis will be a union of ranges
  19354. * of all the axes with the same direction. Defaults to 'false'.
  19355. */
  19356. reconcileRange: false,
  19357. /**
  19358. * @cfg {Number} minZoom
  19359. * The minimum zooming level for axis.
  19360. */
  19361. minZoom: 1,
  19362. /**
  19363. * @cfg {Number} maxZoom
  19364. * The maximum zooming level for axis.
  19365. */
  19366. maxZoom: 10000,
  19367. /**
  19368. * @cfg {Object|Ext.chart.axis.layout.Layout} layout
  19369. * The axis layout config. See {@link Ext.chart.axis.layout.Layout}
  19370. */
  19371. layout: 'continuous',
  19372. /**
  19373. * @cfg {Object|Ext.chart.axis.segmenter.Segmenter} segmenter
  19374. * The segmenter config. See {@link Ext.chart.axis.segmenter.Segmenter}
  19375. */
  19376. segmenter: 'numeric',
  19377. /**
  19378. * @cfg {Boolean} hidden
  19379. * Indicate whether to hide the axis.
  19380. * If the axis is hidden, one of the axis line, ticks, labels or the title will be shown and
  19381. * no margin will be taken.
  19382. * The coordination mechanism works fine no matter if the axis is hidden.
  19383. */
  19384. hidden: false,
  19385. /**
  19386. * @cfg {Number} [majorTickSteps=0]
  19387. * Forces the number of major ticks to the specified value.
  19388. * Both {@link #minimum} and {@link #maximum} should be specified.
  19389. */
  19390. majorTickSteps: 0,
  19391. /**
  19392. * @cfg {Number} [minorTickSteps=0]
  19393. * The number of small ticks between two major ticks.
  19394. */
  19395. minorTickSteps: 0,
  19396. /**
  19397. * @cfg {Boolean} adjustByMajorUnit
  19398. * Whether to make the auto-calculated minimum and maximum of the axis
  19399. * a multiple of the interval between the major ticks of the axis.
  19400. * If {@link #majorTickSteps}, {@link #minimum} or {@link #maximum}
  19401. * configs have been set, this config will be ignored.
  19402. * Defaults to 'true'.
  19403. * Note: this config has no effect if the axis is {@link #hidden}.
  19404. */
  19405. adjustByMajorUnit: true,
  19406. /**
  19407. * @cfg {String|Object} title
  19408. * The title for the Axis.
  19409. * If given a String, the 'text' attribute of the title sprite will be set,
  19410. * otherwise the style will be set.
  19411. */
  19412. title: null,
  19413. /**
  19414. * @private
  19415. * @cfg {Number} [expandRangeBy=0]
  19416. */
  19417. expandRangeBy: 0,
  19418. /**
  19419. * @private
  19420. * @cfg {Number} length
  19421. * Length of the axis position. Equals to the size of inner rect on the docking side
  19422. * of this axis.
  19423. * WARNING: Meant to be set automatically by chart. Do not set it manually.
  19424. */
  19425. length: 0,
  19426. /**
  19427. * @private
  19428. * @cfg {Array} center
  19429. * Center of the polar axis.
  19430. * WARNING: Meant to be set automatically by chart. Do not set it manually.
  19431. */
  19432. center: null,
  19433. /**
  19434. * @private
  19435. * @cfg {Number} radius
  19436. * Radius of the polar axis.
  19437. * WARNING: Meant to be set automatically by chart. Do not set it manually.
  19438. */
  19439. radius: null,
  19440. /**
  19441. * @private
  19442. */
  19443. totalAngle: Math.PI,
  19444. /**
  19445. * @private
  19446. * @cfg {Number} rotation
  19447. * Rotation of the polar axis in radians.
  19448. * WARNING: Meant to be set automatically by chart. Do not set it manually.
  19449. */
  19450. rotation: null,
  19451. /**
  19452. * @cfg {Array} visibleRange
  19453. * Specify the proportion of the axis to be rendered. The series bound to
  19454. * this axis will be synchronized and transformed accordingly.
  19455. */
  19456. visibleRange: [
  19457. 0,
  19458. 1
  19459. ],
  19460. /**
  19461. * @cfg {Boolean} needHighPrecision
  19462. * Indicates that the axis needs high precision surface implementation.
  19463. * See {@link Ext.draw.engine.Canvas#highPrecision}
  19464. */
  19465. needHighPrecision: false,
  19466. /**
  19467. * @cfg {Ext.chart.axis.Axis|String|Number} linkedTo
  19468. * Axis (itself, its ID or index) that this axis is linked to.
  19469. * When an axis is linked to a master axis, it will use the same data as the master axis.
  19470. * It can be used to show additional info, or to ease reading the chart by duplicating
  19471. * the scales.
  19472. */
  19473. linkedTo: null,
  19474. /**
  19475. * @cfg {Number|Object}
  19476. * If `floating` is a number, then it's a percentage displacement of the axis from its
  19477. * initial {@link #position} in the direction opposite to the axis' direction. For instance,
  19478. * '{position:"left", floating:75}' displays a vertical axis at 3/4 of the chart, starting
  19479. * from the left. It is equivalent to '{position:"right", floating:25}'. If `floating` is
  19480. * an object, then `floating.value` is the position of this axis along another axis, defined
  19481. * by `floating.alongAxis`, where `alongAxis` is an ID, an
  19482. * {@link Ext.chart.AbstractChart#axes} config index, or the other axis itself. `alongAxis`
  19483. * must have an opposite {@link Ext.chart.axis.Axis#getAlignment alignment}.
  19484. * For example:
  19485. *
  19486. *
  19487. * axes: [
  19488. * {
  19489. * title: 'Average Temperature (F)',
  19490. * type: 'numeric',
  19491. * position: 'left',
  19492. * id: 'temperature-vertical-axis',
  19493. * minimum: -30,
  19494. * maximum: 130
  19495. * },
  19496. * {
  19497. * title: 'Month (2013)',
  19498. * type: 'category',
  19499. * position: 'bottom',
  19500. * floating: {
  19501. * value: 32,
  19502. * alongAxis: 'temperature-vertical-axis'
  19503. * }
  19504. * }
  19505. * ]
  19506. */
  19507. floating: null
  19508. },
  19509. titleOffset: 0,
  19510. spriteAnimationCount: 0,
  19511. boundSeries: [],
  19512. sprites: null,
  19513. surface: null,
  19514. /**
  19515. * @private
  19516. * @property {Array} range
  19517. * The full data range of the axis. Should not be set directly, Clear it to `null`
  19518. * and use `getRange` to update.
  19519. */
  19520. range: null,
  19521. defaultRange: [
  19522. 0,
  19523. 1
  19524. ],
  19525. rangePadding: 0.5,
  19526. xValues: [],
  19527. yValues: [],
  19528. masterAxis: null,
  19529. applyRotation: function(rotation) {
  19530. var twoPie = Math.PI * 2;
  19531. return (rotation % twoPie + Math.PI) % twoPie - Math.PI;
  19532. },
  19533. updateRotation: function(rotation) {
  19534. var sprites = this.getSprites(),
  19535. position = this.getPosition();
  19536. if (!this.getHidden() && position === 'angular' && sprites[0]) {
  19537. sprites[0].setAttributes({
  19538. baseRotation: rotation
  19539. });
  19540. }
  19541. },
  19542. applyTitle: function(title, oldTitle) {
  19543. var surface;
  19544. if (Ext.isString(title)) {
  19545. title = {
  19546. text: title
  19547. };
  19548. }
  19549. if (!oldTitle) {
  19550. oldTitle = Ext.create('sprite.text', title);
  19551. if ((surface = this.getSurface())) {
  19552. surface.add(oldTitle);
  19553. }
  19554. } else {
  19555. oldTitle.setAttributes(title);
  19556. }
  19557. return oldTitle;
  19558. },
  19559. getAdjustByMajorUnit: function() {
  19560. return !this.getHidden() && this.callParent();
  19561. },
  19562. applyFloating: function(floating, oldFloating) {
  19563. if (floating === null) {
  19564. floating = {
  19565. value: null,
  19566. alongAxis: null
  19567. };
  19568. } else if (Ext.isNumber(floating)) {
  19569. floating = {
  19570. value: floating,
  19571. alongAxis: null
  19572. };
  19573. }
  19574. if (Ext.isObject(floating)) {
  19575. if (oldFloating && oldFloating.alongAxis) {
  19576. delete this.getChart().getAxis(oldFloating.alongAxis).floatingAxes[this.getId()];
  19577. }
  19578. return floating;
  19579. }
  19580. return oldFloating;
  19581. },
  19582. constructor: function(config) {
  19583. var me = this,
  19584. id;
  19585. me.sprites = [];
  19586. me.labels = [];
  19587. // Maps IDs of the axes that float along this axis to their floating values.
  19588. me.floatingAxes = {};
  19589. config = config || {};
  19590. if (config.position === 'angular') {
  19591. config.style = config.style || {};
  19592. config.style.estStepSize = 1;
  19593. }
  19594. if ('id' in config) {
  19595. id = config.id;
  19596. } else if ('id' in me.config) {
  19597. id = me.config.id;
  19598. } else {
  19599. id = me.getId();
  19600. }
  19601. me.setId(id);
  19602. me.mixins.observable.constructor.apply(me, arguments);
  19603. },
  19604. /**
  19605. * @private
  19606. * @return {String}
  19607. */
  19608. getAlignment: function() {
  19609. switch (this.getPosition()) {
  19610. case 'left':
  19611. case 'right':
  19612. return 'vertical';
  19613. case 'top':
  19614. case 'bottom':
  19615. return 'horizontal';
  19616. case 'radial':
  19617. return 'radial';
  19618. case 'angular':
  19619. return 'angular';
  19620. }
  19621. },
  19622. /**
  19623. * @private
  19624. * @return {String}
  19625. */
  19626. getGridAlignment: function() {
  19627. switch (this.getPosition()) {
  19628. case 'left':
  19629. case 'right':
  19630. return 'horizontal';
  19631. case 'top':
  19632. case 'bottom':
  19633. return 'vertical';
  19634. case 'radial':
  19635. return 'circular';
  19636. case 'angular':
  19637. return 'radial';
  19638. }
  19639. },
  19640. /**
  19641. * @private
  19642. * Get the surface for drawing the series sprites
  19643. */
  19644. getSurface: function() {
  19645. var me = this,
  19646. chart = me.getChart(),
  19647. surface, gridSurface;
  19648. if (chart && !me.surface) {
  19649. surface = me.surface = chart.getSurface(me.getId(), 'axis');
  19650. gridSurface = me.gridSurface = chart.getSurface('main');
  19651. gridSurface.waitFor(surface);
  19652. me.getGrid();
  19653. me.createLimits();
  19654. }
  19655. return me.surface;
  19656. },
  19657. createLimits: function() {
  19658. var me = this,
  19659. chart = me.getChart(),
  19660. axisSprite = me.getSprites()[0],
  19661. gridAlignment = me.getGridAlignment(),
  19662. limits;
  19663. if (me.getLimits() && gridAlignment) {
  19664. gridAlignment = gridAlignment.replace('3d', '');
  19665. me.limits = limits = {
  19666. surface: chart.getSurface('overlay'),
  19667. lines: new Ext.chart.Markers(),
  19668. titles: new Ext.draw.sprite.Instancing()
  19669. };
  19670. limits.lines.setTemplate({
  19671. xclass: 'grid.' + gridAlignment
  19672. });
  19673. limits.lines.getTemplate().setAttributes({
  19674. strokeStyle: 'black'
  19675. }, true);
  19676. limits.surface.add(limits.lines);
  19677. axisSprite.bindMarker(gridAlignment + '-limit-lines', me.limits.lines);
  19678. me.limitTitleTpl = new Ext.draw.sprite.Text();
  19679. limits.titles.setTemplate(me.limitTitleTpl);
  19680. limits.surface.add(limits.titles);
  19681. }
  19682. },
  19683. applyGrid: function(grid) {
  19684. // Returning an empty object here if grid was set to 'true' so that
  19685. // config merging in the theme works properly.
  19686. if (grid === true) {
  19687. return {};
  19688. }
  19689. return grid;
  19690. },
  19691. updateGrid: function(grid) {
  19692. var me = this,
  19693. chart = me.getChart(),
  19694. gridSurface = me.gridSurface,
  19695. axisSprite, gridAlignment, gridSprite;
  19696. if (!chart) {
  19697. me.on({
  19698. chartattached: Ext.bind(me.updateGrid, me, [
  19699. grid
  19700. ]),
  19701. single: true
  19702. });
  19703. return;
  19704. }
  19705. axisSprite = me.getSprites()[0];
  19706. gridAlignment = me.getGridAlignment();
  19707. if (grid) {
  19708. gridSprite = me.gridSpriteEven;
  19709. if (!gridSprite) {
  19710. gridSprite = me.gridSpriteEven = new Ext.chart.Markers();
  19711. gridSprite.setTemplate({
  19712. xclass: 'grid.' + gridAlignment
  19713. });
  19714. gridSurface.add(gridSprite);
  19715. axisSprite.bindMarker(gridAlignment + '-even', gridSprite);
  19716. }
  19717. if (Ext.isObject(grid)) {
  19718. gridSprite.getTemplate().setAttributes(grid);
  19719. if (Ext.isObject(grid.even)) {
  19720. gridSprite.getTemplate().setAttributes(grid.even);
  19721. }
  19722. }
  19723. gridSprite = me.gridSpriteOdd;
  19724. if (!gridSprite) {
  19725. gridSprite = me.gridSpriteOdd = new Ext.chart.Markers();
  19726. gridSprite.setTemplate({
  19727. xclass: 'grid.' + gridAlignment
  19728. });
  19729. gridSurface.add(gridSprite);
  19730. axisSprite.bindMarker(gridAlignment + '-odd', gridSprite);
  19731. }
  19732. if (Ext.isObject(grid)) {
  19733. gridSprite.getTemplate().setAttributes(grid);
  19734. if (Ext.isObject(grid.odd)) {
  19735. gridSprite.getTemplate().setAttributes(grid.odd);
  19736. }
  19737. }
  19738. }
  19739. },
  19740. updateMinorTickSteps: function(minorTickSteps) {
  19741. var me = this,
  19742. sprites = me.getSprites(),
  19743. axisSprite = sprites && sprites[0],
  19744. surface;
  19745. if (axisSprite) {
  19746. axisSprite.setAttributes({
  19747. minorTicks: !!minorTickSteps
  19748. });
  19749. surface = me.getSurface();
  19750. if (!me.isConfiguring && surface) {
  19751. surface.renderFrame();
  19752. }
  19753. }
  19754. },
  19755. /**
  19756. *
  19757. * Mapping data value into coordinate.
  19758. *
  19759. * @param {*} value
  19760. * @param {String} field
  19761. * @param {Number} [idx]
  19762. * @param {Ext.util.MixedCollection} [items]
  19763. * @return {Number}
  19764. */
  19765. getCoordFor: function(value, field, idx, items) {
  19766. return this.getLayout().getCoordFor(value, field, idx, items);
  19767. },
  19768. applyPosition: function(pos) {
  19769. return pos.toLowerCase();
  19770. },
  19771. applyLength: function(length, oldLength) {
  19772. return length > 0 ? length : oldLength;
  19773. },
  19774. applyLabel: function(label, oldLabel) {
  19775. if (!oldLabel) {
  19776. oldLabel = new Ext.draw.sprite.Text({});
  19777. }
  19778. if (label) {
  19779. if (this.limitTitleTpl) {
  19780. this.limitTitleTpl.setAttributes(label);
  19781. }
  19782. oldLabel.setAttributes(label);
  19783. }
  19784. return oldLabel;
  19785. },
  19786. applyLayout: function(layout, oldLayout) {
  19787. layout = Ext.factory(layout, null, oldLayout, 'axisLayout');
  19788. layout.setAxis(this);
  19789. return layout;
  19790. },
  19791. applySegmenter: function(segmenter, oldSegmenter) {
  19792. segmenter = Ext.factory(segmenter, null, oldSegmenter, 'segmenter');
  19793. segmenter.setAxis(this);
  19794. return segmenter;
  19795. },
  19796. updateMinimum: function() {
  19797. this.range = null;
  19798. },
  19799. updateMaximum: function() {
  19800. this.range = null;
  19801. },
  19802. hideLabels: function() {
  19803. this.getSprites()[0].setDirty(true);
  19804. this.setLabel({
  19805. hidden: true
  19806. });
  19807. },
  19808. showLabels: function() {
  19809. this.getSprites()[0].setDirty(true);
  19810. this.setLabel({
  19811. hidden: false
  19812. });
  19813. },
  19814. /**
  19815. * Invokes renderFrame on this axis's surface(s)
  19816. */
  19817. renderFrame: function() {
  19818. this.getSurface().renderFrame();
  19819. },
  19820. updateChart: function(newChart, oldChart) {
  19821. var me = this,
  19822. surface;
  19823. if (oldChart) {
  19824. oldChart.unregister(me);
  19825. oldChart.un('serieschange', me.onSeriesChange, me);
  19826. me.linkAxis();
  19827. me.fireEvent('chartdetached', oldChart, me);
  19828. }
  19829. if (newChart) {
  19830. newChart.on('serieschange', me.onSeriesChange, me);
  19831. me.surface = null;
  19832. surface = me.getSurface();
  19833. me.getLabel().setSurface(surface);
  19834. surface.add(me.getSprites());
  19835. surface.add(me.getTitle());
  19836. newChart.register(me);
  19837. me.fireEvent('chartattached', newChart, me);
  19838. }
  19839. },
  19840. applyBackground: function(background) {
  19841. var rect = Ext.ClassManager.getByAlias('sprite.rect');
  19842. return rect.def.normalize(background);
  19843. },
  19844. /**
  19845. * @protected
  19846. * Invoked when data has changed.
  19847. */
  19848. processData: function() {
  19849. this.getLayout().processData();
  19850. this.range = null;
  19851. },
  19852. getDirection: function() {
  19853. return this.getChart().getDirectionForAxis(this.getPosition());
  19854. },
  19855. isSide: function() {
  19856. var position = this.getPosition();
  19857. return position === 'left' || position === 'right';
  19858. },
  19859. applyFields: function(fields) {
  19860. return Ext.Array.from(fields);
  19861. },
  19862. applyVisibleRange: function(visibleRange, oldVisibleRange) {
  19863. var temp;
  19864. this.getChart();
  19865. // If it is in reversed order swap them
  19866. if (visibleRange[0] > visibleRange[1]) {
  19867. temp = visibleRange[0];
  19868. visibleRange[0] = visibleRange[1];
  19869. visibleRange[0] = temp;
  19870. }
  19871. if (visibleRange[1] === visibleRange[0]) {
  19872. visibleRange[1] += 1 / this.getMaxZoom();
  19873. }
  19874. if (visibleRange[1] > visibleRange[0] + 1) {
  19875. visibleRange[0] = 0;
  19876. visibleRange[1] = 1;
  19877. } else if (visibleRange[0] < 0) {
  19878. visibleRange[1] -= visibleRange[0];
  19879. visibleRange[0] = 0;
  19880. } else if (visibleRange[1] > 1) {
  19881. visibleRange[0] -= visibleRange[1] - 1;
  19882. visibleRange[1] = 1;
  19883. }
  19884. if (oldVisibleRange && visibleRange[0] === oldVisibleRange[0] && visibleRange[1] === oldVisibleRange[1]) {
  19885. return undefined;
  19886. }
  19887. return visibleRange;
  19888. },
  19889. updateVisibleRange: function(visibleRange) {
  19890. this.fireEvent('visiblerangechange', this, visibleRange);
  19891. },
  19892. onSeriesChange: function(chart) {
  19893. var me = this,
  19894. series = chart.getSeries(),
  19895. boundSeries = [],
  19896. linkedTo, masterAxis, getAxisMethod, i, ln;
  19897. if (series) {
  19898. getAxisMethod = 'get' + me.getDirection() + 'Axis';
  19899. for (i = 0 , ln = series.length; i < ln; i++) {
  19900. if (this === series[i][getAxisMethod]()) {
  19901. boundSeries.push(series[i]);
  19902. }
  19903. }
  19904. }
  19905. me.boundSeries = boundSeries;
  19906. linkedTo = me.getLinkedTo();
  19907. masterAxis = !Ext.isEmpty(linkedTo) && chart.getAxis(linkedTo);
  19908. if (masterAxis) {
  19909. me.linkAxis(masterAxis);
  19910. } else {
  19911. me.getLayout().processData();
  19912. }
  19913. },
  19914. linkAxis: function(masterAxis) {
  19915. var me = this;
  19916. function link(action, slave, master) {
  19917. master.getLayout()[action]('datachange', 'onDataChange', slave);
  19918. master[action]('rangechange', 'onMasterAxisRangeChange', slave);
  19919. }
  19920. if (me.masterAxis) {
  19921. if (!me.masterAxis.destroyed) {
  19922. link('un', me, me.masterAxis);
  19923. }
  19924. me.masterAxis = null;
  19925. }
  19926. if (masterAxis) {
  19927. if (masterAxis.type !== this.type) {
  19928. Ext.Error.raise("Linked axes must be of the same type.");
  19929. }
  19930. link('on', me, masterAxis);
  19931. me.onDataChange(masterAxis.getLayout().labels);
  19932. me.onMasterAxisRangeChange(masterAxis, masterAxis.range);
  19933. me.setStyle(Ext.apply({}, me.config.style, masterAxis.config.style));
  19934. me.setTitle(Ext.apply({}, me.config.title, masterAxis.config.title));
  19935. me.setLabel(Ext.apply({}, me.config.label, masterAxis.config.label));
  19936. me.masterAxis = masterAxis;
  19937. }
  19938. },
  19939. onDataChange: function(data) {
  19940. this.getLayout().labels = data;
  19941. },
  19942. onMasterAxisRangeChange: function(masterAxis, range) {
  19943. this.range = range;
  19944. },
  19945. applyRange: function(newRange) {
  19946. if (!newRange) {
  19947. return this.dataRange.slice(0);
  19948. } else {
  19949. return [
  19950. newRange[0] === null ? this.dataRange[0] : newRange[0],
  19951. newRange[1] === null ? this.dataRange[1] : newRange[1]
  19952. ];
  19953. }
  19954. },
  19955. /**
  19956. * @private
  19957. */
  19958. setBoundSeriesRange: function(range) {
  19959. var boundSeries = this.boundSeries,
  19960. style = {},
  19961. series, i, sprites, j, ln;
  19962. style['range' + this.getDirection()] = range;
  19963. for (i = 0 , ln = boundSeries.length; i < ln; i++) {
  19964. series = boundSeries[i];
  19965. if (series.getHidden() === true) {
  19966. continue;
  19967. }
  19968. sprites = series.getSprites();
  19969. for (j = 0; j < sprites.length; j++) {
  19970. sprites[j].setAttributes(style);
  19971. }
  19972. }
  19973. },
  19974. /**
  19975. * Get the range derived from all the bound series.
  19976. * The range value is cached and returned the next time this method is called.
  19977. * Set `recalculate` to `true` to recalculate the range, if changes to the
  19978. * chart, its components or data are expected to affect the range.
  19979. * @param {Boolean} [recalculate]
  19980. * @return {Number[]}
  19981. */
  19982. getRange: function(recalculate) {
  19983. var me = this,
  19984. range = recalculate ? null : me.range,
  19985. oldRange = me.oldRange,
  19986. minimum, maximum;
  19987. if (!range) {
  19988. if (me.masterAxis) {
  19989. range = me.masterAxis.range;
  19990. } else {
  19991. minimum = me.getMinimum();
  19992. maximum = me.getMaximum();
  19993. if (Ext.isNumber(minimum) && Ext.isNumber(maximum)) {
  19994. range = [
  19995. minimum,
  19996. maximum
  19997. ];
  19998. } else {
  19999. range = me.calculateRange();
  20000. }
  20001. me.range = range;
  20002. }
  20003. }
  20004. if (range && (!oldRange || range[0] !== oldRange[0] || range[1] !== oldRange[1])) {
  20005. me.fireEvent('rangechange', me, range, oldRange);
  20006. me.oldRange = range;
  20007. }
  20008. return range;
  20009. },
  20010. isSingleDataPoint: function(range) {
  20011. return (range[0] + this.rangePadding) === 0 && (range[1] - this.rangePadding) === 0;
  20012. },
  20013. calculateRange: function() {
  20014. var me = this,
  20015. boundSeries = me.boundSeries,
  20016. layout = me.getLayout(),
  20017. segmenter = me.getSegmenter(),
  20018. minimum = me.getMinimum(),
  20019. maximum = me.getMaximum(),
  20020. visibleRange = me.getVisibleRange(),
  20021. getRangeMethod = 'get' + me.getDirection() + 'Range',
  20022. expandRangeBy = me.getExpandRangeBy(),
  20023. context, attr, majorTicks, series, i, ln, seriesRange,
  20024. range = [
  20025. NaN,
  20026. NaN
  20027. ];
  20028. // For each series bound to this axis, ask the series for its min/max values
  20029. // and use them to find the overall min/max.
  20030. for (i = 0 , ln = boundSeries.length; i < ln; i++) {
  20031. series = boundSeries[i];
  20032. if (series.getHidden() === true) {
  20033. continue;
  20034. }
  20035. seriesRange = series[getRangeMethod]();
  20036. if (seriesRange) {
  20037. Ext.chart.Util.expandRange(range, seriesRange);
  20038. }
  20039. }
  20040. range = Ext.chart.Util.validateRange(range, me.defaultRange, me.rangePadding);
  20041. // The second condition is there to account for a special case where we only have
  20042. // a single data point, so the effective range of coordinated data is 0 (whatever
  20043. // the actual value of that single data point is, it will be assigned an index of
  20044. // zero, as the first and only data point). Since zero range is invalid, the
  20045. // validateRange function above will expand the range by the value of the rangePadding,
  20046. // which makes further expansion by the value of expandRangeBy unnecessary.
  20047. if (expandRangeBy && (!me.isSingleDataPoint(range))) {
  20048. range[0] -= expandRangeBy;
  20049. range[1] += expandRangeBy;
  20050. }
  20051. if (isFinite(minimum)) {
  20052. range[0] = minimum;
  20053. }
  20054. if (isFinite(maximum)) {
  20055. range[1] = maximum;
  20056. }
  20057. // When series `fullStack` config is used, the values may add up to
  20058. // slightly more than the value of the `fullStackTotal` config
  20059. // because of a precision error.
  20060. range[0] = Ext.Number.correctFloat(range[0]);
  20061. range[1] = Ext.Number.correctFloat(range[1]);
  20062. me.range = range;
  20063. // It's important to call 'me.reconcileRange' after the 'range'
  20064. // has been assigned to avoid circular calls.
  20065. if (me.getReconcileRange()) {
  20066. me.reconcileRange();
  20067. }
  20068. // TODO: Find a better way to do this.
  20069. // TODO: The original design didn't take into account that the range of an axis
  20070. // TODO: will depend not just on the range of the data of the bound series in the
  20071. // TODO: direction of the axis, but also on the range of other axes with the
  20072. // TODO: same direction and on the segmentation of the axis (interval between
  20073. // TODO: major ticks).
  20074. // TODO: While the fist omission was possible to retrofit rather gracefully
  20075. // TODO: by adding the axis.reconcileRange method, the second one is harder to deal with.
  20076. // TODO: The issue is that the resulting axis segmentation, which is a part of
  20077. // TODO: the axis sprite layout has to be known before layout has begun.
  20078. // TODO: Example for the logic below:
  20079. // TODO: If we have a range of data of 0..34.5 the step will be 2 and we
  20080. // TODO: will round up the max to 36 based on that step, but when the range is 0..36,
  20081. // TODO: the step becomes 5, so we have to reconcile the range once again where max
  20082. // TODO: becomes 40.
  20083. if (range[0] !== range[1] && me.getAdjustByMajorUnit() && segmenter.adjustByMajorUnit && !me.getMajorTickSteps()) {
  20084. attr = Ext.Object.chain(me.getSprites()[0].attr);
  20085. attr.min = range[0];
  20086. attr.max = range[1];
  20087. attr.visibleMin = visibleRange[0];
  20088. attr.visibleMax = visibleRange[1];
  20089. context = {
  20090. attr: attr,
  20091. segmenter: segmenter
  20092. };
  20093. layout.calculateLayout(context);
  20094. majorTicks = context.majorTicks;
  20095. if (majorTicks) {
  20096. segmenter.adjustByMajorUnit(majorTicks.step, majorTicks.unit.scale, range);
  20097. attr.min = range[0];
  20098. attr.max = range[1];
  20099. context.majorTicks = null;
  20100. layout.calculateLayout(context);
  20101. majorTicks = context.majorTicks;
  20102. segmenter.adjustByMajorUnit(majorTicks.step, majorTicks.unit.scale, range);
  20103. } else if (!me.hasClearRangePending) {
  20104. // Axis hasn't been rendered yet.
  20105. me.hasClearRangePending = true;
  20106. me.getChart().on('layout', 'clearRange', me);
  20107. }
  20108. }
  20109. return range;
  20110. },
  20111. /**
  20112. * @private
  20113. */
  20114. clearRange: function() {
  20115. this.hasClearRangePending = null;
  20116. this.range = null;
  20117. },
  20118. /**
  20119. * Expands the range of the axis
  20120. * based on the range of other axes with the same direction (if any).
  20121. */
  20122. reconcileRange: function() {
  20123. var me = this,
  20124. axes = me.getChart().getAxes(),
  20125. direction = me.getDirection(),
  20126. i, ln, axis, range;
  20127. if (!axes) {
  20128. return;
  20129. }
  20130. for (i = 0 , ln = axes.length; i < ln; i++) {
  20131. axis = axes[i];
  20132. range = axis.getRange();
  20133. if (axis === me || axis.getDirection() !== direction || !range || !axis.getReconcileRange()) {
  20134. continue;
  20135. }
  20136. if (range[0] < me.range[0]) {
  20137. me.range[0] = range[0];
  20138. }
  20139. if (range[1] > me.range[1]) {
  20140. me.range[1] = range[1];
  20141. }
  20142. }
  20143. },
  20144. applyStyle: function(style, oldStyle) {
  20145. var cls = Ext.ClassManager.getByAlias('sprite.' + this.seriesType);
  20146. if (cls && cls.def) {
  20147. style = cls.def.normalize(style);
  20148. }
  20149. oldStyle = Ext.apply(oldStyle || {}, style);
  20150. return oldStyle;
  20151. },
  20152. themeOnlyIfConfigured: {
  20153. grid: true
  20154. },
  20155. updateTheme: function(theme) {
  20156. var me = this,
  20157. axisTheme = theme.getAxis(),
  20158. position = me.getPosition(),
  20159. initialConfig = me.getInitialConfig(),
  20160. defaultConfig = me.defaultConfig,
  20161. configs = me.self.getConfigurator().configs,
  20162. genericAxisTheme = axisTheme.defaults,
  20163. specificAxisTheme = axisTheme[position],
  20164. themeOnlyIfConfigured = me.themeOnlyIfConfigured,
  20165. key, value, isObjValue, isUnusedConfig, initialValue, cfg;
  20166. axisTheme = Ext.merge({}, genericAxisTheme, specificAxisTheme);
  20167. for (key in axisTheme) {
  20168. value = axisTheme[key];
  20169. cfg = configs[key];
  20170. if (value !== null && value !== undefined && cfg) {
  20171. initialValue = initialConfig[key];
  20172. isObjValue = Ext.isObject(value);
  20173. isUnusedConfig = initialValue === defaultConfig[key];
  20174. if (isObjValue) {
  20175. if (isUnusedConfig && themeOnlyIfConfigured[key]) {
  20176. continue;
  20177. }
  20178. value = Ext.merge({}, value, initialValue);
  20179. }
  20180. if (isUnusedConfig || isObjValue) {
  20181. me[cfg.names.set](value);
  20182. }
  20183. }
  20184. }
  20185. },
  20186. updateCenter: function(center) {
  20187. var me = this,
  20188. sprites = me.getSprites(),
  20189. axisSprite = sprites[0],
  20190. centerX = center[0],
  20191. centerY = center[1];
  20192. if (axisSprite) {
  20193. axisSprite.setAttributes({
  20194. centerX: centerX,
  20195. centerY: centerY
  20196. });
  20197. }
  20198. if (me.gridSpriteEven) {
  20199. me.gridSpriteEven.getTemplate().setAttributes({
  20200. translationX: centerX,
  20201. translationY: centerY,
  20202. rotationCenterX: centerX,
  20203. rotationCenterY: centerY
  20204. });
  20205. }
  20206. if (me.gridSpriteOdd) {
  20207. me.gridSpriteOdd.getTemplate().setAttributes({
  20208. translationX: centerX,
  20209. translationY: centerY,
  20210. rotationCenterX: centerX,
  20211. rotationCenterY: centerY
  20212. });
  20213. }
  20214. },
  20215. getSprites: function() {
  20216. if (!this.getChart()) {
  20217. return;
  20218. }
  20219. // eslint-disable-next-line vars-on-top
  20220. var me = this,
  20221. range = me.getRange(),
  20222. position = me.getPosition(),
  20223. chart = me.getChart(),
  20224. animation = chart.getAnimation(),
  20225. length = me.getLength(),
  20226. axisClass = me.superclass,
  20227. mainSprite, style, animationModifier;
  20228. // If animation is false, then stop animation.
  20229. if (animation === false) {
  20230. animation = {
  20231. duration: 0
  20232. };
  20233. }
  20234. style = Ext.applyIf({
  20235. position: position,
  20236. axis: me,
  20237. length: length,
  20238. grid: me.getGrid(),
  20239. hidden: me.getHidden(),
  20240. titleOffset: me.titleOffset,
  20241. layout: me.getLayout(),
  20242. segmenter: me.getSegmenter(),
  20243. totalAngle: me.getTotalAngle(),
  20244. label: me.getLabel()
  20245. }, me.getStyle());
  20246. if (range) {
  20247. style.min = range[0];
  20248. style.max = range[1];
  20249. }
  20250. // If the sprites are not created.
  20251. if (!me.sprites.length) {
  20252. while (!axisClass.xtype) {
  20253. axisClass = axisClass.superclass;
  20254. }
  20255. mainSprite = Ext.create('sprite.' + axisClass.xtype, style);
  20256. animationModifier = mainSprite.getAnimation();
  20257. animationModifier.setCustomDurations({
  20258. baseRotation: 0
  20259. });
  20260. animationModifier.on('animationstart', 'onAnimationStart', me);
  20261. animationModifier.on('animationend', 'onAnimationEnd', me);
  20262. mainSprite.setLayout(me.getLayout());
  20263. mainSprite.setSegmenter(me.getSegmenter());
  20264. mainSprite.setLabel(me.getLabel());
  20265. me.sprites.push(mainSprite);
  20266. me.updateTitleSprite();
  20267. } else {
  20268. mainSprite = me.sprites[0];
  20269. mainSprite.setAnimation(animation);
  20270. mainSprite.setAttributes(style);
  20271. }
  20272. if (me.getRenderer()) {
  20273. mainSprite.setRenderer(me.getRenderer());
  20274. }
  20275. return me.sprites;
  20276. },
  20277. /**
  20278. * @private
  20279. */
  20280. performLayout: function() {
  20281. if (this.isConfiguring) {
  20282. return;
  20283. }
  20284. // eslint-disable-next-line vars-on-top
  20285. var me = this,
  20286. sprites = me.getSprites(),
  20287. surface = me.getSurface(),
  20288. chart = me.getChart(),
  20289. sprite = sprites && sprites[0];
  20290. if (chart && surface && sprite) {
  20291. sprite.callUpdater(null, 'layout');
  20292. // recalculate axis ticks
  20293. chart.scheduleLayout();
  20294. }
  20295. },
  20296. updateTitleSprite: function() {
  20297. var me = this,
  20298. length = me.getLength(),
  20299. surface, thickness, title, position, margin, titleMargin, anchor;
  20300. if (!me.sprites[0] || !Ext.isNumber(length)) {
  20301. return;
  20302. }
  20303. thickness = this.sprites[0].thickness;
  20304. surface = me.getSurface();
  20305. title = me.getTitle();
  20306. position = me.getPosition();
  20307. margin = me.getMargin();
  20308. titleMargin = me.getTitleMargin();
  20309. anchor = surface.roundPixel(length / 2);
  20310. if (title) {
  20311. switch (position) {
  20312. case 'top':
  20313. title.setAttributes({
  20314. x: anchor,
  20315. y: margin + titleMargin / 2,
  20316. textBaseline: 'top',
  20317. textAlign: 'center'
  20318. }, true);
  20319. title.applyTransformations();
  20320. me.titleOffset = title.getBBox().height + titleMargin;
  20321. break;
  20322. case 'bottom':
  20323. title.setAttributes({
  20324. x: anchor,
  20325. y: thickness + titleMargin / 2,
  20326. textBaseline: 'top',
  20327. textAlign: 'center'
  20328. }, true);
  20329. title.applyTransformations();
  20330. me.titleOffset = title.getBBox().height + titleMargin;
  20331. break;
  20332. case 'left':
  20333. title.setAttributes({
  20334. x: margin + titleMargin / 2,
  20335. y: anchor,
  20336. textBaseline: 'top',
  20337. textAlign: 'center',
  20338. rotationCenterX: margin + titleMargin / 2,
  20339. rotationCenterY: anchor,
  20340. rotationRads: -Math.PI / 2
  20341. }, true);
  20342. title.applyTransformations();
  20343. me.titleOffset = title.getBBox().width + titleMargin;
  20344. break;
  20345. case 'right':
  20346. title.setAttributes({
  20347. x: thickness - margin + titleMargin / 2,
  20348. y: anchor,
  20349. textBaseline: 'bottom',
  20350. textAlign: 'center',
  20351. rotationCenterX: thickness + titleMargin / 2,
  20352. rotationCenterY: anchor,
  20353. rotationRads: Math.PI / 2
  20354. }, true);
  20355. title.applyTransformations();
  20356. me.titleOffset = title.getBBox().width + titleMargin;
  20357. break;
  20358. }
  20359. }
  20360. },
  20361. onThicknessChanged: function() {
  20362. this.getChart().onThicknessChanged();
  20363. },
  20364. getThickness: function() {
  20365. if (this.getHidden()) {
  20366. return 0;
  20367. }
  20368. return (this.sprites[0] && this.sprites[0].thickness || 1) + this.titleOffset + this.getMargin();
  20369. },
  20370. onAnimationStart: function() {
  20371. this.spriteAnimationCount++;
  20372. if (this.spriteAnimationCount === 1) {
  20373. this.fireEvent('animationstart', this);
  20374. }
  20375. },
  20376. onAnimationEnd: function() {
  20377. this.spriteAnimationCount--;
  20378. if (this.spriteAnimationCount === 0) {
  20379. this.fireEvent('animationend', this);
  20380. }
  20381. },
  20382. // Methods used in ComponentQuery and controller
  20383. getItemId: function() {
  20384. return this.getId();
  20385. },
  20386. getAncestorIds: function() {
  20387. return [
  20388. this.getChart().getId()
  20389. ];
  20390. },
  20391. isXType: function(xtype) {
  20392. return xtype === 'axis';
  20393. },
  20394. // Override the Observable's method to redirect listener scope
  20395. // resolution to the chart.
  20396. resolveListenerScope: function(defaultScope) {
  20397. var me = this,
  20398. namedScope = Ext._namedScopes[defaultScope],
  20399. chart = me.getChart(),
  20400. scope;
  20401. if (!namedScope) {
  20402. scope = chart ? chart.resolveListenerScope(defaultScope, false) : (defaultScope || me);
  20403. } else if (namedScope.isThis) {
  20404. scope = me;
  20405. } else if (namedScope.isController) {
  20406. scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
  20407. } else if (namedScope.isSelf) {
  20408. scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
  20409. // Class body listener. No chart controller, nor chart container controller.
  20410. if (scope === chart && !chart.getInheritedConfig('defaultListenerScope')) {
  20411. scope = me;
  20412. }
  20413. }
  20414. return scope;
  20415. },
  20416. destroy: function() {
  20417. var me = this;
  20418. me.setChart(null);
  20419. me.surface.destroy();
  20420. me.surface = null;
  20421. me.callParent();
  20422. }
  20423. });
  20424. /**
  20425. * The legend base class adapater for modern toolkit.
  20426. */
  20427. Ext.define('Ext.chart.legend.LegendBase', {
  20428. extend: 'Ext.dataview.DataView',
  20429. config: {
  20430. /* eslint-disable max-len, no-useless-escape */
  20431. itemTpl: [
  20432. '<span class="',
  20433. Ext.baseCSSPrefix,
  20434. 'legend-item-marker {[ values.disabled ? Ext.baseCSSPrefix + \'legend-item-inactive\' : \'\' ]}" style="background:{mark};"></span>{name}'
  20435. ],
  20436. /* eslint-enable max-len, no-useless-escape */
  20437. inline: true,
  20438. scrollable: false
  20439. },
  20440. // for IE11 vertical align
  20441. constructor: function(config) {
  20442. var scroller, onDrag;
  20443. this.callParent([
  20444. config
  20445. ]);
  20446. scroller = this.getScrollable();
  20447. onDrag = scroller.onDrag;
  20448. scroller.onDrag = function(e) {
  20449. e.stopPropagation();
  20450. onDrag.call(this, e);
  20451. };
  20452. },
  20453. updateDocked: function(docked, oldDocked) {
  20454. var me = this,
  20455. el = me.el;
  20456. me.callParent([
  20457. docked,
  20458. oldDocked
  20459. ]);
  20460. switch (docked) {
  20461. case 'top':
  20462. // eslint-disable-next-line no-fallthrough
  20463. case 'bottom':
  20464. el.addCls(me.horizontalCls);
  20465. el.removeCls(me.verticalCls);
  20466. break;
  20467. case 'left':
  20468. // eslint-disable-next-line no-fallthrough
  20469. case 'right':
  20470. el.addCls(me.verticalCls);
  20471. el.removeCls(me.horizontalCls);
  20472. break;
  20473. }
  20474. },
  20475. onChildTap: function(view, context) {
  20476. this.callParent([
  20477. view,
  20478. context
  20479. ]);
  20480. this.toggleItem(context.viewIndex);
  20481. }
  20482. });
  20483. /**
  20484. * This class provides a dataview-based chart legend.
  20485. */
  20486. Ext.define('Ext.chart.legend.Legend', {
  20487. extend: 'Ext.chart.legend.LegendBase',
  20488. alternateClassName: 'Ext.chart.Legend',
  20489. xtype: 'legend',
  20490. alias: 'legend.dom',
  20491. type: 'dom',
  20492. isLegend: true,
  20493. isDomLegend: true,
  20494. config: {
  20495. /**
  20496. * @cfg {Array}
  20497. * The rect of the legend relative to its container.
  20498. */
  20499. rect: null,
  20500. /**
  20501. * @cfg {Boolean} toggleable
  20502. * `true` to allow series items to have their visibility
  20503. * toggled by interaction with the legend items.
  20504. */
  20505. toggleable: true
  20506. },
  20507. /**
  20508. * @cfg {Ext.chart.legend.store.Store} store
  20509. * The {@link Ext.chart.legend.store.Store} to bind this legend to.
  20510. * @private
  20511. */
  20512. baseCls: Ext.baseCSSPrefix + 'legend',
  20513. horizontalCls: Ext.baseCSSPrefix + 'legend-horizontal',
  20514. verticalCls: Ext.baseCSSPrefix + 'legend-vertical',
  20515. toggleItem: function(index) {
  20516. var disabledCount = 0,
  20517. canToggle = true,
  20518. disabled, store, count, record, i;
  20519. if (!this.getToggleable()) {
  20520. return;
  20521. }
  20522. store = this.getStore();
  20523. if (store) {
  20524. count = store.getCount();
  20525. for (i = 0; i < count; i++) {
  20526. record = store.getAt(i);
  20527. if (record.get('disabled')) {
  20528. disabledCount++;
  20529. }
  20530. }
  20531. canToggle = count - disabledCount > 1;
  20532. record = store.getAt(index);
  20533. if (record) {
  20534. disabled = record.get('disabled');
  20535. if (disabled || canToggle) {
  20536. // This will trigger AbstractChart.onLegendStoreUpdate.
  20537. record.set('disabled', !disabled);
  20538. }
  20539. }
  20540. }
  20541. },
  20542. onResize: function(width, height, oldWidth, oldHeight) {
  20543. var me = this,
  20544. chart = me.chart;
  20545. if (!me.isConfiguring) {
  20546. if (chart) {
  20547. chart.scheduleLayout();
  20548. }
  20549. }
  20550. }
  20551. });
  20552. /**
  20553. * @private
  20554. */
  20555. Ext.define('Ext.chart.legend.sprite.Item', {
  20556. extend: 'Ext.draw.sprite.Composite',
  20557. alias: 'sprite.legenditem',
  20558. type: 'legenditem',
  20559. isLegendItem: true,
  20560. requires: [
  20561. 'Ext.draw.sprite.Text',
  20562. 'Ext.draw.sprite.Circle'
  20563. ],
  20564. inheritableStatics: {
  20565. def: {
  20566. processors: {
  20567. enabled: 'limited01',
  20568. markerLabelGap: 'number'
  20569. },
  20570. animationProcessors: {
  20571. enabled: null,
  20572. markerLabelGap: null
  20573. },
  20574. defaults: {
  20575. enabled: true,
  20576. markerLabelGap: 5
  20577. },
  20578. triggers: {
  20579. enabled: 'enabled',
  20580. markerLabelGap: 'layout'
  20581. },
  20582. updaters: {
  20583. layout: 'layoutUpdater',
  20584. enabled: 'enabledUpdater'
  20585. }
  20586. }
  20587. },
  20588. config: {
  20589. // Sprite's attributes are processed after initConfig.
  20590. // So we need to init below configs lazily, as otherwise
  20591. // adding sprites (created from those configs) to composite
  20592. // will result in an attempt to access attributes that
  20593. // composite doesn't have yet.
  20594. label: {
  20595. $value: {
  20596. type: 'text'
  20597. },
  20598. lazy: true
  20599. },
  20600. marker: {
  20601. $value: {
  20602. type: 'circle'
  20603. },
  20604. lazy: true
  20605. },
  20606. legend: null,
  20607. store: null,
  20608. record: null,
  20609. series: null
  20610. },
  20611. applyLabel: function(label, oldLabel) {
  20612. var sprite;
  20613. if (label) {
  20614. if (label.isSprite && label.type === 'text') {
  20615. sprite = label;
  20616. } else {
  20617. if (oldLabel && label.type === oldLabel.type) {
  20618. oldLabel.setConfig(label);
  20619. sprite = oldLabel;
  20620. this.scheduleUpdater(this.attr, 'layout');
  20621. } else {
  20622. sprite = new Ext.draw.sprite.Text(label);
  20623. }
  20624. }
  20625. }
  20626. return sprite;
  20627. },
  20628. defaultMarkerSize: 10,
  20629. updateLabel: function(label, oldLabel) {
  20630. var me = this;
  20631. me.removeSprite(oldLabel);
  20632. label.setAttributes({
  20633. textBaseline: 'middle'
  20634. });
  20635. me.addSprite(label);
  20636. me.scheduleUpdater(me.attr, 'layout');
  20637. },
  20638. applyMarker: function(config) {
  20639. var marker;
  20640. if (config) {
  20641. if (config.isSprite) {
  20642. marker = config;
  20643. } else {
  20644. marker = this.createMarker(config);
  20645. }
  20646. }
  20647. marker = this.resetMarker(marker, config);
  20648. return marker;
  20649. },
  20650. createMarker: function(config) {
  20651. var marker;
  20652. // If marker attributes are animated, the attributes change over
  20653. // time from default values to the values specified in the marker
  20654. // config. But the 'legenditem' sprite needs final values
  20655. // to properly layout its children.
  20656. delete config.animation;
  20657. if (config.type === 'image') {
  20658. delete config.width;
  20659. delete config.height;
  20660. }
  20661. marker = Ext.create('sprite.' + config.type, config);
  20662. return marker;
  20663. },
  20664. resetMarker: function(sprite, config) {
  20665. var size = config.size || this.defaultMarkerSize,
  20666. bbox, max, scale;
  20667. // Layout may not work properly,
  20668. // if the marker sprite is transformed to begin with.
  20669. sprite.setTransform([
  20670. 1,
  20671. 0,
  20672. 0,
  20673. 1,
  20674. 0,
  20675. 0
  20676. ], true);
  20677. if (config.type === 'image') {
  20678. sprite.setAttributes({
  20679. width: size,
  20680. height: size
  20681. });
  20682. } else {
  20683. // This should work with any sprite, irrespective of what attribute
  20684. // is used to control sprite's size ('size', 'r', or something else).
  20685. // However, the 'image' sprite above is a special case.
  20686. bbox = sprite.getBBox();
  20687. max = Math.max(bbox.width, bbox.height);
  20688. scale = size / max;
  20689. sprite.setAttributes({
  20690. scalingX: scale,
  20691. scalingY: scale
  20692. });
  20693. }
  20694. return sprite;
  20695. },
  20696. updateMarker: function(marker, oldMarker) {
  20697. var me = this;
  20698. me.removeSprite(oldMarker);
  20699. me.addSprite(marker);
  20700. me.scheduleUpdater(me.attr, 'layout');
  20701. },
  20702. updateSurface: function(surface, oldSurface) {
  20703. var me = this;
  20704. me.callParent([
  20705. surface,
  20706. oldSurface
  20707. ]);
  20708. if (surface) {
  20709. me.scheduleUpdater(me.attr, 'layout');
  20710. }
  20711. },
  20712. enabledUpdater: function(attr) {
  20713. var marker = this.getMarker();
  20714. if (marker) {
  20715. marker.setAttributes({
  20716. globalAlpha: attr.enabled ? 1 : 0.3
  20717. });
  20718. }
  20719. },
  20720. layoutUpdater: function() {
  20721. var me = this,
  20722. attr = me.attr,
  20723. label = me.getLabel(),
  20724. marker = me.getMarker(),
  20725. labelBBox, markerBBox, totalHeight;
  20726. // Measuring bounding boxes of transformed marker and label
  20727. // sprites and translating the sprites by required amount,
  20728. // makes layout virtually bullet-proof to unaccounted for
  20729. // changes in sprite attributes, whatever the sprite type may be.
  20730. markerBBox = marker.getBBox();
  20731. labelBBox = label.getBBox();
  20732. totalHeight = Math.max(markerBBox.height, labelBBox.height);
  20733. // Because we are getting an already transformed bounding box,
  20734. // we want to add to that transformation, not replace it,
  20735. // so setting translationX/Y attributes here would be inappropriate.
  20736. marker.transform([
  20737. 1,
  20738. 0,
  20739. 0,
  20740. 1,
  20741. -markerBBox.x,
  20742. -markerBBox.y + (totalHeight - markerBBox.height) / 2
  20743. ], true);
  20744. label.transform([
  20745. 1,
  20746. 0,
  20747. 0,
  20748. 1,
  20749. -labelBBox.x + markerBBox.width + attr.markerLabelGap,
  20750. -labelBBox.y + (totalHeight - labelBBox.height) / 2
  20751. ], true);
  20752. me.bboxUpdater(attr);
  20753. }
  20754. });
  20755. /**
  20756. * @private
  20757. */
  20758. Ext.define('Ext.chart.legend.sprite.Border', {
  20759. extend: 'Ext.draw.sprite.Rect',
  20760. alias: 'sprite.legendborder',
  20761. type: 'legendborder',
  20762. isLegendBorder: true
  20763. });
  20764. /**
  20765. * @private
  20766. * Singleton that provides methods used by the Ext.draw.Path
  20767. * for hit testing and finding path intersection points.
  20768. */
  20769. Ext.define('Ext.draw.PathUtil', function() {
  20770. var abs = Math.abs,
  20771. pow = Math.pow,
  20772. cos = Math.cos,
  20773. acos = Math.acos,
  20774. sqrt = Math.sqrt,
  20775. PI = Math.PI;
  20776. // For extra info see: http://pomax.github.io/bezierinfo/
  20777. return {
  20778. singleton: true,
  20779. requires: [
  20780. 'Ext.draw.overrides.hittest.Path',
  20781. 'Ext.draw.overrides.hittest.sprite.Path'
  20782. ],
  20783. /**
  20784. * @private
  20785. * Finds roots of a cubic equation in t, where t lies in the interval of [0,1].
  20786. * Based on http://www.particleincell.com/blog/2013/cubic-line-intersection/
  20787. * @param P {Number[]} Cubic equation coefficients.
  20788. * @return {Number[]} Returns an array of parametric intersection locations along the cubic,
  20789. * with -1 indicating an out-of-bounds intersection
  20790. * (before or after the end point or in the imaginary plane).
  20791. */
  20792. cubicRoots: function(P) {
  20793. var a = P[0],
  20794. b = P[1],
  20795. c = P[2],
  20796. d = P[3];
  20797. if (a === 0) {
  20798. return this.quadraticRoots(b, c, d);
  20799. }
  20800. // eslint-disable-next-line vars-on-top, one-var
  20801. var A = b / a,
  20802. B = c / a,
  20803. C = d / a,
  20804. Q = (3 * B - pow(A, 2)) / 9,
  20805. R = (9 * A * B - 27 * C - 2 * pow(A, 3)) / 54,
  20806. D = pow(Q, 3) + pow(R, 2),
  20807. // Polynomial discriminant.
  20808. t = [],
  20809. S, T, Im, th, i,
  20810. sign = Ext.Number.sign;
  20811. if (D >= 0) {
  20812. // Complex or duplicate roots.
  20813. S = sign(R + sqrt(D)) * pow(abs(R + sqrt(D)), 1 / 3);
  20814. T = sign(R - sqrt(D)) * pow(abs(R - sqrt(D)), 1 / 3);
  20815. t[0] = -A / 3 + (S + T);
  20816. // Real root.
  20817. t[1] = -A / 3 - (S + T) / 2;
  20818. // Real part of complex root.
  20819. t[2] = t[1];
  20820. // Real part of complex root.
  20821. Im = abs(sqrt(3) * (S - T) / 2);
  20822. // Complex part of root pair.
  20823. // Discard complex roots.
  20824. if (Im !== 0) {
  20825. t[1] = -1;
  20826. t[2] = -1;
  20827. }
  20828. } else {
  20829. // Distinct real roots.
  20830. th = acos(R / sqrt(-pow(Q, 3)));
  20831. t[0] = 2 * sqrt(-Q) * cos(th / 3) - A / 3;
  20832. t[1] = 2 * sqrt(-Q) * cos((th + 2 * PI) / 3) - A / 3;
  20833. t[2] = 2 * sqrt(-Q) * cos((th + 4 * PI) / 3) - A / 3;
  20834. }
  20835. // Discard out of spec roots.
  20836. for (i = 0; i < 3; i++) {
  20837. if (t[i] < 0 || t[i] > 1) {
  20838. t[i] = -1;
  20839. }
  20840. }
  20841. return t;
  20842. },
  20843. /**
  20844. * @private
  20845. * Finds roots of a quadratic equation in t, where t lies in the interval of [0,1].
  20846. * Takes three quadratic equation coefficients as parameters.
  20847. * @param a {Number}
  20848. * @param b {Number}
  20849. * @param c {Number}
  20850. * @return {Array}
  20851. */
  20852. quadraticRoots: function(a, b, c) {
  20853. var D, rD, t, i;
  20854. if (a === 0) {
  20855. return this.linearRoot(b, c);
  20856. }
  20857. D = b * b - 4 * a * c;
  20858. if (D === 0) {
  20859. // One real root.
  20860. t = [
  20861. -b / (2 * a)
  20862. ];
  20863. } else if (D > 0) {
  20864. // Distinct real roots.
  20865. rD = sqrt(D);
  20866. t = [
  20867. (-b - rD) / (2 * a),
  20868. (-b + rD) / (2 * a)
  20869. ];
  20870. } else {
  20871. // Complex roots.
  20872. return [];
  20873. }
  20874. for (i = 0; i < t.length; i++) {
  20875. if (t[i] < 0 || t[i] > 1) {
  20876. t[i] = -1;
  20877. }
  20878. }
  20879. return t;
  20880. },
  20881. /**
  20882. * @private
  20883. * Finds roots of a linear equation in t, where t lies in the interval of [0,1].
  20884. * Takes two linear equation coefficients as parameters.
  20885. * @param a {Number}
  20886. * @param b {Number}
  20887. * @return {Array}
  20888. */
  20889. linearRoot: function(a, b) {
  20890. var t = -b / a;
  20891. if (a === 0 || t < 0 || t > 1) {
  20892. return [];
  20893. }
  20894. return [
  20895. t
  20896. ];
  20897. },
  20898. /**
  20899. * @private
  20900. * Calculates the coefficients of a cubic function for the given coordinates.
  20901. * @param P0 {Number}
  20902. * @param P1 {Number}
  20903. * @param P2 {Number}
  20904. * @param P3 {Number}
  20905. * @return {Array}
  20906. */
  20907. bezierCoeffs: function(P0, P1, P2, P3) {
  20908. var Z = [];
  20909. Z[0] = -P0 + 3 * P1 - 3 * P2 + P3;
  20910. Z[1] = 3 * P0 - 6 * P1 + 3 * P2;
  20911. Z[2] = -3 * P0 + 3 * P1;
  20912. Z[3] = P0;
  20913. return Z;
  20914. },
  20915. /**
  20916. * @private
  20917. * Computes intersection points between a cubic spline and a line segment.
  20918. * Takes in x/y components of cubic control points and line segment start/end points
  20919. * as parameters.
  20920. * @param px1 {Number}
  20921. * @param px2 {Number}
  20922. * @param px3 {Number}
  20923. * @param px4 {Number}
  20924. * @param py1 {Number}
  20925. * @param py2 {Number}
  20926. * @param py3 {Number}
  20927. * @param py4 {Number}
  20928. * @param x1 {Number}
  20929. * @param y1 {Number}
  20930. * @param x2 {Number}
  20931. * @param y2 {Number}
  20932. * @return {Array} Array of intersection points, where each intersection point
  20933. * is itself a two-item array [x,y].
  20934. */
  20935. cubicLineIntersections: function(px1, px2, px3, px4, py1, py2, py3, py4, x1, y1, x2, y2) {
  20936. var P = [],
  20937. intersections = [],
  20938. // Finding line equation coefficients.
  20939. A = y1 - y2,
  20940. B = x2 - x1,
  20941. C = x1 * (y2 - y1) - y1 * (x2 - x1),
  20942. // Finding cubic Bezier curve equation coefficients.
  20943. bx = this.bezierCoeffs(px1, px2, px3, px4),
  20944. by = this.bezierCoeffs(py1, py2, py3, py4),
  20945. i, r, s, t, tt, ttt, cx, cy;
  20946. P[0] = A * bx[0] + B * by[0];
  20947. // t^3
  20948. P[1] = A * bx[1] + B * by[1];
  20949. // t^2
  20950. P[2] = A * bx[2] + B * by[2];
  20951. // t
  20952. P[3] = A * bx[3] + B * by[3] + C;
  20953. // 1
  20954. r = this.cubicRoots(P);
  20955. // Verify the roots are in bounds of the linear segment.
  20956. for (i = 0; i < r.length; i++) {
  20957. t = r[i];
  20958. if (t < 0 || t > 1) {
  20959. continue;
  20960. }
  20961. tt = t * t;
  20962. ttt = tt * t;
  20963. cx = bx[0] * ttt + bx[1] * tt + bx[2] * t + bx[3];
  20964. cy = by[0] * ttt + by[1] * tt + by[2] * t + by[3];
  20965. // Above is intersection point assuming infinitely long line segment,
  20966. // make sure we are also in bounds of the line.
  20967. if ((x2 - x1) !== 0) {
  20968. // If not vertical line
  20969. s = (cx - x1) / (x2 - x1);
  20970. } else {
  20971. s = (cy - y1) / (y2 - y1);
  20972. }
  20973. // In bounds?
  20974. if (!(s < 0 || s > 1)) {
  20975. intersections.push([
  20976. cx,
  20977. cy
  20978. ]);
  20979. }
  20980. }
  20981. return intersections;
  20982. },
  20983. /**
  20984. * @private
  20985. * Splits cubic Bezier curve into two cubic Bezier curves at point z,
  20986. * where z belongs to a range of [0, 1].
  20987. * Accepts cubic coefficients and point z as parameters.
  20988. * @param P1 {Number}
  20989. * @param P2 {Number}
  20990. * @param P3 {Number}
  20991. * @param P4 {Number}
  20992. * @param z Point to split the given curve at.
  20993. * @return {Array} Two-item array, where each item is itself an array
  20994. * of cubic coefficients.
  20995. */
  20996. splitCubic: function(P1, P2, P3, P4, z) {
  20997. var zz = z * z,
  20998. zzz = z * zz,
  20999. iz = z - 1,
  21000. izz = iz * iz,
  21001. izzz = iz * izz,
  21002. // Common point for both curves.
  21003. P = zzz * P4 - 3 * zz * iz * P3 + 3 * z * izz * P2 - izzz * P1;
  21004. return [
  21005. [
  21006. P1,
  21007. z * P2 - iz * P1,
  21008. zz * P3 - 2 * z * iz * P2 + izz * P1,
  21009. P
  21010. ],
  21011. [
  21012. P,
  21013. zz * P4 - 2 * z * iz * P3 + izz * P2,
  21014. z * P4 - iz * P3,
  21015. P4
  21016. ]
  21017. ];
  21018. },
  21019. /**
  21020. * @private
  21021. * Returns the dimension of a cubic Bezier curve in a single direction.
  21022. * @param a {Number}
  21023. * @param b {Number}
  21024. * @param c {Number}
  21025. * @param d {Number}
  21026. * @return {Array} Two-item array representing cubic's range in the given direction.
  21027. */
  21028. cubicDimension: function(a, b, c, d) {
  21029. var qa = 3 * (-a + 3 * (b - c) + d),
  21030. qb = 6 * (a - 2 * b + c),
  21031. qc = -3 * (a - b),
  21032. x, y,
  21033. min = Math.min(a, d),
  21034. max = Math.max(a, d),
  21035. delta;
  21036. if (qa === 0) {
  21037. if (qb === 0) {
  21038. return [
  21039. min,
  21040. max
  21041. ];
  21042. } else {
  21043. x = -qc / qb;
  21044. if (0 < x && x < 1) {
  21045. y = this.interpolateCubic(a, b, c, d, x);
  21046. min = Math.min(min, y);
  21047. max = Math.max(max, y);
  21048. }
  21049. }
  21050. } else {
  21051. delta = qb * qb - 4 * qa * qc;
  21052. if (delta >= 0) {
  21053. delta = sqrt(delta);
  21054. x = (delta - qb) / 2 / qa;
  21055. if (0 < x && x < 1) {
  21056. y = this.interpolateCubic(a, b, c, d, x);
  21057. min = Math.min(min, y);
  21058. max = Math.max(max, y);
  21059. }
  21060. if (delta > 0) {
  21061. x -= delta / qa;
  21062. if (0 < x && x < 1) {
  21063. y = this.interpolateCubic(a, b, c, d, x);
  21064. min = Math.min(min, y);
  21065. max = Math.max(max, y);
  21066. }
  21067. }
  21068. }
  21069. }
  21070. return [
  21071. min,
  21072. max
  21073. ];
  21074. },
  21075. /**
  21076. * @private
  21077. * Calculates a value of a cubic function at the given point t. In other words
  21078. * returns a * (1 - t) ^ 3 + 3 * b (1 - t) ^ 2 * t + 3 * c (1 - t) * t ^ 3 + d * t ^ 3
  21079. * for given a, b, c, d and t, where t belongs to an interval of [0, 1].
  21080. * @param a {Number}
  21081. * @param b {Number}
  21082. * @param c {Number}
  21083. * @param d {Number}
  21084. * @param t {Number}
  21085. * @return {Number}
  21086. */
  21087. interpolateCubic: function(a, b, c, d, t) {
  21088. var rate;
  21089. if (t === 0) {
  21090. return a;
  21091. }
  21092. if (t === 1) {
  21093. return d;
  21094. }
  21095. rate = (1 - t) / t;
  21096. return t * t * t * (d + rate * (3 * c + rate * (3 * b + rate * a)));
  21097. },
  21098. /**
  21099. * @private
  21100. * Computes intersection points between two cubic Bezier curve segments.
  21101. * Takes x/y components of control points for two Bezier curve segments.
  21102. * @param ax1 {Number}
  21103. * @param ax2 {Number}
  21104. * @param ax3 {Number}
  21105. * @param ax4 {Number}
  21106. * @param ay1 {Number}
  21107. * @param ay2 {Number}
  21108. * @param ay3 {Number}
  21109. * @param ay4 {Number}
  21110. * @param bx1 {Number}
  21111. * @param bx2 {Number}
  21112. * @param bx3 {Number}
  21113. * @param bx4 {Number}
  21114. * @param by1 {Number}
  21115. * @param by2 {Number}
  21116. * @param by3 {Number}
  21117. * @param by4 {Number}
  21118. * @return {Array} Array of intersection points, where each intersection point
  21119. * is itself a two-item array [x,y].
  21120. */
  21121. cubicsIntersections: function(ax1, ax2, ax3, ax4, ay1, ay2, ay3, ay4, bx1, bx2, bx3, bx4, by1, by2, by3, by4) {
  21122. /* eslint-disable max-len */
  21123. var me = this,
  21124. axDim = me.cubicDimension(ax1, ax2, ax3, ax4),
  21125. ayDim = me.cubicDimension(ay1, ay2, ay3, ay4),
  21126. bxDim = me.cubicDimension(bx1, bx2, bx3, bx4),
  21127. byDim = me.cubicDimension(by1, by2, by3, by4),
  21128. splitAx, splitAy, splitBx, splitBy,
  21129. points = [];
  21130. // Curves' bounding boxes don't intersect.
  21131. if (axDim[0] > bxDim[1] || axDim[1] < bxDim[0] || ayDim[0] > byDim[1] || ayDim[1] < byDim[0]) {
  21132. return [];
  21133. }
  21134. // Both curves occupy sub-pixel areas which is effectively their intersection point.
  21135. 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) {
  21136. return [
  21137. [
  21138. (ax1 + ax4) * 0.5,
  21139. (ay1 + ay2) * 0.5
  21140. ]
  21141. ];
  21142. }
  21143. splitAx = me.splitCubic(ax1, ax2, ax3, ax4, 0.5);
  21144. splitAy = me.splitCubic(ay1, ay2, ay3, ay4, 0.5);
  21145. splitBx = me.splitCubic(bx1, bx2, bx3, bx4, 0.5);
  21146. splitBy = me.splitCubic(by1, by2, by3, by4, 0.5);
  21147. points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[0].concat(splitAy[0], splitBx[0], splitBy[0])));
  21148. points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[0].concat(splitAy[0], splitBx[1], splitBy[1])));
  21149. points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[1].concat(splitAy[1], splitBx[0], splitBy[0])));
  21150. points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[1].concat(splitAy[1], splitBx[1], splitBy[1])));
  21151. return points;
  21152. },
  21153. /* eslint-enable max-len */
  21154. /**
  21155. * @private
  21156. * Returns the point [x,y] where two line segments intersect or null.
  21157. * Takes x/y components of the start and end point of the segments as parameters.
  21158. * Based on Paul Bourke's explanation:
  21159. * http://paulbourke.net/geometry/pointlineplane/
  21160. * @param x1 {Number}
  21161. * @param y1 {Number}
  21162. * @param x2 {Number}
  21163. * @param y2 {Number}
  21164. * @param x3 {Number}
  21165. * @param y3 {Number}
  21166. * @param x4 {Number}
  21167. * @param y4 {Number}
  21168. * @return {Number[]|null}
  21169. */
  21170. linesIntersection: function(x1, y1, x2, y2, x3, y3, x4, y4) {
  21171. var d = (x2 - x1) * (y4 - y3) - (y2 - y1) * (x4 - x3),
  21172. ua, ub;
  21173. if (d === 0) {
  21174. // Lines are parallel.
  21175. return null;
  21176. }
  21177. ua = ((x4 - x3) * (y1 - y3) - (x1 - x3) * (y4 - y3)) / d;
  21178. ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / d;
  21179. if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
  21180. return [
  21181. x1 + ua * (x2 - x1),
  21182. // x
  21183. y1 + ua * (y2 - y1)
  21184. ];
  21185. }
  21186. // y
  21187. return null;
  21188. },
  21189. // The intersection point is outside one or both segments.
  21190. /**
  21191. * @private
  21192. * Checks if a point belongs to a line segment.
  21193. * Takes x/y components of the start and end points of the segment and the point's
  21194. * coordinates as parameters.
  21195. * @param x1 {Number}
  21196. * @param y1 {Number}
  21197. * @param x2 {Number}
  21198. * @param y2 {Number}
  21199. * @param x {Number}
  21200. * @param y {Number}
  21201. * @return {Boolean}
  21202. */
  21203. pointOnLine: function(x1, y1, x2, y2, x, y) {
  21204. var t, _;
  21205. if (abs(x2 - x1) < abs(y2 - y1)) {
  21206. _ = x1;
  21207. x1 = y1;
  21208. y1 = _;
  21209. _ = x2;
  21210. x2 = y2;
  21211. y2 = _;
  21212. _ = x;
  21213. x = y;
  21214. y = _;
  21215. }
  21216. t = (x - x1) / (x2 - x1);
  21217. if (t < 0 || t > 1) {
  21218. return false;
  21219. }
  21220. return abs(y1 + t * (y2 - y1) - y) < 4;
  21221. },
  21222. /**
  21223. * @private
  21224. * Checks if a point belongs to a cubic Bezier curve segment.
  21225. * Takes x/y components of the control points of the segment and the point's
  21226. * coordinates as parameters.
  21227. * @param px1 {Number}
  21228. * @param px2 {Number}
  21229. * @param px3 {Number}
  21230. * @param px4 {Number}
  21231. * @param py1 {Number}
  21232. * @param py2 {Number}
  21233. * @param py3 {Number}
  21234. * @param py4 {Number}
  21235. * @param x {Number}
  21236. * @param y {Number}
  21237. * @return {Boolean}
  21238. */
  21239. pointOnCubic: function(px1, px2, px3, px4, py1, py2, py3, py4, x, y) {
  21240. // Finding cubic Bezier curve equation coefficients.
  21241. var me = this,
  21242. bx = me.bezierCoeffs(px1, px2, px3, px4),
  21243. by = me.bezierCoeffs(py1, py2, py3, py4),
  21244. i, j, rx, ry, t;
  21245. bx[3] -= x;
  21246. by[3] -= y;
  21247. rx = me.cubicRoots(bx);
  21248. ry = me.cubicRoots(by);
  21249. for (i = 0; i < rx.length; i++) {
  21250. t = rx[i];
  21251. for (j = 0; j < ry.length; j++) {
  21252. // TODO: for more accurate results tolerance should be dynamic
  21253. // TODO: based on the length and shape of the segment.
  21254. if (t >= 0 && t <= 1 && abs(t - ry[j]) < 0.05) {
  21255. return true;
  21256. }
  21257. }
  21258. }
  21259. return false;
  21260. }
  21261. };
  21262. });
  21263. Ext.define('Ext.draw.overrides.hittest.All', {
  21264. requires: [
  21265. 'Ext.draw.PathUtil',
  21266. 'Ext.draw.overrides.hittest.sprite.Instancing',
  21267. 'Ext.draw.overrides.hittest.Surface'
  21268. ]
  21269. });
  21270. /**
  21271. * This class uses `Ext.draw.sprite.Sprite` to render the chart legend.
  21272. *
  21273. * The DOM legend is essentially a data view docked inside a draw container, which a chart is.
  21274. * The sprite legend, on the other hand, is not a foreign entity in a draw container,
  21275. * and is rendered in a draw surface with sprites, just like series and axes.
  21276. *
  21277. * This means that:
  21278. *
  21279. * * it is styleable with chart themes
  21280. * * it shows up in chart preview and chart download
  21281. * * it renders markers exactly as they are in the series
  21282. * * it can't be styled with CSS
  21283. * * it doesn't scroll, instead the items are grouped into columns,
  21284. * and the legend grows in size as the number of items increases
  21285. *
  21286. */
  21287. Ext.define('Ext.chart.legend.SpriteLegend', {
  21288. alias: 'legend.sprite',
  21289. type: 'sprite',
  21290. isLegend: true,
  21291. isSpriteLegend: true,
  21292. mixins: [
  21293. 'Ext.mixin.Observable'
  21294. ],
  21295. requires: [
  21296. 'Ext.chart.legend.sprite.Item',
  21297. 'Ext.chart.legend.sprite.Border',
  21298. 'Ext.draw.overrides.hittest.All',
  21299. 'Ext.draw.Animator'
  21300. ],
  21301. config: {
  21302. /**
  21303. * @cfg {'top'/'left'/'right'/'bottom'} docked
  21304. * The position of the legend in the chart.
  21305. */
  21306. docked: 'bottom',
  21307. /**
  21308. * @cfg {Ext.chart.legend.store.Store} store
  21309. * The {@link Ext.chart.legend.store.Store} to bind this legend to.
  21310. * @private
  21311. */
  21312. store: null,
  21313. /**
  21314. * @cfg {Ext.chart.AbstractChart} chart
  21315. * The chart that the store belongs to.
  21316. */
  21317. chart: null,
  21318. /**
  21319. * @cfg {Ext.draw.Surface} surface
  21320. * The chart surface used to render legend sprites.
  21321. * @protected
  21322. */
  21323. surface: null,
  21324. /**
  21325. * @cfg {Object} size
  21326. * The size of the area occupied by the legend's sprites.
  21327. * This is set by the legend itself and then used during chart layout
  21328. * to make sure the 'legend' surface is big enough to accommodate
  21329. * legend sprites.
  21330. * @cfg {Number} size.width
  21331. * @cfg {Number} size.height
  21332. * @readonly
  21333. */
  21334. size: {
  21335. width: 0,
  21336. height: 0
  21337. },
  21338. /**
  21339. * @cfg {Boolean} toggleable
  21340. * `true` to allow series items to have their visibility
  21341. * toggled by interaction with the legend items.
  21342. */
  21343. toggleable: true,
  21344. /**
  21345. * @cfg {Number} padding
  21346. * The padding amount between legend items and legend border.
  21347. */
  21348. padding: 10,
  21349. label: {
  21350. preciseMeasurement: true
  21351. },
  21352. /**
  21353. * The sprite to use as a legend item marker. By default a corresponding series
  21354. * marker is used. If the series has no marker, the `circle` sprite
  21355. * is used as a legend item marker, where its `fillStyle`, `strokeStyle` and
  21356. * `lineWidth` match that of the series. The size of a legend item marker is
  21357. * controlled by the `size` property, which to defaults to `10` (pixels).
  21358. */
  21359. marker: {},
  21360. /**
  21361. * @cfg {Object} border
  21362. * The border that goes around legend item sprites.
  21363. * The type of the sprite is determined by this config,
  21364. * while the styling comes from a theme {@link Ext.chart.theme.Base #legend}.
  21365. * If both this config and the theme provide values for the
  21366. * same configs, the values from this config are used.
  21367. * The sprite class used a legend border should have the `isLegendBorder`
  21368. * property set to true on the prototype. The legend border sprite
  21369. * should also have the `x`, `y`, `width` and `height` attributes
  21370. * that determine it's position and dimensions.
  21371. */
  21372. border: {
  21373. $value: {
  21374. type: 'legendborder'
  21375. },
  21376. // The config should be processed at the time of the 'getSprites' call,
  21377. // when we already have the legend surface, otherwise the border sprite
  21378. // will not be added to the surface.
  21379. lazy: true
  21380. },
  21381. /**
  21382. * @cfg {Object} background
  21383. * Sets the legend background.
  21384. * This can be a gradient object, image, or color. This config works similarly
  21385. * to the {@link Ext.chart.AbstractChart#background} config.
  21386. */
  21387. background: null,
  21388. /**
  21389. * @cfg {Boolean} hidden Toggles the visibility of the legend.
  21390. */
  21391. hidden: false
  21392. },
  21393. sprites: null,
  21394. spriteZIndexes: {
  21395. background: 0,
  21396. border: 1,
  21397. // Item sprites should have a higher zIndex than border,
  21398. // or they won't react to clicks.
  21399. item: 2
  21400. },
  21401. dockedValues: {
  21402. left: true,
  21403. right: true,
  21404. top: true,
  21405. bottom: true
  21406. },
  21407. constructor: function(config) {
  21408. var me = this;
  21409. me.oldSize = {
  21410. width: 0,
  21411. height: 0
  21412. };
  21413. me.getId();
  21414. me.mixins.observable.constructor.call(me, config);
  21415. },
  21416. applyStore: function(store) {
  21417. return store && Ext.StoreManager.lookup(store);
  21418. },
  21419. updateStore: function(store, oldStore) {
  21420. var me = this;
  21421. if (oldStore) {
  21422. oldStore.un('datachanged', me.onDataChanged, me);
  21423. oldStore.un('update', me.onDataUpdate, me);
  21424. }
  21425. if (store) {
  21426. store.on('datachanged', me.onDataChanged, me);
  21427. store.on('update', me.onDataUpdate, me);
  21428. me.onDataChanged(store);
  21429. }
  21430. me.performLayout();
  21431. },
  21432. //<debug>
  21433. applyDocked: function(docked) {
  21434. if (!(docked in this.dockedValues)) {
  21435. Ext.raise("Invalid 'docked' config value.");
  21436. }
  21437. return docked;
  21438. },
  21439. //</debug>
  21440. updateDocked: function(docked) {
  21441. this.isTop = docked === 'top';
  21442. if (!this.isConfiguring) {
  21443. this.layoutChart();
  21444. }
  21445. },
  21446. updateHidden: function(hidden) {
  21447. var surface;
  21448. this.getChart();
  21449. // 'chart' updater will set the surface
  21450. surface = this.getSurface();
  21451. if (surface) {
  21452. surface.setHidden(hidden);
  21453. }
  21454. if (!this.isConfiguring) {
  21455. this.layoutChart();
  21456. }
  21457. },
  21458. /**
  21459. * @private
  21460. */
  21461. layoutChart: function() {
  21462. var chart;
  21463. if (!this.isConfiguring) {
  21464. chart = this.getChart();
  21465. if (chart) {
  21466. chart.scheduleLayout();
  21467. }
  21468. }
  21469. },
  21470. /**
  21471. * @private
  21472. * Calculates and returns the legend surface rect and adjusts the passed `chartRect`
  21473. * accordingly. The first time this is called, the `SpriteLegend` will have zero size
  21474. * (no width or height).
  21475. * @param {Number[]} chartRect [left, top, width, height] components as an array.
  21476. * @return {Number[]} [left, top, width, height] components as an array, or null.
  21477. */
  21478. computeRect: function(chartRect) {
  21479. var rect, docked, size, height, width;
  21480. if (this.getHidden()) {
  21481. return null;
  21482. }
  21483. rect = [
  21484. 0,
  21485. 0,
  21486. 0,
  21487. 0
  21488. ];
  21489. docked = this.getDocked();
  21490. size = this.getSize();
  21491. height = size.height;
  21492. width = size.width;
  21493. switch (docked) {
  21494. case 'top':
  21495. rect[1] = chartRect[1];
  21496. rect[2] = chartRect[2];
  21497. rect[3] = height;
  21498. chartRect[1] += height;
  21499. chartRect[3] -= height;
  21500. break;
  21501. case 'bottom':
  21502. chartRect[3] -= height;
  21503. rect[1] = chartRect[3];
  21504. rect[2] = chartRect[2];
  21505. rect[3] = height;
  21506. break;
  21507. case 'left':
  21508. chartRect[0] += width;
  21509. chartRect[2] -= width;
  21510. rect[2] = width;
  21511. rect[3] = chartRect[3];
  21512. break;
  21513. case 'right':
  21514. chartRect[2] -= width;
  21515. rect[0] = chartRect[2];
  21516. rect[2] = width;
  21517. rect[3] = chartRect[3];
  21518. break;
  21519. }
  21520. return rect;
  21521. },
  21522. applyBorder: function(config) {
  21523. var border;
  21524. if (config) {
  21525. if (config.isSprite) {
  21526. border = config;
  21527. } else {
  21528. border = Ext.create('sprite.' + config.type, config);
  21529. }
  21530. }
  21531. if (border) {
  21532. border.isLegendBorder = true;
  21533. border.setAttributes({
  21534. zIndex: this.spriteZIndexes.border
  21535. });
  21536. }
  21537. return border;
  21538. },
  21539. updateBorder: function(border, oldBorder) {
  21540. var surface = this.getSurface();
  21541. this.borderSprite = null;
  21542. if (surface) {
  21543. if (oldBorder) {
  21544. surface.remove(oldBorder);
  21545. }
  21546. if (border) {
  21547. this.borderSprite = surface.add(border);
  21548. }
  21549. }
  21550. },
  21551. scheduleLayout: function() {
  21552. if (!this.scheduledLayoutId) {
  21553. this.scheduledLayoutId = Ext.draw.Animator.schedule('performLayout', this);
  21554. }
  21555. },
  21556. cancelLayout: function() {
  21557. Ext.draw.Animator.cancel(this.scheduledLayoutId);
  21558. this.scheduledLayoutId = null;
  21559. },
  21560. performLayout: function() {
  21561. var me = this,
  21562. size = me.getSize(),
  21563. gap = me.getPadding(),
  21564. sprites = me.getSprites(),
  21565. surface = me.getSurface(),
  21566. background = me.getBackground(),
  21567. surfaceRect = surface.getRect(),
  21568. store = me.getStore(),
  21569. ln = (sprites && sprites.length) || 0,
  21570. i, sprite;
  21571. if (!surface || !surfaceRect || !store) {
  21572. return false;
  21573. }
  21574. me.cancelLayout();
  21575. // eslint-disable-next-line vars-on-top, one-var
  21576. var docked = me.getDocked(),
  21577. surfaceWidth = surfaceRect[2],
  21578. surfaceHeight = surfaceRect[3],
  21579. border = me.borderSprite,
  21580. bboxes = [],
  21581. startX, // Coordinates of the top-left corner.
  21582. startY, // of the first 'legenditem' sprite.
  21583. columnSize, // Number of items in a column.
  21584. columnCount, // Number of columns.
  21585. columnWidth, itemsWidth, itemsHeight, paddedItemsWidth, // The horizontal span of all 'legenditem' sprites.
  21586. paddedItemsHeight, // The vertical span of all 'legenditem' sprites.
  21587. paddedBorderWidth, paddedBorderHeight, // eslint-disable-line no-unused-vars
  21588. itemHeight, bbox, x, y;
  21589. for (i = 0; i < ln; i++) {
  21590. sprite = sprites[i];
  21591. bbox = sprite.getBBox();
  21592. bboxes.push(bbox);
  21593. }
  21594. if (bbox) {
  21595. itemHeight = bbox.height;
  21596. }
  21597. switch (docked) {
  21598. /*
  21599. Horizontal legend.
  21600. The outer box is the legend surface.
  21601. The inner box is the legend border.
  21602. There's a fixed amount of padding between all the items,
  21603. denoted by ##. This amount is controlled by the 'padding' config
  21604. of the legend.
  21605. |-------------------------------------------------------------|
  21606. | ## |
  21607. | |---------------------------------------------------| |
  21608. | | ## ## ## | |
  21609. | | -------- ----------- -------- | |
  21610. | ## | ## | Item 0 | ## | Item 2 | ## | Item 4 | ## | ## |
  21611. | | -------- ----------- -------- | |
  21612. | | ## ## ## | |
  21613. | | ---------- --------- | |
  21614. | | ## | Item 1 | ## | Item 3 | | |
  21615. | | ---------- --------- | |
  21616. | | ## ## | |
  21617. | |---------------------------------------------------| |
  21618. | ## |
  21619. |-------------------------------------------------------------|
  21620. */
  21621. case 'bottom':
  21622. case 'top':
  21623. // surface must have a width before we can proceed to layout top/bottom
  21624. // docked legend. width may be 0 if we are rendered into an inactive tab.
  21625. // see https://sencha.jira.com/browse/EXTJS-22454
  21626. if (!surfaceWidth) {
  21627. return false;
  21628. };
  21629. columnSize = 0;
  21630. // Split legend items into columns until the width is suitable.
  21631. do {
  21632. itemsWidth = 0;
  21633. columnWidth = 0;
  21634. columnCount = 0;
  21635. columnSize++;
  21636. for (i = 0; i < ln; i++) {
  21637. bbox = bboxes[i];
  21638. if (bbox.width > columnWidth) {
  21639. columnWidth = bbox.width;
  21640. columnWidth = Math.min(columnWidth, surfaceWidth - (gap * 4));
  21641. }
  21642. if ((i + 1) % columnSize === 0) {
  21643. itemsWidth += columnWidth;
  21644. columnWidth = 0;
  21645. columnCount++;
  21646. }
  21647. }
  21648. if (i % columnSize !== 0) {
  21649. itemsWidth += columnWidth;
  21650. columnCount++;
  21651. }
  21652. paddedItemsWidth = itemsWidth + (columnCount - 1) * gap;
  21653. paddedBorderWidth = paddedItemsWidth + gap * 4;
  21654. } while (// if the column width is greater than available surface width,
  21655. // set it to maximum available width
  21656. paddedBorderWidth > surfaceWidth);
  21657. paddedItemsHeight = itemHeight * columnSize + (columnSize - 1) * gap;
  21658. break;
  21659. /*
  21660. Vertical legend.
  21661. |-----------------------------------------------|
  21662. | ## |
  21663. | |-------------------------------------| |
  21664. | | ## ## | |
  21665. | | -------- ----------- | |
  21666. | | ## | Item 0 | ## | Item 1 | ## | |
  21667. | | -------- ----------- | |
  21668. | | ## ## | |
  21669. | | ---------- --------- | |
  21670. | ## | ## | Item 2 | ## | Item 3 | | ## |
  21671. | | ---------- --------- | |
  21672. | | ## | |
  21673. | | -------- | |
  21674. | | ## | Item 4 | | |
  21675. | | -------- | |
  21676. | | ## | |
  21677. | |-------------------------------------| |
  21678. | ## |
  21679. |-----------------------------------------------|
  21680. */
  21681. case 'right':
  21682. case 'left':
  21683. // surface must have a height before we can proceed to layout right/left
  21684. // docked legend. height may be 0 if we are rendered into an inactive tab.
  21685. // see https://sencha.jira.com/browse/EXTJS-22454
  21686. if (!surfaceHeight) {
  21687. return false;
  21688. };
  21689. columnSize = ln * 2;
  21690. // Split legend items into columns until the height is suitable.
  21691. do {
  21692. columnSize = (columnSize >> 1) + (columnSize % 2);
  21693. itemsWidth = 0;
  21694. itemsHeight = 0;
  21695. columnWidth = 0;
  21696. columnCount = 0;
  21697. for (i = 0; i < ln; i++) {
  21698. bbox = bboxes[i];
  21699. if (!columnCount) {
  21700. itemsHeight += bbox.height;
  21701. }
  21702. if (bbox.width > columnWidth) {
  21703. columnWidth = bbox.width;
  21704. }
  21705. if ((i + 1) % columnSize === 0) {
  21706. itemsWidth += columnWidth;
  21707. columnWidth = 0;
  21708. columnCount++;
  21709. }
  21710. }
  21711. if (i % columnSize !== 0) {
  21712. itemsWidth += columnWidth;
  21713. columnCount++;
  21714. }
  21715. paddedItemsWidth = itemsWidth + (columnCount - 1) * gap;
  21716. paddedItemsHeight = itemsHeight + (columnSize - 1) * gap;
  21717. paddedBorderWidth = paddedItemsWidth + gap * 4;
  21718. paddedBorderHeight = paddedItemsHeight + gap * 4;
  21719. } while (// Integer division by 2, plus remainder.
  21720. // itemsHeight is determined by the height of the first column.
  21721. paddedItemsHeight > surfaceHeight);
  21722. break;
  21723. }
  21724. startX = (surfaceWidth - paddedItemsWidth) / 2;
  21725. startY = (surfaceHeight - paddedItemsHeight) / 2;
  21726. x = 0;
  21727. y = 0;
  21728. columnWidth = 0;
  21729. for (i = 0; i < ln; i++) {
  21730. sprite = sprites[i];
  21731. bbox = bboxes[i];
  21732. sprite.setAttributes({
  21733. translationX: startX + x,
  21734. translationY: startY + y
  21735. });
  21736. if (bbox.width > columnWidth) {
  21737. columnWidth = bbox.width;
  21738. }
  21739. if ((i + 1) % columnSize === 0) {
  21740. x += columnWidth + gap;
  21741. y = 0;
  21742. columnWidth = 0;
  21743. } else {
  21744. y += bbox.height + gap;
  21745. }
  21746. }
  21747. if (border) {
  21748. border.setAttributes({
  21749. hidden: !ln,
  21750. x: startX - gap,
  21751. y: startY - gap,
  21752. width: paddedItemsWidth + gap * 2,
  21753. height: paddedItemsHeight + gap * 2
  21754. });
  21755. }
  21756. size.width = border.attr.width + gap * 2;
  21757. size.height = border.attr.height + gap * 2;
  21758. if (size.width !== me.oldSize.width || size.height !== me.oldSize.height) {
  21759. // Do not simply assign size to oldSize, as we want them to be
  21760. // separate objects.
  21761. Ext.apply(me.oldSize, size);
  21762. // Legend size has changed, so we return 'false' to cancel the current
  21763. // chart layout (this method is called by chart's 'performLayout' method)
  21764. // and manually start a new chart layout.
  21765. me.getChart().scheduleLayout();
  21766. return false;
  21767. }
  21768. if (background) {
  21769. me.resizeBackground(surface, background);
  21770. }
  21771. surface.renderFrame();
  21772. return true;
  21773. },
  21774. // Doesn't include the border sprite which also belongs to the 'legend'
  21775. // surface. To get it, use the 'getBorder' method.
  21776. getSprites: function() {
  21777. this.updateSprites();
  21778. return this.sprites;
  21779. },
  21780. /**
  21781. * @private
  21782. * Creates a 'legenditem' sprite in the given surface
  21783. * using the legend store record data provided.
  21784. * @param {Ext.draw.Surface} surface
  21785. * @param {Ext.chart.legend.store.Item} record
  21786. * @return {Ext.chart.legend.sprite.Item}
  21787. */
  21788. createSprite: function(surface, record) {
  21789. var me = this,
  21790. data = record.data,
  21791. chart = me.getChart(),
  21792. series = chart.get(data.series),
  21793. seriesMarker = series.getMarker(),
  21794. sprite = null,
  21795. markerConfig, labelConfig, legendItemConfig;
  21796. if (surface) {
  21797. markerConfig = series.getMarkerStyleByIndex(data.index);
  21798. markerConfig.fillStyle = data.mark;
  21799. markerConfig.hidden = false;
  21800. if (seriesMarker && seriesMarker.type) {
  21801. markerConfig.type = seriesMarker.type;
  21802. }
  21803. Ext.apply(markerConfig, me.getMarker());
  21804. markerConfig.surface = surface;
  21805. labelConfig = me.getLabel();
  21806. legendItemConfig = {
  21807. type: 'legenditem',
  21808. zIndex: me.spriteZIndexes.item,
  21809. text: data.name,
  21810. enabled: !data.disabled,
  21811. marker: markerConfig,
  21812. label: labelConfig,
  21813. series: data.series,
  21814. record: record
  21815. };
  21816. sprite = surface.add(legendItemConfig);
  21817. }
  21818. return sprite;
  21819. },
  21820. /**
  21821. * @private
  21822. * Creates legend item sprites and associates them with legend store records.
  21823. * Updates attributes of the sprites when legend store data changes.
  21824. */
  21825. updateSprites: function() {
  21826. var me = this,
  21827. chart = me.getChart(),
  21828. store = me.getStore(),
  21829. surface = me.getSurface(),
  21830. item, items, itemSprite, i, ln, sprites, unusedSprites, border;
  21831. if (!(chart && store && surface)) {
  21832. return;
  21833. }
  21834. me.sprites = sprites = me.sprites || [];
  21835. items = store.getData().items;
  21836. ln = items.length;
  21837. for (i = 0; i < ln; i++) {
  21838. item = items[i];
  21839. itemSprite = sprites[i];
  21840. if (itemSprite) {
  21841. me.updateSprite(itemSprite, item);
  21842. } else {
  21843. itemSprite = me.createSprite(surface, item);
  21844. surface.add(itemSprite);
  21845. sprites.push(itemSprite);
  21846. }
  21847. }
  21848. unusedSprites = Ext.Array.splice(sprites, i, sprites.length);
  21849. for (i = 0 , ln = unusedSprites.length; i < ln; i++) {
  21850. itemSprite = unusedSprites[i];
  21851. itemSprite.destroy();
  21852. }
  21853. border = me.getBorder();
  21854. if (border) {
  21855. me.borderSprite = border;
  21856. }
  21857. me.updateTheme(chart.getTheme());
  21858. },
  21859. /**
  21860. * @private
  21861. * Updates the given legend item sprite based on store record data.
  21862. * @param {Ext.chart.legend.sprite.Item} sprite
  21863. * @param {Ext.chart.legend.store.Item} record
  21864. */
  21865. updateSprite: function(sprite, record) {
  21866. var data = record.data,
  21867. chart = this.getChart(),
  21868. series = chart.get(data.series),
  21869. marker, label, markerConfig;
  21870. if (sprite) {
  21871. label = sprite.getLabel();
  21872. label.setAttributes({
  21873. text: data.name
  21874. });
  21875. sprite.setAttributes({
  21876. enabled: !data.disabled
  21877. });
  21878. sprite.setConfig({
  21879. series: data.series,
  21880. record: record
  21881. });
  21882. markerConfig = series.getMarkerStyleByIndex(data.index);
  21883. markerConfig.fillStyle = data.mark;
  21884. markerConfig.hidden = false;
  21885. Ext.apply(markerConfig, this.getMarker());
  21886. marker = sprite.getMarker();
  21887. marker.setAttributes({
  21888. fillStyle: markerConfig.fillStyle,
  21889. strokeStyle: markerConfig.strokeStyle
  21890. });
  21891. sprite.layoutUpdater(sprite.attr);
  21892. }
  21893. },
  21894. updateChart: function(newChart, oldChart) {
  21895. var me = this;
  21896. if (oldChart) {
  21897. me.setSurface(null);
  21898. }
  21899. if (newChart) {
  21900. me.setSurface(newChart.getSurface('legend'));
  21901. }
  21902. },
  21903. updateSurface: function(surface, oldSurface) {
  21904. if (oldSurface) {
  21905. oldSurface.el.un('click', 'onClick', this);
  21906. // The surface should not be destroyed here, just cleared.
  21907. // E.g. we may remove the sprite legend only to add another one.
  21908. oldSurface.removeAll(true);
  21909. }
  21910. if (surface) {
  21911. surface.isLegendSurface = true;
  21912. surface.el.on('click', 'onClick', this);
  21913. }
  21914. },
  21915. onClick: function(event) {
  21916. var chart = this.getChart(),
  21917. surface = this.getSurface(),
  21918. result, point;
  21919. if (chart && chart.hasFirstLayout && surface) {
  21920. point = surface.getEventXY(event);
  21921. result = surface.hitTest(point);
  21922. if (result && result.sprite) {
  21923. this.toggleItem(result.sprite);
  21924. }
  21925. }
  21926. },
  21927. applyBackground: function(newBackground, oldBackground) {
  21928. var me = this,
  21929. // It's important to get the `chart` first here,
  21930. // because the `surface` is set by the `chart` updater.
  21931. chart = me.getChart(),
  21932. surface = me.getSurface(),
  21933. background;
  21934. background = chart.refreshBackground(surface, newBackground, oldBackground);
  21935. if (background) {
  21936. background.setAttributes({
  21937. zIndex: me.spriteZIndexes.background
  21938. });
  21939. }
  21940. return background;
  21941. },
  21942. resizeBackground: function(surface, background) {
  21943. var width = background.attr.width,
  21944. height = background.attr.height,
  21945. surfaceRect = surface.getRect();
  21946. if (surfaceRect && (width !== surfaceRect[2] || height !== surfaceRect[3])) {
  21947. background.setAttributes({
  21948. width: surfaceRect[2],
  21949. height: surfaceRect[3]
  21950. });
  21951. }
  21952. },
  21953. themeableConfigs: {
  21954. background: true
  21955. },
  21956. updateTheme: function(theme) {
  21957. var me = this,
  21958. surface = me.getSurface(),
  21959. sprites = surface.getItems(),
  21960. legendTheme = theme.getLegend(),
  21961. labelConfig = me.getLabel(),
  21962. configs = me.self.getConfigurator().configs,
  21963. themeableConfigs = me.themeableConfigs,
  21964. themeOnlyIfConfigured = me.themeOnlyIfConfigured,
  21965. initialConfig = me.getInitialConfig(),
  21966. defaultConfig = me.defaultConfig,
  21967. value, cfg, isObjValue, isUnusedConfig, initialValue, sprite, style, labelSprite, key, attr, i, ln;
  21968. for (i = 0 , ln = sprites.length; i < ln; i++) {
  21969. sprite = sprites[i];
  21970. if (sprite.isLegendItem) {
  21971. style = legendTheme.label;
  21972. if (style) {
  21973. attr = null;
  21974. for (key in style) {
  21975. if (!(key in labelConfig)) {
  21976. attr = attr || {};
  21977. attr[key] = style[key];
  21978. }
  21979. }
  21980. if (attr) {
  21981. labelSprite = sprite.getLabel();
  21982. labelSprite.setAttributes(attr);
  21983. }
  21984. }
  21985. continue;
  21986. } else if (sprite.isLegendBorder) {
  21987. style = legendTheme.border;
  21988. } else {
  21989. continue;
  21990. }
  21991. if (style) {
  21992. attr = {};
  21993. for (key in style) {
  21994. if (!(key in sprite.config)) {
  21995. attr[key] = style[key];
  21996. }
  21997. }
  21998. sprite.setAttributes(attr);
  21999. }
  22000. }
  22001. value = legendTheme.background;
  22002. cfg = configs.background;
  22003. if (value !== null && value !== undefined && cfg) {}
  22004. // TODO
  22005. for (key in legendTheme) {
  22006. if (!(key in themeableConfigs)) {
  22007. continue;
  22008. }
  22009. value = legendTheme[key];
  22010. cfg = configs[key];
  22011. if (value !== null && value !== undefined && cfg) {
  22012. initialValue = initialConfig[key];
  22013. isObjValue = Ext.isObject(value);
  22014. isUnusedConfig = initialValue === defaultConfig[key];
  22015. if (isObjValue) {
  22016. if (isUnusedConfig && themeOnlyIfConfigured[key]) {
  22017. continue;
  22018. }
  22019. value = Ext.merge({}, value, initialValue);
  22020. }
  22021. if (isUnusedConfig || isObjValue) {
  22022. me[cfg.names.set](value);
  22023. }
  22024. }
  22025. }
  22026. },
  22027. onDataChanged: function(store) {
  22028. this.updateSprites();
  22029. this.scheduleLayout();
  22030. },
  22031. onDataUpdate: function(store, record) {
  22032. var me = this,
  22033. sprites = me.sprites,
  22034. ln = sprites.length,
  22035. i = 0,
  22036. sprite, spriteRecord, match;
  22037. for (; i < ln; i++) {
  22038. sprite = sprites[i];
  22039. spriteRecord = sprite.getRecord();
  22040. if (spriteRecord === record) {
  22041. match = sprite;
  22042. break;
  22043. }
  22044. }
  22045. if (match) {
  22046. me.updateSprite(match, record);
  22047. me.scheduleLayout();
  22048. }
  22049. },
  22050. toggleItem: function(sprite) {
  22051. var disabledCount = 0,
  22052. canToggle = true,
  22053. store, i, count, record, disabled;
  22054. if (!this.getToggleable() || !sprite.isLegendItem) {
  22055. return;
  22056. }
  22057. store = this.getStore();
  22058. if (store) {
  22059. count = store.getCount();
  22060. for (i = 0; i < count; i++) {
  22061. record = store.getAt(i);
  22062. if (record.get('disabled')) {
  22063. disabledCount++;
  22064. }
  22065. }
  22066. canToggle = count - disabledCount > 1;
  22067. record = sprite.getRecord();
  22068. if (record) {
  22069. disabled = record.get('disabled');
  22070. if (disabled || canToggle) {
  22071. // This will trigger AbstractChart.onLegendStoreUpdate.
  22072. record.set('disabled', !disabled);
  22073. sprite.setAttributes({
  22074. enabled: disabled
  22075. });
  22076. }
  22077. }
  22078. }
  22079. },
  22080. destroy: function() {
  22081. var me = this;
  22082. me.destroying = true;
  22083. me.cancelLayout();
  22084. me.setChart(null);
  22085. me.callParent();
  22086. }
  22087. });
  22088. /**
  22089. * Chart captions can be used to place titles, subtitles, credits and other captions
  22090. * inside a chart. Please see the chart's {@link Ext.chart.AbstractChart#captions}
  22091. * config documentation for the general description of the way captions work, and
  22092. * refer to the documentation of this class' configs for details.
  22093. */
  22094. Ext.define('Ext.chart.Caption', {
  22095. mixins: [
  22096. 'Ext.mixin.Observable',
  22097. 'Ext.mixin.Bindable'
  22098. ],
  22099. isCaption: true,
  22100. config: {
  22101. /**
  22102. * The weight controls the order in which the captions are created.
  22103. * Captions with lower weights are created first.
  22104. * This affects chart's layout. For example, if two captions are docked
  22105. * to the 'top', the one with the lower weight will end up on top
  22106. * of the other.
  22107. */
  22108. weight: 0,
  22109. /**
  22110. * @cfg {String} text
  22111. * The text displayed by the caption.
  22112. * Multi-line captions are allowed, e.g.:
  22113. *
  22114. * captions: {
  22115. * title: {
  22116. * text: 'India\'s tiger population\n'
  22117. * + 'from 1970 to 2015'
  22118. * }
  22119. * }
  22120. *
  22121. */
  22122. text: '',
  22123. /**
  22124. * @cfg {'left'/'center'/'right'} [align='center']
  22125. * Determines the horizontal alignment of the caption's text.
  22126. */
  22127. align: 'center',
  22128. /**
  22129. * @cfg {'series'/'chart'} [alignTo='series']
  22130. * Whether to align the caption to the 'series' (default) or the 'chart'.
  22131. */
  22132. alignTo: 'series',
  22133. /**
  22134. * @cfg {Number} padding
  22135. * The uniform padding applied to both top and bottom of the caption's text.
  22136. */
  22137. padding: 0,
  22138. /**
  22139. * @cfg {Boolean} [hidden=false]
  22140. * Controls the visibility of the caption.
  22141. */
  22142. hidden: false,
  22143. /**
  22144. * @cfg {'top'/'bottom'} [docked='top']
  22145. * The position of the caption in a chart.
  22146. */
  22147. docked: 'top',
  22148. /**
  22149. * @cfg {Object} style
  22150. * Style attributes for the caption's text.
  22151. * All attributes that are valid {@link Ext.draw.sprite.Text text sprite} attributes
  22152. * are valid here. However, only font attributes (such as `fontSize`, `fontFamily`, ...),
  22153. * color attributes (such as `fillStyle`) and `textAlign` attribute are guaranteed to
  22154. * produce correct behavior. For example, transform attributes are not officially supported.
  22155. */
  22156. style: {
  22157. fontSize: '14px',
  22158. fontWeight: 'bold',
  22159. fontFamily: 'Verdana, Aria, sans-serif'
  22160. },
  22161. /**
  22162. * @private
  22163. * @cfg {Ext.chart.AbstractChart} chart
  22164. * The chart the label belongs to.
  22165. */
  22166. chart: null,
  22167. /**
  22168. * @private
  22169. * The text sprite used to render caption's text.
  22170. */
  22171. sprite: {
  22172. type: 'text',
  22173. preciseMeasurement: true,
  22174. zIndex: 10
  22175. },
  22176. //<debug>
  22177. /**
  22178. * @private
  22179. * @cfg {Boolean} debug
  22180. * Whether to show the bounding boxes or not.
  22181. */
  22182. debug: false,
  22183. //</debug>
  22184. /**
  22185. * @private
  22186. * The logical rect of the caption in the `surfaceName` surface.
  22187. */
  22188. rect: null
  22189. },
  22190. surfaceName: 'caption',
  22191. constructor: function(config) {
  22192. var me = this,
  22193. id;
  22194. if ('id' in config) {
  22195. id = config.id;
  22196. } else if ('id' in me.config) {
  22197. id = me.config.id;
  22198. } else {
  22199. id = me.getId();
  22200. }
  22201. me.setId(id);
  22202. me.mixins.observable.constructor.call(me, config);
  22203. me.initBindable();
  22204. },
  22205. updateChart: function() {
  22206. if (!this.isConfiguring) {
  22207. // Re-create caption's sprite in another chart.
  22208. this.setSprite({
  22209. type: 'text'
  22210. });
  22211. }
  22212. },
  22213. applySprite: function(sprite) {
  22214. var me = this,
  22215. chart = me.getChart(),
  22216. surface = me.surface = chart.getSurface(me.surfaceName);
  22217. //<debug>
  22218. me.rectSprite = surface.add({
  22219. type: 'rect',
  22220. fillStyle: 'yellow',
  22221. strokeStyle: 'red'
  22222. });
  22223. //</debug>
  22224. return sprite && surface.add(sprite);
  22225. },
  22226. updateSprite: function(sprite, oldSprite) {
  22227. if (oldSprite) {
  22228. oldSprite.destroy();
  22229. }
  22230. },
  22231. updateText: function(text) {
  22232. this.getSprite().setAttributes({
  22233. text: text
  22234. });
  22235. },
  22236. updateStyle: function(style) {
  22237. this.getSprite().setAttributes(style);
  22238. },
  22239. //<debug>
  22240. updateDebug: function(debug) {
  22241. var me = this,
  22242. sprite = me.getSprite();
  22243. if (debug && !me.rectSprite) {
  22244. me.rectSprite = me.surface.add({
  22245. type: 'rect',
  22246. fillStyle: 'yellow',
  22247. strokeStyle: 'red'
  22248. });
  22249. }
  22250. if (sprite) {
  22251. sprite.setAttributes({
  22252. debug: debug ? {
  22253. bbox: true
  22254. } : null
  22255. });
  22256. }
  22257. if (me.rectSprite) {
  22258. me.rectSprite.setAttributes({
  22259. hidden: !debug
  22260. });
  22261. }
  22262. if (!me.isConfiguring) {
  22263. me.surface.renderFrame();
  22264. }
  22265. },
  22266. //</debug>
  22267. updateRect: function(rect) {
  22268. if (this.rectSprite) {
  22269. this.rectSprite.setAttributes({
  22270. x: rect[0],
  22271. y: rect[1],
  22272. width: rect[2],
  22273. height: rect[3]
  22274. });
  22275. }
  22276. },
  22277. updateDocked: function() {
  22278. var chart = this.getChart();
  22279. if (chart && !this.isConfiguring) {
  22280. chart.scheduleLayout();
  22281. }
  22282. },
  22283. /**
  22284. * @private
  22285. * Computes and sets the caption's rect.
  22286. * Shrinks the given chart rect to accomodate the caption.
  22287. * The chart rect is [top, left, width, height] in chart's
  22288. * body element coordinates.
  22289. * The shrink rect is {left, top, right, bottom} in `caption`
  22290. * surface coordinates.
  22291. */
  22292. computeRect: function(chartRect, shrinkRect) {
  22293. if (this.getHidden()) {
  22294. return null;
  22295. }
  22296. // eslint-disable-next-line vars-on-top
  22297. var rect = [
  22298. 0,
  22299. 0,
  22300. chartRect[2],
  22301. 0
  22302. ],
  22303. docked = this.getDocked(),
  22304. padding = this.getPadding(),
  22305. textSize = this.getSprite().getBBox(),
  22306. height = textSize.height + padding * 2;
  22307. switch (docked) {
  22308. case 'top':
  22309. rect[1] = shrinkRect.top;
  22310. rect[3] = height;
  22311. chartRect[1] += height;
  22312. chartRect[3] -= height;
  22313. shrinkRect.top += height;
  22314. break;
  22315. case 'bottom':
  22316. chartRect[3] -= height;
  22317. shrinkRect.bottom -= height;
  22318. rect[1] = shrinkRect.bottom;
  22319. rect[3] = height;
  22320. break;
  22321. }
  22322. this.setRect(rect);
  22323. },
  22324. alignRect: function(seriesRect) {
  22325. var surfaceRect = this.surface.getRect(),
  22326. rect = this.getRect();
  22327. rect[0] = seriesRect[0] - surfaceRect[0];
  22328. rect[2] = seriesRect[2];
  22329. // Slice to trigger the applier/updater.
  22330. this.setRect(rect.slice());
  22331. },
  22332. performLayout: function() {
  22333. var me = this,
  22334. rect = me.getRect(),
  22335. x = rect[0],
  22336. y = rect[1],
  22337. width = rect[2],
  22338. height = rect[3],
  22339. sprite = me.getSprite(),
  22340. tx = sprite.attr.translationX,
  22341. ty = sprite.attr.translationY,
  22342. bbox = sprite.getBBox(),
  22343. align = me.getAlign(),
  22344. dx, dy;
  22345. switch (align) {
  22346. case 'left':
  22347. dx = x - bbox.x;
  22348. break;
  22349. case 'right':
  22350. dx = (x + width) - (bbox.x + bbox.width);
  22351. break;
  22352. case 'center':
  22353. dx = x + (width - bbox.width) / 2 - bbox.x;
  22354. break;
  22355. }
  22356. dy = y + (height - bbox.height) / 2 - bbox.y;
  22357. sprite.setAttributes({
  22358. translationX: tx + dx,
  22359. translationY: ty + dy
  22360. });
  22361. },
  22362. destroy: function() {
  22363. var me = this;
  22364. //<debug>
  22365. if (me.rectSprite) {
  22366. me.rectSprite.destroy();
  22367. }
  22368. //</debug>
  22369. me.getSprite().destroy();
  22370. me.callParent();
  22371. }
  22372. });
  22373. /**
  22374. * The data model for legend items.
  22375. */
  22376. Ext.define('Ext.chart.legend.store.Item', {
  22377. extend: 'Ext.data.Model',
  22378. fields: [
  22379. 'id',
  22380. 'name',
  22381. // The series title.
  22382. 'mark',
  22383. // The color of the series.
  22384. 'disabled',
  22385. // The state of the series.
  22386. 'series',
  22387. // A reference to the series instance.
  22388. // A sprite index, e.g. for stacked or pie series.
  22389. // For such series an individual component of the series
  22390. // is hidden or shown when the legend item is toggled.
  22391. 'index'
  22392. ]
  22393. });
  22394. /**
  22395. * The store type used for legend items.
  22396. */
  22397. Ext.define('Ext.chart.legend.store.Store', {
  22398. extend: 'Ext.data.Store',
  22399. requires: [
  22400. 'Ext.chart.legend.store.Item'
  22401. ],
  22402. model: 'Ext.chart.legend.store.Item',
  22403. isLegendStore: true,
  22404. config: {
  22405. autoDestroy: true
  22406. }
  22407. });
  22408. /**
  22409. * The Ext.chart package provides the capability to visualize data.
  22410. * Each chart binds directly to a {@link Ext.data.Store store} enabling automatic
  22411. * updates of the chart. A chart configuration object has some overall styling
  22412. * options as well as an array of axes and series. A chart instance example could
  22413. * look like this:
  22414. *
  22415. * Ext.create('Ext.chart.CartesianChart', {
  22416. * width: 800,
  22417. * height: 600,
  22418. * animation: {
  22419. * easing: 'backOut',
  22420. * duration: 500
  22421. * },
  22422. * store: store1,
  22423. * legend: {
  22424. * position: 'right'
  22425. * },
  22426. * axes: [
  22427. * // ...some axes options...
  22428. * ],
  22429. * series: [
  22430. * // ...some series options...
  22431. * ]
  22432. * });
  22433. *
  22434. * In this example we set the `width` and `height` of a chart; We decide whether
  22435. * our series are animated or not and we select a store to be bound to the chart;
  22436. * We also set the legend to the right part of the chart.
  22437. *
  22438. * You can register certain interactions such as {@link Ext.chart.interactions.PanZoom}
  22439. * on the chart by specifying an array of names or more specific config objects.
  22440. * All the events will be wired automatically.
  22441. *
  22442. * You can also listen to series `itemXXX` events on both chart and series level.
  22443. *
  22444. * For example:
  22445. *
  22446. * Ext.create('Ext.chart.CartesianChart', {
  22447. * plugins: {
  22448. * chartitemevents: {
  22449. * moveEvents: true
  22450. * }
  22451. * },
  22452. * store: {
  22453. * fields: ['pet', 'households', 'total'],
  22454. * data: [
  22455. * {pet: 'Cats', households: 38, total: 93},
  22456. * {pet: 'Dogs', households: 45, total: 79},
  22457. * {pet: 'Fish', households: 13, total: 171}
  22458. * ]
  22459. * },
  22460. * axes: [{
  22461. * type: 'numeric',
  22462. * position: 'left'
  22463. * }, {
  22464. * type: 'category',
  22465. * position: 'bottom'
  22466. * }],
  22467. * series: [{
  22468. * type: 'bar',
  22469. * xField: 'pet',
  22470. * yField: 'households',
  22471. * listeners: {
  22472. * itemmousemove: function (series, item, event) {
  22473. * console.log('itemmousemove', item.category, item.field);
  22474. * }
  22475. * }
  22476. * }, {
  22477. * type: 'line',
  22478. * xField: 'pet',
  22479. * yField: 'total',
  22480. * marker: true
  22481. * }],
  22482. * listeners: { // Listen to itemclick events on all series.
  22483. * itemclick: function (chart, item, event) {
  22484. * console.log('itemclick', item.category, item.field);
  22485. * }
  22486. * }
  22487. * });
  22488. *
  22489. * Important! It's generally a poor design choice to put interactive charts
  22490. * inside scrollable views, in such cases it's not possible to tell
  22491. * which component should respond to the interaction.
  22492. * Since charts are typically interactive their default touch action config
  22493. * looks as follows: {@link Ext.draw.Container#touchAction}.
  22494. * If you do have a chart inside a scrollable view, even if it has no interactions,
  22495. * you have to set its `touchAction` config to the following:
  22496. *
  22497. * touchAction: {
  22498. * panX: true,
  22499. * panY: true
  22500. * }
  22501. *
  22502. * Otherwise, if a touch action started on a chart, a swipe will not scroll
  22503. * the view.
  22504. *
  22505. * For more information about the axes and series configurations please check
  22506. * the documentation of each series (Line, Bar, Pie, etc).
  22507. *
  22508. */
  22509. Ext.define('Ext.chart.AbstractChart', {
  22510. extend: 'Ext.draw.Container',
  22511. requires: [
  22512. 'Ext.chart.theme.Default',
  22513. 'Ext.chart.series.Series',
  22514. 'Ext.chart.interactions.Abstract',
  22515. 'Ext.chart.axis.Axis',
  22516. 'Ext.chart.Util',
  22517. 'Ext.data.StoreManager',
  22518. 'Ext.chart.legend.Legend',
  22519. 'Ext.chart.legend.SpriteLegend',
  22520. 'Ext.chart.Caption',
  22521. 'Ext.chart.legend.store.Store',
  22522. 'Ext.data.Store'
  22523. ],
  22524. isChart: true,
  22525. defaultBindProperty: 'store',
  22526. /**
  22527. * @event beforerefresh
  22528. * Fires before a refresh to the chart data is called. If the `beforerefresh`
  22529. * handler returns `false` the {@link #refresh} action will be canceled.
  22530. * @param {Ext.chart.AbstractChart} this
  22531. */
  22532. /**
  22533. * @event refresh
  22534. * Fires after the chart data has been refreshed.
  22535. * @param {Ext.chart.AbstractChart} this
  22536. */
  22537. /**
  22538. * @event redraw
  22539. * Fires after each {@link #event!redraw} call.
  22540. * @param {Ext.chart.AbstractChart} this
  22541. */
  22542. /**
  22543. * @private
  22544. * @event layout
  22545. * Fires after the final layout is done.
  22546. * (Two layouts may be required to fully render a chart.
  22547. * Typically for the initial render and every time thickness
  22548. * of the chart's axes changes.)
  22549. * @param {Ext.chart.AbstractChart} this
  22550. */
  22551. /**
  22552. * @event itemhighlight
  22553. * Fires when an item is highlighted.
  22554. * @param {Ext.chart.AbstractChart} this
  22555. * @param {Object} newItem The new highlight item.
  22556. * @param {Object} oldItem The old highlight item.
  22557. */
  22558. /**
  22559. * @event itemhighlightchange
  22560. * Fires when an item's highlight changes.
  22561. * @param this
  22562. * @param {Object} newItem The new highlight item.
  22563. * @param {Object} oldItem The old highlight item.
  22564. */
  22565. /**
  22566. * @event itemmousemove
  22567. * Fires when the mouse is moved on a series item.
  22568. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22569. * plugin be added to the chart.
  22570. * @param {Ext.chart.AbstractChart} chart
  22571. * @param {Object} item
  22572. * @param {Event} event
  22573. */
  22574. /**
  22575. * @event itemmouseup
  22576. * Fires when a mouseup event occurs on a series item.
  22577. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22578. * plugin be added to the chart.
  22579. * @param {Ext.chart.AbstractChart} chart
  22580. * @param {Object} item
  22581. * @param {Event} event
  22582. */
  22583. /**
  22584. * @event itemmousedown
  22585. * Fires when a mousedown event occurs on a series item.
  22586. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22587. * plugin be added to the chart.
  22588. * @param {Ext.chart.AbstractChart} chart
  22589. * @param {Object} item
  22590. * @param {Event} event
  22591. */
  22592. /**
  22593. * @event itemmouseover
  22594. * Fires when the mouse enters a series item.
  22595. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22596. * plugin be added to the chart.
  22597. * @param {Ext.chart.AbstractChart} chart
  22598. * @param {Object} item
  22599. * @param {Event} event
  22600. */
  22601. /**
  22602. * @event itemmouseout
  22603. * Fires when the mouse exits a series item.
  22604. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22605. * plugin be added to the chart.
  22606. * @param {Ext.chart.AbstractChart} chart
  22607. * @param {Object} item
  22608. * @param {Event} event
  22609. */
  22610. /**
  22611. * @event itemclick
  22612. * Fires when a click event occurs on a series item.
  22613. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22614. * plugin be added to the chart.
  22615. * @param {Ext.chart.AbstractChart} chart
  22616. * @param {Object} item
  22617. * @param {Event} event
  22618. */
  22619. /**
  22620. * @event itemdblclick
  22621. * Fires when a double click event occurs on a series item.
  22622. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22623. * plugin be added to the chart.
  22624. * @param {Ext.chart.AbstractChart} chart
  22625. * @param {Object} item
  22626. * @param {Event} event
  22627. */
  22628. /**
  22629. * @event itemtap
  22630. * Fires when a tap event occurs on a series item.
  22631. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22632. * plugin be added to the chart.
  22633. * @param {Ext.chart.AbstractChart} chart
  22634. * @param {Object} item
  22635. * @param {Event} event
  22636. */
  22637. /**
  22638. * @event storechange
  22639. * Fires when the store of the chart changes.
  22640. * @param {Ext.chart.AbstractChart} chart
  22641. * @param {Ext.data.Store} newStore
  22642. * @param {Ext.data.Store} oldStore
  22643. */
  22644. config: {
  22645. /**
  22646. * @cfg {Ext.data.Store/String/Object} store
  22647. * The data source to which the chart is bound.
  22648. * Acceptable values for this property are:
  22649. *
  22650. * - **any {@link Ext.data.Store Store} class / subclass**
  22651. * - **an {@link Ext.data.Store#storeId ID of a store}**
  22652. * - **a {@link Ext.data.Store Store} config object**. When passing a config you can
  22653. * specify the store type by alias. Passing a config object with a store type will
  22654. * dynamically create a new store of that type when the chart is instantiated.
  22655. *
  22656. * For example:
  22657. *
  22658. * Ext.define('MyApp.store.Customer', {
  22659. * extend: 'Ext.data.Store',
  22660. * alias: 'store.customerstore',
  22661. *
  22662. * fields: ['name', 'value']
  22663. * });
  22664. *
  22665. *
  22666. * Ext.create({
  22667. * xtype: 'cartesian',
  22668. * renderTo: document.body,
  22669. * height: 400,
  22670. * width: 400,
  22671. * store: {
  22672. * type: 'customerstore',
  22673. * data: [{
  22674. * name: 'metric one',
  22675. * value: 10
  22676. * }]
  22677. * },
  22678. * axes: [{
  22679. * type: 'numeric',
  22680. * position: 'left',
  22681. * title: {
  22682. * text: 'Sample Values',
  22683. * fontSize: 15
  22684. * },
  22685. * fields: 'value'
  22686. * }, {
  22687. * type: 'category',
  22688. * position: 'bottom',
  22689. * title: {
  22690. * text: 'Sample Values',
  22691. * fontSize: 15
  22692. * },
  22693. * fields: 'name'
  22694. * }],
  22695. * series: {
  22696. * type: 'bar',
  22697. * xField: 'name',
  22698. * yField: 'value'
  22699. * }
  22700. * });
  22701. */
  22702. store: 'ext-empty-store',
  22703. /**
  22704. * @cfg {String} [theme="default"]
  22705. * The name of the theme to be used. A theme defines the colors and styles
  22706. * used by the series, axes, markers and other chart components.
  22707. * Please see the documentation for the {@link Ext.chart.theme.Base} class
  22708. * for more information.
  22709. *
  22710. * Possible theme values are:
  22711. * - 'green', 'sky', 'red', 'purple', 'blue', 'yellow'
  22712. * - 'category1' to 'category6'
  22713. * - and the above theme names with the '-gradients' suffix, e.g. 'green-gradients'
  22714. *
  22715. * IMPORTANT: You should require the themes you use; for example, to use:
  22716. *
  22717. * theme: 'blue'
  22718. *
  22719. * the `Ext.chart.theme.Blue` class should be required:
  22720. *
  22721. * requires: 'Ext.chart.theme.Blue'
  22722. *
  22723. * To require all chart themes:
  22724. *
  22725. * requires: 'Ext.chart.theme.*'
  22726. */
  22727. theme: 'default',
  22728. /**
  22729. * Chart captions can be used to place titles, subtitles, credits and other captions
  22730. * inside a chart. For example:
  22731. *
  22732. * captions: {
  22733. * title: {
  22734. * text: 'Consumer Price Index'
  22735. * },
  22736. * subtitle: {
  22737. * text: 'from 2007 to 2017'
  22738. * },
  22739. * credits: {
  22740. * text: 'Source: 'bls.gov'
  22741. * }
  22742. * }
  22743. *
  22744. * One can use any names for properties in the `captions` config, but the `title`,
  22745. * `subtitle` and `credits` ones have a special meaning - they are automatically
  22746. * themeable. The `title` and `subtitle` are automatically docked to the top of
  22747. * a chart and the `credits` to the bottom. The `title` uses the largest and
  22748. * the heaviest font, while the `credits` - the smallest and the lightest.
  22749. *
  22750. * Other captions besides those three can be easily defined as well:
  22751. *
  22752. * captions: {
  22753. * myFancyCaption: {
  22754. * docked: 'bottom',
  22755. * align: 'left',
  22756. * style: {
  22757. * fontSize: 18,
  22758. * fontWeight: 'bold',
  22759. * fontFamily: 'Verdana'
  22760. * }
  22761. * }
  22762. * }
  22763. *
  22764. * If a caption config only specifies text, a shorthand syntax is also possible:
  22765. *
  22766. * captions: {
  22767. * title: 'Consumer Price Index'
  22768. * }
  22769. *
  22770. * @cfg {Object} captions
  22771. * @cfg {Ext.chart.Caption} captions.title
  22772. * @cfg {Ext.chart.Caption} captions.subtitle
  22773. * @cfg {Ext.chart.Caption} captions.credits
  22774. */
  22775. captions: null,
  22776. /**
  22777. * @cfg {Object} style
  22778. * The style for the chart component.
  22779. */
  22780. style: null,
  22781. /**
  22782. * @cfg {Boolean/Object} [animation=true]
  22783. * Defaults to `easeInOut` easing with a 500ms duration.
  22784. * See {@link Ext.draw.modifier.Animation} for possible configuration options.
  22785. */
  22786. animation: !Ext.isIE8,
  22787. /**
  22788. * @cfg {Ext.chart.series.Series/Array} series
  22789. * Array of {@link Ext.chart.series.Series Series} instances or config objects.
  22790. * For example:
  22791. *
  22792. * series: [{
  22793. * type: 'column',
  22794. * axis: 'left',
  22795. * listeners: {
  22796. * 'afterrender': function() {
  22797. * console.log('afterrender');
  22798. * }
  22799. * },
  22800. * xField: 'category',
  22801. * yField: 'data1'
  22802. * }]
  22803. */
  22804. series: [],
  22805. /**
  22806. * @cfg {Ext.chart.axis.Axis/Array/Object} axes
  22807. * Array of {@link Ext.chart.axis.Axis Axis} instances or config objects.
  22808. * For example:
  22809. *
  22810. * axes: [{
  22811. * type: 'numeric',
  22812. * position: 'left',
  22813. * title: 'Number of Hits',
  22814. * minimum: 0
  22815. * }, {
  22816. * type: 'category',
  22817. * position: 'bottom',
  22818. * title: 'Month of the Year'
  22819. * }]
  22820. */
  22821. axes: [],
  22822. /**
  22823. * @cfg {Ext.chart.legend.Legend/Ext.chart.legend.SpriteLegend/Boolean} legend
  22824. * The legend config for the chart. If specified, a legend block will be shown
  22825. * next to the chart.
  22826. * Each legend item displays the {@link Ext.chart.series.Series#title title}
  22827. * of the series, the color of the series and allows to toggle the visibility
  22828. * of the series (at least one series should remain visible).
  22829. *
  22830. * Sencha Charts support two types of legends: sprite based and DOM based.
  22831. *
  22832. * The sprite based legend can be shown in chart {@link Ext.draw.Container#preview preview}
  22833. * and is a part of the downloaded {@link Ext.draw.Container#download chart image}.
  22834. * The sprite based legend is always displayed in full and takes as much space as necessary,
  22835. * the legend items are split into columns to use the available space efficiently.
  22836. * The sprite based legend is styled via a {@link Ext.chart.theme.Base chart theme}.
  22837. *
  22838. * The DOM based legend supports RTL.
  22839. * It occupies a fixed width or height and scrolls when the content overflows.
  22840. * The DOM based legend is styled via CSS rules.
  22841. *
  22842. * By default the sprite legend is used. The type can be explicitly specified:
  22843. *
  22844. * legend: {
  22845. * type: 'dom', // 'sprite' is another possible value
  22846. * docked: 'top'
  22847. * }
  22848. *
  22849. * If the legend config is set to `true`, the sprite legend will be used
  22850. * docked to the bottom.
  22851. */
  22852. legend: null,
  22853. /**
  22854. * @cfg {Array} colors
  22855. * Array of colors/gradients to override the color of items and legends.
  22856. */
  22857. colors: null,
  22858. /**
  22859. * @cfg {Object/Number/String} insetPadding
  22860. * The amount of inset padding in pixels for the chart.
  22861. * Inset padding is the padding from the boundary of the chart to any
  22862. * of its contents.
  22863. */
  22864. insetPadding: {
  22865. top: 10,
  22866. left: 10,
  22867. right: 10,
  22868. bottom: 10
  22869. },
  22870. /**
  22871. * @cfg {Object} background Set the chart background.
  22872. * This can be a gradient object, image, or color.
  22873. *
  22874. * For example, if `background` were to be a color we could set the object as
  22875. *
  22876. * background: '#ccc'
  22877. *
  22878. * You can specify an image by using:
  22879. *
  22880. * background: {
  22881. * type: 'image',
  22882. * src: 'http://path.to.image/'
  22883. * }
  22884. *
  22885. * Also you can specify a gradient by using the gradient object syntax:
  22886. *
  22887. * background: {
  22888. * type: 'linear',
  22889. * degrees: 0,
  22890. * stops: [
  22891. * {
  22892. * offset: 0,
  22893. * color: 'white'
  22894. * },
  22895. * {
  22896. * offset: 1,
  22897. * color: 'blue'
  22898. * }
  22899. * ]
  22900. * }
  22901. */
  22902. background: null,
  22903. /**
  22904. * @cfg {Array} interactions
  22905. * Interactions are optional modules that can be plugged in to a chart
  22906. * to allow the user to interact with the chart and its data in special ways.
  22907. * The `interactions` config takes an Array of Object configurations,
  22908. * each one corresponding to a particular interaction class identified
  22909. * by a `type` property:
  22910. *
  22911. * new Ext.chart.AbstractChart({
  22912. * renderTo: Ext.getBody(),
  22913. * width: 800,
  22914. * height: 600,
  22915. * store: store1,
  22916. * axes: [
  22917. * // ...some axes options...
  22918. * ],
  22919. * series: [
  22920. * // ...some series options...
  22921. * ],
  22922. * interactions: [{
  22923. * type: 'interactiontype'
  22924. * // ...additional configs for the interaction...
  22925. * }]
  22926. * });
  22927. *
  22928. * When adding an interaction which uses only its default configuration
  22929. * (no extra properties other than `type`), you can alternately specify
  22930. * only the type as a String rather than the full Object:
  22931. *
  22932. * interactions: ['reset', 'rotate']
  22933. *
  22934. * The current supported interaction types include:
  22935. *
  22936. * - {@link Ext.chart.interactions.PanZoom panzoom} - allows pan and zoom of axes
  22937. * - {@link Ext.chart.interactions.ItemHighlight itemhighlight} - allows highlighting
  22938. * of series data points
  22939. * - {@link Ext.chart.interactions.ItemInfo iteminfo} - allows displaying details of
  22940. * a data point in a popup panel
  22941. * - {@link Ext.chart.interactions.Rotate rotate} - allows rotation of pie and radar series
  22942. *
  22943. * See the documentation for each of those interaction classes to see how they
  22944. * can be configured.
  22945. *
  22946. * Additional custom interactions can be registered using `'interactions.'` alias prefix.
  22947. */
  22948. interactions: [],
  22949. /**
  22950. * @private
  22951. * The main area of the chart where grid and series are drawn.
  22952. */
  22953. mainRect: null,
  22954. /**
  22955. * @private
  22956. * Override value.
  22957. */
  22958. resizeHandler: null,
  22959. /**
  22960. * @cfg {Object} highlightItem
  22961. * The current highlight item in the chart.
  22962. * The object must be the one that you get from item events.
  22963. *
  22964. * Note that series can also own highlight items.
  22965. * This notion is separate from this one and should not be used at the same time.
  22966. */
  22967. highlightItem: null,
  22968. /* eslint-disable indent */
  22969. surfaceZIndexes: {
  22970. background: 0,
  22971. // Contains the backround 'rect' sprite.
  22972. main: 1,
  22973. // Contains grid lines and CrossZoom overlay 'rect' sprite.
  22974. grid: 2,
  22975. // Reserved.
  22976. series: 3,
  22977. // Contains series sprites.
  22978. axis: 4,
  22979. // No actual `axis` surface is created, but this zIndex is used
  22980. // for all axis surfaces (one surface is created per axis).
  22981. chart: 5,
  22982. // Covers whole chart, minus the legend area.
  22983. // Contains sprites defined in the `sprites` config,
  22984. // title, subtitle and credits.
  22985. caption: 6,
  22986. // Contains title, subtitle and credits sprites.
  22987. overlay: 7,
  22988. // This surface will typically contain chart labels
  22989. // and interaction sprites like crosshair lines.
  22990. // With cartesian charts, equivalent in size to the `series` surface.
  22991. // With polar charts, equivalent in size to the `chart` surface.
  22992. legend: 8
  22993. }
  22994. },
  22995. // `SpriteLegend` surface.
  22996. /* eslint-enable indent */
  22997. /**
  22998. * @private
  22999. */
  23000. legendStore: null,
  23001. /**
  23002. * When this is non-zero, changes to sprite attributes apply instantly.
  23003. * See {@link #getAnimation}.
  23004. * @private
  23005. */
  23006. animationSuspendCount: 0,
  23007. /**
  23008. * @private
  23009. */
  23010. chartLayoutSuspendCount: 0,
  23011. /**
  23012. * @private
  23013. */
  23014. chartLayoutCount: 0,
  23015. /**
  23016. * @private
  23017. */
  23018. scheduledLayoutId: null,
  23019. /**
  23020. * @private
  23021. */
  23022. axisThicknessSuspendCount: 0,
  23023. /**
  23024. * @private
  23025. * Indicates that thickness of one or more axes has changed,
  23026. * at the time of {@link #performLayout} call. I.e. 'performLayout'
  23027. * should be called again when current layout is done.
  23028. */
  23029. isThicknessChanged: false,
  23030. constructor: function(config) {
  23031. var me = this;
  23032. me.itemListeners = {};
  23033. me.surfaceMap = {};
  23034. me.chartComponents = {};
  23035. me.isInitializing = true;
  23036. me.suspendChartLayout();
  23037. me.animationSuspendCount++;
  23038. me.callParent(arguments);
  23039. me.isInitializing = false;
  23040. me.getSurface('main');
  23041. me.getSurface('chart').setFlipRtlText(me.getInherited().rtl);
  23042. me.getSurface('overlay').waitFor(me.getSurface('series'));
  23043. me.animationSuspendCount--;
  23044. me.resumeChartLayout();
  23045. },
  23046. applyAnimation: function(animation, oldAnimation) {
  23047. return Ext.chart.Util.applyAnimation(animation, oldAnimation);
  23048. },
  23049. updateAnimation: function() {
  23050. if (this.isConfiguring) {
  23051. return;
  23052. }
  23053. // eslint-disable-next-line vars-on-top
  23054. var seriesList = this.getSeries(),
  23055. ln = seriesList.length,
  23056. i, series;
  23057. this.isSettingSeriesAnimation = true;
  23058. for (i = 0; i < ln; i++) {
  23059. series = seriesList[i];
  23060. // Don't update the series animation config, if it was set by
  23061. // a user, unless 'suspendAnimation' was called.
  23062. if (!series.isUserAnimation || this.animationSuspendCount) {
  23063. series.setAnimation(series.getAnimation());
  23064. }
  23065. }
  23066. this.isSettingSeriesAnimation = false;
  23067. },
  23068. getAnimation: function() {
  23069. var result;
  23070. if (this.animationSuspendCount) {
  23071. result = {
  23072. duration: 0
  23073. };
  23074. } else {
  23075. result = this.callParent();
  23076. }
  23077. return result;
  23078. },
  23079. suspendAnimation: function() {
  23080. this.animationSuspendCount++;
  23081. if (this.animationSuspendCount === 1) {
  23082. this.updateAnimation();
  23083. }
  23084. },
  23085. resumeAnimation: function() {
  23086. this.animationSuspendCount--;
  23087. if (this.animationSuspendCount === 0) {
  23088. this.updateAnimation();
  23089. }
  23090. },
  23091. applyInsetPadding: function(padding, oldPadding) {
  23092. var result;
  23093. if (!Ext.isObject(padding)) {
  23094. result = Ext.util.Format.parseBox(padding);
  23095. } else if (!oldPadding) {
  23096. result = padding;
  23097. } else {
  23098. result = Ext.apply(oldPadding, padding);
  23099. }
  23100. return result;
  23101. },
  23102. /**
  23103. * Suspends chart's layout.
  23104. */
  23105. suspendChartLayout: function() {
  23106. var me = this;
  23107. me.chartLayoutSuspendCount++;
  23108. if (me.chartLayoutSuspendCount === 1) {
  23109. if (me.scheduledLayoutId) {
  23110. me.layoutInSuspension = true;
  23111. me.cancelChartLayout();
  23112. } else {
  23113. me.layoutInSuspension = false;
  23114. }
  23115. }
  23116. },
  23117. /**
  23118. * Decrements chart's layout suspend count.
  23119. * When the suspend count is decremented to zero,
  23120. * a layout is scheduled.
  23121. */
  23122. resumeChartLayout: function() {
  23123. var me = this;
  23124. me.chartLayoutSuspendCount--;
  23125. if (me.chartLayoutSuspendCount === 0) {
  23126. if (me.layoutInSuspension) {
  23127. me.scheduleLayout();
  23128. }
  23129. }
  23130. },
  23131. /**
  23132. * Cancel a scheduled layout.
  23133. */
  23134. cancelChartLayout: function() {
  23135. if (this.scheduledLayoutId) {
  23136. Ext.draw.Animator.cancel(this.scheduledLayoutId);
  23137. this.scheduledLayoutId = null;
  23138. this.checkLayoutEnd();
  23139. }
  23140. },
  23141. /**
  23142. * Schedule a layout at next frame.
  23143. */
  23144. scheduleLayout: function() {
  23145. var me = this;
  23146. if (me.allowSchedule() && !me.scheduledLayoutId) {
  23147. me.scheduledLayoutId = Ext.draw.Animator.schedule('doScheduleLayout', me);
  23148. }
  23149. },
  23150. allowSchedule: function() {
  23151. return true;
  23152. },
  23153. doScheduleLayout: function() {
  23154. var me = this;
  23155. me.scheduledLayoutId = null;
  23156. if (me.chartLayoutSuspendCount) {
  23157. me.layoutInSuspension = true;
  23158. } else {
  23159. me.performLayout();
  23160. }
  23161. },
  23162. /**
  23163. * Prevent axes from triggering chart layout when their thickness changes.
  23164. * E.g. during an interaction that makes changes to the axes,
  23165. * or when chart layout was triggered by something else,
  23166. * for example a chart resize event.
  23167. */
  23168. suspendThicknessChanged: function() {
  23169. this.axisThicknessSuspendCount++;
  23170. },
  23171. /**
  23172. * Decrements axis thickness suspend count.
  23173. * When axis thickness suspend count is decremented to zero,
  23174. * chart layout is performed.
  23175. */
  23176. resumeThicknessChanged: function() {
  23177. if (this.axisThicknessSuspendCount > 0) {
  23178. this.axisThicknessSuspendCount--;
  23179. if (this.axisThicknessSuspendCount === 0 && this.isThicknessChanged) {
  23180. this.onThicknessChanged();
  23181. }
  23182. }
  23183. },
  23184. onThicknessChanged: function() {
  23185. if (this.axisThicknessSuspendCount === 0) {
  23186. this.isThicknessChanged = false;
  23187. this.performLayout();
  23188. } else {
  23189. this.isThicknessChanged = true;
  23190. }
  23191. },
  23192. applySprites: function(sprites) {
  23193. var surface = this.getSurface('chart');
  23194. sprites = Ext.Array.from(sprites);
  23195. surface.removeAll(true);
  23196. surface.add(sprites);
  23197. return sprites;
  23198. },
  23199. initItems: function() {
  23200. var items = this.items,
  23201. i, ln, item;
  23202. if (items && !items.isMixedCollection) {
  23203. this.items = [];
  23204. items = Ext.Array.from(items);
  23205. for (i = 0 , ln = items.length; i < ln; i++) {
  23206. item = items[i];
  23207. if (item.type) {
  23208. Ext.raise("To add custom sprites to the chart use the 'sprites' config.");
  23209. } else {
  23210. this.items.push(item);
  23211. }
  23212. }
  23213. }
  23214. // @noOptimize.callParent
  23215. this.callParent();
  23216. },
  23217. // noOptimize is needed because in the ext build we have a parent method to call,
  23218. // but in touch we do not so we need to suppress the cmd warning during optimized build
  23219. applyBackground: function(newBackground, oldBackground) {
  23220. var surface = this.getSurface('background');
  23221. return this.refreshBackground(surface, newBackground, oldBackground);
  23222. },
  23223. /**
  23224. * @private
  23225. * The background updater. Used by both the chart and the sprite legend.
  23226. * @param surface The surface to put the background in.
  23227. * @param newBackground
  23228. * @param oldBackground
  23229. * @return {Ext.draw.sprite.Rect/Ext.draw.sprite.Sprite}
  23230. */
  23231. refreshBackground: function(surface, newBackground, oldBackground) {
  23232. var width, height, isUpdateOld;
  23233. if (newBackground) {
  23234. if (oldBackground) {
  23235. width = oldBackground.attr.width;
  23236. height = oldBackground.attr.height;
  23237. isUpdateOld = oldBackground.type === (newBackground.type || 'rect');
  23238. }
  23239. if (newBackground.isSprite) {
  23240. oldBackground = newBackground;
  23241. } else if (newBackground.type === 'image' && Ext.isString(newBackground.src)) {
  23242. if (isUpdateOld) {
  23243. oldBackground.setAttributes({
  23244. src: newBackground.src
  23245. });
  23246. } else {
  23247. surface.remove(oldBackground, true);
  23248. oldBackground = surface.add(newBackground);
  23249. }
  23250. } else {
  23251. if (isUpdateOld) {
  23252. oldBackground.setAttributes({
  23253. fillStyle: newBackground
  23254. });
  23255. } else {
  23256. surface.remove(oldBackground, true);
  23257. oldBackground = surface.add({
  23258. type: 'rect',
  23259. fillStyle: newBackground,
  23260. animation: {
  23261. customDurations: {
  23262. x: 0,
  23263. y: 0,
  23264. width: 0,
  23265. height: 0
  23266. }
  23267. }
  23268. });
  23269. }
  23270. }
  23271. }
  23272. if (width && height) {
  23273. oldBackground.setAttributes({
  23274. width: width,
  23275. height: height
  23276. });
  23277. }
  23278. oldBackground.setAnimation(this.getAnimation());
  23279. return oldBackground;
  23280. },
  23281. defaultResizeHandler: function(size) {
  23282. this.scheduleLayout();
  23283. return false;
  23284. },
  23285. applyMainRect: function(newRect, rect) {
  23286. if (!rect) {
  23287. return newRect;
  23288. }
  23289. this.getSeries();
  23290. this.getAxes();
  23291. if (newRect[0] === rect[0] && newRect[1] === rect[1] && newRect[2] === rect[2] && newRect[3] === rect[3]) {
  23292. return rect;
  23293. } else {
  23294. return newRect;
  23295. }
  23296. },
  23297. register: function(component) {
  23298. var map = this.chartComponents,
  23299. id = component.getId();
  23300. //<debug>
  23301. if (id === undefined) {
  23302. Ext.raise('Chart component id is undefined. ' + 'Please ensure the component has an id.');
  23303. }
  23304. if (id in map) {
  23305. Ext.raise('Registering duplicate chart component id "' + id + '"');
  23306. }
  23307. //</debug>
  23308. map[id] = component;
  23309. },
  23310. unregister: function(component) {
  23311. var map = this.chartComponents,
  23312. id = component.getId();
  23313. delete map[id];
  23314. },
  23315. get: function(id) {
  23316. return this.chartComponents[id];
  23317. },
  23318. /**
  23319. * @method getAxis Returns an axis instance based on the type of data passed.
  23320. * @param {String/Number/Ext.chart.axis.Axis} axis You may request an axis by passing
  23321. * an id, the number of the array key returned by {@link #getAxes}, or an axis instance.
  23322. * @return {Ext.chart.axis.Axis} The axis requested.
  23323. */
  23324. getAxis: function(axis) {
  23325. if (axis instanceof Ext.chart.axis.Axis) {
  23326. return axis;
  23327. } else if (Ext.isNumber(axis)) {
  23328. return this.getAxes()[axis];
  23329. } else if (Ext.isString(axis)) {
  23330. return this.get(axis);
  23331. }
  23332. },
  23333. getSurface: function(id, type) {
  23334. var me = this,
  23335. map = me.surfaceMap,
  23336. surface;
  23337. id = id || 'main';
  23338. type = type || id;
  23339. surface = this.callParent([
  23340. id,
  23341. type
  23342. ]);
  23343. if (!map[type]) {
  23344. map[type] = [];
  23345. }
  23346. if (Ext.Array.indexOf(map[type], surface) < 0) {
  23347. surface.type = type;
  23348. map[type].push(surface);
  23349. surface.on('destroy', me.forgetSurface, me);
  23350. }
  23351. return surface;
  23352. },
  23353. forgetSurface: function(surface) {
  23354. var map = this.surfaceMap,
  23355. group, index;
  23356. if (!map || this.destroying) {
  23357. return;
  23358. }
  23359. group = map[surface.type];
  23360. index = group ? Ext.Array.indexOf(group, surface) : -1;
  23361. if (index >= 0) {
  23362. group.splice(index, 1);
  23363. }
  23364. },
  23365. applyAxes: function(newAxes, oldAxes) {
  23366. var me = this,
  23367. positions = {
  23368. left: 'right',
  23369. right: 'left'
  23370. },
  23371. result = [],
  23372. axis, oldAxis, linkedTo, id, oldMap, series, i, j, ln;
  23373. me.animationSuspendCount++;
  23374. me.getStore();
  23375. if (!oldAxes) {
  23376. oldAxes = [];
  23377. oldAxes.map = {};
  23378. }
  23379. oldMap = oldAxes.map;
  23380. result.map = {};
  23381. newAxes = Ext.Array.from(newAxes, true);
  23382. for (i = 0 , ln = newAxes.length; i < ln; i++) {
  23383. axis = newAxes[i];
  23384. if (!axis) {
  23385. continue;
  23386. }
  23387. if (axis instanceof Ext.chart.axis.Axis) {
  23388. oldAxis = oldMap[axis.getId()];
  23389. axis.setChart(me);
  23390. } else {
  23391. axis = Ext.Object.chain(axis);
  23392. linkedTo = axis.linkedTo;
  23393. id = axis.id;
  23394. if (Ext.isNumber(linkedTo)) {
  23395. axis = Ext.merge({}, newAxes[linkedTo], axis);
  23396. } else if (Ext.isString(linkedTo)) {
  23397. Ext.Array.each(newAxes, function(item) {
  23398. if (item.id === axis.linkedTo) {
  23399. axis = Ext.merge({}, item, axis);
  23400. return false;
  23401. }
  23402. });
  23403. }
  23404. axis.id = id;
  23405. axis.chart = me;
  23406. if (me.getInherited().rtl) {
  23407. axis.position = positions[axis.position] || axis.position;
  23408. }
  23409. id = axis.getId && axis.getId() || axis.id;
  23410. axis = Ext.factory(axis, null, oldAxis = oldMap[id], 'axis');
  23411. }
  23412. if (axis) {
  23413. result.push(axis);
  23414. result.map[axis.getId()] = axis;
  23415. }
  23416. }
  23417. me.axesChangeSeries = {};
  23418. for (i in oldMap) {
  23419. if (!result.map[i]) {
  23420. oldAxis = oldMap[i];
  23421. if (oldAxis && !oldAxis.destroyed) {
  23422. // At this point the series still have their `xAxis` and `yAxis` configs
  23423. // set to old axes. We need to update such series with new matching axes
  23424. // by calling their `onAxesChange` method.
  23425. for (j = 0 , ln = oldAxis.boundSeries.length; j < ln; j++) {
  23426. series = oldAxis.boundSeries[j];
  23427. me.axesChangeSeries[series.getId()] = series;
  23428. }
  23429. oldAxis.destroy();
  23430. }
  23431. }
  23432. }
  23433. me.animationSuspendCount--;
  23434. return result;
  23435. },
  23436. updateAxes: function(axes) {
  23437. var me = this,
  23438. seriesMap = me.axesChangeSeries,
  23439. series, id, i, ln, axis;
  23440. for (id in seriesMap) {
  23441. series = seriesMap[id];
  23442. // `true` to force set series' axes, even if they are already set
  23443. // (in this case to old axes that were just destroyed in the `axes` applier).
  23444. series.onAxesChange(me, true);
  23445. }
  23446. // If changes to the `axes` config are made post chart creation, without making any
  23447. // changes to the series afterwards, we need to figure out the new axes' `boundSeries`
  23448. // manually, as the 'serieschange' event won't be fired in this case.
  23449. for (i = 0 , ln = axes.length; i < ln; i++) {
  23450. axis = axes[i];
  23451. axis.onSeriesChange(me);
  23452. }
  23453. if (!me.isConfiguring && !me.destroying) {
  23454. me.scheduleLayout();
  23455. }
  23456. },
  23457. circularCopyArray: function(inArray, startIndex, count) {
  23458. var outArray = [],
  23459. i,
  23460. len = inArray && inArray.length;
  23461. if (len) {
  23462. for (i = 0; i < count; i++) {
  23463. outArray.push(inArray[(startIndex + i) % len]);
  23464. }
  23465. }
  23466. return outArray;
  23467. },
  23468. circularCopyObject: function(inObject, startIndex, count) {
  23469. var me = this,
  23470. name, value,
  23471. outObject = {};
  23472. if (count) {
  23473. for (name in inObject) {
  23474. if (inObject.hasOwnProperty(name)) {
  23475. value = inObject[name];
  23476. if (Ext.isArray(value)) {
  23477. outObject[name] = me.circularCopyArray(value, startIndex, count);
  23478. } else {
  23479. outObject[name] = value;
  23480. }
  23481. }
  23482. }
  23483. }
  23484. return outObject;
  23485. },
  23486. getColors: function() {
  23487. var me = this,
  23488. configColors = me.config.colors,
  23489. theme = me.getTheme();
  23490. if (Ext.isArray(configColors) && configColors.length > 0) {
  23491. configColors = me.applyColors(configColors);
  23492. }
  23493. return configColors || (theme && theme.getColors());
  23494. },
  23495. applyColors: function(newColors) {
  23496. newColors = Ext.Array.map(newColors, function(color) {
  23497. if (Ext.isString(color)) {
  23498. return color;
  23499. } else {
  23500. return color.toString();
  23501. }
  23502. });
  23503. return newColors;
  23504. },
  23505. updateColors: function(newColors) {
  23506. var me = this,
  23507. theme = me.getTheme(),
  23508. colors = newColors || (theme && theme.getColors()),
  23509. colorIndex = 0,
  23510. series = me.getSeries(),
  23511. seriesCount = series && series.length,
  23512. i, seriesItem, seriesColors, seriesColorCount;
  23513. if (colors.length) {
  23514. for (i = 0; i < seriesCount; i++) {
  23515. seriesItem = series[i];
  23516. seriesColorCount = seriesItem.themeColorCount();
  23517. seriesColors = me.circularCopyArray(colors, colorIndex, seriesColorCount);
  23518. colorIndex += seriesColorCount;
  23519. seriesItem.updateChartColors(seriesColors);
  23520. }
  23521. }
  23522. if (!me.isConfiguring) {
  23523. me.refreshLegendStore();
  23524. }
  23525. },
  23526. applyTheme: function(theme) {
  23527. if (theme && theme.isTheme) {
  23528. return theme;
  23529. }
  23530. return Ext.Factory.chartTheme(theme);
  23531. },
  23532. updateGradients: function(gradients) {
  23533. if (!Ext.isEmpty(gradients)) {
  23534. this.updateTheme(this.getTheme());
  23535. }
  23536. },
  23537. updateTheme: function(theme, oldTheme) {
  23538. var me = this,
  23539. axes = me.getAxes(),
  23540. series = me.getSeries(),
  23541. colors = me.getColors(),
  23542. i;
  23543. if (!series) {
  23544. return;
  23545. }
  23546. me.updateChartTheme(theme);
  23547. for (i = 0; i < axes.length; i++) {
  23548. axes[i].updateTheme(theme);
  23549. }
  23550. for (i = 0; i < series.length; i++) {
  23551. series[i].setTheme(theme);
  23552. }
  23553. me.updateSpriteTheme(theme);
  23554. me.updateColors(colors);
  23555. // It may be necessary to perform a layout here.
  23556. // But instead of the 'chart.scheduleLayout' call, we can call
  23557. // 'chart.redraw'. If after the redraw call the thickness
  23558. // of any axis changes, this will automatically trigger
  23559. // chart layout (see Ext.chart.axis.sprite.Axis.doThicknessChanged).
  23560. // Otherwise, no layout is necessary.
  23561. me.redraw();
  23562. me.fireEvent('themechange', me, theme, oldTheme);
  23563. },
  23564. themeOnlyIfConfigured: {
  23565. captions: true
  23566. },
  23567. updateChartTheme: function(theme) {
  23568. var me = this,
  23569. chartTheme = theme.getChart(),
  23570. initialConfig = me.getInitialConfig(),
  23571. defaultConfig = me.defaultConfig,
  23572. configs = me.self.getConfigurator().configs,
  23573. genericChartTheme = chartTheme.defaults,
  23574. specificChartTheme = chartTheme[me.xtype],
  23575. themeOnlyIfConfigured = me.themeOnlyIfConfigured,
  23576. key, value, isObjValue, isUnusedConfig, initialValue, cfg;
  23577. chartTheme = Ext.merge({}, genericChartTheme, specificChartTheme);
  23578. for (key in chartTheme) {
  23579. value = chartTheme[key];
  23580. cfg = configs[key];
  23581. if (value !== null && value !== undefined && cfg) {
  23582. initialValue = initialConfig[key];
  23583. isObjValue = Ext.isObject(value);
  23584. isUnusedConfig = initialValue === defaultConfig[key];
  23585. if (isObjValue) {
  23586. if (isUnusedConfig && themeOnlyIfConfigured[key]) {
  23587. continue;
  23588. }
  23589. value = Ext.merge({}, value, initialValue);
  23590. }
  23591. if (isUnusedConfig || isObjValue) {
  23592. me[cfg.names.set](value);
  23593. }
  23594. }
  23595. }
  23596. },
  23597. updateSpriteTheme: function(theme) {
  23598. var me = this,
  23599. chartSurface, sprites, styles, sprite, style, key, attr, isText, i, ln;
  23600. me.getSprites();
  23601. chartSurface = me.getSurface('chart');
  23602. sprites = chartSurface.getItems();
  23603. styles = theme.getSprites();
  23604. for (i = 0 , ln = sprites.length; i < ln; i++) {
  23605. sprite = sprites[i];
  23606. style = styles[sprite.type];
  23607. if (style) {
  23608. attr = {};
  23609. isText = sprite.type === 'text';
  23610. for (key in style) {
  23611. if (!(key in sprite.config)) {
  23612. // Setting individual font attributes will take over the 'font' shorthand
  23613. // attribute, but this behavior is undesireable for theming.
  23614. if (!(isText && key.indexOf('font') === 0 && sprite.config.font)) {
  23615. attr[key] = style[key];
  23616. }
  23617. }
  23618. }
  23619. sprite.setAttributes(attr);
  23620. }
  23621. }
  23622. },
  23623. /**
  23624. * Adds a {@link Ext.chart.series.Series Series} to this chart.
  23625. *
  23626. * The Series (or array) passed will be added to the existing series. If an `id` is specified
  23627. * in a new Series, any existing Series of that `id` will be updated.
  23628. *
  23629. * The chart will be redrawn in response to the change.
  23630. *
  23631. * @param {Object/Object[]/Ext.chart.series.Series/Ext.chart.series.Series[]} newSeries A
  23632. * config object describing the Series to add, or an instantiated Series object. Or an array
  23633. * of these.
  23634. */
  23635. addSeries: function(newSeries) {
  23636. var series = this.getSeries();
  23637. series = series.concat(Ext.Array.from(newSeries));
  23638. this.setSeries(series);
  23639. },
  23640. /**
  23641. * Remove a {@link Ext.chart.series.Series Series} from this chart.
  23642. * The Series (or array) passed will be removed from the existing series.
  23643. *
  23644. * The chart will be redrawn in response to the change.
  23645. *
  23646. * @param {Ext.chart.series.Series/String} series The Series or the `id` of the Series
  23647. * to remove. May be an array.
  23648. */
  23649. removeSeries: function(series) {
  23650. var existingSeries = this.getSeries(),
  23651. newSeries = [],
  23652. removeMap = {},
  23653. i, len, s;
  23654. series = Ext.Array.from(series);
  23655. // Build a map of the Series IDs that are to be removed
  23656. for (i = 0 , len = series.length; i < len; i++) {
  23657. s = series[i];
  23658. // If they passed a Series Object
  23659. if (typeof s !== 'string') {
  23660. s = s.getId();
  23661. }
  23662. removeMap[s] = true;
  23663. }
  23664. // Build a new Series array that excludes those Series scheduled for removal
  23665. for (i = 0 , len = existingSeries.length; i < len; i++) {
  23666. if (!removeMap[existingSeries[i].getId()]) {
  23667. newSeries.push(existingSeries[i]);
  23668. }
  23669. }
  23670. this.setSeries(newSeries);
  23671. },
  23672. applySeries: function(newSeries, oldSeries) {
  23673. var me = this,
  23674. result = [],
  23675. oldMap, oldSeriesItem, i, ln, series;
  23676. me.animationSuspendCount++;
  23677. me.getAxes();
  23678. if (oldSeries) {
  23679. oldMap = oldSeries.map;
  23680. } else {
  23681. oldSeries = [];
  23682. oldMap = oldSeries.map = {};
  23683. }
  23684. result.map = {};
  23685. newSeries = Ext.Array.from(newSeries, true);
  23686. for (i = 0 , ln = newSeries.length; i < ln; i++) {
  23687. series = newSeries[i];
  23688. if (!series) {
  23689. continue;
  23690. }
  23691. oldSeriesItem = oldMap[series.getId && series.getId() || series.id];
  23692. // New Series instance passed in
  23693. if (series instanceof Ext.chart.series.Series) {
  23694. // Replacing
  23695. if (oldSeriesItem && oldSeriesItem !== series) {
  23696. oldSeriesItem.destroy();
  23697. }
  23698. series.setChart(me);
  23699. }
  23700. // Series config object passed in
  23701. else if (Ext.isObject(series)) {
  23702. // Config object matched an existing Series item by id;
  23703. // update its configuration
  23704. if (oldSeriesItem) {
  23705. oldSeriesItem.setConfig(series);
  23706. series = oldSeriesItem;
  23707. } else // Create a new Series
  23708. {
  23709. if (Ext.isString(series)) {
  23710. series = {
  23711. type: series
  23712. };
  23713. }
  23714. series.chart = me;
  23715. series = Ext.create(series.xclass || ('series.' + series.type), series);
  23716. }
  23717. }
  23718. result.push(series);
  23719. result.map[series.getId()] = series;
  23720. }
  23721. for (i in oldMap) {
  23722. if (!result.map[oldMap[i].id]) {
  23723. oldMap[i].destroy();
  23724. }
  23725. }
  23726. me.animationSuspendCount--;
  23727. return result;
  23728. },
  23729. updateSeries: function(newSeries, oldSeries) {
  23730. var me = this;
  23731. if (me.destroying) {
  23732. return;
  23733. }
  23734. me.animationSuspendCount++;
  23735. me.fireEvent('serieschange', me, newSeries, oldSeries);
  23736. if (!Ext.isEmpty(newSeries)) {
  23737. me.updateTheme(me.getTheme());
  23738. }
  23739. me.refreshLegendStore();
  23740. if (!me.isConfiguring && !me.destroying) {
  23741. me.scheduleLayout();
  23742. }
  23743. me.animationSuspendCount--;
  23744. },
  23745. defaultLegendType: 'sprite',
  23746. applyLegend: function(legend, oldLegend) {
  23747. var me = this,
  23748. result = null,
  23749. alias;
  23750. if (oldLegend && !(oldLegend.destroyed || oldLegend.destroying)) {
  23751. if (me.legendStoreListeners) {
  23752. me.legendStoreListeners.destroy();
  23753. }
  23754. if (me.legendStore) {
  23755. me.legendStore.destroy();
  23756. }
  23757. oldLegend.destroy();
  23758. }
  23759. if (legend) {
  23760. if (Ext.isBoolean(legend)) {
  23761. result = Ext.create('legend.' + me.defaultLegendType, {
  23762. docked: 'bottom',
  23763. chart: me
  23764. });
  23765. } else {
  23766. legend.docked = legend.docked || 'bottom';
  23767. legend.chart = me;
  23768. alias = 'legend.' + (legend.type || me.defaultLegendType);
  23769. result = Ext.create(alias, legend);
  23770. }
  23771. }
  23772. return result;
  23773. },
  23774. updateLegend: function(legend) {
  23775. var me = this;
  23776. // Probably has been already destroyed with the old legend,
  23777. // but making sure.
  23778. me.destroyLegendStore();
  23779. if (legend) {
  23780. me.getItems();
  23781. legend.setStore(me.refreshLegendStore());
  23782. }
  23783. if (!me.isConfiguring) {
  23784. me.scheduleLayout();
  23785. }
  23786. },
  23787. captionApplier: function(caption, oldCaption) {
  23788. var me = this,
  23789. result;
  23790. if (oldCaption && !(oldCaption.destroyed || oldCaption.destroying)) {
  23791. oldCaption.destroy();
  23792. }
  23793. if (caption) {
  23794. caption.chart = me;
  23795. result = new Ext.chart.Caption(caption);
  23796. }
  23797. return result;
  23798. },
  23799. applyCaptions: function(captions, oldCaptions) {
  23800. var map = {},
  23801. caption, oldCaption, name, any;
  23802. for (name in captions) {
  23803. caption = captions[name];
  23804. if (caption && !caption.length && !(caption.text && caption.text.length)) {
  23805. caption = null;
  23806. } else if (typeof caption === 'string') {
  23807. caption = {
  23808. text: caption
  23809. };
  23810. // Initial config is used for proper theming (see `updateChartTheme`)
  23811. // and config merging, however, mergin won't work as expected, if
  23812. // the initial config value remains a string, so we modify it here.
  23813. this.getInitialConfig().captions[name] = caption;
  23814. }
  23815. oldCaption = oldCaptions && oldCaptions[name];
  23816. caption = this.captionApplier(caption, oldCaption);
  23817. if (caption) {
  23818. any = true;
  23819. map[name] = caption;
  23820. }
  23821. }
  23822. return any && map;
  23823. },
  23824. updateCaptions: function() {
  23825. var me = this;
  23826. if (!me.isConfiguring) {
  23827. me.scheduleLayout();
  23828. }
  23829. },
  23830. /**
  23831. * Return the legend store that contains all the legend information.
  23832. * This information is collected from all the series.
  23833. * @return {Ext.chart.legend.store.Store}
  23834. */
  23835. getLegendStore: function() {
  23836. var me = this,
  23837. store = me.legendStore;
  23838. if (!store) {
  23839. store = me.legendStore = new Ext.chart.legend.store.Store({
  23840. chart: me
  23841. });
  23842. me.legendStoreListeners = store.on({
  23843. scope: me,
  23844. update: 'onLegendStoreUpdate',
  23845. destroyable: true
  23846. });
  23847. }
  23848. return store;
  23849. },
  23850. destroyLegendStore: function() {
  23851. var store = this.legendStore;
  23852. if (store && !(store.destroyed || store.destroying)) {
  23853. store.destroy();
  23854. }
  23855. this.legendStore = null;
  23856. },
  23857. refreshLegendStore: function() {
  23858. var me = this,
  23859. legendStore = me.getLegendStore(),
  23860. series, seriesList, legendData, i, ln;
  23861. if (legendStore) {
  23862. seriesList = me.getSeries();
  23863. legendData = [];
  23864. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  23865. series = seriesList[i];
  23866. if (series.getShowInLegend()) {
  23867. series.provideLegendInfo(legendData);
  23868. }
  23869. }
  23870. legendStore.setData(legendData);
  23871. }
  23872. return legendStore;
  23873. },
  23874. onLegendStoreUpdate: function(store, record) {
  23875. var me = this,
  23876. series;
  23877. if (record) {
  23878. series = this.getSeries().map[record.get('series')];
  23879. if (series) {
  23880. series.setHiddenByIndex(record.get('index'), record.get('disabled'));
  23881. me.redraw();
  23882. }
  23883. }
  23884. },
  23885. applyInteractions: function(interactions, oldInteractions) {
  23886. var me = this,
  23887. result = [],
  23888. oldMap, interaction, i, ln;
  23889. interactions = Ext.Array.from(interactions, true);
  23890. if (!oldInteractions) {
  23891. oldInteractions = [];
  23892. oldInteractions.map = {};
  23893. }
  23894. oldMap = oldInteractions.map;
  23895. result.map = {};
  23896. for (i = 0 , ln = interactions.length; i < ln; i++) {
  23897. interaction = interactions[i];
  23898. if (!interaction) {
  23899. continue;
  23900. }
  23901. // eslint-disable-next-line max-len
  23902. interaction = Ext.factory(interaction, null, oldMap[interaction.getId && interaction.getId() || interaction.id], 'interaction');
  23903. if (interaction) {
  23904. interaction.setChart(me);
  23905. result.push(interaction);
  23906. result.map[interaction.getId()] = interaction;
  23907. }
  23908. }
  23909. for (i in oldMap) {
  23910. if (!result.map[i]) {
  23911. oldMap[i].destroy();
  23912. }
  23913. }
  23914. return result;
  23915. },
  23916. /**
  23917. * Get an interaction by type.
  23918. * @param {String} type The type of the interaction.
  23919. * @return {Ext.chart.interactions.Abstract} The interaction. `null`
  23920. * if not found.
  23921. */
  23922. getInteraction: function(type) {
  23923. var interactions = this.getInteractions(),
  23924. len = interactions && interactions.length,
  23925. out = null,
  23926. interaction, i;
  23927. if (len) {
  23928. for (i = 0; i < len; ++i) {
  23929. interaction = interactions[i];
  23930. if (interaction.type === type) {
  23931. out = interaction;
  23932. break;
  23933. }
  23934. }
  23935. }
  23936. return out;
  23937. },
  23938. applyStore: function(store) {
  23939. return store && Ext.StoreManager.lookup(store);
  23940. },
  23941. updateStore: function(newStore, oldStore) {
  23942. var me = this;
  23943. if (oldStore && !oldStore.destroyed) {
  23944. oldStore.un({
  23945. datachanged: 'onDataChanged',
  23946. update: 'onDataChanged',
  23947. scope: me,
  23948. order: 'after'
  23949. });
  23950. if (oldStore.autoDestroy) {
  23951. oldStore.destroy();
  23952. }
  23953. }
  23954. if (newStore) {
  23955. newStore.on({
  23956. datachanged: 'onDataChanged',
  23957. update: 'onDataChanged',
  23958. scope: me,
  23959. order: 'after'
  23960. });
  23961. }
  23962. me.fireEvent('storechange', me, newStore, oldStore);
  23963. me.onDataChanged();
  23964. },
  23965. /**
  23966. * Redraw the chart. If animations are set this will animate the chart too.
  23967. * Note: the actual redraw is performed in a subclass.
  23968. */
  23969. redraw: function() {
  23970. this.fireEvent('redraw', this);
  23971. },
  23972. /**
  23973. * @private
  23974. * Lays out chart components and triggers a {@link #event!redraw}.
  23975. * Note: the actual layout is performed in a subclass.
  23976. * A subclass should not perform a layout, if this parent method
  23977. * returns `false`.
  23978. * @return {Boolean}
  23979. */
  23980. performLayout: function() {
  23981. if (this.destroying || this.destroyed) {
  23982. //<debug>
  23983. Ext.raise('Attempting to lay out a dead chart: ' + this.getId());
  23984. //</debug>
  23985. return false;
  23986. }
  23987. // Cancel subclass layout.
  23988. // eslint-disable-next-line vars-on-top
  23989. var me = this,
  23990. legend = me.getLegend(),
  23991. chartRect = me.getChartRect(true),
  23992. background = me.getBackground(),
  23993. result = true,
  23994. legendRect;
  23995. me.cancelChartLayout();
  23996. //<debug>
  23997. // Unlike the 'layout' event that is called after all chart layouts are done
  23998. // and none are pending, this event fires before the start of each layout.
  23999. me.fireEvent('beforelayout', me);
  24000. //</debug>
  24001. if (background) {
  24002. me.getSurface('background').setRect(chartRect.slice());
  24003. background.setAttributes({
  24004. width: chartRect[2],
  24005. height: chartRect[3]
  24006. });
  24007. }
  24008. // The top docked legend is a special case and should be laid out after captions.
  24009. if (legend && legend.isSpriteLegend && !legend.isTop) {
  24010. legendRect = legend.computeRect(chartRect);
  24011. }
  24012. me.layoutCaptions(chartRect);
  24013. if (legend && legend.isSpriteLegend && legend.isTop) {
  24014. legendRect = legend.computeRect(chartRect);
  24015. }
  24016. if (legendRect) {
  24017. me.getSurface('legend').setRect(legendRect);
  24018. result = legend.performLayout();
  24019. }
  24020. me.getSurface('chart').setRect(chartRect);
  24021. if (result) {
  24022. me.hasFirstLayout = true;
  24023. }
  24024. return result;
  24025. },
  24026. layoutCaptions: function(chartRect) {
  24027. var captions = this.getCaptions(),
  24028. shrinkRect = {
  24029. left: 0,
  24030. top: 0,
  24031. right: chartRect[2],
  24032. bottom: chartRect[3]
  24033. },
  24034. caption, captionName, captionList, i, ln;
  24035. if (captions) {
  24036. captionList = [];
  24037. for (captionName in captions) {
  24038. captionList.push(captions[captionName]);
  24039. }
  24040. captionList.sort(function(a, b) {
  24041. return a.getWeight() - b.getWeight();
  24042. });
  24043. for (i = 0 , ln = captionList.length; i < ln; i++) {
  24044. caption = captionList[i];
  24045. if (!i) {
  24046. this.getSurface(caption.surfaceName).setRect(chartRect.slice());
  24047. }
  24048. caption.computeRect(chartRect, shrinkRect);
  24049. }
  24050. this.captionList = captionList;
  24051. }
  24052. },
  24053. /**
  24054. * @private
  24055. */
  24056. checkLayoutEnd: function() {
  24057. // not running not pending
  24058. if (!this.chartLayoutCount && !this.scheduledLayoutId) {
  24059. this.onLayoutEnd();
  24060. }
  24061. },
  24062. /**
  24063. * @private
  24064. */
  24065. onLayoutEnd: function() {
  24066. var me = this;
  24067. me.fireEvent('layout', me);
  24068. },
  24069. /**
  24070. * @private
  24071. * The area of the chart minus the legend, title, subtitle and credits.
  24072. * Cache chart rect as element.getSize() results in
  24073. * a relatively expensive call to the getComputedStyle().
  24074. */
  24075. getChartRect: function(isRecompute) {
  24076. var me = this,
  24077. chartRect, bodySize;
  24078. if (isRecompute) {
  24079. me.chartRect = null;
  24080. }
  24081. if (me.chartRect) {
  24082. chartRect = me.chartRect;
  24083. } else {
  24084. bodySize = me.bodyElement.getSize();
  24085. chartRect = me.chartRect = [
  24086. 0,
  24087. 0,
  24088. bodySize.width,
  24089. bodySize.height
  24090. ];
  24091. }
  24092. return chartRect;
  24093. },
  24094. /**
  24095. * @private
  24096. * Converts page coordinates into chart's 'series' surface coordinates.
  24097. */
  24098. getEventXY: function(e) {
  24099. return this.getSurface('series').getEventXY(e);
  24100. },
  24101. /**
  24102. * Given an x/y point relative to the chart, find and return the first series item that
  24103. * matches that point.
  24104. * @param {Number} x
  24105. * @param {Number} y
  24106. * @return {Object} An object with `series` and `item` properties, or `false` if no item found.
  24107. */
  24108. getItemForPoint: function(x, y) {
  24109. var me = this,
  24110. seriesList = me.getSeries(),
  24111. rect = me.getMainRect(),
  24112. ln = seriesList.length,
  24113. minDistance = Infinity,
  24114. result = null,
  24115. i, item;
  24116. // The x,y here are already converted to the 'main' surface coordinates.
  24117. // Series surface rect matches the main surface rect.
  24118. if (!(me.hasFirstLayout && rect && x >= 0 && x <= rect[2] && y >= 0 && y <= rect[3])) {
  24119. return null;
  24120. }
  24121. // Iterate in reverse order so that the series that render later (on top)
  24122. // get hit tested first.
  24123. for (i = ln - 1; i >= 0; i--) {
  24124. item = seriesList[i].getItemForPoint(x, y);
  24125. if (item) {
  24126. // Imagine a chart with multiple series, e.g. 'line', 'scatter' and 'bar'.
  24127. // For 'line' and 'scatter' series, the method will look for the nearest
  24128. // marker, but for 'bar' series, it will look for the first bar that
  24129. // contains the given point. For such series, the 'distance' information
  24130. // is absent and meaningless.
  24131. if (!item.distance) {
  24132. result = item;
  24133. break;
  24134. }
  24135. if (item.distance < minDistance) {
  24136. minDistance = item.distance;
  24137. result = item;
  24138. }
  24139. }
  24140. }
  24141. return result;
  24142. },
  24143. /**
  24144. * @private
  24145. * Given an x/y point relative to the chart, find and return all series items that match
  24146. * that point.
  24147. * @param {Number} x
  24148. * @param {Number} y
  24149. * @return {Array} An array of objects with `series` and `item` properties.
  24150. * @deprecated 6.5.2 This method is deprecated
  24151. */
  24152. getItemsForPoint: function(x, y) {
  24153. var me = this,
  24154. seriesList = me.getSeries(),
  24155. ln = seriesList.length,
  24156. // If we haven't drawn yet, don't attempt to find any items.
  24157. i = me.hasFirstLayout ? ln - 1 : -1,
  24158. items = [],
  24159. series, item;
  24160. // Iterate from the end so that the series that are drawn later get hit tested first.
  24161. for (; i >= 0; i--) {
  24162. series = seriesList[i];
  24163. item = series.getItemForPoint(x, y);
  24164. if (item && (item.category === 'items' || item.category === 'markers')) {
  24165. items.push(item);
  24166. }
  24167. }
  24168. return items;
  24169. },
  24170. /**
  24171. * @private
  24172. */
  24173. onDataChanged: function() {
  24174. var me = this,
  24175. rect, store, series, axes;
  24176. if (me.isInitializing) {
  24177. return;
  24178. }
  24179. rect = me.getMainRect();
  24180. store = me.getStore();
  24181. series = me.getSeries();
  24182. axes = me.getAxes();
  24183. if (!store || !axes || !series) {
  24184. return;
  24185. }
  24186. if (!rect) {
  24187. // The chart hasn't been rendered yet.
  24188. me.on({
  24189. redraw: me.onDataChanged,
  24190. scope: me,
  24191. single: true
  24192. });
  24193. return;
  24194. }
  24195. me.processData();
  24196. me.redraw();
  24197. },
  24198. /**
  24199. * @private
  24200. * The number of records in the chart's store last time the data was changed.
  24201. */
  24202. recordCount: 0,
  24203. /**
  24204. * @private
  24205. */
  24206. processData: function() {
  24207. var me = this,
  24208. recordCount = me.getStore().getCount(),
  24209. seriesList = me.getSeries(),
  24210. ln = seriesList.length,
  24211. isNeedUpdateColors = false,
  24212. i = 0,
  24213. series;
  24214. for (; i < ln; i++) {
  24215. series = seriesList[i];
  24216. series.processData();
  24217. if (!isNeedUpdateColors && series.isStoreDependantColorCount) {
  24218. isNeedUpdateColors = true;
  24219. }
  24220. }
  24221. if (isNeedUpdateColors && recordCount > me.recordCount) {
  24222. me.updateColors(me.getColors());
  24223. me.recordCount = recordCount;
  24224. }
  24225. // 'refreshLegendStore' will attemp to grab the 'series',
  24226. // which are still configuring at this point.
  24227. // The legend store will be refreshed inside the chart.series
  24228. // updater anyway.
  24229. if (!me.isConfiguring) {
  24230. me.refreshLegendStore();
  24231. }
  24232. },
  24233. /**
  24234. * Changes the data store bound to this chart and refreshes it.
  24235. * @param {Ext.data.Store} store The store to bind to this chart.
  24236. */
  24237. bindStore: function(store) {
  24238. this.setStore(store);
  24239. },
  24240. applyHighlightItem: function(newHighlightItem, oldHighlightItem) {
  24241. var i1, i2, s1, s2;
  24242. if (newHighlightItem === oldHighlightItem) {
  24243. return;
  24244. }
  24245. if (Ext.isObject(newHighlightItem) && Ext.isObject(oldHighlightItem)) {
  24246. i1 = newHighlightItem;
  24247. i2 = oldHighlightItem;
  24248. s1 = i1.sprite && (i1.sprite[0] || i1.sprite);
  24249. s2 = i2.sprite && (i2.sprite[0] || i2.sprite);
  24250. if (s1 === s2 && i1.index === i2.index) {
  24251. return;
  24252. }
  24253. }
  24254. return newHighlightItem;
  24255. },
  24256. updateHighlightItem: function(newHighlightItem, oldHighlightItem) {
  24257. var newHighlight, oldHighlight;
  24258. if (oldHighlightItem) {
  24259. oldHighlight = oldHighlightItem.series.getHighlight();
  24260. if (oldHighlight) {
  24261. oldHighlightItem.series.setAttributesForItem(oldHighlightItem, {
  24262. highlighted: false
  24263. });
  24264. }
  24265. }
  24266. if (newHighlightItem) {
  24267. newHighlight = newHighlightItem.series.getHighlight();
  24268. if (newHighlight) {
  24269. newHighlightItem.series.setAttributesForItem(newHighlightItem, {
  24270. highlighted: true
  24271. });
  24272. }
  24273. }
  24274. if (oldHighlight || newHighlight) {
  24275. this.fireEvent('itemhighlight', this, newHighlightItem, oldHighlightItem);
  24276. }
  24277. },
  24278. destroyChart: function() {
  24279. var me = this;
  24280. // The order is important here.
  24281. me.setInteractions(null);
  24282. me.setAxes(null);
  24283. me.setSeries(null);
  24284. me.setLegend(null);
  24285. me.setStore(null);
  24286. me.cancelChartLayout();
  24287. },
  24288. /* ---------------------------------
  24289. Methods needed for ComponentQuery
  24290. ---------------------------------- */
  24291. /**
  24292. * @private
  24293. * @param {Boolean} deep
  24294. * @return {Array}
  24295. */
  24296. getRefItems: function(deep) {
  24297. var me = this,
  24298. series = me.getSeries(),
  24299. axes = me.getAxes(),
  24300. interaction = me.getInteractions(),
  24301. legend = me.getLegend(),
  24302. ans = [],
  24303. i, ln;
  24304. for (i = 0 , ln = series.length; i < ln; i++) {
  24305. ans.push(series[i]);
  24306. if (series[i].getRefItems) {
  24307. ans.push.apply(ans, series[i].getRefItems(deep));
  24308. }
  24309. }
  24310. for (i = 0 , ln = axes.length; i < ln; i++) {
  24311. ans.push(axes[i]);
  24312. if (axes[i].getRefItems) {
  24313. ans.push.apply(ans, axes[i].getRefItems(deep));
  24314. }
  24315. }
  24316. for (i = 0 , ln = interaction.length; i < ln; i++) {
  24317. ans.push(interaction[i]);
  24318. if (interaction[i].getRefItems) {
  24319. ans.push.apply(ans, interaction[i].getRefItems(deep));
  24320. }
  24321. }
  24322. if (legend) {
  24323. ans.push(legend);
  24324. }
  24325. return ans;
  24326. }
  24327. });
  24328. Ext.define('Ext.chart.overrides.AbstractChart', {
  24329. override: 'Ext.chart.AbstractChart',
  24330. // In Modern toolkit, if chart element style has no z-index specified,
  24331. // some chart surfaces with higher z-indexes (e.g. overlay)
  24332. // may end up on top of modal dialogs shown over the chart.
  24333. zIndex: 0,
  24334. updateLegend: function(legend, oldLegend) {
  24335. this.callParent([
  24336. legend,
  24337. oldLegend
  24338. ]);
  24339. if (legend && legend.isDomLegend) {
  24340. this.add(legend);
  24341. }
  24342. },
  24343. onItemRemove: function(item, index, destroy) {
  24344. var map = this.surfaceMap,
  24345. type = item.type,
  24346. items = map && map[type];
  24347. this.callParent([
  24348. item,
  24349. index,
  24350. destroy
  24351. ]);
  24352. if (items) {
  24353. Ext.Array.remove(items, item);
  24354. if (items.length === 0) {
  24355. delete map[type];
  24356. }
  24357. }
  24358. },
  24359. doDestroy: function() {
  24360. this.destroyChart();
  24361. this.callParent();
  24362. }
  24363. });
  24364. /**
  24365. * @class Ext.chart.grid.HorizontalGrid
  24366. * @extends Ext.draw.sprite.Sprite
  24367. *
  24368. * Horizontal Grid sprite. Used in Cartesian Charts.
  24369. */
  24370. Ext.define('Ext.chart.grid.HorizontalGrid', {
  24371. extend: 'Ext.draw.sprite.Sprite',
  24372. alias: 'grid.horizontal',
  24373. inheritableStatics: {
  24374. def: {
  24375. processors: {
  24376. x: 'number',
  24377. y: 'number',
  24378. width: 'number',
  24379. height: 'number'
  24380. },
  24381. defaults: {
  24382. x: 0,
  24383. y: 0,
  24384. width: 1,
  24385. height: 1,
  24386. strokeStyle: '#DDD'
  24387. }
  24388. }
  24389. },
  24390. render: function(surface, ctx, rect) {
  24391. var attr = this.attr,
  24392. y = surface.roundPixel(attr.y),
  24393. halfLineWidth = ctx.lineWidth * 0.5;
  24394. ctx.beginPath();
  24395. ctx.rect(rect[0] - surface.matrix.getDX(), y + halfLineWidth, +rect[2], attr.height);
  24396. ctx.fill();
  24397. ctx.beginPath();
  24398. ctx.moveTo(rect[0] - surface.matrix.getDX(), y + halfLineWidth);
  24399. ctx.lineTo(rect[0] + rect[2] - surface.matrix.getDX(), y + halfLineWidth);
  24400. ctx.stroke();
  24401. }
  24402. });
  24403. /**
  24404. * @class Ext.chart.grid.VerticalGrid
  24405. * @extends Ext.draw.sprite.Sprite
  24406. *
  24407. * Vertical Grid sprite. Used in Cartesian Charts.
  24408. */
  24409. Ext.define('Ext.chart.grid.VerticalGrid', {
  24410. extend: 'Ext.draw.sprite.Sprite',
  24411. alias: 'grid.vertical',
  24412. inheritableStatics: {
  24413. def: {
  24414. processors: {
  24415. x: 'number',
  24416. y: 'number',
  24417. width: 'number',
  24418. height: 'number'
  24419. },
  24420. defaults: {
  24421. x: 0,
  24422. y: 0,
  24423. width: 1,
  24424. height: 1,
  24425. strokeStyle: '#DDD'
  24426. }
  24427. }
  24428. },
  24429. render: function(surface, ctx, rect) {
  24430. var attr = this.attr,
  24431. x = surface.roundPixel(attr.x),
  24432. halfLineWidth = ctx.lineWidth * 0.5;
  24433. ctx.beginPath();
  24434. ctx.rect(x - halfLineWidth, rect[1] - surface.matrix.getDY(), attr.width, rect[3]);
  24435. ctx.fill();
  24436. ctx.beginPath();
  24437. ctx.moveTo(x - halfLineWidth, rect[1] - surface.matrix.getDY());
  24438. ctx.lineTo(x - halfLineWidth, rect[1] + rect[3] - surface.matrix.getDY());
  24439. ctx.stroke();
  24440. }
  24441. });
  24442. /**
  24443. * Represents a chart that uses cartesian coordinates.
  24444. * A cartesian chart has two directions, X direction and Y direction.
  24445. * The series and axes are coordinated along these directions.
  24446. * By default the x direction is horizontal and y direction is vertical,
  24447. * You can swap the direction by setting the {@link #flipXY} config to `true`.
  24448. *
  24449. * Cartesian series often treats x direction an y direction differently.
  24450. * In most cases, data on x direction are assumed to be monotonically increasing.
  24451. * Based on this property, cartesian series can be trimmed and summarized properly
  24452. * to gain a better performance.
  24453. *
  24454. * Please check out the summary for the {@link Ext.chart.AbstractChart} as well,
  24455. * for helpful tips and important details.
  24456. *
  24457. */
  24458. Ext.define('Ext.chart.CartesianChart', {
  24459. extend: 'Ext.chart.AbstractChart',
  24460. alternateClassName: 'Ext.chart.Chart',
  24461. requires: [
  24462. 'Ext.chart.grid.HorizontalGrid',
  24463. 'Ext.chart.grid.VerticalGrid'
  24464. ],
  24465. xtype: [
  24466. 'cartesian',
  24467. 'chart'
  24468. ],
  24469. isCartesian: true,
  24470. config: {
  24471. /**
  24472. * @cfg {Boolean} flipXY Flip the direction of X and Y axis.
  24473. * If flipXY is `true`, the X axes will be vertical and Y axes will be horizontal.
  24474. * Note that {@link Ext.chart.axis.Axis#position positions} of chart axes have
  24475. * to be updated accordingly: axes positioned to the `top` and `bottom` should
  24476. * be positioned to the `left` or `right` and vice versa.
  24477. */
  24478. flipXY: false,
  24479. /*
  24480. While it may seem tedious to change the position config of all axes every time
  24481. when the value of the flipXY config is changed, it's hard to predict the
  24482. expectaction of the user here, as illustrated below.
  24483. The 'num' and 'cat' here stand for the numeric and the category axis, respectively.
  24484. And the right column shows the expected (subjective) result of setting the flipXY
  24485. config of the chart to 'true'.
  24486. As one can see, there's no single rule (e.g. position swapping, clockwise 90° chart
  24487. rotation) that will produce a universally accepted result.
  24488. So we are letting the user decide, instead of doing it for them.
  24489. ---------------------------------------------
  24490. | flipXY: false | flipXY: true |
  24491. ---------------------------------------------
  24492. | ^ | ^ |
  24493. | | * | | * * * |
  24494. | num1 | * * | cat | * * |
  24495. | | * * * | | * |
  24496. | --------> | --------> |
  24497. | cat | num1 |
  24498. ---------------------------------------------
  24499. | | num1 |
  24500. | ^ ^ | ^-------> |
  24501. | | * | | | * * * |
  24502. | num1 | * * | num2 | cat | * * |
  24503. | | * * * | | | * |
  24504. | --------> | --------> |
  24505. | cat | num2 |
  24506. ---------------------------------------------
  24507. */
  24508. innerRect: [
  24509. 0,
  24510. 0,
  24511. 1,
  24512. 1
  24513. ],
  24514. /**
  24515. * @cfg {Object} innerPadding The amount of inner padding in pixels.
  24516. * Inner padding is the padding from the innermost axes to the series.
  24517. */
  24518. innerPadding: {
  24519. top: 0,
  24520. left: 0,
  24521. right: 0,
  24522. bottom: 0
  24523. }
  24524. },
  24525. applyInnerPadding: function(padding, oldPadding) {
  24526. if (!Ext.isObject(padding)) {
  24527. return Ext.util.Format.parseBox(padding);
  24528. } else if (!oldPadding) {
  24529. return padding;
  24530. } else {
  24531. return Ext.apply(oldPadding, padding);
  24532. }
  24533. },
  24534. getDirectionForAxis: function(position) {
  24535. var flipXY = this.getFlipXY(),
  24536. direction;
  24537. if (position === 'left' || position === 'right') {
  24538. direction = flipXY ? 'X' : 'Y';
  24539. } else {
  24540. direction = flipXY ? 'Y' : 'X';
  24541. }
  24542. return direction;
  24543. },
  24544. /**
  24545. * Layout the axes and series.
  24546. */
  24547. performLayout: function() {
  24548. var me = this;
  24549. if (me.callParent() === false) {
  24550. return;
  24551. }
  24552. me.chartLayoutCount++;
  24553. me.suspendAnimation();
  24554. // 'chart' surface rect is the size of the chart's inner element
  24555. // (see chart.getChartBox), i.e. the portion of the chart minus
  24556. // the legend area (whether DOM or sprite based).
  24557. // eslint-disable-next-line vars-on-top, one-var
  24558. var chartRect = me.getSurface('chart').getRect(),
  24559. left = chartRect[0],
  24560. top = chartRect[1],
  24561. width = chartRect[2],
  24562. height = chartRect[3],
  24563. captionList = me.captionList,
  24564. axes = me.getAxes(),
  24565. axis,
  24566. seriesList = me.getSeries(),
  24567. series, axisSurface, thickness,
  24568. insetPadding = me.getInsetPadding(),
  24569. innerPadding = me.getInnerPadding(),
  24570. surface, gridSurface,
  24571. // shrinkBox represents padding added on each side by
  24572. // innerPadding & insetPadding configs and the legend.
  24573. shrinkBox = Ext.apply({}, insetPadding),
  24574. mainRect, innerWidth, innerHeight, elements, floating, floatingValue, matrix, i, ln,
  24575. isRtl = me.getInherited().rtl,
  24576. flipXY = me.getFlipXY(),
  24577. caption;
  24578. if (width <= 0 || height <= 0) {
  24579. return;
  24580. }
  24581. me.suspendThicknessChanged();
  24582. for (i = 0; i < axes.length; i++) {
  24583. axis = axes[i];
  24584. axisSurface = axis.getSurface();
  24585. floating = axis.getFloating();
  24586. floatingValue = floating ? floating.value : null;
  24587. thickness = axis.getThickness();
  24588. switch (axis.getPosition()) {
  24589. case 'top':
  24590. axisSurface.setRect([
  24591. left,
  24592. top + shrinkBox.top + 1,
  24593. width,
  24594. thickness
  24595. ]);
  24596. break;
  24597. case 'bottom':
  24598. axisSurface.setRect([
  24599. left,
  24600. top + height - (shrinkBox.bottom + thickness),
  24601. width,
  24602. thickness
  24603. ]);
  24604. break;
  24605. case 'left':
  24606. axisSurface.setRect([
  24607. left + shrinkBox.left,
  24608. top,
  24609. thickness,
  24610. height
  24611. ]);
  24612. break;
  24613. case 'right':
  24614. axisSurface.setRect([
  24615. left + width - (shrinkBox.right + thickness),
  24616. top,
  24617. thickness,
  24618. height
  24619. ]);
  24620. break;
  24621. }
  24622. if (floatingValue === null) {
  24623. shrinkBox[axis.getPosition()] += thickness;
  24624. }
  24625. }
  24626. width -= shrinkBox.left + shrinkBox.right;
  24627. height -= shrinkBox.top + shrinkBox.bottom;
  24628. mainRect = [
  24629. left + shrinkBox.left,
  24630. top + shrinkBox.top,
  24631. width,
  24632. height
  24633. ];
  24634. shrinkBox.left += innerPadding.left;
  24635. shrinkBox.top += innerPadding.top;
  24636. shrinkBox.right += innerPadding.right;
  24637. shrinkBox.bottom += innerPadding.bottom;
  24638. innerWidth = width - innerPadding.left - innerPadding.right;
  24639. innerHeight = height - innerPadding.top - innerPadding.bottom;
  24640. me.setInnerRect([
  24641. shrinkBox.left,
  24642. shrinkBox.top,
  24643. innerWidth,
  24644. innerHeight
  24645. ]);
  24646. if (innerWidth <= 0 || innerHeight <= 0) {
  24647. return;
  24648. }
  24649. me.setMainRect(mainRect);
  24650. me.getSurface().setRect(mainRect);
  24651. for (i = 0 , ln = me.surfaceMap.grid && me.surfaceMap.grid.length; i < ln; i++) {
  24652. gridSurface = me.surfaceMap.grid[i];
  24653. gridSurface.setRect(mainRect);
  24654. gridSurface.matrix.set(1, 0, 0, 1, innerPadding.left, innerPadding.top);
  24655. gridSurface.matrix.inverse(gridSurface.inverseMatrix);
  24656. }
  24657. for (i = 0; i < axes.length; i++) {
  24658. axis = axes[i];
  24659. axis.getRange(true);
  24660. axisSurface = axis.getSurface();
  24661. matrix = axisSurface.matrix;
  24662. elements = matrix.elements;
  24663. switch (axis.getPosition()) {
  24664. case 'top':
  24665. case 'bottom':
  24666. elements[4] = shrinkBox.left;
  24667. axis.setLength(innerWidth);
  24668. break;
  24669. case 'left':
  24670. case 'right':
  24671. elements[5] = shrinkBox.top;
  24672. axis.setLength(innerHeight);
  24673. break;
  24674. }
  24675. axis.updateTitleSprite();
  24676. matrix.inverse(axisSurface.inverseMatrix);
  24677. }
  24678. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  24679. series = seriesList[i];
  24680. surface = series.getSurface();
  24681. surface.setRect(mainRect);
  24682. if (flipXY) {
  24683. if (isRtl) {
  24684. surface.matrix.set(0, -1, -1, 0, innerPadding.left + innerWidth, innerPadding.top + innerHeight);
  24685. } else {
  24686. surface.matrix.set(0, -1, 1, 0, innerPadding.left, innerPadding.top + innerHeight);
  24687. }
  24688. } else {
  24689. surface.matrix.set(1, 0, 0, -1, innerPadding.left, innerPadding.top + innerHeight);
  24690. }
  24691. surface.matrix.inverse(surface.inverseMatrix);
  24692. series.getOverlaySurface().setRect(mainRect);
  24693. }
  24694. if (captionList) {
  24695. for (i = 0 , ln = captionList.length; i < ln; i++) {
  24696. caption = captionList[i];
  24697. if (caption.getAlignTo() === 'series') {
  24698. caption.alignRect(mainRect);
  24699. }
  24700. caption.performLayout();
  24701. }
  24702. }
  24703. // In certain cases 'performLayout' override is not an option without major code duplication
  24704. // 'afterChartLayout' can be a cleaner solution in such cases (because of the timing
  24705. // of its call).
  24706. me.afterChartLayout();
  24707. // currently in cartesian charts only (used by Navigator)
  24708. me.redraw();
  24709. me.resumeAnimation();
  24710. // 'resumeThicknessChanged' may trigger another layout, if the 'redraw' call above
  24711. // resulted in a situation where an axis is no longer 'thick' enough to accommodate
  24712. // the new labels. E.g. the labels were: 'Bob', 'Ann', 'Joe' and now they are 'Jonathan',
  24713. // 'Rachael', 'Michael'. An axis has to be made thicker now, and another layout should be
  24714. // performed. This second layout is not scheduled, but performed immediately, which will
  24715. // increment the 'chartLayoutCount' again.
  24716. me.resumeThicknessChanged();
  24717. me.chartLayoutCount--;
  24718. // 'checkLayoutEnd' will check if another layout is already running or scheduled and,
  24719. // if neither is the case, will fire the 'layout' event, meaning we are totally done
  24720. // with layout at this point.
  24721. me.checkLayoutEnd();
  24722. },
  24723. afterChartLayout: Ext.emptyFn,
  24724. refloatAxes: function() {
  24725. var me = this,
  24726. axes = me.getAxes(),
  24727. axesCount = (axes && axes.length) || 0,
  24728. axis, axisSurface, axisRect, floating, value, alongAxis, matrix,
  24729. chartRect = me.getChartRect(),
  24730. inset = me.getInsetPadding(),
  24731. inner = me.getInnerPadding(),
  24732. width = chartRect[2] - inset.left - inset.right,
  24733. height = chartRect[3] - inset.top - inset.bottom,
  24734. isHorizontal, i;
  24735. for (i = 0; i < axesCount; i++) {
  24736. axis = axes[i];
  24737. floating = axis.getFloating();
  24738. value = floating ? floating.value : null;
  24739. if (value === null) {
  24740. axis.floatingAtCoord = null;
  24741. continue;
  24742. }
  24743. axisSurface = axis.getSurface();
  24744. axisRect = axisSurface.getRect();
  24745. if (!axisRect) {
  24746. continue;
  24747. }
  24748. axisRect = axisRect.slice();
  24749. alongAxis = me.getAxis(floating.alongAxis);
  24750. if (alongAxis) {
  24751. isHorizontal = alongAxis.getAlignment() === 'horizontal';
  24752. if (Ext.isString(value)) {
  24753. value = alongAxis.getCoordFor(value);
  24754. }
  24755. alongAxis.floatingAxes[axis.getId()] = value;
  24756. matrix = alongAxis.getSprites()[0].attr.matrix;
  24757. if (isHorizontal) {
  24758. value = value * matrix.getXX() + matrix.getDX();
  24759. axis.floatingAtCoord = value + inner.left + inner.right;
  24760. } else {
  24761. value = value * matrix.getYY() + matrix.getDY();
  24762. axis.floatingAtCoord = value + inner.top + inner.bottom;
  24763. }
  24764. } else {
  24765. isHorizontal = axis.getAlignment() === 'horizontal';
  24766. if (isHorizontal) {
  24767. axis.floatingAtCoord = value + inner.top + inner.bottom;
  24768. } else {
  24769. axis.floatingAtCoord = value + inner.left + inner.right;
  24770. }
  24771. value = axisSurface.roundPixel(0.01 * value * (isHorizontal ? height : width));
  24772. }
  24773. switch (axis.getPosition()) {
  24774. case 'top':
  24775. axisRect[1] = inset.top + inner.top + value - axisRect[3] + 1;
  24776. break;
  24777. case 'bottom':
  24778. axisRect[1] = inset.top + inner.top + (alongAxis ? value : height - value);
  24779. break;
  24780. case 'left':
  24781. axisRect[0] = inset.left + inner.left + value - axisRect[2];
  24782. break;
  24783. case 'right':
  24784. axisRect[0] = inset.left + inner.left + (alongAxis ? value : width - value) - 1;
  24785. break;
  24786. }
  24787. axisSurface.setRect(axisRect);
  24788. }
  24789. },
  24790. redraw: function() {
  24791. var me = this,
  24792. seriesList = me.getSeries(),
  24793. axes = me.getAxes(),
  24794. rect = me.getMainRect(),
  24795. innerWidth, innerHeight,
  24796. innerPadding = me.getInnerPadding(),
  24797. sprites, xRange, yRange, isSide, attr, i, j, ln, axis, axisX, axisY, range, visibleRange,
  24798. flipXY = me.getFlipXY(),
  24799. zBase = 1000,
  24800. zIndex, markersZIndex, series, sprite, markers;
  24801. if (!rect) {
  24802. return;
  24803. }
  24804. innerWidth = rect[2] - innerPadding.left - innerPadding.right;
  24805. innerHeight = rect[3] - innerPadding.top - innerPadding.bottom;
  24806. for (i = 0; i < seriesList.length; i++) {
  24807. series = seriesList[i];
  24808. axisX = series.getXAxis();
  24809. if (axisX) {
  24810. visibleRange = axisX.getVisibleRange();
  24811. xRange = axisX.getRange();
  24812. xRange = [
  24813. xRange[0] + (xRange[1] - xRange[0]) * visibleRange[0],
  24814. xRange[0] + (xRange[1] - xRange[0]) * visibleRange[1]
  24815. ];
  24816. } else {
  24817. xRange = series.getXRange();
  24818. }
  24819. axisY = series.getYAxis();
  24820. if (axisY) {
  24821. visibleRange = axisY.getVisibleRange();
  24822. yRange = axisY.getRange();
  24823. yRange = [
  24824. yRange[0] + (yRange[1] - yRange[0]) * visibleRange[0],
  24825. yRange[0] + (yRange[1] - yRange[0]) * visibleRange[1]
  24826. ];
  24827. } else {
  24828. yRange = series.getYRange();
  24829. }
  24830. attr = {
  24831. visibleMinX: xRange[0],
  24832. visibleMaxX: xRange[1],
  24833. visibleMinY: yRange[0],
  24834. visibleMaxY: yRange[1],
  24835. innerWidth: innerWidth,
  24836. innerHeight: innerHeight,
  24837. flipXY: flipXY
  24838. };
  24839. sprites = series.getSprites();
  24840. for (j = 0 , ln = sprites.length; j < ln; j++) {
  24841. // All the series now share the same surface, so we must assign
  24842. // the sprites a zIndex that depends on the index of their series.
  24843. sprite = sprites[j];
  24844. zIndex = sprite.attr.zIndex;
  24845. if (zIndex < zBase) {
  24846. // Set the sprite's zIndex
  24847. zIndex += (i + 1) * 100 + zBase;
  24848. sprite.attr.zIndex = zIndex;
  24849. // If the sprite is a MarkerHolder, set zIndex of the bound markers as well.
  24850. // Do this for the 'items' markers only, as those are the only ones
  24851. // that go into the 'series' surface. 'labels' and 'markers' markers
  24852. // go into the 'overlay' surface instead.
  24853. markers = sprite.getMarker('items');
  24854. if (markers) {
  24855. markersZIndex = markers.attr.zIndex;
  24856. if (markersZIndex === Number.MAX_VALUE) {
  24857. markers.attr.zIndex = zIndex;
  24858. } else if (markersZIndex < zBase) {
  24859. markers.attr.zIndex = zIndex + markersZIndex;
  24860. }
  24861. }
  24862. }
  24863. sprite.setAttributes(attr, true);
  24864. }
  24865. }
  24866. for (i = 0; i < axes.length; i++) {
  24867. axis = axes[i];
  24868. isSide = axis.isSide();
  24869. sprites = axis.getSprites();
  24870. range = axis.getRange();
  24871. visibleRange = axis.getVisibleRange();
  24872. attr = {
  24873. dataMin: range[0],
  24874. dataMax: range[1],
  24875. visibleMin: visibleRange[0],
  24876. visibleMax: visibleRange[1]
  24877. };
  24878. if (isSide) {
  24879. attr.length = innerHeight;
  24880. attr.startGap = innerPadding.bottom;
  24881. attr.endGap = innerPadding.top;
  24882. } else {
  24883. attr.length = innerWidth;
  24884. attr.startGap = innerPadding.left;
  24885. attr.endGap = innerPadding.right;
  24886. }
  24887. for (j = 0 , ln = sprites.length; j < ln; j++) {
  24888. sprites[j].setAttributes(attr, true);
  24889. }
  24890. }
  24891. me.renderFrame();
  24892. me.callParent();
  24893. },
  24894. renderFrame: function() {
  24895. this.refloatAxes();
  24896. this.callParent();
  24897. }
  24898. });
  24899. /**
  24900. * @class Ext.chart.grid.CircularGrid
  24901. * @extends Ext.draw.sprite.Circle
  24902. *
  24903. * Circular Grid sprite. Used by Radar chart to render a series of concentric circles.
  24904. */
  24905. Ext.define('Ext.chart.grid.CircularGrid', {
  24906. extend: 'Ext.draw.sprite.Circle',
  24907. alias: 'grid.circular',
  24908. inheritableStatics: {
  24909. def: {
  24910. defaults: {
  24911. r: 1,
  24912. strokeStyle: '#DDD'
  24913. }
  24914. }
  24915. }
  24916. });
  24917. /**
  24918. * @class Ext.chart.grid.RadialGrid
  24919. * @extends Ext.draw.sprite.Path
  24920. *
  24921. * Radial Grid sprite. Used by Radar chart to render a series of radial lines.
  24922. * Represents the scale of the radar chart on the yField.
  24923. */
  24924. Ext.define('Ext.chart.grid.RadialGrid', {
  24925. extend: 'Ext.draw.sprite.Path',
  24926. alias: 'grid.radial',
  24927. inheritableStatics: {
  24928. def: {
  24929. processors: {
  24930. startRadius: 'number',
  24931. endRadius: 'number'
  24932. },
  24933. defaults: {
  24934. startRadius: 0,
  24935. endRadius: 1,
  24936. scalingCenterX: 0,
  24937. scalingCenterY: 0,
  24938. strokeStyle: '#DDD'
  24939. },
  24940. triggers: {
  24941. startRadius: 'path,bbox',
  24942. endRadius: 'path,bbox'
  24943. }
  24944. }
  24945. },
  24946. render: function() {
  24947. this.callParent(arguments);
  24948. },
  24949. updatePath: function(path, attr) {
  24950. var startRadius = attr.startRadius,
  24951. endRadius = attr.endRadius;
  24952. path.moveTo(startRadius, 0);
  24953. path.lineTo(endRadius, 0);
  24954. }
  24955. });
  24956. /**
  24957. * @class Ext.chart.PolarChart
  24958. * @extends Ext.chart.AbstractChart
  24959. * @xtype polar
  24960. *
  24961. * Represent a chart that uses polar coordinates.
  24962. * A polar chart has two axes: an angular axis (which is a circle) and
  24963. * a radial axis (a straight line from the center to the edge of the circle).
  24964. * The angular axis is usually a Category axis while the radial axis is
  24965. * typically numerical.
  24966. *
  24967. * Pie charts and Radar charts are common examples of Polar charts.
  24968. *
  24969. * Please check out the summary for the {@link Ext.chart.AbstractChart} as well,
  24970. * for helpful tips and important details.
  24971. *
  24972. */
  24973. Ext.define('Ext.chart.PolarChart', {
  24974. extend: 'Ext.chart.AbstractChart',
  24975. requires: [
  24976. 'Ext.chart.grid.CircularGrid',
  24977. 'Ext.chart.grid.RadialGrid'
  24978. ],
  24979. xtype: 'polar',
  24980. isPolar: true,
  24981. config: {
  24982. /**
  24983. * @cfg {Array} center Determines the center of the polar chart.
  24984. * Updated when the chart performs layout.
  24985. */
  24986. center: [
  24987. 0,
  24988. 0
  24989. ],
  24990. /**
  24991. * @cfg {Number} radius Determines the radius of the polar chart.
  24992. * Updated when the chart performs layout.
  24993. */
  24994. radius: 0,
  24995. /**
  24996. * @cfg {Number} innerPadding The amount of inner padding in pixels.
  24997. * Inner padding is the padding from the outermost angular axis to the series.
  24998. */
  24999. innerPadding: 0
  25000. },
  25001. getDirectionForAxis: function(position) {
  25002. return position === 'radial' ? 'Y' : 'X';
  25003. },
  25004. updateCenter: function(center) {
  25005. var me = this,
  25006. axes = me.getAxes(),
  25007. series = me.getSeries(),
  25008. i, ln, axis, seriesItem;
  25009. for (i = 0 , ln = axes.length; i < ln; i++) {
  25010. axis = axes[i];
  25011. axis.setCenter(center);
  25012. }
  25013. for (i = 0 , ln = series.length; i < ln; i++) {
  25014. seriesItem = series[i];
  25015. seriesItem.setCenter(center);
  25016. }
  25017. },
  25018. applyInnerPadding: function(padding, oldPadding) {
  25019. return Ext.isNumber(padding) ? padding : oldPadding;
  25020. },
  25021. updateInnerPadding: function() {
  25022. if (!this.isConfiguring) {
  25023. this.performLayout();
  25024. }
  25025. },
  25026. doSetSurfaceRect: function(surface, rect) {
  25027. var mainRect = this.getMainRect();
  25028. surface.setRect(rect);
  25029. surface.matrix.set(1, 0, 0, 1, mainRect[0] - rect[0], mainRect[1] - rect[1]);
  25030. surface.inverseMatrix.set(1, 0, 0, 1, rect[0] - mainRect[0], rect[1] - mainRect[1]);
  25031. },
  25032. applyAxes: function(newAxes, oldAxes) {
  25033. var me = this,
  25034. firstSeries = Ext.Array.from(me.config.series)[0],
  25035. i, ln, axis, foundAngular;
  25036. if (firstSeries && firstSeries.type === 'radar' && newAxes && newAxes.length) {
  25037. // For compatibility with ExtJS: add a default angular axis if it's missing
  25038. for (i = 0 , ln = newAxes.length; i < ln; i++) {
  25039. axis = newAxes[i];
  25040. if (axis.position === 'angular') {
  25041. foundAngular = true;
  25042. break;
  25043. }
  25044. }
  25045. if (!foundAngular) {
  25046. newAxes.push({
  25047. type: 'category',
  25048. position: 'angular',
  25049. fields: firstSeries.xField || firstSeries.angleField,
  25050. style: {
  25051. estStepSize: 1
  25052. },
  25053. grid: true
  25054. });
  25055. }
  25056. }
  25057. return this.callParent([
  25058. newAxes,
  25059. oldAxes
  25060. ]);
  25061. },
  25062. performLayout: function() {
  25063. var me = this,
  25064. applyThickness = true;
  25065. try {
  25066. me.chartLayoutCount++;
  25067. me.suspendAnimation();
  25068. if (this.callParent() === false) {
  25069. applyThickness = false;
  25070. // Animation will be decremented in finally block
  25071. return;
  25072. }
  25073. me.suspendThicknessChanged();
  25074. // eslint-disable-next-line vars-on-top, one-var
  25075. var chartRect = me.getSurface('chart').getRect(),
  25076. inset = me.getInsetPadding(),
  25077. inner = me.getInnerPadding(),
  25078. shrinkBox = Ext.apply({}, inset),
  25079. width = Math.max(1, chartRect[2] - chartRect[0] - inset.left - inset.right),
  25080. height = Math.max(1, chartRect[3] - chartRect[1] - inset.top - inset.bottom),
  25081. mainRect = [
  25082. chartRect[0] + inset.left,
  25083. chartRect[1] + inset.top,
  25084. width + chartRect[0],
  25085. height + chartRect[1]
  25086. ],
  25087. seriesList = me.getSeries(),
  25088. innerWidth = width - inner * 2,
  25089. innerHeight = height - inner * 2,
  25090. center = [
  25091. (chartRect[0] + innerWidth) * 0.5 + inner,
  25092. (chartRect[1] + innerHeight) * 0.5 + inner
  25093. ],
  25094. radius = Math.min(innerWidth, innerHeight) * 0.5,
  25095. axes = me.getAxes(),
  25096. angularAxes = [],
  25097. radialAxes = [],
  25098. seriesRadius = radius - inner,
  25099. grid = me.surfaceMap.grid,
  25100. captionList = me.captionList,
  25101. i, ln, shrinkRadius, floating, floatingValue, // eslint-disable-line no-unused-vars
  25102. gaugeSeries, gaugeRadius, side, series, axis, thickness, halfLineWidth, caption;
  25103. me.setMainRect(mainRect);
  25104. me.doSetSurfaceRect(me.getSurface(), mainRect);
  25105. if (grid) {
  25106. for (i = 0 , ln = grid.length; i < ln; i++) {
  25107. me.doSetSurfaceRect(grid[i], chartRect);
  25108. }
  25109. }
  25110. for (i = 0 , ln = axes.length; i < ln; i++) {
  25111. axis = axes[i];
  25112. switch (axis.getPosition()) {
  25113. case 'angular':
  25114. angularAxes.push(axis);
  25115. break;
  25116. case 'radial':
  25117. radialAxes.push(axis);
  25118. break;
  25119. }
  25120. }
  25121. for (i = 0 , ln = angularAxes.length; i < ln; i++) {
  25122. axis = angularAxes[i];
  25123. floating = axis.getFloating();
  25124. floatingValue = floating ? floating.value : null;
  25125. me.doSetSurfaceRect(axis.getSurface(), chartRect);
  25126. thickness = axis.getThickness();
  25127. for (side in shrinkBox) {
  25128. shrinkBox[side] += thickness;
  25129. }
  25130. width = chartRect[2] - shrinkBox.left - shrinkBox.right;
  25131. height = chartRect[3] - shrinkBox.top - shrinkBox.bottom;
  25132. shrinkRadius = Math.min(width, height) * 0.5;
  25133. if (i === 0) {
  25134. seriesRadius = shrinkRadius - inner;
  25135. }
  25136. axis.setMinimum(0);
  25137. axis.setLength(shrinkRadius);
  25138. axis.getSprites();
  25139. halfLineWidth = axis.sprites[0].attr.lineWidth * 0.5;
  25140. for (side in shrinkBox) {
  25141. shrinkBox[side] += halfLineWidth;
  25142. }
  25143. }
  25144. for (i = 0 , ln = radialAxes.length; i < ln; i++) {
  25145. axis = radialAxes[i];
  25146. me.doSetSurfaceRect(axis.getSurface(), chartRect);
  25147. axis.setMinimum(0);
  25148. axis.setLength(seriesRadius);
  25149. axis.getSprites();
  25150. }
  25151. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  25152. series = seriesList[i];
  25153. if (series.type === 'gauge' && !gaugeSeries) {
  25154. gaugeSeries = series;
  25155. } else {
  25156. series.setRadius(seriesRadius);
  25157. }
  25158. me.doSetSurfaceRect(series.getSurface(), mainRect);
  25159. }
  25160. me.doSetSurfaceRect(me.getSurface('overlay'), chartRect);
  25161. if (gaugeSeries) {
  25162. gaugeSeries.setRect(mainRect);
  25163. gaugeRadius = gaugeSeries.getRadius() - inner;
  25164. me.setRadius(gaugeRadius);
  25165. me.setCenter(gaugeSeries.getCenter());
  25166. gaugeSeries.setRadius(gaugeRadius);
  25167. if (axes.length && axes[0].getPosition() === 'gauge') {
  25168. axis = axes[0];
  25169. me.doSetSurfaceRect(axis.getSurface(), chartRect);
  25170. axis.setTotalAngle(gaugeSeries.getTotalAngle());
  25171. axis.setLength(gaugeRadius);
  25172. }
  25173. } else {
  25174. me.setRadius(radius);
  25175. me.setCenter(center);
  25176. }
  25177. if (captionList) {
  25178. for (i = 0 , ln = captionList.length; i < ln; i++) {
  25179. caption = captionList[i];
  25180. if (caption.getAlignTo() === 'series') {
  25181. caption.alignRect(mainRect);
  25182. }
  25183. caption.performLayout();
  25184. }
  25185. }
  25186. me.redraw();
  25187. } finally {
  25188. me.resumeAnimation();
  25189. if (applyThickness) {
  25190. me.resumeThicknessChanged();
  25191. }
  25192. me.chartLayoutCount--;
  25193. me.checkLayoutEnd();
  25194. }
  25195. },
  25196. refloatAxes: function() {
  25197. var me = this,
  25198. axes = me.getAxes(),
  25199. mainRect = me.getMainRect(),
  25200. floating, value, alongAxis, i, n, axis, radius;
  25201. if (!mainRect) {
  25202. return;
  25203. }
  25204. radius = 0.5 * Math.min(mainRect[2], mainRect[3]);
  25205. for (i = 0 , n = axes.length; i < n; i++) {
  25206. axis = axes[i];
  25207. floating = axis.getFloating();
  25208. value = floating ? floating.value : null;
  25209. if (value !== null) {
  25210. alongAxis = me.getAxis(floating.alongAxis);
  25211. if (axis.getPosition() === 'angular') {
  25212. if (alongAxis) {
  25213. value = alongAxis.getLength() * value / alongAxis.getRange()[1];
  25214. } else {
  25215. value = 0.01 * value * radius;
  25216. }
  25217. axis.sprites[0].setAttributes({
  25218. length: value
  25219. }, true);
  25220. } else {
  25221. if (alongAxis) {
  25222. if (Ext.isString(value)) {
  25223. value = alongAxis.getCoordFor(value);
  25224. }
  25225. value = value / (alongAxis.getRange()[1] + 1) * Math.PI * 2 - Math.PI * 1.5 + axis.getRotation();
  25226. } else {
  25227. value = Ext.draw.Draw.rad(value);
  25228. }
  25229. axis.sprites[0].setAttributes({
  25230. baseRotation: value
  25231. }, true);
  25232. }
  25233. }
  25234. }
  25235. },
  25236. redraw: function() {
  25237. var me = this,
  25238. axes = me.getAxes(),
  25239. axis,
  25240. seriesList = me.getSeries(),
  25241. series, i, ln;
  25242. for (i = 0 , ln = axes.length; i < ln; i++) {
  25243. axis = axes[i];
  25244. axis.getSprites();
  25245. }
  25246. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  25247. series = seriesList[i];
  25248. series.getSprites();
  25249. }
  25250. me.renderFrame();
  25251. me.callParent();
  25252. },
  25253. renderFrame: function() {
  25254. this.refloatAxes();
  25255. this.callParent();
  25256. }
  25257. });
  25258. /**
  25259. * @class Ext.chart.SpaceFillingChart
  25260. * @extends Ext.chart.AbstractChart
  25261. *
  25262. * Creates a chart that fills the entire area of the chart.
  25263. * e.g. Gauge Charts
  25264. */
  25265. Ext.define('Ext.chart.SpaceFillingChart', {
  25266. extend: 'Ext.chart.AbstractChart',
  25267. xtype: 'spacefilling',
  25268. config: {},
  25269. performLayout: function() {
  25270. var me = this;
  25271. try {
  25272. me.chartLayoutCount++;
  25273. me.suspendAnimation();
  25274. if (me.callParent() === false) {
  25275. // animationSuspendCount will still be decremented
  25276. return;
  25277. }
  25278. // eslint-disable-next-line vars-on-top, one-var
  25279. var chartRect = me.getSurface('chart').getRect(),
  25280. padding = me.getInsetPadding(),
  25281. width = chartRect[2] - padding.left - padding.right,
  25282. height = chartRect[3] - padding.top - padding.bottom,
  25283. mainRect = [
  25284. padding.left,
  25285. padding.top,
  25286. width,
  25287. height
  25288. ],
  25289. seriesList = me.getSeries(),
  25290. series, i, ln;
  25291. me.getSurface().setRect(mainRect);
  25292. me.setMainRect(mainRect);
  25293. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  25294. series = seriesList[i];
  25295. series.getSurface().setRect(mainRect);
  25296. if (series.setRect) {
  25297. series.setRect(mainRect);
  25298. }
  25299. series.getOverlaySurface().setRect(chartRect);
  25300. }
  25301. me.redraw();
  25302. } finally {
  25303. me.resumeAnimation();
  25304. me.chartLayoutCount--;
  25305. me.checkLayoutEnd();
  25306. }
  25307. },
  25308. redraw: function() {
  25309. var me = this,
  25310. seriesList = me.getSeries(),
  25311. series, i, ln;
  25312. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  25313. series = seriesList[i];
  25314. series.getSprites();
  25315. }
  25316. me.renderFrame();
  25317. me.callParent();
  25318. }
  25319. });
  25320. /**
  25321. * @private
  25322. * @class Ext.chart.axis.sprite.Axis3D
  25323. * @extends Ext.chart.axis.sprite.Axis
  25324. *
  25325. * The {@link Ext.chart.axis.Axis3D 3D axis} sprite.
  25326. * Only 3D cartesian axes are rendered with this sprite.
  25327. */
  25328. Ext.define('Ext.chart.axis.sprite.Axis3D', {
  25329. extend: 'Ext.chart.axis.sprite.Axis',
  25330. alias: 'sprite.axis3d',
  25331. type: 'axis3d',
  25332. inheritableStatics: {
  25333. def: {
  25334. processors: {
  25335. depth: 'number'
  25336. },
  25337. defaults: {
  25338. depth: 0
  25339. },
  25340. triggers: {
  25341. depth: 'layout'
  25342. }
  25343. }
  25344. },
  25345. config: {
  25346. animation: {
  25347. customDurations: {
  25348. depth: 0
  25349. }
  25350. }
  25351. },
  25352. layoutUpdater: function() {
  25353. var me = this,
  25354. chart = me.getAxis().getChart();
  25355. if (chart.isInitializing) {
  25356. return;
  25357. }
  25358. // eslint-disable-next-line vars-on-top, one-var
  25359. var attr = me.attr,
  25360. layout = me.getLayout(),
  25361. depth = layout.isDiscrete ? 0 : attr.depth,
  25362. isRtl = chart.getInherited().rtl,
  25363. min = attr.dataMin + (attr.dataMax - attr.dataMin) * attr.visibleMin,
  25364. max = attr.dataMin + (attr.dataMax - attr.dataMin) * attr.visibleMax,
  25365. context = {
  25366. attr: attr,
  25367. segmenter: me.getSegmenter(),
  25368. renderer: me.defaultRenderer
  25369. };
  25370. if (attr.position === 'left' || attr.position === 'right') {
  25371. attr.translationX = 0;
  25372. attr.translationY = max * (attr.length - depth) / (max - min) + depth;
  25373. attr.scalingX = 1;
  25374. attr.scalingY = (-attr.length + depth) / (max - min);
  25375. attr.scalingCenterY = 0;
  25376. attr.scalingCenterX = 0;
  25377. me.applyTransformations(true);
  25378. } else if (attr.position === 'top' || attr.position === 'bottom') {
  25379. if (isRtl) {
  25380. attr.translationX = attr.length + min * attr.length / (max - min) + 1;
  25381. } else {
  25382. attr.translationX = -min * attr.length / (max - min);
  25383. }
  25384. attr.translationY = 0;
  25385. attr.scalingX = (isRtl ? -1 : 1) * (attr.length - depth) / (max - min);
  25386. attr.scalingY = 1;
  25387. attr.scalingCenterY = 0;
  25388. attr.scalingCenterX = 0;
  25389. me.applyTransformations(true);
  25390. }
  25391. if (layout) {
  25392. layout.calculateLayout(context);
  25393. me.setLayoutContext(context);
  25394. }
  25395. },
  25396. renderAxisLine: function(surface, ctx, layout, clipRect) {
  25397. var me = this,
  25398. attr = me.attr,
  25399. halfLineWidth = attr.lineWidth * 0.5,
  25400. layout = me.getLayout(),
  25401. // eslint-disable-line no-redeclare
  25402. depth = layout.isDiscrete ? 0 : attr.depth,
  25403. docked = attr.position,
  25404. position, gaugeAngles;
  25405. if (attr.axisLine && attr.length) {
  25406. switch (docked) {
  25407. case 'left':
  25408. position = surface.roundPixel(clipRect[2]) - halfLineWidth;
  25409. ctx.moveTo(position, -attr.endGap + depth);
  25410. ctx.lineTo(position, attr.length + attr.startGap);
  25411. break;
  25412. case 'right':
  25413. ctx.moveTo(halfLineWidth, -attr.endGap);
  25414. ctx.lineTo(halfLineWidth, attr.length + attr.startGap);
  25415. break;
  25416. case 'bottom':
  25417. ctx.moveTo(-attr.startGap, halfLineWidth);
  25418. ctx.lineTo(attr.length - depth + attr.endGap, halfLineWidth);
  25419. break;
  25420. case 'top':
  25421. position = surface.roundPixel(clipRect[3]) - halfLineWidth;
  25422. ctx.moveTo(-attr.startGap, position);
  25423. ctx.lineTo(attr.length + attr.endGap, position);
  25424. break;
  25425. case 'angular':
  25426. ctx.moveTo(attr.centerX + attr.length, attr.centerY);
  25427. ctx.arc(attr.centerX, attr.centerY, attr.length, 0, Math.PI * 2, true);
  25428. break;
  25429. case 'gauge':
  25430. gaugeAngles = me.getGaugeAngles();
  25431. ctx.moveTo(attr.centerX + Math.cos(gaugeAngles.start) * attr.length, attr.centerY + Math.sin(gaugeAngles.start) * attr.length);
  25432. ctx.arc(attr.centerX, attr.centerY, attr.length, gaugeAngles.start, gaugeAngles.end, true);
  25433. break;
  25434. }
  25435. }
  25436. }
  25437. });
  25438. /**
  25439. * @class Ext.chart.axis.Axis3D
  25440. * @extends Ext.chart.axis.Axis
  25441. * @xtype axis3d
  25442. *
  25443. * Defines a 3D axis for charts.
  25444. *
  25445. * A 3D axis has the same properties as the regular {@link Ext.chart.axis.Axis axis},
  25446. * plus a notion of depth. The depth of the 3D axis is determined automatically
  25447. * based on the depth of the bound series.
  25448. *
  25449. * This type of axis has the following limitations compared to the regular axis class:
  25450. * - supported {@link Ext.chart.axis.Axis#position positions} are 'left' and 'bottom' only;
  25451. * - floating axes are not supported.
  25452. *
  25453. * At the present moment only {@link Ext.chart.series.Bar3D} series can make use of the 3D axis.
  25454. */
  25455. Ext.define('Ext.chart.axis.Axis3D', {
  25456. extend: 'Ext.chart.axis.Axis',
  25457. xtype: 'axis3d',
  25458. requires: [
  25459. 'Ext.chart.axis.sprite.Axis3D'
  25460. ],
  25461. config: {
  25462. /**
  25463. * @private
  25464. * The depth of the axis. Determined automatically.
  25465. */
  25466. depth: 0
  25467. },
  25468. /**
  25469. * @cfg {String} position
  25470. * Where to set the axis. Available options are `left` and `bottom`.
  25471. */
  25472. onSeriesChange: function(chart) {
  25473. var me = this,
  25474. eventName = 'depthchange',
  25475. listenerName = 'onSeriesDepthChange',
  25476. i, series;
  25477. function toggle(action) {
  25478. var boundSeries = me.boundSeries;
  25479. for (i = 0; i < boundSeries.length; i++) {
  25480. series = boundSeries[i];
  25481. series[action](eventName, listenerName, me);
  25482. }
  25483. }
  25484. // Remove 'depthchange' listeners from old bound series, if any.
  25485. toggle('un');
  25486. me.callParent(arguments);
  25487. // Add 'depthchange' listeners to new bound series.
  25488. toggle('on');
  25489. },
  25490. onSeriesDepthChange: function(series, depth) {
  25491. var me = this,
  25492. maxDepth = depth,
  25493. boundSeries = me.boundSeries,
  25494. i, item;
  25495. if (depth > me.getDepth()) {
  25496. maxDepth = depth;
  25497. } else {
  25498. for (i = 0; i < boundSeries.length; i++) {
  25499. item = boundSeries[i];
  25500. if (item !== series && item.getDepth) {
  25501. depth = item.getDepth();
  25502. if (depth > maxDepth) {
  25503. maxDepth = depth;
  25504. }
  25505. }
  25506. }
  25507. }
  25508. me.setDepth(maxDepth);
  25509. },
  25510. updateDepth: function(depth) {
  25511. var me = this,
  25512. sprites = me.getSprites(),
  25513. attr = {
  25514. depth: depth
  25515. };
  25516. if (sprites && sprites.length) {
  25517. sprites[0].setAttributes(attr);
  25518. }
  25519. if (me.gridSpriteEven && me.gridSpriteOdd) {
  25520. me.gridSpriteEven.getTemplate().setAttributes(attr);
  25521. me.gridSpriteOdd.getTemplate().setAttributes(attr);
  25522. }
  25523. },
  25524. getGridAlignment: function() {
  25525. switch (this.getPosition()) {
  25526. case 'left':
  25527. case 'right':
  25528. return 'horizontal3d';
  25529. case 'top':
  25530. case 'bottom':
  25531. return 'vertical3d';
  25532. }
  25533. }
  25534. });
  25535. /**
  25536. * @class Ext.chart.axis.Category
  25537. * @extends Ext.chart.axis.Axis
  25538. *
  25539. * A type of axis that displays items in categories. This axis is generally used to
  25540. * display categorical information like names of items, month names, quarters, etc.
  25541. * but no quantitative values. For that other type of information
  25542. * {@link Ext.chart.axis.Numeric Numeric} axis are more suitable.
  25543. *
  25544. * As with other axis you can set the position of the axis and its title. For example:
  25545. *
  25546. * @example
  25547. * Ext.create({
  25548. * xtype: 'cartesian',
  25549. * renderTo: document.body,
  25550. * width: 600,
  25551. * height: 400,
  25552. * innerPadding: '0 40 0 40',
  25553. * store: {
  25554. * fields: ['name', 'data1', 'data2', 'data3'],
  25555. * data: [{
  25556. * 'name': 'metric one',
  25557. * 'data1': 10,
  25558. * 'data2': 12,
  25559. * 'data3': 14
  25560. * }, {
  25561. * 'name': 'metric two',
  25562. * 'data1': 7,
  25563. * 'data2': 8,
  25564. * 'data3': 16
  25565. * }, {
  25566. * 'name': 'metric three',
  25567. * 'data1': 5,
  25568. * 'data2': 2,
  25569. * 'data3': 14
  25570. * }, {
  25571. * 'name': 'metric four',
  25572. * 'data1': 2,
  25573. * 'data2': 14,
  25574. * 'data3': 6
  25575. * }, {
  25576. * 'name': 'metric five',
  25577. * 'data1': 27,
  25578. * 'data2': 38,
  25579. * 'data3': 36
  25580. * }]
  25581. * },
  25582. * axes: {
  25583. * type: 'category',
  25584. * position: 'bottom',
  25585. * fields: ['name'],
  25586. * title: {
  25587. * text: 'Sample Values',
  25588. * fontSize: 15
  25589. * }
  25590. * },
  25591. * series: {
  25592. * type: 'area',
  25593. * subStyle: {
  25594. * fill: ['#0A3F50', '#30BDA7', '#96D4C6']
  25595. * },
  25596. * xField: 'name',
  25597. * yField: ['data1', 'data2', 'data3']
  25598. * }
  25599. * });
  25600. *
  25601. * In this example with set the category axis to the bottom of the surface, bound the axis to
  25602. * the `name` property and set as title "Sample Values".
  25603. */
  25604. Ext.define('Ext.chart.axis.Category', {
  25605. requires: [
  25606. 'Ext.chart.axis.layout.CombineDuplicate',
  25607. 'Ext.chart.axis.segmenter.Names'
  25608. ],
  25609. extend: 'Ext.chart.axis.Axis',
  25610. alias: 'axis.category',
  25611. type: 'category',
  25612. isCategory: true,
  25613. config: {
  25614. layout: 'combineDuplicate',
  25615. segmenter: 'names'
  25616. }
  25617. });
  25618. /**
  25619. * Category 3D Axis
  25620. */
  25621. Ext.define('Ext.chart.axis.Category3D', {
  25622. requires: [
  25623. 'Ext.chart.axis.layout.CombineDuplicate',
  25624. 'Ext.chart.axis.segmenter.Names'
  25625. ],
  25626. extend: 'Ext.chart.axis.Axis3D',
  25627. alias: 'axis.category3d',
  25628. type: 'category3d',
  25629. config: {
  25630. layout: 'combineDuplicate',
  25631. segmenter: 'names'
  25632. }
  25633. });
  25634. /**
  25635. * @class Ext.chart.axis.Numeric
  25636. * @extends Ext.chart.axis.Axis
  25637. *
  25638. * An axis to handle numeric values. This axis is used for quantitative data as
  25639. * opposed to the category axis. You can set minimum and maximum values to the
  25640. * axis so that the values are bound to that. If no values are set, then the
  25641. * scale will auto-adjust to the values.
  25642. *
  25643. * @example
  25644. * Ext.create({
  25645. * xtype: 'cartesian',
  25646. * renderTo: document.body,
  25647. * width: 600,
  25648. * height: 400,
  25649. * store: {
  25650. * fields: ['name', 'data1', 'data2', 'data3'],
  25651. * data: [{
  25652. * 'name': 1,
  25653. * 'data1': 10,
  25654. * 'data2': 12,
  25655. * 'data3': 14
  25656. * }, {
  25657. * 'name': 2,
  25658. * 'data1': 7,
  25659. * 'data2': 8,
  25660. * 'data3': 16
  25661. * }, {
  25662. * 'name': 3,
  25663. * 'data1': 5,
  25664. * 'data2': 2,
  25665. * 'data3': 14
  25666. * }, {
  25667. * 'name': 4,
  25668. * 'data1': 2,
  25669. * 'data2': 14,
  25670. * 'data3': 6
  25671. * }, {
  25672. * 'name': 5,
  25673. * 'data1': 27,
  25674. * 'data2': 38,
  25675. * 'data3': 36
  25676. * }]
  25677. * },
  25678. * axes: {
  25679. * type: 'numeric',
  25680. * position: 'left',
  25681. * minimum: 0,
  25682. * fields: ['data1', 'data2', 'data3'],
  25683. * title: 'Sample Values',
  25684. * grid: {
  25685. * odd: {
  25686. * opacity: 1,
  25687. * fill: '#F2F2F2',
  25688. * stroke: '#DDD',
  25689. * 'lineWidth': 1
  25690. * }
  25691. * }
  25692. * },
  25693. * series: {
  25694. * type: 'area',
  25695. * subStyle: {
  25696. * fill: ['#0A3F50', '#30BDA7', '#96D4C6']
  25697. * },
  25698. * xField: 'name',
  25699. * yField: ['data1', 'data2', 'data3']
  25700. * }
  25701. * });
  25702. *
  25703. * In this example we create an axis of Numeric type. We set a minimum value so that
  25704. * even if all series have values greater than zero, the grid starts at zero. We bind
  25705. * the axis onto the left part of the surface by setting _position_ to _left_.
  25706. * We bind three different store fields to this axis by setting _fields_ to an array.
  25707. * We set the title of the axis to _Number of Hits_ by using the _title_ property.
  25708. * We use a _grid_ configuration to set odd background rows to a certain style and even rows
  25709. * to be transparent/ignored.
  25710. *
  25711. */
  25712. Ext.define('Ext.chart.axis.Numeric', {
  25713. extend: 'Ext.chart.axis.Axis',
  25714. type: 'numeric',
  25715. alias: [
  25716. 'axis.numeric',
  25717. 'axis.radial'
  25718. ],
  25719. // legacy charts compatibility
  25720. requires: [
  25721. 'Ext.chart.axis.layout.Continuous',
  25722. 'Ext.chart.axis.segmenter.Numeric'
  25723. ],
  25724. config: {
  25725. layout: 'continuous',
  25726. segmenter: 'numeric',
  25727. aggregator: 'double'
  25728. }
  25729. });
  25730. /**
  25731. * @class Ext.chart.axis.Numeric3D
  25732. */
  25733. Ext.define('Ext.chart.axis.Numeric3D', {
  25734. extend: 'Ext.chart.axis.Axis3D',
  25735. alias: [
  25736. 'axis.numeric3d'
  25737. ],
  25738. type: 'numeric3d',
  25739. requires: [
  25740. 'Ext.chart.axis.layout.Continuous',
  25741. 'Ext.chart.axis.segmenter.Numeric'
  25742. ],
  25743. config: {
  25744. layout: 'continuous',
  25745. segmenter: 'numeric',
  25746. aggregator: 'double'
  25747. }
  25748. });
  25749. /**
  25750. * @class Ext.chart.axis.Time
  25751. * @extends Ext.chart.axis.Numeric
  25752. *
  25753. * A type of axis whose units are measured in time values. Use this axis
  25754. * for listing dates that you will want to group or dynamically change.
  25755. * If you just want to display dates as categories then use the
  25756. * Category class for axis instead.
  25757. *
  25758. * @example
  25759. * Ext.create({
  25760. * xtype: 'cartesian',
  25761. * renderTo: document.body,
  25762. * width: 600,
  25763. * height: 400,
  25764. * store: {
  25765. * fields: ['time', 'open', 'high', 'low', 'close'],
  25766. * data: [{
  25767. * 'time': new Date('Jan 1 2010').getTime(),
  25768. * 'open': 600,
  25769. * 'high': 614,
  25770. * 'low': 578,
  25771. * 'close': 590
  25772. * }, {
  25773. * 'time': new Date('Jan 2 2010').getTime(),
  25774. * 'open': 590,
  25775. * 'high': 609,
  25776. * 'low': 580,
  25777. * 'close': 580
  25778. * }, {
  25779. * 'time': new Date('Jan 3 2010').getTime(),
  25780. * 'open': 580,
  25781. * 'high': 602,
  25782. * 'low': 578,
  25783. * 'close': 602
  25784. * }, {
  25785. * 'time': new Date('Jan 4 2010').getTime(),
  25786. * 'open': 602,
  25787. * 'high': 614,
  25788. * 'low': 586,
  25789. * 'close': 586
  25790. * }]
  25791. * },
  25792. * axes: [{
  25793. * type: 'numeric',
  25794. * position: 'left',
  25795. * fields: ['open', 'high', 'low', 'close'],
  25796. * title: {
  25797. * text: 'Sample Values',
  25798. * fontSize: 15
  25799. * },
  25800. * grid: true,
  25801. * minimum: 560,
  25802. * maximum: 640
  25803. * }, {
  25804. * type: 'time',
  25805. * position: 'bottom',
  25806. * fields: ['time'],
  25807. * fromDate: new Date('Dec 31 2009'),
  25808. * toDate: new Date('Jan 5 2010'),
  25809. * title: {
  25810. * text: 'Sample Values',
  25811. * fontSize: 15
  25812. * },
  25813. * style: {
  25814. * axisLine: false
  25815. * }
  25816. * }],
  25817. * series: {
  25818. * type: 'candlestick',
  25819. * xField: 'time',
  25820. * openField: 'open',
  25821. * highField: 'high',
  25822. * lowField: 'low',
  25823. * closeField: 'close',
  25824. * style: {
  25825. * ohlcType: 'ohlc',
  25826. * dropStyle: {
  25827. * fill: 'rgb(255, 128, 128)',
  25828. * stroke: 'rgb(255, 128, 128)',
  25829. * lineWidth: 3
  25830. * },
  25831. * raiseStyle: {
  25832. * fill: 'rgb(48, 189, 167)',
  25833. * stroke: 'rgb(48, 189, 167)',
  25834. * lineWidth: 3
  25835. * }
  25836. * }
  25837. * }
  25838. * });
  25839. */
  25840. Ext.define('Ext.chart.axis.Time', {
  25841. extend: 'Ext.chart.axis.Numeric',
  25842. alias: 'axis.time',
  25843. type: 'time',
  25844. requires: [
  25845. 'Ext.chart.axis.layout.Continuous',
  25846. 'Ext.chart.axis.segmenter.Time'
  25847. ],
  25848. config: {
  25849. /**
  25850. * @cfg {String} dateFormat
  25851. * Indicates the format the date will be rendered in.
  25852. * For example: 'M d' will render the dates as 'Jan 30'.
  25853. * This config works by setting the {@link #renderer} config
  25854. * to a function that uses {@link Ext.Date#format} to format the dates
  25855. * using the given `dateFormat`.
  25856. * If the {@link #renderer} config was set by the user, changes to this config
  25857. * won't replace the user set renderer (until the user removes the renderer by
  25858. * setting the `renderer` config to `null`). In this case the way the `dateFormat`
  25859. * is used (if at all) is up to the user.
  25860. */
  25861. dateFormat: null,
  25862. /**
  25863. * @cfg {Date} fromDate The starting date for the time axis.
  25864. */
  25865. fromDate: null,
  25866. /**
  25867. * @cfg {Date} toDate The ending date for the time axis.
  25868. */
  25869. toDate: null,
  25870. layout: 'continuous',
  25871. segmenter: 'time',
  25872. aggregator: 'time'
  25873. },
  25874. updateDateFormat: function(format) {
  25875. var renderer = this.getRenderer();
  25876. if (!renderer || renderer.isDefault) {
  25877. renderer = function(axis, date) {
  25878. return Ext.Date.format(new Date(date), format);
  25879. };
  25880. renderer.isDefault = true;
  25881. this.setRenderer(renderer);
  25882. this.performLayout();
  25883. }
  25884. },
  25885. updateRenderer: function(renderer) {
  25886. var dateFormat = this.getDateFormat();
  25887. if (renderer) {
  25888. this.performLayout();
  25889. } else if (dateFormat) {
  25890. // If the user removes custom `renderer` and `dateFormat` is set,
  25891. // set the `renderer` to the default one based on `dateFormat`.
  25892. this.updateDateFormat(dateFormat);
  25893. }
  25894. },
  25895. updateFromDate: function(date) {
  25896. this.setMinimum(+date);
  25897. },
  25898. updateToDate: function(date) {
  25899. this.setMaximum(+date);
  25900. },
  25901. getCoordFor: function(value) {
  25902. if (Ext.isString(value)) {
  25903. value = new Date(value);
  25904. }
  25905. return +value;
  25906. }
  25907. });
  25908. /**
  25909. * @class Ext.chart.axis.Time3D
  25910. */
  25911. Ext.define('Ext.chart.axis.Time3D', {
  25912. extend: 'Ext.chart.axis.Numeric3D',
  25913. alias: 'axis.time3d',
  25914. type: 'time3d',
  25915. requires: [
  25916. 'Ext.chart.axis.layout.Continuous',
  25917. 'Ext.chart.axis.segmenter.Time'
  25918. ],
  25919. config: {
  25920. /**
  25921. * @cfg {String/Boolean} dateFormat
  25922. * Indicates the format the date will be rendered on.
  25923. * For example: 'M d' will render the dates as 'Jan 30', etc.
  25924. */
  25925. dateFormat: null,
  25926. /**
  25927. * @cfg {Date} fromDate The starting date for the time axis.
  25928. */
  25929. fromDate: null,
  25930. /**
  25931. * @cfg {Date} toDate The ending date for the time axis.
  25932. */
  25933. toDate: null,
  25934. layout: 'continuous',
  25935. segmenter: 'time',
  25936. aggregator: 'time'
  25937. },
  25938. updateDateFormat: function(format) {
  25939. this.setRenderer(function(axis, date) {
  25940. return Ext.Date.format(new Date(date), format);
  25941. });
  25942. },
  25943. updateFromDate: function(date) {
  25944. this.setMinimum(+date);
  25945. },
  25946. updateToDate: function(date) {
  25947. this.setMaximum(+date);
  25948. },
  25949. getCoordFor: function(value) {
  25950. if (Ext.isString(value)) {
  25951. value = new Date(value);
  25952. }
  25953. return +value;
  25954. }
  25955. });
  25956. /**
  25957. * @class Ext.chart.grid.HorizontalGrid3D
  25958. * @extends Ext.chart.grid.HorizontalGrid
  25959. *
  25960. * Horizontal 3D Grid sprite. Used in 3D Cartesian Charts.
  25961. */
  25962. Ext.define('Ext.chart.grid.HorizontalGrid3D', {
  25963. extend: 'Ext.chart.grid.HorizontalGrid',
  25964. alias: 'grid.horizontal3d',
  25965. inheritableStatics: {
  25966. def: {
  25967. processors: {
  25968. depth: 'number'
  25969. },
  25970. defaults: {
  25971. depth: 0
  25972. }
  25973. }
  25974. },
  25975. render: function(surface, ctx, rect) {
  25976. var attr = this.attr,
  25977. x = surface.roundPixel(attr.x),
  25978. y = surface.roundPixel(attr.y),
  25979. dx = surface.matrix.getDX(),
  25980. halfLineWidth = ctx.lineWidth * 0.5,
  25981. height = attr.height,
  25982. depth = attr.depth,
  25983. left, top;
  25984. if (y <= rect[1]) {
  25985. return;
  25986. }
  25987. // Horizontal stripe.
  25988. left = rect[0] + depth - dx;
  25989. top = y + halfLineWidth - depth;
  25990. ctx.beginPath();
  25991. ctx.rect(left, top, rect[2], height);
  25992. ctx.fill();
  25993. // Horizontal line.
  25994. ctx.beginPath();
  25995. ctx.moveTo(left, top);
  25996. ctx.lineTo(left + rect[2], top);
  25997. ctx.stroke();
  25998. // Diagonal stripe.
  25999. left = rect[0] + x - dx;
  26000. top = y + halfLineWidth;
  26001. ctx.beginPath();
  26002. ctx.moveTo(left, top);
  26003. ctx.lineTo(left + depth, top - depth);
  26004. ctx.lineTo(left + depth, top - depth + height);
  26005. ctx.lineTo(left, top + height);
  26006. ctx.closePath();
  26007. ctx.fill();
  26008. // Diagonal line.
  26009. ctx.beginPath();
  26010. ctx.moveTo(left, top);
  26011. ctx.lineTo(left + depth, top - depth);
  26012. ctx.stroke();
  26013. }
  26014. });
  26015. /**
  26016. * @class Ext.chart.grid.VerticalGrid3D
  26017. * @extends Ext.chart.grid.VerticalGrid
  26018. *
  26019. * Vertical 3D Grid sprite. Used in 3D Cartesian Charts.
  26020. */
  26021. Ext.define('Ext.chart.grid.VerticalGrid3D', {
  26022. extend: 'Ext.chart.grid.VerticalGrid',
  26023. alias: 'grid.vertical3d',
  26024. inheritableStatics: {
  26025. def: {
  26026. processors: {
  26027. depth: 'number'
  26028. },
  26029. defaults: {
  26030. depth: 0
  26031. }
  26032. }
  26033. },
  26034. render: function(surface, ctx, clipRect) {
  26035. var attr = this.attr,
  26036. x = surface.roundPixel(attr.x),
  26037. dy = surface.matrix.getDY(),
  26038. halfLineWidth = ctx.lineWidth * 0.5,
  26039. width = attr.width,
  26040. depth = attr.depth,
  26041. left, top;
  26042. if (x >= clipRect[2]) {
  26043. return;
  26044. }
  26045. // Vertical stripe.
  26046. left = x - halfLineWidth + depth;
  26047. top = clipRect[1] - depth - dy;
  26048. ctx.beginPath();
  26049. ctx.rect(left, top, width, clipRect[3]);
  26050. ctx.fill();
  26051. // Vertical line.
  26052. ctx.beginPath();
  26053. ctx.moveTo(left, top);
  26054. ctx.lineTo(left, top + clipRect[3]);
  26055. ctx.stroke();
  26056. // Diagonal stripe.
  26057. left = x - halfLineWidth;
  26058. top = clipRect[3];
  26059. ctx.beginPath();
  26060. ctx.moveTo(left, top);
  26061. ctx.lineTo(left + depth, top - depth);
  26062. ctx.lineTo(left + depth + width, top - depth);
  26063. ctx.lineTo(left + width, top);
  26064. ctx.closePath();
  26065. ctx.fill();
  26066. // Diagonal line.
  26067. left = x - halfLineWidth;
  26068. top = clipRect[3];
  26069. ctx.beginPath();
  26070. ctx.moveTo(left, top);
  26071. ctx.lineTo(left + depth, top - depth);
  26072. ctx.stroke();
  26073. }
  26074. });
  26075. /**
  26076. * @class Ext.chart.interactions.CrossZoom
  26077. * @extends Ext.chart.interactions.Abstract
  26078. *
  26079. * The CrossZoom interaction allows the user to zoom in on a selected area of the chart.
  26080. *
  26081. * @example
  26082. * Ext.create({
  26083. * xtype: 'cartesian',
  26084. * renderTo: Ext.getBody(),
  26085. * width: 600,
  26086. * height: 400,
  26087. * insetPadding: 40,
  26088. * interactions: 'crosszoom',
  26089. * store: {
  26090. * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
  26091. * data: [{
  26092. * 'name': 'metric one',
  26093. * 'data1': 10,
  26094. * 'data2': 12,
  26095. * 'data3': 14,
  26096. * 'data4': 8,
  26097. * 'data5': 13
  26098. * }, {
  26099. * 'name': 'metric two',
  26100. * 'data1': 7,
  26101. * 'data2': 8,
  26102. * 'data3': 16,
  26103. * 'data4': 10,
  26104. * 'data5': 3
  26105. * }, {
  26106. * 'name': 'metric three',
  26107. * 'data1': 5,
  26108. * 'data2': 2,
  26109. * 'data3': 14,
  26110. * 'data4': 12,
  26111. * 'data5': 7
  26112. * }, {
  26113. * 'name': 'metric four',
  26114. * 'data1': 2,
  26115. * 'data2': 14,
  26116. * 'data3': 6,
  26117. * 'data4': 1,
  26118. * 'data5': 23
  26119. * }, {
  26120. * 'name': 'metric five',
  26121. * 'data1': 27,
  26122. * 'data2': 38,
  26123. * 'data3': 36,
  26124. * 'data4': 13,
  26125. * 'data5': 33
  26126. * }]
  26127. * },
  26128. * axes: [{
  26129. * type: 'numeric',
  26130. * position: 'left',
  26131. * fields: ['data1'],
  26132. * title: {
  26133. * text: 'Sample Values',
  26134. * fontSize: 15
  26135. * },
  26136. * grid: true,
  26137. * minimum: 0
  26138. * }, {
  26139. * type: 'category',
  26140. * position: 'bottom',
  26141. * fields: ['name'],
  26142. * title: {
  26143. * text: 'Sample Values',
  26144. * fontSize: 15
  26145. * }
  26146. * }],
  26147. * series: [{
  26148. * type: 'line',
  26149. * highlight: {
  26150. * size: 7,
  26151. * radius: 7
  26152. * },
  26153. * style: {
  26154. * stroke: 'rgb(143,203,203)'
  26155. * },
  26156. * xField: 'name',
  26157. * yField: 'data1',
  26158. * marker: {
  26159. * type: 'path',
  26160. * path: ['M', - 2, 0, 0, 2, 2, 0, 0, - 2, 'Z'],
  26161. * stroke: 'blue',
  26162. * lineWidth: 0
  26163. * }
  26164. * }, {
  26165. * type: 'line',
  26166. * highlight: {
  26167. * size: 7,
  26168. * radius: 7
  26169. * },
  26170. * fill: true,
  26171. * xField: 'name',
  26172. * yField: 'data3',
  26173. * marker: {
  26174. * type: 'circle',
  26175. * radius: 4,
  26176. * lineWidth: 0
  26177. * }
  26178. * }]
  26179. * });
  26180. */
  26181. Ext.define('Ext.chart.interactions.CrossZoom', {
  26182. extend: 'Ext.chart.interactions.Abstract',
  26183. type: 'crosszoom',
  26184. alias: 'interaction.crosszoom',
  26185. isCrossZoom: true,
  26186. config: {
  26187. /**
  26188. * @cfg {Object/Array} axes
  26189. * Specifies which axes should be made navigable. The config value can take the following
  26190. * formats:
  26191. *
  26192. * - An Object whose keys correspond to the {@link Ext.chart.axis.Axis#position position}
  26193. * of each axis that should be made navigable. Each key's value can either be an Object
  26194. * with further configuration options for each axis or simply `true` for a default
  26195. * set of options.
  26196. * {
  26197. * type: 'crosszoom',
  26198. * axes: {
  26199. * left: {
  26200. * maxZoom: 5,
  26201. * allowPan: false
  26202. * },
  26203. * bottom: true
  26204. * }
  26205. * }
  26206. *
  26207. * If using the full Object form, the following options can be specified for each axis:
  26208. *
  26209. * - minZoom (Number) A minimum zoom level for the axis. Defaults to `1` which is its
  26210. * natural size.
  26211. * - maxZoom (Number) A maximum zoom level for the axis. Defaults to `10`.
  26212. * - startZoom (Number) A starting zoom level for the axis. Defaults to `1`.
  26213. * - allowZoom (Boolean) Whether zooming is allowed for the axis. Defaults to `true`.
  26214. * - allowPan (Boolean) Whether panning is allowed for the axis. Defaults to `true`.
  26215. * - startPan (Boolean) A starting panning offset for the axis. Defaults to `0`.
  26216. *
  26217. * - An Array of strings, each one corresponding to the
  26218. * {@link Ext.chart.axis.Axis#position position} of an axis that should be made navigable.
  26219. * The default options will be used for each named axis.
  26220. *
  26221. * {
  26222. * type: 'crosszoom',
  26223. * axes: ['left', 'bottom']
  26224. * }
  26225. *
  26226. * If the `axes` config is not specified, it will default to making all axes navigable
  26227. * with the default axis options.
  26228. */
  26229. axes: true,
  26230. gestures: {
  26231. dragstart: 'onGestureStart',
  26232. drag: 'onGesture',
  26233. dragend: 'onGestureEnd',
  26234. dblclick: 'onDoubleTap'
  26235. },
  26236. undoButton: {}
  26237. },
  26238. stopAnimationBeforeSync: false,
  26239. zoomAnimationInProgress: false,
  26240. constructor: function() {
  26241. this.callParent(arguments);
  26242. this.zoomHistory = [];
  26243. },
  26244. applyAxes: function(axesConfig) {
  26245. var result = {};
  26246. if (axesConfig === true) {
  26247. return {
  26248. top: {},
  26249. right: {},
  26250. bottom: {},
  26251. left: {}
  26252. };
  26253. } else if (Ext.isArray(axesConfig)) {
  26254. // array of axis names - translate to full object form
  26255. result = {};
  26256. Ext.each(axesConfig, function(axis) {
  26257. result[axis] = {};
  26258. });
  26259. } else if (Ext.isObject(axesConfig)) {
  26260. Ext.iterate(axesConfig, function(key, val) {
  26261. // axis name with `true` value -> translate to object
  26262. if (val === true) {
  26263. result[key] = {};
  26264. } else if (val !== false) {
  26265. result[key] = val;
  26266. }
  26267. });
  26268. }
  26269. return result;
  26270. },
  26271. applyUndoButton: function(button, oldButton) {
  26272. var me = this;
  26273. if (oldButton) {
  26274. oldButton.destroy();
  26275. }
  26276. if (button) {
  26277. return Ext.create('Ext.Button', Ext.apply({
  26278. cls: [],
  26279. text: 'Undo Zoom',
  26280. disabled: true,
  26281. handler: function() {
  26282. me.undoZoom();
  26283. }
  26284. }, button));
  26285. }
  26286. },
  26287. getSurface: function() {
  26288. return this.getChart() && this.getChart().getSurface('overlay');
  26289. },
  26290. setSeriesOpacity: function(opacity) {
  26291. var surface = this.getChart() && this.getChart().getSurface('series');
  26292. if (surface) {
  26293. surface.element.setStyle('opacity', opacity);
  26294. }
  26295. },
  26296. onGestureStart: function(e) {
  26297. var me = this,
  26298. chart = me.getChart(),
  26299. surface = me.getSurface(),
  26300. rect = chart.getInnerRect(),
  26301. innerPadding = chart.getInnerPadding(),
  26302. minX = innerPadding.left,
  26303. maxX = minX + rect[2],
  26304. minY = innerPadding.top,
  26305. maxY = minY + rect[3],
  26306. xy = chart.getEventXY(e),
  26307. x = xy[0],
  26308. y = xy[1];
  26309. e.claimGesture();
  26310. if (me.zoomAnimationInProgress) {
  26311. return;
  26312. }
  26313. if (x > minX && x < maxX && y > minY && y < maxY) {
  26314. me.gestureEvent = 'drag';
  26315. me.lockEvents(me.gestureEvent);
  26316. me.startX = x;
  26317. me.startY = y;
  26318. me.selectionRect = surface.add({
  26319. type: 'rect',
  26320. globalAlpha: 0.5,
  26321. fillStyle: 'rgba(80,80,140,0.5)',
  26322. strokeStyle: 'rgba(80,80,140,1)',
  26323. lineWidth: 2,
  26324. x: x,
  26325. y: y,
  26326. width: 0,
  26327. height: 0,
  26328. zIndex: 10000
  26329. });
  26330. me.setSeriesOpacity(0.8);
  26331. return false;
  26332. }
  26333. },
  26334. onGesture: function(e) {
  26335. var me = this;
  26336. if (me.zoomAnimationInProgress) {
  26337. return;
  26338. }
  26339. if (me.getLocks()[me.gestureEvent] === me) {
  26340. // eslint-disable-next-line vars-on-top, one-var
  26341. var chart = me.getChart(),
  26342. surface = me.getSurface(),
  26343. rect = chart.getInnerRect(),
  26344. innerPadding = chart.getInnerPadding(),
  26345. minX = innerPadding.left,
  26346. maxX = minX + rect[2],
  26347. minY = innerPadding.top,
  26348. maxY = minY + rect[3],
  26349. xy = chart.getEventXY(e),
  26350. x = xy[0],
  26351. y = xy[1];
  26352. if (x < minX) {
  26353. x = minX;
  26354. } else if (x > maxX) {
  26355. x = maxX;
  26356. }
  26357. if (y < minY) {
  26358. y = minY;
  26359. } else if (y > maxY) {
  26360. y = maxY;
  26361. }
  26362. me.selectionRect.setAttributes({
  26363. width: x - me.startX,
  26364. height: y - me.startY
  26365. });
  26366. if (Math.abs(me.startX - x) < 11 || Math.abs(me.startY - y) < 11) {
  26367. me.selectionRect.setAttributes({
  26368. globalAlpha: 0.5
  26369. });
  26370. } else {
  26371. me.selectionRect.setAttributes({
  26372. globalAlpha: 1
  26373. });
  26374. }
  26375. surface.renderFrame();
  26376. return false;
  26377. }
  26378. },
  26379. onGestureEnd: function(e) {
  26380. var me = this;
  26381. if (me.zoomAnimationInProgress) {
  26382. return;
  26383. }
  26384. if (me.getLocks()[me.gestureEvent] === me) {
  26385. // eslint-disable-next-line vars-on-top, one-var
  26386. var chart = me.getChart(),
  26387. surface = me.getSurface(),
  26388. rect = chart.getInnerRect(),
  26389. innerPadding = chart.getInnerPadding(),
  26390. minX = innerPadding.left,
  26391. maxX = minX + rect[2],
  26392. minY = innerPadding.top,
  26393. maxY = minY + rect[3],
  26394. rectWidth = rect[2],
  26395. rectHeight = rect[3],
  26396. xy = chart.getEventXY(e),
  26397. x = xy[0],
  26398. y = xy[1];
  26399. if (x < minX) {
  26400. x = minX;
  26401. } else if (x > maxX) {
  26402. x = maxX;
  26403. }
  26404. if (y < minY) {
  26405. y = minY;
  26406. } else if (y > maxY) {
  26407. y = maxY;
  26408. }
  26409. if (Math.abs(me.startX - x) < 11 || Math.abs(me.startY - y) < 11) {
  26410. surface.remove(me.selectionRect);
  26411. } else {
  26412. me.zoomBy([
  26413. Math.min(me.startX, x) / rectWidth,
  26414. 1 - Math.max(me.startY, y) / rectHeight,
  26415. Math.max(me.startX, x) / rectWidth,
  26416. 1 - Math.min(me.startY, y) / rectHeight
  26417. ]);
  26418. me.selectionRect.setAttributes({
  26419. x: Math.min(me.startX, x),
  26420. y: Math.min(me.startY, y),
  26421. width: Math.abs(me.startX - x),
  26422. height: Math.abs(me.startY - y)
  26423. });
  26424. me.selectionRect.setAnimation(chart.getAnimation() || {
  26425. duration: 0
  26426. });
  26427. me.selectionRect.setAttributes({
  26428. globalAlpha: 0,
  26429. x: 0,
  26430. y: 0,
  26431. width: rectWidth,
  26432. height: rectHeight
  26433. });
  26434. me.zoomAnimationInProgress = true;
  26435. chart.suspendThicknessChanged();
  26436. me.selectionRect.getAnimation().on('animationend', function() {
  26437. chart.resumeThicknessChanged();
  26438. surface.remove(me.selectionRect);
  26439. me.selectionRect = null;
  26440. me.zoomAnimationInProgress = false;
  26441. });
  26442. }
  26443. surface.renderFrame();
  26444. me.sync();
  26445. me.unlockEvents(me.gestureEvent);
  26446. me.setSeriesOpacity(1);
  26447. if (!me.zoomAnimationInProgress) {
  26448. surface.remove(me.selectionRect);
  26449. me.selectionRect = null;
  26450. }
  26451. }
  26452. },
  26453. zoomBy: function(rect) {
  26454. var me = this,
  26455. axisConfigs = me.getAxes(),
  26456. chart = me.getChart(),
  26457. axes = chart.getAxes(),
  26458. isRtl = chart.getInherited().rtl,
  26459. zoomMap = {},
  26460. config, axis, x1, x2, isSide, oldRange, i;
  26461. if (isRtl) {
  26462. rect = rect.slice();
  26463. x1 = 1 - rect[0];
  26464. x2 = 1 - rect[2];
  26465. rect[0] = Math.min(x1, x2);
  26466. rect[2] = Math.max(x1, x2);
  26467. }
  26468. for (i = 0; i < axes.length; i++) {
  26469. axis = axes[i];
  26470. config = axisConfigs[axis.getPosition()];
  26471. if (config && config.allowZoom !== false) {
  26472. isSide = axis.isSide();
  26473. oldRange = axis.getVisibleRange();
  26474. zoomMap[axis.getId()] = oldRange.slice(0);
  26475. if (!isSide) {
  26476. axis.setVisibleRange([
  26477. (oldRange[1] - oldRange[0]) * rect[0] + oldRange[0],
  26478. (oldRange[1] - oldRange[0]) * rect[2] + oldRange[0]
  26479. ]);
  26480. } else {
  26481. axis.setVisibleRange([
  26482. (oldRange[1] - oldRange[0]) * rect[1] + oldRange[0],
  26483. (oldRange[1] - oldRange[0]) * rect[3] + oldRange[0]
  26484. ]);
  26485. }
  26486. }
  26487. }
  26488. me.zoomHistory.push(zoomMap);
  26489. me.getUndoButton().setDisabled(false);
  26490. },
  26491. undoZoom: function() {
  26492. var zoomMap = this.zoomHistory.pop(),
  26493. axes = this.getChart().getAxes(),
  26494. axis, i;
  26495. if (zoomMap) {
  26496. for (i = 0; i < axes.length; i++) {
  26497. axis = axes[i];
  26498. if (zoomMap[axis.getId()]) {
  26499. axis.setVisibleRange(zoomMap[axis.getId()]);
  26500. }
  26501. }
  26502. }
  26503. this.getUndoButton().setDisabled(this.zoomHistory.length === 0);
  26504. this.sync();
  26505. },
  26506. onDoubleTap: function(e) {
  26507. this.undoZoom();
  26508. },
  26509. destroy: function() {
  26510. this.setUndoButton(null);
  26511. this.callParent();
  26512. }
  26513. });
  26514. /**
  26515. * The Crosshair interaction allows the user to get precise values for a specific point
  26516. * on the chart. The values are obtained by single-touch dragging on the chart.
  26517. *
  26518. * @example
  26519. * Ext.create('Ext.Container', {
  26520. * renderTo: Ext.getBody(),
  26521. * width: 600,
  26522. * height: 400,
  26523. * layout: 'fit',
  26524. * items: {
  26525. * xtype: 'cartesian',
  26526. * innerPadding: 20,
  26527. * interactions: {
  26528. * type: 'crosshair',
  26529. * axes: {
  26530. * left: {
  26531. * label: {
  26532. * fillStyle: 'white'
  26533. * },
  26534. * rect: {
  26535. * fillStyle: 'brown',
  26536. * radius: 6
  26537. * }
  26538. * },
  26539. * bottom: {
  26540. * label: {
  26541. * fontSize: '14px',
  26542. * fontWeight: 'bold'
  26543. * }
  26544. * }
  26545. * },
  26546. * lines: {
  26547. * horizontal: {
  26548. * strokeStyle: 'brown',
  26549. * lineWidth: 2,
  26550. * lineDash: [20, 2, 2, 2, 2, 2, 2, 2]
  26551. * }
  26552. * }
  26553. * },
  26554. * store: {
  26555. * fields: ['name', 'data'],
  26556. * data: [
  26557. * {name: 'apple', data: 300},
  26558. * {name: 'orange', data: 900},
  26559. * {name: 'banana', data: 800},
  26560. * {name: 'pear', data: 400},
  26561. * {name: 'grape', data: 500}
  26562. * ]
  26563. * },
  26564. * axes: [{
  26565. * type: 'numeric',
  26566. * position: 'left',
  26567. * fields: ['data'],
  26568. * title: {
  26569. * text: 'Value',
  26570. * fontSize: 15
  26571. * },
  26572. * grid: true,
  26573. * label: {
  26574. * rotationRads: -Math.PI / 4
  26575. * }
  26576. * }, {
  26577. * type: 'category',
  26578. * position: 'bottom',
  26579. * fields: ['name'],
  26580. * title: {
  26581. * text: 'Category',
  26582. * fontSize: 15
  26583. * }
  26584. * }],
  26585. * series: {
  26586. * type: 'line',
  26587. * style: {
  26588. * strokeStyle: 'black'
  26589. * },
  26590. * xField: 'name',
  26591. * yField: 'data',
  26592. * marker: {
  26593. * type: 'circle',
  26594. * radius: 5,
  26595. * fillStyle: 'lightblue'
  26596. * }
  26597. * }
  26598. * }
  26599. * });
  26600. */
  26601. Ext.define('Ext.chart.interactions.Crosshair', {
  26602. extend: 'Ext.chart.interactions.Abstract',
  26603. requires: [
  26604. 'Ext.chart.grid.HorizontalGrid',
  26605. 'Ext.chart.grid.VerticalGrid',
  26606. 'Ext.chart.CartesianChart',
  26607. 'Ext.chart.axis.layout.Discrete'
  26608. ],
  26609. type: 'crosshair',
  26610. alias: 'interaction.crosshair',
  26611. config: {
  26612. /**
  26613. * @cfg {Object} axes
  26614. * Specifies label text and label rect configs on per axis basis or as a single config
  26615. * for all axes.
  26616. *
  26617. * {
  26618. * type: 'crosshair',
  26619. * axes: {
  26620. * label: { fillStyle: 'white' },
  26621. * rect: { fillStyle: 'maroon'}
  26622. * }
  26623. * }
  26624. *
  26625. * In case per axis configuration is used, an object with keys corresponding
  26626. * to the {@link Ext.chart.axis.Axis#position position} must be provided.
  26627. *
  26628. * {
  26629. * type: 'crosshair',
  26630. * axes: {
  26631. * left: {
  26632. * label: { fillStyle: 'white' },
  26633. * rect: {
  26634. * fillStyle: 'maroon',
  26635. * radius: 4
  26636. * }
  26637. * },
  26638. * bottom: {
  26639. * label: {
  26640. * fontSize: '14px',
  26641. * fontWeight: 'bold'
  26642. * },
  26643. * rect: { fillStyle: 'white' }
  26644. * }
  26645. * }
  26646. *
  26647. * If the `axes` config is not specified, the following defaults will be used:
  26648. * - `label` will use values from the {@link Ext.chart.axis.Axis#label label} config.
  26649. * - `rect` will use the 'white' fillStyle.
  26650. */
  26651. axes: {
  26652. top: {
  26653. label: {},
  26654. rect: {}
  26655. },
  26656. right: {
  26657. label: {},
  26658. rect: {}
  26659. },
  26660. bottom: {
  26661. label: {},
  26662. rect: {}
  26663. },
  26664. left: {
  26665. label: {},
  26666. rect: {}
  26667. }
  26668. },
  26669. /**
  26670. * @cfg {Object} lines
  26671. * Specifies attributes of horizontal and vertical lines that make up the crosshair.
  26672. * If this config is missing, black dashed lines will be used.
  26673. *
  26674. * {
  26675. * horizontal: {
  26676. * strokeStyle: 'red',
  26677. * lineDash: [] // solid line
  26678. * },
  26679. * vertical: {
  26680. * lineWidth: 2,
  26681. * lineDash: [15, 5, 5, 5]
  26682. * }
  26683. * }
  26684. */
  26685. lines: {
  26686. horizontal: {
  26687. strokeStyle: 'black',
  26688. lineDash: [
  26689. 5,
  26690. 5
  26691. ]
  26692. },
  26693. vertical: {
  26694. strokeStyle: 'black',
  26695. lineDash: [
  26696. 5,
  26697. 5
  26698. ]
  26699. }
  26700. },
  26701. /**
  26702. * @cfg {String} gesture
  26703. * Specifies which gesture should be used for starting/maintaining/ending the interaction.
  26704. */
  26705. gesture: 'drag'
  26706. },
  26707. applyAxes: function(axesConfig, oldAxesConfig) {
  26708. return Ext.merge(oldAxesConfig || {}, axesConfig);
  26709. },
  26710. applyLines: function(linesConfig, oldLinesConfig) {
  26711. return Ext.merge(oldLinesConfig || {}, linesConfig);
  26712. },
  26713. updateChart: function(chart) {
  26714. if (chart && !chart.isCartesian) {
  26715. Ext.raise("Crosshair interaction can only be used on cartesian charts.");
  26716. }
  26717. this.callParent(arguments);
  26718. },
  26719. getGestures: function() {
  26720. var me = this,
  26721. gestures = {},
  26722. gesture = me.getGesture();
  26723. gestures[gesture] = 'onGesture';
  26724. gestures[gesture + 'start'] = 'onGestureStart';
  26725. gestures[gesture + 'end'] = 'onGestureEnd';
  26726. gestures[gesture + 'cancel'] = 'onGestureCancel';
  26727. return gestures;
  26728. },
  26729. onGestureStart: function(e) {
  26730. var me = this,
  26731. chart = me.getChart(),
  26732. axesTheme = chart.getTheme().getAxis(),
  26733. axisTheme,
  26734. surface = chart.getSurface('overlay'),
  26735. rect = chart.getInnerRect(),
  26736. chartWidth = rect[2],
  26737. chartHeight = rect[3],
  26738. xy = chart.getEventXY(e),
  26739. x = xy[0],
  26740. y = xy[1],
  26741. axes = chart.getAxes(),
  26742. axesConfig = me.getAxes(),
  26743. linesConfig = me.getLines(),
  26744. axis, axisSurface, axisRect, axisWidth, axisHeight, axisPosition, axisAlignment, axisLabel, axisLabelConfig, crosshairLabelConfig, tickPadding, axisSprite, attr, lineWidth, halfLineWidth, title, titleBBox, horizontalLineCfg, verticalLineCfg, i;
  26745. e.claimGesture();
  26746. if (x > 0 && x < chartWidth && y > 0 && y < chartHeight) {
  26747. me.lockEvents(me.getGesture());
  26748. horizontalLineCfg = Ext.apply({
  26749. xclass: 'Ext.chart.grid.HorizontalGrid',
  26750. x: 0,
  26751. y: y,
  26752. width: chartWidth
  26753. }, linesConfig.horizontal);
  26754. verticalLineCfg = Ext.apply({
  26755. xclass: 'Ext.chart.grid.VerticalGrid',
  26756. x: x,
  26757. y: 0,
  26758. height: chartHeight
  26759. }, linesConfig.vertical);
  26760. me.axesLabels = me.axesLabels || {};
  26761. for (i = 0; i < axes.length; i++) {
  26762. axis = axes[i];
  26763. axisSurface = axis.getSurface();
  26764. axisRect = axisSurface.getRect();
  26765. axisSprite = axis.getSprites()[0];
  26766. axisWidth = axisRect[2];
  26767. axisHeight = axisRect[3];
  26768. axisPosition = axis.getPosition();
  26769. axisAlignment = axis.getAlignment();
  26770. title = axis.getTitle();
  26771. titleBBox = title && title.attr.text !== '' && title.getBBox();
  26772. attr = axisSprite.attr;
  26773. lineWidth = attr.axisLine ? attr.lineWidth : 0;
  26774. halfLineWidth = lineWidth / 2;
  26775. tickPadding = Math.max(attr.majorTickSize, attr.minorTickSize) + lineWidth;
  26776. axisLabel = me.axesLabels[axisPosition] = axisSurface.add({
  26777. type: 'composite'
  26778. });
  26779. axisLabel.labelRect = axisLabel.addSprite(Ext.apply({
  26780. type: 'rect',
  26781. fillStyle: 'white',
  26782. x: axisPosition === 'right' ? lineWidth : 0,
  26783. y: axisPosition === 'bottom' ? lineWidth : 0,
  26784. width: axisWidth - lineWidth - (axisAlignment === 'vertical' && titleBBox ? titleBBox.width : 0),
  26785. height: axisHeight - lineWidth - (axisAlignment === 'horizontal' && titleBBox ? titleBBox.height : 0),
  26786. translationX: axisPosition === 'left' && titleBBox ? titleBBox.width : 0,
  26787. translationY: axisPosition === 'top' && titleBBox ? titleBBox.height : 0
  26788. }, axesConfig.rect || axesConfig[axisPosition].rect));
  26789. if (axisAlignment === 'vertical' && !verticalLineCfg.strokeStyle) {
  26790. verticalLineCfg.strokeStyle = attr.strokeStyle;
  26791. }
  26792. if (axisAlignment === 'horizontal' && !horizontalLineCfg.strokeStyle) {
  26793. horizontalLineCfg.strokeStyle = attr.strokeStyle;
  26794. }
  26795. axisTheme = Ext.merge({}, axesTheme.defaults, axesTheme[axisPosition]);
  26796. axisLabelConfig = Ext.apply({}, axis.config.label, axisTheme.label);
  26797. crosshairLabelConfig = axesConfig.label || axesConfig[axisPosition].label;
  26798. axisLabel.labelText = axisLabel.addSprite(Ext.apply(axisLabelConfig, crosshairLabelConfig, {
  26799. type: 'text',
  26800. x: me.calculateLabelTextPoint(false, axisPosition, tickPadding, titleBBox, axisWidth, halfLineWidth),
  26801. y: me.calculateLabelTextPoint(true, axisPosition, tickPadding, titleBBox, axisHeight, halfLineWidth)
  26802. }));
  26803. }
  26804. me.horizontalLine = surface.add(horizontalLineCfg);
  26805. me.verticalLine = surface.add(verticalLineCfg);
  26806. return false;
  26807. }
  26808. },
  26809. onGesture: function(e) {
  26810. var me = this;
  26811. if (me.getLocks()[me.getGesture()] !== me) {
  26812. return;
  26813. }
  26814. // eslint-disable-next-line vars-on-top, one-var
  26815. var chart = me.getChart(),
  26816. surface = chart.getSurface('overlay'),
  26817. rect = Ext.Array.slice(chart.getInnerRect()),
  26818. padding = chart.getInnerPadding(),
  26819. px = padding.left,
  26820. py = padding.top,
  26821. chartWidth = rect[2],
  26822. chartHeight = rect[3],
  26823. xy = chart.getEventXY(e),
  26824. x = xy[0],
  26825. y = xy[1],
  26826. axes = chart.getAxes(),
  26827. axis, axisPosition, axisAlignment, axisSurface, axisSprite, axisMatrix, axisLayoutContext, axisSegmenter, axisLabel, labelBBox, textPadding, xx, yy, dx, dy, xValue, yValue, text, i;
  26828. if (x < 0) {
  26829. x = 0;
  26830. } else if (x > chartWidth) {
  26831. x = chartWidth;
  26832. }
  26833. if (y < 0) {
  26834. y = 0;
  26835. } else if (y > chartHeight) {
  26836. y = chartHeight;
  26837. }
  26838. x += px;
  26839. y += py;
  26840. for (i = 0; i < axes.length; i++) {
  26841. axis = axes[i];
  26842. axisPosition = axis.getPosition();
  26843. axisAlignment = axis.getAlignment();
  26844. axisSurface = axis.getSurface();
  26845. axisSprite = axis.getSprites()[0];
  26846. axisMatrix = axisSprite.attr.matrix;
  26847. textPadding = axisSprite.attr.textPadding * 2;
  26848. axisLabel = me.axesLabels[axisPosition];
  26849. axisLayoutContext = axisSprite.getLayoutContext();
  26850. axisSegmenter = axis.getSegmenter();
  26851. if (axisLabel) {
  26852. if (axisAlignment === 'vertical') {
  26853. yy = axisMatrix.getYY();
  26854. dy = axisMatrix.getDY();
  26855. yValue = (y - dy - py) / yy;
  26856. if (axis.getLayout() instanceof Ext.chart.axis.layout.Discrete) {
  26857. y = Math.round(yValue) * yy + dy + py;
  26858. yValue = axisSegmenter.from(Math.round(yValue));
  26859. yValue = axisSprite.attr.data[yValue];
  26860. } else {
  26861. yValue = axisSegmenter.from(yValue);
  26862. }
  26863. text = axisSegmenter.renderer(yValue, axisLayoutContext);
  26864. axisLabel.setAttributes({
  26865. translationY: y - py
  26866. });
  26867. axisLabel.labelText.setAttributes({
  26868. text: text
  26869. });
  26870. labelBBox = axisLabel.labelText.getBBox();
  26871. axisLabel.labelRect.setAttributes({
  26872. height: labelBBox.height + textPadding,
  26873. y: -(labelBBox.height + textPadding) / 2
  26874. });
  26875. axisSurface.renderFrame();
  26876. } else {
  26877. xx = axisMatrix.getXX();
  26878. dx = axisMatrix.getDX();
  26879. xValue = (x - dx - px) / xx;
  26880. if (axis.getLayout() instanceof Ext.chart.axis.layout.Discrete) {
  26881. x = Math.round(xValue) * xx + dx + px;
  26882. xValue = axisSegmenter.from(Math.round(xValue));
  26883. xValue = axisSprite.attr.data[xValue];
  26884. } else {
  26885. xValue = axisSegmenter.from(xValue);
  26886. }
  26887. text = axisSegmenter.renderer(xValue, axisLayoutContext);
  26888. axisLabel.setAttributes({
  26889. translationX: x - px
  26890. });
  26891. axisLabel.labelText.setAttributes({
  26892. text: text
  26893. });
  26894. labelBBox = axisLabel.labelText.getBBox();
  26895. axisLabel.labelRect.setAttributes({
  26896. width: labelBBox.width + textPadding,
  26897. x: -(labelBBox.width + textPadding) / 2
  26898. });
  26899. axisSurface.renderFrame();
  26900. }
  26901. }
  26902. }
  26903. me.horizontalLine.setAttributes({
  26904. y: y,
  26905. strokeStyle: axisSprite.attr.strokeStyle
  26906. });
  26907. me.verticalLine.setAttributes({
  26908. x: x,
  26909. strokeStyle: axisSprite.attr.strokeStyle
  26910. });
  26911. surface.renderFrame();
  26912. return false;
  26913. },
  26914. onGestureEnd: function(e) {
  26915. var me = this,
  26916. chart = me.getChart(),
  26917. surface = chart.getSurface('overlay'),
  26918. axes = chart.getAxes(),
  26919. axis, axisPosition, axisSurface, axisLabel, i;
  26920. surface.remove(me.verticalLine);
  26921. surface.remove(me.horizontalLine);
  26922. for (i = 0; i < axes.length; i++) {
  26923. axis = axes[i];
  26924. axisPosition = axis.getPosition();
  26925. axisSurface = axis.getSurface();
  26926. axisLabel = me.axesLabels[axisPosition];
  26927. if (axisLabel) {
  26928. delete me.axesLabels[axisPosition];
  26929. axisSurface.remove(axisLabel);
  26930. }
  26931. axisSurface.renderFrame();
  26932. }
  26933. surface.renderFrame();
  26934. me.unlockEvents(me.getGesture());
  26935. },
  26936. onGestureCancel: function(e) {
  26937. this.onGestureEnd(e);
  26938. },
  26939. privates: {
  26940. vertMap: {
  26941. top: 'start',
  26942. bottom: 'end'
  26943. },
  26944. horzMap: {
  26945. left: 'start',
  26946. right: 'end'
  26947. },
  26948. calculateLabelTextPoint: function(vertical, position, tickPadding, titleBBox, axisSize, halfLineWidth) {
  26949. var titlePadding, sizeProp, pointProp;
  26950. if (vertical) {
  26951. pointProp = 'y';
  26952. sizeProp = 'height';
  26953. position = this.vertMap[position];
  26954. } else {
  26955. pointProp = 'x';
  26956. sizeProp = 'width';
  26957. position = this.horzMap[position];
  26958. }
  26959. switch (position) {
  26960. case 'start':
  26961. titlePadding = titleBBox ? titleBBox[pointProp] + titleBBox[sizeProp] : 0;
  26962. return titlePadding + (axisSize - titlePadding - tickPadding) / 2 - halfLineWidth;
  26963. case 'end':
  26964. titlePadding = titleBBox ? axisSize - titleBBox[pointProp] : 0;
  26965. return tickPadding + (axisSize - tickPadding - titlePadding) / 2 + halfLineWidth;
  26966. default:
  26967. return 0;
  26968. }
  26969. }
  26970. }
  26971. });
  26972. /**
  26973. * @class Ext.chart.interactions.ItemHighlight
  26974. * @extends Ext.chart.interactions.Abstract
  26975. *
  26976. * The 'itemhighlight' interaction allows the user to highlight series items in the chart.
  26977. */
  26978. Ext.define('Ext.chart.interactions.ItemHighlight', {
  26979. extend: 'Ext.chart.interactions.Abstract',
  26980. type: 'itemhighlight',
  26981. alias: 'interaction.itemhighlight',
  26982. isItemHighlight: true,
  26983. config: {
  26984. gestures: {
  26985. tap: 'onTapGesture',
  26986. mousemove: 'onMouseMoveGesture',
  26987. mousedown: 'onMouseDownGesture',
  26988. mouseup: 'onMouseUpGesture',
  26989. mouseleave: 'onMouseUpGesture'
  26990. },
  26991. /**
  26992. * @cfg {Boolean} [sticky=false]
  26993. * Disables mouse tracking.
  26994. * Series items will only be highlighted/unhighlighted on mouse click.
  26995. * This config has no effect on touch devices.
  26996. */
  26997. sticky: false,
  26998. /**
  26999. * @cfg {Boolean} [multiTooltips=false]
  27000. * Enable displaying multiple tooltips for overlapping or adjacent series items within
  27001. * {@link Ext.chart.series.Line#selectionTolerance} radius.
  27002. * Default is to display a tooltip only for the last series item rendered.
  27003. * When multiple tooltips are displayed, they may overlap partially or completely;
  27004. * it is up to the developer to ensure tooltip positioning is satisfactory.
  27005. *
  27006. * @since 6.6.0
  27007. */
  27008. multiTooltips: false
  27009. },
  27010. constructor: function(config) {
  27011. this.callParent([
  27012. config
  27013. ]);
  27014. this.stickyHighlightItem = null;
  27015. this.tooltipItems = [];
  27016. },
  27017. destroy: function() {
  27018. this.stickyHighlightItem = this.tooltipItems = null;
  27019. this.callParent();
  27020. },
  27021. onMouseMoveGesture: function(e) {
  27022. var me = this,
  27023. tooltipItems = me.tooltipItems,
  27024. isMousePointer = e.pointerType === 'mouse',
  27025. tooltips = [],
  27026. item, oldItem, items, tooltip, oldTooltip, i, len, j, jLen;
  27027. if (me.getSticky()) {
  27028. return true;
  27029. }
  27030. if (isMousePointer && me.stickyHighlightItem) {
  27031. me.stickyHighlightItem = null;
  27032. me.highlight(null);
  27033. }
  27034. if (me.isDragging) {
  27035. if (tooltipItems.length && isMousePointer) {
  27036. me.hideTooltips(tooltipItems);
  27037. tooltipItems.length = 0;
  27038. }
  27039. } else if (!me.stickyHighlightItem) {
  27040. if (me.getMultiTooltips()) {
  27041. items = me.getItemsForEvent(e);
  27042. } else {
  27043. item = me.getItemForEvent(e);
  27044. items = item ? [
  27045. item
  27046. ] : [];
  27047. }
  27048. for (i = 0 , len = items.length; i < len; i++) {
  27049. item = items[i];
  27050. // Items are returned top to down, so first item is the top one.
  27051. // Chart can only have one highlighted item.
  27052. if (i === 0 && item !== me.getChart().getHighlightItem()) {
  27053. me.highlight(item);
  27054. me.sync();
  27055. }
  27056. tooltip = item.series.getTooltip();
  27057. if (tooltip) {
  27058. tooltips.push(tooltip);
  27059. }
  27060. }
  27061. if (isMousePointer) {
  27062. // If we detected a mouse hit, show/refresh the tooltip
  27063. if (items.length) {
  27064. for (i = 0 , len = items.length; i < len; i++) {
  27065. item = items[i];
  27066. tooltip = item.series.getTooltip();
  27067. if (tooltip) {
  27068. // If there were different previously active items
  27069. // that are not going to be included in current active items,
  27070. // ask them to hide their tooltips. Unless those are
  27071. // the same tooltip instances that we are about to show,
  27072. // in which case we are just going to reposition them.
  27073. for (j = 0 , jLen = tooltipItems.length; j < jLen; j++) {
  27074. oldItem = tooltipItems[j];
  27075. if (!Ext.Array.contains(items, oldItem)) {
  27076. oldTooltip = oldItem.series.getTooltip();
  27077. if (!Ext.Array.contains(tooltips, oldTooltip)) {
  27078. oldItem.series.hideTooltip(oldItem, true);
  27079. }
  27080. }
  27081. }
  27082. if (tooltip.getTrackMouse()) {
  27083. item.series.showTooltip(item, e);
  27084. } else {
  27085. me.showUntracked(item);
  27086. }
  27087. }
  27088. }
  27089. me.tooltipItems = items;
  27090. } else // No mouse hit - schedule a hide for hideDelay ms.
  27091. // If pointer enters another item within that time,
  27092. // there will be no flickery reshow.
  27093. {
  27094. me.hideTooltips(tooltipItems);
  27095. tooltipItems.length = 0;
  27096. }
  27097. }
  27098. return false;
  27099. }
  27100. },
  27101. highlight: function(item) {
  27102. // This is its own function to make it easier for subclasses
  27103. // to enhance the behavior. An alternative would be to listen
  27104. // for the chart's 'itemhighlight' event.
  27105. this.getChart().setHighlightItem(item);
  27106. },
  27107. showTooltip: function(e, item) {
  27108. item.series.showTooltip(item, e);
  27109. Ext.Array.include(this.tooltipItems, item);
  27110. },
  27111. showUntracked: function(item) {
  27112. var marker = item.sprite.getMarker(item.category),
  27113. surface, surfaceXY, isInverseY, itemBBox, matrix;
  27114. if (marker) {
  27115. surface = marker.getSurface();
  27116. isInverseY = surface.matrix.elements[3] < 0;
  27117. surfaceXY = surface.element.getXY();
  27118. itemBBox = Ext.clone(marker.getBBoxFor(item.index));
  27119. if (isInverseY) {
  27120. // The item.category for bar series will be 'items'.
  27121. // The item.category for line series will be 'markers'.
  27122. // 'items' are in the 'series' surface, which is flipped vertically
  27123. // for cartesian series.
  27124. // 'markers' are in the 'overlay' surface, which isn't flipped.
  27125. // So for 'markers' we already have the bbox in a coordinate system
  27126. // with the origin at the top-left of the surface, but for 'items'
  27127. // we need to do a conversion.
  27128. if (surface.getInherited().rtl) {
  27129. matrix = surface.inverseMatrix.clone().flipX().translate(item.sprite.attr.innerWidth, 0, true);
  27130. } else {
  27131. matrix = surface.inverseMatrix;
  27132. }
  27133. itemBBox = matrix.transformBBox(itemBBox);
  27134. }
  27135. itemBBox.x += surfaceXY[0];
  27136. itemBBox.y += surfaceXY[1];
  27137. item.series.showTooltipAt(item, itemBBox.x + itemBBox.width * 0.5, itemBBox.y + itemBBox.height * 0.5);
  27138. }
  27139. },
  27140. onMouseDownGesture: function() {
  27141. this.isDragging = true;
  27142. },
  27143. onMouseUpGesture: function() {
  27144. this.isDragging = false;
  27145. },
  27146. isSameItem: function(a, b) {
  27147. return a && b && a.series === b.series && a.field === b.field && a.index === b.index;
  27148. },
  27149. onTapGesture: function(e) {
  27150. var me = this,
  27151. item;
  27152. // A click/tap on an item makes its highlight sticky.
  27153. // It requires another click/tap to unhighlight.
  27154. if (e.pointerType === 'mouse' && !me.getSticky()) {
  27155. return;
  27156. }
  27157. item = me.getItemForEvent(e);
  27158. if (me.isSameItem(me.stickyHighlightItem, item)) {
  27159. item = null;
  27160. }
  27161. // toggle
  27162. me.stickyHighlightItem = item;
  27163. me.highlight(item);
  27164. },
  27165. privates: {
  27166. hideTooltips: function(items, force) {
  27167. var item, i, len;
  27168. items = Ext.isArray(items) ? items : [
  27169. items
  27170. ];
  27171. for (i = 0 , len = items.length; i < len; i++) {
  27172. item = items[i];
  27173. if (item && item.series && !item.series.destroyed) {
  27174. item.series.hideTooltip(item, force);
  27175. }
  27176. }
  27177. }
  27178. }
  27179. });
  27180. /**
  27181. * @class Ext.chart.interactions.ItemEdit
  27182. * @extends Ext.chart.interactions.ItemHighlight
  27183. *
  27184. * The 'itemedit' interaction allows the user to edit store data
  27185. * by dragging series items in the chart.
  27186. *
  27187. * The 'itemedit' interaction extends the
  27188. * {@link Ext.chart.interactions.ItemHighlight 'itemhighlight'} interaction,
  27189. * so it also acts like one. If you need both interactions in a single chart,
  27190. * 'itemedit' should be sufficient. Hovering/tapping will result in highlighting,
  27191. * and dragging will result in editing.
  27192. */
  27193. Ext.define('Ext.chart.interactions.ItemEdit', {
  27194. extend: 'Ext.chart.interactions.ItemHighlight',
  27195. requires: [
  27196. 'Ext.tip.ToolTip'
  27197. ],
  27198. type: 'itemedit',
  27199. alias: 'interaction.itemedit',
  27200. isItemEdit: true,
  27201. config: {
  27202. /**
  27203. * @cfg {Object} [style=null]
  27204. * The style that will be applied to the series item on dragging.
  27205. * By default, series item will have no fill,
  27206. * and will have a dashed stroke of the same color.
  27207. */
  27208. style: null,
  27209. /**
  27210. * @cfg {Function/String} [renderer=null]
  27211. * A function that returns style attributes for the item that's being dragged.
  27212. * This is useful if you want to give a visual feedback to the user when
  27213. * they dragged to a certain point.
  27214. *
  27215. * @param {Object} [data] The following properties are available:
  27216. *
  27217. * @param {Object} data.target The object containing the xField/xValue or/and
  27218. * yField/yValue properties, where the xField/yField specify the store records
  27219. * being edited and the xValue/yValue the target values to be set when
  27220. * the interaction ends. The object also contains the 'index' of the record
  27221. * being edited.
  27222. * @param {Object} data.style The style that is going to be used for the dragged item.
  27223. * The attributes returned by the renderer will be applied on top of this style.
  27224. * @param {Object} data.item The series item being dragged.
  27225. * This is actually the {@link Ext.chart.AbstractChart#highlightItem}.
  27226. *
  27227. * @return {Object} The style attributes to be set on the dragged item.
  27228. */
  27229. renderer: null,
  27230. /**
  27231. * @cfg {Object/Boolean} [tooltip=true]
  27232. */
  27233. tooltip: true,
  27234. gestures: {
  27235. dragstart: 'onDragStart',
  27236. drag: 'onDrag',
  27237. dragend: 'onDragEnd'
  27238. },
  27239. cursors: {
  27240. ewResize: 'ew-resize',
  27241. nsResize: 'ns-resize',
  27242. move: 'move'
  27243. }
  27244. },
  27245. /**
  27246. * @private
  27247. * @cfg {Boolean} [sticky=false]
  27248. */
  27249. /**
  27250. * @event beginitemedit
  27251. * Fires when item edit operation (dragging) begins.
  27252. * @param {Ext.chart.AbstractChart} chart The chart the interaction belongs to.
  27253. * @param {Ext.chart.interactions.ItemEdit} interaction The interaction.
  27254. * @param {Object} item The item that is about to be edited.
  27255. */
  27256. /**
  27257. * @event enditemedit
  27258. * Fires when item edit operation (dragging) ends.
  27259. * @param {Ext.chart.AbstractChart} chart The chart the interaction belongs to.
  27260. * @param {Ext.chart.interactions.ItemEdit} interaction The interaction.
  27261. * @param {Object} item The item that was edited.
  27262. * @param {Object} target The object containing target values the were used.
  27263. */
  27264. item: null,
  27265. // Item being edited.
  27266. applyTooltip: function(tooltip) {
  27267. var config;
  27268. if (tooltip) {
  27269. config = Ext.apply({}, tooltip, {
  27270. renderer: this.defaultTooltipRenderer,
  27271. constrainPosition: true,
  27272. shrinkWrapDock: true,
  27273. autoHide: true,
  27274. trackMouse: true,
  27275. mouseOffset: [
  27276. 20,
  27277. 20
  27278. ]
  27279. });
  27280. tooltip = new Ext.tip.ToolTip(config);
  27281. }
  27282. return tooltip;
  27283. },
  27284. defaultTooltipRenderer: function(tooltip, item, target, e) {
  27285. var parts = [];
  27286. if (target.xField) {
  27287. parts.push(target.xField + ': ' + target.xValue);
  27288. }
  27289. if (target.yField) {
  27290. parts.push(target.yField + ': ' + target.yValue);
  27291. }
  27292. tooltip.setHtml(parts.join('<br>'));
  27293. },
  27294. onDragStart: function(e) {
  27295. var me = this,
  27296. chart = me.getChart(),
  27297. item = chart.getHighlightItem();
  27298. e.claimGesture();
  27299. if (item) {
  27300. chart.fireEvent('beginitemedit', chart, me, me.item = item);
  27301. // If ItemEdit interaction comes before other interactions
  27302. // in the chart's 'interactions' config, this will
  27303. // prevent other interactions hijacking the 'dragstart'
  27304. // event. We only stop event propagation is there's
  27305. // an item to edit under cursor/finger, otherwise we
  27306. // let other interactions (e.g. 'panzoom') handle the event.
  27307. return false;
  27308. }
  27309. },
  27310. onDrag: function(e) {
  27311. var me = this,
  27312. chart = me.getChart(),
  27313. item = chart.getHighlightItem(),
  27314. type = item && item.sprite.type;
  27315. if (item) {
  27316. switch (type) {
  27317. case 'barSeries':
  27318. return me.onDragBar(e);
  27319. case 'scatterSeries':
  27320. return me.onDragScatter(e);
  27321. }
  27322. }
  27323. },
  27324. highlight: function(item) {
  27325. var me = this,
  27326. chart = me.getChart(),
  27327. flipXY = chart.getFlipXY(),
  27328. cursors = me.getCursors(),
  27329. type = item && item.sprite.type,
  27330. style = chart.el.dom.style;
  27331. me.callParent([
  27332. item
  27333. ]);
  27334. if (item) {
  27335. switch (type) {
  27336. case 'barSeries':
  27337. if (flipXY) {
  27338. style.cursor = cursors.ewResize;
  27339. } else {
  27340. style.cursor = cursors.nsResize;
  27341. };
  27342. break;
  27343. case 'scatterSeries':
  27344. style.cursor = cursors.move;
  27345. break;
  27346. }
  27347. } else {
  27348. chart.el.dom.style.cursor = 'default';
  27349. }
  27350. },
  27351. onDragBar: function(e) {
  27352. var me = this,
  27353. chart = me.getChart(),
  27354. isRtl = chart.getInherited().rtl,
  27355. flipXY = chart.isCartesian && chart.getFlipXY(),
  27356. item = chart.getHighlightItem(),
  27357. marker = item.sprite.getMarker('items'),
  27358. instance = marker.getMarkerFor(item.sprite.getId(), item.index),
  27359. surface = item.sprite.getSurface(),
  27360. surfaceRect = surface.getRect(),
  27361. xy = surface.getEventXY(e),
  27362. matrix = item.sprite.attr.matrix,
  27363. renderer = me.getRenderer(),
  27364. style, changes, params, positionY;
  27365. if (flipXY) {
  27366. positionY = isRtl ? surfaceRect[2] - xy[0] : xy[0];
  27367. } else {
  27368. positionY = surfaceRect[3] - xy[1];
  27369. }
  27370. style = {
  27371. x: instance.x,
  27372. y: positionY,
  27373. width: instance.width,
  27374. height: instance.height + (instance.y - positionY),
  27375. radius: instance.radius,
  27376. fillStyle: 'none',
  27377. lineDash: [
  27378. 4,
  27379. 4
  27380. ],
  27381. zIndex: 100
  27382. };
  27383. Ext.apply(style, me.getStyle());
  27384. if (Ext.isArray(item.series.getYField())) {
  27385. // stacked bars
  27386. positionY = positionY - instance.y - instance.height;
  27387. }
  27388. me.target = {
  27389. index: item.index,
  27390. yField: item.field,
  27391. yValue: (positionY - matrix.getDY()) / matrix.getYY()
  27392. };
  27393. params = [
  27394. chart,
  27395. {
  27396. target: me.target,
  27397. style: style,
  27398. item: item
  27399. }
  27400. ];
  27401. changes = Ext.callback(renderer, null, params, 0, chart);
  27402. if (changes) {
  27403. Ext.apply(style, changes);
  27404. }
  27405. // The interaction works by putting another series item instance
  27406. // under 'itemedit' ID with a slightly different style (default) or
  27407. // whatever style the user provided.
  27408. item.sprite.putMarker('items', style, 'itemedit');
  27409. me.showTooltip(e, me.target, item);
  27410. surface.renderFrame();
  27411. },
  27412. onDragScatter: function(e) {
  27413. var me = this,
  27414. chart = me.getChart(),
  27415. isRtl = chart.getInherited().rtl,
  27416. flipXY = chart.isCartesian && chart.getFlipXY(),
  27417. item = chart.getHighlightItem(),
  27418. marker = item.sprite.getMarker('markers'),
  27419. instance = marker.getMarkerFor(item.sprite.getId(), item.index),
  27420. surface = item.sprite.getSurface(),
  27421. surfaceRect = surface.getRect(),
  27422. xy = surface.getEventXY(e),
  27423. matrix = item.sprite.attr.matrix,
  27424. xAxis = item.series.getXAxis(),
  27425. isEditableX = xAxis && xAxis.getLayout().isContinuous,
  27426. renderer = me.getRenderer(),
  27427. style, changes, params, positionX, positionY, hintX, hintY;
  27428. if (flipXY) {
  27429. positionY = isRtl ? surfaceRect[2] - xy[0] : xy[0];
  27430. } else {
  27431. positionY = surfaceRect[3] - xy[1];
  27432. }
  27433. if (isEditableX) {
  27434. if (flipXY) {
  27435. positionX = surfaceRect[3] - xy[1];
  27436. } else {
  27437. positionX = xy[0];
  27438. }
  27439. } else {
  27440. positionX = instance.translationX;
  27441. }
  27442. if (isEditableX) {
  27443. hintX = xy[0];
  27444. hintY = xy[1];
  27445. } else {
  27446. if (flipXY) {
  27447. hintX = xy[0];
  27448. hintY = instance.translationY;
  27449. } else // no change
  27450. {
  27451. hintX = instance.translationX;
  27452. hintY = xy[1];
  27453. }
  27454. }
  27455. // no change
  27456. style = {
  27457. translationX: hintX,
  27458. translationY: hintY,
  27459. scalingX: instance.scalingX,
  27460. scalingY: instance.scalingY,
  27461. r: instance.r,
  27462. fillStyle: 'none',
  27463. lineDash: [
  27464. 4,
  27465. 4
  27466. ],
  27467. zIndex: 100
  27468. };
  27469. Ext.apply(style, me.getStyle());
  27470. me.target = {
  27471. index: item.index,
  27472. yField: item.field,
  27473. yValue: (positionY - matrix.getDY()) / matrix.getYY()
  27474. };
  27475. if (isEditableX) {
  27476. Ext.apply(me.target, {
  27477. xField: item.series.getXField(),
  27478. xValue: (positionX - matrix.getDX()) / matrix.getXX()
  27479. });
  27480. }
  27481. params = [
  27482. chart,
  27483. {
  27484. target: me.target,
  27485. style: style,
  27486. item: item
  27487. }
  27488. ];
  27489. changes = Ext.callback(renderer, null, params, 0, chart);
  27490. if (changes) {
  27491. Ext.apply(style, changes);
  27492. }
  27493. // This marker acts as a visual hint while dragging.
  27494. item.sprite.putMarker('markers', style, 'itemedit');
  27495. me.showTooltip(e, me.target, item);
  27496. surface.renderFrame();
  27497. },
  27498. showTooltip: function(e, target, item) {
  27499. var tooltip = this.getTooltip(),
  27500. config, chart;
  27501. if (tooltip && Ext.toolkit !== 'modern') {
  27502. config = tooltip.config;
  27503. chart = this.getChart();
  27504. Ext.callback(config.renderer, null, [
  27505. tooltip,
  27506. item,
  27507. target,
  27508. e
  27509. ], 0, chart);
  27510. // If trackMouse is set, a ToolTip shows by its pointerEvent
  27511. tooltip.pointerEvent = e;
  27512. if (tooltip.isVisible()) {
  27513. // After show handling repositions according
  27514. // to configuration. trackMouse uses the pointerEvent
  27515. // If aligning to an element, it uses a currentTarget
  27516. // flyweight which may be attached to any DOM element.
  27517. tooltip.realignToTarget();
  27518. } else {
  27519. tooltip.show();
  27520. }
  27521. }
  27522. },
  27523. hideTooltip: function() {
  27524. var tooltip = this.getTooltip();
  27525. if (tooltip && Ext.toolkit !== 'modern') {
  27526. tooltip.hide();
  27527. }
  27528. },
  27529. onDragEnd: function(e) {
  27530. var me = this,
  27531. target = me.target,
  27532. chart = me.getChart(),
  27533. store = chart.getStore(),
  27534. record;
  27535. if (target) {
  27536. record = store.getAt(target.index);
  27537. if (target.yField) {
  27538. record.set(target.yField, target.yValue, {
  27539. convert: false
  27540. });
  27541. }
  27542. if (target.xField) {
  27543. record.set(target.xField, target.xValue, {
  27544. convert: false
  27545. });
  27546. }
  27547. if (target.yField || target.xField) {
  27548. me.getChart().onDataChanged();
  27549. }
  27550. me.target = null;
  27551. }
  27552. me.hideTooltip();
  27553. if (me.item) {
  27554. chart.fireEvent('enditemedit', chart, me, me.item, target);
  27555. }
  27556. me.highlight(me.item = null);
  27557. },
  27558. destroy: function() {
  27559. // Peek at the config, so we don't create one just to destroy it,
  27560. // if a user has set 'tooltip' config to 'false'.
  27561. var tooltip = this.getConfig('tooltip', true);
  27562. Ext.destroy(tooltip);
  27563. this.callParent();
  27564. }
  27565. });
  27566. /**
  27567. * The PanZoom interaction allows the user to navigate the data for one or more chart
  27568. * axes by panning and/or zooming. Navigation can be limited to particular axes. Zooming is
  27569. * performed by pinching on the chart or axis area; panning is performed by single-touch dragging.
  27570. * The interaction only works with cartesian charts/series.
  27571. *
  27572. * For devices which do not support multiple-touch events, zooming can not be done via pinch
  27573. * gestures; in this case the interaction will allow the user to perform both zooming and panning
  27574. * using the same single-touch drag gesture.
  27575. * {@link #modeToggleButton} provides a button to indicate and toggle between two modes.
  27576. *
  27577. * @example
  27578. * Ext.create({
  27579. * renderTo: document.body,
  27580. * xtype: 'cartesian',
  27581. * width: 600,
  27582. * height: 400,
  27583. * insetPadding: 40,
  27584. * interactions: [{
  27585. * type: 'panzoom',
  27586. * zoomOnPan: true
  27587. * }],
  27588. * store: {
  27589. * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
  27590. * data: [{
  27591. * 'name': 'metric one',
  27592. * 'data1': 10,
  27593. * 'data2': 12,
  27594. * 'data3': 14,
  27595. * 'data4': 8,
  27596. * 'data5': 13
  27597. * }, {
  27598. * 'name': 'metric two',
  27599. * 'data1': 7,
  27600. * 'data2': 8,
  27601. * 'data3': 16,
  27602. * 'data4': 10,
  27603. * 'data5': 3
  27604. * }, {
  27605. * 'name': 'metric three',
  27606. * 'data1': 5,
  27607. * 'data2': 2,
  27608. * 'data3': 14,
  27609. * 'data4': 12,
  27610. * 'data5': 7
  27611. * }, {
  27612. * 'name': 'metric four',
  27613. * 'data1': 2,
  27614. * 'data2': 14,
  27615. * 'data3': 6,
  27616. * 'data4': 1,
  27617. * 'data5': 23
  27618. * }, {
  27619. * 'name': 'metric five',
  27620. * 'data1': 27,
  27621. * 'data2': 38,
  27622. * 'data3': 36,
  27623. * 'data4': 13,
  27624. * 'data5': 33
  27625. * }]
  27626. * },
  27627. * axes: [{
  27628. * type: 'numeric',
  27629. * position: 'left',
  27630. * fields: ['data1'],
  27631. * title: {
  27632. * text: 'Sample Values',
  27633. * fontSize: 15
  27634. * },
  27635. * grid: true,
  27636. * minimum: 0
  27637. * }, {
  27638. * type: 'category',
  27639. * position: 'bottom',
  27640. * fields: ['name'],
  27641. * title: {
  27642. * text: 'Sample Values',
  27643. * fontSize: 15
  27644. * }
  27645. * }],
  27646. * series: [{
  27647. * type: 'line',
  27648. * highlight: {
  27649. * size: 7,
  27650. * radius: 7
  27651. * },
  27652. * style: {
  27653. * stroke: 'rgb(143,203,203)'
  27654. * },
  27655. * xField: 'name',
  27656. * yField: 'data1',
  27657. * marker: {
  27658. * type: 'path',
  27659. * path: ['M', - 2, 0, 0, 2, 2, 0, 0, - 2, 'Z'],
  27660. * stroke: 'blue',
  27661. * lineWidth: 0
  27662. * }
  27663. * }, {
  27664. * type: 'line',
  27665. * highlight: {
  27666. * size: 7,
  27667. * radius: 7
  27668. * },
  27669. * fill: true,
  27670. * xField: 'name',
  27671. * yField: 'data3',
  27672. * marker: {
  27673. * type: 'circle',
  27674. * radius: 4,
  27675. * lineWidth: 0
  27676. * }
  27677. * }]
  27678. * });
  27679. *
  27680. * The configuration object for the `panzoom` interaction type should specify which axes
  27681. * will be made navigable via the `axes` config. See the {@link #axes} config documentation
  27682. * for details on the allowed formats. If the `axes` config is not specified, it will default
  27683. * to making all axes navigable with the default axis options.
  27684. *
  27685. */
  27686. Ext.define('Ext.chart.interactions.PanZoom', {
  27687. extend: 'Ext.chart.interactions.Abstract',
  27688. type: 'panzoom',
  27689. alias: 'interaction.panzoom',
  27690. requires: [
  27691. 'Ext.draw.Animator'
  27692. ],
  27693. config: {
  27694. /**
  27695. * @cfg {Object/Array} axes
  27696. * Specifies which axes should be made navigable. The config value can take the following
  27697. * formats:
  27698. *
  27699. * - An Object with keys corresponding to the {@link Ext.chart.axis.Axis#position position}
  27700. * of each axis that should be made navigable. Each key's value can either be an Object
  27701. * with further configuration options for each axis or simply `true` for a default set
  27702. * of options.
  27703. *
  27704. * {
  27705. * type: 'panzoom',
  27706. * axes: {
  27707. * left: {
  27708. * maxZoom: 5,
  27709. * allowPan: false
  27710. * },
  27711. * bottom: true
  27712. * }
  27713. * }
  27714. *
  27715. * If using the full Object form, the following options can be specified for each axis:
  27716. *
  27717. * - minZoom (Number) A minimum zoom level for the axis. Defaults to `1` which is its
  27718. * natural size.
  27719. * - maxZoom (Number) A maximum zoom level for the axis. Defaults to `10`.
  27720. * - startZoom (Number) A starting zoom level for the axis. Defaults to `1`.
  27721. * - allowZoom (Boolean) Whether zooming is allowed for the axis. Defaults to `true`.
  27722. * - allowPan (Boolean) Whether panning is allowed for the axis. Defaults to `true`.
  27723. * - startPan (Boolean) A starting panning offset for the axis. Defaults to `0`.
  27724. *
  27725. * - An Array of strings, each one corresponding to the {@link Ext.chart.axis.Axis#position
  27726. * position} of an axis that should be made navigable. The default options will be used
  27727. * for each named axis.
  27728. *
  27729. * {
  27730. * type: 'panzoom',
  27731. * axes: ['left', 'bottom']
  27732. * }
  27733. *
  27734. * If the `axes` config is not specified, it will default to making all axes navigable
  27735. * with the default axis options.
  27736. */
  27737. axes: {
  27738. top: {},
  27739. right: {},
  27740. bottom: {},
  27741. left: {}
  27742. },
  27743. minZoom: null,
  27744. maxZoom: null,
  27745. /**
  27746. * @cfg {Boolean} showOverflowArrows
  27747. * If `true`, arrows will be conditionally shown at either end of each axis to indicate that
  27748. * the axis is overflowing and can therefore be panned in that direction. Set this
  27749. * to `false` to prevent the arrows from being displayed.
  27750. */
  27751. showOverflowArrows: true,
  27752. /**
  27753. * @cfg {Object} overflowArrowOptions
  27754. * A set of optional overrides for the overflow arrow sprites' options. Only relevant when
  27755. * {@link #showOverflowArrows} is `true`.
  27756. */
  27757. /**
  27758. * @cfg {String} panGesture
  27759. * Defines the gesture that initiates panning.
  27760. * @private
  27761. */
  27762. panGesture: 'drag',
  27763. /**
  27764. * @cfg {String} zoomGesture
  27765. * Defines the gesture that initiates zooming.
  27766. * @private
  27767. */
  27768. zoomGesture: 'pinch',
  27769. /**
  27770. * @cfg {Boolean} zoomOnPanGesture
  27771. * @deprecated 6.2 Please use {@link #zoomOnPan} instead.
  27772. * If `true`, the pan gesture will zoom the chart.
  27773. */
  27774. zoomOnPanGesture: null,
  27775. /**
  27776. * @cfg {Boolean} zoomOnPan
  27777. * If `true`, the pan gesture will zoom the chart.
  27778. */
  27779. zoomOnPan: false,
  27780. /**
  27781. * @cfg {Boolean} [doubleTapReset=false]
  27782. * If `true`, the double tap on a chart will reset the current pan/zoom to show the whole
  27783. * chart.
  27784. */
  27785. doubleTapReset: false,
  27786. modeToggleButton: {
  27787. xtype: 'segmentedbutton',
  27788. width: 200,
  27789. defaults: {
  27790. ui: 'default-toolbar'
  27791. },
  27792. cls: Ext.baseCSSPrefix + 'panzoom-toggle',
  27793. items: [
  27794. {
  27795. text: 'Pan',
  27796. value: 'pan'
  27797. },
  27798. {
  27799. text: 'Zoom',
  27800. value: 'zoom'
  27801. }
  27802. ]
  27803. },
  27804. hideLabelInGesture: false
  27805. },
  27806. // Ext.os.is.Android
  27807. stopAnimationBeforeSync: true,
  27808. applyAxes: function(axesConfig, oldAxesConfig) {
  27809. return Ext.merge(oldAxesConfig || {}, axesConfig);
  27810. },
  27811. updateZoomOnPan: function(zoomOnPan) {
  27812. var button = this.getModeToggleButton();
  27813. button.setValue(zoomOnPan ? 'zoom' : 'pan');
  27814. },
  27815. updateZoomOnPanGesture: function(zoomOnPanGesture) {
  27816. this.setZoomOnPan(zoomOnPanGesture);
  27817. },
  27818. getZoomOnPanGesture: function() {
  27819. return this.getZoomOnPan();
  27820. },
  27821. applyModeToggleButton: function(button, oldButton) {
  27822. return Ext.factory(button, 'Ext.button.Segmented', oldButton);
  27823. },
  27824. updateModeToggleButton: function(button) {
  27825. if (button) {
  27826. button.on('change', 'onModeToggleChange', this);
  27827. }
  27828. },
  27829. onModeToggleChange: function(segmentedButton, value) {
  27830. this.setZoomOnPan(value === 'zoom');
  27831. },
  27832. getGestures: function() {
  27833. var me = this,
  27834. gestures = {},
  27835. pan = me.getPanGesture(),
  27836. zoom = me.getZoomGesture();
  27837. gestures[zoom] = 'onZoomGestureMove';
  27838. gestures[zoom + 'start'] = 'onZoomGestureStart';
  27839. gestures[zoom + 'end'] = 'onZoomGestureEnd';
  27840. gestures[pan] = 'onPanGestureMove';
  27841. gestures[pan + 'start'] = 'onPanGestureStart';
  27842. gestures[pan + 'end'] = 'onPanGestureEnd';
  27843. gestures.doubletap = 'onDoubleTap';
  27844. return gestures;
  27845. },
  27846. onDoubleTap: function(e) {
  27847. var me = this,
  27848. doubleTapReset = me.getDoubleTapReset(),
  27849. chart, axes, axis, i, ln;
  27850. if (doubleTapReset) {
  27851. chart = me.getChart();
  27852. axes = chart.getAxes();
  27853. for (i = 0 , ln = axes.length; i < ln; i++) {
  27854. axis = axes[i];
  27855. axis.setVisibleRange([
  27856. 0,
  27857. 1
  27858. ]);
  27859. }
  27860. chart.redraw();
  27861. }
  27862. },
  27863. onPanGestureStart: function(e) {
  27864. var me = this,
  27865. chart, rect, xy;
  27866. if (!e || !e.touches || e.touches.length < 2) {
  27867. // Limit drags to single touch
  27868. chart = me.getChart();
  27869. rect = chart.getInnerRect();
  27870. xy = chart.element.getXY();
  27871. e.claimGesture();
  27872. chart.suspendAnimation();
  27873. me.startX = e.getX() - xy[0] - rect[0];
  27874. me.startY = e.getY() - xy[1] - rect[1];
  27875. me.oldVisibleRanges = null;
  27876. me.hideLabels();
  27877. chart.suspendThicknessChanged();
  27878. me.lockEvents(me.getPanGesture());
  27879. return false;
  27880. }
  27881. },
  27882. onPanGestureMove: function(e) {
  27883. var me = this,
  27884. isMouse = e.pointerType === 'mouse',
  27885. isZoomOnPan = isMouse && me.getZoomOnPan(),
  27886. chart, rect, xy;
  27887. if (me.getLocks()[me.getPanGesture()] === me) {
  27888. // Limit drags to single touch.
  27889. chart = me.getChart();
  27890. rect = chart.getInnerRect();
  27891. xy = chart.element.getXY();
  27892. if (isZoomOnPan) {
  27893. me.transformAxesBy(me.getZoomableAxes(e), 0, 0, (e.getX() - xy[0] - rect[0]) / me.startX, me.startY / (e.getY() - xy[1] - rect[1]));
  27894. } else {
  27895. me.transformAxesBy(me.getPannableAxes(e), e.getX() - xy[0] - rect[0] - me.startX, e.getY() - xy[1] - rect[1] - me.startY, 1, 1);
  27896. }
  27897. me.sync();
  27898. return false;
  27899. }
  27900. },
  27901. onPanGestureEnd: function(e) {
  27902. var me = this,
  27903. pan = me.getPanGesture(),
  27904. chart;
  27905. if (me.getLocks()[pan] === me) {
  27906. chart = me.getChart();
  27907. chart.resumeThicknessChanged();
  27908. me.showLabels();
  27909. me.sync();
  27910. me.unlockEvents(pan);
  27911. chart.resumeAnimation();
  27912. return false;
  27913. }
  27914. },
  27915. onZoomGestureStart: function(e) {
  27916. if (e.touches && e.touches.length === 2) {
  27917. // eslint-disable-next-line vars-on-top
  27918. var me = this,
  27919. chart = me.getChart(),
  27920. xy = chart.element.getXY(),
  27921. rect = chart.getInnerRect(),
  27922. x = xy[0] + rect[0],
  27923. y = xy[1] + rect[1],
  27924. newPoints = [
  27925. e.touches[0].point.x - x,
  27926. e.touches[0].point.y - y,
  27927. e.touches[1].point.x - x,
  27928. e.touches[1].point.y - y
  27929. ],
  27930. xDistance = Math.max(44, Math.abs(newPoints[2] - newPoints[0])),
  27931. yDistance = Math.max(44, Math.abs(newPoints[3] - newPoints[1]));
  27932. e.claimGesture();
  27933. chart.suspendAnimation();
  27934. chart.suspendThicknessChanged();
  27935. me.lastZoomDistances = [
  27936. xDistance,
  27937. yDistance
  27938. ];
  27939. me.lastPoints = newPoints;
  27940. me.oldVisibleRanges = null;
  27941. me.hideLabels();
  27942. me.lockEvents(me.getZoomGesture());
  27943. return false;
  27944. }
  27945. },
  27946. onZoomGestureMove: function(e) {
  27947. var me = this;
  27948. if (me.getLocks()[me.getZoomGesture()] === me) {
  27949. // eslint-disable-next-line vars-on-top
  27950. var chart = me.getChart(),
  27951. rect = chart.getInnerRect(),
  27952. xy = chart.element.getXY(),
  27953. x = xy[0] + rect[0],
  27954. y = xy[1] + rect[1],
  27955. abs = Math.abs,
  27956. lastPoints = me.lastPoints,
  27957. newPoints = [
  27958. e.touches[0].point.x - x,
  27959. e.touches[0].point.y - y,
  27960. e.touches[1].point.x - x,
  27961. e.touches[1].point.y - y
  27962. ],
  27963. xDistance = Math.max(44, abs(newPoints[2] - newPoints[0])),
  27964. yDistance = Math.max(44, abs(newPoints[3] - newPoints[1])),
  27965. lastDistances = this.lastZoomDistances || [
  27966. xDistance,
  27967. yDistance
  27968. ],
  27969. zoomX = xDistance / lastDistances[0],
  27970. zoomY = yDistance / lastDistances[1];
  27971. 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);
  27972. me.sync();
  27973. return false;
  27974. }
  27975. },
  27976. onZoomGestureEnd: function(e) {
  27977. var me = this,
  27978. zoom = me.getZoomGesture(),
  27979. chart;
  27980. if (me.getLocks()[zoom] === me) {
  27981. chart = me.getChart();
  27982. chart.resumeThicknessChanged();
  27983. me.showLabels();
  27984. me.sync();
  27985. me.unlockEvents(zoom);
  27986. chart.resumeAnimation();
  27987. return false;
  27988. }
  27989. },
  27990. hideLabels: function() {
  27991. if (this.getHideLabelInGesture()) {
  27992. this.eachInteractiveAxes(function(axis) {
  27993. axis.hideLabels();
  27994. });
  27995. }
  27996. },
  27997. showLabels: function() {
  27998. if (this.getHideLabelInGesture()) {
  27999. this.eachInteractiveAxes(function(axis) {
  28000. axis.showLabels();
  28001. });
  28002. }
  28003. },
  28004. isEventOnAxis: function(e, axis) {
  28005. // TODO: right now this uses the current event position but really we want to only
  28006. // use the gesture's start event. Pinch does not give that to us though.
  28007. var rect = axis.getSurface().getRect();
  28008. return rect[0] <= e.getX() && e.getX() <= rect[0] + rect[2] && rect[1] <= e.getY() && e.getY() <= rect[1] + rect[3];
  28009. },
  28010. getPannableAxes: function(e) {
  28011. var me = this,
  28012. axisConfigs = me.getAxes(),
  28013. axes = me.getChart().getAxes(),
  28014. i,
  28015. ln = axes.length,
  28016. result = [],
  28017. isEventOnAxis = false,
  28018. config;
  28019. if (e) {
  28020. for (i = 0; i < ln; i++) {
  28021. if (this.isEventOnAxis(e, axes[i])) {
  28022. isEventOnAxis = true;
  28023. break;
  28024. }
  28025. }
  28026. }
  28027. for (i = 0; i < ln; i++) {
  28028. config = axisConfigs[axes[i].getPosition()];
  28029. if (config && config.allowPan !== false && (!isEventOnAxis || this.isEventOnAxis(e, axes[i]))) {
  28030. result.push(axes[i]);
  28031. }
  28032. }
  28033. return result;
  28034. },
  28035. getZoomableAxes: function(e) {
  28036. var me = this,
  28037. axisConfigs = me.getAxes(),
  28038. axes = me.getChart().getAxes(),
  28039. result = [],
  28040. i,
  28041. ln = axes.length,
  28042. axis,
  28043. isEventOnAxis = false,
  28044. config;
  28045. if (e) {
  28046. for (i = 0; i < ln; i++) {
  28047. if (this.isEventOnAxis(e, axes[i])) {
  28048. isEventOnAxis = true;
  28049. break;
  28050. }
  28051. }
  28052. }
  28053. for (i = 0; i < ln; i++) {
  28054. axis = axes[i];
  28055. config = axisConfigs[axis.getPosition()];
  28056. if (config && config.allowZoom !== false && (!isEventOnAxis || this.isEventOnAxis(e, axis))) {
  28057. result.push(axis);
  28058. }
  28059. }
  28060. return result;
  28061. },
  28062. eachInteractiveAxes: function(fn) {
  28063. var me = this,
  28064. axisConfigs = me.getAxes(),
  28065. axes = me.getChart().getAxes(),
  28066. i;
  28067. for (i = 0; i < axes.length; i++) {
  28068. if (axisConfigs[axes[i].getPosition()]) {
  28069. if (false === fn.call(this, axes[i])) {
  28070. return;
  28071. }
  28072. }
  28073. }
  28074. },
  28075. transformAxesBy: function(axes, panX, panY, sx, sy) {
  28076. var rect = this.getChart().getInnerRect(),
  28077. axesCfg = this.getAxes(),
  28078. oldVisibleRanges = this.oldVisibleRanges,
  28079. result = false,
  28080. axisCfg, i;
  28081. if (!oldVisibleRanges) {
  28082. this.oldVisibleRanges = oldVisibleRanges = {};
  28083. this.eachInteractiveAxes(function(axis) {
  28084. oldVisibleRanges[axis.getId()] = axis.getVisibleRange();
  28085. });
  28086. }
  28087. if (!rect) {
  28088. return;
  28089. }
  28090. for (i = 0; i < axes.length; i++) {
  28091. axisCfg = axesCfg[axes[i].getPosition()];
  28092. result = this.transformAxisBy(axes[i], oldVisibleRanges[axes[i].getId()], panX, panY, sx, sy, this.minZoom || axisCfg.minZoom, this.maxZoom || axisCfg.maxZoom) || result;
  28093. }
  28094. return result;
  28095. },
  28096. transformAxisBy: function(axis, oldVisibleRange, panX, panY, sx, sy, minZoom, maxZoom) {
  28097. var me = this,
  28098. visibleLength = oldVisibleRange[1] - oldVisibleRange[0],
  28099. visibleRange = axis.getVisibleRange(),
  28100. actualMinZoom = minZoom || me.getMinZoom() || axis.config.minZoom,
  28101. actualMaxZoom = maxZoom || me.getMaxZoom() || axis.config.maxZoom,
  28102. rect = me.getChart().getInnerRect(),
  28103. left, right, isSide, pan, length;
  28104. if (!rect) {
  28105. return;
  28106. }
  28107. isSide = axis.isSide();
  28108. length = isSide ? rect[3] : rect[2];
  28109. pan = isSide ? -panY : panX;
  28110. visibleLength /= isSide ? sy : sx;
  28111. if (visibleLength < 0) {
  28112. visibleLength = -visibleLength;
  28113. }
  28114. if (visibleLength * actualMinZoom > 1) {
  28115. visibleLength = 1;
  28116. }
  28117. if (visibleLength * actualMaxZoom < 1) {
  28118. visibleLength = 1 / actualMaxZoom;
  28119. }
  28120. left = oldVisibleRange[0];
  28121. right = oldVisibleRange[1];
  28122. visibleRange = visibleRange[1] - visibleRange[0];
  28123. if (visibleLength === visibleRange && visibleRange === 1) {
  28124. return;
  28125. }
  28126. axis.setVisibleRange([
  28127. (oldVisibleRange[0] + oldVisibleRange[1] - visibleLength) * 0.5 - pan / length * visibleLength,
  28128. (oldVisibleRange[0] + oldVisibleRange[1] + visibleLength) * 0.5 - pan / length * visibleLength
  28129. ]);
  28130. return Math.abs(left - axis.getVisibleRange()[0]) > 1.0E-10 || Math.abs(right - axis.getVisibleRange()[1]) > 1.0E-10;
  28131. },
  28132. destroy: function() {
  28133. this.setModeToggleButton(null);
  28134. this.callParent();
  28135. }
  28136. });
  28137. /* eslint-disable max-len */
  28138. /**
  28139. * @class Ext.chart.interactions.Rotate
  28140. * @extends Ext.chart.interactions.Abstract
  28141. *
  28142. * The Rotate interaction allows the user to rotate a polar chart about its central point.
  28143. *
  28144. * @example
  28145. * Ext.create('Ext.Container', {
  28146. * renderTo: Ext.getBody(),
  28147. * width: 600,
  28148. * height: 400,
  28149. * layout: 'fit',
  28150. * items: {
  28151. * xtype: 'polar',
  28152. * interactions: 'rotate',
  28153. * colors: ["#115fa6", "#94ae0a", "#a61120", "#ff8809", "#ffd13e"],
  28154. * store: {
  28155. * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
  28156. * data: [
  28157. * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
  28158. * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
  28159. * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
  28160. * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
  28161. * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
  28162. * ]
  28163. * },
  28164. * series: {
  28165. * type: 'pie',
  28166. * label: {
  28167. * field: 'name',
  28168. * display: 'rotate'
  28169. * },
  28170. * xField: 'data3',
  28171. * donut: 30
  28172. * }
  28173. * }
  28174. * });
  28175. */
  28176. /* eslint-enable max-len */
  28177. Ext.define('Ext.chart.interactions.Rotate', {
  28178. extend: 'Ext.chart.interactions.Abstract',
  28179. type: 'rotate',
  28180. alternateClassName: 'Ext.chart.interactions.RotatePie3D',
  28181. alias: [
  28182. 'interaction.rotate',
  28183. 'interaction.rotatePie3d'
  28184. ],
  28185. /**
  28186. * @event rotate
  28187. * Fires on every tick of the rotation.
  28188. * @param {Ext.chart.interactions.Rotate} this This interaction.
  28189. * @param {Number} angle The new current rotation angle.
  28190. */
  28191. /**
  28192. * @event rotatestart
  28193. * Fires when a user initiates the rotation.
  28194. * @param {Ext.chart.interactions.Rotate} this This interaction.
  28195. * @param {Number} angle The new current rotation angle.
  28196. */
  28197. /**
  28198. * @event rotateend
  28199. * Fires after a user finishes the rotation.
  28200. * @param {Ext.chart.interactions.Rotate} this This interaction.
  28201. * @param {Number} angle The new current rotation angle.
  28202. */
  28203. /**
  28204. * @deprecated 6.5.1 Use the 'rotateend' event instead.
  28205. * @event rotationEnd
  28206. * Fires after a user finishes the rotation
  28207. * @param {Ext.chart.interactions.Rotate} this This interaction.
  28208. * @param {Number} angle The new current rotation angle.
  28209. */
  28210. config: {
  28211. /**
  28212. * @cfg {String} gesture
  28213. * Defines the gesture type that will be used to rotate the chart. Currently only
  28214. * supports `pinch` for two-finger rotation and `drag` for single-finger rotation.
  28215. * @private
  28216. */
  28217. gesture: 'rotate',
  28218. gestures: {
  28219. dragstart: 'onGestureStart',
  28220. drag: 'onGesture',
  28221. dragend: 'onGestureEnd'
  28222. },
  28223. /**
  28224. * @cfg {Number} rotation
  28225. * Saves the current rotation of the series. Accepts negative values
  28226. * and values > 360 ( / 180 * Math.PI)
  28227. * @private
  28228. */
  28229. rotation: 0
  28230. },
  28231. oldRotations: null,
  28232. getAngle: function(e) {
  28233. var me = this,
  28234. chart = me.getChart(),
  28235. xy = chart.getEventXY(e),
  28236. center = chart.getCenter();
  28237. return Math.atan2(xy[1] - center[1], xy[0] - center[0]);
  28238. },
  28239. onGestureStart: function(e) {
  28240. var me = this;
  28241. e.claimGesture();
  28242. me.lockEvents('drag');
  28243. me.angle = me.getAngle(e);
  28244. me.oldRotations = {};
  28245. me.getChart().suspendAnimation();
  28246. me.fireEvent('rotatestart', me, me.getRotation());
  28247. return false;
  28248. },
  28249. onGesture: function(e) {
  28250. var me = this,
  28251. angle = me.getAngle(e) - me.angle;
  28252. if (me.getLocks().drag === me) {
  28253. me.doRotateTo(angle, true);
  28254. return false;
  28255. }
  28256. },
  28257. /**
  28258. * @private
  28259. */
  28260. doRotateTo: function(angle, relative) {
  28261. var me = this,
  28262. chart = me.getChart(),
  28263. axes = chart.getAxes(),
  28264. seriesList = chart.getSeries(),
  28265. oldRotations = me.oldRotations,
  28266. rotation, oldRotation, axis, series, id, i, ln;
  28267. for (i = 0 , ln = axes.length; i < ln; i++) {
  28268. axis = axes[i];
  28269. id = axis.getId();
  28270. oldRotation = oldRotations[id] || (oldRotations[id] = axis.getRotation());
  28271. rotation = angle + (relative ? oldRotation : 0);
  28272. axis.setRotation(rotation);
  28273. }
  28274. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  28275. series = seriesList[i];
  28276. id = series.getId();
  28277. oldRotation = oldRotations[id] || (oldRotations[id] = series.getRotation());
  28278. // Unline axis's 'rotation', Polar series' 'rotation' is a public config and in degrees.
  28279. rotation = Ext.draw.Draw.degrees(angle + (relative ? oldRotation : 0));
  28280. series.setRotation(rotation);
  28281. }
  28282. me.setRotation(rotation);
  28283. me.fireEvent('rotate', me, me.getRotation());
  28284. me.sync();
  28285. },
  28286. /**
  28287. * Rotates a polar chart about its center point to the specified angle.
  28288. * @param {Number} angle The angle to rotate to.
  28289. * @param {Boolean} [relative=false] Whether the rotation is relative to the current angle
  28290. * or not.
  28291. * @param {Boolean} [animate=false] Whether to animate the rotation or not.
  28292. */
  28293. rotateTo: function(angle, relative, animate) {
  28294. var me = this,
  28295. chart = me.getChart();
  28296. if (!animate) {
  28297. chart.suspendAnimation();
  28298. }
  28299. me.doRotateTo(angle, relative, animate);
  28300. me.oldRotations = {};
  28301. if (!animate) {
  28302. chart.resumeAnimation();
  28303. }
  28304. },
  28305. onGestureEnd: function(e) {
  28306. var me = this;
  28307. if (me.getLocks().drag === me) {
  28308. me.onGesture(e);
  28309. me.unlockEvents('drag');
  28310. me.getChart().resumeAnimation();
  28311. me.fireEvent('rotateend', me, me.getRotation());
  28312. me.fireEvent('rotationEnd', me, me.getRotation());
  28313. return false;
  28314. }
  28315. }
  28316. });
  28317. /**
  28318. *
  28319. */
  28320. Ext.define('Ext.chart.navigator.ContainerBase', {
  28321. extend: 'Ext.Container',
  28322. updateNavigator: function(navigator, oldNavigator) {
  28323. if (oldNavigator) {
  28324. this.remove(oldNavigator, true);
  28325. }
  28326. this.add(navigator);
  28327. }
  28328. });
  28329. /**
  28330. *
  28331. */
  28332. Ext.define('Ext.chart.navigator.NavigatorBase', {
  28333. extend: 'Ext.chart.CartesianChart',
  28334. initialize: function() {
  28335. var me = this;
  28336. me.callParent();
  28337. me.setupEvents();
  28338. }
  28339. });
  28340. /**
  28341. * The overlay sprite used by the {@link Ext.chart.navigator.Navigator} component
  28342. * to render the selected visible range or a chart's horizontal axis.
  28343. */
  28344. Ext.define('Ext.chart.navigator.sprite.RangeMask', {
  28345. extend: 'Ext.draw.sprite.Sprite',
  28346. alias: 'sprite.rangemask',
  28347. inheritableStatics: {
  28348. def: {
  28349. processors: {
  28350. min: 'limited01',
  28351. max: 'limited01',
  28352. thumbOpacity: 'limited01'
  28353. },
  28354. defaults: {
  28355. min: 0,
  28356. max: 1,
  28357. lineWidth: 2,
  28358. miterLimit: 1,
  28359. strokeStyle: '#787878',
  28360. thumbOpacity: 1
  28361. }
  28362. }
  28363. },
  28364. getBBox: function(isWithoutTransform) {
  28365. var me = this,
  28366. attr = me.attr,
  28367. bbox = attr.bbox;
  28368. bbox.plain = {
  28369. x: 0,
  28370. y: 0,
  28371. width: 1,
  28372. height: 1
  28373. };
  28374. if (isWithoutTransform) {
  28375. return bbox.plain;
  28376. }
  28377. return bbox.transform || (bbox.transform = attr.matrix.transformBBox(bbox.plain));
  28378. },
  28379. renderThumb: function(surface, ctx, x, y) {
  28380. var me = this,
  28381. shapeSprite = me.shapeSprite,
  28382. textureSprite = me.textureSprite,
  28383. thumbOpacity = me.attr.thumbOpacity,
  28384. thumbAttributes = {
  28385. opacity: thumbOpacity,
  28386. translationX: x,
  28387. translationY: y
  28388. };
  28389. if (!shapeSprite) {
  28390. shapeSprite = me.shapeSprite = new Ext.draw.sprite.Rect({
  28391. x: -9.5,
  28392. y: -9.5,
  28393. width: 19,
  28394. height: 19,
  28395. radius: 4,
  28396. lineWidth: 1,
  28397. fillStyle: {
  28398. type: 'linear',
  28399. degrees: 90,
  28400. stops: [
  28401. {
  28402. offset: 0,
  28403. color: '#EEE'
  28404. },
  28405. {
  28406. offset: 1,
  28407. color: '#FFF'
  28408. }
  28409. ]
  28410. },
  28411. strokeStyle: '#999'
  28412. });
  28413. textureSprite = me.textureSprite = new Ext.draw.sprite.Path({
  28414. path: 'M -4, -5, -4, 5 M 0, -5, 0, 5 M 4, -5, 4, 5',
  28415. strokeStyle: {
  28416. type: 'linear',
  28417. degrees: 90,
  28418. stops: [
  28419. {
  28420. offset: 0,
  28421. color: '#CCC'
  28422. },
  28423. {
  28424. offset: 1,
  28425. color: '#BBB'
  28426. }
  28427. ]
  28428. },
  28429. lineWidth: 2
  28430. });
  28431. }
  28432. ctx.save();
  28433. shapeSprite.setAttributes(thumbAttributes);
  28434. shapeSprite.applyTransformations();
  28435. textureSprite.setAttributes(thumbAttributes);
  28436. textureSprite.applyTransformations();
  28437. shapeSprite.useAttributes(ctx);
  28438. shapeSprite.render(surface, ctx);
  28439. textureSprite.useAttributes(ctx);
  28440. textureSprite.render(surface, ctx);
  28441. ctx.restore();
  28442. },
  28443. render: function(surface, ctx) {
  28444. var me = this,
  28445. attr = me.attr,
  28446. matrix = attr.matrix.elements,
  28447. sx = matrix[0],
  28448. sy = matrix[3],
  28449. tx = matrix[4],
  28450. ty = matrix[5],
  28451. min = attr.min,
  28452. max = attr.max,
  28453. // s_min and s_max are range values in screen coordinates (scaled and translated)
  28454. s_min = min * sx + tx,
  28455. s_max = max * sx + tx,
  28456. s_y = Math.round(0.5 * sy + ty);
  28457. // thumb position in screen coordinates (mid-height)
  28458. ctx.beginPath();
  28459. // Rect that represents the whole range.
  28460. ctx.moveTo(tx, ty);
  28461. ctx.lineTo(sx + tx, ty);
  28462. ctx.lineTo(sx + tx, sy + ty);
  28463. ctx.lineTo(tx, sy + ty);
  28464. ctx.lineTo(tx, ty);
  28465. // Rect that represents the visible range.
  28466. ctx.moveTo(s_min, ty);
  28467. ctx.lineTo(s_min, sy + ty);
  28468. ctx.lineTo(s_max, sy + ty);
  28469. ctx.lineTo(s_max, ty);
  28470. ctx.lineTo(s_min, ty);
  28471. ctx.fillStroke(attr, true);
  28472. me.renderThumb(surface, ctx, Math.round(s_min), s_y);
  28473. me.renderThumb(surface, ctx, Math.round(s_max), s_y);
  28474. }
  28475. });
  28476. /**
  28477. * The Navigator component is used to visually set the visible range of the x-axis
  28478. * of a cartesian chart.
  28479. *
  28480. * This component is meant to be used with the Navigator Container
  28481. * via its {@link Ext.chart.navigator.Container#navigator} config.
  28482. *
  28483. * IMPORTANT: even though the Navigator component is a kind of chart, it should not be
  28484. * treated as such. Correct behavior is not guaranteed when using hidden/private configs.
  28485. */
  28486. Ext.define('Ext.chart.navigator.Navigator', {
  28487. extend: 'Ext.chart.navigator.NavigatorBase',
  28488. isNavigator: true,
  28489. requires: [
  28490. 'Ext.chart.navigator.sprite.RangeMask'
  28491. ],
  28492. config: {
  28493. /**
  28494. * @cfg {'bottom'/'top'} [docked='bottom']
  28495. */
  28496. docked: 'bottom',
  28497. /**
  28498. * @cfg {'series'/'chart'} [span='series']
  28499. * Whether the navigator should span the 'series' (default) or the whole 'chart'.
  28500. */
  28501. span: 'series',
  28502. insetPadding: 0,
  28503. innerPadding: 0,
  28504. /**
  28505. * @cfg {Ext.chart.navigator.Container} navigatorContainer
  28506. * 'parent' is reserved in Modern, 'container' is reserved in Classic,
  28507. * so we use 'navigatorContainer' as a config name.
  28508. * @private
  28509. */
  28510. navigatorContainer: null,
  28511. /**
  28512. * @cfg {String} axis (required)
  28513. * The ID of the {@link #chart chart's} axis to link to.
  28514. * The axis should be positioned to 'bottom' or 'top' in the chart.
  28515. */
  28516. axis: null,
  28517. /**
  28518. * @cfg {Number} [tolerance=20]
  28519. * The maximum horizontal delta between the pointer/finger and the center of a navigator
  28520. * thumb. Used for hit testing.
  28521. */
  28522. tolerance: 20,
  28523. /**
  28524. * @cfg {Number} [minimum=0.8]
  28525. * The start of the visible range, where the visible range is a [0, 1] interval.
  28526. */
  28527. minimum: 0.8,
  28528. /**
  28529. * @cfg {Number} [maximum=1]
  28530. * The end of the visible range, where the visible range is a [0, 1] interval.
  28531. */
  28532. maximum: 1,
  28533. /**
  28534. * @cfg {Number} [thumbGap=30]
  28535. * Minimum gap between navigator thumbs in pixels.
  28536. */
  28537. thumbGap: 30,
  28538. autoHideThumbs: true,
  28539. width: '100%',
  28540. /**
  28541. * @cfg {Number} [height=75]
  28542. * The height of the navigator component.
  28543. */
  28544. height: 75
  28545. },
  28546. /**
  28547. * @cfg flipXY
  28548. * @hide
  28549. */
  28550. /**
  28551. * @cfg series
  28552. * @hide
  28553. */
  28554. /**
  28555. * @cfg axes
  28556. * @hide
  28557. */
  28558. /**
  28559. * @cfg store
  28560. * @hide
  28561. */
  28562. /**
  28563. * @cfg legend
  28564. * @hide
  28565. */
  28566. /**
  28567. * @cfg interactions
  28568. * @hide
  28569. */
  28570. /**
  28571. * @cfg highlightItem
  28572. * @hide
  28573. */
  28574. /**
  28575. * @cfg theme
  28576. * @hide
  28577. */
  28578. /**
  28579. * @cfg innerPadding
  28580. * @hide
  28581. */
  28582. /**
  28583. * @cfg insetPadding
  28584. * @hide
  28585. */
  28586. dragType: null,
  28587. constructor: function(config) {
  28588. var me = this,
  28589. visibleRange, overlay;
  28590. config = config || {};
  28591. visibleRange = [
  28592. config.minimum || 0.8,
  28593. config.maximum || 1
  28594. ];
  28595. me.callParent([
  28596. config
  28597. ]);
  28598. overlay = me.overlaySurface;
  28599. overlay.element.setStyle({
  28600. zIndex: 100
  28601. });
  28602. me.rangeMask = overlay.add({
  28603. type: 'rangemask',
  28604. min: visibleRange[0],
  28605. max: visibleRange[1],
  28606. fillStyle: 'rgba(0, 0, 0, .25)'
  28607. });
  28608. me.onDragEnd();
  28609. // Set 'thumbOpacity' of the range mask sprite to 0, if needed,
  28610. // and apply animation modifier changes after that, so that the attribute is set
  28611. // instantly.
  28612. me.rangeMask.setAnimation({
  28613. duration: 500,
  28614. customDurations: {
  28615. min: 0,
  28616. max: 0,
  28617. translationX: 0,
  28618. translationY: 0,
  28619. scalingX: 0,
  28620. scalingY: 0,
  28621. scalingCenterX: 0,
  28622. scalingCenterY: 0,
  28623. fillStyle: 0,
  28624. strokeStyle: 0
  28625. }
  28626. });
  28627. me.setVisibleRange(visibleRange);
  28628. },
  28629. createSurface: function(id) {
  28630. var surface = this.callParent([
  28631. id
  28632. ]);
  28633. if (id === 'overlay') {
  28634. this.overlaySurface = surface;
  28635. }
  28636. return surface;
  28637. },
  28638. // Note: 'applyDock' and 'updateDock' won't ever be called in Classic.
  28639. // See Classic NavigatorBase.
  28640. applyAxis: function(axis) {
  28641. return this.getNavigatorContainer().getChart().getAxis(axis);
  28642. },
  28643. updateAxis: function(axis, oldAxis) {
  28644. var me = this,
  28645. eventName = 'visiblerangechange',
  28646. eventHandler = 'onAxisVisibleRangeChange';
  28647. if (oldAxis) {
  28648. oldAxis.un(eventName, eventHandler, me);
  28649. }
  28650. if (axis) {
  28651. axis.on(eventName, eventHandler, me);
  28652. }
  28653. me.axis = axis;
  28654. },
  28655. getAxis: function() {
  28656. // The superclass doesn't have the 'axis' config, but it has the same method,
  28657. // which we override here to act as a getter for the config. The user is not
  28658. // expected to use the original method in this subclass anyway.
  28659. return this.axis;
  28660. },
  28661. onAxisVisibleRangeChange: function(axis, visibleRange) {
  28662. this.setVisibleRange(visibleRange);
  28663. },
  28664. updateNavigatorContainer: function(navigatorContainer) {
  28665. var me = this,
  28666. oldChart = me.chart,
  28667. chart = me.chart = navigatorContainer && navigatorContainer.getChart(),
  28668. chartSeriesList = chart && chart.getSeries(),
  28669. // 'legendStore' already exists in the base class.
  28670. chartLegendStore = me.chartLegendStore,
  28671. navigatorSeriesList = [],
  28672. storeEventName = 'update',
  28673. // 'onLegendStoreUpdate' already exists in the base class.
  28674. storeEventHandler = 'onChartLegendStoreUpdate',
  28675. chartSeries, navigatorSeries, seriesConfig, i;
  28676. if (oldChart) {
  28677. oldChart.un('layout', 'afterBoundChartLayout', me);
  28678. oldChart.un('themechange', 'onChartThemeChange', me);
  28679. oldChart.un('storechange', 'onChartStoreChange', me);
  28680. }
  28681. chart.on('layout', 'afterBoundChartLayout', me);
  28682. for (i = 0; i < chartSeriesList.length; i++) {
  28683. chartSeries = chartSeriesList[i];
  28684. seriesConfig = me.getSeriesConfig(chartSeries);
  28685. navigatorSeries = Ext.create('series.' + seriesConfig.type, seriesConfig);
  28686. navigatorSeries.parentSeries = chartSeries;
  28687. chartSeries.navigatorSeries = navigatorSeries;
  28688. navigatorSeriesList.push(navigatorSeries);
  28689. }
  28690. if (chartLegendStore) {
  28691. chartLegendStore.un(storeEventName, storeEventHandler, me);
  28692. me.chartLegendStore = null;
  28693. }
  28694. if (chart) {
  28695. me.setStore(chart.getStore());
  28696. me.chartLegendStore = chartLegendStore = chart.getLegendStore();
  28697. if (chartLegendStore) {
  28698. chartLegendStore.on(storeEventName, storeEventHandler, me);
  28699. }
  28700. chart.on('themechange', 'onChartThemeChange', me);
  28701. chart.on('storechange', 'onChartStoreChange', me);
  28702. me.onChartThemeChange(chart, chart.getTheme());
  28703. }
  28704. me.setSeries(navigatorSeriesList);
  28705. },
  28706. onChartThemeChange: function(chart, theme) {
  28707. this.setTheme(theme);
  28708. },
  28709. onChartStoreChange: function(chart, store) {
  28710. this.setStore(store);
  28711. },
  28712. addCustomStyle: function(config, style, subStyle) {
  28713. var fillStyle, strokeStyle;
  28714. style = style || {};
  28715. subStyle = subStyle || {};
  28716. config.style = config.style || {};
  28717. config.subStyle = config.subStyle || {};
  28718. fillStyle = style && (style.fillStyle || style.fill);
  28719. strokeStyle = style && (style.strokeStyle || style.stroke);
  28720. if (fillStyle) {
  28721. config.style.fillStyle = fillStyle;
  28722. }
  28723. if (strokeStyle) {
  28724. config.style.strokeStyle = strokeStyle;
  28725. }
  28726. fillStyle = subStyle && (subStyle.fillStyle || subStyle.fill);
  28727. strokeStyle = subStyle && (subStyle.strokeStyle || subStyle.stroke);
  28728. if (fillStyle) {
  28729. config.subStyle.fillStyle = fillStyle;
  28730. }
  28731. if (strokeStyle) {
  28732. config.subStyle.strokeStyle = strokeStyle;
  28733. }
  28734. return config;
  28735. },
  28736. getSeriesConfig: function(chartSeries) {
  28737. var me = this,
  28738. style = chartSeries.getStyle(),
  28739. config;
  28740. if (chartSeries.isLine) {
  28741. config = me.addCustomStyle({
  28742. type: 'line',
  28743. fill: true,
  28744. xField: chartSeries.getXField(),
  28745. yField: chartSeries.getYField(),
  28746. smooth: chartSeries.getSmooth()
  28747. }, style);
  28748. } else if (chartSeries.isCandleStick) {
  28749. config = me.addCustomStyle({
  28750. type: 'line',
  28751. fill: true,
  28752. xField: chartSeries.getXField(),
  28753. yField: chartSeries.getCloseField()
  28754. }, style.raiseStyle);
  28755. } else if (chartSeries.isArea || chartSeries.isBar) {
  28756. config = me.addCustomStyle({
  28757. type: 'area',
  28758. xField: chartSeries.getXField(),
  28759. yField: chartSeries.getYField()
  28760. }, style, chartSeries.getSubStyle());
  28761. } else {
  28762. Ext.raise("Navigator only works with 'line', 'bar', 'candlestick' and 'area' series.");
  28763. }
  28764. config.style.fillOpacity = 0.2;
  28765. return config;
  28766. },
  28767. onChartLegendStoreUpdate: function(store, record) {
  28768. var me = this,
  28769. chart = me.chart,
  28770. series;
  28771. if (chart && record) {
  28772. series = chart.getSeries().map[record.get('series')];
  28773. if (series && series.navigatorSeries) {
  28774. series.navigatorSeries.setHiddenByIndex(record.get('index'), record.get('disabled'));
  28775. me.redraw();
  28776. }
  28777. }
  28778. },
  28779. setupEvents: function() {
  28780. // Called from NavigatorBase classes.
  28781. var me = this,
  28782. overlayEl = me.overlaySurface.element;
  28783. overlayEl.on({
  28784. scope: me,
  28785. drag: 'onDrag',
  28786. dragstart: 'onDragStart',
  28787. dragend: 'onDragEnd',
  28788. dragcancel: 'onDragEnd',
  28789. mousemove: 'onMouseMove'
  28790. });
  28791. },
  28792. onMouseMove: function(e) {
  28793. var me = this,
  28794. overlayEl = me.overlaySurface.element,
  28795. style = overlayEl.dom.style,
  28796. dragType = me.getDragType(e.pageX - overlayEl.getXY()[0]);
  28797. switch (dragType) {
  28798. case 'min':
  28799. case 'max':
  28800. style.cursor = 'ew-resize';
  28801. break;
  28802. case 'pan':
  28803. style.cursor = 'move';
  28804. break;
  28805. default:
  28806. style.cursor = 'default';
  28807. }
  28808. },
  28809. getDragType: function(x) {
  28810. var me = this,
  28811. t = me.getTolerance(),
  28812. width = me.overlaySurface.element.getSize().width,
  28813. rangeMask = me.rangeMask,
  28814. min = width * rangeMask.attr.min,
  28815. max = width * rangeMask.attr.max,
  28816. dragType;
  28817. if (x > min + t && x < max - t) {
  28818. dragType = 'pan';
  28819. } else if (x <= min + t && x > min - t) {
  28820. dragType = 'min';
  28821. } else if (x >= max - t && x < max + t) {
  28822. dragType = 'max';
  28823. }
  28824. return dragType;
  28825. },
  28826. onDragStart: function(e) {
  28827. var me = this,
  28828. x, dragType;
  28829. // Limit drags to single touch.
  28830. if (me.dragType || e && e.touches && e.touches.length > 1) {
  28831. return;
  28832. }
  28833. x = e.touches[0].pageX - me.overlaySurface.element.getXY()[0];
  28834. dragType = me.getDragType(x);
  28835. me.rangeMask.attr.thumbOpacity = 1;
  28836. if (dragType) {
  28837. me.dragType = dragType;
  28838. me.touchId = e.touches[0].identifier;
  28839. me.dragX = x;
  28840. }
  28841. },
  28842. onDrag: function(e) {
  28843. if (e.touch.identifier !== this.touchId) {
  28844. return;
  28845. }
  28846. // eslint-disable-next-line vars-on-top
  28847. var me = this,
  28848. overlayEl = me.overlaySurface.element,
  28849. width = overlayEl.getSize().width,
  28850. x = e.touches[0].pageX - overlayEl.getXY()[0],
  28851. thumbGap = me.getThumbGap() / width,
  28852. rangeMask = me.rangeMask,
  28853. min = rangeMask.attr.min,
  28854. max = rangeMask.attr.max,
  28855. delta = max - min,
  28856. dragType = me.dragType,
  28857. drag = me.dragX,
  28858. dx = (x - drag) / width;
  28859. if (dragType === 'pan') {
  28860. min += dx;
  28861. max += dx;
  28862. if (min < 0) {
  28863. min = 0;
  28864. max = delta;
  28865. }
  28866. if (max > 1) {
  28867. max = 1;
  28868. min = max - delta;
  28869. }
  28870. } else if (dragType === 'min') {
  28871. min += dx;
  28872. if (min < 0) {
  28873. min = 0;
  28874. }
  28875. if (min > max - thumbGap) {
  28876. min = max - thumbGap;
  28877. }
  28878. } else if (dragType === 'max') {
  28879. max += dx;
  28880. if (max > 1) {
  28881. max = 1;
  28882. }
  28883. if (max < min + thumbGap) {
  28884. max = min + thumbGap;
  28885. }
  28886. } else {
  28887. return;
  28888. }
  28889. me.dragX = x;
  28890. me.setVisibleRange([
  28891. min,
  28892. max
  28893. ]);
  28894. },
  28895. onDragEnd: function() {
  28896. var me = this,
  28897. autoHideThumbs = me.getAutoHideThumbs();
  28898. me.dragType = null;
  28899. if (autoHideThumbs) {
  28900. me.rangeMask.setAttributes({
  28901. thumbOpacity: 0
  28902. });
  28903. }
  28904. },
  28905. updateMinimum: function(mininum) {
  28906. if (!this.isConfiguring) {
  28907. this.setVisibleRange([
  28908. mininum,
  28909. this.getMaximum()
  28910. ]);
  28911. }
  28912. },
  28913. updateMaximum: function(maximum) {
  28914. if (!this.isConfiguring) {
  28915. this.setVisibleRange([
  28916. this.getMinimum(),
  28917. maximum
  28918. ]);
  28919. }
  28920. },
  28921. getMinimum: function() {
  28922. return this.rangeMask.attr.min;
  28923. },
  28924. getMaximum: function() {
  28925. return this.rangeMask.attr.max;
  28926. },
  28927. setVisibleRange: function(visibleRange) {
  28928. var me = this,
  28929. chart = me.chart;
  28930. me.axis.setVisibleRange(visibleRange);
  28931. me.rangeMask.setAttributes({
  28932. min: visibleRange[0],
  28933. max: visibleRange[1]
  28934. });
  28935. me.getSurface('overlay').renderFrame();
  28936. chart.suspendAnimation();
  28937. chart.redraw();
  28938. chart.resumeAnimation();
  28939. },
  28940. afterBoundChartLayout: function() {
  28941. var me = this,
  28942. spanSeries = me.getSpan() === 'series',
  28943. mainRect = me.chart.getMainRect(),
  28944. size = me.element.getSize();
  28945. if (mainRect && spanSeries) {
  28946. me.setInsetPadding({
  28947. left: mainRect[0],
  28948. right: size.width - mainRect[2] - mainRect[0],
  28949. top: 0,
  28950. bottom: 0
  28951. });
  28952. me.performLayout();
  28953. }
  28954. },
  28955. afterChartLayout: function() {
  28956. var me = this,
  28957. size = me.overlaySurface.element.getSize();
  28958. me.rangeMask.setAttributes({
  28959. scalingCenterX: 0,
  28960. scalingCenterY: 0,
  28961. scalingX: size.width,
  28962. scalingY: size.height
  28963. });
  28964. },
  28965. doDestroy: function() {
  28966. var chart = this.chart;
  28967. if (chart && !chart.destroyed) {
  28968. chart.un('layout', 'afterBoundChartLayout', this);
  28969. }
  28970. this.callParent();
  28971. }
  28972. });
  28973. /**
  28974. * The Navigator Container is a component used to lay out the chart and its
  28975. * {@link Ext.chart.navigator.Navigator navigator}, where the navigator is docked
  28976. * to the top/bottom, and the chart fills the rest of the container's space.
  28977. *
  28978. * For example:
  28979. *
  28980. * @example
  28981. * Ext.create({
  28982. * xtype: 'chartnavigator',
  28983. * renderTo: Ext.getBody(),
  28984. * width: 600,
  28985. * height: 400,
  28986. *
  28987. * chart: {
  28988. * xtype: 'cartesian',
  28989. *
  28990. * store: {
  28991. * data: (function () {
  28992. * var data = [];
  28993. * for (var i = 0; i < 360; i++) {
  28994. * data.push({
  28995. * x: i,
  28996. * y: Math.sin(i / 45 * Math.PI)
  28997. * });
  28998. * }
  28999. * return data;
  29000. * })()
  29001. * },
  29002. * axes: [
  29003. * {
  29004. * id: 'navigable-axis',
  29005. *
  29006. * type: 'numeric',
  29007. * position: 'bottom'
  29008. * },
  29009. * {
  29010. * type: 'numeric',
  29011. * position: 'left'
  29012. * }
  29013. * ],
  29014. * series: {
  29015. * type: 'line',
  29016. * xField: 'x',
  29017. * yField: 'y'
  29018. * }
  29019. * },
  29020. *
  29021. * navigator: {
  29022. * axis: 'navigable-axis'
  29023. * }
  29024. * });
  29025. *
  29026. */
  29027. Ext.define('Ext.chart.navigator.Container', {
  29028. // We are interested in the docking functionality that's available in
  29029. // the Container in Modern and in the Panel in Classic.
  29030. extend: 'Ext.chart.navigator.ContainerBase',
  29031. requires: [
  29032. 'Ext.chart.CartesianChart',
  29033. 'Ext.chart.navigator.Navigator'
  29034. ],
  29035. xtype: 'chartnavigator',
  29036. config: {
  29037. /**
  29038. * @cfg {Ext.chart.CartesianChart} chart
  29039. * The chart to make navigable.
  29040. */
  29041. chart: null,
  29042. /**
  29043. * @cfg {Ext.chart.navigator.Navigator} navigator
  29044. */
  29045. navigator: {}
  29046. },
  29047. layout: 'fit',
  29048. applyChart: function(chart, oldChart) {
  29049. if (oldChart) {
  29050. oldChart.destroy();
  29051. }
  29052. if (chart) {
  29053. if (chart.isCartesian) {
  29054. Ext.raise('Only cartesian charts are supported.');
  29055. }
  29056. if (!chart.isChart) {
  29057. chart.$initParent = this;
  29058. chart = new Ext.chart.CartesianChart(chart);
  29059. delete chart.$initParent;
  29060. }
  29061. }
  29062. return chart;
  29063. },
  29064. legendStore: null,
  29065. surfaceRects: null,
  29066. updateChart: function(chart, oldChart) {
  29067. var me = this;
  29068. if (chart) {
  29069. me.legendStore = chart.getLegendStore();
  29070. if (!me.items && me.initItems) {
  29071. me.initItems();
  29072. }
  29073. me.add(chart);
  29074. }
  29075. },
  29076. applyNavigator: function(navigator, oldNavigator) {
  29077. var instance;
  29078. if (oldNavigator) {
  29079. oldNavigator.destroy();
  29080. }
  29081. if (navigator) {
  29082. navigator.navigatorContainer = navigator.parent = this;
  29083. instance = new Ext.chart.navigator.Navigator(navigator);
  29084. }
  29085. return instance;
  29086. },
  29087. preview: function() {
  29088. this.getNavigator().preview(this.getImage());
  29089. },
  29090. download: function(config) {
  29091. config = config || {};
  29092. config.data = this.getImage().data;
  29093. this.getNavigator().download(config);
  29094. },
  29095. setVisibleRange: function(visibleRange) {
  29096. this.getNavigator().setVisibleRange(visibleRange);
  29097. },
  29098. getImage: function(format) {
  29099. var me = this,
  29100. chart = me.getChart(),
  29101. navigator = me.getNavigator(),
  29102. docked = navigator.getDocked(),
  29103. chartImageSize = chart.bodyElement.getSize(),
  29104. navigatorImageSize = navigator.bodyElement.getSize(),
  29105. chartSurfaces = chart.getSurfaces(true),
  29106. navigatorSurfaces = navigator.getSurfaces(true),
  29107. size = {
  29108. width: chartImageSize.width,
  29109. height: chartImageSize.height + navigatorImageSize.height
  29110. },
  29111. image, imageElement, surfaces, surface;
  29112. if (docked === 'top') {
  29113. me.shiftSurfaces(chartSurfaces, 0, navigatorImageSize.height);
  29114. } else {
  29115. me.shiftSurfaces(navigatorSurfaces, 0, chartImageSize.height);
  29116. }
  29117. surfaces = chartSurfaces.concat(navigatorSurfaces);
  29118. surface = surfaces[0];
  29119. if ((Ext.isIE || Ext.isEdge) && surface.isSVG) {
  29120. // SVG data URLs don't work in IE/Edge as a source for an 'img' element,
  29121. // so we need to render SVG the usual way.
  29122. image = {
  29123. data: surface.toSVG(size, surfaces),
  29124. type: 'svg-markup'
  29125. };
  29126. } else {
  29127. image = surface.flatten(size, surfaces);
  29128. if (format === 'image') {
  29129. imageElement = new Image();
  29130. imageElement.src = image.data;
  29131. image.data = imageElement;
  29132. return image;
  29133. }
  29134. if (format === 'stream') {
  29135. image.data = image.data.replace(/^data:image\/[^;]+/, 'data:application/octet-stream');
  29136. return image;
  29137. }
  29138. }
  29139. me.unshiftSurfaces(surfaces);
  29140. return image;
  29141. },
  29142. shiftSurfaces: function(surfaces, x, y) {
  29143. var ln = surfaces.length,
  29144. i = 0,
  29145. surface;
  29146. this.surfaceRects = {};
  29147. for (; i < ln; i++) {
  29148. surface = surfaces[i];
  29149. this.shiftSurface(surface, x, y);
  29150. }
  29151. },
  29152. shiftSurface: function(surface, x, y) {
  29153. var rect = surface.getRect();
  29154. this.surfaceRects[surface.getId()] = rect.slice();
  29155. rect[0] += x;
  29156. rect[1] += y;
  29157. },
  29158. unshiftSurfaces: function(surfaces) {
  29159. var rects = this.surfaceRects,
  29160. ln = surfaces.length,
  29161. i = 0,
  29162. surface, rect, oldRect;
  29163. if (rects) {
  29164. for (; i < ln; i++) {
  29165. surface = surfaces[i];
  29166. rect = surface.getRect();
  29167. oldRect = rects[surface.getId()];
  29168. if (oldRect) {
  29169. rect[0] = oldRect[0];
  29170. rect[1] = oldRect[1];
  29171. }
  29172. }
  29173. }
  29174. this.surfaceRects = null;
  29175. }
  29176. });
  29177. /**
  29178. * A chart {@link Ext.AbstractPlugin plugin} that adds ability to listen to chart series
  29179. * items events. Item event listeners are passed two parameters: the target item and the
  29180. * event itself. The item object has the following properties:
  29181. *
  29182. * * **category** - the category the item falls under: 'items' or 'markers'
  29183. * * **field** - the store field used by this series item
  29184. * * **index** - the index of the series item
  29185. * * **record** - the store record associated with this series item
  29186. * * **series** - the series the item belongs to
  29187. * * **sprite** - the sprite used to represents this series item
  29188. *
  29189. * For example:
  29190. *
  29191. * Ext.create('Ext.chart.CartesianChart', {
  29192. * plugins: {
  29193. * chartitemevents: {
  29194. * moveEvents: true
  29195. * }
  29196. * },
  29197. * store: {
  29198. * fields: ['pet', 'households', 'total'],
  29199. * data: [
  29200. * {pet: 'Cats', households: 38, total: 93},
  29201. * {pet: 'Dogs', households: 45, total: 79},
  29202. * {pet: 'Fish', households: 13, total: 171}
  29203. * ]
  29204. * },
  29205. * axes: [{
  29206. * type: 'numeric',
  29207. * position: 'left'
  29208. * }, {
  29209. * type: 'category',
  29210. * position: 'bottom'
  29211. * }],
  29212. * series: [{
  29213. * type: 'bar',
  29214. * xField: 'pet',
  29215. * yField: 'households',
  29216. * listeners: {
  29217. * itemmousemove: function (series, item, event) {
  29218. * console.log('itemmousemove', item.category, item.field);
  29219. * }
  29220. * }
  29221. * }, {
  29222. * type: 'line',
  29223. * xField: 'pet',
  29224. * yField: 'total',
  29225. * marker: true
  29226. * }],
  29227. * listeners: { // Listen to itemclick events on all series.
  29228. * itemclick: function (chart, item, event) {
  29229. * console.log('itemclick', item.category, item.field);
  29230. * }
  29231. * }
  29232. * });
  29233. *
  29234. */
  29235. Ext.define('Ext.chart.plugin.ItemEvents', {
  29236. extend: 'Ext.plugin.Abstract',
  29237. alias: 'plugin.chartitemevents',
  29238. /**
  29239. * @cfg {Boolean} [moveEvents=false]
  29240. * If `itemmousemove`, `itemmouseover` or `itemmouseout` event listeners are attached
  29241. * to the chart, the plugin will detect those and will hit test series items on
  29242. * every move. However, if the above item events are attached on the series level
  29243. * only, this config has to be set to true, as the plugin won't perform a similar
  29244. * detection on every series.
  29245. */
  29246. moveEvents: false,
  29247. mouseMoveEvents: {
  29248. mousemove: true,
  29249. mouseover: true,
  29250. mouseout: true
  29251. },
  29252. itemMouseMoveEvents: {
  29253. itemmousemove: true,
  29254. itemmouseover: true,
  29255. itemmouseout: true
  29256. },
  29257. init: function(chart) {
  29258. var handleEvent = 'handleEvent';
  29259. this.chart = chart;
  29260. chart.addElementListener({
  29261. click: handleEvent,
  29262. tap: handleEvent,
  29263. dblclick: handleEvent,
  29264. mousedown: handleEvent,
  29265. mousemove: handleEvent,
  29266. mouseup: handleEvent,
  29267. mouseover: handleEvent,
  29268. mouseout: handleEvent,
  29269. // run our handlers before user code
  29270. priority: 1001,
  29271. scope: this
  29272. });
  29273. },
  29274. hasItemMouseMoveListeners: function() {
  29275. var listeners = this.chart.hasListeners,
  29276. name;
  29277. for (name in this.itemMouseMoveEvents) {
  29278. if (name in listeners) {
  29279. return true;
  29280. }
  29281. }
  29282. return false;
  29283. },
  29284. handleEvent: function(e) {
  29285. var me = this,
  29286. chart = me.chart,
  29287. isMouseMoveEvent = e.type in me.mouseMoveEvents,
  29288. lastItem = me.lastItem,
  29289. chartXY, item;
  29290. if (isMouseMoveEvent && !me.hasItemMouseMoveListeners() && !me.moveEvents) {
  29291. return;
  29292. }
  29293. chartXY = chart.getEventXY(e);
  29294. item = chart.getItemForPoint(chartXY[0], chartXY[1]);
  29295. if (isMouseMoveEvent && !Ext.Object.equals(item, lastItem)) {
  29296. if (lastItem) {
  29297. chart.fireEvent('itemmouseout', chart, lastItem, e);
  29298. lastItem.series.fireEvent('itemmouseout', lastItem.series, lastItem, e);
  29299. }
  29300. if (item) {
  29301. chart.fireEvent('itemmouseover', chart, item, e);
  29302. item.series.fireEvent('itemmouseover', item.series, item, e);
  29303. }
  29304. }
  29305. if (item) {
  29306. chart.fireEvent('item' + e.type, chart, item, e);
  29307. item.series.fireEvent('item' + e.type, item.series, item, e);
  29308. }
  29309. me.lastItem = item;
  29310. }
  29311. });
  29312. /**
  29313. * @abstract
  29314. * @class Ext.chart.series.Cartesian
  29315. * @extends Ext.chart.series.Series
  29316. *
  29317. * Common base class for series implementations that plot values using cartesian coordinates.
  29318. *
  29319. * @constructor
  29320. */
  29321. Ext.define('Ext.chart.series.Cartesian', {
  29322. extend: 'Ext.chart.series.Series',
  29323. config: {
  29324. /**
  29325. * @cfg {String} xField
  29326. * The field used to access the x axis value from the items from the data source.
  29327. */
  29328. xField: null,
  29329. /**
  29330. * @cfg {String|String[]} yField
  29331. * The field(s) used to access the y-axis value(s) of the items from the data source.
  29332. */
  29333. yField: null,
  29334. /**
  29335. * @cfg {Ext.chart.axis.Axis|Number|String}
  29336. * xAxis The chart axis the series is bound to in the 'X' direction.
  29337. * Normally, this would be set automatically by the series.
  29338. * For charts with multiple x-axes, this defines which x-axis is used by the series.
  29339. * It refers to either axis' ID or the (zero-based) index of the axis
  29340. * in the chart's {@link Ext.chart.AbstractChart#axes axes} config.
  29341. */
  29342. xAxis: null,
  29343. /**
  29344. * @cfg {Ext.chart.axis.Axis|Number|String}
  29345. * yAxis The chart axis the series is bound to in the 'Y' direction.
  29346. * Normally, this would be set automatically by the series.
  29347. * For charts with multiple y-axes, this defines which y-axis is used by the series.
  29348. * It refers to either axis' ID or the (zero-based) index of the axis
  29349. * in the chart's {@link Ext.chart.AbstractChart#axes axes} config.
  29350. */
  29351. yAxis: null
  29352. },
  29353. directions: [
  29354. 'X',
  29355. 'Y'
  29356. ],
  29357. /**
  29358. * @private
  29359. *
  29360. * Tells which store record fields should be used for a specific axis direction. E.g. for
  29361. *
  29362. * fieldCategory<direction>: ['<fieldConfig1>', '<fieldConfig2>', ...]
  29363. *
  29364. * the field names from the following configs will be used:
  29365. *
  29366. * series.<fieldConfig1>Field, series.<fieldConfig2>Field, ...
  29367. *
  29368. * See {@link Ext.chart.series.StackedCartesian#getFields}.
  29369. *
  29370. */
  29371. fieldCategoryX: [
  29372. 'X'
  29373. ],
  29374. fieldCategoryY: [
  29375. 'Y'
  29376. ],
  29377. applyXAxis: function(newAxis, oldAxis) {
  29378. return this.getChart().getAxis(newAxis) || oldAxis;
  29379. },
  29380. applyYAxis: function(newAxis, oldAxis) {
  29381. return this.getChart().getAxis(newAxis) || oldAxis;
  29382. },
  29383. updateXAxis: function(axis) {
  29384. axis.processData(this);
  29385. },
  29386. updateYAxis: function(axis) {
  29387. axis.processData(this);
  29388. },
  29389. coordinateX: function() {
  29390. return this.coordinate('X', 0, 2);
  29391. },
  29392. coordinateY: function() {
  29393. return this.coordinate('Y', 1, 2);
  29394. },
  29395. getItemForPoint: function(x, y) {
  29396. var me = this,
  29397. sprite = me.getSprites()[0],
  29398. store = me.getStore(),
  29399. point;
  29400. if (sprite && !me.getHidden()) {
  29401. point = sprite.getNearestDataPoint(x, y);
  29402. }
  29403. return point ? {
  29404. series: me,
  29405. sprite: sprite,
  29406. category: me.getItemInstancing() ? 'items' : 'markers',
  29407. index: point.index,
  29408. record: store.getData().items[point.index],
  29409. field: me.getYField(),
  29410. distance: point.distance
  29411. } : null;
  29412. },
  29413. createSprite: function() {
  29414. var me = this,
  29415. sprite = me.callParent(),
  29416. chart = me.getChart(),
  29417. xAxis = me.getXAxis();
  29418. sprite.setAttributes({
  29419. flipXY: chart.getFlipXY(),
  29420. xAxis: xAxis
  29421. });
  29422. if (sprite.setAggregator && xAxis && xAxis.getAggregator) {
  29423. if (xAxis.getAggregator) {
  29424. sprite.setAggregator({
  29425. strategy: xAxis.getAggregator()
  29426. });
  29427. } else {
  29428. sprite.setAggregator({});
  29429. }
  29430. }
  29431. return sprite;
  29432. },
  29433. getSprites: function() {
  29434. var me = this,
  29435. chart = this.getChart(),
  29436. sprites = me.sprites;
  29437. if (!chart) {
  29438. return Ext.emptyArray;
  29439. }
  29440. if (!sprites.length) {
  29441. me.createSprite();
  29442. }
  29443. return sprites;
  29444. },
  29445. getXRange: function() {
  29446. return [
  29447. this.dataRange[0],
  29448. this.dataRange[2]
  29449. ];
  29450. },
  29451. getYRange: function() {
  29452. return [
  29453. this.dataRange[1],
  29454. this.dataRange[3]
  29455. ];
  29456. }
  29457. });
  29458. /**
  29459. * @abstract
  29460. * @extends Ext.chart.series.Cartesian
  29461. * Abstract class for all the stacked cartesian series including area series
  29462. * and bar series.
  29463. */
  29464. Ext.define('Ext.chart.series.StackedCartesian', {
  29465. extend: 'Ext.chart.series.Cartesian',
  29466. config: {
  29467. /**
  29468. * @cfg {Boolean} [stacked=true]
  29469. * `true` to display the series in its stacked configuration.
  29470. */
  29471. stacked: true,
  29472. /**
  29473. * @cfg {Boolean} [splitStacks=true]
  29474. * `true` to stack negative/positive values in respective y-axis directions.
  29475. */
  29476. splitStacks: true,
  29477. /**
  29478. * @cfg {Boolean} [fullStack=false]
  29479. * If `true`, the height of a stacked bar is always the full height of the chart,
  29480. * with individual components viewed as shares of the whole determined by the
  29481. * {@link #fullStackTotal} config.
  29482. */
  29483. fullStack: false,
  29484. /**
  29485. * @cfg {Boolean} [fullStackTotal=100]
  29486. * If the {@link #fullStack} config is set to `true`, this will determine
  29487. * the absolute total value of each stack.
  29488. */
  29489. fullStackTotal: 100,
  29490. /**
  29491. * @cfg {Array} hidden
  29492. */
  29493. hidden: []
  29494. },
  29495. /**
  29496. * @private
  29497. * @property
  29498. * If `true`, each subsequent sprite has a lower zIndex so that the stroke of previous
  29499. * sprite in the stack is not covered by the next sprite (which makes the very top
  29500. * segment look odd in flat bar and area series, especially when wide strokes are used).
  29501. */
  29502. reversedSpriteZOrder: true,
  29503. spriteAnimationCount: 0,
  29504. themeColorCount: function() {
  29505. var me = this,
  29506. yField = me.getYField();
  29507. return Ext.isArray(yField) ? yField.length : 1;
  29508. },
  29509. updateStacked: function() {
  29510. this.processData();
  29511. },
  29512. updateSplitStacks: function() {
  29513. this.processData();
  29514. },
  29515. coordinateY: function() {
  29516. return this.coordinateStacked('Y', 1, 2);
  29517. },
  29518. coordinateStacked: function(direction, directionOffset, directionCount) {
  29519. var me = this,
  29520. store = me.getStore(),
  29521. items = store.getData().items,
  29522. itemCount = items.length,
  29523. axis = me['get' + direction + 'Axis'](),
  29524. hidden = me.getHidden(),
  29525. splitStacks = me.getSplitStacks(),
  29526. fullStack = me.getFullStack(),
  29527. fullStackTotal = me.getFullStackTotal(),
  29528. range = [
  29529. 0,
  29530. 0
  29531. ],
  29532. directions = me['fieldCategory' + direction],
  29533. dataStart = [],
  29534. posDataStart = [],
  29535. negDataStart = [],
  29536. dataEnd,
  29537. stacked = me.getStacked(),
  29538. sprites = me.getSprites(),
  29539. coordinatedData = [],
  29540. i, j, k, fields, fieldCount, posTotals, negTotals, fieldCategoriesItem, data, attr;
  29541. if (!sprites.length) {
  29542. return;
  29543. }
  29544. for (i = 0; i < directions.length; i++) {
  29545. fieldCategoriesItem = directions[i];
  29546. fields = me.getFields([
  29547. fieldCategoriesItem
  29548. ]);
  29549. fieldCount = fields.length;
  29550. for (j = 0; j < itemCount; j++) {
  29551. dataStart[j] = 0;
  29552. posDataStart[j] = 0;
  29553. negDataStart[j] = 0;
  29554. }
  29555. for (j = 0; j < fieldCount; j++) {
  29556. if (!hidden[j]) {
  29557. coordinatedData[j] = me.coordinateData(items, fields[j], axis);
  29558. }
  29559. }
  29560. if (stacked && fullStack) {
  29561. posTotals = [];
  29562. if (splitStacks) {
  29563. negTotals = [];
  29564. }
  29565. for (j = 0; j < itemCount; j++) {
  29566. posTotals[j] = 0;
  29567. if (splitStacks) {
  29568. negTotals[j] = 0;
  29569. }
  29570. for (k = 0; k < fieldCount; k++) {
  29571. data = coordinatedData[k];
  29572. if (!data) {
  29573. // If the field is hidden there's no coordinated data for it.
  29574. continue;
  29575. }
  29576. data = data[j];
  29577. if (data >= 0 || !splitStacks) {
  29578. posTotals[j] += data;
  29579. } else if (data < 0) {
  29580. negTotals[j] += data;
  29581. }
  29582. }
  29583. }
  29584. }
  29585. // else not a valid number
  29586. for (j = 0; j < fieldCount; j++) {
  29587. attr = {};
  29588. if (hidden[j]) {
  29589. attr['dataStart' + fieldCategoriesItem] = dataStart;
  29590. attr['data' + fieldCategoriesItem] = dataStart;
  29591. sprites[j].setAttributes(attr);
  29592. continue;
  29593. }
  29594. data = coordinatedData[j];
  29595. if (stacked) {
  29596. dataEnd = [];
  29597. for (k = 0; k < itemCount; k++) {
  29598. if (!data[k]) {
  29599. data[k] = 0;
  29600. }
  29601. if (data[k] >= 0 || !splitStacks) {
  29602. if (fullStack && posTotals[k]) {
  29603. data[k] *= fullStackTotal / posTotals[k];
  29604. }
  29605. dataStart[k] = posDataStart[k];
  29606. posDataStart[k] += data[k];
  29607. dataEnd[k] = posDataStart[k];
  29608. } else {
  29609. if (fullStack && negTotals[k]) {
  29610. data[k] *= fullStackTotal / negTotals[k];
  29611. }
  29612. dataStart[k] = negDataStart[k];
  29613. negDataStart[k] += data[k];
  29614. dataEnd[k] = negDataStart[k];
  29615. }
  29616. }
  29617. attr['dataStart' + fieldCategoriesItem] = dataStart;
  29618. attr['data' + fieldCategoriesItem] = dataEnd;
  29619. Ext.chart.Util.expandRange(range, dataStart);
  29620. Ext.chart.Util.expandRange(range, dataEnd);
  29621. } else {
  29622. attr['dataStart' + fieldCategoriesItem] = dataStart;
  29623. attr['data' + fieldCategoriesItem] = data;
  29624. Ext.chart.Util.expandRange(range, data);
  29625. }
  29626. sprites[j].setAttributes(attr);
  29627. }
  29628. }
  29629. range = Ext.chart.Util.validateRange(range, me.defaultRange);
  29630. me.dataRange[directionOffset] = range[0];
  29631. me.dataRange[directionOffset + directionCount] = range[1];
  29632. attr = {};
  29633. attr['dataMin' + direction] = range[0];
  29634. attr['dataMax' + direction] = range[1];
  29635. for (i = 0; i < sprites.length; i++) {
  29636. sprites[i].setAttributes(attr);
  29637. }
  29638. },
  29639. getFields: function(fieldCategory) {
  29640. var me = this,
  29641. fields = [],
  29642. ln = fieldCategory.length,
  29643. i, fieldsItem;
  29644. for (i = 0; i < ln; i++) {
  29645. fieldsItem = me['get' + fieldCategory[i] + 'Field']();
  29646. if (Ext.isArray(fieldsItem)) {
  29647. fields.push.apply(fields, fieldsItem);
  29648. } else {
  29649. fields.push(fieldsItem);
  29650. }
  29651. }
  29652. return fields;
  29653. },
  29654. updateLabelOverflowPadding: function(labelOverflowPadding) {
  29655. var me = this,
  29656. label;
  29657. if (!me.isConfiguring) {
  29658. label = me.getLabel();
  29659. if (label) {
  29660. label.setAttributes({
  29661. labelOverflowPadding: labelOverflowPadding
  29662. });
  29663. }
  29664. }
  29665. },
  29666. updateLabelData: function() {
  29667. var me = this,
  29668. label = me.getLabel();
  29669. if (label) {
  29670. label.setAttributes({
  29671. labelOverflowPadding: me.getLabelOverflowPadding()
  29672. });
  29673. }
  29674. me.callParent();
  29675. },
  29676. getSprites: function() {
  29677. var me = this,
  29678. chart = me.getChart(),
  29679. fields = me.getFields(me.fieldCategoryY),
  29680. itemInstancing = me.getItemInstancing(),
  29681. sprites = me.sprites,
  29682. hidden = me.getHidden(),
  29683. spritesCreated = false,
  29684. fieldCount = fields.length,
  29685. i, sprite;
  29686. if (!chart) {
  29687. return [];
  29688. }
  29689. // Create one Ext.chart.series.sprite.StackedCartesian sprite per field.
  29690. for (i = 0; i < fieldCount; i++) {
  29691. sprite = sprites[i];
  29692. if (!sprite) {
  29693. sprite = me.createSprite();
  29694. sprite.setAttributes({
  29695. zIndex: (me.reversedSpriteZOrder ? -1 : 1) * i
  29696. });
  29697. sprite.setField(fields[i]);
  29698. spritesCreated = true;
  29699. hidden.push(false);
  29700. if (itemInstancing) {
  29701. sprite.getMarker('items').getTemplate().setAttributes(me.getStyleByIndex(i));
  29702. } else {
  29703. sprite.setAttributes(me.getStyleByIndex(i));
  29704. }
  29705. }
  29706. }
  29707. if (spritesCreated) {
  29708. me.updateHidden(hidden);
  29709. }
  29710. return sprites;
  29711. },
  29712. getItemForPoint: function(x, y) {
  29713. var me = this,
  29714. sprites = me.getSprites(),
  29715. store = me.getStore(),
  29716. hidden = me.getHidden(),
  29717. minDistance = Infinity,
  29718. item = null,
  29719. spriteIndex = -1,
  29720. pointIndex = -1,
  29721. point, yField, sprite, i, ln;
  29722. for (i = 0 , ln = sprites.length; i < ln; i++) {
  29723. if (hidden[i]) {
  29724. continue;
  29725. }
  29726. sprite = sprites[i];
  29727. point = sprite.getNearestDataPoint(x, y);
  29728. // Don't stop when the first matching point is found.
  29729. // Keep looking for the nearest point.
  29730. if (point) {
  29731. if (point.distance < minDistance) {
  29732. minDistance = point.distance;
  29733. pointIndex = point.index;
  29734. spriteIndex = i;
  29735. }
  29736. }
  29737. }
  29738. if (spriteIndex > -1) {
  29739. yField = me.getYField();
  29740. item = {
  29741. series: me,
  29742. sprite: sprites[spriteIndex],
  29743. category: me.getItemInstancing() ? 'items' : 'markers',
  29744. index: pointIndex,
  29745. record: store.getData().items[pointIndex],
  29746. // Handle the case where we're stacked but a single segment
  29747. field: typeof yField === 'string' ? yField : yField[spriteIndex],
  29748. distance: minDistance
  29749. };
  29750. }
  29751. return item;
  29752. },
  29753. provideLegendInfo: function(target) {
  29754. var me = this,
  29755. sprites = me.getSprites(),
  29756. title = me.getTitle(),
  29757. field = me.getYField(),
  29758. hidden = me.getHidden(),
  29759. single = sprites.length === 1,
  29760. style, fill, i, name;
  29761. for (i = 0; i < sprites.length; i++) {
  29762. style = me.getStyleByIndex(i);
  29763. fill = style.fillStyle;
  29764. if (title) {
  29765. if (Ext.isArray(title)) {
  29766. name = title[i];
  29767. } else if (single) {
  29768. name = title;
  29769. }
  29770. }
  29771. if (!title || !name) {
  29772. if (Ext.isArray(field)) {
  29773. name = field[i];
  29774. } else {
  29775. name = me.getId();
  29776. }
  29777. }
  29778. target.push({
  29779. name: name,
  29780. mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
  29781. disabled: hidden[i],
  29782. series: me.getId(),
  29783. index: i
  29784. });
  29785. }
  29786. },
  29787. onSpriteAnimationStart: function(sprite) {
  29788. this.spriteAnimationCount++;
  29789. if (this.spriteAnimationCount === 1) {
  29790. this.fireEvent('animationstart');
  29791. }
  29792. },
  29793. onSpriteAnimationEnd: function(sprite) {
  29794. this.spriteAnimationCount--;
  29795. if (this.spriteAnimationCount === 0) {
  29796. this.fireEvent('animationend');
  29797. }
  29798. }
  29799. });
  29800. /**
  29801. * Base class for all series sprites.
  29802. * Defines attributes common to all series sprites, like data in x/y directions and its
  29803. * min/max values, and configs, like the {@link Ext.chart.series.Series} instance that manages
  29804. * the sprite.
  29805. *
  29806. */
  29807. Ext.define('Ext.chart.series.sprite.Series', {
  29808. extend: 'Ext.draw.sprite.Sprite',
  29809. mixins: {
  29810. markerHolder: 'Ext.chart.MarkerHolder'
  29811. },
  29812. inheritableStatics: {
  29813. def: {
  29814. processors: {
  29815. /**
  29816. * @cfg {Number} [dataMinX=0] Data minimum on the x-axis.
  29817. */
  29818. dataMinX: 'number',
  29819. /**
  29820. * @cfg {Number} [dataMaxX=1] Data maximum on the x-axis.
  29821. */
  29822. dataMaxX: 'number',
  29823. /**
  29824. * @cfg {Number} [dataMinY=0] Data minimum on the y-axis.
  29825. */
  29826. dataMinY: 'number',
  29827. /**
  29828. * @cfg {Number} [dataMaxY=1] Data maximum on the y-axis.
  29829. */
  29830. dataMaxY: 'number',
  29831. /**
  29832. * @cfg {Array} [rangeX=null] Data range derived from all the series bound
  29833. * to the x-axis.
  29834. */
  29835. rangeX: 'data',
  29836. /**
  29837. * @cfg {Array} [rangeY=null] Data range derived from all the series bound
  29838. * to the y-axis.
  29839. */
  29840. rangeY: 'data',
  29841. /**
  29842. * @cfg {Object} [dataX=null] Data items on the x-axis.
  29843. */
  29844. dataX: 'data',
  29845. /**
  29846. * @cfg {Object} [dataY=null] Data items on the y-axis.
  29847. */
  29848. dataY: 'data',
  29849. /**
  29850. * @cfg {Object} [labels=null] Labels used in the series.
  29851. */
  29852. labels: 'default',
  29853. /**
  29854. * @cfg {Number} [labelOverflowPadding=10] Padding around labels to determine
  29855. * overlap.
  29856. */
  29857. labelOverflowPadding: 'number'
  29858. },
  29859. defaults: {
  29860. dataMinX: 0,
  29861. dataMaxX: 1,
  29862. dataMinY: 0,
  29863. dataMaxY: 1,
  29864. rangeX: null,
  29865. rangeY: null,
  29866. dataX: null,
  29867. dataY: null,
  29868. labels: null,
  29869. labelOverflowPadding: 10
  29870. },
  29871. triggers: {
  29872. dataX: 'bbox',
  29873. dataY: 'bbox',
  29874. dataMinX: 'bbox',
  29875. dataMaxX: 'bbox',
  29876. dataMinY: 'bbox',
  29877. dataMaxY: 'bbox'
  29878. }
  29879. }
  29880. },
  29881. config: {
  29882. /**
  29883. * @private
  29884. * @cfg {Object} store The store that is passed to the renderer.
  29885. */
  29886. store: null,
  29887. series: null,
  29888. /**
  29889. * @cfg {String} field The store field used by the series.
  29890. */
  29891. field: null
  29892. }
  29893. });
  29894. /**
  29895. * Cartesian sprite.
  29896. */
  29897. Ext.define('Ext.chart.series.sprite.Cartesian', {
  29898. extend: 'Ext.chart.series.sprite.Series',
  29899. inheritableStatics: {
  29900. def: {
  29901. processors: {
  29902. /**
  29903. * @cfg {Number} [selectionTolerance=20]
  29904. * The distance from the event position to the sprite's data points to trigger
  29905. * interactions (used for 'iteminfo', etc).
  29906. */
  29907. selectionTolerance: 'number',
  29908. /**
  29909. * @cfg {Boolean} flipXY If flipXY is 'true', the series is flipped.
  29910. */
  29911. flipXY: 'bool',
  29912. renderer: 'default',
  29913. // Visible range of data (pan/zoom) information.
  29914. visibleMinX: 'number',
  29915. visibleMinY: 'number',
  29916. visibleMaxX: 'number',
  29917. visibleMaxY: 'number',
  29918. innerWidth: 'number',
  29919. innerHeight: 'number'
  29920. },
  29921. defaults: {
  29922. selectionTolerance: 20,
  29923. flipXY: false,
  29924. renderer: null,
  29925. transformFillStroke: false,
  29926. visibleMinX: 0,
  29927. visibleMinY: 0,
  29928. visibleMaxX: 1,
  29929. visibleMaxY: 1,
  29930. innerWidth: 1,
  29931. innerHeight: 1
  29932. },
  29933. triggers: {
  29934. dataX: 'dataX,bbox',
  29935. dataY: 'dataY,bbox',
  29936. visibleMinX: 'panzoom',
  29937. visibleMinY: 'panzoom',
  29938. visibleMaxX: 'panzoom',
  29939. visibleMaxY: 'panzoom',
  29940. innerWidth: 'panzoom',
  29941. innerHeight: 'panzoom'
  29942. },
  29943. updaters: {
  29944. dataX: function(attr) {
  29945. this.processDataX();
  29946. this.scheduleUpdater(attr, 'dataY', [
  29947. 'dataY'
  29948. ]);
  29949. },
  29950. dataY: function() {
  29951. this.processDataY();
  29952. },
  29953. panzoom: function(attr) {
  29954. // dx, dy are deltas between min & max of coordinated data values.
  29955. var dx = attr.visibleMaxX - attr.visibleMinX,
  29956. dy = attr.visibleMaxY - attr.visibleMinY,
  29957. innerWidth = attr.flipXY ? attr.innerHeight : attr.innerWidth,
  29958. innerHeight = !attr.flipXY ? attr.innerHeight : attr.innerWidth,
  29959. surface = this.getSurface(),
  29960. isRtl = surface ? surface.getInherited().rtl : false;
  29961. attr.scalingCenterX = 0;
  29962. attr.scalingCenterY = 0;
  29963. attr.scalingX = innerWidth / dx;
  29964. attr.scalingY = innerHeight / dy;
  29965. // (attr.visibleMinY * attr.scalingY) will be the vertical position of
  29966. // our minimum data points, which we want to be at zero, so we offset
  29967. // by this amount.
  29968. attr.translationX = -(attr.visibleMinX * attr.scalingX);
  29969. attr.translationY = -(attr.visibleMinY * attr.scalingY);
  29970. if (isRtl && !attr.flipXY) {
  29971. attr.scalingX *= -1;
  29972. attr.translationX *= -1;
  29973. attr.translationX += innerWidth;
  29974. }
  29975. this.applyTransformations(true);
  29976. }
  29977. }
  29978. }
  29979. },
  29980. processDataY: Ext.emptyFn,
  29981. processDataX: Ext.emptyFn,
  29982. updatePlainBBox: function(plain) {
  29983. var attr = this.attr;
  29984. plain.x = attr.dataMinX;
  29985. plain.y = attr.dataMinY;
  29986. plain.width = attr.dataMaxX - attr.dataMinX;
  29987. plain.height = attr.dataMaxY - attr.dataMinY;
  29988. },
  29989. /**
  29990. * Does a binary search of the data on the x-axis using the given key.
  29991. * @param {String} key
  29992. * @return {*}
  29993. */
  29994. binarySearch: function(key) {
  29995. var dx = this.attr.dataX,
  29996. start = 0,
  29997. end = dx.length,
  29998. mid, val;
  29999. if (key <= dx[0]) {
  30000. return start;
  30001. }
  30002. if (key >= dx[end - 1]) {
  30003. return end - 1;
  30004. }
  30005. while (start + 1 < end) {
  30006. mid = (start + end) >> 1;
  30007. val = dx[mid];
  30008. if (val === key) {
  30009. return mid;
  30010. } else if (val < key) {
  30011. start = mid;
  30012. } else {
  30013. end = mid;
  30014. }
  30015. }
  30016. return start;
  30017. },
  30018. render: function(surface, ctx, surfaceClipRect) {
  30019. var me = this,
  30020. attr = me.attr,
  30021. margin = 1,
  30022. // TODO: why do we need it?
  30023. inverseMatrix = attr.inverseMatrix.clone(),
  30024. dataClipRect;
  30025. // The sprite's `attr.matrix` is stretching/shrinking data coordinates
  30026. // to surface coordinates.
  30027. // This matrix is set (indirectly) by the 'panzoom' updater.
  30028. // The sprite's `attr.inverseMatrix` does the opposite.
  30029. //
  30030. // The `surface.matrix` of the 'series' surface of a cartesian chart flips the
  30031. // surface content vertically, so that y=0 is at the bottom (look for
  30032. // `surface.matrix.set` call in the CartesianChart.performLayout method).
  30033. // This matrix is set in the 'performLayout' of the CartesianChart.
  30034. // The `surface.inverseMatrix` flips the content back.
  30035. //
  30036. // By combining the inverse matrices of the series surface and the series sprite,
  30037. // we essentially get a transformation that allows us to go from surface coordinates
  30038. // in a final flipped drawing back to data points.
  30039. //
  30040. // For example
  30041. //
  30042. // inverseMatrix.transformPoint([ 0, rect[3] ])
  30043. // inverseMatrix.transformPoint([ rect[2], 0 ])
  30044. //
  30045. // will return
  30046. //
  30047. // [attr.dataMinX, attr.dataMinY]
  30048. // [attr.dataMaxX, attr.dataMaxY]
  30049. //
  30050. // because left/bottom and top/right of the series surface is where the first smallest
  30051. // and last largest data points would be (given no pan/zoom), respectively.
  30052. //
  30053. // So the `dataClipRect` passed to the `renderClipped` call below is effectively
  30054. // the visible rect in data (not surface!) coordinates.
  30055. // It is important to note, that the all the scaling and translation is defined
  30056. // by the sprite's matrix, the 'series' surface matrix does not contain scaling
  30057. // or translation components, except for the vertical flipping.
  30058. // This is important because there is a common pattern in chart series sprites
  30059. // (MarkerHolders) - instead of using transform attributes for their Markers
  30060. // (e.g. instances of a 'rect' sprite in case of 'bar' series), the attributes
  30061. // that would position a sprite with no transformations are transformed.
  30062. // For example, to draw a rect with coordinates TL(10, 10), BR(20, 40),
  30063. // we could use the folling 'rect' sprite attributes:
  30064. //
  30065. // {
  30066. // x: 0,
  30067. // y: 0
  30068. // width: 10,
  30069. // height: 30
  30070. //
  30071. // translationX: 10,
  30072. // translationY: 10
  30073. //
  30074. // But the correct thing to do here is
  30075. //
  30076. // {
  30077. // x: 10,
  30078. // y: 10,
  30079. // width: 10,
  30080. // height: 30
  30081. // }
  30082. //
  30083. // Similarly, if the sprite was scaled, the 'x', 'y', 'width', 'height' attributes
  30084. // would have to account for that as well.
  30085. //
  30086. // This is done, so that the attribute values a marker gets by the time it renders,
  30087. // are the final values, and are not affected later by other transforms, such as
  30088. // surface matrix scaling, which could ruin the visual result, if the attributes
  30089. // values are doctored to make lines align to the pixel grid (which is typically
  30090. // the case).
  30091. inverseMatrix.appendMatrix(surface.inverseMatrix);
  30092. if (attr.dataX === null || attr.dataX === undefined) {
  30093. return;
  30094. }
  30095. if (attr.dataY === null || attr.dataY === undefined) {
  30096. return;
  30097. }
  30098. if (inverseMatrix.getXX() * inverseMatrix.getYX() || inverseMatrix.getXY() * inverseMatrix.getYY()) {
  30099. Ext.Logger.warn('Cartesian Series sprite does not support rotation/sheering');
  30100. return;
  30101. }
  30102. dataClipRect = inverseMatrix.transformList([
  30103. [
  30104. surfaceClipRect[0] - margin,
  30105. surfaceClipRect[3] + margin
  30106. ],
  30107. // (left, height)
  30108. [
  30109. surfaceClipRect[0] + surfaceClipRect[2] + margin,
  30110. -margin
  30111. ]
  30112. ]);
  30113. // (width, top)
  30114. dataClipRect = dataClipRect[0].concat(dataClipRect[1]);
  30115. // TODO: RTL improvements:
  30116. // TODO: produce such a dataClipRect here, so that we don't have to do:
  30117. // TODO: min = Math.min(dataClipRect[0], dataClipRect[2])
  30118. // TODO: max = Math.max(dataClipRect[0], dataClipRect[2])
  30119. // TODO: inside each 'renderClipped' call
  30120. me.renderClipped(surface, ctx, dataClipRect, surfaceClipRect);
  30121. },
  30122. /**
  30123. * Render the given visible clip range.
  30124. * @param {Ext.draw.Surface} surface A draw container surface.
  30125. * @param {CanvasRenderingContext2D} ctx A context object that is API compatible with the native
  30126. * [CanvasRenderingContext2D](https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D).
  30127. * @param {Number[]} dataClipRect The clip rect in data coordinates, roughly equivalent to
  30128. * [attr.dataMinX, attr.dataMinY, attr.dataMaxX, attr.dataMaxY] for an untranslated/unscaled
  30129. * surface/sprite.
  30130. * @param {Number[]} surfaceClipRect The clip rect in surface coordinates:
  30131. * [left, top, width, height].
  30132. * @method
  30133. */
  30134. renderClipped: Ext.emptyFn,
  30135. /**
  30136. * Get the nearest item index from point (x, y). -1 as not found.
  30137. * @param {Number} x
  30138. * @param {Number} y
  30139. * @return {Number} The index
  30140. * @deprecated 6.5.2 Use {@link #getNearestDataPoint} instead.
  30141. */
  30142. getIndexNearPoint: function(x, y) {
  30143. var result = this.getNearestDataPoint(x, y);
  30144. return result ? result.index : -1;
  30145. },
  30146. /**
  30147. * Given a point in 'series' surface element coordinates, returns the `index` of the
  30148. * sprite's data point that is nearest to that point, along with the `distance`
  30149. * between points.
  30150. * If the `selectionTolerance` attribute of the sprite is not zero, only the data points
  30151. * that are within that pixel distance from the given point will be checked.
  30152. * In the event no such data points exist or the data is empty, `null` is returned.
  30153. *
  30154. * Notes:
  30155. * 1) given a mouse/pointer event object, the surface coordinates of the event can be
  30156. * obtained with the `getEventXY` method of the chart;
  30157. * 2) using `selectionTolerance` of zero is useful for series with no visible markers,
  30158. * such as the Area series, where this attribute becomes meaningless.
  30159. *
  30160. * @param {Number} x
  30161. * @param {Number} y
  30162. * @return {Object}
  30163. */
  30164. getNearestDataPoint: function(x, y) {
  30165. var me = this,
  30166. attr = me.attr,
  30167. series = me.getSeries(),
  30168. surface = me.getSurface(),
  30169. items = me.boundMarkers.items,
  30170. matrix = attr.matrix,
  30171. dataX = attr.dataX,
  30172. dataY = attr.dataY,
  30173. selectionTolerance = attr.selectionTolerance,
  30174. minDistance = Infinity,
  30175. index = -1,
  30176. result = null,
  30177. distance, dx, dy, xy, i, ln, end, inc, bbox;
  30178. // Notes:
  30179. // Instead of converting the given point from surface coordinates to data coordinates
  30180. // and then measuring the distances between it and the data points, we have to
  30181. // convert all the data points to surface coordinates and measure the distances
  30182. // between them and the given point. This is because the data coordinates can use
  30183. // different scales, which makes distance measurement impossible.
  30184. // For example, if the x-axis is a `category` axis, the categories will be assigned
  30185. // indexes starting from 0, that's what the `attr.dataX` array will contain;
  30186. // and if the y-axis is a `numeric` axis, the `attr.dataY` array will simply contain
  30187. // the original values.
  30188. //
  30189. // Either 'items' or 'markers' will be highlighted. If a sprite has both (for example,
  30190. // 'bar' series with the 'marker' config, where the bars are 'items' and marker instances
  30191. // are 'markers'), only the 'items' (bars) will be highlighted.
  30192. if (items) {
  30193. ln = dataX.length;
  30194. if (series.reversedSpriteZOrder) {
  30195. i = ln - 1;
  30196. end = -1;
  30197. inc = -1;
  30198. } else {
  30199. i = 0;
  30200. end = ln;
  30201. inc = 1;
  30202. }
  30203. for (; i !== end; i += inc) {
  30204. bbox = me.getMarkerBBox('items', i);
  30205. // Transform the given surface element coordinates to logical coordinates
  30206. // of the surface (the ones the bbox uses).
  30207. xy = surface.inverseMatrix.transformPoint([
  30208. x,
  30209. y
  30210. ]);
  30211. if (Ext.draw.Draw.isPointInBBox(xy[0], xy[1], bbox)) {
  30212. index = i;
  30213. minDistance = 0;
  30214. // Return the first item that contains our touch point.
  30215. break;
  30216. }
  30217. }
  30218. } else {
  30219. // markers
  30220. for (i = 0 , ln = dataX.length; i < ln; i++) {
  30221. // Convert from data coordinates to coordinates within inner size rectangle.
  30222. // See `panzoom` method for more details.
  30223. xy = matrix.transformPoint([
  30224. dataX[i],
  30225. dataY[i]
  30226. ]);
  30227. // Flip back vertically and padding adjust (see `render` method comments).
  30228. xy = surface.matrix.transformPoint(xy);
  30229. // Essentially sprites go through the same two transformations when they render
  30230. // data points.
  30231. dx = x - xy[0];
  30232. dy = y - xy[1];
  30233. distance = Math.sqrt(dx * dx + dy * dy);
  30234. if (selectionTolerance && distance > selectionTolerance) {
  30235. continue;
  30236. }
  30237. if (distance < minDistance) {
  30238. minDistance = distance;
  30239. index = i;
  30240. }
  30241. }
  30242. }
  30243. // Keep looking for the nearest marker.
  30244. if (index > -1) {
  30245. result = {
  30246. index: index,
  30247. distance: minDistance
  30248. };
  30249. }
  30250. return result;
  30251. }
  30252. });
  30253. /**
  30254. * @class Ext.chart.series.sprite.StackedCartesian
  30255. * @extends Ext.chart.series.sprite.Cartesian
  30256. *
  30257. * Stacked cartesian sprite.
  30258. */
  30259. Ext.define('Ext.chart.series.sprite.StackedCartesian', {
  30260. extend: 'Ext.chart.series.sprite.Cartesian',
  30261. inheritableStatics: {
  30262. def: {
  30263. processors: {
  30264. /**
  30265. * @private
  30266. * @cfg {Number} [groupCount=1] The number of items (e.g. bars) in a group.
  30267. */
  30268. groupCount: 'number',
  30269. /**
  30270. * @private
  30271. * @cfg {Number} [groupOffset=0] The group index of the series sprite.
  30272. */
  30273. groupOffset: 'number',
  30274. /**
  30275. * @private
  30276. * @cfg {Object} [dataStartY=null] The starting point of the data
  30277. * used in the series.
  30278. */
  30279. dataStartY: 'data'
  30280. },
  30281. defaults: {
  30282. selectionTolerance: 20,
  30283. groupCount: 1,
  30284. groupOffset: 0,
  30285. dataStartY: null
  30286. },
  30287. triggers: {
  30288. dataStartY: 'dataY,bbox'
  30289. }
  30290. }
  30291. }
  30292. });
  30293. /**
  30294. * @class Ext.chart.series.sprite.Area
  30295. * @extends Ext.chart.series.sprite.StackedCartesian
  30296. *
  30297. * Area series sprite.
  30298. */
  30299. Ext.define('Ext.chart.series.sprite.Area', {
  30300. alias: 'sprite.areaSeries',
  30301. extend: 'Ext.chart.series.sprite.StackedCartesian',
  30302. inheritableStatics: {
  30303. def: {
  30304. processors: {
  30305. /**
  30306. * @cfg {Boolean} [step=false] 'true' if the area is represented with steps
  30307. * instead of lines.
  30308. */
  30309. step: 'bool'
  30310. },
  30311. defaults: {
  30312. selectionTolerance: 0,
  30313. step: false
  30314. }
  30315. }
  30316. },
  30317. renderClipped: function(surface, ctx, dataClipRect) {
  30318. var me = this,
  30319. store = me.getStore(),
  30320. series = me.getSeries(),
  30321. attr = me.attr,
  30322. dataX = attr.dataX,
  30323. dataY = attr.dataY,
  30324. dataStartY = attr.dataStartY,
  30325. matrix = attr.matrix,
  30326. x, y, i, lastX, lastY, startX, startY,
  30327. xx = matrix.elements[0],
  30328. dx = matrix.elements[4],
  30329. yy = matrix.elements[3],
  30330. dy = matrix.elements[5],
  30331. surfaceMatrix = me.surfaceMatrix,
  30332. markerCfg = {},
  30333. min = Math.min(dataClipRect[0], dataClipRect[2]),
  30334. max = Math.max(dataClipRect[0], dataClipRect[2]),
  30335. start = Math.max(0, this.binarySearch(min)),
  30336. end = Math.min(dataX.length - 1, this.binarySearch(max) + 1),
  30337. renderer = attr.renderer,
  30338. rendererData = {
  30339. store: store
  30340. },
  30341. rendererChanges;
  30342. ctx.beginPath();
  30343. startX = dataX[start] * xx + dx;
  30344. startY = dataY[start] * yy + dy;
  30345. ctx.moveTo(startX, startY);
  30346. if (attr.step) {
  30347. lastY = startY;
  30348. for (i = start; i <= end; i++) {
  30349. x = dataX[i] * xx + dx;
  30350. y = dataY[i] * yy + dy;
  30351. ctx.lineTo(x, lastY);
  30352. ctx.lineTo(x, lastY = y);
  30353. }
  30354. } else {
  30355. for (i = start; i <= end; i++) {
  30356. x = dataX[i] * xx + dx;
  30357. y = dataY[i] * yy + dy;
  30358. ctx.lineTo(x, y);
  30359. }
  30360. }
  30361. if (dataStartY) {
  30362. if (attr.step) {
  30363. lastX = dataX[end] * xx + dx;
  30364. for (i = end; i >= start; i--) {
  30365. x = dataX[i] * xx + dx;
  30366. y = dataStartY[i] * yy + dy;
  30367. ctx.lineTo(lastX, y);
  30368. ctx.lineTo(lastX = x, y);
  30369. }
  30370. } else {
  30371. for (i = end; i >= start; i--) {
  30372. x = dataX[i] * xx + dx;
  30373. y = dataStartY[i] * yy + dy;
  30374. ctx.lineTo(x, y);
  30375. }
  30376. }
  30377. } else {
  30378. ctx.lineTo(dataX[end] * xx + dx, y);
  30379. ctx.lineTo(dataX[end] * xx + dx, dy);
  30380. ctx.lineTo(startX, dy);
  30381. ctx.lineTo(startX, dataY[i] * yy + dy);
  30382. }
  30383. if (attr.transformFillStroke) {
  30384. attr.matrix.toContext(ctx);
  30385. }
  30386. ctx.fill();
  30387. if (attr.transformFillStroke) {
  30388. attr.inverseMatrix.toContext(ctx);
  30389. }
  30390. ctx.beginPath();
  30391. ctx.moveTo(startX, startY);
  30392. if (attr.step) {
  30393. for (i = start; i <= end; i++) {
  30394. x = dataX[i] * xx + dx;
  30395. y = dataY[i] * yy + dy;
  30396. ctx.lineTo(x, lastY);
  30397. ctx.lineTo(x, lastY = y);
  30398. markerCfg.translationX = surfaceMatrix.x(x, y);
  30399. markerCfg.translationY = surfaceMatrix.y(x, y);
  30400. if (renderer) {
  30401. // callback(fn, scope, args, delay, caller)
  30402. rendererChanges = Ext.callback(renderer, null, [
  30403. me,
  30404. markerCfg,
  30405. rendererData,
  30406. i
  30407. ], 0, series);
  30408. Ext.apply(markerCfg, rendererChanges);
  30409. }
  30410. me.putMarker('markers', markerCfg, i, !renderer);
  30411. }
  30412. } else {
  30413. for (i = start; i <= end; i++) {
  30414. x = dataX[i] * xx + dx;
  30415. y = dataY[i] * yy + dy;
  30416. ctx.lineTo(x, y);
  30417. markerCfg.translationX = surfaceMatrix.x(x, y);
  30418. markerCfg.translationY = surfaceMatrix.y(x, y);
  30419. if (renderer) {
  30420. rendererChanges = Ext.callback(renderer, null, [
  30421. me,
  30422. markerCfg,
  30423. rendererData,
  30424. i
  30425. ], 0, series);
  30426. Ext.apply(markerCfg, rendererChanges);
  30427. }
  30428. me.putMarker('markers', markerCfg, i, !renderer);
  30429. }
  30430. }
  30431. if (attr.transformFillStroke) {
  30432. attr.matrix.toContext(ctx);
  30433. }
  30434. ctx.stroke();
  30435. }
  30436. });
  30437. /**
  30438. * @class Ext.chart.series.Area
  30439. * @extends Ext.chart.series.StackedCartesian
  30440. *
  30441. * Creates an Area Chart.
  30442. *
  30443. * @example
  30444. * Ext.create({
  30445. * xtype: 'cartesian',
  30446. * renderTo: document.body,
  30447. * width: 600,
  30448. * height: 400,
  30449. * insetPadding: 40,
  30450. * store: {
  30451. * fields: ['name', 'data1', 'data2', 'data3'],
  30452. * data: [{
  30453. * name: 'metric one',
  30454. * data1: 10,
  30455. * data2: 12,
  30456. * data3: 14
  30457. * }, {
  30458. * name: 'metric two',
  30459. * data1: 7,
  30460. * data2: 8,
  30461. * data3: 16
  30462. * }, {
  30463. * name: 'metric three',
  30464. * data1: 5,
  30465. * data2: 2,
  30466. * data3: 14
  30467. * }, {
  30468. * name: 'metric four',
  30469. * data1: 2,
  30470. * data2: 14,
  30471. * data3: 6
  30472. * }, {
  30473. * name: 'metric five',
  30474. * data1: 27,
  30475. * data2: 38,
  30476. * data3: 36
  30477. * }]
  30478. * },
  30479. * axes: [{
  30480. * type: 'numeric',
  30481. * position: 'left',
  30482. * fields: ['data1'],
  30483. * grid: true,
  30484. * minimum: 0
  30485. * }, {
  30486. * type: 'category',
  30487. * position: 'bottom',
  30488. * fields: ['name']
  30489. * }],
  30490. * series: {
  30491. * type: 'area',
  30492. * subStyle: {
  30493. * fill: ['#0A3F50', '#30BDA7', '#96D4C6']
  30494. * },
  30495. * xField: 'name',
  30496. * yField: ['data1', 'data2', 'data3']
  30497. * }
  30498. * });
  30499. */
  30500. Ext.define('Ext.chart.series.Area', {
  30501. extend: 'Ext.chart.series.StackedCartesian',
  30502. alias: 'series.area',
  30503. type: 'area',
  30504. /**
  30505. * @property seriesType
  30506. * @inheritdoc
  30507. */
  30508. seriesType: 'areaSeries',
  30509. isArea: true,
  30510. requires: [
  30511. 'Ext.chart.series.sprite.Area'
  30512. ],
  30513. config: {
  30514. /**
  30515. * @cfg splitStacks
  30516. * @inheritdoc
  30517. */
  30518. splitStacks: false
  30519. }
  30520. });
  30521. /**
  30522. * @cfg renderer
  30523. * @inheritdoc
  30524. * Area series renderers only affect markers.
  30525. * For styling individual segments with a renderer it is possible to use
  30526. * the Line series with {@link Ext.chart.series.Line#fill} config set to `true`,
  30527. * which makes Line series look like Area series.
  30528. */
  30529. /**
  30530. * @class Ext.chart.series.sprite.Bar
  30531. * @extends Ext.chart.series.sprite.StackedCartesian
  30532. *
  30533. * Draws a sprite used in the bar series.
  30534. */
  30535. Ext.define('Ext.chart.series.sprite.Bar', {
  30536. alias: 'sprite.barSeries',
  30537. extend: 'Ext.chart.series.sprite.StackedCartesian',
  30538. inheritableStatics: {
  30539. def: {
  30540. processors: {
  30541. /**
  30542. * @cfg {Number} [minBarWidth=2] The minimum bar width.
  30543. */
  30544. minBarWidth: 'number',
  30545. /**
  30546. * @cfg {Number} [maxBarWidth=100] The maximum bar width.
  30547. */
  30548. maxBarWidth: 'number',
  30549. /**
  30550. * @cfg {Number} [minGapWidth=5] The minimum gap between bars.
  30551. */
  30552. minGapWidth: 'number',
  30553. /**
  30554. * @cfg {Number} [radius=0] The degree of rounding for rounded bars.
  30555. */
  30556. radius: 'number',
  30557. /**
  30558. * @cfg {Number} [inGroupGapWidth=3] The gap between grouped bars.
  30559. */
  30560. inGroupGapWidth: 'number'
  30561. },
  30562. defaults: {
  30563. minBarWidth: 2,
  30564. maxBarWidth: 100,
  30565. minGapWidth: 5,
  30566. inGroupGapWidth: 3,
  30567. radius: 0
  30568. }
  30569. }
  30570. },
  30571. drawLabel: function(text, dataX, dataStartY, dataY, labelId) {
  30572. var me = this,
  30573. attr = me.attr,
  30574. label = me.getMarker('labels'),
  30575. labelTpl = label.getTemplate(),
  30576. labelCfg = me.labelCfg || (me.labelCfg = {}),
  30577. surfaceMatrix = me.surfaceMatrix,
  30578. labelOverflowPadding = attr.labelOverflowPadding,
  30579. labelDisplay = labelTpl.attr.display,
  30580. labelOrientation = labelTpl.attr.orientation,
  30581. isVerticalText = (labelOrientation === 'horizontal' && attr.flipXY) || (labelOrientation === 'vertical' && !attr.flipXY) || !labelOrientation,
  30582. calloutLine = labelTpl.getCalloutLine(),
  30583. labelY, halfText, labelBBox, calloutLineLength, changes, hasPendingChanges, params;
  30584. // The coordinates below (data point converted to surface coordinates)
  30585. // are just for the renderer to give it a notion of where the label will be positioned.
  30586. // The actual position of the label will be different
  30587. // (unless the renderer returns x/y coordinates in the changes object)
  30588. // and depend on several things including the size of the text,
  30589. // which has to be measured after the renderer call,
  30590. // since text can be modified by the renderer.
  30591. labelCfg.x = surfaceMatrix.x(dataX, dataY);
  30592. labelCfg.y = surfaceMatrix.y(dataX, dataY);
  30593. if (calloutLine) {
  30594. calloutLineLength = calloutLine.length;
  30595. } else {
  30596. calloutLineLength = 0;
  30597. }
  30598. // Set defaults
  30599. if (!attr.flipXY) {
  30600. labelCfg.rotationRads = -Math.PI * 0.5;
  30601. } else {
  30602. labelCfg.rotationRads = 0;
  30603. }
  30604. labelCfg.calloutVertical = !attr.flipXY;
  30605. // Check if we have a specific orientation specified, if so, set
  30606. // the appropriate values.
  30607. switch (labelOrientation) {
  30608. case 'horizontal':
  30609. labelCfg.rotationRads = 0;
  30610. labelCfg.calloutVertical = false;
  30611. break;
  30612. case 'vertical':
  30613. labelCfg.rotationRads = -Math.PI * 0.5;
  30614. labelCfg.calloutVertical = true;
  30615. break;
  30616. }
  30617. labelCfg.text = text;
  30618. if (labelTpl.attr.renderer) {
  30619. // The label instance won't exist on first render before the renderer is called,
  30620. // it's only created later by `me.putMarker` after the renderer call. To make
  30621. // sure the renderer always can access the label instance, we make this check here.
  30622. if (!label.get(labelId)) {
  30623. label.putMarkerFor('labels', {}, labelId);
  30624. }
  30625. params = [
  30626. text,
  30627. label,
  30628. labelCfg,
  30629. {
  30630. store: me.getStore()
  30631. },
  30632. labelId
  30633. ];
  30634. changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
  30635. if (typeof changes === 'string') {
  30636. labelCfg.text = changes;
  30637. } else if (typeof changes === 'object') {
  30638. if ('text' in changes) {
  30639. labelCfg.text = changes.text;
  30640. }
  30641. hasPendingChanges = true;
  30642. }
  30643. }
  30644. labelBBox = me.getMarkerBBox('labels', labelId, true);
  30645. if (!labelBBox) {
  30646. me.putMarker('labels', labelCfg, labelId);
  30647. labelBBox = me.getMarkerBBox('labels', labelId, true);
  30648. }
  30649. if (calloutLineLength > 0) {
  30650. halfText = calloutLineLength;
  30651. } else if (calloutLineLength === 0) {
  30652. halfText = (isVerticalText ? labelBBox.width : labelBBox.height) / 2;
  30653. } else {
  30654. halfText = (isVerticalText ? labelBBox.width : labelBBox.height) / 2 + labelOverflowPadding;
  30655. }
  30656. if (dataStartY > dataY) {
  30657. halfText = -halfText;
  30658. }
  30659. if (isVerticalText) {
  30660. labelY = (labelDisplay === 'insideStart') ? dataStartY + halfText : dataY - halfText;
  30661. } else {
  30662. labelY = (labelDisplay === 'insideStart') ? dataStartY + labelOverflowPadding * 2 : dataY - labelOverflowPadding * 2;
  30663. }
  30664. labelCfg.x = surfaceMatrix.x(dataX, labelY);
  30665. labelCfg.y = surfaceMatrix.y(dataX, labelY);
  30666. labelY = (labelDisplay === 'insideStart') ? dataStartY : dataY;
  30667. labelCfg.calloutStartX = surfaceMatrix.x(dataX, labelY);
  30668. labelCfg.calloutStartY = surfaceMatrix.y(dataX, labelY);
  30669. labelY = (labelDisplay === 'insideStart') ? dataStartY - halfText : dataY + halfText;
  30670. labelCfg.calloutPlaceX = surfaceMatrix.x(dataX, labelY);
  30671. labelCfg.calloutPlaceY = surfaceMatrix.y(dataX, labelY);
  30672. labelCfg.calloutColor = (calloutLine && calloutLine.color) || me.attr.fillStyle;
  30673. if (calloutLine) {
  30674. if (calloutLine.width) {
  30675. labelCfg.calloutWidth = calloutLine.width;
  30676. }
  30677. } else {
  30678. labelCfg.calloutColor = 'none';
  30679. }
  30680. if (dataStartY > dataY) {
  30681. halfText = -halfText;
  30682. }
  30683. if (Math.abs(dataY - dataStartY) <= halfText * 2 || labelDisplay === 'outside') {
  30684. labelCfg.callout = 1;
  30685. } else {
  30686. labelCfg.callout = 0;
  30687. }
  30688. if (hasPendingChanges) {
  30689. Ext.apply(labelCfg, changes);
  30690. }
  30691. me.putMarker('labels', labelCfg, labelId);
  30692. },
  30693. drawBar: function(ctx, surface, rect, left, top, right, bottom, index) {
  30694. var me = this,
  30695. itemCfg = {},
  30696. renderer = me.attr.renderer,
  30697. changes;
  30698. itemCfg.x = left;
  30699. itemCfg.y = top;
  30700. itemCfg.width = right - left;
  30701. itemCfg.height = bottom - top;
  30702. itemCfg.radius = me.attr.radius;
  30703. if (renderer) {
  30704. changes = Ext.callback(renderer, null, [
  30705. me,
  30706. itemCfg,
  30707. {
  30708. store: me.getStore()
  30709. },
  30710. index
  30711. ], 0, me.getSeries());
  30712. Ext.apply(itemCfg, changes);
  30713. }
  30714. me.putMarker('items', itemCfg, index, !renderer);
  30715. },
  30716. renderClipped: function(surface, ctx, dataClipRect) {
  30717. if (this.cleanRedraw) {
  30718. return;
  30719. }
  30720. // eslint-disable-next-line vars-on-top
  30721. var me = this,
  30722. attr = me.attr,
  30723. dataX = attr.dataX,
  30724. dataY = attr.dataY,
  30725. dataText = attr.labels,
  30726. dataStartY = attr.dataStartY,
  30727. groupCount = attr.groupCount,
  30728. groupOffset = attr.groupOffset - (groupCount - 1) * 0.5,
  30729. inGroupGapWidth = attr.inGroupGapWidth,
  30730. lineWidth = ctx.lineWidth,
  30731. matrix = attr.matrix,
  30732. xx = matrix.elements[0],
  30733. yy = matrix.elements[3],
  30734. dx = matrix.elements[4],
  30735. dy = surface.roundPixel(matrix.elements[5]) - 1,
  30736. maxBarWidth = Math.abs(xx) - attr.minGapWidth,
  30737. minBarWidth = (Math.min(maxBarWidth, attr.maxBarWidth) - inGroupGapWidth * (groupCount - 1)) / groupCount,
  30738. barWidth = surface.roundPixel(Math.max(attr.minBarWidth, minBarWidth)),
  30739. surfaceMatrix = me.surfaceMatrix,
  30740. left, right, bottom, top, i, center,
  30741. halfLineWidth = 0.5 * attr.lineWidth,
  30742. // Finding min/max so that bars render properly in both LTR and RTL modes.
  30743. min = Math.min(dataClipRect[0], dataClipRect[2]),
  30744. max = Math.max(dataClipRect[0], dataClipRect[2]),
  30745. start = Math.max(0, Math.floor(min)),
  30746. end = Math.min(dataX.length - 1, Math.ceil(max)),
  30747. isDrawLabels = dataText && me.getMarker('labels'),
  30748. yLow, yHi;
  30749. // The scaling (xx) and translation (dx) here will already be such that the midpoints
  30750. // of the first and last bars are not at the surface edges (which would mean that
  30751. // bars are half-clipped), but padded, so that those bars are fully visible
  30752. // (assuming no pan/zoom).
  30753. for (i = start; i <= end; i++) {
  30754. yLow = dataStartY ? dataStartY[i] : 0;
  30755. yHi = dataY[i];
  30756. center = dataX[i] * xx + dx + groupOffset * (barWidth + inGroupGapWidth);
  30757. left = surface.roundPixel(center - barWidth / 2) + halfLineWidth;
  30758. top = surface.roundPixel(yHi * yy + dy + lineWidth);
  30759. right = surface.roundPixel(center + barWidth / 2) - halfLineWidth;
  30760. bottom = surface.roundPixel(yLow * yy + dy + lineWidth);
  30761. me.drawBar(ctx, surface, dataClipRect, left, top - halfLineWidth, right, bottom - halfLineWidth, i);
  30762. // We want 0 values to be passed to the renderer
  30763. if (isDrawLabels && dataText[i] != null) {
  30764. me.drawLabel(dataText[i], center, bottom, top, i);
  30765. }
  30766. me.putMarker('markers', {
  30767. translationX: surfaceMatrix.x(center, top),
  30768. translationY: surfaceMatrix.y(center, top)
  30769. }, i, true);
  30770. }
  30771. }
  30772. });
  30773. /**
  30774. * @class Ext.chart.series.Bar
  30775. * @extends Ext.chart.series.StackedCartesian
  30776. *
  30777. * Creates a Bar or Column Chart (depending on the value of the
  30778. * {@link Ext.chart.CartesianChart#flipXY flipXY} config).
  30779. *
  30780. * Note: 'bar' series is meant to be used with the
  30781. * {@link Ext.chart.axis.Category 'category'} axis as its x-axis.
  30782. *
  30783. * @example
  30784. * Ext.create({
  30785. * xtype: 'cartesian',
  30786. * renderTo: document.body,
  30787. * width: 600,
  30788. * height: 400,
  30789. * store: {
  30790. * fields: ['name', 'value'],
  30791. * data: [{
  30792. * name: 'metric one',
  30793. * value: 10
  30794. * }, {
  30795. * name: 'metric two',
  30796. * value: 7
  30797. * }, {
  30798. * name: 'metric three',
  30799. * value: 5
  30800. * }, {
  30801. * name: 'metric four',
  30802. * value: 2
  30803. * }, {
  30804. * name: 'metric five',
  30805. * value: 27
  30806. * }]
  30807. * },
  30808. * axes: [{
  30809. * type: 'numeric',
  30810. * position: 'left',
  30811. * title: {
  30812. * text: 'Sample Values',
  30813. * fontSize: 15
  30814. * },
  30815. * fields: 'value'
  30816. * }, {
  30817. * type: 'category',
  30818. * position: 'bottom',
  30819. * title: {
  30820. * text: 'Sample Values',
  30821. * fontSize: 15
  30822. * },
  30823. * fields: 'name'
  30824. * }],
  30825. * series: {
  30826. * type: 'bar',
  30827. * subStyle: {
  30828. * fill: ['#388FAD'],
  30829. * stroke: '#1F6D91'
  30830. * },
  30831. * xField: 'name',
  30832. * yField: 'value'
  30833. * }
  30834. * });
  30835. */
  30836. Ext.define('Ext.chart.series.Bar', {
  30837. extend: 'Ext.chart.series.StackedCartesian',
  30838. alias: 'series.bar',
  30839. type: 'bar',
  30840. seriesType: 'barSeries',
  30841. isBar: true,
  30842. requires: [
  30843. 'Ext.chart.series.sprite.Bar',
  30844. 'Ext.draw.sprite.Rect'
  30845. ],
  30846. config: {
  30847. /**
  30848. * @private
  30849. * @cfg {Object} itemInstancing Sprite template used for series.
  30850. */
  30851. itemInstancing: {
  30852. type: 'rect',
  30853. animation: {
  30854. customDurations: {
  30855. x: 0,
  30856. y: 0,
  30857. width: 0,
  30858. height: 0,
  30859. radius: 0
  30860. }
  30861. }
  30862. }
  30863. },
  30864. getItemForPoint: function(x, y) {
  30865. var chart, padding, isRtl;
  30866. if (this.getSprites().length) {
  30867. chart = this.getChart();
  30868. padding = chart.getInnerPadding();
  30869. isRtl = chart.getInherited().rtl;
  30870. // Convert the coordinates because the "items" sprites that draw
  30871. // the bars ignore the chart's InnerPadding.
  30872. arguments[0] = x + (isRtl ? padding.right : -padding.left);
  30873. arguments[1] = y + padding.bottom;
  30874. return this.callParent(arguments);
  30875. }
  30876. },
  30877. updateXAxis: function(xAxis) {
  30878. //<debug>
  30879. if (!this.is3D && !xAxis.isCategory) {
  30880. Ext.raise("'bar' series should be used with a 'category' axis. " + "Please refer to the bar series docs.");
  30881. }
  30882. //</debug>
  30883. xAxis.setExpandRangeBy(0.5);
  30884. this.callParent(arguments);
  30885. },
  30886. updateHidden: function(hidden) {
  30887. this.callParent(arguments);
  30888. this.updateStacked();
  30889. },
  30890. updateStacked: function(stacked) {
  30891. var me = this,
  30892. attributes = {},
  30893. sprites = me.getSprites(),
  30894. spriteCount = sprites.length,
  30895. visibleSprites = [],
  30896. visibleSpriteCount, i;
  30897. for (i = 0; i < spriteCount; i++) {
  30898. if (!sprites[i].attr.hidden) {
  30899. visibleSprites.push(sprites[i]);
  30900. }
  30901. }
  30902. visibleSpriteCount = visibleSprites.length;
  30903. if (me.getStacked()) {
  30904. attributes.groupCount = 1;
  30905. attributes.groupOffset = 0;
  30906. for (i = 0; i < visibleSpriteCount; i++) {
  30907. visibleSprites[i].setAttributes(attributes);
  30908. }
  30909. } else {
  30910. attributes.groupCount = visibleSpriteCount;
  30911. for (i = 0; i < visibleSpriteCount; i++) {
  30912. attributes.groupOffset = i;
  30913. visibleSprites[i].setAttributes(attributes);
  30914. }
  30915. }
  30916. me.callParent(arguments);
  30917. }
  30918. });
  30919. /**
  30920. * @class Ext.chart.series.sprite.Bar3D
  30921. * @extends Ext.chart.series.sprite.Bar
  30922. *
  30923. * Draws a sprite used in {@link Ext.chart.series.Bar3D} series.
  30924. */
  30925. Ext.define('Ext.chart.series.sprite.Bar3D', {
  30926. extend: 'Ext.chart.series.sprite.Bar',
  30927. alias: 'sprite.bar3dSeries',
  30928. requires: [
  30929. 'Ext.draw.gradient.Linear'
  30930. ],
  30931. inheritableStatics: {
  30932. def: {
  30933. processors: {
  30934. depthWidthRatio: 'number',
  30935. /**
  30936. * @cfg {Number} [saturationFactor=1]
  30937. * The factor applied to the saturation of the bars.
  30938. */
  30939. saturationFactor: 'number',
  30940. /**
  30941. * @cfg {Number} [brightnessFactor=1]
  30942. * The factor applied to the brightness of the bars.
  30943. */
  30944. brightnessFactor: 'number',
  30945. /**
  30946. * @cfg {Number} [colorSpread=1]
  30947. * An attribute used to control how flat the bar gradient looks.
  30948. * A value of 0 essentially means no gradient (flat color).
  30949. */
  30950. colorSpread: 'number'
  30951. },
  30952. defaults: {
  30953. depthWidthRatio: 1 / 3,
  30954. saturationFactor: 1,
  30955. brightnessFactor: 1,
  30956. colorSpread: 1,
  30957. transformFillStroke: true
  30958. },
  30959. triggers: {
  30960. groupCount: 'panzoom'
  30961. },
  30962. updaters: {
  30963. panzoom: function(attr) {
  30964. var me = this,
  30965. dx = attr.visibleMaxX - attr.visibleMinX,
  30966. dy = attr.visibleMaxY - attr.visibleMinY,
  30967. innerWidth = attr.flipXY ? attr.innerHeight : attr.innerWidth,
  30968. innerHeight = !attr.flipXY ? attr.innerHeight : attr.innerWidth,
  30969. surface = me.getSurface(),
  30970. isRtl = surface ? surface.getInherited().rtl : false;
  30971. if (isRtl && !attr.flipXY) {
  30972. attr.translationX = innerWidth + attr.visibleMinX * innerWidth / dx;
  30973. } else {
  30974. attr.translationX = -attr.visibleMinX * innerWidth / dx;
  30975. }
  30976. attr.translationY = -attr.visibleMinY * (innerHeight - me.depth) / dy;
  30977. attr.scalingX = (isRtl && !attr.flipXY ? -1 : 1) * innerWidth / dx;
  30978. attr.scalingY = (innerHeight - me.depth) / dy;
  30979. attr.scalingCenterX = 0;
  30980. attr.scalingCenterY = 0;
  30981. me.applyTransformations(true);
  30982. }
  30983. }
  30984. }
  30985. },
  30986. config: {
  30987. showStroke: false
  30988. },
  30989. depth: 0,
  30990. drawBar: function(ctx, surface, clip, left, top, right, bottom, index) {
  30991. var me = this,
  30992. attr = me.attr,
  30993. itemCfg = {},
  30994. renderer = attr.renderer,
  30995. changes, depth, series, params;
  30996. itemCfg.x = (left + right) * 0.5;
  30997. itemCfg.y = top;
  30998. itemCfg.width = (right - left) * 0.75;
  30999. itemCfg.height = bottom - top;
  31000. itemCfg.depth = depth = itemCfg.width * attr.depthWidthRatio;
  31001. itemCfg.orientation = attr.flipXY ? 'horizontal' : 'vertical';
  31002. itemCfg.saturationFactor = attr.saturationFactor;
  31003. itemCfg.brightnessFactor = attr.brightnessFactor;
  31004. itemCfg.colorSpread = attr.colorSpread;
  31005. if (depth !== me.depth) {
  31006. me.depth = depth;
  31007. series = me.getSeries();
  31008. series.fireEvent('depthchange', series, depth);
  31009. }
  31010. if (renderer) {
  31011. params = [
  31012. me,
  31013. itemCfg,
  31014. {
  31015. store: me.getStore()
  31016. },
  31017. index
  31018. ];
  31019. changes = Ext.callback(renderer, null, params, 0, me.getSeries());
  31020. Ext.apply(itemCfg, changes);
  31021. }
  31022. me.putMarker('items', itemCfg, index, !renderer);
  31023. }
  31024. });
  31025. /**
  31026. * @class Ext.chart.sprite.Bar3D
  31027. * @extends Ext.draw.sprite.Sprite
  31028. *
  31029. * A sprite that represents a 3D bar or column.
  31030. * Used as an item template by the {@link Ext.chart.series.sprite.Bar3D} marker holder.
  31031. *
  31032. */
  31033. Ext.define('Ext.chart.sprite.Bar3D', {
  31034. extend: 'Ext.draw.sprite.Sprite',
  31035. alias: 'sprite.bar3d',
  31036. type: 'bar3d',
  31037. inheritableStatics: {
  31038. def: {
  31039. processors: {
  31040. /**
  31041. * @cfg {Number} [x=0]
  31042. * The position of the sprite on the x-axis.
  31043. * Corresponds to the center of the front face of the box.
  31044. */
  31045. x: 'number',
  31046. /**
  31047. * @cfg {Number} [y=0]
  31048. * The position of the sprite on the y-axis.
  31049. * Corresponds to the top of the front face of the box.
  31050. */
  31051. y: 'number',
  31052. /**
  31053. * @cfg {Number} [width=8] The width of the box.
  31054. */
  31055. width: 'number',
  31056. /**
  31057. * @cfg {Number} [height=8] The height of the box.
  31058. */
  31059. height: 'number',
  31060. /**
  31061. * @cfg {Number} [depth=8] The depth of the box.
  31062. */
  31063. depth: 'number',
  31064. /**
  31065. * @cfg {String} [orientation='vertical'] The orientation of the box.
  31066. */
  31067. orientation: 'enums(vertical,horizontal)',
  31068. /**
  31069. * @cfg {Boolean} [showStroke=false]
  31070. * Whether to render the stroke or not.
  31071. */
  31072. showStroke: 'bool',
  31073. /**
  31074. * @cfg {Number} [saturationFactor=1]
  31075. * The factor applied to the saturation of the box.
  31076. */
  31077. saturationFactor: 'number',
  31078. /**
  31079. * @cfg {Number} [brightnessFactor=1]
  31080. * The factor applied to the brightness of the box.
  31081. */
  31082. brightnessFactor: 'number',
  31083. /**
  31084. * @cfg {Number} [colorSpread=1]
  31085. * An attribute used to control how flat the bar gradient looks.
  31086. * A value of 0 essentially means no gradient (flat color).
  31087. */
  31088. colorSpread: 'number'
  31089. },
  31090. triggers: {
  31091. x: 'bbox',
  31092. y: 'bbox',
  31093. width: 'bbox',
  31094. height: 'bbox',
  31095. depth: 'bbox',
  31096. orientation: 'bbox'
  31097. },
  31098. defaults: {
  31099. x: 0,
  31100. y: 0,
  31101. width: 8,
  31102. height: 8,
  31103. depth: 8,
  31104. orientation: 'vertical',
  31105. showStroke: false,
  31106. saturationFactor: 1,
  31107. brightnessFactor: 1,
  31108. colorSpread: 1,
  31109. lineJoin: 'bevel'
  31110. }
  31111. }
  31112. },
  31113. constructor: function(config) {
  31114. this.callParent([
  31115. config
  31116. ]);
  31117. this.topGradient = new Ext.draw.gradient.Linear({});
  31118. this.rightGradient = new Ext.draw.gradient.Linear({});
  31119. this.frontGradient = new Ext.draw.gradient.Linear({});
  31120. },
  31121. updatePlainBBox: function(plain) {
  31122. var attr = this.attr,
  31123. x = attr.x,
  31124. y = attr.y,
  31125. width = attr.width,
  31126. height = attr.height,
  31127. depth = attr.depth;
  31128. plain.x = x - width * 0.5;
  31129. plain.width = width + depth;
  31130. if (height > 0) {
  31131. plain.y = y;
  31132. plain.height = height + depth;
  31133. } else {
  31134. plain.y = y + depth;
  31135. plain.height = height - depth;
  31136. }
  31137. },
  31138. render: function(surface, ctx) {
  31139. var me = this,
  31140. attr = me.attr,
  31141. center = attr.x,
  31142. top = attr.y,
  31143. bottom = top + attr.height,
  31144. isNegative = top < bottom,
  31145. halfWidth = attr.width * 0.5,
  31146. depth = attr.depth,
  31147. isHorizontal = attr.orientation === 'horizontal',
  31148. isTransparent = attr.globalAlpha < 1,
  31149. fillStyle = attr.fillStyle,
  31150. color = Ext.util.Color.create(fillStyle.isGradient ? fillStyle.getStops()[0].color : fillStyle),
  31151. saturationFactor = attr.saturationFactor,
  31152. brightnessFactor = attr.brightnessFactor,
  31153. colorSpread = attr.colorSpread,
  31154. hsv = color.getHSV(),
  31155. bbox = {},
  31156. roundX, roundY, temp;
  31157. if (!attr.showStroke) {
  31158. ctx.strokeStyle = Ext.util.Color.RGBA_NONE;
  31159. }
  31160. if (isNegative) {
  31161. temp = top;
  31162. top = bottom;
  31163. bottom = temp;
  31164. }
  31165. // Refresh gradients based on sprite's fillStyle and other attributes.
  31166. me.topGradient.setDegrees(isHorizontal ? 0 : 80);
  31167. me.topGradient.setStops([
  31168. {
  31169. offset: 0,
  31170. 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))
  31171. },
  31172. {
  31173. offset: 1,
  31174. 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))
  31175. }
  31176. ]);
  31177. me.rightGradient.setDegrees(isHorizontal ? 45 : 90);
  31178. me.rightGradient.setStops([
  31179. {
  31180. offset: 0,
  31181. 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))
  31182. },
  31183. {
  31184. offset: 1,
  31185. 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))
  31186. }
  31187. ]);
  31188. if (isHorizontal) {
  31189. // 0° angle looks like 90° angle because the chart is flipped
  31190. me.frontGradient.setDegrees(0);
  31191. } else {
  31192. me.frontGradient.setRadians(Math.atan2(top - bottom, halfWidth * 2));
  31193. }
  31194. me.frontGradient.setStops([
  31195. {
  31196. offset: 0,
  31197. 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))
  31198. },
  31199. {
  31200. offset: 1,
  31201. 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))
  31202. }
  31203. ]);
  31204. if (isTransparent || isNegative) {
  31205. // Bottom side.
  31206. ctx.beginPath();
  31207. ctx.moveTo(center - halfWidth, bottom);
  31208. ctx.lineTo(center - halfWidth + depth, bottom + depth);
  31209. ctx.lineTo(center + halfWidth + depth, bottom + depth);
  31210. ctx.lineTo(center + halfWidth, bottom);
  31211. ctx.closePath();
  31212. bbox.x = center - halfWidth;
  31213. bbox.y = top;
  31214. bbox.width = halfWidth + depth;
  31215. bbox.height = depth;
  31216. // eslint-disable-next-line max-len
  31217. ctx.fillStyle = (isHorizontal ? me.rightGradient : me.topGradient).generateGradient(ctx, bbox);
  31218. ctx.fillStroke(attr);
  31219. }
  31220. if (isTransparent) {
  31221. // Left side.
  31222. ctx.beginPath();
  31223. ctx.moveTo(center - halfWidth, top);
  31224. ctx.lineTo(center - halfWidth + depth, top + depth);
  31225. ctx.lineTo(center - halfWidth + depth, bottom + depth);
  31226. ctx.lineTo(center - halfWidth, bottom);
  31227. ctx.closePath();
  31228. bbox.x = center + halfWidth;
  31229. bbox.y = bottom;
  31230. bbox.width = depth;
  31231. bbox.height = top + depth - bottom;
  31232. // eslint-disable-next-line max-len
  31233. ctx.fillStyle = (isHorizontal ? me.topGradient : me.rightGradient).generateGradient(ctx, bbox);
  31234. ctx.fillStroke(attr);
  31235. }
  31236. // Top side.
  31237. roundY = surface.roundPixel(top);
  31238. ctx.beginPath();
  31239. ctx.moveTo(center - halfWidth, roundY);
  31240. ctx.lineTo(center - halfWidth + depth, top + depth);
  31241. ctx.lineTo(center + halfWidth + depth, top + depth);
  31242. ctx.lineTo(center + halfWidth, roundY);
  31243. ctx.closePath();
  31244. bbox.x = center - halfWidth;
  31245. bbox.y = top;
  31246. bbox.width = halfWidth + depth;
  31247. bbox.height = depth;
  31248. // eslint-disable-next-line max-len
  31249. ctx.fillStyle = (isHorizontal ? me.rightGradient : me.topGradient).generateGradient(ctx, bbox);
  31250. ctx.fillStroke(attr);
  31251. // Right side.
  31252. roundX = surface.roundPixel(center + halfWidth);
  31253. ctx.beginPath();
  31254. ctx.moveTo(roundX, surface.roundPixel(top));
  31255. ctx.lineTo(center + halfWidth + depth, top + depth);
  31256. ctx.lineTo(center + halfWidth + depth, bottom + depth);
  31257. ctx.lineTo(roundX, bottom);
  31258. ctx.closePath();
  31259. bbox.x = center + halfWidth;
  31260. bbox.y = bottom;
  31261. bbox.width = depth;
  31262. bbox.height = top + depth - bottom;
  31263. // eslint-disable-next-line max-len
  31264. ctx.fillStyle = (isHorizontal ? me.topGradient : me.rightGradient).generateGradient(ctx, bbox);
  31265. ctx.fillStroke(attr);
  31266. // Front side.
  31267. roundX = surface.roundPixel(center + halfWidth);
  31268. roundY = surface.roundPixel(top);
  31269. ctx.beginPath();
  31270. ctx.moveTo(center - halfWidth, bottom);
  31271. ctx.lineTo(center - halfWidth, roundY);
  31272. ctx.lineTo(roundX, roundY);
  31273. ctx.lineTo(roundX, bottom);
  31274. ctx.closePath();
  31275. bbox.x = center - halfWidth;
  31276. bbox.y = bottom;
  31277. bbox.width = halfWidth * 2;
  31278. bbox.height = top - bottom;
  31279. ctx.fillStyle = me.frontGradient.generateGradient(ctx, bbox);
  31280. ctx.fillStroke(attr);
  31281. }
  31282. });
  31283. /**
  31284. * @class Ext.chart.series.Bar3D
  31285. * @extends Ext.chart.series.Bar
  31286. *
  31287. * Creates a 3D Bar or 3D Column Chart (depending on the value of the
  31288. * {@link Ext.chart.CartesianChart#flipXY flipXY} config).
  31289. *
  31290. * Note: 'bar3d' series is meant to be used with the
  31291. * {@link Ext.chart.axis.Category 'category3d'} axis as its x-axis.
  31292. *
  31293. * @example
  31294. * Ext.create({
  31295. * xtype: 'cartesian',
  31296. * renderTo: Ext.getBody(),
  31297. * width: 600,
  31298. * height: 400,
  31299. * innerPadding: '0 10 0 10',
  31300. * store: {
  31301. * fields: ['name', 'apples', 'oranges'],
  31302. * data: [{
  31303. * name: 'Eric',
  31304. * apples: 10,
  31305. * oranges: 3
  31306. * }, {
  31307. * name: 'Mary',
  31308. * apples: 7,
  31309. * oranges: 2
  31310. * }, {
  31311. * name: 'John',
  31312. * apples: 5,
  31313. * oranges: 2
  31314. * }, {
  31315. * name: 'Bob',
  31316. * apples: 2,
  31317. * oranges: 3
  31318. * }, {
  31319. * name: 'Joe',
  31320. * apples: 19,
  31321. * oranges: 1
  31322. * }, {
  31323. * name: 'Macy',
  31324. * apples: 13,
  31325. * oranges: 4
  31326. * }]
  31327. * },
  31328. * axes: [{
  31329. * type: 'numeric3d',
  31330. * position: 'left',
  31331. * fields: ['apples', 'oranges'],
  31332. * title: {
  31333. * text: 'Inventory',
  31334. * fontSize: 15
  31335. * },
  31336. * grid: {
  31337. * odd: {
  31338. * fillStyle: 'rgba(255, 255, 255, 0.06)'
  31339. * },
  31340. * even: {
  31341. * fillStyle: 'rgba(0, 0, 0, 0.03)'
  31342. * }
  31343. * }
  31344. * }, {
  31345. * type: 'category3d',
  31346. * position: 'bottom',
  31347. * title: {
  31348. * text: 'People',
  31349. * fontSize: 15
  31350. * },
  31351. * fields: 'name'
  31352. * }],
  31353. * series: {
  31354. * type: 'bar3d',
  31355. * xField: 'name',
  31356. * yField: ['apples', 'oranges']
  31357. * }
  31358. * });
  31359. */
  31360. Ext.define('Ext.chart.series.Bar3D', {
  31361. extend: 'Ext.chart.series.Bar',
  31362. requires: [
  31363. 'Ext.chart.series.sprite.Bar3D',
  31364. 'Ext.chart.sprite.Bar3D'
  31365. ],
  31366. alias: 'series.bar3d',
  31367. type: 'bar3d',
  31368. seriesType: 'bar3dSeries',
  31369. is3D: true,
  31370. config: {
  31371. itemInstancing: {
  31372. type: 'bar3d',
  31373. animation: {
  31374. customDurations: {
  31375. x: 0,
  31376. y: 0,
  31377. width: 0,
  31378. height: 0,
  31379. depth: 0
  31380. }
  31381. }
  31382. },
  31383. highlightCfg: {
  31384. opacity: 0.8
  31385. }
  31386. },
  31387. /**
  31388. * For 3D series, it's quite the opposite. It would be extremely odd,
  31389. * if top segments were rendered as if they were under the bottom ones.
  31390. */
  31391. reversedSpriteZOrder: false,
  31392. updateXAxis: function(xAxis, oldXAxis) {
  31393. //<debug>
  31394. if (xAxis.type !== 'category3d') {
  31395. Ext.raise("'bar3d' series should be used with a 'category3d' axis." + " Please refer to the 'bar3d' series docs.");
  31396. }
  31397. //</debug>
  31398. this.callParent([
  31399. xAxis,
  31400. oldXAxis
  31401. ]);
  31402. },
  31403. getDepth: function() {
  31404. var sprite = this.getSprites()[0];
  31405. return sprite ? (sprite.depth || 0) : 0;
  31406. }
  31407. });
  31408. /**
  31409. * BoxPlot series sprite that manages {@link Ext.chart.sprite.BoxPlot} instances.
  31410. */
  31411. Ext.define('Ext.chart.series.sprite.BoxPlot', {
  31412. alias: 'sprite.boxplotSeries',
  31413. extend: 'Ext.chart.series.sprite.Cartesian',
  31414. inheritableStatics: {
  31415. def: {
  31416. processors: {
  31417. /**
  31418. * @cfg {Number[]} [dataLow=null] Array of coordinated minimum values.
  31419. */
  31420. dataLow: 'data',
  31421. /**
  31422. * @cfg {Number[]} [dataQ1=null] Array of coordinated 1-st quartile values.
  31423. */
  31424. dataQ1: 'data',
  31425. /**
  31426. * @cfg {Number[]} [dataQ3=null] Array of coordinated 3-rd quartile values.
  31427. */
  31428. dataQ3: 'data',
  31429. /**
  31430. * @cfg {Number[]} [dataHigh=null] Array of coordinated maximum values.
  31431. */
  31432. dataHigh: 'data',
  31433. /**
  31434. * @cfg {Number} [minBoxWidth=2] The minimum box width.
  31435. */
  31436. minBoxWidth: 'number',
  31437. /**
  31438. * @cfg {Number} [maxBoxWidth=20] The maximum box width.
  31439. */
  31440. maxBoxWidth: 'number',
  31441. /**
  31442. * @cfg {Number} [minGapWidth=5] The minimum gap between boxes.
  31443. */
  31444. minGapWidth: 'number'
  31445. },
  31446. aliases: {
  31447. /**
  31448. * The `dataMedian` attribute can be used to set the value of
  31449. * the `dataY` attribute. E.g.:
  31450. *
  31451. * sprite.setAttributes({
  31452. * dataMedian: [...]
  31453. * });
  31454. *
  31455. * To fetch the value of the attribute one has to use
  31456. *
  31457. * sprite.attr.dataY // array of coordinated median values
  31458. *
  31459. * and not
  31460. *
  31461. * sprite.attr.dataMedian // WRONG!
  31462. *
  31463. * `dataY` attribute is defined by the `Ext.chart.series.sprite.Series`.
  31464. *
  31465. * @cfg {Number[]} [dataMedian=null] Array of coordinated median values.
  31466. */
  31467. dataMedian: 'dataY'
  31468. },
  31469. defaults: {
  31470. minBoxWidth: 2,
  31471. maxBoxWidth: 40,
  31472. minGapWidth: 5
  31473. }
  31474. }
  31475. },
  31476. renderClipped: function(surface, ctx, dataClipRect) {
  31477. if (this.cleanRedraw) {
  31478. return;
  31479. }
  31480. // eslint-disable-next-line vars-on-top
  31481. var me = this,
  31482. attr = me.attr,
  31483. series = me.getSeries(),
  31484. renderer = attr.renderer,
  31485. rendererData = {
  31486. store: me.getStore()
  31487. },
  31488. itemCfg = {},
  31489. dataX = attr.dataX,
  31490. dataLow = attr.dataLow,
  31491. dataQ1 = attr.dataQ1,
  31492. dataMedian = attr.dataY,
  31493. dataQ3 = attr.dataQ3,
  31494. dataHigh = attr.dataHigh,
  31495. min = Math.min(dataClipRect[0], dataClipRect[2]),
  31496. max = Math.max(dataClipRect[0], dataClipRect[2]),
  31497. start = Math.max(0, Math.floor(min)),
  31498. end = Math.min(dataX.length - 1, Math.ceil(max)),
  31499. // surfaceMatrix = me.surfaceMatrix,
  31500. matrix = attr.matrix,
  31501. xx = matrix.elements[0],
  31502. // horizontal scaling can be < 0, if RTL
  31503. yy = matrix.elements[3],
  31504. dx = matrix.elements[4],
  31505. dy = matrix.elements[5],
  31506. // `xx` essentially represents the distance between data points in surface coordinates.
  31507. maxBoxWidth = Math.abs(xx) - attr.minGapWidth,
  31508. minBoxWidth = Math.min(maxBoxWidth, attr.maxBoxWidth),
  31509. boxWidth = Math.round(Math.max(attr.minBoxWidth, minBoxWidth)),
  31510. x, low, q1, median, q3, high, rendererParams, changes, i;
  31511. if (renderer) {
  31512. rendererParams = [
  31513. me,
  31514. itemCfg,
  31515. rendererData
  31516. ];
  31517. }
  31518. for (i = start; i <= end; i++) {
  31519. x = dataX[i] * xx + dx;
  31520. low = dataLow[i] * yy + dy;
  31521. q1 = dataQ1[i] * yy + dy;
  31522. median = dataMedian[i] * yy + dy;
  31523. q3 = dataQ3[i] * yy + dy;
  31524. high = dataHigh[i] * yy + dy;
  31525. // --- Draw Box ---
  31526. // Reuse 'itemCfg' object and 'rendererParams' arrays for better performance.
  31527. itemCfg.x = x;
  31528. itemCfg.low = low;
  31529. itemCfg.q1 = q1;
  31530. itemCfg.median = median;
  31531. itemCfg.q3 = q3;
  31532. itemCfg.high = high;
  31533. itemCfg.boxWidth = boxWidth;
  31534. if (renderer) {
  31535. rendererParams[3] = i;
  31536. changes = Ext.callback(renderer, null, rendererParams, 0, series);
  31537. Ext.apply(itemCfg, changes);
  31538. }
  31539. me.putMarker('items', itemCfg, i, !renderer);
  31540. }
  31541. }
  31542. });
  31543. /**
  31544. * A sprite that represents an individual box with whiskers.
  31545. * This sprite is meant to be managed by the {@link Ext.chart.series.sprite.BoxPlot}
  31546. * {@link Ext.chart.MarkerHolder MarkerHolder}, but can also be used independently:
  31547. *
  31548. * @example
  31549. * new Ext.draw.Container({
  31550. * width: 100,
  31551. * height: 100,
  31552. * renderTo: Ext.getBody(),
  31553. * sprites: [{
  31554. * type: 'boxplot',
  31555. * translationX: 50,
  31556. * translationY: 50
  31557. * }]
  31558. * });
  31559. *
  31560. * IMPORTANT: the attributes that represent y-coordinates are in screen coordinates,
  31561. * just like with any other sprite. For this particular sprite this means that, if 'low'
  31562. * and 'high' attributes are 10 and 90, then the minimium whisker is rendered at the top
  31563. * of a draw container {@link Ext.draw.Surface surface} at y = 10, and the maximum whisker
  31564. * is rendered at the bottom at y = 90. But because the series surface is flipped vertically
  31565. * in cartesian charts, this means that there minimum is rendered at the bottom and maximum
  31566. * at the top, just as one would expect.
  31567. */
  31568. Ext.define('Ext.chart.sprite.BoxPlot', {
  31569. extend: 'Ext.draw.sprite.Sprite',
  31570. alias: 'sprite.boxplot',
  31571. type: 'boxplot',
  31572. inheritableStatics: {
  31573. def: {
  31574. processors: {
  31575. /**
  31576. * @cfg {Number} [x=0] The coordinate of the horizontal center of a boxplot.
  31577. */
  31578. x: 'number',
  31579. /**
  31580. * @cfg {Number} [low=-20] The y-coordinate of the whisker that represents
  31581. * the minimum.
  31582. */
  31583. low: 'number',
  31584. /**
  31585. * @cfg {Number} [q1=-10] The y-coordinate of the box edge that represents
  31586. * the 1-st quartile.
  31587. */
  31588. q1: 'number',
  31589. /**
  31590. * @cfg {Number} [median=0] The y-coordinate of the line that represents the median.
  31591. */
  31592. median: 'number',
  31593. /**
  31594. * @cfg {Number} [q3=10] The y-coordinate of the box edge that represents
  31595. * the 3-rd quartile.
  31596. */
  31597. q3: 'number',
  31598. /**
  31599. * @cfg {Number} [high=20] The y-coordinate of the whisker that represents
  31600. * the maximum.
  31601. */
  31602. high: 'number',
  31603. /**
  31604. * @cfg {Number} [boxWidth=12] The width of the box in pixels.
  31605. */
  31606. boxWidth: 'number',
  31607. /**
  31608. * @cfg {Number} [whiskerWidth=0.5] The length of the lines at the ends
  31609. * of the whiskers, as a ratio of `boxWidth`.
  31610. */
  31611. whiskerWidth: 'number',
  31612. /**
  31613. * @cfg {Boolean} [crisp=true] Whether to snap the rendered lines to the pixel grid
  31614. * of not. Generally, it's best to have this set to `true` (which is the default)
  31615. * for pixel perfect results (especially on non-HiDPI displays), but for boxplots
  31616. * with small `boxWidth` visible artifacts caused by pixel grid snapping may become
  31617. * noticeable, and setting this to `false` can be a remedy at the expense
  31618. * of clarity.
  31619. */
  31620. crisp: 'bool'
  31621. },
  31622. triggers: {
  31623. x: 'bbox',
  31624. low: 'bbox',
  31625. high: 'bbox',
  31626. boxWidth: 'bbox',
  31627. whiskerWidth: 'bbox',
  31628. crisp: 'bbox'
  31629. },
  31630. defaults: {
  31631. x: 0,
  31632. low: -20,
  31633. q1: -10,
  31634. median: 0,
  31635. q3: 10,
  31636. high: 20,
  31637. boxWidth: 12,
  31638. whiskerWidth: 0.5,
  31639. crisp: true,
  31640. fillStyle: '#ccc',
  31641. strokeStyle: '#000'
  31642. }
  31643. }
  31644. },
  31645. updatePlainBBox: function(plain) {
  31646. var me = this,
  31647. attr = me.attr,
  31648. halfLineWidth = attr.lineWidth / 2,
  31649. x = attr.x - attr.boxWidth / 2 - halfLineWidth,
  31650. y = attr.high - halfLineWidth,
  31651. width = attr.boxWidth + attr.lineWidth,
  31652. height = attr.low - attr.high + attr.lineWidth;
  31653. plain.x = x;
  31654. plain.y = y;
  31655. plain.width = width;
  31656. plain.height = height;
  31657. },
  31658. render: function(surface, ctx) {
  31659. var me = this,
  31660. attr = me.attr;
  31661. attr.matrix.toContext(ctx);
  31662. // enable sprite transformations
  31663. if (attr.crisp) {
  31664. me.crispRender(surface, ctx);
  31665. } else {
  31666. me.softRender(surface, ctx);
  31667. }
  31668. //<debug>
  31669. // eslint-disable-next-line vars-on-top, one-var
  31670. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  31671. if (debug) {
  31672. // This assumes no part of the sprite is rendered after this call.
  31673. // If it is, we need to re-apply transformations.
  31674. // But the bounding box should always be rendered as is, untransformed.
  31675. this.attr.inverseMatrix.toContext(ctx);
  31676. if (debug.bbox) {
  31677. this.renderBBox(surface, ctx);
  31678. }
  31679. }
  31680. },
  31681. //</debug>
  31682. /**
  31683. * @private
  31684. * Renders a single box with whiskers.
  31685. * Changes to this method have to be reflected in the {@link #crispRender} as well.
  31686. * @param surface
  31687. * @param ctx
  31688. */
  31689. softRender: function(surface, ctx) {
  31690. var me = this,
  31691. attr = me.attr,
  31692. x = attr.x,
  31693. low = attr.low,
  31694. q1 = attr.q1,
  31695. median = attr.median,
  31696. q3 = attr.q3,
  31697. high = attr.high,
  31698. halfBoxWidth = attr.boxWidth / 2,
  31699. halfWhiskerWidth = attr.boxWidth * attr.whiskerWidth / 2,
  31700. dash = ctx.getLineDash();
  31701. ctx.setLineDash([]);
  31702. // Only stem can be dashed.
  31703. // Box.
  31704. ctx.beginPath();
  31705. ctx.moveTo(x - halfBoxWidth, q3);
  31706. ctx.lineTo(x + halfBoxWidth, q3);
  31707. ctx.lineTo(x + halfBoxWidth, q1);
  31708. ctx.lineTo(x - halfBoxWidth, q1);
  31709. ctx.closePath();
  31710. ctx.fillStroke(attr, true);
  31711. // Stem.
  31712. ctx.setLineDash(dash);
  31713. ctx.beginPath();
  31714. ctx.moveTo(x, q3);
  31715. ctx.lineTo(x, high);
  31716. ctx.moveTo(x, q1);
  31717. ctx.lineTo(x, low);
  31718. ctx.stroke();
  31719. ctx.setLineDash([]);
  31720. // Whiskers.
  31721. ctx.beginPath();
  31722. ctx.moveTo(x - halfWhiskerWidth, low);
  31723. ctx.lineTo(x + halfWhiskerWidth, low);
  31724. ctx.moveTo(x - halfBoxWidth, median);
  31725. ctx.lineTo(x + halfBoxWidth, median);
  31726. ctx.moveTo(x - halfWhiskerWidth, high);
  31727. ctx.lineTo(x + halfWhiskerWidth, high);
  31728. ctx.stroke();
  31729. },
  31730. alignLine: function(x, lineWidth) {
  31731. lineWidth = lineWidth || this.attr.lineWidth;
  31732. x = Math.round(x);
  31733. if (lineWidth % 2 === 1) {
  31734. x -= 0.5;
  31735. }
  31736. return x;
  31737. },
  31738. /**
  31739. * @private
  31740. * Renders a pixel-perfect single box with whiskers by aligning to the pixel grid.
  31741. * Changes to this method have to be reflected in the {@link #softRender} as well.
  31742. *
  31743. * Note: crisp image is only guaranteed when `attr.lineWidth` is a whole number.
  31744. * @param surface
  31745. * @param ctx
  31746. */
  31747. crispRender: function(surface, ctx) {
  31748. var me = this,
  31749. attr = me.attr,
  31750. x = attr.x,
  31751. low = me.alignLine(attr.low),
  31752. q1 = me.alignLine(attr.q1),
  31753. median = me.alignLine(attr.median),
  31754. q3 = me.alignLine(attr.q3),
  31755. high = me.alignLine(attr.high),
  31756. halfBoxWidth = attr.boxWidth / 2,
  31757. halfWhiskerWidth = attr.boxWidth * attr.whiskerWidth / 2,
  31758. stemX = me.alignLine(x),
  31759. boxLeft = me.alignLine(x - halfBoxWidth),
  31760. boxRight = me.alignLine(x + halfBoxWidth),
  31761. whiskerLeft = stemX + Math.round(-halfWhiskerWidth),
  31762. whiskerRight = stemX + Math.round(halfWhiskerWidth),
  31763. dash = ctx.getLineDash();
  31764. ctx.setLineDash([]);
  31765. // Only stem can be dashed.
  31766. // Box.
  31767. ctx.beginPath();
  31768. ctx.moveTo(boxLeft, q3);
  31769. ctx.lineTo(boxRight, q3);
  31770. ctx.lineTo(boxRight, q1);
  31771. ctx.lineTo(boxLeft, q1);
  31772. ctx.closePath();
  31773. ctx.fillStroke(attr, true);
  31774. // Stem.
  31775. ctx.setLineDash(dash);
  31776. ctx.beginPath();
  31777. ctx.moveTo(stemX, q3);
  31778. ctx.lineTo(stemX, high);
  31779. ctx.moveTo(stemX, q1);
  31780. ctx.lineTo(stemX, low);
  31781. ctx.stroke();
  31782. ctx.setLineDash([]);
  31783. // Whiskers.
  31784. ctx.beginPath();
  31785. ctx.moveTo(whiskerLeft, low);
  31786. ctx.lineTo(whiskerRight, low);
  31787. ctx.moveTo(boxLeft, median);
  31788. ctx.lineTo(boxRight, median);
  31789. ctx.moveTo(whiskerLeft, high);
  31790. ctx.lineTo(whiskerRight, high);
  31791. ctx.stroke();
  31792. }
  31793. });
  31794. /**
  31795. * A box plot chart is a useful tool for visializing data distribution within datasets.
  31796. * For example, salary ranges for a set of occupations, or life expectancy for a set
  31797. * of countries. A single box with whiskers displays the following values for a dataset:
  31798. *
  31799. * * minimum
  31800. * * lower quartile (Q1)
  31801. * * median (Q2)
  31802. * * higher quartile (Q3)
  31803. * * maximum
  31804. *
  31805. * For example:
  31806. *
  31807. * @example
  31808. * Ext.create({
  31809. * xtype: 'cartesian',
  31810. * width: 400,
  31811. * height: 400,
  31812. * renderTo: Ext.getBody(),
  31813. * insetPadding: '20 20 10 10',
  31814. * store: {
  31815. * data: [{
  31816. * category: 'Engineer IV',
  31817. * low: 110, q1: 130, median: 175, q3: 200, high: 225
  31818. * }, {
  31819. * category: 'Market',
  31820. * low: 75, q1: 125, median: 210, q3: 230, high: 255
  31821. * }]
  31822. * },
  31823. * axes: [
  31824. * {
  31825. * type: 'numeric',
  31826. * position: 'left',
  31827. * renderer: function (axis, text) {
  31828. * return '$' + text + ' K'
  31829. * }
  31830. * },
  31831. * {
  31832. * type: 'category',
  31833. * position: 'bottom'
  31834. * }
  31835. * ],
  31836. * series: {
  31837. * type: 'boxplot',
  31838. * xField: 'category',
  31839. * style: {
  31840. * maxBoxWidth: 50,
  31841. * lineWidth: 2
  31842. * }
  31843. * }
  31844. * });
  31845. *
  31846. */
  31847. Ext.define('Ext.chart.series.BoxPlot', {
  31848. extend: 'Ext.chart.series.Cartesian',
  31849. alias: 'series.boxplot',
  31850. type: 'boxplot',
  31851. seriesType: 'boxplotSeries',
  31852. isBoxPlot: true,
  31853. requires: [
  31854. 'Ext.chart.series.sprite.BoxPlot',
  31855. 'Ext.chart.sprite.BoxPlot'
  31856. ],
  31857. config: {
  31858. itemInstancing: {
  31859. type: 'boxplot',
  31860. animation: {
  31861. // Setting the duration of these attributes to zero because
  31862. // the 'data' attributes of the series sprite (MarkerHolder)
  31863. // will be animated instead, and then changes applied to
  31864. // the attributes of 'boxplot' instances instantly.
  31865. customDurations: {
  31866. x: 0,
  31867. low: 0,
  31868. q1: 0,
  31869. median: 0,
  31870. q3: 0,
  31871. high: 0
  31872. }
  31873. }
  31874. },
  31875. /**
  31876. * @cfg {String} [lowField='low']
  31877. * The name of the store record field that represents the smallest value of a dataset.
  31878. */
  31879. lowField: 'low',
  31880. /**
  31881. * @cfg {String} [q1Field='q1']
  31882. * The name of the store record field that represents the lower (1-st) quartile
  31883. * value of a dataset.
  31884. */
  31885. q1Field: 'q1',
  31886. /**
  31887. * @cfg {String} [medianField='median']
  31888. * The name of the store record field that represents the median of a dataset.
  31889. */
  31890. medianField: 'median',
  31891. /**
  31892. * @cfg {String} [q3Field='q3']
  31893. * The name of the store record field that represents the upper (3-rd) quartile
  31894. * value of a dataset.
  31895. */
  31896. q3Field: 'q3',
  31897. /**
  31898. * @cfg {String} [highField='high']
  31899. * The name of the store record field that represents the largest value of a dataset.
  31900. */
  31901. highField: 'high'
  31902. },
  31903. fieldCategoryY: [
  31904. 'Low',
  31905. 'Q1',
  31906. 'Median',
  31907. 'Q3',
  31908. 'High'
  31909. ],
  31910. updateXAxis: function(xAxis) {
  31911. xAxis.setExpandRangeBy(0.5);
  31912. this.callParent(arguments);
  31913. }
  31914. });
  31915. /**
  31916. * Limited cache is a size limited cache container that stores limited number of objects.
  31917. *
  31918. * When {@link #get} is called, the container will try to find the object in the list.
  31919. * If failed it will call the {@link #feeder} to create that object. If there are too many
  31920. * objects in the container, the old ones are removed.
  31921. *
  31922. * __Note:__ This is not using a Least Recently Used policy due to simplicity and performance
  31923. * consideration.
  31924. * @private
  31925. */
  31926. Ext.define('Ext.draw.LimitedCache', {
  31927. config: {
  31928. /**
  31929. * @cfg {Number}
  31930. * The amount limit of the cache.
  31931. */
  31932. limit: 40,
  31933. /**
  31934. * @cfg {Function}
  31935. * Function that generates the object when look-up failed.
  31936. * @return {Number}
  31937. */
  31938. feeder: function() {
  31939. return 0;
  31940. },
  31941. /**
  31942. * @cfg {Object}
  31943. * The scope for {@link #feeder}
  31944. */
  31945. scope: null
  31946. },
  31947. cache: null,
  31948. constructor: function(config) {
  31949. this.cache = {};
  31950. this.cache.list = [];
  31951. this.cache.tail = 0;
  31952. this.initConfig(config);
  31953. },
  31954. /**
  31955. * Get a cached object.
  31956. * @param {String} id
  31957. * @return {Object}
  31958. */
  31959. get: function(id) {
  31960. // TODO: Implement cache hit optimization
  31961. var cache = this.cache,
  31962. limit = this.getLimit(),
  31963. feeder = this.getFeeder(),
  31964. scope = this.getScope() || this;
  31965. if (cache[id]) {
  31966. return cache[id].value;
  31967. }
  31968. if (cache.list[cache.tail]) {
  31969. delete cache[cache.list[cache.tail].cacheId];
  31970. }
  31971. cache[id] = cache.list[cache.tail] = {
  31972. value: feeder.apply(scope, Array.prototype.slice.call(arguments, 1)),
  31973. cacheId: id
  31974. };
  31975. cache.tail++;
  31976. if (cache.tail === limit) {
  31977. cache.tail = 0;
  31978. }
  31979. return cache[id].value;
  31980. },
  31981. /**
  31982. * Clear all the objects.
  31983. */
  31984. clear: function() {
  31985. this.cache = {};
  31986. this.cache.list = [];
  31987. this.cache.tail = 0;
  31988. }
  31989. });
  31990. /**
  31991. * This class we summarize the data and returns it when required.
  31992. */
  31993. Ext.define("Ext.draw.SegmentTree", {
  31994. config: {
  31995. strategy: "double"
  31996. },
  31997. /**
  31998. * @private
  31999. * @param {Object} result
  32000. * @param {Number} last
  32001. * @param {Number} dataX
  32002. * @param {Number} dataOpen
  32003. * @param {Number} dataHigh
  32004. * @param {Number} dataLow
  32005. * @param {Number} dataClose
  32006. */
  32007. time: function(result, last, dataX, dataOpen, dataHigh, dataLow, dataClose) {
  32008. var start = 0,
  32009. lastOffset, lastOffsetEnd,
  32010. minimum = new Date(dataX[result.startIdx[0]]),
  32011. maximum = new Date(dataX[result.endIdx[last - 1]]),
  32012. extDate = Ext.Date,
  32013. units = [
  32014. [
  32015. extDate.MILLI,
  32016. 1,
  32017. 'ms1',
  32018. null
  32019. ],
  32020. [
  32021. extDate.MILLI,
  32022. 2,
  32023. 'ms2',
  32024. 'ms1'
  32025. ],
  32026. [
  32027. extDate.MILLI,
  32028. 5,
  32029. 'ms5',
  32030. 'ms1'
  32031. ],
  32032. [
  32033. extDate.MILLI,
  32034. 10,
  32035. 'ms10',
  32036. 'ms5'
  32037. ],
  32038. [
  32039. extDate.MILLI,
  32040. 50,
  32041. 'ms50',
  32042. 'ms10'
  32043. ],
  32044. [
  32045. extDate.MILLI,
  32046. 100,
  32047. 'ms100',
  32048. 'ms50'
  32049. ],
  32050. [
  32051. extDate.MILLI,
  32052. 500,
  32053. 'ms500',
  32054. 'ms100'
  32055. ],
  32056. [
  32057. extDate.SECOND,
  32058. 1,
  32059. 's1',
  32060. 'ms500'
  32061. ],
  32062. [
  32063. extDate.SECOND,
  32064. 10,
  32065. 's10',
  32066. 's1'
  32067. ],
  32068. [
  32069. extDate.SECOND,
  32070. 30,
  32071. 's30',
  32072. 's10'
  32073. ],
  32074. [
  32075. extDate.MINUTE,
  32076. 1,
  32077. 'mi1',
  32078. 's10'
  32079. ],
  32080. [
  32081. extDate.MINUTE,
  32082. 5,
  32083. 'mi5',
  32084. 'mi1'
  32085. ],
  32086. [
  32087. extDate.MINUTE,
  32088. 10,
  32089. 'mi10',
  32090. 'mi5'
  32091. ],
  32092. [
  32093. extDate.MINUTE,
  32094. 30,
  32095. 'mi30',
  32096. 'mi10'
  32097. ],
  32098. [
  32099. extDate.HOUR,
  32100. 1,
  32101. 'h1',
  32102. 'mi30'
  32103. ],
  32104. [
  32105. extDate.HOUR,
  32106. 6,
  32107. 'h6',
  32108. 'h1'
  32109. ],
  32110. [
  32111. extDate.HOUR,
  32112. 12,
  32113. 'h12',
  32114. 'h6'
  32115. ],
  32116. [
  32117. extDate.DAY,
  32118. 1,
  32119. 'd1',
  32120. 'h12'
  32121. ],
  32122. [
  32123. extDate.DAY,
  32124. 7,
  32125. 'd7',
  32126. 'd1'
  32127. ],
  32128. [
  32129. extDate.MONTH,
  32130. 1,
  32131. 'mo1',
  32132. 'd1'
  32133. ],
  32134. [
  32135. extDate.MONTH,
  32136. 3,
  32137. 'mo3',
  32138. 'mo1'
  32139. ],
  32140. [
  32141. extDate.MONTH,
  32142. 6,
  32143. 'mo6',
  32144. 'mo3'
  32145. ],
  32146. [
  32147. extDate.YEAR,
  32148. 1,
  32149. 'y1',
  32150. 'mo3'
  32151. ],
  32152. [
  32153. extDate.YEAR,
  32154. 5,
  32155. 'y5',
  32156. 'y1'
  32157. ],
  32158. [
  32159. extDate.YEAR,
  32160. 10,
  32161. 'y10',
  32162. 'y5'
  32163. ],
  32164. [
  32165. extDate.YEAR,
  32166. 100,
  32167. 'y100',
  32168. 'y10'
  32169. ]
  32170. ],
  32171. unitIdx, currentUnit,
  32172. plainStart = start,
  32173. plainEnd = last,
  32174. startIdxs = result.startIdx,
  32175. endIdxs = result.endIdx,
  32176. minIdxs = result.minIdx,
  32177. maxIdxs = result.maxIdx,
  32178. opens = result.open,
  32179. closes = result.close,
  32180. minXs = result.minX,
  32181. minYs = result.minY,
  32182. maxXs = result.maxX,
  32183. maxYs = result.maxY,
  32184. i, current;
  32185. for (unitIdx = 0; last > start + 1 && unitIdx < units.length; unitIdx++) {
  32186. minimum = new Date(dataX[startIdxs[0]]);
  32187. currentUnit = units[unitIdx];
  32188. minimum = extDate.align(minimum, currentUnit[0], currentUnit[1]);
  32189. // eslint-disable-next-line max-len
  32190. if (extDate.diff(minimum, maximum, currentUnit[0]) > dataX.length * 2 * currentUnit[1]) {
  32191. continue;
  32192. }
  32193. if (currentUnit[3] && result.map['time_' + currentUnit[3]]) {
  32194. lastOffset = result.map['time_' + currentUnit[3]][0];
  32195. lastOffsetEnd = result.map['time_' + currentUnit[3]][1];
  32196. } else {
  32197. lastOffset = plainStart;
  32198. lastOffsetEnd = plainEnd;
  32199. }
  32200. start = last;
  32201. current = minimum;
  32202. startIdxs[last] = startIdxs[lastOffset];
  32203. endIdxs[last] = endIdxs[lastOffset];
  32204. minIdxs[last] = minIdxs[lastOffset];
  32205. maxIdxs[last] = maxIdxs[lastOffset];
  32206. opens[last] = opens[lastOffset];
  32207. closes[last] = closes[lastOffset];
  32208. minXs[last] = minXs[lastOffset];
  32209. minYs[last] = minYs[lastOffset];
  32210. maxXs[last] = maxXs[lastOffset];
  32211. maxYs[last] = maxYs[lastOffset];
  32212. current = Ext.Date.add(current, currentUnit[0], currentUnit[1]);
  32213. for (i = lastOffset + 1; i < lastOffsetEnd; i++) {
  32214. if (dataX[endIdxs[i]] < +current) {
  32215. endIdxs[last] = endIdxs[i];
  32216. closes[last] = closes[i];
  32217. if (maxYs[i] > maxYs[last]) {
  32218. maxYs[last] = maxYs[i];
  32219. maxXs[last] = maxXs[i];
  32220. maxIdxs[last] = maxIdxs[i];
  32221. }
  32222. if (minYs[i] < minYs[last]) {
  32223. minYs[last] = minYs[i];
  32224. minXs[last] = minXs[i];
  32225. minIdxs[last] = minIdxs[i];
  32226. }
  32227. } else {
  32228. last++;
  32229. startIdxs[last] = startIdxs[i];
  32230. endIdxs[last] = endIdxs[i];
  32231. minIdxs[last] = minIdxs[i];
  32232. maxIdxs[last] = maxIdxs[i];
  32233. opens[last] = opens[i];
  32234. closes[last] = closes[i];
  32235. minXs[last] = minXs[i];
  32236. minYs[last] = minYs[i];
  32237. maxXs[last] = maxXs[i];
  32238. maxYs[last] = maxYs[i];
  32239. current = Ext.Date.add(current, currentUnit[0], currentUnit[1]);
  32240. }
  32241. }
  32242. if (last > start) {
  32243. result.map['time_' + currentUnit[2]] = [
  32244. start,
  32245. last
  32246. ];
  32247. }
  32248. }
  32249. },
  32250. /**
  32251. * @private
  32252. * @param {Object} result
  32253. * @param {Number} position
  32254. * @param {Number} dataX
  32255. * @param {Number} dataOpen
  32256. * @param {Number} dataHigh
  32257. * @param {Number} dataLow
  32258. * @param {Number} dataClose
  32259. */
  32260. "double": function(result, position, dataX, dataOpen, dataHigh, dataLow, dataClose) {
  32261. var offset = 0,
  32262. lastOffset,
  32263. step = 1,
  32264. i, startIdx, endIdx, minIdx, maxIdx, open, close, minX, minY, maxX, maxY;
  32265. while (position > offset + 1) {
  32266. lastOffset = offset;
  32267. offset = position;
  32268. step += step;
  32269. for (i = lastOffset; i < offset; i += 2) {
  32270. if (i === offset - 1) {
  32271. startIdx = result.startIdx[i];
  32272. endIdx = result.endIdx[i];
  32273. minIdx = result.minIdx[i];
  32274. maxIdx = result.maxIdx[i];
  32275. open = result.open[i];
  32276. close = result.close[i];
  32277. minX = result.minX[i];
  32278. minY = result.minY[i];
  32279. maxX = result.maxX[i];
  32280. maxY = result.maxY[i];
  32281. } else {
  32282. startIdx = result.startIdx[i];
  32283. endIdx = result.endIdx[i + 1];
  32284. open = result.open[i];
  32285. close = result.close[i];
  32286. if (result.minY[i] <= result.minY[i + 1]) {
  32287. minIdx = result.minIdx[i];
  32288. minX = result.minX[i];
  32289. minY = result.minY[i];
  32290. } else {
  32291. minIdx = result.minIdx[i + 1];
  32292. minX = result.minX[i + 1];
  32293. minY = result.minY[i + 1];
  32294. }
  32295. if (result.maxY[i] >= result.maxY[i + 1]) {
  32296. maxIdx = result.maxIdx[i];
  32297. maxX = result.maxX[i];
  32298. maxY = result.maxY[i];
  32299. } else {
  32300. maxIdx = result.maxIdx[i + 1];
  32301. maxX = result.maxX[i + 1];
  32302. maxY = result.maxY[i + 1];
  32303. }
  32304. }
  32305. result.startIdx[position] = startIdx;
  32306. result.endIdx[position] = endIdx;
  32307. result.minIdx[position] = minIdx;
  32308. result.maxIdx[position] = maxIdx;
  32309. result.open[position] = open;
  32310. result.close[position] = close;
  32311. result.minX[position] = minX;
  32312. result.minY[position] = minY;
  32313. result.maxX[position] = maxX;
  32314. result.maxY[position] = maxY;
  32315. position++;
  32316. }
  32317. result.map['double_' + step] = [
  32318. offset,
  32319. position
  32320. ];
  32321. }
  32322. },
  32323. /**
  32324. * @method
  32325. * @private
  32326. */
  32327. none: Ext.emptyFn,
  32328. /**
  32329. * @private
  32330. *
  32331. * @param {Number} dataX
  32332. * @param {Number} dataOpen
  32333. * @param {Number} dataHigh
  32334. * @param {Number} dataLow
  32335. * @param {Number} dataClose
  32336. * @return {Object}
  32337. */
  32338. aggregateData: function(dataX, dataOpen, dataHigh, dataLow, dataClose) {
  32339. var length = dataX.length,
  32340. startIdx = [],
  32341. endIdx = [],
  32342. minIdx = [],
  32343. maxIdx = [],
  32344. open = [],
  32345. minX = [],
  32346. minY = [],
  32347. maxX = [],
  32348. maxY = [],
  32349. close = [],
  32350. result = {
  32351. startIdx: startIdx,
  32352. endIdx: endIdx,
  32353. minIdx: minIdx,
  32354. maxIdx: maxIdx,
  32355. open: open,
  32356. minX: minX,
  32357. minY: minY,
  32358. maxX: maxX,
  32359. maxY: maxY,
  32360. close: close
  32361. },
  32362. i;
  32363. for (i = 0; i < length; i++) {
  32364. startIdx[i] = i;
  32365. endIdx[i] = i;
  32366. minIdx[i] = i;
  32367. maxIdx[i] = i;
  32368. open[i] = dataOpen[i];
  32369. minX[i] = dataX[i];
  32370. minY[i] = dataLow[i];
  32371. maxX[i] = dataX[i];
  32372. maxY[i] = dataHigh[i];
  32373. close[i] = dataClose[i];
  32374. }
  32375. result.map = {
  32376. original: [
  32377. 0,
  32378. length
  32379. ]
  32380. };
  32381. if (length) {
  32382. this[this.getStrategy()](result, length, dataX, dataOpen, dataHigh, dataLow, dataClose);
  32383. }
  32384. return result;
  32385. },
  32386. /**
  32387. * @private
  32388. * @param {Object} items
  32389. * @param {Number} start
  32390. * @param {Number} end
  32391. * @param {Number} key
  32392. * @return {*}
  32393. */
  32394. binarySearchMin: function(items, start, end, key) {
  32395. var dx = this.dataX,
  32396. mid, val;
  32397. if (key <= dx[items.startIdx[0]]) {
  32398. return start;
  32399. }
  32400. if (key >= dx[items.startIdx[end - 1]]) {
  32401. return end - 1;
  32402. }
  32403. while (start + 1 < end) {
  32404. mid = (start + end) >> 1;
  32405. val = dx[items.startIdx[mid]];
  32406. if (val === key) {
  32407. return mid;
  32408. } else if (val < key) {
  32409. start = mid;
  32410. } else {
  32411. end = mid;
  32412. }
  32413. }
  32414. return start;
  32415. },
  32416. /**
  32417. * @private
  32418. * @param {Object} items
  32419. * @param {Number} start
  32420. * @param {Number} end
  32421. * @param {Number} key
  32422. * @return {*}
  32423. */
  32424. binarySearchMax: function(items, start, end, key) {
  32425. var dx = this.dataX,
  32426. mid, val;
  32427. if (key <= dx[items.endIdx[0]]) {
  32428. return start;
  32429. }
  32430. if (key >= dx[items.endIdx[end - 1]]) {
  32431. return end - 1;
  32432. }
  32433. while (start + 1 < end) {
  32434. mid = (start + end) >> 1;
  32435. val = dx[items.endIdx[mid]];
  32436. if (val === key) {
  32437. return mid;
  32438. } else if (val < key) {
  32439. start = mid;
  32440. } else {
  32441. end = mid;
  32442. }
  32443. }
  32444. return end;
  32445. },
  32446. constructor: function(config) {
  32447. this.initConfig(config);
  32448. },
  32449. /**
  32450. * Sets the data of the segment tree.
  32451. * @param {Number} dataX
  32452. * @param {Number} dataOpen
  32453. * @param {Number} dataHigh
  32454. * @param {Number} dataLow
  32455. * @param {Number} dataClose
  32456. */
  32457. setData: function(dataX, dataOpen, dataHigh, dataLow, dataClose) {
  32458. if (!dataHigh) {
  32459. dataClose = dataLow = dataHigh = dataOpen;
  32460. }
  32461. this.dataX = dataX;
  32462. this.dataOpen = dataOpen;
  32463. this.dataHigh = dataHigh;
  32464. this.dataLow = dataLow;
  32465. this.dataClose = dataClose;
  32466. if (dataX.length === dataHigh.length && dataX.length === dataLow.length) {
  32467. this.cache = this.aggregateData(dataX, dataOpen, dataHigh, dataLow, dataClose);
  32468. }
  32469. },
  32470. /**
  32471. * Returns the minimum range of data that fits the given range and step size.
  32472. *
  32473. * @param {Number} min
  32474. * @param {Number} max
  32475. * @param {Number} estStep
  32476. * @return {Object} The aggregation information.
  32477. * @return {Number} return.start
  32478. * @return {Number} return.end
  32479. * @return {Object} return.data The aggregated data
  32480. */
  32481. getAggregation: function(min, max, estStep) {
  32482. if (!this.cache) {
  32483. return null;
  32484. }
  32485. // eslint-disable-next-line vars-on-top
  32486. var minStep = Infinity,
  32487. range = this.dataX[this.dataX.length - 1] - this.dataX[0],
  32488. cacheMap = this.cache.map,
  32489. result = cacheMap.original,
  32490. name, positions, ln, step, minIdx, maxIdx;
  32491. for (name in cacheMap) {
  32492. positions = cacheMap[name];
  32493. ln = positions[1] - positions[0] - 1;
  32494. step = range / ln;
  32495. if (estStep <= step && step < minStep) {
  32496. result = positions;
  32497. minStep = step;
  32498. }
  32499. }
  32500. minIdx = Math.max(this.binarySearchMin(this.cache, result[0], result[1], min), result[0]);
  32501. maxIdx = Math.min(this.binarySearchMax(this.cache, result[0], result[1], max) + 1, result[1]);
  32502. return {
  32503. data: this.cache,
  32504. start: minIdx,
  32505. end: maxIdx
  32506. };
  32507. }
  32508. });
  32509. /**
  32510. *
  32511. */
  32512. Ext.define('Ext.chart.series.sprite.Aggregative', {
  32513. extend: 'Ext.chart.series.sprite.Cartesian',
  32514. requires: [
  32515. 'Ext.draw.LimitedCache',
  32516. 'Ext.draw.SegmentTree'
  32517. ],
  32518. inheritableStatics: {
  32519. def: {
  32520. processors: {
  32521. /**
  32522. * @cfg {Number[]} [dataHigh=null] Data items representing the high values
  32523. * of the aggregated data.
  32524. */
  32525. dataHigh: 'data',
  32526. /**
  32527. * @cfg {Number[]} [dataLow=null] Data items representing the low values
  32528. * of the aggregated data.
  32529. */
  32530. dataLow: 'data',
  32531. /**
  32532. * @cfg {Number[]} [dataClose=null] Data items representing the closing values
  32533. * of the aggregated data.
  32534. */
  32535. dataClose: 'data'
  32536. },
  32537. aliases: {
  32538. /**
  32539. * @cfg {Number[]} [dataOpen=null] Data items representing the opening values
  32540. * of the aggregated data.
  32541. */
  32542. dataOpen: 'dataY'
  32543. },
  32544. defaults: {
  32545. dataHigh: null,
  32546. dataLow: null,
  32547. dataClose: null
  32548. }
  32549. }
  32550. },
  32551. config: {
  32552. aggregator: {}
  32553. },
  32554. applyAggregator: function(aggregator, oldAggr) {
  32555. return Ext.factory(aggregator, Ext.draw.SegmentTree, oldAggr);
  32556. },
  32557. constructor: function() {
  32558. this.callParent(arguments);
  32559. },
  32560. processDataY: function() {
  32561. var me = this,
  32562. attr = me.attr,
  32563. high = attr.dataHigh,
  32564. low = attr.dataLow,
  32565. close = attr.dataClose,
  32566. open = attr.dataY,
  32567. aggregator;
  32568. me.callParent(arguments);
  32569. if (attr.dataX && open && open.length > 0) {
  32570. aggregator = me.getAggregator();
  32571. if (high) {
  32572. aggregator.setData(attr.dataX, attr.dataY, high, low, close);
  32573. } else {
  32574. aggregator.setData(attr.dataX, attr.dataY);
  32575. }
  32576. }
  32577. },
  32578. getGapWidth: function() {
  32579. return 1;
  32580. },
  32581. renderClipped: function(surface, ctx, dataClipRect, surfaceClipRect) {
  32582. var me = this,
  32583. min = Math.min(dataClipRect[0], dataClipRect[2]),
  32584. max = Math.max(dataClipRect[0], dataClipRect[2]),
  32585. aggregator = me.getAggregator(),
  32586. aggregates = aggregator && aggregator.getAggregation(min, max, (max - min) / surfaceClipRect[2] * me.getGapWidth());
  32587. if (aggregates) {
  32588. me.dataStart = aggregates.data.startIdx[aggregates.start];
  32589. me.dataEnd = aggregates.data.endIdx[aggregates.end - 1];
  32590. me.renderAggregates(aggregates.data, aggregates.start, aggregates.end, surface, ctx, dataClipRect, surfaceClipRect);
  32591. }
  32592. }
  32593. });
  32594. /**
  32595. * @class Ext.chart.series.sprite.CandleStick
  32596. * @extends Ext.chart.series.sprite.Aggregative
  32597. *
  32598. * CandleStick series sprite.
  32599. */
  32600. Ext.define('Ext.chart.series.sprite.CandleStick', {
  32601. alias: 'sprite.candlestickSeries',
  32602. extend: 'Ext.chart.series.sprite.Aggregative',
  32603. inheritableStatics: {
  32604. def: {
  32605. processors: {
  32606. raiseStyle: function(n, o) {
  32607. return Ext.merge({}, o || {}, n);
  32608. },
  32609. dropStyle: function(n, o) {
  32610. return Ext.merge({}, o || {}, n);
  32611. },
  32612. /**
  32613. * @cfg {Number} [barWidth=15] The bar width of the candles.
  32614. */
  32615. barWidth: 'number',
  32616. /**
  32617. * @cfg {Number} [padding=3] The amount of padding between candles.
  32618. */
  32619. padding: 'number',
  32620. /**
  32621. * @cfg {String} [ohlcType='candlestick'] Determines whether candlestick
  32622. * or ohlc is used.
  32623. */
  32624. ohlcType: 'enums(candlestick,ohlc)'
  32625. },
  32626. defaults: {
  32627. raiseStyle: {
  32628. strokeStyle: 'green',
  32629. fillStyle: 'green'
  32630. },
  32631. dropStyle: {
  32632. strokeStyle: 'red',
  32633. fillStyle: 'red'
  32634. },
  32635. barWidth: 15,
  32636. padding: 3,
  32637. lineJoin: 'miter',
  32638. miterLimit: 5,
  32639. ohlcType: 'candlestick'
  32640. },
  32641. triggers: {
  32642. raiseStyle: 'raiseStyle',
  32643. dropStyle: 'dropStyle'
  32644. },
  32645. updaters: {
  32646. raiseStyle: function() {
  32647. var me = this,
  32648. tpl = me.raiseTemplate;
  32649. if (tpl) {
  32650. tpl.setAttributes(me.attr.raiseStyle);
  32651. }
  32652. },
  32653. dropStyle: function() {
  32654. var me = this,
  32655. tpl = me.dropTemplate;
  32656. if (tpl) {
  32657. tpl.setAttributes(me.attr.dropStyle);
  32658. }
  32659. }
  32660. }
  32661. }
  32662. },
  32663. candlestick: function(ctx, open, high, low, close, mid, halfWidth) {
  32664. var minOC = Math.min(open, close),
  32665. maxOC = Math.max(open, close);
  32666. // lower stick
  32667. ctx.moveTo(mid, low);
  32668. ctx.lineTo(mid, minOC);
  32669. // body rect
  32670. ctx.moveTo(mid + halfWidth, maxOC);
  32671. ctx.lineTo(mid + halfWidth, minOC);
  32672. ctx.lineTo(mid - halfWidth, minOC);
  32673. ctx.lineTo(mid - halfWidth, maxOC);
  32674. ctx.closePath();
  32675. // upper stick
  32676. ctx.moveTo(mid, high);
  32677. ctx.lineTo(mid, maxOC);
  32678. },
  32679. ohlc: function(ctx, open, high, low, close, mid, halfWidth) {
  32680. ctx.moveTo(mid, high);
  32681. ctx.lineTo(mid, low);
  32682. ctx.moveTo(mid, open);
  32683. ctx.lineTo(mid - halfWidth, open);
  32684. ctx.moveTo(mid, close);
  32685. ctx.lineTo(mid + halfWidth, close);
  32686. },
  32687. constructor: function() {
  32688. var me = this,
  32689. Rect = Ext.draw.sprite.Rect;
  32690. me.callParent(arguments);
  32691. me.raiseTemplate = new Rect({
  32692. parent: me
  32693. });
  32694. me.dropTemplate = new Rect({
  32695. parent: me
  32696. });
  32697. },
  32698. getGapWidth: function() {
  32699. var attr = this.attr,
  32700. barWidth = attr.barWidth,
  32701. padding = attr.padding;
  32702. return barWidth + padding;
  32703. },
  32704. renderAggregates: function(aggregates, start, end, surface, ctx, clip) {
  32705. var me = this,
  32706. attr = me.attr,
  32707. ohlcType = attr.ohlcType,
  32708. series = me.getSeries(),
  32709. matrix = attr.matrix,
  32710. xx = matrix.getXX(),
  32711. yy = matrix.getYY(),
  32712. dx = matrix.getDX(),
  32713. dy = matrix.getDY(),
  32714. halfWidth = Math.round(attr.barWidth * 0.5),
  32715. dataX = attr.dataX,
  32716. opens = aggregates.open,
  32717. closes = aggregates.close,
  32718. maxYs = aggregates.maxY,
  32719. minYs = aggregates.minY,
  32720. startIdxs = aggregates.startIdx,
  32721. pixelAdjust = attr.lineWidth * surface.devicePixelRatio / 2,
  32722. renderer = attr.renderer,
  32723. rendererConfig = renderer && {},
  32724. rendererParams, rendererChanges, open, high, low, close, mid, i, template;
  32725. me.rendererData = me.rendererData || {
  32726. store: me.getStore()
  32727. };
  32728. pixelAdjust -= Math.floor(pixelAdjust);
  32729. // Render raises.
  32730. ctx.save();
  32731. template = me.raiseTemplate;
  32732. template.useAttributes(ctx, clip);
  32733. if (!renderer) {
  32734. ctx.beginPath();
  32735. }
  32736. for (i = start; i < end; i++) {
  32737. if (opens[i] <= closes[i]) {
  32738. open = Math.round(opens[i] * yy + dy) + pixelAdjust;
  32739. high = Math.round(maxYs[i] * yy + dy) + pixelAdjust;
  32740. low = Math.round(minYs[i] * yy + dy) + pixelAdjust;
  32741. close = Math.round(closes[i] * yy + dy) + pixelAdjust;
  32742. mid = Math.round(dataX[startIdxs[i]] * xx + dx) + pixelAdjust;
  32743. if (renderer) {
  32744. ctx.save();
  32745. ctx.beginPath();
  32746. rendererConfig.open = open;
  32747. rendererConfig.high = high;
  32748. rendererConfig.low = low;
  32749. rendererConfig.close = close;
  32750. rendererConfig.mid = mid;
  32751. rendererConfig.halfWidth = halfWidth;
  32752. rendererParams = [
  32753. me,
  32754. rendererConfig,
  32755. me.rendererData,
  32756. i
  32757. ];
  32758. rendererChanges = Ext.callback(renderer, null, rendererParams, 0, series);
  32759. Ext.apply(ctx, rendererChanges);
  32760. }
  32761. me[ohlcType](ctx, open, high, low, close, mid, halfWidth);
  32762. if (renderer) {
  32763. ctx.fillStroke(template.attr);
  32764. ctx.restore();
  32765. }
  32766. }
  32767. }
  32768. if (!renderer) {
  32769. ctx.fillStroke(template.attr);
  32770. }
  32771. ctx.restore();
  32772. // Render drops.
  32773. ctx.save();
  32774. template = me.dropTemplate;
  32775. template.useAttributes(ctx, clip);
  32776. if (!renderer) {
  32777. ctx.beginPath();
  32778. }
  32779. for (i = start; i < end; i++) {
  32780. if (opens[i] > closes[i]) {
  32781. open = Math.round(opens[i] * yy + dy) + pixelAdjust;
  32782. high = Math.round(maxYs[i] * yy + dy) + pixelAdjust;
  32783. low = Math.round(minYs[i] * yy + dy) + pixelAdjust;
  32784. close = Math.round(closes[i] * yy + dy) + pixelAdjust;
  32785. mid = Math.round(dataX[startIdxs[i]] * xx + dx) + pixelAdjust;
  32786. if (renderer) {
  32787. ctx.save();
  32788. ctx.beginPath();
  32789. rendererConfig.open = open;
  32790. rendererConfig.high = high;
  32791. rendererConfig.low = low;
  32792. rendererConfig.close = close;
  32793. rendererConfig.mid = mid;
  32794. rendererConfig.halfWidth = halfWidth;
  32795. rendererParams = [
  32796. me,
  32797. rendererConfig,
  32798. me.rendererData,
  32799. i
  32800. ];
  32801. rendererChanges = Ext.callback(renderer, null, rendererParams, 0, me.getSeries());
  32802. Ext.apply(ctx, rendererChanges);
  32803. }
  32804. me[ohlcType](ctx, open, high, low, close, mid, halfWidth);
  32805. if (renderer) {
  32806. ctx.fillStroke(template.attr);
  32807. ctx.restore();
  32808. }
  32809. }
  32810. }
  32811. if (!renderer) {
  32812. ctx.fillStroke(template.attr);
  32813. }
  32814. ctx.restore();
  32815. }
  32816. });
  32817. /**
  32818. * @class Ext.chart.series.CandleStick
  32819. * @extends Ext.chart.series.Cartesian
  32820. *
  32821. * Creates a candlestick or OHLC Chart.
  32822. *
  32823. * CandleStick series are typically used to plot price movements of a security on an exchange
  32824. * over time. The series can be used with the 'time' axis, but since exchanges often close
  32825. * for weekends, and the price data has gaps for those days, it's more practical to use this series
  32826. * with the 'category' axis to avoid rendering those data gaps. The 'category' axis has no notion
  32827. * of time (and thus gaps) and treats every Date object (value of the 'xField') as a unique
  32828. * category. However, it also means that it doesn't support the 'dateFormat' config,
  32829. * which can be easily remedied with a 'renderer' that formats a Date object for use
  32830. * as an axis label. For example:
  32831. *
  32832. * @example
  32833. * new Ext.chart.CartesianChart({
  32834. * xtype: 'cartesian',
  32835. * renderTo: document.body,
  32836. * width: 700,
  32837. * height: 500,
  32838. * insetPadding: 20,
  32839. * innerPadding: '0 20 0 20',
  32840. *
  32841. * store: {
  32842. * data: [
  32843. * {
  32844. * time: new Date('Nov 17 2016'),
  32845. * o: 52.40, h: 52.74, l: 52.18, c: 52.29
  32846. * },
  32847. * {
  32848. * time: new Date('Nov 18 2016'),
  32849. * o: 51.87, h: 52.22, l: 51.51, c: 52.04
  32850. * },
  32851. * {
  32852. * time: new Date('Nov 21 2016'),
  32853. * o: 53.02, h: 53.40, l: 53.02, c: 53.33
  32854. * },
  32855. * {
  32856. * time: new Date('Nov 22 2016'),
  32857. * o: 53.48, h: 53.80, l: 53.13, c: 53.70
  32858. * },
  32859. * {
  32860. * time: new Date('Nov 23 2016'),
  32861. * o: 52.85, h: 53.39, l: 52.76, c: 53.28
  32862. * },
  32863. * {
  32864. * time: new Date('Nov 25 2016'),
  32865. * o: 53.28, h: 53.45, l: 53.20, c: 53.40
  32866. * },
  32867. * {
  32868. * time: new Date('Nov 28 2016'),
  32869. * o: 52.51, h: 52.58, l: 51.96, c: 52.00
  32870. * },
  32871. * {
  32872. * time: new Date('Nov 29 2016'),
  32873. * o: 51.25, h: 51.98, l: 51.10, c: 51.79
  32874. * },
  32875. * {
  32876. * time: new Date('Nov 30 2016'),
  32877. * o: 53.65, h: 54.56, l: 53.60, c: 54.17
  32878. * },
  32879. * {
  32880. * time: new Date('Dec 01 2016'),
  32881. * o: 55.26, h: 55.75, l: 54.94, c: 55.13
  32882. * }
  32883. * ]
  32884. * },
  32885. * axes: [
  32886. * {
  32887. * type: 'numeric',
  32888. * position: 'left'
  32889. * },
  32890. * {
  32891. * type: 'category',
  32892. * position: 'bottom',
  32893. *
  32894. * renderer: function (axis, value) {
  32895. * return Ext.Date.format(value, 'M j\nY');
  32896. * }
  32897. * }
  32898. * ],
  32899. * series: {
  32900. * type: 'candlestick',
  32901. *
  32902. * xField: 'time',
  32903. *
  32904. * openField: 'o',
  32905. * highField: 'h',
  32906. * lowField: 'l',
  32907. * closeField: 'c',
  32908. *
  32909. * style: {
  32910. * barWidth: 10,
  32911. *
  32912. * dropStyle: {
  32913. * fill: 'rgb(222, 87, 87)',
  32914. * stroke: 'rgb(222, 87, 87)',
  32915. * lineWidth: 3
  32916. * },
  32917. * raiseStyle: {
  32918. * fill: 'rgb(48, 189, 167)',
  32919. * stroke: 'rgb(48, 189, 167)',
  32920. * lineWidth: 3
  32921. * }
  32922. * }
  32923. * }
  32924. * });
  32925. */
  32926. Ext.define('Ext.chart.series.CandleStick', {
  32927. extend: 'Ext.chart.series.Cartesian',
  32928. requires: [
  32929. 'Ext.chart.series.sprite.CandleStick'
  32930. ],
  32931. alias: 'series.candlestick',
  32932. type: 'candlestick',
  32933. seriesType: 'candlestickSeries',
  32934. isCandleStick: true,
  32935. config: {
  32936. /**
  32937. * @cfg {String} openField
  32938. * The store record field name that represents the opening value of the given period.
  32939. */
  32940. openField: null,
  32941. /**
  32942. * @cfg {String} highField
  32943. * The store record field name that represents the highest value of the time interval
  32944. * represented.
  32945. */
  32946. highField: null,
  32947. /**
  32948. * @cfg {String} lowField
  32949. * The store record field name that represents the lowest value of the time interval
  32950. * represented.
  32951. */
  32952. lowField: null,
  32953. /**
  32954. * @cfg {String} closeField
  32955. * The store record field name that represents the closing value of the given period.
  32956. */
  32957. closeField: null
  32958. },
  32959. fieldCategoryY: [
  32960. 'Open',
  32961. 'High',
  32962. 'Low',
  32963. 'Close'
  32964. ],
  32965. themeColorCount: function() {
  32966. return 2;
  32967. }
  32968. });
  32969. /**
  32970. * @abstract
  32971. * @class Ext.chart.series.Polar
  32972. * @extends Ext.chart.series.Series
  32973. *
  32974. * Common base class for series implementations that plot values using polar coordinates.
  32975. *
  32976. * Polar charts accept angles in radians. You can calculate radians with the following
  32977. * formula:
  32978. *
  32979. * radians = degrees x Π/180
  32980. */
  32981. Ext.define('Ext.chart.series.Polar', {
  32982. extend: 'Ext.chart.series.Series',
  32983. config: {
  32984. /**
  32985. * @cfg {Number} [rotation=0]
  32986. * The angle in radians at which the first polar series item should start.
  32987. */
  32988. rotation: 0,
  32989. /**
  32990. * @cfg {Number} radius
  32991. * @private
  32992. * Use {@link Ext.chart.series.Pie#cfg!radiusFactor radiusFactor} instead.
  32993. *
  32994. * The internally used radius of the polar series. Set to `null` will fit the
  32995. * polar series to the boundary.
  32996. */
  32997. radius: null,
  32998. /**
  32999. * @cfg {Array} center for the polar series.
  33000. */
  33001. center: [
  33002. 0,
  33003. 0
  33004. ],
  33005. /**
  33006. * @cfg {Number} [offsetX=0]
  33007. * The x-offset of center of the polar series related to the center of the boundary.
  33008. */
  33009. offsetX: 0,
  33010. /**
  33011. * @cfg {Number} [offsetY=0]
  33012. * The y-offset of center of the polar series related to the center of the boundary.
  33013. */
  33014. offsetY: 0,
  33015. /**
  33016. * @cfg {Boolean} [showInLegend=true]
  33017. * Whether to add the series elements as legend items.
  33018. */
  33019. showInLegend: true,
  33020. /**
  33021. * @private
  33022. * @cfg {String} xField
  33023. */
  33024. xField: null,
  33025. /**
  33026. * @private
  33027. * @cfg {String} yField
  33028. */
  33029. yField: null,
  33030. /**
  33031. * @cfg {String} angleField
  33032. * The store record field name for the angular axes in radar charts,
  33033. * or the size of the slices in pie charts.
  33034. */
  33035. angleField: null,
  33036. /**
  33037. * @cfg {String} radiusField
  33038. * The store record field name for the radial axes in radar charts,
  33039. * or the radius of the slices in pie charts.
  33040. */
  33041. radiusField: null,
  33042. xAxis: null,
  33043. yAxis: null
  33044. },
  33045. directions: [
  33046. 'X',
  33047. 'Y'
  33048. ],
  33049. fieldCategoryX: [
  33050. 'X'
  33051. ],
  33052. fieldCategoryY: [
  33053. 'Y'
  33054. ],
  33055. deprecatedConfigs: {
  33056. field: 'angleField',
  33057. lengthField: 'radiusField'
  33058. },
  33059. constructor: function(config) {
  33060. var me = this,
  33061. configurator = me.self.getConfigurator(),
  33062. configs = configurator.configs,
  33063. p;
  33064. if (config) {
  33065. for (p in me.deprecatedConfigs) {
  33066. if (p in config && !(config in configs)) {
  33067. Ext.raise("'" + p + "' config has been deprecated. Please use the '" + me.deprecatedConfigs[p] + "' config instead.");
  33068. }
  33069. }
  33070. }
  33071. me.callParent([
  33072. config
  33073. ]);
  33074. },
  33075. getXField: function() {
  33076. return this.getAngleField();
  33077. },
  33078. updateXField: function(value) {
  33079. this.setAngleField(value);
  33080. },
  33081. getYField: function() {
  33082. return this.getRadiusField();
  33083. },
  33084. updateYField: function(value) {
  33085. this.setRadiusField(value);
  33086. },
  33087. applyXAxis: function(newAxis, oldAxis) {
  33088. return this.getChart().getAxis(newAxis) || oldAxis;
  33089. },
  33090. applyYAxis: function(newAxis, oldAxis) {
  33091. return this.getChart().getAxis(newAxis) || oldAxis;
  33092. },
  33093. getXRange: function() {
  33094. return [
  33095. this.dataRange[0],
  33096. this.dataRange[2]
  33097. ];
  33098. },
  33099. getYRange: function() {
  33100. return [
  33101. this.dataRange[1],
  33102. this.dataRange[3]
  33103. ];
  33104. },
  33105. themeColorCount: function() {
  33106. var me = this,
  33107. store = me.getStore(),
  33108. count = store && store.getCount() || 0;
  33109. return count;
  33110. },
  33111. isStoreDependantColorCount: true,
  33112. getDefaultSpriteConfig: function() {
  33113. return {
  33114. type: this.seriesType,
  33115. renderer: this.getRenderer(),
  33116. centerX: 0,
  33117. centerY: 0,
  33118. rotationCenterX: 0,
  33119. rotationCenterY: 0
  33120. };
  33121. },
  33122. applyRotation: function(rotation) {
  33123. return Ext.draw.sprite.AttributeParser.angle(Ext.draw.Draw.rad(rotation));
  33124. },
  33125. updateRotation: function(rotation) {
  33126. var sprites = this.getSprites();
  33127. if (sprites && sprites[0]) {
  33128. sprites[0].setAttributes({
  33129. baseRotation: rotation
  33130. });
  33131. }
  33132. }
  33133. });
  33134. /**
  33135. * Displays a gauge chart.
  33136. *
  33137. * @example
  33138. * Ext.create({
  33139. * xtype: 'polar',
  33140. * renderTo: document.body,
  33141. * width: 600,
  33142. * height: 400,
  33143. * store: {
  33144. * fields: ['mph', 'fuel', 'temp', 'rpm'],
  33145. * data: [{
  33146. * mph: 65,
  33147. * fuel: 50,
  33148. * temp: 150,
  33149. * rpm: 6000
  33150. * }]
  33151. * },
  33152. * series: {
  33153. * type: 'gauge',
  33154. * colors: ['#1F6D91', '#90BCC9'],
  33155. * angleField: 'mph',
  33156. * needle: true,
  33157. * donut: 30
  33158. * }
  33159. * });
  33160. */
  33161. Ext.define('Ext.chart.series.Gauge', {
  33162. alias: 'series.gauge',
  33163. extend: 'Ext.chart.series.Polar',
  33164. type: 'gauge',
  33165. seriesType: 'pieslice',
  33166. requires: [
  33167. 'Ext.draw.sprite.Sector'
  33168. ],
  33169. config: {
  33170. /**
  33171. * @cfg {String} angleField
  33172. * The store record field name to be used for the gauge value.
  33173. * The values bound to this field name must be positive real numbers.
  33174. */
  33175. /**
  33176. * @cfg {Boolean} needle
  33177. * If true, display the gauge as a needle, otherwise as a sector.
  33178. */
  33179. needle: false,
  33180. /**
  33181. * @cfg {Number} needleLength
  33182. * Percentage of the length of needle compared to the radius of the entire disk.
  33183. */
  33184. needleLength: 90,
  33185. /**
  33186. * @cfg {Number} needleWidth
  33187. * Width of the needle in pixels.
  33188. */
  33189. needleWidth: 4,
  33190. /**
  33191. * @cfg {Number} donut
  33192. * Percentage of the radius of the donut hole compared to the entire disk.
  33193. */
  33194. donut: 30,
  33195. /**
  33196. * @cfg {Boolean} showInLegend
  33197. * Whether to add the gauge chart elements as legend items.
  33198. */
  33199. showInLegend: false,
  33200. /**
  33201. * @cfg {Number} value
  33202. * Directly sets the displayed value of the gauge.
  33203. * It is ignored if {@link #angleField} is provided.
  33204. */
  33205. value: null,
  33206. /**
  33207. * @cfg {Array} colors (required)
  33208. * An array of color values which is used for the needle and the `sectors`.
  33209. */
  33210. colors: null,
  33211. /**
  33212. * @cfg {Array} sectors
  33213. * Allows to paint sectors of different colors in the background of the gauge,
  33214. * with optional labels.
  33215. *
  33216. * It can be an array of numbers (each between `minimum` and `maximum`) that
  33217. * define the highest value of each sector. For N sectors, only (N-1) values are
  33218. * needed because it is assumed that the first sector starts at `minimum` and the
  33219. * last sector ends at `maximum`. Example: a water temperature gauge that is blue
  33220. * below 20C, red above 80C, gray in-between, and with an orange needle...
  33221. *
  33222. * minimum: 0,
  33223. * maximum: 100,
  33224. * sectors: [20, 80],
  33225. * colors: ['orange', 'blue', 'lightgray', 'red']
  33226. *
  33227. * It can be also an array of objects, each with the following properties:
  33228. *
  33229. * @cfg {Number} sectors.start The starting value of the sector. If omitted, it
  33230. * uses the previous sector's `end` value or the chart's `minimum`.
  33231. * @cfg {Number} sectors.end The ending value of the sector. If omitted, it uses
  33232. * the `maximum` defined for the chart.
  33233. * @cfg {String} sectors.label The label for this sector. Labels are styled using
  33234. * the series' {@link Ext.chart.series.Series#label label} config.
  33235. * @cfg {String} sectors.color The color of the sector. If omitted, it uses one
  33236. * of the `colors` defined for the series or for the chart.
  33237. * @cfg {Object} sectors.style An additional style object for the sector (for
  33238. * instance to set the opacity or to draw a line of a different color around the
  33239. * sector).
  33240. *
  33241. * minimum: 0,
  33242. * maximum: 100,
  33243. * sectors: [{
  33244. * end: 20,
  33245. * label: 'Cold',
  33246. * color: 'aqua'
  33247. * },
  33248. * {
  33249. * end: 80,
  33250. * label: 'Temp.',
  33251. * color: 'lightgray',
  33252. * style: { strokeStyle:'black', strokeOpacity:1, lineWidth:1 }
  33253. * },
  33254. * {
  33255. * label: 'Hot',
  33256. * color: 'tomato'
  33257. * }]
  33258. */
  33259. sectors: null,
  33260. /**
  33261. * @cfg {Number} minimum
  33262. * The minimum value of the gauge.
  33263. */
  33264. minimum: 0,
  33265. /**
  33266. * @cfg {Number} maximum
  33267. * The maximum value of the gauge.
  33268. */
  33269. maximum: 100,
  33270. rotation: 0,
  33271. /**
  33272. * @cfg {Number} totalAngle
  33273. * The size of the sector that the series will occupy.
  33274. */
  33275. totalAngle: Math.PI / 2,
  33276. rect: [
  33277. 0,
  33278. 0,
  33279. 1,
  33280. 1
  33281. ],
  33282. center: [
  33283. 0.5,
  33284. 0.75
  33285. ],
  33286. radius: 0.5,
  33287. /**
  33288. * @cfg {Boolean} wholeDisk Indicates whether to show the whole disk
  33289. * or only the marked part.
  33290. */
  33291. wholeDisk: false
  33292. },
  33293. coordinateX: function() {
  33294. return this.coordinate('X', 0, 2);
  33295. },
  33296. coordinateY: function() {
  33297. return this.coordinate('Y', 1, 2);
  33298. },
  33299. updateNeedle: function(needle) {
  33300. var me = this,
  33301. sprites = me.getSprites(),
  33302. angle = me.valueToAngle(me.getValue());
  33303. if (sprites && sprites.length) {
  33304. sprites[0].setAttributes({
  33305. startAngle: (needle ? angle : 0),
  33306. endAngle: angle,
  33307. strokeOpacity: (needle ? 1 : 0),
  33308. lineWidth: (needle ? me.getNeedleWidth() : 0)
  33309. });
  33310. me.doUpdateStyles();
  33311. }
  33312. },
  33313. themeColorCount: function() {
  33314. var me = this,
  33315. store = me.getStore(),
  33316. count = store && store.getCount() || 0;
  33317. return count + (me.getNeedle() ? 0 : 1);
  33318. },
  33319. updateColors: function(colors, oldColors) {
  33320. var me = this,
  33321. sectors = me.getSectors(),
  33322. sectorCount = sectors && sectors.length,
  33323. sprites = me.getSprites(),
  33324. newColors = Ext.Array.clone(colors),
  33325. colorCount = colors && colors.length,
  33326. i;
  33327. if (!colorCount || !colors[0]) {
  33328. return;
  33329. }
  33330. // Make sure the 'sectors' colors are not overridden.
  33331. for (i = 0; i < sectorCount; i++) {
  33332. newColors[i + 1] = sectors[i].color || newColors[i + 1] || colors[i % colorCount];
  33333. }
  33334. if (sprites.length) {
  33335. sprites[0].setAttributes({
  33336. strokeStyle: newColors[0]
  33337. });
  33338. }
  33339. this.setSubStyle({
  33340. fillStyle: newColors,
  33341. strokeStyle: newColors
  33342. });
  33343. this.doUpdateStyles();
  33344. },
  33345. updateRect: function(rect) {
  33346. var wholeDisk = this.getWholeDisk(),
  33347. halfTotalAngle = wholeDisk ? Math.PI : this.getTotalAngle() / 2,
  33348. donut = this.getDonut() / 100,
  33349. width, height, radius;
  33350. if (halfTotalAngle <= Math.PI / 2) {
  33351. width = 2 * Math.sin(halfTotalAngle);
  33352. height = 1 - donut * Math.cos(halfTotalAngle);
  33353. } else {
  33354. width = 2;
  33355. height = 1 - Math.cos(halfTotalAngle);
  33356. }
  33357. radius = Math.min(rect[2] / width, rect[3] / height);
  33358. this.setRadius(radius);
  33359. this.setCenter([
  33360. rect[2] / 2,
  33361. radius + (rect[3] - height * radius) / 2
  33362. ]);
  33363. },
  33364. updateCenter: function(center) {
  33365. this.setStyle({
  33366. centerX: center[0],
  33367. centerY: center[1],
  33368. rotationCenterX: center[0],
  33369. rotationCenterY: center[1]
  33370. });
  33371. this.doUpdateStyles();
  33372. },
  33373. updateRotation: function(rotation) {
  33374. this.setStyle({
  33375. rotationRads: rotation - (this.getTotalAngle() + Math.PI) / 2
  33376. });
  33377. this.doUpdateStyles();
  33378. },
  33379. doUpdateShape: function(radius, donut) {
  33380. var me = this,
  33381. sectors = me.getSectors(),
  33382. sectorCount = (sectors && sectors.length) || 0,
  33383. needleLength = me.getNeedleLength() / 100,
  33384. endRhoArray;
  33385. // Initialize an array that contains the endRho for each sprite.
  33386. // The first sprite is for the needle, the others for the gauge background sectors.
  33387. // Note: SubStyle arrays are handled in series.getStyleByIndex().
  33388. endRhoArray = [
  33389. radius * needleLength,
  33390. radius
  33391. ];
  33392. while (sectorCount--) {
  33393. endRhoArray.push(radius);
  33394. }
  33395. me.setSubStyle({
  33396. endRho: endRhoArray,
  33397. startRho: radius / 100 * donut
  33398. });
  33399. me.doUpdateStyles();
  33400. },
  33401. updateRadius: function(radius) {
  33402. var donut = this.getDonut();
  33403. this.doUpdateShape(radius, donut);
  33404. },
  33405. updateDonut: function(donut) {
  33406. var radius = this.getRadius();
  33407. this.doUpdateShape(radius, donut);
  33408. },
  33409. valueToAngle: function(value) {
  33410. value = this.applyValue(value);
  33411. return this.getTotalAngle() * (value - this.getMinimum()) / (this.getMaximum() - this.getMinimum());
  33412. },
  33413. applyValue: function(value) {
  33414. return Math.min(this.getMaximum(), Math.max(value, this.getMinimum()));
  33415. },
  33416. updateValue: function(value) {
  33417. var me = this,
  33418. needle = me.getNeedle(),
  33419. angle = me.valueToAngle(value),
  33420. sprites = me.getSprites();
  33421. sprites[0].getRendererData().value = value;
  33422. sprites[0].setAttributes({
  33423. startAngle: (needle ? angle : 0),
  33424. endAngle: angle
  33425. });
  33426. me.doUpdateStyles();
  33427. },
  33428. processData: function() {
  33429. var me = this,
  33430. store = me.getStore(),
  33431. record = store && store.first(),
  33432. animation, duration, axis, min, max, xField, value;
  33433. if (record) {
  33434. xField = me.getXField();
  33435. if (xField) {
  33436. value = record.get(xField);
  33437. }
  33438. }
  33439. axis = me.getXAxis();
  33440. if (axis) {
  33441. min = axis.getMinimum();
  33442. max = axis.getMaximum();
  33443. // Animating the axis here can lead to weird looking results.
  33444. animation = axis.getSprites()[0].getAnimation();
  33445. duration = animation.getDuration();
  33446. animation.setDuration(0);
  33447. if (Ext.isNumber(min)) {
  33448. me.setMinimum(min);
  33449. } else {
  33450. axis.setMinimum(me.getMinimum());
  33451. }
  33452. if (Ext.isNumber(max)) {
  33453. me.setMaximum(max);
  33454. } else {
  33455. axis.setMaximum(me.getMaximum());
  33456. }
  33457. animation.setDuration(duration);
  33458. }
  33459. if (!Ext.isNumber(value)) {
  33460. value = me.getMinimum();
  33461. }
  33462. me.setValue(value);
  33463. },
  33464. getDefaultSpriteConfig: function() {
  33465. return {
  33466. type: this.seriesType,
  33467. renderer: this.getRenderer(),
  33468. animation: {
  33469. customDurations: {
  33470. translationX: 0,
  33471. translationY: 0,
  33472. rotationCenterX: 0,
  33473. rotationCenterY: 0,
  33474. centerX: 0,
  33475. centerY: 0,
  33476. startRho: 0,
  33477. endRho: 0,
  33478. baseRotation: 0
  33479. }
  33480. }
  33481. };
  33482. },
  33483. normalizeSectors: function(sectors) {
  33484. // Make sure all the sectors in the array have a legit start and end.
  33485. // Note: the array is modified in-place.
  33486. var me = this,
  33487. sectorCount = (sectors && sectors.length) || 0,
  33488. i, value, start, end;
  33489. if (sectorCount) {
  33490. for (i = 0; i < sectorCount; i++) {
  33491. value = sectors[i];
  33492. if (typeof value === 'number') {
  33493. sectors[i] = {
  33494. start: (i > 0 ? sectors[i - 1].end : me.getMinimum()),
  33495. end: Math.min(value, me.getMaximum())
  33496. };
  33497. if (i === (sectorCount - 1) && sectors[i].end < me.getMaximum()) {
  33498. sectors[i + 1] = {
  33499. start: sectors[i].end,
  33500. end: me.getMaximum()
  33501. };
  33502. }
  33503. } else {
  33504. if (typeof value.start === 'number') {
  33505. start = Math.max(value.start, me.getMinimum());
  33506. } else {
  33507. start = (i > 0 ? sectors[i - 1].end : me.getMinimum());
  33508. }
  33509. if (typeof value.end === 'number') {
  33510. end = Math.min(value.end, me.getMaximum());
  33511. } else {
  33512. end = me.getMaximum();
  33513. }
  33514. sectors[i].start = start;
  33515. sectors[i].end = end;
  33516. }
  33517. }
  33518. } else {
  33519. sectors = [
  33520. {
  33521. start: me.getMinimum(),
  33522. end: me.getMaximum()
  33523. }
  33524. ];
  33525. }
  33526. return sectors;
  33527. },
  33528. getSprites: function() {
  33529. var me = this,
  33530. store = me.getStore(),
  33531. value = me.getValue(),
  33532. label = me.getLabel(),
  33533. i, ln;
  33534. // The store must be initialized, or the value must be set
  33535. if (!store && !Ext.isNumber(value)) {
  33536. return Ext.emptyArray;
  33537. }
  33538. // Return cached sprites
  33539. // eslint-disable-next-line vars-on-top, one-var
  33540. var chart = me.getChart(),
  33541. animation = me.getAnimation() || chart && chart.getAnimation(),
  33542. sprites = me.sprites,
  33543. spriteIndex = 0,
  33544. sprite, sectors, attr, rendererData,
  33545. // Hack to avoid having the lineWidths overwritten by the one specified in the theme.
  33546. // In fact, all the style properties from the needle and sectors should go
  33547. // to the series subStyle.
  33548. lineWidths = [];
  33549. if (sprites && sprites.length) {
  33550. sprites[0].setAnimation(animation);
  33551. return sprites;
  33552. }
  33553. rendererData = {
  33554. store: store,
  33555. field: me.getXField(),
  33556. // for backward compatibility only (deprecated in 5.5)
  33557. angleField: me.getXField(),
  33558. value: value,
  33559. series: me
  33560. };
  33561. // Create needle sprite
  33562. me.needleSprite = sprite = me.createSprite();
  33563. sprite.setAttributes({
  33564. zIndex: 10
  33565. }, true);
  33566. sprite.setRendererData(rendererData);
  33567. sprite.setRendererIndex(spriteIndex++);
  33568. lineWidths.push(me.getNeedleWidth());
  33569. if (label) {
  33570. label.getTemplate().setField(true);
  33571. }
  33572. // Enable labels
  33573. // Create background sprite(s)
  33574. sectors = me.normalizeSectors(me.getSectors());
  33575. for (i = 0 , ln = sectors.length; i < ln; i++) {
  33576. attr = {
  33577. startAngle: me.valueToAngle(sectors[i].start),
  33578. endAngle: me.valueToAngle(sectors[i].end),
  33579. label: sectors[i].label,
  33580. fillStyle: sectors[i].color,
  33581. strokeOpacity: 0,
  33582. doCallout: false,
  33583. // Show labels inside sectors.
  33584. labelOverflowPadding: -1
  33585. };
  33586. // Allow labels to overlap.
  33587. Ext.apply(attr, sectors[i].style);
  33588. sprite = me.createSprite();
  33589. sprite.setRendererData(rendererData);
  33590. sprite.setRendererIndex(spriteIndex++);
  33591. sprite.setAttributes(attr, true);
  33592. lineWidths.push(attr.lineWidth);
  33593. }
  33594. me.setSubStyle({
  33595. lineWidth: lineWidths
  33596. });
  33597. me.doUpdateStyles();
  33598. return sprites;
  33599. },
  33600. doUpdateStyles: function() {
  33601. var me = this;
  33602. me.callParent();
  33603. if (me.sprites.length) {
  33604. me.needleSprite.setAttributes({
  33605. startRho: me.getNeedle() ? 0 : (me.getRadius() / 100 * me.getDonut())
  33606. });
  33607. }
  33608. }
  33609. });
  33610. /**
  33611. * @class Ext.chart.series.sprite.Line
  33612. * @extends Ext.chart.series.sprite.Aggregative
  33613. *
  33614. * Line series sprite.
  33615. */
  33616. Ext.define('Ext.chart.series.sprite.Line', {
  33617. alias: 'sprite.lineSeries',
  33618. extend: 'Ext.chart.series.sprite.Aggregative',
  33619. inheritableStatics: {
  33620. def: {
  33621. processors: {
  33622. /**
  33623. * @cfg {Object} [curve={type: 'linear'}]
  33624. * The type of curve that connects the data points.
  33625. *
  33626. * For example:
  33627. *
  33628. * // The data points are connected by line segments.
  33629. * // This is the default setting.
  33630. * curve: {
  33631. * type: 'linear'
  33632. * }
  33633. *
  33634. * // Cardinal spline interpolation is used to produce the curve
  33635. * // that connects the data points. The `tension` parameter can
  33636. * // be used to control the smoothness of the curve. A tension
  33637. * // of 0 corresponds to infinite tension, which results in straight
  33638. * // lines between data points. A tension of 1 corresponds to
  33639. * // no tension, allowing the spline to take the path of least
  33640. * // total bend. With tension values greater than 1, the curve
  33641. * // behaves like a compressed spring, pushed to take a longer path.
  33642. * // A cardinal spline with a tension of 0.5 is a special case.
  33643. * // It is then called a Catmull-Rom spline. Catmull-Rom splines are
  33644. * // thought to be esthetically pleasing and are quite common.
  33645. * // Note: spline interpolation only works on gapless data.
  33646. * curve: {
  33647. * type: 'cardinal,
  33648. * tension: 0.5
  33649. * }
  33650. *
  33651. * // Produces a natural cubic spline with the second derivative
  33652. * // of the spline set to zero at the endpoints.
  33653. * curve: {
  33654. * type: 'natural'
  33655. * }
  33656. *
  33657. * // The data points are connected by alternating horizontal and
  33658. * // vertical lines. The y-value changes after the x-value.
  33659. * curve: {
  33660. * type: 'step-after'
  33661. * }
  33662. *
  33663. */
  33664. curve: 'default',
  33665. /**
  33666. * @cfg {Boolean} [fillArea=false]
  33667. * `true` if the sprite paints the area underneath the line.
  33668. */
  33669. fillArea: 'bool',
  33670. /**
  33671. * @cfg {"gap"/"connect"/"origin"} [nullStyle="gap"]
  33672. * Possible values:
  33673. * 'gap' - null points are rendered as gaps.
  33674. * 'connect' - non-null points are connected across null points, so that
  33675. * there is no gap, unless null points are at the beginning/end of the line.
  33676. * Only the visible data points are connected - if a visible data point
  33677. * is followed by a series of null points that go off screen and eventually
  33678. * terminate with a non-null point, the connection won't be made.
  33679. * 'origin' - null data points are rendered at the origin,
  33680. * which is the y-coordinate of a point where the x and y axes meet.
  33681. * This requires that at least the x-coordinate of a point is a valid value.
  33682. */
  33683. nullStyle: 'enums(gap,connect,origin)',
  33684. /**
  33685. * @cfg {Boolean} [preciseStroke=true]
  33686. * `true` if the line uses precise stroke.
  33687. */
  33688. preciseStroke: 'bool',
  33689. /**
  33690. * @private
  33691. * The x-axis associated with the Line series.
  33692. * We need to know the position of the x-axis to fill the area underneath
  33693. * the stroke properly.
  33694. */
  33695. xAxis: 'default',
  33696. /**
  33697. * @cfg {Number} [yCap=Math.pow(2, 20)]
  33698. * Absolute maximum y-value.
  33699. * Larger values will be capped to avoid rendering issues.
  33700. */
  33701. // The 'default' processor is used here as we don't want this attribute to animate.
  33702. yCap: 'default'
  33703. },
  33704. defaults: {
  33705. curve: {
  33706. type: 'linear'
  33707. },
  33708. nullStyle: 'connect',
  33709. fillArea: false,
  33710. preciseStroke: true,
  33711. xAxis: null,
  33712. yCap: Math.pow(2, 20),
  33713. yJump: 50
  33714. },
  33715. triggers: {
  33716. dataX: 'dataX,bbox,curve',
  33717. dataY: 'dataY,bbox,curve',
  33718. curve: 'curve'
  33719. },
  33720. updaters: {
  33721. curve: 'curveUpdater'
  33722. }
  33723. }
  33724. },
  33725. list: null,
  33726. curveUpdater: function(attr) {
  33727. var me = this,
  33728. dataX = attr.dataX,
  33729. dataY = attr.dataY,
  33730. curve = attr.curve,
  33731. smoothable = dataX && dataY && dataX.length > 2 && dataY.length > 2,
  33732. type = curve.type;
  33733. if (smoothable) {
  33734. if (type === 'natural') {
  33735. me.smoothX = Ext.draw.Draw.naturalSpline(dataX);
  33736. me.smoothY = Ext.draw.Draw.naturalSpline(dataY);
  33737. } else if (type === 'cardinal') {
  33738. me.smoothX = Ext.draw.Draw.cardinalSpline(dataX, curve.tension);
  33739. me.smoothY = Ext.draw.Draw.cardinalSpline(dataY, curve.tension);
  33740. } else {
  33741. smoothable = false;
  33742. }
  33743. }
  33744. if (!smoothable) {
  33745. delete me.smoothX;
  33746. delete me.smoothY;
  33747. }
  33748. },
  33749. updatePlainBBox: function(plain) {
  33750. var attr = this.attr,
  33751. ymin = Math.min(0, attr.dataMinY),
  33752. ymax = Math.max(0, attr.dataMaxY);
  33753. plain.x = attr.dataMinX;
  33754. plain.y = ymin;
  33755. plain.width = attr.dataMaxX - attr.dataMinX;
  33756. plain.height = ymax - ymin;
  33757. },
  33758. drawStrip: function(ctx, strip) {
  33759. var i, ln;
  33760. ctx.moveTo(strip[0], strip[1]);
  33761. for (i = 2 , ln = strip.length; i < ln; i += 2) {
  33762. ctx.lineTo(strip[i], strip[i + 1]);
  33763. }
  33764. },
  33765. drawStraightStroke: function(surface, ctx, start, end, list, xAxis) {
  33766. var me = this,
  33767. attr = me.attr,
  33768. nullStyle = attr.nullStyle,
  33769. isConnect = nullStyle === 'connect',
  33770. isOrigin = nullStyle === 'origin',
  33771. renderer = attr.renderer,
  33772. curve = attr.curve,
  33773. step = curve.type === 'step-after',
  33774. needMoveTo = true,
  33775. ln = list.length,
  33776. lineConfig = {
  33777. type: 'line',
  33778. smooth: false,
  33779. step: step
  33780. },
  33781. rendererChanges, params, stripStartX, isValidX0, isValidX, isValidX1, isValidPoint0, isValidPoint, isValidPoint1, isGap, lastValidPoint, px, py, x, y, x0, y0, x1, y1, i,
  33782. // 'strip' stores last continuous segment of the stroke,
  33783. // which we may need to re-build, if there's a fill as well.
  33784. // For example, if the renderer returned a style that needs
  33785. // to be applied to the current step, or we reached a null
  33786. // point in the data, where we have to fill the current continuous
  33787. // segment, we build and close a path that will be filled, then
  33788. // re-build the stroke path, using coordinates saved in the 'strip',
  33789. // and render the stroke on top of the fill.
  33790. strip = [];
  33791. ctx.beginPath();
  33792. for (i = 3; i < ln; i += 3) {
  33793. x0 = list[i - 3];
  33794. y0 = list[i - 2];
  33795. x = list[i];
  33796. y = list[i + 1];
  33797. x1 = list[i + 3];
  33798. y1 = list[i + 4];
  33799. isValidX0 = Ext.isNumber(x0);
  33800. isValidX = Ext.isNumber(x);
  33801. isValidX1 = Ext.isNumber(x1);
  33802. isValidPoint0 = isValidX0 && Ext.isNumber(y0);
  33803. isValidPoint = isValidX && Ext.isNumber(y);
  33804. isValidPoint1 = isValidX1 && Ext.isNumber(y1);
  33805. if (isOrigin) {
  33806. // If only the y-component isn't a valid number,
  33807. // we can 'fix' it by setting it to value of y-origin.
  33808. if (!isValidPoint0 && isValidX0) {
  33809. y0 = xAxis;
  33810. isValidPoint0 = true;
  33811. }
  33812. if (!isValidPoint && isValidX) {
  33813. y = xAxis;
  33814. isValidPoint = true;
  33815. }
  33816. if (!isValidPoint1 && isValidX1) {
  33817. y1 = xAxis;
  33818. isValidPoint1 = true;
  33819. }
  33820. }
  33821. if (renderer) {
  33822. lineConfig.x = x;
  33823. lineConfig.y = y;
  33824. lineConfig.x0 = x0;
  33825. lineConfig.y0 = y0;
  33826. params = [
  33827. me,
  33828. lineConfig,
  33829. me.rendererData,
  33830. start + i / 3
  33831. ];
  33832. // callback(fn, scope, args, delay, caller)
  33833. rendererChanges = Ext.callback(renderer, null, params, 0, me.getSeries());
  33834. }
  33835. if (isGap && isConnect && isValidPoint0 && lastValidPoint) {
  33836. px = lastValidPoint[0];
  33837. py = lastValidPoint[1];
  33838. if (needMoveTo) {
  33839. ctx.beginPath();
  33840. ctx.moveTo(px, py);
  33841. strip.push(px, py);
  33842. stripStartX = px;
  33843. needMoveTo = false;
  33844. }
  33845. if (step) {
  33846. ctx.lineTo(x0, py);
  33847. strip.push(x0, py);
  33848. }
  33849. ctx.lineTo(x0, y0);
  33850. strip.push(x0, y0);
  33851. lastValidPoint = [
  33852. x0,
  33853. y0
  33854. ];
  33855. isGap = false;
  33856. }
  33857. // Special case where we have an uninterrupted segment, followed
  33858. // by a gap, then a valid point, then another gap. The uninterrupted
  33859. // segment should be connenected with the dot situated between the gaps.
  33860. if (isConnect && lastValidPoint && isValidPoint && !isValidPoint0) {
  33861. x0 = lastValidPoint[0];
  33862. y0 = lastValidPoint[1];
  33863. isValidPoint0 = true;
  33864. }
  33865. // Remember last valid point to connect the gap
  33866. // when the next valid point is encountered.
  33867. if (isValidPoint) {
  33868. lastValidPoint = [
  33869. x,
  33870. y
  33871. ];
  33872. }
  33873. if (isValidPoint0 && isValidPoint) {
  33874. if (needMoveTo) {
  33875. ctx.beginPath();
  33876. ctx.moveTo(x0, y0);
  33877. strip.push(x0, y0);
  33878. stripStartX = x0;
  33879. needMoveTo = false;
  33880. }
  33881. } else {
  33882. isGap = true;
  33883. continue;
  33884. }
  33885. if (step) {
  33886. ctx.lineTo(x, y0);
  33887. strip.push(x, y0);
  33888. }
  33889. ctx.lineTo(x, y);
  33890. strip.push(x, y);
  33891. // If the next point is a gap, then we need to fill what
  33892. // has been already rendered so far. The same applies
  33893. // if the renderer returned some changes to apply to
  33894. // the current step.
  33895. if (rendererChanges || !isValidPoint1) {
  33896. ctx.save();
  33897. Ext.apply(ctx, rendererChanges);
  33898. rendererChanges = null;
  33899. if (attr.fillArea) {
  33900. ctx.lineTo(x, xAxis);
  33901. ctx.lineTo(stripStartX, xAxis);
  33902. ctx.closePath();
  33903. ctx.fill();
  33904. }
  33905. // Draw the line on top of the filled area.
  33906. ctx.beginPath();
  33907. me.drawStrip(ctx, strip);
  33908. strip = [];
  33909. ctx.stroke();
  33910. ctx.restore();
  33911. ctx.beginPath();
  33912. // Take note that the starting point of a path has been reset
  33913. // (as a result of filling a sub-path) and needs to be set again
  33914. // for the line to continue in a proper manner.
  33915. needMoveTo = true;
  33916. }
  33917. }
  33918. },
  33919. calculateScale: function(count, end) {
  33920. var power = 0,
  33921. n = count;
  33922. while (n < end && count > 0) {
  33923. power++;
  33924. n += count >> power;
  33925. }
  33926. return Math.pow(2, power > 0 ? power - 1 : power);
  33927. },
  33928. drawSmoothStroke: function(surface, ctx, start, end, list, xAxis) {
  33929. var me = this,
  33930. attr = me.attr,
  33931. step = attr.step,
  33932. matrix = attr.matrix,
  33933. renderer = attr.renderer,
  33934. xx = matrix.getXX(),
  33935. yy = matrix.getYY(),
  33936. dx = matrix.getDX(),
  33937. dy = matrix.getDY(),
  33938. smoothX = me.smoothX,
  33939. smoothY = me.smoothY,
  33940. scale = me.calculateScale(attr.dataX.length, end),
  33941. cx1, cy1, cx2, cy2, x, y, x0, y0, i, j, changes, params,
  33942. lineConfig = {
  33943. type: 'line',
  33944. smooth: true,
  33945. step: step
  33946. };
  33947. ctx.beginPath();
  33948. ctx.moveTo(smoothX[start * 3] * xx + dx, smoothY[start * 3] * yy + dy);
  33949. for (i = 0 , j = start * 3 + 1; i < list.length - 3; i += 3 , j += 3 * scale) {
  33950. cx1 = smoothX[j] * xx + dx;
  33951. cy1 = smoothY[j] * yy + dy;
  33952. cx2 = smoothX[j + 1] * xx + dx;
  33953. cy2 = smoothY[j + 1] * yy + dy;
  33954. x = surface.roundPixel(list[i + 3]);
  33955. y = list[i + 4];
  33956. x0 = surface.roundPixel(list[i]);
  33957. y0 = list[i + 1];
  33958. if (renderer) {
  33959. lineConfig.x0 = x0;
  33960. lineConfig.y0 = y0;
  33961. lineConfig.cx1 = cx1;
  33962. lineConfig.cy1 = cy1;
  33963. lineConfig.cx2 = cx2;
  33964. lineConfig.cy2 = cy2;
  33965. lineConfig.x = x;
  33966. lineConfig.y = y;
  33967. params = [
  33968. me,
  33969. lineConfig,
  33970. me.rendererData,
  33971. start + i / 3 + 1
  33972. ];
  33973. changes = Ext.callback(renderer, null, params, 0, me.getSeries());
  33974. ctx.save();
  33975. Ext.apply(ctx, changes);
  33976. }
  33977. if (attr.fillArea) {
  33978. ctx.moveTo(x0, y0);
  33979. ctx.bezierCurveTo(cx1, cy1, cx2, cy2, x, y);
  33980. ctx.lineTo(x, xAxis);
  33981. ctx.lineTo(x0, xAxis);
  33982. ctx.lineTo(x0, y0);
  33983. ctx.closePath();
  33984. ctx.fill();
  33985. ctx.beginPath();
  33986. }
  33987. // Draw the line on top of the filled area.
  33988. ctx.moveTo(x0, y0);
  33989. ctx.bezierCurveTo(cx1, cy1, cx2, cy2, x, y);
  33990. ctx.stroke();
  33991. ctx.moveTo(x0, y0);
  33992. ctx.closePath();
  33993. if (renderer) {
  33994. ctx.restore();
  33995. }
  33996. ctx.beginPath();
  33997. ctx.moveTo(x, y);
  33998. }
  33999. // Prevent the last visible segment from being stroked twice
  34000. // (second time by the ctx.fillStroke inside Path sprite 'render' method)
  34001. ctx.beginPath();
  34002. },
  34003. drawLabel: function(text, dataX, dataY, labelId, rect) {
  34004. var me = this,
  34005. attr = me.attr,
  34006. label = me.getMarker('labels'),
  34007. labelTpl = label.getTemplate(),
  34008. labelCfg = me.labelCfg || (me.labelCfg = {}),
  34009. surfaceMatrix = me.surfaceMatrix,
  34010. labelX, labelY,
  34011. labelOverflowPadding = attr.labelOverflowPadding,
  34012. halfHeight, labelBBox, changes, params, hasPendingChanges;
  34013. // The coordinates below (data point converted to surface coordinates)
  34014. // are just for the renderer to give it a notion of where the label will be positioned.
  34015. // The actual position of the label will be different
  34016. // (unless the renderer returns x/y coordinates in the changes object)
  34017. // and depend on several things including the size of the text,
  34018. // which has to be measured after the renderer call,
  34019. // since text can be modified by the renderer.
  34020. labelCfg.x = surfaceMatrix.x(dataX, dataY);
  34021. labelCfg.y = surfaceMatrix.y(dataX, dataY);
  34022. if (attr.flipXY) {
  34023. labelCfg.rotationRads = Math.PI * 0.5;
  34024. } else {
  34025. labelCfg.rotationRads = 0;
  34026. }
  34027. labelCfg.text = text;
  34028. if (labelTpl.attr.renderer) {
  34029. params = [
  34030. text,
  34031. label,
  34032. labelCfg,
  34033. me.rendererData,
  34034. labelId
  34035. ];
  34036. changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
  34037. if (typeof changes === 'string') {
  34038. labelCfg.text = changes;
  34039. } else if (typeof changes === 'object') {
  34040. if ('text' in changes) {
  34041. labelCfg.text = changes.text;
  34042. }
  34043. hasPendingChanges = true;
  34044. }
  34045. }
  34046. labelBBox = me.getMarkerBBox('labels', labelId, true);
  34047. if (!labelBBox) {
  34048. me.putMarker('labels', labelCfg, labelId);
  34049. labelBBox = me.getMarkerBBox('labels', labelId, true);
  34050. }
  34051. halfHeight = labelBBox.height / 2;
  34052. labelX = dataX;
  34053. switch (labelTpl.attr.display) {
  34054. case 'under':
  34055. labelY = dataY - halfHeight - labelOverflowPadding;
  34056. break;
  34057. case 'rotate':
  34058. labelX += labelOverflowPadding;
  34059. labelY = dataY - labelOverflowPadding;
  34060. labelCfg.rotationRads = -Math.PI / 4;
  34061. break;
  34062. default:
  34063. // 'over'
  34064. labelY = dataY + halfHeight + labelOverflowPadding;
  34065. }
  34066. labelCfg.x = surfaceMatrix.x(labelX, labelY);
  34067. labelCfg.y = surfaceMatrix.y(labelX, labelY);
  34068. if (hasPendingChanges) {
  34069. Ext.apply(labelCfg, changes);
  34070. }
  34071. me.putMarker('labels', labelCfg, labelId);
  34072. },
  34073. drawMarker: function(x, y, index) {
  34074. var me = this,
  34075. attr = me.attr,
  34076. renderer = attr.renderer,
  34077. surfaceMatrix = me.surfaceMatrix,
  34078. markerCfg = {},
  34079. changes, params;
  34080. if (renderer && me.getMarker('markers')) {
  34081. markerCfg.type = 'marker';
  34082. markerCfg.x = x;
  34083. markerCfg.y = y;
  34084. params = [
  34085. me,
  34086. markerCfg,
  34087. me.rendererData,
  34088. index
  34089. ];
  34090. changes = Ext.callback(renderer, null, params, 0, me.getSeries());
  34091. if (changes) {
  34092. Ext.apply(markerCfg, changes);
  34093. }
  34094. }
  34095. markerCfg.translationX = surfaceMatrix.x(x, y);
  34096. markerCfg.translationY = surfaceMatrix.y(x, y);
  34097. delete markerCfg.x;
  34098. delete markerCfg.y;
  34099. me.putMarker('markers', markerCfg, index, !renderer);
  34100. },
  34101. drawStroke: function(surface, ctx, start, end, list, xAxis) {
  34102. var me = this,
  34103. isSmooth = me.smoothX && me.smoothY;
  34104. if (isSmooth) {
  34105. me.drawSmoothStroke(surface, ctx, start, end, list, xAxis);
  34106. } else {
  34107. me.drawStraightStroke(surface, ctx, start, end, list, xAxis);
  34108. }
  34109. },
  34110. renderAggregates: function(aggregates, start, end, surface, ctx, clip, rect) {
  34111. var me = this,
  34112. attr = me.attr,
  34113. dataX = attr.dataX,
  34114. dataY = attr.dataY,
  34115. labels = attr.labels,
  34116. xAxis = attr.xAxis,
  34117. yCap = attr.yCap,
  34118. isSmooth = attr.smooth && me.smoothX && me.smoothY,
  34119. isDrawLabels = labels && me.getMarker('labels'),
  34120. isDrawMarkers = me.getMarker('markers'),
  34121. matrix = attr.matrix,
  34122. pixel = surface.devicePixelRatio,
  34123. xx = matrix.getXX(),
  34124. yy = matrix.getYY(),
  34125. dx = matrix.getDX(),
  34126. dy = matrix.getDY(),
  34127. list = me.list || (me.list = []),
  34128. minXs = aggregates.minX,
  34129. maxXs = aggregates.maxX,
  34130. minYs = aggregates.minY,
  34131. maxYs = aggregates.maxY,
  34132. idx = aggregates.startIdx,
  34133. isContinuousLine = true,
  34134. isValidMinX, isValidMaxX, isValidMinY, isValidMaxY, xAxisOrigin, isVerticalX, x, y, i, index, minX, maxX, minY, maxY, lastPointX, lastPointY, firstPointX, firstPointY;
  34135. me.rendererData = {
  34136. store: me.getStore()
  34137. };
  34138. list.length = 0;
  34139. // Say we have 7 y-items (attr.dataY): [20, 19, 17, 15, 11, 10, 14]
  34140. // and 7 x-items (attr.dataX): [0, 1, 2, 3, 4, 5, 6].
  34141. // Then aggregates.startIdx is an aggregated index,
  34142. // where every other item is skipped on each aggregation level:
  34143. // [0, 1, 2, 3, 4, 5, 6,
  34144. // 0, 2, 4, 6,
  34145. // 0, 4,
  34146. // 0]
  34147. // aggregates.minY
  34148. // [20, 19, 17, 15, 11, 10, 14,
  34149. // 19, 15, 10, 14,
  34150. // 15, 10,
  34151. // 10]
  34152. // aggregates.maxY
  34153. // [20, 19, 17, 15, 11, 10, 14,
  34154. // 20, 17, 11, 14,
  34155. // 20, 14,
  34156. // 20]
  34157. // aggregates.minX is
  34158. // [0, 1, 2, 3, 4, 5, 6,
  34159. // 1, 3, 5, 6, // TODO: why this order for min?
  34160. // 3, 5, // TODO: why this inconsistency?
  34161. // 5]
  34162. // aggregates.maxX is
  34163. // [0, 1, 2, 3, 4, 5, 6,
  34164. // 0, 2, 4, 6,
  34165. // 0, 6,
  34166. // 0]
  34167. // Create a list of the form [x0, y0, idx0, x1, y1, idx1, ...],
  34168. // where each x,y pair is a coordinate representing original data point
  34169. // at the idx position.
  34170. for (i = start; i < end; i++) {
  34171. minX = minXs[i];
  34172. maxX = maxXs[i];
  34173. minY = minYs[i];
  34174. maxY = maxYs[i];
  34175. isValidMinX = Ext.isNumber(minX);
  34176. isValidMinY = Ext.isNumber(minY);
  34177. isValidMaxX = Ext.isNumber(maxX);
  34178. isValidMaxY = Ext.isNumber(maxY);
  34179. if (minX < maxX) {
  34180. list.push(isValidMinX ? (minX * xx + dx) : null, isValidMinY ? (minY * yy + dy) : null, idx[i]);
  34181. list.push(isValidMaxX ? (maxX * xx + dx) : null, isValidMaxY ? (maxY * yy + dy) : null, idx[i]);
  34182. } else if (minX > maxX) {
  34183. list.push(isValidMaxX ? (maxX * xx + dx) : null, isValidMaxY ? (maxY * yy + dy) : null, idx[i]);
  34184. list.push(isValidMinX ? (minX * xx + dx) : null, isValidMinY ? (minY * yy + dy) : null, idx[i]);
  34185. } else {
  34186. list.push(isValidMaxX ? (maxX * xx + dx) : null, isValidMaxY ? (maxY * yy + dy) : null, idx[i]);
  34187. }
  34188. }
  34189. if (list.length) {
  34190. for (i = 0; i < list.length; i += 3) {
  34191. x = list[i];
  34192. y = list[i + 1];
  34193. if (Ext.isNumber(x) && Ext.isNumber(y)) {
  34194. if (y > yCap) {
  34195. y = yCap;
  34196. } else if (y < -yCap) {
  34197. y = -yCap;
  34198. }
  34199. list[i + 1] = y;
  34200. } else {
  34201. isContinuousLine = false;
  34202. continue;
  34203. }
  34204. index = list[i + 2];
  34205. if (isDrawMarkers) {
  34206. me.drawMarker(x, y, index);
  34207. }
  34208. if (isDrawLabels && labels[index]) {
  34209. me.drawLabel(labels[index], x, y, index, rect);
  34210. }
  34211. }
  34212. me.isContinuousLine = isContinuousLine;
  34213. if (isSmooth && !isContinuousLine) {
  34214. Ext.raise("Line smoothing in only supported for gapless data, " + "where all data points are finite numbers.");
  34215. }
  34216. if (xAxis) {
  34217. isVerticalX = xAxis.getAlignment() === 'vertical';
  34218. if (Ext.isNumber(xAxis.floatingAtCoord)) {
  34219. xAxisOrigin = (isVerticalX ? rect[2] : rect[3]) - xAxis.floatingAtCoord;
  34220. } else {
  34221. xAxisOrigin = isVerticalX ? rect[0] : rect[1];
  34222. }
  34223. } else {
  34224. xAxisOrigin = attr.flipXY ? rect[0] : rect[1];
  34225. }
  34226. if (attr.preciseStroke) {
  34227. if (attr.fillArea) {
  34228. ctx.fill();
  34229. }
  34230. if (attr.transformFillStroke) {
  34231. attr.inverseMatrix.toContext(ctx);
  34232. }
  34233. me.drawStroke(surface, ctx, start, end, list, xAxisOrigin);
  34234. if (attr.transformFillStroke) {
  34235. attr.matrix.toContext(ctx);
  34236. }
  34237. ctx.stroke();
  34238. } else {
  34239. me.drawStroke(surface, ctx, start, end, list, xAxisOrigin);
  34240. if (isContinuousLine && isSmooth && attr.fillArea && !attr.renderer) {
  34241. lastPointX = dataX[dataX.length - 1] * xx + dx + pixel;
  34242. lastPointY = dataY[dataY.length - 1] * yy + dy;
  34243. firstPointX = dataX[0] * xx + dx - pixel;
  34244. firstPointY = dataY[0] * yy + dy;
  34245. // Fill the area from the series to the xAxis in case there
  34246. // are no gaps and no renderer is used, in which case the
  34247. // area would be filled per uninterrupted segment or per
  34248. // step, instead of being filled a single pass.
  34249. ctx.lineTo(lastPointX, lastPointY);
  34250. ctx.lineTo(lastPointX, xAxisOrigin - attr.lineWidth);
  34251. ctx.lineTo(firstPointX, xAxisOrigin - attr.lineWidth);
  34252. ctx.lineTo(firstPointX, firstPointY);
  34253. }
  34254. if (attr.transformFillStroke) {
  34255. attr.matrix.toContext(ctx);
  34256. }
  34257. // Prevent the reverse transform to fix floating point error.
  34258. if (attr.fillArea) {
  34259. ctx.fillStroke(attr, true);
  34260. } else {
  34261. ctx.stroke(true);
  34262. }
  34263. }
  34264. }
  34265. }
  34266. });
  34267. /**
  34268. * @class Ext.chart.series.Line
  34269. * @extends Ext.chart.series.Cartesian
  34270. *
  34271. * Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative
  34272. * information for different categories or other real values (as opposed to the bar chart),
  34273. * that can show some progression (or regression) in the dataset.
  34274. * As with all other series, the Line Series must be appended in the *series* Chart array
  34275. * configuration. See the Chart documentation for more information. A typical configuration object
  34276. * for the line series could be:
  34277. *
  34278. * @example
  34279. * Ext.create({
  34280. * xtype: 'cartesian',
  34281. * renderTo: document.body,
  34282. * width: 600,
  34283. * height: 400,
  34284. * insetPadding: 40,
  34285. * store: {
  34286. * fields: ['name', 'data1', 'data2'],
  34287. * data: [{
  34288. * 'name': 'metric one',
  34289. * 'data1': 10,
  34290. * 'data2': 14
  34291. * }, {
  34292. * 'name': 'metric two',
  34293. * 'data1': 7,
  34294. * 'data2': 16
  34295. * }, {
  34296. * 'name': 'metric three',
  34297. * 'data1': 5,
  34298. * 'data2': 14
  34299. * }, {
  34300. * 'name': 'metric four',
  34301. * 'data1': 2,
  34302. * 'data2': 6
  34303. * }, {
  34304. * 'name': 'metric five',
  34305. * 'data1': 27,
  34306. * 'data2': 36
  34307. * }]
  34308. * },
  34309. * axes: [{
  34310. * type: 'numeric',
  34311. * position: 'left',
  34312. * fields: ['data1'],
  34313. * title: {
  34314. * text: 'Sample Values',
  34315. * fontSize: 15
  34316. * },
  34317. * grid: true,
  34318. * minimum: 0
  34319. * }, {
  34320. * type: 'category',
  34321. * position: 'bottom',
  34322. * fields: ['name'],
  34323. * title: {
  34324. * text: 'Sample Values',
  34325. * fontSize: 15
  34326. * }
  34327. * }],
  34328. * series: [{
  34329. * type: 'line',
  34330. * style: {
  34331. * stroke: '#30BDA7',
  34332. * lineWidth: 2
  34333. * },
  34334. * xField: 'name',
  34335. * yField: 'data1',
  34336. * marker: {
  34337. * type: 'path',
  34338. * path: ['M', - 4, 0, 0, 4, 4, 0, 0, - 4, 'Z'],
  34339. * stroke: '#30BDA7',
  34340. * lineWidth: 2,
  34341. * fill: 'white'
  34342. * }
  34343. * }, {
  34344. * type: 'line',
  34345. * fill: true,
  34346. * style: {
  34347. * fill: '#96D4C6',
  34348. * fillOpacity: .6,
  34349. * stroke: '#0A3F50',
  34350. * strokeOpacity: .6,
  34351. * },
  34352. * xField: 'name',
  34353. * yField: 'data2',
  34354. * marker: {
  34355. * type: 'circle',
  34356. * radius: 4,
  34357. * lineWidth: 2,
  34358. * fill: 'white'
  34359. * }
  34360. * }]
  34361. * });
  34362. *
  34363. * In this configuration we're adding two series (or lines), one bound to the `data1`
  34364. * property of the store and the other to `data3`. The type for both configurations is
  34365. * `line`. The `xField` for both series is the same, the `name` property of the store.
  34366. * Both line series share the same axis, the left axis. You can set particular marker
  34367. * configuration by adding properties onto the marker object. Both series have
  34368. * an object as highlight so that markers animate smoothly to the properties in highlight
  34369. * when hovered. The second series has `fill = true` which means that the line will also
  34370. * have an area below it of the same color.
  34371. *
  34372. * **Note:** In the series definition remember to explicitly set the axis to bind the
  34373. * values of the line series to. This can be done by using the `axis` configuration property.
  34374. */
  34375. Ext.define('Ext.chart.series.Line', {
  34376. extend: 'Ext.chart.series.Cartesian',
  34377. alias: 'series.line',
  34378. type: 'line',
  34379. seriesType: 'lineSeries',
  34380. isLine: true,
  34381. requires: [
  34382. 'Ext.chart.series.sprite.Line'
  34383. ],
  34384. config: {
  34385. /**
  34386. * @cfg {Number} selectionTolerance
  34387. * The offset distance from the cursor position to the line series to trigger events
  34388. * (then used for highlighting series, etc).
  34389. */
  34390. selectionTolerance: 20,
  34391. /**
  34392. * @cfg {Object} curve
  34393. * The type of curve that connects the data points.
  34394. * Please see {@link Ext.chart.series.sprite.Line#curve line sprite documentation}
  34395. * for the full description.
  34396. */
  34397. curve: {
  34398. type: 'linear'
  34399. },
  34400. /**
  34401. * @cfg {Object} style
  34402. * An object containing styles for the visualization lines. These styles will override
  34403. * the theme styles.
  34404. * Some options contained within the style object will are described next.
  34405. */
  34406. /**
  34407. * @cfg {Boolean} smooth
  34408. * `true` if the series' line should be smoothed.
  34409. * Line smoothing only works with gapless data.
  34410. * @deprecated 6.5.0 Use the {@link #curve} config instead.
  34411. */
  34412. smooth: null,
  34413. /**
  34414. * @cfg {Boolean} step
  34415. * If set to `true`, the line uses steps instead of straight lines to connect the dots.
  34416. * It is ignored if `smooth` is true.
  34417. * @deprecated 6.5.0 Use the {@link #curve} config instead.
  34418. */
  34419. step: null,
  34420. /**
  34421. * @cfg {"gap"/"connect"/"origin"} [nullStyle="gap"]
  34422. * Possible values:
  34423. * 'gap' - null points are rendered as gaps.
  34424. * 'connect' - non-null points are connected across null points, so that
  34425. * there is no gap, unless null points are at the beginning/end of the line.
  34426. * Only the visible data points are connected - if a visible data point
  34427. * is followed by a series of null points that go off screen and eventually
  34428. * terminate with a non-null point, the connection won't be made.
  34429. * 'origin' - null data points are rendered at the origin,
  34430. * which is the y-coordinate of a point where the x and y axes meet.
  34431. * This requires that at least the x-coordinate of a point is a valid value.
  34432. */
  34433. nullStyle: 'gap',
  34434. /**
  34435. * @cfg {Boolean} fill
  34436. * If set to `true`, the area underneath the line is filled with the color defined
  34437. * as follows, listed by priority:
  34438. * - The color that is configured for this series ({@link Ext.chart.series.Series#colors}).
  34439. * - The color that is configured for this chart ({@link Ext.chart.AbstractChart#colors}).
  34440. * - The fill color that is set in the {@link #style} config.
  34441. * - The stroke color that is set in the {@link #style} config, or the same color
  34442. * as the line.
  34443. *
  34444. * Note: Do not confuse `series.config.fill` (which is a boolean) with `series.style.fill'
  34445. * (which is an alias for the `fillStyle` property and contains a color). For compatibility
  34446. * with previous versions of the API, if `config.fill` is undefined but a `style.fill' color
  34447. * is provided, `config.fill` is considered true. So the default value below must be
  34448. * undefined, not false.
  34449. */
  34450. fill: undefined,
  34451. aggregator: {
  34452. strategy: 'double'
  34453. }
  34454. },
  34455. themeMarkerCount: function() {
  34456. return 1;
  34457. },
  34458. /**
  34459. * @private
  34460. * Override {@link Ext.chart.series.Series#getDefaultSpriteConfig}
  34461. */
  34462. getDefaultSpriteConfig: function() {
  34463. var me = this,
  34464. parentConfig = me.callParent(arguments),
  34465. style = Ext.apply({}, me.getStyle()),
  34466. styleWithTheme,
  34467. fillArea = false;
  34468. if (me.config.fill !== undefined) {
  34469. // If config.fill is present but there is no fillStyle, then use the
  34470. // strokeStyle to fill (and paint the area the same color as the line).
  34471. if (me.config.fill) {
  34472. fillArea = true;
  34473. if (style.fillStyle === undefined) {
  34474. if (style.strokeStyle === undefined) {
  34475. styleWithTheme = me.getStyleWithTheme();
  34476. style.fillStyle = styleWithTheme.fillStyle;
  34477. style.strokeStyle = styleWithTheme.strokeStyle;
  34478. } else {
  34479. style.fillStyle = style.strokeStyle;
  34480. }
  34481. }
  34482. }
  34483. } else {
  34484. // For compatibility with previous versions of the API, if config.fill
  34485. // is undefined but style.fillStyle is provided, we fill the area.
  34486. if (style.fillStyle) {
  34487. fillArea = true;
  34488. }
  34489. }
  34490. // If we don't fill, then delete the fillStyle because that's what is used by
  34491. // the Line sprite to fill below the line.
  34492. if (!fillArea) {
  34493. delete style.fillStyle;
  34494. }
  34495. style = Ext.apply(parentConfig || {}, style);
  34496. return Ext.apply(style, {
  34497. fillArea: fillArea,
  34498. selectionTolerance: me.config.selectionTolerance
  34499. });
  34500. },
  34501. updateFill: function(fill) {
  34502. this.withSprite(function(sprite) {
  34503. return sprite.setAttributes({
  34504. fillArea: fill
  34505. });
  34506. });
  34507. },
  34508. updateCurve: function(curve) {
  34509. this.withSprite(function(sprite) {
  34510. return sprite.setAttributes({
  34511. curve: curve
  34512. });
  34513. });
  34514. },
  34515. getCurve: function() {
  34516. return this.withSprite(function(sprite) {
  34517. return sprite.attr.curve;
  34518. });
  34519. },
  34520. updateNullStyle: function(nullStyle) {
  34521. this.withSprite(function(sprite) {
  34522. return sprite.setAttributes({
  34523. nullStyle: nullStyle
  34524. });
  34525. });
  34526. },
  34527. updateSmooth: function(smooth) {
  34528. this.setCurve({
  34529. type: smooth ? 'natural' : 'linear'
  34530. });
  34531. },
  34532. updateStep: function(step) {
  34533. this.setCurve({
  34534. type: step ? 'step-after' : 'linear'
  34535. });
  34536. }
  34537. });
  34538. /**
  34539. * @class Ext.chart.series.sprite.PieSlice
  34540. *
  34541. * Pie slice sprite.
  34542. */
  34543. Ext.define('Ext.chart.series.sprite.PieSlice', {
  34544. extend: 'Ext.draw.sprite.Sector',
  34545. mixins: {
  34546. markerHolder: 'Ext.chart.MarkerHolder'
  34547. },
  34548. alias: 'sprite.pieslice',
  34549. inheritableStatics: {
  34550. def: {
  34551. processors: {
  34552. /**
  34553. * @cfg {Boolean} [doCallout=true]
  34554. * 'true' if the pie series uses label callouts.
  34555. */
  34556. doCallout: 'bool',
  34557. /**
  34558. * @cfg {String} [label='']
  34559. * Label associated with the Pie sprite.
  34560. */
  34561. label: 'string',
  34562. // @deprecated Use series.label.orientation config instead.
  34563. // @since 5.0.1
  34564. rotateLabels: 'bool',
  34565. /**
  34566. * @cfg {Number} [labelOverflowPadding=10]
  34567. * Padding around labels to determine overlap.
  34568. * Any negative number allows the labels to overlap.
  34569. */
  34570. labelOverflowPadding: 'number',
  34571. renderer: 'default'
  34572. },
  34573. defaults: {
  34574. doCallout: true,
  34575. rotateLabels: true,
  34576. label: '',
  34577. labelOverflowPadding: 10,
  34578. renderer: null
  34579. }
  34580. }
  34581. },
  34582. config: {
  34583. /**
  34584. * @private
  34585. * @cfg {Object} rendererData The object that is passed to the renderer.
  34586. *
  34587. * For instance when the PieSlice sprite is used in a Gauge chart, the object
  34588. * contains the 'store' and 'angleField' properties, and the 'value' as well
  34589. * for that one PieSlice that is used to draw the needle of the Gauge.
  34590. */
  34591. rendererData: null,
  34592. rendererIndex: 0,
  34593. series: null
  34594. },
  34595. setGradientBBox: function(ctx, rect) {
  34596. var me = this,
  34597. attr = me.attr,
  34598. hasGradients = (attr.fillStyle && attr.fillStyle.isGradient) || (attr.strokeStyle && attr.strokeStyle.isGradient);
  34599. if (hasGradients && !attr.constrainGradients) {
  34600. // eslint-disable-next-line vars-on-top, one-var
  34601. var midAngle = me.getMidAngle(),
  34602. margin = attr.margin,
  34603. cx = attr.centerX,
  34604. cy = attr.centerY,
  34605. r = attr.endRho,
  34606. matrix = attr.matrix,
  34607. scaleX = matrix.getScaleX(),
  34608. scaleY = matrix.getScaleY(),
  34609. w = scaleX * r,
  34610. h = scaleY * r,
  34611. bbox = {
  34612. width: w + w,
  34613. height: h + h
  34614. };
  34615. if (margin) {
  34616. cx += margin * Math.cos(midAngle);
  34617. cy += margin * Math.sin(midAngle);
  34618. }
  34619. bbox.x = matrix.x(cx, cy) - w;
  34620. bbox.y = matrix.y(cx, cy) - h;
  34621. ctx.setGradientBBox(bbox);
  34622. } else {
  34623. me.callParent([
  34624. ctx,
  34625. rect
  34626. ]);
  34627. }
  34628. },
  34629. render: function(surface, ctx, rect) {
  34630. var me = this,
  34631. attr = me.attr,
  34632. itemCfg = {},
  34633. changes;
  34634. if (attr.renderer) {
  34635. itemCfg = {
  34636. type: 'sector',
  34637. centerX: attr.centerX,
  34638. centerY: attr.centerY,
  34639. margin: attr.margin,
  34640. startAngle: Math.min(attr.startAngle, attr.endAngle),
  34641. endAngle: Math.max(attr.startAngle, attr.endAngle),
  34642. startRho: Math.min(attr.startRho, attr.endRho),
  34643. endRho: Math.max(attr.startRho, attr.endRho)
  34644. };
  34645. changes = Ext.callback(attr.renderer, null, [
  34646. me,
  34647. itemCfg,
  34648. me.getRendererData(),
  34649. me.getRendererIndex()
  34650. ], 0, me.getSeries());
  34651. me.setAttributes(changes);
  34652. me.useAttributes(ctx, rect);
  34653. }
  34654. // Draw the sector
  34655. me.callParent([
  34656. surface,
  34657. ctx,
  34658. rect
  34659. ]);
  34660. // Draw the labels
  34661. if (attr.label && me.getMarker('labels')) {
  34662. me.placeLabel();
  34663. }
  34664. },
  34665. placeLabel: function() {
  34666. var me = this,
  34667. attr = me.attr,
  34668. attributeId = attr.attributeId,
  34669. startAngle = Math.min(attr.startAngle, attr.endAngle),
  34670. endAngle = Math.max(attr.startAngle, attr.endAngle),
  34671. midAngle = (startAngle + endAngle) * 0.5,
  34672. margin = attr.margin,
  34673. centerX = attr.centerX,
  34674. centerY = attr.centerY,
  34675. sinMidAngle = Math.sin(midAngle),
  34676. cosMidAngle = Math.cos(midAngle),
  34677. startRho = Math.min(attr.startRho, attr.endRho) + margin,
  34678. endRho = Math.max(attr.startRho, attr.endRho) + margin,
  34679. midRho = (startRho + endRho) * 0.5,
  34680. surfaceMatrix = me.surfaceMatrix,
  34681. labelCfg = me.labelCfg || (me.labelCfg = {}),
  34682. label = me.getMarker('labels'),
  34683. labelTpl = label.getTemplate(),
  34684. hideLessThan = labelTpl.getHideLessThan(),
  34685. calloutLine = labelTpl.getCalloutLine(),
  34686. labelBox, x, y, changes, params, calloutLineLength;
  34687. if (calloutLine) {
  34688. calloutLineLength = calloutLine.length || 40;
  34689. } else {
  34690. calloutLineLength = 0;
  34691. }
  34692. surfaceMatrix.appendMatrix(attr.matrix);
  34693. labelCfg.text = attr.label;
  34694. x = centerX + cosMidAngle * midRho;
  34695. y = centerY + sinMidAngle * midRho;
  34696. labelCfg.x = surfaceMatrix.x(x, y);
  34697. labelCfg.y = surfaceMatrix.y(x, y);
  34698. x = centerX + cosMidAngle * endRho;
  34699. y = centerY + sinMidAngle * endRho;
  34700. labelCfg.calloutStartX = surfaceMatrix.x(x, y);
  34701. labelCfg.calloutStartY = surfaceMatrix.y(x, y);
  34702. x = centerX + cosMidAngle * (endRho + calloutLineLength);
  34703. y = centerY + sinMidAngle * (endRho + calloutLineLength);
  34704. labelCfg.calloutPlaceX = surfaceMatrix.x(x, y);
  34705. labelCfg.calloutPlaceY = surfaceMatrix.y(x, y);
  34706. if (!attr.rotateLabels) {
  34707. labelCfg.rotationRads = 0;
  34708. //<debug>
  34709. Ext.log.warn("'series.style.rotateLabels' config is deprecated. " + "Use 'series.label.orientation' config instead.");
  34710. } else //</debug>
  34711. {
  34712. switch (labelTpl.attr.orientation) {
  34713. case 'horizontal':
  34714. labelCfg.rotationRads = midAngle + Math.atan2(surfaceMatrix.y(1, 0) - surfaceMatrix.y(0, 0), surfaceMatrix.x(1, 0) - surfaceMatrix.x(0, 0)) + Math.PI / 2;
  34715. break;
  34716. case 'vertical':
  34717. labelCfg.rotationRads = midAngle + Math.atan2(surfaceMatrix.y(1, 0) - surfaceMatrix.y(0, 0), surfaceMatrix.x(1, 0) - surfaceMatrix.x(0, 0));
  34718. break;
  34719. }
  34720. }
  34721. labelCfg.calloutColor = (calloutLine && calloutLine.color) || me.attr.fillStyle;
  34722. if (calloutLine) {
  34723. if (calloutLine.width) {
  34724. labelCfg.calloutWidth = calloutLine.width;
  34725. }
  34726. } else {
  34727. labelCfg.calloutColor = 'none';
  34728. }
  34729. labelCfg.globalAlpha = attr.globalAlpha * attr.fillOpacity;
  34730. // If a slice is empty, don't display the label.
  34731. // This behavior can be overridden by a renderer.
  34732. if (labelTpl.display !== 'none') {
  34733. // eslint-disable-next-line eqeqeq
  34734. labelCfg.hidden = (attr.startAngle == attr.endAngle);
  34735. }
  34736. if (labelTpl.attr.renderer) {
  34737. // Note: the labels are 'put' by the Ext.chart.series.Pie.updateLabelData, so we can
  34738. // be sure the label sprite instances will exist and can be accessed from the label
  34739. // renderer on first render. For example, with 'bar' series this isn't the case,
  34740. // so we make a check and create a label instance if necessary.
  34741. params = [
  34742. me.attr.label,
  34743. label,
  34744. labelCfg,
  34745. me.getRendererData(),
  34746. me.getRendererIndex()
  34747. ];
  34748. changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
  34749. if (typeof changes === 'string') {
  34750. labelCfg.text = changes;
  34751. } else {
  34752. Ext.apply(labelCfg, changes);
  34753. }
  34754. }
  34755. me.putMarker('labels', labelCfg, attributeId);
  34756. labelBox = me.getMarkerBBox('labels', attributeId, true);
  34757. if (labelBox) {
  34758. if (attr.doCallout && ((endAngle - startAngle) * endRho > hideLessThan || attr.highlighted)) {
  34759. if (labelTpl.attr.display === 'outside') {
  34760. me.putMarker('labels', {
  34761. callout: 1
  34762. }, attributeId);
  34763. } else if (labelTpl.attr.display === 'inside') {
  34764. me.putMarker('labels', {
  34765. callout: 0
  34766. }, attributeId);
  34767. } else {
  34768. me.putMarker('labels', {
  34769. callout: 1 - me.sliceContainsLabel(attr, labelBox)
  34770. }, attributeId);
  34771. }
  34772. } else {
  34773. me.putMarker('labels', {
  34774. globalAlpha: me.sliceContainsLabel(attr, labelBox)
  34775. }, attributeId);
  34776. }
  34777. }
  34778. },
  34779. sliceContainsLabel: function(attr, bbox) {
  34780. var padding = attr.labelOverflowPadding,
  34781. middle = (attr.endRho + attr.startRho) / 2,
  34782. outer = middle + (bbox.width + padding) / 2,
  34783. inner = middle - (bbox.width + padding) / 2,
  34784. sliceAngle, l1, l2, l3;
  34785. if (padding < 0) {
  34786. return 1;
  34787. }
  34788. if (bbox.width + padding * 2 > (attr.endRho - attr.startRho)) {
  34789. return 0;
  34790. }
  34791. l1 = Math.sqrt(attr.endRho * attr.endRho - outer * outer);
  34792. l2 = Math.sqrt(attr.endRho * attr.endRho - inner * inner);
  34793. sliceAngle = Math.abs(attr.endAngle - attr.startAngle);
  34794. l3 = (sliceAngle > Math.PI / 2 ? inner : Math.abs(Math.tan(sliceAngle / 2)) * inner);
  34795. if (bbox.height + padding * 2 > Math.min(l1, l2, l3) * 2) {
  34796. return 0;
  34797. }
  34798. return 1;
  34799. }
  34800. });
  34801. /**
  34802. * @class Ext.chart.series.Pie
  34803. * @extends Ext.chart.series.Polar
  34804. *
  34805. * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display
  34806. * quantitative information for different categories that also have a meaning as a whole.
  34807. * As with all other series, the Pie Series must be appended in the *series* Chart array
  34808. * configuration. See the Chart documentation for more information. A typical configuration
  34809. * object for the pie series could be:
  34810. *
  34811. * @example
  34812. * Ext.create({
  34813. * xtype: 'polar',
  34814. * renderTo: document.body,
  34815. * width: 400,
  34816. * height: 400,
  34817. * theme: 'green',
  34818. * interactions: ['rotate', 'itemhighlight'],
  34819. * store: {
  34820. * fields: ['name', 'data1'],
  34821. * data: [{
  34822. * name: 'metric one',
  34823. * data1: 14
  34824. * }, {
  34825. * name: 'metric two',
  34826. * data1: 16
  34827. * }, {
  34828. * name: 'metric three',
  34829. * data1: 14
  34830. * }, {
  34831. * name: 'metric four',
  34832. * data1: 6
  34833. * }, {
  34834. * name: 'metric five',
  34835. * data1: 36
  34836. * }]
  34837. * },
  34838. * series: {
  34839. * type: 'pie',
  34840. * highlight: true,
  34841. * angleField: 'data1',
  34842. * label: {
  34843. * field: 'name',
  34844. * display: 'rotate'
  34845. * },
  34846. * donut: 30
  34847. * }
  34848. * });
  34849. *
  34850. * In this configuration we set `pie` as the type for the series, then set the `highlight` config
  34851. * to `true` (we can also specify an object with specific style properties for highlighting options)
  34852. * which is triggered when hovering or tapping elements.
  34853. * We set `data1` as the value of the `angleField` to determine the angular span for each pie slice.
  34854. * We also set a label configuration object where we set the name of the store field
  34855. * to be rendered as text for the label. The labels will also be displayed rotated.
  34856. * And finally, we specify the donut hole radius for the pie series in percentages of the series
  34857. * radius.
  34858. *
  34859. */
  34860. Ext.define('Ext.chart.series.Pie', {
  34861. extend: 'Ext.chart.series.Polar',
  34862. requires: [
  34863. 'Ext.chart.series.sprite.PieSlice'
  34864. ],
  34865. type: 'pie',
  34866. alias: 'series.pie',
  34867. seriesType: 'pieslice',
  34868. isPie: true,
  34869. config: {
  34870. /**
  34871. * @cfg {String} radiusField
  34872. * The store record field name to be used for the pie slice lengths.
  34873. * The values bound to this field name must be positive real numbers.
  34874. */
  34875. /**
  34876. * @cfg {Number} donut Specifies the radius of the donut hole, as a percentage
  34877. * of the chart's radius.
  34878. * Defaults to 0 (no donut hole).
  34879. */
  34880. donut: 0,
  34881. /**
  34882. * @cfg {Number} rotation The starting angle of the pie slices.
  34883. */
  34884. rotation: 0,
  34885. /**
  34886. * @cfg {Boolean} clockwise
  34887. * Whether the pie slices are displayed clockwise. Default's true.
  34888. */
  34889. clockwise: true,
  34890. /**
  34891. * @cfg {Number} [totalAngle=2*PI] The total angle of the pie series.
  34892. */
  34893. totalAngle: 2 * Math.PI,
  34894. /**
  34895. * @cfg {Array} hidden Determines which pie slices are hidden.
  34896. */
  34897. hidden: [],
  34898. /**
  34899. * @cfg {Number} [radiusFactor=100] Allows adjustment of the radius
  34900. * by a specific percentage.
  34901. */
  34902. radiusFactor: 100,
  34903. /**
  34904. * @cfg {Ext.chart.series.sprite.PieSlice/Object} highlightCfg
  34905. * Default highlight config for the pie series.
  34906. * Slides highlighted pie sector outward by default.
  34907. *
  34908. * highlightCfg accepts as its value a config object (or array of configs) for a
  34909. * {@link Ext.chart.series.sprite.PieSlice pie sprite}.
  34910. *
  34911. *
  34912. * Example config:
  34913. *
  34914. * Ext.create('Ext.chart.PolarChart', {
  34915. * renderTo: document.body,
  34916. * width: 600,
  34917. * height: 400,
  34918. * innerPadding: 5,
  34919. * store: {
  34920. * fields: ['name', 'data1'],
  34921. * data: [{
  34922. * name: 'metric one',
  34923. * data1: 10
  34924. * }, {
  34925. * name: 'metric two',
  34926. * data1: 7
  34927. * }, {
  34928. * name: 'metric three',
  34929. * data1: 5
  34930. * }]
  34931. * },
  34932. * series: {
  34933. * type: 'pie',
  34934. * label: {
  34935. * field: 'name',
  34936. * display: 'rotate'
  34937. * },
  34938. * xField: 'data1',
  34939. * donut: 30,
  34940. * highlightCfg: {
  34941. * margin: 10,
  34942. * fillOpacity: .7
  34943. * }
  34944. * }
  34945. * });
  34946. */
  34947. highlightCfg: {
  34948. margin: 20
  34949. },
  34950. style: {}
  34951. },
  34952. directions: [
  34953. 'X'
  34954. ],
  34955. applyLabel: function(newLabel, oldLabel) {
  34956. if (Ext.isObject(newLabel) && !Ext.isString(newLabel.orientation)) {
  34957. // Override default label orientation from '' to 'vertical'.
  34958. Ext.apply(newLabel = Ext.Object.chain(newLabel), {
  34959. orientation: 'vertical'
  34960. });
  34961. }
  34962. return this.callParent([
  34963. newLabel,
  34964. oldLabel
  34965. ]);
  34966. },
  34967. updateLabelData: function() {
  34968. var me = this,
  34969. store = me.getStore(),
  34970. items = store.getData().items,
  34971. sprites = me.getSprites(),
  34972. label = me.getLabel(),
  34973. labelField = label && label.getTemplate().getField(),
  34974. hidden = me.getHidden(),
  34975. i, ln, labels, sprite;
  34976. if (sprites.length && labelField) {
  34977. labels = [];
  34978. for (i = 0 , ln = items.length; i < ln; i++) {
  34979. labels.push(items[i].get(labelField));
  34980. }
  34981. for (i = 0 , ln = sprites.length; i < ln; i++) {
  34982. sprite = sprites[i];
  34983. sprite.setAttributes({
  34984. label: labels[i]
  34985. });
  34986. sprite.putMarker('labels', {
  34987. hidden: hidden[i]
  34988. }, sprite.attr.attributeId);
  34989. }
  34990. }
  34991. },
  34992. coordinateX: function() {
  34993. var me = this,
  34994. store = me.getStore(),
  34995. records = store.getData().items,
  34996. recordCount = records.length,
  34997. xField = me.getXField(),
  34998. yField = me.getYField(),
  34999. x,
  35000. sumX = 0,
  35001. unit, y,
  35002. maxY = 0,
  35003. hidden = me.getHidden(),
  35004. summation = [],
  35005. i,
  35006. lastAngle = 0,
  35007. totalAngle = me.getTotalAngle(),
  35008. clockwise = me.getClockwise() ? 1 : -1,
  35009. sprites = me.getSprites(),
  35010. sprite, labels;
  35011. if (!sprites) {
  35012. return;
  35013. }
  35014. for (i = 0; i < recordCount; i++) {
  35015. x = Math.abs(Number(records[i].get(xField))) || 0;
  35016. y = yField && Math.abs(Number(records[i].get(yField))) || 0;
  35017. if (!hidden[i]) {
  35018. sumX += x;
  35019. if (y > maxY) {
  35020. maxY = y;
  35021. }
  35022. }
  35023. summation[i] = sumX;
  35024. if (i >= hidden.length) {
  35025. hidden[i] = false;
  35026. }
  35027. }
  35028. hidden.length = recordCount;
  35029. me.maxY = maxY;
  35030. if (sumX !== 0) {
  35031. unit = totalAngle / sumX;
  35032. }
  35033. for (i = 0; i < recordCount; i++) {
  35034. sprites[i].setAttributes({
  35035. startAngle: lastAngle,
  35036. endAngle: lastAngle = (unit ? clockwise * summation[i] * unit : 0),
  35037. globalAlpha: 1
  35038. });
  35039. }
  35040. if (recordCount < sprites.length) {
  35041. for (i = recordCount; i < sprites.length; i++) {
  35042. sprite = sprites[i];
  35043. labels = sprite.getMarker('labels');
  35044. if (labels) {
  35045. // Don't want the 'labels' Markers and its 'template' sprite to be destroyed
  35046. // with the PieSlice MarkerHolder, as it is also used by other pie slices.
  35047. // So we release 'labels' before destroying the PieSlice.
  35048. // But first, we have to clear the instances of the 'labels'
  35049. // Markers created by the PieSlice MarkerHolder.
  35050. labels.clear(sprite.getId());
  35051. sprite.releaseMarker('labels');
  35052. }
  35053. sprite.destroy();
  35054. }
  35055. sprites.length = recordCount;
  35056. }
  35057. for (i = recordCount; i < sprites.length; i++) {
  35058. sprites[i].setAttributes({
  35059. startAngle: totalAngle,
  35060. endAngle: totalAngle,
  35061. globalAlpha: 0
  35062. });
  35063. }
  35064. },
  35065. updateCenter: function(center) {
  35066. this.setStyle({
  35067. translationX: center[0] + this.getOffsetX(),
  35068. translationY: center[1] + this.getOffsetY()
  35069. });
  35070. this.doUpdateStyles();
  35071. },
  35072. updateRadius: function(radius) {
  35073. this.setStyle({
  35074. startRho: radius * this.getDonut() * 0.01,
  35075. endRho: radius * this.getRadiusFactor() * 0.01
  35076. });
  35077. this.doUpdateStyles();
  35078. },
  35079. getStyleByIndex: function(i) {
  35080. var me = this,
  35081. store = me.getStore(),
  35082. item = store.getAt(i),
  35083. yField = me.getYField(),
  35084. radius = me.getRadius(),
  35085. style = {},
  35086. startRho, endRho, y;
  35087. if (item) {
  35088. y = yField && Math.abs(Number(item.get(yField))) || 0;
  35089. startRho = radius * me.getDonut() * 0.01;
  35090. endRho = radius * me.getRadiusFactor() * 0.01;
  35091. style = me.callParent([
  35092. i
  35093. ]);
  35094. style.startRho = startRho;
  35095. style.endRho = me.maxY ? (startRho + (endRho - startRho) * y / me.maxY) : endRho;
  35096. }
  35097. return style;
  35098. },
  35099. updateDonut: function(donut) {
  35100. var radius = this.getRadius();
  35101. this.setStyle({
  35102. startRho: radius * donut * 0.01,
  35103. endRho: radius * this.getRadiusFactor() * 0.01
  35104. });
  35105. this.doUpdateStyles();
  35106. },
  35107. // Subtract 90 degrees from rotation, so that `rotation` config's default
  35108. // value of 0 makes first pie sector start at noon, rather than 3 o'clock.
  35109. rotationOffset: -Math.PI / 2,
  35110. updateRotation: function(rotation) {
  35111. this.setStyle({
  35112. rotationRads: rotation + this.rotationOffset
  35113. });
  35114. this.doUpdateStyles();
  35115. },
  35116. updateTotalAngle: function(totalAngle) {
  35117. this.processData();
  35118. },
  35119. getSprites: function() {
  35120. var me = this,
  35121. chart = me.getChart(),
  35122. store = me.getStore();
  35123. if (!chart || !store) {
  35124. return Ext.emptyArray;
  35125. }
  35126. me.getColors();
  35127. me.getSubStyle();
  35128. // eslint-disable-next-line vars-on-top, one-var
  35129. var items = store.getData().items,
  35130. length = items.length,
  35131. animation = me.getAnimation() || chart && chart.getAnimation(),
  35132. sprites = me.sprites,
  35133. sprite,
  35134. spriteCreated = false,
  35135. spriteIndex = 0,
  35136. label = me.getLabel(),
  35137. labelTpl = label && label.getTemplate(),
  35138. i, rendererData;
  35139. rendererData = {
  35140. store: store,
  35141. field: me.getXField(),
  35142. // for backward compatibility only (deprecated in 5.5)
  35143. angleField: me.getXField(),
  35144. radiusField: me.getYField(),
  35145. series: me
  35146. };
  35147. for (i = 0; i < length; i++) {
  35148. sprite = sprites[i];
  35149. if (!sprite) {
  35150. sprite = me.createSprite();
  35151. if (me.getHighlight()) {
  35152. sprite.config.highlight = me.getHighlight();
  35153. sprite.addModifier('highlight', true);
  35154. }
  35155. if (labelTpl && labelTpl.getField()) {
  35156. labelTpl.setAttributes({
  35157. labelOverflowPadding: me.getLabelOverflowPadding()
  35158. });
  35159. labelTpl.getAnimation().setCustomDurations({
  35160. 'callout': 200
  35161. });
  35162. }
  35163. sprite.setAttributes(me.getStyleByIndex(i));
  35164. sprite.setRendererData(rendererData);
  35165. spriteCreated = true;
  35166. }
  35167. sprite.setRendererIndex(spriteIndex++);
  35168. sprite.setAnimation(animation);
  35169. }
  35170. if (spriteCreated) {
  35171. me.doUpdateStyles();
  35172. }
  35173. return me.sprites;
  35174. },
  35175. betweenAngle: function(x, a, b) {
  35176. var pp = Math.PI * 2,
  35177. offset = this.rotationOffset;
  35178. if (a === b) {
  35179. return false;
  35180. }
  35181. if (!this.getClockwise()) {
  35182. x *= -1;
  35183. a *= -1;
  35184. b *= -1;
  35185. a -= offset;
  35186. b -= offset;
  35187. } else {
  35188. a += offset;
  35189. b += offset;
  35190. }
  35191. x -= a;
  35192. b -= a;
  35193. // Normalize, so that both x and b are in the [0,360) interval.
  35194. x %= pp;
  35195. b %= pp;
  35196. x += pp;
  35197. b += pp;
  35198. x %= pp;
  35199. b %= pp;
  35200. // Because 360 * n angles will be normalized to 0,
  35201. // we need to treat b ~= 0 as a special case.
  35202. return x < b || Ext.Number.isEqual(b, 0, 1.0E-8);
  35203. },
  35204. getItemByIndex: function(index, category) {
  35205. category = category || 'sprites';
  35206. return this.callParent([
  35207. index,
  35208. category
  35209. ]);
  35210. },
  35211. /**
  35212. * Returns the pie slice for a given angle
  35213. * @param {Number} angle The angle to search for the slice
  35214. * @return {Object} An object containing the reocord, sprite, scope etc.
  35215. */
  35216. getItemForAngle: function(angle) {
  35217. var me = this,
  35218. sprites = me.getSprites(),
  35219. attr, store, items, hidden, i, ln;
  35220. angle %= Math.PI * 2;
  35221. while (angle < 0) {
  35222. angle += Math.PI * 2;
  35223. }
  35224. if (sprites) {
  35225. store = me.getStore();
  35226. items = store.getData().items;
  35227. hidden = me.getHidden();
  35228. for (i = 0 , ln = store.getCount(); i < ln; i++) {
  35229. if (!hidden[i]) {
  35230. // Fortunately, item's id equals its index in the instances list.
  35231. attr = sprites[i].attr;
  35232. if (attr.startAngle <= angle && attr.endAngle >= angle) {
  35233. return {
  35234. series: me,
  35235. sprite: sprites[i],
  35236. index: i,
  35237. record: items[i],
  35238. field: me.getXField()
  35239. };
  35240. }
  35241. }
  35242. }
  35243. }
  35244. return null;
  35245. },
  35246. getItemForPoint: function(x, y) {
  35247. var me = this,
  35248. sprites = me.getSprites(),
  35249. center = me.getCenter(),
  35250. offsetX = me.getOffsetX(),
  35251. offsetY = me.getOffsetY(),
  35252. // Distance from the center of the series to the cursor.
  35253. dx = x - center[0] + offsetX,
  35254. dy = y - center[1] + offsetY,
  35255. store = me.getStore(),
  35256. donut = me.getDonut(),
  35257. records = store.getData().items,
  35258. direction = Math.atan2(dy, dx) - me.getRotation(),
  35259. radius = Math.sqrt(dx * dx + dy * dy),
  35260. startRadius = me.getRadius() * donut * 0.01,
  35261. hidden = me.getHidden(),
  35262. result = null,
  35263. i, ln, attr, sprite;
  35264. for (i = 0 , ln = records.length; i < ln; i++) {
  35265. if (hidden[i]) {
  35266. continue;
  35267. }
  35268. sprite = sprites[i];
  35269. if (!sprite) {
  35270. break;
  35271. }
  35272. attr = sprite.attr;
  35273. if (radius >= startRadius + attr.margin && radius <= attr.endRho + attr.margin && me.betweenAngle(direction, attr.startAngle, attr.endAngle)) {
  35274. result = {
  35275. series: me,
  35276. sprite: sprites[i],
  35277. index: i,
  35278. record: records[i],
  35279. field: me.getXField()
  35280. };
  35281. break;
  35282. }
  35283. }
  35284. return result;
  35285. },
  35286. provideLegendInfo: function(target) {
  35287. var me = this,
  35288. store = me.getStore(),
  35289. items, labelField, xField, hidden, i, style, fill;
  35290. if (store) {
  35291. items = store.getData().items;
  35292. labelField = me.getLabel().getTemplate().getField();
  35293. xField = me.getXField();
  35294. hidden = me.getHidden();
  35295. for (i = 0; i < items.length; i++) {
  35296. style = me.getStyleByIndex(i);
  35297. fill = style.fillStyle;
  35298. if (Ext.isObject(fill)) {
  35299. fill = fill.stops && fill.stops[0].color;
  35300. }
  35301. target.push({
  35302. name: labelField ? String(items[i].get(labelField)) : xField + ' ' + i,
  35303. mark: fill || style.strokeStyle || 'black',
  35304. disabled: hidden[i],
  35305. series: me.getId(),
  35306. index: i
  35307. });
  35308. }
  35309. }
  35310. }
  35311. });
  35312. /**
  35313. * @class Ext.chart.series.sprite.Pie3DPart
  35314. * @extends Ext.draw.sprite.Path
  35315. *
  35316. * Pie3D series sprite.
  35317. */
  35318. Ext.define('Ext.chart.series.sprite.Pie3DPart', {
  35319. extend: 'Ext.draw.sprite.Path',
  35320. mixins: {
  35321. markerHolder: 'Ext.chart.MarkerHolder'
  35322. },
  35323. alias: 'sprite.pie3dPart',
  35324. inheritableStatics: {
  35325. def: {
  35326. processors: {
  35327. /**
  35328. * @cfg {Number} [centerX=0]
  35329. * The central point of the series on the x-axis.
  35330. */
  35331. centerX: 'number',
  35332. /**
  35333. * @cfg {Number} [centerY=0]
  35334. * The central point of the series on the x-axis.
  35335. */
  35336. centerY: 'number',
  35337. /**
  35338. * @cfg {Number} [startAngle=0]
  35339. * The starting angle of the polar series.
  35340. */
  35341. startAngle: 'number',
  35342. /**
  35343. * @cfg {Number} [endAngle=Math.PI]
  35344. * The ending angle of the polar series.
  35345. */
  35346. endAngle: 'number',
  35347. /**
  35348. * @cfg {Number} [startRho=0]
  35349. * The starting radius of the polar series.
  35350. */
  35351. startRho: 'number',
  35352. /**
  35353. * @cfg {Number} [endRho=150]
  35354. * The ending radius of the polar series.
  35355. */
  35356. endRho: 'number',
  35357. /**
  35358. * @cfg {Number} [margin=0]
  35359. * Margin from the center of the pie. Used for donut.
  35360. */
  35361. margin: 'number',
  35362. /**
  35363. * @cfg {Number} [thickness=0]
  35364. * The thickness of the 3D pie part.
  35365. */
  35366. thickness: 'number',
  35367. /**
  35368. * @cfg {Number} [bevelWidth=5]
  35369. * The size of the 3D pie bevel.
  35370. */
  35371. bevelWidth: 'number',
  35372. /**
  35373. * @cfg {Number} [distortion=0]
  35374. * The distortion of the 3D pie part.
  35375. */
  35376. distortion: 'number',
  35377. /**
  35378. * @cfg {Object} [baseColor='white']
  35379. * The color of the 3D pie part before adding the 3D effect.
  35380. */
  35381. baseColor: 'color',
  35382. /**
  35383. * @cfg {Number} [colorSpread=0.7]
  35384. * An attribute used to control how flat the gradient of the sprite looks.
  35385. * A value of 0 essentially means no gradient (flat color).
  35386. */
  35387. colorSpread: 'number',
  35388. /**
  35389. * @cfg {Number} [baseRotation=0]
  35390. * The starting rotation of the polar series.
  35391. */
  35392. baseRotation: 'number',
  35393. /**
  35394. * @cfg {String} [part='top']
  35395. * The part of the 3D Pie represented by the sprite.
  35396. */
  35397. part: 'enums(top,bottom,start,end,innerFront,innerBack,outerFront,outerBack)',
  35398. /**
  35399. * @cfg {String} [label='']
  35400. * The label associated with the 'top' part of the sprite.
  35401. */
  35402. label: 'string'
  35403. },
  35404. aliases: {
  35405. rho: 'endRho'
  35406. },
  35407. triggers: {
  35408. centerX: 'path,bbox',
  35409. centerY: 'path,bbox',
  35410. startAngle: 'path,partZIndex',
  35411. endAngle: 'path,partZIndex',
  35412. startRho: 'path',
  35413. endRho: 'path,bbox',
  35414. margin: 'path,bbox',
  35415. thickness: 'path',
  35416. distortion: 'path',
  35417. baseRotation: 'path,partZIndex',
  35418. baseColor: 'partZIndex,partColor',
  35419. colorSpread: 'partColor',
  35420. part: 'path,partZIndex',
  35421. globalAlpha: 'canvas,alpha',
  35422. fillOpacity: 'canvas,alpha'
  35423. },
  35424. defaults: {
  35425. centerX: 0,
  35426. centerY: 0,
  35427. startAngle: Math.PI * 2,
  35428. endAngle: Math.PI * 2,
  35429. startRho: 0,
  35430. endRho: 150,
  35431. margin: 0,
  35432. thickness: 35,
  35433. distortion: 0.5,
  35434. baseRotation: 0,
  35435. baseColor: 'white',
  35436. colorSpread: 0.5,
  35437. miterLimit: 1,
  35438. bevelWidth: 5,
  35439. strokeOpacity: 0,
  35440. part: 'top',
  35441. label: ''
  35442. },
  35443. updaters: {
  35444. alpha: 'alphaUpdater',
  35445. partColor: 'partColorUpdater',
  35446. partZIndex: 'partZIndexUpdater'
  35447. }
  35448. }
  35449. },
  35450. config: {
  35451. renderer: null,
  35452. rendererData: null,
  35453. rendererIndex: 0,
  35454. series: null
  35455. },
  35456. bevelParams: [],
  35457. constructor: function(config) {
  35458. this.callParent([
  35459. config
  35460. ]);
  35461. this.bevelGradient = new Ext.draw.gradient.Linear({
  35462. stops: [
  35463. {
  35464. offset: 0,
  35465. color: 'rgba(255,255,255,0)'
  35466. },
  35467. {
  35468. offset: 0.7,
  35469. color: 'rgba(255,255,255,0.6)'
  35470. },
  35471. {
  35472. offset: 1,
  35473. color: 'rgba(255,255,255,0)'
  35474. }
  35475. ]
  35476. });
  35477. },
  35478. updateRenderer: function() {
  35479. this.setDirty(true);
  35480. },
  35481. updateRendererData: function() {
  35482. this.setDirty(true);
  35483. },
  35484. updateRendererIndex: function() {
  35485. this.setDirty(true);
  35486. },
  35487. alphaUpdater: function(attr) {
  35488. var me = this,
  35489. opacity = attr.globalAlpha,
  35490. fillOpacity = attr.fillOpacity,
  35491. oldOpacity = me.oldOpacity,
  35492. oldFillOpacity = me.oldFillOpacity;
  35493. // Update the path when the sprite becomes translucent or completely opaque.
  35494. if ((opacity !== oldOpacity && (opacity === 1 || oldOpacity === 1)) || (fillOpacity !== oldFillOpacity && (fillOpacity === 1 || oldFillOpacity === 1))) {
  35495. me.scheduleUpdater(attr, 'path', [
  35496. 'globalAlpha'
  35497. ]);
  35498. me.oldOpacity = opacity;
  35499. me.oldFillOpacity = fillOpacity;
  35500. }
  35501. },
  35502. partColorUpdater: function(attr) {
  35503. var color = Ext.util.Color.fly(attr.baseColor),
  35504. colorString = color.toString(),
  35505. colorSpread = attr.colorSpread,
  35506. fillStyle;
  35507. switch (attr.part) {
  35508. case 'top':
  35509. fillStyle = new Ext.draw.gradient.Radial({
  35510. start: {
  35511. x: 0,
  35512. y: 0,
  35513. r: 0
  35514. },
  35515. end: {
  35516. x: 0,
  35517. y: 0,
  35518. r: 1
  35519. },
  35520. stops: [
  35521. {
  35522. offset: 0,
  35523. color: color.createLighter(0.1 * colorSpread)
  35524. },
  35525. {
  35526. offset: 1,
  35527. color: color.createDarker(0.1 * colorSpread)
  35528. }
  35529. ]
  35530. });
  35531. break;
  35532. case 'bottom':
  35533. fillStyle = new Ext.draw.gradient.Radial({
  35534. start: {
  35535. x: 0,
  35536. y: 0,
  35537. r: 0
  35538. },
  35539. end: {
  35540. x: 0,
  35541. y: 0,
  35542. r: 1
  35543. },
  35544. stops: [
  35545. {
  35546. offset: 0,
  35547. color: color.createDarker(0.2 * colorSpread)
  35548. },
  35549. {
  35550. offset: 1,
  35551. color: color.toString()
  35552. }
  35553. ]
  35554. });
  35555. break;
  35556. case 'outerFront':
  35557. case 'outerBack':
  35558. fillStyle = new Ext.draw.gradient.Linear({
  35559. stops: [
  35560. {
  35561. offset: 0,
  35562. color: color.createDarker(0.15 * colorSpread).toString()
  35563. },
  35564. {
  35565. offset: 0.3,
  35566. color: colorString
  35567. },
  35568. {
  35569. offset: 0.8,
  35570. color: color.createLighter(0.2 * colorSpread).toString()
  35571. },
  35572. {
  35573. offset: 1,
  35574. color: color.createDarker(0.25 * colorSpread).toString()
  35575. }
  35576. ]
  35577. });
  35578. break;
  35579. case 'start':
  35580. fillStyle = new Ext.draw.gradient.Linear({
  35581. stops: [
  35582. {
  35583. offset: 0,
  35584. color: color.createDarker(0.1 * colorSpread).toString()
  35585. },
  35586. {
  35587. offset: 1,
  35588. color: color.createLighter(0.2 * colorSpread).toString()
  35589. }
  35590. ]
  35591. });
  35592. break;
  35593. case 'end':
  35594. fillStyle = new Ext.draw.gradient.Linear({
  35595. stops: [
  35596. {
  35597. offset: 0,
  35598. color: color.createDarker(0.1 * colorSpread).toString()
  35599. },
  35600. {
  35601. offset: 1,
  35602. color: color.createLighter(0.2 * colorSpread).toString()
  35603. }
  35604. ]
  35605. });
  35606. break;
  35607. case 'innerFront':
  35608. case 'innerBack':
  35609. fillStyle = new Ext.draw.gradient.Linear({
  35610. stops: [
  35611. {
  35612. offset: 0,
  35613. color: color.createDarker(0.1 * colorSpread).toString()
  35614. },
  35615. {
  35616. offset: 0.2,
  35617. color: color.createLighter(0.2 * colorSpread).toString()
  35618. },
  35619. {
  35620. offset: 0.7,
  35621. color: colorString
  35622. },
  35623. {
  35624. offset: 1,
  35625. color: color.createDarker(0.1 * colorSpread).toString()
  35626. }
  35627. ]
  35628. });
  35629. break;
  35630. }
  35631. attr.fillStyle = fillStyle;
  35632. attr.canvasAttributes.fillStyle = fillStyle;
  35633. },
  35634. partZIndexUpdater: function(attr) {
  35635. var normalize = Ext.draw.sprite.AttributeParser.angle,
  35636. rotation = attr.baseRotation,
  35637. startAngle = attr.startAngle,
  35638. endAngle = attr.endAngle,
  35639. depth;
  35640. switch (attr.part) {
  35641. case 'top':
  35642. attr.zIndex = 6;
  35643. break;
  35644. case 'outerFront':
  35645. startAngle = normalize(startAngle + rotation);
  35646. endAngle = normalize(endAngle + rotation);
  35647. if (startAngle >= 0 && endAngle < 0) {
  35648. depth = Math.sin(startAngle);
  35649. } else if (startAngle <= 0 && endAngle > 0) {
  35650. depth = Math.sin(endAngle);
  35651. } else if (startAngle >= 0 && endAngle > 0) {
  35652. if (startAngle > endAngle) {
  35653. depth = 0;
  35654. } else {
  35655. depth = Math.max(Math.sin(startAngle), Math.sin(endAngle));
  35656. }
  35657. } else {
  35658. depth = 1;
  35659. };
  35660. attr.zIndex = 4 + depth;
  35661. break;
  35662. case 'outerBack':
  35663. attr.zIndex = 1;
  35664. break;
  35665. case 'start':
  35666. attr.zIndex = 4 + Math.sin(normalize(startAngle + rotation));
  35667. break;
  35668. case 'end':
  35669. attr.zIndex = 4 + Math.sin(normalize(endAngle + rotation));
  35670. break;
  35671. case 'innerFront':
  35672. attr.zIndex = 2;
  35673. break;
  35674. case 'innerBack':
  35675. attr.zIndex = 4 + Math.sin(normalize((startAngle + endAngle) / 2 + rotation));
  35676. break;
  35677. case 'bottom':
  35678. attr.zIndex = 0;
  35679. break;
  35680. }
  35681. attr.dirtyZIndex = true;
  35682. },
  35683. updatePlainBBox: function(plain) {
  35684. var attr = this.attr,
  35685. part = attr.part,
  35686. baseRotation = attr.baseRotation,
  35687. centerX = attr.centerX,
  35688. centerY = attr.centerY,
  35689. rho, angle, x, y, sin, cos;
  35690. if (part === 'start') {
  35691. angle = attr.startAngle + baseRotation;
  35692. } else if (part === 'end') {
  35693. angle = attr.endAngle + baseRotation;
  35694. }
  35695. if (Ext.isNumber(angle)) {
  35696. sin = Math.sin(angle);
  35697. cos = Math.cos(angle);
  35698. x = Math.min(centerX + cos * attr.startRho, centerX + cos * attr.endRho);
  35699. y = centerY + sin * attr.startRho * attr.distortion;
  35700. plain.x = x;
  35701. plain.y = y;
  35702. plain.width = cos * (attr.endRho - attr.startRho);
  35703. plain.height = attr.thickness + sin * (attr.endRho - attr.startRho) * 2;
  35704. return;
  35705. }
  35706. if (part === 'innerFront' || part === 'innerBack') {
  35707. rho = attr.startRho;
  35708. } else {
  35709. rho = attr.endRho;
  35710. }
  35711. plain.width = rho * 2;
  35712. plain.height = rho * attr.distortion * 2 + attr.thickness;
  35713. plain.x = attr.centerX - rho;
  35714. plain.y = attr.centerY - rho * attr.distortion;
  35715. },
  35716. updateTransformedBBox: function(transform) {
  35717. if (this.attr.part === 'start' || this.attr.part === 'end') {
  35718. return this.callParent(arguments);
  35719. }
  35720. return this.updatePlainBBox(transform);
  35721. },
  35722. updatePath: function(path) {
  35723. if (!this.attr.globalAlpha) {
  35724. return;
  35725. }
  35726. if (this.attr.endAngle < this.attr.startAngle) {
  35727. return;
  35728. }
  35729. this[this.attr.part + 'Renderer'](path);
  35730. },
  35731. render: function(surface, ctx, rect) {
  35732. var me = this,
  35733. renderer = me.getRenderer(),
  35734. attr = me.attr,
  35735. part = attr.part,
  35736. itemCfg, changes;
  35737. if (!attr.globalAlpha || Ext.Number.isEqual(attr.startAngle, attr.endAngle, 1.0E-8)) {
  35738. return;
  35739. }
  35740. if (renderer) {
  35741. itemCfg = {
  35742. type: 'pie3dPart',
  35743. part: attr.part,
  35744. margin: attr.margin,
  35745. distortion: attr.distortion,
  35746. centerX: attr.centerX,
  35747. centerY: attr.centerY,
  35748. baseRotation: attr.baseRotation,
  35749. startAngle: attr.startAngle,
  35750. endAngle: attr.endAngle,
  35751. startRho: attr.startRho,
  35752. endRho: attr.endRho
  35753. };
  35754. changes = Ext.callback(renderer, null, [
  35755. me,
  35756. itemCfg,
  35757. me.getRendererData(),
  35758. me.getRendererIndex()
  35759. ], 0, me.getSeries());
  35760. if (changes) {
  35761. if (changes.part) {
  35762. // Can't let users change the nature of the sprite.
  35763. changes.part = part;
  35764. }
  35765. me.setAttributes(changes);
  35766. me.useAttributes(ctx, rect);
  35767. }
  35768. }
  35769. me.callParent([
  35770. surface,
  35771. ctx
  35772. ]);
  35773. me.bevelRenderer(surface, ctx);
  35774. // Only the top part will have the label attribute (set by the series).
  35775. if (attr.label && me.getMarker('labels')) {
  35776. me.placeLabel();
  35777. }
  35778. },
  35779. placeLabel: function() {
  35780. var me = this,
  35781. attr = me.attr,
  35782. attributeId = attr.attributeId,
  35783. margin = attr.margin,
  35784. distortion = attr.distortion,
  35785. centerX = attr.centerX,
  35786. centerY = attr.centerY,
  35787. baseRotation = attr.baseRotation,
  35788. startAngle = attr.startAngle + baseRotation,
  35789. endAngle = attr.endAngle + baseRotation,
  35790. midAngle = (startAngle + endAngle) / 2,
  35791. startRho = attr.startRho + margin,
  35792. endRho = attr.endRho + margin,
  35793. midRho = (startRho + endRho) / 2,
  35794. sin = Math.sin(midAngle),
  35795. cos = Math.cos(midAngle),
  35796. surfaceMatrix = me.surfaceMatrix,
  35797. label = me.getMarker('labels'),
  35798. labelTpl = label.getTemplate(),
  35799. calloutLine = labelTpl.getCalloutLine(),
  35800. calloutLineLength = calloutLine && calloutLine.length || 40,
  35801. labelCfg = {},
  35802. rendererParams, rendererChanges, x, y;
  35803. surfaceMatrix.appendMatrix(attr.matrix);
  35804. labelCfg.text = attr.label;
  35805. x = centerX + cos * midRho;
  35806. y = centerY + sin * midRho * distortion;
  35807. labelCfg.x = surfaceMatrix.x(x, y);
  35808. labelCfg.y = surfaceMatrix.y(x, y);
  35809. x = centerX + cos * endRho;
  35810. y = centerY + sin * endRho * distortion;
  35811. labelCfg.calloutStartX = surfaceMatrix.x(x, y);
  35812. labelCfg.calloutStartY = surfaceMatrix.y(x, y);
  35813. x = centerX + cos * (endRho + calloutLineLength);
  35814. y = centerY + sin * (endRho + calloutLineLength) * distortion;
  35815. labelCfg.calloutPlaceX = surfaceMatrix.x(x, y);
  35816. labelCfg.calloutPlaceY = surfaceMatrix.y(x, y);
  35817. labelCfg.calloutWidth = 2;
  35818. if (labelTpl.attr.renderer) {
  35819. rendererParams = [
  35820. me.attr.label,
  35821. label,
  35822. labelCfg,
  35823. me.getRendererData(),
  35824. me.getRendererIndex()
  35825. ];
  35826. rendererChanges = Ext.callback(labelTpl.attr.renderer, null, rendererParams, 0, me.getSeries());
  35827. if (typeof rendererChanges === 'string') {
  35828. labelCfg.text = rendererChanges;
  35829. } else {
  35830. Ext.apply(labelCfg, rendererChanges);
  35831. }
  35832. }
  35833. me.putMarker('labels', labelCfg, attributeId);
  35834. me.putMarker('labels', {
  35835. callout: 1
  35836. }, attributeId);
  35837. },
  35838. bevelRenderer: function(surface, ctx) {
  35839. var me = this,
  35840. attr = me.attr,
  35841. bevelWidth = attr.bevelWidth,
  35842. params = me.bevelParams,
  35843. i;
  35844. for (i = 0; i < params.length; i++) {
  35845. ctx.beginPath();
  35846. ctx.ellipse.apply(ctx, params[i]);
  35847. ctx.save();
  35848. ctx.lineWidth = bevelWidth;
  35849. ctx.strokeOpacity = bevelWidth ? 1 : 0;
  35850. ctx.strokeGradient = me.bevelGradient;
  35851. ctx.stroke(attr);
  35852. ctx.restore();
  35853. }
  35854. },
  35855. lidRenderer: function(path, thickness) {
  35856. var attr = this.attr,
  35857. margin = attr.margin,
  35858. distortion = attr.distortion,
  35859. centerX = attr.centerX,
  35860. centerY = attr.centerY,
  35861. baseRotation = attr.baseRotation,
  35862. startAngle = attr.startAngle + baseRotation,
  35863. endAngle = attr.endAngle + baseRotation,
  35864. midAngle = (startAngle + endAngle) / 2,
  35865. startRho = attr.startRho,
  35866. endRho = attr.endRho,
  35867. sinEnd = Math.sin(endAngle),
  35868. cosEnd = Math.cos(endAngle);
  35869. centerX += Math.cos(midAngle) * margin;
  35870. centerY += Math.sin(midAngle) * margin * distortion;
  35871. path.ellipse(centerX, centerY + thickness, startRho, startRho * distortion, 0, startAngle, endAngle, false);
  35872. path.lineTo(centerX + cosEnd * endRho, centerY + thickness + sinEnd * endRho * distortion);
  35873. path.ellipse(centerX, centerY + thickness, endRho, endRho * distortion, 0, endAngle, startAngle, true);
  35874. path.closePath();
  35875. },
  35876. topRenderer: function(path) {
  35877. this.lidRenderer(path, 0);
  35878. },
  35879. bottomRenderer: function(path) {
  35880. var attr = this.attr,
  35881. none = Ext.util.Color.RGBA_NONE;
  35882. if (attr.globalAlpha < 1 || attr.fillOpacity < 1 || attr.shadowColor !== none) {
  35883. this.lidRenderer(path, attr.thickness);
  35884. }
  35885. },
  35886. sideRenderer: function(path, position) {
  35887. var attr = this.attr,
  35888. margin = attr.margin,
  35889. centerX = attr.centerX,
  35890. centerY = attr.centerY,
  35891. distortion = attr.distortion,
  35892. baseRotation = attr.baseRotation,
  35893. startAngle = attr.startAngle + baseRotation,
  35894. endAngle = attr.endAngle + baseRotation,
  35895. // eslint-disable-next-line max-len
  35896. isFullPie = (!attr.startAngle && Ext.Number.isEqual(Math.PI * 2, attr.endAngle, 1.0E-7)),
  35897. thickness = attr.thickness,
  35898. startRho = attr.startRho,
  35899. endRho = attr.endRho,
  35900. angle = (position === 'start' && startAngle) || (position === 'end' && endAngle),
  35901. sin = Math.sin(angle),
  35902. cos = Math.cos(angle),
  35903. isTranslucent = attr.globalAlpha < 1,
  35904. isVisible = position === 'start' && cos < 0 || position === 'end' && cos > 0 || isTranslucent,
  35905. midAngle;
  35906. if (isVisible && !isFullPie) {
  35907. midAngle = (startAngle + endAngle) / 2;
  35908. centerX += Math.cos(midAngle) * margin;
  35909. centerY += Math.sin(midAngle) * margin * distortion;
  35910. path.moveTo(centerX + cos * startRho, centerY + sin * startRho * distortion);
  35911. path.lineTo(centerX + cos * endRho, centerY + sin * endRho * distortion);
  35912. path.lineTo(centerX + cos * endRho, centerY + sin * endRho * distortion + thickness);
  35913. path.lineTo(centerX + cos * startRho, centerY + sin * startRho * distortion + thickness);
  35914. path.closePath();
  35915. }
  35916. },
  35917. startRenderer: function(path) {
  35918. this.sideRenderer(path, 'start');
  35919. },
  35920. endRenderer: function(path) {
  35921. this.sideRenderer(path, 'end');
  35922. },
  35923. rimRenderer: function(path, radius, isDonut, isFront) {
  35924. var me = this,
  35925. attr = me.attr,
  35926. margin = attr.margin,
  35927. centerX = attr.centerX,
  35928. centerY = attr.centerY,
  35929. distortion = attr.distortion,
  35930. baseRotation = attr.baseRotation,
  35931. normalize = Ext.draw.sprite.AttributeParser.angle,
  35932. startAngle = attr.startAngle + baseRotation,
  35933. endAngle = attr.endAngle + baseRotation,
  35934. // It's critical to use non-normalized start and end angles
  35935. // for middle angle calculation. Consider a situation where the
  35936. // start angle is +170 degrees and the end engle is -170 degrees
  35937. // after normalization (the middle angle is 0 then, but it should be 180 degrees).
  35938. midAngle = normalize((startAngle + endAngle) / 2),
  35939. thickness = attr.thickness,
  35940. isTranslucent = attr.globalAlpha < 1,
  35941. isAllFront, isAllBack, params;
  35942. me.bevelParams = [];
  35943. startAngle = normalize(startAngle);
  35944. endAngle = normalize(endAngle);
  35945. centerX += Math.cos(midAngle) * margin;
  35946. centerY += Math.sin(midAngle) * margin * distortion;
  35947. isAllFront = startAngle >= 0 && endAngle >= 0;
  35948. isAllBack = startAngle <= 0 && endAngle <= 0;
  35949. function renderLeftFrontChunk() {
  35950. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, Math.PI, startAngle, true);
  35951. path.lineTo(centerX + Math.cos(startAngle) * radius, centerY + Math.sin(startAngle) * radius * distortion);
  35952. params = [
  35953. centerX,
  35954. centerY,
  35955. radius,
  35956. radius * distortion,
  35957. 0,
  35958. startAngle,
  35959. Math.PI,
  35960. false
  35961. ];
  35962. if (!isDonut) {
  35963. me.bevelParams.push(params);
  35964. }
  35965. path.ellipse.apply(path, params);
  35966. path.closePath();
  35967. }
  35968. function renderRightFrontChunk() {
  35969. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, 0, endAngle, false);
  35970. path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion);
  35971. params = [
  35972. centerX,
  35973. centerY,
  35974. radius,
  35975. radius * distortion,
  35976. 0,
  35977. endAngle,
  35978. 0,
  35979. true
  35980. ];
  35981. if (!isDonut) {
  35982. me.bevelParams.push(params);
  35983. }
  35984. path.ellipse.apply(path, params);
  35985. path.closePath();
  35986. }
  35987. function renderLeftBackChunk() {
  35988. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, Math.PI, endAngle, false);
  35989. path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion);
  35990. params = [
  35991. centerX,
  35992. centerY,
  35993. radius,
  35994. radius * distortion,
  35995. 0,
  35996. endAngle,
  35997. Math.PI,
  35998. true
  35999. ];
  36000. if (isDonut) {
  36001. me.bevelParams.push(params);
  36002. }
  36003. path.ellipse.apply(path, params);
  36004. path.closePath();
  36005. }
  36006. function renderRightBackChunk() {
  36007. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, startAngle, 0, false);
  36008. path.lineTo(centerX + radius, centerY);
  36009. params = [
  36010. centerX,
  36011. centerY,
  36012. radius,
  36013. radius * distortion,
  36014. 0,
  36015. 0,
  36016. startAngle,
  36017. true
  36018. ];
  36019. if (isDonut) {
  36020. me.bevelParams.push(params);
  36021. }
  36022. path.ellipse.apply(path, params);
  36023. path.closePath();
  36024. }
  36025. if (isFront) {
  36026. if (!isDonut || isTranslucent) {
  36027. if (startAngle >= 0 && endAngle < 0) {
  36028. renderLeftFrontChunk();
  36029. } else if (startAngle <= 0 && endAngle > 0) {
  36030. renderRightFrontChunk();
  36031. } else if (startAngle <= 0 && endAngle < 0) {
  36032. if (startAngle > endAngle) {
  36033. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, 0, Math.PI, false);
  36034. path.lineTo(centerX - radius, centerY);
  36035. params = [
  36036. centerX,
  36037. centerY,
  36038. radius,
  36039. radius * distortion,
  36040. 0,
  36041. Math.PI,
  36042. 0,
  36043. true
  36044. ];
  36045. if (!isDonut) {
  36046. me.bevelParams.push(params);
  36047. }
  36048. path.ellipse.apply(path, params);
  36049. path.closePath();
  36050. }
  36051. } else {
  36052. // startAngle >= 0 && endAngle > 0
  36053. // obtuse horseshoe-like slice with the gap facing forward
  36054. if (startAngle > endAngle) {
  36055. renderLeftFrontChunk();
  36056. renderRightFrontChunk();
  36057. } else {
  36058. // acute slice facing forward
  36059. params = [
  36060. centerX,
  36061. centerY,
  36062. radius,
  36063. radius * distortion,
  36064. 0,
  36065. startAngle,
  36066. endAngle,
  36067. false
  36068. ];
  36069. if (isAllFront && !isDonut || isAllBack && isDonut) {
  36070. me.bevelParams.push(params);
  36071. }
  36072. path.ellipse.apply(path, params);
  36073. path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion + thickness);
  36074. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, endAngle, startAngle, true);
  36075. path.closePath();
  36076. }
  36077. }
  36078. }
  36079. } else {
  36080. if (isDonut || isTranslucent) {
  36081. if (startAngle >= 0 && endAngle < 0) {
  36082. renderLeftBackChunk();
  36083. } else if (startAngle <= 0 && endAngle > 0) {
  36084. renderRightBackChunk();
  36085. } else if (startAngle <= 0 && endAngle < 0) {
  36086. if (startAngle > endAngle) {
  36087. renderLeftBackChunk();
  36088. renderRightBackChunk();
  36089. } else {
  36090. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, startAngle, endAngle, false);
  36091. path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion);
  36092. params = [
  36093. centerX,
  36094. centerY,
  36095. radius,
  36096. radius * distortion,
  36097. 0,
  36098. endAngle,
  36099. startAngle,
  36100. true
  36101. ];
  36102. if (isDonut) {
  36103. me.bevelParams.push(params);
  36104. }
  36105. path.ellipse.apply(path, params);
  36106. path.closePath();
  36107. }
  36108. } else {
  36109. // startAngle >= 0 && endAngle > 0
  36110. if (startAngle > endAngle) {
  36111. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, -Math.PI, 0, false);
  36112. path.lineTo(centerX + radius, centerY);
  36113. params = [
  36114. centerX,
  36115. centerY,
  36116. radius,
  36117. radius * distortion,
  36118. 0,
  36119. 0,
  36120. -Math.PI,
  36121. true
  36122. ];
  36123. if (isDonut) {
  36124. me.bevelParams.push(params);
  36125. }
  36126. path.ellipse.apply(path, params);
  36127. path.closePath();
  36128. }
  36129. }
  36130. }
  36131. }
  36132. },
  36133. innerFrontRenderer: function(path) {
  36134. this.rimRenderer(path, this.attr.startRho, true, true);
  36135. },
  36136. innerBackRenderer: function(path) {
  36137. this.rimRenderer(path, this.attr.startRho, true, false);
  36138. },
  36139. outerFrontRenderer: function(path) {
  36140. this.rimRenderer(path, this.attr.endRho, false, true);
  36141. },
  36142. outerBackRenderer: function(path) {
  36143. this.rimRenderer(path, this.attr.endRho, false, false);
  36144. }
  36145. });
  36146. /**
  36147. * @class Ext.chart.series.Pie3D
  36148. * @extends Ext.chart.series.Polar
  36149. *
  36150. * Creates a 3D Pie Chart.
  36151. *
  36152. * **Note:** Labels, legends, and lines are not currently available when using the
  36153. * 3D Pie chart series.
  36154. *
  36155. * @example
  36156. * Ext.create({
  36157. * xtype: 'polar',
  36158. * renderTo: document.body,
  36159. * width: 600,
  36160. * height: 400,
  36161. * theme: 'green',
  36162. * interactions: 'rotate',
  36163. * store: {
  36164. * fields: ['data3'],
  36165. * data: [{
  36166. * 'data3': 14
  36167. * }, {
  36168. * 'data3': 16
  36169. * }, {
  36170. * 'data3': 14
  36171. * }, {
  36172. * 'data3': 6
  36173. * }, {
  36174. * 'data3': 36
  36175. * }]
  36176. * },
  36177. * series: {
  36178. * type: 'pie3d',
  36179. * angleField: 'data3',
  36180. * donut: 30
  36181. * }
  36182. * });
  36183. */
  36184. Ext.define('Ext.chart.series.Pie3D', {
  36185. extend: 'Ext.chart.series.Polar',
  36186. requires: [
  36187. 'Ext.chart.series.sprite.Pie3DPart',
  36188. 'Ext.draw.PathUtil'
  36189. ],
  36190. type: 'pie3d',
  36191. seriesType: 'pie3d',
  36192. alias: 'series.pie3d',
  36193. is3D: true,
  36194. config: {
  36195. rect: [
  36196. 0,
  36197. 0,
  36198. 0,
  36199. 0
  36200. ],
  36201. thickness: 35,
  36202. distortion: 0.5,
  36203. /**
  36204. * @cfg {String} angleField (required)
  36205. * The store record field name to be used for the pie angles.
  36206. * The values bound to this field name must be positive real numbers.
  36207. */
  36208. /**
  36209. * @private
  36210. * @cfg {String} radiusField
  36211. * Not supported.
  36212. */
  36213. /**
  36214. * @cfg {Number} donut Specifies the radius of the donut hole, as a percentage
  36215. * of the chart's radius.
  36216. * Defaults to 0 (no donut hole).
  36217. */
  36218. donut: 0,
  36219. /**
  36220. * @cfg {Array} hidden Determines which pie slices are hidden.
  36221. */
  36222. hidden: [],
  36223. // Populated by the coordinateX method.
  36224. /**
  36225. * @cfg {Object} highlightCfg Default {@link #highlight} config for the 3D pie series.
  36226. * Slides highlighted pie sector outward.
  36227. */
  36228. highlightCfg: {
  36229. margin: 20
  36230. },
  36231. /**
  36232. * @cfg {Number} [rotation=0] The starting angle of the pie slices.
  36233. */
  36234. /**
  36235. * @private
  36236. * @cfg {Boolean/Object} [shadow=false]
  36237. */
  36238. shadow: false
  36239. },
  36240. // Subtract 90 degrees from rotation, so that `rotation` config's default
  36241. // zero value makes first pie sector start at noon, rather than 3 o'clock.
  36242. rotationOffset: -Math.PI / 2,
  36243. setField: function(value) {
  36244. return this.setXField(value);
  36245. },
  36246. getField: function() {
  36247. return this.getXField();
  36248. },
  36249. updateRotation: function(rotation) {
  36250. var attributes = {
  36251. baseRotation: rotation + this.rotationOffset
  36252. };
  36253. this.forEachSprite(function(sprite) {
  36254. sprite.setAttributes(attributes);
  36255. });
  36256. },
  36257. updateColors: function(colors) {
  36258. var chart;
  36259. this.setSubStyle({
  36260. baseColor: colors
  36261. });
  36262. if (!this.isConfiguring) {
  36263. chart = this.getChart();
  36264. if (chart) {
  36265. chart.refreshLegendStore();
  36266. }
  36267. }
  36268. },
  36269. applyShadow: function(shadow) {
  36270. if (shadow === true) {
  36271. shadow = {
  36272. shadowColor: 'rgba(0,0,0,0.8)',
  36273. shadowBlur: 30
  36274. };
  36275. } else if (!Ext.isObject(shadow)) {
  36276. shadow = {
  36277. shadowColor: Ext.util.Color.RGBA_NONE
  36278. };
  36279. }
  36280. return shadow;
  36281. },
  36282. updateShadow: function(shadow) {
  36283. var me = this,
  36284. sprites = me.getSprites(),
  36285. spritesPerSlice = me.spritesPerSlice,
  36286. ln = sprites && sprites.length,
  36287. i, sprite;
  36288. for (i = 1; i < ln; i += spritesPerSlice) {
  36289. sprite = sprites[i];
  36290. if (sprite.attr.part === 'bottom') {
  36291. sprite.setAttributes(shadow);
  36292. }
  36293. }
  36294. },
  36295. // This is a temporary solution until the Series.getStyleByIndex is fixed
  36296. // to give user styles the priority over theme ones. Also, for sprites of
  36297. // this particular series, the fillStyle shouldn't be set directly. Instead,
  36298. // the 'baseColor' attribute should be set, from which the stops of the
  36299. // gradient (used for fillStyle) will be calculated. Themes can't handle
  36300. // situations like that properly.
  36301. getStyleByIndex: function(i) {
  36302. var indexStyle = this.callParent([
  36303. i
  36304. ]),
  36305. style = this.getStyle(),
  36306. // 'fill' and 'color' are 'fillStyle' aliases
  36307. // (see Ext.draw.sprite.Sprite.inheritableStatics.def.aliases)
  36308. fillStyle = indexStyle.fillStyle || indexStyle.fill || indexStyle.color,
  36309. strokeStyle = style.strokeStyle || style.stroke;
  36310. if (fillStyle) {
  36311. indexStyle.baseColor = fillStyle;
  36312. delete indexStyle.fillStyle;
  36313. delete indexStyle.fill;
  36314. delete indexStyle.color;
  36315. }
  36316. if (strokeStyle) {
  36317. indexStyle.strokeStyle = strokeStyle;
  36318. }
  36319. return indexStyle;
  36320. },
  36321. doUpdateStyles: function() {
  36322. var me = this,
  36323. sprites = me.getSprites(),
  36324. spritesPerSlice = me.spritesPerSlice,
  36325. ln = sprites && sprites.length,
  36326. i = 0,
  36327. j = 0,
  36328. k, style;
  36329. for (; i < ln; i += spritesPerSlice , j++) {
  36330. style = me.getStyleByIndex(j);
  36331. for (k = 0; k < spritesPerSlice; k++) {
  36332. sprites[i + k].setAttributes(style);
  36333. }
  36334. }
  36335. },
  36336. coordinateX: function() {
  36337. var me = this,
  36338. store = me.getStore(),
  36339. records = store.getData().items,
  36340. recordCount = records.length,
  36341. xField = me.getXField(),
  36342. animation = me.getAnimation(),
  36343. rotation = me.getRotation(),
  36344. hidden = me.getHidden(),
  36345. sprites = me.getSprites(true),
  36346. spriteCount = sprites.length,
  36347. spritesPerSlice = me.spritesPerSlice,
  36348. center = me.getCenter(),
  36349. offsetX = me.getOffsetX(),
  36350. offsetY = me.getOffsetY(),
  36351. radius = me.getRadius(),
  36352. thickness = me.getThickness(),
  36353. distortion = me.getDistortion(),
  36354. renderer = me.getRenderer(),
  36355. rendererData = me.getRendererData(),
  36356. highlight = me.getHighlight(),
  36357. // eslint-disable-line no-unused-vars
  36358. lastAngle = 0,
  36359. twoPi = Math.PI * 2,
  36360. // To avoid adjacent start/end part blinking (z-index jitter)
  36361. // when rotating a translucent pie chart.
  36362. delta = 1.0E-10,
  36363. endAngles = [],
  36364. sum = 0,
  36365. value, unit, sprite, style, i, j;
  36366. for (i = 0; i < recordCount; i++) {
  36367. value = Math.abs(+records[i].get(xField)) || 0;
  36368. if (!hidden[i]) {
  36369. sum += value;
  36370. }
  36371. endAngles[i] = sum;
  36372. if (i >= hidden.length) {
  36373. hidden[i] = false;
  36374. }
  36375. }
  36376. if (sum === 0) {
  36377. return;
  36378. }
  36379. // Angular value of 1 in radians.
  36380. unit = 2 * Math.PI / sum;
  36381. for (i = 0; i < recordCount; i++) {
  36382. endAngles[i] *= unit;
  36383. }
  36384. for (i = 0; i < recordCount; i++) {
  36385. style = this.getStyleByIndex(i);
  36386. for (j = 0; j < spritesPerSlice; j++) {
  36387. sprite = sprites[i * spritesPerSlice + j];
  36388. sprite.setAnimation(animation);
  36389. sprite.setAttributes({
  36390. centerX: center[0] + offsetX,
  36391. centerY: center[1] + offsetY - thickness / 2,
  36392. endRho: radius,
  36393. startRho: radius * me.getDonut() / 100,
  36394. baseRotation: rotation + me.rotationOffset,
  36395. startAngle: lastAngle,
  36396. endAngle: endAngles[i] - delta,
  36397. thickness: thickness,
  36398. distortion: distortion,
  36399. globalAlpha: 1
  36400. });
  36401. sprite.setAttributes(style);
  36402. sprite.setConfig({
  36403. renderer: renderer,
  36404. rendererData: rendererData,
  36405. rendererIndex: i
  36406. });
  36407. }
  36408. // if (highlight) {
  36409. // if (!sprite.modifiers.highlight) {
  36410. // debugger
  36411. // sprite.addModifier(highlight, true);
  36412. // }
  36413. // // sprite.modifiers.highlight.setConfig(highlight);
  36414. // }
  36415. lastAngle = endAngles[i];
  36416. }
  36417. for (i *= spritesPerSlice; i < spriteCount; i++) {
  36418. sprite = sprites[i];
  36419. sprite.setAnimation(animation);
  36420. sprite.setAttributes({
  36421. startAngle: twoPi,
  36422. endAngle: twoPi,
  36423. globalAlpha: 0,
  36424. baseRotation: rotation + me.rotationOffset
  36425. });
  36426. }
  36427. },
  36428. updateHighlight: function(highlight, oldHighlight) {
  36429. this.callParent([
  36430. highlight,
  36431. oldHighlight
  36432. ]);
  36433. this.forEachSprite(function(sprite) {
  36434. if (highlight) {
  36435. if (sprite.modifiers.highlight) {
  36436. sprite.modifiers.highlight.setConfig(highlight);
  36437. } else {
  36438. sprite.config.highlight = highlight;
  36439. sprite.addModifier(highlight, true);
  36440. }
  36441. }
  36442. });
  36443. },
  36444. updateLabelData: function() {
  36445. var me = this,
  36446. store = me.getStore(),
  36447. items = store.getData().items,
  36448. sprites = me.getSprites(),
  36449. label = me.getLabel(),
  36450. labelField = label && label.getTemplate().getField(),
  36451. hidden = me.getHidden(),
  36452. spritesPerSlice = me.spritesPerSlice,
  36453. ln, labels, sprite,
  36454. name = 'labels',
  36455. i, // sprite index
  36456. j;
  36457. // record index
  36458. if (sprites.length) {
  36459. if (labelField) {
  36460. labels = [];
  36461. for (j = 0 , ln = items.length; j < ln; j++) {
  36462. labels.push(items[j].get(labelField));
  36463. }
  36464. }
  36465. // Only set labels for the sprites that compose the top lid of the pie.
  36466. for (i = 0 , j = 0 , ln = sprites.length; i < ln; i += spritesPerSlice , j++) {
  36467. sprite = sprites[i];
  36468. if (label) {
  36469. if (!sprite.getMarker(name)) {
  36470. sprite.bindMarker(name, label);
  36471. }
  36472. if (labels) {
  36473. sprite.setAttributes({
  36474. label: labels[j]
  36475. });
  36476. }
  36477. sprite.putMarker(name, {
  36478. hidden: hidden[j]
  36479. }, sprite.attr.attributeId);
  36480. } else {
  36481. sprite.releaseMarker(name);
  36482. }
  36483. }
  36484. }
  36485. },
  36486. // The radius here will normally be set by the PolarChart.performLayout,
  36487. // where it's half the width or height (whichever is smaller) of the chart's rect.
  36488. // But for 3D pie series we have to take the thickness of the pie and the
  36489. // distortion into account to calculate the proper radius.
  36490. // The passed value is never used (or derived from) since the radius config
  36491. // is not really meant to be used directly, as it will be reset by the next layout.
  36492. applyRadius: function() {
  36493. var me = this,
  36494. chart = me.getChart(),
  36495. padding = chart.getInnerPadding(),
  36496. rect = chart.getMainRect() || [
  36497. 0,
  36498. 0,
  36499. 1,
  36500. 1
  36501. ],
  36502. width = rect[2] - padding * 2,
  36503. height = rect[3] - padding * 2 - me.getThickness(),
  36504. horizontalRadius = width / 2,
  36505. verticalRadius = horizontalRadius * me.getDistortion(),
  36506. result;
  36507. if (verticalRadius > height / 2) {
  36508. result = height / (me.getDistortion() * 2);
  36509. } else {
  36510. result = horizontalRadius;
  36511. }
  36512. return Math.max(result, 0);
  36513. },
  36514. forEachSprite: function(fn) {
  36515. var sprites = this.sprites,
  36516. ln = sprites.length,
  36517. i;
  36518. for (i = 0; i < ln; i++) {
  36519. fn(sprites[i], Math.floor(i / this.spritesPerSlice));
  36520. }
  36521. },
  36522. updateRadius: function(radius) {
  36523. var donut;
  36524. // The side effects of the 'getChart' call will result
  36525. // in the 'coordinateX' method call, which we want to have called
  36526. // first, to coordinate the data and create sprites for pie slices,
  36527. // before we set their attributes here.
  36528. // updateChart -> onChartAttached -> processData -> coordinateX
  36529. this.getChart();
  36530. donut = this.getDonut();
  36531. this.forEachSprite(function(sprite) {
  36532. sprite.setAttributes({
  36533. endRho: radius,
  36534. startRho: radius * donut / 100
  36535. });
  36536. });
  36537. },
  36538. updateDonut: function(donut) {
  36539. var radius;
  36540. // See 'updateRadius' comments.
  36541. this.getChart();
  36542. radius = this.getRadius();
  36543. this.forEachSprite(function(sprite) {
  36544. sprite.setAttributes({
  36545. startRho: radius * donut / 100
  36546. });
  36547. });
  36548. },
  36549. updateCenter: function(center) {
  36550. var offsetX, offsetY, thickness;
  36551. // See 'updateRadius' comments.
  36552. this.getChart();
  36553. offsetX = this.getOffsetX();
  36554. offsetY = this.getOffsetY();
  36555. thickness = this.getThickness();
  36556. this.forEachSprite(function(sprite) {
  36557. sprite.setAttributes({
  36558. centerX: center[0] + offsetX,
  36559. centerY: center[1] + offsetY - thickness / 2
  36560. });
  36561. });
  36562. },
  36563. updateThickness: function(thickness) {
  36564. var center, offsetY;
  36565. // See 'updateRadius' comments.
  36566. this.getChart();
  36567. // Radius depends on thickness and distortion,
  36568. // this will trigger its recalculation in the applier.
  36569. this.setRadius();
  36570. center = this.getCenter();
  36571. offsetY = this.getOffsetY();
  36572. this.forEachSprite(function(sprite) {
  36573. sprite.setAttributes({
  36574. thickness: thickness,
  36575. centerY: center[1] + offsetY - thickness / 2
  36576. });
  36577. });
  36578. },
  36579. updateDistortion: function(distortion) {
  36580. // See 'updateRadius' comments.
  36581. this.getChart();
  36582. // Radius depends on thickness and distortion,
  36583. // this will trigger its recalculation in the applier.
  36584. this.setRadius();
  36585. this.forEachSprite(function(sprite) {
  36586. sprite.setAttributes({
  36587. distortion: distortion
  36588. });
  36589. });
  36590. },
  36591. updateOffsetX: function(offsetX) {
  36592. var center;
  36593. // See 'updateRadius' comments.
  36594. this.getChart();
  36595. center = this.getCenter();
  36596. this.forEachSprite(function(sprite) {
  36597. sprite.setAttributes({
  36598. centerX: center[0] + offsetX
  36599. });
  36600. });
  36601. },
  36602. updateOffsetY: function(offsetY) {
  36603. var center, thickness;
  36604. // See 'updateRadius' comments.
  36605. this.getChart();
  36606. center = this.getCenter();
  36607. thickness = this.getThickness();
  36608. this.forEachSprite(function(sprite) {
  36609. sprite.setAttributes({
  36610. centerY: center[1] + offsetY - thickness / 2
  36611. });
  36612. });
  36613. },
  36614. updateAnimation: function(animation) {
  36615. // See 'updateRadius' comments.
  36616. this.getChart();
  36617. this.forEachSprite(function(sprite) {
  36618. sprite.setAnimation(animation);
  36619. });
  36620. },
  36621. updateRenderer: function(renderer) {
  36622. var rendererData;
  36623. // See 'updateRadius' comments.
  36624. this.getChart();
  36625. rendererData = this.getRendererData();
  36626. this.forEachSprite(function(sprite, itemIndex) {
  36627. sprite.setConfig({
  36628. renderer: renderer,
  36629. rendererData: rendererData,
  36630. rendererIndex: itemIndex
  36631. });
  36632. });
  36633. },
  36634. getRendererData: function() {
  36635. return {
  36636. store: this.getStore(),
  36637. angleField: this.getXField(),
  36638. radiusField: this.getYField(),
  36639. series: this
  36640. };
  36641. },
  36642. getSprites: function(createMissing) {
  36643. var me = this,
  36644. store = me.getStore(),
  36645. sprites = me.sprites;
  36646. if (!store) {
  36647. return Ext.emptyArray;
  36648. }
  36649. if (sprites && !createMissing) {
  36650. return sprites;
  36651. }
  36652. // eslint-disable-next-line vars-on-top, one-var
  36653. var surface = me.getSurface(),
  36654. records = store.getData().items,
  36655. spritesPerSlice = me.spritesPerSlice,
  36656. partCount = me.partNames.length,
  36657. recordCount = records.length,
  36658. sprite, i, j;
  36659. for (i = 0; i < recordCount; i++) {
  36660. if (!sprites[i * spritesPerSlice]) {
  36661. for (j = 0; j < partCount; j++) {
  36662. sprite = surface.add({
  36663. type: 'pie3dPart',
  36664. part: me.partNames[j],
  36665. series: me
  36666. });
  36667. sprite.getAnimation().setDurationOn('baseRotation', 0);
  36668. sprites.push(sprite);
  36669. }
  36670. }
  36671. }
  36672. return sprites;
  36673. },
  36674. betweenAngle: function(x, a, b) {
  36675. var pp = Math.PI * 2,
  36676. offset = this.rotationOffset;
  36677. a += offset;
  36678. b += offset;
  36679. x -= a;
  36680. b -= a;
  36681. // Normalize, so that both x and b are in the [0,360) interval.
  36682. // Since 360 * n angles will be normalized to 0,
  36683. // we need to treat b === 0 as a special case.
  36684. x %= pp;
  36685. b %= pp;
  36686. x += pp;
  36687. b += pp;
  36688. x %= pp;
  36689. b %= pp;
  36690. return x < b || b === 0;
  36691. },
  36692. getItemForPoint: function(x, y) {
  36693. var me = this,
  36694. sprites = me.getSprites(),
  36695. spritesPerSlice = me.spritesPerSlice,
  36696. result = null,
  36697. store, records, hidden, i, ln, sprite, topPartIndex;
  36698. if (!sprites) {
  36699. return result;
  36700. }
  36701. store = me.getStore();
  36702. records = store.getData().items;
  36703. hidden = me.getHidden();
  36704. for (i = 0 , ln = records.length; i < ln; i++) {
  36705. if (hidden[i]) {
  36706. continue;
  36707. }
  36708. topPartIndex = i * spritesPerSlice;
  36709. sprite = sprites[topPartIndex];
  36710. // This is CPU intensive on mousemove (no visial slowdown
  36711. // on a fast machine, but some throttling might be desirable
  36712. // on slower machines).
  36713. // On touch devices performance/battery hit is negligible.
  36714. if (sprite.hitTest([
  36715. x,
  36716. y
  36717. ])) {
  36718. result = {
  36719. series: me,
  36720. sprite: sprites.slice(topPartIndex, topPartIndex + spritesPerSlice),
  36721. index: i,
  36722. record: records[i],
  36723. category: 'sprites',
  36724. field: me.getXField()
  36725. };
  36726. break;
  36727. }
  36728. }
  36729. return result;
  36730. },
  36731. provideLegendInfo: function(target) {
  36732. var me = this,
  36733. store = me.getStore(),
  36734. items, labelField, field, hidden, style, color, i;
  36735. if (store) {
  36736. items = store.getData().items;
  36737. labelField = me.getLabel().getTemplate().getField();
  36738. field = me.getField();
  36739. hidden = me.getHidden();
  36740. for (i = 0; i < items.length; i++) {
  36741. style = me.getStyleByIndex(i);
  36742. color = style.baseColor;
  36743. target.push({
  36744. name: labelField ? String(items[i].get(labelField)) : field + ' ' + i,
  36745. mark: color || 'black',
  36746. disabled: hidden[i],
  36747. series: me.getId(),
  36748. index: i
  36749. });
  36750. }
  36751. }
  36752. }
  36753. }, function() {
  36754. var proto = this.prototype,
  36755. definition = Ext.chart.series.sprite.Pie3DPart.def.getInitialConfig().processors.part;
  36756. proto.partNames = definition.replace(/^enums\(|\)/g, '').split(',');
  36757. proto.spritesPerSlice = proto.partNames.length;
  36758. });
  36759. /**
  36760. * Polar sprite.
  36761. */
  36762. Ext.define('Ext.chart.series.sprite.Polar', {
  36763. extend: 'Ext.chart.series.sprite.Series',
  36764. inheritableStatics: {
  36765. def: {
  36766. processors: {
  36767. /**
  36768. * @cfg {Number} [centerX=0] The central point of the series on the x-axis.
  36769. */
  36770. centerX: 'number',
  36771. /**
  36772. * @cfg {Number} [centerY=0] The central point of the series on the y-axis.
  36773. */
  36774. centerY: 'number',
  36775. /**
  36776. * @cfg {Number} [startAngle=0] The starting angle of the polar series.
  36777. */
  36778. startAngle: 'number',
  36779. /**
  36780. * @cfg {Number} [endAngle=Math.PI] The ending angle of the polar series.
  36781. */
  36782. endAngle: 'number',
  36783. /**
  36784. * @cfg {Number} [startRho=0] The starting radius of the polar series.
  36785. */
  36786. startRho: 'number',
  36787. /**
  36788. * @cfg {Number} [endRho=150] The ending radius of the polar series.
  36789. */
  36790. endRho: 'number',
  36791. /**
  36792. * @cfg {Number} [baseRotation=0] The starting rotation of the polar series.
  36793. */
  36794. baseRotation: 'number'
  36795. },
  36796. defaults: {
  36797. centerX: 0,
  36798. centerY: 0,
  36799. startAngle: 0,
  36800. endAngle: Math.PI,
  36801. startRho: 0,
  36802. endRho: 150,
  36803. baseRotation: 0
  36804. },
  36805. triggers: {
  36806. centerX: 'bbox',
  36807. centerY: 'bbox',
  36808. startAngle: 'bbox',
  36809. endAngle: 'bbox',
  36810. startRho: 'bbox',
  36811. endRho: 'bbox',
  36812. baseRotation: 'bbox'
  36813. }
  36814. }
  36815. },
  36816. updatePlainBBox: function(plain) {
  36817. var attr = this.attr;
  36818. plain.x = attr.centerX - attr.endRho;
  36819. plain.y = attr.centerY + attr.endRho;
  36820. plain.width = attr.endRho * 2;
  36821. plain.height = attr.endRho * 2;
  36822. }
  36823. });
  36824. /**
  36825. * @class Ext.chart.series.sprite.Radar
  36826. * @extends Ext.chart.series.sprite.Polar
  36827. *
  36828. * Radar series sprite.
  36829. */
  36830. Ext.define('Ext.chart.series.sprite.Radar', {
  36831. alias: 'sprite.radar',
  36832. extend: 'Ext.chart.series.sprite.Polar',
  36833. getDataPointXY: function(index) {
  36834. var me = this,
  36835. attr = me.attr,
  36836. centerX = attr.centerX,
  36837. centerY = attr.centerY,
  36838. matrix = attr.matrix,
  36839. minX = attr.dataMinX,
  36840. maxX = attr.dataMaxX,
  36841. dataX = attr.dataX,
  36842. dataY = attr.dataY,
  36843. endRho = attr.endRho,
  36844. startRho = attr.startRho,
  36845. baseRotation = attr.baseRotation,
  36846. x, y, r, th, ox, oy, maxY;
  36847. if (attr.rangeY) {
  36848. maxY = attr.rangeY[1];
  36849. } else {
  36850. maxY = attr.dataMaxY;
  36851. }
  36852. th = (dataX[index] - minX) / (maxX - minX + 1) * 2 * Math.PI + baseRotation;
  36853. r = dataY[index] / maxY * (endRho - startRho) + startRho;
  36854. // Original coordinates.
  36855. ox = centerX + Math.cos(th) * r;
  36856. oy = centerY + Math.sin(th) * r;
  36857. // Transformed coordinates.
  36858. x = matrix.x(ox, oy);
  36859. y = matrix.y(ox, oy);
  36860. return [
  36861. x,
  36862. y
  36863. ];
  36864. },
  36865. render: function(surface, ctx) {
  36866. var me = this,
  36867. attr = me.attr,
  36868. dataX = attr.dataX,
  36869. length = dataX.length,
  36870. surfaceMatrix = me.surfaceMatrix,
  36871. markerCfg = {},
  36872. i, x, y, xy;
  36873. ctx.beginPath();
  36874. for (i = 0; i < length; i++) {
  36875. xy = me.getDataPointXY(i);
  36876. x = xy[0];
  36877. y = xy[1];
  36878. if (i === 0) {
  36879. ctx.moveTo(x, y);
  36880. }
  36881. ctx.lineTo(x, y);
  36882. markerCfg.translationX = surfaceMatrix.x(x, y);
  36883. markerCfg.translationY = surfaceMatrix.y(x, y);
  36884. me.putMarker('markers', markerCfg, i, true);
  36885. }
  36886. ctx.closePath();
  36887. ctx.fillStroke(attr);
  36888. }
  36889. });
  36890. /**
  36891. * @class Ext.chart.series.Radar
  36892. * @extends Ext.chart.series.Polar
  36893. *
  36894. * Creates a Radar Chart. A Radar Chart is a useful visualization technique for comparing different
  36895. * quantitative values for a constrained number of categories.
  36896. * As with all other series, the Radar series must be appended in the *series* Chart array
  36897. * configuration. See the Chart documentation for more information. A typical configuration object
  36898. * for the radar series could be:
  36899. *
  36900. * @example
  36901. * Ext.create({
  36902. * xtype: 'polar',
  36903. * renderTo: document.body,
  36904. * width: 500,
  36905. * height: 400,
  36906. * interactions: 'rotate',
  36907. * store: {
  36908. * fields: ['name', 'data1'],
  36909. * data: [{
  36910. * 'name': 'metric one',
  36911. * 'data1': 8
  36912. * }, {
  36913. * 'name': 'metric two',
  36914. * 'data1': 10
  36915. * }, {
  36916. * 'name': 'metric three',
  36917. * 'data1': 12
  36918. * }, {
  36919. * 'name': 'metric four',
  36920. * 'data1': 1
  36921. * }, {
  36922. * 'name': 'metric five',
  36923. * 'data1': 13
  36924. * }]
  36925. * },
  36926. * series: {
  36927. * type: 'radar',
  36928. * angleField: 'name',
  36929. * radiusField: 'data1',
  36930. * style: {
  36931. * fillStyle: '#388FAD',
  36932. * fillOpacity: .1,
  36933. * strokeStyle: '#388FAD',
  36934. * strokeOpacity: .8,
  36935. * lineWidth: 1
  36936. * }
  36937. * },
  36938. * axes: [{
  36939. * type: 'numeric',
  36940. * position: 'radial',
  36941. * fields: 'data1',
  36942. * style: {
  36943. * estStepSize: 10
  36944. * },
  36945. * grid: true
  36946. * }, {
  36947. * type: 'category',
  36948. * position: 'angular',
  36949. * fields: 'name',
  36950. * style: {
  36951. * estStepSize: 1
  36952. * },
  36953. * grid: true
  36954. * }]
  36955. * });
  36956. *
  36957. */
  36958. Ext.define('Ext.chart.series.Radar', {
  36959. extend: 'Ext.chart.series.Polar',
  36960. type: 'radar',
  36961. seriesType: 'radar',
  36962. alias: 'series.radar',
  36963. requires: [
  36964. 'Ext.chart.series.sprite.Radar'
  36965. ],
  36966. themeColorCount: function() {
  36967. return 1;
  36968. },
  36969. isStoreDependantColorCount: false,
  36970. themeMarkerCount: function() {
  36971. return 1;
  36972. },
  36973. updateAngularAxis: function(axis) {
  36974. axis.processData(this);
  36975. },
  36976. updateRadialAxis: function(axis) {
  36977. axis.processData(this);
  36978. },
  36979. coordinateX: function() {
  36980. return this.coordinate('X', 0, 2);
  36981. },
  36982. coordinateY: function() {
  36983. return this.coordinate('Y', 1, 2);
  36984. },
  36985. updateCenter: function(center) {
  36986. this.setStyle({
  36987. translationX: center[0] + this.getOffsetX(),
  36988. translationY: center[1] + this.getOffsetY()
  36989. });
  36990. this.doUpdateStyles();
  36991. },
  36992. updateRadius: function(radius) {
  36993. this.setStyle({
  36994. endRho: radius
  36995. });
  36996. this.doUpdateStyles();
  36997. },
  36998. updateRotation: function(rotation) {
  36999. // Overrides base class method.
  37000. var me = this,
  37001. chart = me.getChart(),
  37002. axes = chart.getAxes(),
  37003. i, ln, axis;
  37004. for (i = 0 , ln = axes.length; i < ln; i++) {
  37005. axis = axes[i];
  37006. axis.setRotation(rotation);
  37007. }
  37008. me.setStyle({
  37009. rotationRads: rotation
  37010. });
  37011. me.doUpdateStyles();
  37012. },
  37013. updateTotalAngle: function(totalAngle) {
  37014. this.processData();
  37015. },
  37016. getItemForPoint: function(x, y) {
  37017. var me = this,
  37018. sprite = me.sprites && me.sprites[0],
  37019. attr = sprite.attr,
  37020. dataX = attr.dataX,
  37021. length = dataX.length,
  37022. store = me.getStore(),
  37023. marker = me.getMarker(),
  37024. threshhold, item, xy, i, bbox, markers;
  37025. if (me.getHidden()) {
  37026. return null;
  37027. }
  37028. if (sprite && marker) {
  37029. markers = sprite.getMarker('markers');
  37030. for (i = 0; i < length; i++) {
  37031. bbox = markers.getBBoxFor(i);
  37032. threshhold = (bbox.width + bbox.height) * 0.25;
  37033. xy = sprite.getDataPointXY(i);
  37034. if (Math.abs(xy[0] - x) < threshhold && Math.abs(xy[1] - y) < threshhold) {
  37035. item = {
  37036. series: me,
  37037. sprite: sprite,
  37038. index: i,
  37039. category: 'markers',
  37040. record: store.getData().items[i],
  37041. field: me.getYField()
  37042. };
  37043. return item;
  37044. }
  37045. }
  37046. }
  37047. return me.callParent(arguments);
  37048. },
  37049. getDefaultSpriteConfig: function() {
  37050. var config = this.callParent(),
  37051. animation = {
  37052. customDurations: {
  37053. translationX: 0,
  37054. translationY: 0,
  37055. rotationRads: 0,
  37056. // Prevent animation of 'dataMinX' and 'dataMaxX' attributes in order
  37057. // to react instantaniously to changes to the 'hidden' attribute.
  37058. dataMinX: 0,
  37059. dataMaxX: 0
  37060. }
  37061. };
  37062. if (config.animation) {
  37063. Ext.apply(config.animation, animation);
  37064. } else {
  37065. config.animation = animation;
  37066. }
  37067. return config;
  37068. },
  37069. getSprites: function() {
  37070. var me = this,
  37071. chart = me.getChart(),
  37072. sprites = me.sprites;
  37073. if (!chart) {
  37074. return Ext.emptyArray;
  37075. }
  37076. if (!sprites.length) {
  37077. me.createSprite();
  37078. }
  37079. return sprites;
  37080. },
  37081. provideLegendInfo: function(target) {
  37082. var me = this,
  37083. style = me.getSubStyleWithTheme(),
  37084. fill = style.fillStyle;
  37085. if (Ext.isArray(fill)) {
  37086. fill = fill[0];
  37087. }
  37088. target.push({
  37089. name: me.getTitle() || me.getYField() || me.getId(),
  37090. mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
  37091. disabled: me.getHidden(),
  37092. series: me.getId(),
  37093. index: 0
  37094. });
  37095. }
  37096. });
  37097. /**
  37098. * @class Ext.chart.series.sprite.Scatter
  37099. * @extends Ext.chart.series.sprite.Cartesian
  37100. *
  37101. * Scatter series sprite.
  37102. */
  37103. Ext.define('Ext.chart.series.sprite.Scatter', {
  37104. alias: 'sprite.scatterSeries',
  37105. extend: 'Ext.chart.series.sprite.Cartesian',
  37106. renderClipped: function(surface, ctx, dataClipRect, surfaceClipRect) {
  37107. if (this.cleanRedraw) {
  37108. return;
  37109. }
  37110. // eslint-disable-next-line vars-on-top
  37111. var me = this,
  37112. attr = me.attr,
  37113. dataX = attr.dataX,
  37114. dataY = attr.dataY,
  37115. labels = attr.labels,
  37116. series = me.getSeries(),
  37117. isDrawLabels = labels && me.getMarker('labels'),
  37118. surfaceMatrix = me.surfaceMatrix,
  37119. matrix = me.attr.matrix,
  37120. xx = matrix.getXX(),
  37121. yy = matrix.getYY(),
  37122. dx = matrix.getDX(),
  37123. dy = matrix.getDY(),
  37124. markerCfg = {},
  37125. changes, params,
  37126. xScalingDirection = surface.getInherited().rtl && !attr.flipXY ? -1 : 1,
  37127. left, right, top, bottom, x, y, i;
  37128. if (attr.flipXY) {
  37129. left = surfaceClipRect[1] - xx * xScalingDirection;
  37130. right = surfaceClipRect[1] + surfaceClipRect[3] + xx * xScalingDirection;
  37131. top = surfaceClipRect[0] - yy;
  37132. bottom = surfaceClipRect[0] + surfaceClipRect[2] + yy;
  37133. } else {
  37134. left = surfaceClipRect[0] - xx * xScalingDirection;
  37135. right = surfaceClipRect[0] + surfaceClipRect[2] + xx * xScalingDirection;
  37136. top = surfaceClipRect[1] - yy;
  37137. bottom = surfaceClipRect[1] + surfaceClipRect[3] + yy;
  37138. }
  37139. for (i = 0; i < dataX.length; i++) {
  37140. x = dataX[i];
  37141. y = dataY[i];
  37142. x = x * xx + dx;
  37143. y = y * yy + dy;
  37144. if (left <= x && x <= right && top <= y && y <= bottom) {
  37145. if (attr.renderer) {
  37146. markerCfg = {
  37147. type: 'markers',
  37148. translationX: surfaceMatrix.x(x, y),
  37149. translationY: surfaceMatrix.y(x, y)
  37150. };
  37151. params = [
  37152. me,
  37153. markerCfg,
  37154. {
  37155. store: me.getStore()
  37156. },
  37157. i
  37158. ];
  37159. changes = Ext.callback(attr.renderer, null, params, 0, series);
  37160. markerCfg = Ext.apply(markerCfg, changes);
  37161. } else {
  37162. markerCfg.translationX = surfaceMatrix.x(x, y);
  37163. markerCfg.translationY = surfaceMatrix.y(x, y);
  37164. }
  37165. me.putMarker('markers', markerCfg, i, !attr.renderer);
  37166. if (isDrawLabels && labels[i]) {
  37167. me.drawLabel(labels[i], x, y, i, surfaceClipRect);
  37168. }
  37169. }
  37170. }
  37171. },
  37172. drawLabel: function(text, dataX, dataY, labelId, rect) {
  37173. var me = this,
  37174. attr = me.attr,
  37175. label = me.getMarker('labels'),
  37176. labelTpl = label.getTemplate(),
  37177. labelCfg = me.labelCfg || (me.labelCfg = {}),
  37178. surfaceMatrix = me.surfaceMatrix,
  37179. labelX, labelY,
  37180. labelOverflowPadding = attr.labelOverflowPadding,
  37181. flipXY = attr.flipXY,
  37182. halfHeight, labelBox, changes, params;
  37183. labelCfg.text = text;
  37184. labelBox = me.getMarkerBBox('labels', labelId, true);
  37185. if (!labelBox) {
  37186. me.putMarker('labels', labelCfg, labelId);
  37187. labelBox = me.getMarkerBBox('labels', labelId, true);
  37188. }
  37189. if (flipXY) {
  37190. labelCfg.rotationRads = Math.PI * 0.5;
  37191. } else {
  37192. labelCfg.rotationRads = 0;
  37193. }
  37194. halfHeight = labelBox.height / 2;
  37195. labelX = dataX;
  37196. switch (labelTpl.attr.display) {
  37197. case 'under':
  37198. labelY = dataY - halfHeight - labelOverflowPadding;
  37199. break;
  37200. case 'rotate':
  37201. labelX += labelOverflowPadding;
  37202. labelY = dataY - labelOverflowPadding;
  37203. labelCfg.rotationRads = -Math.PI / 4;
  37204. break;
  37205. default:
  37206. // 'over'
  37207. labelY = dataY + halfHeight + labelOverflowPadding;
  37208. }
  37209. labelCfg.x = surfaceMatrix.x(labelX, labelY);
  37210. labelCfg.y = surfaceMatrix.y(labelX, labelY);
  37211. if (labelTpl.attr.renderer) {
  37212. params = [
  37213. text,
  37214. label,
  37215. labelCfg,
  37216. {
  37217. store: me.getStore()
  37218. },
  37219. labelId
  37220. ];
  37221. changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
  37222. if (typeof changes === 'string') {
  37223. labelCfg.text = changes;
  37224. } else {
  37225. Ext.apply(labelCfg, changes);
  37226. }
  37227. }
  37228. me.putMarker('labels', labelCfg, labelId);
  37229. }
  37230. });
  37231. /**
  37232. * @class Ext.chart.series.Scatter
  37233. * @extends Ext.chart.series.Cartesian
  37234. *
  37235. * Creates a Scatter Chart. The scatter plot is useful when trying to display more than
  37236. * two variables in the same visualization. These variables can be mapped into x, y coordinates
  37237. * and also to an element's radius/size, color, etc. As with all other series, the Scatter Series
  37238. * must be appended in the *series* Chart array configuration. See the Chart documentation for more
  37239. * information on creating charts. A typical configuration object for the scatter could be:
  37240. *
  37241. * @example
  37242. * Ext.create({
  37243. * xtype: 'cartesian',
  37244. * renderTo: document.body,
  37245. * width: 600,
  37246. * height: 400,
  37247. * insetPadding: 40,
  37248. * interactions: ['itemhighlight'],
  37249. * store: {
  37250. * fields: ['name', 'data1', 'data2'],
  37251. * data: [{
  37252. * 'name': 'metric one',
  37253. * 'data1': 10,
  37254. * 'data2': 14
  37255. * }, {
  37256. * 'name': 'metric two',
  37257. * 'data1': 7,
  37258. * 'data2': 16
  37259. * }, {
  37260. * 'name': 'metric three',
  37261. * 'data1': 5,
  37262. * 'data2': 14
  37263. * }, {
  37264. * 'name': 'metric four',
  37265. * 'data1': 2,
  37266. * 'data2': 6
  37267. * }, {
  37268. * 'name': 'metric five',
  37269. * 'data1': 27,
  37270. * 'data2': 36
  37271. * }]
  37272. * },
  37273. * axes: [{
  37274. * type: 'numeric',
  37275. * position: 'left',
  37276. * fields: ['data1'],
  37277. * title: {
  37278. * text: 'Sample Values',
  37279. * fontSize: 15
  37280. * },
  37281. * grid: true,
  37282. * minimum: 0
  37283. * }, {
  37284. * type: 'category',
  37285. * position: 'bottom',
  37286. * fields: ['name'],
  37287. * title: {
  37288. * text: 'Sample Values',
  37289. * fontSize: 15
  37290. * }
  37291. * }],
  37292. * series: {
  37293. * type: 'scatter',
  37294. * highlight: {
  37295. * size: 12,
  37296. * radius: 12,
  37297. * fill: '#96D4C6',
  37298. * stroke: '#30BDA7'
  37299. * },
  37300. * fill: true,
  37301. * xField: 'name',
  37302. * yField: 'data2',
  37303. * marker: {
  37304. * type: 'circle',
  37305. * fill: '#30BDA7',
  37306. * radius: 10,
  37307. * lineWidth: 0
  37308. * }
  37309. * }
  37310. * });
  37311. *
  37312. * In this configuration we add three different categories of scatter series. Each of them is bound
  37313. * to a different field of the same data store, `data1`, `data2` and `data3` respectively.
  37314. * All x-fields for the series must be the same field, in this case `name`. Each scatter series
  37315. * has a different styling configuration for markers, specified by the `marker` object. Finally
  37316. * we set the left axis as axis to show the current values of the elements.
  37317. *
  37318. */
  37319. Ext.define('Ext.chart.series.Scatter', {
  37320. extend: 'Ext.chart.series.Cartesian',
  37321. alias: 'series.scatter',
  37322. type: 'scatter',
  37323. seriesType: 'scatterSeries',
  37324. requires: [
  37325. 'Ext.chart.series.sprite.Scatter'
  37326. ],
  37327. config: {
  37328. itemInstancing: null,
  37329. marker: true
  37330. },
  37331. themeMarkerCount: function() {
  37332. return 1;
  37333. },
  37334. provideLegendInfo: function(target) {
  37335. var me = this,
  37336. style = me.getMarkerStyleByIndex(0),
  37337. fill = style.fillStyle;
  37338. target.push({
  37339. name: me.getTitle() || me.getYField() || me.getId(),
  37340. mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
  37341. disabled: me.getHidden(),
  37342. series: me.getId(),
  37343. index: 0
  37344. });
  37345. }
  37346. });
  37347. Ext.define('Ext.chart.theme.Blue', {
  37348. extend: 'Ext.chart.theme.Base',
  37349. singleton: true,
  37350. alias: [
  37351. 'chart.theme.blue',
  37352. 'chart.theme.Blue'
  37353. ],
  37354. config: {
  37355. baseColor: '#4d7fe6'
  37356. }
  37357. });
  37358. Ext.define('Ext.chart.theme.BlueGradients', {
  37359. extend: 'Ext.chart.theme.Base',
  37360. singleton: true,
  37361. alias: [
  37362. 'chart.theme.blue-gradients',
  37363. 'chart.theme.Blue:gradients'
  37364. ],
  37365. config: {
  37366. baseColor: '#4d7fe6',
  37367. gradients: {
  37368. type: 'linear',
  37369. degrees: 90
  37370. }
  37371. }
  37372. });
  37373. Ext.define('Ext.chart.theme.Category1', {
  37374. extend: 'Ext.chart.theme.Base',
  37375. singleton: true,
  37376. alias: [
  37377. 'chart.theme.category1',
  37378. 'chart.theme.Category1'
  37379. ],
  37380. config: {
  37381. colors: [
  37382. '#f0a50a',
  37383. '#c20024',
  37384. '#2044ba',
  37385. '#810065',
  37386. '#7eae29'
  37387. ]
  37388. }
  37389. });
  37390. Ext.define('Ext.chart.theme.Category1Gradients', {
  37391. extend: 'Ext.chart.theme.Base',
  37392. singleton: true,
  37393. alias: [
  37394. 'chart.theme.category1-gradients',
  37395. 'chart.theme.Category1:gradients'
  37396. ],
  37397. config: {
  37398. colors: [
  37399. '#f0a50a',
  37400. '#c20024',
  37401. '#2044ba',
  37402. '#810065',
  37403. '#7eae29'
  37404. ],
  37405. gradients: {
  37406. type: 'linear',
  37407. degrees: 90
  37408. }
  37409. }
  37410. });
  37411. Ext.define('Ext.chart.theme.Category2', {
  37412. extend: 'Ext.chart.theme.Base',
  37413. singleton: true,
  37414. alias: [
  37415. 'chart.theme.category2',
  37416. 'chart.theme.Category2'
  37417. ],
  37418. config: {
  37419. colors: [
  37420. '#6d9824',
  37421. '#87146e',
  37422. '#2a9196',
  37423. '#d39006',
  37424. '#1e40ac'
  37425. ]
  37426. }
  37427. });
  37428. Ext.define('Ext.chart.theme.Category2Gradients', {
  37429. extend: 'Ext.chart.theme.Base',
  37430. singleton: true,
  37431. alias: [
  37432. 'chart.theme.category2-gradients',
  37433. 'chart.theme.Category2:gradients'
  37434. ],
  37435. config: {
  37436. colors: [
  37437. '#6d9824',
  37438. '#87146e',
  37439. '#2a9196',
  37440. '#d39006',
  37441. '#1e40ac'
  37442. ],
  37443. gradients: {
  37444. type: 'linear',
  37445. degrees: 90
  37446. }
  37447. }
  37448. });
  37449. Ext.define('Ext.chart.theme.Category3', {
  37450. extend: 'Ext.chart.theme.Base',
  37451. singleton: true,
  37452. alias: [
  37453. 'chart.theme.category3',
  37454. 'chart.theme.Category3'
  37455. ],
  37456. config: {
  37457. colors: [
  37458. '#fbbc29',
  37459. '#ce2e4e',
  37460. '#7e0062',
  37461. '#158b90',
  37462. '#57880e'
  37463. ]
  37464. }
  37465. });
  37466. Ext.define('Ext.chart.theme.Category3Gradients', {
  37467. extend: 'Ext.chart.theme.Base',
  37468. singleton: true,
  37469. alias: [
  37470. 'chart.theme.category3-gradients',
  37471. 'chart.theme.Category3:gradients'
  37472. ],
  37473. config: {
  37474. colors: [
  37475. '#fbbc29',
  37476. '#ce2e4e',
  37477. '#7e0062',
  37478. '#158b90',
  37479. '#57880e'
  37480. ],
  37481. gradients: {
  37482. type: 'linear',
  37483. degrees: 90
  37484. }
  37485. }
  37486. });
  37487. Ext.define('Ext.chart.theme.Category4', {
  37488. extend: 'Ext.chart.theme.Base',
  37489. singleton: true,
  37490. alias: [
  37491. 'chart.theme.category4',
  37492. 'chart.theme.Category4'
  37493. ],
  37494. config: {
  37495. colors: [
  37496. '#ef5773',
  37497. '#fcbd2a',
  37498. '#4f770d',
  37499. '#1d3eaa',
  37500. '#9b001f'
  37501. ]
  37502. }
  37503. });
  37504. Ext.define('Ext.chart.theme.Category4Gradients', {
  37505. extend: 'Ext.chart.theme.Base',
  37506. singleton: true,
  37507. alias: [
  37508. 'chart.theme.category4-gradients',
  37509. 'chart.theme.Category4:gradients'
  37510. ],
  37511. config: {
  37512. colors: [
  37513. '#ef5773',
  37514. '#fcbd2a',
  37515. '#4f770d',
  37516. '#1d3eaa',
  37517. '#9b001f'
  37518. ],
  37519. gradients: {
  37520. type: 'linear',
  37521. degrees: 90
  37522. }
  37523. }
  37524. });
  37525. Ext.define('Ext.chart.theme.Category5', {
  37526. extend: 'Ext.chart.theme.Base',
  37527. singleton: true,
  37528. alias: [
  37529. 'chart.theme.category5',
  37530. 'chart.theme.Category5'
  37531. ],
  37532. config: {
  37533. colors: [
  37534. '#7eae29',
  37535. '#fdbe2a',
  37536. '#910019',
  37537. '#27b4bc',
  37538. '#d74dbc'
  37539. ]
  37540. }
  37541. });
  37542. Ext.define('Ext.chart.theme.Category5Gradients', {
  37543. extend: 'Ext.chart.theme.Base',
  37544. singleton: true,
  37545. alias: [
  37546. 'chart.theme.category5-gradients',
  37547. 'chart.theme.Category5:gradients'
  37548. ],
  37549. config: {
  37550. colors: [
  37551. '#7eae29',
  37552. '#fdbe2a',
  37553. '#910019',
  37554. '#27b4bc',
  37555. '#d74dbc'
  37556. ],
  37557. gradients: {
  37558. type: 'linear',
  37559. degrees: 90
  37560. }
  37561. }
  37562. });
  37563. Ext.define('Ext.chart.theme.Category6', {
  37564. extend: 'Ext.chart.theme.Base',
  37565. singleton: true,
  37566. alias: [
  37567. 'chart.theme.category6',
  37568. 'chart.theme.Category6'
  37569. ],
  37570. config: {
  37571. colors: [
  37572. '#44dce1',
  37573. '#0b2592',
  37574. '#996e05',
  37575. '#7fb325',
  37576. '#b821a1'
  37577. ]
  37578. }
  37579. });
  37580. Ext.define('Ext.chart.theme.Category6Gradients', {
  37581. extend: 'Ext.chart.theme.Base',
  37582. singleton: true,
  37583. alias: [
  37584. 'chart.theme.category6-gradients',
  37585. 'chart.theme.Category6:gradients'
  37586. ],
  37587. config: {
  37588. colors: [
  37589. '#44dce1',
  37590. '#0b2592',
  37591. '#996e05',
  37592. '#7fb325',
  37593. '#b821a1'
  37594. ],
  37595. gradients: {
  37596. type: 'linear',
  37597. degrees: 90
  37598. }
  37599. }
  37600. });
  37601. Ext.define('Ext.chart.theme.DefaultGradients', {
  37602. extend: 'Ext.chart.theme.Base',
  37603. singleton: true,
  37604. alias: [
  37605. 'chart.theme.default-gradients',
  37606. 'chart.theme.Base:gradients'
  37607. ],
  37608. config: {
  37609. gradients: {
  37610. type: 'linear',
  37611. degrees: 90
  37612. }
  37613. }
  37614. });
  37615. Ext.define('Ext.chart.theme.Green', {
  37616. extend: 'Ext.chart.theme.Base',
  37617. singleton: true,
  37618. alias: [
  37619. 'chart.theme.green',
  37620. 'chart.theme.Green'
  37621. ],
  37622. config: {
  37623. baseColor: '#b1da5a'
  37624. }
  37625. });
  37626. Ext.define('Ext.chart.theme.GreenGradients', {
  37627. extend: 'Ext.chart.theme.Base',
  37628. singleton: true,
  37629. alias: [
  37630. 'chart.theme.green-gradients',
  37631. 'chart.theme.Green:gradients'
  37632. ],
  37633. config: {
  37634. baseColor: '#b1da5a',
  37635. gradients: {
  37636. type: 'linear',
  37637. degrees: 90
  37638. }
  37639. }
  37640. });
  37641. Ext.define('Ext.chart.theme.Midnight', {
  37642. extend: 'Ext.chart.theme.Base',
  37643. singleton: true,
  37644. alias: [
  37645. 'chart.theme.midnight',
  37646. 'chart.theme.Midnight'
  37647. ],
  37648. config: {
  37649. colors: [
  37650. '#a837ff',
  37651. '#4ac0f2',
  37652. '#ff4d35',
  37653. '#ff8809',
  37654. '#61c102',
  37655. '#ff37ea'
  37656. ],
  37657. chart: {
  37658. defaults: {
  37659. captions: {
  37660. title: {
  37661. docked: 'top',
  37662. padding: 5,
  37663. style: {
  37664. textAlign: 'center',
  37665. fontFamily: 'default',
  37666. fontWeight: 'bold',
  37667. fillStyle: 'rgb(224, 224, 227)',
  37668. fontSize: 'default*1.6'
  37669. }
  37670. },
  37671. subtitle: {
  37672. docked: 'top',
  37673. style: {
  37674. textAlign: 'center',
  37675. fontFamily: 'default',
  37676. fontWeight: 'normal',
  37677. fillStyle: 'rgb(224, 224, 227)',
  37678. fontSize: 'default*1.3'
  37679. }
  37680. },
  37681. credits: {
  37682. docked: 'bottom',
  37683. padding: 5,
  37684. style: {
  37685. textAlign: 'left',
  37686. fontFamily: 'default',
  37687. fontWeight: 'lighter',
  37688. fillStyle: 'rgb(224, 224, 227)',
  37689. fontSize: 'default'
  37690. }
  37691. }
  37692. },
  37693. background: 'rgb(52, 52, 53)'
  37694. }
  37695. },
  37696. axis: {
  37697. defaults: {
  37698. style: {
  37699. strokeStyle: 'rgb(224, 224, 227)'
  37700. },
  37701. label: {
  37702. fillStyle: 'rgb(224, 224, 227)'
  37703. },
  37704. title: {
  37705. fillStyle: 'rgb(224, 224, 227)'
  37706. },
  37707. grid: {
  37708. strokeStyle: 'rgb(112, 112, 115)'
  37709. }
  37710. }
  37711. },
  37712. series: {
  37713. defaults: {
  37714. label: {
  37715. fillStyle: 'rgb(224, 224, 227)'
  37716. }
  37717. }
  37718. },
  37719. sprites: {
  37720. text: {
  37721. fillStyle: 'rgb(224, 224, 227)'
  37722. }
  37723. },
  37724. legend: {
  37725. label: {
  37726. fillStyle: 'white'
  37727. },
  37728. border: {
  37729. lineWidth: 2,
  37730. fillStyle: 'rgba(255, 255, 255, 0.3)',
  37731. strokeStyle: 'rgb(150, 150, 150)'
  37732. },
  37733. background: 'rgb(52, 52, 53)'
  37734. }
  37735. }
  37736. });
  37737. Ext.define('Ext.chart.theme.Muted', {
  37738. extend: 'Ext.chart.theme.Base',
  37739. singleton: true,
  37740. alias: [
  37741. 'chart.theme.muted',
  37742. 'chart.theme.Muted'
  37743. ],
  37744. config: {
  37745. colors: [
  37746. '#8ca640',
  37747. '#974144',
  37748. '#4091ba',
  37749. '#8e658e',
  37750. '#3b8d8b',
  37751. '#b86465',
  37752. '#d2af69',
  37753. '#6e8852',
  37754. '#3dcc7e',
  37755. '#a6bed1',
  37756. '#cbaa4b',
  37757. '#998baa'
  37758. ]
  37759. }
  37760. });
  37761. Ext.define('Ext.chart.theme.Purple', {
  37762. extend: 'Ext.chart.theme.Base',
  37763. singleton: true,
  37764. alias: [
  37765. 'chart.theme.purple',
  37766. 'chart.theme.Purple'
  37767. ],
  37768. config: {
  37769. baseColor: '#da5abd'
  37770. }
  37771. });
  37772. Ext.define('Ext.chart.theme.PurpleGradients', {
  37773. extend: 'Ext.chart.theme.Base',
  37774. singleton: true,
  37775. alias: [
  37776. 'chart.theme.purple-gradients',
  37777. 'chart.theme.Purple:gradients'
  37778. ],
  37779. config: {
  37780. baseColor: '#da5abd',
  37781. gradients: {
  37782. type: 'linear',
  37783. degrees: 90
  37784. }
  37785. }
  37786. });
  37787. Ext.define('Ext.chart.theme.Red', {
  37788. extend: 'Ext.chart.theme.Base',
  37789. singleton: true,
  37790. alias: [
  37791. 'chart.theme.red',
  37792. 'chart.theme.Red'
  37793. ],
  37794. config: {
  37795. baseColor: '#e84b67'
  37796. }
  37797. });
  37798. Ext.define('Ext.chart.theme.RedGradients', {
  37799. extend: 'Ext.chart.theme.Base',
  37800. singleton: true,
  37801. alias: [
  37802. 'chart.theme.red-gradients',
  37803. 'chart.theme.Red:gradients'
  37804. ],
  37805. config: {
  37806. baseColor: '#e84b67',
  37807. gradients: {
  37808. type: 'linear',
  37809. degrees: 90
  37810. }
  37811. }
  37812. });
  37813. Ext.define('Ext.chart.theme.Sky', {
  37814. extend: 'Ext.chart.theme.Base',
  37815. singleton: true,
  37816. alias: [
  37817. 'chart.theme.sky',
  37818. 'chart.theme.Sky'
  37819. ],
  37820. config: {
  37821. baseColor: '#4ce0e7'
  37822. }
  37823. });
  37824. Ext.define('Ext.chart.theme.SkyGradients', {
  37825. extend: 'Ext.chart.theme.Base',
  37826. singleton: true,
  37827. alias: [
  37828. 'chart.theme.sky-gradients',
  37829. 'chart.theme.Sky:gradients'
  37830. ],
  37831. config: {
  37832. baseColor: '#4ce0e7',
  37833. gradients: {
  37834. type: 'linear',
  37835. degrees: 90
  37836. }
  37837. }
  37838. });
  37839. Ext.define('Ext.chart.theme.Yellow', {
  37840. extend: 'Ext.chart.theme.Base',
  37841. singleton: true,
  37842. alias: [
  37843. 'chart.theme.yellow',
  37844. 'chart.theme.Yellow'
  37845. ],
  37846. config: {
  37847. baseColor: '#fec935'
  37848. }
  37849. });
  37850. Ext.define('Ext.chart.theme.YellowGradients', {
  37851. extend: 'Ext.chart.theme.Base',
  37852. singleton: true,
  37853. alias: [
  37854. 'chart.theme.yellow-gradients',
  37855. 'chart.theme.Yellow:gradients'
  37856. ],
  37857. config: {
  37858. baseColor: '#fec935',
  37859. gradients: {
  37860. type: 'linear',
  37861. degrees: 90
  37862. }
  37863. }
  37864. });
  37865. /**
  37866. * A helper class to facilitate common operations on points and vectors.
  37867. */
  37868. Ext.define('Ext.draw.Point', {
  37869. requires: [
  37870. 'Ext.draw.Draw',
  37871. 'Ext.draw.Matrix'
  37872. ],
  37873. isPoint: true,
  37874. x: 0,
  37875. y: 0,
  37876. length: 0,
  37877. angle: 0,
  37878. angleUnits: 'degrees',
  37879. statics: {
  37880. /**
  37881. * @method
  37882. * @static
  37883. * Creates a flyweight Ext.draw.Point instance.
  37884. * Takes the same parameters as the {@link Ext.draw.Point#method!constructor}.
  37885. * Do not hold the instance of the flyweight point.
  37886. *
  37887. * @param {Number/Number[]/Object/Ext.draw.Point} point
  37888. * @return {Ext.draw.Point}
  37889. */
  37890. fly: (function() {
  37891. var point = null;
  37892. return function(x, y) {
  37893. if (!point) {
  37894. point = new Ext.draw.Point();
  37895. }
  37896. point.constructor(x, y);
  37897. return point;
  37898. };
  37899. })()
  37900. },
  37901. /**
  37902. * Creates a point.
  37903. *
  37904. * new Ext.draw.Point(3, 4);
  37905. * new Ext.draw.Point(3); // both x and y equal 3
  37906. * new Ext.draw.Point([3, 4]);
  37907. * new Ext.draw.Point({x: 3, y: 4});
  37908. * new Ext.draw.Point(p); // where `p` is a Ext.draw.Point instance.
  37909. *
  37910. * @param {Number/Number[]/Object/Ext.draw.Point} x
  37911. * @param {Number/Number[]/Object/Ext.draw.Point} y
  37912. */
  37913. constructor: function(x, y) {
  37914. var me = this;
  37915. if (typeof x === 'number') {
  37916. me.x = x;
  37917. if (typeof y === 'number') {
  37918. me.y = y;
  37919. } else {
  37920. me.y = x;
  37921. }
  37922. } else if (Ext.isArray(x)) {
  37923. me.x = x[0];
  37924. me.y = x[1];
  37925. } else if (x) {
  37926. me.x = x.x;
  37927. me.y = x.y;
  37928. }
  37929. me.calculatePolar();
  37930. },
  37931. calculateCartesian: function() {
  37932. var me = this,
  37933. length = me.length,
  37934. angle = me.angle;
  37935. if (me.angleUnits === 'degrees') {
  37936. angle = Ext.draw.Draw.rad(angle);
  37937. }
  37938. me.x = Math.cos(angle) * length;
  37939. me.y = Math.sin(angle) * length;
  37940. },
  37941. calculatePolar: function() {
  37942. var me = this,
  37943. x = me.x,
  37944. y = me.y;
  37945. me.length = Math.sqrt(x * x + y * y);
  37946. me.angle = Math.atan2(y, x);
  37947. if (me.angleUnits === 'degrees') {
  37948. me.angle = Ext.draw.Draw.degrees(me.angle);
  37949. }
  37950. },
  37951. /**
  37952. * Sets the x-coordinate of the point.
  37953. * @param {Number} x
  37954. */
  37955. setX: function(x) {
  37956. this.x = x;
  37957. this.calculatePolar();
  37958. },
  37959. /**
  37960. * Sets the y-coordinate of the point.
  37961. * @param {Number} y
  37962. */
  37963. setY: function(y) {
  37964. this.y = y;
  37965. this.calculatePolar();
  37966. },
  37967. /**
  37968. * Sets coordinates of the point.
  37969. * Takes the same parameters as the {@link #method!constructor}.
  37970. * @param {Number/Number[]/Object/Ext.draw.Point} x
  37971. * @param {Number/Number[]/Object/Ext.draw.Point} y
  37972. */
  37973. set: function(x, y) {
  37974. this.constructor(x, y);
  37975. },
  37976. /**
  37977. * Sets the angle of the vector (measured from the x-axis to the vector)
  37978. * without changing its length.
  37979. * @param {Number} angle
  37980. */
  37981. setAngle: function(angle) {
  37982. this.angle = angle;
  37983. this.calculateCartesian();
  37984. },
  37985. /**
  37986. * Sets the length of the vector without changing its angle.
  37987. * @param {Number} length
  37988. */
  37989. setLength: function(length) {
  37990. this.length = length;
  37991. this.calculateCartesian();
  37992. },
  37993. /**
  37994. * Sets both the angle and the length of the vector.
  37995. * A point can be thought of as a vector pointing from the origin to the point's location.
  37996. * This can also be interpreted as setting coordinates of a point in the polar
  37997. * coordinate system.
  37998. * @param {Number} angle
  37999. * @param {Number} length
  38000. */
  38001. setPolar: function(angle, length) {
  38002. this.angle = angle;
  38003. this.length = length;
  38004. this.calculateCartesian();
  38005. },
  38006. /**
  38007. * Returns a copy of the point.
  38008. * @return {Ext.draw.Point}
  38009. */
  38010. clone: function() {
  38011. return new Ext.draw.Point(this.x, this.y);
  38012. },
  38013. /**
  38014. * Adds another vector to this one and returns the resulting vector
  38015. * without changing this vector.
  38016. * @param {Number/Number[]/Object/Ext.draw.Point} x
  38017. * @param {Number/Number[]/Object/Ext.draw.Point} y
  38018. * @return {Ext.draw.Point}
  38019. */
  38020. add: function(x, y) {
  38021. var fly = Ext.draw.Point.fly(x, y);
  38022. return new Ext.draw.Point(this.x + fly.x, this.y + fly.y);
  38023. },
  38024. /**
  38025. * Subtracts another vector from this one and returns the resulting vector
  38026. * without changing this vector.
  38027. * @param {Number/Number[]/Object/Ext.draw.Point} x
  38028. * @param {Number/Number[]/Object/Ext.draw.Point} y
  38029. * @return {Ext.draw.Point}
  38030. */
  38031. sub: function(x, y) {
  38032. var fly = Ext.draw.Point.fly(x, y);
  38033. return new Ext.draw.Point(this.x - fly.x, this.y - fly.y);
  38034. },
  38035. /**
  38036. * Returns the result of scalar multiplication of this vector by the given factor.
  38037. * This vector is not modified.
  38038. * @param {Number} n The factor.
  38039. * @return {Ext.draw.Point}
  38040. */
  38041. mul: function(n) {
  38042. return new Ext.draw.Point(this.x * n, this.y * n);
  38043. },
  38044. /**
  38045. * Returns a vector which coordinates are the result of division of this vector's
  38046. * coordinates by the given number. This vector is not modified.
  38047. * This vector is not modified.
  38048. * @param {Number} n The denominator.
  38049. * @return {Ext.draw.Point}
  38050. */
  38051. div: function(n) {
  38052. return new Ext.draw.Point(this.x / n, this.y / n);
  38053. },
  38054. /**
  38055. * Returns the dot product of this vector and the given vector.
  38056. * @param {Number/Number[]/Object/Ext.draw.Point} x
  38057. * @param {Number/Number[]/Object/Ext.draw.Point} y
  38058. * @return {Number}
  38059. */
  38060. dot: function(x, y) {
  38061. var fly = Ext.draw.Point.fly(x, y);
  38062. return this.x * fly.x + this.y * fly.y;
  38063. },
  38064. /**
  38065. * Checks whether coordinates of the point match those of the point provided.
  38066. * @param {Number/Number[]/Object/Ext.draw.Point} x
  38067. * @param {Number/Number[]/Object/Ext.draw.Point} y
  38068. * @return {Boolean}
  38069. */
  38070. equals: function(x, y) {
  38071. var fly = Ext.draw.Point.fly(x, y);
  38072. return this.x === fly.x && this.y === fly.y;
  38073. },
  38074. /**
  38075. * Rotates the point by the given angle. This point is not modified.
  38076. * @param {Number} angle The rotation angle.
  38077. * @param {Ext.draw.Point} [center] The center of rotation (optional). Defaults to origin.
  38078. * @return {Ext.draw.Point} The rotated point.
  38079. */
  38080. rotate: function(angle, center) {
  38081. var sin, cos, cx, cy, point;
  38082. if (this.angleUnits === 'degrees') {
  38083. angle = Ext.draw.Draw.rad(angle);
  38084. sin = Math.sin(angle);
  38085. cos = Math.cos(angle);
  38086. }
  38087. if (center) {
  38088. cx = center.x;
  38089. cy = center.y;
  38090. } else {
  38091. cx = 0;
  38092. cy = 0;
  38093. }
  38094. point = Ext.draw.Matrix.fly([
  38095. cos,
  38096. sin,
  38097. -sin,
  38098. cos,
  38099. cx - cos * cx + cy * sin,
  38100. cy - cos * cy + cx * -sin
  38101. ]).transformPoint(this);
  38102. return new Ext.draw.Point(point);
  38103. },
  38104. /**
  38105. * Transforms the point from one coordinate system to another
  38106. * using the transformation matrix provided. This point is not modified.
  38107. * @param {Ext.draw.Matrix/Number[]} matrix A trasformation matrix or its elements.
  38108. * @return {Ext.draw.Point}
  38109. */
  38110. transform: function(matrix) {
  38111. if (matrix && matrix.isMatrix) {
  38112. return new Ext.draw.Point(matrix.transformPoint(this));
  38113. } else if (arguments.length === 6) {
  38114. return new Ext.draw.Point(Ext.draw.Matrix.fly(arguments).transformPoint(this));
  38115. } else {
  38116. Ext.raise("Invalid parameters.");
  38117. }
  38118. },
  38119. /**
  38120. * Returns a new point with rounded x and y values. This point is not modified.
  38121. * @return {Ext.draw.Point}
  38122. */
  38123. round: function() {
  38124. return new Ext.draw.Point(Math.round(this.x), Math.round(this.y));
  38125. },
  38126. /**
  38127. * Returns a new point with ceiled x and y values. This point is not modified.
  38128. * @return {Ext.draw.Point}
  38129. */
  38130. ceil: function() {
  38131. return new Ext.draw.Point(Math.ceil(this.x), Math.ceil(this.y));
  38132. },
  38133. /**
  38134. * Returns a new point with floored x and y values. This point is not modified.
  38135. * @return {Ext.draw.Point}
  38136. */
  38137. floor: function() {
  38138. return new Ext.draw.Point(Math.floor(this.x), Math.floor(this.y));
  38139. },
  38140. /**
  38141. * Returns a new point with absolute values of the x and y values of this point.
  38142. * This point is not modified.
  38143. * @return {Ext.draw.Point}
  38144. */
  38145. abs: function(x, y) {
  38146. return new Ext.draw.Point(Math.abs(this.x), Math.abs(this.y));
  38147. },
  38148. /**
  38149. * Normalizes the vector by changing its length to 1 without changing its angle.
  38150. * The returned result is a normalized vector. This vector is not modified.
  38151. * @param {Number} [factor=1] Multiplication factor. Defaults to 1.
  38152. * @return {Ext.draw.Point}
  38153. */
  38154. normalize: function(factor) {
  38155. var x = this.x,
  38156. y = this.y,
  38157. k = (factor || 1) / Math.sqrt(x * x + y * y);
  38158. return new Ext.draw.Point(x * k, y * k);
  38159. },
  38160. /**
  38161. * Returns the vector from the point perpendicular to the line (shortest distance).
  38162. * Where line is specified using two points or the coordinates of those points.
  38163. * @param {Ext.draw.Point} p1
  38164. * @param {Ext.draw.Point} p2
  38165. * @return {Ext.draw.Point}
  38166. */
  38167. getDistanceToLine: function(p1, p2) {
  38168. var n, pp1;
  38169. if (arguments.length === 4) {
  38170. p1 = new Ext.draw.Point(arguments[0], arguments[1]);
  38171. p2 = new Ext.draw.Point(arguments[2], arguments[3]);
  38172. }
  38173. // See http://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Vector_formulation
  38174. n = p2.sub(p1).normalize();
  38175. pp1 = p1.sub(this);
  38176. return pp1.sub(n.mul(pp1.dot(n)));
  38177. },
  38178. /**
  38179. * Checks if both x and y coordinates of the point are zero.
  38180. * @return {Boolean}
  38181. */
  38182. isZero: function() {
  38183. return this.x === 0 && this.y === 0;
  38184. },
  38185. /**
  38186. * Checks if both x and y coordinates of the point are valid numbers.
  38187. * @return {Boolean}
  38188. */
  38189. isNumber: function() {
  38190. return Ext.isNumber(this.x) && Ext.isNumber(this.y);
  38191. }
  38192. });
  38193. /**
  38194. * A draw container {@link Ext.AbstractPlugin plugin} that adds ability to listen
  38195. * to sprite events. For example:
  38196. *
  38197. * var drawContainer = Ext.create('Ext.draw.Container', {
  38198. * plugins: {
  38199. * spriteevents: true
  38200. * },
  38201. * renderTo: Ext.getBody(),
  38202. * width: 200,
  38203. * height: 200,
  38204. * sprites: [{
  38205. * type: 'circle',
  38206. * fillStyle: '#79BB3F',
  38207. * r: 50,
  38208. * x: 100,
  38209. * y: 100
  38210. * }],
  38211. * listeners: {
  38212. * spriteclick: function (item, event) {
  38213. * var sprite = item && item.sprite;
  38214. * if (sprite) {
  38215. * sprite.setAttributes({fillStyle: 'red'});
  38216. sprite.getSurface().renderFrame();
  38217. * }
  38218. * }
  38219. * }
  38220. * });
  38221. */
  38222. Ext.define('Ext.draw.plugin.SpriteEvents', {
  38223. extend: 'Ext.plugin.Abstract',
  38224. alias: 'plugin.spriteevents',
  38225. requires: [
  38226. 'Ext.draw.overrides.hittest.All'
  38227. ],
  38228. /**
  38229. * @event spritemousemove
  38230. * Fires when the mouse is moved on a sprite.
  38231. * @param {Object} sprite
  38232. * @param {Event} event
  38233. */
  38234. /**
  38235. * @event spritemouseup
  38236. * Fires when a mouseup event occurs on a sprite.
  38237. * @param {Object} sprite
  38238. * @param {Event} event
  38239. */
  38240. /**
  38241. * @event spritemousedown
  38242. * Fires when a mousedown event occurs on a sprite.
  38243. * @param {Object} sprite
  38244. * @param {Event} event
  38245. */
  38246. /**
  38247. * @event spritemouseover
  38248. * Fires when the mouse enters a sprite.
  38249. * @param {Object} sprite
  38250. * @param {Event} event
  38251. */
  38252. /**
  38253. * @event spritemouseout
  38254. * Fires when the mouse exits a sprite.
  38255. * @param {Object} sprite
  38256. * @param {Event} event
  38257. */
  38258. /**
  38259. * @event spriteclick
  38260. * Fires when a click event occurs on a sprite.
  38261. * @param {Object} sprite
  38262. * @param {Event} event
  38263. */
  38264. /**
  38265. * @event spritedblclick
  38266. * Fires when a double click event occurs on a sprite.
  38267. * @param {Object} sprite
  38268. * @param {Event} event
  38269. */
  38270. /**
  38271. * @event spritetap
  38272. * Fires when a tap event occurs on a sprite.
  38273. * @param {Object} sprite
  38274. * @param {Event} event
  38275. */
  38276. mouseMoveEvents: {
  38277. mousemove: true,
  38278. mouseover: true,
  38279. mouseout: true
  38280. },
  38281. spriteMouseMoveEvents: {
  38282. spritemousemove: true,
  38283. spritemouseover: true,
  38284. spritemouseout: true
  38285. },
  38286. init: function(drawContainer) {
  38287. var handleEvent = 'handleEvent';
  38288. this.drawContainer = drawContainer;
  38289. drawContainer.addElementListener({
  38290. click: handleEvent,
  38291. dblclick: handleEvent,
  38292. mousedown: handleEvent,
  38293. mousemove: handleEvent,
  38294. mouseup: handleEvent,
  38295. mouseover: handleEvent,
  38296. mouseout: handleEvent,
  38297. // run our handlers before user code
  38298. priority: 1001,
  38299. scope: this
  38300. });
  38301. },
  38302. hasSpriteMouseMoveListeners: function() {
  38303. var listeners = this.drawContainer.hasListeners,
  38304. name;
  38305. for (name in this.spriteMouseMoveEvents) {
  38306. if (name in listeners) {
  38307. return true;
  38308. }
  38309. }
  38310. return false;
  38311. },
  38312. hitTestEvent: function(e) {
  38313. var items = this.drawContainer.getItems(),
  38314. surface, sprite, i;
  38315. for (i = items.length - 1; i >= 0; i--) {
  38316. surface = items.get(i);
  38317. sprite = surface.hitTestEvent(e);
  38318. if (sprite) {
  38319. return sprite;
  38320. }
  38321. }
  38322. return null;
  38323. },
  38324. handleEvent: function(e) {
  38325. var me = this,
  38326. drawContainer = me.drawContainer,
  38327. isMouseMoveEvent = e.type in me.mouseMoveEvents,
  38328. lastSprite = me.lastSprite,
  38329. sprite;
  38330. if (isMouseMoveEvent && !me.hasSpriteMouseMoveListeners()) {
  38331. return;
  38332. }
  38333. sprite = me.hitTestEvent(e);
  38334. if (isMouseMoveEvent && !Ext.Object.equals(sprite, lastSprite)) {
  38335. if (lastSprite) {
  38336. drawContainer.fireEvent('spritemouseout', lastSprite, e);
  38337. }
  38338. if (sprite) {
  38339. drawContainer.fireEvent('spritemouseover', sprite, e);
  38340. }
  38341. }
  38342. if (sprite) {
  38343. drawContainer.fireEvent('sprite' + e.type, sprite, e);
  38344. }
  38345. me.lastSprite = sprite;
  38346. }
  38347. });
  38348. /**
  38349. * The ItemInfo interaction allows displaying detailed information about a series data
  38350. * point in a popup panel.
  38351. *
  38352. * To attach this interaction to a chart, include an entry in the chart's
  38353. * {@link Ext.chart.AbstractChart#interactions interactions} config with the `iteminfo` type:
  38354. *
  38355. * new Ext.chart.AbstractChart({
  38356. * renderTo: Ext.getBody(),
  38357. * width: 800,
  38358. * height: 600,
  38359. * store: store1,
  38360. * axes: [ ...some axes options... ],
  38361. * series: [ ...some series options... ],
  38362. * interactions: [{
  38363. * type: 'iteminfo',
  38364. * listeners: {
  38365. * show: function(me, item, panel) {
  38366. * panel.setHtml('Stock Price: $' + item.record.get('price'));
  38367. * }
  38368. * }
  38369. * }]
  38370. * });
  38371. */
  38372. Ext.define('Ext.chart.interactions.ItemInfo', {
  38373. extend: 'Ext.chart.interactions.Abstract',
  38374. type: 'iteminfo',
  38375. alias: 'interaction.iteminfo',
  38376. /**
  38377. * @event show
  38378. * Fires when the info panel is shown.
  38379. * @param {Ext.chart.interactions.ItemInfo} this The interaction instance
  38380. * @param {Object} item The item whose info is being displayed
  38381. * @param {Ext.Panel} panel The panel for displaying the info
  38382. */
  38383. config: {
  38384. /**
  38385. * @cfg {Object} gestures
  38386. * Defines the gestures that should trigger the item info panel to be displayed.
  38387. */
  38388. gestures: {
  38389. tap: 'onInfoGesture'
  38390. },
  38391. /**
  38392. * @cfg {Object} panel
  38393. * An optional set of configuration overrides for the {@link Ext.Panel} that gets
  38394. * displayed. This object will be merged with the default panel configuration.
  38395. */
  38396. panel: {
  38397. modal: true,
  38398. centered: true,
  38399. width: 300,
  38400. height: 200,
  38401. scrollable: 'vertical',
  38402. hideOnMaskTap: true,
  38403. fullscreen: false,
  38404. hidden: false,
  38405. zIndex: 30
  38406. }
  38407. },
  38408. item: null,
  38409. applyPanel: function(panel, oldPanel) {
  38410. return Ext.factory(panel, 'Ext.Panel', oldPanel);
  38411. },
  38412. updatePanel: function(panel, oldPanel) {
  38413. if (panel) {
  38414. panel.on('hide', "reset", this);
  38415. }
  38416. if (oldPanel) {
  38417. oldPanel.un('hide', "reset", this);
  38418. }
  38419. },
  38420. onInfoGesture: function(e, element) {
  38421. var me = this,
  38422. panel = me.getPanel(),
  38423. item = me.getItemForEvent(e);
  38424. if (item) {
  38425. me.item = item;
  38426. me.fireEvent('show', me, item, panel);
  38427. Ext.Viewport.add(panel);
  38428. panel.show('pop');
  38429. item.series.setAttributesForItem(item, {
  38430. highlighted: true
  38431. });
  38432. me.sync();
  38433. }
  38434. return false;
  38435. },
  38436. reset: function() {
  38437. var me = this,
  38438. item = me.item;
  38439. if (item) {
  38440. item.series.setAttributesForItem(item, {
  38441. highlighted: false
  38442. });
  38443. me.item = null;
  38444. me.sync();
  38445. }
  38446. }
  38447. });