charts-debug.js 1.3 MB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737207382073920740207412074220743207442074520746207472074820749207502075120752207532075420755207562075720758207592076020761207622076320764207652076620767207682076920770207712077220773207742077520776207772077820779207802078120782207832078420785207862078720788207892079020791207922079320794207952079620797207982079920800208012080220803208042080520806208072080820809208102081120812208132081420815208162081720818208192082020821208222082320824208252082620827208282082920830208312083220833208342083520836208372083820839208402084120842208432084420845208462084720848208492085020851208522085320854208552085620857208582085920860208612086220863208642086520866208672086820869208702087120872208732087420875208762087720878208792088020881208822088320884208852088620887208882088920890208912089220893208942089520896208972089820899209002090120902209032090420905209062090720908209092091020911209122091320914209152091620917209182091920920209212092220923209242092520926209272092820929209302093120932209332093420935209362093720938209392094020941209422094320944209452094620947209482094920950209512095220953209542095520956209572095820959209602096120962209632096420965209662096720968209692097020971209722097320974209752097620977209782097920980209812098220983209842098520986209872098820989209902099120992209932099420995209962099720998209992100021001210022100321004210052100621007210082100921010210112101221013210142101521016210172101821019210202102121022210232102421025210262102721028210292103021031210322103321034210352103621037210382103921040210412104221043210442104521046210472104821049210502105121052210532105421055210562105721058210592106021061210622106321064210652106621067210682106921070210712107221073210742107521076210772107821079210802108121082210832108421085210862108721088210892109021091210922109321094210952109621097210982109921100211012110221103211042110521106211072110821109211102111121112211132111421115211162111721118211192112021121211222112321124211252112621127211282112921130211312113221133211342113521136211372113821139211402114121142211432114421145211462114721148211492115021151211522115321154211552115621157211582115921160211612116221163211642116521166211672116821169211702117121172211732117421175211762117721178211792118021181211822118321184211852118621187211882118921190211912119221193211942119521196211972119821199212002120121202212032120421205212062120721208212092121021211212122121321214212152121621217212182121921220212212122221223212242122521226212272122821229212302123121232212332123421235212362123721238212392124021241212422124321244212452124621247212482124921250212512125221253212542125521256212572125821259212602126121262212632126421265212662126721268212692127021271212722127321274212752127621277212782127921280212812128221283212842128521286212872128821289212902129121292212932129421295212962129721298212992130021301213022130321304213052130621307213082130921310213112131221313213142131521316213172131821319213202132121322213232132421325213262132721328213292133021331213322133321334213352133621337213382133921340213412134221343213442134521346213472134821349213502135121352213532135421355213562135721358213592136021361213622136321364213652136621367213682136921370213712137221373213742137521376213772137821379213802138121382213832138421385213862138721388213892139021391213922139321394213952139621397213982139921400214012140221403214042140521406214072140821409214102141121412214132141421415214162141721418214192142021421214222142321424214252142621427214282142921430214312143221433214342143521436214372143821439214402144121442214432144421445214462144721448214492145021451214522145321454214552145621457214582145921460214612146221463214642146521466214672146821469214702147121472214732147421475214762147721478214792148021481214822148321484214852148621487214882148921490214912149221493214942149521496214972149821499215002150121502215032150421505215062150721508215092151021511215122151321514215152151621517215182151921520215212152221523215242152521526215272152821529215302153121532215332153421535215362153721538215392154021541215422154321544215452154621547215482154921550215512155221553215542155521556215572155821559215602156121562215632156421565215662156721568215692157021571215722157321574215752157621577215782157921580215812158221583215842158521586215872158821589215902159121592215932159421595215962159721598215992160021601216022160321604216052160621607216082160921610216112161221613216142161521616216172161821619216202162121622216232162421625216262162721628216292163021631216322163321634216352163621637216382163921640216412164221643216442164521646216472164821649216502165121652216532165421655216562165721658216592166021661216622166321664216652166621667216682166921670216712167221673216742167521676216772167821679216802168121682216832168421685216862168721688216892169021691216922169321694216952169621697216982169921700217012170221703217042170521706217072170821709217102171121712217132171421715217162171721718217192172021721217222172321724217252172621727217282172921730217312173221733217342173521736217372173821739217402174121742217432174421745217462174721748217492175021751217522175321754217552175621757217582175921760217612176221763217642176521766217672176821769217702177121772217732177421775217762177721778217792178021781217822178321784217852178621787217882178921790217912179221793217942179521796217972179821799218002180121802218032180421805218062180721808218092181021811218122181321814218152181621817218182181921820218212182221823218242182521826218272182821829218302183121832218332183421835218362183721838218392184021841218422184321844218452184621847218482184921850218512185221853218542185521856218572185821859218602186121862218632186421865218662186721868218692187021871218722187321874218752187621877218782187921880218812188221883218842188521886218872188821889218902189121892218932189421895218962189721898218992190021901219022190321904219052190621907219082190921910219112191221913219142191521916219172191821919219202192121922219232192421925219262192721928219292193021931219322193321934219352193621937219382193921940219412194221943219442194521946219472194821949219502195121952219532195421955219562195721958219592196021961219622196321964219652196621967219682196921970219712197221973219742197521976219772197821979219802198121982219832198421985219862198721988219892199021991219922199321994219952199621997219982199922000220012200222003220042200522006220072200822009220102201122012220132201422015220162201722018220192202022021220222202322024220252202622027220282202922030220312203222033220342203522036220372203822039220402204122042220432204422045220462204722048220492205022051220522205322054220552205622057220582205922060220612206222063220642206522066220672206822069220702207122072220732207422075220762207722078220792208022081220822208322084220852208622087220882208922090220912209222093220942209522096220972209822099221002210122102221032210422105221062210722108221092211022111221122211322114221152211622117221182211922120221212212222123221242212522126221272212822129221302213122132221332213422135221362213722138221392214022141221422214322144221452214622147221482214922150221512215222153221542215522156221572215822159221602216122162221632216422165221662216722168221692217022171221722217322174221752217622177221782217922180221812218222183221842218522186221872218822189221902219122192221932219422195221962219722198221992220022201222022220322204222052220622207222082220922210222112221222213222142221522216222172221822219222202222122222222232222422225222262222722228222292223022231222322223322234222352223622237222382223922240222412224222243222442224522246222472224822249222502225122252222532225422255222562225722258222592226022261222622226322264222652226622267222682226922270222712227222273222742227522276222772227822279222802228122282222832228422285222862228722288222892229022291222922229322294222952229622297222982229922300223012230222303223042230522306223072230822309223102231122312223132231422315223162231722318223192232022321223222232322324223252232622327223282232922330223312233222333223342233522336223372233822339223402234122342223432234422345223462234722348223492235022351223522235322354223552235622357223582235922360223612236222363223642236522366223672236822369223702237122372223732237422375223762237722378223792238022381223822238322384223852238622387223882238922390223912239222393223942239522396223972239822399224002240122402224032240422405224062240722408224092241022411224122241322414224152241622417224182241922420224212242222423224242242522426224272242822429224302243122432224332243422435224362243722438224392244022441224422244322444224452244622447224482244922450224512245222453224542245522456224572245822459224602246122462224632246422465224662246722468224692247022471224722247322474224752247622477224782247922480224812248222483224842248522486224872248822489224902249122492224932249422495224962249722498224992250022501225022250322504225052250622507225082250922510225112251222513225142251522516225172251822519225202252122522225232252422525225262252722528225292253022531225322253322534225352253622537225382253922540225412254222543225442254522546225472254822549225502255122552225532255422555225562255722558225592256022561225622256322564225652256622567225682256922570225712257222573225742257522576225772257822579225802258122582225832258422585225862258722588225892259022591225922259322594225952259622597225982259922600226012260222603226042260522606226072260822609226102261122612226132261422615226162261722618226192262022621226222262322624226252262622627226282262922630226312263222633226342263522636226372263822639226402264122642226432264422645226462264722648226492265022651226522265322654226552265622657226582265922660226612266222663226642266522666226672266822669226702267122672226732267422675226762267722678226792268022681226822268322684226852268622687226882268922690226912269222693226942269522696226972269822699227002270122702227032270422705227062270722708227092271022711227122271322714227152271622717227182271922720227212272222723227242272522726227272272822729227302273122732227332273422735227362273722738227392274022741227422274322744227452274622747227482274922750227512275222753227542275522756227572275822759227602276122762227632276422765227662276722768227692277022771227722277322774227752277622777227782277922780227812278222783227842278522786227872278822789227902279122792227932279422795227962279722798227992280022801228022280322804228052280622807228082280922810228112281222813228142281522816228172281822819228202282122822228232282422825228262282722828228292283022831228322283322834228352283622837228382283922840228412284222843228442284522846228472284822849228502285122852228532285422855228562285722858228592286022861228622286322864228652286622867228682286922870228712287222873228742287522876228772287822879228802288122882228832288422885228862288722888228892289022891228922289322894228952289622897228982289922900229012290222903229042290522906229072290822909229102291122912229132291422915229162291722918229192292022921229222292322924229252292622927229282292922930229312293222933229342293522936229372293822939229402294122942229432294422945229462294722948229492295022951229522295322954229552295622957229582295922960229612296222963229642296522966229672296822969229702297122972229732297422975229762297722978229792298022981229822298322984229852298622987229882298922990229912299222993229942299522996229972299822999230002300123002230032300423005230062300723008230092301023011230122301323014230152301623017230182301923020230212302223023230242302523026230272302823029230302303123032230332303423035230362303723038230392304023041230422304323044230452304623047230482304923050230512305223053230542305523056230572305823059230602306123062230632306423065230662306723068230692307023071230722307323074230752307623077230782307923080230812308223083230842308523086230872308823089230902309123092230932309423095230962309723098230992310023101231022310323104231052310623107231082310923110231112311223113231142311523116231172311823119231202312123122231232312423125231262312723128231292313023131231322313323134231352313623137231382313923140231412314223143231442314523146231472314823149231502315123152231532315423155231562315723158231592316023161231622316323164231652316623167231682316923170231712317223173231742317523176231772317823179231802318123182231832318423185231862318723188231892319023191231922319323194231952319623197231982319923200232012320223203232042320523206232072320823209232102321123212232132321423215232162321723218232192322023221232222322323224232252322623227232282322923230232312323223233232342323523236232372323823239232402324123242232432324423245232462324723248232492325023251232522325323254232552325623257232582325923260232612326223263232642326523266232672326823269232702327123272232732327423275232762327723278232792328023281232822328323284232852328623287232882328923290232912329223293232942329523296232972329823299233002330123302233032330423305233062330723308233092331023311233122331323314233152331623317233182331923320233212332223323233242332523326233272332823329233302333123332233332333423335233362333723338233392334023341233422334323344233452334623347233482334923350233512335223353233542335523356233572335823359233602336123362233632336423365233662336723368233692337023371233722337323374233752337623377233782337923380233812338223383233842338523386233872338823389233902339123392233932339423395233962339723398233992340023401234022340323404234052340623407234082340923410234112341223413234142341523416234172341823419234202342123422234232342423425234262342723428234292343023431234322343323434234352343623437234382343923440234412344223443234442344523446234472344823449234502345123452234532345423455234562345723458234592346023461234622346323464234652346623467234682346923470234712347223473234742347523476234772347823479234802348123482234832348423485234862348723488234892349023491234922349323494234952349623497234982349923500235012350223503235042350523506235072350823509235102351123512235132351423515235162351723518235192352023521235222352323524235252352623527235282352923530235312353223533235342353523536235372353823539235402354123542235432354423545235462354723548235492355023551235522355323554235552355623557235582355923560235612356223563235642356523566235672356823569235702357123572235732357423575235762357723578235792358023581235822358323584235852358623587235882358923590235912359223593235942359523596235972359823599236002360123602236032360423605236062360723608236092361023611236122361323614236152361623617236182361923620236212362223623236242362523626236272362823629236302363123632236332363423635236362363723638236392364023641236422364323644236452364623647236482364923650236512365223653236542365523656236572365823659236602366123662236632366423665236662366723668236692367023671236722367323674236752367623677236782367923680236812368223683236842368523686236872368823689236902369123692236932369423695236962369723698236992370023701237022370323704237052370623707237082370923710237112371223713237142371523716237172371823719237202372123722237232372423725237262372723728237292373023731237322373323734237352373623737237382373923740237412374223743237442374523746237472374823749237502375123752237532375423755237562375723758237592376023761237622376323764237652376623767237682376923770237712377223773237742377523776237772377823779237802378123782237832378423785237862378723788237892379023791237922379323794237952379623797237982379923800238012380223803238042380523806238072380823809238102381123812238132381423815238162381723818238192382023821238222382323824238252382623827238282382923830238312383223833238342383523836238372383823839238402384123842238432384423845238462384723848238492385023851238522385323854238552385623857238582385923860238612386223863238642386523866238672386823869238702387123872238732387423875238762387723878238792388023881238822388323884238852388623887238882388923890238912389223893238942389523896238972389823899239002390123902239032390423905239062390723908239092391023911239122391323914239152391623917239182391923920239212392223923239242392523926239272392823929239302393123932239332393423935239362393723938239392394023941239422394323944239452394623947239482394923950239512395223953239542395523956239572395823959239602396123962239632396423965239662396723968239692397023971239722397323974239752397623977239782397923980239812398223983239842398523986239872398823989239902399123992239932399423995239962399723998239992400024001240022400324004240052400624007240082400924010240112401224013240142401524016240172401824019240202402124022240232402424025240262402724028240292403024031240322403324034240352403624037240382403924040240412404224043240442404524046240472404824049240502405124052240532405424055240562405724058240592406024061240622406324064240652406624067240682406924070240712407224073240742407524076240772407824079240802408124082240832408424085240862408724088240892409024091240922409324094240952409624097240982409924100241012410224103241042410524106241072410824109241102411124112241132411424115241162411724118241192412024121241222412324124241252412624127241282412924130241312413224133241342413524136241372413824139241402414124142241432414424145241462414724148241492415024151241522415324154241552415624157241582415924160241612416224163241642416524166241672416824169241702417124172241732417424175241762417724178241792418024181241822418324184241852418624187241882418924190241912419224193241942419524196241972419824199242002420124202242032420424205242062420724208242092421024211242122421324214242152421624217242182421924220242212422224223242242422524226242272422824229242302423124232242332423424235242362423724238242392424024241242422424324244242452424624247242482424924250242512425224253242542425524256242572425824259242602426124262242632426424265242662426724268242692427024271242722427324274242752427624277242782427924280242812428224283242842428524286242872428824289242902429124292242932429424295242962429724298242992430024301243022430324304243052430624307243082430924310243112431224313243142431524316243172431824319243202432124322243232432424325243262432724328243292433024331243322433324334243352433624337243382433924340243412434224343243442434524346243472434824349243502435124352243532435424355243562435724358243592436024361243622436324364243652436624367243682436924370243712437224373243742437524376243772437824379243802438124382243832438424385243862438724388243892439024391243922439324394243952439624397243982439924400244012440224403244042440524406244072440824409244102441124412244132441424415244162441724418244192442024421244222442324424244252442624427244282442924430244312443224433244342443524436244372443824439244402444124442244432444424445244462444724448244492445024451244522445324454244552445624457244582445924460244612446224463244642446524466244672446824469244702447124472244732447424475244762447724478244792448024481244822448324484244852448624487244882448924490244912449224493244942449524496244972449824499245002450124502245032450424505245062450724508245092451024511245122451324514245152451624517245182451924520245212452224523245242452524526245272452824529245302453124532245332453424535245362453724538245392454024541245422454324544245452454624547245482454924550245512455224553245542455524556245572455824559245602456124562245632456424565245662456724568245692457024571245722457324574245752457624577245782457924580245812458224583245842458524586245872458824589245902459124592245932459424595245962459724598245992460024601246022460324604246052460624607246082460924610246112461224613246142461524616246172461824619246202462124622246232462424625246262462724628246292463024631246322463324634246352463624637246382463924640246412464224643246442464524646246472464824649246502465124652246532465424655246562465724658246592466024661246622466324664246652466624667246682466924670246712467224673246742467524676246772467824679246802468124682246832468424685246862468724688246892469024691246922469324694246952469624697246982469924700247012470224703247042470524706247072470824709247102471124712247132471424715247162471724718247192472024721247222472324724247252472624727247282472924730247312473224733247342473524736247372473824739247402474124742247432474424745247462474724748247492475024751247522475324754247552475624757247582475924760247612476224763247642476524766247672476824769247702477124772247732477424775247762477724778247792478024781247822478324784247852478624787247882478924790247912479224793247942479524796247972479824799248002480124802248032480424805248062480724808248092481024811248122481324814248152481624817248182481924820248212482224823248242482524826248272482824829248302483124832248332483424835248362483724838248392484024841248422484324844248452484624847248482484924850248512485224853248542485524856248572485824859248602486124862248632486424865248662486724868248692487024871248722487324874248752487624877248782487924880248812488224883248842488524886248872488824889248902489124892248932489424895248962489724898248992490024901249022490324904249052490624907249082490924910249112491224913249142491524916249172491824919249202492124922249232492424925249262492724928249292493024931249322493324934249352493624937249382493924940249412494224943249442494524946249472494824949249502495124952249532495424955249562495724958249592496024961249622496324964249652496624967249682496924970249712497224973249742497524976249772497824979249802498124982249832498424985249862498724988249892499024991249922499324994249952499624997249982499925000250012500225003250042500525006250072500825009250102501125012250132501425015250162501725018250192502025021250222502325024250252502625027250282502925030250312503225033250342503525036250372503825039250402504125042250432504425045250462504725048250492505025051250522505325054250552505625057250582505925060250612506225063250642506525066250672506825069250702507125072250732507425075250762507725078250792508025081250822508325084250852508625087250882508925090250912509225093250942509525096250972509825099251002510125102251032510425105251062510725108251092511025111251122511325114251152511625117251182511925120251212512225123251242512525126251272512825129251302513125132251332513425135251362513725138251392514025141251422514325144251452514625147251482514925150251512515225153251542515525156251572515825159251602516125162251632516425165251662516725168251692517025171251722517325174251752517625177251782517925180251812518225183251842518525186251872518825189251902519125192251932519425195251962519725198251992520025201252022520325204252052520625207252082520925210252112521225213252142521525216252172521825219252202522125222252232522425225252262522725228252292523025231252322523325234252352523625237252382523925240252412524225243252442524525246252472524825249252502525125252252532525425255252562525725258252592526025261252622526325264252652526625267252682526925270252712527225273252742527525276252772527825279252802528125282252832528425285252862528725288252892529025291252922529325294252952529625297252982529925300253012530225303253042530525306253072530825309253102531125312253132531425315253162531725318253192532025321253222532325324253252532625327253282532925330253312533225333253342533525336253372533825339253402534125342253432534425345253462534725348253492535025351253522535325354253552535625357253582535925360253612536225363253642536525366253672536825369253702537125372253732537425375253762537725378253792538025381253822538325384253852538625387253882538925390253912539225393253942539525396253972539825399254002540125402254032540425405254062540725408254092541025411254122541325414254152541625417254182541925420254212542225423254242542525426254272542825429254302543125432254332543425435254362543725438254392544025441254422544325444254452544625447254482544925450254512545225453254542545525456254572545825459254602546125462254632546425465254662546725468254692547025471254722547325474254752547625477254782547925480254812548225483254842548525486254872548825489254902549125492254932549425495254962549725498254992550025501255022550325504255052550625507255082550925510255112551225513255142551525516255172551825519255202552125522255232552425525255262552725528255292553025531255322553325534255352553625537255382553925540255412554225543255442554525546255472554825549255502555125552255532555425555255562555725558255592556025561255622556325564255652556625567255682556925570255712557225573255742557525576255772557825579255802558125582255832558425585255862558725588255892559025591255922559325594255952559625597255982559925600256012560225603256042560525606256072560825609256102561125612256132561425615256162561725618256192562025621256222562325624256252562625627256282562925630256312563225633256342563525636256372563825639256402564125642256432564425645256462564725648256492565025651256522565325654256552565625657256582565925660256612566225663256642566525666256672566825669256702567125672256732567425675256762567725678256792568025681256822568325684256852568625687256882568925690256912569225693256942569525696256972569825699257002570125702257032570425705257062570725708257092571025711257122571325714257152571625717257182571925720257212572225723257242572525726257272572825729257302573125732257332573425735257362573725738257392574025741257422574325744257452574625747257482574925750257512575225753257542575525756257572575825759257602576125762257632576425765257662576725768257692577025771257722577325774257752577625777257782577925780257812578225783257842578525786257872578825789257902579125792257932579425795257962579725798257992580025801258022580325804258052580625807258082580925810258112581225813258142581525816258172581825819258202582125822258232582425825258262582725828258292583025831258322583325834258352583625837258382583925840258412584225843258442584525846258472584825849258502585125852258532585425855258562585725858258592586025861258622586325864258652586625867258682586925870258712587225873258742587525876258772587825879258802588125882258832588425885258862588725888258892589025891258922589325894258952589625897258982589925900259012590225903259042590525906259072590825909259102591125912259132591425915259162591725918259192592025921259222592325924259252592625927259282592925930259312593225933259342593525936259372593825939259402594125942259432594425945259462594725948259492595025951259522595325954259552595625957259582595925960259612596225963259642596525966259672596825969259702597125972259732597425975259762597725978259792598025981259822598325984259852598625987259882598925990259912599225993259942599525996259972599825999260002600126002260032600426005260062600726008260092601026011260122601326014260152601626017260182601926020260212602226023260242602526026260272602826029260302603126032260332603426035260362603726038260392604026041260422604326044260452604626047260482604926050260512605226053260542605526056260572605826059260602606126062260632606426065260662606726068260692607026071260722607326074260752607626077260782607926080260812608226083260842608526086260872608826089260902609126092260932609426095260962609726098260992610026101261022610326104261052610626107261082610926110261112611226113261142611526116261172611826119261202612126122261232612426125261262612726128261292613026131261322613326134261352613626137261382613926140261412614226143261442614526146261472614826149261502615126152261532615426155261562615726158261592616026161261622616326164261652616626167261682616926170261712617226173261742617526176261772617826179261802618126182261832618426185261862618726188261892619026191261922619326194261952619626197261982619926200262012620226203262042620526206262072620826209262102621126212262132621426215262162621726218262192622026221262222622326224262252622626227262282622926230262312623226233262342623526236262372623826239262402624126242262432624426245262462624726248262492625026251262522625326254262552625626257262582625926260262612626226263262642626526266262672626826269262702627126272262732627426275262762627726278262792628026281262822628326284262852628626287262882628926290262912629226293262942629526296262972629826299263002630126302263032630426305263062630726308263092631026311263122631326314263152631626317263182631926320263212632226323263242632526326263272632826329263302633126332263332633426335263362633726338263392634026341263422634326344263452634626347263482634926350263512635226353263542635526356263572635826359263602636126362263632636426365263662636726368263692637026371263722637326374263752637626377263782637926380263812638226383263842638526386263872638826389263902639126392263932639426395263962639726398263992640026401264022640326404264052640626407264082640926410264112641226413264142641526416264172641826419264202642126422264232642426425264262642726428264292643026431264322643326434264352643626437264382643926440264412644226443264442644526446264472644826449264502645126452264532645426455264562645726458264592646026461264622646326464264652646626467264682646926470264712647226473264742647526476264772647826479264802648126482264832648426485264862648726488264892649026491264922649326494264952649626497264982649926500265012650226503265042650526506265072650826509265102651126512265132651426515265162651726518265192652026521265222652326524265252652626527265282652926530265312653226533265342653526536265372653826539265402654126542265432654426545265462654726548265492655026551265522655326554265552655626557265582655926560265612656226563265642656526566265672656826569265702657126572265732657426575265762657726578265792658026581265822658326584265852658626587265882658926590265912659226593265942659526596265972659826599266002660126602266032660426605266062660726608266092661026611266122661326614266152661626617266182661926620266212662226623266242662526626266272662826629266302663126632266332663426635266362663726638266392664026641266422664326644266452664626647266482664926650266512665226653266542665526656266572665826659266602666126662266632666426665266662666726668266692667026671266722667326674266752667626677266782667926680266812668226683266842668526686266872668826689266902669126692266932669426695266962669726698266992670026701267022670326704267052670626707267082670926710267112671226713267142671526716267172671826719267202672126722267232672426725267262672726728267292673026731267322673326734267352673626737267382673926740267412674226743267442674526746267472674826749267502675126752267532675426755267562675726758267592676026761267622676326764267652676626767267682676926770267712677226773267742677526776267772677826779267802678126782267832678426785267862678726788267892679026791267922679326794267952679626797267982679926800268012680226803268042680526806268072680826809268102681126812268132681426815268162681726818268192682026821268222682326824268252682626827268282682926830268312683226833268342683526836268372683826839268402684126842268432684426845268462684726848268492685026851268522685326854268552685626857268582685926860268612686226863268642686526866268672686826869268702687126872268732687426875268762687726878268792688026881268822688326884268852688626887268882688926890268912689226893268942689526896268972689826899269002690126902269032690426905269062690726908269092691026911269122691326914269152691626917269182691926920269212692226923269242692526926269272692826929269302693126932269332693426935269362693726938269392694026941269422694326944269452694626947269482694926950269512695226953269542695526956269572695826959269602696126962269632696426965269662696726968269692697026971269722697326974269752697626977269782697926980269812698226983269842698526986269872698826989269902699126992269932699426995269962699726998269992700027001270022700327004270052700627007270082700927010270112701227013270142701527016270172701827019270202702127022270232702427025270262702727028270292703027031270322703327034270352703627037270382703927040270412704227043270442704527046270472704827049270502705127052270532705427055270562705727058270592706027061270622706327064270652706627067270682706927070270712707227073270742707527076270772707827079270802708127082270832708427085270862708727088270892709027091270922709327094270952709627097270982709927100271012710227103271042710527106271072710827109271102711127112271132711427115271162711727118271192712027121271222712327124271252712627127271282712927130271312713227133271342713527136271372713827139271402714127142271432714427145271462714727148271492715027151271522715327154271552715627157271582715927160271612716227163271642716527166271672716827169271702717127172271732717427175271762717727178271792718027181271822718327184271852718627187271882718927190271912719227193271942719527196271972719827199272002720127202272032720427205272062720727208272092721027211272122721327214272152721627217272182721927220272212722227223272242722527226272272722827229272302723127232272332723427235272362723727238272392724027241272422724327244272452724627247272482724927250272512725227253272542725527256272572725827259272602726127262272632726427265272662726727268272692727027271272722727327274272752727627277272782727927280272812728227283272842728527286272872728827289272902729127292272932729427295272962729727298272992730027301273022730327304273052730627307273082730927310273112731227313273142731527316273172731827319273202732127322273232732427325273262732727328273292733027331273322733327334273352733627337273382733927340273412734227343273442734527346273472734827349273502735127352273532735427355273562735727358273592736027361273622736327364273652736627367273682736927370273712737227373273742737527376273772737827379273802738127382273832738427385273862738727388273892739027391273922739327394273952739627397273982739927400274012740227403274042740527406274072740827409274102741127412274132741427415274162741727418274192742027421274222742327424274252742627427274282742927430274312743227433274342743527436274372743827439274402744127442274432744427445274462744727448274492745027451274522745327454274552745627457274582745927460274612746227463274642746527466274672746827469274702747127472274732747427475274762747727478274792748027481274822748327484274852748627487274882748927490274912749227493274942749527496274972749827499275002750127502275032750427505275062750727508275092751027511275122751327514275152751627517275182751927520275212752227523275242752527526275272752827529275302753127532275332753427535275362753727538275392754027541275422754327544275452754627547275482754927550275512755227553275542755527556275572755827559275602756127562275632756427565275662756727568275692757027571275722757327574275752757627577275782757927580275812758227583275842758527586275872758827589275902759127592275932759427595275962759727598275992760027601276022760327604276052760627607276082760927610276112761227613276142761527616276172761827619276202762127622276232762427625276262762727628276292763027631276322763327634276352763627637276382763927640276412764227643276442764527646276472764827649276502765127652276532765427655276562765727658276592766027661276622766327664276652766627667276682766927670276712767227673276742767527676276772767827679276802768127682276832768427685276862768727688276892769027691276922769327694276952769627697276982769927700277012770227703277042770527706277072770827709277102771127712277132771427715277162771727718277192772027721277222772327724277252772627727277282772927730277312773227733277342773527736277372773827739277402774127742277432774427745277462774727748277492775027751277522775327754277552775627757277582775927760277612776227763277642776527766277672776827769277702777127772277732777427775277762777727778277792778027781277822778327784277852778627787277882778927790277912779227793277942779527796277972779827799278002780127802278032780427805278062780727808278092781027811278122781327814278152781627817278182781927820278212782227823278242782527826278272782827829278302783127832278332783427835278362783727838278392784027841278422784327844278452784627847278482784927850278512785227853278542785527856278572785827859278602786127862278632786427865278662786727868278692787027871278722787327874278752787627877278782787927880278812788227883278842788527886278872788827889278902789127892278932789427895278962789727898278992790027901279022790327904279052790627907279082790927910279112791227913279142791527916279172791827919279202792127922279232792427925279262792727928279292793027931279322793327934279352793627937279382793927940279412794227943279442794527946279472794827949279502795127952279532795427955279562795727958279592796027961279622796327964279652796627967279682796927970279712797227973279742797527976279772797827979279802798127982279832798427985279862798727988279892799027991279922799327994279952799627997279982799928000280012800228003280042800528006280072800828009280102801128012280132801428015280162801728018280192802028021280222802328024280252802628027280282802928030280312803228033280342803528036280372803828039280402804128042280432804428045280462804728048280492805028051280522805328054280552805628057280582805928060280612806228063280642806528066280672806828069280702807128072280732807428075280762807728078280792808028081280822808328084280852808628087280882808928090280912809228093280942809528096280972809828099281002810128102281032810428105281062810728108281092811028111281122811328114281152811628117281182811928120281212812228123281242812528126281272812828129281302813128132281332813428135281362813728138281392814028141281422814328144281452814628147281482814928150281512815228153281542815528156281572815828159281602816128162281632816428165281662816728168281692817028171281722817328174281752817628177281782817928180281812818228183281842818528186281872818828189281902819128192281932819428195281962819728198281992820028201282022820328204282052820628207282082820928210282112821228213282142821528216282172821828219282202822128222282232822428225282262822728228282292823028231282322823328234282352823628237282382823928240282412824228243282442824528246282472824828249282502825128252282532825428255282562825728258282592826028261282622826328264282652826628267282682826928270282712827228273282742827528276282772827828279282802828128282282832828428285282862828728288282892829028291282922829328294282952829628297282982829928300283012830228303283042830528306283072830828309283102831128312283132831428315283162831728318283192832028321283222832328324283252832628327283282832928330283312833228333283342833528336283372833828339283402834128342283432834428345283462834728348283492835028351283522835328354283552835628357283582835928360283612836228363283642836528366283672836828369283702837128372283732837428375283762837728378283792838028381283822838328384283852838628387283882838928390283912839228393283942839528396283972839828399284002840128402284032840428405284062840728408284092841028411284122841328414284152841628417284182841928420284212842228423284242842528426284272842828429284302843128432284332843428435284362843728438284392844028441284422844328444284452844628447284482844928450284512845228453284542845528456284572845828459284602846128462284632846428465284662846728468284692847028471284722847328474284752847628477284782847928480284812848228483284842848528486284872848828489284902849128492284932849428495284962849728498284992850028501285022850328504285052850628507285082850928510285112851228513285142851528516285172851828519285202852128522285232852428525285262852728528285292853028531285322853328534285352853628537285382853928540285412854228543285442854528546285472854828549285502855128552285532855428555285562855728558285592856028561285622856328564285652856628567285682856928570285712857228573285742857528576285772857828579285802858128582285832858428585285862858728588285892859028591285922859328594285952859628597285982859928600286012860228603286042860528606286072860828609286102861128612286132861428615286162861728618286192862028621286222862328624286252862628627286282862928630286312863228633286342863528636286372863828639286402864128642286432864428645286462864728648286492865028651286522865328654286552865628657286582865928660286612866228663286642866528666286672866828669286702867128672286732867428675286762867728678286792868028681286822868328684286852868628687286882868928690286912869228693286942869528696286972869828699287002870128702287032870428705287062870728708287092871028711287122871328714287152871628717287182871928720287212872228723287242872528726287272872828729287302873128732287332873428735287362873728738287392874028741287422874328744287452874628747287482874928750287512875228753287542875528756287572875828759287602876128762287632876428765287662876728768287692877028771287722877328774287752877628777287782877928780287812878228783287842878528786287872878828789287902879128792287932879428795287962879728798287992880028801288022880328804288052880628807288082880928810288112881228813288142881528816288172881828819288202882128822288232882428825288262882728828288292883028831288322883328834288352883628837288382883928840288412884228843288442884528846288472884828849288502885128852288532885428855288562885728858288592886028861288622886328864288652886628867288682886928870288712887228873288742887528876288772887828879288802888128882288832888428885288862888728888288892889028891288922889328894288952889628897288982889928900289012890228903289042890528906289072890828909289102891128912289132891428915289162891728918289192892028921289222892328924289252892628927289282892928930289312893228933289342893528936289372893828939289402894128942289432894428945289462894728948289492895028951289522895328954289552895628957289582895928960289612896228963289642896528966289672896828969289702897128972289732897428975289762897728978289792898028981289822898328984289852898628987289882898928990289912899228993289942899528996289972899828999290002900129002290032900429005290062900729008290092901029011290122901329014290152901629017290182901929020290212902229023290242902529026290272902829029290302903129032290332903429035290362903729038290392904029041290422904329044290452904629047290482904929050290512905229053290542905529056290572905829059290602906129062290632906429065290662906729068290692907029071290722907329074290752907629077290782907929080290812908229083290842908529086290872908829089290902909129092290932909429095290962909729098290992910029101291022910329104291052910629107291082910929110291112911229113291142911529116291172911829119291202912129122291232912429125291262912729128291292913029131291322913329134291352913629137291382913929140291412914229143291442914529146291472914829149291502915129152291532915429155291562915729158291592916029161291622916329164291652916629167291682916929170291712917229173291742917529176291772917829179291802918129182291832918429185291862918729188291892919029191291922919329194291952919629197291982919929200292012920229203292042920529206292072920829209292102921129212292132921429215292162921729218292192922029221292222922329224292252922629227292282922929230292312923229233292342923529236292372923829239292402924129242292432924429245292462924729248292492925029251292522925329254292552925629257292582925929260292612926229263292642926529266292672926829269292702927129272292732927429275292762927729278292792928029281292822928329284292852928629287292882928929290292912929229293292942929529296292972929829299293002930129302293032930429305293062930729308293092931029311293122931329314293152931629317293182931929320293212932229323293242932529326293272932829329293302933129332293332933429335293362933729338293392934029341293422934329344293452934629347293482934929350293512935229353293542935529356293572935829359293602936129362293632936429365293662936729368293692937029371293722937329374293752937629377293782937929380293812938229383293842938529386293872938829389293902939129392293932939429395293962939729398293992940029401294022940329404294052940629407294082940929410294112941229413294142941529416294172941829419294202942129422294232942429425294262942729428294292943029431294322943329434294352943629437294382943929440294412944229443294442944529446294472944829449294502945129452294532945429455294562945729458294592946029461294622946329464294652946629467294682946929470294712947229473294742947529476294772947829479294802948129482294832948429485294862948729488294892949029491294922949329494294952949629497294982949929500295012950229503295042950529506295072950829509295102951129512295132951429515295162951729518295192952029521295222952329524295252952629527295282952929530295312953229533295342953529536295372953829539295402954129542295432954429545295462954729548295492955029551295522955329554295552955629557295582955929560295612956229563295642956529566295672956829569295702957129572295732957429575295762957729578295792958029581295822958329584295852958629587295882958929590295912959229593295942959529596295972959829599296002960129602296032960429605296062960729608296092961029611296122961329614296152961629617296182961929620296212962229623296242962529626296272962829629296302963129632296332963429635296362963729638296392964029641296422964329644296452964629647296482964929650296512965229653296542965529656296572965829659296602966129662296632966429665296662966729668296692967029671296722967329674296752967629677296782967929680296812968229683296842968529686296872968829689296902969129692296932969429695296962969729698296992970029701297022970329704297052970629707297082970929710297112971229713297142971529716297172971829719297202972129722297232972429725297262972729728297292973029731297322973329734297352973629737297382973929740297412974229743297442974529746297472974829749297502975129752297532975429755297562975729758297592976029761297622976329764297652976629767297682976929770297712977229773297742977529776297772977829779297802978129782297832978429785297862978729788297892979029791297922979329794297952979629797297982979929800298012980229803298042980529806298072980829809298102981129812298132981429815298162981729818298192982029821298222982329824298252982629827298282982929830298312983229833298342983529836298372983829839298402984129842298432984429845298462984729848298492985029851298522985329854298552985629857298582985929860298612986229863298642986529866298672986829869298702987129872298732987429875298762987729878298792988029881298822988329884298852988629887298882988929890298912989229893298942989529896298972989829899299002990129902299032990429905299062990729908299092991029911299122991329914299152991629917299182991929920299212992229923299242992529926299272992829929299302993129932299332993429935299362993729938299392994029941299422994329944299452994629947299482994929950299512995229953299542995529956299572995829959299602996129962299632996429965299662996729968299692997029971299722997329974299752997629977299782997929980299812998229983299842998529986299872998829989299902999129992299932999429995299962999729998299993000030001300023000330004300053000630007300083000930010300113001230013300143001530016300173001830019300203002130022300233002430025300263002730028300293003030031300323003330034300353003630037300383003930040300413004230043300443004530046300473004830049300503005130052300533005430055300563005730058300593006030061300623006330064300653006630067300683006930070300713007230073300743007530076300773007830079300803008130082300833008430085300863008730088300893009030091300923009330094300953009630097300983009930100301013010230103301043010530106301073010830109301103011130112301133011430115301163011730118301193012030121301223012330124301253012630127301283012930130301313013230133301343013530136301373013830139301403014130142301433014430145301463014730148301493015030151301523015330154301553015630157301583015930160301613016230163301643016530166301673016830169301703017130172301733017430175301763017730178301793018030181301823018330184301853018630187301883018930190301913019230193301943019530196301973019830199302003020130202302033020430205302063020730208302093021030211302123021330214302153021630217302183021930220302213022230223302243022530226302273022830229302303023130232302333023430235302363023730238302393024030241302423024330244302453024630247302483024930250302513025230253302543025530256302573025830259302603026130262302633026430265302663026730268302693027030271302723027330274302753027630277302783027930280302813028230283302843028530286302873028830289302903029130292302933029430295302963029730298302993030030301303023030330304303053030630307303083030930310303113031230313303143031530316303173031830319303203032130322303233032430325303263032730328303293033030331303323033330334303353033630337303383033930340303413034230343303443034530346303473034830349303503035130352303533035430355303563035730358303593036030361303623036330364303653036630367303683036930370303713037230373303743037530376303773037830379303803038130382303833038430385303863038730388303893039030391303923039330394303953039630397303983039930400304013040230403304043040530406304073040830409304103041130412304133041430415304163041730418304193042030421304223042330424304253042630427304283042930430304313043230433304343043530436304373043830439304403044130442304433044430445304463044730448304493045030451304523045330454304553045630457304583045930460304613046230463304643046530466304673046830469304703047130472304733047430475304763047730478304793048030481304823048330484304853048630487304883048930490304913049230493304943049530496304973049830499305003050130502305033050430505305063050730508305093051030511305123051330514305153051630517305183051930520305213052230523305243052530526305273052830529305303053130532305333053430535305363053730538305393054030541305423054330544305453054630547305483054930550305513055230553305543055530556305573055830559305603056130562305633056430565305663056730568305693057030571305723057330574305753057630577305783057930580305813058230583305843058530586305873058830589305903059130592305933059430595305963059730598305993060030601306023060330604306053060630607306083060930610306113061230613306143061530616306173061830619306203062130622306233062430625306263062730628306293063030631306323063330634306353063630637306383063930640306413064230643306443064530646306473064830649306503065130652306533065430655306563065730658306593066030661306623066330664306653066630667306683066930670306713067230673306743067530676306773067830679306803068130682306833068430685306863068730688306893069030691306923069330694306953069630697306983069930700307013070230703307043070530706307073070830709307103071130712307133071430715307163071730718307193072030721307223072330724307253072630727307283072930730307313073230733307343073530736307373073830739307403074130742307433074430745307463074730748307493075030751307523075330754307553075630757307583075930760307613076230763307643076530766307673076830769307703077130772307733077430775307763077730778307793078030781307823078330784307853078630787307883078930790307913079230793307943079530796307973079830799308003080130802308033080430805308063080730808308093081030811308123081330814308153081630817308183081930820308213082230823308243082530826308273082830829308303083130832308333083430835308363083730838308393084030841308423084330844308453084630847308483084930850308513085230853308543085530856308573085830859308603086130862308633086430865308663086730868308693087030871308723087330874308753087630877308783087930880308813088230883308843088530886308873088830889308903089130892308933089430895308963089730898308993090030901309023090330904309053090630907309083090930910309113091230913309143091530916309173091830919309203092130922309233092430925309263092730928309293093030931309323093330934309353093630937309383093930940309413094230943309443094530946309473094830949309503095130952309533095430955309563095730958309593096030961309623096330964309653096630967309683096930970309713097230973309743097530976309773097830979309803098130982309833098430985309863098730988309893099030991309923099330994309953099630997309983099931000310013100231003310043100531006310073100831009310103101131012310133101431015310163101731018310193102031021310223102331024310253102631027310283102931030310313103231033310343103531036310373103831039310403104131042310433104431045310463104731048310493105031051310523105331054310553105631057310583105931060310613106231063310643106531066310673106831069310703107131072310733107431075310763107731078310793108031081310823108331084310853108631087310883108931090310913109231093310943109531096310973109831099311003110131102311033110431105311063110731108311093111031111311123111331114311153111631117311183111931120311213112231123311243112531126311273112831129311303113131132311333113431135311363113731138311393114031141311423114331144311453114631147311483114931150311513115231153311543115531156311573115831159311603116131162311633116431165311663116731168311693117031171311723117331174311753117631177311783117931180311813118231183311843118531186311873118831189311903119131192311933119431195311963119731198311993120031201312023120331204312053120631207312083120931210312113121231213312143121531216312173121831219312203122131222312233122431225312263122731228312293123031231312323123331234312353123631237312383123931240312413124231243312443124531246312473124831249312503125131252312533125431255312563125731258312593126031261312623126331264312653126631267312683126931270312713127231273312743127531276312773127831279312803128131282312833128431285312863128731288312893129031291312923129331294312953129631297312983129931300313013130231303313043130531306313073130831309313103131131312313133131431315313163131731318313193132031321313223132331324313253132631327313283132931330313313133231333313343133531336313373133831339313403134131342313433134431345313463134731348313493135031351313523135331354313553135631357313583135931360313613136231363313643136531366313673136831369313703137131372313733137431375313763137731378313793138031381313823138331384313853138631387313883138931390313913139231393313943139531396313973139831399314003140131402314033140431405314063140731408314093141031411314123141331414314153141631417314183141931420314213142231423314243142531426314273142831429314303143131432314333143431435314363143731438314393144031441314423144331444314453144631447314483144931450314513145231453314543145531456314573145831459314603146131462314633146431465314663146731468314693147031471314723147331474314753147631477314783147931480314813148231483314843148531486314873148831489314903149131492314933149431495314963149731498314993150031501315023150331504315053150631507315083150931510315113151231513315143151531516315173151831519315203152131522315233152431525315263152731528315293153031531315323153331534315353153631537315383153931540315413154231543315443154531546315473154831549315503155131552315533155431555315563155731558315593156031561315623156331564315653156631567315683156931570315713157231573315743157531576315773157831579315803158131582315833158431585315863158731588315893159031591315923159331594315953159631597315983159931600316013160231603316043160531606316073160831609316103161131612316133161431615316163161731618316193162031621316223162331624316253162631627316283162931630316313163231633316343163531636316373163831639316403164131642316433164431645316463164731648316493165031651316523165331654316553165631657316583165931660316613166231663316643166531666316673166831669316703167131672316733167431675316763167731678316793168031681316823168331684316853168631687316883168931690316913169231693316943169531696316973169831699317003170131702317033170431705317063170731708317093171031711317123171331714317153171631717317183171931720317213172231723317243172531726317273172831729317303173131732317333173431735317363173731738317393174031741317423174331744317453174631747317483174931750317513175231753317543175531756317573175831759317603176131762317633176431765317663176731768317693177031771317723177331774317753177631777317783177931780317813178231783317843178531786317873178831789317903179131792317933179431795317963179731798317993180031801318023180331804318053180631807318083180931810318113181231813318143181531816318173181831819318203182131822318233182431825318263182731828318293183031831318323183331834318353183631837318383183931840318413184231843318443184531846318473184831849318503185131852318533185431855318563185731858318593186031861318623186331864318653186631867318683186931870318713187231873318743187531876318773187831879318803188131882318833188431885318863188731888318893189031891318923189331894318953189631897318983189931900319013190231903319043190531906319073190831909319103191131912319133191431915319163191731918319193192031921319223192331924319253192631927319283192931930319313193231933319343193531936319373193831939319403194131942319433194431945319463194731948319493195031951319523195331954319553195631957319583195931960319613196231963319643196531966319673196831969319703197131972319733197431975319763197731978319793198031981319823198331984319853198631987319883198931990319913199231993319943199531996319973199831999320003200132002320033200432005320063200732008320093201032011320123201332014320153201632017320183201932020320213202232023320243202532026320273202832029320303203132032320333203432035320363203732038320393204032041320423204332044320453204632047320483204932050320513205232053320543205532056320573205832059320603206132062320633206432065320663206732068320693207032071320723207332074320753207632077320783207932080320813208232083320843208532086320873208832089320903209132092320933209432095320963209732098320993210032101321023210332104321053210632107321083210932110321113211232113321143211532116321173211832119321203212132122321233212432125321263212732128321293213032131321323213332134321353213632137321383213932140321413214232143321443214532146321473214832149321503215132152321533215432155321563215732158321593216032161321623216332164321653216632167321683216932170321713217232173321743217532176321773217832179321803218132182321833218432185321863218732188321893219032191321923219332194321953219632197321983219932200322013220232203322043220532206322073220832209322103221132212322133221432215322163221732218322193222032221322223222332224322253222632227322283222932230322313223232233322343223532236322373223832239322403224132242322433224432245322463224732248322493225032251322523225332254322553225632257322583225932260322613226232263322643226532266322673226832269322703227132272322733227432275322763227732278322793228032281322823228332284322853228632287322883228932290322913229232293322943229532296322973229832299323003230132302323033230432305323063230732308323093231032311323123231332314323153231632317323183231932320323213232232323323243232532326323273232832329323303233132332323333233432335323363233732338323393234032341323423234332344323453234632347323483234932350323513235232353323543235532356323573235832359323603236132362323633236432365323663236732368323693237032371323723237332374323753237632377323783237932380323813238232383323843238532386323873238832389323903239132392323933239432395323963239732398323993240032401324023240332404324053240632407324083240932410324113241232413324143241532416324173241832419324203242132422324233242432425324263242732428324293243032431324323243332434324353243632437324383243932440324413244232443324443244532446324473244832449324503245132452324533245432455324563245732458324593246032461324623246332464324653246632467324683246932470324713247232473324743247532476324773247832479324803248132482324833248432485324863248732488324893249032491324923249332494324953249632497324983249932500325013250232503325043250532506325073250832509325103251132512325133251432515325163251732518325193252032521325223252332524325253252632527325283252932530325313253232533325343253532536325373253832539325403254132542325433254432545325463254732548325493255032551325523255332554325553255632557325583255932560325613256232563325643256532566325673256832569325703257132572325733257432575325763257732578325793258032581325823258332584325853258632587325883258932590325913259232593325943259532596325973259832599326003260132602326033260432605326063260732608326093261032611326123261332614326153261632617326183261932620326213262232623326243262532626326273262832629326303263132632326333263432635326363263732638326393264032641326423264332644326453264632647326483264932650326513265232653326543265532656326573265832659326603266132662326633266432665326663266732668326693267032671326723267332674326753267632677326783267932680326813268232683326843268532686326873268832689326903269132692326933269432695326963269732698326993270032701327023270332704327053270632707327083270932710327113271232713327143271532716327173271832719327203272132722327233272432725327263272732728327293273032731327323273332734327353273632737327383273932740327413274232743327443274532746327473274832749327503275132752327533275432755327563275732758327593276032761327623276332764327653276632767327683276932770327713277232773327743277532776327773277832779327803278132782327833278432785327863278732788327893279032791327923279332794327953279632797327983279932800328013280232803328043280532806328073280832809328103281132812328133281432815328163281732818328193282032821328223282332824328253282632827328283282932830328313283232833328343283532836328373283832839328403284132842328433284432845328463284732848328493285032851328523285332854328553285632857328583285932860328613286232863328643286532866328673286832869328703287132872328733287432875328763287732878328793288032881328823288332884328853288632887328883288932890328913289232893328943289532896328973289832899329003290132902329033290432905329063290732908329093291032911329123291332914329153291632917329183291932920329213292232923329243292532926329273292832929329303293132932329333293432935329363293732938329393294032941329423294332944329453294632947329483294932950329513295232953329543295532956329573295832959329603296132962329633296432965329663296732968329693297032971329723297332974329753297632977329783297932980329813298232983329843298532986329873298832989329903299132992329933299432995329963299732998329993300033001330023300333004330053300633007330083300933010330113301233013330143301533016330173301833019330203302133022330233302433025330263302733028330293303033031330323303333034330353303633037330383303933040330413304233043330443304533046330473304833049330503305133052330533305433055330563305733058330593306033061330623306333064330653306633067330683306933070330713307233073330743307533076330773307833079330803308133082330833308433085330863308733088330893309033091330923309333094330953309633097330983309933100331013310233103331043310533106331073310833109331103311133112331133311433115331163311733118331193312033121331223312333124331253312633127331283312933130331313313233133331343313533136331373313833139331403314133142331433314433145331463314733148331493315033151331523315333154331553315633157331583315933160331613316233163331643316533166331673316833169331703317133172331733317433175331763317733178331793318033181331823318333184331853318633187331883318933190331913319233193331943319533196331973319833199332003320133202332033320433205332063320733208332093321033211332123321333214332153321633217332183321933220332213322233223332243322533226332273322833229332303323133232332333323433235332363323733238332393324033241332423324333244332453324633247332483324933250332513325233253332543325533256332573325833259332603326133262332633326433265332663326733268332693327033271332723327333274332753327633277332783327933280332813328233283332843328533286332873328833289332903329133292332933329433295332963329733298332993330033301333023330333304333053330633307333083330933310333113331233313333143331533316333173331833319333203332133322333233332433325333263332733328333293333033331333323333333334333353333633337333383333933340333413334233343333443334533346333473334833349333503335133352333533335433355333563335733358333593336033361333623336333364333653336633367333683336933370333713337233373333743337533376333773337833379333803338133382333833338433385333863338733388333893339033391333923339333394333953339633397333983339933400334013340233403334043340533406334073340833409334103341133412334133341433415334163341733418334193342033421334223342333424334253342633427334283342933430334313343233433334343343533436334373343833439334403344133442334433344433445334463344733448334493345033451334523345333454334553345633457334583345933460334613346233463334643346533466334673346833469334703347133472334733347433475334763347733478334793348033481334823348333484334853348633487334883348933490334913349233493334943349533496334973349833499335003350133502335033350433505335063350733508335093351033511335123351333514335153351633517335183351933520335213352233523335243352533526335273352833529335303353133532335333353433535335363353733538335393354033541335423354333544335453354633547335483354933550335513355233553335543355533556335573355833559335603356133562335633356433565335663356733568335693357033571335723357333574335753357633577335783357933580335813358233583335843358533586335873358833589335903359133592335933359433595335963359733598335993360033601336023360333604336053360633607336083360933610336113361233613336143361533616336173361833619336203362133622336233362433625336263362733628336293363033631336323363333634336353363633637336383363933640336413364233643336443364533646336473364833649336503365133652336533365433655336563365733658336593366033661336623366333664336653366633667336683366933670336713367233673336743367533676336773367833679336803368133682336833368433685336863368733688336893369033691336923369333694336953369633697336983369933700337013370233703337043370533706337073370833709337103371133712337133371433715337163371733718337193372033721337223372333724337253372633727337283372933730337313373233733337343373533736337373373833739337403374133742337433374433745337463374733748337493375033751337523375333754337553375633757337583375933760337613376233763337643376533766337673376833769337703377133772337733377433775337763377733778337793378033781337823378333784337853378633787337883378933790337913379233793337943379533796337973379833799338003380133802338033380433805338063380733808338093381033811338123381333814338153381633817338183381933820338213382233823338243382533826338273382833829338303383133832338333383433835338363383733838338393384033841338423384333844338453384633847338483384933850338513385233853338543385533856338573385833859338603386133862338633386433865338663386733868338693387033871338723387333874338753387633877338783387933880338813388233883338843388533886338873388833889338903389133892338933389433895338963389733898338993390033901339023390333904339053390633907339083390933910339113391233913339143391533916339173391833919339203392133922339233392433925339263392733928339293393033931339323393333934339353393633937339383393933940339413394233943339443394533946339473394833949339503395133952339533395433955339563395733958339593396033961339623396333964339653396633967339683396933970339713397233973339743397533976339773397833979339803398133982339833398433985339863398733988339893399033991339923399333994339953399633997339983399934000340013400234003340043400534006340073400834009340103401134012340133401434015340163401734018340193402034021340223402334024340253402634027340283402934030340313403234033340343403534036340373403834039340403404134042340433404434045340463404734048340493405034051340523405334054340553405634057340583405934060340613406234063340643406534066340673406834069340703407134072340733407434075340763407734078340793408034081340823408334084340853408634087340883408934090340913409234093340943409534096340973409834099341003410134102341033410434105341063410734108341093411034111341123411334114341153411634117341183411934120341213412234123341243412534126341273412834129341303413134132341333413434135341363413734138341393414034141341423414334144341453414634147341483414934150341513415234153341543415534156341573415834159341603416134162341633416434165341663416734168341693417034171341723417334174341753417634177341783417934180341813418234183341843418534186341873418834189341903419134192341933419434195341963419734198341993420034201342023420334204342053420634207342083420934210342113421234213342143421534216342173421834219342203422134222342233422434225342263422734228342293423034231342323423334234342353423634237342383423934240342413424234243342443424534246342473424834249342503425134252342533425434255342563425734258342593426034261342623426334264342653426634267342683426934270342713427234273342743427534276342773427834279342803428134282342833428434285342863428734288342893429034291342923429334294342953429634297342983429934300343013430234303343043430534306343073430834309343103431134312343133431434315343163431734318343193432034321343223432334324343253432634327343283432934330343313433234333343343433534336343373433834339343403434134342343433434434345343463434734348343493435034351343523435334354343553435634357343583435934360343613436234363343643436534366343673436834369343703437134372343733437434375343763437734378343793438034381343823438334384343853438634387343883438934390343913439234393343943439534396343973439834399344003440134402344033440434405344063440734408344093441034411344123441334414344153441634417344183441934420344213442234423344243442534426344273442834429344303443134432344333443434435344363443734438344393444034441344423444334444344453444634447344483444934450344513445234453344543445534456344573445834459344603446134462344633446434465344663446734468344693447034471344723447334474344753447634477344783447934480344813448234483344843448534486344873448834489344903449134492344933449434495344963449734498344993450034501345023450334504345053450634507345083450934510345113451234513345143451534516345173451834519345203452134522345233452434525345263452734528345293453034531345323453334534345353453634537345383453934540345413454234543345443454534546345473454834549345503455134552345533455434555345563455734558345593456034561345623456334564345653456634567345683456934570345713457234573345743457534576345773457834579345803458134582345833458434585345863458734588345893459034591345923459334594345953459634597345983459934600346013460234603346043460534606346073460834609346103461134612346133461434615346163461734618346193462034621346223462334624346253462634627346283462934630346313463234633346343463534636346373463834639346403464134642346433464434645346463464734648346493465034651346523465334654346553465634657346583465934660346613466234663346643466534666346673466834669346703467134672346733467434675346763467734678346793468034681346823468334684346853468634687346883468934690346913469234693346943469534696346973469834699347003470134702347033470434705347063470734708347093471034711347123471334714347153471634717347183471934720347213472234723347243472534726347273472834729347303473134732347333473434735347363473734738347393474034741347423474334744347453474634747347483474934750347513475234753347543475534756347573475834759347603476134762347633476434765347663476734768347693477034771347723477334774347753477634777347783477934780347813478234783347843478534786347873478834789347903479134792347933479434795347963479734798347993480034801348023480334804348053480634807348083480934810348113481234813348143481534816348173481834819348203482134822348233482434825348263482734828348293483034831348323483334834348353483634837348383483934840348413484234843348443484534846348473484834849348503485134852348533485434855348563485734858348593486034861348623486334864348653486634867348683486934870348713487234873348743487534876348773487834879348803488134882348833488434885348863488734888348893489034891348923489334894348953489634897348983489934900349013490234903349043490534906349073490834909349103491134912349133491434915349163491734918349193492034921349223492334924349253492634927349283492934930349313493234933349343493534936349373493834939349403494134942349433494434945349463494734948349493495034951349523495334954349553495634957349583495934960349613496234963349643496534966349673496834969349703497134972349733497434975349763497734978349793498034981349823498334984349853498634987349883498934990349913499234993349943499534996349973499834999350003500135002350033500435005350063500735008350093501035011350123501335014350153501635017350183501935020350213502235023350243502535026350273502835029350303503135032350333503435035350363503735038350393504035041350423504335044350453504635047350483504935050350513505235053350543505535056350573505835059350603506135062350633506435065350663506735068350693507035071350723507335074350753507635077350783507935080350813508235083350843508535086350873508835089350903509135092350933509435095350963509735098350993510035101351023510335104351053510635107351083510935110351113511235113351143511535116351173511835119351203512135122351233512435125351263512735128351293513035131351323513335134351353513635137351383513935140351413514235143351443514535146351473514835149351503515135152351533515435155351563515735158351593516035161351623516335164351653516635167351683516935170351713517235173351743517535176351773517835179351803518135182351833518435185351863518735188351893519035191351923519335194351953519635197351983519935200352013520235203352043520535206352073520835209352103521135212352133521435215352163521735218352193522035221352223522335224352253522635227352283522935230352313523235233352343523535236352373523835239352403524135242352433524435245352463524735248352493525035251352523525335254352553525635257352583525935260352613526235263352643526535266352673526835269352703527135272352733527435275352763527735278352793528035281352823528335284352853528635287352883528935290352913529235293352943529535296352973529835299353003530135302353033530435305353063530735308353093531035311353123531335314353153531635317353183531935320353213532235323353243532535326353273532835329353303533135332353333533435335353363533735338353393534035341353423534335344353453534635347353483534935350353513535235353353543535535356353573535835359353603536135362353633536435365353663536735368353693537035371353723537335374353753537635377353783537935380353813538235383353843538535386353873538835389353903539135392353933539435395353963539735398353993540035401354023540335404354053540635407354083540935410354113541235413354143541535416354173541835419354203542135422354233542435425354263542735428354293543035431354323543335434354353543635437354383543935440354413544235443354443544535446354473544835449354503545135452354533545435455354563545735458354593546035461354623546335464354653546635467354683546935470354713547235473354743547535476354773547835479354803548135482354833548435485354863548735488354893549035491354923549335494354953549635497354983549935500355013550235503355043550535506355073550835509355103551135512355133551435515355163551735518355193552035521355223552335524355253552635527355283552935530355313553235533355343553535536355373553835539355403554135542355433554435545355463554735548355493555035551355523555335554355553555635557355583555935560355613556235563355643556535566355673556835569355703557135572355733557435575355763557735578355793558035581355823558335584355853558635587355883558935590355913559235593355943559535596355973559835599356003560135602356033560435605356063560735608356093561035611356123561335614356153561635617356183561935620356213562235623356243562535626356273562835629356303563135632356333563435635356363563735638356393564035641356423564335644356453564635647356483564935650356513565235653356543565535656356573565835659356603566135662356633566435665356663566735668356693567035671356723567335674356753567635677356783567935680356813568235683356843568535686356873568835689356903569135692356933569435695356963569735698356993570035701357023570335704357053570635707357083570935710357113571235713357143571535716357173571835719357203572135722357233572435725357263572735728357293573035731357323573335734357353573635737357383573935740357413574235743357443574535746357473574835749357503575135752357533575435755357563575735758357593576035761357623576335764357653576635767357683576935770357713577235773357743577535776357773577835779357803578135782357833578435785357863578735788357893579035791357923579335794357953579635797357983579935800358013580235803358043580535806358073580835809358103581135812358133581435815358163581735818358193582035821358223582335824358253582635827358283582935830358313583235833358343583535836358373583835839358403584135842358433584435845358463584735848358493585035851358523585335854358553585635857358583585935860358613586235863358643586535866358673586835869358703587135872358733587435875358763587735878358793588035881358823588335884358853588635887358883588935890358913589235893358943589535896358973589835899359003590135902359033590435905359063590735908359093591035911359123591335914359153591635917359183591935920359213592235923359243592535926359273592835929359303593135932359333593435935359363593735938359393594035941359423594335944359453594635947359483594935950359513595235953359543595535956359573595835959359603596135962359633596435965359663596735968359693597035971359723597335974359753597635977359783597935980359813598235983359843598535986359873598835989359903599135992359933599435995359963599735998359993600036001360023600336004360053600636007360083600936010360113601236013360143601536016360173601836019360203602136022360233602436025360263602736028360293603036031360323603336034360353603636037360383603936040360413604236043360443604536046360473604836049360503605136052360533605436055360563605736058360593606036061360623606336064360653606636067360683606936070360713607236073360743607536076360773607836079360803608136082360833608436085360863608736088360893609036091360923609336094360953609636097360983609936100361013610236103361043610536106361073610836109361103611136112361133611436115361163611736118361193612036121361223612336124361253612636127361283612936130361313613236133361343613536136361373613836139361403614136142361433614436145361463614736148361493615036151361523615336154361553615636157361583615936160361613616236163361643616536166361673616836169361703617136172361733617436175361763617736178361793618036181361823618336184361853618636187361883618936190361913619236193361943619536196361973619836199362003620136202362033620436205362063620736208362093621036211362123621336214362153621636217362183621936220362213622236223362243622536226362273622836229362303623136232362333623436235362363623736238362393624036241362423624336244362453624636247362483624936250362513625236253362543625536256362573625836259362603626136262362633626436265362663626736268362693627036271362723627336274362753627636277362783627936280362813628236283362843628536286362873628836289362903629136292362933629436295362963629736298362993630036301363023630336304363053630636307363083630936310363113631236313363143631536316363173631836319363203632136322363233632436325363263632736328363293633036331363323633336334363353633636337363383633936340363413634236343363443634536346363473634836349363503635136352363533635436355363563635736358363593636036361363623636336364363653636636367363683636936370363713637236373363743637536376363773637836379363803638136382363833638436385363863638736388363893639036391363923639336394363953639636397363983639936400364013640236403364043640536406364073640836409364103641136412364133641436415364163641736418364193642036421364223642336424364253642636427364283642936430364313643236433364343643536436364373643836439364403644136442364433644436445364463644736448364493645036451364523645336454364553645636457364583645936460364613646236463364643646536466364673646836469364703647136472364733647436475364763647736478364793648036481364823648336484364853648636487364883648936490364913649236493364943649536496364973649836499365003650136502365033650436505365063650736508365093651036511365123651336514365153651636517365183651936520365213652236523365243652536526365273652836529365303653136532365333653436535365363653736538365393654036541365423654336544365453654636547365483654936550365513655236553365543655536556365573655836559365603656136562365633656436565365663656736568365693657036571365723657336574365753657636577365783657936580365813658236583365843658536586365873658836589365903659136592365933659436595365963659736598365993660036601366023660336604366053660636607366083660936610366113661236613366143661536616366173661836619366203662136622366233662436625366263662736628366293663036631366323663336634366353663636637366383663936640366413664236643366443664536646366473664836649366503665136652366533665436655366563665736658366593666036661366623666336664366653666636667366683666936670366713667236673366743667536676366773667836679366803668136682366833668436685366863668736688366893669036691366923669336694366953669636697366983669936700367013670236703367043670536706367073670836709367103671136712367133671436715367163671736718367193672036721367223672336724367253672636727367283672936730367313673236733367343673536736367373673836739367403674136742367433674436745367463674736748367493675036751367523675336754367553675636757367583675936760367613676236763367643676536766367673676836769367703677136772367733677436775367763677736778367793678036781367823678336784367853678636787367883678936790367913679236793367943679536796367973679836799368003680136802368033680436805368063680736808368093681036811368123681336814368153681636817368183681936820368213682236823368243682536826368273682836829368303683136832368333683436835368363683736838368393684036841368423684336844368453684636847368483684936850368513685236853368543685536856368573685836859368603686136862368633686436865368663686736868368693687036871368723687336874368753687636877368783687936880368813688236883368843688536886368873688836889368903689136892368933689436895368963689736898368993690036901369023690336904369053690636907369083690936910369113691236913369143691536916369173691836919369203692136922369233692436925369263692736928369293693036931369323693336934369353693636937369383693936940369413694236943369443694536946369473694836949369503695136952369533695436955369563695736958369593696036961369623696336964369653696636967369683696936970369713697236973369743697536976369773697836979369803698136982369833698436985369863698736988369893699036991369923699336994369953699636997369983699937000370013700237003370043700537006370073700837009370103701137012370133701437015370163701737018370193702037021370223702337024370253702637027370283702937030370313703237033370343703537036370373703837039370403704137042370433704437045370463704737048370493705037051370523705337054370553705637057370583705937060370613706237063370643706537066370673706837069370703707137072370733707437075370763707737078370793708037081370823708337084370853708637087370883708937090370913709237093370943709537096370973709837099371003710137102371033710437105371063710737108371093711037111371123711337114371153711637117371183711937120371213712237123371243712537126371273712837129371303713137132371333713437135371363713737138371393714037141371423714337144371453714637147371483714937150371513715237153371543715537156371573715837159371603716137162371633716437165371663716737168371693717037171371723717337174371753717637177371783717937180371813718237183371843718537186371873718837189371903719137192371933719437195371963719737198371993720037201372023720337204372053720637207372083720937210372113721237213372143721537216372173721837219372203722137222372233722437225372263722737228372293723037231372323723337234372353723637237372383723937240372413724237243372443724537246372473724837249372503725137252372533725437255372563725737258372593726037261372623726337264372653726637267372683726937270372713727237273372743727537276372773727837279372803728137282372833728437285372863728737288372893729037291372923729337294372953729637297372983729937300373013730237303373043730537306373073730837309373103731137312373133731437315373163731737318373193732037321373223732337324373253732637327373283732937330373313733237333373343733537336373373733837339373403734137342373433734437345373463734737348373493735037351373523735337354373553735637357373583735937360373613736237363373643736537366373673736837369373703737137372373733737437375373763737737378373793738037381373823738337384373853738637387373883738937390373913739237393373943739537396373973739837399374003740137402374033740437405374063740737408374093741037411374123741337414374153741637417374183741937420374213742237423374243742537426374273742837429374303743137432374333743437435374363743737438374393744037441374423744337444374453744637447374483744937450374513745237453374543745537456374573745837459374603746137462374633746437465374663746737468374693747037471374723747337474374753747637477374783747937480374813748237483374843748537486374873748837489374903749137492374933749437495374963749737498374993750037501375023750337504375053750637507375083750937510375113751237513375143751537516375173751837519375203752137522375233752437525375263752737528375293753037531375323753337534375353753637537375383753937540375413754237543375443754537546375473754837549375503755137552375533755437555375563755737558375593756037561375623756337564375653756637567375683756937570375713757237573375743757537576375773757837579375803758137582375833758437585375863758737588375893759037591375923759337594375953759637597375983759937600376013760237603376043760537606376073760837609376103761137612376133761437615376163761737618376193762037621376223762337624376253762637627376283762937630376313763237633376343763537636376373763837639376403764137642376433764437645376463764737648376493765037651376523765337654376553765637657376583765937660376613766237663376643766537666376673766837669376703767137672376733767437675376763767737678376793768037681376823768337684376853768637687376883768937690376913769237693376943769537696376973769837699377003770137702377033770437705377063770737708377093771037711377123771337714377153771637717377183771937720377213772237723377243772537726377273772837729377303773137732377333773437735377363773737738377393774037741377423774337744377453774637747377483774937750377513775237753377543775537756377573775837759377603776137762377633776437765377663776737768377693777037771377723777337774377753777637777377783777937780377813778237783377843778537786377873778837789377903779137792377933779437795377963779737798377993780037801378023780337804378053780637807378083780937810378113781237813378143781537816378173781837819378203782137822378233782437825378263782737828378293783037831378323783337834378353783637837378383783937840378413784237843378443784537846378473784837849378503785137852378533785437855378563785737858378593786037861378623786337864378653786637867378683786937870378713787237873378743787537876378773787837879378803788137882378833788437885378863788737888378893789037891378923789337894378953789637897378983789937900379013790237903379043790537906379073790837909379103791137912379133791437915379163791737918379193792037921379223792337924379253792637927379283792937930379313793237933379343793537936379373793837939379403794137942379433794437945379463794737948379493795037951379523795337954379553795637957379583795937960379613796237963379643796537966379673796837969379703797137972379733797437975379763797737978379793798037981379823798337984379853798637987379883798937990379913799237993379943799537996379973799837999380003800138002380033800438005380063800738008380093801038011380123801338014380153801638017380183801938020380213802238023380243802538026380273802838029380303803138032380333803438035380363803738038380393804038041380423804338044380453804638047380483804938050380513805238053380543805538056380573805838059380603806138062380633806438065380663806738068380693807038071380723807338074380753807638077380783807938080380813808238083380843808538086380873808838089380903809138092380933809438095380963809738098380993810038101381023810338104381053810638107381083810938110381113811238113381143811538116381173811838119381203812138122381233812438125381263812738128381293813038131381323813338134381353813638137381383813938140381413814238143381443814538146381473814838149381503815138152381533815438155381563815738158381593816038161381623816338164381653816638167381683816938170381713817238173381743817538176381773817838179381803818138182381833818438185381863818738188381893819038191381923819338194381953819638197381983819938200382013820238203382043820538206382073820838209382103821138212382133821438215382163821738218382193822038221382223822338224382253822638227382283822938230382313823238233382343823538236382373823838239382403824138242382433824438245382463824738248382493825038251382523825338254382553825638257382583825938260382613826238263382643826538266382673826838269382703827138272382733827438275382763827738278382793828038281382823828338284382853828638287382883828938290382913829238293382943829538296382973829838299383003830138302383033830438305383063830738308383093831038311383123831338314383153831638317383183831938320383213832238323383243832538326383273832838329383303833138332383333833438335383363833738338383393834038341383423834338344383453834638347383483834938350383513835238353
  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. var i = 0,
  235. ln = fromStripes.length,
  236. j = 0,
  237. ln2, from, to,
  238. temp = toStripes.temp.params,
  239. pos = 0;
  240. for (; i < ln; i++) {
  241. from = fromStripes[i];
  242. to = toStripes[i];
  243. ln2 = from.length;
  244. for (j = 0; j < ln2; j++) {
  245. temp[pos++] = to[j] * delta + from[j];
  246. }
  247. }
  248. return toStripes.temp;
  249. }
  250. },
  251. data: {
  252. compute: function(from, to, delta, target) {
  253. var iMaxFrom = from.length - 1,
  254. iMaxTo = to.length - 1,
  255. iMax = Math.max(iMaxFrom, iMaxTo),
  256. i, start, end;
  257. if (!target || target === from) {
  258. target = [];
  259. }
  260. target.length = iMax + 1;
  261. for (i = 0; i <= iMax; i++) {
  262. start = from[Math.min(i, iMaxFrom)];
  263. end = to[Math.min(i, iMaxTo)];
  264. if (Ext.isNumber(start)) {
  265. if (!Ext.isNumber(end)) {
  266. // This may not give the desired visual result during
  267. // animation (after all, we don't know what the target
  268. // value should be, if it wasn't given to us), but it's
  269. // better than spitting out a bunch of NaNs in the target
  270. // array, when transitioning from a non-empty to an empty
  271. // array.
  272. end = 0;
  273. }
  274. target[i] = start + (end - start) * delta;
  275. } else {
  276. target[i] = end;
  277. }
  278. }
  279. return target;
  280. }
  281. },
  282. text: {
  283. compute: function(from, to, delta) {
  284. return from.substr(0, Math.round(from.length * (1 - delta))) + to.substr(Math.round(to.length * (1 - delta)));
  285. }
  286. },
  287. limited: 'number',
  288. limited01: 'number'
  289. };
  290. });
  291. (function() {
  292. if (!Ext.global.Float32Array) {
  293. // Typed Array polyfill
  294. var Float32Array = function(array) {
  295. if (typeof array === 'number') {
  296. this.length = array;
  297. } else if ('length' in array) {
  298. this.length = array.length;
  299. for (var i = 0,
  300. len = array.length; i < len; i++) {
  301. this[i] = +array[i];
  302. }
  303. }
  304. };
  305. Float32Array.prototype = [];
  306. Ext.global.Float32Array = Float32Array;
  307. }
  308. })();
  309. /**
  310. * Utility class providing mathematics functionalities through all the draw package.
  311. */
  312. Ext.define('Ext.draw.Draw', {
  313. singleton: true,
  314. radian: Math.PI / 180,
  315. pi2: Math.PI * 2,
  316. /**
  317. * @deprecated 6.5.0 Please use the {@link Ext#identityFn} instead.
  318. * Function that returns its first element.
  319. * @param {Mixed} a
  320. * @return {Mixed}
  321. */
  322. reflectFn: function(a) {
  323. return a;
  324. },
  325. /**
  326. * Converting degrees to radians.
  327. * @param {Number} degrees
  328. * @return {Number}
  329. */
  330. rad: function(degrees) {
  331. return (degrees % 360) * this.radian;
  332. },
  333. /**
  334. * Converting radians to degrees.
  335. * @param {Number} radian
  336. * @return {Number}
  337. */
  338. degrees: function(radian) {
  339. return (radian / this.radian) % 360;
  340. },
  341. /**
  342. *
  343. * @param {Object} bbox1
  344. * @param {Object} bbox2
  345. * @param {Number} [padding]
  346. * @return {Boolean}
  347. */
  348. isBBoxIntersect: function(bbox1, bbox2, padding) {
  349. padding = padding || 0;
  350. 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));
  351. },
  352. /**
  353. * Checks if a point is within a bounding box.
  354. * @param x
  355. * @param y
  356. * @param bbox
  357. * @return {Boolean}
  358. */
  359. isPointInBBox: function(x, y, bbox) {
  360. return !!bbox && x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height);
  361. },
  362. /**
  363. * Natural cubic spline interpolation.
  364. * This algorithm runs in linear time.
  365. *
  366. * @param {Array} points Array of numbers.
  367. */
  368. naturalSpline: function(points) {
  369. var i, j,
  370. ln = points.length,
  371. nd, d, y, ny,
  372. r = 0,
  373. zs = new Float32Array(points.length),
  374. result = new Float32Array(points.length * 3 - 2);
  375. zs[0] = 0;
  376. zs[ln - 1] = 0;
  377. for (i = 1; i < ln - 1; i++) {
  378. zs[i] = (points[i + 1] + points[i - 1] - 2 * points[i]) - zs[i - 1];
  379. r = 1 / (4 - r);
  380. zs[i] *= r;
  381. }
  382. for (i = ln - 2; i > 0; i--) {
  383. r = 3.732050807568877 + 48.248711305964385 / (-13.928203230275537 + Math.pow(0.07179676972449123, i));
  384. zs[i] -= zs[i + 1] * r;
  385. }
  386. ny = points[0];
  387. nd = ny - zs[0];
  388. for (i = 0 , j = 0; i < ln - 1; j += 3) {
  389. y = ny;
  390. d = nd;
  391. i++;
  392. ny = points[i];
  393. nd = ny - zs[i];
  394. result[j] = y;
  395. result[j + 1] = (nd + 2 * d) / 3;
  396. result[j + 2] = (nd * 2 + d) / 3;
  397. }
  398. result[j] = ny;
  399. return result;
  400. },
  401. /**
  402. * Shorthand for {@link #naturalSpline}
  403. */
  404. spline: function(points) {
  405. return this.naturalSpline(points);
  406. },
  407. /**
  408. * @private
  409. * Cardinal spline interpolation.
  410. * Goes from cardinal control points to cubic Bezier control points.
  411. */
  412. cardinalToBezier: function(P1, P2, P3, P4, tension) {
  413. return [
  414. P2,
  415. P2 + (P3 - P1) / 6 * tension,
  416. P3 - (P4 - P2) / 6 * tension,
  417. P3
  418. ];
  419. },
  420. /**
  421. * @private
  422. * @param {Number[]} P An array of n x- or y-coordinates.
  423. * @param {Number} tension
  424. * @return {Float32Array} An array of 3n - 2 Bezier control points.
  425. */
  426. cardinalSpline: function(P, tension) {
  427. var n = P.length,
  428. result = new Float32Array(n * 3 - 2),
  429. i, bezier;
  430. if (tension === undefined) {
  431. tension = 0.5;
  432. }
  433. bezier = this.cardinalToBezier(P[0], P[0], P[1], P[2], tension);
  434. result[0] = bezier[0];
  435. result[1] = bezier[1];
  436. result[2] = bezier[2];
  437. result[3] = bezier[3];
  438. for (i = 0; i < n - 3; i++) {
  439. bezier = this.cardinalToBezier(P[i], P[i + 1], P[i + 2], P[i + 3], tension);
  440. result[4 + i * 3] = bezier[1];
  441. result[4 + i * 3 + 1] = bezier[2];
  442. result[4 + i * 3 + 2] = bezier[3];
  443. }
  444. bezier = this.cardinalToBezier(P[n - 3], P[n - 2], P[n - 1], P[n - 1], 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. return result;
  449. },
  450. /**
  451. * @private
  452. *
  453. * Calculates bezier curve control anchor points for a particular point in a path, with a
  454. * smoothing curve applied. The smoothness of the curve is controlled by the 'value' parameter.
  455. * Note that this algorithm assumes that the line being smoothed is normalized going from left
  456. * to right; it makes special adjustments assuming this orientation.
  457. *
  458. * @param {Number} prevX X coordinate of the previous point in the path
  459. * @param {Number} prevY Y coordinate of the previous point in the path
  460. * @param {Number} curX X coordinate of the current point in the path
  461. * @param {Number} curY Y coordinate of the current point in the path
  462. * @param {Number} nextX X coordinate of the next point in the path
  463. * @param {Number} nextY Y coordinate of the next point in the path
  464. * @param {Number} value A value to control the smoothness of the curve; this is used to
  465. * divide the distance between points, so a value of 2 corresponds to
  466. * half the distance between points (a very smooth line) while higher values
  467. * result in less smooth curves. Defaults to 4.
  468. * @return {Object} Object containing x1, y1, x2, y2 bezier control anchor points; x1 and y1
  469. * are the control point for the curve toward the previous path point, and
  470. * x2 and y2 are the control point for the curve toward the next path point.
  471. */
  472. getAnchors: function(prevX, prevY, curX, curY, nextX, nextY, value) {
  473. value = value || 4;
  474. var PI = Math.PI,
  475. halfPI = PI / 2,
  476. abs = Math.abs,
  477. sin = Math.sin,
  478. cos = Math.cos,
  479. atan = Math.atan,
  480. control1Length, control2Length, control1Angle, control2Angle, control1X, control1Y, control2X, control2Y, alpha;
  481. // Find the length of each control anchor line, by dividing the horizontal distance
  482. // between points by the value parameter.
  483. control1Length = (curX - prevX) / value;
  484. control2Length = (nextX - curX) / value;
  485. // Determine the angle of each control anchor line. If the middle point is a vertical
  486. // turnaround then we force it to a flat horizontal angle to prevent the curve from
  487. // dipping above or below the middle point. Otherwise we use an angle that points
  488. // toward the previous/next target point.
  489. if ((curY >= prevY && curY >= nextY) || (curY <= prevY && curY <= nextY)) {
  490. control1Angle = control2Angle = halfPI;
  491. } else {
  492. control1Angle = atan((curX - prevX) / abs(curY - prevY));
  493. if (prevY < curY) {
  494. control1Angle = PI - control1Angle;
  495. }
  496. control2Angle = atan((nextX - curX) / abs(curY - nextY));
  497. if (nextY < curY) {
  498. control2Angle = PI - control2Angle;
  499. }
  500. }
  501. // Adjust the calculated angles so they point away from each other on the same line
  502. alpha = halfPI - ((control1Angle + control2Angle) % (PI * 2)) / 2;
  503. if (alpha > halfPI) {
  504. alpha -= PI;
  505. }
  506. control1Angle += alpha;
  507. control2Angle += alpha;
  508. // Find the control anchor points from the angles and length
  509. control1X = curX - control1Length * sin(control1Angle);
  510. control1Y = curY + control1Length * cos(control1Angle);
  511. control2X = curX + control2Length * sin(control2Angle);
  512. control2Y = curY + control2Length * cos(control2Angle);
  513. // One last adjustment, make sure that no control anchor point extends vertically past
  514. // its target prev/next point, as that results in curves dipping above or below and
  515. // bending back strangely. If we find this happening we keep the control angle but
  516. // reduce the length of the control line so it stays within bounds.
  517. if ((curY > prevY && control1Y < prevY) || (curY < prevY && control1Y > prevY)) {
  518. control1X += abs(prevY - control1Y) * (control1X - curX) / (control1Y - curY);
  519. control1Y = prevY;
  520. }
  521. if ((curY > nextY && control2Y < nextY) || (curY < nextY && control2Y > nextY)) {
  522. control2X -= abs(nextY - control2Y) * (control2X - curX) / (control2Y - curY);
  523. control2Y = nextY;
  524. }
  525. return {
  526. x1: control1X,
  527. y1: control1Y,
  528. x2: control2X,
  529. y2: control2Y
  530. };
  531. },
  532. /**
  533. * Given coordinates of the points, calculates coordinates of a Bezier curve that goes through them.
  534. * @param dataX x-coordinates of the points.
  535. * @param dataY y-coordinates of the points.
  536. * @param value A value to control the smoothness of the curve.
  537. * @return {Object} Object holding two arrays, for x and y coordinates of the curve.
  538. */
  539. smooth: function(dataX, dataY, value) {
  540. var ln = dataX.length,
  541. prevX, prevY, curX, curY, nextX, nextY, x, y,
  542. smoothX = [],
  543. smoothY = [],
  544. i, anchors;
  545. for (i = 0; i < ln - 1; i++) {
  546. prevX = dataX[i];
  547. prevY = dataY[i];
  548. if (i === 0) {
  549. x = prevX;
  550. y = prevY;
  551. smoothX.push(x);
  552. smoothY.push(y);
  553. if (ln === 1) {
  554. break;
  555. }
  556. }
  557. curX = dataX[i + 1];
  558. curY = dataY[i + 1];
  559. nextX = dataX[i + 2];
  560. nextY = dataY[i + 2];
  561. if (!(Ext.isNumber(nextX) && Ext.isNumber(nextY))) {
  562. smoothX.push(x, curX, curX);
  563. smoothY.push(y, curY, curY);
  564. break;
  565. }
  566. anchors = this.getAnchors(prevX, prevY, curX, curY, nextX, nextY, value);
  567. smoothX.push(x, anchors.x1, curX);
  568. smoothY.push(y, anchors.y1, curY);
  569. x = anchors.x2;
  570. y = anchors.y2;
  571. }
  572. return {
  573. smoothX: smoothX,
  574. smoothY: smoothY
  575. };
  576. },
  577. /**
  578. * @method
  579. * @private
  580. * Work around for iOS.
  581. * Nested 3d-transforms seems to prevent the redraw inside it until some event is fired.
  582. */
  583. beginUpdateIOS: Ext.os.is.iOS ? function() {
  584. this.iosUpdateEl = Ext.getBody().createChild({
  585. //<debug>
  586. 'data-sticky': true,
  587. //</debug>
  588. style: 'position: absolute; top: 0px; bottom: 0px; left: 0px; right: 0px; background: rgba(0,0,0,0.001); z-index: 100000'
  589. });
  590. } : Ext.emptyFn,
  591. endUpdateIOS: function() {
  592. this.iosUpdateEl = Ext.destroy(this.iosUpdateEl);
  593. }
  594. });
  595. /**
  596. * @class Ext.draw.gradient.Gradient
  597. *
  598. * Creates a gradient.
  599. */
  600. Ext.define('Ext.draw.gradient.Gradient', {
  601. requires: [
  602. 'Ext.draw.Color'
  603. ],
  604. isGradient: true,
  605. config: {
  606. /**
  607. * @cfg {Object[]} stops
  608. * Defines the stops of the gradient.
  609. */
  610. stops: []
  611. },
  612. applyStops: function(newStops) {
  613. var stops = [],
  614. ln = newStops.length,
  615. i, stop, color;
  616. for (i = 0; i < ln; i++) {
  617. stop = newStops[i];
  618. color = stop.color;
  619. if (!(color && color.isColor)) {
  620. color = Ext.util.Color.fly(color || Ext.util.Color.NONE);
  621. }
  622. stops.push({
  623. offset: Math.min(1, Math.max(0, 'offset' in stop ? stop.offset : stop.position || 0)),
  624. color: color.toString()
  625. });
  626. }
  627. stops.sort(function(a, b) {
  628. return a.offset - b.offset;
  629. });
  630. return stops;
  631. },
  632. onClassExtended: function(subClass, member) {
  633. if (!member.alias && member.type) {
  634. member.alias = 'gradient.' + member.type;
  635. }
  636. },
  637. constructor: function(config) {
  638. this.initConfig(config);
  639. },
  640. /**
  641. * @method
  642. * @protected
  643. * Generates the gradient for the given context.
  644. * @param {Ext.draw.engine.SvgContext} ctx The context.
  645. * @param {Object} bbox
  646. * @return {CanvasGradient/Ext.draw.engine.SvgContext.Gradient/Ext.util.Color.NONE}
  647. */
  648. generateGradient: Ext.emptyFn
  649. });
  650. /**
  651. * @class Ext.draw.gradient.GradientDefinition
  652. *
  653. * A global map of all gradient configs.
  654. */
  655. Ext.define('Ext.draw.gradient.GradientDefinition', {
  656. singleton: true,
  657. urlStringRe: /^url\(#([\w\-]+)\)$/,
  658. gradients: {},
  659. add: function(gradients) {
  660. var store = this.gradients,
  661. i, n, gradient;
  662. for (i = 0 , n = gradients.length; i < n; i++) {
  663. gradient = gradients[i];
  664. if (Ext.isString(gradient.id)) {
  665. store[gradient.id] = gradient;
  666. }
  667. }
  668. },
  669. get: function(str) {
  670. var store = this.gradients,
  671. match = str.match(this.urlStringRe),
  672. gradient;
  673. if (match && match[1] && (gradient = store[match[1]])) {
  674. return gradient || str;
  675. }
  676. return str;
  677. }
  678. });
  679. /**
  680. * @private
  681. * @class Ext.draw.sprite.AttributeParser
  682. *
  683. * Parsers used for sprite attributes if they are {@link Ext.draw.sprite.AttributeDefinition#normalize normalized}
  684. * (default) when being {@link Ext.draw.sprite.Sprite#setAttributes set}.
  685. *
  686. * Methods of the singleton correpond either to the processor functions themselves or processor factories.
  687. */
  688. Ext.define('Ext.draw.sprite.AttributeParser', {
  689. singleton: true,
  690. attributeRe: /^url\(#([a-zA-Z\-]+)\)$/,
  691. requires: [
  692. 'Ext.draw.Color',
  693. 'Ext.draw.gradient.GradientDefinition'
  694. ],
  695. 'default': Ext.identityFn,
  696. string: function(n) {
  697. return String(n);
  698. },
  699. number: function(n) {
  700. // Numbers as strings will be converted to numbers,
  701. // null will be converted to 0.
  702. if (Ext.isNumber(+n)) {
  703. return n;
  704. }
  705. },
  706. /**
  707. * Normalize angle to the [-180,180) interval.
  708. * @param n Angle in radians.
  709. * @return {Number/undefined} Normalized angle or undefined.
  710. */
  711. angle: function(n) {
  712. if (Ext.isNumber(n)) {
  713. n %= Math.PI * 2;
  714. if (n < -Math.PI) {
  715. n += Math.PI * 2;
  716. } else if (n >= Math.PI) {
  717. n -= Math.PI * 2;
  718. }
  719. return n;
  720. }
  721. },
  722. data: function(n) {
  723. if (Ext.isArray(n)) {
  724. return n.slice();
  725. } else if (n instanceof Float32Array) {
  726. return new Float32Array(n);
  727. }
  728. },
  729. bool: function(n) {
  730. return !!n;
  731. },
  732. color: function(n) {
  733. if (n && n.isColor) {
  734. return n.toString();
  735. } else if (n && n.isGradient) {
  736. return n;
  737. } else if (!n) {
  738. return Ext.util.Color.NONE;
  739. } else if (Ext.isString(n)) {
  740. if (n.substr(0, 3) === 'url') {
  741. n = Ext.draw.gradient.GradientDefinition.get(n);
  742. if (Ext.isString(n)) {
  743. return n;
  744. }
  745. } else {
  746. return Ext.util.Color.fly(n).toString();
  747. }
  748. }
  749. if (n.type === 'linear') {
  750. return Ext.create('Ext.draw.gradient.Linear', n);
  751. } else if (n.type === 'radial') {
  752. return Ext.create('Ext.draw.gradient.Radial', n);
  753. } else if (n.type === 'pattern') {
  754. return Ext.create('Ext.draw.gradient.Pattern', n);
  755. } else {
  756. return Ext.util.Color.NONE;
  757. }
  758. },
  759. limited: function(low, hi) {
  760. return function(n) {
  761. n = +n;
  762. return Ext.isNumber(n) ? Math.min(Math.max(n, low), hi) : undefined;
  763. };
  764. },
  765. limited01: function(n) {
  766. n = +n;
  767. return Ext.isNumber(n) ? Math.min(Math.max(n, 0), 1) : undefined;
  768. },
  769. /**
  770. * Generates a function that checks if a value matches
  771. * one of the given attributes.
  772. * @return {Function}
  773. */
  774. enums: function() {
  775. var enums = {},
  776. args = Array.prototype.slice.call(arguments, 0),
  777. i, ln;
  778. for (i = 0 , ln = args.length; i < ln; i++) {
  779. enums[args[i]] = true;
  780. }
  781. return function(n) {
  782. return n in enums ? n : undefined;
  783. };
  784. }
  785. });
  786. /**
  787. * @private
  788. * Flyweight object to process the attributes of a sprite.
  789. * A single instance of the AttributeDefinition is created per sprite class.
  790. * See `onClassCreated` and `onClassExtended` callbacks
  791. * of the {@link Ext.draw.sprite.Sprite} for more info.
  792. */
  793. Ext.define('Ext.draw.sprite.AttributeDefinition', {
  794. requires: [
  795. 'Ext.draw.sprite.AttributeParser',
  796. 'Ext.draw.sprite.AnimationParser'
  797. ],
  798. config: {
  799. /**
  800. * @cfg {Object} defaults Defines the default values of attributes.
  801. */
  802. defaults: {
  803. $value: {},
  804. lazy: true
  805. },
  806. /**
  807. * @cfg {Object} aliases Defines the alternative names for attributes.
  808. */
  809. aliases: {},
  810. /**
  811. * @cfg {Object} animationProcessors Defines the process used to animate between attributes.
  812. * One doesn't have to define animation processors for sprite attributes that use
  813. * predefined {@link #processors} from the {@link Ext.draw.sprite.AttributeParser} singleton.
  814. * For such attributes matching animation processors from the {@link Ext.draw.sprite.AnimationParser}
  815. * singleton will be used automatically.
  816. * However, if you have a custom processor for an attribute that should support
  817. * animation, you must provide a corresponding animation processor for it here.
  818. * For more information on animation processors please see {@link Ext.draw.sprite.AnimationParser}
  819. * documentation.
  820. */
  821. animationProcessors: {},
  822. /**
  823. * @cfg {Object} processors Defines the preprocessing used on the attributes.
  824. * One can define a custom processor function here or use the name of a predefined
  825. * processor from the {@link Ext.draw.sprite.AttributeParser} singleton.
  826. */
  827. processors: {
  828. // A plus side of lazy initialization is that the 'processors' and 'defaults' will
  829. // only be applied for those sprite classes that are actually instantiated.
  830. $value: {},
  831. lazy: true
  832. },
  833. /**
  834. * @cfg {Object} dirtyTriggers
  835. * @deprecated 6.5.0 Use the {@link #triggers} config instead.
  836. */
  837. dirtyTriggers: {},
  838. /**
  839. * @cfg {Object} triggers Defines which updaters have to be called when an attribute is changed.
  840. * For example, the config below indicates that the 'size' updater
  841. * of a {@link Ext.draw.sprite.Square square} sprite has to be called
  842. * when the 'size' attribute changes.
  843. *
  844. * triggers: {
  845. * size: 'size' // Use comma-separated values here if multiple updaters have to be called.
  846. * } // Note that the order is _not_ guaranteed.
  847. *
  848. * If any of the updaters to be called (triggered by the {@link Ext.draw.sprite.Sprite#setAttributes} call)
  849. * set attributes themselves and those attributes have triggers defined for them,
  850. * then their updaters will be called after all current updaters finish execution.
  851. *
  852. * The updater functions themselves are defined in the {@link #updaters} config,
  853. * aside from the 'canvas' updater, which doesn't have to be defined and acts as a flag,
  854. * indicating that this attribute should be applied to a Canvas context (or whatever emulates it).
  855. * @since 5.1.0
  856. */
  857. triggers: {},
  858. /**
  859. * @cfg {Object} updaters Defines the postprocessing used by the attribute.
  860. * Inside the updater function 'this' refers to the sprite that the attributes belong to.
  861. * In case of an instancing sprite 'this' will refer to the instancing template.
  862. * The two parameters passed to the updater function are the attributes object
  863. * of the sprite or instance, and the names of attributes that triggered this updater call.
  864. *
  865. * The example below shows how the 'size' updater changes other attributes
  866. * of a {@link Ext.draw.sprite.Square square} sprite sprite when its 'size' attribute changes.
  867. *
  868. * updaters: {
  869. * size: function (attr) {
  870. * var size = attr.size;
  871. * this.setAttributes({ // Changes to these attributes will trigger the 'path' updater.
  872. * x: attr.x - size,
  873. * y: attr.y - size,
  874. * height: 2 * size,
  875. * width: 2 * size
  876. * });
  877. * }
  878. * }
  879. */
  880. updaters: {}
  881. },
  882. inheritableStatics: {
  883. /**
  884. * @private
  885. * Processor declaration in the form of 'processorFactory(argument1,argument2,...)'.
  886. * E.g.: {@link Ext.draw.sprite.AttributeParser#enums enums},
  887. * {@link Ext.draw.sprite.AttributeParser#limited limited}.
  888. */
  889. processorFactoryRe: /^(\w+)\(([\w\-,]*)\)$/
  890. },
  891. // The sprite class for which AttributeDefinition instance is created.
  892. spriteClass: null,
  893. constructor: function(config) {
  894. var me = this;
  895. me.initConfig(config);
  896. },
  897. applyDefaults: function(defaults, oldDefaults) {
  898. oldDefaults = Ext.apply(oldDefaults || {}, this.normalize(defaults));
  899. return oldDefaults;
  900. },
  901. applyAliases: function(aliases, oldAliases) {
  902. return Ext.apply(oldAliases || {}, aliases);
  903. },
  904. applyProcessors: function(processors, oldProcessors) {
  905. this.getAnimationProcessors();
  906. // Apply custom animation processors first.
  907. var result = oldProcessors || {},
  908. defaultProcessor = Ext.draw.sprite.AttributeParser,
  909. processorFactoryRe = this.self.processorFactoryRe,
  910. animationProcessors = {},
  911. anyAnimationProcessors, name, match, fn;
  912. for (name in processors) {
  913. fn = processors[name];
  914. if (typeof fn === 'string') {
  915. match = fn.match(processorFactoryRe);
  916. if (match) {
  917. // enums(... , limited(... or something of that nature.
  918. fn = defaultProcessor[match[1]].apply(defaultProcessor, match[2].split(','));
  919. } else if (defaultProcessor[fn]) {
  920. // Names of animation parsers match the names of attribute parsers.
  921. animationProcessors[name] = fn;
  922. anyAnimationProcessors = true;
  923. fn = defaultProcessor[fn];
  924. }
  925. }
  926. //<debug>
  927. if (!Ext.isFunction(fn)) {
  928. Ext.raise(this.spriteClass.$className + ": processor '" + name + "' has not been found.");
  929. }
  930. //</debug>
  931. result[name] = fn;
  932. }
  933. if (anyAnimationProcessors) {
  934. this.setAnimationProcessors(animationProcessors);
  935. }
  936. return result;
  937. },
  938. applyAnimationProcessors: function(animationProcessors, oldAnimationProcessors) {
  939. var parser = Ext.draw.sprite.AnimationParser,
  940. name, item;
  941. if (!oldAnimationProcessors) {
  942. oldAnimationProcessors = {};
  943. }
  944. for (name in animationProcessors) {
  945. item = animationProcessors[name];
  946. if (item === 'none') {
  947. oldAnimationProcessors[name] = null;
  948. } else if (Ext.isString(item) && !(name in oldAnimationProcessors)) {
  949. if (item in parser) {
  950. // The while loop is used to resolve aliases, e.g. `num: 'number'`,
  951. // where `number` maps to a parser object or is an alias too.
  952. while (Ext.isString(parser[item])) {
  953. item = parser[item];
  954. }
  955. oldAnimationProcessors[name] = parser[item];
  956. }
  957. } else if (Ext.isObject(item)) {
  958. oldAnimationProcessors[name] = item;
  959. }
  960. }
  961. return oldAnimationProcessors;
  962. },
  963. updateDirtyTriggers: function(dirtyTriggers) {
  964. this.setTriggers(dirtyTriggers);
  965. },
  966. applyTriggers: function(triggers, oldTriggers) {
  967. if (!oldTriggers) {
  968. oldTriggers = {};
  969. }
  970. for (var name in triggers) {
  971. oldTriggers[name] = triggers[name].split(',');
  972. }
  973. return oldTriggers;
  974. },
  975. applyUpdaters: function(updaters, oldUpdaters) {
  976. return Ext.apply(oldUpdaters || {}, updaters);
  977. },
  978. batchedNormalize: function(batchedChanges, keepUnrecognized) {
  979. if (!batchedChanges) {
  980. return {};
  981. }
  982. var processors = this.getProcessors(),
  983. aliases = this.getAliases(),
  984. translation = batchedChanges.translation || batchedChanges.translate,
  985. normalized = {},
  986. i, ln, name, val, rotation, scaling, matrix, subVal, split;
  987. if ('rotation' in batchedChanges) {
  988. rotation = batchedChanges.rotation;
  989. } else {
  990. rotation = ('rotate' in batchedChanges) ? batchedChanges.rotate : undefined;
  991. }
  992. if ('scaling' in batchedChanges) {
  993. scaling = batchedChanges.scaling;
  994. } else {
  995. scaling = ('scale' in batchedChanges) ? batchedChanges.scale : undefined;
  996. }
  997. if (typeof scaling !== 'undefined') {
  998. if (Ext.isNumber(scaling)) {
  999. normalized.scalingX = scaling;
  1000. normalized.scalingY = scaling;
  1001. } else {
  1002. if ('x' in scaling) {
  1003. normalized.scalingX = scaling.x;
  1004. }
  1005. if ('y' in scaling) {
  1006. normalized.scalingY = scaling.y;
  1007. }
  1008. if ('centerX' in scaling) {
  1009. normalized.scalingCenterX = scaling.centerX;
  1010. }
  1011. if ('centerY' in scaling) {
  1012. normalized.scalingCenterY = scaling.centerY;
  1013. }
  1014. }
  1015. }
  1016. if (typeof rotation !== 'undefined') {
  1017. if (Ext.isNumber(rotation)) {
  1018. rotation = Ext.draw.Draw.rad(rotation);
  1019. normalized.rotationRads = rotation;
  1020. } else {
  1021. if ('rads' in rotation) {
  1022. normalized.rotationRads = rotation.rads;
  1023. } else if ('degrees' in rotation) {
  1024. if (Ext.isArray(rotation.degrees)) {
  1025. normalized.rotationRads = Ext.Array.map(rotation.degrees, function(deg) {
  1026. return Ext.draw.Draw.rad(deg);
  1027. });
  1028. } else {
  1029. normalized.rotationRads = Ext.draw.Draw.rad(rotation.degrees);
  1030. }
  1031. }
  1032. if ('centerX' in rotation) {
  1033. normalized.rotationCenterX = rotation.centerX;
  1034. }
  1035. if ('centerY' in rotation) {
  1036. normalized.rotationCenterY = rotation.centerY;
  1037. }
  1038. }
  1039. }
  1040. if (typeof translation !== 'undefined') {
  1041. if ('x' in translation) {
  1042. normalized.translationX = translation.x;
  1043. }
  1044. if ('y' in translation) {
  1045. normalized.translationY = translation.y;
  1046. }
  1047. }
  1048. if ('matrix' in batchedChanges) {
  1049. matrix = Ext.draw.Matrix.create(batchedChanges.matrix);
  1050. split = matrix.split();
  1051. normalized.matrix = matrix;
  1052. normalized.rotationRads = split.rotation;
  1053. normalized.rotationCenterX = 0;
  1054. normalized.rotationCenterY = 0;
  1055. normalized.scalingX = split.scaleX;
  1056. normalized.scalingY = split.scaleY;
  1057. normalized.scalingCenterX = 0;
  1058. normalized.scalingCenterY = 0;
  1059. normalized.translationX = split.translateX;
  1060. normalized.translationY = split.translateY;
  1061. }
  1062. for (name in batchedChanges) {
  1063. val = batchedChanges[name];
  1064. if (typeof val === 'undefined') {
  1065. continue;
  1066. } else if (Ext.isArray(val)) {
  1067. if (name in aliases) {
  1068. name = aliases[name];
  1069. }
  1070. if (name in processors) {
  1071. normalized[name] = [];
  1072. for (i = 0 , ln = val.length; i < ln; i++) {
  1073. subVal = processors[name].call(this, val[i]);
  1074. if (typeof subVal !== 'undefined') {
  1075. normalized[name][i] = subVal;
  1076. }
  1077. }
  1078. } else if (keepUnrecognized) {
  1079. normalized[name] = val;
  1080. }
  1081. } else {
  1082. if (name in aliases) {
  1083. name = aliases[name];
  1084. }
  1085. if (name in processors) {
  1086. val = processors[name].call(this, val);
  1087. if (typeof val !== 'undefined') {
  1088. normalized[name] = val;
  1089. }
  1090. } else if (keepUnrecognized) {
  1091. normalized[name] = val;
  1092. }
  1093. }
  1094. }
  1095. return normalized;
  1096. },
  1097. /**
  1098. * Normalizes the changes given via their processors before they are applied as attributes.
  1099. *
  1100. * @param {Object} changes The changes given.
  1101. * @param {Boolean} keepUnrecognized If 'true', unknown attributes will be passed through as normalized values.
  1102. * @return {Object} The normalized values.
  1103. */
  1104. normalize: function(changes, keepUnrecognized) {
  1105. if (!changes) {
  1106. return {};
  1107. }
  1108. var processors = this.getProcessors(),
  1109. aliases = this.getAliases(),
  1110. translation = changes.translation || changes.translate,
  1111. normalized = {},
  1112. name, val, rotation, scaling, matrix, split;
  1113. if ('rotation' in changes) {
  1114. rotation = changes.rotation;
  1115. } else {
  1116. rotation = ('rotate' in changes) ? changes.rotate : undefined;
  1117. }
  1118. if ('scaling' in changes) {
  1119. scaling = changes.scaling;
  1120. } else {
  1121. scaling = ('scale' in changes) ? changes.scale : undefined;
  1122. }
  1123. if (translation) {
  1124. if ('x' in translation) {
  1125. normalized.translationX = translation.x;
  1126. }
  1127. if ('y' in translation) {
  1128. normalized.translationY = translation.y;
  1129. }
  1130. }
  1131. if (typeof scaling !== 'undefined') {
  1132. if (Ext.isNumber(scaling)) {
  1133. normalized.scalingX = scaling;
  1134. normalized.scalingY = scaling;
  1135. } else {
  1136. if ('x' in scaling) {
  1137. normalized.scalingX = scaling.x;
  1138. }
  1139. if ('y' in scaling) {
  1140. normalized.scalingY = scaling.y;
  1141. }
  1142. if ('centerX' in scaling) {
  1143. normalized.scalingCenterX = scaling.centerX;
  1144. }
  1145. if ('centerY' in scaling) {
  1146. normalized.scalingCenterY = scaling.centerY;
  1147. }
  1148. }
  1149. }
  1150. if (typeof rotation !== 'undefined') {
  1151. if (Ext.isNumber(rotation)) {
  1152. rotation = Ext.draw.Draw.rad(rotation);
  1153. normalized.rotationRads = rotation;
  1154. } else {
  1155. if ('rads' in rotation) {
  1156. normalized.rotationRads = rotation.rads;
  1157. } else if ('degrees' in rotation) {
  1158. normalized.rotationRads = Ext.draw.Draw.rad(rotation.degrees);
  1159. }
  1160. if ('centerX' in rotation) {
  1161. normalized.rotationCenterX = rotation.centerX;
  1162. }
  1163. if ('centerY' in rotation) {
  1164. normalized.rotationCenterY = rotation.centerY;
  1165. }
  1166. }
  1167. }
  1168. if ('matrix' in changes) {
  1169. matrix = Ext.draw.Matrix.create(changes.matrix);
  1170. split = matrix.split();
  1171. // This will NOT update the transformation matrix of a sprite
  1172. // with the given elements. It will attempt to extract the
  1173. // individual transformation attributes from the transformation matrix
  1174. // elements provided. Then the extracted attributes will be used by
  1175. // the sprite's 'applyTransformations' method to calculate
  1176. // the transformation matrix of the sprite.
  1177. // It's not possible to recover all the information from the given
  1178. // transformation matrix elements. Shearing and centers of rotation
  1179. // and scaling are not recovered.
  1180. // Ideally, this should work like sprite.transform([elements], true),
  1181. // i.e. update the transformation matrix of a sprite directly,
  1182. // without attempting to update sprite's transformation attributes.
  1183. // But we are not changing the behavior (just yet) for compatibility
  1184. // reasons.
  1185. normalized.matrix = matrix;
  1186. normalized.rotationRads = split.rotation;
  1187. normalized.rotationCenterX = 0;
  1188. normalized.rotationCenterY = 0;
  1189. normalized.scalingX = split.scaleX;
  1190. normalized.scalingY = split.scaleY;
  1191. normalized.scalingCenterX = 0;
  1192. normalized.scalingCenterY = 0;
  1193. normalized.translationX = split.translateX;
  1194. normalized.translationY = split.translateY;
  1195. }
  1196. for (name in changes) {
  1197. val = changes[name];
  1198. if (typeof val === 'undefined') {
  1199. continue;
  1200. }
  1201. if (name in aliases) {
  1202. name = aliases[name];
  1203. }
  1204. if (name in processors) {
  1205. val = processors[name].call(this, val);
  1206. if (typeof val !== 'undefined') {
  1207. normalized[name] = val;
  1208. }
  1209. } else if (keepUnrecognized) {
  1210. normalized[name] = val;
  1211. }
  1212. }
  1213. return normalized;
  1214. },
  1215. setBypassingNormalization: function(attr, modifierStack, changes) {
  1216. return modifierStack.pushDown(attr, changes);
  1217. },
  1218. set: function(attr, modifierStack, changes) {
  1219. changes = this.normalize(changes);
  1220. return this.setBypassingNormalization(attr, modifierStack, changes);
  1221. }
  1222. });
  1223. /**
  1224. * Ext.draw.Matix is a utility class used to calculate
  1225. * [affine transformation](http://en.wikipedia.org/wiki/Affine_transformation) matrix.
  1226. * The matrix class is used to apply transformations to existing
  1227. * {@link Ext.draw.sprite.Sprite sprites} using a number of convenience transform
  1228. * methods.
  1229. *
  1230. * Transformations configured directly on a sprite are processed in the following order:
  1231. * scaling, rotation, and translation. The matrix class offers additional flexibility.
  1232. * Once a sprite is created, you can use the matrix class's transform methods as many
  1233. * times as needed and in any order you choose.
  1234. *
  1235. * To demonstrate, we'll start with a simple {@link Ext.draw.sprite.Rect rect} sprite
  1236. * with the intent of rotating it 180 degrees with the bottom right corner being the
  1237. * center of rotation. To begin, let's look at the initial, untransformed sprite:
  1238. *
  1239. * @example
  1240. * var drawContainer = new Ext.draw.Container({
  1241. * renderTo: Ext.getBody(),
  1242. * width: 380,
  1243. * height: 380,
  1244. * sprites: [{
  1245. * type: 'rect',
  1246. * width: 100,
  1247. * height: 100,
  1248. * fillStyle: 'red'
  1249. * }]
  1250. * });
  1251. *
  1252. * Next, we'll use the {@link #rotate} and {@link #translate} methods from our matrix
  1253. * class to position the rect sprite.
  1254. *
  1255. * @example
  1256. * var drawContainer = new Ext.draw.Container({
  1257. * renderTo: Ext.getBody(),
  1258. * width: 380,
  1259. * height: 380,
  1260. * sprites: [{
  1261. * type: 'rect',
  1262. * width: 100,
  1263. * height: 100,
  1264. * fillStyle: 'red'
  1265. * }]
  1266. * });
  1267. *
  1268. * var main = drawContainer.getSurface();
  1269. * var rect = main.getItems()[0];
  1270. *
  1271. * var m = new Ext.draw.Matrix().translate(100, 100).
  1272. * rotate(Math.PI).
  1273. * translate(-100, - 100);
  1274. *
  1275. * rect.setTransform(m);
  1276. * main.renderFrame();
  1277. *
  1278. * In the previous example we perform the following steps in order to achieve our
  1279. * desired rotated output:
  1280. *
  1281. * - translate the rect to the right and down by 100
  1282. * - rotate by 180 degrees
  1283. * - translate the rect to the right and down by 100
  1284. *
  1285. * **Note:** A couple of things to note at this stage; 1) the rotation center point is
  1286. * the upper left corner of the sprite by default and 2) with transformations, the
  1287. * sprite itself isn't transformed, but rather the entire coordinate plane of the sprite
  1288. * is transformed. The coordinate plane itself is translated by 100 and then rotated
  1289. * 180 degrees. And that is why in the third step we translate the sprite using
  1290. * negative values. Translating by -100 in the third step results in the sprite
  1291. * visually moving to the right and down within the draw container.
  1292. *
  1293. * Fortunately there is a shortcut we can apply using two optional params of the rotate
  1294. * method allowing us to specify the center point of rotation:
  1295. *
  1296. * @example
  1297. * var drawContainer = new Ext.draw.Container({
  1298. * renderTo: Ext.getBody(),
  1299. * width: 380,
  1300. * height: 380,
  1301. * sprites: [{
  1302. * type: 'rect',
  1303. * width: 100,
  1304. * height: 100,
  1305. * fillStyle: 'red'
  1306. * }]
  1307. * });
  1308. *
  1309. * var main = drawContainer.getSurface();
  1310. * var rect = main.getItems()[0];
  1311. *
  1312. * var m = new Ext.draw.Matrix().rotate(Math.PI, 100, 100);
  1313. *
  1314. * rect.setTransform(m);
  1315. * main.renderFrame();
  1316. *
  1317. *
  1318. * This class is compatible with
  1319. * [SVGMatrix](http://www.w3.org/TR/SVG11/coords.html#InterfaceSVGMatrix) except:
  1320. *
  1321. * 1. Ext.draw.Matrix is not read only
  1322. * 2. Using Number as its values rather than floats
  1323. *
  1324. * Using this class helps to reduce the severe numeric
  1325. * [problem with HTML Canvas and SVG transformation](http://stackoverflow.com/questions/8784405/large-numbers-in-html-canvas-translate-result-in-strange-behavior)
  1326. *
  1327. * Additionally, there's no way to get the current transformation matrix
  1328. * [in Canvas](http://stackoverflow.com/questions/7395813/html5-canvas-get-transform-matrix).
  1329. */
  1330. Ext.define('Ext.draw.Matrix', {
  1331. isMatrix: true,
  1332. statics: {
  1333. /**
  1334. * @static
  1335. * Return the affine matrix that transform two points (x0, y0) and (x1, y1) to (x0p, y0p) and (x1p, y1p)
  1336. * @param {Number} x0
  1337. * @param {Number} y0
  1338. * @param {Number} x1
  1339. * @param {Number} y1
  1340. * @param {Number} x0p
  1341. * @param {Number} y0p
  1342. * @param {Number} x1p
  1343. * @param {Number} y1p
  1344. */
  1345. createAffineMatrixFromTwoPair: function(x0, y0, x1, y1, x0p, y0p, x1p, y1p) {
  1346. var dx = x1 - x0,
  1347. dy = y1 - y0,
  1348. dxp = x1p - x0p,
  1349. dyp = y1p - y0p,
  1350. r = 1 / (dx * dx + dy * dy),
  1351. a = dx * dxp + dy * dyp,
  1352. b = dxp * dy - dx * dyp,
  1353. c = -a * x0 - b * y0,
  1354. f = b * x0 - a * y0;
  1355. return new this(a * r, -b * r, b * r, a * r, c * r + x0p, f * r + y0p);
  1356. },
  1357. /**
  1358. * @static
  1359. * Return the affine matrix that transform two points (x0, y0) and (x1, y1) to (x0p, y0p) and (x1p, y1p)
  1360. * @param {Number} x0
  1361. * @param {Number} y0
  1362. * @param {Number} x1
  1363. * @param {Number} y1
  1364. * @param {Number} x0p
  1365. * @param {Number} y0p
  1366. * @param {Number} x1p
  1367. * @param {Number} y1p
  1368. */
  1369. createPanZoomFromTwoPair: function(x0, y0, x1, y1, x0p, y0p, x1p, y1p) {
  1370. if (arguments.length === 2) {
  1371. return this.createPanZoomFromTwoPair.apply(this, x0.concat(y0));
  1372. }
  1373. var dx = x1 - x0,
  1374. dy = y1 - y0,
  1375. cx = (x0 + x1) * 0.5,
  1376. cy = (y0 + y1) * 0.5,
  1377. dxp = x1p - x0p,
  1378. dyp = y1p - y0p,
  1379. cxp = (x0p + x1p) * 0.5,
  1380. cyp = (y0p + y1p) * 0.5,
  1381. r = dx * dx + dy * dy,
  1382. rp = dxp * dxp + dyp * dyp,
  1383. scale = Math.sqrt(rp / r);
  1384. return new this(scale, 0, 0, scale, cxp - scale * cx, cyp - scale * cy);
  1385. },
  1386. /**
  1387. * @method
  1388. * @static
  1389. * Create a flyweight to wrap the given array.
  1390. * The flyweight will directly refer the object and the elements can be changed by other methods.
  1391. *
  1392. * Do not hold the instance of flyweight matrix.
  1393. *
  1394. * @param {Array} elements
  1395. * @return {Ext.draw.Matrix}
  1396. */
  1397. fly: (function() {
  1398. var flyMatrix = null,
  1399. simplefly = function(elements) {
  1400. flyMatrix.elements = elements;
  1401. return flyMatrix;
  1402. };
  1403. return function(elements) {
  1404. if (!flyMatrix) {
  1405. flyMatrix = new Ext.draw.Matrix();
  1406. }
  1407. flyMatrix.elements = elements;
  1408. Ext.draw.Matrix.fly = simplefly;
  1409. return flyMatrix;
  1410. };
  1411. })(),
  1412. /**
  1413. * @static
  1414. * Create a matrix from `mat`. If `mat` is already a matrix, returns it.
  1415. * @param {Mixed} mat
  1416. * @return {Ext.draw.Matrix}
  1417. */
  1418. create: function(mat) {
  1419. if (mat instanceof this) {
  1420. return mat;
  1421. }
  1422. return new this(mat);
  1423. }
  1424. },
  1425. /**
  1426. * Create an affine transform matrix.
  1427. *
  1428. * @param {Number} xx Coefficient from x to x
  1429. * @param {Number} xy Coefficient from x to y
  1430. * @param {Number} yx Coefficient from y to x
  1431. * @param {Number} yy Coefficient from y to y
  1432. * @param {Number} dx Offset of x
  1433. * @param {Number} dy Offset of y
  1434. */
  1435. constructor: function(xx, xy, yx, yy, dx, dy) {
  1436. if (xx && xx.length === 6) {
  1437. this.elements = xx.slice();
  1438. } else if (xx !== undefined) {
  1439. this.elements = [
  1440. xx,
  1441. xy,
  1442. yx,
  1443. yy,
  1444. dx,
  1445. dy
  1446. ];
  1447. } else {
  1448. this.elements = [
  1449. 1,
  1450. 0,
  1451. 0,
  1452. 1,
  1453. 0,
  1454. 0
  1455. ];
  1456. }
  1457. },
  1458. /**
  1459. * Prepend a matrix onto the current.
  1460. *
  1461. * __Note:__ The given transform will come after the current one.
  1462. *
  1463. * @param {Number} xx Coefficient from x to x.
  1464. * @param {Number} xy Coefficient from x to y.
  1465. * @param {Number} yx Coefficient from y to x.
  1466. * @param {Number} yy Coefficient from y to y.
  1467. * @param {Number} dx Offset of x.
  1468. * @param {Number} dy Offset of y.
  1469. * @return {Ext.draw.Matrix} this
  1470. */
  1471. prepend: function(xx, xy, yx, yy, dx, dy) {
  1472. var elements = this.elements,
  1473. xx0 = elements[0],
  1474. xy0 = elements[1],
  1475. yx0 = elements[2],
  1476. yy0 = elements[3],
  1477. dx0 = elements[4],
  1478. dy0 = elements[5];
  1479. elements[0] = xx * xx0 + yx * xy0;
  1480. elements[1] = xy * xx0 + yy * xy0;
  1481. elements[2] = xx * yx0 + yx * yy0;
  1482. elements[3] = xy * yx0 + yy * yy0;
  1483. elements[4] = xx * dx0 + yx * dy0 + dx;
  1484. elements[5] = xy * dx0 + yy * dy0 + dy;
  1485. return this;
  1486. },
  1487. /**
  1488. * Prepend a matrix onto the current.
  1489. *
  1490. * __Note:__ The given transform will come after the current one.
  1491. * @param {Ext.draw.Matrix} matrix
  1492. * @return {Ext.draw.Matrix} this
  1493. */
  1494. prependMatrix: function(matrix) {
  1495. return this.prepend.apply(this, matrix.elements);
  1496. },
  1497. /**
  1498. * Postpend a matrix onto the current.
  1499. *
  1500. * __Note:__ The given transform will come before the current one.
  1501. *
  1502. * @param {Number} xx Coefficient from x to x.
  1503. * @param {Number} xy Coefficient from x to y.
  1504. * @param {Number} yx Coefficient from y to x.
  1505. * @param {Number} yy Coefficient from y to y.
  1506. * @param {Number} dx Offset of x.
  1507. * @param {Number} dy Offset of y.
  1508. * @return {Ext.draw.Matrix} this
  1509. */
  1510. append: function(xx, xy, yx, yy, dx, dy) {
  1511. var elements = this.elements,
  1512. xx0 = elements[0],
  1513. xy0 = elements[1],
  1514. yx0 = elements[2],
  1515. yy0 = elements[3],
  1516. dx0 = elements[4],
  1517. dy0 = elements[5];
  1518. elements[0] = xx * xx0 + xy * yx0;
  1519. elements[1] = xx * xy0 + xy * yy0;
  1520. elements[2] = yx * xx0 + yy * yx0;
  1521. elements[3] = yx * xy0 + yy * yy0;
  1522. elements[4] = dx * xx0 + dy * yx0 + dx0;
  1523. elements[5] = dx * xy0 + dy * yy0 + dy0;
  1524. return this;
  1525. },
  1526. /**
  1527. * Postpend a matrix onto the current.
  1528. *
  1529. * __Note:__ The given transform will come before the current one.
  1530. *
  1531. * @param {Ext.draw.Matrix} matrix
  1532. * @return {Ext.draw.Matrix} this
  1533. */
  1534. appendMatrix: function(matrix) {
  1535. return this.append.apply(this, matrix.elements);
  1536. },
  1537. /**
  1538. * Set the elements of a Matrix
  1539. * @param {Number} xx
  1540. * @param {Number} xy
  1541. * @param {Number} yx
  1542. * @param {Number} yy
  1543. * @param {Number} dx
  1544. * @param {Number} dy
  1545. * @return {Ext.draw.Matrix} this
  1546. */
  1547. set: function(xx, xy, yx, yy, dx, dy) {
  1548. var elements = this.elements;
  1549. elements[0] = xx;
  1550. elements[1] = xy;
  1551. elements[2] = yx;
  1552. elements[3] = yy;
  1553. elements[4] = dx;
  1554. elements[5] = dy;
  1555. return this;
  1556. },
  1557. /**
  1558. * Return a new matrix represents the opposite transformation of the current one.
  1559. *
  1560. * @param {Ext.draw.Matrix} [target] A target matrix. If present, it will receive
  1561. * the result of inversion to avoid creating a new object.
  1562. *
  1563. * @return {Ext.draw.Matrix}
  1564. */
  1565. inverse: function(target) {
  1566. var elements = this.elements,
  1567. a = elements[0],
  1568. b = elements[1],
  1569. c = elements[2],
  1570. d = elements[3],
  1571. e = elements[4],
  1572. f = elements[5],
  1573. rDim = 1 / (a * d - b * c);
  1574. a *= rDim;
  1575. b *= rDim;
  1576. c *= rDim;
  1577. d *= rDim;
  1578. if (target) {
  1579. target.set(d, -b, -c, a, c * f - d * e, b * e - a * f);
  1580. return target;
  1581. } else {
  1582. return new Ext.draw.Matrix(d, -b, -c, a, c * f - d * e, b * e - a * f);
  1583. }
  1584. },
  1585. /**
  1586. * Translate the matrix.
  1587. *
  1588. * @param {Number} x
  1589. * @param {Number} y
  1590. * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
  1591. * @return {Ext.draw.Matrix} this
  1592. */
  1593. translate: function(x, y, prepend) {
  1594. if (prepend) {
  1595. return this.prepend(1, 0, 0, 1, x, y);
  1596. } else {
  1597. return this.append(1, 0, 0, 1, x, y);
  1598. }
  1599. },
  1600. /**
  1601. * Scale the matrix.
  1602. *
  1603. * @param {Number} sx
  1604. * @param {Number} sy
  1605. * @param {Number} scx
  1606. * @param {Number} scy
  1607. * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
  1608. * @return {Ext.draw.Matrix} this
  1609. */
  1610. scale: function(sx, sy, scx, scy, prepend) {
  1611. var me = this;
  1612. // null or undefined
  1613. if (sy == null) {
  1614. sy = sx;
  1615. }
  1616. if (scx === undefined) {
  1617. scx = 0;
  1618. }
  1619. if (scy === undefined) {
  1620. scy = 0;
  1621. }
  1622. if (prepend) {
  1623. return me.prepend(sx, 0, 0, sy, scx - scx * sx, scy - scy * sy);
  1624. } else {
  1625. return me.append(sx, 0, 0, sy, scx - scx * sx, scy - scy * sy);
  1626. }
  1627. },
  1628. /**
  1629. * Rotate the matrix.
  1630. *
  1631. * @param {Number} angle Radians to rotate
  1632. * @param {Number|null} rcx Center of rotation.
  1633. * @param {Number|null} rcy Center of rotation.
  1634. * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
  1635. * @return {Ext.draw.Matrix} this
  1636. */
  1637. rotate: function(angle, rcx, rcy, prepend) {
  1638. var me = this,
  1639. cos = Math.cos(angle),
  1640. sin = Math.sin(angle);
  1641. rcx = rcx || 0;
  1642. rcy = rcy || 0;
  1643. if (prepend) {
  1644. return me.prepend(cos, sin, -sin, cos, rcx - cos * rcx + rcy * sin, rcy - cos * rcy - rcx * sin);
  1645. } else {
  1646. return me.append(cos, sin, -sin, cos, rcx - cos * rcx + rcy * sin, rcy - cos * rcy - rcx * sin);
  1647. }
  1648. },
  1649. /**
  1650. * Rotate the matrix by the angle of a vector.
  1651. *
  1652. * @param {Number} x
  1653. * @param {Number} y
  1654. * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
  1655. * @return {Ext.draw.Matrix} this
  1656. */
  1657. rotateFromVector: function(x, y, prepend) {
  1658. var me = this,
  1659. d = Math.sqrt(x * x + y * y),
  1660. cos = x / d,
  1661. sin = y / d;
  1662. if (prepend) {
  1663. return me.prepend(cos, sin, -sin, cos, 0, 0);
  1664. } else {
  1665. return me.append(cos, sin, -sin, cos, 0, 0);
  1666. }
  1667. },
  1668. /**
  1669. * Clone this matrix.
  1670. * @return {Ext.draw.Matrix}
  1671. */
  1672. clone: function() {
  1673. return new Ext.draw.Matrix(this.elements);
  1674. },
  1675. /**
  1676. * Horizontally flip the matrix
  1677. * @return {Ext.draw.Matrix} this
  1678. */
  1679. flipX: function() {
  1680. return this.append(-1, 0, 0, 1, 0, 0);
  1681. },
  1682. /**
  1683. * Vertically flip the matrix
  1684. * @return {Ext.draw.Matrix} this
  1685. */
  1686. flipY: function() {
  1687. return this.append(1, 0, 0, -1, 0, 0);
  1688. },
  1689. /**
  1690. * Skew the matrix
  1691. * @param {Number} angle
  1692. * @return {Ext.draw.Matrix} this
  1693. */
  1694. skewX: function(angle) {
  1695. return this.append(1, 0, Math.tan(angle), 1, 0, 0);
  1696. },
  1697. /**
  1698. * Skew the matrix
  1699. * @param {Number} angle
  1700. * @return {Ext.draw.Matrix} this
  1701. */
  1702. skewY: function(angle) {
  1703. return this.append(1, Math.tan(angle), 0, 1, 0, 0);
  1704. },
  1705. /**
  1706. * Shear the matrix along the x-axis.
  1707. * @param factor The horizontal shear factor.
  1708. * @return {Ext.draw.Matrix} this
  1709. */
  1710. shearX: function(factor) {
  1711. return this.append(1, 0, factor, 1, 0, 0);
  1712. },
  1713. /**
  1714. * Shear the matrix along the y-axis.
  1715. * @param factor The vertical shear factor.
  1716. * @return {Ext.draw.Matrix} this
  1717. */
  1718. shearY: function(factor) {
  1719. return this.append(1, factor, 0, 1, 0, 0);
  1720. },
  1721. /**
  1722. * Reset the matrix to identical.
  1723. * @return {Ext.draw.Matrix} this
  1724. */
  1725. reset: function() {
  1726. return this.set(1, 0, 0, 1, 0, 0);
  1727. },
  1728. /**
  1729. * @private
  1730. * Split Matrix to `{{devicePixelRatio,c,0},{b,devicePixelRatio,0},{0,0,1}}.{{xx,0,dx},{0,yy,dy},{0,0,1}}`
  1731. * @return {Object} Object with b,c,d=devicePixelRatio,xx,yy,dx,dy
  1732. */
  1733. precisionCompensate: function(devicePixelRatio, comp) {
  1734. var elements = this.elements,
  1735. x2x = elements[0],
  1736. x2y = elements[1],
  1737. y2x = elements[2],
  1738. y2y = elements[3],
  1739. newDx = elements[4],
  1740. newDy = elements[5],
  1741. r = x2y * y2x - x2x * y2y;
  1742. comp.b = devicePixelRatio * x2y / x2x;
  1743. comp.c = devicePixelRatio * y2x / y2y;
  1744. comp.d = devicePixelRatio;
  1745. comp.xx = x2x / devicePixelRatio;
  1746. comp.yy = y2y / devicePixelRatio;
  1747. comp.dx = (newDy * x2x * y2x - newDx * x2x * y2y) / r / devicePixelRatio;
  1748. comp.dy = (newDx * x2y * y2y - newDy * x2x * y2y) / r / devicePixelRatio;
  1749. },
  1750. /**
  1751. * @private
  1752. * Split Matrix to `{{1,c,0},{b,d,0},{0,0,1}}.{{xx,0,dx},{0,xx,dy},{0,0,1}}`
  1753. * @return {Object} Object with b,c,d,xx,yy=xx,dx,dy
  1754. */
  1755. precisionCompensateRect: function(devicePixelRatio, comp) {
  1756. var elements = this.elements,
  1757. x2x = elements[0],
  1758. x2y = elements[1],
  1759. y2x = elements[2],
  1760. y2y = elements[3],
  1761. newDx = elements[4],
  1762. newDy = elements[5],
  1763. yxOnXx = y2x / x2x;
  1764. comp.b = devicePixelRatio * x2y / x2x;
  1765. comp.c = devicePixelRatio * yxOnXx;
  1766. comp.d = devicePixelRatio * y2y / x2x;
  1767. comp.xx = x2x / devicePixelRatio;
  1768. comp.yy = x2x / devicePixelRatio;
  1769. comp.dx = (newDy * y2x - newDx * y2y) / (x2y * yxOnXx - y2y) / devicePixelRatio;
  1770. comp.dy = -(newDy * x2x - newDx * x2y) / (x2y * yxOnXx - y2y) / devicePixelRatio;
  1771. },
  1772. /**
  1773. * Transform point returning the x component of the result.
  1774. * @param {Number} x
  1775. * @param {Number} y
  1776. * @return {Number} x component of the result.
  1777. */
  1778. x: function(x, y) {
  1779. var elements = this.elements;
  1780. return x * elements[0] + y * elements[2] + elements[4];
  1781. },
  1782. /**
  1783. * Transform point returning the y component of the result.
  1784. * @param {Number} x
  1785. * @param {Number} y
  1786. * @return {Number} y component of the result.
  1787. */
  1788. y: function(x, y) {
  1789. var elements = this.elements;
  1790. return x * elements[1] + y * elements[3] + elements[5];
  1791. },
  1792. /**
  1793. * @private
  1794. * @param {Number} i
  1795. * @param {Number} j
  1796. * @return {String}
  1797. */
  1798. get: function(i, j) {
  1799. return +this.elements[i + j * 2].toFixed(4);
  1800. },
  1801. /**
  1802. * Transform a point to a new array.
  1803. * @param {Array} point
  1804. * @return {Array}
  1805. */
  1806. transformPoint: function(point) {
  1807. var elements = this.elements,
  1808. x, y;
  1809. if (point.isPoint) {
  1810. x = point.x;
  1811. y = point.y;
  1812. } else {
  1813. x = point[0];
  1814. y = point[1];
  1815. }
  1816. return [
  1817. x * elements[0] + y * elements[2] + elements[4],
  1818. x * elements[1] + y * elements[3] + elements[5]
  1819. ];
  1820. },
  1821. /**
  1822. * @param {Object} bbox Given as `{x: Number, y: Number, width: Number, height: Number}`.
  1823. * @param {Number} [radius]
  1824. * @param {Object} [target] Optional target object to recieve the result.
  1825. * Recommended to use it for better gc.
  1826. *
  1827. * @return {Object} Object with x, y, width and height.
  1828. */
  1829. transformBBox: function(bbox, radius, target) {
  1830. var elements = this.elements,
  1831. l = bbox.x,
  1832. t = bbox.y,
  1833. w0 = bbox.width * 0.5,
  1834. h0 = bbox.height * 0.5,
  1835. xx = elements[0],
  1836. xy = elements[1],
  1837. yx = elements[2],
  1838. yy = elements[3],
  1839. cx = l + w0,
  1840. cy = t + h0,
  1841. w, h, scales;
  1842. if (radius) {
  1843. w0 -= radius;
  1844. h0 -= radius;
  1845. scales = [
  1846. Math.sqrt(elements[0] * elements[0] + elements[2] * elements[2]),
  1847. Math.sqrt(elements[1] * elements[1] + elements[3] * elements[3])
  1848. ];
  1849. w = Math.abs(w0 * xx) + Math.abs(h0 * yx) + Math.abs(scales[0] * radius);
  1850. h = Math.abs(w0 * xy) + Math.abs(h0 * yy) + Math.abs(scales[1] * radius);
  1851. } else {
  1852. w = Math.abs(w0 * xx) + Math.abs(h0 * yx);
  1853. h = Math.abs(w0 * xy) + Math.abs(h0 * yy);
  1854. }
  1855. if (!target) {
  1856. target = {};
  1857. }
  1858. target.x = cx * xx + cy * yx + elements[4] - w;
  1859. target.y = cx * xy + cy * yy + elements[5] - h;
  1860. target.width = w + w;
  1861. target.height = h + h;
  1862. return target;
  1863. },
  1864. /**
  1865. * Transform a list for points.
  1866. *
  1867. * __Note:__ will change the original list but not points inside it.
  1868. * @param {Array} list
  1869. * @return {Array} list
  1870. */
  1871. transformList: function(list) {
  1872. var elements = this.elements,
  1873. xx = elements[0],
  1874. yx = elements[2],
  1875. dx = elements[4],
  1876. xy = elements[1],
  1877. yy = elements[3],
  1878. dy = elements[5],
  1879. ln = list.length,
  1880. p, i;
  1881. for (i = 0; i < ln; i++) {
  1882. p = list[i];
  1883. list[i] = [
  1884. p[0] * xx + p[1] * yx + dx,
  1885. p[0] * xy + p[1] * yy + dy
  1886. ];
  1887. }
  1888. return list;
  1889. },
  1890. /**
  1891. * Determines whether this matrix is an identity matrix (no transform).
  1892. * @return {Boolean}
  1893. */
  1894. isIdentity: function() {
  1895. var elements = this.elements;
  1896. return elements[0] === 1 && elements[1] === 0 && elements[2] === 0 && elements[3] === 1 && elements[4] === 0 && elements[5] === 0;
  1897. },
  1898. /**
  1899. * Determines if this matrix has the same values as another matrix.
  1900. * @param {Ext.draw.Matrix} matrix A maxtrix or array of its elements.
  1901. * @return {Boolean}
  1902. */
  1903. isEqual: function(matrix) {
  1904. var elements = matrix && matrix.isMatrix ? matrix.elements : matrix,
  1905. myElements = this.elements;
  1906. 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];
  1907. },
  1908. /**
  1909. * @deprecated 6.0.1 This method is deprecated.
  1910. * Determines if this matrix has the same values as another matrix.
  1911. * @param {Ext.draw.Matrix} matrix
  1912. * @return {Boolean}
  1913. */
  1914. equals: function(matrix) {
  1915. return this.isEqual(matrix);
  1916. },
  1917. /**
  1918. * Create an array of elements by horizontal order (xx,yx,dx,yx,yy,dy).
  1919. * @return {Array}
  1920. */
  1921. toArray: function() {
  1922. var elements = this.elements;
  1923. return [
  1924. elements[0],
  1925. elements[2],
  1926. elements[4],
  1927. elements[1],
  1928. elements[3],
  1929. elements[5]
  1930. ];
  1931. },
  1932. /**
  1933. * Create an array of elements by vertical order (xx,xy,yx,yy,dx,dy).
  1934. * @return {Array|String}
  1935. */
  1936. toVerticalArray: function() {
  1937. return this.elements.slice();
  1938. },
  1939. /**
  1940. * Get an array of elements.
  1941. * The numbers are rounded to keep only 4 decimals.
  1942. * @return {Array}
  1943. */
  1944. toString: function() {
  1945. var me = this;
  1946. return [
  1947. me.get(0, 0),
  1948. me.get(0, 1),
  1949. me.get(1, 0),
  1950. me.get(1, 1),
  1951. me.get(2, 0),
  1952. me.get(2, 1)
  1953. ].join(',');
  1954. },
  1955. /**
  1956. * Apply the matrix to a drawing context.
  1957. * @param {Object} ctx
  1958. * @return {Ext.draw.Matrix} this
  1959. */
  1960. toContext: function(ctx) {
  1961. ctx.transform.apply(ctx, this.elements);
  1962. return this;
  1963. },
  1964. /**
  1965. * Return a string that can be used as transform attribute in SVG.
  1966. * @return {String}
  1967. */
  1968. toSvg: function() {
  1969. var elements = this.elements;
  1970. // The reason why we cannot use `.join` is the `1e5` form is not accepted in svg.
  1971. 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) + ")";
  1972. },
  1973. /**
  1974. * Get the x scale of the matrix.
  1975. * @return {Number}
  1976. */
  1977. getScaleX: function() {
  1978. var elements = this.elements;
  1979. return Math.sqrt(elements[0] * elements[0] + elements[2] * elements[2]);
  1980. },
  1981. /**
  1982. * Get the y scale of the matrix.
  1983. * @return {Number}
  1984. */
  1985. getScaleY: function() {
  1986. var elements = this.elements;
  1987. return Math.sqrt(elements[1] * elements[1] + elements[3] * elements[3]);
  1988. },
  1989. /**
  1990. * Get x-to-x component of the matrix
  1991. * @return {Number}
  1992. */
  1993. getXX: function() {
  1994. return this.elements[0];
  1995. },
  1996. /**
  1997. * Get x-to-y component of the matrix.
  1998. * @return {Number}
  1999. */
  2000. getXY: function() {
  2001. return this.elements[1];
  2002. },
  2003. /**
  2004. * Get y-to-x component of the matrix.
  2005. * @return {Number}
  2006. */
  2007. getYX: function() {
  2008. return this.elements[2];
  2009. },
  2010. /**
  2011. * Get y-to-y component of the matrix.
  2012. * @return {Number}
  2013. */
  2014. getYY: function() {
  2015. return this.elements[3];
  2016. },
  2017. /**
  2018. * Get offset x component of the matrix.
  2019. * @return {Number}
  2020. */
  2021. getDX: function() {
  2022. return this.elements[4];
  2023. },
  2024. /**
  2025. * Get offset y component of the matrix.
  2026. * @return {Number}
  2027. */
  2028. getDY: function() {
  2029. return this.elements[5];
  2030. },
  2031. /**
  2032. * Splits this transformation matrix into Scale, Rotate, Translate components,
  2033. * assuming it was produced by applying transformations in that order.
  2034. * @return {Object}
  2035. */
  2036. split: function() {
  2037. var el = this.elements,
  2038. xx = el[0],
  2039. xy = el[1],
  2040. yy = el[3],
  2041. out = {
  2042. translateX: el[4],
  2043. translateY: el[5]
  2044. };
  2045. out.rotate = out.rotation = Math.atan2(xy, xx);
  2046. out.scaleX = xx / Math.cos(out.rotate);
  2047. out.scaleY = yy / xx * out.scaleX;
  2048. return out;
  2049. }
  2050. }, function() {
  2051. function registerName(properties, name, i) {
  2052. properties[name] = {
  2053. get: function() {
  2054. return this.elements[i];
  2055. },
  2056. set: function(val) {
  2057. this.elements[i] = val;
  2058. }
  2059. };
  2060. }
  2061. // Compatibility with SVGMatrix.
  2062. if (Object.defineProperties) {
  2063. var properties = {};
  2064. /**
  2065. * @property {Number} a Get x-to-x component of the matrix. Avoid using it for performance consideration.
  2066. * Use {@link #getXX} instead.
  2067. */
  2068. registerName(properties, 'a', 0);
  2069. registerName(properties, 'b', 1);
  2070. registerName(properties, 'c', 2);
  2071. registerName(properties, 'd', 3);
  2072. registerName(properties, 'e', 4);
  2073. registerName(properties, 'f', 5);
  2074. Object.defineProperties(this.prototype, properties);
  2075. }
  2076. /**
  2077. * Performs matrix multiplication. This matrix is post-multiplied by another matrix.
  2078. *
  2079. * __Note:__ The given transform will come before the current one.
  2080. *
  2081. * @method
  2082. * @param {Ext.draw.Matrix} matrix
  2083. * @return {Ext.draw.Matrix} this
  2084. */
  2085. this.prototype.multiply = this.prototype.appendMatrix;
  2086. });
  2087. /**
  2088. * @class Ext.draw.modifier.Modifier
  2089. *
  2090. * Each sprite has a stack of modifiers. The resulting attributes of sprite is
  2091. * the content of the stack top. When setting attributes to a sprite,
  2092. * changes will be pushed-down though the stack of modifiers and pop-back the
  2093. * additive changes; When modifier is triggered to change the attribute of a
  2094. * sprite, it will pop-up the changes to the top.
  2095. */
  2096. Ext.define('Ext.draw.modifier.Modifier', {
  2097. isModifier: true,
  2098. mixins: {
  2099. observable: 'Ext.mixin.Observable'
  2100. },
  2101. config: {
  2102. /**
  2103. * @private
  2104. * @cfg {Ext.draw.modifier.Modifier} lower Modifier that receives the push-down changes.
  2105. */
  2106. lower: null,
  2107. /**
  2108. * @private
  2109. * @cfg {Ext.draw.modifier.Modifier} upper Modifier that receives the pop-up changes.
  2110. */
  2111. upper: null,
  2112. /**
  2113. * @cfg {Ext.draw.sprite.Sprite} sprite The sprite to which the modifier belongs.
  2114. */
  2115. sprite: null
  2116. },
  2117. constructor: function(config) {
  2118. this.mixins.observable.constructor.call(this, config);
  2119. },
  2120. updateUpper: function(upper) {
  2121. if (upper) {
  2122. upper.setLower(this);
  2123. }
  2124. },
  2125. updateLower: function(lower) {
  2126. if (lower) {
  2127. lower.setUpper(this);
  2128. }
  2129. },
  2130. /**
  2131. * @private
  2132. * Validate attribute set before use.
  2133. *
  2134. * @param {Object} attr The attribute to be validated. Note that it may be already initialized, so do
  2135. * not override properties that have already been used.
  2136. */
  2137. prepareAttributes: function(attr) {
  2138. if (this._lower) {
  2139. this._lower.prepareAttributes(attr);
  2140. }
  2141. },
  2142. /**
  2143. * @private
  2144. * Invoked when changes need to be popped up to the top.
  2145. * @param {Object} attr The source attributes.
  2146. * @param {Object} changes The changes to be popped up.
  2147. */
  2148. popUp: function(attr, changes) {
  2149. if (this._upper) {
  2150. this._upper.popUp(attr, changes);
  2151. } else {
  2152. Ext.apply(attr, changes);
  2153. }
  2154. },
  2155. /**
  2156. * @private
  2157. *
  2158. * This method will filter out the properties from the `changes` object, if they
  2159. * have the same values as in the `attr` object (sprite's attributes).
  2160. *
  2161. * If the `receiver` object is provided, the attributes with the new values will be
  2162. * copied from the `changes` object to the `receiver` object, and the `changes`
  2163. * object will be left unchanged.
  2164. *
  2165. * The method returns the `receiver` object, if it was provided, or the `changes`
  2166. * object otherwise.
  2167. *
  2168. * The method also handles a special case when a sprite attribute that is meant to be
  2169. * animated was set to a certain value (e.g. 5), that is different from the original
  2170. * value (e.g. 3) of the attribute, and immediately set to another value again, that
  2171. * is the same as the original value (3). In this case, the attribute's current
  2172. * value is still the original value, because the attribute hasn't started animating
  2173. * yet, so a comparison against the current value is not appropriate, and the target
  2174. * value (value at the end of animation, 5) should be used for comparison instead, so
  2175. * that 3 won't be filtered out.
  2176. */
  2177. filterChanges: function(attr, changes, receiver) {
  2178. var targets = attr.targets,
  2179. name, value;
  2180. if (receiver) {
  2181. for (name in changes) {
  2182. value = changes[name];
  2183. if (value !== attr[name] || (targets && value !== targets[name])) {
  2184. receiver[name] = value;
  2185. }
  2186. }
  2187. } else {
  2188. for (name in changes) {
  2189. value = changes[name];
  2190. if (value === attr[name] && (!targets || value === targets[name])) {
  2191. delete changes[name];
  2192. }
  2193. }
  2194. }
  2195. return receiver || changes;
  2196. },
  2197. /**
  2198. * @private
  2199. * Invoked when changes need to be pushed down to the sprite.
  2200. * @param {Object} attr The source attributes.
  2201. * @param {Object} changes The changes to make. This object might be changed unexpectedly inside the method.
  2202. * @return {Mixed}
  2203. */
  2204. pushDown: function(attr, changes) {
  2205. return this._lower ? this._lower.pushDown(attr, changes) : this.filterChanges(attr, changes);
  2206. }
  2207. });
  2208. /**
  2209. * @class Ext.draw.modifier.Target
  2210. * @extends Ext.draw.modifier.Modifier
  2211. *
  2212. * This is the destination (top) modifier that has to be put at
  2213. * the top of the modifier stack.
  2214. *
  2215. * The Target modifier figures out which updaters have to be called
  2216. * for the changed set of attributes and makes the sprite and its instances (if any)
  2217. * call them.
  2218. */
  2219. Ext.define('Ext.draw.modifier.Target', {
  2220. requires: [
  2221. 'Ext.draw.Matrix'
  2222. ],
  2223. extend: 'Ext.draw.modifier.Modifier',
  2224. alias: 'modifier.target',
  2225. statics: {
  2226. /**
  2227. * @private
  2228. */
  2229. uniqueId: 0
  2230. },
  2231. prepareAttributes: function(attr) {
  2232. if (this._lower) {
  2233. this._lower.prepareAttributes(attr);
  2234. }
  2235. attr.attributeId = 'attribute-' + Ext.draw.modifier.Target.uniqueId++;
  2236. if (!attr.hasOwnProperty('canvasAttributes')) {
  2237. attr.bbox = {
  2238. plain: {
  2239. dirty: true
  2240. },
  2241. transform: {
  2242. dirty: true
  2243. }
  2244. };
  2245. attr.dirty = true;
  2246. /*
  2247. Maps updaters that have to be called to the attributes that triggered the update.
  2248. It is basically a reversed `triggers` map (see Ext.draw.sprite.AttributeDefinition),
  2249. but only for those attributes that have changed.
  2250. Pending updaters are called by the Ext.draw.sprite.Sprite.callUpdaters method.
  2251. The 'canvas' updater is a special kind of updater that is not actually a function
  2252. but a flag indicating that the attribute should be applied directly to a canvas
  2253. context.
  2254. */
  2255. attr.pendingUpdaters = {};
  2256. /*
  2257. Holds the attributes that triggered the canvas update (attr.pendingUpdaters.canvas).
  2258. Canvas attributes are applied directly to a canvas context
  2259. by the sprite.useAttributes method.
  2260. */
  2261. attr.canvasAttributes = {};
  2262. attr.matrix = new Ext.draw.Matrix();
  2263. attr.inverseMatrix = new Ext.draw.Matrix();
  2264. }
  2265. },
  2266. /**
  2267. * @private
  2268. * Applies changes to sprite/instance attributes and determines which updaters
  2269. * have to be called as a result of attributes change.
  2270. * @param {Object} attr The source attributes.
  2271. * @param {Object} changes The modifier changes.
  2272. */
  2273. applyChanges: function(attr, changes) {
  2274. Ext.apply(attr, changes);
  2275. var sprite = this.getSprite(),
  2276. pendingUpdaters = attr.pendingUpdaters,
  2277. triggers = sprite.self.def.getTriggers(),
  2278. updaters, instances, instance, name, hasChanges, canvasAttributes, i, j, ln;
  2279. for (name in changes) {
  2280. hasChanges = true;
  2281. if ((updaters = triggers[name])) {
  2282. sprite.scheduleUpdaters(attr, updaters, [
  2283. name
  2284. ]);
  2285. }
  2286. if (attr.template && changes.removeFromInstance && changes.removeFromInstance[name]) {
  2287. delete attr[name];
  2288. }
  2289. }
  2290. if (!hasChanges) {
  2291. return;
  2292. }
  2293. // This can prevent sub objects to set duplicated attributes to context.
  2294. if (pendingUpdaters.canvas) {
  2295. canvasAttributes = pendingUpdaters.canvas;
  2296. delete pendingUpdaters.canvas;
  2297. for (i = 0 , ln = canvasAttributes.length; i < ln; i++) {
  2298. name = canvasAttributes[i];
  2299. attr.canvasAttributes[name] = attr[name];
  2300. }
  2301. }
  2302. // If the attributes of an instancing sprite template are being modified here,
  2303. // then spread the pending updaters to the instances (template's children).
  2304. if (attr.hasOwnProperty('children')) {
  2305. instances = attr.children;
  2306. for (i = 0 , ln = instances.length; i < ln; i++) {
  2307. instance = instances[i];
  2308. Ext.apply(instance.pendingUpdaters, pendingUpdaters);
  2309. if (canvasAttributes) {
  2310. for (j = 0; j < canvasAttributes.length; j++) {
  2311. name = canvasAttributes[j];
  2312. instance.canvasAttributes[name] = instance[name];
  2313. }
  2314. }
  2315. sprite.callUpdaters(instance);
  2316. }
  2317. }
  2318. sprite.setDirty(true);
  2319. sprite.callUpdaters(attr);
  2320. },
  2321. popUp: function(attr, changes) {
  2322. this.applyChanges(attr, changes);
  2323. },
  2324. pushDown: function(attr, changes) {
  2325. // Modifier chain looks like this:
  2326. // sprite.modifiers.target <---> postFx <---> sprite.modifiers.animation <---> preFx
  2327. // There can be any number of postFx and preFx modifiers, the difference between them is that:
  2328. // `preFx` modifier changes are animated.
  2329. // `postFx` modifier changes are not.
  2330. // preFx modifiers include Highlight (Draw) and Callout (Charts).
  2331. // There are no postFx modifiers at the moment.
  2332. if (this._lower) {
  2333. // Without any postFx modifiers, `lower` is going to be Animation.
  2334. changes = this._lower.pushDown(attr, changes);
  2335. }
  2336. this.applyChanges(attr, changes);
  2337. return changes;
  2338. }
  2339. });
  2340. /**
  2341. * @class Ext.draw.TimingFunctions
  2342. * @singleton
  2343. *
  2344. * Singleton that provides easing functions for use in sprite animations.
  2345. */
  2346. Ext.define('Ext.draw.TimingFunctions', function() {
  2347. var pow = Math.pow,
  2348. sin = Math.sin,
  2349. cos = Math.cos,
  2350. sqrt = Math.sqrt,
  2351. pi = Math.PI,
  2352. poly = [
  2353. 'quad',
  2354. 'cube',
  2355. 'quart',
  2356. 'quint'
  2357. ],
  2358. easings = {
  2359. pow: function(p, x) {
  2360. return pow(p, x || 6);
  2361. },
  2362. expo: function(p) {
  2363. return pow(2, 8 * (p - 1));
  2364. },
  2365. circ: function(p) {
  2366. return 1 - sqrt(1 - p * p);
  2367. },
  2368. sine: function(p) {
  2369. return 1 - sin((1 - p) * pi / 2);
  2370. },
  2371. back: function(p, n) {
  2372. n = n || 1.616;
  2373. return p * p * ((n + 1) * p - n);
  2374. },
  2375. bounce: function(p) {
  2376. for (var a = 0,
  2377. b = 1; 1; a += b , b /= 2) {
  2378. if (p >= (7 - 4 * a) / 11) {
  2379. return b * b - pow((11 - 6 * a - 11 * p) / 4, 2);
  2380. }
  2381. }
  2382. },
  2383. elastic: function(p, x) {
  2384. return pow(2, 10 * --p) * cos(20 * p * pi * (x || 1) / 3);
  2385. }
  2386. },
  2387. easingsMap = {},
  2388. name, len, i;
  2389. // Create polynomial easing equations.
  2390. function createPoly(times) {
  2391. return function(p) {
  2392. return pow(p, times);
  2393. };
  2394. }
  2395. function addEasing(name, easing) {
  2396. easingsMap[name + 'In'] = function(pos) {
  2397. return easing(pos);
  2398. };
  2399. easingsMap[name + 'Out'] = function(pos) {
  2400. return 1 - easing(1 - pos);
  2401. };
  2402. easingsMap[name + 'InOut'] = function(pos) {
  2403. return (pos <= 0.5) ? easing(2 * pos) / 2 : (2 - easing(2 * (1 - pos))) / 2;
  2404. };
  2405. }
  2406. for (i = 0 , len = poly.length; i < len; ++i) {
  2407. easings[poly[i]] = createPoly(i + 2);
  2408. }
  2409. for (name in easings) {
  2410. addEasing(name, easings[name]);
  2411. }
  2412. // Add linear interpolator.
  2413. easingsMap.linear = Ext.identityFn;
  2414. // Add aliases for quad easings.
  2415. easingsMap.easeIn = easingsMap.quadIn;
  2416. easingsMap.easeOut = easingsMap.quadOut;
  2417. easingsMap.easeInOut = easingsMap.quadInOut;
  2418. return {
  2419. singleton: true,
  2420. easingMap: easingsMap
  2421. };
  2422. }, function(Cls) {
  2423. Ext.apply(Cls, Cls.easingMap);
  2424. });
  2425. /**
  2426. * @class Ext.draw.Animator
  2427. *
  2428. * Singleton class that manages the animation pool.
  2429. */
  2430. Ext.define('Ext.draw.Animator', {
  2431. uses: [
  2432. 'Ext.draw.Draw'
  2433. ],
  2434. singleton: true,
  2435. frameCallbacks: {},
  2436. frameCallbackId: 0,
  2437. scheduled: 0,
  2438. frameStartTimeOffset: Ext.now(),
  2439. animations: [],
  2440. running: false,
  2441. /**
  2442. * Cross platform `animationTime` implementation.
  2443. * @return {Number}
  2444. */
  2445. animationTime: function() {
  2446. return Ext.AnimationQueue.frameStartTime - this.frameStartTimeOffset;
  2447. },
  2448. /**
  2449. * Adds an animated object to the animation pool.
  2450. *
  2451. * @param {Object} animation The animation descriptor to add to the pool.
  2452. */
  2453. add: function(animation) {
  2454. var me = this;
  2455. if (!me.contains(animation)) {
  2456. me.animations.push(animation);
  2457. me.ignite();
  2458. if ('fireEvent' in animation) {
  2459. animation.fireEvent('animationstart', animation);
  2460. }
  2461. }
  2462. },
  2463. /**
  2464. * Removes an animation from the pool.
  2465. * TODO: This is broken when called within `step` method.
  2466. * @param {Object} animation The animation to remove from the pool.
  2467. */
  2468. remove: function(animation) {
  2469. var me = this,
  2470. animations = me.animations,
  2471. i = 0,
  2472. l = animations.length;
  2473. for (; i < l; ++i) {
  2474. if (animations[i] === animation) {
  2475. animations.splice(i, 1);
  2476. if ('fireEvent' in animation) {
  2477. animation.fireEvent('animationend', animation);
  2478. }
  2479. return;
  2480. }
  2481. }
  2482. },
  2483. /**
  2484. * Returns `true` or `false` whether it contains the given animation or not.
  2485. *
  2486. * @param {Object} animation The animation to check for.
  2487. * @return {Boolean}
  2488. */
  2489. contains: function(animation) {
  2490. return Ext.Array.indexOf(this.animations, animation) > -1;
  2491. },
  2492. /**
  2493. * Returns `true` or `false` whether the pool is empty or not.
  2494. * @return {Boolean}
  2495. */
  2496. empty: function() {
  2497. return this.animations.length === 0;
  2498. },
  2499. idle: function() {
  2500. return this.scheduled === 0 && this.animations.length === 0;
  2501. },
  2502. /**
  2503. * Given a frame time it will filter out finished animations from the pool.
  2504. *
  2505. * @param {Number} frameTime The frame's start time, in milliseconds.
  2506. */
  2507. step: function(frameTime) {
  2508. var me = this,
  2509. animations = me.animations,
  2510. animation,
  2511. i = 0,
  2512. ln = animations.length;
  2513. for (; i < ln; i++) {
  2514. animation = animations[i];
  2515. animation.step(frameTime);
  2516. if (!animation.animating) {
  2517. animations.splice(i, 1);
  2518. i--;
  2519. ln--;
  2520. if (animation.fireEvent) {
  2521. animation.fireEvent('animationend', animation);
  2522. }
  2523. }
  2524. }
  2525. },
  2526. /**
  2527. * Register a one-time callback that will be called at the next frame.
  2528. * @param {Function/String} callback
  2529. * @param {Object} scope
  2530. * @return {String} The ID of the scheduled callback.
  2531. */
  2532. schedule: function(callback, scope) {
  2533. scope = scope || this;
  2534. var id = 'frameCallback' + (this.frameCallbackId++);
  2535. if (Ext.isString(callback)) {
  2536. callback = scope[callback];
  2537. }
  2538. Ext.draw.Animator.frameCallbacks[id] = {
  2539. fn: callback,
  2540. scope: scope,
  2541. once: true
  2542. };
  2543. this.scheduled++;
  2544. Ext.draw.Animator.ignite();
  2545. return id;
  2546. },
  2547. /**
  2548. * Register a one-time callback that will be called at the next frame,
  2549. * if that callback (with a matching function and scope) isn't already scheduled.
  2550. * @param {Function/String} callback
  2551. * @param {Object} scope
  2552. * @return {String/null} The ID of the scheduled callback or null, if that callback has already been scheduled.
  2553. */
  2554. scheduleIf: function(callback, scope) {
  2555. scope = scope || this;
  2556. var frameCallbacks = Ext.draw.Animator.frameCallbacks,
  2557. cb, id;
  2558. if (Ext.isString(callback)) {
  2559. callback = scope[callback];
  2560. }
  2561. for (id in frameCallbacks) {
  2562. cb = frameCallbacks[id];
  2563. if (cb.once && cb.fn === callback && cb.scope === scope) {
  2564. return null;
  2565. }
  2566. }
  2567. return this.schedule(callback, scope);
  2568. },
  2569. /**
  2570. * Cancel a registered one-time callback
  2571. * @param {String} id
  2572. */
  2573. cancel: function(id) {
  2574. if (Ext.draw.Animator.frameCallbacks[id] && Ext.draw.Animator.frameCallbacks[id].once) {
  2575. this.scheduled = Math.max(--this.scheduled, 0);
  2576. delete Ext.draw.Animator.frameCallbacks[id];
  2577. Ext.draw.Draw.endUpdateIOS();
  2578. }
  2579. if (this.idle()) {
  2580. this.extinguish();
  2581. }
  2582. },
  2583. clear: function() {
  2584. this.animations.length = 0;
  2585. Ext.draw.Animator.frameCallbacks = {};
  2586. this.extinguish();
  2587. },
  2588. /**
  2589. * Register a recursive callback that will be called at every frame.
  2590. *
  2591. * @param {Function} callback
  2592. * @param {Object} scope
  2593. * @return {String}
  2594. */
  2595. addFrameCallback: function(callback, scope) {
  2596. scope = scope || this;
  2597. if (Ext.isString(callback)) {
  2598. callback = scope[callback];
  2599. }
  2600. var id = 'frameCallback' + (this.frameCallbackId++);
  2601. Ext.draw.Animator.frameCallbacks[id] = {
  2602. fn: callback,
  2603. scope: scope
  2604. };
  2605. return id;
  2606. },
  2607. /**
  2608. * Unregister a recursive callback.
  2609. * @param {String} id
  2610. */
  2611. removeFrameCallback: function(id) {
  2612. delete Ext.draw.Animator.frameCallbacks[id];
  2613. if (this.idle()) {
  2614. this.extinguish();
  2615. }
  2616. },
  2617. /**
  2618. * @private
  2619. */
  2620. fireFrameCallbacks: function() {
  2621. var callbacks = this.frameCallbacks,
  2622. id, fn, cb;
  2623. for (id in callbacks) {
  2624. cb = callbacks[id];
  2625. fn = cb.fn;
  2626. if (Ext.isString(fn)) {
  2627. fn = cb.scope[fn];
  2628. }
  2629. fn.call(cb.scope);
  2630. if (callbacks[id] && cb.once) {
  2631. this.scheduled = Math.max(--this.scheduled, 0);
  2632. delete callbacks[id];
  2633. }
  2634. }
  2635. },
  2636. handleFrame: function() {
  2637. var me = this;
  2638. me.step(me.animationTime());
  2639. me.fireFrameCallbacks();
  2640. if (me.idle()) {
  2641. me.extinguish();
  2642. }
  2643. },
  2644. ignite: function() {
  2645. if (!this.running) {
  2646. this.running = true;
  2647. Ext.AnimationQueue.start(this.handleFrame, this);
  2648. Ext.draw.Draw.beginUpdateIOS();
  2649. }
  2650. },
  2651. extinguish: function() {
  2652. this.running = false;
  2653. Ext.AnimationQueue.stop(this.handleFrame, this);
  2654. Ext.draw.Draw.endUpdateIOS();
  2655. }
  2656. });
  2657. /**
  2658. * The Animation modifier.
  2659. *
  2660. * Sencha Charts allow users to use transitional animation on sprites. Simply set the duration
  2661. * and easing in the animation modifier, then all the changes to the sprites will be animated.
  2662. *
  2663. * @example
  2664. * var drawCt = Ext.create({
  2665. * xtype: 'draw',
  2666. * renderTo: document.body,
  2667. * width: 400,
  2668. * height: 400,
  2669. * sprites: [{
  2670. * type: 'rect',
  2671. * x: 50,
  2672. * y: 50,
  2673. * width: 100,
  2674. * height: 100,
  2675. * fillStyle: '#1F6D91'
  2676. * }]
  2677. * });
  2678. *
  2679. * var rect = drawCt.getSurface().getItems()[0];
  2680. *
  2681. * rect.setAnimation({
  2682. * duration: 1000,
  2683. * easing: 'elasticOut'
  2684. * });
  2685. *
  2686. * Ext.defer(function () {
  2687. * rect.setAttributes({
  2688. * width: 250
  2689. * });
  2690. * }, 500);
  2691. *
  2692. * Also, you can use different durations and easing functions on different attributes by using
  2693. * {@link #customDurations} and {@link #customEasings}.
  2694. *
  2695. * By default, an animation modifier will be created during the initialization of a sprite.
  2696. * You can get the animation modifier of a sprite via its
  2697. * {@link Ext.draw.sprite.Sprite#method-getAnimation getAnimation} method.
  2698. */
  2699. Ext.define('Ext.draw.modifier.Animation', {
  2700. extend: 'Ext.draw.modifier.Modifier',
  2701. alias: 'modifier.animation',
  2702. requires: [
  2703. 'Ext.draw.TimingFunctions',
  2704. 'Ext.draw.Animator'
  2705. ],
  2706. config: {
  2707. /**
  2708. * @cfg {Function} easing
  2709. * Default easing function.
  2710. */
  2711. easing: Ext.identityFn,
  2712. /**
  2713. * @cfg {Number} duration
  2714. * Default duration time (ms).
  2715. */
  2716. duration: 0,
  2717. /**
  2718. * @cfg {Object} customEasings Overrides the default easing function for defined attributes. E.g.:
  2719. *
  2720. * // Assuming the sprite the modifier is applied to is a 'circle'.
  2721. * customEasings: {
  2722. * r: 'easeOut',
  2723. * 'fillStyle,strokeStyle': 'linear',
  2724. * 'cx,cy': function (p, n) {
  2725. * p = 1 - p;
  2726. * n = n || 1.616;
  2727. * return 1 - p * p * ((n + 1) * p - n);
  2728. * }
  2729. * }
  2730. */
  2731. customEasings: {},
  2732. /**
  2733. * @cfg {Object} customDurations Overrides the default duration for defined attributes. E.g.:
  2734. *
  2735. * // Assuming the sprite the modifier is applied to is a 'circle'.
  2736. * customDurations: {
  2737. * r: 1000,
  2738. * 'fillStyle,strokeStyle': 2000,
  2739. * 'cx,cy': 1000
  2740. * }
  2741. */
  2742. customDurations: {}
  2743. },
  2744. constructor: function(config) {
  2745. var me = this;
  2746. me.anyAnimation = me.anySpecialAnimations = false;
  2747. me.animating = 0;
  2748. me.animatingPool = [];
  2749. me.callParent([
  2750. config
  2751. ]);
  2752. },
  2753. prepareAttributes: function(attr) {
  2754. if (!attr.hasOwnProperty('timers')) {
  2755. attr.animating = false;
  2756. attr.timers = {};
  2757. // The 'targets' object is used to hold the target values for the
  2758. // attributes while they are being animated from source to target values.
  2759. // The 'targets' is pushed down to the lower level modifiers,
  2760. // instead of the actual attr object, to hide the fact that the
  2761. // attributes are being animated.
  2762. attr.targets = Ext.Object.chain(attr);
  2763. attr.targets.prototype = attr;
  2764. }
  2765. if (this._lower) {
  2766. this._lower.prepareAttributes(attr.targets);
  2767. }
  2768. },
  2769. updateSprite: function(sprite) {
  2770. this.setConfig(sprite.config.animation);
  2771. },
  2772. updateDuration: function(duration) {
  2773. this.anyAnimation = duration > 0;
  2774. },
  2775. applyEasing: function(easing) {
  2776. if (typeof easing === 'string') {
  2777. easing = Ext.draw.TimingFunctions.easingMap[easing];
  2778. }
  2779. return easing;
  2780. },
  2781. applyCustomEasings: function(newEasings, oldEasings) {
  2782. oldEasings = oldEasings || {};
  2783. var any, key, attrs, easing, i, ln;
  2784. for (key in newEasings) {
  2785. any = true;
  2786. easing = newEasings[key];
  2787. attrs = key.split(',');
  2788. if (typeof easing === 'string') {
  2789. easing = Ext.draw.TimingFunctions.easingMap[easing];
  2790. }
  2791. for (i = 0 , ln = attrs.length; i < ln; i++) {
  2792. oldEasings[attrs[i]] = easing;
  2793. }
  2794. }
  2795. if (any) {
  2796. this.anySpecialAnimations = any;
  2797. }
  2798. return oldEasings;
  2799. },
  2800. /**
  2801. * Set special easings on the given attributes. E.g.:
  2802. *
  2803. * circleSprite.getAnimation().setEasingOn('r', 'elasticIn');
  2804. *
  2805. * @param {String/Array} attrs The source attribute(s).
  2806. * @param {String} easing The special easings.
  2807. */
  2808. setEasingOn: function(attrs, easing) {
  2809. attrs = Ext.Array.from(attrs).slice();
  2810. var customEasings = {},
  2811. ln = attrs.length,
  2812. i = 0;
  2813. for (; i < ln; i++) {
  2814. customEasings[attrs[i]] = easing;
  2815. }
  2816. this.setCustomEasings(customEasings);
  2817. },
  2818. /**
  2819. * Remove special easings on the given attributes.
  2820. * @param {String/Array} attrs The source attribute(s).
  2821. */
  2822. clearEasingOn: function(attrs) {
  2823. attrs = Ext.Array.from(attrs, true);
  2824. var i = 0,
  2825. ln = attrs.length;
  2826. for (; i < ln; i++) {
  2827. delete this._customEasings[attrs[i]];
  2828. }
  2829. },
  2830. applyCustomDurations: function(newDurations, oldDurations) {
  2831. oldDurations = oldDurations || {};
  2832. var any, key, duration, attrs, i, ln;
  2833. for (key in newDurations) {
  2834. any = true;
  2835. duration = newDurations[key];
  2836. attrs = key.split(',');
  2837. for (i = 0 , ln = attrs.length; i < ln; i++) {
  2838. oldDurations[attrs[i]] = duration;
  2839. }
  2840. }
  2841. if (any) {
  2842. this.anySpecialAnimations = any;
  2843. }
  2844. return oldDurations;
  2845. },
  2846. /**
  2847. * Set special duration on the given attributes. E.g.:
  2848. *
  2849. * rectSprite.getAnimation().setDurationOn('height', 2000);
  2850. *
  2851. * @param {String/Array} attrs The source attributes.
  2852. * @param {Number} duration The special duration.
  2853. */
  2854. setDurationOn: function(attrs, duration) {
  2855. attrs = Ext.Array.from(attrs).slice();
  2856. var customDurations = {},
  2857. i = 0,
  2858. ln = attrs.length;
  2859. for (; i < ln; i++) {
  2860. customDurations[attrs[i]] = duration;
  2861. }
  2862. this.setCustomDurations(customDurations);
  2863. },
  2864. /**
  2865. * Remove special easings on the given attributes.
  2866. * @param {Object} attrs The source attributes.
  2867. */
  2868. clearDurationOn: function(attrs) {
  2869. attrs = Ext.Array.from(attrs, true);
  2870. for (var i = 0,
  2871. ln = attrs.length; i < ln; i++) {
  2872. delete this._customDurations[attrs[i]];
  2873. }
  2874. },
  2875. /**
  2876. * @private
  2877. * Initializes Animator for the animation.
  2878. * @param {Object} attr The source attributes.
  2879. * @param {Boolean} animating The animating flag.
  2880. */
  2881. setAnimating: function(attr, animating) {
  2882. var me = this,
  2883. pool = me.animatingPool;
  2884. if (attr.animating !== animating) {
  2885. attr.animating = animating;
  2886. if (animating) {
  2887. pool.push(attr);
  2888. if (me.animating === 0) {
  2889. Ext.draw.Animator.add(me);
  2890. }
  2891. me.animating++;
  2892. } else {
  2893. for (var i = pool.length; i--; ) {
  2894. if (pool[i] === attr) {
  2895. pool.splice(i, 1);
  2896. }
  2897. }
  2898. me.animating = pool.length;
  2899. }
  2900. }
  2901. },
  2902. /**
  2903. * @private
  2904. * Set the attr with given easing and duration.
  2905. * @param {Object} attr The attributes collection.
  2906. * @param {Object} changes The changes that popped up from lower modifier.
  2907. * @return {Object} The changes to pop up.
  2908. */
  2909. setAttrs: function(attr, changes) {
  2910. var me = this,
  2911. timers = attr.timers,
  2912. parsers = me._sprite.self.def._animationProcessors,
  2913. defaultEasing = me._easing,
  2914. defaultDuration = me._duration,
  2915. customDurations = me._customDurations,
  2916. customEasings = me._customEasings,
  2917. anySpecial = me.anySpecialAnimations,
  2918. any = me.anyAnimation || anySpecial,
  2919. targets = attr.targets,
  2920. ignite = false,
  2921. timer, name, newValue, startValue, parser, easing, duration;
  2922. if (!any) {
  2923. // If there is no animation enabled.
  2924. // When applying changes to attributes, simply stop current animation
  2925. // and set the value.
  2926. for (name in changes) {
  2927. if (attr[name] === changes[name]) {
  2928. delete changes[name];
  2929. } else {
  2930. attr[name] = changes[name];
  2931. }
  2932. delete targets[name];
  2933. delete timers[name];
  2934. }
  2935. return changes;
  2936. } else {
  2937. // If any animation.
  2938. for (name in changes) {
  2939. newValue = changes[name];
  2940. startValue = attr[name];
  2941. if (newValue !== startValue && startValue !== undefined && startValue !== null && (parser = parsers[name])) {
  2942. // If this property is animating.
  2943. // Figure out the desired duration and easing.
  2944. easing = defaultEasing;
  2945. duration = defaultDuration;
  2946. if (anySpecial) {
  2947. // Deducing the easing function and duration
  2948. if (name in customEasings) {
  2949. easing = customEasings[name];
  2950. }
  2951. if (name in customDurations) {
  2952. duration = customDurations[name];
  2953. }
  2954. }
  2955. // Transitions betweens color and gradient or between gradients are not supported.
  2956. if (startValue && startValue.isGradient || newValue && newValue.isGradient) {
  2957. duration = 0;
  2958. }
  2959. // If the property is animating
  2960. if (duration) {
  2961. if (!timers[name]) {
  2962. timers[name] = {};
  2963. }
  2964. timer = timers[name];
  2965. timer.start = 0;
  2966. timer.easing = easing;
  2967. timer.duration = duration;
  2968. timer.compute = parser.compute;
  2969. timer.serve = parser.serve || Ext.identityFn;
  2970. timer.remove = changes.removeFromInstance && changes.removeFromInstance[name];
  2971. if (parser.parseInitial) {
  2972. var initial = parser.parseInitial(startValue, newValue);
  2973. timer.source = initial[0];
  2974. timer.target = initial[1];
  2975. } else if (parser.parse) {
  2976. timer.source = parser.parse(startValue);
  2977. timer.target = parser.parse(newValue);
  2978. } else {
  2979. timer.source = startValue;
  2980. timer.target = newValue;
  2981. }
  2982. // The animation started. Change to originalVal.
  2983. targets[name] = newValue;
  2984. delete changes[name];
  2985. ignite = true;
  2986. continue;
  2987. } else {
  2988. delete targets[name];
  2989. }
  2990. } else {
  2991. delete targets[name];
  2992. }
  2993. // If the property is not animating.
  2994. delete timers[name];
  2995. }
  2996. }
  2997. if (ignite && !attr.animating) {
  2998. me.setAnimating(attr, true);
  2999. }
  3000. return changes;
  3001. },
  3002. /**
  3003. * @private
  3004. *
  3005. * Update attributes to current value according to current animation time.
  3006. * This method will not affect the values of lower layers, but may delete a
  3007. * value from it.
  3008. * @param {Object} attr The source attributes.
  3009. * @return {Object} The changes to pop up or null.
  3010. */
  3011. updateAttributes: function(attr) {
  3012. if (!attr.animating) {
  3013. return {};
  3014. }
  3015. var changes = {},
  3016. any = false,
  3017. timers = attr.timers,
  3018. targets = attr.targets,
  3019. now = Ext.draw.Animator.animationTime(),
  3020. name, timer, delta;
  3021. // If updated in the same frame, return.
  3022. if (attr.lastUpdate === now) {
  3023. return null;
  3024. }
  3025. for (name in timers) {
  3026. timer = timers[name];
  3027. if (!timer.start) {
  3028. timer.start = now;
  3029. delta = 0;
  3030. } else {
  3031. delta = (now - timer.start) / timer.duration;
  3032. }
  3033. if (delta >= 1) {
  3034. changes[name] = targets[name];
  3035. delete targets[name];
  3036. if (timers[name].remove) {
  3037. changes.removeFromInstance = changes.removeFromInstance || {};
  3038. changes.removeFromInstance[name] = true;
  3039. }
  3040. delete timers[name];
  3041. } else {
  3042. changes[name] = timer.serve(timer.compute(timer.source, timer.target, timer.easing(delta), attr[name]));
  3043. any = true;
  3044. }
  3045. }
  3046. attr.lastUpdate = now;
  3047. this.setAnimating(attr, any);
  3048. return changes;
  3049. },
  3050. pushDown: function(attr, changes) {
  3051. changes = this.callParent([
  3052. attr.targets,
  3053. changes
  3054. ]);
  3055. return this.setAttrs(attr, changes);
  3056. },
  3057. popUp: function(attr, changes) {
  3058. attr = attr.prototype;
  3059. changes = this.setAttrs(attr, changes);
  3060. if (this._upper) {
  3061. return this._upper.popUp(attr, changes);
  3062. } else {
  3063. return Ext.apply(attr, changes);
  3064. }
  3065. },
  3066. /**
  3067. * @private
  3068. * This is called as an animated object in `Ext.draw.Animator`.
  3069. */
  3070. step: function(frameTime) {
  3071. var me = this,
  3072. pool = me.animatingPool.slice(),
  3073. ln = pool.length,
  3074. i = 0,
  3075. attr, changes;
  3076. for (; i < ln; i++) {
  3077. attr = pool[i];
  3078. changes = me.updateAttributes(attr);
  3079. if (changes && me._upper) {
  3080. me._upper.popUp(attr, changes);
  3081. }
  3082. }
  3083. },
  3084. /**
  3085. * Stop all animations affected by this modifier.
  3086. */
  3087. stop: function() {
  3088. this.step();
  3089. var me = this,
  3090. pool = me.animatingPool,
  3091. i, ln;
  3092. for (i = 0 , ln = pool.length; i < ln; i++) {
  3093. pool[i].animating = false;
  3094. }
  3095. me.animatingPool.length = 0;
  3096. me.animating = 0;
  3097. Ext.draw.Animator.remove(me);
  3098. },
  3099. destroy: function() {
  3100. Ext.draw.Animator.remove(this);
  3101. this.callParent();
  3102. }
  3103. });
  3104. /**
  3105. * @class Ext.draw.modifier.Highlight
  3106. * @extends Ext.draw.modifier.Modifier
  3107. *
  3108. * Highlight is a modifier that will override sprite attributes
  3109. * with {@link Ext.draw.modifier.Highlight#style style} attributes
  3110. * when sprite's `highlighted` attribute is true.
  3111. */
  3112. Ext.define('Ext.draw.modifier.Highlight', {
  3113. extend: 'Ext.draw.modifier.Modifier',
  3114. alias: 'modifier.highlight',
  3115. config: {
  3116. /**
  3117. * @cfg {Boolean} enabled 'true' if the highlight is applied.
  3118. */
  3119. enabled: false,
  3120. /**
  3121. * @cfg {Object} style The style attributes of the highlight modifier.
  3122. */
  3123. style: null
  3124. },
  3125. preFx: true,
  3126. applyStyle: function(style, oldStyle) {
  3127. oldStyle = oldStyle || {};
  3128. if (this.getSprite()) {
  3129. Ext.apply(oldStyle, this.getSprite().self.def.normalize(style));
  3130. } else {
  3131. Ext.apply(oldStyle, style);
  3132. }
  3133. return oldStyle;
  3134. },
  3135. prepareAttributes: function(attr) {
  3136. if (!attr.hasOwnProperty('highlightOriginal')) {
  3137. attr.highlighted = false;
  3138. attr.highlightOriginal = Ext.Object.chain(attr);
  3139. attr.highlightOriginal.prototype = attr;
  3140. // A list of attributes that should be removed from a sprite instance
  3141. // when it is unhighlighted.
  3142. attr.highlightOriginal.removeFromInstance = {};
  3143. }
  3144. if (this._lower) {
  3145. this._lower.prepareAttributes(attr.highlightOriginal);
  3146. }
  3147. },
  3148. updateSprite: function(sprite, oldSprite) {
  3149. var me = this,
  3150. style = me.getStyle(),
  3151. attributeDefinitions;
  3152. if (sprite) {
  3153. attributeDefinitions = sprite.self.def;
  3154. if (style) {
  3155. me._style = attributeDefinitions.normalize(style);
  3156. }
  3157. me.setStyle(sprite.config.highlight);
  3158. // Add highlight related attributes to sprite's attribute definition.
  3159. // This will affect all sprites of the same type, even those without
  3160. // the highlight modifier.
  3161. attributeDefinitions.setConfig({
  3162. defaults: {
  3163. highlighted: false
  3164. },
  3165. processors: {
  3166. highlighted: 'bool'
  3167. }
  3168. });
  3169. }
  3170. this.setSprite(sprite);
  3171. },
  3172. /**
  3173. * @private
  3174. * Filter out modifier changes that override highlight style or source attributes.
  3175. * @param {Object} attr The source attributes.
  3176. * @param {Object} changes The modifier changes.
  3177. * @return {*} The filtered changes.
  3178. */
  3179. filterChanges: function(attr, changes) {
  3180. var me = this,
  3181. highlightOriginal = attr.highlightOriginal,
  3182. style = me.getStyle(),
  3183. name;
  3184. if (attr.highlighted) {
  3185. for (name in changes) {
  3186. if (style.hasOwnProperty(name)) {
  3187. // If sprite is highlighted, then stash the changes
  3188. // to the `style` attributes made by lower level modifiers
  3189. // to apply them later when sprite is unhighlighted.
  3190. highlightOriginal[name] = changes[name];
  3191. delete changes[name];
  3192. }
  3193. }
  3194. }
  3195. return changes;
  3196. },
  3197. pushDown: function(attr, changes) {
  3198. var style = this.getStyle(),
  3199. highlightOriginal = attr.highlightOriginal,
  3200. removeFromInstance = highlightOriginal.removeFromInstance,
  3201. highlighted, name, tplAttr, timer;
  3202. if (changes.hasOwnProperty('highlighted')) {
  3203. highlighted = changes.highlighted;
  3204. // Hide `highlighted` and `style` from underlying modifiers.
  3205. delete changes.highlighted;
  3206. if (this._lower) {
  3207. changes = this._lower.pushDown(highlightOriginal, changes);
  3208. }
  3209. changes = this.filterChanges(attr, changes);
  3210. if (highlighted !== attr.highlighted) {
  3211. if (highlighted) {
  3212. // Switching ON.
  3213. // At this time, original should be empty.
  3214. for (name in style) {
  3215. // Remember the values of attributes to revert back to them on unhighlight.
  3216. if (name in changes) {
  3217. // Remember value set by lower level modifiers.
  3218. highlightOriginal[name] = changes[name];
  3219. } else {
  3220. // Remember the original value.
  3221. // If this is a sprite instance and it doesn't have its own
  3222. // 'name' attribute, (i.e. inherits template's attribute value)
  3223. // than we have to get the value for the 'name' attribute from
  3224. // the template's 'targets' object instead of its
  3225. // 'attr' object (which is the prototype of the instance),
  3226. // because the 'name' attribute of the template may be animating.
  3227. // Check out the prepareAttributes method of the Animation
  3228. // modifier for more details on the 'targets' object.
  3229. tplAttr = attr.template && attr.template.ownAttr;
  3230. if (tplAttr && !attr.prototype.hasOwnProperty(name)) {
  3231. removeFromInstance[name] = true;
  3232. highlightOriginal[name] = tplAttr.targets[name];
  3233. } else {
  3234. // Even if a sprite instance has its own property, it may
  3235. // still have to be removed from the instance after
  3236. // unhighlighting is done.
  3237. // Consider a situation where an instance doesn't originally
  3238. // have its own attribute (that is used for highlighting and
  3239. // unhighlighting). It will however have that attribute as
  3240. // its own when the highlight/unhighlight animation is in
  3241. // progress, until the attribute is removed from the instance
  3242. // when the unhighlighting is done.
  3243. // So in a scenario where the instance is highlighted, then
  3244. // unhighlighted (i.e. starts animating back to its original
  3245. // value) and then highlighted again before the unhighlight
  3246. // animation is done, we should still mark the attribute
  3247. // for removal from the instance, if it was our original
  3248. // intention. To tell if it was, we can check the timer
  3249. // for the attribute and see if the 'remove' flag is set.
  3250. timer = highlightOriginal.timers[name];
  3251. if (timer && timer.remove) {
  3252. removeFromInstance[name] = true;
  3253. }
  3254. highlightOriginal[name] = attr[name];
  3255. }
  3256. }
  3257. if (highlightOriginal[name] !== style[name]) {
  3258. changes[name] = style[name];
  3259. }
  3260. }
  3261. } else {
  3262. // Switching OFF.
  3263. for (name in style) {
  3264. if (!(name in changes)) {
  3265. changes[name] = highlightOriginal[name];
  3266. }
  3267. delete highlightOriginal[name];
  3268. }
  3269. changes.removeFromInstance = changes.removeFromInstance || {};
  3270. // Let the higher lever animation modifier know which attributes
  3271. // should be removed from instance when the animation is done.
  3272. Ext.apply(changes.removeFromInstance, removeFromInstance);
  3273. highlightOriginal.removeFromInstance = {};
  3274. }
  3275. changes.highlighted = highlighted;
  3276. }
  3277. } else {
  3278. if (this._lower) {
  3279. changes = this._lower.pushDown(highlightOriginal, changes);
  3280. }
  3281. changes = this.filterChanges(attr, changes);
  3282. }
  3283. return changes;
  3284. },
  3285. popUp: function(attr, changes) {
  3286. changes = this.filterChanges(attr, changes);
  3287. this.callParent([
  3288. attr,
  3289. changes
  3290. ]);
  3291. }
  3292. });
  3293. /**
  3294. * A sprite is a basic primitive from the charts package which represents a graphical
  3295. * object that can be drawn. Sprites are used extensively in the charts package to
  3296. * create the visual elements of each chart. You can also create a desired image by
  3297. * adding one or more sprites to a {@link Ext.draw.Container draw container}.
  3298. *
  3299. * The Sprite class itself is an abstract class and is not meant to be used directly.
  3300. * There are many different kinds of sprites available in the charts package that extend
  3301. * Ext.draw.sprite.Sprite. Each sprite type has various attributes that define how that
  3302. * sprite should look. For example, this is a {@link Ext.draw.sprite.Rect rect} sprite:
  3303. *
  3304. * @example
  3305. * Ext.create({
  3306. * xtype: 'draw',
  3307. * renderTo: document.body,
  3308. * width: 400,
  3309. * height: 400,
  3310. * sprites: [{
  3311. * type: 'rect',
  3312. * x: 50,
  3313. * y: 50,
  3314. * width: 100,
  3315. * height: 100,
  3316. * fillStyle: '#1F6D91'
  3317. * }]
  3318. * });
  3319. *
  3320. * By default, sprites are added to the default 'main' {@link Ext.draw.Surface surface}
  3321. * of the draw container. However, sprites may also be configured with a reference to a
  3322. * specific Ext.draw.Surface when set in the draw container's
  3323. * {@link Ext.draw.Container#cfg-sprites sprites} config. Specifying a surface
  3324. * other than 'main' will create a surface by that name if it does not already exist.
  3325. *
  3326. * @example
  3327. * Ext.create({
  3328. * xtype: 'draw',
  3329. * renderTo: document.body,
  3330. * width: 400,
  3331. * height: 400,
  3332. * sprites: [{
  3333. * type: 'rect',
  3334. * surface: 'anim', // a surface with id "anim" will be created automatically
  3335. * x: 50,
  3336. * y: 50,
  3337. * width: 100,
  3338. * height: 100,
  3339. * fillStyle: '#1F6D91'
  3340. * }]
  3341. * });
  3342. *
  3343. * The ability to have multiple surfaces is useful for performance (and battery life)
  3344. * reasons. Because changes to sprite attributes cause the whole surface (and all
  3345. * sprites in it) to re-render, it makes sense to group sprites by surface, so changes
  3346. * to one group of sprites will only trigger the surface they are in to re-render.
  3347. *
  3348. * You can add a sprite to an existing drawing by adding the sprite to a draw surface.
  3349. *
  3350. * @example
  3351. * var drawCt = Ext.create({
  3352. * xtype: 'draw',
  3353. * renderTo: document.body,
  3354. * width: 400,
  3355. * height: 400
  3356. * });
  3357. *
  3358. * // If the surface name is not specified then 'main' will be used
  3359. * var surface = drawCt.getSurface();
  3360. *
  3361. * surface.add({
  3362. * type: 'rect',
  3363. * x: 50,
  3364. * y: 50,
  3365. * width: 100,
  3366. * height: 100,
  3367. * fillStyle: '#1F6D91'
  3368. * });
  3369. *
  3370. * surface.renderFrame();
  3371. *
  3372. * **Note:** Changes to the sprites on a surface will be not be reflected in the DOM
  3373. * until you call the surface's {@link Ext.draw.Surface#method-renderFrame renderFrame}
  3374. * method. This must be done after adding, removing, or modifying sprites in order to
  3375. * see the changes on-screen.
  3376. *
  3377. * For information on configuring a sprite with an initial transformation see
  3378. * {@link #scaling}, {@link #rotation}, and {@link #translation}.
  3379. *
  3380. * For information on applying a transformation to an existing sprite see the
  3381. * Ext.draw.Matrix class.
  3382. */
  3383. Ext.define('Ext.draw.sprite.Sprite', {
  3384. alias: 'sprite.sprite',
  3385. mixins: {
  3386. observable: 'Ext.mixin.Observable'
  3387. },
  3388. requires: [
  3389. 'Ext.draw.Draw',
  3390. 'Ext.draw.gradient.Gradient',
  3391. 'Ext.draw.sprite.AttributeDefinition',
  3392. 'Ext.draw.modifier.Target',
  3393. 'Ext.draw.modifier.Animation',
  3394. 'Ext.draw.modifier.Highlight'
  3395. ],
  3396. isSprite: true,
  3397. $configStrict: false,
  3398. statics: {
  3399. defaultHitTestOptions: {
  3400. fill: true,
  3401. stroke: true
  3402. },
  3403. //<debug>
  3404. /**
  3405. * Debug rendering options:
  3406. *
  3407. * debug: {
  3408. * bbox: true, // renders the bounding box of the sprite
  3409. * xray: true // renders control points of the path (for Ext.draw.sprite.Path and descendants only)
  3410. * }
  3411. *
  3412. */
  3413. debug: false
  3414. },
  3415. //</debug>
  3416. inheritableStatics: {
  3417. def: {
  3418. processors: {
  3419. //<debug>
  3420. debug: 'default',
  3421. //</debug>
  3422. /**
  3423. * @cfg {String} [strokeStyle="none"] The color of the stroke (a CSS color value).
  3424. */
  3425. strokeStyle: "color",
  3426. /**
  3427. * @cfg {String} [fillStyle="none"] The color of the shape (a CSS color value).
  3428. */
  3429. fillStyle: "color",
  3430. /**
  3431. * @cfg {Number} [strokeOpacity=1] The opacity of the stroke. Limited from 0 to 1.
  3432. */
  3433. strokeOpacity: "limited01",
  3434. /**
  3435. * @cfg {Number} [fillOpacity=1] The opacity of the fill. Limited from 0 to 1.
  3436. */
  3437. fillOpacity: "limited01",
  3438. /**
  3439. * @cfg {Number} [lineWidth=1] The width of the line stroke.
  3440. */
  3441. lineWidth: "number",
  3442. /**
  3443. * @cfg {String} [lineCap="butt"] The style of the line caps.
  3444. */
  3445. lineCap: "enums(butt,round,square)",
  3446. /**
  3447. * @cfg {String} [lineJoin="miter"] The style of the line join.
  3448. */
  3449. lineJoin: "enums(round,bevel,miter)",
  3450. /**
  3451. * @cfg {Array} [lineDash=[]]
  3452. * An even number of non-negative numbers specifying a dash/space sequence.
  3453. * Note that while this is supported in IE8 (VML engine), the behavior is
  3454. * different from Canvas and SVG. Please refer to this document for details:
  3455. * http://msdn.microsoft.com/en-us/library/bb264085(v=vs.85).aspx
  3456. * Although IE9 and IE10 have Canvas support, the 'lineDash'
  3457. * attribute is not supported in those browsers.
  3458. */
  3459. lineDash: "data",
  3460. /**
  3461. * @cfg {Number} [lineDashOffset=0]
  3462. * A number specifying how far into the line dash sequence drawing commences.
  3463. */
  3464. lineDashOffset: "number",
  3465. /**
  3466. * @cfg {Number} [miterLimit=10]
  3467. * Sets the distance between the inner corner and the outer corner where two lines meet.
  3468. */
  3469. miterLimit: "number",
  3470. /**
  3471. * @cfg {String} [shadowColor="none"] The color of the shadow (a CSS color value).
  3472. */
  3473. shadowColor: "color",
  3474. /**
  3475. * @cfg {Number} [shadowOffsetX=0] The offset of the sprite's shadow on the x-axis.
  3476. */
  3477. shadowOffsetX: "number",
  3478. /**
  3479. * @cfg {Number} [shadowOffsetY=0] The offset of the sprite's shadow on the y-axis.
  3480. */
  3481. shadowOffsetY: "number",
  3482. /**
  3483. * @cfg {Number} [shadowBlur=0] The amount blur used on the shadow.
  3484. */
  3485. shadowBlur: "number",
  3486. /**
  3487. * @cfg {Number} [globalAlpha=1] The opacity of the sprite. Limited from 0 to 1.
  3488. */
  3489. globalAlpha: "limited01",
  3490. /**
  3491. * @cfg {String} [globalCompositeOperation=source-over]
  3492. * Indicates how source images are drawn onto a destination image.
  3493. * globalCompositeOperation attribute is not supported by the SVG and VML (excanvas) engines.
  3494. */
  3495. globalCompositeOperation: "enums(source-over,destination-over,source-in,destination-in,source-out,destination-out,source-atop,destination-atop,lighter,xor,copy)",
  3496. /**
  3497. * @cfg {Boolean} [hidden=false] Determines whether or not the sprite is hidden.
  3498. */
  3499. hidden: "bool",
  3500. /**
  3501. * @cfg {Boolean} [transformFillStroke=false]
  3502. * Determines whether the fill and stroke are affected by sprite transformations.
  3503. */
  3504. transformFillStroke: "bool",
  3505. /**
  3506. * @cfg {Number} [zIndex=0]
  3507. * The stacking order of the sprite.
  3508. */
  3509. zIndex: "number",
  3510. /**
  3511. * @cfg {Number} [translationX=0]
  3512. * The translation, position offset, of the sprite on the x-axis.
  3513. *
  3514. * **Note:** Transform configs are *always* performed in the following
  3515. * order:
  3516. *
  3517. * 1. Scaling
  3518. * 2. Rotation
  3519. * 3. Translation
  3520. *
  3521. * See also: {@link #translation} and {@link #translationY}
  3522. */
  3523. translationX: "number",
  3524. /**
  3525. * @cfg {Number} [translationY=0]
  3526. * The translation, position offset, of the sprite on the y-axis.
  3527. *
  3528. * **Note:** Transform configs are *always* performed in the following
  3529. * order:
  3530. *
  3531. * 1. Scaling
  3532. * 2. Rotation
  3533. * 3. Translation
  3534. *
  3535. * See also: {@link #translation} and {@link #translationX}
  3536. */
  3537. translationY: "number",
  3538. /**
  3539. * @cfg {Number} [rotationRads=0]
  3540. * The angle of rotation of the sprite in radians.
  3541. *
  3542. * **Note:** Transform configs are *always* performed in the following
  3543. * order:
  3544. *
  3545. * 1. Scaling
  3546. * 2. Rotation
  3547. * 3. Translation
  3548. *
  3549. * See also: {@link #rotation}, {@link #rotationCenterX}, and
  3550. * {@link #rotationCenterY}
  3551. */
  3552. rotationRads: "number",
  3553. /**
  3554. * @cfg {Number} [rotationCenterX=null]
  3555. * The central coordinate of the sprite's scale operation on the x-axis.
  3556. * Unless explicitly set, will default to the calculated center of the
  3557. * sprite along the x-axis.
  3558. *
  3559. * **Note:** Transform configs are *always* performed in the following
  3560. * order:
  3561. *
  3562. * 1. Scaling
  3563. * 2. Rotation
  3564. * 3. Translation
  3565. *
  3566. * See also: {@link #rotation}, {@link #rotationRads}, and
  3567. * {@link #rotationCenterY}
  3568. */
  3569. rotationCenterX: "number",
  3570. /**
  3571. * @cfg {Number} [rotationCenterY=null]
  3572. * The central coordinate of the sprite's rotate operation on the y-axis.
  3573. * Unless explicitly set, will default to the calculated center of the
  3574. * sprite along the y-axis.
  3575. *
  3576. * **Note:** Transform configs are *always* performed in the following
  3577. * order:
  3578. *
  3579. * 1. Scaling
  3580. * 2. Rotation
  3581. * 3. Translation
  3582. *
  3583. * See also: {@link #rotation}, {@link #rotationRads}, and
  3584. * {@link #rotationCenterX}
  3585. */
  3586. rotationCenterY: "number",
  3587. /**
  3588. * @cfg {Number} [scalingX=1] The scaling of the sprite on the x-axis.
  3589. * The number value represents a percentage by which to scale the
  3590. * sprite. **1** is equal to 100%, **2** would be 200%, etc.
  3591. *
  3592. * **Note:** Transform configs are *always* performed in the following
  3593. * order:
  3594. *
  3595. * 1. Scaling
  3596. * 2. Rotation
  3597. * 3. Translation
  3598. *
  3599. * See also: {@link #scaling}, {@link #scalingY},
  3600. * {@link #scalingCenterX}, and {@link #scalingCenterY}
  3601. */
  3602. scalingX: "number",
  3603. /**
  3604. * @cfg {Number} [scalingY=1] The scaling of the sprite on the y-axis.
  3605. * The number value represents a percentage by which to scale the
  3606. * sprite. **1** is equal to 100%, **2** would be 200%, etc.
  3607. *
  3608. * **Note:** Transform configs are *always* performed in the following
  3609. * order:
  3610. *
  3611. * 1. Scaling
  3612. * 2. Rotation
  3613. * 3. Translation
  3614. *
  3615. * See also: {@link #scaling}, {@link #scalingX},
  3616. * {@link #scalingCenterX}, and {@link #scalingCenterY}
  3617. */
  3618. scalingY: "number",
  3619. /**
  3620. * @cfg {Number} [scalingCenterX=null]
  3621. * The central coordinate of the sprite's scale operation on the x-axis.
  3622. *
  3623. * **Note:** Transform configs are *always* performed in the following
  3624. * order:
  3625. *
  3626. * 1. Scaling
  3627. * 2. Rotation
  3628. * 3. Translation
  3629. *
  3630. * See also: {@link #scaling}, {@link #scalingX},
  3631. * {@link #scalingY}, and {@link #scalingCenterY}
  3632. */
  3633. scalingCenterX: "number",
  3634. /**
  3635. * @cfg {Number} [scalingCenterY=null]
  3636. * The central coordinate of the sprite's scale operation on the y-axis.
  3637. *
  3638. * **Note:** Transform configs are *always* performed in the following
  3639. * order:
  3640. *
  3641. * 1. Scaling
  3642. * 2. Rotation
  3643. * 3. Translation
  3644. *
  3645. * See also: {@link #scaling}, {@link #scalingX},
  3646. * {@link #scalingY}, and {@link #scalingCenterX}
  3647. */
  3648. scalingCenterY: "number",
  3649. constrainGradients: "bool"
  3650. },
  3651. /**
  3652. * @cfg {Number/Object} rotation
  3653. * Applies an initial angle of rotation to the sprite. May be a number
  3654. * specifying the rotation in degrees. Or may be a config object using
  3655. * the below config options.
  3656. *
  3657. * **Note:** Rotation config options will be overridden by values set on
  3658. * the {@link #rotationRads}, {@link #rotationCenterX}, and
  3659. * {@link #rotationCenterY} configs.
  3660. *
  3661. * Ext.create({
  3662. * xtype: 'draw',
  3663. * renderTo: Ext.getBody(),
  3664. * width: 600,
  3665. * height: 400,
  3666. * sprites: [{
  3667. * type: 'rect',
  3668. * x: 50,
  3669. * y: 50,
  3670. * width: 100,
  3671. * height: 100,
  3672. * fillStyle: '#1F6D91',
  3673. * //rotation: 45
  3674. * rotation: {
  3675. * degrees: 45,
  3676. * //rads: Math.PI / 4,
  3677. * //centerX: 50,
  3678. * //centerY: 50
  3679. * }
  3680. * }]
  3681. * });
  3682. *
  3683. * **Note:** Transform configs are *always* performed in the following
  3684. * order:
  3685. *
  3686. * 1. Scaling
  3687. * 2. Rotation
  3688. * 3. Translation
  3689. *
  3690. * @cfg {Number} rotation.rads
  3691. * The angle in radians to rotate the sprite
  3692. *
  3693. * @cfg {Number} rotation.degrees
  3694. * The angle in degrees to rotate the sprite (is ignored if rads or
  3695. * {@link #rotationRads} is set
  3696. *
  3697. * @cfg {Number} rotation.centerX
  3698. * The central coordinate of the sprite's rotation on the x-axis.
  3699. * Unless explicitly set, will default to the calculated center of the
  3700. * sprite along the x-axis.
  3701. *
  3702. * @cfg {Number} rotation.centerY
  3703. * The central coordinate of the sprite's rotation on the y-axis.
  3704. * Unless explicitly set, will default to the calculated center of the
  3705. * sprite along the y-axis.
  3706. */
  3707. /**
  3708. * @cfg {Number/Object} scaling
  3709. * Applies initial scaling to the sprite. May be a number specifying
  3710. * the amount to scale both the x and y-axis. The number value
  3711. * represents a percentage by which to scale the sprite. **1** is equal
  3712. * to 100%, **2** would be 200%, etc. Or may be a config object using
  3713. * the below config options.
  3714. *
  3715. * **Note:** Scaling config options will be overridden by values set on
  3716. * the {@link #scalingX}, {@link #scalingY}, {@link #scalingCenterX},
  3717. * and {@link #scalingCenterY} configs.
  3718. *
  3719. * Ext.create({
  3720. * xtype: 'draw',
  3721. * renderTo: Ext.getBody(),
  3722. * width: 600,
  3723. * height: 400,
  3724. * sprites: [{
  3725. * type: 'rect',
  3726. * x: 50,
  3727. * y: 50,
  3728. * width: 100,
  3729. * height: 100,
  3730. * fillStyle: '#1F6D91',
  3731. * //scaling: 2,
  3732. * scaling: {
  3733. * x: 2,
  3734. * y: 2
  3735. * //centerX: 100,
  3736. * //centerY: 100
  3737. * }
  3738. * }]
  3739. * });
  3740. *
  3741. * **Note:** Transform configs are *always* performed in the following
  3742. * order:
  3743. *
  3744. * 1. Scaling
  3745. * 2. Rotation
  3746. * 3. Translation
  3747. *
  3748. * @cfg {Number} scaling.x
  3749. * The amount by which to scale the sprite along the x-axis. The number
  3750. * value represents a percentage by which to scale the sprite. **1** is
  3751. * equal to 100%, **2** would be 200%, etc.
  3752. *
  3753. * @cfg {Number} scaling.y
  3754. * The amount by which to scale the sprite along the y-axis. The number
  3755. * value represents a percentage by which to scale the sprite. **1** is
  3756. * equal to 100%, **2** would be 200%, etc.
  3757. *
  3758. * @cfg scaling.centerX
  3759. * The central coordinate of the sprite's scaling on the x-axis. Unless
  3760. * explicitly set, will default to the calculated center of the sprite
  3761. * along the x-axis.
  3762. *
  3763. * @cfg {Number} scaling.centerY
  3764. * The central coordinate of the sprite's scaling on the y-axis. Unless
  3765. * explicitly set, will default to the calculated center of the sprite
  3766. * along the y-axis.
  3767. */
  3768. /**
  3769. * @cfg {Object} translation
  3770. * Applies an initial translation, adjustment in x/y positioning, to the
  3771. * sprite.
  3772. *
  3773. * **Note:** Translation config options will be overridden by values set
  3774. * on the {@link #translationX} and {@link #translationY} configs.
  3775. *
  3776. * Ext.create({
  3777. * xtype: 'draw',
  3778. * renderTo: Ext.getBody(),
  3779. * width: 600,
  3780. * height: 400,
  3781. * sprites: [{
  3782. * type: 'rect',
  3783. * x: 50,
  3784. * y: 50,
  3785. * width: 100,
  3786. * height: 100,
  3787. * fillStyle: '#1F6D91',
  3788. * translation: {
  3789. * x: 50,
  3790. * y: 50
  3791. * }
  3792. * }]
  3793. * });
  3794. *
  3795. * **Note:** Transform configs are *always* performed in the following
  3796. * order:
  3797. *
  3798. * 1. Scaling
  3799. * 2. Rotation
  3800. * 3. Translation
  3801. *
  3802. * @cfg {Number} translation.x
  3803. * The amount to translate the sprite along the x-axis.
  3804. *
  3805. * @cfg {Number} translation.y
  3806. * The amount to translate the sprite along the y-axis.
  3807. */
  3808. aliases: {
  3809. "stroke": "strokeStyle",
  3810. "fill": "fillStyle",
  3811. "color": "fillStyle",
  3812. "stroke-width": "lineWidth",
  3813. "stroke-linecap": "lineCap",
  3814. "stroke-linejoin": "lineJoin",
  3815. "stroke-miterlimit": "miterLimit",
  3816. "text-anchor": "textAlign",
  3817. "opacity": "globalAlpha",
  3818. translateX: "translationX",
  3819. translateY: "translationY",
  3820. rotateRads: "rotationRads",
  3821. rotateCenterX: "rotationCenterX",
  3822. rotateCenterY: "rotationCenterY",
  3823. scaleX: "scalingX",
  3824. scaleY: "scalingY",
  3825. scaleCenterX: "scalingCenterX",
  3826. scaleCenterY: "scalingCenterY"
  3827. },
  3828. defaults: {
  3829. hidden: false,
  3830. zIndex: 0,
  3831. strokeStyle: "none",
  3832. fillStyle: "none",
  3833. lineWidth: 1,
  3834. lineDash: [],
  3835. lineDashOffset: 0,
  3836. lineCap: "butt",
  3837. lineJoin: "miter",
  3838. miterLimit: 10,
  3839. shadowColor: "none",
  3840. shadowOffsetX: 0,
  3841. shadowOffsetY: 0,
  3842. shadowBlur: 0,
  3843. globalAlpha: 1,
  3844. strokeOpacity: 1,
  3845. fillOpacity: 1,
  3846. transformFillStroke: false,
  3847. translationX: 0,
  3848. translationY: 0,
  3849. rotationRads: 0,
  3850. rotationCenterX: null,
  3851. rotationCenterY: null,
  3852. scalingX: 1,
  3853. scalingY: 1,
  3854. scalingCenterX: null,
  3855. scalingCenterY: null,
  3856. constrainGradients: false
  3857. },
  3858. triggers: {
  3859. zIndex: "zIndex",
  3860. globalAlpha: "canvas",
  3861. globalCompositeOperation: "canvas",
  3862. transformFillStroke: "canvas",
  3863. strokeStyle: "canvas",
  3864. fillStyle: "canvas",
  3865. strokeOpacity: "canvas",
  3866. fillOpacity: "canvas",
  3867. lineWidth: "canvas",
  3868. lineCap: "canvas",
  3869. lineJoin: "canvas",
  3870. lineDash: "canvas",
  3871. lineDashOffset: "canvas",
  3872. miterLimit: "canvas",
  3873. shadowColor: "canvas",
  3874. shadowOffsetX: "canvas",
  3875. shadowOffsetY: "canvas",
  3876. shadowBlur: "canvas",
  3877. translationX: "transform",
  3878. translationY: "transform",
  3879. rotationRads: "transform",
  3880. rotationCenterX: "transform",
  3881. rotationCenterY: "transform",
  3882. scalingX: "transform",
  3883. scalingY: "transform",
  3884. scalingCenterX: "transform",
  3885. scalingCenterY: "transform",
  3886. constrainGradients: "canvas"
  3887. },
  3888. updaters: {
  3889. // 'bbox' updater is meant to be called by subclasses when changes
  3890. // to attributes are expected to result in a change in sprite's dimensions.
  3891. bbox: 'bboxUpdater',
  3892. zIndex: function(attr) {
  3893. attr.dirtyZIndex = true;
  3894. },
  3895. transform: function(attr) {
  3896. attr.dirtyTransform = true;
  3897. attr.bbox.transform.dirty = true;
  3898. }
  3899. }
  3900. }
  3901. },
  3902. /**
  3903. * @property {Object} attr
  3904. * The visual attributes of the sprite, e.g. strokeStyle, fillStyle, lineWidth...
  3905. */
  3906. /**
  3907. * @cfg {Ext.draw.modifier.Animation} animation
  3908. * @accessor
  3909. */
  3910. config: {
  3911. /**
  3912. * @private
  3913. * @cfg {Ext.draw.Surface/Ext.draw.sprite.Instancing/Ext.draw.sprite.Composite} parent
  3914. * The immediate parent of the sprite. Not necessarily a surface.
  3915. */
  3916. parent: null,
  3917. /**
  3918. * @private
  3919. * @cfg {Ext.draw.Surface} surface
  3920. * The surface that this sprite is rendered into.
  3921. * This config is not meant to be used directly.
  3922. * Please use the {@link Ext.draw.Surface#add} method instead.
  3923. */
  3924. surface: null
  3925. },
  3926. onClassExtended: function(subClass, data) {
  3927. // The `def` here is no longer a config, but an instance
  3928. // of the AttributeDefinition class created with that config,
  3929. // which can now be retrieved from `initialConfig`.
  3930. var superclassCfg = subClass.superclass.self.def.initialConfig,
  3931. ownCfg = data.inheritableStatics && data.inheritableStatics.def,
  3932. cfg;
  3933. // If sprite defines attributes of its own, merge that with those of its parent.
  3934. if (ownCfg) {
  3935. cfg = Ext.Object.merge({}, superclassCfg, ownCfg);
  3936. subClass.def = new Ext.draw.sprite.AttributeDefinition(cfg);
  3937. delete data.inheritableStatics.def;
  3938. } else {
  3939. subClass.def = new Ext.draw.sprite.AttributeDefinition(superclassCfg);
  3940. }
  3941. subClass.def.spriteClass = subClass;
  3942. },
  3943. constructor: function(config) {
  3944. //<debug>
  3945. if (Ext.getClassName(this) === 'Ext.draw.sprite.Sprite') {
  3946. throw 'Ext.draw.sprite.Sprite is an abstract class';
  3947. }
  3948. //</debug>
  3949. var me = this,
  3950. attributeDefinition = me.self.def,
  3951. // It is important to get defaults (make sure
  3952. // 'defaults' config applier of the AttributeDefinition is called,
  3953. // since it is initialized lazily) before the attributes
  3954. // are initialized ('initializeAttributes' call).
  3955. defaults = attributeDefinition.getDefaults(),
  3956. processors = attributeDefinition.getProcessors(),
  3957. modifiers, name;
  3958. config = Ext.isObject(config) ? config : {};
  3959. me.id = config.id || Ext.id(null, 'ext-sprite-');
  3960. me.attr = {};
  3961. // Observable's constructor also calls the initConfig for us.
  3962. me.mixins.observable.constructor.apply(me, arguments);
  3963. modifiers = Ext.Array.from(config.modifiers, true);
  3964. me.createModifiers(modifiers);
  3965. me.initializeAttributes();
  3966. me.setAttributes(defaults, true);
  3967. //<debug>
  3968. for (name in config) {
  3969. if (name in processors && me['get' + name.charAt(0).toUpperCase() + name.substr(1)]) {
  3970. Ext.raise('The ' + me.$className + ' sprite has both a config and an attribute with the same name: ' + name + '.');
  3971. }
  3972. }
  3973. //</debug>
  3974. me.setAttributes(config);
  3975. },
  3976. updateSurface: function(surface, oldSurface) {
  3977. if (oldSurface) {
  3978. oldSurface.remove(this);
  3979. }
  3980. },
  3981. /**
  3982. * @private
  3983. * Current state of the sprite.
  3984. * Set to `true` if the sprite needs to be repainted.
  3985. * @cfg {Boolean} dirty
  3986. * @accessor
  3987. */
  3988. getDirty: function() {
  3989. return this.attr.dirty;
  3990. },
  3991. setDirty: function(dirty) {
  3992. // This could have been a regular attribute.
  3993. // Instead, it's a hidden one, which is initialized inside in the
  3994. // Target's modifier `prepareAttributes` method and is exposed
  3995. // as a config. The idea is to skip the modifier chain when
  3996. // we simply need to change the sprite's state and notify
  3997. // the sprite's parent.
  3998. this.attr.dirty = dirty;
  3999. if (dirty) {
  4000. var parent = this.getParent();
  4001. if (parent) {
  4002. parent.setDirty(true);
  4003. }
  4004. }
  4005. },
  4006. addModifier: function(modifier, reinitializeAttributes) {
  4007. var me = this,
  4008. mods = me.modifiers,
  4009. animation = mods.animation,
  4010. target = mods.target,
  4011. type;
  4012. if (!(modifier instanceof Ext.draw.modifier.Modifier)) {
  4013. type = typeof modifier === 'string' ? modifier : modifier.type;
  4014. if (type && !mods[type]) {
  4015. mods[type] = modifier = Ext.factory(modifier, null, null, 'modifier');
  4016. }
  4017. }
  4018. modifier.setSprite(me);
  4019. if (modifier.preFx || modifier.config && modifier.config.preFx) {
  4020. if (animation._lower) {
  4021. animation._lower.setUpper(modifier);
  4022. }
  4023. modifier.setUpper(animation);
  4024. } else {
  4025. target._lower.setUpper(modifier);
  4026. modifier.setUpper(target);
  4027. }
  4028. if (reinitializeAttributes) {
  4029. me.initializeAttributes();
  4030. }
  4031. return modifier;
  4032. },
  4033. createModifiers: function(modifiers) {
  4034. var me = this,
  4035. Modifier = Ext.draw.modifier,
  4036. animation = me.getInitialConfig().animation,
  4037. mods, i, ln;
  4038. // Create default modifiers.
  4039. me.modifiers = mods = {
  4040. target: new Modifier.Target({
  4041. sprite: me
  4042. }),
  4043. animation: new Modifier.Animation(Ext.apply({
  4044. sprite: me
  4045. }, animation))
  4046. };
  4047. // Link modifiers.
  4048. mods.animation.setUpper(mods.target);
  4049. for (i = 0 , ln = modifiers.length; i < ln; i++) {
  4050. me.addModifier(modifiers[i], false);
  4051. }
  4052. return mods;
  4053. },
  4054. /**
  4055. * Returns the current animation instance.
  4056. * return {Ext.draw.modifier.Animation} The animation modifier used to animate the
  4057. * sprite
  4058. */
  4059. getAnimation: function() {
  4060. return this.modifiers.animation;
  4061. },
  4062. /**
  4063. * Sets the animation config used by the sprite when animating the sprite's
  4064. * attributes and transformation properties.
  4065. *
  4066. * var drawCt = Ext.create({
  4067. * xtype: 'draw',
  4068. * renderTo: document.body,
  4069. * width: 400,
  4070. * height: 400,
  4071. * sprites: [{
  4072. * type: 'rect',
  4073. * x: 50,
  4074. * y: 50,
  4075. * width: 100,
  4076. * height: 100,
  4077. * fillStyle: '#1F6D91'
  4078. * }]
  4079. * });
  4080. *
  4081. * var rect = drawCt.getSurface().getItems()[0];
  4082. *
  4083. * rect.setAnimation({
  4084. * duration: 1000,
  4085. * easing: 'elasticOut'
  4086. * });
  4087. *
  4088. * Ext.defer(function () {
  4089. * rect.setAttributes({
  4090. * width: 250
  4091. * });
  4092. * }, 500);
  4093. *
  4094. * @param {Object} config The Ext.draw.modifier.Animation config for this sprite's
  4095. * animations.
  4096. */
  4097. setAnimation: function(config) {
  4098. if (!this.isConfiguring) {
  4099. this.modifiers.animation.setConfig(config || {
  4100. duration: 0
  4101. });
  4102. }
  4103. },
  4104. initializeAttributes: function() {
  4105. this.modifiers.target.prepareAttributes(this.attr);
  4106. },
  4107. /**
  4108. * @private
  4109. * Calls updaters triggered by changes to sprite attributes.
  4110. * @param attr The attributes of a sprite or its instance.
  4111. */
  4112. callUpdaters: function(attr) {
  4113. attr = attr || this.attr;
  4114. var me = this,
  4115. pendingUpdaters = attr.pendingUpdaters,
  4116. updaters = me.self.def.getUpdaters(),
  4117. any = false,
  4118. dirty = false,
  4119. flags, updater, fn;
  4120. // If updaters set sprite attributes that trigger other updaters,
  4121. // those updaters are not called right away, but wait until all current
  4122. // updaters are called (till the next do/while loop iteration).
  4123. me.callUpdaters = Ext.emptyFn;
  4124. // Hide class method from the instance.
  4125. do {
  4126. any = false;
  4127. for (updater in pendingUpdaters) {
  4128. any = true;
  4129. flags = pendingUpdaters[updater];
  4130. delete pendingUpdaters[updater];
  4131. fn = updaters[updater];
  4132. if (typeof fn === 'string') {
  4133. fn = me[fn];
  4134. }
  4135. if (fn) {
  4136. fn.call(me, attr, flags);
  4137. }
  4138. }
  4139. dirty = dirty || any;
  4140. } while (any);
  4141. delete me.callUpdaters;
  4142. // Restore class method.
  4143. if (dirty) {
  4144. me.setDirty(true);
  4145. }
  4146. },
  4147. /**
  4148. * @private
  4149. */
  4150. callUpdater: function(attr, updater, triggers) {
  4151. this.scheduleUpdater(attr, updater, triggers);
  4152. this.callUpdaters(attr);
  4153. },
  4154. /**
  4155. * @private
  4156. * Schedules specified updaters to be called.
  4157. * Updaters are called implicitly as a result of a change to sprite attributes.
  4158. * But sometimes it may be required to call an updater without setting an attribute,
  4159. * and without messing up the updater call order (by calling the updater immediately).
  4160. * For example:
  4161. *
  4162. * updaters: {
  4163. * onDataX: function (attr) {
  4164. * this.processDataX();
  4165. * // Process data Y every time data X is processed.
  4166. * // Call the onDataY updater as if changes to dataY attribute itself
  4167. * // triggered the update.
  4168. * this.scheduleUpdaters(attr, {onDataY: ['dataY']});
  4169. * // Alternatively:
  4170. * // this.scheduleUpdaters(attr, ['onDataY'], ['dataY']);
  4171. * }
  4172. * }
  4173. *
  4174. * @param {Object} attr The attributes object (not necesseraly of a sprite, but of its instance).
  4175. * @param {Object/String[]} updaters A map of updaters to be called to attributes that triggered the update.
  4176. * @param {String[]} [triggers] Attributes that triggered the update. An optional parameter.
  4177. * If used, the `updaters` parameter will be treated as an array of updaters to be called.
  4178. */
  4179. scheduleUpdaters: function(attr, updaters, triggers) {
  4180. var updater;
  4181. attr = attr || this.attr;
  4182. if (triggers) {
  4183. for (var i = 0,
  4184. ln = updaters.length; i < ln; i++) {
  4185. updater = updaters[i];
  4186. this.scheduleUpdater(attr, updater, triggers);
  4187. }
  4188. } else {
  4189. for (updater in updaters) {
  4190. triggers = updaters[updater];
  4191. this.scheduleUpdater(attr, updater, triggers);
  4192. }
  4193. }
  4194. },
  4195. /**
  4196. * @private
  4197. * @param attr {Object} The attributes object (not necesseraly of a sprite, but of its instance).
  4198. * @param updater {String} Updater to be called.
  4199. * @param {String[]} [triggers] Attributes that triggered the update.
  4200. */
  4201. scheduleUpdater: function(attr, updater, triggers) {
  4202. triggers = triggers || [];
  4203. attr = attr || this.attr;
  4204. var pendingUpdaters = attr.pendingUpdaters;
  4205. if (updater in pendingUpdaters) {
  4206. if (triggers.length) {
  4207. pendingUpdaters[updater] = Ext.Array.merge(pendingUpdaters[updater], triggers);
  4208. }
  4209. } else {
  4210. pendingUpdaters[updater] = triggers;
  4211. }
  4212. },
  4213. /**
  4214. * Set attributes of the sprite.
  4215. * By default only the attributes that have processors will be set
  4216. * and all other attributes will be filtered out as a result of the
  4217. * normalization process.
  4218. * The normalization process can be skipped. In that case all the given
  4219. * attributes will be set unprocessed. This will result in better
  4220. * performance, but might also pollute the sprite's attributes with
  4221. * unwanted attributes or attributes with invalid values, if one is not
  4222. * careful. See also {@link #setAttributesBypassingNormalization}.
  4223. * If normalization is skipped, one may also chose to avoid copying
  4224. * the given object. This may result in even better performance, but
  4225. * only in cases where most of the attributes have values that are
  4226. * different from the old values, because copying additionally checks
  4227. * if the value has changed.
  4228. *
  4229. * @param {Object} changes The content of the change.
  4230. * @param {Boolean} [bypassNormalization] `true` to avoid normalization of the given changes.
  4231. * @param {Boolean} [avoidCopy] `true` to avoid copying the `changes` object.
  4232. * `bypassNormalization` should also be `true`. The content of object may be destroyed.
  4233. */
  4234. setAttributes: function(changes, bypassNormalization, avoidCopy) {
  4235. var me = this,
  4236. changesToPush;
  4237. //<debug>
  4238. if (me.destroyed) {
  4239. Ext.Error.raise("Setting attributes of a destroyed sprite.");
  4240. }
  4241. //</debug>
  4242. if (bypassNormalization) {
  4243. if (avoidCopy) {
  4244. changesToPush = changes;
  4245. } else {
  4246. changesToPush = Ext.apply({}, changes);
  4247. }
  4248. } else {
  4249. changesToPush = me.self.def.normalize(changes);
  4250. }
  4251. me.modifiers.target.pushDown(me.attr, changesToPush);
  4252. },
  4253. /**
  4254. * Set attributes of the sprite, assuming the names and values have already been
  4255. * normalized.
  4256. *
  4257. * @deprecated 6.5.0 Use setAttributes directly with bypassNormalization argument being `true`.
  4258. * @param {Object} changes The content of the change.
  4259. * @param {Boolean} [avoidCopy] `true` to avoid copying the `changes` object.
  4260. * The content of object may be destroyed.
  4261. */
  4262. setAttributesBypassingNormalization: function(changes, avoidCopy) {
  4263. return this.setAttributes(changes, true, avoidCopy);
  4264. },
  4265. /**
  4266. * @private
  4267. */
  4268. bboxUpdater: function(attr) {
  4269. var hasRotation = attr.rotationRads !== 0,
  4270. hasScaling = attr.scalingX !== 1 || attr.scalingY !== 1,
  4271. noRotationCenter = attr.rotationCenterX === null || attr.rotationCenterY === null,
  4272. noScalingCenter = attr.scalingCenterX === null || attr.scalingCenterY === null;
  4273. // 'bbox' is not a standard attribute (in the sense that it doesn't have
  4274. // a processor = not explicitly declared and cannot be set by a user)
  4275. // and is calculated automatically by the 'getBBox' method.
  4276. // The 'bbox' attribute is created by the 'prepareAttributes' method
  4277. // of the Target modifier at construction time.
  4278. // Both plain and tranformed bounding boxes need to be updated.
  4279. // Mark them as such below.
  4280. attr.bbox.plain.dirty = true;
  4281. // updated by the 'updatePlainBBox' method
  4282. // Before transformed bounding box can be updated,
  4283. // we must ensure that we have correct forward and inverse
  4284. // transformation matrices (which are also created by the Target modifier),
  4285. // so that they reflect the current state of the scaling, rotation
  4286. // and other transformation attributes.
  4287. // The 'applyTransformations' method does just that.
  4288. // The 'dirtyTransform' flag (another implicit attribute)
  4289. // is set to true when any of the transformation attributes change,
  4290. // to let us know that transformation matrices need to be updated.
  4291. attr.bbox.transform.dirty = true;
  4292. // updated by the 'updateTransformedBBox' method
  4293. if (hasRotation && noRotationCenter || hasScaling && noScalingCenter) {
  4294. this.scheduleUpdater(attr, 'transform');
  4295. }
  4296. },
  4297. /**
  4298. * Returns the bounding box for the given Sprite as calculated with the Canvas engine.
  4299. *
  4300. * @param {Boolean} [isWithoutTransform] Whether to calculate the bounding box with the current transforms or not.
  4301. */
  4302. getBBox: function(isWithoutTransform) {
  4303. var me = this,
  4304. attr = me.attr,
  4305. bbox = attr.bbox,
  4306. plain = bbox.plain,
  4307. transform = bbox.transform;
  4308. if (plain.dirty) {
  4309. me.updatePlainBBox(plain);
  4310. plain.dirty = false;
  4311. }
  4312. if (!isWithoutTransform) {
  4313. // If tranformations are to be applied ('dirtyTransform' is true),
  4314. // then this will itself call the 'getBBox' method
  4315. // to get the plain untransformed bbox and calculate its center.
  4316. me.applyTransformations();
  4317. if (transform.dirty) {
  4318. me.updateTransformedBBox(transform, plain);
  4319. transform.dirty = false;
  4320. }
  4321. return transform;
  4322. }
  4323. return plain;
  4324. },
  4325. /**
  4326. * @method
  4327. * @protected
  4328. * Subclass will fill the plain object with `x`, `y`, `width`, `height` information
  4329. * of the plain bounding box of this sprite.
  4330. *
  4331. * @param {Object} plain Target object.
  4332. */
  4333. updatePlainBBox: Ext.emptyFn,
  4334. /**
  4335. * @protected
  4336. * Subclass will fill the plain object with `x`, `y`, `width`, `height` information
  4337. * of the transformed bounding box of this sprite.
  4338. *
  4339. * @param {Object} transform Target object (transformed bounding box) to populate.
  4340. * @param {Object} plain Untransformed bounding box.
  4341. */
  4342. updateTransformedBBox: function(transform, plain) {
  4343. this.attr.matrix.transformBBox(plain, 0, transform);
  4344. },
  4345. /**
  4346. * Subclass can rewrite this function to gain better performance.
  4347. * @param {Boolean} isWithoutTransform
  4348. * @return {Array}
  4349. */
  4350. getBBoxCenter: function(isWithoutTransform) {
  4351. var bbox = this.getBBox(isWithoutTransform);
  4352. if (bbox) {
  4353. return [
  4354. bbox.x + bbox.width * 0.5,
  4355. bbox.y + bbox.height * 0.5
  4356. ];
  4357. } else {
  4358. return [
  4359. 0,
  4360. 0
  4361. ];
  4362. }
  4363. },
  4364. /**
  4365. * Hide the sprite.
  4366. * @return {Ext.draw.sprite.Sprite} this
  4367. * @chainable
  4368. */
  4369. hide: function() {
  4370. this.attr.hidden = true;
  4371. this.setDirty(true);
  4372. return this;
  4373. },
  4374. /**
  4375. * Show the sprite.
  4376. * @return {Ext.draw.sprite.Sprite} this
  4377. * @chainable
  4378. */
  4379. show: function() {
  4380. this.attr.hidden = false;
  4381. this.setDirty(true);
  4382. return this;
  4383. },
  4384. /**
  4385. * Applies sprite's attributes to the given context.
  4386. * @param {Object} ctx Context to apply sprite's attributes to.
  4387. * @param {Array} rect The rect of the context to be affected by gradients.
  4388. */
  4389. useAttributes: function(ctx, rect) {
  4390. // Always (force) apply transformation to sprite instances,
  4391. // even if their 'dirtyTransform' flag is false.
  4392. // The 'dirtyTransform' flag of an instance may never be set to 'true', as the
  4393. // 'transform' updater won't ever be called for sprite instances that have
  4394. // the same transform attributes as their template, because there's nothing to update
  4395. // (an instance is simply a prototype chained template's 'attr' object, that only
  4396. // has own properties for attributes whose values are different).
  4397. // Making the modifier recognize transform attributes set on sprite instances
  4398. // (see Ext.draw.modifier.Modifier's 'pushDown' method, where attributes with
  4399. // same values are removed from the 'changes' object) and making sure their 'dirtyTransform'
  4400. // flag is set to 'true' is not a correct solution here, because of the way instances
  4401. // are rendered (see Ext.draw.sprite.Instancing's 'render' method) - there is no way
  4402. // an instance wounldn't want its 'applyTransformations' method called.
  4403. this.applyTransformations(this.isSpriteInstance);
  4404. var attr = this.attr,
  4405. canvasAttributes = attr.canvasAttributes,
  4406. strokeStyle = canvasAttributes.strokeStyle,
  4407. fillStyle = canvasAttributes.fillStyle,
  4408. lineDash = canvasAttributes.lineDash,
  4409. lineDashOffset = canvasAttributes.lineDashOffset,
  4410. id;
  4411. if (strokeStyle) {
  4412. if (strokeStyle.isGradient) {
  4413. ctx.strokeStyle = 'black';
  4414. ctx.strokeGradient = strokeStyle;
  4415. } else {
  4416. ctx.strokeGradient = false;
  4417. }
  4418. }
  4419. if (fillStyle) {
  4420. if (fillStyle.isGradient) {
  4421. ctx.fillStyle = 'black';
  4422. ctx.fillGradient = fillStyle;
  4423. } else {
  4424. ctx.fillGradient = false;
  4425. }
  4426. }
  4427. if (lineDash) {
  4428. ctx.setLineDash(lineDash);
  4429. }
  4430. // Only set lineDashOffset to contexts that support the property (excludes VML).
  4431. if (Ext.isNumber(lineDashOffset) && Ext.isNumber(ctx.lineDashOffset)) {
  4432. ctx.lineDashOffset = lineDashOffset;
  4433. }
  4434. for (id in canvasAttributes) {
  4435. if (canvasAttributes[id] !== undefined && canvasAttributes[id] !== ctx[id]) {
  4436. ctx[id] = canvasAttributes[id];
  4437. }
  4438. }
  4439. this.setGradientBBox(ctx, rect);
  4440. },
  4441. setGradientBBox: function(ctx, rect) {
  4442. var attr = this.attr;
  4443. if (attr.constrainGradients) {
  4444. ctx.setGradientBBox({
  4445. x: rect[0],
  4446. y: rect[1],
  4447. width: rect[2],
  4448. height: rect[3]
  4449. });
  4450. } else {
  4451. ctx.setGradientBBox(this.getBBox(attr.transformFillStroke));
  4452. }
  4453. },
  4454. /**
  4455. * @private
  4456. *
  4457. * Calculates forward and inverse transform matrices from sprite's attributes.
  4458. * Transformations are applied in the following order: Scaling, Rotation, Translation.
  4459. * @param {Boolean} [force=false] Forces recalculation of transform matrices even when
  4460. * sprite's transform attributes supposedly haven't changed.
  4461. */
  4462. applyTransformations: function(force) {
  4463. if (!force && !this.attr.dirtyTransform) {
  4464. return;
  4465. }
  4466. var me = this,
  4467. attr = me.attr,
  4468. center = me.getBBoxCenter(true),
  4469. centerX = center[0],
  4470. centerY = center[1],
  4471. tx = attr.translationX,
  4472. ty = attr.translationY,
  4473. sx = attr.scalingX,
  4474. sy = attr.scalingY === null ? attr.scalingX : attr.scalingY,
  4475. scx = attr.scalingCenterX === null ? centerX : attr.scalingCenterX,
  4476. scy = attr.scalingCenterY === null ? centerY : attr.scalingCenterY,
  4477. rad = attr.rotationRads,
  4478. rcx = attr.rotationCenterX === null ? centerX : attr.rotationCenterX,
  4479. rcy = attr.rotationCenterY === null ? centerY : attr.rotationCenterY,
  4480. cos = Math.cos(rad),
  4481. sin = Math.sin(rad),
  4482. tx_4, ty_4;
  4483. if (sx === 1 && sy === 1) {
  4484. scx = 0;
  4485. scy = 0;
  4486. }
  4487. if (rad === 0) {
  4488. rcx = 0;
  4489. rcy = 0;
  4490. }
  4491. // Translation component after steps 1-4 (see below).
  4492. // Saving it here to prevent double calculation.
  4493. tx_4 = scx * (1 - sx) - rcx;
  4494. ty_4 = scy * (1 - sy) - rcy;
  4495. // The matrix below is a result of:
  4496. // (7) (6) (5) (4) (3) (2) (1)
  4497. // | 1 0 tx | | 1 0 rcx | | cos -sin 0 | | 1 0 -rcx | | 1 0 scx | | sx 0 0 | | 1 0 -scx |
  4498. // | 0 1 ty | * | 0 1 rcy | * | sin cos 0 | * | 0 1 -rcy | * | 0 1 scy | * | 0 sy 0 | * | 0 1 -scy |
  4499. // | 0 0 1 | | 0 0 1 | | 0 0 1 | | 0 0 1 | | 0 0 1 | | 0 0 0 | | 0 0 1 |
  4500. attr.matrix.elements = [
  4501. cos * sx,
  4502. sin * sx,
  4503. -sin * sy,
  4504. cos * sy,
  4505. cos * tx_4 - sin * ty_4 + rcx + tx,
  4506. sin * tx_4 + cos * ty_4 + rcy + ty
  4507. ];
  4508. attr.matrix.inverse(attr.inverseMatrix);
  4509. attr.dirtyTransform = false;
  4510. attr.bbox.transform.dirty = true;
  4511. },
  4512. /**
  4513. * Pre-multiplies the current transformation matrix of a sprite with the given matrix.
  4514. * If `isSplit` parameter is `true`, the resulting matrix is also split into
  4515. * individual components (scaling, rotation, translation) and corresponding sprite
  4516. * attributes are updated. The shearing component is not extracted.
  4517. * Note, that transformation attributes work as if transformations are applied to the
  4518. * local coordinate system of a sprite, while matrix transformations transform
  4519. * the global coordinate space or the surface grid.
  4520. * Since the `transform` method returns the sprite itself, calls to the method
  4521. * can be chained. And if updating sprite transformation attributes is desired,
  4522. * it can be achieved by setting the `isSplit` parameter of the last call to `true`.
  4523. * For example:
  4524. *
  4525. * sprite.transform(matrixA).transform(matrixB).transform(matrixC, true);
  4526. *
  4527. * See also: {@link #setTransform}
  4528. *
  4529. * @param {Ext.draw.Matrix/Number[]} matrix A transformation matrix or array of its elements.
  4530. * @param {Boolean} [isSplit=false] If 'true', transformation attributes are updated.
  4531. * @return {Ext.draw.sprite.Sprite} This sprite.
  4532. */
  4533. transform: function(matrix, isSplit) {
  4534. var attr = this.attr,
  4535. spriteMatrix = attr.matrix,
  4536. elements;
  4537. if (matrix && matrix.isMatrix) {
  4538. elements = matrix.elements;
  4539. } else {
  4540. elements = matrix;
  4541. }
  4542. //<debug>
  4543. if (!(Ext.isArray(elements) && elements.length === 6)) {
  4544. Ext.raise("An instance of Ext.draw.Matrix or an array of 6 numbers is expected.");
  4545. }
  4546. //</debug>
  4547. spriteMatrix.prepend.apply(spriteMatrix, elements.slice());
  4548. spriteMatrix.inverse(attr.inverseMatrix);
  4549. if (isSplit) {
  4550. this.updateTransformAttributes();
  4551. }
  4552. attr.dirtyTransform = false;
  4553. attr.bbox.transform.dirty = true;
  4554. this.setDirty(true);
  4555. return this;
  4556. },
  4557. /**
  4558. * @private
  4559. */
  4560. updateTransformAttributes: function() {
  4561. var attr = this.attr,
  4562. split = attr.matrix.split();
  4563. attr.rotationRads = split.rotate;
  4564. attr.rotationCenterX = 0;
  4565. attr.rotationCenterY = 0;
  4566. attr.scalingX = split.scaleX;
  4567. attr.scalingY = split.scaleY;
  4568. attr.scalingCenterX = 0;
  4569. attr.scalingCenterY = 0;
  4570. attr.translationX = split.translateX;
  4571. attr.translationY = split.translateY;
  4572. },
  4573. /**
  4574. * Resets current transformation matrix of a sprite to the identify matrix.
  4575. * @param {Boolean} [isSplit=false] If 'true', transformation attributes are updated.
  4576. * @return {Ext.draw.sprite.Sprite} This sprite.
  4577. */
  4578. resetTransform: function(isSplit) {
  4579. var attr = this.attr;
  4580. attr.matrix.reset();
  4581. attr.inverseMatrix.reset();
  4582. if (!isSplit) {
  4583. this.updateTransformAttributes();
  4584. }
  4585. attr.dirtyTransform = false;
  4586. attr.bbox.transform.dirty = true;
  4587. this.setDirty(true);
  4588. return this;
  4589. },
  4590. /**
  4591. * Resets current transformation matrix of a sprite to the identify matrix
  4592. * and pre-multiplies it with the given matrix.
  4593. * This is effectively the same as calling {@link #resetTransform},
  4594. * followed by {@link #transform} with the same arguments.
  4595. *
  4596. * See also: {@link #transform}
  4597. *
  4598. * var drawContainer = new Ext.draw.Container({
  4599. * renderTo: Ext.getBody(),
  4600. * width: 380,
  4601. * height: 380,
  4602. * sprites: [{
  4603. * type: 'rect',
  4604. * width: 100,
  4605. * height: 100,
  4606. * fillStyle: 'red'
  4607. * }]
  4608. * });
  4609. *
  4610. * var main = drawContainer.getSurface();
  4611. * var rect = main.getItems()[0];
  4612. *
  4613. * var m = new Ext.draw.Matrix().rotate(Math.PI, 100, 100);
  4614. *
  4615. * rect.setTransform(m);
  4616. * main.renderFrame();
  4617. *
  4618. * There may be times where the transformation you need to apply cannot easily be
  4619. * accomplished using the sprite’s convenience transform methods. Or, you may want
  4620. * to pass a matrix directly to the sprite in order to set a transformation. The
  4621. * `setTransform` method allows for this sort of advanced usage as well. The
  4622. * following tables show each transformation matrix used when applying
  4623. * transformations to a sprite.
  4624. *
  4625. * ### Translate
  4626. * <table style="text-align: center;">
  4627. * <tr>
  4628. * <td style="font-weight: normal;">1</td>
  4629. * <td style="font-weight: normal;">0</td>
  4630. * <td style="font-weight: normal;">tx</td>
  4631. * </tr>
  4632. * <tr>
  4633. * <td>0</td>
  4634. * <td>1</td>
  4635. * <td>ty</td>
  4636. * </tr>
  4637. * <tr>
  4638. * <td>0</td>
  4639. * <td>0</td>
  4640. * <td>1</td>
  4641. * </tr>
  4642. * </table>
  4643. *
  4644. * ### Rotate (θ is the angle of rotation)
  4645. * <table style="text-align: center;">
  4646. * <tr>
  4647. * <td style="font-weight: normal;">cos(θ)</td>
  4648. * <td style="font-weight: normal;">-sin(θ)</td>
  4649. * <td style="font-weight: normal;">0</td>
  4650. * </tr>
  4651. * <tr>
  4652. * <td>0</td>
  4653. * <td>cos(θ)</td>
  4654. * <td>0</td>
  4655. * </tr>
  4656. * <tr>
  4657. * <td>0</td>
  4658. * <td>0</td>
  4659. * <td>1</td>
  4660. * </tr>
  4661. * </table>
  4662. *
  4663. * ### Scale
  4664. * <table style="text-align: center;">
  4665. * <tr>
  4666. * <td style="font-weight: normal;">sx</td>
  4667. * <td style="font-weight: normal;">0</td>
  4668. * <td style="font-weight: normal;">0</td>
  4669. * </tr>
  4670. * <tr>
  4671. * <td>0</td>
  4672. * <td>cos(θ)</td>
  4673. * <td>0</td>
  4674. * </tr>
  4675. * <tr>
  4676. * <td>0</td>
  4677. * <td>0</td>
  4678. * <td>1</td>
  4679. * </tr>
  4680. * </table>
  4681. *
  4682. * ### Shear X _(λ is the distance on the x axis to shear by)_
  4683. * <table style="text-align: center;">
  4684. * <tr>
  4685. * <td style="font-weight: normal;">1</td>
  4686. * <td style="font-weight: normal;">λx</td>
  4687. * <td style="font-weight: normal;">0</td>
  4688. * </tr>
  4689. * <tr>
  4690. * <td>0</td>
  4691. * <td>1</td>
  4692. * <td>0</td>
  4693. * </tr>
  4694. * <tr>
  4695. * <td>0</td>
  4696. * <td>0</td>
  4697. * <td>1</td>
  4698. * </tr>
  4699. * </table>
  4700. *
  4701. * ### Shear Y (λ is the distance on the y axis to shear by)
  4702. * <table style="text-align: center;">
  4703. * <tr>
  4704. * <td style="font-weight: normal;">1</td>
  4705. * <td style="font-weight: normal;">0</td>
  4706. * <td style="font-weight: normal;">0</td>
  4707. * </tr>
  4708. * <tr>
  4709. * <td>λy</td>
  4710. * <td>1</td>
  4711. * <td>0</td>
  4712. * </tr>
  4713. * <tr>
  4714. * <td>0</td>
  4715. * <td>0</td>
  4716. * <td>1</td>
  4717. * </tr>
  4718. * </table>
  4719. *
  4720. * ### Skew X (θ is the angle to skew by)
  4721. * <table style="text-align: center;">
  4722. * <tr>
  4723. * <td style="font-weight: normal;">1</td>
  4724. * <td style="font-weight: normal;">tan(θ)</td>
  4725. * <td style="font-weight: normal;">0</td>
  4726. * </tr>
  4727. * <tr>
  4728. * <td>0</td>
  4729. * <td>1</td>
  4730. * <td>0</td>
  4731. * </tr>
  4732. * <tr>
  4733. * <td>0</td>
  4734. * <td>0</td>
  4735. * <td>1</td>
  4736. * </tr>
  4737. * </table>
  4738. *
  4739. * ### Skew Y (θ is the angle to skew by)
  4740. * <table style="text-align: center;">
  4741. * <tr>
  4742. * <td style="font-weight: normal;">1</td>
  4743. * <td style="font-weight: normal;">0</td>
  4744. * <td style="font-weight: normal;">0</td>
  4745. * </tr>
  4746. * <tr>
  4747. * <td>tan(θ)</td>
  4748. * <td>1</td>
  4749. * <td>0</td>
  4750. * </tr>
  4751. * <tr>
  4752. * <td>0</td>
  4753. * <td>0</td>
  4754. * <td>1</td>
  4755. * </tr>
  4756. * </table>
  4757. *
  4758. * Multiplying matrices for translation, rotation, scaling, and shearing / skewing
  4759. * any number of times in the desired order produces a single matrix for a composite
  4760. * transformation. You can use the product as a value for the `setTransform`method
  4761. * of a sprite:
  4762. *
  4763. * mySprite.setTransform([a, b, c, d, e, f]);
  4764. *
  4765. * Where `a`, `b`, `c`, `d`, `e`, `f` are numeric values that correspond to the
  4766. * following transformation matrix components:
  4767. *
  4768. * <table style="text-align: center;">
  4769. * <tr>
  4770. * <td style="font-weight: normal;">a</td>
  4771. * <td style="font-weight: normal;">c</td>
  4772. * <td style="font-weight: normal;">e</td>
  4773. * </tr>
  4774. * <tr>
  4775. * <td>b</td>
  4776. * <td>d</td>
  4777. * <td>f</td>
  4778. * </tr>
  4779. * <tr>
  4780. * <td>0</td>
  4781. * <td>0</td>
  4782. * <td>1</td>
  4783. * </tr>
  4784. * </table>
  4785. *
  4786. * @param {Ext.draw.Matrix/Number[]} matrix The transformation matrix to apply or its
  4787. * raw elements as an array.
  4788. * @param {Boolean} [isSplit=false] If `true`, transformation attributes are updated.
  4789. * @return {Ext.draw.sprite.Sprite} This sprite.
  4790. */
  4791. setTransform: function(matrix, isSplit) {
  4792. this.resetTransform(true);
  4793. this.transform.call(this, matrix, isSplit);
  4794. return this;
  4795. },
  4796. /**
  4797. * @method
  4798. * Called before rendering.
  4799. */
  4800. preRender: Ext.emptyFn,
  4801. /**
  4802. * @method
  4803. * This is where the actual sprite rendering happens by calling `ctx` methods.
  4804. * @param {Ext.draw.Surface} surface A draw container surface.
  4805. * @param {CanvasRenderingContext2D} ctx A context object that is API compatible with the native
  4806. * [CanvasRenderingContext2D](https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D).
  4807. * @param {Number[]} surfaceClipRect The clip rect: [left, top, width, height].
  4808. * Not to be confused with the `surface.getRect()`, which represents the location
  4809. * and size of the surface in a draw container, in draw container coordinates.
  4810. * The clip rect on the other hand represents the portion of the surface that is being
  4811. * rendered, in surface coordinates.
  4812. *
  4813. * @return {*} returns `false` to stop rendering in this frame.
  4814. * All the sprites that haven't been rendered will have their dirty flag untouched.
  4815. */
  4816. render: Ext.emptyFn,
  4817. //<debug>
  4818. /**
  4819. * @private
  4820. * Renders the bounding box of transformed sprite.
  4821. */
  4822. renderBBox: function(surface, ctx) {
  4823. var bbox = this.getBBox();
  4824. ctx.beginPath();
  4825. ctx.moveTo(bbox.x, bbox.y);
  4826. ctx.lineTo(bbox.x + bbox.width, bbox.y);
  4827. ctx.lineTo(bbox.x + bbox.width, bbox.y + bbox.height);
  4828. ctx.lineTo(bbox.x, bbox.y + bbox.height);
  4829. ctx.closePath();
  4830. ctx.strokeStyle = 'red';
  4831. ctx.strokeOpacity = 1;
  4832. ctx.lineWidth = 0.5;
  4833. ctx.stroke();
  4834. },
  4835. //</debug>
  4836. /**
  4837. * Performs a hit test on the sprite.
  4838. * @param {Array} point A two-item array containing x and y coordinates of the point.
  4839. * @param {Object} options Hit testing options.
  4840. * @return {Object} A hit result object that contains more information about what
  4841. * exactly was hit or null if nothing was hit.
  4842. */
  4843. hitTest: function(point, options) {
  4844. // Meant to be overridden in subclasses for more precise hit testing.
  4845. // This version doesn't take any options and simply hit tests sprite's
  4846. // bounding box, if the sprite is visible.
  4847. if (this.isVisible()) {
  4848. var x = point[0],
  4849. y = point[1],
  4850. bbox = this.getBBox(),
  4851. isBBoxHit = bbox && x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height);
  4852. if (isBBoxHit) {
  4853. return {
  4854. sprite: this
  4855. };
  4856. }
  4857. }
  4858. return null;
  4859. },
  4860. /**
  4861. * @private
  4862. * Checks if the sprite can be seen.
  4863. * This includes the `hidden` attribute check, alpha/opacity checks,
  4864. * fill/stroke color checks and surface/parent checks.
  4865. * The method doesn't check if the sprite is off-screen.
  4866. * @return {Boolean} Returns `true`, if the sprite can be seen.
  4867. */
  4868. isVisible: function() {
  4869. var attr = this.attr,
  4870. parent = this.getParent(),
  4871. hasParent = parent && (parent.isSurface || parent.isVisible()),
  4872. isSeen = hasParent && !attr.hidden && attr.globalAlpha,
  4873. none1 = Ext.util.Color.NONE,
  4874. none2 = Ext.util.Color.RGBA_NONE,
  4875. hasFill = attr.fillOpacity && attr.fillStyle !== none1 && attr.fillStyle !== none2,
  4876. hasStroke = attr.strokeOpacity && attr.strokeStyle !== none1 && attr.strokeStyle !== none2,
  4877. result = isSeen && (hasFill || hasStroke);
  4878. return !!result;
  4879. },
  4880. repaint: function() {
  4881. var surface = this.getSurface();
  4882. if (surface) {
  4883. surface.renderFrame();
  4884. }
  4885. },
  4886. /**
  4887. * Removes this sprite from its surface.
  4888. * The sprite itself is not destroyed.
  4889. * @return {Ext.draw.sprite.Sprite} Returns the removed sprite or `null` otherwise.
  4890. */
  4891. remove: function() {
  4892. var surface = this.getSurface();
  4893. if (surface && surface.isSurface) {
  4894. return surface.remove(this);
  4895. }
  4896. return null;
  4897. },
  4898. /**
  4899. * Removes the sprite and clears all listeners.
  4900. */
  4901. destroy: function() {
  4902. var me = this,
  4903. modifier = me.modifiers.target,
  4904. currentModifier;
  4905. while (modifier) {
  4906. currentModifier = modifier;
  4907. modifier = modifier._lower;
  4908. currentModifier.destroy();
  4909. }
  4910. delete me.attr;
  4911. me.remove();
  4912. if (me.fireEvent('beforedestroy', me) !== false) {
  4913. me.fireEvent('destroy', me);
  4914. }
  4915. me.callParent();
  4916. }
  4917. }, function() {
  4918. // onClassCreated
  4919. // Create one AttributeDefinition instance per sprite class when a class is created
  4920. // and replace the `def` config with the instance that was created using that config.
  4921. // Here we only create an AttributeDefinition instance for the base Sprite class,
  4922. // attribute definitions for subclasses are created inside onClassExtended method.
  4923. this.def = new Ext.draw.sprite.AttributeDefinition(this.def);
  4924. this.def.spriteClass = this;
  4925. });
  4926. /**
  4927. * Class representing a path.
  4928. * Designed to be compatible with [CanvasPathMethods](http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#canvaspathmethods)
  4929. * and will hopefully be replaced by the browsers' implementation of the Path object.
  4930. */
  4931. Ext.define('Ext.draw.Path', {
  4932. requires: [
  4933. 'Ext.draw.Draw'
  4934. ],
  4935. statics: {
  4936. pathRe: /,?([achlmqrstvxz]),?/gi,
  4937. pathRe2: /-/gi,
  4938. pathSplitRe: /\s|,/g
  4939. },
  4940. svgString: '',
  4941. /**
  4942. * Create a path from pathString.
  4943. * @constructor
  4944. * @param {String} pathString
  4945. */
  4946. constructor: function(pathString) {
  4947. var me = this;
  4948. me.commands = [];
  4949. // Stores command letters from the SVG path data ('d' attribute).
  4950. me.params = [];
  4951. // Stores command parameters from the SVG path data.
  4952. // All command parameters are actually point coordinates as the only commands used
  4953. // are the M, L, C, Z. This makes path transformations and hit testing easier.
  4954. // Arcs are approximated using cubic Bezier curves, H and S commands are translated
  4955. // to L commands and relative commands are translated to their absolute versions.
  4956. me.cursor = null;
  4957. me.startX = 0;
  4958. me.startY = 0;
  4959. if (pathString) {
  4960. me.fromSvgString(pathString);
  4961. }
  4962. },
  4963. /**
  4964. * Clear the path.
  4965. */
  4966. clear: function() {
  4967. var me = this;
  4968. me.params.length = 0;
  4969. me.commands.length = 0;
  4970. me.cursor = null;
  4971. me.startX = 0;
  4972. me.startY = 0;
  4973. me.dirt();
  4974. },
  4975. /**
  4976. * @private
  4977. */
  4978. dirt: function() {
  4979. this.svgString = '';
  4980. },
  4981. /**
  4982. * Move to a position.
  4983. * @param {Number} x
  4984. * @param {Number} y
  4985. */
  4986. moveTo: function(x, y) {
  4987. var me = this;
  4988. if (!me.cursor) {
  4989. me.cursor = [
  4990. x,
  4991. y
  4992. ];
  4993. }
  4994. me.params.push(x, y);
  4995. me.commands.push('M');
  4996. me.startX = x;
  4997. me.startY = y;
  4998. me.cursor[0] = x;
  4999. me.cursor[1] = y;
  5000. me.dirt();
  5001. },
  5002. /**
  5003. * A straight line to a position.
  5004. * @param {Number} x
  5005. * @param {Number} y
  5006. */
  5007. lineTo: function(x, y) {
  5008. var me = this;
  5009. if (!me.cursor) {
  5010. me.cursor = [
  5011. x,
  5012. y
  5013. ];
  5014. me.params.push(x, y);
  5015. me.commands.push('M');
  5016. } else {
  5017. me.params.push(x, y);
  5018. me.commands.push('L');
  5019. }
  5020. me.cursor[0] = x;
  5021. me.cursor[1] = y;
  5022. me.dirt();
  5023. },
  5024. /**
  5025. * A cubic bezier curve to a position.
  5026. * @param {Number} cx1
  5027. * @param {Number} cy1
  5028. * @param {Number} cx2
  5029. * @param {Number} cy2
  5030. * @param {Number} x
  5031. * @param {Number} y
  5032. */
  5033. bezierCurveTo: function(cx1, cy1, cx2, cy2, x, y) {
  5034. var me = this;
  5035. if (!me.cursor) {
  5036. me.moveTo(cx1, cy1);
  5037. }
  5038. me.params.push(cx1, cy1, cx2, cy2, x, y);
  5039. me.commands.push('C');
  5040. me.cursor[0] = x;
  5041. me.cursor[1] = y;
  5042. me.dirt();
  5043. },
  5044. /**
  5045. * A quadratic bezier curve to a position.
  5046. * @param {Number} cx
  5047. * @param {Number} cy
  5048. * @param {Number} x
  5049. * @param {Number} y
  5050. */
  5051. quadraticCurveTo: function(cx, cy, x, y) {
  5052. var me = this;
  5053. if (!me.cursor) {
  5054. me.moveTo(cx, cy);
  5055. }
  5056. me.bezierCurveTo((2 * cx + me.cursor[0]) / 3, (2 * cy + me.cursor[1]) / 3, (2 * cx + x) / 3, (2 * cy + y) / 3, x, y);
  5057. },
  5058. /**
  5059. * Close this path with a straight line.
  5060. */
  5061. closePath: function() {
  5062. var me = this;
  5063. if (me.cursor) {
  5064. me.cursor = null;
  5065. me.commands.push('Z');
  5066. me.dirt();
  5067. }
  5068. },
  5069. /**
  5070. * Create a elliptic arc curve compatible with SVG's arc to instruction.
  5071. *
  5072. * The curve start from (`x1`, `y1`) and ends at (`x2`, `y2`). The ellipse
  5073. * has radius `rx` and `ry` and a rotation of `rotation`.
  5074. * @param {Number} x1
  5075. * @param {Number} y1
  5076. * @param {Number} x2
  5077. * @param {Number} y2
  5078. * @param {Number} [rx]
  5079. * @param {Number} [ry]
  5080. * @param {Number} [rotation]
  5081. */
  5082. arcTo: function(x1, y1, x2, y2, rx, ry, rotation) {
  5083. var me = this;
  5084. if (ry === undefined) {
  5085. ry = rx;
  5086. }
  5087. if (rotation === undefined) {
  5088. rotation = 0;
  5089. }
  5090. if (!me.cursor) {
  5091. me.moveTo(x1, y1);
  5092. return;
  5093. }
  5094. if (rx === 0 || ry === 0) {
  5095. me.lineTo(x1, y1);
  5096. return;
  5097. }
  5098. x2 -= x1;
  5099. y2 -= y1;
  5100. var x0 = me.cursor[0] - x1,
  5101. y0 = me.cursor[1] - y1,
  5102. area = x2 * y0 - y2 * x0,
  5103. cos, sin, xx, yx, xy, yy,
  5104. l0 = Math.sqrt(x0 * x0 + y0 * y0),
  5105. l2 = Math.sqrt(x2 * x2 + y2 * y2),
  5106. dist, cx, cy;
  5107. // cos rx, -sin ry , x1 - cos rx x1 + ry sin y1
  5108. // sin rx, cos ry, -rx sin x1 + y1 - cos ry y1
  5109. if (area === 0) {
  5110. me.lineTo(x1, y1);
  5111. return;
  5112. }
  5113. if (ry !== rx) {
  5114. cos = Math.cos(rotation);
  5115. sin = Math.sin(rotation);
  5116. xx = cos / rx;
  5117. yx = sin / ry;
  5118. xy = -sin / rx;
  5119. yy = cos / ry;
  5120. var temp = xx * x0 + yx * y0;
  5121. y0 = xy * x0 + yy * y0;
  5122. x0 = temp;
  5123. temp = xx * x2 + yx * y2;
  5124. y2 = xy * x2 + yy * y2;
  5125. x2 = temp;
  5126. } else {
  5127. x0 /= rx;
  5128. y0 /= ry;
  5129. x2 /= rx;
  5130. y2 /= ry;
  5131. }
  5132. cx = x0 * l2 + x2 * l0;
  5133. cy = y0 * l2 + y2 * l0;
  5134. dist = 1 / (Math.sin(Math.asin(Math.abs(area) / (l0 * l2)) * 0.5) * Math.sqrt(cx * cx + cy * cy));
  5135. cx *= dist;
  5136. cy *= dist;
  5137. var k0 = (cx * x0 + cy * y0) / (x0 * x0 + y0 * y0),
  5138. k2 = (cx * x2 + cy * y2) / (x2 * x2 + y2 * y2);
  5139. var cosStart = x0 * k0 - cx,
  5140. sinStart = y0 * k0 - cy,
  5141. cosEnd = x2 * k2 - cx,
  5142. sinEnd = y2 * k2 - cy,
  5143. startAngle = Math.atan2(sinStart, cosStart),
  5144. endAngle = Math.atan2(sinEnd, cosEnd);
  5145. if (area > 0) {
  5146. if (endAngle < startAngle) {
  5147. endAngle += Math.PI * 2;
  5148. }
  5149. } else {
  5150. if (startAngle < endAngle) {
  5151. startAngle += Math.PI * 2;
  5152. }
  5153. }
  5154. if (ry !== rx) {
  5155. cx = cos * cx * rx - sin * cy * ry + x1;
  5156. cy = sin * cy * ry + cos * cy * ry + y1;
  5157. me.lineTo(cos * rx * cosStart - sin * ry * sinStart + cx, sin * rx * cosStart + cos * ry * sinStart + cy);
  5158. me.ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, area < 0);
  5159. } else {
  5160. cx = cx * rx + x1;
  5161. cy = cy * ry + y1;
  5162. me.lineTo(rx * cosStart + cx, ry * sinStart + cy);
  5163. me.ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, area < 0);
  5164. }
  5165. },
  5166. /**
  5167. * Create an elliptic arc.
  5168. *
  5169. * See [the whatwg reference of ellipse](http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-ellipse).
  5170. *
  5171. * @param {Number} cx
  5172. * @param {Number} cy
  5173. * @param {Number} radiusX
  5174. * @param {Number} radiusY
  5175. * @param {Number} rotation
  5176. * @param {Number} startAngle
  5177. * @param {Number} endAngle
  5178. * @param {Number} anticlockwise
  5179. */
  5180. ellipse: function(cx, cy, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise) {
  5181. var me = this,
  5182. params = me.params,
  5183. start = params.length,
  5184. count, i, j;
  5185. if (endAngle - startAngle >= Math.PI * 2) {
  5186. me.ellipse(cx, cy, radiusX, radiusY, rotation, startAngle, startAngle + Math.PI, anticlockwise);
  5187. me.ellipse(cx, cy, radiusX, radiusY, rotation, startAngle + Math.PI, endAngle, anticlockwise);
  5188. return;
  5189. }
  5190. if (!anticlockwise) {
  5191. if (endAngle < startAngle) {
  5192. endAngle += Math.PI * 2;
  5193. }
  5194. count = me.approximateArc(params, cx, cy, radiusX, radiusY, rotation, startAngle, endAngle);
  5195. } else {
  5196. if (startAngle < endAngle) {
  5197. startAngle += Math.PI * 2;
  5198. }
  5199. count = me.approximateArc(params, cx, cy, radiusX, radiusY, rotation, endAngle, startAngle);
  5200. for (i = start , j = params.length - 2; i < j; i += 2 , j -= 2) {
  5201. var temp = params[i];
  5202. params[i] = params[j];
  5203. params[j] = temp;
  5204. temp = params[i + 1];
  5205. params[i + 1] = params[j + 1];
  5206. params[j + 1] = temp;
  5207. }
  5208. }
  5209. if (!me.cursor) {
  5210. me.cursor = [
  5211. params[params.length - 2],
  5212. params[params.length - 1]
  5213. ];
  5214. me.commands.push('M');
  5215. } else {
  5216. me.cursor[0] = params[params.length - 2];
  5217. me.cursor[1] = params[params.length - 1];
  5218. me.commands.push('L');
  5219. }
  5220. for (i = 2; i < count; i += 6) {
  5221. me.commands.push('C');
  5222. }
  5223. me.dirt();
  5224. },
  5225. /**
  5226. * Create an circular arc.
  5227. *
  5228. * @param {Number} x
  5229. * @param {Number} y
  5230. * @param {Number} radius
  5231. * @param {Number} startAngle
  5232. * @param {Number} endAngle
  5233. * @param {Number} anticlockwise
  5234. */
  5235. arc: function(x, y, radius, startAngle, endAngle, anticlockwise) {
  5236. this.ellipse(x, y, radius, radius, 0, startAngle, endAngle, anticlockwise);
  5237. },
  5238. /**
  5239. * Draw a rectangle and close it.
  5240. *
  5241. * @param {Number} x
  5242. * @param {Number} y
  5243. * @param {Number} width
  5244. * @param {Number} height
  5245. */
  5246. rect: function(x, y, width, height) {
  5247. if (width == 0 || height == 0) {
  5248. return;
  5249. }
  5250. var me = this;
  5251. me.moveTo(x, y);
  5252. me.lineTo(x + width, y);
  5253. me.lineTo(x + width, y + height);
  5254. me.lineTo(x, y + height);
  5255. me.closePath();
  5256. },
  5257. /**
  5258. * @private
  5259. * @param {Array} result
  5260. * @param {Number} cx
  5261. * @param {Number} cy
  5262. * @param {Number} rx
  5263. * @param {Number} ry
  5264. * @param {Number} phi
  5265. * @param {Number} theta1
  5266. * @param {Number} theta2
  5267. * @return {Number}
  5268. */
  5269. approximateArc: function(result, cx, cy, rx, ry, phi, theta1, theta2) {
  5270. var cosPhi = Math.cos(phi),
  5271. sinPhi = Math.sin(phi),
  5272. cosTheta1 = Math.cos(theta1),
  5273. sinTheta1 = Math.sin(theta1),
  5274. xx = cosPhi * cosTheta1 * rx - sinPhi * sinTheta1 * ry,
  5275. yx = -cosPhi * sinTheta1 * rx - sinPhi * cosTheta1 * ry,
  5276. xy = sinPhi * cosTheta1 * rx + cosPhi * sinTheta1 * ry,
  5277. yy = -sinPhi * sinTheta1 * rx + cosPhi * cosTheta1 * ry,
  5278. rightAngle = Math.PI / 2,
  5279. count = 2,
  5280. exx = xx,
  5281. eyx = yx,
  5282. exy = xy,
  5283. eyy = yy,
  5284. rho = 0.547443256150549,
  5285. temp, y1, x3, y3, x2, y2;
  5286. theta2 -= theta1;
  5287. if (theta2 < 0) {
  5288. theta2 += Math.PI * 2;
  5289. }
  5290. result.push(xx + cx, xy + cy);
  5291. while (theta2 >= rightAngle) {
  5292. result.push(exx + eyx * rho + cx, exy + eyy * rho + cy, exx * rho + eyx + cx, exy * rho + eyy + cy, eyx + cx, eyy + cy);
  5293. count += 6;
  5294. theta2 -= rightAngle;
  5295. temp = exx;
  5296. exx = eyx;
  5297. eyx = -temp;
  5298. temp = exy;
  5299. exy = eyy;
  5300. eyy = -temp;
  5301. }
  5302. if (theta2) {
  5303. y1 = (0.3294738052815987 + 0.012120855841304373 * theta2) * theta2;
  5304. x3 = Math.cos(theta2);
  5305. y3 = Math.sin(theta2);
  5306. x2 = x3 + y1 * y3;
  5307. y2 = y3 - y1 * x3;
  5308. 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);
  5309. count += 6;
  5310. }
  5311. return count;
  5312. },
  5313. /**
  5314. * [http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes](http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes)
  5315. * @param {Number} rx
  5316. * @param {Number} ry
  5317. * @param {Number} rotation Differ from svg spec, this is radian.
  5318. * @param {Number} fA
  5319. * @param {Number} fS
  5320. * @param {Number} x2
  5321. * @param {Number} y2
  5322. */
  5323. arcSvg: function(rx, ry, rotation, fA, fS, x2, y2) {
  5324. if (rx < 0) {
  5325. rx = -rx;
  5326. }
  5327. if (ry < 0) {
  5328. ry = -ry;
  5329. }
  5330. var me = this,
  5331. x1 = me.cursor[0],
  5332. y1 = me.cursor[1],
  5333. hdx = (x1 - x2) / 2,
  5334. hdy = (y1 - y2) / 2,
  5335. cosPhi = Math.cos(rotation),
  5336. sinPhi = Math.sin(rotation),
  5337. xp = hdx * cosPhi + hdy * sinPhi,
  5338. yp = -hdx * sinPhi + hdy * cosPhi,
  5339. ratX = xp / rx,
  5340. ratY = yp / ry,
  5341. lambda = ratX * ratX + ratY * ratY,
  5342. cx = (x1 + x2) * 0.5,
  5343. cy = (y1 + y2) * 0.5,
  5344. cpx = 0,
  5345. cpy = 0;
  5346. if (lambda >= 1) {
  5347. lambda = Math.sqrt(lambda);
  5348. rx *= lambda;
  5349. ry *= lambda;
  5350. } else // me gives lambda == cpx == cpy == 0;
  5351. {
  5352. lambda = Math.sqrt(1 / lambda - 1);
  5353. if (fA === fS) {
  5354. lambda = -lambda;
  5355. }
  5356. cpx = lambda * rx * ratY;
  5357. cpy = -lambda * ry * ratX;
  5358. cx += cosPhi * cpx - sinPhi * cpy;
  5359. cy += sinPhi * cpx + cosPhi * cpy;
  5360. }
  5361. var theta1 = Math.atan2((yp - cpy) / ry, (xp - cpx) / rx),
  5362. deltaTheta = Math.atan2((-yp - cpy) / ry, (-xp - cpx) / rx) - theta1;
  5363. if (fS) {
  5364. if (deltaTheta <= 0) {
  5365. deltaTheta += Math.PI * 2;
  5366. }
  5367. } else {
  5368. if (deltaTheta >= 0) {
  5369. deltaTheta -= Math.PI * 2;
  5370. }
  5371. }
  5372. me.ellipse(cx, cy, rx, ry, rotation, theta1, theta1 + deltaTheta, 1 - fS);
  5373. },
  5374. /**
  5375. * Feed the path from svg path string.
  5376. * @param {String} pathString
  5377. */
  5378. fromSvgString: function(pathString) {
  5379. if (!pathString) {
  5380. return;
  5381. }
  5382. var me = this,
  5383. parts,
  5384. paramCounts = {
  5385. a: 7,
  5386. c: 6,
  5387. h: 1,
  5388. l: 2,
  5389. m: 2,
  5390. q: 4,
  5391. s: 4,
  5392. t: 2,
  5393. v: 1,
  5394. z: 0,
  5395. A: 7,
  5396. C: 6,
  5397. H: 1,
  5398. L: 2,
  5399. M: 2,
  5400. Q: 4,
  5401. S: 4,
  5402. T: 2,
  5403. V: 1,
  5404. Z: 0
  5405. },
  5406. lastCommand = '',
  5407. lastControlX, lastControlY,
  5408. lastX = 0,
  5409. lastY = 0,
  5410. part = false,
  5411. i, partLength, relative;
  5412. // Split the string to items.
  5413. if (Ext.isString(pathString)) {
  5414. parts = pathString.replace(Ext.draw.Path.pathRe, " $1 ").replace(Ext.draw.Path.pathRe2, " -").split(Ext.draw.Path.pathSplitRe);
  5415. } else if (Ext.isArray(pathString)) {
  5416. parts = pathString.join(',').split(Ext.draw.Path.pathSplitRe);
  5417. }
  5418. // Remove empty entries
  5419. for (i = 0 , partLength = 0; i < parts.length; i++) {
  5420. if (parts[i] !== '') {
  5421. parts[partLength++] = parts[i];
  5422. }
  5423. }
  5424. parts.length = partLength;
  5425. me.clear();
  5426. for (i = 0; i < parts.length; ) {
  5427. lastCommand = part;
  5428. part = parts[i];
  5429. relative = (part.toUpperCase() !== part);
  5430. i++;
  5431. switch (part) {
  5432. case 'M':
  5433. me.moveTo(lastX = +parts[i], lastY = +parts[i + 1]);
  5434. i += 2;
  5435. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5436. me.lineTo(lastX = +parts[i], lastY = +parts[i + 1]);
  5437. i += 2;
  5438. };
  5439. break;
  5440. case 'L':
  5441. me.lineTo(lastX = +parts[i], lastY = +parts[i + 1]);
  5442. i += 2;
  5443. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5444. me.lineTo(lastX = +parts[i], lastY = +parts[i + 1]);
  5445. i += 2;
  5446. };
  5447. break;
  5448. case 'A':
  5449. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5450. 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]);
  5451. i += 7;
  5452. };
  5453. break;
  5454. case 'C':
  5455. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5456. me.bezierCurveTo(+parts[i], +parts[i + 1], lastControlX = +parts[i + 2], lastControlY = +parts[i + 3], lastX = +parts[i + 4], lastY = +parts[i + 5]);
  5457. i += 6;
  5458. };
  5459. break;
  5460. case 'Z':
  5461. me.closePath();
  5462. break;
  5463. case 'm':
  5464. me.moveTo(lastX += +parts[i], lastY += +parts[i + 1]);
  5465. i += 2;
  5466. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5467. me.lineTo(lastX += +parts[i], lastY += +parts[i + 1]);
  5468. i += 2;
  5469. };
  5470. break;
  5471. case 'l':
  5472. me.lineTo(lastX += +parts[i], lastY += +parts[i + 1]);
  5473. i += 2;
  5474. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5475. me.lineTo(lastX += +parts[i], lastY += +parts[i + 1]);
  5476. i += 2;
  5477. };
  5478. break;
  5479. case 'a':
  5480. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5481. 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]);
  5482. i += 7;
  5483. };
  5484. break;
  5485. case 'c':
  5486. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5487. 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]);
  5488. i += 6;
  5489. };
  5490. break;
  5491. case 'z':
  5492. me.closePath();
  5493. break;
  5494. case 's':
  5495. if (!(lastCommand === 'c' || lastCommand === 'C' || lastCommand === 's' || lastCommand === 'S')) {
  5496. lastControlX = lastX;
  5497. lastControlY = lastY;
  5498. };
  5499. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5500. 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]);
  5501. i += 4;
  5502. };
  5503. break;
  5504. case 'S':
  5505. if (!(lastCommand === 'c' || lastCommand === 'C' || lastCommand === 's' || lastCommand === 'S')) {
  5506. lastControlX = lastX;
  5507. lastControlY = lastY;
  5508. };
  5509. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5510. me.bezierCurveTo(lastX + lastX - lastControlX, lastY + lastY - lastControlY, lastControlX = +parts[i], lastControlY = +parts[i + 1], lastX = (+parts[i + 2]), lastY = (+parts[i + 3]));
  5511. i += 4;
  5512. };
  5513. break;
  5514. case 'q':
  5515. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5516. me.quadraticCurveTo(lastControlX = lastX + (+parts[i]), lastControlY = lastY + (+parts[i + 1]), lastX += +parts[i + 2], lastY += +parts[i + 3]);
  5517. i += 4;
  5518. };
  5519. break;
  5520. case 'Q':
  5521. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5522. me.quadraticCurveTo(lastControlX = +parts[i], lastControlY = +parts[i + 1], lastX = +parts[i + 2], lastY = +parts[i + 3]);
  5523. i += 4;
  5524. };
  5525. break;
  5526. case 't':
  5527. if (!(lastCommand === 'q' || lastCommand === 'Q' || lastCommand === 't' || lastCommand === 'T')) {
  5528. lastControlX = lastX;
  5529. lastControlY = lastY;
  5530. };
  5531. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5532. me.quadraticCurveTo(lastControlX = lastX + lastX - lastControlX, lastControlY = lastY + lastY - lastControlY, lastX += +parts[i + 1], lastY += +parts[i + 2]);
  5533. i += 2;
  5534. };
  5535. break;
  5536. case 'T':
  5537. if (!(lastCommand === 'q' || lastCommand === 'Q' || lastCommand === 't' || lastCommand === 'T')) {
  5538. lastControlX = lastX;
  5539. lastControlY = lastY;
  5540. };
  5541. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5542. me.quadraticCurveTo(lastControlX = lastX + lastX - lastControlX, lastControlY = lastY + lastY - lastControlY, lastX = (+parts[i + 1]), lastY = (+parts[i + 2]));
  5543. i += 2;
  5544. };
  5545. break;
  5546. case 'h':
  5547. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5548. me.lineTo(lastX += +parts[i], lastY);
  5549. i++;
  5550. };
  5551. break;
  5552. case 'H':
  5553. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5554. me.lineTo(lastX = +parts[i], lastY);
  5555. i++;
  5556. };
  5557. break;
  5558. case 'v':
  5559. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5560. me.lineTo(lastX, lastY += +parts[i]);
  5561. i++;
  5562. };
  5563. break;
  5564. case 'V':
  5565. while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
  5566. me.lineTo(lastX, lastY = +parts[i]);
  5567. i++;
  5568. };
  5569. break;
  5570. }
  5571. }
  5572. },
  5573. /**
  5574. * Clone this path.
  5575. * @return {Ext.draw.Path}
  5576. */
  5577. clone: function() {
  5578. var me = this,
  5579. path = new Ext.draw.Path();
  5580. path.params = me.params.slice(0);
  5581. path.commands = me.commands.slice(0);
  5582. path.cursor = me.cursor ? me.cursor.slice(0) : null;
  5583. path.startX = me.startX;
  5584. path.startY = me.startY;
  5585. path.svgString = me.svgString;
  5586. return path;
  5587. },
  5588. /**
  5589. * Transform the current path by a matrix.
  5590. * @param {Ext.draw.Matrix} matrix
  5591. */
  5592. transform: function(matrix) {
  5593. if (matrix.isIdentity()) {
  5594. return;
  5595. }
  5596. var xx = matrix.getXX(),
  5597. yx = matrix.getYX(),
  5598. dx = matrix.getDX(),
  5599. xy = matrix.getXY(),
  5600. yy = matrix.getYY(),
  5601. dy = matrix.getDY(),
  5602. params = this.params,
  5603. i = 0,
  5604. ln = params.length,
  5605. x, y;
  5606. for (; i < ln; i += 2) {
  5607. x = params[i];
  5608. y = params[i + 1];
  5609. params[i] = x * xx + y * yx + dx;
  5610. params[i + 1] = x * xy + y * yy + dy;
  5611. }
  5612. this.dirt();
  5613. },
  5614. /**
  5615. * Get the bounding box of this matrix.
  5616. * @param {Object} [target] Optional object to receive the result.
  5617. *
  5618. * @return {Object} Object with x, y, width and height
  5619. */
  5620. getDimension: function(target) {
  5621. if (!target) {
  5622. target = {};
  5623. }
  5624. if (!this.commands || !this.commands.length) {
  5625. target.x = 0;
  5626. target.y = 0;
  5627. target.width = 0;
  5628. target.height = 0;
  5629. return target;
  5630. }
  5631. target.left = Infinity;
  5632. target.top = Infinity;
  5633. target.right = -Infinity;
  5634. target.bottom = -Infinity;
  5635. var i = 0,
  5636. j = 0,
  5637. commands = this.commands,
  5638. params = this.params,
  5639. ln = commands.length,
  5640. x, y;
  5641. for (; i < ln; i++) {
  5642. switch (commands[i]) {
  5643. case 'M':
  5644. case 'L':
  5645. x = params[j];
  5646. y = params[j + 1];
  5647. target.left = Math.min(x, target.left);
  5648. target.top = Math.min(y, target.top);
  5649. target.right = Math.max(x, target.right);
  5650. target.bottom = Math.max(y, target.bottom);
  5651. j += 2;
  5652. break;
  5653. case 'C':
  5654. this.expandDimension(target, x, y, params[j], params[j + 1], params[j + 2], params[j + 3], x = params[j + 4], y = params[j + 5]);
  5655. j += 6;
  5656. break;
  5657. }
  5658. }
  5659. target.x = target.left;
  5660. target.y = target.top;
  5661. target.width = target.right - target.left;
  5662. target.height = target.bottom - target.top;
  5663. return target;
  5664. },
  5665. /**
  5666. * Get the bounding box as if the path is transformed by a matrix.
  5667. *
  5668. * @param {Ext.draw.Matrix} matrix
  5669. * @param {Object} [target] Optional object to receive the result.
  5670. *
  5671. * @return {Object} An object with x, y, width and height.
  5672. */
  5673. getDimensionWithTransform: function(matrix, target) {
  5674. if (!this.commands || !this.commands.length) {
  5675. if (!target) {
  5676. target = {};
  5677. }
  5678. target.x = 0;
  5679. target.y = 0;
  5680. target.width = 0;
  5681. target.height = 0;
  5682. return target;
  5683. }
  5684. target.left = Infinity;
  5685. target.top = Infinity;
  5686. target.right = -Infinity;
  5687. target.bottom = -Infinity;
  5688. var xx = matrix.getXX(),
  5689. yx = matrix.getYX(),
  5690. dx = matrix.getDX(),
  5691. xy = matrix.getXY(),
  5692. yy = matrix.getYY(),
  5693. dy = matrix.getDY(),
  5694. i = 0,
  5695. j = 0,
  5696. commands = this.commands,
  5697. params = this.params,
  5698. ln = commands.length,
  5699. x, y;
  5700. for (; i < ln; i++) {
  5701. switch (commands[i]) {
  5702. case 'M':
  5703. case 'L':
  5704. x = params[j] * xx + params[j + 1] * yx + dx;
  5705. y = params[j] * xy + params[j + 1] * yy + dy;
  5706. target.left = Math.min(x, target.left);
  5707. target.top = Math.min(y, target.top);
  5708. target.right = Math.max(x, target.right);
  5709. target.bottom = Math.max(y, target.bottom);
  5710. j += 2;
  5711. break;
  5712. case 'C':
  5713. 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);
  5714. j += 6;
  5715. break;
  5716. }
  5717. }
  5718. if (!target) {
  5719. target = {};
  5720. }
  5721. target.x = target.left;
  5722. target.y = target.top;
  5723. target.width = target.right - target.left;
  5724. target.height = target.bottom - target.top;
  5725. return target;
  5726. },
  5727. /**
  5728. * @private
  5729. * Expand the rect by the bbox of a bezier curve.
  5730. *
  5731. * @param {Object} target
  5732. * @param {Number} x1
  5733. * @param {Number} y1
  5734. * @param {Number} cx1
  5735. * @param {Number} cy1
  5736. * @param {Number} cx2
  5737. * @param {Number} cy2
  5738. * @param {Number} x2
  5739. * @param {Number} y2
  5740. */
  5741. expandDimension: function(target, x1, y1, cx1, cy1, cx2, cy2, x2, y2) {
  5742. var me = this,
  5743. l = target.left,
  5744. r = target.right,
  5745. t = target.top,
  5746. b = target.bottom,
  5747. dim = me.dim || (me.dim = []);
  5748. me.curveDimension(x1, cx1, cx2, x2, dim);
  5749. l = Math.min(l, dim[0]);
  5750. r = Math.max(r, dim[1]);
  5751. me.curveDimension(y1, cy1, cy2, y2, dim);
  5752. t = Math.min(t, dim[0]);
  5753. b = Math.max(b, dim[1]);
  5754. target.left = l;
  5755. target.right = r;
  5756. target.top = t;
  5757. target.bottom = b;
  5758. },
  5759. /**
  5760. * @private
  5761. * Determine the curve
  5762. * @param {Number} a
  5763. * @param {Number} b
  5764. * @param {Number} c
  5765. * @param {Number} d
  5766. * @param {Number} dim
  5767. */
  5768. curveDimension: function(a, b, c, d, dim) {
  5769. var qa = 3 * (-a + 3 * (b - c) + d),
  5770. qb = 6 * (a - 2 * b + c),
  5771. qc = -3 * (a - b),
  5772. x, y,
  5773. min = Math.min(a, d),
  5774. max = Math.max(a, d),
  5775. delta;
  5776. if (qa === 0) {
  5777. if (qb === 0) {
  5778. dim[0] = min;
  5779. dim[1] = max;
  5780. return;
  5781. } else {
  5782. x = -qc / qb;
  5783. if (0 < x && x < 1) {
  5784. y = this.interpolate(a, b, c, d, x);
  5785. min = Math.min(min, y);
  5786. max = Math.max(max, y);
  5787. }
  5788. }
  5789. } else {
  5790. delta = qb * qb - 4 * qa * qc;
  5791. if (delta >= 0) {
  5792. delta = Math.sqrt(delta);
  5793. x = (delta - qb) / 2 / qa;
  5794. if (0 < x && x < 1) {
  5795. y = this.interpolate(a, b, c, d, x);
  5796. min = Math.min(min, y);
  5797. max = Math.max(max, y);
  5798. }
  5799. if (delta > 0) {
  5800. x -= delta / qa;
  5801. if (0 < x && x < 1) {
  5802. y = this.interpolate(a, b, c, d, x);
  5803. min = Math.min(min, y);
  5804. max = Math.max(max, y);
  5805. }
  5806. }
  5807. }
  5808. }
  5809. dim[0] = min;
  5810. dim[1] = max;
  5811. },
  5812. /**
  5813. * @private
  5814. *
  5815. * Returns `a * (1 - t) ^ 3 + 3 * b (1 - t) ^ 2 * t + 3 * c (1 - t) * t ^ 3 + d * t ^ 3`.
  5816. *
  5817. * @param {Number} a
  5818. * @param {Number} b
  5819. * @param {Number} c
  5820. * @param {Number} d
  5821. * @param {Number} t
  5822. * @return {Number}
  5823. */
  5824. interpolate: function(a, b, c, d, t) {
  5825. if (t === 0) {
  5826. return a;
  5827. }
  5828. if (t === 1) {
  5829. return d;
  5830. }
  5831. var rate = (1 - t) / t;
  5832. return t * t * t * (d + rate * (3 * c + rate * (3 * b + rate * a)));
  5833. },
  5834. /**
  5835. * Reconstruct path from cubic bezier curve stripes.
  5836. * @param {Array} stripes
  5837. */
  5838. fromStripes: function(stripes) {
  5839. var me = this,
  5840. i = 0,
  5841. ln = stripes.length,
  5842. j, ln2, stripe;
  5843. me.clear();
  5844. for (; i < ln; i++) {
  5845. stripe = stripes[i];
  5846. me.params.push.apply(me.params, stripe);
  5847. me.commands.push('M');
  5848. for (j = 2 , ln2 = stripe.length; j < ln2; j += 6) {
  5849. me.commands.push('C');
  5850. }
  5851. }
  5852. if (!me.cursor) {
  5853. me.cursor = [];
  5854. }
  5855. me.cursor[0] = me.params[me.params.length - 2];
  5856. me.cursor[1] = me.params[me.params.length - 1];
  5857. me.dirt();
  5858. },
  5859. /**
  5860. * Convert path to bezier curve stripes.
  5861. * @param {Array} [target] The optional array to receive the result.
  5862. * @return {Array}
  5863. */
  5864. toStripes: function(target) {
  5865. var stripes = target || [],
  5866. curr, x, y, lastX, lastY, startX, startY, i, j,
  5867. commands = this.commands,
  5868. params = this.params,
  5869. ln = commands.length;
  5870. for (i = 0 , j = 0; i < ln; i++) {
  5871. switch (commands[i]) {
  5872. case 'M':
  5873. curr = [
  5874. startX = lastX = params[j++],
  5875. startY = lastY = params[j++]
  5876. ];
  5877. stripes.push(curr);
  5878. break;
  5879. case 'L':
  5880. x = params[j++];
  5881. y = params[j++];
  5882. curr.push((lastX + lastX + x) / 3, (lastY + lastY + y) / 3, (lastX + x + x) / 3, (lastY + y + y) / 3, lastX = x, lastY = y);
  5883. break;
  5884. case 'C':
  5885. curr.push(params[j++], params[j++], params[j++], params[j++], lastX = params[j++], lastY = params[j++]);
  5886. break;
  5887. case 'Z':
  5888. x = startX;
  5889. y = startY;
  5890. curr.push((lastX + lastX + x) / 3, (lastY + lastY + y) / 3, (lastX + x + x) / 3, (lastY + y + y) / 3, lastX = x, lastY = y);
  5891. break;
  5892. }
  5893. }
  5894. return stripes;
  5895. },
  5896. /**
  5897. * @private
  5898. * Update cache for svg string of this path.
  5899. */
  5900. updateSvgString: function() {
  5901. var result = [],
  5902. commands = this.commands,
  5903. params = this.params,
  5904. ln = commands.length,
  5905. i = 0,
  5906. j = 0;
  5907. for (; i < ln; i++) {
  5908. switch (commands[i]) {
  5909. case 'M':
  5910. result.push('M' + params[j] + ',' + params[j + 1]);
  5911. j += 2;
  5912. break;
  5913. case 'L':
  5914. result.push('L' + params[j] + ',' + params[j + 1]);
  5915. j += 2;
  5916. break;
  5917. case 'C':
  5918. result.push('C' + params[j] + ',' + params[j + 1] + ' ' + params[j + 2] + ',' + params[j + 3] + ' ' + params[j + 4] + ',' + params[j + 5]);
  5919. j += 6;
  5920. break;
  5921. case 'Z':
  5922. result.push('Z');
  5923. break;
  5924. }
  5925. }
  5926. this.svgString = result.join('');
  5927. },
  5928. /**
  5929. * Return an svg path string for this path.
  5930. * @return {String}
  5931. */
  5932. toString: function() {
  5933. if (!this.svgString) {
  5934. this.updateSvgString();
  5935. }
  5936. return this.svgString;
  5937. }
  5938. });
  5939. /**
  5940. * @private
  5941. * Adds hit testing and path intersection points methods to the Ext.draw.Path.
  5942. * Included by the Ext.draw.PathUtil.
  5943. */
  5944. Ext.define('Ext.draw.overrides.hittest.Path', {
  5945. override: 'Ext.draw.Path',
  5946. // An arbitrary point outside the path used for hit testing with ray casting method.
  5947. rayOrigin: {
  5948. x: -10000,
  5949. y: -10000
  5950. },
  5951. /**
  5952. * Tests whether the given point is inside the path.
  5953. * @param {Number} x
  5954. * @param {Number} y
  5955. * @return {Boolean}
  5956. * @member Ext.draw.Path
  5957. */
  5958. isPointInPath: function(x, y) {
  5959. var me = this,
  5960. commands = me.commands,
  5961. solver = Ext.draw.PathUtil,
  5962. origin = me.rayOrigin,
  5963. params = me.params,
  5964. ln = commands.length,
  5965. firstX = null,
  5966. firstY = null,
  5967. lastX = 0,
  5968. lastY = 0,
  5969. count = 0,
  5970. i, j;
  5971. for (i = 0 , j = 0; i < ln; i++) {
  5972. switch (commands[i]) {
  5973. case 'M':
  5974. if (firstX !== null) {
  5975. if (solver.linesIntersection(firstX, firstY, lastX, lastY, origin.x, origin.y, x, y)) {
  5976. count += 1;
  5977. }
  5978. };
  5979. firstX = lastX = params[j];
  5980. firstY = lastY = params[j + 1];
  5981. j += 2;
  5982. break;
  5983. case 'L':
  5984. if (solver.linesIntersection(lastX, lastY, params[j], params[j + 1], origin.x, origin.y, x, y)) {
  5985. count += 1;
  5986. };
  5987. lastX = params[j];
  5988. lastY = params[j + 1];
  5989. j += 2;
  5990. break;
  5991. case 'C':
  5992. 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;
  5993. lastX = params[j + 4];
  5994. lastY = params[j + 5];
  5995. j += 6;
  5996. break;
  5997. case 'Z':
  5998. if (firstX !== null) {
  5999. if (solver.linesIntersection(firstX, firstY, lastX, lastY, origin.x, origin.y, x, y)) {
  6000. count += 1;
  6001. }
  6002. };
  6003. break;
  6004. }
  6005. }
  6006. return count % 2 === 1;
  6007. },
  6008. /**
  6009. * Tests whether the given point is on the path.
  6010. * @param {Number} x
  6011. * @param {Number} y
  6012. * @return {Boolean}
  6013. * @member Ext.draw.Path
  6014. */
  6015. isPointOnPath: function(x, y) {
  6016. var me = this,
  6017. commands = me.commands,
  6018. solver = Ext.draw.PathUtil,
  6019. params = me.params,
  6020. ln = commands.length,
  6021. firstX = null,
  6022. firstY = null,
  6023. lastX = 0,
  6024. lastY = 0,
  6025. i, j;
  6026. for (i = 0 , j = 0; i < ln; i++) {
  6027. switch (commands[i]) {
  6028. case 'M':
  6029. if (firstX !== null) {
  6030. if (solver.pointOnLine(firstX, firstY, lastX, lastY, x, y)) {
  6031. return true;
  6032. }
  6033. };
  6034. firstX = lastX = params[j];
  6035. firstY = lastY = params[j + 1];
  6036. j += 2;
  6037. break;
  6038. case 'L':
  6039. if (solver.pointOnLine(lastX, lastY, params[j], params[j + 1], x, y)) {
  6040. return true;
  6041. };
  6042. lastX = params[j];
  6043. lastY = params[j + 1];
  6044. j += 2;
  6045. break;
  6046. case 'C':
  6047. if (solver.pointOnCubic(lastX, params[j], params[j + 2], params[j + 4], lastY, params[j + 1], params[j + 3], params[j + 5], x, y)) {
  6048. return true;
  6049. };
  6050. lastX = params[j + 4];
  6051. lastY = params[j + 5];
  6052. j += 6;
  6053. break;
  6054. case 'Z':
  6055. if (firstX !== null) {
  6056. if (solver.pointOnLine(firstX, firstY, lastX, lastY, x, y)) {
  6057. return true;
  6058. }
  6059. };
  6060. break;
  6061. }
  6062. }
  6063. return false;
  6064. },
  6065. /**
  6066. * Calculates the points where the given segment intersects the path.
  6067. * If four parameters are given then the segment is considered to be a line segment,
  6068. * where given parameters are the coordinates of the start and end points.
  6069. * If eight parameters are given then the segment is considered to be
  6070. * a cubic Bezier curve segment, where given parameters are the
  6071. * coordinates of its edge points and control points.
  6072. * @param x1
  6073. * @param y1
  6074. * @param x2
  6075. * @param y2
  6076. * @param x3
  6077. * @param y3
  6078. * @param x4
  6079. * @param y4
  6080. * @return {Array}
  6081. * @member Ext.draw.Path
  6082. */
  6083. getSegmentIntersections: function(x1, y1, x2, y2, x3, y3, x4, y4) {
  6084. var me = this,
  6085. count = arguments.length,
  6086. solver = Ext.draw.PathUtil,
  6087. commands = me.commands,
  6088. params = me.params,
  6089. ln = commands.length,
  6090. firstX = null,
  6091. firstY = null,
  6092. lastX = 0,
  6093. lastY = 0,
  6094. intersections = [],
  6095. i, j, points;
  6096. for (i = 0 , j = 0; i < ln; i++) {
  6097. switch (commands[i]) {
  6098. case 'M':
  6099. if (firstX !== null) {
  6100. switch (count) {
  6101. case 4:
  6102. points = solver.linesIntersection(firstX, firstY, lastX, lastY, x1, y1, x2, y2);
  6103. if (points) {
  6104. intersections.push(points);
  6105. };
  6106. break;
  6107. case 8:
  6108. points = solver.cubicLineIntersections(x1, x2, x3, x4, y1, y2, y3, y4, firstX, firstY, lastX, lastY);
  6109. intersections.push.apply(intersections, points);
  6110. break;
  6111. }
  6112. };
  6113. firstX = lastX = params[j];
  6114. firstY = lastY = params[j + 1];
  6115. j += 2;
  6116. break;
  6117. case 'L':
  6118. switch (count) {
  6119. case 4:
  6120. points = solver.linesIntersection(lastX, lastY, params[j], params[j + 1], x1, y1, x2, y2);
  6121. if (points) {
  6122. intersections.push(points);
  6123. };
  6124. break;
  6125. case 8:
  6126. points = solver.cubicLineIntersections(x1, x2, x3, x4, y1, y2, y3, y4, lastX, lastY, params[j], params[j + 1]);
  6127. intersections.push.apply(intersections, points);
  6128. break;
  6129. };
  6130. lastX = params[j];
  6131. lastY = params[j + 1];
  6132. j += 2;
  6133. break;
  6134. case 'C':
  6135. switch (count) {
  6136. case 4:
  6137. 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);
  6138. intersections.push.apply(intersections, points);
  6139. break;
  6140. case 8:
  6141. 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);
  6142. intersections.push.apply(intersections, points);
  6143. break;
  6144. };
  6145. lastX = params[j + 4];
  6146. lastY = params[j + 5];
  6147. j += 6;
  6148. break;
  6149. case 'Z':
  6150. if (firstX !== null) {
  6151. switch (count) {
  6152. case 4:
  6153. points = solver.linesIntersection(firstX, firstY, lastX, lastY, x1, y1, x2, y2);
  6154. if (points) {
  6155. intersections.push(points);
  6156. };
  6157. break;
  6158. case 8:
  6159. points = solver.cubicLineIntersections(x1, x2, x3, x4, y1, y2, y3, y4, firstX, firstY, lastX, lastY);
  6160. intersections.push.apply(intersections, points);
  6161. break;
  6162. }
  6163. };
  6164. break;
  6165. }
  6166. }
  6167. return intersections;
  6168. },
  6169. getIntersections: function(path) {
  6170. var me = this,
  6171. commands = me.commands,
  6172. params = me.params,
  6173. ln = commands.length,
  6174. firstX = null,
  6175. firstY = null,
  6176. lastX = 0,
  6177. lastY = 0,
  6178. intersections = [],
  6179. i, j, points;
  6180. for (i = 0 , j = 0; i < ln; i++) {
  6181. switch (commands[i]) {
  6182. case 'M':
  6183. if (firstX !== null) {
  6184. points = path.getSegmentIntersections.call(path, firstX, firstY, lastX, lastY);
  6185. intersections.push.apply(intersections, points);
  6186. };
  6187. firstX = lastX = params[j];
  6188. firstY = lastY = params[j + 1];
  6189. j += 2;
  6190. break;
  6191. case 'L':
  6192. points = path.getSegmentIntersections.call(path, lastX, lastY, params[j], params[j + 1]);
  6193. intersections.push.apply(intersections, points);
  6194. lastX = params[j];
  6195. lastY = params[j + 1];
  6196. j += 2;
  6197. break;
  6198. case 'C':
  6199. points = path.getSegmentIntersections.call(path, lastX, lastY, params[j], params[j + 1], params[j + 2], params[j + 3], params[j + 4], params[j + 5]);
  6200. intersections.push.apply(intersections, points);
  6201. lastX = params[j + 4];
  6202. lastY = params[j + 5];
  6203. j += 6;
  6204. break;
  6205. case 'Z':
  6206. if (firstX !== null) {
  6207. points = path.getSegmentIntersections.call(path, firstX, firstY, lastX, lastY);
  6208. intersections.push.apply(intersections, points);
  6209. };
  6210. break;
  6211. }
  6212. }
  6213. return intersections;
  6214. }
  6215. });
  6216. /**
  6217. * @class Ext.draw.sprite.Path
  6218. * @extends Ext.draw.sprite.Sprite
  6219. *
  6220. * A sprite that represents a path.
  6221. *
  6222. * @example
  6223. * Ext.create({
  6224. * xtype: 'draw',
  6225. * renderTo: document.body,
  6226. * width: 600,
  6227. * height: 400,
  6228. * sprites: [{
  6229. * type: 'path',
  6230. * path: 'M20,30 c0,-50 75,50 75,0 c0,-50 -75,50 -75,0',
  6231. * fillStyle: '#1F6D91'
  6232. * }]
  6233. * });
  6234. *
  6235. * ### Drawing with SVG Paths
  6236. * You may use special SVG Path syntax to "describe" the drawing path. Here are the SVG path commands:
  6237. *
  6238. * + M = moveto
  6239. * + L = lineto
  6240. * + H = horizontal lineto
  6241. * + V = vertical lineto
  6242. * + C = curveto
  6243. * + S = smooth curveto
  6244. * + Q = quadratic Bézier curve
  6245. * + T = smooth quadratic Bézier curveto
  6246. * + A = elliptical Arc
  6247. * + Z = closepath
  6248. *
  6249. * **Note:** Capital letters indicate that the item should be absolutely positioned.
  6250. * Use lower case letters for relative positioning.
  6251. */
  6252. Ext.define('Ext.draw.sprite.Path', {
  6253. extend: 'Ext.draw.sprite.Sprite',
  6254. requires: [
  6255. 'Ext.draw.Draw',
  6256. 'Ext.draw.Path'
  6257. ],
  6258. alias: [
  6259. 'sprite.path',
  6260. 'Ext.draw.Sprite'
  6261. ],
  6262. type: 'path',
  6263. isPath: true,
  6264. inheritableStatics: {
  6265. def: {
  6266. processors: {
  6267. /**
  6268. * @cfg {String} path The SVG based path string used by the sprite.
  6269. */
  6270. path: function(n, o) {
  6271. if (!(n instanceof Ext.draw.Path)) {
  6272. n = new Ext.draw.Path(n);
  6273. }
  6274. return n;
  6275. }
  6276. },
  6277. aliases: {
  6278. d: 'path'
  6279. },
  6280. triggers: {
  6281. path: 'bbox'
  6282. },
  6283. updaters: {
  6284. path: function(attr) {
  6285. var path = attr.path;
  6286. if (!path || path.bindAttr !== attr) {
  6287. path = new Ext.draw.Path();
  6288. path.bindAttr = attr;
  6289. attr.path = path;
  6290. }
  6291. path.clear();
  6292. this.updatePath(path, attr);
  6293. this.scheduleUpdater(attr, 'bbox', [
  6294. 'path'
  6295. ]);
  6296. }
  6297. }
  6298. }
  6299. },
  6300. updatePlainBBox: function(plain) {
  6301. if (this.attr.path) {
  6302. this.attr.path.getDimension(plain);
  6303. }
  6304. },
  6305. updateTransformedBBox: function(transform) {
  6306. if (this.attr.path) {
  6307. this.attr.path.getDimensionWithTransform(this.attr.matrix, transform);
  6308. }
  6309. },
  6310. render: function(surface, ctx) {
  6311. var mat = this.attr.matrix,
  6312. attr = this.attr;
  6313. if (!attr.path || attr.path.params.length === 0) {
  6314. return;
  6315. }
  6316. mat.toContext(ctx);
  6317. ctx.appendPath(attr.path);
  6318. ctx.fillStroke(attr);
  6319. //<debug>
  6320. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  6321. if (debug) {
  6322. debug.bbox && this.renderBBox(surface, ctx);
  6323. debug.xray && this.renderXRay(surface, ctx);
  6324. }
  6325. },
  6326. //</debug>
  6327. //<debug>
  6328. renderXRay: function(surface, ctx) {
  6329. var attr = this.attr,
  6330. mat = attr.matrix,
  6331. imat = attr.inverseMatrix,
  6332. path = attr.path,
  6333. commands = path.commands,
  6334. params = path.params,
  6335. ln = commands.length,
  6336. twoPi = Math.PI * 2,
  6337. size = 2,
  6338. i, j;
  6339. mat.toContext(ctx);
  6340. ctx.beginPath();
  6341. for (i = 0 , j = 0; i < ln; i++) {
  6342. switch (commands[i]) {
  6343. case 'M':
  6344. ctx.moveTo(params[j] - size, params[j + 1] - size);
  6345. ctx.rect(params[j] - size, params[j + 1] - size, size * 2, size * 2);
  6346. j += 2;
  6347. break;
  6348. case 'L':
  6349. ctx.moveTo(params[j] - size, params[j + 1] - size);
  6350. ctx.rect(params[j] - size, params[j + 1] - size, size * 2, size * 2);
  6351. j += 2;
  6352. break;
  6353. case 'C':
  6354. ctx.moveTo(params[j] + size, params[j + 1]);
  6355. ctx.arc(params[j], params[j + 1], size, 0, twoPi, true);
  6356. j += 2;
  6357. ctx.moveTo(params[j] + size, params[j + 1]);
  6358. ctx.arc(params[j], params[j + 1], size, 0, twoPi, true);
  6359. j += 2;
  6360. ctx.moveTo(params[j] + size * 2, params[j + 1]);
  6361. ctx.rect(params[j] - size, params[j + 1] - size, size * 2, size * 2);
  6362. j += 2;
  6363. break;
  6364. default:
  6365. }
  6366. }
  6367. imat.toContext(ctx);
  6368. ctx.strokeStyle = 'black';
  6369. ctx.strokeOpacity = 1;
  6370. ctx.lineWidth = 1;
  6371. ctx.stroke();
  6372. mat.toContext(ctx);
  6373. ctx.beginPath();
  6374. for (i = 0 , j = 0; i < ln; i++) {
  6375. switch (commands[i]) {
  6376. case 'M':
  6377. ctx.moveTo(params[j], params[j + 1]);
  6378. j += 2;
  6379. break;
  6380. case 'L':
  6381. ctx.moveTo(params[j], params[j + 1]);
  6382. j += 2;
  6383. break;
  6384. case 'C':
  6385. ctx.lineTo(params[j], params[j + 1]);
  6386. j += 2;
  6387. ctx.moveTo(params[j], params[j + 1]);
  6388. j += 2;
  6389. ctx.lineTo(params[j], params[j + 1]);
  6390. j += 2;
  6391. break;
  6392. default:
  6393. }
  6394. }
  6395. imat.toContext(ctx);
  6396. ctx.lineWidth = 0.5;
  6397. ctx.stroke();
  6398. },
  6399. //</debug>
  6400. /**
  6401. * Update the path.
  6402. * @param {Ext.draw.Path} path An empty path to draw on using path API.
  6403. * @param {Object} attr The attribute object. Note: DO NOT use the `sprite.attr` instead of this
  6404. * if you want to work with instancing.
  6405. */
  6406. updatePath: function(path, attr) {}
  6407. });
  6408. /**
  6409. * @private
  6410. * Adds hit testing methods to the Ext.draw.sprite.Path sprite.
  6411. * Included by the Ext.draw.PathUtil.
  6412. */
  6413. Ext.define('Ext.draw.overrides.hittest.sprite.Path', {
  6414. override: 'Ext.draw.sprite.Path',
  6415. requires: [
  6416. 'Ext.draw.Color'
  6417. ],
  6418. /**
  6419. * Tests whether the given point is inside the path.
  6420. * @param x
  6421. * @param y
  6422. * @return {Boolean}
  6423. * @member Ext.draw.sprite.Path
  6424. */
  6425. isPointInPath: function(x, y) {
  6426. var attr = this.attr;
  6427. if (attr.fillStyle === Ext.util.Color.RGBA_NONE) {
  6428. return this.isPointOnPath(x, y);
  6429. }
  6430. var path = attr.path,
  6431. matrix = attr.matrix,
  6432. params, result;
  6433. if (!matrix.isIdentity()) {
  6434. params = path.params.slice(0);
  6435. path.transform(attr.matrix);
  6436. }
  6437. result = path.isPointInPath(x, y);
  6438. if (params) {
  6439. path.params = params;
  6440. }
  6441. return result;
  6442. },
  6443. /**
  6444. * Tests whether the given point is on the path.
  6445. * @param x
  6446. * @param y
  6447. * @return {Boolean}
  6448. * @member Ext.draw.sprite.Path
  6449. */
  6450. isPointOnPath: function(x, y) {
  6451. var attr = this.attr,
  6452. path = attr.path,
  6453. matrix = attr.matrix,
  6454. params, result;
  6455. if (!matrix.isIdentity()) {
  6456. params = path.params.slice(0);
  6457. path.transform(attr.matrix);
  6458. }
  6459. result = path.isPointOnPath(x, y);
  6460. if (params) {
  6461. path.params = params;
  6462. }
  6463. return result;
  6464. },
  6465. /**
  6466. * @method hitTest
  6467. * @inheritdoc Ext.draw.Surface#method-hitTest
  6468. */
  6469. hitTest: function(point, options) {
  6470. var me = this,
  6471. attr = me.attr,
  6472. path = attr.path,
  6473. matrix = attr.matrix,
  6474. x = point[0],
  6475. y = point[1],
  6476. parentResult = me.callParent([
  6477. point,
  6478. options
  6479. ]),
  6480. result = null,
  6481. params, isFilled;
  6482. if (!parentResult) {
  6483. // The sprite is not visible or bounding box wasn't hit.
  6484. return result;
  6485. }
  6486. options = options || Ext.draw.sprite.Sprite.defaultHitTestOptions;
  6487. if (!matrix.isIdentity()) {
  6488. params = path.params.slice(0);
  6489. path.transform(attr.matrix);
  6490. }
  6491. if (options.fill && options.stroke) {
  6492. isFilled = attr.fillStyle !== Ext.util.Color.NONE && attr.fillStyle !== Ext.util.Color.RGBA_NONE;
  6493. if (isFilled) {
  6494. if (path.isPointInPath(x, y)) {
  6495. result = {
  6496. sprite: me
  6497. };
  6498. }
  6499. } else {
  6500. if (path.isPointInPath(x, y) || path.isPointOnPath(x, y)) {
  6501. result = {
  6502. sprite: me
  6503. };
  6504. }
  6505. }
  6506. } else if (options.stroke && !options.fill) {
  6507. if (path.isPointOnPath(x, y)) {
  6508. result = {
  6509. sprite: me
  6510. };
  6511. }
  6512. } else if (options.fill && !options.stroke) {
  6513. if (path.isPointInPath(x, y)) {
  6514. result = {
  6515. sprite: me
  6516. };
  6517. }
  6518. }
  6519. if (params) {
  6520. path.params = params;
  6521. }
  6522. return result;
  6523. },
  6524. /**
  6525. * Returns all points where this sprite intersects the given sprite.
  6526. * The given sprite must be an instance of the {@link Ext.draw.sprite.Path} class
  6527. * or its subclass.
  6528. * @param path
  6529. * @return {Array}
  6530. * @member Ext.draw.sprite.Path
  6531. */
  6532. getIntersections: function(path) {
  6533. if (!(path.isSprite && path.isPath)) {
  6534. return [];
  6535. }
  6536. var aAttr = this.attr,
  6537. bAttr = path.attr,
  6538. aPath = aAttr.path,
  6539. bPath = bAttr.path,
  6540. aMatrix = aAttr.matrix,
  6541. bMatrix = bAttr.matrix,
  6542. aParams, bParams, intersections;
  6543. if (!aMatrix.isIdentity()) {
  6544. aParams = aPath.params.slice(0);
  6545. aPath.transform(aAttr.matrix);
  6546. }
  6547. if (!bMatrix.isIdentity()) {
  6548. bParams = bPath.params.slice(0);
  6549. bPath.transform(bAttr.matrix);
  6550. }
  6551. intersections = aPath.getIntersections(bPath);
  6552. if (aParams) {
  6553. aPath.params = aParams;
  6554. }
  6555. if (bParams) {
  6556. bPath.params = bParams;
  6557. }
  6558. return intersections;
  6559. }
  6560. });
  6561. /**
  6562. * @class Ext.draw.sprite.Circle
  6563. * @extends Ext.draw.sprite.Path
  6564. *
  6565. * A sprite that represents a circle.
  6566. *
  6567. * @example
  6568. * Ext.create({
  6569. * xtype: 'draw',
  6570. * renderTo: document.body,
  6571. * width: 600,
  6572. * height: 400,
  6573. * sprites: [{
  6574. * type: 'circle',
  6575. * cx: 100,
  6576. * cy: 100,
  6577. * r: 50,
  6578. * fillStyle: '#1F6D91'
  6579. * }]
  6580. * });
  6581. */
  6582. Ext.define('Ext.draw.sprite.Circle', {
  6583. extend: 'Ext.draw.sprite.Path',
  6584. alias: 'sprite.circle',
  6585. type: 'circle',
  6586. inheritableStatics: {
  6587. def: {
  6588. processors: {
  6589. /**
  6590. * @cfg {Number} [cx=0] The center coordinate of the sprite on the x-axis.
  6591. */
  6592. cx: 'number',
  6593. /**
  6594. * @cfg {Number} [cy=0] The center coordinate of the sprite on the y-axis.
  6595. */
  6596. cy: 'number',
  6597. /**
  6598. * @cfg {Number} [r=0] The radius of the sprite.
  6599. */
  6600. r: 'number'
  6601. },
  6602. aliases: {
  6603. radius: 'r',
  6604. x: 'cx',
  6605. y: 'cy',
  6606. centerX: 'cx',
  6607. centerY: 'cy'
  6608. },
  6609. defaults: {
  6610. cx: 0,
  6611. cy: 0,
  6612. r: 4
  6613. },
  6614. triggers: {
  6615. cx: 'path',
  6616. cy: 'path',
  6617. r: 'path'
  6618. }
  6619. }
  6620. },
  6621. updatePlainBBox: function(plain) {
  6622. var attr = this.attr,
  6623. cx = attr.cx,
  6624. cy = attr.cy,
  6625. r = attr.r;
  6626. plain.x = cx - r;
  6627. plain.y = cy - r;
  6628. plain.width = r + r;
  6629. plain.height = r + r;
  6630. },
  6631. updateTransformedBBox: function(transform) {
  6632. var attr = this.attr,
  6633. cx = attr.cx,
  6634. cy = attr.cy,
  6635. r = attr.r,
  6636. matrix = attr.matrix,
  6637. scaleX = matrix.getScaleX(),
  6638. scaleY = matrix.getScaleY(),
  6639. rx, ry;
  6640. rx = scaleX * r;
  6641. ry = scaleY * r;
  6642. transform.x = matrix.x(cx, cy) - rx;
  6643. transform.y = matrix.y(cx, cy) - ry;
  6644. transform.width = rx + rx;
  6645. transform.height = ry + ry;
  6646. },
  6647. updatePath: function(path, attr) {
  6648. path.arc(attr.cx, attr.cy, attr.r, 0, Math.PI * 2, false);
  6649. }
  6650. });
  6651. /**
  6652. * @class Ext.draw.sprite.Arc
  6653. * @extend Ext.draw.sprite.Circle
  6654. *
  6655. * A sprite that represents a circular arc.
  6656. *
  6657. * @example
  6658. * Ext.create({
  6659. * xtype: 'draw',
  6660. * renderTo: document.body,
  6661. * width: 600,
  6662. * height: 400,
  6663. * sprites: [{
  6664. * type: 'arc',
  6665. * cx: 100,
  6666. * cy: 100,
  6667. * r: 80,
  6668. * fillStyle: '#1F6D91',
  6669. * startAngle: 0,
  6670. * endAngle: Math.PI,
  6671. * anticlockwise: true
  6672. * }]
  6673. * });
  6674. */
  6675. Ext.define('Ext.draw.sprite.Arc', {
  6676. extend: 'Ext.draw.sprite.Circle',
  6677. alias: 'sprite.arc',
  6678. type: 'arc',
  6679. inheritableStatics: {
  6680. def: {
  6681. processors: {
  6682. /**
  6683. * @cfg {Number} [startAngle=0] The beginning angle of the arc.
  6684. */
  6685. startAngle: 'number',
  6686. /**
  6687. * @cfg {Number} [endAngle=Math.PI*2] The ending angle of the arc.
  6688. */
  6689. endAngle: 'number',
  6690. /**
  6691. * @cfg {Boolean} [anticlockwise=false] Determines whether or not the arc is drawn clockwise.
  6692. */
  6693. anticlockwise: 'bool'
  6694. },
  6695. aliases: {
  6696. from: 'startAngle',
  6697. to: 'endAngle',
  6698. start: 'startAngle',
  6699. end: 'endAngle'
  6700. },
  6701. defaults: {
  6702. startAngle: 0,
  6703. endAngle: Math.PI * 2,
  6704. anticlockwise: false
  6705. },
  6706. triggers: {
  6707. startAngle: 'path',
  6708. endAngle: 'path',
  6709. anticlockwise: 'path'
  6710. }
  6711. }
  6712. },
  6713. updatePath: function(path, attr) {
  6714. path.arc(attr.cx, attr.cy, attr.r, attr.startAngle, attr.endAngle, attr.anticlockwise);
  6715. }
  6716. });
  6717. /**
  6718. * A sprite that represents an arrow.
  6719. *
  6720. * @example
  6721. * Ext.create({
  6722. * xtype: 'draw',
  6723. * renderTo: document.body,
  6724. * width: 600,
  6725. * height: 400,
  6726. * sprites: [{
  6727. * type: 'arrow',
  6728. * translationX: 100,
  6729. * translationY: 100,
  6730. * size: 40,
  6731. * fillStyle: '#30BDA7'
  6732. * }]
  6733. * });
  6734. */
  6735. Ext.define('Ext.draw.sprite.Arrow', {
  6736. extend: 'Ext.draw.sprite.Path',
  6737. alias: 'sprite.arrow',
  6738. inheritableStatics: {
  6739. def: {
  6740. processors: {
  6741. x: 'number',
  6742. y: 'number',
  6743. /**
  6744. * @cfg {Number} [size=4] The size of the sprite.
  6745. * Meant to be comparable to the size of a circle sprite with the same radius.
  6746. */
  6747. size: 'number'
  6748. },
  6749. defaults: {
  6750. x: 0,
  6751. y: 0,
  6752. size: 4
  6753. },
  6754. triggers: {
  6755. x: 'path',
  6756. y: 'path',
  6757. size: 'path'
  6758. }
  6759. }
  6760. },
  6761. updatePath: function(path, attr) {
  6762. var s = attr.size * 1.5,
  6763. x = attr.x - attr.lineWidth / 2,
  6764. y = attr.y;
  6765. path.fromSvgString('M'.concat(x - s * 0.7, ',', y - s * 0.4, 'l', [
  6766. s * 0.6,
  6767. 0,
  6768. 0,
  6769. -s * 0.4,
  6770. s,
  6771. s * 0.8,
  6772. -s,
  6773. s * 0.8,
  6774. 0,
  6775. -s * 0.4,
  6776. -s * 0.6,
  6777. 0
  6778. ], 'z'));
  6779. }
  6780. });
  6781. /**
  6782. * @class Ext.draw.sprite.Composite
  6783. *
  6784. * Represents a group of sprites.
  6785. * Composite's sprites are rendered in the order they've been added to the Composite.
  6786. * The rendering order of composite sprites themselves is determined by the value of
  6787. * their zIndex attribute, just like with any other sprite.
  6788. * Every sprite that is added to the Composite is removed from whatever Surface/Composite
  6789. * it belongs to.
  6790. */
  6791. Ext.define('Ext.draw.sprite.Composite', {
  6792. extend: 'Ext.draw.sprite.Sprite',
  6793. alias: 'sprite.composite',
  6794. type: 'composite',
  6795. isComposite: true,
  6796. config: {
  6797. sprites: []
  6798. },
  6799. constructor: function(config) {
  6800. this.sprites = [];
  6801. this.map = {};
  6802. this.callParent([
  6803. config
  6804. ]);
  6805. },
  6806. /**
  6807. * Adds sprite(s) to the composite.
  6808. * @param {Ext.draw.sprite.Sprite/Ext.draw.sprite.Sprite[]/Object/Object[]} sprite
  6809. * @return {Ext.draw.sprite.Sprite/Ext.draw.sprite.Sprite[]}
  6810. */
  6811. addSprite: function(sprite) {
  6812. var i = 0,
  6813. results;
  6814. if (Ext.isArray(sprite)) {
  6815. results = [];
  6816. while (i < sprite.length) {
  6817. results.push(this.addSprite(sprite[i++]));
  6818. }
  6819. return results;
  6820. }
  6821. if (sprite && sprite.type && !sprite.isSprite) {
  6822. sprite = Ext.create('sprite.' + sprite.type, sprite);
  6823. }
  6824. if (!sprite || !sprite.isSprite || sprite.isComposite) {
  6825. return null;
  6826. }
  6827. sprite.setSurface(null);
  6828. sprite.setParent(this);
  6829. var attr = this.attr,
  6830. oldTransformations = sprite.applyTransformations;
  6831. sprite.applyTransformations = function(force) {
  6832. if (sprite.attr.dirtyTransform) {
  6833. attr.dirtyTransform = true;
  6834. attr.bbox.plain.dirty = true;
  6835. attr.bbox.transform.dirty = true;
  6836. }
  6837. oldTransformations.call(sprite, force);
  6838. };
  6839. this.sprites.push(sprite);
  6840. this.map[sprite.id] = sprite.getId();
  6841. attr.bbox.plain.dirty = true;
  6842. attr.bbox.transform.dirty = true;
  6843. return sprite;
  6844. },
  6845. /**
  6846. * @deprecated 6.2.1 Use {@link #addSprite} instead.
  6847. */
  6848. add: function(sprite) {
  6849. return this.addSprite(sprite);
  6850. },
  6851. removeSprite: function(sprite, isDestroy) {
  6852. var me = this,
  6853. id, isOwnSprite;
  6854. if (sprite) {
  6855. if (sprite.charAt) {
  6856. // is String
  6857. sprite = me.map[sprite];
  6858. }
  6859. if (!sprite || !sprite.isSprite) {
  6860. return null;
  6861. }
  6862. if (sprite.destroyed || sprite.destroying) {
  6863. return sprite;
  6864. }
  6865. id = sprite.getId();
  6866. isOwnSprite = me.map[id];
  6867. delete me.map[id];
  6868. if (isDestroy) {
  6869. sprite.destroy();
  6870. }
  6871. if (!isOwnSprite) {
  6872. return sprite;
  6873. }
  6874. sprite.setParent(null);
  6875. // sprite.setSurface(null);
  6876. Ext.Array.remove(me.sprites, sprite);
  6877. me.dirtyZIndex = true;
  6878. me.setDirty(true);
  6879. }
  6880. return sprite || null;
  6881. },
  6882. /**
  6883. * @deprecated 6.2.1 Use {@link #addSprite} instead.
  6884. * Adds a list of sprites to the composite.
  6885. * @param {Ext.draw.sprite.Sprite[]|Object[]|Ext.draw.sprite.Sprite|Object} sprites
  6886. */
  6887. addAll: function(sprites) {
  6888. if (sprites.isSprite || sprites.type) {
  6889. this.add(sprites);
  6890. } else if (Ext.isArray(sprites)) {
  6891. var i = 0;
  6892. while (i < sprites.length) {
  6893. this.add(sprites[i++]);
  6894. }
  6895. }
  6896. },
  6897. /**
  6898. * Updates the bounding box of the composite, which contains the bounding box of all sprites in the composite.
  6899. */
  6900. updatePlainBBox: function(plain) {
  6901. var me = this,
  6902. left = Infinity,
  6903. right = -Infinity,
  6904. top = Infinity,
  6905. bottom = -Infinity,
  6906. sprite, bbox, i, ln;
  6907. for (i = 0 , ln = me.sprites.length; i < ln; i++) {
  6908. sprite = me.sprites[i];
  6909. sprite.applyTransformations();
  6910. bbox = sprite.getBBox();
  6911. if (left > bbox.x) {
  6912. left = bbox.x;
  6913. }
  6914. if (right < bbox.x + bbox.width) {
  6915. right = bbox.x + bbox.width;
  6916. }
  6917. if (top > bbox.y) {
  6918. top = bbox.y;
  6919. }
  6920. if (bottom < bbox.y + bbox.height) {
  6921. bottom = bbox.y + bbox.height;
  6922. }
  6923. }
  6924. plain.x = left;
  6925. plain.y = top;
  6926. plain.width = right - left;
  6927. plain.height = bottom - top;
  6928. },
  6929. isVisible: function() {
  6930. // Override the abstract Sprite's method.
  6931. // Composite uses a simpler check, because it has no fill or stroke
  6932. // style of its own, it just houses other sprites.
  6933. var attr = this.attr,
  6934. parent = this.getParent(),
  6935. hasParent = parent && (parent.isSurface || parent.isVisible()),
  6936. isSeen = hasParent && !attr.hidden && attr.globalAlpha;
  6937. return !!isSeen;
  6938. },
  6939. /**
  6940. * Renders all sprites contained in the composite to the surface.
  6941. */
  6942. render: function(surface, ctx, rect) {
  6943. var me = this,
  6944. attr = me.attr,
  6945. mat = me.attr.matrix,
  6946. sprites = me.sprites,
  6947. ln = sprites.length,
  6948. i = 0;
  6949. mat.toContext(ctx);
  6950. for (; i < ln; i++) {
  6951. surface.renderSprite(sprites[i], rect);
  6952. }
  6953. //<debug>
  6954. var debug = attr.debug || me.statics().debug || Ext.draw.sprite.Sprite.debug;
  6955. if (debug) {
  6956. attr.inverseMatrix.toContext(ctx);
  6957. if (debug.bbox) {
  6958. me.renderBBox(surface, ctx);
  6959. }
  6960. }
  6961. },
  6962. //</debug>
  6963. destroy: function() {
  6964. var me = this,
  6965. sprites = me.sprites,
  6966. ln = sprites.length,
  6967. i;
  6968. for (i = 0; i < ln; i++) {
  6969. sprites[i].destroy();
  6970. }
  6971. sprites.length = 0;
  6972. me.callParent();
  6973. }
  6974. });
  6975. /**
  6976. * A sprite that represents a cross.
  6977. *
  6978. * @example
  6979. * Ext.create({
  6980. * xtype: 'draw',
  6981. * renderTo: document.body,
  6982. * width: 600,
  6983. * height: 400,
  6984. * sprites: [{
  6985. * type: 'cross',
  6986. * translationX: 100,
  6987. * translationY: 100,
  6988. * size: 40,
  6989. * fillStyle: '#1F6D91'
  6990. * }]
  6991. * });
  6992. */
  6993. Ext.define('Ext.draw.sprite.Cross', {
  6994. extend: 'Ext.draw.sprite.Path',
  6995. alias: 'sprite.cross',
  6996. inheritableStatics: {
  6997. def: {
  6998. processors: {
  6999. x: 'number',
  7000. y: 'number',
  7001. /**
  7002. * @cfg {Number} [size=4] The size of the sprite.
  7003. * Meant to be comparable to the size of a circle sprite with the same radius.
  7004. */
  7005. size: 'number'
  7006. },
  7007. defaults: {
  7008. x: 0,
  7009. y: 0,
  7010. size: 4
  7011. },
  7012. triggers: {
  7013. x: 'path',
  7014. y: 'path',
  7015. size: 'path'
  7016. }
  7017. }
  7018. },
  7019. updatePath: function(path, attr) {
  7020. var s = attr.size / 1.7,
  7021. x = attr.x - attr.lineWidth / 2,
  7022. y = attr.y;
  7023. path.fromSvgString('M'.concat(x - s, ',', y, 'l', [
  7024. -s,
  7025. -s,
  7026. s,
  7027. -s,
  7028. s,
  7029. s,
  7030. s,
  7031. -s,
  7032. s,
  7033. s,
  7034. -s,
  7035. s,
  7036. s,
  7037. s,
  7038. -s,
  7039. s,
  7040. -s,
  7041. -s,
  7042. -s,
  7043. s,
  7044. -s,
  7045. -s,
  7046. 'z'
  7047. ]));
  7048. }
  7049. });
  7050. /**
  7051. * A sprite that represents a diamond.
  7052. *
  7053. * @example
  7054. * Ext.create({
  7055. * xtype: 'draw',
  7056. * renderTo: document.body,
  7057. * width: 600,
  7058. * height: 400,
  7059. * sprites: [{
  7060. * type: 'diamond',
  7061. * translationX: 100,
  7062. * translationY: 100,
  7063. * size: 40,
  7064. * fillStyle: '#1F6D91'
  7065. * }]
  7066. * });
  7067. */
  7068. Ext.define('Ext.draw.sprite.Diamond', {
  7069. extend: 'Ext.draw.sprite.Path',
  7070. alias: 'sprite.diamond',
  7071. inheritableStatics: {
  7072. def: {
  7073. processors: {
  7074. x: 'number',
  7075. y: 'number',
  7076. /**
  7077. * @cfg {Number} [size=4] The size of the sprite.
  7078. * Meant to be comparable to the size of a circle sprite with the same radius.
  7079. */
  7080. size: 'number'
  7081. },
  7082. defaults: {
  7083. x: 0,
  7084. y: 0,
  7085. size: 4
  7086. },
  7087. triggers: {
  7088. x: 'path',
  7089. y: 'path',
  7090. size: 'path'
  7091. }
  7092. }
  7093. },
  7094. updatePath: function(path, attr) {
  7095. var s = attr.size * 1.25,
  7096. x = attr.x - attr.lineWidth / 2,
  7097. y = attr.y;
  7098. path.fromSvgString([
  7099. 'M',
  7100. x,
  7101. y - s,
  7102. 'l',
  7103. s,
  7104. s,
  7105. -s,
  7106. s,
  7107. -s,
  7108. -s,
  7109. s,
  7110. -s,
  7111. 'z'
  7112. ]);
  7113. }
  7114. });
  7115. /**
  7116. * @class Ext.draw.sprite.Ellipse
  7117. * @extends Ext.draw.sprite.Path
  7118. *
  7119. * A sprite that represents an ellipse.
  7120. *
  7121. * @example
  7122. * Ext.create({
  7123. * xtype: 'draw',
  7124. * renderTo: document.body,
  7125. * width: 600,
  7126. * height: 400,
  7127. * sprites: [{
  7128. * type: 'ellipse',
  7129. * cx: 100,
  7130. * cy: 100,
  7131. * rx: 80,
  7132. * ry: 50,
  7133. * fillStyle: '#1F6D91'
  7134. * }]
  7135. * });
  7136. */
  7137. Ext.define("Ext.draw.sprite.Ellipse", {
  7138. extend: "Ext.draw.sprite.Path",
  7139. alias: 'sprite.ellipse',
  7140. type: 'ellipse',
  7141. inheritableStatics: {
  7142. def: {
  7143. processors: {
  7144. /**
  7145. * @cfg {Number} [cx=0] The center coordinate of the sprite on the x-axis.
  7146. */
  7147. cx: "number",
  7148. /**
  7149. * @cfg {Number} [cy=0] The center coordinate of the sprite on the y-axis.
  7150. */
  7151. cy: "number",
  7152. /**
  7153. * @cfg {Number} [rx=1] The radius of the sprite on the x-axis.
  7154. */
  7155. rx: "number",
  7156. /**
  7157. * @cfg {Number} [ry=1] The radius of the sprite on the y-axis.
  7158. */
  7159. ry: "number",
  7160. /**
  7161. * @cfg {Number} [axisRotation=0] The rotation of the sprite about its axis.
  7162. */
  7163. axisRotation: "number"
  7164. },
  7165. aliases: {
  7166. radius: "r",
  7167. x: "cx",
  7168. y: "cy",
  7169. centerX: "cx",
  7170. centerY: "cy",
  7171. radiusX: "rx",
  7172. radiusY: "ry"
  7173. },
  7174. defaults: {
  7175. cx: 0,
  7176. cy: 0,
  7177. rx: 1,
  7178. ry: 1,
  7179. axisRotation: 0
  7180. },
  7181. triggers: {
  7182. cx: 'path',
  7183. cy: 'path',
  7184. rx: 'path',
  7185. ry: 'path',
  7186. axisRotation: 'path'
  7187. }
  7188. }
  7189. },
  7190. updatePlainBBox: function(plain) {
  7191. var attr = this.attr,
  7192. cx = attr.cx,
  7193. cy = attr.cy,
  7194. rx = attr.rx,
  7195. ry = attr.ry;
  7196. plain.x = cx - rx;
  7197. plain.y = cy - ry;
  7198. plain.width = rx + rx;
  7199. plain.height = ry + ry;
  7200. },
  7201. updateTransformedBBox: function(transform) {
  7202. var attr = this.attr,
  7203. cx = attr.cx,
  7204. cy = attr.cy,
  7205. rx = attr.rx,
  7206. ry = attr.ry,
  7207. rxy = ry / rx,
  7208. matrix = attr.matrix.clone(),
  7209. xx, xy, yx, yy, dx, dy, w, h;
  7210. matrix.append(1, 0, 0, rxy, 0, cy * (1 - rxy));
  7211. xx = matrix.getXX();
  7212. yx = matrix.getYX();
  7213. dx = matrix.getDX();
  7214. xy = matrix.getXY();
  7215. yy = matrix.getYY();
  7216. dy = matrix.getDY();
  7217. w = Math.sqrt(xx * xx + yx * yx) * rx;
  7218. h = Math.sqrt(xy * xy + yy * yy) * rx;
  7219. transform.x = cx * xx + cy * yx + dx - w;
  7220. transform.y = cx * xy + cy * yy + dy - h;
  7221. transform.width = w + w;
  7222. transform.height = h + h;
  7223. },
  7224. updatePath: function(path, attr) {
  7225. path.ellipse(attr.cx, attr.cy, attr.rx, attr.ry, attr.axisRotation, 0, Math.PI * 2, false);
  7226. }
  7227. });
  7228. /**
  7229. * @class Ext.draw.sprite.EllipticalArc
  7230. * @extends Ext.draw.sprite.Ellipse
  7231. *
  7232. * A sprite that represents an elliptical arc.
  7233. *
  7234. * @example
  7235. * Ext.create({
  7236. * xtype: 'draw',
  7237. * renderTo: document.body,
  7238. * width: 600,
  7239. * height: 400,
  7240. * sprites: [{
  7241. * type: 'ellipticalArc',
  7242. * cx: 100,
  7243. * cy: 100,
  7244. * rx: 80,
  7245. * ry: 50,
  7246. * fillStyle: '#1F6D91',
  7247. * startAngle: 0,
  7248. * endAngle: Math.PI,
  7249. * anticlockwise: true
  7250. * }]
  7251. * });
  7252. */
  7253. Ext.define('Ext.draw.sprite.EllipticalArc', {
  7254. extend: 'Ext.draw.sprite.Ellipse',
  7255. alias: 'sprite.ellipticalArc',
  7256. type: 'ellipticalArc',
  7257. inheritableStatics: {
  7258. def: {
  7259. processors: {
  7260. /**
  7261. * @cfg {Number} [startAngle=0] The beginning angle of the arc.
  7262. */
  7263. startAngle: 'number',
  7264. /**
  7265. * @cfg {Number} [endAngle=Math.PI*2] The ending angle of the arc.
  7266. */
  7267. endAngle: 'number',
  7268. /**
  7269. * @cfg {Boolean} [anticlockwise=false] Determines whether or not the arc is drawn clockwise.
  7270. */
  7271. anticlockwise: 'bool'
  7272. },
  7273. aliases: {
  7274. from: 'startAngle',
  7275. to: 'endAngle',
  7276. start: 'startAngle',
  7277. end: 'endAngle'
  7278. },
  7279. defaults: {
  7280. startAngle: 0,
  7281. endAngle: Math.PI * 2,
  7282. anticlockwise: false
  7283. },
  7284. triggers: {
  7285. startAngle: 'path',
  7286. endAngle: 'path',
  7287. anticlockwise: 'path'
  7288. }
  7289. }
  7290. },
  7291. updatePath: function(path, attr) {
  7292. path.ellipse(attr.cx, attr.cy, attr.rx, attr.ry, attr.axisRotation, attr.startAngle, attr.endAngle, attr.anticlockwise);
  7293. }
  7294. });
  7295. /**
  7296. * @class Ext.draw.sprite.Rect
  7297. * @extends Ext.draw.sprite.Path
  7298. *
  7299. * A sprite that represents a rectangle.
  7300. *
  7301. * @example
  7302. * Ext.create({
  7303. * xtype: 'draw',
  7304. * renderTo: document.body,
  7305. * width: 600,
  7306. * height: 400,
  7307. * sprites: [{
  7308. * type: 'rect',
  7309. * x: 50,
  7310. * y: 50,
  7311. * width: 100,
  7312. * height: 100,
  7313. * fillStyle: '#1F6D91'
  7314. * }]
  7315. * });
  7316. */
  7317. Ext.define('Ext.draw.sprite.Rect', {
  7318. extend: 'Ext.draw.sprite.Path',
  7319. alias: 'sprite.rect',
  7320. type: 'rect',
  7321. inheritableStatics: {
  7322. def: {
  7323. processors: {
  7324. /**
  7325. * @cfg {Number} [x=0] The position of the sprite on the x-axis.
  7326. */
  7327. x: 'number',
  7328. /**
  7329. * @cfg {Number} [y=0] The position of the sprite on the y-axis.
  7330. */
  7331. y: 'number',
  7332. /**
  7333. * @cfg {Number} [width=8] The width of the sprite.
  7334. */
  7335. width: 'number',
  7336. /**
  7337. * @cfg {Number} [height=8] The height of the sprite.
  7338. */
  7339. height: 'number',
  7340. /**
  7341. * @cfg {Number} [radius=0] The radius of the rounded corners.
  7342. */
  7343. radius: 'number'
  7344. },
  7345. aliases: {},
  7346. triggers: {
  7347. x: 'path',
  7348. y: 'path',
  7349. width: 'path',
  7350. height: 'path',
  7351. radius: 'path'
  7352. },
  7353. defaults: {
  7354. x: 0,
  7355. y: 0,
  7356. width: 8,
  7357. height: 8,
  7358. radius: 0
  7359. }
  7360. }
  7361. },
  7362. updatePlainBBox: function(plain) {
  7363. var attr = this.attr;
  7364. plain.x = attr.x;
  7365. plain.y = attr.y;
  7366. plain.width = attr.width;
  7367. plain.height = attr.height;
  7368. },
  7369. updateTransformedBBox: function(transform, plain) {
  7370. this.attr.matrix.transformBBox(plain, this.attr.radius, transform);
  7371. },
  7372. updatePath: function(path, attr) {
  7373. var x = attr.x,
  7374. y = attr.y,
  7375. width = attr.width,
  7376. height = attr.height,
  7377. radius = Math.min(attr.radius, Math.abs(height) * 0.5, Math.abs(width) * 0.5);
  7378. if (radius === 0) {
  7379. path.rect(x, y, width, height);
  7380. } else {
  7381. path.moveTo(x + radius, y);
  7382. path.arcTo(x + width, y, x + width, y + height, radius);
  7383. path.arcTo(x + width, y + height, x, y + height, radius);
  7384. path.arcTo(x, y + height, x, y, radius);
  7385. path.arcTo(x, y, x + radius, y, radius);
  7386. path.closePath();
  7387. }
  7388. }
  7389. });
  7390. /**
  7391. * @class Ext.draw.sprite.Image
  7392. * @extends Ext.draw.sprite.Rect
  7393. *
  7394. * A sprite that represents an image.
  7395. */
  7396. Ext.define('Ext.draw.sprite.Image', {
  7397. extend: 'Ext.draw.sprite.Rect',
  7398. alias: 'sprite.image',
  7399. type: 'image',
  7400. statics: {
  7401. imageLoaders: {}
  7402. },
  7403. inheritableStatics: {
  7404. def: {
  7405. processors: {
  7406. /**
  7407. * @cfg {String} [src=''] The image source of the sprite.
  7408. */
  7409. src: 'string'
  7410. },
  7411. /**
  7412. * @private
  7413. * @cfg {Number} radius
  7414. */
  7415. triggers: {
  7416. src: 'src'
  7417. },
  7418. updaters: {
  7419. src: 'updateSource'
  7420. },
  7421. defaults: {
  7422. src: '',
  7423. /**
  7424. * @cfg {Number} [width=null] The width of the image.
  7425. * For consistent image size on all devices the width must be explicitly set.
  7426. * Otherwise the natural image width devided by the device pixel ratio
  7427. * (for a crisp looking image) will be used as the width of the sprite.
  7428. */
  7429. width: null,
  7430. /**
  7431. * @cfg {Number} [height=null] The height of the image.
  7432. * For consistent image size on all devices the height must be explicitly set.
  7433. * Otherwise the natural image height devided by the device pixel ratio
  7434. * (for a crisp looking image) will be used as the height of the sprite.
  7435. */
  7436. height: null
  7437. }
  7438. }
  7439. },
  7440. updateSurface: function(surface) {
  7441. if (surface) {
  7442. this.updateSource(this.attr);
  7443. }
  7444. },
  7445. updateSource: function(attr) {
  7446. var me = this,
  7447. src = attr.src,
  7448. surface = me.getSurface(),
  7449. loadingStub = Ext.draw.sprite.Image.imageLoaders[src],
  7450. width = attr.width,
  7451. height = attr.height,
  7452. imageLoader, i;
  7453. if (!surface) {
  7454. // First time this is called the sprite won't have a surface yet.
  7455. return;
  7456. }
  7457. if (!loadingStub) {
  7458. imageLoader = new Image();
  7459. loadingStub = Ext.draw.sprite.Image.imageLoaders[src] = {
  7460. image: imageLoader,
  7461. done: false,
  7462. pendingSprites: [
  7463. me
  7464. ],
  7465. pendingSurfaces: [
  7466. surface
  7467. ]
  7468. };
  7469. imageLoader.width = width;
  7470. imageLoader.height = height;
  7471. imageLoader.onload = function() {
  7472. var item;
  7473. if (!loadingStub.done) {
  7474. loadingStub.done = true;
  7475. for (i = 0; i < loadingStub.pendingSprites.length; i++) {
  7476. item = loadingStub.pendingSprites[i];
  7477. if (!item.destroyed) {
  7478. item.setDirty(true);
  7479. }
  7480. }
  7481. for (i = 0; i < loadingStub.pendingSurfaces.length; i++) {
  7482. item = loadingStub.pendingSurfaces[i];
  7483. if (!item.destroyed) {
  7484. item.renderFrame();
  7485. }
  7486. }
  7487. }
  7488. };
  7489. imageLoader.src = src;
  7490. } else {
  7491. Ext.Array.include(loadingStub.pendingSprites, me);
  7492. Ext.Array.include(loadingStub.pendingSurfaces, surface);
  7493. }
  7494. },
  7495. render: function(surface, ctx) {
  7496. var me = this,
  7497. attr = me.attr,
  7498. mat = attr.matrix,
  7499. src = attr.src,
  7500. x = attr.x,
  7501. y = attr.y,
  7502. width = attr.width,
  7503. height = attr.height,
  7504. loadingStub = Ext.draw.sprite.Image.imageLoaders[src],
  7505. image;
  7506. if (loadingStub && loadingStub.done) {
  7507. mat.toContext(ctx);
  7508. image = loadingStub.image;
  7509. ctx.drawImage(image, x, y, width || (image.naturalWidth || image.width) / surface.devicePixelRatio, height || (image.naturalHeight || image.height) / surface.devicePixelRatio);
  7510. }
  7511. //<debug>
  7512. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  7513. if (debug) {
  7514. debug.bbox && this.renderBBox(surface, ctx);
  7515. }
  7516. },
  7517. //</debug>
  7518. /**
  7519. * @private
  7520. */
  7521. isVisible: function() {
  7522. var attr = this.attr,
  7523. parent = this.getParent(),
  7524. hasParent = parent && (parent.isSurface || parent.isVisible()),
  7525. isSeen = hasParent && !attr.hidden && attr.globalAlpha;
  7526. return !!isSeen;
  7527. }
  7528. });
  7529. /**
  7530. * @class Ext.draw.sprite.Instancing
  7531. * @extends Ext.draw.sprite.Sprite
  7532. *
  7533. * Sprite that represents multiple instances based on the given template.
  7534. */
  7535. Ext.define('Ext.draw.sprite.Instancing', {
  7536. extend: 'Ext.draw.sprite.Sprite',
  7537. alias: 'sprite.instancing',
  7538. type: 'instancing',
  7539. isInstancing: true,
  7540. config: {
  7541. /**
  7542. * @cfg {Object} [template] The sprite template used by all instances.
  7543. */
  7544. template: null,
  7545. /**
  7546. * @cfg {Array} [instances]
  7547. * The instances of the {@link #template} sprite as configs of attributes.
  7548. */
  7549. instances: null
  7550. },
  7551. instances: null,
  7552. applyTemplate: function(template) {
  7553. //<debug>
  7554. if (!Ext.isObject(template)) {
  7555. 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.");
  7556. } else if (template.isInstancing || template.isComposite) {
  7557. Ext.raise("Can't use an instancing or composite sprite " + "as a template for an instancing sprite.");
  7558. }
  7559. //</debug>
  7560. if (!template.isSprite) {
  7561. if (!template.xclass && !template.type) {
  7562. // For compatibility with legacy charts.
  7563. template.type = 'circle';
  7564. }
  7565. template = Ext.create(template.xclass || 'sprite.' + template.type, template);
  7566. }
  7567. var surface = template.getSurface();
  7568. if (surface) {
  7569. surface.remove(template);
  7570. }
  7571. template.setParent(this);
  7572. return template;
  7573. },
  7574. updateTemplate: function(template, oldTemplate) {
  7575. if (oldTemplate) {
  7576. delete oldTemplate.ownAttr;
  7577. }
  7578. template.setSurface(this.getSurface());
  7579. // ownAttr is used to get a reference to the template's attributes
  7580. // when one of the instances is rendering, as at that moment the template's
  7581. // attributes (template.attr) are the instance's attributes.
  7582. template.ownAttr = template.attr;
  7583. this.clearAll();
  7584. this.setDirty(true);
  7585. },
  7586. updateInstances: function(instances) {
  7587. this.clearAll();
  7588. if (Ext.isArray(instances)) {
  7589. for (var i = 0,
  7590. ln = instances.length; i < ln; i++) {
  7591. this.add(instances[i]);
  7592. }
  7593. }
  7594. },
  7595. updateSurface: function(surface) {
  7596. var template = this.getTemplate();
  7597. if (template && !template.destroyed) {
  7598. template.setSurface(surface);
  7599. }
  7600. },
  7601. get: function(index) {
  7602. return this.instances[index];
  7603. },
  7604. getCount: function() {
  7605. return this.instances.length;
  7606. },
  7607. clearAll: function() {
  7608. var template = this.getTemplate();
  7609. template.attr.children = this.instances = [];
  7610. this.position = 0;
  7611. },
  7612. /**
  7613. * @deprecated 6.2.0
  7614. * Deprecated, use the {@link #add} method instead.
  7615. */
  7616. createInstance: function(config, bypassNormalization, avoidCopy) {
  7617. return this.add(config, bypassNormalization, avoidCopy);
  7618. },
  7619. /**
  7620. * Creates a new sprite instance.
  7621. *
  7622. * @param {Object} config The configuration of the instance.
  7623. * @param {Boolean} [bypassNormalization] 'true' to bypass attribute normalization.
  7624. * @param {Boolean} [avoidCopy] 'true' to avoid copying the `config` object.
  7625. * @return {Object} The attributes of the instance.
  7626. */
  7627. add: function(config, bypassNormalization, avoidCopy) {
  7628. var me = this,
  7629. template = me.getTemplate(),
  7630. originalAttr = template.attr,
  7631. attr = Ext.Object.chain(originalAttr);
  7632. template.modifiers.target.prepareAttributes(attr);
  7633. template.attr = attr;
  7634. template.setAttributes(config, bypassNormalization, avoidCopy);
  7635. attr.template = template;
  7636. me.instances.push(attr);
  7637. template.attr = originalAttr;
  7638. me.position++;
  7639. return attr;
  7640. },
  7641. /**
  7642. * Not supported.
  7643. *
  7644. * @return {null}
  7645. */
  7646. getBBox: function() {
  7647. return null;
  7648. },
  7649. /**
  7650. * Returns the bounding box for the instance at the given index.
  7651. *
  7652. * @param {Number} index The index of the instance.
  7653. * @param {Boolean} [isWithoutTransform] 'true' to not apply sprite transforms to the bounding box.
  7654. * @return {Object} The bounding box for the instance.
  7655. */
  7656. getBBoxFor: function(index, isWithoutTransform) {
  7657. var template = this.getTemplate(),
  7658. originalAttr = template.attr,
  7659. bbox;
  7660. template.attr = this.instances[index];
  7661. bbox = template.getBBox(isWithoutTransform);
  7662. template.attr = originalAttr;
  7663. return bbox;
  7664. },
  7665. /**
  7666. * @private
  7667. * Checks if the instancing sprite can be seen.
  7668. * @return {Boolean}
  7669. */
  7670. isVisible: function() {
  7671. var attr = this.attr,
  7672. parent = this.getParent(),
  7673. result;
  7674. result = parent && parent.isSurface && !attr.hidden && attr.globalAlpha;
  7675. return !!result;
  7676. },
  7677. /**
  7678. * @private
  7679. * Checks if the instance of an instancing sprite can be seen.
  7680. * @param {Number} index The index of the instance.
  7681. */
  7682. isInstanceVisible: function(index) {
  7683. var me = this,
  7684. template = me.getTemplate(),
  7685. originalAttr = template.attr,
  7686. instances = me.instances,
  7687. result = false;
  7688. if (!Ext.isNumber(index) || index < 0 || index >= instances.length || !me.isVisible()) {
  7689. return result;
  7690. }
  7691. template.attr = instances[index];
  7692. result = template.isVisible(point, options);
  7693. template.attr = originalAttr;
  7694. return result;
  7695. },
  7696. render: function(surface, ctx, rect) {
  7697. //<debug>
  7698. if (!this.getTemplate()) {
  7699. Ext.raise('An instancing sprite must have a template.');
  7700. }
  7701. //</debug>
  7702. var me = this,
  7703. template = me.getTemplate(),
  7704. surfaceRect = surface.getRect(),
  7705. mat = me.attr.matrix,
  7706. originalAttr = template.attr,
  7707. instances = me.instances,
  7708. ln = me.position,
  7709. i;
  7710. mat.toContext(ctx);
  7711. template.preRender(surface, ctx, rect);
  7712. template.useAttributes(ctx, surfaceRect);
  7713. template.isSpriteInstance = true;
  7714. for (i = 0; i < ln; i++) {
  7715. if (instances[i].hidden) {
  7716. continue;
  7717. }
  7718. ctx.save();
  7719. template.attr = instances[i];
  7720. template.useAttributes(ctx, surfaceRect);
  7721. template.render(surface, ctx, rect);
  7722. ctx.restore();
  7723. }
  7724. template.isSpriteInstance = false;
  7725. template.attr = originalAttr;
  7726. },
  7727. /**
  7728. * Sets the attributes for the instance at the given index.
  7729. *
  7730. * @param {Number} index the index of the instance
  7731. * @param {Object} changes the attributes to change
  7732. * @param {Boolean} [bypassNormalization] 'true' to avoid attribute normalization
  7733. */
  7734. setAttributesFor: function(index, changes, bypassNormalization) {
  7735. var template = this.getTemplate(),
  7736. originalAttr = template.attr,
  7737. attr = this.instances[index];
  7738. if (!attr) {
  7739. return;
  7740. }
  7741. template.attr = attr;
  7742. if (bypassNormalization) {
  7743. changes = Ext.apply({}, changes);
  7744. } else {
  7745. changes = template.self.def.normalize(changes);
  7746. }
  7747. template.modifiers.target.pushDown(attr, changes);
  7748. template.attr = originalAttr;
  7749. },
  7750. destroy: function() {
  7751. var me = this,
  7752. template = me.getTemplate();
  7753. me.instances = null;
  7754. if (template) {
  7755. template.destroy();
  7756. }
  7757. me.callParent();
  7758. }
  7759. });
  7760. /**
  7761. * @private
  7762. * Adds hit testing methods to the Ext.draw.sprite.Instancing.
  7763. * Included by the Ext.draw.plugin.SpriteEvents.
  7764. */
  7765. Ext.define('Ext.draw.overrides.hittest.sprite.Instancing', {
  7766. override: 'Ext.draw.sprite.Instancing',
  7767. /**
  7768. * Performs a hit test on the instances of an instancing sprite.
  7769. * @param point A two-item array containing x and y coordinates of the point.
  7770. * @param options Hit testing options.
  7771. * @return {Object} A hit result object that contains more information about what
  7772. * exactly was hit or null if nothing was hit.
  7773. * @return {Boolean} return.isInstance `true` if an instance was hit.
  7774. * @return {Ext.draw.sprite.Instancing} return.sprite The instancing sprite.
  7775. * @return {Ext.draw.sprite.Sprite} return.template The template of the instancing sprite.
  7776. * @return {Object} return.instance The attributes of the instance.
  7777. * @return {Number} return.index The index of the instance.
  7778. */
  7779. hitTest: function(point, options) {
  7780. var me = this,
  7781. template = me.getTemplate(),
  7782. originalAttr = template.attr,
  7783. instances = me.instances,
  7784. ln = instances.length,
  7785. i = 0,
  7786. result = null;
  7787. if (!me.isVisible()) {
  7788. return result;
  7789. }
  7790. for (; i < ln; i++) {
  7791. template.attr = instances[i];
  7792. result = template.hitTest(point, options);
  7793. if (result) {
  7794. result.isInstance = true;
  7795. result.template = result.sprite;
  7796. result.sprite = this;
  7797. result.instance = instances[i];
  7798. result.index = i;
  7799. return result;
  7800. }
  7801. }
  7802. template.attr = originalAttr;
  7803. return result;
  7804. }
  7805. });
  7806. /**
  7807. * A sprite that represents a line.
  7808. *
  7809. * @example
  7810. * Ext.create({
  7811. * xtype: 'draw',
  7812. * renderTo: document.body,
  7813. * width: 600,
  7814. * height: 400,
  7815. * sprites: [{
  7816. * type: 'line',
  7817. * fromX: 20,
  7818. * fromY: 20,
  7819. * toX: 120,
  7820. * toY: 120,
  7821. * strokeStyle: '#1F6D91',
  7822. * lineWidth: 3
  7823. * }]
  7824. * });
  7825. */
  7826. Ext.define('Ext.draw.sprite.Line', {
  7827. extend: 'Ext.draw.sprite.Sprite',
  7828. alias: 'sprite.line',
  7829. type: 'line',
  7830. inheritableStatics: {
  7831. def: {
  7832. processors: {
  7833. fromX: 'number',
  7834. fromY: 'number',
  7835. toX: 'number',
  7836. toY: 'number',
  7837. crisp: 'bool'
  7838. },
  7839. defaults: {
  7840. fromX: 0,
  7841. fromY: 0,
  7842. toX: 1,
  7843. toY: 1,
  7844. crisp: false,
  7845. strokeStyle: 'black'
  7846. },
  7847. aliases: {
  7848. x1: 'fromX',
  7849. y1: 'fromY',
  7850. x2: 'toX',
  7851. y2: 'toY'
  7852. },
  7853. triggers: {
  7854. crisp: 'bbox'
  7855. }
  7856. }
  7857. },
  7858. updateLineBBox: function(bbox, isTransform, x1, y1, x2, y2) {
  7859. var attr = this.attr,
  7860. matrix = attr.matrix,
  7861. halfLineWidth = attr.lineWidth / 2,
  7862. fromX, fromY, toX, toY, p;
  7863. if (attr.crisp) {
  7864. x1 = this.align(x1);
  7865. x2 = this.align(x2);
  7866. y1 = this.align(y1);
  7867. y2 = this.align(y2);
  7868. }
  7869. if (isTransform) {
  7870. p = matrix.transformPoint([
  7871. x1,
  7872. y1
  7873. ]);
  7874. x1 = p[0];
  7875. y1 = p[1];
  7876. p = matrix.transformPoint([
  7877. x2,
  7878. y2
  7879. ]);
  7880. x2 = p[0];
  7881. y2 = p[1];
  7882. }
  7883. fromX = Math.min(x1, x2);
  7884. toX = Math.max(x1, x2);
  7885. fromY = Math.min(y1, y2);
  7886. toY = Math.max(y1, y2);
  7887. var angle = Math.atan2(toX - fromX, toY - fromY),
  7888. sin = Math.sin(angle),
  7889. cos = Math.cos(angle),
  7890. dx = halfLineWidth * cos,
  7891. dy = halfLineWidth * sin;
  7892. // Offset start and end points of the line by half its thickness,
  7893. // while accounting for line's angle.
  7894. fromX -= dx;
  7895. fromY -= dy;
  7896. toX += dx;
  7897. toY += dy;
  7898. bbox.x = fromX;
  7899. bbox.y = fromY;
  7900. bbox.width = toX - fromX;
  7901. bbox.height = toY - fromY;
  7902. },
  7903. updatePlainBBox: function(plain) {
  7904. var attr = this.attr;
  7905. this.updateLineBBox(plain, false, attr.fromX, attr.fromY, attr.toX, attr.toY);
  7906. },
  7907. updateTransformedBBox: function(transform, plain) {
  7908. var attr = this.attr;
  7909. this.updateLineBBox(transform, true, attr.fromX, attr.fromY, attr.toX, attr.toY);
  7910. },
  7911. align: function(x) {
  7912. return Math.round(x) - 0.5;
  7913. },
  7914. render: function(surface, ctx) {
  7915. var me = this,
  7916. attr = me.attr,
  7917. matrix = attr.matrix;
  7918. matrix.toContext(ctx);
  7919. ctx.beginPath();
  7920. if (attr.crisp) {
  7921. ctx.moveTo(me.align(attr.fromX), me.align(attr.fromY));
  7922. ctx.lineTo(me.align(attr.toX), me.align(attr.toY));
  7923. } else {
  7924. ctx.moveTo(attr.fromX, attr.fromY);
  7925. ctx.lineTo(attr.toX, attr.toY);
  7926. }
  7927. ctx.stroke();
  7928. //<debug>
  7929. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  7930. if (debug) {
  7931. // This assumes no part of the sprite is rendered after this call.
  7932. // If it is, we need to re-apply transformations.
  7933. // But the bounding box should always be rendered as is, untransformed.
  7934. this.attr.inverseMatrix.toContext(ctx);
  7935. debug.bbox && this.renderBBox(surface, ctx);
  7936. }
  7937. }
  7938. });
  7939. //</debug>
  7940. /**
  7941. * A sprite that represents a plus.
  7942. *
  7943. * @example
  7944. * Ext.create({
  7945. * xtype: 'draw',
  7946. * renderTo: document.body,
  7947. * width: 600,
  7948. * height: 400,
  7949. * sprites: [{
  7950. * type: 'plus',
  7951. * translationX: 100,
  7952. * translationY: 100,
  7953. * size: 40,
  7954. * fillStyle: '#1F6D91'
  7955. * }]
  7956. * });
  7957. */
  7958. Ext.define('Ext.draw.sprite.Plus', {
  7959. extend: 'Ext.draw.sprite.Path',
  7960. alias: 'sprite.plus',
  7961. inheritableStatics: {
  7962. def: {
  7963. processors: {
  7964. x: 'number',
  7965. y: 'number',
  7966. /**
  7967. * @cfg {Number} [size=4] The size of the sprite.
  7968. * Meant to be comparable to the size of a circle sprite with the same radius.
  7969. */
  7970. size: 'number'
  7971. },
  7972. defaults: {
  7973. x: 0,
  7974. y: 0,
  7975. size: 4
  7976. },
  7977. triggers: {
  7978. x: 'path',
  7979. y: 'path',
  7980. size: 'path'
  7981. }
  7982. }
  7983. },
  7984. updatePath: function(path, attr) {
  7985. var s = attr.size / 1.3,
  7986. x = attr.x - attr.lineWidth / 2,
  7987. y = attr.y;
  7988. path.fromSvgString('M'.concat(x - s / 2, ',', y - s / 2, 'l', [
  7989. 0,
  7990. -s,
  7991. s,
  7992. 0,
  7993. 0,
  7994. s,
  7995. s,
  7996. 0,
  7997. 0,
  7998. s,
  7999. -s,
  8000. 0,
  8001. 0,
  8002. s,
  8003. -s,
  8004. 0,
  8005. 0,
  8006. -s,
  8007. -s,
  8008. 0,
  8009. 0,
  8010. -s,
  8011. 'z'
  8012. ]));
  8013. }
  8014. });
  8015. /**
  8016. * @class Ext.draw.sprite.Sector
  8017. * @extends Ext.draw.sprite.Path
  8018. *
  8019. * A sprite representing a pie slice.
  8020. *
  8021. * @example
  8022. * Ext.create({
  8023. * xtype: 'draw',
  8024. * renderTo: document.body,
  8025. * width: 600,
  8026. * height: 400,
  8027. * sprites: [{
  8028. * type: 'sector',
  8029. * centerX: 100,
  8030. * centerY: 100,
  8031. * startAngle: -2.355,
  8032. * endAngle: -.785,
  8033. * endRho: 50,
  8034. * fillStyle: '#1F6D91'
  8035. * }]
  8036. * });
  8037. */
  8038. Ext.define('Ext.draw.sprite.Sector', {
  8039. extend: 'Ext.draw.sprite.Path',
  8040. alias: 'sprite.sector',
  8041. type: 'sector',
  8042. inheritableStatics: {
  8043. def: {
  8044. processors: {
  8045. /**
  8046. * @cfg {Number} [centerX=0] The center coordinate of the sprite on the x-axis.
  8047. */
  8048. centerX: 'number',
  8049. /**
  8050. * @cfg {Number} [centerY=0] The center coordinate of the sprite on the y-axis.
  8051. */
  8052. centerY: 'number',
  8053. /**
  8054. * @cfg {Number} [startAngle=0] The starting angle of the sprite.
  8055. */
  8056. startAngle: 'number',
  8057. /**
  8058. * @cfg {Number} [endAngle=0] The ending angle of the sprite.
  8059. */
  8060. endAngle: 'number',
  8061. /**
  8062. * @cfg {Number} [startRho=0] The starting point of the radius of the sprite.
  8063. */
  8064. startRho: 'number',
  8065. /**
  8066. * @cfg {Number} [endRho=150] The ending point of the radius of the sprite.
  8067. */
  8068. endRho: 'number',
  8069. /**
  8070. * @cfg {Number} [margin=0] The margin of the sprite from the center of pie.
  8071. */
  8072. margin: 'number'
  8073. },
  8074. aliases: {
  8075. rho: 'endRho'
  8076. },
  8077. triggers: {
  8078. centerX: 'path,bbox',
  8079. centerY: 'path,bbox',
  8080. startAngle: 'path,bbox',
  8081. endAngle: 'path,bbox',
  8082. startRho: 'path,bbox',
  8083. endRho: 'path,bbox',
  8084. margin: 'path,bbox'
  8085. },
  8086. defaults: {
  8087. centerX: 0,
  8088. centerY: 0,
  8089. startAngle: 0,
  8090. endAngle: 0,
  8091. startRho: 0,
  8092. endRho: 150,
  8093. margin: 0,
  8094. path: 'M 0,0'
  8095. }
  8096. }
  8097. },
  8098. getMidAngle: function() {
  8099. return this.midAngle || 0;
  8100. },
  8101. updatePath: function(path, attr) {
  8102. var startAngle = Math.min(attr.startAngle, attr.endAngle),
  8103. endAngle = Math.max(attr.startAngle, attr.endAngle),
  8104. midAngle = this.midAngle = (startAngle + endAngle) * 0.5,
  8105. fullPie = Ext.Number.isEqual(Math.abs(endAngle - startAngle), Ext.draw.Draw.pi2, 1.0E-10),
  8106. margin = attr.margin,
  8107. centerX = attr.centerX,
  8108. centerY = attr.centerY,
  8109. startRho = Math.min(attr.startRho, attr.endRho),
  8110. endRho = Math.max(attr.startRho, attr.endRho);
  8111. if (margin) {
  8112. centerX += margin * Math.cos(midAngle);
  8113. centerY += margin * Math.sin(midAngle);
  8114. }
  8115. if (!fullPie) {
  8116. path.moveTo(centerX + startRho * Math.cos(startAngle), centerY + startRho * Math.sin(startAngle));
  8117. path.lineTo(centerX + endRho * Math.cos(startAngle), centerY + endRho * Math.sin(startAngle));
  8118. }
  8119. path.arc(centerX, centerY, endRho, startAngle, endAngle, false);
  8120. path[fullPie ? 'moveTo' : 'lineTo'](centerX + startRho * Math.cos(endAngle), centerY + startRho * Math.sin(endAngle));
  8121. path.arc(centerX, centerY, startRho, endAngle, startAngle, true);
  8122. }
  8123. });
  8124. /**
  8125. * A sprite that represents a square.
  8126. *
  8127. * @example
  8128. * Ext.create({
  8129. * xtype: 'draw',
  8130. * renderTo: document.body,
  8131. * width: 600,
  8132. * height: 400,
  8133. * sprites: [{
  8134. * type: 'square',
  8135. * x: 100,
  8136. * y: 100,
  8137. * size: 50,
  8138. * fillStyle: '#1F6D91'
  8139. * }]
  8140. * });
  8141. */
  8142. Ext.define('Ext.draw.sprite.Square', {
  8143. extend: 'Ext.draw.sprite.Path',
  8144. alias: 'sprite.square',
  8145. inheritableStatics: {
  8146. def: {
  8147. processors: {
  8148. x: 'number',
  8149. y: 'number',
  8150. /**
  8151. * @cfg {Number} [size=4] The size of the sprite.
  8152. * Meant to be comparable to the size of a circle sprite with the same radius.
  8153. */
  8154. size: 'number'
  8155. },
  8156. defaults: {
  8157. x: 0,
  8158. y: 0,
  8159. size: 4
  8160. },
  8161. triggers: {
  8162. x: 'path',
  8163. y: 'path',
  8164. size: 'size'
  8165. }
  8166. }
  8167. },
  8168. updatePath: function(path, attr) {
  8169. var size = attr.size * 1.2,
  8170. s = size * 2,
  8171. x = attr.x - attr.lineWidth / 2,
  8172. y = attr.y;
  8173. path.fromSvgString('M'.concat(x - size, ',', y - size, 'l', [
  8174. s,
  8175. 0,
  8176. 0,
  8177. s,
  8178. -s,
  8179. 0,
  8180. 0,
  8181. -s,
  8182. 'z'
  8183. ]));
  8184. }
  8185. });
  8186. /**
  8187. * Utility class to provide a way to *approximately* measure the dimension of text
  8188. * without a drawing context.
  8189. */
  8190. Ext.define('Ext.draw.TextMeasurer', {
  8191. singleton: true,
  8192. requires: [
  8193. 'Ext.util.TextMetrics'
  8194. ],
  8195. measureDiv: null,
  8196. measureCache: {},
  8197. /**
  8198. * @cfg {Boolean} [precise=false]
  8199. * This singleton tries not to make use of the Ext.util.TextMetrics because it is
  8200. * several times slower than TextMeasurer's own solution. TextMetrics is more precise
  8201. * though, so if you have a case where the error is too big, you may want to set
  8202. * this config to `true` to get perfect results at the expense of performance.
  8203. * Note: defaults to `true` in IE8.
  8204. */
  8205. precise: Ext.isIE8,
  8206. measureDivTpl: {
  8207. id: 'ext-draw-text-measurer',
  8208. tag: 'div',
  8209. style: {
  8210. overflow: 'hidden',
  8211. position: 'relative',
  8212. 'float': 'left',
  8213. // 'float' is a reserved word. Don't unquote, or it will break the CMD build.
  8214. width: 0,
  8215. height: 0
  8216. },
  8217. //<debug>
  8218. // Tell the spec runner to ignore this element when checking if the dom is clean.
  8219. 'data-sticky': true,
  8220. //</debug>
  8221. children: {
  8222. tag: 'div',
  8223. style: {
  8224. display: 'block',
  8225. position: 'absolute',
  8226. x: -100000,
  8227. y: -100000,
  8228. padding: 0,
  8229. margin: 0,
  8230. 'z-index': -100000,
  8231. 'white-space': 'nowrap'
  8232. }
  8233. }
  8234. },
  8235. /**
  8236. * @private
  8237. * Measure the size of a text with specific font by using DOM to measure it.
  8238. * Could be very expensive therefore should be used lazily.
  8239. * @param {String} text
  8240. * @param {String} font
  8241. * @return {Object} An object with `width` and `height` properties.
  8242. * @return {Number} return.width
  8243. * @return {Number} return.height
  8244. */
  8245. actualMeasureText: function(text, font) {
  8246. var me = Ext.draw.TextMeasurer,
  8247. measureDiv = me.measureDiv,
  8248. FARAWAY = 100000,
  8249. size;
  8250. if (!measureDiv) {
  8251. var parent = Ext.Element.create({
  8252. //<debug>
  8253. // Tell the spec runner to ignore this element when checking if the dom is clean.
  8254. 'data-sticky': true,
  8255. //</debug>
  8256. style: {
  8257. "overflow": "hidden",
  8258. "position": "relative",
  8259. "float": "left",
  8260. // DO NOT REMOVE THE QUOTE OR IT WILL BREAK COMPRESSOR
  8261. "width": 0,
  8262. "height": 0
  8263. }
  8264. });
  8265. me.measureDiv = measureDiv = Ext.Element.create({
  8266. style: {
  8267. "position": 'absolute',
  8268. "x": FARAWAY,
  8269. "y": FARAWAY,
  8270. "z-index": -FARAWAY,
  8271. "white-space": "nowrap",
  8272. "display": 'block',
  8273. "padding": 0,
  8274. "margin": 0
  8275. }
  8276. });
  8277. Ext.getBody().appendChild(parent);
  8278. parent.appendChild(measureDiv);
  8279. }
  8280. if (font) {
  8281. measureDiv.setStyle({
  8282. font: font,
  8283. lineHeight: 'normal'
  8284. });
  8285. }
  8286. measureDiv.setText('(' + text + ')');
  8287. size = measureDiv.getSize();
  8288. measureDiv.setText('()');
  8289. size.width -= measureDiv.getSize().width;
  8290. return size;
  8291. },
  8292. /**
  8293. * Measure a single-line text with specific font.
  8294. * This will split the text into characters and add up their size.
  8295. * That may *not* be the exact size of the text as it is displayed.
  8296. * @param {String} text
  8297. * @param {String} font
  8298. * @return {Object} An object with `width` and `height` properties.
  8299. * @return {Number} return.width
  8300. * @return {Number} return.height
  8301. */
  8302. measureTextSingleLine: function(text, font) {
  8303. if (this.precise) {
  8304. return this.preciseMeasureTextSingleLine(text, font);
  8305. }
  8306. text = text.toString();
  8307. var cache = this.measureCache,
  8308. chars = text.split(''),
  8309. width = 0,
  8310. height = 0,
  8311. cachedItem, charactor, i, ln, size;
  8312. if (!cache[font]) {
  8313. cache[font] = {};
  8314. }
  8315. cache = cache[font];
  8316. if (cache[text]) {
  8317. return cache[text];
  8318. }
  8319. for (i = 0 , ln = chars.length; i < ln; i++) {
  8320. charactor = chars[i];
  8321. if (!(cachedItem = cache[charactor])) {
  8322. size = this.actualMeasureText(charactor, font);
  8323. cachedItem = cache[charactor] = size;
  8324. }
  8325. width += cachedItem.width;
  8326. height = Math.max(height, cachedItem.height);
  8327. }
  8328. return cache[text] = {
  8329. width: width,
  8330. height: height
  8331. };
  8332. },
  8333. // A more precise but slower version of the measureTextSingleLine method.
  8334. preciseMeasureTextSingleLine: function(text, font) {
  8335. text = text.toString();
  8336. var measureDiv = this.measureDiv || (this.measureDiv = Ext.getBody().createChild(this.measureDivTpl).down('div'));
  8337. measureDiv.setStyle({
  8338. font: font || ''
  8339. });
  8340. return Ext.util.TextMetrics.measure(measureDiv, text);
  8341. },
  8342. /**
  8343. * Measure a text with specific font.
  8344. * This will split the text to lines and add up their size.
  8345. * That may *not* be the exact size of the text as it is displayed.
  8346. * @param {String} text
  8347. * @param {String} font
  8348. * @return {Object} An object with `width`, `height` and `sizes` properties.
  8349. * @return {Number} return.width
  8350. * @return {Number} return.height
  8351. * @return {Object} return.sizes Results of individual line measurements, in case of multiline text.
  8352. */
  8353. measureText: function(text, font) {
  8354. var lines = text.split('\n'),
  8355. ln = lines.length,
  8356. height = 0,
  8357. width = 0,
  8358. line, i, sizes;
  8359. if (ln === 1) {
  8360. return this.measureTextSingleLine(text, font);
  8361. }
  8362. sizes = [];
  8363. for (i = 0; i < ln; i++) {
  8364. line = this.measureTextSingleLine(lines[i], font);
  8365. sizes.push(line);
  8366. height += line.height;
  8367. width = Math.max(width, line.width);
  8368. }
  8369. return {
  8370. width: width,
  8371. height: height,
  8372. sizes: sizes
  8373. };
  8374. }
  8375. });
  8376. /**
  8377. * @class Ext.draw.sprite.Text
  8378. * @extends Ext.draw.sprite.Sprite
  8379. *
  8380. * A sprite that represents text.
  8381. *
  8382. * @example
  8383. * Ext.create({
  8384. * xtype: 'draw',
  8385. * renderTo: document.body,
  8386. * width: 600,
  8387. * height: 400,
  8388. * sprites: [{
  8389. * type: 'text',
  8390. * x: 50,
  8391. * y: 50,
  8392. * text: 'Sencha',
  8393. * fontSize: 30,
  8394. * fillStyle: '#1F6D91'
  8395. * }]
  8396. * });
  8397. */
  8398. Ext.define('Ext.draw.sprite.Text', function() {
  8399. // Absolute font sizes.
  8400. var fontSizes = {
  8401. 'xx-small': true,
  8402. 'x-small': true,
  8403. 'small': true,
  8404. 'medium': true,
  8405. 'large': true,
  8406. 'x-large': true,
  8407. 'xx-large': true
  8408. };
  8409. var fontWeights = {
  8410. normal: true,
  8411. bold: true,
  8412. bolder: true,
  8413. lighter: true,
  8414. 100: true,
  8415. 200: true,
  8416. 300: true,
  8417. 400: true,
  8418. 500: true,
  8419. 600: true,
  8420. 700: true,
  8421. 800: true,
  8422. 900: true
  8423. };
  8424. var textAlignments = {
  8425. start: 'start',
  8426. left: 'start',
  8427. center: 'center',
  8428. middle: 'center',
  8429. end: 'end',
  8430. right: 'end'
  8431. };
  8432. var textBaselines = {
  8433. top: 'top',
  8434. hanging: 'hanging',
  8435. middle: 'middle',
  8436. center: 'middle',
  8437. alphabetic: 'alphabetic',
  8438. ideographic: 'ideographic',
  8439. bottom: 'bottom'
  8440. };
  8441. return {
  8442. extend: 'Ext.draw.sprite.Sprite',
  8443. requires: [
  8444. 'Ext.draw.TextMeasurer',
  8445. 'Ext.draw.Color'
  8446. ],
  8447. alias: 'sprite.text',
  8448. type: 'text',
  8449. lineBreakRe: /\r?\n/g,
  8450. //<debug>
  8451. statics: {
  8452. /**
  8453. * Debug rendering options:
  8454. *
  8455. * debug: {
  8456. * bbox: true // renders the bounding box of the text sprite
  8457. * }
  8458. *
  8459. */
  8460. debug: false,
  8461. fontSizes: fontSizes,
  8462. fontWeights: fontWeights,
  8463. textAlignments: textAlignments,
  8464. textBaselines: textBaselines
  8465. },
  8466. //</debug>
  8467. inheritableStatics: {
  8468. def: {
  8469. animationProcessors: {
  8470. text: 'text'
  8471. },
  8472. processors: {
  8473. /**
  8474. * @cfg {Number} [x=0]
  8475. * The position of the sprite on the x-axis.
  8476. */
  8477. x: 'number',
  8478. /**
  8479. * @cfg {Number} [y=0]
  8480. * The position of the sprite on the y-axis.
  8481. */
  8482. y: 'number',
  8483. /**
  8484. * @cfg {String} [text='']
  8485. * The text represented in the sprite.
  8486. */
  8487. text: 'string',
  8488. /**
  8489. * @cfg {String/Number} [fontSize='10px']
  8490. * The size of the font displayed.
  8491. */
  8492. fontSize: function(n) {
  8493. // Numbers as strings will be converted to numbers,
  8494. // null will be converted to 0.
  8495. if (Ext.isNumber(+n)) {
  8496. return n + 'px';
  8497. } else if (n.match(Ext.dom.Element.unitRe)) {
  8498. return n;
  8499. } else if (n in fontSizes) {
  8500. return n;
  8501. }
  8502. },
  8503. /**
  8504. * @cfg {String} [fontStyle='']
  8505. * The style of the font displayed. {normal, italic, oblique}
  8506. */
  8507. fontStyle: 'enums(,italic,oblique)',
  8508. /**
  8509. * @cfg {String} [fontVariant='']
  8510. * The variant of the font displayed. {normal, small-caps}
  8511. */
  8512. fontVariant: 'enums(,small-caps)',
  8513. /**
  8514. * @cfg {String} [fontWeight='']
  8515. * The weight of the font displayed. {normal, bold, bolder, lighter}
  8516. */
  8517. fontWeight: function(n) {
  8518. if (n in fontWeights) {
  8519. return String(n);
  8520. } else {
  8521. return '';
  8522. }
  8523. },
  8524. /**
  8525. * @cfg {String} [fontFamily='sans-serif']
  8526. * The family of the font displayed.
  8527. */
  8528. fontFamily: 'string',
  8529. /**
  8530. * @cfg {String} [textAlign='start']
  8531. * The alignment of the text displayed.
  8532. * {left, right, center, start, end}
  8533. */
  8534. textAlign: function(n) {
  8535. return textAlignments[n] || 'center';
  8536. },
  8537. /**
  8538. * @cfg {String} [textBaseline="alphabetic"]
  8539. * The baseline of the text displayed.
  8540. * {top, hanging, middle, alphabetic, ideographic, bottom}
  8541. */
  8542. textBaseline: function(n) {
  8543. return textBaselines[n] || 'alphabetic';
  8544. },
  8545. /**
  8546. * @cfg {String} [font='10px sans-serif']
  8547. * The font displayed.
  8548. */
  8549. font: 'string',
  8550. //<debug>
  8551. debug: 'default'
  8552. },
  8553. //</debug>
  8554. aliases: {
  8555. 'font-size': 'fontSize',
  8556. 'font-family': 'fontFamily',
  8557. 'font-weight': 'fontWeight',
  8558. 'font-variant': 'fontVariant',
  8559. 'text-anchor': 'textAlign',
  8560. 'dominant-baseline': 'textBaseline'
  8561. },
  8562. defaults: {
  8563. fontStyle: '',
  8564. fontVariant: '',
  8565. fontWeight: '',
  8566. fontSize: '10px',
  8567. fontFamily: 'sans-serif',
  8568. font: '10px sans-serif',
  8569. textBaseline: 'alphabetic',
  8570. textAlign: 'start',
  8571. strokeStyle: 'rgba(0, 0, 0, 0)',
  8572. fillStyle: '#000',
  8573. x: 0,
  8574. y: 0,
  8575. text: ''
  8576. },
  8577. triggers: {
  8578. fontStyle: 'fontX,bbox',
  8579. fontVariant: 'fontX,bbox',
  8580. fontWeight: 'fontX,bbox',
  8581. fontSize: 'fontX,bbox',
  8582. fontFamily: 'fontX,bbox',
  8583. font: 'font,bbox,canvas',
  8584. textBaseline: 'bbox',
  8585. textAlign: 'bbox',
  8586. x: 'bbox',
  8587. y: 'bbox',
  8588. text: 'bbox'
  8589. },
  8590. updaters: {
  8591. fontX: 'makeFontShorthand',
  8592. font: 'parseFontShorthand'
  8593. }
  8594. }
  8595. },
  8596. config: {
  8597. /**
  8598. * @private
  8599. * If the value is boolean, it overrides the TextMeasurer's 'precise' config
  8600. * (for the given sprite only).
  8601. */
  8602. preciseMeasurement: undefined
  8603. },
  8604. constructor: function(config) {
  8605. if (config && config.font) {
  8606. config = Ext.clone(config);
  8607. for (var key in config) {
  8608. if (key !== 'font' && key.indexOf('font') === 0) {
  8609. delete config[key];
  8610. }
  8611. }
  8612. }
  8613. Ext.draw.sprite.Sprite.prototype.constructor.call(this, config);
  8614. },
  8615. // Maps values to font properties they belong to.
  8616. fontValuesMap: {
  8617. // Skip 'normal' and 'inherit' values, as the first one
  8618. // is the default and the second one has no meaning in Canvas.
  8619. 'italic': 'fontStyle',
  8620. 'oblique': 'fontStyle',
  8621. 'small-caps': 'fontVariant',
  8622. 'bold': 'fontWeight',
  8623. 'bolder': 'fontWeight',
  8624. 'lighter': 'fontWeight',
  8625. '100': 'fontWeight',
  8626. '200': 'fontWeight',
  8627. '300': 'fontWeight',
  8628. '400': 'fontWeight',
  8629. '500': 'fontWeight',
  8630. '600': 'fontWeight',
  8631. '700': 'fontWeight',
  8632. '800': 'fontWeight',
  8633. '900': 'fontWeight',
  8634. // Absolute font sizes.
  8635. 'xx-small': 'fontSize',
  8636. 'x-small': 'fontSize',
  8637. 'small': 'fontSize',
  8638. 'medium': 'fontSize',
  8639. 'large': 'fontSize',
  8640. 'x-large': 'fontSize',
  8641. 'xx-large': 'fontSize'
  8642. },
  8643. // Relative font sizes like 'smaller' and 'larger'
  8644. // have no meaning, and are not included.
  8645. makeFontShorthand: function(attr) {
  8646. var parts = [];
  8647. if (attr.fontStyle) {
  8648. parts.push(attr.fontStyle);
  8649. }
  8650. if (attr.fontVariant) {
  8651. parts.push(attr.fontVariant);
  8652. }
  8653. if (attr.fontWeight) {
  8654. parts.push(attr.fontWeight);
  8655. }
  8656. if (attr.fontSize) {
  8657. parts.push(attr.fontSize);
  8658. }
  8659. if (attr.fontFamily) {
  8660. parts.push(attr.fontFamily);
  8661. }
  8662. this.setAttributes({
  8663. font: parts.join(' ')
  8664. }, true);
  8665. },
  8666. // For more info see:
  8667. // http://www.w3.org/TR/CSS21/fonts.html#font-shorthand
  8668. parseFontShorthand: function(attr) {
  8669. var value = attr.font,
  8670. ln = value.length,
  8671. changes = {},
  8672. dispatcher = this.fontValuesMap,
  8673. start = 0,
  8674. end, slashIndex, part, fontProperty;
  8675. while (start < ln && end !== -1) {
  8676. end = value.indexOf(' ', start);
  8677. if (end < 0) {
  8678. part = value.substr(start);
  8679. } else if (end > start) {
  8680. part = value.substr(start, end - start);
  8681. } else {
  8682. continue;
  8683. }
  8684. // Since Canvas fillText doesn't support multi-line text,
  8685. // it is assumed that line height is never specified, i.e.
  8686. // in entries like these the part after slash is omitted:
  8687. // 12px/14px sans-serif
  8688. // x-large/110% "New Century Schoolbook", serif
  8689. slashIndex = part.indexOf('/');
  8690. if (slashIndex > 0) {
  8691. part = part.substr(0, slashIndex);
  8692. } else if (slashIndex === 0) {
  8693. continue;
  8694. }
  8695. // All optional font properties (fontStyle, fontVariant or fontWeight) can be 'normal'.
  8696. // They can go in any order. Which ones are 'normal' is determined by elimination.
  8697. // E.g. if only fontVariant is specified, then 'normal' applies to fontStyle and fontWeight.
  8698. // If none are explicitly mentioned, then all are 'normal'.
  8699. if (part !== 'normal' && part !== 'inherit') {
  8700. fontProperty = dispatcher[part];
  8701. if (fontProperty) {
  8702. changes[fontProperty] = part;
  8703. } else if (part.match(Ext.dom.Element.unitRe)) {
  8704. changes.fontSize = part;
  8705. } else {
  8706. // Assuming that font family always goes last in the font shorthand.
  8707. changes.fontFamily = value.substr(start);
  8708. break;
  8709. }
  8710. }
  8711. start = end + 1;
  8712. }
  8713. if (!changes.fontStyle) {
  8714. changes.fontStyle = '';
  8715. }
  8716. // same as 'normal'
  8717. if (!changes.fontVariant) {
  8718. changes.fontVariant = '';
  8719. }
  8720. // same as 'normal'
  8721. if (!changes.fontWeight) {
  8722. changes.fontWeight = '';
  8723. }
  8724. // same as 'normal'
  8725. this.setAttributes(changes, true);
  8726. },
  8727. fontProperties: {
  8728. fontStyle: true,
  8729. fontVariant: true,
  8730. fontWeight: true,
  8731. fontSize: true,
  8732. fontFamily: true
  8733. },
  8734. setAttributes: function(changes, bypassNormalization, avoidCopy) {
  8735. var key, obj;
  8736. // Discard individual font properties if 'font' shorthand was also provided.
  8737. // Example: a user provides a config for chart series labels, using the font
  8738. // shorthand, which is parsed into individual font properties and corresponding
  8739. // sprite attributes are set. Then a theme is applied to the chart, and
  8740. // individual font properties from the theme make up the new font shorthand
  8741. // that overrides the previous one. In other words, no matter what font
  8742. // the user has specified, theme font will be used.
  8743. // This workaround relies on the fact that the theme merges its own config with
  8744. // the user config (where user config values take over the same theme config
  8745. // values). So both user font shorthand and individual font properties from
  8746. // the theme are present in the resulting config (since there are no collisions),
  8747. // which ends up here as the 'changes' parameter.
  8748. // If the user wants their font config to merged with the the theme's font config,
  8749. // instead of taking over it, individual font properties should be used
  8750. // by the user as well.
  8751. if (changes && changes.font) {
  8752. obj = {};
  8753. for (key in changes) {
  8754. if (!(key in this.fontProperties)) {
  8755. obj[key] = changes[key];
  8756. }
  8757. }
  8758. changes = obj;
  8759. }
  8760. this.callParent([
  8761. changes,
  8762. bypassNormalization,
  8763. avoidCopy
  8764. ]);
  8765. },
  8766. // Overriding the getBBox method of the abstract sprite here to always
  8767. // recalculate the bounding box of the text in flipped RTL mode
  8768. // because in that case the position of the sprite depends not just on
  8769. // the value of its 'x' attribute, but also on the width of the surface
  8770. // the sprite belongs to.
  8771. getBBox: function(isWithoutTransform) {
  8772. var me = this,
  8773. plain = me.attr.bbox.plain,
  8774. surface = me.getSurface();
  8775. //<debug>
  8776. // The sprite's bounding box won't account for RTL if it doesn't
  8777. // belong to a surface.
  8778. //if (!surface) {
  8779. // Ext.raise("The sprite does not belong to a surface.");
  8780. //}
  8781. //</debug>
  8782. if (plain.dirty) {
  8783. me.updatePlainBBox(plain);
  8784. plain.dirty = false;
  8785. }
  8786. if (surface && surface.getInherited().rtl && surface.getFlipRtlText()) {
  8787. // Since sprite's attributes haven't actually changed at this point,
  8788. // and we just want to update the position of its bbox
  8789. // based on surface's width, there's no reason to perform
  8790. // expensive text measurement operation here,
  8791. // so we can use the result of the last measurement instead.
  8792. me.updatePlainBBox(plain, true);
  8793. }
  8794. return me.callParent([
  8795. isWithoutTransform
  8796. ]);
  8797. },
  8798. rtlAlignments: {
  8799. start: 'end',
  8800. center: 'center',
  8801. end: 'start'
  8802. },
  8803. updatePlainBBox: function(plain, useOldSize) {
  8804. var me = this,
  8805. attr = me.attr,
  8806. x = attr.x,
  8807. y = attr.y,
  8808. dx = [],
  8809. font = attr.font,
  8810. text = attr.text,
  8811. baseline = attr.textBaseline,
  8812. alignment = attr.textAlign,
  8813. precise = me.getPreciseMeasurement(),
  8814. size, textMeasurerPrecision;
  8815. if (useOldSize && me.oldSize) {
  8816. size = me.oldSize;
  8817. } else {
  8818. textMeasurerPrecision = Ext.draw.TextMeasurer.precise;
  8819. if (Ext.isBoolean(precise)) {
  8820. Ext.draw.TextMeasurer.precise = precise;
  8821. }
  8822. size = me.oldSize = Ext.draw.TextMeasurer.measureText(text, font);
  8823. Ext.draw.TextMeasurer.precise = textMeasurerPrecision;
  8824. }
  8825. var surface = me.getSurface(),
  8826. isRtl = (surface && surface.getInherited().rtl) || false,
  8827. flipRtlText = isRtl && surface.getFlipRtlText(),
  8828. sizes = size.sizes,
  8829. blockHeight = size.height,
  8830. blockWidth = size.width,
  8831. ln = sizes ? sizes.length : 0,
  8832. lineWidth, rect,
  8833. i = 0;
  8834. // To get consistent results in all browsers we don't apply textAlign
  8835. // and textBaseline attributes of the sprite to context, so text is always
  8836. // left aligned and has an alphabetic baseline.
  8837. //
  8838. // Instead we have to calculate the horizontal offset of each line
  8839. // based on sprite's textAlign, and the vertical offset of the bounding box
  8840. // based on sprite's textBaseline.
  8841. //
  8842. // These offsets are then used by the sprite's 'render' method
  8843. // to position text properly.
  8844. switch (baseline) {
  8845. case 'hanging':
  8846. case 'top':
  8847. break;
  8848. case 'ideographic':
  8849. case 'bottom':
  8850. y -= blockHeight;
  8851. break;
  8852. case 'alphabetic':
  8853. y -= blockHeight * 0.8;
  8854. break;
  8855. case 'middle':
  8856. y -= blockHeight * 0.5;
  8857. break;
  8858. }
  8859. if (flipRtlText) {
  8860. rect = surface.getRect();
  8861. x = rect[2] - rect[0] - x;
  8862. alignment = me.rtlAlignments[alignment];
  8863. }
  8864. switch (alignment) {
  8865. case 'start':
  8866. if (isRtl) {
  8867. for (; i < ln; i++) {
  8868. lineWidth = sizes[i].width;
  8869. dx.push(-(blockWidth - lineWidth));
  8870. }
  8871. };
  8872. break;
  8873. case 'end':
  8874. x -= blockWidth;
  8875. if (isRtl) {
  8876. break;
  8877. };
  8878. for (; i < ln; i++) {
  8879. lineWidth = sizes[i].width;
  8880. dx.push(blockWidth - lineWidth);
  8881. };
  8882. break;
  8883. case 'center':
  8884. x -= blockWidth * 0.5;
  8885. for (; i < ln; i++) {
  8886. lineWidth = sizes[i].width;
  8887. dx.push((isRtl ? -1 : 1) * (blockWidth - lineWidth) * 0.5);
  8888. };
  8889. break;
  8890. }
  8891. attr.textAlignOffsets = dx;
  8892. plain.x = x;
  8893. plain.y = y;
  8894. plain.width = blockWidth;
  8895. plain.height = blockHeight;
  8896. },
  8897. setText: function(text) {
  8898. this.setAttributes({
  8899. text: text
  8900. }, true);
  8901. },
  8902. render: function(surface, ctx, rect) {
  8903. var me = this,
  8904. attr = me.attr,
  8905. mat = Ext.draw.Matrix.fly(attr.matrix.elements.slice(0)),
  8906. bbox = me.getBBox(true),
  8907. dx = attr.textAlignOffsets,
  8908. none = Ext.util.Color.RGBA_NONE,
  8909. x, y, i, lines, lineHeight;
  8910. if (attr.text.length === 0) {
  8911. return;
  8912. }
  8913. lines = attr.text.split(me.lineBreakRe);
  8914. lineHeight = bbox.height / lines.length;
  8915. // Simulate textBaseline and textAlign.
  8916. x = attr.bbox.plain.x;
  8917. // lineHeight * 0.78 is the approximate distance between the top and the alphabetic baselines
  8918. y = attr.bbox.plain.y + lineHeight * 0.78;
  8919. mat.toContext(ctx);
  8920. if (surface.getInherited().rtl) {
  8921. // Canvas element in RTL mode automatically flips text alignment.
  8922. // Here we compensate for that change.
  8923. // So text is still positioned and aligned as in the LTR mode,
  8924. // but the direction of the text is RTL.
  8925. x += attr.bbox.plain.width;
  8926. }
  8927. for (i = 0; i < lines.length; i++) {
  8928. if (ctx.fillStyle !== none) {
  8929. ctx.fillText(lines[i], x + (dx[i] || 0), y + lineHeight * i);
  8930. }
  8931. if (ctx.strokeStyle !== none) {
  8932. ctx.strokeText(lines[i], x + (dx[i] || 0), y + lineHeight * i);
  8933. }
  8934. }
  8935. //<debug>
  8936. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  8937. if (debug) {
  8938. // This assumes no part of the sprite is rendered after this call.
  8939. // If it is, we need to re-apply transformations.
  8940. // But the bounding box is already transformed, so we remove the transformation.
  8941. this.attr.inverseMatrix.toContext(ctx);
  8942. debug.bbox && me.renderBBox(surface, ctx);
  8943. }
  8944. }
  8945. };
  8946. });
  8947. //</debug>
  8948. /**
  8949. * A veritical line sprite. The x and y configs set the center of the line with the size
  8950. * value determining the height of the line (the line will be twice the height of 'size'
  8951. * since 'size' is added to above and below 'y' to set the line endpoints).
  8952. *
  8953. * @example
  8954. * Ext.create({
  8955. * xtype: 'draw',
  8956. * renderTo: document.body,
  8957. * width: 600,
  8958. * height: 400,
  8959. * sprites: [{
  8960. * type: 'tick',
  8961. * x: 20,
  8962. * y: 40,
  8963. * size: 10,
  8964. * strokeStyle: '#388FAD',
  8965. * lineWidth: 2
  8966. * }]
  8967. * });
  8968. */
  8969. Ext.define('Ext.draw.sprite.Tick', {
  8970. extend: 'Ext.draw.sprite.Line',
  8971. alias: 'sprite.tick',
  8972. inheritableStatics: {
  8973. def: {
  8974. processors: {
  8975. /**
  8976. * @cfg {Object} x The position of the center of the sprite on the x-axis.
  8977. */
  8978. x: 'number',
  8979. /**
  8980. * @cfg {Object} y The position of the center of the sprite on the y-axis.
  8981. */
  8982. y: 'number',
  8983. /**
  8984. * @cfg {Number} [size=4] The size of the sprite.
  8985. * Meant to be comparable to the size of a circle sprite with the same radius.
  8986. */
  8987. size: 'number'
  8988. },
  8989. defaults: {
  8990. x: 0,
  8991. y: 0,
  8992. size: 4
  8993. },
  8994. triggers: {
  8995. x: 'tick',
  8996. y: 'tick',
  8997. size: 'tick'
  8998. },
  8999. updaters: {
  9000. tick: function(attr) {
  9001. var size = attr.size * 1.5,
  9002. halfLineWidth = attr.lineWidth / 2,
  9003. x = attr.x,
  9004. y = attr.y;
  9005. this.setAttributes({
  9006. fromX: x - halfLineWidth,
  9007. fromY: y - size,
  9008. toX: x - halfLineWidth,
  9009. toY: y + size
  9010. });
  9011. }
  9012. }
  9013. }
  9014. }
  9015. });
  9016. /**
  9017. * A sprite that represents a triangle.
  9018. *
  9019. * @example
  9020. * Ext.create({
  9021. * xtype: 'draw',
  9022. * renderTo: document.body,
  9023. * width: 600,
  9024. * height: 400,
  9025. * sprites: [{
  9026. * type: 'triangle',
  9027. * size: 50,
  9028. * translationX: 100,
  9029. * translationY: 100,
  9030. * fillStyle: '#1F6D91'
  9031. * }]
  9032. * });
  9033. *
  9034. */
  9035. Ext.define('Ext.draw.sprite.Triangle', {
  9036. extend: 'Ext.draw.sprite.Path',
  9037. alias: 'sprite.triangle',
  9038. inheritableStatics: {
  9039. def: {
  9040. processors: {
  9041. x: 'number',
  9042. y: 'number',
  9043. /**
  9044. * @cfg {Number} [size=4] The size of the sprite.
  9045. * Meant to be comparable to the size of a circle sprite with the same radius.
  9046. */
  9047. size: 'number'
  9048. },
  9049. defaults: {
  9050. x: 0,
  9051. y: 0,
  9052. size: 4
  9053. },
  9054. triggers: {
  9055. x: 'path',
  9056. y: 'path',
  9057. size: 'path'
  9058. }
  9059. }
  9060. },
  9061. updatePath: function(path, attr) {
  9062. var s = attr.size * 2.2,
  9063. x = attr.x,
  9064. y = attr.y;
  9065. path.fromSvgString('M'.concat(x, ',', y, 'm0-', s * 0.48, 'l', s * 0.5, ',', s * 0.87, '-', s, ',0z'));
  9066. }
  9067. });
  9068. /**
  9069. * Linear gradient.
  9070. *
  9071. * @example
  9072. * Ext.create({
  9073. * xtype: 'draw',
  9074. * renderTo: document.body,
  9075. * width: 600,
  9076. * height: 400,
  9077. * sprites: [{
  9078. * type: 'circle',
  9079. * cx: 100,
  9080. * cy: 100,
  9081. * r: 100,
  9082. * fillStyle: {
  9083. * type: 'linear',
  9084. * degrees: 180,
  9085. * stops: [{
  9086. * offset: 0,
  9087. * color: '#1F6D91'
  9088. * }, {
  9089. * offset: 1,
  9090. * color: '#90BCC9'
  9091. * }]
  9092. * }
  9093. * }]
  9094. * });
  9095. */
  9096. Ext.define('Ext.draw.gradient.Linear', {
  9097. extend: 'Ext.draw.gradient.Gradient',
  9098. requires: [
  9099. 'Ext.draw.Color'
  9100. ],
  9101. type: 'linear',
  9102. config: {
  9103. /**
  9104. * @cfg {Number} degrees
  9105. * The angle of rotation of the gradient in degrees.
  9106. */
  9107. degrees: 0,
  9108. /**
  9109. * @cfg {Number} radians
  9110. * The angle of rotation of the gradient in radians.
  9111. */
  9112. radians: 0
  9113. },
  9114. applyRadians: function(radians, oldRadians) {
  9115. if (Ext.isNumber(radians)) {
  9116. return radians;
  9117. }
  9118. return oldRadians;
  9119. },
  9120. applyDegrees: function(degrees, oldDegrees) {
  9121. if (Ext.isNumber(degrees)) {
  9122. return degrees;
  9123. }
  9124. return oldDegrees;
  9125. },
  9126. updateRadians: function(radians) {
  9127. this.setDegrees(Ext.draw.Draw.degrees(radians));
  9128. },
  9129. updateDegrees: function(degrees) {
  9130. this.setRadians(Ext.draw.Draw.rad(degrees));
  9131. },
  9132. /**
  9133. * @method generateGradient
  9134. * @inheritdoc
  9135. */
  9136. generateGradient: function(ctx, bbox) {
  9137. var angle = this.getRadians(),
  9138. cos = Math.cos(angle),
  9139. sin = Math.sin(angle),
  9140. w = bbox.width,
  9141. h = bbox.height,
  9142. cx = bbox.x + w * 0.5,
  9143. cy = bbox.y + h * 0.5,
  9144. stops = this.getStops(),
  9145. ln = stops.length,
  9146. gradient, l, i;
  9147. if (Ext.isNumber(cx) && Ext.isNumber(cy) && h > 0 && w > 0) {
  9148. l = (Math.sqrt(h * h + w * w) * Math.abs(Math.cos(angle - Math.atan(h / w)))) / 2;
  9149. gradient = ctx.createLinearGradient(cx + cos * l, cy + sin * l, cx - cos * l, cy - sin * l);
  9150. for (i = 0; i < ln; i++) {
  9151. gradient.addColorStop(stops[i].offset, stops[i].color);
  9152. }
  9153. return gradient;
  9154. }
  9155. return Ext.util.Color.NONE;
  9156. }
  9157. });
  9158. /**
  9159. * Radial gradient.
  9160. *
  9161. * @example
  9162. * Ext.create({
  9163. * xtype: 'draw',
  9164. * renderTo: document.body,
  9165. * width: 600,
  9166. * height: 400,
  9167. * sprites: [{
  9168. * type: 'circle',
  9169. * cx: 100,
  9170. * cy: 100,
  9171. * r: 100,
  9172. * fillStyle: {
  9173. * type: 'radial',
  9174. * start: {
  9175. * x: 0,
  9176. * y: 0,
  9177. * r: 0
  9178. * },
  9179. * end: {
  9180. * x: 0,
  9181. * y: 0,
  9182. * r: 1
  9183. * },
  9184. * stops: [{
  9185. * offset: 0,
  9186. * color: '#90BCC9'
  9187. * }, {
  9188. * offset: 1,
  9189. * color: '#1F6D91'
  9190. * }]
  9191. * }
  9192. * }]
  9193. * });
  9194. */
  9195. Ext.define('Ext.draw.gradient.Radial', {
  9196. extend: 'Ext.draw.gradient.Gradient',
  9197. type: 'radial',
  9198. config: {
  9199. /**
  9200. * @cfg {Object} start
  9201. * The starting circle of the gradient.
  9202. */
  9203. start: {
  9204. x: 0,
  9205. y: 0,
  9206. r: 0
  9207. },
  9208. /**
  9209. * @cfg {Object} end
  9210. * The ending circle of the gradient.
  9211. */
  9212. end: {
  9213. x: 0,
  9214. y: 0,
  9215. r: 1
  9216. }
  9217. },
  9218. applyStart: function(newStart, oldStart) {
  9219. if (!oldStart) {
  9220. return newStart;
  9221. }
  9222. var circle = {
  9223. x: oldStart.x,
  9224. y: oldStart.y,
  9225. r: oldStart.r
  9226. };
  9227. if ('x' in newStart) {
  9228. circle.x = newStart.x;
  9229. } else if ('centerX' in newStart) {
  9230. circle.x = newStart.centerX;
  9231. }
  9232. if ('y' in newStart) {
  9233. circle.y = newStart.y;
  9234. } else if ('centerY' in newStart) {
  9235. circle.y = newStart.centerY;
  9236. }
  9237. if ('r' in newStart) {
  9238. circle.r = newStart.r;
  9239. } else if ('radius' in newStart) {
  9240. circle.r = newStart.radius;
  9241. }
  9242. return circle;
  9243. },
  9244. applyEnd: function(newEnd, oldEnd) {
  9245. if (!oldEnd) {
  9246. return newEnd;
  9247. }
  9248. var circle = {
  9249. x: oldEnd.x,
  9250. y: oldEnd.y,
  9251. r: oldEnd.r
  9252. };
  9253. if ('x' in newEnd) {
  9254. circle.x = newEnd.x;
  9255. } else if ('centerX' in newEnd) {
  9256. circle.x = newEnd.centerX;
  9257. }
  9258. if ('y' in newEnd) {
  9259. circle.y = newEnd.y;
  9260. } else if ('centerY' in newEnd) {
  9261. circle.y = newEnd.centerY;
  9262. }
  9263. if ('r' in newEnd) {
  9264. circle.r = newEnd.r;
  9265. } else if ('radius' in newEnd) {
  9266. circle.r = newEnd.radius;
  9267. }
  9268. return circle;
  9269. },
  9270. /**
  9271. * @method generateGradient
  9272. * @inheritdoc
  9273. */
  9274. generateGradient: function(ctx, bbox) {
  9275. var start = this.getStart(),
  9276. end = this.getEnd(),
  9277. w = bbox.width * 0.5,
  9278. h = bbox.height * 0.5,
  9279. x = bbox.x + w,
  9280. y = bbox.y + h,
  9281. 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)),
  9282. stops = this.getStops(),
  9283. ln = stops.length,
  9284. i;
  9285. for (i = 0; i < ln; i++) {
  9286. gradient.addColorStop(stops[i].offset, stops[i].color);
  9287. }
  9288. return gradient;
  9289. }
  9290. });
  9291. /**
  9292. * A surface is an interface to render {@link Ext.draw.sprite.Sprite sprites} inside a
  9293. * {@link Ext.draw.Container draw container}. The surface API has methods to render
  9294. * sprites, get sprite bounding boxes (dimensions), add sprites to the underlying DOM,
  9295. * and more.
  9296. *
  9297. * A surface is automatically created when a draw container is created. By default,
  9298. * this will be a surface with an `id` of "main" and will manage all sprites in the draw
  9299. * container (unless the sprite configs specify a unique surface "id").
  9300. *
  9301. * @example
  9302. * Ext.create({
  9303. * xtype: 'draw',
  9304. * renderTo: document.body,
  9305. * width: 400,
  9306. * height: 400,
  9307. * sprites: [{
  9308. * type: 'rect',
  9309. * surface: 'anim', // a surface with id "anim" will be created automatically
  9310. * x: 50,
  9311. * y: 50,
  9312. * width: 100,
  9313. * height: 100,
  9314. * fillStyle: '#1F6D91'
  9315. * }]
  9316. * });
  9317. *
  9318. * The ability to have multiple surfaces is useful for performance (and battery life)
  9319. * reasons. Because changes to sprite attributes cause the whole surface (and all
  9320. * sprites in it) to re-render, it makes sense to group sprites by surface, so changes
  9321. * to one group of sprites will only trigger the surface they are in to re-render.
  9322. *
  9323. * One of the more useful methods is the {@link #add} method used to add sprites to the
  9324. * surface:
  9325. *
  9326. * @example
  9327. * var drawCt = Ext.create({
  9328. * xtype: 'draw',
  9329. * renderTo: document.body,
  9330. * width: 400,
  9331. * height: 400
  9332. * });
  9333. *
  9334. * // If the surface name is not specified then 'main' will be used
  9335. * var surface = drawCt.getSurface();
  9336. *
  9337. * surface.add({
  9338. * type: 'rect',
  9339. * x: 50,
  9340. * y: 50,
  9341. * width: 100,
  9342. * height: 100,
  9343. * fillStyle: '#1F6D91'
  9344. * });
  9345. *
  9346. * surface.renderFrame();
  9347. *
  9348. * **Note:** Changes to the sprites on a surface will be not be reflected in the DOM
  9349. * until you call the surface's {@link Ext.draw.Surface#method-renderFrame renderFrame}
  9350. * method. This must be done after adding, removing, or modifying sprites in order to
  9351. * see the changes on-screen.
  9352. */
  9353. Ext.define('Ext.draw.Surface', {
  9354. extend: 'Ext.draw.SurfaceBase',
  9355. xtype: 'surface',
  9356. requires: [
  9357. 'Ext.draw.sprite.*',
  9358. 'Ext.draw.gradient.*',
  9359. 'Ext.draw.sprite.AttributeDefinition',
  9360. 'Ext.draw.Matrix',
  9361. 'Ext.draw.Draw'
  9362. ],
  9363. uses: [
  9364. 'Ext.draw.engine.Canvas'
  9365. ],
  9366. /**
  9367. * The reported device pixel density.
  9368. * devicePixelRatio is only supported from IE11,
  9369. * so we use deviceXDPI and logicalXDPI that are supported from IE6.
  9370. */
  9371. devicePixelRatio: window.devicePixelRatio || window.screen.deviceXDPI / window.screen.logicalXDPI,
  9372. deprecated: {
  9373. '5.1.0': {
  9374. statics: {
  9375. methods: {
  9376. /**
  9377. * @deprecated 5.1.0
  9378. * Stably sort the list of sprites by their zIndex.
  9379. * Deprecated, use the {@link Ext.Array#sort} method instead.
  9380. * @param {Array} list
  9381. * @return {Array} Sorted array.
  9382. */
  9383. stableSort: function(list) {
  9384. return Ext.Array.sort(list, function(a, b) {
  9385. return a.attr.zIndex - b.attr.zIndex;
  9386. });
  9387. }
  9388. }
  9389. }
  9390. }
  9391. },
  9392. cls: Ext.baseCSSPrefix + 'surface',
  9393. config: {
  9394. /**
  9395. * @cfg {Array}
  9396. * The [x, y, width, height] rect of the surface related to its container.
  9397. */
  9398. rect: null,
  9399. /**
  9400. * @cfg {Object}
  9401. * Background sprite config of the surface.
  9402. */
  9403. background: null,
  9404. /**
  9405. * @cfg {Array}
  9406. * Array of sprite instances.
  9407. */
  9408. items: [],
  9409. /**
  9410. * @cfg {Boolean}
  9411. * Indicates whether the surface needs to redraw.
  9412. */
  9413. dirty: false,
  9414. /**
  9415. * @cfg {Boolean} flipRtlText
  9416. * If the surface is in the RTL mode, text will render with the RTL direction,
  9417. * but the alignment and position of the text won't change by default.
  9418. * Setting this config to 'true' will get text alignment and its position
  9419. * within a surface mirrored.
  9420. */
  9421. flipRtlText: false
  9422. },
  9423. isSurface: true,
  9424. /**
  9425. * @private
  9426. * This flag is used to indicate that `predecessors` surfaces that should render
  9427. * before this surface renders are dirty, and to call `renderFrame`
  9428. * when all `predecessors` have their `renderFrame` called (i.e. not dirty anymore).
  9429. * This flag indicates that current surface has surfaces that are yet to render
  9430. * before current surface can render. When all the `predecessors` surfaces
  9431. * have rendered, i.e. when `dirtyPredecessorCount` reaches zero,
  9432. */
  9433. isPendingRenderFrame: false,
  9434. dirtyPredecessorCount: 0,
  9435. emptyRect: [
  9436. 0,
  9437. 0,
  9438. 0,
  9439. 0
  9440. ],
  9441. constructor: function(config) {
  9442. var me = this;
  9443. me.predecessors = [];
  9444. me.successors = [];
  9445. me.map = {};
  9446. me.callParent([
  9447. config
  9448. ]);
  9449. me.matrix = new Ext.draw.Matrix();
  9450. me.inverseMatrix = me.matrix.inverse();
  9451. },
  9452. /**
  9453. * Round the number to align to the pixels on device.
  9454. * @param {Number} num The number to align.
  9455. * @return {Number} The resultant alignment.
  9456. */
  9457. roundPixel: function(num) {
  9458. return Math.round(this.devicePixelRatio * num) / this.devicePixelRatio;
  9459. },
  9460. /**
  9461. * Mark the surface to render after another surface is updated.
  9462. * @param {Ext.draw.Surface} surface The surface to wait for.
  9463. */
  9464. waitFor: function(surface) {
  9465. var me = this,
  9466. predecessors = me.predecessors;
  9467. if (!Ext.Array.contains(predecessors, surface)) {
  9468. predecessors.push(surface);
  9469. surface.successors.push(me);
  9470. if (surface.getDirty()) {
  9471. me.dirtyPredecessorCount++;
  9472. }
  9473. }
  9474. },
  9475. updateDirty: function(dirty) {
  9476. var successors = this.successors,
  9477. ln = successors.length,
  9478. i = 0,
  9479. successor;
  9480. for (; i < ln; i++) {
  9481. successor = successors[i];
  9482. if (dirty) {
  9483. successor.dirtyPredecessorCount++;
  9484. successor.setDirty(true);
  9485. } else {
  9486. successor.dirtyPredecessorCount--;
  9487. // Don't need to call `setDirty(false)` on a successor here,
  9488. // as this will be done by `renderFrame`.
  9489. if (successor.dirtyPredecessorCount === 0 && successor.isPendingRenderFrame) {
  9490. successor.renderFrame();
  9491. }
  9492. }
  9493. }
  9494. },
  9495. applyBackground: function(background, oldBackground) {
  9496. this.setDirty(true);
  9497. if (Ext.isString(background)) {
  9498. background = {
  9499. fillStyle: background
  9500. };
  9501. }
  9502. return Ext.factory(background, Ext.draw.sprite.Rect, oldBackground);
  9503. },
  9504. applyRect: function(rect, oldRect) {
  9505. if (oldRect && rect[0] === oldRect[0] && rect[1] === oldRect[1] && rect[2] === oldRect[2] && rect[3] === oldRect[3]) {
  9506. return oldRect;
  9507. }
  9508. if (Ext.isArray(rect)) {
  9509. return [
  9510. rect[0],
  9511. rect[1],
  9512. rect[2],
  9513. rect[3]
  9514. ];
  9515. } else if (Ext.isObject(rect)) {
  9516. return [
  9517. rect.x || rect.left,
  9518. rect.y || rect.top,
  9519. rect.width || (rect.right - rect.left),
  9520. rect.height || (rect.bottom - rect.top)
  9521. ];
  9522. }
  9523. },
  9524. updateRect: function(rect) {
  9525. var me = this,
  9526. l = rect[0],
  9527. t = rect[1],
  9528. r = l + rect[2],
  9529. b = t + rect[3],
  9530. background = me.getBackground(),
  9531. element = me.element;
  9532. element.setLocalXY(Math.floor(l), Math.floor(t));
  9533. element.setSize(Math.ceil(r - Math.floor(l)), Math.ceil(b - Math.floor(t)));
  9534. if (background) {
  9535. background.setAttributes({
  9536. x: 0,
  9537. y: 0,
  9538. width: Math.ceil(r - Math.floor(l)),
  9539. height: Math.ceil(b - Math.floor(t))
  9540. });
  9541. }
  9542. me.setDirty(true);
  9543. },
  9544. /**
  9545. * Reset the matrix of the surface.
  9546. */
  9547. resetTransform: function() {
  9548. this.matrix.set(1, 0, 0, 1, 0, 0);
  9549. this.inverseMatrix.set(1, 0, 0, 1, 0, 0);
  9550. this.setDirty(true);
  9551. },
  9552. /**
  9553. * Get the sprite by id or index.
  9554. * It will first try to find a sprite with the given id, otherwise will try to use the id as an index.
  9555. * @param {String|Number} id
  9556. * @return {Ext.draw.sprite.Sprite}
  9557. */
  9558. get: function(id) {
  9559. return this.map[id] || this.getItems()[id];
  9560. },
  9561. /**
  9562. * @method
  9563. * Add a Sprite to the surface.
  9564. * You can put any number of objects as the parameter.
  9565. * See {@link Ext.draw.sprite.Sprite} for the configuration object to be passed into this method.
  9566. *
  9567. * For example:
  9568. *
  9569. * drawContainer.getSurface().add({
  9570. * type: 'circle',
  9571. * fill: '#ffc',
  9572. * radius: 100,
  9573. * x: 100,
  9574. * y: 100
  9575. * });
  9576. * drawContainer.renderFrame();
  9577. *
  9578. * @param {Object/Object[]} sprite
  9579. * @return {Ext.draw.sprite.Sprite/Ext.draw.sprite.Sprite[]}
  9580. *
  9581. */
  9582. add: function() {
  9583. var me = this,
  9584. args = Array.prototype.slice.call(arguments),
  9585. argIsArray = Ext.isArray(args[0]),
  9586. map = me.map,
  9587. results = [],
  9588. items, item, sprite, oldSurface, i, ln;
  9589. items = Ext.Array.clean(argIsArray ? args[0] : args);
  9590. if (!items.length) {
  9591. return results;
  9592. }
  9593. for (i = 0 , ln = items.length; i < ln; i++) {
  9594. item = items[i];
  9595. if (!item || item.destroyed) {
  9596. continue;
  9597. }
  9598. sprite = null;
  9599. if (item.isSprite && !map[item.getId()]) {
  9600. sprite = item;
  9601. } else if (!map[item.id]) {
  9602. sprite = this.createItem(item);
  9603. }
  9604. if (sprite) {
  9605. map[sprite.getId()] = sprite;
  9606. results.push(sprite);
  9607. oldSurface = sprite.getSurface();
  9608. if (oldSurface && oldSurface.isSurface) {
  9609. oldSurface.remove(sprite);
  9610. }
  9611. sprite.setParent(me);
  9612. sprite.setSurface(me);
  9613. me.onAdd(sprite);
  9614. }
  9615. }
  9616. items = me.getItems();
  9617. if (items) {
  9618. items.push.apply(items, results);
  9619. }
  9620. me.dirtyZIndex = true;
  9621. me.setDirty(true);
  9622. if (!argIsArray && results.length === 1) {
  9623. return results[0];
  9624. } else {
  9625. return results;
  9626. }
  9627. },
  9628. /**
  9629. * @method
  9630. * @protected
  9631. * Invoked when a sprite is added to the surface.
  9632. * @param {Ext.draw.sprite.Sprite} sprite The sprite to be added.
  9633. */
  9634. onAdd: Ext.emptyFn,
  9635. /**
  9636. * Remove a given sprite from the surface,
  9637. * optionally destroying the sprite in the process.
  9638. * You can also call the sprite's own `remove` method.
  9639. *
  9640. * For example:
  9641. *
  9642. * drawContainer.surface.remove(sprite);
  9643. * // or...
  9644. * sprite.remove();
  9645. *
  9646. * @param {Ext.draw.sprite.Sprite/String} sprite A sprite instance or its ID.
  9647. * @param {Boolean} [isDestroy=false] If `true`, the sprite will be destroyed.
  9648. * @return {Ext.draw.sprite.Sprite} Returns the removed/destroyed sprite or `null` otherwise.
  9649. */
  9650. remove: function(sprite, isDestroy) {
  9651. var me = this,
  9652. destroying = me.clearing,
  9653. id, isOwnSprite;
  9654. if (sprite) {
  9655. if (sprite.charAt) {
  9656. // is String
  9657. sprite = me.map[sprite];
  9658. }
  9659. if (!sprite || !sprite.isSprite) {
  9660. return null;
  9661. }
  9662. id = sprite.id;
  9663. isOwnSprite = me.map[id];
  9664. delete me.map[id];
  9665. if (sprite.destroyed || sprite.destroying) {
  9666. if (isOwnSprite && !destroying) {
  9667. // Somehow this sprite was destroyed,
  9668. // but still belongs to the surface.
  9669. Ext.Array.remove(me.getItems(), sprite);
  9670. }
  9671. return sprite;
  9672. }
  9673. if (!isOwnSprite) {
  9674. if (isDestroy) {
  9675. sprite.destroy();
  9676. }
  9677. return sprite;
  9678. }
  9679. sprite.setParent(null);
  9680. sprite.setSurface(null);
  9681. if (isDestroy) {
  9682. sprite.destroy();
  9683. }
  9684. if (!destroying) {
  9685. Ext.Array.remove(me.getItems(), sprite);
  9686. me.dirtyZIndex = true;
  9687. me.setDirty(true);
  9688. }
  9689. }
  9690. return sprite || null;
  9691. },
  9692. /**
  9693. * Remove all sprites from the surface, optionally destroying the sprites in the process.
  9694. *
  9695. * For example:
  9696. *
  9697. * drawContainer.getSurface('main').removeAll();
  9698. *
  9699. * @param {Boolean} [isDestroy=false]
  9700. */
  9701. removeAll: function(isDestroy) {
  9702. var me = this,
  9703. items = me.getItems(),
  9704. item, ln, i;
  9705. me.clearing = !!isDestroy;
  9706. for (i = items.length - 1; i >= 0; i--) {
  9707. item = items[i];
  9708. if (isDestroy) {
  9709. // Some sprites may destroy other sprites, however if we're destroying then
  9710. // we don't remove anything from the items array since we'll just clear it later.
  9711. // If a sprite is destroyed, the remove method will just drop out with no harm done.
  9712. item.destroy();
  9713. } else {
  9714. item.setParent(null);
  9715. item.setSurface(null);
  9716. }
  9717. }
  9718. me.clearing = false;
  9719. items.length = 0;
  9720. me.map = {};
  9721. me.dirtyZIndex = true;
  9722. if (!me.destroying) {
  9723. me.setDirty(true);
  9724. }
  9725. },
  9726. /**
  9727. * @private
  9728. */
  9729. applyItems: function(items) {
  9730. if (this.getItems()) {
  9731. this.removeAll(true);
  9732. }
  9733. return Ext.Array.from(this.add(items));
  9734. },
  9735. /**
  9736. * @private
  9737. * Creates an item and appends it to the surface. Called
  9738. * as an internal method when calling `add`.
  9739. */
  9740. createItem: function(config) {
  9741. return Ext.create(config.xclass || 'sprite.' + config.type, config);
  9742. },
  9743. /**
  9744. * Return the minimal bounding box that contains all the sprites bounding boxes in the given list of sprites.
  9745. * @param {Ext.draw.sprite.Sprite[]|Ext.draw.sprite.Sprite} sprites
  9746. * @param {Boolean} [isWithoutTransform=false]
  9747. * @return {{x: Number, y: Number, width: number, height: number}}
  9748. */
  9749. getBBox: function(sprites, isWithoutTransform) {
  9750. sprites = Ext.Array.from(sprites);
  9751. var left = Infinity,
  9752. right = -Infinity,
  9753. top = Infinity,
  9754. bottom = -Infinity,
  9755. ln = sprites.length,
  9756. sprite, bbox, i;
  9757. for (i = 0; i < ln; i++) {
  9758. sprite = sprites[i];
  9759. bbox = sprite.getBBox(isWithoutTransform);
  9760. if (left > bbox.x) {
  9761. left = bbox.x;
  9762. }
  9763. if (right < bbox.x + bbox.width) {
  9764. right = bbox.x + bbox.width;
  9765. }
  9766. if (top > bbox.y) {
  9767. top = bbox.y;
  9768. }
  9769. if (bottom < bbox.y + bbox.height) {
  9770. bottom = bbox.y + bbox.height;
  9771. }
  9772. }
  9773. return {
  9774. x: left,
  9775. y: top,
  9776. width: right - left,
  9777. height: bottom - top
  9778. };
  9779. },
  9780. /**
  9781. * @private
  9782. * @method getOwnerBody
  9783. * The body element of the chart or the draw container
  9784. * (doesn't include docked items like a legend).
  9785. * Draw Container is a Panel in Classic (to allow for docked items)
  9786. * and a Container in Modern, so the body is retrieved differently.
  9787. * @return {Ext.dom.Element}
  9788. */
  9789. /**
  9790. * @private
  9791. * Converts event's page coordinates into surface coordinates.
  9792. * Note: surface's x-coordinates always go LTR, regardless of RTL mode.
  9793. */
  9794. getEventXY: function(e) {
  9795. var me = this,
  9796. isRtl = me.getInherited().rtl,
  9797. pageXY = e.getXY(),
  9798. // Event position in page coordinates.
  9799. container = me.getOwnerBody(),
  9800. // The body of the chart (doesn't include docked items like legend).
  9801. xy = container.getXY(),
  9802. // Surface container position in page coordinates.
  9803. rect = me.getRect() || me.emptyRect,
  9804. // Surface position in surface container coordinates (LTR).
  9805. result = [],
  9806. width;
  9807. if (isRtl) {
  9808. width = container.getWidth();
  9809. // The line below is actually a simplified form of
  9810. // rect[2] - (pageXY[0] - xy[0] - (width - (rect[0] + rect[2]))).
  9811. result[0] = xy[0] - pageXY[0] - rect[0] + width;
  9812. } else {
  9813. result[0] = pageXY[0] - xy[0] - rect[0];
  9814. }
  9815. result[1] = pageXY[1] - xy[1] - rect[1];
  9816. return result;
  9817. },
  9818. /**
  9819. * @method
  9820. * Empty the surface content (without touching the sprites.)
  9821. */
  9822. clear: Ext.emptyFn,
  9823. /**
  9824. * @private
  9825. * Order the items by their z-index if any of that has been changed since last sort.
  9826. */
  9827. orderByZIndex: function() {
  9828. var me = this,
  9829. items = me.getItems(),
  9830. dirtyZIndex = false,
  9831. i, ln;
  9832. if (me.getDirty()) {
  9833. for (i = 0 , ln = items.length; i < ln; i++) {
  9834. if (items[i].attr.dirtyZIndex) {
  9835. dirtyZIndex = true;
  9836. break;
  9837. }
  9838. }
  9839. if (dirtyZIndex) {
  9840. // sort by zIndex
  9841. Ext.Array.sort(items, function(a, b) {
  9842. return a.attr.zIndex - b.attr.zIndex;
  9843. });
  9844. this.setDirty(true);
  9845. }
  9846. for (i = 0 , ln = items.length; i < ln; i++) {
  9847. items[i].attr.dirtyZIndex = false;
  9848. }
  9849. }
  9850. },
  9851. /**
  9852. * Force the element to redraw.
  9853. */
  9854. repaint: function() {
  9855. var me = this;
  9856. me.repaint = Ext.emptyFn;
  9857. Ext.defer(function() {
  9858. delete me.repaint;
  9859. me.element.repaint();
  9860. }, 1);
  9861. },
  9862. /**
  9863. * Triggers the re-rendering of the canvas.
  9864. */
  9865. renderFrame: function() {
  9866. var me = this;
  9867. if (!(me.element && me.getDirty() && me.getRect())) {
  9868. return;
  9869. }
  9870. if (me.dirtyPredecessorCount > 0) {
  9871. me.isPendingRenderFrame = true;
  9872. return;
  9873. }
  9874. var background = me.getBackground(),
  9875. items = me.getItems(),
  9876. item, i, ln;
  9877. // This will also check the dirty flags of the sprites.
  9878. me.orderByZIndex();
  9879. if (me.getDirty()) {
  9880. me.clear();
  9881. me.clearTransform();
  9882. if (background) {
  9883. me.renderSprite(background);
  9884. }
  9885. for (i = 0 , ln = items.length; i < ln; i++) {
  9886. item = items[i];
  9887. if (me.renderSprite(item) === false) {
  9888. return;
  9889. }
  9890. item.attr.textPositionCount = me.textPosition;
  9891. }
  9892. me.setDirty(false);
  9893. }
  9894. },
  9895. /**
  9896. * @method
  9897. * @private
  9898. * Renders a single sprite into the surface.
  9899. * Do not call it from outside `renderFrame` method.
  9900. *
  9901. * @param {Ext.draw.sprite.Sprite} sprite The Sprite to be rendered.
  9902. * @return {Boolean} returns `false` to stop the rendering to continue.
  9903. */
  9904. renderSprite: Ext.emptyFn,
  9905. /**
  9906. * @method flatten
  9907. * Flattens the given drawing surfaces into a single image
  9908. * and returns an object containing the data (in the DataURL format)
  9909. * and the type (e.g. 'png' or 'svg') of that image.
  9910. * @param {Object} size The size of the final image.
  9911. * @param {Number} size.width
  9912. * @param {Number} size.height
  9913. * @param {Ext.draw.Surface[]} surfaces The surfaces to flatten.
  9914. * @return {Object}
  9915. * @return {String} return.data The DataURL of the flattened image.
  9916. * @return {String} return.type The type of the image.
  9917. *
  9918. */
  9919. /**
  9920. * @method
  9921. * @private
  9922. * Clears the current transformation state on the surface.
  9923. */
  9924. clearTransform: Ext.emptyFn,
  9925. /**
  9926. * Destroys the surface. This is done by removing all components from it and
  9927. * also removing its reference to a DOM element.
  9928. *
  9929. * For example:
  9930. *
  9931. * drawContainer.surface.destroy();
  9932. */
  9933. destroy: function() {
  9934. var me = this;
  9935. me.destroying = true;
  9936. me.removeAll(true);
  9937. me.destroying = false;
  9938. me.predecessors = me.successors = null;
  9939. if (me.hasListeners.destroy) {
  9940. me.fireEvent('destroy', me);
  9941. }
  9942. me.callParent();
  9943. }
  9944. });
  9945. /**
  9946. * @private
  9947. * Adds hit testing methods to the Ext.draw.Surface.
  9948. * Included by the Ext.draw.plugin.SpriteEvents.
  9949. */
  9950. Ext.define('Ext.draw.overrides.hittest.Surface', {
  9951. override: 'Ext.draw.Surface',
  9952. /**
  9953. * Performs a hit test on all sprites in the surface, returning the first matching one.
  9954. * @param {Array} point A two-item array containing x and y coordinates of the point
  9955. * in surface coordinate system.
  9956. * @param {Object} options Hit testing options.
  9957. * @return {Object} A hit result object that contains more information about what
  9958. * exactly was hit or null if nothing was hit.
  9959. * @member Ext.draw.Surface
  9960. */
  9961. hitTest: function(point, options) {
  9962. var me = this,
  9963. sprites = me.getItems(),
  9964. i, sprite, result;
  9965. options = options || Ext.draw.sprite.Sprite.defaultHitTestOptions;
  9966. for (i = sprites.length - 1; i >= 0; i--) {
  9967. sprite = sprites[i];
  9968. if (sprite.hitTest) {
  9969. result = sprite.hitTest(point, options);
  9970. if (result) {
  9971. return result;
  9972. }
  9973. }
  9974. }
  9975. return null;
  9976. },
  9977. /**
  9978. * Performs a hit test on all sprites in the surface, returning the first matching one.
  9979. * Since hit testing is typically performed on mouse events, this convenience method
  9980. * converts event's page coordinates to surface coordinates before calling {@link #hitTest}.
  9981. * @param {Object} event An event object.
  9982. * @param {Object} options Hit testing options.
  9983. * @return {Object} A hit result object that contains more information about what
  9984. * exactly was hit or null if nothing was hit.
  9985. * @member Ext.draw.Surface
  9986. */
  9987. hitTestEvent: function(event, options) {
  9988. var xy = this.getEventXY(event);
  9989. return this.hitTest(xy, options);
  9990. }
  9991. });
  9992. /**
  9993. * @class Ext.draw.engine.SvgContext
  9994. *
  9995. * A class that imitates a canvas context but generates svg elements instead.
  9996. */
  9997. Ext.define('Ext.draw.engine.SvgContext', {
  9998. requires: [
  9999. 'Ext.draw.Color'
  10000. ],
  10001. /**
  10002. * @private
  10003. * Properties to be saved/restored in the `save` and `restore` methods.
  10004. */
  10005. toSave: [
  10006. 'strokeOpacity',
  10007. 'strokeStyle',
  10008. 'fillOpacity',
  10009. 'fillStyle',
  10010. 'globalAlpha',
  10011. 'lineWidth',
  10012. 'lineCap',
  10013. 'lineJoin',
  10014. 'lineDash',
  10015. 'lineDashOffset',
  10016. 'miterLimit',
  10017. 'shadowOffsetX',
  10018. 'shadowOffsetY',
  10019. 'shadowBlur',
  10020. 'shadowColor',
  10021. 'globalCompositeOperation',
  10022. 'position',
  10023. 'fillGradient',
  10024. 'strokeGradient'
  10025. ],
  10026. strokeOpacity: 1,
  10027. strokeStyle: 'none',
  10028. fillOpacity: 1,
  10029. fillStyle: 'none',
  10030. lineDas: [],
  10031. lineDashOffset: 0,
  10032. globalAlpha: 1,
  10033. lineWidth: 1,
  10034. lineCap: 'butt',
  10035. lineJoin: 'miter',
  10036. miterLimit: 10,
  10037. shadowOffsetX: 0,
  10038. shadowOffsetY: 0,
  10039. shadowBlur: 0,
  10040. shadowColor: 'none',
  10041. globalCompositeOperation: 'src',
  10042. urlStringRe: /^url\(#([\w\-]+)\)$/,
  10043. constructor: function(SvgSurface) {
  10044. var me = this;
  10045. me.surface = SvgSurface;
  10046. // Stack of contexts.
  10047. me.state = [];
  10048. me.matrix = new Ext.draw.Matrix();
  10049. // Currently manipulated path.
  10050. me.path = null;
  10051. me.clear();
  10052. },
  10053. /**
  10054. * Clears the context.
  10055. */
  10056. clear: function() {
  10057. // Current group to put paths into.
  10058. this.group = this.surface.mainGroup;
  10059. // Position within the current group.
  10060. this.position = 0;
  10061. this.path = null;
  10062. },
  10063. /**
  10064. * @private
  10065. * @param {String} tag
  10066. * @return {*}
  10067. */
  10068. getElement: function(tag) {
  10069. return this.surface.getSvgElement(this.group, tag, this.position++);
  10070. },
  10071. /**
  10072. * Pushes the context state to the state stack.
  10073. */
  10074. save: function() {
  10075. var toSave = this.toSave,
  10076. obj = {},
  10077. group = this.getElement('g'),
  10078. key, i;
  10079. for (i = 0; i < toSave.length; i++) {
  10080. key = toSave[i];
  10081. if (key in this) {
  10082. obj[key] = this[key];
  10083. }
  10084. }
  10085. this.position = 0;
  10086. obj.matrix = this.matrix.clone();
  10087. this.state.push(obj);
  10088. this.group = group;
  10089. return group;
  10090. },
  10091. /**
  10092. * Pops the state stack and restores the state.
  10093. */
  10094. restore: function() {
  10095. var toSave = this.toSave,
  10096. obj = this.state.pop(),
  10097. group = this.group,
  10098. children = group.dom.childNodes,
  10099. key, i;
  10100. // Removing extra DOM elements that were not reused.
  10101. while (children.length > this.position) {
  10102. group.last().destroy();
  10103. }
  10104. for (i = 0; i < toSave.length; i++) {
  10105. key = toSave[i];
  10106. if (key in obj) {
  10107. this[key] = obj[key];
  10108. } else {
  10109. delete this[key];
  10110. }
  10111. }
  10112. this.setTransform.apply(this, obj.matrix.elements);
  10113. this.group = group.getParent();
  10114. },
  10115. /**
  10116. * Changes the transformation matrix to apply the matrix given by the arguments as described below.
  10117. * @param {Number} xx
  10118. * @param {Number} yx
  10119. * @param {Number} xy
  10120. * @param {Number} yy
  10121. * @param {Number} dx
  10122. * @param {Number} dy
  10123. */
  10124. transform: function(xx, yx, xy, yy, dx, dy) {
  10125. if (this.path) {
  10126. var inv = Ext.draw.Matrix.fly([
  10127. xx,
  10128. yx,
  10129. xy,
  10130. yy,
  10131. dx,
  10132. dy
  10133. ]).inverse();
  10134. this.path.transform(inv);
  10135. }
  10136. this.matrix.append(xx, yx, xy, yy, dx, dy);
  10137. },
  10138. /**
  10139. * Changes the transformation matrix to the matrix given by the arguments as described below.
  10140. * @param {Number} xx
  10141. * @param {Number} yx
  10142. * @param {Number} xy
  10143. * @param {Number} yy
  10144. * @param {Number} dx
  10145. * @param {Number} dy
  10146. */
  10147. setTransform: function(xx, yx, xy, yy, dx, dy) {
  10148. if (this.path) {
  10149. this.path.transform(this.matrix);
  10150. }
  10151. this.matrix.reset();
  10152. this.transform(xx, yx, xy, yy, dx, dy);
  10153. },
  10154. /**
  10155. * Scales the current context by the specified horizontal (x) and vertical (y) factors.
  10156. * @param {Number} x The horizontal scaling factor, where 1 equals unity or 100% scale.
  10157. * @param {Number} y The vertical scaling factor.
  10158. */
  10159. scale: function(x, y) {
  10160. this.transform(x, 0, 0, y, 0, 0);
  10161. },
  10162. /**
  10163. * Rotates the current context coordinates (that is, a transformation matrix).
  10164. * @param {Number} angle The rotation angle, in radians.
  10165. */
  10166. rotate: function(angle) {
  10167. var xx = Math.cos(angle),
  10168. yx = Math.sin(angle),
  10169. xy = -Math.sin(angle),
  10170. yy = Math.cos(angle);
  10171. this.transform(xx, yx, xy, yy, 0, 0);
  10172. },
  10173. /**
  10174. * Specifies values to move the origin point in a canvas.
  10175. * @param {Number} x The value to add to horizontal (or x) coordinates.
  10176. * @param {Number} y The value to add to vertical (or y) coordinates.
  10177. */
  10178. translate: function(x, y) {
  10179. this.transform(1, 0, 0, 1, x, y);
  10180. },
  10181. setGradientBBox: function(bbox) {
  10182. this.bbox = bbox;
  10183. },
  10184. /**
  10185. * Resets the current default path.
  10186. */
  10187. beginPath: function() {
  10188. this.path = new Ext.draw.Path();
  10189. },
  10190. /**
  10191. * Creates a new subpath with the given point.
  10192. * @param {Number} x
  10193. * @param {Number} y
  10194. */
  10195. moveTo: function(x, y) {
  10196. if (!this.path) {
  10197. this.beginPath();
  10198. }
  10199. this.path.moveTo(x, y);
  10200. this.path.element = null;
  10201. },
  10202. /**
  10203. * Adds the given point to the current subpath, connected to the previous one by a straight line.
  10204. * @param {Number} x
  10205. * @param {Number} y
  10206. */
  10207. lineTo: function(x, y) {
  10208. if (!this.path) {
  10209. this.beginPath();
  10210. }
  10211. this.path.lineTo(x, y);
  10212. this.path.element = null;
  10213. },
  10214. /**
  10215. * Adds a new closed subpath to the path, representing the given rectangle.
  10216. * @param {Number} x
  10217. * @param {Number} y
  10218. * @param {Number} width
  10219. * @param {Number} height
  10220. */
  10221. rect: function(x, y, width, height) {
  10222. this.moveTo(x, y);
  10223. this.lineTo(x + width, y);
  10224. this.lineTo(x + width, y + height);
  10225. this.lineTo(x, y + height);
  10226. this.closePath();
  10227. },
  10228. /**
  10229. * Paints the box that outlines the given rectangle onto the canvas, using the current stroke style.
  10230. * @param {Number} x
  10231. * @param {Number} y
  10232. * @param {Number} width
  10233. * @param {Number} height
  10234. */
  10235. strokeRect: function(x, y, width, height) {
  10236. this.beginPath();
  10237. this.rect(x, y, width, height);
  10238. this.stroke();
  10239. },
  10240. /**
  10241. * Paints the given rectangle onto the canvas, using the current fill style.
  10242. * @param {Number} x
  10243. * @param {Number} y
  10244. * @param {Number} width
  10245. * @param {Number} height
  10246. */
  10247. fillRect: function(x, y, width, height) {
  10248. this.beginPath();
  10249. this.rect(x, y, width, height);
  10250. this.fill();
  10251. },
  10252. /**
  10253. * Marks the current subpath as closed, and starts a new subpath with a point the same as the start and end of the newly closed subpath.
  10254. */
  10255. closePath: function() {
  10256. if (!this.path) {
  10257. this.beginPath();
  10258. }
  10259. this.path.closePath();
  10260. this.path.element = null;
  10261. },
  10262. /**
  10263. * Arc command using svg parameters.
  10264. * @param {Number} r1
  10265. * @param {Number} r2
  10266. * @param {Number} rotation
  10267. * @param {Number} large
  10268. * @param {Number} swipe
  10269. * @param {Number} x2
  10270. * @param {Number} y2
  10271. */
  10272. arcSvg: function(r1, r2, rotation, large, swipe, x2, y2) {
  10273. if (!this.path) {
  10274. this.beginPath();
  10275. }
  10276. this.path.arcSvg(r1, r2, rotation, large, swipe, x2, y2);
  10277. this.path.element = null;
  10278. },
  10279. /**
  10280. * Adds points to the subpath such that the arc described by the circumference of the circle described by the arguments, starting at the given start angle and ending at the given end angle, going in the given direction (defaulting to clockwise), is added to the path, connected to the previous point by a straight line.
  10281. * @param {Number} x
  10282. * @param {Number} y
  10283. * @param {Number} radius
  10284. * @param {Number} startAngle
  10285. * @param {Number} endAngle
  10286. * @param {Number} anticlockwise
  10287. */
  10288. arc: function(x, y, radius, startAngle, endAngle, anticlockwise) {
  10289. if (!this.path) {
  10290. this.beginPath();
  10291. }
  10292. this.path.arc(x, y, radius, startAngle, endAngle, anticlockwise);
  10293. this.path.element = null;
  10294. },
  10295. /**
  10296. * Adds points to the subpath such that the arc described by the circumference of the ellipse described by the arguments, starting at the given start angle and ending at the given end angle, going in the given direction (defaulting to clockwise), is added to the path, connected to the previous point by a straight line.
  10297. * @param {Number} x
  10298. * @param {Number} y
  10299. * @param {Number} radiusX
  10300. * @param {Number} radiusY
  10301. * @param {Number} rotation
  10302. * @param {Number} startAngle
  10303. * @param {Number} endAngle
  10304. * @param {Number} anticlockwise
  10305. */
  10306. ellipse: function(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise) {
  10307. if (!this.path) {
  10308. this.beginPath();
  10309. }
  10310. this.path.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise);
  10311. this.path.element = null;
  10312. },
  10313. /**
  10314. * Adds an arc with the given control points and radius to the current subpath, connected to the previous point by a straight line.
  10315. * If two radii are provided, the first controls the width of the arc's ellipse, and the second controls the height. If only one is provided, or if they are the same, the arc is from a circle.
  10316. * In the case of an ellipse, the rotation argument controls the clockwise inclination of the ellipse relative to the x-axis.
  10317. * @param {Number} x1
  10318. * @param {Number} y1
  10319. * @param {Number} x2
  10320. * @param {Number} y2
  10321. * @param {Number} radiusX
  10322. * @param {Number} radiusY
  10323. * @param {Number} rotation
  10324. */
  10325. arcTo: function(x1, y1, x2, y2, radiusX, radiusY, rotation) {
  10326. if (!this.path) {
  10327. this.beginPath();
  10328. }
  10329. this.path.arcTo(x1, y1, x2, y2, radiusX, radiusY, rotation);
  10330. this.path.element = null;
  10331. },
  10332. /**
  10333. * Adds the given point to the current subpath, connected to the previous one by a cubic Bézier curve with the given control points.
  10334. * @param {Number} x1
  10335. * @param {Number} y1
  10336. * @param {Number} x2
  10337. * @param {Number} y2
  10338. * @param {Number} x3
  10339. * @param {Number} y3
  10340. */
  10341. bezierCurveTo: function(x1, y1, x2, y2, x3, y3) {
  10342. if (!this.path) {
  10343. this.beginPath();
  10344. }
  10345. this.path.bezierCurveTo(x1, y1, x2, y2, x3, y3);
  10346. this.path.element = null;
  10347. },
  10348. /**
  10349. * Strokes the given text at the given position. If a maximum width is provided, the text will be scaled to fit that width if necessary.
  10350. * @param {String} text
  10351. * @param {Number} x
  10352. * @param {Number} y
  10353. */
  10354. strokeText: function(text, x, y) {
  10355. text = String(text);
  10356. if (this.strokeStyle) {
  10357. var element = this.getElement('text'),
  10358. tspan = this.surface.getSvgElement(element, 'tspan', 0);
  10359. this.surface.setElementAttributes(element, {
  10360. "x": x,
  10361. "y": y,
  10362. "transform": this.matrix.toSvg(),
  10363. "stroke": this.strokeStyle,
  10364. "fill": "none",
  10365. "opacity": this.globalAlpha,
  10366. "stroke-opacity": this.strokeOpacity,
  10367. "style": "font: " + this.font,
  10368. "stroke-dasharray": this.lineDash.join(','),
  10369. "stroke-dashoffset": this.lineDashOffset
  10370. });
  10371. if (this.lineDash.length) {
  10372. this.surface.setElementAttributes(element, {
  10373. "stroke-dasharray": this.lineDash.join(','),
  10374. "stroke-dashoffset": this.lineDashOffset
  10375. });
  10376. }
  10377. if (tspan.dom.firstChild) {
  10378. tspan.dom.removeChild(tspan.dom.firstChild);
  10379. }
  10380. this.surface.setElementAttributes(tspan, {
  10381. "alignment-baseline": "alphabetic"
  10382. });
  10383. tspan.dom.appendChild(document.createTextNode(Ext.String.htmlDecode(text)));
  10384. }
  10385. },
  10386. /**
  10387. * Fills the given text at the given position. If a maximum width is provided, the text will be scaled to fit that width if necessary.
  10388. * @param {String} text
  10389. * @param {Number} x
  10390. * @param {Number} y
  10391. */
  10392. fillText: function(text, x, y) {
  10393. text = String(text);
  10394. if (this.fillStyle) {
  10395. var element = this.getElement('text'),
  10396. tspan = this.surface.getSvgElement(element, 'tspan', 0);
  10397. this.surface.setElementAttributes(element, {
  10398. "x": x,
  10399. "y": y,
  10400. "transform": this.matrix.toSvg(),
  10401. "fill": this.fillStyle,
  10402. "opacity": this.globalAlpha,
  10403. "fill-opacity": this.fillOpacity,
  10404. "style": "font: " + this.font
  10405. });
  10406. if (tspan.dom.firstChild) {
  10407. tspan.dom.removeChild(tspan.dom.firstChild);
  10408. }
  10409. this.surface.setElementAttributes(tspan, {
  10410. "alignment-baseline": "alphabetic"
  10411. });
  10412. tspan.dom.appendChild(document.createTextNode(Ext.String.htmlDecode(text)));
  10413. }
  10414. },
  10415. /**
  10416. * Draws the given image onto the canvas.
  10417. * If the first argument isn't an img, canvas, or video element, throws a TypeMismatchError exception. If the image has no image data, throws an InvalidStateError exception. If the one of the source rectangle dimensions is zero, throws an IndexSizeError exception. If the image isn't yet fully decoded, then nothing is drawn.
  10418. * @param {HTMLElement} image
  10419. * @param {Number} sx
  10420. * @param {Number} sy
  10421. * @param {Number} sw
  10422. * @param {Number} sh
  10423. * @param {Number} dx
  10424. * @param {Number} dy
  10425. * @param {Number} dw
  10426. * @param {Number} dh
  10427. */
  10428. drawImage: function(image, sx, sy, sw, sh, dx, dy, dw, dh) {
  10429. var me = this,
  10430. element = me.getElement('image'),
  10431. x = sx,
  10432. y = sy,
  10433. width = typeof sw === 'undefined' ? image.width : sw,
  10434. height = typeof sh === 'undefined' ? image.height : sh,
  10435. viewBox = null;
  10436. if (typeof dh !== 'undefined') {
  10437. viewBox = sx + " " + sy + " " + sw + " " + sh;
  10438. x = dx;
  10439. y = dy;
  10440. width = dw;
  10441. height = dh;
  10442. }
  10443. element.dom.setAttributeNS("http:/" + "/www.w3.org/1999/xlink", "href", image.src);
  10444. me.surface.setElementAttributes(element, {
  10445. viewBox: viewBox,
  10446. x: x,
  10447. y: y,
  10448. width: width,
  10449. height: height,
  10450. opacity: me.globalAlpha,
  10451. transform: me.matrix.toSvg()
  10452. });
  10453. },
  10454. /**
  10455. * Fills the subpaths of the current default path or the given path with the current fill style.
  10456. */
  10457. fill: function() {
  10458. var me = this;
  10459. if (!me.path) {
  10460. return;
  10461. }
  10462. if (me.fillStyle) {
  10463. var path,
  10464. fillGradient = me.fillGradient,
  10465. element = me.path.element,
  10466. bbox = me.bbox,
  10467. fill;
  10468. if (!element) {
  10469. path = me.path.toString();
  10470. element = me.path.element = me.getElement('path');
  10471. me.surface.setElementAttributes(element, {
  10472. "d": path,
  10473. "transform": me.matrix.toSvg()
  10474. });
  10475. }
  10476. if (fillGradient && bbox) {
  10477. // This indirectly calls ctx.createLinearGradient or ctx.createRadialGradient,
  10478. // depending on the type of gradient, and returns an instance of
  10479. // Ext.draw.engine.SvgContext.Gradient.
  10480. fill = fillGradient.generateGradient(me, bbox);
  10481. } else {
  10482. fill = me.fillStyle;
  10483. }
  10484. me.surface.setElementAttributes(element, {
  10485. "fill": fill,
  10486. "fill-opacity": me.fillOpacity * me.globalAlpha
  10487. });
  10488. }
  10489. },
  10490. /**
  10491. * Strokes the subpaths of the current default path or the given path with the current stroke style.
  10492. */
  10493. stroke: function() {
  10494. var me = this;
  10495. if (!me.path) {
  10496. return;
  10497. }
  10498. if (me.strokeStyle) {
  10499. var path,
  10500. strokeGradient = me.strokeGradient,
  10501. element = me.path.element,
  10502. bbox = me.bbox,
  10503. stroke;
  10504. if (!element || !me.path.svgString) {
  10505. path = me.path.toString();
  10506. if (!path) {
  10507. return;
  10508. }
  10509. element = me.path.element = me.getElement('path');
  10510. me.surface.setElementAttributes(element, {
  10511. "fill": "none",
  10512. "d": path,
  10513. "transform": me.matrix.toSvg()
  10514. });
  10515. }
  10516. if (strokeGradient && bbox) {
  10517. // This indirectly calls ctx.createLinearGradient or ctx.createRadialGradient,
  10518. // depending on the type of gradient, and returns an instance of
  10519. // Ext.draw.engine.SvgContext.Gradient.
  10520. stroke = strokeGradient.generateGradient(me, bbox);
  10521. } else {
  10522. stroke = me.strokeStyle;
  10523. }
  10524. me.surface.setElementAttributes(element, {
  10525. "stroke": stroke,
  10526. "stroke-linecap": me.lineCap,
  10527. "stroke-linejoin": me.lineJoin,
  10528. "stroke-width": me.lineWidth,
  10529. "stroke-opacity": me.strokeOpacity * me.globalAlpha,
  10530. "stroke-dasharray": me.lineDash.join(','),
  10531. "stroke-dashoffset": me.lineDashOffset
  10532. });
  10533. if (me.lineDash.length) {
  10534. me.surface.setElementAttributes(element, {
  10535. "stroke-dasharray": me.lineDash.join(','),
  10536. "stroke-dashoffset": me.lineDashOffset
  10537. });
  10538. }
  10539. }
  10540. },
  10541. /**
  10542. * @protected
  10543. *
  10544. * Note: After the method guarantees the transform matrix will be inverted.
  10545. * @param {Object} attr The attribute object
  10546. * @param {Boolean} [transformFillStroke] Indicate whether to transform fill and stroke. If this is not
  10547. * given, then uses `attr.transformFillStroke` instead.
  10548. */
  10549. fillStroke: function(attr, transformFillStroke) {
  10550. var ctx = this,
  10551. fillStyle = ctx.fillStyle,
  10552. strokeStyle = ctx.strokeStyle,
  10553. fillOpacity = ctx.fillOpacity,
  10554. strokeOpacity = ctx.strokeOpacity;
  10555. if (transformFillStroke === undefined) {
  10556. transformFillStroke = attr.transformFillStroke;
  10557. }
  10558. if (!transformFillStroke) {
  10559. attr.inverseMatrix.toContext(ctx);
  10560. }
  10561. if (fillStyle && fillOpacity !== 0) {
  10562. ctx.fill();
  10563. }
  10564. if (strokeStyle && strokeOpacity !== 0) {
  10565. ctx.stroke();
  10566. }
  10567. },
  10568. appendPath: function(path) {
  10569. this.path = path.clone();
  10570. },
  10571. setLineDash: function(lineDash) {
  10572. this.lineDash = lineDash;
  10573. },
  10574. getLineDash: function() {
  10575. return this.lineDash;
  10576. },
  10577. /**
  10578. * Returns an object that represents a linear gradient that paints along the line
  10579. * given by the coordinates represented by the arguments.
  10580. * @param {Number} x0
  10581. * @param {Number} y0
  10582. * @param {Number} x1
  10583. * @param {Number} y1
  10584. * @return {Ext.draw.engine.SvgContext.Gradient}
  10585. */
  10586. createLinearGradient: function(x0, y0, x1, y1) {
  10587. var me = this,
  10588. element = me.surface.getNextDef('linearGradient'),
  10589. gradient;
  10590. me.surface.setElementAttributes(element, {
  10591. "x1": x0,
  10592. "y1": y0,
  10593. "x2": x1,
  10594. "y2": y1,
  10595. "gradientUnits": "userSpaceOnUse"
  10596. });
  10597. gradient = new Ext.draw.engine.SvgContext.Gradient(me, me.surface, element);
  10598. return gradient;
  10599. },
  10600. /**
  10601. * Returns a CanvasGradient object that represents a radial gradient that paints
  10602. * along the cone given by the circles represented by the arguments.
  10603. * If either of the radii are negative, throws an IndexSizeError exception.
  10604. * @param {Number} x0
  10605. * @param {Number} y0
  10606. * @param {Number} r0
  10607. * @param {Number} x1
  10608. * @param {Number} y1
  10609. * @param {Number} r1
  10610. * @return {Ext.draw.engine.SvgContext.Gradient}
  10611. */
  10612. createRadialGradient: function(x0, y0, r0, x1, y1, r1) {
  10613. var me = this,
  10614. element = me.surface.getNextDef('radialGradient'),
  10615. gradient;
  10616. me.surface.setElementAttributes(element, {
  10617. fx: x0,
  10618. fy: y0,
  10619. cx: x1,
  10620. cy: y1,
  10621. r: r1,
  10622. gradientUnits: 'userSpaceOnUse'
  10623. });
  10624. gradient = new Ext.draw.engine.SvgContext.Gradient(me, me.surface, element, r0 / r1);
  10625. return gradient;
  10626. }
  10627. });
  10628. /**
  10629. * @class Ext.draw.engine.SvgContext.Gradient
  10630. *
  10631. * A class that implements native CanvasGradient interface
  10632. * (https://developer.mozilla.org/en/docs/Web/API/CanvasGradient)
  10633. * and a `toString` method that returns the ID of the gradient.
  10634. */
  10635. Ext.define('Ext.draw.engine.SvgContext.Gradient', {
  10636. // Gradients workflow in SVG engine:
  10637. //
  10638. // Inside the 'fill' & 'stroke' methods of the SVG Context
  10639. // we check if the 'ctx.fillGradient' or 'ctx.strokeGradient'
  10640. // objects exist.
  10641. // These objects are instances of Ext.draw.gradient.Gradient
  10642. // and are assigned to the ctx by the sprite's 'useAttributes' method,
  10643. // if the sprite has any gradients.
  10644. //
  10645. // Additionally, we check if the 'ctx.bbox' object exists - the bounding box
  10646. // for the gradients, set by the sprite's 'setGradientBBox' method.
  10647. //
  10648. // If we have both bbox and a valid instance of Ext.draw.gradient.Gradient,
  10649. // the 'generateGradient' method of the instance is called,
  10650. // which in turn calls 'ctx.createLinearGradient' or 'ctx.createRadialGradient'
  10651. // depending on the type of the gradient represented by the instance.
  10652. // These methods create a 'linearGradient' or 'radialGradient' SVG
  10653. // node and wrap it into a Ext.draw.engine.SvgContext.Gradient instance.
  10654. //
  10655. // The Ext.draw.engine.SvgContext.Gradient instance is then used internally
  10656. // by the Ext.draw.gradient.Gradient to add color 'stop' nodes
  10657. // to the gradient node, and by the SVG context when the 'fill' or
  10658. // 'stroke' attribute of a 'path' node is set to the Ext.draw.engine.SvgContext.Gradient
  10659. // instance, which is implicitly converted to a string - a 'url(#id)' reference
  10660. // to the gradient element wrapped by the instance.
  10661. isGradient: true,
  10662. constructor: function(ctx, surface, element, compression) {
  10663. var me = this;
  10664. me.ctx = ctx;
  10665. me.surface = surface;
  10666. me.element = element;
  10667. me.position = 0;
  10668. me.compression = compression || 0;
  10669. },
  10670. /**
  10671. * Adds a color stop with the given color to the gradient at the given offset. 0.0 is the offset at one end of the gradient, 1.0 is the offset at the other end.
  10672. * @param {Number} offset
  10673. * @param {String} color
  10674. */
  10675. addColorStop: function(offset, color) {
  10676. var me = this,
  10677. stop = me.surface.getSvgElement(me.element, 'stop', me.position++),
  10678. compression = me.compression;
  10679. me.surface.setElementAttributes(stop, {
  10680. "offset": (((1 - compression) * offset + compression) * 100).toFixed(2) + '%',
  10681. "stop-color": color,
  10682. "stop-opacity": Ext.util.Color.fly(color).a.toFixed(15)
  10683. });
  10684. },
  10685. toString: function() {
  10686. var children = this.element.dom.childNodes;
  10687. // Removing surplus stops in case existing gradient element with more stops was reused.
  10688. while (children.length > this.position) {
  10689. Ext.fly(children[children.length - 1]).destroy();
  10690. }
  10691. return 'url(#' + this.element.getId() + ')';
  10692. }
  10693. });
  10694. /**
  10695. * @class Ext.draw.engine.Svg
  10696. * @extends Ext.draw.Surface
  10697. *
  10698. * SVG engine.
  10699. */
  10700. Ext.define('Ext.draw.engine.Svg', {
  10701. extend: 'Ext.draw.Surface',
  10702. requires: [
  10703. 'Ext.draw.engine.SvgContext'
  10704. ],
  10705. isSVG: true,
  10706. config: {
  10707. /**
  10708. * @cfg {Boolean} highPrecision
  10709. * Nothing needs to be done in high precision mode.
  10710. */
  10711. highPrecision: false
  10712. },
  10713. getElementConfig: function() {
  10714. return {
  10715. reference: 'element',
  10716. style: {
  10717. position: 'absolute'
  10718. },
  10719. children: [
  10720. {
  10721. reference: 'bodyElement',
  10722. style: {
  10723. width: '100%',
  10724. height: '100%',
  10725. position: 'relative'
  10726. },
  10727. children: [
  10728. {
  10729. tag: 'svg',
  10730. reference: 'svgElement',
  10731. namespace: "http://www.w3.org/2000/svg",
  10732. width: '100%',
  10733. height: '100%',
  10734. version: 1.1
  10735. }
  10736. ]
  10737. }
  10738. ]
  10739. };
  10740. },
  10741. constructor: function(config) {
  10742. var me = this;
  10743. me.callParent([
  10744. config
  10745. ]);
  10746. me.mainGroup = me.createSvgNode("g");
  10747. me.defsElement = me.createSvgNode("defs");
  10748. // me.svgElement is assigned in element creation of Ext.Component.
  10749. me.svgElement.appendChild(me.mainGroup);
  10750. me.svgElement.appendChild(me.defsElement);
  10751. me.ctx = new Ext.draw.engine.SvgContext(me);
  10752. },
  10753. /**
  10754. * Creates a DOM element under the SVG namespace of the given type.
  10755. * @param {String} type The type of the SVG DOM element.
  10756. * @return {*} The created element.
  10757. */
  10758. createSvgNode: function(type) {
  10759. var node = document.createElementNS("http://www.w3.org/2000/svg", type);
  10760. return Ext.get(node);
  10761. },
  10762. /**
  10763. * @private
  10764. * Returns the SVG DOM element at the given position.
  10765. * If it does not already exist or is a different element tag,
  10766. * it will be created and inserted into the DOM.
  10767. * @param {Ext.dom.Element} group The parent DOM element.
  10768. * @param {String} tag The SVG element tag.
  10769. * @param {Number} position The position of the element in the DOM.
  10770. * @return {Ext.dom.Element} The SVG element.
  10771. */
  10772. getSvgElement: function(group, tag, position) {
  10773. var childNodes = group.dom.childNodes,
  10774. length = childNodes.length,
  10775. element;
  10776. if (position < length) {
  10777. element = childNodes[position];
  10778. if (element.tagName === tag) {
  10779. return Ext.get(element);
  10780. } else {
  10781. Ext.destroy(element);
  10782. }
  10783. } else if (position > length) {
  10784. Ext.raise("Invalid position.");
  10785. }
  10786. element = Ext.get(this.createSvgNode(tag));
  10787. if (position === 0) {
  10788. group.insertFirst(element);
  10789. } else {
  10790. element.insertAfter(Ext.fly(childNodes[position - 1]));
  10791. }
  10792. element.cache = {};
  10793. return element;
  10794. },
  10795. /**
  10796. * @private
  10797. * Applies attributes to the given element.
  10798. * @param {Ext.dom.Element} element The DOM element to be applied.
  10799. * @param {Object} attributes The attributes to apply to the element.
  10800. */
  10801. setElementAttributes: function(element, attributes) {
  10802. var dom = element.dom,
  10803. cache = element.cache,
  10804. name, value;
  10805. for (name in attributes) {
  10806. value = attributes[name];
  10807. if (cache[name] !== value) {
  10808. cache[name] = value;
  10809. dom.setAttribute(name, value);
  10810. }
  10811. }
  10812. },
  10813. /**
  10814. * @private
  10815. * Gets the next reference element under the SVG 'defs' tag.
  10816. * @param {String} tagName The type of reference element.
  10817. * @return {Ext.dom.Element} The reference element.
  10818. */
  10819. getNextDef: function(tagName) {
  10820. return this.getSvgElement(this.defsElement, tagName, this.defsPosition++);
  10821. },
  10822. /**
  10823. * @method clearTransform
  10824. * @inheritdoc
  10825. */
  10826. clearTransform: function() {
  10827. var me = this;
  10828. me.mainGroup.set({
  10829. transform: me.matrix.toSvg()
  10830. });
  10831. },
  10832. /**
  10833. * @method clear
  10834. * @inheritdoc
  10835. */
  10836. clear: function() {
  10837. this.ctx.clear();
  10838. this.removeSurplusDefs();
  10839. this.defsPosition = 0;
  10840. },
  10841. removeSurplusDefs: function() {
  10842. var defsElement = this.defsElement,
  10843. defs = defsElement.dom.childNodes,
  10844. ln = defs.length,
  10845. i;
  10846. for (i = ln - 1; i > this.defsPosition; i--) {
  10847. defsElement.removeChild(defs[i]);
  10848. }
  10849. },
  10850. /**
  10851. * @method renderSprite
  10852. * @inheritdoc
  10853. */
  10854. renderSprite: function(sprite) {
  10855. var me = this,
  10856. rect = me.getRect(),
  10857. ctx = me.ctx;
  10858. // This check is simplistic, but should result in a better performance
  10859. // compared to !sprite.isVisible() when most surface sprites are visible.
  10860. if (sprite.attr.hidden || sprite.attr.globalAlpha === 0) {
  10861. // Create an empty group for each hidden sprite,
  10862. // so that when these sprites do become visible,
  10863. // they don't need groups to be created and don't
  10864. // mess up the previous order of elements in the
  10865. // document, i.e. sprites rendered in the next
  10866. // frame reuse the same elements they used in the
  10867. // previous frame.
  10868. ctx.save();
  10869. ctx.restore();
  10870. return;
  10871. }
  10872. // Each sprite is rendered in its own group ('g' element),
  10873. // returned by the `ctx.save` method.
  10874. // Essentially, the group _is_ the sprite.
  10875. sprite.element = ctx.save();
  10876. sprite.preRender(this);
  10877. sprite.useAttributes(ctx, rect);
  10878. if (false === sprite.render(this, ctx, [
  10879. 0,
  10880. 0,
  10881. rect[2],
  10882. rect[3]
  10883. ])) {
  10884. return false;
  10885. }
  10886. sprite.setDirty(false);
  10887. ctx.restore();
  10888. },
  10889. /**
  10890. * @private
  10891. */
  10892. toSVG: function(size, surfaces) {
  10893. var className = Ext.getClassName(this),
  10894. svg, surface, rect, i;
  10895. svg = '<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg"' + ' width="' + size.width + '"' + ' height="' + size.height + '">';
  10896. for (i = 0; i < surfaces.length; i++) {
  10897. surface = surfaces[i];
  10898. if (Ext.getClassName(surface) !== className) {
  10899. continue;
  10900. }
  10901. rect = surface.getRect();
  10902. svg += '<g transform="translate(' + rect[0] + ',' + rect[1] + ')">';
  10903. svg += this.serializeNode(surface.svgElement.dom);
  10904. svg += '</g>';
  10905. }
  10906. svg += '</svg>';
  10907. return svg;
  10908. },
  10909. b64EncodeUnicode: function(str) {
  10910. // Since DOMStrings are 16-bit-encoded strings, in most browsers calling window.btoa
  10911. // on a Unicode string will cause a Character Out Of Range exception if a character
  10912. // exceeds the range of a 8-bit ASCII-encoded character. More information:
  10913. // https://developer.mozilla.org/en/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
  10914. return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
  10915. return String.fromCharCode('0x' + p1);
  10916. }));
  10917. },
  10918. flatten: function(size, surfaces) {
  10919. var svg = '<?xml version="1.0" standalone="yes"?>';
  10920. svg += this.toSVG(size, surfaces);
  10921. return {
  10922. data: 'data:image/svg+xml;base64,' + this.b64EncodeUnicode(svg),
  10923. type: 'svg'
  10924. };
  10925. },
  10926. /**
  10927. * @private
  10928. * Serializes an SVG DOM element and its children recursively into a string.
  10929. * @param {Object} node DOM element to serialize.
  10930. * @return {String}
  10931. */
  10932. serializeNode: function(node) {
  10933. var result = '',
  10934. i, n, attr, child;
  10935. if (node.nodeType === document.TEXT_NODE) {
  10936. return node.nodeValue;
  10937. }
  10938. result += '<' + node.nodeName;
  10939. if (node.attributes.length) {
  10940. for (i = 0 , n = node.attributes.length; i < n; i++) {
  10941. attr = node.attributes[i];
  10942. result += ' ' + attr.name + '="' + Ext.String.htmlEncode(attr.value) + '"';
  10943. }
  10944. }
  10945. result += '>';
  10946. if (node.childNodes && node.childNodes.length) {
  10947. for (i = 0 , n = node.childNodes.length; i < n; i++) {
  10948. child = node.childNodes[i];
  10949. result += this.serializeNode(child);
  10950. }
  10951. }
  10952. result += '</' + node.nodeName + '>';
  10953. return result;
  10954. },
  10955. /**
  10956. * Destroys the Canvas element and prepares it for Garbage Collection.
  10957. */
  10958. destroy: function() {
  10959. var me = this;
  10960. me.ctx.destroy();
  10961. me.mainGroup.destroy();
  10962. me.defsElement.destroy();
  10963. delete me.mainGroup;
  10964. delete me.defsElement;
  10965. delete me.ctx;
  10966. me.callParent();
  10967. },
  10968. remove: function(sprite, destroySprite) {
  10969. if (sprite && sprite.element) {
  10970. // If sprite has an associated SVG element, remove it from the surface.
  10971. sprite.element.destroy();
  10972. sprite.element = null;
  10973. }
  10974. this.callParent(arguments);
  10975. }
  10976. });
  10977. // @define Ext.draw.engine.excanvas
  10978. /**
  10979. * @private
  10980. */
  10981. Ext.draw || (Ext.draw = {});
  10982. Ext.draw.engine || (Ext.draw.engine = {});
  10983. Ext.draw.engine.excanvas = true;
  10984. // Copyright 2006 Google Inc.
  10985. //
  10986. // Licensed under the Apache License, Version 2.0 (the "License");
  10987. // you may not use this file except in compliance with the License.
  10988. // You may obtain a copy of the License at
  10989. //
  10990. // http://www.apache.org/licenses/LICENSE-2.0
  10991. //
  10992. // Unless required by applicable law or agreed to in writing, software
  10993. // distributed under the License is distributed on an "AS IS" BASIS,
  10994. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10995. // See the License for the specific language governing permissions and
  10996. // limitations under the License.
  10997. // Known Issues:
  10998. //
  10999. // * Patterns only support repeat.
  11000. // * Radial gradient are not implemented. The VML version of these look very
  11001. // different from the canvas one.
  11002. // * Clipping paths are not implemented.
  11003. // * Coordsize. The width and height attribute have higher priority than the
  11004. // width and height style values which isn't correct.
  11005. // * Painting mode isn't implemented.
  11006. // * Canvas width/height should is using content-box by default. IE in
  11007. // Quirks mode will draw the canvas using border-box. Either change your
  11008. // doctype to HTML5
  11009. // (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
  11010. // or use Box Sizing Behavior from WebFX
  11011. // (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
  11012. // * Non uniform scaling does not correctly scale strokes.
  11013. // * Optimize. There is always room for speed improvements.
  11014. // Only add this code if we do not already have a canvas implementation
  11015. if (!document.createElement('canvas').getContext) {
  11016. (function() {
  11017. // alias some functions to make (compiled) code shorter
  11018. var m = Math;
  11019. var mr = m.round;
  11020. var ms = m.sin;
  11021. var mc = m.cos;
  11022. var abs = m.abs;
  11023. var sqrt = m.sqrt;
  11024. // this is used for sub pixel precision
  11025. var Z = 10;
  11026. var Z2 = Z / 2;
  11027. var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];
  11028. /*
  11029. * @method getContext
  11030. * This function is assigned to the <canvas></canvas> elements as element.getContext().
  11031. * @return {CanvasRenderingContext2D_}
  11032. */
  11033. function getContext() {
  11034. return this.context_ || (this.context_ = new CanvasRenderingContext2D_(this));
  11035. }
  11036. var slice = Array.prototype.slice;
  11037. /*
  11038. * @method bind
  11039. * Binds a function to an object. The returned function will always use the
  11040. * passed in {@code obj} as {@code this}.
  11041. *
  11042. * Example:
  11043. *
  11044. * g = bind(f, obj, a, b)
  11045. * g(c, d) // will do f.call(obj, a, b, c, d)
  11046. *
  11047. * @param {Function} f The function to bind the object to
  11048. * @param {Object} obj The object that should act as this when the function
  11049. * is called
  11050. * @param {*} var_args Rest arguments that will be used as the initial
  11051. * arguments when the function is called
  11052. * @return {Function} A new function that has bound this
  11053. */
  11054. function bind(f, obj, var_args) {
  11055. var a = slice.call(arguments, 2);
  11056. return function() {
  11057. return f.apply(obj, a.concat(slice.call(arguments)));
  11058. };
  11059. }
  11060. function encodeHtmlAttribute(s) {
  11061. return String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
  11062. }
  11063. function addNamespace(doc, prefix, urn) {
  11064. Ext.onReady(function() {
  11065. if (!doc.namespaces[prefix]) {
  11066. doc.namespaces.add(prefix, urn, '#default#VML');
  11067. }
  11068. });
  11069. }
  11070. function addNamespacesAndStylesheet(doc) {
  11071. addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml');
  11072. addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office');
  11073. // Setup default CSS. Only add one style sheet per document
  11074. if (!doc.styleSheets['ex_canvas_']) {
  11075. var ss = doc.createStyleSheet();
  11076. ss.owningElement.id = 'ex_canvas_';
  11077. ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + // default size is 300x150 in Gecko and Opera
  11078. 'text-align:left;width:300px;height:150px}';
  11079. }
  11080. }
  11081. // Add namespaces and stylesheet at startup.
  11082. addNamespacesAndStylesheet(document);
  11083. var G_vmlCanvasManager_ = {
  11084. init: function(opt_doc) {
  11085. var doc = opt_doc || document;
  11086. // Create a dummy element so that IE will allow canvas elements to be
  11087. // recognized.
  11088. doc.createElement('canvas');
  11089. doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
  11090. },
  11091. init_: function(doc) {
  11092. // find all canvas elements
  11093. var els = doc.getElementsByTagName('canvas');
  11094. for (var i = 0; i < els.length; i++) {
  11095. this.initElement(els[i]);
  11096. }
  11097. },
  11098. /*
  11099. * Public initializes a canvas element so that it can be used as canvas
  11100. * element from now on. This is called automatically before the page is
  11101. * loaded but if you are creating elements using createElement you need to
  11102. * make sure this is called on the element.
  11103. * @param {HTMLElement} el The canvas element to initialize.
  11104. * @return {HTMLElement} the element that was created.
  11105. */
  11106. initElement: function(el) {
  11107. if (!el.getContext) {
  11108. el.getContext = getContext;
  11109. // Add namespaces and stylesheet to document of the element.
  11110. addNamespacesAndStylesheet(el.ownerDocument);
  11111. // Remove fallback content. There is no way to hide text nodes so we
  11112. // just remove all childNodes. We could hide all elements and remove
  11113. // text nodes but who really cares about the fallback content.
  11114. el.innerHTML = '';
  11115. // do not use inline function because that will leak memory
  11116. el.attachEvent('onpropertychange', onPropertyChange);
  11117. el.attachEvent('onresize', onResize);
  11118. var attrs = el.attributes;
  11119. if (attrs.width && attrs.width.specified) {
  11120. // TODO: use runtimeStyle and coordsize
  11121. // el.getContext().setWidth_(attrs.width.nodeValue);
  11122. el.style.width = attrs.width.nodeValue + 'px';
  11123. } else {
  11124. el.width = el.clientWidth;
  11125. }
  11126. if (attrs.height && attrs.height.specified) {
  11127. // TODO: use runtimeStyle and coordsize
  11128. // el.getContext().setHeight_(attrs.height.nodeValue);
  11129. el.style.height = attrs.height.nodeValue + 'px';
  11130. } else {
  11131. el.height = el.clientHeight;
  11132. }
  11133. }
  11134. //el.getContext().setCoordsize_()
  11135. return el;
  11136. }
  11137. };
  11138. function onPropertyChange(e) {
  11139. var el = e.srcElement;
  11140. switch (e.propertyName) {
  11141. case 'width':
  11142. el.getContext().clearRect();
  11143. el.style.width = el.attributes.width.nodeValue + 'px';
  11144. // In IE8 this does not trigger onresize.
  11145. el.firstChild.style.width = el.clientWidth + 'px';
  11146. break;
  11147. case 'height':
  11148. el.getContext().clearRect();
  11149. el.style.height = el.attributes.height.nodeValue + 'px';
  11150. el.firstChild.style.height = el.clientHeight + 'px';
  11151. break;
  11152. }
  11153. }
  11154. function onResize(e) {
  11155. var el = e.srcElement;
  11156. if (el.firstChild) {
  11157. el.firstChild.style.width = el.clientWidth + 'px';
  11158. el.firstChild.style.height = el.clientHeight + 'px';
  11159. }
  11160. }
  11161. G_vmlCanvasManager_.init();
  11162. // precompute "00" to "FF"
  11163. var decToHex = [];
  11164. for (var i = 0; i < 16; i++) {
  11165. for (var j = 0; j < 16; j++) {
  11166. decToHex[i * 16 + j] = i.toString(16) + j.toString(16);
  11167. }
  11168. }
  11169. function createMatrixIdentity() {
  11170. return [
  11171. [
  11172. 1,
  11173. 0,
  11174. 0
  11175. ],
  11176. [
  11177. 0,
  11178. 1,
  11179. 0
  11180. ],
  11181. [
  11182. 0,
  11183. 0,
  11184. 1
  11185. ]
  11186. ];
  11187. }
  11188. function matrixMultiply(m1, m2) {
  11189. var result = createMatrixIdentity();
  11190. for (var x = 0; x < 3; x++) {
  11191. for (var y = 0; y < 3; y++) {
  11192. var sum = 0;
  11193. for (var z = 0; z < 3; z++) {
  11194. sum += m1[x][z] * m2[z][y];
  11195. }
  11196. result[x][y] = sum;
  11197. }
  11198. }
  11199. return result;
  11200. }
  11201. function copyState(o1, o2) {
  11202. o2.fillStyle = o1.fillStyle;
  11203. o2.lineCap = o1.lineCap;
  11204. o2.lineJoin = o1.lineJoin;
  11205. o2.lineDash = o1.lineDash;
  11206. o2.lineWidth = o1.lineWidth;
  11207. o2.miterLimit = o1.miterLimit;
  11208. o2.shadowBlur = o1.shadowBlur;
  11209. o2.shadowColor = o1.shadowColor;
  11210. o2.shadowOffsetX = o1.shadowOffsetX;
  11211. o2.shadowOffsetY = o1.shadowOffsetY;
  11212. o2.strokeStyle = o1.strokeStyle;
  11213. o2.globalAlpha = o1.globalAlpha;
  11214. o2.font = o1.font;
  11215. o2.textAlign = o1.textAlign;
  11216. o2.textBaseline = o1.textBaseline;
  11217. o2.arcScaleX_ = o1.arcScaleX_;
  11218. o2.arcScaleY_ = o1.arcScaleY_;
  11219. o2.lineScale_ = o1.lineScale_;
  11220. }
  11221. var colorData = {
  11222. aliceblue: '#F0F8FF',
  11223. antiquewhite: '#FAEBD7',
  11224. aquamarine: '#7FFFD4',
  11225. azure: '#F0FFFF',
  11226. beige: '#F5F5DC',
  11227. bisque: '#FFE4C4',
  11228. black: '#000000',
  11229. blanchedalmond: '#FFEBCD',
  11230. blueviolet: '#8A2BE2',
  11231. brown: '#A52A2A',
  11232. burlywood: '#DEB887',
  11233. cadetblue: '#5F9EA0',
  11234. chartreuse: '#7FFF00',
  11235. chocolate: '#D2691E',
  11236. coral: '#FF7F50',
  11237. cornflowerblue: '#6495ED',
  11238. cornsilk: '#FFF8DC',
  11239. crimson: '#DC143C',
  11240. cyan: '#00FFFF',
  11241. darkblue: '#00008B',
  11242. darkcyan: '#008B8B',
  11243. darkgoldenrod: '#B8860B',
  11244. darkgray: '#A9A9A9',
  11245. darkgreen: '#006400',
  11246. darkgrey: '#A9A9A9',
  11247. darkkhaki: '#BDB76B',
  11248. darkmagenta: '#8B008B',
  11249. darkolivegreen: '#556B2F',
  11250. darkorange: '#FF8C00',
  11251. darkorchid: '#9932CC',
  11252. darkred: '#8B0000',
  11253. darksalmon: '#E9967A',
  11254. darkseagreen: '#8FBC8F',
  11255. darkslateblue: '#483D8B',
  11256. darkslategray: '#2F4F4F',
  11257. darkslategrey: '#2F4F4F',
  11258. darkturquoise: '#00CED1',
  11259. darkviolet: '#9400D3',
  11260. deeppink: '#FF1493',
  11261. deepskyblue: '#00BFFF',
  11262. dimgray: '#696969',
  11263. dimgrey: '#696969',
  11264. dodgerblue: '#1E90FF',
  11265. firebrick: '#B22222',
  11266. floralwhite: '#FFFAF0',
  11267. forestgreen: '#228B22',
  11268. gainsboro: '#DCDCDC',
  11269. ghostwhite: '#F8F8FF',
  11270. gold: '#FFD700',
  11271. goldenrod: '#DAA520',
  11272. grey: '#808080',
  11273. greenyellow: '#ADFF2F',
  11274. honeydew: '#F0FFF0',
  11275. hotpink: '#FF69B4',
  11276. indianred: '#CD5C5C',
  11277. indigo: '#4B0082',
  11278. ivory: '#FFFFF0',
  11279. khaki: '#F0E68C',
  11280. lavender: '#E6E6FA',
  11281. lavenderblush: '#FFF0F5',
  11282. lawngreen: '#7CFC00',
  11283. lemonchiffon: '#FFFACD',
  11284. lightblue: '#ADD8E6',
  11285. lightcoral: '#F08080',
  11286. lightcyan: '#E0FFFF',
  11287. lightgoldenrodyellow: '#FAFAD2',
  11288. lightgreen: '#90EE90',
  11289. lightgrey: '#D3D3D3',
  11290. lightpink: '#FFB6C1',
  11291. lightsalmon: '#FFA07A',
  11292. lightseagreen: '#20B2AA',
  11293. lightskyblue: '#87CEFA',
  11294. lightslategray: '#778899',
  11295. lightslategrey: '#778899',
  11296. lightsteelblue: '#B0C4DE',
  11297. lightyellow: '#FFFFE0',
  11298. limegreen: '#32CD32',
  11299. linen: '#FAF0E6',
  11300. magenta: '#FF00FF',
  11301. mediumaquamarine: '#66CDAA',
  11302. mediumblue: '#0000CD',
  11303. mediumorchid: '#BA55D3',
  11304. mediumpurple: '#9370DB',
  11305. mediumseagreen: '#3CB371',
  11306. mediumslateblue: '#7B68EE',
  11307. mediumspringgreen: '#00FA9A',
  11308. mediumturquoise: '#48D1CC',
  11309. mediumvioletred: '#C71585',
  11310. midnightblue: '#191970',
  11311. mintcream: '#F5FFFA',
  11312. mistyrose: '#FFE4E1',
  11313. moccasin: '#FFE4B5',
  11314. navajowhite: '#FFDEAD',
  11315. oldlace: '#FDF5E6',
  11316. olivedrab: '#6B8E23',
  11317. orange: '#FFA500',
  11318. orangered: '#FF4500',
  11319. orchid: '#DA70D6',
  11320. palegoldenrod: '#EEE8AA',
  11321. palegreen: '#98FB98',
  11322. paleturquoise: '#AFEEEE',
  11323. palevioletred: '#DB7093',
  11324. papayawhip: '#FFEFD5',
  11325. peachpuff: '#FFDAB9',
  11326. peru: '#CD853F',
  11327. pink: '#FFC0CB',
  11328. plum: '#DDA0DD',
  11329. powderblue: '#B0E0E6',
  11330. rosybrown: '#BC8F8F',
  11331. royalblue: '#4169E1',
  11332. saddlebrown: '#8B4513',
  11333. salmon: '#FA8072',
  11334. sandybrown: '#F4A460',
  11335. seagreen: '#2E8B57',
  11336. seashell: '#FFF5EE',
  11337. sienna: '#A0522D',
  11338. skyblue: '#87CEEB',
  11339. slateblue: '#6A5ACD',
  11340. slategray: '#708090',
  11341. slategrey: '#708090',
  11342. snow: '#FFFAFA',
  11343. springgreen: '#00FF7F',
  11344. steelblue: '#4682B4',
  11345. tan: '#D2B48C',
  11346. thistle: '#D8BFD8',
  11347. tomato: '#FF6347',
  11348. turquoise: '#40E0D0',
  11349. violet: '#EE82EE',
  11350. wheat: '#F5DEB3',
  11351. whitesmoke: '#F5F5F5',
  11352. yellowgreen: '#9ACD32'
  11353. };
  11354. function getRgbHslContent(styleString) {
  11355. var start = styleString.indexOf('(', 3);
  11356. var end = styleString.indexOf(')', start + 1);
  11357. var parts = styleString.substring(start + 1, end).split(',');
  11358. // add alpha if needed
  11359. if (parts.length != 4 || styleString.charAt(3) != 'a') {
  11360. parts[3] = 1;
  11361. }
  11362. return parts;
  11363. }
  11364. function percent(s) {
  11365. return parseFloat(s) / 100;
  11366. }
  11367. function clamp(v, min, max) {
  11368. return Math.min(max, Math.max(min, v));
  11369. }
  11370. function hslToRgb(parts) {
  11371. var r, g, b, h, s, l;
  11372. h = parseFloat(parts[0]) / 360 % 360;
  11373. if (h < 0) {
  11374. h++;
  11375. }
  11376. s = clamp(percent(parts[1]), 0, 1);
  11377. l = clamp(percent(parts[2]), 0, 1);
  11378. if (s == 0) {
  11379. r = g = b = l;
  11380. } else // achromatic
  11381. {
  11382. var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  11383. var p = 2 * l - q;
  11384. r = hueToRgb(p, q, h + 1 / 3);
  11385. g = hueToRgb(p, q, h);
  11386. b = hueToRgb(p, q, h - 1 / 3);
  11387. }
  11388. return '#' + decToHex[Math.floor(r * 255)] + decToHex[Math.floor(g * 255)] + decToHex[Math.floor(b * 255)];
  11389. }
  11390. function hueToRgb(m1, m2, h) {
  11391. if (h < 0) {
  11392. h++;
  11393. }
  11394. if (h > 1) {
  11395. h--;
  11396. }
  11397. if (6 * h < 1) {
  11398. return m1 + (m2 - m1) * 6 * h;
  11399. }
  11400. else if (2 * h < 1) {
  11401. return m2;
  11402. }
  11403. else if (3 * h < 2) {
  11404. return m1 + (m2 - m1) * (2 / 3 - h) * 6;
  11405. }
  11406. else {
  11407. return m1;
  11408. }
  11409. }
  11410. var processStyleCache = {};
  11411. function processStyle(styleString) {
  11412. if (styleString in processStyleCache) {
  11413. return processStyleCache[styleString];
  11414. }
  11415. var str,
  11416. alpha = 1;
  11417. styleString = String(styleString);
  11418. if (styleString.charAt(0) == '#') {
  11419. str = styleString;
  11420. } else if (/^rgb/.test(styleString)) {
  11421. var parts = getRgbHslContent(styleString);
  11422. var str = '#',
  11423. n;
  11424. for (var i = 0; i < 3; i++) {
  11425. if (parts[i].indexOf('%') != -1) {
  11426. n = Math.floor(percent(parts[i]) * 255);
  11427. } else {
  11428. n = +parts[i];
  11429. }
  11430. str += decToHex[clamp(n, 0, 255)];
  11431. }
  11432. alpha = +parts[3];
  11433. } else if (/^hsl/.test(styleString)) {
  11434. var parts = getRgbHslContent(styleString);
  11435. str = hslToRgb(parts);
  11436. alpha = parts[3];
  11437. } else {
  11438. str = colorData[styleString] || styleString;
  11439. }
  11440. return processStyleCache[styleString] = {
  11441. color: str,
  11442. alpha: alpha
  11443. };
  11444. }
  11445. var DEFAULT_STYLE = {
  11446. style: 'normal',
  11447. variant: 'normal',
  11448. weight: 'normal',
  11449. size: 10,
  11450. family: 'sans-serif'
  11451. };
  11452. // Internal text style cache
  11453. var fontStyleCache = {};
  11454. function processFontStyle(styleString) {
  11455. if (fontStyleCache[styleString]) {
  11456. return fontStyleCache[styleString];
  11457. }
  11458. var el = document.createElement('div');
  11459. var style = el.style;
  11460. try {
  11461. style.font = styleString;
  11462. } catch (ex) {}
  11463. // Ignore failures to set to invalid font.
  11464. return fontStyleCache[styleString] = {
  11465. style: style.fontStyle || DEFAULT_STYLE.style,
  11466. variant: style.fontVariant || DEFAULT_STYLE.variant,
  11467. weight: style.fontWeight || DEFAULT_STYLE.weight,
  11468. size: style.fontSize || DEFAULT_STYLE.size,
  11469. family: style.fontFamily || DEFAULT_STYLE.family
  11470. };
  11471. }
  11472. function getComputedStyle(style, element) {
  11473. var computedStyle = {};
  11474. for (var p in style) {
  11475. computedStyle[p] = style[p];
  11476. }
  11477. // Compute the size
  11478. var canvasFontSize = parseFloat(element.currentStyle.fontSize),
  11479. fontSize = parseFloat(style.size);
  11480. if (typeof style.size == 'number') {
  11481. computedStyle.size = style.size;
  11482. } else if (style.size.indexOf('px') != -1) {
  11483. computedStyle.size = fontSize;
  11484. } else if (style.size.indexOf('em') != -1) {
  11485. computedStyle.size = canvasFontSize * fontSize;
  11486. } else if (style.size.indexOf('%') != -1) {
  11487. computedStyle.size = (canvasFontSize / 100) * fontSize;
  11488. } else if (style.size.indexOf('pt') != -1) {
  11489. computedStyle.size = fontSize / 0.75;
  11490. } else {
  11491. computedStyle.size = canvasFontSize;
  11492. }
  11493. // Different scaling between normal text and VML text. This was found using
  11494. // trial and error to get the same size as non VML text.
  11495. computedStyle.size *= 0.981;
  11496. return computedStyle;
  11497. }
  11498. function buildStyle(style) {
  11499. return style.style + ' ' + style.variant + ' ' + style.weight + ' ' + style.size + 'px ' + style.family;
  11500. }
  11501. var lineCapMap = {
  11502. 'butt': 'flat',
  11503. 'round': 'round'
  11504. };
  11505. function processLineCap(lineCap) {
  11506. return lineCapMap[lineCap] || 'square';
  11507. }
  11508. /*
  11509. * This class implements CanvasRenderingContext2D interface as described by
  11510. * the WHATWG.
  11511. * @param {HTMLElement} canvasElement The element that the 2D context should
  11512. * be associated with
  11513. * @private
  11514. */
  11515. function CanvasRenderingContext2D_(canvasElement) {
  11516. this.m_ = createMatrixIdentity();
  11517. this.mStack_ = [];
  11518. this.aStack_ = [];
  11519. this.currentPath_ = [];
  11520. // Canvas context properties
  11521. this.strokeStyle = '#000';
  11522. this.fillStyle = '#000';
  11523. this.lineWidth = 1;
  11524. this.lineJoin = 'miter';
  11525. this.lineDash = [];
  11526. this.lineCap = 'butt';
  11527. this.miterLimit = Z * 1;
  11528. this.globalAlpha = 1;
  11529. this.font = '10px sans-serif';
  11530. this.textAlign = 'left';
  11531. this.textBaseline = 'alphabetic';
  11532. this.canvas = canvasElement;
  11533. var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' + canvasElement.clientHeight + 'px;overflow:hidden;position:absolute';
  11534. var el = canvasElement.ownerDocument.createElement('div');
  11535. el.style.cssText = cssText;
  11536. canvasElement.appendChild(el);
  11537. var overlayEl = el.cloneNode(false);
  11538. // Use a non transparent background.
  11539. overlayEl.style.backgroundColor = 'red';
  11540. overlayEl.style.filter = 'alpha(opacity=0)';
  11541. canvasElement.appendChild(overlayEl);
  11542. this.element_ = el;
  11543. this.arcScaleX_ = 1;
  11544. this.arcScaleY_ = 1;
  11545. this.lineScale_ = 1;
  11546. }
  11547. var contextPrototype = CanvasRenderingContext2D_.prototype;
  11548. contextPrototype.clearRect = function() {
  11549. if (this.textMeasureEl_) {
  11550. this.textMeasureEl_.removeNode(true);
  11551. this.textMeasureEl_ = null;
  11552. }
  11553. this.element_.innerHTML = '';
  11554. };
  11555. contextPrototype.beginPath = function() {
  11556. // TODO: Branch current matrix so that save/restore has no effect
  11557. // as per safari docs.
  11558. this.currentPath_ = [];
  11559. };
  11560. contextPrototype.moveTo = function(aX, aY) {
  11561. var p = getCoords(this, aX, aY);
  11562. this.currentPath_.push({
  11563. type: 'moveTo',
  11564. x: p.x,
  11565. y: p.y
  11566. });
  11567. this.currentX_ = p.x;
  11568. this.currentY_ = p.y;
  11569. };
  11570. contextPrototype.lineTo = function(aX, aY) {
  11571. var p = getCoords(this, aX, aY);
  11572. this.currentPath_.push({
  11573. type: 'lineTo',
  11574. x: p.x,
  11575. y: p.y
  11576. });
  11577. this.currentX_ = p.x;
  11578. this.currentY_ = p.y;
  11579. };
  11580. contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) {
  11581. var p = getCoords(this, aX, aY);
  11582. var cp1 = getCoords(this, aCP1x, aCP1y);
  11583. var cp2 = getCoords(this, aCP2x, aCP2y);
  11584. bezierCurveTo(this, cp1, cp2, p);
  11585. };
  11586. // Helper function that takes the already fixed cordinates.
  11587. function bezierCurveTo(self, cp1, cp2, p) {
  11588. self.currentPath_.push({
  11589. type: 'bezierCurveTo',
  11590. cp1x: cp1.x,
  11591. cp1y: cp1.y,
  11592. cp2x: cp2.x,
  11593. cp2y: cp2.y,
  11594. x: p.x,
  11595. y: p.y
  11596. });
  11597. self.currentX_ = p.x;
  11598. self.currentY_ = p.y;
  11599. }
  11600. contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
  11601. // the following is lifted almost directly from
  11602. // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
  11603. var cp = getCoords(this, aCPx, aCPy);
  11604. var p = getCoords(this, aX, aY);
  11605. var cp1 = {
  11606. x: this.currentX_ + 2 / 3 * (cp.x - this.currentX_),
  11607. y: this.currentY_ + 2 / 3 * (cp.y - this.currentY_)
  11608. };
  11609. var cp2 = {
  11610. x: cp1.x + (p.x - this.currentX_) / 3,
  11611. y: cp1.y + (p.y - this.currentY_) / 3
  11612. };
  11613. bezierCurveTo(this, cp1, cp2, p);
  11614. };
  11615. contextPrototype.arc = function(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) {
  11616. aRadius *= Z;
  11617. var arcType = aClockwise ? 'at' : 'wa';
  11618. var xStart = aX + mc(aStartAngle) * aRadius - Z2;
  11619. var yStart = aY + ms(aStartAngle) * aRadius - Z2;
  11620. var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
  11621. var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
  11622. // IE won't render arches drawn counter clockwise if xStart == xEnd.
  11623. if (xStart == xEnd && !aClockwise) {
  11624. xStart += 0.125;
  11625. }
  11626. // Offset xStart by 1/80 of a pixel. Use something
  11627. // that can be represented in binary
  11628. var p = getCoords(this, aX, aY);
  11629. var pStart = getCoords(this, xStart, yStart);
  11630. var pEnd = getCoords(this, xEnd, yEnd);
  11631. this.currentPath_.push({
  11632. type: arcType,
  11633. x: p.x,
  11634. y: p.y,
  11635. radius: aRadius,
  11636. xStart: pStart.x,
  11637. yStart: pStart.y,
  11638. xEnd: pEnd.x,
  11639. yEnd: pEnd.y
  11640. });
  11641. };
  11642. contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
  11643. this.moveTo(aX, aY);
  11644. this.lineTo(aX + aWidth, aY);
  11645. this.lineTo(aX + aWidth, aY + aHeight);
  11646. this.lineTo(aX, aY + aHeight);
  11647. this.closePath();
  11648. };
  11649. contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
  11650. var oldPath = this.currentPath_;
  11651. this.beginPath();
  11652. this.moveTo(aX, aY);
  11653. this.lineTo(aX + aWidth, aY);
  11654. this.lineTo(aX + aWidth, aY + aHeight);
  11655. this.lineTo(aX, aY + aHeight);
  11656. this.closePath();
  11657. this.stroke();
  11658. this.currentPath_ = oldPath;
  11659. };
  11660. contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
  11661. var oldPath = this.currentPath_;
  11662. this.beginPath();
  11663. this.moveTo(aX, aY);
  11664. this.lineTo(aX + aWidth, aY);
  11665. this.lineTo(aX + aWidth, aY + aHeight);
  11666. this.lineTo(aX, aY + aHeight);
  11667. this.closePath();
  11668. this.fill();
  11669. this.currentPath_ = oldPath;
  11670. };
  11671. contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
  11672. var gradient = new CanvasGradient_('gradient');
  11673. gradient.x0_ = aX0;
  11674. gradient.y0_ = aY0;
  11675. gradient.x1_ = aX1;
  11676. gradient.y1_ = aY1;
  11677. return gradient;
  11678. };
  11679. contextPrototype.createRadialGradient = function(aX0, aY0, aR0, aX1, aY1, aR1) {
  11680. var gradient = new CanvasGradient_('gradientradial');
  11681. gradient.x0_ = aX0;
  11682. gradient.y0_ = aY0;
  11683. gradient.r0_ = aR0;
  11684. gradient.x1_ = aX1;
  11685. gradient.y1_ = aY1;
  11686. gradient.r1_ = aR1;
  11687. return gradient;
  11688. };
  11689. contextPrototype.drawImage = function(image, var_args) {
  11690. var dx, dy, dw, dh, sx, sy, sw, sh;
  11691. // to find the original width we overide the width and height
  11692. var oldRuntimeWidth = image.runtimeStyle.width;
  11693. var oldRuntimeHeight = image.runtimeStyle.height;
  11694. image.runtimeStyle.width = 'auto';
  11695. image.runtimeStyle.height = 'auto';
  11696. // get the original size
  11697. var w = image.width;
  11698. var h = image.height;
  11699. // and remove overides
  11700. image.runtimeStyle.width = oldRuntimeWidth;
  11701. image.runtimeStyle.height = oldRuntimeHeight;
  11702. if (arguments.length == 3) {
  11703. dx = arguments[1];
  11704. dy = arguments[2];
  11705. sx = sy = 0;
  11706. sw = dw = w;
  11707. sh = dh = h;
  11708. } else if (arguments.length == 5) {
  11709. dx = arguments[1];
  11710. dy = arguments[2];
  11711. dw = arguments[3];
  11712. dh = arguments[4];
  11713. sx = sy = 0;
  11714. sw = w;
  11715. sh = h;
  11716. } else if (arguments.length == 9) {
  11717. sx = arguments[1];
  11718. sy = arguments[2];
  11719. sw = arguments[3];
  11720. sh = arguments[4];
  11721. dx = arguments[5];
  11722. dy = arguments[6];
  11723. dw = arguments[7];
  11724. dh = arguments[8];
  11725. } else {
  11726. throw Error('Invalid number of arguments');
  11727. }
  11728. var d = getCoords(this, dx, dy);
  11729. var vmlStr = [];
  11730. var W = 10;
  11731. var H = 10;
  11732. var m = this.m_;
  11733. 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), ';');
  11734. 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>');
  11735. this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
  11736. };
  11737. contextPrototype.setLineDash = function(lineDash) {
  11738. if (lineDash.length === 1) {
  11739. lineDash = lineDash.slice();
  11740. lineDash[1] = lineDash[0];
  11741. }
  11742. this.lineDash = lineDash;
  11743. };
  11744. contextPrototype.getLineDash = function() {
  11745. return this.lineDash;
  11746. };
  11747. contextPrototype.stroke = function(aFill) {
  11748. var lineStr = [];
  11749. var W = 10;
  11750. var H = 10;
  11751. 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="');
  11752. var min = {
  11753. x: null,
  11754. y: null
  11755. };
  11756. var max = {
  11757. x: null,
  11758. y: null
  11759. };
  11760. for (var i = 0; i < this.currentPath_.length; i++) {
  11761. var p = this.currentPath_[i];
  11762. var c;
  11763. switch (p.type) {
  11764. case 'moveTo':
  11765. c = p;
  11766. lineStr.push(' m ', mr(p.x), ',', mr(p.y));
  11767. break;
  11768. case 'lineTo':
  11769. lineStr.push(' l ', mr(p.x), ',', mr(p.y));
  11770. break;
  11771. case 'close':
  11772. lineStr.push(' x ');
  11773. p = null;
  11774. break;
  11775. case 'bezierCurveTo':
  11776. lineStr.push(' c ', mr(p.cp1x), ',', mr(p.cp1y), ',', mr(p.cp2x), ',', mr(p.cp2y), ',', mr(p.x), ',', mr(p.y));
  11777. break;
  11778. case 'at':
  11779. case 'wa':
  11780. 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));
  11781. break;
  11782. }
  11783. // TODO: Following is broken for curves due to
  11784. // move to proper paths.
  11785. // Figure out dimensions so we can do gradient fills
  11786. // properly
  11787. if (p) {
  11788. if (min.x == null || p.x < min.x) {
  11789. min.x = p.x;
  11790. }
  11791. if (max.x == null || p.x > max.x) {
  11792. max.x = p.x;
  11793. }
  11794. if (min.y == null || p.y < min.y) {
  11795. min.y = p.y;
  11796. }
  11797. if (max.y == null || p.y > max.y) {
  11798. max.y = p.y;
  11799. }
  11800. }
  11801. }
  11802. lineStr.push(' ">');
  11803. if (!aFill) {
  11804. appendStroke(this, lineStr);
  11805. } else {
  11806. appendFill(this, lineStr, min, max);
  11807. }
  11808. lineStr.push('</g_vml_:shape>');
  11809. this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
  11810. };
  11811. function appendStroke(ctx, lineStr) {
  11812. var a = processStyle(ctx.strokeStyle);
  11813. var color = a.color;
  11814. var opacity = a.alpha * ctx.globalAlpha;
  11815. var lineWidth = ctx.lineScale_ * ctx.lineWidth;
  11816. // VML cannot correctly render a line if the width is less than 1px.
  11817. // In that case, we dilute the color to make the line look thinner.
  11818. if (lineWidth < 1) {
  11819. opacity *= lineWidth;
  11820. }
  11821. 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, '" />');
  11822. }
  11823. function appendFill(ctx, lineStr, min, max) {
  11824. var fillStyle = ctx.fillStyle;
  11825. var arcScaleX = ctx.arcScaleX_;
  11826. var arcScaleY = ctx.arcScaleY_;
  11827. var width = max.x - min.x;
  11828. var height = max.y - min.y;
  11829. if (fillStyle instanceof CanvasGradient_) {
  11830. // TODO: Gradients transformed with the transformation matrix.
  11831. var angle = 0;
  11832. var focus = {
  11833. x: 0,
  11834. y: 0
  11835. };
  11836. // additional offset
  11837. var shift = 0;
  11838. // scale factor for offset
  11839. var expansion = 1;
  11840. if (fillStyle.type_ == 'gradient') {
  11841. var x0 = fillStyle.x0_ / arcScaleX;
  11842. var y0 = fillStyle.y0_ / arcScaleY;
  11843. var x1 = fillStyle.x1_ / arcScaleX;
  11844. var y1 = fillStyle.y1_ / arcScaleY;
  11845. var p0 = getCoords(ctx, x0, y0);
  11846. var p1 = getCoords(ctx, x1, y1);
  11847. var dx = p1.x - p0.x;
  11848. var dy = p1.y - p0.y;
  11849. angle = Math.atan2(dx, dy) * 180 / Math.PI;
  11850. // The angle should be a non-negative number.
  11851. if (angle < 0) {
  11852. angle += 360;
  11853. }
  11854. // Very small angles produce an unexpected result because they are
  11855. // converted to a scientific notation string.
  11856. if (angle < 1.0E-6) {
  11857. angle = 0;
  11858. }
  11859. } else {
  11860. var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_);
  11861. focus = {
  11862. x: (p0.x - min.x) / width,
  11863. y: (p0.y - min.y) / height
  11864. };
  11865. width /= arcScaleX * Z;
  11866. height /= arcScaleY * Z;
  11867. var dimension = m.max(width, height);
  11868. shift = 2 * fillStyle.r0_ / dimension;
  11869. expansion = 2 * fillStyle.r1_ / dimension - shift;
  11870. }
  11871. // We need to sort the color stops in ascending order by offset,
  11872. // otherwise IE won't interpret it correctly.
  11873. var stops = fillStyle.colors_;
  11874. stops.sort(function(cs1, cs2) {
  11875. return cs1.offset - cs2.offset;
  11876. });
  11877. var length = stops.length;
  11878. var color1 = stops[0].color;
  11879. var color2 = stops[length - 1].color;
  11880. var opacity1 = stops[0].alpha * ctx.globalAlpha;
  11881. var opacity2 = stops[length - 1].alpha * ctx.globalAlpha;
  11882. var colors = [];
  11883. for (var i = 0; i < length; i++) {
  11884. var stop = stops[i];
  11885. colors.push(stop.offset * expansion + shift + ' ' + stop.color);
  11886. }
  11887. // When colors attribute is used, the meanings of opacity and o:opacity2
  11888. // are reversed.
  11889. 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, '" />');
  11890. } else if (fillStyle instanceof CanvasPattern_) {
  11891. if (width && height) {
  11892. var deltaLeft = -min.x;
  11893. var deltaTop = -min.y;
  11894. 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.
  11895. //' size="', w, 'px ', h, 'px"',
  11896. ' src="', fillStyle.src_, '" />');
  11897. }
  11898. } else {
  11899. var a = processStyle(ctx.fillStyle);
  11900. var color = a.color;
  11901. var opacity = a.alpha * ctx.globalAlpha;
  11902. lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />');
  11903. }
  11904. }
  11905. contextPrototype.fill = function() {
  11906. // Calling `$stroke` here because otherwise we'd call not the native ctx.stroke,
  11907. // but our override from Ext.draw.engine.Canvas.statics.contextOverrides.
  11908. this.$stroke(true);
  11909. };
  11910. contextPrototype.closePath = function() {
  11911. this.currentPath_.push({
  11912. type: 'close'
  11913. });
  11914. };
  11915. function getCoords(ctx, aX, aY) {
  11916. var m = ctx.m_;
  11917. return {
  11918. x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
  11919. y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
  11920. };
  11921. }
  11922. contextPrototype.save = function() {
  11923. var o = {};
  11924. copyState(this, o);
  11925. this.aStack_.push(o);
  11926. this.mStack_.push(this.m_);
  11927. };
  11928. contextPrototype.restore = function() {
  11929. if (this.aStack_.length) {
  11930. copyState(this.aStack_.pop(), this);
  11931. this.m_ = this.mStack_.pop();
  11932. }
  11933. };
  11934. function matrixIsFinite(m) {
  11935. 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]);
  11936. }
  11937. function setM(ctx, m, updateLineScale) {
  11938. if (!matrixIsFinite(m)) {
  11939. return;
  11940. }
  11941. ctx.m_ = m;
  11942. if (updateLineScale) {
  11943. // Get the line scale.
  11944. // Determinant of this.m_ means how much the area is enlarged by the
  11945. // transformation. So its square root can be used as a scale factor
  11946. // for width.
  11947. var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
  11948. ctx.lineScale_ = sqrt(abs(det));
  11949. }
  11950. }
  11951. contextPrototype.translate = function(aX, aY) {
  11952. var m1 = [
  11953. [
  11954. 1,
  11955. 0,
  11956. 0
  11957. ],
  11958. [
  11959. 0,
  11960. 1,
  11961. 0
  11962. ],
  11963. [
  11964. aX,
  11965. aY,
  11966. 1
  11967. ]
  11968. ];
  11969. setM(this, matrixMultiply(m1, this.m_), false);
  11970. };
  11971. contextPrototype.rotate = function(aRot) {
  11972. var c = mc(aRot);
  11973. var s = ms(aRot);
  11974. var m1 = [
  11975. [
  11976. c,
  11977. s,
  11978. 0
  11979. ],
  11980. [
  11981. -s,
  11982. c,
  11983. 0
  11984. ],
  11985. [
  11986. 0,
  11987. 0,
  11988. 1
  11989. ]
  11990. ];
  11991. setM(this, matrixMultiply(m1, this.m_), false);
  11992. };
  11993. contextPrototype.scale = function(aX, aY) {
  11994. this.arcScaleX_ *= aX;
  11995. this.arcScaleY_ *= aY;
  11996. var m1 = [
  11997. [
  11998. aX,
  11999. 0,
  12000. 0
  12001. ],
  12002. [
  12003. 0,
  12004. aY,
  12005. 0
  12006. ],
  12007. [
  12008. 0,
  12009. 0,
  12010. 1
  12011. ]
  12012. ];
  12013. setM(this, matrixMultiply(m1, this.m_), true);
  12014. };
  12015. contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
  12016. var m1 = [
  12017. [
  12018. m11,
  12019. m12,
  12020. 0
  12021. ],
  12022. [
  12023. m21,
  12024. m22,
  12025. 0
  12026. ],
  12027. [
  12028. dx,
  12029. dy,
  12030. 1
  12031. ]
  12032. ];
  12033. setM(this, matrixMultiply(m1, this.m_), true);
  12034. };
  12035. contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
  12036. var m = [
  12037. [
  12038. m11,
  12039. m12,
  12040. 0
  12041. ],
  12042. [
  12043. m21,
  12044. m22,
  12045. 0
  12046. ],
  12047. [
  12048. dx,
  12049. dy,
  12050. 1
  12051. ]
  12052. ];
  12053. setM(this, m, true);
  12054. };
  12055. /*
  12056. * The text drawing function.
  12057. * The maxWidth argument isn't taken in account, since no browser supports
  12058. * it yet.
  12059. */
  12060. contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
  12061. var m = this.m_,
  12062. delta = 1000,
  12063. left = 0,
  12064. right = delta,
  12065. offset = {
  12066. x: 0,
  12067. y: 0
  12068. },
  12069. lineStr = [];
  12070. var fontStyle = getComputedStyle(processFontStyle(this.font), this.element_);
  12071. var fontStyleString = buildStyle(fontStyle);
  12072. var elementStyle = this.element_.currentStyle;
  12073. var textAlign = this.textAlign.toLowerCase();
  12074. switch (textAlign) {
  12075. case 'left':
  12076. case 'center':
  12077. case 'right':
  12078. break;
  12079. case 'end':
  12080. textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
  12081. break;
  12082. case 'start':
  12083. textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
  12084. break;
  12085. default:
  12086. textAlign = 'left';
  12087. }
  12088. // 1.75 is an arbitrary number, as there is no info about the text baseline
  12089. switch (this.textBaseline) {
  12090. case 'hanging':
  12091. case 'top':
  12092. offset.y = fontStyle.size / 1.75;
  12093. break;
  12094. case 'middle':
  12095. break;
  12096. default:
  12097. case null:
  12098. case 'alphabetic':
  12099. case 'ideographic':
  12100. case 'bottom':
  12101. offset.y = -fontStyle.size / 3;
  12102. break;
  12103. }
  12104. switch (textAlign) {
  12105. case 'right':
  12106. left = delta;
  12107. right = 0.05;
  12108. break;
  12109. case 'center':
  12110. left = right = delta / 2;
  12111. break;
  12112. }
  12113. var d = getCoords(this, x + offset.x, y + offset.y);
  12114. 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;">');
  12115. if (stroke) {
  12116. appendStroke(this, lineStr);
  12117. } else {
  12118. // TODO: Fix the min and max params.
  12119. appendFill(this, lineStr, {
  12120. x: -left,
  12121. y: 0
  12122. }, {
  12123. x: right,
  12124. y: fontStyle.size
  12125. });
  12126. }
  12127. var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' + m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0';
  12128. var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z);
  12129. 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>');
  12130. this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
  12131. };
  12132. contextPrototype.fillText = function(text, x, y, maxWidth) {
  12133. this.drawText_(text, x, y, maxWidth, false);
  12134. };
  12135. contextPrototype.strokeText = function(text, x, y, maxWidth) {
  12136. this.drawText_(text, x, y, maxWidth, true);
  12137. };
  12138. contextPrototype.measureText = function(text) {
  12139. if (!this.textMeasureEl_) {
  12140. var s = '<span style="position:absolute;' + 'top:-20000px;left:0;padding:0;margin:0;border:none;' + 'white-space:pre;"></span>';
  12141. this.element_.insertAdjacentHTML('beforeEnd', s);
  12142. this.textMeasureEl_ = this.element_.lastChild;
  12143. }
  12144. var doc = this.element_.ownerDocument;
  12145. this.textMeasureEl_.innerHTML = '';
  12146. this.textMeasureEl_.style.font = this.font;
  12147. // Don't use innerHTML or innerText because they allow markup/whitespace.
  12148. this.textMeasureEl_.appendChild(doc.createTextNode(text));
  12149. return {
  12150. width: this.textMeasureEl_.offsetWidth
  12151. };
  12152. };
  12153. /* STUBS */
  12154. contextPrototype.clip = function() {};
  12155. // TODO: Implement
  12156. contextPrototype.arcTo = function() {};
  12157. // TODO: Implement
  12158. contextPrototype.createPattern = function(image, repetition) {
  12159. return new CanvasPattern_(image, repetition);
  12160. };
  12161. // Gradient / Pattern Stubs
  12162. function CanvasGradient_(aType) {
  12163. this.type_ = aType;
  12164. this.x0_ = 0;
  12165. this.y0_ = 0;
  12166. this.r0_ = 0;
  12167. this.x1_ = 0;
  12168. this.y1_ = 0;
  12169. this.r1_ = 0;
  12170. this.colors_ = [];
  12171. }
  12172. CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
  12173. aColor = processStyle(aColor);
  12174. this.colors_.push({
  12175. offset: aOffset,
  12176. color: aColor.color,
  12177. alpha: aColor.alpha
  12178. });
  12179. };
  12180. function CanvasPattern_(image, repetition) {
  12181. assertImageIsValid(image);
  12182. switch (repetition) {
  12183. case 'repeat':
  12184. case null:
  12185. case '':
  12186. this.repetition_ = 'repeat';
  12187. break;
  12188. case 'repeat-x':
  12189. case 'repeat-y':
  12190. case 'no-repeat':
  12191. this.repetition_ = repetition;
  12192. break;
  12193. default:
  12194. throwException('SYNTAX_ERR');
  12195. }
  12196. this.src_ = image.src;
  12197. this.width_ = image.width;
  12198. this.height_ = image.height;
  12199. }
  12200. function throwException(s) {
  12201. throw new DOMException_(s);
  12202. }
  12203. function assertImageIsValid(img) {
  12204. if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
  12205. throwException('TYPE_MISMATCH_ERR');
  12206. }
  12207. if (img.readyState != 'complete') {
  12208. throwException('INVALID_STATE_ERR');
  12209. }
  12210. }
  12211. function DOMException_(s) {
  12212. this.code = this[s];
  12213. this.message = s + ': DOM Exception ' + this.code;
  12214. }
  12215. var p = DOMException_.prototype = new Error();
  12216. p.INDEX_SIZE_ERR = 1;
  12217. p.DOMSTRING_SIZE_ERR = 2;
  12218. p.HIERARCHY_REQUEST_ERR = 3;
  12219. p.WRONG_DOCUMENT_ERR = 4;
  12220. p.INVALID_CHARACTER_ERR = 5;
  12221. p.NO_DATA_ALLOWED_ERR = 6;
  12222. p.NO_MODIFICATION_ALLOWED_ERR = 7;
  12223. p.NOT_FOUND_ERR = 8;
  12224. p.NOT_SUPPORTED_ERR = 9;
  12225. p.INUSE_ATTRIBUTE_ERR = 10;
  12226. p.INVALID_STATE_ERR = 11;
  12227. p.SYNTAX_ERR = 12;
  12228. p.INVALID_MODIFICATION_ERR = 13;
  12229. p.NAMESPACE_ERR = 14;
  12230. p.INVALID_ACCESS_ERR = 15;
  12231. p.VALIDATION_ERR = 16;
  12232. p.TYPE_MISMATCH_ERR = 17;
  12233. // set up externs
  12234. G_vmlCanvasManager = G_vmlCanvasManager_;
  12235. CanvasRenderingContext2D = CanvasRenderingContext2D_;
  12236. CanvasGradient = CanvasGradient_;
  12237. CanvasPattern = CanvasPattern_;
  12238. DOMException = DOMException_;
  12239. })();
  12240. }
  12241. // if
  12242. /**
  12243. * Provides specific methods to draw with 2D Canvas element.
  12244. */
  12245. Ext.define('Ext.draw.engine.Canvas', {
  12246. extend: 'Ext.draw.Surface',
  12247. isCanvas: true,
  12248. requires: [
  12249. //<feature legacyBrowser>
  12250. 'Ext.draw.engine.excanvas',
  12251. //</feature>
  12252. 'Ext.draw.Animator',
  12253. 'Ext.draw.Color'
  12254. ],
  12255. config: {
  12256. /**
  12257. * @cfg {Boolean} highPrecision
  12258. * True to have the Canvas use JavaScript Number instead of single precision floating point for transforms.
  12259. *
  12260. * For example, when using data with big numbers to plot line series, the transformation
  12261. * matrix of the canvas will have big elements. Due to the implementation of the SVGMatrix,
  12262. * the elements are represented by 32-bits floats, which will work incorrectly.
  12263. * To compensate for that, we enable the canvas context to perform all the transformations
  12264. * in JavaScript.
  12265. *
  12266. * Do not use this if you are not encountering 32-bit floating point errors problem,
  12267. * since this will result in a performance penalty.
  12268. */
  12269. highPrecision: false
  12270. },
  12271. statics: {
  12272. contextOverrides: {
  12273. /**
  12274. * @ignore
  12275. */
  12276. setGradientBBox: function(bbox) {
  12277. this.bbox = bbox;
  12278. },
  12279. /**
  12280. * Fills the subpaths of the current default path or the given path with the current fill style.
  12281. * @ignore
  12282. */
  12283. fill: function() {
  12284. var fillStyle = this.fillStyle,
  12285. fillGradient = this.fillGradient,
  12286. fillOpacity = this.fillOpacity,
  12287. alpha = this.globalAlpha,
  12288. bbox = this.bbox;
  12289. if (fillStyle !== Ext.util.Color.RGBA_NONE && fillOpacity !== 0) {
  12290. if (fillGradient && bbox) {
  12291. this.fillStyle = fillGradient.generateGradient(this, bbox);
  12292. }
  12293. if (fillOpacity !== 1) {
  12294. this.globalAlpha = alpha * fillOpacity;
  12295. }
  12296. this.$fill();
  12297. if (fillOpacity !== 1) {
  12298. this.globalAlpha = alpha;
  12299. }
  12300. if (fillGradient && bbox) {
  12301. this.fillStyle = fillStyle;
  12302. }
  12303. }
  12304. },
  12305. /**
  12306. * Strokes the subpaths of the current default path or the given path with the current stroke style.
  12307. * @ignore
  12308. */
  12309. stroke: function() {
  12310. var strokeStyle = this.strokeStyle,
  12311. strokeGradient = this.strokeGradient,
  12312. strokeOpacity = this.strokeOpacity,
  12313. alpha = this.globalAlpha,
  12314. bbox = this.bbox;
  12315. if (strokeStyle !== Ext.util.Color.RGBA_NONE && strokeOpacity !== 0) {
  12316. if (strokeGradient && bbox) {
  12317. this.strokeStyle = strokeGradient.generateGradient(this, bbox);
  12318. }
  12319. if (strokeOpacity !== 1) {
  12320. this.globalAlpha = alpha * strokeOpacity;
  12321. }
  12322. this.$stroke();
  12323. if (strokeOpacity !== 1) {
  12324. this.globalAlpha = alpha;
  12325. }
  12326. if (strokeGradient && bbox) {
  12327. this.strokeStyle = strokeStyle;
  12328. }
  12329. }
  12330. },
  12331. /**
  12332. * @ignore
  12333. */
  12334. fillStroke: function(attr, transformFillStroke) {
  12335. var ctx = this,
  12336. fillStyle = this.fillStyle,
  12337. fillOpacity = this.fillOpacity,
  12338. strokeStyle = this.strokeStyle,
  12339. strokeOpacity = this.strokeOpacity,
  12340. shadowColor = ctx.shadowColor,
  12341. shadowBlur = ctx.shadowBlur,
  12342. none = Ext.util.Color.RGBA_NONE;
  12343. if (transformFillStroke === undefined) {
  12344. transformFillStroke = attr.transformFillStroke;
  12345. }
  12346. if (!transformFillStroke) {
  12347. attr.inverseMatrix.toContext(ctx);
  12348. }
  12349. if (fillStyle !== none && fillOpacity !== 0) {
  12350. ctx.fill();
  12351. ctx.shadowColor = none;
  12352. ctx.shadowBlur = 0;
  12353. }
  12354. if (strokeStyle !== none && strokeOpacity !== 0) {
  12355. ctx.stroke();
  12356. }
  12357. ctx.shadowColor = shadowColor;
  12358. ctx.shadowBlur = shadowBlur;
  12359. },
  12360. /**
  12361. * 2D Canvas context in IE (up to IE10, inclusive) doesn't support
  12362. * the setLineDash method and the lineDashOffset property.
  12363. * @param dashList An even number of non-negative numbers specifying a dash list.
  12364. */
  12365. setLineDash: function(dashList) {
  12366. if (this.$setLineDash) {
  12367. this.$setLineDash(dashList);
  12368. }
  12369. },
  12370. getLineDash: function() {
  12371. if (this.$getLineDash) {
  12372. return this.$getLineDash();
  12373. }
  12374. },
  12375. /**
  12376. * Adds points to the subpath such that the arc described by the circumference of the
  12377. * ellipse described by the arguments, starting at the given start angle and ending at
  12378. * the given end angle, going in the given direction (defaulting to clockwise), is added
  12379. * to the path, connected to the previous point by a straight line.
  12380. * @ignore
  12381. */
  12382. ellipse: function(cx, cy, rx, ry, rotation, start, end, anticlockwise) {
  12383. var cos = Math.cos(rotation),
  12384. sin = Math.sin(rotation);
  12385. this.transform(cos * rx, sin * rx, -sin * ry, cos * ry, cx, cy);
  12386. this.arc(0, 0, 1, start, end, anticlockwise);
  12387. this.transform(cos / rx, -sin / ry, sin / rx, cos / ry, -(cos * cx + sin * cy) / rx, (sin * cx - cos * cy) / ry);
  12388. },
  12389. /**
  12390. * Uses the given path commands to begin a new path on the canvas.
  12391. * @ignore
  12392. */
  12393. appendPath: function(path) {
  12394. var me = this,
  12395. i = 0,
  12396. j = 0,
  12397. commands = path.commands,
  12398. params = path.params,
  12399. ln = commands.length;
  12400. me.beginPath();
  12401. for (; i < ln; i++) {
  12402. switch (commands[i]) {
  12403. case 'M':
  12404. me.moveTo(params[j], params[j + 1]);
  12405. j += 2;
  12406. break;
  12407. case 'L':
  12408. me.lineTo(params[j], params[j + 1]);
  12409. j += 2;
  12410. break;
  12411. case 'C':
  12412. me.bezierCurveTo(params[j], params[j + 1], params[j + 2], params[j + 3], params[j + 4], params[j + 5]);
  12413. j += 6;
  12414. break;
  12415. case 'Z':
  12416. me.closePath();
  12417. break;
  12418. }
  12419. }
  12420. },
  12421. save: function() {
  12422. var toSave = this.toSave,
  12423. ln = toSave.length,
  12424. obj = ln && {},
  12425. // Don't allocate memory if we don't have to.
  12426. i = 0,
  12427. key;
  12428. for (; i < ln; i++) {
  12429. key = toSave[i];
  12430. if (key in this) {
  12431. obj[key] = this[key];
  12432. }
  12433. }
  12434. this.state.push(obj);
  12435. this.$save();
  12436. },
  12437. restore: function() {
  12438. var obj = this.state.pop(),
  12439. key;
  12440. if (obj) {
  12441. for (key in obj) {
  12442. this[key] = obj[key];
  12443. }
  12444. }
  12445. this.$restore();
  12446. }
  12447. }
  12448. },
  12449. splitThreshold: 3000,
  12450. /**
  12451. * @private
  12452. * Properties to be saved/restored in the `save` and `restore` methods.
  12453. */
  12454. toSave: [
  12455. 'fillGradient',
  12456. 'strokeGradient'
  12457. ],
  12458. /**
  12459. * @property element
  12460. * @inheritdoc
  12461. */
  12462. element: {
  12463. reference: 'element',
  12464. children: [
  12465. {
  12466. reference: 'bodyElement',
  12467. style: {
  12468. width: '100%',
  12469. height: '100%',
  12470. position: 'relative'
  12471. }
  12472. }
  12473. ]
  12474. },
  12475. /**
  12476. * @private
  12477. *
  12478. * Creates the canvas element.
  12479. */
  12480. createCanvas: function() {
  12481. var canvas = Ext.Element.create({
  12482. tag: 'canvas',
  12483. cls: Ext.baseCSSPrefix + 'surface-canvas'
  12484. });
  12485. // Emulate Canvas in IE8 with VML.
  12486. if (window['G_vmlCanvasManager']) {
  12487. G_vmlCanvasManager.initElement(canvas.dom);
  12488. this.isVML = true;
  12489. }
  12490. var overrides = Ext.draw.engine.Canvas.contextOverrides,
  12491. ctx = canvas.dom.getContext('2d'),
  12492. name;
  12493. if (ctx.ellipse) {
  12494. delete overrides.ellipse;
  12495. }
  12496. ctx.state = [];
  12497. ctx.toSave = this.toSave;
  12498. // Saving references to the native Canvas context methods that we'll be overriding.
  12499. for (name in overrides) {
  12500. ctx['$' + name] = ctx[name];
  12501. }
  12502. Ext.apply(ctx, overrides);
  12503. if (this.getHighPrecision()) {
  12504. this.enablePrecisionCompensation(ctx);
  12505. } else {
  12506. this.disablePrecisionCompensation(ctx);
  12507. }
  12508. this.bodyElement.appendChild(canvas);
  12509. this.canvases.push(canvas);
  12510. this.contexts.push(ctx);
  12511. },
  12512. updateHighPrecision: function(highPrecision) {
  12513. var contexts = this.contexts,
  12514. ln = contexts.length,
  12515. i, context;
  12516. for (i = 0; i < ln; i++) {
  12517. context = contexts[i];
  12518. if (highPrecision) {
  12519. this.enablePrecisionCompensation(context);
  12520. } else {
  12521. this.disablePrecisionCompensation(context);
  12522. }
  12523. }
  12524. },
  12525. precisionNames: [
  12526. 'rect',
  12527. 'fillRect',
  12528. 'strokeRect',
  12529. 'clearRect',
  12530. 'moveTo',
  12531. 'lineTo',
  12532. 'arc',
  12533. 'arcTo',
  12534. 'save',
  12535. 'restore',
  12536. 'updatePrecisionCompensate',
  12537. 'setTransform',
  12538. 'transform',
  12539. 'scale',
  12540. 'translate',
  12541. 'rotate',
  12542. 'quadraticCurveTo',
  12543. 'bezierCurveTo',
  12544. 'createLinearGradient',
  12545. 'createRadialGradient',
  12546. 'fillText',
  12547. 'strokeText',
  12548. 'drawImage'
  12549. ],
  12550. /**
  12551. * @private
  12552. * Clears canvas of compensation for canvas' use of single precision floating point.
  12553. * @param {CanvasRenderingContext2D} ctx The canvas context.
  12554. */
  12555. disablePrecisionCompensation: function(ctx) {
  12556. var regularOverrides = Ext.draw.engine.Canvas.contextOverrides,
  12557. precisionOverrides = this.precisionNames,
  12558. ln = precisionOverrides.length,
  12559. i, name;
  12560. for (i = 0; i < ln; i++) {
  12561. name = precisionOverrides[i];
  12562. if (!(name in regularOverrides)) {
  12563. delete ctx[name];
  12564. }
  12565. }
  12566. this.setDirty(true);
  12567. },
  12568. /**
  12569. * @private
  12570. * Compensate for canvas' use of single precision floating point.
  12571. * @param {CanvasRenderingContext2D} ctx The canvas context.
  12572. */
  12573. enablePrecisionCompensation: function(ctx) {
  12574. var surface = this,
  12575. xx = 1,
  12576. yy = 1,
  12577. dx = 0,
  12578. dy = 0,
  12579. matrix = new Ext.draw.Matrix(),
  12580. transStack = [],
  12581. comp = {},
  12582. regularOverrides = Ext.draw.engine.Canvas.contextOverrides,
  12583. originalCtx = ctx.constructor.prototype;
  12584. /**
  12585. * @cfg {Object} precisionOverrides
  12586. * @ignore
  12587. */
  12588. var precisionOverrides = {
  12589. toSave: surface.toSave,
  12590. /**
  12591. * Adds a new closed subpath to the path, representing the given rectangle.
  12592. * @return {*}
  12593. * @ignore
  12594. */
  12595. rect: function(x, y, w, h) {
  12596. return originalCtx.rect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
  12597. },
  12598. /**
  12599. * Paints the given rectangle onto the canvas, using the current fill style.
  12600. * @ignore
  12601. */
  12602. fillRect: function(x, y, w, h) {
  12603. this.updatePrecisionCompensateRect();
  12604. originalCtx.fillRect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
  12605. this.updatePrecisionCompensate();
  12606. },
  12607. /**
  12608. * Paints the box that outlines the given rectangle onto the canvas, using the current stroke style.
  12609. * @ignore
  12610. */
  12611. strokeRect: function(x, y, w, h) {
  12612. this.updatePrecisionCompensateRect();
  12613. originalCtx.strokeRect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
  12614. this.updatePrecisionCompensate();
  12615. },
  12616. /**
  12617. * Clears all pixels on the canvas in the given rectangle to transparent black.
  12618. * @ignore
  12619. */
  12620. clearRect: function(x, y, w, h) {
  12621. return originalCtx.clearRect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
  12622. },
  12623. /**
  12624. * Creates a new subpath with the given point.
  12625. * @ignore
  12626. */
  12627. moveTo: function(x, y) {
  12628. return originalCtx.moveTo.call(this, x * xx + dx, y * yy + dy);
  12629. },
  12630. /**
  12631. * Adds the given point to the current subpath, connected to the previous one by a straight line.
  12632. * @ignore
  12633. */
  12634. lineTo: function(x, y) {
  12635. return originalCtx.lineTo.call(this, x * xx + dx, y * yy + dy);
  12636. },
  12637. /**
  12638. * Adds points to the subpath such that the arc described by the circumference of the
  12639. * circle described by the arguments, starting at the given start angle and ending at
  12640. * the given end angle, going in the given direction (defaulting to clockwise), is added
  12641. * to the path, connected to the previous point by a straight line.
  12642. * @ignore
  12643. */
  12644. arc: function(x, y, radius, startAngle, endAngle, anticlockwise) {
  12645. this.updatePrecisionCompensateRect();
  12646. originalCtx.arc.call(this, x * xx + dx, y * xx + dy, radius * xx, startAngle, endAngle, anticlockwise);
  12647. this.updatePrecisionCompensate();
  12648. },
  12649. /**
  12650. * Adds an arc with the given control points and radius to the current subpath,
  12651. * connected to the previous point by a straight line. If two radii are provided, the
  12652. * first controls the width of the arc's ellipse, and the second controls the height. If
  12653. * only one is provided, or if they are the same, the arc is from a circle.
  12654. *
  12655. * In the case of an ellipse, the rotation argument controls the clockwise inclination
  12656. * of the ellipse relative to the x-axis.
  12657. * @ignore
  12658. */
  12659. arcTo: function(x1, y1, x2, y2, radius) {
  12660. this.updatePrecisionCompensateRect();
  12661. originalCtx.arcTo.call(this, x1 * xx + dx, y1 * yy + dy, x2 * xx + dx, y2 * yy + dy, radius * xx);
  12662. this.updatePrecisionCompensate();
  12663. },
  12664. /**
  12665. * Pushes the context state to the state stack.
  12666. * @ignore
  12667. */
  12668. save: function() {
  12669. transStack.push(matrix);
  12670. matrix = matrix.clone();
  12671. regularOverrides.save.call(this);
  12672. originalCtx.save.call(this);
  12673. },
  12674. /**
  12675. * Pops the state stack and restores the state.
  12676. * @ignore
  12677. */
  12678. restore: function() {
  12679. matrix = transStack.pop();
  12680. regularOverrides.restore.call(this);
  12681. originalCtx.restore.call(this);
  12682. this.updatePrecisionCompensate();
  12683. },
  12684. /**
  12685. * @ignore
  12686. */
  12687. updatePrecisionCompensate: function() {
  12688. matrix.precisionCompensate(surface.devicePixelRatio, comp);
  12689. xx = comp.xx;
  12690. yy = comp.yy;
  12691. dx = comp.dx;
  12692. dy = comp.dy;
  12693. originalCtx.setTransform.call(this, surface.devicePixelRatio, comp.b, comp.c, comp.d, 0, 0);
  12694. },
  12695. /**
  12696. * @ignore
  12697. */
  12698. updatePrecisionCompensateRect: function() {
  12699. matrix.precisionCompensateRect(surface.devicePixelRatio, comp);
  12700. xx = comp.xx;
  12701. yy = comp.yy;
  12702. dx = comp.dx;
  12703. dy = comp.dy;
  12704. originalCtx.setTransform.call(this, surface.devicePixelRatio, comp.b, comp.c, comp.d, 0, 0);
  12705. },
  12706. /**
  12707. * Changes the transformation matrix to the matrix given by the arguments as described below.
  12708. * @ignore
  12709. */
  12710. setTransform: function(x2x, x2y, y2x, y2y, newDx, newDy) {
  12711. matrix.set(x2x, x2y, y2x, y2y, newDx, newDy);
  12712. this.updatePrecisionCompensate();
  12713. },
  12714. /**
  12715. * Changes the transformation matrix to apply the matrix given by the arguments as described below.
  12716. * @ignore
  12717. */
  12718. transform: function(x2x, x2y, y2x, y2y, newDx, newDy) {
  12719. matrix.append(x2x, x2y, y2x, y2y, newDx, newDy);
  12720. this.updatePrecisionCompensate();
  12721. },
  12722. /**
  12723. * Scales the transformation matrix.
  12724. * @return {*}
  12725. * @ignore
  12726. */
  12727. scale: function(sx, sy) {
  12728. this.transform(sx, 0, 0, sy, 0, 0);
  12729. },
  12730. /**
  12731. * Translates the transformation matrix.
  12732. * @return {*}
  12733. * @ignore
  12734. */
  12735. translate: function(dx, dy) {
  12736. this.transform(1, 0, 0, 1, dx, dy);
  12737. },
  12738. /**
  12739. * Rotates the transformation matrix.
  12740. * @return {*}
  12741. * @ignore
  12742. */
  12743. rotate: function(radians) {
  12744. var cos = Math.cos(radians),
  12745. sin = Math.sin(radians);
  12746. this.transform(cos, sin, -sin, cos, 0, 0);
  12747. },
  12748. /**
  12749. * Adds the given point to the current subpath, connected to the previous one by a
  12750. * quadratic Bézier curve with the given control point.
  12751. * @return {*}
  12752. * @ignore
  12753. */
  12754. quadraticCurveTo: function(cx, cy, x, y) {
  12755. originalCtx.quadraticCurveTo.call(this, cx * xx + dx, cy * yy + dy, x * xx + dx, y * yy + dy);
  12756. },
  12757. /**
  12758. * Adds the given point to the current subpath, connected to the previous one by a cubic
  12759. * Bézier curve with the given control points.
  12760. * @return {*}
  12761. * @ignore
  12762. */
  12763. bezierCurveTo: function(c1x, c1y, c2x, c2y, x, y) {
  12764. originalCtx.bezierCurveTo.call(this, c1x * xx + dx, c1y * yy + dy, c2x * xx + dx, c2y * yy + dy, x * xx + dx, y * yy + dy);
  12765. },
  12766. /**
  12767. * Returns an object that represents a linear gradient that paints along the line given
  12768. * by the coordinates represented by the arguments.
  12769. * @return {*}
  12770. * @ignore
  12771. */
  12772. createLinearGradient: function(x0, y0, x1, y1) {
  12773. this.updatePrecisionCompensateRect();
  12774. var grad = originalCtx.createLinearGradient.call(this, x0 * xx + dx, y0 * yy + dy, x1 * xx + dx, y1 * yy + dy);
  12775. this.updatePrecisionCompensate();
  12776. return grad;
  12777. },
  12778. /**
  12779. * Returns a CanvasGradient object that represents a radial gradient that paints along
  12780. * the cone given by the circles represented by the arguments. If either of the radii
  12781. * are negative, throws an IndexSizeError exception.
  12782. * @return {*}
  12783. * @ignore
  12784. */
  12785. createRadialGradient: function(x0, y0, r0, x1, y1, r1) {
  12786. this.updatePrecisionCompensateRect();
  12787. var grad = originalCtx.createLinearGradient.call(this, x0 * xx + dx, y0 * xx + dy, r0 * xx, x1 * xx + dx, y1 * xx + dy, r1 * xx);
  12788. this.updatePrecisionCompensate();
  12789. return grad;
  12790. },
  12791. /**
  12792. * Fills the given text at the given position. If a maximum width is provided, the text
  12793. * will be scaled to fit that width if necessary.
  12794. * @ignore
  12795. */
  12796. fillText: function(text, x, y, maxWidth) {
  12797. originalCtx.setTransform.apply(this, matrix.elements);
  12798. if (typeof maxWidth === 'undefined') {
  12799. originalCtx.fillText.call(this, text, x, y);
  12800. } else {
  12801. originalCtx.fillText.call(this, text, x, y, maxWidth);
  12802. }
  12803. this.updatePrecisionCompensate();
  12804. },
  12805. /**
  12806. * Strokes the given text at the given position. If a
  12807. * maximum width is provided, the text will be scaled to
  12808. * fit that width if necessary.
  12809. * @ignore
  12810. */
  12811. strokeText: function(text, x, y, maxWidth) {
  12812. originalCtx.setTransform.apply(this, matrix.elements);
  12813. if (typeof maxWidth === 'undefined') {
  12814. originalCtx.strokeText.call(this, text, x, y);
  12815. } else {
  12816. originalCtx.strokeText.call(this, text, x, y, maxWidth);
  12817. }
  12818. this.updatePrecisionCompensate();
  12819. },
  12820. /**
  12821. * Fills the subpaths of the current default path or the given path with the current fill style.
  12822. * @ignore
  12823. */
  12824. fill: function() {
  12825. var fillGradient = this.fillGradient,
  12826. bbox = this.bbox;
  12827. this.updatePrecisionCompensateRect();
  12828. if (fillGradient && bbox) {
  12829. this.fillStyle = fillGradient.generateGradient(this, bbox);
  12830. }
  12831. originalCtx.fill.call(this);
  12832. this.updatePrecisionCompensate();
  12833. },
  12834. /**
  12835. * Strokes the subpaths of the current default path or the given path with the current stroke style.
  12836. * @ignore
  12837. */
  12838. stroke: function() {
  12839. var strokeGradient = this.strokeGradient,
  12840. bbox = this.bbox;
  12841. this.updatePrecisionCompensateRect();
  12842. if (strokeGradient && bbox) {
  12843. this.strokeStyle = strokeGradient.generateGradient(this, bbox);
  12844. }
  12845. originalCtx.stroke.call(this);
  12846. this.updatePrecisionCompensate();
  12847. },
  12848. /**
  12849. * Draws the given image onto the canvas. If the first argument isn't an img, canvas,
  12850. * or video element, throws a TypeMismatchError exception. If the image has no image
  12851. * data, throws an InvalidStateError exception. If the one of the source rectangle
  12852. * dimensions is zero, throws an IndexSizeError exception. If the image isn't yet fully
  12853. * decoded, then nothing is drawn.
  12854. * @return {*}
  12855. * @ignore
  12856. */
  12857. drawImage: function(img_elem, arg1, arg2, arg3, arg4, dst_x, dst_y, dw, dh) {
  12858. switch (arguments.length) {
  12859. case 3:
  12860. return originalCtx.drawImage.call(this, img_elem, arg1 * xx + dx, arg2 * yy + dy);
  12861. case 5:
  12862. return originalCtx.drawImage.call(this, img_elem, arg1 * xx + dx, arg2 * yy + dy, arg3 * xx, arg4 * yy);
  12863. case 9:
  12864. return originalCtx.drawImage.call(this, img_elem, arg1, arg2, arg3, arg4, dst_x * xx + dx, dst_y * yy * dy, dw * xx, dh * yy);
  12865. }
  12866. }
  12867. };
  12868. Ext.apply(ctx, precisionOverrides);
  12869. this.setDirty(true);
  12870. },
  12871. /**
  12872. * Normally, a surface will have a single canvas.
  12873. * However, on certain platforms/browsers there's a limit to how big a canvas can be.
  12874. * 'splitThreshold' is used to determine maximum width/height of a single canvas element.
  12875. * When a surface is wider/taller than the splitThreshold, extra canvas element(s)
  12876. * will be created and tiled inside the surface.
  12877. */
  12878. updateRect: function(rect) {
  12879. this.callParent([
  12880. rect
  12881. ]);
  12882. var me = this,
  12883. l = Math.floor(rect[0]),
  12884. t = Math.floor(rect[1]),
  12885. r = Math.ceil(rect[0] + rect[2]),
  12886. b = Math.ceil(rect[1] + rect[3]),
  12887. devicePixelRatio = me.devicePixelRatio,
  12888. canvases = me.canvases,
  12889. w = r - l,
  12890. h = b - t,
  12891. splitThreshold = Math.round(me.splitThreshold / devicePixelRatio),
  12892. xSplits = me.xSplits = Math.ceil(w / splitThreshold),
  12893. ySplits = me.ySplits = Math.ceil(h / splitThreshold),
  12894. i, j, k, offsetX, offsetY, dom, width, height;
  12895. for (j = 0 , offsetY = 0; j < ySplits; j++ , offsetY += splitThreshold) {
  12896. for (i = 0 , offsetX = 0; i < xSplits; i++ , offsetX += splitThreshold) {
  12897. k = j * xSplits + i;
  12898. if (k >= canvases.length) {
  12899. me.createCanvas();
  12900. }
  12901. dom = canvases[k].dom;
  12902. dom.style.left = offsetX + 'px';
  12903. dom.style.top = offsetY + 'px';
  12904. // The Canvas doesn't automatically support hi-DPI displays.
  12905. // We have to actually create a larger canvas (more pixels)
  12906. // while keeping its physical size the same.
  12907. height = Math.min(splitThreshold, h - offsetY);
  12908. if (height * devicePixelRatio !== dom.height) {
  12909. dom.height = height * devicePixelRatio;
  12910. dom.style.height = height + 'px';
  12911. }
  12912. width = Math.min(splitThreshold, w - offsetX);
  12913. if (width * devicePixelRatio !== dom.width) {
  12914. dom.width = width * devicePixelRatio;
  12915. dom.style.width = width + 'px';
  12916. }
  12917. me.applyDefaults(me.contexts[k]);
  12918. }
  12919. }
  12920. me.activeCanvases = k = xSplits * ySplits;
  12921. while (canvases.length > k) {
  12922. canvases.pop().destroy();
  12923. }
  12924. me.clear();
  12925. },
  12926. /**
  12927. * @method clearTransform
  12928. * @inheritdoc
  12929. */
  12930. clearTransform: function() {
  12931. var me = this,
  12932. xSplits = me.xSplits,
  12933. ySplits = me.ySplits,
  12934. contexts = me.contexts,
  12935. splitThreshold = me.splitThreshold,
  12936. devicePixelRatio = me.devicePixelRatio,
  12937. i, j, k, ctx;
  12938. for (i = 0; i < xSplits; i++) {
  12939. for (j = 0; j < ySplits; j++) {
  12940. k = j * xSplits + i;
  12941. ctx = contexts[k];
  12942. ctx.translate(-splitThreshold * i, -splitThreshold * j);
  12943. ctx.scale(devicePixelRatio, devicePixelRatio);
  12944. me.matrix.toContext(ctx);
  12945. }
  12946. }
  12947. },
  12948. /**
  12949. * @method renderSprite
  12950. * @inheritdoc
  12951. */
  12952. renderSprite: function(sprite) {
  12953. var me = this,
  12954. rect = me.getRect(),
  12955. surfaceMatrix = me.matrix,
  12956. parent = sprite.getParent(),
  12957. matrix = Ext.draw.Matrix.fly([
  12958. 1,
  12959. 0,
  12960. 0,
  12961. 1,
  12962. 0,
  12963. 0
  12964. ]),
  12965. splitThreshold = me.splitThreshold / me.devicePixelRatio,
  12966. xSplits = me.xSplits,
  12967. ySplits = me.ySplits,
  12968. offsetX, offsetY, ctx, bbox, width, height,
  12969. left = 0,
  12970. right,
  12971. top = 0,
  12972. bottom,
  12973. w = rect[2],
  12974. h = rect[3],
  12975. i, j, k;
  12976. while (parent && parent.isSprite) {
  12977. matrix.prependMatrix(parent.matrix || parent.attr && parent.attr.matrix);
  12978. parent = parent.getParent();
  12979. }
  12980. matrix.prependMatrix(surfaceMatrix);
  12981. bbox = sprite.getBBox();
  12982. if (bbox) {
  12983. bbox = matrix.transformBBox(bbox);
  12984. }
  12985. sprite.preRender(me);
  12986. if (sprite.attr.hidden || sprite.attr.globalAlpha === 0) {
  12987. sprite.setDirty(false);
  12988. return;
  12989. }
  12990. // Render this sprite on all Canvas elements it spans, skipping the rest.
  12991. for (j = 0 , offsetY = 0; j < ySplits; j++ , offsetY += splitThreshold) {
  12992. for (i = 0 , offsetX = 0; i < xSplits; i++ , offsetX += splitThreshold) {
  12993. k = j * xSplits + i;
  12994. ctx = me.contexts[k];
  12995. width = Math.min(splitThreshold, w - offsetX);
  12996. height = Math.min(splitThreshold, h - offsetY);
  12997. left = offsetX;
  12998. right = left + width;
  12999. top = offsetY;
  13000. bottom = top + height;
  13001. if (bbox) {
  13002. if (bbox.x > right || bbox.x + bbox.width < left || bbox.y > bottom || bbox.y + bbox.height < top) {
  13003. continue;
  13004. }
  13005. }
  13006. ctx.save();
  13007. sprite.useAttributes(ctx, rect);
  13008. if (false === sprite.render(me, ctx, [
  13009. left,
  13010. top,
  13011. width,
  13012. height
  13013. ])) {
  13014. return false;
  13015. }
  13016. ctx.restore();
  13017. }
  13018. }
  13019. sprite.setDirty(false);
  13020. },
  13021. flatten: function(size, surfaces) {
  13022. var targetCanvas = document.createElement('canvas'),
  13023. className = Ext.getClassName(this),
  13024. ratio = this.devicePixelRatio,
  13025. ctx = targetCanvas.getContext('2d'),
  13026. surface, canvas, rect, i, j, xy;
  13027. targetCanvas.width = Math.ceil(size.width * ratio);
  13028. targetCanvas.height = Math.ceil(size.height * ratio);
  13029. for (i = 0; i < surfaces.length; i++) {
  13030. surface = surfaces[i];
  13031. if (Ext.getClassName(surface) !== className) {
  13032. continue;
  13033. }
  13034. rect = surface.getRect();
  13035. for (j = 0; j < surface.canvases.length; j++) {
  13036. canvas = surface.canvases[j];
  13037. xy = canvas.getOffsetsTo(canvas.getParent());
  13038. ctx.drawImage(canvas.dom, (rect[0] + xy[0]) * ratio, (rect[1] + xy[1]) * ratio);
  13039. }
  13040. }
  13041. return {
  13042. data: targetCanvas.toDataURL(),
  13043. type: 'png'
  13044. };
  13045. },
  13046. applyDefaults: function(ctx) {
  13047. var none = Ext.util.Color.RGBA_NONE;
  13048. ctx.strokeStyle = none;
  13049. ctx.fillStyle = none;
  13050. ctx.textAlign = 'start';
  13051. ctx.textBaseline = 'alphabetic';
  13052. ctx.miterLimit = 1;
  13053. },
  13054. /**
  13055. * @method clear
  13056. * @inheritdoc
  13057. */
  13058. clear: function() {
  13059. var me = this,
  13060. activeCanvases = me.activeCanvases,
  13061. i, canvas, ctx;
  13062. for (i = 0; i < activeCanvases; i++) {
  13063. canvas = me.canvases[i].dom;
  13064. ctx = me.contexts[i];
  13065. ctx.setTransform(1, 0, 0, 1, 0, 0);
  13066. ctx.clearRect(0, 0, canvas.width, canvas.height);
  13067. }
  13068. me.setDirty(true);
  13069. },
  13070. /**
  13071. * Destroys the Canvas element and prepares it for Garbage Collection.
  13072. */
  13073. destroy: function() {
  13074. var me = this,
  13075. canvases = me.canvases,
  13076. ln = canvases.length,
  13077. i;
  13078. for (i = 0; i < ln; i++) {
  13079. me.contexts[i] = null;
  13080. canvases[i].destroy();
  13081. canvases[i] = null;
  13082. }
  13083. me.contexts = me.canvases = null;
  13084. me.callParent();
  13085. },
  13086. privates: {
  13087. initElement: function() {
  13088. var me = this;
  13089. me.callParent();
  13090. me.canvases = [];
  13091. me.contexts = [];
  13092. me.activeCanvases = me.xSplits = me.ySplits = 0;
  13093. }
  13094. }
  13095. }, function() {
  13096. var me = this,
  13097. proto = me.prototype,
  13098. splitThreshold = 1.0E10;
  13099. if (Ext.os.is.Android4 && Ext.browser.is.Chrome) {
  13100. splitThreshold = 3000;
  13101. } else if (Ext.is.iOS) {
  13102. splitThreshold = 2200;
  13103. }
  13104. proto.splitThreshold = splitThreshold;
  13105. });
  13106. /**
  13107. * The container that holds and manages instances of the {@link Ext.draw.Surface}
  13108. * in which {@link Ext.draw.sprite.Sprite sprites} are rendered. Draw containers are
  13109. * used as the foundation for all of the chart classes but may also be created directly
  13110. * in order to create custom drawings.
  13111. *
  13112. * @example
  13113. * var drawContainer = Ext.create('Ext.draw.Container', {
  13114. * renderTo: Ext.getBody(),
  13115. * width:200,
  13116. * height:200,
  13117. * sprites: [{
  13118. * type: 'circle',
  13119. * fillStyle: '#79BB3F',
  13120. * r: 100,
  13121. * x: 100,
  13122. * y: 100
  13123. * }]
  13124. * });
  13125. *
  13126. * // Uncomment to trigger a download of the painted circle.
  13127. * // drawContainer.download({
  13128. * // filename: 'Circle',
  13129. * // url: 'http://svg.sencha.io' // Default server the image data is sent to.
  13130. * // });
  13131. *
  13132. * In the previous example we created a draw container and configured it with a single
  13133. * sprite. The *type* of the sprite is {@link Ext.draw.sprite.Circle circle}, so if you
  13134. * run this code you'll see a green circle.
  13135. *
  13136. * You can attach sprite event listeners to the draw container with the help of the
  13137. * {@link Ext.draw.plugin.SpriteEvents} plugin.
  13138. *
  13139. * For more information on sprites, the core elements added to a draw container's
  13140. * surface, refer to the Ext.draw.sprite.Sprite documentation.
  13141. *
  13142. * For more information on surfaces, the interface owned by the draw container used to
  13143. * manage all sprites, see the Ext.draw.Surface documentation.
  13144. */
  13145. Ext.define('Ext.draw.Container', {
  13146. extend: 'Ext.draw.ContainerBase',
  13147. alternateClassName: 'Ext.draw.Component',
  13148. xtype: 'draw',
  13149. defaultType: 'surface',
  13150. isDrawContainer: true,
  13151. requires: [
  13152. 'Ext.draw.Surface',
  13153. 'Ext.draw.engine.Svg',
  13154. 'Ext.draw.engine.Canvas',
  13155. 'Ext.draw.gradient.GradientDefinition'
  13156. ],
  13157. /**
  13158. * @cfg {String} [engine="Ext.draw.engine.Canvas"]
  13159. * Defines the engine (type of surface) used to render draw container contents.
  13160. *
  13161. * The render engine is selected automatically depending on the platform used. Priority
  13162. * is given to the {@link Ext.draw.engine.Canvas} engine due to its performance advantage.
  13163. *
  13164. * You may also set the engine config to be `Ext.draw.engine.Svg` if so desired.
  13165. */
  13166. engine: 'Ext.draw.engine.Canvas',
  13167. /**
  13168. * @event spritemousemove
  13169. * Fires when the mouse is moved on a sprite.
  13170. * @param {Object} sprite
  13171. * @param {Event} event
  13172. */
  13173. /**
  13174. * @event spritemouseup
  13175. * Fires when a mouseup event occurs on a sprite.
  13176. * @param {Object} sprite
  13177. * @param {Event} event
  13178. */
  13179. /**
  13180. * @event spritemousedown
  13181. * Fires when a mousedown event occurs on a sprite.
  13182. * @param {Object} sprite
  13183. * @param {Event} event
  13184. */
  13185. /**
  13186. * @event spritemouseover
  13187. * Fires when the mouse enters a sprite.
  13188. * @param {Object} sprite
  13189. * @param {Event} event
  13190. */
  13191. /**
  13192. * @event spritemouseout
  13193. * Fires when the mouse exits a sprite.
  13194. * @param {Object} sprite
  13195. * @param {Event} event
  13196. */
  13197. /**
  13198. * @event spriteclick
  13199. * Fires when a click event occurs on a sprite.
  13200. * @param {Object} sprite
  13201. * @param {Event} event
  13202. */
  13203. /**
  13204. * @event spritedblclick
  13205. * Fires when a double click event occurs on a sprite.
  13206. * @param {Object} sprite
  13207. * @param {Event} event
  13208. */
  13209. /**
  13210. * @event spritetap
  13211. * Fires when a tap event occurs on a sprite.
  13212. * @param {Object} sprite
  13213. * @param {Event} event
  13214. */
  13215. /**
  13216. * @event bodyresize
  13217. * Fires when the size of the draw container body changes.
  13218. * @param {Object} size The object containing 'width' and 'height' of the draw container's body.
  13219. */
  13220. config: {
  13221. cls: [
  13222. Ext.baseCSSPrefix + 'draw-container',
  13223. Ext.baseCSSPrefix + 'unselectable'
  13224. ],
  13225. /**
  13226. * @cfg {Function} [resizeHandler]
  13227. * The resize function that can be configured to have a behavior,
  13228. * e.g. resize draw surfaces based on new draw container dimensions.
  13229. * The `resizeHandler` function takes a single parameter -
  13230. * the size object with `width` and `height` properties.
  13231. *
  13232. * **Note:** Since resize events trigger {@link #renderFrame} calls automatically,
  13233. * return `false` from the resize function, if it also calls `renderFrame`,
  13234. * to prevent double rendering.
  13235. */
  13236. resizeHandler: null,
  13237. /**
  13238. * @cfg {Object[]} sprites
  13239. * Defines a set of sprites to be added to the drawContainer surface.
  13240. *
  13241. * For example:
  13242. *
  13243. * sprites: [{
  13244. * type: 'circle',
  13245. * fillStyle: '#79BB3F',
  13246. * r: 100,
  13247. * x: 100,
  13248. * y: 100
  13249. * }]
  13250. *
  13251. */
  13252. sprites: null,
  13253. /**
  13254. * @cfg {Object[]} gradients
  13255. * Defines a set of gradients that can be used as color properties
  13256. * (fillStyle and strokeStyle, but not shadowColor) in sprites.
  13257. * The gradients array is an array of objects with the following properties:
  13258. * - **id** - string - The unique name of the gradient.
  13259. * - **type** - string, optional - The type of the gradient. Available types are: 'linear', 'radial'. Defaults to 'linear'.
  13260. * - **angle** - number, optional - The angle of the gradient in degrees.
  13261. * - **stops** - array - An array of objects with 'color' and 'offset' properties, where 'offset' is a real number from 0 to 1.
  13262. *
  13263. * For example:
  13264. *
  13265. * gradients: [{
  13266. * id: 'gradientId1',
  13267. * type: 'linear',
  13268. * angle: 45,
  13269. * stops: [{
  13270. * offset: 0,
  13271. * color: 'red'
  13272. * }, {
  13273. * offset: 1,
  13274. * color: 'yellow'
  13275. * }]
  13276. * }, {
  13277. * id: 'gradientId2',
  13278. * type: 'radial',
  13279. * stops: [{
  13280. * offset: 0,
  13281. * color: '#555',
  13282. * }, {
  13283. * offset: 1,
  13284. * color: '#ddd',
  13285. * }]
  13286. * }]
  13287. *
  13288. * Then the sprites can use 'gradientId1' and 'gradientId2' by setting the color attributes to those ids, for example:
  13289. *
  13290. * sprite.setAttributes({
  13291. * fillStyle: 'url(#gradientId1)',
  13292. * strokeStyle: 'url(#gradientId2)'
  13293. * });
  13294. */
  13295. gradients: [],
  13296. /**
  13297. * @cfg {String} downloadServerUrl
  13298. * The default URL used by the {@link #download} method.
  13299. */
  13300. downloadServerUrl: undefined,
  13301. touchAction: {
  13302. panX: false,
  13303. panY: false,
  13304. pinchZoom: false,
  13305. doubleTapZoom: false
  13306. },
  13307. /**
  13308. * @private
  13309. * @cfg {Object} surfaceZIndexes A map of surface type name to zIndex.
  13310. * The z-indexes to use for the various types of surfaces.
  13311. */
  13312. surfaceZIndexes: {
  13313. main: 1
  13314. }
  13315. },
  13316. /**
  13317. * @private
  13318. * @property {String} [defaultDownloadServerUrl="http://svg.sencha.io"]
  13319. * The default URL used by the {@link #download} method if the {@link #downloadServerUrl}
  13320. * config wasn't set.
  13321. * To override this globally, set the `Ext.draw.Container.prototype.defaultDownloadServerUrl`.
  13322. */
  13323. defaultDownloadServerUrl: 'http://svg.sencha.io',
  13324. /**
  13325. * @property {Array} [supportedFormats=["png", "pdf", "jpeg", "gif"]]
  13326. * A list of export types supported by the server.
  13327. * @private
  13328. */
  13329. supportedFormats: [
  13330. 'png',
  13331. 'pdf',
  13332. 'jpeg',
  13333. 'gif'
  13334. ],
  13335. supportedOptions: {
  13336. version: Ext.isNumber,
  13337. data: Ext.isString,
  13338. format: function(format) {
  13339. return Ext.Array.indexOf(this.supportedFormats, format) >= 0;
  13340. },
  13341. filename: Ext.isString,
  13342. width: Ext.isNumber,
  13343. height: Ext.isNumber,
  13344. scale: Ext.isNumber,
  13345. pdf: Ext.isObject,
  13346. jpeg: Ext.isObject
  13347. },
  13348. initAnimator: function() {
  13349. this.frameCallbackId = Ext.draw.Animator.addFrameCallback('renderFrame', this);
  13350. },
  13351. applyDownloadServerUrl: function(url) {
  13352. var defaultUrl = this.defaultDownloadServerUrl;
  13353. if (!url) {
  13354. url = defaultUrl;
  13355. //<debug>
  13356. // Skip this warning when unit testing.
  13357. if (!window.jasmine) {
  13358. 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() + ')');
  13359. }
  13360. }
  13361. //</debug>
  13362. return url;
  13363. },
  13364. applyGradients: function(gradients) {
  13365. var result = [],
  13366. i, n, gradient, offset;
  13367. if (!Ext.isArray(gradients)) {
  13368. return result;
  13369. }
  13370. for (i = 0 , n = gradients.length; i < n; i++) {
  13371. gradient = gradients[i];
  13372. if (!Ext.isObject(gradient)) {
  13373. continue;
  13374. }
  13375. // ExtJS only supported linear gradients, so we didn't have to specify their type
  13376. if (typeof gradient.type !== 'string') {
  13377. gradient.type = 'linear';
  13378. }
  13379. if (gradient.angle) {
  13380. gradient.degrees = gradient.angle;
  13381. delete gradient.angle;
  13382. }
  13383. // Convert ExtJS stops object to Touch stops array
  13384. if (Ext.isObject(gradient.stops)) {
  13385. gradient.stops = (function(stops) {
  13386. var result = [],
  13387. stop;
  13388. for (offset in stops) {
  13389. stop = stops[offset];
  13390. stop.offset = offset / 100;
  13391. result.push(stop);
  13392. }
  13393. return result;
  13394. })(gradient.stops);
  13395. }
  13396. result.push(gradient);
  13397. }
  13398. Ext.draw.gradient.GradientDefinition.add(result);
  13399. return result;
  13400. },
  13401. applySprites: function(sprites) {
  13402. // Never update.
  13403. if (!sprites) {
  13404. return;
  13405. }
  13406. sprites = Ext.Array.from(sprites);
  13407. var ln = sprites.length,
  13408. result = [],
  13409. i, surface, sprite;
  13410. for (i = 0; i < ln; i++) {
  13411. sprite = sprites[i];
  13412. surface = sprite.surface;
  13413. if (!(surface && surface.isSurface)) {
  13414. if (Ext.isString(surface)) {
  13415. surface = this.getSurface(surface);
  13416. delete sprite.surface;
  13417. } else {
  13418. surface = this.getSurface('main');
  13419. }
  13420. }
  13421. sprite = surface.add(sprite);
  13422. result.push(sprite);
  13423. }
  13424. return result;
  13425. },
  13426. resizeDelay: 500,
  13427. // in milliseconds
  13428. resizeTimerId: 0,
  13429. lastResizeTime: null,
  13430. /**
  13431. * @private
  13432. * @property
  13433. * Last valid size.
  13434. */
  13435. size: null,
  13436. /**
  13437. * Triggers the {@link #resizeHandler} with the size of the draw container
  13438. * element as the parameter.
  13439. */
  13440. handleResize: function(size, instantly) {
  13441. // See the following:
  13442. // Classic: Ext.draw.ContainerBase.reattachToBody
  13443. // Modern: Ext.draw.ContainerBase.initialize
  13444. var me = this,
  13445. el = me.element,
  13446. resizeHandler = me.getResizeHandler() || me.defaultResizeHandler,
  13447. resizeDelay = me.resizeDelay,
  13448. lastResizeTime = me.lastResizeTime,
  13449. defer, result;
  13450. if (!el) {
  13451. return;
  13452. }
  13453. size = size || el.getSize();
  13454. if (!(size.width && size.height)) {
  13455. return;
  13456. }
  13457. me.size = size;
  13458. me.stopResizeTimer();
  13459. // Only want to defer when multiple resize events happen in quick succession.
  13460. // That way it doesn't feel luggy during an occasional resize, nor it's too straining
  13461. // when continuously resizing.
  13462. defer = !instantly && lastResizeTime && (Ext.Date.now() - lastResizeTime < resizeDelay);
  13463. if (defer) {
  13464. me.resizeTimerId = Ext.defer(me.handleResize, resizeDelay, me, [
  13465. size,
  13466. true
  13467. ]);
  13468. return;
  13469. }
  13470. me.fireEvent('bodyresize', me, size);
  13471. Ext.callback(resizeHandler, null, [
  13472. size
  13473. ], 0, me);
  13474. if (result !== false) {
  13475. me.renderFrame();
  13476. }
  13477. me.lastResizeTime = Ext.Date.now();
  13478. },
  13479. /**
  13480. * @private
  13481. */
  13482. stopResizeTimer: function() {
  13483. if (this.resizeTimerId) {
  13484. Ext.undefer(this.resizeTimerId);
  13485. this.resizeTimerId = 0;
  13486. }
  13487. },
  13488. defaultResizeHandler: function(size) {
  13489. this.getItems().each(function(surface) {
  13490. surface.setRect([
  13491. 0,
  13492. 0,
  13493. size.width,
  13494. size.height
  13495. ]);
  13496. });
  13497. },
  13498. /**
  13499. * Get a surface by the given id or create one if it doesn't exist.
  13500. * This will automatically call the {@link #resizeHandler}. Which
  13501. * means that, if no custom resize handler has been provided, the
  13502. * surface will be sized to match the container.
  13503. * If the {@link #method!add} method is used, it is the responsibility
  13504. * of the user to call the {@link #handleResize} method, to update
  13505. * the size of all added surfaces.
  13506. * @param {String} [id="main"]
  13507. * @param {String} type
  13508. * @return {Ext.draw.Surface}
  13509. */
  13510. getSurface: function(id, type) {
  13511. id = id || 'main';
  13512. type = type || id;
  13513. var me = this,
  13514. surfaces = me.getItems(),
  13515. oldCount = surfaces.getCount(),
  13516. zIndexes = me.getSurfaceZIndexes(),
  13517. surface;
  13518. surface = me.createSurface(id);
  13519. if (type in zIndexes) {
  13520. surface.element.setStyle('zIndex', zIndexes[type]);
  13521. }
  13522. if (surfaces.getCount() > oldCount) {
  13523. // Immediately call resize handler of the draw container,
  13524. // so that the newly created surface gets a size.
  13525. me.handleResize(null, true);
  13526. }
  13527. return surface;
  13528. },
  13529. createSurface: function(id) {
  13530. id = this.getId() + '-' + (id || 'main');
  13531. var me = this,
  13532. surfaces = me.getItems(),
  13533. surface = surfaces.get(id);
  13534. if (!surface) {
  13535. surface = me.add({
  13536. xclass: me.engine,
  13537. id: id
  13538. });
  13539. }
  13540. return surface;
  13541. },
  13542. /**
  13543. * Render all the surfaces in the container.
  13544. */
  13545. renderFrame: function() {
  13546. var me = this,
  13547. surfaces = me.getItems(),
  13548. i, ln, item;
  13549. for (i = 0 , ln = surfaces.length; i < ln; i++) {
  13550. item = surfaces.items[i];
  13551. if (item.isSurface) {
  13552. item.renderFrame();
  13553. }
  13554. }
  13555. },
  13556. /**
  13557. * @private
  13558. * Returns a slice of the surfaces (items) array of the draw container,
  13559. * optionally sorting them by zIndex.
  13560. * Overridden in subclasses.
  13561. */
  13562. getSurfaces: function(sort) {
  13563. var surfaces = Array.prototype.slice.call(this.items.items),
  13564. zIndexes = this.getSurfaceZIndexes(),
  13565. i, j, surface, zIndex;
  13566. if (sort) {
  13567. // Sort the surfaces by zIndex using insertion sort.
  13568. for (j = 1; j < surfaces.length; j++) {
  13569. surface = surfaces[j];
  13570. zIndex = zIndexes[surface.type];
  13571. i = j - 1;
  13572. while (i >= 0 && zIndexes[surfaces[i].type] > zIndex) {
  13573. surfaces[i + 1] = surfaces[i];
  13574. i--;
  13575. }
  13576. surfaces[i + 1] = surface;
  13577. }
  13578. }
  13579. return surfaces;
  13580. },
  13581. /**
  13582. * Produces an image of the chart / drawing.
  13583. * @param {String} [format] Possible options are 'image' (the method will return an
  13584. * Image object) and 'stream' (the method will return the image as a byte stream).
  13585. * If missing, the data URI of the drawing's (or chart's) image will be returned.
  13586. * Note: for an SVG based drawing/chart in IE/Edge browsers the method will always
  13587. * return SVG markup instead of a data URI, as 'img' elements won't accept a data
  13588. * URI anyway in those browsers.
  13589. * @return {Object}
  13590. * @return {String} return.data Image element, byte stream or DataURL.
  13591. * @return {String} return.type The type of the data (e.g. 'png' or 'svg').
  13592. */
  13593. getImage: function(format) {
  13594. var size = this.bodyElement.getSize(),
  13595. surfaces = this.getSurfaces(true),
  13596. surface = surfaces[0],
  13597. image, imageElement;
  13598. if ((Ext.isIE || Ext.isEdge) && surface.isSVG) {
  13599. // SVG data URLs don't work in IE/Edge as a source for an 'img' element,
  13600. // so we need to render SVG the usual way.
  13601. image = {
  13602. data: surface.toSVG(size, surfaces),
  13603. type: 'svg-markup'
  13604. };
  13605. } else {
  13606. image = surface.flatten(size, surfaces);
  13607. if (format === 'image') {
  13608. imageElement = new Image();
  13609. imageElement.src = image.data;
  13610. image.data = imageElement;
  13611. return image;
  13612. }
  13613. if (format === 'stream') {
  13614. image.data = image.data.replace(/^data:image\/[^;]+/, 'data:application/octet-stream');
  13615. return image;
  13616. }
  13617. }
  13618. return image;
  13619. },
  13620. /**
  13621. * Downloads an image or PDF of the chart / drawing or opens it in a separate
  13622. * browser tab/window if the download can't be triggered. The exact behavior is
  13623. * platform and browser specific. For more consistent results on mobile devices use
  13624. * the {@link #preview} method instead. This method doesn't work in IE8.
  13625. *
  13626. * Important: The default download mechanism sends image data to `http://svg.sencha.io`,
  13627. * which is a server operated by Sencha. This can be changed by setting
  13628. * the {@link #downloadServerUrl} config to the address of another server.
  13629. *
  13630. * You can deploy your own server by using the code from the `server` directory
  13631. * in the Charts package. The server is Node.js based and uses PhantomJS to
  13632. * generate images and PDFs from received data.
  13633. *
  13634. * The warning that the default download server is used can be suppressed
  13635. * by explicitly setting the value of the {@link #downloadServerUrl} config
  13636. * to `http://svg.sencha.io`.
  13637. *
  13638. * @param {Object} [config] The following config options are supported:
  13639. *
  13640. * @param {String} config.url The url to post the data to. Defaults to
  13641. * the value of the {@link #downloadServerUrl} config.
  13642. *
  13643. * @param {String} config.format The format of image to export. See the
  13644. * {@link #supportedFormats}. Defaults to 'png' on the Sencha IO server.
  13645. * Note that you can't export to 'svg' format if the {@link Ext.draw.engine.Canvas Canvas}
  13646. * {@link Ext.draw.Container#engine engine} is used.
  13647. *
  13648. * @param {Number} config.width A width to send to the server for
  13649. * configuring the image width. Defaults to natural image width on
  13650. * the Sencha IO server.
  13651. *
  13652. * @param {Number} config.height A height to send to the server for
  13653. * configuring the image height. Defaults to natural image height on
  13654. * the Sencha IO server.
  13655. *
  13656. * @param {String} config.filename The filename of the downloaded image.
  13657. * Defaults to 'chart' on the Sencha IO server. The config.format is used
  13658. * as a filename extension.
  13659. *
  13660. * @param {Number} config.scale The scaling of the downloaded image.
  13661. * Defaults to 1 on the Sencha IO server. The server will try to determine the natural
  13662. * size of the image unless the width/height configs have been set. If the
  13663. * {@link Ext.draw.engine.Canvas Canvas} {@link Ext.draw.Container#engine engine} is
  13664. * used the natural image size will depend on the value of the window.devicePixelRatio.
  13665. * For example, for devices with devicePixelRatio of 2 the produced image will be
  13666. * two times larger than for devices with devicePixelRatio of 1 for the same drawing.
  13667. * This is done so that the users with devices with HiDPI screens get a downloaded
  13668. * image that looks as crisp on their device as the original drawing.
  13669. * If you want image size to be consistent across devices with different device
  13670. * pixel ratios, you can set the value of this config to 1/devicePixelRatio.
  13671. * This parameter is ignored by the Sencha IO server if config.format is set to 'svg'.
  13672. *
  13673. * @param {Object} config.pdf PDF specific options.
  13674. * This config is only used if config.format is set to 'pdf'.
  13675. * The given object should be in either this format:
  13676. *
  13677. * {
  13678. * width: '200px',
  13679. * height: '300px',
  13680. * border: '0px'
  13681. * }
  13682. *
  13683. * or this format:
  13684. *
  13685. * {
  13686. * format: 'A4',
  13687. * orientation: 'portrait',
  13688. * border: '1cm'
  13689. * }
  13690. *
  13691. * Supported dimension units are: 'mm', 'cm', 'in', 'px'. No unit means 'px'.
  13692. * Supported formats are: 'A3', 'A4', 'A5', 'Legal', 'Letter', 'Tabloid'.
  13693. * Orientation ('portrait', 'landscape') is optional and defaults to 'portrait'.
  13694. *
  13695. * @param {Object} config.jpeg JPEG specific options.
  13696. * This config is only used if config.format is set to 'jpeg'.
  13697. * The given object should be in this format:
  13698. *
  13699. * {
  13700. * quality: 80
  13701. * }
  13702. *
  13703. * Where quality is an integer between 0 and 100.
  13704. *
  13705. * @return {Boolean} True if request was successfully sent to the server.
  13706. */
  13707. download: function(config) {
  13708. var me = this,
  13709. inputs = [],
  13710. markup, name, value;
  13711. if (Ext.isIE8) {
  13712. return false;
  13713. }
  13714. config = config || {};
  13715. config.version = 2;
  13716. if (!config.data) {
  13717. config.data = me.getImage().data;
  13718. }
  13719. for (name in config) {
  13720. if (config.hasOwnProperty(name)) {
  13721. value = config[name];
  13722. if (name in me.supportedOptions) {
  13723. if (me.supportedOptions[name].call(me, value)) {
  13724. inputs.push({
  13725. tag: 'input',
  13726. type: 'hidden',
  13727. name: name,
  13728. value: Ext.String.htmlEncode(Ext.isObject(value) ? Ext.JSON.encode(value) : value)
  13729. });
  13730. } else //<debug>
  13731. {
  13732. Ext.log.error('Invalid value for image download option "' + name + '": ' + value);
  13733. }
  13734. } else //</debug>
  13735. //<debug>
  13736. {
  13737. Ext.log.error('Invalid image download option: "' + name + '"');
  13738. }
  13739. }
  13740. }
  13741. //</debug>
  13742. markup = Ext.dom.Helper.markup({
  13743. tag: 'html',
  13744. children: [
  13745. {
  13746. tag: 'head'
  13747. },
  13748. {
  13749. tag: 'body',
  13750. children: [
  13751. {
  13752. tag: 'form',
  13753. method: 'POST',
  13754. action: config.url || me.getDownloadServerUrl(),
  13755. children: inputs
  13756. },
  13757. {
  13758. tag: 'script',
  13759. type: 'text/javascript',
  13760. children: 'document.getElementsByTagName("form")[0].submit();'
  13761. }
  13762. ]
  13763. }
  13764. ]
  13765. });
  13766. window.open('', 'ImageDownload_' + Date.now()).document.write(markup);
  13767. },
  13768. /**
  13769. * @method preview
  13770. * Displays an image of a Ext.draw.Container on screen.
  13771. * On mobile devices this lets users tap-and-hold to bring up the menu
  13772. * with image saving options.
  13773. * Notes:
  13774. * - some browsers won't save the preview image if it's SVG based
  13775. * (i.e. generated from a draw container that uses 'Ext.draw.engine.Svg' engine);
  13776. * - some platforms may not have the means of viewing successfully saved SVG images;
  13777. * - this method does not work on IE8.
  13778. */
  13779. doDestroy: function() {
  13780. var me = this,
  13781. callbackId = me.frameCallbackId;
  13782. if (callbackId) {
  13783. Ext.draw.Animator.removeFrameCallback(callbackId);
  13784. }
  13785. me.stopResizeTimer();
  13786. me.callParent();
  13787. }
  13788. }, function() {
  13789. if (location.search.match('svg')) {
  13790. Ext.draw.Container.prototype.engine = 'Ext.draw.engine.Svg';
  13791. } 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))) {
  13792. // http://code.google.com/p/android/issues/detail?id=37529
  13793. Ext.draw.Container.prototype.engine = 'Ext.draw.engine.Svg';
  13794. }
  13795. });
  13796. /**
  13797. *
  13798. */
  13799. Ext.define('Ext.chart.theme.BaseTheme', {
  13800. defaultsDivCls: 'x-root'
  13801. });
  13802. /**
  13803. * Abstract class that provides default styles for non-specified things.
  13804. * Should be sub-classed when creating new themes.
  13805. * For example:
  13806. *
  13807. * Ext.define('Ext.chart.theme.Custom', {
  13808. * extend: 'Ext.chart.theme.Base',
  13809. * singleton: true,
  13810. * alias: 'chart.theme.custom',
  13811. * config: {
  13812. * baseColor: '#ff9f00'
  13813. * }
  13814. * });
  13815. *
  13816. * Theme provided values will not override the values provided in an instance config.
  13817. * However, if a theme provided value is an object, it will be merged with the value
  13818. * from the instance config, unless the theme provided object has a '$default' key
  13819. * set to 'true'.
  13820. *
  13821. * Certain chart theme configs (e.g. 'fontSize') may use the 'default' value to indicate
  13822. * that they should inherit a value from the corresponding CSS style provided by
  13823. * a framework theme. Additionally, one can use basic binary operators like multiplication,
  13824. * addition and subtraction to derive from the default value, e.g. fontSize: 'default*1.3'.
  13825. *
  13826. * Important: the theme should not use the 'font' shorthand to specify the font of labels
  13827. * and other text elements of a chart. Instead, individual font properties should be used:
  13828. * 'fontStyle', 'fontVariant', 'fontWeight', 'fontSize' and 'fontFamily'.
  13829. */
  13830. Ext.define('Ext.chart.theme.Base', {
  13831. extend: 'Ext.chart.theme.BaseTheme',
  13832. mixins: {
  13833. factoryable: 'Ext.mixin.Factoryable'
  13834. },
  13835. requires: [
  13836. 'Ext.draw.Color'
  13837. ],
  13838. factoryConfig: {
  13839. type: 'chart.theme'
  13840. },
  13841. isTheme: true,
  13842. isBase: true,
  13843. config: {
  13844. /**
  13845. * @cfg {String/Ext.util.Color} baseColor
  13846. * The base color used to generate the {@link Ext.chart.AbstractChart#colors} of the theme.
  13847. */
  13848. baseColor: null,
  13849. /**
  13850. * @cfg {Array} colors
  13851. *
  13852. * Array of colors/gradients to be used by the theme.
  13853. * Defaults to {@link #colorDefaults}.
  13854. */
  13855. colors: undefined,
  13856. /**
  13857. * @cfg {Object} gradients
  13858. *
  13859. * The gradient config to be used by series' sprites. E.g.:
  13860. *
  13861. * {
  13862. * type: 'linear',
  13863. * degrees: 90
  13864. * }
  13865. *
  13866. * Please refer to the documentation for the {@link Ext.draw.gradient.Linear linear}
  13867. * and {@link Ext.draw.gradient.Radial radial} gradients for all possible options.
  13868. * The color {@link Ext.draw.gradient.Gradient#stops stops} for the gradients
  13869. * will be generated by the theme based on the {@link #colors} config.
  13870. */
  13871. gradients: null,
  13872. /**
  13873. * @cfg {Object} chart
  13874. * Theme defaults for the chart.
  13875. * Can apply to all charts or just a specific type of chart.
  13876. * For example:
  13877. *
  13878. * chart: {
  13879. * defaults: {
  13880. * background: 'lightgray'
  13881. * },
  13882. * polar: {
  13883. * background: 'green'
  13884. * }
  13885. * }
  13886. *
  13887. * The values from the chart.defaults and chart.*type* configs (where *type* is a valid
  13888. * chart xtype, e.g. '{@link Ext.chart.CartesianChart cartesian}' or '{@link Ext.chart.PolarChart polar}')
  13889. * will be applied to corresponding chart configs.
  13890. * E.g., the chart.defaults.background config will set the {@link Ext.chart.AbstractChart#background}
  13891. * config of all charts, where the chart.cartesian.flipXY config will only set the
  13892. * {@link Ext.chart.CartesianChart#flipXY} config of all cartesian charts.
  13893. */
  13894. chart: {
  13895. defaults: {
  13896. captions: {
  13897. title: {
  13898. docked: 'top',
  13899. padding: 5,
  13900. style: {
  13901. textAlign: 'center',
  13902. fontFamily: 'default',
  13903. fontWeight: '500',
  13904. fillStyle: 'black',
  13905. fontSize: 'default*1.6'
  13906. }
  13907. },
  13908. subtitle: {
  13909. docked: 'top',
  13910. style: {
  13911. textAlign: 'center',
  13912. fontFamily: 'default',
  13913. fontWeight: 'normal',
  13914. fillStyle: 'black',
  13915. fontSize: 'default*1.3'
  13916. }
  13917. },
  13918. credits: {
  13919. docked: 'bottom',
  13920. padding: 5,
  13921. style: {
  13922. textAlign: 'left',
  13923. fontFamily: 'default',
  13924. fontWeight: 'lighter',
  13925. fillStyle: 'black',
  13926. fontSize: 'default'
  13927. }
  13928. }
  13929. },
  13930. background: 'white'
  13931. }
  13932. },
  13933. /**
  13934. * @cfg {Object} axis
  13935. * Theme defaults for the axes.
  13936. * Can apply to all axes or only axes with a specific position.
  13937. * For example:
  13938. *
  13939. * axis: {
  13940. * defaults: {
  13941. * style: {strokeStyle: 'red'}
  13942. * },
  13943. * left: {
  13944. * title: {fillStyle: 'green'}
  13945. * }
  13946. * }
  13947. *
  13948. * The values from the axis.defaults and axis.*position* configs (where *position*
  13949. * is a valid axis {@link Ext.chart.axis.Axis#position}, e.g. 'bottom') will be
  13950. * applied to corresponding {@link Ext.chart.axis.Axis axis} configs.
  13951. * E.g., the axis.defaults.label config will apply to the {@link Ext.chart.axis.Axis#label}
  13952. * config of all axes, where the axis.left.titleMargin config will only apply to the
  13953. * {@link Ext.chart.axis.Axis#titleMargin} config of all axes positioned to the left.
  13954. */
  13955. axis: {
  13956. defaults: {
  13957. label: {
  13958. x: 0,
  13959. y: 0,
  13960. textBaseline: 'middle',
  13961. textAlign: 'center',
  13962. fontSize: 'default',
  13963. fontFamily: 'default',
  13964. fontWeight: 'default',
  13965. fillStyle: 'black'
  13966. },
  13967. title: {
  13968. fillStyle: 'black',
  13969. fontSize: 'default*1.23',
  13970. fontFamily: 'default',
  13971. fontWeight: 'default'
  13972. },
  13973. style: {
  13974. strokeStyle: 'black'
  13975. },
  13976. grid: {
  13977. strokeStyle: 'rgb(221, 221, 221)'
  13978. }
  13979. },
  13980. top: {
  13981. style: {
  13982. textPadding: 5
  13983. }
  13984. },
  13985. bottom: {
  13986. style: {
  13987. textPadding: 5
  13988. }
  13989. }
  13990. },
  13991. /**
  13992. * @cfg {Object} series
  13993. * Theme defaults for the series.
  13994. * Can apply to all series or just a specific type of series.
  13995. * For example:
  13996. *
  13997. * series: {
  13998. * defaults: {
  13999. * style: {
  14000. * lineWidth: 2
  14001. * }
  14002. * },
  14003. * bar: {
  14004. * animation: {
  14005. * easing: 'bounceOut',
  14006. * duration: 1000
  14007. * }
  14008. * }
  14009. * }
  14010. *
  14011. * The values from the series.defaults and series.*type* configs (where *type*
  14012. * is a valid series {@link Ext.chart.series.Series#type}, e.g. 'line') will be
  14013. * applied to corresponding series configs.
  14014. * E.g., the series.defaults.label config will apply to the {@link Ext.chart.series.Series#label}
  14015. * config of all series, where the series.line.step config will only apply to the
  14016. * {@link Ext.chart.series.Line#step} config of {@link Ext.chart.series.Line line} series.
  14017. */
  14018. series: {
  14019. defaults: {
  14020. label: {
  14021. fillStyle: 'black',
  14022. strokeStyle: 'none',
  14023. fontFamily: 'default',
  14024. fontWeight: 'default',
  14025. fontSize: 'default*1.077',
  14026. textBaseline: 'middle',
  14027. textAlign: 'center'
  14028. },
  14029. labelOverflowPadding: 5
  14030. }
  14031. },
  14032. /**
  14033. * @cfg {Object} sprites
  14034. * Default style for the custom chart sprites by type.
  14035. * For example:
  14036. *
  14037. * sprites: {
  14038. * text: {
  14039. * fontWeight: 300
  14040. * }
  14041. * }
  14042. *
  14043. * These sprite attribute overrides will apply to custom sprites of all charts
  14044. * specified using the {@link Ext.draw.Container#sprites} config.
  14045. * The overrides are specified by sprite type, e.g. sprites.text config
  14046. * tells to apply given attributes to all {@link Ext.draw.sprite.Text text} sprites.
  14047. */
  14048. sprites: {
  14049. text: {
  14050. fontSize: 'default',
  14051. fontWeight: 'default',
  14052. fontFamily: 'default',
  14053. fillStyle: 'black'
  14054. }
  14055. },
  14056. /**
  14057. * Style information for the {Ext.chart.legend.SpriteLegend sprite legend}.
  14058. * If the {@link Ext.chart.legend.Legend DOM} legend is used, this config is ignored.
  14059. * For additional details see {@link Ext.chart.AbstractChart#legend}.
  14060. * @cfg {Object} legend
  14061. * @cfg {Ext.chart.legend.sprite.Item} legend.item
  14062. * @cfg {Object} legend.border See {@link Ext.chart.legend.SpriteLegend#border}.
  14063. */
  14064. legend: {
  14065. label: {
  14066. fontSize: 14,
  14067. fontWeight: 'default',
  14068. fontFamily: 'default',
  14069. fillStyle: 'black'
  14070. },
  14071. border: {
  14072. lineWidth: 1,
  14073. radius: 4,
  14074. fillStyle: 'none',
  14075. strokeStyle: 'gray'
  14076. },
  14077. background: 'white'
  14078. },
  14079. /**
  14080. * @private
  14081. * An object with the following structure:
  14082. * {
  14083. * fillStyle: [color, color, ...],
  14084. * strokeStyle: [color, color, ...],
  14085. * ...
  14086. * }
  14087. * If missing, generated from the other configs: 'baseColor, 'gradients', 'colors'.
  14088. */
  14089. seriesThemes: undefined,
  14090. markerThemes: {
  14091. type: [
  14092. 'circle',
  14093. 'cross',
  14094. 'plus',
  14095. 'square',
  14096. 'triangle',
  14097. 'diamond'
  14098. ]
  14099. },
  14100. /**
  14101. * @deprecated 5.0.1 Use the {@link Ext.draw.Container#gradients} config instead.
  14102. */
  14103. useGradients: false,
  14104. /**
  14105. * @deprecated 5.0.1 Use the {@link Ext.chart.AbstractChart#background} config instead.
  14106. */
  14107. background: null
  14108. },
  14109. colorDefaults: [
  14110. '#94ae0a',
  14111. '#115fa6',
  14112. '#a61120',
  14113. '#ff8809',
  14114. '#ffd13e',
  14115. '#a61187',
  14116. '#24ad9a',
  14117. '#7c7474',
  14118. '#a66111'
  14119. ],
  14120. constructor: function(config) {
  14121. this.initConfig(config);
  14122. this.resolveDefaults();
  14123. },
  14124. defaultRegEx: /^default([+\-/\*]\d+(?:\.\d+)?)?$/,
  14125. defaultOperators: {
  14126. '*': function(v1, v2) {
  14127. return v1 * v2;
  14128. },
  14129. '+': function(v1, v2) {
  14130. return v1 + v2;
  14131. },
  14132. '-': function(v1, v2) {
  14133. return v1 - v2;
  14134. }
  14135. },
  14136. resolveChartDefaults: function() {
  14137. var chart = Ext.clone(this.getChart()),
  14138. chartType, captionName, chartConfig, captionConfig;
  14139. for (chartType in chart) {
  14140. chartConfig = chart[chartType];
  14141. if ('captions' in chartConfig) {
  14142. for (captionName in chartConfig.captions) {
  14143. captionConfig = chartConfig.captions[captionName];
  14144. if (captionConfig) {
  14145. this.replaceDefaults(captionConfig.style);
  14146. }
  14147. }
  14148. }
  14149. }
  14150. this.setChart(chart);
  14151. },
  14152. resolveDefaults: function() {
  14153. var me = this;
  14154. Ext.onInternalReady(function() {
  14155. var sprites = Ext.clone(me.getSprites()),
  14156. legend = Ext.clone(me.getLegend()),
  14157. axis = Ext.clone(me.getAxis()),
  14158. series = Ext.clone(me.getSeries()),
  14159. div, key, config;
  14160. if (!me.superclass.defaults) {
  14161. div = Ext.getBody().createChild({
  14162. tag: 'div',
  14163. cls: me.defaultsDivCls
  14164. });
  14165. me.superclass.defaults = {
  14166. fontFamily: div.getStyle('fontFamily'),
  14167. fontWeight: div.getStyle('fontWeight'),
  14168. fontSize: parseFloat(div.getStyle('fontSize')),
  14169. fontVariant: div.getStyle('fontVariant'),
  14170. fontStyle: div.getStyle('fontStyle')
  14171. };
  14172. div.destroy();
  14173. }
  14174. me.resolveChartDefaults();
  14175. me.replaceDefaults(sprites.text);
  14176. me.setSprites(sprites);
  14177. me.replaceDefaults(legend.label);
  14178. me.setLegend(legend);
  14179. for (key in axis) {
  14180. config = axis[key];
  14181. me.replaceDefaults(config.label);
  14182. me.replaceDefaults(config.title);
  14183. }
  14184. me.setAxis(axis);
  14185. for (key in series) {
  14186. config = series[key];
  14187. me.replaceDefaults(config.label);
  14188. }
  14189. me.setSeries(series);
  14190. });
  14191. },
  14192. replaceDefaults: function(target) {
  14193. var me = this,
  14194. defaults = me.superclass.defaults,
  14195. defaultRegEx = me.defaultRegEx,
  14196. key, value, match, binaryFn;
  14197. if (Ext.isObject(target)) {
  14198. for (key in defaults) {
  14199. match = defaultRegEx.exec(target[key]);
  14200. if (match) {
  14201. value = defaults[key];
  14202. match = match[1];
  14203. if (match) {
  14204. binaryFn = me.defaultOperators[match.charAt(0)];
  14205. value = Math.round(binaryFn(value, parseFloat(match.substr(1))));
  14206. }
  14207. target[key] = value;
  14208. }
  14209. }
  14210. }
  14211. },
  14212. applyBaseColor: function(baseColor) {
  14213. var midColor, midL;
  14214. if (baseColor) {
  14215. midColor = baseColor.isColor ? baseColor : Ext.util.Color.fromString(baseColor);
  14216. midL = midColor.getHSL()[2];
  14217. if (midL < 0.15) {
  14218. midColor = midColor.createLighter(0.3);
  14219. } else if (midL < 0.3) {
  14220. midColor = midColor.createLighter(0.15);
  14221. } else if (midL > 0.85) {
  14222. midColor = midColor.createDarker(0.3);
  14223. } else if (midL > 0.7) {
  14224. midColor = midColor.createDarker(0.15);
  14225. }
  14226. this.setColors([
  14227. midColor.createDarker(0.3).toString(),
  14228. midColor.createDarker(0.15).toString(),
  14229. midColor.toString(),
  14230. midColor.createLighter(0.12).toString(),
  14231. midColor.createLighter(0.24).toString(),
  14232. midColor.createLighter(0.31).toString()
  14233. ]);
  14234. }
  14235. return baseColor;
  14236. },
  14237. applyColors: function(newColors) {
  14238. return newColors || this.colorDefaults;
  14239. },
  14240. updateUseGradients: function(useGradients) {
  14241. if (useGradients) {
  14242. this.updateGradients({
  14243. type: 'linear',
  14244. degrees: 90
  14245. });
  14246. }
  14247. },
  14248. updateBackground: function(background) {
  14249. if (background) {
  14250. var chart = this.getChart();
  14251. chart.defaults.background = background;
  14252. this.setChart(chart);
  14253. }
  14254. },
  14255. updateGradients: function(gradients) {
  14256. var colors = this.getColors(),
  14257. items = [],
  14258. gradient, midColor, color, i, ln;
  14259. if (Ext.isObject(gradients)) {
  14260. for (i = 0 , ln = colors && colors.length || 0; i < ln; i++) {
  14261. midColor = Ext.util.Color.fromString(colors[i]);
  14262. if (midColor) {
  14263. color = midColor.createLighter(0.15).toString();
  14264. gradient = Ext.apply(Ext.Object.chain(gradients), {
  14265. stops: [
  14266. {
  14267. offset: 1,
  14268. color: midColor.toString()
  14269. },
  14270. {
  14271. offset: 0,
  14272. color: color.toString()
  14273. }
  14274. ]
  14275. });
  14276. items.push(gradient);
  14277. }
  14278. }
  14279. this.setColors(items);
  14280. }
  14281. },
  14282. applySeriesThemes: function(newSeriesThemes) {
  14283. // Init the 'colors' config with solid colors generated from the 'baseColor'.
  14284. this.getBaseColor();
  14285. // Init the 'gradients' config with a hardcoded value, if the legacy 'useGradients'
  14286. // config was set to 'true'. This in turn updates the 'colors' config.
  14287. this.getUseGradients();
  14288. // Init the 'gradients' config normally. This also updates the 'colors' config.
  14289. this.getGradients();
  14290. var colors = this.getColors();
  14291. // Final colors.
  14292. if (!newSeriesThemes) {
  14293. newSeriesThemes = {
  14294. fillStyle: Ext.Array.clone(colors),
  14295. strokeStyle: Ext.Array.map(colors, function(value) {
  14296. var color = Ext.util.Color.fromString(value.stops ? value.stops[0].color : value);
  14297. return color.createDarker(0.15).toString();
  14298. })
  14299. };
  14300. }
  14301. return newSeriesThemes;
  14302. }
  14303. });
  14304. /**
  14305. * @private
  14306. */
  14307. Ext.define('Ext.chart.theme.Default', {
  14308. extend: 'Ext.chart.theme.Base',
  14309. singleton: true,
  14310. alias: [
  14311. 'chart.theme.default',
  14312. 'chart.theme.Default',
  14313. 'chart.theme.Base'
  14314. ]
  14315. });
  14316. /**
  14317. * @private
  14318. */
  14319. Ext.define('Ext.chart.Util', {
  14320. singleton: true,
  14321. /**
  14322. * @private
  14323. * Given a `range` array of two (min/max) numbers and an arbitrary array of numbers
  14324. * (`data`), updates the given `range`, if the range of `data` exceeds it.
  14325. * Typically, one would start with the `[NaN, NaN]` range array instance, call the
  14326. * method on multiple datasets with that range instance, then validate it with
  14327. * {@link #validateRange}.
  14328. * @param {Number[]} range
  14329. * @param {Number[]} data
  14330. */
  14331. expandRange: function(range, data) {
  14332. var length = data.length,
  14333. min = range[0],
  14334. max = range[1],
  14335. i, value;
  14336. for (i = 0; i < length; i++) {
  14337. value = data[i];
  14338. // `null` is a "finite" number in JavaScript
  14339. // and greater than any negative number.
  14340. if (value == null || !isFinite(value)) {
  14341. continue;
  14342. }
  14343. if (value < min || !isFinite(min)) {
  14344. min = value;
  14345. }
  14346. if (value > max || !isFinite(max)) {
  14347. max = value;
  14348. }
  14349. }
  14350. range[0] = min;
  14351. range[1] = max;
  14352. },
  14353. defaultRange: [
  14354. 0,
  14355. 1
  14356. ],
  14357. /**
  14358. * @private
  14359. * Makes sure the range exists, is not zero, and its min/max values are finite numbers.
  14360. * If this is not the case, the values from the provided `defaultRange`
  14361. * are used.
  14362. *
  14363. * The range to validate. Never modified.
  14364. * @param {Number[]} range
  14365. * The default range to use, if the given range is not a valid data structure,
  14366. * if both values are infinities, or if both values are the same and dangerously
  14367. * close to either infinity (which makes expansion of the range by the value of
  14368. * `padding` impossible).
  14369. * If only a single value is infinity, the other value will be derived
  14370. * from the finite value by incrementing/decrementing it by the span
  14371. * of the default range towards the infinity.
  14372. * For example, if the `defaultRange` is `[0, 1]`, we have:
  14373. *
  14374. * [5, Infinity] --> [5, 6]
  14375. * [3, -Infinity] --> [2, 3]
  14376. * [-Infinity, -5] --> [-6, -5]
  14377. * [-3, -Infinity] --> [-4, -3]
  14378. *
  14379. * @param {Number[]} [defaultRange=[0, 1]]
  14380. * A non-negative padding to use in case of identical min/max.
  14381. * Note that the range span is not guaranteed to be `padding * 2` in this case,
  14382. * if min/max are close to MIN_SAFE_INTEGER/MAX_SAFE_INTEGER.
  14383. * @param {Number} [padding=0.5]
  14384. * @return {Number[]}
  14385. */
  14386. validateRange: function(range, defaultRange, padding) {
  14387. defaultRange = defaultRange || this.defaultRange.slice();
  14388. if (!(padding === 0 || padding > 0)) {
  14389. padding = 0.5;
  14390. }
  14391. if (!range || range.length !== 2) {
  14392. return defaultRange;
  14393. }
  14394. range = [
  14395. range[0],
  14396. range[1]
  14397. ];
  14398. if (!range[0]) {
  14399. range[0] = 0;
  14400. }
  14401. if (!range[1]) {
  14402. range[1] = 0;
  14403. }
  14404. if (padding && range[0] === range[1]) {
  14405. range = [
  14406. range[0] - padding,
  14407. range[0] + padding
  14408. ];
  14409. // In case the range values are at Infinity, the expansion above by the value
  14410. // of 'padding' won't do us much good, so we still have to fall back to the
  14411. // 'defaultRange'.
  14412. if (range[0] === range[1]) {
  14413. return defaultRange;
  14414. }
  14415. }
  14416. // Same sign infinities are ruled out at this point.
  14417. var isFin0 = isFinite(range[0]);
  14418. var isFin1 = isFinite(range[1]);
  14419. if (!isFin0 && !isFin1) {
  14420. return defaultRange;
  14421. }
  14422. // Different sign infinities are ruled out at this point.
  14423. if (isFin0 && !isFin1) {
  14424. range[1] = range[0] + Ext.Number.sign(range[1]) * (defaultRange[1] - defaultRange[0]);
  14425. } else if (isFin1 && !isFin0) {
  14426. range[0] = range[1] + Ext.Number.sign(range[0]) * (defaultRange[1] - defaultRange[0]);
  14427. }
  14428. // All infinities are ruled out at this point.
  14429. return [
  14430. Math.min(range[0], range[1]),
  14431. Math.max(range[0], range[1])
  14432. ];
  14433. },
  14434. applyAnimation: function(animation, oldAnimation) {
  14435. if (!animation) {
  14436. animation = {
  14437. duration: 0
  14438. };
  14439. } else if (animation === true) {
  14440. animation = {
  14441. easing: 'easeInOut',
  14442. duration: 500
  14443. };
  14444. }
  14445. return oldAnimation ? Ext.apply({}, animation, oldAnimation) : animation;
  14446. }
  14447. });
  14448. /**
  14449. * @class Ext.chart.Markers
  14450. * @extends Ext.draw.sprite.Instancing
  14451. *
  14452. * Marker sprite. A specialized version of instancing sprite that groups instances.
  14453. * Putting a marker is grouped by its category id. Clearing removes that category.
  14454. */
  14455. Ext.define('Ext.chart.Markers', {
  14456. extend: 'Ext.draw.sprite.Instancing',
  14457. isMarkers: true,
  14458. defaultCategory: 'default',
  14459. constructor: function() {
  14460. this.callParent(arguments);
  14461. // `categories` maps category names to a map that maps instance index in category to its global index:
  14462. // categoryName: {instanceIndexInCategory: globalInstanceIndex}
  14463. this.categories = {};
  14464. // The `revisions` map keeps revision numbers of instance categories.
  14465. // When a marker (instance) is put (created or updated), it gets the revision
  14466. // of the category. When a category is cleared, its revision is incremented,
  14467. // but its instances are not removed.
  14468. // An instance is only rendered if its revision matches category revision.
  14469. // In other words, a marker has to be put again after its category has been cleared
  14470. // or it won't render.
  14471. this.revisions = {};
  14472. },
  14473. destroy: function() {
  14474. this.categories = null;
  14475. this.revisions = null;
  14476. this.callParent();
  14477. },
  14478. getMarkerFor: function(category, index) {
  14479. if (category in this.categories) {
  14480. var categoryInstances = this.categories[category];
  14481. if (index in categoryInstances) {
  14482. return this.get(categoryInstances[index]);
  14483. }
  14484. }
  14485. },
  14486. /**
  14487. * Clears the markers in the category.
  14488. * @param {String} category
  14489. */
  14490. clear: function(category) {
  14491. category = category || this.defaultCategory;
  14492. if (!(category in this.revisions)) {
  14493. this.revisions[category] = 1;
  14494. } else {
  14495. this.revisions[category]++;
  14496. }
  14497. },
  14498. clearAll: function() {
  14499. this.callParent();
  14500. this.categories = {};
  14501. this.revisions = {};
  14502. },
  14503. /**
  14504. * Puts a marker in the category with additional attributes.
  14505. * @param {String} category
  14506. * @param {Object} attr
  14507. * @param {String|Number} index
  14508. * @param {Boolean} [bypassNormalization]
  14509. * @param {Boolean} [keepRevision]
  14510. */
  14511. putMarkerFor: function(category, attr, index, bypassNormalization, keepRevision) {
  14512. category = category || this.defaultCategory;
  14513. var me = this,
  14514. categoryInstances = me.categories[category] || (me.categories[category] = {}),
  14515. instance;
  14516. if (index in categoryInstances) {
  14517. me.setAttributesFor(categoryInstances[index], attr, bypassNormalization);
  14518. } else {
  14519. categoryInstances[index] = me.getCount();
  14520. // get the index of the instance created on next line
  14521. me.add(attr, bypassNormalization);
  14522. }
  14523. instance = me.get(categoryInstances[index]);
  14524. if (instance) {
  14525. instance.category = category;
  14526. if (!keepRevision) {
  14527. instance.revision = me.revisions[category] || (me.revisions[category] = 1);
  14528. }
  14529. }
  14530. },
  14531. /**
  14532. *
  14533. * @param {String} category
  14534. * @param {Mixed} index
  14535. * @param {Boolean} [isWithoutTransform]
  14536. */
  14537. getMarkerBBoxFor: function(category, index, isWithoutTransform) {
  14538. if (category in this.categories) {
  14539. var categoryInstances = this.categories[category];
  14540. if (index in categoryInstances) {
  14541. return this.getBBoxFor(categoryInstances[index], isWithoutTransform);
  14542. }
  14543. }
  14544. },
  14545. getBBox: function() {
  14546. return null;
  14547. },
  14548. render: function(surface, ctx, rect) {
  14549. var me = this,
  14550. surfaceRect = surface.getRect(),
  14551. revisions = me.revisions,
  14552. mat = me.attr.matrix,
  14553. template = me.getTemplate(),
  14554. templateAttr = template.attr,
  14555. ln = me.instances.length,
  14556. instance, i;
  14557. mat.toContext(ctx);
  14558. template.preRender(surface, ctx, rect);
  14559. template.useAttributes(ctx, surfaceRect);
  14560. for (i = 0; i < ln; i++) {
  14561. instance = me.get(i);
  14562. if (instance.hidden || instance.revision !== revisions[instance.category]) {
  14563. continue;
  14564. }
  14565. ctx.save();
  14566. template.attr = instance;
  14567. template.useAttributes(ctx, surfaceRect);
  14568. template.render(surface, ctx, rect);
  14569. ctx.restore();
  14570. }
  14571. template.attr = templateAttr;
  14572. }
  14573. });
  14574. /**
  14575. * This is a modifier to place labels and callouts by additional attributes.
  14576. */
  14577. Ext.define('Ext.chart.modifier.Callout', {
  14578. extend: 'Ext.draw.modifier.Modifier',
  14579. alternateClassName: 'Ext.chart.label.Callout',
  14580. prepareAttributes: function(attr) {
  14581. if (!attr.hasOwnProperty('calloutOriginal')) {
  14582. attr.calloutOriginal = Ext.Object.chain(attr);
  14583. // No __proto__, nor getPrototypeOf in IE8,
  14584. // so manually saving a reference to 'attr' after chaining.
  14585. attr.calloutOriginal.prototype = attr;
  14586. }
  14587. if (this._lower) {
  14588. this._lower.prepareAttributes(attr.calloutOriginal);
  14589. }
  14590. },
  14591. setAttrs: function(attr, changes) {
  14592. var callout = attr.callout,
  14593. origin = attr.calloutOriginal,
  14594. bbox = attr.bbox.plain,
  14595. width = (bbox.width || 0) + attr.labelOverflowPadding,
  14596. height = (bbox.height || 0) + attr.labelOverflowPadding,
  14597. dx, dy;
  14598. if ('callout' in changes) {
  14599. callout = changes.callout;
  14600. }
  14601. if ('callout' in changes || 'calloutPlaceX' in changes || 'calloutPlaceY' in changes || 'x' in changes || 'y' in changes) {
  14602. var rotationRads = 'rotationRads' in changes ? origin.rotationRads = changes.rotationRads : origin.rotationRads,
  14603. x = 'x' in changes ? (origin.x = changes.x) : origin.x,
  14604. y = 'y' in changes ? (origin.y = changes.y) : origin.y,
  14605. calloutPlaceX = 'calloutPlaceX' in changes ? changes.calloutPlaceX : attr.calloutPlaceX,
  14606. calloutPlaceY = 'calloutPlaceY' in changes ? changes.calloutPlaceY : attr.calloutPlaceY,
  14607. calloutVertical = 'calloutVertical' in changes ? changes.calloutVertical : attr.calloutVertical,
  14608. temp;
  14609. // Normalize Rotations
  14610. rotationRads %= Math.PI * 2;
  14611. if (Math.cos(rotationRads) < 0) {
  14612. rotationRads = (rotationRads + Math.PI) % (Math.PI * 2);
  14613. }
  14614. if (rotationRads > Math.PI) {
  14615. rotationRads -= Math.PI * 2;
  14616. }
  14617. if (calloutVertical) {
  14618. rotationRads = rotationRads * (1 - callout) - Math.PI / 2 * callout;
  14619. temp = width;
  14620. width = height;
  14621. height = temp;
  14622. } else {
  14623. rotationRads = rotationRads * (1 - callout);
  14624. }
  14625. changes.rotationRads = rotationRads;
  14626. // Placing a label in the middle of a pie slice (x/y)
  14627. // if callout doesn't exists (callout=0),
  14628. // or outside the pie slice (calloutPlaceX/Y) if it does (callout=1).
  14629. changes.x = x * (1 - callout) + calloutPlaceX * callout;
  14630. changes.y = y * (1 - callout) + calloutPlaceY * callout;
  14631. dx = calloutPlaceX - x;
  14632. dy = calloutPlaceY - y;
  14633. // Finding where the callout line intersects the bbox of the label
  14634. // if it were to go to the center of the label,
  14635. // and make that intersection point the end of the callout line.
  14636. // Effectively, the end of the callout line traces label's bbox when chart is rotated.
  14637. if (Math.abs(dy * width) > Math.abs(dx * height)) {
  14638. // on top/bottom
  14639. if (dy > 0) {
  14640. changes.calloutEndX = changes.x - (height / 2) * (dx / dy) * callout;
  14641. changes.calloutEndY = changes.y - (height / 2) * callout;
  14642. } else {
  14643. changes.calloutEndX = changes.x + (height / 2) * (dx / dy) * callout;
  14644. changes.calloutEndY = changes.y + (height / 2) * callout;
  14645. }
  14646. } else {
  14647. // on left/right
  14648. if (dx > 0) {
  14649. changes.calloutEndX = changes.x - width / 2;
  14650. changes.calloutEndY = changes.y - (width / 2) * (dy / dx) * callout;
  14651. } else {
  14652. changes.calloutEndX = changes.x + width / 2;
  14653. changes.calloutEndY = changes.y + (width / 2) * (dy / dx) * callout;
  14654. }
  14655. }
  14656. // Since the length of the callout line is adjusted depending on the label's position
  14657. // and dimensions, we hide the callout line if the length becomes negative.
  14658. if (changes.calloutStartX && changes.calloutStartY) {
  14659. 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);
  14660. } else {
  14661. changes.calloutHasLine = true;
  14662. }
  14663. }
  14664. return changes;
  14665. },
  14666. pushDown: function(attr, changes) {
  14667. changes = this.callParent([
  14668. attr.calloutOriginal,
  14669. changes
  14670. ]);
  14671. return this.setAttrs(attr, changes);
  14672. },
  14673. popUp: function(attr, changes) {
  14674. attr = attr.prototype;
  14675. changes = this.setAttrs(attr, changes);
  14676. if (this._upper) {
  14677. return this._upper.popUp(attr, changes);
  14678. } else {
  14679. return Ext.apply(attr, changes);
  14680. }
  14681. }
  14682. });
  14683. /**
  14684. * @class Ext.chart.sprite.Label
  14685. * @extends Ext.draw.sprite.Text
  14686. *
  14687. * Sprite used to represent labels in series.
  14688. *
  14689. * Important: the actual default values are determined by the theme used.
  14690. * Please see the `label` config of the {@link Ext.chart.theme.Base#axis}.
  14691. */
  14692. Ext.define('Ext.chart.sprite.Label', {
  14693. extend: 'Ext.draw.sprite.Text',
  14694. alternateClassName: 'Ext.chart.label.Label',
  14695. requires: [
  14696. 'Ext.chart.modifier.Callout'
  14697. ],
  14698. inheritableStatics: {
  14699. def: {
  14700. processors: {
  14701. callout: 'limited01',
  14702. // Meant to be set by the Callout modifier only.
  14703. calloutHasLine: 'bool',
  14704. // The position where the callout would end, if not for the label:
  14705. // callout stops at the bounding box of the label,
  14706. // so the actual point where the callout ends - calloutEndX/Y -
  14707. // is calculated by the Callout modifier.
  14708. calloutPlaceX: 'number',
  14709. calloutPlaceY: 'number',
  14710. // The start/end points used to render the callout line.
  14711. calloutStartX: 'number',
  14712. calloutStartY: 'number',
  14713. calloutEndX: 'number',
  14714. calloutEndY: 'number',
  14715. calloutColor: 'color',
  14716. calloutWidth: 'number',
  14717. calloutVertical: 'bool',
  14718. labelOverflowPadding: 'number',
  14719. display: 'enums(none,under,over,rotate,insideStart,insideEnd,inside,outside)',
  14720. orientation: 'enums(horizontal,vertical)',
  14721. renderer: 'default'
  14722. },
  14723. defaults: {
  14724. callout: 0,
  14725. calloutHasLine: true,
  14726. calloutPlaceX: 0,
  14727. calloutPlaceY: 0,
  14728. calloutStartX: 0,
  14729. calloutStartY: 0,
  14730. calloutEndX: 0,
  14731. calloutEndY: 0,
  14732. calloutWidth: 1,
  14733. calloutVertical: false,
  14734. calloutColor: 'black',
  14735. labelOverflowPadding: 5,
  14736. display: 'none',
  14737. orientation: '',
  14738. renderer: null
  14739. },
  14740. triggers: {
  14741. callout: 'transform',
  14742. calloutPlaceX: 'transform',
  14743. calloutPlaceY: 'transform',
  14744. labelOverflowPadding: 'transform',
  14745. calloutRotation: 'transform',
  14746. display: 'hidden'
  14747. },
  14748. updaters: {
  14749. hidden: function(attr) {
  14750. attr.hidden = (attr.display === 'none');
  14751. }
  14752. }
  14753. }
  14754. },
  14755. config: {
  14756. /**
  14757. * @cfg {Object} fx Animation configuration.
  14758. */
  14759. animation: {
  14760. customDurations: {
  14761. callout: 200
  14762. }
  14763. },
  14764. /**
  14765. * @cfg {String} field The store record field used by the label sprite.
  14766. *
  14767. * Note: the label sprite is typically used indirectly (by a Ext.chart.MarkerHolder
  14768. * series sprite, via a Ext.chart.Markers sprite, where the latter is passed to the
  14769. * label renderer), so to get to the label field one has to do:
  14770. *
  14771. * renderer: function (text, sprite, config, data, index) {
  14772. * var field = sprite.getTemplate().getField();
  14773. * }
  14774. *
  14775. * To get the actual label sprite instance one can use:
  14776. *
  14777. * sprite.get(index)
  14778. *
  14779. */
  14780. field: null,
  14781. /**
  14782. * @cfg {Boolean|Object} calloutLine
  14783. *
  14784. * True to draw a line between the label and the chart with the default settings,
  14785. * or an Object that defines the 'color', 'width' and 'length' properties of the line.
  14786. * This config is only applicable when the label is displayed outside the chart.
  14787. *
  14788. * Default value: false.
  14789. */
  14790. calloutLine: true,
  14791. /**
  14792. * @cfg {Number} [hideLessThan=20]
  14793. * Hides labels for pie slices with segment length less than this value (in pixels).
  14794. */
  14795. hideLessThan: 20
  14796. },
  14797. applyCalloutLine: function(calloutLine) {
  14798. if (calloutLine) {
  14799. return Ext.apply({}, calloutLine);
  14800. }
  14801. },
  14802. createModifiers: function() {
  14803. var me = this,
  14804. mods = me.callParent(arguments);
  14805. mods.callout = new Ext.chart.modifier.Callout({
  14806. sprite: me
  14807. });
  14808. mods.animation.setUpper(mods.callout);
  14809. mods.callout.setUpper(mods.target);
  14810. },
  14811. render: function(surface, ctx) {
  14812. var me = this,
  14813. attr = me.attr,
  14814. calloutColor = attr.calloutColor;
  14815. ctx.save();
  14816. ctx.globalAlpha *= attr.callout;
  14817. if (ctx.globalAlpha > 0 && attr.calloutHasLine) {
  14818. if (calloutColor && calloutColor.isGradient) {
  14819. calloutColor = calloutColor.getStops()[0].color;
  14820. }
  14821. ctx.strokeStyle = calloutColor;
  14822. ctx.fillStyle = calloutColor;
  14823. ctx.lineWidth = attr.calloutWidth;
  14824. ctx.beginPath();
  14825. ctx.moveTo(me.attr.calloutStartX, me.attr.calloutStartY);
  14826. ctx.lineTo(me.attr.calloutEndX, me.attr.calloutEndY);
  14827. ctx.stroke();
  14828. ctx.beginPath();
  14829. ctx.arc(me.attr.calloutStartX, me.attr.calloutStartY, 1 * attr.calloutWidth, 0, 2 * Math.PI, true);
  14830. ctx.fill();
  14831. ctx.beginPath();
  14832. ctx.arc(me.attr.calloutEndX, me.attr.calloutEndY, 1 * attr.calloutWidth, 0, 2 * Math.PI, true);
  14833. ctx.fill();
  14834. }
  14835. ctx.restore();
  14836. Ext.draw.sprite.Text.prototype.render.apply(me, arguments);
  14837. }
  14838. });
  14839. /**
  14840. * Series is the abstract class containing the common logic to all chart series.
  14841. * Series includes methods from Labels, Highlights, and Callouts mixins. This class
  14842. * implements the logic of animating, hiding, showing all elements and returning the
  14843. * color of the series to be used as a legend item.
  14844. *
  14845. * ## Listeners
  14846. *
  14847. * The series class supports listeners via the Observable syntax.
  14848. *
  14849. * For example:
  14850. *
  14851. * Ext.create('Ext.chart.CartesianChart', {
  14852. * plugins: {
  14853. * chartitemevents: {
  14854. * moveEvents: true
  14855. * }
  14856. * },
  14857. * store: {
  14858. * fields: ['pet', 'households', 'total'],
  14859. * data: [
  14860. * {pet: 'Cats', households: 38, total: 93},
  14861. * {pet: 'Dogs', households: 45, total: 79},
  14862. * {pet: 'Fish', households: 13, total: 171}
  14863. * ]
  14864. * },
  14865. * axes: [{
  14866. * type: 'numeric',
  14867. * position: 'left'
  14868. * }, {
  14869. * type: 'category',
  14870. * position: 'bottom'
  14871. * }],
  14872. * series: [{
  14873. * type: 'bar',
  14874. * xField: 'pet',
  14875. * yField: 'households',
  14876. * listeners: {
  14877. * itemmousemove: function (series, item, event) {
  14878. * console.log('itemmousemove', item.category, item.field);
  14879. * }
  14880. * }
  14881. * }, {
  14882. * type: 'line',
  14883. * xField: 'pet',
  14884. * yField: 'total',
  14885. * marker: true
  14886. * }]
  14887. * });
  14888. *
  14889. */
  14890. Ext.define('Ext.chart.series.Series', {
  14891. requires: [
  14892. 'Ext.chart.Util',
  14893. 'Ext.chart.Markers',
  14894. 'Ext.chart.sprite.Label',
  14895. 'Ext.tip.ToolTip'
  14896. ],
  14897. mixins: [
  14898. 'Ext.mixin.Observable',
  14899. 'Ext.mixin.Bindable'
  14900. ],
  14901. isSeries: true,
  14902. defaultBindProperty: 'store',
  14903. /**
  14904. * @property {String} type
  14905. * The type of series. Set in subclasses.
  14906. * @protected
  14907. */
  14908. type: null,
  14909. /**
  14910. * @property {String} seriesType
  14911. * Default series sprite type.
  14912. */
  14913. seriesType: 'sprite',
  14914. identifiablePrefix: 'ext-line-',
  14915. observableType: 'series',
  14916. darkerStrokeRatio: 0.15,
  14917. /**
  14918. * @event itemmousemove
  14919. * Fires when the mouse is moved on a series item.
  14920. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  14921. * plugin be added to the chart.
  14922. * @param {Ext.chart.series.Series} series
  14923. * @param {Object} item
  14924. * @param {Event} event
  14925. */
  14926. /**
  14927. * @event itemmouseup
  14928. * Fires when a mouseup event occurs on a series item.
  14929. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  14930. * plugin be added to the chart.
  14931. * @param {Ext.chart.series.Series} series
  14932. * @param {Object} item
  14933. * @param {Event} event
  14934. */
  14935. /**
  14936. * @event itemmousedown
  14937. * Fires when a mousedown event occurs on a series item.
  14938. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  14939. * plugin be added to the chart.
  14940. * @param {Ext.chart.series.Series} series
  14941. * @param {Object} item
  14942. * @param {Event} event
  14943. */
  14944. /**
  14945. * @event itemmouseover
  14946. * Fires when the mouse enters a series item.
  14947. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  14948. * plugin be added to the chart.
  14949. * @param {Ext.chart.series.Series} series
  14950. * @param {Object} item
  14951. * @param {Event} event
  14952. */
  14953. /**
  14954. * @event itemmouseout
  14955. * Fires when the mouse exits a series item.
  14956. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  14957. * plugin be added to the chart.
  14958. * @param {Ext.chart.series.Series} series
  14959. * @param {Object} item
  14960. * @param {Event} event
  14961. */
  14962. /**
  14963. * @event itemclick
  14964. * Fires when a click event occurs on a series item.
  14965. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  14966. * plugin be added to the chart.
  14967. * @param {Ext.chart.series.Series} series
  14968. * @param {Object} item
  14969. * @param {Event} event
  14970. */
  14971. /**
  14972. * @event itemdblclick
  14973. * Fires when a double click event occurs on a series item.
  14974. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  14975. * plugin be added to the chart.
  14976. * @param {Ext.chart.series.Series} series
  14977. * @param {Object} item
  14978. * @param {Event} event
  14979. */
  14980. /**
  14981. * @event itemtap
  14982. * Fires when a tap event occurs on a series item.
  14983. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  14984. * plugin be added to the chart.
  14985. * @param {Ext.chart.series.Series} series
  14986. * @param {Object} item
  14987. * @param {Event} event
  14988. */
  14989. /**
  14990. * @event chartattached
  14991. * Fires when the {@link Ext.chart.AbstractChart} has been attached to this series.
  14992. * @param {Ext.chart.AbstractChart} chart
  14993. * @param {Ext.chart.series.Series} series
  14994. */
  14995. /**
  14996. * @event chartdetached
  14997. * Fires when the {@link Ext.chart.AbstractChart} has been detached from this series.
  14998. * @param {Ext.chart.AbstractChart} chart
  14999. * @param {Ext.chart.series.Series} series
  15000. */
  15001. /**
  15002. * @event storechange
  15003. * Fires when the store of the series changes.
  15004. * @param {Ext.chart.series.Series} series
  15005. * @param {Ext.data.Store} newStore
  15006. * @param {Ext.data.Store} oldStore
  15007. */
  15008. config: {
  15009. /**
  15010. * @private
  15011. * @cfg {Object} chart The chart that the series is bound.
  15012. */
  15013. chart: null,
  15014. /**
  15015. * @cfg {String|String[]} title
  15016. * The human-readable name of the series (displayed in the legend).
  15017. * If the series is stacked (has multiple components in it) this
  15018. * should be an array, where each string corresponds to a stacked component.
  15019. */
  15020. title: null,
  15021. /**
  15022. * @cfg {Function} renderer
  15023. * A function that can be provided to set custom styling properties to each
  15024. * rendered element. It receives `(sprite, config, rendererData, index)`
  15025. * as parameters.
  15026. *
  15027. * @param {Object} sprite The sprite affected by the renderer.
  15028. * The visual attributes are in `sprite.attr`.
  15029. * The data field is available in `sprite.getField()`.
  15030. * @param {Object} config The sprite configuration, which varies with the series
  15031. * and the type of sprite. For instance, a Line chart sprite might have just the
  15032. * `x` and `y` properties while a Bar chart sprite also has `width` and `height`.
  15033. * A `type` might be present too. For instance to draw each marker and each segment
  15034. * of a Line chart, the renderer is called with the `config.type` set to either
  15035. * `marker` or `line`.
  15036. * @param {Object} rendererData A record with different properties depending on
  15037. * the type of chart. The only guaranteed property is `rendererData.store`, the
  15038. * store used by the series. In some cases, a store may not exist: for instance
  15039. * a Gauge chart may read its value directly from its configuration; in this case
  15040. * rendererData.store is null and the value is available in rendererData.value.
  15041. * @param {Number} index The index of the sprite. It is usually the index of the
  15042. * store record associated with the sprite, in which case the record can be obtained
  15043. * with `store.getData().items[index]`. If the chart is not associated with a store,
  15044. * the index represents the index of the sprite within the series. For instance
  15045. * a Gauge chart may have as many sprites as there are sectors in the background of
  15046. * the gauge, plus one for the needle.
  15047. *
  15048. * @return {Object} The attributes that have been changed or added.
  15049. * Note: it is usually possible to add or modify the attributes directly into the
  15050. * `config` parameter and not return anything, but returning an object with only
  15051. * those attributes that have been changed may allow for optimizations in the
  15052. * rendering of some series. Example to draw every other marker in red:
  15053. *
  15054. * renderer: function (sprite, config, rendererData, index) {
  15055. * if (config.type === 'marker') {
  15056. * return { strokeStyle: (index % 2 === 0 ? 'red' : 'black') };
  15057. * }
  15058. * }
  15059. *
  15060. * @controllable
  15061. */
  15062. renderer: null,
  15063. /**
  15064. * @cfg {Boolean} showInLegend
  15065. * Whether to show this series in the legend.
  15066. */
  15067. showInLegend: true,
  15068. /**
  15069. * @private
  15070. * Trigger drawlistener flag
  15071. */
  15072. triggerAfterDraw: false,
  15073. /**
  15074. * @private
  15075. */
  15076. theme: null,
  15077. /**
  15078. * @cfg {Object} style Custom style configuration for the sprite used in the series.
  15079. * It overrides the style that is provided by the current theme.
  15080. */
  15081. style: {},
  15082. /**
  15083. * @cfg {Object} subStyle This is the cyclic used if the series has multiple sprites.
  15084. */
  15085. subStyle: {},
  15086. /**
  15087. * @private
  15088. * @cfg {Object} themeStyle Style configuration that is provided by the current theme.
  15089. * It is composed of five objects:
  15090. * @cfg {Object} themeStyle.style Properties common to all the series,
  15091. * for instance the 'lineWidth'.
  15092. * @cfg {Object} themeStyle.subStyle Cyclic used if the series has multiple sprites.
  15093. * @cfg {Object} themeStyle.label Sprite config for the labels,
  15094. * for instance the font and color.
  15095. * @cfg {Object} themeStyle.marker Sprite config for the markers,
  15096. * for instance the size and stroke color.
  15097. * @cfg {Object} themeStyle.markerSubStyle Cyclic used if series have multiple marker sprites.
  15098. */
  15099. themeStyle: {},
  15100. /**
  15101. * @cfg {Array} colors
  15102. * An array of color values which is used, in order of appearance, by the series. Each series
  15103. * can request one or more colors from the array. Radar, Scatter or Line charts require just
  15104. * one color each. Candlestick and OHLC require two (1 for drops + 1 for rises). Pie charts
  15105. * and Stacked charts (like Bar or Pie charts) require one color for each data category
  15106. * they represent, so one color for each slice of a Pie chart or each segment (not bar) of
  15107. * a Bar chart.
  15108. * It overrides the colors that are provided by the current theme.
  15109. */
  15110. colors: null,
  15111. /**
  15112. * @cfg {Boolean|Number} useDarkerStrokeColor
  15113. * Colors for the series can be set directly through the 'colors' config, or indirectly
  15114. * with the current theme or the 'colors' config that is set onto the chart. These colors
  15115. * are used as "fill color". Set this config to true, if you want a darker color for the
  15116. * strokes. Set it to false if you want to use the same color as the fill color.
  15117. * Alternatively, you can set it to a number between 0 and 1 to control how much darker
  15118. * the strokes should be.
  15119. * Note: this should be initial config and cannot be changed later on.
  15120. */
  15121. useDarkerStrokeColor: true,
  15122. /**
  15123. * @cfg {Object} store The store to use for this series. If not specified,
  15124. * the series will use the chart's {@link Ext.chart.AbstractChart#store store}.
  15125. */
  15126. store: null,
  15127. /**
  15128. * @cfg {Object} label
  15129. * Object with the following properties:
  15130. *
  15131. * @cfg {String} label.display
  15132. *
  15133. * Specifies the presence and position of the labels.
  15134. * The possible values depend on the series type.
  15135. * For Line and Scatter series: 'under' | 'over' | 'rotate'.
  15136. * For Bar and 3D Bar series: 'insideStart' | 'insideEnd' | 'outside'.
  15137. * For Pie series: 'inside' | 'outside' | 'rotate' | 'horizontal' | 'vertical'.
  15138. * Area, Radar and Candlestick series don't support labels.
  15139. * For Area and Radar series please consider using {@link #tooltip tooltips} instead.
  15140. * 3D Pie series currently always display labels 'outside'.
  15141. * For all series: 'none' hides the labels.
  15142. *
  15143. * Default value: 'none'.
  15144. *
  15145. * @cfg {String} label.color
  15146. *
  15147. * The color of the label text.
  15148. *
  15149. * Default value: '#000' (black).
  15150. *
  15151. * @cfg {String|String[]} label.field
  15152. *
  15153. * The name(s) of the field(s) to be displayed in the labels. If your chart has 3 series
  15154. * that correspond to the fields 'a', 'b', and 'c' of your model, and you only want to
  15155. * display labels for the series 'c', you must still provide an array `[null, null, 'c']`.
  15156. *
  15157. * Default value: null.
  15158. *
  15159. * @cfg {String} label.font
  15160. *
  15161. * The font used for the labels.
  15162. *
  15163. * Default value: '14px Helvetica'.
  15164. *
  15165. * @cfg {String} label.orientation
  15166. *
  15167. * Either 'horizontal' or 'vertical'. If not set (default), the orientation is inferred
  15168. * from the value of the flipXY property of the series.
  15169. *
  15170. * Default value: ''.
  15171. *
  15172. * @cfg {Function} label.renderer
  15173. *
  15174. * Optional function for formatting the label into a displayable value.
  15175. *
  15176. * The arguments to the method are:
  15177. *
  15178. * - *`text`*, *`sprite`*, *`config`*, *`rendererData`*, *`index`*
  15179. *
  15180. * Label's renderer is passed the same arguments as {@link #renderer}
  15181. * plus one extra 'text' argument which comes first.
  15182. *
  15183. * @return {Object|String} The attributes that have been changed or added,
  15184. * or the text for the label.
  15185. * Example to enclose every other label in parentheses:
  15186. *
  15187. * renderer: function (text) {
  15188. * if (index % 2 == 0) {
  15189. * return '(' + text + ')'
  15190. * }
  15191. * }
  15192. */
  15193. label: null,
  15194. /**
  15195. * @cfg {Number} labelOverflowPadding
  15196. * Extra distance value for which the labelOverflow listener is triggered.
  15197. */
  15198. labelOverflowPadding: null,
  15199. /**
  15200. * @cfg {Boolean} showMarkers
  15201. * Whether markers should be displayed at the data points along the line. If true,
  15202. * then the {@link #marker} config item will determine the markers' styling.
  15203. */
  15204. showMarkers: true,
  15205. /**
  15206. * @cfg {Object|Boolean} marker
  15207. * The sprite template used by marker instances on the series.
  15208. * If the value of the marker config is set to `true` or the type
  15209. * of the sprite instance is not specified, the {@link Ext.draw.sprite.Circle}
  15210. * sprite will be used.
  15211. *
  15212. * Examples:
  15213. *
  15214. * marker: true
  15215. *
  15216. * marker: {
  15217. * radius: 8
  15218. * }
  15219. *
  15220. * marker: {
  15221. * type: 'arrow',
  15222. * animation: {
  15223. * duration: 200,
  15224. * easing: 'backOut'
  15225. * }
  15226. * }
  15227. */
  15228. marker: null,
  15229. /**
  15230. * @cfg {Object} markerSubStyle
  15231. * This is cyclic used if series have multiple marker sprites.
  15232. */
  15233. markerSubStyle: null,
  15234. /**
  15235. * @protected
  15236. * @cfg {Object} itemInstancing
  15237. * The sprite template used to create sprite instances in the series.
  15238. */
  15239. itemInstancing: null,
  15240. /**
  15241. * @cfg {Object} background
  15242. * Sets the background of the surface the series is attached.
  15243. */
  15244. background: null,
  15245. /**
  15246. * @protected
  15247. * @cfg {Ext.draw.Surface} surface
  15248. * The chart surface used to render series sprites.
  15249. */
  15250. surface: null,
  15251. /**
  15252. * @protected
  15253. * @cfg {Object} overlaySurface
  15254. * The surface used to render series labels.
  15255. */
  15256. overlaySurface: null,
  15257. /**
  15258. * @cfg {Boolean|Array} hidden
  15259. */
  15260. hidden: false,
  15261. /**
  15262. * @cfg {Boolean/Object} highlight
  15263. * The sprite attributes that will be applied to the highlighted items in the series.
  15264. * If set to 'true', the default highlight style from {@link #highlightCfg} will be used.
  15265. * If the value of this config is an object, it will be merged with the {@link #highlightCfg}.
  15266. * In case merging of 'highlight' and 'highlightCfg' configs in not the desired behavior,
  15267. * provide the 'highlightCfg' instead.
  15268. */
  15269. highlight: false,
  15270. /**
  15271. * @protected
  15272. * @cfg {Object} highlightCfg
  15273. * The default style for the highlighted item.
  15274. * Used when {@link #highlight} config was simply set to 'true' instead of specifying
  15275. * a style.
  15276. */
  15277. highlightCfg: {
  15278. // Make custom highlightCfg's in subclasses replace this one.
  15279. merge: function(value) {
  15280. return value;
  15281. },
  15282. $value: {
  15283. fillStyle: 'yellow',
  15284. strokeStyle: 'red'
  15285. }
  15286. },
  15287. /**
  15288. * @cfg {Object} animation The series animation configuration.
  15289. * By default, the series is using the same animation the chart uses,
  15290. * if it's own animation is not explicitly configured.
  15291. */
  15292. animation: null,
  15293. /**
  15294. * @cfg {Object} tooltip
  15295. * Add tooltips to the visualization's markers. The config options for the
  15296. * tooltip are the same configuration used with {@link Ext.tip.ToolTip} plus a
  15297. * `renderer` config option and a `scope` for the renderer. For example:
  15298. *
  15299. * tooltip: {
  15300. * trackMouse: true,
  15301. * width: 140,
  15302. * height: 28,
  15303. * renderer: function (toolTip, record, ctx) {
  15304. * toolTip.setHtml(record.get('name') + ': ' + record.get('data1') + ' views');
  15305. * }
  15306. * }
  15307. *
  15308. * Note that tooltips are shown for series markers and won't work
  15309. * if the {@link #marker} is not configured.
  15310. *
  15311. * You can also configure
  15312. * {@link Ext.chart.interactions.ItemHighlight#multiTooltips}
  15313. * to display multiple tooltips for adjacent or overlapping Line series
  15314. * data points within {@link Ext.chart.series.Line#selectionTolerance} radius.
  15315. *
  15316. * @cfg {Object} tooltip.scope The scope to use when the renderer function is
  15317. * called. Defaults to the Series instance.
  15318. * @cfg {Function} tooltip.renderer An 'interceptor' method which can be used to
  15319. * modify the tooltip attributes before it is shown. The renderer function is
  15320. * passed the following params:
  15321. * @cfg {Ext.tip.ToolTip} tooltip.renderer.toolTip The tooltip instance
  15322. * @cfg {Ext.data.Model} tooltip.renderer.record The record instance for the
  15323. * chart item (sprite) currently targeted by the tooltip.
  15324. * @cfg {Object} tooltip.renderer.ctx A data object with values relating to the
  15325. * currently targeted chart sprite
  15326. * @cfg {String} tooltip.renderer.ctx.category The type of sprite passed to the
  15327. * renderer function (will be "items", "markers", or "labels" depending on the
  15328. * target sprite of the tooltip)
  15329. * @cfg {String} tooltip.renderer.ctx.field The {@link #yField} for the series
  15330. * @cfg {Number} tooltip.renderer.ctx.index The target sprite's index within the
  15331. * series' items
  15332. * @cfg {Ext.data.Model} tooltip.renderer.ctx.record The record instance for the
  15333. * chart item (sprite) currently targeted by the tooltip.
  15334. * @cfg {Ext.chart.series.Series} tooltip.renderer.ctx.series The series instance
  15335. * containing the tooltip's target sprite
  15336. * @cfg {Ext.draw.sprite.Sprite} tooltip.renderer.ctx.sprite The sprite (item)
  15337. * target of the tooltip
  15338. */
  15339. tooltip: null
  15340. },
  15341. directions: [],
  15342. sprites: null,
  15343. /**
  15344. * @private
  15345. * Returns the number of colors this series needs.
  15346. * A Pie chart needs one color per slice while a Stacked Bar chart needs one per segment.
  15347. * An OHLC chart needs 2 colors (one for drops, one for rises), and most other charts
  15348. * need just a single color.
  15349. */
  15350. themeColorCount: function() {
  15351. return 1;
  15352. },
  15353. /**
  15354. * @private
  15355. * @property
  15356. * Series, where the number of sprites (an so unique colors they require)
  15357. * depends on the number of records in the store should set this to 'true'.
  15358. */
  15359. isStoreDependantColorCount: false,
  15360. /**
  15361. * @private
  15362. * Returns the number of markers this series needs.
  15363. * Currently, only the Line, Scatter and Radar series use markers - and they need
  15364. * just one each.
  15365. */
  15366. themeMarkerCount: function() {
  15367. return 0;
  15368. },
  15369. /**
  15370. * @private
  15371. * Each series has configs that tell which store record fields to use as data
  15372. * for a certain dimension. For example, `xField`, `yField` for most cartesian series,
  15373. * `angleField`, `radiusField` for polar series, `openField`, ..., `closeField`
  15374. * for CandleStick series, etc. The field category is an array of capitalized config
  15375. * names, minus the 'Field' part, to use as data for a certain dimension.
  15376. * For example, for CandleStick series we have:
  15377. *
  15378. * fieldCategoryY: ['Open', 'High', 'Low', 'Close']
  15379. *
  15380. * While for generic Cartesian series it is simply:
  15381. *
  15382. * fieldCategoryY: ['Y']
  15383. *
  15384. * This method fetches the values of those configs, i.e. the actual record fields to use.
  15385. *
  15386. * The {@link #coordinate} method in turn will use the values from the `fieldCategory`
  15387. * array to set data attributes of the series sprite. E.g., in case of CandleStick series,
  15388. * the following attributes will be set based on the values in the `fieldCategoryY` array:
  15389. *
  15390. * `dataOpen`, `dataHigh`, `dataLow`, `dataClose`
  15391. *
  15392. * Where the value of each attribute is a coordinated array of data from the corresponding
  15393. * field.
  15394. *
  15395. * @param {String[]} fieldCategory
  15396. * @return {String[]}
  15397. */
  15398. getFields: function(fieldCategory) {
  15399. var me = this,
  15400. fields = [],
  15401. ln = fieldCategory.length,
  15402. i, field;
  15403. for (i = 0; i < ln; i++) {
  15404. field = me['get' + fieldCategory[i] + 'Field']();
  15405. if (Ext.isArray(field)) {
  15406. fields.push.apply(fields, field);
  15407. } else {
  15408. fields.push(field);
  15409. }
  15410. }
  15411. return fields;
  15412. },
  15413. applyAnimation: function(animation, oldAnimation) {
  15414. var chart = this.getChart();
  15415. if (!chart.isSettingSeriesAnimation) {
  15416. this.isUserAnimation = true;
  15417. }
  15418. return Ext.chart.Util.applyAnimation(animation, oldAnimation);
  15419. },
  15420. updateAnimation: function(animation) {
  15421. var sprites = this.getSprites(),
  15422. itemsMarker, markersMarker, i, ln, sprite;
  15423. for (i = 0 , ln = sprites.length; i < ln; i++) {
  15424. sprite = sprites[i];
  15425. if (sprite.isMarkerHolder) {
  15426. itemsMarker = sprite.getMarker('items');
  15427. if (itemsMarker) {
  15428. itemsMarker.getTemplate().setAnimation(animation);
  15429. }
  15430. markersMarker = sprite.getMarker('markers');
  15431. if (markersMarker) {
  15432. markersMarker.getTemplate().setAnimation(animation);
  15433. }
  15434. }
  15435. sprite.setAnimation(animation);
  15436. }
  15437. },
  15438. getAnimation: function() {
  15439. var chart = this.getChart(),
  15440. animation;
  15441. if (chart && chart.animationSuspendCount) {
  15442. animation = {
  15443. duration: 0
  15444. };
  15445. } else {
  15446. if (this.isUserAnimation) {
  15447. animation = this.callParent();
  15448. } else {
  15449. animation = chart.getAnimation();
  15450. }
  15451. }
  15452. return animation;
  15453. },
  15454. updateTitle: function() {
  15455. var me = this,
  15456. chart = me.getChart();
  15457. if (chart && !chart.isInitializing) {
  15458. chart.refreshLegendStore();
  15459. }
  15460. },
  15461. applyHighlight: function(highlight, oldHighlight) {
  15462. var me = this,
  15463. highlightCfg = me.getHighlightCfg();
  15464. if (Ext.isObject(highlight)) {
  15465. highlight = Ext.merge({}, highlightCfg, highlight);
  15466. } else if (highlight === true) {
  15467. highlight = highlightCfg;
  15468. }
  15469. if (highlight) {
  15470. highlight.type = 'highlight';
  15471. }
  15472. return highlight && Ext.merge({}, oldHighlight, highlight);
  15473. },
  15474. updateHighlight: function(highlight) {
  15475. var me = this,
  15476. sprites = me.sprites,
  15477. highlightCfg = me.getHighlightCfg(),
  15478. i, ln, sprite, items, markers;
  15479. me.getStyle();
  15480. // Make sure the 'markers' sprite has been created,
  15481. // so that we can set the 'style' config of its 'highlight' modifier here.
  15482. me.getMarker();
  15483. if (!Ext.Object.isEmpty(highlight)) {
  15484. me.addItemHighlight();
  15485. for (i = 0 , ln = sprites.length; i < ln; i++) {
  15486. sprite = sprites[i];
  15487. if (sprite.isMarkerHolder) {
  15488. items = sprite.getMarker('items');
  15489. if (items) {
  15490. items.getTemplate().modifiers.highlight.setStyle(highlight);
  15491. }
  15492. markers = sprite.getMarker('markers');
  15493. if (markers) {
  15494. markers.getTemplate().modifiers.highlight.setStyle(highlight);
  15495. }
  15496. }
  15497. }
  15498. } else if (!Ext.Object.equals(highlightCfg, this.defaultConfig.highlightCfg)) {
  15499. this.addItemHighlight();
  15500. }
  15501. },
  15502. updateHighlightCfg: function(highlightCfg) {
  15503. // Make sure to add the 'itemhighlight' interaction to the series, if the default
  15504. // highlight style changes, even if the 'highlight' config isn't set (defaults to false),
  15505. // since we probably want to use item highlighting now or later, if we are changing
  15506. // the default highlight style.
  15507. // This updater will be triggered by the 'highlight' applier, and the 'addItemHighlight'
  15508. // call here will in turn call 'getHighlight' down the call stack, which will return
  15509. // 'undefined' since the value hasn't been processed yet. So we don't call 'addItemHighlight'
  15510. // here during configuration and instead call it in the 'highlight' updater, if it hasn't
  15511. // already been called ('highlight' config is set to 'false').
  15512. if (!this.isConfiguring && !Ext.Object.equals(highlightCfg, this.defaultConfig.highlightCfg)) {
  15513. this.addItemHighlight();
  15514. }
  15515. },
  15516. applyItemInstancing: function(config, oldConfig) {
  15517. if (config && oldConfig && (!config.type || config.type === oldConfig.type)) {
  15518. // Have to merge to a new object, or the updater won't be called.
  15519. config = Ext.merge({}, oldConfig, config);
  15520. }
  15521. if (config && !config.type) {
  15522. config = null;
  15523. }
  15524. return config;
  15525. },
  15526. setAttributesForItem: function(item, change) {
  15527. var sprite = item && item.sprite,
  15528. i;
  15529. if (sprite) {
  15530. if (sprite.isMarkerHolder && item.category === 'items') {
  15531. sprite.putMarker(item.category, change, item.index, false, true);
  15532. }
  15533. if (sprite.isMarkerHolder && item.category === 'markers') {
  15534. sprite.putMarker(item.category, change, item.index, false, true);
  15535. } else if (sprite.isInstancing) {
  15536. sprite.setAttributesFor(item.index, change);
  15537. } else if (Ext.isArray(sprite)) {
  15538. // In some instances, like with the 3D pie series,
  15539. // an item can be composed of multiple sprites
  15540. // (e.g. 8 sprites are used to render a single 3D pie slice).
  15541. for (i = 0; i < sprite.length; i++) {
  15542. sprite[i].setAttributes(change);
  15543. }
  15544. } else {
  15545. sprite.setAttributes(change);
  15546. }
  15547. }
  15548. },
  15549. getBBoxForItem: function(item) {
  15550. var sprite = item && item.sprite,
  15551. result = null;
  15552. if (sprite) {
  15553. if (sprite.getMarker('items') && item.category === 'items') {
  15554. result = sprite.getMarkerBBox(item.category, item.index);
  15555. } else if (sprite instanceof Ext.draw.sprite.Instancing) {
  15556. result = sprite.getBBoxFor(item.index);
  15557. } else {
  15558. result = sprite.getBBox();
  15559. }
  15560. }
  15561. return result;
  15562. },
  15563. /**
  15564. * @private
  15565. * @property
  15566. * The range of "coordinated" data.
  15567. * Typically, for two directions ('X' and 'Y') the `dataRange` would look like this:
  15568. *
  15569. * dataRange[0] - minX
  15570. * dataRange[1] - minY
  15571. * dataRange[2] - maxX
  15572. * dataRange[3] - maxY
  15573. *
  15574. * And the series' {@link #coordinate} method would be called like this:
  15575. *
  15576. * coordinate('X', 0, 2)
  15577. * coordinate('Y', 1, 2)
  15578. *
  15579. * For numbers, coordinated data are numbers themselves.
  15580. * For categories - their indexes.
  15581. * For Date objects - their timestamps.
  15582. * In other words, whatever source data we have, it has to be converted to numbers
  15583. * before it can be plotted.
  15584. */
  15585. dataRange: null,
  15586. constructor: function(config) {
  15587. var me = this,
  15588. id;
  15589. config = config || {};
  15590. // Backward compatibility with Ext.
  15591. if (config.tips) {
  15592. config = Ext.apply({
  15593. tooltip: config.tips
  15594. }, config);
  15595. }
  15596. // Backward compatibility with Touch.
  15597. if (config.highlightCfg) {
  15598. config = Ext.apply({
  15599. highlight: config.highlightCfg
  15600. }, config);
  15601. }
  15602. if ('id' in config) {
  15603. id = config.id;
  15604. } else if ('id' in me.config) {
  15605. id = me.config.id;
  15606. } else {
  15607. id = me.getId();
  15608. }
  15609. me.setId(id);
  15610. me.sprites = [];
  15611. me.dataRange = [];
  15612. me.mixins.observable.constructor.call(me, config);
  15613. me.initBindable();
  15614. },
  15615. lookupViewModel: function(skipThis) {
  15616. // Override the Bindable's method to redirect view model
  15617. // lookup to the chart.
  15618. var chart = this.getChart();
  15619. return chart ? chart.lookupViewModel(skipThis) : null;
  15620. },
  15621. applyTooltip: function(tooltip, oldTooltip) {
  15622. var config = Ext.apply({
  15623. xtype: 'tooltip',
  15624. renderer: Ext.emptyFn,
  15625. constrainPosition: true,
  15626. shrinkWrapDock: true,
  15627. autoHide: true,
  15628. hideDelay: 200,
  15629. mouseOffset: [
  15630. 20,
  15631. 20
  15632. ],
  15633. trackMouse: true
  15634. }, tooltip);
  15635. return Ext.create(config);
  15636. },
  15637. updateTooltip: function() {
  15638. // Tooltips can't work without the 'itemhighlight' or the 'itemedit' interaction.
  15639. this.addItemHighlight();
  15640. },
  15641. // Adds the 'itemhighlight' interaction to the chart that owns the series.
  15642. addItemHighlight: function() {
  15643. var chart = this.getChart();
  15644. if (!chart) {
  15645. return;
  15646. }
  15647. var interactions = chart.getInteractions(),
  15648. i, interaction, hasRequiredInteraction;
  15649. for (i = 0; i < interactions.length; i++) {
  15650. interaction = interactions[i];
  15651. if (interaction.isItemHighlight || interaction.isItemEdit) {
  15652. hasRequiredInteraction = true;
  15653. break;
  15654. }
  15655. }
  15656. if (!hasRequiredInteraction) {
  15657. interactions.push('itemhighlight');
  15658. chart.setInteractions(interactions);
  15659. }
  15660. },
  15661. showTooltip: function(item, event) {
  15662. var me = this,
  15663. tooltip = me.getTooltip();
  15664. if (!tooltip) {
  15665. return;
  15666. }
  15667. Ext.callback(tooltip.renderer, tooltip.scope, [
  15668. tooltip,
  15669. item.record,
  15670. item
  15671. ], 0, me);
  15672. tooltip.showBy(event);
  15673. },
  15674. showTooltipAt: function(item, x, y) {
  15675. var me = this,
  15676. tooltip = me.getTooltip(),
  15677. mouseOffset = tooltip.config.mouseOffset;
  15678. if (!tooltip || !tooltip.showAt) {
  15679. return;
  15680. }
  15681. if (mouseOffset) {
  15682. x += mouseOffset[0];
  15683. y += mouseOffset[1];
  15684. }
  15685. Ext.callback(tooltip.renderer, tooltip.scope, [
  15686. tooltip,
  15687. item.record,
  15688. item
  15689. ], 0, me);
  15690. tooltip.showAt([
  15691. x,
  15692. y
  15693. ]);
  15694. },
  15695. hideTooltip: function(item, immediate) {
  15696. var me = this,
  15697. tooltip = me.getTooltip();
  15698. if (!tooltip) {
  15699. return;
  15700. }
  15701. if (immediate) {
  15702. tooltip.hide();
  15703. } else {
  15704. tooltip.delayHide();
  15705. }
  15706. },
  15707. applyStore: function(store) {
  15708. return store && Ext.StoreManager.lookup(store);
  15709. },
  15710. getStore: function() {
  15711. return this._store || this.getChart() && this.getChart().getStore();
  15712. },
  15713. updateStore: function(newStore, oldStore) {
  15714. var me = this,
  15715. chart = me.getChart(),
  15716. chartStore = chart && chart.getStore(),
  15717. sprites, sprite, len, i;
  15718. oldStore = oldStore || chartStore;
  15719. if (oldStore && oldStore !== newStore) {
  15720. oldStore.un({
  15721. datachanged: 'onDataChanged',
  15722. update: 'onDataChanged',
  15723. scope: me
  15724. });
  15725. }
  15726. if (newStore) {
  15727. newStore.on({
  15728. datachanged: 'onDataChanged',
  15729. update: 'onDataChanged',
  15730. scope: me
  15731. });
  15732. sprites = me.getSprites();
  15733. for (i = 0 , len = sprites.length; i < len; i++) {
  15734. sprite = sprites[i];
  15735. if (sprite.setStore) {
  15736. sprite.setStore(newStore);
  15737. }
  15738. }
  15739. me.onDataChanged();
  15740. }
  15741. me.fireEvent('storechange', me, newStore, oldStore);
  15742. },
  15743. onStoreChange: function(chart, newStore, oldStore) {
  15744. if (!this._store) {
  15745. this.updateStore(newStore, oldStore);
  15746. }
  15747. },
  15748. defaultRange: [
  15749. 0,
  15750. 1
  15751. ],
  15752. /**
  15753. * @private
  15754. * @param direction {'X'/'Y'}
  15755. * @param directionOffset
  15756. * @param directionCount
  15757. */
  15758. coordinate: function(direction, directionOffset, directionCount) {
  15759. var me = this,
  15760. store = me.getStore(),
  15761. hidden = me.getHidden(),
  15762. items = store.getData().items,
  15763. axis = me['get' + direction + 'Axis'](),
  15764. dataRange = [
  15765. NaN,
  15766. NaN
  15767. ],
  15768. fieldCategory = me['fieldCategory' + direction] || [
  15769. direction
  15770. ],
  15771. fields = me.getFields(fieldCategory),
  15772. i, field, data,
  15773. style = {},
  15774. sprites = me.getSprites(),
  15775. axisRange;
  15776. if (sprites.length && !Ext.isBoolean(hidden) || !hidden) {
  15777. for (i = 0; i < fieldCategory.length; i++) {
  15778. field = fields[i];
  15779. data = me.coordinateData(items, field, axis);
  15780. Ext.chart.Util.expandRange(dataRange, data);
  15781. style['data' + fieldCategory[i]] = data;
  15782. }
  15783. // We don't want to expand the range that has a span of 0 here
  15784. // (e.g. [5, 5] that we'd get if all values for a field are 5).
  15785. // We only want to do this in the Axis, when we calculate the
  15786. // combined range.
  15787. // This is because, if we try to expand the range of values here,
  15788. // and we have multiple fields, the combined range for the axis
  15789. // may not represent the actual range of the data.
  15790. // E.g. if other fields have non-zero span ranges like [4.95, 5.03],
  15791. // [4.91, 5.08], and if the `padding` param to `validateRange` is 0.5,
  15792. // the range of the axis will end up being [4.5, 5.5], because the
  15793. // [5, 5] range of one of the series was expanded to [4.5, 5.5]
  15794. // which encompasses the rest of the ranges.
  15795. dataRange = Ext.chart.Util.validateRange(dataRange, me.defaultRange, 0);
  15796. // See `dataRange` docs.
  15797. me.dataRange[directionOffset] = dataRange[0];
  15798. me.dataRange[directionOffset + directionCount] = dataRange[1];
  15799. style['dataMin' + direction] = dataRange[0];
  15800. style['dataMax' + direction] = dataRange[1];
  15801. if (axis) {
  15802. axisRange = axis.getRange(true);
  15803. axis.setBoundSeriesRange(axisRange);
  15804. }
  15805. for (i = 0; i < sprites.length; i++) {
  15806. sprites[i].setAttributes(style);
  15807. }
  15808. }
  15809. },
  15810. /**
  15811. * @private
  15812. * This method will return an array containing data coordinated by a specific axis.
  15813. * @param {Array} items Store records.
  15814. * @param {String} field The field to fetch from each record.
  15815. * @param {Ext.chart.axis.Axis} axis The axis used to lay out the data.
  15816. * @return {Array}
  15817. */
  15818. coordinateData: function(items, field, axis) {
  15819. var data = [],
  15820. length = items.length,
  15821. layout = axis && axis.getLayout(),
  15822. i, x;
  15823. for (i = 0; i < length; i++) {
  15824. x = items[i].data[field];
  15825. // An empty string (a valid discrete axis value) will be coordinated
  15826. // by the axis layout (if axis is given), otherwise it will be converted
  15827. // to zero (via +'').
  15828. if (!Ext.isEmpty(x, true)) {
  15829. if (layout) {
  15830. data[i] = layout.getCoordFor(x, field, i, items);
  15831. } else {
  15832. x = +x;
  15833. // 'x' can be a category name here.
  15834. data[i] = Ext.isNumber(x) ? x : i;
  15835. }
  15836. } else {
  15837. data[i] = x;
  15838. }
  15839. }
  15840. return data;
  15841. },
  15842. updateLabelData: function() {
  15843. var label = this.getLabel();
  15844. if (!label) {
  15845. return;
  15846. }
  15847. var store = this.getStore(),
  15848. items = store.getData().items,
  15849. sprites = this.getSprites(),
  15850. labelTpl = label.getTemplate(),
  15851. labelFields = Ext.Array.from(labelTpl.getField()),
  15852. i, j, ln, labels, sprite, field;
  15853. if (!sprites.length || !labelFields.length) {
  15854. return;
  15855. }
  15856. for (i = 0; i < sprites.length; i++) {
  15857. sprite = sprites[i];
  15858. if (!sprite.getField) {
  15859. // The 'gauge' series is misnormer, its sprites
  15860. // do not extend from the base Series sprite and
  15861. // so do not have the 'field' config. They also
  15862. // don't support labels in the traditional sense.
  15863. continue;
  15864. }
  15865. labels = [];
  15866. field = sprite.getField();
  15867. if (Ext.Array.indexOf(labelFields, field) < 0) {
  15868. field = labelFields[i];
  15869. }
  15870. for (j = 0 , ln = items.length; j < ln; j++) {
  15871. labels.push(items[j].get(field));
  15872. }
  15873. sprite.setAttributes({
  15874. labels: labels
  15875. });
  15876. }
  15877. },
  15878. /**
  15879. * @private
  15880. *
  15881. * *** Data processing overview. ***
  15882. *
  15883. * The data is processed in the following order:
  15884. *
  15885. * 1) chart.processData() - calls `processData` of all series
  15886. * 2) series.processData() - calls `processData` of all bound axes,
  15887. * or jumps to (5) directly, if the series has no axis
  15888. * in this direction
  15889. * 3) axis.processData() - calls the `processData` of its own layout
  15890. * 4) axisLayout.processData() - calls `coordinateX/Y` of all bound series
  15891. * 5) series.coordinateX/Y - calls its own `coordinate` method in that direction
  15892. * 6) series.coordinate - calls its own `coordinateData` method using the right
  15893. * record fields and axes
  15894. * 7) series.coordinateData - calls `getCoordFor` of the axis layout for the given
  15895. * field
  15896. * 8) layout.getCoordFor - returns a numeric value for the given field value,
  15897. * whatever its type may be
  15898. *
  15899. * The `dataX`, `dataY` attributes of the series' sprites are set by the
  15900. * `series.coordinate` method using the data returned by the `coordinateData`.
  15901. * `series.coordinate` also calculates the range of said data (via `expandRange`)
  15902. * and sets the `dataMinX/Y`, `dataMaxX/Y` attributes of the series' sprites.
  15903. */
  15904. processData: function() {
  15905. var me = this;
  15906. if (me.isProcessingData || !me.getStore()) {
  15907. return;
  15908. }
  15909. var directions = this.directions,
  15910. i,
  15911. ln = directions.length,
  15912. direction, axis, name;
  15913. me.isProcessingData = true;
  15914. for (i = 0; i < ln; i++) {
  15915. direction = directions[i];
  15916. axis = me['get' + direction + 'Axis']();
  15917. if (axis) {
  15918. axis.processData(me);
  15919. continue;
  15920. }
  15921. name = 'coordinate' + direction;
  15922. if (me[name]) {
  15923. me[name]();
  15924. }
  15925. }
  15926. me.updateLabelData();
  15927. me.isProcessingData = false;
  15928. },
  15929. applyBackground: function(background) {
  15930. var surface, result;
  15931. if (this.getChart()) {
  15932. surface = this.getSurface();
  15933. surface.setBackground(background);
  15934. result = surface.getBackground();
  15935. } else {
  15936. result = background;
  15937. }
  15938. return result;
  15939. },
  15940. updateChart: function(newChart, oldChart) {
  15941. var me = this,
  15942. store = me._store;
  15943. if (oldChart) {
  15944. oldChart.un('axeschange', 'onAxesChange', me);
  15945. me.clearSprites();
  15946. me.setSurface(null);
  15947. me.setOverlaySurface(null);
  15948. oldChart.unregister(me);
  15949. me.onChartDetached(oldChart);
  15950. if (!store) {
  15951. me.updateStore(null);
  15952. }
  15953. }
  15954. if (newChart) {
  15955. me.setSurface(newChart.getSurface('series'));
  15956. me.setOverlaySurface(newChart.getSurface('overlay'));
  15957. newChart.on('axeschange', 'onAxesChange', me);
  15958. // TODO: Gauge series should render correctly when chart's store is missing.
  15959. // TODO: When store is initially missing the getAxes will return null here,
  15960. // TODO: since applyAxes has actually triggered this series.updateChart call
  15961. // TODO: indirectly.
  15962. // TODO: Figure out why it doesn't go this route when a store is present.
  15963. if (newChart.getAxes()) {
  15964. me.onAxesChange(newChart);
  15965. }
  15966. me.onChartAttached(newChart);
  15967. newChart.register(me);
  15968. if (!store) {
  15969. me.updateStore(newChart.getStore());
  15970. }
  15971. }
  15972. },
  15973. onAxesChange: function(chart, force) {
  15974. if (chart.destroying || chart.destroyed) {
  15975. return;
  15976. }
  15977. var me = this,
  15978. axes = chart.getAxes(),
  15979. axis,
  15980. directionToAxesMap = {},
  15981. directionToFieldsMap = {},
  15982. needHighPrecision = false,
  15983. directions = this.directions,
  15984. direction, i, ln;
  15985. for (i = 0 , ln = directions.length; i < ln; i++) {
  15986. direction = directions[i];
  15987. directionToFieldsMap[direction] = me.getFields(me['fieldCategory' + direction]);
  15988. }
  15989. for (i = 0 , ln = axes.length; i < ln; i++) {
  15990. axis = axes[i];
  15991. direction = axis.getDirection();
  15992. if (!directionToAxesMap[direction]) {
  15993. directionToAxesMap[direction] = [
  15994. axis
  15995. ];
  15996. } else {
  15997. directionToAxesMap[direction].push(axis);
  15998. }
  15999. }
  16000. for (i = 0 , ln = directions.length; i < ln; i++) {
  16001. direction = directions[i];
  16002. if (!force && me['get' + direction + 'Axis']()) {
  16003. continue;
  16004. }
  16005. if (directionToAxesMap[direction]) {
  16006. axis = me.findMatchingAxis(directionToAxesMap[direction], directionToFieldsMap[direction]);
  16007. if (axis) {
  16008. me['set' + direction + 'Axis'](axis);
  16009. if (axis.getNeedHighPrecision()) {
  16010. needHighPrecision = true;
  16011. }
  16012. }
  16013. }
  16014. }
  16015. this.getSurface().setHighPrecision(needHighPrecision);
  16016. },
  16017. /**
  16018. * @private
  16019. * Given the list of axes in a certain direction and a list of series fields in that
  16020. * direction returns the first matching axis for the series in that direction,
  16021. * or undefined if a match wasn't found.
  16022. */
  16023. findMatchingAxis: function(directionAxes, directionFields) {
  16024. var axis, axisFields, i, j;
  16025. for (i = 0; i < directionAxes.length; i++) {
  16026. axis = directionAxes[i];
  16027. axisFields = axis.getFields();
  16028. if (!axisFields.length) {
  16029. return axis;
  16030. } else if (directionFields) {
  16031. for (j = 0; j < directionFields.length; j++) {
  16032. if (Ext.Array.indexOf(axisFields, directionFields[j]) >= 0) {
  16033. return axis;
  16034. }
  16035. }
  16036. }
  16037. }
  16038. },
  16039. onChartDetached: function(oldChart) {
  16040. var me = this;
  16041. me.fireEvent('chartdetached', oldChart, me);
  16042. oldChart.un('storechange', 'onStoreChange', me);
  16043. },
  16044. onChartAttached: function(chart) {
  16045. var me = this;
  16046. me.fireEvent('chartattached', chart, me);
  16047. chart.on('storechange', 'onStoreChange', me);
  16048. me.processData();
  16049. },
  16050. updateOverlaySurface: function(overlaySurface) {
  16051. var label = this.getLabel();
  16052. if (overlaySurface && label) {
  16053. overlaySurface.add(label);
  16054. }
  16055. },
  16056. getLabel: function() {
  16057. return this.labelMarker;
  16058. },
  16059. setLabel: function(label) {
  16060. var me = this,
  16061. chart = me.getChart(),
  16062. marker = me.labelMarker,
  16063. template;
  16064. // The label sprite is reused unless the value of 'label' is falsy,
  16065. // so that we can transition from one attribute set to another with an
  16066. // animation, which is important for example during theme switching.
  16067. if (!label && marker) {
  16068. marker.getTemplate().destroy();
  16069. marker.destroy();
  16070. me.labelMarker = marker = null;
  16071. }
  16072. if (label) {
  16073. if (!marker) {
  16074. marker = me.labelMarker = new Ext.chart.Markers({
  16075. zIndex: 10
  16076. });
  16077. marker.setTemplate(new Ext.chart.sprite.Label());
  16078. me.getOverlaySurface().add(marker);
  16079. }
  16080. template = marker.getTemplate();
  16081. template.setAttributes(label);
  16082. template.setConfig(label);
  16083. if (label.field) {
  16084. template.setField(label.field);
  16085. }
  16086. if (label.display) {
  16087. marker.setAttributes({
  16088. hidden: label.display === 'none'
  16089. });
  16090. }
  16091. marker.setDirty(true);
  16092. }
  16093. // Inform the label about the template change.
  16094. me.updateLabelData();
  16095. if (chart && !chart.isInitializing && !me.isConfiguring) {
  16096. chart.redraw();
  16097. }
  16098. },
  16099. createItemInstancingSprite: function(sprite, itemInstancing) {
  16100. var me = this,
  16101. markers = new Ext.chart.Markers(),
  16102. config = Ext.apply({
  16103. modifiers: 'highlight'
  16104. }, itemInstancing),
  16105. style = me.getStyle(),
  16106. template, animation;
  16107. markers.setAttributes({
  16108. zIndex: Number.MAX_VALUE
  16109. });
  16110. markers.setTemplate(config);
  16111. template = markers.getTemplate();
  16112. template.setAttributes(style);
  16113. animation = template.getAnimation();
  16114. animation.on('animationstart', 'onSpriteAnimationStart', this);
  16115. animation.on('animationend', 'onSpriteAnimationEnd', this);
  16116. sprite.bindMarker('items', markers);
  16117. me.getSurface().add(markers);
  16118. return markers;
  16119. },
  16120. getDefaultSpriteConfig: function() {
  16121. return {
  16122. type: this.seriesType,
  16123. renderer: this.getRenderer()
  16124. };
  16125. },
  16126. updateRenderer: function(renderer) {
  16127. var me = this,
  16128. chart = me.getChart();
  16129. if (chart && chart.isInitializing) {
  16130. return;
  16131. }
  16132. // We have to be careful and not call the 'getSprites' method here, as this
  16133. // method itself may have been called by the 'getSprites' method indirectly already.
  16134. if (me.sprites.length) {
  16135. me.sprites[0].setAttributes({
  16136. renderer: renderer || null
  16137. });
  16138. if (chart && !chart.isInitializing) {
  16139. chart.redraw();
  16140. }
  16141. }
  16142. },
  16143. updateShowMarkers: function(showMarkers) {
  16144. var sprite = this.getSprite(),
  16145. markers = sprite && sprite.getMarker('markers');
  16146. if (markers) {
  16147. markers.getTemplate().setAttributes({
  16148. hidden: !showMarkers
  16149. });
  16150. }
  16151. },
  16152. createSprite: function() {
  16153. var me = this,
  16154. surface = me.getSurface(),
  16155. itemInstancing = me.getItemInstancing(),
  16156. sprite = surface.add(me.getDefaultSpriteConfig()),
  16157. animation, label;
  16158. sprite.setAttributes(me.getStyle());
  16159. sprite.setSeries(me);
  16160. if (itemInstancing) {
  16161. me.createItemInstancingSprite(sprite, itemInstancing);
  16162. }
  16163. if (sprite.isMarkerHolder) {
  16164. label = me.getLabel();
  16165. if (label && label.getTemplate().getField()) {
  16166. sprite.bindMarker('labels', label);
  16167. }
  16168. }
  16169. if (sprite.setStore) {
  16170. sprite.setStore(me.getStore());
  16171. }
  16172. animation = sprite.getAnimation();
  16173. animation.on('animationstart', 'onSpriteAnimationStart', me);
  16174. animation.on('animationend', 'onSpriteAnimationEnd', me);
  16175. me.sprites.push(sprite);
  16176. return sprite;
  16177. },
  16178. /**
  16179. * @method
  16180. * Returns the read-only array of sprites the are used to draw this series.
  16181. */
  16182. getSprites: null,
  16183. /**
  16184. * @private
  16185. * Returns the first sprite. Convenience method for series that have
  16186. * a single markerholder sprite.
  16187. */
  16188. getSprite: function() {
  16189. var sprites = this.getSprites();
  16190. return sprites && sprites[0];
  16191. },
  16192. /**
  16193. * @private
  16194. */
  16195. withSprite: function(fn) {
  16196. var sprite = this.getSprite();
  16197. return sprite && fn(sprite) || undefined;
  16198. },
  16199. forEachSprite: function(fn) {
  16200. var sprites = this.getSprites(),
  16201. i, ln;
  16202. for (i = 0 , ln = sprites.length; i < ln; i++) {
  16203. fn(sprites[i]);
  16204. }
  16205. },
  16206. onDataChanged: function() {
  16207. var me = this,
  16208. chart = me.getChart(),
  16209. chartStore = chart && chart.getStore(),
  16210. seriesStore = me.getStore();
  16211. if (seriesStore !== chartStore) {
  16212. me.processData();
  16213. }
  16214. },
  16215. isXType: function(xtype) {
  16216. return xtype === 'series';
  16217. },
  16218. getItemId: function() {
  16219. return this.getId();
  16220. },
  16221. applyThemeStyle: function(theme, oldTheme) {
  16222. var me = this,
  16223. fill, stroke;
  16224. fill = theme && theme.subStyle && theme.subStyle.fillStyle;
  16225. stroke = fill && theme.subStyle.strokeStyle;
  16226. if (fill && !stroke) {
  16227. theme.subStyle.strokeStyle = me.getStrokeColorsFromFillColors(fill);
  16228. }
  16229. fill = theme && theme.markerSubStyle && theme.markerSubStyle.fillStyle;
  16230. stroke = fill && theme.markerSubStyle.strokeStyle;
  16231. if (fill && !stroke) {
  16232. theme.markerSubStyle.strokeStyle = me.getStrokeColorsFromFillColors(fill);
  16233. }
  16234. return Ext.apply(oldTheme || {}, theme);
  16235. },
  16236. applyStyle: function(style, oldStyle) {
  16237. return Ext.apply({}, style, oldStyle);
  16238. },
  16239. applySubStyle: function(subStyle, oldSubStyle) {
  16240. var name = Ext.ClassManager.getNameByAlias('sprite.' + this.seriesType),
  16241. cls = Ext.ClassManager.get(name);
  16242. if (cls && cls.def) {
  16243. subStyle = cls.def.batchedNormalize(subStyle, true);
  16244. }
  16245. return Ext.merge({}, oldSubStyle, subStyle);
  16246. },
  16247. applyMarker: function(marker, oldMarker) {
  16248. var type, cls;
  16249. if (marker) {
  16250. if (!Ext.isObject(marker)) {
  16251. marker = {};
  16252. }
  16253. type = marker.type || 'circle';
  16254. if (oldMarker && type === oldMarker.type) {
  16255. marker = Ext.merge({}, oldMarker, marker);
  16256. }
  16257. }
  16258. // Note: reusing the `oldMaker` like `Ext.merge(oldMarker, marker)`
  16259. // isn't possible because the `updateMarker` won't be called.
  16260. if (type) {
  16261. cls = Ext.ClassManager.get(Ext.ClassManager.getNameByAlias('sprite.' + type));
  16262. }
  16263. if (cls && cls.def) {
  16264. marker = cls.def.normalize(marker, true);
  16265. marker.type = type;
  16266. } else {
  16267. marker = null;
  16268. //<debug>
  16269. Ext.log.warn('Invalid series marker type: ' + type);
  16270. }
  16271. //</debug>
  16272. return marker;
  16273. },
  16274. updateMarker: function(marker) {
  16275. var me = this,
  16276. sprites = me.getSprites(),
  16277. seriesSprite, markerSprite, markerTplConfig, i, ln;
  16278. for (i = 0 , ln = sprites.length; i < ln; i++) {
  16279. seriesSprite = sprites[i];
  16280. if (!seriesSprite.isMarkerHolder) {
  16281. continue;
  16282. }
  16283. markerSprite = seriesSprite.getMarker('markers');
  16284. if (marker) {
  16285. if (!markerSprite) {
  16286. markerSprite = new Ext.chart.Markers();
  16287. seriesSprite.bindMarker('markers', markerSprite);
  16288. me.getOverlaySurface().add(markerSprite);
  16289. }
  16290. markerTplConfig = Ext.Object.merge({
  16291. modifiers: 'highlight'
  16292. }, marker);
  16293. markerSprite.setTemplate(markerTplConfig);
  16294. markerSprite.getTemplate().getAnimation().setCustomDurations({
  16295. translationX: 0,
  16296. translationY: 0
  16297. });
  16298. } else if (markerSprite) {
  16299. seriesSprite.releaseMarker('markers');
  16300. me.getOverlaySurface().remove(markerSprite, true);
  16301. }
  16302. seriesSprite.setDirty(true);
  16303. }
  16304. // If we call, for example, `series.setMarker({type: 'circle'})` on a series
  16305. // that has been already constructed, the newly added marker still has to be
  16306. // themed, and the 'style' config of its 'highlight' modifier has to be set.
  16307. if (!me.isConfiguring) {
  16308. me.doUpdateStyles();
  16309. me.updateHighlight(me.getHighlight());
  16310. }
  16311. },
  16312. applyMarkerSubStyle: function(marker, oldMarker) {
  16313. var type = (marker && marker.type) || (oldMarker && oldMarker.type) || 'circle',
  16314. cls = Ext.ClassManager.get(Ext.ClassManager.getNameByAlias('sprite.' + type));
  16315. if (cls && cls.def) {
  16316. marker = cls.def.batchedNormalize(marker, true);
  16317. }
  16318. return Ext.merge(oldMarker || {}, marker);
  16319. },
  16320. updateHidden: function(hidden) {
  16321. var me = this;
  16322. me.getColors();
  16323. me.getSubStyle();
  16324. me.setSubStyle({
  16325. hidden: hidden
  16326. });
  16327. me.processData();
  16328. me.doUpdateStyles();
  16329. if (!Ext.isArray(hidden)) {
  16330. me.updateLegendStore(hidden);
  16331. }
  16332. },
  16333. /**
  16334. * @private
  16335. * Updates chart's legend store when the value of the series' {@link #hidden} config
  16336. * changes or when the {@link #setHiddenByIndex} method is called.
  16337. * @param hidden Whether series (or its component) should be hidden or not.
  16338. * @param index Used for stacked series.
  16339. * If present, only the component with the specified index will change
  16340. * visibility.
  16341. */
  16342. updateLegendStore: function(hidden, index) {
  16343. var me = this,
  16344. chart = me.getChart(),
  16345. legendStore = chart && chart.getLegendStore(),
  16346. id = me.getId(),
  16347. record;
  16348. if (legendStore) {
  16349. if (arguments.length > 1) {
  16350. record = legendStore.findBy(function(rec) {
  16351. return rec.get('series') === id && rec.get('index') === index;
  16352. });
  16353. if (record !== -1) {
  16354. record = legendStore.getAt(record);
  16355. }
  16356. } else {
  16357. record = legendStore.findRecord('series', id);
  16358. }
  16359. if (record && record.get('disabled') !== hidden) {
  16360. record.set('disabled', hidden);
  16361. }
  16362. }
  16363. },
  16364. /**
  16365. *
  16366. * @param {Number} index
  16367. * @param {Boolean} value
  16368. */
  16369. setHiddenByIndex: function(index, value) {
  16370. var me = this;
  16371. if (Ext.isArray(me.getHidden())) {
  16372. // Multi-sprite series like Pie and StackedCartesian.
  16373. me.getHidden()[index] = value;
  16374. me.updateHidden(me.getHidden());
  16375. me.updateLegendStore(value, index);
  16376. } else {
  16377. me.setHidden(value);
  16378. }
  16379. },
  16380. getStrokeColorsFromFillColors: function(colors) {
  16381. var me = this,
  16382. darker = me.getUseDarkerStrokeColor(),
  16383. darkerRatio = (Ext.isNumber(darker) ? darker : me.darkerStrokeRatio),
  16384. strokeColors;
  16385. if (darker) {
  16386. strokeColors = Ext.Array.map(colors, function(color) {
  16387. color = Ext.isString(color) ? color : color.stops[0].color;
  16388. color = Ext.util.Color.fromString(color);
  16389. return color.createDarker(darkerRatio).toString();
  16390. });
  16391. } else {
  16392. strokeColors = Ext.Array.clone(colors);
  16393. }
  16394. return strokeColors;
  16395. },
  16396. updateThemeColors: function(colors) {
  16397. var me = this,
  16398. theme = me.getThemeStyle(),
  16399. fillColors = Ext.Array.clone(colors),
  16400. strokeColors = me.getStrokeColorsFromFillColors(colors),
  16401. newSubStyle = {
  16402. fillStyle: fillColors,
  16403. strokeStyle: strokeColors
  16404. };
  16405. theme.subStyle = Ext.apply(theme.subStyle || {}, newSubStyle);
  16406. theme.markerSubStyle = Ext.apply(theme.markerSubStyle || {}, newSubStyle);
  16407. me.doUpdateStyles();
  16408. if (!me.isConfiguring) {
  16409. me.getChart().refreshLegendStore();
  16410. }
  16411. },
  16412. themeOnlyIfConfigured: {},
  16413. updateTheme: function(theme) {
  16414. var me = this,
  16415. seriesTheme = theme.getSeries(),
  16416. initialConfig = me.getInitialConfig(),
  16417. defaultConfig = me.defaultConfig,
  16418. configs = me.self.getConfigurator().configs,
  16419. genericSeriesTheme = seriesTheme.defaults,
  16420. specificSeriesTheme = seriesTheme[me.type],
  16421. themeOnlyIfConfigured = me.themeOnlyIfConfigured,
  16422. key, value, isObjValue, isUnusedConfig, initialValue, cfg;
  16423. seriesTheme = Ext.merge({}, genericSeriesTheme, specificSeriesTheme);
  16424. for (key in seriesTheme) {
  16425. value = seriesTheme[key];
  16426. cfg = configs[key];
  16427. if (value !== null && value !== undefined && cfg) {
  16428. initialValue = initialConfig[key];
  16429. isObjValue = Ext.isObject(value);
  16430. isUnusedConfig = initialValue === defaultConfig[key];
  16431. if (isObjValue) {
  16432. if (isUnusedConfig && themeOnlyIfConfigured[key]) {
  16433. continue;
  16434. }
  16435. value = Ext.merge({}, value, initialValue);
  16436. }
  16437. if (isUnusedConfig || isObjValue) {
  16438. me[cfg.names.set](value);
  16439. }
  16440. }
  16441. }
  16442. },
  16443. /**
  16444. * @private
  16445. * When the chart's "colors" config changes, these colors are passed onto the series
  16446. * where they are used with the same priority as theme colors, i.e. they do not override
  16447. * the series' "colors" config, nor the series' "style" config, but they do override
  16448. * the colors from the theme's "seriesThemes" config.
  16449. */
  16450. updateChartColors: function(colors) {
  16451. var me = this;
  16452. if (!me.getColors()) {
  16453. me.updateThemeColors(colors);
  16454. }
  16455. },
  16456. updateColors: function(colors) {
  16457. this.updateThemeColors(colors);
  16458. if (!this.isConfiguring) {
  16459. var chart = this.getChart();
  16460. if (chart) {
  16461. chart.refreshLegendStore();
  16462. }
  16463. }
  16464. },
  16465. updateStyle: function() {
  16466. this.doUpdateStyles();
  16467. },
  16468. updateSubStyle: function() {
  16469. this.doUpdateStyles();
  16470. },
  16471. updateThemeStyle: function() {
  16472. this.doUpdateStyles();
  16473. },
  16474. doUpdateStyles: function() {
  16475. var me = this,
  16476. sprites = me.sprites,
  16477. itemInstancing = me.getItemInstancing(),
  16478. ln = sprites && sprites.length,
  16479. // 'showMarkers' updater calls 'series.getSprites()',
  16480. // which we don't want to call here.
  16481. showMarkers = me.getConfig('showMarkers', true),
  16482. style, sprite, marker, i;
  16483. for (i = 0; i < ln; i++) {
  16484. sprite = sprites[i];
  16485. style = me.getStyleByIndex(i);
  16486. if (itemInstancing) {
  16487. sprite.getMarker('items').getTemplate().setAttributes(style);
  16488. }
  16489. sprite.setAttributes(style);
  16490. marker = sprite.isMarkerHolder && sprite.getMarker('markers');
  16491. if (marker) {
  16492. marker.getTemplate().setAttributes(me.getMarkerStyleByIndex(i));
  16493. }
  16494. }
  16495. },
  16496. getStyleWithTheme: function() {
  16497. var me = this,
  16498. theme = me.getThemeStyle(),
  16499. style = Ext.clone(me.getStyle());
  16500. if (theme && theme.style) {
  16501. Ext.applyIf(style, theme.style);
  16502. }
  16503. return style;
  16504. },
  16505. getSubStyleWithTheme: function() {
  16506. var me = this,
  16507. theme = me.getThemeStyle(),
  16508. subStyle = Ext.clone(me.getSubStyle());
  16509. if (theme && theme.subStyle) {
  16510. Ext.applyIf(subStyle, theme.subStyle);
  16511. }
  16512. return subStyle;
  16513. },
  16514. getStyleByIndex: function(i) {
  16515. var me = this,
  16516. theme = me.getThemeStyle(),
  16517. style, themeStyle, subStyle, themeSubStyle,
  16518. result = {};
  16519. style = me.getStyle();
  16520. themeStyle = (theme && theme.style) || {};
  16521. subStyle = me.styleDataForIndex(me.getSubStyle(), i);
  16522. themeSubStyle = me.styleDataForIndex((theme && theme.subStyle), i);
  16523. Ext.apply(result, themeStyle);
  16524. Ext.apply(result, themeSubStyle);
  16525. Ext.apply(result, style);
  16526. Ext.apply(result, subStyle);
  16527. return result;
  16528. },
  16529. getMarkerStyleByIndex: function(i) {
  16530. var me = this,
  16531. theme = me.getThemeStyle(),
  16532. style, themeStyle, subStyle, themeSubStyle, markerStyle, themeMarkerStyle, markerSubStyle, themeMarkerSubStyle,
  16533. result = {};
  16534. style = me.getStyle();
  16535. themeStyle = (theme && theme.style) || {};
  16536. // 'series.updateHidden()' will update 'series.subStyle.hidden' config
  16537. // with the value of the 'series.hidden' config.
  16538. // But we also need to account for 'series.showMarkers' config
  16539. // to determine whether the markers should be hidden or not.
  16540. subStyle = me.styleDataForIndex(me.getSubStyle(), i);
  16541. if (subStyle.hasOwnProperty('hidden')) {
  16542. subStyle.hidden = subStyle.hidden || !this.getConfig('showMarkers', true);
  16543. }
  16544. themeSubStyle = me.styleDataForIndex((theme && theme.subStyle), i);
  16545. markerStyle = me.getMarker();
  16546. themeMarkerStyle = (theme && theme.marker) || {};
  16547. markerSubStyle = me.getMarkerSubStyle();
  16548. themeMarkerSubStyle = me.styleDataForIndex((theme && theme.markerSubStyle), i);
  16549. Ext.apply(result, themeStyle);
  16550. Ext.apply(result, themeSubStyle);
  16551. Ext.apply(result, themeMarkerStyle);
  16552. Ext.apply(result, themeMarkerSubStyle);
  16553. Ext.apply(result, style);
  16554. Ext.apply(result, subStyle);
  16555. Ext.apply(result, markerStyle);
  16556. Ext.apply(result, markerSubStyle);
  16557. return result;
  16558. },
  16559. styleDataForIndex: function(style, i) {
  16560. var value, name,
  16561. result = {};
  16562. if (style) {
  16563. for (name in style) {
  16564. value = style[name];
  16565. if (Ext.isArray(value)) {
  16566. result[name] = value[i % value.length];
  16567. } else {
  16568. result[name] = value;
  16569. }
  16570. }
  16571. }
  16572. return result;
  16573. },
  16574. /**
  16575. * @method
  16576. * For a given x/y point relative to the main rect, find a corresponding item from this
  16577. * series, if any.
  16578. * @param {Number} x
  16579. * @param {Number} y
  16580. * @param {Object} [target] optional target to receive the result
  16581. * @return {Object} An object describing the item, or null if there is no matching item.
  16582. * The exact contents of this object will vary by series type, but should always contain
  16583. * at least the following:
  16584. *
  16585. * @return {Ext.data.Model} return.record the record of the item.
  16586. * @return {Array} return.point the x/y coordinates relative to the chart box
  16587. * of a single point for this data item, which can be used as e.g. a tooltip anchor
  16588. * point.
  16589. * @return {Ext.draw.sprite.Sprite} return.sprite the item's rendering Sprite.
  16590. * @return {Number} return.subSprite the index if sprite is an instancing sprite.
  16591. */
  16592. getItemForPoint: Ext.emptyFn,
  16593. /**
  16594. * Returns a series item by index and (optional) category.
  16595. * @param {Number} index The index of the item (matches store record index).
  16596. * @param {String} [category] The category of item, e.g.: 'items', 'markers', 'sprites'.
  16597. * @return {Object} item
  16598. */
  16599. getItemByIndex: function(index, category) {
  16600. var me = this,
  16601. sprites = me.getSprites(),
  16602. sprite = sprites && sprites[0],
  16603. item;
  16604. if (!sprite) {
  16605. return;
  16606. }
  16607. // 'category' is not defined, making our best guess here.
  16608. if (category === undefined && sprite.isMarkerHolder) {
  16609. category = me.getItemInstancing() ? 'items' : 'markers';
  16610. } else if (!category || category === '' || category === 'sprites') {
  16611. sprite = sprites[index];
  16612. }
  16613. if (sprite) {
  16614. item = {
  16615. series: me,
  16616. category: category,
  16617. index: index,
  16618. record: me.getStore().getData().items[index],
  16619. field: me.getYField(),
  16620. sprite: sprite
  16621. };
  16622. return item;
  16623. }
  16624. },
  16625. onSpriteAnimationStart: function(sprite) {
  16626. this.fireEvent('animationstart', this, sprite);
  16627. },
  16628. onSpriteAnimationEnd: function(sprite) {
  16629. this.fireEvent('animationend', this, sprite);
  16630. },
  16631. resolveListenerScope: function(defaultScope) {
  16632. // Override the Observable's method to redirect listener scope
  16633. // resolution to the chart.
  16634. var me = this,
  16635. namedScope = Ext._namedScopes[defaultScope],
  16636. chart = me.getChart(),
  16637. scope;
  16638. if (!namedScope) {
  16639. scope = chart ? chart.resolveListenerScope(defaultScope, false) : (defaultScope || me);
  16640. } else if (namedScope.isThis) {
  16641. scope = me;
  16642. } else if (namedScope.isController) {
  16643. scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
  16644. } else if (namedScope.isSelf) {
  16645. scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
  16646. // Class body listener. No chart controller, nor chart container controller.
  16647. if (scope === chart && !chart.getInheritedConfig('defaultListenerScope')) {
  16648. scope = me;
  16649. }
  16650. }
  16651. return scope;
  16652. },
  16653. /**
  16654. * Provide legend information to target array.
  16655. *
  16656. * @param {Array} target
  16657. *
  16658. * The information consists:
  16659. * @param {String} target.name
  16660. * @param {String} target.mark
  16661. * @param {Boolean} target.disabled
  16662. * @param {String} target.series
  16663. * @param {Number} target.index
  16664. */
  16665. provideLegendInfo: function(target) {
  16666. var me = this,
  16667. style = me.getSubStyleWithTheme(),
  16668. fill = style.fillStyle;
  16669. if (Ext.isArray(fill)) {
  16670. fill = fill[0];
  16671. }
  16672. target.push({
  16673. name: me.getTitle() || me.getYField() || me.getId(),
  16674. mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
  16675. disabled: me.getHidden(),
  16676. series: me.getId(),
  16677. index: 0
  16678. });
  16679. },
  16680. clearSprites: function() {
  16681. var sprites = this.sprites,
  16682. sprite, i, ln;
  16683. for (i = 0 , ln = sprites.length; i < ln; i++) {
  16684. sprite = sprites[i];
  16685. if (sprite && sprite.isSprite) {
  16686. sprite.destroy();
  16687. }
  16688. }
  16689. this.sprites = [];
  16690. },
  16691. destroy: function() {
  16692. var me = this,
  16693. store = me._store,
  16694. // Peek at the config so we don't create one just to destroy it
  16695. tooltip = me.getConfig('tooltip', true);
  16696. if (store && store.getAutoDestroy()) {
  16697. Ext.destroy(store);
  16698. }
  16699. me.setChart(null);
  16700. me.clearListeners();
  16701. if (tooltip) {
  16702. Ext.destroy(tooltip);
  16703. }
  16704. me.callParent();
  16705. }
  16706. });
  16707. /**
  16708. * @class Ext.chart.interactions.Abstract
  16709. *
  16710. * Defines a common abstract parent class for all interactions.
  16711. *
  16712. */
  16713. Ext.define('Ext.chart.interactions.Abstract', {
  16714. xtype: 'interaction',
  16715. mixins: {
  16716. observable: 'Ext.mixin.Observable'
  16717. },
  16718. config: {
  16719. /**
  16720. * @cfg {Object} gesture
  16721. * Maps gestures that should be used for starting/maintaining/ending the interaction
  16722. * to corresponding class methods.
  16723. * @private
  16724. */
  16725. gestures: {
  16726. tap: 'onGesture'
  16727. },
  16728. /**
  16729. * @cfg {Ext.chart.AbstractChart} chart The chart that the interaction is bound.
  16730. */
  16731. chart: null,
  16732. /**
  16733. * @cfg {Boolean} enabled 'true' if the interaction is enabled.
  16734. */
  16735. enabled: true
  16736. },
  16737. /**
  16738. * Android device is emerging too many events so if we re-render every frame it will take forever to finish a frame.
  16739. * This throttle technique will limit the timespan between two frames.
  16740. */
  16741. throttleGap: 0,
  16742. stopAnimationBeforeSync: false,
  16743. constructor: function(config) {
  16744. var me = this,
  16745. id;
  16746. config = config || {};
  16747. if ('id' in config) {
  16748. id = config.id;
  16749. } else if ('id' in me.config) {
  16750. id = me.config.id;
  16751. } else {
  16752. id = me.getId();
  16753. }
  16754. me.setId(id);
  16755. me.mixins.observable.constructor.call(me, config);
  16756. },
  16757. updateChart: function(newChart, oldChart) {
  16758. var me = this;
  16759. if (oldChart === newChart) {
  16760. return;
  16761. }
  16762. if (oldChart) {
  16763. oldChart.unregister(me);
  16764. me.removeChartListener(oldChart);
  16765. }
  16766. if (newChart) {
  16767. newChart.register(me);
  16768. me.addChartListener();
  16769. }
  16770. },
  16771. updateEnabled: function(enabled) {
  16772. var me = this,
  16773. chart = me.getChart();
  16774. if (chart) {
  16775. if (enabled) {
  16776. me.addChartListener();
  16777. } else {
  16778. me.removeChartListener(chart);
  16779. }
  16780. }
  16781. },
  16782. /**
  16783. * @method
  16784. * @protected
  16785. * Placeholder method.
  16786. */
  16787. onGesture: Ext.emptyFn,
  16788. /**
  16789. * @protected
  16790. * Find and return a single series item corresponding to the given event,
  16791. * or null if no matching item is found.
  16792. * @param {Event} e
  16793. * @return {Object} the item object or null if none found.
  16794. */
  16795. getItemForEvent: function(e) {
  16796. var me = this,
  16797. chart = me.getChart(),
  16798. chartXY = chart.getEventXY(e);
  16799. return chart.getItemForPoint(chartXY[0], chartXY[1]);
  16800. },
  16801. /**
  16802. * Find and return all series items corresponding to the given event.
  16803. * @param {Event} e
  16804. * @return {Array} array of matching item objects
  16805. * @private
  16806. * @deprecated 6.5.2 This method is deprecated
  16807. */
  16808. getItemsForEvent: function(e) {
  16809. var me = this,
  16810. chart = me.getChart(),
  16811. chartXY = chart.getEventXY(e);
  16812. return chart.getItemsForPoint(chartXY[0], chartXY[1]);
  16813. },
  16814. /**
  16815. * @private
  16816. */
  16817. addChartListener: function() {
  16818. var me = this,
  16819. chart = me.getChart(),
  16820. gestures = me.getGestures(),
  16821. gesture;
  16822. if (!me.getEnabled()) {
  16823. return;
  16824. }
  16825. function insertGesture(name, fn) {
  16826. chart.addElementListener(name, // wrap the handler so it does not fire if the event is locked by another interaction
  16827. me.listeners[name] = function(e) {
  16828. var locks = me.getLocks(),
  16829. result;
  16830. if (me.getEnabled() && (!(name in locks) || locks[name] === me)) {
  16831. result = (Ext.isFunction(fn) ? fn : me[fn]).apply(this, arguments);
  16832. if (result === false && e && e.stopPropagation) {
  16833. e.stopPropagation();
  16834. }
  16835. return result;
  16836. }
  16837. }, me);
  16838. }
  16839. me.listeners = me.listeners || {};
  16840. for (gesture in gestures) {
  16841. insertGesture(gesture, gestures[gesture]);
  16842. }
  16843. },
  16844. removeChartListener: function(chart) {
  16845. var me = this,
  16846. gestures = me.getGestures(),
  16847. gesture;
  16848. function removeGesture(name) {
  16849. var fn = me.listeners[name];
  16850. if (fn) {
  16851. chart.removeElementListener(name, fn);
  16852. delete me.listeners[name];
  16853. }
  16854. }
  16855. if (me.listeners) {
  16856. for (gesture in gestures) {
  16857. removeGesture(gesture);
  16858. }
  16859. }
  16860. },
  16861. lockEvents: function() {
  16862. var me = this,
  16863. locks = me.getLocks(),
  16864. args = Array.prototype.slice.call(arguments),
  16865. i = args.length;
  16866. while (i--) {
  16867. locks[args[i]] = me;
  16868. }
  16869. },
  16870. unlockEvents: function() {
  16871. var locks = this.getLocks(),
  16872. args = Array.prototype.slice.call(arguments),
  16873. i = args.length;
  16874. while (i--) {
  16875. delete locks[args[i]];
  16876. }
  16877. },
  16878. getLocks: function() {
  16879. var chart = this.getChart();
  16880. return chart.lockedEvents || (chart.lockedEvents = {});
  16881. },
  16882. doSync: function() {
  16883. var me = this,
  16884. chart = me.getChart();
  16885. if (me.syncTimer) {
  16886. Ext.undefer(me.syncTimer);
  16887. me.syncTimer = null;
  16888. }
  16889. if (me.stopAnimationBeforeSync) {
  16890. chart.animationSuspendCount++;
  16891. }
  16892. chart.redraw();
  16893. if (me.stopAnimationBeforeSync) {
  16894. chart.animationSuspendCount--;
  16895. }
  16896. me.syncThrottle = Date.now() + me.throttleGap;
  16897. },
  16898. sync: function() {
  16899. var me = this;
  16900. if (me.throttleGap && Ext.frameStartTime < me.syncThrottle) {
  16901. if (me.syncTimer) {
  16902. return;
  16903. }
  16904. me.syncTimer = Ext.defer(function() {
  16905. me.doSync();
  16906. }, me.throttleGap);
  16907. } else {
  16908. me.doSync();
  16909. }
  16910. },
  16911. getItemId: function() {
  16912. return this.getId();
  16913. },
  16914. isXType: function(xtype) {
  16915. return xtype === 'interaction';
  16916. },
  16917. destroy: function() {
  16918. var me = this;
  16919. me.setChart(null);
  16920. delete me.listeners;
  16921. me.callParent();
  16922. }
  16923. }, function() {
  16924. if (Ext.os.is.Android4) {
  16925. this.prototype.throttleGap = 40;
  16926. }
  16927. });
  16928. /**
  16929. * Mixin that provides the functionality to place markers.
  16930. */
  16931. Ext.define('Ext.chart.MarkerHolder', {
  16932. extend: 'Ext.Mixin',
  16933. requires: [
  16934. 'Ext.chart.Markers'
  16935. ],
  16936. mixinConfig: {
  16937. id: 'markerHolder',
  16938. after: {
  16939. constructor: 'constructor',
  16940. preRender: 'preRender'
  16941. },
  16942. before: {
  16943. destroy: 'destroy'
  16944. }
  16945. },
  16946. isMarkerHolder: true,
  16947. // The combined transformation applied to the sprite by its parents.
  16948. // Does not include the transformation matrix of the sprite itself.
  16949. surfaceMatrix: null,
  16950. // The inverse of the above transformation to go back to the original state.
  16951. inverseSurfaceMatrix: null,
  16952. deprecated: {
  16953. 6: {
  16954. methods: {
  16955. /**
  16956. * Returns the markers bound to the given name.
  16957. * @param {String} name The name of the marker (e.g., "items", "labels", etc.).
  16958. * @return {Ext.chart.Markers[]}
  16959. * @method getBoundMarker
  16960. * @deprecated 6.0 Use {@link #getMarker} instead.
  16961. */
  16962. getBoundMarker: {
  16963. message: "Please use the 'getMarker' method instead.",
  16964. fn: function(name) {
  16965. var marker = this.boundMarkers[name];
  16966. return marker ? [
  16967. marker
  16968. ] : marker;
  16969. }
  16970. }
  16971. }
  16972. }
  16973. },
  16974. constructor: function() {
  16975. this.boundMarkers = {};
  16976. this.cleanRedraw = false;
  16977. },
  16978. /**
  16979. * Registers the given marker with the marker holder under the specified name.
  16980. * @param {String} name The name of the marker (e.g., "items", "labels", etc.).
  16981. * @param {Ext.chart.Markers} marker
  16982. */
  16983. bindMarker: function(name, marker) {
  16984. var me = this,
  16985. markers = me.boundMarkers;
  16986. if (marker && marker.isMarkers) {
  16987. //<debug>
  16988. if (markers[name] && markers[name] === marker) {
  16989. Ext.log.warn(me.getId(), " (MarkerHolder): the Markers instance '", marker.getId(), "' is already bound under the name '", name, "'.");
  16990. }
  16991. //</debug>
  16992. me.releaseMarker(name);
  16993. markers[name] = marker;
  16994. marker.on('destroy', me.onMarkerDestroy, me);
  16995. }
  16996. },
  16997. onMarkerDestroy: function(marker) {
  16998. this.releaseMarker(marker);
  16999. },
  17000. /**
  17001. * Unregisters the given marker or a marker with the given name.
  17002. * Providing a name of the marker is more efficient as it avoids lookup.
  17003. * @param marker {String/Ext.chart.Markers}
  17004. * @return {Ext.chart.Markers} Released marker or null.
  17005. */
  17006. releaseMarker: function(marker) {
  17007. var markers = this.boundMarkers,
  17008. name;
  17009. if (marker && marker.isMarkers) {
  17010. for (name in markers) {
  17011. if (markers[name] === marker) {
  17012. delete markers[name];
  17013. break;
  17014. }
  17015. }
  17016. } else {
  17017. name = marker;
  17018. marker = markers[name];
  17019. delete markers[name];
  17020. }
  17021. return marker || null;
  17022. },
  17023. /**
  17024. * Returns the marker bound to the given name (or null). See {@link #bindMarker}.
  17025. * @param {String} name The name of the marker (e.g., "items", "labels", etc.).
  17026. * @return {Ext.chart.Markers}
  17027. */
  17028. getMarker: function(name) {
  17029. return this.boundMarkers[name] || null;
  17030. },
  17031. preRender: function(surface, ctx, rect) {
  17032. var me = this,
  17033. id = me.getId(),
  17034. boundMarkers = me.boundMarkers,
  17035. parent = me.getParent(),
  17036. name, marker, matrix;
  17037. if (me.surfaceMatrix) {
  17038. matrix = me.surfaceMatrix.set(1, 0, 0, 1, 0, 0);
  17039. } else {
  17040. matrix = me.surfaceMatrix = new Ext.draw.Matrix();
  17041. }
  17042. me.cleanRedraw = !me.attr.dirty;
  17043. if (!me.cleanRedraw) {
  17044. for (name in boundMarkers) {
  17045. marker = boundMarkers[name];
  17046. if (marker) {
  17047. marker.clear(id);
  17048. }
  17049. }
  17050. }
  17051. // Parent can be either a sprite (like a composite or instancing)
  17052. // or a surface. First, climb up and apply transformations of the
  17053. // parent sprites.
  17054. while (parent && parent.attr && parent.attr.matrix) {
  17055. matrix.prependMatrix(parent.attr.matrix);
  17056. parent = parent.getParent();
  17057. }
  17058. // Finally, apply the transformation used by the surface.
  17059. matrix.prependMatrix(parent.matrix);
  17060. me.surfaceMatrix = matrix;
  17061. me.inverseSurfaceMatrix = matrix.inverse(me.inverseSurfaceMatrix);
  17062. },
  17063. putMarker: function(name, attr, index, bypassNormalization, keepRevision) {
  17064. var marker = this.boundMarkers[name];
  17065. if (marker) {
  17066. marker.putMarkerFor(this.getId(), attr, index, bypassNormalization, keepRevision);
  17067. }
  17068. },
  17069. getMarkerBBox: function(name, index, isWithoutTransform) {
  17070. var marker = this.boundMarkers[name];
  17071. if (marker) {
  17072. return marker.getMarkerBBoxFor(this.getId(), index, isWithoutTransform);
  17073. }
  17074. },
  17075. destroy: function() {
  17076. var boundMarkers = this.boundMarkers,
  17077. name, marker;
  17078. for (name in boundMarkers) {
  17079. marker = boundMarkers[name];
  17080. marker.destroy();
  17081. }
  17082. }
  17083. });
  17084. /**
  17085. * @private
  17086. * @class Ext.chart.axis.sprite.Axis
  17087. * @extends Ext.draw.sprite.Sprite
  17088. *
  17089. * The axis sprite. Currently all types of the axis will be rendered with this sprite.
  17090. */
  17091. Ext.define('Ext.chart.axis.sprite.Axis', {
  17092. extend: 'Ext.draw.sprite.Sprite',
  17093. alias: 'sprite.axis',
  17094. type: 'axis',
  17095. mixins: {
  17096. markerHolder: 'Ext.chart.MarkerHolder'
  17097. },
  17098. requires: [
  17099. 'Ext.draw.sprite.Text'
  17100. ],
  17101. inheritableStatics: {
  17102. def: {
  17103. processors: {
  17104. /**
  17105. * @cfg {Boolean} grid 'true' if the axis has a grid.
  17106. */
  17107. grid: 'bool',
  17108. /**
  17109. * @cfg {Boolean} axisLine 'true' if the main line of the axis is drawn.
  17110. */
  17111. axisLine: 'bool',
  17112. /**
  17113. * @cfg {Boolean} minorTicks 'true' if the axis has sub ticks.
  17114. */
  17115. minorTicks: 'bool',
  17116. /**
  17117. * @cfg {Number} minorTickSize The length of the minor ticks.
  17118. */
  17119. minorTickSize: 'number',
  17120. /**
  17121. * @cfg {Boolean} majorTicks 'true' if the axis has major ticks.
  17122. */
  17123. majorTicks: 'bool',
  17124. /**
  17125. * @cfg {Number} majorTickSize The length of the major ticks.
  17126. */
  17127. majorTickSize: 'number',
  17128. /**
  17129. * @cfg {Number} length The total length of the axis.
  17130. */
  17131. length: 'number',
  17132. /**
  17133. * @private
  17134. * @cfg {Number} startGap Axis start determined by the chart inset padding.
  17135. */
  17136. startGap: 'number',
  17137. /**
  17138. * @private
  17139. * @cfg {Number} endGap Axis end determined by the chart inset padding.
  17140. */
  17141. endGap: 'number',
  17142. /**
  17143. * @cfg {Number} dataMin The minimum value of the axis data.
  17144. */
  17145. dataMin: 'number',
  17146. /**
  17147. * @cfg {Number} dataMax The maximum value of the axis data.
  17148. */
  17149. dataMax: 'number',
  17150. /**
  17151. * @cfg {Number} visibleMin The minimum value that is displayed.
  17152. */
  17153. visibleMin: 'number',
  17154. /**
  17155. * @cfg {Number} visibleMax The maximum value that is displayed.
  17156. */
  17157. visibleMax: 'number',
  17158. /**
  17159. * @cfg {String} position The position of the axis on the chart.
  17160. */
  17161. position: 'enums(left,right,top,bottom,angular,radial,gauge)',
  17162. /**
  17163. * @cfg {Number} minStepSize The minimum step size between ticks.
  17164. */
  17165. minStepSize: 'number',
  17166. /**
  17167. * @private
  17168. * @cfg {Number} estStepSize The estimated step size between ticks.
  17169. */
  17170. estStepSize: 'number',
  17171. /**
  17172. * @private
  17173. * Unused.
  17174. */
  17175. titleOffset: 'number',
  17176. /**
  17177. * @cfg {Number} [textPadding=0]
  17178. * The padding around axis labels to determine collision.
  17179. * The default is 0 for all axes except horizontal axes of cartesian charts,
  17180. * where the default is 5 to prevent axis labels from blending one into another.
  17181. * This default is defined in the {@link Ext.chart.theme.Base#axis axis} config
  17182. * of the {@link Ext.chart.theme.Base Base} theme.
  17183. * You may want to change this default to a smaller number or 0, if you have
  17184. * horizontal axis labels rotated, which allows for more text to fit in.
  17185. */
  17186. textPadding: 'number',
  17187. /**
  17188. * @cfg {Number} min The minimum value of the axis.
  17189. * `min` and {@link #max} attributes represent the effective range of the axis
  17190. * after segmentation, layout, and range reconciliation between axes.
  17191. */
  17192. min: 'number',
  17193. /**
  17194. * @cfg {Number} max The maximum value of the axis.
  17195. * {@link #min} and `max` attributes represent the effective range of the axis
  17196. * after segmentation, layout, and range reconciliation between axes.
  17197. */
  17198. max: 'number',
  17199. /**
  17200. * @cfg {Number} centerX The central point of the angular axis on the x-axis.
  17201. */
  17202. centerX: 'number',
  17203. /**
  17204. * @cfg {Number} centerY The central point of the angular axis on the y-axis.
  17205. */
  17206. centerY: 'number',
  17207. /**
  17208. * @private
  17209. * @cfg {Number} radius
  17210. * Unused.
  17211. */
  17212. radius: 'number',
  17213. /**
  17214. * @private
  17215. */
  17216. totalAngle: 'number',
  17217. /**
  17218. * @cfg {Number} baseRotation The starting rotation of the angular axis.
  17219. */
  17220. baseRotation: 'number',
  17221. /**
  17222. * @private
  17223. * Unused.
  17224. */
  17225. data: 'default',
  17226. /**
  17227. * @cfg {Boolean} 'true' if the estimated step size is adjusted by text size.
  17228. */
  17229. enlargeEstStepSizeByText: 'bool'
  17230. },
  17231. defaults: {
  17232. grid: false,
  17233. axisLine: true,
  17234. minorTicks: false,
  17235. minorTickSize: 3,
  17236. majorTicks: true,
  17237. majorTickSize: 5,
  17238. length: 0,
  17239. startGap: 0,
  17240. endGap: 0,
  17241. visibleMin: 0,
  17242. visibleMax: 1,
  17243. dataMin: 0,
  17244. dataMax: 1,
  17245. position: '',
  17246. minStepSize: 0,
  17247. estStepSize: 20,
  17248. min: 0,
  17249. max: 1,
  17250. centerX: 0,
  17251. centerY: 0,
  17252. radius: 1,
  17253. baseRotation: 0,
  17254. data: null,
  17255. titleOffset: 0,
  17256. textPadding: 0,
  17257. scalingCenterY: 0,
  17258. scalingCenterX: 0,
  17259. // Override default
  17260. strokeStyle: 'black',
  17261. enlargeEstStepSizeByText: false
  17262. },
  17263. triggers: {
  17264. minorTickSize: 'bbox',
  17265. majorTickSize: 'bbox',
  17266. position: 'bbox,layout',
  17267. axisLine: 'bbox,layout',
  17268. minorTicks: 'layout',
  17269. min: 'layout',
  17270. max: 'layout',
  17271. length: 'layout',
  17272. minStepSize: 'layout',
  17273. estStepSize: 'layout',
  17274. data: 'layout',
  17275. dataMin: 'layout',
  17276. dataMax: 'layout',
  17277. visibleMin: 'layout',
  17278. visibleMax: 'layout',
  17279. enlargeEstStepSizeByText: 'layout'
  17280. },
  17281. updaters: {
  17282. layout: 'layoutUpdater'
  17283. }
  17284. }
  17285. },
  17286. config: {
  17287. /**
  17288. * @cfg {Object} label
  17289. *
  17290. * The label configuration object for the Axis. This object may include style attributes
  17291. * like `spacing`, `padding`, `font` that receives a string or number and
  17292. * returns a new string with the modified values.
  17293. */
  17294. label: null,
  17295. /**
  17296. * @cfg {Number} labelOffset
  17297. * The distance between the label and the edge of a major tick.
  17298. * Only applicable for 'gauge' and 'angular' axes.
  17299. */
  17300. labelOffset: 10,
  17301. /**
  17302. * @cfg {Object|Ext.chart.axis.layout.Layout} layout The layout configuration used by the axis.
  17303. */
  17304. layout: null,
  17305. /**
  17306. * @cfg {Object|Ext.chart.axis.segmenter.Segmenter} segmenter The method of segmenter used by the axis.
  17307. */
  17308. segmenter: null,
  17309. /**
  17310. * @cfg {Function} renderer Allows direct customisation of rendered axis sprites.
  17311. */
  17312. renderer: null,
  17313. /**
  17314. * @private
  17315. * @cfg {Object} layoutContext Stores the context after calculating layout.
  17316. */
  17317. layoutContext: null,
  17318. /**
  17319. * @cfg {Ext.chart.axis.Axis} axis The axis represented by this sprite.
  17320. */
  17321. axis: null
  17322. },
  17323. thickness: 0,
  17324. stepSize: 0,
  17325. getBBox: function() {
  17326. return null;
  17327. },
  17328. defaultRenderer: function(v) {
  17329. // 'this' pointer in this case is a layoutContext
  17330. return this.segmenter.renderer(v, this);
  17331. },
  17332. layoutUpdater: function() {
  17333. var me = this,
  17334. chart = me.getAxis().getChart();
  17335. if (chart.isInitializing) {
  17336. return;
  17337. }
  17338. var attr = me.attr,
  17339. layout = me.getLayout(),
  17340. isRtl = chart.getInherited().rtl,
  17341. dataRange = attr.dataMax - attr.dataMin,
  17342. min = attr.dataMin + dataRange * attr.visibleMin,
  17343. max = attr.dataMin + dataRange * attr.visibleMax,
  17344. range = max - min,
  17345. position = attr.position,
  17346. context = {
  17347. attr: attr,
  17348. segmenter: me.getSegmenter(),
  17349. renderer: me.defaultRenderer
  17350. };
  17351. if (position === 'left' || position === 'right') {
  17352. attr.translationX = 0;
  17353. attr.translationY = max * attr.length / range;
  17354. attr.scalingX = 1;
  17355. attr.scalingY = -attr.length / range;
  17356. attr.scalingCenterY = 0;
  17357. attr.scalingCenterX = 0;
  17358. me.applyTransformations(true);
  17359. } else if (position === 'top' || position === 'bottom') {
  17360. if (isRtl) {
  17361. attr.translationX = attr.length + min * attr.length / range + 1;
  17362. } else {
  17363. attr.translationX = -min * attr.length / range;
  17364. }
  17365. attr.translationY = 0;
  17366. attr.scalingX = (isRtl ? -1 : 1) * attr.length / range;
  17367. attr.scalingY = 1;
  17368. attr.scalingCenterY = 0;
  17369. attr.scalingCenterX = 0;
  17370. me.applyTransformations(true);
  17371. }
  17372. if (layout) {
  17373. layout.calculateLayout(context);
  17374. me.setLayoutContext(context);
  17375. }
  17376. },
  17377. iterate: function(snaps, fn) {
  17378. var i, position, id, axis, floatingAxes, floatingValues,
  17379. some = Ext.Array.some,
  17380. abs = Math.abs,
  17381. threshold;
  17382. if (snaps.getLabel) {
  17383. // Discrete layout.
  17384. if (snaps.min < snaps.from) {
  17385. fn.call(this, snaps.min, snaps.getLabel(snaps.min), -1, snaps);
  17386. }
  17387. for (i = 0; i <= snaps.steps; i++) {
  17388. fn.call(this, snaps.get(i), snaps.getLabel(i), i, snaps);
  17389. }
  17390. if (snaps.max > snaps.to) {
  17391. fn.call(this, snaps.max, snaps.getLabel(snaps.max), snaps.steps + 1, snaps);
  17392. }
  17393. } else {
  17394. axis = this.getAxis();
  17395. floatingAxes = axis.floatingAxes;
  17396. floatingValues = [];
  17397. threshold = (snaps.to - snaps.from) / (snaps.steps + 1);
  17398. if (axis.getFloating()) {
  17399. for (id in floatingAxes) {
  17400. floatingValues.push(floatingAxes[id]);
  17401. }
  17402. }
  17403. // Don't render ticks in axes intersection points.
  17404. function isTickVisible(position) {
  17405. return !floatingValues.length || some(floatingValues, function(value) {
  17406. return abs(value - position) > threshold;
  17407. });
  17408. }
  17409. if (snaps.min < snaps.from && isTickVisible(snaps.min)) {
  17410. fn.call(this, snaps.min, snaps.min, -1, snaps);
  17411. }
  17412. for (i = 0; i <= snaps.steps; i++) {
  17413. position = snaps.get(i);
  17414. if (isTickVisible(position)) {
  17415. fn.call(this, position, position, i, snaps);
  17416. }
  17417. }
  17418. if (snaps.max > snaps.to && isTickVisible(snaps.max)) {
  17419. fn.call(this, snaps.max, snaps.max, snaps.steps + 1, snaps);
  17420. }
  17421. }
  17422. },
  17423. renderTicks: function(surface, ctx, layout, clipRect) {
  17424. var me = this,
  17425. attr = me.attr,
  17426. docked = attr.position,
  17427. matrix = attr.matrix,
  17428. halfLineWidth = 0.5 * attr.lineWidth,
  17429. xx = matrix.getXX(),
  17430. dx = matrix.getDX(),
  17431. yy = matrix.getYY(),
  17432. dy = matrix.getDY(),
  17433. majorTicks = layout.majorTicks,
  17434. majorTickSize = attr.majorTickSize,
  17435. minorTicks = layout.minorTicks,
  17436. minorTickSize = attr.minorTickSize;
  17437. if (majorTicks) {
  17438. switch (docked) {
  17439. case 'right':
  17440. function getRightTickFn(size) {
  17441. return function(position, labelText, i) {
  17442. position = surface.roundPixel(position * yy + dy) + halfLineWidth;
  17443. ctx.moveTo(0, position);
  17444. ctx.lineTo(size, position);
  17445. };
  17446. };
  17447. me.iterate(majorTicks, getRightTickFn(majorTickSize));
  17448. minorTicks && me.iterate(minorTicks, getRightTickFn(minorTickSize));
  17449. break;
  17450. case 'left':
  17451. function getLeftTickFn(size) {
  17452. return function(position, labelText, i) {
  17453. position = surface.roundPixel(position * yy + dy) + halfLineWidth;
  17454. ctx.moveTo(clipRect[2] - size, position);
  17455. ctx.lineTo(clipRect[2], position);
  17456. };
  17457. };
  17458. me.iterate(majorTicks, getLeftTickFn(majorTickSize));
  17459. minorTicks && me.iterate(minorTicks, getLeftTickFn(minorTickSize));
  17460. break;
  17461. case 'bottom':
  17462. function getBottomTickFn(size) {
  17463. return function(position, labelText, i) {
  17464. position = surface.roundPixel(position * xx + dx) - halfLineWidth;
  17465. ctx.moveTo(position, 0);
  17466. ctx.lineTo(position, size);
  17467. };
  17468. };
  17469. me.iterate(majorTicks, getBottomTickFn(majorTickSize));
  17470. minorTicks && me.iterate(minorTicks, getBottomTickFn(minorTickSize));
  17471. break;
  17472. case 'top':
  17473. function getTopTickFn(size) {
  17474. return function(position, labelText, i) {
  17475. position = surface.roundPixel(position * xx + dx) - halfLineWidth;
  17476. ctx.moveTo(position, clipRect[3]);
  17477. ctx.lineTo(position, clipRect[3] - size);
  17478. };
  17479. };
  17480. me.iterate(majorTicks, getTopTickFn(majorTickSize));
  17481. minorTicks && me.iterate(minorTicks, getTopTickFn(minorTickSize));
  17482. break;
  17483. case 'angular':
  17484. me.iterate(majorTicks, function(position, labelText, i) {
  17485. position = position / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
  17486. ctx.moveTo(attr.centerX + (attr.length) * Math.cos(position), attr.centerY + (attr.length) * Math.sin(position));
  17487. ctx.lineTo(attr.centerX + (attr.length + majorTickSize) * Math.cos(position), attr.centerY + (attr.length + majorTickSize) * Math.sin(position));
  17488. });
  17489. break;
  17490. case 'gauge':
  17491. var gaugeAngles = me.getGaugeAngles();
  17492. me.iterate(majorTicks, function(position, labelText, i) {
  17493. position = (position - attr.min) / (attr.max - attr.min) * attr.totalAngle - attr.totalAngle + gaugeAngles.start;
  17494. ctx.moveTo(attr.centerX + (attr.length) * Math.cos(position), attr.centerY + (attr.length) * Math.sin(position));
  17495. ctx.lineTo(attr.centerX + (attr.length + majorTickSize) * Math.cos(position), attr.centerY + (attr.length + majorTickSize) * Math.sin(position));
  17496. });
  17497. break;
  17498. }
  17499. }
  17500. },
  17501. renderLabels: function(surface, ctx, layoutContext, clipRect) {
  17502. var me = this,
  17503. attr = me.attr,
  17504. halfLineWidth = 0.5 * attr.lineWidth,
  17505. docked = attr.position,
  17506. matrix = attr.matrix,
  17507. textPadding = attr.textPadding,
  17508. xx = matrix.getXX(),
  17509. dx = matrix.getDX(),
  17510. yy = matrix.getYY(),
  17511. dy = matrix.getDY(),
  17512. thickness = 0,
  17513. majorTicks = layoutContext.majorTicks,
  17514. tickPadding = Math.max(attr.majorTickSize, attr.minorTickSize) + attr.lineWidth,
  17515. isBBoxIntersect = Ext.draw.Draw.isBBoxIntersect,
  17516. label = me.getLabel(),
  17517. font,
  17518. labelOffset = me.getLabelOffset(),
  17519. lastLabelText = null,
  17520. textSize = 0,
  17521. textCount = 0,
  17522. segmenter = layoutContext.segmenter,
  17523. renderer = me.getRenderer(),
  17524. axis = me.getAxis(),
  17525. title = axis.getTitle(),
  17526. titleBBox = title && title.attr.text !== '' && title.getBBox(),
  17527. labelInverseMatrix,
  17528. lastBBox = null,
  17529. bbox, fly, text, titlePadding, translation, gaugeAngles;
  17530. if (majorTicks && label && !label.attr.hidden) {
  17531. font = label.attr.font;
  17532. if (ctx.font !== font) {
  17533. ctx.font = font;
  17534. }
  17535. // This can profoundly improve performance.
  17536. label.setAttributes({
  17537. translationX: 0,
  17538. translationY: 0
  17539. }, true);
  17540. label.applyTransformations();
  17541. labelInverseMatrix = label.attr.inverseMatrix.elements.slice(0);
  17542. switch (docked) {
  17543. case 'left':
  17544. titlePadding = titleBBox ? titleBBox.x + titleBBox.width : 0;
  17545. switch (label.attr.textAlign) {
  17546. case 'start':
  17547. translation = surface.roundPixel(titlePadding + dx) - halfLineWidth;
  17548. break;
  17549. case 'end':
  17550. translation = surface.roundPixel(clipRect[2] - tickPadding + dx) - halfLineWidth;
  17551. break;
  17552. default:
  17553. // 'center'
  17554. translation = surface.roundPixel(titlePadding + (clipRect[2] - titlePadding - tickPadding) / 2 + dx) - halfLineWidth;
  17555. };
  17556. label.setAttributes({
  17557. translationX: translation
  17558. }, true);
  17559. break;
  17560. case 'right':
  17561. titlePadding = titleBBox ? clipRect[2] - titleBBox.x : 0;
  17562. switch (label.attr.textAlign) {
  17563. case 'start':
  17564. translation = surface.roundPixel(tickPadding + dx) + halfLineWidth;
  17565. break;
  17566. case 'end':
  17567. translation = surface.roundPixel(clipRect[2] - titlePadding + dx) + halfLineWidth;
  17568. break;
  17569. default:
  17570. // 'center'
  17571. translation = surface.roundPixel(tickPadding + (clipRect[2] - tickPadding - titlePadding) / 2 + dx) + halfLineWidth;
  17572. };
  17573. label.setAttributes({
  17574. translationX: translation
  17575. }, true);
  17576. break;
  17577. case 'top':
  17578. titlePadding = titleBBox ? titleBBox.y + titleBBox.height : 0;
  17579. label.setAttributes({
  17580. translationY: surface.roundPixel(titlePadding + (clipRect[3] - titlePadding - tickPadding) / 2) - halfLineWidth
  17581. }, true);
  17582. break;
  17583. case 'bottom':
  17584. titlePadding = titleBBox ? clipRect[3] - titleBBox.y : 0;
  17585. label.setAttributes({
  17586. translationY: surface.roundPixel(tickPadding + (clipRect[3] - tickPadding - titlePadding) / 2) + halfLineWidth
  17587. }, true);
  17588. break;
  17589. case 'radial':
  17590. label.setAttributes({
  17591. translationX: attr.centerX
  17592. }, true);
  17593. break;
  17594. case 'angular':
  17595. label.setAttributes({
  17596. translationY: attr.centerY
  17597. }, true);
  17598. break;
  17599. case 'gauge':
  17600. label.setAttributes({
  17601. translationY: attr.centerY
  17602. }, true);
  17603. break;
  17604. }
  17605. // TODO: there are better ways to detect collision.
  17606. if (docked === 'left' || docked === 'right') {
  17607. me.iterate(majorTicks, function(position, labelText, i) {
  17608. if (labelText === undefined) {
  17609. return;
  17610. }
  17611. if (renderer) {
  17612. text = Ext.callback(renderer, null, [
  17613. axis,
  17614. labelText,
  17615. layoutContext,
  17616. lastLabelText
  17617. ], 0, axis);
  17618. } else {
  17619. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17620. }
  17621. lastLabelText = labelText;
  17622. label.setAttributes({
  17623. text: String(text),
  17624. translationY: surface.roundPixel(position * yy + dy)
  17625. }, true);
  17626. label.applyTransformations();
  17627. thickness = Math.max(thickness, label.getBBox().width + tickPadding);
  17628. fly = Ext.draw.Matrix.fly(label.attr.matrix.elements.slice(0));
  17629. bbox = fly.prepend.apply(fly, labelInverseMatrix).transformBBox(label.getBBox(true));
  17630. if (lastBBox && !isBBoxIntersect(bbox, lastBBox, textPadding)) {
  17631. return;
  17632. }
  17633. surface.renderSprite(label);
  17634. lastBBox = bbox;
  17635. textSize += bbox.height;
  17636. textCount++;
  17637. });
  17638. } else if (docked === 'top' || docked === 'bottom') {
  17639. me.iterate(majorTicks, function(position, labelText, i) {
  17640. if (labelText === undefined) {
  17641. return;
  17642. }
  17643. if (renderer) {
  17644. text = Ext.callback(renderer, null, [
  17645. axis,
  17646. labelText,
  17647. layoutContext,
  17648. lastLabelText
  17649. ], 0, axis);
  17650. } else {
  17651. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17652. }
  17653. lastLabelText = labelText;
  17654. label.setAttributes({
  17655. text: String(text),
  17656. translationX: surface.roundPixel(position * xx + dx)
  17657. }, true);
  17658. label.applyTransformations();
  17659. thickness = Math.max(thickness, label.getBBox().height + tickPadding);
  17660. fly = Ext.draw.Matrix.fly(label.attr.matrix.elements.slice(0));
  17661. bbox = fly.prepend.apply(fly, labelInverseMatrix).transformBBox(label.getBBox(true));
  17662. if (lastBBox && !isBBoxIntersect(bbox, lastBBox, textPadding)) {
  17663. return;
  17664. }
  17665. surface.renderSprite(label);
  17666. lastBBox = bbox;
  17667. textSize += bbox.width;
  17668. textCount++;
  17669. });
  17670. } else if (docked === 'radial') {
  17671. me.iterate(majorTicks, function(position, labelText, i) {
  17672. if (labelText === undefined) {
  17673. return;
  17674. }
  17675. if (renderer) {
  17676. text = Ext.callback(renderer, null, [
  17677. axis,
  17678. labelText,
  17679. layoutContext,
  17680. lastLabelText
  17681. ], 0, axis);
  17682. } else {
  17683. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17684. }
  17685. lastLabelText = labelText;
  17686. if (typeof text !== 'undefined') {
  17687. label.setAttributes({
  17688. text: String(text),
  17689. translationX: attr.centerX - surface.roundPixel(position) / attr.max * attr.length * Math.cos(attr.baseRotation + Math.PI / 2),
  17690. translationY: attr.centerY - surface.roundPixel(position) / attr.max * attr.length * Math.sin(attr.baseRotation + Math.PI / 2)
  17691. }, true);
  17692. label.applyTransformations();
  17693. bbox = label.attr.matrix.transformBBox(label.getBBox(true));
  17694. if (lastBBox && !isBBoxIntersect(bbox, lastBBox)) {
  17695. return;
  17696. }
  17697. surface.renderSprite(label);
  17698. lastBBox = bbox;
  17699. textSize += bbox.width;
  17700. textCount++;
  17701. }
  17702. });
  17703. } else if (docked === 'angular') {
  17704. labelOffset += attr.majorTickSize + attr.lineWidth * 0.5;
  17705. me.iterate(majorTicks, function(position, labelText, i) {
  17706. if (labelText === undefined) {
  17707. return;
  17708. }
  17709. if (renderer) {
  17710. text = Ext.callback(renderer, null, [
  17711. axis,
  17712. labelText,
  17713. layoutContext,
  17714. lastLabelText
  17715. ], 0, axis);
  17716. } else {
  17717. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17718. }
  17719. lastLabelText = labelText;
  17720. thickness = Math.max(thickness, Math.max(attr.majorTickSize, attr.minorTickSize) + (attr.lineCap !== 'butt' ? attr.lineWidth * 0.5 : 0));
  17721. if (typeof text !== 'undefined') {
  17722. var angle = position / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
  17723. label.setAttributes({
  17724. text: String(text),
  17725. translationX: attr.centerX + (attr.length + labelOffset) * Math.cos(angle),
  17726. translationY: attr.centerY + (attr.length + labelOffset) * Math.sin(angle)
  17727. }, true);
  17728. label.applyTransformations();
  17729. bbox = label.attr.matrix.transformBBox(label.getBBox(true));
  17730. if (lastBBox && !isBBoxIntersect(bbox, lastBBox)) {
  17731. return;
  17732. }
  17733. surface.renderSprite(label);
  17734. lastBBox = bbox;
  17735. textSize += bbox.width;
  17736. textCount++;
  17737. }
  17738. });
  17739. } else if (docked === 'gauge') {
  17740. gaugeAngles = me.getGaugeAngles();
  17741. labelOffset += attr.majorTickSize + attr.lineWidth * 0.5;
  17742. me.iterate(majorTicks, function(position, labelText, i) {
  17743. if (labelText === undefined) {
  17744. return;
  17745. }
  17746. if (renderer) {
  17747. text = Ext.callback(renderer, null, [
  17748. axis,
  17749. labelText,
  17750. layoutContext,
  17751. lastLabelText
  17752. ], 0, axis);
  17753. } else {
  17754. text = segmenter.renderer(labelText, layoutContext, lastLabelText);
  17755. }
  17756. lastLabelText = labelText;
  17757. if (typeof text !== 'undefined') {
  17758. var angle = (position - attr.min) / (attr.max - attr.min) * attr.totalAngle - attr.totalAngle + gaugeAngles.start;
  17759. label.setAttributes({
  17760. text: String(text),
  17761. translationX: attr.centerX + (attr.length + labelOffset) * Math.cos(angle),
  17762. translationY: attr.centerY + (attr.length + labelOffset) * Math.sin(angle)
  17763. }, true);
  17764. label.applyTransformations();
  17765. bbox = label.attr.matrix.transformBBox(label.getBBox(true));
  17766. if (lastBBox && !isBBoxIntersect(bbox, lastBBox)) {
  17767. return;
  17768. }
  17769. surface.renderSprite(label);
  17770. lastBBox = bbox;
  17771. textSize += bbox.width;
  17772. textCount++;
  17773. }
  17774. });
  17775. }
  17776. if (attr.enlargeEstStepSizeByText && textCount) {
  17777. textSize /= textCount;
  17778. textSize += tickPadding;
  17779. textSize *= 2;
  17780. if (attr.estStepSize < textSize) {
  17781. attr.estStepSize = textSize;
  17782. }
  17783. }
  17784. if (Math.abs(me.thickness - thickness) > 1) {
  17785. me.thickness = thickness;
  17786. attr.bbox.plain.dirty = true;
  17787. attr.bbox.transform.dirty = true;
  17788. me.doThicknessChanged();
  17789. return false;
  17790. }
  17791. }
  17792. },
  17793. renderAxisLine: function(surface, ctx, layout, clipRect) {
  17794. var me = this,
  17795. attr = me.attr,
  17796. halfLineWidth = attr.lineWidth * 0.5,
  17797. docked = attr.position,
  17798. position, gaugeAngles;
  17799. if (attr.axisLine && attr.length) {
  17800. switch (docked) {
  17801. case 'left':
  17802. position = surface.roundPixel(clipRect[2]) - halfLineWidth;
  17803. ctx.moveTo(position, -attr.endGap);
  17804. ctx.lineTo(position, attr.length + attr.startGap + 1);
  17805. break;
  17806. case 'right':
  17807. ctx.moveTo(halfLineWidth, -attr.endGap);
  17808. ctx.lineTo(halfLineWidth, attr.length + attr.startGap + 1);
  17809. break;
  17810. case 'bottom':
  17811. ctx.moveTo(-attr.startGap, halfLineWidth);
  17812. ctx.lineTo(attr.length + attr.endGap, halfLineWidth);
  17813. break;
  17814. case 'top':
  17815. position = surface.roundPixel(clipRect[3]) - halfLineWidth;
  17816. ctx.moveTo(-attr.startGap, position);
  17817. ctx.lineTo(attr.length + attr.endGap, position);
  17818. break;
  17819. case 'angular':
  17820. ctx.moveTo(attr.centerX + attr.length, attr.centerY);
  17821. ctx.arc(attr.centerX, attr.centerY, attr.length, 0, Math.PI * 2, true);
  17822. break;
  17823. case 'gauge':
  17824. gaugeAngles = me.getGaugeAngles();
  17825. ctx.moveTo(attr.centerX + Math.cos(gaugeAngles.start) * attr.length, attr.centerY + Math.sin(gaugeAngles.start) * attr.length);
  17826. ctx.arc(attr.centerX, attr.centerY, attr.length, gaugeAngles.start, gaugeAngles.end, true);
  17827. break;
  17828. }
  17829. }
  17830. },
  17831. getGaugeAngles: function() {
  17832. var me = this,
  17833. angle = me.attr.totalAngle,
  17834. offset;
  17835. if (angle <= Math.PI) {
  17836. offset = (Math.PI - angle) * 0.5;
  17837. } else {
  17838. offset = -(Math.PI * 2 - angle) * 0.5;
  17839. }
  17840. offset = Math.PI * 2 - offset;
  17841. return {
  17842. start: offset,
  17843. end: offset - angle
  17844. };
  17845. },
  17846. renderGridLines: function(surface, ctx, layout, clipRect) {
  17847. var me = this,
  17848. axis = me.getAxis(),
  17849. attr = me.attr,
  17850. matrix = attr.matrix,
  17851. startGap = attr.startGap,
  17852. endGap = attr.endGap,
  17853. xx = matrix.getXX(),
  17854. yy = matrix.getYY(),
  17855. dx = matrix.getDX(),
  17856. dy = matrix.getDY(),
  17857. position = attr.position,
  17858. alignment = axis.getGridAlignment(),
  17859. majorTicks = layout.majorTicks,
  17860. anchor, j, lastAnchor;
  17861. if (attr.grid) {
  17862. if (majorTicks) {
  17863. if (position === 'left' || position === 'right') {
  17864. lastAnchor = attr.min * yy + dy + endGap + startGap;
  17865. me.iterate(majorTicks, function(position, labelText, i) {
  17866. anchor = position * yy + dy + endGap;
  17867. me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
  17868. y: anchor,
  17869. height: lastAnchor - anchor
  17870. }, j = i, true);
  17871. lastAnchor = anchor;
  17872. });
  17873. j++;
  17874. anchor = 0;
  17875. me.putMarker(alignment + '-' + (j % 2 ? 'odd' : 'even'), {
  17876. y: anchor,
  17877. height: lastAnchor - anchor
  17878. }, j, true);
  17879. } else if (position === 'top' || position === 'bottom') {
  17880. lastAnchor = attr.min * xx + dx + startGap;
  17881. if (startGap) {
  17882. me.putMarker(alignment + '-even', {
  17883. x: 0,
  17884. width: lastAnchor
  17885. }, -1, true);
  17886. }
  17887. me.iterate(majorTicks, function(position, labelText, i) {
  17888. anchor = position * xx + dx + startGap;
  17889. me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
  17890. x: anchor,
  17891. width: lastAnchor - anchor
  17892. }, j = i, true);
  17893. lastAnchor = anchor;
  17894. });
  17895. j++;
  17896. anchor = attr.length + attr.startGap + attr.endGap;
  17897. me.putMarker(alignment + '-' + (j % 2 ? 'odd' : 'even'), {
  17898. x: anchor,
  17899. width: lastAnchor - anchor
  17900. }, j, true);
  17901. } else if (position === 'radial') {
  17902. me.iterate(majorTicks, function(position, labelText, i) {
  17903. if (!position) {
  17904. return;
  17905. }
  17906. anchor = position / attr.max * attr.length;
  17907. me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
  17908. scalingX: anchor,
  17909. scalingY: anchor
  17910. }, i, true);
  17911. lastAnchor = anchor;
  17912. });
  17913. } else if (position === 'angular') {
  17914. me.iterate(majorTicks, function(position, labelText, i) {
  17915. if (!attr.length) {
  17916. return;
  17917. }
  17918. anchor = position / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
  17919. me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
  17920. rotationRads: anchor,
  17921. rotationCenterX: 0,
  17922. rotationCenterY: 0,
  17923. scalingX: attr.length,
  17924. scalingY: attr.length
  17925. }, i, true);
  17926. lastAnchor = anchor;
  17927. });
  17928. }
  17929. }
  17930. }
  17931. },
  17932. renderLimits: function(clipRect) {
  17933. var me = this,
  17934. attr = me.attr,
  17935. axis = me.getAxis(),
  17936. limits = Ext.Array.from(axis.getLimits());
  17937. if (!limits.length || attr.dataMin === attr.dataMax) {
  17938. if (axis.limits) {
  17939. axis.limits.titles.attr.hidden = true;
  17940. }
  17941. return;
  17942. }
  17943. var chart = axis.getChart(),
  17944. innerPadding = chart.getInnerPadding(),
  17945. limitsRect = axis.limits.surface.getRect(),
  17946. matrix = attr.matrix,
  17947. position = attr.position,
  17948. chain = Ext.Object.chain,
  17949. titles = axis.limits.titles,
  17950. titleBBox, titlePosition, titleFlip, limit, value, i, ln, x, y;
  17951. titles.attr.hidden = false;
  17952. titles.instances = [];
  17953. titles.position = 0;
  17954. if (position === 'left' || position === 'right') {
  17955. for (i = 0 , ln = limits.length; i < ln; i++) {
  17956. limit = chain(limits[i]);
  17957. !limit.line && (limit.line = {});
  17958. value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
  17959. value = value * matrix.getYY() + matrix.getDY();
  17960. limit.line.y = value + innerPadding.top;
  17961. limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
  17962. me.putMarker('horizontal-limit-lines', limit.line, i, true);
  17963. if (limit.line.title) {
  17964. titles.add(limit.line.title);
  17965. titleBBox = titles.getBBoxFor(titles.position - 1);
  17966. titlePosition = limit.line.title.position || (position === 'left' ? 'start' : 'end');
  17967. switch (titlePosition) {
  17968. case 'start':
  17969. x = 10;
  17970. break;
  17971. case 'end':
  17972. x = limitsRect[2] - 10;
  17973. break;
  17974. case 'middle':
  17975. x = limitsRect[2] / 2;
  17976. break;
  17977. }
  17978. titles.setAttributesFor(titles.position - 1, {
  17979. x: x,
  17980. y: limit.line.y - titleBBox.height / 2,
  17981. textAlign: titlePosition,
  17982. fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle
  17983. });
  17984. }
  17985. }
  17986. } else if (position === 'top' || position === 'bottom') {
  17987. for (i = 0 , ln = limits.length; i < ln; i++) {
  17988. limit = chain(limits[i]);
  17989. !limit.line && (limit.line = {});
  17990. value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
  17991. value = value * matrix.getXX() + matrix.getDX();
  17992. limit.line.x = value + innerPadding.left;
  17993. limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
  17994. me.putMarker('vertical-limit-lines', limit.line, i, true);
  17995. if (limit.line.title) {
  17996. titles.add(limit.line.title);
  17997. titleBBox = titles.getBBoxFor(titles.position - 1);
  17998. titlePosition = limit.line.title.position || (position === 'top' ? 'end' : 'start');
  17999. switch (titlePosition) {
  18000. case 'start':
  18001. y = limitsRect[3] - titleBBox.width / 2 - 10;
  18002. break;
  18003. case 'end':
  18004. y = titleBBox.width / 2 + 10;
  18005. break;
  18006. case 'middle':
  18007. y = limitsRect[3] / 2;
  18008. break;
  18009. }
  18010. titles.setAttributesFor(titles.position - 1, {
  18011. x: limit.line.x + titleBBox.height / 2,
  18012. y: y,
  18013. fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle,
  18014. rotationRads: Math.PI / 2
  18015. });
  18016. }
  18017. }
  18018. } else if (position === 'radial') {
  18019. for (i = 0 , ln = limits.length; i < ln; i++) {
  18020. limit = chain(limits[i]);
  18021. !limit.line && (limit.line = {});
  18022. value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
  18023. if (value > attr.max) {
  18024. continue;
  18025. }
  18026. value = value / attr.max * attr.length;
  18027. limit.line.cx = attr.centerX;
  18028. limit.line.cy = attr.centerY;
  18029. limit.line.scalingX = value;
  18030. limit.line.scalingY = value;
  18031. limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
  18032. me.putMarker('circular-limit-lines', limit.line, i, true);
  18033. if (limit.line.title) {
  18034. titles.add(limit.line.title);
  18035. titleBBox = titles.getBBoxFor(titles.position - 1);
  18036. titles.setAttributesFor(titles.position - 1, {
  18037. x: attr.centerX,
  18038. y: attr.centerY - value - titleBBox.height / 2,
  18039. fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle
  18040. });
  18041. }
  18042. }
  18043. } else if (position === 'angular') {
  18044. for (i = 0 , ln = limits.length; i < ln; i++) {
  18045. limit = chain(limits[i]);
  18046. !limit.line && (limit.line = {});
  18047. value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
  18048. value = value / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
  18049. limit.line.translationX = attr.centerX;
  18050. limit.line.translationY = attr.centerY;
  18051. limit.line.rotationRads = value;
  18052. limit.line.rotationCenterX = 0;
  18053. limit.line.rotationCenterY = 0;
  18054. limit.line.scalingX = attr.length;
  18055. limit.line.scalingY = attr.length;
  18056. limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
  18057. me.putMarker('radial-limit-lines', limit.line, i, true);
  18058. if (limit.line.title) {
  18059. titles.add(limit.line.title);
  18060. titleBBox = titles.getBBoxFor(titles.position - 1);
  18061. titleFlip = ((value > -0.5 * Math.PI && value < 0.5 * Math.PI) || (value > 1.5 * Math.PI && value < 2 * Math.PI)) ? 1 : -1;
  18062. titles.setAttributesFor(titles.position - 1, {
  18063. x: attr.centerX + 0.5 * attr.length * Math.cos(value) + titleFlip * titleBBox.height / 2 * Math.sin(value),
  18064. y: attr.centerY + 0.5 * attr.length * Math.sin(value) - titleFlip * titleBBox.height / 2 * Math.cos(value),
  18065. rotationRads: titleFlip === 1 ? value : value - Math.PI,
  18066. fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle
  18067. });
  18068. }
  18069. }
  18070. } else if (position === 'gauge') {}
  18071. },
  18072. doThicknessChanged: function() {
  18073. var axis = this.getAxis();
  18074. if (axis) {
  18075. axis.onThicknessChanged();
  18076. }
  18077. },
  18078. render: function(surface, ctx, rect) {
  18079. var me = this,
  18080. layoutContext = me.getLayoutContext();
  18081. if (layoutContext) {
  18082. if (false === me.renderLabels(surface, ctx, layoutContext, rect)) {
  18083. return false;
  18084. }
  18085. ctx.beginPath();
  18086. me.renderTicks(surface, ctx, layoutContext, rect);
  18087. me.renderAxisLine(surface, ctx, layoutContext, rect);
  18088. me.renderGridLines(surface, ctx, layoutContext, rect);
  18089. me.renderLimits(rect);
  18090. ctx.stroke();
  18091. }
  18092. }
  18093. });
  18094. /*
  18095. Moved TODO comments to bottom
  18096. TODO(touch-2.2): Split different types of axis into different sprite classes.
  18097. */
  18098. /**
  18099. * @abstract
  18100. * @class Ext.chart.axis.segmenter.Segmenter
  18101. *
  18102. * Interface for a segmenter in an Axis. A segmenter defines the operations you can do to a specific
  18103. * data type.
  18104. *
  18105. * See {@link Ext.chart.axis.Axis}.
  18106. *
  18107. */
  18108. Ext.define('Ext.chart.axis.segmenter.Segmenter', {
  18109. config: {
  18110. /**
  18111. * @cfg {Ext.chart.axis.Axis} axis The axis that the Segmenter is bound.
  18112. */
  18113. axis: null
  18114. },
  18115. constructor: function(config) {
  18116. this.initConfig(config);
  18117. },
  18118. /**
  18119. * This method formats the value.
  18120. *
  18121. * @param {*} value The value to format.
  18122. * @param {Object} context Axis layout context.
  18123. * @return {String}
  18124. */
  18125. renderer: function(value, context) {
  18126. return String(value);
  18127. },
  18128. /**
  18129. * Convert from any data into the target type.
  18130. * @param {*} value The value to convert from
  18131. * @return {*} The converted value.
  18132. */
  18133. from: function(value) {
  18134. return value;
  18135. },
  18136. /**
  18137. * @method
  18138. * Returns the difference between the min and max value based on the given unit scale.
  18139. *
  18140. * @param {*} min The smaller value.
  18141. * @param {*} max The larger value.
  18142. * @param {*} unit The unit scale. Unit can be any type.
  18143. * @return {Number} The number of `unit`s between min and max. It is the minimum n that min + n * unit >= max.
  18144. */
  18145. diff: Ext.emptyFn,
  18146. /**
  18147. * @method
  18148. * Align value with step of units.
  18149. * For example, for the date segmenter, if the unit is "Month" and step is 3, the value will be aligned by
  18150. * seasons.
  18151. *
  18152. * @param {*} value The value to be aligned.
  18153. * @param {Number} step The step of units.
  18154. * @param {*} unit The unit.
  18155. * @return {*} Aligned value.
  18156. */
  18157. align: Ext.emptyFn,
  18158. /**
  18159. * @method
  18160. * Add `step` `unit`s to the value.
  18161. * @param {*} value The value to be added.
  18162. * @param {Number} step The step of units. Negative value are allowed.
  18163. * @param {*} unit The unit.
  18164. */
  18165. add: Ext.emptyFn,
  18166. /**
  18167. * @method
  18168. * Given a start point and estimated step size of a range, determine the preferred step size.
  18169. *
  18170. * @param {*} start The start point of range.
  18171. * @param {*} estStepSize The estimated step size.
  18172. * @return {Object} Return the step size by an object of step x unit.
  18173. * @return {Number} return.step The step count of units.
  18174. * @return {Number|Object} return.unit The unit.
  18175. */
  18176. preferredStep: Ext.emptyFn
  18177. });
  18178. /**
  18179. * @class Ext.chart.axis.segmenter.Names
  18180. * @extends Ext.chart.axis.segmenter.Segmenter
  18181. *
  18182. * Names data type. Names will be calculated as their indices in the methods in this class.
  18183. * The `preferredStep` always return `{ unit: 1, step: 1 }` to indicate "show every item".
  18184. *
  18185. */
  18186. Ext.define('Ext.chart.axis.segmenter.Names', {
  18187. extend: 'Ext.chart.axis.segmenter.Segmenter',
  18188. alias: 'segmenter.names',
  18189. renderer: function(value, context) {
  18190. return value;
  18191. },
  18192. diff: function(min, max, unit) {
  18193. return Math.floor(max - min);
  18194. },
  18195. align: function(value, step, unit) {
  18196. return Math.floor(value);
  18197. },
  18198. add: function(value, step, unit) {
  18199. return value + step;
  18200. },
  18201. preferredStep: function(min, estStepSize, minIdx, data) {
  18202. return {
  18203. unit: 1,
  18204. step: 1
  18205. };
  18206. }
  18207. });
  18208. /**
  18209. * @class Ext.chart.axis.segmenter.Numeric
  18210. * @extends Ext.chart.axis.segmenter.Segmenter
  18211. *
  18212. * Numeric data type.
  18213. */
  18214. Ext.define('Ext.chart.axis.segmenter.Numeric', {
  18215. extend: 'Ext.chart.axis.segmenter.Segmenter',
  18216. alias: 'segmenter.numeric',
  18217. isNumeric: true,
  18218. renderer: function(value, context) {
  18219. return value.toFixed(Math.max(0, context.majorTicks.unit.fixes));
  18220. },
  18221. diff: function(min, max, unit) {
  18222. return Math.floor((max - min) / unit.scale);
  18223. },
  18224. align: function(value, step, unit) {
  18225. var scaledStep = unit.scale * step;
  18226. return Math.floor(value / scaledStep) * scaledStep;
  18227. },
  18228. add: function(value, step, unit) {
  18229. return value + step * unit.scale;
  18230. },
  18231. preferredStep: function(min, estStepSize) {
  18232. // Getting an order of magnitude of the estStepSize with a common logarithm.
  18233. var order = Math.floor(Math.log(estStepSize) * Math.LOG10E),
  18234. scale = Math.pow(10, order);
  18235. estStepSize /= scale;
  18236. if (estStepSize < 2) {
  18237. estStepSize = 2;
  18238. } else if (estStepSize < 5) {
  18239. estStepSize = 5;
  18240. } else if (estStepSize < 10) {
  18241. estStepSize = 10;
  18242. order++;
  18243. }
  18244. return {
  18245. unit: {
  18246. // When passed estStepSize is less than 1, its order of magnitude
  18247. // is equal to -number_of_leading_zeros in the estStepSize.
  18248. fixes: -order,
  18249. // Number of fractional digits.
  18250. scale: scale
  18251. },
  18252. step: estStepSize
  18253. };
  18254. },
  18255. leadingZeros: function(n) {
  18256. // For example:
  18257. // leadingZeros(0.2) is 1,
  18258. // leadingZeros(-0.01) is 2.
  18259. return -Math.floor(Ext.Number.log10(Math.abs(n)));
  18260. },
  18261. /**
  18262. * Wraps the provided estimated step size of a range without altering it into a step size object.
  18263. *
  18264. * @param {*} min The start point of range.
  18265. * @param {*} estStepSize The estimated step size.
  18266. * @return {Object} Return the step size by an object of step x unit.
  18267. * @return {Number} return.step The step count of units.
  18268. * @return {Object} return.unit The unit.
  18269. */
  18270. exactStep: function(min, estStepSize) {
  18271. var stepZeros = this.leadingZeros(estStepSize),
  18272. scale = Math.pow(10, stepZeros);
  18273. return {
  18274. unit: {
  18275. // add one decimal point if estStepSize is not a multiple of scale
  18276. fixes: stepZeros + (estStepSize % scale === 0 ? 0 : 1),
  18277. // Swap scale & step, if the estStepSize < 1,
  18278. // or 'diff' method will give us rounding errors.
  18279. scale: estStepSize < 1 ? estStepSize : 1
  18280. },
  18281. step: estStepSize < 1 ? 1 : estStepSize
  18282. };
  18283. },
  18284. adjustByMajorUnit: function(step, scale, range) {
  18285. var min = range[0],
  18286. max = range[1],
  18287. increment = step * scale,
  18288. remainder, multiplier;
  18289. multiplier = Math.max(1 / (min || 1), 1 / (increment || 1));
  18290. multiplier = multiplier > 1 ? multiplier : 1;
  18291. remainder = ((min * multiplier) % (increment * multiplier)) / multiplier;
  18292. if (remainder !== 0) {
  18293. range[0] = min - remainder + (min < 0 ? -increment : 0);
  18294. }
  18295. multiplier = Math.max(1 / (max || 1), 1 / (increment || 1));
  18296. multiplier = multiplier > 1 ? multiplier : 1;
  18297. remainder = ((max * multiplier) % (increment * multiplier)) / multiplier;
  18298. if (remainder !== 0) {
  18299. range[1] = max - remainder + (max > 0 ? increment : 0);
  18300. }
  18301. }
  18302. });
  18303. /**
  18304. * @class Ext.chart.axis.segmenter.Time
  18305. * @extends Ext.chart.axis.segmenter.Segmenter
  18306. *
  18307. * Time data type.
  18308. */
  18309. Ext.define('Ext.chart.axis.segmenter.Time', {
  18310. extend: 'Ext.chart.axis.segmenter.Segmenter',
  18311. alias: 'segmenter.time',
  18312. config: {
  18313. /**
  18314. * @cfg {Object} step
  18315. * @cfg {String} step.unit The unit of the step (Ext.Date.DAY, Ext.Date.MONTH, etc).
  18316. * @cfg {Number} step.step The number of units for the step (1, 2, etc).
  18317. * If specified, will override the result of {@link #preferredStep}.
  18318. * For example:
  18319. *
  18320. * step: {
  18321. * unit: Ext.Date.HOUR,
  18322. * step: 1
  18323. * }
  18324. */
  18325. step: null
  18326. },
  18327. renderer: function(value, context) {
  18328. var ExtDate = Ext.Date;
  18329. switch (context.majorTicks.unit) {
  18330. case 'y':
  18331. return ExtDate.format(value, 'Y');
  18332. case 'mo':
  18333. return ExtDate.format(value, 'Y-m');
  18334. case 'd':
  18335. return ExtDate.format(value, 'Y-m-d');
  18336. }
  18337. return ExtDate.format(value, 'Y-m-d\nH:i:s');
  18338. },
  18339. from: function(value) {
  18340. return new Date(value);
  18341. },
  18342. diff: function(min, max, unit) {
  18343. if (isFinite(min)) {
  18344. min = new Date(min);
  18345. }
  18346. if (isFinite(max)) {
  18347. max = new Date(max);
  18348. }
  18349. return Ext.Date.diff(min, max, unit);
  18350. },
  18351. updateStep: function() {
  18352. var axis = this.getAxis();
  18353. if (axis && !this.isConfiguring) {
  18354. axis.performLayout();
  18355. }
  18356. },
  18357. align: function(date, step, unit) {
  18358. if (unit === 'd' && step >= 7) {
  18359. date = Ext.Date.align(date, 'd', step);
  18360. date.setDate(date.getDate() - date.getDay() + 1);
  18361. return date;
  18362. } else {
  18363. return Ext.Date.align(date, unit, step);
  18364. }
  18365. },
  18366. add: function(value, step, unit) {
  18367. return Ext.Date.add(new Date(value), unit, step);
  18368. },
  18369. timeBuckets: [
  18370. {
  18371. unit: Ext.Date.YEAR,
  18372. steps: [
  18373. 1,
  18374. 2,
  18375. 5,
  18376. 10,
  18377. 20,
  18378. 50,
  18379. 100,
  18380. 200,
  18381. 500
  18382. ]
  18383. },
  18384. {
  18385. unit: Ext.Date.MONTH,
  18386. steps: [
  18387. 1,
  18388. 3,
  18389. 6
  18390. ]
  18391. },
  18392. {
  18393. unit: Ext.Date.DAY,
  18394. steps: [
  18395. 1,
  18396. 7,
  18397. 14
  18398. ]
  18399. },
  18400. {
  18401. unit: Ext.Date.HOUR,
  18402. steps: [
  18403. 1,
  18404. 6,
  18405. 12
  18406. ]
  18407. },
  18408. {
  18409. unit: Ext.Date.MINUTE,
  18410. steps: [
  18411. 1,
  18412. 5,
  18413. 15,
  18414. 30
  18415. ]
  18416. },
  18417. {
  18418. unit: Ext.Date.SECOND,
  18419. steps: [
  18420. 1,
  18421. 5,
  18422. 15,
  18423. 30
  18424. ]
  18425. },
  18426. {
  18427. unit: Ext.Date.MILLI,
  18428. steps: [
  18429. 1,
  18430. 2,
  18431. 5,
  18432. 10,
  18433. 20,
  18434. 50,
  18435. 100,
  18436. 200,
  18437. 500
  18438. ]
  18439. }
  18440. ],
  18441. /**
  18442. * @private
  18443. * Takes a time interval and figures out what is the smallest nice number of which
  18444. * units (years, months, days, etc.) that can fully encompass that interval.
  18445. * @param {Date} min
  18446. * @param {Date} max
  18447. * @return {Object}
  18448. * @return {String} return.unit The unit.
  18449. * @return {Number} return.step The number of units.
  18450. */
  18451. getTimeBucket: function(min, max) {
  18452. var buckets = this.timeBuckets,
  18453. unit, unitCount, steps, step, result, i, j;
  18454. for (i = 0; i < buckets.length; i++) {
  18455. unit = buckets[i].unit;
  18456. unitCount = this.diff(min, max, unit);
  18457. if (unitCount > 0) {
  18458. steps = buckets[i].steps;
  18459. for (j = 0; j < steps.length; j++) {
  18460. step = steps[j];
  18461. if (unitCount <= step) {
  18462. break;
  18463. }
  18464. }
  18465. result = {
  18466. unit: unit,
  18467. step: step
  18468. };
  18469. break;
  18470. }
  18471. }
  18472. // If the interval is smaller then one millisecond ...
  18473. if (!result) {
  18474. // ... we can't go smaller than one millisecond.
  18475. result = {
  18476. unit: Ext.Date.MILLI,
  18477. step: 1
  18478. };
  18479. }
  18480. return result;
  18481. },
  18482. preferredStep: function(min, estStepSize) {
  18483. var step = this.getStep();
  18484. return step ? step : this.getTimeBucket(new Date(+min), new Date(+min + Math.ceil(estStepSize)));
  18485. }
  18486. });
  18487. /**
  18488. * @abstract
  18489. * @class Ext.chart.axis.layout.Layout
  18490. *
  18491. * Interface used by Axis to process its data into a meaningful layout.
  18492. */
  18493. Ext.define('Ext.chart.axis.layout.Layout', {
  18494. mixins: {
  18495. observable: 'Ext.mixin.Observable'
  18496. },
  18497. config: {
  18498. /**
  18499. * @cfg {Ext.chart.axis.Axis} axis The axis that the Layout is bound.
  18500. */
  18501. axis: null
  18502. },
  18503. constructor: function(config) {
  18504. this.mixins.observable.constructor.call(this, config);
  18505. },
  18506. /**
  18507. * Processes the data of the series bound to the axis.
  18508. * @param {Ext.chart.series.Series} series The bound series.
  18509. */
  18510. processData: function(series) {
  18511. var me = this,
  18512. axis = me.getAxis(),
  18513. direction = axis.getDirection(),
  18514. boundSeries = axis.boundSeries,
  18515. i, ln;
  18516. if (series) {
  18517. series['coordinate' + direction]();
  18518. } else {
  18519. for (i = 0 , ln = boundSeries.length; i < ln; i++) {
  18520. boundSeries[i]['coordinate' + direction]();
  18521. }
  18522. }
  18523. },
  18524. /**
  18525. * Calculates the position of major ticks for the axis.
  18526. * @param {Object} context
  18527. */
  18528. calculateMajorTicks: function(context) {
  18529. var me = this,
  18530. attr = context.attr,
  18531. range = attr.max - attr.min,
  18532. zoom = range / Math.max(1, attr.length) * (attr.visibleMax - attr.visibleMin),
  18533. viewMin = attr.min + range * attr.visibleMin,
  18534. viewMax = attr.min + range * attr.visibleMax,
  18535. estStepSize = attr.estStepSize * zoom,
  18536. majorTicks = me.snapEnds(context, attr.min, attr.max, estStepSize);
  18537. if (majorTicks) {
  18538. me.trimByRange(context, majorTicks, viewMin, viewMax);
  18539. context.majorTicks = majorTicks;
  18540. }
  18541. },
  18542. /**
  18543. * Calculates the position of sub ticks for the axis.
  18544. * @param {Object} context
  18545. */
  18546. calculateMinorTicks: function(context) {
  18547. if (this.snapMinorEnds) {
  18548. context.minorTicks = this.snapMinorEnds(context);
  18549. }
  18550. },
  18551. /**
  18552. * Calculates the position of tick marks for the axis.
  18553. * @param {Object} context
  18554. * @return {*}
  18555. */
  18556. calculateLayout: function(context) {
  18557. var me = this,
  18558. attr = context.attr;
  18559. if (attr.length === 0) {
  18560. return null;
  18561. }
  18562. if (attr.majorTicks) {
  18563. me.calculateMajorTicks(context);
  18564. if (attr.minorTicks) {
  18565. me.calculateMinorTicks(context);
  18566. }
  18567. }
  18568. },
  18569. /**
  18570. * @method
  18571. * Snaps the data bound to the axis to meaningful tick marks.
  18572. * @param {Object} context
  18573. * @param {Number} min
  18574. * @param {Number} max
  18575. * @param {Number} estStepSize
  18576. */
  18577. snapEnds: Ext.emptyFn,
  18578. /**
  18579. * Trims the layout of the axis by the defined minimum and maximum.
  18580. * @param {Object} context
  18581. * @param {Object} ticks Ticks object (e.g. major ticks) to be modified.
  18582. * @param {Number} trimMin
  18583. * @param {Number} trimMax
  18584. */
  18585. trimByRange: function(context, ticks, trimMin, trimMax) {
  18586. var segmenter = context.segmenter,
  18587. unit = ticks.unit,
  18588. beginIdx = segmenter.diff(ticks.from, trimMin, unit),
  18589. endIdx = segmenter.diff(ticks.from, trimMax, unit),
  18590. begin = Math.max(0, Math.ceil(beginIdx / ticks.step)),
  18591. end = Math.min(ticks.steps, Math.floor(endIdx / ticks.step));
  18592. if (end < ticks.steps) {
  18593. ticks.to = segmenter.add(ticks.from, end * ticks.step, unit);
  18594. }
  18595. if (ticks.max > trimMax) {
  18596. ticks.max = ticks.to;
  18597. }
  18598. if (ticks.from < trimMin) {
  18599. ticks.from = segmenter.add(ticks.from, begin * ticks.step, unit);
  18600. while (ticks.from < trimMin) {
  18601. begin++;
  18602. ticks.from = segmenter.add(ticks.from, ticks.step, unit);
  18603. }
  18604. }
  18605. if (ticks.min < trimMin) {
  18606. ticks.min = ticks.from;
  18607. }
  18608. ticks.steps = end - begin;
  18609. }
  18610. });
  18611. /**
  18612. * @class Ext.chart.axis.layout.Discrete
  18613. * @extends Ext.chart.axis.layout.Layout
  18614. *
  18615. * Simple processor for data that cannot be interpolated.
  18616. */
  18617. Ext.define('Ext.chart.axis.layout.Discrete', {
  18618. extend: 'Ext.chart.axis.layout.Layout',
  18619. alias: 'axisLayout.discrete',
  18620. isDiscrete: true,
  18621. processData: function() {
  18622. var me = this,
  18623. axis = me.getAxis(),
  18624. seriesList = axis.boundSeries,
  18625. direction = axis.getDirection(),
  18626. i, ln, series;
  18627. me.labels = [];
  18628. me.labelMap = {};
  18629. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  18630. series = seriesList[i];
  18631. if (series['get' + direction + 'Axis']() === axis) {
  18632. series['coordinate' + direction]();
  18633. }
  18634. }
  18635. // About the labels on Category axes (aka. axes with a Discrete layout)...
  18636. //
  18637. // When the data set from the store changes, series.processData() is called, which does its thing
  18638. // at the series level and then calls series.updateLabelData() to update the labels in the sprites
  18639. // that belong to the series. At the same time, series.processData() calls axis.processData(), which
  18640. // also does its thing but at the axis level, and also needs to update the labels for the sprite(s)
  18641. // that belong to the axis. This is not that simple, however. So how are the axis labels rendered?
  18642. // First, axis.sprite.Axis.render() calls renderLabels() which obtains the majorTicks from the
  18643. // axis.layout and iterate() through them. The majorTicks are an object returned by snapEnds() below
  18644. // which provides a getLabel() function that returns the label from the axis.layoutContext.data array.
  18645. // So now the question is: how are the labels transferred from the axis.layout to the axis.layoutContext?
  18646. // The easy response is: it's in calculateLayout() below. The issue is to call calculateLayout() because
  18647. // it takes in an axis.layoutContext that can only be created in axis.sprite.Axis.layoutUpdater(), which is
  18648. // a private "updater" function that is called by all the sprite's "triggers". Of course, we don't
  18649. // want to call layoutUpdater() directly from here, so instead we update the sprite's data attribute, which
  18650. // sets the trigger which calls layoutUpdater() which calls calculateLayout() etc...
  18651. // Note that the sprite's data attribute could be set to any value and it would still result in the
  18652. // trigger we need. For consistency, however, it is set to the labels.
  18653. axis.getSprites()[0].setAttributes({
  18654. data: me.labels
  18655. });
  18656. me.fireEvent('datachange', me.labels);
  18657. },
  18658. /**
  18659. * @method calculateLayout
  18660. * @inheritdoc
  18661. */
  18662. calculateLayout: function(context) {
  18663. context.data = this.labels;
  18664. this.callParent([
  18665. context
  18666. ]);
  18667. },
  18668. /**
  18669. * @method calculateMajorTicks
  18670. * @inheritdoc
  18671. */
  18672. calculateMajorTicks: function(context) {
  18673. var me = this,
  18674. attr = context.attr,
  18675. data = context.data,
  18676. range = attr.max - attr.min,
  18677. viewMin = attr.min + range * attr.visibleMin,
  18678. viewMax = attr.min + range * attr.visibleMax,
  18679. out;
  18680. out = me.snapEnds(context, Math.max(0, attr.min), Math.min(attr.max, data.length - 1), 1);
  18681. if (out) {
  18682. me.trimByRange(context, out, viewMin, viewMax);
  18683. context.majorTicks = out;
  18684. }
  18685. },
  18686. /**
  18687. * @method snapEnds
  18688. * @inheritdoc
  18689. */
  18690. snapEnds: function(context, min, max, estStepSize) {
  18691. estStepSize = Math.ceil(estStepSize);
  18692. var steps = Math.floor((max - min) / estStepSize),
  18693. data = context.data;
  18694. return {
  18695. min: min,
  18696. max: max,
  18697. from: min,
  18698. to: steps * estStepSize + min,
  18699. step: estStepSize,
  18700. steps: steps,
  18701. unit: 1,
  18702. getLabel: function(currentStep) {
  18703. return data[this.from + this.step * currentStep];
  18704. },
  18705. get: function(currentStep) {
  18706. return this.from + this.step * currentStep;
  18707. }
  18708. };
  18709. },
  18710. /**
  18711. * @method trimByRange
  18712. * @inheritdoc
  18713. */
  18714. trimByRange: function(context, out, trimMin, trimMax) {
  18715. var unit = out.unit,
  18716. beginIdx = Math.ceil((trimMin - out.from) / unit) * unit,
  18717. endIdx = Math.floor((trimMax - out.from) / unit) * unit,
  18718. begin = Math.max(0, Math.ceil(beginIdx / out.step)),
  18719. end = Math.min(out.steps, Math.floor(endIdx / out.step));
  18720. if (end < out.steps) {
  18721. out.to = end;
  18722. }
  18723. if (out.max > trimMax) {
  18724. out.max = out.to;
  18725. }
  18726. if (out.from < trimMin && out.step > 0) {
  18727. out.from = out.from + begin * out.step * unit;
  18728. while (out.from < trimMin) {
  18729. begin++;
  18730. out.from += out.step * unit;
  18731. }
  18732. }
  18733. if (out.min < trimMin) {
  18734. out.min = out.from;
  18735. }
  18736. out.steps = end - begin;
  18737. },
  18738. getCoordFor: function(value, field, idx, items) {
  18739. this.labels.push(value);
  18740. return this.labels.length - 1;
  18741. }
  18742. });
  18743. /**
  18744. * Discrete layout that combines duplicate data points only if they have the same index.
  18745. * For example:
  18746. *
  18747. * @example
  18748. * Ext.create({
  18749. * xtype: 'cartesian',
  18750. * title: 'Weight vs Calories',
  18751. *
  18752. * renderTo: document.body,
  18753. * width: 400,
  18754. * height: 400,
  18755. *
  18756. * store: {
  18757. * fields: ['month', 'weight', 'calories'],
  18758. * data: [
  18759. * {
  18760. * month: 'Jan',
  18761. * weight: 185,
  18762. * calories: 2650
  18763. * },
  18764. * {
  18765. * month: 'Jan',
  18766. * weight: 188,
  18767. * calories: 2800
  18768. * },
  18769. * {
  18770. * month: 'Feb',
  18771. * weight: 188,
  18772. * calories: 2800
  18773. * },
  18774. * {
  18775. * month: 'Mar',
  18776. * weight: 191,
  18777. * calories: 2800
  18778. * },
  18779. * {
  18780. * month: 'Apr',
  18781. * weight: 189,
  18782. * calories: 1500
  18783. * },
  18784. * {
  18785. * month: 'May',
  18786. * weight: 187,
  18787. * calories: 1350
  18788. * }
  18789. * ]
  18790. * },
  18791. *
  18792. * axes: [{
  18793. * type: 'numeric',
  18794. * position: 'left',
  18795. * fields: ['weight'],
  18796. * minimum: 140
  18797. * }, {
  18798. * type: 'numeric',
  18799. * position: 'right',
  18800. * fields: ['calories'],
  18801. * minimum: 500,
  18802. * maximum: 3500
  18803. * }, {
  18804. * type: 'category',
  18805. * grid: true,
  18806. * layout: 'combineByIndex',
  18807. * fields: 'month',
  18808. * position: 'bottom',
  18809. * label: {
  18810. * rotate: {
  18811. * degrees: -45
  18812. * }
  18813. * }
  18814. * }],
  18815. *
  18816. * series: [{
  18817. * type: 'line',
  18818. * title: 'Weight',
  18819. * xField: 'month',
  18820. * yField: 'weight',
  18821. * smooth: true,
  18822. * marker: true
  18823. * }, {
  18824. * type: 'line',
  18825. * title: 'Calories',
  18826. * xField: 'month',
  18827. * yField: 'calories',
  18828. * smooth: true,
  18829. * marker: true
  18830. * }],
  18831. *
  18832. * legend: {
  18833. * docked: 'bottom'
  18834. * }
  18835. *
  18836. * });
  18837. *
  18838. * @since 6.5.0
  18839. */
  18840. Ext.define('Ext.chart.axis.layout.CombineByIndex', {
  18841. extend: 'Ext.chart.axis.layout.Discrete',
  18842. alias: 'axisLayout.combineByIndex',
  18843. getCoordFor: function(value, field, idx, items) {
  18844. var labels = this.labels,
  18845. result = idx;
  18846. if (labels[idx] !== value) {
  18847. result = labels.push(value) - 1;
  18848. }
  18849. return result;
  18850. }
  18851. });
  18852. /**
  18853. * Discrete processor that combines duplicate data points.
  18854. */
  18855. Ext.define('Ext.chart.axis.layout.CombineDuplicate', {
  18856. extend: 'Ext.chart.axis.layout.Discrete',
  18857. alias: 'axisLayout.combineDuplicate',
  18858. getCoordFor: function(value, field, idx, items) {
  18859. if (!(value in this.labelMap)) {
  18860. var result = this.labelMap[value] = this.labels.length;
  18861. this.labels.push(value);
  18862. return result;
  18863. }
  18864. return this.labelMap[value];
  18865. }
  18866. });
  18867. /**
  18868. * @class Ext.chart.axis.layout.Continuous
  18869. * @extends Ext.chart.axis.layout.Layout
  18870. *
  18871. * Processor for axis data that can be interpolated.
  18872. */
  18873. Ext.define('Ext.chart.axis.layout.Continuous', {
  18874. extend: 'Ext.chart.axis.layout.Layout',
  18875. alias: 'axisLayout.continuous',
  18876. isContinuous: true,
  18877. config: {
  18878. adjustMinimumByMajorUnit: false,
  18879. adjustMaximumByMajorUnit: false
  18880. },
  18881. getCoordFor: function(value, field, idx, items) {
  18882. return +value;
  18883. },
  18884. /**
  18885. * @method snapEnds
  18886. * @inheritdoc
  18887. */
  18888. snapEnds: function(context, min, max, estStepSize) {
  18889. var segmenter = context.segmenter,
  18890. axis = this.getAxis(),
  18891. noAnimation = !axis.spriteAnimationCount,
  18892. majorTickSteps = axis.getMajorTickSteps(),
  18893. // if specific number of steps requested and the segmenter supports such segmentation
  18894. bucket = majorTickSteps && segmenter.exactStep ? segmenter.exactStep(min, (max - min) / majorTickSteps) : segmenter.preferredStep(min, estStepSize),
  18895. unit = bucket.unit,
  18896. step = bucket.step,
  18897. diffSteps = segmenter.diff(min, max, unit),
  18898. steps = (majorTickSteps || diffSteps) + 1,
  18899. from;
  18900. // If 'majorTickSteps' config of the axis is set (is not 0), it means that
  18901. // we want to split the range at that number of equal intervals (segmenter.exactStep),
  18902. // and don't care if the resulting ticks are at nice round values or not.
  18903. // So 'from' (aligned) step is equal to 'min' (unaligned step).
  18904. // And 'to' is equal to 'max'.
  18905. //
  18906. // Another case where this is possible, is when the range between 'min' and
  18907. // 'max' can be represented by n steps, where n is an integer.
  18908. // For example, if the data values are [7, 17, 27, 37, 47], the data step is 10
  18909. // and, if the calculated tick step (segmenter.preferredStep) is also 10,
  18910. // there is no need to segmenter.align the 'min' to 0, so that the ticks are at
  18911. // [0, 10, 20, 30, 40, 50], because the data points are already perfectly
  18912. // spaced, so the ticks can be exactly at the data points without runing the
  18913. // aesthetics.
  18914. //
  18915. // The 'noAnimation' check is required to prevent EXTJS-25413 from happening.
  18916. // The segmentation described above is ideal for a static chart, but produces
  18917. // unwanted effects during animation.
  18918. if (majorTickSteps || (noAnimation && +segmenter.add(min, diffSteps, unit) === max)) {
  18919. from = min;
  18920. } else {
  18921. from = segmenter.align(min, step, unit);
  18922. }
  18923. return {
  18924. // min/max are NOT aligned to step
  18925. min: segmenter.from(min),
  18926. max: segmenter.from(max),
  18927. // from/to are aligned to step
  18928. from: from,
  18929. to: segmenter.add(from, steps, unit),
  18930. step: step,
  18931. steps: steps,
  18932. unit: unit,
  18933. get: function(currentStep) {
  18934. return segmenter.add(this.from, this.step * currentStep, this.unit);
  18935. }
  18936. };
  18937. },
  18938. snapMinorEnds: function(context) {
  18939. var majorTicks = context.majorTicks,
  18940. minorTickSteps = this.getAxis().getMinorTickSteps(),
  18941. segmenter = context.segmenter,
  18942. min = majorTicks.min,
  18943. max = majorTicks.max,
  18944. from = majorTicks.from,
  18945. unit = majorTicks.unit,
  18946. step = majorTicks.step / minorTickSteps,
  18947. scaledStep = step * unit.scale,
  18948. fromMargin = from - min,
  18949. offset = Math.floor(fromMargin / scaledStep),
  18950. extraSteps = offset + Math.floor((max - majorTicks.to) / scaledStep) + 1,
  18951. steps = majorTicks.steps * minorTickSteps + extraSteps;
  18952. return {
  18953. min: min,
  18954. max: max,
  18955. from: min + fromMargin % scaledStep,
  18956. to: segmenter.add(from, steps * step, unit),
  18957. step: step,
  18958. steps: steps,
  18959. unit: unit,
  18960. get: function(current) {
  18961. return (current % minorTickSteps + offset + 1 !== 0) ? // don't render minor tick in major tick position
  18962. segmenter.add(this.from, this.step * current, unit) : null;
  18963. }
  18964. };
  18965. }
  18966. });
  18967. /**
  18968. * @class Ext.chart.axis.Axis
  18969. *
  18970. * Defines axis for charts.
  18971. *
  18972. * Using the current model, the type of axis can be easily extended. By default, Sencha Charts provide three different
  18973. * types of axis:
  18974. *
  18975. * * **numeric** - the data attached to this axis is numeric and continuous.
  18976. * * **time** - the data attached to this axis is (or gets converted into) a date/time value; it is continuous.
  18977. * * **category** - the data attached to this axis belongs to a finite set. The data points are evenly placed along the axis.
  18978. *
  18979. * The behavior of an axis can be easily changed by setting different types of axis layout and axis segmenter to the axis.
  18980. *
  18981. * Axis layout defines how the data points are placed. Using continuous layout, the data points will be distributed by
  18982. * the numeric value. Using discrete layout the data points will be spaced evenly. Furthermore, if you want to combine
  18983. * the data points with the duplicate values in a discrete layout, you should use combineDuplicate layout.
  18984. *
  18985. * Segmenter defines the way to segment data range. For example, if you have a Date-type data range from Jan 1, 1997 to
  18986. * Jan 1, 2017, the segmenter will segement the data range into years, months or days based on the current zooming
  18987. * level.
  18988. *
  18989. * It is possible to write custom axis layouts and segmenters to extends this behavior by simply implementing interfaces
  18990. * {@link Ext.chart.axis.layout.Layout} and {@link Ext.chart.axis.segmenter.Segmenter}.
  18991. *
  18992. * Here's an example for the axes part of a chart definition:
  18993. * An example of axis for a series (in this case for an area chart that has multiple layers of yFields) could be:
  18994. *
  18995. * axes: [{
  18996. * type: 'numeric',
  18997. * position: 'left',
  18998. * title: 'Number of Hits',
  18999. * grid: {
  19000. * odd: {
  19001. * opacity: 1,
  19002. * fill: '#ddd',
  19003. * stroke: '#bbb',
  19004. * lineWidth: 1
  19005. * }
  19006. * },
  19007. * minimum: 0
  19008. * }, {
  19009. * type: 'category',
  19010. * position: 'bottom',
  19011. * title: 'Month of the Year',
  19012. * grid: true,
  19013. * label: {
  19014. * rotate: {
  19015. * degrees: 315
  19016. * }
  19017. * }
  19018. * }]
  19019. *
  19020. * In this case we use a `numeric` axis for displaying the values of the Area series and a `category` axis for displaying the names of
  19021. * the store elements. The numeric axis is placed on the left of the screen, while the category axis is placed at the bottom of the chart.
  19022. * Both the category and numeric axes have `grid` set, which means that horizontal and vertical lines will cover the chart background. In the
  19023. * category axis the labels will be rotated so they can fit the space better.
  19024. */
  19025. Ext.define('Ext.chart.axis.Axis', {
  19026. xtype: 'axis',
  19027. mixins: {
  19028. observable: 'Ext.mixin.Observable'
  19029. },
  19030. requires: [
  19031. 'Ext.chart.axis.sprite.Axis',
  19032. 'Ext.chart.axis.segmenter.*',
  19033. 'Ext.chart.axis.layout.*',
  19034. 'Ext.chart.Util'
  19035. ],
  19036. isAxis: true,
  19037. /**
  19038. * @event rangechange
  19039. * Fires when the {@link Ext.chart.axis.Axis#range range} of the axis changes.
  19040. * @param {Ext.chart.axis.Axis} axis
  19041. * @param {Array} range
  19042. * @param {Array} oldRange
  19043. */
  19044. /**
  19045. * @event visiblerangechange
  19046. * Fires when the {@link #visibleRange} of the axis changes.
  19047. * @param {Ext.chart.axis.Axis} axis
  19048. * @param {Array} visibleRange
  19049. */
  19050. /**
  19051. * @cfg {String} id
  19052. * The **unique** id of this axis instance.
  19053. */
  19054. config: {
  19055. /**
  19056. * @cfg {String} position
  19057. * Where to set the axis. Available options are `left`, `bottom`, `right`, `top`, `radial` and `angular`.
  19058. */
  19059. position: 'bottom',
  19060. /**
  19061. * @cfg {Array} fields
  19062. * An array containing the names of the record fields which should be mapped along the axis.
  19063. * This is optional if the binding between series and fields is clear.
  19064. */
  19065. fields: [],
  19066. /**
  19067. * @cfg {Object} label
  19068. *
  19069. * The label configuration object for the Axis. This object may include style attributes
  19070. * like `spacing`, `padding`, `font` that receives a string or number and
  19071. * returns a new string with the modified values.
  19072. *
  19073. * For more supported values, see the configurations for {@link Ext.chart.sprite.Label}.
  19074. */
  19075. label: undefined,
  19076. /**
  19077. * @cfg {Object} grid
  19078. * The grid configuration object for the Axis style. Can contain `stroke` or `fill` attributes.
  19079. * Also may contain an `odd` or `even` property in which you only style things on odd or even rows.
  19080. * For example:
  19081. *
  19082. *
  19083. * grid {
  19084. * odd: {
  19085. * stroke: '#555'
  19086. * },
  19087. * even: {
  19088. * stroke: '#ccc'
  19089. * }
  19090. * }
  19091. */
  19092. grid: false,
  19093. /**
  19094. * @cfg {Array|Object} limits
  19095. * The limit lines configuration for the axis.
  19096. * For example:
  19097. *
  19098. * limits: [{
  19099. * value: 50,
  19100. * line: {
  19101. * strokeStyle: 'red',
  19102. * lineDash: [6, 3],
  19103. * title: {
  19104. * text: 'Monthly minimum',
  19105. * fontSize: 14
  19106. * }
  19107. * }
  19108. * }]
  19109. */
  19110. limits: null,
  19111. /**
  19112. * @cfg {Function} renderer Allows to change the text shown next to the tick.
  19113. * @param {Ext.chart.axis.Axis} axis The axis.
  19114. * @param {String/Number} label The label.
  19115. * @param {Object} layoutContext The object that holds calculated positions
  19116. * of axis' ticks based on current layout, segmenter, axis length and configuration.
  19117. * @param {String/Number/null} lastLabel The last label (if any).
  19118. * @return {String} The label to display.
  19119. * @controllable
  19120. */
  19121. renderer: null,
  19122. /**
  19123. * @protected
  19124. * @cfg {Ext.chart.AbstractChart} chart The Chart that the Axis is bound.
  19125. */
  19126. chart: null,
  19127. /**
  19128. * @cfg {Object} style
  19129. * The style for the axis line and ticks.
  19130. * Refer to the {@link Ext.chart.axis.sprite.Axis}
  19131. */
  19132. style: null,
  19133. /**
  19134. * @cfg {Number} margin
  19135. * The margin of the axis. Used to control the spacing between axes in charts with multiple axes.
  19136. * Unlike CSS where the margin is added on all 4 sides of an element, the `margin` is the total space
  19137. * that is added horizontally for a vertical axis, vertically for a horizontal axis,
  19138. * and radially for an angular axis.
  19139. */
  19140. margin: 0,
  19141. /**
  19142. * @cfg {Number} [titleMargin=4]
  19143. * The margin around the axis title. Unlike CSS where the margin is added on all 4
  19144. * sides of an element, the `titleMargin` is the total space that is added horizontally
  19145. * for a vertical title and vertically for an horizontal title, with half the `titleMargin`
  19146. * being added on either side.
  19147. */
  19148. titleMargin: 4,
  19149. /**
  19150. * @cfg {Object} background
  19151. * The background config for the axis surface.
  19152. */
  19153. background: null,
  19154. /**
  19155. * @cfg {Number} minimum
  19156. * The minimum value drawn by the axis. If not set explicitly, the axis
  19157. * minimum will be calculated automatically.
  19158. */
  19159. minimum: NaN,
  19160. /**
  19161. * @cfg {Number} maximum
  19162. * The maximum value drawn by the axis. If not set explicitly, the axis
  19163. * maximum will be calculated automatically.
  19164. */
  19165. maximum: NaN,
  19166. /**
  19167. * @cfg {Boolean} reconcileRange
  19168. * If 'true' the range of the axis will be a union of ranges
  19169. * of all the axes with the same direction. Defaults to 'false'.
  19170. */
  19171. reconcileRange: false,
  19172. /**
  19173. * @cfg {Number} minZoom
  19174. * The minimum zooming level for axis.
  19175. */
  19176. minZoom: 1,
  19177. /**
  19178. * @cfg {Number} maxZoom
  19179. * The maximum zooming level for axis.
  19180. */
  19181. maxZoom: 10000,
  19182. /**
  19183. * @cfg {Object|Ext.chart.axis.layout.Layout} layout
  19184. * The axis layout config. See {@link Ext.chart.axis.layout.Layout}
  19185. */
  19186. layout: 'continuous',
  19187. /**
  19188. * @cfg {Object|Ext.chart.axis.segmenter.Segmenter} segmenter
  19189. * The segmenter config. See {@link Ext.chart.axis.segmenter.Segmenter}
  19190. */
  19191. segmenter: 'numeric',
  19192. /**
  19193. * @cfg {Boolean} hidden
  19194. * Indicate whether to hide the axis.
  19195. * If the axis is hidden, one of the axis line, ticks, labels or the title will be shown and
  19196. * no margin will be taken.
  19197. * The coordination mechanism works fine no matter if the axis is hidden.
  19198. */
  19199. hidden: false,
  19200. /**
  19201. * @cfg {Number} [majorTickSteps=0]
  19202. * Forces the number of major ticks to the specified value.
  19203. * Both {@link #minimum} and {@link #maximum} should be specified.
  19204. */
  19205. majorTickSteps: 0,
  19206. /**
  19207. * @cfg {Number} [minorTickSteps=0]
  19208. * The number of small ticks between two major ticks.
  19209. */
  19210. minorTickSteps: 0,
  19211. /**
  19212. * @cfg {Boolean} adjustByMajorUnit
  19213. * Whether to make the auto-calculated minimum and maximum of the axis
  19214. * a multiple of the interval between the major ticks of the axis.
  19215. * If {@link #majorTickSteps}, {@link #minimum} or {@link #maximum}
  19216. * configs have been set, this config will be ignored.
  19217. * Defaults to 'true'.
  19218. * Note: this config has no effect if the axis is {@link #hidden}.
  19219. */
  19220. adjustByMajorUnit: true,
  19221. /**
  19222. * @cfg {String|Object} title
  19223. * The title for the Axis.
  19224. * If given a String, the 'text' attribute of the title sprite will be set,
  19225. * otherwise the style will be set.
  19226. */
  19227. title: null,
  19228. /**
  19229. * @private
  19230. * @cfg {Number} [expandRangeBy=0]
  19231. */
  19232. expandRangeBy: 0,
  19233. /**
  19234. * @private
  19235. * @cfg {Number} length
  19236. * Length of the axis position. Equals to the size of inner rect on the docking side of this axis.
  19237. * WARNING: Meant to be set automatically by chart. Do not set it manually.
  19238. */
  19239. length: 0,
  19240. /**
  19241. * @private
  19242. * @cfg {Array} center
  19243. * Center of the polar axis.
  19244. * WARNING: Meant to be set automatically by chart. Do not set it manually.
  19245. */
  19246. center: null,
  19247. /**
  19248. * @private
  19249. * @cfg {Number} radius
  19250. * Radius of the polar axis.
  19251. * WARNING: Meant to be set automatically by chart. Do not set it manually.
  19252. */
  19253. radius: null,
  19254. /**
  19255. * @private
  19256. */
  19257. totalAngle: Math.PI,
  19258. /**
  19259. * @private
  19260. * @cfg {Number} rotation
  19261. * Rotation of the polar axis in radians.
  19262. * WARNING: Meant to be set automatically by chart. Do not set it manually.
  19263. */
  19264. rotation: null,
  19265. /**
  19266. * @cfg {Array} visibleRange
  19267. * Specify the proportion of the axis to be rendered. The series bound to
  19268. * this axis will be synchronized and transformed accordingly.
  19269. */
  19270. visibleRange: [
  19271. 0,
  19272. 1
  19273. ],
  19274. /**
  19275. * @cfg {Boolean} needHighPrecision
  19276. * Indicates that the axis needs high precision surface implementation.
  19277. * See {@link Ext.draw.engine.Canvas#highPrecision}
  19278. */
  19279. needHighPrecision: false,
  19280. /**
  19281. * @cfg {Ext.chart.axis.Axis|String|Number} linkedTo
  19282. * Axis (itself, its ID or index) that this axis is linked to.
  19283. * When an axis is linked to a master axis, it will use the same data as the master axis.
  19284. * It can be used to show additional info, or to ease reading the chart by duplicating the scales.
  19285. */
  19286. linkedTo: null,
  19287. /**
  19288. * @cfg {Number|Object}
  19289. * If `floating` is a number, then it's a percentage displacement of the axis from its initial {@link #position}
  19290. * in the direction opposite to the axis' direction. For instance, '{position:"left", floating:75}' displays a vertical
  19291. * axis at 3/4 of the chart, starting from the left. It is equivalent to '{position:"right", floating:25}'.
  19292. * If `floating` is an object, then `floating.value` is the position of this axis along another axis,
  19293. * defined by `floating.alongAxis`, where `alongAxis` is an ID, an {@link Ext.chart.AbstractChart#axes} config index,
  19294. * or the other axis itself. `alongAxis` must have an opposite {@link Ext.chart.axis.Axis#getAlignment alignment}.
  19295. * For example:
  19296. *
  19297. *
  19298. * axes: [
  19299. * {
  19300. * title: 'Average Temperature (F)',
  19301. * type: 'numeric',
  19302. * position: 'left',
  19303. * id: 'temperature-vertical-axis',
  19304. * minimum: -30,
  19305. * maximum: 130
  19306. * },
  19307. * {
  19308. * title: 'Month (2013)',
  19309. * type: 'category',
  19310. * position: 'bottom',
  19311. * floating: {
  19312. * value: 32,
  19313. * alongAxis: 'temperature-vertical-axis'
  19314. * }
  19315. * }
  19316. * ]
  19317. */
  19318. floating: null
  19319. },
  19320. titleOffset: 0,
  19321. spriteAnimationCount: 0,
  19322. boundSeries: [],
  19323. sprites: null,
  19324. surface: null,
  19325. /**
  19326. * @private
  19327. * @property {Array} range
  19328. * The full data range of the axis. Should not be set directly, Clear it to `null`
  19329. * and use `getRange` to update.
  19330. */
  19331. range: null,
  19332. defaultRange: [
  19333. 0,
  19334. 1
  19335. ],
  19336. rangePadding: 0.5,
  19337. xValues: [],
  19338. yValues: [],
  19339. masterAxis: null,
  19340. applyRotation: function(rotation) {
  19341. var twoPie = Math.PI * 2;
  19342. return (rotation % twoPie + Math.PI) % twoPie - Math.PI;
  19343. },
  19344. updateRotation: function(rotation) {
  19345. var sprites = this.getSprites(),
  19346. position = this.getPosition();
  19347. if (!this.getHidden() && position === 'angular' && sprites[0]) {
  19348. sprites[0].setAttributes({
  19349. baseRotation: rotation
  19350. });
  19351. }
  19352. },
  19353. applyTitle: function(title, oldTitle) {
  19354. var surface;
  19355. if (Ext.isString(title)) {
  19356. title = {
  19357. text: title
  19358. };
  19359. }
  19360. if (!oldTitle) {
  19361. oldTitle = Ext.create('sprite.text', title);
  19362. if ((surface = this.getSurface())) {
  19363. surface.add(oldTitle);
  19364. }
  19365. } else {
  19366. oldTitle.setAttributes(title);
  19367. }
  19368. return oldTitle;
  19369. },
  19370. getAdjustByMajorUnit: function() {
  19371. return !this.getHidden() && this.callParent();
  19372. },
  19373. applyFloating: function(floating, oldFloating) {
  19374. if (floating === null) {
  19375. floating = {
  19376. value: null,
  19377. alongAxis: null
  19378. };
  19379. } else if (Ext.isNumber(floating)) {
  19380. floating = {
  19381. value: floating,
  19382. alongAxis: null
  19383. };
  19384. }
  19385. if (Ext.isObject(floating)) {
  19386. if (oldFloating && oldFloating.alongAxis) {
  19387. delete this.getChart().getAxis(oldFloating.alongAxis).floatingAxes[this.getId()];
  19388. }
  19389. return floating;
  19390. }
  19391. return oldFloating;
  19392. },
  19393. constructor: function(config) {
  19394. var me = this,
  19395. id;
  19396. me.sprites = [];
  19397. me.labels = [];
  19398. // Maps IDs of the axes that float along this axis to their floating values.
  19399. me.floatingAxes = {};
  19400. config = config || {};
  19401. if (config.position === 'angular') {
  19402. config.style = config.style || {};
  19403. config.style.estStepSize = 1;
  19404. }
  19405. if ('id' in config) {
  19406. id = config.id;
  19407. } else if ('id' in me.config) {
  19408. id = me.config.id;
  19409. } else {
  19410. id = me.getId();
  19411. }
  19412. me.setId(id);
  19413. me.mixins.observable.constructor.apply(me, arguments);
  19414. },
  19415. /**
  19416. * @private
  19417. * @return {String}
  19418. */
  19419. getAlignment: function() {
  19420. switch (this.getPosition()) {
  19421. case 'left':
  19422. case 'right':
  19423. return 'vertical';
  19424. case 'top':
  19425. case 'bottom':
  19426. return 'horizontal';
  19427. case 'radial':
  19428. return 'radial';
  19429. case 'angular':
  19430. return 'angular';
  19431. }
  19432. },
  19433. /**
  19434. * @private
  19435. * @return {String}
  19436. */
  19437. getGridAlignment: function() {
  19438. switch (this.getPosition()) {
  19439. case 'left':
  19440. case 'right':
  19441. return 'horizontal';
  19442. case 'top':
  19443. case 'bottom':
  19444. return 'vertical';
  19445. case 'radial':
  19446. return 'circular';
  19447. case 'angular':
  19448. return 'radial';
  19449. }
  19450. },
  19451. /**
  19452. * @private
  19453. * Get the surface for drawing the series sprites
  19454. */
  19455. getSurface: function() {
  19456. var me = this,
  19457. chart = me.getChart();
  19458. if (chart && !me.surface) {
  19459. var surface = me.surface = chart.getSurface(me.getId(), 'axis'),
  19460. gridSurface = me.gridSurface = chart.getSurface('main');
  19461. gridSurface.waitFor(surface);
  19462. me.getGrid();
  19463. me.createLimits();
  19464. }
  19465. return me.surface;
  19466. },
  19467. createLimits: function() {
  19468. var me = this,
  19469. chart = me.getChart(),
  19470. axisSprite = me.getSprites()[0],
  19471. gridAlignment = me.getGridAlignment(),
  19472. limits;
  19473. if (me.getLimits() && gridAlignment) {
  19474. gridAlignment = gridAlignment.replace('3d', '');
  19475. me.limits = limits = {
  19476. surface: chart.getSurface('overlay'),
  19477. lines: new Ext.chart.Markers(),
  19478. titles: new Ext.draw.sprite.Instancing()
  19479. };
  19480. limits.lines.setTemplate({
  19481. xclass: 'grid.' + gridAlignment
  19482. });
  19483. limits.lines.getTemplate().setAttributes({
  19484. strokeStyle: 'black'
  19485. }, true);
  19486. limits.surface.add(limits.lines);
  19487. axisSprite.bindMarker(gridAlignment + '-limit-lines', me.limits.lines);
  19488. me.limitTitleTpl = new Ext.draw.sprite.Text();
  19489. limits.titles.setTemplate(me.limitTitleTpl);
  19490. limits.surface.add(limits.titles);
  19491. }
  19492. },
  19493. applyGrid: function(grid) {
  19494. // Returning an empty object here if grid was set to 'true' so that
  19495. // config merging in the theme works properly.
  19496. if (grid === true) {
  19497. return {};
  19498. }
  19499. return grid;
  19500. },
  19501. updateGrid: function(grid) {
  19502. var me = this,
  19503. chart = me.getChart();
  19504. if (!chart) {
  19505. me.on({
  19506. chartattached: Ext.bind(me.updateGrid, me, [
  19507. grid
  19508. ]),
  19509. single: true
  19510. });
  19511. return;
  19512. }
  19513. var gridSurface = me.gridSurface,
  19514. axisSprite = me.getSprites()[0],
  19515. gridAlignment = me.getGridAlignment(),
  19516. gridSprite;
  19517. if (grid) {
  19518. gridSprite = me.gridSpriteEven;
  19519. if (!gridSprite) {
  19520. gridSprite = me.gridSpriteEven = new Ext.chart.Markers();
  19521. gridSprite.setTemplate({
  19522. xclass: 'grid.' + gridAlignment
  19523. });
  19524. gridSurface.add(gridSprite);
  19525. axisSprite.bindMarker(gridAlignment + '-even', gridSprite);
  19526. }
  19527. if (Ext.isObject(grid)) {
  19528. gridSprite.getTemplate().setAttributes(grid);
  19529. if (Ext.isObject(grid.even)) {
  19530. gridSprite.getTemplate().setAttributes(grid.even);
  19531. }
  19532. }
  19533. gridSprite = me.gridSpriteOdd;
  19534. if (!gridSprite) {
  19535. gridSprite = me.gridSpriteOdd = new Ext.chart.Markers();
  19536. gridSprite.setTemplate({
  19537. xclass: 'grid.' + gridAlignment
  19538. });
  19539. gridSurface.add(gridSprite);
  19540. axisSprite.bindMarker(gridAlignment + '-odd', gridSprite);
  19541. }
  19542. if (Ext.isObject(grid)) {
  19543. gridSprite.getTemplate().setAttributes(grid);
  19544. if (Ext.isObject(grid.odd)) {
  19545. gridSprite.getTemplate().setAttributes(grid.odd);
  19546. }
  19547. }
  19548. }
  19549. },
  19550. updateMinorTickSteps: function(minorTickSteps) {
  19551. var me = this,
  19552. sprites = me.getSprites(),
  19553. axisSprite = sprites && sprites[0],
  19554. surface;
  19555. if (axisSprite) {
  19556. axisSprite.setAttributes({
  19557. minorTicks: !!minorTickSteps
  19558. });
  19559. surface = me.getSurface();
  19560. if (!me.isConfiguring && surface) {
  19561. surface.renderFrame();
  19562. }
  19563. }
  19564. },
  19565. /**
  19566. *
  19567. * Mapping data value into coordinate.
  19568. *
  19569. * @param {*} value
  19570. * @param {String} field
  19571. * @param {Number} [idx]
  19572. * @param {Ext.util.MixedCollection} [items]
  19573. * @return {Number}
  19574. */
  19575. getCoordFor: function(value, field, idx, items) {
  19576. return this.getLayout().getCoordFor(value, field, idx, items);
  19577. },
  19578. applyPosition: function(pos) {
  19579. return pos.toLowerCase();
  19580. },
  19581. applyLength: function(length, oldLength) {
  19582. return length > 0 ? length : oldLength;
  19583. },
  19584. applyLabel: function(label, oldLabel) {
  19585. if (!oldLabel) {
  19586. oldLabel = new Ext.draw.sprite.Text({});
  19587. }
  19588. if (label) {
  19589. if (this.limitTitleTpl) {
  19590. this.limitTitleTpl.setAttributes(label);
  19591. }
  19592. oldLabel.setAttributes(label);
  19593. }
  19594. return oldLabel;
  19595. },
  19596. applyLayout: function(layout, oldLayout) {
  19597. layout = Ext.factory(layout, null, oldLayout, 'axisLayout');
  19598. layout.setAxis(this);
  19599. return layout;
  19600. },
  19601. applySegmenter: function(segmenter, oldSegmenter) {
  19602. segmenter = Ext.factory(segmenter, null, oldSegmenter, 'segmenter');
  19603. segmenter.setAxis(this);
  19604. return segmenter;
  19605. },
  19606. updateMinimum: function() {
  19607. this.range = null;
  19608. },
  19609. updateMaximum: function() {
  19610. this.range = null;
  19611. },
  19612. hideLabels: function() {
  19613. this.getSprites()[0].setDirty(true);
  19614. this.setLabel({
  19615. hidden: true
  19616. });
  19617. },
  19618. showLabels: function() {
  19619. this.getSprites()[0].setDirty(true);
  19620. this.setLabel({
  19621. hidden: false
  19622. });
  19623. },
  19624. /**
  19625. * Invokes renderFrame on this axis's surface(s)
  19626. */
  19627. renderFrame: function() {
  19628. this.getSurface().renderFrame();
  19629. },
  19630. updateChart: function(newChart, oldChart) {
  19631. var me = this,
  19632. surface;
  19633. if (oldChart) {
  19634. oldChart.unregister(me);
  19635. oldChart.un('serieschange', me.onSeriesChange, me);
  19636. me.linkAxis();
  19637. me.fireEvent('chartdetached', oldChart, me);
  19638. }
  19639. if (newChart) {
  19640. newChart.on('serieschange', me.onSeriesChange, me);
  19641. me.surface = null;
  19642. surface = me.getSurface();
  19643. me.getLabel().setSurface(surface);
  19644. surface.add(me.getSprites());
  19645. surface.add(me.getTitle());
  19646. newChart.register(me);
  19647. me.fireEvent('chartattached', newChart, me);
  19648. }
  19649. },
  19650. applyBackground: function(background) {
  19651. var rect = Ext.ClassManager.getByAlias('sprite.rect');
  19652. return rect.def.normalize(background);
  19653. },
  19654. /**
  19655. * @protected
  19656. * Invoked when data has changed.
  19657. */
  19658. processData: function() {
  19659. this.getLayout().processData();
  19660. this.range = null;
  19661. },
  19662. getDirection: function() {
  19663. return this.getChart().getDirectionForAxis(this.getPosition());
  19664. },
  19665. isSide: function() {
  19666. var position = this.getPosition();
  19667. return position === 'left' || position === 'right';
  19668. },
  19669. applyFields: function(fields) {
  19670. return Ext.Array.from(fields);
  19671. },
  19672. applyVisibleRange: function(visibleRange, oldVisibleRange) {
  19673. this.getChart();
  19674. // If it is in reversed order swap them
  19675. if (visibleRange[0] > visibleRange[1]) {
  19676. var temp = visibleRange[0];
  19677. visibleRange[0] = visibleRange[1];
  19678. visibleRange[0] = temp;
  19679. }
  19680. if (visibleRange[1] === visibleRange[0]) {
  19681. visibleRange[1] += 1 / this.getMaxZoom();
  19682. }
  19683. if (visibleRange[1] > visibleRange[0] + 1) {
  19684. visibleRange[0] = 0;
  19685. visibleRange[1] = 1;
  19686. } else if (visibleRange[0] < 0) {
  19687. visibleRange[1] -= visibleRange[0];
  19688. visibleRange[0] = 0;
  19689. } else if (visibleRange[1] > 1) {
  19690. visibleRange[0] -= visibleRange[1] - 1;
  19691. visibleRange[1] = 1;
  19692. }
  19693. if (oldVisibleRange && visibleRange[0] === oldVisibleRange[0] && visibleRange[1] === oldVisibleRange[1]) {
  19694. return undefined;
  19695. }
  19696. return visibleRange;
  19697. },
  19698. updateVisibleRange: function(visibleRange) {
  19699. this.fireEvent('visiblerangechange', this, visibleRange);
  19700. },
  19701. onSeriesChange: function(chart) {
  19702. var me = this,
  19703. series = chart.getSeries(),
  19704. boundSeries = [],
  19705. linkedTo, masterAxis, getAxisMethod, i, ln;
  19706. if (series) {
  19707. getAxisMethod = 'get' + me.getDirection() + 'Axis';
  19708. for (i = 0 , ln = series.length; i < ln; i++) {
  19709. if (this === series[i][getAxisMethod]()) {
  19710. boundSeries.push(series[i]);
  19711. }
  19712. }
  19713. }
  19714. me.boundSeries = boundSeries;
  19715. linkedTo = me.getLinkedTo();
  19716. masterAxis = !Ext.isEmpty(linkedTo) && chart.getAxis(linkedTo);
  19717. if (masterAxis) {
  19718. me.linkAxis(masterAxis);
  19719. } else {
  19720. me.getLayout().processData();
  19721. }
  19722. },
  19723. linkAxis: function(masterAxis) {
  19724. var me = this;
  19725. function link(action, slave, master) {
  19726. master.getLayout()[action]('datachange', 'onDataChange', slave);
  19727. master[action]('rangechange', 'onMasterAxisRangeChange', slave);
  19728. }
  19729. if (me.masterAxis) {
  19730. if (!me.masterAxis.destroyed) {
  19731. link('un', me, me.masterAxis);
  19732. }
  19733. me.masterAxis = null;
  19734. }
  19735. if (masterAxis) {
  19736. if (masterAxis.type !== this.type) {
  19737. Ext.Error.raise("Linked axes must be of the same type.");
  19738. }
  19739. link('on', me, masterAxis);
  19740. me.onDataChange(masterAxis.getLayout().labels);
  19741. me.onMasterAxisRangeChange(masterAxis, masterAxis.range);
  19742. me.setStyle(Ext.apply({}, me.config.style, masterAxis.config.style));
  19743. me.setTitle(Ext.apply({}, me.config.title, masterAxis.config.title));
  19744. me.setLabel(Ext.apply({}, me.config.label, masterAxis.config.label));
  19745. me.masterAxis = masterAxis;
  19746. }
  19747. },
  19748. onDataChange: function(data) {
  19749. this.getLayout().labels = data;
  19750. },
  19751. onMasterAxisRangeChange: function(masterAxis, range) {
  19752. this.range = range;
  19753. },
  19754. applyRange: function(newRange) {
  19755. if (!newRange) {
  19756. return this.dataRange.slice(0);
  19757. } else {
  19758. return [
  19759. newRange[0] === null ? this.dataRange[0] : newRange[0],
  19760. newRange[1] === null ? this.dataRange[1] : newRange[1]
  19761. ];
  19762. }
  19763. },
  19764. /**
  19765. * @private
  19766. */
  19767. setBoundSeriesRange: function(range) {
  19768. var boundSeries = this.boundSeries,
  19769. style = {},
  19770. series, i, sprites, j, ln;
  19771. style['range' + this.getDirection()] = range;
  19772. for (i = 0 , ln = boundSeries.length; i < ln; i++) {
  19773. series = boundSeries[i];
  19774. if (series.getHidden() === true) {
  19775. continue;
  19776. }
  19777. sprites = series.getSprites();
  19778. for (j = 0; j < sprites.length; j++) {
  19779. sprites[j].setAttributes(style);
  19780. }
  19781. }
  19782. },
  19783. /**
  19784. * Get the range derived from all the bound series.
  19785. * The range value is cached and returned the next time this method is called.
  19786. * Set `recalculate` to `true` to recalculate the range, if changes to the
  19787. * chart, its components or data are expected to affect the range.
  19788. * @param {Boolean} [recalculate]
  19789. * @return {Number[]}
  19790. */
  19791. getRange: function(recalculate) {
  19792. var me = this,
  19793. range = recalculate ? null : me.range,
  19794. oldRange = me.oldRange,
  19795. minimum, maximum;
  19796. if (!range) {
  19797. if (me.masterAxis) {
  19798. range = me.masterAxis.range;
  19799. } else {
  19800. minimum = me.getMinimum();
  19801. maximum = me.getMaximum();
  19802. if (Ext.isNumber(minimum) && Ext.isNumber(maximum)) {
  19803. range = [
  19804. minimum,
  19805. maximum
  19806. ];
  19807. } else {
  19808. range = me.calculateRange();
  19809. }
  19810. me.range = range;
  19811. }
  19812. }
  19813. if (range && (!oldRange || range[0] !== oldRange[0] || range[1] !== oldRange[1])) {
  19814. me.fireEvent('rangechange', me, range, oldRange);
  19815. me.oldRange = range;
  19816. }
  19817. return range;
  19818. },
  19819. isSingleDataPoint: function(range) {
  19820. return (range[0] + this.rangePadding) === 0 && (range[1] - this.rangePadding) === 0;
  19821. },
  19822. calculateRange: function() {
  19823. var me = this,
  19824. boundSeries = me.boundSeries,
  19825. layout = me.getLayout(),
  19826. segmenter = me.getSegmenter(),
  19827. minimum = me.getMinimum(),
  19828. maximum = me.getMaximum(),
  19829. visibleRange = me.getVisibleRange(),
  19830. getRangeMethod = 'get' + me.getDirection() + 'Range',
  19831. expandRangeBy = me.getExpandRangeBy(),
  19832. context, attr, majorTicks, series, i, ln, seriesRange,
  19833. range = [
  19834. NaN,
  19835. NaN
  19836. ];
  19837. // For each series bound to this axis, ask the series for its min/max values
  19838. // and use them to find the overall min/max.
  19839. for (i = 0 , ln = boundSeries.length; i < ln; i++) {
  19840. series = boundSeries[i];
  19841. if (series.getHidden() === true) {
  19842. continue;
  19843. }
  19844. seriesRange = series[getRangeMethod]();
  19845. if (seriesRange) {
  19846. Ext.chart.Util.expandRange(range, seriesRange);
  19847. }
  19848. }
  19849. range = Ext.chart.Util.validateRange(range, me.defaultRange, me.rangePadding);
  19850. // The second condition is there to account for a special case where we only have
  19851. // a single data point, so the effective range of coordinated data is 0 (whatever
  19852. // the actual value of that single data point is, it will be assigned an index of
  19853. // zero, as the first and only data point). Since zero range is invalid, the
  19854. // validateRange function above will expand the range by the value of the rangePadding,
  19855. // which makes further expansion by the value of expandRangeBy unnecessary.
  19856. if (expandRangeBy && (!me.isSingleDataPoint(range))) {
  19857. range[0] -= expandRangeBy;
  19858. range[1] += expandRangeBy;
  19859. }
  19860. if (isFinite(minimum)) {
  19861. range[0] = minimum;
  19862. }
  19863. if (isFinite(maximum)) {
  19864. range[1] = maximum;
  19865. }
  19866. // When series `fullStack` config is used, the values may add up to
  19867. // slightly more than the value of the `fullStackTotal` config
  19868. // because of a precision error.
  19869. range[0] = Ext.Number.correctFloat(range[0]);
  19870. range[1] = Ext.Number.correctFloat(range[1]);
  19871. me.range = range;
  19872. // It's important to call 'me.reconcileRange' after the 'range'
  19873. // has been assigned to avoid circular calls.
  19874. if (me.getReconcileRange()) {
  19875. me.reconcileRange();
  19876. }
  19877. // TODO: Find a better way to do this.
  19878. // TODO: The original design didn't take into account that the range of an axis
  19879. // TODO: will depend not just on the range of the data of the bound series in the
  19880. // TODO: direction of the axis, but also on the range of other axes with the
  19881. // TODO: same direction and on the segmentation of the axis (interval between
  19882. // TODO: major ticks).
  19883. // TODO: While the fist omission was possible to retrofit rather gracefully
  19884. // TODO: by adding the axis.reconcileRange method, the second one is harder to deal with.
  19885. // TODO: The issue is that the resulting axis segmentation, which is a part of
  19886. // TODO: the axis sprite layout has to be known before layout has begun.
  19887. // TODO: Example for the logic below:
  19888. // TODO: If we have a range of data of 0..34.5 the step will be 2 and we
  19889. // TODO: will round up the max to 36 based on that step, but when the range is 0..36,
  19890. // TODO: the step becomes 5, so we have to reconcile the range once again where max
  19891. // TODO: becomes 40.
  19892. if (range[0] !== range[1] && me.getAdjustByMajorUnit() && segmenter.adjustByMajorUnit && !me.getMajorTickSteps()) {
  19893. attr = Ext.Object.chain(me.getSprites()[0].attr);
  19894. attr.min = range[0];
  19895. attr.max = range[1];
  19896. attr.visibleMin = visibleRange[0];
  19897. attr.visibleMax = visibleRange[1];
  19898. context = {
  19899. attr: attr,
  19900. segmenter: segmenter
  19901. };
  19902. layout.calculateLayout(context);
  19903. majorTicks = context.majorTicks;
  19904. if (majorTicks) {
  19905. segmenter.adjustByMajorUnit(majorTicks.step, majorTicks.unit.scale, range);
  19906. attr.min = range[0];
  19907. attr.max = range[1];
  19908. context.majorTicks = null;
  19909. layout.calculateLayout(context);
  19910. majorTicks = context.majorTicks;
  19911. segmenter.adjustByMajorUnit(majorTicks.step, majorTicks.unit.scale, range);
  19912. } else if (!me.hasClearRangePending) {
  19913. // Axis hasn't been rendered yet.
  19914. me.hasClearRangePending = true;
  19915. me.getChart().on('layout', 'clearRange', me);
  19916. }
  19917. }
  19918. return range;
  19919. },
  19920. /**
  19921. * @private
  19922. */
  19923. clearRange: function() {
  19924. this.hasClearRangePending = null;
  19925. this.range = null;
  19926. },
  19927. /**
  19928. * Expands the range of the axis
  19929. * based on the range of other axes with the same direction (if any).
  19930. */
  19931. reconcileRange: function() {
  19932. var me = this,
  19933. axes = me.getChart().getAxes(),
  19934. direction = me.getDirection(),
  19935. i, ln, axis, range;
  19936. if (!axes) {
  19937. return;
  19938. }
  19939. for (i = 0 , ln = axes.length; i < ln; i++) {
  19940. axis = axes[i];
  19941. range = axis.getRange();
  19942. if (axis === me || axis.getDirection() !== direction || !range || !axis.getReconcileRange()) {
  19943. continue;
  19944. }
  19945. if (range[0] < me.range[0]) {
  19946. me.range[0] = range[0];
  19947. }
  19948. if (range[1] > me.range[1]) {
  19949. me.range[1] = range[1];
  19950. }
  19951. }
  19952. },
  19953. applyStyle: function(style, oldStyle) {
  19954. var cls = Ext.ClassManager.getByAlias('sprite.' + this.seriesType);
  19955. if (cls && cls.def) {
  19956. style = cls.def.normalize(style);
  19957. }
  19958. oldStyle = Ext.apply(oldStyle || {}, style);
  19959. return oldStyle;
  19960. },
  19961. themeOnlyIfConfigured: {
  19962. grid: true
  19963. },
  19964. updateTheme: function(theme) {
  19965. var me = this,
  19966. axisTheme = theme.getAxis(),
  19967. position = me.getPosition(),
  19968. initialConfig = me.getInitialConfig(),
  19969. defaultConfig = me.defaultConfig,
  19970. configs = me.self.getConfigurator().configs,
  19971. genericAxisTheme = axisTheme.defaults,
  19972. specificAxisTheme = axisTheme[position],
  19973. themeOnlyIfConfigured = me.themeOnlyIfConfigured,
  19974. key, value, isObjValue, isUnusedConfig, initialValue, cfg;
  19975. axisTheme = Ext.merge({}, genericAxisTheme, specificAxisTheme);
  19976. for (key in axisTheme) {
  19977. value = axisTheme[key];
  19978. cfg = configs[key];
  19979. if (value !== null && value !== undefined && cfg) {
  19980. initialValue = initialConfig[key];
  19981. isObjValue = Ext.isObject(value);
  19982. isUnusedConfig = initialValue === defaultConfig[key];
  19983. if (isObjValue) {
  19984. if (isUnusedConfig && themeOnlyIfConfigured[key]) {
  19985. continue;
  19986. }
  19987. value = Ext.merge({}, value, initialValue);
  19988. }
  19989. if (isUnusedConfig || isObjValue) {
  19990. me[cfg.names.set](value);
  19991. }
  19992. }
  19993. }
  19994. },
  19995. updateCenter: function(center) {
  19996. var me = this,
  19997. sprites = me.getSprites(),
  19998. axisSprite = sprites[0],
  19999. centerX = center[0],
  20000. centerY = center[1];
  20001. if (axisSprite) {
  20002. axisSprite.setAttributes({
  20003. centerX: centerX,
  20004. centerY: centerY
  20005. });
  20006. }
  20007. if (me.gridSpriteEven) {
  20008. me.gridSpriteEven.getTemplate().setAttributes({
  20009. translationX: centerX,
  20010. translationY: centerY,
  20011. rotationCenterX: centerX,
  20012. rotationCenterY: centerY
  20013. });
  20014. }
  20015. if (me.gridSpriteOdd) {
  20016. me.gridSpriteOdd.getTemplate().setAttributes({
  20017. translationX: centerX,
  20018. translationY: centerY,
  20019. rotationCenterX: centerX,
  20020. rotationCenterY: centerY
  20021. });
  20022. }
  20023. },
  20024. getSprites: function() {
  20025. if (!this.getChart()) {
  20026. return;
  20027. }
  20028. var me = this,
  20029. range = me.getRange(),
  20030. position = me.getPosition(),
  20031. chart = me.getChart(),
  20032. animation = chart.getAnimation(),
  20033. length = me.getLength(),
  20034. axisClass = me.superclass,
  20035. mainSprite, style, animationModifier;
  20036. // If animation is false, then stop animation.
  20037. if (animation === false) {
  20038. animation = {
  20039. duration: 0
  20040. };
  20041. }
  20042. style = Ext.applyIf({
  20043. position: position,
  20044. axis: me,
  20045. length: length,
  20046. grid: me.getGrid(),
  20047. hidden: me.getHidden(),
  20048. titleOffset: me.titleOffset,
  20049. layout: me.getLayout(),
  20050. segmenter: me.getSegmenter(),
  20051. totalAngle: me.getTotalAngle(),
  20052. label: me.getLabel()
  20053. }, me.getStyle());
  20054. if (range) {
  20055. style.min = range[0];
  20056. style.max = range[1];
  20057. }
  20058. // If the sprites are not created.
  20059. if (!me.sprites.length) {
  20060. while (!axisClass.xtype) {
  20061. axisClass = axisClass.superclass;
  20062. }
  20063. mainSprite = Ext.create('sprite.' + axisClass.xtype, style);
  20064. animationModifier = mainSprite.getAnimation();
  20065. animationModifier.setCustomDurations({
  20066. baseRotation: 0
  20067. });
  20068. animationModifier.on('animationstart', 'onAnimationStart', me);
  20069. animationModifier.on('animationend', 'onAnimationEnd', me);
  20070. mainSprite.setLayout(me.getLayout());
  20071. mainSprite.setSegmenter(me.getSegmenter());
  20072. mainSprite.setLabel(me.getLabel());
  20073. me.sprites.push(mainSprite);
  20074. me.updateTitleSprite();
  20075. } else {
  20076. mainSprite = me.sprites[0];
  20077. mainSprite.setAnimation(animation);
  20078. mainSprite.setAttributes(style);
  20079. }
  20080. if (me.getRenderer()) {
  20081. mainSprite.setRenderer(me.getRenderer());
  20082. }
  20083. return me.sprites;
  20084. },
  20085. /**
  20086. * @private
  20087. */
  20088. performLayout: function() {
  20089. if (this.isConfiguring) {
  20090. return;
  20091. }
  20092. var me = this,
  20093. sprites = me.getSprites(),
  20094. surface = me.getSurface(),
  20095. chart = me.getChart(),
  20096. sprite = sprites && sprites[0];
  20097. if (chart && surface && sprite) {
  20098. sprite.callUpdater(null, 'layout');
  20099. // recalculate axis ticks
  20100. chart.scheduleLayout();
  20101. }
  20102. },
  20103. updateTitleSprite: function() {
  20104. var me = this,
  20105. length = me.getLength();
  20106. if (!me.sprites[0] || !Ext.isNumber(length)) {
  20107. return;
  20108. }
  20109. var thickness = this.sprites[0].thickness,
  20110. surface = me.getSurface(),
  20111. title = me.getTitle(),
  20112. position = me.getPosition(),
  20113. margin = me.getMargin(),
  20114. titleMargin = me.getTitleMargin(),
  20115. anchor = surface.roundPixel(length / 2);
  20116. if (title) {
  20117. switch (position) {
  20118. case 'top':
  20119. title.setAttributes({
  20120. x: anchor,
  20121. y: margin + titleMargin / 2,
  20122. textBaseline: 'top',
  20123. textAlign: 'center'
  20124. }, true);
  20125. title.applyTransformations();
  20126. me.titleOffset = title.getBBox().height + titleMargin;
  20127. break;
  20128. case 'bottom':
  20129. title.setAttributes({
  20130. x: anchor,
  20131. y: thickness + titleMargin / 2,
  20132. textBaseline: 'top',
  20133. textAlign: 'center'
  20134. }, true);
  20135. title.applyTransformations();
  20136. me.titleOffset = title.getBBox().height + titleMargin;
  20137. break;
  20138. case 'left':
  20139. title.setAttributes({
  20140. x: margin + titleMargin / 2,
  20141. y: anchor,
  20142. textBaseline: 'top',
  20143. textAlign: 'center',
  20144. rotationCenterX: margin + titleMargin / 2,
  20145. rotationCenterY: anchor,
  20146. rotationRads: -Math.PI / 2
  20147. }, true);
  20148. title.applyTransformations();
  20149. me.titleOffset = title.getBBox().width + titleMargin;
  20150. break;
  20151. case 'right':
  20152. title.setAttributes({
  20153. x: thickness - margin + titleMargin / 2,
  20154. y: anchor,
  20155. textBaseline: 'bottom',
  20156. textAlign: 'center',
  20157. rotationCenterX: thickness + titleMargin / 2,
  20158. rotationCenterY: anchor,
  20159. rotationRads: Math.PI / 2
  20160. }, true);
  20161. title.applyTransformations();
  20162. me.titleOffset = title.getBBox().width + titleMargin;
  20163. break;
  20164. }
  20165. }
  20166. },
  20167. onThicknessChanged: function() {
  20168. this.getChart().onThicknessChanged();
  20169. },
  20170. getThickness: function() {
  20171. if (this.getHidden()) {
  20172. return 0;
  20173. }
  20174. return (this.sprites[0] && this.sprites[0].thickness || 1) + this.titleOffset + this.getMargin();
  20175. },
  20176. onAnimationStart: function() {
  20177. this.spriteAnimationCount++;
  20178. if (this.spriteAnimationCount === 1) {
  20179. this.fireEvent('animationstart', this);
  20180. }
  20181. },
  20182. onAnimationEnd: function() {
  20183. this.spriteAnimationCount--;
  20184. if (this.spriteAnimationCount === 0) {
  20185. this.fireEvent('animationend', this);
  20186. }
  20187. },
  20188. // Methods used in ComponentQuery and controller
  20189. getItemId: function() {
  20190. return this.getId();
  20191. },
  20192. getAncestorIds: function() {
  20193. return [
  20194. this.getChart().getId()
  20195. ];
  20196. },
  20197. isXType: function(xtype) {
  20198. return xtype === 'axis';
  20199. },
  20200. // Override the Observable's method to redirect listener scope
  20201. // resolution to the chart.
  20202. resolveListenerScope: function(defaultScope) {
  20203. var me = this,
  20204. namedScope = Ext._namedScopes[defaultScope],
  20205. chart = me.getChart(),
  20206. scope;
  20207. if (!namedScope) {
  20208. scope = chart ? chart.resolveListenerScope(defaultScope, false) : (defaultScope || me);
  20209. } else if (namedScope.isThis) {
  20210. scope = me;
  20211. } else if (namedScope.isController) {
  20212. scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
  20213. } else if (namedScope.isSelf) {
  20214. scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
  20215. // Class body listener. No chart controller, nor chart container controller.
  20216. if (scope === chart && !chart.getInheritedConfig('defaultListenerScope')) {
  20217. scope = me;
  20218. }
  20219. }
  20220. return scope;
  20221. },
  20222. destroy: function() {
  20223. var me = this;
  20224. me.setChart(null);
  20225. me.surface.destroy();
  20226. me.surface = null;
  20227. me.callParent();
  20228. }
  20229. });
  20230. /**
  20231. * The legend base class adapater for modern toolkit.
  20232. */
  20233. Ext.define('Ext.chart.legend.LegendBase', {
  20234. extend: 'Ext.dataview.DataView',
  20235. config: {
  20236. itemTpl: [
  20237. '<span class="',
  20238. Ext.baseCSSPrefix,
  20239. 'legend-item-marker {[ values.disabled ? Ext.baseCSSPrefix + \'legend-item-inactive\' : \'\' ]}" style="background:{mark};"></span>{name}'
  20240. ],
  20241. inline: true,
  20242. scrollable: false
  20243. },
  20244. // for IE11 vertical align
  20245. constructor: function(config) {
  20246. this.callParent([
  20247. config
  20248. ]);
  20249. var scroller = this.getScrollable(),
  20250. onDrag = scroller.onDrag;
  20251. scroller.onDrag = function(e) {
  20252. e.stopPropagation();
  20253. onDrag.call(this, e);
  20254. };
  20255. },
  20256. updateDocked: function(docked, oldDocked) {
  20257. var me = this,
  20258. el = me.el;
  20259. me.callParent([
  20260. docked,
  20261. oldDocked
  20262. ]);
  20263. switch (docked) {
  20264. case 'top':
  20265. case 'bottom':
  20266. el.addCls(me.horizontalCls);
  20267. el.removeCls(me.verticalCls);
  20268. break;
  20269. case 'left':
  20270. case 'right':
  20271. el.addCls(me.verticalCls);
  20272. el.removeCls(me.horizontalCls);
  20273. break;
  20274. }
  20275. },
  20276. onChildTap: function(view, context) {
  20277. this.callParent([
  20278. view,
  20279. context
  20280. ]);
  20281. this.toggleItem(context.viewIndex);
  20282. }
  20283. });
  20284. /**
  20285. * This class provides a dataview-based chart legend.
  20286. */
  20287. Ext.define('Ext.chart.legend.Legend', {
  20288. extend: 'Ext.chart.legend.LegendBase',
  20289. alternateClassName: 'Ext.chart.Legend',
  20290. xtype: 'legend',
  20291. alias: 'legend.dom',
  20292. type: 'dom',
  20293. isLegend: true,
  20294. isDomLegend: true,
  20295. config: {
  20296. /**
  20297. * @cfg {Array}
  20298. * The rect of the legend relative to its container.
  20299. */
  20300. rect: null,
  20301. /**
  20302. * @cfg {Boolean} toggleable
  20303. * `true` to allow series items to have their visibility
  20304. * toggled by interaction with the legend items.
  20305. */
  20306. toggleable: true
  20307. },
  20308. /**
  20309. * @cfg {Ext.chart.legend.store.Store} store
  20310. * The {@link Ext.chart.legend.store.Store} to bind this legend to.
  20311. * @private
  20312. */
  20313. baseCls: Ext.baseCSSPrefix + 'legend',
  20314. horizontalCls: Ext.baseCSSPrefix + 'legend-horizontal',
  20315. verticalCls: Ext.baseCSSPrefix + 'legend-vertical',
  20316. toggleItem: function(index) {
  20317. if (!this.getToggleable()) {
  20318. return;
  20319. }
  20320. var store = this.getStore(),
  20321. disabledCount = 0,
  20322. disabled,
  20323. canToggle = true,
  20324. i, count, record;
  20325. if (store) {
  20326. count = store.getCount();
  20327. for (i = 0; i < count; i++) {
  20328. record = store.getAt(i);
  20329. if (record.get('disabled')) {
  20330. disabledCount++;
  20331. }
  20332. }
  20333. canToggle = count - disabledCount > 1;
  20334. record = store.getAt(index);
  20335. if (record) {
  20336. disabled = record.get('disabled');
  20337. if (disabled || canToggle) {
  20338. // This will trigger AbstractChart.onLegendStoreUpdate.
  20339. record.set('disabled', !disabled);
  20340. }
  20341. }
  20342. }
  20343. },
  20344. onResize: function(width, height, oldWidth, oldHeight) {
  20345. var me = this,
  20346. chart = me.chart;
  20347. if (!me.isConfiguring) {
  20348. if (chart) {
  20349. chart.scheduleLayout();
  20350. }
  20351. }
  20352. }
  20353. });
  20354. /**
  20355. * @private
  20356. */
  20357. Ext.define('Ext.chart.legend.sprite.Item', {
  20358. extend: 'Ext.draw.sprite.Composite',
  20359. alias: 'sprite.legenditem',
  20360. type: 'legenditem',
  20361. isLegendItem: true,
  20362. requires: [
  20363. 'Ext.draw.sprite.Text',
  20364. 'Ext.draw.sprite.Circle'
  20365. ],
  20366. inheritableStatics: {
  20367. def: {
  20368. processors: {
  20369. enabled: 'limited01',
  20370. markerLabelGap: 'number'
  20371. },
  20372. animationProcessors: {
  20373. enabled: null,
  20374. markerLabelGap: null
  20375. },
  20376. defaults: {
  20377. enabled: true,
  20378. markerLabelGap: 5
  20379. },
  20380. triggers: {
  20381. enabled: 'enabled',
  20382. markerLabelGap: 'layout'
  20383. },
  20384. updaters: {
  20385. layout: 'layoutUpdater',
  20386. enabled: 'enabledUpdater'
  20387. }
  20388. }
  20389. },
  20390. config: {
  20391. // Sprite's attributes are processed after initConfig.
  20392. // So we need to init below configs lazily, as otherwise
  20393. // adding sprites (created from those configs) to composite
  20394. // will result in an attempt to access attributes that
  20395. // composite doesn't have yet.
  20396. label: {
  20397. $value: {
  20398. type: 'text'
  20399. },
  20400. lazy: true
  20401. },
  20402. marker: {
  20403. $value: {
  20404. type: 'circle'
  20405. },
  20406. lazy: true
  20407. },
  20408. legend: null,
  20409. store: null,
  20410. record: null,
  20411. series: null
  20412. },
  20413. applyLabel: function(label, oldLabel) {
  20414. var sprite;
  20415. if (label) {
  20416. if (label.isSprite && label.type === 'text') {
  20417. sprite = label;
  20418. } else {
  20419. if (oldLabel && label.type === oldLabel.type) {
  20420. oldLabel.setConfig(label);
  20421. sprite = oldLabel;
  20422. this.scheduleUpdater(this.attr, 'layout');
  20423. } else {
  20424. sprite = new Ext.draw.sprite.Text(label);
  20425. }
  20426. }
  20427. }
  20428. return sprite;
  20429. },
  20430. defaultMarkerSize: 10,
  20431. updateLabel: function(label, oldLabel) {
  20432. var me = this;
  20433. me.removeSprite(oldLabel);
  20434. label.setAttributes({
  20435. textBaseline: 'middle'
  20436. });
  20437. me.addSprite(label);
  20438. me.scheduleUpdater(me.attr, 'layout');
  20439. },
  20440. applyMarker: function(config) {
  20441. var marker;
  20442. if (config) {
  20443. if (config.isSprite) {
  20444. marker = config;
  20445. } else {
  20446. marker = this.createMarker(config);
  20447. }
  20448. }
  20449. marker = this.resetMarker(marker, config);
  20450. return marker;
  20451. },
  20452. createMarker: function(config) {
  20453. var marker;
  20454. // If marker attributes are animated, the attributes change over
  20455. // time from default values to the values specified in the marker
  20456. // config. But the 'legenditem' sprite needs final values
  20457. // to properly layout its children.
  20458. delete config.animation;
  20459. if (config.type === 'image') {
  20460. delete config.width;
  20461. delete config.height;
  20462. }
  20463. marker = Ext.create('sprite.' + config.type, config);
  20464. return marker;
  20465. },
  20466. resetMarker: function(sprite, config) {
  20467. var size = config.size || this.defaultMarkerSize,
  20468. bbox, max, scale;
  20469. // Layout may not work properly,
  20470. // if the marker sprite is transformed to begin with.
  20471. sprite.setTransform([
  20472. 1,
  20473. 0,
  20474. 0,
  20475. 1,
  20476. 0,
  20477. 0
  20478. ], true);
  20479. if (config.type === 'image') {
  20480. sprite.setAttributes({
  20481. width: size,
  20482. height: size
  20483. });
  20484. } else {
  20485. // This should work with any sprite, irrespective of what attribute
  20486. // is used to control sprite's size ('size', 'r', or something else).
  20487. // However, the 'image' sprite above is a special case.
  20488. bbox = sprite.getBBox();
  20489. max = Math.max(bbox.width, bbox.height);
  20490. scale = size / max;
  20491. sprite.setAttributes({
  20492. scalingX: scale,
  20493. scalingY: scale
  20494. });
  20495. }
  20496. return sprite;
  20497. },
  20498. updateMarker: function(marker, oldMarker) {
  20499. var me = this;
  20500. me.removeSprite(oldMarker);
  20501. me.addSprite(marker);
  20502. me.scheduleUpdater(me.attr, 'layout');
  20503. },
  20504. updateSurface: function(surface, oldSurface) {
  20505. var me = this;
  20506. me.callParent([
  20507. surface,
  20508. oldSurface
  20509. ]);
  20510. if (surface) {
  20511. me.scheduleUpdater(me.attr, 'layout');
  20512. }
  20513. },
  20514. enabledUpdater: function(attr) {
  20515. var marker = this.getMarker();
  20516. if (marker) {
  20517. marker.setAttributes({
  20518. globalAlpha: attr.enabled ? 1 : 0.3
  20519. });
  20520. }
  20521. },
  20522. layoutUpdater: function() {
  20523. var me = this,
  20524. attr = me.attr,
  20525. label = me.getLabel(),
  20526. marker = me.getMarker(),
  20527. labelBBox, markerBBox, totalHeight;
  20528. // Measuring bounding boxes of transformed marker and label
  20529. // sprites and translating the sprites by required amount,
  20530. // makes layout virtually bullet-proof to unaccounted for
  20531. // changes in sprite attributes, whatever the sprite type may be.
  20532. markerBBox = marker.getBBox();
  20533. labelBBox = label.getBBox();
  20534. totalHeight = Math.max(markerBBox.height, labelBBox.height);
  20535. // Because we are getting an already transformed bounding box,
  20536. // we want to add to that transformation, not replace it,
  20537. // so setting translationX/Y attributes here would be inappropriate.
  20538. marker.transform([
  20539. 1,
  20540. 0,
  20541. 0,
  20542. 1,
  20543. -markerBBox.x,
  20544. -markerBBox.y + (totalHeight - markerBBox.height) / 2
  20545. ], true);
  20546. label.transform([
  20547. 1,
  20548. 0,
  20549. 0,
  20550. 1,
  20551. -labelBBox.x + markerBBox.width + attr.markerLabelGap,
  20552. -labelBBox.y + (totalHeight - labelBBox.height) / 2
  20553. ], true);
  20554. me.bboxUpdater(attr);
  20555. }
  20556. });
  20557. /**
  20558. * @private
  20559. */
  20560. Ext.define('Ext.chart.legend.sprite.Border', {
  20561. extend: 'Ext.draw.sprite.Rect',
  20562. alias: 'sprite.legendborder',
  20563. type: 'legendborder',
  20564. isLegendBorder: true
  20565. });
  20566. /**
  20567. * @private
  20568. * Singleton that provides methods used by the Ext.draw.Path
  20569. * for hit testing and finding path intersection points.
  20570. */
  20571. Ext.define('Ext.draw.PathUtil', function() {
  20572. var abs = Math.abs,
  20573. pow = Math.pow,
  20574. cos = Math.cos,
  20575. acos = Math.acos,
  20576. sqrt = Math.sqrt,
  20577. PI = Math.PI;
  20578. // For extra info see: http://pomax.github.io/bezierinfo/
  20579. return {
  20580. singleton: true,
  20581. requires: [
  20582. 'Ext.draw.overrides.hittest.Path',
  20583. 'Ext.draw.overrides.hittest.sprite.Path'
  20584. ],
  20585. /**
  20586. * @private
  20587. * Finds roots of a cubic equation in t, where t lies in the interval of [0,1].
  20588. * Based on http://www.particleincell.com/blog/2013/cubic-line-intersection/
  20589. * @param P {Number[]} Cubic equation coefficients.
  20590. * @return {Number[]} Returns an array of parametric intersection locations along the cubic,
  20591. * with -1 indicating an out-of-bounds intersection
  20592. * (before or after the end point or in the imaginary plane).
  20593. */
  20594. cubicRoots: function(P) {
  20595. var a = P[0],
  20596. b = P[1],
  20597. c = P[2],
  20598. d = P[3];
  20599. if (a === 0) {
  20600. return this.quadraticRoots(b, c, d);
  20601. }
  20602. var A = b / a,
  20603. B = c / a,
  20604. C = d / a,
  20605. Q = (3 * B - pow(A, 2)) / 9,
  20606. R = (9 * A * B - 27 * C - 2 * pow(A, 3)) / 54,
  20607. D = pow(Q, 3) + pow(R, 2),
  20608. // Polynomial discriminant.
  20609. t = [],
  20610. S, T, Im, th, i,
  20611. sign = Ext.Number.sign;
  20612. if (D >= 0) {
  20613. // Complex or duplicate roots.
  20614. S = sign(R + sqrt(D)) * pow(abs(R + sqrt(D)), 1 / 3);
  20615. T = sign(R - sqrt(D)) * pow(abs(R - sqrt(D)), 1 / 3);
  20616. t[0] = -A / 3 + (S + T);
  20617. // Real root.
  20618. t[1] = -A / 3 - (S + T) / 2;
  20619. // Real part of complex root.
  20620. t[2] = t[1];
  20621. // Real part of complex root.
  20622. Im = abs(sqrt(3) * (S - T) / 2);
  20623. // Complex part of root pair.
  20624. // Discard complex roots.
  20625. if (Im !== 0) {
  20626. t[1] = -1;
  20627. t[2] = -1;
  20628. }
  20629. } else {
  20630. // Distinct real roots.
  20631. th = acos(R / sqrt(-pow(Q, 3)));
  20632. t[0] = 2 * sqrt(-Q) * cos(th / 3) - A / 3;
  20633. t[1] = 2 * sqrt(-Q) * cos((th + 2 * PI) / 3) - A / 3;
  20634. t[2] = 2 * sqrt(-Q) * cos((th + 4 * PI) / 3) - A / 3;
  20635. }
  20636. // Discard out of spec roots.
  20637. for (i = 0; i < 3; i++) {
  20638. if (t[i] < 0 || t[i] > 1) {
  20639. t[i] = -1;
  20640. }
  20641. }
  20642. return t;
  20643. },
  20644. /**
  20645. * @private
  20646. * Finds roots of a quadratic equation in t, where t lies in the interval of [0,1].
  20647. * Takes three quadratic equation coefficients as parameters.
  20648. * @param a {Number}
  20649. * @param b {Number}
  20650. * @param c {Number}
  20651. * @return {Array}
  20652. */
  20653. quadraticRoots: function(a, b, c) {
  20654. var D, rD, t, i;
  20655. if (a === 0) {
  20656. return this.linearRoot(b, c);
  20657. }
  20658. D = b * b - 4 * a * c;
  20659. if (D === 0) {
  20660. // One real root.
  20661. t = [
  20662. -b / (2 * a)
  20663. ];
  20664. } else if (D > 0) {
  20665. // Distinct real roots.
  20666. rD = sqrt(D);
  20667. t = [
  20668. (-b - rD) / (2 * a),
  20669. (-b + rD) / (2 * a)
  20670. ];
  20671. } else {
  20672. // Complex roots.
  20673. return [];
  20674. }
  20675. for (i = 0; i < t.length; i++) {
  20676. if (t[i] < 0 || t[i] > 1) {
  20677. t[i] = -1;
  20678. }
  20679. }
  20680. return t;
  20681. },
  20682. /**
  20683. * @private
  20684. * Finds roots of a linear equation in t, where t lies in the interval of [0,1].
  20685. * Takes two linear equation coefficients as parameters.
  20686. * @param a {Number}
  20687. * @param b {Number}
  20688. * @return {Array}
  20689. */
  20690. linearRoot: function(a, b) {
  20691. var t = -b / a;
  20692. if (a === 0 || t < 0 || t > 1) {
  20693. return [];
  20694. }
  20695. return [
  20696. t
  20697. ];
  20698. },
  20699. /**
  20700. * @private
  20701. * Calculates the coefficients of a cubic function for the given coordinates.
  20702. * @param P0 {Number}
  20703. * @param P1 {Number}
  20704. * @param P2 {Number}
  20705. * @param P3 {Number}
  20706. * @return {Array}
  20707. */
  20708. bezierCoeffs: function(P0, P1, P2, P3) {
  20709. var Z = [];
  20710. Z[0] = -P0 + 3 * P1 - 3 * P2 + P3;
  20711. Z[1] = 3 * P0 - 6 * P1 + 3 * P2;
  20712. Z[2] = -3 * P0 + 3 * P1;
  20713. Z[3] = P0;
  20714. return Z;
  20715. },
  20716. /**
  20717. * @private
  20718. * Computes intersection points between a cubic spline and a line segment.
  20719. * Takes in x/y components of cubic control points and line segment start/end points
  20720. * as parameters.
  20721. * @param px1 {Number}
  20722. * @param px2 {Number}
  20723. * @param px3 {Number}
  20724. * @param px4 {Number}
  20725. * @param py1 {Number}
  20726. * @param py2 {Number}
  20727. * @param py3 {Number}
  20728. * @param py4 {Number}
  20729. * @param x1 {Number}
  20730. * @param y1 {Number}
  20731. * @param x2 {Number}
  20732. * @param y2 {Number}
  20733. * @return {Array} Array of intersection points, where each intersection point
  20734. * is itself a two-item array [x,y].
  20735. */
  20736. cubicLineIntersections: function(px1, px2, px3, px4, py1, py2, py3, py4, x1, y1, x2, y2) {
  20737. var P = [],
  20738. intersections = [],
  20739. // Finding line equation coefficients.
  20740. A = y1 - y2,
  20741. B = x2 - x1,
  20742. C = x1 * (y2 - y1) - y1 * (x2 - x1),
  20743. // Finding cubic Bezier curve equation coefficients.
  20744. bx = this.bezierCoeffs(px1, px2, px3, px4),
  20745. by = this.bezierCoeffs(py1, py2, py3, py4),
  20746. i, r, s, t, tt, ttt, cx, cy;
  20747. P[0] = A * bx[0] + B * by[0];
  20748. // t^3
  20749. P[1] = A * bx[1] + B * by[1];
  20750. // t^2
  20751. P[2] = A * bx[2] + B * by[2];
  20752. // t
  20753. P[3] = A * bx[3] + B * by[3] + C;
  20754. // 1
  20755. r = this.cubicRoots(P);
  20756. // Verify the roots are in bounds of the linear segment.
  20757. for (i = 0; i < r.length; i++) {
  20758. t = r[i];
  20759. if (t < 0 || t > 1) {
  20760. continue;
  20761. }
  20762. tt = t * t;
  20763. ttt = tt * t;
  20764. cx = bx[0] * ttt + bx[1] * tt + bx[2] * t + bx[3];
  20765. cy = by[0] * ttt + by[1] * tt + by[2] * t + by[3];
  20766. // Above is intersection point assuming infinitely long line segment,
  20767. // make sure we are also in bounds of the line.
  20768. if ((x2 - x1) !== 0) {
  20769. // If not vertical line
  20770. s = (cx - x1) / (x2 - x1);
  20771. } else {
  20772. s = (cy - y1) / (y2 - y1);
  20773. }
  20774. // In bounds?
  20775. if (!(s < 0 || s > 1)) {
  20776. intersections.push([
  20777. cx,
  20778. cy
  20779. ]);
  20780. }
  20781. }
  20782. return intersections;
  20783. },
  20784. /**
  20785. * @private
  20786. * Splits cubic Bezier curve into two cubic Bezier curves at point z,
  20787. * where z belongs to a range of [0, 1].
  20788. * Accepts cubic coefficients and point z as parameters.
  20789. * @param P1 {Number}
  20790. * @param P2 {Number}
  20791. * @param P3 {Number}
  20792. * @param P4 {Number}
  20793. * @param z Point to split the given curve at.
  20794. * @return {Array} Two-item array, where each item is itself an array
  20795. * of cubic coefficients.
  20796. */
  20797. splitCubic: function(P1, P2, P3, P4, z) {
  20798. var zz = z * z,
  20799. zzz = z * zz,
  20800. iz = z - 1,
  20801. izz = iz * iz,
  20802. izzz = iz * izz,
  20803. // Common point for both curves.
  20804. P = zzz * P4 - 3 * zz * iz * P3 + 3 * z * izz * P2 - izzz * P1;
  20805. return [
  20806. [
  20807. P1,
  20808. z * P2 - iz * P1,
  20809. zz * P3 - 2 * z * iz * P2 + izz * P1,
  20810. P
  20811. ],
  20812. [
  20813. P,
  20814. zz * P4 - 2 * z * iz * P3 + izz * P2,
  20815. z * P4 - iz * P3,
  20816. P4
  20817. ]
  20818. ];
  20819. },
  20820. /**
  20821. * @private
  20822. * Returns the dimension of a cubic Bezier curve in a single direction.
  20823. * @param a {Number}
  20824. * @param b {Number}
  20825. * @param c {Number}
  20826. * @param d {Number}
  20827. * @return {Array} Two-item array representing cubic's range in the given direction.
  20828. */
  20829. cubicDimension: function(a, b, c, d) {
  20830. var qa = 3 * (-a + 3 * (b - c) + d),
  20831. qb = 6 * (a - 2 * b + c),
  20832. qc = -3 * (a - b),
  20833. x, y,
  20834. min = Math.min(a, d),
  20835. max = Math.max(a, d),
  20836. delta;
  20837. if (qa === 0) {
  20838. if (qb === 0) {
  20839. return [
  20840. min,
  20841. max
  20842. ];
  20843. } else {
  20844. x = -qc / qb;
  20845. if (0 < x && x < 1) {
  20846. y = this.interpolateCubic(a, b, c, d, x);
  20847. min = Math.min(min, y);
  20848. max = Math.max(max, y);
  20849. }
  20850. }
  20851. } else {
  20852. delta = qb * qb - 4 * qa * qc;
  20853. if (delta >= 0) {
  20854. delta = sqrt(delta);
  20855. x = (delta - qb) / 2 / qa;
  20856. if (0 < x && x < 1) {
  20857. y = this.interpolateCubic(a, b, c, d, x);
  20858. min = Math.min(min, y);
  20859. max = Math.max(max, y);
  20860. }
  20861. if (delta > 0) {
  20862. x -= delta / qa;
  20863. if (0 < x && x < 1) {
  20864. y = this.interpolateCubic(a, b, c, d, x);
  20865. min = Math.min(min, y);
  20866. max = Math.max(max, y);
  20867. }
  20868. }
  20869. }
  20870. }
  20871. return [
  20872. min,
  20873. max
  20874. ];
  20875. },
  20876. /**
  20877. * @private
  20878. * Calculates a value of a cubic function at the given point t. In other words
  20879. * returns a * (1 - t) ^ 3 + 3 * b (1 - t) ^ 2 * t + 3 * c (1 - t) * t ^ 3 + d * t ^ 3
  20880. * for given a, b, c, d and t, where t belongs to an interval of [0, 1].
  20881. * @param a {Number}
  20882. * @param b {Number}
  20883. * @param c {Number}
  20884. * @param d {Number}
  20885. * @param t {Number}
  20886. * @return {Number}
  20887. */
  20888. interpolateCubic: function(a, b, c, d, t) {
  20889. if (t === 0) {
  20890. return a;
  20891. }
  20892. if (t === 1) {
  20893. return d;
  20894. }
  20895. var rate = (1 - t) / t;
  20896. return t * t * t * (d + rate * (3 * c + rate * (3 * b + rate * a)));
  20897. },
  20898. /**
  20899. * @private
  20900. * Computes intersection points between two cubic Bezier curve segments.
  20901. * Takes x/y components of control points for two Bezier curve segments.
  20902. * @param ax1 {Number}
  20903. * @param ax2 {Number}
  20904. * @param ax3 {Number}
  20905. * @param ax4 {Number}
  20906. * @param ay1 {Number}
  20907. * @param ay2 {Number}
  20908. * @param ay3 {Number}
  20909. * @param ay4 {Number}
  20910. * @param bx1 {Number}
  20911. * @param bx2 {Number}
  20912. * @param bx3 {Number}
  20913. * @param bx4 {Number}
  20914. * @param by1 {Number}
  20915. * @param by2 {Number}
  20916. * @param by3 {Number}
  20917. * @param by4 {Number}
  20918. * @return {Array} Array of intersection points, where each intersection point
  20919. * is itself a two-item array [x,y].
  20920. */
  20921. cubicsIntersections: function(ax1, ax2, ax3, ax4, ay1, ay2, ay3, ay4, bx1, bx2, bx3, bx4, by1, by2, by3, by4) {
  20922. var me = this,
  20923. axDim = me.cubicDimension(ax1, ax2, ax3, ax4),
  20924. ayDim = me.cubicDimension(ay1, ay2, ay3, ay4),
  20925. bxDim = me.cubicDimension(bx1, bx2, bx3, bx4),
  20926. byDim = me.cubicDimension(by1, by2, by3, by4),
  20927. splitAx, splitAy, splitBx, splitBy,
  20928. points = [];
  20929. // Curves' bounding boxes don't intersect.
  20930. if (axDim[0] > bxDim[1] || axDim[1] < bxDim[0] || ayDim[0] > byDim[1] || ayDim[1] < byDim[0]) {
  20931. return [];
  20932. }
  20933. // Both curves occupy sub-pixel areas which is effectively their intersection point.
  20934. 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) {
  20935. return [
  20936. [
  20937. (ax1 + ax4) * 0.5,
  20938. (ay1 + ay2) * 0.5
  20939. ]
  20940. ];
  20941. }
  20942. splitAx = me.splitCubic(ax1, ax2, ax3, ax4, 0.5);
  20943. splitAy = me.splitCubic(ay1, ay2, ay3, ay4, 0.5);
  20944. splitBx = me.splitCubic(bx1, bx2, bx3, bx4, 0.5);
  20945. splitBy = me.splitCubic(by1, by2, by3, by4, 0.5);
  20946. points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[0].concat(splitAy[0], splitBx[0], splitBy[0])));
  20947. points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[0].concat(splitAy[0], splitBx[1], splitBy[1])));
  20948. points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[1].concat(splitAy[1], splitBx[0], splitBy[0])));
  20949. points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[1].concat(splitAy[1], splitBx[1], splitBy[1])));
  20950. return points;
  20951. },
  20952. /**
  20953. * @private
  20954. * Returns the point [x,y] where two line segments intersect or null.
  20955. * Takes x/y components of the start and end point of the segments as parameters.
  20956. * Based on Paul Bourke's explanation:
  20957. * http://paulbourke.net/geometry/pointlineplane/
  20958. * @param x1 {Number}
  20959. * @param y1 {Number}
  20960. * @param x2 {Number}
  20961. * @param y2 {Number}
  20962. * @param x3 {Number}
  20963. * @param y3 {Number}
  20964. * @param x4 {Number}
  20965. * @param y4 {Number}
  20966. * @return {Number[]|null}
  20967. */
  20968. linesIntersection: function(x1, y1, x2, y2, x3, y3, x4, y4) {
  20969. var d = (x2 - x1) * (y4 - y3) - (y2 - y1) * (x4 - x3),
  20970. ua, ub;
  20971. if (d === 0) {
  20972. // Lines are parallel.
  20973. return null;
  20974. }
  20975. ua = ((x4 - x3) * (y1 - y3) - (x1 - x3) * (y4 - y3)) / d;
  20976. ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / d;
  20977. if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
  20978. return [
  20979. x1 + ua * (x2 - x1),
  20980. // x
  20981. y1 + ua * (y2 - y1)
  20982. ];
  20983. }
  20984. // y
  20985. return null;
  20986. },
  20987. // The intersection point is outside one or both segments.
  20988. /**
  20989. * @private
  20990. * Checks if a point belongs to a line segment.
  20991. * Takes x/y components of the start and end points of the segment and the point's
  20992. * coordinates as parameters.
  20993. * @param x1 {Number}
  20994. * @param y1 {Number}
  20995. * @param x2 {Number}
  20996. * @param y2 {Number}
  20997. * @param x {Number}
  20998. * @param y {Number}
  20999. * @return {Boolean}
  21000. */
  21001. pointOnLine: function(x1, y1, x2, y2, x, y) {
  21002. var t, _;
  21003. if (abs(x2 - x1) < abs(y2 - y1)) {
  21004. _ = x1;
  21005. x1 = y1;
  21006. y1 = _;
  21007. _ = x2;
  21008. x2 = y2;
  21009. y2 = _;
  21010. _ = x;
  21011. x = y;
  21012. y = _;
  21013. }
  21014. t = (x - x1) / (x2 - x1);
  21015. if (t < 0 || t > 1) {
  21016. return false;
  21017. }
  21018. return abs(y1 + t * (y2 - y1) - y) < 4;
  21019. },
  21020. /**
  21021. * @private
  21022. * Checks if a point belongs to a cubic Bezier curve segment.
  21023. * Takes x/y components of the control points of the segment and the point's
  21024. * coordinates as parameters.
  21025. * @param px1 {Number}
  21026. * @param px2 {Number}
  21027. * @param px3 {Number}
  21028. * @param px4 {Number}
  21029. * @param py1 {Number}
  21030. * @param py2 {Number}
  21031. * @param py3 {Number}
  21032. * @param py4 {Number}
  21033. * @param x {Number}
  21034. * @param y {Number}
  21035. * @return {Boolean}
  21036. */
  21037. pointOnCubic: function(px1, px2, px3, px4, py1, py2, py3, py4, x, y) {
  21038. // Finding cubic Bezier curve equation coefficients.
  21039. var me = this,
  21040. bx = me.bezierCoeffs(px1, px2, px3, px4),
  21041. by = me.bezierCoeffs(py1, py2, py3, py4),
  21042. i, j, rx, ry, t;
  21043. bx[3] -= x;
  21044. by[3] -= y;
  21045. rx = me.cubicRoots(bx);
  21046. ry = me.cubicRoots(by);
  21047. for (i = 0; i < rx.length; i++) {
  21048. t = rx[i];
  21049. for (j = 0; j < ry.length; j++) {
  21050. // TODO: for more accurate results tolerance should be dynamic
  21051. // TODO: based on the length and shape of the segment.
  21052. if (t >= 0 && t <= 1 && abs(t - ry[j]) < 0.05) {
  21053. return true;
  21054. }
  21055. }
  21056. }
  21057. return false;
  21058. }
  21059. };
  21060. });
  21061. Ext.define('Ext.draw.overrides.hittest.All', {
  21062. requires: [
  21063. 'Ext.draw.PathUtil',
  21064. 'Ext.draw.overrides.hittest.sprite.Instancing',
  21065. 'Ext.draw.overrides.hittest.Surface'
  21066. ]
  21067. });
  21068. /**
  21069. * This class uses `Ext.draw.sprite.Sprite` to render the chart legend.
  21070. *
  21071. * The DOM legend is essentially a data view docked inside a draw container, which a chart is.
  21072. * The sprite legend, on the other hand, is not a foreign entity in a draw container,
  21073. * and is rendered in a draw surface with sprites, just like series and axes.
  21074. *
  21075. * This means that:
  21076. *
  21077. * * it is styleable with chart themes
  21078. * * it shows up in chart preview and chart download
  21079. * * it renders markers exactly as they are in the series
  21080. * * it can't be styled with CSS
  21081. * * it doesn't scroll, instead the items are grouped into columns,
  21082. * and the legend grows in size as the number of items increases
  21083. *
  21084. */
  21085. Ext.define('Ext.chart.legend.SpriteLegend', {
  21086. alias: 'legend.sprite',
  21087. type: 'sprite',
  21088. isLegend: true,
  21089. isSpriteLegend: true,
  21090. mixins: [
  21091. 'Ext.mixin.Observable'
  21092. ],
  21093. requires: [
  21094. 'Ext.chart.legend.sprite.Item',
  21095. 'Ext.chart.legend.sprite.Border',
  21096. 'Ext.draw.overrides.hittest.All',
  21097. 'Ext.draw.Animator'
  21098. ],
  21099. config: {
  21100. /**
  21101. * @cfg {'top'/'left'/'right'/'bottom'} docked
  21102. * The position of the legend in the chart.
  21103. */
  21104. docked: 'bottom',
  21105. /**
  21106. * @cfg {Ext.chart.legend.store.Store} store
  21107. * The {@link Ext.chart.legend.store.Store} to bind this legend to.
  21108. * @private
  21109. */
  21110. store: null,
  21111. /**
  21112. * @cfg {Ext.chart.AbstractChart} chart
  21113. * The chart that the store belongs to.
  21114. */
  21115. chart: null,
  21116. /**
  21117. * @cfg {Ext.draw.Surface} surface
  21118. * The chart surface used to render legend sprites.
  21119. * @protected
  21120. */
  21121. surface: null,
  21122. /**
  21123. * @cfg {Object} size
  21124. * The size of the area occupied by the legend's sprites.
  21125. * This is set by the legend itself and then used during chart layout
  21126. * to make sure the 'legend' surface is big enough to accommodate
  21127. * legend sprites.
  21128. * @cfg {Number} size.width
  21129. * @cfg {Number} size.height
  21130. * @readonly
  21131. */
  21132. size: {
  21133. width: 0,
  21134. height: 0
  21135. },
  21136. /**
  21137. * @cfg {Boolean} toggleable
  21138. * `true` to allow series items to have their visibility
  21139. * toggled by interaction with the legend items.
  21140. */
  21141. toggleable: true,
  21142. /**
  21143. * @cfg {Number} padding
  21144. * The padding amount between legend items and legend border.
  21145. */
  21146. padding: 10,
  21147. label: {
  21148. preciseMeasurement: true
  21149. },
  21150. /**
  21151. * The sprite to use as a legend item marker. By default a corresponding series
  21152. * marker is used. If the series has no marker, the `circle` sprite
  21153. * is used as a legend item marker, where its `fillStyle`, `strokeStyle` and
  21154. * `lineWidth` match that of the series. The size of a legend item marker is
  21155. * controlled by the `size` property, which to defaults to `10` (pixels).
  21156. */
  21157. marker: {},
  21158. /**
  21159. * @cfg {Object} border
  21160. * The border that goes around legend item sprites.
  21161. * The type of the sprite is determined by this config,
  21162. * while the styling comes from a theme {@link Ext.chart.theme.Base #legend}.
  21163. * If both this config and the theme provide values for the
  21164. * same configs, the values from this config are used.
  21165. * The sprite class used a legend border should have the `isLegendBorder`
  21166. * property set to true on the prototype. The legend border sprite
  21167. * should also have the `x`, `y`, `width` and `height` attributes
  21168. * that determine it's position and dimensions.
  21169. */
  21170. border: {
  21171. $value: {
  21172. type: 'legendborder'
  21173. },
  21174. // The config should be processed at the time of the 'getSprites' call,
  21175. // when we already have the legend surface, otherwise the border sprite
  21176. // will not be added to the surface.
  21177. lazy: true
  21178. },
  21179. /**
  21180. * @cfg {Object} background
  21181. * Sets the legend background.
  21182. * This can be a gradient object, image, or color. This config works similarly
  21183. * to the {@link Ext.chart.AbstractChart#background} config.
  21184. */
  21185. background: null,
  21186. /**
  21187. * @cfg {Boolean} hidden Toggles the visibility of the legend.
  21188. */
  21189. hidden: false
  21190. },
  21191. sprites: null,
  21192. spriteZIndexes: {
  21193. background: 0,
  21194. border: 1,
  21195. // Item sprites should have a higher zIndex than border,
  21196. // or they won't react to clicks.
  21197. item: 2
  21198. },
  21199. dockedValues: {
  21200. left: true,
  21201. right: true,
  21202. top: true,
  21203. bottom: true
  21204. },
  21205. constructor: function(config) {
  21206. var me = this;
  21207. me.oldSize = {
  21208. width: 0,
  21209. height: 0
  21210. };
  21211. me.getId();
  21212. me.mixins.observable.constructor.call(me, config);
  21213. },
  21214. applyStore: function(store) {
  21215. return store && Ext.StoreManager.lookup(store);
  21216. },
  21217. updateStore: function(store, oldStore) {
  21218. var me = this;
  21219. if (oldStore) {
  21220. oldStore.un('datachanged', me.onDataChanged, me);
  21221. oldStore.un('update', me.onDataUpdate, me);
  21222. }
  21223. if (store) {
  21224. store.on('datachanged', me.onDataChanged, me);
  21225. store.on('update', me.onDataUpdate, me);
  21226. me.onDataChanged(store);
  21227. }
  21228. me.performLayout();
  21229. },
  21230. //<debug>
  21231. applyDocked: function(docked) {
  21232. if (!(docked in this.dockedValues)) {
  21233. Ext.raise("Invalid 'docked' config value.");
  21234. }
  21235. return docked;
  21236. },
  21237. //</debug>
  21238. updateDocked: function(docked) {
  21239. this.isTop = docked === 'top';
  21240. if (!this.isConfiguring) {
  21241. this.layoutChart();
  21242. }
  21243. },
  21244. updateHidden: function(hidden) {
  21245. this.getChart();
  21246. // 'chart' updater will set the surface
  21247. var surface = this.getSurface();
  21248. if (surface) {
  21249. surface.setHidden(hidden);
  21250. }
  21251. if (!this.isConfiguring) {
  21252. this.layoutChart();
  21253. }
  21254. },
  21255. /**
  21256. * @private
  21257. */
  21258. layoutChart: function() {
  21259. if (!this.isConfiguring) {
  21260. var chart = this.getChart();
  21261. if (chart) {
  21262. chart.scheduleLayout();
  21263. }
  21264. }
  21265. },
  21266. /**
  21267. * @private
  21268. * Calculates and returns the legend surface rect and adjusts the passed `chartRect`
  21269. * accordingly. The first time this is called, the `SpriteLegend` will have zero size
  21270. * (no width or height).
  21271. * @param {Number[]} chartRect [left, top, width, height] components as an array.
  21272. * @return {Number[]} [left, top, width, height] components as an array, or null.
  21273. */
  21274. computeRect: function(chartRect) {
  21275. if (this.getHidden()) {
  21276. return null;
  21277. }
  21278. var rect = [
  21279. 0,
  21280. 0,
  21281. 0,
  21282. 0
  21283. ],
  21284. docked = this.getDocked(),
  21285. size = this.getSize(),
  21286. height = size.height,
  21287. width = size.width;
  21288. switch (docked) {
  21289. case 'top':
  21290. rect[1] = chartRect[1];
  21291. rect[2] = chartRect[2];
  21292. rect[3] = height;
  21293. chartRect[1] += height;
  21294. chartRect[3] -= height;
  21295. break;
  21296. case 'bottom':
  21297. chartRect[3] -= height;
  21298. rect[1] = chartRect[3];
  21299. rect[2] = chartRect[2];
  21300. rect[3] = height;
  21301. break;
  21302. case 'left':
  21303. chartRect[0] += width;
  21304. chartRect[2] -= width;
  21305. rect[2] = width;
  21306. rect[3] = chartRect[3];
  21307. break;
  21308. case 'right':
  21309. chartRect[2] -= width;
  21310. rect[0] = chartRect[2];
  21311. rect[2] = width;
  21312. rect[3] = chartRect[3];
  21313. break;
  21314. }
  21315. return rect;
  21316. },
  21317. applyBorder: function(config) {
  21318. var border;
  21319. if (config) {
  21320. if (config.isSprite) {
  21321. border = config;
  21322. } else {
  21323. border = Ext.create('sprite.' + config.type, config);
  21324. }
  21325. }
  21326. if (border) {
  21327. border.isLegendBorder = true;
  21328. border.setAttributes({
  21329. zIndex: this.spriteZIndexes.border
  21330. });
  21331. }
  21332. return border;
  21333. },
  21334. updateBorder: function(border, oldBorder) {
  21335. var surface = this.getSurface();
  21336. this.borderSprite = null;
  21337. if (surface) {
  21338. if (oldBorder) {
  21339. surface.remove(oldBorder);
  21340. }
  21341. if (border) {
  21342. this.borderSprite = surface.add(border);
  21343. }
  21344. }
  21345. },
  21346. scheduleLayout: function() {
  21347. if (!this.scheduledLayoutId) {
  21348. this.scheduledLayoutId = Ext.draw.Animator.schedule('performLayout', this);
  21349. }
  21350. },
  21351. cancelLayout: function() {
  21352. Ext.draw.Animator.cancel(this.scheduledLayoutId);
  21353. this.scheduledLayoutId = null;
  21354. },
  21355. performLayout: function() {
  21356. var me = this,
  21357. size = me.getSize(),
  21358. gap = me.getPadding(),
  21359. sprites = me.getSprites(),
  21360. surface = me.getSurface(),
  21361. background = me.getBackground(),
  21362. surfaceRect = surface.getRect(),
  21363. store = me.getStore(),
  21364. ln = (sprites && sprites.length) || 0,
  21365. i, sprite;
  21366. if (!surface || !surfaceRect || !store) {
  21367. return false;
  21368. }
  21369. me.cancelLayout();
  21370. var docked = me.getDocked(),
  21371. surfaceWidth = surfaceRect[2],
  21372. surfaceHeight = surfaceRect[3],
  21373. border = me.borderSprite,
  21374. bboxes = [],
  21375. startX, // Coordinates of the top-left corner.
  21376. startY, // of the first 'legenditem' sprite.
  21377. columnSize, // Number of items in a column.
  21378. columnCount, // Number of columns.
  21379. columnWidth, itemsWidth, itemsHeight, paddedItemsWidth, // The horizontal span of all 'legenditem' sprites.
  21380. paddedItemsHeight, // The vertical span of all 'legenditem' sprites.
  21381. paddedBorderWidth, paddedBorderHeight, itemHeight, bbox, x, y;
  21382. for (i = 0; i < ln; i++) {
  21383. sprite = sprites[i];
  21384. bbox = sprite.getBBox();
  21385. bboxes.push(bbox);
  21386. }
  21387. if (bbox) {
  21388. itemHeight = bbox.height;
  21389. }
  21390. switch (docked) {
  21391. /*
  21392. Horizontal legend.
  21393. The outer box is the legend surface.
  21394. The inner box is the legend border.
  21395. There's a fixed amount of padding between all the items,
  21396. denoted by ##. This amount is controlled by the 'padding' config
  21397. of the legend.
  21398. |-------------------------------------------------------------|
  21399. | ## |
  21400. | |---------------------------------------------------| |
  21401. | | ## ## ## | |
  21402. | | -------- ----------- -------- | |
  21403. | ## | ## | Item 0 | ## | Item 2 | ## | Item 4 | ## | ## |
  21404. | | -------- ----------- -------- | |
  21405. | | ## ## ## | |
  21406. | | ---------- --------- | |
  21407. | | ## | Item 1 | ## | Item 3 | | |
  21408. | | ---------- --------- | |
  21409. | | ## ## | |
  21410. | |---------------------------------------------------| |
  21411. | ## |
  21412. |-------------------------------------------------------------|
  21413. */
  21414. case 'bottom':
  21415. case 'top':
  21416. // surface must have a width before we can proceed to layout top/bottom
  21417. // docked legend. width may be 0 if we are rendered into an inactive tab.
  21418. // see https://sencha.jira.com/browse/EXTJS-22454
  21419. if (!surfaceWidth) {
  21420. return false;
  21421. };
  21422. columnSize = 0;
  21423. // Split legend items into columns until the width is suitable.
  21424. do {
  21425. itemsWidth = 0;
  21426. columnWidth = 0;
  21427. columnCount = 0;
  21428. columnSize++;
  21429. for (i = 0; i < ln; i++) {
  21430. bbox = bboxes[i];
  21431. if (bbox.width > columnWidth) {
  21432. columnWidth = bbox.width;
  21433. }
  21434. if ((i + 1) % columnSize === 0) {
  21435. itemsWidth += columnWidth;
  21436. columnWidth = 0;
  21437. columnCount++;
  21438. }
  21439. }
  21440. if (i % columnSize !== 0) {
  21441. itemsWidth += columnWidth;
  21442. columnCount++;
  21443. }
  21444. paddedItemsWidth = itemsWidth + (columnCount - 1) * gap;
  21445. paddedBorderWidth = paddedItemsWidth + gap * 4;
  21446. } while (paddedBorderWidth > surfaceWidth);
  21447. paddedItemsHeight = itemHeight * columnSize + (columnSize - 1) * gap;
  21448. break;
  21449. /*
  21450. Vertical legend.
  21451. |-----------------------------------------------|
  21452. | ## |
  21453. | |-------------------------------------| |
  21454. | | ## ## | |
  21455. | | -------- ----------- | |
  21456. | | ## | Item 0 | ## | Item 1 | ## | |
  21457. | | -------- ----------- | |
  21458. | | ## ## | |
  21459. | | ---------- --------- | |
  21460. | ## | ## | Item 2 | ## | Item 3 | | ## |
  21461. | | ---------- --------- | |
  21462. | | ## | |
  21463. | | -------- | |
  21464. | | ## | Item 4 | | |
  21465. | | -------- | |
  21466. | | ## | |
  21467. | |-------------------------------------| |
  21468. | ## |
  21469. |-----------------------------------------------|
  21470. */
  21471. case 'right':
  21472. case 'left':
  21473. // surface must have a height before we can proceed to layout right/left
  21474. // docked legend. height may be 0 if we are rendered into an inactive tab.
  21475. // see https://sencha.jira.com/browse/EXTJS-22454
  21476. if (!surfaceHeight) {
  21477. return false;
  21478. };
  21479. columnSize = ln * 2;
  21480. // Split legend items into columns until the height is suitable.
  21481. do {
  21482. columnSize = (columnSize >> 1) + (columnSize % 2);
  21483. itemsWidth = 0;
  21484. itemsHeight = 0;
  21485. columnWidth = 0;
  21486. columnCount = 0;
  21487. for (i = 0; i < ln; i++) {
  21488. bbox = bboxes[i];
  21489. if (!columnCount) {
  21490. itemsHeight += bbox.height;
  21491. }
  21492. if (bbox.width > columnWidth) {
  21493. columnWidth = bbox.width;
  21494. }
  21495. if ((i + 1) % columnSize === 0) {
  21496. itemsWidth += columnWidth;
  21497. columnWidth = 0;
  21498. columnCount++;
  21499. }
  21500. }
  21501. if (i % columnSize !== 0) {
  21502. itemsWidth += columnWidth;
  21503. columnCount++;
  21504. }
  21505. paddedItemsWidth = itemsWidth + (columnCount - 1) * gap;
  21506. paddedItemsHeight = itemsHeight + (columnSize - 1) * gap;
  21507. paddedBorderWidth = paddedItemsWidth + gap * 4;
  21508. paddedBorderHeight = paddedItemsHeight + gap * 4;
  21509. } while (// Integer division by 2, plus remainder.
  21510. // itemsHeight is determined by the height of the first column.
  21511. paddedItemsHeight > surfaceHeight);
  21512. break;
  21513. }
  21514. startX = (surfaceWidth - paddedItemsWidth) / 2;
  21515. startY = (surfaceHeight - paddedItemsHeight) / 2;
  21516. x = 0;
  21517. y = 0;
  21518. columnWidth = 0;
  21519. for (i = 0; i < ln; i++) {
  21520. sprite = sprites[i];
  21521. bbox = bboxes[i];
  21522. sprite.setAttributes({
  21523. translationX: startX + x,
  21524. translationY: startY + y
  21525. });
  21526. if (bbox.width > columnWidth) {
  21527. columnWidth = bbox.width;
  21528. }
  21529. if ((i + 1) % columnSize === 0) {
  21530. x += columnWidth + gap;
  21531. y = 0;
  21532. columnWidth = 0;
  21533. } else {
  21534. y += bbox.height + gap;
  21535. }
  21536. }
  21537. if (border) {
  21538. border.setAttributes({
  21539. hidden: !ln,
  21540. x: startX - gap,
  21541. y: startY - gap,
  21542. width: paddedItemsWidth + gap * 2,
  21543. height: paddedItemsHeight + gap * 2
  21544. });
  21545. }
  21546. size.width = border.attr.width + gap * 2;
  21547. size.height = border.attr.height + gap * 2;
  21548. if (size.width !== me.oldSize.width || size.height !== me.oldSize.height) {
  21549. // Do not simply assign size to oldSize, as we want them to be
  21550. // separate objects.
  21551. Ext.apply(me.oldSize, size);
  21552. // Legend size has changed, so we return 'false' to cancel the current
  21553. // chart layout (this method is called by chart's 'performLayout' method)
  21554. // and manually start a new chart layout.
  21555. me.getChart().scheduleLayout();
  21556. return false;
  21557. }
  21558. if (background) {
  21559. me.resizeBackground(surface, background);
  21560. }
  21561. surface.renderFrame();
  21562. return true;
  21563. },
  21564. // Doesn't include the border sprite which also belongs to the 'legend'
  21565. // surface. To get it, use the 'getBorder' method.
  21566. getSprites: function() {
  21567. this.updateSprites();
  21568. return this.sprites;
  21569. },
  21570. /**
  21571. * @private
  21572. * Creates a 'legenditem' sprite in the given surface
  21573. * using the legend store record data provided.
  21574. * @param {Ext.draw.Surface} surface
  21575. * @param {Ext.chart.legend.store.Item} record
  21576. * @return {Ext.chart.legend.sprite.Item}
  21577. */
  21578. createSprite: function(surface, record) {
  21579. var me = this,
  21580. data = record.data,
  21581. chart = me.getChart(),
  21582. series = chart.get(data.series),
  21583. seriesMarker = series.getMarker(),
  21584. sprite = null,
  21585. markerConfig, labelConfig, legendItemConfig;
  21586. if (surface) {
  21587. markerConfig = series.getMarkerStyleByIndex(data.index);
  21588. markerConfig.fillStyle = data.mark;
  21589. markerConfig.hidden = false;
  21590. if (seriesMarker && seriesMarker.type) {
  21591. markerConfig.type = seriesMarker.type;
  21592. }
  21593. Ext.apply(markerConfig, me.getMarker());
  21594. markerConfig.surface = surface;
  21595. labelConfig = me.getLabel();
  21596. legendItemConfig = {
  21597. type: 'legenditem',
  21598. zIndex: me.spriteZIndexes.item,
  21599. text: data.name,
  21600. enabled: !data.disabled,
  21601. marker: markerConfig,
  21602. label: labelConfig,
  21603. series: data.series,
  21604. record: record
  21605. };
  21606. sprite = surface.add(legendItemConfig);
  21607. }
  21608. return sprite;
  21609. },
  21610. /**
  21611. * @private
  21612. * Creates legend item sprites and associates them with legend store records.
  21613. * Updates attributes of the sprites when legend store data changes.
  21614. */
  21615. updateSprites: function() {
  21616. var me = this,
  21617. chart = me.getChart(),
  21618. store = me.getStore(),
  21619. surface = me.getSurface(),
  21620. item, items, itemSprite, i, ln, sprites, unusedSprites, border;
  21621. if (!(chart && store && surface)) {
  21622. return;
  21623. }
  21624. me.sprites = sprites = me.sprites || [];
  21625. items = store.getData().items;
  21626. ln = items.length;
  21627. for (i = 0; i < ln; i++) {
  21628. item = items[i];
  21629. itemSprite = sprites[i];
  21630. if (itemSprite) {
  21631. me.updateSprite(itemSprite, item);
  21632. } else {
  21633. itemSprite = me.createSprite(surface, item);
  21634. surface.add(itemSprite);
  21635. sprites.push(itemSprite);
  21636. }
  21637. }
  21638. unusedSprites = Ext.Array.splice(sprites, i, sprites.length);
  21639. for (i = 0 , ln = unusedSprites.length; i < ln; i++) {
  21640. itemSprite = unusedSprites[i];
  21641. itemSprite.destroy();
  21642. }
  21643. border = me.getBorder();
  21644. if (border) {
  21645. me.borderSprite = border;
  21646. }
  21647. me.updateTheme(chart.getTheme());
  21648. },
  21649. /**
  21650. * @private
  21651. * Updates the given legend item sprite based on store record data.
  21652. * @param {Ext.chart.legend.sprite.Item} sprite
  21653. * @param {Ext.chart.legend.store.Item} record
  21654. */
  21655. updateSprite: function(sprite, record) {
  21656. var data = record.data,
  21657. chart = this.getChart(),
  21658. series = chart.get(data.series),
  21659. marker, label, markerConfig;
  21660. if (sprite) {
  21661. label = sprite.getLabel();
  21662. label.setAttributes({
  21663. text: data.name
  21664. });
  21665. sprite.setAttributes({
  21666. enabled: !data.disabled
  21667. });
  21668. sprite.setConfig({
  21669. series: data.series,
  21670. record: record
  21671. });
  21672. markerConfig = series.getMarkerStyleByIndex(data.index);
  21673. markerConfig.fillStyle = data.mark;
  21674. markerConfig.hidden = false;
  21675. Ext.apply(markerConfig, this.getMarker());
  21676. marker = sprite.getMarker();
  21677. marker.setAttributes({
  21678. fillStyle: markerConfig.fillStyle,
  21679. strokeStyle: markerConfig.strokeStyle
  21680. });
  21681. sprite.layoutUpdater(sprite.attr);
  21682. }
  21683. },
  21684. updateChart: function(newChart, oldChart) {
  21685. var me = this;
  21686. if (oldChart) {
  21687. me.setSurface(null);
  21688. }
  21689. if (newChart) {
  21690. me.setSurface(newChart.getSurface('legend'));
  21691. }
  21692. },
  21693. updateSurface: function(surface, oldSurface) {
  21694. if (oldSurface) {
  21695. oldSurface.el.un('click', 'onClick', this);
  21696. // The surface should not be destroyed here, just cleared.
  21697. // E.g. we may remove the sprite legend only to add another one.
  21698. oldSurface.removeAll(true);
  21699. }
  21700. if (surface) {
  21701. surface.isLegendSurface = true;
  21702. surface.el.on('click', 'onClick', this);
  21703. }
  21704. },
  21705. onClick: function(event) {
  21706. var chart = this.getChart(),
  21707. surface = this.getSurface(),
  21708. result, point;
  21709. if (chart && chart.hasFirstLayout && surface) {
  21710. point = surface.getEventXY(event);
  21711. result = surface.hitTest(point);
  21712. if (result && result.sprite) {
  21713. this.toggleItem(result.sprite);
  21714. }
  21715. }
  21716. },
  21717. applyBackground: function(newBackground, oldBackground) {
  21718. var me = this,
  21719. // It's important to get the `chart` first here,
  21720. // because the `surface` is set by the `chart` updater.
  21721. chart = me.getChart(),
  21722. surface = me.getSurface(),
  21723. background;
  21724. background = chart.refreshBackground(surface, newBackground, oldBackground);
  21725. if (background) {
  21726. background.setAttributes({
  21727. zIndex: me.spriteZIndexes.background
  21728. });
  21729. }
  21730. return background;
  21731. },
  21732. resizeBackground: function(surface, background) {
  21733. var width = background.attr.width,
  21734. height = background.attr.height,
  21735. surfaceRect = surface.getRect();
  21736. if (surfaceRect && (width !== surfaceRect[2] || height !== surfaceRect[3])) {
  21737. background.setAttributes({
  21738. width: surfaceRect[2],
  21739. height: surfaceRect[3]
  21740. });
  21741. }
  21742. },
  21743. themeableConfigs: {
  21744. background: true
  21745. },
  21746. updateTheme: function(theme) {
  21747. var me = this,
  21748. surface = me.getSurface(),
  21749. sprites = surface.getItems(),
  21750. legendTheme = theme.getLegend(),
  21751. labelConfig = me.getLabel(),
  21752. configs = me.self.getConfigurator().configs,
  21753. themeableConfigs = me.themeableConfigs,
  21754. initialConfig = me.getInitialConfig(),
  21755. defaultConfig = me.defaultConfig,
  21756. value, cfg, isObjValue, isUnusedConfig, initialValue, sprite, style, labelSprite, key, attr, i, ln;
  21757. for (i = 0 , ln = sprites.length; i < ln; i++) {
  21758. sprite = sprites[i];
  21759. if (sprite.isLegendItem) {
  21760. style = legendTheme.label;
  21761. if (style) {
  21762. attr = null;
  21763. for (key in style) {
  21764. if (!(key in labelConfig)) {
  21765. attr = attr || {};
  21766. attr[key] = style[key];
  21767. }
  21768. }
  21769. if (attr) {
  21770. labelSprite = sprite.getLabel();
  21771. labelSprite.setAttributes(attr);
  21772. }
  21773. }
  21774. continue;
  21775. } else if (sprite.isLegendBorder) {
  21776. style = legendTheme.border;
  21777. } else {
  21778. continue;
  21779. }
  21780. if (style) {
  21781. attr = {};
  21782. for (key in style) {
  21783. if (!(key in sprite.config)) {
  21784. attr[key] = style[key];
  21785. }
  21786. }
  21787. sprite.setAttributes(attr);
  21788. }
  21789. }
  21790. value = legendTheme.background;
  21791. cfg = configs.background;
  21792. if (value !== null && value !== undefined && cfg) {}
  21793. for (key in legendTheme) {
  21794. if (!(key in themeableConfigs)) {
  21795. continue;
  21796. }
  21797. value = legendTheme[key];
  21798. cfg = configs[key];
  21799. if (value !== null && value !== undefined && cfg) {
  21800. initialValue = initialConfig[key];
  21801. isObjValue = Ext.isObject(value);
  21802. isUnusedConfig = initialValue === defaultConfig[key];
  21803. if (isObjValue) {
  21804. if (isUnusedConfig && themeOnlyIfConfigured[key]) {
  21805. continue;
  21806. }
  21807. value = Ext.merge({}, value, initialValue);
  21808. }
  21809. if (isUnusedConfig || isObjValue) {
  21810. me[cfg.names.set](value);
  21811. }
  21812. }
  21813. }
  21814. },
  21815. onDataChanged: function(store) {
  21816. this.updateSprites();
  21817. this.scheduleLayout();
  21818. },
  21819. onDataUpdate: function(store, record) {
  21820. var me = this,
  21821. sprites = me.sprites,
  21822. ln = sprites.length,
  21823. i = 0,
  21824. sprite, spriteRecord, match;
  21825. for (; i < ln; i++) {
  21826. sprite = sprites[i];
  21827. spriteRecord = sprite.getRecord();
  21828. if (spriteRecord === record) {
  21829. match = sprite;
  21830. break;
  21831. }
  21832. }
  21833. if (match) {
  21834. me.updateSprite(match, record);
  21835. me.scheduleLayout();
  21836. }
  21837. },
  21838. toggleItem: function(sprite) {
  21839. if (!this.getToggleable() || !sprite.isLegendItem) {
  21840. return;
  21841. }
  21842. var store = this.getStore(),
  21843. disabledCount = 0,
  21844. canToggle = true,
  21845. i, count, record, disabled;
  21846. if (store) {
  21847. count = store.getCount();
  21848. for (i = 0; i < count; i++) {
  21849. record = store.getAt(i);
  21850. if (record.get('disabled')) {
  21851. disabledCount++;
  21852. }
  21853. }
  21854. canToggle = count - disabledCount > 1;
  21855. record = sprite.getRecord();
  21856. if (record) {
  21857. disabled = record.get('disabled');
  21858. if (disabled || canToggle) {
  21859. // This will trigger AbstractChart.onLegendStoreUpdate.
  21860. record.set('disabled', !disabled);
  21861. sprite.setAttributes({
  21862. enabled: disabled
  21863. });
  21864. }
  21865. }
  21866. }
  21867. },
  21868. destroy: function() {
  21869. var me = this;
  21870. me.destroying = true;
  21871. me.cancelLayout();
  21872. me.setChart(null);
  21873. me.callParent();
  21874. }
  21875. });
  21876. /**
  21877. * Chart captions can be used to place titles, subtitles, credits and other captions
  21878. * inside a chart. Please see the chart's {@link Ext.chart.AbstractChart#captions}
  21879. * config documentation for the general description of the way captions work, and
  21880. * refer to the documentation of this class' configs for details.
  21881. */
  21882. Ext.define('Ext.chart.Caption', {
  21883. mixins: [
  21884. 'Ext.mixin.Observable',
  21885. 'Ext.mixin.Bindable'
  21886. ],
  21887. isCaption: true,
  21888. config: {
  21889. /**
  21890. * The weight controls the order in which the captions are created.
  21891. * Captions with lower weights are created first.
  21892. * This affects chart's layout. For example, if two captions are docked
  21893. * to the 'top', the one with the lower weight will end up on top
  21894. * of the other.
  21895. */
  21896. weight: 0,
  21897. /**
  21898. * @cfg {String} text
  21899. * The text displayed by the caption.
  21900. * Multi-line captions are allowed, e.g.:
  21901. *
  21902. * captions: {
  21903. * title: {
  21904. * text: 'India\'s tiger population\n'
  21905. * + 'from 1970 to 2015'
  21906. * }
  21907. * }
  21908. *
  21909. */
  21910. text: '',
  21911. /**
  21912. * @cfg {'left'/'center'/'right'} [align='center']
  21913. * Determines the horizontal alignment of the caption's text.
  21914. */
  21915. align: 'center',
  21916. /**
  21917. * @cfg {'series'/'chart'} [alignTo='series']
  21918. * Whether to align the caption to the 'series' (default) or the 'chart'.
  21919. */
  21920. alignTo: 'series',
  21921. /**
  21922. * @cfg {Number} padding
  21923. * The uniform padding applied to both top and bottom of the caption's text.
  21924. */
  21925. padding: 0,
  21926. /**
  21927. * @cfg {Boolean} [hidden=false]
  21928. * Controls the visibility of the caption.
  21929. */
  21930. hidden: false,
  21931. /**
  21932. * @cfg {'top'/'bottom'} [docked='top']
  21933. * The position of the caption in a chart.
  21934. */
  21935. docked: 'top',
  21936. /**
  21937. * @cfg {Object} style
  21938. * Style attributes for the caption's text.
  21939. * All attributes that are valid {@link Ext.draw.sprite.Text text sprite} attributes
  21940. * are valid here. However, only font attributes (such as `fontSize`, `fontFamily`, ...),
  21941. * color attributes (such as `fillStyle`) and `textAlign` attribute are guaranteed to
  21942. * produce correct behavior. For example, transform attributes are not officially supported.
  21943. */
  21944. style: {
  21945. fontSize: '14px',
  21946. fontWeight: 'bold',
  21947. fontFamily: 'Verdana, Aria, sans-serif'
  21948. },
  21949. /**
  21950. * @private
  21951. * @cfg {Ext.chart.AbstractChart} chart
  21952. * The chart the label belongs to.
  21953. */
  21954. chart: null,
  21955. /**
  21956. * @private
  21957. * The text sprite used to render caption's text.
  21958. */
  21959. sprite: {
  21960. type: 'text',
  21961. preciseMeasurement: true,
  21962. zIndex: 10
  21963. },
  21964. //<debug>
  21965. /**
  21966. * @private
  21967. * @cfg {Boolean} debug
  21968. * Whether to show the bounding boxes or not.
  21969. */
  21970. debug: false,
  21971. //</debug>
  21972. /**
  21973. * @private
  21974. * The logical rect of the caption in the `surfaceName` surface.
  21975. */
  21976. rect: null
  21977. },
  21978. surfaceName: 'caption',
  21979. constructor: function(config) {
  21980. var me = this,
  21981. id;
  21982. if ('id' in config) {
  21983. id = config.id;
  21984. } else if ('id' in me.config) {
  21985. id = me.config.id;
  21986. } else {
  21987. id = me.getId();
  21988. }
  21989. me.setId(id);
  21990. me.mixins.observable.constructor.call(me, config);
  21991. me.initBindable();
  21992. },
  21993. updateChart: function() {
  21994. if (!this.isConfiguring) {
  21995. // Re-create caption's sprite in another chart.
  21996. this.setSprite({
  21997. type: 'text'
  21998. });
  21999. }
  22000. },
  22001. applySprite: function(sprite) {
  22002. var me = this,
  22003. chart = me.getChart(),
  22004. surface = me.surface = chart.getSurface(me.surfaceName);
  22005. //<debug>
  22006. me.rectSprite = surface.add({
  22007. type: 'rect',
  22008. fillStyle: 'yellow',
  22009. strokeStyle: 'red'
  22010. });
  22011. //</debug>
  22012. return sprite && surface.add(sprite);
  22013. },
  22014. updateSprite: function(sprite, oldSprite) {
  22015. if (oldSprite) {
  22016. oldSprite.destroy();
  22017. }
  22018. },
  22019. updateText: function(text) {
  22020. this.getSprite().setAttributes({
  22021. text: text
  22022. });
  22023. },
  22024. updateStyle: function(style) {
  22025. this.getSprite().setAttributes(style);
  22026. },
  22027. //<debug>
  22028. updateDebug: function(debug) {
  22029. var me = this,
  22030. sprite = me.getSprite();
  22031. if (debug && !me.rectSprite) {
  22032. me.rectSprite = me.surface.add({
  22033. type: 'rect',
  22034. fillStyle: 'yellow',
  22035. strokeStyle: 'red'
  22036. });
  22037. }
  22038. if (sprite) {
  22039. sprite.setAttributes({
  22040. debug: debug ? {
  22041. bbox: true
  22042. } : null
  22043. });
  22044. }
  22045. if (me.rectSprite) {
  22046. me.rectSprite.setAttributes({
  22047. hidden: !debug
  22048. });
  22049. }
  22050. if (!me.isConfiguring) {
  22051. me.surface.renderFrame();
  22052. }
  22053. },
  22054. //</debug>
  22055. updateRect: function(rect) {
  22056. if (this.rectSprite) {
  22057. this.rectSprite.setAttributes({
  22058. x: rect[0],
  22059. y: rect[1],
  22060. width: rect[2],
  22061. height: rect[3]
  22062. });
  22063. }
  22064. },
  22065. updateDocked: function() {
  22066. var chart = this.getChart();
  22067. if (chart && !this.isConfiguring) {
  22068. chart.scheduleLayout();
  22069. }
  22070. },
  22071. /**
  22072. * @private
  22073. * Computes and sets the caption's rect.
  22074. * Shrinks the given chart rect to accomodate the caption.
  22075. * The chart rect is [top, left, width, height] in chart's
  22076. * body element coordinates.
  22077. * The shrink rect is {left, top, right, bottom} in `caption`
  22078. * surface coordinates.
  22079. */
  22080. computeRect: function(chartRect, shrinkRect) {
  22081. if (this.getHidden()) {
  22082. return null;
  22083. }
  22084. var rect = [
  22085. 0,
  22086. 0,
  22087. chartRect[2],
  22088. 0
  22089. ],
  22090. docked = this.getDocked(),
  22091. padding = this.getPadding(),
  22092. textSize = this.getSprite().getBBox(),
  22093. height = textSize.height + padding * 2;
  22094. switch (docked) {
  22095. case 'top':
  22096. rect[1] = shrinkRect.top;
  22097. rect[3] = height;
  22098. chartRect[1] += height;
  22099. chartRect[3] -= height;
  22100. shrinkRect.top += height;
  22101. break;
  22102. case 'bottom':
  22103. chartRect[3] -= height;
  22104. shrinkRect.bottom -= height;
  22105. rect[1] = shrinkRect.bottom;
  22106. rect[3] = height;
  22107. break;
  22108. }
  22109. this.setRect(rect);
  22110. },
  22111. alignRect: function(seriesRect) {
  22112. var surfaceRect = this.surface.getRect(),
  22113. rect = this.getRect();
  22114. rect[0] = seriesRect[0] - surfaceRect[0];
  22115. rect[2] = seriesRect[2];
  22116. // Slice to trigger the applier/updater.
  22117. this.setRect(rect.slice());
  22118. },
  22119. performLayout: function() {
  22120. var me = this,
  22121. rect = me.getRect(),
  22122. x = rect[0],
  22123. y = rect[1],
  22124. width = rect[2],
  22125. height = rect[3],
  22126. sprite = me.getSprite(),
  22127. tx = sprite.attr.translationX,
  22128. ty = sprite.attr.translationY,
  22129. bbox = sprite.getBBox(),
  22130. align = me.getAlign(),
  22131. dx, dy;
  22132. switch (align) {
  22133. case 'left':
  22134. dx = x - bbox.x;
  22135. break;
  22136. case 'right':
  22137. dx = (x + width) - (bbox.x + bbox.width);
  22138. break;
  22139. case 'center':
  22140. dx = x + (width - bbox.width) / 2 - bbox.x;
  22141. break;
  22142. }
  22143. dy = y + (height - bbox.height) / 2 - bbox.y;
  22144. sprite.setAttributes({
  22145. translationX: tx + dx,
  22146. translationY: ty + dy
  22147. });
  22148. },
  22149. destroy: function() {
  22150. var me = this;
  22151. //<debug>
  22152. if (me.rectSprite) {
  22153. me.rectSprite.destroy();
  22154. }
  22155. //</debug>
  22156. me.getSprite().destroy();
  22157. me.callParent();
  22158. }
  22159. });
  22160. /**
  22161. * The data model for legend items.
  22162. */
  22163. Ext.define('Ext.chart.legend.store.Item', {
  22164. extend: 'Ext.data.Model',
  22165. fields: [
  22166. 'id',
  22167. 'name',
  22168. // The series title.
  22169. 'mark',
  22170. // The color of the series.
  22171. 'disabled',
  22172. // The state of the series.
  22173. 'series',
  22174. // A reference to the series instance.
  22175. 'index'
  22176. ]
  22177. });
  22178. // A sprite index, e.g. for stacked or pie series.
  22179. // For such series an individual component of the series
  22180. // is hidden or shown when the legend item is toggled.
  22181. /**
  22182. * The store type used for legend items.
  22183. */
  22184. Ext.define('Ext.chart.legend.store.Store', {
  22185. extend: 'Ext.data.Store',
  22186. requires: [
  22187. 'Ext.chart.legend.store.Item'
  22188. ],
  22189. model: 'Ext.chart.legend.store.Item',
  22190. isLegendStore: true,
  22191. config: {
  22192. autoDestroy: true
  22193. }
  22194. });
  22195. /**
  22196. * The Ext.chart package provides the capability to visualize data.
  22197. * Each chart binds directly to a {@link Ext.data.Store store} enabling automatic
  22198. * updates of the chart. A chart configuration object has some overall styling
  22199. * options as well as an array of axes and series. A chart instance example could
  22200. * look like this:
  22201. *
  22202. * Ext.create('Ext.chart.CartesianChart', {
  22203. * width: 800,
  22204. * height: 600,
  22205. * animation: {
  22206. * easing: 'backOut',
  22207. * duration: 500
  22208. * },
  22209. * store: store1,
  22210. * legend: {
  22211. * position: 'right'
  22212. * },
  22213. * axes: [
  22214. * // ...some axes options...
  22215. * ],
  22216. * series: [
  22217. * // ...some series options...
  22218. * ]
  22219. * });
  22220. *
  22221. * In this example we set the `width` and `height` of a chart; We decide whether
  22222. * our series are animated or not and we select a store to be bound to the chart;
  22223. * We also set the legend to the right part of the chart.
  22224. *
  22225. * You can register certain interactions such as {@link Ext.chart.interactions.PanZoom}
  22226. * on the chart by specifying an array of names or more specific config objects.
  22227. * All the events will be wired automatically.
  22228. *
  22229. * You can also listen to series `itemXXX` events on both chart and series level.
  22230. *
  22231. * For example:
  22232. *
  22233. * Ext.create('Ext.chart.CartesianChart', {
  22234. * plugins: {
  22235. * chartitemevents: {
  22236. * moveEvents: true
  22237. * }
  22238. * },
  22239. * store: {
  22240. * fields: ['pet', 'households', 'total'],
  22241. * data: [
  22242. * {pet: 'Cats', households: 38, total: 93},
  22243. * {pet: 'Dogs', households: 45, total: 79},
  22244. * {pet: 'Fish', households: 13, total: 171}
  22245. * ]
  22246. * },
  22247. * axes: [{
  22248. * type: 'numeric',
  22249. * position: 'left'
  22250. * }, {
  22251. * type: 'category',
  22252. * position: 'bottom'
  22253. * }],
  22254. * series: [{
  22255. * type: 'bar',
  22256. * xField: 'pet',
  22257. * yField: 'households',
  22258. * listeners: {
  22259. * itemmousemove: function (series, item, event) {
  22260. * console.log('itemmousemove', item.category, item.field);
  22261. * }
  22262. * }
  22263. * }, {
  22264. * type: 'line',
  22265. * xField: 'pet',
  22266. * yField: 'total',
  22267. * marker: true
  22268. * }],
  22269. * listeners: { // Listen to itemclick events on all series.
  22270. * itemclick: function (chart, item, event) {
  22271. * console.log('itemclick', item.category, item.field);
  22272. * }
  22273. * }
  22274. * });
  22275. *
  22276. * Important! It's generally a poor design choice to put interactive charts
  22277. * inside scrollable views, in such cases it's not possible to tell
  22278. * which component should respond to the interaction.
  22279. * Since charts are typically interactive their default touch action config
  22280. * looks as follows: {@link Ext.draw.Container#touchAction}.
  22281. * If you do have a chart inside a scrollable view, even if it has no interactions,
  22282. * you have to set its `touchAction` config to the following:
  22283. *
  22284. * touchAction: {
  22285. * panX: true,
  22286. * panY: true
  22287. * }
  22288. *
  22289. * Otherwise, if a touch action started on a chart, a swipe will not scroll
  22290. * the view.
  22291. *
  22292. * For more information about the axes and series configurations please check
  22293. * the documentation of each series (Line, Bar, Pie, etc).
  22294. *
  22295. */
  22296. Ext.define('Ext.chart.AbstractChart', {
  22297. extend: 'Ext.draw.Container',
  22298. requires: [
  22299. 'Ext.chart.theme.Default',
  22300. 'Ext.chart.series.Series',
  22301. 'Ext.chart.interactions.Abstract',
  22302. 'Ext.chart.axis.Axis',
  22303. 'Ext.chart.Util',
  22304. 'Ext.data.StoreManager',
  22305. 'Ext.chart.legend.Legend',
  22306. 'Ext.chart.legend.SpriteLegend',
  22307. 'Ext.chart.Caption',
  22308. 'Ext.chart.legend.store.Store',
  22309. 'Ext.data.Store'
  22310. ],
  22311. isChart: true,
  22312. defaultBindProperty: 'store',
  22313. /**
  22314. * @event beforerefresh
  22315. * Fires before a refresh to the chart data is called. If the `beforerefresh`
  22316. * handler returns `false` the {@link #refresh} action will be canceled.
  22317. * @param {Ext.chart.AbstractChart} this
  22318. */
  22319. /**
  22320. * @event refresh
  22321. * Fires after the chart data has been refreshed.
  22322. * @param {Ext.chart.AbstractChart} this
  22323. */
  22324. /**
  22325. * @event redraw
  22326. * Fires after each {@link #event!redraw} call.
  22327. * @param {Ext.chart.AbstractChart} this
  22328. */
  22329. /**
  22330. * @private
  22331. * @event layout
  22332. * Fires after the final layout is done.
  22333. * (Two layouts may be required to fully render a chart.
  22334. * Typically for the initial render and every time thickness
  22335. * of the chart's axes changes.)
  22336. * @param {Ext.chart.AbstractChart} this
  22337. */
  22338. /**
  22339. * @event itemhighlight
  22340. * Fires when an item is highlighted.
  22341. * @param {Ext.chart.AbstractChart} this
  22342. * @param {Object} newItem The new highlight item.
  22343. * @param {Object} oldItem The old highlight item.
  22344. */
  22345. /**
  22346. * @event itemhighlightchange
  22347. * Fires when an item's highlight changes.
  22348. * @param this
  22349. * @param {Object} newItem The new highlight item.
  22350. * @param {Object} oldItem The old highlight item.
  22351. */
  22352. /**
  22353. * @event itemmousemove
  22354. * Fires when the mouse is moved on a series item.
  22355. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22356. * plugin be added to the chart.
  22357. * @param {Ext.chart.AbstractChart} chart
  22358. * @param {Object} item
  22359. * @param {Event} event
  22360. */
  22361. /**
  22362. * @event itemmouseup
  22363. * Fires when a mouseup event occurs on a series item.
  22364. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22365. * plugin be added to the chart.
  22366. * @param {Ext.chart.AbstractChart} chart
  22367. * @param {Object} item
  22368. * @param {Event} event
  22369. */
  22370. /**
  22371. * @event itemmousedown
  22372. * Fires when a mousedown event occurs on a series item.
  22373. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22374. * plugin be added to the chart.
  22375. * @param {Ext.chart.AbstractChart} chart
  22376. * @param {Object} item
  22377. * @param {Event} event
  22378. */
  22379. /**
  22380. * @event itemmouseover
  22381. * Fires when the mouse enters a series item.
  22382. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22383. * plugin be added to the chart.
  22384. * @param {Ext.chart.AbstractChart} chart
  22385. * @param {Object} item
  22386. * @param {Event} event
  22387. */
  22388. /**
  22389. * @event itemmouseout
  22390. * Fires when the mouse exits a series item.
  22391. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22392. * plugin be added to the chart.
  22393. * @param {Ext.chart.AbstractChart} chart
  22394. * @param {Object} item
  22395. * @param {Event} event
  22396. */
  22397. /**
  22398. * @event itemclick
  22399. * Fires when a click event occurs on a series item.
  22400. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22401. * plugin be added to the chart.
  22402. * @param {Ext.chart.AbstractChart} chart
  22403. * @param {Object} item
  22404. * @param {Event} event
  22405. */
  22406. /**
  22407. * @event itemdblclick
  22408. * Fires when a double click event occurs on a series item.
  22409. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22410. * plugin be added to the chart.
  22411. * @param {Ext.chart.AbstractChart} chart
  22412. * @param {Object} item
  22413. * @param {Event} event
  22414. */
  22415. /**
  22416. * @event itemtap
  22417. * Fires when a tap event occurs on a series item.
  22418. * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
  22419. * plugin be added to the chart.
  22420. * @param {Ext.chart.AbstractChart} chart
  22421. * @param {Object} item
  22422. * @param {Event} event
  22423. */
  22424. /**
  22425. * @event storechange
  22426. * Fires when the store of the chart changes.
  22427. * @param {Ext.chart.AbstractChart} chart
  22428. * @param {Ext.data.Store} newStore
  22429. * @param {Ext.data.Store} oldStore
  22430. */
  22431. config: {
  22432. /**
  22433. * @cfg {Ext.data.Store/String/Object} store
  22434. * The data source to which the chart is bound.
  22435. * Acceptable values for this property are:
  22436. *
  22437. * - **any {@link Ext.data.Store Store} class / subclass**
  22438. * - **an {@link Ext.data.Store#storeId ID of a store}**
  22439. * - **a {@link Ext.data.Store Store} config object**. When passing a config you can
  22440. * specify the store type by alias. Passing a config object with a store type will
  22441. * dynamically create a new store of that type when the chart is instantiated.
  22442. *
  22443. * For example:
  22444. *
  22445. * Ext.define('MyApp.store.Customer', {
  22446. * extend: 'Ext.data.Store',
  22447. * alias: 'store.customerstore',
  22448. *
  22449. * fields: ['name', 'value']
  22450. * });
  22451. *
  22452. *
  22453. * Ext.create({
  22454. * xtype: 'cartesian',
  22455. * renderTo: document.body,
  22456. * height: 400,
  22457. * width: 400,
  22458. * store: {
  22459. * type: 'customerstore',
  22460. * data: [{
  22461. * name: 'metric one',
  22462. * value: 10
  22463. * }]
  22464. * },
  22465. * axes: [{
  22466. * type: 'numeric',
  22467. * position: 'left',
  22468. * title: {
  22469. * text: 'Sample Values',
  22470. * fontSize: 15
  22471. * },
  22472. * fields: 'value'
  22473. * }, {
  22474. * type: 'category',
  22475. * position: 'bottom',
  22476. * title: {
  22477. * text: 'Sample Values',
  22478. * fontSize: 15
  22479. * },
  22480. * fields: 'name'
  22481. * }],
  22482. * series: {
  22483. * type: 'bar',
  22484. * xField: 'name',
  22485. * yField: 'value'
  22486. * }
  22487. * });
  22488. */
  22489. store: 'ext-empty-store',
  22490. /**
  22491. * @cfg {String} [theme="default"]
  22492. * The name of the theme to be used. A theme defines the colors and styles
  22493. * used by the series, axes, markers and other chart components.
  22494. * Please see the documentation for the {@link Ext.chart.theme.Base} class
  22495. * for more information.
  22496. *
  22497. * Possible theme values are:
  22498. * - 'green', 'sky', 'red', 'purple', 'blue', 'yellow'
  22499. * - 'category1' to 'category6'
  22500. * - and the above theme names with the '-gradients' suffix, e.g. 'green-gradients'
  22501. *
  22502. * IMPORTANT: You should require the themes you use; for example, to use:
  22503. *
  22504. * theme: 'blue'
  22505. *
  22506. * the `Ext.chart.theme.Blue` class should be required:
  22507. *
  22508. * requires: 'Ext.chart.theme.Blue'
  22509. *
  22510. * To require all chart themes:
  22511. *
  22512. * requires: 'Ext.chart.theme.*'
  22513. */
  22514. theme: 'default',
  22515. /**
  22516. * Chart captions can be used to place titles, subtitles, credits and other captions
  22517. * inside a chart. For example:
  22518. *
  22519. * captions: {
  22520. * title: {
  22521. * text: 'Consumer Price Index'
  22522. * },
  22523. * subtitle: {
  22524. * text: 'from 2007 to 2017'
  22525. * },
  22526. * credits: {
  22527. * text: 'Source: 'bls.gov'
  22528. * }
  22529. * }
  22530. *
  22531. * One can use any names for properties in the `captions` config, but the `title`,
  22532. * `subtitle` and `credits` ones have a special meaning - they are automatically
  22533. * themeable. The `title` and `subtitle` are automatically docked to the top of
  22534. * a chart and the `credits` to the bottom. The `title` uses the largest and
  22535. * the heaviest font, while the `credits` - the smallest and the lightest.
  22536. *
  22537. * Other captions besides those three can be easily defined as well:
  22538. *
  22539. * captions: {
  22540. * myFancyCaption: {
  22541. * docked: 'bottom',
  22542. * align: 'left',
  22543. * style: {
  22544. * fontSize: 18,
  22545. * fontWeight: 'bold',
  22546. * fontFamily: 'Verdana'
  22547. * }
  22548. * }
  22549. * }
  22550. *
  22551. * If a caption config only specifies text, a shorthand syntax is also possible:
  22552. *
  22553. * captions: {
  22554. * title: 'Consumer Price Index'
  22555. * }
  22556. *
  22557. * @cfg {Object} captions
  22558. * @cfg {Ext.chart.Caption} captions.title
  22559. * @cfg {Ext.chart.Caption} captions.subtitle
  22560. * @cfg {Ext.chart.Caption} captions.credits
  22561. */
  22562. captions: null,
  22563. /**
  22564. * @cfg {Object} style
  22565. * The style for the chart component.
  22566. */
  22567. style: null,
  22568. /**
  22569. * @cfg {Boolean/Object} [animation=true]
  22570. * Defaults to `easeInOut` easing with a 500ms duration.
  22571. * See {@link Ext.draw.modifier.Animation} for possible configuration options.
  22572. */
  22573. animation: !Ext.isIE8,
  22574. /**
  22575. * @cfg {Ext.chart.series.Series/Array} series
  22576. * Array of {@link Ext.chart.series.Series Series} instances or config objects.
  22577. * For example:
  22578. *
  22579. * series: [{
  22580. * type: 'column',
  22581. * axis: 'left',
  22582. * listeners: {
  22583. * 'afterrender': function() {
  22584. * console.log('afterrender');
  22585. * }
  22586. * },
  22587. * xField: 'category',
  22588. * yField: 'data1'
  22589. * }]
  22590. */
  22591. series: [],
  22592. /**
  22593. * @cfg {Ext.chart.axis.Axis/Array/Object} axes
  22594. * Array of {@link Ext.chart.axis.Axis Axis} instances or config objects.
  22595. * For example:
  22596. *
  22597. * axes: [{
  22598. * type: 'numeric',
  22599. * position: 'left',
  22600. * title: 'Number of Hits',
  22601. * minimum: 0
  22602. * }, {
  22603. * type: 'category',
  22604. * position: 'bottom',
  22605. * title: 'Month of the Year'
  22606. * }]
  22607. */
  22608. axes: [],
  22609. /**
  22610. * @cfg {Ext.chart.legend.Legend/Ext.chart.legend.SpriteLegend/Boolean} legend
  22611. * The legend config for the chart. If specified, a legend block will be shown
  22612. * next to the chart.
  22613. * Each legend item displays the {@link Ext.chart.series.Series#title title}
  22614. * of the series, the color of the series and allows to toggle the visibility
  22615. * of the series (at least one series should remain visible).
  22616. *
  22617. * Sencha Charts support two types of legends: sprite based and DOM based.
  22618. *
  22619. * The sprite based legend can be shown in chart {@link Ext.draw.Container#preview preview}
  22620. * and is a part of the downloaded {@link Ext.draw.Container#download chart image}.
  22621. * The sprite based legend is always displayed in full and takes as much space as necessary,
  22622. * the legend items are split into columns to use the available space efficiently.
  22623. * The sprite based legend is styled via a {@link Ext.chart.theme.Base chart theme}.
  22624. *
  22625. * The DOM based legend supports RTL.
  22626. * It occupies a fixed width or height and scrolls when the content overflows.
  22627. * The DOM based legend is styled via CSS rules.
  22628. *
  22629. * By default the sprite legend is used. The type can be explicitly specified:
  22630. *
  22631. * legend: {
  22632. * type: 'dom', // 'sprite' is another possible value
  22633. * docked: 'top'
  22634. * }
  22635. *
  22636. * If the legend config is set to `true`, the sprite legend will be used
  22637. * docked to the bottom.
  22638. */
  22639. legend: null,
  22640. /**
  22641. * @cfg {Array} colors
  22642. * Array of colors/gradients to override the color of items and legends.
  22643. */
  22644. colors: null,
  22645. /**
  22646. * @cfg {Object/Number/String} insetPadding
  22647. * The amount of inset padding in pixels for the chart.
  22648. * Inset padding is the padding from the boundary of the chart to any
  22649. * of its contents.
  22650. */
  22651. insetPadding: {
  22652. top: 10,
  22653. left: 10,
  22654. right: 10,
  22655. bottom: 10
  22656. },
  22657. /**
  22658. * @cfg {Object} background Set the chart background.
  22659. * This can be a gradient object, image, or color.
  22660. *
  22661. * For example, if `background` were to be a color we could set the object as
  22662. *
  22663. * background: '#ccc'
  22664. *
  22665. * You can specify an image by using:
  22666. *
  22667. * background: {
  22668. * type: 'image',
  22669. * src: 'http://path.to.image/'
  22670. * }
  22671. *
  22672. * Also you can specify a gradient by using the gradient object syntax:
  22673. *
  22674. * background: {
  22675. * type: 'linear',
  22676. * degrees: 0,
  22677. * stops: [
  22678. * {
  22679. * offset: 0,
  22680. * color: 'white'
  22681. * },
  22682. * {
  22683. * offset: 1,
  22684. * color: 'blue'
  22685. * }
  22686. * ]
  22687. * }
  22688. */
  22689. background: null,
  22690. /**
  22691. * @cfg {Array} interactions
  22692. * Interactions are optional modules that can be plugged in to a chart
  22693. * to allow the user to interact with the chart and its data in special ways.
  22694. * The `interactions` config takes an Array of Object configurations,
  22695. * each one corresponding to a particular interaction class identified
  22696. * by a `type` property:
  22697. *
  22698. * new Ext.chart.AbstractChart({
  22699. * renderTo: Ext.getBody(),
  22700. * width: 800,
  22701. * height: 600,
  22702. * store: store1,
  22703. * axes: [
  22704. * // ...some axes options...
  22705. * ],
  22706. * series: [
  22707. * // ...some series options...
  22708. * ],
  22709. * interactions: [{
  22710. * type: 'interactiontype'
  22711. * // ...additional configs for the interaction...
  22712. * }]
  22713. * });
  22714. *
  22715. * When adding an interaction which uses only its default configuration
  22716. * (no extra properties other than `type`), you can alternately specify
  22717. * only the type as a String rather than the full Object:
  22718. *
  22719. * interactions: ['reset', 'rotate']
  22720. *
  22721. * The current supported interaction types include:
  22722. *
  22723. * - {@link Ext.chart.interactions.PanZoom panzoom} - allows pan and zoom of axes
  22724. * - {@link Ext.chart.interactions.ItemHighlight itemhighlight} - allows highlighting of series data points
  22725. * - {@link Ext.chart.interactions.ItemInfo iteminfo} - allows displaying details of a data point in a popup panel
  22726. * - {@link Ext.chart.interactions.Rotate rotate} - allows rotation of pie and radar series
  22727. *
  22728. * See the documentation for each of those interaction classes to see how they can be configured.
  22729. *
  22730. * Additional custom interactions can be registered using `'interactions.'` alias prefix.
  22731. */
  22732. interactions: [],
  22733. /**
  22734. * @private
  22735. * The main area of the chart where grid and series are drawn.
  22736. */
  22737. mainRect: null,
  22738. /**
  22739. * @private
  22740. * Override value.
  22741. */
  22742. resizeHandler: null,
  22743. /**
  22744. * @cfg {Object} highlightItem
  22745. * The current highlight item in the chart.
  22746. * The object must be the one that you get from item events.
  22747. *
  22748. * Note that series can also own highlight items.
  22749. * This notion is separate from this one and should not be used at the same time.
  22750. */
  22751. highlightItem: null,
  22752. surfaceZIndexes: {
  22753. background: 0,
  22754. // Contains the backround 'rect' sprite.
  22755. main: 1,
  22756. // Contains grid lines and CrossZoom overlay 'rect' sprite.
  22757. grid: 2,
  22758. // Reserved.
  22759. series: 3,
  22760. // Contains series sprites.
  22761. axis: 4,
  22762. // No actual `axis` surface is created, but this zIndex is used
  22763. // for all axis surfaces (one surface is created per axis).
  22764. chart: 5,
  22765. // Covers whole chart, minus the legend area.
  22766. // Contains sprites defined in the `sprites` config,
  22767. // title, subtitle and credits.
  22768. caption: 6,
  22769. // Contains title, subtitle and credits sprites.
  22770. overlay: 7,
  22771. // This surface will typically contain chart labels
  22772. // and interaction sprites like crosshair lines.
  22773. // With cartesian charts, equivalent in size to the `series` surface.
  22774. // With polar charts, equivalent in size to the `chart` surface.
  22775. legend: 8
  22776. }
  22777. },
  22778. // `SpriteLegend` surface.
  22779. /**
  22780. * @private
  22781. */
  22782. legendStore: null,
  22783. /**
  22784. * When this is non-zero, changes to sprite attributes apply instantly.
  22785. * See {@link #getAnimation}.
  22786. * @private
  22787. */
  22788. animationSuspendCount: 0,
  22789. /**
  22790. * @private
  22791. */
  22792. chartLayoutSuspendCount: 0,
  22793. /**
  22794. * @private
  22795. */
  22796. chartLayoutCount: 0,
  22797. /**
  22798. * @private
  22799. */
  22800. scheduledLayoutId: null,
  22801. /**
  22802. * @private
  22803. */
  22804. axisThicknessSuspendCount: 0,
  22805. /**
  22806. * @private
  22807. * Indicates that thickness of one or more axes has changed,
  22808. * at the time of {@link #performLayout} call. I.e. 'performLayout'
  22809. * should be called again when current layout is done.
  22810. */
  22811. isThicknessChanged: false,
  22812. constructor: function(config) {
  22813. var me = this;
  22814. me.itemListeners = {};
  22815. me.surfaceMap = {};
  22816. me.chartComponents = {};
  22817. me.isInitializing = true;
  22818. me.suspendChartLayout();
  22819. me.animationSuspendCount++;
  22820. me.callParent(arguments);
  22821. me.isInitializing = false;
  22822. me.getSurface('main');
  22823. me.getSurface('chart').setFlipRtlText(me.getInherited().rtl);
  22824. me.getSurface('overlay').waitFor(me.getSurface('series'));
  22825. me.animationSuspendCount--;
  22826. me.resumeChartLayout();
  22827. },
  22828. applyAnimation: function(animation, oldAnimation) {
  22829. return Ext.chart.Util.applyAnimation(animation, oldAnimation);
  22830. },
  22831. updateAnimation: function() {
  22832. if (this.isConfiguring) {
  22833. return;
  22834. }
  22835. var seriesList = this.getSeries(),
  22836. ln = seriesList.length,
  22837. i, series;
  22838. this.isSettingSeriesAnimation = true;
  22839. for (i = 0; i < ln; i++) {
  22840. series = seriesList[i];
  22841. // Don't update the series animation config, if it was set by
  22842. // a user, unless 'suspendAnimation' was called.
  22843. if (!series.isUserAnimation || this.animationSuspendCount) {
  22844. series.setAnimation(series.getAnimation());
  22845. }
  22846. }
  22847. this.isSettingSeriesAnimation = false;
  22848. },
  22849. getAnimation: function() {
  22850. var result;
  22851. if (this.animationSuspendCount) {
  22852. result = {
  22853. duration: 0
  22854. };
  22855. } else {
  22856. result = this.callParent();
  22857. }
  22858. return result;
  22859. },
  22860. suspendAnimation: function() {
  22861. this.animationSuspendCount++;
  22862. if (this.animationSuspendCount === 1) {
  22863. this.updateAnimation();
  22864. }
  22865. },
  22866. resumeAnimation: function() {
  22867. this.animationSuspendCount--;
  22868. if (this.animationSuspendCount === 0) {
  22869. this.updateAnimation();
  22870. }
  22871. },
  22872. applyInsetPadding: function(padding, oldPadding) {
  22873. var result;
  22874. if (!Ext.isObject(padding)) {
  22875. result = Ext.util.Format.parseBox(padding);
  22876. } else if (!oldPadding) {
  22877. result = padding;
  22878. } else {
  22879. result = Ext.apply(oldPadding, padding);
  22880. }
  22881. return result;
  22882. },
  22883. /**
  22884. * Suspends chart's layout.
  22885. */
  22886. suspendChartLayout: function() {
  22887. var me = this;
  22888. me.chartLayoutSuspendCount++;
  22889. if (me.chartLayoutSuspendCount === 1) {
  22890. if (me.scheduledLayoutId) {
  22891. me.layoutInSuspension = true;
  22892. me.cancelChartLayout();
  22893. } else {
  22894. me.layoutInSuspension = false;
  22895. }
  22896. }
  22897. },
  22898. /**
  22899. * Decrements chart's layout suspend count.
  22900. * When the suspend count is decremented to zero,
  22901. * a layout is scheduled.
  22902. */
  22903. resumeChartLayout: function() {
  22904. var me = this;
  22905. me.chartLayoutSuspendCount--;
  22906. if (me.chartLayoutSuspendCount === 0) {
  22907. if (me.layoutInSuspension) {
  22908. me.scheduleLayout();
  22909. }
  22910. }
  22911. },
  22912. /**
  22913. * Cancel a scheduled layout.
  22914. */
  22915. cancelChartLayout: function() {
  22916. if (this.scheduledLayoutId) {
  22917. Ext.draw.Animator.cancel(this.scheduledLayoutId);
  22918. this.scheduledLayoutId = null;
  22919. this.checkLayoutEnd();
  22920. }
  22921. },
  22922. /**
  22923. * Schedule a layout at next frame.
  22924. */
  22925. scheduleLayout: function() {
  22926. var me = this;
  22927. if (me.allowSchedule() && !me.scheduledLayoutId) {
  22928. me.scheduledLayoutId = Ext.draw.Animator.schedule('doScheduleLayout', me);
  22929. }
  22930. },
  22931. allowSchedule: function() {
  22932. return true;
  22933. },
  22934. doScheduleLayout: function() {
  22935. var me = this;
  22936. me.scheduledLayoutId = null;
  22937. if (me.chartLayoutSuspendCount) {
  22938. me.layoutInSuspension = true;
  22939. } else {
  22940. me.performLayout();
  22941. }
  22942. },
  22943. /**
  22944. * Prevent axes from triggering chart layout when their thickness changes.
  22945. * E.g. during an interaction that makes changes to the axes,
  22946. * or when chart layout was triggered by something else,
  22947. * for example a chart resize event.
  22948. */
  22949. suspendThicknessChanged: function() {
  22950. this.axisThicknessSuspendCount++;
  22951. },
  22952. /**
  22953. * Decrements axis thickness suspend count.
  22954. * When axis thickness suspend count is decremented to zero,
  22955. * chart layout is performed.
  22956. */
  22957. resumeThicknessChanged: function() {
  22958. if (this.axisThicknessSuspendCount > 0) {
  22959. this.axisThicknessSuspendCount--;
  22960. if (this.axisThicknessSuspendCount === 0 && this.isThicknessChanged) {
  22961. this.onThicknessChanged();
  22962. }
  22963. }
  22964. },
  22965. onThicknessChanged: function() {
  22966. if (this.axisThicknessSuspendCount === 0) {
  22967. this.isThicknessChanged = false;
  22968. this.performLayout();
  22969. } else {
  22970. this.isThicknessChanged = true;
  22971. }
  22972. },
  22973. applySprites: function(sprites) {
  22974. var surface = this.getSurface('chart');
  22975. sprites = Ext.Array.from(sprites);
  22976. surface.removeAll(true);
  22977. surface.add(sprites);
  22978. return sprites;
  22979. },
  22980. initItems: function() {
  22981. var items = this.items,
  22982. i, ln, item;
  22983. if (items && !items.isMixedCollection) {
  22984. this.items = [];
  22985. items = Ext.Array.from(items);
  22986. for (i = 0 , ln = items.length; i < ln; i++) {
  22987. item = items[i];
  22988. if (item.type) {
  22989. Ext.raise("To add custom sprites to the chart use the 'sprites' config.");
  22990. } else {
  22991. this.items.push(item);
  22992. }
  22993. }
  22994. }
  22995. // @noOptimize.callParent
  22996. this.callParent();
  22997. },
  22998. // noOptimize is needed because in the ext build we have a parent method to call,
  22999. // but in touch we do not so we need to suppress the cmd warning during optimized build
  23000. applyBackground: function(newBackground, oldBackground) {
  23001. var surface = this.getSurface('background');
  23002. return this.refreshBackground(surface, newBackground, oldBackground);
  23003. },
  23004. /**
  23005. * @private
  23006. * The background updater. Used by both the chart and the sprite legend.
  23007. * @param surface The surface to put the background in.
  23008. * @param newBackground
  23009. * @param oldBackground
  23010. * @return {Ext.draw.sprite.Rect/Ext.draw.sprite.Sprite}
  23011. */
  23012. refreshBackground: function(surface, newBackground, oldBackground) {
  23013. var width, height, isUpdateOld;
  23014. if (newBackground) {
  23015. if (oldBackground) {
  23016. width = oldBackground.attr.width;
  23017. height = oldBackground.attr.height;
  23018. isUpdateOld = oldBackground.type === (newBackground.type || 'rect');
  23019. }
  23020. if (newBackground.isSprite) {
  23021. oldBackground = newBackground;
  23022. } else if (newBackground.type === 'image' && Ext.isString(newBackground.src)) {
  23023. if (isUpdateOld) {
  23024. oldBackground.setAttributes({
  23025. src: newBackground.src
  23026. });
  23027. } else {
  23028. surface.remove(oldBackground, true);
  23029. oldBackground = surface.add(newBackground);
  23030. }
  23031. } else {
  23032. if (isUpdateOld) {
  23033. oldBackground.setAttributes({
  23034. fillStyle: newBackground
  23035. });
  23036. } else {
  23037. surface.remove(oldBackground, true);
  23038. oldBackground = surface.add({
  23039. type: 'rect',
  23040. fillStyle: newBackground,
  23041. animation: {
  23042. customDurations: {
  23043. x: 0,
  23044. y: 0,
  23045. width: 0,
  23046. height: 0
  23047. }
  23048. }
  23049. });
  23050. }
  23051. }
  23052. }
  23053. if (width && height) {
  23054. oldBackground.setAttributes({
  23055. width: width,
  23056. height: height
  23057. });
  23058. }
  23059. oldBackground.setAnimation(this.getAnimation());
  23060. return oldBackground;
  23061. },
  23062. defaultResizeHandler: function(size) {
  23063. this.scheduleLayout();
  23064. return false;
  23065. },
  23066. applyMainRect: function(newRect, rect) {
  23067. if (!rect) {
  23068. return newRect;
  23069. }
  23070. this.getSeries();
  23071. this.getAxes();
  23072. if (newRect[0] === rect[0] && newRect[1] === rect[1] && newRect[2] === rect[2] && newRect[3] === rect[3]) {
  23073. return rect;
  23074. } else {
  23075. return newRect;
  23076. }
  23077. },
  23078. register: function(component) {
  23079. var map = this.chartComponents,
  23080. id = component.getId();
  23081. //<debug>
  23082. if (id === undefined) {
  23083. Ext.raise('Chart component id is undefined. ' + 'Please ensure the component has an id.');
  23084. }
  23085. if (id in map) {
  23086. Ext.raise('Registering duplicate chart component id "' + id + '"');
  23087. }
  23088. //</debug>
  23089. map[id] = component;
  23090. },
  23091. unregister: function(component) {
  23092. var map = this.chartComponents,
  23093. id = component.getId();
  23094. delete map[id];
  23095. },
  23096. get: function(id) {
  23097. return this.chartComponents[id];
  23098. },
  23099. /**
  23100. * @method getAxis Returns an axis instance based on the type of data passed.
  23101. * @param {String/Number/Ext.chart.axis.Axis} axis You may request an axis by passing
  23102. * an id, the number of the array key returned by {@link #getAxes}, or an axis instance.
  23103. * @return {Ext.chart.axis.Axis} The axis requested.
  23104. */
  23105. getAxis: function(axis) {
  23106. if (axis instanceof Ext.chart.axis.Axis) {
  23107. return axis;
  23108. } else if (Ext.isNumber(axis)) {
  23109. return this.getAxes()[axis];
  23110. } else if (Ext.isString(axis)) {
  23111. return this.get(axis);
  23112. }
  23113. },
  23114. getSurface: function(id, type) {
  23115. id = id || 'main';
  23116. type = type || id;
  23117. var me = this,
  23118. surface = this.callParent([
  23119. id,
  23120. type
  23121. ]),
  23122. map = me.surfaceMap;
  23123. if (!map[type]) {
  23124. map[type] = [];
  23125. }
  23126. if (Ext.Array.indexOf(map[type], surface) < 0) {
  23127. surface.type = type;
  23128. map[type].push(surface);
  23129. surface.on('destroy', me.forgetSurface, me);
  23130. }
  23131. return surface;
  23132. },
  23133. forgetSurface: function(surface) {
  23134. var map = this.surfaceMap;
  23135. if (!map || this.destroying) {
  23136. return;
  23137. }
  23138. var group = map[surface.type],
  23139. index = group ? Ext.Array.indexOf(group, surface) : -1;
  23140. if (index >= 0) {
  23141. group.splice(index, 1);
  23142. }
  23143. },
  23144. applyAxes: function(newAxes, oldAxes) {
  23145. var me = this,
  23146. positions = {
  23147. left: 'right',
  23148. right: 'left'
  23149. },
  23150. result = [],
  23151. axis, oldAxis, linkedTo, id, i, j, ln, oldMap, series;
  23152. me.animationSuspendCount++;
  23153. me.getStore();
  23154. if (!oldAxes) {
  23155. oldAxes = [];
  23156. oldAxes.map = {};
  23157. }
  23158. oldMap = oldAxes.map;
  23159. result.map = {};
  23160. newAxes = Ext.Array.from(newAxes, true);
  23161. for (i = 0 , ln = newAxes.length; i < ln; i++) {
  23162. axis = newAxes[i];
  23163. if (!axis) {
  23164. continue;
  23165. }
  23166. if (axis instanceof Ext.chart.axis.Axis) {
  23167. oldAxis = oldMap[axis.getId()];
  23168. axis.setChart(me);
  23169. } else {
  23170. axis = Ext.Object.chain(axis);
  23171. linkedTo = axis.linkedTo;
  23172. id = axis.id;
  23173. if (Ext.isNumber(linkedTo)) {
  23174. axis = Ext.merge({}, newAxes[linkedTo], axis);
  23175. } else if (Ext.isString(linkedTo)) {
  23176. Ext.Array.each(newAxes, function(item) {
  23177. if (item.id === axis.linkedTo) {
  23178. axis = Ext.merge({}, item, axis);
  23179. return false;
  23180. }
  23181. });
  23182. }
  23183. axis.id = id;
  23184. axis.chart = me;
  23185. if (me.getInherited().rtl) {
  23186. axis.position = positions[axis.position] || axis.position;
  23187. }
  23188. id = axis.getId && axis.getId() || axis.id;
  23189. axis = Ext.factory(axis, null, oldAxis = oldMap[id], 'axis');
  23190. }
  23191. if (axis) {
  23192. result.push(axis);
  23193. result.map[axis.getId()] = axis;
  23194. }
  23195. }
  23196. me.axesChangeSeries = {};
  23197. for (i in oldMap) {
  23198. if (!result.map[i]) {
  23199. oldAxis = oldMap[i];
  23200. if (oldAxis && !oldAxis.destroyed) {
  23201. // At this point the series still have their `xAxis` and `yAxis` configs
  23202. // set to old axes. We need to update such series with new matching axes
  23203. // by calling their `onAxesChange` method.
  23204. for (j = 0 , ln = oldAxis.boundSeries.length; j < ln; j++) {
  23205. series = oldAxis.boundSeries[j];
  23206. me.axesChangeSeries[series.getId()] = series;
  23207. }
  23208. oldAxis.destroy();
  23209. }
  23210. }
  23211. }
  23212. me.animationSuspendCount--;
  23213. return result;
  23214. },
  23215. updateAxes: function(axes) {
  23216. var me = this,
  23217. seriesMap = me.axesChangeSeries,
  23218. series, id, i, ln, axis;
  23219. for (id in seriesMap) {
  23220. series = seriesMap[id];
  23221. // `true` to force set series' axes, even if they are already set
  23222. // (in this case to old axes that were just destroyed in the `axes` applier).
  23223. series.onAxesChange(me, true);
  23224. }
  23225. // If changes to the `axes` config are made post chart creation, without making any
  23226. // changes to the series afterwards, we need to figure out the new axes' `boundSeries`
  23227. // manually, as the 'serieschange' event won't be fired in this case.
  23228. for (i = 0 , ln = axes.length; i < ln; i++) {
  23229. axis = axes[i];
  23230. axis.onSeriesChange(me);
  23231. }
  23232. if (!me.isConfiguring && !me.destroying) {
  23233. me.scheduleLayout();
  23234. }
  23235. },
  23236. circularCopyArray: function(inArray, startIndex, count) {
  23237. var outArray = [],
  23238. i,
  23239. len = inArray && inArray.length;
  23240. if (len) {
  23241. for (i = 0; i < count; i++) {
  23242. outArray.push(inArray[(startIndex + i) % len]);
  23243. }
  23244. }
  23245. return outArray;
  23246. },
  23247. circularCopyObject: function(inObject, startIndex, count) {
  23248. var me = this,
  23249. name, value,
  23250. outObject = {};
  23251. if (count) {
  23252. for (name in inObject) {
  23253. if (inObject.hasOwnProperty(name)) {
  23254. value = inObject[name];
  23255. if (Ext.isArray(value)) {
  23256. outObject[name] = me.circularCopyArray(value, startIndex, count);
  23257. } else {
  23258. outObject[name] = value;
  23259. }
  23260. }
  23261. }
  23262. }
  23263. return outObject;
  23264. },
  23265. getColors: function() {
  23266. var me = this,
  23267. configColors = me.config.colors,
  23268. theme = me.getTheme();
  23269. if (Ext.isArray(configColors) && configColors.length > 0) {
  23270. configColors = me.applyColors(configColors);
  23271. }
  23272. return configColors || (theme && theme.getColors());
  23273. },
  23274. applyColors: function(newColors) {
  23275. newColors = Ext.Array.map(newColors, function(color) {
  23276. if (Ext.isString(color)) {
  23277. return color;
  23278. } else {
  23279. return color.toString();
  23280. }
  23281. });
  23282. return newColors;
  23283. },
  23284. updateColors: function(newColors) {
  23285. var me = this,
  23286. theme = me.getTheme(),
  23287. colors = newColors || (theme && theme.getColors()),
  23288. colorIndex = 0,
  23289. series = me.getSeries(),
  23290. seriesCount = series && series.length,
  23291. i, seriesItem, seriesColors, seriesColorCount;
  23292. if (colors.length) {
  23293. for (i = 0; i < seriesCount; i++) {
  23294. seriesItem = series[i];
  23295. seriesColorCount = seriesItem.themeColorCount();
  23296. seriesColors = me.circularCopyArray(colors, colorIndex, seriesColorCount);
  23297. colorIndex += seriesColorCount;
  23298. seriesItem.updateChartColors(seriesColors);
  23299. }
  23300. }
  23301. if (!me.isConfiguring) {
  23302. me.refreshLegendStore();
  23303. }
  23304. },
  23305. applyTheme: function(theme) {
  23306. if (theme && theme.isTheme) {
  23307. return theme;
  23308. }
  23309. return Ext.Factory.chartTheme(theme);
  23310. },
  23311. updateGradients: function(gradients) {
  23312. if (!Ext.isEmpty(gradients)) {
  23313. this.updateTheme(this.getTheme());
  23314. }
  23315. },
  23316. updateTheme: function(theme, oldTheme) {
  23317. var me = this,
  23318. axes = me.getAxes(),
  23319. series = me.getSeries(),
  23320. colors = me.getColors(),
  23321. i;
  23322. if (!series) {
  23323. return;
  23324. }
  23325. me.updateChartTheme(theme);
  23326. for (i = 0; i < axes.length; i++) {
  23327. axes[i].updateTheme(theme);
  23328. }
  23329. for (i = 0; i < series.length; i++) {
  23330. series[i].setTheme(theme);
  23331. }
  23332. me.updateSpriteTheme(theme);
  23333. me.updateColors(colors);
  23334. // It may be necessary to perform a layout here.
  23335. // But instead of the 'chart.scheduleLayout' call, we can call
  23336. // 'chart.redraw'. If after the redraw call the thickness
  23337. // of any axis changes, this will automatically trigger
  23338. // chart layout (see Ext.chart.axis.sprite.Axis.doThicknessChanged).
  23339. // Otherwise, no layout is necessary.
  23340. me.redraw();
  23341. me.fireEvent('themechange', me, theme, oldTheme);
  23342. },
  23343. themeOnlyIfConfigured: {
  23344. captions: true
  23345. },
  23346. updateChartTheme: function(theme) {
  23347. var me = this,
  23348. chartTheme = theme.getChart(),
  23349. initialConfig = me.getInitialConfig(),
  23350. defaultConfig = me.defaultConfig,
  23351. configs = me.self.getConfigurator().configs,
  23352. genericChartTheme = chartTheme.defaults,
  23353. specificChartTheme = chartTheme[me.xtype],
  23354. themeOnlyIfConfigured = me.themeOnlyIfConfigured,
  23355. key, value, isObjValue, isUnusedConfig, initialValue, cfg;
  23356. chartTheme = Ext.merge({}, genericChartTheme, specificChartTheme);
  23357. for (key in chartTheme) {
  23358. value = chartTheme[key];
  23359. cfg = configs[key];
  23360. if (value !== null && value !== undefined && cfg) {
  23361. initialValue = initialConfig[key];
  23362. isObjValue = Ext.isObject(value);
  23363. isUnusedConfig = initialValue === defaultConfig[key];
  23364. if (isObjValue) {
  23365. if (isUnusedConfig && themeOnlyIfConfigured[key]) {
  23366. continue;
  23367. }
  23368. value = Ext.merge({}, value, initialValue);
  23369. }
  23370. if (isUnusedConfig || isObjValue) {
  23371. me[cfg.names.set](value);
  23372. }
  23373. }
  23374. }
  23375. },
  23376. updateSpriteTheme: function(theme) {
  23377. this.getSprites();
  23378. var me = this,
  23379. chartSurface = me.getSurface('chart'),
  23380. sprites = chartSurface.getItems(),
  23381. styles = theme.getSprites(),
  23382. sprite, style, key, attr, isText, i, ln;
  23383. for (i = 0 , ln = sprites.length; i < ln; i++) {
  23384. sprite = sprites[i];
  23385. style = styles[sprite.type];
  23386. if (style) {
  23387. attr = {};
  23388. isText = sprite.type === 'text';
  23389. for (key in style) {
  23390. if (!(key in sprite.config)) {
  23391. // Setting individual font attributes will take over the 'font' shorthand
  23392. // attribute, but this behavior is undesireable for theming.
  23393. if (!(isText && key.indexOf('font') === 0 && sprite.config.font)) {
  23394. attr[key] = style[key];
  23395. }
  23396. }
  23397. }
  23398. sprite.setAttributes(attr);
  23399. }
  23400. }
  23401. },
  23402. /**
  23403. * Adds a {@link Ext.chart.series.Series Series} to this chart.
  23404. *
  23405. * The Series (or array) passed will be added to the existing series. If an `id` is specified
  23406. * in a new Series, any existing Series of that `id` will be updated.
  23407. *
  23408. * The chart will be redrawn in response to the change.
  23409. *
  23410. * @param {Object/Object[]/Ext.chart.series.Series/Ext.chart.series.Series[]} newSeries A config object
  23411. * describing the Series to add, or an instantiated Series object. Or an array of these.
  23412. */
  23413. addSeries: function(newSeries) {
  23414. var series = this.getSeries();
  23415. series = series.concat(Ext.Array.from(newSeries));
  23416. this.setSeries(series);
  23417. },
  23418. /**
  23419. * Remove a {@link Ext.chart.series.Series Series} from this chart.
  23420. * The Series (or array) passed will be removed from the existing series.
  23421. *
  23422. * The chart will be redrawn in response to the change.
  23423. *
  23424. * @param {Ext.chart.series.Series/String} series The Series or the `id` of the Series to remove. May be an array.
  23425. */
  23426. removeSeries: function(series) {
  23427. series = Ext.Array.from(series);
  23428. var existingSeries = this.getSeries(),
  23429. newSeries = [],
  23430. len = series.length,
  23431. removeMap = {},
  23432. i, s;
  23433. // Build a map of the Series IDs that are to be removed
  23434. for (i = 0; i < len; i++) {
  23435. s = series[i];
  23436. // If they passed a Series Object
  23437. if (typeof s !== 'string') {
  23438. s = s.getId();
  23439. }
  23440. removeMap[s] = true;
  23441. }
  23442. // Build a new Series array that excludes those Series scheduled for removal
  23443. for (i = 0 , len = existingSeries.length; i < len; i++) {
  23444. if (!removeMap[existingSeries[i].getId()]) {
  23445. newSeries.push(existingSeries[i]);
  23446. }
  23447. }
  23448. this.setSeries(newSeries);
  23449. },
  23450. applySeries: function(newSeries, oldSeries) {
  23451. var me = this,
  23452. result = [],
  23453. oldMap, oldSeriesItem, i, ln, series;
  23454. me.animationSuspendCount++;
  23455. me.getAxes();
  23456. if (oldSeries) {
  23457. oldMap = oldSeries.map;
  23458. } else {
  23459. oldSeries = [];
  23460. oldMap = oldSeries.map = {};
  23461. }
  23462. result.map = {};
  23463. newSeries = Ext.Array.from(newSeries, true);
  23464. for (i = 0 , ln = newSeries.length; i < ln; i++) {
  23465. series = newSeries[i];
  23466. if (!series) {
  23467. continue;
  23468. }
  23469. oldSeriesItem = oldMap[series.getId && series.getId() || series.id];
  23470. // New Series instance passed in
  23471. if (series instanceof Ext.chart.series.Series) {
  23472. // Replacing
  23473. if (oldSeriesItem && oldSeriesItem !== series) {
  23474. oldSeriesItem.destroy();
  23475. }
  23476. series.setChart(me);
  23477. }
  23478. // Series config object passed in
  23479. else if (Ext.isObject(series)) {
  23480. // Config object matched an existing Series item by id;
  23481. // update its configuration
  23482. if (oldSeriesItem) {
  23483. oldSeriesItem.setConfig(series);
  23484. series = oldSeriesItem;
  23485. } else // Create a new Series
  23486. {
  23487. if (Ext.isString(series)) {
  23488. series = {
  23489. type: series
  23490. };
  23491. }
  23492. series.chart = me;
  23493. series = Ext.create(series.xclass || ('series.' + series.type), series);
  23494. }
  23495. }
  23496. result.push(series);
  23497. result.map[series.getId()] = series;
  23498. }
  23499. for (i in oldMap) {
  23500. if (!result.map[oldMap[i].id]) {
  23501. oldMap[i].destroy();
  23502. }
  23503. }
  23504. me.animationSuspendCount--;
  23505. return result;
  23506. },
  23507. updateSeries: function(newSeries, oldSeries) {
  23508. var me = this;
  23509. if (me.destroying) {
  23510. return;
  23511. }
  23512. me.animationSuspendCount++;
  23513. me.fireEvent('serieschange', me, newSeries, oldSeries);
  23514. if (!Ext.isEmpty(newSeries)) {
  23515. me.updateTheme(me.getTheme());
  23516. }
  23517. me.refreshLegendStore();
  23518. if (!me.isConfiguring && !me.destroying) {
  23519. me.scheduleLayout();
  23520. }
  23521. me.animationSuspendCount--;
  23522. },
  23523. defaultLegendType: 'sprite',
  23524. applyLegend: function(legend, oldLegend) {
  23525. var me = this,
  23526. result = null,
  23527. alias;
  23528. if (oldLegend && !(oldLegend.destroyed || oldLegend.destroying)) {
  23529. if (me.legendStoreListeners) {
  23530. me.legendStoreListeners.destroy();
  23531. }
  23532. if (me.legendStore) {
  23533. me.legendStore.destroy();
  23534. }
  23535. oldLegend.destroy();
  23536. }
  23537. if (legend) {
  23538. if (Ext.isBoolean(legend)) {
  23539. result = Ext.create('legend.' + me.defaultLegendType, {
  23540. docked: 'bottom',
  23541. chart: me
  23542. });
  23543. } else {
  23544. legend.docked = legend.docked || 'bottom';
  23545. legend.chart = me;
  23546. alias = 'legend.' + (legend.type || me.defaultLegendType);
  23547. result = Ext.create(alias, legend);
  23548. }
  23549. }
  23550. return result;
  23551. },
  23552. updateLegend: function(legend) {
  23553. var me = this;
  23554. // Probably has been already destroyed with the old legend,
  23555. // but making sure.
  23556. me.destroyLegendStore();
  23557. if (legend) {
  23558. me.getItems();
  23559. legend.setStore(me.refreshLegendStore());
  23560. }
  23561. if (!me.isConfiguring) {
  23562. me.scheduleLayout();
  23563. }
  23564. },
  23565. captionApplier: function(caption, oldCaption) {
  23566. var me = this,
  23567. result;
  23568. if (oldCaption && !(oldCaption.destroyed || oldCaption.destroying)) {
  23569. oldCaption.destroy();
  23570. }
  23571. if (caption) {
  23572. caption.chart = me;
  23573. result = new Ext.chart.Caption(caption);
  23574. }
  23575. return result;
  23576. },
  23577. applyCaptions: function(captions, oldCaptions) {
  23578. var map = {},
  23579. caption, oldCaption, name, any;
  23580. for (name in captions) {
  23581. caption = captions[name];
  23582. if (caption && !caption.length && !(caption.text && caption.text.length)) {
  23583. caption = null;
  23584. } else if (typeof caption === 'string') {
  23585. caption = {
  23586. text: caption
  23587. };
  23588. // Initial config is used for proper theming (see `updateChartTheme`)
  23589. // and config merging, however, mergin won't work as expected, if
  23590. // the initial config value remains a string, so we modify it here.
  23591. this.getInitialConfig().captions[name] = caption;
  23592. }
  23593. oldCaption = oldCaptions && oldCaptions[name];
  23594. caption = this.captionApplier(caption, oldCaption);
  23595. if (caption) {
  23596. any = true;
  23597. map[name] = caption;
  23598. }
  23599. }
  23600. return any && map;
  23601. },
  23602. updateCaptions: function() {
  23603. var me = this;
  23604. if (!me.isConfiguring) {
  23605. me.scheduleLayout();
  23606. }
  23607. },
  23608. /**
  23609. * Return the legend store that contains all the legend information.
  23610. * This information is collected from all the series.
  23611. * @return {Ext.chart.legend.store.Store}
  23612. */
  23613. getLegendStore: function() {
  23614. var me = this,
  23615. store = me.legendStore;
  23616. if (!store) {
  23617. store = me.legendStore = new Ext.chart.legend.store.Store({
  23618. chart: me
  23619. });
  23620. me.legendStoreListeners = store.on({
  23621. scope: me,
  23622. update: 'onLegendStoreUpdate',
  23623. destroyable: true
  23624. });
  23625. }
  23626. return store;
  23627. },
  23628. destroyLegendStore: function() {
  23629. var store = this.legendStore;
  23630. if (store && !(store.destroyed || store.destroying)) {
  23631. store.destroy();
  23632. }
  23633. this.legendStore = null;
  23634. },
  23635. refreshLegendStore: function() {
  23636. var me = this,
  23637. legendStore = me.getLegendStore(),
  23638. series;
  23639. if (legendStore) {
  23640. var seriesList = me.getSeries(),
  23641. ln = seriesList.length,
  23642. legendData = [],
  23643. i = 0;
  23644. for (; i < ln; i++) {
  23645. series = seriesList[i];
  23646. if (series.getShowInLegend()) {
  23647. series.provideLegendInfo(legendData);
  23648. }
  23649. }
  23650. legendStore.setData(legendData);
  23651. }
  23652. return legendStore;
  23653. },
  23654. onLegendStoreUpdate: function(store, record) {
  23655. var me = this,
  23656. series;
  23657. if (record) {
  23658. series = this.getSeries().map[record.get('series')];
  23659. if (series) {
  23660. series.setHiddenByIndex(record.get('index'), record.get('disabled'));
  23661. me.redraw();
  23662. }
  23663. }
  23664. },
  23665. applyInteractions: function(interactions, oldInteractions) {
  23666. interactions = Ext.Array.from(interactions, true);
  23667. if (!oldInteractions) {
  23668. oldInteractions = [];
  23669. oldInteractions.map = {};
  23670. }
  23671. var me = this,
  23672. result = [],
  23673. oldMap = oldInteractions.map,
  23674. i, ln, interaction;
  23675. result.map = {};
  23676. for (i = 0 , ln = interactions.length; i < ln; i++) {
  23677. interaction = interactions[i];
  23678. if (!interaction) {
  23679. continue;
  23680. }
  23681. interaction = Ext.factory(interaction, null, oldMap[interaction.getId && interaction.getId() || interaction.id], 'interaction');
  23682. if (interaction) {
  23683. interaction.setChart(me);
  23684. result.push(interaction);
  23685. result.map[interaction.getId()] = interaction;
  23686. }
  23687. }
  23688. for (i in oldMap) {
  23689. if (!result.map[i]) {
  23690. oldMap[i].destroy();
  23691. }
  23692. }
  23693. return result;
  23694. },
  23695. /**
  23696. * Get an interaction by type.
  23697. * @param {String} type The type of the interaction.
  23698. * @return {Ext.chart.interactions.Abstract} The interaction. `null`
  23699. * if not found.
  23700. */
  23701. getInteraction: function(type) {
  23702. var interactions = this.getInteractions(),
  23703. len = interactions && interactions.length,
  23704. out = null,
  23705. interaction, i;
  23706. if (len) {
  23707. for (i = 0; i < len; ++i) {
  23708. interaction = interactions[i];
  23709. if (interaction.type === type) {
  23710. out = interaction;
  23711. break;
  23712. }
  23713. }
  23714. }
  23715. return out;
  23716. },
  23717. applyStore: function(store) {
  23718. return store && Ext.StoreManager.lookup(store);
  23719. },
  23720. updateStore: function(newStore, oldStore) {
  23721. var me = this;
  23722. if (oldStore && !oldStore.destroyed) {
  23723. oldStore.un({
  23724. datachanged: 'onDataChanged',
  23725. update: 'onDataChanged',
  23726. scope: me,
  23727. order: 'after'
  23728. });
  23729. if (oldStore.autoDestroy) {
  23730. oldStore.destroy();
  23731. }
  23732. }
  23733. if (newStore) {
  23734. newStore.on({
  23735. datachanged: 'onDataChanged',
  23736. update: 'onDataChanged',
  23737. scope: me,
  23738. order: 'after'
  23739. });
  23740. }
  23741. me.fireEvent('storechange', me, newStore, oldStore);
  23742. me.onDataChanged();
  23743. },
  23744. /**
  23745. * Redraw the chart. If animations are set this will animate the chart too.
  23746. * Note: the actual redraw is performed in a subclass.
  23747. */
  23748. redraw: function() {
  23749. this.fireEvent('redraw', this);
  23750. },
  23751. /**
  23752. * @private
  23753. * Lays out chart components and triggers a {@link #event!redraw}.
  23754. * Note: the actual layout is performed in a subclass.
  23755. * A subclass should not perform a layout, if this parent method
  23756. * returns `false`.
  23757. * @return {Boolean}
  23758. */
  23759. performLayout: function() {
  23760. if (this.destroying || this.destroyed) {
  23761. //<debug>
  23762. Ext.raise('Attempting to lay out a dead chart: ' + this.getId());
  23763. //</debug>
  23764. return false;
  23765. }
  23766. // Cancel subclass layout.
  23767. var me = this,
  23768. legend = me.getLegend(),
  23769. chartRect = me.getChartRect(true),
  23770. background = me.getBackground(),
  23771. result = true,
  23772. legendRect;
  23773. me.cancelChartLayout();
  23774. //<debug>
  23775. // Unlike the 'layout' event that is called after all chart layouts are done
  23776. // and none are pending, this event fires before the start of each layout.
  23777. me.fireEvent('beforelayout', me);
  23778. //</debug>
  23779. if (background) {
  23780. me.getSurface('background').setRect(chartRect.slice());
  23781. background.setAttributes({
  23782. width: chartRect[2],
  23783. height: chartRect[3]
  23784. });
  23785. }
  23786. // The top docked legend is a special case and should be laid out after captions.
  23787. if (legend && legend.isSpriteLegend && !legend.isTop) {
  23788. legendRect = legend.computeRect(chartRect);
  23789. }
  23790. me.layoutCaptions(chartRect);
  23791. if (legend && legend.isSpriteLegend && legend.isTop) {
  23792. legendRect = legend.computeRect(chartRect);
  23793. }
  23794. if (legendRect) {
  23795. me.getSurface('legend').setRect(legendRect);
  23796. result = legend.performLayout();
  23797. }
  23798. me.getSurface('chart').setRect(chartRect);
  23799. if (result) {
  23800. me.hasFirstLayout = true;
  23801. }
  23802. return result;
  23803. },
  23804. layoutCaptions: function(chartRect) {
  23805. var captions = this.getCaptions(),
  23806. shrinkRect = {
  23807. left: 0,
  23808. top: 0,
  23809. right: chartRect[2],
  23810. bottom: chartRect[3]
  23811. },
  23812. caption, captionName, captionList, i, ln;
  23813. if (captions) {
  23814. captionList = [];
  23815. for (captionName in captions) {
  23816. captionList.push(captions[captionName]);
  23817. }
  23818. captionList.sort(function(a, b) {
  23819. return a.getWeight() - b.getWeight();
  23820. });
  23821. for (i = 0 , ln = captionList.length; i < ln; i++) {
  23822. caption = captionList[i];
  23823. if (!i) {
  23824. this.getSurface(caption.surfaceName).setRect(chartRect.slice());
  23825. }
  23826. caption.computeRect(chartRect, shrinkRect);
  23827. }
  23828. this.captionList = captionList;
  23829. }
  23830. },
  23831. /**
  23832. * @private
  23833. */
  23834. checkLayoutEnd: function() {
  23835. // not running not pending
  23836. if (!this.chartLayoutCount && !this.scheduledLayoutId) {
  23837. this.onLayoutEnd();
  23838. }
  23839. },
  23840. /**
  23841. * @private
  23842. */
  23843. onLayoutEnd: function() {
  23844. var me = this;
  23845. me.fireEvent('layout', me);
  23846. },
  23847. /**
  23848. * @private
  23849. * The area of the chart minus the legend, title, subtitle and credits.
  23850. * Cache chart rect as element.getSize() results in
  23851. * a relatively expensive call to the getComputedStyle().
  23852. */
  23853. getChartRect: function(isRecompute) {
  23854. var me = this,
  23855. chartRect, bodySize;
  23856. if (isRecompute) {
  23857. me.chartRect = null;
  23858. }
  23859. if (me.chartRect) {
  23860. chartRect = me.chartRect;
  23861. } else {
  23862. bodySize = me.bodyElement.getSize();
  23863. chartRect = me.chartRect = [
  23864. 0,
  23865. 0,
  23866. bodySize.width,
  23867. bodySize.height
  23868. ];
  23869. }
  23870. return chartRect;
  23871. },
  23872. /**
  23873. * @private
  23874. * Converts page coordinates into chart's 'series' surface coordinates.
  23875. */
  23876. getEventXY: function(e) {
  23877. return this.getSurface('series').getEventXY(e);
  23878. },
  23879. /**
  23880. * Given an x/y point relative to the chart, find and return the first series item that
  23881. * matches that point.
  23882. * @param {Number} x
  23883. * @param {Number} y
  23884. * @return {Object} An object with `series` and `item` properties, or `false` if no item found.
  23885. */
  23886. getItemForPoint: function(x, y) {
  23887. var me = this,
  23888. seriesList = me.getSeries(),
  23889. rect = me.getMainRect(),
  23890. ln = seriesList.length,
  23891. minDistance = Infinity,
  23892. result = null,
  23893. i, item;
  23894. // The x,y here are already converted to the 'main' surface coordinates.
  23895. // Series surface rect matches the main surface rect.
  23896. if (!(me.hasFirstLayout && rect && x >= 0 && x <= rect[2] && y >= 0 && y <= rect[3])) {
  23897. return null;
  23898. }
  23899. // Iterate in reverse order so that the series that render later (on top)
  23900. // get hit tested first.
  23901. for (i = ln - 1; i >= 0; i--) {
  23902. item = seriesList[i].getItemForPoint(x, y);
  23903. if (item) {
  23904. // Imagine a chart with multiple series, e.g. 'line', 'scatter' and 'bar'.
  23905. // For 'line' and 'scatter' series, the method will look for the nearest
  23906. // marker, but for 'bar' series, it will look for the first bar that
  23907. // contains the given point. For such series, the 'distance' information
  23908. // is absent and meaningless.
  23909. if (!item.distance) {
  23910. result = item;
  23911. break;
  23912. }
  23913. if (item.distance < minDistance) {
  23914. minDistance = item.distance;
  23915. result = item;
  23916. }
  23917. }
  23918. }
  23919. return result;
  23920. },
  23921. /**
  23922. * @private
  23923. * Given an x/y point relative to the chart, find and return all series items that match that point.
  23924. * @param {Number} x
  23925. * @param {Number} y
  23926. * @return {Array} An array of objects with `series` and `item` properties.
  23927. * @deprecated 6.5.2 This method is deprecated
  23928. */
  23929. getItemsForPoint: function(x, y) {
  23930. var me = this,
  23931. seriesList = me.getSeries(),
  23932. ln = seriesList.length,
  23933. // If we haven't drawn yet, don't attempt to find any items.
  23934. i = me.hasFirstLayout ? ln - 1 : -1,
  23935. items = [],
  23936. series, item;
  23937. // Iterate from the end so that the series that are drawn later get hit tested first.
  23938. for (; i >= 0; i--) {
  23939. series = seriesList[i];
  23940. item = series.getItemForPoint(x, y);
  23941. if (item && (item.category === 'items' || item.category === 'markers')) {
  23942. items.push(item);
  23943. }
  23944. }
  23945. return items;
  23946. },
  23947. /**
  23948. * @private
  23949. */
  23950. onDataChanged: function() {
  23951. var me = this;
  23952. if (me.isInitializing) {
  23953. return;
  23954. }
  23955. var rect = me.getMainRect(),
  23956. store = me.getStore(),
  23957. series = me.getSeries(),
  23958. axes = me.getAxes();
  23959. if (!store || !axes || !series) {
  23960. return;
  23961. }
  23962. if (!rect) {
  23963. // The chart hasn't been rendered yet.
  23964. me.on({
  23965. redraw: me.onDataChanged,
  23966. scope: me,
  23967. single: true
  23968. });
  23969. return;
  23970. }
  23971. me.processData();
  23972. me.redraw();
  23973. },
  23974. /**
  23975. * @private
  23976. * The number of records in the chart's store last time the data was changed.
  23977. */
  23978. recordCount: 0,
  23979. /**
  23980. * @private
  23981. */
  23982. processData: function() {
  23983. var me = this,
  23984. recordCount = me.getStore().getCount(),
  23985. seriesList = me.getSeries(),
  23986. ln = seriesList.length,
  23987. isNeedUpdateColors = false,
  23988. i = 0,
  23989. series;
  23990. for (; i < ln; i++) {
  23991. series = seriesList[i];
  23992. series.processData();
  23993. if (!isNeedUpdateColors && series.isStoreDependantColorCount) {
  23994. isNeedUpdateColors = true;
  23995. }
  23996. }
  23997. if (isNeedUpdateColors && recordCount > me.recordCount) {
  23998. me.updateColors(me.getColors());
  23999. me.recordCount = recordCount;
  24000. }
  24001. // 'refreshLegendStore' will attemp to grab the 'series',
  24002. // which are still configuring at this point.
  24003. // The legend store will be refreshed inside the chart.series
  24004. // updater anyway.
  24005. if (!me.isConfiguring) {
  24006. me.refreshLegendStore();
  24007. }
  24008. },
  24009. /**
  24010. * Changes the data store bound to this chart and refreshes it.
  24011. * @param {Ext.data.Store} store The store to bind to this chart.
  24012. */
  24013. bindStore: function(store) {
  24014. this.setStore(store);
  24015. },
  24016. applyHighlightItem: function(newHighlightItem, oldHighlightItem) {
  24017. if (newHighlightItem === oldHighlightItem) {
  24018. return;
  24019. }
  24020. if (Ext.isObject(newHighlightItem) && Ext.isObject(oldHighlightItem)) {
  24021. var i1 = newHighlightItem,
  24022. i2 = oldHighlightItem,
  24023. s1 = i1.sprite && (i1.sprite[0] || i1.sprite),
  24024. s2 = i2.sprite && (i2.sprite[0] || i2.sprite);
  24025. if (s1 === s2 && i1.index === i2.index) {
  24026. return;
  24027. }
  24028. }
  24029. return newHighlightItem;
  24030. },
  24031. updateHighlightItem: function(newHighlightItem, oldHighlightItem) {
  24032. var newHighlight, oldHighlight;
  24033. if (oldHighlightItem) {
  24034. oldHighlight = oldHighlightItem.series.getHighlight();
  24035. if (oldHighlight) {
  24036. oldHighlightItem.series.setAttributesForItem(oldHighlightItem, {
  24037. highlighted: false
  24038. });
  24039. }
  24040. }
  24041. if (newHighlightItem) {
  24042. newHighlight = newHighlightItem.series.getHighlight();
  24043. if (newHighlight) {
  24044. newHighlightItem.series.setAttributesForItem(newHighlightItem, {
  24045. highlighted: true
  24046. });
  24047. }
  24048. }
  24049. if (oldHighlight || newHighlight) {
  24050. this.fireEvent('itemhighlight', this, newHighlightItem, oldHighlightItem);
  24051. }
  24052. },
  24053. destroyChart: function() {
  24054. var me = this;
  24055. // The order is important here.
  24056. me.setInteractions(null);
  24057. me.setAxes(null);
  24058. me.setSeries(null);
  24059. me.setLegend(null);
  24060. me.setStore(null);
  24061. me.cancelChartLayout();
  24062. },
  24063. /* ---------------------------------
  24064. Methods needed for ComponentQuery
  24065. ----------------------------------*/
  24066. /**
  24067. * @private
  24068. * @param {Boolean} deep
  24069. * @return {Array}
  24070. */
  24071. getRefItems: function(deep) {
  24072. var me = this,
  24073. series = me.getSeries(),
  24074. axes = me.getAxes(),
  24075. interaction = me.getInteractions(),
  24076. legend = me.getLegend(),
  24077. ans = [],
  24078. i, ln;
  24079. for (i = 0 , ln = series.length; i < ln; i++) {
  24080. ans.push(series[i]);
  24081. if (series[i].getRefItems) {
  24082. ans.push.apply(ans, series[i].getRefItems(deep));
  24083. }
  24084. }
  24085. for (i = 0 , ln = axes.length; i < ln; i++) {
  24086. ans.push(axes[i]);
  24087. if (axes[i].getRefItems) {
  24088. ans.push.apply(ans, axes[i].getRefItems(deep));
  24089. }
  24090. }
  24091. for (i = 0 , ln = interaction.length; i < ln; i++) {
  24092. ans.push(interaction[i]);
  24093. if (interaction[i].getRefItems) {
  24094. ans.push.apply(ans, interaction[i].getRefItems(deep));
  24095. }
  24096. }
  24097. if (legend) {
  24098. ans.push(legend);
  24099. }
  24100. return ans;
  24101. }
  24102. });
  24103. Ext.define('Ext.chart.overrides.AbstractChart', {
  24104. override: 'Ext.chart.AbstractChart',
  24105. // In Modern toolkit, if chart element style has no z-index specified,
  24106. // some chart surfaces with higher z-indexes (e.g. overlay)
  24107. // may end up on top of modal dialogs shown over the chart.
  24108. zIndex: 0,
  24109. updateLegend: function(legend, oldLegend) {
  24110. this.callParent([
  24111. legend,
  24112. oldLegend
  24113. ]);
  24114. if (legend && legend.isDomLegend) {
  24115. this.add(legend);
  24116. }
  24117. },
  24118. onItemRemove: function(item, index, destroy) {
  24119. var map = this.surfaceMap,
  24120. type = item.type,
  24121. items = map && map[type];
  24122. this.callParent([
  24123. item,
  24124. index,
  24125. destroy
  24126. ]);
  24127. if (items) {
  24128. Ext.Array.remove(items, item);
  24129. if (items.length === 0) {
  24130. delete map[type];
  24131. }
  24132. }
  24133. },
  24134. doDestroy: function() {
  24135. this.destroyChart();
  24136. this.callParent();
  24137. }
  24138. });
  24139. /**
  24140. * @class Ext.chart.grid.HorizontalGrid
  24141. * @extends Ext.draw.sprite.Sprite
  24142. *
  24143. * Horizontal Grid sprite. Used in Cartesian Charts.
  24144. */
  24145. Ext.define('Ext.chart.grid.HorizontalGrid', {
  24146. extend: 'Ext.draw.sprite.Sprite',
  24147. alias: 'grid.horizontal',
  24148. inheritableStatics: {
  24149. def: {
  24150. processors: {
  24151. x: 'number',
  24152. y: 'number',
  24153. width: 'number',
  24154. height: 'number'
  24155. },
  24156. defaults: {
  24157. x: 0,
  24158. y: 0,
  24159. width: 1,
  24160. height: 1,
  24161. strokeStyle: '#DDD'
  24162. }
  24163. }
  24164. },
  24165. render: function(surface, ctx, rect) {
  24166. var attr = this.attr,
  24167. y = surface.roundPixel(attr.y),
  24168. halfLineWidth = ctx.lineWidth * 0.5;
  24169. ctx.beginPath();
  24170. ctx.rect(rect[0] - surface.matrix.getDX(), y + halfLineWidth, +rect[2], attr.height);
  24171. ctx.fill();
  24172. ctx.beginPath();
  24173. ctx.moveTo(rect[0] - surface.matrix.getDX(), y + halfLineWidth);
  24174. ctx.lineTo(rect[0] + rect[2] - surface.matrix.getDX(), y + halfLineWidth);
  24175. ctx.stroke();
  24176. }
  24177. });
  24178. /**
  24179. * @class Ext.chart.grid.VerticalGrid
  24180. * @extends Ext.draw.sprite.Sprite
  24181. *
  24182. * Vertical Grid sprite. Used in Cartesian Charts.
  24183. */
  24184. Ext.define('Ext.chart.grid.VerticalGrid', {
  24185. extend: 'Ext.draw.sprite.Sprite',
  24186. alias: 'grid.vertical',
  24187. inheritableStatics: {
  24188. def: {
  24189. processors: {
  24190. x: 'number',
  24191. y: 'number',
  24192. width: 'number',
  24193. height: 'number'
  24194. },
  24195. defaults: {
  24196. x: 0,
  24197. y: 0,
  24198. width: 1,
  24199. height: 1,
  24200. strokeStyle: '#DDD'
  24201. }
  24202. }
  24203. },
  24204. render: function(surface, ctx, rect) {
  24205. var attr = this.attr,
  24206. x = surface.roundPixel(attr.x),
  24207. halfLineWidth = ctx.lineWidth * 0.5;
  24208. ctx.beginPath();
  24209. ctx.rect(x - halfLineWidth, rect[1] - surface.matrix.getDY(), attr.width, rect[3]);
  24210. ctx.fill();
  24211. ctx.beginPath();
  24212. ctx.moveTo(x - halfLineWidth, rect[1] - surface.matrix.getDY());
  24213. ctx.lineTo(x - halfLineWidth, rect[1] + rect[3] - surface.matrix.getDY());
  24214. ctx.stroke();
  24215. }
  24216. });
  24217. /**
  24218. * Represents a chart that uses cartesian coordinates.
  24219. * A cartesian chart has two directions, X direction and Y direction.
  24220. * The series and axes are coordinated along these directions.
  24221. * By default the x direction is horizontal and y direction is vertical,
  24222. * You can swap the direction by setting the {@link #flipXY} config to `true`.
  24223. *
  24224. * Cartesian series often treats x direction an y direction differently.
  24225. * In most cases, data on x direction are assumed to be monotonically increasing.
  24226. * Based on this property, cartesian series can be trimmed and summarized properly
  24227. * to gain a better performance.
  24228. *
  24229. * Please check out the summary for the {@link Ext.chart.AbstractChart} as well,
  24230. * for helpful tips and important details.
  24231. *
  24232. */
  24233. Ext.define('Ext.chart.CartesianChart', {
  24234. extend: 'Ext.chart.AbstractChart',
  24235. alternateClassName: 'Ext.chart.Chart',
  24236. requires: [
  24237. 'Ext.chart.grid.HorizontalGrid',
  24238. 'Ext.chart.grid.VerticalGrid'
  24239. ],
  24240. xtype: [
  24241. 'cartesian',
  24242. 'chart'
  24243. ],
  24244. isCartesian: true,
  24245. config: {
  24246. /**
  24247. * @cfg {Boolean} flipXY Flip the direction of X and Y axis.
  24248. * If flipXY is `true`, the X axes will be vertical and Y axes will be horizontal.
  24249. * Note that {@link Ext.chart.axis.Axis#position positions} of chart axes have
  24250. * to be updated accordingly: axes positioned to the `top` and `bottom` should
  24251. * be positioned to the `left` or `right` and vice versa.
  24252. */
  24253. flipXY: false,
  24254. /*
  24255. While it may seem tedious to change the position config of all axes every time
  24256. when the value of the flipXY config is changed, it's hard to predict the
  24257. expectaction of the user here, as illustrated below.
  24258. The 'num' and 'cat' here stand for the numeric and the category axis, respectively.
  24259. And the right column shows the expected (subjective) result of setting the flipXY
  24260. config of the chart to 'true'.
  24261. As one can see, there's no single rule (e.g. position swapping, clockwise 90° chart
  24262. rotation) that will produce a universally accepted result.
  24263. So we are letting the user decide, instead of doing it for them.
  24264. ---------------------------------------------
  24265. | flipXY: false | flipXY: true |
  24266. ---------------------------------------------
  24267. | ^ | ^ |
  24268. | | * | | * * * |
  24269. | num1 | * * | cat | * * |
  24270. | | * * * | | * |
  24271. | --------> | --------> |
  24272. | cat | num1 |
  24273. ---------------------------------------------
  24274. | | num1 |
  24275. | ^ ^ | ^-------> |
  24276. | | * | | | * * * |
  24277. | num1 | * * | num2 | cat | * * |
  24278. | | * * * | | | * |
  24279. | --------> | --------> |
  24280. | cat | num2 |
  24281. ---------------------------------------------
  24282. */
  24283. innerRect: [
  24284. 0,
  24285. 0,
  24286. 1,
  24287. 1
  24288. ],
  24289. /**
  24290. * @cfg {Object} innerPadding The amount of inner padding in pixels.
  24291. * Inner padding is the padding from the innermost axes to the series.
  24292. */
  24293. innerPadding: {
  24294. top: 0,
  24295. left: 0,
  24296. right: 0,
  24297. bottom: 0
  24298. }
  24299. },
  24300. applyInnerPadding: function(padding, oldPadding) {
  24301. if (!Ext.isObject(padding)) {
  24302. return Ext.util.Format.parseBox(padding);
  24303. } else if (!oldPadding) {
  24304. return padding;
  24305. } else {
  24306. return Ext.apply(oldPadding, padding);
  24307. }
  24308. },
  24309. getDirectionForAxis: function(position) {
  24310. var flipXY = this.getFlipXY(),
  24311. direction;
  24312. if (position === 'left' || position === 'right') {
  24313. direction = flipXY ? 'X' : 'Y';
  24314. } else {
  24315. direction = flipXY ? 'Y' : 'X';
  24316. }
  24317. return direction;
  24318. },
  24319. /**
  24320. * Layout the axes and series.
  24321. */
  24322. performLayout: function() {
  24323. var me = this;
  24324. if (me.callParent() === false) {
  24325. return;
  24326. }
  24327. me.chartLayoutCount++;
  24328. me.suspendAnimation();
  24329. // 'chart' surface rect is the size of the chart's inner element
  24330. // (see chart.getChartBox), i.e. the portion of the chart minus
  24331. // the legend area (whether DOM or sprite based).
  24332. var chartRect = me.getSurface('chart').getRect(),
  24333. left = chartRect[0],
  24334. top = chartRect[1],
  24335. width = chartRect[2],
  24336. height = chartRect[3],
  24337. captionList = me.captionList,
  24338. axes = me.getAxes(),
  24339. axis,
  24340. seriesList = me.getSeries(),
  24341. series, axisSurface, thickness,
  24342. insetPadding = me.getInsetPadding(),
  24343. innerPadding = me.getInnerPadding(),
  24344. surface, gridSurface,
  24345. // shrinkBox represents padding added on each side by
  24346. // innerPadding & insetPadding configs and the legend.
  24347. shrinkBox = Ext.apply({}, insetPadding),
  24348. mainRect, innerWidth, innerHeight, elements, floating, floatingValue, matrix, i, ln,
  24349. isRtl = me.getInherited().rtl,
  24350. flipXY = me.getFlipXY(),
  24351. caption;
  24352. if (width <= 0 || height <= 0) {
  24353. return;
  24354. }
  24355. me.suspendThicknessChanged();
  24356. for (i = 0; i < axes.length; i++) {
  24357. axis = axes[i];
  24358. axisSurface = axis.getSurface();
  24359. floating = axis.getFloating();
  24360. floatingValue = floating ? floating.value : null;
  24361. thickness = axis.getThickness();
  24362. switch (axis.getPosition()) {
  24363. case 'top':
  24364. axisSurface.setRect([
  24365. left,
  24366. top + shrinkBox.top + 1,
  24367. width,
  24368. thickness
  24369. ]);
  24370. break;
  24371. case 'bottom':
  24372. axisSurface.setRect([
  24373. left,
  24374. top + height - (shrinkBox.bottom + thickness),
  24375. width,
  24376. thickness
  24377. ]);
  24378. break;
  24379. case 'left':
  24380. axisSurface.setRect([
  24381. left + shrinkBox.left,
  24382. top,
  24383. thickness,
  24384. height
  24385. ]);
  24386. break;
  24387. case 'right':
  24388. axisSurface.setRect([
  24389. left + width - (shrinkBox.right + thickness),
  24390. top,
  24391. thickness,
  24392. height
  24393. ]);
  24394. break;
  24395. }
  24396. if (floatingValue === null) {
  24397. shrinkBox[axis.getPosition()] += thickness;
  24398. }
  24399. }
  24400. width -= shrinkBox.left + shrinkBox.right;
  24401. height -= shrinkBox.top + shrinkBox.bottom;
  24402. mainRect = [
  24403. left + shrinkBox.left,
  24404. top + shrinkBox.top,
  24405. width,
  24406. height
  24407. ];
  24408. shrinkBox.left += innerPadding.left;
  24409. shrinkBox.top += innerPadding.top;
  24410. shrinkBox.right += innerPadding.right;
  24411. shrinkBox.bottom += innerPadding.bottom;
  24412. innerWidth = width - innerPadding.left - innerPadding.right;
  24413. innerHeight = height - innerPadding.top - innerPadding.bottom;
  24414. me.setInnerRect([
  24415. shrinkBox.left,
  24416. shrinkBox.top,
  24417. innerWidth,
  24418. innerHeight
  24419. ]);
  24420. if (innerWidth <= 0 || innerHeight <= 0) {
  24421. return;
  24422. }
  24423. me.setMainRect(mainRect);
  24424. me.getSurface().setRect(mainRect);
  24425. for (i = 0 , ln = me.surfaceMap.grid && me.surfaceMap.grid.length; i < ln; i++) {
  24426. gridSurface = me.surfaceMap.grid[i];
  24427. gridSurface.setRect(mainRect);
  24428. gridSurface.matrix.set(1, 0, 0, 1, innerPadding.left, innerPadding.top);
  24429. gridSurface.matrix.inverse(gridSurface.inverseMatrix);
  24430. }
  24431. for (i = 0; i < axes.length; i++) {
  24432. axis = axes[i];
  24433. axis.getRange(true);
  24434. axisSurface = axis.getSurface();
  24435. matrix = axisSurface.matrix;
  24436. elements = matrix.elements;
  24437. switch (axis.getPosition()) {
  24438. case 'top':
  24439. case 'bottom':
  24440. elements[4] = shrinkBox.left;
  24441. axis.setLength(innerWidth);
  24442. break;
  24443. case 'left':
  24444. case 'right':
  24445. elements[5] = shrinkBox.top;
  24446. axis.setLength(innerHeight);
  24447. break;
  24448. }
  24449. axis.updateTitleSprite();
  24450. matrix.inverse(axisSurface.inverseMatrix);
  24451. }
  24452. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  24453. series = seriesList[i];
  24454. surface = series.getSurface();
  24455. surface.setRect(mainRect);
  24456. if (flipXY) {
  24457. if (isRtl) {
  24458. surface.matrix.set(0, -1, -1, 0, innerPadding.left + innerWidth, innerPadding.top + innerHeight);
  24459. } else {
  24460. surface.matrix.set(0, -1, 1, 0, innerPadding.left, innerPadding.top + innerHeight);
  24461. }
  24462. } else {
  24463. surface.matrix.set(1, 0, 0, -1, innerPadding.left, innerPadding.top + innerHeight);
  24464. }
  24465. surface.matrix.inverse(surface.inverseMatrix);
  24466. series.getOverlaySurface().setRect(mainRect);
  24467. }
  24468. if (captionList) {
  24469. for (i = 0 , ln = captionList.length; i < ln; i++) {
  24470. caption = captionList[i];
  24471. if (caption.getAlignTo() === 'series') {
  24472. caption.alignRect(mainRect);
  24473. }
  24474. caption.performLayout();
  24475. }
  24476. }
  24477. // In certain cases 'performLayout' override is not an option without major code duplication.
  24478. // 'afterChartLayout' can be a cleaner solution in such cases (because of the timing of its call).
  24479. me.afterChartLayout();
  24480. // currently in cartesian charts only (used by Navigator)
  24481. me.redraw();
  24482. me.resumeAnimation();
  24483. // 'resumeThicknessChanged' may trigger another layout, if the 'redraw' call above
  24484. // resulted in a situation where an axis is no longer 'thick' enough to accommodate
  24485. // the new labels. E.g. the labels were: 'Bob', 'Ann', 'Joe' and now they are 'Jonathan',
  24486. // 'Rachael', 'Michael'. An axis has to be made thicker now, and another layout should be
  24487. // performed. This second layout is not scheduled, but performed immediately, which will
  24488. // increment the 'chartLayoutCount' again.
  24489. me.resumeThicknessChanged();
  24490. me.chartLayoutCount--;
  24491. // 'checkLayoutEnd' will check if another layout is already running or scheduled and,
  24492. // if neither is the case, will fire the 'layout' event, meaning we are totally done
  24493. // with layout at this point.
  24494. me.checkLayoutEnd();
  24495. },
  24496. afterChartLayout: Ext.emptyFn,
  24497. refloatAxes: function() {
  24498. var me = this,
  24499. axes = me.getAxes(),
  24500. axesCount = (axes && axes.length) || 0,
  24501. axis, axisSurface, axisRect, floating, value, alongAxis, matrix,
  24502. chartRect = me.getChartRect(),
  24503. inset = me.getInsetPadding(),
  24504. inner = me.getInnerPadding(),
  24505. width = chartRect[2] - inset.left - inset.right,
  24506. height = chartRect[3] - inset.top - inset.bottom,
  24507. isHorizontal, i;
  24508. for (i = 0; i < axesCount; i++) {
  24509. axis = axes[i];
  24510. floating = axis.getFloating();
  24511. value = floating ? floating.value : null;
  24512. if (value === null) {
  24513. axis.floatingAtCoord = null;
  24514. continue;
  24515. }
  24516. axisSurface = axis.getSurface();
  24517. axisRect = axisSurface.getRect();
  24518. if (!axisRect) {
  24519. continue;
  24520. }
  24521. axisRect = axisRect.slice();
  24522. alongAxis = me.getAxis(floating.alongAxis);
  24523. if (alongAxis) {
  24524. isHorizontal = alongAxis.getAlignment() === 'horizontal';
  24525. if (Ext.isString(value)) {
  24526. value = alongAxis.getCoordFor(value);
  24527. }
  24528. alongAxis.floatingAxes[axis.getId()] = value;
  24529. matrix = alongAxis.getSprites()[0].attr.matrix;
  24530. if (isHorizontal) {
  24531. value = value * matrix.getXX() + matrix.getDX();
  24532. axis.floatingAtCoord = value + inner.left + inner.right;
  24533. } else {
  24534. value = value * matrix.getYY() + matrix.getDY();
  24535. axis.floatingAtCoord = value + inner.top + inner.bottom;
  24536. }
  24537. } else {
  24538. isHorizontal = axis.getAlignment() === 'horizontal';
  24539. if (isHorizontal) {
  24540. axis.floatingAtCoord = value + inner.top + inner.bottom;
  24541. } else {
  24542. axis.floatingAtCoord = value + inner.left + inner.right;
  24543. }
  24544. value = axisSurface.roundPixel(0.01 * value * (isHorizontal ? height : width));
  24545. }
  24546. switch (axis.getPosition()) {
  24547. case 'top':
  24548. axisRect[1] = inset.top + inner.top + value - axisRect[3] + 1;
  24549. break;
  24550. case 'bottom':
  24551. axisRect[1] = inset.top + inner.top + (alongAxis ? value : height - value);
  24552. break;
  24553. case 'left':
  24554. axisRect[0] = inset.left + inner.left + value - axisRect[2];
  24555. break;
  24556. case 'right':
  24557. axisRect[0] = inset.left + inner.left + (alongAxis ? value : width - value) - 1;
  24558. break;
  24559. }
  24560. axisSurface.setRect(axisRect);
  24561. }
  24562. },
  24563. redraw: function() {
  24564. var me = this,
  24565. seriesList = me.getSeries(),
  24566. axes = me.getAxes(),
  24567. rect = me.getMainRect(),
  24568. innerWidth, innerHeight,
  24569. innerPadding = me.getInnerPadding(),
  24570. sprites, xRange, yRange, isSide, attr, i, j, ln, axis, axisX, axisY, range, visibleRange,
  24571. flipXY = me.getFlipXY(),
  24572. zBase = 1000,
  24573. zIndex, markersZIndex, series, sprite, markers;
  24574. if (!rect) {
  24575. return;
  24576. }
  24577. innerWidth = rect[2] - innerPadding.left - innerPadding.right;
  24578. innerHeight = rect[3] - innerPadding.top - innerPadding.bottom;
  24579. for (i = 0; i < seriesList.length; i++) {
  24580. series = seriesList[i];
  24581. axisX = series.getXAxis();
  24582. if (axisX) {
  24583. visibleRange = axisX.getVisibleRange();
  24584. xRange = axisX.getRange();
  24585. xRange = [
  24586. xRange[0] + (xRange[1] - xRange[0]) * visibleRange[0],
  24587. xRange[0] + (xRange[1] - xRange[0]) * visibleRange[1]
  24588. ];
  24589. } else {
  24590. xRange = series.getXRange();
  24591. }
  24592. axisY = series.getYAxis();
  24593. if (axisY) {
  24594. visibleRange = axisY.getVisibleRange();
  24595. yRange = axisY.getRange();
  24596. yRange = [
  24597. yRange[0] + (yRange[1] - yRange[0]) * visibleRange[0],
  24598. yRange[0] + (yRange[1] - yRange[0]) * visibleRange[1]
  24599. ];
  24600. } else {
  24601. yRange = series.getYRange();
  24602. }
  24603. attr = {
  24604. visibleMinX: xRange[0],
  24605. visibleMaxX: xRange[1],
  24606. visibleMinY: yRange[0],
  24607. visibleMaxY: yRange[1],
  24608. innerWidth: innerWidth,
  24609. innerHeight: innerHeight,
  24610. flipXY: flipXY
  24611. };
  24612. sprites = series.getSprites();
  24613. for (j = 0 , ln = sprites.length; j < ln; j++) {
  24614. // All the series now share the same surface, so we must assign
  24615. // the sprites a zIndex that depends on the index of their series.
  24616. sprite = sprites[j];
  24617. zIndex = sprite.attr.zIndex;
  24618. if (zIndex < zBase) {
  24619. // Set the sprite's zIndex
  24620. zIndex += (i + 1) * 100 + zBase;
  24621. sprite.attr.zIndex = zIndex;
  24622. // If the sprite is a MarkerHolder, set zIndex of the bound markers as well.
  24623. // Do this for the 'items' markers only, as those are the only ones
  24624. // that go into the 'series' surface. 'labels' and 'markers' markers
  24625. // go into the 'overlay' surface instead.
  24626. markers = sprite.getMarker('items');
  24627. if (markers) {
  24628. markersZIndex = markers.attr.zIndex;
  24629. if (markersZIndex === Number.MAX_VALUE) {
  24630. markers.attr.zIndex = zIndex;
  24631. } else if (markersZIndex < zBase) {
  24632. markers.attr.zIndex = zIndex + markersZIndex;
  24633. }
  24634. }
  24635. }
  24636. sprite.setAttributes(attr, true);
  24637. }
  24638. }
  24639. for (i = 0; i < axes.length; i++) {
  24640. axis = axes[i];
  24641. isSide = axis.isSide();
  24642. sprites = axis.getSprites();
  24643. range = axis.getRange();
  24644. visibleRange = axis.getVisibleRange();
  24645. attr = {
  24646. dataMin: range[0],
  24647. dataMax: range[1],
  24648. visibleMin: visibleRange[0],
  24649. visibleMax: visibleRange[1]
  24650. };
  24651. if (isSide) {
  24652. attr.length = innerHeight;
  24653. attr.startGap = innerPadding.bottom;
  24654. attr.endGap = innerPadding.top;
  24655. } else {
  24656. attr.length = innerWidth;
  24657. attr.startGap = innerPadding.left;
  24658. attr.endGap = innerPadding.right;
  24659. }
  24660. for (j = 0 , ln = sprites.length; j < ln; j++) {
  24661. sprites[j].setAttributes(attr, true);
  24662. }
  24663. }
  24664. me.renderFrame();
  24665. me.callParent();
  24666. },
  24667. renderFrame: function() {
  24668. this.refloatAxes();
  24669. this.callParent();
  24670. }
  24671. });
  24672. /**
  24673. * @class Ext.chart.grid.CircularGrid
  24674. * @extends Ext.draw.sprite.Circle
  24675. *
  24676. * Circular Grid sprite. Used by Radar chart to render a series of concentric circles.
  24677. */
  24678. Ext.define('Ext.chart.grid.CircularGrid', {
  24679. extend: 'Ext.draw.sprite.Circle',
  24680. alias: 'grid.circular',
  24681. inheritableStatics: {
  24682. def: {
  24683. defaults: {
  24684. r: 1,
  24685. strokeStyle: '#DDD'
  24686. }
  24687. }
  24688. }
  24689. });
  24690. /**
  24691. * @class Ext.chart.grid.RadialGrid
  24692. * @extends Ext.draw.sprite.Path
  24693. *
  24694. * Radial Grid sprite. Used by Radar chart to render a series of radial lines.
  24695. * Represents the scale of the radar chart on the yField.
  24696. */
  24697. Ext.define('Ext.chart.grid.RadialGrid', {
  24698. extend: 'Ext.draw.sprite.Path',
  24699. alias: 'grid.radial',
  24700. inheritableStatics: {
  24701. def: {
  24702. processors: {
  24703. startRadius: 'number',
  24704. endRadius: 'number'
  24705. },
  24706. defaults: {
  24707. startRadius: 0,
  24708. endRadius: 1,
  24709. scalingCenterX: 0,
  24710. scalingCenterY: 0,
  24711. strokeStyle: '#DDD'
  24712. },
  24713. triggers: {
  24714. startRadius: 'path,bbox',
  24715. endRadius: 'path,bbox'
  24716. }
  24717. }
  24718. },
  24719. render: function() {
  24720. this.callParent(arguments);
  24721. },
  24722. updatePath: function(path, attr) {
  24723. var startRadius = attr.startRadius,
  24724. endRadius = attr.endRadius;
  24725. path.moveTo(startRadius, 0);
  24726. path.lineTo(endRadius, 0);
  24727. }
  24728. });
  24729. /**
  24730. * @class Ext.chart.PolarChart
  24731. * @extends Ext.chart.AbstractChart
  24732. * @xtype polar
  24733. *
  24734. * Represent a chart that uses polar coordinates.
  24735. * A polar chart has two axes: an angular axis (which is a circle) and
  24736. * a radial axis (a straight line from the center to the edge of the circle).
  24737. * The angular axis is usually a Category axis while the radial axis is
  24738. * typically numerical.
  24739. *
  24740. * Pie charts and Radar charts are common examples of Polar charts.
  24741. *
  24742. * Please check out the summary for the {@link Ext.chart.AbstractChart} as well,
  24743. * for helpful tips and important details.
  24744. *
  24745. */
  24746. Ext.define('Ext.chart.PolarChart', {
  24747. extend: 'Ext.chart.AbstractChart',
  24748. requires: [
  24749. 'Ext.chart.grid.CircularGrid',
  24750. 'Ext.chart.grid.RadialGrid'
  24751. ],
  24752. xtype: 'polar',
  24753. isPolar: true,
  24754. config: {
  24755. /**
  24756. * @cfg {Array} center Determines the center of the polar chart.
  24757. * Updated when the chart performs layout.
  24758. */
  24759. center: [
  24760. 0,
  24761. 0
  24762. ],
  24763. /**
  24764. * @cfg {Number} radius Determines the radius of the polar chart.
  24765. * Updated when the chart performs layout.
  24766. */
  24767. radius: 0,
  24768. /**
  24769. * @cfg {Number} innerPadding The amount of inner padding in pixels.
  24770. * Inner padding is the padding from the outermost angular axis to the series.
  24771. */
  24772. innerPadding: 0
  24773. },
  24774. getDirectionForAxis: function(position) {
  24775. return position === 'radial' ? 'Y' : 'X';
  24776. },
  24777. updateCenter: function(center) {
  24778. var me = this,
  24779. axes = me.getAxes(),
  24780. series = me.getSeries(),
  24781. i, ln, axis, seriesItem;
  24782. for (i = 0 , ln = axes.length; i < ln; i++) {
  24783. axis = axes[i];
  24784. axis.setCenter(center);
  24785. }
  24786. for (i = 0 , ln = series.length; i < ln; i++) {
  24787. seriesItem = series[i];
  24788. seriesItem.setCenter(center);
  24789. }
  24790. },
  24791. applyInnerPadding: function(padding, oldPadding) {
  24792. return Ext.isNumber(padding) ? padding : oldPadding;
  24793. },
  24794. updateInnerPadding: function() {
  24795. if (!this.isConfiguring) {
  24796. this.performLayout();
  24797. }
  24798. },
  24799. doSetSurfaceRect: function(surface, rect) {
  24800. var mainRect = this.getMainRect();
  24801. surface.setRect(rect);
  24802. surface.matrix.set(1, 0, 0, 1, mainRect[0] - rect[0], mainRect[1] - rect[1]);
  24803. surface.inverseMatrix.set(1, 0, 0, 1, rect[0] - mainRect[0], rect[1] - mainRect[1]);
  24804. },
  24805. applyAxes: function(newAxes, oldAxes) {
  24806. var me = this,
  24807. firstSeries = Ext.Array.from(me.config.series)[0],
  24808. i, ln, axis, foundAngular;
  24809. if (firstSeries && firstSeries.type === 'radar' && newAxes && newAxes.length) {
  24810. // For compatibility with ExtJS: add a default angular axis if it's missing
  24811. for (i = 0 , ln = newAxes.length; i < ln; i++) {
  24812. axis = newAxes[i];
  24813. if (axis.position === 'angular') {
  24814. foundAngular = true;
  24815. break;
  24816. }
  24817. }
  24818. if (!foundAngular) {
  24819. newAxes.push({
  24820. type: 'category',
  24821. position: 'angular',
  24822. fields: firstSeries.xField || firstSeries.angleField,
  24823. style: {
  24824. estStepSize: 1
  24825. },
  24826. grid: true
  24827. });
  24828. }
  24829. }
  24830. return this.callParent([
  24831. newAxes,
  24832. oldAxes
  24833. ]);
  24834. },
  24835. performLayout: function() {
  24836. var me = this,
  24837. applyThickness = true;
  24838. try {
  24839. me.chartLayoutCount++;
  24840. me.suspendAnimation();
  24841. if (this.callParent() === false) {
  24842. applyThickness = false;
  24843. // Animation will be decremented in finally block
  24844. return;
  24845. }
  24846. me.suspendThicknessChanged();
  24847. var chartRect = me.getSurface('chart').getRect(),
  24848. inset = me.getInsetPadding(),
  24849. inner = me.getInnerPadding(),
  24850. shrinkBox = Ext.apply({}, inset),
  24851. width = Math.max(1, chartRect[2] - chartRect[0] - inset.left - inset.right),
  24852. height = Math.max(1, chartRect[3] - chartRect[1] - inset.top - inset.bottom),
  24853. mainRect = [
  24854. chartRect[0] + inset.left,
  24855. chartRect[1] + inset.top,
  24856. width + chartRect[0],
  24857. height + chartRect[1]
  24858. ],
  24859. seriesList = me.getSeries(),
  24860. innerWidth = width - inner * 2,
  24861. innerHeight = height - inner * 2,
  24862. center = [
  24863. (chartRect[0] + innerWidth) * 0.5 + inner,
  24864. (chartRect[1] + innerHeight) * 0.5 + inner
  24865. ],
  24866. radius = Math.min(innerWidth, innerHeight) * 0.5,
  24867. axes = me.getAxes(),
  24868. angularAxes = [],
  24869. radialAxes = [],
  24870. seriesRadius = radius - inner,
  24871. grid = me.surfaceMap.grid,
  24872. captionList = me.captionList,
  24873. i, ln, shrinkRadius, floating, floatingValue, gaugeSeries, gaugeRadius, side, series, axis, thickness, halfLineWidth, caption;
  24874. me.setMainRect(mainRect);
  24875. me.doSetSurfaceRect(me.getSurface(), mainRect);
  24876. if (grid) {
  24877. for (i = 0 , ln = grid.length; i < ln; i++) {
  24878. me.doSetSurfaceRect(grid[i], chartRect);
  24879. }
  24880. }
  24881. for (i = 0 , ln = axes.length; i < ln; i++) {
  24882. axis = axes[i];
  24883. switch (axis.getPosition()) {
  24884. case 'angular':
  24885. angularAxes.push(axis);
  24886. break;
  24887. case 'radial':
  24888. radialAxes.push(axis);
  24889. break;
  24890. }
  24891. }
  24892. for (i = 0 , ln = angularAxes.length; i < ln; i++) {
  24893. axis = angularAxes[i];
  24894. floating = axis.getFloating();
  24895. floatingValue = floating ? floating.value : null;
  24896. me.doSetSurfaceRect(axis.getSurface(), chartRect);
  24897. thickness = axis.getThickness();
  24898. for (side in shrinkBox) {
  24899. shrinkBox[side] += thickness;
  24900. }
  24901. width = chartRect[2] - shrinkBox.left - shrinkBox.right;
  24902. height = chartRect[3] - shrinkBox.top - shrinkBox.bottom;
  24903. shrinkRadius = Math.min(width, height) * 0.5;
  24904. if (i === 0) {
  24905. seriesRadius = shrinkRadius - inner;
  24906. }
  24907. axis.setMinimum(0);
  24908. axis.setLength(shrinkRadius);
  24909. axis.getSprites();
  24910. halfLineWidth = axis.sprites[0].attr.lineWidth * 0.5;
  24911. for (side in shrinkBox) {
  24912. shrinkBox[side] += halfLineWidth;
  24913. }
  24914. }
  24915. for (i = 0 , ln = radialAxes.length; i < ln; i++) {
  24916. axis = radialAxes[i];
  24917. me.doSetSurfaceRect(axis.getSurface(), chartRect);
  24918. axis.setMinimum(0);
  24919. axis.setLength(seriesRadius);
  24920. axis.getSprites();
  24921. }
  24922. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  24923. series = seriesList[i];
  24924. if (series.type === 'gauge' && !gaugeSeries) {
  24925. gaugeSeries = series;
  24926. } else {
  24927. series.setRadius(seriesRadius);
  24928. }
  24929. me.doSetSurfaceRect(series.getSurface(), mainRect);
  24930. }
  24931. me.doSetSurfaceRect(me.getSurface('overlay'), chartRect);
  24932. if (gaugeSeries) {
  24933. gaugeSeries.setRect(mainRect);
  24934. gaugeRadius = gaugeSeries.getRadius() - inner;
  24935. me.setRadius(gaugeRadius);
  24936. me.setCenter(gaugeSeries.getCenter());
  24937. gaugeSeries.setRadius(gaugeRadius);
  24938. if (axes.length && axes[0].getPosition() === 'gauge') {
  24939. axis = axes[0];
  24940. me.doSetSurfaceRect(axis.getSurface(), chartRect);
  24941. axis.setTotalAngle(gaugeSeries.getTotalAngle());
  24942. axis.setLength(gaugeRadius);
  24943. }
  24944. } else {
  24945. me.setRadius(radius);
  24946. me.setCenter(center);
  24947. }
  24948. if (captionList) {
  24949. for (i = 0 , ln = captionList.length; i < ln; i++) {
  24950. caption = captionList[i];
  24951. if (caption.getAlignTo() === 'series') {
  24952. caption.alignRect(mainRect);
  24953. }
  24954. caption.performLayout();
  24955. }
  24956. }
  24957. me.redraw();
  24958. } finally {
  24959. me.resumeAnimation();
  24960. if (applyThickness) {
  24961. me.resumeThicknessChanged();
  24962. }
  24963. me.chartLayoutCount--;
  24964. me.checkLayoutEnd();
  24965. }
  24966. },
  24967. refloatAxes: function() {
  24968. var me = this,
  24969. axes = me.getAxes(),
  24970. mainRect = me.getMainRect(),
  24971. floating, value, alongAxis, i, n, axis, radius;
  24972. if (!mainRect) {
  24973. return;
  24974. }
  24975. radius = 0.5 * Math.min(mainRect[2], mainRect[3]);
  24976. for (i = 0 , n = axes.length; i < n; i++) {
  24977. axis = axes[i];
  24978. floating = axis.getFloating();
  24979. value = floating ? floating.value : null;
  24980. if (value !== null) {
  24981. alongAxis = me.getAxis(floating.alongAxis);
  24982. if (axis.getPosition() === 'angular') {
  24983. if (alongAxis) {
  24984. value = alongAxis.getLength() * value / alongAxis.getRange()[1];
  24985. } else {
  24986. value = 0.01 * value * radius;
  24987. }
  24988. axis.sprites[0].setAttributes({
  24989. length: value
  24990. }, true);
  24991. } else {
  24992. if (alongAxis) {
  24993. if (Ext.isString(value)) {
  24994. value = alongAxis.getCoordFor(value);
  24995. }
  24996. value = value / (alongAxis.getRange()[1] + 1) * Math.PI * 2 - Math.PI * 1.5 + axis.getRotation();
  24997. } else {
  24998. value = Ext.draw.Draw.rad(value);
  24999. }
  25000. axis.sprites[0].setAttributes({
  25001. baseRotation: value
  25002. }, true);
  25003. }
  25004. }
  25005. }
  25006. },
  25007. redraw: function() {
  25008. var me = this,
  25009. axes = me.getAxes(),
  25010. axis,
  25011. seriesList = me.getSeries(),
  25012. series, i, ln;
  25013. for (i = 0 , ln = axes.length; i < ln; i++) {
  25014. axis = axes[i];
  25015. axis.getSprites();
  25016. }
  25017. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  25018. series = seriesList[i];
  25019. series.getSprites();
  25020. }
  25021. me.renderFrame();
  25022. me.callParent();
  25023. },
  25024. renderFrame: function() {
  25025. this.refloatAxes();
  25026. this.callParent();
  25027. }
  25028. });
  25029. /**
  25030. * @class Ext.chart.SpaceFillingChart
  25031. * @extends Ext.chart.AbstractChart
  25032. *
  25033. * Creates a chart that fills the entire area of the chart.
  25034. * e.g. Gauge Charts
  25035. */
  25036. Ext.define('Ext.chart.SpaceFillingChart', {
  25037. extend: 'Ext.chart.AbstractChart',
  25038. xtype: 'spacefilling',
  25039. config: {},
  25040. performLayout: function() {
  25041. var me = this;
  25042. try {
  25043. me.chartLayoutCount++;
  25044. me.suspendAnimation();
  25045. if (me.callParent() === false) {
  25046. // animationSuspendCount will still be decremented
  25047. return;
  25048. }
  25049. var chartRect = me.getSurface('chart').getRect(),
  25050. padding = me.getInsetPadding(),
  25051. width = chartRect[2] - padding.left - padding.right,
  25052. height = chartRect[3] - padding.top - padding.bottom,
  25053. mainRect = [
  25054. padding.left,
  25055. padding.top,
  25056. width,
  25057. height
  25058. ],
  25059. seriesList = me.getSeries(),
  25060. series, i, ln;
  25061. me.getSurface().setRect(mainRect);
  25062. me.setMainRect(mainRect);
  25063. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  25064. series = seriesList[i];
  25065. series.getSurface().setRect(mainRect);
  25066. if (series.setRect) {
  25067. series.setRect(mainRect);
  25068. }
  25069. series.getOverlaySurface().setRect(chartRect);
  25070. }
  25071. me.redraw();
  25072. } finally {
  25073. me.resumeAnimation();
  25074. me.chartLayoutCount--;
  25075. me.checkLayoutEnd();
  25076. }
  25077. },
  25078. redraw: function() {
  25079. var me = this,
  25080. seriesList = me.getSeries(),
  25081. series, i, ln;
  25082. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  25083. series = seriesList[i];
  25084. series.getSprites();
  25085. }
  25086. me.renderFrame();
  25087. me.callParent();
  25088. }
  25089. });
  25090. /**
  25091. * @private
  25092. * @class Ext.chart.axis.sprite.Axis3D
  25093. * @extends Ext.chart.axis.sprite.Axis
  25094. *
  25095. * The {@link Ext.chart.axis.Axis3D 3D axis} sprite.
  25096. * Only 3D cartesian axes are rendered with this sprite.
  25097. */
  25098. Ext.define('Ext.chart.axis.sprite.Axis3D', {
  25099. extend: 'Ext.chart.axis.sprite.Axis',
  25100. alias: 'sprite.axis3d',
  25101. type: 'axis3d',
  25102. inheritableStatics: {
  25103. def: {
  25104. processors: {
  25105. depth: 'number'
  25106. },
  25107. defaults: {
  25108. depth: 0
  25109. },
  25110. triggers: {
  25111. depth: 'layout'
  25112. }
  25113. }
  25114. },
  25115. config: {
  25116. animation: {
  25117. customDurations: {
  25118. depth: 0
  25119. }
  25120. }
  25121. },
  25122. layoutUpdater: function() {
  25123. var me = this,
  25124. chart = me.getAxis().getChart();
  25125. if (chart.isInitializing) {
  25126. return;
  25127. }
  25128. var attr = me.attr,
  25129. layout = me.getLayout(),
  25130. depth = layout.isDiscrete ? 0 : attr.depth,
  25131. isRtl = chart.getInherited().rtl,
  25132. min = attr.dataMin + (attr.dataMax - attr.dataMin) * attr.visibleMin,
  25133. max = attr.dataMin + (attr.dataMax - attr.dataMin) * attr.visibleMax,
  25134. context = {
  25135. attr: attr,
  25136. segmenter: me.getSegmenter(),
  25137. renderer: me.defaultRenderer
  25138. };
  25139. if (attr.position === 'left' || attr.position === 'right') {
  25140. attr.translationX = 0;
  25141. attr.translationY = max * (attr.length - depth) / (max - min) + depth;
  25142. attr.scalingX = 1;
  25143. attr.scalingY = (-attr.length + depth) / (max - min);
  25144. attr.scalingCenterY = 0;
  25145. attr.scalingCenterX = 0;
  25146. me.applyTransformations(true);
  25147. } else if (attr.position === 'top' || attr.position === 'bottom') {
  25148. if (isRtl) {
  25149. attr.translationX = attr.length + min * attr.length / (max - min) + 1;
  25150. } else {
  25151. attr.translationX = -min * attr.length / (max - min);
  25152. }
  25153. attr.translationY = 0;
  25154. attr.scalingX = (isRtl ? -1 : 1) * (attr.length - depth) / (max - min);
  25155. attr.scalingY = 1;
  25156. attr.scalingCenterY = 0;
  25157. attr.scalingCenterX = 0;
  25158. me.applyTransformations(true);
  25159. }
  25160. if (layout) {
  25161. layout.calculateLayout(context);
  25162. me.setLayoutContext(context);
  25163. }
  25164. },
  25165. renderAxisLine: function(surface, ctx, layout, clipRect) {
  25166. var me = this,
  25167. attr = me.attr,
  25168. halfLineWidth = attr.lineWidth * 0.5,
  25169. layout = me.getLayout(),
  25170. depth = layout.isDiscrete ? 0 : attr.depth,
  25171. docked = attr.position,
  25172. position, gaugeAngles;
  25173. if (attr.axisLine && attr.length) {
  25174. switch (docked) {
  25175. case 'left':
  25176. position = surface.roundPixel(clipRect[2]) - halfLineWidth;
  25177. ctx.moveTo(position, -attr.endGap + depth);
  25178. ctx.lineTo(position, attr.length + attr.startGap);
  25179. break;
  25180. case 'right':
  25181. ctx.moveTo(halfLineWidth, -attr.endGap);
  25182. ctx.lineTo(halfLineWidth, attr.length + attr.startGap);
  25183. break;
  25184. case 'bottom':
  25185. ctx.moveTo(-attr.startGap, halfLineWidth);
  25186. ctx.lineTo(attr.length - depth + attr.endGap, halfLineWidth);
  25187. break;
  25188. case 'top':
  25189. position = surface.roundPixel(clipRect[3]) - halfLineWidth;
  25190. ctx.moveTo(-attr.startGap, position);
  25191. ctx.lineTo(attr.length + attr.endGap, position);
  25192. break;
  25193. case 'angular':
  25194. ctx.moveTo(attr.centerX + attr.length, attr.centerY);
  25195. ctx.arc(attr.centerX, attr.centerY, attr.length, 0, Math.PI * 2, true);
  25196. break;
  25197. case 'gauge':
  25198. gaugeAngles = me.getGaugeAngles();
  25199. ctx.moveTo(attr.centerX + Math.cos(gaugeAngles.start) * attr.length, attr.centerY + Math.sin(gaugeAngles.start) * attr.length);
  25200. ctx.arc(attr.centerX, attr.centerY, attr.length, gaugeAngles.start, gaugeAngles.end, true);
  25201. break;
  25202. }
  25203. }
  25204. }
  25205. });
  25206. /**
  25207. * @class Ext.chart.axis.Axis3D
  25208. * @extends Ext.chart.axis.Axis
  25209. * @xtype axis3d
  25210. *
  25211. * Defines a 3D axis for charts.
  25212. *
  25213. * A 3D axis has the same properties as the regular {@link Ext.chart.axis.Axis axis},
  25214. * plus a notion of depth. The depth of the 3D axis is determined automatically
  25215. * based on the depth of the bound series.
  25216. *
  25217. * This type of axis has the following limitations compared to the regular axis class:
  25218. * - supported {@link Ext.chart.axis.Axis#position positions} are 'left' and 'bottom' only;
  25219. * - floating axes are not supported.
  25220. *
  25221. * At the present moment only {@link Ext.chart.series.Bar3D} series can make use of the 3D axis.
  25222. */
  25223. Ext.define('Ext.chart.axis.Axis3D', {
  25224. extend: 'Ext.chart.axis.Axis',
  25225. xtype: 'axis3d',
  25226. requires: [
  25227. 'Ext.chart.axis.sprite.Axis3D'
  25228. ],
  25229. config: {
  25230. /**
  25231. * @private
  25232. * The depth of the axis. Determined automatically.
  25233. */
  25234. depth: 0
  25235. },
  25236. /**
  25237. * @cfg {String} position
  25238. * Where to set the axis. Available options are `left` and `bottom`.
  25239. */
  25240. onSeriesChange: function(chart) {
  25241. var me = this,
  25242. eventName = 'depthchange',
  25243. listenerName = 'onSeriesDepthChange',
  25244. i, series;
  25245. function toggle(action) {
  25246. var boundSeries = me.boundSeries;
  25247. for (i = 0; i < boundSeries.length; i++) {
  25248. series = boundSeries[i];
  25249. series[action](eventName, listenerName, me);
  25250. }
  25251. }
  25252. // Remove 'depthchange' listeners from old bound series, if any.
  25253. toggle('un');
  25254. me.callParent(arguments);
  25255. // Add 'depthchange' listeners to new bound series.
  25256. toggle('on');
  25257. },
  25258. onSeriesDepthChange: function(series, depth) {
  25259. var me = this,
  25260. maxDepth = depth,
  25261. boundSeries = me.boundSeries,
  25262. i, item;
  25263. if (depth > me.getDepth()) {
  25264. maxDepth = depth;
  25265. } else {
  25266. for (i = 0; i < boundSeries.length; i++) {
  25267. item = boundSeries[i];
  25268. if (item !== series && item.getDepth) {
  25269. depth = item.getDepth();
  25270. if (depth > maxDepth) {
  25271. maxDepth = depth;
  25272. }
  25273. }
  25274. }
  25275. }
  25276. me.setDepth(maxDepth);
  25277. },
  25278. updateDepth: function(depth) {
  25279. var me = this,
  25280. sprites = me.getSprites(),
  25281. attr = {
  25282. depth: depth
  25283. };
  25284. if (sprites && sprites.length) {
  25285. sprites[0].setAttributes(attr);
  25286. }
  25287. if (me.gridSpriteEven && me.gridSpriteOdd) {
  25288. me.gridSpriteEven.getTemplate().setAttributes(attr);
  25289. me.gridSpriteOdd.getTemplate().setAttributes(attr);
  25290. }
  25291. },
  25292. getGridAlignment: function() {
  25293. switch (this.getPosition()) {
  25294. case 'left':
  25295. case 'right':
  25296. return 'horizontal3d';
  25297. case 'top':
  25298. case 'bottom':
  25299. return 'vertical3d';
  25300. }
  25301. }
  25302. });
  25303. /**
  25304. * @class Ext.chart.axis.Category
  25305. * @extends Ext.chart.axis.Axis
  25306. *
  25307. * A type of axis that displays items in categories. This axis is generally used to
  25308. * display categorical information like names of items, month names, quarters, etc.
  25309. * but no quantitative values. For that other type of information {@link Ext.chart.axis.Numeric Numeric}
  25310. * axis are more suitable.
  25311. *
  25312. * As with other axis you can set the position of the axis and its title. For example:
  25313. *
  25314. * @example
  25315. * Ext.create({
  25316. * xtype: 'cartesian',
  25317. * renderTo: document.body,
  25318. * width: 600,
  25319. * height: 400,
  25320. * innerPadding: '0 40 0 40',
  25321. * store: {
  25322. * fields: ['name', 'data1', 'data2', 'data3'],
  25323. * data: [{
  25324. * 'name': 'metric one',
  25325. * 'data1': 10,
  25326. * 'data2': 12,
  25327. * 'data3': 14
  25328. * }, {
  25329. * 'name': 'metric two',
  25330. * 'data1': 7,
  25331. * 'data2': 8,
  25332. * 'data3': 16
  25333. * }, {
  25334. * 'name': 'metric three',
  25335. * 'data1': 5,
  25336. * 'data2': 2,
  25337. * 'data3': 14
  25338. * }, {
  25339. * 'name': 'metric four',
  25340. * 'data1': 2,
  25341. * 'data2': 14,
  25342. * 'data3': 6
  25343. * }, {
  25344. * 'name': 'metric five',
  25345. * 'data1': 27,
  25346. * 'data2': 38,
  25347. * 'data3': 36
  25348. * }]
  25349. * },
  25350. * axes: {
  25351. * type: 'category',
  25352. * position: 'bottom',
  25353. * fields: ['name'],
  25354. * title: {
  25355. * text: 'Sample Values',
  25356. * fontSize: 15
  25357. * }
  25358. * },
  25359. * series: {
  25360. * type: 'area',
  25361. * subStyle: {
  25362. * fill: ['#0A3F50', '#30BDA7', '#96D4C6']
  25363. * },
  25364. * xField: 'name',
  25365. * yField: ['data1', 'data2', 'data3']
  25366. * }
  25367. * });
  25368. *
  25369. * In this example with set the category axis to the bottom of the surface, bound the axis to
  25370. * the `name` property and set as title "Sample Values".
  25371. */
  25372. Ext.define('Ext.chart.axis.Category', {
  25373. requires: [
  25374. 'Ext.chart.axis.layout.CombineDuplicate',
  25375. 'Ext.chart.axis.segmenter.Names'
  25376. ],
  25377. extend: 'Ext.chart.axis.Axis',
  25378. alias: 'axis.category',
  25379. type: 'category',
  25380. isCategory: true,
  25381. config: {
  25382. layout: 'combineDuplicate',
  25383. segmenter: 'names'
  25384. }
  25385. });
  25386. /**
  25387. * Category 3D Axis
  25388. */
  25389. Ext.define('Ext.chart.axis.Category3D', {
  25390. requires: [
  25391. 'Ext.chart.axis.layout.CombineDuplicate',
  25392. 'Ext.chart.axis.segmenter.Names'
  25393. ],
  25394. extend: 'Ext.chart.axis.Axis3D',
  25395. alias: 'axis.category3d',
  25396. type: 'category3d',
  25397. config: {
  25398. layout: 'combineDuplicate',
  25399. segmenter: 'names'
  25400. }
  25401. });
  25402. /**
  25403. * @class Ext.chart.axis.Numeric
  25404. * @extends Ext.chart.axis.Axis
  25405. *
  25406. * An axis to handle numeric values. This axis is used for quantitative data as
  25407. * opposed to the category axis. You can set minimum and maximum values to the
  25408. * axis so that the values are bound to that. If no values are set, then the
  25409. * scale will auto-adjust to the values.
  25410. *
  25411. * @example
  25412. * Ext.create({
  25413. * xtype: 'cartesian',
  25414. * renderTo: document.body,
  25415. * width: 600,
  25416. * height: 400,
  25417. * store: {
  25418. * fields: ['name', 'data1', 'data2', 'data3'],
  25419. * data: [{
  25420. * 'name': 1,
  25421. * 'data1': 10,
  25422. * 'data2': 12,
  25423. * 'data3': 14
  25424. * }, {
  25425. * 'name': 2,
  25426. * 'data1': 7,
  25427. * 'data2': 8,
  25428. * 'data3': 16
  25429. * }, {
  25430. * 'name': 3,
  25431. * 'data1': 5,
  25432. * 'data2': 2,
  25433. * 'data3': 14
  25434. * }, {
  25435. * 'name': 4,
  25436. * 'data1': 2,
  25437. * 'data2': 14,
  25438. * 'data3': 6
  25439. * }, {
  25440. * 'name': 5,
  25441. * 'data1': 27,
  25442. * 'data2': 38,
  25443. * 'data3': 36
  25444. * }]
  25445. * },
  25446. * axes: {
  25447. * type: 'numeric',
  25448. * position: 'left',
  25449. * minimum: 0,
  25450. * fields: ['data1', 'data2', 'data3'],
  25451. * title: 'Sample Values',
  25452. * grid: {
  25453. * odd: {
  25454. * opacity: 1,
  25455. * fill: '#F2F2F2',
  25456. * stroke: '#DDD',
  25457. * 'lineWidth': 1
  25458. * }
  25459. * }
  25460. * },
  25461. * series: {
  25462. * type: 'area',
  25463. * subStyle: {
  25464. * fill: ['#0A3F50', '#30BDA7', '#96D4C6']
  25465. * },
  25466. * xField: 'name',
  25467. * yField: ['data1', 'data2', 'data3']
  25468. * }
  25469. * });
  25470. *
  25471. * In this example we create an axis of Numeric type. We set a minimum value so that
  25472. * even if all series have values greater than zero, the grid starts at zero. We bind
  25473. * the axis onto the left part of the surface by setting _position_ to _left_.
  25474. * We bind three different store fields to this axis by setting _fields_ to an array.
  25475. * We set the title of the axis to _Number of Hits_ by using the _title_ property.
  25476. * We use a _grid_ configuration to set odd background rows to a certain style and even rows
  25477. * to be transparent/ignored.
  25478. *
  25479. */
  25480. Ext.define('Ext.chart.axis.Numeric', {
  25481. extend: 'Ext.chart.axis.Axis',
  25482. type: 'numeric',
  25483. alias: [
  25484. 'axis.numeric',
  25485. 'axis.radial'
  25486. ],
  25487. // legacy charts compatibility
  25488. requires: [
  25489. 'Ext.chart.axis.layout.Continuous',
  25490. 'Ext.chart.axis.segmenter.Numeric'
  25491. ],
  25492. config: {
  25493. layout: 'continuous',
  25494. segmenter: 'numeric',
  25495. aggregator: 'double'
  25496. }
  25497. });
  25498. /**
  25499. * @class Ext.chart.axis.Numeric3D
  25500. */
  25501. Ext.define('Ext.chart.axis.Numeric3D', {
  25502. extend: 'Ext.chart.axis.Axis3D',
  25503. alias: [
  25504. 'axis.numeric3d'
  25505. ],
  25506. type: 'numeric3d',
  25507. requires: [
  25508. 'Ext.chart.axis.layout.Continuous',
  25509. 'Ext.chart.axis.segmenter.Numeric'
  25510. ],
  25511. config: {
  25512. layout: 'continuous',
  25513. segmenter: 'numeric',
  25514. aggregator: 'double'
  25515. }
  25516. });
  25517. /**
  25518. * @class Ext.chart.axis.Time
  25519. * @extends Ext.chart.axis.Numeric
  25520. *
  25521. * A type of axis whose units are measured in time values. Use this axis
  25522. * for listing dates that you will want to group or dynamically change.
  25523. * If you just want to display dates as categories then use the
  25524. * Category class for axis instead.
  25525. *
  25526. * @example
  25527. * Ext.create({
  25528. * xtype: 'cartesian',
  25529. * renderTo: document.body,
  25530. * width: 600,
  25531. * height: 400,
  25532. * store: {
  25533. * fields: ['time', 'open', 'high', 'low', 'close'],
  25534. * data: [{
  25535. * 'time': new Date('Jan 1 2010').getTime(),
  25536. * 'open': 600,
  25537. * 'high': 614,
  25538. * 'low': 578,
  25539. * 'close': 590
  25540. * }, {
  25541. * 'time': new Date('Jan 2 2010').getTime(),
  25542. * 'open': 590,
  25543. * 'high': 609,
  25544. * 'low': 580,
  25545. * 'close': 580
  25546. * }, {
  25547. * 'time': new Date('Jan 3 2010').getTime(),
  25548. * 'open': 580,
  25549. * 'high': 602,
  25550. * 'low': 578,
  25551. * 'close': 602
  25552. * }, {
  25553. * 'time': new Date('Jan 4 2010').getTime(),
  25554. * 'open': 602,
  25555. * 'high': 614,
  25556. * 'low': 586,
  25557. * 'close': 586
  25558. * }]
  25559. * },
  25560. * axes: [{
  25561. * type: 'numeric',
  25562. * position: 'left',
  25563. * fields: ['open', 'high', 'low', 'close'],
  25564. * title: {
  25565. * text: 'Sample Values',
  25566. * fontSize: 15
  25567. * },
  25568. * grid: true,
  25569. * minimum: 560,
  25570. * maximum: 640
  25571. * }, {
  25572. * type: 'time',
  25573. * position: 'bottom',
  25574. * fields: ['time'],
  25575. * fromDate: new Date('Dec 31 2009'),
  25576. * toDate: new Date('Jan 5 2010'),
  25577. * title: {
  25578. * text: 'Sample Values',
  25579. * fontSize: 15
  25580. * },
  25581. * style: {
  25582. * axisLine: false
  25583. * }
  25584. * }],
  25585. * series: {
  25586. * type: 'candlestick',
  25587. * xField: 'time',
  25588. * openField: 'open',
  25589. * highField: 'high',
  25590. * lowField: 'low',
  25591. * closeField: 'close',
  25592. * style: {
  25593. * ohlcType: 'ohlc',
  25594. * dropStyle: {
  25595. * fill: 'rgb(255, 128, 128)',
  25596. * stroke: 'rgb(255, 128, 128)',
  25597. * lineWidth: 3
  25598. * },
  25599. * raiseStyle: {
  25600. * fill: 'rgb(48, 189, 167)',
  25601. * stroke: 'rgb(48, 189, 167)',
  25602. * lineWidth: 3
  25603. * }
  25604. * }
  25605. * }
  25606. * });
  25607. */
  25608. Ext.define('Ext.chart.axis.Time', {
  25609. extend: 'Ext.chart.axis.Numeric',
  25610. alias: 'axis.time',
  25611. type: 'time',
  25612. requires: [
  25613. 'Ext.chart.axis.layout.Continuous',
  25614. 'Ext.chart.axis.segmenter.Time'
  25615. ],
  25616. config: {
  25617. /**
  25618. * @cfg {String} dateFormat
  25619. * Indicates the format the date will be rendered in.
  25620. * For example: 'M d' will render the dates as 'Jan 30'.
  25621. * This config works by setting the {@link #renderer} config
  25622. * to a function that uses {@link Ext.Date#format} to format the dates
  25623. * using the given `dateFormat`.
  25624. * If the {@link #renderer} config was set by the user, changes to this config
  25625. * won't replace the user set renderer (until the user removes the renderer by
  25626. * setting the `renderer` config to `null`). In this case the way the `dateFormat`
  25627. * is used (if at all) is up to the user.
  25628. */
  25629. dateFormat: null,
  25630. /**
  25631. * @cfg {Date} fromDate The starting date for the time axis.
  25632. */
  25633. fromDate: null,
  25634. /**
  25635. * @cfg {Date} toDate The ending date for the time axis.
  25636. */
  25637. toDate: null,
  25638. layout: 'continuous',
  25639. segmenter: 'time',
  25640. aggregator: 'time'
  25641. },
  25642. updateDateFormat: function(format) {
  25643. var renderer = this.getRenderer();
  25644. if (!renderer || renderer.isDefault) {
  25645. renderer = function(axis, date) {
  25646. return Ext.Date.format(new Date(date), format);
  25647. };
  25648. renderer.isDefault = true;
  25649. this.setRenderer(renderer);
  25650. this.performLayout();
  25651. }
  25652. },
  25653. updateRenderer: function(renderer) {
  25654. var dateFormat = this.getDateFormat();
  25655. if (renderer) {
  25656. this.performLayout();
  25657. } else if (dateFormat) {
  25658. // If the user removes custom `renderer` and `dateFormat` is set,
  25659. // set the `renderer` to the default one based on `dateFormat`.
  25660. this.updateDateFormat(dateFormat);
  25661. }
  25662. },
  25663. updateFromDate: function(date) {
  25664. this.setMinimum(+date);
  25665. },
  25666. updateToDate: function(date) {
  25667. this.setMaximum(+date);
  25668. },
  25669. getCoordFor: function(value) {
  25670. if (Ext.isString(value)) {
  25671. value = new Date(value);
  25672. }
  25673. return +value;
  25674. }
  25675. });
  25676. /**
  25677. * @class Ext.chart.axis.Time3D
  25678. */
  25679. Ext.define('Ext.chart.axis.Time3D', {
  25680. extend: 'Ext.chart.axis.Numeric3D',
  25681. alias: 'axis.time3d',
  25682. type: 'time3d',
  25683. requires: [
  25684. 'Ext.chart.axis.layout.Continuous',
  25685. 'Ext.chart.axis.segmenter.Time'
  25686. ],
  25687. config: {
  25688. /**
  25689. * @cfg {String/Boolean} dateFormat
  25690. * Indicates the format the date will be rendered on.
  25691. * For example: 'M d' will render the dates as 'Jan 30', etc.
  25692. */
  25693. dateFormat: null,
  25694. /**
  25695. * @cfg {Date} fromDate The starting date for the time axis.
  25696. */
  25697. fromDate: null,
  25698. /**
  25699. * @cfg {Date} toDate The ending date for the time axis.
  25700. */
  25701. toDate: null,
  25702. layout: 'continuous',
  25703. segmenter: 'time',
  25704. aggregator: 'time'
  25705. },
  25706. updateDateFormat: function(format) {
  25707. this.setRenderer(function(axis, date) {
  25708. return Ext.Date.format(new Date(date), format);
  25709. });
  25710. },
  25711. updateFromDate: function(date) {
  25712. this.setMinimum(+date);
  25713. },
  25714. updateToDate: function(date) {
  25715. this.setMaximum(+date);
  25716. },
  25717. getCoordFor: function(value) {
  25718. if (Ext.isString(value)) {
  25719. value = new Date(value);
  25720. }
  25721. return +value;
  25722. }
  25723. });
  25724. /**
  25725. * @class Ext.chart.grid.HorizontalGrid3D
  25726. * @extends Ext.chart.grid.HorizontalGrid
  25727. *
  25728. * Horizontal 3D Grid sprite. Used in 3D Cartesian Charts.
  25729. */
  25730. Ext.define('Ext.chart.grid.HorizontalGrid3D', {
  25731. extend: 'Ext.chart.grid.HorizontalGrid',
  25732. alias: 'grid.horizontal3d',
  25733. inheritableStatics: {
  25734. def: {
  25735. processors: {
  25736. depth: 'number'
  25737. },
  25738. defaults: {
  25739. depth: 0
  25740. }
  25741. }
  25742. },
  25743. render: function(surface, ctx, rect) {
  25744. var attr = this.attr,
  25745. x = surface.roundPixel(attr.x),
  25746. y = surface.roundPixel(attr.y),
  25747. dx = surface.matrix.getDX(),
  25748. halfLineWidth = ctx.lineWidth * 0.5,
  25749. height = attr.height,
  25750. depth = attr.depth,
  25751. left, top;
  25752. if (y <= rect[1]) {
  25753. return;
  25754. }
  25755. // Horizontal stripe.
  25756. left = rect[0] + depth - dx;
  25757. top = y + halfLineWidth - depth;
  25758. ctx.beginPath();
  25759. ctx.rect(left, top, rect[2], height);
  25760. ctx.fill();
  25761. // Horizontal line.
  25762. ctx.beginPath();
  25763. ctx.moveTo(left, top);
  25764. ctx.lineTo(left + rect[2], top);
  25765. ctx.stroke();
  25766. // Diagonal stripe.
  25767. left = rect[0] + x - dx;
  25768. top = y + halfLineWidth;
  25769. ctx.beginPath();
  25770. ctx.moveTo(left, top);
  25771. ctx.lineTo(left + depth, top - depth);
  25772. ctx.lineTo(left + depth, top - depth + height);
  25773. ctx.lineTo(left, top + height);
  25774. ctx.closePath();
  25775. ctx.fill();
  25776. // Diagonal line.
  25777. ctx.beginPath();
  25778. ctx.moveTo(left, top);
  25779. ctx.lineTo(left + depth, top - depth);
  25780. ctx.stroke();
  25781. }
  25782. });
  25783. /**
  25784. * @class Ext.chart.grid.VerticalGrid3D
  25785. * @extends Ext.chart.grid.VerticalGrid
  25786. *
  25787. * Vertical 3D Grid sprite. Used in 3D Cartesian Charts.
  25788. */
  25789. Ext.define('Ext.chart.grid.VerticalGrid3D', {
  25790. extend: 'Ext.chart.grid.VerticalGrid',
  25791. alias: 'grid.vertical3d',
  25792. inheritableStatics: {
  25793. def: {
  25794. processors: {
  25795. depth: 'number'
  25796. },
  25797. defaults: {
  25798. depth: 0
  25799. }
  25800. }
  25801. },
  25802. render: function(surface, ctx, clipRect) {
  25803. var attr = this.attr,
  25804. x = surface.roundPixel(attr.x),
  25805. dy = surface.matrix.getDY(),
  25806. halfLineWidth = ctx.lineWidth * 0.5,
  25807. width = attr.width,
  25808. depth = attr.depth,
  25809. left, top;
  25810. if (x >= clipRect[2]) {
  25811. return;
  25812. }
  25813. // Vertical stripe.
  25814. left = x - halfLineWidth + depth;
  25815. top = clipRect[1] - depth - dy;
  25816. ctx.beginPath();
  25817. ctx.rect(left, top, width, clipRect[3]);
  25818. ctx.fill();
  25819. // Vertical line.
  25820. ctx.beginPath();
  25821. ctx.moveTo(left, top);
  25822. ctx.lineTo(left, top + clipRect[3]);
  25823. ctx.stroke();
  25824. // Diagonal stripe.
  25825. left = x - halfLineWidth;
  25826. top = clipRect[3];
  25827. ctx.beginPath();
  25828. ctx.moveTo(left, top);
  25829. ctx.lineTo(left + depth, top - depth);
  25830. ctx.lineTo(left + depth + width, top - depth);
  25831. ctx.lineTo(left + width, top);
  25832. ctx.closePath();
  25833. ctx.fill();
  25834. // Diagonal line.
  25835. left = x - halfLineWidth;
  25836. top = clipRect[3];
  25837. ctx.beginPath();
  25838. ctx.moveTo(left, top);
  25839. ctx.lineTo(left + depth, top - depth);
  25840. ctx.stroke();
  25841. }
  25842. });
  25843. /**
  25844. * @class Ext.chart.interactions.CrossZoom
  25845. * @extends Ext.chart.interactions.Abstract
  25846. *
  25847. * The CrossZoom interaction allows the user to zoom in on a selected area of the chart.
  25848. *
  25849. * @example
  25850. * Ext.create({
  25851. * xtype: 'cartesian',
  25852. * renderTo: Ext.getBody(),
  25853. * width: 600,
  25854. * height: 400,
  25855. * insetPadding: 40,
  25856. * interactions: 'crosszoom',
  25857. * store: {
  25858. * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
  25859. * data: [{
  25860. * 'name': 'metric one',
  25861. * 'data1': 10,
  25862. * 'data2': 12,
  25863. * 'data3': 14,
  25864. * 'data4': 8,
  25865. * 'data5': 13
  25866. * }, {
  25867. * 'name': 'metric two',
  25868. * 'data1': 7,
  25869. * 'data2': 8,
  25870. * 'data3': 16,
  25871. * 'data4': 10,
  25872. * 'data5': 3
  25873. * }, {
  25874. * 'name': 'metric three',
  25875. * 'data1': 5,
  25876. * 'data2': 2,
  25877. * 'data3': 14,
  25878. * 'data4': 12,
  25879. * 'data5': 7
  25880. * }, {
  25881. * 'name': 'metric four',
  25882. * 'data1': 2,
  25883. * 'data2': 14,
  25884. * 'data3': 6,
  25885. * 'data4': 1,
  25886. * 'data5': 23
  25887. * }, {
  25888. * 'name': 'metric five',
  25889. * 'data1': 27,
  25890. * 'data2': 38,
  25891. * 'data3': 36,
  25892. * 'data4': 13,
  25893. * 'data5': 33
  25894. * }]
  25895. * },
  25896. * axes: [{
  25897. * type: 'numeric',
  25898. * position: 'left',
  25899. * fields: ['data1'],
  25900. * title: {
  25901. * text: 'Sample Values',
  25902. * fontSize: 15
  25903. * },
  25904. * grid: true,
  25905. * minimum: 0
  25906. * }, {
  25907. * type: 'category',
  25908. * position: 'bottom',
  25909. * fields: ['name'],
  25910. * title: {
  25911. * text: 'Sample Values',
  25912. * fontSize: 15
  25913. * }
  25914. * }],
  25915. * series: [{
  25916. * type: 'line',
  25917. * highlight: {
  25918. * size: 7,
  25919. * radius: 7
  25920. * },
  25921. * style: {
  25922. * stroke: 'rgb(143,203,203)'
  25923. * },
  25924. * xField: 'name',
  25925. * yField: 'data1',
  25926. * marker: {
  25927. * type: 'path',
  25928. * path: ['M', - 2, 0, 0, 2, 2, 0, 0, - 2, 'Z'],
  25929. * stroke: 'blue',
  25930. * lineWidth: 0
  25931. * }
  25932. * }, {
  25933. * type: 'line',
  25934. * highlight: {
  25935. * size: 7,
  25936. * radius: 7
  25937. * },
  25938. * fill: true,
  25939. * xField: 'name',
  25940. * yField: 'data3',
  25941. * marker: {
  25942. * type: 'circle',
  25943. * radius: 4,
  25944. * lineWidth: 0
  25945. * }
  25946. * }]
  25947. * });
  25948. */
  25949. Ext.define('Ext.chart.interactions.CrossZoom', {
  25950. extend: 'Ext.chart.interactions.Abstract',
  25951. type: 'crosszoom',
  25952. alias: 'interaction.crosszoom',
  25953. isCrossZoom: true,
  25954. config: {
  25955. /**
  25956. * @cfg {Object/Array} axes
  25957. * Specifies which axes should be made navigable. The config value can take the following formats:
  25958. *
  25959. * - An Object whose keys correspond to the {@link Ext.chart.axis.Axis#position position} of each
  25960. * axis that should be made navigable. Each key's value can either be an Object with further
  25961. * configuration options for each axis or simply `true` for a default set of options.
  25962. * {
  25963. * type: 'crosszoom',
  25964. * axes: {
  25965. * left: {
  25966. * maxZoom: 5,
  25967. * allowPan: false
  25968. * },
  25969. * bottom: true
  25970. * }
  25971. * }
  25972. *
  25973. * If using the full Object form, the following options can be specified for each axis:
  25974. *
  25975. * - minZoom (Number) A minimum zoom level for the axis. Defaults to `1` which is its natural size.
  25976. * - maxZoom (Number) A maximum zoom level for the axis. Defaults to `10`.
  25977. * - startZoom (Number) A starting zoom level for the axis. Defaults to `1`.
  25978. * - allowZoom (Boolean) Whether zooming is allowed for the axis. Defaults to `true`.
  25979. * - allowPan (Boolean) Whether panning is allowed for the axis. Defaults to `true`.
  25980. * - startPan (Boolean) A starting panning offset for the axis. Defaults to `0`.
  25981. *
  25982. * - An Array of strings, each one corresponding to the {@link Ext.chart.axis.Axis#position position}
  25983. * of an axis that should be made navigable. The default options will be used for each named axis.
  25984. *
  25985. * {
  25986. * type: 'crosszoom',
  25987. * axes: ['left', 'bottom']
  25988. * }
  25989. *
  25990. * If the `axes` config is not specified, it will default to making all axes navigable with the
  25991. * default axis options.
  25992. */
  25993. axes: true,
  25994. gestures: {
  25995. dragstart: 'onGestureStart',
  25996. drag: 'onGesture',
  25997. dragend: 'onGestureEnd',
  25998. dblclick: 'onDoubleTap'
  25999. },
  26000. undoButton: {}
  26001. },
  26002. stopAnimationBeforeSync: false,
  26003. zoomAnimationInProgress: false,
  26004. constructor: function() {
  26005. this.callParent(arguments);
  26006. this.zoomHistory = [];
  26007. },
  26008. applyAxes: function(axesConfig) {
  26009. var result = {};
  26010. if (axesConfig === true) {
  26011. return {
  26012. top: {},
  26013. right: {},
  26014. bottom: {},
  26015. left: {}
  26016. };
  26017. } else if (Ext.isArray(axesConfig)) {
  26018. // array of axis names - translate to full object form
  26019. result = {};
  26020. Ext.each(axesConfig, function(axis) {
  26021. result[axis] = {};
  26022. });
  26023. } else if (Ext.isObject(axesConfig)) {
  26024. Ext.iterate(axesConfig, function(key, val) {
  26025. // axis name with `true` value -> translate to object
  26026. if (val === true) {
  26027. result[key] = {};
  26028. } else if (val !== false) {
  26029. result[key] = val;
  26030. }
  26031. });
  26032. }
  26033. return result;
  26034. },
  26035. applyUndoButton: function(button, oldButton) {
  26036. var me = this;
  26037. if (oldButton) {
  26038. oldButton.destroy();
  26039. }
  26040. if (button) {
  26041. return Ext.create('Ext.Button', Ext.apply({
  26042. cls: [],
  26043. text: 'Undo Zoom',
  26044. disabled: true,
  26045. handler: function() {
  26046. me.undoZoom();
  26047. }
  26048. }, button));
  26049. }
  26050. },
  26051. getSurface: function() {
  26052. return this.getChart() && this.getChart().getSurface('overlay');
  26053. },
  26054. setSeriesOpacity: function(opacity) {
  26055. var surface = this.getChart() && this.getChart().getSurface('series');
  26056. if (surface) {
  26057. surface.element.setStyle('opacity', opacity);
  26058. }
  26059. },
  26060. onGestureStart: function(e) {
  26061. var me = this,
  26062. chart = me.getChart(),
  26063. surface = me.getSurface(),
  26064. rect = chart.getInnerRect(),
  26065. innerPadding = chart.getInnerPadding(),
  26066. minX = innerPadding.left,
  26067. maxX = minX + rect[2],
  26068. minY = innerPadding.top,
  26069. maxY = minY + rect[3],
  26070. xy = chart.getEventXY(e),
  26071. x = xy[0],
  26072. y = xy[1];
  26073. e.claimGesture();
  26074. if (me.zoomAnimationInProgress) {
  26075. return;
  26076. }
  26077. if (x > minX && x < maxX && y > minY && y < maxY) {
  26078. me.gestureEvent = 'drag';
  26079. me.lockEvents(me.gestureEvent);
  26080. me.startX = x;
  26081. me.startY = y;
  26082. me.selectionRect = surface.add({
  26083. type: 'rect',
  26084. globalAlpha: 0.5,
  26085. fillStyle: 'rgba(80,80,140,0.5)',
  26086. strokeStyle: 'rgba(80,80,140,1)',
  26087. lineWidth: 2,
  26088. x: x,
  26089. y: y,
  26090. width: 0,
  26091. height: 0,
  26092. zIndex: 10000
  26093. });
  26094. me.setSeriesOpacity(0.8);
  26095. return false;
  26096. }
  26097. },
  26098. onGesture: function(e) {
  26099. var me = this;
  26100. if (me.zoomAnimationInProgress) {
  26101. return;
  26102. }
  26103. if (me.getLocks()[me.gestureEvent] === me) {
  26104. var chart = me.getChart(),
  26105. surface = me.getSurface(),
  26106. rect = chart.getInnerRect(),
  26107. innerPadding = chart.getInnerPadding(),
  26108. minX = innerPadding.left,
  26109. maxX = minX + rect[2],
  26110. minY = innerPadding.top,
  26111. maxY = minY + rect[3],
  26112. xy = chart.getEventXY(e),
  26113. x = xy[0],
  26114. y = xy[1];
  26115. if (x < minX) {
  26116. x = minX;
  26117. } else if (x > maxX) {
  26118. x = maxX;
  26119. }
  26120. if (y < minY) {
  26121. y = minY;
  26122. } else if (y > maxY) {
  26123. y = maxY;
  26124. }
  26125. me.selectionRect.setAttributes({
  26126. width: x - me.startX,
  26127. height: y - me.startY
  26128. });
  26129. if (Math.abs(me.startX - x) < 11 || Math.abs(me.startY - y) < 11) {
  26130. me.selectionRect.setAttributes({
  26131. globalAlpha: 0.5
  26132. });
  26133. } else {
  26134. me.selectionRect.setAttributes({
  26135. globalAlpha: 1
  26136. });
  26137. }
  26138. surface.renderFrame();
  26139. return false;
  26140. }
  26141. },
  26142. onGestureEnd: function(e) {
  26143. var me = this;
  26144. if (me.zoomAnimationInProgress) {
  26145. return;
  26146. }
  26147. if (me.getLocks()[me.gestureEvent] === me) {
  26148. var chart = me.getChart(),
  26149. surface = me.getSurface(),
  26150. rect = chart.getInnerRect(),
  26151. innerPadding = chart.getInnerPadding(),
  26152. minX = innerPadding.left,
  26153. maxX = minX + rect[2],
  26154. minY = innerPadding.top,
  26155. maxY = minY + rect[3],
  26156. rectWidth = rect[2],
  26157. rectHeight = rect[3],
  26158. xy = chart.getEventXY(e),
  26159. x = xy[0],
  26160. y = xy[1];
  26161. if (x < minX) {
  26162. x = minX;
  26163. } else if (x > maxX) {
  26164. x = maxX;
  26165. }
  26166. if (y < minY) {
  26167. y = minY;
  26168. } else if (y > maxY) {
  26169. y = maxY;
  26170. }
  26171. if (Math.abs(me.startX - x) < 11 || Math.abs(me.startY - y) < 11) {
  26172. surface.remove(me.selectionRect);
  26173. } else {
  26174. me.zoomBy([
  26175. Math.min(me.startX, x) / rectWidth,
  26176. 1 - Math.max(me.startY, y) / rectHeight,
  26177. Math.max(me.startX, x) / rectWidth,
  26178. 1 - Math.min(me.startY, y) / rectHeight
  26179. ]);
  26180. me.selectionRect.setAttributes({
  26181. x: Math.min(me.startX, x),
  26182. y: Math.min(me.startY, y),
  26183. width: Math.abs(me.startX - x),
  26184. height: Math.abs(me.startY - y)
  26185. });
  26186. me.selectionRect.setAnimation(chart.getAnimation() || {
  26187. duration: 0
  26188. });
  26189. me.selectionRect.setAttributes({
  26190. globalAlpha: 0,
  26191. x: 0,
  26192. y: 0,
  26193. width: rectWidth,
  26194. height: rectHeight
  26195. });
  26196. me.zoomAnimationInProgress = true;
  26197. chart.suspendThicknessChanged();
  26198. me.selectionRect.getAnimation().on('animationend', function() {
  26199. chart.resumeThicknessChanged();
  26200. surface.remove(me.selectionRect);
  26201. me.selectionRect = null;
  26202. me.zoomAnimationInProgress = false;
  26203. });
  26204. }
  26205. surface.renderFrame();
  26206. me.sync();
  26207. me.unlockEvents(me.gestureEvent);
  26208. me.setSeriesOpacity(1);
  26209. if (!me.zoomAnimationInProgress) {
  26210. surface.remove(me.selectionRect);
  26211. me.selectionRect = null;
  26212. }
  26213. }
  26214. },
  26215. zoomBy: function(rect) {
  26216. var me = this,
  26217. axisConfigs = me.getAxes(),
  26218. chart = me.getChart(),
  26219. axes = chart.getAxes(),
  26220. isRtl = chart.getInherited().rtl,
  26221. config,
  26222. zoomMap = {},
  26223. x1, x2;
  26224. if (isRtl) {
  26225. rect = rect.slice();
  26226. x1 = 1 - rect[0];
  26227. x2 = 1 - rect[2];
  26228. rect[0] = Math.min(x1, x2);
  26229. rect[2] = Math.max(x1, x2);
  26230. }
  26231. for (var i = 0; i < axes.length; i++) {
  26232. var axis = axes[i];
  26233. config = axisConfigs[axis.getPosition()];
  26234. if (config && config.allowZoom !== false) {
  26235. var isSide = axis.isSide(),
  26236. oldRange = axis.getVisibleRange();
  26237. zoomMap[axis.getId()] = oldRange.slice(0);
  26238. if (!isSide) {
  26239. axis.setVisibleRange([
  26240. (oldRange[1] - oldRange[0]) * rect[0] + oldRange[0],
  26241. (oldRange[1] - oldRange[0]) * rect[2] + oldRange[0]
  26242. ]);
  26243. } else {
  26244. axis.setVisibleRange([
  26245. (oldRange[1] - oldRange[0]) * rect[1] + oldRange[0],
  26246. (oldRange[1] - oldRange[0]) * rect[3] + oldRange[0]
  26247. ]);
  26248. }
  26249. }
  26250. }
  26251. me.zoomHistory.push(zoomMap);
  26252. me.getUndoButton().setDisabled(false);
  26253. },
  26254. undoZoom: function() {
  26255. var zoomMap = this.zoomHistory.pop(),
  26256. axes = this.getChart().getAxes();
  26257. if (zoomMap) {
  26258. for (var i = 0; i < axes.length; i++) {
  26259. var axis = axes[i];
  26260. if (zoomMap[axis.getId()]) {
  26261. axis.setVisibleRange(zoomMap[axis.getId()]);
  26262. }
  26263. }
  26264. }
  26265. this.getUndoButton().setDisabled(this.zoomHistory.length === 0);
  26266. this.sync();
  26267. },
  26268. onDoubleTap: function(e) {
  26269. this.undoZoom();
  26270. },
  26271. destroy: function() {
  26272. this.setUndoButton(null);
  26273. this.callParent();
  26274. }
  26275. });
  26276. /**
  26277. * The Crosshair interaction allows the user to get precise values for a specific point on the chart.
  26278. * The values are obtained by single-touch dragging on the chart.
  26279. *
  26280. * @example
  26281. * Ext.create('Ext.Container', {
  26282. * renderTo: Ext.getBody(),
  26283. * width: 600,
  26284. * height: 400,
  26285. * layout: 'fit',
  26286. * items: {
  26287. * xtype: 'cartesian',
  26288. * innerPadding: 20,
  26289. * interactions: {
  26290. * type: 'crosshair',
  26291. * axes: {
  26292. * left: {
  26293. * label: {
  26294. * fillStyle: 'white'
  26295. * },
  26296. * rect: {
  26297. * fillStyle: 'brown',
  26298. * radius: 6
  26299. * }
  26300. * },
  26301. * bottom: {
  26302. * label: {
  26303. * fontSize: '14px',
  26304. * fontWeight: 'bold'
  26305. * }
  26306. * }
  26307. * },
  26308. * lines: {
  26309. * horizontal: {
  26310. * strokeStyle: 'brown',
  26311. * lineWidth: 2,
  26312. * lineDash: [20, 2, 2, 2, 2, 2, 2, 2]
  26313. * }
  26314. * }
  26315. * },
  26316. * store: {
  26317. * fields: ['name', 'data'],
  26318. * data: [
  26319. * {name: 'apple', data: 300},
  26320. * {name: 'orange', data: 900},
  26321. * {name: 'banana', data: 800},
  26322. * {name: 'pear', data: 400},
  26323. * {name: 'grape', data: 500}
  26324. * ]
  26325. * },
  26326. * axes: [{
  26327. * type: 'numeric',
  26328. * position: 'left',
  26329. * fields: ['data'],
  26330. * title: {
  26331. * text: 'Value',
  26332. * fontSize: 15
  26333. * },
  26334. * grid: true,
  26335. * label: {
  26336. * rotationRads: -Math.PI / 4
  26337. * }
  26338. * }, {
  26339. * type: 'category',
  26340. * position: 'bottom',
  26341. * fields: ['name'],
  26342. * title: {
  26343. * text: 'Category',
  26344. * fontSize: 15
  26345. * }
  26346. * }],
  26347. * series: {
  26348. * type: 'line',
  26349. * style: {
  26350. * strokeStyle: 'black'
  26351. * },
  26352. * xField: 'name',
  26353. * yField: 'data',
  26354. * marker: {
  26355. * type: 'circle',
  26356. * radius: 5,
  26357. * fillStyle: 'lightblue'
  26358. * }
  26359. * }
  26360. * }
  26361. * });
  26362. */
  26363. Ext.define('Ext.chart.interactions.Crosshair', {
  26364. extend: 'Ext.chart.interactions.Abstract',
  26365. requires: [
  26366. 'Ext.chart.grid.HorizontalGrid',
  26367. 'Ext.chart.grid.VerticalGrid',
  26368. 'Ext.chart.CartesianChart',
  26369. 'Ext.chart.axis.layout.Discrete'
  26370. ],
  26371. type: 'crosshair',
  26372. alias: 'interaction.crosshair',
  26373. config: {
  26374. /**
  26375. * @cfg {Object} axes
  26376. * Specifies label text and label rect configs on per axis basis or as a single config for all axes.
  26377. *
  26378. * {
  26379. * type: 'crosshair',
  26380. * axes: {
  26381. * label: { fillStyle: 'white' },
  26382. * rect: { fillStyle: 'maroon'}
  26383. * }
  26384. * }
  26385. *
  26386. * In case per axis configuration is used, an object with keys corresponding
  26387. * to the {@link Ext.chart.axis.Axis#position position} must be provided.
  26388. *
  26389. * {
  26390. * type: 'crosshair',
  26391. * axes: {
  26392. * left: {
  26393. * label: { fillStyle: 'white' },
  26394. * rect: {
  26395. * fillStyle: 'maroon',
  26396. * radius: 4
  26397. * }
  26398. * },
  26399. * bottom: {
  26400. * label: {
  26401. * fontSize: '14px',
  26402. * fontWeight: 'bold'
  26403. * },
  26404. * rect: { fillStyle: 'white' }
  26405. * }
  26406. * }
  26407. *
  26408. * If the `axes` config is not specified, the following defaults will be used:
  26409. * - `label` will use values from the {@link Ext.chart.axis.Axis#label label} config.
  26410. * - `rect` will use the 'white' fillStyle.
  26411. */
  26412. axes: {
  26413. top: {
  26414. label: {},
  26415. rect: {}
  26416. },
  26417. right: {
  26418. label: {},
  26419. rect: {}
  26420. },
  26421. bottom: {
  26422. label: {},
  26423. rect: {}
  26424. },
  26425. left: {
  26426. label: {},
  26427. rect: {}
  26428. }
  26429. },
  26430. /**
  26431. * @cfg {Object} lines
  26432. * Specifies attributes of horizontal and vertical lines that make up the crosshair.
  26433. * If this config is missing, black dashed lines will be used.
  26434. *
  26435. * {
  26436. * horizontal: {
  26437. * strokeStyle: 'red',
  26438. * lineDash: [] // solid line
  26439. * },
  26440. * vertical: {
  26441. * lineWidth: 2,
  26442. * lineDash: [15, 5, 5, 5]
  26443. * }
  26444. * }
  26445. */
  26446. lines: {
  26447. horizontal: {
  26448. strokeStyle: 'black',
  26449. lineDash: [
  26450. 5,
  26451. 5
  26452. ]
  26453. },
  26454. vertical: {
  26455. strokeStyle: 'black',
  26456. lineDash: [
  26457. 5,
  26458. 5
  26459. ]
  26460. }
  26461. },
  26462. /**
  26463. * @cfg {String} gesture
  26464. * Specifies which gesture should be used for starting/maintaining/ending the interaction.
  26465. */
  26466. gesture: 'drag'
  26467. },
  26468. applyAxes: function(axesConfig, oldAxesConfig) {
  26469. return Ext.merge(oldAxesConfig || {}, axesConfig);
  26470. },
  26471. applyLines: function(linesConfig, oldLinesConfig) {
  26472. return Ext.merge(oldLinesConfig || {}, linesConfig);
  26473. },
  26474. updateChart: function(chart) {
  26475. if (chart && !chart.isCartesian) {
  26476. Ext.raise("Crosshair interaction can only be used on cartesian charts.");
  26477. }
  26478. this.callParent(arguments);
  26479. },
  26480. getGestures: function() {
  26481. var me = this,
  26482. gestures = {},
  26483. gesture = me.getGesture();
  26484. gestures[gesture] = 'onGesture';
  26485. gestures[gesture + 'start'] = 'onGestureStart';
  26486. gestures[gesture + 'end'] = 'onGestureEnd';
  26487. gestures[gesture + 'cancel'] = 'onGestureCancel';
  26488. return gestures;
  26489. },
  26490. onGestureStart: function(e) {
  26491. var me = this,
  26492. chart = me.getChart(),
  26493. axesTheme = chart.getTheme().getAxis(),
  26494. axisTheme,
  26495. surface = chart.getSurface('overlay'),
  26496. rect = chart.getInnerRect(),
  26497. chartWidth = rect[2],
  26498. chartHeight = rect[3],
  26499. xy = chart.getEventXY(e),
  26500. x = xy[0],
  26501. y = xy[1],
  26502. axes = chart.getAxes(),
  26503. axesConfig = me.getAxes(),
  26504. linesConfig = me.getLines(),
  26505. axis, axisSurface, axisRect, axisWidth, axisHeight, axisPosition, axisAlignment, axisLabel, axisLabelConfig, crosshairLabelConfig, tickPadding, axisSprite, attr, axisThickness, lineWidth, halfLineWidth, title, titleBBox, horizontalLineCfg, verticalLineCfg, i;
  26506. e.claimGesture();
  26507. if (x > 0 && x < chartWidth && y > 0 && y < chartHeight) {
  26508. me.lockEvents(me.getGesture());
  26509. horizontalLineCfg = Ext.apply({
  26510. xclass: 'Ext.chart.grid.HorizontalGrid',
  26511. x: 0,
  26512. y: y,
  26513. width: chartWidth
  26514. }, linesConfig.horizontal);
  26515. verticalLineCfg = Ext.apply({
  26516. xclass: 'Ext.chart.grid.VerticalGrid',
  26517. x: x,
  26518. y: 0,
  26519. height: chartHeight
  26520. }, linesConfig.vertical);
  26521. me.axesLabels = me.axesLabels || {};
  26522. for (i = 0; i < axes.length; i++) {
  26523. axis = axes[i];
  26524. axisSurface = axis.getSurface();
  26525. axisRect = axisSurface.getRect();
  26526. axisSprite = axis.getSprites()[0];
  26527. axisWidth = axisRect[2];
  26528. axisHeight = axisRect[3];
  26529. axisPosition = axis.getPosition();
  26530. axisAlignment = axis.getAlignment();
  26531. title = axis.getTitle();
  26532. titleBBox = title && title.attr.text !== '' && title.getBBox();
  26533. attr = axisSprite.attr;
  26534. axisThickness = axisSprite.thickness;
  26535. lineWidth = attr.axisLine ? attr.lineWidth : 0;
  26536. halfLineWidth = lineWidth / 2;
  26537. tickPadding = Math.max(attr.majorTickSize, attr.minorTickSize) + lineWidth;
  26538. axisLabel = me.axesLabels[axisPosition] = axisSurface.add({
  26539. type: 'composite'
  26540. });
  26541. axisLabel.labelRect = axisLabel.addSprite(Ext.apply({
  26542. type: 'rect',
  26543. fillStyle: 'white',
  26544. x: axisPosition === 'right' ? lineWidth : 0,
  26545. y: axisPosition === 'bottom' ? lineWidth : 0,
  26546. width: axisWidth - lineWidth - (axisAlignment === 'vertical' && titleBBox ? titleBBox.width : 0),
  26547. height: axisHeight - lineWidth - (axisAlignment === 'horizontal' && titleBBox ? titleBBox.height : 0),
  26548. translationX: axisPosition === 'left' && titleBBox ? titleBBox.width : 0,
  26549. translationY: axisPosition === 'top' && titleBBox ? titleBBox.height : 0
  26550. }, axesConfig.rect || axesConfig[axisPosition].rect));
  26551. if (axisAlignment === 'vertical' && !verticalLineCfg.strokeStyle) {
  26552. verticalLineCfg.strokeStyle = attr.strokeStyle;
  26553. }
  26554. if (axisAlignment === 'horizontal' && !horizontalLineCfg.strokeStyle) {
  26555. horizontalLineCfg.strokeStyle = attr.strokeStyle;
  26556. }
  26557. axisTheme = Ext.merge({}, axesTheme.defaults, axesTheme[axisPosition]);
  26558. axisLabelConfig = Ext.apply({}, axis.config.label, axisTheme.label);
  26559. crosshairLabelConfig = axesConfig.label || axesConfig[axisPosition].label;
  26560. axisLabel.labelText = axisLabel.addSprite(Ext.apply(axisLabelConfig, crosshairLabelConfig, {
  26561. type: 'text',
  26562. x: me.calculateLabelTextPoint(false, axisPosition, tickPadding, titleBBox, axisWidth, halfLineWidth),
  26563. y: me.calculateLabelTextPoint(true, axisPosition, tickPadding, titleBBox, axisHeight, halfLineWidth)
  26564. }));
  26565. }
  26566. me.horizontalLine = surface.add(horizontalLineCfg);
  26567. me.verticalLine = surface.add(verticalLineCfg);
  26568. return false;
  26569. }
  26570. },
  26571. onGesture: function(e) {
  26572. var me = this;
  26573. if (me.getLocks()[me.getGesture()] !== me) {
  26574. return;
  26575. }
  26576. var chart = me.getChart(),
  26577. surface = chart.getSurface('overlay'),
  26578. rect = Ext.Array.slice(chart.getInnerRect()),
  26579. padding = chart.getInnerPadding(),
  26580. px = padding.left,
  26581. py = padding.top,
  26582. chartWidth = rect[2],
  26583. chartHeight = rect[3],
  26584. xy = chart.getEventXY(e),
  26585. x = xy[0],
  26586. y = xy[1],
  26587. axes = chart.getAxes(),
  26588. axis, axisPosition, axisAlignment, axisSurface, axisSprite, axisMatrix, axisLayoutContext, axisSegmenter, axisLabel, labelBBox, textPadding, xx, yy, dx, dy, xValue, yValue, text, i;
  26589. if (x < 0) {
  26590. x = 0;
  26591. } else if (x > chartWidth) {
  26592. x = chartWidth;
  26593. }
  26594. if (y < 0) {
  26595. y = 0;
  26596. } else if (y > chartHeight) {
  26597. y = chartHeight;
  26598. }
  26599. x += px;
  26600. y += py;
  26601. for (i = 0; i < axes.length; i++) {
  26602. axis = axes[i];
  26603. axisPosition = axis.getPosition();
  26604. axisAlignment = axis.getAlignment();
  26605. axisSurface = axis.getSurface();
  26606. axisSprite = axis.getSprites()[0];
  26607. axisMatrix = axisSprite.attr.matrix;
  26608. textPadding = axisSprite.attr.textPadding * 2;
  26609. axisLabel = me.axesLabels[axisPosition];
  26610. axisLayoutContext = axisSprite.getLayoutContext();
  26611. axisSegmenter = axis.getSegmenter();
  26612. if (axisLabel) {
  26613. if (axisAlignment === 'vertical') {
  26614. yy = axisMatrix.getYY();
  26615. dy = axisMatrix.getDY();
  26616. yValue = (y - dy - py) / yy;
  26617. if (axis.getLayout() instanceof Ext.chart.axis.layout.Discrete) {
  26618. y = Math.round(yValue) * yy + dy + py;
  26619. yValue = axisSegmenter.from(Math.round(yValue));
  26620. yValue = axisSprite.attr.data[yValue];
  26621. } else {
  26622. yValue = axisSegmenter.from(yValue);
  26623. }
  26624. text = axisSegmenter.renderer(yValue, axisLayoutContext);
  26625. axisLabel.setAttributes({
  26626. translationY: y - py
  26627. });
  26628. axisLabel.labelText.setAttributes({
  26629. text: text
  26630. });
  26631. labelBBox = axisLabel.labelText.getBBox();
  26632. axisLabel.labelRect.setAttributes({
  26633. height: labelBBox.height + textPadding,
  26634. y: -(labelBBox.height + textPadding) / 2
  26635. });
  26636. axisSurface.renderFrame();
  26637. } else {
  26638. xx = axisMatrix.getXX();
  26639. dx = axisMatrix.getDX();
  26640. xValue = (x - dx - px) / xx;
  26641. if (axis.getLayout() instanceof Ext.chart.axis.layout.Discrete) {
  26642. x = Math.round(xValue) * xx + dx + px;
  26643. xValue = axisSegmenter.from(Math.round(xValue));
  26644. xValue = axisSprite.attr.data[xValue];
  26645. } else {
  26646. xValue = axisSegmenter.from(xValue);
  26647. }
  26648. text = axisSegmenter.renderer(xValue, axisLayoutContext);
  26649. axisLabel.setAttributes({
  26650. translationX: x - px
  26651. });
  26652. axisLabel.labelText.setAttributes({
  26653. text: text
  26654. });
  26655. labelBBox = axisLabel.labelText.getBBox();
  26656. axisLabel.labelRect.setAttributes({
  26657. width: labelBBox.width + textPadding,
  26658. x: -(labelBBox.width + textPadding) / 2
  26659. });
  26660. axisSurface.renderFrame();
  26661. }
  26662. }
  26663. }
  26664. me.horizontalLine.setAttributes({
  26665. y: y,
  26666. strokeStyle: axisSprite.attr.strokeStyle
  26667. });
  26668. me.verticalLine.setAttributes({
  26669. x: x,
  26670. strokeStyle: axisSprite.attr.strokeStyle
  26671. });
  26672. surface.renderFrame();
  26673. return false;
  26674. },
  26675. onGestureEnd: function(e) {
  26676. var me = this,
  26677. chart = me.getChart(),
  26678. surface = chart.getSurface('overlay'),
  26679. axes = chart.getAxes(),
  26680. axis, axisPosition, axisSurface, axisLabel, i;
  26681. surface.remove(me.verticalLine);
  26682. surface.remove(me.horizontalLine);
  26683. for (i = 0; i < axes.length; i++) {
  26684. axis = axes[i];
  26685. axisPosition = axis.getPosition();
  26686. axisSurface = axis.getSurface();
  26687. axisLabel = me.axesLabels[axisPosition];
  26688. if (axisLabel) {
  26689. delete me.axesLabels[axisPosition];
  26690. axisSurface.remove(axisLabel);
  26691. }
  26692. axisSurface.renderFrame();
  26693. }
  26694. surface.renderFrame();
  26695. me.unlockEvents(me.getGesture());
  26696. },
  26697. onGestureCancel: function(e) {
  26698. this.onGestureEnd(e);
  26699. },
  26700. privates: {
  26701. vertMap: {
  26702. top: 'start',
  26703. bottom: 'end'
  26704. },
  26705. horzMap: {
  26706. left: 'start',
  26707. right: 'end'
  26708. },
  26709. calculateLabelTextPoint: function(vertical, position, tickPadding, titleBBox, axisSize, halfLineWidth) {
  26710. var titlePadding, sizeProp, pointProp;
  26711. if (vertical) {
  26712. pointProp = 'y';
  26713. sizeProp = 'height';
  26714. position = this.vertMap[position];
  26715. } else {
  26716. pointProp = 'x';
  26717. sizeProp = 'width';
  26718. position = this.horzMap[position];
  26719. }
  26720. switch (position) {
  26721. case 'start':
  26722. titlePadding = titleBBox ? titleBBox[pointProp] + titleBBox[sizeProp] : 0;
  26723. return titlePadding + (axisSize - titlePadding - tickPadding) / 2 - halfLineWidth;
  26724. case 'end':
  26725. titlePadding = titleBBox ? axisSize - titleBBox[pointProp] : 0;
  26726. return tickPadding + (axisSize - tickPadding - titlePadding) / 2 + halfLineWidth;
  26727. default:
  26728. return 0;
  26729. }
  26730. }
  26731. }
  26732. });
  26733. /**
  26734. * @class Ext.chart.interactions.ItemHighlight
  26735. * @extends Ext.chart.interactions.Abstract
  26736. *
  26737. * The 'itemhighlight' interaction allows the user to highlight series items in the chart.
  26738. */
  26739. Ext.define('Ext.chart.interactions.ItemHighlight', {
  26740. extend: 'Ext.chart.interactions.Abstract',
  26741. type: 'itemhighlight',
  26742. alias: 'interaction.itemhighlight',
  26743. isItemHighlight: true,
  26744. config: {
  26745. gestures: {
  26746. tap: 'onTapGesture',
  26747. mousemove: 'onMouseMoveGesture',
  26748. mousedown: 'onMouseDownGesture',
  26749. mouseup: 'onMouseUpGesture',
  26750. mouseleave: 'onMouseUpGesture'
  26751. },
  26752. /**
  26753. * @cfg {Boolean} [sticky=false]
  26754. * Disables mouse tracking.
  26755. * Series items will only be highlighted/unhighlighted on mouse click.
  26756. * This config has no effect on touch devices.
  26757. */
  26758. sticky: false,
  26759. /**
  26760. * @cfg {Boolean} [multiTooltips=false]
  26761. * Enable displaying multiple tooltips for overlapping or adjacent series items within
  26762. * {@link Ext.chart.series.Line#selectionTolerance} radius.
  26763. * Default is to display a tooltip only for the last series item rendered.
  26764. * When multiple tooltips are displayed, they may overlap partially or completely;
  26765. * it is up to the developer to ensure tooltip positioning is satisfactory.
  26766. *
  26767. * @since 6.6.0
  26768. */
  26769. multiTooltips: false
  26770. },
  26771. constructor: function(config) {
  26772. this.callParent([
  26773. config
  26774. ]);
  26775. this.stickyHighlightItem = null;
  26776. this.tooltipItems = [];
  26777. },
  26778. destroy: function() {
  26779. this.stickyHighlightItem = this.tooltipItems = null;
  26780. this.callParent();
  26781. },
  26782. onMouseMoveGesture: function(e) {
  26783. var me = this,
  26784. tooltipItems = me.tooltipItems,
  26785. isMousePointer = e.pointerType === 'mouse',
  26786. tooltips = [],
  26787. item, oldItem, items, tooltip, oldTooltip, i, len, j, jLen;
  26788. if (me.getSticky()) {
  26789. return true;
  26790. }
  26791. if (isMousePointer && me.stickyHighlightItem) {
  26792. me.stickyHighlightItem = null;
  26793. me.highlight(null);
  26794. }
  26795. if (me.isDragging) {
  26796. if (tooltipItems.length && isMousePointer) {
  26797. me.hideTooltips(tooltipItems);
  26798. tooltipItems.length = 0;
  26799. }
  26800. } else if (!me.stickyHighlightItem) {
  26801. if (me.getMultiTooltips()) {
  26802. items = me.getItemsForEvent(e);
  26803. } else {
  26804. item = me.getItemForEvent(e);
  26805. items = item ? [
  26806. item
  26807. ] : [];
  26808. }
  26809. for (i = 0 , len = items.length; i < len; i++) {
  26810. item = items[i];
  26811. // Items are returned top to down, so first item is the top one.
  26812. // Chart can only have one highlighted item.
  26813. if (i === 0 && item !== me.getChart().getHighlightItem()) {
  26814. me.highlight(item);
  26815. me.sync();
  26816. }
  26817. tooltip = item.series.getTooltip();
  26818. if (tooltip) {
  26819. tooltips.push(tooltip);
  26820. }
  26821. }
  26822. if (isMousePointer) {
  26823. // If we detected a mouse hit, show/refresh the tooltip
  26824. if (items.length) {
  26825. for (i = 0 , len = items.length; i < len; i++) {
  26826. item = items[i];
  26827. tooltip = item.series.getTooltip();
  26828. if (tooltip) {
  26829. // If there were different previously active items
  26830. // that are not going to be included in current active items,
  26831. // ask them to hide their tooltips. Unless those are
  26832. // the same tooltip instances that we are about to show,
  26833. // in which case we are just going to reposition them.
  26834. for (j = 0 , jLen = tooltipItems.length; j < jLen; j++) {
  26835. oldItem = tooltipItems[j];
  26836. if (!Ext.Array.contains(items, oldItem)) {
  26837. oldTooltip = oldItem.series.getTooltip();
  26838. if (!Ext.Array.contains(tooltips, oldTooltip)) {
  26839. oldItem.series.hideTooltip(oldItem, true);
  26840. }
  26841. }
  26842. }
  26843. if (tooltip.getTrackMouse()) {
  26844. item.series.showTooltip(item, e);
  26845. } else {
  26846. me.showUntracked(item);
  26847. }
  26848. }
  26849. }
  26850. me.tooltipItems = items;
  26851. } else // No mouse hit - schedule a hide for hideDelay ms.
  26852. // If pointer enters another item within that time,
  26853. // there will be no flickery reshow.
  26854. {
  26855. me.hideTooltips(tooltipItems);
  26856. tooltipItems.length = 0;
  26857. }
  26858. }
  26859. return false;
  26860. }
  26861. },
  26862. highlight: function(item) {
  26863. // This is its own function to make it easier for subclasses
  26864. // to enhance the behavior. An alternative would be to listen
  26865. // for the chart's 'itemhighlight' event.
  26866. this.getChart().setHighlightItem(item);
  26867. },
  26868. showTooltip: function(e, item) {
  26869. item.series.showTooltip(item, e);
  26870. Ext.Array.include(this.tooltipItems, item);
  26871. },
  26872. showUntracked: function(item) {
  26873. var marker = item.sprite.getMarker(item.category),
  26874. surface, surfaceXY, isInverseY, itemBBox, matrix;
  26875. if (marker) {
  26876. surface = marker.getSurface();
  26877. isInverseY = surface.matrix.elements[3] < 0;
  26878. surfaceXY = surface.element.getXY();
  26879. itemBBox = Ext.clone(marker.getBBoxFor(item.index));
  26880. if (isInverseY) {
  26881. // The item.category for bar series will be 'items'.
  26882. // The item.category for line series will be 'markers'.
  26883. // 'items' are in the 'series' surface, which is flipped vertically
  26884. // for cartesian series.
  26885. // 'markers' are in the 'overlay' surface, which isn't flipped.
  26886. // So for 'markers' we already have the bbox in a coordinate system
  26887. // with the origin at the top-left of the surface, but for 'items'
  26888. // we need to do a conversion.
  26889. if (surface.getInherited().rtl) {
  26890. matrix = surface.inverseMatrix.clone().flipX().translate(item.sprite.attr.innerWidth, 0, true);
  26891. } else {
  26892. matrix = surface.inverseMatrix;
  26893. }
  26894. itemBBox = matrix.transformBBox(itemBBox);
  26895. }
  26896. itemBBox.x += surfaceXY[0];
  26897. itemBBox.y += surfaceXY[1];
  26898. item.series.showTooltipAt(item, itemBBox.x + itemBBox.width * 0.5, itemBBox.y + itemBBox.height * 0.5);
  26899. }
  26900. },
  26901. onMouseDownGesture: function() {
  26902. this.isDragging = true;
  26903. },
  26904. onMouseUpGesture: function() {
  26905. this.isDragging = false;
  26906. },
  26907. isSameItem: function(a, b) {
  26908. return a && b && a.series === b.series && a.field === b.field && a.index === b.index;
  26909. },
  26910. onTapGesture: function(e) {
  26911. var me = this;
  26912. // A click/tap on an item makes its highlight sticky.
  26913. // It requires another click/tap to unhighlight.
  26914. if (e.pointerType === 'mouse' && !me.getSticky()) {
  26915. return;
  26916. }
  26917. var item = me.getItemForEvent(e);
  26918. if (me.isSameItem(me.stickyHighlightItem, item)) {
  26919. item = null;
  26920. }
  26921. // toggle
  26922. me.stickyHighlightItem = item;
  26923. me.highlight(item);
  26924. },
  26925. privates: {
  26926. hideTooltips: function(items, force) {
  26927. var item, i, len;
  26928. items = Ext.isArray(items) ? items : [
  26929. items
  26930. ];
  26931. for (i = 0 , len = items.length; i < len; i++) {
  26932. item = items[i];
  26933. if (item && item.series && !item.series.destroyed) {
  26934. item.series.hideTooltip(item, force);
  26935. }
  26936. }
  26937. }
  26938. }
  26939. });
  26940. /**
  26941. * @class Ext.chart.interactions.ItemEdit
  26942. * @extends Ext.chart.interactions.ItemHighlight
  26943. *
  26944. * The 'itemedit' interaction allows the user to edit store data
  26945. * by dragging series items in the chart.
  26946. *
  26947. * The 'itemedit' interaction extends the
  26948. * {@link Ext.chart.interactions.ItemHighlight 'itemhighlight'} interaction,
  26949. * so it also acts like one. If you need both interactions in a single chart,
  26950. * 'itemedit' should be sufficient. Hovering/tapping will result in highlighting,
  26951. * and dragging will result in editing.
  26952. */
  26953. Ext.define('Ext.chart.interactions.ItemEdit', {
  26954. extend: 'Ext.chart.interactions.ItemHighlight',
  26955. requires: [
  26956. 'Ext.tip.ToolTip'
  26957. ],
  26958. type: 'itemedit',
  26959. alias: 'interaction.itemedit',
  26960. isItemEdit: true,
  26961. config: {
  26962. /**
  26963. * @cfg {Object} [style=null]
  26964. * The style that will be applied to the series item on dragging.
  26965. * By default, series item will have no fill,
  26966. * and will have a dashed stroke of the same color.
  26967. */
  26968. style: null,
  26969. /**
  26970. * @cfg {Function/String} [renderer=null]
  26971. * A function that returns style attributes for the item that's being dragged.
  26972. * This is useful if you want to give a visual feedback to the user when
  26973. * they dragged to a certain point.
  26974. *
  26975. * @param {Object} [data] The following properties are available:
  26976. *
  26977. * @param {Object} data.target The object containing the xField/xValue or/and
  26978. * yField/yValue properties, where the xField/yField specify the store records
  26979. * being edited and the xValue/yValue the target values to be set when
  26980. * the interaction ends. The object also contains the 'index' of the record
  26981. * being edited.
  26982. * @param {Object} data.style The style that is going to be used for the dragged item.
  26983. * The attributes returned by the renderer will be applied on top of this style.
  26984. * @param {Object} data.item The series item being dragged.
  26985. * This is actually the {@link Ext.chart.AbstractChart#highlightItem}.
  26986. *
  26987. * @return {Object} The style attributes to be set on the dragged item.
  26988. */
  26989. renderer: null,
  26990. /**
  26991. * @cfg {Object/Boolean} [tooltip=true]
  26992. */
  26993. tooltip: true,
  26994. gestures: {
  26995. dragstart: 'onDragStart',
  26996. drag: 'onDrag',
  26997. dragend: 'onDragEnd'
  26998. },
  26999. cursors: {
  27000. ewResize: 'ew-resize',
  27001. nsResize: 'ns-resize',
  27002. move: 'move'
  27003. }
  27004. },
  27005. /**
  27006. * @private
  27007. * @cfg {Boolean} [sticky=false]
  27008. */
  27009. /**
  27010. * @event beginitemedit
  27011. * Fires when item edit operation (dragging) begins.
  27012. * @param {Ext.chart.AbstractChart} chart The chart the interaction belongs to.
  27013. * @param {Ext.chart.interactions.ItemEdit} interaction The interaction.
  27014. * @param {Object} item The item that is about to be edited.
  27015. */
  27016. /**
  27017. * @event enditemedit
  27018. * Fires when item edit operation (dragging) ends.
  27019. * @param {Ext.chart.AbstractChart} chart The chart the interaction belongs to.
  27020. * @param {Ext.chart.interactions.ItemEdit} interaction The interaction.
  27021. * @param {Object} item The item that was edited.
  27022. * @param {Object} target The object containing target values the were used.
  27023. */
  27024. item: null,
  27025. // Item being edited.
  27026. applyTooltip: function(tooltip) {
  27027. if (tooltip) {
  27028. var config = Ext.apply({}, tooltip, {
  27029. renderer: this.defaultTooltipRenderer,
  27030. constrainPosition: true,
  27031. shrinkWrapDock: true,
  27032. autoHide: true,
  27033. trackMouse: true,
  27034. mouseOffset: [
  27035. 20,
  27036. 20
  27037. ]
  27038. });
  27039. tooltip = new Ext.tip.ToolTip(config);
  27040. }
  27041. return tooltip;
  27042. },
  27043. defaultTooltipRenderer: function(tooltip, item, target, e) {
  27044. var parts = [];
  27045. if (target.xField) {
  27046. parts.push(target.xField + ': ' + target.xValue);
  27047. }
  27048. if (target.yField) {
  27049. parts.push(target.yField + ': ' + target.yValue);
  27050. }
  27051. tooltip.setHtml(parts.join('<br>'));
  27052. },
  27053. onDragStart: function(e) {
  27054. var me = this,
  27055. chart = me.getChart(),
  27056. item = chart.getHighlightItem();
  27057. e.claimGesture();
  27058. if (item) {
  27059. chart.fireEvent('beginitemedit', chart, me, me.item = item);
  27060. // If ItemEdit interaction comes before other interactions
  27061. // in the chart's 'interactions' config, this will
  27062. // prevent other interactions hijacking the 'dragstart'
  27063. // event. We only stop event propagation is there's
  27064. // an item to edit under cursor/finger, otherwise we
  27065. // let other interactions (e.g. 'panzoom') handle the event.
  27066. return false;
  27067. }
  27068. },
  27069. onDrag: function(e) {
  27070. var me = this,
  27071. chart = me.getChart(),
  27072. item = chart.getHighlightItem(),
  27073. type = item && item.sprite.type;
  27074. if (item) {
  27075. switch (type) {
  27076. case 'barSeries':
  27077. return me.onDragBar(e);
  27078. case 'scatterSeries':
  27079. return me.onDragScatter(e);
  27080. }
  27081. }
  27082. },
  27083. highlight: function(item) {
  27084. var me = this,
  27085. chart = me.getChart(),
  27086. flipXY = chart.getFlipXY(),
  27087. cursors = me.getCursors(),
  27088. type = item && item.sprite.type,
  27089. style = chart.el.dom.style;
  27090. me.callParent([
  27091. item
  27092. ]);
  27093. if (item) {
  27094. switch (type) {
  27095. case 'barSeries':
  27096. if (flipXY) {
  27097. style.cursor = cursors.ewResize;
  27098. } else {
  27099. style.cursor = cursors.nsResize;
  27100. };
  27101. break;
  27102. case 'scatterSeries':
  27103. style.cursor = cursors.move;
  27104. break;
  27105. }
  27106. } else {
  27107. chart.el.dom.style.cursor = 'default';
  27108. }
  27109. },
  27110. onDragBar: function(e) {
  27111. var me = this,
  27112. chart = me.getChart(),
  27113. isRtl = chart.getInherited().rtl,
  27114. flipXY = chart.isCartesian && chart.getFlipXY(),
  27115. item = chart.getHighlightItem(),
  27116. marker = item.sprite.getMarker('items'),
  27117. instance = marker.getMarkerFor(item.sprite.getId(), item.index),
  27118. surface = item.sprite.getSurface(),
  27119. surfaceRect = surface.getRect(),
  27120. xy = surface.getEventXY(e),
  27121. matrix = item.sprite.attr.matrix,
  27122. renderer = me.getRenderer(),
  27123. style, changes, params, positionY;
  27124. if (flipXY) {
  27125. positionY = isRtl ? surfaceRect[2] - xy[0] : xy[0];
  27126. } else {
  27127. positionY = surfaceRect[3] - xy[1];
  27128. }
  27129. style = {
  27130. x: instance.x,
  27131. y: positionY,
  27132. width: instance.width,
  27133. height: instance.height + (instance.y - positionY),
  27134. radius: instance.radius,
  27135. fillStyle: 'none',
  27136. lineDash: [
  27137. 4,
  27138. 4
  27139. ],
  27140. zIndex: 100
  27141. };
  27142. Ext.apply(style, me.getStyle());
  27143. if (Ext.isArray(item.series.getYField())) {
  27144. // stacked bars
  27145. positionY = positionY - instance.y - instance.height;
  27146. }
  27147. me.target = {
  27148. index: item.index,
  27149. yField: item.field,
  27150. yValue: (positionY - matrix.getDY()) / matrix.getYY()
  27151. };
  27152. params = [
  27153. chart,
  27154. {
  27155. target: me.target,
  27156. style: style,
  27157. item: item
  27158. }
  27159. ];
  27160. changes = Ext.callback(renderer, null, params, 0, chart);
  27161. if (changes) {
  27162. Ext.apply(style, changes);
  27163. }
  27164. // The interaction works by putting another series item instance
  27165. // under 'itemedit' ID with a slightly different style (default) or
  27166. // whatever style the user provided.
  27167. item.sprite.putMarker('items', style, 'itemedit');
  27168. me.showTooltip(e, me.target, item);
  27169. surface.renderFrame();
  27170. },
  27171. onDragScatter: function(e) {
  27172. var me = this,
  27173. chart = me.getChart(),
  27174. isRtl = chart.getInherited().rtl,
  27175. flipXY = chart.isCartesian && chart.getFlipXY(),
  27176. item = chart.getHighlightItem(),
  27177. marker = item.sprite.getMarker('markers'),
  27178. instance = marker.getMarkerFor(item.sprite.getId(), item.index),
  27179. surface = item.sprite.getSurface(),
  27180. surfaceRect = surface.getRect(),
  27181. xy = surface.getEventXY(e),
  27182. matrix = item.sprite.attr.matrix,
  27183. xAxis = item.series.getXAxis(),
  27184. isEditableX = xAxis && xAxis.getLayout().isContinuous,
  27185. renderer = me.getRenderer(),
  27186. style, changes, params, positionX, positionY, hintX, hintY;
  27187. if (flipXY) {
  27188. positionY = isRtl ? surfaceRect[2] - xy[0] : xy[0];
  27189. } else {
  27190. positionY = surfaceRect[3] - xy[1];
  27191. }
  27192. if (isEditableX) {
  27193. if (flipXY) {
  27194. positionX = surfaceRect[3] - xy[1];
  27195. } else {
  27196. positionX = xy[0];
  27197. }
  27198. } else {
  27199. positionX = instance.translationX;
  27200. }
  27201. if (isEditableX) {
  27202. hintX = xy[0];
  27203. hintY = xy[1];
  27204. } else {
  27205. if (flipXY) {
  27206. hintX = xy[0];
  27207. hintY = instance.translationY;
  27208. } else // no change
  27209. {
  27210. hintX = instance.translationX;
  27211. hintY = xy[1];
  27212. }
  27213. }
  27214. // no change
  27215. style = {
  27216. translationX: hintX,
  27217. translationY: hintY,
  27218. scalingX: instance.scalingX,
  27219. scalingY: instance.scalingY,
  27220. r: instance.r,
  27221. fillStyle: 'none',
  27222. lineDash: [
  27223. 4,
  27224. 4
  27225. ],
  27226. zIndex: 100
  27227. };
  27228. Ext.apply(style, me.getStyle());
  27229. me.target = {
  27230. index: item.index,
  27231. yField: item.field,
  27232. yValue: (positionY - matrix.getDY()) / matrix.getYY()
  27233. };
  27234. if (isEditableX) {
  27235. Ext.apply(me.target, {
  27236. xField: item.series.getXField(),
  27237. xValue: (positionX - matrix.getDX()) / matrix.getXX()
  27238. });
  27239. }
  27240. params = [
  27241. chart,
  27242. {
  27243. target: me.target,
  27244. style: style,
  27245. item: item
  27246. }
  27247. ];
  27248. changes = Ext.callback(renderer, null, params, 0, chart);
  27249. if (changes) {
  27250. Ext.apply(style, changes);
  27251. }
  27252. // This marker acts as a visual hint while dragging.
  27253. item.sprite.putMarker('markers', style, 'itemedit');
  27254. me.showTooltip(e, me.target, item);
  27255. surface.renderFrame();
  27256. },
  27257. showTooltip: function(e, target, item) {
  27258. var tooltip = this.getTooltip(),
  27259. config, chart;
  27260. if (tooltip && Ext.toolkit !== 'modern') {
  27261. config = tooltip.config;
  27262. chart = this.getChart();
  27263. Ext.callback(config.renderer, null, [
  27264. tooltip,
  27265. item,
  27266. target,
  27267. e
  27268. ], 0, chart);
  27269. // If trackMouse is set, a ToolTip shows by its pointerEvent
  27270. tooltip.pointerEvent = e;
  27271. if (tooltip.isVisible()) {
  27272. // After show handling repositions according
  27273. // to configuration. trackMouse uses the pointerEvent
  27274. // If aligning to an element, it uses a currentTarget
  27275. // flyweight which may be attached to any DOM element.
  27276. tooltip.realignToTarget();
  27277. } else {
  27278. tooltip.show();
  27279. }
  27280. }
  27281. },
  27282. hideTooltip: function() {
  27283. var tooltip = this.getTooltip();
  27284. if (tooltip && Ext.toolkit !== 'modern') {
  27285. tooltip.hide();
  27286. }
  27287. },
  27288. onDragEnd: function(e) {
  27289. var me = this,
  27290. target = me.target,
  27291. chart = me.getChart(),
  27292. store = chart.getStore(),
  27293. record;
  27294. if (target) {
  27295. record = store.getAt(target.index);
  27296. if (target.yField) {
  27297. record.set(target.yField, target.yValue, {
  27298. convert: false
  27299. });
  27300. }
  27301. if (target.xField) {
  27302. record.set(target.xField, target.xValue, {
  27303. convert: false
  27304. });
  27305. }
  27306. if (target.yField || target.xField) {
  27307. me.getChart().onDataChanged();
  27308. }
  27309. me.target = null;
  27310. }
  27311. me.hideTooltip();
  27312. if (me.item) {
  27313. chart.fireEvent('enditemedit', chart, me, me.item, target);
  27314. }
  27315. me.highlight(me.item = null);
  27316. },
  27317. destroy: function() {
  27318. // Peek at the config, so we don't create one just to destroy it,
  27319. // if a user has set 'tooltip' config to 'false'.
  27320. var tooltip = this.getConfig('tooltip', true);
  27321. Ext.destroy(tooltip);
  27322. this.callParent();
  27323. }
  27324. });
  27325. /**
  27326. * The PanZoom interaction allows the user to navigate the data for one or more chart
  27327. * axes by panning and/or zooming. Navigation can be limited to particular axes. Zooming is
  27328. * performed by pinching on the chart or axis area; panning is performed by single-touch dragging.
  27329. * The interaction only works with cartesian charts/series.
  27330. *
  27331. * For devices which do not support multiple-touch events, zooming can not be done via pinch gestures; in this case the
  27332. * interaction will allow the user to perform both zooming and panning using the same single-touch drag gesture.
  27333. * {@link #modeToggleButton} provides a button to indicate and toggle between two modes.
  27334. *
  27335. * @example
  27336. * Ext.create({
  27337. * renderTo: document.body,
  27338. * xtype: 'cartesian',
  27339. * width: 600,
  27340. * height: 400,
  27341. * insetPadding: 40,
  27342. * interactions: [{
  27343. * type: 'panzoom',
  27344. * zoomOnPan: true
  27345. * }],
  27346. * store: {
  27347. * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
  27348. * data: [{
  27349. * 'name': 'metric one',
  27350. * 'data1': 10,
  27351. * 'data2': 12,
  27352. * 'data3': 14,
  27353. * 'data4': 8,
  27354. * 'data5': 13
  27355. * }, {
  27356. * 'name': 'metric two',
  27357. * 'data1': 7,
  27358. * 'data2': 8,
  27359. * 'data3': 16,
  27360. * 'data4': 10,
  27361. * 'data5': 3
  27362. * }, {
  27363. * 'name': 'metric three',
  27364. * 'data1': 5,
  27365. * 'data2': 2,
  27366. * 'data3': 14,
  27367. * 'data4': 12,
  27368. * 'data5': 7
  27369. * }, {
  27370. * 'name': 'metric four',
  27371. * 'data1': 2,
  27372. * 'data2': 14,
  27373. * 'data3': 6,
  27374. * 'data4': 1,
  27375. * 'data5': 23
  27376. * }, {
  27377. * 'name': 'metric five',
  27378. * 'data1': 27,
  27379. * 'data2': 38,
  27380. * 'data3': 36,
  27381. * 'data4': 13,
  27382. * 'data5': 33
  27383. * }]
  27384. * },
  27385. * axes: [{
  27386. * type: 'numeric',
  27387. * position: 'left',
  27388. * fields: ['data1'],
  27389. * title: {
  27390. * text: 'Sample Values',
  27391. * fontSize: 15
  27392. * },
  27393. * grid: true,
  27394. * minimum: 0
  27395. * }, {
  27396. * type: 'category',
  27397. * position: 'bottom',
  27398. * fields: ['name'],
  27399. * title: {
  27400. * text: 'Sample Values',
  27401. * fontSize: 15
  27402. * }
  27403. * }],
  27404. * series: [{
  27405. * type: 'line',
  27406. * highlight: {
  27407. * size: 7,
  27408. * radius: 7
  27409. * },
  27410. * style: {
  27411. * stroke: 'rgb(143,203,203)'
  27412. * },
  27413. * xField: 'name',
  27414. * yField: 'data1',
  27415. * marker: {
  27416. * type: 'path',
  27417. * path: ['M', - 2, 0, 0, 2, 2, 0, 0, - 2, 'Z'],
  27418. * stroke: 'blue',
  27419. * lineWidth: 0
  27420. * }
  27421. * }, {
  27422. * type: 'line',
  27423. * highlight: {
  27424. * size: 7,
  27425. * radius: 7
  27426. * },
  27427. * fill: true,
  27428. * xField: 'name',
  27429. * yField: 'data3',
  27430. * marker: {
  27431. * type: 'circle',
  27432. * radius: 4,
  27433. * lineWidth: 0
  27434. * }
  27435. * }]
  27436. * });
  27437. *
  27438. * The configuration object for the `panzoom` interaction type should specify which axes
  27439. * will be made navigable via the `axes` config. See the {@link #axes} config documentation
  27440. * for details on the allowed formats. If the `axes` config is not specified, it will default
  27441. * to making all axes navigable with the default axis options.
  27442. *
  27443. */
  27444. Ext.define('Ext.chart.interactions.PanZoom', {
  27445. extend: 'Ext.chart.interactions.Abstract',
  27446. type: 'panzoom',
  27447. alias: 'interaction.panzoom',
  27448. requires: [
  27449. 'Ext.draw.Animator'
  27450. ],
  27451. config: {
  27452. /**
  27453. * @cfg {Object/Array} axes
  27454. * Specifies which axes should be made navigable. The config value can take the following formats:
  27455. *
  27456. * - An Object with keys corresponding to the {@link Ext.chart.axis.Axis#position position} of each
  27457. * axis that should be made navigable. Each key's value can either be an Object with further
  27458. * configuration options for each axis or simply `true` for a default set of options.
  27459. *
  27460. * {
  27461. * type: 'panzoom',
  27462. * axes: {
  27463. * left: {
  27464. * maxZoom: 5,
  27465. * allowPan: false
  27466. * },
  27467. * bottom: true
  27468. * }
  27469. * }
  27470. *
  27471. * If using the full Object form, the following options can be specified for each axis:
  27472. *
  27473. * - minZoom (Number) A minimum zoom level for the axis. Defaults to `1` which is its natural size.
  27474. * - maxZoom (Number) A maximum zoom level for the axis. Defaults to `10`.
  27475. * - startZoom (Number) A starting zoom level for the axis. Defaults to `1`.
  27476. * - allowZoom (Boolean) Whether zooming is allowed for the axis. Defaults to `true`.
  27477. * - allowPan (Boolean) Whether panning is allowed for the axis. Defaults to `true`.
  27478. * - startPan (Boolean) A starting panning offset for the axis. Defaults to `0`.
  27479. *
  27480. * - An Array of strings, each one corresponding to the {@link Ext.chart.axis.Axis#position position}
  27481. * of an axis that should be made navigable. The default options will be used for each named axis.
  27482. *
  27483. * {
  27484. * type: 'panzoom',
  27485. * axes: ['left', 'bottom']
  27486. * }
  27487. *
  27488. * If the `axes` config is not specified, it will default to making all axes navigable with the
  27489. * default axis options.
  27490. */
  27491. axes: {
  27492. top: {},
  27493. right: {},
  27494. bottom: {},
  27495. left: {}
  27496. },
  27497. minZoom: null,
  27498. maxZoom: null,
  27499. /**
  27500. * @cfg {Boolean} showOverflowArrows
  27501. * If `true`, arrows will be conditionally shown at either end of each axis to indicate that the
  27502. * axis is overflowing and can therefore be panned in that direction. Set this to `false` to
  27503. * prevent the arrows from being displayed.
  27504. */
  27505. showOverflowArrows: true,
  27506. /**
  27507. * @cfg {Object} overflowArrowOptions
  27508. * A set of optional overrides for the overflow arrow sprites' options. Only relevant when
  27509. * {@link #showOverflowArrows} is `true`.
  27510. */
  27511. /**
  27512. * @cfg {String} panGesture
  27513. * Defines the gesture that initiates panning.
  27514. * @private
  27515. */
  27516. panGesture: 'drag',
  27517. /**
  27518. * @cfg {String} zoomGesture
  27519. * Defines the gesture that initiates zooming.
  27520. * @private
  27521. */
  27522. zoomGesture: 'pinch',
  27523. /**
  27524. * @cfg {Boolean} zoomOnPanGesture
  27525. * @deprecated 6.2 Please use {@link #zoomOnPan} instead.
  27526. * If `true`, the pan gesture will zoom the chart.
  27527. */
  27528. zoomOnPanGesture: null,
  27529. /**
  27530. * @cfg {Boolean} zoomOnPan
  27531. * If `true`, the pan gesture will zoom the chart.
  27532. */
  27533. zoomOnPan: false,
  27534. /**
  27535. * @cfg {Boolean} [doubleTapReset=false]
  27536. * If `true`, the double tap on a chart will reset the current pan/zoom to show the whole chart.
  27537. */
  27538. doubleTapReset: false,
  27539. modeToggleButton: {
  27540. xtype: 'segmentedbutton',
  27541. width: 200,
  27542. defaults: {
  27543. ui: 'default-toolbar'
  27544. },
  27545. cls: Ext.baseCSSPrefix + 'panzoom-toggle',
  27546. items: [
  27547. {
  27548. text: 'Pan',
  27549. value: 'pan'
  27550. },
  27551. {
  27552. text: 'Zoom',
  27553. value: 'zoom'
  27554. }
  27555. ]
  27556. },
  27557. hideLabelInGesture: false
  27558. },
  27559. // Ext.os.is.Android
  27560. stopAnimationBeforeSync: true,
  27561. applyAxes: function(axesConfig, oldAxesConfig) {
  27562. return Ext.merge(oldAxesConfig || {}, axesConfig);
  27563. },
  27564. updateZoomOnPan: function(zoomOnPan) {
  27565. var button = this.getModeToggleButton();
  27566. button.setValue(zoomOnPan ? 'zoom' : 'pan');
  27567. },
  27568. updateZoomOnPanGesture: function(zoomOnPanGesture) {
  27569. this.setZoomOnPan(zoomOnPanGesture);
  27570. },
  27571. getZoomOnPanGesture: function() {
  27572. return this.getZoomOnPan();
  27573. },
  27574. applyModeToggleButton: function(button, oldButton) {
  27575. return Ext.factory(button, 'Ext.button.Segmented', oldButton);
  27576. },
  27577. updateModeToggleButton: function(button) {
  27578. if (button) {
  27579. button.on('change', 'onModeToggleChange', this);
  27580. }
  27581. },
  27582. onModeToggleChange: function(segmentedButton, value) {
  27583. this.setZoomOnPan(value === 'zoom');
  27584. },
  27585. getGestures: function() {
  27586. var me = this,
  27587. gestures = {},
  27588. pan = me.getPanGesture(),
  27589. zoom = me.getZoomGesture();
  27590. gestures[zoom] = 'onZoomGestureMove';
  27591. gestures[zoom + 'start'] = 'onZoomGestureStart';
  27592. gestures[zoom + 'end'] = 'onZoomGestureEnd';
  27593. gestures[pan] = 'onPanGestureMove';
  27594. gestures[pan + 'start'] = 'onPanGestureStart';
  27595. gestures[pan + 'end'] = 'onPanGestureEnd';
  27596. gestures.doubletap = 'onDoubleTap';
  27597. return gestures;
  27598. },
  27599. onDoubleTap: function(e) {
  27600. var me = this,
  27601. doubleTapReset = me.getDoubleTapReset(),
  27602. chart, axes, axis, i, ln;
  27603. if (doubleTapReset) {
  27604. chart = me.getChart();
  27605. axes = chart.getAxes();
  27606. for (i = 0 , ln = axes.length; i < ln; i++) {
  27607. axis = axes[i];
  27608. axis.setVisibleRange([
  27609. 0,
  27610. 1
  27611. ]);
  27612. }
  27613. chart.redraw();
  27614. }
  27615. },
  27616. onPanGestureStart: function(e) {
  27617. if (!e || !e.touches || e.touches.length < 2) {
  27618. //Limit drags to single touch
  27619. var me = this,
  27620. chart = me.getChart(),
  27621. rect = chart.getInnerRect(),
  27622. xy = chart.element.getXY();
  27623. e.claimGesture();
  27624. chart.suspendAnimation();
  27625. me.startX = e.getX() - xy[0] - rect[0];
  27626. me.startY = e.getY() - xy[1] - rect[1];
  27627. me.oldVisibleRanges = null;
  27628. me.hideLabels();
  27629. chart.suspendThicknessChanged();
  27630. me.lockEvents(me.getPanGesture());
  27631. return false;
  27632. }
  27633. },
  27634. onPanGestureMove: function(e) {
  27635. var me = this,
  27636. isMouse = e.pointerType === 'mouse',
  27637. isZoomOnPan = isMouse && me.getZoomOnPan();
  27638. if (me.getLocks()[me.getPanGesture()] === me) {
  27639. // Limit drags to single touch.
  27640. var chart = me.getChart(),
  27641. rect = chart.getInnerRect(),
  27642. xy = chart.element.getXY();
  27643. if (isZoomOnPan) {
  27644. me.transformAxesBy(me.getZoomableAxes(e), 0, 0, (e.getX() - xy[0] - rect[0]) / me.startX, me.startY / (e.getY() - xy[1] - rect[1]));
  27645. } else {
  27646. me.transformAxesBy(me.getPannableAxes(e), e.getX() - xy[0] - rect[0] - me.startX, e.getY() - xy[1] - rect[1] - me.startY, 1, 1);
  27647. }
  27648. me.sync();
  27649. return false;
  27650. }
  27651. },
  27652. onPanGestureEnd: function(e) {
  27653. var me = this,
  27654. pan = me.getPanGesture(),
  27655. chart;
  27656. if (me.getLocks()[pan] === me) {
  27657. chart = me.getChart();
  27658. chart.resumeThicknessChanged();
  27659. me.showLabels();
  27660. me.sync();
  27661. me.unlockEvents(pan);
  27662. chart.resumeAnimation();
  27663. return false;
  27664. }
  27665. },
  27666. onZoomGestureStart: function(e) {
  27667. if (e.touches && e.touches.length === 2) {
  27668. var me = this,
  27669. chart = me.getChart(),
  27670. xy = chart.element.getXY(),
  27671. rect = chart.getInnerRect(),
  27672. x = xy[0] + rect[0],
  27673. y = xy[1] + rect[1],
  27674. newPoints = [
  27675. e.touches[0].point.x - x,
  27676. e.touches[0].point.y - y,
  27677. e.touches[1].point.x - x,
  27678. e.touches[1].point.y - y
  27679. ],
  27680. xDistance = Math.max(44, Math.abs(newPoints[2] - newPoints[0])),
  27681. yDistance = Math.max(44, Math.abs(newPoints[3] - newPoints[1]));
  27682. e.claimGesture();
  27683. chart.suspendAnimation();
  27684. chart.suspendThicknessChanged();
  27685. me.lastZoomDistances = [
  27686. xDistance,
  27687. yDistance
  27688. ];
  27689. me.lastPoints = newPoints;
  27690. me.oldVisibleRanges = null;
  27691. me.hideLabels();
  27692. me.lockEvents(me.getZoomGesture());
  27693. return false;
  27694. }
  27695. },
  27696. onZoomGestureMove: function(e) {
  27697. var me = this;
  27698. if (me.getLocks()[me.getZoomGesture()] === me) {
  27699. var chart = me.getChart(),
  27700. rect = chart.getInnerRect(),
  27701. xy = chart.element.getXY(),
  27702. x = xy[0] + rect[0],
  27703. y = xy[1] + rect[1],
  27704. abs = Math.abs,
  27705. lastPoints = me.lastPoints,
  27706. newPoints = [
  27707. e.touches[0].point.x - x,
  27708. e.touches[0].point.y - y,
  27709. e.touches[1].point.x - x,
  27710. e.touches[1].point.y - y
  27711. ],
  27712. xDistance = Math.max(44, abs(newPoints[2] - newPoints[0])),
  27713. yDistance = Math.max(44, abs(newPoints[3] - newPoints[1])),
  27714. lastDistances = this.lastZoomDistances || [
  27715. xDistance,
  27716. yDistance
  27717. ],
  27718. zoomX = xDistance / lastDistances[0],
  27719. zoomY = yDistance / lastDistances[1];
  27720. 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);
  27721. me.sync();
  27722. return false;
  27723. }
  27724. },
  27725. onZoomGestureEnd: function(e) {
  27726. var me = this,
  27727. zoom = me.getZoomGesture(),
  27728. chart;
  27729. if (me.getLocks()[zoom] === me) {
  27730. chart = me.getChart();
  27731. chart.resumeThicknessChanged();
  27732. me.showLabels();
  27733. me.sync();
  27734. me.unlockEvents(zoom);
  27735. chart.resumeAnimation();
  27736. return false;
  27737. }
  27738. },
  27739. hideLabels: function() {
  27740. if (this.getHideLabelInGesture()) {
  27741. this.eachInteractiveAxes(function(axis) {
  27742. axis.hideLabels();
  27743. });
  27744. }
  27745. },
  27746. showLabels: function() {
  27747. if (this.getHideLabelInGesture()) {
  27748. this.eachInteractiveAxes(function(axis) {
  27749. axis.showLabels();
  27750. });
  27751. }
  27752. },
  27753. isEventOnAxis: function(e, axis) {
  27754. // TODO: right now this uses the current event position but really we want to only
  27755. // use the gesture's start event. Pinch does not give that to us though.
  27756. var rect = axis.getSurface().getRect();
  27757. return rect[0] <= e.getX() && e.getX() <= rect[0] + rect[2] && rect[1] <= e.getY() && e.getY() <= rect[1] + rect[3];
  27758. },
  27759. getPannableAxes: function(e) {
  27760. var me = this,
  27761. axisConfigs = me.getAxes(),
  27762. axes = me.getChart().getAxes(),
  27763. i,
  27764. ln = axes.length,
  27765. result = [],
  27766. isEventOnAxis = false,
  27767. config;
  27768. if (e) {
  27769. for (i = 0; i < ln; i++) {
  27770. if (this.isEventOnAxis(e, axes[i])) {
  27771. isEventOnAxis = true;
  27772. break;
  27773. }
  27774. }
  27775. }
  27776. for (i = 0; i < ln; i++) {
  27777. config = axisConfigs[axes[i].getPosition()];
  27778. if (config && config.allowPan !== false && (!isEventOnAxis || this.isEventOnAxis(e, axes[i]))) {
  27779. result.push(axes[i]);
  27780. }
  27781. }
  27782. return result;
  27783. },
  27784. getZoomableAxes: function(e) {
  27785. var me = this,
  27786. axisConfigs = me.getAxes(),
  27787. axes = me.getChart().getAxes(),
  27788. result = [],
  27789. i,
  27790. ln = axes.length,
  27791. axis,
  27792. isEventOnAxis = false,
  27793. config;
  27794. if (e) {
  27795. for (i = 0; i < ln; i++) {
  27796. if (this.isEventOnAxis(e, axes[i])) {
  27797. isEventOnAxis = true;
  27798. break;
  27799. }
  27800. }
  27801. }
  27802. for (i = 0; i < ln; i++) {
  27803. axis = axes[i];
  27804. config = axisConfigs[axis.getPosition()];
  27805. if (config && config.allowZoom !== false && (!isEventOnAxis || this.isEventOnAxis(e, axis))) {
  27806. result.push(axis);
  27807. }
  27808. }
  27809. return result;
  27810. },
  27811. eachInteractiveAxes: function(fn) {
  27812. var me = this,
  27813. axisConfigs = me.getAxes(),
  27814. axes = me.getChart().getAxes();
  27815. for (var i = 0; i < axes.length; i++) {
  27816. if (axisConfigs[axes[i].getPosition()]) {
  27817. if (false === fn.call(this, axes[i])) {
  27818. return;
  27819. }
  27820. }
  27821. }
  27822. },
  27823. transformAxesBy: function(axes, panX, panY, sx, sy) {
  27824. var rect = this.getChart().getInnerRect(),
  27825. axesCfg = this.getAxes(),
  27826. axisCfg,
  27827. oldVisibleRanges = this.oldVisibleRanges,
  27828. result = false;
  27829. if (!oldVisibleRanges) {
  27830. this.oldVisibleRanges = oldVisibleRanges = {};
  27831. this.eachInteractiveAxes(function(axis) {
  27832. oldVisibleRanges[axis.getId()] = axis.getVisibleRange();
  27833. });
  27834. }
  27835. if (!rect) {
  27836. return;
  27837. }
  27838. for (var i = 0; i < axes.length; i++) {
  27839. axisCfg = axesCfg[axes[i].getPosition()];
  27840. result = this.transformAxisBy(axes[i], oldVisibleRanges[axes[i].getId()], panX, panY, sx, sy, this.minZoom || axisCfg.minZoom, this.maxZoom || axisCfg.maxZoom) || result;
  27841. }
  27842. return result;
  27843. },
  27844. transformAxisBy: function(axis, oldVisibleRange, panX, panY, sx, sy, minZoom, maxZoom) {
  27845. var me = this,
  27846. visibleLength = oldVisibleRange[1] - oldVisibleRange[0],
  27847. visibleRange = axis.getVisibleRange(),
  27848. actualMinZoom = minZoom || me.getMinZoom() || axis.config.minZoom,
  27849. actualMaxZoom = maxZoom || me.getMaxZoom() || axis.config.maxZoom,
  27850. rect = me.getChart().getInnerRect(),
  27851. left, right;
  27852. if (!rect) {
  27853. return;
  27854. }
  27855. var isSide = axis.isSide(),
  27856. length = isSide ? rect[3] : rect[2],
  27857. pan = isSide ? -panY : panX;
  27858. visibleLength /= isSide ? sy : sx;
  27859. if (visibleLength < 0) {
  27860. visibleLength = -visibleLength;
  27861. }
  27862. if (visibleLength * actualMinZoom > 1) {
  27863. visibleLength = 1;
  27864. }
  27865. if (visibleLength * actualMaxZoom < 1) {
  27866. visibleLength = 1 / actualMaxZoom;
  27867. }
  27868. left = oldVisibleRange[0];
  27869. right = oldVisibleRange[1];
  27870. visibleRange = visibleRange[1] - visibleRange[0];
  27871. if (visibleLength === visibleRange && visibleRange === 1) {
  27872. return;
  27873. }
  27874. axis.setVisibleRange([
  27875. (oldVisibleRange[0] + oldVisibleRange[1] - visibleLength) * 0.5 - pan / length * visibleLength,
  27876. (oldVisibleRange[0] + oldVisibleRange[1] + visibleLength) * 0.5 - pan / length * visibleLength
  27877. ]);
  27878. return Math.abs(left - axis.getVisibleRange()[0]) > 1.0E-10 || Math.abs(right - axis.getVisibleRange()[1]) > 1.0E-10;
  27879. },
  27880. destroy: function() {
  27881. this.setModeToggleButton(null);
  27882. this.callParent();
  27883. }
  27884. });
  27885. /**
  27886. * @class Ext.chart.interactions.Rotate
  27887. * @extends Ext.chart.interactions.Abstract
  27888. *
  27889. * The Rotate interaction allows the user to rotate a polar chart about its central point.
  27890. *
  27891. * @example
  27892. * Ext.create('Ext.Container', {
  27893. * renderTo: Ext.getBody(),
  27894. * width: 600,
  27895. * height: 400,
  27896. * layout: 'fit',
  27897. * items: {
  27898. * xtype: 'polar',
  27899. * interactions: 'rotate',
  27900. * colors: ["#115fa6", "#94ae0a", "#a61120", "#ff8809", "#ffd13e"],
  27901. * store: {
  27902. * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
  27903. * data: [
  27904. * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
  27905. * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
  27906. * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
  27907. * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
  27908. * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
  27909. * ]
  27910. * },
  27911. * series: {
  27912. * type: 'pie',
  27913. * label: {
  27914. * field: 'name',
  27915. * display: 'rotate'
  27916. * },
  27917. * xField: 'data3',
  27918. * donut: 30
  27919. * }
  27920. * }
  27921. * });
  27922. */
  27923. Ext.define('Ext.chart.interactions.Rotate', {
  27924. extend: 'Ext.chart.interactions.Abstract',
  27925. type: 'rotate',
  27926. alternateClassName: 'Ext.chart.interactions.RotatePie3D',
  27927. alias: [
  27928. 'interaction.rotate',
  27929. 'interaction.rotatePie3d'
  27930. ],
  27931. /**
  27932. * @event rotate
  27933. * Fires on every tick of the rotation.
  27934. * @param {Ext.chart.interactions.Rotate} this This interaction.
  27935. * @param {Number} angle The new current rotation angle.
  27936. */
  27937. /**
  27938. * @event rotatestart
  27939. * Fires when a user initiates the rotation.
  27940. * @param {Ext.chart.interactions.Rotate} this This interaction.
  27941. * @param {Number} angle The new current rotation angle.
  27942. */
  27943. /**
  27944. * @event rotateend
  27945. * Fires after a user finishes the rotation.
  27946. * @param {Ext.chart.interactions.Rotate} this This interaction.
  27947. * @param {Number} angle The new current rotation angle.
  27948. */
  27949. /**
  27950. * @deprecated 6.5.1 Use the 'rotateend' event instead.
  27951. * @event rotationEnd
  27952. * Fires after a user finishes the rotation
  27953. * @param {Ext.chart.interactions.Rotate} this This interaction.
  27954. * @param {Number} angle The new current rotation angle.
  27955. */
  27956. config: {
  27957. /**
  27958. * @cfg {String} gesture
  27959. * Defines the gesture type that will be used to rotate the chart. Currently only
  27960. * supports `pinch` for two-finger rotation and `drag` for single-finger rotation.
  27961. * @private
  27962. */
  27963. gesture: 'rotate',
  27964. gestures: {
  27965. dragstart: 'onGestureStart',
  27966. drag: 'onGesture',
  27967. dragend: 'onGestureEnd'
  27968. },
  27969. /**
  27970. * @cfg {Number} rotation
  27971. * Saves the current rotation of the series. Accepts negative values and values > 360 ( / 180 * Math.PI)
  27972. * @private
  27973. */
  27974. rotation: 0
  27975. },
  27976. oldRotations: null,
  27977. getAngle: function(e) {
  27978. var me = this,
  27979. chart = me.getChart(),
  27980. xy = chart.getEventXY(e),
  27981. center = chart.getCenter();
  27982. return Math.atan2(xy[1] - center[1], xy[0] - center[0]);
  27983. },
  27984. onGestureStart: function(e) {
  27985. var me = this;
  27986. e.claimGesture();
  27987. me.lockEvents('drag');
  27988. me.angle = me.getAngle(e);
  27989. me.oldRotations = {};
  27990. me.getChart().suspendAnimation();
  27991. me.fireEvent('rotatestart', me, me.getRotation());
  27992. return false;
  27993. },
  27994. onGesture: function(e) {
  27995. var me = this,
  27996. angle = me.getAngle(e) - me.angle;
  27997. if (me.getLocks().drag === me) {
  27998. me.doRotateTo(angle, true);
  27999. return false;
  28000. }
  28001. },
  28002. /**
  28003. * @private
  28004. */
  28005. doRotateTo: function(angle, relative) {
  28006. var me = this,
  28007. chart = me.getChart(),
  28008. axes = chart.getAxes(),
  28009. seriesList = chart.getSeries(),
  28010. oldRotations = me.oldRotations,
  28011. rotation, oldRotation, axis, series, id, i, ln;
  28012. for (i = 0 , ln = axes.length; i < ln; i++) {
  28013. axis = axes[i];
  28014. id = axis.getId();
  28015. oldRotation = oldRotations[id] || (oldRotations[id] = axis.getRotation());
  28016. rotation = angle + (relative ? oldRotation : 0);
  28017. axis.setRotation(rotation);
  28018. }
  28019. for (i = 0 , ln = seriesList.length; i < ln; i++) {
  28020. series = seriesList[i];
  28021. id = series.getId();
  28022. oldRotation = oldRotations[id] || (oldRotations[id] = series.getRotation());
  28023. // Unline axis's 'rotation', Polar series' 'rotation' is a public config and in degrees.
  28024. rotation = Ext.draw.Draw.degrees(angle + (relative ? oldRotation : 0));
  28025. series.setRotation(rotation);
  28026. }
  28027. me.setRotation(rotation);
  28028. me.fireEvent('rotate', me, me.getRotation());
  28029. me.sync();
  28030. },
  28031. /**
  28032. * Rotates a polar chart about its center point to the specified angle.
  28033. * @param {Number} angle The angle to rotate to.
  28034. * @param {Boolean} [relative=false] Whether the rotation is relative to the current angle or not.
  28035. * @param {Boolean} [animate=false] Whether to animate the rotation or not.
  28036. */
  28037. rotateTo: function(angle, relative, animate) {
  28038. var me = this,
  28039. chart = me.getChart();
  28040. if (!animate) {
  28041. chart.suspendAnimation();
  28042. }
  28043. me.doRotateTo(angle, relative, animate);
  28044. me.oldRotations = {};
  28045. if (!animate) {
  28046. chart.resumeAnimation();
  28047. }
  28048. },
  28049. onGestureEnd: function(e) {
  28050. var me = this;
  28051. if (me.getLocks().drag === me) {
  28052. me.onGesture(e);
  28053. me.unlockEvents('drag');
  28054. me.getChart().resumeAnimation();
  28055. me.fireEvent('rotateend', me, me.getRotation());
  28056. me.fireEvent('rotationEnd', me, me.getRotation());
  28057. return false;
  28058. }
  28059. }
  28060. });
  28061. /**
  28062. *
  28063. */
  28064. Ext.define('Ext.chart.navigator.ContainerBase', {
  28065. extend: 'Ext.Container',
  28066. updateNavigator: function(navigator, oldNavigator) {
  28067. if (oldNavigator) {
  28068. this.remove(oldNavigator, true);
  28069. }
  28070. this.add(navigator);
  28071. }
  28072. });
  28073. /**
  28074. *
  28075. */
  28076. Ext.define('Ext.chart.navigator.NavigatorBase', {
  28077. extend: 'Ext.chart.CartesianChart',
  28078. initialize: function() {
  28079. var me = this;
  28080. me.callParent();
  28081. me.setupEvents();
  28082. }
  28083. });
  28084. /**
  28085. * The overlay sprite used by the {@link Ext.chart.navigator.Navigator} component
  28086. * to render the selected visible range or a chart's horizontal axis.
  28087. */
  28088. Ext.define('Ext.chart.navigator.sprite.RangeMask', {
  28089. extend: 'Ext.draw.sprite.Sprite',
  28090. alias: 'sprite.rangemask',
  28091. inheritableStatics: {
  28092. def: {
  28093. processors: {
  28094. min: 'limited01',
  28095. max: 'limited01',
  28096. thumbOpacity: 'limited01'
  28097. },
  28098. defaults: {
  28099. min: 0,
  28100. max: 1,
  28101. lineWidth: 2,
  28102. miterLimit: 1,
  28103. strokeStyle: '#787878',
  28104. thumbOpacity: 1
  28105. }
  28106. }
  28107. },
  28108. getBBox: function(isWithoutTransform) {
  28109. var me = this,
  28110. attr = me.attr,
  28111. bbox = attr.bbox;
  28112. bbox.plain = {
  28113. x: 0,
  28114. y: 0,
  28115. width: 1,
  28116. height: 1
  28117. };
  28118. if (isWithoutTransform) {
  28119. return bbox.plain;
  28120. }
  28121. return bbox.transform || (bbox.transform = attr.matrix.transformBBox(bbox.plain));
  28122. },
  28123. renderThumb: function(surface, ctx, x, y) {
  28124. var me = this,
  28125. shapeSprite = me.shapeSprite,
  28126. textureSprite = me.textureSprite,
  28127. thumbOpacity = me.attr.thumbOpacity,
  28128. thumbAttributes = {
  28129. opacity: thumbOpacity,
  28130. translationX: x,
  28131. translationY: y
  28132. };
  28133. if (!shapeSprite) {
  28134. shapeSprite = me.shapeSprite = new Ext.draw.sprite.Rect({
  28135. x: -9.5,
  28136. y: -9.5,
  28137. width: 19,
  28138. height: 19,
  28139. radius: 4,
  28140. lineWidth: 1,
  28141. fillStyle: {
  28142. type: 'linear',
  28143. degrees: 90,
  28144. stops: [
  28145. {
  28146. offset: 0,
  28147. color: '#EEE'
  28148. },
  28149. {
  28150. offset: 1,
  28151. color: '#FFF'
  28152. }
  28153. ]
  28154. },
  28155. strokeStyle: '#999'
  28156. });
  28157. textureSprite = me.textureSprite = new Ext.draw.sprite.Path({
  28158. path: 'M -4, -5, -4, 5 M 0, -5, 0, 5 M 4, -5, 4, 5',
  28159. strokeStyle: {
  28160. type: 'linear',
  28161. degrees: 90,
  28162. stops: [
  28163. {
  28164. offset: 0,
  28165. color: '#CCC'
  28166. },
  28167. {
  28168. offset: 1,
  28169. color: '#BBB'
  28170. }
  28171. ]
  28172. },
  28173. lineWidth: 2
  28174. });
  28175. }
  28176. ctx.save();
  28177. shapeSprite.setAttributes(thumbAttributes);
  28178. shapeSprite.applyTransformations();
  28179. textureSprite.setAttributes(thumbAttributes);
  28180. textureSprite.applyTransformations();
  28181. shapeSprite.useAttributes(ctx);
  28182. shapeSprite.render(surface, ctx);
  28183. textureSprite.useAttributes(ctx);
  28184. textureSprite.render(surface, ctx);
  28185. ctx.restore();
  28186. },
  28187. render: function(surface, ctx) {
  28188. var me = this,
  28189. attr = me.attr,
  28190. matrix = attr.matrix.elements,
  28191. sx = matrix[0],
  28192. sy = matrix[3],
  28193. tx = matrix[4],
  28194. ty = matrix[5],
  28195. min = attr.min,
  28196. max = attr.max,
  28197. // s_min and s_max are range values in screen coordinates (scaled and translated)
  28198. s_min = min * sx + tx,
  28199. s_max = max * sx + tx,
  28200. s_y = Math.round(0.5 * sy + ty);
  28201. // thumb position in screen coordinates (mid-height)
  28202. ctx.beginPath();
  28203. // Rect that represents the whole range.
  28204. ctx.moveTo(tx, ty);
  28205. ctx.lineTo(sx + tx, ty);
  28206. ctx.lineTo(sx + tx, sy + ty);
  28207. ctx.lineTo(tx, sy + ty);
  28208. ctx.lineTo(tx, ty);
  28209. // Rect that represents the visible range.
  28210. ctx.moveTo(s_min, ty);
  28211. ctx.lineTo(s_min, sy + ty);
  28212. ctx.lineTo(s_max, sy + ty);
  28213. ctx.lineTo(s_max, ty);
  28214. ctx.lineTo(s_min, ty);
  28215. ctx.fillStroke(attr, true);
  28216. me.renderThumb(surface, ctx, Math.round(s_min), s_y);
  28217. me.renderThumb(surface, ctx, Math.round(s_max), s_y);
  28218. }
  28219. });
  28220. /**
  28221. * The Navigator component is used to visually set the visible range of the x-axis
  28222. * of a cartesian chart.
  28223. *
  28224. * This component is meant to be used with the Navigator Container
  28225. * via its {@link Ext.chart.navigator.Container#navigator} config.
  28226. *
  28227. * IMPORTANT: even though the Navigator component is a kind of chart, it should not be
  28228. * treated as such. Correct behavior is not guaranteed when using hidden/private configs.
  28229. */
  28230. Ext.define('Ext.chart.navigator.Navigator', {
  28231. extend: 'Ext.chart.navigator.NavigatorBase',
  28232. isNavigator: true,
  28233. requires: [
  28234. 'Ext.chart.navigator.sprite.RangeMask'
  28235. ],
  28236. config: {
  28237. /**
  28238. * @cfg {'bottom'/'top'} [docked='bottom']
  28239. */
  28240. docked: 'bottom',
  28241. /**
  28242. * @cfg {'series'/'chart'} [span='series']
  28243. * Whether the navigator should span the 'series' (default) or the whole 'chart'.
  28244. */
  28245. span: 'series',
  28246. insetPadding: 0,
  28247. innerPadding: 0,
  28248. /**
  28249. * @cfg {Ext.chart.navigator.Container} navigatorContainer
  28250. * 'parent' is reserved in Modern, 'container' is reserved in Classic,
  28251. * so we use 'navigatorContainer' as a config name.
  28252. * @private
  28253. */
  28254. navigatorContainer: null,
  28255. /**
  28256. * @cfg {String} axis (required)
  28257. * The ID of the {@link #chart chart's} axis to link to.
  28258. * The axis should be positioned to 'bottom' or 'top' in the chart.
  28259. */
  28260. axis: null,
  28261. /**
  28262. * @cfg {Number} [tolerance=20]
  28263. * The maximum horizontal delta between the pointer/finger and the center of a navigator thumb.
  28264. * Used for hit testing.
  28265. */
  28266. tolerance: 20,
  28267. /**
  28268. * @cfg {Number} [minimum=0.8]
  28269. * The start of the visible range, where the visible range is a [0, 1] interval.
  28270. */
  28271. minimum: 0.8,
  28272. /**
  28273. * @cfg {Number} [maximum=1]
  28274. * The end of the visible range, where the visible range is a [0, 1] interval.
  28275. */
  28276. maximum: 1,
  28277. /**
  28278. * @cfg {Number} [thumbGap=30]
  28279. * Minimum gap between navigator thumbs in pixels.
  28280. */
  28281. thumbGap: 30,
  28282. autoHideThumbs: true,
  28283. width: '100%',
  28284. /**
  28285. * @cfg {Number} [height=75]
  28286. * The height of the navigator component.
  28287. */
  28288. height: 75
  28289. },
  28290. /**
  28291. * @cfg flipXY
  28292. * @hide
  28293. */
  28294. /**
  28295. * @cfg series
  28296. * @hide
  28297. */
  28298. /**
  28299. * @cfg axes
  28300. * @hide
  28301. */
  28302. /**
  28303. * @cfg store
  28304. * @hide
  28305. */
  28306. /**
  28307. * @cfg legend
  28308. * @hide
  28309. */
  28310. /**
  28311. * @cfg interactions
  28312. * @hide
  28313. */
  28314. /**
  28315. * @cfg highlightItem
  28316. * @hide
  28317. */
  28318. /**
  28319. * @cfg theme
  28320. * @hide
  28321. */
  28322. /**
  28323. * @cfg innerPadding
  28324. * @hide
  28325. */
  28326. /**
  28327. * @cfg insetPadding
  28328. * @hide
  28329. */
  28330. dragType: null,
  28331. constructor: function(config) {
  28332. config = config || {};
  28333. var me = this,
  28334. visibleRange = [
  28335. config.minimum || 0.8,
  28336. config.maximum || 1
  28337. ],
  28338. overlay;
  28339. me.callParent([
  28340. config
  28341. ]);
  28342. overlay = me.overlaySurface;
  28343. overlay.element.setStyle({
  28344. zIndex: 100
  28345. });
  28346. me.rangeMask = overlay.add({
  28347. type: 'rangemask',
  28348. min: visibleRange[0],
  28349. max: visibleRange[1],
  28350. fillStyle: 'rgba(0, 0, 0, .25)'
  28351. });
  28352. me.onDragEnd();
  28353. // Set 'thumbOpacity' of the range mask sprite to 0, if needed,
  28354. // and apply animation modifier changes after that, so that the attribute is set
  28355. // instantly.
  28356. me.rangeMask.setAnimation({
  28357. duration: 500,
  28358. customDurations: {
  28359. min: 0,
  28360. max: 0,
  28361. translationX: 0,
  28362. translationY: 0,
  28363. scalingX: 0,
  28364. scalingY: 0,
  28365. scalingCenterX: 0,
  28366. scalingCenterY: 0,
  28367. fillStyle: 0,
  28368. strokeStyle: 0
  28369. }
  28370. });
  28371. me.setVisibleRange(visibleRange);
  28372. },
  28373. createSurface: function(id) {
  28374. var surface = this.callParent([
  28375. id
  28376. ]);
  28377. if (id === 'overlay') {
  28378. this.overlaySurface = surface;
  28379. }
  28380. return surface;
  28381. },
  28382. // Note: 'applyDock' and 'updateDock' won't ever be called in Classic.
  28383. // See Classic NavigatorBase.
  28384. applyAxis: function(axis) {
  28385. return this.getNavigatorContainer().getChart().getAxis(axis);
  28386. },
  28387. updateAxis: function(axis, oldAxis) {
  28388. var me = this,
  28389. eventName = 'visiblerangechange',
  28390. eventHandler = 'onAxisVisibleRangeChange';
  28391. if (oldAxis) {
  28392. oldAxis.un(eventName, eventHandler, me);
  28393. }
  28394. if (axis) {
  28395. axis.on(eventName, eventHandler, me);
  28396. }
  28397. me.axis = axis;
  28398. },
  28399. getAxis: function() {
  28400. // The superclass doesn't have the 'axis' config, but it has the same method,
  28401. // which we override here to act as a getter for the config. The user is not
  28402. // expected to use the original method in this subclass anyway.
  28403. return this.axis;
  28404. },
  28405. onAxisVisibleRangeChange: function(axis, visibleRange) {
  28406. this.setVisibleRange(visibleRange);
  28407. },
  28408. updateNavigatorContainer: function(navigatorContainer) {
  28409. var me = this,
  28410. oldChart = me.chart,
  28411. chart = me.chart = navigatorContainer && navigatorContainer.getChart(),
  28412. chartSeriesList = chart && chart.getSeries(),
  28413. // 'legendStore' already exists in the base class.
  28414. chartLegendStore = me.chartLegendStore,
  28415. navigatorSeriesList = [],
  28416. storeEventName = 'update',
  28417. // 'onLegendStoreUpdate' already exists in the base class.
  28418. storeEventHandler = 'onChartLegendStoreUpdate',
  28419. chartSeries, navigatorSeries, seriesConfig, i;
  28420. if (oldChart) {
  28421. oldChart.un('layout', 'afterBoundChartLayout', me);
  28422. oldChart.un('themechange', 'onChartThemeChange', me);
  28423. oldChart.un('storechange', 'onChartStoreChange', me);
  28424. }
  28425. chart.on('layout', 'afterBoundChartLayout', me);
  28426. for (i = 0; i < chartSeriesList.length; i++) {
  28427. chartSeries = chartSeriesList[i];
  28428. seriesConfig = me.getSeriesConfig(chartSeries);
  28429. navigatorSeries = Ext.create('series.' + seriesConfig.type, seriesConfig);
  28430. navigatorSeries.parentSeries = chartSeries;
  28431. chartSeries.navigatorSeries = navigatorSeries;
  28432. navigatorSeriesList.push(navigatorSeries);
  28433. }
  28434. if (chartLegendStore) {
  28435. chartLegendStore.un(storeEventName, storeEventHandler, me);
  28436. me.chartLegendStore = null;
  28437. }
  28438. if (chart) {
  28439. me.setStore(chart.getStore());
  28440. me.chartLegendStore = chartLegendStore = chart.getLegendStore();
  28441. if (chartLegendStore) {
  28442. chartLegendStore.on(storeEventName, storeEventHandler, me);
  28443. }
  28444. chart.on('themechange', 'onChartThemeChange', me);
  28445. chart.on('storechange', 'onChartStoreChange', me);
  28446. me.onChartThemeChange(chart, chart.getTheme());
  28447. }
  28448. me.setSeries(navigatorSeriesList);
  28449. },
  28450. onChartThemeChange: function(chart, theme) {
  28451. this.setTheme(theme);
  28452. },
  28453. onChartStoreChange: function(chart, store) {
  28454. this.setStore(store);
  28455. },
  28456. addCustomStyle: function(config, style, subStyle) {
  28457. var fillStyle, strokeStyle;
  28458. style = style || {};
  28459. subStyle = subStyle || {};
  28460. config.style = config.style || {};
  28461. config.subStyle = config.subStyle || {};
  28462. fillStyle = style && (style.fillStyle || style.fill);
  28463. strokeStyle = style && (style.strokeStyle || style.stroke);
  28464. if (fillStyle) {
  28465. config.style.fillStyle = fillStyle;
  28466. }
  28467. if (strokeStyle) {
  28468. config.style.strokeStyle = strokeStyle;
  28469. }
  28470. fillStyle = subStyle && (subStyle.fillStyle || subStyle.fill);
  28471. strokeStyle = subStyle && (subStyle.strokeStyle || subStyle.stroke);
  28472. if (fillStyle) {
  28473. config.subStyle.fillStyle = fillStyle;
  28474. }
  28475. if (strokeStyle) {
  28476. config.subStyle.strokeStyle = strokeStyle;
  28477. }
  28478. return config;
  28479. },
  28480. getSeriesConfig: function(chartSeries) {
  28481. var me = this,
  28482. style = chartSeries.getStyle(),
  28483. config;
  28484. if (chartSeries.isLine) {
  28485. config = me.addCustomStyle({
  28486. type: 'line',
  28487. fill: true,
  28488. xField: chartSeries.getXField(),
  28489. yField: chartSeries.getYField(),
  28490. smooth: chartSeries.getSmooth()
  28491. }, style);
  28492. } else if (chartSeries.isCandleStick) {
  28493. config = me.addCustomStyle({
  28494. type: 'line',
  28495. fill: true,
  28496. xField: chartSeries.getXField(),
  28497. yField: chartSeries.getCloseField()
  28498. }, style.raiseStyle);
  28499. } else if (chartSeries.isArea || chartSeries.isBar) {
  28500. config = me.addCustomStyle({
  28501. type: 'area',
  28502. xField: chartSeries.getXField(),
  28503. yField: chartSeries.getYField()
  28504. }, style, chartSeries.getSubStyle());
  28505. } else {
  28506. Ext.raise("Navigator only works with 'line', 'bar', 'candlestick' and 'area' series.");
  28507. }
  28508. config.style.fillOpacity = 0.2;
  28509. return config;
  28510. },
  28511. onChartLegendStoreUpdate: function(store, record) {
  28512. var me = this,
  28513. chart = me.chart,
  28514. series;
  28515. if (chart && record) {
  28516. series = chart.getSeries().map[record.get('series')];
  28517. if (series && series.navigatorSeries) {
  28518. series.navigatorSeries.setHiddenByIndex(record.get('index'), record.get('disabled'));
  28519. me.redraw();
  28520. }
  28521. }
  28522. },
  28523. setupEvents: function() {
  28524. // Called from NavigatorBase classes.
  28525. var me = this,
  28526. overlayEl = me.overlaySurface.element;
  28527. overlayEl.on({
  28528. scope: me,
  28529. drag: 'onDrag',
  28530. dragstart: 'onDragStart',
  28531. dragend: 'onDragEnd',
  28532. dragcancel: 'onDragEnd',
  28533. mousemove: 'onMouseMove'
  28534. });
  28535. },
  28536. onMouseMove: function(e) {
  28537. var me = this,
  28538. overlayEl = me.overlaySurface.element,
  28539. style = overlayEl.dom.style,
  28540. dragType = me.getDragType(e.pageX - overlayEl.getXY()[0]);
  28541. switch (dragType) {
  28542. case 'min':
  28543. case 'max':
  28544. style.cursor = 'ew-resize';
  28545. break;
  28546. case 'pan':
  28547. style.cursor = 'move';
  28548. break;
  28549. default:
  28550. style.cursor = 'default';
  28551. }
  28552. },
  28553. getDragType: function(x) {
  28554. var me = this,
  28555. t = me.getTolerance(),
  28556. width = me.overlaySurface.element.getSize().width,
  28557. rangeMask = me.rangeMask,
  28558. min = width * rangeMask.attr.min,
  28559. max = width * rangeMask.attr.max,
  28560. dragType;
  28561. if (x > min + t && x < max - t) {
  28562. dragType = 'pan';
  28563. } else if (x <= min + t && x > min - t) {
  28564. dragType = 'min';
  28565. } else if (x >= max - t && x < max + t) {
  28566. dragType = 'max';
  28567. }
  28568. return dragType;
  28569. },
  28570. onDragStart: function(e) {
  28571. // Limit drags to single touch.
  28572. if (this.dragType || e && e.touches && e.touches.length > 1) {
  28573. return;
  28574. }
  28575. var me = this,
  28576. x = e.touches[0].pageX - me.overlaySurface.element.getXY()[0],
  28577. dragType = me.getDragType(x);
  28578. me.rangeMask.attr.thumbOpacity = 1;
  28579. if (dragType) {
  28580. me.dragType = dragType;
  28581. me.touchId = e.touches[0].identifier;
  28582. me.dragX = x;
  28583. }
  28584. },
  28585. onDrag: function(e) {
  28586. if (e.touch.identifier !== this.touchId) {
  28587. return;
  28588. }
  28589. var me = this,
  28590. overlayEl = me.overlaySurface.element,
  28591. width = overlayEl.getSize().width,
  28592. x = e.touches[0].pageX - overlayEl.getXY()[0],
  28593. thumbGap = me.getThumbGap() / width,
  28594. rangeMask = me.rangeMask,
  28595. min = rangeMask.attr.min,
  28596. max = rangeMask.attr.max,
  28597. delta = max - min,
  28598. dragType = me.dragType,
  28599. drag = me.dragX,
  28600. dx = (x - drag) / width;
  28601. if (dragType === 'pan') {
  28602. min += dx;
  28603. max += dx;
  28604. if (min < 0) {
  28605. min = 0;
  28606. max = delta;
  28607. }
  28608. if (max > 1) {
  28609. max = 1;
  28610. min = max - delta;
  28611. }
  28612. } else if (dragType === 'min') {
  28613. min += dx;
  28614. if (min < 0) {
  28615. min = 0;
  28616. }
  28617. if (min > max - thumbGap) {
  28618. min = max - thumbGap;
  28619. }
  28620. } else if (dragType === 'max') {
  28621. max += dx;
  28622. if (max > 1) {
  28623. max = 1;
  28624. }
  28625. if (max < min + thumbGap) {
  28626. max = min + thumbGap;
  28627. }
  28628. } else {
  28629. return;
  28630. }
  28631. me.dragX = x;
  28632. me.setVisibleRange([
  28633. min,
  28634. max
  28635. ]);
  28636. },
  28637. onDragEnd: function() {
  28638. var me = this,
  28639. autoHideThumbs = me.getAutoHideThumbs();
  28640. me.dragType = null;
  28641. if (autoHideThumbs) {
  28642. me.rangeMask.setAttributes({
  28643. thumbOpacity: 0
  28644. });
  28645. }
  28646. },
  28647. updateMinimum: function(mininum) {
  28648. if (!this.isConfiguring) {
  28649. this.setVisibleRange([
  28650. mininum,
  28651. this.getMaximum()
  28652. ]);
  28653. }
  28654. },
  28655. updateMaximum: function(maximum) {
  28656. if (!this.isConfiguring) {
  28657. this.setVisibleRange([
  28658. this.getMinimum(),
  28659. maximum
  28660. ]);
  28661. }
  28662. },
  28663. getMinimum: function() {
  28664. return this.rangeMask.attr.min;
  28665. },
  28666. getMaximum: function() {
  28667. return this.rangeMask.attr.max;
  28668. },
  28669. setVisibleRange: function(visibleRange) {
  28670. var me = this,
  28671. chart = me.chart;
  28672. me.axis.setVisibleRange(visibleRange);
  28673. me.rangeMask.setAttributes({
  28674. min: visibleRange[0],
  28675. max: visibleRange[1]
  28676. });
  28677. me.getSurface('overlay').renderFrame();
  28678. chart.suspendAnimation();
  28679. chart.redraw();
  28680. chart.resumeAnimation();
  28681. },
  28682. afterBoundChartLayout: function() {
  28683. var me = this,
  28684. spanSeries = me.getSpan() === 'series',
  28685. mainRect = me.chart.getMainRect(),
  28686. size = me.element.getSize();
  28687. if (mainRect && spanSeries) {
  28688. me.setInsetPadding({
  28689. left: mainRect[0],
  28690. right: size.width - mainRect[2] - mainRect[0],
  28691. top: 0,
  28692. bottom: 0
  28693. });
  28694. me.performLayout();
  28695. }
  28696. },
  28697. afterChartLayout: function() {
  28698. var me = this,
  28699. size = me.overlaySurface.element.getSize();
  28700. me.rangeMask.setAttributes({
  28701. scalingCenterX: 0,
  28702. scalingCenterY: 0,
  28703. scalingX: size.width,
  28704. scalingY: size.height
  28705. });
  28706. },
  28707. doDestroy: function() {
  28708. var chart = this.chart;
  28709. if (chart && !chart.destroyed) {
  28710. chart.un('layout', 'afterBoundChartLayout', this);
  28711. }
  28712. this.callParent();
  28713. }
  28714. });
  28715. /**
  28716. * The Navigator Container is a component used to lay out the chart and its
  28717. * {@link Ext.chart.navigator.Navigator navigator}, where the navigator is docked
  28718. * to the top/bottom, and the chart fills the rest of the container's space.
  28719. *
  28720. * For example:
  28721. *
  28722. * @example
  28723. * Ext.create({
  28724. * xtype: 'chartnavigator',
  28725. * renderTo: Ext.getBody(),
  28726. * width: 600,
  28727. * height: 400,
  28728. *
  28729. * chart: {
  28730. * xtype: 'cartesian',
  28731. *
  28732. * store: {
  28733. * data: (function () {
  28734. * var data = [];
  28735. * for (var i = 0; i < 360; i++) {
  28736. * data.push({
  28737. * x: i,
  28738. * y: Math.sin(i / 45 * Math.PI)
  28739. * });
  28740. * }
  28741. * return data;
  28742. * })()
  28743. * },
  28744. * axes: [
  28745. * {
  28746. * id: 'navigable-axis',
  28747. *
  28748. * type: 'numeric',
  28749. * position: 'bottom'
  28750. * },
  28751. * {
  28752. * type: 'numeric',
  28753. * position: 'left'
  28754. * }
  28755. * ],
  28756. * series: {
  28757. * type: 'line',
  28758. * xField: 'x',
  28759. * yField: 'y'
  28760. * }
  28761. * },
  28762. *
  28763. * navigator: {
  28764. * axis: 'navigable-axis'
  28765. * }
  28766. * });
  28767. *
  28768. */
  28769. Ext.define('Ext.chart.navigator.Container', {
  28770. // We are interested in the docking functionality that's available in
  28771. // the Container in Modern and in the Panel in Classic.
  28772. extend: 'Ext.chart.navigator.ContainerBase',
  28773. requires: [
  28774. 'Ext.chart.CartesianChart',
  28775. 'Ext.chart.navigator.Navigator'
  28776. ],
  28777. xtype: 'chartnavigator',
  28778. config: {
  28779. /**
  28780. * @cfg {Ext.chart.CartesianChart} chart
  28781. * The chart to make navigable.
  28782. */
  28783. chart: null,
  28784. /**
  28785. * @cfg {Ext.chart.navigator.Navigator} navigator
  28786. */
  28787. navigator: {}
  28788. },
  28789. layout: 'fit',
  28790. applyChart: function(chart, oldChart) {
  28791. if (oldChart) {
  28792. oldChart.destroy();
  28793. }
  28794. if (chart) {
  28795. if (chart.isCartesian) {
  28796. Ext.raise('Only cartesian charts are supported.');
  28797. }
  28798. if (!chart.isChart) {
  28799. chart.$initParent = this;
  28800. chart = new Ext.chart.CartesianChart(chart);
  28801. delete chart.$initParent;
  28802. }
  28803. }
  28804. return chart;
  28805. },
  28806. legendStore: null,
  28807. surfaceRects: null,
  28808. updateChart: function(chart, oldChart) {
  28809. var me = this;
  28810. if (chart) {
  28811. me.legendStore = chart.getLegendStore();
  28812. if (!me.items && me.initItems) {
  28813. me.initItems();
  28814. }
  28815. me.add(chart);
  28816. }
  28817. },
  28818. applyNavigator: function(navigator, oldNavigator) {
  28819. var instance;
  28820. if (oldNavigator) {
  28821. oldNavigator.destroy();
  28822. }
  28823. if (navigator) {
  28824. navigator.navigatorContainer = navigator.parent = this;
  28825. instance = new Ext.chart.navigator.Navigator(navigator);
  28826. }
  28827. return instance;
  28828. },
  28829. preview: function() {
  28830. this.getNavigator().preview(this.getImage());
  28831. },
  28832. download: function(config) {
  28833. config = config || {};
  28834. config.data = this.getImage().data;
  28835. this.getNavigator().download(config);
  28836. },
  28837. setVisibleRange: function(visibleRange) {
  28838. this.getNavigator().setVisibleRange(visibleRange);
  28839. },
  28840. getImage: function(format) {
  28841. var me = this,
  28842. chart = me.getChart(),
  28843. navigator = me.getNavigator(),
  28844. docked = navigator.getDocked(),
  28845. chartImageSize = chart.bodyElement.getSize(),
  28846. navigatorImageSize = navigator.bodyElement.getSize(),
  28847. chartSurfaces = chart.getSurfaces(true),
  28848. navigatorSurfaces = navigator.getSurfaces(true),
  28849. size = {
  28850. width: chartImageSize.width,
  28851. height: chartImageSize.height + navigatorImageSize.height
  28852. },
  28853. image, imageElement, surfaces, surface;
  28854. if (docked === 'top') {
  28855. me.shiftSurfaces(chartSurfaces, 0, navigatorImageSize.height);
  28856. } else {
  28857. me.shiftSurfaces(navigatorSurfaces, 0, chartImageSize.height);
  28858. }
  28859. surfaces = chartSurfaces.concat(navigatorSurfaces);
  28860. surface = surfaces[0];
  28861. if ((Ext.isIE || Ext.isEdge) && surface.isSVG) {
  28862. // SVG data URLs don't work in IE/Edge as a source for an 'img' element,
  28863. // so we need to render SVG the usual way.
  28864. image = {
  28865. data: surface.toSVG(size, surfaces),
  28866. type: 'svg-markup'
  28867. };
  28868. } else {
  28869. image = surface.flatten(size, surfaces);
  28870. if (format === 'image') {
  28871. imageElement = new Image();
  28872. imageElement.src = image.data;
  28873. image.data = imageElement;
  28874. return image;
  28875. }
  28876. if (format === 'stream') {
  28877. image.data = image.data.replace(/^data:image\/[^;]+/, 'data:application/octet-stream');
  28878. return image;
  28879. }
  28880. }
  28881. me.unshiftSurfaces(surfaces);
  28882. return image;
  28883. },
  28884. shiftSurfaces: function(surfaces, x, y) {
  28885. var ln = surfaces.length,
  28886. i = 0,
  28887. surface;
  28888. this.surfaceRects = {};
  28889. for (; i < ln; i++) {
  28890. surface = surfaces[i];
  28891. this.shiftSurface(surface, x, y);
  28892. }
  28893. },
  28894. shiftSurface: function(surface, x, y) {
  28895. var rect = surface.getRect();
  28896. this.surfaceRects[surface.getId()] = rect.slice();
  28897. rect[0] += x;
  28898. rect[1] += y;
  28899. },
  28900. unshiftSurfaces: function(surfaces) {
  28901. var rects = this.surfaceRects,
  28902. ln = surfaces.length,
  28903. i = 0,
  28904. surface, rect, oldRect;
  28905. if (rects) {
  28906. for (; i < ln; i++) {
  28907. surface = surfaces[i];
  28908. rect = surface.getRect();
  28909. oldRect = rects[surface.getId()];
  28910. if (oldRect) {
  28911. rect[0] = oldRect[0];
  28912. rect[1] = oldRect[1];
  28913. }
  28914. }
  28915. }
  28916. this.surfaceRects = null;
  28917. }
  28918. });
  28919. /**
  28920. * A chart {@link Ext.AbstractPlugin plugin} that adds ability to listen to chart series
  28921. * items events. Item event listeners are passed two parameters: the target item and the
  28922. * event itself. The item object has the following properties:
  28923. *
  28924. * * **category** - the category the item falls under: 'items' or 'markers'
  28925. * * **field** - the store field used by this series item
  28926. * * **index** - the index of the series item
  28927. * * **record** - the store record associated with this series item
  28928. * * **series** - the series the item belongs to
  28929. * * **sprite** - the sprite used to represents this series item
  28930. *
  28931. * For example:
  28932. *
  28933. * Ext.create('Ext.chart.CartesianChart', {
  28934. * plugins: {
  28935. * chartitemevents: {
  28936. * moveEvents: true
  28937. * }
  28938. * },
  28939. * store: {
  28940. * fields: ['pet', 'households', 'total'],
  28941. * data: [
  28942. * {pet: 'Cats', households: 38, total: 93},
  28943. * {pet: 'Dogs', households: 45, total: 79},
  28944. * {pet: 'Fish', households: 13, total: 171}
  28945. * ]
  28946. * },
  28947. * axes: [{
  28948. * type: 'numeric',
  28949. * position: 'left'
  28950. * }, {
  28951. * type: 'category',
  28952. * position: 'bottom'
  28953. * }],
  28954. * series: [{
  28955. * type: 'bar',
  28956. * xField: 'pet',
  28957. * yField: 'households',
  28958. * listeners: {
  28959. * itemmousemove: function (series, item, event) {
  28960. * console.log('itemmousemove', item.category, item.field);
  28961. * }
  28962. * }
  28963. * }, {
  28964. * type: 'line',
  28965. * xField: 'pet',
  28966. * yField: 'total',
  28967. * marker: true
  28968. * }],
  28969. * listeners: { // Listen to itemclick events on all series.
  28970. * itemclick: function (chart, item, event) {
  28971. * console.log('itemclick', item.category, item.field);
  28972. * }
  28973. * }
  28974. * });
  28975. *
  28976. */
  28977. Ext.define('Ext.chart.plugin.ItemEvents', {
  28978. extend: 'Ext.plugin.Abstract',
  28979. alias: 'plugin.chartitemevents',
  28980. /**
  28981. * @cfg {Boolean} [moveEvents=false]
  28982. * If `itemmousemove`, `itemmouseover` or `itemmouseout` event listeners are attached
  28983. * to the chart, the plugin will detect those and will hit test series items on
  28984. * every move. However, if the above item events are attached on the series level
  28985. * only, this config has to be set to true, as the plugin won't perform a similar
  28986. * detection on every series.
  28987. */
  28988. moveEvents: false,
  28989. mouseMoveEvents: {
  28990. mousemove: true,
  28991. mouseover: true,
  28992. mouseout: true
  28993. },
  28994. itemMouseMoveEvents: {
  28995. itemmousemove: true,
  28996. itemmouseover: true,
  28997. itemmouseout: true
  28998. },
  28999. init: function(chart) {
  29000. var handleEvent = 'handleEvent';
  29001. this.chart = chart;
  29002. chart.addElementListener({
  29003. click: handleEvent,
  29004. tap: handleEvent,
  29005. dblclick: handleEvent,
  29006. mousedown: handleEvent,
  29007. mousemove: handleEvent,
  29008. mouseup: handleEvent,
  29009. mouseover: handleEvent,
  29010. mouseout: handleEvent,
  29011. // run our handlers before user code
  29012. priority: 1001,
  29013. scope: this
  29014. });
  29015. },
  29016. hasItemMouseMoveListeners: function() {
  29017. var listeners = this.chart.hasListeners,
  29018. name;
  29019. for (name in this.itemMouseMoveEvents) {
  29020. if (name in listeners) {
  29021. return true;
  29022. }
  29023. }
  29024. return false;
  29025. },
  29026. handleEvent: function(e) {
  29027. var me = this,
  29028. chart = me.chart,
  29029. isMouseMoveEvent = e.type in me.mouseMoveEvents,
  29030. lastItem = me.lastItem,
  29031. chartXY, item;
  29032. if (isMouseMoveEvent && !me.hasItemMouseMoveListeners() && !me.moveEvents) {
  29033. return;
  29034. }
  29035. chartXY = chart.getEventXY(e);
  29036. item = chart.getItemForPoint(chartXY[0], chartXY[1]);
  29037. if (isMouseMoveEvent && !Ext.Object.equals(item, lastItem)) {
  29038. if (lastItem) {
  29039. chart.fireEvent('itemmouseout', chart, lastItem, e);
  29040. lastItem.series.fireEvent('itemmouseout', lastItem.series, lastItem, e);
  29041. }
  29042. if (item) {
  29043. chart.fireEvent('itemmouseover', chart, item, e);
  29044. item.series.fireEvent('itemmouseover', item.series, item, e);
  29045. }
  29046. }
  29047. if (item) {
  29048. chart.fireEvent('item' + e.type, chart, item, e);
  29049. item.series.fireEvent('item' + e.type, item.series, item, e);
  29050. }
  29051. me.lastItem = item;
  29052. }
  29053. });
  29054. /**
  29055. * @abstract
  29056. * @class Ext.chart.series.Cartesian
  29057. * @extends Ext.chart.series.Series
  29058. *
  29059. * Common base class for series implementations that plot values using cartesian coordinates.
  29060. *
  29061. * @constructor
  29062. */
  29063. Ext.define('Ext.chart.series.Cartesian', {
  29064. extend: 'Ext.chart.series.Series',
  29065. config: {
  29066. /**
  29067. * @cfg {String} xField
  29068. * The field used to access the x axis value from the items from the data source.
  29069. */
  29070. xField: null,
  29071. /**
  29072. * @cfg {String|String[]} yField
  29073. * The field(s) used to access the y-axis value(s) of the items from the data source.
  29074. */
  29075. yField: null,
  29076. /**
  29077. * @cfg {Ext.chart.axis.Axis|Number|String}
  29078. * xAxis The chart axis the series is bound to in the 'X' direction.
  29079. * Normally, this would be set automatically by the series.
  29080. * For charts with multiple x-axes, this defines which x-axis is used by the series.
  29081. * It refers to either axis' ID or the (zero-based) index of the axis
  29082. * in the chart's {@link Ext.chart.AbstractChart#axes axes} config.
  29083. */
  29084. xAxis: null,
  29085. /**
  29086. * @cfg {Ext.chart.axis.Axis|Number|String}
  29087. * yAxis The chart axis the series is bound to in the 'Y' direction.
  29088. * Normally, this would be set automatically by the series.
  29089. * For charts with multiple y-axes, this defines which y-axis is used by the series.
  29090. * It refers to either axis' ID or the (zero-based) index of the axis
  29091. * in the chart's {@link Ext.chart.AbstractChart#axes axes} config.
  29092. */
  29093. yAxis: null
  29094. },
  29095. directions: [
  29096. 'X',
  29097. 'Y'
  29098. ],
  29099. /**
  29100. * @private
  29101. *
  29102. * Tells which store record fields should be used for a specific axis direction. E.g. for
  29103. *
  29104. * fieldCategory<direction>: ['<fieldConfig1>', '<fieldConfig2>', ...]
  29105. *
  29106. * the field names from the following configs will be used:
  29107. *
  29108. * series.<fieldConfig1>Field, series.<fieldConfig2>Field, ...
  29109. *
  29110. * See {@link Ext.chart.series.StackedCartesian#getFields}.
  29111. *
  29112. */
  29113. fieldCategoryX: [
  29114. 'X'
  29115. ],
  29116. fieldCategoryY: [
  29117. 'Y'
  29118. ],
  29119. applyXAxis: function(newAxis, oldAxis) {
  29120. return this.getChart().getAxis(newAxis) || oldAxis;
  29121. },
  29122. applyYAxis: function(newAxis, oldAxis) {
  29123. return this.getChart().getAxis(newAxis) || oldAxis;
  29124. },
  29125. updateXAxis: function(axis) {
  29126. axis.processData(this);
  29127. },
  29128. updateYAxis: function(axis) {
  29129. axis.processData(this);
  29130. },
  29131. coordinateX: function() {
  29132. return this.coordinate('X', 0, 2);
  29133. },
  29134. coordinateY: function() {
  29135. return this.coordinate('Y', 1, 2);
  29136. },
  29137. getItemForPoint: function(x, y) {
  29138. var me = this,
  29139. sprite = me.getSprites()[0],
  29140. store = me.getStore(),
  29141. point;
  29142. if (sprite && !me.getHidden()) {
  29143. point = sprite.getNearestDataPoint(x, y);
  29144. }
  29145. return point ? {
  29146. series: me,
  29147. sprite: sprite,
  29148. category: me.getItemInstancing() ? 'items' : 'markers',
  29149. index: point.index,
  29150. record: store.getData().items[point.index],
  29151. field: me.getYField(),
  29152. distance: point.distance
  29153. } : null;
  29154. },
  29155. createSprite: function() {
  29156. var me = this,
  29157. sprite = me.callParent(),
  29158. chart = me.getChart(),
  29159. xAxis = me.getXAxis();
  29160. sprite.setAttributes({
  29161. flipXY: chart.getFlipXY(),
  29162. xAxis: xAxis
  29163. });
  29164. if (sprite.setAggregator && xAxis && xAxis.getAggregator) {
  29165. if (xAxis.getAggregator) {
  29166. sprite.setAggregator({
  29167. strategy: xAxis.getAggregator()
  29168. });
  29169. } else {
  29170. sprite.setAggregator({});
  29171. }
  29172. }
  29173. return sprite;
  29174. },
  29175. getSprites: function() {
  29176. var me = this,
  29177. chart = this.getChart(),
  29178. sprites = me.sprites;
  29179. if (!chart) {
  29180. return Ext.emptyArray;
  29181. }
  29182. if (!sprites.length) {
  29183. me.createSprite();
  29184. }
  29185. return sprites;
  29186. },
  29187. getXRange: function() {
  29188. return [
  29189. this.dataRange[0],
  29190. this.dataRange[2]
  29191. ];
  29192. },
  29193. getYRange: function() {
  29194. return [
  29195. this.dataRange[1],
  29196. this.dataRange[3]
  29197. ];
  29198. }
  29199. });
  29200. /**
  29201. * @abstract
  29202. * @extends Ext.chart.series.Cartesian
  29203. * Abstract class for all the stacked cartesian series including area series
  29204. * and bar series.
  29205. */
  29206. Ext.define('Ext.chart.series.StackedCartesian', {
  29207. extend: 'Ext.chart.series.Cartesian',
  29208. config: {
  29209. /**
  29210. * @cfg {Boolean} [stacked=true]
  29211. * `true` to display the series in its stacked configuration.
  29212. */
  29213. stacked: true,
  29214. /**
  29215. * @cfg {Boolean} [splitStacks=true]
  29216. * `true` to stack negative/positive values in respective y-axis directions.
  29217. */
  29218. splitStacks: true,
  29219. /**
  29220. * @cfg {Boolean} [fullStack=false]
  29221. * If `true`, the height of a stacked bar is always the full height of the chart,
  29222. * with individual components viewed as shares of the whole determined by the
  29223. * {@link #fullStackTotal} config.
  29224. */
  29225. fullStack: false,
  29226. /**
  29227. * @cfg {Boolean} [fullStackTotal=100]
  29228. * If the {@link #fullStack} config is set to `true`, this will determine
  29229. * the absolute total value of each stack.
  29230. */
  29231. fullStackTotal: 100,
  29232. /**
  29233. * @cfg {Array} hidden
  29234. */
  29235. hidden: []
  29236. },
  29237. /**
  29238. * @private
  29239. * @property
  29240. * If `true`, each subsequent sprite has a lower zIndex so that the stroke of previous
  29241. * sprite in the stack is not covered by the next sprite (which makes the very top
  29242. * segment look odd in flat bar and area series, especially when wide strokes are used).
  29243. */
  29244. reversedSpriteZOrder: true,
  29245. spriteAnimationCount: 0,
  29246. themeColorCount: function() {
  29247. var me = this,
  29248. yField = me.getYField();
  29249. return Ext.isArray(yField) ? yField.length : 1;
  29250. },
  29251. updateStacked: function() {
  29252. this.processData();
  29253. },
  29254. updateSplitStacks: function() {
  29255. this.processData();
  29256. },
  29257. coordinateY: function() {
  29258. return this.coordinateStacked('Y', 1, 2);
  29259. },
  29260. coordinateStacked: function(direction, directionOffset, directionCount) {
  29261. var me = this,
  29262. store = me.getStore(),
  29263. items = store.getData().items,
  29264. itemCount = items.length,
  29265. axis = me['get' + direction + 'Axis'](),
  29266. hidden = me.getHidden(),
  29267. splitStacks = me.getSplitStacks(),
  29268. fullStack = me.getFullStack(),
  29269. fullStackTotal = me.getFullStackTotal(),
  29270. range = [
  29271. 0,
  29272. 0
  29273. ],
  29274. directions = me['fieldCategory' + direction],
  29275. dataStart = [],
  29276. posDataStart = [],
  29277. negDataStart = [],
  29278. dataEnd,
  29279. stacked = me.getStacked(),
  29280. sprites = me.getSprites(),
  29281. coordinatedData = [],
  29282. i, j, k, fields, fieldCount, posTotals, negTotals, fieldCategoriesItem, data, attr;
  29283. if (!sprites.length) {
  29284. return;
  29285. }
  29286. for (i = 0; i < directions.length; i++) {
  29287. fieldCategoriesItem = directions[i];
  29288. fields = me.getFields([
  29289. fieldCategoriesItem
  29290. ]);
  29291. fieldCount = fields.length;
  29292. for (j = 0; j < itemCount; j++) {
  29293. dataStart[j] = 0;
  29294. posDataStart[j] = 0;
  29295. negDataStart[j] = 0;
  29296. }
  29297. for (j = 0; j < fieldCount; j++) {
  29298. if (!hidden[j]) {
  29299. coordinatedData[j] = me.coordinateData(items, fields[j], axis);
  29300. }
  29301. }
  29302. if (stacked && fullStack) {
  29303. posTotals = [];
  29304. if (splitStacks) {
  29305. negTotals = [];
  29306. }
  29307. for (j = 0; j < itemCount; j++) {
  29308. posTotals[j] = 0;
  29309. if (splitStacks) {
  29310. negTotals[j] = 0;
  29311. }
  29312. for (k = 0; k < fieldCount; k++) {
  29313. data = coordinatedData[k];
  29314. if (!data) {
  29315. // If the field is hidden there's no coordinated data for it.
  29316. continue;
  29317. }
  29318. data = data[j];
  29319. if (data >= 0 || !splitStacks) {
  29320. posTotals[j] += data;
  29321. } else if (data < 0) {
  29322. negTotals[j] += data;
  29323. }
  29324. }
  29325. }
  29326. }
  29327. // else not a valid number
  29328. for (j = 0; j < fieldCount; j++) {
  29329. attr = {};
  29330. if (hidden[j]) {
  29331. attr['dataStart' + fieldCategoriesItem] = dataStart;
  29332. attr['data' + fieldCategoriesItem] = dataStart;
  29333. sprites[j].setAttributes(attr);
  29334. continue;
  29335. }
  29336. data = coordinatedData[j];
  29337. if (stacked) {
  29338. dataEnd = [];
  29339. for (k = 0; k < itemCount; k++) {
  29340. if (!data[k]) {
  29341. data[k] = 0;
  29342. }
  29343. if (data[k] >= 0 || !splitStacks) {
  29344. if (fullStack && posTotals[k]) {
  29345. data[k] *= fullStackTotal / posTotals[k];
  29346. }
  29347. dataStart[k] = posDataStart[k];
  29348. posDataStart[k] += data[k];
  29349. dataEnd[k] = posDataStart[k];
  29350. } else {
  29351. if (fullStack && negTotals[k]) {
  29352. data[k] *= fullStackTotal / negTotals[k];
  29353. }
  29354. dataStart[k] = negDataStart[k];
  29355. negDataStart[k] += data[k];
  29356. dataEnd[k] = negDataStart[k];
  29357. }
  29358. }
  29359. attr['dataStart' + fieldCategoriesItem] = dataStart;
  29360. attr['data' + fieldCategoriesItem] = dataEnd;
  29361. Ext.chart.Util.expandRange(range, dataStart);
  29362. Ext.chart.Util.expandRange(range, dataEnd);
  29363. } else {
  29364. attr['dataStart' + fieldCategoriesItem] = dataStart;
  29365. attr['data' + fieldCategoriesItem] = data;
  29366. Ext.chart.Util.expandRange(range, data);
  29367. }
  29368. sprites[j].setAttributes(attr);
  29369. }
  29370. }
  29371. range = Ext.chart.Util.validateRange(range, me.defaultRange);
  29372. me.dataRange[directionOffset] = range[0];
  29373. me.dataRange[directionOffset + directionCount] = range[1];
  29374. attr = {};
  29375. attr['dataMin' + direction] = range[0];
  29376. attr['dataMax' + direction] = range[1];
  29377. for (i = 0; i < sprites.length; i++) {
  29378. sprites[i].setAttributes(attr);
  29379. }
  29380. },
  29381. getFields: function(fieldCategory) {
  29382. var me = this,
  29383. fields = [],
  29384. ln = fieldCategory.length,
  29385. i, fieldsItem;
  29386. for (i = 0; i < ln; i++) {
  29387. fieldsItem = me['get' + fieldCategory[i] + 'Field']();
  29388. if (Ext.isArray(fieldsItem)) {
  29389. fields.push.apply(fields, fieldsItem);
  29390. } else {
  29391. fields.push(fieldsItem);
  29392. }
  29393. }
  29394. return fields;
  29395. },
  29396. updateLabelOverflowPadding: function(labelOverflowPadding) {
  29397. var me = this,
  29398. label;
  29399. if (!me.isConfiguring) {
  29400. label = me.getLabel();
  29401. if (label) {
  29402. label.setAttributes({
  29403. labelOverflowPadding: labelOverflowPadding
  29404. });
  29405. }
  29406. }
  29407. },
  29408. updateLabelData: function() {
  29409. var me = this,
  29410. label = me.getLabel();
  29411. if (label) {
  29412. label.setAttributes({
  29413. labelOverflowPadding: me.getLabelOverflowPadding()
  29414. });
  29415. }
  29416. me.callParent();
  29417. },
  29418. getSprites: function() {
  29419. var me = this,
  29420. chart = me.getChart(),
  29421. fields = me.getFields(me.fieldCategoryY),
  29422. itemInstancing = me.getItemInstancing(),
  29423. sprites = me.sprites,
  29424. hidden = me.getHidden(),
  29425. spritesCreated = false,
  29426. fieldCount = fields.length,
  29427. i, sprite;
  29428. if (!chart) {
  29429. return [];
  29430. }
  29431. // Create one Ext.chart.series.sprite.StackedCartesian sprite per field.
  29432. for (i = 0; i < fieldCount; i++) {
  29433. sprite = sprites[i];
  29434. if (!sprite) {
  29435. sprite = me.createSprite();
  29436. sprite.setAttributes({
  29437. zIndex: (me.reversedSpriteZOrder ? -1 : 1) * i
  29438. });
  29439. sprite.setField(fields[i]);
  29440. spritesCreated = true;
  29441. hidden.push(false);
  29442. if (itemInstancing) {
  29443. sprite.getMarker('items').getTemplate().setAttributes(me.getStyleByIndex(i));
  29444. } else {
  29445. sprite.setAttributes(me.getStyleByIndex(i));
  29446. }
  29447. }
  29448. }
  29449. if (spritesCreated) {
  29450. me.updateHidden(hidden);
  29451. }
  29452. return sprites;
  29453. },
  29454. getItemForPoint: function(x, y) {
  29455. var me = this,
  29456. sprites = me.getSprites(),
  29457. store = me.getStore(),
  29458. hidden = me.getHidden(),
  29459. minDistance = Infinity,
  29460. item = null,
  29461. spriteIndex = -1,
  29462. pointIndex = -1,
  29463. point, yField, sprite, i, ln;
  29464. for (i = 0 , ln = sprites.length; i < ln; i++) {
  29465. if (hidden[i]) {
  29466. continue;
  29467. }
  29468. sprite = sprites[i];
  29469. point = sprite.getNearestDataPoint(x, y);
  29470. // Don't stop when the first matching point is found.
  29471. // Keep looking for the nearest point.
  29472. if (point) {
  29473. if (point.distance < minDistance) {
  29474. minDistance = point.distance;
  29475. pointIndex = point.index;
  29476. spriteIndex = i;
  29477. }
  29478. }
  29479. }
  29480. if (spriteIndex > -1) {
  29481. yField = me.getYField();
  29482. item = {
  29483. series: me,
  29484. sprite: sprites[spriteIndex],
  29485. category: me.getItemInstancing() ? 'items' : 'markers',
  29486. index: pointIndex,
  29487. record: store.getData().items[pointIndex],
  29488. // Handle the case where we're stacked but a single segment
  29489. field: typeof yField === 'string' ? yField : yField[spriteIndex],
  29490. distance: minDistance
  29491. };
  29492. }
  29493. return item;
  29494. },
  29495. provideLegendInfo: function(target) {
  29496. var me = this,
  29497. sprites = me.getSprites(),
  29498. title = me.getTitle(),
  29499. field = me.getYField(),
  29500. hidden = me.getHidden(),
  29501. single = sprites.length === 1,
  29502. style, fill, i, name;
  29503. for (i = 0; i < sprites.length; i++) {
  29504. style = me.getStyleByIndex(i);
  29505. fill = style.fillStyle;
  29506. if (title) {
  29507. if (Ext.isArray(title)) {
  29508. name = title[i];
  29509. } else if (single) {
  29510. name = title;
  29511. }
  29512. }
  29513. if (!title || !name) {
  29514. if (Ext.isArray(field)) {
  29515. name = field[i];
  29516. } else {
  29517. name = me.getId();
  29518. }
  29519. }
  29520. target.push({
  29521. name: name,
  29522. mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
  29523. disabled: hidden[i],
  29524. series: me.getId(),
  29525. index: i
  29526. });
  29527. }
  29528. },
  29529. onSpriteAnimationStart: function(sprite) {
  29530. this.spriteAnimationCount++;
  29531. if (this.spriteAnimationCount === 1) {
  29532. this.fireEvent('animationstart');
  29533. }
  29534. },
  29535. onSpriteAnimationEnd: function(sprite) {
  29536. this.spriteAnimationCount--;
  29537. if (this.spriteAnimationCount === 0) {
  29538. this.fireEvent('animationend');
  29539. }
  29540. }
  29541. });
  29542. /**
  29543. * Base class for all series sprites.
  29544. * Defines attributes common to all series sprites, like data in x/y directions and its min/max values,
  29545. * and configs, like the {@link Ext.chart.series.Series} instance that manages the sprite.
  29546. *
  29547. */
  29548. Ext.define('Ext.chart.series.sprite.Series', {
  29549. extend: 'Ext.draw.sprite.Sprite',
  29550. mixins: {
  29551. markerHolder: 'Ext.chart.MarkerHolder'
  29552. },
  29553. inheritableStatics: {
  29554. def: {
  29555. processors: {
  29556. /**
  29557. * @cfg {Number} [dataMinX=0] Data minimum on the x-axis.
  29558. */
  29559. dataMinX: 'number',
  29560. /**
  29561. * @cfg {Number} [dataMaxX=1] Data maximum on the x-axis.
  29562. */
  29563. dataMaxX: 'number',
  29564. /**
  29565. * @cfg {Number} [dataMinY=0] Data minimum on the y-axis.
  29566. */
  29567. dataMinY: 'number',
  29568. /**
  29569. * @cfg {Number} [dataMaxY=1] Data maximum on the y-axis.
  29570. */
  29571. dataMaxY: 'number',
  29572. /**
  29573. * @cfg {Array} [rangeX=null] Data range derived from all the series bound to the x-axis.
  29574. */
  29575. rangeX: 'data',
  29576. /**
  29577. * @cfg {Array} [rangeY=null] Data range derived from all the series bound to the y-axis.
  29578. */
  29579. rangeY: 'data',
  29580. /**
  29581. * @cfg {Object} [dataX=null] Data items on the x-axis.
  29582. */
  29583. dataX: 'data',
  29584. /**
  29585. * @cfg {Object} [dataY=null] Data items on the y-axis.
  29586. */
  29587. dataY: 'data',
  29588. /**
  29589. * @cfg {Object} [labels=null] Labels used in the series.
  29590. */
  29591. labels: 'default',
  29592. /**
  29593. * @cfg {Number} [labelOverflowPadding=10] Padding around labels to determine overlap.
  29594. */
  29595. labelOverflowPadding: 'number'
  29596. },
  29597. defaults: {
  29598. dataMinX: 0,
  29599. dataMaxX: 1,
  29600. dataMinY: 0,
  29601. dataMaxY: 1,
  29602. rangeX: null,
  29603. rangeY: null,
  29604. dataX: null,
  29605. dataY: null,
  29606. labels: null,
  29607. labelOverflowPadding: 10
  29608. },
  29609. triggers: {
  29610. dataX: 'bbox',
  29611. dataY: 'bbox',
  29612. dataMinX: 'bbox',
  29613. dataMaxX: 'bbox',
  29614. dataMinY: 'bbox',
  29615. dataMaxY: 'bbox'
  29616. }
  29617. }
  29618. },
  29619. config: {
  29620. /**
  29621. * @private
  29622. * @cfg {Object} store The store that is passed to the renderer.
  29623. */
  29624. store: null,
  29625. series: null,
  29626. /**
  29627. * @cfg {String} field The store field used by the series.
  29628. */
  29629. field: null
  29630. }
  29631. });
  29632. /**
  29633. * Cartesian sprite.
  29634. */
  29635. Ext.define('Ext.chart.series.sprite.Cartesian', {
  29636. extend: 'Ext.chart.series.sprite.Series',
  29637. inheritableStatics: {
  29638. def: {
  29639. processors: {
  29640. /**
  29641. * @cfg {Number} [selectionTolerance=20]
  29642. * The distance from the event position to the sprite's data points to trigger interactions (used for 'iteminfo', etc).
  29643. */
  29644. selectionTolerance: 'number',
  29645. /**
  29646. * @cfg {Boolean} flipXY If flipXY is 'true', the series is flipped.
  29647. */
  29648. flipXY: 'bool',
  29649. renderer: 'default',
  29650. // Visible range of data (pan/zoom) information.
  29651. visibleMinX: 'number',
  29652. visibleMinY: 'number',
  29653. visibleMaxX: 'number',
  29654. visibleMaxY: 'number',
  29655. innerWidth: 'number',
  29656. innerHeight: 'number'
  29657. },
  29658. defaults: {
  29659. selectionTolerance: 20,
  29660. flipXY: false,
  29661. renderer: null,
  29662. transformFillStroke: false,
  29663. visibleMinX: 0,
  29664. visibleMinY: 0,
  29665. visibleMaxX: 1,
  29666. visibleMaxY: 1,
  29667. innerWidth: 1,
  29668. innerHeight: 1
  29669. },
  29670. triggers: {
  29671. dataX: 'dataX,bbox',
  29672. dataY: 'dataY,bbox',
  29673. visibleMinX: 'panzoom',
  29674. visibleMinY: 'panzoom',
  29675. visibleMaxX: 'panzoom',
  29676. visibleMaxY: 'panzoom',
  29677. innerWidth: 'panzoom',
  29678. innerHeight: 'panzoom'
  29679. },
  29680. updaters: {
  29681. dataX: function(attr) {
  29682. this.processDataX();
  29683. this.scheduleUpdater(attr, 'dataY', [
  29684. 'dataY'
  29685. ]);
  29686. },
  29687. dataY: function() {
  29688. this.processDataY();
  29689. },
  29690. panzoom: function(attr) {
  29691. // dx, dy are deltas between min & max of coordinated data values.
  29692. var dx = attr.visibleMaxX - attr.visibleMinX,
  29693. dy = attr.visibleMaxY - attr.visibleMinY,
  29694. innerWidth = attr.flipXY ? attr.innerHeight : attr.innerWidth,
  29695. innerHeight = !attr.flipXY ? attr.innerHeight : attr.innerWidth,
  29696. surface = this.getSurface(),
  29697. isRtl = surface ? surface.getInherited().rtl : false;
  29698. attr.scalingCenterX = 0;
  29699. attr.scalingCenterY = 0;
  29700. attr.scalingX = innerWidth / dx;
  29701. attr.scalingY = innerHeight / dy;
  29702. // (attr.visibleMinY * attr.scalingY) will be the vertical position of
  29703. // our minimum data points, which we want to be at zero, so we offset
  29704. // by this amount.
  29705. attr.translationX = -(attr.visibleMinX * attr.scalingX);
  29706. attr.translationY = -(attr.visibleMinY * attr.scalingY);
  29707. if (isRtl && !attr.flipXY) {
  29708. attr.scalingX *= -1;
  29709. attr.translationX *= -1;
  29710. attr.translationX += innerWidth;
  29711. }
  29712. this.applyTransformations(true);
  29713. }
  29714. }
  29715. }
  29716. },
  29717. processDataY: Ext.emptyFn,
  29718. processDataX: Ext.emptyFn,
  29719. updatePlainBBox: function(plain) {
  29720. var attr = this.attr;
  29721. plain.x = attr.dataMinX;
  29722. plain.y = attr.dataMinY;
  29723. plain.width = attr.dataMaxX - attr.dataMinX;
  29724. plain.height = attr.dataMaxY - attr.dataMinY;
  29725. },
  29726. /**
  29727. * Does a binary search of the data on the x-axis using the given key.
  29728. * @param {String} key
  29729. * @return {*}
  29730. */
  29731. binarySearch: function(key) {
  29732. var dx = this.attr.dataX,
  29733. start = 0,
  29734. end = dx.length;
  29735. if (key <= dx[0]) {
  29736. return start;
  29737. }
  29738. if (key >= dx[end - 1]) {
  29739. return end - 1;
  29740. }
  29741. while (start + 1 < end) {
  29742. var mid = (start + end) >> 1,
  29743. val = dx[mid];
  29744. if (val === key) {
  29745. return mid;
  29746. } else if (val < key) {
  29747. start = mid;
  29748. } else {
  29749. end = mid;
  29750. }
  29751. }
  29752. return start;
  29753. },
  29754. render: function(surface, ctx, surfaceClipRect) {
  29755. var me = this,
  29756. attr = me.attr,
  29757. margin = 1,
  29758. // TODO: why do we need it?
  29759. inverseMatrix = attr.inverseMatrix.clone();
  29760. // The sprite's `attr.matrix` is stretching/shrinking data coordinates
  29761. // to surface coordinates.
  29762. // This matrix is set (indirectly) by the 'panzoom' updater.
  29763. // The sprite's `attr.inverseMatrix` does the opposite.
  29764. //
  29765. // The `surface.matrix` of the 'series' surface of a cartesian chart flips the
  29766. // surface content vertically, so that y=0 is at the bottom (look for
  29767. // `surface.matrix.set` call in the CartesianChart.performLayout method).
  29768. // This matrix is set in the 'performLayout' of the CartesianChart.
  29769. // The `surface.inverseMatrix` flips the content back.
  29770. //
  29771. // By combining the inverse matrices of the series surface and the series sprite,
  29772. // we essentially get a transformation that allows us to go from surface coordinates
  29773. // in a final flipped drawing back to data points.
  29774. //
  29775. // For example
  29776. //
  29777. // inverseMatrix.transformPoint([ 0, rect[3] ])
  29778. // inverseMatrix.transformPoint([ rect[2], 0 ])
  29779. //
  29780. // will return
  29781. //
  29782. // [attr.dataMinX, attr.dataMinY]
  29783. // [attr.dataMaxX, attr.dataMaxY]
  29784. //
  29785. // because left/bottom and top/right of the series surface is where the first smallest
  29786. // and last largest data points would be (given no pan/zoom), respectively.
  29787. //
  29788. // So the `dataClipRect` passed to the `renderClipped` call below is effectively
  29789. // the visible rect in data (not surface!) coordinates.
  29790. // It is important to note, that the all the scaling and translation is defined
  29791. // by the sprite's matrix, the 'series' surface matrix does not contain scaling
  29792. // or translation components, except for the vertical flipping.
  29793. // This is important because there is a common pattern in chart series sprites
  29794. // (MarkerHolders) - instead of using transform attributes for their Markers
  29795. // (e.g. instances of a 'rect' sprite in case of 'bar' series), the attributes
  29796. // that would position a sprite with no transformations are transformed.
  29797. // For example, to draw a rect with coordinates TL(10, 10), BR(20, 40),
  29798. // we could use the folling 'rect' sprite attributes:
  29799. //
  29800. // {
  29801. // x: 0,
  29802. // y: 0
  29803. // width: 10,
  29804. // height: 30
  29805. //
  29806. // translationX: 10,
  29807. // translationY: 10
  29808. //
  29809. // But the correct thing to do here is
  29810. //
  29811. // {
  29812. // x: 10,
  29813. // y: 10,
  29814. // width: 10,
  29815. // height: 30
  29816. // }
  29817. //
  29818. // Similarly, if the sprite was scaled, the 'x', 'y', 'width', 'height' attributes
  29819. // would have to account for that as well.
  29820. //
  29821. // This is done, so that the attribute values a marker gets by the time it renders,
  29822. // are the final values, and are not affected later by other transforms, such as
  29823. // surface matrix scaling, which could ruin the visual result, if the attributes
  29824. // values are doctored to make lines align to the pixel grid (which is typically
  29825. // the case).
  29826. inverseMatrix.appendMatrix(surface.inverseMatrix);
  29827. if (attr.dataX === null || attr.dataX === undefined) {
  29828. return;
  29829. }
  29830. if (attr.dataY === null || attr.dataY === undefined) {
  29831. return;
  29832. }
  29833. if (inverseMatrix.getXX() * inverseMatrix.getYX() || inverseMatrix.getXY() * inverseMatrix.getYY()) {
  29834. Ext.Logger.warn('Cartesian Series sprite does not support rotation/sheering');
  29835. return;
  29836. }
  29837. var dataClipRect = inverseMatrix.transformList([
  29838. [
  29839. surfaceClipRect[0] - margin,
  29840. surfaceClipRect[3] + margin
  29841. ],
  29842. // (left, height)
  29843. [
  29844. surfaceClipRect[0] + surfaceClipRect[2] + margin,
  29845. -margin
  29846. ]
  29847. ]);
  29848. // (width, top)
  29849. dataClipRect = dataClipRect[0].concat(dataClipRect[1]);
  29850. // TODO: RTL improvements:
  29851. // TODO: produce such a dataClipRect here, so that we don't have to do:
  29852. // TODO: min = Math.min(dataClipRect[0], dataClipRect[2])
  29853. // TODO: max = Math.max(dataClipRect[0], dataClipRect[2])
  29854. // TODO: inside each 'renderClipped' call
  29855. me.renderClipped(surface, ctx, dataClipRect, surfaceClipRect);
  29856. },
  29857. /**
  29858. * Render the given visible clip range.
  29859. * @param {Ext.draw.Surface} surface A draw container surface.
  29860. * @param {CanvasRenderingContext2D} ctx A context object that is API compatible with the native
  29861. * [CanvasRenderingContext2D](https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D).
  29862. * @param {Number[]} dataClipRect The clip rect in data coordinates, roughly equivalent to
  29863. * [attr.dataMinX, attr.dataMinY, attr.dataMaxX, attr.dataMaxY] for an untranslated/unscaled surface/sprite.
  29864. * @param {Number[]} surfaceClipRect The clip rect in surface coordinates: [left, top, width, height].
  29865. * @method
  29866. */
  29867. renderClipped: Ext.emptyFn,
  29868. /**
  29869. * Get the nearest item index from point (x, y). -1 as not found.
  29870. * @param {Number} x
  29871. * @param {Number} y
  29872. * @return {Number} The index
  29873. * @deprecated 6.5.2 Use {@link #getNearestDataPoint} instead.
  29874. */
  29875. getIndexNearPoint: function(x, y) {
  29876. var result = this.getNearestDataPoint(x, y);
  29877. return result ? result.index : -1;
  29878. },
  29879. /**
  29880. * Given a point in 'series' surface element coordinates, returns the `index` of the
  29881. * sprite's data point that is nearest to that point, along with the `distance`
  29882. * between points.
  29883. * If the `selectionTolerance` attribute of the sprite is not zero, only the data points
  29884. * that are within that pixel distance from the given point will be checked.
  29885. * In the event no such data points exist or the data is empty, `null` is returned.
  29886. *
  29887. * Notes:
  29888. * 1) given a mouse/pointer event object, the surface coordinates of the event can be
  29889. * obtained with the `getEventXY` method of the chart;
  29890. * 2) using `selectionTolerance` of zero is useful for series with no visible markers,
  29891. * such as the Area series, where this attribute becomes meaningless.
  29892. *
  29893. * @param {Number} x
  29894. * @param {Number} y
  29895. * @return {Object}
  29896. */
  29897. getNearestDataPoint: function(x, y) {
  29898. var me = this,
  29899. attr = me.attr,
  29900. series = me.getSeries(),
  29901. surface = me.getSurface(),
  29902. items = me.boundMarkers.items,
  29903. matrix = attr.matrix,
  29904. dataX = attr.dataX,
  29905. dataY = attr.dataY,
  29906. selectionTolerance = attr.selectionTolerance,
  29907. minDistance = Infinity,
  29908. index = -1,
  29909. result = null,
  29910. distance, dx, dy, xy, i, ln, end, inc;
  29911. // Notes:
  29912. // Instead of converting the given point from surface coordinates to data coordinates
  29913. // and then measuring the distances between it and the data points, we have to
  29914. // convert all the data points to surface coordinates and measure the distances
  29915. // between them and the given point. This is because the data coordinates can use
  29916. // different scales, which makes distance measurement impossible.
  29917. // For example, if the x-axis is a `category` axis, the categories will be assigned
  29918. // indexes starting from 0, that's what the `attr.dataX` array will contain;
  29919. // and if the y-axis is a `numeric` axis, the `attr.dataY` array will simply contain
  29920. // the original values.
  29921. //
  29922. // Either 'items' or 'markers' will be highlighted. If a sprite has both (for example,
  29923. // 'bar' series with the 'marker' config, where the bars are 'items' and marker instances
  29924. // are 'markers'), only the 'items' (bars) will be highlighted.
  29925. if (items) {
  29926. ln = dataX.length;
  29927. if (series.reversedSpriteZOrder) {
  29928. i = ln - 1;
  29929. end = -1;
  29930. inc = -1;
  29931. } else {
  29932. i = 0;
  29933. end = ln;
  29934. inc = 1;
  29935. }
  29936. for (; i !== end; i += inc) {
  29937. var bbox = me.getMarkerBBox('items', i);
  29938. // Transform the given surface element coordinates to logical coordinates
  29939. // of the surface (the ones the bbox uses).
  29940. xy = surface.inverseMatrix.transformPoint([
  29941. x,
  29942. y
  29943. ]);
  29944. if (Ext.draw.Draw.isPointInBBox(xy[0], xy[1], bbox)) {
  29945. index = i;
  29946. minDistance = 0;
  29947. // Return the first item that contains our touch point.
  29948. break;
  29949. }
  29950. }
  29951. } else {
  29952. // markers
  29953. for (i = 0 , ln = dataX.length; i < ln; i++) {
  29954. // Convert from data coordinates to coordinates within inner size rectangle.
  29955. // See `panzoom` method for more details.
  29956. xy = matrix.transformPoint([
  29957. dataX[i],
  29958. dataY[i]
  29959. ]);
  29960. // Flip back vertically and padding adjust (see `render` method comments).
  29961. xy = surface.matrix.transformPoint(xy);
  29962. // Essentially sprites go through the same two transformations when they render
  29963. // data points.
  29964. dx = x - xy[0];
  29965. dy = y - xy[1];
  29966. distance = Math.sqrt(dx * dx + dy * dy);
  29967. if (selectionTolerance && distance > selectionTolerance) {
  29968. continue;
  29969. }
  29970. if (distance < minDistance) {
  29971. minDistance = distance;
  29972. index = i;
  29973. }
  29974. }
  29975. }
  29976. // Keep looking for the nearest marker.
  29977. if (index > -1) {
  29978. result = {
  29979. index: index,
  29980. distance: minDistance
  29981. };
  29982. }
  29983. return result;
  29984. }
  29985. });
  29986. /**
  29987. * @class Ext.chart.series.sprite.StackedCartesian
  29988. * @extends Ext.chart.series.sprite.Cartesian
  29989. *
  29990. * Stacked cartesian sprite.
  29991. */
  29992. Ext.define('Ext.chart.series.sprite.StackedCartesian', {
  29993. extend: 'Ext.chart.series.sprite.Cartesian',
  29994. inheritableStatics: {
  29995. def: {
  29996. processors: {
  29997. /**
  29998. * @private
  29999. * @cfg {Number} [groupCount=1] The number of items (e.g. bars) in a group.
  30000. */
  30001. groupCount: 'number',
  30002. /**
  30003. * @private
  30004. * @cfg {Number} [groupOffset=0] The group index of the series sprite.
  30005. */
  30006. groupOffset: 'number',
  30007. /**
  30008. * @private
  30009. * @cfg {Object} [dataStartY=null] The starting point of the data used in the series.
  30010. */
  30011. dataStartY: 'data'
  30012. },
  30013. defaults: {
  30014. selectionTolerance: 20,
  30015. groupCount: 1,
  30016. groupOffset: 0,
  30017. dataStartY: null
  30018. },
  30019. triggers: {
  30020. dataStartY: 'dataY,bbox'
  30021. }
  30022. }
  30023. }
  30024. });
  30025. /**
  30026. * @class Ext.chart.series.sprite.Area
  30027. * @extends Ext.chart.series.sprite.StackedCartesian
  30028. *
  30029. * Area series sprite.
  30030. */
  30031. Ext.define('Ext.chart.series.sprite.Area', {
  30032. alias: 'sprite.areaSeries',
  30033. extend: 'Ext.chart.series.sprite.StackedCartesian',
  30034. inheritableStatics: {
  30035. def: {
  30036. processors: {
  30037. /**
  30038. * @cfg {Boolean} [step=false] 'true' if the area is represented with steps instead of lines.
  30039. */
  30040. step: 'bool'
  30041. },
  30042. defaults: {
  30043. selectionTolerance: 0,
  30044. step: false
  30045. }
  30046. }
  30047. },
  30048. renderClipped: function(surface, ctx, dataClipRect) {
  30049. var me = this,
  30050. store = me.getStore(),
  30051. series = me.getSeries(),
  30052. attr = me.attr,
  30053. dataX = attr.dataX,
  30054. dataY = attr.dataY,
  30055. dataStartY = attr.dataStartY,
  30056. matrix = attr.matrix,
  30057. x, y, i, lastX, lastY, startX, startY,
  30058. xx = matrix.elements[0],
  30059. dx = matrix.elements[4],
  30060. yy = matrix.elements[3],
  30061. dy = matrix.elements[5],
  30062. surfaceMatrix = me.surfaceMatrix,
  30063. markerCfg = {},
  30064. min = Math.min(dataClipRect[0], dataClipRect[2]),
  30065. max = Math.max(dataClipRect[0], dataClipRect[2]),
  30066. start = Math.max(0, this.binarySearch(min)),
  30067. end = Math.min(dataX.length - 1, this.binarySearch(max) + 1),
  30068. renderer = attr.renderer,
  30069. rendererData = {
  30070. store: store
  30071. },
  30072. rendererChanges;
  30073. ctx.beginPath();
  30074. startX = dataX[start] * xx + dx;
  30075. startY = dataY[start] * yy + dy;
  30076. ctx.moveTo(startX, startY);
  30077. if (attr.step) {
  30078. lastY = startY;
  30079. for (i = start; i <= end; i++) {
  30080. x = dataX[i] * xx + dx;
  30081. y = dataY[i] * yy + dy;
  30082. ctx.lineTo(x, lastY);
  30083. ctx.lineTo(x, lastY = y);
  30084. }
  30085. } else {
  30086. for (i = start; i <= end; i++) {
  30087. x = dataX[i] * xx + dx;
  30088. y = dataY[i] * yy + dy;
  30089. ctx.lineTo(x, y);
  30090. }
  30091. }
  30092. if (dataStartY) {
  30093. if (attr.step) {
  30094. lastX = dataX[end] * xx + dx;
  30095. for (i = end; i >= start; i--) {
  30096. x = dataX[i] * xx + dx;
  30097. y = dataStartY[i] * yy + dy;
  30098. ctx.lineTo(lastX, y);
  30099. ctx.lineTo(lastX = x, y);
  30100. }
  30101. } else {
  30102. for (i = end; i >= start; i--) {
  30103. x = dataX[i] * xx + dx;
  30104. y = dataStartY[i] * yy + dy;
  30105. ctx.lineTo(x, y);
  30106. }
  30107. }
  30108. } else {
  30109. ctx.lineTo(dataX[end] * xx + dx, y);
  30110. ctx.lineTo(dataX[end] * xx + dx, dy);
  30111. ctx.lineTo(startX, dy);
  30112. ctx.lineTo(startX, dataY[i] * yy + dy);
  30113. }
  30114. if (attr.transformFillStroke) {
  30115. attr.matrix.toContext(ctx);
  30116. }
  30117. ctx.fill();
  30118. if (attr.transformFillStroke) {
  30119. attr.inverseMatrix.toContext(ctx);
  30120. }
  30121. ctx.beginPath();
  30122. ctx.moveTo(startX, startY);
  30123. if (attr.step) {
  30124. for (i = start; i <= end; i++) {
  30125. x = dataX[i] * xx + dx;
  30126. y = dataY[i] * yy + dy;
  30127. ctx.lineTo(x, lastY);
  30128. ctx.lineTo(x, lastY = y);
  30129. markerCfg.translationX = surfaceMatrix.x(x, y);
  30130. markerCfg.translationY = surfaceMatrix.y(x, y);
  30131. if (renderer) {
  30132. // callback(fn, scope, args, delay, caller)
  30133. rendererChanges = Ext.callback(renderer, null, [
  30134. me,
  30135. markerCfg,
  30136. rendererData,
  30137. i
  30138. ], 0, series);
  30139. Ext.apply(markerCfg, rendererChanges);
  30140. }
  30141. me.putMarker('markers', markerCfg, i, !renderer);
  30142. }
  30143. } else {
  30144. for (i = start; i <= end; i++) {
  30145. x = dataX[i] * xx + dx;
  30146. y = dataY[i] * yy + dy;
  30147. ctx.lineTo(x, y);
  30148. markerCfg.translationX = surfaceMatrix.x(x, y);
  30149. markerCfg.translationY = surfaceMatrix.y(x, y);
  30150. if (renderer) {
  30151. rendererChanges = Ext.callback(renderer, null, [
  30152. me,
  30153. markerCfg,
  30154. rendererData,
  30155. i
  30156. ], 0, series);
  30157. Ext.apply(markerCfg, rendererChanges);
  30158. }
  30159. me.putMarker('markers', markerCfg, i, !renderer);
  30160. }
  30161. }
  30162. if (attr.transformFillStroke) {
  30163. attr.matrix.toContext(ctx);
  30164. }
  30165. ctx.stroke();
  30166. }
  30167. });
  30168. /**
  30169. * @class Ext.chart.series.Area
  30170. * @extends Ext.chart.series.StackedCartesian
  30171. *
  30172. * Creates an Area Chart.
  30173. *
  30174. * @example
  30175. * Ext.create({
  30176. * xtype: 'cartesian',
  30177. * renderTo: document.body,
  30178. * width: 600,
  30179. * height: 400,
  30180. * insetPadding: 40,
  30181. * store: {
  30182. * fields: ['name', 'data1', 'data2', 'data3'],
  30183. * data: [{
  30184. * name: 'metric one',
  30185. * data1: 10,
  30186. * data2: 12,
  30187. * data3: 14
  30188. * }, {
  30189. * name: 'metric two',
  30190. * data1: 7,
  30191. * data2: 8,
  30192. * data3: 16
  30193. * }, {
  30194. * name: 'metric three',
  30195. * data1: 5,
  30196. * data2: 2,
  30197. * data3: 14
  30198. * }, {
  30199. * name: 'metric four',
  30200. * data1: 2,
  30201. * data2: 14,
  30202. * data3: 6
  30203. * }, {
  30204. * name: 'metric five',
  30205. * data1: 27,
  30206. * data2: 38,
  30207. * data3: 36
  30208. * }]
  30209. * },
  30210. * axes: [{
  30211. * type: 'numeric',
  30212. * position: 'left',
  30213. * fields: ['data1'],
  30214. * grid: true,
  30215. * minimum: 0
  30216. * }, {
  30217. * type: 'category',
  30218. * position: 'bottom',
  30219. * fields: ['name']
  30220. * }],
  30221. * series: {
  30222. * type: 'area',
  30223. * subStyle: {
  30224. * fill: ['#0A3F50', '#30BDA7', '#96D4C6']
  30225. * },
  30226. * xField: 'name',
  30227. * yField: ['data1', 'data2', 'data3']
  30228. * }
  30229. * });
  30230. */
  30231. Ext.define('Ext.chart.series.Area', {
  30232. extend: 'Ext.chart.series.StackedCartesian',
  30233. alias: 'series.area',
  30234. type: 'area',
  30235. /**
  30236. * @property seriesType
  30237. * @inheritdoc
  30238. */
  30239. seriesType: 'areaSeries',
  30240. isArea: true,
  30241. requires: [
  30242. 'Ext.chart.series.sprite.Area'
  30243. ],
  30244. config: {
  30245. /**
  30246. * @cfg splitStacks
  30247. * @inheritdoc
  30248. */
  30249. splitStacks: false
  30250. }
  30251. });
  30252. /**
  30253. * @cfg renderer
  30254. * @inheritdoc
  30255. * Area series renderers only affect markers.
  30256. * For styling individual segments with a renderer it is possible to use
  30257. * the Line series with {@link Ext.chart.series.Line#fill} config set to `true`,
  30258. * which makes Line series look like Area series.
  30259. */
  30260. /**
  30261. * @class Ext.chart.series.sprite.Bar
  30262. * @extends Ext.chart.series.sprite.StackedCartesian
  30263. *
  30264. * Draws a sprite used in the bar series.
  30265. */
  30266. Ext.define('Ext.chart.series.sprite.Bar', {
  30267. alias: 'sprite.barSeries',
  30268. extend: 'Ext.chart.series.sprite.StackedCartesian',
  30269. inheritableStatics: {
  30270. def: {
  30271. processors: {
  30272. /**
  30273. * @cfg {Number} [minBarWidth=2] The minimum bar width.
  30274. */
  30275. minBarWidth: 'number',
  30276. /**
  30277. * @cfg {Number} [maxBarWidth=100] The maximum bar width.
  30278. */
  30279. maxBarWidth: 'number',
  30280. /**
  30281. * @cfg {Number} [minGapWidth=5] The minimum gap between bars.
  30282. */
  30283. minGapWidth: 'number',
  30284. /**
  30285. * @cfg {Number} [radius=0] The degree of rounding for rounded bars.
  30286. */
  30287. radius: 'number',
  30288. /**
  30289. * @cfg {Number} [inGroupGapWidth=3] The gap between grouped bars.
  30290. */
  30291. inGroupGapWidth: 'number'
  30292. },
  30293. defaults: {
  30294. minBarWidth: 2,
  30295. maxBarWidth: 100,
  30296. minGapWidth: 5,
  30297. inGroupGapWidth: 3,
  30298. radius: 0
  30299. }
  30300. }
  30301. },
  30302. drawLabel: function(text, dataX, dataStartY, dataY, labelId) {
  30303. var me = this,
  30304. attr = me.attr,
  30305. label = me.getMarker('labels'),
  30306. labelTpl = label.getTemplate(),
  30307. labelCfg = me.labelCfg || (me.labelCfg = {}),
  30308. surfaceMatrix = me.surfaceMatrix,
  30309. labelOverflowPadding = attr.labelOverflowPadding,
  30310. labelDisplay = labelTpl.attr.display,
  30311. labelOrientation = labelTpl.attr.orientation,
  30312. isVerticalText = (labelOrientation === 'horizontal' && attr.flipXY) || (labelOrientation === 'vertical' && !attr.flipXY) || !labelOrientation,
  30313. calloutLine = labelTpl.getCalloutLine(),
  30314. labelY, halfText, labelBBox, calloutLineLength, changes, hasPendingChanges, params;
  30315. // The coordinates below (data point converted to surface coordinates)
  30316. // are just for the renderer to give it a notion of where the label will be positioned.
  30317. // The actual position of the label will be different
  30318. // (unless the renderer returns x/y coordinates in the changes object)
  30319. // and depend on several things including the size of the text,
  30320. // which has to be measured after the renderer call,
  30321. // since text can be modified by the renderer.
  30322. labelCfg.x = surfaceMatrix.x(dataX, dataY);
  30323. labelCfg.y = surfaceMatrix.y(dataX, dataY);
  30324. if (calloutLine) {
  30325. calloutLineLength = calloutLine.length;
  30326. } else {
  30327. calloutLineLength = 0;
  30328. }
  30329. // Set defaults
  30330. if (!attr.flipXY) {
  30331. labelCfg.rotationRads = -Math.PI * 0.5;
  30332. } else {
  30333. labelCfg.rotationRads = 0;
  30334. }
  30335. labelCfg.calloutVertical = !attr.flipXY;
  30336. // Check if we have a specific orientation specified, if so, set
  30337. // the appropriate values.
  30338. switch (labelOrientation) {
  30339. case 'horizontal':
  30340. labelCfg.rotationRads = 0;
  30341. labelCfg.calloutVertical = false;
  30342. break;
  30343. case 'vertical':
  30344. labelCfg.rotationRads = -Math.PI * 0.5;
  30345. labelCfg.calloutVertical = true;
  30346. break;
  30347. }
  30348. labelCfg.text = text;
  30349. if (labelTpl.attr.renderer) {
  30350. // The label instance won't exist on first render before the renderer is called,
  30351. // it's only created later by `me.putMarker` after the renderer call. To make
  30352. // sure the renderer always can access the label instance, we make this check here.
  30353. if (!label.get(labelId)) {
  30354. label.putMarkerFor('labels', {}, labelId);
  30355. }
  30356. params = [
  30357. text,
  30358. label,
  30359. labelCfg,
  30360. {
  30361. store: me.getStore()
  30362. },
  30363. labelId
  30364. ];
  30365. changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
  30366. if (typeof changes === 'string') {
  30367. labelCfg.text = changes;
  30368. } else if (typeof changes === 'object') {
  30369. if ('text' in changes) {
  30370. labelCfg.text = changes.text;
  30371. }
  30372. hasPendingChanges = true;
  30373. }
  30374. }
  30375. labelBBox = me.getMarkerBBox('labels', labelId, true);
  30376. if (!labelBBox) {
  30377. me.putMarker('labels', labelCfg, labelId);
  30378. labelBBox = me.getMarkerBBox('labels', labelId, true);
  30379. }
  30380. if (calloutLineLength > 0) {
  30381. halfText = calloutLineLength;
  30382. } else if (calloutLineLength === 0) {
  30383. halfText = (isVerticalText ? labelBBox.width : labelBBox.height) / 2;
  30384. } else {
  30385. halfText = (isVerticalText ? labelBBox.width : labelBBox.height) / 2 + labelOverflowPadding;
  30386. }
  30387. if (dataStartY > dataY) {
  30388. halfText = -halfText;
  30389. }
  30390. if (isVerticalText) {
  30391. labelY = (labelDisplay === 'insideStart') ? dataStartY + halfText : dataY - halfText;
  30392. } else {
  30393. labelY = (labelDisplay === 'insideStart') ? dataStartY + labelOverflowPadding * 2 : dataY - labelOverflowPadding * 2;
  30394. }
  30395. labelCfg.x = surfaceMatrix.x(dataX, labelY);
  30396. labelCfg.y = surfaceMatrix.y(dataX, labelY);
  30397. labelY = (labelDisplay === 'insideStart') ? dataStartY : dataY;
  30398. labelCfg.calloutStartX = surfaceMatrix.x(dataX, labelY);
  30399. labelCfg.calloutStartY = surfaceMatrix.y(dataX, labelY);
  30400. labelY = (labelDisplay === 'insideStart') ? dataStartY - halfText : dataY + halfText;
  30401. labelCfg.calloutPlaceX = surfaceMatrix.x(dataX, labelY);
  30402. labelCfg.calloutPlaceY = surfaceMatrix.y(dataX, labelY);
  30403. labelCfg.calloutColor = (calloutLine && calloutLine.color) || me.attr.fillStyle;
  30404. if (calloutLine) {
  30405. if (calloutLine.width) {
  30406. labelCfg.calloutWidth = calloutLine.width;
  30407. }
  30408. } else {
  30409. labelCfg.calloutColor = 'none';
  30410. }
  30411. if (dataStartY > dataY) {
  30412. halfText = -halfText;
  30413. }
  30414. if (Math.abs(dataY - dataStartY) <= halfText * 2 || labelDisplay === 'outside') {
  30415. labelCfg.callout = 1;
  30416. } else {
  30417. labelCfg.callout = 0;
  30418. }
  30419. if (hasPendingChanges) {
  30420. Ext.apply(labelCfg, changes);
  30421. }
  30422. me.putMarker('labels', labelCfg, labelId);
  30423. },
  30424. drawBar: function(ctx, surface, rect, left, top, right, bottom, index) {
  30425. var me = this,
  30426. itemCfg = {},
  30427. renderer = me.attr.renderer,
  30428. changes;
  30429. itemCfg.x = left;
  30430. itemCfg.y = top;
  30431. itemCfg.width = right - left;
  30432. itemCfg.height = bottom - top;
  30433. itemCfg.radius = me.attr.radius;
  30434. if (renderer) {
  30435. changes = Ext.callback(renderer, null, [
  30436. me,
  30437. itemCfg,
  30438. {
  30439. store: me.getStore()
  30440. },
  30441. index
  30442. ], 0, me.getSeries());
  30443. Ext.apply(itemCfg, changes);
  30444. }
  30445. me.putMarker('items', itemCfg, index, !renderer);
  30446. },
  30447. renderClipped: function(surface, ctx, dataClipRect) {
  30448. if (this.cleanRedraw) {
  30449. return;
  30450. }
  30451. var me = this,
  30452. attr = me.attr,
  30453. dataX = attr.dataX,
  30454. dataY = attr.dataY,
  30455. dataText = attr.labels,
  30456. dataStartY = attr.dataStartY,
  30457. groupCount = attr.groupCount,
  30458. groupOffset = attr.groupOffset - (groupCount - 1) * 0.5,
  30459. inGroupGapWidth = attr.inGroupGapWidth,
  30460. lineWidth = ctx.lineWidth,
  30461. matrix = attr.matrix,
  30462. xx = matrix.elements[0],
  30463. yy = matrix.elements[3],
  30464. dx = matrix.elements[4],
  30465. dy = surface.roundPixel(matrix.elements[5]) - 1,
  30466. maxBarWidth = Math.abs(xx) - attr.minGapWidth,
  30467. minBarWidth = (Math.min(maxBarWidth, attr.maxBarWidth) - inGroupGapWidth * (groupCount - 1)) / groupCount,
  30468. barWidth = surface.roundPixel(Math.max(attr.minBarWidth, minBarWidth)),
  30469. surfaceMatrix = me.surfaceMatrix,
  30470. left, right, bottom, top, i, center,
  30471. halfLineWidth = 0.5 * attr.lineWidth,
  30472. // Finding min/max so that bars render properly in both LTR and RTL modes.
  30473. min = Math.min(dataClipRect[0], dataClipRect[2]),
  30474. max = Math.max(dataClipRect[0], dataClipRect[2]),
  30475. start = Math.max(0, Math.floor(min)),
  30476. end = Math.min(dataX.length - 1, Math.ceil(max)),
  30477. isDrawLabels = dataText && me.getMarker('labels'),
  30478. yLow, yHi;
  30479. // The scaling (xx) and translation (dx) here will already be such that the midpoints
  30480. // of the first and last bars are not at the surface edges (which would mean that
  30481. // bars are half-clipped), but padded, so that those bars are fully visible (assuming no pan/zoom).
  30482. for (i = start; i <= end; i++) {
  30483. yLow = dataStartY ? dataStartY[i] : 0;
  30484. yHi = dataY[i];
  30485. center = dataX[i] * xx + dx + groupOffset * (barWidth + inGroupGapWidth);
  30486. left = surface.roundPixel(center - barWidth / 2) + halfLineWidth;
  30487. top = surface.roundPixel(yHi * yy + dy + lineWidth);
  30488. right = surface.roundPixel(center + barWidth / 2) - halfLineWidth;
  30489. bottom = surface.roundPixel(yLow * yy + dy + lineWidth);
  30490. me.drawBar(ctx, surface, dataClipRect, left, top - halfLineWidth, right, bottom - halfLineWidth, i);
  30491. // We want 0 values to be passed to the renderer
  30492. if (isDrawLabels && dataText[i] != null) {
  30493. me.drawLabel(dataText[i], center, bottom, top, i);
  30494. }
  30495. me.putMarker('markers', {
  30496. translationX: surfaceMatrix.x(center, top),
  30497. translationY: surfaceMatrix.y(center, top)
  30498. }, i, true);
  30499. }
  30500. }
  30501. });
  30502. /**
  30503. * @class Ext.chart.series.Bar
  30504. * @extends Ext.chart.series.StackedCartesian
  30505. *
  30506. * Creates a Bar or Column Chart (depending on the value of the
  30507. * {@link Ext.chart.CartesianChart#flipXY flipXY} config).
  30508. *
  30509. * Note: 'bar' series is meant to be used with the
  30510. * {@link Ext.chart.axis.Category 'category'} axis as its x-axis.
  30511. *
  30512. * @example
  30513. * Ext.create({
  30514. * xtype: 'cartesian',
  30515. * renderTo: document.body,
  30516. * width: 600,
  30517. * height: 400,
  30518. * store: {
  30519. * fields: ['name', 'value'],
  30520. * data: [{
  30521. * name: 'metric one',
  30522. * value: 10
  30523. * }, {
  30524. * name: 'metric two',
  30525. * value: 7
  30526. * }, {
  30527. * name: 'metric three',
  30528. * value: 5
  30529. * }, {
  30530. * name: 'metric four',
  30531. * value: 2
  30532. * }, {
  30533. * name: 'metric five',
  30534. * value: 27
  30535. * }]
  30536. * },
  30537. * axes: [{
  30538. * type: 'numeric',
  30539. * position: 'left',
  30540. * title: {
  30541. * text: 'Sample Values',
  30542. * fontSize: 15
  30543. * },
  30544. * fields: 'value'
  30545. * }, {
  30546. * type: 'category',
  30547. * position: 'bottom',
  30548. * title: {
  30549. * text: 'Sample Values',
  30550. * fontSize: 15
  30551. * },
  30552. * fields: 'name'
  30553. * }],
  30554. * series: {
  30555. * type: 'bar',
  30556. * subStyle: {
  30557. * fill: ['#388FAD'],
  30558. * stroke: '#1F6D91'
  30559. * },
  30560. * xField: 'name',
  30561. * yField: 'value'
  30562. * }
  30563. * });
  30564. */
  30565. Ext.define('Ext.chart.series.Bar', {
  30566. extend: 'Ext.chart.series.StackedCartesian',
  30567. alias: 'series.bar',
  30568. type: 'bar',
  30569. seriesType: 'barSeries',
  30570. isBar: true,
  30571. requires: [
  30572. 'Ext.chart.series.sprite.Bar',
  30573. 'Ext.draw.sprite.Rect'
  30574. ],
  30575. config: {
  30576. /**
  30577. * @private
  30578. * @cfg {Object} itemInstancing Sprite template used for series.
  30579. */
  30580. itemInstancing: {
  30581. type: 'rect',
  30582. animation: {
  30583. customDurations: {
  30584. x: 0,
  30585. y: 0,
  30586. width: 0,
  30587. height: 0,
  30588. radius: 0
  30589. }
  30590. }
  30591. }
  30592. },
  30593. getItemForPoint: function(x, y) {
  30594. if (this.getSprites().length) {
  30595. var chart = this.getChart(),
  30596. padding = chart.getInnerPadding(),
  30597. isRtl = chart.getInherited().rtl;
  30598. // Convert the coordinates because the "items" sprites that draw
  30599. // the bars ignore the chart's InnerPadding.
  30600. arguments[0] = x + (isRtl ? padding.right : -padding.left);
  30601. arguments[1] = y + padding.bottom;
  30602. return this.callParent(arguments);
  30603. }
  30604. },
  30605. updateXAxis: function(xAxis) {
  30606. //<debug>
  30607. if (!this.is3D && !xAxis.isCategory) {
  30608. Ext.raise("'bar' series should be used with a 'category' axis. Please refer to the bar series docs.");
  30609. }
  30610. //</debug>
  30611. xAxis.setExpandRangeBy(0.5);
  30612. this.callParent(arguments);
  30613. },
  30614. updateHidden: function(hidden) {
  30615. this.callParent(arguments);
  30616. this.updateStacked();
  30617. },
  30618. updateStacked: function(stacked) {
  30619. var me = this,
  30620. attributes = {},
  30621. sprites = me.getSprites(),
  30622. spriteCount = sprites.length,
  30623. visibleSprites = [],
  30624. visibleSpriteCount, i;
  30625. for (i = 0; i < spriteCount; i++) {
  30626. if (!sprites[i].attr.hidden) {
  30627. visibleSprites.push(sprites[i]);
  30628. }
  30629. }
  30630. visibleSpriteCount = visibleSprites.length;
  30631. if (me.getStacked()) {
  30632. attributes.groupCount = 1;
  30633. attributes.groupOffset = 0;
  30634. for (i = 0; i < visibleSpriteCount; i++) {
  30635. visibleSprites[i].setAttributes(attributes);
  30636. }
  30637. } else {
  30638. attributes.groupCount = visibleSpriteCount;
  30639. for (i = 0; i < visibleSpriteCount; i++) {
  30640. attributes.groupOffset = i;
  30641. visibleSprites[i].setAttributes(attributes);
  30642. }
  30643. }
  30644. me.callParent(arguments);
  30645. }
  30646. });
  30647. /**
  30648. * @class Ext.chart.series.sprite.Bar3D
  30649. * @extends Ext.chart.series.sprite.Bar
  30650. *
  30651. * Draws a sprite used in {@link Ext.chart.series.Bar3D} series.
  30652. */
  30653. Ext.define('Ext.chart.series.sprite.Bar3D', {
  30654. extend: 'Ext.chart.series.sprite.Bar',
  30655. alias: 'sprite.bar3dSeries',
  30656. requires: [
  30657. 'Ext.draw.gradient.Linear'
  30658. ],
  30659. inheritableStatics: {
  30660. def: {
  30661. processors: {
  30662. depthWidthRatio: 'number',
  30663. /**
  30664. * @cfg {Number} [saturationFactor=1]
  30665. * The factor applied to the saturation of the bars.
  30666. */
  30667. saturationFactor: 'number',
  30668. /**
  30669. * @cfg {Number} [brightnessFactor=1]
  30670. * The factor applied to the brightness of the bars.
  30671. */
  30672. brightnessFactor: 'number',
  30673. /**
  30674. * @cfg {Number} [colorSpread=1]
  30675. * An attribute used to control how flat the bar gradient looks.
  30676. * A value of 0 essentially means no gradient (flat color).
  30677. */
  30678. colorSpread: 'number'
  30679. },
  30680. defaults: {
  30681. depthWidthRatio: 1 / 3,
  30682. saturationFactor: 1,
  30683. brightnessFactor: 1,
  30684. colorSpread: 1,
  30685. transformFillStroke: true
  30686. },
  30687. triggers: {
  30688. groupCount: 'panzoom'
  30689. },
  30690. updaters: {
  30691. panzoom: function(attr) {
  30692. var me = this,
  30693. dx = attr.visibleMaxX - attr.visibleMinX,
  30694. dy = attr.visibleMaxY - attr.visibleMinY,
  30695. innerWidth = attr.flipXY ? attr.innerHeight : attr.innerWidth,
  30696. innerHeight = !attr.flipXY ? attr.innerHeight : attr.innerWidth,
  30697. surface = me.getSurface(),
  30698. isRtl = surface ? surface.getInherited().rtl : false;
  30699. if (isRtl && !attr.flipXY) {
  30700. attr.translationX = innerWidth + attr.visibleMinX * innerWidth / dx;
  30701. } else {
  30702. attr.translationX = -attr.visibleMinX * innerWidth / dx;
  30703. }
  30704. attr.translationY = -attr.visibleMinY * (innerHeight - me.depth) / dy;
  30705. attr.scalingX = (isRtl && !attr.flipXY ? -1 : 1) * innerWidth / dx;
  30706. attr.scalingY = (innerHeight - me.depth) / dy;
  30707. attr.scalingCenterX = 0;
  30708. attr.scalingCenterY = 0;
  30709. me.applyTransformations(true);
  30710. }
  30711. }
  30712. }
  30713. },
  30714. config: {
  30715. showStroke: false
  30716. },
  30717. depth: 0,
  30718. drawBar: function(ctx, surface, clip, left, top, right, bottom, index) {
  30719. var me = this,
  30720. attr = me.attr,
  30721. itemCfg = {},
  30722. renderer = attr.renderer,
  30723. changes, depth, series, params;
  30724. itemCfg.x = (left + right) * 0.5;
  30725. itemCfg.y = top;
  30726. itemCfg.width = (right - left) * 0.75;
  30727. itemCfg.height = bottom - top;
  30728. itemCfg.depth = depth = itemCfg.width * attr.depthWidthRatio;
  30729. itemCfg.orientation = attr.flipXY ? 'horizontal' : 'vertical';
  30730. itemCfg.saturationFactor = attr.saturationFactor;
  30731. itemCfg.brightnessFactor = attr.brightnessFactor;
  30732. itemCfg.colorSpread = attr.colorSpread;
  30733. if (depth !== me.depth) {
  30734. me.depth = depth;
  30735. series = me.getSeries();
  30736. series.fireEvent('depthchange', series, depth);
  30737. }
  30738. if (renderer) {
  30739. params = [
  30740. me,
  30741. itemCfg,
  30742. {
  30743. store: me.getStore()
  30744. },
  30745. index
  30746. ];
  30747. changes = Ext.callback(renderer, null, params, 0, me.getSeries());
  30748. Ext.apply(itemCfg, changes);
  30749. }
  30750. me.putMarker('items', itemCfg, index, !renderer);
  30751. }
  30752. });
  30753. /**
  30754. * @class Ext.chart.sprite.Bar3D
  30755. * @extends Ext.draw.sprite.Sprite
  30756. *
  30757. * A sprite that represents a 3D bar or column.
  30758. * Used as an item template by the {@link Ext.chart.series.sprite.Bar3D} marker holder.
  30759. *
  30760. */
  30761. Ext.define('Ext.chart.sprite.Bar3D', {
  30762. extend: 'Ext.draw.sprite.Sprite',
  30763. alias: 'sprite.bar3d',
  30764. type: 'bar3d',
  30765. inheritableStatics: {
  30766. def: {
  30767. processors: {
  30768. /**
  30769. * @cfg {Number} [x=0]
  30770. * The position of the sprite on the x-axis.
  30771. * Corresponds to the center of the front face of the box.
  30772. */
  30773. x: 'number',
  30774. /**
  30775. * @cfg {Number} [y=0]
  30776. * The position of the sprite on the y-axis.
  30777. * Corresponds to the top of the front face of the box.
  30778. */
  30779. y: 'number',
  30780. /**
  30781. * @cfg {Number} [width=8] The width of the box.
  30782. */
  30783. width: 'number',
  30784. /**
  30785. * @cfg {Number} [height=8] The height of the box.
  30786. */
  30787. height: 'number',
  30788. /**
  30789. * @cfg {Number} [depth=8] The depth of the box.
  30790. */
  30791. depth: 'number',
  30792. /**
  30793. * @cfg {String} [orientation='vertical'] The orientation of the box.
  30794. */
  30795. orientation: 'enums(vertical,horizontal)',
  30796. /**
  30797. * @cfg {Boolean} [showStroke=false]
  30798. * Whether to render the stroke or not.
  30799. */
  30800. showStroke: 'bool',
  30801. /**
  30802. * @cfg {Number} [saturationFactor=1]
  30803. * The factor applied to the saturation of the box.
  30804. */
  30805. saturationFactor: 'number',
  30806. /**
  30807. * @cfg {Number} [brightnessFactor=1]
  30808. * The factor applied to the brightness of the box.
  30809. */
  30810. brightnessFactor: 'number',
  30811. /**
  30812. * @cfg {Number} [colorSpread=1]
  30813. * An attribute used to control how flat the bar gradient looks.
  30814. * A value of 0 essentially means no gradient (flat color).
  30815. */
  30816. colorSpread: 'number'
  30817. },
  30818. triggers: {
  30819. x: 'bbox',
  30820. y: 'bbox',
  30821. width: 'bbox',
  30822. height: 'bbox',
  30823. depth: 'bbox',
  30824. orientation: 'bbox'
  30825. },
  30826. defaults: {
  30827. x: 0,
  30828. y: 0,
  30829. width: 8,
  30830. height: 8,
  30831. depth: 8,
  30832. orientation: 'vertical',
  30833. showStroke: false,
  30834. saturationFactor: 1,
  30835. brightnessFactor: 1,
  30836. colorSpread: 1,
  30837. lineJoin: 'bevel'
  30838. }
  30839. }
  30840. },
  30841. constructor: function(config) {
  30842. this.callParent([
  30843. config
  30844. ]);
  30845. this.topGradient = new Ext.draw.gradient.Linear({});
  30846. this.rightGradient = new Ext.draw.gradient.Linear({});
  30847. this.frontGradient = new Ext.draw.gradient.Linear({});
  30848. },
  30849. updatePlainBBox: function(plain) {
  30850. var attr = this.attr,
  30851. x = attr.x,
  30852. y = attr.y,
  30853. width = attr.width,
  30854. height = attr.height,
  30855. depth = attr.depth;
  30856. plain.x = x - width * 0.5;
  30857. plain.width = width + depth;
  30858. if (height > 0) {
  30859. plain.y = y;
  30860. plain.height = height + depth;
  30861. } else {
  30862. plain.y = y + depth;
  30863. plain.height = height - depth;
  30864. }
  30865. },
  30866. render: function(surface, ctx) {
  30867. var me = this,
  30868. attr = me.attr,
  30869. center = attr.x,
  30870. top = attr.y,
  30871. bottom = top + attr.height,
  30872. isNegative = top < bottom,
  30873. halfWidth = attr.width * 0.5,
  30874. depth = attr.depth,
  30875. isHorizontal = attr.orientation === 'horizontal',
  30876. isTransparent = attr.globalAlpha < 1,
  30877. fillStyle = attr.fillStyle,
  30878. color = Ext.util.Color.create(fillStyle.isGradient ? fillStyle.getStops()[0].color : fillStyle),
  30879. saturationFactor = attr.saturationFactor,
  30880. brightnessFactor = attr.brightnessFactor,
  30881. colorSpread = attr.colorSpread,
  30882. hsv = color.getHSV(),
  30883. bbox = {},
  30884. roundX, roundY, temp;
  30885. if (!attr.showStroke) {
  30886. ctx.strokeStyle = Ext.util.Color.RGBA_NONE;
  30887. }
  30888. if (isNegative) {
  30889. temp = top;
  30890. top = bottom;
  30891. bottom = temp;
  30892. }
  30893. // Refresh gradients based on sprite's fillStyle and other attributes.
  30894. me.topGradient.setDegrees(isHorizontal ? 0 : 80);
  30895. me.topGradient.setStops([
  30896. {
  30897. offset: 0,
  30898. 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))
  30899. },
  30900. {
  30901. offset: 1,
  30902. 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))
  30903. }
  30904. ]);
  30905. me.rightGradient.setDegrees(isHorizontal ? 45 : 90);
  30906. me.rightGradient.setStops([
  30907. {
  30908. offset: 0,
  30909. 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))
  30910. },
  30911. {
  30912. offset: 1,
  30913. 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))
  30914. }
  30915. ]);
  30916. if (isHorizontal) {
  30917. me.frontGradient.setDegrees(0);
  30918. } else // 0° angle looks like 90° angle because the chart is flipped
  30919. {
  30920. me.frontGradient.setRadians(Math.atan2(top - bottom, halfWidth * 2));
  30921. }
  30922. me.frontGradient.setStops([
  30923. {
  30924. offset: 0,
  30925. 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))
  30926. },
  30927. {
  30928. offset: 1,
  30929. 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))
  30930. }
  30931. ]);
  30932. if (isTransparent || isNegative) {
  30933. // Bottom side.
  30934. ctx.beginPath();
  30935. ctx.moveTo(center - halfWidth, bottom);
  30936. ctx.lineTo(center - halfWidth + depth, bottom + depth);
  30937. ctx.lineTo(center + halfWidth + depth, bottom + depth);
  30938. ctx.lineTo(center + halfWidth, bottom);
  30939. ctx.closePath();
  30940. bbox.x = center - halfWidth;
  30941. bbox.y = top;
  30942. bbox.width = halfWidth + depth;
  30943. bbox.height = depth;
  30944. ctx.fillStyle = (isHorizontal ? me.rightGradient : me.topGradient).generateGradient(ctx, bbox);
  30945. ctx.fillStroke(attr);
  30946. }
  30947. if (isTransparent) {
  30948. // Left side.
  30949. ctx.beginPath();
  30950. ctx.moveTo(center - halfWidth, top);
  30951. ctx.lineTo(center - halfWidth + depth, top + depth);
  30952. ctx.lineTo(center - halfWidth + depth, bottom + depth);
  30953. ctx.lineTo(center - halfWidth, bottom);
  30954. ctx.closePath();
  30955. bbox.x = center + halfWidth;
  30956. bbox.y = bottom;
  30957. bbox.width = depth;
  30958. bbox.height = top + depth - bottom;
  30959. ctx.fillStyle = (isHorizontal ? me.topGradient : me.rightGradient).generateGradient(ctx, bbox);
  30960. ctx.fillStroke(attr);
  30961. }
  30962. // Top side.
  30963. roundY = surface.roundPixel(top);
  30964. ctx.beginPath();
  30965. ctx.moveTo(center - halfWidth, roundY);
  30966. ctx.lineTo(center - halfWidth + depth, top + depth);
  30967. ctx.lineTo(center + halfWidth + depth, top + depth);
  30968. ctx.lineTo(center + halfWidth, roundY);
  30969. ctx.closePath();
  30970. bbox.x = center - halfWidth;
  30971. bbox.y = top;
  30972. bbox.width = halfWidth + depth;
  30973. bbox.height = depth;
  30974. ctx.fillStyle = (isHorizontal ? me.rightGradient : me.topGradient).generateGradient(ctx, bbox);
  30975. ctx.fillStroke(attr);
  30976. // Right side.
  30977. roundX = surface.roundPixel(center + halfWidth);
  30978. ctx.beginPath();
  30979. ctx.moveTo(roundX, surface.roundPixel(top));
  30980. ctx.lineTo(center + halfWidth + depth, top + depth);
  30981. ctx.lineTo(center + halfWidth + depth, bottom + depth);
  30982. ctx.lineTo(roundX, bottom);
  30983. ctx.closePath();
  30984. bbox.x = center + halfWidth;
  30985. bbox.y = bottom;
  30986. bbox.width = depth;
  30987. bbox.height = top + depth - bottom;
  30988. ctx.fillStyle = (isHorizontal ? me.topGradient : me.rightGradient).generateGradient(ctx, bbox);
  30989. ctx.fillStroke(attr);
  30990. // Front side.
  30991. roundX = surface.roundPixel(center + halfWidth);
  30992. roundY = surface.roundPixel(top);
  30993. ctx.beginPath();
  30994. ctx.moveTo(center - halfWidth, bottom);
  30995. ctx.lineTo(center - halfWidth, roundY);
  30996. ctx.lineTo(roundX, roundY);
  30997. ctx.lineTo(roundX, bottom);
  30998. ctx.closePath();
  30999. bbox.x = center - halfWidth;
  31000. bbox.y = bottom;
  31001. bbox.width = halfWidth * 2;
  31002. bbox.height = top - bottom;
  31003. ctx.fillStyle = me.frontGradient.generateGradient(ctx, bbox);
  31004. ctx.fillStroke(attr);
  31005. }
  31006. });
  31007. /**
  31008. * @class Ext.chart.series.Bar3D
  31009. * @extends Ext.chart.series.Bar
  31010. *
  31011. * Creates a 3D Bar or 3D Column Chart (depending on the value of the
  31012. * {@link Ext.chart.CartesianChart#flipXY flipXY} config).
  31013. *
  31014. * Note: 'bar3d' series is meant to be used with the
  31015. * {@link Ext.chart.axis.Category 'category3d'} axis as its x-axis.
  31016. *
  31017. * @example
  31018. * Ext.create({
  31019. * xtype: 'cartesian',
  31020. * renderTo: Ext.getBody(),
  31021. * width: 600,
  31022. * height: 400,
  31023. * innerPadding: '0 10 0 10',
  31024. * store: {
  31025. * fields: ['name', 'apples', 'oranges'],
  31026. * data: [{
  31027. * name: 'Eric',
  31028. * apples: 10,
  31029. * oranges: 3
  31030. * }, {
  31031. * name: 'Mary',
  31032. * apples: 7,
  31033. * oranges: 2
  31034. * }, {
  31035. * name: 'John',
  31036. * apples: 5,
  31037. * oranges: 2
  31038. * }, {
  31039. * name: 'Bob',
  31040. * apples: 2,
  31041. * oranges: 3
  31042. * }, {
  31043. * name: 'Joe',
  31044. * apples: 19,
  31045. * oranges: 1
  31046. * }, {
  31047. * name: 'Macy',
  31048. * apples: 13,
  31049. * oranges: 4
  31050. * }]
  31051. * },
  31052. * axes: [{
  31053. * type: 'numeric3d',
  31054. * position: 'left',
  31055. * fields: ['apples', 'oranges'],
  31056. * title: {
  31057. * text: 'Inventory',
  31058. * fontSize: 15
  31059. * },
  31060. * grid: {
  31061. * odd: {
  31062. * fillStyle: 'rgba(255, 255, 255, 0.06)'
  31063. * },
  31064. * even: {
  31065. * fillStyle: 'rgba(0, 0, 0, 0.03)'
  31066. * }
  31067. * }
  31068. * }, {
  31069. * type: 'category3d',
  31070. * position: 'bottom',
  31071. * title: {
  31072. * text: 'People',
  31073. * fontSize: 15
  31074. * },
  31075. * fields: 'name'
  31076. * }],
  31077. * series: {
  31078. * type: 'bar3d',
  31079. * xField: 'name',
  31080. * yField: ['apples', 'oranges']
  31081. * }
  31082. * });
  31083. */
  31084. Ext.define('Ext.chart.series.Bar3D', {
  31085. extend: 'Ext.chart.series.Bar',
  31086. requires: [
  31087. 'Ext.chart.series.sprite.Bar3D',
  31088. 'Ext.chart.sprite.Bar3D'
  31089. ],
  31090. alias: 'series.bar3d',
  31091. type: 'bar3d',
  31092. seriesType: 'bar3dSeries',
  31093. is3D: true,
  31094. config: {
  31095. itemInstancing: {
  31096. type: 'bar3d',
  31097. animation: {
  31098. customDurations: {
  31099. x: 0,
  31100. y: 0,
  31101. width: 0,
  31102. height: 0,
  31103. depth: 0
  31104. }
  31105. }
  31106. },
  31107. highlightCfg: {
  31108. opacity: 0.8
  31109. }
  31110. },
  31111. /**
  31112. * For 3D series, it's quite the opposite. It would be extremely odd,
  31113. * if top segments were rendered as if they were under the bottom ones.
  31114. */
  31115. reversedSpriteZOrder: false,
  31116. updateXAxis: function(xAxis, oldXAxis) {
  31117. //<debug>
  31118. if (xAxis.type !== 'category3d') {
  31119. Ext.raise("'bar3d' series should be used with a 'category3d' axis." + " Please refer to the 'bar3d' series docs.");
  31120. }
  31121. //</debug>
  31122. this.callParent([
  31123. xAxis,
  31124. oldXAxis
  31125. ]);
  31126. },
  31127. getDepth: function() {
  31128. var sprite = this.getSprites()[0];
  31129. return sprite ? (sprite.depth || 0) : 0;
  31130. }
  31131. });
  31132. /**
  31133. * BoxPlot series sprite that manages {@link Ext.chart.sprite.BoxPlot} instances.
  31134. */
  31135. Ext.define('Ext.chart.series.sprite.BoxPlot', {
  31136. alias: 'sprite.boxplotSeries',
  31137. extend: 'Ext.chart.series.sprite.Cartesian',
  31138. inheritableStatics: {
  31139. def: {
  31140. processors: {
  31141. /**
  31142. * @cfg {Number[]} [dataLow=null] Array of coordinated minimum values.
  31143. */
  31144. dataLow: 'data',
  31145. /**
  31146. * @cfg {Number[]} [dataQ1=null] Array of coordinated 1-st quartile values.
  31147. */
  31148. dataQ1: 'data',
  31149. /**
  31150. * @cfg {Number[]} [dataQ3=null] Array of coordinated 3-rd quartile values.
  31151. */
  31152. dataQ3: 'data',
  31153. /**
  31154. * @cfg {Number[]} [dataHigh=null] Array of coordinated maximum values.
  31155. */
  31156. dataHigh: 'data',
  31157. /**
  31158. * @cfg {Number} [minBoxWidth=2] The minimum box width.
  31159. */
  31160. minBoxWidth: 'number',
  31161. /**
  31162. * @cfg {Number} [maxBoxWidth=20] The maximum box width.
  31163. */
  31164. maxBoxWidth: 'number',
  31165. /**
  31166. * @cfg {Number} [minGapWidth=5] The minimum gap between boxes.
  31167. */
  31168. minGapWidth: 'number'
  31169. },
  31170. aliases: {
  31171. /**
  31172. * The `dataMedian` attribute can be used to set the value of
  31173. * the `dataY` attribute. E.g.:
  31174. *
  31175. * sprite.setAttributes({
  31176. * dataMedian: [...]
  31177. * });
  31178. *
  31179. * To fetch the value of the attribute one has to use
  31180. *
  31181. * sprite.attr.dataY // array of coordinated median values
  31182. *
  31183. * and not
  31184. *
  31185. * sprite.attr.dataMedian // WRONG!
  31186. *
  31187. * `dataY` attribute is defined by the `Ext.chart.series.sprite.Series`.
  31188. *
  31189. * @cfg {Number[]} [dataMedian=null] Array of coordinated median values.
  31190. */
  31191. dataMedian: 'dataY'
  31192. },
  31193. defaults: {
  31194. minBoxWidth: 2,
  31195. maxBoxWidth: 40,
  31196. minGapWidth: 5
  31197. }
  31198. }
  31199. },
  31200. renderClipped: function(surface, ctx, dataClipRect) {
  31201. if (this.cleanRedraw) {
  31202. return;
  31203. }
  31204. var me = this,
  31205. attr = me.attr,
  31206. series = me.getSeries(),
  31207. renderer = attr.renderer,
  31208. rendererData = {
  31209. store: me.getStore()
  31210. },
  31211. itemCfg = {},
  31212. dataX = attr.dataX,
  31213. dataLow = attr.dataLow,
  31214. dataQ1 = attr.dataQ1,
  31215. dataMedian = attr.dataY,
  31216. dataQ3 = attr.dataQ3,
  31217. dataHigh = attr.dataHigh,
  31218. min = Math.min(dataClipRect[0], dataClipRect[2]),
  31219. max = Math.max(dataClipRect[0], dataClipRect[2]),
  31220. start = Math.max(0, Math.floor(min)),
  31221. end = Math.min(dataX.length - 1, Math.ceil(max)),
  31222. // surfaceMatrix = me.surfaceMatrix,
  31223. matrix = attr.matrix,
  31224. xx = matrix.elements[0],
  31225. // horizontal scaling can be < 0, if RTL
  31226. yy = matrix.elements[3],
  31227. dx = matrix.elements[4],
  31228. dy = matrix.elements[5],
  31229. // `xx` essentially represents the distance between data points in surface coordinates.
  31230. maxBoxWidth = Math.abs(xx) - attr.minGapWidth,
  31231. minBoxWidth = Math.min(maxBoxWidth, attr.maxBoxWidth),
  31232. boxWidth = Math.round(Math.max(attr.minBoxWidth, minBoxWidth)),
  31233. x, low, q1, median, q3, high, rendererParams, changes, i;
  31234. if (renderer) {
  31235. rendererParams = [
  31236. me,
  31237. itemCfg,
  31238. rendererData
  31239. ];
  31240. }
  31241. for (i = start; i <= end; i++) {
  31242. x = dataX[i] * xx + dx;
  31243. low = dataLow[i] * yy + dy;
  31244. q1 = dataQ1[i] * yy + dy;
  31245. median = dataMedian[i] * yy + dy;
  31246. q3 = dataQ3[i] * yy + dy;
  31247. high = dataHigh[i] * yy + dy;
  31248. // --- Draw Box ---
  31249. // Reuse 'itemCfg' object and 'rendererParams' arrays for better performance.
  31250. itemCfg.x = x;
  31251. itemCfg.low = low;
  31252. itemCfg.q1 = q1;
  31253. itemCfg.median = median;
  31254. itemCfg.q3 = q3;
  31255. itemCfg.high = high;
  31256. itemCfg.boxWidth = boxWidth;
  31257. if (renderer) {
  31258. rendererParams[3] = i;
  31259. changes = Ext.callback(renderer, null, rendererParams, 0, series);
  31260. Ext.apply(itemCfg, changes);
  31261. }
  31262. me.putMarker('items', itemCfg, i, !renderer);
  31263. }
  31264. }
  31265. });
  31266. /**
  31267. * A sprite that represents an individual box with whiskers.
  31268. * This sprite is meant to be managed by the {@link Ext.chart.series.sprite.BoxPlot}
  31269. * {@link Ext.chart.MarkerHolder MarkerHolder}, but can also be used independently:
  31270. *
  31271. * @example
  31272. * new Ext.draw.Container({
  31273. * width: 100,
  31274. * height: 100,
  31275. * renderTo: Ext.getBody(),
  31276. * sprites: [{
  31277. * type: 'boxplot',
  31278. * translationX: 50,
  31279. * translationY: 50
  31280. * }]
  31281. * });
  31282. *
  31283. * IMPORTANT: the attributes that represent y-coordinates are in screen coordinates,
  31284. * just like with any other sprite. For this particular sprite this means that, if 'low'
  31285. * and 'high' attributes are 10 and 90, then the minimium whisker is rendered at the top
  31286. * of a draw container {@link Ext.draw.Surface surface} at y = 10, and the maximum whisker
  31287. * is rendered at the bottom at y = 90. But because the series surface is flipped vertically
  31288. * in cartesian charts, this means that there minimum is rendered at the bottom and maximum
  31289. * at the top, just as one would expect.
  31290. */
  31291. Ext.define('Ext.chart.sprite.BoxPlot', {
  31292. extend: 'Ext.draw.sprite.Sprite',
  31293. alias: 'sprite.boxplot',
  31294. type: 'boxplot',
  31295. inheritableStatics: {
  31296. def: {
  31297. processors: {
  31298. /**
  31299. * @cfg {Number} [x=0] The coordinate of the horizontal center of a boxplot.
  31300. */
  31301. x: 'number',
  31302. /**
  31303. * @cfg {Number} [low=-20] The y-coordinate of the whisker that represents the minimum.
  31304. */
  31305. low: 'number',
  31306. /**
  31307. * @cfg {Number} [q1=-10] The y-coordinate of the box edge that represents the 1-st quartile.
  31308. */
  31309. q1: 'number',
  31310. /**
  31311. * @cfg {Number} [median=0] The y-coordinate of the line that represents the median.
  31312. */
  31313. median: 'number',
  31314. /**
  31315. * @cfg {Number} [q3=10] The y-coordinate of the box edge that represents the 3-rd quartile.
  31316. */
  31317. q3: 'number',
  31318. /**
  31319. * @cfg {Number} [high=20] The y-coordinate of the whisker that represents the maximum.
  31320. */
  31321. high: 'number',
  31322. /**
  31323. * @cfg {Number} [boxWidth=12] The width of the box in pixels.
  31324. */
  31325. boxWidth: 'number',
  31326. /**
  31327. * @cfg {Number} [whiskerWidth=0.5] The length of the lines at the ends of the whiskers, as a ratio of `boxWidth`.
  31328. */
  31329. whiskerWidth: 'number',
  31330. /**
  31331. * @cfg {Boolean} [crisp=true] Whether to snap the rendered lines to the pixel grid of not.
  31332. * Generally, it's best to have this set to `true` (which is the default) fox pixel perfect
  31333. * results (especially on non-HiDPI displays), but for boxplots with small `boxWidth`
  31334. * visible artifacts caused by pixel grid snapping may become noticeable, and setting this
  31335. * to `false` can be a remedy at the expense of clarity.
  31336. */
  31337. crisp: 'bool'
  31338. },
  31339. triggers: {
  31340. x: 'bbox',
  31341. low: 'bbox',
  31342. high: 'bbox',
  31343. boxWidth: 'bbox',
  31344. whiskerWidth: 'bbox',
  31345. crisp: 'bbox'
  31346. },
  31347. defaults: {
  31348. x: 0,
  31349. low: -20,
  31350. q1: -10,
  31351. median: 0,
  31352. q3: 10,
  31353. high: 20,
  31354. boxWidth: 12,
  31355. whiskerWidth: 0.5,
  31356. crisp: true,
  31357. fillStyle: '#ccc',
  31358. strokeStyle: '#000'
  31359. }
  31360. }
  31361. },
  31362. updatePlainBBox: function(plain) {
  31363. var me = this,
  31364. attr = me.attr,
  31365. halfLineWidth = attr.lineWidth / 2,
  31366. x = attr.x - attr.boxWidth / 2 - halfLineWidth,
  31367. y = attr.high - halfLineWidth,
  31368. width = attr.boxWidth + attr.lineWidth,
  31369. height = attr.low - attr.high + attr.lineWidth;
  31370. plain.x = x;
  31371. plain.y = y;
  31372. plain.width = width;
  31373. plain.height = height;
  31374. },
  31375. render: function(surface, ctx) {
  31376. var me = this,
  31377. attr = me.attr;
  31378. attr.matrix.toContext(ctx);
  31379. // enable sprite transformations
  31380. if (attr.crisp) {
  31381. me.crispRender(surface, ctx);
  31382. } else {
  31383. me.softRender(surface, ctx);
  31384. }
  31385. //<debug>
  31386. var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
  31387. if (debug) {
  31388. // This assumes no part of the sprite is rendered after this call.
  31389. // If it is, we need to re-apply transformations.
  31390. // But the bounding box should always be rendered as is, untransformed.
  31391. this.attr.inverseMatrix.toContext(ctx);
  31392. debug.bbox && this.renderBBox(surface, ctx);
  31393. }
  31394. },
  31395. //</debug>
  31396. /**
  31397. * @private
  31398. * Renders a single box with whiskers.
  31399. * Changes to this method have to be reflected in the {@link #crispRender} as well.
  31400. * @param surface
  31401. * @param ctx
  31402. */
  31403. softRender: function(surface, ctx) {
  31404. var me = this,
  31405. attr = me.attr,
  31406. x = attr.x,
  31407. low = attr.low,
  31408. q1 = attr.q1,
  31409. median = attr.median,
  31410. q3 = attr.q3,
  31411. high = attr.high,
  31412. halfBoxWidth = attr.boxWidth / 2,
  31413. halfWhiskerWidth = attr.boxWidth * attr.whiskerWidth / 2,
  31414. dash = ctx.getLineDash();
  31415. ctx.setLineDash([]);
  31416. // Only stem can be dashed.
  31417. // Box.
  31418. ctx.beginPath();
  31419. ctx.moveTo(x - halfBoxWidth, q3);
  31420. ctx.lineTo(x + halfBoxWidth, q3);
  31421. ctx.lineTo(x + halfBoxWidth, q1);
  31422. ctx.lineTo(x - halfBoxWidth, q1);
  31423. ctx.closePath();
  31424. ctx.fillStroke(attr, true);
  31425. // Stem.
  31426. ctx.setLineDash(dash);
  31427. ctx.beginPath();
  31428. ctx.moveTo(x, q3);
  31429. ctx.lineTo(x, high);
  31430. ctx.moveTo(x, q1);
  31431. ctx.lineTo(x, low);
  31432. ctx.stroke();
  31433. ctx.setLineDash([]);
  31434. // Whiskers.
  31435. ctx.beginPath();
  31436. ctx.moveTo(x - halfWhiskerWidth, low);
  31437. ctx.lineTo(x + halfWhiskerWidth, low);
  31438. ctx.moveTo(x - halfBoxWidth, median);
  31439. ctx.lineTo(x + halfBoxWidth, median);
  31440. ctx.moveTo(x - halfWhiskerWidth, high);
  31441. ctx.lineTo(x + halfWhiskerWidth, high);
  31442. ctx.stroke();
  31443. },
  31444. alignLine: function(x, lineWidth) {
  31445. lineWidth = lineWidth || this.attr.lineWidth;
  31446. x = Math.round(x);
  31447. if (lineWidth % 2 === 1) {
  31448. x -= 0.5;
  31449. }
  31450. return x;
  31451. },
  31452. /**
  31453. * @private
  31454. * Renders a pixel-perfect single box with whiskers by aligning to the pixel grid.
  31455. * Changes to this method have to be reflected in the {@link #softRender} as well.
  31456. *
  31457. * Note: crisp image is only guaranteed when `attr.lineWidth` is a whole number.
  31458. * @param surface
  31459. * @param ctx
  31460. */
  31461. crispRender: function(surface, ctx) {
  31462. var me = this,
  31463. attr = me.attr,
  31464. x = attr.x,
  31465. low = me.alignLine(attr.low),
  31466. q1 = me.alignLine(attr.q1),
  31467. median = me.alignLine(attr.median),
  31468. q3 = me.alignLine(attr.q3),
  31469. high = me.alignLine(attr.high),
  31470. halfBoxWidth = attr.boxWidth / 2,
  31471. halfWhiskerWidth = attr.boxWidth * attr.whiskerWidth / 2,
  31472. stemX = me.alignLine(x),
  31473. boxLeft = me.alignLine(x - halfBoxWidth),
  31474. boxRight = me.alignLine(x + halfBoxWidth),
  31475. whiskerLeft = stemX + Math.round(-halfWhiskerWidth),
  31476. whiskerRight = stemX + Math.round(halfWhiskerWidth),
  31477. dash = ctx.getLineDash();
  31478. ctx.setLineDash([]);
  31479. // Only stem can be dashed.
  31480. // Box.
  31481. ctx.beginPath();
  31482. ctx.moveTo(boxLeft, q3);
  31483. ctx.lineTo(boxRight, q3);
  31484. ctx.lineTo(boxRight, q1);
  31485. ctx.lineTo(boxLeft, q1);
  31486. ctx.closePath();
  31487. ctx.fillStroke(attr, true);
  31488. // Stem.
  31489. ctx.setLineDash(dash);
  31490. ctx.beginPath();
  31491. ctx.moveTo(stemX, q3);
  31492. ctx.lineTo(stemX, high);
  31493. ctx.moveTo(stemX, q1);
  31494. ctx.lineTo(stemX, low);
  31495. ctx.stroke();
  31496. ctx.setLineDash([]);
  31497. // Whiskers.
  31498. ctx.beginPath();
  31499. ctx.moveTo(whiskerLeft, low);
  31500. ctx.lineTo(whiskerRight, low);
  31501. ctx.moveTo(boxLeft, median);
  31502. ctx.lineTo(boxRight, median);
  31503. ctx.moveTo(whiskerLeft, high);
  31504. ctx.lineTo(whiskerRight, high);
  31505. ctx.stroke();
  31506. }
  31507. });
  31508. /**
  31509. * A box plot chart is a useful tool for visializing data distribution within datasets.
  31510. * For example, salary ranges for a set of occupations, or life expectancy for a set
  31511. * of countries. A single box with whiskers displays the following values for a dataset:
  31512. *
  31513. * * minimum
  31514. * * lower quartile (Q1)
  31515. * * median (Q2)
  31516. * * higher quartile (Q3)
  31517. * * maximum
  31518. *
  31519. * For example:
  31520. *
  31521. * @example
  31522. * Ext.create({
  31523. * xtype: 'cartesian',
  31524. * width: 400,
  31525. * height: 400,
  31526. * renderTo: Ext.getBody(),
  31527. * insetPadding: '20 20 10 10',
  31528. * store: {
  31529. * data: [{
  31530. * category: 'Engineer IV',
  31531. * low: 110, q1: 130, median: 175, q3: 200, high: 225
  31532. * }, {
  31533. * category: 'Market',
  31534. * low: 75, q1: 125, median: 210, q3: 230, high: 255
  31535. * }]
  31536. * },
  31537. * axes: [
  31538. * {
  31539. * type: 'numeric',
  31540. * position: 'left',
  31541. * renderer: function (axis, text) {
  31542. * return '$' + text + ' K'
  31543. * }
  31544. * },
  31545. * {
  31546. * type: 'category',
  31547. * position: 'bottom'
  31548. * }
  31549. * ],
  31550. * series: {
  31551. * type: 'boxplot',
  31552. * xField: 'category',
  31553. * style: {
  31554. * maxBoxWidth: 50,
  31555. * lineWidth: 2
  31556. * }
  31557. * }
  31558. * });
  31559. *
  31560. */
  31561. Ext.define('Ext.chart.series.BoxPlot', {
  31562. extend: 'Ext.chart.series.Cartesian',
  31563. alias: 'series.boxplot',
  31564. type: 'boxplot',
  31565. seriesType: 'boxplotSeries',
  31566. isBoxPlot: true,
  31567. requires: [
  31568. 'Ext.chart.series.sprite.BoxPlot',
  31569. 'Ext.chart.sprite.BoxPlot'
  31570. ],
  31571. config: {
  31572. itemInstancing: {
  31573. type: 'boxplot',
  31574. animation: {
  31575. // Setting the duration of these attributes to zero because
  31576. // the 'data' attributes of the series sprite (MarkerHolder)
  31577. // will be animated instead, and then changes applied to
  31578. // the attributes of 'boxplot' instances instantly.
  31579. customDurations: {
  31580. x: 0,
  31581. low: 0,
  31582. q1: 0,
  31583. median: 0,
  31584. q3: 0,
  31585. high: 0
  31586. }
  31587. }
  31588. },
  31589. /**
  31590. * @cfg {String} [lowField='low']
  31591. * The name of the store record field that represents the smallest value of a dataset.
  31592. */
  31593. lowField: 'low',
  31594. /**
  31595. * @cfg {String} [q1Field='q1']
  31596. * The name of the store record field that represents the lower (1-st) quartile
  31597. * value of a dataset.
  31598. */
  31599. q1Field: 'q1',
  31600. /**
  31601. * @cfg {String} [medianField='median']
  31602. * The name of the store record field that represents the median of a dataset.
  31603. */
  31604. medianField: 'median',
  31605. /**
  31606. * @cfg {String} [q3Field='q3']
  31607. * The name of the store record field that represents the upper (3-rd) quartile
  31608. * value of a dataset.
  31609. */
  31610. q3Field: 'q3',
  31611. /**
  31612. * @cfg {String} [highField='high']
  31613. * The name of the store record field that represents the largest value of a dataset.
  31614. */
  31615. highField: 'high'
  31616. },
  31617. fieldCategoryY: [
  31618. 'Low',
  31619. 'Q1',
  31620. 'Median',
  31621. 'Q3',
  31622. 'High'
  31623. ],
  31624. updateXAxis: function(xAxis) {
  31625. xAxis.setExpandRangeBy(0.5);
  31626. this.callParent(arguments);
  31627. }
  31628. });
  31629. /**
  31630. * Limited cache is a size limited cache container that stores limited number of objects.
  31631. *
  31632. * When {@link #get} is called, the container will try to find the object in the list.
  31633. * If failed it will call the {@link #feeder} to create that object. If there are too many
  31634. * objects in the container, the old ones are removed.
  31635. *
  31636. * __Note:__ This is not using a Least Recently Used policy due to simplicity and performance consideration.
  31637. * @private
  31638. */
  31639. Ext.define('Ext.draw.LimitedCache', {
  31640. config: {
  31641. /**
  31642. * @cfg {Number}
  31643. * The amount limit of the cache.
  31644. */
  31645. limit: 40,
  31646. /**
  31647. * @cfg {Function}
  31648. * Function that generates the object when look-up failed.
  31649. * @return {Number}
  31650. */
  31651. feeder: function() {
  31652. return 0;
  31653. },
  31654. /**
  31655. * @cfg {Object}
  31656. * The scope for {@link #feeder}
  31657. */
  31658. scope: null
  31659. },
  31660. cache: null,
  31661. constructor: function(config) {
  31662. this.cache = {};
  31663. this.cache.list = [];
  31664. this.cache.tail = 0;
  31665. this.initConfig(config);
  31666. },
  31667. /**
  31668. * Get a cached object.
  31669. * @param {String} id
  31670. * @return {Object}
  31671. */
  31672. get: function(id) {
  31673. // TODO: Implement cache hit optimization
  31674. var cache = this.cache,
  31675. limit = this.getLimit(),
  31676. feeder = this.getFeeder(),
  31677. scope = this.getScope() || this;
  31678. if (cache[id]) {
  31679. return cache[id].value;
  31680. }
  31681. if (cache.list[cache.tail]) {
  31682. delete cache[cache.list[cache.tail].cacheId];
  31683. }
  31684. cache[id] = cache.list[cache.tail] = {
  31685. value: feeder.apply(scope, Array.prototype.slice.call(arguments, 1)),
  31686. cacheId: id
  31687. };
  31688. cache.tail++;
  31689. if (cache.tail === limit) {
  31690. cache.tail = 0;
  31691. }
  31692. return cache[id].value;
  31693. },
  31694. /**
  31695. * Clear all the objects.
  31696. */
  31697. clear: function() {
  31698. this.cache = {};
  31699. this.cache.list = [];
  31700. this.cache.tail = 0;
  31701. }
  31702. });
  31703. /**
  31704. * This class we summarize the data and returns it when required.
  31705. */
  31706. Ext.define("Ext.draw.SegmentTree", {
  31707. config: {
  31708. strategy: "double"
  31709. },
  31710. /**
  31711. * @private
  31712. * @param {Object} result
  31713. * @param {Number} last
  31714. * @param {Number} dataX
  31715. * @param {Number} dataOpen
  31716. * @param {Number} dataHigh
  31717. * @param {Number} dataLow
  31718. * @param {Number} dataClose
  31719. */
  31720. time: function(result, last, dataX, dataOpen, dataHigh, dataLow, dataClose) {
  31721. var start = 0,
  31722. lastOffset, lastOffsetEnd,
  31723. minimum = new Date(dataX[result.startIdx[0]]),
  31724. maximum = new Date(dataX[result.endIdx[last - 1]]),
  31725. extDate = Ext.Date,
  31726. units = [
  31727. [
  31728. extDate.MILLI,
  31729. 1,
  31730. 'ms1',
  31731. null
  31732. ],
  31733. [
  31734. extDate.MILLI,
  31735. 2,
  31736. 'ms2',
  31737. 'ms1'
  31738. ],
  31739. [
  31740. extDate.MILLI,
  31741. 5,
  31742. 'ms5',
  31743. 'ms1'
  31744. ],
  31745. [
  31746. extDate.MILLI,
  31747. 10,
  31748. 'ms10',
  31749. 'ms5'
  31750. ],
  31751. [
  31752. extDate.MILLI,
  31753. 50,
  31754. 'ms50',
  31755. 'ms10'
  31756. ],
  31757. [
  31758. extDate.MILLI,
  31759. 100,
  31760. 'ms100',
  31761. 'ms50'
  31762. ],
  31763. [
  31764. extDate.MILLI,
  31765. 500,
  31766. 'ms500',
  31767. 'ms100'
  31768. ],
  31769. [
  31770. extDate.SECOND,
  31771. 1,
  31772. 's1',
  31773. 'ms500'
  31774. ],
  31775. [
  31776. extDate.SECOND,
  31777. 10,
  31778. 's10',
  31779. 's1'
  31780. ],
  31781. [
  31782. extDate.SECOND,
  31783. 30,
  31784. 's30',
  31785. 's10'
  31786. ],
  31787. [
  31788. extDate.MINUTE,
  31789. 1,
  31790. 'mi1',
  31791. 's10'
  31792. ],
  31793. [
  31794. extDate.MINUTE,
  31795. 5,
  31796. 'mi5',
  31797. 'mi1'
  31798. ],
  31799. [
  31800. extDate.MINUTE,
  31801. 10,
  31802. 'mi10',
  31803. 'mi5'
  31804. ],
  31805. [
  31806. extDate.MINUTE,
  31807. 30,
  31808. 'mi30',
  31809. 'mi10'
  31810. ],
  31811. [
  31812. extDate.HOUR,
  31813. 1,
  31814. 'h1',
  31815. 'mi30'
  31816. ],
  31817. [
  31818. extDate.HOUR,
  31819. 6,
  31820. 'h6',
  31821. 'h1'
  31822. ],
  31823. [
  31824. extDate.HOUR,
  31825. 12,
  31826. 'h12',
  31827. 'h6'
  31828. ],
  31829. [
  31830. extDate.DAY,
  31831. 1,
  31832. 'd1',
  31833. 'h12'
  31834. ],
  31835. [
  31836. extDate.DAY,
  31837. 7,
  31838. 'd7',
  31839. 'd1'
  31840. ],
  31841. [
  31842. extDate.MONTH,
  31843. 1,
  31844. 'mo1',
  31845. 'd1'
  31846. ],
  31847. [
  31848. extDate.MONTH,
  31849. 3,
  31850. 'mo3',
  31851. 'mo1'
  31852. ],
  31853. [
  31854. extDate.MONTH,
  31855. 6,
  31856. 'mo6',
  31857. 'mo3'
  31858. ],
  31859. [
  31860. extDate.YEAR,
  31861. 1,
  31862. 'y1',
  31863. 'mo3'
  31864. ],
  31865. [
  31866. extDate.YEAR,
  31867. 5,
  31868. 'y5',
  31869. 'y1'
  31870. ],
  31871. [
  31872. extDate.YEAR,
  31873. 10,
  31874. 'y10',
  31875. 'y5'
  31876. ],
  31877. [
  31878. extDate.YEAR,
  31879. 100,
  31880. 'y100',
  31881. 'y10'
  31882. ]
  31883. ],
  31884. unitIdx, currentUnit,
  31885. plainStart = start,
  31886. plainEnd = last,
  31887. first = false,
  31888. startIdxs = result.startIdx,
  31889. endIdxs = result.endIdx,
  31890. minIdxs = result.minIdx,
  31891. maxIdxs = result.maxIdx,
  31892. opens = result.open,
  31893. closes = result.close,
  31894. minXs = result.minX,
  31895. minYs = result.minY,
  31896. maxXs = result.maxX,
  31897. maxYs = result.maxY,
  31898. i, current;
  31899. for (unitIdx = 0; last > start + 1 && unitIdx < units.length; unitIdx++) {
  31900. minimum = new Date(dataX[startIdxs[0]]);
  31901. currentUnit = units[unitIdx];
  31902. minimum = extDate.align(minimum, currentUnit[0], currentUnit[1]);
  31903. if (extDate.diff(minimum, maximum, currentUnit[0]) > dataX.length * 2 * currentUnit[1]) {
  31904. continue;
  31905. }
  31906. if (currentUnit[3] && result.map['time_' + currentUnit[3]]) {
  31907. lastOffset = result.map['time_' + currentUnit[3]][0];
  31908. lastOffsetEnd = result.map['time_' + currentUnit[3]][1];
  31909. } else {
  31910. lastOffset = plainStart;
  31911. lastOffsetEnd = plainEnd;
  31912. }
  31913. start = last;
  31914. current = minimum;
  31915. first = true;
  31916. startIdxs[last] = startIdxs[lastOffset];
  31917. endIdxs[last] = endIdxs[lastOffset];
  31918. minIdxs[last] = minIdxs[lastOffset];
  31919. maxIdxs[last] = maxIdxs[lastOffset];
  31920. opens[last] = opens[lastOffset];
  31921. closes[last] = closes[lastOffset];
  31922. minXs[last] = minXs[lastOffset];
  31923. minYs[last] = minYs[lastOffset];
  31924. maxXs[last] = maxXs[lastOffset];
  31925. maxYs[last] = maxYs[lastOffset];
  31926. current = Ext.Date.add(current, currentUnit[0], currentUnit[1]);
  31927. for (i = lastOffset + 1; i < lastOffsetEnd; i++) {
  31928. if (dataX[endIdxs[i]] < +current) {
  31929. endIdxs[last] = endIdxs[i];
  31930. closes[last] = closes[i];
  31931. if (maxYs[i] > maxYs[last]) {
  31932. maxYs[last] = maxYs[i];
  31933. maxXs[last] = maxXs[i];
  31934. maxIdxs[last] = maxIdxs[i];
  31935. }
  31936. if (minYs[i] < minYs[last]) {
  31937. minYs[last] = minYs[i];
  31938. minXs[last] = minXs[i];
  31939. minIdxs[last] = minIdxs[i];
  31940. }
  31941. } else {
  31942. last++;
  31943. startIdxs[last] = startIdxs[i];
  31944. endIdxs[last] = endIdxs[i];
  31945. minIdxs[last] = minIdxs[i];
  31946. maxIdxs[last] = maxIdxs[i];
  31947. opens[last] = opens[i];
  31948. closes[last] = closes[i];
  31949. minXs[last] = minXs[i];
  31950. minYs[last] = minYs[i];
  31951. maxXs[last] = maxXs[i];
  31952. maxYs[last] = maxYs[i];
  31953. current = Ext.Date.add(current, currentUnit[0], currentUnit[1]);
  31954. }
  31955. }
  31956. if (last > start) {
  31957. result.map['time_' + currentUnit[2]] = [
  31958. start,
  31959. last
  31960. ];
  31961. }
  31962. }
  31963. },
  31964. /**
  31965. * @private
  31966. * @param {Object} result
  31967. * @param {Number} position
  31968. * @param {Number} dataX
  31969. * @param {Number} dataOpen
  31970. * @param {Number} dataHigh
  31971. * @param {Number} dataLow
  31972. * @param {Number} dataClose
  31973. */
  31974. "double": function(result, position, dataX, dataOpen, dataHigh, dataLow, dataClose) {
  31975. var offset = 0,
  31976. lastOffset,
  31977. step = 1,
  31978. i, startIdx, endIdx, minIdx, maxIdx, open, close, minX, minY, maxX, maxY;
  31979. while (position > offset + 1) {
  31980. lastOffset = offset;
  31981. offset = position;
  31982. step += step;
  31983. for (i = lastOffset; i < offset; i += 2) {
  31984. if (i === offset - 1) {
  31985. startIdx = result.startIdx[i];
  31986. endIdx = result.endIdx[i];
  31987. minIdx = result.minIdx[i];
  31988. maxIdx = result.maxIdx[i];
  31989. open = result.open[i];
  31990. close = result.close[i];
  31991. minX = result.minX[i];
  31992. minY = result.minY[i];
  31993. maxX = result.maxX[i];
  31994. maxY = result.maxY[i];
  31995. } else {
  31996. startIdx = result.startIdx[i];
  31997. endIdx = result.endIdx[i + 1];
  31998. open = result.open[i];
  31999. close = result.close[i];
  32000. if (result.minY[i] <= result.minY[i + 1]) {
  32001. minIdx = result.minIdx[i];
  32002. minX = result.minX[i];
  32003. minY = result.minY[i];
  32004. } else {
  32005. minIdx = result.minIdx[i + 1];
  32006. minX = result.minX[i + 1];
  32007. minY = result.minY[i + 1];
  32008. }
  32009. if (result.maxY[i] >= result.maxY[i + 1]) {
  32010. maxIdx = result.maxIdx[i];
  32011. maxX = result.maxX[i];
  32012. maxY = result.maxY[i];
  32013. } else {
  32014. maxIdx = result.maxIdx[i + 1];
  32015. maxX = result.maxX[i + 1];
  32016. maxY = result.maxY[i + 1];
  32017. }
  32018. }
  32019. result.startIdx[position] = startIdx;
  32020. result.endIdx[position] = endIdx;
  32021. result.minIdx[position] = minIdx;
  32022. result.maxIdx[position] = maxIdx;
  32023. result.open[position] = open;
  32024. result.close[position] = close;
  32025. result.minX[position] = minX;
  32026. result.minY[position] = minY;
  32027. result.maxX[position] = maxX;
  32028. result.maxY[position] = maxY;
  32029. position++;
  32030. }
  32031. result.map['double_' + step] = [
  32032. offset,
  32033. position
  32034. ];
  32035. }
  32036. },
  32037. /**
  32038. * @method
  32039. * @private
  32040. */
  32041. none: Ext.emptyFn,
  32042. /**
  32043. * @private
  32044. *
  32045. * @param {Number} dataX
  32046. * @param {Number} dataOpen
  32047. * @param {Number} dataHigh
  32048. * @param {Number} dataLow
  32049. * @param {Number} dataClose
  32050. * @return {Object}
  32051. */
  32052. aggregateData: function(dataX, dataOpen, dataHigh, dataLow, dataClose) {
  32053. var length = dataX.length,
  32054. startIdx = [],
  32055. endIdx = [],
  32056. minIdx = [],
  32057. maxIdx = [],
  32058. open = [],
  32059. minX = [],
  32060. minY = [],
  32061. maxX = [],
  32062. maxY = [],
  32063. close = [],
  32064. result = {
  32065. startIdx: startIdx,
  32066. endIdx: endIdx,
  32067. minIdx: minIdx,
  32068. maxIdx: maxIdx,
  32069. open: open,
  32070. minX: minX,
  32071. minY: minY,
  32072. maxX: maxX,
  32073. maxY: maxY,
  32074. close: close
  32075. },
  32076. i;
  32077. for (i = 0; i < length; i++) {
  32078. startIdx[i] = i;
  32079. endIdx[i] = i;
  32080. minIdx[i] = i;
  32081. maxIdx[i] = i;
  32082. open[i] = dataOpen[i];
  32083. minX[i] = dataX[i];
  32084. minY[i] = dataLow[i];
  32085. maxX[i] = dataX[i];
  32086. maxY[i] = dataHigh[i];
  32087. close[i] = dataClose[i];
  32088. }
  32089. result.map = {
  32090. original: [
  32091. 0,
  32092. length
  32093. ]
  32094. };
  32095. if (length) {
  32096. this[this.getStrategy()](result, length, dataX, dataOpen, dataHigh, dataLow, dataClose);
  32097. }
  32098. return result;
  32099. },
  32100. /**
  32101. * @private
  32102. * @param {Object} items
  32103. * @param {Number} start
  32104. * @param {Number} end
  32105. * @param {Number} key
  32106. * @return {*}
  32107. */
  32108. binarySearchMin: function(items, start, end, key) {
  32109. var dx = this.dataX;
  32110. if (key <= dx[items.startIdx[0]]) {
  32111. return start;
  32112. }
  32113. if (key >= dx[items.startIdx[end - 1]]) {
  32114. return end - 1;
  32115. }
  32116. while (start + 1 < end) {
  32117. var mid = (start + end) >> 1,
  32118. val = dx[items.startIdx[mid]];
  32119. if (val === key) {
  32120. return mid;
  32121. } else if (val < key) {
  32122. start = mid;
  32123. } else {
  32124. end = mid;
  32125. }
  32126. }
  32127. return start;
  32128. },
  32129. /**
  32130. * @private
  32131. * @param {Object} items
  32132. * @param {Number} start
  32133. * @param {Number} end
  32134. * @param {Number} key
  32135. * @return {*}
  32136. */
  32137. binarySearchMax: function(items, start, end, key) {
  32138. var dx = this.dataX;
  32139. if (key <= dx[items.endIdx[0]]) {
  32140. return start;
  32141. }
  32142. if (key >= dx[items.endIdx[end - 1]]) {
  32143. return end - 1;
  32144. }
  32145. while (start + 1 < end) {
  32146. var mid = (start + end) >> 1,
  32147. val = dx[items.endIdx[mid]];
  32148. if (val === key) {
  32149. return mid;
  32150. } else if (val < key) {
  32151. start = mid;
  32152. } else {
  32153. end = mid;
  32154. }
  32155. }
  32156. return end;
  32157. },
  32158. constructor: function(config) {
  32159. this.initConfig(config);
  32160. },
  32161. /**
  32162. * Sets the data of the segment tree.
  32163. * @param {Number} dataX
  32164. * @param {Number} dataOpen
  32165. * @param {Number} dataHigh
  32166. * @param {Number} dataLow
  32167. * @param {Number} dataClose
  32168. */
  32169. setData: function(dataX, dataOpen, dataHigh, dataLow, dataClose) {
  32170. if (!dataHigh) {
  32171. dataClose = dataLow = dataHigh = dataOpen;
  32172. }
  32173. this.dataX = dataX;
  32174. this.dataOpen = dataOpen;
  32175. this.dataHigh = dataHigh;
  32176. this.dataLow = dataLow;
  32177. this.dataClose = dataClose;
  32178. if (dataX.length === dataHigh.length && dataX.length === dataLow.length) {
  32179. this.cache = this.aggregateData(dataX, dataOpen, dataHigh, dataLow, dataClose);
  32180. }
  32181. },
  32182. /**
  32183. * Returns the minimum range of data that fits the given range and step size.
  32184. *
  32185. * @param {Number} min
  32186. * @param {Number} max
  32187. * @param {Number} estStep
  32188. * @return {Object} The aggregation information.
  32189. * @return {Number} return.start
  32190. * @return {Number} return.end
  32191. * @return {Object} return.data The aggregated data
  32192. */
  32193. getAggregation: function(min, max, estStep) {
  32194. if (!this.cache) {
  32195. return null;
  32196. }
  32197. var minStep = Infinity,
  32198. range = this.dataX[this.dataX.length - 1] - this.dataX[0],
  32199. cacheMap = this.cache.map,
  32200. result = cacheMap.original,
  32201. name, positions, ln, step, minIdx, maxIdx;
  32202. for (name in cacheMap) {
  32203. positions = cacheMap[name];
  32204. ln = positions[1] - positions[0] - 1;
  32205. step = range / ln;
  32206. if (estStep <= step && step < minStep) {
  32207. result = positions;
  32208. minStep = step;
  32209. }
  32210. }
  32211. minIdx = Math.max(this.binarySearchMin(this.cache, result[0], result[1], min), result[0]);
  32212. maxIdx = Math.min(this.binarySearchMax(this.cache, result[0], result[1], max) + 1, result[1]);
  32213. return {
  32214. data: this.cache,
  32215. start: minIdx,
  32216. end: maxIdx
  32217. };
  32218. }
  32219. });
  32220. /**
  32221. *
  32222. */
  32223. Ext.define('Ext.chart.series.sprite.Aggregative', {
  32224. extend: 'Ext.chart.series.sprite.Cartesian',
  32225. requires: [
  32226. 'Ext.draw.LimitedCache',
  32227. 'Ext.draw.SegmentTree'
  32228. ],
  32229. inheritableStatics: {
  32230. def: {
  32231. processors: {
  32232. /**
  32233. * @cfg {Number[]} [dataHigh=null] Data items representing the high values of the aggregated data.
  32234. */
  32235. dataHigh: 'data',
  32236. /**
  32237. * @cfg {Number[]} [dataLow=null] Data items representing the low values of the aggregated data.
  32238. */
  32239. dataLow: 'data',
  32240. /**
  32241. * @cfg {Number[]} [dataClose=null] Data items representing the closing values of the aggregated data.
  32242. */
  32243. dataClose: 'data'
  32244. },
  32245. aliases: {
  32246. /**
  32247. * @cfg {Number[]} [dataOpen=null] Data items representing the opening values of the aggregated data.
  32248. */
  32249. dataOpen: 'dataY'
  32250. },
  32251. defaults: {
  32252. dataHigh: null,
  32253. dataLow: null,
  32254. dataClose: null
  32255. }
  32256. }
  32257. },
  32258. config: {
  32259. aggregator: {}
  32260. },
  32261. applyAggregator: function(aggregator, oldAggr) {
  32262. return Ext.factory(aggregator, Ext.draw.SegmentTree, oldAggr);
  32263. },
  32264. constructor: function() {
  32265. this.callParent(arguments);
  32266. },
  32267. processDataY: function() {
  32268. var me = this,
  32269. attr = me.attr,
  32270. high = attr.dataHigh,
  32271. low = attr.dataLow,
  32272. close = attr.dataClose,
  32273. open = attr.dataY,
  32274. aggregator;
  32275. me.callParent(arguments);
  32276. if (attr.dataX && open && open.length > 0) {
  32277. aggregator = me.getAggregator();
  32278. if (high) {
  32279. aggregator.setData(attr.dataX, attr.dataY, high, low, close);
  32280. } else {
  32281. aggregator.setData(attr.dataX, attr.dataY);
  32282. }
  32283. }
  32284. },
  32285. getGapWidth: function() {
  32286. return 1;
  32287. },
  32288. renderClipped: function(surface, ctx, dataClipRect, surfaceClipRect) {
  32289. var me = this,
  32290. min = Math.min(dataClipRect[0], dataClipRect[2]),
  32291. max = Math.max(dataClipRect[0], dataClipRect[2]),
  32292. aggregator = me.getAggregator(),
  32293. aggregates = aggregator && aggregator.getAggregation(min, max, (max - min) / surfaceClipRect[2] * me.getGapWidth());
  32294. if (aggregates) {
  32295. me.dataStart = aggregates.data.startIdx[aggregates.start];
  32296. me.dataEnd = aggregates.data.endIdx[aggregates.end - 1];
  32297. me.renderAggregates(aggregates.data, aggregates.start, aggregates.end, surface, ctx, dataClipRect, surfaceClipRect);
  32298. }
  32299. }
  32300. });
  32301. /**
  32302. * @class Ext.chart.series.sprite.CandleStick
  32303. * @extends Ext.chart.series.sprite.Aggregative
  32304. *
  32305. * CandleStick series sprite.
  32306. */
  32307. Ext.define('Ext.chart.series.sprite.CandleStick', {
  32308. alias: 'sprite.candlestickSeries',
  32309. extend: 'Ext.chart.series.sprite.Aggregative',
  32310. inheritableStatics: {
  32311. def: {
  32312. processors: {
  32313. raiseStyle: function(n, o) {
  32314. return Ext.merge({}, o || {}, n);
  32315. },
  32316. dropStyle: function(n, o) {
  32317. return Ext.merge({}, o || {}, n);
  32318. },
  32319. /**
  32320. * @cfg {Number} [barWidth=15] The bar width of the candles.
  32321. */
  32322. barWidth: 'number',
  32323. /**
  32324. * @cfg {Number} [padding=3] The amount of padding between candles.
  32325. */
  32326. padding: 'number',
  32327. /**
  32328. * @cfg {String} [ohlcType='candlestick'] Determines whether candlestick or ohlc is used.
  32329. */
  32330. ohlcType: 'enums(candlestick,ohlc)'
  32331. },
  32332. defaults: {
  32333. raiseStyle: {
  32334. strokeStyle: 'green',
  32335. fillStyle: 'green'
  32336. },
  32337. dropStyle: {
  32338. strokeStyle: 'red',
  32339. fillStyle: 'red'
  32340. },
  32341. barWidth: 15,
  32342. padding: 3,
  32343. lineJoin: 'miter',
  32344. miterLimit: 5,
  32345. ohlcType: 'candlestick'
  32346. },
  32347. triggers: {
  32348. raiseStyle: 'raiseStyle',
  32349. dropStyle: 'dropStyle'
  32350. },
  32351. updaters: {
  32352. raiseStyle: function() {
  32353. var me = this,
  32354. tpl = me.raiseTemplate;
  32355. if (tpl) {
  32356. tpl.setAttributes(me.attr.raiseStyle);
  32357. }
  32358. },
  32359. dropStyle: function() {
  32360. var me = this,
  32361. tpl = me.dropTemplate;
  32362. if (tpl) {
  32363. tpl.setAttributes(me.attr.dropStyle);
  32364. }
  32365. }
  32366. }
  32367. }
  32368. },
  32369. candlestick: function(ctx, open, high, low, close, mid, halfWidth) {
  32370. var minOC = Math.min(open, close),
  32371. maxOC = Math.max(open, close);
  32372. // lower stick
  32373. ctx.moveTo(mid, low);
  32374. ctx.lineTo(mid, minOC);
  32375. // body rect
  32376. ctx.moveTo(mid + halfWidth, maxOC);
  32377. ctx.lineTo(mid + halfWidth, minOC);
  32378. ctx.lineTo(mid - halfWidth, minOC);
  32379. ctx.lineTo(mid - halfWidth, maxOC);
  32380. ctx.closePath();
  32381. // upper stick
  32382. ctx.moveTo(mid, high);
  32383. ctx.lineTo(mid, maxOC);
  32384. },
  32385. ohlc: function(ctx, open, high, low, close, mid, halfWidth) {
  32386. ctx.moveTo(mid, high);
  32387. ctx.lineTo(mid, low);
  32388. ctx.moveTo(mid, open);
  32389. ctx.lineTo(mid - halfWidth, open);
  32390. ctx.moveTo(mid, close);
  32391. ctx.lineTo(mid + halfWidth, close);
  32392. },
  32393. constructor: function() {
  32394. var me = this,
  32395. Rect = Ext.draw.sprite.Rect;
  32396. me.callParent(arguments);
  32397. me.raiseTemplate = new Rect({
  32398. parent: me
  32399. });
  32400. me.dropTemplate = new Rect({
  32401. parent: me
  32402. });
  32403. },
  32404. getGapWidth: function() {
  32405. var attr = this.attr,
  32406. barWidth = attr.barWidth,
  32407. padding = attr.padding;
  32408. return barWidth + padding;
  32409. },
  32410. renderAggregates: function(aggregates, start, end, surface, ctx, clip) {
  32411. var me = this,
  32412. attr = me.attr,
  32413. ohlcType = attr.ohlcType,
  32414. series = me.getSeries(),
  32415. matrix = attr.matrix,
  32416. xx = matrix.getXX(),
  32417. yy = matrix.getYY(),
  32418. dx = matrix.getDX(),
  32419. dy = matrix.getDY(),
  32420. halfWidth = Math.round(attr.barWidth * 0.5),
  32421. dataX = attr.dataX,
  32422. opens = aggregates.open,
  32423. closes = aggregates.close,
  32424. maxYs = aggregates.maxY,
  32425. minYs = aggregates.minY,
  32426. startIdxs = aggregates.startIdx,
  32427. pixelAdjust = attr.lineWidth * surface.devicePixelRatio / 2,
  32428. renderer = attr.renderer,
  32429. rendererConfig = renderer && {},
  32430. rendererParams, rendererChanges, open, high, low, close, mid, i, template;
  32431. me.rendererData = me.rendererData || {
  32432. store: me.getStore()
  32433. };
  32434. pixelAdjust -= Math.floor(pixelAdjust);
  32435. // Render raises.
  32436. ctx.save();
  32437. template = me.raiseTemplate;
  32438. template.useAttributes(ctx, clip);
  32439. if (!renderer) {
  32440. ctx.beginPath();
  32441. }
  32442. for (i = start; i < end; i++) {
  32443. if (opens[i] <= closes[i]) {
  32444. open = Math.round(opens[i] * yy + dy) + pixelAdjust;
  32445. high = Math.round(maxYs[i] * yy + dy) + pixelAdjust;
  32446. low = Math.round(minYs[i] * yy + dy) + pixelAdjust;
  32447. close = Math.round(closes[i] * yy + dy) + pixelAdjust;
  32448. mid = Math.round(dataX[startIdxs[i]] * xx + dx) + pixelAdjust;
  32449. if (renderer) {
  32450. ctx.save();
  32451. ctx.beginPath();
  32452. rendererConfig.open = open;
  32453. rendererConfig.high = high;
  32454. rendererConfig.low = low;
  32455. rendererConfig.close = close;
  32456. rendererConfig.mid = mid;
  32457. rendererConfig.halfWidth = halfWidth;
  32458. rendererParams = [
  32459. me,
  32460. rendererConfig,
  32461. me.rendererData,
  32462. i
  32463. ];
  32464. rendererChanges = Ext.callback(renderer, null, rendererParams, 0, series);
  32465. Ext.apply(ctx, rendererChanges);
  32466. }
  32467. me[ohlcType](ctx, open, high, low, close, mid, halfWidth);
  32468. if (renderer) {
  32469. ctx.fillStroke(template.attr);
  32470. ctx.restore();
  32471. }
  32472. }
  32473. }
  32474. if (!renderer) {
  32475. ctx.fillStroke(template.attr);
  32476. }
  32477. ctx.restore();
  32478. // Render drops.
  32479. ctx.save();
  32480. template = me.dropTemplate;
  32481. template.useAttributes(ctx, clip);
  32482. if (!renderer) {
  32483. ctx.beginPath();
  32484. }
  32485. for (i = start; i < end; i++) {
  32486. if (opens[i] > closes[i]) {
  32487. open = Math.round(opens[i] * yy + dy) + pixelAdjust;
  32488. high = Math.round(maxYs[i] * yy + dy) + pixelAdjust;
  32489. low = Math.round(minYs[i] * yy + dy) + pixelAdjust;
  32490. close = Math.round(closes[i] * yy + dy) + pixelAdjust;
  32491. mid = Math.round(dataX[startIdxs[i]] * xx + dx) + pixelAdjust;
  32492. if (renderer) {
  32493. ctx.save();
  32494. ctx.beginPath();
  32495. rendererConfig.open = open;
  32496. rendererConfig.high = high;
  32497. rendererConfig.low = low;
  32498. rendererConfig.close = close;
  32499. rendererConfig.mid = mid;
  32500. rendererConfig.halfWidth = halfWidth;
  32501. rendererParams = [
  32502. me,
  32503. rendererConfig,
  32504. me.rendererData,
  32505. i
  32506. ];
  32507. rendererChanges = Ext.callback(renderer, null, rendererParams, 0, me.getSeries());
  32508. Ext.apply(ctx, rendererChanges);
  32509. }
  32510. me[ohlcType](ctx, open, high, low, close, mid, halfWidth);
  32511. if (renderer) {
  32512. ctx.fillStroke(template.attr);
  32513. ctx.restore();
  32514. }
  32515. }
  32516. }
  32517. if (!renderer) {
  32518. ctx.fillStroke(template.attr);
  32519. }
  32520. ctx.restore();
  32521. }
  32522. });
  32523. /**
  32524. * @class Ext.chart.series.CandleStick
  32525. * @extends Ext.chart.series.Cartesian
  32526. *
  32527. * Creates a candlestick or OHLC Chart.
  32528. *
  32529. * CandleStick series are typically used to plot price movements of a security on an exchange over time.
  32530. * The series can be used with the 'time' axis, but since exchanges often close for weekends,
  32531. * and the price data has gaps for those days, it's more practical to use this series with
  32532. * the 'category' axis to avoid rendering those data gaps. The 'category' axis has no notion
  32533. * of time (and thus gaps) and treats every Date object (value of the 'xField') as a unique
  32534. * category. However, it also means that it doesn't support the 'dateFormat' config,
  32535. * which can be easily remedied with a 'renderer' that formats a Date object for use
  32536. * as an axis label. For example:
  32537. *
  32538. * @example
  32539. * new Ext.chart.CartesianChart({
  32540. * xtype: 'cartesian',
  32541. * renderTo: document.body,
  32542. * width: 700,
  32543. * height: 500,
  32544. * insetPadding: 20,
  32545. * innerPadding: '0 20 0 20',
  32546. *
  32547. * store: {
  32548. * data: [
  32549. * {
  32550. * time: new Date('Nov 17 2016'),
  32551. * o: 52.40, h: 52.74, l: 52.18, c: 52.29
  32552. * },
  32553. * {
  32554. * time: new Date('Nov 18 2016'),
  32555. * o: 51.87, h: 52.22, l: 51.51, c: 52.04
  32556. * },
  32557. * {
  32558. * time: new Date('Nov 21 2016'),
  32559. * o: 53.02, h: 53.40, l: 53.02, c: 53.33
  32560. * },
  32561. * {
  32562. * time: new Date('Nov 22 2016'),
  32563. * o: 53.48, h: 53.80, l: 53.13, c: 53.70
  32564. * },
  32565. * {
  32566. * time: new Date('Nov 23 2016'),
  32567. * o: 52.85, h: 53.39, l: 52.76, c: 53.28
  32568. * },
  32569. * {
  32570. * time: new Date('Nov 25 2016'),
  32571. * o: 53.28, h: 53.45, l: 53.20, c: 53.40
  32572. * },
  32573. * {
  32574. * time: new Date('Nov 28 2016'),
  32575. * o: 52.51, h: 52.58, l: 51.96, c: 52.00
  32576. * },
  32577. * {
  32578. * time: new Date('Nov 29 2016'),
  32579. * o: 51.25, h: 51.98, l: 51.10, c: 51.79
  32580. * },
  32581. * {
  32582. * time: new Date('Nov 30 2016'),
  32583. * o: 53.65, h: 54.56, l: 53.60, c: 54.17
  32584. * },
  32585. * {
  32586. * time: new Date('Dec 01 2016'),
  32587. * o: 55.26, h: 55.75, l: 54.94, c: 55.13
  32588. * }
  32589. * ]
  32590. * },
  32591. * axes: [
  32592. * {
  32593. * type: 'numeric',
  32594. * position: 'left'
  32595. * },
  32596. * {
  32597. * type: 'category',
  32598. * position: 'bottom',
  32599. *
  32600. * renderer: function (axis, value) {
  32601. * return Ext.Date.format(value, 'M j\nY');
  32602. * }
  32603. * }
  32604. * ],
  32605. * series: {
  32606. * type: 'candlestick',
  32607. *
  32608. * xField: 'time',
  32609. *
  32610. * openField: 'o',
  32611. * highField: 'h',
  32612. * lowField: 'l',
  32613. * closeField: 'c',
  32614. *
  32615. * style: {
  32616. * barWidth: 10,
  32617. *
  32618. * dropStyle: {
  32619. * fill: 'rgb(222, 87, 87)',
  32620. * stroke: 'rgb(222, 87, 87)',
  32621. * lineWidth: 3
  32622. * },
  32623. * raiseStyle: {
  32624. * fill: 'rgb(48, 189, 167)',
  32625. * stroke: 'rgb(48, 189, 167)',
  32626. * lineWidth: 3
  32627. * }
  32628. * }
  32629. * }
  32630. * });
  32631. */
  32632. Ext.define('Ext.chart.series.CandleStick', {
  32633. extend: 'Ext.chart.series.Cartesian',
  32634. requires: [
  32635. 'Ext.chart.series.sprite.CandleStick'
  32636. ],
  32637. alias: 'series.candlestick',
  32638. type: 'candlestick',
  32639. seriesType: 'candlestickSeries',
  32640. isCandleStick: true,
  32641. config: {
  32642. /**
  32643. * @cfg {String} openField
  32644. * The store record field name that represents the opening value of the given period.
  32645. */
  32646. openField: null,
  32647. /**
  32648. * @cfg {String} highField
  32649. * The store record field name that represents the highest value of the time interval represented.
  32650. */
  32651. highField: null,
  32652. /**
  32653. * @cfg {String} lowField
  32654. * The store record field name that represents the lowest value of the time interval represented.
  32655. */
  32656. lowField: null,
  32657. /**
  32658. * @cfg {String} closeField
  32659. * The store record field name that represents the closing value of the given period.
  32660. */
  32661. closeField: null
  32662. },
  32663. fieldCategoryY: [
  32664. 'Open',
  32665. 'High',
  32666. 'Low',
  32667. 'Close'
  32668. ],
  32669. themeColorCount: function() {
  32670. return 2;
  32671. }
  32672. });
  32673. /**
  32674. * @abstract
  32675. * @class Ext.chart.series.Polar
  32676. * @extends Ext.chart.series.Series
  32677. *
  32678. * Common base class for series implementations that plot values using polar coordinates.
  32679. *
  32680. * Polar charts accept angles in radians. You can calculate radians with the following
  32681. * formula:
  32682. *
  32683. * radians = degrees x Π/180
  32684. */
  32685. Ext.define('Ext.chart.series.Polar', {
  32686. extend: 'Ext.chart.series.Series',
  32687. config: {
  32688. /**
  32689. * @cfg {Number} [rotation=0]
  32690. * The angle in radians at which the first polar series item should start.
  32691. */
  32692. rotation: 0,
  32693. /**
  32694. * @cfg {Number} radius
  32695. * @private
  32696. * Use {@link Ext.chart.series.Pie#cfg!radiusFactor radiusFactor} instead.
  32697. *
  32698. * The internally used radius of the polar series. Set to `null` will fit the
  32699. * polar series to the boundary.
  32700. */
  32701. radius: null,
  32702. /**
  32703. * @cfg {Array} center for the polar series.
  32704. */
  32705. center: [
  32706. 0,
  32707. 0
  32708. ],
  32709. /**
  32710. * @cfg {Number} [offsetX=0]
  32711. * The x-offset of center of the polar series related to the center of the boundary.
  32712. */
  32713. offsetX: 0,
  32714. /**
  32715. * @cfg {Number} [offsetY=0]
  32716. * The y-offset of center of the polar series related to the center of the boundary.
  32717. */
  32718. offsetY: 0,
  32719. /**
  32720. * @cfg {Boolean} [showInLegend=true]
  32721. * Whether to add the series elements as legend items.
  32722. */
  32723. showInLegend: true,
  32724. /**
  32725. * @private
  32726. * @cfg {String} xField
  32727. */
  32728. xField: null,
  32729. /**
  32730. * @private
  32731. * @cfg {String} yField
  32732. */
  32733. yField: null,
  32734. /**
  32735. * @cfg {String} angleField
  32736. * The store record field name for the angular axes in radar charts,
  32737. * or the size of the slices in pie charts.
  32738. */
  32739. angleField: null,
  32740. /**
  32741. * @cfg {String} radiusField
  32742. * The store record field name for the radial axes in radar charts,
  32743. * or the radius of the slices in pie charts.
  32744. */
  32745. radiusField: null,
  32746. xAxis: null,
  32747. yAxis: null
  32748. },
  32749. directions: [
  32750. 'X',
  32751. 'Y'
  32752. ],
  32753. fieldCategoryX: [
  32754. 'X'
  32755. ],
  32756. fieldCategoryY: [
  32757. 'Y'
  32758. ],
  32759. deprecatedConfigs: {
  32760. field: 'angleField',
  32761. lengthField: 'radiusField'
  32762. },
  32763. constructor: function(config) {
  32764. var me = this,
  32765. configurator = me.self.getConfigurator(),
  32766. configs = configurator.configs,
  32767. p;
  32768. if (config) {
  32769. for (p in me.deprecatedConfigs) {
  32770. if (p in config && !(config in configs)) {
  32771. Ext.raise("'" + p + "' config has been deprecated. Please use the '" + me.deprecatedConfigs[p] + "' config instead.");
  32772. }
  32773. }
  32774. }
  32775. me.callParent([
  32776. config
  32777. ]);
  32778. },
  32779. getXField: function() {
  32780. return this.getAngleField();
  32781. },
  32782. updateXField: function(value) {
  32783. this.setAngleField(value);
  32784. },
  32785. getYField: function() {
  32786. return this.getRadiusField();
  32787. },
  32788. updateYField: function(value) {
  32789. this.setRadiusField(value);
  32790. },
  32791. applyXAxis: function(newAxis, oldAxis) {
  32792. return this.getChart().getAxis(newAxis) || oldAxis;
  32793. },
  32794. applyYAxis: function(newAxis, oldAxis) {
  32795. return this.getChart().getAxis(newAxis) || oldAxis;
  32796. },
  32797. getXRange: function() {
  32798. return [
  32799. this.dataRange[0],
  32800. this.dataRange[2]
  32801. ];
  32802. },
  32803. getYRange: function() {
  32804. return [
  32805. this.dataRange[1],
  32806. this.dataRange[3]
  32807. ];
  32808. },
  32809. themeColorCount: function() {
  32810. var me = this,
  32811. store = me.getStore(),
  32812. count = store && store.getCount() || 0;
  32813. return count;
  32814. },
  32815. isStoreDependantColorCount: true,
  32816. getDefaultSpriteConfig: function() {
  32817. return {
  32818. type: this.seriesType,
  32819. renderer: this.getRenderer(),
  32820. centerX: 0,
  32821. centerY: 0,
  32822. rotationCenterX: 0,
  32823. rotationCenterY: 0
  32824. };
  32825. },
  32826. applyRotation: function(rotation) {
  32827. return Ext.draw.sprite.AttributeParser.angle(Ext.draw.Draw.rad(rotation));
  32828. },
  32829. updateRotation: function(rotation) {
  32830. var sprites = this.getSprites();
  32831. if (sprites && sprites[0]) {
  32832. sprites[0].setAttributes({
  32833. baseRotation: rotation
  32834. });
  32835. }
  32836. }
  32837. });
  32838. /**
  32839. * Displays a gauge chart.
  32840. *
  32841. * @example
  32842. * Ext.create({
  32843. * xtype: 'polar',
  32844. * renderTo: document.body,
  32845. * width: 600,
  32846. * height: 400,
  32847. * store: {
  32848. * fields: ['mph', 'fuel', 'temp', 'rpm'],
  32849. * data: [{
  32850. * mph: 65,
  32851. * fuel: 50,
  32852. * temp: 150,
  32853. * rpm: 6000
  32854. * }]
  32855. * },
  32856. * series: {
  32857. * type: 'gauge',
  32858. * colors: ['#1F6D91', '#90BCC9'],
  32859. * angleField: 'mph',
  32860. * needle: true,
  32861. * donut: 30
  32862. * }
  32863. * });
  32864. */
  32865. Ext.define('Ext.chart.series.Gauge', {
  32866. alias: 'series.gauge',
  32867. extend: 'Ext.chart.series.Polar',
  32868. type: 'gauge',
  32869. seriesType: 'pieslice',
  32870. requires: [
  32871. 'Ext.draw.sprite.Sector'
  32872. ],
  32873. config: {
  32874. /**
  32875. * @cfg {String} angleField
  32876. * The store record field name to be used for the gauge value.
  32877. * The values bound to this field name must be positive real numbers.
  32878. */
  32879. /**
  32880. * @cfg {Boolean} needle
  32881. * If true, display the gauge as a needle, otherwise as a sector.
  32882. */
  32883. needle: false,
  32884. /**
  32885. * @cfg {Number} needleLength
  32886. * Percentage of the length of needle compared to the radius of the entire disk.
  32887. */
  32888. needleLength: 90,
  32889. /**
  32890. * @cfg {Number} needleWidth
  32891. * Width of the needle in pixels.
  32892. */
  32893. needleWidth: 4,
  32894. /**
  32895. * @cfg {Number} donut
  32896. * Percentage of the radius of the donut hole compared to the entire disk.
  32897. */
  32898. donut: 30,
  32899. /**
  32900. * @cfg {Boolean} showInLegend
  32901. * Whether to add the gauge chart elements as legend items.
  32902. */
  32903. showInLegend: false,
  32904. /**
  32905. * @cfg {Number} value
  32906. * Directly sets the displayed value of the gauge.
  32907. * It is ignored if {@link #angleField} is provided.
  32908. */
  32909. value: null,
  32910. /**
  32911. * @cfg {Array} colors (required)
  32912. * An array of color values which is used for the needle and the `sectors`.
  32913. */
  32914. colors: null,
  32915. /**
  32916. * @cfg {Array} sectors
  32917. * Allows to paint sectors of different colors in the background of the gauge,
  32918. * with optional labels.
  32919. *
  32920. * It can be an array of numbers (each between `minimum` and `maximum`) that
  32921. * define the highest value of each sector. For N sectors, only (N-1) values are
  32922. * needed because it is assumed that the first sector starts at `minimum` and the
  32923. * last sector ends at `maximum`. Example: a water temperature gauge that is blue
  32924. * below 20C, red above 80C, gray in-between, and with an orange needle...
  32925. *
  32926. * minimum: 0,
  32927. * maximum: 100,
  32928. * sectors: [20, 80],
  32929. * colors: ['orange', 'blue', 'lightgray', 'red']
  32930. *
  32931. * It can be also an array of objects, each with the following properties:
  32932. *
  32933. * @cfg {Number} sectors.start The starting value of the sector. If omitted, it
  32934. * uses the previous sector's `end` value or the chart's `minimum`.
  32935. * @cfg {Number} sectors.end The ending value of the sector. If omitted, it uses
  32936. * the `maximum` defined for the chart.
  32937. * @cfg {String} sectors.label The label for this sector. Labels are styled using
  32938. * the series' {@link Ext.chart.series.Series#label label} config.
  32939. * @cfg {String} sectors.color The color of the sector. If omitted, it uses one
  32940. * of the `colors` defined for the series or for the chart.
  32941. * @cfg {Object} sectors.style An additional style object for the sector (for
  32942. * instance to set the opacity or to draw a line of a different color around the
  32943. * sector).
  32944. *
  32945. * minimum: 0,
  32946. * maximum: 100,
  32947. * sectors: [{
  32948. * end: 20,
  32949. * label: 'Cold',
  32950. * color: 'aqua'
  32951. * },
  32952. * {
  32953. * end: 80,
  32954. * label: 'Temp.',
  32955. * color: 'lightgray',
  32956. * style: { strokeStyle:'black', strokeOpacity:1, lineWidth:1 }
  32957. * },
  32958. * {
  32959. * label: 'Hot',
  32960. * color: 'tomato'
  32961. * }]
  32962. */
  32963. sectors: null,
  32964. /**
  32965. * @cfg {Number} minimum
  32966. * The minimum value of the gauge.
  32967. */
  32968. minimum: 0,
  32969. /**
  32970. * @cfg {Number} maximum
  32971. * The maximum value of the gauge.
  32972. */
  32973. maximum: 100,
  32974. rotation: 0,
  32975. /**
  32976. * @cfg {Number} totalAngle
  32977. * The size of the sector that the series will occupy.
  32978. */
  32979. totalAngle: Math.PI / 2,
  32980. rect: [
  32981. 0,
  32982. 0,
  32983. 1,
  32984. 1
  32985. ],
  32986. center: [
  32987. 0.5,
  32988. 0.75
  32989. ],
  32990. radius: 0.5,
  32991. /**
  32992. * @cfg {Boolean} wholeDisk Indicates whether to show the whole disk or only the marked part.
  32993. */
  32994. wholeDisk: false
  32995. },
  32996. coordinateX: function() {
  32997. return this.coordinate('X', 0, 2);
  32998. },
  32999. coordinateY: function() {
  33000. return this.coordinate('Y', 1, 2);
  33001. },
  33002. updateNeedle: function(needle) {
  33003. var me = this,
  33004. sprites = me.getSprites(),
  33005. angle = me.valueToAngle(me.getValue());
  33006. if (sprites && sprites.length) {
  33007. sprites[0].setAttributes({
  33008. startAngle: (needle ? angle : 0),
  33009. endAngle: angle,
  33010. strokeOpacity: (needle ? 1 : 0),
  33011. lineWidth: (needle ? me.getNeedleWidth() : 0)
  33012. });
  33013. me.doUpdateStyles();
  33014. }
  33015. },
  33016. themeColorCount: function() {
  33017. var me = this,
  33018. store = me.getStore(),
  33019. count = store && store.getCount() || 0;
  33020. return count + (me.getNeedle() ? 0 : 1);
  33021. },
  33022. updateColors: function(colors, oldColors) {
  33023. var me = this,
  33024. sectors = me.getSectors(),
  33025. sectorCount = sectors && sectors.length,
  33026. sprites = me.getSprites(),
  33027. newColors = Ext.Array.clone(colors),
  33028. colorCount = colors && colors.length,
  33029. i;
  33030. if (!colorCount || !colors[0]) {
  33031. return;
  33032. }
  33033. // Make sure the 'sectors' colors are not overridden.
  33034. for (i = 0; i < sectorCount; i++) {
  33035. newColors[i + 1] = sectors[i].color || newColors[i + 1] || colors[i % colorCount];
  33036. }
  33037. if (sprites.length) {
  33038. sprites[0].setAttributes({
  33039. strokeStyle: newColors[0]
  33040. });
  33041. }
  33042. this.setSubStyle({
  33043. fillStyle: newColors,
  33044. strokeStyle: newColors
  33045. });
  33046. this.doUpdateStyles();
  33047. },
  33048. updateRect: function(rect) {
  33049. var wholeDisk = this.getWholeDisk(),
  33050. halfTotalAngle = wholeDisk ? Math.PI : this.getTotalAngle() / 2,
  33051. donut = this.getDonut() / 100,
  33052. width, height, radius;
  33053. if (halfTotalAngle <= Math.PI / 2) {
  33054. width = 2 * Math.sin(halfTotalAngle);
  33055. height = 1 - donut * Math.cos(halfTotalAngle);
  33056. } else {
  33057. width = 2;
  33058. height = 1 - Math.cos(halfTotalAngle);
  33059. }
  33060. radius = Math.min(rect[2] / width, rect[3] / height);
  33061. this.setRadius(radius);
  33062. this.setCenter([
  33063. rect[2] / 2,
  33064. radius + (rect[3] - height * radius) / 2
  33065. ]);
  33066. },
  33067. updateCenter: function(center) {
  33068. this.setStyle({
  33069. centerX: center[0],
  33070. centerY: center[1],
  33071. rotationCenterX: center[0],
  33072. rotationCenterY: center[1]
  33073. });
  33074. this.doUpdateStyles();
  33075. },
  33076. updateRotation: function(rotation) {
  33077. this.setStyle({
  33078. rotationRads: rotation - (this.getTotalAngle() + Math.PI) / 2
  33079. });
  33080. this.doUpdateStyles();
  33081. },
  33082. doUpdateShape: function(radius, donut) {
  33083. var me = this,
  33084. sectors = me.getSectors(),
  33085. sectorCount = (sectors && sectors.length) || 0,
  33086. needleLength = me.getNeedleLength() / 100,
  33087. endRhoArray;
  33088. // Initialize an array that contains the endRho for each sprite.
  33089. // The first sprite is for the needle, the others for the gauge background sectors.
  33090. // Note: SubStyle arrays are handled in series.getStyleByIndex().
  33091. endRhoArray = [
  33092. radius * needleLength,
  33093. radius
  33094. ];
  33095. while (sectorCount--) {
  33096. endRhoArray.push(radius);
  33097. }
  33098. me.setSubStyle({
  33099. endRho: endRhoArray,
  33100. startRho: radius / 100 * donut
  33101. });
  33102. me.doUpdateStyles();
  33103. },
  33104. updateRadius: function(radius) {
  33105. var donut = this.getDonut();
  33106. this.doUpdateShape(radius, donut);
  33107. },
  33108. updateDonut: function(donut) {
  33109. var radius = this.getRadius();
  33110. this.doUpdateShape(radius, donut);
  33111. },
  33112. valueToAngle: function(value) {
  33113. value = this.applyValue(value);
  33114. return this.getTotalAngle() * (value - this.getMinimum()) / (this.getMaximum() - this.getMinimum());
  33115. },
  33116. applyValue: function(value) {
  33117. return Math.min(this.getMaximum(), Math.max(value, this.getMinimum()));
  33118. },
  33119. updateValue: function(value) {
  33120. var me = this,
  33121. needle = me.getNeedle(),
  33122. angle = me.valueToAngle(value),
  33123. sprites = me.getSprites();
  33124. sprites[0].getRendererData().value = value;
  33125. sprites[0].setAttributes({
  33126. startAngle: (needle ? angle : 0),
  33127. endAngle: angle
  33128. });
  33129. me.doUpdateStyles();
  33130. },
  33131. processData: function() {
  33132. var me = this,
  33133. store = me.getStore(),
  33134. record = store && store.first(),
  33135. animation, duration, axis, min, max, xField, value;
  33136. if (record) {
  33137. xField = me.getXField();
  33138. if (xField) {
  33139. value = record.get(xField);
  33140. }
  33141. }
  33142. if (axis = me.getXAxis()) {
  33143. min = axis.getMinimum();
  33144. max = axis.getMaximum();
  33145. // Animating the axis here can lead to weird looking results.
  33146. animation = axis.getSprites()[0].getAnimation();
  33147. duration = animation.getDuration();
  33148. animation.setDuration(0);
  33149. if (Ext.isNumber(min)) {
  33150. me.setMinimum(min);
  33151. } else {
  33152. axis.setMinimum(me.getMinimum());
  33153. }
  33154. if (Ext.isNumber(max)) {
  33155. me.setMaximum(max);
  33156. } else {
  33157. axis.setMaximum(me.getMaximum());
  33158. }
  33159. animation.setDuration(duration);
  33160. }
  33161. if (!Ext.isNumber(value)) {
  33162. value = me.getMinimum();
  33163. }
  33164. me.setValue(value);
  33165. },
  33166. getDefaultSpriteConfig: function() {
  33167. return {
  33168. type: this.seriesType,
  33169. renderer: this.getRenderer(),
  33170. animation: {
  33171. customDurations: {
  33172. translationX: 0,
  33173. translationY: 0,
  33174. rotationCenterX: 0,
  33175. rotationCenterY: 0,
  33176. centerX: 0,
  33177. centerY: 0,
  33178. startRho: 0,
  33179. endRho: 0,
  33180. baseRotation: 0
  33181. }
  33182. }
  33183. };
  33184. },
  33185. normalizeSectors: function(sectors) {
  33186. // Make sure all the sectors in the array have a legit start and end.
  33187. // Note: the array is modified in-place.
  33188. var me = this,
  33189. sectorCount = (sectors && sectors.length) || 0,
  33190. i, value, start, end;
  33191. if (sectorCount) {
  33192. for (i = 0; i < sectorCount; i++) {
  33193. value = sectors[i];
  33194. if (typeof value === 'number') {
  33195. sectors[i] = {
  33196. start: (i > 0 ? sectors[i - 1].end : me.getMinimum()),
  33197. end: Math.min(value, me.getMaximum())
  33198. };
  33199. if (i == (sectorCount - 1) && sectors[i].end < me.getMaximum()) {
  33200. sectors[i + 1] = {
  33201. start: sectors[i].end,
  33202. end: me.getMaximum()
  33203. };
  33204. }
  33205. } else {
  33206. if (typeof value.start === 'number') {
  33207. start = Math.max(value.start, me.getMinimum());
  33208. } else {
  33209. start = (i > 0 ? sectors[i - 1].end : me.getMinimum());
  33210. }
  33211. if (typeof value.end === 'number') {
  33212. end = Math.min(value.end, me.getMaximum());
  33213. } else {
  33214. end = me.getMaximum();
  33215. }
  33216. sectors[i].start = start;
  33217. sectors[i].end = end;
  33218. }
  33219. }
  33220. } else {
  33221. sectors = [
  33222. {
  33223. start: me.getMinimum(),
  33224. end: me.getMaximum()
  33225. }
  33226. ];
  33227. }
  33228. return sectors;
  33229. },
  33230. getSprites: function() {
  33231. var me = this,
  33232. store = me.getStore(),
  33233. value = me.getValue(),
  33234. label = me.getLabel(),
  33235. i, ln;
  33236. // The store must be initialized, or the value must be set
  33237. if (!store && !Ext.isNumber(value)) {
  33238. return Ext.emptyArray;
  33239. }
  33240. // Return cached sprites
  33241. var chart = me.getChart(),
  33242. animation = me.getAnimation() || chart && chart.getAnimation(),
  33243. sprites = me.sprites,
  33244. spriteIndex = 0,
  33245. sprite, sectors, attr, rendererData,
  33246. lineWidths = [];
  33247. // Hack to avoid having the lineWidths overwritten by the one specified in the theme.
  33248. // In fact, all the style properties from the needle and sectors should go to the series subStyle.
  33249. if (sprites && sprites.length) {
  33250. sprites[0].setAnimation(animation);
  33251. return sprites;
  33252. }
  33253. rendererData = {
  33254. store: store,
  33255. field: me.getXField(),
  33256. // for backward compatibility only (deprecated in 5.5)
  33257. angleField: me.getXField(),
  33258. value: value,
  33259. series: me
  33260. };
  33261. // Create needle sprite
  33262. me.needleSprite = sprite = me.createSprite();
  33263. sprite.setAttributes({
  33264. zIndex: 10
  33265. }, true);
  33266. sprite.setRendererData(rendererData);
  33267. sprite.setRendererIndex(spriteIndex++);
  33268. lineWidths.push(me.getNeedleWidth());
  33269. if (label) {
  33270. label.getTemplate().setField(true);
  33271. }
  33272. // Enable labels
  33273. // Create background sprite(s)
  33274. sectors = me.normalizeSectors(me.getSectors());
  33275. for (i = 0 , ln = sectors.length; i < ln; i++) {
  33276. attr = {
  33277. startAngle: me.valueToAngle(sectors[i].start),
  33278. endAngle: me.valueToAngle(sectors[i].end),
  33279. label: sectors[i].label,
  33280. fillStyle: sectors[i].color,
  33281. strokeOpacity: 0,
  33282. doCallout: false,
  33283. // Show labels inside sectors.
  33284. labelOverflowPadding: -1
  33285. };
  33286. // Allow labels to overlap.
  33287. Ext.apply(attr, sectors[i].style);
  33288. sprite = me.createSprite();
  33289. sprite.setRendererData(rendererData);
  33290. sprite.setRendererIndex(spriteIndex++);
  33291. sprite.setAttributes(attr, true);
  33292. lineWidths.push(attr.lineWidth);
  33293. }
  33294. me.setSubStyle({
  33295. lineWidth: lineWidths
  33296. });
  33297. me.doUpdateStyles();
  33298. return sprites;
  33299. },
  33300. doUpdateStyles: function() {
  33301. var me = this;
  33302. me.callParent();
  33303. if (me.sprites.length) {
  33304. me.needleSprite.setAttributes({
  33305. startRho: me.getNeedle() ? 0 : (me.getRadius() / 100 * me.getDonut())
  33306. });
  33307. }
  33308. }
  33309. });
  33310. /**
  33311. * @class Ext.chart.series.sprite.Line
  33312. * @extends Ext.chart.series.sprite.Aggregative
  33313. *
  33314. * Line series sprite.
  33315. */
  33316. Ext.define('Ext.chart.series.sprite.Line', {
  33317. alias: 'sprite.lineSeries',
  33318. extend: 'Ext.chart.series.sprite.Aggregative',
  33319. inheritableStatics: {
  33320. def: {
  33321. processors: {
  33322. /**
  33323. * @cfg {Object} [curve={type: 'linear'}]
  33324. * The type of curve that connects the data points.
  33325. *
  33326. * For example:
  33327. *
  33328. * // The data points are connected by line segments.
  33329. * // This is the default setting.
  33330. * curve: {
  33331. * type: 'linear'
  33332. * }
  33333. *
  33334. * // Cardinal spline interpolation is used to produce the curve
  33335. * // that connects the data points. The `tension` parameter can
  33336. * // be used to control the smoothness of the curve. A tension
  33337. * // of 0 corresponds to infinite tension, which results in straight
  33338. * // lines between data points. A tension of 1 corresponds to
  33339. * // no tension, allowing the spline to take the path of least
  33340. * // total bend. With tension values greater than 1, the curve
  33341. * // behaves like a compressed spring, pushed to take a longer path.
  33342. * // A cardinal spline with a tension of 0.5 is a special case.
  33343. * // It is then called a Catmull-Rom spline. Catmull-Rom splines are
  33344. * // thought to be esthetically pleasing and are quite common.
  33345. * // Note: spline interpolation only works on gapless data.
  33346. * curve: {
  33347. * type: 'cardinal,
  33348. * tension: 0.5
  33349. * }
  33350. *
  33351. * // Produces a natural cubic spline with the second derivative
  33352. * // of the spline set to zero at the endpoints.
  33353. * curve: {
  33354. * type: 'natural'
  33355. * }
  33356. *
  33357. * // The data points are connected by alternating horizontal and
  33358. * // vertical lines. The y-value changes after the x-value.
  33359. * curve: {
  33360. * type: 'step-after'
  33361. * }
  33362. *
  33363. */
  33364. curve: 'default',
  33365. /**
  33366. * @cfg {Boolean} [fillArea=false]
  33367. * `true` if the sprite paints the area underneath the line.
  33368. */
  33369. fillArea: 'bool',
  33370. /**
  33371. * @cfg {"gap"/"connect"/"origin"} [nullStyle="gap"]
  33372. * Possible values:
  33373. * 'gap' - null points are rendered as gaps.
  33374. * 'connect' - non-null points are connected across null points, so that
  33375. * there is no gap, unless null points are at the beginning/end of the line.
  33376. * Only the visible data points are connected - if a visible data point
  33377. * is followed by a series of null points that go off screen and eventually
  33378. * terminate with a non-null point, the connection won't be made.
  33379. * 'origin' - null data points are rendered at the origin,
  33380. * which is the y-coordinate of a point where the x and y axes meet.
  33381. * This requires that at least the x-coordinate of a point is a valid value.
  33382. */
  33383. nullStyle: 'enums(gap,connect,origin)',
  33384. /**
  33385. * @cfg {Boolean} [preciseStroke=true]
  33386. * `true` if the line uses precise stroke.
  33387. */
  33388. preciseStroke: 'bool',
  33389. /**
  33390. * @private
  33391. * The x-axis associated with the Line series.
  33392. * We need to know the position of the x-axis to fill the area underneath
  33393. * the stroke properly.
  33394. */
  33395. xAxis: 'default',
  33396. /**
  33397. * @cfg {Number} [yCap=Math.pow(2, 20)]
  33398. * Absolute maximum y-value.
  33399. * Larger values will be capped to avoid rendering issues.
  33400. */
  33401. yCap: 'default'
  33402. },
  33403. // The 'default' processor is used here as we don't want this attribute to animate.
  33404. defaults: {
  33405. curve: {
  33406. type: 'linear'
  33407. },
  33408. nullStyle: 'connect',
  33409. fillArea: false,
  33410. preciseStroke: true,
  33411. xAxis: null,
  33412. yCap: Math.pow(2, 20),
  33413. yJump: 50
  33414. },
  33415. triggers: {
  33416. dataX: 'dataX,bbox,curve',
  33417. dataY: 'dataY,bbox,curve',
  33418. curve: 'curve'
  33419. },
  33420. updaters: {
  33421. curve: 'curveUpdater'
  33422. }
  33423. }
  33424. },
  33425. list: null,
  33426. curveUpdater: function(attr) {
  33427. var me = this,
  33428. dataX = attr.dataX,
  33429. dataY = attr.dataY,
  33430. curve = attr.curve,
  33431. smoothable = dataX && dataY && dataX.length > 2 && dataY.length > 2,
  33432. type = curve.type;
  33433. if (smoothable) {
  33434. if (type === 'natural') {
  33435. me.smoothX = Ext.draw.Draw.naturalSpline(dataX);
  33436. me.smoothY = Ext.draw.Draw.naturalSpline(dataY);
  33437. } else if (type === 'cardinal') {
  33438. me.smoothX = Ext.draw.Draw.cardinalSpline(dataX, curve.tension);
  33439. me.smoothY = Ext.draw.Draw.cardinalSpline(dataY, curve.tension);
  33440. } else {
  33441. smoothable = false;
  33442. }
  33443. }
  33444. if (!smoothable) {
  33445. delete me.smoothX;
  33446. delete me.smoothY;
  33447. }
  33448. },
  33449. updatePlainBBox: function(plain) {
  33450. var attr = this.attr,
  33451. ymin = Math.min(0, attr.dataMinY),
  33452. ymax = Math.max(0, attr.dataMaxY);
  33453. plain.x = attr.dataMinX;
  33454. plain.y = ymin;
  33455. plain.width = attr.dataMaxX - attr.dataMinX;
  33456. plain.height = ymax - ymin;
  33457. },
  33458. drawStrip: function(ctx, strip) {
  33459. ctx.moveTo(strip[0], strip[1]);
  33460. for (var i = 2,
  33461. ln = strip.length; i < ln; i += 2) {
  33462. ctx.lineTo(strip[i], strip[i + 1]);
  33463. }
  33464. },
  33465. drawStraightStroke: function(surface, ctx, start, end, list, xAxis) {
  33466. var me = this,
  33467. attr = me.attr,
  33468. nullStyle = attr.nullStyle,
  33469. isConnect = nullStyle === 'connect',
  33470. isOrigin = nullStyle === 'origin',
  33471. renderer = attr.renderer,
  33472. curve = attr.curve,
  33473. step = curve.type === 'step-after',
  33474. needMoveTo = true,
  33475. ln = list.length,
  33476. lineConfig = {
  33477. type: 'line',
  33478. smooth: false,
  33479. step: step
  33480. };
  33481. var rendererChanges, params, stripStartX, isValidX0, isValidX, isValidX1, isValidPoint0, isValidPoint, isValidPoint1, isGap, lastValidPoint, px, py, x, y, x0, y0, x1, y1, i;
  33482. // 'strip' stores last continuous segment of the stroke,
  33483. // which we may need to re-build, if there's a fill as well.
  33484. // For example, if the renderer returned a style that needs
  33485. // to be applied to the current step, or we reached a null
  33486. // point in the data, where we have to fill the current continuous
  33487. // segment, we build and close a path that will be filled, then
  33488. // re-build the stroke path, using coordinates saved in the 'strip',
  33489. // and render the stroke on top of the fill.
  33490. var strip = [];
  33491. ctx.beginPath();
  33492. for (i = 3; i < ln; i += 3) {
  33493. x0 = list[i - 3];
  33494. y0 = list[i - 2];
  33495. x = list[i];
  33496. y = list[i + 1];
  33497. x1 = list[i + 3];
  33498. y1 = list[i + 4];
  33499. isValidX0 = Ext.isNumber(x0);
  33500. isValidX = Ext.isNumber(x);
  33501. isValidX1 = Ext.isNumber(x1);
  33502. isValidPoint0 = isValidX0 && Ext.isNumber(y0);
  33503. isValidPoint = isValidX && Ext.isNumber(y);
  33504. isValidPoint1 = isValidX1 && Ext.isNumber(y1);
  33505. if (isOrigin) {
  33506. // If only the y-component isn't a valid number,
  33507. // we can 'fix' it by setting it to value of y-origin.
  33508. if (!isValidPoint0 && isValidX0) {
  33509. y0 = xAxis;
  33510. isValidPoint0 = true;
  33511. }
  33512. if (!isValidPoint && isValidX) {
  33513. y = xAxis;
  33514. isValidPoint = true;
  33515. }
  33516. if (!isValidPoint1 && isValidX1) {
  33517. y1 = xAxis;
  33518. isValidPoint1 = true;
  33519. }
  33520. }
  33521. if (renderer) {
  33522. lineConfig.x = x;
  33523. lineConfig.y = y;
  33524. lineConfig.x0 = x0;
  33525. lineConfig.y0 = y0;
  33526. params = [
  33527. me,
  33528. lineConfig,
  33529. me.rendererData,
  33530. start + i / 3
  33531. ];
  33532. // callback(fn, scope, args, delay, caller)
  33533. rendererChanges = Ext.callback(renderer, null, params, 0, me.getSeries());
  33534. }
  33535. if (isGap && isConnect && isValidPoint0 && lastValidPoint) {
  33536. px = lastValidPoint[0];
  33537. py = lastValidPoint[1];
  33538. if (needMoveTo) {
  33539. ctx.beginPath();
  33540. ctx.moveTo(px, py);
  33541. strip.push(px, py);
  33542. stripStartX = px;
  33543. needMoveTo = false;
  33544. }
  33545. if (step) {
  33546. ctx.lineTo(x0, py);
  33547. strip.push(x0, py);
  33548. }
  33549. ctx.lineTo(x0, y0);
  33550. strip.push(x0, y0);
  33551. lastValidPoint = [
  33552. x0,
  33553. y0
  33554. ];
  33555. isGap = false;
  33556. }
  33557. // Special case where we have an uninterrupted segment, followed
  33558. // by a gap, then a valid point, then another gap. The uninterrupted
  33559. // segment should be connenected with the dot situated between the gaps.
  33560. if (isConnect && lastValidPoint && isValidPoint && !isValidPoint0) {
  33561. x0 = lastValidPoint[0];
  33562. y0 = lastValidPoint[1];
  33563. isValidPoint0 = true;
  33564. }
  33565. // Remember last valid point to connect the gap
  33566. // when the next valid point is encountered.
  33567. if (isValidPoint) {
  33568. lastValidPoint = [
  33569. x,
  33570. y
  33571. ];
  33572. }
  33573. if (isValidPoint0 && isValidPoint) {
  33574. if (needMoveTo) {
  33575. ctx.beginPath();
  33576. ctx.moveTo(x0, y0);
  33577. strip.push(x0, y0);
  33578. stripStartX = x0;
  33579. needMoveTo = false;
  33580. }
  33581. } else {
  33582. isGap = true;
  33583. continue;
  33584. }
  33585. if (step) {
  33586. ctx.lineTo(x, y0);
  33587. strip.push(x, y0);
  33588. }
  33589. ctx.lineTo(x, y);
  33590. strip.push(x, y);
  33591. // If the next point is a gap, then we need to fill what
  33592. // has been already rendered so far. The same applies
  33593. // if the renderer returned some changes to apply to
  33594. // the current step.
  33595. if (rendererChanges || !isValidPoint1) {
  33596. ctx.save();
  33597. Ext.apply(ctx, rendererChanges);
  33598. rendererChanges = null;
  33599. if (attr.fillArea) {
  33600. ctx.lineTo(x, xAxis);
  33601. ctx.lineTo(stripStartX, xAxis);
  33602. ctx.closePath();
  33603. ctx.fill();
  33604. }
  33605. // Draw the line on top of the filled area.
  33606. ctx.beginPath();
  33607. me.drawStrip(ctx, strip);
  33608. strip = [];
  33609. ctx.stroke();
  33610. ctx.restore();
  33611. ctx.beginPath();
  33612. // Take note that the starting point of a path has been reset
  33613. // (as a result of filling a sub-path) and needs to be set again
  33614. // for the line to continue in a proper manner.
  33615. needMoveTo = true;
  33616. }
  33617. }
  33618. },
  33619. calculateScale: function(count, end) {
  33620. var power = 0,
  33621. n = count;
  33622. while (n < end && count > 0) {
  33623. power++;
  33624. n += count >> power;
  33625. }
  33626. return Math.pow(2, power > 0 ? power - 1 : power);
  33627. },
  33628. drawSmoothStroke: function(surface, ctx, start, end, list, xAxis) {
  33629. var me = this,
  33630. attr = me.attr,
  33631. step = attr.step,
  33632. matrix = attr.matrix,
  33633. renderer = attr.renderer,
  33634. xx = matrix.getXX(),
  33635. yy = matrix.getYY(),
  33636. dx = matrix.getDX(),
  33637. dy = matrix.getDY(),
  33638. smoothX = me.smoothX,
  33639. smoothY = me.smoothY,
  33640. scale = me.calculateScale(attr.dataX.length, end),
  33641. cx1, cy1, cx2, cy2, x, y, x0, y0, i, j, changes, params,
  33642. lineConfig = {
  33643. type: 'line',
  33644. smooth: true,
  33645. step: step
  33646. };
  33647. ctx.beginPath();
  33648. ctx.moveTo(smoothX[start * 3] * xx + dx, smoothY[start * 3] * yy + dy);
  33649. for (i = 0 , j = start * 3 + 1; i < list.length - 3; i += 3 , j += 3 * scale) {
  33650. cx1 = smoothX[j] * xx + dx;
  33651. cy1 = smoothY[j] * yy + dy;
  33652. cx2 = smoothX[j + 1] * xx + dx;
  33653. cy2 = smoothY[j + 1] * yy + dy;
  33654. x = surface.roundPixel(list[i + 3]);
  33655. y = list[i + 4];
  33656. x0 = surface.roundPixel(list[i]);
  33657. y0 = list[i + 1];
  33658. if (renderer) {
  33659. lineConfig.x0 = x0;
  33660. lineConfig.y0 = y0;
  33661. lineConfig.cx1 = cx1;
  33662. lineConfig.cy1 = cy1;
  33663. lineConfig.cx2 = cx2;
  33664. lineConfig.cy2 = cy2;
  33665. lineConfig.x = x;
  33666. lineConfig.y = y;
  33667. params = [
  33668. me,
  33669. lineConfig,
  33670. me.rendererData,
  33671. start + i / 3 + 1
  33672. ];
  33673. changes = Ext.callback(renderer, null, params, 0, me.getSeries());
  33674. ctx.save();
  33675. Ext.apply(ctx, changes);
  33676. }
  33677. if (attr.fillArea) {
  33678. ctx.moveTo(x0, y0);
  33679. ctx.bezierCurveTo(cx1, cy1, cx2, cy2, x, y);
  33680. ctx.lineTo(x, xAxis);
  33681. ctx.lineTo(x0, xAxis);
  33682. ctx.lineTo(x0, y0);
  33683. ctx.closePath();
  33684. ctx.fill();
  33685. ctx.beginPath();
  33686. }
  33687. // Draw the line on top of the filled area.
  33688. ctx.moveTo(x0, y0);
  33689. ctx.bezierCurveTo(cx1, cy1, cx2, cy2, x, y);
  33690. ctx.stroke();
  33691. ctx.moveTo(x0, y0);
  33692. ctx.closePath();
  33693. if (renderer) {
  33694. ctx.restore();
  33695. }
  33696. ctx.beginPath();
  33697. ctx.moveTo(x, y);
  33698. }
  33699. // Prevent the last visible segment from being stroked twice
  33700. // (second time by the ctx.fillStroke inside Path sprite 'render' method)
  33701. ctx.beginPath();
  33702. },
  33703. drawLabel: function(text, dataX, dataY, labelId, rect) {
  33704. var me = this,
  33705. attr = me.attr,
  33706. label = me.getMarker('labels'),
  33707. labelTpl = label.getTemplate(),
  33708. labelCfg = me.labelCfg || (me.labelCfg = {}),
  33709. surfaceMatrix = me.surfaceMatrix,
  33710. labelX, labelY,
  33711. labelOverflowPadding = attr.labelOverflowPadding,
  33712. halfHeight, labelBBox, changes, params, hasPendingChanges;
  33713. // The coordinates below (data point converted to surface coordinates)
  33714. // are just for the renderer to give it a notion of where the label will be positioned.
  33715. // The actual position of the label will be different
  33716. // (unless the renderer returns x/y coordinates in the changes object)
  33717. // and depend on several things including the size of the text,
  33718. // which has to be measured after the renderer call,
  33719. // since text can be modified by the renderer.
  33720. labelCfg.x = surfaceMatrix.x(dataX, dataY);
  33721. labelCfg.y = surfaceMatrix.y(dataX, dataY);
  33722. if (attr.flipXY) {
  33723. labelCfg.rotationRads = Math.PI * 0.5;
  33724. } else {
  33725. labelCfg.rotationRads = 0;
  33726. }
  33727. labelCfg.text = text;
  33728. if (labelTpl.attr.renderer) {
  33729. params = [
  33730. text,
  33731. label,
  33732. labelCfg,
  33733. me.rendererData,
  33734. labelId
  33735. ];
  33736. changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
  33737. if (typeof changes === 'string') {
  33738. labelCfg.text = changes;
  33739. } else if (typeof changes === 'object') {
  33740. if ('text' in changes) {
  33741. labelCfg.text = changes.text;
  33742. }
  33743. hasPendingChanges = true;
  33744. }
  33745. }
  33746. labelBBox = me.getMarkerBBox('labels', labelId, true);
  33747. if (!labelBBox) {
  33748. me.putMarker('labels', labelCfg, labelId);
  33749. labelBBox = me.getMarkerBBox('labels', labelId, true);
  33750. }
  33751. halfHeight = labelBBox.height / 2;
  33752. labelX = dataX;
  33753. switch (labelTpl.attr.display) {
  33754. case 'under':
  33755. labelY = dataY - halfHeight - labelOverflowPadding;
  33756. break;
  33757. case 'rotate':
  33758. labelX += labelOverflowPadding;
  33759. labelY = dataY - labelOverflowPadding;
  33760. labelCfg.rotationRads = -Math.PI / 4;
  33761. break;
  33762. default:
  33763. // 'over'
  33764. labelY = dataY + halfHeight + labelOverflowPadding;
  33765. }
  33766. labelCfg.x = surfaceMatrix.x(labelX, labelY);
  33767. labelCfg.y = surfaceMatrix.y(labelX, labelY);
  33768. if (hasPendingChanges) {
  33769. Ext.apply(labelCfg, changes);
  33770. }
  33771. me.putMarker('labels', labelCfg, labelId);
  33772. },
  33773. drawMarker: function(x, y, index) {
  33774. var me = this,
  33775. attr = me.attr,
  33776. renderer = attr.renderer,
  33777. surfaceMatrix = me.surfaceMatrix,
  33778. markerCfg = {},
  33779. changes, params;
  33780. if (renderer && me.getMarker('markers')) {
  33781. markerCfg.type = 'marker';
  33782. markerCfg.x = x;
  33783. markerCfg.y = y;
  33784. params = [
  33785. me,
  33786. markerCfg,
  33787. me.rendererData,
  33788. index
  33789. ];
  33790. changes = Ext.callback(renderer, null, params, 0, me.getSeries());
  33791. if (changes) {
  33792. Ext.apply(markerCfg, changes);
  33793. }
  33794. }
  33795. markerCfg.translationX = surfaceMatrix.x(x, y);
  33796. markerCfg.translationY = surfaceMatrix.y(x, y);
  33797. delete markerCfg.x;
  33798. delete markerCfg.y;
  33799. me.putMarker('markers', markerCfg, index, !renderer);
  33800. },
  33801. drawStroke: function(surface, ctx, start, end, list, xAxis) {
  33802. var me = this,
  33803. isSmooth = me.smoothX && me.smoothY;
  33804. if (isSmooth) {
  33805. me.drawSmoothStroke(surface, ctx, start, end, list, xAxis);
  33806. } else {
  33807. me.drawStraightStroke(surface, ctx, start, end, list, xAxis);
  33808. }
  33809. },
  33810. renderAggregates: function(aggregates, start, end, surface, ctx, clip, rect) {
  33811. var me = this,
  33812. attr = me.attr,
  33813. dataX = attr.dataX,
  33814. dataY = attr.dataY,
  33815. labels = attr.labels,
  33816. xAxis = attr.xAxis,
  33817. yCap = attr.yCap,
  33818. isSmooth = attr.smooth && me.smoothX && me.smoothY,
  33819. isDrawLabels = labels && me.getMarker('labels'),
  33820. isDrawMarkers = me.getMarker('markers'),
  33821. matrix = attr.matrix,
  33822. pixel = surface.devicePixelRatio,
  33823. xx = matrix.getXX(),
  33824. yy = matrix.getYY(),
  33825. dx = matrix.getDX(),
  33826. dy = matrix.getDY(),
  33827. list = me.list || (me.list = []),
  33828. minXs = aggregates.minX,
  33829. maxXs = aggregates.maxX,
  33830. minYs = aggregates.minY,
  33831. maxYs = aggregates.maxY,
  33832. idx = aggregates.startIdx,
  33833. isContinuousLine = true,
  33834. isValidMinX, isValidMaxX, isValidMinY, isValidMaxY, xAxisOrigin, isVerticalX, x, y, i, index;
  33835. me.rendererData = {
  33836. store: me.getStore()
  33837. };
  33838. list.length = 0;
  33839. // Say we have 7 y-items (attr.dataY): [20, 19, 17, 15, 11, 10, 14]
  33840. // and 7 x-items (attr.dataX): [0, 1, 2, 3, 4, 5, 6].
  33841. // Then aggregates.startIdx is an aggregated index,
  33842. // where every other item is skipped on each aggregation level:
  33843. // [0, 1, 2, 3, 4, 5, 6,
  33844. // 0, 2, 4, 6,
  33845. // 0, 4,
  33846. // 0]
  33847. // aggregates.minY
  33848. // [20, 19, 17, 15, 11, 10, 14,
  33849. // 19, 15, 10, 14,
  33850. // 15, 10,
  33851. // 10]
  33852. // aggregates.maxY
  33853. // [20, 19, 17, 15, 11, 10, 14,
  33854. // 20, 17, 11, 14,
  33855. // 20, 14,
  33856. // 20]
  33857. // aggregates.minX is
  33858. // [0, 1, 2, 3, 4, 5, 6,
  33859. // 1, 3, 5, 6, // TODO: why this order for min?
  33860. // 3, 5, // TODO: why this inconsistency?
  33861. // 5]
  33862. // aggregates.maxX is
  33863. // [0, 1, 2, 3, 4, 5, 6,
  33864. // 0, 2, 4, 6,
  33865. // 0, 6,
  33866. // 0]
  33867. // Create a list of the form [x0, y0, idx0, x1, y1, idx1, ...],
  33868. // where each x,y pair is a coordinate representing original data point
  33869. // at the idx position.
  33870. for (i = start; i < end; i++) {
  33871. var minX = minXs[i],
  33872. maxX = maxXs[i],
  33873. minY = minYs[i],
  33874. maxY = maxYs[i];
  33875. isValidMinX = Ext.isNumber(minX);
  33876. isValidMinY = Ext.isNumber(minY);
  33877. isValidMaxX = Ext.isNumber(maxX);
  33878. isValidMaxY = Ext.isNumber(maxY);
  33879. if (minX < maxX) {
  33880. list.push(isValidMinX ? (minX * xx + dx) : null, isValidMinY ? (minY * yy + dy) : null, idx[i]);
  33881. list.push(isValidMaxX ? (maxX * xx + dx) : null, isValidMaxY ? (maxY * yy + dy) : null, idx[i]);
  33882. } else if (minX > maxX) {
  33883. list.push(isValidMaxX ? (maxX * xx + dx) : null, isValidMaxY ? (maxY * yy + dy) : null, idx[i]);
  33884. list.push(isValidMinX ? (minX * xx + dx) : null, isValidMinY ? (minY * yy + dy) : null, idx[i]);
  33885. } else {
  33886. list.push(isValidMaxX ? (maxX * xx + dx) : null, isValidMaxY ? (maxY * yy + dy) : null, idx[i]);
  33887. }
  33888. }
  33889. if (list.length) {
  33890. for (i = 0; i < list.length; i += 3) {
  33891. x = list[i];
  33892. y = list[i + 1];
  33893. if (Ext.isNumber(x) && Ext.isNumber(y)) {
  33894. if (y > yCap) {
  33895. y = yCap;
  33896. } else if (y < -yCap) {
  33897. y = -yCap;
  33898. }
  33899. list[i + 1] = y;
  33900. } else {
  33901. isContinuousLine = false;
  33902. continue;
  33903. }
  33904. index = list[i + 2];
  33905. if (isDrawMarkers) {
  33906. me.drawMarker(x, y, index);
  33907. }
  33908. if (isDrawLabels && labels[index]) {
  33909. me.drawLabel(labels[index], x, y, index, rect);
  33910. }
  33911. }
  33912. me.isContinuousLine = isContinuousLine;
  33913. if (isSmooth && !isContinuousLine) {
  33914. Ext.raise("Line smoothing in only supported for gapless data, " + "where all data points are finite numbers.");
  33915. }
  33916. if (xAxis) {
  33917. isVerticalX = xAxis.getAlignment() === 'vertical';
  33918. if (Ext.isNumber(xAxis.floatingAtCoord)) {
  33919. xAxisOrigin = (isVerticalX ? rect[2] : rect[3]) - xAxis.floatingAtCoord;
  33920. } else {
  33921. xAxisOrigin = isVerticalX ? rect[0] : rect[1];
  33922. }
  33923. } else {
  33924. xAxisOrigin = attr.flipXY ? rect[0] : rect[1];
  33925. }
  33926. if (attr.preciseStroke) {
  33927. if (attr.fillArea) {
  33928. ctx.fill();
  33929. }
  33930. if (attr.transformFillStroke) {
  33931. attr.inverseMatrix.toContext(ctx);
  33932. }
  33933. me.drawStroke(surface, ctx, start, end, list, xAxisOrigin);
  33934. if (attr.transformFillStroke) {
  33935. attr.matrix.toContext(ctx);
  33936. }
  33937. ctx.stroke();
  33938. } else {
  33939. me.drawStroke(surface, ctx, start, end, list, xAxisOrigin);
  33940. if (isContinuousLine && isSmooth && attr.fillArea && !attr.renderer) {
  33941. var lastPointX = dataX[dataX.length - 1] * xx + dx + pixel,
  33942. lastPointY = dataY[dataY.length - 1] * yy + dy,
  33943. firstPointX = dataX[0] * xx + dx - pixel,
  33944. firstPointY = dataY[0] * yy + dy;
  33945. // Fill the area from the series to the xAxis in case there
  33946. // are no gaps and no renderer is used, in which case the
  33947. // area would be filled per uninterrupted segment or per
  33948. // step, instead of being filled a single pass.
  33949. ctx.lineTo(lastPointX, lastPointY);
  33950. ctx.lineTo(lastPointX, xAxisOrigin - attr.lineWidth);
  33951. ctx.lineTo(firstPointX, xAxisOrigin - attr.lineWidth);
  33952. ctx.lineTo(firstPointX, firstPointY);
  33953. }
  33954. if (attr.transformFillStroke) {
  33955. attr.matrix.toContext(ctx);
  33956. }
  33957. // Prevent the reverse transform to fix floating point error.
  33958. if (attr.fillArea) {
  33959. ctx.fillStroke(attr, true);
  33960. } else {
  33961. ctx.stroke(true);
  33962. }
  33963. }
  33964. }
  33965. }
  33966. });
  33967. /**
  33968. * @class Ext.chart.series.Line
  33969. * @extends Ext.chart.series.Cartesian
  33970. *
  33971. * Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative information for different
  33972. * categories or other real values (as opposed to the bar chart), that can show some progression (or regression) in the dataset.
  33973. * As with all other series, the Line Series must be appended in the *series* Chart array configuration. See the Chart
  33974. * documentation for more information. A typical configuration object for the line series could be:
  33975. *
  33976. * @example
  33977. * Ext.create({
  33978. * xtype: 'cartesian',
  33979. * renderTo: document.body,
  33980. * width: 600,
  33981. * height: 400,
  33982. * insetPadding: 40,
  33983. * store: {
  33984. * fields: ['name', 'data1', 'data2'],
  33985. * data: [{
  33986. * 'name': 'metric one',
  33987. * 'data1': 10,
  33988. * 'data2': 14
  33989. * }, {
  33990. * 'name': 'metric two',
  33991. * 'data1': 7,
  33992. * 'data2': 16
  33993. * }, {
  33994. * 'name': 'metric three',
  33995. * 'data1': 5,
  33996. * 'data2': 14
  33997. * }, {
  33998. * 'name': 'metric four',
  33999. * 'data1': 2,
  34000. * 'data2': 6
  34001. * }, {
  34002. * 'name': 'metric five',
  34003. * 'data1': 27,
  34004. * 'data2': 36
  34005. * }]
  34006. * },
  34007. * axes: [{
  34008. * type: 'numeric',
  34009. * position: 'left',
  34010. * fields: ['data1'],
  34011. * title: {
  34012. * text: 'Sample Values',
  34013. * fontSize: 15
  34014. * },
  34015. * grid: true,
  34016. * minimum: 0
  34017. * }, {
  34018. * type: 'category',
  34019. * position: 'bottom',
  34020. * fields: ['name'],
  34021. * title: {
  34022. * text: 'Sample Values',
  34023. * fontSize: 15
  34024. * }
  34025. * }],
  34026. * series: [{
  34027. * type: 'line',
  34028. * style: {
  34029. * stroke: '#30BDA7',
  34030. * lineWidth: 2
  34031. * },
  34032. * xField: 'name',
  34033. * yField: 'data1',
  34034. * marker: {
  34035. * type: 'path',
  34036. * path: ['M', - 4, 0, 0, 4, 4, 0, 0, - 4, 'Z'],
  34037. * stroke: '#30BDA7',
  34038. * lineWidth: 2,
  34039. * fill: 'white'
  34040. * }
  34041. * }, {
  34042. * type: 'line',
  34043. * fill: true,
  34044. * style: {
  34045. * fill: '#96D4C6',
  34046. * fillOpacity: .6,
  34047. * stroke: '#0A3F50',
  34048. * strokeOpacity: .6,
  34049. * },
  34050. * xField: 'name',
  34051. * yField: 'data2',
  34052. * marker: {
  34053. * type: 'circle',
  34054. * radius: 4,
  34055. * lineWidth: 2,
  34056. * fill: 'white'
  34057. * }
  34058. * }]
  34059. * });
  34060. *
  34061. * In this configuration we're adding two series (or lines), one bound to the `data1`
  34062. * property of the store and the other to `data3`. The type for both configurations is
  34063. * `line`. The `xField` for both series is the same, the `name` property of the store.
  34064. * Both line series share the same axis, the left axis. You can set particular marker
  34065. * configuration by adding properties onto the marker object. Both series have
  34066. * an object as highlight so that markers animate smoothly to the properties in highlight
  34067. * when hovered. The second series has `fill = true` which means that the line will also
  34068. * have an area below it of the same color.
  34069. *
  34070. * **Note:** In the series definition remember to explicitly set the axis to bind the
  34071. * values of the line series to. This can be done by using the `axis` configuration property.
  34072. */
  34073. Ext.define('Ext.chart.series.Line', {
  34074. extend: 'Ext.chart.series.Cartesian',
  34075. alias: 'series.line',
  34076. type: 'line',
  34077. seriesType: 'lineSeries',
  34078. isLine: true,
  34079. requires: [
  34080. 'Ext.chart.series.sprite.Line'
  34081. ],
  34082. config: {
  34083. /**
  34084. * @cfg {Number} selectionTolerance
  34085. * The offset distance from the cursor position to the line series to trigger events (then used for highlighting series, etc).
  34086. */
  34087. selectionTolerance: 20,
  34088. /**
  34089. * @cfg {Object} curve
  34090. * The type of curve that connects the data points.
  34091. * Please see {@link Ext.chart.series.sprite.Line#curve line sprite documentation}
  34092. * for the full description.
  34093. */
  34094. curve: {
  34095. type: 'linear'
  34096. },
  34097. /**
  34098. * @cfg {Object} style
  34099. * An object containing styles for the visualization lines. These styles will override the theme styles.
  34100. * Some options contained within the style object will are described next.
  34101. */
  34102. /**
  34103. * @cfg {Boolean} smooth
  34104. * `true` if the series' line should be smoothed.
  34105. * Line smoothing only works with gapless data.
  34106. * @deprecated 6.5.0 Use the {@link #curve} config instead.
  34107. */
  34108. smooth: null,
  34109. /**
  34110. * @cfg {Boolean} step
  34111. * If set to `true`, the line uses steps instead of straight lines to connect the dots.
  34112. * It is ignored if `smooth` is true.
  34113. * @deprecated 6.5.0 Use the {@link #curve} config instead.
  34114. */
  34115. step: null,
  34116. /**
  34117. * @cfg {"gap"/"connect"/"origin"} [nullStyle="gap"]
  34118. * Possible values:
  34119. * 'gap' - null points are rendered as gaps.
  34120. * 'connect' - non-null points are connected across null points, so that
  34121. * there is no gap, unless null points are at the beginning/end of the line.
  34122. * Only the visible data points are connected - if a visible data point
  34123. * is followed by a series of null points that go off screen and eventually
  34124. * terminate with a non-null point, the connection won't be made.
  34125. * 'origin' - null data points are rendered at the origin,
  34126. * which is the y-coordinate of a point where the x and y axes meet.
  34127. * This requires that at least the x-coordinate of a point is a valid value.
  34128. */
  34129. nullStyle: 'gap',
  34130. /**
  34131. * @cfg {Boolean} fill
  34132. * If set to `true`, the area underneath the line is filled with the color defined as follows, listed by priority:
  34133. * - The color that is configured for this series ({@link Ext.chart.series.Series#colors}).
  34134. * - The color that is configured for this chart ({@link Ext.chart.AbstractChart#colors}).
  34135. * - The fill color that is set in the {@link #style} config.
  34136. * - The stroke color that is set in the {@link #style} config, or the same color as the line.
  34137. *
  34138. * Note: Do not confuse `series.config.fill` (which is a boolean) with `series.style.fill' (which is an alias
  34139. * for the `fillStyle` property and contains a color). For compatibility with previous versions of the API,
  34140. * if `config.fill` is undefined but a `style.fill' color is provided, `config.fill` is considered true.
  34141. * So the default value below must be undefined, not false.
  34142. */
  34143. fill: undefined,
  34144. aggregator: {
  34145. strategy: 'double'
  34146. }
  34147. },
  34148. themeMarkerCount: function() {
  34149. return 1;
  34150. },
  34151. /**
  34152. * @private
  34153. * Override {@link Ext.chart.series.Series#getDefaultSpriteConfig}
  34154. */
  34155. getDefaultSpriteConfig: function() {
  34156. var me = this,
  34157. parentConfig = me.callParent(arguments),
  34158. style = Ext.apply({}, me.getStyle()),
  34159. styleWithTheme,
  34160. fillArea = false;
  34161. if (me.config.fill !== undefined) {
  34162. // If config.fill is present but there is no fillStyle, then use the
  34163. // strokeStyle to fill (and paint the area the same color as the line).
  34164. if (me.config.fill) {
  34165. fillArea = true;
  34166. if (style.fillStyle === undefined) {
  34167. if (style.strokeStyle === undefined) {
  34168. styleWithTheme = me.getStyleWithTheme();
  34169. style.fillStyle = styleWithTheme.fillStyle;
  34170. style.strokeStyle = styleWithTheme.strokeStyle;
  34171. } else {
  34172. style.fillStyle = style.strokeStyle;
  34173. }
  34174. }
  34175. }
  34176. } else {
  34177. // For compatibility with previous versions of the API, if config.fill
  34178. // is undefined but style.fillStyle is provided, we fill the area.
  34179. if (style.fillStyle) {
  34180. fillArea = true;
  34181. }
  34182. }
  34183. // If we don't fill, then delete the fillStyle because that's what is used by
  34184. // the Line sprite to fill below the line.
  34185. if (!fillArea) {
  34186. delete style.fillStyle;
  34187. }
  34188. style = Ext.apply(parentConfig || {}, style);
  34189. return Ext.apply(style, {
  34190. fillArea: fillArea,
  34191. selectionTolerance: me.config.selectionTolerance
  34192. });
  34193. },
  34194. updateFill: function(fill) {
  34195. this.withSprite(function(sprite) {
  34196. return sprite.setAttributes({
  34197. fillArea: fill
  34198. });
  34199. });
  34200. },
  34201. updateCurve: function(curve) {
  34202. this.withSprite(function(sprite) {
  34203. return sprite.setAttributes({
  34204. curve: curve
  34205. });
  34206. });
  34207. },
  34208. getCurve: function() {
  34209. return this.withSprite(function(sprite) {
  34210. return sprite.attr.curve;
  34211. });
  34212. },
  34213. updateNullStyle: function(nullStyle) {
  34214. this.withSprite(function(sprite) {
  34215. return sprite.setAttributes({
  34216. nullStyle: nullStyle
  34217. });
  34218. });
  34219. },
  34220. updateSmooth: function(smooth) {
  34221. this.setCurve({
  34222. type: smooth ? 'natural' : 'linear'
  34223. });
  34224. },
  34225. updateStep: function(step) {
  34226. this.setCurve({
  34227. type: step ? 'step-after' : 'linear'
  34228. });
  34229. }
  34230. });
  34231. /**
  34232. * @class Ext.chart.series.sprite.PieSlice
  34233. *
  34234. * Pie slice sprite.
  34235. */
  34236. Ext.define('Ext.chart.series.sprite.PieSlice', {
  34237. extend: 'Ext.draw.sprite.Sector',
  34238. mixins: {
  34239. markerHolder: 'Ext.chart.MarkerHolder'
  34240. },
  34241. alias: 'sprite.pieslice',
  34242. inheritableStatics: {
  34243. def: {
  34244. processors: {
  34245. /**
  34246. * @cfg {Boolean} [doCallout=true]
  34247. * 'true' if the pie series uses label callouts.
  34248. */
  34249. doCallout: 'bool',
  34250. /**
  34251. * @cfg {String} [label='']
  34252. * Label associated with the Pie sprite.
  34253. */
  34254. label: 'string',
  34255. // @deprecated Use series.label.orientation config instead.
  34256. // @since 5.0.1
  34257. rotateLabels: 'bool',
  34258. /**
  34259. * @cfg {Number} [labelOverflowPadding=10]
  34260. * Padding around labels to determine overlap.
  34261. * Any negative number allows the labels to overlap.
  34262. */
  34263. labelOverflowPadding: 'number',
  34264. renderer: 'default'
  34265. },
  34266. defaults: {
  34267. doCallout: true,
  34268. rotateLabels: true,
  34269. label: '',
  34270. labelOverflowPadding: 10,
  34271. renderer: null
  34272. }
  34273. }
  34274. },
  34275. config: {
  34276. /**
  34277. * @private
  34278. * @cfg {Object} rendererData The object that is passed to the renderer.
  34279. *
  34280. * For instance when the PieSlice sprite is used in a Gauge chart, the object
  34281. * contains the 'store' and 'angleField' properties, and the 'value' as well
  34282. * for that one PieSlice that is used to draw the needle of the Gauge.
  34283. */
  34284. rendererData: null,
  34285. rendererIndex: 0,
  34286. series: null
  34287. },
  34288. setGradientBBox: function(ctx, rect) {
  34289. var me = this,
  34290. attr = me.attr,
  34291. hasGradients = (attr.fillStyle && attr.fillStyle.isGradient) || (attr.strokeStyle && attr.strokeStyle.isGradient);
  34292. if (hasGradients && !attr.constrainGradients) {
  34293. var midAngle = me.getMidAngle(),
  34294. margin = attr.margin,
  34295. cx = attr.centerX,
  34296. cy = attr.centerY,
  34297. r = attr.endRho,
  34298. matrix = attr.matrix,
  34299. scaleX = matrix.getScaleX(),
  34300. scaleY = matrix.getScaleY(),
  34301. w = scaleX * r,
  34302. h = scaleY * r,
  34303. bbox = {
  34304. width: w + w,
  34305. height: h + h
  34306. };
  34307. if (margin) {
  34308. cx += margin * Math.cos(midAngle);
  34309. cy += margin * Math.sin(midAngle);
  34310. }
  34311. bbox.x = matrix.x(cx, cy) - w;
  34312. bbox.y = matrix.y(cx, cy) - h;
  34313. ctx.setGradientBBox(bbox);
  34314. } else {
  34315. me.callParent([
  34316. ctx,
  34317. rect
  34318. ]);
  34319. }
  34320. },
  34321. render: function(surface, ctx, rect) {
  34322. var me = this,
  34323. attr = me.attr,
  34324. itemCfg = {},
  34325. changes;
  34326. if (attr.renderer) {
  34327. itemCfg = {
  34328. type: 'sector',
  34329. centerX: attr.centerX,
  34330. centerY: attr.centerY,
  34331. margin: attr.margin,
  34332. startAngle: Math.min(attr.startAngle, attr.endAngle),
  34333. endAngle: Math.max(attr.startAngle, attr.endAngle),
  34334. startRho: Math.min(attr.startRho, attr.endRho),
  34335. endRho: Math.max(attr.startRho, attr.endRho)
  34336. };
  34337. changes = Ext.callback(attr.renderer, null, [
  34338. me,
  34339. itemCfg,
  34340. me.getRendererData(),
  34341. me.getRendererIndex()
  34342. ], 0, me.getSeries());
  34343. me.setAttributes(changes);
  34344. me.useAttributes(ctx, rect);
  34345. }
  34346. // Draw the sector
  34347. me.callParent([
  34348. surface,
  34349. ctx,
  34350. rect
  34351. ]);
  34352. // Draw the labels
  34353. if (attr.label && me.getMarker('labels')) {
  34354. me.placeLabel();
  34355. }
  34356. },
  34357. placeLabel: function() {
  34358. var me = this,
  34359. attr = me.attr,
  34360. attributeId = attr.attributeId,
  34361. startAngle = Math.min(attr.startAngle, attr.endAngle),
  34362. endAngle = Math.max(attr.startAngle, attr.endAngle),
  34363. midAngle = (startAngle + endAngle) * 0.5,
  34364. margin = attr.margin,
  34365. centerX = attr.centerX,
  34366. centerY = attr.centerY,
  34367. sinMidAngle = Math.sin(midAngle),
  34368. cosMidAngle = Math.cos(midAngle),
  34369. startRho = Math.min(attr.startRho, attr.endRho) + margin,
  34370. endRho = Math.max(attr.startRho, attr.endRho) + margin,
  34371. midRho = (startRho + endRho) * 0.5,
  34372. surfaceMatrix = me.surfaceMatrix,
  34373. labelCfg = me.labelCfg || (me.labelCfg = {}),
  34374. label = me.getMarker('labels'),
  34375. labelTpl = label.getTemplate(),
  34376. hideLessThan = labelTpl.getHideLessThan(),
  34377. calloutLine = labelTpl.getCalloutLine(),
  34378. labelBox, x, y, changes, params, calloutLineLength;
  34379. if (calloutLine) {
  34380. calloutLineLength = calloutLine.length || 40;
  34381. } else {
  34382. calloutLineLength = 0;
  34383. }
  34384. surfaceMatrix.appendMatrix(attr.matrix);
  34385. labelCfg.text = attr.label;
  34386. x = centerX + cosMidAngle * midRho;
  34387. y = centerY + sinMidAngle * midRho;
  34388. labelCfg.x = surfaceMatrix.x(x, y);
  34389. labelCfg.y = surfaceMatrix.y(x, y);
  34390. x = centerX + cosMidAngle * endRho;
  34391. y = centerY + sinMidAngle * endRho;
  34392. labelCfg.calloutStartX = surfaceMatrix.x(x, y);
  34393. labelCfg.calloutStartY = surfaceMatrix.y(x, y);
  34394. x = centerX + cosMidAngle * (endRho + calloutLineLength);
  34395. y = centerY + sinMidAngle * (endRho + calloutLineLength);
  34396. labelCfg.calloutPlaceX = surfaceMatrix.x(x, y);
  34397. labelCfg.calloutPlaceY = surfaceMatrix.y(x, y);
  34398. if (!attr.rotateLabels) {
  34399. labelCfg.rotationRads = 0;
  34400. //<debug>
  34401. Ext.log.warn("'series.style.rotateLabels' config is deprecated. " + "Use 'series.label.orientation' config instead.");
  34402. } else //</debug>
  34403. {
  34404. switch (labelTpl.attr.orientation) {
  34405. case 'horizontal':
  34406. labelCfg.rotationRads = midAngle + Math.atan2(surfaceMatrix.y(1, 0) - surfaceMatrix.y(0, 0), surfaceMatrix.x(1, 0) - surfaceMatrix.x(0, 0)) + Math.PI / 2;
  34407. break;
  34408. case 'vertical':
  34409. labelCfg.rotationRads = midAngle + Math.atan2(surfaceMatrix.y(1, 0) - surfaceMatrix.y(0, 0), surfaceMatrix.x(1, 0) - surfaceMatrix.x(0, 0));
  34410. break;
  34411. }
  34412. }
  34413. labelCfg.calloutColor = (calloutLine && calloutLine.color) || me.attr.fillStyle;
  34414. if (calloutLine) {
  34415. if (calloutLine.width) {
  34416. labelCfg.calloutWidth = calloutLine.width;
  34417. }
  34418. } else {
  34419. labelCfg.calloutColor = 'none';
  34420. }
  34421. labelCfg.globalAlpha = attr.globalAlpha * attr.fillOpacity;
  34422. // If a slice is empty, don't display the label.
  34423. // This behavior can be overridden by a renderer.
  34424. if (labelTpl.display !== 'none') {
  34425. labelCfg.hidden = (attr.startAngle == attr.endAngle);
  34426. }
  34427. if (labelTpl.attr.renderer) {
  34428. // Note: the labels are 'put' by the Ext.chart.series.Pie.updateLabelData, so we can
  34429. // be sure the label sprite instances will exist and can be accessed from the label
  34430. // renderer on first render. For example, with 'bar' series this isn't the case,
  34431. // so we make a check and create a label instance if necessary.
  34432. params = [
  34433. me.attr.label,
  34434. label,
  34435. labelCfg,
  34436. me.getRendererData(),
  34437. me.getRendererIndex()
  34438. ];
  34439. changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
  34440. if (typeof changes === 'string') {
  34441. labelCfg.text = changes;
  34442. } else {
  34443. Ext.apply(labelCfg, changes);
  34444. }
  34445. }
  34446. me.putMarker('labels', labelCfg, attributeId);
  34447. labelBox = me.getMarkerBBox('labels', attributeId, true);
  34448. if (labelBox) {
  34449. if (attr.doCallout && ((endAngle - startAngle) * endRho > hideLessThan || attr.highlighted)) {
  34450. if (labelTpl.attr.display === 'outside') {
  34451. me.putMarker('labels', {
  34452. callout: 1
  34453. }, attributeId);
  34454. } else if (labelTpl.attr.display === 'inside') {
  34455. me.putMarker('labels', {
  34456. callout: 0
  34457. }, attributeId);
  34458. } else {
  34459. me.putMarker('labels', {
  34460. callout: 1 - me.sliceContainsLabel(attr, labelBox)
  34461. }, attributeId);
  34462. }
  34463. } else {
  34464. me.putMarker('labels', {
  34465. globalAlpha: me.sliceContainsLabel(attr, labelBox)
  34466. }, attributeId);
  34467. }
  34468. }
  34469. },
  34470. sliceContainsLabel: function(attr, bbox) {
  34471. var padding = attr.labelOverflowPadding,
  34472. middle = (attr.endRho + attr.startRho) / 2,
  34473. outer = middle + (bbox.width + padding) / 2,
  34474. inner = middle - (bbox.width + padding) / 2,
  34475. sliceAngle, l1, l2, l3;
  34476. if (padding < 0) {
  34477. return 1;
  34478. }
  34479. if (bbox.width + padding * 2 > (attr.endRho - attr.startRho)) {
  34480. return 0;
  34481. }
  34482. l1 = Math.sqrt(attr.endRho * attr.endRho - outer * outer);
  34483. l2 = Math.sqrt(attr.endRho * attr.endRho - inner * inner);
  34484. sliceAngle = Math.abs(attr.endAngle - attr.startAngle);
  34485. l3 = (sliceAngle > Math.PI / 2 ? inner : Math.abs(Math.tan(sliceAngle / 2)) * inner);
  34486. if (bbox.height + padding * 2 > Math.min(l1, l2, l3) * 2) {
  34487. return 0;
  34488. }
  34489. return 1;
  34490. }
  34491. });
  34492. /**
  34493. * @class Ext.chart.series.Pie
  34494. * @extends Ext.chart.series.Polar
  34495. *
  34496. * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display
  34497. * quantitative information for different categories that also have a meaning as a whole.
  34498. * As with all other series, the Pie Series must be appended in the *series* Chart array
  34499. * configuration. See the Chart documentation for more information. A typical configuration
  34500. * object for the pie series could be:
  34501. *
  34502. * @example
  34503. * Ext.create({
  34504. * xtype: 'polar',
  34505. * renderTo: document.body,
  34506. * width: 400,
  34507. * height: 400,
  34508. * theme: 'green',
  34509. * interactions: ['rotate', 'itemhighlight'],
  34510. * store: {
  34511. * fields: ['name', 'data1'],
  34512. * data: [{
  34513. * name: 'metric one',
  34514. * data1: 14
  34515. * }, {
  34516. * name: 'metric two',
  34517. * data1: 16
  34518. * }, {
  34519. * name: 'metric three',
  34520. * data1: 14
  34521. * }, {
  34522. * name: 'metric four',
  34523. * data1: 6
  34524. * }, {
  34525. * name: 'metric five',
  34526. * data1: 36
  34527. * }]
  34528. * },
  34529. * series: {
  34530. * type: 'pie',
  34531. * highlight: true,
  34532. * angleField: 'data1',
  34533. * label: {
  34534. * field: 'name',
  34535. * display: 'rotate'
  34536. * },
  34537. * donut: 30
  34538. * }
  34539. * });
  34540. *
  34541. * In this configuration we set `pie` as the type for the series, then set the `highlight` config
  34542. * to `true` (we can also specify an object with specific style properties for highlighting options)
  34543. * which is triggered when hovering or tapping elements.
  34544. * We set `data1` as the value of the `angleField` to determine the angular span for each pie slice.
  34545. * We also set a label configuration object where we set the name of the store field
  34546. * to be rendered as text for the label. The labels will also be displayed rotated.
  34547. * And finally, we specify the donut hole radius for the pie series in percentages of the series radius.
  34548. *
  34549. */
  34550. Ext.define('Ext.chart.series.Pie', {
  34551. extend: 'Ext.chart.series.Polar',
  34552. requires: [
  34553. 'Ext.chart.series.sprite.PieSlice'
  34554. ],
  34555. type: 'pie',
  34556. alias: 'series.pie',
  34557. seriesType: 'pieslice',
  34558. isPie: true,
  34559. config: {
  34560. /**
  34561. * @cfg {String} radiusField
  34562. * The store record field name to be used for the pie slice lengths.
  34563. * The values bound to this field name must be positive real numbers.
  34564. */
  34565. /**
  34566. * @cfg {Number} donut Specifies the radius of the donut hole, as a percentage of the chart's radius.
  34567. * Defaults to 0 (no donut hole).
  34568. */
  34569. donut: 0,
  34570. /**
  34571. * @cfg {Number} rotation The starting angle of the pie slices.
  34572. */
  34573. rotation: 0,
  34574. /**
  34575. * @cfg {Boolean} clockwise
  34576. * Whether the pie slices are displayed clockwise. Default's true.
  34577. */
  34578. clockwise: true,
  34579. /**
  34580. * @cfg {Number} [totalAngle=2*PI] The total angle of the pie series.
  34581. */
  34582. totalAngle: 2 * Math.PI,
  34583. /**
  34584. * @cfg {Array} hidden Determines which pie slices are hidden.
  34585. */
  34586. hidden: [],
  34587. /**
  34588. * @cfg {Number} [radiusFactor=100] Allows adjustment of the radius by a specific percentage.
  34589. */
  34590. radiusFactor: 100,
  34591. /**
  34592. * @cfg {Ext.chart.series.sprite.PieSlice/Object} highlightCfg
  34593. * Default highlight config for the pie series.
  34594. * Slides highlighted pie sector outward by default.
  34595. *
  34596. * highlightCfg accepts as its value a config object (or array of configs) for a
  34597. * {@link Ext.chart.series.sprite.PieSlice pie sprite}.
  34598. *
  34599. *
  34600. * Example config:
  34601. *
  34602. * Ext.create('Ext.chart.PolarChart', {
  34603. * renderTo: document.body,
  34604. * width: 600,
  34605. * height: 400,
  34606. * innerPadding: 5,
  34607. * store: {
  34608. * fields: ['name', 'data1'],
  34609. * data: [{
  34610. * name: 'metric one',
  34611. * data1: 10
  34612. * }, {
  34613. * name: 'metric two',
  34614. * data1: 7
  34615. * }, {
  34616. * name: 'metric three',
  34617. * data1: 5
  34618. * }]
  34619. * },
  34620. * series: {
  34621. * type: 'pie',
  34622. * label: {
  34623. * field: 'name',
  34624. * display: 'rotate'
  34625. * },
  34626. * xField: 'data1',
  34627. * donut: 30,
  34628. * highlightCfg: {
  34629. * margin: 10,
  34630. * fillOpacity: .7
  34631. * }
  34632. * }
  34633. * });
  34634. */
  34635. highlightCfg: {
  34636. margin: 20
  34637. },
  34638. style: {}
  34639. },
  34640. directions: [
  34641. 'X'
  34642. ],
  34643. applyLabel: function(newLabel, oldLabel) {
  34644. if (Ext.isObject(newLabel) && !Ext.isString(newLabel.orientation)) {
  34645. // Override default label orientation from '' to 'vertical'.
  34646. Ext.apply(newLabel = Ext.Object.chain(newLabel), {
  34647. orientation: 'vertical'
  34648. });
  34649. }
  34650. return this.callParent([
  34651. newLabel,
  34652. oldLabel
  34653. ]);
  34654. },
  34655. updateLabelData: function() {
  34656. var me = this,
  34657. store = me.getStore(),
  34658. items = store.getData().items,
  34659. sprites = me.getSprites(),
  34660. label = me.getLabel(),
  34661. labelField = label && label.getTemplate().getField(),
  34662. hidden = me.getHidden(),
  34663. i, ln, labels, sprite;
  34664. if (sprites.length && labelField) {
  34665. labels = [];
  34666. for (i = 0 , ln = items.length; i < ln; i++) {
  34667. labels.push(items[i].get(labelField));
  34668. }
  34669. for (i = 0 , ln = sprites.length; i < ln; i++) {
  34670. sprite = sprites[i];
  34671. sprite.setAttributes({
  34672. label: labels[i]
  34673. });
  34674. sprite.putMarker('labels', {
  34675. hidden: hidden[i]
  34676. }, sprite.attr.attributeId);
  34677. }
  34678. }
  34679. },
  34680. coordinateX: function() {
  34681. var me = this,
  34682. store = me.getStore(),
  34683. records = store.getData().items,
  34684. recordCount = records.length,
  34685. xField = me.getXField(),
  34686. yField = me.getYField(),
  34687. x,
  34688. sumX = 0,
  34689. unit, y,
  34690. maxY = 0,
  34691. hidden = me.getHidden(),
  34692. summation = [],
  34693. i,
  34694. lastAngle = 0,
  34695. totalAngle = me.getTotalAngle(),
  34696. clockwise = me.getClockwise() ? 1 : -1,
  34697. sprites = me.getSprites(),
  34698. sprite, labels;
  34699. if (!sprites) {
  34700. return;
  34701. }
  34702. for (i = 0; i < recordCount; i++) {
  34703. x = Math.abs(Number(records[i].get(xField))) || 0;
  34704. y = yField && Math.abs(Number(records[i].get(yField))) || 0;
  34705. if (!hidden[i]) {
  34706. sumX += x;
  34707. if (y > maxY) {
  34708. maxY = y;
  34709. }
  34710. }
  34711. summation[i] = sumX;
  34712. if (i >= hidden.length) {
  34713. hidden[i] = false;
  34714. }
  34715. }
  34716. hidden.length = recordCount;
  34717. me.maxY = maxY;
  34718. if (sumX !== 0) {
  34719. unit = totalAngle / sumX;
  34720. }
  34721. for (i = 0; i < recordCount; i++) {
  34722. sprites[i].setAttributes({
  34723. startAngle: lastAngle,
  34724. endAngle: lastAngle = (unit ? clockwise * summation[i] * unit : 0),
  34725. globalAlpha: 1
  34726. });
  34727. }
  34728. if (recordCount < sprites.length) {
  34729. for (i = recordCount; i < sprites.length; i++) {
  34730. sprite = sprites[i];
  34731. labels = sprite.getMarker('labels');
  34732. if (labels) {
  34733. // Don't want the 'labels' Markers and its 'template' sprite to be destroyed
  34734. // with the PieSlice MarkerHolder, as it is also used by other pie slices.
  34735. // So we release 'labels' before destroying the PieSlice.
  34736. // But first, we have to clear the instances of the 'labels'
  34737. // Markers created by the PieSlice MarkerHolder.
  34738. labels.clear(sprite.getId());
  34739. sprite.releaseMarker('labels');
  34740. }
  34741. sprite.destroy();
  34742. }
  34743. sprites.length = recordCount;
  34744. }
  34745. for (i = recordCount; i < sprites.length; i++) {
  34746. sprites[i].setAttributes({
  34747. startAngle: totalAngle,
  34748. endAngle: totalAngle,
  34749. globalAlpha: 0
  34750. });
  34751. }
  34752. },
  34753. updateCenter: function(center) {
  34754. this.setStyle({
  34755. translationX: center[0] + this.getOffsetX(),
  34756. translationY: center[1] + this.getOffsetY()
  34757. });
  34758. this.doUpdateStyles();
  34759. },
  34760. updateRadius: function(radius) {
  34761. this.setStyle({
  34762. startRho: radius * this.getDonut() * 0.01,
  34763. endRho: radius * this.getRadiusFactor() * 0.01
  34764. });
  34765. this.doUpdateStyles();
  34766. },
  34767. getStyleByIndex: function(i) {
  34768. var me = this,
  34769. store = me.getStore(),
  34770. item = store.getAt(i),
  34771. yField = me.getYField(),
  34772. radius = me.getRadius(),
  34773. style = {},
  34774. startRho, endRho, y;
  34775. if (item) {
  34776. y = yField && Math.abs(Number(item.get(yField))) || 0;
  34777. startRho = radius * me.getDonut() * 0.01;
  34778. endRho = radius * me.getRadiusFactor() * 0.01;
  34779. style = me.callParent([
  34780. i
  34781. ]);
  34782. style.startRho = startRho;
  34783. style.endRho = me.maxY ? (startRho + (endRho - startRho) * y / me.maxY) : endRho;
  34784. }
  34785. return style;
  34786. },
  34787. updateDonut: function(donut) {
  34788. var radius = this.getRadius();
  34789. this.setStyle({
  34790. startRho: radius * donut * 0.01,
  34791. endRho: radius * this.getRadiusFactor() * 0.01
  34792. });
  34793. this.doUpdateStyles();
  34794. },
  34795. // Subtract 90 degrees from rotation, so that `rotation` config's default
  34796. // value of 0 makes first pie sector start at noon, rather than 3 o'clock.
  34797. rotationOffset: -Math.PI / 2,
  34798. updateRotation: function(rotation) {
  34799. this.setStyle({
  34800. rotationRads: rotation + this.rotationOffset
  34801. });
  34802. this.doUpdateStyles();
  34803. },
  34804. updateTotalAngle: function(totalAngle) {
  34805. this.processData();
  34806. },
  34807. getSprites: function() {
  34808. var me = this,
  34809. chart = me.getChart(),
  34810. store = me.getStore();
  34811. if (!chart || !store) {
  34812. return Ext.emptyArray;
  34813. }
  34814. me.getColors();
  34815. me.getSubStyle();
  34816. var items = store.getData().items,
  34817. length = items.length,
  34818. animation = me.getAnimation() || chart && chart.getAnimation(),
  34819. sprites = me.sprites,
  34820. sprite,
  34821. spriteCreated = false,
  34822. spriteIndex = 0,
  34823. label = me.getLabel(),
  34824. labelTpl = label && label.getTemplate(),
  34825. i, rendererData;
  34826. rendererData = {
  34827. store: store,
  34828. field: me.getXField(),
  34829. // for backward compatibility only (deprecated in 5.5)
  34830. angleField: me.getXField(),
  34831. radiusField: me.getYField(),
  34832. series: me
  34833. };
  34834. for (i = 0; i < length; i++) {
  34835. sprite = sprites[i];
  34836. if (!sprite) {
  34837. sprite = me.createSprite();
  34838. if (me.getHighlight()) {
  34839. sprite.config.highlight = me.getHighlight();
  34840. sprite.addModifier('highlight', true);
  34841. }
  34842. if (labelTpl && labelTpl.getField()) {
  34843. labelTpl.setAttributes({
  34844. labelOverflowPadding: me.getLabelOverflowPadding()
  34845. });
  34846. labelTpl.getAnimation().setCustomDurations({
  34847. 'callout': 200
  34848. });
  34849. }
  34850. sprite.setAttributes(me.getStyleByIndex(i));
  34851. sprite.setRendererData(rendererData);
  34852. spriteCreated = true;
  34853. }
  34854. sprite.setRendererIndex(spriteIndex++);
  34855. sprite.setAnimation(animation);
  34856. }
  34857. if (spriteCreated) {
  34858. me.doUpdateStyles();
  34859. }
  34860. return me.sprites;
  34861. },
  34862. betweenAngle: function(x, a, b) {
  34863. var pp = Math.PI * 2,
  34864. offset = this.rotationOffset;
  34865. if (a === b) {
  34866. return false;
  34867. }
  34868. if (!this.getClockwise()) {
  34869. x *= -1;
  34870. a *= -1;
  34871. b *= -1;
  34872. a -= offset;
  34873. b -= offset;
  34874. } else {
  34875. a += offset;
  34876. b += offset;
  34877. }
  34878. x -= a;
  34879. b -= a;
  34880. // Normalize, so that both x and b are in the [0,360) interval.
  34881. x %= pp;
  34882. b %= pp;
  34883. x += pp;
  34884. b += pp;
  34885. x %= pp;
  34886. b %= pp;
  34887. // Because 360 * n angles will be normalized to 0,
  34888. // we need to treat b ~= 0 as a special case.
  34889. return x < b || Ext.Number.isEqual(b, 0, 1.0E-8);
  34890. },
  34891. getItemByIndex: function(index, category) {
  34892. category = category || 'sprites';
  34893. return this.callParent([
  34894. index,
  34895. category
  34896. ]);
  34897. },
  34898. /**
  34899. * Returns the pie slice for a given angle
  34900. * @param {Number} angle The angle to search for the slice
  34901. * @return {Object} An object containing the reocord, sprite, scope etc.
  34902. */
  34903. getItemForAngle: function(angle) {
  34904. var me = this,
  34905. sprites = me.getSprites(),
  34906. attr;
  34907. angle %= Math.PI * 2;
  34908. while (angle < 0) {
  34909. angle += Math.PI * 2;
  34910. }
  34911. if (sprites) {
  34912. var store = me.getStore(),
  34913. items = store.getData().items,
  34914. hidden = me.getHidden(),
  34915. i = 0,
  34916. ln = store.getCount();
  34917. for (; i < ln; i++) {
  34918. if (!hidden[i]) {
  34919. // Fortunately, item's id equals its index in the instances list.
  34920. attr = sprites[i].attr;
  34921. if (attr.startAngle <= angle && attr.endAngle >= angle) {
  34922. return {
  34923. series: me,
  34924. sprite: sprites[i],
  34925. index: i,
  34926. record: items[i],
  34927. field: me.getXField()
  34928. };
  34929. }
  34930. }
  34931. }
  34932. }
  34933. return null;
  34934. },
  34935. getItemForPoint: function(x, y) {
  34936. var me = this,
  34937. sprites = me.getSprites(),
  34938. center = me.getCenter(),
  34939. offsetX = me.getOffsetX(),
  34940. offsetY = me.getOffsetY(),
  34941. // Distance from the center of the series to the cursor.
  34942. dx = x - center[0] + offsetX,
  34943. dy = y - center[1] + offsetY,
  34944. store = me.getStore(),
  34945. donut = me.getDonut(),
  34946. records = store.getData().items,
  34947. direction = Math.atan2(dy, dx) - me.getRotation(),
  34948. radius = Math.sqrt(dx * dx + dy * dy),
  34949. startRadius = me.getRadius() * donut * 0.01,
  34950. hidden = me.getHidden(),
  34951. result = null,
  34952. i, ln, attr, sprite;
  34953. for (i = 0 , ln = records.length; i < ln; i++) {
  34954. if (hidden[i]) {
  34955. continue;
  34956. }
  34957. sprite = sprites[i];
  34958. if (!sprite) {
  34959. break;
  34960. }
  34961. attr = sprite.attr;
  34962. if (radius >= startRadius + attr.margin && radius <= attr.endRho + attr.margin && me.betweenAngle(direction, attr.startAngle, attr.endAngle)) {
  34963. result = {
  34964. series: me,
  34965. sprite: sprites[i],
  34966. index: i,
  34967. record: records[i],
  34968. field: me.getXField()
  34969. };
  34970. break;
  34971. }
  34972. }
  34973. return result;
  34974. },
  34975. provideLegendInfo: function(target) {
  34976. var me = this,
  34977. store = me.getStore();
  34978. if (store) {
  34979. var items = store.getData().items,
  34980. labelField = me.getLabel().getTemplate().getField(),
  34981. xField = me.getXField(),
  34982. hidden = me.getHidden(),
  34983. i, style, fill;
  34984. for (i = 0; i < items.length; i++) {
  34985. style = me.getStyleByIndex(i);
  34986. fill = style.fillStyle;
  34987. if (Ext.isObject(fill)) {
  34988. fill = fill.stops && fill.stops[0].color;
  34989. }
  34990. target.push({
  34991. name: labelField ? String(items[i].get(labelField)) : xField + ' ' + i,
  34992. mark: fill || style.strokeStyle || 'black',
  34993. disabled: hidden[i],
  34994. series: me.getId(),
  34995. index: i
  34996. });
  34997. }
  34998. }
  34999. }
  35000. });
  35001. /**
  35002. * @class Ext.chart.series.sprite.Pie3DPart
  35003. * @extends Ext.draw.sprite.Path
  35004. *
  35005. * Pie3D series sprite.
  35006. */
  35007. Ext.define('Ext.chart.series.sprite.Pie3DPart', {
  35008. extend: 'Ext.draw.sprite.Path',
  35009. mixins: {
  35010. markerHolder: 'Ext.chart.MarkerHolder'
  35011. },
  35012. alias: 'sprite.pie3dPart',
  35013. inheritableStatics: {
  35014. def: {
  35015. processors: {
  35016. /**
  35017. * @cfg {Number} [centerX=0]
  35018. * The central point of the series on the x-axis.
  35019. */
  35020. centerX: 'number',
  35021. /**
  35022. * @cfg {Number} [centerY=0]
  35023. * The central point of the series on the x-axis.
  35024. */
  35025. centerY: 'number',
  35026. /**
  35027. * @cfg {Number} [startAngle=0]
  35028. * The starting angle of the polar series.
  35029. */
  35030. startAngle: 'number',
  35031. /**
  35032. * @cfg {Number} [endAngle=Math.PI]
  35033. * The ending angle of the polar series.
  35034. */
  35035. endAngle: 'number',
  35036. /**
  35037. * @cfg {Number} [startRho=0]
  35038. * The starting radius of the polar series.
  35039. */
  35040. startRho: 'number',
  35041. /**
  35042. * @cfg {Number} [endRho=150]
  35043. * The ending radius of the polar series.
  35044. */
  35045. endRho: 'number',
  35046. /**
  35047. * @cfg {Number} [margin=0]
  35048. * Margin from the center of the pie. Used for donut.
  35049. */
  35050. margin: 'number',
  35051. /**
  35052. * @cfg {Number} [thickness=0]
  35053. * The thickness of the 3D pie part.
  35054. */
  35055. thickness: 'number',
  35056. /**
  35057. * @cfg {Number} [bevelWidth=5]
  35058. * The size of the 3D pie bevel.
  35059. */
  35060. bevelWidth: 'number',
  35061. /**
  35062. * @cfg {Number} [distortion=0]
  35063. * The distortion of the 3D pie part.
  35064. */
  35065. distortion: 'number',
  35066. /**
  35067. * @cfg {Object} [baseColor='white']
  35068. * The color of the 3D pie part before adding the 3D effect.
  35069. */
  35070. baseColor: 'color',
  35071. /**
  35072. * @cfg {Number} [colorSpread=0.7]
  35073. * An attribute used to control how flat the gradient of the sprite looks.
  35074. * A value of 0 essentially means no gradient (flat color).
  35075. */
  35076. colorSpread: 'number',
  35077. /**
  35078. * @cfg {Number} [baseRotation=0]
  35079. * The starting rotation of the polar series.
  35080. */
  35081. baseRotation: 'number',
  35082. /**
  35083. * @cfg {String} [part='top']
  35084. * The part of the 3D Pie represented by the sprite.
  35085. */
  35086. part: 'enums(top,bottom,start,end,innerFront,innerBack,outerFront,outerBack)',
  35087. /**
  35088. * @cfg {String} [label='']
  35089. * The label associated with the 'top' part of the sprite.
  35090. */
  35091. label: 'string'
  35092. },
  35093. aliases: {
  35094. rho: 'endRho'
  35095. },
  35096. triggers: {
  35097. centerX: 'path,bbox',
  35098. centerY: 'path,bbox',
  35099. startAngle: 'path,partZIndex',
  35100. endAngle: 'path,partZIndex',
  35101. startRho: 'path',
  35102. endRho: 'path,bbox',
  35103. margin: 'path,bbox',
  35104. thickness: 'path',
  35105. distortion: 'path',
  35106. baseRotation: 'path,partZIndex',
  35107. baseColor: 'partZIndex,partColor',
  35108. colorSpread: 'partColor',
  35109. part: 'path,partZIndex',
  35110. globalAlpha: 'canvas,alpha',
  35111. fillOpacity: 'canvas,alpha'
  35112. },
  35113. defaults: {
  35114. centerX: 0,
  35115. centerY: 0,
  35116. startAngle: Math.PI * 2,
  35117. endAngle: Math.PI * 2,
  35118. startRho: 0,
  35119. endRho: 150,
  35120. margin: 0,
  35121. thickness: 35,
  35122. distortion: 0.5,
  35123. baseRotation: 0,
  35124. baseColor: 'white',
  35125. colorSpread: 0.5,
  35126. miterLimit: 1,
  35127. bevelWidth: 5,
  35128. strokeOpacity: 0,
  35129. part: 'top',
  35130. label: ''
  35131. },
  35132. updaters: {
  35133. alpha: 'alphaUpdater',
  35134. partColor: 'partColorUpdater',
  35135. partZIndex: 'partZIndexUpdater'
  35136. }
  35137. }
  35138. },
  35139. config: {
  35140. renderer: null,
  35141. rendererData: null,
  35142. rendererIndex: 0,
  35143. series: null
  35144. },
  35145. bevelParams: [],
  35146. constructor: function(config) {
  35147. this.callParent([
  35148. config
  35149. ]);
  35150. this.bevelGradient = new Ext.draw.gradient.Linear({
  35151. stops: [
  35152. {
  35153. offset: 0,
  35154. color: 'rgba(255,255,255,0)'
  35155. },
  35156. {
  35157. offset: 0.7,
  35158. color: 'rgba(255,255,255,0.6)'
  35159. },
  35160. {
  35161. offset: 1,
  35162. color: 'rgba(255,255,255,0)'
  35163. }
  35164. ]
  35165. });
  35166. },
  35167. updateRenderer: function() {
  35168. this.setDirty(true);
  35169. },
  35170. updateRendererData: function() {
  35171. this.setDirty(true);
  35172. },
  35173. updateRendererIndex: function() {
  35174. this.setDirty(true);
  35175. },
  35176. alphaUpdater: function(attr) {
  35177. var me = this,
  35178. opacity = attr.globalAlpha,
  35179. fillOpacity = attr.fillOpacity,
  35180. oldOpacity = me.oldOpacity,
  35181. oldFillOpacity = me.oldFillOpacity;
  35182. // Update the path when the sprite becomes translucent or completely opaque.
  35183. if ((opacity !== oldOpacity && (opacity === 1 || oldOpacity === 1)) || (fillOpacity !== oldFillOpacity && (fillOpacity === 1 || oldFillOpacity === 1))) {
  35184. me.scheduleUpdater(attr, 'path', [
  35185. 'globalAlpha'
  35186. ]);
  35187. me.oldOpacity = opacity;
  35188. me.oldFillOpacity = fillOpacity;
  35189. }
  35190. },
  35191. partColorUpdater: function(attr) {
  35192. var color = Ext.util.Color.fly(attr.baseColor),
  35193. colorString = color.toString(),
  35194. colorSpread = attr.colorSpread,
  35195. fillStyle;
  35196. switch (attr.part) {
  35197. case 'top':
  35198. fillStyle = new Ext.draw.gradient.Radial({
  35199. start: {
  35200. x: 0,
  35201. y: 0,
  35202. r: 0
  35203. },
  35204. end: {
  35205. x: 0,
  35206. y: 0,
  35207. r: 1
  35208. },
  35209. stops: [
  35210. {
  35211. offset: 0,
  35212. color: color.createLighter(0.1 * colorSpread)
  35213. },
  35214. {
  35215. offset: 1,
  35216. color: color.createDarker(0.1 * colorSpread)
  35217. }
  35218. ]
  35219. });
  35220. break;
  35221. case 'bottom':
  35222. fillStyle = new Ext.draw.gradient.Radial({
  35223. start: {
  35224. x: 0,
  35225. y: 0,
  35226. r: 0
  35227. },
  35228. end: {
  35229. x: 0,
  35230. y: 0,
  35231. r: 1
  35232. },
  35233. stops: [
  35234. {
  35235. offset: 0,
  35236. color: color.createDarker(0.2 * colorSpread)
  35237. },
  35238. {
  35239. offset: 1,
  35240. color: color.toString()
  35241. }
  35242. ]
  35243. });
  35244. break;
  35245. case 'outerFront':
  35246. case 'outerBack':
  35247. fillStyle = new Ext.draw.gradient.Linear({
  35248. stops: [
  35249. {
  35250. offset: 0,
  35251. color: color.createDarker(0.15 * colorSpread).toString()
  35252. },
  35253. {
  35254. offset: 0.3,
  35255. color: colorString
  35256. },
  35257. {
  35258. offset: 0.8,
  35259. color: color.createLighter(0.2 * colorSpread).toString()
  35260. },
  35261. {
  35262. offset: 1,
  35263. color: color.createDarker(0.25 * colorSpread).toString()
  35264. }
  35265. ]
  35266. });
  35267. break;
  35268. case 'start':
  35269. fillStyle = new Ext.draw.gradient.Linear({
  35270. stops: [
  35271. {
  35272. offset: 0,
  35273. color: color.createDarker(0.1 * colorSpread).toString()
  35274. },
  35275. {
  35276. offset: 1,
  35277. color: color.createLighter(0.2 * colorSpread).toString()
  35278. }
  35279. ]
  35280. });
  35281. break;
  35282. case 'end':
  35283. fillStyle = new Ext.draw.gradient.Linear({
  35284. stops: [
  35285. {
  35286. offset: 0,
  35287. color: color.createDarker(0.1 * colorSpread).toString()
  35288. },
  35289. {
  35290. offset: 1,
  35291. color: color.createLighter(0.2 * colorSpread).toString()
  35292. }
  35293. ]
  35294. });
  35295. break;
  35296. case 'innerFront':
  35297. case 'innerBack':
  35298. fillStyle = new Ext.draw.gradient.Linear({
  35299. stops: [
  35300. {
  35301. offset: 0,
  35302. color: color.createDarker(0.1 * colorSpread).toString()
  35303. },
  35304. {
  35305. offset: 0.2,
  35306. color: color.createLighter(0.2 * colorSpread).toString()
  35307. },
  35308. {
  35309. offset: 0.7,
  35310. color: colorString
  35311. },
  35312. {
  35313. offset: 1,
  35314. color: color.createDarker(0.1 * colorSpread).toString()
  35315. }
  35316. ]
  35317. });
  35318. break;
  35319. }
  35320. attr.fillStyle = fillStyle;
  35321. attr.canvasAttributes.fillStyle = fillStyle;
  35322. },
  35323. partZIndexUpdater: function(attr) {
  35324. var normalize = Ext.draw.sprite.AttributeParser.angle,
  35325. rotation = attr.baseRotation,
  35326. startAngle = attr.startAngle,
  35327. endAngle = attr.endAngle,
  35328. depth;
  35329. switch (attr.part) {
  35330. case 'top':
  35331. attr.zIndex = 6;
  35332. break;
  35333. case 'outerFront':
  35334. startAngle = normalize(startAngle + rotation);
  35335. endAngle = normalize(endAngle + rotation);
  35336. if (startAngle >= 0 && endAngle < 0) {
  35337. depth = Math.sin(startAngle);
  35338. } else if (startAngle <= 0 && endAngle > 0) {
  35339. depth = Math.sin(endAngle);
  35340. } else if (startAngle >= 0 && endAngle > 0) {
  35341. if (startAngle > endAngle) {
  35342. depth = 0;
  35343. } else {
  35344. depth = Math.max(Math.sin(startAngle), Math.sin(endAngle));
  35345. }
  35346. } else {
  35347. depth = 1;
  35348. };
  35349. attr.zIndex = 4 + depth;
  35350. break;
  35351. case 'outerBack':
  35352. attr.zIndex = 1;
  35353. break;
  35354. case 'start':
  35355. attr.zIndex = 4 + Math.sin(normalize(startAngle + rotation));
  35356. break;
  35357. case 'end':
  35358. attr.zIndex = 4 + Math.sin(normalize(endAngle + rotation));
  35359. break;
  35360. case 'innerFront':
  35361. attr.zIndex = 2;
  35362. break;
  35363. case 'innerBack':
  35364. attr.zIndex = 4 + Math.sin(normalize((startAngle + endAngle) / 2 + rotation));
  35365. break;
  35366. case 'bottom':
  35367. attr.zIndex = 0;
  35368. break;
  35369. }
  35370. attr.dirtyZIndex = true;
  35371. },
  35372. updatePlainBBox: function(plain) {
  35373. var attr = this.attr,
  35374. part = attr.part,
  35375. baseRotation = attr.baseRotation,
  35376. centerX = attr.centerX,
  35377. centerY = attr.centerY,
  35378. rho, angle, x, y, sin, cos;
  35379. if (part === 'start') {
  35380. angle = attr.startAngle + baseRotation;
  35381. } else if (part === 'end') {
  35382. angle = attr.endAngle + baseRotation;
  35383. }
  35384. if (Ext.isNumber(angle)) {
  35385. sin = Math.sin(angle);
  35386. cos = Math.cos(angle);
  35387. x = Math.min(centerX + cos * attr.startRho, centerX + cos * attr.endRho);
  35388. y = centerY + sin * attr.startRho * attr.distortion;
  35389. plain.x = x;
  35390. plain.y = y;
  35391. plain.width = cos * (attr.endRho - attr.startRho);
  35392. plain.height = attr.thickness + sin * (attr.endRho - attr.startRho) * 2;
  35393. return;
  35394. }
  35395. if (part === 'innerFront' || part === 'innerBack') {
  35396. rho = attr.startRho;
  35397. } else {
  35398. rho = attr.endRho;
  35399. }
  35400. plain.width = rho * 2;
  35401. plain.height = rho * attr.distortion * 2 + attr.thickness;
  35402. plain.x = attr.centerX - rho;
  35403. plain.y = attr.centerY - rho * attr.distortion;
  35404. },
  35405. updateTransformedBBox: function(transform) {
  35406. if (this.attr.part === 'start' || this.attr.part === 'end') {
  35407. return this.callParent(arguments);
  35408. }
  35409. return this.updatePlainBBox(transform);
  35410. },
  35411. updatePath: function(path) {
  35412. if (!this.attr.globalAlpha) {
  35413. return;
  35414. }
  35415. if (this.attr.endAngle < this.attr.startAngle) {
  35416. return;
  35417. }
  35418. this[this.attr.part + 'Renderer'](path);
  35419. },
  35420. render: function(surface, ctx, rect) {
  35421. var me = this,
  35422. renderer = me.getRenderer(),
  35423. attr = me.attr,
  35424. part = attr.part,
  35425. itemCfg, changes;
  35426. if (!attr.globalAlpha || Ext.Number.isEqual(attr.startAngle, attr.endAngle, 1.0E-8)) {
  35427. return;
  35428. }
  35429. if (renderer) {
  35430. itemCfg = {
  35431. type: 'pie3dPart',
  35432. part: attr.part,
  35433. margin: attr.margin,
  35434. distortion: attr.distortion,
  35435. centerX: attr.centerX,
  35436. centerY: attr.centerY,
  35437. baseRotation: attr.baseRotation,
  35438. startAngle: attr.startAngle,
  35439. endAngle: attr.endAngle,
  35440. startRho: attr.startRho,
  35441. endRho: attr.endRho
  35442. };
  35443. changes = Ext.callback(renderer, null, [
  35444. me,
  35445. itemCfg,
  35446. me.getRendererData(),
  35447. me.getRendererIndex()
  35448. ], 0, me.getSeries());
  35449. if (changes) {
  35450. if (changes.part) {
  35451. // Can't let users change the nature of the sprite.
  35452. changes.part = part;
  35453. }
  35454. me.setAttributes(changes);
  35455. me.useAttributes(ctx, rect);
  35456. }
  35457. }
  35458. me.callParent([
  35459. surface,
  35460. ctx
  35461. ]);
  35462. me.bevelRenderer(surface, ctx);
  35463. // Only the top part will have the label attribute (set by the series).
  35464. if (attr.label && me.getMarker('labels')) {
  35465. me.placeLabel();
  35466. }
  35467. },
  35468. placeLabel: function() {
  35469. var me = this,
  35470. attr = me.attr,
  35471. attributeId = attr.attributeId,
  35472. margin = attr.margin,
  35473. distortion = attr.distortion,
  35474. centerX = attr.centerX,
  35475. centerY = attr.centerY,
  35476. baseRotation = attr.baseRotation,
  35477. startAngle = attr.startAngle + baseRotation,
  35478. endAngle = attr.endAngle + baseRotation,
  35479. midAngle = (startAngle + endAngle) / 2,
  35480. startRho = attr.startRho + margin,
  35481. endRho = attr.endRho + margin,
  35482. midRho = (startRho + endRho) / 2,
  35483. sin = Math.sin(midAngle),
  35484. cos = Math.cos(midAngle),
  35485. surfaceMatrix = me.surfaceMatrix,
  35486. label = me.getMarker('labels'),
  35487. labelTpl = label.getTemplate(),
  35488. calloutLine = labelTpl.getCalloutLine(),
  35489. calloutLineLength = calloutLine && calloutLine.length || 40,
  35490. labelCfg = {},
  35491. rendererParams, rendererChanges, x, y;
  35492. surfaceMatrix.appendMatrix(attr.matrix);
  35493. labelCfg.text = attr.label;
  35494. x = centerX + cos * midRho;
  35495. y = centerY + sin * midRho * distortion;
  35496. labelCfg.x = surfaceMatrix.x(x, y);
  35497. labelCfg.y = surfaceMatrix.y(x, y);
  35498. x = centerX + cos * endRho;
  35499. y = centerY + sin * endRho * distortion;
  35500. labelCfg.calloutStartX = surfaceMatrix.x(x, y);
  35501. labelCfg.calloutStartY = surfaceMatrix.y(x, y);
  35502. x = centerX + cos * (endRho + calloutLineLength);
  35503. y = centerY + sin * (endRho + calloutLineLength) * distortion;
  35504. labelCfg.calloutPlaceX = surfaceMatrix.x(x, y);
  35505. labelCfg.calloutPlaceY = surfaceMatrix.y(x, y);
  35506. labelCfg.calloutWidth = 2;
  35507. if (labelTpl.attr.renderer) {
  35508. rendererParams = [
  35509. me.attr.label,
  35510. label,
  35511. labelCfg,
  35512. me.getRendererData(),
  35513. me.getRendererIndex()
  35514. ];
  35515. rendererChanges = Ext.callback(labelTpl.attr.renderer, null, rendererParams, 0, me.getSeries());
  35516. if (typeof rendererChanges === 'string') {
  35517. labelCfg.text = rendererChanges;
  35518. } else {
  35519. Ext.apply(labelCfg, rendererChanges);
  35520. }
  35521. }
  35522. me.putMarker('labels', labelCfg, attributeId);
  35523. me.putMarker('labels', {
  35524. callout: 1
  35525. }, attributeId);
  35526. },
  35527. bevelRenderer: function(surface, ctx) {
  35528. var me = this,
  35529. attr = me.attr,
  35530. bevelWidth = attr.bevelWidth,
  35531. params = me.bevelParams,
  35532. i;
  35533. for (i = 0; i < params.length; i++) {
  35534. ctx.beginPath();
  35535. ctx.ellipse.apply(ctx, params[i]);
  35536. ctx.save();
  35537. ctx.lineWidth = bevelWidth;
  35538. ctx.strokeOpacity = bevelWidth ? 1 : 0;
  35539. ctx.strokeGradient = me.bevelGradient;
  35540. ctx.stroke(attr);
  35541. ctx.restore();
  35542. }
  35543. },
  35544. lidRenderer: function(path, thickness) {
  35545. var attr = this.attr,
  35546. margin = attr.margin,
  35547. distortion = attr.distortion,
  35548. centerX = attr.centerX,
  35549. centerY = attr.centerY,
  35550. baseRotation = attr.baseRotation,
  35551. startAngle = attr.startAngle + baseRotation,
  35552. endAngle = attr.endAngle + baseRotation,
  35553. midAngle = (startAngle + endAngle) / 2,
  35554. startRho = attr.startRho,
  35555. endRho = attr.endRho,
  35556. sinEnd = Math.sin(endAngle),
  35557. cosEnd = Math.cos(endAngle);
  35558. centerX += Math.cos(midAngle) * margin;
  35559. centerY += Math.sin(midAngle) * margin * distortion;
  35560. path.ellipse(centerX, centerY + thickness, startRho, startRho * distortion, 0, startAngle, endAngle, false);
  35561. path.lineTo(centerX + cosEnd * endRho, centerY + thickness + sinEnd * endRho * distortion);
  35562. path.ellipse(centerX, centerY + thickness, endRho, endRho * distortion, 0, endAngle, startAngle, true);
  35563. path.closePath();
  35564. },
  35565. topRenderer: function(path) {
  35566. this.lidRenderer(path, 0);
  35567. },
  35568. bottomRenderer: function(path) {
  35569. var attr = this.attr,
  35570. none = Ext.util.Color.RGBA_NONE;
  35571. if (attr.globalAlpha < 1 || attr.fillOpacity < 1 || attr.shadowColor !== none) {
  35572. this.lidRenderer(path, attr.thickness);
  35573. }
  35574. },
  35575. sideRenderer: function(path, position) {
  35576. var attr = this.attr,
  35577. margin = attr.margin,
  35578. centerX = attr.centerX,
  35579. centerY = attr.centerY,
  35580. distortion = attr.distortion,
  35581. baseRotation = attr.baseRotation,
  35582. startAngle = attr.startAngle + baseRotation,
  35583. endAngle = attr.endAngle + baseRotation,
  35584. isFullPie = (!attr.startAngle && Ext.Number.isEqual(Math.PI * 2, attr.endAngle, 1.0E-7)),
  35585. thickness = attr.thickness,
  35586. startRho = attr.startRho,
  35587. endRho = attr.endRho,
  35588. angle = (position === 'start' && startAngle) || (position === 'end' && endAngle),
  35589. sin = Math.sin(angle),
  35590. cos = Math.cos(angle),
  35591. isTranslucent = attr.globalAlpha < 1,
  35592. isVisible = position === 'start' && cos < 0 || position === 'end' && cos > 0 || isTranslucent,
  35593. midAngle;
  35594. if (isVisible && !isFullPie) {
  35595. midAngle = (startAngle + endAngle) / 2;
  35596. centerX += Math.cos(midAngle) * margin;
  35597. centerY += Math.sin(midAngle) * margin * distortion;
  35598. path.moveTo(centerX + cos * startRho, centerY + sin * startRho * distortion);
  35599. path.lineTo(centerX + cos * endRho, centerY + sin * endRho * distortion);
  35600. path.lineTo(centerX + cos * endRho, centerY + sin * endRho * distortion + thickness);
  35601. path.lineTo(centerX + cos * startRho, centerY + sin * startRho * distortion + thickness);
  35602. path.closePath();
  35603. }
  35604. },
  35605. startRenderer: function(path) {
  35606. this.sideRenderer(path, 'start');
  35607. },
  35608. endRenderer: function(path) {
  35609. this.sideRenderer(path, 'end');
  35610. },
  35611. rimRenderer: function(path, radius, isDonut, isFront) {
  35612. var me = this,
  35613. attr = me.attr,
  35614. margin = attr.margin,
  35615. centerX = attr.centerX,
  35616. centerY = attr.centerY,
  35617. distortion = attr.distortion,
  35618. baseRotation = attr.baseRotation,
  35619. normalize = Ext.draw.sprite.AttributeParser.angle,
  35620. startAngle = attr.startAngle + baseRotation,
  35621. endAngle = attr.endAngle + baseRotation,
  35622. // It's critical to use non-normalized start and end angles
  35623. // for middle angle calculation. Consider a situation where the
  35624. // start angle is +170 degrees and the end engle is -170 degrees
  35625. // after normalization (the middle angle is 0 then, but it should be 180 degrees).
  35626. midAngle = normalize((startAngle + endAngle) / 2),
  35627. thickness = attr.thickness,
  35628. isTranslucent = attr.globalAlpha < 1,
  35629. isAllFront, isAllBack, params;
  35630. me.bevelParams = [];
  35631. startAngle = normalize(startAngle);
  35632. endAngle = normalize(endAngle);
  35633. centerX += Math.cos(midAngle) * margin;
  35634. centerY += Math.sin(midAngle) * margin * distortion;
  35635. isAllFront = startAngle >= 0 && endAngle >= 0;
  35636. isAllBack = startAngle <= 0 && endAngle <= 0;
  35637. function renderLeftFrontChunk() {
  35638. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, Math.PI, startAngle, true);
  35639. path.lineTo(centerX + Math.cos(startAngle) * radius, centerY + Math.sin(startAngle) * radius * distortion);
  35640. params = [
  35641. centerX,
  35642. centerY,
  35643. radius,
  35644. radius * distortion,
  35645. 0,
  35646. startAngle,
  35647. Math.PI,
  35648. false
  35649. ];
  35650. if (!isDonut) {
  35651. me.bevelParams.push(params);
  35652. }
  35653. path.ellipse.apply(path, params);
  35654. path.closePath();
  35655. }
  35656. function renderRightFrontChunk() {
  35657. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, 0, endAngle, false);
  35658. path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion);
  35659. params = [
  35660. centerX,
  35661. centerY,
  35662. radius,
  35663. radius * distortion,
  35664. 0,
  35665. endAngle,
  35666. 0,
  35667. true
  35668. ];
  35669. if (!isDonut) {
  35670. me.bevelParams.push(params);
  35671. }
  35672. path.ellipse.apply(path, params);
  35673. path.closePath();
  35674. }
  35675. function renderLeftBackChunk() {
  35676. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, Math.PI, endAngle, false);
  35677. path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion);
  35678. params = [
  35679. centerX,
  35680. centerY,
  35681. radius,
  35682. radius * distortion,
  35683. 0,
  35684. endAngle,
  35685. Math.PI,
  35686. true
  35687. ];
  35688. if (isDonut) {
  35689. me.bevelParams.push(params);
  35690. }
  35691. path.ellipse.apply(path, params);
  35692. path.closePath();
  35693. }
  35694. function renderRightBackChunk() {
  35695. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, startAngle, 0, false);
  35696. path.lineTo(centerX + radius, centerY);
  35697. params = [
  35698. centerX,
  35699. centerY,
  35700. radius,
  35701. radius * distortion,
  35702. 0,
  35703. 0,
  35704. startAngle,
  35705. true
  35706. ];
  35707. if (isDonut) {
  35708. me.bevelParams.push(params);
  35709. }
  35710. path.ellipse.apply(path, params);
  35711. path.closePath();
  35712. }
  35713. if (isFront) {
  35714. if (!isDonut || isTranslucent) {
  35715. if (startAngle >= 0 && endAngle < 0) {
  35716. renderLeftFrontChunk();
  35717. } else if (startAngle <= 0 && endAngle > 0) {
  35718. renderRightFrontChunk();
  35719. } else if (startAngle <= 0 && endAngle < 0) {
  35720. if (startAngle > endAngle) {
  35721. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, 0, Math.PI, false);
  35722. path.lineTo(centerX - radius, centerY);
  35723. params = [
  35724. centerX,
  35725. centerY,
  35726. radius,
  35727. radius * distortion,
  35728. 0,
  35729. Math.PI,
  35730. 0,
  35731. true
  35732. ];
  35733. if (!isDonut) {
  35734. me.bevelParams.push(params);
  35735. }
  35736. path.ellipse.apply(path, params);
  35737. path.closePath();
  35738. }
  35739. } else {
  35740. // startAngle >= 0 && endAngle > 0
  35741. // obtuse horseshoe-like slice with the gap facing forward
  35742. if (startAngle > endAngle) {
  35743. renderLeftFrontChunk();
  35744. renderRightFrontChunk();
  35745. } else {
  35746. // acute slice facing forward
  35747. params = [
  35748. centerX,
  35749. centerY,
  35750. radius,
  35751. radius * distortion,
  35752. 0,
  35753. startAngle,
  35754. endAngle,
  35755. false
  35756. ];
  35757. if (isAllFront && !isDonut || isAllBack && isDonut) {
  35758. me.bevelParams.push(params);
  35759. }
  35760. path.ellipse.apply(path, params);
  35761. path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion + thickness);
  35762. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, endAngle, startAngle, true);
  35763. path.closePath();
  35764. }
  35765. }
  35766. }
  35767. } else {
  35768. if (isDonut || isTranslucent) {
  35769. if (startAngle >= 0 && endAngle < 0) {
  35770. renderLeftBackChunk();
  35771. } else if (startAngle <= 0 && endAngle > 0) {
  35772. renderRightBackChunk();
  35773. } else if (startAngle <= 0 && endAngle < 0) {
  35774. if (startAngle > endAngle) {
  35775. renderLeftBackChunk();
  35776. renderRightBackChunk();
  35777. } else {
  35778. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, startAngle, endAngle, false);
  35779. path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion);
  35780. params = [
  35781. centerX,
  35782. centerY,
  35783. radius,
  35784. radius * distortion,
  35785. 0,
  35786. endAngle,
  35787. startAngle,
  35788. true
  35789. ];
  35790. if (isDonut) {
  35791. me.bevelParams.push(params);
  35792. }
  35793. path.ellipse.apply(path, params);
  35794. path.closePath();
  35795. }
  35796. } else {
  35797. // startAngle >= 0 && endAngle > 0
  35798. if (startAngle > endAngle) {
  35799. path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, -Math.PI, 0, false);
  35800. path.lineTo(centerX + radius, centerY);
  35801. params = [
  35802. centerX,
  35803. centerY,
  35804. radius,
  35805. radius * distortion,
  35806. 0,
  35807. 0,
  35808. -Math.PI,
  35809. true
  35810. ];
  35811. if (isDonut) {
  35812. me.bevelParams.push(params);
  35813. }
  35814. path.ellipse.apply(path, params);
  35815. path.closePath();
  35816. }
  35817. }
  35818. }
  35819. }
  35820. },
  35821. innerFrontRenderer: function(path) {
  35822. this.rimRenderer(path, this.attr.startRho, true, true);
  35823. },
  35824. innerBackRenderer: function(path) {
  35825. this.rimRenderer(path, this.attr.startRho, true, false);
  35826. },
  35827. outerFrontRenderer: function(path) {
  35828. this.rimRenderer(path, this.attr.endRho, false, true);
  35829. },
  35830. outerBackRenderer: function(path) {
  35831. this.rimRenderer(path, this.attr.endRho, false, false);
  35832. }
  35833. });
  35834. /**
  35835. * @class Ext.chart.series.Pie3D
  35836. * @extends Ext.chart.series.Polar
  35837. *
  35838. * Creates a 3D Pie Chart.
  35839. *
  35840. * **Note:** Labels, legends, and lines are not currently available when using the
  35841. * 3D Pie chart series.
  35842. *
  35843. * @example
  35844. * Ext.create({
  35845. * xtype: 'polar',
  35846. * renderTo: document.body,
  35847. * width: 600,
  35848. * height: 400,
  35849. * theme: 'green',
  35850. * interactions: 'rotate',
  35851. * store: {
  35852. * fields: ['data3'],
  35853. * data: [{
  35854. * 'data3': 14
  35855. * }, {
  35856. * 'data3': 16
  35857. * }, {
  35858. * 'data3': 14
  35859. * }, {
  35860. * 'data3': 6
  35861. * }, {
  35862. * 'data3': 36
  35863. * }]
  35864. * },
  35865. * series: {
  35866. * type: 'pie3d',
  35867. * angleField: 'data3',
  35868. * donut: 30
  35869. * }
  35870. * });
  35871. */
  35872. Ext.define('Ext.chart.series.Pie3D', {
  35873. extend: 'Ext.chart.series.Polar',
  35874. requires: [
  35875. 'Ext.chart.series.sprite.Pie3DPart',
  35876. 'Ext.draw.PathUtil'
  35877. ],
  35878. type: 'pie3d',
  35879. seriesType: 'pie3d',
  35880. alias: 'series.pie3d',
  35881. is3D: true,
  35882. config: {
  35883. rect: [
  35884. 0,
  35885. 0,
  35886. 0,
  35887. 0
  35888. ],
  35889. thickness: 35,
  35890. distortion: 0.5,
  35891. /**
  35892. * @cfg {String} angleField (required)
  35893. * The store record field name to be used for the pie angles.
  35894. * The values bound to this field name must be positive real numbers.
  35895. */
  35896. /**
  35897. * @private
  35898. * @cfg {String} radiusField
  35899. * Not supported.
  35900. */
  35901. /**
  35902. * @cfg {Number} donut Specifies the radius of the donut hole, as a percentage of the chart's radius.
  35903. * Defaults to 0 (no donut hole).
  35904. */
  35905. donut: 0,
  35906. /**
  35907. * @cfg {Array} hidden Determines which pie slices are hidden.
  35908. */
  35909. hidden: [],
  35910. // Populated by the coordinateX method.
  35911. /**
  35912. * @cfg {Object} highlightCfg Default {@link #highlight} config for the 3D pie series.
  35913. * Slides highlighted pie sector outward.
  35914. */
  35915. highlightCfg: {
  35916. margin: 20
  35917. },
  35918. /**
  35919. * @cfg {Number} [rotation=0] The starting angle of the pie slices.
  35920. */
  35921. /**
  35922. * @private
  35923. * @cfg {Boolean/Object} [shadow=false]
  35924. */
  35925. shadow: false
  35926. },
  35927. // Subtract 90 degrees from rotation, so that `rotation` config's default
  35928. // zero value makes first pie sector start at noon, rather than 3 o'clock.
  35929. rotationOffset: -Math.PI / 2,
  35930. setField: function(value) {
  35931. return this.setXField(value);
  35932. },
  35933. getField: function() {
  35934. return this.getXField();
  35935. },
  35936. updateRotation: function(rotation) {
  35937. var attributes = {
  35938. baseRotation: rotation + this.rotationOffset
  35939. };
  35940. this.forEachSprite(function(sprite) {
  35941. sprite.setAttributes(attributes);
  35942. });
  35943. },
  35944. updateColors: function(colors) {
  35945. this.setSubStyle({
  35946. baseColor: colors
  35947. });
  35948. if (!this.isConfiguring) {
  35949. var chart = this.getChart();
  35950. if (chart) {
  35951. chart.refreshLegendStore();
  35952. }
  35953. }
  35954. },
  35955. applyShadow: function(shadow) {
  35956. if (shadow === true) {
  35957. shadow = {
  35958. shadowColor: 'rgba(0,0,0,0.8)',
  35959. shadowBlur: 30
  35960. };
  35961. } else if (!Ext.isObject(shadow)) {
  35962. shadow = {
  35963. shadowColor: Ext.util.Color.RGBA_NONE
  35964. };
  35965. }
  35966. return shadow;
  35967. },
  35968. updateShadow: function(shadow) {
  35969. var me = this,
  35970. sprites = me.getSprites(),
  35971. spritesPerSlice = me.spritesPerSlice,
  35972. ln = sprites && sprites.length,
  35973. i, sprite;
  35974. for (i = 1; i < ln; i += spritesPerSlice) {
  35975. sprite = sprites[i];
  35976. if (sprite.attr.part = 'bottom') {
  35977. sprite.setAttributes(shadow);
  35978. }
  35979. }
  35980. },
  35981. // This is a temporary solution until the Series.getStyleByIndex is fixed
  35982. // to give user styles the priority over theme ones. Also, for sprites of
  35983. // this particular series, the fillStyle shouldn't be set directly. Instead,
  35984. // the 'baseColor' attribute should be set, from which the stops of the
  35985. // gradient (used for fillStyle) will be calculated. Themes can't handle
  35986. // situations like that properly.
  35987. getStyleByIndex: function(i) {
  35988. var indexStyle = this.callParent([
  35989. i
  35990. ]),
  35991. style = this.getStyle(),
  35992. // 'fill' and 'color' are 'fillStyle' aliases
  35993. // (see Ext.draw.sprite.Sprite.inheritableStatics.def.aliases)
  35994. fillStyle = indexStyle.fillStyle || indexStyle.fill || indexStyle.color,
  35995. strokeStyle = style.strokeStyle || style.stroke;
  35996. if (fillStyle) {
  35997. indexStyle.baseColor = fillStyle;
  35998. delete indexStyle.fillStyle;
  35999. delete indexStyle.fill;
  36000. delete indexStyle.color;
  36001. }
  36002. if (strokeStyle) {
  36003. indexStyle.strokeStyle = strokeStyle;
  36004. }
  36005. return indexStyle;
  36006. },
  36007. doUpdateStyles: function() {
  36008. var me = this,
  36009. sprites = me.getSprites(),
  36010. spritesPerSlice = me.spritesPerSlice,
  36011. ln = sprites && sprites.length,
  36012. i = 0,
  36013. j = 0,
  36014. k, style;
  36015. for (; i < ln; i += spritesPerSlice , j++) {
  36016. style = me.getStyleByIndex(j);
  36017. for (k = 0; k < spritesPerSlice; k++) {
  36018. sprites[i + k].setAttributes(style);
  36019. }
  36020. }
  36021. },
  36022. coordinateX: function() {
  36023. var me = this,
  36024. store = me.getStore(),
  36025. records = store.getData().items,
  36026. recordCount = records.length,
  36027. xField = me.getXField(),
  36028. animation = me.getAnimation(),
  36029. rotation = me.getRotation(),
  36030. hidden = me.getHidden(),
  36031. sprites = me.getSprites(true),
  36032. spriteCount = sprites.length,
  36033. spritesPerSlice = me.spritesPerSlice,
  36034. center = me.getCenter(),
  36035. offsetX = me.getOffsetX(),
  36036. offsetY = me.getOffsetY(),
  36037. radius = me.getRadius(),
  36038. thickness = me.getThickness(),
  36039. distortion = me.getDistortion(),
  36040. renderer = me.getRenderer(),
  36041. rendererData = me.getRendererData(),
  36042. highlight = me.getHighlight(),
  36043. lastAngle = 0,
  36044. twoPi = Math.PI * 2,
  36045. // To avoid adjacent start/end part blinking (z-index jitter)
  36046. // when rotating a translucent pie chart.
  36047. delta = 1.0E-10,
  36048. endAngles = [],
  36049. sum = 0,
  36050. value, unit, sprite, style, i, j;
  36051. for (i = 0; i < recordCount; i++) {
  36052. value = Math.abs(+records[i].get(xField)) || 0;
  36053. if (!hidden[i]) {
  36054. sum += value;
  36055. }
  36056. endAngles[i] = sum;
  36057. if (i >= hidden.length) {
  36058. hidden[i] = false;
  36059. }
  36060. }
  36061. if (sum === 0) {
  36062. return;
  36063. }
  36064. // Angular value of 1 in radians.
  36065. unit = 2 * Math.PI / sum;
  36066. for (i = 0; i < recordCount; i++) {
  36067. endAngles[i] *= unit;
  36068. }
  36069. for (i = 0; i < recordCount; i++) {
  36070. style = this.getStyleByIndex(i);
  36071. for (j = 0; j < spritesPerSlice; j++) {
  36072. sprite = sprites[i * spritesPerSlice + j];
  36073. sprite.setAnimation(animation);
  36074. sprite.setAttributes({
  36075. centerX: center[0] + offsetX,
  36076. centerY: center[1] + offsetY - thickness / 2,
  36077. endRho: radius,
  36078. startRho: radius * me.getDonut() / 100,
  36079. baseRotation: rotation + me.rotationOffset,
  36080. startAngle: lastAngle,
  36081. endAngle: endAngles[i] - delta,
  36082. thickness: thickness,
  36083. distortion: distortion,
  36084. globalAlpha: 1
  36085. });
  36086. sprite.setAttributes(style);
  36087. sprite.setConfig({
  36088. renderer: renderer,
  36089. rendererData: rendererData,
  36090. rendererIndex: i
  36091. });
  36092. }
  36093. // if (highlight) {
  36094. // if (!sprite.modifiers.highlight) {
  36095. // debugger
  36096. // sprite.addModifier(highlight, true);
  36097. // }
  36098. // // sprite.modifiers.highlight.setConfig(highlight);
  36099. // }
  36100. lastAngle = endAngles[i];
  36101. }
  36102. for (i *= spritesPerSlice; i < spriteCount; i++) {
  36103. sprite = sprites[i];
  36104. sprite.setAnimation(animation);
  36105. sprite.setAttributes({
  36106. startAngle: twoPi,
  36107. endAngle: twoPi,
  36108. globalAlpha: 0,
  36109. baseRotation: rotation + me.rotationOffset
  36110. });
  36111. }
  36112. },
  36113. updateHighlight: function(highlight, oldHighlight) {
  36114. this.callParent([
  36115. highlight,
  36116. oldHighlight
  36117. ]);
  36118. this.forEachSprite(function(sprite) {
  36119. if (highlight) {
  36120. if (sprite.modifiers.highlight) {
  36121. sprite.modifiers.highlight.setConfig(highlight);
  36122. } else {
  36123. sprite.config.highlight = highlight;
  36124. sprite.addModifier(highlight, true);
  36125. }
  36126. }
  36127. });
  36128. },
  36129. updateLabelData: function() {
  36130. var me = this,
  36131. store = me.getStore(),
  36132. items = store.getData().items,
  36133. sprites = me.getSprites(),
  36134. label = me.getLabel(),
  36135. labelField = label && label.getTemplate().getField(),
  36136. hidden = me.getHidden(),
  36137. spritesPerSlice = me.spritesPerSlice,
  36138. ln, labels, sprite,
  36139. name = 'labels',
  36140. i, // sprite index
  36141. j;
  36142. // record index
  36143. if (sprites.length) {
  36144. if (labelField) {
  36145. labels = [];
  36146. for (j = 0 , ln = items.length; j < ln; j++) {
  36147. labels.push(items[j].get(labelField));
  36148. }
  36149. }
  36150. // Only set labels for the sprites that compose the top lid of the pie.
  36151. for (i = 0 , j = 0 , ln = sprites.length; i < ln; i += spritesPerSlice , j++) {
  36152. sprite = sprites[i];
  36153. if (label) {
  36154. if (!sprite.getMarker(name)) {
  36155. sprite.bindMarker(name, label);
  36156. }
  36157. if (labels) {
  36158. sprite.setAttributes({
  36159. label: labels[j]
  36160. });
  36161. }
  36162. sprite.putMarker(name, {
  36163. hidden: hidden[j]
  36164. }, sprite.attr.attributeId);
  36165. } else {
  36166. sprite.releaseMarker(name);
  36167. }
  36168. }
  36169. }
  36170. },
  36171. // The radius here will normally be set by the PolarChart.performLayout,
  36172. // where it's half the width or height (whichever is smaller) of the chart's rect.
  36173. // But for 3D pie series we have to take the thickness of the pie and the
  36174. // distortion into account to calculate the proper radius.
  36175. // The passed value is never used (or derived from) since the radius config
  36176. // is not really meant to be used directly, as it will be reset by the next layout.
  36177. applyRadius: function() {
  36178. var me = this,
  36179. chart = me.getChart(),
  36180. padding = chart.getInnerPadding(),
  36181. rect = chart.getMainRect() || [
  36182. 0,
  36183. 0,
  36184. 1,
  36185. 1
  36186. ],
  36187. width = rect[2] - padding * 2,
  36188. height = rect[3] - padding * 2 - me.getThickness(),
  36189. horizontalRadius = width / 2,
  36190. verticalRadius = horizontalRadius * me.getDistortion(),
  36191. result;
  36192. if (verticalRadius > height / 2) {
  36193. result = height / (me.getDistortion() * 2);
  36194. } else {
  36195. result = horizontalRadius;
  36196. }
  36197. return Math.max(result, 0);
  36198. },
  36199. forEachSprite: function(fn) {
  36200. var sprites = this.sprites,
  36201. ln = sprites.length,
  36202. i;
  36203. for (i = 0; i < ln; i++) {
  36204. fn(sprites[i], Math.floor(i / this.spritesPerSlice));
  36205. }
  36206. },
  36207. updateRadius: function(radius) {
  36208. // The side effects of the 'getChart' call will result
  36209. // in the 'coordinateX' method call, which we want to have called
  36210. // first, to coordinate the data and create sprites for pie slices,
  36211. // before we set their attributes here.
  36212. // updateChart -> onChartAttached -> processData -> coordinateX
  36213. this.getChart();
  36214. var donut = this.getDonut();
  36215. this.forEachSprite(function(sprite) {
  36216. sprite.setAttributes({
  36217. endRho: radius,
  36218. startRho: radius * donut / 100
  36219. });
  36220. });
  36221. },
  36222. updateDonut: function(donut) {
  36223. // See 'updateRadius' comments.
  36224. this.getChart();
  36225. var radius = this.getRadius();
  36226. this.forEachSprite(function(sprite) {
  36227. sprite.setAttributes({
  36228. startRho: radius * donut / 100
  36229. });
  36230. });
  36231. },
  36232. updateCenter: function(center) {
  36233. // See 'updateRadius' comments.
  36234. this.getChart();
  36235. var offsetX = this.getOffsetX(),
  36236. offsetY = this.getOffsetY(),
  36237. thickness = this.getThickness();
  36238. this.forEachSprite(function(sprite) {
  36239. sprite.setAttributes({
  36240. centerX: center[0] + offsetX,
  36241. centerY: center[1] + offsetY - thickness / 2
  36242. });
  36243. });
  36244. },
  36245. updateThickness: function(thickness) {
  36246. // See 'updateRadius' comments.
  36247. this.getChart();
  36248. // Radius depends on thickness and distortion,
  36249. // this will trigger its recalculation in the applier.
  36250. this.setRadius();
  36251. var center = this.getCenter(),
  36252. offsetY = this.getOffsetY();
  36253. this.forEachSprite(function(sprite) {
  36254. sprite.setAttributes({
  36255. thickness: thickness,
  36256. centerY: center[1] + offsetY - thickness / 2
  36257. });
  36258. });
  36259. },
  36260. updateDistortion: function(distortion) {
  36261. // See 'updateRadius' comments.
  36262. this.getChart();
  36263. // Radius depends on thickness and distortion,
  36264. // this will trigger its recalculation in the applier.
  36265. this.setRadius();
  36266. this.forEachSprite(function(sprite) {
  36267. sprite.setAttributes({
  36268. distortion: distortion
  36269. });
  36270. });
  36271. },
  36272. updateOffsetX: function(offsetX) {
  36273. // See 'updateRadius' comments.
  36274. this.getChart();
  36275. var center = this.getCenter();
  36276. this.forEachSprite(function(sprite) {
  36277. sprite.setAttributes({
  36278. centerX: center[0] + offsetX
  36279. });
  36280. });
  36281. },
  36282. updateOffsetY: function(offsetY) {
  36283. // See 'updateRadius' comments.
  36284. this.getChart();
  36285. var center = this.getCenter(),
  36286. thickness = this.getThickness();
  36287. this.forEachSprite(function(sprite) {
  36288. sprite.setAttributes({
  36289. centerY: center[1] + offsetY - thickness / 2
  36290. });
  36291. });
  36292. },
  36293. updateAnimation: function(animation) {
  36294. // See 'updateRadius' comments.
  36295. this.getChart();
  36296. this.forEachSprite(function(sprite) {
  36297. sprite.setAnimation(animation);
  36298. });
  36299. },
  36300. updateRenderer: function(renderer) {
  36301. // See 'updateRadius' comments.
  36302. this.getChart();
  36303. var rendererData = this.getRendererData();
  36304. this.forEachSprite(function(sprite, itemIndex) {
  36305. sprite.setConfig({
  36306. renderer: renderer,
  36307. rendererData: rendererData,
  36308. rendererIndex: itemIndex
  36309. });
  36310. });
  36311. },
  36312. getRendererData: function() {
  36313. return {
  36314. store: this.getStore(),
  36315. angleField: this.getXField(),
  36316. radiusField: this.getYField(),
  36317. series: this
  36318. };
  36319. },
  36320. getSprites: function(createMissing) {
  36321. var me = this,
  36322. store = me.getStore(),
  36323. sprites = me.sprites;
  36324. if (!store) {
  36325. return Ext.emptyArray;
  36326. }
  36327. if (sprites && !createMissing) {
  36328. return sprites;
  36329. }
  36330. var surface = me.getSurface(),
  36331. records = store.getData().items,
  36332. spritesPerSlice = me.spritesPerSlice,
  36333. partCount = me.partNames.length,
  36334. recordCount = records.length,
  36335. sprite, i, j;
  36336. for (i = 0; i < recordCount; i++) {
  36337. if (!sprites[i * spritesPerSlice]) {
  36338. for (j = 0; j < partCount; j++) {
  36339. sprite = surface.add({
  36340. type: 'pie3dPart',
  36341. part: me.partNames[j],
  36342. series: me
  36343. });
  36344. sprite.getAnimation().setDurationOn('baseRotation', 0);
  36345. sprites.push(sprite);
  36346. }
  36347. }
  36348. }
  36349. return sprites;
  36350. },
  36351. betweenAngle: function(x, a, b) {
  36352. var pp = Math.PI * 2,
  36353. offset = this.rotationOffset;
  36354. a += offset;
  36355. b += offset;
  36356. x -= a;
  36357. b -= a;
  36358. // Normalize, so that both x and b are in the [0,360) interval.
  36359. // Since 360 * n angles will be normalized to 0,
  36360. // we need to treat b === 0 as a special case.
  36361. x %= pp;
  36362. b %= pp;
  36363. x += pp;
  36364. b += pp;
  36365. x %= pp;
  36366. b %= pp;
  36367. return x < b || b === 0;
  36368. },
  36369. getItemForPoint: function(x, y) {
  36370. var me = this,
  36371. sprites = me.getSprites(),
  36372. result = null;
  36373. if (!sprites) {
  36374. return result;
  36375. }
  36376. var store = me.getStore(),
  36377. records = store.getData().items,
  36378. spritesPerSlice = me.spritesPerSlice,
  36379. hidden = me.getHidden(),
  36380. i, ln, sprite, topPartIndex;
  36381. for (i = 0 , ln = records.length; i < ln; i++) {
  36382. if (hidden[i]) {
  36383. continue;
  36384. }
  36385. topPartIndex = i * spritesPerSlice;
  36386. sprite = sprites[topPartIndex];
  36387. // This is CPU intensive on mousemove (no visial slowdown
  36388. // on a fast machine, but some throttling might be desirable
  36389. // on slower machines).
  36390. // On touch devices performance/battery hit is negligible.
  36391. if (sprite.hitTest([
  36392. x,
  36393. y
  36394. ])) {
  36395. result = {
  36396. series: me,
  36397. sprite: sprites.slice(topPartIndex, topPartIndex + spritesPerSlice),
  36398. index: i,
  36399. record: records[i],
  36400. category: 'sprites',
  36401. field: me.getXField()
  36402. };
  36403. break;
  36404. }
  36405. }
  36406. return result;
  36407. },
  36408. provideLegendInfo: function(target) {
  36409. var me = this,
  36410. store = me.getStore();
  36411. if (store) {
  36412. var items = store.getData().items,
  36413. labelField = me.getLabel().getTemplate().getField(),
  36414. field = me.getField(),
  36415. hidden = me.getHidden(),
  36416. i, style, color;
  36417. for (i = 0; i < items.length; i++) {
  36418. style = me.getStyleByIndex(i);
  36419. color = style.baseColor;
  36420. target.push({
  36421. name: labelField ? String(items[i].get(labelField)) : field + ' ' + i,
  36422. mark: color || 'black',
  36423. disabled: hidden[i],
  36424. series: me.getId(),
  36425. index: i
  36426. });
  36427. }
  36428. }
  36429. }
  36430. }, function() {
  36431. var proto = this.prototype,
  36432. definition = Ext.chart.series.sprite.Pie3DPart.def.getInitialConfig().processors.part;
  36433. proto.partNames = definition.replace(/^enums\(|\)/g, '').split(',');
  36434. proto.spritesPerSlice = proto.partNames.length;
  36435. });
  36436. /**
  36437. * Polar sprite.
  36438. */
  36439. Ext.define('Ext.chart.series.sprite.Polar', {
  36440. extend: 'Ext.chart.series.sprite.Series',
  36441. inheritableStatics: {
  36442. def: {
  36443. processors: {
  36444. /**
  36445. * @cfg {Number} [centerX=0] The central point of the series on the x-axis.
  36446. */
  36447. centerX: 'number',
  36448. /**
  36449. * @cfg {Number} [centerY=0] The central point of the series on the y-axis.
  36450. */
  36451. centerY: 'number',
  36452. /**
  36453. * @cfg {Number} [startAngle=0] The starting angle of the polar series.
  36454. */
  36455. startAngle: 'number',
  36456. /**
  36457. * @cfg {Number} [endAngle=Math.PI] The ending angle of the polar series.
  36458. */
  36459. endAngle: 'number',
  36460. /**
  36461. * @cfg {Number} [startRho=0] The starting radius of the polar series.
  36462. */
  36463. startRho: 'number',
  36464. /**
  36465. * @cfg {Number} [endRho=150] The ending radius of the polar series.
  36466. */
  36467. endRho: 'number',
  36468. /**
  36469. * @cfg {Number} [baseRotation=0] The starting rotation of the polar series.
  36470. */
  36471. baseRotation: 'number'
  36472. },
  36473. defaults: {
  36474. centerX: 0,
  36475. centerY: 0,
  36476. startAngle: 0,
  36477. endAngle: Math.PI,
  36478. startRho: 0,
  36479. endRho: 150,
  36480. baseRotation: 0
  36481. },
  36482. triggers: {
  36483. centerX: 'bbox',
  36484. centerY: 'bbox',
  36485. startAngle: 'bbox',
  36486. endAngle: 'bbox',
  36487. startRho: 'bbox',
  36488. endRho: 'bbox',
  36489. baseRotation: 'bbox'
  36490. }
  36491. }
  36492. },
  36493. updatePlainBBox: function(plain) {
  36494. var attr = this.attr;
  36495. plain.x = attr.centerX - attr.endRho;
  36496. plain.y = attr.centerY + attr.endRho;
  36497. plain.width = attr.endRho * 2;
  36498. plain.height = attr.endRho * 2;
  36499. }
  36500. });
  36501. /**
  36502. * @class Ext.chart.series.sprite.Radar
  36503. * @extends Ext.chart.series.sprite.Polar
  36504. *
  36505. * Radar series sprite.
  36506. */
  36507. Ext.define('Ext.chart.series.sprite.Radar', {
  36508. alias: 'sprite.radar',
  36509. extend: 'Ext.chart.series.sprite.Polar',
  36510. getDataPointXY: function(index) {
  36511. var me = this,
  36512. attr = me.attr,
  36513. centerX = attr.centerX,
  36514. centerY = attr.centerY,
  36515. matrix = attr.matrix,
  36516. minX = attr.dataMinX,
  36517. maxX = attr.dataMaxX,
  36518. dataX = attr.dataX,
  36519. dataY = attr.dataY,
  36520. endRho = attr.endRho,
  36521. startRho = attr.startRho,
  36522. baseRotation = attr.baseRotation,
  36523. x, y, r, th, ox, oy, maxY;
  36524. if (attr.rangeY) {
  36525. maxY = attr.rangeY[1];
  36526. } else {
  36527. maxY = attr.dataMaxY;
  36528. }
  36529. th = (dataX[index] - minX) / (maxX - minX + 1) * 2 * Math.PI + baseRotation;
  36530. r = dataY[index] / maxY * (endRho - startRho) + startRho;
  36531. // Original coordinates.
  36532. ox = centerX + Math.cos(th) * r;
  36533. oy = centerY + Math.sin(th) * r;
  36534. // Transformed coordinates.
  36535. x = matrix.x(ox, oy);
  36536. y = matrix.y(ox, oy);
  36537. return [
  36538. x,
  36539. y
  36540. ];
  36541. },
  36542. render: function(surface, ctx) {
  36543. var me = this,
  36544. attr = me.attr,
  36545. dataX = attr.dataX,
  36546. length = dataX.length,
  36547. surfaceMatrix = me.surfaceMatrix,
  36548. markerCfg = {},
  36549. i, x, y, xy;
  36550. ctx.beginPath();
  36551. for (i = 0; i < length; i++) {
  36552. xy = me.getDataPointXY(i);
  36553. x = xy[0];
  36554. y = xy[1];
  36555. if (i === 0) {
  36556. ctx.moveTo(x, y);
  36557. }
  36558. ctx.lineTo(x, y);
  36559. markerCfg.translationX = surfaceMatrix.x(x, y);
  36560. markerCfg.translationY = surfaceMatrix.y(x, y);
  36561. me.putMarker('markers', markerCfg, i, true);
  36562. }
  36563. ctx.closePath();
  36564. ctx.fillStroke(attr);
  36565. }
  36566. });
  36567. /**
  36568. * @class Ext.chart.series.Radar
  36569. * @extends Ext.chart.series.Polar
  36570. *
  36571. * Creates a Radar Chart. A Radar Chart is a useful visualization technique for comparing different quantitative values for
  36572. * a constrained number of categories.
  36573. * As with all other series, the Radar series must be appended in the *series* Chart array configuration. See the Chart
  36574. * documentation for more information. A typical configuration object for the radar series could be:
  36575. *
  36576. * @example
  36577. * Ext.create({
  36578. * xtype: 'polar',
  36579. * renderTo: document.body,
  36580. * width: 500,
  36581. * height: 400,
  36582. * interactions: 'rotate',
  36583. * store: {
  36584. * fields: ['name', 'data1'],
  36585. * data: [{
  36586. * 'name': 'metric one',
  36587. * 'data1': 8
  36588. * }, {
  36589. * 'name': 'metric two',
  36590. * 'data1': 10
  36591. * }, {
  36592. * 'name': 'metric three',
  36593. * 'data1': 12
  36594. * }, {
  36595. * 'name': 'metric four',
  36596. * 'data1': 1
  36597. * }, {
  36598. * 'name': 'metric five',
  36599. * 'data1': 13
  36600. * }]
  36601. * },
  36602. * series: {
  36603. * type: 'radar',
  36604. * angleField: 'name',
  36605. * radiusField: 'data1',
  36606. * style: {
  36607. * fillStyle: '#388FAD',
  36608. * fillOpacity: .1,
  36609. * strokeStyle: '#388FAD',
  36610. * strokeOpacity: .8,
  36611. * lineWidth: 1
  36612. * }
  36613. * },
  36614. * axes: [{
  36615. * type: 'numeric',
  36616. * position: 'radial',
  36617. * fields: 'data1',
  36618. * style: {
  36619. * estStepSize: 10
  36620. * },
  36621. * grid: true
  36622. * }, {
  36623. * type: 'category',
  36624. * position: 'angular',
  36625. * fields: 'name',
  36626. * style: {
  36627. * estStepSize: 1
  36628. * },
  36629. * grid: true
  36630. * }]
  36631. * });
  36632. *
  36633. */
  36634. Ext.define('Ext.chart.series.Radar', {
  36635. extend: 'Ext.chart.series.Polar',
  36636. type: 'radar',
  36637. seriesType: 'radar',
  36638. alias: 'series.radar',
  36639. requires: [
  36640. 'Ext.chart.series.sprite.Radar'
  36641. ],
  36642. themeColorCount: function() {
  36643. return 1;
  36644. },
  36645. isStoreDependantColorCount: false,
  36646. themeMarkerCount: function() {
  36647. return 1;
  36648. },
  36649. updateAngularAxis: function(axis) {
  36650. axis.processData(this);
  36651. },
  36652. updateRadialAxis: function(axis) {
  36653. axis.processData(this);
  36654. },
  36655. coordinateX: function() {
  36656. return this.coordinate('X', 0, 2);
  36657. },
  36658. coordinateY: function() {
  36659. return this.coordinate('Y', 1, 2);
  36660. },
  36661. updateCenter: function(center) {
  36662. this.setStyle({
  36663. translationX: center[0] + this.getOffsetX(),
  36664. translationY: center[1] + this.getOffsetY()
  36665. });
  36666. this.doUpdateStyles();
  36667. },
  36668. updateRadius: function(radius) {
  36669. this.setStyle({
  36670. endRho: radius
  36671. });
  36672. this.doUpdateStyles();
  36673. },
  36674. updateRotation: function(rotation) {
  36675. // Overrides base class method.
  36676. var me = this,
  36677. chart = me.getChart(),
  36678. axes = chart.getAxes(),
  36679. i, ln, axis;
  36680. for (i = 0 , ln = axes.length; i < ln; i++) {
  36681. axis = axes[i];
  36682. axis.setRotation(rotation);
  36683. }
  36684. me.setStyle({
  36685. rotationRads: rotation
  36686. });
  36687. me.doUpdateStyles();
  36688. },
  36689. updateTotalAngle: function(totalAngle) {
  36690. this.processData();
  36691. },
  36692. getItemForPoint: function(x, y) {
  36693. var me = this,
  36694. sprite = me.sprites && me.sprites[0],
  36695. attr = sprite.attr,
  36696. dataX = attr.dataX,
  36697. length = dataX.length,
  36698. store = me.getStore(),
  36699. marker = me.getMarker(),
  36700. threshhold, item, xy, i, bbox, markers;
  36701. if (me.getHidden()) {
  36702. return null;
  36703. }
  36704. if (sprite && marker) {
  36705. markers = sprite.getMarker('markers');
  36706. for (i = 0; i < length; i++) {
  36707. bbox = markers.getBBoxFor(i);
  36708. threshhold = (bbox.width + bbox.height) * 0.25;
  36709. xy = sprite.getDataPointXY(i);
  36710. if (Math.abs(xy[0] - x) < threshhold && Math.abs(xy[1] - y) < threshhold) {
  36711. item = {
  36712. series: me,
  36713. sprite: sprite,
  36714. index: i,
  36715. category: 'markers',
  36716. record: store.getData().items[i],
  36717. field: me.getYField()
  36718. };
  36719. return item;
  36720. }
  36721. }
  36722. }
  36723. return me.callParent(arguments);
  36724. },
  36725. getDefaultSpriteConfig: function() {
  36726. var config = this.callParent(),
  36727. animation = {
  36728. customDurations: {
  36729. translationX: 0,
  36730. translationY: 0,
  36731. rotationRads: 0,
  36732. // Prevent animation of 'dataMinX' and 'dataMaxX' attributes in order
  36733. // to react instantaniously to changes to the 'hidden' attribute.
  36734. dataMinX: 0,
  36735. dataMaxX: 0
  36736. }
  36737. };
  36738. if (config.animation) {
  36739. Ext.apply(config.animation, animation);
  36740. } else {
  36741. config.animation = animation;
  36742. }
  36743. return config;
  36744. },
  36745. getSprites: function() {
  36746. var me = this,
  36747. chart = me.getChart(),
  36748. sprites = me.sprites;
  36749. if (!chart) {
  36750. return Ext.emptyArray;
  36751. }
  36752. if (!sprites.length) {
  36753. me.createSprite();
  36754. }
  36755. return sprites;
  36756. },
  36757. provideLegendInfo: function(target) {
  36758. var me = this,
  36759. style = me.getSubStyleWithTheme(),
  36760. fill = style.fillStyle;
  36761. if (Ext.isArray(fill)) {
  36762. fill = fill[0];
  36763. }
  36764. target.push({
  36765. name: me.getTitle() || me.getYField() || me.getId(),
  36766. mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
  36767. disabled: me.getHidden(),
  36768. series: me.getId(),
  36769. index: 0
  36770. });
  36771. }
  36772. });
  36773. /**
  36774. * @class Ext.chart.series.sprite.Scatter
  36775. * @extends Ext.chart.series.sprite.Cartesian
  36776. *
  36777. * Scatter series sprite.
  36778. */
  36779. Ext.define('Ext.chart.series.sprite.Scatter', {
  36780. alias: 'sprite.scatterSeries',
  36781. extend: 'Ext.chart.series.sprite.Cartesian',
  36782. renderClipped: function(surface, ctx, dataClipRect, surfaceClipRect) {
  36783. if (this.cleanRedraw) {
  36784. return;
  36785. }
  36786. var me = this,
  36787. attr = me.attr,
  36788. dataX = attr.dataX,
  36789. dataY = attr.dataY,
  36790. labels = attr.labels,
  36791. series = me.getSeries(),
  36792. isDrawLabels = labels && me.getMarker('labels'),
  36793. surfaceMatrix = me.surfaceMatrix,
  36794. matrix = me.attr.matrix,
  36795. xx = matrix.getXX(),
  36796. yy = matrix.getYY(),
  36797. dx = matrix.getDX(),
  36798. dy = matrix.getDY(),
  36799. markerCfg = {},
  36800. changes, params,
  36801. xScalingDirection = surface.getInherited().rtl && !attr.flipXY ? -1 : 1,
  36802. left, right, top, bottom, x, y, i;
  36803. if (attr.flipXY) {
  36804. left = surfaceClipRect[1] - xx * xScalingDirection;
  36805. right = surfaceClipRect[1] + surfaceClipRect[3] + xx * xScalingDirection;
  36806. top = surfaceClipRect[0] - yy;
  36807. bottom = surfaceClipRect[0] + surfaceClipRect[2] + yy;
  36808. } else {
  36809. left = surfaceClipRect[0] - xx * xScalingDirection;
  36810. right = surfaceClipRect[0] + surfaceClipRect[2] + xx * xScalingDirection;
  36811. top = surfaceClipRect[1] - yy;
  36812. bottom = surfaceClipRect[1] + surfaceClipRect[3] + yy;
  36813. }
  36814. for (i = 0; i < dataX.length; i++) {
  36815. x = dataX[i];
  36816. y = dataY[i];
  36817. x = x * xx + dx;
  36818. y = y * yy + dy;
  36819. if (left <= x && x <= right && top <= y && y <= bottom) {
  36820. if (attr.renderer) {
  36821. markerCfg = {
  36822. type: 'markers',
  36823. translationX: surfaceMatrix.x(x, y),
  36824. translationY: surfaceMatrix.y(x, y)
  36825. };
  36826. params = [
  36827. me,
  36828. markerCfg,
  36829. {
  36830. store: me.getStore()
  36831. },
  36832. i
  36833. ];
  36834. changes = Ext.callback(attr.renderer, null, params, 0, series);
  36835. markerCfg = Ext.apply(markerCfg, changes);
  36836. } else {
  36837. markerCfg.translationX = surfaceMatrix.x(x, y);
  36838. markerCfg.translationY = surfaceMatrix.y(x, y);
  36839. }
  36840. me.putMarker('markers', markerCfg, i, !attr.renderer);
  36841. if (isDrawLabels && labels[i]) {
  36842. me.drawLabel(labels[i], x, y, i, surfaceClipRect);
  36843. }
  36844. }
  36845. }
  36846. },
  36847. drawLabel: function(text, dataX, dataY, labelId, rect) {
  36848. var me = this,
  36849. attr = me.attr,
  36850. label = me.getMarker('labels'),
  36851. labelTpl = label.getTemplate(),
  36852. labelCfg = me.labelCfg || (me.labelCfg = {}),
  36853. surfaceMatrix = me.surfaceMatrix,
  36854. labelX, labelY,
  36855. labelOverflowPadding = attr.labelOverflowPadding,
  36856. flipXY = attr.flipXY,
  36857. halfHeight, labelBox, changes, params;
  36858. labelCfg.text = text;
  36859. labelBox = me.getMarkerBBox('labels', labelId, true);
  36860. if (!labelBox) {
  36861. me.putMarker('labels', labelCfg, labelId);
  36862. labelBox = me.getMarkerBBox('labels', labelId, true);
  36863. }
  36864. if (flipXY) {
  36865. labelCfg.rotationRads = Math.PI * 0.5;
  36866. } else {
  36867. labelCfg.rotationRads = 0;
  36868. }
  36869. halfHeight = labelBox.height / 2;
  36870. labelX = dataX;
  36871. switch (labelTpl.attr.display) {
  36872. case 'under':
  36873. labelY = dataY - halfHeight - labelOverflowPadding;
  36874. break;
  36875. case 'rotate':
  36876. labelX += labelOverflowPadding;
  36877. labelY = dataY - labelOverflowPadding;
  36878. labelCfg.rotationRads = -Math.PI / 4;
  36879. break;
  36880. default:
  36881. // 'over'
  36882. labelY = dataY + halfHeight + labelOverflowPadding;
  36883. }
  36884. labelCfg.x = surfaceMatrix.x(labelX, labelY);
  36885. labelCfg.y = surfaceMatrix.y(labelX, labelY);
  36886. if (labelTpl.attr.renderer) {
  36887. params = [
  36888. text,
  36889. label,
  36890. labelCfg,
  36891. {
  36892. store: me.getStore()
  36893. },
  36894. labelId
  36895. ];
  36896. changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
  36897. if (typeof changes === 'string') {
  36898. labelCfg.text = changes;
  36899. } else {
  36900. Ext.apply(labelCfg, changes);
  36901. }
  36902. }
  36903. me.putMarker('labels', labelCfg, labelId);
  36904. }
  36905. });
  36906. /**
  36907. * @class Ext.chart.series.Scatter
  36908. * @extends Ext.chart.series.Cartesian
  36909. *
  36910. * Creates a Scatter Chart. The scatter plot is useful when trying to display more than two variables in the same visualization.
  36911. * These variables can be mapped into x, y coordinates and also to an element's radius/size, color, etc.
  36912. * As with all other series, the Scatter Series must be appended in the *series* Chart array configuration. See the Chart
  36913. * documentation for more information on creating charts. A typical configuration object for the scatter could be:
  36914. *
  36915. * @example
  36916. * Ext.create({
  36917. * xtype: 'cartesian',
  36918. * renderTo: document.body,
  36919. * width: 600,
  36920. * height: 400,
  36921. * insetPadding: 40,
  36922. * interactions: ['itemhighlight'],
  36923. * store: {
  36924. * fields: ['name', 'data1', 'data2'],
  36925. * data: [{
  36926. * 'name': 'metric one',
  36927. * 'data1': 10,
  36928. * 'data2': 14
  36929. * }, {
  36930. * 'name': 'metric two',
  36931. * 'data1': 7,
  36932. * 'data2': 16
  36933. * }, {
  36934. * 'name': 'metric three',
  36935. * 'data1': 5,
  36936. * 'data2': 14
  36937. * }, {
  36938. * 'name': 'metric four',
  36939. * 'data1': 2,
  36940. * 'data2': 6
  36941. * }, {
  36942. * 'name': 'metric five',
  36943. * 'data1': 27,
  36944. * 'data2': 36
  36945. * }]
  36946. * },
  36947. * axes: [{
  36948. * type: 'numeric',
  36949. * position: 'left',
  36950. * fields: ['data1'],
  36951. * title: {
  36952. * text: 'Sample Values',
  36953. * fontSize: 15
  36954. * },
  36955. * grid: true,
  36956. * minimum: 0
  36957. * }, {
  36958. * type: 'category',
  36959. * position: 'bottom',
  36960. * fields: ['name'],
  36961. * title: {
  36962. * text: 'Sample Values',
  36963. * fontSize: 15
  36964. * }
  36965. * }],
  36966. * series: {
  36967. * type: 'scatter',
  36968. * highlight: {
  36969. * size: 12,
  36970. * radius: 12,
  36971. * fill: '#96D4C6',
  36972. * stroke: '#30BDA7'
  36973. * },
  36974. * fill: true,
  36975. * xField: 'name',
  36976. * yField: 'data2',
  36977. * marker: {
  36978. * type: 'circle',
  36979. * fill: '#30BDA7',
  36980. * radius: 10,
  36981. * lineWidth: 0
  36982. * }
  36983. * }
  36984. * });
  36985. *
  36986. * In this configuration we add three different categories of scatter series. Each of them is bound to a different field of the same data store,
  36987. * `data1`, `data2` and `data3` respectively. All x-fields for the series must be the same field, in this case `name`.
  36988. * Each scatter series has a different styling configuration for markers, specified by the `marker` object. Finally we set the left axis as
  36989. * axis to show the current values of the elements.
  36990. *
  36991. */
  36992. Ext.define('Ext.chart.series.Scatter', {
  36993. extend: 'Ext.chart.series.Cartesian',
  36994. alias: 'series.scatter',
  36995. type: 'scatter',
  36996. seriesType: 'scatterSeries',
  36997. requires: [
  36998. 'Ext.chart.series.sprite.Scatter'
  36999. ],
  37000. config: {
  37001. itemInstancing: null,
  37002. marker: true
  37003. },
  37004. themeMarkerCount: function() {
  37005. return 1;
  37006. },
  37007. provideLegendInfo: function(target) {
  37008. var me = this,
  37009. style = me.getMarkerStyleByIndex(0),
  37010. fill = style.fillStyle;
  37011. target.push({
  37012. name: me.getTitle() || me.getYField() || me.getId(),
  37013. mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
  37014. disabled: me.getHidden(),
  37015. series: me.getId(),
  37016. index: 0
  37017. });
  37018. }
  37019. });
  37020. Ext.define('Ext.chart.theme.Blue', {
  37021. extend: 'Ext.chart.theme.Base',
  37022. singleton: true,
  37023. alias: [
  37024. 'chart.theme.blue',
  37025. 'chart.theme.Blue'
  37026. ],
  37027. config: {
  37028. baseColor: '#4d7fe6'
  37029. }
  37030. });
  37031. Ext.define('Ext.chart.theme.BlueGradients', {
  37032. extend: 'Ext.chart.theme.Base',
  37033. singleton: true,
  37034. alias: [
  37035. 'chart.theme.blue-gradients',
  37036. 'chart.theme.Blue:gradients'
  37037. ],
  37038. config: {
  37039. baseColor: '#4d7fe6',
  37040. gradients: {
  37041. type: 'linear',
  37042. degrees: 90
  37043. }
  37044. }
  37045. });
  37046. Ext.define('Ext.chart.theme.Category1', {
  37047. extend: 'Ext.chart.theme.Base',
  37048. singleton: true,
  37049. alias: [
  37050. 'chart.theme.category1',
  37051. 'chart.theme.Category1'
  37052. ],
  37053. config: {
  37054. colors: [
  37055. '#f0a50a',
  37056. '#c20024',
  37057. '#2044ba',
  37058. '#810065',
  37059. '#7eae29'
  37060. ]
  37061. }
  37062. });
  37063. Ext.define('Ext.chart.theme.Category1Gradients', {
  37064. extend: 'Ext.chart.theme.Base',
  37065. singleton: true,
  37066. alias: [
  37067. 'chart.theme.category1-gradients',
  37068. 'chart.theme.Category1:gradients'
  37069. ],
  37070. config: {
  37071. colors: [
  37072. '#f0a50a',
  37073. '#c20024',
  37074. '#2044ba',
  37075. '#810065',
  37076. '#7eae29'
  37077. ],
  37078. gradients: {
  37079. type: 'linear',
  37080. degrees: 90
  37081. }
  37082. }
  37083. });
  37084. Ext.define('Ext.chart.theme.Category2', {
  37085. extend: 'Ext.chart.theme.Base',
  37086. singleton: true,
  37087. alias: [
  37088. 'chart.theme.category2',
  37089. 'chart.theme.Category2'
  37090. ],
  37091. config: {
  37092. colors: [
  37093. '#6d9824',
  37094. '#87146e',
  37095. '#2a9196',
  37096. '#d39006',
  37097. '#1e40ac'
  37098. ]
  37099. }
  37100. });
  37101. Ext.define('Ext.chart.theme.Category2Gradients', {
  37102. extend: 'Ext.chart.theme.Base',
  37103. singleton: true,
  37104. alias: [
  37105. 'chart.theme.category2-gradients',
  37106. 'chart.theme.Category2:gradients'
  37107. ],
  37108. config: {
  37109. colors: [
  37110. '#6d9824',
  37111. '#87146e',
  37112. '#2a9196',
  37113. '#d39006',
  37114. '#1e40ac'
  37115. ],
  37116. gradients: {
  37117. type: 'linear',
  37118. degrees: 90
  37119. }
  37120. }
  37121. });
  37122. Ext.define('Ext.chart.theme.Category3', {
  37123. extend: 'Ext.chart.theme.Base',
  37124. singleton: true,
  37125. alias: [
  37126. 'chart.theme.category3',
  37127. 'chart.theme.Category3'
  37128. ],
  37129. config: {
  37130. colors: [
  37131. '#fbbc29',
  37132. '#ce2e4e',
  37133. '#7e0062',
  37134. '#158b90',
  37135. '#57880e'
  37136. ]
  37137. }
  37138. });
  37139. Ext.define('Ext.chart.theme.Category3Gradients', {
  37140. extend: 'Ext.chart.theme.Base',
  37141. singleton: true,
  37142. alias: [
  37143. 'chart.theme.category3-gradients',
  37144. 'chart.theme.Category3:gradients'
  37145. ],
  37146. config: {
  37147. colors: [
  37148. '#fbbc29',
  37149. '#ce2e4e',
  37150. '#7e0062',
  37151. '#158b90',
  37152. '#57880e'
  37153. ],
  37154. gradients: {
  37155. type: 'linear',
  37156. degrees: 90
  37157. }
  37158. }
  37159. });
  37160. Ext.define('Ext.chart.theme.Category4', {
  37161. extend: 'Ext.chart.theme.Base',
  37162. singleton: true,
  37163. alias: [
  37164. 'chart.theme.category4',
  37165. 'chart.theme.Category4'
  37166. ],
  37167. config: {
  37168. colors: [
  37169. '#ef5773',
  37170. '#fcbd2a',
  37171. '#4f770d',
  37172. '#1d3eaa',
  37173. '#9b001f'
  37174. ]
  37175. }
  37176. });
  37177. Ext.define('Ext.chart.theme.Category4Gradients', {
  37178. extend: 'Ext.chart.theme.Base',
  37179. singleton: true,
  37180. alias: [
  37181. 'chart.theme.category4-gradients',
  37182. 'chart.theme.Category4:gradients'
  37183. ],
  37184. config: {
  37185. colors: [
  37186. '#ef5773',
  37187. '#fcbd2a',
  37188. '#4f770d',
  37189. '#1d3eaa',
  37190. '#9b001f'
  37191. ],
  37192. gradients: {
  37193. type: 'linear',
  37194. degrees: 90
  37195. }
  37196. }
  37197. });
  37198. Ext.define('Ext.chart.theme.Category5', {
  37199. extend: 'Ext.chart.theme.Base',
  37200. singleton: true,
  37201. alias: [
  37202. 'chart.theme.category5',
  37203. 'chart.theme.Category5'
  37204. ],
  37205. config: {
  37206. colors: [
  37207. '#7eae29',
  37208. '#fdbe2a',
  37209. '#910019',
  37210. '#27b4bc',
  37211. '#d74dbc'
  37212. ]
  37213. }
  37214. });
  37215. Ext.define('Ext.chart.theme.Category5Gradients', {
  37216. extend: 'Ext.chart.theme.Base',
  37217. singleton: true,
  37218. alias: [
  37219. 'chart.theme.category5-gradients',
  37220. 'chart.theme.Category5:gradients'
  37221. ],
  37222. config: {
  37223. colors: [
  37224. '#7eae29',
  37225. '#fdbe2a',
  37226. '#910019',
  37227. '#27b4bc',
  37228. '#d74dbc'
  37229. ],
  37230. gradients: {
  37231. type: 'linear',
  37232. degrees: 90
  37233. }
  37234. }
  37235. });
  37236. Ext.define('Ext.chart.theme.Category6', {
  37237. extend: 'Ext.chart.theme.Base',
  37238. singleton: true,
  37239. alias: [
  37240. 'chart.theme.category6',
  37241. 'chart.theme.Category6'
  37242. ],
  37243. config: {
  37244. colors: [
  37245. '#44dce1',
  37246. '#0b2592',
  37247. '#996e05',
  37248. '#7fb325',
  37249. '#b821a1'
  37250. ]
  37251. }
  37252. });
  37253. Ext.define('Ext.chart.theme.Category6Gradients', {
  37254. extend: 'Ext.chart.theme.Base',
  37255. singleton: true,
  37256. alias: [
  37257. 'chart.theme.category6-gradients',
  37258. 'chart.theme.Category6:gradients'
  37259. ],
  37260. config: {
  37261. colors: [
  37262. '#44dce1',
  37263. '#0b2592',
  37264. '#996e05',
  37265. '#7fb325',
  37266. '#b821a1'
  37267. ],
  37268. gradients: {
  37269. type: 'linear',
  37270. degrees: 90
  37271. }
  37272. }
  37273. });
  37274. Ext.define('Ext.chart.theme.DefaultGradients', {
  37275. extend: 'Ext.chart.theme.Base',
  37276. singleton: true,
  37277. alias: [
  37278. 'chart.theme.default-gradients',
  37279. 'chart.theme.Base:gradients'
  37280. ],
  37281. config: {
  37282. gradients: {
  37283. type: 'linear',
  37284. degrees: 90
  37285. }
  37286. }
  37287. });
  37288. Ext.define('Ext.chart.theme.Green', {
  37289. extend: 'Ext.chart.theme.Base',
  37290. singleton: true,
  37291. alias: [
  37292. 'chart.theme.green',
  37293. 'chart.theme.Green'
  37294. ],
  37295. config: {
  37296. baseColor: '#b1da5a'
  37297. }
  37298. });
  37299. Ext.define('Ext.chart.theme.GreenGradients', {
  37300. extend: 'Ext.chart.theme.Base',
  37301. singleton: true,
  37302. alias: [
  37303. 'chart.theme.green-gradients',
  37304. 'chart.theme.Green:gradients'
  37305. ],
  37306. config: {
  37307. baseColor: '#b1da5a',
  37308. gradients: {
  37309. type: 'linear',
  37310. degrees: 90
  37311. }
  37312. }
  37313. });
  37314. Ext.define('Ext.chart.theme.Midnight', {
  37315. extend: 'Ext.chart.theme.Base',
  37316. singleton: true,
  37317. alias: [
  37318. 'chart.theme.midnight',
  37319. 'chart.theme.Midnight'
  37320. ],
  37321. config: {
  37322. colors: [
  37323. '#a837ff',
  37324. '#4ac0f2',
  37325. '#ff4d35',
  37326. '#ff8809',
  37327. '#61c102',
  37328. '#ff37ea'
  37329. ],
  37330. chart: {
  37331. defaults: {
  37332. captions: {
  37333. title: {
  37334. docked: 'top',
  37335. padding: 5,
  37336. style: {
  37337. textAlign: 'center',
  37338. fontFamily: 'default',
  37339. fontWeight: 'bold',
  37340. fillStyle: 'rgb(224, 224, 227)',
  37341. fontSize: 'default*1.6'
  37342. }
  37343. },
  37344. subtitle: {
  37345. docked: 'top',
  37346. style: {
  37347. textAlign: 'center',
  37348. fontFamily: 'default',
  37349. fontWeight: 'normal',
  37350. fillStyle: 'rgb(224, 224, 227)',
  37351. fontSize: 'default*1.3'
  37352. }
  37353. },
  37354. credits: {
  37355. docked: 'bottom',
  37356. padding: 5,
  37357. style: {
  37358. textAlign: 'left',
  37359. fontFamily: 'default',
  37360. fontWeight: 'lighter',
  37361. fillStyle: 'rgb(224, 224, 227)',
  37362. fontSize: 'default'
  37363. }
  37364. }
  37365. },
  37366. background: 'rgb(52, 52, 53)'
  37367. }
  37368. },
  37369. axis: {
  37370. defaults: {
  37371. style: {
  37372. strokeStyle: 'rgb(224, 224, 227)'
  37373. },
  37374. label: {
  37375. fillStyle: 'rgb(224, 224, 227)'
  37376. },
  37377. title: {
  37378. fillStyle: 'rgb(224, 224, 227)'
  37379. },
  37380. grid: {
  37381. strokeStyle: 'rgb(112, 112, 115)'
  37382. }
  37383. }
  37384. },
  37385. series: {
  37386. defaults: {
  37387. label: {
  37388. fillStyle: 'rgb(224, 224, 227)'
  37389. }
  37390. }
  37391. },
  37392. sprites: {
  37393. text: {
  37394. fillStyle: 'rgb(224, 224, 227)'
  37395. }
  37396. },
  37397. legend: {
  37398. label: {
  37399. fillStyle: 'white'
  37400. },
  37401. border: {
  37402. lineWidth: 2,
  37403. fillStyle: 'rgba(255, 255, 255, 0.3)',
  37404. strokeStyle: 'rgb(150, 150, 150)'
  37405. },
  37406. background: 'rgb(52, 52, 53)'
  37407. }
  37408. }
  37409. });
  37410. Ext.define('Ext.chart.theme.Muted', {
  37411. extend: 'Ext.chart.theme.Base',
  37412. singleton: true,
  37413. alias: [
  37414. 'chart.theme.muted',
  37415. 'chart.theme.Muted'
  37416. ],
  37417. config: {
  37418. colors: [
  37419. '#8ca640',
  37420. '#974144',
  37421. '#4091ba',
  37422. '#8e658e',
  37423. '#3b8d8b',
  37424. '#b86465',
  37425. '#d2af69',
  37426. '#6e8852',
  37427. '#3dcc7e',
  37428. '#a6bed1',
  37429. '#cbaa4b',
  37430. '#998baa'
  37431. ]
  37432. }
  37433. });
  37434. Ext.define('Ext.chart.theme.Purple', {
  37435. extend: 'Ext.chart.theme.Base',
  37436. singleton: true,
  37437. alias: [
  37438. 'chart.theme.purple',
  37439. 'chart.theme.Purple'
  37440. ],
  37441. config: {
  37442. baseColor: '#da5abd'
  37443. }
  37444. });
  37445. Ext.define('Ext.chart.theme.PurpleGradients', {
  37446. extend: 'Ext.chart.theme.Base',
  37447. singleton: true,
  37448. alias: [
  37449. 'chart.theme.purple-gradients',
  37450. 'chart.theme.Purple:gradients'
  37451. ],
  37452. config: {
  37453. baseColor: '#da5abd',
  37454. gradients: {
  37455. type: 'linear',
  37456. degrees: 90
  37457. }
  37458. }
  37459. });
  37460. Ext.define('Ext.chart.theme.Red', {
  37461. extend: 'Ext.chart.theme.Base',
  37462. singleton: true,
  37463. alias: [
  37464. 'chart.theme.red',
  37465. 'chart.theme.Red'
  37466. ],
  37467. config: {
  37468. baseColor: '#e84b67'
  37469. }
  37470. });
  37471. Ext.define('Ext.chart.theme.RedGradients', {
  37472. extend: 'Ext.chart.theme.Base',
  37473. singleton: true,
  37474. alias: [
  37475. 'chart.theme.red-gradients',
  37476. 'chart.theme.Red:gradients'
  37477. ],
  37478. config: {
  37479. baseColor: '#e84b67',
  37480. gradients: {
  37481. type: 'linear',
  37482. degrees: 90
  37483. }
  37484. }
  37485. });
  37486. Ext.define('Ext.chart.theme.Sky', {
  37487. extend: 'Ext.chart.theme.Base',
  37488. singleton: true,
  37489. alias: [
  37490. 'chart.theme.sky',
  37491. 'chart.theme.Sky'
  37492. ],
  37493. config: {
  37494. baseColor: '#4ce0e7'
  37495. }
  37496. });
  37497. Ext.define('Ext.chart.theme.SkyGradients', {
  37498. extend: 'Ext.chart.theme.Base',
  37499. singleton: true,
  37500. alias: [
  37501. 'chart.theme.sky-gradients',
  37502. 'chart.theme.Sky:gradients'
  37503. ],
  37504. config: {
  37505. baseColor: '#4ce0e7',
  37506. gradients: {
  37507. type: 'linear',
  37508. degrees: 90
  37509. }
  37510. }
  37511. });
  37512. Ext.define('Ext.chart.theme.Yellow', {
  37513. extend: 'Ext.chart.theme.Base',
  37514. singleton: true,
  37515. alias: [
  37516. 'chart.theme.yellow',
  37517. 'chart.theme.Yellow'
  37518. ],
  37519. config: {
  37520. baseColor: '#fec935'
  37521. }
  37522. });
  37523. Ext.define('Ext.chart.theme.YellowGradients', {
  37524. extend: 'Ext.chart.theme.Base',
  37525. singleton: true,
  37526. alias: [
  37527. 'chart.theme.yellow-gradients',
  37528. 'chart.theme.Yellow:gradients'
  37529. ],
  37530. config: {
  37531. baseColor: '#fec935',
  37532. gradients: {
  37533. type: 'linear',
  37534. degrees: 90
  37535. }
  37536. }
  37537. });
  37538. /**
  37539. * A helper class to facilitate common operations on points and vectors.
  37540. */
  37541. Ext.define('Ext.draw.Point', {
  37542. requires: [
  37543. 'Ext.draw.Draw',
  37544. 'Ext.draw.Matrix'
  37545. ],
  37546. isPoint: true,
  37547. x: 0,
  37548. y: 0,
  37549. length: 0,
  37550. angle: 0,
  37551. angleUnits: 'degrees',
  37552. statics: {
  37553. /**
  37554. * @method
  37555. * @static
  37556. * Creates a flyweight Ext.draw.Point instance.
  37557. * Takes the same parameters as the {@link Ext.draw.Point#method!constructor}.
  37558. * Do not hold the instance of the flyweight point.
  37559. *
  37560. * @param {Number/Number[]/Object/Ext.draw.Point} point
  37561. * @return {Ext.draw.Point}
  37562. */
  37563. fly: (function() {
  37564. var point = null;
  37565. return function(x, y) {
  37566. if (!point) {
  37567. point = new Ext.draw.Point();
  37568. }
  37569. point.constructor(x, y);
  37570. return point;
  37571. };
  37572. })()
  37573. },
  37574. /**
  37575. * Creates a point.
  37576. *
  37577. * new Ext.draw.Point(3, 4);
  37578. * new Ext.draw.Point(3); // both x and y equal 3
  37579. * new Ext.draw.Point([3, 4]);
  37580. * new Ext.draw.Point({x: 3, y: 4});
  37581. * new Ext.draw.Point(p); // where `p` is a Ext.draw.Point instance.
  37582. *
  37583. * @param {Number/Number[]/Object/Ext.draw.Point} x
  37584. * @param {Number/Number[]/Object/Ext.draw.Point} y
  37585. */
  37586. constructor: function(x, y) {
  37587. var me = this;
  37588. if (typeof x === 'number') {
  37589. me.x = x;
  37590. if (typeof y === 'number') {
  37591. me.y = y;
  37592. } else {
  37593. me.y = x;
  37594. }
  37595. } else if (Ext.isArray(x)) {
  37596. me.x = x[0];
  37597. me.y = x[1];
  37598. } else if (x) {
  37599. me.x = x.x;
  37600. me.y = x.y;
  37601. }
  37602. me.calculatePolar();
  37603. },
  37604. calculateCartesian: function() {
  37605. var me = this,
  37606. length = me.length,
  37607. angle = me.angle;
  37608. if (me.angleUnits === 'degrees') {
  37609. angle = Ext.draw.Draw.rad(angle);
  37610. }
  37611. me.x = Math.cos(angle) * length;
  37612. me.y = Math.sin(angle) * length;
  37613. },
  37614. calculatePolar: function() {
  37615. var me = this,
  37616. x = me.x,
  37617. y = me.y;
  37618. me.length = Math.sqrt(x * x + y * y);
  37619. me.angle = Math.atan2(y, x);
  37620. if (me.angleUnits === 'degrees') {
  37621. me.angle = Ext.draw.Draw.degrees(me.angle);
  37622. }
  37623. },
  37624. /**
  37625. * Sets the x-coordinate of the point.
  37626. * @param {Number} x
  37627. */
  37628. setX: function(x) {
  37629. this.x = x;
  37630. this.calculatePolar();
  37631. },
  37632. /**
  37633. * Sets the y-coordinate of the point.
  37634. * @param {Number} y
  37635. */
  37636. setY: function(y) {
  37637. this.y = y;
  37638. this.calculatePolar();
  37639. },
  37640. /**
  37641. * Sets coordinates of the point.
  37642. * Takes the same parameters as the {@link #method!constructor}.
  37643. * @param {Number/Number[]/Object/Ext.draw.Point} x
  37644. * @param {Number/Number[]/Object/Ext.draw.Point} y
  37645. */
  37646. set: function(x, y) {
  37647. this.constructor(x, y);
  37648. },
  37649. /**
  37650. * Sets the angle of the vector (measured from the x-axis to the vector)
  37651. * without changing its length.
  37652. * @param {Number} angle
  37653. */
  37654. setAngle: function(angle) {
  37655. this.angle = angle;
  37656. this.calculateCartesian();
  37657. },
  37658. /**
  37659. * Sets the length of the vector without changing its angle.
  37660. * @param {Number} length
  37661. */
  37662. setLength: function(length) {
  37663. this.length = length;
  37664. this.calculateCartesian();
  37665. },
  37666. /**
  37667. * Sets both the angle and the length of the vector.
  37668. * A point can be thought of as a vector pointing from the origin to the point's location.
  37669. * This can also be interpreted as setting coordinates of a point in the polar
  37670. * coordinate system.
  37671. * @param {Number} angle
  37672. * @param {Number} length
  37673. */
  37674. setPolar: function(angle, length) {
  37675. this.angle = angle;
  37676. this.length = length;
  37677. this.calculateCartesian();
  37678. },
  37679. /**
  37680. * Returns a copy of the point.
  37681. * @return {Ext.draw.Point}
  37682. */
  37683. clone: function() {
  37684. return new Ext.draw.Point(this.x, this.y);
  37685. },
  37686. /**
  37687. * Adds another vector to this one and returns the resulting vector
  37688. * without changing this vector.
  37689. * @param {Number/Number[]/Object/Ext.draw.Point} x
  37690. * @param {Number/Number[]/Object/Ext.draw.Point} y
  37691. * @return {Ext.draw.Point}
  37692. */
  37693. add: function(x, y) {
  37694. var fly = Ext.draw.Point.fly(x, y);
  37695. return new Ext.draw.Point(this.x + fly.x, this.y + fly.y);
  37696. },
  37697. /**
  37698. * Subtracts another vector from this one and returns the resulting vector
  37699. * without changing this vector.
  37700. * @param {Number/Number[]/Object/Ext.draw.Point} x
  37701. * @param {Number/Number[]/Object/Ext.draw.Point} y
  37702. * @return {Ext.draw.Point}
  37703. */
  37704. sub: function(x, y) {
  37705. var fly = Ext.draw.Point.fly(x, y);
  37706. return new Ext.draw.Point(this.x - fly.x, this.y - fly.y);
  37707. },
  37708. /**
  37709. * Returns the result of scalar multiplication of this vector by the given factor.
  37710. * This vector is not modified.
  37711. * @param {Number} n The factor.
  37712. * @return {Ext.draw.Point}
  37713. */
  37714. mul: function(n) {
  37715. return new Ext.draw.Point(this.x * n, this.y * n);
  37716. },
  37717. /**
  37718. * Returns a vector which coordinates are the result of division of this vector's
  37719. * coordinates by the given number. This vector is not modified.
  37720. * This vector is not modified.
  37721. * @param {Number} n The denominator.
  37722. * @return {Ext.draw.Point}
  37723. */
  37724. div: function(n) {
  37725. return new Ext.draw.Point(this.x / n, this.y / n);
  37726. },
  37727. /**
  37728. * Returns the dot product of this vector and the given vector.
  37729. * @param {Number/Number[]/Object/Ext.draw.Point} x
  37730. * @param {Number/Number[]/Object/Ext.draw.Point} y
  37731. * @return {Number}
  37732. */
  37733. dot: function(x, y) {
  37734. var fly = Ext.draw.Point.fly(x, y);
  37735. return this.x * fly.x + this.y * fly.y;
  37736. },
  37737. /**
  37738. * Checks whether coordinates of the point match those of the point provided.
  37739. * @param {Number/Number[]/Object/Ext.draw.Point} x
  37740. * @param {Number/Number[]/Object/Ext.draw.Point} y
  37741. * @return {Boolean}
  37742. */
  37743. equals: function(x, y) {
  37744. var fly = Ext.draw.Point.fly(x, y);
  37745. return this.x === fly.x && this.y === fly.y;
  37746. },
  37747. /**
  37748. * Rotates the point by the given angle. This point is not modified.
  37749. * @param {Number} angle The rotation angle.
  37750. * @param {Ext.draw.Point} [center] The center of rotation (optional). Defaults to origin.
  37751. * @return {Ext.draw.Point} The rotated point.
  37752. */
  37753. rotate: function(angle, center) {
  37754. var sin, cos, cx, cy, point;
  37755. if (this.angleUnits === 'degrees') {
  37756. angle = Ext.draw.Draw.rad(angle);
  37757. sin = Math.sin(angle);
  37758. cos = Math.cos(angle);
  37759. }
  37760. if (center) {
  37761. cx = center.x;
  37762. cy = center.y;
  37763. } else {
  37764. cx = 0;
  37765. cy = 0;
  37766. }
  37767. point = Ext.draw.Matrix.fly([
  37768. cos,
  37769. sin,
  37770. -sin,
  37771. cos,
  37772. cx - cos * cx + cy * sin,
  37773. cy - cos * cy + cx * -sin
  37774. ]).transformPoint(this);
  37775. return new Ext.draw.Point(point);
  37776. },
  37777. /**
  37778. * Transforms the point from one coordinate system to another
  37779. * using the transformation matrix provided. This point is not modified.
  37780. * @param {Ext.draw.Matrix/Number[]} matrix A trasformation matrix or its elements.
  37781. * @return {Ext.draw.Point}
  37782. */
  37783. transform: function(matrix) {
  37784. if (matrix && matrix.isMatrix) {
  37785. return new Ext.draw.Point(matrix.transformPoint(this));
  37786. } else if (arguments.length === 6) {
  37787. return new Ext.draw.Point(Ext.draw.Matrix.fly(arguments).transformPoint(this));
  37788. } else {
  37789. Ext.raise("Invalid parameters.");
  37790. }
  37791. },
  37792. /**
  37793. * Returns a new point with rounded x and y values. This point is not modified.
  37794. * @return {Ext.draw.Point}
  37795. */
  37796. round: function() {
  37797. return new Ext.draw.Point(Math.round(this.x), Math.round(this.y));
  37798. },
  37799. /**
  37800. * Returns a new point with ceiled x and y values. This point is not modified.
  37801. * @return {Ext.draw.Point}
  37802. */
  37803. ceil: function() {
  37804. return new Ext.draw.Point(Math.ceil(this.x), Math.ceil(this.y));
  37805. },
  37806. /**
  37807. * Returns a new point with floored x and y values. This point is not modified.
  37808. * @return {Ext.draw.Point}
  37809. */
  37810. floor: function() {
  37811. return new Ext.draw.Point(Math.floor(this.x), Math.floor(this.y));
  37812. },
  37813. /**
  37814. * Returns a new point with absolute values of the x and y values of this point.
  37815. * This point is not modified.
  37816. * @return {Ext.draw.Point}
  37817. */
  37818. abs: function(x, y) {
  37819. return new Ext.draw.Point(Math.abs(this.x), Math.abs(this.y));
  37820. },
  37821. /**
  37822. * Normalizes the vector by changing its length to 1 without changing its angle.
  37823. * The returned result is a normalized vector. This vector is not modified.
  37824. * @param {Number} [factor=1] Multiplication factor. Defaults to 1.
  37825. * @return {Ext.draw.Point}
  37826. */
  37827. normalize: function(factor) {
  37828. var x = this.x,
  37829. y = this.y,
  37830. k = (factor || 1) / Math.sqrt(x * x + y * y);
  37831. return new Ext.draw.Point(x * k, y * k);
  37832. },
  37833. /**
  37834. * Returns the vector from the point perpendicular to the line (shortest distance).
  37835. * Where line is specified using two points or the coordinates of those points.
  37836. * @param {Ext.draw.Point} p1
  37837. * @param {Ext.draw.Point} p2
  37838. * @return {Ext.draw.Point}
  37839. */
  37840. getDistanceToLine: function(p1, p2) {
  37841. if (arguments.length === 4) {
  37842. p1 = new Ext.draw.Point(arguments[0], arguments[1]);
  37843. p2 = new Ext.draw.Point(arguments[2], arguments[3]);
  37844. }
  37845. // See http://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Vector_formulation
  37846. var n = p2.sub(p1).normalize(),
  37847. pp1 = p1.sub(this);
  37848. return pp1.sub(n.mul(pp1.dot(n)));
  37849. },
  37850. /**
  37851. * Checks if both x and y coordinates of the point are zero.
  37852. * @return {Boolean}
  37853. */
  37854. isZero: function() {
  37855. return this.x === 0 && this.y === 0;
  37856. },
  37857. /**
  37858. * Checks if both x and y coordinates of the point are valid numbers.
  37859. * @return {Boolean}
  37860. */
  37861. isNumber: function() {
  37862. return Ext.isNumber(this.x) && Ext.isNumber(this.y);
  37863. }
  37864. });
  37865. /**
  37866. * A draw container {@link Ext.AbstractPlugin plugin} that adds ability to listen
  37867. * to sprite events. For example:
  37868. *
  37869. * var drawContainer = Ext.create('Ext.draw.Container', {
  37870. * plugins: {
  37871. * spriteevents: true
  37872. * },
  37873. * renderTo: Ext.getBody(),
  37874. * width: 200,
  37875. * height: 200,
  37876. * sprites: [{
  37877. * type: 'circle',
  37878. * fillStyle: '#79BB3F',
  37879. * r: 50,
  37880. * x: 100,
  37881. * y: 100
  37882. * }],
  37883. * listeners: {
  37884. * spriteclick: function (item, event) {
  37885. * var sprite = item && item.sprite;
  37886. * if (sprite) {
  37887. * sprite.setAttributes({fillStyle: 'red'});
  37888. sprite.getSurface().renderFrame();
  37889. * }
  37890. * }
  37891. * }
  37892. * });
  37893. */
  37894. Ext.define('Ext.draw.plugin.SpriteEvents', {
  37895. extend: 'Ext.plugin.Abstract',
  37896. alias: 'plugin.spriteevents',
  37897. requires: [
  37898. 'Ext.draw.overrides.hittest.All'
  37899. ],
  37900. /**
  37901. * @event spritemousemove
  37902. * Fires when the mouse is moved on a sprite.
  37903. * @param {Object} sprite
  37904. * @param {Event} event
  37905. */
  37906. /**
  37907. * @event spritemouseup
  37908. * Fires when a mouseup event occurs on a sprite.
  37909. * @param {Object} sprite
  37910. * @param {Event} event
  37911. */
  37912. /**
  37913. * @event spritemousedown
  37914. * Fires when a mousedown event occurs on a sprite.
  37915. * @param {Object} sprite
  37916. * @param {Event} event
  37917. */
  37918. /**
  37919. * @event spritemouseover
  37920. * Fires when the mouse enters a sprite.
  37921. * @param {Object} sprite
  37922. * @param {Event} event
  37923. */
  37924. /**
  37925. * @event spritemouseout
  37926. * Fires when the mouse exits a sprite.
  37927. * @param {Object} sprite
  37928. * @param {Event} event
  37929. */
  37930. /**
  37931. * @event spriteclick
  37932. * Fires when a click event occurs on a sprite.
  37933. * @param {Object} sprite
  37934. * @param {Event} event
  37935. */
  37936. /**
  37937. * @event spritedblclick
  37938. * Fires when a double click event occurs on a sprite.
  37939. * @param {Object} sprite
  37940. * @param {Event} event
  37941. */
  37942. /**
  37943. * @event spritetap
  37944. * Fires when a tap event occurs on a sprite.
  37945. * @param {Object} sprite
  37946. * @param {Event} event
  37947. */
  37948. mouseMoveEvents: {
  37949. mousemove: true,
  37950. mouseover: true,
  37951. mouseout: true
  37952. },
  37953. spriteMouseMoveEvents: {
  37954. spritemousemove: true,
  37955. spritemouseover: true,
  37956. spritemouseout: true
  37957. },
  37958. init: function(drawContainer) {
  37959. var handleEvent = 'handleEvent';
  37960. this.drawContainer = drawContainer;
  37961. drawContainer.addElementListener({
  37962. click: handleEvent,
  37963. dblclick: handleEvent,
  37964. mousedown: handleEvent,
  37965. mousemove: handleEvent,
  37966. mouseup: handleEvent,
  37967. mouseover: handleEvent,
  37968. mouseout: handleEvent,
  37969. // run our handlers before user code
  37970. priority: 1001,
  37971. scope: this
  37972. });
  37973. },
  37974. hasSpriteMouseMoveListeners: function() {
  37975. var listeners = this.drawContainer.hasListeners,
  37976. name;
  37977. for (name in this.spriteMouseMoveEvents) {
  37978. if (name in listeners) {
  37979. return true;
  37980. }
  37981. }
  37982. return false;
  37983. },
  37984. hitTestEvent: function(e) {
  37985. var items = this.drawContainer.getItems(),
  37986. surface, sprite, i;
  37987. for (i = items.length - 1; i >= 0; i--) {
  37988. surface = items.get(i);
  37989. sprite = surface.hitTestEvent(e);
  37990. if (sprite) {
  37991. return sprite;
  37992. }
  37993. }
  37994. return null;
  37995. },
  37996. handleEvent: function(e) {
  37997. var me = this,
  37998. drawContainer = me.drawContainer,
  37999. isMouseMoveEvent = e.type in me.mouseMoveEvents,
  38000. lastSprite = me.lastSprite,
  38001. sprite;
  38002. if (isMouseMoveEvent && !me.hasSpriteMouseMoveListeners()) {
  38003. return;
  38004. }
  38005. sprite = me.hitTestEvent(e);
  38006. if (isMouseMoveEvent && !Ext.Object.equals(sprite, lastSprite)) {
  38007. if (lastSprite) {
  38008. drawContainer.fireEvent('spritemouseout', lastSprite, e);
  38009. }
  38010. if (sprite) {
  38011. drawContainer.fireEvent('spritemouseover', sprite, e);
  38012. }
  38013. }
  38014. if (sprite) {
  38015. drawContainer.fireEvent('sprite' + e.type, sprite, e);
  38016. }
  38017. me.lastSprite = sprite;
  38018. }
  38019. });
  38020. /**
  38021. * The ItemInfo interaction allows displaying detailed information about a series data
  38022. * point in a popup panel.
  38023. *
  38024. * To attach this interaction to a chart, include an entry in the chart's
  38025. * {@link Ext.chart.AbstractChart#interactions interactions} config with the `iteminfo` type:
  38026. *
  38027. * new Ext.chart.AbstractChart({
  38028. * renderTo: Ext.getBody(),
  38029. * width: 800,
  38030. * height: 600,
  38031. * store: store1,
  38032. * axes: [ ...some axes options... ],
  38033. * series: [ ...some series options... ],
  38034. * interactions: [{
  38035. * type: 'iteminfo',
  38036. * listeners: {
  38037. * show: function(me, item, panel) {
  38038. * panel.setHtml('Stock Price: $' + item.record.get('price'));
  38039. * }
  38040. * }
  38041. * }]
  38042. * });
  38043. */
  38044. Ext.define('Ext.chart.interactions.ItemInfo', {
  38045. extend: 'Ext.chart.interactions.Abstract',
  38046. type: 'iteminfo',
  38047. alias: 'interaction.iteminfo',
  38048. /**
  38049. * @event show
  38050. * Fires when the info panel is shown.
  38051. * @param {Ext.chart.interactions.ItemInfo} this The interaction instance
  38052. * @param {Object} item The item whose info is being displayed
  38053. * @param {Ext.Panel} panel The panel for displaying the info
  38054. */
  38055. config: {
  38056. /**
  38057. * @cfg {Object} gestures
  38058. * Defines the gestures that should trigger the item info panel to be displayed.
  38059. */
  38060. gestures: {
  38061. tap: 'onInfoGesture'
  38062. },
  38063. /**
  38064. * @cfg {Object} panel
  38065. * An optional set of configuration overrides for the {@link Ext.Panel} that gets
  38066. * displayed. This object will be merged with the default panel configuration.
  38067. */
  38068. panel: {
  38069. modal: true,
  38070. centered: true,
  38071. width: 300,
  38072. height: 200,
  38073. scrollable: 'vertical',
  38074. hideOnMaskTap: true,
  38075. fullscreen: false,
  38076. hidden: false,
  38077. zIndex: 30
  38078. }
  38079. },
  38080. item: null,
  38081. applyPanel: function(panel, oldPanel) {
  38082. return Ext.factory(panel, 'Ext.Panel', oldPanel);
  38083. },
  38084. updatePanel: function(panel, oldPanel) {
  38085. if (panel) {
  38086. panel.on('hide', "reset", this);
  38087. }
  38088. if (oldPanel) {
  38089. oldPanel.un('hide', "reset", this);
  38090. }
  38091. },
  38092. onInfoGesture: function(e, element) {
  38093. var me = this,
  38094. panel = me.getPanel(),
  38095. item = me.getItemForEvent(e);
  38096. if (item) {
  38097. me.item = item;
  38098. me.fireEvent('show', me, item, panel);
  38099. Ext.Viewport.add(panel);
  38100. panel.show('pop');
  38101. item.series.setAttributesForItem(item, {
  38102. highlighted: true
  38103. });
  38104. me.sync();
  38105. }
  38106. return false;
  38107. },
  38108. reset: function() {
  38109. var me = this,
  38110. item = me.item;
  38111. if (item) {
  38112. item.series.setAttributesForItem(item, {
  38113. highlighted: false
  38114. });
  38115. me.item = null;
  38116. me.sync();
  38117. }
  38118. }
  38119. });