12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737207382073920740207412074220743207442074520746207472074820749207502075120752207532075420755207562075720758207592076020761207622076320764207652076620767207682076920770207712077220773207742077520776207772077820779207802078120782207832078420785207862078720788207892079020791207922079320794207952079620797207982079920800208012080220803208042080520806208072080820809208102081120812208132081420815208162081720818208192082020821208222082320824208252082620827208282082920830208312083220833208342083520836208372083820839208402084120842208432084420845208462084720848208492085020851208522085320854208552085620857208582085920860208612086220863208642086520866208672086820869208702087120872208732087420875208762087720878208792088020881208822088320884208852088620887208882088920890208912089220893208942089520896208972089820899209002090120902209032090420905209062090720908209092091020911209122091320914209152091620917209182091920920209212092220923209242092520926209272092820929209302093120932209332093420935209362093720938209392094020941209422094320944209452094620947209482094920950209512095220953209542095520956209572095820959209602096120962209632096420965209662096720968209692097020971209722097320974209752097620977209782097920980209812098220983209842098520986209872098820989209902099120992209932099420995209962099720998209992100021001210022100321004210052100621007210082100921010210112101221013210142101521016210172101821019210202102121022210232102421025210262102721028210292103021031210322103321034210352103621037210382103921040210412104221043210442104521046210472104821049210502105121052210532105421055210562105721058210592106021061210622106321064210652106621067210682106921070210712107221073210742107521076210772107821079210802108121082210832108421085210862108721088210892109021091210922109321094210952109621097210982109921100211012110221103211042110521106211072110821109211102111121112211132111421115211162111721118211192112021121211222112321124211252112621127211282112921130211312113221133211342113521136211372113821139211402114121142211432114421145211462114721148211492115021151211522115321154211552115621157211582115921160211612116221163211642116521166211672116821169211702117121172211732117421175211762117721178211792118021181211822118321184211852118621187211882118921190211912119221193211942119521196211972119821199212002120121202212032120421205212062120721208212092121021211212122121321214212152121621217212182121921220212212122221223212242122521226212272122821229212302123121232212332123421235212362123721238212392124021241212422124321244212452124621247212482124921250212512125221253212542125521256212572125821259212602126121262212632126421265212662126721268212692127021271212722127321274212752127621277212782127921280212812128221283212842128521286212872128821289212902129121292212932129421295212962129721298212992130021301213022130321304213052130621307213082130921310213112131221313213142131521316213172131821319213202132121322213232132421325213262132721328213292133021331213322133321334213352133621337213382133921340213412134221343213442134521346213472134821349213502135121352213532135421355213562135721358213592136021361213622136321364213652136621367213682136921370213712137221373213742137521376213772137821379213802138121382213832138421385213862138721388213892139021391213922139321394213952139621397213982139921400214012140221403214042140521406214072140821409214102141121412214132141421415214162141721418214192142021421214222142321424214252142621427214282142921430214312143221433214342143521436214372143821439214402144121442214432144421445214462144721448214492145021451214522145321454214552145621457214582145921460214612146221463214642146521466214672146821469214702147121472214732147421475214762147721478214792148021481214822148321484214852148621487214882148921490214912149221493214942149521496214972149821499215002150121502215032150421505215062150721508215092151021511215122151321514215152151621517215182151921520215212152221523215242152521526215272152821529215302153121532215332153421535215362153721538215392154021541215422154321544215452154621547215482154921550215512155221553215542155521556215572155821559215602156121562215632156421565215662156721568215692157021571215722157321574215752157621577215782157921580215812158221583215842158521586215872158821589215902159121592215932159421595215962159721598215992160021601216022160321604216052160621607216082160921610216112161221613216142161521616216172161821619216202162121622216232162421625216262162721628216292163021631216322163321634216352163621637216382163921640216412164221643216442164521646216472164821649216502165121652216532165421655216562165721658216592166021661216622166321664216652166621667216682166921670216712167221673216742167521676216772167821679216802168121682216832168421685216862168721688216892169021691216922169321694216952169621697216982169921700217012170221703217042170521706217072170821709217102171121712217132171421715217162171721718217192172021721217222172321724217252172621727217282172921730217312173221733217342173521736217372173821739217402174121742217432174421745217462174721748217492175021751217522175321754217552175621757217582175921760217612176221763217642176521766217672176821769217702177121772217732177421775217762177721778217792178021781217822178321784217852178621787217882178921790217912179221793217942179521796217972179821799218002180121802218032180421805218062180721808218092181021811218122181321814218152181621817218182181921820218212182221823218242182521826218272182821829218302183121832218332183421835218362183721838218392184021841218422184321844218452184621847218482184921850218512185221853218542185521856218572185821859218602186121862218632186421865218662186721868218692187021871218722187321874218752187621877218782187921880218812188221883218842188521886218872188821889218902189121892218932189421895218962189721898218992190021901219022190321904219052190621907219082190921910219112191221913219142191521916219172191821919219202192121922219232192421925219262192721928219292193021931219322193321934219352193621937219382193921940219412194221943219442194521946219472194821949219502195121952219532195421955219562195721958219592196021961219622196321964219652196621967219682196921970219712197221973219742197521976219772197821979219802198121982219832198421985219862198721988219892199021991219922199321994219952199621997219982199922000220012200222003220042200522006220072200822009220102201122012220132201422015220162201722018220192202022021220222202322024220252202622027220282202922030220312203222033220342203522036220372203822039220402204122042220432204422045220462204722048220492205022051220522205322054220552205622057220582205922060220612206222063220642206522066220672206822069220702207122072220732207422075220762207722078220792208022081220822208322084220852208622087220882208922090220912209222093220942209522096220972209822099221002210122102221032210422105221062210722108221092211022111221122211322114221152211622117221182211922120221212212222123221242212522126221272212822129221302213122132221332213422135221362213722138221392214022141221422214322144221452214622147221482214922150221512215222153221542215522156221572215822159221602216122162221632216422165221662216722168221692217022171221722217322174221752217622177221782217922180221812218222183221842218522186221872218822189221902219122192221932219422195221962219722198221992220022201222022220322204222052220622207222082220922210222112221222213222142221522216222172221822219222202222122222222232222422225222262222722228222292223022231222322223322234222352223622237222382223922240222412224222243222442224522246222472224822249222502225122252222532225422255222562225722258222592226022261222622226322264222652226622267222682226922270222712227222273222742227522276222772227822279222802228122282222832228422285222862228722288222892229022291222922229322294222952229622297222982229922300223012230222303223042230522306223072230822309223102231122312223132231422315223162231722318223192232022321223222232322324223252232622327223282232922330223312233222333223342233522336223372233822339223402234122342223432234422345223462234722348223492235022351223522235322354223552235622357223582235922360223612236222363223642236522366223672236822369223702237122372223732237422375223762237722378223792238022381223822238322384223852238622387223882238922390223912239222393223942239522396223972239822399224002240122402224032240422405224062240722408224092241022411224122241322414224152241622417224182241922420224212242222423224242242522426224272242822429224302243122432224332243422435224362243722438224392244022441224422244322444224452244622447224482244922450224512245222453224542245522456224572245822459224602246122462224632246422465224662246722468224692247022471224722247322474224752247622477224782247922480224812248222483224842248522486224872248822489224902249122492224932249422495224962249722498224992250022501225022250322504225052250622507225082250922510225112251222513225142251522516225172251822519225202252122522225232252422525225262252722528225292253022531225322253322534225352253622537225382253922540225412254222543225442254522546225472254822549225502255122552225532255422555225562255722558225592256022561225622256322564225652256622567225682256922570225712257222573225742257522576225772257822579225802258122582225832258422585225862258722588225892259022591225922259322594225952259622597225982259922600226012260222603226042260522606226072260822609226102261122612226132261422615226162261722618226192262022621226222262322624226252262622627226282262922630226312263222633226342263522636226372263822639226402264122642226432264422645226462264722648226492265022651226522265322654226552265622657226582265922660226612266222663226642266522666226672266822669226702267122672226732267422675226762267722678226792268022681226822268322684226852268622687226882268922690226912269222693226942269522696226972269822699227002270122702227032270422705227062270722708227092271022711227122271322714227152271622717227182271922720227212272222723227242272522726227272272822729227302273122732227332273422735227362273722738227392274022741227422274322744227452274622747227482274922750227512275222753227542275522756227572275822759227602276122762227632276422765227662276722768227692277022771227722277322774227752277622777227782277922780227812278222783227842278522786227872278822789227902279122792227932279422795227962279722798227992280022801228022280322804228052280622807228082280922810228112281222813228142281522816228172281822819228202282122822228232282422825228262282722828228292283022831228322283322834228352283622837228382283922840228412284222843228442284522846228472284822849228502285122852228532285422855228562285722858228592286022861228622286322864228652286622867228682286922870228712287222873228742287522876228772287822879228802288122882228832288422885228862288722888228892289022891228922289322894228952289622897228982289922900229012290222903229042290522906229072290822909229102291122912229132291422915229162291722918229192292022921229222292322924229252292622927229282292922930229312293222933229342293522936229372293822939229402294122942229432294422945229462294722948229492295022951229522295322954229552295622957229582295922960229612296222963229642296522966229672296822969229702297122972229732297422975229762297722978229792298022981229822298322984229852298622987229882298922990229912299222993229942299522996229972299822999230002300123002230032300423005230062300723008230092301023011230122301323014230152301623017230182301923020230212302223023230242302523026230272302823029230302303123032230332303423035230362303723038230392304023041230422304323044230452304623047230482304923050230512305223053230542305523056230572305823059230602306123062230632306423065230662306723068230692307023071230722307323074230752307623077230782307923080230812308223083230842308523086230872308823089230902309123092230932309423095230962309723098230992310023101231022310323104231052310623107231082310923110231112311223113231142311523116231172311823119231202312123122231232312423125231262312723128231292313023131231322313323134231352313623137231382313923140231412314223143231442314523146231472314823149231502315123152231532315423155231562315723158231592316023161231622316323164231652316623167231682316923170231712317223173231742317523176231772317823179231802318123182231832318423185231862318723188231892319023191231922319323194231952319623197231982319923200232012320223203232042320523206232072320823209232102321123212232132321423215232162321723218232192322023221232222322323224232252322623227232282322923230232312323223233232342323523236232372323823239232402324123242232432324423245232462324723248232492325023251232522325323254232552325623257232582325923260232612326223263232642326523266232672326823269232702327123272232732327423275232762327723278232792328023281232822328323284232852328623287232882328923290232912329223293232942329523296232972329823299233002330123302233032330423305233062330723308233092331023311233122331323314233152331623317233182331923320233212332223323233242332523326233272332823329233302333123332233332333423335233362333723338233392334023341233422334323344233452334623347233482334923350233512335223353233542335523356233572335823359233602336123362233632336423365233662336723368233692337023371233722337323374233752337623377233782337923380233812338223383233842338523386233872338823389233902339123392233932339423395233962339723398233992340023401234022340323404234052340623407234082340923410234112341223413234142341523416234172341823419234202342123422234232342423425234262342723428234292343023431234322343323434234352343623437234382343923440234412344223443234442344523446234472344823449234502345123452234532345423455234562345723458234592346023461234622346323464234652346623467234682346923470234712347223473234742347523476234772347823479234802348123482234832348423485234862348723488234892349023491234922349323494234952349623497234982349923500235012350223503235042350523506235072350823509235102351123512235132351423515235162351723518235192352023521235222352323524235252352623527235282352923530235312353223533235342353523536235372353823539235402354123542235432354423545235462354723548235492355023551235522355323554235552355623557235582355923560235612356223563235642356523566235672356823569235702357123572235732357423575235762357723578235792358023581235822358323584235852358623587235882358923590235912359223593235942359523596235972359823599236002360123602236032360423605236062360723608236092361023611236122361323614236152361623617236182361923620236212362223623236242362523626236272362823629236302363123632236332363423635236362363723638236392364023641236422364323644236452364623647236482364923650236512365223653236542365523656236572365823659236602366123662236632366423665236662366723668236692367023671236722367323674236752367623677236782367923680236812368223683236842368523686236872368823689236902369123692236932369423695236962369723698236992370023701237022370323704237052370623707237082370923710237112371223713237142371523716237172371823719237202372123722237232372423725237262372723728237292373023731237322373323734237352373623737237382373923740237412374223743237442374523746237472374823749237502375123752237532375423755237562375723758237592376023761237622376323764237652376623767237682376923770237712377223773237742377523776237772377823779237802378123782237832378423785237862378723788237892379023791237922379323794237952379623797237982379923800238012380223803238042380523806238072380823809238102381123812238132381423815238162381723818238192382023821238222382323824238252382623827238282382923830238312383223833238342383523836238372383823839238402384123842238432384423845238462384723848238492385023851238522385323854238552385623857238582385923860238612386223863238642386523866238672386823869238702387123872238732387423875238762387723878238792388023881238822388323884238852388623887238882388923890238912389223893238942389523896238972389823899239002390123902239032390423905239062390723908239092391023911239122391323914239152391623917239182391923920239212392223923239242392523926239272392823929239302393123932239332393423935239362393723938239392394023941239422394323944239452394623947239482394923950239512395223953239542395523956239572395823959239602396123962239632396423965239662396723968239692397023971239722397323974239752397623977239782397923980239812398223983239842398523986239872398823989239902399123992239932399423995239962399723998239992400024001240022400324004240052400624007240082400924010240112401224013240142401524016240172401824019240202402124022240232402424025240262402724028240292403024031240322403324034240352403624037240382403924040240412404224043240442404524046240472404824049240502405124052240532405424055240562405724058240592406024061240622406324064240652406624067240682406924070240712407224073240742407524076240772407824079240802408124082240832408424085240862408724088240892409024091240922409324094240952409624097240982409924100241012410224103241042410524106241072410824109241102411124112241132411424115241162411724118241192412024121241222412324124241252412624127241282412924130241312413224133241342413524136241372413824139241402414124142241432414424145241462414724148241492415024151241522415324154241552415624157241582415924160241612416224163241642416524166241672416824169241702417124172241732417424175241762417724178241792418024181241822418324184241852418624187241882418924190241912419224193241942419524196241972419824199242002420124202242032420424205242062420724208242092421024211242122421324214242152421624217242182421924220242212422224223242242422524226242272422824229242302423124232242332423424235242362423724238242392424024241242422424324244242452424624247242482424924250242512425224253242542425524256242572425824259242602426124262242632426424265242662426724268242692427024271242722427324274242752427624277242782427924280242812428224283242842428524286242872428824289242902429124292242932429424295242962429724298242992430024301243022430324304243052430624307243082430924310243112431224313243142431524316243172431824319243202432124322243232432424325243262432724328243292433024331243322433324334243352433624337243382433924340243412434224343243442434524346243472434824349243502435124352243532435424355243562435724358243592436024361243622436324364243652436624367243682436924370243712437224373243742437524376243772437824379243802438124382243832438424385243862438724388243892439024391243922439324394243952439624397243982439924400244012440224403244042440524406244072440824409244102441124412244132441424415244162441724418244192442024421244222442324424244252442624427244282442924430244312443224433244342443524436244372443824439244402444124442244432444424445244462444724448244492445024451244522445324454244552445624457244582445924460244612446224463244642446524466244672446824469244702447124472244732447424475244762447724478244792448024481244822448324484244852448624487244882448924490244912449224493244942449524496244972449824499245002450124502245032450424505245062450724508245092451024511245122451324514245152451624517245182451924520245212452224523245242452524526245272452824529245302453124532245332453424535245362453724538245392454024541245422454324544245452454624547245482454924550245512455224553245542455524556245572455824559245602456124562245632456424565245662456724568245692457024571245722457324574245752457624577245782457924580245812458224583245842458524586245872458824589245902459124592245932459424595245962459724598245992460024601246022460324604246052460624607246082460924610246112461224613246142461524616246172461824619246202462124622246232462424625246262462724628246292463024631246322463324634246352463624637246382463924640246412464224643246442464524646246472464824649246502465124652246532465424655246562465724658246592466024661246622466324664246652466624667246682466924670246712467224673246742467524676246772467824679246802468124682246832468424685246862468724688246892469024691246922469324694246952469624697246982469924700247012470224703247042470524706247072470824709247102471124712247132471424715247162471724718247192472024721247222472324724247252472624727247282472924730247312473224733247342473524736247372473824739247402474124742247432474424745247462474724748247492475024751247522475324754247552475624757247582475924760247612476224763247642476524766247672476824769247702477124772247732477424775247762477724778247792478024781247822478324784247852478624787247882478924790247912479224793247942479524796247972479824799248002480124802248032480424805248062480724808248092481024811248122481324814248152481624817248182481924820248212482224823248242482524826248272482824829248302483124832248332483424835248362483724838248392484024841248422484324844248452484624847248482484924850248512485224853248542485524856248572485824859248602486124862248632486424865248662486724868248692487024871248722487324874248752487624877248782487924880248812488224883248842488524886248872488824889248902489124892248932489424895248962489724898248992490024901249022490324904249052490624907249082490924910249112491224913249142491524916249172491824919249202492124922249232492424925249262492724928249292493024931249322493324934249352493624937249382493924940249412494224943249442494524946249472494824949249502495124952249532495424955249562495724958249592496024961249622496324964249652496624967249682496924970249712497224973249742497524976249772497824979249802498124982249832498424985249862498724988249892499024991249922499324994249952499624997249982499925000250012500225003250042500525006250072500825009250102501125012250132501425015250162501725018250192502025021250222502325024250252502625027250282502925030250312503225033250342503525036250372503825039250402504125042250432504425045250462504725048250492505025051250522505325054250552505625057250582505925060250612506225063250642506525066250672506825069250702507125072250732507425075250762507725078250792508025081250822508325084250852508625087250882508925090250912509225093250942509525096250972509825099251002510125102251032510425105251062510725108251092511025111251122511325114251152511625117251182511925120251212512225123251242512525126251272512825129251302513125132251332513425135251362513725138251392514025141251422514325144251452514625147251482514925150251512515225153251542515525156251572515825159251602516125162251632516425165251662516725168251692517025171251722517325174251752517625177251782517925180251812518225183251842518525186251872518825189251902519125192251932519425195251962519725198251992520025201252022520325204252052520625207252082520925210252112521225213252142521525216252172521825219252202522125222252232522425225252262522725228252292523025231252322523325234252352523625237252382523925240252412524225243252442524525246252472524825249252502525125252252532525425255252562525725258252592526025261252622526325264252652526625267252682526925270252712527225273252742527525276252772527825279252802528125282252832528425285252862528725288252892529025291252922529325294252952529625297252982529925300253012530225303253042530525306253072530825309253102531125312253132531425315253162531725318253192532025321253222532325324253252532625327253282532925330253312533225333253342533525336253372533825339253402534125342253432534425345253462534725348253492535025351253522535325354253552535625357253582535925360253612536225363253642536525366253672536825369253702537125372253732537425375253762537725378253792538025381253822538325384253852538625387253882538925390253912539225393253942539525396253972539825399254002540125402254032540425405254062540725408254092541025411254122541325414254152541625417254182541925420254212542225423254242542525426254272542825429254302543125432254332543425435254362543725438254392544025441254422544325444254452544625447254482544925450254512545225453254542545525456254572545825459254602546125462254632546425465254662546725468254692547025471254722547325474254752547625477254782547925480254812548225483254842548525486254872548825489254902549125492254932549425495254962549725498254992550025501255022550325504255052550625507255082550925510255112551225513255142551525516255172551825519255202552125522255232552425525255262552725528255292553025531255322553325534255352553625537255382553925540255412554225543255442554525546255472554825549255502555125552255532555425555255562555725558255592556025561255622556325564255652556625567255682556925570255712557225573255742557525576255772557825579255802558125582255832558425585255862558725588255892559025591255922559325594255952559625597255982559925600256012560225603256042560525606256072560825609256102561125612256132561425615256162561725618256192562025621256222562325624256252562625627256282562925630256312563225633256342563525636256372563825639256402564125642256432564425645256462564725648256492565025651256522565325654256552565625657256582565925660256612566225663256642566525666256672566825669256702567125672256732567425675256762567725678256792568025681256822568325684256852568625687256882568925690256912569225693256942569525696256972569825699257002570125702257032570425705257062570725708257092571025711257122571325714257152571625717257182571925720257212572225723257242572525726257272572825729257302573125732257332573425735257362573725738257392574025741257422574325744257452574625747257482574925750257512575225753257542575525756257572575825759257602576125762257632576425765257662576725768257692577025771257722577325774257752577625777257782577925780257812578225783257842578525786257872578825789257902579125792257932579425795257962579725798257992580025801258022580325804258052580625807258082580925810258112581225813258142581525816258172581825819258202582125822258232582425825258262582725828258292583025831258322583325834258352583625837258382583925840258412584225843258442584525846258472584825849258502585125852258532585425855258562585725858258592586025861258622586325864258652586625867258682586925870258712587225873258742587525876258772587825879258802588125882258832588425885258862588725888258892589025891258922589325894258952589625897258982589925900259012590225903259042590525906259072590825909259102591125912259132591425915259162591725918259192592025921259222592325924259252592625927259282592925930259312593225933259342593525936259372593825939259402594125942259432594425945259462594725948259492595025951259522595325954259552595625957259582595925960259612596225963259642596525966259672596825969259702597125972259732597425975259762597725978259792598025981259822598325984259852598625987259882598925990259912599225993259942599525996259972599825999260002600126002260032600426005260062600726008260092601026011260122601326014260152601626017260182601926020260212602226023260242602526026260272602826029260302603126032260332603426035260362603726038260392604026041260422604326044260452604626047260482604926050260512605226053260542605526056260572605826059260602606126062260632606426065260662606726068260692607026071260722607326074260752607626077260782607926080260812608226083260842608526086260872608826089260902609126092260932609426095260962609726098260992610026101261022610326104261052610626107261082610926110261112611226113261142611526116261172611826119261202612126122261232612426125261262612726128261292613026131261322613326134261352613626137261382613926140261412614226143261442614526146261472614826149261502615126152261532615426155261562615726158261592616026161261622616326164261652616626167261682616926170261712617226173261742617526176261772617826179261802618126182261832618426185261862618726188261892619026191261922619326194261952619626197261982619926200262012620226203262042620526206262072620826209262102621126212262132621426215262162621726218262192622026221262222622326224262252622626227262282622926230262312623226233262342623526236262372623826239262402624126242262432624426245262462624726248262492625026251262522625326254262552625626257262582625926260262612626226263262642626526266262672626826269262702627126272262732627426275262762627726278262792628026281262822628326284262852628626287262882628926290262912629226293262942629526296262972629826299263002630126302263032630426305263062630726308263092631026311263122631326314263152631626317263182631926320263212632226323263242632526326263272632826329263302633126332263332633426335263362633726338263392634026341263422634326344263452634626347263482634926350263512635226353263542635526356263572635826359263602636126362263632636426365263662636726368263692637026371263722637326374263752637626377263782637926380263812638226383263842638526386263872638826389263902639126392263932639426395263962639726398263992640026401264022640326404264052640626407264082640926410264112641226413264142641526416264172641826419264202642126422264232642426425264262642726428264292643026431264322643326434264352643626437264382643926440264412644226443264442644526446264472644826449264502645126452264532645426455264562645726458264592646026461264622646326464264652646626467264682646926470264712647226473264742647526476264772647826479264802648126482264832648426485264862648726488264892649026491264922649326494264952649626497264982649926500265012650226503265042650526506265072650826509265102651126512265132651426515265162651726518265192652026521265222652326524265252652626527265282652926530265312653226533265342653526536265372653826539265402654126542265432654426545265462654726548265492655026551265522655326554265552655626557265582655926560265612656226563265642656526566265672656826569265702657126572265732657426575265762657726578265792658026581265822658326584265852658626587265882658926590265912659226593265942659526596265972659826599266002660126602266032660426605266062660726608266092661026611266122661326614266152661626617266182661926620266212662226623266242662526626266272662826629266302663126632266332663426635266362663726638266392664026641266422664326644266452664626647266482664926650266512665226653266542665526656266572665826659266602666126662266632666426665266662666726668266692667026671266722667326674266752667626677266782667926680266812668226683266842668526686266872668826689266902669126692266932669426695266962669726698266992670026701267022670326704267052670626707267082670926710267112671226713267142671526716267172671826719267202672126722267232672426725267262672726728267292673026731267322673326734267352673626737267382673926740267412674226743267442674526746267472674826749267502675126752267532675426755267562675726758267592676026761267622676326764267652676626767267682676926770267712677226773267742677526776267772677826779267802678126782267832678426785267862678726788267892679026791267922679326794267952679626797267982679926800268012680226803268042680526806268072680826809268102681126812268132681426815268162681726818268192682026821268222682326824268252682626827268282682926830268312683226833268342683526836268372683826839268402684126842268432684426845268462684726848268492685026851268522685326854268552685626857268582685926860268612686226863268642686526866268672686826869268702687126872268732687426875268762687726878268792688026881268822688326884268852688626887268882688926890268912689226893268942689526896268972689826899269002690126902269032690426905269062690726908269092691026911269122691326914269152691626917269182691926920269212692226923269242692526926269272692826929269302693126932269332693426935269362693726938269392694026941269422694326944269452694626947269482694926950269512695226953269542695526956269572695826959269602696126962269632696426965269662696726968269692697026971269722697326974269752697626977269782697926980269812698226983269842698526986269872698826989269902699126992269932699426995269962699726998269992700027001270022700327004270052700627007270082700927010270112701227013270142701527016270172701827019270202702127022270232702427025270262702727028270292703027031270322703327034270352703627037270382703927040270412704227043270442704527046270472704827049270502705127052270532705427055270562705727058270592706027061270622706327064270652706627067270682706927070270712707227073270742707527076270772707827079270802708127082270832708427085270862708727088270892709027091270922709327094270952709627097270982709927100271012710227103271042710527106271072710827109271102711127112271132711427115271162711727118271192712027121271222712327124271252712627127271282712927130271312713227133271342713527136271372713827139271402714127142271432714427145271462714727148271492715027151271522715327154271552715627157271582715927160271612716227163271642716527166271672716827169271702717127172271732717427175271762717727178271792718027181271822718327184271852718627187271882718927190271912719227193271942719527196271972719827199272002720127202272032720427205272062720727208272092721027211272122721327214272152721627217272182721927220272212722227223272242722527226272272722827229272302723127232272332723427235272362723727238272392724027241272422724327244272452724627247272482724927250272512725227253272542725527256272572725827259272602726127262272632726427265272662726727268272692727027271272722727327274272752727627277272782727927280272812728227283272842728527286272872728827289272902729127292272932729427295272962729727298272992730027301273022730327304273052730627307273082730927310273112731227313273142731527316273172731827319273202732127322273232732427325273262732727328273292733027331273322733327334273352733627337273382733927340273412734227343273442734527346273472734827349273502735127352273532735427355273562735727358273592736027361273622736327364273652736627367273682736927370273712737227373273742737527376273772737827379273802738127382273832738427385273862738727388273892739027391273922739327394273952739627397273982739927400274012740227403274042740527406274072740827409274102741127412274132741427415274162741727418274192742027421274222742327424274252742627427274282742927430274312743227433274342743527436274372743827439274402744127442274432744427445274462744727448274492745027451274522745327454274552745627457274582745927460274612746227463274642746527466274672746827469274702747127472274732747427475274762747727478274792748027481274822748327484274852748627487274882748927490274912749227493274942749527496274972749827499275002750127502275032750427505275062750727508275092751027511275122751327514275152751627517275182751927520275212752227523275242752527526275272752827529275302753127532275332753427535275362753727538275392754027541275422754327544275452754627547275482754927550275512755227553275542755527556275572755827559275602756127562275632756427565275662756727568275692757027571275722757327574275752757627577275782757927580275812758227583275842758527586275872758827589275902759127592275932759427595275962759727598275992760027601276022760327604276052760627607276082760927610276112761227613276142761527616276172761827619276202762127622276232762427625276262762727628276292763027631276322763327634276352763627637276382763927640276412764227643276442764527646276472764827649276502765127652276532765427655276562765727658276592766027661276622766327664276652766627667276682766927670276712767227673276742767527676276772767827679276802768127682276832768427685276862768727688276892769027691276922769327694276952769627697276982769927700277012770227703277042770527706277072770827709277102771127712277132771427715277162771727718277192772027721277222772327724277252772627727277282772927730277312773227733277342773527736277372773827739277402774127742277432774427745277462774727748277492775027751277522775327754277552775627757277582775927760277612776227763277642776527766277672776827769277702777127772277732777427775277762777727778277792778027781277822778327784277852778627787277882778927790277912779227793277942779527796277972779827799278002780127802278032780427805278062780727808278092781027811278122781327814278152781627817278182781927820278212782227823278242782527826278272782827829278302783127832278332783427835278362783727838278392784027841278422784327844278452784627847278482784927850278512785227853278542785527856278572785827859278602786127862278632786427865278662786727868278692787027871278722787327874278752787627877278782787927880278812788227883278842788527886278872788827889278902789127892278932789427895278962789727898278992790027901279022790327904279052790627907279082790927910279112791227913279142791527916279172791827919279202792127922279232792427925279262792727928279292793027931279322793327934279352793627937279382793927940279412794227943279442794527946279472794827949279502795127952279532795427955279562795727958279592796027961279622796327964279652796627967279682796927970279712797227973279742797527976279772797827979279802798127982279832798427985279862798727988279892799027991279922799327994279952799627997279982799928000280012800228003280042800528006280072800828009280102801128012280132801428015280162801728018280192802028021280222802328024280252802628027280282802928030280312803228033280342803528036280372803828039280402804128042280432804428045280462804728048280492805028051280522805328054280552805628057280582805928060280612806228063280642806528066280672806828069280702807128072280732807428075280762807728078280792808028081280822808328084280852808628087280882808928090280912809228093280942809528096280972809828099281002810128102281032810428105281062810728108281092811028111281122811328114281152811628117281182811928120281212812228123281242812528126281272812828129281302813128132281332813428135281362813728138281392814028141281422814328144281452814628147281482814928150281512815228153281542815528156281572815828159281602816128162281632816428165281662816728168281692817028171281722817328174281752817628177281782817928180281812818228183281842818528186281872818828189281902819128192281932819428195281962819728198281992820028201282022820328204282052820628207282082820928210282112821228213282142821528216282172821828219282202822128222282232822428225282262822728228282292823028231282322823328234282352823628237282382823928240282412824228243282442824528246282472824828249282502825128252282532825428255282562825728258282592826028261282622826328264282652826628267282682826928270282712827228273282742827528276282772827828279282802828128282282832828428285282862828728288282892829028291282922829328294282952829628297282982829928300283012830228303283042830528306283072830828309283102831128312283132831428315283162831728318283192832028321283222832328324283252832628327283282832928330283312833228333283342833528336283372833828339283402834128342283432834428345283462834728348283492835028351283522835328354283552835628357283582835928360283612836228363283642836528366283672836828369283702837128372283732837428375283762837728378283792838028381283822838328384283852838628387283882838928390283912839228393283942839528396283972839828399284002840128402284032840428405284062840728408284092841028411284122841328414284152841628417284182841928420284212842228423284242842528426284272842828429284302843128432284332843428435284362843728438284392844028441284422844328444284452844628447284482844928450284512845228453284542845528456284572845828459284602846128462284632846428465284662846728468284692847028471284722847328474284752847628477284782847928480284812848228483284842848528486284872848828489284902849128492284932849428495284962849728498284992850028501285022850328504285052850628507285082850928510285112851228513285142851528516285172851828519285202852128522285232852428525285262852728528285292853028531285322853328534285352853628537285382853928540285412854228543285442854528546285472854828549285502855128552285532855428555285562855728558285592856028561285622856328564285652856628567285682856928570285712857228573285742857528576285772857828579285802858128582285832858428585285862858728588285892859028591285922859328594285952859628597285982859928600286012860228603286042860528606286072860828609286102861128612286132861428615286162861728618286192862028621286222862328624286252862628627286282862928630286312863228633286342863528636286372863828639286402864128642286432864428645286462864728648286492865028651286522865328654286552865628657286582865928660286612866228663286642866528666286672866828669286702867128672286732867428675286762867728678286792868028681286822868328684286852868628687286882868928690286912869228693286942869528696286972869828699287002870128702287032870428705287062870728708287092871028711287122871328714287152871628717287182871928720287212872228723287242872528726287272872828729287302873128732287332873428735287362873728738287392874028741287422874328744287452874628747287482874928750287512875228753287542875528756287572875828759287602876128762287632876428765287662876728768287692877028771287722877328774287752877628777287782877928780287812878228783287842878528786287872878828789287902879128792287932879428795287962879728798287992880028801288022880328804288052880628807288082880928810288112881228813288142881528816288172881828819288202882128822288232882428825288262882728828288292883028831288322883328834288352883628837288382883928840288412884228843288442884528846288472884828849288502885128852288532885428855288562885728858288592886028861288622886328864288652886628867288682886928870288712887228873288742887528876288772887828879288802888128882288832888428885288862888728888288892889028891288922889328894288952889628897288982889928900289012890228903289042890528906289072890828909289102891128912289132891428915289162891728918289192892028921289222892328924289252892628927289282892928930289312893228933289342893528936289372893828939289402894128942289432894428945289462894728948289492895028951289522895328954289552895628957289582895928960289612896228963289642896528966289672896828969289702897128972289732897428975289762897728978289792898028981289822898328984289852898628987289882898928990289912899228993289942899528996289972899828999290002900129002290032900429005290062900729008290092901029011290122901329014290152901629017290182901929020290212902229023290242902529026290272902829029290302903129032290332903429035290362903729038290392904029041290422904329044290452904629047290482904929050290512905229053290542905529056290572905829059290602906129062290632906429065290662906729068290692907029071290722907329074290752907629077290782907929080290812908229083290842908529086290872908829089290902909129092290932909429095290962909729098290992910029101291022910329104291052910629107291082910929110291112911229113291142911529116291172911829119291202912129122291232912429125291262912729128291292913029131291322913329134291352913629137291382913929140291412914229143291442914529146291472914829149291502915129152291532915429155291562915729158291592916029161291622916329164291652916629167291682916929170291712917229173291742917529176291772917829179291802918129182291832918429185291862918729188291892919029191291922919329194291952919629197291982919929200292012920229203292042920529206292072920829209292102921129212292132921429215292162921729218292192922029221292222922329224292252922629227292282922929230292312923229233292342923529236292372923829239292402924129242292432924429245292462924729248292492925029251292522925329254292552925629257292582925929260292612926229263292642926529266292672926829269292702927129272292732927429275292762927729278292792928029281292822928329284292852928629287292882928929290292912929229293292942929529296292972929829299293002930129302293032930429305293062930729308293092931029311293122931329314293152931629317293182931929320293212932229323293242932529326293272932829329293302933129332293332933429335293362933729338293392934029341293422934329344293452934629347293482934929350293512935229353293542935529356293572935829359293602936129362293632936429365293662936729368293692937029371293722937329374293752937629377293782937929380293812938229383293842938529386293872938829389293902939129392293932939429395293962939729398293992940029401294022940329404294052940629407294082940929410294112941229413294142941529416294172941829419294202942129422294232942429425294262942729428294292943029431294322943329434294352943629437294382943929440294412944229443294442944529446294472944829449294502945129452294532945429455294562945729458294592946029461294622946329464294652946629467294682946929470294712947229473294742947529476294772947829479294802948129482294832948429485294862948729488294892949029491294922949329494294952949629497294982949929500295012950229503295042950529506295072950829509295102951129512295132951429515295162951729518295192952029521295222952329524295252952629527295282952929530295312953229533295342953529536295372953829539295402954129542295432954429545295462954729548295492955029551295522955329554295552955629557295582955929560295612956229563295642956529566295672956829569295702957129572295732957429575295762957729578295792958029581295822958329584295852958629587295882958929590295912959229593295942959529596295972959829599296002960129602296032960429605296062960729608296092961029611296122961329614296152961629617296182961929620296212962229623296242962529626296272962829629296302963129632296332963429635296362963729638296392964029641296422964329644296452964629647296482964929650296512965229653296542965529656296572965829659296602966129662296632966429665296662966729668296692967029671296722967329674296752967629677296782967929680296812968229683296842968529686296872968829689296902969129692296932969429695296962969729698296992970029701297022970329704297052970629707297082970929710297112971229713297142971529716297172971829719297202972129722297232972429725297262972729728297292973029731297322973329734297352973629737297382973929740297412974229743297442974529746297472974829749297502975129752297532975429755297562975729758297592976029761297622976329764297652976629767297682976929770297712977229773297742977529776297772977829779297802978129782297832978429785297862978729788297892979029791297922979329794297952979629797297982979929800298012980229803298042980529806298072980829809298102981129812298132981429815298162981729818298192982029821298222982329824298252982629827298282982929830298312983229833298342983529836298372983829839298402984129842298432984429845298462984729848298492985029851298522985329854298552985629857298582985929860298612986229863298642986529866298672986829869298702987129872298732987429875298762987729878298792988029881298822988329884298852988629887298882988929890298912989229893298942989529896298972989829899299002990129902299032990429905299062990729908299092991029911299122991329914299152991629917299182991929920299212992229923299242992529926299272992829929299302993129932299332993429935299362993729938299392994029941299422994329944299452994629947299482994929950299512995229953299542995529956299572995829959299602996129962299632996429965299662996729968299692997029971299722997329974299752997629977299782997929980299812998229983299842998529986299872998829989299902999129992299932999429995299962999729998299993000030001300023000330004300053000630007300083000930010300113001230013300143001530016300173001830019300203002130022300233002430025300263002730028300293003030031300323003330034300353003630037300383003930040300413004230043300443004530046300473004830049300503005130052300533005430055300563005730058300593006030061300623006330064300653006630067300683006930070300713007230073300743007530076300773007830079300803008130082300833008430085300863008730088300893009030091300923009330094300953009630097300983009930100301013010230103301043010530106301073010830109301103011130112301133011430115301163011730118301193012030121301223012330124301253012630127301283012930130301313013230133301343013530136301373013830139301403014130142301433014430145301463014730148301493015030151301523015330154301553015630157301583015930160301613016230163301643016530166301673016830169301703017130172301733017430175301763017730178301793018030181301823018330184301853018630187301883018930190301913019230193301943019530196301973019830199302003020130202302033020430205302063020730208302093021030211302123021330214302153021630217302183021930220302213022230223302243022530226302273022830229302303023130232302333023430235302363023730238302393024030241302423024330244302453024630247302483024930250302513025230253302543025530256302573025830259302603026130262302633026430265302663026730268302693027030271302723027330274302753027630277302783027930280302813028230283302843028530286302873028830289302903029130292302933029430295302963029730298302993030030301303023030330304303053030630307303083030930310303113031230313303143031530316303173031830319303203032130322303233032430325303263032730328303293033030331303323033330334303353033630337303383033930340303413034230343303443034530346303473034830349303503035130352303533035430355303563035730358303593036030361303623036330364303653036630367303683036930370303713037230373303743037530376303773037830379303803038130382303833038430385303863038730388303893039030391303923039330394303953039630397303983039930400304013040230403304043040530406304073040830409304103041130412304133041430415304163041730418304193042030421304223042330424304253042630427304283042930430304313043230433304343043530436304373043830439304403044130442304433044430445304463044730448304493045030451304523045330454304553045630457304583045930460304613046230463304643046530466304673046830469304703047130472304733047430475304763047730478304793048030481304823048330484304853048630487304883048930490304913049230493304943049530496304973049830499305003050130502305033050430505305063050730508305093051030511305123051330514305153051630517305183051930520305213052230523305243052530526305273052830529305303053130532305333053430535305363053730538305393054030541305423054330544305453054630547305483054930550305513055230553305543055530556305573055830559305603056130562305633056430565305663056730568305693057030571305723057330574305753057630577305783057930580305813058230583305843058530586305873058830589305903059130592305933059430595305963059730598305993060030601306023060330604306053060630607306083060930610306113061230613306143061530616306173061830619306203062130622306233062430625306263062730628306293063030631306323063330634306353063630637306383063930640306413064230643306443064530646306473064830649306503065130652306533065430655306563065730658306593066030661306623066330664306653066630667306683066930670306713067230673306743067530676306773067830679306803068130682306833068430685306863068730688306893069030691306923069330694306953069630697306983069930700307013070230703307043070530706307073070830709307103071130712307133071430715307163071730718307193072030721307223072330724307253072630727307283072930730307313073230733307343073530736307373073830739307403074130742307433074430745307463074730748307493075030751307523075330754307553075630757307583075930760307613076230763307643076530766307673076830769307703077130772307733077430775307763077730778307793078030781307823078330784307853078630787307883078930790307913079230793307943079530796307973079830799308003080130802308033080430805308063080730808308093081030811308123081330814308153081630817308183081930820308213082230823308243082530826308273082830829308303083130832308333083430835308363083730838308393084030841308423084330844308453084630847308483084930850308513085230853308543085530856308573085830859308603086130862308633086430865308663086730868308693087030871308723087330874308753087630877308783087930880308813088230883308843088530886308873088830889308903089130892308933089430895308963089730898308993090030901309023090330904309053090630907309083090930910309113091230913309143091530916309173091830919309203092130922309233092430925309263092730928309293093030931309323093330934309353093630937309383093930940309413094230943309443094530946309473094830949309503095130952309533095430955309563095730958309593096030961309623096330964309653096630967309683096930970309713097230973309743097530976309773097830979309803098130982309833098430985309863098730988309893099030991309923099330994309953099630997309983099931000310013100231003310043100531006310073100831009310103101131012310133101431015310163101731018310193102031021310223102331024310253102631027310283102931030310313103231033310343103531036310373103831039310403104131042310433104431045310463104731048310493105031051310523105331054310553105631057310583105931060310613106231063310643106531066310673106831069310703107131072310733107431075310763107731078310793108031081310823108331084310853108631087310883108931090310913109231093310943109531096310973109831099311003110131102311033110431105311063110731108311093111031111311123111331114311153111631117311183111931120311213112231123311243112531126311273112831129311303113131132311333113431135311363113731138311393114031141311423114331144311453114631147311483114931150311513115231153311543115531156311573115831159311603116131162311633116431165311663116731168311693117031171311723117331174311753117631177311783117931180311813118231183311843118531186311873118831189311903119131192311933119431195311963119731198311993120031201312023120331204312053120631207312083120931210312113121231213312143121531216312173121831219312203122131222312233122431225312263122731228312293123031231312323123331234312353123631237312383123931240312413124231243312443124531246312473124831249312503125131252312533125431255312563125731258312593126031261312623126331264312653126631267312683126931270312713127231273312743127531276312773127831279312803128131282312833128431285312863128731288312893129031291312923129331294312953129631297312983129931300313013130231303313043130531306313073130831309313103131131312313133131431315313163131731318313193132031321313223132331324313253132631327313283132931330313313133231333313343133531336313373133831339313403134131342313433134431345313463134731348313493135031351313523135331354313553135631357313583135931360313613136231363313643136531366313673136831369313703137131372313733137431375313763137731378313793138031381313823138331384313853138631387313883138931390313913139231393313943139531396313973139831399314003140131402314033140431405314063140731408314093141031411314123141331414314153141631417314183141931420314213142231423314243142531426314273142831429314303143131432314333143431435314363143731438314393144031441314423144331444314453144631447314483144931450314513145231453314543145531456314573145831459314603146131462314633146431465314663146731468314693147031471314723147331474314753147631477314783147931480314813148231483314843148531486314873148831489314903149131492314933149431495314963149731498314993150031501315023150331504315053150631507315083150931510315113151231513315143151531516315173151831519315203152131522315233152431525315263152731528315293153031531315323153331534315353153631537315383153931540315413154231543315443154531546315473154831549315503155131552315533155431555315563155731558315593156031561315623156331564315653156631567315683156931570315713157231573315743157531576315773157831579315803158131582315833158431585315863158731588315893159031591315923159331594315953159631597315983159931600316013160231603316043160531606316073160831609316103161131612316133161431615316163161731618316193162031621316223162331624316253162631627316283162931630316313163231633316343163531636316373163831639316403164131642316433164431645316463164731648316493165031651316523165331654316553165631657316583165931660316613166231663316643166531666316673166831669316703167131672316733167431675316763167731678316793168031681316823168331684316853168631687316883168931690316913169231693316943169531696316973169831699317003170131702317033170431705317063170731708317093171031711317123171331714317153171631717317183171931720317213172231723317243172531726317273172831729317303173131732317333173431735317363173731738317393174031741317423174331744317453174631747317483174931750317513175231753317543175531756317573175831759317603176131762317633176431765317663176731768317693177031771317723177331774317753177631777317783177931780317813178231783317843178531786317873178831789317903179131792317933179431795317963179731798317993180031801318023180331804318053180631807318083180931810318113181231813318143181531816318173181831819318203182131822318233182431825318263182731828318293183031831318323183331834318353183631837318383183931840318413184231843318443184531846318473184831849318503185131852318533185431855318563185731858318593186031861318623186331864318653186631867318683186931870318713187231873318743187531876318773187831879318803188131882318833188431885318863188731888318893189031891318923189331894318953189631897318983189931900319013190231903319043190531906319073190831909319103191131912319133191431915319163191731918319193192031921319223192331924319253192631927319283192931930319313193231933319343193531936319373193831939319403194131942319433194431945319463194731948319493195031951319523195331954319553195631957319583195931960319613196231963319643196531966319673196831969319703197131972319733197431975319763197731978319793198031981319823198331984319853198631987319883198931990319913199231993319943199531996319973199831999320003200132002320033200432005320063200732008320093201032011320123201332014320153201632017320183201932020320213202232023320243202532026320273202832029320303203132032320333203432035320363203732038320393204032041320423204332044320453204632047320483204932050320513205232053320543205532056320573205832059320603206132062320633206432065320663206732068320693207032071320723207332074320753207632077320783207932080320813208232083320843208532086320873208832089320903209132092320933209432095320963209732098320993210032101321023210332104321053210632107321083210932110321113211232113321143211532116321173211832119321203212132122321233212432125321263212732128321293213032131321323213332134321353213632137321383213932140321413214232143321443214532146321473214832149321503215132152321533215432155321563215732158321593216032161321623216332164321653216632167321683216932170321713217232173321743217532176321773217832179321803218132182321833218432185321863218732188321893219032191321923219332194321953219632197321983219932200322013220232203322043220532206322073220832209322103221132212322133221432215322163221732218322193222032221322223222332224322253222632227322283222932230322313223232233322343223532236322373223832239322403224132242322433224432245322463224732248322493225032251322523225332254322553225632257322583225932260322613226232263322643226532266322673226832269322703227132272322733227432275322763227732278322793228032281322823228332284322853228632287322883228932290322913229232293322943229532296322973229832299323003230132302323033230432305323063230732308323093231032311323123231332314323153231632317323183231932320323213232232323323243232532326323273232832329323303233132332323333233432335323363233732338323393234032341323423234332344323453234632347323483234932350323513235232353323543235532356323573235832359323603236132362323633236432365323663236732368323693237032371323723237332374323753237632377323783237932380323813238232383323843238532386323873238832389323903239132392323933239432395323963239732398323993240032401324023240332404324053240632407324083240932410324113241232413324143241532416324173241832419324203242132422324233242432425324263242732428324293243032431324323243332434324353243632437324383243932440324413244232443324443244532446324473244832449324503245132452324533245432455324563245732458324593246032461324623246332464324653246632467324683246932470324713247232473324743247532476324773247832479324803248132482324833248432485324863248732488324893249032491324923249332494324953249632497324983249932500325013250232503325043250532506325073250832509325103251132512325133251432515325163251732518325193252032521325223252332524325253252632527325283252932530325313253232533325343253532536325373253832539325403254132542325433254432545325463254732548325493255032551325523255332554325553255632557325583255932560325613256232563325643256532566325673256832569325703257132572325733257432575325763257732578325793258032581325823258332584325853258632587325883258932590325913259232593325943259532596325973259832599326003260132602326033260432605326063260732608326093261032611326123261332614326153261632617326183261932620326213262232623326243262532626326273262832629326303263132632326333263432635326363263732638326393264032641326423264332644326453264632647326483264932650326513265232653326543265532656326573265832659326603266132662326633266432665326663266732668326693267032671326723267332674326753267632677326783267932680326813268232683326843268532686326873268832689326903269132692326933269432695326963269732698326993270032701327023270332704327053270632707327083270932710327113271232713327143271532716327173271832719327203272132722327233272432725327263272732728327293273032731327323273332734327353273632737327383273932740327413274232743327443274532746327473274832749327503275132752327533275432755327563275732758327593276032761327623276332764327653276632767327683276932770327713277232773327743277532776327773277832779327803278132782327833278432785327863278732788327893279032791327923279332794327953279632797327983279932800328013280232803328043280532806328073280832809328103281132812328133281432815328163281732818328193282032821328223282332824328253282632827328283282932830328313283232833328343283532836328373283832839328403284132842328433284432845328463284732848328493285032851328523285332854328553285632857328583285932860328613286232863328643286532866328673286832869328703287132872328733287432875328763287732878328793288032881328823288332884328853288632887328883288932890328913289232893328943289532896328973289832899329003290132902329033290432905329063290732908329093291032911329123291332914329153291632917329183291932920329213292232923329243292532926329273292832929329303293132932329333293432935329363293732938329393294032941329423294332944329453294632947329483294932950329513295232953329543295532956329573295832959329603296132962329633296432965329663296732968329693297032971329723297332974329753297632977329783297932980329813298232983329843298532986329873298832989329903299132992329933299432995329963299732998329993300033001330023300333004330053300633007330083300933010330113301233013330143301533016330173301833019330203302133022330233302433025330263302733028330293303033031330323303333034330353303633037330383303933040330413304233043330443304533046330473304833049330503305133052330533305433055330563305733058330593306033061330623306333064330653306633067330683306933070330713307233073330743307533076330773307833079330803308133082330833308433085330863308733088330893309033091330923309333094330953309633097330983309933100331013310233103331043310533106331073310833109331103311133112331133311433115331163311733118331193312033121331223312333124331253312633127331283312933130331313313233133331343313533136331373313833139331403314133142331433314433145331463314733148331493315033151331523315333154331553315633157331583315933160331613316233163331643316533166331673316833169331703317133172331733317433175331763317733178331793318033181331823318333184331853318633187331883318933190331913319233193331943319533196331973319833199332003320133202332033320433205332063320733208332093321033211332123321333214332153321633217332183321933220332213322233223332243322533226332273322833229332303323133232332333323433235332363323733238332393324033241332423324333244332453324633247332483324933250332513325233253332543325533256332573325833259332603326133262332633326433265332663326733268332693327033271332723327333274332753327633277332783327933280332813328233283332843328533286332873328833289332903329133292332933329433295332963329733298332993330033301333023330333304333053330633307333083330933310333113331233313333143331533316333173331833319333203332133322333233332433325333263332733328333293333033331333323333333334333353333633337333383333933340333413334233343333443334533346333473334833349333503335133352333533335433355333563335733358333593336033361333623336333364333653336633367333683336933370333713337233373333743337533376333773337833379333803338133382333833338433385333863338733388333893339033391333923339333394333953339633397333983339933400334013340233403334043340533406334073340833409334103341133412334133341433415334163341733418334193342033421334223342333424334253342633427334283342933430334313343233433334343343533436334373343833439334403344133442334433344433445334463344733448334493345033451334523345333454334553345633457334583345933460334613346233463334643346533466334673346833469334703347133472334733347433475334763347733478334793348033481334823348333484334853348633487334883348933490334913349233493334943349533496334973349833499335003350133502335033350433505335063350733508335093351033511335123351333514335153351633517335183351933520335213352233523335243352533526335273352833529335303353133532335333353433535335363353733538335393354033541335423354333544335453354633547335483354933550335513355233553335543355533556335573355833559335603356133562335633356433565335663356733568335693357033571335723357333574335753357633577335783357933580335813358233583335843358533586335873358833589335903359133592335933359433595335963359733598335993360033601336023360333604336053360633607336083360933610336113361233613336143361533616336173361833619336203362133622336233362433625336263362733628336293363033631336323363333634336353363633637336383363933640336413364233643336443364533646336473364833649336503365133652336533365433655336563365733658336593366033661336623366333664336653366633667336683366933670336713367233673336743367533676336773367833679336803368133682336833368433685336863368733688336893369033691336923369333694336953369633697336983369933700337013370233703337043370533706337073370833709337103371133712337133371433715337163371733718337193372033721337223372333724337253372633727337283372933730337313373233733337343373533736337373373833739337403374133742337433374433745337463374733748337493375033751337523375333754337553375633757337583375933760337613376233763337643376533766337673376833769337703377133772337733377433775337763377733778337793378033781337823378333784337853378633787337883378933790337913379233793337943379533796337973379833799338003380133802338033380433805338063380733808338093381033811338123381333814338153381633817338183381933820338213382233823338243382533826338273382833829338303383133832338333383433835338363383733838338393384033841338423384333844338453384633847338483384933850338513385233853338543385533856338573385833859338603386133862338633386433865338663386733868338693387033871338723387333874338753387633877338783387933880338813388233883338843388533886338873388833889338903389133892338933389433895338963389733898338993390033901339023390333904339053390633907339083390933910339113391233913339143391533916339173391833919339203392133922339233392433925339263392733928339293393033931339323393333934339353393633937339383393933940339413394233943339443394533946339473394833949339503395133952339533395433955339563395733958339593396033961339623396333964339653396633967339683396933970339713397233973339743397533976339773397833979339803398133982339833398433985339863398733988339893399033991339923399333994339953399633997339983399934000340013400234003340043400534006340073400834009340103401134012340133401434015340163401734018340193402034021340223402334024340253402634027340283402934030340313403234033340343403534036340373403834039340403404134042340433404434045340463404734048340493405034051340523405334054340553405634057340583405934060340613406234063340643406534066340673406834069340703407134072340733407434075340763407734078340793408034081340823408334084340853408634087340883408934090340913409234093340943409534096340973409834099341003410134102341033410434105341063410734108341093411034111341123411334114341153411634117341183411934120341213412234123341243412534126341273412834129341303413134132341333413434135341363413734138341393414034141341423414334144341453414634147341483414934150341513415234153341543415534156341573415834159341603416134162341633416434165341663416734168341693417034171341723417334174341753417634177341783417934180341813418234183341843418534186341873418834189341903419134192341933419434195341963419734198341993420034201342023420334204342053420634207342083420934210342113421234213342143421534216342173421834219342203422134222342233422434225342263422734228342293423034231342323423334234342353423634237342383423934240342413424234243342443424534246342473424834249342503425134252342533425434255342563425734258342593426034261342623426334264342653426634267342683426934270342713427234273342743427534276342773427834279342803428134282342833428434285342863428734288342893429034291342923429334294342953429634297342983429934300343013430234303343043430534306343073430834309343103431134312343133431434315343163431734318343193432034321343223432334324343253432634327343283432934330343313433234333343343433534336343373433834339343403434134342343433434434345343463434734348343493435034351343523435334354343553435634357343583435934360343613436234363343643436534366343673436834369343703437134372343733437434375343763437734378343793438034381343823438334384343853438634387343883438934390343913439234393343943439534396343973439834399344003440134402344033440434405344063440734408344093441034411344123441334414344153441634417344183441934420344213442234423344243442534426344273442834429344303443134432344333443434435344363443734438344393444034441344423444334444344453444634447344483444934450344513445234453344543445534456344573445834459344603446134462344633446434465344663446734468344693447034471344723447334474344753447634477344783447934480344813448234483344843448534486344873448834489344903449134492344933449434495344963449734498344993450034501345023450334504345053450634507345083450934510345113451234513345143451534516345173451834519345203452134522345233452434525345263452734528345293453034531345323453334534345353453634537345383453934540345413454234543345443454534546345473454834549345503455134552345533455434555345563455734558345593456034561345623456334564345653456634567345683456934570345713457234573345743457534576345773457834579345803458134582345833458434585345863458734588345893459034591345923459334594345953459634597345983459934600346013460234603346043460534606346073460834609346103461134612346133461434615346163461734618346193462034621346223462334624346253462634627346283462934630346313463234633346343463534636346373463834639346403464134642346433464434645346463464734648346493465034651346523465334654346553465634657346583465934660346613466234663346643466534666346673466834669346703467134672346733467434675346763467734678346793468034681346823468334684346853468634687346883468934690346913469234693346943469534696346973469834699347003470134702347033470434705347063470734708347093471034711347123471334714347153471634717347183471934720347213472234723347243472534726347273472834729347303473134732347333473434735347363473734738347393474034741347423474334744347453474634747347483474934750347513475234753347543475534756347573475834759347603476134762347633476434765347663476734768347693477034771347723477334774347753477634777347783477934780347813478234783347843478534786347873478834789347903479134792347933479434795347963479734798347993480034801348023480334804348053480634807348083480934810348113481234813348143481534816348173481834819348203482134822348233482434825348263482734828348293483034831348323483334834348353483634837348383483934840348413484234843348443484534846348473484834849348503485134852348533485434855348563485734858348593486034861348623486334864348653486634867348683486934870348713487234873348743487534876348773487834879348803488134882348833488434885348863488734888348893489034891348923489334894348953489634897348983489934900349013490234903349043490534906349073490834909349103491134912349133491434915349163491734918349193492034921349223492334924349253492634927349283492934930349313493234933349343493534936349373493834939349403494134942349433494434945349463494734948349493495034951349523495334954349553495634957349583495934960349613496234963349643496534966349673496834969349703497134972349733497434975349763497734978349793498034981349823498334984349853498634987349883498934990349913499234993349943499534996349973499834999350003500135002350033500435005350063500735008350093501035011350123501335014350153501635017350183501935020350213502235023350243502535026350273502835029350303503135032350333503435035350363503735038350393504035041350423504335044350453504635047350483504935050350513505235053350543505535056350573505835059350603506135062350633506435065350663506735068350693507035071350723507335074350753507635077350783507935080350813508235083350843508535086350873508835089350903509135092350933509435095350963509735098350993510035101351023510335104351053510635107351083510935110351113511235113351143511535116351173511835119351203512135122351233512435125351263512735128351293513035131351323513335134351353513635137351383513935140351413514235143351443514535146351473514835149351503515135152351533515435155351563515735158351593516035161351623516335164351653516635167351683516935170351713517235173351743517535176351773517835179351803518135182351833518435185351863518735188351893519035191351923519335194351953519635197351983519935200352013520235203352043520535206352073520835209352103521135212352133521435215352163521735218352193522035221352223522335224352253522635227352283522935230352313523235233352343523535236352373523835239352403524135242352433524435245352463524735248352493525035251352523525335254352553525635257352583525935260352613526235263352643526535266352673526835269352703527135272352733527435275352763527735278352793528035281352823528335284352853528635287352883528935290352913529235293352943529535296352973529835299353003530135302353033530435305353063530735308353093531035311353123531335314353153531635317353183531935320353213532235323353243532535326353273532835329353303533135332353333533435335353363533735338353393534035341353423534335344353453534635347353483534935350353513535235353353543535535356353573535835359353603536135362353633536435365353663536735368353693537035371353723537335374353753537635377353783537935380353813538235383353843538535386353873538835389353903539135392353933539435395353963539735398353993540035401354023540335404354053540635407354083540935410354113541235413354143541535416354173541835419354203542135422354233542435425354263542735428354293543035431354323543335434354353543635437354383543935440354413544235443354443544535446354473544835449354503545135452354533545435455354563545735458354593546035461354623546335464354653546635467354683546935470354713547235473354743547535476354773547835479354803548135482354833548435485354863548735488354893549035491354923549335494354953549635497354983549935500355013550235503355043550535506355073550835509355103551135512355133551435515355163551735518355193552035521355223552335524355253552635527355283552935530355313553235533355343553535536355373553835539355403554135542355433554435545355463554735548355493555035551355523555335554355553555635557355583555935560355613556235563355643556535566355673556835569355703557135572355733557435575355763557735578355793558035581355823558335584355853558635587355883558935590355913559235593355943559535596355973559835599356003560135602356033560435605356063560735608356093561035611356123561335614356153561635617356183561935620356213562235623356243562535626356273562835629356303563135632356333563435635356363563735638356393564035641356423564335644356453564635647356483564935650356513565235653356543565535656356573565835659356603566135662356633566435665356663566735668356693567035671356723567335674356753567635677356783567935680356813568235683356843568535686356873568835689356903569135692356933569435695356963569735698356993570035701357023570335704357053570635707357083570935710357113571235713357143571535716357173571835719357203572135722357233572435725357263572735728357293573035731357323573335734357353573635737357383573935740357413574235743357443574535746357473574835749357503575135752357533575435755357563575735758357593576035761357623576335764357653576635767357683576935770357713577235773357743577535776357773577835779357803578135782357833578435785357863578735788357893579035791357923579335794357953579635797357983579935800358013580235803358043580535806358073580835809358103581135812358133581435815358163581735818358193582035821358223582335824358253582635827358283582935830358313583235833358343583535836358373583835839358403584135842358433584435845358463584735848358493585035851358523585335854358553585635857358583585935860358613586235863358643586535866358673586835869358703587135872358733587435875358763587735878358793588035881358823588335884358853588635887358883588935890358913589235893358943589535896358973589835899359003590135902359033590435905359063590735908359093591035911359123591335914359153591635917359183591935920359213592235923359243592535926359273592835929359303593135932359333593435935359363593735938359393594035941359423594335944359453594635947359483594935950359513595235953359543595535956359573595835959359603596135962359633596435965359663596735968359693597035971359723597335974359753597635977359783597935980359813598235983359843598535986359873598835989359903599135992359933599435995359963599735998359993600036001360023600336004360053600636007360083600936010360113601236013360143601536016360173601836019360203602136022360233602436025360263602736028360293603036031360323603336034360353603636037360383603936040360413604236043360443604536046360473604836049360503605136052360533605436055360563605736058360593606036061360623606336064360653606636067360683606936070360713607236073360743607536076360773607836079360803608136082360833608436085360863608736088360893609036091360923609336094360953609636097360983609936100361013610236103361043610536106361073610836109361103611136112361133611436115361163611736118361193612036121361223612336124361253612636127361283612936130361313613236133361343613536136361373613836139361403614136142361433614436145361463614736148361493615036151361523615336154361553615636157361583615936160361613616236163361643616536166361673616836169361703617136172361733617436175361763617736178361793618036181361823618336184361853618636187361883618936190361913619236193361943619536196361973619836199362003620136202362033620436205362063620736208362093621036211362123621336214362153621636217362183621936220362213622236223362243622536226362273622836229362303623136232362333623436235362363623736238362393624036241362423624336244362453624636247362483624936250362513625236253362543625536256362573625836259362603626136262362633626436265362663626736268362693627036271362723627336274362753627636277362783627936280362813628236283362843628536286362873628836289362903629136292362933629436295362963629736298362993630036301363023630336304363053630636307363083630936310363113631236313363143631536316363173631836319363203632136322363233632436325363263632736328363293633036331363323633336334363353633636337363383633936340363413634236343363443634536346363473634836349363503635136352363533635436355363563635736358363593636036361363623636336364363653636636367363683636936370363713637236373363743637536376363773637836379363803638136382363833638436385363863638736388363893639036391363923639336394363953639636397363983639936400364013640236403364043640536406364073640836409364103641136412364133641436415364163641736418364193642036421364223642336424364253642636427364283642936430364313643236433364343643536436364373643836439364403644136442364433644436445364463644736448364493645036451364523645336454364553645636457364583645936460364613646236463364643646536466364673646836469364703647136472364733647436475364763647736478364793648036481364823648336484364853648636487364883648936490364913649236493364943649536496364973649836499365003650136502365033650436505365063650736508365093651036511365123651336514365153651636517365183651936520365213652236523365243652536526365273652836529365303653136532365333653436535365363653736538365393654036541365423654336544365453654636547365483654936550365513655236553365543655536556365573655836559365603656136562365633656436565365663656736568365693657036571365723657336574365753657636577365783657936580365813658236583365843658536586365873658836589365903659136592365933659436595365963659736598365993660036601366023660336604366053660636607366083660936610366113661236613366143661536616366173661836619366203662136622366233662436625366263662736628366293663036631366323663336634366353663636637366383663936640366413664236643366443664536646366473664836649366503665136652366533665436655366563665736658366593666036661366623666336664366653666636667366683666936670366713667236673366743667536676366773667836679366803668136682366833668436685366863668736688366893669036691366923669336694366953669636697366983669936700367013670236703367043670536706367073670836709367103671136712367133671436715367163671736718367193672036721367223672336724367253672636727367283672936730367313673236733367343673536736367373673836739367403674136742367433674436745367463674736748367493675036751367523675336754367553675636757367583675936760367613676236763367643676536766367673676836769367703677136772367733677436775367763677736778367793678036781367823678336784367853678636787367883678936790367913679236793367943679536796367973679836799368003680136802368033680436805368063680736808368093681036811368123681336814368153681636817368183681936820368213682236823368243682536826368273682836829368303683136832368333683436835368363683736838368393684036841368423684336844368453684636847368483684936850368513685236853368543685536856368573685836859368603686136862368633686436865368663686736868368693687036871368723687336874368753687636877368783687936880368813688236883368843688536886368873688836889368903689136892368933689436895368963689736898368993690036901369023690336904369053690636907369083690936910369113691236913369143691536916369173691836919369203692136922369233692436925369263692736928369293693036931369323693336934369353693636937369383693936940369413694236943369443694536946369473694836949369503695136952369533695436955369563695736958369593696036961369623696336964369653696636967369683696936970369713697236973369743697536976369773697836979369803698136982369833698436985369863698736988369893699036991369923699336994369953699636997369983699937000370013700237003370043700537006370073700837009370103701137012370133701437015370163701737018370193702037021370223702337024370253702637027370283702937030370313703237033370343703537036370373703837039370403704137042370433704437045370463704737048370493705037051370523705337054370553705637057370583705937060370613706237063370643706537066370673706837069370703707137072370733707437075370763707737078370793708037081370823708337084370853708637087370883708937090370913709237093370943709537096370973709837099371003710137102371033710437105371063710737108371093711037111371123711337114371153711637117371183711937120371213712237123371243712537126371273712837129371303713137132371333713437135371363713737138371393714037141371423714337144371453714637147371483714937150371513715237153371543715537156371573715837159371603716137162371633716437165371663716737168371693717037171371723717337174371753717637177371783717937180371813718237183371843718537186371873718837189371903719137192371933719437195371963719737198371993720037201372023720337204372053720637207372083720937210372113721237213372143721537216372173721837219372203722137222372233722437225372263722737228372293723037231372323723337234372353723637237372383723937240372413724237243372443724537246372473724837249372503725137252372533725437255372563725737258372593726037261372623726337264372653726637267372683726937270372713727237273372743727537276372773727837279372803728137282372833728437285372863728737288372893729037291372923729337294372953729637297372983729937300373013730237303373043730537306373073730837309373103731137312373133731437315373163731737318373193732037321373223732337324373253732637327373283732937330373313733237333373343733537336373373733837339373403734137342373433734437345373463734737348373493735037351373523735337354373553735637357373583735937360373613736237363373643736537366373673736837369373703737137372373733737437375373763737737378373793738037381373823738337384373853738637387373883738937390373913739237393373943739537396373973739837399374003740137402374033740437405374063740737408374093741037411374123741337414374153741637417374183741937420374213742237423374243742537426374273742837429374303743137432374333743437435374363743737438374393744037441374423744337444374453744637447374483744937450374513745237453374543745537456374573745837459374603746137462374633746437465374663746737468374693747037471374723747337474374753747637477374783747937480374813748237483374843748537486374873748837489374903749137492374933749437495374963749737498374993750037501375023750337504375053750637507375083750937510375113751237513375143751537516375173751837519375203752137522375233752437525375263752737528375293753037531375323753337534375353753637537375383753937540375413754237543375443754537546375473754837549375503755137552375533755437555375563755737558375593756037561375623756337564375653756637567375683756937570375713757237573375743757537576375773757837579375803758137582375833758437585375863758737588375893759037591375923759337594375953759637597375983759937600376013760237603376043760537606376073760837609376103761137612376133761437615376163761737618376193762037621376223762337624376253762637627376283762937630376313763237633376343763537636376373763837639376403764137642376433764437645376463764737648376493765037651376523765337654376553765637657376583765937660376613766237663376643766537666376673766837669376703767137672376733767437675376763767737678376793768037681376823768337684376853768637687376883768937690376913769237693376943769537696376973769837699377003770137702377033770437705377063770737708377093771037711377123771337714377153771637717377183771937720377213772237723377243772537726377273772837729377303773137732377333773437735377363773737738377393774037741377423774337744377453774637747377483774937750377513775237753377543775537756377573775837759377603776137762377633776437765377663776737768377693777037771377723777337774377753777637777377783777937780377813778237783377843778537786377873778837789377903779137792377933779437795377963779737798377993780037801378023780337804378053780637807378083780937810378113781237813378143781537816378173781837819378203782137822378233782437825378263782737828378293783037831378323783337834378353783637837378383783937840378413784237843378443784537846378473784837849378503785137852378533785437855378563785737858378593786037861378623786337864378653786637867378683786937870378713787237873378743787537876378773787837879378803788137882378833788437885378863788737888378893789037891378923789337894378953789637897378983789937900379013790237903379043790537906379073790837909379103791137912379133791437915379163791737918379193792037921379223792337924379253792637927379283792937930379313793237933379343793537936379373793837939379403794137942379433794437945379463794737948379493795037951379523795337954379553795637957379583795937960379613796237963379643796537966379673796837969379703797137972379733797437975379763797737978379793798037981379823798337984379853798637987379883798937990379913799237993379943799537996379973799837999380003800138002380033800438005380063800738008380093801038011380123801338014380153801638017380183801938020380213802238023380243802538026380273802838029380303803138032380333803438035380363803738038380393804038041380423804338044380453804638047380483804938050380513805238053380543805538056380573805838059380603806138062380633806438065380663806738068380693807038071380723807338074380753807638077380783807938080380813808238083380843808538086380873808838089380903809138092380933809438095380963809738098380993810038101381023810338104381053810638107381083810938110381113811238113381143811538116381173811838119381203812138122381233812438125381263812738128381293813038131381323813338134381353813638137381383813938140381413814238143381443814538146381473814838149381503815138152381533815438155381563815738158381593816038161381623816338164381653816638167381683816938170381713817238173381743817538176381773817838179381803818138182381833818438185381863818738188381893819038191381923819338194381953819638197381983819938200382013820238203382043820538206382073820838209382103821138212382133821438215382163821738218382193822038221382223822338224382253822638227382283822938230382313823238233382343823538236382373823838239382403824138242382433824438245382463824738248382493825038251382523825338254382553825638257382583825938260382613826238263382643826538266382673826838269382703827138272382733827438275382763827738278382793828038281382823828338284382853828638287382883828938290382913829238293382943829538296382973829838299383003830138302383033830438305383063830738308383093831038311383123831338314383153831638317383183831938320383213832238323383243832538326383273832838329383303833138332383333833438335383363833738338383393834038341383423834338344383453834638347383483834938350383513835238353383543835538356383573835838359383603836138362383633836438365383663836738368383693837038371383723837338374383753837638377383783837938380383813838238383383843838538386383873838838389383903839138392383933839438395383963839738398383993840038401384023840338404384053840638407384083840938410384113841238413384143841538416384173841838419384203842138422384233842438425384263842738428384293843038431384323843338434384353843638437384383843938440384413844238443384443844538446384473844838449384503845138452384533845438455384563845738458384593846038461384623846338464384653846638467384683846938470384713847238473384743847538476384773847838479384803848138482384833848438485384863848738488384893849038491384923849338494384953849638497384983849938500385013850238503385043850538506385073850838509385103851138512385133851438515385163851738518385193852038521385223852338524385253852638527385283852938530385313853238533385343853538536385373853838539385403854138542385433854438545385463854738548385493855038551385523855338554385553855638557385583855938560385613856238563385643856538566385673856838569385703857138572385733857438575385763857738578385793858038581385823858338584385853858638587385883858938590385913859238593385943859538596385973859838599386003860138602386033860438605386063860738608386093861038611386123861338614386153861638617386183861938620386213862238623386243862538626386273862838629386303863138632386333863438635386363863738638386393864038641386423864338644386453864638647386483864938650386513865238653386543865538656386573865838659386603866138662386633866438665386663866738668386693867038671386723867338674386753867638677386783867938680 |
- /**
- * @class Ext.draw.ContainerBase
- * @private
- */
- Ext.define('Ext.draw.ContainerBase', {
- extend: 'Ext.Container',
- constructor: function(config) {
- this.callParent([
- config
- ]);
- this.initAnimator();
- },
- onResize: function(width, height, oldWidth, oldHeight) {
- this.handleResize({
- width: width,
- height: height
- }, true);
- },
- addElementListener: function() {
- var el = this.element;
- el.on.apply(el, arguments);
- },
- removeElementListener: function() {
- var el = this.element;
- el.un.apply(el, arguments);
- },
- preview: function(image) {
- var item;
- image = image || this.getImage();
- if (image.type === 'svg-markup') {
- item = {
- xtype: 'container',
- html: image.data
- };
- } else {
- item = {
- xtype: 'image',
- mode: 'img',
- imageCls: '',
- cls: Ext.baseCSSPrefix + 'chart-preview',
- src: image.data
- };
- }
- Ext.Viewport.add({
- xtype: 'panel',
- layout: 'fit',
- modal: true,
- border: 1,
- shadow: true,
- width: '90%',
- height: '90%',
- hideOnMaskTap: true,
- centered: true,
- floated: true,
- scrollable: false,
- closable: true,
- // Use 'hide' so that hiding via close button/mask tap go through
- // the same code path
- closeAction: 'hide',
- items: [
- item
- ],
- listeners: {
- hide: function() {
- this.destroy();
- }
- }
- }).show();
- }
- });
- /**
- * @private
- * @class Ext.draw.SurfaceBase
- */
- Ext.define('Ext.draw.SurfaceBase', {
- extend: 'Ext.Widget',
- getOwnerBody: function() {
- return this.getRefOwner().bodyElement;
- }
- });
- /**
- * @private
- * @class Ext.draw.sprite.AnimationParser
- *
- * Computes an intermidiate value between two values of the same type for use in animations.
- * Can have pre- and post- processor functions if the values need to be processed
- * before an intermidiate value can be computed (parseInitial), or the computed value
- * needs to be processed before it can be used as a valid attribute value (serve).
- */
- Ext.define('Ext.draw.sprite.AnimationParser', function() {
- function compute(from, to, delta) {
- return from + (to - from) * delta;
- }
- return {
- singleton: true,
- attributeRe: /^url\(#([a-zA-Z-]+)\)$/,
- requires: [
- 'Ext.draw.Color'
- ],
- color: {
- parseInitial: function(color1, color2) {
- if (Ext.isString(color1)) {
- color1 = Ext.util.Color.create(color1);
- }
- if (Ext.isString(color2)) {
- color2 = Ext.util.Color.create(color2);
- }
- if ((color1 && color1.isColor) && (color2 && color2.isColor)) {
- return [
- [
- color1.r,
- color1.g,
- color1.b,
- color1.a
- ],
- [
- color2.r,
- color2.g,
- color2.b,
- color2.a
- ]
- ];
- } else {
- return [
- color1 || color2,
- color2 || color1
- ];
- }
- },
- compute: function(from, to, delta) {
- if (!Ext.isArray(from) || !Ext.isArray(to)) {
- return to || from;
- } else {
- return [
- compute(from[0], to[0], delta),
- compute(from[1], to[1], delta),
- compute(from[2], to[2], delta),
- compute(from[3], to[3], delta)
- ];
- }
- },
- serve: function(array) {
- var color = Ext.util.Color.fly(array[0], array[1], array[2], array[3]);
- return color.toString();
- }
- },
- number: {
- parse: function(n) {
- return n === null ? null : +n;
- },
- compute: function(from, to, delta) {
- if (!Ext.isNumber(from) || !Ext.isNumber(to)) {
- return to || from;
- } else {
- return compute(from, to, delta);
- }
- }
- },
- angle: {
- parseInitial: function(from, to) {
- if (to - from > Math.PI) {
- to -= Math.PI * 2;
- } else if (to - from < -Math.PI) {
- to += Math.PI * 2;
- }
- return [
- from,
- to
- ];
- },
- compute: function(from, to, delta) {
- if (!Ext.isNumber(from) || !Ext.isNumber(to)) {
- return to || from;
- } else {
- return compute(from, to, delta);
- }
- }
- },
- path: {
- parseInitial: function(from, to) {
- var fromStripes = from.toStripes(),
- toStripes = to.toStripes(),
- i, j,
- fromLength = fromStripes.length,
- toLength = toStripes.length,
- fromStripe, toStripe, length,
- lastStripe = toStripes[toLength - 1],
- endPoint = [
- lastStripe[lastStripe.length - 2],
- lastStripe[lastStripe.length - 1]
- ];
- for (i = fromLength; i < toLength; i++) {
- fromStripes.push(fromStripes[fromLength - 1].slice(0));
- }
- for (i = toLength; i < fromLength; i++) {
- toStripes.push(endPoint.slice(0));
- }
- length = fromStripes.length;
- toStripes.path = to;
- toStripes.temp = new Ext.draw.Path();
- for (i = 0; i < length; i++) {
- fromStripe = fromStripes[i];
- toStripe = toStripes[i];
- fromLength = fromStripe.length;
- toLength = toStripe.length;
- toStripes.temp.commands.push('M');
- for (j = toLength; j < fromLength; j += 6) {
- toStripe.push(endPoint[0], endPoint[1], endPoint[0], endPoint[1], endPoint[0], endPoint[1]);
- }
- lastStripe = toStripes[toStripes.length - 1];
- endPoint = [
- lastStripe[lastStripe.length - 2],
- lastStripe[lastStripe.length - 1]
- ];
- for (j = fromLength; j < toLength; j += 6) {
- fromStripe.push(endPoint[0], endPoint[1], endPoint[0], endPoint[1], endPoint[0], endPoint[1]);
- }
- for (i = 0; i < toStripe.length; i++) {
- toStripe[i] -= fromStripe[i];
- }
- for (i = 2; i < toStripe.length; i += 6) {
- toStripes.temp.commands.push('C');
- }
- }
- return [
- fromStripes,
- toStripes
- ];
- },
- compute: function(fromStripes, toStripes, delta) {
- if (delta >= 1) {
- return toStripes.path;
- }
- // eslint-disable-next-line vars-on-top
- var i = 0,
- ln = fromStripes.length,
- j = 0,
- ln2, from, to,
- temp = toStripes.temp.params,
- pos = 0;
- for (; i < ln; i++) {
- from = fromStripes[i];
- to = toStripes[i];
- ln2 = from.length;
- for (j = 0; j < ln2; j++) {
- temp[pos++] = to[j] * delta + from[j];
- }
- }
- return toStripes.temp;
- }
- },
- data: {
- compute: function(from, to, delta, target) {
- var iMaxFrom = from.length - 1,
- iMaxTo = to.length - 1,
- iMax = Math.max(iMaxFrom, iMaxTo),
- i, start, end;
- if (!target || target === from) {
- target = [];
- }
- target.length = iMax + 1;
- for (i = 0; i <= iMax; i++) {
- start = from[Math.min(i, iMaxFrom)];
- end = to[Math.min(i, iMaxTo)];
- if (Ext.isNumber(start)) {
- if (!Ext.isNumber(end)) {
- // This may not give the desired visual result during
- // animation (after all, we don't know what the target
- // value should be, if it wasn't given to us), but it's
- // better than spitting out a bunch of NaNs in the target
- // array, when transitioning from a non-empty to an empty
- // array.
- end = 0;
- }
- target[i] = start + (end - start) * delta;
- } else {
- target[i] = end;
- }
- }
- return target;
- }
- },
- text: {
- compute: function(from, to, delta) {
- return from.substr(0, Math.round(from.length * (1 - delta))) + to.substr(Math.round(to.length * (1 - delta)));
- }
- },
- limited: 'number',
- limited01: 'number'
- };
- });
- /* global Float32Array */
- /* eslint-disable indent */
- (function() {
- if (!Ext.global.Float32Array) {
- // Typed Array polyfill
- // eslint-disable-next-line vars-on-top
- var Float32Array = function(array) {
- var i, len;
- if (typeof array === 'number') {
- this.length = array;
- } else if ('length' in array) {
- this.length = array.length;
- for (i = 0 , len = array.length; i < len; i++) {
- this[i] = +array[i];
- }
- }
- };
- Float32Array.prototype = [];
- Ext.global.Float32Array = Float32Array;
- }
- })();
- /* eslint-enable indent */
- /**
- * Utility class providing mathematics functionalities through all the draw package.
- */
- Ext.define('Ext.draw.Draw', {
- singleton: true,
- radian: Math.PI / 180,
- pi2: Math.PI * 2,
- /**
- * @deprecated 6.5.0 Please use the {@link Ext#identityFn} instead.
- * Function that returns its first element.
- * @param {Mixed} a
- * @return {Mixed}
- */
- reflectFn: function(a) {
- return a;
- },
- /**
- * Converting degrees to radians.
- * @param {Number} degrees
- * @return {Number}
- */
- rad: function(degrees) {
- return (degrees % 360) * this.radian;
- },
- /**
- * Converting radians to degrees.
- * @param {Number} radian
- * @return {Number}
- */
- degrees: function(radian) {
- return (radian / this.radian) % 360;
- },
- /**
- *
- * @param {Object} bbox1
- * @param {Object} bbox2
- * @param {Number} [padding]
- * @return {Boolean}
- */
- isBBoxIntersect: function(bbox1, bbox2, padding) {
- padding = padding || 0;
- 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));
- },
- /**
- * Checks if a point is within a bounding box.
- * @param x
- * @param y
- * @param bbox
- * @return {Boolean}
- */
- isPointInBBox: function(x, y, bbox) {
- return !!bbox && x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height);
- },
- /**
- * Natural cubic spline interpolation.
- * This algorithm runs in linear time.
- *
- * @param {Array} points Array of numbers.
- */
- naturalSpline: function(points) {
- var i, j,
- ln = points.length,
- nd, d, y, ny,
- r = 0,
- zs = new Float32Array(points.length),
- result = new Float32Array(points.length * 3 - 2);
- zs[0] = 0;
- zs[ln - 1] = 0;
- for (i = 1; i < ln - 1; i++) {
- zs[i] = (points[i + 1] + points[i - 1] - 2 * points[i]) - zs[i - 1];
- r = 1 / (4 - r);
- zs[i] *= r;
- }
- for (i = ln - 2; i > 0; i--) {
- r = 3.732050807568877 + 48.248711305964385 / (-13.928203230275537 + Math.pow(0.07179676972449123, i));
- zs[i] -= zs[i + 1] * r;
- }
- ny = points[0];
- nd = ny - zs[0];
- for (i = 0 , j = 0; i < ln - 1; j += 3) {
- y = ny;
- d = nd;
- i++;
- ny = points[i];
- nd = ny - zs[i];
- result[j] = y;
- result[j + 1] = (nd + 2 * d) / 3;
- result[j + 2] = (nd * 2 + d) / 3;
- }
- result[j] = ny;
- return result;
- },
- /**
- * Shorthand for {@link #naturalSpline}
- */
- spline: function(points) {
- return this.naturalSpline(points);
- },
- /**
- * @private
- * Cardinal spline interpolation.
- * Goes from cardinal control points to cubic Bezier control points.
- */
- cardinalToBezier: function(P1, P2, P3, P4, tension) {
- return [
- P2,
- P2 + (P3 - P1) / 6 * tension,
- P3 - (P4 - P2) / 6 * tension,
- P3
- ];
- },
- /**
- * @private
- * @param {Number[]} P An array of n x- or y-coordinates.
- * @param {Number} tension
- * @return {Float32Array} An array of 3n - 2 Bezier control points.
- */
- cardinalSpline: function(P, tension) {
- var n = P.length,
- result = new Float32Array(n * 3 - 2),
- i, bezier;
- if (tension === undefined) {
- tension = 0.5;
- }
- bezier = this.cardinalToBezier(P[0], P[0], P[1], P[2], tension);
- result[0] = bezier[0];
- result[1] = bezier[1];
- result[2] = bezier[2];
- result[3] = bezier[3];
- for (i = 0; i < n - 3; i++) {
- bezier = this.cardinalToBezier(P[i], P[i + 1], P[i + 2], P[i + 3], tension);
- result[4 + i * 3] = bezier[1];
- result[4 + i * 3 + 1] = bezier[2];
- result[4 + i * 3 + 2] = bezier[3];
- }
- bezier = this.cardinalToBezier(P[n - 3], P[n - 2], P[n - 1], P[n - 1], tension);
- result[4 + i * 3] = bezier[1];
- result[4 + i * 3 + 1] = bezier[2];
- result[4 + i * 3 + 2] = bezier[3];
- return result;
- },
- /**
- * @private
- *
- * Calculates bezier curve control anchor points for a particular point in a path, with a
- * smoothing curve applied. The smoothness of the curve is controlled by the 'value' parameter.
- * Note that this algorithm assumes that the line being smoothed is normalized going from left
- * to right; it makes special adjustments assuming this orientation.
- *
- * @param {Number} prevX X coordinate of the previous point in the path
- * @param {Number} prevY Y coordinate of the previous point in the path
- * @param {Number} curX X coordinate of the current point in the path
- * @param {Number} curY Y coordinate of the current point in the path
- * @param {Number} nextX X coordinate of the next point in the path
- * @param {Number} nextY Y coordinate of the next point in the path
- * @param {Number} value A value to control the smoothness of the curve; this is used to
- * divide the distance between points, so a value of 2 corresponds to
- * half the distance between points (a very smooth line) while higher values
- * result in less smooth curves. Defaults to 4.
- * @return {Object} Object containing x1, y1, x2, y2 bezier control anchor points; x1 and y1
- * are the control point for the curve toward the previous path point, and
- * x2 and y2 are the control point for the curve toward the next path point.
- */
- getAnchors: function(prevX, prevY, curX, curY, nextX, nextY, value) {
- var PI = Math.PI,
- halfPI = PI / 2,
- abs = Math.abs,
- sin = Math.sin,
- cos = Math.cos,
- atan = Math.atan,
- control1Length, control2Length, control1Angle, control2Angle, control1X, control1Y, control2X, control2Y, alpha;
- value = value || 4;
- // Find the length of each control anchor line, by dividing the horizontal distance
- // between points by the value parameter.
- control1Length = (curX - prevX) / value;
- control2Length = (nextX - curX) / value;
- // Determine the angle of each control anchor line. If the middle point is a vertical
- // turnaround then we force it to a flat horizontal angle to prevent the curve from
- // dipping above or below the middle point. Otherwise we use an angle that points
- // toward the previous/next target point.
- if ((curY >= prevY && curY >= nextY) || (curY <= prevY && curY <= nextY)) {
- control1Angle = control2Angle = halfPI;
- } else {
- control1Angle = atan((curX - prevX) / abs(curY - prevY));
- if (prevY < curY) {
- control1Angle = PI - control1Angle;
- }
- control2Angle = atan((nextX - curX) / abs(curY - nextY));
- if (nextY < curY) {
- control2Angle = PI - control2Angle;
- }
- }
- // Adjust the calculated angles so they point away from each other on the same line
- alpha = halfPI - ((control1Angle + control2Angle) % (PI * 2)) / 2;
- if (alpha > halfPI) {
- alpha -= PI;
- }
- control1Angle += alpha;
- control2Angle += alpha;
- // Find the control anchor points from the angles and length
- control1X = curX - control1Length * sin(control1Angle);
- control1Y = curY + control1Length * cos(control1Angle);
- control2X = curX + control2Length * sin(control2Angle);
- control2Y = curY + control2Length * cos(control2Angle);
- // One last adjustment, make sure that no control anchor point extends vertically past
- // its target prev/next point, as that results in curves dipping above or below and
- // bending back strangely. If we find this happening we keep the control angle but
- // reduce the length of the control line so it stays within bounds.
- if ((curY > prevY && control1Y < prevY) || (curY < prevY && control1Y > prevY)) {
- control1X += abs(prevY - control1Y) * (control1X - curX) / (control1Y - curY);
- control1Y = prevY;
- }
- if ((curY > nextY && control2Y < nextY) || (curY < nextY && control2Y > nextY)) {
- control2X -= abs(nextY - control2Y) * (control2X - curX) / (control2Y - curY);
- control2Y = nextY;
- }
- return {
- x1: control1X,
- y1: control1Y,
- x2: control2X,
- y2: control2Y
- };
- },
- /**
- * Given coordinates of the points, calculates coordinates of a Bezier curve
- * that goes through them.
- * @param dataX x-coordinates of the points.
- * @param dataY y-coordinates of the points.
- * @param value A value to control the smoothness of the curve.
- * @return {Object} Object holding two arrays, for x and y coordinates of the curve.
- */
- smooth: function(dataX, dataY, value) {
- var ln = dataX.length,
- prevX, prevY, curX, curY, nextX, nextY, x, y,
- smoothX = [],
- smoothY = [],
- i, anchors;
- for (i = 0; i < ln - 1; i++) {
- prevX = dataX[i];
- prevY = dataY[i];
- if (i === 0) {
- x = prevX;
- y = prevY;
- smoothX.push(x);
- smoothY.push(y);
- if (ln === 1) {
- break;
- }
- }
- curX = dataX[i + 1];
- curY = dataY[i + 1];
- nextX = dataX[i + 2];
- nextY = dataY[i + 2];
- if (!(Ext.isNumber(nextX) && Ext.isNumber(nextY))) {
- smoothX.push(x, curX, curX);
- smoothY.push(y, curY, curY);
- break;
- }
- anchors = this.getAnchors(prevX, prevY, curX, curY, nextX, nextY, value);
- smoothX.push(x, anchors.x1, curX);
- smoothY.push(y, anchors.y1, curY);
- x = anchors.x2;
- y = anchors.y2;
- }
- return {
- smoothX: smoothX,
- smoothY: smoothY
- };
- },
- /**
- * @method
- * @private
- * Work around for iOS.
- * Nested 3d-transforms seems to prevent the redraw inside it until some event is fired.
- */
- beginUpdateIOS: Ext.os.is.iOS ? function() {
- this.iosUpdateEl = Ext.getBody().createChild({
- //<debug>
- 'data-sticky': true,
- //</debug>
- style: 'position: absolute; top: 0px; bottom: 0px; left: 0px; right: 0px; ' + 'background: rgba(0,0,0,0.001); z-index: 100000'
- });
- } : Ext.emptyFn,
- endUpdateIOS: function() {
- this.iosUpdateEl = Ext.destroy(this.iosUpdateEl);
- }
- });
- /**
- * @class Ext.draw.gradient.Gradient
- *
- * Creates a gradient.
- */
- Ext.define('Ext.draw.gradient.Gradient', {
- requires: [
- 'Ext.draw.Color'
- ],
- isGradient: true,
- config: {
- /**
- * @cfg {Object[]} stops
- * Defines the stops of the gradient.
- */
- stops: []
- },
- applyStops: function(newStops) {
- var stops = [],
- ln = newStops.length,
- i, stop, color;
- for (i = 0; i < ln; i++) {
- stop = newStops[i];
- color = stop.color;
- if (!(color && color.isColor)) {
- color = Ext.util.Color.fly(color || Ext.util.Color.NONE);
- }
- stops.push({
- // eslint-disable-next-line max-len
- offset: Math.min(1, Math.max(0, 'offset' in stop ? stop.offset : stop.position || 0)),
- color: color.toString()
- });
- }
- stops.sort(function(a, b) {
- return a.offset - b.offset;
- });
- return stops;
- },
- onClassExtended: function(subClass, member) {
- if (!member.alias && member.type) {
- member.alias = 'gradient.' + member.type;
- }
- },
- constructor: function(config) {
- this.initConfig(config);
- },
- /**
- * @method
- * @protected
- * Generates the gradient for the given context.
- * @param {Ext.draw.engine.SvgContext} ctx The context.
- * @param {Object} bbox
- * @return {CanvasGradient/Ext.draw.engine.SvgContext.Gradient/Ext.util.Color.NONE}
- */
- generateGradient: Ext.emptyFn
- });
- /**
- * @class Ext.draw.gradient.GradientDefinition
- *
- * A global map of all gradient configs.
- */
- Ext.define('Ext.draw.gradient.GradientDefinition', {
- singleton: true,
- urlStringRe: /^url\(#([\w-]+)\)$/,
- gradients: {},
- add: function(gradients) {
- var store = this.gradients,
- i, n, gradient;
- for (i = 0 , n = gradients.length; i < n; i++) {
- gradient = gradients[i];
- if (Ext.isString(gradient.id)) {
- store[gradient.id] = gradient;
- }
- }
- },
- get: function(str) {
- var store = this.gradients,
- match = str.match(this.urlStringRe),
- gradient;
- if (match && match[1] && (gradient = store[match[1]])) {
- return gradient || str;
- }
- return str;
- }
- });
- /* global Float32Array */
- /**
- * @private
- * @class Ext.draw.sprite.AttributeParser
- *
- * Parsers used for sprite attributes if they are
- * {@link Ext.draw.sprite.AttributeDefinition#normalize normalized} (default) when being
- * {@link Ext.draw.sprite.Sprite#setAttributes set}.
- *
- * Methods of the singleton correpond either to the processor functions themselves or processor
- * factories.
- */
- Ext.define('Ext.draw.sprite.AttributeParser', {
- singleton: true,
- attributeRe: /^url\(#([a-zA-Z-]+)\)$/,
- requires: [
- 'Ext.draw.Color',
- 'Ext.draw.gradient.GradientDefinition'
- ],
- 'default': Ext.identityFn,
- string: function(n) {
- return String(n);
- },
- number: function(n) {
- // Numbers as strings will be converted to numbers,
- // null will be converted to 0.
- if (Ext.isNumber(+n)) {
- return n;
- }
- },
- /**
- * Normalize angle to the [-180,180) interval.
- * @param n Angle in radians.
- * @return {Number/undefined} Normalized angle or undefined.
- */
- angle: function(n) {
- if (Ext.isNumber(n)) {
- n %= Math.PI * 2;
- if (n < -Math.PI) {
- n += Math.PI * 2;
- } else if (n >= Math.PI) {
- n -= Math.PI * 2;
- }
- return n;
- }
- },
- data: function(n) {
- if (Ext.isArray(n)) {
- return n.slice();
- } else if (n instanceof Float32Array) {
- return new Float32Array(n);
- }
- },
- bool: function(n) {
- return !!n;
- },
- color: function(n) {
- if (n && n.isColor) {
- return n.toString();
- } else if (n && n.isGradient) {
- return n;
- } else if (!n) {
- return Ext.util.Color.NONE;
- } else if (Ext.isString(n)) {
- if (n.substr(0, 3) === 'url') {
- n = Ext.draw.gradient.GradientDefinition.get(n);
- if (Ext.isString(n)) {
- return n;
- }
- } else {
- return Ext.util.Color.fly(n).toString();
- }
- }
- if (n.type === 'linear') {
- return Ext.create('Ext.draw.gradient.Linear', n);
- } else if (n.type === 'radial') {
- return Ext.create('Ext.draw.gradient.Radial', n);
- } else if (n.type === 'pattern') {
- return Ext.create('Ext.draw.gradient.Pattern', n);
- } else {
- return Ext.util.Color.NONE;
- }
- },
- limited: function(low, hi) {
- return function(n) {
- n = +n;
- return Ext.isNumber(n) ? Math.min(Math.max(n, low), hi) : undefined;
- };
- },
- limited01: function(n) {
- n = +n;
- return Ext.isNumber(n) ? Math.min(Math.max(n, 0), 1) : undefined;
- },
- /**
- * Generates a function that checks if a value matches
- * one of the given attributes.
- * @return {Function}
- */
- enums: function() {
- var enums = {},
- args = Array.prototype.slice.call(arguments, 0),
- i, ln;
- for (i = 0 , ln = args.length; i < ln; i++) {
- enums[args[i]] = true;
- }
- return function(n) {
- return n in enums ? n : undefined;
- };
- }
- });
- /**
- * @private
- * Flyweight object to process the attributes of a sprite.
- * A single instance of the AttributeDefinition is created per sprite class.
- * See `onClassCreated` and `onClassExtended` callbacks
- * of the {@link Ext.draw.sprite.Sprite} for more info.
- */
- Ext.define('Ext.draw.sprite.AttributeDefinition', {
- requires: [
- 'Ext.draw.sprite.AttributeParser',
- 'Ext.draw.sprite.AnimationParser'
- ],
- config: {
- /**
- * @cfg {Object} defaults Defines the default values of attributes.
- */
- defaults: {
- $value: {},
- lazy: true
- },
- /**
- * @cfg {Object} aliases Defines the alternative names for attributes.
- */
- aliases: {},
- /**
- * @cfg {Object} animationProcessors Defines the process used to animate between attributes.
- * One doesn't have to define animation processors for sprite attributes that use
- * predefined {@link #processors} from the {@link Ext.draw.sprite.AttributeParser}
- * singleton.
- * For such attributes matching animation processors from the
- * {@link Ext.draw.sprite.AnimationParser} singleton will be used automatically.
- * However, if you have a custom processor for an attribute that should support
- * animation, you must provide a corresponding animation processor for it here.
- * For more information on animation processors please see
- * {@link Ext.draw.sprite.AnimationParser} documentation.
- */
- animationProcessors: {},
- /**
- * @cfg {Object} processors Defines the preprocessing used on the attributes.
- * One can define a custom processor function here or use the name of a predefined
- * processor from the {@link Ext.draw.sprite.AttributeParser} singleton.
- */
- processors: {
- // A plus side of lazy initialization is that the 'processors' and 'defaults' will
- // only be applied for those sprite classes that are actually instantiated.
- $value: {},
- lazy: true
- },
- /**
- * @cfg {Object} dirtyTriggers
- * @deprecated 6.5.0 Use the {@link #triggers} config instead.
- */
- dirtyTriggers: {},
- /* eslint-disable max-len */
- /**
- * @cfg {Object} triggers Defines which updaters have to be called when an attribute is changed.
- * For example, the config below indicates that the 'size' updater
- * of a {@link Ext.draw.sprite.Square square} sprite has to be called
- * when the 'size' attribute changes.
- *
- * triggers: {
- * size: 'size' // Use comma-separated values here if multiple updaters have to be called.
- * } // Note that the order is _not_ guaranteed.
- *
- * If any of the updaters to be called (triggered by the {@link Ext.draw.sprite.Sprite#setAttributes} call)
- * set attributes themselves and those attributes have triggers defined for them,
- * then their updaters will be called after all current updaters finish execution.
- *
- * The updater functions themselves are defined in the {@link #updaters} config,
- * aside from the 'canvas' updater, which doesn't have to be defined and acts as a flag,
- * indicating that this attribute should be applied to a Canvas context (or whatever emulates it).
- * @since 5.1.0
- */
- triggers: {},
- /**
- * @cfg {Object} updaters Defines the postprocessing used by the attribute.
- * Inside the updater function 'this' refers to the sprite that the attributes belong to.
- * In case of an instancing sprite 'this' will refer to the instancing template.
- * The two parameters passed to the updater function are the attributes object
- * of the sprite or instance, and the names of attributes that triggered this updater call.
- *
- * The example below shows how the 'size' updater changes other attributes
- * of a {@link Ext.draw.sprite.Square square} sprite sprite when its 'size' attribute changes.
- *
- * updaters: {
- * size: function (attr) {
- * var size = attr.size;
- * this.setAttributes({ // Changes to these attributes will trigger the 'path' updater.
- * x: attr.x - size,
- * y: attr.y - size,
- * height: 2 * size,
- * width: 2 * size
- * });
- * }
- * }
- */
- updaters: {}
- },
- /* eslint-enable max-len */
- inheritableStatics: {
- /**
- * @private
- * Processor declaration in the form of 'processorFactory(argument1,argument2,...)'.
- * E.g.: {@link Ext.draw.sprite.AttributeParser#enums enums},
- * {@link Ext.draw.sprite.AttributeParser#limited limited}.
- */
- processorFactoryRe: /^(\w+)\(([\w\-,]*)\)$/
- },
- // The sprite class for which AttributeDefinition instance is created.
- spriteClass: null,
- constructor: function(config) {
- var me = this;
- me.initConfig(config);
- },
- applyDefaults: function(defaults, oldDefaults) {
- oldDefaults = Ext.apply(oldDefaults || {}, this.normalize(defaults));
- return oldDefaults;
- },
- applyAliases: function(aliases, oldAliases) {
- return Ext.apply(oldAliases || {}, aliases);
- },
- applyProcessors: function(processors, oldProcessors) {
- this.getAnimationProcessors();
- // Apply custom animation processors first.
- // eslint-disable-next-line vars-on-top
- var result = oldProcessors || {},
- defaultProcessor = Ext.draw.sprite.AttributeParser,
- processorFactoryRe = this.self.processorFactoryRe,
- animationProcessors = {},
- anyAnimationProcessors, name, match, fn;
- for (name in processors) {
- fn = processors[name];
- if (typeof fn === 'string') {
- match = fn.match(processorFactoryRe);
- if (match) {
- // enums(... , limited(... or something of that nature.
- fn = defaultProcessor[match[1]].apply(defaultProcessor, match[2].split(','));
- } else if (defaultProcessor[fn]) {
- // Names of animation parsers match the names of attribute parsers.
- animationProcessors[name] = fn;
- anyAnimationProcessors = true;
- fn = defaultProcessor[fn];
- }
- }
- //<debug>
- if (!Ext.isFunction(fn)) {
- Ext.raise(this.spriteClass.$className + ": processor '" + name + "' has not been found.");
- }
- //</debug>
- result[name] = fn;
- }
- if (anyAnimationProcessors) {
- this.setAnimationProcessors(animationProcessors);
- }
- return result;
- },
- applyAnimationProcessors: function(animationProcessors, oldAnimationProcessors) {
- var parser = Ext.draw.sprite.AnimationParser,
- name, item;
- if (!oldAnimationProcessors) {
- oldAnimationProcessors = {};
- }
- for (name in animationProcessors) {
- item = animationProcessors[name];
- if (item === 'none') {
- oldAnimationProcessors[name] = null;
- } else if (Ext.isString(item) && !(name in oldAnimationProcessors)) {
- if (item in parser) {
- // The while loop is used to resolve aliases, e.g. `num: 'number'`,
- // where `number` maps to a parser object or is an alias too.
- while (Ext.isString(parser[item])) {
- item = parser[item];
- }
- oldAnimationProcessors[name] = parser[item];
- }
- } else if (Ext.isObject(item)) {
- oldAnimationProcessors[name] = item;
- }
- }
- return oldAnimationProcessors;
- },
- updateDirtyTriggers: function(dirtyTriggers) {
- this.setTriggers(dirtyTriggers);
- },
- applyTriggers: function(triggers, oldTriggers) {
- var name;
- if (!oldTriggers) {
- oldTriggers = {};
- }
- for (name in triggers) {
- oldTriggers[name] = triggers[name].split(',');
- }
- return oldTriggers;
- },
- applyUpdaters: function(updaters, oldUpdaters) {
- return Ext.apply(oldUpdaters || {}, updaters);
- },
- batchedNormalize: function(batchedChanges, keepUnrecognized) {
- if (!batchedChanges) {
- return {};
- }
- // eslint-disable-next-line vars-on-top
- var processors = this.getProcessors(),
- aliases = this.getAliases(),
- translation = batchedChanges.translation || batchedChanges.translate,
- normalized = {},
- i, ln, name, val, rotation, scaling, matrix, subVal, split;
- if ('rotation' in batchedChanges) {
- rotation = batchedChanges.rotation;
- } else {
- rotation = ('rotate' in batchedChanges) ? batchedChanges.rotate : undefined;
- }
- if ('scaling' in batchedChanges) {
- scaling = batchedChanges.scaling;
- } else {
- scaling = ('scale' in batchedChanges) ? batchedChanges.scale : undefined;
- }
- if (typeof scaling !== 'undefined') {
- if (Ext.isNumber(scaling)) {
- normalized.scalingX = scaling;
- normalized.scalingY = scaling;
- } else {
- if ('x' in scaling) {
- normalized.scalingX = scaling.x;
- }
- if ('y' in scaling) {
- normalized.scalingY = scaling.y;
- }
- if ('centerX' in scaling) {
- normalized.scalingCenterX = scaling.centerX;
- }
- if ('centerY' in scaling) {
- normalized.scalingCenterY = scaling.centerY;
- }
- }
- }
- if (typeof rotation !== 'undefined') {
- if (Ext.isNumber(rotation)) {
- rotation = Ext.draw.Draw.rad(rotation);
- normalized.rotationRads = rotation;
- } else {
- if ('rads' in rotation) {
- normalized.rotationRads = rotation.rads;
- } else if ('degrees' in rotation) {
- if (Ext.isArray(rotation.degrees)) {
- normalized.rotationRads = Ext.Array.map(rotation.degrees, function(deg) {
- return Ext.draw.Draw.rad(deg);
- });
- } else {
- normalized.rotationRads = Ext.draw.Draw.rad(rotation.degrees);
- }
- }
- if ('centerX' in rotation) {
- normalized.rotationCenterX = rotation.centerX;
- }
- if ('centerY' in rotation) {
- normalized.rotationCenterY = rotation.centerY;
- }
- }
- }
- if (typeof translation !== 'undefined') {
- if ('x' in translation) {
- normalized.translationX = translation.x;
- }
- if ('y' in translation) {
- normalized.translationY = translation.y;
- }
- }
- if ('matrix' in batchedChanges) {
- matrix = Ext.draw.Matrix.create(batchedChanges.matrix);
- split = matrix.split();
- normalized.matrix = matrix;
- normalized.rotationRads = split.rotation;
- normalized.rotationCenterX = 0;
- normalized.rotationCenterY = 0;
- normalized.scalingX = split.scaleX;
- normalized.scalingY = split.scaleY;
- normalized.scalingCenterX = 0;
- normalized.scalingCenterY = 0;
- normalized.translationX = split.translateX;
- normalized.translationY = split.translateY;
- }
- for (name in batchedChanges) {
- val = batchedChanges[name];
- if (typeof val === 'undefined') {
-
- continue;
- } else if (Ext.isArray(val)) {
- if (name in aliases) {
- name = aliases[name];
- }
- if (name in processors) {
- normalized[name] = [];
- for (i = 0 , ln = val.length; i < ln; i++) {
- subVal = processors[name].call(this, val[i]);
- if (typeof subVal !== 'undefined') {
- normalized[name][i] = subVal;
- }
- }
- } else if (keepUnrecognized) {
- normalized[name] = val;
- }
- } else {
- if (name in aliases) {
- name = aliases[name];
- }
- if (name in processors) {
- val = processors[name].call(this, val);
- if (typeof val !== 'undefined') {
- normalized[name] = val;
- }
- } else if (keepUnrecognized) {
- normalized[name] = val;
- }
- }
- }
- return normalized;
- },
- /**
- * Normalizes the changes given via their processors before they are applied as attributes.
- *
- * @param {Object} changes The changes given.
- * @param {Boolean} keepUnrecognized If 'true', unknown attributes will be passed through
- * as normalized values.
- * @return {Object} The normalized values.
- */
- normalize: function(changes, keepUnrecognized) {
- if (!changes) {
- return {};
- }
- // eslint-disable-next-line vars-on-top
- var processors = this.getProcessors(),
- aliases = this.getAliases(),
- translation = changes.translation || changes.translate,
- normalized = {},
- name, val, rotation, scaling, matrix, split;
- if ('rotation' in changes) {
- rotation = changes.rotation;
- } else {
- rotation = ('rotate' in changes) ? changes.rotate : undefined;
- }
- if ('scaling' in changes) {
- scaling = changes.scaling;
- } else {
- scaling = ('scale' in changes) ? changes.scale : undefined;
- }
- if (translation) {
- if ('x' in translation) {
- normalized.translationX = translation.x;
- }
- if ('y' in translation) {
- normalized.translationY = translation.y;
- }
- }
- if (typeof scaling !== 'undefined') {
- if (Ext.isNumber(scaling)) {
- normalized.scalingX = scaling;
- normalized.scalingY = scaling;
- } else {
- if ('x' in scaling) {
- normalized.scalingX = scaling.x;
- }
- if ('y' in scaling) {
- normalized.scalingY = scaling.y;
- }
- if ('centerX' in scaling) {
- normalized.scalingCenterX = scaling.centerX;
- }
- if ('centerY' in scaling) {
- normalized.scalingCenterY = scaling.centerY;
- }
- }
- }
- if (typeof rotation !== 'undefined') {
- if (Ext.isNumber(rotation)) {
- rotation = Ext.draw.Draw.rad(rotation);
- normalized.rotationRads = rotation;
- } else {
- if ('rads' in rotation) {
- normalized.rotationRads = rotation.rads;
- } else if ('degrees' in rotation) {
- normalized.rotationRads = Ext.draw.Draw.rad(rotation.degrees);
- }
- if ('centerX' in rotation) {
- normalized.rotationCenterX = rotation.centerX;
- }
- if ('centerY' in rotation) {
- normalized.rotationCenterY = rotation.centerY;
- }
- }
- }
- if ('matrix' in changes) {
- matrix = Ext.draw.Matrix.create(changes.matrix);
- split = matrix.split();
- // This will NOT update the transformation matrix of a sprite
- // with the given elements. It will attempt to extract the
- // individual transformation attributes from the transformation matrix
- // elements provided. Then the extracted attributes will be used by
- // the sprite's 'applyTransformations' method to calculate
- // the transformation matrix of the sprite.
- // It's not possible to recover all the information from the given
- // transformation matrix elements. Shearing and centers of rotation
- // and scaling are not recovered.
- // Ideally, this should work like sprite.transform([elements], true),
- // i.e. update the transformation matrix of a sprite directly,
- // without attempting to update sprite's transformation attributes.
- // But we are not changing the behavior (just yet) for compatibility
- // reasons.
- normalized.matrix = matrix;
- normalized.rotationRads = split.rotation;
- normalized.rotationCenterX = 0;
- normalized.rotationCenterY = 0;
- normalized.scalingX = split.scaleX;
- normalized.scalingY = split.scaleY;
- normalized.scalingCenterX = 0;
- normalized.scalingCenterY = 0;
- normalized.translationX = split.translateX;
- normalized.translationY = split.translateY;
- }
- for (name in changes) {
- val = changes[name];
- if (typeof val === 'undefined') {
-
- continue;
- }
- if (name in aliases) {
- name = aliases[name];
- }
- if (name in processors) {
- val = processors[name].call(this, val);
- if (typeof val !== 'undefined') {
- normalized[name] = val;
- }
- } else if (keepUnrecognized) {
- normalized[name] = val;
- }
- }
- return normalized;
- },
- setBypassingNormalization: function(attr, modifierStack, changes) {
- return modifierStack.pushDown(attr, changes);
- },
- set: function(attr, modifierStack, changes) {
- changes = this.normalize(changes);
- return this.setBypassingNormalization(attr, modifierStack, changes);
- }
- });
- /**
- * Ext.draw.Matix is a utility class used to calculate
- * [affine transformation](http://en.wikipedia.org/wiki/Affine_transformation) matrix.
- * The matrix class is used to apply transformations to existing
- * {@link Ext.draw.sprite.Sprite sprites} using a number of convenience transform
- * methods.
- *
- * Transformations configured directly on a sprite are processed in the following order:
- * scaling, rotation, and translation. The matrix class offers additional flexibility.
- * Once a sprite is created, you can use the matrix class's transform methods as many
- * times as needed and in any order you choose.
- *
- * To demonstrate, we'll start with a simple {@link Ext.draw.sprite.Rect rect} sprite
- * with the intent of rotating it 180 degrees with the bottom right corner being the
- * center of rotation. To begin, let's look at the initial, untransformed sprite:
- *
- * @example
- * var drawContainer = new Ext.draw.Container({
- * renderTo: Ext.getBody(),
- * width: 380,
- * height: 380,
- * sprites: [{
- * type: 'rect',
- * width: 100,
- * height: 100,
- * fillStyle: 'red'
- * }]
- * });
- *
- * Next, we'll use the {@link #rotate} and {@link #translate} methods from our matrix
- * class to position the rect sprite.
- *
- * @example
- * var drawContainer = new Ext.draw.Container({
- * renderTo: Ext.getBody(),
- * width: 380,
- * height: 380,
- * sprites: [{
- * type: 'rect',
- * width: 100,
- * height: 100,
- * fillStyle: 'red'
- * }]
- * });
- *
- * var main = drawContainer.getSurface();
- * var rect = main.getItems()[0];
- *
- * var m = new Ext.draw.Matrix().translate(100, 100).
- * rotate(Math.PI).
- * translate(-100, - 100);
- *
- * rect.setTransform(m);
- * main.renderFrame();
- *
- * In the previous example we perform the following steps in order to achieve our
- * desired rotated output:
- *
- * - translate the rect to the right and down by 100
- * - rotate by 180 degrees
- * - translate the rect to the right and down by 100
- *
- * **Note:** A couple of things to note at this stage; 1) the rotation center point is
- * the upper left corner of the sprite by default and 2) with transformations, the
- * sprite itself isn't transformed, but rather the entire coordinate plane of the sprite
- * is transformed. The coordinate plane itself is translated by 100 and then rotated
- * 180 degrees. And that is why in the third step we translate the sprite using
- * negative values. Translating by -100 in the third step results in the sprite
- * visually moving to the right and down within the draw container.
- *
- * Fortunately there is a shortcut we can apply using two optional params of the rotate
- * method allowing us to specify the center point of rotation:
- *
- * @example
- * var drawContainer = new Ext.draw.Container({
- * renderTo: Ext.getBody(),
- * width: 380,
- * height: 380,
- * sprites: [{
- * type: 'rect',
- * width: 100,
- * height: 100,
- * fillStyle: 'red'
- * }]
- * });
- *
- * var main = drawContainer.getSurface();
- * var rect = main.getItems()[0];
- *
- * var m = new Ext.draw.Matrix().rotate(Math.PI, 100, 100);
- *
- * rect.setTransform(m);
- * main.renderFrame();
- *
- *
- * This class is compatible with
- * [SVGMatrix](http://www.w3.org/TR/SVG11/coords.html#InterfaceSVGMatrix) except:
- *
- * 1. Ext.draw.Matrix is not read only
- * 2. Using Number as its values rather than floats
- *
- * Using this class helps to reduce the severe numeric
- * [problem with HTML Canvas and SVG transformation](http://stackoverflow.com/questions/8784405/large-numbers-in-html-canvas-translate-result-in-strange-behavior)
- *
- * Additionally, there's no way to get the current transformation matrix
- * [in Canvas](http://stackoverflow.com/questions/7395813/html5-canvas-get-transform-matrix).
- */
- Ext.define('Ext.draw.Matrix', {
- isMatrix: true,
- statics: {
- /**
- * @static
- * Return the affine matrix that transform two points (x0, y0) and (x1, y1) to (x0p, y0p)
- * and (x1p, y1p)
- * @param {Number} x0
- * @param {Number} y0
- * @param {Number} x1
- * @param {Number} y1
- * @param {Number} x0p
- * @param {Number} y0p
- * @param {Number} x1p
- * @param {Number} y1p
- */
- createAffineMatrixFromTwoPair: function(x0, y0, x1, y1, x0p, y0p, x1p, y1p) {
- var dx = x1 - x0,
- dy = y1 - y0,
- dxp = x1p - x0p,
- dyp = y1p - y0p,
- r = 1 / (dx * dx + dy * dy),
- a = dx * dxp + dy * dyp,
- b = dxp * dy - dx * dyp,
- c = -a * x0 - b * y0,
- f = b * x0 - a * y0;
- return new this(a * r, -b * r, b * r, a * r, c * r + x0p, f * r + y0p);
- },
- /**
- * @static
- * Return the affine matrix that transform two points (x0, y0) and (x1, y1) to (x0p, y0p)
- * and (x1p, y1p)
- * @param {Number} x0
- * @param {Number} y0
- * @param {Number} x1
- * @param {Number} y1
- * @param {Number} x0p
- * @param {Number} y0p
- * @param {Number} x1p
- * @param {Number} y1p
- */
- createPanZoomFromTwoPair: function(x0, y0, x1, y1, x0p, y0p, x1p, y1p) {
- if (arguments.length === 2) {
- return this.createPanZoomFromTwoPair.apply(this, x0.concat(y0));
- }
- // eslint-disable-next-line vars-on-top
- var dx = x1 - x0,
- dy = y1 - y0,
- cx = (x0 + x1) * 0.5,
- cy = (y0 + y1) * 0.5,
- dxp = x1p - x0p,
- dyp = y1p - y0p,
- cxp = (x0p + x1p) * 0.5,
- cyp = (y0p + y1p) * 0.5,
- r = dx * dx + dy * dy,
- rp = dxp * dxp + dyp * dyp,
- scale = Math.sqrt(rp / r);
- return new this(scale, 0, 0, scale, cxp - scale * cx, cyp - scale * cy);
- },
- /**
- * @method
- * @static
- * Create a flyweight to wrap the given array.
- * The flyweight will directly refer the object and the elements can be changed
- * by other methods.
- *
- * Do not hold the instance of flyweight matrix.
- *
- * @param {Array} elements
- * @return {Ext.draw.Matrix}
- */
- fly: (function() {
- var flyMatrix = null,
- simplefly = function(elements) {
- flyMatrix.elements = elements;
- return flyMatrix;
- };
- return function(elements) {
- if (!flyMatrix) {
- flyMatrix = new Ext.draw.Matrix();
- }
- flyMatrix.elements = elements;
- Ext.draw.Matrix.fly = simplefly;
- return flyMatrix;
- };
- })(),
- /**
- * @static
- * Create a matrix from `mat`. If `mat` is already a matrix, returns it.
- * @param {Mixed} mat
- * @return {Ext.draw.Matrix}
- */
- create: function(mat) {
- if (mat instanceof this) {
- return mat;
- }
- return new this(mat);
- }
- },
- /**
- * Create an affine transform matrix.
- *
- * @param {Number} xx Coefficient from x to x
- * @param {Number} xy Coefficient from x to y
- * @param {Number} yx Coefficient from y to x
- * @param {Number} yy Coefficient from y to y
- * @param {Number} dx Offset of x
- * @param {Number} dy Offset of y
- */
- constructor: function(xx, xy, yx, yy, dx, dy) {
- if (xx && xx.length === 6) {
- this.elements = xx.slice();
- } else if (xx !== undefined) {
- this.elements = [
- xx,
- xy,
- yx,
- yy,
- dx,
- dy
- ];
- } else {
- this.elements = [
- 1,
- 0,
- 0,
- 1,
- 0,
- 0
- ];
- }
- },
- /**
- * Prepend a matrix onto the current.
- *
- * __Note:__ The given transform will come after the current one.
- *
- * @param {Number} xx Coefficient from x to x.
- * @param {Number} xy Coefficient from x to y.
- * @param {Number} yx Coefficient from y to x.
- * @param {Number} yy Coefficient from y to y.
- * @param {Number} dx Offset of x.
- * @param {Number} dy Offset of y.
- * @return {Ext.draw.Matrix} this
- */
- prepend: function(xx, xy, yx, yy, dx, dy) {
- var elements = this.elements,
- xx0 = elements[0],
- xy0 = elements[1],
- yx0 = elements[2],
- yy0 = elements[3],
- dx0 = elements[4],
- dy0 = elements[5];
- elements[0] = xx * xx0 + yx * xy0;
- elements[1] = xy * xx0 + yy * xy0;
- elements[2] = xx * yx0 + yx * yy0;
- elements[3] = xy * yx0 + yy * yy0;
- elements[4] = xx * dx0 + yx * dy0 + dx;
- elements[5] = xy * dx0 + yy * dy0 + dy;
- return this;
- },
- /**
- * Prepend a matrix onto the current.
- *
- * __Note:__ The given transform will come after the current one.
- * @param {Ext.draw.Matrix} matrix
- * @return {Ext.draw.Matrix} this
- */
- prependMatrix: function(matrix) {
- return this.prepend.apply(this, matrix.elements);
- },
- /**
- * Postpend a matrix onto the current.
- *
- * __Note:__ The given transform will come before the current one.
- *
- * @param {Number} xx Coefficient from x to x.
- * @param {Number} xy Coefficient from x to y.
- * @param {Number} yx Coefficient from y to x.
- * @param {Number} yy Coefficient from y to y.
- * @param {Number} dx Offset of x.
- * @param {Number} dy Offset of y.
- * @return {Ext.draw.Matrix} this
- */
- append: function(xx, xy, yx, yy, dx, dy) {
- var elements = this.elements,
- xx0 = elements[0],
- xy0 = elements[1],
- yx0 = elements[2],
- yy0 = elements[3],
- dx0 = elements[4],
- dy0 = elements[5];
- elements[0] = xx * xx0 + xy * yx0;
- elements[1] = xx * xy0 + xy * yy0;
- elements[2] = yx * xx0 + yy * yx0;
- elements[3] = yx * xy0 + yy * yy0;
- elements[4] = dx * xx0 + dy * yx0 + dx0;
- elements[5] = dx * xy0 + dy * yy0 + dy0;
- return this;
- },
- /**
- * Postpend a matrix onto the current.
- *
- * __Note:__ The given transform will come before the current one.
- *
- * @param {Ext.draw.Matrix} matrix
- * @return {Ext.draw.Matrix} this
- */
- appendMatrix: function(matrix) {
- return this.append.apply(this, matrix.elements);
- },
- /**
- * Set the elements of a Matrix
- * @param {Number} xx
- * @param {Number} xy
- * @param {Number} yx
- * @param {Number} yy
- * @param {Number} dx
- * @param {Number} dy
- * @return {Ext.draw.Matrix} this
- */
- set: function(xx, xy, yx, yy, dx, dy) {
- var elements = this.elements;
- elements[0] = xx;
- elements[1] = xy;
- elements[2] = yx;
- elements[3] = yy;
- elements[4] = dx;
- elements[5] = dy;
- return this;
- },
- /**
- * Return a new matrix represents the opposite transformation of the current one.
- *
- * @param {Ext.draw.Matrix} [target] A target matrix. If present, it will receive
- * the result of inversion to avoid creating a new object.
- *
- * @return {Ext.draw.Matrix}
- */
- inverse: function(target) {
- var elements = this.elements,
- a = elements[0],
- b = elements[1],
- c = elements[2],
- d = elements[3],
- e = elements[4],
- f = elements[5],
- rDim = 1 / (a * d - b * c);
- a *= rDim;
- b *= rDim;
- c *= rDim;
- d *= rDim;
- if (target) {
- target.set(d, -b, -c, a, c * f - d * e, b * e - a * f);
- return target;
- } else {
- return new Ext.draw.Matrix(d, -b, -c, a, c * f - d * e, b * e - a * f);
- }
- },
- /**
- * Translate the matrix.
- *
- * @param {Number} x
- * @param {Number} y
- * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
- * @return {Ext.draw.Matrix} this
- */
- translate: function(x, y, prepend) {
- if (prepend) {
- return this.prepend(1, 0, 0, 1, x, y);
- } else {
- return this.append(1, 0, 0, 1, x, y);
- }
- },
- /**
- * Scale the matrix.
- *
- * @param {Number} sx
- * @param {Number} sy
- * @param {Number} scx
- * @param {Number} scy
- * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
- * @return {Ext.draw.Matrix} this
- */
- scale: function(sx, sy, scx, scy, prepend) {
- var me = this;
- // null or undefined
- if (sy == null) {
- sy = sx;
- }
- if (scx === undefined) {
- scx = 0;
- }
- if (scy === undefined) {
- scy = 0;
- }
- if (prepend) {
- return me.prepend(sx, 0, 0, sy, scx - scx * sx, scy - scy * sy);
- } else {
- return me.append(sx, 0, 0, sy, scx - scx * sx, scy - scy * sy);
- }
- },
- /**
- * Rotate the matrix.
- *
- * @param {Number} angle Radians to rotate
- * @param {Number|null} rcx Center of rotation.
- * @param {Number|null} rcy Center of rotation.
- * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
- * @return {Ext.draw.Matrix} this
- */
- rotate: function(angle, rcx, rcy, prepend) {
- var me = this,
- cos = Math.cos(angle),
- sin = Math.sin(angle);
- rcx = rcx || 0;
- rcy = rcy || 0;
- if (prepend) {
- return me.prepend(cos, sin, -sin, cos, rcx - cos * rcx + rcy * sin, rcy - cos * rcy - rcx * sin);
- } else {
- return me.append(cos, sin, -sin, cos, rcx - cos * rcx + rcy * sin, rcy - cos * rcy - rcx * sin);
- }
- },
- /**
- * Rotate the matrix by the angle of a vector.
- *
- * @param {Number} x
- * @param {Number} y
- * @param {Boolean} [prepend] If `true`, this will transformation be prepended to the matrix.
- * @return {Ext.draw.Matrix} this
- */
- rotateFromVector: function(x, y, prepend) {
- var me = this,
- d = Math.sqrt(x * x + y * y),
- cos = x / d,
- sin = y / d;
- if (prepend) {
- return me.prepend(cos, sin, -sin, cos, 0, 0);
- } else {
- return me.append(cos, sin, -sin, cos, 0, 0);
- }
- },
- /**
- * Clone this matrix.
- * @return {Ext.draw.Matrix}
- */
- clone: function() {
- return new Ext.draw.Matrix(this.elements);
- },
- /**
- * Horizontally flip the matrix
- * @return {Ext.draw.Matrix} this
- */
- flipX: function() {
- return this.append(-1, 0, 0, 1, 0, 0);
- },
- /**
- * Vertically flip the matrix
- * @return {Ext.draw.Matrix} this
- */
- flipY: function() {
- return this.append(1, 0, 0, -1, 0, 0);
- },
- /**
- * Skew the matrix
- * @param {Number} angle
- * @return {Ext.draw.Matrix} this
- */
- skewX: function(angle) {
- return this.append(1, 0, Math.tan(angle), 1, 0, 0);
- },
- /**
- * Skew the matrix
- * @param {Number} angle
- * @return {Ext.draw.Matrix} this
- */
- skewY: function(angle) {
- return this.append(1, Math.tan(angle), 0, 1, 0, 0);
- },
- /**
- * Shear the matrix along the x-axis.
- * @param factor The horizontal shear factor.
- * @return {Ext.draw.Matrix} this
- */
- shearX: function(factor) {
- return this.append(1, 0, factor, 1, 0, 0);
- },
- /**
- * Shear the matrix along the y-axis.
- * @param factor The vertical shear factor.
- * @return {Ext.draw.Matrix} this
- */
- shearY: function(factor) {
- return this.append(1, factor, 0, 1, 0, 0);
- },
- /**
- * Reset the matrix to identical.
- * @return {Ext.draw.Matrix} this
- */
- reset: function() {
- return this.set(1, 0, 0, 1, 0, 0);
- },
- /* eslint-disable max-len */
- /**
- * @private
- * Split Matrix to `{{devicePixelRatio,c,0},{b,devicePixelRatio,0},{0,0,1}}.{{xx,0,dx},{0,yy,dy},{0,0,1}}`
- * @return {Object} Object with b,c,d=devicePixelRatio,xx,yy,dx,dy
- */
- precisionCompensate: function(devicePixelRatio, comp) {
- /* eslint-enable max-len */
- var elements = this.elements,
- x2x = elements[0],
- x2y = elements[1],
- y2x = elements[2],
- y2y = elements[3],
- newDx = elements[4],
- newDy = elements[5],
- r = x2y * y2x - x2x * y2y;
- comp.b = devicePixelRatio * x2y / x2x;
- comp.c = devicePixelRatio * y2x / y2y;
- comp.d = devicePixelRatio;
- comp.xx = x2x / devicePixelRatio;
- comp.yy = y2y / devicePixelRatio;
- comp.dx = (newDy * x2x * y2x - newDx * x2x * y2y) / r / devicePixelRatio;
- comp.dy = (newDx * x2y * y2y - newDy * x2x * y2y) / r / devicePixelRatio;
- },
- /**
- * @private
- * Split Matrix to `{{1,c,0},{b,d,0},{0,0,1}}.{{xx,0,dx},{0,xx,dy},{0,0,1}}`
- * @return {Object} Object with b,c,d,xx,yy=xx,dx,dy
- */
- precisionCompensateRect: function(devicePixelRatio, comp) {
- var elements = this.elements,
- x2x = elements[0],
- x2y = elements[1],
- y2x = elements[2],
- y2y = elements[3],
- newDx = elements[4],
- newDy = elements[5],
- yxOnXx = y2x / x2x;
- comp.b = devicePixelRatio * x2y / x2x;
- comp.c = devicePixelRatio * yxOnXx;
- comp.d = devicePixelRatio * y2y / x2x;
- comp.xx = x2x / devicePixelRatio;
- comp.yy = x2x / devicePixelRatio;
- comp.dx = (newDy * y2x - newDx * y2y) / (x2y * yxOnXx - y2y) / devicePixelRatio;
- comp.dy = -(newDy * x2x - newDx * x2y) / (x2y * yxOnXx - y2y) / devicePixelRatio;
- },
- /**
- * Transform point returning the x component of the result.
- * @param {Number} x
- * @param {Number} y
- * @return {Number} x component of the result.
- */
- x: function(x, y) {
- var elements = this.elements;
- return x * elements[0] + y * elements[2] + elements[4];
- },
- /**
- * Transform point returning the y component of the result.
- * @param {Number} x
- * @param {Number} y
- * @return {Number} y component of the result.
- */
- y: function(x, y) {
- var elements = this.elements;
- return x * elements[1] + y * elements[3] + elements[5];
- },
- /**
- * @private
- * @param {Number} i
- * @param {Number} j
- * @return {String}
- */
- get: function(i, j) {
- return +this.elements[i + j * 2].toFixed(4);
- },
- /**
- * Transform a point to a new array.
- * @param {Array} point
- * @return {Array}
- */
- transformPoint: function(point) {
- var elements = this.elements,
- x, y;
- if (point.isPoint) {
- x = point.x;
- y = point.y;
- } else {
- x = point[0];
- y = point[1];
- }
- return [
- x * elements[0] + y * elements[2] + elements[4],
- x * elements[1] + y * elements[3] + elements[5]
- ];
- },
- /**
- * @param {Object} bbox Given as `{x: Number, y: Number, width: Number, height: Number}`.
- * @param {Number} [radius]
- * @param {Object} [target] Optional target object to recieve the result.
- * Recommended to use it for better gc.
- *
- * @return {Object} Object with x, y, width and height.
- */
- transformBBox: function(bbox, radius, target) {
- var elements = this.elements,
- l = bbox.x,
- t = bbox.y,
- w0 = bbox.width * 0.5,
- h0 = bbox.height * 0.5,
- xx = elements[0],
- xy = elements[1],
- yx = elements[2],
- yy = elements[3],
- cx = l + w0,
- cy = t + h0,
- w, h, scales;
- if (radius) {
- w0 -= radius;
- h0 -= radius;
- scales = [
- Math.sqrt(elements[0] * elements[0] + elements[2] * elements[2]),
- Math.sqrt(elements[1] * elements[1] + elements[3] * elements[3])
- ];
- w = Math.abs(w0 * xx) + Math.abs(h0 * yx) + Math.abs(scales[0] * radius);
- h = Math.abs(w0 * xy) + Math.abs(h0 * yy) + Math.abs(scales[1] * radius);
- } else {
- w = Math.abs(w0 * xx) + Math.abs(h0 * yx);
- h = Math.abs(w0 * xy) + Math.abs(h0 * yy);
- }
- if (!target) {
- target = {};
- }
- target.x = cx * xx + cy * yx + elements[4] - w;
- target.y = cx * xy + cy * yy + elements[5] - h;
- target.width = w + w;
- target.height = h + h;
- return target;
- },
- /**
- * Transform a list for points.
- *
- * __Note:__ will change the original list but not points inside it.
- * @param {Array} list
- * @return {Array} list
- */
- transformList: function(list) {
- var elements = this.elements,
- xx = elements[0],
- yx = elements[2],
- dx = elements[4],
- xy = elements[1],
- yy = elements[3],
- dy = elements[5],
- ln = list.length,
- p, i;
- for (i = 0; i < ln; i++) {
- p = list[i];
- list[i] = [
- p[0] * xx + p[1] * yx + dx,
- p[0] * xy + p[1] * yy + dy
- ];
- }
- return list;
- },
- /**
- * Determines whether this matrix is an identity matrix (no transform).
- * @return {Boolean}
- */
- isIdentity: function() {
- var elements = this.elements;
- return elements[0] === 1 && elements[1] === 0 && elements[2] === 0 && elements[3] === 1 && elements[4] === 0 && elements[5] === 0;
- },
- /**
- * Determines if this matrix has the same values as another matrix.
- * @param {Ext.draw.Matrix} matrix A maxtrix or array of its elements.
- * @return {Boolean}
- */
- isEqual: function(matrix) {
- var elements = matrix && matrix.isMatrix ? matrix.elements : matrix,
- myElements = this.elements;
- 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];
- },
- /**
- * @deprecated 6.0.1 This method is deprecated.
- * Determines if this matrix has the same values as another matrix.
- * @param {Ext.draw.Matrix} matrix
- * @return {Boolean}
- */
- equals: function(matrix) {
- return this.isEqual(matrix);
- },
- /**
- * Create an array of elements by horizontal order (xx,yx,dx,yx,yy,dy).
- * @return {Array}
- */
- toArray: function() {
- var elements = this.elements;
- return [
- elements[0],
- elements[2],
- elements[4],
- elements[1],
- elements[3],
- elements[5]
- ];
- },
- /**
- * Create an array of elements by vertical order (xx,xy,yx,yy,dx,dy).
- * @return {Array|String}
- */
- toVerticalArray: function() {
- return this.elements.slice();
- },
- /**
- * Get an array of elements.
- * The numbers are rounded to keep only 4 decimals.
- * @return {Array}
- */
- toString: function() {
- var me = this;
- return [
- me.get(0, 0),
- me.get(0, 1),
- me.get(1, 0),
- me.get(1, 1),
- me.get(2, 0),
- me.get(2, 1)
- ].join(',');
- },
- /**
- * Apply the matrix to a drawing context.
- * @param {Object} ctx
- * @return {Ext.draw.Matrix} this
- */
- toContext: function(ctx) {
- ctx.transform.apply(ctx, this.elements);
- return this;
- },
- /**
- * Return a string that can be used as transform attribute in SVG.
- * @return {String}
- */
- toSvg: function() {
- var elements = this.elements;
- // The reason why we cannot use `.join` is the `1e5` form is not accepted in svg.
- 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) + ")";
- },
- /**
- * Get the x scale of the matrix.
- * @return {Number}
- */
- getScaleX: function() {
- var elements = this.elements;
- return Math.sqrt(elements[0] * elements[0] + elements[2] * elements[2]);
- },
- /**
- * Get the y scale of the matrix.
- * @return {Number}
- */
- getScaleY: function() {
- var elements = this.elements;
- return Math.sqrt(elements[1] * elements[1] + elements[3] * elements[3]);
- },
- /**
- * Get x-to-x component of the matrix
- * @return {Number}
- */
- getXX: function() {
- return this.elements[0];
- },
- /**
- * Get x-to-y component of the matrix.
- * @return {Number}
- */
- getXY: function() {
- return this.elements[1];
- },
- /**
- * Get y-to-x component of the matrix.
- * @return {Number}
- */
- getYX: function() {
- return this.elements[2];
- },
- /**
- * Get y-to-y component of the matrix.
- * @return {Number}
- */
- getYY: function() {
- return this.elements[3];
- },
- /**
- * Get offset x component of the matrix.
- * @return {Number}
- */
- getDX: function() {
- return this.elements[4];
- },
- /**
- * Get offset y component of the matrix.
- * @return {Number}
- */
- getDY: function() {
- return this.elements[5];
- },
- /**
- * Splits this transformation matrix into Scale, Rotate, Translate components,
- * assuming it was produced by applying transformations in that order.
- * @return {Object}
- */
- split: function() {
- var el = this.elements,
- xx = el[0],
- xy = el[1],
- yy = el[3],
- out = {
- translateX: el[4],
- translateY: el[5]
- };
- out.rotate = out.rotation = Math.atan2(xy, xx);
- out.scaleX = xx / Math.cos(out.rotate);
- out.scaleY = yy / xx * out.scaleX;
- return out;
- }
- }, function() {
- function registerName(properties, name, i) {
- properties[name] = {
- get: function() {
- return this.elements[i];
- },
- set: function(val) {
- this.elements[i] = val;
- }
- };
- }
- // Compatibility with SVGMatrix.
- if (Object.defineProperties) {
- var properties = {};
- // eslint-disable-line vars-on-top
- /**
- * @property {Number} a Get x-to-x component of the matrix. Avoid using it for performance
- * consideration.
- * Use {@link #getXX} instead.
- */
- registerName(properties, 'a', 0);
- registerName(properties, 'b', 1);
- registerName(properties, 'c', 2);
- registerName(properties, 'd', 3);
- registerName(properties, 'e', 4);
- registerName(properties, 'f', 5);
- Object.defineProperties(this.prototype, properties);
- }
- /**
- * Performs matrix multiplication. This matrix is post-multiplied by another matrix.
- *
- * __Note:__ The given transform will come before the current one.
- *
- * @method
- * @param {Ext.draw.Matrix} matrix
- * @return {Ext.draw.Matrix} this
- */
- this.prototype.multiply = this.prototype.appendMatrix;
- });
- /**
- * @class Ext.draw.modifier.Modifier
- *
- * Each sprite has a stack of modifiers. The resulting attributes of sprite is
- * the content of the stack top. When setting attributes to a sprite,
- * changes will be pushed-down though the stack of modifiers and pop-back the
- * additive changes; When modifier is triggered to change the attribute of a
- * sprite, it will pop-up the changes to the top.
- */
- Ext.define('Ext.draw.modifier.Modifier', {
- isModifier: true,
- mixins: {
- observable: 'Ext.mixin.Observable'
- },
- config: {
- /**
- * @private
- * @cfg {Ext.draw.modifier.Modifier} lower Modifier that receives the push-down changes.
- */
- lower: null,
- /**
- * @private
- * @cfg {Ext.draw.modifier.Modifier} upper Modifier that receives the pop-up changes.
- */
- upper: null,
- /**
- * @cfg {Ext.draw.sprite.Sprite} sprite The sprite to which the modifier belongs.
- */
- sprite: null
- },
- constructor: function(config) {
- this.mixins.observable.constructor.call(this, config);
- },
- updateUpper: function(upper) {
- if (upper) {
- upper.setLower(this);
- }
- },
- updateLower: function(lower) {
- if (lower) {
- lower.setUpper(this);
- }
- },
- /**
- * @private
- * Validate attribute set before use.
- *
- * @param {Object} attr The attribute to be validated. Note that it may be already initialized,
- * so do not override properties that have already been used.
- */
- prepareAttributes: function(attr) {
- if (this._lower) {
- this._lower.prepareAttributes(attr);
- }
- },
- /**
- * @private
- * Invoked when changes need to be popped up to the top.
- * @param {Object} attr The source attributes.
- * @param {Object} changes The changes to be popped up.
- */
- popUp: function(attr, changes) {
- if (this._upper) {
- this._upper.popUp(attr, changes);
- } else {
- Ext.apply(attr, changes);
- }
- },
- /**
- * @private
- *
- * This method will filter out the properties from the `changes` object, if they
- * have the same values as in the `attr` object (sprite's attributes).
- *
- * If the `receiver` object is provided, the attributes with the new values will be
- * copied from the `changes` object to the `receiver` object, and the `changes`
- * object will be left unchanged.
- *
- * The method returns the `receiver` object, if it was provided, or the `changes`
- * object otherwise.
- *
- * The method also handles a special case when a sprite attribute that is meant to be
- * animated was set to a certain value (e.g. 5), that is different from the original
- * value (e.g. 3) of the attribute, and immediately set to another value again, that
- * is the same as the original value (3). In this case, the attribute's current
- * value is still the original value, because the attribute hasn't started animating
- * yet, so a comparison against the current value is not appropriate, and the target
- * value (value at the end of animation, 5) should be used for comparison instead, so
- * that 3 won't be filtered out.
- */
- filterChanges: function(attr, changes, receiver) {
- var targets = attr.targets,
- name, value;
- if (receiver) {
- for (name in changes) {
- value = changes[name];
- if (value !== attr[name] || (targets && value !== targets[name])) {
- receiver[name] = value;
- }
- }
- } else {
- for (name in changes) {
- value = changes[name];
- if (value === attr[name] && (!targets || value === targets[name])) {
- delete changes[name];
- }
- }
- }
- return receiver || changes;
- },
- /**
- * @private
- * Invoked when changes need to be pushed down to the sprite.
- * @param {Object} attr The source attributes.
- * @param {Object} changes The changes to make. This object might be changed unexpectedly
- * inside the method.
- * @return {Mixed}
- */
- pushDown: function(attr, changes) {
- return this._lower ? this._lower.pushDown(attr, changes) : this.filterChanges(attr, changes);
- }
- });
- /**
- * @class Ext.draw.modifier.Target
- * @extends Ext.draw.modifier.Modifier
- *
- * This is the destination (top) modifier that has to be put at
- * the top of the modifier stack.
- *
- * The Target modifier figures out which updaters have to be called
- * for the changed set of attributes and makes the sprite and its instances (if any)
- * call them.
- */
- Ext.define('Ext.draw.modifier.Target', {
- requires: [
- 'Ext.draw.Matrix'
- ],
- extend: 'Ext.draw.modifier.Modifier',
- alias: 'modifier.target',
- statics: {
- /**
- * @private
- */
- uniqueId: 0
- },
- prepareAttributes: function(attr) {
- if (this._lower) {
- this._lower.prepareAttributes(attr);
- }
- attr.attributeId = 'attribute-' + Ext.draw.modifier.Target.uniqueId++;
- if (!attr.hasOwnProperty('canvasAttributes')) {
- attr.bbox = {
- plain: {
- dirty: true
- },
- transform: {
- dirty: true
- }
- };
- attr.dirty = true;
- /*
- Maps updaters that have to be called to the attributes that triggered the update.
- It is basically a reversed `triggers` map (see Ext.draw.sprite.AttributeDefinition),
- but only for those attributes that have changed.
- Pending updaters are called by the Ext.draw.sprite.Sprite.callUpdaters method.
- The 'canvas' updater is a special kind of updater that is not actually a function
- but a flag indicating that the attribute should be applied directly to a canvas
- context.
- */
- attr.pendingUpdaters = {};
- /*
- Holds the attributes that triggered the canvas update (attr.pendingUpdaters.canvas).
- Canvas attributes are applied directly to a canvas context
- by the sprite.useAttributes method.
- */
- attr.canvasAttributes = {};
- attr.matrix = new Ext.draw.Matrix();
- attr.inverseMatrix = new Ext.draw.Matrix();
- }
- },
- /**
- * @private
- * Applies changes to sprite/instance attributes and determines which updaters
- * have to be called as a result of attributes change.
- * @param {Object} attr The source attributes.
- * @param {Object} changes The modifier changes.
- */
- applyChanges: function(attr, changes) {
- Ext.apply(attr, changes);
- // eslint-disable-next-line vars-on-top
- var sprite = this.getSprite(),
- pendingUpdaters = attr.pendingUpdaters,
- triggers = sprite.self.def.getTriggers(),
- updaters, instances, instance, name, hasChanges, canvasAttributes, i, j, ln;
- for (name in changes) {
- hasChanges = true;
- if ((updaters = triggers[name])) {
- sprite.scheduleUpdaters(attr, updaters, [
- name
- ]);
- }
- if (attr.template && changes.removeFromInstance && changes.removeFromInstance[name]) {
- delete attr[name];
- }
- }
- if (!hasChanges) {
- return;
- }
- // This can prevent sub objects to set duplicated attributes to context.
- if (pendingUpdaters.canvas) {
- canvasAttributes = pendingUpdaters.canvas;
- delete pendingUpdaters.canvas;
- for (i = 0 , ln = canvasAttributes.length; i < ln; i++) {
- name = canvasAttributes[i];
- attr.canvasAttributes[name] = attr[name];
- }
- }
- // If the attributes of an instancing sprite template are being modified here,
- // then spread the pending updaters to the instances (template's children).
- if (attr.hasOwnProperty('children')) {
- instances = attr.children;
- for (i = 0 , ln = instances.length; i < ln; i++) {
- instance = instances[i];
- Ext.apply(instance.pendingUpdaters, pendingUpdaters);
- if (canvasAttributes) {
- for (j = 0; j < canvasAttributes.length; j++) {
- name = canvasAttributes[j];
- instance.canvasAttributes[name] = instance[name];
- }
- }
- sprite.callUpdaters(instance);
- }
- }
- sprite.setDirty(true);
- sprite.callUpdaters(attr);
- },
- popUp: function(attr, changes) {
- this.applyChanges(attr, changes);
- },
- pushDown: function(attr, changes) {
- // Modifier chain looks like this:
- // sprite.modifiers.target <---> postFx <---> sprite.modifiers.animation <---> preFx
- // There can be any number of postFx and preFx modifiers, the difference between them
- // is that:
- // `preFx` modifier changes are animated.
- // `postFx` modifier changes are not.
- // preFx modifiers include Highlight (Draw) and Callout (Charts).
- // There are no postFx modifiers at the moment.
- if (this._lower) {
- // Without any postFx modifiers, `lower` is going to be Animation.
- changes = this._lower.pushDown(attr, changes);
- }
- this.applyChanges(attr, changes);
- return changes;
- }
- });
- /**
- * @class Ext.draw.TimingFunctions
- * @singleton
- *
- * Singleton that provides easing functions for use in sprite animations.
- */
- Ext.define('Ext.draw.TimingFunctions', function() {
- var pow = Math.pow,
- sin = Math.sin,
- cos = Math.cos,
- sqrt = Math.sqrt,
- pi = Math.PI,
- poly = [
- 'quad',
- 'cube',
- 'quart',
- 'quint'
- ],
- easings = {
- pow: function(p, x) {
- return pow(p, x || 6);
- },
- expo: function(p) {
- return pow(2, 8 * (p - 1));
- },
- circ: function(p) {
- return 1 - sqrt(1 - p * p);
- },
- sine: function(p) {
- return 1 - sin((1 - p) * pi / 2);
- },
- back: function(p, n) {
- n = n || 1.616;
- return p * p * ((n + 1) * p - n);
- },
- bounce: function(p) {
- var a, b;
- // eslint-disable-next-line no-constant-condition
- for (a = 0 , b = 1; 1; a += b , b /= 2) {
- if (p >= (7 - 4 * a) / 11) {
- return b * b - pow((11 - 6 * a - 11 * p) / 4, 2);
- }
- }
- },
- elastic: function(p, x) {
- return pow(2, 10 * --p) * cos(20 * p * pi * (x || 1) / 3);
- }
- },
- easingsMap = {},
- name, len, i;
- // Create polynomial easing equations.
- function createPoly(times) {
- return function(p) {
- return pow(p, times);
- };
- }
- function addEasing(name, easing) {
- easingsMap[name + 'In'] = function(pos) {
- return easing(pos);
- };
- easingsMap[name + 'Out'] = function(pos) {
- return 1 - easing(1 - pos);
- };
- easingsMap[name + 'InOut'] = function(pos) {
- return (pos <= 0.5) ? easing(2 * pos) / 2 : (2 - easing(2 * (1 - pos))) / 2;
- };
- }
- for (i = 0 , len = poly.length; i < len; ++i) {
- easings[poly[i]] = createPoly(i + 2);
- }
- for (name in easings) {
- addEasing(name, easings[name]);
- }
- // Add linear interpolator.
- easingsMap.linear = Ext.identityFn;
- // Add aliases for quad easings.
- easingsMap.easeIn = easingsMap.quadIn;
- easingsMap.easeOut = easingsMap.quadOut;
- easingsMap.easeInOut = easingsMap.quadInOut;
- return {
- singleton: true,
- easingMap: easingsMap
- };
- }, function(Cls) {
- Ext.apply(Cls, Cls.easingMap);
- });
- /**
- * @class Ext.draw.Animator
- *
- * Singleton class that manages the animation pool.
- */
- Ext.define('Ext.draw.Animator', {
- uses: [
- 'Ext.draw.Draw'
- ],
- singleton: true,
- frameCallbacks: {},
- frameCallbackId: 0,
- scheduled: 0,
- frameStartTimeOffset: Ext.now(),
- animations: [],
- running: false,
- /**
- * Cross platform `animationTime` implementation.
- * @return {Number}
- */
- animationTime: function() {
- return Ext.AnimationQueue.frameStartTime - this.frameStartTimeOffset;
- },
- /**
- * Adds an animated object to the animation pool.
- *
- * @param {Object} animation The animation descriptor to add to the pool.
- */
- add: function(animation) {
- var me = this;
- if (!me.contains(animation)) {
- me.animations.push(animation);
- me.ignite();
- if ('fireEvent' in animation) {
- animation.fireEvent('animationstart', animation);
- }
- }
- },
- /**
- * Removes an animation from the pool.
- * TODO: This is broken when called within `step` method.
- * @param {Object} animation The animation to remove from the pool.
- */
- remove: function(animation) {
- var me = this,
- animations = me.animations,
- i = 0,
- l = animations.length;
- for (; i < l; ++i) {
- if (animations[i] === animation) {
- animations.splice(i, 1);
- if ('fireEvent' in animation) {
- animation.fireEvent('animationend', animation);
- }
- return;
- }
- }
- },
- /**
- * Returns `true` or `false` whether it contains the given animation or not.
- *
- * @param {Object} animation The animation to check for.
- * @return {Boolean}
- */
- contains: function(animation) {
- return Ext.Array.indexOf(this.animations, animation) > -1;
- },
- /**
- * Returns `true` or `false` whether the pool is empty or not.
- * @return {Boolean}
- */
- empty: function() {
- return this.animations.length === 0;
- },
- idle: function() {
- return this.scheduled === 0 && this.animations.length === 0;
- },
- /**
- * Given a frame time it will filter out finished animations from the pool.
- *
- * @param {Number} frameTime The frame's start time, in milliseconds.
- */
- step: function(frameTime) {
- var me = this,
- animations = me.animations,
- animation,
- i = 0,
- ln = animations.length;
- for (; i < ln; i++) {
- animation = animations[i];
- animation.step(frameTime);
- if (!animation.animating) {
- animations.splice(i, 1);
- i--;
- ln--;
- if (animation.fireEvent) {
- animation.fireEvent('animationend', animation);
- }
- }
- }
- },
- /**
- * Register a one-time callback that will be called at the next frame.
- * @param {Function/String} callback
- * @param {Object} scope
- * @return {String} The ID of the scheduled callback.
- */
- schedule: function(callback, scope) {
- var id = 'frameCallback' + (this.frameCallbackId++);
- scope = scope || this;
- if (Ext.isString(callback)) {
- callback = scope[callback];
- }
- Ext.draw.Animator.frameCallbacks[id] = {
- fn: callback,
- scope: scope,
- once: true
- };
- this.scheduled++;
- Ext.draw.Animator.ignite();
- return id;
- },
- /**
- * Register a one-time callback that will be called at the next frame,
- * if that callback (with a matching function and scope) isn't already scheduled.
- * @param {Function/String} callback
- * @param {Object} scope
- * @return {String/null} The ID of the scheduled callback or null, if that callback
- * has already been scheduled.
- */
- scheduleIf: function(callback, scope) {
- var frameCallbacks = Ext.draw.Animator.frameCallbacks,
- cb, id;
- scope = scope || this;
- if (Ext.isString(callback)) {
- callback = scope[callback];
- }
- for (id in frameCallbacks) {
- cb = frameCallbacks[id];
- if (cb.once && cb.fn === callback && cb.scope === scope) {
- return null;
- }
- }
- return this.schedule(callback, scope);
- },
- /**
- * Cancel a registered one-time callback
- * @param {String} id
- */
- cancel: function(id) {
- if (Ext.draw.Animator.frameCallbacks[id] && Ext.draw.Animator.frameCallbacks[id].once) {
- this.scheduled = Math.max(--this.scheduled, 0);
- delete Ext.draw.Animator.frameCallbacks[id];
- Ext.draw.Draw.endUpdateIOS();
- }
- if (this.idle()) {
- this.extinguish();
- }
- },
- clear: function() {
- this.animations.length = 0;
- Ext.draw.Animator.frameCallbacks = {};
- this.extinguish();
- },
- /**
- * Register a recursive callback that will be called at every frame.
- *
- * @param {Function} callback
- * @param {Object} scope
- * @return {String}
- */
- addFrameCallback: function(callback, scope) {
- var id = 'frameCallback' + (this.frameCallbackId++);
- scope = scope || this;
- if (Ext.isString(callback)) {
- callback = scope[callback];
- }
- Ext.draw.Animator.frameCallbacks[id] = {
- fn: callback,
- scope: scope
- };
- return id;
- },
- /**
- * Unregister a recursive callback.
- * @param {String} id
- */
- removeFrameCallback: function(id) {
- delete Ext.draw.Animator.frameCallbacks[id];
- if (this.idle()) {
- this.extinguish();
- }
- },
- /**
- * @private
- */
- fireFrameCallbacks: function() {
- var callbacks = this.frameCallbacks,
- id, fn, cb;
- for (id in callbacks) {
- cb = callbacks[id];
- fn = cb.fn;
- if (Ext.isString(fn)) {
- fn = cb.scope[fn];
- }
- fn.call(cb.scope);
- if (callbacks[id] && cb.once) {
- this.scheduled = Math.max(--this.scheduled, 0);
- delete callbacks[id];
- }
- }
- },
- handleFrame: function() {
- var me = this;
- me.step(me.animationTime());
- me.fireFrameCallbacks();
- if (me.idle()) {
- me.extinguish();
- }
- },
- ignite: function() {
- if (!this.running) {
- this.running = true;
- Ext.AnimationQueue.start(this.handleFrame, this);
- Ext.draw.Draw.beginUpdateIOS();
- }
- },
- extinguish: function() {
- this.running = false;
- Ext.AnimationQueue.stop(this.handleFrame, this);
- Ext.draw.Draw.endUpdateIOS();
- }
- });
- /**
- * The Animation modifier.
- *
- * Sencha Charts allow users to use transitional animation on sprites. Simply set the duration
- * and easing in the animation modifier, then all the changes to the sprites will be animated.
- *
- * @example
- * var drawCt = Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 400,
- * height: 400,
- * sprites: [{
- * type: 'rect',
- * x: 50,
- * y: 50,
- * width: 100,
- * height: 100,
- * fillStyle: '#1F6D91'
- * }]
- * });
- *
- * var rect = drawCt.getSurface().getItems()[0];
- *
- * rect.setAnimation({
- * duration: 1000,
- * easing: 'elasticOut'
- * });
- *
- * Ext.defer(function () {
- * rect.setAttributes({
- * width: 250
- * });
- * }, 500);
- *
- * Also, you can use different durations and easing functions on different attributes by using
- * {@link #customDurations} and {@link #customEasings}.
- *
- * By default, an animation modifier will be created during the initialization of a sprite.
- * You can get the animation modifier of a sprite via its
- * {@link Ext.draw.sprite.Sprite#method-getAnimation getAnimation} method.
- */
- Ext.define('Ext.draw.modifier.Animation', {
- extend: 'Ext.draw.modifier.Modifier',
- alias: 'modifier.animation',
- requires: [
- 'Ext.draw.TimingFunctions',
- 'Ext.draw.Animator'
- ],
- config: {
- /**
- * @cfg {Function} easing
- * Default easing function.
- */
- easing: Ext.identityFn,
- /**
- * @cfg {Number} duration
- * Default duration time (ms).
- */
- duration: 0,
- /**
- * @cfg {Object} customEasings Overrides the default easing function for defined attributes.
- * E.g.:
- *
- * // Assuming the sprite the modifier is applied to is a 'circle'.
- * customEasings: {
- * r: 'easeOut',
- * 'fillStyle,strokeStyle': 'linear',
- * 'cx,cy': function (p, n) {
- * p = 1 - p;
- * n = n || 1.616;
- * return 1 - p * p * ((n + 1) * p - n);
- * }
- * }
- */
- customEasings: {},
- /**
- * @cfg {Object} customDurations Overrides the default duration for defined attributes.
- * E.g.:
- *
- * // Assuming the sprite the modifier is applied to is a 'circle'.
- * customDurations: {
- * r: 1000,
- * 'fillStyle,strokeStyle': 2000,
- * 'cx,cy': 1000
- * }
- */
- customDurations: {}
- },
- constructor: function(config) {
- var me = this;
- me.anyAnimation = me.anySpecialAnimations = false;
- me.animating = 0;
- me.animatingPool = [];
- me.callParent([
- config
- ]);
- },
- prepareAttributes: function(attr) {
- if (!attr.hasOwnProperty('timers')) {
- attr.animating = false;
- attr.timers = {};
- // The 'targets' object is used to hold the target values for the
- // attributes while they are being animated from source to target values.
- // The 'targets' is pushed down to the lower level modifiers,
- // instead of the actual attr object, to hide the fact that the
- // attributes are being animated.
- attr.targets = Ext.Object.chain(attr);
- attr.targets.prototype = attr;
- }
- if (this._lower) {
- this._lower.prepareAttributes(attr.targets);
- }
- },
- updateSprite: function(sprite) {
- this.setConfig(sprite.config.animation);
- },
- updateDuration: function(duration) {
- this.anyAnimation = duration > 0;
- },
- applyEasing: function(easing) {
- if (typeof easing === 'string') {
- easing = Ext.draw.TimingFunctions.easingMap[easing];
- }
- return easing;
- },
- applyCustomEasings: function(newEasings, oldEasings) {
- var any, key, attrs, easing, i, ln;
- oldEasings = oldEasings || {};
- for (key in newEasings) {
- any = true;
- easing = newEasings[key];
- attrs = key.split(',');
- if (typeof easing === 'string') {
- easing = Ext.draw.TimingFunctions.easingMap[easing];
- }
- for (i = 0 , ln = attrs.length; i < ln; i++) {
- oldEasings[attrs[i]] = easing;
- }
- }
- if (any) {
- this.anySpecialAnimations = any;
- }
- return oldEasings;
- },
- /**
- * Set special easings on the given attributes. E.g.:
- *
- * circleSprite.getAnimation().setEasingOn('r', 'elasticIn');
- *
- * @param {String/Array} attrs The source attribute(s).
- * @param {String} easing The special easings.
- */
- setEasingOn: function(attrs, easing) {
- var customEasings = {},
- i, ln;
- attrs = Ext.Array.from(attrs).slice();
- for (i = 0 , ln = attrs.length; i < ln; i++) {
- customEasings[attrs[i]] = easing;
- }
- this.setCustomEasings(customEasings);
- },
- /**
- * Remove special easings on the given attributes.
- * @param {String/Array} attrs The source attribute(s).
- */
- clearEasingOn: function(attrs) {
- var i, ln;
- attrs = Ext.Array.from(attrs, true);
- for (i = 0 , ln = attrs.length; i < ln; i++) {
- delete this._customEasings[attrs[i]];
- }
- },
- applyCustomDurations: function(newDurations, oldDurations) {
- var any, key, duration, attrs, i, ln;
- oldDurations = oldDurations || {};
- for (key in newDurations) {
- any = true;
- duration = newDurations[key];
- attrs = key.split(',');
- for (i = 0 , ln = attrs.length; i < ln; i++) {
- oldDurations[attrs[i]] = duration;
- }
- }
- if (any) {
- this.anySpecialAnimations = any;
- }
- return oldDurations;
- },
- /**
- * Set special duration on the given attributes. E.g.:
- *
- * rectSprite.getAnimation().setDurationOn('height', 2000);
- *
- * @param {String/Array} attrs The source attributes.
- * @param {Number} duration The special duration.
- */
- setDurationOn: function(attrs, duration) {
- var customDurations = {},
- i, ln;
- attrs = Ext.Array.from(attrs).slice();
- for (i = 0 , ln = attrs.length; i < ln; i++) {
- customDurations[attrs[i]] = duration;
- }
- this.setCustomDurations(customDurations);
- },
- /**
- * Remove special easings on the given attributes.
- * @param {Object} attrs The source attributes.
- */
- clearDurationOn: function(attrs) {
- var i, ln;
- attrs = Ext.Array.from(attrs, true);
- for (i = 0 , ln = attrs.length; i < ln; i++) {
- delete this._customDurations[attrs[i]];
- }
- },
- /**
- * @private
- * Initializes Animator for the animation.
- * @param {Object} attr The source attributes.
- * @param {Boolean} animating The animating flag.
- */
- setAnimating: function(attr, animating) {
- var me = this,
- pool = me.animatingPool,
- i;
- if (attr.animating !== animating) {
- attr.animating = animating;
- if (animating) {
- pool.push(attr);
- if (me.animating === 0) {
- Ext.draw.Animator.add(me);
- }
- me.animating++;
- } else {
- for (i = pool.length; i--; ) {
- if (pool[i] === attr) {
- pool.splice(i, 1);
- }
- }
- me.animating = pool.length;
- }
- }
- },
- /**
- * @private
- * Set the attr with given easing and duration.
- * @param {Object} attr The attributes collection.
- * @param {Object} changes The changes that popped up from lower modifier.
- * @return {Object} The changes to pop up.
- */
- setAttrs: function(attr, changes) {
- var me = this,
- timers = attr.timers,
- parsers = me._sprite.self.def._animationProcessors,
- defaultEasing = me._easing,
- defaultDuration = me._duration,
- customDurations = me._customDurations,
- customEasings = me._customEasings,
- anySpecial = me.anySpecialAnimations,
- any = me.anyAnimation || anySpecial,
- targets = attr.targets,
- ignite = false,
- timer, name, newValue, startValue, parser, easing, duration, initial;
- if (!any) {
- // If there is no animation enabled.
- // When applying changes to attributes, simply stop current animation
- // and set the value.
- for (name in changes) {
- if (attr[name] === changes[name]) {
- delete changes[name];
- } else {
- attr[name] = changes[name];
- }
- delete targets[name];
- delete timers[name];
- }
- return changes;
- } else {
- // If any animation.
- for (name in changes) {
- newValue = changes[name];
- startValue = attr[name];
- if (newValue !== startValue && startValue !== undefined && startValue !== null && (parser = parsers[name])) {
- // If this property is animating.
- // Figure out the desired duration and easing.
- easing = defaultEasing;
- duration = defaultDuration;
- if (anySpecial) {
- // Deducing the easing function and duration
- if (name in customEasings) {
- easing = customEasings[name];
- }
- if (name in customDurations) {
- duration = customDurations[name];
- }
- }
- // Transitions betweens color and gradient or between gradients
- // are not supported.
- if (startValue && startValue.isGradient || newValue && newValue.isGradient) {
- duration = 0;
- }
- // If the property is animating
- if (duration) {
- if (!timers[name]) {
- timers[name] = {};
- }
- timer = timers[name];
- timer.start = 0;
- timer.easing = easing;
- timer.duration = duration;
- timer.compute = parser.compute;
- timer.serve = parser.serve || Ext.identityFn;
- timer.remove = changes.removeFromInstance && changes.removeFromInstance[name];
- if (parser.parseInitial) {
- initial = parser.parseInitial(startValue, newValue);
- timer.source = initial[0];
- timer.target = initial[1];
- } else if (parser.parse) {
- timer.source = parser.parse(startValue);
- timer.target = parser.parse(newValue);
- } else {
- timer.source = startValue;
- timer.target = newValue;
- }
- // The animation started. Change to originalVal.
- targets[name] = newValue;
- delete changes[name];
- ignite = true;
-
- continue;
- } else {
- delete targets[name];
- }
- } else {
- delete targets[name];
- }
- // If the property is not animating.
- delete timers[name];
- }
- }
- if (ignite && !attr.animating) {
- me.setAnimating(attr, true);
- }
- return changes;
- },
- /**
- * @private
- *
- * Update attributes to current value according to current animation time.
- * This method will not affect the values of lower layers, but may delete a
- * value from it.
- * @param {Object} attr The source attributes.
- * @return {Object} The changes to pop up or null.
- */
- updateAttributes: function(attr) {
- if (!attr.animating) {
- return {};
- }
- // eslint-disable-next-line vars-on-top
- var changes = {},
- any = false,
- timers = attr.timers,
- targets = attr.targets,
- now = Ext.draw.Animator.animationTime(),
- name, timer, delta;
- // If updated in the same frame, return.
- if (attr.lastUpdate === now) {
- return null;
- }
- for (name in timers) {
- timer = timers[name];
- if (!timer.start) {
- timer.start = now;
- delta = 0;
- } else {
- delta = (now - timer.start) / timer.duration;
- }
- if (delta >= 1) {
- changes[name] = targets[name];
- delete targets[name];
- if (timers[name].remove) {
- changes.removeFromInstance = changes.removeFromInstance || {};
- changes.removeFromInstance[name] = true;
- }
- delete timers[name];
- } else {
- changes[name] = timer.serve(timer.compute(timer.source, timer.target, timer.easing(delta), attr[name]));
- any = true;
- }
- }
- attr.lastUpdate = now;
- this.setAnimating(attr, any);
- return changes;
- },
- pushDown: function(attr, changes) {
- changes = this.callParent([
- attr.targets,
- changes
- ]);
- return this.setAttrs(attr, changes);
- },
- popUp: function(attr, changes) {
- attr = attr.prototype;
- changes = this.setAttrs(attr, changes);
- if (this._upper) {
- return this._upper.popUp(attr, changes);
- } else {
- return Ext.apply(attr, changes);
- }
- },
- /**
- * @private
- * This is called as an animated object in `Ext.draw.Animator`.
- */
- step: function(frameTime) {
- var me = this,
- pool = me.animatingPool.slice(),
- ln = pool.length,
- i = 0,
- attr, changes;
- for (; i < ln; i++) {
- attr = pool[i];
- changes = me.updateAttributes(attr);
- if (changes && me._upper) {
- me._upper.popUp(attr, changes);
- }
- }
- },
- /**
- * Stop all animations affected by this modifier.
- */
- stop: function() {
- var me = this,
- pool = me.animatingPool,
- i, ln;
- this.step();
- for (i = 0 , ln = pool.length; i < ln; i++) {
- pool[i].animating = false;
- }
- me.animatingPool.length = 0;
- me.animating = 0;
- Ext.draw.Animator.remove(me);
- },
- destroy: function() {
- Ext.draw.Animator.remove(this);
- this.callParent();
- }
- });
- /**
- * @class Ext.draw.modifier.Highlight
- * @extends Ext.draw.modifier.Modifier
- *
- * Highlight is a modifier that will override sprite attributes
- * with {@link Ext.draw.modifier.Highlight#style style} attributes
- * when sprite's `highlighted` attribute is true.
- */
- Ext.define('Ext.draw.modifier.Highlight', {
- extend: 'Ext.draw.modifier.Modifier',
- alias: 'modifier.highlight',
- config: {
- /**
- * @cfg {Boolean} enabled 'true' if the highlight is applied.
- */
- enabled: false,
- /**
- * @cfg {Object} style The style attributes of the highlight modifier.
- */
- style: null
- },
- preFx: true,
- applyStyle: function(style, oldStyle) {
- oldStyle = oldStyle || {};
- if (this.getSprite()) {
- Ext.apply(oldStyle, this.getSprite().self.def.normalize(style));
- } else {
- Ext.apply(oldStyle, style);
- }
- return oldStyle;
- },
- prepareAttributes: function(attr) {
- if (!attr.hasOwnProperty('highlightOriginal')) {
- attr.highlighted = false;
- attr.highlightOriginal = Ext.Object.chain(attr);
- attr.highlightOriginal.prototype = attr;
- // A list of attributes that should be removed from a sprite instance
- // when it is unhighlighted.
- attr.highlightOriginal.removeFromInstance = {};
- }
- if (this._lower) {
- this._lower.prepareAttributes(attr.highlightOriginal);
- }
- },
- updateSprite: function(sprite, oldSprite) {
- var me = this,
- style = me.getStyle(),
- attributeDefinitions;
- if (sprite) {
- attributeDefinitions = sprite.self.def;
- if (style) {
- me._style = attributeDefinitions.normalize(style);
- }
- me.setStyle(sprite.config.highlight);
- // Add highlight related attributes to sprite's attribute definition.
- // This will affect all sprites of the same type, even those without
- // the highlight modifier.
- attributeDefinitions.setConfig({
- defaults: {
- highlighted: false
- },
- processors: {
- highlighted: 'bool'
- }
- });
- }
- this.setSprite(sprite);
- },
- /**
- * @private
- * Filter out modifier changes that override highlight style or source attributes.
- * @param {Object} attr The source attributes.
- * @param {Object} changes The modifier changes.
- * @return {*} The filtered changes.
- */
- filterChanges: function(attr, changes) {
- var me = this,
- highlightOriginal = attr.highlightOriginal,
- style = me.getStyle(),
- name;
- if (attr.highlighted) {
- for (name in changes) {
- if (style.hasOwnProperty(name)) {
- // If sprite is highlighted, then stash the changes
- // to the `style` attributes made by lower level modifiers
- // to apply them later when sprite is unhighlighted.
- highlightOriginal[name] = changes[name];
- delete changes[name];
- }
- }
- }
- return changes;
- },
- pushDown: function(attr, changes) {
- var style = this.getStyle(),
- highlightOriginal = attr.highlightOriginal,
- removeFromInstance = highlightOriginal.removeFromInstance,
- highlighted, name, tplAttr, timer;
- if (changes.hasOwnProperty('highlighted')) {
- highlighted = changes.highlighted;
- // Hide `highlighted` and `style` from underlying modifiers.
- delete changes.highlighted;
- if (this._lower) {
- changes = this._lower.pushDown(highlightOriginal, changes);
- }
- changes = this.filterChanges(attr, changes);
- if (highlighted !== attr.highlighted) {
- if (highlighted) {
- // Switching ON.
- // At this time, original should be empty.
- for (name in style) {
- // Remember the values of attributes to revert back to them on unhighlight.
- if (name in changes) {
- // Remember value set by lower level modifiers.
- highlightOriginal[name] = changes[name];
- } else {
- // Remember the original value.
- // If this is a sprite instance and it doesn't have its own
- // 'name' attribute, (i.e. inherits template's attribute value)
- // than we have to get the value for the 'name' attribute from
- // the template's 'targets' object instead of its
- // 'attr' object (which is the prototype of the instance),
- // because the 'name' attribute of the template may be animating.
- // Check out the prepareAttributes method of the Animation
- // modifier for more details on the 'targets' object.
- tplAttr = attr.template && attr.template.ownAttr;
- if (tplAttr && !attr.prototype.hasOwnProperty(name)) {
- removeFromInstance[name] = true;
- highlightOriginal[name] = tplAttr.targets[name];
- } else {
- // Even if a sprite instance has its own property, it may
- // still have to be removed from the instance after
- // unhighlighting is done.
- // Consider a situation where an instance doesn't originally
- // have its own attribute (that is used for highlighting and
- // unhighlighting). It will however have that attribute as
- // its own when the highlight/unhighlight animation is in
- // progress, until the attribute is removed from the instance
- // when the unhighlighting is done.
- // So in a scenario where the instance is highlighted, then
- // unhighlighted (i.e. starts animating back to its original
- // value) and then highlighted again before the unhighlight
- // animation is done, we should still mark the attribute
- // for removal from the instance, if it was our original
- // intention. To tell if it was, we can check the timer
- // for the attribute and see if the 'remove' flag is set.
- timer = highlightOriginal.timers[name];
- if (timer && timer.remove) {
- removeFromInstance[name] = true;
- }
- highlightOriginal[name] = attr[name];
- }
- }
- if (highlightOriginal[name] !== style[name]) {
- changes[name] = style[name];
- }
- }
- } else {
- // Switching OFF.
- for (name in style) {
- if (!(name in changes)) {
- changes[name] = highlightOriginal[name];
- }
- delete highlightOriginal[name];
- }
- changes.removeFromInstance = changes.removeFromInstance || {};
- // Let the higher lever animation modifier know which attributes
- // should be removed from instance when the animation is done.
- Ext.apply(changes.removeFromInstance, removeFromInstance);
- highlightOriginal.removeFromInstance = {};
- }
- changes.highlighted = highlighted;
- }
- } else {
- if (this._lower) {
- changes = this._lower.pushDown(highlightOriginal, changes);
- }
- changes = this.filterChanges(attr, changes);
- }
- return changes;
- },
- popUp: function(attr, changes) {
- changes = this.filterChanges(attr, changes);
- this.callParent([
- attr,
- changes
- ]);
- }
- });
- /**
- * A sprite is a basic primitive from the charts package which represents a graphical
- * object that can be drawn. Sprites are used extensively in the charts package to
- * create the visual elements of each chart. You can also create a desired image by
- * adding one or more sprites to a {@link Ext.draw.Container draw container}.
- *
- * The Sprite class itself is an abstract class and is not meant to be used directly.
- * There are many different kinds of sprites available in the charts package that extend
- * Ext.draw.sprite.Sprite. Each sprite type has various attributes that define how that
- * sprite should look. For example, this is a {@link Ext.draw.sprite.Rect rect} sprite:
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 400,
- * height: 400,
- * sprites: [{
- * type: 'rect',
- * x: 50,
- * y: 50,
- * width: 100,
- * height: 100,
- * fillStyle: '#1F6D91'
- * }]
- * });
- *
- * By default, sprites are added to the default 'main' {@link Ext.draw.Surface surface}
- * of the draw container. However, sprites may also be configured with a reference to a
- * specific Ext.draw.Surface when set in the draw container's
- * {@link Ext.draw.Container#cfg-sprites sprites} config. Specifying a surface
- * other than 'main' will create a surface by that name if it does not already exist.
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 400,
- * height: 400,
- * sprites: [{
- * type: 'rect',
- * surface: 'anim', // a surface with id "anim" will be created automatically
- * x: 50,
- * y: 50,
- * width: 100,
- * height: 100,
- * fillStyle: '#1F6D91'
- * }]
- * });
- *
- * The ability to have multiple surfaces is useful for performance (and battery life)
- * reasons. Because changes to sprite attributes cause the whole surface (and all
- * sprites in it) to re-render, it makes sense to group sprites by surface, so changes
- * to one group of sprites will only trigger the surface they are in to re-render.
- *
- * You can add a sprite to an existing drawing by adding the sprite to a draw surface.
- *
- * @example
- * var drawCt = Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 400,
- * height: 400
- * });
- *
- * // If the surface name is not specified then 'main' will be used
- * var surface = drawCt.getSurface();
- *
- * surface.add({
- * type: 'rect',
- * x: 50,
- * y: 50,
- * width: 100,
- * height: 100,
- * fillStyle: '#1F6D91'
- * });
- *
- * surface.renderFrame();
- *
- * **Note:** Changes to the sprites on a surface will be not be reflected in the DOM
- * until you call the surface's {@link Ext.draw.Surface#method-renderFrame renderFrame}
- * method. This must be done after adding, removing, or modifying sprites in order to
- * see the changes on-screen.
- *
- * For information on configuring a sprite with an initial transformation see
- * {@link #scaling}, {@link #rotation}, and {@link #translation}.
- *
- * For information on applying a transformation to an existing sprite see the
- * Ext.draw.Matrix class.
- */
- Ext.define('Ext.draw.sprite.Sprite', {
- alias: 'sprite.sprite',
- mixins: {
- observable: 'Ext.mixin.Observable'
- },
- requires: [
- 'Ext.draw.Draw',
- 'Ext.draw.gradient.Gradient',
- 'Ext.draw.sprite.AttributeDefinition',
- 'Ext.draw.modifier.Target',
- 'Ext.draw.modifier.Animation',
- 'Ext.draw.modifier.Highlight'
- ],
- isSprite: true,
- $configStrict: false,
- statics: {
- //<debug>
- /* eslint-disable max-len */
- /**
- * Debug rendering options:
- *
- * debug: {
- * bbox: true, // renders the bounding box of the sprite
- * xray: true // renders control points of the path (for Ext.draw.sprite.Path and descendants only)
- * }
- *
- */
- debug: false,
- /* eslint-enable max-len */
- //</debug>
- defaultHitTestOptions: {
- fill: true,
- stroke: true
- }
- },
- inheritableStatics: {
- def: {
- processors: {
- //<debug>
- debug: 'default',
- //</debug>
- /**
- * @cfg {String} [strokeStyle="none"] The color of the stroke (a CSS color value).
- */
- strokeStyle: "color",
- /**
- * @cfg {String} [fillStyle="none"] The color of the shape (a CSS color value).
- */
- fillStyle: "color",
- /**
- * @cfg {Number} [strokeOpacity=1] The opacity of the stroke. Limited from 0 to 1.
- */
- strokeOpacity: "limited01",
- /**
- * @cfg {Number} [fillOpacity=1] The opacity of the fill. Limited from 0 to 1.
- */
- fillOpacity: "limited01",
- /**
- * @cfg {Number} [lineWidth=1] The width of the line stroke.
- */
- lineWidth: "number",
- /**
- * @cfg {String} [lineCap="butt"] The style of the line caps.
- */
- lineCap: "enums(butt,round,square)",
- /**
- * @cfg {String} [lineJoin="miter"] The style of the line join.
- */
- lineJoin: "enums(round,bevel,miter)",
- /**
- * @cfg {Array} [lineDash=[]]
- * An even number of non-negative numbers specifying a dash/space sequence.
- * Note that while this is supported in IE8 (VML engine), the behavior is
- * different from Canvas and SVG. Please refer to this document for details:
- * http://msdn.microsoft.com/en-us/library/bb264085(v=vs.85).aspx
- * Although IE9 and IE10 have Canvas support, the 'lineDash'
- * attribute is not supported in those browsers.
- */
- lineDash: "data",
- /**
- * @cfg {Number} [lineDashOffset=0]
- * A number specifying how far into the line dash sequence drawing commences.
- */
- lineDashOffset: "number",
- /**
- * @cfg {Number} [miterLimit=10]
- * Sets the distance between the inner corner and the outer corner
- * where two lines meet.
- */
- miterLimit: "number",
- /**
- * @cfg {String} [shadowColor="none"] The color of the shadow (a CSS color value).
- */
- shadowColor: "color",
- /**
- * @cfg {Number} [shadowOffsetX=0] The offset of the sprite's shadow on the x-axis.
- */
- shadowOffsetX: "number",
- /**
- * @cfg {Number} [shadowOffsetY=0] The offset of the sprite's shadow on the y-axis.
- */
- shadowOffsetY: "number",
- /**
- * @cfg {Number} [shadowBlur=0] The amount blur used on the shadow.
- */
- shadowBlur: "number",
- /**
- * @cfg {Number} [globalAlpha=1] The opacity of the sprite. Limited from 0 to 1.
- */
- globalAlpha: "limited01",
- /**
- * @cfg {String} [globalCompositeOperation=source-over]
- * Indicates how source images are drawn onto a destination image.
- * globalCompositeOperation attribute is not supported by the SVG and VML
- * (excanvas) engines.
- */
- // eslint-disable-next-line max-len
- globalCompositeOperation: "enums(source-over,destination-over,source-in,destination-in,source-out,destination-out,source-atop,destination-atop,lighter,xor,copy)",
- /**
- * @cfg {Boolean} [hidden=false] Determines whether or not the sprite is hidden.
- */
- hidden: "bool",
- /**
- * @cfg {Boolean} [transformFillStroke=false]
- * Determines whether the fill and stroke are affected by sprite transformations.
- */
- transformFillStroke: "bool",
- /**
- * @cfg {Number} [zIndex=0]
- * The stacking order of the sprite.
- */
- zIndex: "number",
- /**
- * @cfg {Number} [translationX=0]
- * The translation, position offset, of the sprite on the x-axis.
- *
- * **Note:** Transform configs are *always* performed in the following
- * order:
- *
- * 1. Scaling
- * 2. Rotation
- * 3. Translation
- *
- * See also: {@link #translation} and {@link #translationY}
- */
- translationX: "number",
- /**
- * @cfg {Number} [translationY=0]
- * The translation, position offset, of the sprite on the y-axis.
- *
- * **Note:** Transform configs are *always* performed in the following
- * order:
- *
- * 1. Scaling
- * 2. Rotation
- * 3. Translation
- *
- * See also: {@link #translation} and {@link #translationX}
- */
- translationY: "number",
- /**
- * @cfg {Number} [rotationRads=0]
- * The angle of rotation of the sprite in radians.
- *
- * **Note:** Transform configs are *always* performed in the following
- * order:
- *
- * 1. Scaling
- * 2. Rotation
- * 3. Translation
- *
- * See also: {@link #rotation}, {@link #rotationCenterX}, and
- * {@link #rotationCenterY}
- */
- rotationRads: "number",
- /**
- * @cfg {Number} [rotationCenterX=null]
- * The central coordinate of the sprite's scale operation on the x-axis.
- * Unless explicitly set, will default to the calculated center of the
- * sprite along the x-axis.
- *
- * **Note:** Transform configs are *always* performed in the following
- * order:
- *
- * 1. Scaling
- * 2. Rotation
- * 3. Translation
- *
- * See also: {@link #rotation}, {@link #rotationRads}, and
- * {@link #rotationCenterY}
- */
- rotationCenterX: "number",
- /**
- * @cfg {Number} [rotationCenterY=null]
- * The central coordinate of the sprite's rotate operation on the y-axis.
- * Unless explicitly set, will default to the calculated center of the
- * sprite along the y-axis.
- *
- * **Note:** Transform configs are *always* performed in the following
- * order:
- *
- * 1. Scaling
- * 2. Rotation
- * 3. Translation
- *
- * See also: {@link #rotation}, {@link #rotationRads}, and
- * {@link #rotationCenterX}
- */
- rotationCenterY: "number",
- /**
- * @cfg {Number} [scalingX=1] The scaling of the sprite on the x-axis.
- * The number value represents a percentage by which to scale the
- * sprite. **1** is equal to 100%, **2** would be 200%, etc.
- *
- * **Note:** Transform configs are *always* performed in the following
- * order:
- *
- * 1. Scaling
- * 2. Rotation
- * 3. Translation
- *
- * See also: {@link #scaling}, {@link #scalingY},
- * {@link #scalingCenterX}, and {@link #scalingCenterY}
- */
- scalingX: "number",
- /**
- * @cfg {Number} [scalingY=1] The scaling of the sprite on the y-axis.
- * The number value represents a percentage by which to scale the
- * sprite. **1** is equal to 100%, **2** would be 200%, etc.
- *
- * **Note:** Transform configs are *always* performed in the following
- * order:
- *
- * 1. Scaling
- * 2. Rotation
- * 3. Translation
- *
- * See also: {@link #scaling}, {@link #scalingX},
- * {@link #scalingCenterX}, and {@link #scalingCenterY}
- */
- scalingY: "number",
- /**
- * @cfg {Number} [scalingCenterX=null]
- * The central coordinate of the sprite's scale operation on the x-axis.
- *
- * **Note:** Transform configs are *always* performed in the following
- * order:
- *
- * 1. Scaling
- * 2. Rotation
- * 3. Translation
- *
- * See also: {@link #scaling}, {@link #scalingX},
- * {@link #scalingY}, and {@link #scalingCenterY}
- */
- scalingCenterX: "number",
- /**
- * @cfg {Number} [scalingCenterY=null]
- * The central coordinate of the sprite's scale operation on the y-axis.
- *
- * **Note:** Transform configs are *always* performed in the following
- * order:
- *
- * 1. Scaling
- * 2. Rotation
- * 3. Translation
- *
- * See also: {@link #scaling}, {@link #scalingX},
- * {@link #scalingY}, and {@link #scalingCenterX}
- */
- scalingCenterY: "number",
- constrainGradients: "bool"
- },
- /**
- * @cfg {Number/Object} rotation
- * Applies an initial angle of rotation to the sprite. May be a number
- * specifying the rotation in degrees. Or may be a config object using
- * the below config options.
- *
- * **Note:** Rotation config options will be overridden by values set on
- * the {@link #rotationRads}, {@link #rotationCenterX}, and
- * {@link #rotationCenterY} configs.
- *
- * Ext.create({
- * xtype: 'draw',
- * renderTo: Ext.getBody(),
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'rect',
- * x: 50,
- * y: 50,
- * width: 100,
- * height: 100,
- * fillStyle: '#1F6D91',
- * //rotation: 45
- * rotation: {
- * degrees: 45,
- * //rads: Math.PI / 4,
- * //centerX: 50,
- * //centerY: 50
- * }
- * }]
- * });
- *
- * **Note:** Transform configs are *always* performed in the following
- * order:
- *
- * 1. Scaling
- * 2. Rotation
- * 3. Translation
- *
- * @cfg {Number} rotation.rads
- * The angle in radians to rotate the sprite
- *
- * @cfg {Number} rotation.degrees
- * The angle in degrees to rotate the sprite (is ignored if rads or
- * {@link #rotationRads} is set
- *
- * @cfg {Number} rotation.centerX
- * The central coordinate of the sprite's rotation on the x-axis.
- * Unless explicitly set, will default to the calculated center of the
- * sprite along the x-axis.
- *
- * @cfg {Number} rotation.centerY
- * The central coordinate of the sprite's rotation on the y-axis.
- * Unless explicitly set, will default to the calculated center of the
- * sprite along the y-axis.
- */
- /**
- * @cfg {Number/Object} scaling
- * Applies initial scaling to the sprite. May be a number specifying
- * the amount to scale both the x and y-axis. The number value
- * represents a percentage by which to scale the sprite. **1** is equal
- * to 100%, **2** would be 200%, etc. Or may be a config object using
- * the below config options.
- *
- * **Note:** Scaling config options will be overridden by values set on
- * the {@link #scalingX}, {@link #scalingY}, {@link #scalingCenterX},
- * and {@link #scalingCenterY} configs.
- *
- * Ext.create({
- * xtype: 'draw',
- * renderTo: Ext.getBody(),
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'rect',
- * x: 50,
- * y: 50,
- * width: 100,
- * height: 100,
- * fillStyle: '#1F6D91',
- * //scaling: 2,
- * scaling: {
- * x: 2,
- * y: 2
- * //centerX: 100,
- * //centerY: 100
- * }
- * }]
- * });
- *
- * **Note:** Transform configs are *always* performed in the following
- * order:
- *
- * 1. Scaling
- * 2. Rotation
- * 3. Translation
- *
- * @cfg {Number} scaling.x
- * The amount by which to scale the sprite along the x-axis. The number
- * value represents a percentage by which to scale the sprite. **1** is
- * equal to 100%, **2** would be 200%, etc.
- *
- * @cfg {Number} scaling.y
- * The amount by which to scale the sprite along the y-axis. The number
- * value represents a percentage by which to scale the sprite. **1** is
- * equal to 100%, **2** would be 200%, etc.
- *
- * @cfg scaling.centerX
- * The central coordinate of the sprite's scaling on the x-axis. Unless
- * explicitly set, will default to the calculated center of the sprite
- * along the x-axis.
- *
- * @cfg {Number} scaling.centerY
- * The central coordinate of the sprite's scaling on the y-axis. Unless
- * explicitly set, will default to the calculated center of the sprite
- * along the y-axis.
- */
- /**
- * @cfg {Object} translation
- * Applies an initial translation, adjustment in x/y positioning, to the
- * sprite.
- *
- * **Note:** Translation config options will be overridden by values set
- * on the {@link #translationX} and {@link #translationY} configs.
- *
- * Ext.create({
- * xtype: 'draw',
- * renderTo: Ext.getBody(),
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'rect',
- * x: 50,
- * y: 50,
- * width: 100,
- * height: 100,
- * fillStyle: '#1F6D91',
- * translation: {
- * x: 50,
- * y: 50
- * }
- * }]
- * });
- *
- * **Note:** Transform configs are *always* performed in the following
- * order:
- *
- * 1. Scaling
- * 2. Rotation
- * 3. Translation
- *
- * @cfg {Number} translation.x
- * The amount to translate the sprite along the x-axis.
- *
- * @cfg {Number} translation.y
- * The amount to translate the sprite along the y-axis.
- */
- aliases: {
- "stroke": "strokeStyle",
- "fill": "fillStyle",
- "color": "fillStyle",
- "stroke-width": "lineWidth",
- "stroke-linecap": "lineCap",
- "stroke-linejoin": "lineJoin",
- "stroke-miterlimit": "miterLimit",
- "text-anchor": "textAlign",
- "opacity": "globalAlpha",
- translateX: "translationX",
- translateY: "translationY",
- rotateRads: "rotationRads",
- rotateCenterX: "rotationCenterX",
- rotateCenterY: "rotationCenterY",
- scaleX: "scalingX",
- scaleY: "scalingY",
- scaleCenterX: "scalingCenterX",
- scaleCenterY: "scalingCenterY"
- },
- defaults: {
- hidden: false,
- zIndex: 0,
- strokeStyle: "none",
- fillStyle: "none",
- lineWidth: 1,
- lineDash: [],
- lineDashOffset: 0,
- lineCap: "butt",
- lineJoin: "miter",
- miterLimit: 10,
- shadowColor: "none",
- shadowOffsetX: 0,
- shadowOffsetY: 0,
- shadowBlur: 0,
- globalAlpha: 1,
- strokeOpacity: 1,
- fillOpacity: 1,
- transformFillStroke: false,
- translationX: 0,
- translationY: 0,
- rotationRads: 0,
- rotationCenterX: null,
- rotationCenterY: null,
- scalingX: 1,
- scalingY: 1,
- scalingCenterX: null,
- scalingCenterY: null,
- constrainGradients: false
- },
- triggers: {
- zIndex: "zIndex",
- globalAlpha: "canvas",
- globalCompositeOperation: "canvas",
- transformFillStroke: "canvas",
- strokeStyle: "canvas",
- fillStyle: "canvas",
- strokeOpacity: "canvas",
- fillOpacity: "canvas",
- lineWidth: "canvas",
- lineCap: "canvas",
- lineJoin: "canvas",
- lineDash: "canvas",
- lineDashOffset: "canvas",
- miterLimit: "canvas",
- shadowColor: "canvas",
- shadowOffsetX: "canvas",
- shadowOffsetY: "canvas",
- shadowBlur: "canvas",
- translationX: "transform",
- translationY: "transform",
- rotationRads: "transform",
- rotationCenterX: "transform",
- rotationCenterY: "transform",
- scalingX: "transform",
- scalingY: "transform",
- scalingCenterX: "transform",
- scalingCenterY: "transform",
- constrainGradients: "canvas"
- },
- updaters: {
- // 'bbox' updater is meant to be called by subclasses when changes
- // to attributes are expected to result in a change in sprite's dimensions.
- bbox: 'bboxUpdater',
- zIndex: function(attr) {
- attr.dirtyZIndex = true;
- },
- transform: function(attr) {
- attr.dirtyTransform = true;
- attr.bbox.transform.dirty = true;
- }
- }
- }
- },
- /**
- * @property {Object} attr
- * The visual attributes of the sprite, e.g. strokeStyle, fillStyle, lineWidth...
- */
- /**
- * @cfg {Ext.draw.modifier.Animation} animation
- * @accessor
- */
- config: {
- /**
- * @private
- * @cfg {Ext.draw.Surface/Ext.draw.sprite.Instancing/Ext.draw.sprite.Composite} parent
- * The immediate parent of the sprite. Not necessarily a surface.
- */
- parent: null,
- /**
- * @private
- * @cfg {Ext.draw.Surface} surface
- * The surface that this sprite is rendered into.
- * This config is not meant to be used directly.
- * Please use the {@link Ext.draw.Surface#add} method instead.
- */
- surface: null
- },
- onClassExtended: function(subClass, data) {
- // The `def` here is no longer a config, but an instance
- // of the AttributeDefinition class created with that config,
- // which can now be retrieved from `initialConfig`.
- var superclassCfg = subClass.superclass.self.def.initialConfig,
- ownCfg = data.inheritableStatics && data.inheritableStatics.def,
- cfg;
- // If sprite defines attributes of its own, merge that with those of its parent.
- if (ownCfg) {
- cfg = Ext.Object.merge({}, superclassCfg, ownCfg);
- subClass.def = new Ext.draw.sprite.AttributeDefinition(cfg);
- delete data.inheritableStatics.def;
- } else {
- subClass.def = new Ext.draw.sprite.AttributeDefinition(superclassCfg);
- }
- subClass.def.spriteClass = subClass;
- },
- constructor: function(config) {
- //<debug>
- if (Ext.getClassName(this) === 'Ext.draw.sprite.Sprite') {
- throw 'Ext.draw.sprite.Sprite is an abstract class';
- }
- //</debug>
- // eslint-disable-next-line vars-on-top
- var me = this,
- attributeDefinition = me.self.def,
- // It is important to get defaults (make sure
- // 'defaults' config applier of the AttributeDefinition is called,
- // since it is initialized lazily) before the attributes
- // are initialized ('initializeAttributes' call).
- defaults = attributeDefinition.getDefaults(),
- processors = attributeDefinition.getProcessors(),
- modifiers, name;
- config = Ext.isObject(config) ? config : {};
- me.id = config.id || Ext.id(null, 'ext-sprite-');
- me.attr = {};
- // Observable's constructor also calls the initConfig for us.
- me.mixins.observable.constructor.apply(me, arguments);
- modifiers = Ext.Array.from(config.modifiers, true);
- me.createModifiers(modifiers);
- me.initializeAttributes();
- me.setAttributes(defaults, true);
- //<debug>
- for (name in config) {
- if (name in processors && me['get' + name.charAt(0).toUpperCase() + name.substr(1)]) {
- Ext.raise('The ' + me.$className + ' sprite has both a config and an attribute with the same name: ' + name + '.');
- }
- }
- //</debug>
- me.setAttributes(config);
- },
- updateSurface: function(surface, oldSurface) {
- if (oldSurface) {
- oldSurface.remove(this);
- }
- },
- /**
- * @private
- * Current state of the sprite.
- * Set to `true` if the sprite needs to be repainted.
- * @cfg {Boolean} dirty
- * @accessor
- */
- getDirty: function() {
- return this.attr.dirty;
- },
- setDirty: function(dirty) {
- var parent;
- // This could have been a regular attribute.
- // Instead, it's a hidden one, which is initialized inside in the
- // Target's modifier `prepareAttributes` method and is exposed
- // as a config. The idea is to skip the modifier chain when
- // we simply need to change the sprite's state and notify
- // the sprite's parent.
- this.attr.dirty = dirty;
- if (dirty) {
- parent = this.getParent();
- if (parent) {
- parent.setDirty(true);
- }
- }
- },
- addModifier: function(modifier, reinitializeAttributes) {
- var me = this,
- mods = me.modifiers,
- animation = mods.animation,
- target = mods.target,
- type;
- if (!(modifier instanceof Ext.draw.modifier.Modifier)) {
- type = typeof modifier === 'string' ? modifier : modifier.type;
- if (type && !mods[type]) {
- mods[type] = modifier = Ext.factory(modifier, null, null, 'modifier');
- }
- }
- modifier.setSprite(me);
- if (modifier.preFx || modifier.config && modifier.config.preFx) {
- if (animation._lower) {
- animation._lower.setUpper(modifier);
- }
- modifier.setUpper(animation);
- } else {
- target._lower.setUpper(modifier);
- modifier.setUpper(target);
- }
- if (reinitializeAttributes) {
- me.initializeAttributes();
- }
- return modifier;
- },
- createModifiers: function(modifiers) {
- var me = this,
- Modifier = Ext.draw.modifier,
- animation = me.getInitialConfig().animation,
- mods, i, ln;
- // Create default modifiers.
- me.modifiers = mods = {
- target: new Modifier.Target({
- sprite: me
- }),
- animation: new Modifier.Animation(Ext.apply({
- sprite: me
- }, animation))
- };
- // Link modifiers.
- mods.animation.setUpper(mods.target);
- for (i = 0 , ln = modifiers.length; i < ln; i++) {
- me.addModifier(modifiers[i], false);
- }
- return mods;
- },
- /**
- * Returns the current animation instance.
- * return {Ext.draw.modifier.Animation} The animation modifier used to animate the
- * sprite
- */
- getAnimation: function() {
- return this.modifiers.animation;
- },
- /**
- * Sets the animation config used by the sprite when animating the sprite's
- * attributes and transformation properties.
- *
- * var drawCt = Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 400,
- * height: 400,
- * sprites: [{
- * type: 'rect',
- * x: 50,
- * y: 50,
- * width: 100,
- * height: 100,
- * fillStyle: '#1F6D91'
- * }]
- * });
- *
- * var rect = drawCt.getSurface().getItems()[0];
- *
- * rect.setAnimation({
- * duration: 1000,
- * easing: 'elasticOut'
- * });
- *
- * Ext.defer(function () {
- * rect.setAttributes({
- * width: 250
- * });
- * }, 500);
- *
- * @param {Object} config The Ext.draw.modifier.Animation config for this sprite's
- * animations.
- */
- setAnimation: function(config) {
- if (!this.isConfiguring) {
- this.modifiers.animation.setConfig(config || {
- duration: 0
- });
- }
- },
- initializeAttributes: function() {
- this.modifiers.target.prepareAttributes(this.attr);
- },
- /**
- * @private
- * Calls updaters triggered by changes to sprite attributes.
- * @param attr The attributes of a sprite or its instance.
- */
- callUpdaters: function(attr) {
- var me = this,
- updaters = me.self.def.getUpdaters(),
- any = false,
- dirty = false,
- pendingUpdaters, flags, updater, fn;
- attr = attr || this.attr;
- pendingUpdaters = attr.pendingUpdaters;
- // If updaters set sprite attributes that trigger other updaters,
- // those updaters are not called right away, but wait until all current
- // updaters are called (till the next do/while loop iteration).
- me.callUpdaters = Ext.emptyFn;
- // Hide class method from the instance.
- do {
- any = false;
- for (updater in pendingUpdaters) {
- any = true;
- flags = pendingUpdaters[updater];
- delete pendingUpdaters[updater];
- fn = updaters[updater];
- if (typeof fn === 'string') {
- fn = me[fn];
- }
- if (fn) {
- fn.call(me, attr, flags);
- }
- }
- dirty = dirty || any;
- } while (any);
- delete me.callUpdaters;
- // Restore class method.
- if (dirty) {
- me.setDirty(true);
- }
- },
- /**
- * @private
- */
- callUpdater: function(attr, updater, triggers) {
- this.scheduleUpdater(attr, updater, triggers);
- this.callUpdaters(attr);
- },
- /**
- * @private
- * Schedules specified updaters to be called.
- * Updaters are called implicitly as a result of a change to sprite attributes.
- * But sometimes it may be required to call an updater without setting an attribute,
- * and without messing up the updater call order (by calling the updater immediately).
- * For example:
- *
- * updaters: {
- * onDataX: function (attr) {
- * this.processDataX();
- * // Process data Y every time data X is processed.
- * // Call the onDataY updater as if changes to dataY attribute itself
- * // triggered the update.
- * this.scheduleUpdaters(attr, {onDataY: ['dataY']});
- * // Alternatively:
- * // this.scheduleUpdaters(attr, ['onDataY'], ['dataY']);
- * }
- * }
- *
- * @param {Object} attr The attributes object (not necesseraly of a sprite,
- * but of its instance).
- * @param {Object/String[]} updaters A map of updaters to be called to attributes
- * that triggered the update.
- * @param {String[]} [triggers] Attributes that triggered the update. An optional parameter.
- * If used, the `updaters` parameter will be treated as an array of updaters to be called.
- */
- scheduleUpdaters: function(attr, updaters, triggers) {
- var updater, i, ln;
- attr = attr || this.attr;
- if (triggers) {
- for (i = 0 , ln = updaters.length; i < ln; i++) {
- updater = updaters[i];
- this.scheduleUpdater(attr, updater, triggers);
- }
- } else {
- for (updater in updaters) {
- triggers = updaters[updater];
- this.scheduleUpdater(attr, updater, triggers);
- }
- }
- },
- /**
- * @private
- * @param attr {Object} The attributes object (not necesseraly of a sprite,
- * but of its instance).
- * @param updater {String} Updater to be called.
- * @param {String[]} [triggers] Attributes that triggered the update.
- */
- scheduleUpdater: function(attr, updater, triggers) {
- var pendingUpdaters;
- triggers = triggers || [];
- attr = attr || this.attr;
- pendingUpdaters = attr.pendingUpdaters;
- if (updater in pendingUpdaters) {
- if (triggers.length) {
- pendingUpdaters[updater] = Ext.Array.merge(pendingUpdaters[updater], triggers);
- }
- } else {
- pendingUpdaters[updater] = triggers;
- }
- },
- /**
- * Set attributes of the sprite.
- * By default only the attributes that have processors will be set
- * and all other attributes will be filtered out as a result of the
- * normalization process.
- * The normalization process can be skipped. In that case all the given
- * attributes will be set unprocessed. This will result in better
- * performance, but might also pollute the sprite's attributes with
- * unwanted attributes or attributes with invalid values, if one is not
- * careful. See also {@link #setAttributesBypassingNormalization}.
- * If normalization is skipped, one may also chose to avoid copying
- * the given object. This may result in even better performance, but
- * only in cases where most of the attributes have values that are
- * different from the old values, because copying additionally checks
- * if the value has changed.
- *
- * @param {Object} changes The content of the change.
- * @param {Boolean} [bypassNormalization] `true` to avoid normalization of the given changes.
- * @param {Boolean} [avoidCopy] `true` to avoid copying the `changes` object.
- * `bypassNormalization` should also be `true`. The content of object may be destroyed.
- */
- setAttributes: function(changes, bypassNormalization, avoidCopy) {
- var me = this,
- changesToPush;
- //<debug>
- if (me.destroyed) {
- Ext.Error.raise("Setting attributes of a destroyed sprite.");
- }
- //</debug>
- if (bypassNormalization) {
- if (avoidCopy) {
- changesToPush = changes;
- } else {
- changesToPush = Ext.apply({}, changes);
- }
- } else {
- changesToPush = me.self.def.normalize(changes);
- }
- me.modifiers.target.pushDown(me.attr, changesToPush);
- },
- /**
- * Set attributes of the sprite, assuming the names and values have already been
- * normalized.
- *
- * @deprecated 6.5.0 Use setAttributes directly with bypassNormalization argument being `true`.
- * @param {Object} changes The content of the change.
- * @param {Boolean} [avoidCopy] `true` to avoid copying the `changes` object.
- * The content of object may be destroyed.
- */
- setAttributesBypassingNormalization: function(changes, avoidCopy) {
- return this.setAttributes(changes, true, avoidCopy);
- },
- /**
- * @private
- */
- bboxUpdater: function(attr) {
- var hasRotation = attr.rotationRads !== 0,
- hasScaling = attr.scalingX !== 1 || attr.scalingY !== 1,
- noRotationCenter = attr.rotationCenterX === null || attr.rotationCenterY === null,
- noScalingCenter = attr.scalingCenterX === null || attr.scalingCenterY === null;
- // 'bbox' is not a standard attribute (in the sense that it doesn't have
- // a processor = not explicitly declared and cannot be set by a user)
- // and is calculated automatically by the 'getBBox' method.
- // The 'bbox' attribute is created by the 'prepareAttributes' method
- // of the Target modifier at construction time.
- // Both plain and tranformed bounding boxes need to be updated.
- // Mark them as such below.
- attr.bbox.plain.dirty = true;
- // updated by the 'updatePlainBBox' method
- // Before transformed bounding box can be updated,
- // we must ensure that we have correct forward and inverse
- // transformation matrices (which are also created by the Target modifier),
- // so that they reflect the current state of the scaling, rotation
- // and other transformation attributes.
- // The 'applyTransformations' method does just that.
- // The 'dirtyTransform' flag (another implicit attribute)
- // is set to true when any of the transformation attributes change,
- // to let us know that transformation matrices need to be updated.
- attr.bbox.transform.dirty = true;
- // updated by the 'updateTransformedBBox' method
- if (hasRotation && noRotationCenter || hasScaling && noScalingCenter) {
- this.scheduleUpdater(attr, 'transform');
- }
- },
- /**
- * Returns the bounding box for the given Sprite as calculated with the Canvas engine.
- *
- * @param {Boolean} [isWithoutTransform] Whether to calculate the bounding box
- * with the current transforms or not.
- */
- getBBox: function(isWithoutTransform) {
- var me = this,
- attr = me.attr,
- bbox = attr.bbox,
- plain = bbox.plain,
- transform = bbox.transform;
- if (plain.dirty) {
- me.updatePlainBBox(plain);
- plain.dirty = false;
- }
- if (!isWithoutTransform) {
- // If tranformations are to be applied ('dirtyTransform' is true),
- // then this will itself call the 'getBBox' method
- // to get the plain untransformed bbox and calculate its center.
- me.applyTransformations();
- if (transform.dirty) {
- me.updateTransformedBBox(transform, plain);
- transform.dirty = false;
- }
- return transform;
- }
- return plain;
- },
- /**
- * @method
- * @protected
- * Subclass will fill the plain object with `x`, `y`, `width`, `height` information
- * of the plain bounding box of this sprite.
- *
- * @param {Object} plain Target object.
- */
- updatePlainBBox: Ext.emptyFn,
- /**
- * @protected
- * Subclass will fill the plain object with `x`, `y`, `width`, `height` information
- * of the transformed bounding box of this sprite.
- *
- * @param {Object} transform Target object (transformed bounding box) to populate.
- * @param {Object} plain Untransformed bounding box.
- */
- updateTransformedBBox: function(transform, plain) {
- this.attr.matrix.transformBBox(plain, 0, transform);
- },
- /**
- * Subclass can rewrite this function to gain better performance.
- * @param {Boolean} isWithoutTransform
- * @return {Array}
- */
- getBBoxCenter: function(isWithoutTransform) {
- var bbox = this.getBBox(isWithoutTransform);
- if (bbox) {
- return [
- bbox.x + bbox.width * 0.5,
- bbox.y + bbox.height * 0.5
- ];
- } else {
- return [
- 0,
- 0
- ];
- }
- },
- /**
- * Hide the sprite.
- * @return {Ext.draw.sprite.Sprite} this
- * @chainable
- */
- hide: function() {
- this.attr.hidden = true;
- this.setDirty(true);
- return this;
- },
- /**
- * Show the sprite.
- * @return {Ext.draw.sprite.Sprite} this
- * @chainable
- */
- show: function() {
- this.attr.hidden = false;
- this.setDirty(true);
- return this;
- },
- /**
- * Applies sprite's attributes to the given context.
- * @param {Object} ctx Context to apply sprite's attributes to.
- * @param {Array} rect The rect of the context to be affected by gradients.
- */
- useAttributes: function(ctx, rect) {
- // Always (force) apply transformation to sprite instances,
- // even if their 'dirtyTransform' flag is false.
- // The 'dirtyTransform' flag of an instance may never be set to 'true', as the
- // 'transform' updater won't ever be called for sprite instances that have
- // the same transform attributes as their template, because there's nothing to update
- // (an instance is simply a prototype chained template's 'attr' object, that only
- // has own properties for attributes whose values are different).
- // Making the modifier recognize transform attributes set on sprite instances
- // (see Ext.draw.modifier.Modifier's 'pushDown' method, where attributes with
- // same values are removed from the 'changes' object) and making sure their 'dirtyTransform'
- // flag is set to 'true' is not a correct solution here, because of the way instances
- // are rendered (see Ext.draw.sprite.Instancing's 'render' method) - there is no way
- // an instance wounldn't want its 'applyTransformations' method called.
- this.applyTransformations(this.isSpriteInstance);
- // eslint-disable-next-line vars-on-top
- var attr = this.attr,
- canvasAttributes = attr.canvasAttributes,
- strokeStyle = canvasAttributes.strokeStyle,
- fillStyle = canvasAttributes.fillStyle,
- lineDash = canvasAttributes.lineDash,
- lineDashOffset = canvasAttributes.lineDashOffset,
- id;
- if (strokeStyle) {
- if (strokeStyle.isGradient) {
- ctx.strokeStyle = 'black';
- ctx.strokeGradient = strokeStyle;
- } else {
- ctx.strokeGradient = false;
- }
- }
- if (fillStyle) {
- if (fillStyle.isGradient) {
- ctx.fillStyle = 'black';
- ctx.fillGradient = fillStyle;
- } else {
- ctx.fillGradient = false;
- }
- }
- if (lineDash) {
- ctx.setLineDash(lineDash);
- }
- // Only set lineDashOffset to contexts that support the property (excludes VML).
- if (Ext.isNumber(lineDashOffset) && Ext.isNumber(ctx.lineDashOffset)) {
- ctx.lineDashOffset = lineDashOffset;
- }
- for (id in canvasAttributes) {
- if (canvasAttributes[id] !== undefined && canvasAttributes[id] !== ctx[id]) {
- ctx[id] = canvasAttributes[id];
- }
- }
- this.setGradientBBox(ctx, rect);
- },
- setGradientBBox: function(ctx, rect) {
- var attr = this.attr;
- if (attr.constrainGradients) {
- ctx.setGradientBBox({
- x: rect[0],
- y: rect[1],
- width: rect[2],
- height: rect[3]
- });
- } else {
- ctx.setGradientBBox(this.getBBox(attr.transformFillStroke));
- }
- },
- /**
- * @private
- *
- * Calculates forward and inverse transform matrices from sprite's attributes.
- * Transformations are applied in the following order: Scaling, Rotation, Translation.
- * @param {Boolean} [force=false] Forces recalculation of transform matrices even when
- * sprite's transform attributes supposedly haven't changed.
- */
- applyTransformations: function(force) {
- if (!force && !this.attr.dirtyTransform) {
- return;
- }
- // eslint-disable-next-line vars-on-top
- var me = this,
- attr = me.attr,
- center = me.getBBoxCenter(true),
- centerX = center[0],
- centerY = center[1],
- tx = attr.translationX,
- ty = attr.translationY,
- sx = attr.scalingX,
- sy = attr.scalingY === null ? attr.scalingX : attr.scalingY,
- scx = attr.scalingCenterX === null ? centerX : attr.scalingCenterX,
- scy = attr.scalingCenterY === null ? centerY : attr.scalingCenterY,
- rad = attr.rotationRads,
- rcx = attr.rotationCenterX === null ? centerX : attr.rotationCenterX,
- rcy = attr.rotationCenterY === null ? centerY : attr.rotationCenterY,
- cos = Math.cos(rad),
- sin = Math.sin(rad),
- tx_4, ty_4;
- if (sx === 1 && sy === 1) {
- scx = 0;
- scy = 0;
- }
- if (rad === 0) {
- rcx = 0;
- rcy = 0;
- }
- // Translation component after steps 1-4 (see below).
- // Saving it here to prevent double calculation.
- tx_4 = scx * (1 - sx) - rcx;
- ty_4 = scy * (1 - sy) - rcy;
- /* eslint-disable max-len */
- // The matrix below is a result of:
- // (7) (6) (5) (4) (3) (2) (1)
- // | 1 0 tx | | 1 0 rcx | | cos -sin 0 | | 1 0 -rcx | | 1 0 scx | | sx 0 0 | | 1 0 -scx |
- // | 0 1 ty | * | 0 1 rcy | * | sin cos 0 | * | 0 1 -rcy | * | 0 1 scy | * | 0 sy 0 | * | 0 1 -scy |
- // | 0 0 1 | | 0 0 1 | | 0 0 1 | | 0 0 1 | | 0 0 1 | | 0 0 0 | | 0 0 1 |
- /* eslint-enable max-len */
- attr.matrix.elements = [
- cos * sx,
- sin * sx,
- -sin * sy,
- cos * sy,
- cos * tx_4 - sin * ty_4 + rcx + tx,
- sin * tx_4 + cos * ty_4 + rcy + ty
- ];
- attr.matrix.inverse(attr.inverseMatrix);
- attr.dirtyTransform = false;
- attr.bbox.transform.dirty = true;
- },
- /**
- * Pre-multiplies the current transformation matrix of a sprite with the given matrix.
- * If `isSplit` parameter is `true`, the resulting matrix is also split into
- * individual components (scaling, rotation, translation) and corresponding sprite
- * attributes are updated. The shearing component is not extracted.
- * Note, that transformation attributes work as if transformations are applied to the
- * local coordinate system of a sprite, while matrix transformations transform
- * the global coordinate space or the surface grid.
- * Since the `transform` method returns the sprite itself, calls to the method
- * can be chained. And if updating sprite transformation attributes is desired,
- * it can be achieved by setting the `isSplit` parameter of the last call to `true`.
- * For example:
- *
- * sprite.transform(matrixA).transform(matrixB).transform(matrixC, true);
- *
- * See also: {@link #setTransform}
- *
- * @param {Ext.draw.Matrix/Number[]} matrix A transformation matrix or array of its elements.
- * @param {Boolean} [isSplit=false] If 'true', transformation attributes are updated.
- * @return {Ext.draw.sprite.Sprite} This sprite.
- */
- transform: function(matrix, isSplit) {
- var attr = this.attr,
- spriteMatrix = attr.matrix,
- elements;
- if (matrix && matrix.isMatrix) {
- elements = matrix.elements;
- } else {
- elements = matrix;
- }
- //<debug>
- if (!(Ext.isArray(elements) && elements.length === 6)) {
- Ext.raise("An instance of Ext.draw.Matrix or an array of 6 numbers is expected.");
- }
- //</debug>
- spriteMatrix.prepend.apply(spriteMatrix, elements.slice());
- spriteMatrix.inverse(attr.inverseMatrix);
- if (isSplit) {
- this.updateTransformAttributes();
- }
- attr.dirtyTransform = false;
- attr.bbox.transform.dirty = true;
- this.setDirty(true);
- return this;
- },
- /**
- * @private
- */
- updateTransformAttributes: function() {
- var attr = this.attr,
- split = attr.matrix.split();
- attr.rotationRads = split.rotate;
- attr.rotationCenterX = 0;
- attr.rotationCenterY = 0;
- attr.scalingX = split.scaleX;
- attr.scalingY = split.scaleY;
- attr.scalingCenterX = 0;
- attr.scalingCenterY = 0;
- attr.translationX = split.translateX;
- attr.translationY = split.translateY;
- },
- /**
- * Resets current transformation matrix of a sprite to the identify matrix.
- * @param {Boolean} [isSplit=false] If 'true', transformation attributes are updated.
- * @return {Ext.draw.sprite.Sprite} This sprite.
- */
- resetTransform: function(isSplit) {
- var attr = this.attr;
- attr.matrix.reset();
- attr.inverseMatrix.reset();
- if (!isSplit) {
- this.updateTransformAttributes();
- }
- attr.dirtyTransform = false;
- attr.bbox.transform.dirty = true;
- this.setDirty(true);
- return this;
- },
- /**
- * Resets current transformation matrix of a sprite to the identify matrix
- * and pre-multiplies it with the given matrix.
- * This is effectively the same as calling {@link #resetTransform},
- * followed by {@link #transform} with the same arguments.
- *
- * See also: {@link #transform}
- *
- * var drawContainer = new Ext.draw.Container({
- * renderTo: Ext.getBody(),
- * width: 380,
- * height: 380,
- * sprites: [{
- * type: 'rect',
- * width: 100,
- * height: 100,
- * fillStyle: 'red'
- * }]
- * });
- *
- * var main = drawContainer.getSurface();
- * var rect = main.getItems()[0];
- *
- * var m = new Ext.draw.Matrix().rotate(Math.PI, 100, 100);
- *
- * rect.setTransform(m);
- * main.renderFrame();
- *
- * There may be times where the transformation you need to apply cannot easily be
- * accomplished using the sprite’s convenience transform methods. Or, you may want
- * to pass a matrix directly to the sprite in order to set a transformation. The
- * `setTransform` method allows for this sort of advanced usage as well. The
- * following tables show each transformation matrix used when applying
- * transformations to a sprite.
- *
- * ### Translate
- * <table style="text-align: center;">
- * <tr>
- * <td style="font-weight: normal;">1</td>
- * <td style="font-weight: normal;">0</td>
- * <td style="font-weight: normal;">tx</td>
- * </tr>
- * <tr>
- * <td>0</td>
- * <td>1</td>
- * <td>ty</td>
- * </tr>
- * <tr>
- * <td>0</td>
- * <td>0</td>
- * <td>1</td>
- * </tr>
- * </table>
- *
- * ### Rotate (θ is the angle of rotation)
- * <table style="text-align: center;">
- * <tr>
- * <td style="font-weight: normal;">cos(θ)</td>
- * <td style="font-weight: normal;">-sin(θ)</td>
- * <td style="font-weight: normal;">0</td>
- * </tr>
- * <tr>
- * <td>0</td>
- * <td>cos(θ)</td>
- * <td>0</td>
- * </tr>
- * <tr>
- * <td>0</td>
- * <td>0</td>
- * <td>1</td>
- * </tr>
- * </table>
- *
- * ### Scale
- * <table style="text-align: center;">
- * <tr>
- * <td style="font-weight: normal;">sx</td>
- * <td style="font-weight: normal;">0</td>
- * <td style="font-weight: normal;">0</td>
- * </tr>
- * <tr>
- * <td>0</td>
- * <td>cos(θ)</td>
- * <td>0</td>
- * </tr>
- * <tr>
- * <td>0</td>
- * <td>0</td>
- * <td>1</td>
- * </tr>
- * </table>
- *
- * ### Shear X _(λ is the distance on the x axis to shear by)_
- * <table style="text-align: center;">
- * <tr>
- * <td style="font-weight: normal;">1</td>
- * <td style="font-weight: normal;">λx</td>
- * <td style="font-weight: normal;">0</td>
- * </tr>
- * <tr>
- * <td>0</td>
- * <td>1</td>
- * <td>0</td>
- * </tr>
- * <tr>
- * <td>0</td>
- * <td>0</td>
- * <td>1</td>
- * </tr>
- * </table>
- *
- * ### Shear Y (λ is the distance on the y axis to shear by)
- * <table style="text-align: center;">
- * <tr>
- * <td style="font-weight: normal;">1</td>
- * <td style="font-weight: normal;">0</td>
- * <td style="font-weight: normal;">0</td>
- * </tr>
- * <tr>
- * <td>λy</td>
- * <td>1</td>
- * <td>0</td>
- * </tr>
- * <tr>
- * <td>0</td>
- * <td>0</td>
- * <td>1</td>
- * </tr>
- * </table>
- *
- * ### Skew X (θ is the angle to skew by)
- * <table style="text-align: center;">
- * <tr>
- * <td style="font-weight: normal;">1</td>
- * <td style="font-weight: normal;">tan(θ)</td>
- * <td style="font-weight: normal;">0</td>
- * </tr>
- * <tr>
- * <td>0</td>
- * <td>1</td>
- * <td>0</td>
- * </tr>
- * <tr>
- * <td>0</td>
- * <td>0</td>
- * <td>1</td>
- * </tr>
- * </table>
- *
- * ### Skew Y (θ is the angle to skew by)
- * <table style="text-align: center;">
- * <tr>
- * <td style="font-weight: normal;">1</td>
- * <td style="font-weight: normal;">0</td>
- * <td style="font-weight: normal;">0</td>
- * </tr>
- * <tr>
- * <td>tan(θ)</td>
- * <td>1</td>
- * <td>0</td>
- * </tr>
- * <tr>
- * <td>0</td>
- * <td>0</td>
- * <td>1</td>
- * </tr>
- * </table>
- *
- * Multiplying matrices for translation, rotation, scaling, and shearing / skewing
- * any number of times in the desired order produces a single matrix for a composite
- * transformation. You can use the product as a value for the `setTransform`method
- * of a sprite:
- *
- * mySprite.setTransform([a, b, c, d, e, f]);
- *
- * Where `a`, `b`, `c`, `d`, `e`, `f` are numeric values that correspond to the
- * following transformation matrix components:
- *
- * <table style="text-align: center;">
- * <tr>
- * <td style="font-weight: normal;">a</td>
- * <td style="font-weight: normal;">c</td>
- * <td style="font-weight: normal;">e</td>
- * </tr>
- * <tr>
- * <td>b</td>
- * <td>d</td>
- * <td>f</td>
- * </tr>
- * <tr>
- * <td>0</td>
- * <td>0</td>
- * <td>1</td>
- * </tr>
- * </table>
- *
- * @param {Ext.draw.Matrix/Number[]} matrix The transformation matrix to apply or its
- * raw elements as an array.
- * @param {Boolean} [isSplit=false] If `true`, transformation attributes are updated.
- * @return {Ext.draw.sprite.Sprite} This sprite.
- */
- setTransform: function(matrix, isSplit) {
- this.resetTransform(true);
- this.transform.call(this, matrix, isSplit);
- return this;
- },
- /**
- * @method
- * Called before rendering.
- */
- preRender: Ext.emptyFn,
- /**
- * @method
- * This is where the actual sprite rendering happens by calling `ctx` methods.
- * @param {Ext.draw.Surface} surface A draw container surface.
- * @param {CanvasRenderingContext2D} ctx A context object that is API compatible with the native
- * [CanvasRenderingContext2D](https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D).
- * @param {Number[]} surfaceClipRect The clip rect: [left, top, width, height].
- * Not to be confused with the `surface.getRect()`, which represents the location
- * and size of the surface in a draw container, in draw container coordinates.
- * The clip rect on the other hand represents the portion of the surface that is being
- * rendered, in surface coordinates.
- *
- * @return {*} returns `false` to stop rendering in this frame.
- * All the sprites that haven't been rendered will have their dirty flag untouched.
- */
- render: Ext.emptyFn,
- //<debug>
- /**
- * @private
- * Renders the bounding box of transformed sprite.
- */
- renderBBox: function(surface, ctx) {
- var bbox = this.getBBox();
- ctx.beginPath();
- ctx.moveTo(bbox.x, bbox.y);
- ctx.lineTo(bbox.x + bbox.width, bbox.y);
- ctx.lineTo(bbox.x + bbox.width, bbox.y + bbox.height);
- ctx.lineTo(bbox.x, bbox.y + bbox.height);
- ctx.closePath();
- ctx.strokeStyle = 'red';
- ctx.strokeOpacity = 1;
- ctx.lineWidth = 0.5;
- ctx.stroke();
- },
- //</debug>
- /**
- * Performs a hit test on the sprite.
- * @param {Array} point A two-item array containing x and y coordinates of the point.
- * @param {Object} options Hit testing options.
- * @return {Object} A hit result object that contains more information about what
- * exactly was hit or null if nothing was hit.
- */
- hitTest: function(point, options) {
- var x, y, bbox, isBBoxHit;
- // Meant to be overridden in subclasses for more precise hit testing.
- // This version doesn't take any options and simply hit tests sprite's
- // bounding box, if the sprite is visible.
- if (this.isVisible()) {
- x = point[0];
- y = point[1];
- bbox = this.getBBox();
- isBBoxHit = bbox && x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height);
- if (isBBoxHit) {
- return {
- sprite: this
- };
- }
- }
- return null;
- },
- /**
- * @private
- * Checks if the sprite can be seen.
- * This includes the `hidden` attribute check, alpha/opacity checks,
- * fill/stroke color checks and surface/parent checks.
- * The method doesn't check if the sprite is off-screen.
- * @return {Boolean} Returns `true`, if the sprite can be seen.
- */
- isVisible: function() {
- var attr = this.attr,
- parent = this.getParent(),
- hasParent = parent && (parent.isSurface || parent.isVisible()),
- isSeen = hasParent && !attr.hidden && attr.globalAlpha,
- none1 = Ext.util.Color.NONE,
- none2 = Ext.util.Color.RGBA_NONE,
- hasFill = attr.fillOpacity && attr.fillStyle !== none1 && attr.fillStyle !== none2,
- hasStroke = attr.strokeOpacity && attr.strokeStyle !== none1 && attr.strokeStyle !== none2,
- result = isSeen && (hasFill || hasStroke);
- return !!result;
- },
- repaint: function() {
- var surface = this.getSurface();
- if (surface) {
- surface.renderFrame();
- }
- },
- /**
- * Removes this sprite from its surface.
- * The sprite itself is not destroyed.
- * @return {Ext.draw.sprite.Sprite} Returns the removed sprite or `null` otherwise.
- */
- remove: function() {
- var surface = this.getSurface();
- if (surface && surface.isSurface) {
- return surface.remove(this);
- }
- return null;
- },
- /**
- * Removes the sprite and clears all listeners.
- */
- destroy: function() {
- var me = this,
- modifier = me.modifiers.target,
- currentModifier;
- while (modifier) {
- currentModifier = modifier;
- modifier = modifier._lower;
- currentModifier.destroy();
- }
- delete me.attr;
- me.remove();
- if (me.fireEvent('beforedestroy', me) !== false) {
- me.fireEvent('destroy', me);
- }
- me.callParent();
- }
- }, function() {
- // onClassCreated
- // Create one AttributeDefinition instance per sprite class when a class is created
- // and replace the `def` config with the instance that was created using that config.
- // Here we only create an AttributeDefinition instance for the base Sprite class,
- // attribute definitions for subclasses are created inside onClassExtended method.
- this.def = new Ext.draw.sprite.AttributeDefinition(this.def);
- this.def.spriteClass = this;
- });
- /**
- * Class representing a path.
- * Designed to be compatible with [CanvasPathMethods](http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#canvaspathmethods)
- * and will hopefully be replaced by the browsers' implementation of the Path object.
- */
- Ext.define('Ext.draw.Path', {
- requires: [
- 'Ext.draw.Draw'
- ],
- statics: {
- pathRe: /,?([achlmqrstvxz]),?/gi,
- pathRe2: /-/gi,
- pathSplitRe: /\s|,/g
- },
- svgString: '',
- /**
- * Create a path from pathString.
- * @constructor
- * @param {String} pathString
- */
- constructor: function(pathString) {
- var me = this;
- me.commands = [];
- // Stores command letters from the SVG path data ('d' attribute).
- me.params = [];
- // Stores command parameters from the SVG path data.
- // All command parameters are actually point coordinates as the only commands used
- // are the M, L, C, Z. This makes path transformations and hit testing easier.
- // Arcs are approximated using cubic Bezier curves, H and S commands are translated
- // to L commands and relative commands are translated to their absolute versions.
- me.cursor = null;
- me.startX = 0;
- me.startY = 0;
- if (pathString) {
- me.fromSvgString(pathString);
- }
- },
- /**
- * Clear the path.
- */
- clear: function() {
- var me = this;
- me.params.length = 0;
- me.commands.length = 0;
- me.cursor = null;
- me.startX = 0;
- me.startY = 0;
- me.dirt();
- },
- /**
- * @private
- */
- dirt: function() {
- this.svgString = '';
- },
- /**
- * Move to a position.
- * @param {Number} x
- * @param {Number} y
- */
- moveTo: function(x, y) {
- var me = this;
- if (!me.cursor) {
- me.cursor = [
- x,
- y
- ];
- }
- me.params.push(x, y);
- me.commands.push('M');
- me.startX = x;
- me.startY = y;
- me.cursor[0] = x;
- me.cursor[1] = y;
- me.dirt();
- },
- /**
- * A straight line to a position.
- * @param {Number} x
- * @param {Number} y
- */
- lineTo: function(x, y) {
- var me = this;
- if (!me.cursor) {
- me.cursor = [
- x,
- y
- ];
- me.params.push(x, y);
- me.commands.push('M');
- } else {
- me.params.push(x, y);
- me.commands.push('L');
- }
- me.cursor[0] = x;
- me.cursor[1] = y;
- me.dirt();
- },
- /**
- * A cubic bezier curve to a position.
- * @param {Number} cx1
- * @param {Number} cy1
- * @param {Number} cx2
- * @param {Number} cy2
- * @param {Number} x
- * @param {Number} y
- */
- bezierCurveTo: function(cx1, cy1, cx2, cy2, x, y) {
- var me = this;
- if (!me.cursor) {
- me.moveTo(cx1, cy1);
- }
- me.params.push(cx1, cy1, cx2, cy2, x, y);
- me.commands.push('C');
- me.cursor[0] = x;
- me.cursor[1] = y;
- me.dirt();
- },
- /**
- * A quadratic bezier curve to a position.
- * @param {Number} cx
- * @param {Number} cy
- * @param {Number} x
- * @param {Number} y
- */
- quadraticCurveTo: function(cx, cy, x, y) {
- var me = this;
- if (!me.cursor) {
- me.moveTo(cx, cy);
- }
- me.bezierCurveTo((2 * cx + me.cursor[0]) / 3, (2 * cy + me.cursor[1]) / 3, (2 * cx + x) / 3, (2 * cy + y) / 3, x, y);
- },
- /**
- * Close this path with a straight line.
- */
- closePath: function() {
- var me = this;
- if (me.cursor) {
- me.cursor = null;
- me.commands.push('Z');
- me.dirt();
- }
- },
- /**
- * Create a elliptic arc curve compatible with SVG's arc to instruction.
- *
- * The curve start from (`x1`, `y1`) and ends at (`x2`, `y2`). The ellipse
- * has radius `rx` and `ry` and a rotation of `rotation`.
- * @param {Number} x1
- * @param {Number} y1
- * @param {Number} x2
- * @param {Number} y2
- * @param {Number} [rx]
- * @param {Number} [ry]
- * @param {Number} [rotation]
- */
- arcTo: function(x1, y1, x2, y2, rx, ry, rotation) {
- var me = this;
- if (ry === undefined) {
- ry = rx;
- }
- if (rotation === undefined) {
- rotation = 0;
- }
- if (!me.cursor) {
- me.moveTo(x1, y1);
- return;
- }
- if (rx === 0 || ry === 0) {
- me.lineTo(x1, y1);
- return;
- }
- x2 -= x1;
- y2 -= y1;
- // eslint-disable-next-line vars-on-top, one-var
- var x0 = me.cursor[0] - x1,
- y0 = me.cursor[1] - y1,
- area = x2 * y0 - y2 * x0,
- cos, sin, xx, yx, xy, yy,
- l0 = Math.sqrt(x0 * x0 + y0 * y0),
- l2 = Math.sqrt(x2 * x2 + y2 * y2),
- dist, cx, cy, temp;
- // cos rx, -sin ry , x1 - cos rx x1 + ry sin y1
- // sin rx, cos ry, -rx sin x1 + y1 - cos ry y1
- if (area === 0) {
- me.lineTo(x1, y1);
- return;
- }
- if (ry !== rx) {
- cos = Math.cos(rotation);
- sin = Math.sin(rotation);
- xx = cos / rx;
- yx = sin / ry;
- xy = -sin / rx;
- yy = cos / ry;
- temp = xx * x0 + yx * y0;
- y0 = xy * x0 + yy * y0;
- x0 = temp;
- temp = xx * x2 + yx * y2;
- y2 = xy * x2 + yy * y2;
- x2 = temp;
- } else {
- x0 /= rx;
- y0 /= ry;
- x2 /= rx;
- y2 /= ry;
- }
- cx = x0 * l2 + x2 * l0;
- cy = y0 * l2 + y2 * l0;
- dist = 1 / (Math.sin(Math.asin(Math.abs(area) / (l0 * l2)) * 0.5) * Math.sqrt(cx * cx + cy * cy));
- cx *= dist;
- cy *= dist;
- // eslint-disable-next-line vars-on-top, one-var
- var k0 = (cx * x0 + cy * y0) / (x0 * x0 + y0 * y0),
- k2 = (cx * x2 + cy * y2) / (x2 * x2 + y2 * y2),
- cosStart = x0 * k0 - cx,
- sinStart = y0 * k0 - cy,
- cosEnd = x2 * k2 - cx,
- sinEnd = y2 * k2 - cy,
- startAngle = Math.atan2(sinStart, cosStart),
- endAngle = Math.atan2(sinEnd, cosEnd);
- if (area > 0) {
- if (endAngle < startAngle) {
- endAngle += Math.PI * 2;
- }
- } else {
- if (startAngle < endAngle) {
- startAngle += Math.PI * 2;
- }
- }
- if (ry !== rx) {
- cx = cos * cx * rx - sin * cy * ry + x1;
- cy = sin * cy * ry + cos * cy * ry + y1;
- me.lineTo(cos * rx * cosStart - sin * ry * sinStart + cx, sin * rx * cosStart + cos * ry * sinStart + cy);
- me.ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, area < 0);
- } else {
- cx = cx * rx + x1;
- cy = cy * ry + y1;
- me.lineTo(rx * cosStart + cx, ry * sinStart + cy);
- me.ellipse(cx, cy, rx, ry, rotation, startAngle, endAngle, area < 0);
- }
- },
- /**
- * Create an elliptic arc.
- *
- * See [the whatwg reference of ellipse](http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-ellipse).
- *
- * @param {Number} cx
- * @param {Number} cy
- * @param {Number} radiusX
- * @param {Number} radiusY
- * @param {Number} rotation
- * @param {Number} startAngle
- * @param {Number} endAngle
- * @param {Number} anticlockwise
- */
- ellipse: function(cx, cy, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise) {
- var me = this,
- params = me.params,
- start = params.length,
- count, temp, i, j;
- if (endAngle - startAngle >= Math.PI * 2) {
- me.ellipse(cx, cy, radiusX, radiusY, rotation, startAngle, startAngle + Math.PI, anticlockwise);
- me.ellipse(cx, cy, radiusX, radiusY, rotation, startAngle + Math.PI, endAngle, anticlockwise);
- return;
- }
- if (!anticlockwise) {
- if (endAngle < startAngle) {
- endAngle += Math.PI * 2;
- }
- count = me.approximateArc(params, cx, cy, radiusX, radiusY, rotation, startAngle, endAngle);
- } else {
- if (startAngle < endAngle) {
- startAngle += Math.PI * 2;
- }
- count = me.approximateArc(params, cx, cy, radiusX, radiusY, rotation, endAngle, startAngle);
- for (i = start , j = params.length - 2; i < j; i += 2 , j -= 2) {
- temp = params[i];
- params[i] = params[j];
- params[j] = temp;
- temp = params[i + 1];
- params[i + 1] = params[j + 1];
- params[j + 1] = temp;
- }
- }
- if (!me.cursor) {
- me.cursor = [
- params[params.length - 2],
- params[params.length - 1]
- ];
- me.commands.push('M');
- } else {
- me.cursor[0] = params[params.length - 2];
- me.cursor[1] = params[params.length - 1];
- me.commands.push('L');
- }
- for (i = 2; i < count; i += 6) {
- me.commands.push('C');
- }
- me.dirt();
- },
- /**
- * Create an circular arc.
- *
- * @param {Number} x
- * @param {Number} y
- * @param {Number} radius
- * @param {Number} startAngle
- * @param {Number} endAngle
- * @param {Number} anticlockwise
- */
- arc: function(x, y, radius, startAngle, endAngle, anticlockwise) {
- this.ellipse(x, y, radius, radius, 0, startAngle, endAngle, anticlockwise);
- },
- /**
- * Draw a rectangle and close it.
- *
- * @param {Number} x
- * @param {Number} y
- * @param {Number} width
- * @param {Number} height
- */
- rect: function(x, y, width, height) {
- var me = this;
- if (width === 0 || height === 0) {
- return;
- }
- me.moveTo(x, y);
- me.lineTo(x + width, y);
- me.lineTo(x + width, y + height);
- me.lineTo(x, y + height);
- me.closePath();
- },
- /**
- * @private
- * @param {Array} result
- * @param {Number} cx
- * @param {Number} cy
- * @param {Number} rx
- * @param {Number} ry
- * @param {Number} phi
- * @param {Number} theta1
- * @param {Number} theta2
- * @return {Number}
- */
- approximateArc: function(result, cx, cy, rx, ry, phi, theta1, theta2) {
- var cosPhi = Math.cos(phi),
- sinPhi = Math.sin(phi),
- cosTheta1 = Math.cos(theta1),
- sinTheta1 = Math.sin(theta1),
- xx = cosPhi * cosTheta1 * rx - sinPhi * sinTheta1 * ry,
- yx = -cosPhi * sinTheta1 * rx - sinPhi * cosTheta1 * ry,
- xy = sinPhi * cosTheta1 * rx + cosPhi * sinTheta1 * ry,
- yy = -sinPhi * sinTheta1 * rx + cosPhi * cosTheta1 * ry,
- rightAngle = Math.PI / 2,
- count = 2,
- exx = xx,
- eyx = yx,
- exy = xy,
- eyy = yy,
- rho = 0.547443256150549,
- temp, y1, x3, y3, x2, y2;
- theta2 -= theta1;
- if (theta2 < 0) {
- theta2 += Math.PI * 2;
- }
- result.push(xx + cx, xy + cy);
- while (theta2 >= rightAngle) {
- result.push(exx + eyx * rho + cx, exy + eyy * rho + cy, exx * rho + eyx + cx, exy * rho + eyy + cy, eyx + cx, eyy + cy);
- count += 6;
- theta2 -= rightAngle;
- temp = exx;
- exx = eyx;
- eyx = -temp;
- temp = exy;
- exy = eyy;
- eyy = -temp;
- }
- if (theta2) {
- y1 = (0.3294738052815987 + 0.012120855841304373 * theta2) * theta2;
- x3 = Math.cos(theta2);
- y3 = Math.sin(theta2);
- x2 = x3 + y1 * y3;
- y2 = y3 - y1 * x3;
- 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);
- count += 6;
- }
- return count;
- },
- /**
- * [http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes](http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes)
- * @param {Number} rx
- * @param {Number} ry
- * @param {Number} rotation Differ from svg spec, this is radian.
- * @param {Number} fA
- * @param {Number} fS
- * @param {Number} x2
- * @param {Number} y2
- */
- arcSvg: function(rx, ry, rotation, fA, fS, x2, y2) {
- if (rx < 0) {
- rx = -rx;
- }
- if (ry < 0) {
- ry = -ry;
- }
- // eslint-disable-next-line vars-on-top
- var me = this,
- x1 = me.cursor[0],
- y1 = me.cursor[1],
- hdx = (x1 - x2) / 2,
- hdy = (y1 - y2) / 2,
- cosPhi = Math.cos(rotation),
- sinPhi = Math.sin(rotation),
- xp = hdx * cosPhi + hdy * sinPhi,
- yp = -hdx * sinPhi + hdy * cosPhi,
- ratX = xp / rx,
- ratY = yp / ry,
- lambda = ratX * ratX + ratY * ratY,
- cx = (x1 + x2) * 0.5,
- cy = (y1 + y2) * 0.5,
- cpx = 0,
- cpy = 0,
- theta1, deltaTheta;
- if (lambda >= 1) {
- lambda = Math.sqrt(lambda);
- rx *= lambda;
- ry *= lambda;
- } else // me gives lambda == cpx == cpy == 0;
- {
- lambda = Math.sqrt(1 / lambda - 1);
- if (fA === fS) {
- lambda = -lambda;
- }
- cpx = lambda * rx * ratY;
- cpy = -lambda * ry * ratX;
- cx += cosPhi * cpx - sinPhi * cpy;
- cy += sinPhi * cpx + cosPhi * cpy;
- }
- theta1 = Math.atan2((yp - cpy) / ry, (xp - cpx) / rx);
- deltaTheta = Math.atan2((-yp - cpy) / ry, (-xp - cpx) / rx) - theta1;
- if (fS) {
- if (deltaTheta <= 0) {
- deltaTheta += Math.PI * 2;
- }
- } else {
- if (deltaTheta >= 0) {
- deltaTheta -= Math.PI * 2;
- }
- }
- me.ellipse(cx, cy, rx, ry, rotation, theta1, theta1 + deltaTheta, 1 - fS);
- },
- /**
- * Feed the path from svg path string.
- * @param {String} pathString
- */
- fromSvgString: function(pathString) {
- if (!pathString) {
- return;
- }
- // eslint-disable-next-line vars-on-top
- var me = this,
- parts,
- paramCounts = {
- a: 7,
- c: 6,
- h: 1,
- l: 2,
- m: 2,
- q: 4,
- s: 4,
- t: 2,
- v: 1,
- z: 0,
- A: 7,
- C: 6,
- H: 1,
- L: 2,
- M: 2,
- Q: 4,
- S: 4,
- T: 2,
- V: 1,
- Z: 0
- },
- lastCommand = '',
- lastControlX, lastControlY,
- lastX = 0,
- lastY = 0,
- part = false,
- i, partLength;
- // Split the string to items.
- if (Ext.isString(pathString)) {
- parts = pathString.replace(Ext.draw.Path.pathRe, " $1 ").replace(Ext.draw.Path.pathRe2, " -").split(Ext.draw.Path.pathSplitRe);
- } else if (Ext.isArray(pathString)) {
- parts = pathString.join(',').split(Ext.draw.Path.pathSplitRe);
- }
- // Remove empty entries
- for (i = 0 , partLength = 0; i < parts.length; i++) {
- if (parts[i] !== '') {
- parts[partLength++] = parts[i];
- }
- }
- parts.length = partLength;
- me.clear();
- for (i = 0; i < parts.length; ) {
- lastCommand = part;
- part = parts[i];
- i++;
- switch (part) {
- case 'M':
- me.moveTo(lastX = +parts[i], lastY = +parts[i + 1]);
- i += 2;
- while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
- me.lineTo(lastX = +parts[i], lastY = +parts[i + 1]);
- i += 2;
- };
- break;
- case 'L':
- me.lineTo(lastX = +parts[i], lastY = +parts[i + 1]);
- i += 2;
- while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
- me.lineTo(lastX = +parts[i], lastY = +parts[i + 1]);
- i += 2;
- };
- break;
- case 'A':
- while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
- 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]);
- i += 7;
- };
- break;
- case 'C':
- while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
- me.bezierCurveTo(+parts[i], +parts[i + 1], lastControlX = +parts[i + 2], lastControlY = +parts[i + 3], lastX = +parts[i + 4], lastY = +parts[i + 5]);
- i += 6;
- };
- break;
- case 'Z':
- me.closePath();
- break;
- case 'm':
- me.moveTo(lastX += +parts[i], lastY += +parts[i + 1]);
- i += 2;
- while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
- me.lineTo(lastX += +parts[i], lastY += +parts[i + 1]);
- i += 2;
- };
- break;
- case 'l':
- me.lineTo(lastX += +parts[i], lastY += +parts[i + 1]);
- i += 2;
- while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
- me.lineTo(lastX += +parts[i], lastY += +parts[i + 1]);
- i += 2;
- };
- break;
- case 'a':
- while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
- 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]);
- i += 7;
- };
- break;
- case 'c':
- while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
- 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]);
- i += 6;
- };
- break;
- case 'z':
- me.closePath();
- break;
- case 's':
- if (!(lastCommand === 'c' || lastCommand === 'C' || lastCommand === 's' || lastCommand === 'S')) {
- lastControlX = lastX;
- lastControlY = lastY;
- };
- while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
- 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]);
- i += 4;
- };
- break;
- case 'S':
- if (!(lastCommand === 'c' || lastCommand === 'C' || lastCommand === 's' || lastCommand === 'S')) {
- lastControlX = lastX;
- lastControlY = lastY;
- };
- while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
- me.bezierCurveTo(lastX + lastX - lastControlX, lastY + lastY - lastControlY, lastControlX = +parts[i], lastControlY = +parts[i + 1], lastX = (+parts[i + 2]), lastY = (+parts[i + 3]));
- i += 4;
- };
- break;
- case 'q':
- while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
- me.quadraticCurveTo(lastControlX = lastX + (+parts[i]), lastControlY = lastY + (+parts[i + 1]), lastX += +parts[i + 2], lastY += +parts[i + 3]);
- i += 4;
- };
- break;
- case 'Q':
- while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
- me.quadraticCurveTo(lastControlX = +parts[i], lastControlY = +parts[i + 1], lastX = +parts[i + 2], lastY = +parts[i + 3]);
- i += 4;
- };
- break;
- case 't':
- if (!(lastCommand === 'q' || lastCommand === 'Q' || lastCommand === 't' || lastCommand === 'T')) {
- lastControlX = lastX;
- lastControlY = lastY;
- };
- while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
- me.quadraticCurveTo(lastControlX = lastX + lastX - lastControlX, lastControlY = lastY + lastY - lastControlY, lastX += +parts[i + 1], lastY += +parts[i + 2]);
- i += 2;
- };
- break;
- case 'T':
- if (!(lastCommand === 'q' || lastCommand === 'Q' || lastCommand === 't' || lastCommand === 'T')) {
- lastControlX = lastX;
- lastControlY = lastY;
- };
- while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
- me.quadraticCurveTo(lastControlX = lastX + lastX - lastControlX, lastControlY = lastY + lastY - lastControlY, lastX = (+parts[i + 1]), lastY = (+parts[i + 2]));
- i += 2;
- };
- break;
- case 'h':
- while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
- me.lineTo(lastX += +parts[i], lastY);
- i++;
- };
- break;
- case 'H':
- while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
- me.lineTo(lastX = +parts[i], lastY);
- i++;
- };
- break;
- case 'v':
- while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
- me.lineTo(lastX, lastY += +parts[i]);
- i++;
- };
- break;
- case 'V':
- while (i < partLength && !paramCounts.hasOwnProperty(parts[i])) {
- me.lineTo(lastX, lastY = +parts[i]);
- i++;
- };
- break;
- }
- }
- },
- /**
- * Clone this path.
- * @return {Ext.draw.Path}
- */
- clone: function() {
- var me = this,
- path = new Ext.draw.Path();
- path.params = me.params.slice(0);
- path.commands = me.commands.slice(0);
- path.cursor = me.cursor ? me.cursor.slice(0) : null;
- path.startX = me.startX;
- path.startY = me.startY;
- path.svgString = me.svgString;
- return path;
- },
- /**
- * Transform the current path by a matrix.
- * @param {Ext.draw.Matrix} matrix
- */
- transform: function(matrix) {
- if (matrix.isIdentity()) {
- return;
- }
- // eslint-disable-next-line vars-on-top
- var xx = matrix.getXX(),
- yx = matrix.getYX(),
- dx = matrix.getDX(),
- xy = matrix.getXY(),
- yy = matrix.getYY(),
- dy = matrix.getDY(),
- params = this.params,
- i = 0,
- ln = params.length,
- x, y;
- for (; i < ln; i += 2) {
- x = params[i];
- y = params[i + 1];
- params[i] = x * xx + y * yx + dx;
- params[i + 1] = x * xy + y * yy + dy;
- }
- this.dirt();
- },
- /**
- * Get the bounding box of this matrix.
- * @param {Object} [target] Optional object to receive the result.
- *
- * @return {Object} Object with x, y, width and height
- */
- getDimension: function(target) {
- if (!target) {
- target = {};
- }
- if (!this.commands || !this.commands.length) {
- target.x = 0;
- target.y = 0;
- target.width = 0;
- target.height = 0;
- return target;
- }
- target.left = Infinity;
- target.top = Infinity;
- target.right = -Infinity;
- target.bottom = -Infinity;
- // eslint-disable-next-line vars-on-top
- var i = 0,
- j = 0,
- commands = this.commands,
- params = this.params,
- ln = commands.length,
- x, y;
- for (; i < ln; i++) {
- switch (commands[i]) {
- case 'M':
- case 'L':
- x = params[j];
- y = params[j + 1];
- target.left = Math.min(x, target.left);
- target.top = Math.min(y, target.top);
- target.right = Math.max(x, target.right);
- target.bottom = Math.max(y, target.bottom);
- j += 2;
- break;
- case 'C':
- this.expandDimension(target, x, y, params[j], params[j + 1], params[j + 2], params[j + 3], x = params[j + 4], y = params[j + 5]);
- j += 6;
- break;
- }
- }
- target.x = target.left;
- target.y = target.top;
- target.width = target.right - target.left;
- target.height = target.bottom - target.top;
- return target;
- },
- /**
- * Get the bounding box as if the path is transformed by a matrix.
- *
- * @param {Ext.draw.Matrix} matrix
- * @param {Object} [target] Optional object to receive the result.
- *
- * @return {Object} An object with x, y, width and height.
- */
- getDimensionWithTransform: function(matrix, target) {
- if (!this.commands || !this.commands.length) {
- if (!target) {
- target = {};
- }
- target.x = 0;
- target.y = 0;
- target.width = 0;
- target.height = 0;
- return target;
- }
- target.left = Infinity;
- target.top = Infinity;
- target.right = -Infinity;
- target.bottom = -Infinity;
- // eslint-disable-next-line vars-on-top
- var xx = matrix.getXX(),
- yx = matrix.getYX(),
- dx = matrix.getDX(),
- xy = matrix.getXY(),
- yy = matrix.getYY(),
- dy = matrix.getDY(),
- i = 0,
- j = 0,
- commands = this.commands,
- params = this.params,
- ln = commands.length,
- x, y;
- for (; i < ln; i++) {
- switch (commands[i]) {
- case 'M':
- case 'L':
- x = params[j] * xx + params[j + 1] * yx + dx;
- y = params[j] * xy + params[j + 1] * yy + dy;
- target.left = Math.min(x, target.left);
- target.top = Math.min(y, target.top);
- target.right = Math.max(x, target.right);
- target.bottom = Math.max(y, target.bottom);
- j += 2;
- break;
- case 'C':
- 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);
- j += 6;
- break;
- }
- }
- if (!target) {
- target = {};
- }
- target.x = target.left;
- target.y = target.top;
- target.width = target.right - target.left;
- target.height = target.bottom - target.top;
- return target;
- },
- /**
- * @private
- * Expand the rect by the bbox of a bezier curve.
- *
- * @param {Object} target
- * @param {Number} x1
- * @param {Number} y1
- * @param {Number} cx1
- * @param {Number} cy1
- * @param {Number} cx2
- * @param {Number} cy2
- * @param {Number} x2
- * @param {Number} y2
- */
- expandDimension: function(target, x1, y1, cx1, cy1, cx2, cy2, x2, y2) {
- var me = this,
- l = target.left,
- r = target.right,
- t = target.top,
- b = target.bottom,
- dim = me.dim || (me.dim = []);
- me.curveDimension(x1, cx1, cx2, x2, dim);
- l = Math.min(l, dim[0]);
- r = Math.max(r, dim[1]);
- me.curveDimension(y1, cy1, cy2, y2, dim);
- t = Math.min(t, dim[0]);
- b = Math.max(b, dim[1]);
- target.left = l;
- target.right = r;
- target.top = t;
- target.bottom = b;
- },
- /**
- * @private
- * Determine the curve
- * @param {Number} a
- * @param {Number} b
- * @param {Number} c
- * @param {Number} d
- * @param {Number} dim
- */
- curveDimension: function(a, b, c, d, dim) {
- var qa = 3 * (-a + 3 * (b - c) + d),
- qb = 6 * (a - 2 * b + c),
- qc = -3 * (a - b),
- x, y,
- min = Math.min(a, d),
- max = Math.max(a, d),
- delta;
- if (qa === 0) {
- if (qb === 0) {
- dim[0] = min;
- dim[1] = max;
- return;
- } else {
- x = -qc / qb;
- if (0 < x && x < 1) {
- y = this.interpolate(a, b, c, d, x);
- min = Math.min(min, y);
- max = Math.max(max, y);
- }
- }
- } else {
- delta = qb * qb - 4 * qa * qc;
- if (delta >= 0) {
- delta = Math.sqrt(delta);
- x = (delta - qb) / 2 / qa;
- if (0 < x && x < 1) {
- y = this.interpolate(a, b, c, d, x);
- min = Math.min(min, y);
- max = Math.max(max, y);
- }
- if (delta > 0) {
- x -= delta / qa;
- if (0 < x && x < 1) {
- y = this.interpolate(a, b, c, d, x);
- min = Math.min(min, y);
- max = Math.max(max, y);
- }
- }
- }
- }
- dim[0] = min;
- dim[1] = max;
- },
- /**
- * @private
- *
- * Returns `a * (1 - t) ^ 3 + 3 * b (1 - t) ^ 2 * t + 3 * c (1 - t) * t ^ 3 + d * t ^ 3`.
- *
- * @param {Number} a
- * @param {Number} b
- * @param {Number} c
- * @param {Number} d
- * @param {Number} t
- * @return {Number}
- */
- interpolate: function(a, b, c, d, t) {
- var rate;
- if (t === 0) {
- return a;
- }
- if (t === 1) {
- return d;
- }
- rate = (1 - t) / t;
- return t * t * t * (d + rate * (3 * c + rate * (3 * b + rate * a)));
- },
- /**
- * Reconstruct path from cubic bezier curve stripes.
- * @param {Array} stripes
- */
- fromStripes: function(stripes) {
- var me = this,
- i = 0,
- ln = stripes.length,
- j, ln2, stripe;
- me.clear();
- for (; i < ln; i++) {
- stripe = stripes[i];
- me.params.push.apply(me.params, stripe);
- me.commands.push('M');
- for (j = 2 , ln2 = stripe.length; j < ln2; j += 6) {
- me.commands.push('C');
- }
- }
- if (!me.cursor) {
- me.cursor = [];
- }
- me.cursor[0] = me.params[me.params.length - 2];
- me.cursor[1] = me.params[me.params.length - 1];
- me.dirt();
- },
- /**
- * Convert path to bezier curve stripes.
- * @param {Array} [target] The optional array to receive the result.
- * @return {Array}
- */
- toStripes: function(target) {
- var stripes = target || [],
- curr, x, y, lastX, lastY, startX, startY, i, j,
- commands = this.commands,
- params = this.params,
- ln = commands.length;
- for (i = 0 , j = 0; i < ln; i++) {
- switch (commands[i]) {
- case 'M':
- curr = [
- startX = lastX = params[j++],
- startY = lastY = params[j++]
- ];
- stripes.push(curr);
- break;
- case 'L':
- x = params[j++];
- y = params[j++];
- curr.push((lastX + lastX + x) / 3, (lastY + lastY + y) / 3, (lastX + x + x) / 3, (lastY + y + y) / 3, lastX = x, lastY = y);
- break;
- case 'C':
- curr.push(params[j++], params[j++], params[j++], params[j++], lastX = params[j++], lastY = params[j++]);
- break;
- case 'Z':
- x = startX;
- y = startY;
- curr.push((lastX + lastX + x) / 3, (lastY + lastY + y) / 3, (lastX + x + x) / 3, (lastY + y + y) / 3, lastX = x, lastY = y);
- break;
- }
- }
- return stripes;
- },
- /**
- * @private
- * Update cache for svg string of this path.
- */
- updateSvgString: function() {
- var result = [],
- commands = this.commands,
- params = this.params,
- ln = commands.length,
- i = 0,
- j = 0;
- for (; i < ln; i++) {
- switch (commands[i]) {
- case 'M':
- result.push('M' + params[j] + ',' + params[j + 1]);
- j += 2;
- break;
- case 'L':
- result.push('L' + params[j] + ',' + params[j + 1]);
- j += 2;
- break;
- case 'C':
- result.push('C' + params[j] + ',' + params[j + 1] + ' ' + params[j + 2] + ',' + params[j + 3] + ' ' + params[j + 4] + ',' + params[j + 5]);
- j += 6;
- break;
- case 'Z':
- result.push('Z');
- break;
- }
- }
- this.svgString = result.join('');
- },
- /**
- * Return an svg path string for this path.
- * @return {String}
- */
- toString: function() {
- if (!this.svgString) {
- this.updateSvgString();
- }
- return this.svgString;
- }
- });
- /**
- * @private
- * Adds hit testing and path intersection points methods to the Ext.draw.Path.
- * Included by the Ext.draw.PathUtil.
- */
- Ext.define('Ext.draw.overrides.hittest.Path', {
- override: 'Ext.draw.Path',
- // An arbitrary point outside the path used for hit testing with ray casting method.
- rayOrigin: {
- x: -10000,
- y: -10000
- },
- /**
- * Tests whether the given point is inside the path.
- * @param {Number} x
- * @param {Number} y
- * @return {Boolean}
- * @member Ext.draw.Path
- */
- isPointInPath: function(x, y) {
- var me = this,
- commands = me.commands,
- solver = Ext.draw.PathUtil,
- origin = me.rayOrigin,
- params = me.params,
- ln = commands.length,
- firstX = null,
- firstY = null,
- lastX = 0,
- lastY = 0,
- count = 0,
- i, j;
- for (i = 0 , j = 0; i < ln; i++) {
- switch (commands[i]) {
- case 'M':
- if (firstX !== null) {
- // eslint-disable-next-line max-len
- if (solver.linesIntersection(firstX, firstY, lastX, lastY, origin.x, origin.y, x, y)) {
- count += 1;
- }
- };
- firstX = lastX = params[j];
- firstY = lastY = params[j + 1];
- j += 2;
- break;
- case 'L':
- // eslint-disable-next-line max-len
- if (solver.linesIntersection(lastX, lastY, params[j], params[j + 1], origin.x, origin.y, x, y)) {
- count += 1;
- };
- lastX = params[j];
- lastY = params[j + 1];
- j += 2;
- break;
- case 'C':
- 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;
- lastX = params[j + 4];
- lastY = params[j + 5];
- j += 6;
- break;
- case 'Z':
- if (firstX !== null) {
- // eslint-disable-next-line max-len
- if (solver.linesIntersection(firstX, firstY, lastX, lastY, origin.x, origin.y, x, y)) {
- count += 1;
- }
- };
- break;
- }
- }
- return count % 2 === 1;
- },
- /**
- * Tests whether the given point is on the path.
- * @param {Number} x
- * @param {Number} y
- * @return {Boolean}
- * @member Ext.draw.Path
- */
- isPointOnPath: function(x, y) {
- var me = this,
- commands = me.commands,
- solver = Ext.draw.PathUtil,
- params = me.params,
- ln = commands.length,
- firstX = null,
- firstY = null,
- lastX = 0,
- lastY = 0,
- i, j;
- for (i = 0 , j = 0; i < ln; i++) {
- switch (commands[i]) {
- case 'M':
- if (firstX !== null) {
- if (solver.pointOnLine(firstX, firstY, lastX, lastY, x, y)) {
- return true;
- }
- };
- firstX = lastX = params[j];
- firstY = lastY = params[j + 1];
- j += 2;
- break;
- case 'L':
- if (solver.pointOnLine(lastX, lastY, params[j], params[j + 1], x, y)) {
- return true;
- };
- lastX = params[j];
- lastY = params[j + 1];
- j += 2;
- break;
- case 'C':
- if (solver.pointOnCubic(lastX, params[j], params[j + 2], params[j + 4], lastY, params[j + 1], params[j + 3], params[j + 5], x, y)) {
- return true;
- };
- lastX = params[j + 4];
- lastY = params[j + 5];
- j += 6;
- break;
- case 'Z':
- if (firstX !== null) {
- if (solver.pointOnLine(firstX, firstY, lastX, lastY, x, y)) {
- return true;
- }
- };
- break;
- }
- }
- return false;
- },
- /**
- * Calculates the points where the given segment intersects the path.
- * If four parameters are given then the segment is considered to be a line segment,
- * where given parameters are the coordinates of the start and end points.
- * If eight parameters are given then the segment is considered to be
- * a cubic Bezier curve segment, where given parameters are the
- * coordinates of its edge points and control points.
- * @param x1
- * @param y1
- * @param x2
- * @param y2
- * @param x3
- * @param y3
- * @param x4
- * @param y4
- * @return {Array}
- * @member Ext.draw.Path
- */
- getSegmentIntersections: function(x1, y1, x2, y2, x3, y3, x4, y4) {
- var me = this,
- count = arguments.length,
- solver = Ext.draw.PathUtil,
- commands = me.commands,
- params = me.params,
- ln = commands.length,
- firstX = null,
- firstY = null,
- lastX = 0,
- lastY = 0,
- intersections = [],
- i, j, points;
- for (i = 0 , j = 0; i < ln; i++) {
- switch (commands[i]) {
- case 'M':
- if (firstX !== null) {
- switch (count) {
- case 4:
- points = solver.linesIntersection(firstX, firstY, lastX, lastY, x1, y1, x2, y2);
- if (points) {
- intersections.push(points);
- };
- break;
- case 8:
- points = solver.cubicLineIntersections(x1, x2, x3, x4, y1, y2, y3, y4, firstX, firstY, lastX, lastY);
- intersections.push.apply(intersections, points);
- break;
- }
- };
- firstX = lastX = params[j];
- firstY = lastY = params[j + 1];
- j += 2;
- break;
- case 'L':
- switch (count) {
- case 4:
- points = solver.linesIntersection(lastX, lastY, params[j], params[j + 1], x1, y1, x2, y2);
- if (points) {
- intersections.push(points);
- };
- break;
- case 8:
- points = solver.cubicLineIntersections(x1, x2, x3, x4, y1, y2, y3, y4, lastX, lastY, params[j], params[j + 1]);
- intersections.push.apply(intersections, points);
- break;
- };
- lastX = params[j];
- lastY = params[j + 1];
- j += 2;
- break;
- case 'C':
- switch (count) {
- case 4:
- 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);
- intersections.push.apply(intersections, points);
- break;
- case 8:
- 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);
- intersections.push.apply(intersections, points);
- break;
- };
- lastX = params[j + 4];
- lastY = params[j + 5];
- j += 6;
- break;
- case 'Z':
- if (firstX !== null) {
- switch (count) {
- case 4:
- points = solver.linesIntersection(firstX, firstY, lastX, lastY, x1, y1, x2, y2);
- if (points) {
- intersections.push(points);
- };
- break;
- case 8:
- points = solver.cubicLineIntersections(x1, x2, x3, x4, y1, y2, y3, y4, firstX, firstY, lastX, lastY);
- intersections.push.apply(intersections, points);
- break;
- }
- };
- break;
- }
- }
- return intersections;
- },
- getIntersections: function(path) {
- var me = this,
- commands = me.commands,
- params = me.params,
- ln = commands.length,
- firstX = null,
- firstY = null,
- lastX = 0,
- lastY = 0,
- intersections = [],
- i, j, points;
- for (i = 0 , j = 0; i < ln; i++) {
- switch (commands[i]) {
- case 'M':
- if (firstX !== null) {
- points = path.getSegmentIntersections.call(path, firstX, firstY, lastX, lastY);
- intersections.push.apply(intersections, points);
- };
- firstX = lastX = params[j];
- firstY = lastY = params[j + 1];
- j += 2;
- break;
- case 'L':
- points = path.getSegmentIntersections.call(path, lastX, lastY, params[j], params[j + 1]);
- intersections.push.apply(intersections, points);
- lastX = params[j];
- lastY = params[j + 1];
- j += 2;
- break;
- case 'C':
- points = path.getSegmentIntersections.call(path, lastX, lastY, params[j], params[j + 1], params[j + 2], params[j + 3], params[j + 4], params[j + 5]);
- intersections.push.apply(intersections, points);
- lastX = params[j + 4];
- lastY = params[j + 5];
- j += 6;
- break;
- case 'Z':
- if (firstX !== null) {
- points = path.getSegmentIntersections.call(path, firstX, firstY, lastX, lastY);
- intersections.push.apply(intersections, points);
- };
- break;
- }
- }
- return intersections;
- }
- });
- /**
- * @class Ext.draw.sprite.Path
- * @extends Ext.draw.sprite.Sprite
- *
- * A sprite that represents a path.
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'path',
- * path: 'M20,30 c0,-50 75,50 75,0 c0,-50 -75,50 -75,0',
- * fillStyle: '#1F6D91'
- * }]
- * });
- *
- * ### Drawing with SVG Paths
- * You may use special SVG Path syntax to "describe" the drawing path.
- * Here are the SVG path commands:
- *
- * + M = moveto
- * + L = lineto
- * + H = horizontal lineto
- * + V = vertical lineto
- * + C = curveto
- * + S = smooth curveto
- * + Q = quadratic Bézier curve
- * + T = smooth quadratic Bézier curveto
- * + A = elliptical Arc
- * + Z = closepath
- *
- * **Note:** Capital letters indicate that the item should be absolutely positioned.
- * Use lower case letters for relative positioning.
- */
- Ext.define('Ext.draw.sprite.Path', {
- extend: 'Ext.draw.sprite.Sprite',
- requires: [
- 'Ext.draw.Draw',
- 'Ext.draw.Path'
- ],
- alias: [
- 'sprite.path',
- 'Ext.draw.Sprite'
- ],
- type: 'path',
- isPath: true,
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {String} path The SVG based path string used by the sprite.
- */
- path: function(n, o) {
- if (!(n instanceof Ext.draw.Path)) {
- n = new Ext.draw.Path(n);
- }
- return n;
- }
- },
- aliases: {
- d: 'path'
- },
- triggers: {
- path: 'bbox'
- },
- updaters: {
- path: function(attr) {
- var path = attr.path;
- if (!path || path.bindAttr !== attr) {
- path = new Ext.draw.Path();
- path.bindAttr = attr;
- attr.path = path;
- }
- path.clear();
- this.updatePath(path, attr);
- this.scheduleUpdater(attr, 'bbox', [
- 'path'
- ]);
- }
- }
- }
- },
- updatePlainBBox: function(plain) {
- if (this.attr.path) {
- this.attr.path.getDimension(plain);
- }
- },
- updateTransformedBBox: function(transform) {
- if (this.attr.path) {
- this.attr.path.getDimensionWithTransform(this.attr.matrix, transform);
- }
- },
- render: function(surface, ctx) {
- var mat = this.attr.matrix,
- attr = this.attr;
- if (!attr.path || attr.path.params.length === 0) {
- return;
- }
- mat.toContext(ctx);
- ctx.appendPath(attr.path);
- ctx.fillStroke(attr);
- //<debug>
- // eslint-disable-next-line vars-on-top
- var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
- if (debug) {
- if (debug.bbox) {
- this.renderBBox(surface, ctx);
- }
- if (debug.xray) {
- this.renderXRay(surface, ctx);
- }
- }
- },
- //</debug>
- //<debug>
- renderXRay: function(surface, ctx) {
- var attr = this.attr,
- mat = attr.matrix,
- imat = attr.inverseMatrix,
- path = attr.path,
- commands = path.commands,
- params = path.params,
- ln = commands.length,
- twoPi = Math.PI * 2,
- size = 2,
- i, j;
- mat.toContext(ctx);
- ctx.beginPath();
- for (i = 0 , j = 0; i < ln; i++) {
- switch (commands[i]) {
- case 'M':
- ctx.moveTo(params[j] - size, params[j + 1] - size);
- ctx.rect(params[j] - size, params[j + 1] - size, size * 2, size * 2);
- j += 2;
- break;
- case 'L':
- ctx.moveTo(params[j] - size, params[j + 1] - size);
- ctx.rect(params[j] - size, params[j + 1] - size, size * 2, size * 2);
- j += 2;
- break;
- case 'C':
- ctx.moveTo(params[j] + size, params[j + 1]);
- ctx.arc(params[j], params[j + 1], size, 0, twoPi, true);
- j += 2;
- ctx.moveTo(params[j] + size, params[j + 1]);
- ctx.arc(params[j], params[j + 1], size, 0, twoPi, true);
- j += 2;
- ctx.moveTo(params[j] + size * 2, params[j + 1]);
- ctx.rect(params[j] - size, params[j + 1] - size, size * 2, size * 2);
- j += 2;
- break;
- default:
- }
- }
- imat.toContext(ctx);
- ctx.strokeStyle = 'black';
- ctx.strokeOpacity = 1;
- ctx.lineWidth = 1;
- ctx.stroke();
- mat.toContext(ctx);
- ctx.beginPath();
- for (i = 0 , j = 0; i < ln; i++) {
- switch (commands[i]) {
- case 'M':
- ctx.moveTo(params[j], params[j + 1]);
- j += 2;
- break;
- case 'L':
- ctx.moveTo(params[j], params[j + 1]);
- j += 2;
- break;
- case 'C':
- ctx.lineTo(params[j], params[j + 1]);
- j += 2;
- ctx.moveTo(params[j], params[j + 1]);
- j += 2;
- ctx.lineTo(params[j], params[j + 1]);
- j += 2;
- break;
- default:
- }
- }
- imat.toContext(ctx);
- ctx.lineWidth = 0.5;
- ctx.stroke();
- },
- //</debug>
- /**
- * Update the path.
- * @param {Ext.draw.Path} path An empty path to draw on using path API.
- * @param {Object} attr The attribute object. Note: DO NOT use the `sprite.attr` instead of this
- * if you want to work with instancing.
- */
- updatePath: function(path, attr) {}
- });
- /**
- * @private
- * Adds hit testing methods to the Ext.draw.sprite.Path sprite.
- * Included by the Ext.draw.PathUtil.
- */
- Ext.define('Ext.draw.overrides.hittest.sprite.Path', {
- override: 'Ext.draw.sprite.Path',
- requires: [
- 'Ext.draw.Color'
- ],
- /**
- * Tests whether the given point is inside the path.
- * @param x
- * @param y
- * @return {Boolean}
- * @member Ext.draw.sprite.Path
- */
- isPointInPath: function(x, y) {
- var attr = this.attr,
- path, matrix, params, result;
- if (attr.fillStyle === Ext.util.Color.RGBA_NONE) {
- return this.isPointOnPath(x, y);
- }
- path = attr.path;
- matrix = attr.matrix;
- if (!matrix.isIdentity()) {
- params = path.params.slice(0);
- path.transform(attr.matrix);
- }
- result = path.isPointInPath(x, y);
- if (params) {
- path.params = params;
- }
- return result;
- },
- /**
- * Tests whether the given point is on the path.
- * @param x
- * @param y
- * @return {Boolean}
- * @member Ext.draw.sprite.Path
- */
- isPointOnPath: function(x, y) {
- var attr = this.attr,
- path = attr.path,
- matrix = attr.matrix,
- params, result;
- if (!matrix.isIdentity()) {
- params = path.params.slice(0);
- path.transform(attr.matrix);
- }
- result = path.isPointOnPath(x, y);
- if (params) {
- path.params = params;
- }
- return result;
- },
- /**
- * @method hitTest
- * @inheritdoc Ext.draw.Surface#method-hitTest
- */
- hitTest: function(point, options) {
- var me = this,
- attr = me.attr,
- path = attr.path,
- matrix = attr.matrix,
- x = point[0],
- y = point[1],
- parentResult = me.callParent([
- point,
- options
- ]),
- result = null,
- params, isFilled;
- if (!parentResult) {
- // The sprite is not visible or bounding box wasn't hit.
- return result;
- }
- options = options || Ext.draw.sprite.Sprite.defaultHitTestOptions;
- if (!matrix.isIdentity()) {
- params = path.params.slice(0);
- path.transform(attr.matrix);
- }
- if (options.fill && options.stroke) {
- isFilled = attr.fillStyle !== Ext.util.Color.NONE && attr.fillStyle !== Ext.util.Color.RGBA_NONE;
- if (isFilled) {
- if (path.isPointInPath(x, y)) {
- result = {
- sprite: me
- };
- }
- } else {
- if (path.isPointInPath(x, y) || path.isPointOnPath(x, y)) {
- result = {
- sprite: me
- };
- }
- }
- } else if (options.stroke && !options.fill) {
- if (path.isPointOnPath(x, y)) {
- result = {
- sprite: me
- };
- }
- } else if (options.fill && !options.stroke) {
- if (path.isPointInPath(x, y)) {
- result = {
- sprite: me
- };
- }
- }
- if (params) {
- path.params = params;
- }
- return result;
- },
- /**
- * Returns all points where this sprite intersects the given sprite.
- * The given sprite must be an instance of the {@link Ext.draw.sprite.Path} class
- * or its subclass.
- * @param path
- * @return {Array}
- * @member Ext.draw.sprite.Path
- */
- getIntersections: function(path) {
- if (!(path.isSprite && path.isPath)) {
- return [];
- }
- // eslint-disable-next-line vars-on-top
- var aAttr = this.attr,
- bAttr = path.attr,
- aPath = aAttr.path,
- bPath = bAttr.path,
- aMatrix = aAttr.matrix,
- bMatrix = bAttr.matrix,
- aParams, bParams, intersections;
- if (!aMatrix.isIdentity()) {
- aParams = aPath.params.slice(0);
- aPath.transform(aAttr.matrix);
- }
- if (!bMatrix.isIdentity()) {
- bParams = bPath.params.slice(0);
- bPath.transform(bAttr.matrix);
- }
- intersections = aPath.getIntersections(bPath);
- if (aParams) {
- aPath.params = aParams;
- }
- if (bParams) {
- bPath.params = bParams;
- }
- return intersections;
- }
- });
- /**
- * @class Ext.draw.sprite.Circle
- * @extends Ext.draw.sprite.Path
- *
- * A sprite that represents a circle.
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'circle',
- * cx: 100,
- * cy: 100,
- * r: 50,
- * fillStyle: '#1F6D91'
- * }]
- * });
- */
- Ext.define('Ext.draw.sprite.Circle', {
- extend: 'Ext.draw.sprite.Path',
- alias: 'sprite.circle',
- type: 'circle',
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Number} [cx=0] The center coordinate of the sprite on the x-axis.
- */
- cx: 'number',
- /**
- * @cfg {Number} [cy=0] The center coordinate of the sprite on the y-axis.
- */
- cy: 'number',
- /**
- * @cfg {Number} [r=0] The radius of the sprite.
- */
- r: 'number'
- },
- aliases: {
- radius: 'r',
- x: 'cx',
- y: 'cy',
- centerX: 'cx',
- centerY: 'cy'
- },
- defaults: {
- cx: 0,
- cy: 0,
- r: 4
- },
- triggers: {
- cx: 'path',
- cy: 'path',
- r: 'path'
- }
- }
- },
- updatePlainBBox: function(plain) {
- var attr = this.attr,
- cx = attr.cx,
- cy = attr.cy,
- r = attr.r;
- plain.x = cx - r;
- plain.y = cy - r;
- plain.width = r + r;
- plain.height = r + r;
- },
- updateTransformedBBox: function(transform) {
- var attr = this.attr,
- cx = attr.cx,
- cy = attr.cy,
- r = attr.r,
- matrix = attr.matrix,
- scaleX = matrix.getScaleX(),
- scaleY = matrix.getScaleY(),
- rx, ry;
- rx = scaleX * r;
- ry = scaleY * r;
- transform.x = matrix.x(cx, cy) - rx;
- transform.y = matrix.y(cx, cy) - ry;
- transform.width = rx + rx;
- transform.height = ry + ry;
- },
- updatePath: function(path, attr) {
- path.arc(attr.cx, attr.cy, attr.r, 0, Math.PI * 2, false);
- }
- });
- /**
- * @class Ext.draw.sprite.Arc
- * @extend Ext.draw.sprite.Circle
- *
- * A sprite that represents a circular arc.
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'arc',
- * cx: 100,
- * cy: 100,
- * r: 80,
- * fillStyle: '#1F6D91',
- * startAngle: 0,
- * endAngle: Math.PI,
- * anticlockwise: true
- * }]
- * });
- */
- Ext.define('Ext.draw.sprite.Arc', {
- extend: 'Ext.draw.sprite.Circle',
- alias: 'sprite.arc',
- type: 'arc',
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Number} [startAngle=0] The beginning angle of the arc.
- */
- startAngle: 'number',
- /**
- * @cfg {Number} [endAngle=Math.PI*2] The ending angle of the arc.
- */
- endAngle: 'number',
- /**
- * @cfg {Boolean} [anticlockwise=false] Determines whether or not the arc is drawn
- * clockwise.
- */
- anticlockwise: 'bool'
- },
- aliases: {
- from: 'startAngle',
- to: 'endAngle',
- start: 'startAngle',
- end: 'endAngle'
- },
- defaults: {
- startAngle: 0,
- endAngle: Math.PI * 2,
- anticlockwise: false
- },
- triggers: {
- startAngle: 'path',
- endAngle: 'path',
- anticlockwise: 'path'
- }
- }
- },
- updatePath: function(path, attr) {
- path.arc(attr.cx, attr.cy, attr.r, attr.startAngle, attr.endAngle, attr.anticlockwise);
- }
- });
- /**
- * A sprite that represents an arrow.
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'arrow',
- * translationX: 100,
- * translationY: 100,
- * size: 40,
- * fillStyle: '#30BDA7'
- * }]
- * });
- */
- Ext.define('Ext.draw.sprite.Arrow', {
- extend: 'Ext.draw.sprite.Path',
- alias: 'sprite.arrow',
- inheritableStatics: {
- def: {
- processors: {
- x: 'number',
- y: 'number',
- /**
- * @cfg {Number} [size=4] The size of the sprite.
- * Meant to be comparable to the size of a circle sprite with the same radius.
- */
- size: 'number'
- },
- defaults: {
- x: 0,
- y: 0,
- size: 4
- },
- triggers: {
- x: 'path',
- y: 'path',
- size: 'path'
- }
- }
- },
- updatePath: function(path, attr) {
- var s = attr.size * 1.5,
- x = attr.x - attr.lineWidth / 2,
- y = attr.y;
- path.fromSvgString('M'.concat(x - s * 0.7, ',', y - s * 0.4, 'l', [
- s * 0.6,
- 0,
- 0,
- -s * 0.4,
- s,
- s * 0.8,
- -s,
- s * 0.8,
- 0,
- -s * 0.4,
- -s * 0.6,
- 0
- ], 'z'));
- }
- });
- /**
- * @class Ext.draw.sprite.Composite
- *
- * Represents a group of sprites.
- * Composite's sprites are rendered in the order they've been added to the Composite.
- * The rendering order of composite sprites themselves is determined by the value of
- * their zIndex attribute, just like with any other sprite.
- * Every sprite that is added to the Composite is removed from whatever Surface/Composite
- * it belongs to.
- */
- Ext.define('Ext.draw.sprite.Composite', {
- extend: 'Ext.draw.sprite.Sprite',
- alias: 'sprite.composite',
- type: 'composite',
- isComposite: true,
- config: {
- sprites: []
- },
- constructor: function(config) {
- this.sprites = [];
- this.map = {};
- this.callParent([
- config
- ]);
- },
- /**
- * Adds sprite(s) to the composite.
- * @param {Ext.draw.sprite.Sprite/Ext.draw.sprite.Sprite[]/Object/Object[]} sprite
- * @return {Ext.draw.sprite.Sprite/Ext.draw.sprite.Sprite[]}
- */
- addSprite: function(sprite) {
- var i = 0,
- attr, results, oldTransformations;
- if (Ext.isArray(sprite)) {
- results = [];
- while (i < sprite.length) {
- results.push(this.addSprite(sprite[i++]));
- }
- return results;
- }
- if (sprite && sprite.type && !sprite.isSprite) {
- sprite = Ext.create('sprite.' + sprite.type, sprite);
- }
- if (!sprite || !sprite.isSprite || sprite.isComposite) {
- return null;
- }
- sprite.setSurface(null);
- sprite.setParent(this);
- attr = this.attr;
- oldTransformations = sprite.applyTransformations;
- sprite.applyTransformations = function(force) {
- if (sprite.attr.dirtyTransform) {
- attr.dirtyTransform = true;
- attr.bbox.plain.dirty = true;
- attr.bbox.transform.dirty = true;
- }
- oldTransformations.call(sprite, force);
- };
- this.sprites.push(sprite);
- this.map[sprite.id] = sprite.getId();
- attr.bbox.plain.dirty = true;
- attr.bbox.transform.dirty = true;
- return sprite;
- },
- /**
- * @deprecated 6.2.1 Use {@link #addSprite} instead.
- */
- add: function(sprite) {
- return this.addSprite(sprite);
- },
- removeSprite: function(sprite, isDestroy) {
- var me = this,
- id, isOwnSprite;
- if (sprite) {
- if (sprite.charAt) {
- // is String
- sprite = me.map[sprite];
- }
- if (!sprite || !sprite.isSprite) {
- return null;
- }
- if (sprite.destroyed || sprite.destroying) {
- return sprite;
- }
- id = sprite.getId();
- isOwnSprite = me.map[id];
- delete me.map[id];
- if (isDestroy) {
- sprite.destroy();
- }
- if (!isOwnSprite) {
- return sprite;
- }
- sprite.setParent(null);
- // sprite.setSurface(null);
- Ext.Array.remove(me.sprites, sprite);
- me.dirtyZIndex = true;
- me.setDirty(true);
- }
- return sprite || null;
- },
- /**
- * @deprecated 6.2.1 Use {@link #addSprite} instead.
- * Adds a list of sprites to the composite.
- * @param {Ext.draw.sprite.Sprite[]|Object[]|Ext.draw.sprite.Sprite|Object} sprites
- */
- addAll: function(sprites) {
- var i = 0;
- if (sprites.isSprite || sprites.type) {
- this.add(sprites);
- } else if (Ext.isArray(sprites)) {
- while (i < sprites.length) {
- this.add(sprites[i++]);
- }
- }
- },
- /**
- * Updates the bounding box of the composite, which contains the bounding box of all sprites
- * in the composite.
- */
- updatePlainBBox: function(plain) {
- var me = this,
- left = Infinity,
- right = -Infinity,
- top = Infinity,
- bottom = -Infinity,
- sprite, bbox, i, ln;
- for (i = 0 , ln = me.sprites.length; i < ln; i++) {
- sprite = me.sprites[i];
- sprite.applyTransformations();
- bbox = sprite.getBBox();
- if (left > bbox.x) {
- left = bbox.x;
- }
- if (right < bbox.x + bbox.width) {
- right = bbox.x + bbox.width;
- }
- if (top > bbox.y) {
- top = bbox.y;
- }
- if (bottom < bbox.y + bbox.height) {
- bottom = bbox.y + bbox.height;
- }
- }
- plain.x = left;
- plain.y = top;
- plain.width = right - left;
- plain.height = bottom - top;
- },
- isVisible: function() {
- // Override the abstract Sprite's method.
- // Composite uses a simpler check, because it has no fill or stroke
- // style of its own, it just houses other sprites.
- var attr = this.attr,
- parent = this.getParent(),
- hasParent = parent && (parent.isSurface || parent.isVisible()),
- isSeen = hasParent && !attr.hidden && attr.globalAlpha;
- return !!isSeen;
- },
- /**
- * Renders all sprites contained in the composite to the surface.
- */
- render: function(surface, ctx, rect) {
- var me = this,
- attr = me.attr,
- mat = me.attr.matrix,
- sprites = me.sprites,
- ln = sprites.length,
- i = 0;
- mat.toContext(ctx);
- for (; i < ln; i++) {
- surface.renderSprite(sprites[i], rect);
- }
- //<debug>
- // eslint-disable-next-line vars-on-top
- var debug = attr.debug || me.statics().debug || Ext.draw.sprite.Sprite.debug;
- if (debug) {
- attr.inverseMatrix.toContext(ctx);
- if (debug.bbox) {
- me.renderBBox(surface, ctx);
- }
- }
- },
- //</debug>
- destroy: function() {
- var me = this,
- sprites = me.sprites,
- ln = sprites.length,
- i;
- for (i = 0; i < ln; i++) {
- sprites[i].destroy();
- }
- sprites.length = 0;
- me.callParent();
- }
- });
- /**
- * A sprite that represents a cross.
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'cross',
- * translationX: 100,
- * translationY: 100,
- * size: 40,
- * fillStyle: '#1F6D91'
- * }]
- * });
- */
- Ext.define('Ext.draw.sprite.Cross', {
- extend: 'Ext.draw.sprite.Path',
- alias: 'sprite.cross',
- inheritableStatics: {
- def: {
- processors: {
- x: 'number',
- y: 'number',
- /**
- * @cfg {Number} [size=4] The size of the sprite.
- * Meant to be comparable to the size of a circle sprite with the same radius.
- */
- size: 'number'
- },
- defaults: {
- x: 0,
- y: 0,
- size: 4
- },
- triggers: {
- x: 'path',
- y: 'path',
- size: 'path'
- }
- }
- },
- updatePath: function(path, attr) {
- var s = attr.size / 1.7,
- x = attr.x - attr.lineWidth / 2,
- y = attr.y;
- path.fromSvgString('M'.concat(x - s, ',', y, 'l', [
- -s,
- -s,
- s,
- -s,
- s,
- s,
- s,
- -s,
- s,
- s,
- -s,
- s,
- s,
- s,
- -s,
- s,
- -s,
- -s,
- -s,
- s,
- -s,
- -s,
- 'z'
- ]));
- }
- });
- /**
- * A sprite that represents a diamond.
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'diamond',
- * translationX: 100,
- * translationY: 100,
- * size: 40,
- * fillStyle: '#1F6D91'
- * }]
- * });
- */
- Ext.define('Ext.draw.sprite.Diamond', {
- extend: 'Ext.draw.sprite.Path',
- alias: 'sprite.diamond',
- inheritableStatics: {
- def: {
- processors: {
- x: 'number',
- y: 'number',
- /**
- * @cfg {Number} [size=4] The size of the sprite.
- * Meant to be comparable to the size of a circle sprite with the same radius.
- */
- size: 'number'
- },
- defaults: {
- x: 0,
- y: 0,
- size: 4
- },
- triggers: {
- x: 'path',
- y: 'path',
- size: 'path'
- }
- }
- },
- updatePath: function(path, attr) {
- var s = attr.size * 1.25,
- x = attr.x - attr.lineWidth / 2,
- y = attr.y;
- path.fromSvgString([
- 'M',
- x,
- y - s,
- 'l',
- s,
- s,
- -s,
- s,
- -s,
- -s,
- s,
- -s,
- 'z'
- ]);
- }
- });
- /**
- * @class Ext.draw.sprite.Ellipse
- * @extends Ext.draw.sprite.Path
- *
- * A sprite that represents an ellipse.
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'ellipse',
- * cx: 100,
- * cy: 100,
- * rx: 80,
- * ry: 50,
- * fillStyle: '#1F6D91'
- * }]
- * });
- */
- Ext.define("Ext.draw.sprite.Ellipse", {
- extend: "Ext.draw.sprite.Path",
- alias: 'sprite.ellipse',
- type: 'ellipse',
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Number} [cx=0] The center coordinate of the sprite on the x-axis.
- */
- cx: "number",
- /**
- * @cfg {Number} [cy=0] The center coordinate of the sprite on the y-axis.
- */
- cy: "number",
- /**
- * @cfg {Number} [rx=1] The radius of the sprite on the x-axis.
- */
- rx: "number",
- /**
- * @cfg {Number} [ry=1] The radius of the sprite on the y-axis.
- */
- ry: "number",
- /**
- * @cfg {Number} [axisRotation=0] The rotation of the sprite about its axis.
- */
- axisRotation: "number"
- },
- aliases: {
- radius: "r",
- x: "cx",
- y: "cy",
- centerX: "cx",
- centerY: "cy",
- radiusX: "rx",
- radiusY: "ry"
- },
- defaults: {
- cx: 0,
- cy: 0,
- rx: 1,
- ry: 1,
- axisRotation: 0
- },
- triggers: {
- cx: 'path',
- cy: 'path',
- rx: 'path',
- ry: 'path',
- axisRotation: 'path'
- }
- }
- },
- updatePlainBBox: function(plain) {
- var attr = this.attr,
- cx = attr.cx,
- cy = attr.cy,
- rx = attr.rx,
- ry = attr.ry;
- plain.x = cx - rx;
- plain.y = cy - ry;
- plain.width = rx + rx;
- plain.height = ry + ry;
- },
- updateTransformedBBox: function(transform) {
- var attr = this.attr,
- cx = attr.cx,
- cy = attr.cy,
- rx = attr.rx,
- ry = attr.ry,
- rxy = ry / rx,
- matrix = attr.matrix.clone(),
- xx, xy, yx, yy, dx, dy, w, h;
- matrix.append(1, 0, 0, rxy, 0, cy * (1 - rxy));
- xx = matrix.getXX();
- yx = matrix.getYX();
- dx = matrix.getDX();
- xy = matrix.getXY();
- yy = matrix.getYY();
- dy = matrix.getDY();
- w = Math.sqrt(xx * xx + yx * yx) * rx;
- h = Math.sqrt(xy * xy + yy * yy) * rx;
- transform.x = cx * xx + cy * yx + dx - w;
- transform.y = cx * xy + cy * yy + dy - h;
- transform.width = w + w;
- transform.height = h + h;
- },
- updatePath: function(path, attr) {
- path.ellipse(attr.cx, attr.cy, attr.rx, attr.ry, attr.axisRotation, 0, Math.PI * 2, false);
- }
- });
- /**
- * @class Ext.draw.sprite.EllipticalArc
- * @extends Ext.draw.sprite.Ellipse
- *
- * A sprite that represents an elliptical arc.
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'ellipticalArc',
- * cx: 100,
- * cy: 100,
- * rx: 80,
- * ry: 50,
- * fillStyle: '#1F6D91',
- * startAngle: 0,
- * endAngle: Math.PI,
- * anticlockwise: true
- * }]
- * });
- */
- Ext.define('Ext.draw.sprite.EllipticalArc', {
- extend: 'Ext.draw.sprite.Ellipse',
- alias: 'sprite.ellipticalArc',
- type: 'ellipticalArc',
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Number} [startAngle=0] The beginning angle of the arc.
- */
- startAngle: 'number',
- /**
- * @cfg {Number} [endAngle=Math.PI*2] The ending angle of the arc.
- */
- endAngle: 'number',
- /**
- * @cfg {Boolean} [anticlockwise=false] Determines whether or not the arc
- * is drawn clockwise.
- */
- anticlockwise: 'bool'
- },
- aliases: {
- from: 'startAngle',
- to: 'endAngle',
- start: 'startAngle',
- end: 'endAngle'
- },
- defaults: {
- startAngle: 0,
- endAngle: Math.PI * 2,
- anticlockwise: false
- },
- triggers: {
- startAngle: 'path',
- endAngle: 'path',
- anticlockwise: 'path'
- }
- }
- },
- updatePath: function(path, attr) {
- path.ellipse(attr.cx, attr.cy, attr.rx, attr.ry, attr.axisRotation, attr.startAngle, attr.endAngle, attr.anticlockwise);
- }
- });
- /**
- * @class Ext.draw.sprite.Rect
- * @extends Ext.draw.sprite.Path
- *
- * A sprite that represents a rectangle.
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'rect',
- * x: 50,
- * y: 50,
- * width: 100,
- * height: 100,
- * fillStyle: '#1F6D91'
- * }]
- * });
- */
- Ext.define('Ext.draw.sprite.Rect', {
- extend: 'Ext.draw.sprite.Path',
- alias: 'sprite.rect',
- type: 'rect',
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Number} [x=0] The position of the sprite on the x-axis.
- */
- x: 'number',
- /**
- * @cfg {Number} [y=0] The position of the sprite on the y-axis.
- */
- y: 'number',
- /**
- * @cfg {Number} [width=8] The width of the sprite.
- */
- width: 'number',
- /**
- * @cfg {Number} [height=8] The height of the sprite.
- */
- height: 'number',
- /**
- * @cfg {Number} [radius=0] The radius of the rounded corners.
- */
- radius: 'number'
- },
- aliases: {},
- triggers: {
- x: 'path',
- y: 'path',
- width: 'path',
- height: 'path',
- radius: 'path'
- },
- defaults: {
- x: 0,
- y: 0,
- width: 8,
- height: 8,
- radius: 0
- }
- }
- },
- updatePlainBBox: function(plain) {
- var attr = this.attr;
- plain.x = attr.x;
- plain.y = attr.y;
- plain.width = attr.width;
- plain.height = attr.height;
- },
- updateTransformedBBox: function(transform, plain) {
- this.attr.matrix.transformBBox(plain, this.attr.radius, transform);
- },
- updatePath: function(path, attr) {
- var x = attr.x,
- y = attr.y,
- width = attr.width,
- height = attr.height,
- radius = Math.min(attr.radius, Math.abs(height) * 0.5, Math.abs(width) * 0.5);
- if (radius === 0) {
- path.rect(x, y, width, height);
- } else {
- path.moveTo(x + radius, y);
- path.arcTo(x + width, y, x + width, y + height, radius);
- path.arcTo(x + width, y + height, x, y + height, radius);
- path.arcTo(x, y + height, x, y, radius);
- path.arcTo(x, y, x + radius, y, radius);
- path.closePath();
- }
- }
- });
- /**
- * @class Ext.draw.sprite.Image
- * @extends Ext.draw.sprite.Rect
- *
- * A sprite that represents an image.
- */
- Ext.define('Ext.draw.sprite.Image', {
- extend: 'Ext.draw.sprite.Rect',
- alias: 'sprite.image',
- type: 'image',
- statics: {
- imageLoaders: {}
- },
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {String} [src=''] The image source of the sprite.
- */
- src: 'string'
- },
- /**
- * @private
- * @cfg {Number} radius
- */
- triggers: {
- src: 'src'
- },
- updaters: {
- src: 'updateSource'
- },
- defaults: {
- src: '',
- /**
- * @cfg {Number} [width=null] The width of the image.
- * For consistent image size on all devices the width must be explicitly set.
- * Otherwise the natural image width devided by the device pixel ratio
- * (for a crisp looking image) will be used as the width of the sprite.
- */
- width: null,
- /**
- * @cfg {Number} [height=null] The height of the image.
- * For consistent image size on all devices the height must be explicitly set.
- * Otherwise the natural image height devided by the device pixel ratio
- * (for a crisp looking image) will be used as the height of the sprite.
- */
- height: null
- }
- }
- },
- updateSurface: function(surface) {
- if (surface) {
- this.updateSource(this.attr);
- }
- },
- updateSource: function(attr) {
- var me = this,
- src = attr.src,
- surface = me.getSurface(),
- loadingStub = Ext.draw.sprite.Image.imageLoaders[src],
- width = attr.width,
- height = attr.height,
- imageLoader, i;
- if (!surface) {
- // First time this is called the sprite won't have a surface yet.
- return;
- }
- if (!loadingStub) {
- imageLoader = new Image();
- loadingStub = Ext.draw.sprite.Image.imageLoaders[src] = {
- image: imageLoader,
- done: false,
- pendingSprites: [
- me
- ],
- pendingSurfaces: [
- surface
- ]
- };
- imageLoader.width = width;
- imageLoader.height = height;
- imageLoader.onload = function() {
- var item;
- if (!loadingStub.done) {
- loadingStub.done = true;
- for (i = 0; i < loadingStub.pendingSprites.length; i++) {
- item = loadingStub.pendingSprites[i];
- if (!item.destroyed) {
- item.setDirty(true);
- }
- }
- for (i = 0; i < loadingStub.pendingSurfaces.length; i++) {
- item = loadingStub.pendingSurfaces[i];
- if (!item.destroyed) {
- item.renderFrame();
- }
- }
- }
- };
- imageLoader.src = src;
- } else {
- Ext.Array.include(loadingStub.pendingSprites, me);
- Ext.Array.include(loadingStub.pendingSurfaces, surface);
- }
- },
- render: function(surface, ctx) {
- var me = this,
- attr = me.attr,
- mat = attr.matrix,
- src = attr.src,
- x = attr.x,
- y = attr.y,
- width = attr.width,
- height = attr.height,
- loadingStub = Ext.draw.sprite.Image.imageLoaders[src],
- image;
- if (loadingStub && loadingStub.done) {
- mat.toContext(ctx);
- image = loadingStub.image;
- ctx.drawImage(image, x, y, width || (image.naturalWidth || image.width) / surface.devicePixelRatio, height || (image.naturalHeight || image.height) / surface.devicePixelRatio);
- }
- //<debug>
- // eslint-disable-next-line vars-on-top
- var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
- if (debug && debug.bbox) {
- this.renderBBox(surface, ctx);
- }
- },
- //</debug>
- /**
- * @private
- */
- isVisible: function() {
- var attr = this.attr,
- parent = this.getParent(),
- hasParent = parent && (parent.isSurface || parent.isVisible()),
- isSeen = hasParent && !attr.hidden && attr.globalAlpha;
- return !!isSeen;
- }
- });
- /**
- * @class Ext.draw.sprite.Instancing
- * @extends Ext.draw.sprite.Sprite
- *
- * Sprite that represents multiple instances based on the given template.
- */
- Ext.define('Ext.draw.sprite.Instancing', {
- extend: 'Ext.draw.sprite.Sprite',
- alias: 'sprite.instancing',
- type: 'instancing',
- isInstancing: true,
- config: {
- /**
- * @cfg {Object} [template] The sprite template used by all instances.
- */
- template: null,
- /**
- * @cfg {Array} [instances]
- * The instances of the {@link #template} sprite as configs of attributes.
- */
- instances: null
- },
- instances: null,
- applyTemplate: function(template) {
- var surface;
- //<debug>
- if (!Ext.isObject(template)) {
- 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.");
- } else if (template.isInstancing || template.isComposite) {
- Ext.raise("Can't use an instancing or composite sprite " + "as a template for an instancing sprite.");
- }
- //</debug>
- if (!template.isSprite) {
- if (!template.xclass && !template.type) {
- // For compatibility with legacy charts.
- template.type = 'circle';
- }
- template = Ext.create(template.xclass || 'sprite.' + template.type, template);
- }
- surface = template.getSurface();
- if (surface) {
- surface.remove(template);
- }
- template.setParent(this);
- return template;
- },
- updateTemplate: function(template, oldTemplate) {
- if (oldTemplate) {
- delete oldTemplate.ownAttr;
- }
- template.setSurface(this.getSurface());
- // ownAttr is used to get a reference to the template's attributes
- // when one of the instances is rendering, as at that moment the template's
- // attributes (template.attr) are the instance's attributes.
- template.ownAttr = template.attr;
- this.clearAll();
- this.setDirty(true);
- },
- updateInstances: function(instances) {
- var i, ln;
- this.clearAll();
- if (Ext.isArray(instances)) {
- for (i = 0 , ln = instances.length; i < ln; i++) {
- this.add(instances[i]);
- }
- }
- },
- updateSurface: function(surface) {
- var template = this.getTemplate();
- if (template && !template.destroyed) {
- template.setSurface(surface);
- }
- },
- get: function(index) {
- return this.instances[index];
- },
- getCount: function() {
- return this.instances.length;
- },
- clearAll: function() {
- var template = this.getTemplate();
- template.attr.children = this.instances = [];
- this.position = 0;
- },
- /**
- * @deprecated 6.2.0
- * Deprecated, use the {@link #add} method instead.
- */
- createInstance: function(config, bypassNormalization, avoidCopy) {
- return this.add(config, bypassNormalization, avoidCopy);
- },
- /**
- * Creates a new sprite instance.
- *
- * @param {Object} config The configuration of the instance.
- * @param {Boolean} [bypassNormalization] 'true' to bypass attribute normalization.
- * @param {Boolean} [avoidCopy] 'true' to avoid copying the `config` object.
- * @return {Object} The attributes of the instance.
- */
- add: function(config, bypassNormalization, avoidCopy) {
- var me = this,
- template = me.getTemplate(),
- originalAttr = template.attr,
- attr = Ext.Object.chain(originalAttr);
- template.modifiers.target.prepareAttributes(attr);
- template.attr = attr;
- template.setAttributes(config, bypassNormalization, avoidCopy);
- attr.template = template;
- me.instances.push(attr);
- template.attr = originalAttr;
- me.position++;
- return attr;
- },
- /**
- * Not supported.
- *
- * @return {null}
- */
- getBBox: function() {
- return null;
- },
- /**
- * Returns the bounding box for the instance at the given index.
- *
- * @param {Number} index The index of the instance.
- * @param {Boolean} [isWithoutTransform] 'true' to not apply sprite transforms
- * to the bounding box.
- * @return {Object} The bounding box for the instance.
- */
- getBBoxFor: function(index, isWithoutTransform) {
- var template = this.getTemplate(),
- originalAttr = template.attr,
- bbox;
- template.attr = this.instances[index];
- bbox = template.getBBox(isWithoutTransform);
- template.attr = originalAttr;
- return bbox;
- },
- /**
- * @private
- * Checks if the instancing sprite can be seen.
- * @return {Boolean}
- */
- isVisible: function() {
- var attr = this.attr,
- parent = this.getParent(),
- result;
- result = parent && parent.isSurface && !attr.hidden && attr.globalAlpha;
- return !!result;
- },
- /**
- * @private
- * Checks if the instance of an instancing sprite can be seen.
- * @param {Number} index The index of the instance.
- */
- isInstanceVisible: function(index) {
- var me = this,
- template = me.getTemplate(),
- originalAttr = template.attr,
- instances = me.instances,
- result = false;
- if (!Ext.isNumber(index) || index < 0 || index >= instances.length || !me.isVisible()) {
- return result;
- }
- template.attr = instances[index];
- // TODO This is clearly a bug, fix it
- // eslint-disable-next-line no-undef
- result = template.isVisible(point, options);
- template.attr = originalAttr;
- return result;
- },
- render: function(surface, ctx, rect) {
- //<debug>
- if (!this.getTemplate()) {
- Ext.raise('An instancing sprite must have a template.');
- }
- //</debug>
- // eslint-disable-next-line vars-on-top
- var me = this,
- template = me.getTemplate(),
- surfaceRect = surface.getRect(),
- mat = me.attr.matrix,
- originalAttr = template.attr,
- instances = me.instances,
- ln = me.position,
- i;
- mat.toContext(ctx);
- template.preRender(surface, ctx, rect);
- template.useAttributes(ctx, surfaceRect);
- template.isSpriteInstance = true;
- for (i = 0; i < ln; i++) {
- if (instances[i].hidden) {
-
- continue;
- }
- ctx.save();
- template.attr = instances[i];
- template.useAttributes(ctx, surfaceRect);
- template.render(surface, ctx, rect);
- ctx.restore();
- }
- template.isSpriteInstance = false;
- template.attr = originalAttr;
- },
- /**
- * Sets the attributes for the instance at the given index.
- *
- * @param {Number} index the index of the instance
- * @param {Object} changes the attributes to change
- * @param {Boolean} [bypassNormalization] 'true' to avoid attribute normalization
- */
- setAttributesFor: function(index, changes, bypassNormalization) {
- var template = this.getTemplate(),
- originalAttr = template.attr,
- attr = this.instances[index];
- if (!attr) {
- return;
- }
- template.attr = attr;
- if (bypassNormalization) {
- changes = Ext.apply({}, changes);
- } else {
- changes = template.self.def.normalize(changes);
- }
- template.modifiers.target.pushDown(attr, changes);
- template.attr = originalAttr;
- },
- destroy: function() {
- var me = this,
- template = me.getTemplate();
- me.instances = null;
- if (template) {
- template.destroy();
- }
- me.callParent();
- }
- });
- /**
- * @private
- * Adds hit testing methods to the Ext.draw.sprite.Instancing.
- * Included by the Ext.draw.plugin.SpriteEvents.
- */
- Ext.define('Ext.draw.overrides.hittest.sprite.Instancing', {
- override: 'Ext.draw.sprite.Instancing',
- /**
- * Performs a hit test on the instances of an instancing sprite.
- * @param point A two-item array containing x and y coordinates of the point.
- * @param options Hit testing options.
- * @return {Object} A hit result object that contains more information about what
- * exactly was hit or null if nothing was hit.
- * @return {Boolean} return.isInstance `true` if an instance was hit.
- * @return {Ext.draw.sprite.Instancing} return.sprite The instancing sprite.
- * @return {Ext.draw.sprite.Sprite} return.template The template of the instancing sprite.
- * @return {Object} return.instance The attributes of the instance.
- * @return {Number} return.index The index of the instance.
- */
- hitTest: function(point, options) {
- var me = this,
- template = me.getTemplate(),
- originalAttr = template.attr,
- instances = me.instances,
- ln = instances.length,
- i = 0,
- result = null;
- if (!me.isVisible()) {
- return result;
- }
- for (; i < ln; i++) {
- template.attr = instances[i];
- result = template.hitTest(point, options);
- if (result) {
- result.isInstance = true;
- result.template = result.sprite;
- result.sprite = this;
- result.instance = instances[i];
- result.index = i;
- return result;
- }
- }
- template.attr = originalAttr;
- return result;
- }
- });
- /**
- * A sprite that represents a line.
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'line',
- * fromX: 20,
- * fromY: 20,
- * toX: 120,
- * toY: 120,
- * strokeStyle: '#1F6D91',
- * lineWidth: 3
- * }]
- * });
- */
- Ext.define('Ext.draw.sprite.Line', {
- extend: 'Ext.draw.sprite.Sprite',
- alias: 'sprite.line',
- type: 'line',
- inheritableStatics: {
- def: {
- processors: {
- fromX: 'number',
- fromY: 'number',
- toX: 'number',
- toY: 'number',
- crisp: 'bool'
- },
- defaults: {
- fromX: 0,
- fromY: 0,
- toX: 1,
- toY: 1,
- crisp: false,
- strokeStyle: 'black'
- },
- aliases: {
- x1: 'fromX',
- y1: 'fromY',
- x2: 'toX',
- y2: 'toY'
- },
- triggers: {
- crisp: 'bbox'
- }
- }
- },
- updateLineBBox: function(bbox, isTransform, x1, y1, x2, y2) {
- var attr = this.attr,
- matrix = attr.matrix,
- halfLineWidth = attr.lineWidth / 2,
- fromX, fromY, toX, toY, p, angle, sin, cos, dx, dy;
- if (attr.crisp) {
- x1 = this.align(x1);
- x2 = this.align(x2);
- y1 = this.align(y1);
- y2 = this.align(y2);
- }
- if (isTransform) {
- p = matrix.transformPoint([
- x1,
- y1
- ]);
- x1 = p[0];
- y1 = p[1];
- p = matrix.transformPoint([
- x2,
- y2
- ]);
- x2 = p[0];
- y2 = p[1];
- }
- fromX = Math.min(x1, x2);
- toX = Math.max(x1, x2);
- fromY = Math.min(y1, y2);
- toY = Math.max(y1, y2);
- angle = Math.atan2(toX - fromX, toY - fromY);
- sin = Math.sin(angle);
- cos = Math.cos(angle);
- dx = halfLineWidth * cos;
- dy = halfLineWidth * sin;
- // Offset start and end points of the line by half its thickness,
- // while accounting for line's angle.
- fromX -= dx;
- fromY -= dy;
- toX += dx;
- toY += dy;
- bbox.x = fromX;
- bbox.y = fromY;
- bbox.width = toX - fromX;
- bbox.height = toY - fromY;
- },
- updatePlainBBox: function(plain) {
- var attr = this.attr;
- this.updateLineBBox(plain, false, attr.fromX, attr.fromY, attr.toX, attr.toY);
- },
- updateTransformedBBox: function(transform, plain) {
- var attr = this.attr;
- this.updateLineBBox(transform, true, attr.fromX, attr.fromY, attr.toX, attr.toY);
- },
- align: function(x) {
- return Math.round(x) - 0.5;
- },
- render: function(surface, ctx) {
- var me = this,
- attr = me.attr,
- matrix = attr.matrix;
- matrix.toContext(ctx);
- ctx.beginPath();
- if (attr.crisp) {
- ctx.moveTo(me.align(attr.fromX), me.align(attr.fromY));
- ctx.lineTo(me.align(attr.toX), me.align(attr.toY));
- } else {
- ctx.moveTo(attr.fromX, attr.fromY);
- ctx.lineTo(attr.toX, attr.toY);
- }
- ctx.stroke();
- //<debug>
- // eslint-disable-next-line vars-on-top
- var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
- if (debug) {
- // This assumes no part of the sprite is rendered after this call.
- // If it is, we need to re-apply transformations.
- // But the bounding box should always be rendered as is, untransformed.
- this.attr.inverseMatrix.toContext(ctx);
- if (debug.bbox) {
- this.renderBBox(surface, ctx);
- }
- }
- }
- });
- //</debug>
- /**
- * A sprite that represents a plus.
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'plus',
- * translationX: 100,
- * translationY: 100,
- * size: 40,
- * fillStyle: '#1F6D91'
- * }]
- * });
- */
- Ext.define('Ext.draw.sprite.Plus', {
- extend: 'Ext.draw.sprite.Path',
- alias: 'sprite.plus',
- inheritableStatics: {
- def: {
- processors: {
- x: 'number',
- y: 'number',
- /**
- * @cfg {Number} [size=4] The size of the sprite.
- * Meant to be comparable to the size of a circle sprite with the same radius.
- */
- size: 'number'
- },
- defaults: {
- x: 0,
- y: 0,
- size: 4
- },
- triggers: {
- x: 'path',
- y: 'path',
- size: 'path'
- }
- }
- },
- updatePath: function(path, attr) {
- var s = attr.size / 1.3,
- x = attr.x - attr.lineWidth / 2,
- y = attr.y;
- path.fromSvgString('M'.concat(x - s / 2, ',', y - s / 2, 'l', [
- 0,
- -s,
- s,
- 0,
- 0,
- s,
- s,
- 0,
- 0,
- s,
- -s,
- 0,
- 0,
- s,
- -s,
- 0,
- 0,
- -s,
- -s,
- 0,
- 0,
- -s,
- 'z'
- ]));
- }
- });
- /**
- * @class Ext.draw.sprite.Sector
- * @extends Ext.draw.sprite.Path
- *
- * A sprite representing a pie slice.
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'sector',
- * centerX: 100,
- * centerY: 100,
- * startAngle: -2.355,
- * endAngle: -.785,
- * endRho: 50,
- * fillStyle: '#1F6D91'
- * }]
- * });
- */
- Ext.define('Ext.draw.sprite.Sector', {
- extend: 'Ext.draw.sprite.Path',
- alias: 'sprite.sector',
- type: 'sector',
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Number} [centerX=0] The center coordinate of the sprite on the x-axis.
- */
- centerX: 'number',
- /**
- * @cfg {Number} [centerY=0] The center coordinate of the sprite on the y-axis.
- */
- centerY: 'number',
- /**
- * @cfg {Number} [startAngle=0] The starting angle of the sprite.
- */
- startAngle: 'number',
- /**
- * @cfg {Number} [endAngle=0] The ending angle of the sprite.
- */
- endAngle: 'number',
- /**
- * @cfg {Number} [startRho=0] The starting point of the radius of the sprite.
- */
- startRho: 'number',
- /**
- * @cfg {Number} [endRho=150] The ending point of the radius of the sprite.
- */
- endRho: 'number',
- /**
- * @cfg {Number} [margin=0] The margin of the sprite from the center of pie.
- */
- margin: 'number'
- },
- aliases: {
- rho: 'endRho'
- },
- triggers: {
- centerX: 'path,bbox',
- centerY: 'path,bbox',
- startAngle: 'path,bbox',
- endAngle: 'path,bbox',
- startRho: 'path,bbox',
- endRho: 'path,bbox',
- margin: 'path,bbox'
- },
- defaults: {
- centerX: 0,
- centerY: 0,
- startAngle: 0,
- endAngle: 0,
- startRho: 0,
- endRho: 150,
- margin: 0,
- path: 'M 0,0'
- }
- }
- },
- getMidAngle: function() {
- return this.midAngle || 0;
- },
- updatePath: function(path, attr) {
- var startAngle = Math.min(attr.startAngle, attr.endAngle),
- endAngle = Math.max(attr.startAngle, attr.endAngle),
- midAngle = this.midAngle = (startAngle + endAngle) * 0.5,
- fullPie = Ext.Number.isEqual(Math.abs(endAngle - startAngle), Ext.draw.Draw.pi2, 1.0E-10),
- margin = attr.margin,
- centerX = attr.centerX,
- centerY = attr.centerY,
- startRho = Math.min(attr.startRho, attr.endRho),
- endRho = Math.max(attr.startRho, attr.endRho);
- if (margin) {
- centerX += margin * Math.cos(midAngle);
- centerY += margin * Math.sin(midAngle);
- }
- if (!fullPie) {
- path.moveTo(centerX + startRho * Math.cos(startAngle), centerY + startRho * Math.sin(startAngle));
- path.lineTo(centerX + endRho * Math.cos(startAngle), centerY + endRho * Math.sin(startAngle));
- }
- path.arc(centerX, centerY, endRho, startAngle, endAngle, false);
- path[fullPie ? 'moveTo' : 'lineTo'](centerX + startRho * Math.cos(endAngle), centerY + startRho * Math.sin(endAngle));
- path.arc(centerX, centerY, startRho, endAngle, startAngle, true);
- }
- });
- /**
- * A sprite that represents a square.
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'square',
- * x: 100,
- * y: 100,
- * size: 50,
- * fillStyle: '#1F6D91'
- * }]
- * });
- */
- Ext.define('Ext.draw.sprite.Square', {
- extend: 'Ext.draw.sprite.Path',
- alias: 'sprite.square',
- inheritableStatics: {
- def: {
- processors: {
- x: 'number',
- y: 'number',
- /**
- * @cfg {Number} [size=4] The size of the sprite.
- * Meant to be comparable to the size of a circle sprite with the same radius.
- */
- size: 'number'
- },
- defaults: {
- x: 0,
- y: 0,
- size: 4
- },
- triggers: {
- x: 'path',
- y: 'path',
- size: 'size'
- }
- }
- },
- updatePath: function(path, attr) {
- var size = attr.size * 1.2,
- s = size * 2,
- x = attr.x - attr.lineWidth / 2,
- y = attr.y;
- path.fromSvgString('M'.concat(x - size, ',', y - size, 'l', [
- s,
- 0,
- 0,
- s,
- -s,
- 0,
- 0,
- -s,
- 'z'
- ]));
- }
- });
- /**
- * Utility class to provide a way to *approximately* measure the dimension of text
- * without a drawing context.
- */
- Ext.define('Ext.draw.TextMeasurer', {
- singleton: true,
- requires: [
- 'Ext.util.TextMetrics'
- ],
- measureDiv: null,
- measureCache: {},
- /**
- * @cfg {Boolean} [precise=false]
- * This singleton tries not to make use of the Ext.util.TextMetrics because it is
- * several times slower than TextMeasurer's own solution. TextMetrics is more precise
- * though, so if you have a case where the error is too big, you may want to set
- * this config to `true` to get perfect results at the expense of performance.
- * Note: defaults to `true` in IE8.
- */
- precise: Ext.isIE8,
- measureDivTpl: {
- id: 'ext-draw-text-measurer',
- tag: 'div',
- style: {
- overflow: 'hidden',
- position: 'relative',
- // 'float' is a reserved word. Don't unquote, or it will break the CMD build.
- 'float': 'left',
- width: 0,
- height: 0
- },
- //<debug>
- // Tell the spec runner to ignore this element when checking if the dom is clean.
- 'data-sticky': true,
- //</debug>
- children: {
- tag: 'div',
- style: {
- display: 'block',
- position: 'absolute',
- x: -100000,
- y: -100000,
- padding: 0,
- margin: 0,
- 'z-index': -100000,
- 'white-space': 'nowrap'
- }
- }
- },
- /**
- * @private
- * Measure the size of a text with specific font by using DOM to measure it.
- * Could be very expensive therefore should be used lazily.
- * @param {String} text
- * @param {String} font
- * @return {Object} An object with `width` and `height` properties.
- * @return {Number} return.width
- * @return {Number} return.height
- */
- actualMeasureText: function(text, font) {
- var me = Ext.draw.TextMeasurer,
- measureDiv = me.measureDiv,
- FARAWAY = 100000,
- size, parent;
- if (!measureDiv) {
- parent = Ext.Element.create({
- //<debug>
- // Tell the spec runner to ignore this element when checking if the dom is clean.
- 'data-sticky': true,
- //</debug>
- style: {
- "overflow": "hidden",
- "position": "relative",
- "float": "left",
- // DO NOT REMOVE THE QUOTE OR IT WILL BREAK COMPRESSOR
- "width": 0,
- "height": 0
- }
- });
- me.measureDiv = measureDiv = Ext.Element.create({
- style: {
- "position": 'absolute',
- "x": FARAWAY,
- "y": FARAWAY,
- "z-index": -FARAWAY,
- "white-space": "nowrap",
- "display": 'block',
- "padding": 0,
- "margin": 0
- }
- });
- Ext.getBody().appendChild(parent);
- parent.appendChild(measureDiv);
- }
- if (font) {
- measureDiv.setStyle({
- font: font,
- lineHeight: 'normal'
- });
- }
- measureDiv.setText('(' + text + ')');
- size = measureDiv.getSize();
- measureDiv.setText('()');
- size.width -= measureDiv.getSize().width;
- return size;
- },
- /**
- * Measure a single-line text with specific font.
- * This will split the text into characters and add up their size.
- * That may *not* be the exact size of the text as it is displayed.
- * @param {String} text
- * @param {String} font
- * @return {Object} An object with `width` and `height` properties.
- * @return {Number} return.width
- * @return {Number} return.height
- */
- measureTextSingleLine: function(text, font) {
- var width = 0,
- height = 0,
- cache, cachedItem, chars, charactor, i, ln, size;
- if (this.precise) {
- return this.preciseMeasureTextSingleLine(text, font);
- }
- text = text.toString();
- cache = this.measureCache;
- chars = text.split('');
- if (!cache[font]) {
- cache[font] = {};
- }
- cache = cache[font];
- if (cache[text]) {
- return cache[text];
- }
- for (i = 0 , ln = chars.length; i < ln; i++) {
- charactor = chars[i];
- if (!(cachedItem = cache[charactor])) {
- size = this.actualMeasureText(charactor, font);
- cachedItem = cache[charactor] = size;
- }
- width += cachedItem.width;
- height = Math.max(height, cachedItem.height);
- }
- return cache[text] = {
- width: width,
- height: height
- };
- },
- // A more precise but slower version of the measureTextSingleLine method.
- preciseMeasureTextSingleLine: function(text, font) {
- var measureDiv;
- text = text.toString();
- measureDiv = this.measureDiv || (this.measureDiv = Ext.getBody().createChild(this.measureDivTpl).down('div'));
- measureDiv.setStyle({
- font: font || ''
- });
- return Ext.util.TextMetrics.measure(measureDiv, text);
- },
- /**
- * Measure a text with specific font.
- * This will split the text to lines and add up their size.
- * That may *not* be the exact size of the text as it is displayed.
- * @param {String} text
- * @param {String} font
- * @return {Object} An object with `width`, `height` and `sizes` properties.
- * @return {Number} return.width
- * @return {Number} return.height
- * @return {Object} return.sizes Results of individual line measurements, in case of multiline
- * text.
- */
- measureText: function(text, font) {
- var lines = text.split('\n'),
- ln = lines.length,
- height = 0,
- width = 0,
- line, i, sizes;
- if (ln === 1) {
- return this.measureTextSingleLine(text, font);
- }
- sizes = [];
- for (i = 0; i < ln; i++) {
- line = this.measureTextSingleLine(lines[i], font);
- sizes.push(line);
- height += line.height;
- width = Math.max(width, line.width);
- }
- return {
- width: width,
- height: height,
- sizes: sizes
- };
- }
- });
- /**
- * @class Ext.draw.sprite.Text
- * @extends Ext.draw.sprite.Sprite
- *
- * A sprite that represents text.
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'text',
- * x: 50,
- * y: 50,
- * text: 'Sencha',
- * fontSize: 30,
- * fillStyle: '#1F6D91'
- * }]
- * });
- */
- /* eslint-disable indent */
- Ext.define('Ext.draw.sprite.Text', function() {
- // Absolute font sizes.
- var fontSizes = {
- 'xx-small': true,
- 'x-small': true,
- 'small': true,
- 'medium': true,
- 'large': true,
- 'x-large': true,
- 'xx-large': true
- },
- fontWeights = {
- normal: true,
- bold: true,
- bolder: true,
- lighter: true,
- 100: true,
- 200: true,
- 300: true,
- 400: true,
- 500: true,
- 600: true,
- 700: true,
- 800: true,
- 900: true
- },
- textAlignments = {
- start: 'start',
- left: 'start',
- center: 'center',
- middle: 'center',
- end: 'end',
- right: 'end'
- },
- textBaselines = {
- top: 'top',
- hanging: 'hanging',
- middle: 'middle',
- center: 'middle',
- alphabetic: 'alphabetic',
- ideographic: 'ideographic',
- bottom: 'bottom'
- };
- return {
- extend: 'Ext.draw.sprite.Sprite',
- requires: [
- 'Ext.draw.TextMeasurer',
- 'Ext.draw.Color'
- ],
- alias: 'sprite.text',
- type: 'text',
- lineBreakRe: /\r?\n/g,
- //<debug>
- statics: {
- /**
- * Debug rendering options:
- *
- * debug: {
- * bbox: true // renders the bounding box of the text sprite
- * }
- *
- */
- debug: false,
- fontSizes: fontSizes,
- fontWeights: fontWeights,
- textAlignments: textAlignments,
- textBaselines: textBaselines
- },
- //</debug>
- inheritableStatics: {
- def: {
- animationProcessors: {
- text: 'text'
- },
- processors: {
- /**
- * @cfg {Number} [x=0]
- * The position of the sprite on the x-axis.
- */
- x: 'number',
- /**
- * @cfg {Number} [y=0]
- * The position of the sprite on the y-axis.
- */
- y: 'number',
- /**
- * @cfg {String} [text='']
- * The text represented in the sprite.
- */
- text: 'string',
- /**
- * @cfg {String/Number} [fontSize='10px']
- * The size of the font displayed.
- */
- fontSize: function(n) {
- // Numbers as strings will be converted to numbers,
- // null will be converted to 0.
- if (Ext.isNumber(+n)) {
- return n + 'px';
- } else if (n.match(Ext.dom.Element.unitRe)) {
- return n;
- } else if (n in fontSizes) {
- return n;
- }
- },
- /**
- * @cfg {String} [fontStyle='']
- * The style of the font displayed. {normal, italic, oblique}
- */
- fontStyle: 'enums(,italic,oblique)',
- /**
- * @cfg {String} [fontVariant='']
- * The variant of the font displayed. {normal, small-caps}
- */
- fontVariant: 'enums(,small-caps)',
- /**
- * @cfg {String} [fontWeight='']
- * The weight of the font displayed. {normal, bold, bolder, lighter}
- */
- fontWeight: function(n) {
- if (n in fontWeights) {
- return String(n);
- } else {
- return '';
- }
- },
- /**
- * @cfg {String} [fontFamily='sans-serif']
- * The family of the font displayed.
- */
- fontFamily: 'string',
- /**
- * @cfg {"left"/"right"/"center"/"start"/"end"} [textAlign='start']
- * The alignment of the text displayed.
- */
- textAlign: function(n) {
- return textAlignments[n] || 'center';
- },
- /**
- * @cfg {String} [textBaseline="alphabetic"]
- * The baseline of the text displayed.
- * {top, hanging, middle, alphabetic, ideographic, bottom}
- */
- textBaseline: function(n) {
- return textBaselines[n] || 'alphabetic';
- },
- //<debug>
- debug: 'default',
- //</debug>
- /**
- * @cfg {String} [font='10px sans-serif']
- * The font displayed.
- */
- font: 'string'
- },
- aliases: {
- 'font-size': 'fontSize',
- 'font-family': 'fontFamily',
- 'font-weight': 'fontWeight',
- 'font-variant': 'fontVariant',
- 'text-anchor': 'textAlign',
- 'dominant-baseline': 'textBaseline'
- },
- defaults: {
- fontStyle: '',
- fontVariant: '',
- fontWeight: '',
- fontSize: '10px',
- fontFamily: 'sans-serif',
- font: '10px sans-serif',
- textBaseline: 'alphabetic',
- textAlign: 'start',
- strokeStyle: 'rgba(0, 0, 0, 0)',
- fillStyle: '#000',
- x: 0,
- y: 0,
- text: ''
- },
- triggers: {
- fontStyle: 'fontX,bbox',
- fontVariant: 'fontX,bbox',
- fontWeight: 'fontX,bbox',
- fontSize: 'fontX,bbox',
- fontFamily: 'fontX,bbox',
- font: 'font,bbox,canvas',
- textBaseline: 'bbox',
- textAlign: 'bbox',
- x: 'bbox',
- y: 'bbox',
- text: 'bbox'
- },
- updaters: {
- fontX: 'makeFontShorthand',
- font: 'parseFontShorthand'
- }
- }
- },
- config: {
- /**
- * @private
- * If the value is boolean, it overrides the TextMeasurer's 'precise' config
- * (for the given sprite only).
- */
- preciseMeasurement: undefined
- },
- constructor: function(config) {
- var key;
- if (config && config.font) {
- config = Ext.clone(config);
- for (key in config) {
- if (key !== 'font' && key.indexOf('font') === 0) {
- delete config[key];
- }
- }
- }
- Ext.draw.sprite.Sprite.prototype.constructor.call(this, config);
- },
- // Maps values to font properties they belong to.
- fontValuesMap: {
- // Skip 'normal' and 'inherit' values, as the first one
- // is the default and the second one has no meaning in Canvas.
- 'italic': 'fontStyle',
- 'oblique': 'fontStyle',
- 'small-caps': 'fontVariant',
- 'bold': 'fontWeight',
- 'bolder': 'fontWeight',
- 'lighter': 'fontWeight',
- '100': 'fontWeight',
- '200': 'fontWeight',
- '300': 'fontWeight',
- '400': 'fontWeight',
- '500': 'fontWeight',
- '600': 'fontWeight',
- '700': 'fontWeight',
- '800': 'fontWeight',
- '900': 'fontWeight',
- // Absolute font sizes.
- 'xx-small': 'fontSize',
- 'x-small': 'fontSize',
- 'small': 'fontSize',
- 'medium': 'fontSize',
- 'large': 'fontSize',
- 'x-large': 'fontSize',
- 'xx-large': 'fontSize'
- },
- // Relative font sizes like 'smaller' and 'larger'
- // have no meaning, and are not included.
- makeFontShorthand: function(attr) {
- var parts = [];
- if (attr.fontStyle) {
- parts.push(attr.fontStyle);
- }
- if (attr.fontVariant) {
- parts.push(attr.fontVariant);
- }
- if (attr.fontWeight) {
- parts.push(attr.fontWeight);
- }
- if (attr.fontSize) {
- parts.push(attr.fontSize);
- }
- if (attr.fontFamily) {
- parts.push(attr.fontFamily);
- }
- this.setAttributes({
- font: parts.join(' ')
- }, true);
- },
- // For more info see:
- // http://www.w3.org/TR/CSS21/fonts.html#font-shorthand
- parseFontShorthand: function(attr) {
- var value = attr.font,
- ln = value.length,
- changes = {},
- dispatcher = this.fontValuesMap,
- start = 0,
- end, slashIndex, part, fontProperty;
- while (start < ln && end !== -1) {
- end = value.indexOf(' ', start);
- if (end < 0) {
- part = value.substr(start);
- } else if (end > start) {
- part = value.substr(start, end - start);
- } else {
-
- continue;
- }
- // Since Canvas fillText doesn't support multi-line text,
- // it is assumed that line height is never specified, i.e.
- // in entries like these the part after slash is omitted:
- // 12px/14px sans-serif
- // x-large/110% "New Century Schoolbook", serif
- slashIndex = part.indexOf('/');
- if (slashIndex > 0) {
- part = part.substr(0, slashIndex);
- } else if (slashIndex === 0) {
-
- continue;
- }
- // All optional font properties (fontStyle, fontVariant or fontWeight) can be 'normal'.
- // They can go in any order. Which ones are 'normal' is determined by elimination.
- // E.g. if only fontVariant is specified, then 'normal' applies to fontStyle
- // and fontWeight.
- // If none are explicitly mentioned, then all are 'normal'.
- if (part !== 'normal' && part !== 'inherit') {
- fontProperty = dispatcher[part];
- if (fontProperty) {
- changes[fontProperty] = part;
- } else if (part.match(Ext.dom.Element.unitRe)) {
- changes.fontSize = part;
- } else {
- // Assuming that font family always goes last in the font shorthand.
- changes.fontFamily = value.substr(start);
- break;
- }
- }
- start = end + 1;
- }
- if (!changes.fontStyle) {
- changes.fontStyle = '';
- }
- // same as 'normal'
- if (!changes.fontVariant) {
- changes.fontVariant = '';
- }
- // same as 'normal'
- if (!changes.fontWeight) {
- changes.fontWeight = '';
- }
- // same as 'normal'
- this.setAttributes(changes, true);
- },
- fontProperties: {
- fontStyle: true,
- fontVariant: true,
- fontWeight: true,
- fontSize: true,
- fontFamily: true
- },
- setAttributes: function(changes, bypassNormalization, avoidCopy) {
- var key, obj;
- // Discard individual font properties if 'font' shorthand was also provided.
- // Example: a user provides a config for chart series labels, using the font
- // shorthand, which is parsed into individual font properties and corresponding
- // sprite attributes are set. Then a theme is applied to the chart, and
- // individual font properties from the theme make up the new font shorthand
- // that overrides the previous one. In other words, no matter what font
- // the user has specified, theme font will be used.
- // This workaround relies on the fact that the theme merges its own config with
- // the user config (where user config values take over the same theme config
- // values). So both user font shorthand and individual font properties from
- // the theme are present in the resulting config (since there are no collisions),
- // which ends up here as the 'changes' parameter.
- // If the user wants their font config to merged with the the theme's font config,
- // instead of taking over it, individual font properties should be used
- // by the user as well.
- if (changes && changes.font) {
- obj = {};
- for (key in changes) {
- if (!(key in this.fontProperties)) {
- obj[key] = changes[key];
- }
- }
- changes = obj;
- }
- this.callParent([
- changes,
- bypassNormalization,
- avoidCopy
- ]);
- },
- // Overriding the getBBox method of the abstract sprite here to always
- // recalculate the bounding box of the text in flipped RTL mode
- // because in that case the position of the sprite depends not just on
- // the value of its 'x' attribute, but also on the width of the surface
- // the sprite belongs to.
- getBBox: function(isWithoutTransform) {
- var me = this,
- plain = me.attr.bbox.plain,
- surface = me.getSurface();
- //<debug>
- // The sprite's bounding box won't account for RTL if it doesn't
- // belong to a surface.
- // if (!surface) {
- // Ext.raise("The sprite does not belong to a surface.");
- // }
- //</debug>
- if (plain.dirty) {
- me.updatePlainBBox(plain);
- plain.dirty = false;
- }
- if (surface && surface.getInherited().rtl && surface.getFlipRtlText()) {
- // Since sprite's attributes haven't actually changed at this point,
- // and we just want to update the position of its bbox
- // based on surface's width, there's no reason to perform
- // expensive text measurement operation here,
- // so we can use the result of the last measurement instead.
- me.updatePlainBBox(plain, true);
- }
- return me.callParent([
- isWithoutTransform
- ]);
- },
- rtlAlignments: {
- start: 'end',
- center: 'center',
- end: 'start'
- },
- updatePlainBBox: function(plain, useOldSize) {
- var me = this,
- attr = me.attr,
- x = attr.x,
- y = attr.y,
- dx = [],
- font = attr.font,
- text = attr.text,
- baseline = attr.textBaseline,
- alignment = attr.textAlign,
- precise = me.getPreciseMeasurement(),
- size, textMeasurerPrecision;
- if (useOldSize && me.oldSize) {
- size = me.oldSize;
- } else {
- textMeasurerPrecision = Ext.draw.TextMeasurer.precise;
- if (Ext.isBoolean(precise)) {
- Ext.draw.TextMeasurer.precise = precise;
- }
- size = me.oldSize = Ext.draw.TextMeasurer.measureText(text, font);
- Ext.draw.TextMeasurer.precise = textMeasurerPrecision;
- }
- // eslint-disable-next-line vars-on-top, one-var
- var surface = me.getSurface(),
- isRtl = (surface && surface.getInherited().rtl) || false,
- flipRtlText = isRtl && surface.getFlipRtlText(),
- sizes = size.sizes,
- blockHeight = size.height,
- blockWidth = size.width,
- ln = sizes ? sizes.length : 0,
- lineWidth, rect,
- i = 0;
- // To get consistent results in all browsers we don't apply textAlign
- // and textBaseline attributes of the sprite to context, so text is always
- // left aligned and has an alphabetic baseline.
- //
- // Instead we have to calculate the horizontal offset of each line
- // based on sprite's textAlign, and the vertical offset of the bounding box
- // based on sprite's textBaseline.
- //
- // These offsets are then used by the sprite's 'render' method
- // to position text properly.
- switch (baseline) {
- case 'hanging':
- case 'top':
- break;
- case 'ideographic':
- case 'bottom':
- y -= blockHeight;
- break;
- case 'alphabetic':
- y -= blockHeight * 0.8;
- break;
- case 'middle':
- y -= blockHeight * 0.5;
- break;
- }
- if (flipRtlText) {
- rect = surface.getRect();
- x = rect[2] - rect[0] - x;
- alignment = me.rtlAlignments[alignment];
- }
- switch (alignment) {
- case 'start':
- if (isRtl) {
- for (; i < ln; i++) {
- lineWidth = sizes[i].width;
- dx.push(-(blockWidth - lineWidth));
- }
- };
- break;
- case 'end':
- x -= blockWidth;
- if (isRtl) {
- break;
- };
- for (; i < ln; i++) {
- lineWidth = sizes[i].width;
- dx.push(blockWidth - lineWidth);
- };
- break;
- case 'center':
- x -= blockWidth * 0.5;
- for (; i < ln; i++) {
- lineWidth = sizes[i].width;
- dx.push((isRtl ? -1 : 1) * (blockWidth - lineWidth) * 0.5);
- };
- break;
- }
- attr.textAlignOffsets = dx;
- plain.x = x;
- plain.y = y;
- plain.width = blockWidth;
- plain.height = blockHeight;
- },
- setText: function(text) {
- this.setAttributes({
- text: text
- }, true);
- },
- render: function(surface, ctx, rect) {
- var me = this,
- attr = me.attr,
- mat = Ext.draw.Matrix.fly(attr.matrix.elements.slice(0)),
- bbox = me.getBBox(true),
- dx = attr.textAlignOffsets,
- none = Ext.util.Color.RGBA_NONE,
- x, y, i, lines, lineHeight;
- if (attr.text.length === 0) {
- return;
- }
- lines = attr.text.split(me.lineBreakRe);
- lineHeight = bbox.height / lines.length;
- // Simulate textBaseline and textAlign.
- x = attr.bbox.plain.x;
- // lineHeight * 0.78 is the approximate distance between the top
- // and the alphabetic baselines
- y = attr.bbox.plain.y + lineHeight * 0.78;
- mat.toContext(ctx);
- if (surface.getInherited().rtl) {
- // Canvas element in RTL mode automatically flips text alignment.
- // Here we compensate for that change.
- // So text is still positioned and aligned as in the LTR mode,
- // but the direction of the text is RTL.
- x += attr.bbox.plain.width;
- }
- for (i = 0; i < lines.length; i++) {
- if (ctx.fillStyle !== none) {
- ctx.fillText(lines[i], x + (dx[i] || 0), y + lineHeight * i);
- }
- if (ctx.strokeStyle !== none) {
- ctx.strokeText(lines[i], x + (dx[i] || 0), y + lineHeight * i);
- }
- }
- //<debug>
- // eslint-disable-next-line vars-on-top
- var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
- if (debug) {
- // This assumes no part of the sprite is rendered after this call.
- // If it is, we need to re-apply transformations.
- // But the bounding box is already transformed, so we remove the transformation.
- this.attr.inverseMatrix.toContext(ctx);
- if (debug.bbox) {
- me.renderBBox(surface, ctx);
- }
- }
- }
- };
- });
- //</debug>
- /**
- * A veritical line sprite. The x and y configs set the center of the line with the size
- * value determining the height of the line (the line will be twice the height of 'size'
- * since 'size' is added to above and below 'y' to set the line endpoints).
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'tick',
- * x: 20,
- * y: 40,
- * size: 10,
- * strokeStyle: '#388FAD',
- * lineWidth: 2
- * }]
- * });
- */
- Ext.define('Ext.draw.sprite.Tick', {
- extend: 'Ext.draw.sprite.Line',
- alias: 'sprite.tick',
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Object} x The position of the center of the sprite on the x-axis.
- */
- x: 'number',
- /**
- * @cfg {Object} y The position of the center of the sprite on the y-axis.
- */
- y: 'number',
- /**
- * @cfg {Number} [size=4] The size of the sprite.
- * Meant to be comparable to the size of a circle sprite with the same radius.
- */
- size: 'number'
- },
- defaults: {
- x: 0,
- y: 0,
- size: 4
- },
- triggers: {
- x: 'tick',
- y: 'tick',
- size: 'tick'
- },
- updaters: {
- tick: function(attr) {
- var size = attr.size * 1.5,
- halfLineWidth = attr.lineWidth / 2,
- x = attr.x,
- y = attr.y;
- this.setAttributes({
- fromX: x - halfLineWidth,
- fromY: y - size,
- toX: x - halfLineWidth,
- toY: y + size
- });
- }
- }
- }
- }
- });
- /**
- * A sprite that represents a triangle.
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'triangle',
- * size: 50,
- * translationX: 100,
- * translationY: 100,
- * fillStyle: '#1F6D91'
- * }]
- * });
- *
- */
- Ext.define('Ext.draw.sprite.Triangle', {
- extend: 'Ext.draw.sprite.Path',
- alias: 'sprite.triangle',
- inheritableStatics: {
- def: {
- processors: {
- x: 'number',
- y: 'number',
- /**
- * @cfg {Number} [size=4] The size of the sprite.
- * Meant to be comparable to the size of a circle sprite with the same radius.
- */
- size: 'number'
- },
- defaults: {
- x: 0,
- y: 0,
- size: 4
- },
- triggers: {
- x: 'path',
- y: 'path',
- size: 'path'
- }
- }
- },
- updatePath: function(path, attr) {
- var s = attr.size * 2.2,
- x = attr.x,
- y = attr.y;
- path.fromSvgString('M'.concat(x, ',', y, 'm0-', s * 0.48, 'l', s * 0.5, ',', s * 0.87, '-', s, ',0z'));
- }
- });
- /**
- * Linear gradient.
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'circle',
- * cx: 100,
- * cy: 100,
- * r: 100,
- * fillStyle: {
- * type: 'linear',
- * degrees: 180,
- * stops: [{
- * offset: 0,
- * color: '#1F6D91'
- * }, {
- * offset: 1,
- * color: '#90BCC9'
- * }]
- * }
- * }]
- * });
- */
- Ext.define('Ext.draw.gradient.Linear', {
- extend: 'Ext.draw.gradient.Gradient',
- requires: [
- 'Ext.draw.Color'
- ],
- type: 'linear',
- config: {
- /**
- * @cfg {Number} degrees
- * The angle of rotation of the gradient in degrees.
- */
- degrees: 0,
- /**
- * @cfg {Number} radians
- * The angle of rotation of the gradient in radians.
- */
- radians: 0
- },
- applyRadians: function(radians, oldRadians) {
- if (Ext.isNumber(radians)) {
- return radians;
- }
- return oldRadians;
- },
- applyDegrees: function(degrees, oldDegrees) {
- if (Ext.isNumber(degrees)) {
- return degrees;
- }
- return oldDegrees;
- },
- updateRadians: function(radians) {
- this.setDegrees(Ext.draw.Draw.degrees(radians));
- },
- updateDegrees: function(degrees) {
- this.setRadians(Ext.draw.Draw.rad(degrees));
- },
- /**
- * @method generateGradient
- * @inheritdoc
- */
- generateGradient: function(ctx, bbox) {
- var angle = this.getRadians(),
- cos = Math.cos(angle),
- sin = Math.sin(angle),
- w = bbox.width,
- h = bbox.height,
- cx = bbox.x + w * 0.5,
- cy = bbox.y + h * 0.5,
- stops = this.getStops(),
- ln = stops.length,
- gradient, l, i;
- if (Ext.isNumber(cx) && Ext.isNumber(cy) && h > 0 && w > 0) {
- l = (Math.sqrt(h * h + w * w) * Math.abs(Math.cos(angle - Math.atan(h / w)))) / 2;
- gradient = ctx.createLinearGradient(cx + cos * l, cy + sin * l, cx - cos * l, cy - sin * l);
- for (i = 0; i < ln; i++) {
- gradient.addColorStop(stops[i].offset, stops[i].color);
- }
- return gradient;
- }
- return Ext.util.Color.NONE;
- }
- });
- /**
- * Radial gradient.
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * sprites: [{
- * type: 'circle',
- * cx: 100,
- * cy: 100,
- * r: 100,
- * fillStyle: {
- * type: 'radial',
- * start: {
- * x: 0,
- * y: 0,
- * r: 0
- * },
- * end: {
- * x: 0,
- * y: 0,
- * r: 1
- * },
- * stops: [{
- * offset: 0,
- * color: '#90BCC9'
- * }, {
- * offset: 1,
- * color: '#1F6D91'
- * }]
- * }
- * }]
- * });
- */
- Ext.define('Ext.draw.gradient.Radial', {
- extend: 'Ext.draw.gradient.Gradient',
- type: 'radial',
- config: {
- /**
- * @cfg {Object} start
- * The starting circle of the gradient.
- */
- start: {
- x: 0,
- y: 0,
- r: 0
- },
- /**
- * @cfg {Object} end
- * The ending circle of the gradient.
- */
- end: {
- x: 0,
- y: 0,
- r: 1
- }
- },
- applyStart: function(newStart, oldStart) {
- var circle;
- if (!oldStart) {
- return newStart;
- }
- circle = {
- x: oldStart.x,
- y: oldStart.y,
- r: oldStart.r
- };
- if ('x' in newStart) {
- circle.x = newStart.x;
- } else if ('centerX' in newStart) {
- circle.x = newStart.centerX;
- }
- if ('y' in newStart) {
- circle.y = newStart.y;
- } else if ('centerY' in newStart) {
- circle.y = newStart.centerY;
- }
- if ('r' in newStart) {
- circle.r = newStart.r;
- } else if ('radius' in newStart) {
- circle.r = newStart.radius;
- }
- return circle;
- },
- applyEnd: function(newEnd, oldEnd) {
- var circle;
- if (!oldEnd) {
- return newEnd;
- }
- circle = {
- x: oldEnd.x,
- y: oldEnd.y,
- r: oldEnd.r
- };
- if ('x' in newEnd) {
- circle.x = newEnd.x;
- } else if ('centerX' in newEnd) {
- circle.x = newEnd.centerX;
- }
- if ('y' in newEnd) {
- circle.y = newEnd.y;
- } else if ('centerY' in newEnd) {
- circle.y = newEnd.centerY;
- }
- if ('r' in newEnd) {
- circle.r = newEnd.r;
- } else if ('radius' in newEnd) {
- circle.r = newEnd.radius;
- }
- return circle;
- },
- /**
- * @method generateGradient
- * @inheritdoc
- */
- generateGradient: function(ctx, bbox) {
- var start = this.getStart(),
- end = this.getEnd(),
- w = bbox.width * 0.5,
- h = bbox.height * 0.5,
- x = bbox.x + w,
- y = bbox.y + h,
- 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)),
- stops = this.getStops(),
- ln = stops.length,
- i;
- for (i = 0; i < ln; i++) {
- gradient.addColorStop(stops[i].offset, stops[i].color);
- }
- return gradient;
- }
- });
- /**
- * A surface is an interface to render {@link Ext.draw.sprite.Sprite sprites} inside a
- * {@link Ext.draw.Container draw container}. The surface API has methods to render
- * sprites, get sprite bounding boxes (dimensions), add sprites to the underlying DOM,
- * and more.
- *
- * A surface is automatically created when a draw container is created. By default,
- * this will be a surface with an `id` of "main" and will manage all sprites in the draw
- * container (unless the sprite configs specify a unique surface "id").
- *
- * @example
- * Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 400,
- * height: 400,
- * sprites: [{
- * type: 'rect',
- * surface: 'anim', // a surface with id "anim" will be created automatically
- * x: 50,
- * y: 50,
- * width: 100,
- * height: 100,
- * fillStyle: '#1F6D91'
- * }]
- * });
- *
- * The ability to have multiple surfaces is useful for performance (and battery life)
- * reasons. Because changes to sprite attributes cause the whole surface (and all
- * sprites in it) to re-render, it makes sense to group sprites by surface, so changes
- * to one group of sprites will only trigger the surface they are in to re-render.
- *
- * One of the more useful methods is the {@link #add} method used to add sprites to the
- * surface:
- *
- * @example
- * var drawCt = Ext.create({
- * xtype: 'draw',
- * renderTo: document.body,
- * width: 400,
- * height: 400
- * });
- *
- * // If the surface name is not specified then 'main' will be used
- * var surface = drawCt.getSurface();
- *
- * surface.add({
- * type: 'rect',
- * x: 50,
- * y: 50,
- * width: 100,
- * height: 100,
- * fillStyle: '#1F6D91'
- * });
- *
- * surface.renderFrame();
- *
- * **Note:** Changes to the sprites on a surface will be not be reflected in the DOM
- * until you call the surface's {@link Ext.draw.Surface#method-renderFrame renderFrame}
- * method. This must be done after adding, removing, or modifying sprites in order to
- * see the changes on-screen.
- */
- Ext.define('Ext.draw.Surface', {
- extend: 'Ext.draw.SurfaceBase',
- xtype: 'surface',
- requires: [
- 'Ext.draw.sprite.*',
- 'Ext.draw.gradient.*',
- 'Ext.draw.sprite.AttributeDefinition',
- 'Ext.draw.Matrix',
- 'Ext.draw.Draw'
- ],
- uses: [
- 'Ext.draw.engine.Canvas'
- ],
- /**
- * The reported device pixel density.
- * devicePixelRatio is only supported from IE11,
- * so we use deviceXDPI and logicalXDPI that are supported from IE6.
- */
- devicePixelRatio: window.devicePixelRatio || window.screen.deviceXDPI / window.screen.logicalXDPI,
- deprecated: {
- '5.1.0': {
- statics: {
- methods: {
- /**
- * @deprecated 5.1.0
- * Stably sort the list of sprites by their zIndex.
- * Deprecated, use the {@link Ext.Array#sort} method instead.
- * @param {Array} list
- * @return {Array} Sorted array.
- */
- stableSort: function(list) {
- return Ext.Array.sort(list, function(a, b) {
- return a.attr.zIndex - b.attr.zIndex;
- });
- }
- }
- }
- }
- },
- cls: Ext.baseCSSPrefix + 'surface',
- config: {
- /**
- * @cfg {Array}
- * The [x, y, width, height] rect of the surface related to its container.
- */
- rect: null,
- /**
- * @cfg {Object}
- * Background sprite config of the surface.
- */
- background: null,
- /**
- * @cfg {Array}
- * Array of sprite instances.
- */
- items: [],
- /**
- * @cfg {Boolean}
- * Indicates whether the surface needs to redraw.
- */
- dirty: false,
- /**
- * @cfg {Boolean} flipRtlText
- * If the surface is in the RTL mode, text will render with the RTL direction,
- * but the alignment and position of the text won't change by default.
- * Setting this config to 'true' will get text alignment and its position
- * within a surface mirrored.
- */
- flipRtlText: false
- },
- isSurface: true,
- /**
- * @private
- * This flag is used to indicate that `predecessors` surfaces that should render
- * before this surface renders are dirty, and to call `renderFrame`
- * when all `predecessors` have their `renderFrame` called (i.e. not dirty anymore).
- * This flag indicates that current surface has surfaces that are yet to render
- * before current surface can render. When all the `predecessors` surfaces
- * have rendered, i.e. when `dirtyPredecessorCount` reaches zero,
- */
- isPendingRenderFrame: false,
- dirtyPredecessorCount: 0,
- emptyRect: [
- 0,
- 0,
- 0,
- 0
- ],
- constructor: function(config) {
- var me = this;
- me.predecessors = [];
- me.successors = [];
- me.map = {};
- me.callParent([
- config
- ]);
- me.matrix = new Ext.draw.Matrix();
- me.inverseMatrix = me.matrix.inverse();
- },
- /**
- * Round the number to align to the pixels on device.
- * @param {Number} num The number to align.
- * @return {Number} The resultant alignment.
- */
- roundPixel: function(num) {
- return Math.round(this.devicePixelRatio * num) / this.devicePixelRatio;
- },
- /**
- * Mark the surface to render after another surface is updated.
- * @param {Ext.draw.Surface} surface The surface to wait for.
- */
- waitFor: function(surface) {
- var me = this,
- predecessors = me.predecessors;
- if (!Ext.Array.contains(predecessors, surface)) {
- predecessors.push(surface);
- surface.successors.push(me);
- if (surface.getDirty()) {
- me.dirtyPredecessorCount++;
- }
- }
- },
- updateDirty: function(dirty) {
- var successors = this.successors,
- ln = successors.length,
- i = 0,
- successor;
- for (; i < ln; i++) {
- successor = successors[i];
- if (dirty) {
- successor.dirtyPredecessorCount++;
- successor.setDirty(true);
- } else {
- successor.dirtyPredecessorCount--;
- // Don't need to call `setDirty(false)` on a successor here,
- // as this will be done by `renderFrame`.
- if (successor.dirtyPredecessorCount === 0 && successor.isPendingRenderFrame) {
- successor.renderFrame();
- }
- }
- }
- },
- applyBackground: function(background, oldBackground) {
- this.setDirty(true);
- if (Ext.isString(background)) {
- background = {
- fillStyle: background
- };
- }
- return Ext.factory(background, Ext.draw.sprite.Rect, oldBackground);
- },
- applyRect: function(rect, oldRect) {
- if (oldRect && rect[0] === oldRect[0] && rect[1] === oldRect[1] && rect[2] === oldRect[2] && rect[3] === oldRect[3]) {
- return oldRect;
- }
- if (Ext.isArray(rect)) {
- return [
- rect[0],
- rect[1],
- rect[2],
- rect[3]
- ];
- } else if (Ext.isObject(rect)) {
- return [
- rect.x || rect.left,
- rect.y || rect.top,
- rect.width || (rect.right - rect.left),
- rect.height || (rect.bottom - rect.top)
- ];
- }
- },
- updateRect: function(rect) {
- var me = this,
- l = rect[0],
- t = rect[1],
- r = l + rect[2],
- b = t + rect[3],
- background = me.getBackground(),
- element = me.element;
- element.setLocalXY(Math.floor(l), Math.floor(t));
- element.setSize(Math.ceil(r - Math.floor(l)), Math.ceil(b - Math.floor(t)));
- if (background) {
- background.setAttributes({
- x: 0,
- y: 0,
- width: Math.ceil(r - Math.floor(l)),
- height: Math.ceil(b - Math.floor(t))
- });
- }
- me.setDirty(true);
- },
- /**
- * Reset the matrix of the surface.
- */
- resetTransform: function() {
- this.matrix.set(1, 0, 0, 1, 0, 0);
- this.inverseMatrix.set(1, 0, 0, 1, 0, 0);
- this.setDirty(true);
- },
- /**
- * Get the sprite by id or index.
- * It will first try to find a sprite with the given id, otherwise will try to use the id
- * as an index.
- * @param {String|Number} id
- * @return {Ext.draw.sprite.Sprite}
- */
- get: function(id) {
- return this.map[id] || this.getItems()[id];
- },
- /**
- * @method
- * Add a Sprite to the surface.
- * You can put any number of objects as the parameter.
- * See {@link Ext.draw.sprite.Sprite} for the configuration object to be passed
- * into this method.
- *
- * For example:
- *
- * drawContainer.getSurface().add({
- * type: 'circle',
- * fill: '#ffc',
- * radius: 100,
- * x: 100,
- * y: 100
- * });
- * drawContainer.renderFrame();
- *
- * @param {Object/Object[]} sprite
- * @return {Ext.draw.sprite.Sprite/Ext.draw.sprite.Sprite[]}
- *
- */
- add: function() {
- var me = this,
- args = Array.prototype.slice.call(arguments),
- argIsArray = Ext.isArray(args[0]),
- map = me.map,
- results = [],
- items, item, sprite, oldSurface, i, ln;
- items = Ext.Array.clean(argIsArray ? args[0] : args);
- if (!items.length) {
- return results;
- }
- for (i = 0 , ln = items.length; i < ln; i++) {
- item = items[i];
- if (!item || item.destroyed) {
-
- continue;
- }
- sprite = null;
- if (item.isSprite && !map[item.getId()]) {
- sprite = item;
- } else if (!map[item.id]) {
- sprite = this.createItem(item);
- }
- if (sprite) {
- map[sprite.getId()] = sprite;
- results.push(sprite);
- oldSurface = sprite.getSurface();
- if (oldSurface && oldSurface.isSurface) {
- oldSurface.remove(sprite);
- }
- sprite.setParent(me);
- sprite.setSurface(me);
- me.onAdd(sprite);
- }
- }
- items = me.getItems();
- if (items) {
- items.push.apply(items, results);
- }
- me.dirtyZIndex = true;
- me.setDirty(true);
- if (!argIsArray && results.length === 1) {
- return results[0];
- } else {
- return results;
- }
- },
- /**
- * @method
- * @protected
- * Invoked when a sprite is added to the surface.
- * @param {Ext.draw.sprite.Sprite} sprite The sprite to be added.
- */
- onAdd: Ext.emptyFn,
- /**
- * Remove a given sprite from the surface,
- * optionally destroying the sprite in the process.
- * You can also call the sprite's own `remove` method.
- *
- * For example:
- *
- * drawContainer.surface.remove(sprite);
- * // or...
- * sprite.remove();
- *
- * @param {Ext.draw.sprite.Sprite/String} sprite A sprite instance or its ID.
- * @param {Boolean} [isDestroy=false] If `true`, the sprite will be destroyed.
- * @return {Ext.draw.sprite.Sprite} Returns the removed/destroyed sprite or `null` otherwise.
- */
- remove: function(sprite, isDestroy) {
- var me = this,
- destroying = me.clearing,
- id, isOwnSprite;
- if (sprite) {
- if (sprite.charAt) {
- // is String
- sprite = me.map[sprite];
- }
- if (!sprite || !sprite.isSprite) {
- return null;
- }
- id = sprite.id;
- isOwnSprite = me.map[id];
- delete me.map[id];
- if (sprite.destroyed || sprite.destroying) {
- if (isOwnSprite && !destroying) {
- // Somehow this sprite was destroyed,
- // but still belongs to the surface.
- Ext.Array.remove(me.getItems(), sprite);
- }
- return sprite;
- }
- if (!isOwnSprite) {
- if (isDestroy) {
- sprite.destroy();
- }
- return sprite;
- }
- sprite.setParent(null);
- sprite.setSurface(null);
- if (isDestroy) {
- sprite.destroy();
- }
- if (!destroying) {
- Ext.Array.remove(me.getItems(), sprite);
- me.dirtyZIndex = true;
- me.setDirty(true);
- }
- }
- return sprite || null;
- },
- /**
- * Remove all sprites from the surface, optionally destroying the sprites in the process.
- *
- * For example:
- *
- * drawContainer.getSurface('main').removeAll();
- *
- * @param {Boolean} [isDestroy=false]
- */
- removeAll: function(isDestroy) {
- var me = this,
- items = me.getItems(),
- item, i;
- me.clearing = !!isDestroy;
- for (i = items.length - 1; i >= 0; i--) {
- item = items[i];
- if (isDestroy) {
- // Some sprites may destroy other sprites, however if we're destroying then
- // we don't remove anything from the items array since we'll just clear it later.
- // If a sprite is destroyed, the remove method will just drop out with no harm done.
- item.destroy();
- } else {
- item.setParent(null);
- item.setSurface(null);
- }
- }
- me.clearing = false;
- items.length = 0;
- me.map = {};
- me.dirtyZIndex = true;
- if (!me.destroying) {
- me.setDirty(true);
- }
- },
- /**
- * @private
- */
- applyItems: function(items) {
- if (this.getItems()) {
- this.removeAll(true);
- }
- return Ext.Array.from(this.add(items));
- },
- /**
- * @private
- * Creates an item and appends it to the surface. Called
- * as an internal method when calling `add`.
- */
- createItem: function(config) {
- return Ext.create(config.xclass || 'sprite.' + config.type, config);
- },
- /**
- * Return the minimal bounding box that contains all the sprites bounding boxes
- * in the given list of sprites.
- * @param {Ext.draw.sprite.Sprite[]|Ext.draw.sprite.Sprite} sprites
- * @param {Boolean} [isWithoutTransform=false]
- * @return {{x: Number, y: Number, width: number, height: number}}
- */
- getBBox: function(sprites, isWithoutTransform) {
- var left = Infinity,
- right = -Infinity,
- top = Infinity,
- bottom = -Infinity,
- sprite, bbox, i, ln;
- sprites = Ext.Array.from(sprites);
- for (i = 0 , ln = sprites.length; i < ln; i++) {
- sprite = sprites[i];
- bbox = sprite.getBBox(isWithoutTransform);
- if (left > bbox.x) {
- left = bbox.x;
- }
- if (right < bbox.x + bbox.width) {
- right = bbox.x + bbox.width;
- }
- if (top > bbox.y) {
- top = bbox.y;
- }
- if (bottom < bbox.y + bbox.height) {
- bottom = bbox.y + bbox.height;
- }
- }
- return {
- x: left,
- y: top,
- width: right - left,
- height: bottom - top
- };
- },
- /**
- * @private
- * @method getOwnerBody
- * The body element of the chart or the draw container
- * (doesn't include docked items like a legend).
- * Draw Container is a Panel in Classic (to allow for docked items)
- * and a Container in Modern, so the body is retrieved differently.
- * @return {Ext.dom.Element}
- */
- /**
- * @private
- * Converts event's page coordinates into surface coordinates.
- * Note: surface's x-coordinates always go LTR, regardless of RTL mode.
- */
- getEventXY: function(e) {
- var me = this,
- isRtl = me.getInherited().rtl,
- pageXY = e.getXY(),
- // Event position in page coordinates.
- // The body of the chart (doesn't include docked items like legend).
- container = me.getOwnerBody(),
- xy = container.getXY(),
- // Surface container position in page coordinates.
- // Surface position in surface container coordinates (LTR).
- rect = me.getRect() || me.emptyRect,
- result = [],
- width;
- if (isRtl) {
- width = container.getWidth();
- // The line below is actually a simplified form of
- // rect[2] - (pageXY[0] - xy[0] - (width - (rect[0] + rect[2]))).
- result[0] = xy[0] - pageXY[0] - rect[0] + width;
- } else {
- result[0] = pageXY[0] - xy[0] - rect[0];
- }
- result[1] = pageXY[1] - xy[1] - rect[1];
- return result;
- },
- /**
- * @method
- * Empty the surface content (without touching the sprites.)
- */
- clear: Ext.emptyFn,
- /**
- * @private
- * Order the items by their z-index if any of that has been changed since last sort.
- */
- orderByZIndex: function() {
- var me = this,
- items = me.getItems(),
- dirtyZIndex = false,
- i, ln;
- if (me.getDirty()) {
- for (i = 0 , ln = items.length; i < ln; i++) {
- if (items[i].attr.dirtyZIndex) {
- dirtyZIndex = true;
- break;
- }
- }
- if (dirtyZIndex) {
- // sort by zIndex
- Ext.Array.sort(items, function(a, b) {
- return a.attr.zIndex - b.attr.zIndex;
- });
- this.setDirty(true);
- }
- for (i = 0 , ln = items.length; i < ln; i++) {
- items[i].attr.dirtyZIndex = false;
- }
- }
- },
- /**
- * Force the element to redraw.
- */
- repaint: function() {
- var me = this;
- me.repaint = Ext.emptyFn;
- Ext.defer(function() {
- delete me.repaint;
- me.element.repaint();
- }, 1);
- },
- /**
- * Triggers the re-rendering of the canvas.
- */
- renderFrame: function() {
- var me = this,
- background, items, item, i, ln;
- if (!(me.element && me.getDirty() && me.getRect())) {
- return;
- }
- if (me.dirtyPredecessorCount > 0) {
- me.isPendingRenderFrame = true;
- return;
- }
- background = me.getBackground();
- items = me.getItems();
- // This will also check the dirty flags of the sprites.
- me.orderByZIndex();
- if (me.getDirty()) {
- me.clear();
- me.clearTransform();
- if (background) {
- me.renderSprite(background);
- }
- for (i = 0 , ln = items.length; i < ln; i++) {
- item = items[i];
- if (me.renderSprite(item) === false) {
- return;
- }
- item.attr.textPositionCount = me.textPosition;
- }
- me.setDirty(false);
- }
- },
- /**
- * @method
- * @private
- * Renders a single sprite into the surface.
- * Do not call it from outside `renderFrame` method.
- *
- * @param {Ext.draw.sprite.Sprite} sprite The Sprite to be rendered.
- * @return {Boolean} returns `false` to stop the rendering to continue.
- */
- renderSprite: Ext.emptyFn,
- /**
- * @method flatten
- * Flattens the given drawing surfaces into a single image
- * and returns an object containing the data (in the DataURL format)
- * and the type (e.g. 'png' or 'svg') of that image.
- * @param {Object} size The size of the final image.
- * @param {Number} size.width
- * @param {Number} size.height
- * @param {Ext.draw.Surface[]} surfaces The surfaces to flatten.
- * @return {Object}
- * @return {String} return.data The DataURL of the flattened image.
- * @return {String} return.type The type of the image.
- *
- */
- /**
- * @method
- * @private
- * Clears the current transformation state on the surface.
- */
- clearTransform: Ext.emptyFn,
- /**
- * Destroys the surface. This is done by removing all components from it and
- * also removing its reference to a DOM element.
- *
- * For example:
- *
- * drawContainer.surface.destroy();
- */
- destroy: function() {
- var me = this;
- me.destroying = true;
- me.removeAll(true);
- me.destroying = false;
- me.predecessors = me.successors = null;
- if (me.hasListeners.destroy) {
- me.fireEvent('destroy', me);
- }
- me.callParent();
- }
- });
- /**
- * @private
- * Adds hit testing methods to the Ext.draw.Surface.
- * Included by the Ext.draw.plugin.SpriteEvents.
- */
- Ext.define('Ext.draw.overrides.hittest.Surface', {
- override: 'Ext.draw.Surface',
- /**
- * Performs a hit test on all sprites in the surface, returning the first matching one.
- * @param {Array} point A two-item array containing x and y coordinates of the point
- * in surface coordinate system.
- * @param {Object} options Hit testing options.
- * @return {Object} A hit result object that contains more information about what
- * exactly was hit or null if nothing was hit.
- * @member Ext.draw.Surface
- */
- hitTest: function(point, options) {
- var me = this,
- sprites = me.getItems(),
- i, sprite, result;
- options = options || Ext.draw.sprite.Sprite.defaultHitTestOptions;
- for (i = sprites.length - 1; i >= 0; i--) {
- sprite = sprites[i];
- if (sprite.hitTest) {
- result = sprite.hitTest(point, options);
- if (result) {
- return result;
- }
- }
- }
- return null;
- },
- /**
- * Performs a hit test on all sprites in the surface, returning the first matching one.
- * Since hit testing is typically performed on mouse events, this convenience method
- * converts event's page coordinates to surface coordinates before calling {@link #hitTest}.
- * @param {Object} event An event object.
- * @param {Object} options Hit testing options.
- * @return {Object} A hit result object that contains more information about what
- * exactly was hit or null if nothing was hit.
- * @member Ext.draw.Surface
- */
- hitTestEvent: function(event, options) {
- var xy = this.getEventXY(event);
- return this.hitTest(xy, options);
- }
- });
- /**
- * @class Ext.draw.engine.SvgContext
- *
- * A class that imitates a canvas context but generates svg elements instead.
- */
- Ext.define('Ext.draw.engine.SvgContext', {
- requires: [
- 'Ext.draw.Color'
- ],
- /**
- * @private
- * Properties to be saved/restored in the `save` and `restore` methods.
- */
- toSave: [
- 'strokeOpacity',
- 'strokeStyle',
- 'fillOpacity',
- 'fillStyle',
- 'globalAlpha',
- 'lineWidth',
- 'lineCap',
- 'lineJoin',
- 'lineDash',
- 'lineDashOffset',
- 'miterLimit',
- 'shadowOffsetX',
- 'shadowOffsetY',
- 'shadowBlur',
- 'shadowColor',
- 'globalCompositeOperation',
- 'position',
- 'fillGradient',
- 'strokeGradient'
- ],
- strokeOpacity: 1,
- strokeStyle: 'none',
- fillOpacity: 1,
- fillStyle: 'none',
- lineDas: [],
- lineDashOffset: 0,
- globalAlpha: 1,
- lineWidth: 1,
- lineCap: 'butt',
- lineJoin: 'miter',
- miterLimit: 10,
- shadowOffsetX: 0,
- shadowOffsetY: 0,
- shadowBlur: 0,
- shadowColor: 'none',
- globalCompositeOperation: 'src',
- urlStringRe: /^url\(#([\w-]+)\)$/,
- constructor: function(SvgSurface) {
- var me = this;
- me.surface = SvgSurface;
- // Stack of contexts.
- me.state = [];
- me.matrix = new Ext.draw.Matrix();
- // Currently manipulated path.
- me.path = null;
- me.clear();
- },
- /**
- * Clears the context.
- */
- clear: function() {
- // Current group to put paths into.
- this.group = this.surface.mainGroup;
- // Position within the current group.
- this.position = 0;
- this.path = null;
- },
- /**
- * @private
- * @param {String} tag
- * @return {*}
- */
- getElement: function(tag) {
- return this.surface.getSvgElement(this.group, tag, this.position++);
- },
- /**
- * Pushes the context state to the state stack.
- */
- save: function() {
- var toSave = this.toSave,
- obj = {},
- group = this.getElement('g'),
- key, i;
- for (i = 0; i < toSave.length; i++) {
- key = toSave[i];
- if (key in this) {
- obj[key] = this[key];
- }
- }
- this.position = 0;
- obj.matrix = this.matrix.clone();
- this.state.push(obj);
- this.group = group;
- return group;
- },
- /**
- * Pops the state stack and restores the state.
- */
- restore: function() {
- var toSave = this.toSave,
- obj = this.state.pop(),
- group = this.group,
- children = group.dom.childNodes,
- key, i;
- // Removing extra DOM elements that were not reused.
- while (children.length > this.position) {
- group.last().destroy();
- }
- for (i = 0; i < toSave.length; i++) {
- key = toSave[i];
- if (key in obj) {
- this[key] = obj[key];
- } else {
- delete this[key];
- }
- }
- this.setTransform.apply(this, obj.matrix.elements);
- this.group = group.getParent();
- },
- /**
- * Changes the transformation matrix to apply the matrix given by the arguments
- * as described below.
- * @param {Number} xx
- * @param {Number} yx
- * @param {Number} xy
- * @param {Number} yy
- * @param {Number} dx
- * @param {Number} dy
- */
- transform: function(xx, yx, xy, yy, dx, dy) {
- var inv;
- if (this.path) {
- inv = Ext.draw.Matrix.fly([
- xx,
- yx,
- xy,
- yy,
- dx,
- dy
- ]).inverse();
- this.path.transform(inv);
- }
- this.matrix.append(xx, yx, xy, yy, dx, dy);
- },
- /**
- * Changes the transformation matrix to the matrix given by the arguments as described below.
- * @param {Number} xx
- * @param {Number} yx
- * @param {Number} xy
- * @param {Number} yy
- * @param {Number} dx
- * @param {Number} dy
- */
- setTransform: function(xx, yx, xy, yy, dx, dy) {
- if (this.path) {
- this.path.transform(this.matrix);
- }
- this.matrix.reset();
- this.transform(xx, yx, xy, yy, dx, dy);
- },
- /**
- * Scales the current context by the specified horizontal (x) and vertical (y) factors.
- * @param {Number} x The horizontal scaling factor, where 1 equals unity or 100% scale.
- * @param {Number} y The vertical scaling factor.
- */
- scale: function(x, y) {
- this.transform(x, 0, 0, y, 0, 0);
- },
- /**
- * Rotates the current context coordinates (that is, a transformation matrix).
- * @param {Number} angle The rotation angle, in radians.
- */
- rotate: function(angle) {
- var xx = Math.cos(angle),
- yx = Math.sin(angle),
- xy = -Math.sin(angle),
- yy = Math.cos(angle);
- this.transform(xx, yx, xy, yy, 0, 0);
- },
- /**
- * Specifies values to move the origin point in a canvas.
- * @param {Number} x The value to add to horizontal (or x) coordinates.
- * @param {Number} y The value to add to vertical (or y) coordinates.
- */
- translate: function(x, y) {
- this.transform(1, 0, 0, 1, x, y);
- },
- setGradientBBox: function(bbox) {
- this.bbox = bbox;
- },
- /**
- * Resets the current default path.
- */
- beginPath: function() {
- this.path = new Ext.draw.Path();
- },
- /**
- * Creates a new subpath with the given point.
- * @param {Number} x
- * @param {Number} y
- */
- moveTo: function(x, y) {
- if (!this.path) {
- this.beginPath();
- }
- this.path.moveTo(x, y);
- this.path.element = null;
- },
- /**
- * Adds the given point to the current subpath, connected to the previous one by a straight
- * line.
- * @param {Number} x
- * @param {Number} y
- */
- lineTo: function(x, y) {
- if (!this.path) {
- this.beginPath();
- }
- this.path.lineTo(x, y);
- this.path.element = null;
- },
- /**
- * Adds a new closed subpath to the path, representing the given rectangle.
- * @param {Number} x
- * @param {Number} y
- * @param {Number} width
- * @param {Number} height
- */
- rect: function(x, y, width, height) {
- this.moveTo(x, y);
- this.lineTo(x + width, y);
- this.lineTo(x + width, y + height);
- this.lineTo(x, y + height);
- this.closePath();
- },
- /**
- * Paints the box that outlines the given rectangle onto the canvas, using the current
- * stroke style.
- * @param {Number} x
- * @param {Number} y
- * @param {Number} width
- * @param {Number} height
- */
- strokeRect: function(x, y, width, height) {
- this.beginPath();
- this.rect(x, y, width, height);
- this.stroke();
- },
- /**
- * Paints the given rectangle onto the canvas, using the current fill style.
- * @param {Number} x
- * @param {Number} y
- * @param {Number} width
- * @param {Number} height
- */
- fillRect: function(x, y, width, height) {
- this.beginPath();
- this.rect(x, y, width, height);
- this.fill();
- },
- /**
- * 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.
- */
- closePath: function() {
- if (!this.path) {
- this.beginPath();
- }
- this.path.closePath();
- this.path.element = null;
- },
- /**
- * Arc command using svg parameters.
- * @param {Number} r1
- * @param {Number} r2
- * @param {Number} rotation
- * @param {Number} large
- * @param {Number} swipe
- * @param {Number} x2
- * @param {Number} y2
- */
- arcSvg: function(r1, r2, rotation, large, swipe, x2, y2) {
- if (!this.path) {
- this.beginPath();
- }
- this.path.arcSvg(r1, r2, rotation, large, swipe, x2, y2);
- this.path.element = null;
- },
- /**
- * 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.
- * @param {Number} x
- * @param {Number} y
- * @param {Number} radius
- * @param {Number} startAngle
- * @param {Number} endAngle
- * @param {Number} anticlockwise
- */
- arc: function(x, y, radius, startAngle, endAngle, anticlockwise) {
- if (!this.path) {
- this.beginPath();
- }
- this.path.arc(x, y, radius, startAngle, endAngle, anticlockwise);
- this.path.element = null;
- },
- /**
- * 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.
- * @param {Number} x
- * @param {Number} y
- * @param {Number} radiusX
- * @param {Number} radiusY
- * @param {Number} rotation
- * @param {Number} startAngle
- * @param {Number} endAngle
- * @param {Number} anticlockwise
- */
- ellipse: function(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise) {
- if (!this.path) {
- this.beginPath();
- }
- this.path.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise);
- this.path.element = null;
- },
- /**
- * Adds an arc with the given control points and radius to the current subpath, connected
- * to the previous point by a straight line. 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. In the case of an ellipse, the rotation
- * argument controls the clockwise inclination of the ellipse relative to the x-axis.
- * @param {Number} x1
- * @param {Number} y1
- * @param {Number} x2
- * @param {Number} y2
- * @param {Number} radiusX
- * @param {Number} radiusY
- * @param {Number} rotation
- */
- arcTo: function(x1, y1, x2, y2, radiusX, radiusY, rotation) {
- if (!this.path) {
- this.beginPath();
- }
- this.path.arcTo(x1, y1, x2, y2, radiusX, radiusY, rotation);
- this.path.element = null;
- },
- /**
- * Adds the given point to the current subpath, connected to the previous one by a cubic Bézier
- * curve with the given control points.
- * @param {Number} x1
- * @param {Number} y1
- * @param {Number} x2
- * @param {Number} y2
- * @param {Number} x3
- * @param {Number} y3
- */
- bezierCurveTo: function(x1, y1, x2, y2, x3, y3) {
- if (!this.path) {
- this.beginPath();
- }
- this.path.bezierCurveTo(x1, y1, x2, y2, x3, y3);
- this.path.element = null;
- },
- /**
- * 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.
- * @param {String} text
- * @param {Number} x
- * @param {Number} y
- */
- strokeText: function(text, x, y) {
- var element, tspan;
- text = String(text);
- if (this.strokeStyle) {
- element = this.getElement('text');
- tspan = this.surface.getSvgElement(element, 'tspan', 0);
- this.surface.setElementAttributes(element, {
- "x": x,
- "y": y,
- "transform": this.matrix.toSvg(),
- "stroke": this.strokeStyle,
- "fill": "none",
- "opacity": this.globalAlpha,
- "stroke-opacity": this.strokeOpacity,
- "style": "font: " + this.font,
- "stroke-dasharray": this.lineDash.join(','),
- "stroke-dashoffset": this.lineDashOffset
- });
- if (this.lineDash.length) {
- this.surface.setElementAttributes(element, {
- "stroke-dasharray": this.lineDash.join(','),
- "stroke-dashoffset": this.lineDashOffset
- });
- }
- if (tspan.dom.firstChild) {
- tspan.dom.removeChild(tspan.dom.firstChild);
- }
- this.surface.setElementAttributes(tspan, {
- "alignment-baseline": "alphabetic"
- });
- tspan.dom.appendChild(document.createTextNode(Ext.String.htmlDecode(text)));
- }
- },
- /**
- * 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.
- * @param {String} text
- * @param {Number} x
- * @param {Number} y
- */
- fillText: function(text, x, y) {
- var element, tspan;
- text = String(text);
- if (this.fillStyle) {
- element = this.getElement('text');
- tspan = this.surface.getSvgElement(element, 'tspan', 0);
- this.surface.setElementAttributes(element, {
- "x": x,
- "y": y,
- "transform": this.matrix.toSvg(),
- "fill": this.fillStyle,
- "opacity": this.globalAlpha,
- "fill-opacity": this.fillOpacity,
- "style": "font: " + this.font
- });
- if (tspan.dom.firstChild) {
- tspan.dom.removeChild(tspan.dom.firstChild);
- }
- this.surface.setElementAttributes(tspan, {
- "alignment-baseline": "alphabetic"
- });
- tspan.dom.appendChild(document.createTextNode(Ext.String.htmlDecode(text)));
- }
- },
- /**
- * Draws the given image onto the canvas.
- * 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.
- * @param {HTMLElement} image
- * @param {Number} sx
- * @param {Number} sy
- * @param {Number} sw
- * @param {Number} sh
- * @param {Number} dx
- * @param {Number} dy
- * @param {Number} dw
- * @param {Number} dh
- */
- drawImage: function(image, sx, sy, sw, sh, dx, dy, dw, dh) {
- var me = this,
- element = me.getElement('image'),
- x = sx,
- y = sy,
- width = typeof sw === 'undefined' ? image.width : sw,
- height = typeof sh === 'undefined' ? image.height : sh,
- viewBox = null;
- if (typeof dh !== 'undefined') {
- viewBox = sx + " " + sy + " " + sw + " " + sh;
- x = dx;
- y = dy;
- width = dw;
- height = dh;
- }
- element.dom.setAttributeNS("http:/" + "/www.w3.org/1999/xlink", "href", image.src);
- me.surface.setElementAttributes(element, {
- viewBox: viewBox,
- x: x,
- y: y,
- width: width,
- height: height,
- opacity: me.globalAlpha,
- transform: me.matrix.toSvg()
- });
- },
- /**
- * Fills the subpaths of the current default path or the given path with the current fill style.
- */
- fill: function() {
- var me = this,
- path, fillGradient, element, bbox, fill;
- if (!me.path) {
- return;
- }
- if (me.fillStyle) {
- fillGradient = me.fillGradient;
- element = me.path.element;
- bbox = me.bbox;
- if (!element) {
- path = me.path.toString();
- element = me.path.element = me.getElement('path');
- me.surface.setElementAttributes(element, {
- "d": path,
- "transform": me.matrix.toSvg()
- });
- }
- if (fillGradient && bbox) {
- // This indirectly calls ctx.createLinearGradient or ctx.createRadialGradient,
- // depending on the type of gradient, and returns an instance of
- // Ext.draw.engine.SvgContext.Gradient.
- fill = fillGradient.generateGradient(me, bbox);
- } else {
- fill = me.fillStyle;
- }
- me.surface.setElementAttributes(element, {
- "fill": fill,
- "fill-opacity": me.fillOpacity * me.globalAlpha
- });
- }
- },
- /**
- * Strokes the subpaths of the current default path or the given path with the current
- * stroke style.
- */
- stroke: function() {
- var me = this,
- path, strokeGradient, element, bbox, stroke;
- if (!me.path) {
- return;
- }
- if (me.strokeStyle) {
- strokeGradient = me.strokeGradient;
- element = me.path.element;
- bbox = me.bbox;
- if (!element || !me.path.svgString) {
- path = me.path.toString();
- if (!path) {
- return;
- }
- element = me.path.element = me.getElement('path');
- me.surface.setElementAttributes(element, {
- "fill": "none",
- "d": path,
- "transform": me.matrix.toSvg()
- });
- }
- if (strokeGradient && bbox) {
- // This indirectly calls ctx.createLinearGradient or ctx.createRadialGradient,
- // depending on the type of gradient, and returns an instance of
- // Ext.draw.engine.SvgContext.Gradient.
- stroke = strokeGradient.generateGradient(me, bbox);
- } else {
- stroke = me.strokeStyle;
- }
- me.surface.setElementAttributes(element, {
- "stroke": stroke,
- "stroke-linecap": me.lineCap,
- "stroke-linejoin": me.lineJoin,
- "stroke-width": me.lineWidth,
- "stroke-opacity": me.strokeOpacity * me.globalAlpha,
- "stroke-dasharray": me.lineDash.join(','),
- "stroke-dashoffset": me.lineDashOffset
- });
- if (me.lineDash.length) {
- me.surface.setElementAttributes(element, {
- "stroke-dasharray": me.lineDash.join(','),
- "stroke-dashoffset": me.lineDashOffset
- });
- }
- }
- },
- /**
- * @protected
- *
- * Note: After the method guarantees the transform matrix will be inverted.
- * @param {Object} attr The attribute object
- * @param {Boolean} [transformFillStroke] Indicate whether to transform fill and stroke.
- * If this is not given, then uses `attr.transformFillStroke` instead.
- */
- fillStroke: function(attr, transformFillStroke) {
- var ctx = this,
- fillStyle = ctx.fillStyle,
- strokeStyle = ctx.strokeStyle,
- fillOpacity = ctx.fillOpacity,
- strokeOpacity = ctx.strokeOpacity;
- if (transformFillStroke === undefined) {
- transformFillStroke = attr.transformFillStroke;
- }
- if (!transformFillStroke) {
- attr.inverseMatrix.toContext(ctx);
- }
- if (fillStyle && fillOpacity !== 0) {
- ctx.fill();
- }
- if (strokeStyle && strokeOpacity !== 0) {
- ctx.stroke();
- }
- },
- appendPath: function(path) {
- this.path = path.clone();
- },
- setLineDash: function(lineDash) {
- this.lineDash = lineDash;
- },
- getLineDash: function() {
- return this.lineDash;
- },
- /**
- * Returns an object that represents a linear gradient that paints along the line
- * given by the coordinates represented by the arguments.
- * @param {Number} x0
- * @param {Number} y0
- * @param {Number} x1
- * @param {Number} y1
- * @return {Ext.draw.engine.SvgContext.Gradient}
- */
- createLinearGradient: function(x0, y0, x1, y1) {
- var me = this,
- element = me.surface.getNextDef('linearGradient'),
- gradient;
- me.surface.setElementAttributes(element, {
- "x1": x0,
- "y1": y0,
- "x2": x1,
- "y2": y1,
- "gradientUnits": "userSpaceOnUse"
- });
- gradient = new Ext.draw.engine.SvgContext.Gradient(me, me.surface, element);
- return gradient;
- },
- /**
- * Returns a CanvasGradient object that represents a radial gradient that paints
- * along the cone given by the circles represented by the arguments.
- * If either of the radii are negative, throws an IndexSizeError exception.
- * @param {Number} x0
- * @param {Number} y0
- * @param {Number} r0
- * @param {Number} x1
- * @param {Number} y1
- * @param {Number} r1
- * @return {Ext.draw.engine.SvgContext.Gradient}
- */
- createRadialGradient: function(x0, y0, r0, x1, y1, r1) {
- var me = this,
- element = me.surface.getNextDef('radialGradient'),
- gradient;
- me.surface.setElementAttributes(element, {
- fx: x0,
- fy: y0,
- cx: x1,
- cy: y1,
- r: r1,
- gradientUnits: 'userSpaceOnUse'
- });
- gradient = new Ext.draw.engine.SvgContext.Gradient(me, me.surface, element, r0 / r1);
- return gradient;
- }
- });
- /**
- * @class Ext.draw.engine.SvgContext.Gradient
- *
- * A class that implements native CanvasGradient interface
- * (https://developer.mozilla.org/en/docs/Web/API/CanvasGradient)
- * and a `toString` method that returns the ID of the gradient.
- */
- Ext.define('Ext.draw.engine.SvgContext.Gradient', {
- // Gradients workflow in SVG engine:
- //
- // Inside the 'fill' & 'stroke' methods of the SVG Context
- // we check if the 'ctx.fillGradient' or 'ctx.strokeGradient'
- // objects exist.
- // These objects are instances of Ext.draw.gradient.Gradient
- // and are assigned to the ctx by the sprite's 'useAttributes' method,
- // if the sprite has any gradients.
- //
- // Additionally, we check if the 'ctx.bbox' object exists - the bounding box
- // for the gradients, set by the sprite's 'setGradientBBox' method.
- //
- // If we have both bbox and a valid instance of Ext.draw.gradient.Gradient,
- // the 'generateGradient' method of the instance is called,
- // which in turn calls 'ctx.createLinearGradient' or 'ctx.createRadialGradient'
- // depending on the type of the gradient represented by the instance.
- // These methods create a 'linearGradient' or 'radialGradient' SVG
- // node and wrap it into a Ext.draw.engine.SvgContext.Gradient instance.
- //
- // The Ext.draw.engine.SvgContext.Gradient instance is then used internally
- // by the Ext.draw.gradient.Gradient to add color 'stop' nodes
- // to the gradient node, and by the SVG context when the 'fill' or
- // 'stroke' attribute of a 'path' node is set to the Ext.draw.engine.SvgContext.Gradient
- // instance, which is implicitly converted to a string - a 'url(#id)' reference
- // to the gradient element wrapped by the instance.
- isGradient: true,
- constructor: function(ctx, surface, element, compression) {
- var me = this;
- me.ctx = ctx;
- me.surface = surface;
- me.element = element;
- me.position = 0;
- me.compression = compression || 0;
- },
- /**
- * 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.
- * @param {Number} offset
- * @param {String} color
- */
- addColorStop: function(offset, color) {
- var me = this,
- stop = me.surface.getSvgElement(me.element, 'stop', me.position++),
- compression = me.compression;
- me.surface.setElementAttributes(stop, {
- "offset": (((1 - compression) * offset + compression) * 100).toFixed(2) + '%',
- "stop-color": color,
- "stop-opacity": Ext.util.Color.fly(color).a.toFixed(15)
- });
- },
- toString: function() {
- var children = this.element.dom.childNodes;
- // Removing surplus stops in case existing gradient element with more stops was reused.
- while (children.length > this.position) {
- Ext.fly(children[children.length - 1]).destroy();
- }
- return 'url(#' + this.element.getId() + ')';
- }
- });
- /**
- * @class Ext.draw.engine.Svg
- * @extends Ext.draw.Surface
- *
- * SVG engine.
- */
- Ext.define('Ext.draw.engine.Svg', {
- extend: 'Ext.draw.Surface',
- requires: [
- 'Ext.draw.engine.SvgContext'
- ],
- isSVG: true,
- config: {
- /**
- * @cfg {Boolean} highPrecision
- * Nothing needs to be done in high precision mode.
- */
- highPrecision: false
- },
- getElementConfig: function() {
- return {
- reference: 'element',
- style: {
- position: 'absolute'
- },
- children: [
- {
- reference: 'bodyElement',
- style: {
- width: '100%',
- height: '100%',
- position: 'relative'
- },
- children: [
- {
- tag: 'svg',
- reference: 'svgElement',
- namespace: "http://www.w3.org/2000/svg",
- width: '100%',
- height: '100%',
- version: 1.1
- }
- ]
- }
- ]
- };
- },
- constructor: function(config) {
- var me = this;
- me.callParent([
- config
- ]);
- me.mainGroup = me.createSvgNode("g");
- me.defsElement = me.createSvgNode("defs");
- // me.svgElement is assigned in element creation of Ext.Component.
- me.svgElement.appendChild(me.mainGroup);
- me.svgElement.appendChild(me.defsElement);
- me.ctx = new Ext.draw.engine.SvgContext(me);
- },
- /**
- * Creates a DOM element under the SVG namespace of the given type.
- * @param {String} type The type of the SVG DOM element.
- * @return {*} The created element.
- */
- createSvgNode: function(type) {
- var node = document.createElementNS("http://www.w3.org/2000/svg", type);
- return Ext.get(node);
- },
- /**
- * @private
- * Returns the SVG DOM element at the given position.
- * If it does not already exist or is a different element tag,
- * it will be created and inserted into the DOM.
- * @param {Ext.dom.Element} group The parent DOM element.
- * @param {String} tag The SVG element tag.
- * @param {Number} position The position of the element in the DOM.
- * @return {Ext.dom.Element} The SVG element.
- */
- getSvgElement: function(group, tag, position) {
- var childNodes = group.dom.childNodes,
- length = childNodes.length,
- element;
- if (position < length) {
- element = childNodes[position];
- if (element.tagName === tag) {
- return Ext.get(element);
- } else {
- Ext.destroy(element);
- }
- } else if (position > length) {
- Ext.raise("Invalid position.");
- }
- element = Ext.get(this.createSvgNode(tag));
- if (position === 0) {
- group.insertFirst(element);
- } else {
- element.insertAfter(Ext.fly(childNodes[position - 1]));
- }
- element.cache = {};
- return element;
- },
- /**
- * @private
- * Applies attributes to the given element.
- * @param {Ext.dom.Element} element The DOM element to be applied.
- * @param {Object} attributes The attributes to apply to the element.
- */
- setElementAttributes: function(element, attributes) {
- var dom = element.dom,
- cache = element.cache,
- name, value;
- for (name in attributes) {
- value = attributes[name];
- if (cache[name] !== value) {
- cache[name] = value;
- dom.setAttribute(name, value);
- }
- }
- },
- /**
- * @private
- * Gets the next reference element under the SVG 'defs' tag.
- * @param {String} tagName The type of reference element.
- * @return {Ext.dom.Element} The reference element.
- */
- getNextDef: function(tagName) {
- return this.getSvgElement(this.defsElement, tagName, this.defsPosition++);
- },
- /**
- * @method clearTransform
- * @inheritdoc
- */
- clearTransform: function() {
- var me = this;
- me.mainGroup.set({
- transform: me.matrix.toSvg()
- });
- },
- /**
- * @method clear
- * @inheritdoc
- */
- clear: function() {
- this.ctx.clear();
- this.removeSurplusDefs();
- this.defsPosition = 0;
- },
- removeSurplusDefs: function() {
- var defsElement = this.defsElement,
- defs = defsElement.dom.childNodes,
- ln = defs.length,
- i;
- for (i = ln - 1; i > this.defsPosition; i--) {
- defsElement.removeChild(defs[i]);
- }
- },
- /**
- * @method renderSprite
- * @inheritdoc
- */
- renderSprite: function(sprite) {
- var me = this,
- rect = me.getRect(),
- ctx = me.ctx;
- // This check is simplistic, but should result in a better performance
- // compared to !sprite.isVisible() when most surface sprites are visible.
- if (sprite.attr.hidden || sprite.attr.globalAlpha === 0) {
- // Create an empty group for each hidden sprite,
- // so that when these sprites do become visible,
- // they don't need groups to be created and don't
- // mess up the previous order of elements in the
- // document, i.e. sprites rendered in the next
- // frame reuse the same elements they used in the
- // previous frame.
- ctx.save();
- ctx.restore();
- return;
- }
- // Each sprite is rendered in its own group ('g' element),
- // returned by the `ctx.save` method.
- // Essentially, the group _is_ the sprite.
- sprite.element = ctx.save();
- sprite.preRender(this);
- sprite.useAttributes(ctx, rect);
- if (false === sprite.render(this, ctx, [
- 0,
- 0,
- rect[2],
- rect[3]
- ])) {
- return false;
- }
- sprite.setDirty(false);
- ctx.restore();
- },
- /**
- * @private
- */
- toSVG: function(size, surfaces) {
- var className = Ext.getClassName(this),
- svg, surface, rect, i;
- svg = '<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg"' + ' width="' + size.width + '"' + ' height="' + size.height + '">';
- for (i = 0; i < surfaces.length; i++) {
- surface = surfaces[i];
- if (Ext.getClassName(surface) !== className) {
-
- continue;
- }
- rect = surface.getRect();
- svg += '<g transform="translate(' + rect[0] + ',' + rect[1] + ')">';
- svg += this.serializeNode(surface.svgElement.dom);
- svg += '</g>';
- }
- svg += '</svg>';
- return svg;
- },
- b64EncodeUnicode: function(str) {
- // Since DOMStrings are 16-bit-encoded strings, in most browsers calling window.btoa
- // on a Unicode string will cause a Character Out Of Range exception if a character
- // exceeds the range of a 8-bit ASCII-encoded character. More information:
- // https://developer.mozilla.org/en/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
- return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
- return String.fromCharCode('0x' + p1);
- }));
- },
- flatten: function(size, surfaces) {
- var svg = '<?xml version="1.0" standalone="yes"?>';
- svg += this.toSVG(size, surfaces);
- return {
- data: 'data:image/svg+xml;base64,' + this.b64EncodeUnicode(svg),
- type: 'svg'
- };
- },
- /**
- * @private
- * Serializes an SVG DOM element and its children recursively into a string.
- * @param {Object} node DOM element to serialize.
- * @return {String}
- */
- serializeNode: function(node) {
- var result = '',
- i, n, attr, child;
- if (node.nodeType === document.TEXT_NODE) {
- return node.nodeValue;
- }
- result += '<' + node.nodeName;
- if (node.attributes.length) {
- for (i = 0 , n = node.attributes.length; i < n; i++) {
- attr = node.attributes[i];
- result += ' ' + attr.name + '="' + Ext.String.htmlEncode(attr.value) + '"';
- }
- }
- result += '>';
- if (node.childNodes && node.childNodes.length) {
- for (i = 0 , n = node.childNodes.length; i < n; i++) {
- child = node.childNodes[i];
- result += this.serializeNode(child);
- }
- }
- result += '</' + node.nodeName + '>';
- return result;
- },
- /**
- * Destroys the Canvas element and prepares it for Garbage Collection.
- */
- destroy: function() {
- var me = this;
- me.ctx.destroy();
- me.mainGroup.destroy();
- me.defsElement.destroy();
- delete me.mainGroup;
- delete me.defsElement;
- delete me.ctx;
- me.callParent();
- },
- remove: function(sprite, destroySprite) {
- if (sprite && sprite.element) {
- // If sprite has an associated SVG element, remove it from the surface.
- sprite.element.destroy();
- sprite.element = null;
- }
- this.callParent(arguments);
- }
- });
- // @define Ext.draw.engine.excanvas
- /**
- * @private
- */
- if (!Ext.draw) {
- Ext.draw = {};
- }
- if (!Ext.draw.engine) {
- Ext.draw.engine = {};
- }
- Ext.draw.engine.excanvas = true;
- // Copyright 2006 Google Inc.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- // Known Issues:
- //
- // * Patterns only support repeat.
- // * Radial gradient are not implemented. The VML version of these look very
- // different from the canvas one.
- // * Clipping paths are not implemented.
- // * Coordsize. The width and height attribute have higher priority than the
- // width and height style values which isn't correct.
- // * Painting mode isn't implemented.
- // * Canvas width/height should is using content-box by default. IE in
- // Quirks mode will draw the canvas using border-box. Either change your
- // doctype to HTML5
- // (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
- // or use Box Sizing Behavior from WebFX
- // (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
- // * Non uniform scaling does not correctly scale strokes.
- // * Optimize. There is always room for speed improvements.
- /* eslint-disable */
- // Only add this code if we do not already have a canvas implementation
- if (!document.createElement('canvas').getContext) {
- (function() {
- // alias some functions to make (compiled) code shorter
- var m = Math;
- var mr = m.round;
- var ms = m.sin;
- var mc = m.cos;
- var abs = m.abs;
- var sqrt = m.sqrt;
- // this is used for sub pixel precision
- var Z = 10;
- var Z2 = Z / 2;
- var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];
- /*
- * @method getContext
- * This function is assigned to the <canvas></canvas> elements as element.getContext().
- * @return {CanvasRenderingContext2D_}
- */
- function getContext() {
- return this.context_ || (this.context_ = new CanvasRenderingContext2D_(this));
- }
- var slice = Array.prototype.slice;
- /*
- * @method bind
- * Binds a function to an object. The returned function will always use the
- * passed in {@code obj} as {@code this}.
- *
- * Example:
- *
- * g = bind(f, obj, a, b)
- * g(c, d) // will do f.call(obj, a, b, c, d)
- *
- * @param {Function} f The function to bind the object to
- * @param {Object} obj The object that should act as this when the function
- * is called
- * @param {*} var_args Rest arguments that will be used as the initial
- * arguments when the function is called
- * @return {Function} A new function that has bound this
- */
- function bind(f, obj, var_args) {
- var a = slice.call(arguments, 2);
- return function() {
- return f.apply(obj, a.concat(slice.call(arguments)));
- };
- }
- function encodeHtmlAttribute(s) {
- return String(s).replace(/&/g, '&').replace(/"/g, '"');
- }
- function addNamespace(doc, prefix, urn) {
- Ext.onReady(function() {
- if (!doc.namespaces[prefix]) {
- doc.namespaces.add(prefix, urn, '#default#VML');
- }
- });
- }
- function addNamespacesAndStylesheet(doc) {
- addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml');
- addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office');
- // Setup default CSS. Only add one style sheet per document
- if (!doc.styleSheets['ex_canvas_']) {
- var ss = doc.createStyleSheet();
- ss.owningElement.id = 'ex_canvas_';
- ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + // default size is 300x150 in Gecko and Opera
- 'text-align:left;width:300px;height:150px}';
- }
- }
- // Add namespaces and stylesheet at startup.
- addNamespacesAndStylesheet(document);
- var G_vmlCanvasManager_ = {
- init: function(opt_doc) {
- var doc = opt_doc || document;
- // Create a dummy element so that IE will allow canvas elements to be
- // recognized.
- doc.createElement('canvas');
- doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
- },
- init_: function(doc) {
- // find all canvas elements
- var els = doc.getElementsByTagName('canvas');
- for (var i = 0; i < els.length; i++) {
- this.initElement(els[i]);
- }
- },
- /*
- * Public initializes a canvas element so that it can be used as canvas
- * element from now on. This is called automatically before the page is
- * loaded but if you are creating elements using createElement you need to
- * make sure this is called on the element.
- * @param {HTMLElement} el The canvas element to initialize.
- * @return {HTMLElement} the element that was created.
- */
- initElement: function(el) {
- if (!el.getContext) {
- el.getContext = getContext;
- // Add namespaces and stylesheet to document of the element.
- addNamespacesAndStylesheet(el.ownerDocument);
- // Remove fallback content. There is no way to hide text nodes so we
- // just remove all childNodes. We could hide all elements and remove
- // text nodes but who really cares about the fallback content.
- el.innerHTML = '';
- // do not use inline function because that will leak memory
- el.attachEvent('onpropertychange', onPropertyChange);
- el.attachEvent('onresize', onResize);
- var attrs = el.attributes;
- if (attrs.width && attrs.width.specified) {
- // TODO: use runtimeStyle and coordsize
- // el.getContext().setWidth_(attrs.width.nodeValue);
- el.style.width = attrs.width.nodeValue + 'px';
- } else {
- el.width = el.clientWidth;
- }
- if (attrs.height && attrs.height.specified) {
- // TODO: use runtimeStyle and coordsize
- // el.getContext().setHeight_(attrs.height.nodeValue);
- el.style.height = attrs.height.nodeValue + 'px';
- } else {
- el.height = el.clientHeight;
- }
- }
- //el.getContext().setCoordsize_()
- return el;
- }
- };
- function onPropertyChange(e) {
- var el = e.srcElement;
- switch (e.propertyName) {
- case 'width':
- el.getContext().clearRect();
- el.style.width = el.attributes.width.nodeValue + 'px';
- // In IE8 this does not trigger onresize.
- el.firstChild.style.width = el.clientWidth + 'px';
- break;
- case 'height':
- el.getContext().clearRect();
- el.style.height = el.attributes.height.nodeValue + 'px';
- el.firstChild.style.height = el.clientHeight + 'px';
- break;
- }
- }
- function onResize(e) {
- var el = e.srcElement;
- if (el.firstChild) {
- el.firstChild.style.width = el.clientWidth + 'px';
- el.firstChild.style.height = el.clientHeight + 'px';
- }
- }
- G_vmlCanvasManager_.init();
- // precompute "00" to "FF"
- var decToHex = [];
- for (var i = 0; i < 16; i++) {
- for (var j = 0; j < 16; j++) {
- decToHex[i * 16 + j] = i.toString(16) + j.toString(16);
- }
- }
- function createMatrixIdentity() {
- return [
- [
- 1,
- 0,
- 0
- ],
- [
- 0,
- 1,
- 0
- ],
- [
- 0,
- 0,
- 1
- ]
- ];
- }
- function matrixMultiply(m1, m2) {
- var result = createMatrixIdentity();
- for (var x = 0; x < 3; x++) {
- for (var y = 0; y < 3; y++) {
- var sum = 0;
- for (var z = 0; z < 3; z++) {
- sum += m1[x][z] * m2[z][y];
- }
- result[x][y] = sum;
- }
- }
- return result;
- }
- function copyState(o1, o2) {
- o2.fillStyle = o1.fillStyle;
- o2.lineCap = o1.lineCap;
- o2.lineJoin = o1.lineJoin;
- o2.lineDash = o1.lineDash;
- o2.lineWidth = o1.lineWidth;
- o2.miterLimit = o1.miterLimit;
- o2.shadowBlur = o1.shadowBlur;
- o2.shadowColor = o1.shadowColor;
- o2.shadowOffsetX = o1.shadowOffsetX;
- o2.shadowOffsetY = o1.shadowOffsetY;
- o2.strokeStyle = o1.strokeStyle;
- o2.globalAlpha = o1.globalAlpha;
- o2.font = o1.font;
- o2.textAlign = o1.textAlign;
- o2.textBaseline = o1.textBaseline;
- o2.arcScaleX_ = o1.arcScaleX_;
- o2.arcScaleY_ = o1.arcScaleY_;
- o2.lineScale_ = o1.lineScale_;
- }
- var colorData = {
- aliceblue: '#F0F8FF',
- antiquewhite: '#FAEBD7',
- aquamarine: '#7FFFD4',
- azure: '#F0FFFF',
- beige: '#F5F5DC',
- bisque: '#FFE4C4',
- black: '#000000',
- blanchedalmond: '#FFEBCD',
- blueviolet: '#8A2BE2',
- brown: '#A52A2A',
- burlywood: '#DEB887',
- cadetblue: '#5F9EA0',
- chartreuse: '#7FFF00',
- chocolate: '#D2691E',
- coral: '#FF7F50',
- cornflowerblue: '#6495ED',
- cornsilk: '#FFF8DC',
- crimson: '#DC143C',
- cyan: '#00FFFF',
- darkblue: '#00008B',
- darkcyan: '#008B8B',
- darkgoldenrod: '#B8860B',
- darkgray: '#A9A9A9',
- darkgreen: '#006400',
- darkgrey: '#A9A9A9',
- darkkhaki: '#BDB76B',
- darkmagenta: '#8B008B',
- darkolivegreen: '#556B2F',
- darkorange: '#FF8C00',
- darkorchid: '#9932CC',
- darkred: '#8B0000',
- darksalmon: '#E9967A',
- darkseagreen: '#8FBC8F',
- darkslateblue: '#483D8B',
- darkslategray: '#2F4F4F',
- darkslategrey: '#2F4F4F',
- darkturquoise: '#00CED1',
- darkviolet: '#9400D3',
- deeppink: '#FF1493',
- deepskyblue: '#00BFFF',
- dimgray: '#696969',
- dimgrey: '#696969',
- dodgerblue: '#1E90FF',
- firebrick: '#B22222',
- floralwhite: '#FFFAF0',
- forestgreen: '#228B22',
- gainsboro: '#DCDCDC',
- ghostwhite: '#F8F8FF',
- gold: '#FFD700',
- goldenrod: '#DAA520',
- grey: '#808080',
- greenyellow: '#ADFF2F',
- honeydew: '#F0FFF0',
- hotpink: '#FF69B4',
- indianred: '#CD5C5C',
- indigo: '#4B0082',
- ivory: '#FFFFF0',
- khaki: '#F0E68C',
- lavender: '#E6E6FA',
- lavenderblush: '#FFF0F5',
- lawngreen: '#7CFC00',
- lemonchiffon: '#FFFACD',
- lightblue: '#ADD8E6',
- lightcoral: '#F08080',
- lightcyan: '#E0FFFF',
- lightgoldenrodyellow: '#FAFAD2',
- lightgreen: '#90EE90',
- lightgrey: '#D3D3D3',
- lightpink: '#FFB6C1',
- lightsalmon: '#FFA07A',
- lightseagreen: '#20B2AA',
- lightskyblue: '#87CEFA',
- lightslategray: '#778899',
- lightslategrey: '#778899',
- lightsteelblue: '#B0C4DE',
- lightyellow: '#FFFFE0',
- limegreen: '#32CD32',
- linen: '#FAF0E6',
- magenta: '#FF00FF',
- mediumaquamarine: '#66CDAA',
- mediumblue: '#0000CD',
- mediumorchid: '#BA55D3',
- mediumpurple: '#9370DB',
- mediumseagreen: '#3CB371',
- mediumslateblue: '#7B68EE',
- mediumspringgreen: '#00FA9A',
- mediumturquoise: '#48D1CC',
- mediumvioletred: '#C71585',
- midnightblue: '#191970',
- mintcream: '#F5FFFA',
- mistyrose: '#FFE4E1',
- moccasin: '#FFE4B5',
- navajowhite: '#FFDEAD',
- oldlace: '#FDF5E6',
- olivedrab: '#6B8E23',
- orange: '#FFA500',
- orangered: '#FF4500',
- orchid: '#DA70D6',
- palegoldenrod: '#EEE8AA',
- palegreen: '#98FB98',
- paleturquoise: '#AFEEEE',
- palevioletred: '#DB7093',
- papayawhip: '#FFEFD5',
- peachpuff: '#FFDAB9',
- peru: '#CD853F',
- pink: '#FFC0CB',
- plum: '#DDA0DD',
- powderblue: '#B0E0E6',
- rosybrown: '#BC8F8F',
- royalblue: '#4169E1',
- saddlebrown: '#8B4513',
- salmon: '#FA8072',
- sandybrown: '#F4A460',
- seagreen: '#2E8B57',
- seashell: '#FFF5EE',
- sienna: '#A0522D',
- skyblue: '#87CEEB',
- slateblue: '#6A5ACD',
- slategray: '#708090',
- slategrey: '#708090',
- snow: '#FFFAFA',
- springgreen: '#00FF7F',
- steelblue: '#4682B4',
- tan: '#D2B48C',
- thistle: '#D8BFD8',
- tomato: '#FF6347',
- turquoise: '#40E0D0',
- violet: '#EE82EE',
- wheat: '#F5DEB3',
- whitesmoke: '#F5F5F5',
- yellowgreen: '#9ACD32'
- };
- function getRgbHslContent(styleString) {
- var start = styleString.indexOf('(', 3);
- var end = styleString.indexOf(')', start + 1);
- var parts = styleString.substring(start + 1, end).split(',');
- // add alpha if needed
- if (parts.length != 4 || styleString.charAt(3) != 'a') {
- parts[3] = 1;
- }
- return parts;
- }
- function percent(s) {
- return parseFloat(s) / 100;
- }
- function clamp(v, min, max) {
- return Math.min(max, Math.max(min, v));
- }
- function hslToRgb(parts) {
- var r, g, b, h, s, l;
- h = parseFloat(parts[0]) / 360 % 360;
- if (h < 0) {
- h++;
- }
-
- s = clamp(percent(parts[1]), 0, 1);
- l = clamp(percent(parts[2]), 0, 1);
- if (s == 0) {
- r = g = b = l;
- } else // achromatic
- {
- var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
- var p = 2 * l - q;
- r = hueToRgb(p, q, h + 1 / 3);
- g = hueToRgb(p, q, h);
- b = hueToRgb(p, q, h - 1 / 3);
- }
- return '#' + decToHex[Math.floor(r * 255)] + decToHex[Math.floor(g * 255)] + decToHex[Math.floor(b * 255)];
- }
- function hueToRgb(m1, m2, h) {
- if (h < 0) {
- h++;
- }
-
- if (h > 1) {
- h--;
- }
-
- if (6 * h < 1) {
- return m1 + (m2 - m1) * 6 * h;
- }
- else if (2 * h < 1) {
- return m2;
- }
- else if (3 * h < 2) {
- return m1 + (m2 - m1) * (2 / 3 - h) * 6;
- }
- else {
- return m1;
- }
-
- }
- var processStyleCache = {};
- function processStyle(styleString) {
- if (styleString in processStyleCache) {
- return processStyleCache[styleString];
- }
- var str,
- alpha = 1;
- styleString = String(styleString);
- if (styleString.charAt(0) == '#') {
- str = styleString;
- } else if (/^rgb/.test(styleString)) {
- var parts = getRgbHslContent(styleString);
- var str = '#',
- n;
- for (var i = 0; i < 3; i++) {
- if (parts[i].indexOf('%') != -1) {
- n = Math.floor(percent(parts[i]) * 255);
- } else {
- n = +parts[i];
- }
- str += decToHex[clamp(n, 0, 255)];
- }
- alpha = +parts[3];
- } else if (/^hsl/.test(styleString)) {
- var parts = getRgbHslContent(styleString);
- str = hslToRgb(parts);
- alpha = parts[3];
- } else {
- str = colorData[styleString] || styleString;
- }
- return processStyleCache[styleString] = {
- color: str,
- alpha: alpha
- };
- }
- var DEFAULT_STYLE = {
- style: 'normal',
- variant: 'normal',
- weight: 'normal',
- size: 10,
- family: 'sans-serif'
- };
- // Internal text style cache
- var fontStyleCache = {};
- function processFontStyle(styleString) {
- if (fontStyleCache[styleString]) {
- return fontStyleCache[styleString];
- }
- var el = document.createElement('div');
- var style = el.style;
- try {
- style.font = styleString;
- } catch (ex) {}
- // Ignore failures to set to invalid font.
- return fontStyleCache[styleString] = {
- style: style.fontStyle || DEFAULT_STYLE.style,
- variant: style.fontVariant || DEFAULT_STYLE.variant,
- weight: style.fontWeight || DEFAULT_STYLE.weight,
- size: style.fontSize || DEFAULT_STYLE.size,
- family: style.fontFamily || DEFAULT_STYLE.family
- };
- }
- function getComputedStyle(style, element) {
- var computedStyle = {};
- for (var p in style) {
- computedStyle[p] = style[p];
- }
- // Compute the size
- var canvasFontSize = parseFloat(element.currentStyle.fontSize),
- fontSize = parseFloat(style.size);
- if (typeof style.size == 'number') {
- computedStyle.size = style.size;
- } else if (style.size.indexOf('px') != -1) {
- computedStyle.size = fontSize;
- } else if (style.size.indexOf('em') != -1) {
- computedStyle.size = canvasFontSize * fontSize;
- } else if (style.size.indexOf('%') != -1) {
- computedStyle.size = (canvasFontSize / 100) * fontSize;
- } else if (style.size.indexOf('pt') != -1) {
- computedStyle.size = fontSize / 0.75;
- } else {
- computedStyle.size = canvasFontSize;
- }
- // Different scaling between normal text and VML text. This was found using
- // trial and error to get the same size as non VML text.
- computedStyle.size *= 0.981;
- return computedStyle;
- }
- function buildStyle(style) {
- return style.style + ' ' + style.variant + ' ' + style.weight + ' ' + style.size + 'px ' + style.family;
- }
- var lineCapMap = {
- 'butt': 'flat',
- 'round': 'round'
- };
- function processLineCap(lineCap) {
- return lineCapMap[lineCap] || 'square';
- }
- /*
- * This class implements CanvasRenderingContext2D interface as described by
- * the WHATWG.
- * @param {HTMLElement} canvasElement The element that the 2D context should
- * be associated with
- * @private
- */
- function CanvasRenderingContext2D_(canvasElement) {
- this.m_ = createMatrixIdentity();
- this.mStack_ = [];
- this.aStack_ = [];
- this.currentPath_ = [];
- // Canvas context properties
- this.strokeStyle = '#000';
- this.fillStyle = '#000';
- this.lineWidth = 1;
- this.lineJoin = 'miter';
- this.lineDash = [];
- this.lineCap = 'butt';
- this.miterLimit = Z * 1;
- this.globalAlpha = 1;
- this.font = '10px sans-serif';
- this.textAlign = 'left';
- this.textBaseline = 'alphabetic';
- this.canvas = canvasElement;
- var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' + canvasElement.clientHeight + 'px;overflow:hidden;position:absolute';
- var el = canvasElement.ownerDocument.createElement('div');
- el.style.cssText = cssText;
- canvasElement.appendChild(el);
- var overlayEl = el.cloneNode(false);
- // Use a non transparent background.
- overlayEl.style.backgroundColor = 'red';
- overlayEl.style.filter = 'alpha(opacity=0)';
- canvasElement.appendChild(overlayEl);
- this.element_ = el;
- this.arcScaleX_ = 1;
- this.arcScaleY_ = 1;
- this.lineScale_ = 1;
- }
- var contextPrototype = CanvasRenderingContext2D_.prototype;
- contextPrototype.clearRect = function() {
- if (this.textMeasureEl_) {
- this.textMeasureEl_.removeNode(true);
- this.textMeasureEl_ = null;
- }
- this.element_.innerHTML = '';
- };
- contextPrototype.beginPath = function() {
- // TODO: Branch current matrix so that save/restore has no effect
- // as per safari docs.
- this.currentPath_ = [];
- };
- contextPrototype.moveTo = function(aX, aY) {
- var p = getCoords(this, aX, aY);
- this.currentPath_.push({
- type: 'moveTo',
- x: p.x,
- y: p.y
- });
- this.currentX_ = p.x;
- this.currentY_ = p.y;
- };
- contextPrototype.lineTo = function(aX, aY) {
- var p = getCoords(this, aX, aY);
- this.currentPath_.push({
- type: 'lineTo',
- x: p.x,
- y: p.y
- });
- this.currentX_ = p.x;
- this.currentY_ = p.y;
- };
- contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) {
- var p = getCoords(this, aX, aY);
- var cp1 = getCoords(this, aCP1x, aCP1y);
- var cp2 = getCoords(this, aCP2x, aCP2y);
- bezierCurveTo(this, cp1, cp2, p);
- };
- // Helper function that takes the already fixed cordinates.
- function bezierCurveTo(self, cp1, cp2, p) {
- self.currentPath_.push({
- type: 'bezierCurveTo',
- cp1x: cp1.x,
- cp1y: cp1.y,
- cp2x: cp2.x,
- cp2y: cp2.y,
- x: p.x,
- y: p.y
- });
- self.currentX_ = p.x;
- self.currentY_ = p.y;
- }
- contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
- // the following is lifted almost directly from
- // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
- var cp = getCoords(this, aCPx, aCPy);
- var p = getCoords(this, aX, aY);
- var cp1 = {
- x: this.currentX_ + 2 / 3 * (cp.x - this.currentX_),
- y: this.currentY_ + 2 / 3 * (cp.y - this.currentY_)
- };
- var cp2 = {
- x: cp1.x + (p.x - this.currentX_) / 3,
- y: cp1.y + (p.y - this.currentY_) / 3
- };
- bezierCurveTo(this, cp1, cp2, p);
- };
- contextPrototype.arc = function(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) {
- aRadius *= Z;
- var arcType = aClockwise ? 'at' : 'wa';
- var xStart = aX + mc(aStartAngle) * aRadius - Z2;
- var yStart = aY + ms(aStartAngle) * aRadius - Z2;
- var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
- var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
- // IE won't render arches drawn counter clockwise if xStart == xEnd.
- if (xStart == xEnd && !aClockwise) {
- xStart += 0.125;
- }
- // Offset xStart by 1/80 of a pixel. Use something
- // that can be represented in binary
- var p = getCoords(this, aX, aY);
- var pStart = getCoords(this, xStart, yStart);
- var pEnd = getCoords(this, xEnd, yEnd);
- this.currentPath_.push({
- type: arcType,
- x: p.x,
- y: p.y,
- radius: aRadius,
- xStart: pStart.x,
- yStart: pStart.y,
- xEnd: pEnd.x,
- yEnd: pEnd.y
- });
- };
- contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
- this.moveTo(aX, aY);
- this.lineTo(aX + aWidth, aY);
- this.lineTo(aX + aWidth, aY + aHeight);
- this.lineTo(aX, aY + aHeight);
- this.closePath();
- };
- contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
- var oldPath = this.currentPath_;
- this.beginPath();
- this.moveTo(aX, aY);
- this.lineTo(aX + aWidth, aY);
- this.lineTo(aX + aWidth, aY + aHeight);
- this.lineTo(aX, aY + aHeight);
- this.closePath();
- this.stroke();
- this.currentPath_ = oldPath;
- };
- contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
- var oldPath = this.currentPath_;
- this.beginPath();
- this.moveTo(aX, aY);
- this.lineTo(aX + aWidth, aY);
- this.lineTo(aX + aWidth, aY + aHeight);
- this.lineTo(aX, aY + aHeight);
- this.closePath();
- this.fill();
- this.currentPath_ = oldPath;
- };
- contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
- var gradient = new CanvasGradient_('gradient');
- gradient.x0_ = aX0;
- gradient.y0_ = aY0;
- gradient.x1_ = aX1;
- gradient.y1_ = aY1;
- return gradient;
- };
- contextPrototype.createRadialGradient = function(aX0, aY0, aR0, aX1, aY1, aR1) {
- var gradient = new CanvasGradient_('gradientradial');
- gradient.x0_ = aX0;
- gradient.y0_ = aY0;
- gradient.r0_ = aR0;
- gradient.x1_ = aX1;
- gradient.y1_ = aY1;
- gradient.r1_ = aR1;
- return gradient;
- };
- contextPrototype.drawImage = function(image, var_args) {
- var dx, dy, dw, dh, sx, sy, sw, sh;
- // to find the original width we overide the width and height
- var oldRuntimeWidth = image.runtimeStyle.width;
- var oldRuntimeHeight = image.runtimeStyle.height;
- image.runtimeStyle.width = 'auto';
- image.runtimeStyle.height = 'auto';
- // get the original size
- var w = image.width;
- var h = image.height;
- // and remove overides
- image.runtimeStyle.width = oldRuntimeWidth;
- image.runtimeStyle.height = oldRuntimeHeight;
- if (arguments.length == 3) {
- dx = arguments[1];
- dy = arguments[2];
- sx = sy = 0;
- sw = dw = w;
- sh = dh = h;
- } else if (arguments.length == 5) {
- dx = arguments[1];
- dy = arguments[2];
- dw = arguments[3];
- dh = arguments[4];
- sx = sy = 0;
- sw = w;
- sh = h;
- } else if (arguments.length == 9) {
- sx = arguments[1];
- sy = arguments[2];
- sw = arguments[3];
- sh = arguments[4];
- dx = arguments[5];
- dy = arguments[6];
- dw = arguments[7];
- dh = arguments[8];
- } else {
- throw Error('Invalid number of arguments');
- }
- var d = getCoords(this, dx, dy);
- var vmlStr = [];
- var W = 10;
- var H = 10;
- var m = this.m_;
- 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), ';');
- 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>');
- this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
- };
- contextPrototype.setLineDash = function(lineDash) {
- if (lineDash.length === 1) {
- lineDash = lineDash.slice();
- lineDash[1] = lineDash[0];
- }
- this.lineDash = lineDash;
- };
- contextPrototype.getLineDash = function() {
- return this.lineDash;
- };
- contextPrototype.stroke = function(aFill) {
- var lineStr = [];
- var W = 10;
- var H = 10;
- 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="');
- var min = {
- x: null,
- y: null
- };
- var max = {
- x: null,
- y: null
- };
- for (var i = 0; i < this.currentPath_.length; i++) {
- var p = this.currentPath_[i];
- var c;
- switch (p.type) {
- case 'moveTo':
- c = p;
- lineStr.push(' m ', mr(p.x), ',', mr(p.y));
- break;
- case 'lineTo':
- lineStr.push(' l ', mr(p.x), ',', mr(p.y));
- break;
- case 'close':
- lineStr.push(' x ');
- p = null;
- break;
- case 'bezierCurveTo':
- lineStr.push(' c ', mr(p.cp1x), ',', mr(p.cp1y), ',', mr(p.cp2x), ',', mr(p.cp2y), ',', mr(p.x), ',', mr(p.y));
- break;
- case 'at':
- case 'wa':
- 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));
- break;
- }
- // TODO: Following is broken for curves due to
- // move to proper paths.
- // Figure out dimensions so we can do gradient fills
- // properly
- if (p) {
- if (min.x == null || p.x < min.x) {
- min.x = p.x;
- }
- if (max.x == null || p.x > max.x) {
- max.x = p.x;
- }
- if (min.y == null || p.y < min.y) {
- min.y = p.y;
- }
- if (max.y == null || p.y > max.y) {
- max.y = p.y;
- }
- }
- }
- lineStr.push(' ">');
- if (!aFill) {
- appendStroke(this, lineStr);
- } else {
- appendFill(this, lineStr, min, max);
- }
- lineStr.push('</g_vml_:shape>');
- this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
- };
- function appendStroke(ctx, lineStr) {
- var a = processStyle(ctx.strokeStyle);
- var color = a.color;
- var opacity = a.alpha * ctx.globalAlpha;
- var lineWidth = ctx.lineScale_ * ctx.lineWidth;
- // VML cannot correctly render a line if the width is less than 1px.
- // In that case, we dilute the color to make the line look thinner.
- if (lineWidth < 1) {
- opacity *= lineWidth;
- }
- 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, '" />');
- }
- function appendFill(ctx, lineStr, min, max) {
- var fillStyle = ctx.fillStyle;
- var arcScaleX = ctx.arcScaleX_;
- var arcScaleY = ctx.arcScaleY_;
- var width = max.x - min.x;
- var height = max.y - min.y;
- if (fillStyle instanceof CanvasGradient_) {
- // TODO: Gradients transformed with the transformation matrix.
- var angle = 0;
- var focus = {
- x: 0,
- y: 0
- };
- // additional offset
- var shift = 0;
- // scale factor for offset
- var expansion = 1;
- if (fillStyle.type_ == 'gradient') {
- var x0 = fillStyle.x0_ / arcScaleX;
- var y0 = fillStyle.y0_ / arcScaleY;
- var x1 = fillStyle.x1_ / arcScaleX;
- var y1 = fillStyle.y1_ / arcScaleY;
- var p0 = getCoords(ctx, x0, y0);
- var p1 = getCoords(ctx, x1, y1);
- var dx = p1.x - p0.x;
- var dy = p1.y - p0.y;
- angle = Math.atan2(dx, dy) * 180 / Math.PI;
- // The angle should be a non-negative number.
- if (angle < 0) {
- angle += 360;
- }
- // Very small angles produce an unexpected result because they are
- // converted to a scientific notation string.
- if (angle < 1.0E-6) {
- angle = 0;
- }
- } else {
- var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_);
- focus = {
- x: (p0.x - min.x) / width,
- y: (p0.y - min.y) / height
- };
- width /= arcScaleX * Z;
- height /= arcScaleY * Z;
- var dimension = m.max(width, height);
- shift = 2 * fillStyle.r0_ / dimension;
- expansion = 2 * fillStyle.r1_ / dimension - shift;
- }
- // We need to sort the color stops in ascending order by offset,
- // otherwise IE won't interpret it correctly.
- var stops = fillStyle.colors_;
- stops.sort(function(cs1, cs2) {
- return cs1.offset - cs2.offset;
- });
- var length = stops.length;
- var color1 = stops[0].color;
- var color2 = stops[length - 1].color;
- var opacity1 = stops[0].alpha * ctx.globalAlpha;
- var opacity2 = stops[length - 1].alpha * ctx.globalAlpha;
- var colors = [];
- for (var i = 0; i < length; i++) {
- var stop = stops[i];
- colors.push(stop.offset * expansion + shift + ' ' + stop.color);
- }
- // When colors attribute is used, the meanings of opacity and o:opacity2
- // are reversed.
- 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, '" />');
- } else if (fillStyle instanceof CanvasPattern_) {
- if (width && height) {
- var deltaLeft = -min.x;
- var deltaTop = -min.y;
- 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.
- //' size="', w, 'px ', h, 'px"',
- ' src="', fillStyle.src_, '" />');
- }
- } else {
- var a = processStyle(ctx.fillStyle);
- var color = a.color;
- var opacity = a.alpha * ctx.globalAlpha;
- lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />');
- }
- }
- contextPrototype.fill = function() {
- // Calling `$stroke` here because otherwise we'd call not the native ctx.stroke,
- // but our override from Ext.draw.engine.Canvas.statics.contextOverrides.
- this.$stroke(true);
- };
- contextPrototype.closePath = function() {
- this.currentPath_.push({
- type: 'close'
- });
- };
- function getCoords(ctx, aX, aY) {
- var m = ctx.m_;
- return {
- x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
- y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
- };
- }
- contextPrototype.save = function() {
- var o = {};
- copyState(this, o);
- this.aStack_.push(o);
- this.mStack_.push(this.m_);
- };
- contextPrototype.restore = function() {
- if (this.aStack_.length) {
- copyState(this.aStack_.pop(), this);
- this.m_ = this.mStack_.pop();
- }
- };
- function matrixIsFinite(m) {
- 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]);
- }
- function setM(ctx, m, updateLineScale) {
- if (!matrixIsFinite(m)) {
- return;
- }
- ctx.m_ = m;
- if (updateLineScale) {
- // Get the line scale.
- // Determinant of this.m_ means how much the area is enlarged by the
- // transformation. So its square root can be used as a scale factor
- // for width.
- var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
- ctx.lineScale_ = sqrt(abs(det));
- }
- }
- contextPrototype.translate = function(aX, aY) {
- var m1 = [
- [
- 1,
- 0,
- 0
- ],
- [
- 0,
- 1,
- 0
- ],
- [
- aX,
- aY,
- 1
- ]
- ];
- setM(this, matrixMultiply(m1, this.m_), false);
- };
- contextPrototype.rotate = function(aRot) {
- var c = mc(aRot);
- var s = ms(aRot);
- var m1 = [
- [
- c,
- s,
- 0
- ],
- [
- -s,
- c,
- 0
- ],
- [
- 0,
- 0,
- 1
- ]
- ];
- setM(this, matrixMultiply(m1, this.m_), false);
- };
- contextPrototype.scale = function(aX, aY) {
- this.arcScaleX_ *= aX;
- this.arcScaleY_ *= aY;
- var m1 = [
- [
- aX,
- 0,
- 0
- ],
- [
- 0,
- aY,
- 0
- ],
- [
- 0,
- 0,
- 1
- ]
- ];
- setM(this, matrixMultiply(m1, this.m_), true);
- };
- contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
- var m1 = [
- [
- m11,
- m12,
- 0
- ],
- [
- m21,
- m22,
- 0
- ],
- [
- dx,
- dy,
- 1
- ]
- ];
- setM(this, matrixMultiply(m1, this.m_), true);
- };
- contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
- var m = [
- [
- m11,
- m12,
- 0
- ],
- [
- m21,
- m22,
- 0
- ],
- [
- dx,
- dy,
- 1
- ]
- ];
- setM(this, m, true);
- };
- /*
- * The text drawing function.
- * The maxWidth argument isn't taken in account, since no browser supports
- * it yet.
- */
- contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
- var m = this.m_,
- delta = 1000,
- left = 0,
- right = delta,
- offset = {
- x: 0,
- y: 0
- },
- lineStr = [];
- var fontStyle = getComputedStyle(processFontStyle(this.font), this.element_);
- var fontStyleString = buildStyle(fontStyle);
- var elementStyle = this.element_.currentStyle;
- var textAlign = this.textAlign.toLowerCase();
- switch (textAlign) {
- case 'left':
- case 'center':
- case 'right':
- break;
- case 'end':
- textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
- break;
- case 'start':
- textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
- break;
- default:
- textAlign = 'left';
- }
- // 1.75 is an arbitrary number, as there is no info about the text baseline
- switch (this.textBaseline) {
- case 'hanging':
- case 'top':
- offset.y = fontStyle.size / 1.75;
- break;
- case 'middle':
- break;
- default:
- case null:
- case 'alphabetic':
- case 'ideographic':
- case 'bottom':
- offset.y = -fontStyle.size / 3;
- break;
- }
- switch (textAlign) {
- case 'right':
- left = delta;
- right = 0.05;
- break;
- case 'center':
- left = right = delta / 2;
- break;
- }
- var d = getCoords(this, x + offset.x, y + offset.y);
- 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;">');
- if (stroke) {
- appendStroke(this, lineStr);
- } else {
- // TODO: Fix the min and max params.
- appendFill(this, lineStr, {
- x: -left,
- y: 0
- }, {
- x: right,
- y: fontStyle.size
- });
- }
- var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' + m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0';
- var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z);
- 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>');
- this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
- };
- contextPrototype.fillText = function(text, x, y, maxWidth) {
- this.drawText_(text, x, y, maxWidth, false);
- };
- contextPrototype.strokeText = function(text, x, y, maxWidth) {
- this.drawText_(text, x, y, maxWidth, true);
- };
- contextPrototype.measureText = function(text) {
- if (!this.textMeasureEl_) {
- var s = '<span style="position:absolute;' + 'top:-20000px;left:0;padding:0;margin:0;border:none;' + 'white-space:pre;"></span>';
- this.element_.insertAdjacentHTML('beforeEnd', s);
- this.textMeasureEl_ = this.element_.lastChild;
- }
- var doc = this.element_.ownerDocument;
- this.textMeasureEl_.innerHTML = '';
- this.textMeasureEl_.style.font = this.font;
- // Don't use innerHTML or innerText because they allow markup/whitespace.
- this.textMeasureEl_.appendChild(doc.createTextNode(text));
- return {
- width: this.textMeasureEl_.offsetWidth
- };
- };
- /* STUBS */
- contextPrototype.clip = function() {};
- // TODO: Implement
- contextPrototype.arcTo = function() {};
- // TODO: Implement
- contextPrototype.createPattern = function(image, repetition) {
- return new CanvasPattern_(image, repetition);
- };
- // Gradient / Pattern Stubs
- function CanvasGradient_(aType) {
- this.type_ = aType;
- this.x0_ = 0;
- this.y0_ = 0;
- this.r0_ = 0;
- this.x1_ = 0;
- this.y1_ = 0;
- this.r1_ = 0;
- this.colors_ = [];
- }
- CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
- aColor = processStyle(aColor);
- this.colors_.push({
- offset: aOffset,
- color: aColor.color,
- alpha: aColor.alpha
- });
- };
- function CanvasPattern_(image, repetition) {
- assertImageIsValid(image);
- switch (repetition) {
- case 'repeat':
- case null:
- case '':
- this.repetition_ = 'repeat';
- break;
- case 'repeat-x':
- case 'repeat-y':
- case 'no-repeat':
- this.repetition_ = repetition;
- break;
- default:
- throwException('SYNTAX_ERR');
- }
- this.src_ = image.src;
- this.width_ = image.width;
- this.height_ = image.height;
- }
- function throwException(s) {
- throw new DOMException_(s);
- }
- function assertImageIsValid(img) {
- if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
- throwException('TYPE_MISMATCH_ERR');
- }
- if (img.readyState != 'complete') {
- throwException('INVALID_STATE_ERR');
- }
- }
- function DOMException_(s) {
- this.code = this[s];
- this.message = s + ': DOM Exception ' + this.code;
- }
- var p = DOMException_.prototype = new Error();
- p.INDEX_SIZE_ERR = 1;
- p.DOMSTRING_SIZE_ERR = 2;
- p.HIERARCHY_REQUEST_ERR = 3;
- p.WRONG_DOCUMENT_ERR = 4;
- p.INVALID_CHARACTER_ERR = 5;
- p.NO_DATA_ALLOWED_ERR = 6;
- p.NO_MODIFICATION_ALLOWED_ERR = 7;
- p.NOT_FOUND_ERR = 8;
- p.NOT_SUPPORTED_ERR = 9;
- p.INUSE_ATTRIBUTE_ERR = 10;
- p.INVALID_STATE_ERR = 11;
- p.SYNTAX_ERR = 12;
- p.INVALID_MODIFICATION_ERR = 13;
- p.NAMESPACE_ERR = 14;
- p.INVALID_ACCESS_ERR = 15;
- p.VALIDATION_ERR = 16;
- p.TYPE_MISMATCH_ERR = 17;
- // set up externs
- G_vmlCanvasManager = G_vmlCanvasManager_;
- CanvasRenderingContext2D = CanvasRenderingContext2D_;
- CanvasGradient = CanvasGradient_;
- CanvasPattern = CanvasPattern_;
- DOMException = DOMException_;
- })();
- }
- // if
- /* global G_vmlCanvasManager */
- /**
- * Provides specific methods to draw with 2D Canvas element.
- */
- Ext.define('Ext.draw.engine.Canvas', {
- extend: 'Ext.draw.Surface',
- isCanvas: true,
- requires: [
- //<feature legacyBrowser>
- 'Ext.draw.engine.excanvas',
- //</feature>
- 'Ext.draw.Animator',
- 'Ext.draw.Color'
- ],
- config: {
- /**
- * @cfg {Boolean} highPrecision
- * True to have the Canvas use JavaScript Number instead of single precision floating point
- * for transforms.
- *
- * For example, when using data with big numbers to plot line series, the transformation
- * matrix of the canvas will have big elements. Due to the implementation of the SVGMatrix,
- * the elements are represented by 32-bits floats, which will work incorrectly.
- * To compensate for that, we enable the canvas context to perform all the transformations
- * in JavaScript.
- *
- * Do not use this if you are not encountering 32-bit floating point errors problem,
- * since this will result in a performance penalty.
- */
- highPrecision: false
- },
- statics: {
- contextOverrides: {
- /**
- * @ignore
- */
- setGradientBBox: function(bbox) {
- this.bbox = bbox;
- },
- /**
- * Fills the subpaths of the current default path or the given path with the current
- * fill style.
- * @ignore
- */
- fill: function() {
- var fillStyle = this.fillStyle,
- fillGradient = this.fillGradient,
- fillOpacity = this.fillOpacity,
- alpha = this.globalAlpha,
- bbox = this.bbox;
- if (fillStyle !== Ext.util.Color.RGBA_NONE && fillOpacity !== 0) {
- if (fillGradient && bbox) {
- this.fillStyle = fillGradient.generateGradient(this, bbox);
- }
- if (fillOpacity !== 1) {
- this.globalAlpha = alpha * fillOpacity;
- }
- this.$fill();
- if (fillOpacity !== 1) {
- this.globalAlpha = alpha;
- }
- if (fillGradient && bbox) {
- this.fillStyle = fillStyle;
- }
- }
- },
- /**
- * Strokes the subpaths of the current default path or the given path with the current
- * stroke style.
- * @ignore
- */
- stroke: function() {
- var strokeStyle = this.strokeStyle,
- strokeGradient = this.strokeGradient,
- strokeOpacity = this.strokeOpacity,
- alpha = this.globalAlpha,
- bbox = this.bbox;
- if (strokeStyle !== Ext.util.Color.RGBA_NONE && strokeOpacity !== 0) {
- if (strokeGradient && bbox) {
- this.strokeStyle = strokeGradient.generateGradient(this, bbox);
- }
- if (strokeOpacity !== 1) {
- this.globalAlpha = alpha * strokeOpacity;
- }
- this.$stroke();
- if (strokeOpacity !== 1) {
- this.globalAlpha = alpha;
- }
- if (strokeGradient && bbox) {
- this.strokeStyle = strokeStyle;
- }
- }
- },
- /**
- * @ignore
- */
- fillStroke: function(attr, transformFillStroke) {
- var ctx = this,
- fillStyle = this.fillStyle,
- fillOpacity = this.fillOpacity,
- strokeStyle = this.strokeStyle,
- strokeOpacity = this.strokeOpacity,
- shadowColor = ctx.shadowColor,
- shadowBlur = ctx.shadowBlur,
- none = Ext.util.Color.RGBA_NONE;
- if (transformFillStroke === undefined) {
- transformFillStroke = attr.transformFillStroke;
- }
- if (!transformFillStroke) {
- attr.inverseMatrix.toContext(ctx);
- }
- if (fillStyle !== none && fillOpacity !== 0) {
- ctx.fill();
- ctx.shadowColor = none;
- ctx.shadowBlur = 0;
- }
- if (strokeStyle !== none && strokeOpacity !== 0) {
- ctx.stroke();
- }
- ctx.shadowColor = shadowColor;
- ctx.shadowBlur = shadowBlur;
- },
- /**
- * 2D Canvas context in IE (up to IE10, inclusive) doesn't support
- * the setLineDash method and the lineDashOffset property.
- * @param dashList An even number of non-negative numbers specifying a dash list.
- */
- setLineDash: function(dashList) {
- if (this.$setLineDash) {
- this.$setLineDash(dashList);
- }
- },
- getLineDash: function() {
- if (this.$getLineDash) {
- return this.$getLineDash();
- }
- },
- /**
- * 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.
- * @ignore
- */
- ellipse: function(cx, cy, rx, ry, rotation, start, end, anticlockwise) {
- var cos = Math.cos(rotation),
- sin = Math.sin(rotation);
- this.transform(cos * rx, sin * rx, -sin * ry, cos * ry, cx, cy);
- this.arc(0, 0, 1, start, end, anticlockwise);
- this.transform(cos / rx, -sin / ry, sin / rx, cos / ry, -(cos * cx + sin * cy) / rx, (sin * cx - cos * cy) / ry);
- },
- /**
- * Uses the given path commands to begin a new path on the canvas.
- * @ignore
- */
- appendPath: function(path) {
- var me = this,
- i = 0,
- j = 0,
- commands = path.commands,
- params = path.params,
- ln = commands.length;
- me.beginPath();
- for (; i < ln; i++) {
- switch (commands[i]) {
- case 'M':
- me.moveTo(params[j], params[j + 1]);
- j += 2;
- break;
- case 'L':
- me.lineTo(params[j], params[j + 1]);
- j += 2;
- break;
- case 'C':
- me.bezierCurveTo(params[j], params[j + 1], params[j + 2], params[j + 3], params[j + 4], params[j + 5]);
- j += 6;
- break;
- case 'Z':
- me.closePath();
- break;
- }
- }
- },
- save: function() {
- var toSave = this.toSave,
- ln = toSave.length,
- obj = ln && {},
- // Don't allocate memory if we don't have to.
- i = 0,
- key;
- for (; i < ln; i++) {
- key = toSave[i];
- if (key in this) {
- obj[key] = this[key];
- }
- }
- this.state.push(obj);
- this.$save();
- },
- restore: function() {
- var obj = this.state.pop(),
- key;
- if (obj) {
- for (key in obj) {
- this[key] = obj[key];
- }
- }
- this.$restore();
- }
- }
- },
- splitThreshold: 3000,
- /**
- * @private
- * Properties to be saved/restored in the `save` and `restore` methods.
- */
- toSave: [
- 'fillGradient',
- 'strokeGradient'
- ],
- /**
- * @property element
- * @inheritdoc
- */
- element: {
- reference: 'element',
- children: [
- {
- reference: 'bodyElement',
- style: {
- width: '100%',
- height: '100%',
- position: 'relative'
- }
- }
- ]
- },
- /**
- * @private
- *
- * Creates the canvas element.
- */
- createCanvas: function() {
- var canvas, overrides, ctx, name;
- canvas = Ext.Element.create({
- tag: 'canvas',
- cls: Ext.baseCSSPrefix + 'surface-canvas'
- });
- // Emulate Canvas in IE8 with VML.
- if (window.G_vmlCanvasManager) {
- G_vmlCanvasManager.initElement(canvas.dom);
- this.isVML = true;
- }
- overrides = Ext.draw.engine.Canvas.contextOverrides;
- ctx = canvas.dom.getContext('2d');
- if (ctx.ellipse) {
- delete overrides.ellipse;
- }
- ctx.state = [];
- ctx.toSave = this.toSave;
- // Saving references to the native Canvas context methods that we'll be overriding.
- for (name in overrides) {
- ctx['$' + name] = ctx[name];
- }
- Ext.apply(ctx, overrides);
- if (this.getHighPrecision()) {
- this.enablePrecisionCompensation(ctx);
- } else {
- this.disablePrecisionCompensation(ctx);
- }
- this.bodyElement.appendChild(canvas);
- this.canvases.push(canvas);
- this.contexts.push(ctx);
- },
- updateHighPrecision: function(highPrecision) {
- var contexts = this.contexts,
- ln = contexts.length,
- i, context;
- for (i = 0; i < ln; i++) {
- context = contexts[i];
- if (highPrecision) {
- this.enablePrecisionCompensation(context);
- } else {
- this.disablePrecisionCompensation(context);
- }
- }
- },
- precisionNames: [
- 'rect',
- 'fillRect',
- 'strokeRect',
- 'clearRect',
- 'moveTo',
- 'lineTo',
- 'arc',
- 'arcTo',
- 'save',
- 'restore',
- 'updatePrecisionCompensate',
- 'setTransform',
- 'transform',
- 'scale',
- 'translate',
- 'rotate',
- 'quadraticCurveTo',
- 'bezierCurveTo',
- 'createLinearGradient',
- 'createRadialGradient',
- 'fillText',
- 'strokeText',
- 'drawImage'
- ],
- /**
- * @private
- * Clears canvas of compensation for canvas' use of single precision floating point.
- * @param {CanvasRenderingContext2D} ctx The canvas context.
- */
- disablePrecisionCompensation: function(ctx) {
- var regularOverrides = Ext.draw.engine.Canvas.contextOverrides,
- precisionOverrides = this.precisionNames,
- ln = precisionOverrides.length,
- i, name;
- for (i = 0; i < ln; i++) {
- name = precisionOverrides[i];
- if (!(name in regularOverrides)) {
- delete ctx[name];
- }
- }
- this.setDirty(true);
- },
- /**
- * @private
- * Compensate for canvas' use of single precision floating point.
- * @param {CanvasRenderingContext2D} ctx The canvas context.
- */
- enablePrecisionCompensation: function(ctx) {
- var surface = this,
- xx = 1,
- yy = 1,
- dx = 0,
- dy = 0,
- matrix = new Ext.draw.Matrix(),
- transStack = [],
- comp = {},
- regularOverrides = Ext.draw.engine.Canvas.contextOverrides,
- originalCtx = ctx.constructor.prototype,
- /**
- * @cfg {Object} precisionOverrides
- * @ignore
- */
- precisionOverrides = {
- toSave: surface.toSave,
- /**
- * Adds a new closed subpath to the path, representing the given rectangle.
- * @return {*}
- * @ignore
- */
- rect: function(x, y, w, h) {
- return originalCtx.rect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
- },
- /**
- * Paints the given rectangle onto the canvas, using the current fill style.
- * @ignore
- */
- fillRect: function(x, y, w, h) {
- this.updatePrecisionCompensateRect();
- originalCtx.fillRect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
- this.updatePrecisionCompensate();
- },
- /**
- * Paints the box that outlines the given rectangle onto the canvas, using
- * the current stroke style.
- * @ignore
- */
- strokeRect: function(x, y, w, h) {
- this.updatePrecisionCompensateRect();
- originalCtx.strokeRect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
- this.updatePrecisionCompensate();
- },
- /**
- * Clears all pixels on the canvas in the given rectangle to transparent black.
- * @ignore
- */
- clearRect: function(x, y, w, h) {
- return originalCtx.clearRect.call(this, x * xx + dx, y * yy + dy, w * xx, h * yy);
- },
- /**
- * Creates a new subpath with the given point.
- * @ignore
- */
- moveTo: function(x, y) {
- return originalCtx.moveTo.call(this, x * xx + dx, y * yy + dy);
- },
- /**
- * Adds the given point to the current subpath, connected to the previous one
- * by a straight line.
- * @ignore
- */
- lineTo: function(x, y) {
- return originalCtx.lineTo.call(this, x * xx + dx, y * yy + dy);
- },
- /**
- * 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.
- * @ignore
- */
- arc: function(x, y, radius, startAngle, endAngle, anticlockwise) {
- this.updatePrecisionCompensateRect();
- originalCtx.arc.call(this, x * xx + dx, y * xx + dy, radius * xx, startAngle, endAngle, anticlockwise);
- this.updatePrecisionCompensate();
- },
- /**
- * Adds an arc with the given control points and radius to the current subpath,
- * connected to the previous point by a straight line. 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.
- *
- * In the case of an ellipse, the rotation argument controls the clockwise
- * inclination of the ellipse relative to the x-axis.
- * @ignore
- */
- arcTo: function(x1, y1, x2, y2, radius) {
- this.updatePrecisionCompensateRect();
- originalCtx.arcTo.call(this, x1 * xx + dx, y1 * yy + dy, x2 * xx + dx, y2 * yy + dy, radius * xx);
- this.updatePrecisionCompensate();
- },
- /**
- * Pushes the context state to the state stack.
- * @ignore
- */
- save: function() {
- transStack.push(matrix);
- matrix = matrix.clone();
- regularOverrides.save.call(this);
- originalCtx.save.call(this);
- },
- /**
- * Pops the state stack and restores the state.
- * @ignore
- */
- restore: function() {
- matrix = transStack.pop();
- regularOverrides.restore.call(this);
- originalCtx.restore.call(this);
- this.updatePrecisionCompensate();
- },
- /**
- * @ignore
- */
- updatePrecisionCompensate: function() {
- matrix.precisionCompensate(surface.devicePixelRatio, comp);
- xx = comp.xx;
- yy = comp.yy;
- dx = comp.dx;
- dy = comp.dy;
- originalCtx.setTransform.call(this, surface.devicePixelRatio, comp.b, comp.c, comp.d, 0, 0);
- },
- /**
- * @ignore
- */
- updatePrecisionCompensateRect: function() {
- matrix.precisionCompensateRect(surface.devicePixelRatio, comp);
- xx = comp.xx;
- yy = comp.yy;
- dx = comp.dx;
- dy = comp.dy;
- originalCtx.setTransform.call(this, surface.devicePixelRatio, comp.b, comp.c, comp.d, 0, 0);
- },
- /**
- * Changes the transformation matrix to the matrix given by the arguments
- * as described below.
- * @ignore
- */
- setTransform: function(x2x, x2y, y2x, y2y, newDx, newDy) {
- matrix.set(x2x, x2y, y2x, y2y, newDx, newDy);
- this.updatePrecisionCompensate();
- },
- /**
- * Changes the transformation matrix to apply the matrix given by the arguments
- * as described below.
- * @ignore
- */
- transform: function(x2x, x2y, y2x, y2y, newDx, newDy) {
- matrix.append(x2x, x2y, y2x, y2y, newDx, newDy);
- this.updatePrecisionCompensate();
- },
- /**
- * Scales the transformation matrix.
- * @return {*}
- * @ignore
- */
- scale: function(sx, sy) {
- this.transform(sx, 0, 0, sy, 0, 0);
- },
- /**
- * Translates the transformation matrix.
- * @return {*}
- * @ignore
- */
- translate: function(dx, dy) {
- this.transform(1, 0, 0, 1, dx, dy);
- },
- /**
- * Rotates the transformation matrix.
- * @return {*}
- * @ignore
- */
- rotate: function(radians) {
- var cos = Math.cos(radians),
- sin = Math.sin(radians);
- this.transform(cos, sin, -sin, cos, 0, 0);
- },
- /**
- * Adds the given point to the current subpath, connected to the previous one by a
- * quadratic Bézier curve with the given control point.
- * @return {*}
- * @ignore
- */
- quadraticCurveTo: function(cx, cy, x, y) {
- originalCtx.quadraticCurveTo.call(this, cx * xx + dx, cy * yy + dy, x * xx + dx, y * yy + dy);
- },
- /**
- * Adds the given point to the current subpath, connected to the previous one
- * by a cubic Bézier curve with the given control points.
- * @return {*}
- * @ignore
- */
- bezierCurveTo: function(c1x, c1y, c2x, c2y, x, y) {
- originalCtx.bezierCurveTo.call(this, c1x * xx + dx, c1y * yy + dy, c2x * xx + dx, c2y * yy + dy, x * xx + dx, y * yy + dy);
- },
- /**
- * Returns an object that represents a linear gradient that paints along the line
- * given by the coordinates represented by the arguments.
- * @return {*}
- * @ignore
- */
- createLinearGradient: function(x0, y0, x1, y1) {
- var grad;
- this.updatePrecisionCompensateRect();
- grad = originalCtx.createLinearGradient.call(this, x0 * xx + dx, y0 * yy + dy, x1 * xx + dx, y1 * yy + dy);
- this.updatePrecisionCompensate();
- return grad;
- },
- /**
- * Returns a CanvasGradient object that represents a radial gradient that paints
- * along the cone given by the circles represented by the arguments. If either
- * of the radii are negative, throws an IndexSizeError exception.
- * @return {*}
- * @ignore
- */
- createRadialGradient: function(x0, y0, r0, x1, y1, r1) {
- var grad;
- this.updatePrecisionCompensateRect();
- grad = originalCtx.createLinearGradient.call(this, x0 * xx + dx, y0 * xx + dy, r0 * xx, x1 * xx + dx, y1 * xx + dy, r1 * xx);
- this.updatePrecisionCompensate();
- return grad;
- },
- /**
- * 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.
- * @ignore
- */
- fillText: function(text, x, y, maxWidth) {
- originalCtx.setTransform.apply(this, matrix.elements);
- if (typeof maxWidth === 'undefined') {
- originalCtx.fillText.call(this, text, x, y);
- } else {
- originalCtx.fillText.call(this, text, x, y, maxWidth);
- }
- this.updatePrecisionCompensate();
- },
- /**
- * 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.
- * @ignore
- */
- strokeText: function(text, x, y, maxWidth) {
- originalCtx.setTransform.apply(this, matrix.elements);
- if (typeof maxWidth === 'undefined') {
- originalCtx.strokeText.call(this, text, x, y);
- } else {
- originalCtx.strokeText.call(this, text, x, y, maxWidth);
- }
- this.updatePrecisionCompensate();
- },
- /**
- * Fills the subpaths of the current default path or the given path with the current
- * fill style.
- * @ignore
- */
- fill: function() {
- var fillGradient = this.fillGradient,
- bbox = this.bbox;
- this.updatePrecisionCompensateRect();
- if (fillGradient && bbox) {
- this.fillStyle = fillGradient.generateGradient(this, bbox);
- }
- originalCtx.fill.call(this);
- this.updatePrecisionCompensate();
- },
- /**
- * Strokes the subpaths of the current default path or the given path with the
- * current stroke style.
- * @ignore
- */
- stroke: function() {
- var strokeGradient = this.strokeGradient,
- bbox = this.bbox;
- this.updatePrecisionCompensateRect();
- if (strokeGradient && bbox) {
- this.strokeStyle = strokeGradient.generateGradient(this, bbox);
- }
- originalCtx.stroke.call(this);
- this.updatePrecisionCompensate();
- },
- /**
- * Draws the given image onto the canvas. 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.
- * @return {*}
- * @ignore
- */
- drawImage: function(img_elem, arg1, arg2, arg3, arg4, dst_x, dst_y, dw, dh) {
- switch (arguments.length) {
- case 3:
- return originalCtx.drawImage.call(this, img_elem, arg1 * xx + dx, arg2 * yy + dy);
- case 5:
- return originalCtx.drawImage.call(this, img_elem, arg1 * xx + dx, arg2 * yy + dy, arg3 * xx, arg4 * yy);
- case 9:
- return originalCtx.drawImage.call(this, img_elem, arg1, arg2, arg3, arg4, dst_x * xx + dx, dst_y * yy * dy, dw * xx, dh * yy);
- }
- }
- };
- Ext.apply(ctx, precisionOverrides);
- this.setDirty(true);
- },
- /**
- * Normally, a surface will have a single canvas.
- * However, on certain platforms/browsers there's a limit to how big a canvas can be.
- * 'splitThreshold' is used to determine maximum width/height of a single canvas element.
- * When a surface is wider/taller than the splitThreshold, extra canvas element(s)
- * will be created and tiled inside the surface.
- */
- updateRect: function(rect) {
- this.callParent([
- rect
- ]);
- // eslint-disable-next-line vars-on-top
- var me = this,
- l = Math.floor(rect[0]),
- t = Math.floor(rect[1]),
- r = Math.ceil(rect[0] + rect[2]),
- b = Math.ceil(rect[1] + rect[3]),
- devicePixelRatio = me.devicePixelRatio,
- canvases = me.canvases,
- w = r - l,
- h = b - t,
- splitThreshold = Math.round(me.splitThreshold / devicePixelRatio),
- xSplits = me.xSplits = Math.ceil(w / splitThreshold),
- ySplits = me.ySplits = Math.ceil(h / splitThreshold),
- i, j, k, offsetX, offsetY, dom, width, height;
- for (j = 0 , offsetY = 0; j < ySplits; j++ , offsetY += splitThreshold) {
- for (i = 0 , offsetX = 0; i < xSplits; i++ , offsetX += splitThreshold) {
- k = j * xSplits + i;
- if (k >= canvases.length) {
- me.createCanvas();
- }
- dom = canvases[k].dom;
- dom.style.left = offsetX + 'px';
- dom.style.top = offsetY + 'px';
- // The Canvas doesn't automatically support hi-DPI displays.
- // We have to actually create a larger canvas (more pixels)
- // while keeping its physical size the same.
- height = Math.min(splitThreshold, h - offsetY);
- if (height * devicePixelRatio !== dom.height) {
- dom.height = height * devicePixelRatio;
- dom.style.height = height + 'px';
- }
- width = Math.min(splitThreshold, w - offsetX);
- if (width * devicePixelRatio !== dom.width) {
- dom.width = width * devicePixelRatio;
- dom.style.width = width + 'px';
- }
- me.applyDefaults(me.contexts[k]);
- }
- }
- me.activeCanvases = k = xSplits * ySplits;
- while (canvases.length > k) {
- canvases.pop().destroy();
- }
- me.clear();
- },
- /**
- * @method clearTransform
- * @inheritdoc
- */
- clearTransform: function() {
- var me = this,
- xSplits = me.xSplits,
- ySplits = me.ySplits,
- contexts = me.contexts,
- splitThreshold = me.splitThreshold,
- devicePixelRatio = me.devicePixelRatio,
- i, j, k, ctx;
- for (i = 0; i < xSplits; i++) {
- for (j = 0; j < ySplits; j++) {
- k = j * xSplits + i;
- ctx = contexts[k];
- ctx.translate(-splitThreshold * i, -splitThreshold * j);
- ctx.scale(devicePixelRatio, devicePixelRatio);
- me.matrix.toContext(ctx);
- }
- }
- },
- /**
- * @method renderSprite
- * @inheritdoc
- */
- renderSprite: function(sprite) {
- var me = this,
- rect = me.getRect(),
- surfaceMatrix = me.matrix,
- parent = sprite.getParent(),
- matrix = Ext.draw.Matrix.fly([
- 1,
- 0,
- 0,
- 1,
- 0,
- 0
- ]),
- splitThreshold = me.splitThreshold / me.devicePixelRatio,
- xSplits = me.xSplits,
- ySplits = me.ySplits,
- offsetX, offsetY, ctx, bbox, width, height,
- left = 0,
- right,
- top = 0,
- bottom,
- w = rect[2],
- h = rect[3],
- i, j, k;
- while (parent && parent.isSprite) {
- matrix.prependMatrix(parent.matrix || parent.attr && parent.attr.matrix);
- parent = parent.getParent();
- }
- matrix.prependMatrix(surfaceMatrix);
- bbox = sprite.getBBox();
- if (bbox) {
- bbox = matrix.transformBBox(bbox);
- }
- sprite.preRender(me);
- if (sprite.attr.hidden || sprite.attr.globalAlpha === 0) {
- sprite.setDirty(false);
- return;
- }
- // Render this sprite on all Canvas elements it spans, skipping the rest.
- for (j = 0 , offsetY = 0; j < ySplits; j++ , offsetY += splitThreshold) {
- for (i = 0 , offsetX = 0; i < xSplits; i++ , offsetX += splitThreshold) {
- k = j * xSplits + i;
- ctx = me.contexts[k];
- width = Math.min(splitThreshold, w - offsetX);
- height = Math.min(splitThreshold, h - offsetY);
- left = offsetX;
- right = left + width;
- top = offsetY;
- bottom = top + height;
- if (bbox) {
- if (bbox.x > right || bbox.x + bbox.width < left || bbox.y > bottom || bbox.y + bbox.height < top) {
-
- continue;
- }
- }
- ctx.save();
- sprite.useAttributes(ctx, rect);
- if (false === sprite.render(me, ctx, [
- left,
- top,
- width,
- height
- ])) {
- return false;
- }
- ctx.restore();
- }
- }
- sprite.setDirty(false);
- },
- flatten: function(size, surfaces) {
- var targetCanvas = document.createElement('canvas'),
- className = Ext.getClassName(this),
- ratio = this.devicePixelRatio,
- ctx = targetCanvas.getContext('2d'),
- surface, canvas, rect, i, j, xy;
- targetCanvas.width = Math.ceil(size.width * ratio);
- targetCanvas.height = Math.ceil(size.height * ratio);
- for (i = 0; i < surfaces.length; i++) {
- surface = surfaces[i];
- if (Ext.getClassName(surface) !== className) {
-
- continue;
- }
- rect = surface.getRect();
- for (j = 0; j < surface.canvases.length; j++) {
- canvas = surface.canvases[j];
- xy = canvas.getOffsetsTo(canvas.getParent());
- ctx.drawImage(canvas.dom, (rect[0] + xy[0]) * ratio, (rect[1] + xy[1]) * ratio);
- }
- }
- return {
- data: targetCanvas.toDataURL(),
- type: 'png'
- };
- },
- applyDefaults: function(ctx) {
- var none = Ext.util.Color.RGBA_NONE;
- ctx.strokeStyle = none;
- ctx.fillStyle = none;
- ctx.textAlign = 'start';
- ctx.textBaseline = 'alphabetic';
- ctx.miterLimit = 1;
- },
- /**
- * @method clear
- * @inheritdoc
- */
- clear: function() {
- var me = this,
- activeCanvases = me.activeCanvases,
- i, canvas, ctx;
- for (i = 0; i < activeCanvases; i++) {
- canvas = me.canvases[i].dom;
- ctx = me.contexts[i];
- ctx.setTransform(1, 0, 0, 1, 0, 0);
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- }
- me.setDirty(true);
- },
- /**
- * Destroys the Canvas element and prepares it for Garbage Collection.
- */
- destroy: function() {
- var me = this,
- canvases = me.canvases,
- ln = canvases.length,
- i;
- for (i = 0; i < ln; i++) {
- me.contexts[i] = null;
- canvases[i].destroy();
- canvases[i] = null;
- }
- me.contexts = me.canvases = null;
- me.callParent();
- },
- privates: {
- initElement: function() {
- var me = this;
- me.callParent();
- me.canvases = [];
- me.contexts = [];
- me.activeCanvases = me.xSplits = me.ySplits = 0;
- }
- }
- }, function() {
- var me = this,
- proto = me.prototype,
- splitThreshold = 1.0E10;
- if (Ext.os.is.Android4 && Ext.browser.is.Chrome) {
- splitThreshold = 3000;
- } else if (Ext.is.iOS) {
- splitThreshold = 2200;
- }
- proto.splitThreshold = splitThreshold;
- });
- /**
- * The container that holds and manages instances of the {@link Ext.draw.Surface}
- * in which {@link Ext.draw.sprite.Sprite sprites} are rendered. Draw containers are
- * used as the foundation for all of the chart classes but may also be created directly
- * in order to create custom drawings.
- *
- * @example
- * var drawContainer = Ext.create('Ext.draw.Container', {
- * renderTo: Ext.getBody(),
- * width:200,
- * height:200,
- * sprites: [{
- * type: 'circle',
- * fillStyle: '#79BB3F',
- * r: 100,
- * x: 100,
- * y: 100
- * }]
- * });
- *
- * // Uncomment to trigger a download of the painted circle.
- * // drawContainer.download({
- * // filename: 'Circle',
- * // url: 'http://svg.sencha.io' // Default server the image data is sent to.
- * // });
- *
- * In the previous example we created a draw container and configured it with a single
- * sprite. The *type* of the sprite is {@link Ext.draw.sprite.Circle circle}, so if you
- * run this code you'll see a green circle.
- *
- * You can attach sprite event listeners to the draw container with the help of the
- * {@link Ext.draw.plugin.SpriteEvents} plugin.
- *
- * For more information on sprites, the core elements added to a draw container's
- * surface, refer to the Ext.draw.sprite.Sprite documentation.
- *
- * For more information on surfaces, the interface owned by the draw container used to
- * manage all sprites, see the Ext.draw.Surface documentation.
- */
- Ext.define('Ext.draw.Container', {
- extend: 'Ext.draw.ContainerBase',
- alternateClassName: 'Ext.draw.Component',
- xtype: 'draw',
- defaultType: 'surface',
- isDrawContainer: true,
- requires: [
- 'Ext.draw.Surface',
- 'Ext.draw.engine.Svg',
- 'Ext.draw.engine.Canvas',
- 'Ext.draw.gradient.GradientDefinition'
- ],
- /**
- * @cfg {String} [engine="Ext.draw.engine.Canvas"]
- * Defines the engine (type of surface) used to render draw container contents.
- *
- * The render engine is selected automatically depending on the platform used. Priority
- * is given to the {@link Ext.draw.engine.Canvas} engine due to its performance advantage.
- *
- * You may also set the engine config to be `Ext.draw.engine.Svg` if so desired.
- */
- engine: 'Ext.draw.engine.Canvas',
- /**
- * @event spritemousemove
- * Fires when the mouse is moved on a sprite.
- * @param {Object} sprite
- * @param {Event} event
- */
- /**
- * @event spritemouseup
- * Fires when a mouseup event occurs on a sprite.
- * @param {Object} sprite
- * @param {Event} event
- */
- /**
- * @event spritemousedown
- * Fires when a mousedown event occurs on a sprite.
- * @param {Object} sprite
- * @param {Event} event
- */
- /**
- * @event spritemouseover
- * Fires when the mouse enters a sprite.
- * @param {Object} sprite
- * @param {Event} event
- */
- /**
- * @event spritemouseout
- * Fires when the mouse exits a sprite.
- * @param {Object} sprite
- * @param {Event} event
- */
- /**
- * @event spriteclick
- * Fires when a click event occurs on a sprite.
- * @param {Object} sprite
- * @param {Event} event
- */
- /**
- * @event spritedblclick
- * Fires when a double click event occurs on a sprite.
- * @param {Object} sprite
- * @param {Event} event
- */
- /**
- * @event spritetap
- * Fires when a tap event occurs on a sprite.
- * @param {Object} sprite
- * @param {Event} event
- */
- /**
- * @event bodyresize
- * Fires when the size of the draw container body changes.
- * @param {Object} size The object containing 'width' and 'height' of the draw container's body.
- */
- config: {
- cls: [
- Ext.baseCSSPrefix + 'draw-container',
- Ext.baseCSSPrefix + 'unselectable'
- ],
- /**
- * @cfg {Function} [resizeHandler]
- * The resize function that can be configured to have a behavior,
- * e.g. resize draw surfaces based on new draw container dimensions.
- * The `resizeHandler` function takes a single parameter -
- * the size object with `width` and `height` properties.
- *
- * **Note:** Since resize events trigger {@link #renderFrame} calls automatically,
- * return `false` from the resize function, if it also calls `renderFrame`,
- * to prevent double rendering.
- */
- resizeHandler: null,
- /**
- * @cfg {Object[]} sprites
- * Defines a set of sprites to be added to the drawContainer surface.
- *
- * For example:
- *
- * sprites: [{
- * type: 'circle',
- * fillStyle: '#79BB3F',
- * r: 100,
- * x: 100,
- * y: 100
- * }]
- *
- */
- sprites: null,
- /**
- * @cfg {Object[]} gradients
- * Defines a set of gradients that can be used as color properties
- * (fillStyle and strokeStyle, but not shadowColor) in sprites.
- * The gradients array is an array of objects with the following properties:
- * - **id** - string - The unique name of the gradient.
- * - **type** - string, optional - The type of the gradient. Available types are: 'linear',
- * 'radial'. Defaults to 'linear'.
- * - **angle** - number, optional - The angle of the gradient in degrees.
- * - **stops** - array - An array of objects with 'color' and 'offset' properties, where
- * 'offset' is a real number from 0 to 1.
- *
- * For example:
- *
- * gradients: [{
- * id: 'gradientId1',
- * type: 'linear',
- * angle: 45,
- * stops: [{
- * offset: 0,
- * color: 'red'
- * }, {
- * offset: 1,
- * color: 'yellow'
- * }]
- * }, {
- * id: 'gradientId2',
- * type: 'radial',
- * stops: [{
- * offset: 0,
- * color: '#555',
- * }, {
- * offset: 1,
- * color: '#ddd',
- * }]
- * }]
- *
- * Then the sprites can use 'gradientId1' and 'gradientId2' by setting the color attributes
- * to those ids, for example:
- *
- * sprite.setAttributes({
- * fillStyle: 'url(#gradientId1)',
- * strokeStyle: 'url(#gradientId2)'
- * });
- */
- gradients: [],
- /**
- * @cfg {String} downloadServerUrl
- * The default URL used by the {@link #download} method.
- */
- downloadServerUrl: undefined,
- touchAction: {
- panX: false,
- panY: false,
- pinchZoom: false,
- doubleTapZoom: false
- },
- /**
- * @private
- * @cfg {Object} surfaceZIndexes A map of surface type name to zIndex.
- * The z-indexes to use for the various types of surfaces.
- */
- surfaceZIndexes: {
- main: 1
- }
- },
- /**
- * @private
- * @property {String} [defaultDownloadServerUrl="http://svg.sencha.io"]
- * The default URL used by the {@link #download} method if the {@link #downloadServerUrl}
- * config wasn't set.
- * To override this globally, set the `Ext.draw.Container.prototype.defaultDownloadServerUrl`.
- */
- defaultDownloadServerUrl: 'http://svg.sencha.io',
- /**
- * @property {Array} [supportedFormats=["png", "pdf", "jpeg", "gif"]]
- * A list of export types supported by the server.
- * @private
- */
- supportedFormats: [
- 'png',
- 'pdf',
- 'jpeg',
- 'gif'
- ],
- supportedOptions: {
- version: Ext.isNumber,
- data: Ext.isString,
- format: function(format) {
- return Ext.Array.indexOf(this.supportedFormats, format) >= 0;
- },
- filename: Ext.isString,
- width: Ext.isNumber,
- height: Ext.isNumber,
- scale: Ext.isNumber,
- pdf: Ext.isObject,
- jpeg: Ext.isObject
- },
- initAnimator: function() {
- this.frameCallbackId = Ext.draw.Animator.addFrameCallback('renderFrame', this);
- },
- applyDownloadServerUrl: function(url) {
- var defaultUrl = this.defaultDownloadServerUrl;
- if (!url) {
- url = defaultUrl;
- //<debug>
- // Skip this warning when unit testing.
- if (!window.jasmine) {
- 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() + ')');
- }
- }
- //</debug>
- return url;
- },
- applyGradients: function(gradients) {
- var result = [],
- i, n, gradient, offset;
- if (!Ext.isArray(gradients)) {
- return result;
- }
- for (i = 0 , n = gradients.length; i < n; i++) {
- gradient = gradients[i];
- if (!Ext.isObject(gradient)) {
-
- continue;
- }
- // ExtJS only supported linear gradients, so we didn't have to specify their type
- if (typeof gradient.type !== 'string') {
- gradient.type = 'linear';
- }
- if (gradient.angle) {
- gradient.degrees = gradient.angle;
- delete gradient.angle;
- }
- // Convert ExtJS stops object to Touch stops array
- if (Ext.isObject(gradient.stops)) {
- gradient.stops = (function(stops) {
- var result = [],
- stop;
- for (offset in stops) {
- stop = stops[offset];
- stop.offset = offset / 100;
- result.push(stop);
- }
- return result;
- })(gradient.stops);
- }
- result.push(gradient);
- }
- Ext.draw.gradient.GradientDefinition.add(result);
- return result;
- },
- applySprites: function(sprites) {
- var result, surface, sprite, i, ln;
- // Never update.
- if (!sprites) {
- return;
- }
- sprites = Ext.Array.from(sprites);
- result = [];
- for (i = 0 , ln = sprites.length; i < ln; i++) {
- sprite = sprites[i];
- surface = sprite.surface;
- if (!(surface && surface.isSurface)) {
- if (Ext.isString(surface)) {
- surface = this.getSurface(surface);
- delete sprite.surface;
- } else {
- surface = this.getSurface('main');
- }
- }
- sprite = surface.add(sprite);
- result.push(sprite);
- }
- return result;
- },
- resizeDelay: 500,
- // in milliseconds
- resizeTimerId: 0,
- lastResizeTime: null,
- /**
- * @private
- * @property
- * Last valid size.
- */
- size: null,
- /**
- * Triggers the {@link #resizeHandler} with the size of the draw container
- * element as the parameter.
- */
- handleResize: function(size, instantly) {
- // See the following:
- // Classic: Ext.draw.ContainerBase.reattachToBody
- // Modern: Ext.draw.ContainerBase.initialize
- var me = this,
- el = me.element,
- resizeHandler = me.getResizeHandler() || me.defaultResizeHandler,
- resizeDelay = me.resizeDelay,
- lastResizeTime = me.lastResizeTime,
- defer, result;
- if (!el) {
- return;
- }
- size = size || el.getSize();
- if (!(size.width && size.height)) {
- return;
- }
- me.size = size;
- me.stopResizeTimer();
- // Only want to defer when multiple resize events happen in quick succession.
- // That way it doesn't feel luggy during an occasional resize, nor it's too straining
- // when continuously resizing.
- defer = !instantly && lastResizeTime && (Ext.Date.now() - lastResizeTime < resizeDelay);
- if (defer) {
- me.resizeTimerId = Ext.defer(me.handleResize, resizeDelay, me, [
- size,
- true
- ]);
- return;
- }
- me.fireEvent('bodyresize', me, size);
- Ext.callback(resizeHandler, null, [
- size
- ], 0, me);
- if (result !== false) {
- me.renderFrame();
- }
- me.lastResizeTime = Ext.Date.now();
- },
- /**
- * @private
- */
- stopResizeTimer: function() {
- if (this.resizeTimerId) {
- Ext.undefer(this.resizeTimerId);
- this.resizeTimerId = 0;
- }
- },
- defaultResizeHandler: function(size) {
- this.getItems().each(function(surface) {
- surface.setRect([
- 0,
- 0,
- size.width,
- size.height
- ]);
- });
- },
- /**
- * Get a surface by the given id or create one if it doesn't exist.
- * This will automatically call the {@link #resizeHandler}. Which
- * means that, if no custom resize handler has been provided, the
- * surface will be sized to match the container.
- * If the {@link #method!add} method is used, it is the responsibility
- * of the user to call the {@link #handleResize} method, to update
- * the size of all added surfaces.
- * @param {String} [id="main"]
- * @param {String} type
- * @return {Ext.draw.Surface}
- */
- getSurface: function(id, type) {
- var me = this,
- surfaces = me.getItems(),
- oldCount = surfaces.getCount(),
- zIndexes = me.getSurfaceZIndexes(),
- surface;
- id = id || 'main';
- type = type || id;
- surface = me.createSurface(id);
- if (type in zIndexes) {
- surface.element.setStyle('zIndex', zIndexes[type]);
- }
- if (surfaces.getCount() > oldCount) {
- // Immediately call resize handler of the draw container,
- // so that the newly created surface gets a size.
- me.handleResize(null, true);
- }
- return surface;
- },
- createSurface: function(id) {
- var me = this,
- surfaces = me.getItems(),
- surface;
- id = this.getId() + '-' + (id || 'main');
- surface = surfaces.get(id);
- if (!surface) {
- surface = me.add({
- xclass: me.engine,
- id: id
- });
- }
- return surface;
- },
- /**
- * Render all the surfaces in the container.
- */
- renderFrame: function() {
- var me = this,
- surfaces = me.getItems(),
- i, ln, item;
- for (i = 0 , ln = surfaces.length; i < ln; i++) {
- item = surfaces.items[i];
- if (item.isSurface) {
- item.renderFrame();
- }
- }
- },
- /**
- * @private
- * Returns a slice of the surfaces (items) array of the draw container,
- * optionally sorting them by zIndex.
- * Overridden in subclasses.
- */
- getSurfaces: function(sort) {
- var surfaces = Array.prototype.slice.call(this.items.items),
- zIndexes = this.getSurfaceZIndexes(),
- i, j, surface, zIndex;
- if (sort) {
- // Sort the surfaces by zIndex using insertion sort.
- for (j = 1; j < surfaces.length; j++) {
- surface = surfaces[j];
- zIndex = zIndexes[surface.type];
- i = j - 1;
- while (i >= 0 && zIndexes[surfaces[i].type] > zIndex) {
- surfaces[i + 1] = surfaces[i];
- i--;
- }
- surfaces[i + 1] = surface;
- }
- }
- return surfaces;
- },
- /**
- * Produces an image of the chart / drawing.
- * @param {String} [format] Possible options are 'image' (the method will return an
- * Image object) and 'stream' (the method will return the image as a byte stream).
- * If missing, the data URI of the drawing's (or chart's) image will be returned.
- * Note: for an SVG based drawing/chart in IE/Edge browsers the method will always
- * return SVG markup instead of a data URI, as 'img' elements won't accept a data
- * URI anyway in those browsers.
- * @return {Object}
- * @return {String} return.data Image element, byte stream or DataURL.
- * @return {String} return.type The type of the data (e.g. 'png' or 'svg').
- */
- getImage: function(format) {
- var size = this.bodyElement.getSize(),
- surfaces = this.getSurfaces(true),
- surface = surfaces[0],
- image, imageElement;
- if ((Ext.isIE || Ext.isEdge) && surface.isSVG) {
- // SVG data URLs don't work in IE/Edge as a source for an 'img' element,
- // so we need to render SVG the usual way.
- image = {
- data: surface.toSVG(size, surfaces),
- type: 'svg-markup'
- };
- } else {
- image = surface.flatten(size, surfaces);
- if (format === 'image') {
- imageElement = new Image();
- imageElement.src = image.data;
- image.data = imageElement;
- return image;
- }
- if (format === 'stream') {
- image.data = image.data.replace(/^data:image\/[^;]+/, 'data:application/octet-stream');
- return image;
- }
- }
- return image;
- },
- /**
- * Downloads an image or PDF of the chart / drawing or opens it in a separate
- * browser tab/window if the download can't be triggered. The exact behavior is
- * platform and browser specific. For more consistent results on mobile devices use
- * the {@link #preview} method instead. This method doesn't work in IE8.
- *
- * Important: The default download mechanism sends image data to `http://svg.sencha.io`,
- * which is a server operated by Sencha. This can be changed by setting
- * the {@link #downloadServerUrl} config to the address of another server.
- *
- * You can deploy your own server by using the code from the `server` directory
- * in the Charts package. The server is Node.js based and uses PhantomJS to
- * generate images and PDFs from received data.
- *
- * The warning that the default download server is used can be suppressed
- * by explicitly setting the value of the {@link #downloadServerUrl} config
- * to `http://svg.sencha.io`.
- *
- * @param {Object} [config] The following config options are supported:
- *
- * @param {String} config.url The url to post the data to. Defaults to
- * the value of the {@link #downloadServerUrl} config.
- *
- * @param {String} config.format The format of image to export. See the
- * {@link #supportedFormats}. Defaults to 'png' on the Sencha IO server.
- * Note that you can't export to 'svg' format if the {@link Ext.draw.engine.Canvas Canvas}
- * {@link Ext.draw.Container#engine engine} is used.
- *
- * @param {Number} config.width A width to send to the server for
- * configuring the image width. Defaults to natural image width on
- * the Sencha IO server.
- *
- * @param {Number} config.height A height to send to the server for
- * configuring the image height. Defaults to natural image height on
- * the Sencha IO server.
- *
- * @param {String} config.filename The filename of the downloaded image.
- * Defaults to 'chart' on the Sencha IO server. The config.format is used
- * as a filename extension.
- *
- * @param {Number} config.scale The scaling of the downloaded image.
- * Defaults to 1 on the Sencha IO server. The server will try to determine the natural
- * size of the image unless the width/height configs have been set. If the
- * {@link Ext.draw.engine.Canvas Canvas} {@link Ext.draw.Container#engine engine} is
- * used the natural image size will depend on the value of the window.devicePixelRatio.
- * For example, for devices with devicePixelRatio of 2 the produced image will be
- * two times larger than for devices with devicePixelRatio of 1 for the same drawing.
- * This is done so that the users with devices with HiDPI screens get a downloaded
- * image that looks as crisp on their device as the original drawing.
- * If you want image size to be consistent across devices with different device
- * pixel ratios, you can set the value of this config to 1/devicePixelRatio.
- * This parameter is ignored by the Sencha IO server if config.format is set to 'svg'.
- *
- * @param {Object} config.pdf PDF specific options.
- * This config is only used if config.format is set to 'pdf'.
- * The given object should be in either this format:
- *
- * {
- * width: '200px',
- * height: '300px',
- * border: '0px'
- * }
- *
- * or this format:
- *
- * {
- * format: 'A4',
- * orientation: 'portrait',
- * border: '1cm'
- * }
- *
- * Supported dimension units are: 'mm', 'cm', 'in', 'px'. No unit means 'px'.
- * Supported formats are: 'A3', 'A4', 'A5', 'Legal', 'Letter', 'Tabloid'.
- * Orientation ('portrait', 'landscape') is optional and defaults to 'portrait'.
- *
- * @param {Object} config.jpeg JPEG specific options.
- * This config is only used if config.format is set to 'jpeg'.
- * The given object should be in this format:
- *
- * {
- * quality: 80
- * }
- *
- * Where quality is an integer between 0 and 100.
- *
- * @return {Boolean} True if request was successfully sent to the server.
- */
- download: function(config) {
- var me = this,
- inputs = [],
- markup, name, value;
- if (Ext.isIE8) {
- return false;
- }
- config = config || {};
- config.version = 2;
- if (!config.data) {
- config.data = me.getImage().data;
- }
- for (name in config) {
- if (config.hasOwnProperty(name)) {
- value = config[name];
- if (name in me.supportedOptions) {
- if (me.supportedOptions[name].call(me, value)) {
- inputs.push({
- tag: 'input',
- type: 'hidden',
- name: name,
- value: Ext.String.htmlEncode(Ext.isObject(value) ? Ext.JSON.encode(value) : value)
- });
- } else //<debug>
- {
- Ext.log.error('Invalid value for image download option "' + name + '": ' + value);
- }
- } else //</debug>
- //<debug>
- {
- Ext.log.error('Invalid image download option: "' + name + '"');
- }
- }
- }
- //</debug>
- markup = Ext.dom.Helper.markup({
- tag: 'html',
- children: [
- {
- tag: 'head'
- },
- {
- tag: 'body',
- children: [
- {
- tag: 'form',
- method: 'POST',
- action: config.url || me.getDownloadServerUrl(),
- children: inputs
- },
- {
- tag: 'script',
- type: 'text/javascript',
- children: 'document.getElementsByTagName("form")[0].submit();'
- }
- ]
- }
- ]
- });
- window.open('', 'ImageDownload_' + Date.now()).document.write(markup);
- },
- /**
- * @method preview
- * Displays an image of a Ext.draw.Container on screen.
- * On mobile devices this lets users tap-and-hold to bring up the menu
- * with image saving options.
- * Notes:
- * - some browsers won't save the preview image if it's SVG based
- * (i.e. generated from a draw container that uses 'Ext.draw.engine.Svg' engine);
- * - some platforms may not have the means of viewing successfully saved SVG images;
- * - this method does not work on IE8.
- */
- doDestroy: function() {
- var me = this,
- callbackId = me.frameCallbackId;
- if (callbackId) {
- Ext.draw.Animator.removeFrameCallback(callbackId);
- }
- me.stopResizeTimer();
- me.callParent();
- }
- }, function() {
- if (location.search.match('svg')) {
- Ext.draw.Container.prototype.engine = 'Ext.draw.engine.Svg';
- } 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))) {
- // http://code.google.com/p/android/issues/detail?id=37529
- Ext.draw.Container.prototype.engine = 'Ext.draw.engine.Svg';
- }
- });
- /**
- *
- */
- Ext.define('Ext.chart.theme.BaseTheme', {
- defaultsDivCls: 'x-root'
- });
- /**
- * Abstract class that provides default styles for non-specified things.
- * Should be sub-classed when creating new themes.
- * For example:
- *
- * Ext.define('Ext.chart.theme.Custom', {
- * extend: 'Ext.chart.theme.Base',
- * singleton: true,
- * alias: 'chart.theme.custom',
- * config: {
- * baseColor: '#ff9f00'
- * }
- * });
- *
- * Theme provided values will not override the values provided in an instance config.
- * However, if a theme provided value is an object, it will be merged with the value
- * from the instance config, unless the theme provided object has a '$default' key
- * set to 'true'.
- *
- * Certain chart theme configs (e.g. 'fontSize') may use the 'default' value to indicate
- * that they should inherit a value from the corresponding CSS style provided by
- * a framework theme. Additionally, one can use basic binary operators like multiplication,
- * addition and subtraction to derive from the default value, e.g. fontSize: 'default*1.3'.
- *
- * Important: the theme should not use the 'font' shorthand to specify the font of labels
- * and other text elements of a chart. Instead, individual font properties should be used:
- * 'fontStyle', 'fontVariant', 'fontWeight', 'fontSize' and 'fontFamily'.
- */
- Ext.define('Ext.chart.theme.Base', {
- extend: 'Ext.chart.theme.BaseTheme',
- mixins: {
- factoryable: 'Ext.mixin.Factoryable'
- },
- requires: [
- 'Ext.draw.Color'
- ],
- factoryConfig: {
- type: 'chart.theme'
- },
- isTheme: true,
- isBase: true,
- config: {
- /**
- * @cfg {String/Ext.util.Color} baseColor
- * The base color used to generate the {@link Ext.chart.AbstractChart#colors} of the theme.
- */
- baseColor: null,
- /**
- * @cfg {Array} colors
- *
- * Array of colors/gradients to be used by the theme.
- * Defaults to {@link #colorDefaults}.
- */
- colors: undefined,
- /**
- * @cfg {Object} gradients
- *
- * The gradient config to be used by series' sprites. E.g.:
- *
- * {
- * type: 'linear',
- * degrees: 90
- * }
- *
- * Please refer to the documentation for the {@link Ext.draw.gradient.Linear linear}
- * and {@link Ext.draw.gradient.Radial radial} gradients for all possible options.
- * The color {@link Ext.draw.gradient.Gradient#stops stops} for the gradients
- * will be generated by the theme based on the {@link #colors} config.
- */
- gradients: null,
- /**
- * @cfg {Object} chart
- * Theme defaults for the chart.
- * Can apply to all charts or just a specific type of chart.
- * For example:
- *
- * chart: {
- * defaults: {
- * background: 'lightgray'
- * },
- * polar: {
- * background: 'green'
- * }
- * }
- *
- * The values from the chart.defaults and chart.*type* configs (where *type* is a valid
- * chart xtype, e.g. '{@link Ext.chart.CartesianChart cartesian}' or
- * '{@link Ext.chart.PolarChart polar}') will be applied to corresponding chart configs.
- * E.g., the chart.defaults.background config will set the
- * {@link Ext.chart.AbstractChart#background} config of all charts, where the
- * chart.cartesian.flipXY config will only set the
- * {@link Ext.chart.CartesianChart#flipXY} config of all cartesian charts.
- */
- chart: {
- defaults: {
- captions: {
- title: {
- docked: 'top',
- padding: 5,
- style: {
- textAlign: 'center',
- fontFamily: 'default',
- fontWeight: '500',
- fillStyle: 'black',
- fontSize: 'default*1.6'
- }
- },
- subtitle: {
- docked: 'top',
- style: {
- textAlign: 'center',
- fontFamily: 'default',
- fontWeight: 'normal',
- fillStyle: 'black',
- fontSize: 'default*1.3'
- }
- },
- credits: {
- docked: 'bottom',
- padding: 5,
- style: {
- textAlign: 'left',
- fontFamily: 'default',
- fontWeight: 'lighter',
- fillStyle: 'black',
- fontSize: 'default'
- }
- }
- },
- background: 'white'
- }
- },
- /**
- * @cfg {Object} axis
- * Theme defaults for the axes.
- * Can apply to all axes or only axes with a specific position.
- * For example:
- *
- * axis: {
- * defaults: {
- * style: {strokeStyle: 'red'}
- * },
- * left: {
- * title: {fillStyle: 'green'}
- * }
- * }
- *
- * The values from the axis.defaults and axis.*position* configs (where *position*
- * is a valid axis {@link Ext.chart.axis.Axis#position}, e.g. 'bottom') will be
- * applied to corresponding {@link Ext.chart.axis.Axis axis} configs.
- * E.g., the axis.defaults.label config will apply to the {@link Ext.chart.axis.Axis#label}
- * config of all axes, where the axis.left.titleMargin config will only apply to the
- * {@link Ext.chart.axis.Axis#titleMargin} config of all axes positioned to the left.
- */
- axis: {
- defaults: {
- label: {
- x: 0,
- y: 0,
- textBaseline: 'middle',
- textAlign: 'center',
- fontSize: 'default',
- fontFamily: 'default',
- fontWeight: 'default',
- fillStyle: 'black'
- },
- title: {
- fillStyle: 'black',
- fontSize: 'default*1.23',
- fontFamily: 'default',
- fontWeight: 'default'
- },
- style: {
- strokeStyle: 'black'
- },
- grid: {
- strokeStyle: 'rgb(221, 221, 221)'
- }
- },
- top: {
- style: {
- textPadding: 5
- }
- },
- bottom: {
- style: {
- textPadding: 5
- }
- }
- },
- /**
- * @cfg {Object} series
- * Theme defaults for the series.
- * Can apply to all series or just a specific type of series.
- * For example:
- *
- * series: {
- * defaults: {
- * style: {
- * lineWidth: 2
- * }
- * },
- * bar: {
- * animation: {
- * easing: 'bounceOut',
- * duration: 1000
- * }
- * }
- * }
- *
- * The values from the series.defaults and series.*type* configs (where *type*
- * is a valid series {@link Ext.chart.series.Series#type}, e.g. 'line') will be
- * applied to corresponding series configs.
- * E.g., the series.defaults.label config will apply to the
- * {@link Ext.chart.series.Series#label} config of all series, where the series.line.step
- * config will only apply to the
- * {@link Ext.chart.series.Line#step} config of {@link Ext.chart.series.Line line} series.
- */
- series: {
- defaults: {
- label: {
- fillStyle: 'black',
- strokeStyle: 'none',
- fontFamily: 'default',
- fontWeight: 'default',
- fontSize: 'default*1.077',
- textBaseline: 'middle',
- textAlign: 'center'
- },
- labelOverflowPadding: 5
- }
- },
- /**
- * @cfg {Object} sprites
- * Default style for the custom chart sprites by type.
- * For example:
- *
- * sprites: {
- * text: {
- * fontWeight: 300
- * }
- * }
- *
- * These sprite attribute overrides will apply to custom sprites of all charts
- * specified using the {@link Ext.draw.Container#sprites} config.
- * The overrides are specified by sprite type, e.g. sprites.text config
- * tells to apply given attributes to all {@link Ext.draw.sprite.Text text} sprites.
- */
- sprites: {
- text: {
- fontSize: 'default',
- fontWeight: 'default',
- fontFamily: 'default',
- fillStyle: 'black'
- }
- },
- /**
- * Style information for the {Ext.chart.legend.SpriteLegend sprite legend}.
- * If the {@link Ext.chart.legend.Legend DOM} legend is used, this config is ignored.
- * For additional details see {@link Ext.chart.AbstractChart#legend}.
- * @cfg {Object} legend
- * @cfg {Ext.chart.legend.sprite.Item} legend.item
- * @cfg {Object} legend.border See {@link Ext.chart.legend.SpriteLegend#border}.
- */
- legend: {
- label: {
- fontSize: 14,
- fontWeight: 'default',
- fontFamily: 'default',
- fillStyle: 'black'
- },
- border: {
- lineWidth: 1,
- radius: 4,
- fillStyle: 'none',
- strokeStyle: 'gray'
- },
- background: 'white'
- },
- /**
- * @private
- * An object with the following structure:
- * {
- * fillStyle: [color, color, ...],
- * strokeStyle: [color, color, ...],
- * ...
- * }
- * If missing, generated from the other configs: 'baseColor, 'gradients', 'colors'.
- */
- seriesThemes: undefined,
- markerThemes: {
- type: [
- 'circle',
- 'cross',
- 'plus',
- 'square',
- 'triangle',
- 'diamond'
- ]
- },
- /**
- * @deprecated 5.0.1 Use the {@link Ext.draw.Container#gradients} config instead.
- */
- useGradients: false,
- /**
- * @deprecated 5.0.1 Use the {@link Ext.chart.AbstractChart#background} config instead.
- */
- background: null
- },
- colorDefaults: [
- '#94ae0a',
- '#115fa6',
- '#a61120',
- '#ff8809',
- '#ffd13e',
- '#a61187',
- '#24ad9a',
- '#7c7474',
- '#a66111'
- ],
- constructor: function(config) {
- this.initConfig(config);
- this.resolveDefaults();
- },
- defaultRegEx: /^default([+\-/*]\d+(?:\.\d+)?)?$/,
- defaultOperators: {
- '*': function(v1, v2) {
- return v1 * v2;
- },
- '+': function(v1, v2) {
- return v1 + v2;
- },
- '-': function(v1, v2) {
- return v1 - v2;
- }
- },
- resolveChartDefaults: function() {
- var chart = Ext.clone(this.getChart()),
- chartType, captionName, chartConfig, captionConfig;
- for (chartType in chart) {
- chartConfig = chart[chartType];
- if ('captions' in chartConfig) {
- for (captionName in chartConfig.captions) {
- captionConfig = chartConfig.captions[captionName];
- if (captionConfig) {
- this.replaceDefaults(captionConfig.style);
- }
- }
- }
- }
- this.setChart(chart);
- },
- resolveDefaults: function() {
- var me = this;
- Ext.onInternalReady(function() {
- var sprites = Ext.clone(me.getSprites()),
- legend = Ext.clone(me.getLegend()),
- axis = Ext.clone(me.getAxis()),
- series = Ext.clone(me.getSeries()),
- div, key, config;
- if (!me.superclass.defaults) {
- div = Ext.getBody().createChild({
- tag: 'div',
- cls: me.defaultsDivCls
- });
- me.superclass.defaults = {
- fontFamily: div.getStyle('fontFamily'),
- fontWeight: div.getStyle('fontWeight'),
- fontSize: parseFloat(div.getStyle('fontSize')),
- fontVariant: div.getStyle('fontVariant'),
- fontStyle: div.getStyle('fontStyle')
- };
- div.destroy();
- }
- me.resolveChartDefaults();
- me.replaceDefaults(sprites.text);
- me.setSprites(sprites);
- me.replaceDefaults(legend.label);
- me.setLegend(legend);
- for (key in axis) {
- config = axis[key];
- me.replaceDefaults(config.label);
- me.replaceDefaults(config.title);
- }
- me.setAxis(axis);
- for (key in series) {
- config = series[key];
- me.replaceDefaults(config.label);
- }
- me.setSeries(series);
- });
- },
- replaceDefaults: function(target) {
- var me = this,
- defaults = me.superclass.defaults,
- defaultRegEx = me.defaultRegEx,
- key, value, match, binaryFn;
- if (Ext.isObject(target)) {
- for (key in defaults) {
- match = defaultRegEx.exec(target[key]);
- if (match) {
- value = defaults[key];
- match = match[1];
- if (match) {
- binaryFn = me.defaultOperators[match.charAt(0)];
- value = Math.round(binaryFn(value, parseFloat(match.substr(1))));
- }
- target[key] = value;
- }
- }
- }
- },
- applyBaseColor: function(baseColor) {
- var midColor, midL;
- if (baseColor) {
- midColor = baseColor.isColor ? baseColor : Ext.util.Color.fromString(baseColor);
- midL = midColor.getHSL()[2];
- if (midL < 0.15) {
- midColor = midColor.createLighter(0.3);
- } else if (midL < 0.3) {
- midColor = midColor.createLighter(0.15);
- } else if (midL > 0.85) {
- midColor = midColor.createDarker(0.3);
- } else if (midL > 0.7) {
- midColor = midColor.createDarker(0.15);
- }
- this.setColors([
- midColor.createDarker(0.3).toString(),
- midColor.createDarker(0.15).toString(),
- midColor.toString(),
- midColor.createLighter(0.12).toString(),
- midColor.createLighter(0.24).toString(),
- midColor.createLighter(0.31).toString()
- ]);
- }
- return baseColor;
- },
- applyColors: function(newColors) {
- return newColors || this.colorDefaults;
- },
- updateUseGradients: function(useGradients) {
- if (useGradients) {
- this.updateGradients({
- type: 'linear',
- degrees: 90
- });
- }
- },
- updateBackground: function(background) {
- var chart;
- if (background) {
- chart = this.getChart();
- chart.defaults.background = background;
- this.setChart(chart);
- }
- },
- updateGradients: function(gradients) {
- var colors = this.getColors(),
- items = [],
- gradient, midColor, color, i, ln;
- if (Ext.isObject(gradients)) {
- for (i = 0 , ln = colors && colors.length || 0; i < ln; i++) {
- midColor = Ext.util.Color.fromString(colors[i]);
- if (midColor) {
- color = midColor.createLighter(0.15).toString();
- gradient = Ext.apply(Ext.Object.chain(gradients), {
- stops: [
- {
- offset: 1,
- color: midColor.toString()
- },
- {
- offset: 0,
- color: color.toString()
- }
- ]
- });
- items.push(gradient);
- }
- }
- this.setColors(items);
- }
- },
- applySeriesThemes: function(newSeriesThemes) {
- var colors, color;
- // Init the 'colors' config with solid colors generated from the 'baseColor'.
- this.getBaseColor();
- // Init the 'gradients' config with a hardcoded value, if the legacy 'useGradients'
- // config was set to 'true'. This in turn updates the 'colors' config.
- this.getUseGradients();
- // Init the 'gradients' config normally. This also updates the 'colors' config.
- this.getGradients();
- colors = this.getColors();
- // Final colors.
- if (!newSeriesThemes) {
- newSeriesThemes = {
- fillStyle: Ext.Array.clone(colors),
- strokeStyle: Ext.Array.map(colors, function(value) {
- color = Ext.util.Color.fromString(value.stops ? value.stops[0].color : value);
- return color.createDarker(0.15).toString();
- })
- };
- }
- return newSeriesThemes;
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.chart.theme.Default', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.default',
- 'chart.theme.Default',
- 'chart.theme.Base'
- ]
- });
- /**
- * @private
- */
- Ext.define('Ext.chart.Util', {
- singleton: true,
- /**
- * @private
- * Given a `range` array of two (min/max) numbers and an arbitrary array of numbers
- * (`data`), updates the given `range`, if the range of `data` exceeds it.
- * Typically, one would start with the `[NaN, NaN]` range array instance, call the
- * method on multiple datasets with that range instance, then validate it with
- * {@link #validateRange}.
- * @param {Number[]} range
- * @param {Number[]} data
- */
- expandRange: function(range, data) {
- var length = data.length,
- min = range[0],
- max = range[1],
- i, value;
- for (i = 0; i < length; i++) {
- value = data[i];
- // `null` is a "finite" number in JavaScript
- // and greater than any negative number.
- if (value == null || !isFinite(value)) {
-
- continue;
- }
- if (value < min || !isFinite(min)) {
- min = value;
- }
- if (value > max || !isFinite(max)) {
- max = value;
- }
- }
- range[0] = min;
- range[1] = max;
- },
- defaultRange: [
- 0,
- 1
- ],
- /**
- * @private
- * Makes sure the range exists, is not zero, and its min/max values are finite numbers.
- * If this is not the case, the values from the provided `defaultRange`
- * are used.
- *
- * The range to validate. Never modified.
- * @param {Number[]} range
- * The default range to use, if the given range is not a valid data structure,
- * if both values are infinities, or if both values are the same and dangerously
- * close to either infinity (which makes expansion of the range by the value of
- * `padding` impossible).
- * If only a single value is infinity, the other value will be derived
- * from the finite value by incrementing/decrementing it by the span
- * of the default range towards the infinity.
- * For example, if the `defaultRange` is `[0, 1]`, we have:
- *
- * [5, Infinity] --> [5, 6]
- * [3, -Infinity] --> [2, 3]
- * [-Infinity, -5] --> [-6, -5]
- * [-3, -Infinity] --> [-4, -3]
- *
- * @param {Number[]} [defaultRange=[0, 1]]
- * A non-negative padding to use in case of identical min/max.
- * Note that the range span is not guaranteed to be `padding * 2` in this case,
- * if min/max are close to MIN_SAFE_INTEGER/MAX_SAFE_INTEGER.
- * @param {Number} [padding=0.5]
- * @return {Number[]}
- */
- validateRange: function(range, defaultRange, padding) {
- var isFin0, isFin1;
- defaultRange = defaultRange || this.defaultRange.slice();
- if (!(padding === 0 || padding > 0)) {
- padding = 0.5;
- }
- if (!range || range.length !== 2) {
- return defaultRange;
- }
- range = [
- range[0],
- range[1]
- ];
- if (!range[0]) {
- range[0] = 0;
- }
- if (!range[1]) {
- range[1] = 0;
- }
- if (padding && range[0] === range[1]) {
- range = [
- range[0] - padding,
- range[0] + padding
- ];
- // In case the range values are at Infinity, the expansion above by the value
- // of 'padding' won't do us much good, so we still have to fall back to the
- // 'defaultRange'.
- if (range[0] === range[1]) {
- return defaultRange;
- }
- }
- // Same sign infinities are ruled out at this point.
- isFin0 = isFinite(range[0]);
- isFin1 = isFinite(range[1]);
- if (!isFin0 && !isFin1) {
- return defaultRange;
- }
- // Different sign infinities are ruled out at this point.
- if (isFin0 && !isFin1) {
- range[1] = range[0] + Ext.Number.sign(range[1]) * (defaultRange[1] - defaultRange[0]);
- } else if (isFin1 && !isFin0) {
- range[0] = range[1] + Ext.Number.sign(range[0]) * (defaultRange[1] - defaultRange[0]);
- }
- // All infinities are ruled out at this point.
- return [
- Math.min(range[0], range[1]),
- Math.max(range[0], range[1])
- ];
- },
- applyAnimation: function(animation, oldAnimation) {
- if (!animation) {
- animation = {
- duration: 0
- };
- } else if (animation === true) {
- animation = {
- easing: 'easeInOut',
- duration: 500
- };
- }
- return oldAnimation ? Ext.apply({}, animation, oldAnimation) : animation;
- }
- });
- /**
- * @class Ext.chart.Markers
- * @extends Ext.draw.sprite.Instancing
- *
- * Marker sprite. A specialized version of instancing sprite that groups instances.
- * Putting a marker is grouped by its category id. Clearing removes that category.
- */
- Ext.define('Ext.chart.Markers', {
- extend: 'Ext.draw.sprite.Instancing',
- isMarkers: true,
- defaultCategory: 'default',
- constructor: function() {
- this.callParent(arguments);
- // `categories` maps category names to a map that maps instance index in category to its
- // global index: categoryName: {instanceIndexInCategory: globalInstanceIndex}
- this.categories = {};
- // The `revisions` map keeps revision numbers of instance categories.
- // When a marker (instance) is put (created or updated), it gets the revision
- // of the category. When a category is cleared, its revision is incremented,
- // but its instances are not removed.
- // An instance is only rendered if its revision matches category revision.
- // In other words, a marker has to be put again after its category has been cleared
- // or it won't render.
- this.revisions = {};
- },
- destroy: function() {
- this.categories = null;
- this.revisions = null;
- this.callParent();
- },
- getMarkerFor: function(category, index) {
- var categoryInstances;
- if (category in this.categories) {
- categoryInstances = this.categories[category];
- if (index in categoryInstances) {
- return this.get(categoryInstances[index]);
- }
- }
- },
- /**
- * Clears the markers in the category.
- * @param {String} category
- */
- clear: function(category) {
- category = category || this.defaultCategory;
- if (!(category in this.revisions)) {
- this.revisions[category] = 1;
- } else {
- this.revisions[category]++;
- }
- },
- clearAll: function() {
- this.callParent();
- this.categories = {};
- this.revisions = {};
- },
- /**
- * Puts a marker in the category with additional attributes.
- * @param {String} category
- * @param {Object} attr
- * @param {String|Number} index
- * @param {Boolean} [bypassNormalization]
- * @param {Boolean} [keepRevision]
- */
- putMarkerFor: function(category, attr, index, bypassNormalization, keepRevision) {
- var me = this,
- categoryInstances, instance;
- category = category || this.defaultCategory;
- categoryInstances = me.categories[category] || (me.categories[category] = {});
- if (index in categoryInstances) {
- me.setAttributesFor(categoryInstances[index], attr, bypassNormalization);
- } else {
- // get the index of the instance created on next line
- categoryInstances[index] = me.getCount();
- me.add(attr, bypassNormalization);
- }
- instance = me.get(categoryInstances[index]);
- if (instance) {
- instance.category = category;
- if (!keepRevision) {
- instance.revision = me.revisions[category] || (me.revisions[category] = 1);
- }
- }
- },
- /**
- *
- * @param {String} category
- * @param {Mixed} index
- * @param {Boolean} [isWithoutTransform]
- */
- getMarkerBBoxFor: function(category, index, isWithoutTransform) {
- var categoryInstances;
- if (category in this.categories) {
- categoryInstances = this.categories[category];
- if (index in categoryInstances) {
- return this.getBBoxFor(categoryInstances[index], isWithoutTransform);
- }
- }
- },
- getBBox: function() {
- return null;
- },
- render: function(surface, ctx, rect) {
- var me = this,
- surfaceRect = surface.getRect(),
- revisions = me.revisions,
- mat = me.attr.matrix,
- template = me.getTemplate(),
- templateAttr = template.attr,
- ln = me.instances.length,
- instance, i;
- mat.toContext(ctx);
- template.preRender(surface, ctx, rect);
- template.useAttributes(ctx, surfaceRect);
- for (i = 0; i < ln; i++) {
- instance = me.get(i);
- if (instance.hidden || instance.revision !== revisions[instance.category]) {
-
- continue;
- }
- ctx.save();
- template.attr = instance;
- template.useAttributes(ctx, surfaceRect);
- template.render(surface, ctx, rect);
- ctx.restore();
- }
- template.attr = templateAttr;
- }
- });
- /**
- * This is a modifier to place labels and callouts by additional attributes.
- */
- Ext.define('Ext.chart.modifier.Callout', {
- extend: 'Ext.draw.modifier.Modifier',
- alternateClassName: 'Ext.chart.label.Callout',
- prepareAttributes: function(attr) {
- if (!attr.hasOwnProperty('calloutOriginal')) {
- attr.calloutOriginal = Ext.Object.chain(attr);
- // No __proto__, nor getPrototypeOf in IE8,
- // so manually saving a reference to 'attr' after chaining.
- attr.calloutOriginal.prototype = attr;
- }
- if (this._lower) {
- this._lower.prepareAttributes(attr.calloutOriginal);
- }
- },
- setAttrs: function(attr, changes) {
- var callout = attr.callout,
- origin = attr.calloutOriginal,
- bbox = attr.bbox.plain,
- width = (bbox.width || 0) + attr.labelOverflowPadding,
- height = (bbox.height || 0) + attr.labelOverflowPadding,
- dx, dy, rotationRads, x, y, calloutPlaceX, calloutPlaceY, calloutVertical, temp;
- if ('callout' in changes) {
- callout = changes.callout;
- }
- if ('callout' in changes || 'calloutPlaceX' in changes || 'calloutPlaceY' in changes || 'x' in changes || 'y' in changes) {
- rotationRads = 'rotationRads' in changes ? origin.rotationRads = changes.rotationRads : origin.rotationRads;
- x = 'x' in changes ? (origin.x = changes.x) : origin.x;
- y = 'y' in changes ? (origin.y = changes.y) : origin.y;
- calloutPlaceX = 'calloutPlaceX' in changes ? changes.calloutPlaceX : attr.calloutPlaceX;
- calloutPlaceY = 'calloutPlaceY' in changes ? changes.calloutPlaceY : attr.calloutPlaceY;
- calloutVertical = 'calloutVertical' in changes ? changes.calloutVertical : attr.calloutVertical;
- // Normalize Rotations
- rotationRads %= Math.PI * 2;
- if (Math.cos(rotationRads) < 0) {
- rotationRads = (rotationRads + Math.PI) % (Math.PI * 2);
- }
- if (rotationRads > Math.PI) {
- rotationRads -= Math.PI * 2;
- }
- if (calloutVertical) {
- rotationRads = rotationRads * (1 - callout) - Math.PI / 2 * callout;
- temp = width;
- width = height;
- height = temp;
- } else {
- rotationRads = rotationRads * (1 - callout);
- }
- changes.rotationRads = rotationRads;
- // Placing a label in the middle of a pie slice (x/y)
- // if callout doesn't exists (callout=0),
- // or outside the pie slice (calloutPlaceX/Y) if it does (callout=1).
- changes.x = x * (1 - callout) + calloutPlaceX * callout;
- changes.y = y * (1 - callout) + calloutPlaceY * callout;
- dx = calloutPlaceX - x;
- dy = calloutPlaceY - y;
- // Finding where the callout line intersects the bbox of the label
- // if it were to go to the center of the label,
- // and make that intersection point the end of the callout line.
- // Effectively, the end of the callout line traces label's bbox when chart is rotated.
- if (Math.abs(dy * width) > Math.abs(dx * height)) {
- // on top/bottom
- if (dy > 0) {
- changes.calloutEndX = changes.x - (height / 2) * (dx / dy) * callout;
- changes.calloutEndY = changes.y - (height / 2) * callout;
- } else {
- changes.calloutEndX = changes.x + (height / 2) * (dx / dy) * callout;
- changes.calloutEndY = changes.y + (height / 2) * callout;
- }
- } else {
- // on left/right
- if (dx > 0) {
- changes.calloutEndX = changes.x - width / 2;
- changes.calloutEndY = changes.y - (width / 2) * (dy / dx) * callout;
- } else {
- changes.calloutEndX = changes.x + width / 2;
- changes.calloutEndY = changes.y + (width / 2) * (dy / dx) * callout;
- }
- }
- // Since the length of the callout line is adjusted depending on the label's position
- // and dimensions, we hide the callout line if the length becomes negative.
- if (changes.calloutStartX && changes.calloutStartY) {
- 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);
- } else {
- changes.calloutHasLine = true;
- }
- }
- return changes;
- },
- pushDown: function(attr, changes) {
- changes = this.callParent([
- attr.calloutOriginal,
- changes
- ]);
- return this.setAttrs(attr, changes);
- },
- popUp: function(attr, changes) {
- attr = attr.prototype;
- changes = this.setAttrs(attr, changes);
- if (this._upper) {
- return this._upper.popUp(attr, changes);
- } else {
- return Ext.apply(attr, changes);
- }
- }
- });
- /**
- * @class Ext.chart.sprite.Label
- * @extends Ext.draw.sprite.Text
- *
- * Sprite used to represent labels in series.
- *
- * Important: the actual default values are determined by the theme used.
- * Please see the `label` config of the {@link Ext.chart.theme.Base#axis}.
- */
- Ext.define('Ext.chart.sprite.Label', {
- extend: 'Ext.draw.sprite.Text',
- alternateClassName: 'Ext.chart.label.Label',
- requires: [
- 'Ext.chart.modifier.Callout'
- ],
- inheritableStatics: {
- def: {
- processors: {
- callout: 'limited01',
- // Meant to be set by the Callout modifier only.
- calloutHasLine: 'bool',
- // The position where the callout would end, if not for the label:
- // callout stops at the bounding box of the label,
- // so the actual point where the callout ends - calloutEndX/Y -
- // is calculated by the Callout modifier.
- calloutPlaceX: 'number',
- calloutPlaceY: 'number',
- // The start/end points used to render the callout line.
- calloutStartX: 'number',
- calloutStartY: 'number',
- calloutEndX: 'number',
- calloutEndY: 'number',
- calloutColor: 'color',
- calloutWidth: 'number',
- calloutVertical: 'bool',
- labelOverflowPadding: 'number',
- display: 'enums(none,under,over,rotate,insideStart,insideEnd,inside,outside)',
- orientation: 'enums(horizontal,vertical)',
- renderer: 'default'
- },
- defaults: {
- callout: 0,
- calloutHasLine: true,
- calloutPlaceX: 0,
- calloutPlaceY: 0,
- calloutStartX: 0,
- calloutStartY: 0,
- calloutEndX: 0,
- calloutEndY: 0,
- calloutWidth: 1,
- calloutVertical: false,
- calloutColor: 'black',
- labelOverflowPadding: 5,
- display: 'none',
- orientation: '',
- renderer: null
- },
- triggers: {
- callout: 'transform',
- calloutPlaceX: 'transform',
- calloutPlaceY: 'transform',
- labelOverflowPadding: 'transform',
- calloutRotation: 'transform',
- display: 'hidden'
- },
- updaters: {
- hidden: function(attr) {
- attr.hidden = (attr.display === 'none');
- }
- }
- }
- },
- config: {
- /**
- * @cfg {Object} fx Animation configuration.
- */
- animation: {
- customDurations: {
- callout: 200
- }
- },
- /**
- * @cfg {String} field The store record field used by the label sprite.
- *
- * Note: the label sprite is typically used indirectly (by a Ext.chart.MarkerHolder
- * series sprite, via a Ext.chart.Markers sprite, where the latter is passed to the
- * label renderer), so to get to the label field one has to do:
- *
- * renderer: function (text, sprite, config, data, index) {
- * var field = sprite.getTemplate().getField();
- * }
- *
- * To get the actual label sprite instance one can use:
- *
- * sprite.get(index)
- *
- */
- field: null,
- /**
- * @cfg {Boolean|Object} calloutLine
- *
- * True to draw a line between the label and the chart with the default settings,
- * or an Object that defines the 'color', 'width' and 'length' properties of the line.
- * This config is only applicable when the label is displayed outside the chart.
- *
- * Default value: false.
- */
- calloutLine: true,
- /**
- * @cfg {Number} [hideLessThan=20]
- * Hides labels for pie slices with segment length less than this value (in pixels).
- */
- hideLessThan: 20
- },
- applyCalloutLine: function(calloutLine) {
- if (calloutLine) {
- return Ext.apply({}, calloutLine);
- }
- return calloutLine;
- },
- createModifiers: function() {
- var me = this,
- mods = me.callParent(arguments);
- mods.callout = new Ext.chart.modifier.Callout({
- sprite: me
- });
- mods.animation.setUpper(mods.callout);
- mods.callout.setUpper(mods.target);
- },
- render: function(surface, ctx) {
- var me = this,
- attr = me.attr,
- calloutColor = attr.calloutColor;
- ctx.save();
- ctx.globalAlpha *= attr.callout;
- if (ctx.globalAlpha > 0 && attr.calloutHasLine) {
- if (calloutColor && calloutColor.isGradient) {
- calloutColor = calloutColor.getStops()[0].color;
- }
- ctx.strokeStyle = calloutColor;
- ctx.fillStyle = calloutColor;
- ctx.lineWidth = attr.calloutWidth;
- ctx.beginPath();
- ctx.moveTo(me.attr.calloutStartX, me.attr.calloutStartY);
- ctx.lineTo(me.attr.calloutEndX, me.attr.calloutEndY);
- ctx.stroke();
- ctx.beginPath();
- ctx.arc(me.attr.calloutStartX, me.attr.calloutStartY, 1 * attr.calloutWidth, 0, 2 * Math.PI, true);
- ctx.fill();
- ctx.beginPath();
- ctx.arc(me.attr.calloutEndX, me.attr.calloutEndY, 1 * attr.calloutWidth, 0, 2 * Math.PI, true);
- ctx.fill();
- }
- ctx.restore();
- Ext.draw.sprite.Text.prototype.render.apply(me, arguments);
- }
- });
- /**
- * Series is the abstract class containing the common logic to all chart series.
- * Series includes methods from Labels, Highlights, and Callouts mixins. This class
- * implements the logic of animating, hiding, showing all elements and returning the
- * color of the series to be used as a legend item.
- *
- * ## Listeners
- *
- * The series class supports listeners via the Observable syntax.
- *
- * For example:
- *
- * Ext.create('Ext.chart.CartesianChart', {
- * plugins: {
- * chartitemevents: {
- * moveEvents: true
- * }
- * },
- * store: {
- * fields: ['pet', 'households', 'total'],
- * data: [
- * {pet: 'Cats', households: 38, total: 93},
- * {pet: 'Dogs', households: 45, total: 79},
- * {pet: 'Fish', households: 13, total: 171}
- * ]
- * },
- * axes: [{
- * type: 'numeric',
- * position: 'left'
- * }, {
- * type: 'category',
- * position: 'bottom'
- * }],
- * series: [{
- * type: 'bar',
- * xField: 'pet',
- * yField: 'households',
- * listeners: {
- * itemmousemove: function (series, item, event) {
- * console.log('itemmousemove', item.category, item.field);
- * }
- * }
- * }, {
- * type: 'line',
- * xField: 'pet',
- * yField: 'total',
- * marker: true
- * }]
- * });
- *
- */
- Ext.define('Ext.chart.series.Series', {
- requires: [
- 'Ext.chart.Util',
- 'Ext.chart.Markers',
- 'Ext.chart.sprite.Label',
- 'Ext.tip.ToolTip'
- ],
- mixins: [
- 'Ext.mixin.Observable',
- 'Ext.mixin.Bindable'
- ],
- isSeries: true,
- defaultBindProperty: 'store',
- /**
- * @property {String} type
- * The type of series. Set in subclasses.
- * @protected
- */
- type: null,
- /**
- * @property {String} seriesType
- * Default series sprite type.
- */
- seriesType: 'sprite',
- identifiablePrefix: 'ext-line-',
- observableType: 'series',
- darkerStrokeRatio: 0.15,
- /**
- * @event itemmousemove
- * Fires when the mouse is moved on a series item.
- * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
- * plugin be added to the chart.
- * @param {Ext.chart.series.Series} series
- * @param {Object} item
- * @param {Event} event
- */
- /**
- * @event itemmouseup
- * Fires when a mouseup event occurs on a series item.
- * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
- * plugin be added to the chart.
- * @param {Ext.chart.series.Series} series
- * @param {Object} item
- * @param {Event} event
- */
- /**
- * @event itemmousedown
- * Fires when a mousedown event occurs on a series item.
- * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
- * plugin be added to the chart.
- * @param {Ext.chart.series.Series} series
- * @param {Object} item
- * @param {Event} event
- */
- /**
- * @event itemmouseover
- * Fires when the mouse enters a series item.
- * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
- * plugin be added to the chart.
- * @param {Ext.chart.series.Series} series
- * @param {Object} item
- * @param {Event} event
- */
- /**
- * @event itemmouseout
- * Fires when the mouse exits a series item.
- * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
- * plugin be added to the chart.
- * @param {Ext.chart.series.Series} series
- * @param {Object} item
- * @param {Event} event
- */
- /**
- * @event itemclick
- * Fires when a click event occurs on a series item.
- * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
- * plugin be added to the chart.
- * @param {Ext.chart.series.Series} series
- * @param {Object} item
- * @param {Event} event
- */
- /**
- * @event itemdblclick
- * Fires when a double click event occurs on a series item.
- * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
- * plugin be added to the chart.
- * @param {Ext.chart.series.Series} series
- * @param {Object} item
- * @param {Event} event
- */
- /**
- * @event itemtap
- * Fires when a tap event occurs on a series item.
- * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
- * plugin be added to the chart.
- * @param {Ext.chart.series.Series} series
- * @param {Object} item
- * @param {Event} event
- */
- /**
- * @event chartattached
- * Fires when the {@link Ext.chart.AbstractChart} has been attached to this series.
- * @param {Ext.chart.AbstractChart} chart
- * @param {Ext.chart.series.Series} series
- */
- /**
- * @event chartdetached
- * Fires when the {@link Ext.chart.AbstractChart} has been detached from this series.
- * @param {Ext.chart.AbstractChart} chart
- * @param {Ext.chart.series.Series} series
- */
- /**
- * @event storechange
- * Fires when the store of the series changes.
- * @param {Ext.chart.series.Series} series
- * @param {Ext.data.Store} newStore
- * @param {Ext.data.Store} oldStore
- */
- config: {
- /**
- * @private
- * @cfg {Object} chart The chart that the series is bound.
- */
- chart: null,
- /**
- * @cfg {String|String[]} title
- * The human-readable name of the series (displayed in the legend).
- * If the series is stacked (has multiple components in it) this
- * should be an array, where each string corresponds to a stacked component.
- */
- title: null,
- /**
- * @cfg {Function} renderer
- * A function that can be provided to set custom styling properties to each
- * rendered element. It receives `(sprite, config, rendererData, index)`
- * as parameters.
- *
- * @param {Object} sprite The sprite affected by the renderer.
- * The visual attributes are in `sprite.attr`.
- * The data field is available in `sprite.getField()`.
- * @param {Object} config The sprite configuration, which varies with the series
- * and the type of sprite. For instance, a Line chart sprite might have just the
- * `x` and `y` properties while a Bar chart sprite also has `width` and `height`.
- * A `type` might be present too. For instance to draw each marker and each segment
- * of a Line chart, the renderer is called with the `config.type` set to either
- * `marker` or `line`.
- * @param {Object} rendererData A record with different properties depending on
- * the type of chart. The only guaranteed property is `rendererData.store`, the
- * store used by the series. In some cases, a store may not exist: for instance
- * a Gauge chart may read its value directly from its configuration; in this case
- * rendererData.store is null and the value is available in rendererData.value.
- * @param {Number} index The index of the sprite. It is usually the index of the
- * store record associated with the sprite, in which case the record can be obtained
- * with `store.getData().items[index]`. If the chart is not associated with a store,
- * the index represents the index of the sprite within the series. For instance
- * a Gauge chart may have as many sprites as there are sectors in the background of
- * the gauge, plus one for the needle.
- *
- * @return {Object} The attributes that have been changed or added.
- * Note: it is usually possible to add or modify the attributes directly into the
- * `config` parameter and not return anything, but returning an object with only
- * those attributes that have been changed may allow for optimizations in the
- * rendering of some series. Example to draw every other marker in red:
- *
- * renderer: function (sprite, config, rendererData, index) {
- * if (config.type === 'marker') {
- * return { strokeStyle: (index % 2 === 0 ? 'red' : 'black') };
- * }
- * }
- *
- * @controllable
- */
- renderer: null,
- /**
- * @cfg {Boolean} showInLegend
- * Whether to show this series in the legend.
- */
- showInLegend: true,
- /**
- * @private
- * Trigger drawlistener flag
- */
- triggerAfterDraw: false,
- /**
- * @private
- */
- theme: null,
- /**
- * @cfg {Object} style Custom style configuration for the sprite used in the series.
- * It overrides the style that is provided by the current theme.
- */
- style: {},
- /**
- * @cfg {Object} subStyle This is the cyclic used if the series has multiple sprites.
- */
- subStyle: {},
- /**
- * @private
- * @cfg {Object} themeStyle Style configuration that is provided by the current theme.
- * It is composed of five objects:
- * @cfg {Object} themeStyle.style Properties common to all the series,
- * for instance the 'lineWidth'.
- * @cfg {Object} themeStyle.subStyle Cyclic used if the series has multiple sprites.
- * @cfg {Object} themeStyle.label Sprite config for the labels,
- * for instance the font and color.
- * @cfg {Object} themeStyle.marker Sprite config for the markers,
- * for instance the size and stroke color.
- * @cfg {Object} themeStyle.markerSubStyle Cyclic used if series have multiple marker
- * sprites.
- */
- themeStyle: {},
- /**
- * @cfg {Array} colors
- * An array of color values which is used, in order of appearance, by the series.
- * Each series can request one or more colors from the array. Radar, Scatter or Line charts
- * require just one color each. Candlestick and OHLC require two
- * (1 for drops + 1 for rises). Pie charts and Stacked charts (like Bar or Pie charts)
- * require one color for each data category they represent, so one color for each slice
- * of a Pie chart or each segment (not bar) of a Bar chart.
- * It overrides the colors that are provided by the current theme.
- */
- colors: null,
- /**
- * @cfg {Boolean|Number} useDarkerStrokeColor
- * Colors for the series can be set directly through the 'colors' config, or indirectly
- * with the current theme or the 'colors' config that is set onto the chart. These colors
- * are used as "fill color". Set this config to true, if you want a darker color for the
- * strokes. Set it to false if you want to use the same color as the fill color.
- * Alternatively, you can set it to a number between 0 and 1 to control how much darker
- * the strokes should be.
- * Note: this should be initial config and cannot be changed later on.
- */
- useDarkerStrokeColor: true,
- /**
- * @cfg {Object} store The store to use for this series. If not specified,
- * the series will use the chart's {@link Ext.chart.AbstractChart#store store}.
- */
- store: null,
- /**
- * @cfg {Object} label
- * Object with the following properties:
- *
- * @cfg {String} label.display
- *
- * Specifies the presence and position of the labels.
- * The possible values depend on the series type.
- * For Line and Scatter series: 'under' | 'over' | 'rotate'.
- * For Bar and 3D Bar series: 'insideStart' | 'insideEnd' | 'outside'.
- * For Pie series: 'inside' | 'outside' | 'rotate' | 'horizontal' | 'vertical'.
- * Area, Radar and Candlestick series don't support labels.
- * For Area and Radar series please consider using {@link #tooltip tooltips} instead.
- * 3D Pie series currently always display labels 'outside'.
- * For all series: 'none' hides the labels.
- *
- * Default value: 'none'.
- *
- * @cfg {String} label.color
- *
- * The color of the label text.
- *
- * Default value: '#000' (black).
- *
- * @cfg {String|String[]} label.field
- *
- * The name(s) of the field(s) to be displayed in the labels. If your chart has 3 series
- * that correspond to the fields 'a', 'b', and 'c' of your model, and you only want to
- * display labels for the series 'c', you must still provide an array `[null, null, 'c']`.
- *
- * Default value: null.
- *
- * @cfg {String} label.font
- *
- * The font used for the labels.
- *
- * Default value: '14px Helvetica'.
- *
- * @cfg {String} label.orientation
- *
- * Either 'horizontal' or 'vertical'. If not set (default), the orientation is inferred
- * from the value of the flipXY property of the series.
- *
- * Default value: ''.
- *
- * @cfg {Function} label.renderer
- *
- * Optional function for formatting the label into a displayable value.
- *
- * The arguments to the method are:
- *
- * - *`text`*, *`sprite`*, *`config`*, *`rendererData`*, *`index`*
- *
- * Label's renderer is passed the same arguments as {@link #renderer}
- * plus one extra 'text' argument which comes first.
- *
- * @return {Object|String} The attributes that have been changed or added,
- * or the text for the label.
- * Example to enclose every other label in parentheses:
- *
- * renderer: function (text) {
- * if (index % 2 == 0) {
- * return '(' + text + ')'
- * }
- * }
- */
- label: null,
- /**
- * @cfg {Number} labelOverflowPadding
- * Extra distance value for which the labelOverflow listener is triggered.
- */
- labelOverflowPadding: null,
- /**
- * @cfg {Boolean} showMarkers
- * Whether markers should be displayed at the data points along the line. If true,
- * then the {@link #marker} config item will determine the markers' styling.
- */
- showMarkers: true,
- /**
- * @cfg {Object|Boolean} marker
- * The sprite template used by marker instances on the series.
- * If the value of the marker config is set to `true` or the type
- * of the sprite instance is not specified, the {@link Ext.draw.sprite.Circle}
- * sprite will be used.
- *
- * Examples:
- *
- * marker: true
- *
- * marker: {
- * radius: 8
- * }
- *
- * marker: {
- * type: 'arrow',
- * animation: {
- * duration: 200,
- * easing: 'backOut'
- * }
- * }
- */
- marker: null,
- /**
- * @cfg {Object} markerSubStyle
- * This is cyclic used if series have multiple marker sprites.
- */
- markerSubStyle: null,
- /**
- * @protected
- * @cfg {Object} itemInstancing
- * The sprite template used to create sprite instances in the series.
- */
- itemInstancing: null,
- /**
- * @cfg {Object} background
- * Sets the background of the surface the series is attached.
- */
- background: null,
- /**
- * @protected
- * @cfg {Ext.draw.Surface} surface
- * The chart surface used to render series sprites.
- */
- surface: null,
- /**
- * @protected
- * @cfg {Object} overlaySurface
- * The surface used to render series labels.
- */
- overlaySurface: null,
- /**
- * @cfg {Boolean|Array} hidden
- */
- hidden: false,
- /**
- * @cfg {Boolean/Object} highlight
- * The sprite attributes that will be applied to the highlighted items in the series.
- * If set to 'true', the default highlight style from {@link #highlightCfg} will be used.
- * If the value of this config is an object, it will be merged with the
- * {@link #highlightCfg}. In case merging of 'highlight' and 'highlightCfg' configs
- * in not the desired behavior, provide the 'highlightCfg' instead.
- */
- highlight: false,
- /**
- * @protected
- * @cfg {Object} highlightCfg
- * The default style for the highlighted item.
- * Used when {@link #highlight} config was simply set to 'true' instead of specifying
- * a style.
- */
- highlightCfg: {
- // Make custom highlightCfg's in subclasses replace this one.
- merge: function(value) {
- return value;
- },
- $value: {
- fillStyle: 'yellow',
- strokeStyle: 'red'
- }
- },
- /**
- * @cfg {Object} animation The series animation configuration.
- * By default, the series is using the same animation the chart uses,
- * if it's own animation is not explicitly configured.
- */
- animation: null,
- /**
- * @cfg {Object} tooltip
- * Add tooltips to the visualization's markers. The config options for the
- * tooltip are the same configuration used with {@link Ext.tip.ToolTip} plus a
- * `renderer` config option and a `scope` for the renderer. For example:
- *
- * tooltip: {
- * trackMouse: true,
- * width: 140,
- * height: 28,
- * renderer: function (toolTip, record, ctx) {
- * toolTip.setHtml(record.get('name') + ': ' + record.get('data1') + ' views');
- * }
- * }
- *
- * Note that tooltips are shown for series markers and won't work
- * if the {@link #marker} is not configured.
- *
- * You can also configure
- * {@link Ext.chart.interactions.ItemHighlight#multiTooltips}
- * to display multiple tooltips for adjacent or overlapping Line series
- * data points within {@link Ext.chart.series.Line#selectionTolerance} radius.
- *
- * @cfg {Object} tooltip.scope The scope to use when the renderer function is
- * called. Defaults to the Series instance.
- * @cfg {Function} tooltip.renderer An 'interceptor' method which can be used to
- * modify the tooltip attributes before it is shown. The renderer function is
- * passed the following params:
- * @cfg {Ext.tip.ToolTip} tooltip.renderer.toolTip The tooltip instance
- * @cfg {Ext.data.Model} tooltip.renderer.record The record instance for the
- * chart item (sprite) currently targeted by the tooltip.
- * @cfg {Object} tooltip.renderer.ctx A data object with values relating to the
- * currently targeted chart sprite
- * @cfg {String} tooltip.renderer.ctx.category The type of sprite passed to the
- * renderer function (will be "items", "markers", or "labels" depending on the
- * target sprite of the tooltip)
- * @cfg {String} tooltip.renderer.ctx.field The {@link #yField} for the series
- * @cfg {Number} tooltip.renderer.ctx.index The target sprite's index within the
- * series' items
- * @cfg {Ext.data.Model} tooltip.renderer.ctx.record The record instance for the
- * chart item (sprite) currently targeted by the tooltip.
- * @cfg {Ext.chart.series.Series} tooltip.renderer.ctx.series The series instance
- * containing the tooltip's target sprite
- * @cfg {Ext.draw.sprite.Sprite} tooltip.renderer.ctx.sprite The sprite (item)
- * target of the tooltip
- */
- tooltip: null
- },
- directions: [],
- sprites: null,
- /**
- * @private
- * Returns the number of colors this series needs.
- * A Pie chart needs one color per slice while a Stacked Bar chart needs one per segment.
- * An OHLC chart needs 2 colors (one for drops, one for rises), and most other charts
- * need just a single color.
- */
- themeColorCount: function() {
- return 1;
- },
- /**
- * @private
- * @property
- * Series, where the number of sprites (an so unique colors they require)
- * depends on the number of records in the store should set this to 'true'.
- */
- isStoreDependantColorCount: false,
- /**
- * @private
- * Returns the number of markers this series needs.
- * Currently, only the Line, Scatter and Radar series use markers - and they need
- * just one each.
- */
- themeMarkerCount: function() {
- return 0;
- },
- /**
- * @private
- * Each series has configs that tell which store record fields to use as data
- * for a certain dimension. For example, `xField`, `yField` for most cartesian series,
- * `angleField`, `radiusField` for polar series, `openField`, ..., `closeField`
- * for CandleStick series, etc. The field category is an array of capitalized config
- * names, minus the 'Field' part, to use as data for a certain dimension.
- * For example, for CandleStick series we have:
- *
- * fieldCategoryY: ['Open', 'High', 'Low', 'Close']
- *
- * While for generic Cartesian series it is simply:
- *
- * fieldCategoryY: ['Y']
- *
- * This method fetches the values of those configs, i.e. the actual record fields to use.
- *
- * The {@link #coordinate} method in turn will use the values from the `fieldCategory`
- * array to set data attributes of the series sprite. E.g., in case of CandleStick series,
- * the following attributes will be set based on the values in the `fieldCategoryY` array:
- *
- * `dataOpen`, `dataHigh`, `dataLow`, `dataClose`
- *
- * Where the value of each attribute is a coordinated array of data from the corresponding
- * field.
- *
- * @param {String[]} fieldCategory
- * @return {String[]}
- */
- getFields: function(fieldCategory) {
- var me = this,
- fields = [],
- ln = fieldCategory.length,
- i, field;
- for (i = 0; i < ln; i++) {
- field = me['get' + fieldCategory[i] + 'Field']();
- if (Ext.isArray(field)) {
- fields.push.apply(fields, field);
- } else {
- fields.push(field);
- }
- }
- return fields;
- },
- applyAnimation: function(animation, oldAnimation) {
- var chart = this.getChart();
- if (!chart.isSettingSeriesAnimation) {
- this.isUserAnimation = true;
- }
- return Ext.chart.Util.applyAnimation(animation, oldAnimation);
- },
- updateAnimation: function(animation) {
- var sprites = this.getSprites(),
- itemsMarker, markersMarker, i, ln, sprite;
- for (i = 0 , ln = sprites.length; i < ln; i++) {
- sprite = sprites[i];
- if (sprite.isMarkerHolder) {
- itemsMarker = sprite.getMarker('items');
- if (itemsMarker) {
- itemsMarker.getTemplate().setAnimation(animation);
- }
- markersMarker = sprite.getMarker('markers');
- if (markersMarker) {
- markersMarker.getTemplate().setAnimation(animation);
- }
- }
- sprite.setAnimation(animation);
- }
- },
- getAnimation: function() {
- var chart = this.getChart(),
- animation;
- if (chart && chart.animationSuspendCount) {
- animation = {
- duration: 0
- };
- } else {
- if (this.isUserAnimation) {
- animation = this.callParent();
- } else {
- animation = chart.getAnimation();
- }
- }
- return animation;
- },
- updateTitle: function() {
- var me = this,
- chart = me.getChart();
- if (chart && !chart.isInitializing) {
- chart.refreshLegendStore();
- }
- },
- applyHighlight: function(highlight, oldHighlight) {
- var me = this,
- highlightCfg = me.getHighlightCfg();
- if (Ext.isObject(highlight)) {
- highlight = Ext.merge({}, highlightCfg, highlight);
- } else if (highlight === true) {
- highlight = highlightCfg;
- }
- if (highlight) {
- highlight.type = 'highlight';
- }
- return highlight && Ext.merge({}, oldHighlight, highlight);
- },
- updateHighlight: function(highlight) {
- var me = this,
- sprites = me.sprites,
- highlightCfg = me.getHighlightCfg(),
- i, ln, sprite, items, markers;
- me.getStyle();
- // Make sure the 'markers' sprite has been created,
- // so that we can set the 'style' config of its 'highlight' modifier here.
- me.getMarker();
- if (!Ext.Object.isEmpty(highlight)) {
- me.addItemHighlight();
- for (i = 0 , ln = sprites.length; i < ln; i++) {
- sprite = sprites[i];
- if (sprite.isMarkerHolder) {
- items = sprite.getMarker('items');
- if (items) {
- items.getTemplate().modifiers.highlight.setStyle(highlight);
- }
- markers = sprite.getMarker('markers');
- if (markers) {
- markers.getTemplate().modifiers.highlight.setStyle(highlight);
- }
- }
- }
- } else if (!Ext.Object.equals(highlightCfg, this.defaultConfig.highlightCfg)) {
- this.addItemHighlight();
- }
- },
- updateHighlightCfg: function(highlightCfg) {
- // Make sure to add the 'itemhighlight' interaction to the series, if the default
- // highlight style changes, even if the 'highlight' config isn't set (defaults to false),
- // since we probably want to use item highlighting now or later, if we are changing
- // the default highlight style.
- // This updater will be triggered by the 'highlight' applier, and the 'addItemHighlight'
- // call here will in turn call 'getHighlight' down the call stack, which will return
- // 'undefined' since the value hasn't been processed yet. So we don't call
- // 'addItemHighlight' here during configuration and instead call it in the 'highlight'
- // updater, if it hasn't already been called ('highlight' config is set to 'false').
- if (!this.isConfiguring && !Ext.Object.equals(highlightCfg, this.defaultConfig.highlightCfg)) {
- this.addItemHighlight();
- }
- },
- applyItemInstancing: function(config, oldConfig) {
- if (config && oldConfig && (!config.type || config.type === oldConfig.type)) {
- // Have to merge to a new object, or the updater won't be called.
- config = Ext.merge({}, oldConfig, config);
- }
- if (config && !config.type) {
- config = null;
- }
- return config;
- },
- setAttributesForItem: function(item, change) {
- var sprite = item && item.sprite,
- i;
- if (sprite) {
- if (sprite.isMarkerHolder && item.category === 'items') {
- sprite.putMarker(item.category, change, item.index, false, true);
- }
- if (sprite.isMarkerHolder && item.category === 'markers') {
- sprite.putMarker(item.category, change, item.index, false, true);
- } else if (sprite.isInstancing) {
- sprite.setAttributesFor(item.index, change);
- } else if (Ext.isArray(sprite)) {
- // In some instances, like with the 3D pie series,
- // an item can be composed of multiple sprites
- // (e.g. 8 sprites are used to render a single 3D pie slice).
- for (i = 0; i < sprite.length; i++) {
- sprite[i].setAttributes(change);
- }
- } else {
- sprite.setAttributes(change);
- }
- }
- },
- getBBoxForItem: function(item) {
- var sprite = item && item.sprite,
- result = null;
- if (sprite) {
- if (sprite.getMarker('items') && item.category === 'items') {
- result = sprite.getMarkerBBox(item.category, item.index);
- } else if (sprite instanceof Ext.draw.sprite.Instancing) {
- result = sprite.getBBoxFor(item.index);
- } else {
- result = sprite.getBBox();
- }
- }
- return result;
- },
- /**
- * @private
- * @property
- * The range of "coordinated" data.
- * Typically, for two directions ('X' and 'Y') the `dataRange` would look like this:
- *
- * dataRange[0] - minX
- * dataRange[1] - minY
- * dataRange[2] - maxX
- * dataRange[3] - maxY
- *
- * And the series' {@link #coordinate} method would be called like this:
- *
- * coordinate('X', 0, 2)
- * coordinate('Y', 1, 2)
- *
- * For numbers, coordinated data are numbers themselves.
- * For categories - their indexes.
- * For Date objects - their timestamps.
- * In other words, whatever source data we have, it has to be converted to numbers
- * before it can be plotted.
- */
- dataRange: null,
- constructor: function(config) {
- var me = this,
- id;
- config = config || {};
- // Backward compatibility with Ext.
- if (config.tips) {
- config = Ext.apply({
- tooltip: config.tips
- }, config);
- }
- // Backward compatibility with Touch.
- if (config.highlightCfg) {
- config = Ext.apply({
- highlight: config.highlightCfg
- }, config);
- }
- if ('id' in config) {
- id = config.id;
- } else if ('id' in me.config) {
- id = me.config.id;
- } else {
- id = me.getId();
- }
- me.setId(id);
- me.sprites = [];
- me.dataRange = [];
- me.mixins.observable.constructor.call(me, config);
- me.initBindable();
- },
- lookupViewModel: function(skipThis) {
- // Override the Bindable's method to redirect view model
- // lookup to the chart.
- var chart = this.getChart();
- return chart ? chart.lookupViewModel(skipThis) : null;
- },
- applyTooltip: function(tooltip, oldTooltip) {
- var config = Ext.apply({
- xtype: 'tooltip',
- renderer: Ext.emptyFn,
- constrainPosition: true,
- shrinkWrapDock: true,
- autoHide: true,
- hideDelay: 200,
- mouseOffset: [
- 20,
- 20
- ],
- trackMouse: true
- }, tooltip);
- return Ext.create(config);
- },
- updateTooltip: function() {
- // Tooltips can't work without the 'itemhighlight' or the 'itemedit' interaction.
- this.addItemHighlight();
- },
- // Adds the 'itemhighlight' interaction to the chart that owns the series.
- addItemHighlight: function() {
- var chart = this.getChart(),
- interactions, interaction, hasRequiredInteraction, i;
- if (!chart) {
- return;
- }
- interactions = chart.getInteractions();
- for (i = 0; i < interactions.length; i++) {
- interaction = interactions[i];
- if (interaction.isItemHighlight || interaction.isItemEdit) {
- hasRequiredInteraction = true;
- break;
- }
- }
- if (!hasRequiredInteraction) {
- interactions.push('itemhighlight');
- chart.setInteractions(interactions);
- }
- },
- showTooltip: function(item, event) {
- var me = this,
- tooltip = me.getTooltip();
- if (!tooltip) {
- return;
- }
- Ext.callback(tooltip.renderer, tooltip.scope, [
- tooltip,
- item.record,
- item
- ], 0, me);
- tooltip.showBy(event);
- },
- showTooltipAt: function(item, x, y) {
- var me = this,
- tooltip = me.getTooltip(),
- mouseOffset = tooltip.config.mouseOffset;
- if (!tooltip || !tooltip.showAt) {
- return;
- }
- if (mouseOffset) {
- x += mouseOffset[0];
- y += mouseOffset[1];
- }
- Ext.callback(tooltip.renderer, tooltip.scope, [
- tooltip,
- item.record,
- item
- ], 0, me);
- tooltip.showAt([
- x,
- y
- ]);
- },
- hideTooltip: function(item, immediate) {
- var me = this,
- tooltip = me.getTooltip();
- if (!tooltip) {
- return;
- }
- if (immediate) {
- tooltip.hide();
- } else {
- tooltip.delayHide();
- }
- },
- applyStore: function(store) {
- return store && Ext.StoreManager.lookup(store);
- },
- getStore: function() {
- return this._store || this.getChart() && this.getChart().getStore();
- },
- updateStore: function(newStore, oldStore) {
- var me = this,
- chart = me.getChart(),
- chartStore = chart && chart.getStore(),
- sprites, sprite, len, i;
- oldStore = oldStore || chartStore;
- if (oldStore && oldStore !== newStore) {
- oldStore.un({
- datachanged: 'onDataChanged',
- update: 'onDataChanged',
- scope: me
- });
- }
- if (newStore) {
- newStore.on({
- datachanged: 'onDataChanged',
- update: 'onDataChanged',
- scope: me
- });
- sprites = me.getSprites();
- for (i = 0 , len = sprites.length; i < len; i++) {
- sprite = sprites[i];
- if (sprite.setStore) {
- sprite.setStore(newStore);
- }
- }
- me.onDataChanged();
- }
- me.fireEvent('storechange', me, newStore, oldStore);
- },
- onStoreChange: function(chart, newStore, oldStore) {
- if (!this._store) {
- this.updateStore(newStore, oldStore);
- }
- },
- defaultRange: [
- 0,
- 1
- ],
- /**
- * @private
- * @param direction {'X'/'Y'}
- * @param directionOffset
- * @param directionCount
- */
- coordinate: function(direction, directionOffset, directionCount) {
- var me = this,
- store = me.getStore(),
- hidden = me.getHidden(),
- items = store.getData().items,
- axis = me['get' + direction + 'Axis'](),
- dataRange = [
- NaN,
- NaN
- ],
- fieldCategory = me['fieldCategory' + direction] || [
- direction
- ],
- fields = me.getFields(fieldCategory),
- i, field, data,
- style = {},
- sprites = me.getSprites(),
- axisRange;
- if (sprites.length && !Ext.isBoolean(hidden) || !hidden) {
- for (i = 0; i < fieldCategory.length; i++) {
- field = fields[i];
- data = me.coordinateData(items, field, axis);
- Ext.chart.Util.expandRange(dataRange, data);
- style['data' + fieldCategory[i]] = data;
- }
- // We don't want to expand the range that has a span of 0 here
- // (e.g. [5, 5] that we'd get if all values for a field are 5).
- // We only want to do this in the Axis, when we calculate the
- // combined range.
- // This is because, if we try to expand the range of values here,
- // and we have multiple fields, the combined range for the axis
- // may not represent the actual range of the data.
- // E.g. if other fields have non-zero span ranges like [4.95, 5.03],
- // [4.91, 5.08], and if the `padding` param to `validateRange` is 0.5,
- // the range of the axis will end up being [4.5, 5.5], because the
- // [5, 5] range of one of the series was expanded to [4.5, 5.5]
- // which encompasses the rest of the ranges.
- dataRange = Ext.chart.Util.validateRange(dataRange, me.defaultRange, 0);
- // See `dataRange` docs.
- me.dataRange[directionOffset] = dataRange[0];
- me.dataRange[directionOffset + directionCount] = dataRange[1];
- style['dataMin' + direction] = dataRange[0];
- style['dataMax' + direction] = dataRange[1];
- if (axis) {
- axisRange = axis.getRange(true);
- axis.setBoundSeriesRange(axisRange);
- }
- for (i = 0; i < sprites.length; i++) {
- sprites[i].setAttributes(style);
- }
- }
- },
- /**
- * @private
- * This method will return an array containing data coordinated by a specific axis.
- * @param {Array} items Store records.
- * @param {String} field The field to fetch from each record.
- * @param {Ext.chart.axis.Axis} axis The axis used to lay out the data.
- * @return {Array}
- */
- coordinateData: function(items, field, axis) {
- var data = [],
- length = items.length,
- layout = axis && axis.getLayout(),
- i, x;
- for (i = 0; i < length; i++) {
- x = items[i].data[field];
- // An empty string (a valid discrete axis value) will be coordinated
- // by the axis layout (if axis is given), otherwise it will be converted
- // to zero (via +'').
- if (!Ext.isEmpty(x, true)) {
- if (layout) {
- data[i] = layout.getCoordFor(x, field, i, items);
- } else {
- x = +x;
- // 'x' can be a category name here.
- data[i] = Ext.isNumber(x) ? x : i;
- }
- } else {
- data[i] = x;
- }
- }
- return data;
- },
- updateLabelData: function() {
- var label = this.getLabel();
- if (!label) {
- return;
- }
- // eslint-disable-next-line vars-on-top, one-var
- var store = this.getStore(),
- items = store.getData().items,
- sprites = this.getSprites(),
- labelTpl = label.getTemplate(),
- labelFields = Ext.Array.from(labelTpl.getField()),
- i, j, ln, labels, sprite, field;
- if (!sprites.length || !labelFields.length) {
- return;
- }
- for (i = 0; i < sprites.length; i++) {
- sprite = sprites[i];
- if (!sprite.getField) {
- // The 'gauge' series is misnormer, its sprites
- // do not extend from the base Series sprite and
- // so do not have the 'field' config. They also
- // don't support labels in the traditional sense.
-
- continue;
- }
- labels = [];
- field = sprite.getField();
- if (Ext.Array.indexOf(labelFields, field) < 0) {
- field = labelFields[i];
- }
- for (j = 0 , ln = items.length; j < ln; j++) {
- labels.push(items[j].get(field));
- }
- sprite.setAttributes({
- labels: labels
- });
- }
- },
- /**
- * @private
- *
- * *** Data processing overview. ***
- *
- * The data is processed in the following order:
- *
- * 1) chart.processData() - calls `processData` of all series
- * 2) series.processData() - calls `processData` of all bound axes,
- * or jumps to (5) directly, if the series has no axis
- * in this direction
- * 3) axis.processData() - calls the `processData` of its own layout
- * 4) axisLayout.processData() - calls `coordinateX/Y` of all bound series
- * 5) series.coordinateX/Y - calls its own `coordinate` method in that direction
- * 6) series.coordinate - calls its own `coordinateData` method using the right
- * record fields and axes
- * 7) series.coordinateData - calls `getCoordFor` of the axis layout for the given
- * field
- * 8) layout.getCoordFor - returns a numeric value for the given field value,
- * whatever its type may be
- *
- * The `dataX`, `dataY` attributes of the series' sprites are set by the
- * `series.coordinate` method using the data returned by the `coordinateData`.
- * `series.coordinate` also calculates the range of said data (via `expandRange`)
- * and sets the `dataMinX/Y`, `dataMaxX/Y` attributes of the series' sprites.
- */
- processData: function() {
- var me = this,
- directions = me.directions,
- direction, axis, name, i, ln;
- if (me.isProcessingData || !me.getStore()) {
- return;
- }
- me.isProcessingData = true;
- for (i = 0 , ln = directions.length; i < ln; i++) {
- direction = directions[i];
- axis = me['get' + direction + 'Axis']();
- if (axis) {
- axis.processData(me);
-
- continue;
- }
- name = 'coordinate' + direction;
- if (me[name]) {
- me[name]();
- }
- }
- me.updateLabelData();
- me.isProcessingData = false;
- },
- applyBackground: function(background) {
- var surface, result;
- if (this.getChart()) {
- surface = this.getSurface();
- surface.setBackground(background);
- result = surface.getBackground();
- } else {
- result = background;
- }
- return result;
- },
- updateChart: function(newChart, oldChart) {
- var me = this,
- store = me._store;
- if (oldChart) {
- oldChart.un('axeschange', 'onAxesChange', me);
- me.clearSprites();
- me.setSurface(null);
- me.setOverlaySurface(null);
- oldChart.unregister(me);
- me.onChartDetached(oldChart);
- if (!store) {
- me.updateStore(null);
- }
- }
- if (newChart) {
- me.setSurface(newChart.getSurface('series'));
- me.setOverlaySurface(newChart.getSurface('overlay'));
- newChart.on('axeschange', 'onAxesChange', me);
- // TODO: Gauge series should render correctly when chart's store is missing.
- // TODO: When store is initially missing the getAxes will return null here,
- // TODO: since applyAxes has actually triggered this series.updateChart call
- // TODO: indirectly.
- // TODO: Figure out why it doesn't go this route when a store is present.
- if (newChart.getAxes()) {
- me.onAxesChange(newChart);
- }
- me.onChartAttached(newChart);
- newChart.register(me);
- if (!store) {
- me.updateStore(newChart.getStore());
- }
- }
- },
- onAxesChange: function(chart, force) {
- if (chart.destroying || chart.destroyed) {
- return;
- }
- // eslint-disable-next-line vars-on-top
- var me = this,
- axes = chart.getAxes(),
- axis,
- directionToAxesMap = {},
- directionToFieldsMap = {},
- needHighPrecision = false,
- directions = this.directions,
- direction, i, ln;
- for (i = 0 , ln = directions.length; i < ln; i++) {
- direction = directions[i];
- directionToFieldsMap[direction] = me.getFields(me['fieldCategory' + direction]);
- }
- for (i = 0 , ln = axes.length; i < ln; i++) {
- axis = axes[i];
- direction = axis.getDirection();
- if (!directionToAxesMap[direction]) {
- directionToAxesMap[direction] = [
- axis
- ];
- } else {
- directionToAxesMap[direction].push(axis);
- }
- }
- for (i = 0 , ln = directions.length; i < ln; i++) {
- direction = directions[i];
- if (!force && me['get' + direction + 'Axis']()) {
-
- continue;
- }
- if (directionToAxesMap[direction]) {
- axis = me.findMatchingAxis(directionToAxesMap[direction], directionToFieldsMap[direction]);
- if (axis) {
- me['set' + direction + 'Axis'](axis);
- if (axis.getNeedHighPrecision()) {
- needHighPrecision = true;
- }
- }
- }
- }
- this.getSurface().setHighPrecision(needHighPrecision);
- },
- /**
- * @private
- * Given the list of axes in a certain direction and a list of series fields in that
- * direction returns the first matching axis for the series in that direction,
- * or undefined if a match wasn't found.
- */
- findMatchingAxis: function(directionAxes, directionFields) {
- var axis, axisFields, i, j;
- for (i = 0; i < directionAxes.length; i++) {
- axis = directionAxes[i];
- axisFields = axis.getFields();
- if (!axisFields.length) {
- return axis;
- } else if (directionFields) {
- for (j = 0; j < directionFields.length; j++) {
- if (Ext.Array.indexOf(axisFields, directionFields[j]) >= 0) {
- return axis;
- }
- }
- }
- }
- },
- onChartDetached: function(oldChart) {
- var me = this;
- me.fireEvent('chartdetached', oldChart, me);
- oldChart.un('storechange', 'onStoreChange', me);
- },
- onChartAttached: function(chart) {
- var me = this;
- me.fireEvent('chartattached', chart, me);
- chart.on('storechange', 'onStoreChange', me);
- me.processData();
- },
- updateOverlaySurface: function(overlaySurface) {
- var label = this.getLabel();
- if (overlaySurface && label) {
- overlaySurface.add(label);
- }
- },
- getLabel: function() {
- return this.labelMarker;
- },
- setLabel: function(label) {
- var me = this,
- chart = me.getChart(),
- marker = me.labelMarker,
- template;
- // The label sprite is reused unless the value of 'label' is falsy,
- // so that we can transition from one attribute set to another with an
- // animation, which is important for example during theme switching.
- if (!label && marker) {
- marker.getTemplate().destroy();
- marker.destroy();
- me.labelMarker = marker = null;
- }
- if (label) {
- if (!marker) {
- marker = me.labelMarker = new Ext.chart.Markers({
- zIndex: 10
- });
- marker.setTemplate(new Ext.chart.sprite.Label());
- me.getOverlaySurface().add(marker);
- }
- template = marker.getTemplate();
- template.setAttributes(label);
- template.setConfig(label);
- if (label.field) {
- template.setField(label.field);
- }
- if (label.display) {
- marker.setAttributes({
- hidden: label.display === 'none'
- });
- }
- marker.setDirty(true);
- }
- // Inform the label about the template change.
- me.updateLabelData();
- if (chart && !chart.isInitializing && !me.isConfiguring) {
- chart.redraw();
- }
- },
- createItemInstancingSprite: function(sprite, itemInstancing) {
- var me = this,
- markers = new Ext.chart.Markers(),
- config = Ext.apply({
- modifiers: 'highlight'
- }, itemInstancing),
- style = me.getStyle(),
- template, animation;
- markers.setAttributes({
- zIndex: Number.MAX_VALUE
- });
- markers.setTemplate(config);
- template = markers.getTemplate();
- template.setAttributes(style);
- animation = template.getAnimation();
- animation.on('animationstart', 'onSpriteAnimationStart', this);
- animation.on('animationend', 'onSpriteAnimationEnd', this);
- sprite.bindMarker('items', markers);
- me.getSurface().add(markers);
- return markers;
- },
- getDefaultSpriteConfig: function() {
- return {
- type: this.seriesType,
- renderer: this.getRenderer()
- };
- },
- updateRenderer: function(renderer) {
- var me = this,
- chart = me.getChart();
- if (chart && chart.isInitializing) {
- return;
- }
- // We have to be careful and not call the 'getSprites' method here, as this
- // method itself may have been called by the 'getSprites' method indirectly already.
- if (me.sprites.length) {
- me.sprites[0].setAttributes({
- renderer: renderer || null
- });
- if (chart && !chart.isInitializing) {
- chart.redraw();
- }
- }
- },
- updateShowMarkers: function(showMarkers) {
- var sprite = this.getSprite(),
- markers = sprite && sprite.getMarker('markers');
- if (markers) {
- markers.getTemplate().setAttributes({
- hidden: !showMarkers
- });
- }
- },
- createSprite: function() {
- var me = this,
- surface = me.getSurface(),
- itemInstancing = me.getItemInstancing(),
- sprite = surface.add(me.getDefaultSpriteConfig()),
- animation, label;
- sprite.setAttributes(me.getStyle());
- sprite.setSeries(me);
- if (itemInstancing) {
- me.createItemInstancingSprite(sprite, itemInstancing);
- }
- if (sprite.isMarkerHolder) {
- label = me.getLabel();
- if (label && label.getTemplate().getField()) {
- sprite.bindMarker('labels', label);
- }
- }
- if (sprite.setStore) {
- sprite.setStore(me.getStore());
- }
- animation = sprite.getAnimation();
- animation.on('animationstart', 'onSpriteAnimationStart', me);
- animation.on('animationend', 'onSpriteAnimationEnd', me);
- me.sprites.push(sprite);
- return sprite;
- },
- /**
- * @method
- * Returns the read-only array of sprites the are used to draw this series.
- */
- getSprites: null,
- /**
- * @private
- * Returns the first sprite. Convenience method for series that have
- * a single markerholder sprite.
- */
- getSprite: function() {
- var sprites = this.getSprites();
- return sprites && sprites[0];
- },
- /**
- * @private
- */
- withSprite: function(fn) {
- var sprite = this.getSprite();
- return sprite && fn(sprite) || undefined;
- },
- forEachSprite: function(fn) {
- var sprites = this.getSprites(),
- i, ln;
- for (i = 0 , ln = sprites.length; i < ln; i++) {
- fn(sprites[i]);
- }
- },
- onDataChanged: function() {
- var me = this,
- chart = me.getChart(),
- chartStore = chart && chart.getStore(),
- seriesStore = me.getStore();
- if (seriesStore !== chartStore) {
- me.processData();
- }
- },
- isXType: function(xtype) {
- return xtype === 'series';
- },
- getItemId: function() {
- return this.getId();
- },
- applyThemeStyle: function(theme, oldTheme) {
- var me = this,
- fill, stroke;
- fill = theme && theme.subStyle && theme.subStyle.fillStyle;
- stroke = fill && theme.subStyle.strokeStyle;
- if (fill && !stroke) {
- theme.subStyle.strokeStyle = me.getStrokeColorsFromFillColors(fill);
- }
- fill = theme && theme.markerSubStyle && theme.markerSubStyle.fillStyle;
- stroke = fill && theme.markerSubStyle.strokeStyle;
- if (fill && !stroke) {
- theme.markerSubStyle.strokeStyle = me.getStrokeColorsFromFillColors(fill);
- }
- return Ext.apply(oldTheme || {}, theme);
- },
- applyStyle: function(style, oldStyle) {
- return Ext.apply({}, style, oldStyle);
- },
- applySubStyle: function(subStyle, oldSubStyle) {
- var name = Ext.ClassManager.getNameByAlias('sprite.' + this.seriesType),
- cls = Ext.ClassManager.get(name);
- if (cls && cls.def) {
- subStyle = cls.def.batchedNormalize(subStyle, true);
- }
- return Ext.merge({}, oldSubStyle, subStyle);
- },
- applyMarker: function(marker, oldMarker) {
- var type, cls;
- if (marker) {
- if (!Ext.isObject(marker)) {
- marker = {};
- }
- type = marker.type || 'circle';
- if (oldMarker && type === oldMarker.type) {
- marker = Ext.merge({}, oldMarker, marker);
- }
- }
- // Note: reusing the `oldMaker` like `Ext.merge(oldMarker, marker)`
- // isn't possible because the `updateMarker` won't be called.
- if (type) {
- cls = Ext.ClassManager.get(Ext.ClassManager.getNameByAlias('sprite.' + type));
- }
- if (cls && cls.def) {
- marker = cls.def.normalize(marker, true);
- marker.type = type;
- } else {
- marker = null;
- //<debug>
- Ext.log.warn('Invalid series marker type: ' + type);
- }
- //</debug>
- return marker;
- },
- updateMarker: function(marker) {
- var me = this,
- sprites = me.getSprites(),
- seriesSprite, markerSprite, markerTplConfig, i, ln;
- for (i = 0 , ln = sprites.length; i < ln; i++) {
- seriesSprite = sprites[i];
- if (!seriesSprite.isMarkerHolder) {
-
- continue;
- }
- markerSprite = seriesSprite.getMarker('markers');
- if (marker) {
- if (!markerSprite) {
- markerSprite = new Ext.chart.Markers();
- seriesSprite.bindMarker('markers', markerSprite);
- me.getOverlaySurface().add(markerSprite);
- }
- markerTplConfig = Ext.Object.merge({
- modifiers: 'highlight'
- }, marker);
- markerSprite.setTemplate(markerTplConfig);
- markerSprite.getTemplate().getAnimation().setCustomDurations({
- translationX: 0,
- translationY: 0
- });
- } else if (markerSprite) {
- seriesSprite.releaseMarker('markers');
- me.getOverlaySurface().remove(markerSprite, true);
- }
- seriesSprite.setDirty(true);
- }
- // If we call, for example, `series.setMarker({type: 'circle'})` on a series
- // that has been already constructed, the newly added marker still has to be
- // themed, and the 'style' config of its 'highlight' modifier has to be set.
- if (!me.isConfiguring) {
- me.doUpdateStyles();
- me.updateHighlight(me.getHighlight());
- }
- },
- applyMarkerSubStyle: function(marker, oldMarker) {
- var type = (marker && marker.type) || (oldMarker && oldMarker.type) || 'circle',
- cls = Ext.ClassManager.get(Ext.ClassManager.getNameByAlias('sprite.' + type));
- if (cls && cls.def) {
- marker = cls.def.batchedNormalize(marker, true);
- }
- return Ext.merge(oldMarker || {}, marker);
- },
- updateHidden: function(hidden) {
- var me = this;
- me.getColors();
- me.getSubStyle();
- me.setSubStyle({
- hidden: hidden
- });
- me.processData();
- me.doUpdateStyles();
- if (!Ext.isArray(hidden)) {
- me.updateLegendStore(hidden);
- }
- },
- /**
- * @private
- * Updates chart's legend store when the value of the series' {@link #hidden} config
- * changes or when the {@link #setHiddenByIndex} method is called.
- * @param hidden Whether series (or its component) should be hidden or not.
- * @param index Used for stacked series.
- * If present, only the component with the specified index will change
- * visibility.
- */
- updateLegendStore: function(hidden, index) {
- var me = this,
- chart = me.getChart(),
- legendStore = chart && chart.getLegendStore(),
- id = me.getId(),
- record;
- if (legendStore) {
- if (arguments.length > 1) {
- record = legendStore.findBy(function(rec) {
- return rec.get('series') === id && rec.get('index') === index;
- });
- if (record !== -1) {
- record = legendStore.getAt(record);
- }
- } else {
- record = legendStore.findRecord('series', id);
- }
- if (record && record.get('disabled') !== hidden) {
- record.set('disabled', hidden);
- }
- }
- },
- /**
- *
- * @param {Number} index
- * @param {Boolean} value
- */
- setHiddenByIndex: function(index, value) {
- var me = this;
- if (Ext.isArray(me.getHidden())) {
- // Multi-sprite series like Pie and StackedCartesian.
- me.getHidden()[index] = value;
- me.updateHidden(me.getHidden());
- me.updateLegendStore(value, index);
- } else {
- me.setHidden(value);
- }
- },
- getStrokeColorsFromFillColors: function(colors) {
- var me = this,
- darker = me.getUseDarkerStrokeColor(),
- darkerRatio = (Ext.isNumber(darker) ? darker : me.darkerStrokeRatio),
- strokeColors;
- if (darker) {
- strokeColors = Ext.Array.map(colors, function(color) {
- color = Ext.isString(color) ? color : color.stops[0].color;
- color = Ext.util.Color.fromString(color);
- return color.createDarker(darkerRatio).toString();
- });
- } else {
- strokeColors = Ext.Array.clone(colors);
- }
- return strokeColors;
- },
- updateThemeColors: function(colors) {
- var me = this,
- theme = me.getThemeStyle(),
- fillColors = Ext.Array.clone(colors),
- strokeColors = me.getStrokeColorsFromFillColors(colors),
- newSubStyle = {
- fillStyle: fillColors,
- strokeStyle: strokeColors
- };
- theme.subStyle = Ext.apply(theme.subStyle || {}, newSubStyle);
- theme.markerSubStyle = Ext.apply(theme.markerSubStyle || {}, newSubStyle);
- me.doUpdateStyles();
- if (!me.isConfiguring) {
- me.getChart().refreshLegendStore();
- }
- },
- themeOnlyIfConfigured: {},
- updateTheme: function(theme) {
- var me = this,
- seriesTheme = theme.getSeries(),
- initialConfig = me.getInitialConfig(),
- defaultConfig = me.defaultConfig,
- configs = me.self.getConfigurator().configs,
- genericSeriesTheme = seriesTheme.defaults,
- specificSeriesTheme = seriesTheme[me.type],
- themeOnlyIfConfigured = me.themeOnlyIfConfigured,
- key, value, isObjValue, isUnusedConfig, initialValue, cfg;
- seriesTheme = Ext.merge({}, genericSeriesTheme, specificSeriesTheme);
- for (key in seriesTheme) {
- value = seriesTheme[key];
- cfg = configs[key];
- if (value !== null && value !== undefined && cfg) {
- initialValue = initialConfig[key];
- isObjValue = Ext.isObject(value);
- isUnusedConfig = initialValue === defaultConfig[key];
- if (isObjValue) {
- if (isUnusedConfig && themeOnlyIfConfigured[key]) {
-
- continue;
- }
- value = Ext.merge({}, value, initialValue);
- }
- if (isUnusedConfig || isObjValue) {
- me[cfg.names.set](value);
- }
- }
- }
- },
- /**
- * @private
- * When the chart's "colors" config changes, these colors are passed onto the series
- * where they are used with the same priority as theme colors, i.e. they do not override
- * the series' "colors" config, nor the series' "style" config, but they do override
- * the colors from the theme's "seriesThemes" config.
- */
- updateChartColors: function(colors) {
- var me = this;
- if (!me.getColors()) {
- me.updateThemeColors(colors);
- }
- },
- updateColors: function(colors) {
- var chart;
- this.updateThemeColors(colors);
- if (!this.isConfiguring) {
- chart = this.getChart();
- if (chart) {
- chart.refreshLegendStore();
- }
- }
- },
- updateStyle: function() {
- this.doUpdateStyles();
- },
- updateSubStyle: function() {
- this.doUpdateStyles();
- },
- updateThemeStyle: function() {
- this.doUpdateStyles();
- },
- doUpdateStyles: function() {
- var me = this,
- sprites = me.sprites,
- itemInstancing = me.getItemInstancing(),
- ln = sprites && sprites.length,
- // 'showMarkers' updater calls 'series.getSprites()',
- // which we don't want to call here.
- showMarkers = me.getConfig('showMarkers', true),
- // eslint-disable-line no-unused-vars
- style, sprite, marker, i;
- for (i = 0; i < ln; i++) {
- sprite = sprites[i];
- style = me.getStyleByIndex(i);
- if (itemInstancing) {
- sprite.getMarker('items').getTemplate().setAttributes(style);
- }
- sprite.setAttributes(style);
- marker = sprite.isMarkerHolder && sprite.getMarker('markers');
- if (marker) {
- marker.getTemplate().setAttributes(me.getMarkerStyleByIndex(i));
- }
- }
- },
- getStyleWithTheme: function() {
- var me = this,
- theme = me.getThemeStyle(),
- style = Ext.clone(me.getStyle());
- if (theme && theme.style) {
- Ext.applyIf(style, theme.style);
- }
- return style;
- },
- getSubStyleWithTheme: function() {
- var me = this,
- theme = me.getThemeStyle(),
- subStyle = Ext.clone(me.getSubStyle());
- if (theme && theme.subStyle) {
- Ext.applyIf(subStyle, theme.subStyle);
- }
- return subStyle;
- },
- getStyleByIndex: function(i) {
- var me = this,
- theme = me.getThemeStyle(),
- style, themeStyle, subStyle, themeSubStyle,
- result = {};
- style = me.getStyle();
- themeStyle = (theme && theme.style) || {};
- subStyle = me.styleDataForIndex(me.getSubStyle(), i);
- themeSubStyle = me.styleDataForIndex((theme && theme.subStyle), i);
- Ext.apply(result, themeStyle);
- Ext.apply(result, themeSubStyle);
- Ext.apply(result, style);
- Ext.apply(result, subStyle);
- return result;
- },
- getMarkerStyleByIndex: function(i) {
- var me = this,
- theme = me.getThemeStyle(),
- style, themeStyle, subStyle, themeSubStyle, markerStyle, themeMarkerStyle, markerSubStyle, themeMarkerSubStyle,
- result = {};
- style = me.getStyle();
- themeStyle = (theme && theme.style) || {};
- // 'series.updateHidden()' will update 'series.subStyle.hidden' config
- // with the value of the 'series.hidden' config.
- // But we also need to account for 'series.showMarkers' config
- // to determine whether the markers should be hidden or not.
- subStyle = me.styleDataForIndex(me.getSubStyle(), i);
- if (subStyle.hasOwnProperty('hidden')) {
- subStyle.hidden = subStyle.hidden || !this.getConfig('showMarkers', true);
- }
- themeSubStyle = me.styleDataForIndex((theme && theme.subStyle), i);
- markerStyle = me.getMarker();
- themeMarkerStyle = (theme && theme.marker) || {};
- markerSubStyle = me.getMarkerSubStyle();
- themeMarkerSubStyle = me.styleDataForIndex((theme && theme.markerSubStyle), i);
- Ext.apply(result, themeStyle);
- Ext.apply(result, themeSubStyle);
- Ext.apply(result, themeMarkerStyle);
- Ext.apply(result, themeMarkerSubStyle);
- Ext.apply(result, style);
- Ext.apply(result, subStyle);
- Ext.apply(result, markerStyle);
- Ext.apply(result, markerSubStyle);
- return result;
- },
- styleDataForIndex: function(style, i) {
- var value, name,
- result = {};
- if (style) {
- for (name in style) {
- value = style[name];
- if (Ext.isArray(value)) {
- result[name] = value[i % value.length];
- } else {
- result[name] = value;
- }
- }
- }
- return result;
- },
- /**
- * @method
- * For a given x/y point relative to the main rect, find a corresponding item from this
- * series, if any.
- * @param {Number} x
- * @param {Number} y
- * @param {Object} [target] optional target to receive the result
- * @return {Object} An object describing the item, or null if there is no matching item.
- * The exact contents of this object will vary by series type, but should always contain
- * at least the following:
- *
- * @return {Ext.data.Model} return.record the record of the item.
- * @return {Array} return.point the x/y coordinates relative to the chart box
- * of a single point for this data item, which can be used as e.g. a tooltip anchor
- * point.
- * @return {Ext.draw.sprite.Sprite} return.sprite the item's rendering Sprite.
- * @return {Number} return.subSprite the index if sprite is an instancing sprite.
- */
- getItemForPoint: Ext.emptyFn,
- /**
- * Returns a series item by index and (optional) category.
- * @param {Number} index The index of the item (matches store record index).
- * @param {String} [category] The category of item, e.g.: 'items', 'markers', 'sprites'.
- * @return {Object} item
- */
- getItemByIndex: function(index, category) {
- var me = this,
- sprites = me.getSprites(),
- sprite = sprites && sprites[0],
- item;
- if (!sprite) {
- return;
- }
- // 'category' is not defined, making our best guess here.
- if (category === undefined && sprite.isMarkerHolder) {
- category = me.getItemInstancing() ? 'items' : 'markers';
- } else if (!category || category === '' || category === 'sprites') {
- sprite = sprites[index];
- }
- if (sprite) {
- item = {
- series: me,
- category: category,
- index: index,
- record: me.getStore().getData().items[index],
- field: me.getYField(),
- sprite: sprite
- };
- return item;
- }
- },
- onSpriteAnimationStart: function(sprite) {
- this.fireEvent('animationstart', this, sprite);
- },
- onSpriteAnimationEnd: function(sprite) {
- this.fireEvent('animationend', this, sprite);
- },
- resolveListenerScope: function(defaultScope) {
- // Override the Observable's method to redirect listener scope
- // resolution to the chart.
- var me = this,
- namedScope = Ext._namedScopes[defaultScope],
- chart = me.getChart(),
- scope;
- if (!namedScope) {
- scope = chart ? chart.resolveListenerScope(defaultScope, false) : (defaultScope || me);
- } else if (namedScope.isThis) {
- scope = me;
- } else if (namedScope.isController) {
- scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
- } else if (namedScope.isSelf) {
- scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
- // Class body listener. No chart controller, nor chart container controller.
- if (scope === chart && !chart.getInheritedConfig('defaultListenerScope')) {
- scope = me;
- }
- }
- return scope;
- },
- /**
- * Provide legend information to target array.
- *
- * @param {Array} target
- *
- * The information consists:
- * @param {String} target.name
- * @param {String} target.mark
- * @param {Boolean} target.disabled
- * @param {String} target.series
- * @param {Number} target.index
- */
- provideLegendInfo: function(target) {
- var me = this,
- style = me.getSubStyleWithTheme(),
- fill = style.fillStyle;
- if (Ext.isArray(fill)) {
- fill = fill[0];
- }
- target.push({
- name: me.getTitle() || me.getYField() || me.getId(),
- mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
- disabled: me.getHidden(),
- series: me.getId(),
- index: 0
- });
- },
- clearSprites: function() {
- var sprites = this.sprites,
- sprite, i, ln;
- for (i = 0 , ln = sprites.length; i < ln; i++) {
- sprite = sprites[i];
- if (sprite && sprite.isSprite) {
- sprite.destroy();
- }
- }
- this.sprites = [];
- },
- destroy: function() {
- var me = this,
- store = me._store,
- // Peek at the config so we don't create one just to destroy it
- tooltip = me.getConfig('tooltip', true);
- if (store && store.getAutoDestroy()) {
- Ext.destroy(store);
- }
- me.setChart(null);
- me.clearListeners();
- if (tooltip) {
- Ext.destroy(tooltip);
- }
- me.callParent();
- }
- });
- /**
- * @class Ext.chart.interactions.Abstract
- *
- * Defines a common abstract parent class for all interactions.
- *
- */
- Ext.define('Ext.chart.interactions.Abstract', {
- xtype: 'interaction',
- mixins: {
- observable: 'Ext.mixin.Observable'
- },
- config: {
- /**
- * @cfg {Object} gesture
- * Maps gestures that should be used for starting/maintaining/ending the interaction
- * to corresponding class methods.
- * @private
- */
- gestures: {
- tap: 'onGesture'
- },
- /**
- * @cfg {Ext.chart.AbstractChart} chart The chart that the interaction is bound.
- */
- chart: null,
- /**
- * @cfg {Boolean} enabled 'true' if the interaction is enabled.
- */
- enabled: true
- },
- /**
- * Android device is emerging too many events so if we re-render every frame it will
- * take forever to finish a frame.
- * This throttle technique will limit the timespan between two frames.
- */
- throttleGap: 0,
- stopAnimationBeforeSync: false,
- constructor: function(config) {
- var me = this,
- id;
- config = config || {};
- if ('id' in config) {
- id = config.id;
- } else if ('id' in me.config) {
- id = me.config.id;
- } else {
- id = me.getId();
- }
- me.setId(id);
- me.mixins.observable.constructor.call(me, config);
- },
- updateChart: function(newChart, oldChart) {
- var me = this;
- if (oldChart === newChart) {
- return;
- }
- if (oldChart) {
- oldChart.unregister(me);
- me.removeChartListener(oldChart);
- }
- if (newChart) {
- newChart.register(me);
- me.addChartListener();
- }
- },
- updateEnabled: function(enabled) {
- var me = this,
- chart = me.getChart();
- if (chart) {
- if (enabled) {
- me.addChartListener();
- } else {
- me.removeChartListener(chart);
- }
- }
- },
- /**
- * @method
- * @protected
- * Placeholder method.
- */
- onGesture: Ext.emptyFn,
- /**
- * @protected
- * Find and return a single series item corresponding to the given event,
- * or null if no matching item is found.
- * @param {Event} e
- * @return {Object} the item object or null if none found.
- */
- getItemForEvent: function(e) {
- var me = this,
- chart = me.getChart(),
- chartXY = chart.getEventXY(e);
- return chart.getItemForPoint(chartXY[0], chartXY[1]);
- },
- /**
- * Find and return all series items corresponding to the given event.
- * @param {Event} e
- * @return {Array} array of matching item objects
- * @private
- * @deprecated 6.5.2 This method is deprecated
- */
- getItemsForEvent: function(e) {
- var me = this,
- chart = me.getChart(),
- chartXY = chart.getEventXY(e);
- return chart.getItemsForPoint(chartXY[0], chartXY[1]);
- },
- /**
- * @private
- */
- addChartListener: function() {
- var me = this,
- chart = me.getChart(),
- gestures = me.getGestures(),
- gesture;
- if (!me.getEnabled()) {
- return;
- }
- function insertGesture(name, fn) {
- chart.addElementListener(name, // wrap the handler so it does not fire if the event is locked
- // by another interaction
- me.listeners[name] = function(e) {
- var locks = me.getLocks(),
- result;
- if (me.getEnabled() && (!(name in locks) || locks[name] === me)) {
- result = (Ext.isFunction(fn) ? fn : me[fn]).apply(this, arguments);
- if (result === false && e && e.stopPropagation) {
- e.stopPropagation();
- }
- return result;
- }
- }, me);
- }
- me.listeners = me.listeners || {};
- for (gesture in gestures) {
- insertGesture(gesture, gestures[gesture]);
- }
- },
- removeChartListener: function(chart) {
- var me = this,
- gestures = me.getGestures(),
- gesture;
- function removeGesture(name) {
- var fn = me.listeners[name];
- if (fn) {
- chart.removeElementListener(name, fn);
- delete me.listeners[name];
- }
- }
- if (me.listeners) {
- for (gesture in gestures) {
- removeGesture(gesture);
- }
- }
- },
- lockEvents: function() {
- var me = this,
- locks = me.getLocks(),
- args = Array.prototype.slice.call(arguments),
- i = args.length;
- while (i--) {
- locks[args[i]] = me;
- }
- },
- unlockEvents: function() {
- var locks = this.getLocks(),
- args = Array.prototype.slice.call(arguments),
- i = args.length;
- while (i--) {
- delete locks[args[i]];
- }
- },
- getLocks: function() {
- var chart = this.getChart();
- return chart.lockedEvents || (chart.lockedEvents = {});
- },
- doSync: function() {
- var me = this,
- chart = me.getChart();
- if (me.syncTimer) {
- Ext.undefer(me.syncTimer);
- me.syncTimer = null;
- }
- if (me.stopAnimationBeforeSync) {
- chart.animationSuspendCount++;
- }
- chart.redraw();
- if (me.stopAnimationBeforeSync) {
- chart.animationSuspendCount--;
- }
- me.syncThrottle = Date.now() + me.throttleGap;
- },
- sync: function() {
- var me = this;
- if (me.throttleGap && Ext.frameStartTime < me.syncThrottle) {
- if (me.syncTimer) {
- return;
- }
- me.syncTimer = Ext.defer(function() {
- me.doSync();
- }, me.throttleGap);
- } else {
- me.doSync();
- }
- },
- getItemId: function() {
- return this.getId();
- },
- isXType: function(xtype) {
- return xtype === 'interaction';
- },
- destroy: function() {
- var me = this;
- me.setChart(null);
- delete me.listeners;
- me.callParent();
- }
- }, function() {
- if (Ext.os.is.Android4) {
- this.prototype.throttleGap = 40;
- }
- });
- /**
- * Mixin that provides the functionality to place markers.
- */
- Ext.define('Ext.chart.MarkerHolder', {
- extend: 'Ext.Mixin',
- requires: [
- 'Ext.chart.Markers'
- ],
- mixinConfig: {
- id: 'markerHolder',
- after: {
- constructor: 'constructor',
- preRender: 'preRender'
- },
- before: {
- destroy: 'destroy'
- }
- },
- isMarkerHolder: true,
- // The combined transformation applied to the sprite by its parents.
- // Does not include the transformation matrix of the sprite itself.
- surfaceMatrix: null,
- // The inverse of the above transformation to go back to the original state.
- inverseSurfaceMatrix: null,
- deprecated: {
- 6: {
- methods: {
- /**
- * Returns the markers bound to the given name.
- * @param {String} name The name of the marker (e.g., "items", "labels", etc.).
- * @return {Ext.chart.Markers[]}
- * @method getBoundMarker
- * @deprecated 6.0 Use {@link #getMarker} instead.
- */
- getBoundMarker: {
- message: "Please use the 'getMarker' method instead.",
- fn: function(name) {
- var marker = this.boundMarkers[name];
- return marker ? [
- marker
- ] : marker;
- }
- }
- }
- }
- },
- constructor: function() {
- this.boundMarkers = {};
- this.cleanRedraw = false;
- },
- /**
- * Registers the given marker with the marker holder under the specified name.
- * @param {String} name The name of the marker (e.g., "items", "labels", etc.).
- * @param {Ext.chart.Markers} marker
- */
- bindMarker: function(name, marker) {
- var me = this,
- markers = me.boundMarkers;
- if (marker && marker.isMarkers) {
- //<debug>
- if (markers[name] && markers[name] === marker) {
- Ext.log.warn(me.getId(), " (MarkerHolder): the Markers instance '", marker.getId(), "' is already bound under the name '", name, "'.");
- }
- //</debug>
- me.releaseMarker(name);
- markers[name] = marker;
- marker.on('destroy', me.onMarkerDestroy, me);
- }
- },
- onMarkerDestroy: function(marker) {
- this.releaseMarker(marker);
- },
- /**
- * Unregisters the given marker or a marker with the given name.
- * Providing a name of the marker is more efficient as it avoids lookup.
- * @param marker {String/Ext.chart.Markers}
- * @return {Ext.chart.Markers} Released marker or null.
- */
- releaseMarker: function(marker) {
- var markers = this.boundMarkers,
- name;
- if (marker && marker.isMarkers) {
- for (name in markers) {
- if (markers[name] === marker) {
- delete markers[name];
- break;
- }
- }
- } else {
- name = marker;
- marker = markers[name];
- delete markers[name];
- }
- return marker || null;
- },
- /**
- * Returns the marker bound to the given name (or null). See {@link #bindMarker}.
- * @param {String} name The name of the marker (e.g., "items", "labels", etc.).
- * @return {Ext.chart.Markers}
- */
- getMarker: function(name) {
- return this.boundMarkers[name] || null;
- },
- preRender: function(surface, ctx, rect) {
- var me = this,
- id = me.getId(),
- boundMarkers = me.boundMarkers,
- parent = me.getParent(),
- name, marker, matrix;
- if (me.surfaceMatrix) {
- matrix = me.surfaceMatrix.set(1, 0, 0, 1, 0, 0);
- } else {
- matrix = me.surfaceMatrix = new Ext.draw.Matrix();
- }
- me.cleanRedraw = !me.attr.dirty;
- if (!me.cleanRedraw) {
- for (name in boundMarkers) {
- marker = boundMarkers[name];
- if (marker) {
- marker.clear(id);
- }
- }
- }
- // Parent can be either a sprite (like a composite or instancing)
- // or a surface. First, climb up and apply transformations of the
- // parent sprites.
- while (parent && parent.attr && parent.attr.matrix) {
- matrix.prependMatrix(parent.attr.matrix);
- parent = parent.getParent();
- }
- // Finally, apply the transformation used by the surface.
- matrix.prependMatrix(parent.matrix);
- me.surfaceMatrix = matrix;
- me.inverseSurfaceMatrix = matrix.inverse(me.inverseSurfaceMatrix);
- },
- putMarker: function(name, attr, index, bypassNormalization, keepRevision) {
- var marker = this.boundMarkers[name];
- if (marker) {
- marker.putMarkerFor(this.getId(), attr, index, bypassNormalization, keepRevision);
- }
- },
- getMarkerBBox: function(name, index, isWithoutTransform) {
- var marker = this.boundMarkers[name];
- if (marker) {
- return marker.getMarkerBBoxFor(this.getId(), index, isWithoutTransform);
- }
- },
- destroy: function() {
- var boundMarkers = this.boundMarkers,
- name, marker;
- for (name in boundMarkers) {
- marker = boundMarkers[name];
- marker.destroy();
- }
- }
- });
- /**
- * @private
- * @class Ext.chart.axis.sprite.Axis
- * @extends Ext.draw.sprite.Sprite
- *
- * The axis sprite. Currently all types of the axis will be rendered with this sprite.
- */
- Ext.define('Ext.chart.axis.sprite.Axis', {
- extend: 'Ext.draw.sprite.Sprite',
- alias: 'sprite.axis',
- type: 'axis',
- mixins: {
- markerHolder: 'Ext.chart.MarkerHolder'
- },
- requires: [
- 'Ext.draw.sprite.Text'
- ],
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Boolean} grid 'true' if the axis has a grid.
- */
- grid: 'bool',
- /**
- * @cfg {Boolean} axisLine 'true' if the main line of the axis is drawn.
- */
- axisLine: 'bool',
- /**
- * @cfg {Boolean} minorTicks 'true' if the axis has sub ticks.
- */
- minorTicks: 'bool',
- /**
- * @cfg {Number} minorTickSize The length of the minor ticks.
- */
- minorTickSize: 'number',
- /**
- * @cfg {Boolean} majorTicks 'true' if the axis has major ticks.
- */
- majorTicks: 'bool',
- /**
- * @cfg {Number} majorTickSize The length of the major ticks.
- */
- majorTickSize: 'number',
- /**
- * @cfg {Number} length The total length of the axis.
- */
- length: 'number',
- /**
- * @private
- * @cfg {Number} startGap Axis start determined by the chart inset padding.
- */
- startGap: 'number',
- /**
- * @private
- * @cfg {Number} endGap Axis end determined by the chart inset padding.
- */
- endGap: 'number',
- /**
- * @cfg {Number} dataMin The minimum value of the axis data.
- */
- dataMin: 'number',
- /**
- * @cfg {Number} dataMax The maximum value of the axis data.
- */
- dataMax: 'number',
- /**
- * @cfg {Number} visibleMin The minimum value that is displayed.
- */
- visibleMin: 'number',
- /**
- * @cfg {Number} visibleMax The maximum value that is displayed.
- */
- visibleMax: 'number',
- /**
- * @cfg {String} position The position of the axis on the chart.
- */
- position: 'enums(left,right,top,bottom,angular,radial,gauge)',
- /**
- * @cfg {Number} minStepSize The minimum step size between ticks.
- */
- minStepSize: 'number',
- /**
- * @private
- * @cfg {Number} estStepSize The estimated step size between ticks.
- */
- estStepSize: 'number',
- /**
- * @private
- * Unused.
- */
- titleOffset: 'number',
- /**
- * @cfg {Number} [textPadding=0]
- * The padding around axis labels to determine collision.
- * The default is 0 for all axes except horizontal axes of cartesian charts,
- * where the default is 5 to prevent axis labels from blending one into another.
- * This default is defined in the {@link Ext.chart.theme.Base#axis axis} config
- * of the {@link Ext.chart.theme.Base Base} theme.
- * You may want to change this default to a smaller number or 0, if you have
- * horizontal axis labels rotated, which allows for more text to fit in.
- */
- textPadding: 'number',
- /**
- * @cfg {Number} min The minimum value of the axis.
- * `min` and {@link #max} attributes represent the effective range of the axis
- * after segmentation, layout, and range reconciliation between axes.
- */
- min: 'number',
- /**
- * @cfg {Number} max The maximum value of the axis.
- * {@link #min} and `max` attributes represent the effective range of the axis
- * after segmentation, layout, and range reconciliation between axes.
- */
- max: 'number',
- /**
- * @cfg {Number} centerX The central point of the angular axis on the x-axis.
- */
- centerX: 'number',
- /**
- * @cfg {Number} centerY The central point of the angular axis on the y-axis.
- */
- centerY: 'number',
- /**
- * @private
- * @cfg {Number} radius
- * Unused.
- */
- radius: 'number',
- /**
- * @private
- */
- totalAngle: 'number',
- /**
- * @cfg {Number} baseRotation The starting rotation of the angular axis.
- */
- baseRotation: 'number',
- /**
- * @private
- * Unused.
- */
- data: 'default',
- /**
- * @cfg {Boolean} 'true' if the estimated step size is adjusted by text size.
- */
- enlargeEstStepSizeByText: 'bool'
- },
- defaults: {
- grid: false,
- axisLine: true,
- minorTicks: false,
- minorTickSize: 3,
- majorTicks: true,
- majorTickSize: 5,
- length: 0,
- startGap: 0,
- endGap: 0,
- visibleMin: 0,
- visibleMax: 1,
- dataMin: 0,
- dataMax: 1,
- position: '',
- minStepSize: 0,
- estStepSize: 20,
- min: 0,
- max: 1,
- centerX: 0,
- centerY: 0,
- radius: 1,
- baseRotation: 0,
- data: null,
- titleOffset: 0,
- textPadding: 0,
- scalingCenterY: 0,
- scalingCenterX: 0,
- // Override default
- strokeStyle: 'black',
- enlargeEstStepSizeByText: false
- },
- triggers: {
- minorTickSize: 'bbox',
- majorTickSize: 'bbox',
- position: 'bbox,layout',
- axisLine: 'bbox,layout',
- minorTicks: 'layout',
- min: 'layout',
- max: 'layout',
- length: 'layout',
- minStepSize: 'layout',
- estStepSize: 'layout',
- data: 'layout',
- dataMin: 'layout',
- dataMax: 'layout',
- visibleMin: 'layout',
- visibleMax: 'layout',
- enlargeEstStepSizeByText: 'layout'
- },
- updaters: {
- layout: 'layoutUpdater'
- }
- }
- },
- config: {
- /**
- * @cfg {Object} label
- *
- * The label configuration object for the Axis. This object may include style attributes
- * like `spacing`, `padding`, `font` that receives a string or number and
- * returns a new string with the modified values.
- */
- label: null,
- /**
- * @cfg {Number} labelOffset
- * The distance between the label and the edge of a major tick.
- * Only applicable for 'gauge' and 'angular' axes.
- */
- labelOffset: 10,
- /**
- * @cfg {Object|Ext.chart.axis.layout.Layout} layout The layout configuration used by
- * the axis.
- */
- layout: null,
- /**
- * @cfg {Object|Ext.chart.axis.segmenter.Segmenter} segmenter The method of segmenter
- * used by the axis.
- */
- segmenter: null,
- /**
- * @cfg {Function} renderer Allows direct customisation of rendered axis sprites.
- */
- renderer: null,
- /**
- * @private
- * @cfg {Object} layoutContext Stores the context after calculating layout.
- */
- layoutContext: null,
- /**
- * @cfg {Ext.chart.axis.Axis} axis The axis represented by this sprite.
- */
- axis: null
- },
- thickness: 0,
- stepSize: 0,
- getBBox: function() {
- return null;
- },
- defaultRenderer: function(v) {
- // 'this' pointer in this case is a layoutContext
- return this.segmenter.renderer(v, this);
- },
- layoutUpdater: function() {
- var me = this,
- chart = me.getAxis().getChart();
- if (chart.isInitializing) {
- return;
- }
- // eslint-disable-next-line vars-on-top, one-var
- var attr = me.attr,
- layout = me.getLayout(),
- isRtl = chart.getInherited().rtl,
- dataRange = attr.dataMax - attr.dataMin,
- min = attr.dataMin + dataRange * attr.visibleMin,
- max = attr.dataMin + dataRange * attr.visibleMax,
- range = max - min,
- position = attr.position,
- context = {
- attr: attr,
- segmenter: me.getSegmenter(),
- renderer: me.defaultRenderer
- };
- if (position === 'left' || position === 'right') {
- attr.translationX = 0;
- attr.translationY = max * attr.length / range;
- attr.scalingX = 1;
- attr.scalingY = -attr.length / range;
- attr.scalingCenterY = 0;
- attr.scalingCenterX = 0;
- me.applyTransformations(true);
- } else if (position === 'top' || position === 'bottom') {
- if (isRtl) {
- attr.translationX = attr.length + min * attr.length / range + 1;
- } else {
- attr.translationX = -min * attr.length / range;
- }
- attr.translationY = 0;
- attr.scalingX = (isRtl ? -1 : 1) * attr.length / range;
- attr.scalingY = 1;
- attr.scalingCenterY = 0;
- attr.scalingCenterX = 0;
- me.applyTransformations(true);
- }
- if (layout) {
- layout.calculateLayout(context);
- me.setLayoutContext(context);
- }
- },
- iterate: function(snaps, fn) {
- var i, position, id, axis, floatingAxes, floatingValues,
- some = Ext.Array.some,
- abs = Math.abs,
- threshold, isTickVisible;
- if (snaps.getLabel) {
- // Discrete layout.
- if (snaps.min < snaps.from) {
- fn.call(this, snaps.min, snaps.getLabel(snaps.min), -1, snaps);
- }
- for (i = 0; i <= snaps.steps; i++) {
- fn.call(this, snaps.get(i), snaps.getLabel(i), i, snaps);
- }
- if (snaps.max > snaps.to) {
- fn.call(this, snaps.max, snaps.getLabel(snaps.max), snaps.steps + 1, snaps);
- }
- } else {
- axis = this.getAxis();
- floatingAxes = axis.floatingAxes;
- floatingValues = [];
- threshold = (snaps.to - snaps.from) / (snaps.steps + 1);
- if (axis.getFloating()) {
- for (id in floatingAxes) {
- floatingValues.push(floatingAxes[id]);
- }
- }
- // Don't render ticks in axes intersection points.
- isTickVisible = function(position) {
- return !floatingValues.length || some(floatingValues, function(value) {
- return abs(value - position) > threshold;
- });
- };
- if (snaps.min < snaps.from && isTickVisible(snaps.min)) {
- fn.call(this, snaps.min, snaps.min, -1, snaps);
- }
- for (i = 0; i <= snaps.steps; i++) {
- position = snaps.get(i);
- if (isTickVisible(position)) {
- fn.call(this, position, position, i, snaps);
- }
- }
- if (snaps.max > snaps.to && isTickVisible(snaps.max)) {
- fn.call(this, snaps.max, snaps.max, snaps.steps + 1, snaps);
- }
- }
- },
- renderTicks: function(surface, ctx, layout, clipRect) {
- var me = this,
- attr = me.attr,
- docked = attr.position,
- matrix = attr.matrix,
- halfLineWidth = 0.5 * attr.lineWidth,
- xx = matrix.getXX(),
- dx = matrix.getDX(),
- yy = matrix.getYY(),
- dy = matrix.getDY(),
- majorTicks = layout.majorTicks,
- majorTickSize = attr.majorTickSize,
- minorTicks = layout.minorTicks,
- minorTickSize = attr.minorTickSize,
- gaugeAngles;
- /* eslint-disable no-inner-declarations, no-case-declarations */
- if (majorTicks) {
- switch (docked) {
- case 'right':
- function getRightTickFn(size) {
- return function(position, labelText, i) {
- position = surface.roundPixel(position * yy + dy) + halfLineWidth;
- ctx.moveTo(0, position);
- ctx.lineTo(size, position);
- };
- };
- me.iterate(majorTicks, getRightTickFn(majorTickSize));
- if (minorTicks) {
- me.iterate(minorTicks, getRightTickFn(minorTickSize));
- };
- break;
- case 'left':
- function getLeftTickFn(size) {
- return function(position, labelText, i) {
- position = surface.roundPixel(position * yy + dy) + halfLineWidth;
- ctx.moveTo(clipRect[2] - size, position);
- ctx.lineTo(clipRect[2], position);
- };
- };
- me.iterate(majorTicks, getLeftTickFn(majorTickSize));
- if (minorTicks) {
- me.iterate(minorTicks, getLeftTickFn(minorTickSize));
- };
- break;
- case 'bottom':
- function getBottomTickFn(size) {
- return function(position, labelText, i) {
- position = surface.roundPixel(position * xx + dx) - halfLineWidth;
- ctx.moveTo(position, 0);
- ctx.lineTo(position, size);
- };
- };
- me.iterate(majorTicks, getBottomTickFn(majorTickSize));
- if (minorTicks) {
- me.iterate(minorTicks, getBottomTickFn(minorTickSize));
- };
- break;
- case 'top':
- function getTopTickFn(size) {
- return function(position, labelText, i) {
- position = surface.roundPixel(position * xx + dx) - halfLineWidth;
- ctx.moveTo(position, clipRect[3]);
- ctx.lineTo(position, clipRect[3] - size);
- };
- };
- me.iterate(majorTicks, getTopTickFn(majorTickSize));
- if (minorTicks) {
- me.iterate(minorTicks, getTopTickFn(minorTickSize));
- };
- break;
- case 'angular':
- me.iterate(majorTicks, function(position, labelText, i) {
- position = position / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
- ctx.moveTo(attr.centerX + (attr.length) * Math.cos(position), attr.centerY + (attr.length) * Math.sin(position));
- ctx.lineTo(attr.centerX + (attr.length + majorTickSize) * Math.cos(position), attr.centerY + (attr.length + majorTickSize) * Math.sin(position));
- });
- break;
- case 'gauge':
- gaugeAngles = me.getGaugeAngles();
- me.iterate(majorTicks, function(position, labelText, i) {
- position = (position - attr.min) / (attr.max - attr.min) * attr.totalAngle - attr.totalAngle + gaugeAngles.start;
- ctx.moveTo(attr.centerX + (attr.length) * Math.cos(position), attr.centerY + (attr.length) * Math.sin(position));
- ctx.lineTo(attr.centerX + (attr.length + majorTickSize) * Math.cos(position), attr.centerY + (attr.length + majorTickSize) * Math.sin(position));
- });
- break;
- }
- }
- },
- /* eslint-enable no-inner-declarations, no-case-declarations */
- renderLabels: function(surface, ctx, layoutContext, clipRect) {
- var me = this,
- attr = me.attr,
- halfLineWidth = 0.5 * attr.lineWidth,
- docked = attr.position,
- matrix = attr.matrix,
- textPadding = attr.textPadding,
- xx = matrix.getXX(),
- dx = matrix.getDX(),
- yy = matrix.getYY(),
- dy = matrix.getDY(),
- thickness = 0,
- majorTicks = layoutContext.majorTicks,
- tickPadding = Math.max(attr.majorTickSize, attr.minorTickSize) + attr.lineWidth,
- isBBoxIntersect = Ext.draw.Draw.isBBoxIntersect,
- label = me.getLabel(),
- font,
- labelOffset = me.getLabelOffset(),
- lastLabelText = null,
- textSize = 0,
- textCount = 0,
- segmenter = layoutContext.segmenter,
- renderer = me.getRenderer(),
- axis = me.getAxis(),
- title = axis.getTitle(),
- titleBBox = title && title.attr.text !== '' && title.getBBox(),
- labelInverseMatrix,
- lastBBox = null,
- bbox, fly, text, titlePadding, translation, gaugeAngles, angle;
- if (majorTicks && label && !label.attr.hidden) {
- font = label.attr.font;
- if (ctx.font !== font) {
- ctx.font = font;
- }
- // This can profoundly improve performance.
- label.setAttributes({
- translationX: 0,
- translationY: 0
- }, true);
- label.applyTransformations();
- labelInverseMatrix = label.attr.inverseMatrix.elements.slice(0);
- switch (docked) {
- case 'left':
- titlePadding = titleBBox ? titleBBox.x + titleBBox.width : 0;
- switch (label.attr.textAlign) {
- case 'start':
- translation = surface.roundPixel(titlePadding + dx) - halfLineWidth;
- break;
- case 'end':
- translation = surface.roundPixel(clipRect[2] - tickPadding + dx) - halfLineWidth;
- break;
- default:
- // 'center'
- translation = surface.roundPixel(titlePadding + (clipRect[2] - titlePadding - tickPadding) / 2 + dx) - halfLineWidth;
- };
- label.setAttributes({
- translationX: translation
- }, true);
- break;
- case 'right':
- titlePadding = titleBBox ? clipRect[2] - titleBBox.x : 0;
- switch (label.attr.textAlign) {
- case 'start':
- translation = surface.roundPixel(tickPadding + dx) + halfLineWidth;
- break;
- case 'end':
- translation = surface.roundPixel(clipRect[2] - titlePadding + dx) + halfLineWidth;
- break;
- default:
- // 'center'
- translation = surface.roundPixel(tickPadding + (clipRect[2] - tickPadding - titlePadding) / 2 + dx) + halfLineWidth;
- };
- label.setAttributes({
- translationX: translation
- }, true);
- break;
- case 'top':
- titlePadding = titleBBox ? titleBBox.y + titleBBox.height : 0;
- label.setAttributes({
- translationY: surface.roundPixel(titlePadding + (clipRect[3] - titlePadding - tickPadding) / 2) - halfLineWidth
- }, true);
- break;
- case 'bottom':
- titlePadding = titleBBox ? clipRect[3] - titleBBox.y : 0;
- label.setAttributes({
- translationY: surface.roundPixel(tickPadding + (clipRect[3] - tickPadding - titlePadding) / 2) + halfLineWidth
- }, true);
- break;
- case 'radial':
- label.setAttributes({
- translationX: attr.centerX
- }, true);
- break;
- case 'angular':
- label.setAttributes({
- translationY: attr.centerY
- }, true);
- break;
- case 'gauge':
- label.setAttributes({
- translationY: attr.centerY
- }, true);
- break;
- }
- // TODO: there are better ways to detect collision.
- if (docked === 'left' || docked === 'right') {
- me.iterate(majorTicks, function(position, labelText, i) {
- if (labelText === undefined) {
- return;
- }
- if (renderer) {
- text = Ext.callback(renderer, null, [
- axis,
- labelText,
- layoutContext,
- lastLabelText
- ], 0, axis);
- } else {
- text = segmenter.renderer(labelText, layoutContext, lastLabelText);
- }
- lastLabelText = labelText;
- label.setAttributes({
- text: String(text),
- translationY: surface.roundPixel(position * yy + dy)
- }, true);
- label.applyTransformations();
- thickness = Math.max(thickness, label.getBBox().width + tickPadding);
- fly = Ext.draw.Matrix.fly(label.attr.matrix.elements.slice(0));
- bbox = fly.prepend.apply(fly, labelInverseMatrix).transformBBox(label.getBBox(true));
- if (lastBBox && !isBBoxIntersect(bbox, lastBBox, textPadding)) {
- return;
- }
- surface.renderSprite(label);
- lastBBox = bbox;
- textSize += bbox.height;
- textCount++;
- });
- } else if (docked === 'top' || docked === 'bottom') {
- me.iterate(majorTicks, function(position, labelText, i) {
- if (labelText === undefined) {
- return;
- }
- if (renderer) {
- text = Ext.callback(renderer, null, [
- axis,
- labelText,
- layoutContext,
- lastLabelText
- ], 0, axis);
- } else {
- text = segmenter.renderer(labelText, layoutContext, lastLabelText);
- }
- lastLabelText = labelText;
- label.setAttributes({
- text: String(text),
- translationX: surface.roundPixel(position * xx + dx)
- }, true);
- label.applyTransformations();
- thickness = Math.max(thickness, label.getBBox().height + tickPadding);
- fly = Ext.draw.Matrix.fly(label.attr.matrix.elements.slice(0));
- bbox = fly.prepend.apply(fly, labelInverseMatrix).transformBBox(label.getBBox(true));
- if (lastBBox && !isBBoxIntersect(bbox, lastBBox, textPadding)) {
- return;
- }
- surface.renderSprite(label);
- lastBBox = bbox;
- textSize += bbox.width;
- textCount++;
- });
- } else if (docked === 'radial') {
- me.iterate(majorTicks, function(position, labelText, i) {
- if (labelText === undefined) {
- return;
- }
- if (renderer) {
- text = Ext.callback(renderer, null, [
- axis,
- labelText,
- layoutContext,
- lastLabelText
- ], 0, axis);
- } else {
- text = segmenter.renderer(labelText, layoutContext, lastLabelText);
- }
- lastLabelText = labelText;
- if (typeof text !== 'undefined') {
- label.setAttributes({
- text: String(text),
- translationX: attr.centerX - surface.roundPixel(position) / attr.max * attr.length * Math.cos(attr.baseRotation + Math.PI / 2),
- translationY: attr.centerY - surface.roundPixel(position) / attr.max * attr.length * Math.sin(attr.baseRotation + Math.PI / 2)
- }, true);
- label.applyTransformations();
- bbox = label.attr.matrix.transformBBox(label.getBBox(true));
- if (lastBBox && !isBBoxIntersect(bbox, lastBBox)) {
- return;
- }
- surface.renderSprite(label);
- lastBBox = bbox;
- textSize += bbox.width;
- textCount++;
- }
- });
- } else if (docked === 'angular') {
- labelOffset += attr.majorTickSize + attr.lineWidth * 0.5;
- me.iterate(majorTicks, function(position, labelText, i) {
- if (labelText === undefined) {
- return;
- }
- if (renderer) {
- text = Ext.callback(renderer, null, [
- axis,
- labelText,
- layoutContext,
- lastLabelText
- ], 0, axis);
- } else {
- text = segmenter.renderer(labelText, layoutContext, lastLabelText);
- }
- lastLabelText = labelText;
- thickness = Math.max(thickness, Math.max(attr.majorTickSize, attr.minorTickSize) + (attr.lineCap !== 'butt' ? attr.lineWidth * 0.5 : 0));
- if (typeof text !== 'undefined') {
- angle = position / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
- label.setAttributes({
- text: String(text),
- translationX: attr.centerX + (attr.length + labelOffset) * Math.cos(angle),
- translationY: attr.centerY + (attr.length + labelOffset) * Math.sin(angle)
- }, true);
- label.applyTransformations();
- bbox = label.attr.matrix.transformBBox(label.getBBox(true));
- if (lastBBox && !isBBoxIntersect(bbox, lastBBox)) {
- return;
- }
- surface.renderSprite(label);
- lastBBox = bbox;
- textSize += bbox.width;
- textCount++;
- }
- });
- } else if (docked === 'gauge') {
- gaugeAngles = me.getGaugeAngles();
- labelOffset += attr.majorTickSize + attr.lineWidth * 0.5;
- me.iterate(majorTicks, function(position, labelText, i) {
- if (labelText === undefined) {
- return;
- }
- if (renderer) {
- text = Ext.callback(renderer, null, [
- axis,
- labelText,
- layoutContext,
- lastLabelText
- ], 0, axis);
- } else {
- text = segmenter.renderer(labelText, layoutContext, lastLabelText);
- }
- lastLabelText = labelText;
- if (typeof text !== 'undefined') {
- angle = (position - attr.min) / (attr.max - attr.min) * attr.totalAngle - attr.totalAngle + gaugeAngles.start;
- label.setAttributes({
- text: String(text),
- translationX: attr.centerX + (attr.length + labelOffset) * Math.cos(angle),
- translationY: attr.centerY + (attr.length + labelOffset) * Math.sin(angle)
- }, true);
- label.applyTransformations();
- bbox = label.attr.matrix.transformBBox(label.getBBox(true));
- if (lastBBox && !isBBoxIntersect(bbox, lastBBox)) {
- return;
- }
- surface.renderSprite(label);
- lastBBox = bbox;
- textSize += bbox.width;
- textCount++;
- }
- });
- }
- if (attr.enlargeEstStepSizeByText && textCount) {
- textSize /= textCount;
- textSize += tickPadding;
- textSize *= 2;
- if (attr.estStepSize < textSize) {
- attr.estStepSize = textSize;
- }
- }
- if (Math.abs(me.thickness - thickness) > 1) {
- me.thickness = thickness;
- attr.bbox.plain.dirty = true;
- attr.bbox.transform.dirty = true;
- me.doThicknessChanged();
- return false;
- }
- }
- },
- renderAxisLine: function(surface, ctx, layout, clipRect) {
- var me = this,
- attr = me.attr,
- halfLineWidth = attr.lineWidth * 0.5,
- docked = attr.position,
- position, gaugeAngles;
- if (attr.axisLine && attr.length) {
- switch (docked) {
- case 'left':
- position = surface.roundPixel(clipRect[2]) - halfLineWidth;
- ctx.moveTo(position, -attr.endGap);
- ctx.lineTo(position, attr.length + attr.startGap + 1);
- break;
- case 'right':
- ctx.moveTo(halfLineWidth, -attr.endGap);
- ctx.lineTo(halfLineWidth, attr.length + attr.startGap + 1);
- break;
- case 'bottom':
- ctx.moveTo(-attr.startGap, halfLineWidth);
- ctx.lineTo(attr.length + attr.endGap, halfLineWidth);
- break;
- case 'top':
- position = surface.roundPixel(clipRect[3]) - halfLineWidth;
- ctx.moveTo(-attr.startGap, position);
- ctx.lineTo(attr.length + attr.endGap, position);
- break;
- case 'angular':
- ctx.moveTo(attr.centerX + attr.length, attr.centerY);
- ctx.arc(attr.centerX, attr.centerY, attr.length, 0, Math.PI * 2, true);
- break;
- case 'gauge':
- gaugeAngles = me.getGaugeAngles();
- ctx.moveTo(attr.centerX + Math.cos(gaugeAngles.start) * attr.length, attr.centerY + Math.sin(gaugeAngles.start) * attr.length);
- ctx.arc(attr.centerX, attr.centerY, attr.length, gaugeAngles.start, gaugeAngles.end, true);
- break;
- }
- }
- },
- getGaugeAngles: function() {
- var me = this,
- angle = me.attr.totalAngle,
- offset;
- if (angle <= Math.PI) {
- offset = (Math.PI - angle) * 0.5;
- } else {
- offset = -(Math.PI * 2 - angle) * 0.5;
- }
- offset = Math.PI * 2 - offset;
- return {
- start: offset,
- end: offset - angle
- };
- },
- renderGridLines: function(surface, ctx, layout, clipRect) {
- var me = this,
- axis = me.getAxis(),
- attr = me.attr,
- matrix = attr.matrix,
- startGap = attr.startGap,
- endGap = attr.endGap,
- xx = matrix.getXX(),
- yy = matrix.getYY(),
- dx = matrix.getDX(),
- dy = matrix.getDY(),
- position = attr.position,
- alignment = axis.getGridAlignment(),
- majorTicks = layout.majorTicks,
- anchor, j, lastAnchor;
- if (attr.grid) {
- if (majorTicks) {
- if (position === 'left' || position === 'right') {
- lastAnchor = attr.min * yy + dy + endGap + startGap;
- me.iterate(majorTicks, function(position, labelText, i) {
- anchor = position * yy + dy + endGap;
- me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
- y: anchor,
- height: lastAnchor - anchor
- }, j = i, true);
- lastAnchor = anchor;
- });
- j++;
- anchor = 0;
- me.putMarker(alignment + '-' + (j % 2 ? 'odd' : 'even'), {
- y: anchor,
- height: lastAnchor - anchor
- }, j, true);
- } else if (position === 'top' || position === 'bottom') {
- lastAnchor = attr.min * xx + dx + startGap;
- if (startGap) {
- me.putMarker(alignment + '-even', {
- x: 0,
- width: lastAnchor
- }, -1, true);
- }
- me.iterate(majorTicks, function(position, labelText, i) {
- anchor = position * xx + dx + startGap;
- me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
- x: anchor,
- width: lastAnchor - anchor
- }, j = i, true);
- lastAnchor = anchor;
- });
- j++;
- anchor = attr.length + attr.startGap + attr.endGap;
- me.putMarker(alignment + '-' + (j % 2 ? 'odd' : 'even'), {
- x: anchor,
- width: lastAnchor - anchor
- }, j, true);
- } else if (position === 'radial') {
- me.iterate(majorTicks, function(position, labelText, i) {
- if (!position) {
- return;
- }
- anchor = position / attr.max * attr.length;
- me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
- scalingX: anchor,
- scalingY: anchor
- }, i, true);
- lastAnchor = anchor;
- });
- } else if (position === 'angular') {
- me.iterate(majorTicks, function(position, labelText, i) {
- if (!attr.length) {
- return;
- }
- anchor = position / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
- me.putMarker(alignment + '-' + (i % 2 ? 'odd' : 'even'), {
- rotationRads: anchor,
- rotationCenterX: 0,
- rotationCenterY: 0,
- scalingX: attr.length,
- scalingY: attr.length
- }, i, true);
- lastAnchor = anchor;
- });
- }
- }
- }
- },
- renderLimits: function(clipRect) {
- var me = this,
- attr = me.attr,
- axis = me.getAxis(),
- limits = Ext.Array.from(axis.getLimits());
- if (!limits.length || attr.dataMin === attr.dataMax) {
- if (axis.limits) {
- axis.limits.titles.attr.hidden = true;
- }
- return;
- }
- // eslint-disable-next-line vars-on-top, one-var
- var chart = axis.getChart(),
- innerPadding = chart.getInnerPadding(),
- limitsRect = axis.limits.surface.getRect(),
- matrix = attr.matrix,
- position = attr.position,
- chain = Ext.Object.chain,
- titles = axis.limits.titles,
- titleBBox, titlePosition, titleFlip, limit, value, i, ln, x, y;
- titles.attr.hidden = false;
- titles.instances = [];
- titles.position = 0;
- if (position === 'left' || position === 'right') {
- for (i = 0 , ln = limits.length; i < ln; i++) {
- limit = chain(limits[i]);
- if (!limit.line) {
- limit.line = {};
- }
- value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
- value = value * matrix.getYY() + matrix.getDY();
- limit.line.y = value + innerPadding.top;
- limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
- me.putMarker('horizontal-limit-lines', limit.line, i, true);
- if (limit.line.title) {
- titles.add(limit.line.title);
- titleBBox = titles.getBBoxFor(titles.position - 1);
- titlePosition = limit.line.title.position || (position === 'left' ? 'start' : 'end');
- switch (titlePosition) {
- case 'start':
- x = 10;
- break;
- case 'end':
- x = limitsRect[2] - 10;
- break;
- case 'middle':
- x = limitsRect[2] / 2;
- break;
- }
- titles.setAttributesFor(titles.position - 1, {
- x: x,
- y: limit.line.y - titleBBox.height / 2,
- textAlign: titlePosition,
- fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle
- });
- }
- }
- } else if (position === 'top' || position === 'bottom') {
- for (i = 0 , ln = limits.length; i < ln; i++) {
- limit = chain(limits[i]);
- if (!limit.line) {
- limit.line = {};
- }
- value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
- value = value * matrix.getXX() + matrix.getDX();
- limit.line.x = value + innerPadding.left;
- limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
- me.putMarker('vertical-limit-lines', limit.line, i, true);
- if (limit.line.title) {
- titles.add(limit.line.title);
- titleBBox = titles.getBBoxFor(titles.position - 1);
- titlePosition = limit.line.title.position || (position === 'top' ? 'end' : 'start');
- switch (titlePosition) {
- case 'start':
- y = limitsRect[3] - titleBBox.width / 2 - 10;
- break;
- case 'end':
- y = titleBBox.width / 2 + 10;
- break;
- case 'middle':
- y = limitsRect[3] / 2;
- break;
- }
- titles.setAttributesFor(titles.position - 1, {
- x: limit.line.x + titleBBox.height / 2,
- y: y,
- fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle,
- rotationRads: Math.PI / 2
- });
- }
- }
- } else if (position === 'radial') {
- for (i = 0 , ln = limits.length; i < ln; i++) {
- limit = chain(limits[i]);
- if (!limit.line) {
- limit.line = {};
- }
- value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
- if (value > attr.max) {
-
- continue;
- }
- value = value / attr.max * attr.length;
- limit.line.cx = attr.centerX;
- limit.line.cy = attr.centerY;
- limit.line.scalingX = value;
- limit.line.scalingY = value;
- limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
- me.putMarker('circular-limit-lines', limit.line, i, true);
- if (limit.line.title) {
- titles.add(limit.line.title);
- titleBBox = titles.getBBoxFor(titles.position - 1);
- titles.setAttributesFor(titles.position - 1, {
- x: attr.centerX,
- y: attr.centerY - value - titleBBox.height / 2,
- fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle
- });
- }
- }
- } else if (position === 'angular') {
- for (i = 0 , ln = limits.length; i < ln; i++) {
- limit = chain(limits[i]);
- if (!limit.line) {
- limit.line = {};
- }
- value = Ext.isString(limit.value) ? axis.getCoordFor(limit.value) : limit.value;
- value = value / (attr.max + 1) * Math.PI * 2 + attr.baseRotation;
- limit.line.translationX = attr.centerX;
- limit.line.translationY = attr.centerY;
- limit.line.rotationRads = value;
- limit.line.rotationCenterX = 0;
- limit.line.rotationCenterY = 0;
- limit.line.scalingX = attr.length;
- limit.line.scalingY = attr.length;
- limit.line.strokeStyle = limit.line.strokeStyle || attr.strokeStyle;
- me.putMarker('radial-limit-lines', limit.line, i, true);
- if (limit.line.title) {
- titles.add(limit.line.title);
- titleBBox = titles.getBBoxFor(titles.position - 1);
- titleFlip = ((value > -0.5 * Math.PI && value < 0.5 * Math.PI) || (value > 1.5 * Math.PI && value < 2 * Math.PI)) ? 1 : -1;
- titles.setAttributesFor(titles.position - 1, {
- x: attr.centerX + 0.5 * attr.length * Math.cos(value) + titleFlip * titleBBox.height / 2 * Math.sin(value),
- y: attr.centerY + 0.5 * attr.length * Math.sin(value) - titleFlip * titleBBox.height / 2 * Math.cos(value),
- rotationRads: titleFlip === 1 ? value : value - Math.PI,
- fillStyle: limit.line.title.fillStyle || limit.line.strokeStyle
- });
- }
- }
- } else if (position === 'gauge') {}
- },
- // TODO
- doThicknessChanged: function() {
- var axis = this.getAxis();
- if (axis) {
- axis.onThicknessChanged();
- }
- },
- render: function(surface, ctx, rect) {
- var me = this,
- layoutContext = me.getLayoutContext();
- if (layoutContext) {
- if (me.renderLabels(surface, ctx, layoutContext, rect) === false) {
- return false;
- }
- ctx.beginPath();
- me.renderTicks(surface, ctx, layoutContext, rect);
- me.renderAxisLine(surface, ctx, layoutContext, rect);
- me.renderGridLines(surface, ctx, layoutContext, rect);
- me.renderLimits(rect);
- ctx.stroke();
- }
- }
- });
- /**
- * @abstract
- * @class Ext.chart.axis.segmenter.Segmenter
- *
- * Interface for a segmenter in an Axis. A segmenter defines the operations you can do to a specific
- * data type.
- *
- * See {@link Ext.chart.axis.Axis}.
- *
- */
- Ext.define('Ext.chart.axis.segmenter.Segmenter', {
- config: {
- /**
- * @cfg {Ext.chart.axis.Axis} axis The axis that the Segmenter is bound.
- */
- axis: null
- },
- constructor: function(config) {
- this.initConfig(config);
- },
- /**
- * This method formats the value.
- *
- * @param {*} value The value to format.
- * @param {Object} context Axis layout context.
- * @return {String}
- */
- renderer: function(value, context) {
- return String(value);
- },
- /**
- * Convert from any data into the target type.
- * @param {*} value The value to convert from
- * @return {*} The converted value.
- */
- from: function(value) {
- return value;
- },
- /**
- * @method
- * Returns the difference between the min and max value based on the given unit scale.
- *
- * @param {*} min The smaller value.
- * @param {*} max The larger value.
- * @param {*} unit The unit scale. Unit can be any type.
- * @return {Number} The number of `unit`s between min and max. It is the minimum n that
- * min + n * unit >= max.
- */
- diff: Ext.emptyFn,
- /**
- * @method
- * Align value with step of units.
- * For example, for the date segmenter, if the unit is "Month" and step is 3, the value will be
- * aligned by seasons.
- *
- * @param {*} value The value to be aligned.
- * @param {Number} step The step of units.
- * @param {*} unit The unit.
- * @return {*} Aligned value.
- */
- align: Ext.emptyFn,
- /**
- * @method
- * Add `step` `unit`s to the value.
- * @param {*} value The value to be added.
- * @param {Number} step The step of units. Negative value are allowed.
- * @param {*} unit The unit.
- */
- add: Ext.emptyFn,
- /**
- * @method
- * Given a start point and estimated step size of a range, determine the preferred step size.
- *
- * @param {*} start The start point of range.
- * @param {*} estStepSize The estimated step size.
- * @return {Object} Return the step size by an object of step x unit.
- * @return {Number} return.step The step count of units.
- * @return {Number|Object} return.unit The unit.
- */
- preferredStep: Ext.emptyFn
- });
- /**
- * @class Ext.chart.axis.segmenter.Names
- * @extends Ext.chart.axis.segmenter.Segmenter
- *
- * Names data type. Names will be calculated as their indices in the methods in this class.
- * The `preferredStep` always return `{ unit: 1, step: 1 }` to indicate "show every item".
- *
- */
- Ext.define('Ext.chart.axis.segmenter.Names', {
- extend: 'Ext.chart.axis.segmenter.Segmenter',
- alias: 'segmenter.names',
- renderer: function(value, context) {
- return value;
- },
- diff: function(min, max, unit) {
- return Math.floor(max - min);
- },
- align: function(value, step, unit) {
- return Math.floor(value);
- },
- add: function(value, step, unit) {
- return value + step;
- },
- preferredStep: function(min, estStepSize, minIdx, data) {
- return {
- unit: 1,
- step: 1
- };
- }
- });
- /**
- * @class Ext.chart.axis.segmenter.Numeric
- * @extends Ext.chart.axis.segmenter.Segmenter
- *
- * Numeric data type.
- */
- Ext.define('Ext.chart.axis.segmenter.Numeric', {
- extend: 'Ext.chart.axis.segmenter.Segmenter',
- alias: 'segmenter.numeric',
- isNumeric: true,
- renderer: function(value, context) {
- return value.toFixed(Math.max(0, context.majorTicks.unit.fixes));
- },
- diff: function(min, max, unit) {
- return Math.floor((max - min) / unit.scale);
- },
- align: function(value, step, unit) {
- var scaledStep = unit.scale * step;
- return Math.floor(value / scaledStep) * scaledStep;
- },
- add: function(value, step, unit) {
- return value + step * unit.scale;
- },
- preferredStep: function(min, estStepSize) {
- // Getting an order of magnitude of the estStepSize with a common logarithm.
- var order = Math.floor(Math.log(estStepSize) * Math.LOG10E),
- scale = Math.pow(10, order);
- estStepSize /= scale;
- if (estStepSize < 2) {
- estStepSize = 2;
- } else if (estStepSize < 5) {
- estStepSize = 5;
- } else if (estStepSize < 10) {
- estStepSize = 10;
- order++;
- }
- return {
- unit: {
- // When passed estStepSize is less than 1, its order of magnitude
- // is equal to -number_of_leading_zeros in the estStepSize.
- fixes: -order,
- // Number of fractional digits.
- scale: scale
- },
- step: estStepSize
- };
- },
- leadingZeros: function(n) {
- // For example:
- // leadingZeros(0.2) is 1,
- // leadingZeros(-0.01) is 2.
- return -Math.floor(Ext.Number.log10(Math.abs(n)));
- },
- /**
- * Wraps the provided estimated step size of a range without altering it into a step size
- * object.
- *
- * @param {*} min The start point of range.
- * @param {*} estStepSize The estimated step size.
- * @return {Object} Return the step size by an object of step x unit.
- * @return {Number} return.step The step count of units.
- * @return {Object} return.unit The unit.
- */
- exactStep: function(min, estStepSize) {
- var stepZeros = this.leadingZeros(estStepSize),
- scale = Math.pow(10, stepZeros);
- return {
- unit: {
- // add one decimal point if estStepSize is not a multiple of scale
- fixes: stepZeros + (estStepSize % scale === 0 ? 0 : 1),
- // Swap scale & step, if the estStepSize < 1,
- // or 'diff' method will give us rounding errors.
- scale: estStepSize < 1 ? estStepSize : 1
- },
- step: estStepSize < 1 ? 1 : estStepSize
- };
- },
- adjustByMajorUnit: function(step, scale, range) {
- var min = range[0],
- max = range[1],
- increment = step * scale,
- remainder, multiplier;
- multiplier = Math.max(1 / (min || 1), 1 / (increment || 1));
- multiplier = multiplier > 1 ? multiplier : 1;
- remainder = ((min * multiplier) % (increment * multiplier)) / multiplier;
- if (remainder !== 0) {
- range[0] = min - remainder + (min < 0 ? -increment : 0);
- }
- multiplier = Math.max(1 / (max || 1), 1 / (increment || 1));
- multiplier = multiplier > 1 ? multiplier : 1;
- remainder = ((max * multiplier) % (increment * multiplier)) / multiplier;
- if (remainder !== 0) {
- range[1] = max - remainder + (max > 0 ? increment : 0);
- }
- }
- });
- /**
- * @class Ext.chart.axis.segmenter.Time
- * @extends Ext.chart.axis.segmenter.Segmenter
- *
- * Time data type.
- */
- Ext.define('Ext.chart.axis.segmenter.Time', {
- extend: 'Ext.chart.axis.segmenter.Segmenter',
- alias: 'segmenter.time',
- config: {
- /**
- * @cfg {Object} step
- * @cfg {String} step.unit The unit of the step (Ext.Date.DAY, Ext.Date.MONTH, etc).
- * @cfg {Number} step.step The number of units for the step (1, 2, etc).
- * If specified, will override the result of {@link #preferredStep}.
- * For example:
- *
- * step: {
- * unit: Ext.Date.HOUR,
- * step: 1
- * }
- */
- step: null
- },
- renderer: function(value, context) {
- var ExtDate = Ext.Date;
- switch (context.majorTicks.unit) {
- case 'y':
- return ExtDate.format(value, 'Y');
- case 'mo':
- return ExtDate.format(value, 'Y-m');
- case 'd':
- return ExtDate.format(value, 'Y-m-d');
- }
- return ExtDate.format(value, 'Y-m-d\nH:i:s');
- },
- from: function(value) {
- return new Date(value);
- },
- diff: function(min, max, unit) {
- if (isFinite(min)) {
- min = new Date(min);
- }
- if (isFinite(max)) {
- max = new Date(max);
- }
- return Ext.Date.diff(min, max, unit);
- },
- updateStep: function() {
- var axis = this.getAxis();
- if (axis && !this.isConfiguring) {
- axis.performLayout();
- }
- },
- align: function(date, step, unit) {
- if (unit === 'd' && step >= 7) {
- date = Ext.Date.align(date, 'd', step);
- date.setDate(date.getDate() - date.getDay() + 1);
- return date;
- } else {
- return Ext.Date.align(date, unit, step);
- }
- },
- add: function(value, step, unit) {
- return Ext.Date.add(new Date(value), unit, step);
- },
- timeBuckets: [
- {
- unit: Ext.Date.YEAR,
- steps: [
- 1,
- 2,
- 5,
- 10,
- 20,
- 50,
- 100,
- 200,
- 500
- ]
- },
- {
- unit: Ext.Date.MONTH,
- steps: [
- 1,
- 3,
- 6
- ]
- },
- {
- unit: Ext.Date.DAY,
- steps: [
- 1,
- 7,
- 14
- ]
- },
- {
- unit: Ext.Date.HOUR,
- steps: [
- 1,
- 6,
- 12
- ]
- },
- {
- unit: Ext.Date.MINUTE,
- steps: [
- 1,
- 5,
- 15,
- 30
- ]
- },
- {
- unit: Ext.Date.SECOND,
- steps: [
- 1,
- 5,
- 15,
- 30
- ]
- },
- {
- unit: Ext.Date.MILLI,
- steps: [
- 1,
- 2,
- 5,
- 10,
- 20,
- 50,
- 100,
- 200,
- 500
- ]
- }
- ],
- /**
- * @private
- * Takes a time interval and figures out what is the smallest nice number of which
- * units (years, months, days, etc.) that can fully encompass that interval.
- * @param {Date} min
- * @param {Date} max
- * @return {Object}
- * @return {String} return.unit The unit.
- * @return {Number} return.step The number of units.
- */
- getTimeBucket: function(min, max) {
- var buckets = this.timeBuckets,
- unit, unitCount, steps, step, result, i, j;
- for (i = 0; i < buckets.length; i++) {
- unit = buckets[i].unit;
- unitCount = this.diff(min, max, unit);
- if (unitCount > 0) {
- steps = buckets[i].steps;
- for (j = 0; j < steps.length; j++) {
- step = steps[j];
- if (unitCount <= step) {
- break;
- }
- }
- result = {
- unit: unit,
- step: step
- };
- break;
- }
- }
- // If the interval is smaller then one millisecond ...
- if (!result) {
- // ... we can't go smaller than one millisecond.
- result = {
- unit: Ext.Date.MILLI,
- step: 1
- };
- }
- return result;
- },
- preferredStep: function(min, estStepSize) {
- var step = this.getStep();
- return step ? step : this.getTimeBucket(new Date(+min), new Date(+min + Math.ceil(estStepSize)));
- }
- });
- /**
- * @abstract
- * @class Ext.chart.axis.layout.Layout
- *
- * Interface used by Axis to process its data into a meaningful layout.
- */
- Ext.define('Ext.chart.axis.layout.Layout', {
- mixins: {
- observable: 'Ext.mixin.Observable'
- },
- config: {
- /**
- * @cfg {Ext.chart.axis.Axis} axis The axis that the Layout is bound.
- */
- axis: null
- },
- constructor: function(config) {
- this.mixins.observable.constructor.call(this, config);
- },
- /**
- * Processes the data of the series bound to the axis.
- * @param {Ext.chart.series.Series} series The bound series.
- */
- processData: function(series) {
- var me = this,
- axis = me.getAxis(),
- direction = axis.getDirection(),
- boundSeries = axis.boundSeries,
- i, ln;
- if (series) {
- series['coordinate' + direction]();
- } else {
- for (i = 0 , ln = boundSeries.length; i < ln; i++) {
- boundSeries[i]['coordinate' + direction]();
- }
- }
- },
- /**
- * Calculates the position of major ticks for the axis.
- * @param {Object} context
- */
- calculateMajorTicks: function(context) {
- var me = this,
- attr = context.attr,
- range = attr.max - attr.min,
- zoom = range / Math.max(1, attr.length) * (attr.visibleMax - attr.visibleMin),
- viewMin = attr.min + range * attr.visibleMin,
- viewMax = attr.min + range * attr.visibleMax,
- estStepSize = attr.estStepSize * zoom,
- majorTicks = me.snapEnds(context, attr.min, attr.max, estStepSize);
- if (majorTicks) {
- me.trimByRange(context, majorTicks, viewMin, viewMax);
- context.majorTicks = majorTicks;
- }
- },
- /**
- * Calculates the position of sub ticks for the axis.
- * @param {Object} context
- */
- calculateMinorTicks: function(context) {
- if (this.snapMinorEnds) {
- context.minorTicks = this.snapMinorEnds(context);
- }
- },
- /**
- * Calculates the position of tick marks for the axis.
- * @param {Object} context
- * @return {*}
- */
- calculateLayout: function(context) {
- var me = this,
- attr = context.attr;
- if (attr.length === 0) {
- return null;
- }
- if (attr.majorTicks) {
- me.calculateMajorTicks(context);
- if (attr.minorTicks) {
- me.calculateMinorTicks(context);
- }
- }
- },
- /**
- * @method
- * Snaps the data bound to the axis to meaningful tick marks.
- * @param {Object} context
- * @param {Number} min
- * @param {Number} max
- * @param {Number} estStepSize
- */
- snapEnds: Ext.emptyFn,
- /**
- * Trims the layout of the axis by the defined minimum and maximum.
- * @param {Object} context
- * @param {Object} ticks Ticks object (e.g. major ticks) to be modified.
- * @param {Number} trimMin
- * @param {Number} trimMax
- */
- trimByRange: function(context, ticks, trimMin, trimMax) {
- var segmenter = context.segmenter,
- unit = ticks.unit,
- beginIdx = segmenter.diff(ticks.from, trimMin, unit),
- endIdx = segmenter.diff(ticks.from, trimMax, unit),
- begin = Math.max(0, Math.ceil(beginIdx / ticks.step)),
- end = Math.min(ticks.steps, Math.floor(endIdx / ticks.step));
- if (end < ticks.steps) {
- ticks.to = segmenter.add(ticks.from, end * ticks.step, unit);
- }
- if (ticks.max > trimMax) {
- ticks.max = ticks.to;
- }
- if (ticks.from < trimMin) {
- ticks.from = segmenter.add(ticks.from, begin * ticks.step, unit);
- while (ticks.from < trimMin) {
- begin++;
- ticks.from = segmenter.add(ticks.from, ticks.step, unit);
- }
- }
- if (ticks.min < trimMin) {
- ticks.min = ticks.from;
- }
- ticks.steps = end - begin;
- }
- });
- /**
- * @class Ext.chart.axis.layout.Discrete
- * @extends Ext.chart.axis.layout.Layout
- *
- * Simple processor for data that cannot be interpolated.
- */
- Ext.define('Ext.chart.axis.layout.Discrete', {
- extend: 'Ext.chart.axis.layout.Layout',
- alias: 'axisLayout.discrete',
- isDiscrete: true,
- processData: function() {
- var me = this,
- axis = me.getAxis(),
- seriesList = axis.boundSeries,
- direction = axis.getDirection(),
- i, ln, series;
- me.labels = [];
- me.labelMap = {};
- for (i = 0 , ln = seriesList.length; i < ln; i++) {
- series = seriesList[i];
- if (series['get' + direction + 'Axis']() === axis) {
- series['coordinate' + direction]();
- }
- }
- // About the labels on Category axes (aka. axes with a Discrete layout)...
- //
- // When the data set from the store changes, series.processData() is called, which does
- // its thing at the series level and then calls series.updateLabelData() to update
- // the labels in the sprites that belong to the series. At the same time,
- // series.processData() calls axis.processData(), which also does its thing but at the axis
- // level, and also needs to update the labels for the sprite(s) that belong to the axis.
- // This is not that simple, however. So how are the axis labels rendered?
- // First, axis.sprite.Axis.render() calls renderLabels() which obtains the majorTicks
- // from the axis.layout and iterate() through them. The majorTicks are an object returned
- // by snapEnds() below which provides a getLabel() function that returns the label
- // from the axis.layoutContext.data array. So now the question is: how are the labels
- // transferred from the axis.layout to the axis.layoutContext?
- // The easy response is: it's in calculateLayout() below. The issue is to call
- // calculateLayout() because it takes in an axis.layoutContext that can only be created
- // in axis.sprite.Axis.layoutUpdater(), which is a private "updater" function that is
- // called by all the sprite's "triggers". Of course, we don't want to call layoutUpdater()
- // directly from here, so instead we update the sprite's data attribute, which sets
- // the trigger which calls layoutUpdater() which calls calculateLayout() etc...
- // Note that the sprite's data attribute could be set to any value and it would still result
- // in the trigger we need. For consistency, however, it is set to the labels.
- axis.getSprites()[0].setAttributes({
- data: me.labels
- });
- me.fireEvent('datachange', me.labels);
- },
- /**
- * @method calculateLayout
- * @inheritdoc
- */
- calculateLayout: function(context) {
- context.data = this.labels;
- this.callParent([
- context
- ]);
- },
- /**
- * @method calculateMajorTicks
- * @inheritdoc
- */
- calculateMajorTicks: function(context) {
- var me = this,
- attr = context.attr,
- data = context.data,
- range = attr.max - attr.min,
- viewMin = attr.min + range * attr.visibleMin,
- viewMax = attr.min + range * attr.visibleMax,
- out;
- out = me.snapEnds(context, Math.max(0, attr.min), Math.min(attr.max, data.length - 1), 1);
- if (out) {
- me.trimByRange(context, out, viewMin, viewMax);
- context.majorTicks = out;
- }
- },
- /**
- * @method snapEnds
- * @inheritdoc
- */
- snapEnds: function(context, min, max, estStepSize) {
- var data = context.data,
- steps;
- estStepSize = Math.ceil(estStepSize);
- steps = Math.floor((max - min) / estStepSize);
- return {
- min: min,
- max: max,
- from: min,
- to: steps * estStepSize + min,
- step: estStepSize,
- steps: steps,
- unit: 1,
- getLabel: function(currentStep) {
- return data[this.from + this.step * currentStep];
- },
- get: function(currentStep) {
- return this.from + this.step * currentStep;
- }
- };
- },
- /**
- * @method trimByRange
- * @inheritdoc
- */
- trimByRange: function(context, out, trimMin, trimMax) {
- var unit = out.unit,
- beginIdx = Math.ceil((trimMin - out.from) / unit) * unit,
- endIdx = Math.floor((trimMax - out.from) / unit) * unit,
- begin = Math.max(0, Math.ceil(beginIdx / out.step)),
- end = Math.min(out.steps, Math.floor(endIdx / out.step));
- if (end < out.steps) {
- out.to = end;
- }
- if (out.max > trimMax) {
- out.max = out.to;
- }
- if (out.from < trimMin && out.step > 0) {
- out.from = out.from + begin * out.step * unit;
- while (out.from < trimMin) {
- begin++;
- out.from += out.step * unit;
- }
- }
- if (out.min < trimMin) {
- out.min = out.from;
- }
- out.steps = end - begin;
- },
- getCoordFor: function(value, field, idx, items) {
- this.labels.push(value);
- return this.labels.length - 1;
- }
- });
- /**
- * Discrete layout that combines duplicate data points only if they have the same index.
- * For example:
- *
- * @example
- * Ext.create({
- * xtype: 'cartesian',
- * title: 'Weight vs Calories',
- *
- * renderTo: document.body,
- * width: 400,
- * height: 400,
- *
- * store: {
- * fields: ['month', 'weight', 'calories'],
- * data: [
- * {
- * month: 'Jan',
- * weight: 185,
- * calories: 2650
- * },
- * {
- * month: 'Jan',
- * weight: 188,
- * calories: 2800
- * },
- * {
- * month: 'Feb',
- * weight: 188,
- * calories: 2800
- * },
- * {
- * month: 'Mar',
- * weight: 191,
- * calories: 2800
- * },
- * {
- * month: 'Apr',
- * weight: 189,
- * calories: 1500
- * },
- * {
- * month: 'May',
- * weight: 187,
- * calories: 1350
- * }
- * ]
- * },
- *
- * axes: [{
- * type: 'numeric',
- * position: 'left',
- * fields: ['weight'],
- * minimum: 140
- * }, {
- * type: 'numeric',
- * position: 'right',
- * fields: ['calories'],
- * minimum: 500,
- * maximum: 3500
- * }, {
- * type: 'category',
- * grid: true,
- * layout: 'combineByIndex',
- * fields: 'month',
- * position: 'bottom',
- * label: {
- * rotate: {
- * degrees: -45
- * }
- * }
- * }],
- *
- * series: [{
- * type: 'line',
- * title: 'Weight',
- * xField: 'month',
- * yField: 'weight',
- * smooth: true,
- * marker: true
- * }, {
- * type: 'line',
- * title: 'Calories',
- * xField: 'month',
- * yField: 'calories',
- * smooth: true,
- * marker: true
- * }],
- *
- * legend: {
- * docked: 'bottom'
- * }
- *
- * });
- *
- * @since 6.5.0
- */
- Ext.define('Ext.chart.axis.layout.CombineByIndex', {
- extend: 'Ext.chart.axis.layout.Discrete',
- alias: 'axisLayout.combineByIndex',
- getCoordFor: function(value, field, idx, items) {
- var labels = this.labels,
- result = idx;
- if (labels[idx] !== value) {
- result = labels.push(value) - 1;
- }
- return result;
- }
- });
- /**
- * Discrete processor that combines duplicate data points.
- */
- Ext.define('Ext.chart.axis.layout.CombineDuplicate', {
- extend: 'Ext.chart.axis.layout.Discrete',
- alias: 'axisLayout.combineDuplicate',
- getCoordFor: function(value, field, idx, items) {
- var result;
- if (!(value in this.labelMap)) {
- result = this.labelMap[value] = this.labels.length;
- this.labels.push(value);
- return result;
- }
- return this.labelMap[value];
- }
- });
- /**
- * @class Ext.chart.axis.layout.Continuous
- * @extends Ext.chart.axis.layout.Layout
- *
- * Processor for axis data that can be interpolated.
- */
- Ext.define('Ext.chart.axis.layout.Continuous', {
- extend: 'Ext.chart.axis.layout.Layout',
- alias: 'axisLayout.continuous',
- isContinuous: true,
- config: {
- adjustMinimumByMajorUnit: false,
- adjustMaximumByMajorUnit: false
- },
- getCoordFor: function(value, field, idx, items) {
- return +value;
- },
- /**
- * @method snapEnds
- * @inheritdoc
- */
- snapEnds: function(context, min, max, estStepSize) {
- var segmenter = context.segmenter,
- axis = this.getAxis(),
- noAnimation = !axis.spriteAnimationCount,
- majorTickSteps = axis.getMajorTickSteps(),
- // if specific number of steps requested and the segmenter supports such segmentation
- bucket = majorTickSteps && segmenter.exactStep ? segmenter.exactStep(min, (max - min) / majorTickSteps) : segmenter.preferredStep(min, estStepSize),
- unit = bucket.unit,
- step = bucket.step,
- diffSteps = segmenter.diff(min, max, unit),
- steps = (majorTickSteps || diffSteps) + 1,
- from;
- // If 'majorTickSteps' config of the axis is set (is not 0), it means that
- // we want to split the range at that number of equal intervals (segmenter.exactStep),
- // and don't care if the resulting ticks are at nice round values or not.
- // So 'from' (aligned) step is equal to 'min' (unaligned step).
- // And 'to' is equal to 'max'.
- //
- // Another case where this is possible, is when the range between 'min' and
- // 'max' can be represented by n steps, where n is an integer.
- // For example, if the data values are [7, 17, 27, 37, 47], the data step is 10
- // and, if the calculated tick step (segmenter.preferredStep) is also 10,
- // there is no need to segmenter.align the 'min' to 0, so that the ticks are at
- // [0, 10, 20, 30, 40, 50], because the data points are already perfectly
- // spaced, so the ticks can be exactly at the data points without runing the
- // aesthetics.
- //
- // The 'noAnimation' check is required to prevent EXTJS-25413 from happening.
- // The segmentation described above is ideal for a static chart, but produces
- // unwanted effects during animation.
- if (majorTickSteps || (noAnimation && +segmenter.add(min, diffSteps, unit) === max)) {
- from = min;
- } else {
- from = segmenter.align(min, step, unit);
- }
- return {
- // min/max are NOT aligned to step
- min: segmenter.from(min),
- max: segmenter.from(max),
- // from/to are aligned to step
- from: from,
- to: segmenter.add(from, steps, unit),
- step: step,
- steps: steps,
- unit: unit,
- get: function(currentStep) {
- return segmenter.add(this.from, this.step * currentStep, this.unit);
- }
- };
- },
- snapMinorEnds: function(context) {
- var majorTicks = context.majorTicks,
- minorTickSteps = this.getAxis().getMinorTickSteps(),
- segmenter = context.segmenter,
- min = majorTicks.min,
- max = majorTicks.max,
- from = majorTicks.from,
- unit = majorTicks.unit,
- step = majorTicks.step / minorTickSteps,
- scaledStep = step * unit.scale,
- fromMargin = from - min,
- offset = Math.floor(fromMargin / scaledStep),
- extraSteps = offset + Math.floor((max - majorTicks.to) / scaledStep) + 1,
- steps = majorTicks.steps * minorTickSteps + extraSteps;
- return {
- min: min,
- max: max,
- from: min + fromMargin % scaledStep,
- to: segmenter.add(from, steps * step, unit),
- step: step,
- steps: steps,
- unit: unit,
- get: function(current) {
- // don't render minor tick in major tick position
- return (current % minorTickSteps + offset + 1 !== 0) ? segmenter.add(this.from, this.step * current, unit) : null;
- }
- };
- }
- });
- /**
- * @class Ext.chart.axis.Axis
- *
- * Defines axis for charts.
- *
- * Using the current model, the type of axis can be easily extended. By default, Sencha Charts
- * provide three different types of axis:
- *
- * * **numeric** - the data attached to this axis is numeric and continuous.
- * * **time** - the data attached to this axis is (or gets converted into) a date/time value;
- * it is continuous.
- * * **category** - the data attached to this axis belongs to a finite set. The data points
- * are evenly placed along the axis.
- *
- * The behavior of an axis can be easily changed by setting different types of axis layout and
- * axis segmenter to the axis.
- *
- * Axis layout defines how the data points are placed. Using continuous layout, the data points
- * will be distributed by the numeric value. Using discrete layout the data points will be spaced
- * evenly. Furthermore, if you want to combine the data points with the duplicate values in a
- * discrete layout, you should use combineDuplicate layout.
- *
- * Segmenter defines the way to segment data range. For example, if you have a Date-type data range
- * from Jan 1, 1997 to Jan 1, 2017, the segmenter will segement the data range into years, months or
- * days based on the current zooming level.
- *
- * It is possible to write custom axis layouts and segmenters to extends this behavior by simply
- * implementing interfaces {@link Ext.chart.axis.layout.Layout} and
- * {@link Ext.chart.axis.segmenter.Segmenter}.
- *
- * Here's an example for the axes part of a chart definition:
- * An example of axis for a series (in this case for an area chart that has multiple layers of
- * yFields) could be:
- *
- * axes: [{
- * type: 'numeric',
- * position: 'left',
- * title: 'Number of Hits',
- * grid: {
- * odd: {
- * opacity: 1,
- * fill: '#ddd',
- * stroke: '#bbb',
- * lineWidth: 1
- * }
- * },
- * minimum: 0
- * }, {
- * type: 'category',
- * position: 'bottom',
- * title: 'Month of the Year',
- * grid: true,
- * label: {
- * rotate: {
- * degrees: 315
- * }
- * }
- * }]
- *
- * 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 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.
- * Both the category and numeric axes have `grid` set, which means that horizontal and vertical
- * lines will cover the chart background. In the category axis the labels will be rotated so
- * they can fit the space better.
- */
- Ext.define('Ext.chart.axis.Axis', {
- xtype: 'axis',
- mixins: {
- observable: 'Ext.mixin.Observable'
- },
- requires: [
- 'Ext.chart.axis.sprite.Axis',
- 'Ext.chart.axis.segmenter.*',
- 'Ext.chart.axis.layout.*',
- 'Ext.chart.Util'
- ],
- isAxis: true,
- /**
- * @event rangechange
- * Fires when the {@link Ext.chart.axis.Axis#range range} of the axis changes.
- * @param {Ext.chart.axis.Axis} axis
- * @param {Array} range
- * @param {Array} oldRange
- */
- /**
- * @event visiblerangechange
- * Fires when the {@link #visibleRange} of the axis changes.
- * @param {Ext.chart.axis.Axis} axis
- * @param {Array} visibleRange
- */
- /**
- * @cfg {String} id
- * The **unique** id of this axis instance.
- */
- config: {
- /**
- * @cfg {String} position
- * Where to set the axis. Available options are `left`, `bottom`, `right`, `top`, `radial`,
- * and `angular`.
- */
- position: 'bottom',
- /**
- * @cfg {Array} fields
- * An array containing the names of the record fields which should be mapped along the axis.
- * This is optional if the binding between series and fields is clear.
- */
- fields: [],
- /**
- * @cfg {Object} label
- *
- * The label configuration object for the Axis. This object may include style attributes
- * like `spacing`, `padding`, `font` that receives a string or number and
- * returns a new string with the modified values.
- *
- * For more supported values, see the configurations for {@link Ext.chart.sprite.Label}.
- */
- label: undefined,
- /**
- * @cfg {Object} grid
- * The grid configuration object for the Axis style. Can contain `stroke` or `fill`
- * attributes. Also may contain an `odd` or `even` property in which you only style things
- * on odd or even rows. For example:
- *
- *
- * grid {
- * odd: {
- * stroke: '#555'
- * },
- * even: {
- * stroke: '#ccc'
- * }
- * }
- */
- grid: false,
- /**
- * @cfg {Array|Object} limits
- * The limit lines configuration for the axis.
- * For example:
- *
- * limits: [{
- * value: 50,
- * line: {
- * strokeStyle: 'red',
- * lineDash: [6, 3],
- * title: {
- * text: 'Monthly minimum',
- * fontSize: 14
- * }
- * }
- * }]
- */
- limits: null,
- /**
- * @cfg {Function} renderer Allows to change the text shown next to the tick.
- * @param {Ext.chart.axis.Axis} axis The axis.
- * @param {String/Number} label The label.
- * @param {Object} layoutContext The object that holds calculated positions
- * of axis' ticks based on current layout, segmenter, axis length and configuration.
- * @param {String/Number/null} lastLabel The last label (if any).
- * @return {String} The label to display.
- * @controllable
- */
- renderer: null,
- /**
- * @protected
- * @cfg {Ext.chart.AbstractChart} chart The Chart that the Axis is bound.
- */
- chart: null,
- /**
- * @cfg {Object} style
- * The style for the axis line and ticks.
- * Refer to the {@link Ext.chart.axis.sprite.Axis}
- */
- style: null,
- /**
- * @cfg {Number} margin
- * The margin of the axis. Used to control the spacing between axes in charts with multiple
- * axes. Unlike CSS where the margin is added on all 4 sides of an element, the `margin`
- * is the total space that is added horizontally for a vertical axis, vertically
- * for a horizontal axis, and radially for an angular axis.
- */
- margin: 0,
- /**
- * @cfg {Number} [titleMargin=4]
- * The margin around the axis title. Unlike CSS where the margin is added on all 4
- * sides of an element, the `titleMargin` is the total space that is added horizontally
- * for a vertical title and vertically for an horizontal title, with half the `titleMargin`
- * being added on either side.
- */
- titleMargin: 4,
- /**
- * @cfg {Object} background
- * The background config for the axis surface.
- */
- background: null,
- /**
- * @cfg {Number} minimum
- * The minimum value drawn by the axis. If not set explicitly, the axis
- * minimum will be calculated automatically.
- */
- minimum: NaN,
- /**
- * @cfg {Number} maximum
- * The maximum value drawn by the axis. If not set explicitly, the axis
- * maximum will be calculated automatically.
- */
- maximum: NaN,
- /**
- * @cfg {Boolean} reconcileRange
- * If 'true' the range of the axis will be a union of ranges
- * of all the axes with the same direction. Defaults to 'false'.
- */
- reconcileRange: false,
- /**
- * @cfg {Number} minZoom
- * The minimum zooming level for axis.
- */
- minZoom: 1,
- /**
- * @cfg {Number} maxZoom
- * The maximum zooming level for axis.
- */
- maxZoom: 10000,
- /**
- * @cfg {Object|Ext.chart.axis.layout.Layout} layout
- * The axis layout config. See {@link Ext.chart.axis.layout.Layout}
- */
- layout: 'continuous',
- /**
- * @cfg {Object|Ext.chart.axis.segmenter.Segmenter} segmenter
- * The segmenter config. See {@link Ext.chart.axis.segmenter.Segmenter}
- */
- segmenter: 'numeric',
- /**
- * @cfg {Boolean} hidden
- * Indicate whether to hide the axis.
- * If the axis is hidden, one of the axis line, ticks, labels or the title will be shown and
- * no margin will be taken.
- * The coordination mechanism works fine no matter if the axis is hidden.
- */
- hidden: false,
- /**
- * @cfg {Number} [majorTickSteps=0]
- * Forces the number of major ticks to the specified value.
- * Both {@link #minimum} and {@link #maximum} should be specified.
- */
- majorTickSteps: 0,
- /**
- * @cfg {Number} [minorTickSteps=0]
- * The number of small ticks between two major ticks.
- */
- minorTickSteps: 0,
- /**
- * @cfg {Boolean} adjustByMajorUnit
- * Whether to make the auto-calculated minimum and maximum of the axis
- * a multiple of the interval between the major ticks of the axis.
- * If {@link #majorTickSteps}, {@link #minimum} or {@link #maximum}
- * configs have been set, this config will be ignored.
- * Defaults to 'true'.
- * Note: this config has no effect if the axis is {@link #hidden}.
- */
- adjustByMajorUnit: true,
- /**
- * @cfg {String|Object} title
- * The title for the Axis.
- * If given a String, the 'text' attribute of the title sprite will be set,
- * otherwise the style will be set.
- */
- title: null,
- /**
- * @private
- * @cfg {Number} [expandRangeBy=0]
- */
- expandRangeBy: 0,
- /**
- * @private
- * @cfg {Number} length
- * Length of the axis position. Equals to the size of inner rect on the docking side
- * of this axis.
- * WARNING: Meant to be set automatically by chart. Do not set it manually.
- */
- length: 0,
- /**
- * @private
- * @cfg {Array} center
- * Center of the polar axis.
- * WARNING: Meant to be set automatically by chart. Do not set it manually.
- */
- center: null,
- /**
- * @private
- * @cfg {Number} radius
- * Radius of the polar axis.
- * WARNING: Meant to be set automatically by chart. Do not set it manually.
- */
- radius: null,
- /**
- * @private
- */
- totalAngle: Math.PI,
- /**
- * @private
- * @cfg {Number} rotation
- * Rotation of the polar axis in radians.
- * WARNING: Meant to be set automatically by chart. Do not set it manually.
- */
- rotation: null,
- /**
- * @cfg {Array} visibleRange
- * Specify the proportion of the axis to be rendered. The series bound to
- * this axis will be synchronized and transformed accordingly.
- */
- visibleRange: [
- 0,
- 1
- ],
- /**
- * @cfg {Boolean} needHighPrecision
- * Indicates that the axis needs high precision surface implementation.
- * See {@link Ext.draw.engine.Canvas#highPrecision}
- */
- needHighPrecision: false,
- /**
- * @cfg {Ext.chart.axis.Axis|String|Number} linkedTo
- * Axis (itself, its ID or index) that this axis is linked to.
- * When an axis is linked to a master axis, it will use the same data as the master axis.
- * It can be used to show additional info, or to ease reading the chart by duplicating
- * the scales.
- */
- linkedTo: null,
- /**
- * @cfg {Number|Object}
- * If `floating` is a number, then it's a percentage displacement of the axis from its
- * initial {@link #position} in the direction opposite to the axis' direction. For instance,
- * '{position:"left", floating:75}' displays a vertical axis at 3/4 of the chart, starting
- * from the left. It is equivalent to '{position:"right", floating:25}'. If `floating` is
- * an object, then `floating.value` is the position of this axis along another axis, defined
- * by `floating.alongAxis`, where `alongAxis` is an ID, an
- * {@link Ext.chart.AbstractChart#axes} config index, or the other axis itself. `alongAxis`
- * must have an opposite {@link Ext.chart.axis.Axis#getAlignment alignment}.
- * For example:
- *
- *
- * axes: [
- * {
- * title: 'Average Temperature (F)',
- * type: 'numeric',
- * position: 'left',
- * id: 'temperature-vertical-axis',
- * minimum: -30,
- * maximum: 130
- * },
- * {
- * title: 'Month (2013)',
- * type: 'category',
- * position: 'bottom',
- * floating: {
- * value: 32,
- * alongAxis: 'temperature-vertical-axis'
- * }
- * }
- * ]
- */
- floating: null
- },
- titleOffset: 0,
- spriteAnimationCount: 0,
- boundSeries: [],
- sprites: null,
- surface: null,
- /**
- * @private
- * @property {Array} range
- * The full data range of the axis. Should not be set directly, Clear it to `null`
- * and use `getRange` to update.
- */
- range: null,
- defaultRange: [
- 0,
- 1
- ],
- rangePadding: 0.5,
- xValues: [],
- yValues: [],
- masterAxis: null,
- applyRotation: function(rotation) {
- var twoPie = Math.PI * 2;
- return (rotation % twoPie + Math.PI) % twoPie - Math.PI;
- },
- updateRotation: function(rotation) {
- var sprites = this.getSprites(),
- position = this.getPosition();
- if (!this.getHidden() && position === 'angular' && sprites[0]) {
- sprites[0].setAttributes({
- baseRotation: rotation
- });
- }
- },
- applyTitle: function(title, oldTitle) {
- var surface;
- if (Ext.isString(title)) {
- title = {
- text: title
- };
- }
- if (!oldTitle) {
- oldTitle = Ext.create('sprite.text', title);
- if ((surface = this.getSurface())) {
- surface.add(oldTitle);
- }
- } else {
- oldTitle.setAttributes(title);
- }
- return oldTitle;
- },
- getAdjustByMajorUnit: function() {
- return !this.getHidden() && this.callParent();
- },
- applyFloating: function(floating, oldFloating) {
- if (floating === null) {
- floating = {
- value: null,
- alongAxis: null
- };
- } else if (Ext.isNumber(floating)) {
- floating = {
- value: floating,
- alongAxis: null
- };
- }
- if (Ext.isObject(floating)) {
- if (oldFloating && oldFloating.alongAxis) {
- delete this.getChart().getAxis(oldFloating.alongAxis).floatingAxes[this.getId()];
- }
- return floating;
- }
- return oldFloating;
- },
- constructor: function(config) {
- var me = this,
- id;
- me.sprites = [];
- me.labels = [];
- // Maps IDs of the axes that float along this axis to their floating values.
- me.floatingAxes = {};
- config = config || {};
- if (config.position === 'angular') {
- config.style = config.style || {};
- config.style.estStepSize = 1;
- }
- if ('id' in config) {
- id = config.id;
- } else if ('id' in me.config) {
- id = me.config.id;
- } else {
- id = me.getId();
- }
- me.setId(id);
- me.mixins.observable.constructor.apply(me, arguments);
- },
- /**
- * @private
- * @return {String}
- */
- getAlignment: function() {
- switch (this.getPosition()) {
- case 'left':
- case 'right':
- return 'vertical';
- case 'top':
- case 'bottom':
- return 'horizontal';
- case 'radial':
- return 'radial';
- case 'angular':
- return 'angular';
- }
- },
- /**
- * @private
- * @return {String}
- */
- getGridAlignment: function() {
- switch (this.getPosition()) {
- case 'left':
- case 'right':
- return 'horizontal';
- case 'top':
- case 'bottom':
- return 'vertical';
- case 'radial':
- return 'circular';
- case 'angular':
- return 'radial';
- }
- },
- /**
- * @private
- * Get the surface for drawing the series sprites
- */
- getSurface: function() {
- var me = this,
- chart = me.getChart(),
- surface, gridSurface;
- if (chart && !me.surface) {
- surface = me.surface = chart.getSurface(me.getId(), 'axis');
- gridSurface = me.gridSurface = chart.getSurface('main');
- gridSurface.waitFor(surface);
- me.getGrid();
- me.createLimits();
- }
- return me.surface;
- },
- createLimits: function() {
- var me = this,
- chart = me.getChart(),
- axisSprite = me.getSprites()[0],
- gridAlignment = me.getGridAlignment(),
- limits;
- if (me.getLimits() && gridAlignment) {
- gridAlignment = gridAlignment.replace('3d', '');
- me.limits = limits = {
- surface: chart.getSurface('overlay'),
- lines: new Ext.chart.Markers(),
- titles: new Ext.draw.sprite.Instancing()
- };
- limits.lines.setTemplate({
- xclass: 'grid.' + gridAlignment
- });
- limits.lines.getTemplate().setAttributes({
- strokeStyle: 'black'
- }, true);
- limits.surface.add(limits.lines);
- axisSprite.bindMarker(gridAlignment + '-limit-lines', me.limits.lines);
- me.limitTitleTpl = new Ext.draw.sprite.Text();
- limits.titles.setTemplate(me.limitTitleTpl);
- limits.surface.add(limits.titles);
- }
- },
- applyGrid: function(grid) {
- // Returning an empty object here if grid was set to 'true' so that
- // config merging in the theme works properly.
- if (grid === true) {
- return {};
- }
- return grid;
- },
- updateGrid: function(grid) {
- var me = this,
- chart = me.getChart(),
- gridSurface = me.gridSurface,
- axisSprite, gridAlignment, gridSprite;
- if (!chart) {
- me.on({
- chartattached: Ext.bind(me.updateGrid, me, [
- grid
- ]),
- single: true
- });
- return;
- }
- axisSprite = me.getSprites()[0];
- gridAlignment = me.getGridAlignment();
- if (grid) {
- gridSprite = me.gridSpriteEven;
- if (!gridSprite) {
- gridSprite = me.gridSpriteEven = new Ext.chart.Markers();
- gridSprite.setTemplate({
- xclass: 'grid.' + gridAlignment
- });
- gridSurface.add(gridSprite);
- axisSprite.bindMarker(gridAlignment + '-even', gridSprite);
- }
- if (Ext.isObject(grid)) {
- gridSprite.getTemplate().setAttributes(grid);
- if (Ext.isObject(grid.even)) {
- gridSprite.getTemplate().setAttributes(grid.even);
- }
- }
- gridSprite = me.gridSpriteOdd;
- if (!gridSprite) {
- gridSprite = me.gridSpriteOdd = new Ext.chart.Markers();
- gridSprite.setTemplate({
- xclass: 'grid.' + gridAlignment
- });
- gridSurface.add(gridSprite);
- axisSprite.bindMarker(gridAlignment + '-odd', gridSprite);
- }
- if (Ext.isObject(grid)) {
- gridSprite.getTemplate().setAttributes(grid);
- if (Ext.isObject(grid.odd)) {
- gridSprite.getTemplate().setAttributes(grid.odd);
- }
- }
- }
- },
- updateMinorTickSteps: function(minorTickSteps) {
- var me = this,
- sprites = me.getSprites(),
- axisSprite = sprites && sprites[0],
- surface;
- if (axisSprite) {
- axisSprite.setAttributes({
- minorTicks: !!minorTickSteps
- });
- surface = me.getSurface();
- if (!me.isConfiguring && surface) {
- surface.renderFrame();
- }
- }
- },
- /**
- *
- * Mapping data value into coordinate.
- *
- * @param {*} value
- * @param {String} field
- * @param {Number} [idx]
- * @param {Ext.util.MixedCollection} [items]
- * @return {Number}
- */
- getCoordFor: function(value, field, idx, items) {
- return this.getLayout().getCoordFor(value, field, idx, items);
- },
- applyPosition: function(pos) {
- return pos.toLowerCase();
- },
- applyLength: function(length, oldLength) {
- return length > 0 ? length : oldLength;
- },
- applyLabel: function(label, oldLabel) {
- if (!oldLabel) {
- oldLabel = new Ext.draw.sprite.Text({});
- }
- if (label) {
- if (this.limitTitleTpl) {
- this.limitTitleTpl.setAttributes(label);
- }
- oldLabel.setAttributes(label);
- }
- return oldLabel;
- },
- applyLayout: function(layout, oldLayout) {
- layout = Ext.factory(layout, null, oldLayout, 'axisLayout');
- layout.setAxis(this);
- return layout;
- },
- applySegmenter: function(segmenter, oldSegmenter) {
- segmenter = Ext.factory(segmenter, null, oldSegmenter, 'segmenter');
- segmenter.setAxis(this);
- return segmenter;
- },
- updateMinimum: function() {
- this.range = null;
- },
- updateMaximum: function() {
- this.range = null;
- },
- hideLabels: function() {
- this.getSprites()[0].setDirty(true);
- this.setLabel({
- hidden: true
- });
- },
- showLabels: function() {
- this.getSprites()[0].setDirty(true);
- this.setLabel({
- hidden: false
- });
- },
- /**
- * Invokes renderFrame on this axis's surface(s)
- */
- renderFrame: function() {
- this.getSurface().renderFrame();
- },
- updateChart: function(newChart, oldChart) {
- var me = this,
- surface;
- if (oldChart) {
- oldChart.unregister(me);
- oldChart.un('serieschange', me.onSeriesChange, me);
- me.linkAxis();
- me.fireEvent('chartdetached', oldChart, me);
- }
- if (newChart) {
- newChart.on('serieschange', me.onSeriesChange, me);
- me.surface = null;
- surface = me.getSurface();
- me.getLabel().setSurface(surface);
- surface.add(me.getSprites());
- surface.add(me.getTitle());
- newChart.register(me);
- me.fireEvent('chartattached', newChart, me);
- }
- },
- applyBackground: function(background) {
- var rect = Ext.ClassManager.getByAlias('sprite.rect');
- return rect.def.normalize(background);
- },
- /**
- * @protected
- * Invoked when data has changed.
- */
- processData: function() {
- this.getLayout().processData();
- this.range = null;
- },
- getDirection: function() {
- return this.getChart().getDirectionForAxis(this.getPosition());
- },
- isSide: function() {
- var position = this.getPosition();
- return position === 'left' || position === 'right';
- },
- applyFields: function(fields) {
- return Ext.Array.from(fields);
- },
- applyVisibleRange: function(visibleRange, oldVisibleRange) {
- var temp;
- this.getChart();
- // If it is in reversed order swap them
- if (visibleRange[0] > visibleRange[1]) {
- temp = visibleRange[0];
- visibleRange[0] = visibleRange[1];
- visibleRange[0] = temp;
- }
- if (visibleRange[1] === visibleRange[0]) {
- visibleRange[1] += 1 / this.getMaxZoom();
- }
- if (visibleRange[1] > visibleRange[0] + 1) {
- visibleRange[0] = 0;
- visibleRange[1] = 1;
- } else if (visibleRange[0] < 0) {
- visibleRange[1] -= visibleRange[0];
- visibleRange[0] = 0;
- } else if (visibleRange[1] > 1) {
- visibleRange[0] -= visibleRange[1] - 1;
- visibleRange[1] = 1;
- }
- if (oldVisibleRange && visibleRange[0] === oldVisibleRange[0] && visibleRange[1] === oldVisibleRange[1]) {
- return undefined;
- }
- return visibleRange;
- },
- updateVisibleRange: function(visibleRange) {
- this.fireEvent('visiblerangechange', this, visibleRange);
- },
- onSeriesChange: function(chart) {
- var me = this,
- series = chart.getSeries(),
- boundSeries = [],
- linkedTo, masterAxis, getAxisMethod, i, ln;
- if (series) {
- getAxisMethod = 'get' + me.getDirection() + 'Axis';
- for (i = 0 , ln = series.length; i < ln; i++) {
- if (this === series[i][getAxisMethod]()) {
- boundSeries.push(series[i]);
- }
- }
- }
- me.boundSeries = boundSeries;
- linkedTo = me.getLinkedTo();
- masterAxis = !Ext.isEmpty(linkedTo) && chart.getAxis(linkedTo);
- if (masterAxis) {
- me.linkAxis(masterAxis);
- } else {
- me.getLayout().processData();
- }
- },
- linkAxis: function(masterAxis) {
- var me = this;
- function link(action, slave, master) {
- master.getLayout()[action]('datachange', 'onDataChange', slave);
- master[action]('rangechange', 'onMasterAxisRangeChange', slave);
- }
- if (me.masterAxis) {
- if (!me.masterAxis.destroyed) {
- link('un', me, me.masterAxis);
- }
- me.masterAxis = null;
- }
- if (masterAxis) {
- if (masterAxis.type !== this.type) {
- Ext.Error.raise("Linked axes must be of the same type.");
- }
- link('on', me, masterAxis);
- me.onDataChange(masterAxis.getLayout().labels);
- me.onMasterAxisRangeChange(masterAxis, masterAxis.range);
- me.setStyle(Ext.apply({}, me.config.style, masterAxis.config.style));
- me.setTitle(Ext.apply({}, me.config.title, masterAxis.config.title));
- me.setLabel(Ext.apply({}, me.config.label, masterAxis.config.label));
- me.masterAxis = masterAxis;
- }
- },
- onDataChange: function(data) {
- this.getLayout().labels = data;
- },
- onMasterAxisRangeChange: function(masterAxis, range) {
- this.range = range;
- },
- applyRange: function(newRange) {
- if (!newRange) {
- return this.dataRange.slice(0);
- } else {
- return [
- newRange[0] === null ? this.dataRange[0] : newRange[0],
- newRange[1] === null ? this.dataRange[1] : newRange[1]
- ];
- }
- },
- /**
- * @private
- */
- setBoundSeriesRange: function(range) {
- var boundSeries = this.boundSeries,
- style = {},
- series, i, sprites, j, ln;
- style['range' + this.getDirection()] = range;
- for (i = 0 , ln = boundSeries.length; i < ln; i++) {
- series = boundSeries[i];
- if (series.getHidden() === true) {
-
- continue;
- }
- sprites = series.getSprites();
- for (j = 0; j < sprites.length; j++) {
- sprites[j].setAttributes(style);
- }
- }
- },
- /**
- * Get the range derived from all the bound series.
- * The range value is cached and returned the next time this method is called.
- * Set `recalculate` to `true` to recalculate the range, if changes to the
- * chart, its components or data are expected to affect the range.
- * @param {Boolean} [recalculate]
- * @return {Number[]}
- */
- getRange: function(recalculate) {
- var me = this,
- range = recalculate ? null : me.range,
- oldRange = me.oldRange,
- minimum, maximum;
- if (!range) {
- if (me.masterAxis) {
- range = me.masterAxis.range;
- } else {
- minimum = me.getMinimum();
- maximum = me.getMaximum();
- if (Ext.isNumber(minimum) && Ext.isNumber(maximum)) {
- range = [
- minimum,
- maximum
- ];
- } else {
- range = me.calculateRange();
- }
- me.range = range;
- }
- }
- if (range && (!oldRange || range[0] !== oldRange[0] || range[1] !== oldRange[1])) {
- me.fireEvent('rangechange', me, range, oldRange);
- me.oldRange = range;
- }
- return range;
- },
- isSingleDataPoint: function(range) {
- return (range[0] + this.rangePadding) === 0 && (range[1] - this.rangePadding) === 0;
- },
- calculateRange: function() {
- var me = this,
- boundSeries = me.boundSeries,
- layout = me.getLayout(),
- segmenter = me.getSegmenter(),
- minimum = me.getMinimum(),
- maximum = me.getMaximum(),
- visibleRange = me.getVisibleRange(),
- getRangeMethod = 'get' + me.getDirection() + 'Range',
- expandRangeBy = me.getExpandRangeBy(),
- context, attr, majorTicks, series, i, ln, seriesRange,
- range = [
- NaN,
- NaN
- ];
- // For each series bound to this axis, ask the series for its min/max values
- // and use them to find the overall min/max.
- for (i = 0 , ln = boundSeries.length; i < ln; i++) {
- series = boundSeries[i];
- if (series.getHidden() === true) {
-
- continue;
- }
- seriesRange = series[getRangeMethod]();
- if (seriesRange) {
- Ext.chart.Util.expandRange(range, seriesRange);
- }
- }
- range = Ext.chart.Util.validateRange(range, me.defaultRange, me.rangePadding);
- // The second condition is there to account for a special case where we only have
- // a single data point, so the effective range of coordinated data is 0 (whatever
- // the actual value of that single data point is, it will be assigned an index of
- // zero, as the first and only data point). Since zero range is invalid, the
- // validateRange function above will expand the range by the value of the rangePadding,
- // which makes further expansion by the value of expandRangeBy unnecessary.
- if (expandRangeBy && (!me.isSingleDataPoint(range))) {
- range[0] -= expandRangeBy;
- range[1] += expandRangeBy;
- }
- if (isFinite(minimum)) {
- range[0] = minimum;
- }
- if (isFinite(maximum)) {
- range[1] = maximum;
- }
- // When series `fullStack` config is used, the values may add up to
- // slightly more than the value of the `fullStackTotal` config
- // because of a precision error.
- range[0] = Ext.Number.correctFloat(range[0]);
- range[1] = Ext.Number.correctFloat(range[1]);
- me.range = range;
- // It's important to call 'me.reconcileRange' after the 'range'
- // has been assigned to avoid circular calls.
- if (me.getReconcileRange()) {
- me.reconcileRange();
- }
- // TODO: Find a better way to do this.
- // TODO: The original design didn't take into account that the range of an axis
- // TODO: will depend not just on the range of the data of the bound series in the
- // TODO: direction of the axis, but also on the range of other axes with the
- // TODO: same direction and on the segmentation of the axis (interval between
- // TODO: major ticks).
- // TODO: While the fist omission was possible to retrofit rather gracefully
- // TODO: by adding the axis.reconcileRange method, the second one is harder to deal with.
- // TODO: The issue is that the resulting axis segmentation, which is a part of
- // TODO: the axis sprite layout has to be known before layout has begun.
- // TODO: Example for the logic below:
- // TODO: If we have a range of data of 0..34.5 the step will be 2 and we
- // TODO: will round up the max to 36 based on that step, but when the range is 0..36,
- // TODO: the step becomes 5, so we have to reconcile the range once again where max
- // TODO: becomes 40.
- if (range[0] !== range[1] && me.getAdjustByMajorUnit() && segmenter.adjustByMajorUnit && !me.getMajorTickSteps()) {
- attr = Ext.Object.chain(me.getSprites()[0].attr);
- attr.min = range[0];
- attr.max = range[1];
- attr.visibleMin = visibleRange[0];
- attr.visibleMax = visibleRange[1];
- context = {
- attr: attr,
- segmenter: segmenter
- };
- layout.calculateLayout(context);
- majorTicks = context.majorTicks;
- if (majorTicks) {
- segmenter.adjustByMajorUnit(majorTicks.step, majorTicks.unit.scale, range);
- attr.min = range[0];
- attr.max = range[1];
- context.majorTicks = null;
- layout.calculateLayout(context);
- majorTicks = context.majorTicks;
- segmenter.adjustByMajorUnit(majorTicks.step, majorTicks.unit.scale, range);
- } else if (!me.hasClearRangePending) {
- // Axis hasn't been rendered yet.
- me.hasClearRangePending = true;
- me.getChart().on('layout', 'clearRange', me);
- }
- }
- return range;
- },
- /**
- * @private
- */
- clearRange: function() {
- this.hasClearRangePending = null;
- this.range = null;
- },
- /**
- * Expands the range of the axis
- * based on the range of other axes with the same direction (if any).
- */
- reconcileRange: function() {
- var me = this,
- axes = me.getChart().getAxes(),
- direction = me.getDirection(),
- i, ln, axis, range;
- if (!axes) {
- return;
- }
- for (i = 0 , ln = axes.length; i < ln; i++) {
- axis = axes[i];
- range = axis.getRange();
- if (axis === me || axis.getDirection() !== direction || !range || !axis.getReconcileRange()) {
-
- continue;
- }
- if (range[0] < me.range[0]) {
- me.range[0] = range[0];
- }
- if (range[1] > me.range[1]) {
- me.range[1] = range[1];
- }
- }
- },
- applyStyle: function(style, oldStyle) {
- var cls = Ext.ClassManager.getByAlias('sprite.' + this.seriesType);
- if (cls && cls.def) {
- style = cls.def.normalize(style);
- }
- oldStyle = Ext.apply(oldStyle || {}, style);
- return oldStyle;
- },
- themeOnlyIfConfigured: {
- grid: true
- },
- updateTheme: function(theme) {
- var me = this,
- axisTheme = theme.getAxis(),
- position = me.getPosition(),
- initialConfig = me.getInitialConfig(),
- defaultConfig = me.defaultConfig,
- configs = me.self.getConfigurator().configs,
- genericAxisTheme = axisTheme.defaults,
- specificAxisTheme = axisTheme[position],
- themeOnlyIfConfigured = me.themeOnlyIfConfigured,
- key, value, isObjValue, isUnusedConfig, initialValue, cfg;
- axisTheme = Ext.merge({}, genericAxisTheme, specificAxisTheme);
- for (key in axisTheme) {
- value = axisTheme[key];
- cfg = configs[key];
- if (value !== null && value !== undefined && cfg) {
- initialValue = initialConfig[key];
- isObjValue = Ext.isObject(value);
- isUnusedConfig = initialValue === defaultConfig[key];
- if (isObjValue) {
- if (isUnusedConfig && themeOnlyIfConfigured[key]) {
-
- continue;
- }
- value = Ext.merge({}, value, initialValue);
- }
- if (isUnusedConfig || isObjValue) {
- me[cfg.names.set](value);
- }
- }
- }
- },
- updateCenter: function(center) {
- var me = this,
- sprites = me.getSprites(),
- axisSprite = sprites[0],
- centerX = center[0],
- centerY = center[1];
- if (axisSprite) {
- axisSprite.setAttributes({
- centerX: centerX,
- centerY: centerY
- });
- }
- if (me.gridSpriteEven) {
- me.gridSpriteEven.getTemplate().setAttributes({
- translationX: centerX,
- translationY: centerY,
- rotationCenterX: centerX,
- rotationCenterY: centerY
- });
- }
- if (me.gridSpriteOdd) {
- me.gridSpriteOdd.getTemplate().setAttributes({
- translationX: centerX,
- translationY: centerY,
- rotationCenterX: centerX,
- rotationCenterY: centerY
- });
- }
- },
- getSprites: function() {
- if (!this.getChart()) {
- return;
- }
- // eslint-disable-next-line vars-on-top
- var me = this,
- range = me.getRange(),
- position = me.getPosition(),
- chart = me.getChart(),
- animation = chart.getAnimation(),
- length = me.getLength(),
- axisClass = me.superclass,
- mainSprite, style, animationModifier;
- // If animation is false, then stop animation.
- if (animation === false) {
- animation = {
- duration: 0
- };
- }
- style = Ext.applyIf({
- position: position,
- axis: me,
- length: length,
- grid: me.getGrid(),
- hidden: me.getHidden(),
- titleOffset: me.titleOffset,
- layout: me.getLayout(),
- segmenter: me.getSegmenter(),
- totalAngle: me.getTotalAngle(),
- label: me.getLabel()
- }, me.getStyle());
- if (range) {
- style.min = range[0];
- style.max = range[1];
- }
- // If the sprites are not created.
- if (!me.sprites.length) {
- while (!axisClass.xtype) {
- axisClass = axisClass.superclass;
- }
- mainSprite = Ext.create('sprite.' + axisClass.xtype, style);
- animationModifier = mainSprite.getAnimation();
- animationModifier.setCustomDurations({
- baseRotation: 0
- });
- animationModifier.on('animationstart', 'onAnimationStart', me);
- animationModifier.on('animationend', 'onAnimationEnd', me);
- mainSprite.setLayout(me.getLayout());
- mainSprite.setSegmenter(me.getSegmenter());
- mainSprite.setLabel(me.getLabel());
- me.sprites.push(mainSprite);
- me.updateTitleSprite();
- } else {
- mainSprite = me.sprites[0];
- mainSprite.setAnimation(animation);
- mainSprite.setAttributes(style);
- }
- if (me.getRenderer()) {
- mainSprite.setRenderer(me.getRenderer());
- }
- return me.sprites;
- },
- /**
- * @private
- */
- performLayout: function() {
- if (this.isConfiguring) {
- return;
- }
- // eslint-disable-next-line vars-on-top
- var me = this,
- sprites = me.getSprites(),
- surface = me.getSurface(),
- chart = me.getChart(),
- sprite = sprites && sprites[0];
- if (chart && surface && sprite) {
- sprite.callUpdater(null, 'layout');
- // recalculate axis ticks
- chart.scheduleLayout();
- }
- },
- updateTitleSprite: function() {
- var me = this,
- length = me.getLength(),
- surface, thickness, title, position, margin, titleMargin, anchor;
- if (!me.sprites[0] || !Ext.isNumber(length)) {
- return;
- }
- thickness = this.sprites[0].thickness;
- surface = me.getSurface();
- title = me.getTitle();
- position = me.getPosition();
- margin = me.getMargin();
- titleMargin = me.getTitleMargin();
- anchor = surface.roundPixel(length / 2);
- if (title) {
- switch (position) {
- case 'top':
- title.setAttributes({
- x: anchor,
- y: margin + titleMargin / 2,
- textBaseline: 'top',
- textAlign: 'center'
- }, true);
- title.applyTransformations();
- me.titleOffset = title.getBBox().height + titleMargin;
- break;
- case 'bottom':
- title.setAttributes({
- x: anchor,
- y: thickness + titleMargin / 2,
- textBaseline: 'top',
- textAlign: 'center'
- }, true);
- title.applyTransformations();
- me.titleOffset = title.getBBox().height + titleMargin;
- break;
- case 'left':
- title.setAttributes({
- x: margin + titleMargin / 2,
- y: anchor,
- textBaseline: 'top',
- textAlign: 'center',
- rotationCenterX: margin + titleMargin / 2,
- rotationCenterY: anchor,
- rotationRads: -Math.PI / 2
- }, true);
- title.applyTransformations();
- me.titleOffset = title.getBBox().width + titleMargin;
- break;
- case 'right':
- title.setAttributes({
- x: thickness - margin + titleMargin / 2,
- y: anchor,
- textBaseline: 'bottom',
- textAlign: 'center',
- rotationCenterX: thickness + titleMargin / 2,
- rotationCenterY: anchor,
- rotationRads: Math.PI / 2
- }, true);
- title.applyTransformations();
- me.titleOffset = title.getBBox().width + titleMargin;
- break;
- }
- }
- },
- onThicknessChanged: function() {
- this.getChart().onThicknessChanged();
- },
- getThickness: function() {
- if (this.getHidden()) {
- return 0;
- }
- return (this.sprites[0] && this.sprites[0].thickness || 1) + this.titleOffset + this.getMargin();
- },
- onAnimationStart: function() {
- this.spriteAnimationCount++;
- if (this.spriteAnimationCount === 1) {
- this.fireEvent('animationstart', this);
- }
- },
- onAnimationEnd: function() {
- this.spriteAnimationCount--;
- if (this.spriteAnimationCount === 0) {
- this.fireEvent('animationend', this);
- }
- },
- // Methods used in ComponentQuery and controller
- getItemId: function() {
- return this.getId();
- },
- getAncestorIds: function() {
- return [
- this.getChart().getId()
- ];
- },
- isXType: function(xtype) {
- return xtype === 'axis';
- },
- // Override the Observable's method to redirect listener scope
- // resolution to the chart.
- resolveListenerScope: function(defaultScope) {
- var me = this,
- namedScope = Ext._namedScopes[defaultScope],
- chart = me.getChart(),
- scope;
- if (!namedScope) {
- scope = chart ? chart.resolveListenerScope(defaultScope, false) : (defaultScope || me);
- } else if (namedScope.isThis) {
- scope = me;
- } else if (namedScope.isController) {
- scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
- } else if (namedScope.isSelf) {
- scope = chart ? chart.resolveListenerScope(defaultScope, false) : me;
- // Class body listener. No chart controller, nor chart container controller.
- if (scope === chart && !chart.getInheritedConfig('defaultListenerScope')) {
- scope = me;
- }
- }
- return scope;
- },
- destroy: function() {
- var me = this;
- me.setChart(null);
- me.surface.destroy();
- me.surface = null;
- me.callParent();
- }
- });
- /**
- * The legend base class adapater for modern toolkit.
- */
- Ext.define('Ext.chart.legend.LegendBase', {
- extend: 'Ext.dataview.DataView',
- config: {
- /* eslint-disable max-len, no-useless-escape */
- itemTpl: [
- '<span class="',
- Ext.baseCSSPrefix,
- 'legend-item-marker {[ values.disabled ? Ext.baseCSSPrefix + \'legend-item-inactive\' : \'\' ]}" style="background:{mark};"></span>{name}'
- ],
- /* eslint-enable max-len, no-useless-escape */
- inline: true,
- scrollable: false
- },
- // for IE11 vertical align
- constructor: function(config) {
- var scroller, onDrag;
- this.callParent([
- config
- ]);
- scroller = this.getScrollable();
- onDrag = scroller.onDrag;
- scroller.onDrag = function(e) {
- e.stopPropagation();
- onDrag.call(this, e);
- };
- },
- updateDocked: function(docked, oldDocked) {
- var me = this,
- el = me.el;
- me.callParent([
- docked,
- oldDocked
- ]);
- switch (docked) {
- case 'top':
- // eslint-disable-next-line no-fallthrough
- case 'bottom':
- el.addCls(me.horizontalCls);
- el.removeCls(me.verticalCls);
- break;
- case 'left':
- // eslint-disable-next-line no-fallthrough
- case 'right':
- el.addCls(me.verticalCls);
- el.removeCls(me.horizontalCls);
- break;
- }
- },
- onChildTap: function(view, context) {
- this.callParent([
- view,
- context
- ]);
- this.toggleItem(context.viewIndex);
- }
- });
- /**
- * This class provides a dataview-based chart legend.
- */
- Ext.define('Ext.chart.legend.Legend', {
- extend: 'Ext.chart.legend.LegendBase',
- alternateClassName: 'Ext.chart.Legend',
- xtype: 'legend',
- alias: 'legend.dom',
- type: 'dom',
- isLegend: true,
- isDomLegend: true,
- config: {
- /**
- * @cfg {Array}
- * The rect of the legend relative to its container.
- */
- rect: null,
- /**
- * @cfg {Boolean} toggleable
- * `true` to allow series items to have their visibility
- * toggled by interaction with the legend items.
- */
- toggleable: true
- },
- /**
- * @cfg {Ext.chart.legend.store.Store} store
- * The {@link Ext.chart.legend.store.Store} to bind this legend to.
- * @private
- */
- baseCls: Ext.baseCSSPrefix + 'legend',
- horizontalCls: Ext.baseCSSPrefix + 'legend-horizontal',
- verticalCls: Ext.baseCSSPrefix + 'legend-vertical',
- toggleItem: function(index) {
- var disabledCount = 0,
- canToggle = true,
- disabled, store, count, record, i;
- if (!this.getToggleable()) {
- return;
- }
- store = this.getStore();
- if (store) {
- count = store.getCount();
- for (i = 0; i < count; i++) {
- record = store.getAt(i);
- if (record.get('disabled')) {
- disabledCount++;
- }
- }
- canToggle = count - disabledCount > 1;
- record = store.getAt(index);
- if (record) {
- disabled = record.get('disabled');
- if (disabled || canToggle) {
- // This will trigger AbstractChart.onLegendStoreUpdate.
- record.set('disabled', !disabled);
- }
- }
- }
- },
- onResize: function(width, height, oldWidth, oldHeight) {
- var me = this,
- chart = me.chart;
- if (!me.isConfiguring) {
- if (chart) {
- chart.scheduleLayout();
- }
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.chart.legend.sprite.Item', {
- extend: 'Ext.draw.sprite.Composite',
- alias: 'sprite.legenditem',
- type: 'legenditem',
- isLegendItem: true,
- requires: [
- 'Ext.draw.sprite.Text',
- 'Ext.draw.sprite.Circle'
- ],
- inheritableStatics: {
- def: {
- processors: {
- enabled: 'limited01',
- markerLabelGap: 'number'
- },
- animationProcessors: {
- enabled: null,
- markerLabelGap: null
- },
- defaults: {
- enabled: true,
- markerLabelGap: 5
- },
- triggers: {
- enabled: 'enabled',
- markerLabelGap: 'layout'
- },
- updaters: {
- layout: 'layoutUpdater',
- enabled: 'enabledUpdater'
- }
- }
- },
- config: {
- // Sprite's attributes are processed after initConfig.
- // So we need to init below configs lazily, as otherwise
- // adding sprites (created from those configs) to composite
- // will result in an attempt to access attributes that
- // composite doesn't have yet.
- label: {
- $value: {
- type: 'text'
- },
- lazy: true
- },
- marker: {
- $value: {
- type: 'circle'
- },
- lazy: true
- },
- legend: null,
- store: null,
- record: null,
- series: null
- },
- applyLabel: function(label, oldLabel) {
- var sprite;
- if (label) {
- if (label.isSprite && label.type === 'text') {
- sprite = label;
- } else {
- if (oldLabel && label.type === oldLabel.type) {
- oldLabel.setConfig(label);
- sprite = oldLabel;
- this.scheduleUpdater(this.attr, 'layout');
- } else {
- sprite = new Ext.draw.sprite.Text(label);
- }
- }
- }
- return sprite;
- },
- defaultMarkerSize: 10,
- updateLabel: function(label, oldLabel) {
- var me = this;
- me.removeSprite(oldLabel);
- label.setAttributes({
- textBaseline: 'middle'
- });
- me.addSprite(label);
- me.scheduleUpdater(me.attr, 'layout');
- },
- applyMarker: function(config) {
- var marker;
- if (config) {
- if (config.isSprite) {
- marker = config;
- } else {
- marker = this.createMarker(config);
- }
- }
- marker = this.resetMarker(marker, config);
- return marker;
- },
- createMarker: function(config) {
- var marker;
- // If marker attributes are animated, the attributes change over
- // time from default values to the values specified in the marker
- // config. But the 'legenditem' sprite needs final values
- // to properly layout its children.
- delete config.animation;
- if (config.type === 'image') {
- delete config.width;
- delete config.height;
- }
- marker = Ext.create('sprite.' + config.type, config);
- return marker;
- },
- resetMarker: function(sprite, config) {
- var size = config.size || this.defaultMarkerSize,
- bbox, max, scale;
- // Layout may not work properly,
- // if the marker sprite is transformed to begin with.
- sprite.setTransform([
- 1,
- 0,
- 0,
- 1,
- 0,
- 0
- ], true);
- if (config.type === 'image') {
- sprite.setAttributes({
- width: size,
- height: size
- });
- } else {
- // This should work with any sprite, irrespective of what attribute
- // is used to control sprite's size ('size', 'r', or something else).
- // However, the 'image' sprite above is a special case.
- bbox = sprite.getBBox();
- max = Math.max(bbox.width, bbox.height);
- scale = size / max;
- sprite.setAttributes({
- scalingX: scale,
- scalingY: scale
- });
- }
- return sprite;
- },
- updateMarker: function(marker, oldMarker) {
- var me = this;
- me.removeSprite(oldMarker);
- me.addSprite(marker);
- me.scheduleUpdater(me.attr, 'layout');
- },
- updateSurface: function(surface, oldSurface) {
- var me = this;
- me.callParent([
- surface,
- oldSurface
- ]);
- if (surface) {
- me.scheduleUpdater(me.attr, 'layout');
- }
- },
- enabledUpdater: function(attr) {
- var marker = this.getMarker();
- if (marker) {
- marker.setAttributes({
- globalAlpha: attr.enabled ? 1 : 0.3
- });
- }
- },
- layoutUpdater: function() {
- var me = this,
- attr = me.attr,
- label = me.getLabel(),
- marker = me.getMarker(),
- labelBBox, markerBBox, totalHeight;
- // Measuring bounding boxes of transformed marker and label
- // sprites and translating the sprites by required amount,
- // makes layout virtually bullet-proof to unaccounted for
- // changes in sprite attributes, whatever the sprite type may be.
- markerBBox = marker.getBBox();
- labelBBox = label.getBBox();
- totalHeight = Math.max(markerBBox.height, labelBBox.height);
- // Because we are getting an already transformed bounding box,
- // we want to add to that transformation, not replace it,
- // so setting translationX/Y attributes here would be inappropriate.
- marker.transform([
- 1,
- 0,
- 0,
- 1,
- -markerBBox.x,
- -markerBBox.y + (totalHeight - markerBBox.height) / 2
- ], true);
- label.transform([
- 1,
- 0,
- 0,
- 1,
- -labelBBox.x + markerBBox.width + attr.markerLabelGap,
- -labelBBox.y + (totalHeight - labelBBox.height) / 2
- ], true);
- me.bboxUpdater(attr);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.chart.legend.sprite.Border', {
- extend: 'Ext.draw.sprite.Rect',
- alias: 'sprite.legendborder',
- type: 'legendborder',
- isLegendBorder: true
- });
- /**
- * @private
- * Singleton that provides methods used by the Ext.draw.Path
- * for hit testing and finding path intersection points.
- */
- Ext.define('Ext.draw.PathUtil', function() {
- var abs = Math.abs,
- pow = Math.pow,
- cos = Math.cos,
- acos = Math.acos,
- sqrt = Math.sqrt,
- PI = Math.PI;
- // For extra info see: http://pomax.github.io/bezierinfo/
- return {
- singleton: true,
- requires: [
- 'Ext.draw.overrides.hittest.Path',
- 'Ext.draw.overrides.hittest.sprite.Path'
- ],
- /**
- * @private
- * Finds roots of a cubic equation in t, where t lies in the interval of [0,1].
- * Based on http://www.particleincell.com/blog/2013/cubic-line-intersection/
- * @param P {Number[]} Cubic equation coefficients.
- * @return {Number[]} Returns an array of parametric intersection locations along the cubic,
- * with -1 indicating an out-of-bounds intersection
- * (before or after the end point or in the imaginary plane).
- */
- cubicRoots: function(P) {
- var a = P[0],
- b = P[1],
- c = P[2],
- d = P[3];
- if (a === 0) {
- return this.quadraticRoots(b, c, d);
- }
- // eslint-disable-next-line vars-on-top, one-var
- var A = b / a,
- B = c / a,
- C = d / a,
- Q = (3 * B - pow(A, 2)) / 9,
- R = (9 * A * B - 27 * C - 2 * pow(A, 3)) / 54,
- D = pow(Q, 3) + pow(R, 2),
- // Polynomial discriminant.
- t = [],
- S, T, Im, th, i,
- sign = Ext.Number.sign;
- if (D >= 0) {
- // Complex or duplicate roots.
- S = sign(R + sqrt(D)) * pow(abs(R + sqrt(D)), 1 / 3);
- T = sign(R - sqrt(D)) * pow(abs(R - sqrt(D)), 1 / 3);
- t[0] = -A / 3 + (S + T);
- // Real root.
- t[1] = -A / 3 - (S + T) / 2;
- // Real part of complex root.
- t[2] = t[1];
- // Real part of complex root.
- Im = abs(sqrt(3) * (S - T) / 2);
- // Complex part of root pair.
- // Discard complex roots.
- if (Im !== 0) {
- t[1] = -1;
- t[2] = -1;
- }
- } else {
- // Distinct real roots.
- th = acos(R / sqrt(-pow(Q, 3)));
- t[0] = 2 * sqrt(-Q) * cos(th / 3) - A / 3;
- t[1] = 2 * sqrt(-Q) * cos((th + 2 * PI) / 3) - A / 3;
- t[2] = 2 * sqrt(-Q) * cos((th + 4 * PI) / 3) - A / 3;
- }
- // Discard out of spec roots.
- for (i = 0; i < 3; i++) {
- if (t[i] < 0 || t[i] > 1) {
- t[i] = -1;
- }
- }
- return t;
- },
- /**
- * @private
- * Finds roots of a quadratic equation in t, where t lies in the interval of [0,1].
- * Takes three quadratic equation coefficients as parameters.
- * @param a {Number}
- * @param b {Number}
- * @param c {Number}
- * @return {Array}
- */
- quadraticRoots: function(a, b, c) {
- var D, rD, t, i;
- if (a === 0) {
- return this.linearRoot(b, c);
- }
- D = b * b - 4 * a * c;
- if (D === 0) {
- // One real root.
- t = [
- -b / (2 * a)
- ];
- } else if (D > 0) {
- // Distinct real roots.
- rD = sqrt(D);
- t = [
- (-b - rD) / (2 * a),
- (-b + rD) / (2 * a)
- ];
- } else {
- // Complex roots.
- return [];
- }
- for (i = 0; i < t.length; i++) {
- if (t[i] < 0 || t[i] > 1) {
- t[i] = -1;
- }
- }
- return t;
- },
- /**
- * @private
- * Finds roots of a linear equation in t, where t lies in the interval of [0,1].
- * Takes two linear equation coefficients as parameters.
- * @param a {Number}
- * @param b {Number}
- * @return {Array}
- */
- linearRoot: function(a, b) {
- var t = -b / a;
- if (a === 0 || t < 0 || t > 1) {
- return [];
- }
- return [
- t
- ];
- },
- /**
- * @private
- * Calculates the coefficients of a cubic function for the given coordinates.
- * @param P0 {Number}
- * @param P1 {Number}
- * @param P2 {Number}
- * @param P3 {Number}
- * @return {Array}
- */
- bezierCoeffs: function(P0, P1, P2, P3) {
- var Z = [];
- Z[0] = -P0 + 3 * P1 - 3 * P2 + P3;
- Z[1] = 3 * P0 - 6 * P1 + 3 * P2;
- Z[2] = -3 * P0 + 3 * P1;
- Z[3] = P0;
- return Z;
- },
- /**
- * @private
- * Computes intersection points between a cubic spline and a line segment.
- * Takes in x/y components of cubic control points and line segment start/end points
- * as parameters.
- * @param px1 {Number}
- * @param px2 {Number}
- * @param px3 {Number}
- * @param px4 {Number}
- * @param py1 {Number}
- * @param py2 {Number}
- * @param py3 {Number}
- * @param py4 {Number}
- * @param x1 {Number}
- * @param y1 {Number}
- * @param x2 {Number}
- * @param y2 {Number}
- * @return {Array} Array of intersection points, where each intersection point
- * is itself a two-item array [x,y].
- */
- cubicLineIntersections: function(px1, px2, px3, px4, py1, py2, py3, py4, x1, y1, x2, y2) {
- var P = [],
- intersections = [],
- // Finding line equation coefficients.
- A = y1 - y2,
- B = x2 - x1,
- C = x1 * (y2 - y1) - y1 * (x2 - x1),
- // Finding cubic Bezier curve equation coefficients.
- bx = this.bezierCoeffs(px1, px2, px3, px4),
- by = this.bezierCoeffs(py1, py2, py3, py4),
- i, r, s, t, tt, ttt, cx, cy;
- P[0] = A * bx[0] + B * by[0];
- // t^3
- P[1] = A * bx[1] + B * by[1];
- // t^2
- P[2] = A * bx[2] + B * by[2];
- // t
- P[3] = A * bx[3] + B * by[3] + C;
- // 1
- r = this.cubicRoots(P);
- // Verify the roots are in bounds of the linear segment.
- for (i = 0; i < r.length; i++) {
- t = r[i];
- if (t < 0 || t > 1) {
-
- continue;
- }
- tt = t * t;
- ttt = tt * t;
- cx = bx[0] * ttt + bx[1] * tt + bx[2] * t + bx[3];
- cy = by[0] * ttt + by[1] * tt + by[2] * t + by[3];
- // Above is intersection point assuming infinitely long line segment,
- // make sure we are also in bounds of the line.
- if ((x2 - x1) !== 0) {
- // If not vertical line
- s = (cx - x1) / (x2 - x1);
- } else {
- s = (cy - y1) / (y2 - y1);
- }
- // In bounds?
- if (!(s < 0 || s > 1)) {
- intersections.push([
- cx,
- cy
- ]);
- }
- }
- return intersections;
- },
- /**
- * @private
- * Splits cubic Bezier curve into two cubic Bezier curves at point z,
- * where z belongs to a range of [0, 1].
- * Accepts cubic coefficients and point z as parameters.
- * @param P1 {Number}
- * @param P2 {Number}
- * @param P3 {Number}
- * @param P4 {Number}
- * @param z Point to split the given curve at.
- * @return {Array} Two-item array, where each item is itself an array
- * of cubic coefficients.
- */
- splitCubic: function(P1, P2, P3, P4, z) {
- var zz = z * z,
- zzz = z * zz,
- iz = z - 1,
- izz = iz * iz,
- izzz = iz * izz,
- // Common point for both curves.
- P = zzz * P4 - 3 * zz * iz * P3 + 3 * z * izz * P2 - izzz * P1;
- return [
- [
- P1,
- z * P2 - iz * P1,
- zz * P3 - 2 * z * iz * P2 + izz * P1,
- P
- ],
- [
- P,
- zz * P4 - 2 * z * iz * P3 + izz * P2,
- z * P4 - iz * P3,
- P4
- ]
- ];
- },
- /**
- * @private
- * Returns the dimension of a cubic Bezier curve in a single direction.
- * @param a {Number}
- * @param b {Number}
- * @param c {Number}
- * @param d {Number}
- * @return {Array} Two-item array representing cubic's range in the given direction.
- */
- cubicDimension: function(a, b, c, d) {
- var qa = 3 * (-a + 3 * (b - c) + d),
- qb = 6 * (a - 2 * b + c),
- qc = -3 * (a - b),
- x, y,
- min = Math.min(a, d),
- max = Math.max(a, d),
- delta;
- if (qa === 0) {
- if (qb === 0) {
- return [
- min,
- max
- ];
- } else {
- x = -qc / qb;
- if (0 < x && x < 1) {
- y = this.interpolateCubic(a, b, c, d, x);
- min = Math.min(min, y);
- max = Math.max(max, y);
- }
- }
- } else {
- delta = qb * qb - 4 * qa * qc;
- if (delta >= 0) {
- delta = sqrt(delta);
- x = (delta - qb) / 2 / qa;
- if (0 < x && x < 1) {
- y = this.interpolateCubic(a, b, c, d, x);
- min = Math.min(min, y);
- max = Math.max(max, y);
- }
- if (delta > 0) {
- x -= delta / qa;
- if (0 < x && x < 1) {
- y = this.interpolateCubic(a, b, c, d, x);
- min = Math.min(min, y);
- max = Math.max(max, y);
- }
- }
- }
- }
- return [
- min,
- max
- ];
- },
- /**
- * @private
- * Calculates a value of a cubic function at the given point t. In other words
- * returns a * (1 - t) ^ 3 + 3 * b (1 - t) ^ 2 * t + 3 * c (1 - t) * t ^ 3 + d * t ^ 3
- * for given a, b, c, d and t, where t belongs to an interval of [0, 1].
- * @param a {Number}
- * @param b {Number}
- * @param c {Number}
- * @param d {Number}
- * @param t {Number}
- * @return {Number}
- */
- interpolateCubic: function(a, b, c, d, t) {
- var rate;
- if (t === 0) {
- return a;
- }
- if (t === 1) {
- return d;
- }
- rate = (1 - t) / t;
- return t * t * t * (d + rate * (3 * c + rate * (3 * b + rate * a)));
- },
- /**
- * @private
- * Computes intersection points between two cubic Bezier curve segments.
- * Takes x/y components of control points for two Bezier curve segments.
- * @param ax1 {Number}
- * @param ax2 {Number}
- * @param ax3 {Number}
- * @param ax4 {Number}
- * @param ay1 {Number}
- * @param ay2 {Number}
- * @param ay3 {Number}
- * @param ay4 {Number}
- * @param bx1 {Number}
- * @param bx2 {Number}
- * @param bx3 {Number}
- * @param bx4 {Number}
- * @param by1 {Number}
- * @param by2 {Number}
- * @param by3 {Number}
- * @param by4 {Number}
- * @return {Array} Array of intersection points, where each intersection point
- * is itself a two-item array [x,y].
- */
- cubicsIntersections: function(ax1, ax2, ax3, ax4, ay1, ay2, ay3, ay4, bx1, bx2, bx3, bx4, by1, by2, by3, by4) {
- /* eslint-disable max-len */
- var me = this,
- axDim = me.cubicDimension(ax1, ax2, ax3, ax4),
- ayDim = me.cubicDimension(ay1, ay2, ay3, ay4),
- bxDim = me.cubicDimension(bx1, bx2, bx3, bx4),
- byDim = me.cubicDimension(by1, by2, by3, by4),
- splitAx, splitAy, splitBx, splitBy,
- points = [];
- // Curves' bounding boxes don't intersect.
- if (axDim[0] > bxDim[1] || axDim[1] < bxDim[0] || ayDim[0] > byDim[1] || ayDim[1] < byDim[0]) {
- return [];
- }
- // Both curves occupy sub-pixel areas which is effectively their intersection point.
- 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) {
- return [
- [
- (ax1 + ax4) * 0.5,
- (ay1 + ay2) * 0.5
- ]
- ];
- }
- splitAx = me.splitCubic(ax1, ax2, ax3, ax4, 0.5);
- splitAy = me.splitCubic(ay1, ay2, ay3, ay4, 0.5);
- splitBx = me.splitCubic(bx1, bx2, bx3, bx4, 0.5);
- splitBy = me.splitCubic(by1, by2, by3, by4, 0.5);
- points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[0].concat(splitAy[0], splitBx[0], splitBy[0])));
- points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[0].concat(splitAy[0], splitBx[1], splitBy[1])));
- points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[1].concat(splitAy[1], splitBx[0], splitBy[0])));
- points.push.apply(points, me.cubicsIntersections.apply(me, splitAx[1].concat(splitAy[1], splitBx[1], splitBy[1])));
- return points;
- },
- /* eslint-enable max-len */
- /**
- * @private
- * Returns the point [x,y] where two line segments intersect or null.
- * Takes x/y components of the start and end point of the segments as parameters.
- * Based on Paul Bourke's explanation:
- * http://paulbourke.net/geometry/pointlineplane/
- * @param x1 {Number}
- * @param y1 {Number}
- * @param x2 {Number}
- * @param y2 {Number}
- * @param x3 {Number}
- * @param y3 {Number}
- * @param x4 {Number}
- * @param y4 {Number}
- * @return {Number[]|null}
- */
- linesIntersection: function(x1, y1, x2, y2, x3, y3, x4, y4) {
- var d = (x2 - x1) * (y4 - y3) - (y2 - y1) * (x4 - x3),
- ua, ub;
- if (d === 0) {
- // Lines are parallel.
- return null;
- }
- ua = ((x4 - x3) * (y1 - y3) - (x1 - x3) * (y4 - y3)) / d;
- ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / d;
- if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
- return [
- x1 + ua * (x2 - x1),
- // x
- y1 + ua * (y2 - y1)
- ];
- }
- // y
- return null;
- },
- // The intersection point is outside one or both segments.
- /**
- * @private
- * Checks if a point belongs to a line segment.
- * Takes x/y components of the start and end points of the segment and the point's
- * coordinates as parameters.
- * @param x1 {Number}
- * @param y1 {Number}
- * @param x2 {Number}
- * @param y2 {Number}
- * @param x {Number}
- * @param y {Number}
- * @return {Boolean}
- */
- pointOnLine: function(x1, y1, x2, y2, x, y) {
- var t, _;
- if (abs(x2 - x1) < abs(y2 - y1)) {
- _ = x1;
- x1 = y1;
- y1 = _;
- _ = x2;
- x2 = y2;
- y2 = _;
- _ = x;
- x = y;
- y = _;
- }
- t = (x - x1) / (x2 - x1);
- if (t < 0 || t > 1) {
- return false;
- }
- return abs(y1 + t * (y2 - y1) - y) < 4;
- },
- /**
- * @private
- * Checks if a point belongs to a cubic Bezier curve segment.
- * Takes x/y components of the control points of the segment and the point's
- * coordinates as parameters.
- * @param px1 {Number}
- * @param px2 {Number}
- * @param px3 {Number}
- * @param px4 {Number}
- * @param py1 {Number}
- * @param py2 {Number}
- * @param py3 {Number}
- * @param py4 {Number}
- * @param x {Number}
- * @param y {Number}
- * @return {Boolean}
- */
- pointOnCubic: function(px1, px2, px3, px4, py1, py2, py3, py4, x, y) {
- // Finding cubic Bezier curve equation coefficients.
- var me = this,
- bx = me.bezierCoeffs(px1, px2, px3, px4),
- by = me.bezierCoeffs(py1, py2, py3, py4),
- i, j, rx, ry, t;
- bx[3] -= x;
- by[3] -= y;
- rx = me.cubicRoots(bx);
- ry = me.cubicRoots(by);
- for (i = 0; i < rx.length; i++) {
- t = rx[i];
- for (j = 0; j < ry.length; j++) {
- // TODO: for more accurate results tolerance should be dynamic
- // TODO: based on the length and shape of the segment.
- if (t >= 0 && t <= 1 && abs(t - ry[j]) < 0.05) {
- return true;
- }
- }
- }
- return false;
- }
- };
- });
- Ext.define('Ext.draw.overrides.hittest.All', {
- requires: [
- 'Ext.draw.PathUtil',
- 'Ext.draw.overrides.hittest.sprite.Instancing',
- 'Ext.draw.overrides.hittest.Surface'
- ]
- });
- /**
- * This class uses `Ext.draw.sprite.Sprite` to render the chart legend.
- *
- * The DOM legend is essentially a data view docked inside a draw container, which a chart is.
- * The sprite legend, on the other hand, is not a foreign entity in a draw container,
- * and is rendered in a draw surface with sprites, just like series and axes.
- *
- * This means that:
- *
- * * it is styleable with chart themes
- * * it shows up in chart preview and chart download
- * * it renders markers exactly as they are in the series
- * * it can't be styled with CSS
- * * it doesn't scroll, instead the items are grouped into columns,
- * and the legend grows in size as the number of items increases
- *
- */
- Ext.define('Ext.chart.legend.SpriteLegend', {
- alias: 'legend.sprite',
- type: 'sprite',
- isLegend: true,
- isSpriteLegend: true,
- mixins: [
- 'Ext.mixin.Observable'
- ],
- requires: [
- 'Ext.chart.legend.sprite.Item',
- 'Ext.chart.legend.sprite.Border',
- 'Ext.draw.overrides.hittest.All',
- 'Ext.draw.Animator'
- ],
- config: {
- /**
- * @cfg {'top'/'left'/'right'/'bottom'} docked
- * The position of the legend in the chart.
- */
- docked: 'bottom',
- /**
- * @cfg {Ext.chart.legend.store.Store} store
- * The {@link Ext.chart.legend.store.Store} to bind this legend to.
- * @private
- */
- store: null,
- /**
- * @cfg {Ext.chart.AbstractChart} chart
- * The chart that the store belongs to.
- */
- chart: null,
- /**
- * @cfg {Ext.draw.Surface} surface
- * The chart surface used to render legend sprites.
- * @protected
- */
- surface: null,
- /**
- * @cfg {Object} size
- * The size of the area occupied by the legend's sprites.
- * This is set by the legend itself and then used during chart layout
- * to make sure the 'legend' surface is big enough to accommodate
- * legend sprites.
- * @cfg {Number} size.width
- * @cfg {Number} size.height
- * @readonly
- */
- size: {
- width: 0,
- height: 0
- },
- /**
- * @cfg {Boolean} toggleable
- * `true` to allow series items to have their visibility
- * toggled by interaction with the legend items.
- */
- toggleable: true,
- /**
- * @cfg {Number} padding
- * The padding amount between legend items and legend border.
- */
- padding: 10,
- label: {
- preciseMeasurement: true
- },
- /**
- * The sprite to use as a legend item marker. By default a corresponding series
- * marker is used. If the series has no marker, the `circle` sprite
- * is used as a legend item marker, where its `fillStyle`, `strokeStyle` and
- * `lineWidth` match that of the series. The size of a legend item marker is
- * controlled by the `size` property, which to defaults to `10` (pixels).
- */
- marker: {},
- /**
- * @cfg {Object} border
- * The border that goes around legend item sprites.
- * The type of the sprite is determined by this config,
- * while the styling comes from a theme {@link Ext.chart.theme.Base #legend}.
- * If both this config and the theme provide values for the
- * same configs, the values from this config are used.
- * The sprite class used a legend border should have the `isLegendBorder`
- * property set to true on the prototype. The legend border sprite
- * should also have the `x`, `y`, `width` and `height` attributes
- * that determine it's position and dimensions.
- */
- border: {
- $value: {
- type: 'legendborder'
- },
- // The config should be processed at the time of the 'getSprites' call,
- // when we already have the legend surface, otherwise the border sprite
- // will not be added to the surface.
- lazy: true
- },
- /**
- * @cfg {Object} background
- * Sets the legend background.
- * This can be a gradient object, image, or color. This config works similarly
- * to the {@link Ext.chart.AbstractChart#background} config.
- */
- background: null,
- /**
- * @cfg {Boolean} hidden Toggles the visibility of the legend.
- */
- hidden: false
- },
- sprites: null,
- spriteZIndexes: {
- background: 0,
- border: 1,
- // Item sprites should have a higher zIndex than border,
- // or they won't react to clicks.
- item: 2
- },
- dockedValues: {
- left: true,
- right: true,
- top: true,
- bottom: true
- },
- constructor: function(config) {
- var me = this;
- me.oldSize = {
- width: 0,
- height: 0
- };
- me.getId();
- me.mixins.observable.constructor.call(me, config);
- },
- applyStore: function(store) {
- return store && Ext.StoreManager.lookup(store);
- },
- updateStore: function(store, oldStore) {
- var me = this;
- if (oldStore) {
- oldStore.un('datachanged', me.onDataChanged, me);
- oldStore.un('update', me.onDataUpdate, me);
- }
- if (store) {
- store.on('datachanged', me.onDataChanged, me);
- store.on('update', me.onDataUpdate, me);
- me.onDataChanged(store);
- }
- me.performLayout();
- },
- //<debug>
- applyDocked: function(docked) {
- if (!(docked in this.dockedValues)) {
- Ext.raise("Invalid 'docked' config value.");
- }
- return docked;
- },
- //</debug>
- updateDocked: function(docked) {
- this.isTop = docked === 'top';
- if (!this.isConfiguring) {
- this.layoutChart();
- }
- },
- updateHidden: function(hidden) {
- var surface;
- this.getChart();
- // 'chart' updater will set the surface
- surface = this.getSurface();
- if (surface) {
- surface.setHidden(hidden);
- }
- if (!this.isConfiguring) {
- this.layoutChart();
- }
- },
- /**
- * @private
- */
- layoutChart: function() {
- var chart;
- if (!this.isConfiguring) {
- chart = this.getChart();
- if (chart) {
- chart.scheduleLayout();
- }
- }
- },
- /**
- * @private
- * Calculates and returns the legend surface rect and adjusts the passed `chartRect`
- * accordingly. The first time this is called, the `SpriteLegend` will have zero size
- * (no width or height).
- * @param {Number[]} chartRect [left, top, width, height] components as an array.
- * @return {Number[]} [left, top, width, height] components as an array, or null.
- */
- computeRect: function(chartRect) {
- var rect, docked, size, height, width;
- if (this.getHidden()) {
- return null;
- }
- rect = [
- 0,
- 0,
- 0,
- 0
- ];
- docked = this.getDocked();
- size = this.getSize();
- height = size.height;
- width = size.width;
- switch (docked) {
- case 'top':
- rect[1] = chartRect[1];
- rect[2] = chartRect[2];
- rect[3] = height;
- chartRect[1] += height;
- chartRect[3] -= height;
- break;
- case 'bottom':
- chartRect[3] -= height;
- rect[1] = chartRect[3];
- rect[2] = chartRect[2];
- rect[3] = height;
- break;
- case 'left':
- chartRect[0] += width;
- chartRect[2] -= width;
- rect[2] = width;
- rect[3] = chartRect[3];
- break;
- case 'right':
- chartRect[2] -= width;
- rect[0] = chartRect[2];
- rect[2] = width;
- rect[3] = chartRect[3];
- break;
- }
- return rect;
- },
- applyBorder: function(config) {
- var border;
- if (config) {
- if (config.isSprite) {
- border = config;
- } else {
- border = Ext.create('sprite.' + config.type, config);
- }
- }
- if (border) {
- border.isLegendBorder = true;
- border.setAttributes({
- zIndex: this.spriteZIndexes.border
- });
- }
- return border;
- },
- updateBorder: function(border, oldBorder) {
- var surface = this.getSurface();
- this.borderSprite = null;
- if (surface) {
- if (oldBorder) {
- surface.remove(oldBorder);
- }
- if (border) {
- this.borderSprite = surface.add(border);
- }
- }
- },
- scheduleLayout: function() {
- if (!this.scheduledLayoutId) {
- this.scheduledLayoutId = Ext.draw.Animator.schedule('performLayout', this);
- }
- },
- cancelLayout: function() {
- Ext.draw.Animator.cancel(this.scheduledLayoutId);
- this.scheduledLayoutId = null;
- },
- performLayout: function() {
- var me = this,
- size = me.getSize(),
- gap = me.getPadding(),
- sprites = me.getSprites(),
- surface = me.getSurface(),
- background = me.getBackground(),
- surfaceRect = surface.getRect(),
- store = me.getStore(),
- ln = (sprites && sprites.length) || 0,
- i, sprite;
- if (!surface || !surfaceRect || !store) {
- return false;
- }
- me.cancelLayout();
- // eslint-disable-next-line vars-on-top, one-var
- var docked = me.getDocked(),
- surfaceWidth = surfaceRect[2],
- surfaceHeight = surfaceRect[3],
- border = me.borderSprite,
- bboxes = [],
- startX, // Coordinates of the top-left corner.
- startY, // of the first 'legenditem' sprite.
- columnSize, // Number of items in a column.
- columnCount, // Number of columns.
- columnWidth, itemsWidth, itemsHeight, paddedItemsWidth, // The horizontal span of all 'legenditem' sprites.
- paddedItemsHeight, // The vertical span of all 'legenditem' sprites.
- paddedBorderWidth, paddedBorderHeight, // eslint-disable-line no-unused-vars
- itemHeight, bbox, x, y;
- for (i = 0; i < ln; i++) {
- sprite = sprites[i];
- bbox = sprite.getBBox();
- bboxes.push(bbox);
- }
- if (bbox) {
- itemHeight = bbox.height;
- }
- switch (docked) {
- /*
- Horizontal legend.
- The outer box is the legend surface.
- The inner box is the legend border.
- There's a fixed amount of padding between all the items,
- denoted by ##. This amount is controlled by the 'padding' config
- of the legend.
- |-------------------------------------------------------------|
- | ## |
- | |---------------------------------------------------| |
- | | ## ## ## | |
- | | -------- ----------- -------- | |
- | ## | ## | Item 0 | ## | Item 2 | ## | Item 4 | ## | ## |
- | | -------- ----------- -------- | |
- | | ## ## ## | |
- | | ---------- --------- | |
- | | ## | Item 1 | ## | Item 3 | | |
- | | ---------- --------- | |
- | | ## ## | |
- | |---------------------------------------------------| |
- | ## |
- |-------------------------------------------------------------|
- */
- case 'bottom':
- case 'top':
- // surface must have a width before we can proceed to layout top/bottom
- // docked legend. width may be 0 if we are rendered into an inactive tab.
- // see https://sencha.jira.com/browse/EXTJS-22454
- if (!surfaceWidth) {
- return false;
- };
- columnSize = 0;
- // Split legend items into columns until the width is suitable.
- do {
- itemsWidth = 0;
- columnWidth = 0;
- columnCount = 0;
- columnSize++;
- for (i = 0; i < ln; i++) {
- bbox = bboxes[i];
- if (bbox.width > columnWidth) {
- columnWidth = bbox.width;
- columnWidth = Math.min(columnWidth, surfaceWidth - (gap * 4));
- }
- if ((i + 1) % columnSize === 0) {
- itemsWidth += columnWidth;
- columnWidth = 0;
- columnCount++;
- }
- }
- if (i % columnSize !== 0) {
- itemsWidth += columnWidth;
- columnCount++;
- }
- paddedItemsWidth = itemsWidth + (columnCount - 1) * gap;
- paddedBorderWidth = paddedItemsWidth + gap * 4;
- } while (// if the column width is greater than available surface width,
- // set it to maximum available width
- paddedBorderWidth > surfaceWidth);
- paddedItemsHeight = itemHeight * columnSize + (columnSize - 1) * gap;
- break;
- /*
- Vertical legend.
- |-----------------------------------------------|
- | ## |
- | |-------------------------------------| |
- | | ## ## | |
- | | -------- ----------- | |
- | | ## | Item 0 | ## | Item 1 | ## | |
- | | -------- ----------- | |
- | | ## ## | |
- | | ---------- --------- | |
- | ## | ## | Item 2 | ## | Item 3 | | ## |
- | | ---------- --------- | |
- | | ## | |
- | | -------- | |
- | | ## | Item 4 | | |
- | | -------- | |
- | | ## | |
- | |-------------------------------------| |
- | ## |
- |-----------------------------------------------|
- */
- case 'right':
- case 'left':
- // surface must have a height before we can proceed to layout right/left
- // docked legend. height may be 0 if we are rendered into an inactive tab.
- // see https://sencha.jira.com/browse/EXTJS-22454
- if (!surfaceHeight) {
- return false;
- };
- columnSize = ln * 2;
- // Split legend items into columns until the height is suitable.
- do {
- columnSize = (columnSize >> 1) + (columnSize % 2);
- itemsWidth = 0;
- itemsHeight = 0;
- columnWidth = 0;
- columnCount = 0;
- for (i = 0; i < ln; i++) {
- bbox = bboxes[i];
- if (!columnCount) {
- itemsHeight += bbox.height;
- }
- if (bbox.width > columnWidth) {
- columnWidth = bbox.width;
- }
- if ((i + 1) % columnSize === 0) {
- itemsWidth += columnWidth;
- columnWidth = 0;
- columnCount++;
- }
- }
- if (i % columnSize !== 0) {
- itemsWidth += columnWidth;
- columnCount++;
- }
- paddedItemsWidth = itemsWidth + (columnCount - 1) * gap;
- paddedItemsHeight = itemsHeight + (columnSize - 1) * gap;
- paddedBorderWidth = paddedItemsWidth + gap * 4;
- paddedBorderHeight = paddedItemsHeight + gap * 4;
- } while (// Integer division by 2, plus remainder.
- // itemsHeight is determined by the height of the first column.
- paddedItemsHeight > surfaceHeight);
- break;
- }
- startX = (surfaceWidth - paddedItemsWidth) / 2;
- startY = (surfaceHeight - paddedItemsHeight) / 2;
- x = 0;
- y = 0;
- columnWidth = 0;
- for (i = 0; i < ln; i++) {
- sprite = sprites[i];
- bbox = bboxes[i];
- sprite.setAttributes({
- translationX: startX + x,
- translationY: startY + y
- });
- if (bbox.width > columnWidth) {
- columnWidth = bbox.width;
- }
- if ((i + 1) % columnSize === 0) {
- x += columnWidth + gap;
- y = 0;
- columnWidth = 0;
- } else {
- y += bbox.height + gap;
- }
- }
- if (border) {
- border.setAttributes({
- hidden: !ln,
- x: startX - gap,
- y: startY - gap,
- width: paddedItemsWidth + gap * 2,
- height: paddedItemsHeight + gap * 2
- });
- }
- size.width = border.attr.width + gap * 2;
- size.height = border.attr.height + gap * 2;
- if (size.width !== me.oldSize.width || size.height !== me.oldSize.height) {
- // Do not simply assign size to oldSize, as we want them to be
- // separate objects.
- Ext.apply(me.oldSize, size);
- // Legend size has changed, so we return 'false' to cancel the current
- // chart layout (this method is called by chart's 'performLayout' method)
- // and manually start a new chart layout.
- me.getChart().scheduleLayout();
- return false;
- }
- if (background) {
- me.resizeBackground(surface, background);
- }
- surface.renderFrame();
- return true;
- },
- // Doesn't include the border sprite which also belongs to the 'legend'
- // surface. To get it, use the 'getBorder' method.
- getSprites: function() {
- this.updateSprites();
- return this.sprites;
- },
- /**
- * @private
- * Creates a 'legenditem' sprite in the given surface
- * using the legend store record data provided.
- * @param {Ext.draw.Surface} surface
- * @param {Ext.chart.legend.store.Item} record
- * @return {Ext.chart.legend.sprite.Item}
- */
- createSprite: function(surface, record) {
- var me = this,
- data = record.data,
- chart = me.getChart(),
- series = chart.get(data.series),
- seriesMarker = series.getMarker(),
- sprite = null,
- markerConfig, labelConfig, legendItemConfig;
- if (surface) {
- markerConfig = series.getMarkerStyleByIndex(data.index);
- markerConfig.fillStyle = data.mark;
- markerConfig.hidden = false;
- if (seriesMarker && seriesMarker.type) {
- markerConfig.type = seriesMarker.type;
- }
- Ext.apply(markerConfig, me.getMarker());
- markerConfig.surface = surface;
- labelConfig = me.getLabel();
- legendItemConfig = {
- type: 'legenditem',
- zIndex: me.spriteZIndexes.item,
- text: data.name,
- enabled: !data.disabled,
- marker: markerConfig,
- label: labelConfig,
- series: data.series,
- record: record
- };
- sprite = surface.add(legendItemConfig);
- }
- return sprite;
- },
- /**
- * @private
- * Creates legend item sprites and associates them with legend store records.
- * Updates attributes of the sprites when legend store data changes.
- */
- updateSprites: function() {
- var me = this,
- chart = me.getChart(),
- store = me.getStore(),
- surface = me.getSurface(),
- item, items, itemSprite, i, ln, sprites, unusedSprites, border;
- if (!(chart && store && surface)) {
- return;
- }
- me.sprites = sprites = me.sprites || [];
- items = store.getData().items;
- ln = items.length;
- for (i = 0; i < ln; i++) {
- item = items[i];
- itemSprite = sprites[i];
- if (itemSprite) {
- me.updateSprite(itemSprite, item);
- } else {
- itemSprite = me.createSprite(surface, item);
- surface.add(itemSprite);
- sprites.push(itemSprite);
- }
- }
- unusedSprites = Ext.Array.splice(sprites, i, sprites.length);
- for (i = 0 , ln = unusedSprites.length; i < ln; i++) {
- itemSprite = unusedSprites[i];
- itemSprite.destroy();
- }
- border = me.getBorder();
- if (border) {
- me.borderSprite = border;
- }
- me.updateTheme(chart.getTheme());
- },
- /**
- * @private
- * Updates the given legend item sprite based on store record data.
- * @param {Ext.chart.legend.sprite.Item} sprite
- * @param {Ext.chart.legend.store.Item} record
- */
- updateSprite: function(sprite, record) {
- var data = record.data,
- chart = this.getChart(),
- series = chart.get(data.series),
- marker, label, markerConfig;
- if (sprite) {
- label = sprite.getLabel();
- label.setAttributes({
- text: data.name
- });
- sprite.setAttributes({
- enabled: !data.disabled
- });
- sprite.setConfig({
- series: data.series,
- record: record
- });
- markerConfig = series.getMarkerStyleByIndex(data.index);
- markerConfig.fillStyle = data.mark;
- markerConfig.hidden = false;
- Ext.apply(markerConfig, this.getMarker());
- marker = sprite.getMarker();
- marker.setAttributes({
- fillStyle: markerConfig.fillStyle,
- strokeStyle: markerConfig.strokeStyle
- });
- sprite.layoutUpdater(sprite.attr);
- }
- },
- updateChart: function(newChart, oldChart) {
- var me = this;
- if (oldChart) {
- me.setSurface(null);
- }
- if (newChart) {
- me.setSurface(newChart.getSurface('legend'));
- }
- },
- updateSurface: function(surface, oldSurface) {
- if (oldSurface) {
- oldSurface.el.un('click', 'onClick', this);
- // The surface should not be destroyed here, just cleared.
- // E.g. we may remove the sprite legend only to add another one.
- oldSurface.removeAll(true);
- }
- if (surface) {
- surface.isLegendSurface = true;
- surface.el.on('click', 'onClick', this);
- }
- },
- onClick: function(event) {
- var chart = this.getChart(),
- surface = this.getSurface(),
- result, point;
- if (chart && chart.hasFirstLayout && surface) {
- point = surface.getEventXY(event);
- result = surface.hitTest(point);
- if (result && result.sprite) {
- this.toggleItem(result.sprite);
- }
- }
- },
- applyBackground: function(newBackground, oldBackground) {
- var me = this,
- // It's important to get the `chart` first here,
- // because the `surface` is set by the `chart` updater.
- chart = me.getChart(),
- surface = me.getSurface(),
- background;
- background = chart.refreshBackground(surface, newBackground, oldBackground);
- if (background) {
- background.setAttributes({
- zIndex: me.spriteZIndexes.background
- });
- }
- return background;
- },
- resizeBackground: function(surface, background) {
- var width = background.attr.width,
- height = background.attr.height,
- surfaceRect = surface.getRect();
- if (surfaceRect && (width !== surfaceRect[2] || height !== surfaceRect[3])) {
- background.setAttributes({
- width: surfaceRect[2],
- height: surfaceRect[3]
- });
- }
- },
- themeableConfigs: {
- background: true
- },
- updateTheme: function(theme) {
- var me = this,
- surface = me.getSurface(),
- sprites = surface.getItems(),
- legendTheme = theme.getLegend(),
- labelConfig = me.getLabel(),
- configs = me.self.getConfigurator().configs,
- themeableConfigs = me.themeableConfigs,
- themeOnlyIfConfigured = me.themeOnlyIfConfigured,
- initialConfig = me.getInitialConfig(),
- defaultConfig = me.defaultConfig,
- value, cfg, isObjValue, isUnusedConfig, initialValue, sprite, style, labelSprite, key, attr, i, ln;
- for (i = 0 , ln = sprites.length; i < ln; i++) {
- sprite = sprites[i];
- if (sprite.isLegendItem) {
- style = legendTheme.label;
- if (style) {
- attr = null;
- for (key in style) {
- if (!(key in labelConfig)) {
- attr = attr || {};
- attr[key] = style[key];
- }
- }
- if (attr) {
- labelSprite = sprite.getLabel();
- labelSprite.setAttributes(attr);
- }
- }
-
- continue;
- } else if (sprite.isLegendBorder) {
- style = legendTheme.border;
- } else {
-
- continue;
- }
- if (style) {
- attr = {};
- for (key in style) {
- if (!(key in sprite.config)) {
- attr[key] = style[key];
- }
- }
- sprite.setAttributes(attr);
- }
- }
- value = legendTheme.background;
- cfg = configs.background;
- if (value !== null && value !== undefined && cfg) {}
- // TODO
- for (key in legendTheme) {
- if (!(key in themeableConfigs)) {
-
- continue;
- }
- value = legendTheme[key];
- cfg = configs[key];
- if (value !== null && value !== undefined && cfg) {
- initialValue = initialConfig[key];
- isObjValue = Ext.isObject(value);
- isUnusedConfig = initialValue === defaultConfig[key];
- if (isObjValue) {
- if (isUnusedConfig && themeOnlyIfConfigured[key]) {
-
- continue;
- }
- value = Ext.merge({}, value, initialValue);
- }
- if (isUnusedConfig || isObjValue) {
- me[cfg.names.set](value);
- }
- }
- }
- },
- onDataChanged: function(store) {
- this.updateSprites();
- this.scheduleLayout();
- },
- onDataUpdate: function(store, record) {
- var me = this,
- sprites = me.sprites,
- ln = sprites.length,
- i = 0,
- sprite, spriteRecord, match;
- for (; i < ln; i++) {
- sprite = sprites[i];
- spriteRecord = sprite.getRecord();
- if (spriteRecord === record) {
- match = sprite;
- break;
- }
- }
- if (match) {
- me.updateSprite(match, record);
- me.scheduleLayout();
- }
- },
- toggleItem: function(sprite) {
- var disabledCount = 0,
- canToggle = true,
- store, i, count, record, disabled;
- if (!this.getToggleable() || !sprite.isLegendItem) {
- return;
- }
- store = this.getStore();
- if (store) {
- count = store.getCount();
- for (i = 0; i < count; i++) {
- record = store.getAt(i);
- if (record.get('disabled')) {
- disabledCount++;
- }
- }
- canToggle = count - disabledCount > 1;
- record = sprite.getRecord();
- if (record) {
- disabled = record.get('disabled');
- if (disabled || canToggle) {
- // This will trigger AbstractChart.onLegendStoreUpdate.
- record.set('disabled', !disabled);
- sprite.setAttributes({
- enabled: disabled
- });
- }
- }
- }
- },
- destroy: function() {
- var me = this;
- me.destroying = true;
- me.cancelLayout();
- me.setChart(null);
- me.callParent();
- }
- });
- /**
- * Chart captions can be used to place titles, subtitles, credits and other captions
- * inside a chart. Please see the chart's {@link Ext.chart.AbstractChart#captions}
- * config documentation for the general description of the way captions work, and
- * refer to the documentation of this class' configs for details.
- */
- Ext.define('Ext.chart.Caption', {
- mixins: [
- 'Ext.mixin.Observable',
- 'Ext.mixin.Bindable'
- ],
- isCaption: true,
- config: {
- /**
- * The weight controls the order in which the captions are created.
- * Captions with lower weights are created first.
- * This affects chart's layout. For example, if two captions are docked
- * to the 'top', the one with the lower weight will end up on top
- * of the other.
- */
- weight: 0,
- /**
- * @cfg {String} text
- * The text displayed by the caption.
- * Multi-line captions are allowed, e.g.:
- *
- * captions: {
- * title: {
- * text: 'India\'s tiger population\n'
- * + 'from 1970 to 2015'
- * }
- * }
- *
- */
- text: '',
- /**
- * @cfg {'left'/'center'/'right'} [align='center']
- * Determines the horizontal alignment of the caption's text.
- */
- align: 'center',
- /**
- * @cfg {'series'/'chart'} [alignTo='series']
- * Whether to align the caption to the 'series' (default) or the 'chart'.
- */
- alignTo: 'series',
- /**
- * @cfg {Number} padding
- * The uniform padding applied to both top and bottom of the caption's text.
- */
- padding: 0,
- /**
- * @cfg {Boolean} [hidden=false]
- * Controls the visibility of the caption.
- */
- hidden: false,
- /**
- * @cfg {'top'/'bottom'} [docked='top']
- * The position of the caption in a chart.
- */
- docked: 'top',
- /**
- * @cfg {Object} style
- * Style attributes for the caption's text.
- * All attributes that are valid {@link Ext.draw.sprite.Text text sprite} attributes
- * are valid here. However, only font attributes (such as `fontSize`, `fontFamily`, ...),
- * color attributes (such as `fillStyle`) and `textAlign` attribute are guaranteed to
- * produce correct behavior. For example, transform attributes are not officially supported.
- */
- style: {
- fontSize: '14px',
- fontWeight: 'bold',
- fontFamily: 'Verdana, Aria, sans-serif'
- },
- /**
- * @private
- * @cfg {Ext.chart.AbstractChart} chart
- * The chart the label belongs to.
- */
- chart: null,
- /**
- * @private
- * The text sprite used to render caption's text.
- */
- sprite: {
- type: 'text',
- preciseMeasurement: true,
- zIndex: 10
- },
- //<debug>
- /**
- * @private
- * @cfg {Boolean} debug
- * Whether to show the bounding boxes or not.
- */
- debug: false,
- //</debug>
- /**
- * @private
- * The logical rect of the caption in the `surfaceName` surface.
- */
- rect: null
- },
- surfaceName: 'caption',
- constructor: function(config) {
- var me = this,
- id;
- if ('id' in config) {
- id = config.id;
- } else if ('id' in me.config) {
- id = me.config.id;
- } else {
- id = me.getId();
- }
- me.setId(id);
- me.mixins.observable.constructor.call(me, config);
- me.initBindable();
- },
- updateChart: function() {
- if (!this.isConfiguring) {
- // Re-create caption's sprite in another chart.
- this.setSprite({
- type: 'text'
- });
- }
- },
- applySprite: function(sprite) {
- var me = this,
- chart = me.getChart(),
- surface = me.surface = chart.getSurface(me.surfaceName);
- //<debug>
- me.rectSprite = surface.add({
- type: 'rect',
- fillStyle: 'yellow',
- strokeStyle: 'red'
- });
- //</debug>
- return sprite && surface.add(sprite);
- },
- updateSprite: function(sprite, oldSprite) {
- if (oldSprite) {
- oldSprite.destroy();
- }
- },
- updateText: function(text) {
- this.getSprite().setAttributes({
- text: text
- });
- },
- updateStyle: function(style) {
- this.getSprite().setAttributes(style);
- },
- //<debug>
- updateDebug: function(debug) {
- var me = this,
- sprite = me.getSprite();
- if (debug && !me.rectSprite) {
- me.rectSprite = me.surface.add({
- type: 'rect',
- fillStyle: 'yellow',
- strokeStyle: 'red'
- });
- }
- if (sprite) {
- sprite.setAttributes({
- debug: debug ? {
- bbox: true
- } : null
- });
- }
- if (me.rectSprite) {
- me.rectSprite.setAttributes({
- hidden: !debug
- });
- }
- if (!me.isConfiguring) {
- me.surface.renderFrame();
- }
- },
- //</debug>
- updateRect: function(rect) {
- if (this.rectSprite) {
- this.rectSprite.setAttributes({
- x: rect[0],
- y: rect[1],
- width: rect[2],
- height: rect[3]
- });
- }
- },
- updateDocked: function() {
- var chart = this.getChart();
- if (chart && !this.isConfiguring) {
- chart.scheduleLayout();
- }
- },
- /**
- * @private
- * Computes and sets the caption's rect.
- * Shrinks the given chart rect to accomodate the caption.
- * The chart rect is [top, left, width, height] in chart's
- * body element coordinates.
- * The shrink rect is {left, top, right, bottom} in `caption`
- * surface coordinates.
- */
- computeRect: function(chartRect, shrinkRect) {
- if (this.getHidden()) {
- return null;
- }
- // eslint-disable-next-line vars-on-top
- var rect = [
- 0,
- 0,
- chartRect[2],
- 0
- ],
- docked = this.getDocked(),
- padding = this.getPadding(),
- textSize = this.getSprite().getBBox(),
- height = textSize.height + padding * 2;
- switch (docked) {
- case 'top':
- rect[1] = shrinkRect.top;
- rect[3] = height;
- chartRect[1] += height;
- chartRect[3] -= height;
- shrinkRect.top += height;
- break;
- case 'bottom':
- chartRect[3] -= height;
- shrinkRect.bottom -= height;
- rect[1] = shrinkRect.bottom;
- rect[3] = height;
- break;
- }
- this.setRect(rect);
- },
- alignRect: function(seriesRect) {
- var surfaceRect = this.surface.getRect(),
- rect = this.getRect();
- rect[0] = seriesRect[0] - surfaceRect[0];
- rect[2] = seriesRect[2];
- // Slice to trigger the applier/updater.
- this.setRect(rect.slice());
- },
- performLayout: function() {
- var me = this,
- rect = me.getRect(),
- x = rect[0],
- y = rect[1],
- width = rect[2],
- height = rect[3],
- sprite = me.getSprite(),
- tx = sprite.attr.translationX,
- ty = sprite.attr.translationY,
- bbox = sprite.getBBox(),
- align = me.getAlign(),
- dx, dy;
- switch (align) {
- case 'left':
- dx = x - bbox.x;
- break;
- case 'right':
- dx = (x + width) - (bbox.x + bbox.width);
- break;
- case 'center':
- dx = x + (width - bbox.width) / 2 - bbox.x;
- break;
- }
- dy = y + (height - bbox.height) / 2 - bbox.y;
- sprite.setAttributes({
- translationX: tx + dx,
- translationY: ty + dy
- });
- },
- destroy: function() {
- var me = this;
- //<debug>
- if (me.rectSprite) {
- me.rectSprite.destroy();
- }
- //</debug>
- me.getSprite().destroy();
- me.callParent();
- }
- });
- /**
- * The data model for legend items.
- */
- Ext.define('Ext.chart.legend.store.Item', {
- extend: 'Ext.data.Model',
- fields: [
- 'id',
- 'name',
- // The series title.
- 'mark',
- // The color of the series.
- 'disabled',
- // The state of the series.
- 'series',
- // A reference to the series instance.
- // A sprite index, e.g. for stacked or pie series.
- // For such series an individual component of the series
- // is hidden or shown when the legend item is toggled.
- 'index'
- ]
- });
- /**
- * The store type used for legend items.
- */
- Ext.define('Ext.chart.legend.store.Store', {
- extend: 'Ext.data.Store',
- requires: [
- 'Ext.chart.legend.store.Item'
- ],
- model: 'Ext.chart.legend.store.Item',
- isLegendStore: true,
- config: {
- autoDestroy: true
- }
- });
- /**
- * The Ext.chart package provides the capability to visualize data.
- * Each chart binds directly to a {@link Ext.data.Store store} enabling automatic
- * updates of the chart. A chart configuration object has some overall styling
- * options as well as an array of axes and series. A chart instance example could
- * look like this:
- *
- * Ext.create('Ext.chart.CartesianChart', {
- * width: 800,
- * height: 600,
- * animation: {
- * easing: 'backOut',
- * duration: 500
- * },
- * store: store1,
- * legend: {
- * position: 'right'
- * },
- * axes: [
- * // ...some axes options...
- * ],
- * series: [
- * // ...some series options...
- * ]
- * });
- *
- * In this example we set the `width` and `height` of a chart; We decide whether
- * our series are animated or not and we select a store to be bound to the chart;
- * We also set the legend to the right part of the chart.
- *
- * You can register certain interactions such as {@link Ext.chart.interactions.PanZoom}
- * on the chart by specifying an array of names or more specific config objects.
- * All the events will be wired automatically.
- *
- * You can also listen to series `itemXXX` events on both chart and series level.
- *
- * For example:
- *
- * Ext.create('Ext.chart.CartesianChart', {
- * plugins: {
- * chartitemevents: {
- * moveEvents: true
- * }
- * },
- * store: {
- * fields: ['pet', 'households', 'total'],
- * data: [
- * {pet: 'Cats', households: 38, total: 93},
- * {pet: 'Dogs', households: 45, total: 79},
- * {pet: 'Fish', households: 13, total: 171}
- * ]
- * },
- * axes: [{
- * type: 'numeric',
- * position: 'left'
- * }, {
- * type: 'category',
- * position: 'bottom'
- * }],
- * series: [{
- * type: 'bar',
- * xField: 'pet',
- * yField: 'households',
- * listeners: {
- * itemmousemove: function (series, item, event) {
- * console.log('itemmousemove', item.category, item.field);
- * }
- * }
- * }, {
- * type: 'line',
- * xField: 'pet',
- * yField: 'total',
- * marker: true
- * }],
- * listeners: { // Listen to itemclick events on all series.
- * itemclick: function (chart, item, event) {
- * console.log('itemclick', item.category, item.field);
- * }
- * }
- * });
- *
- * Important! It's generally a poor design choice to put interactive charts
- * inside scrollable views, in such cases it's not possible to tell
- * which component should respond to the interaction.
- * Since charts are typically interactive their default touch action config
- * looks as follows: {@link Ext.draw.Container#touchAction}.
- * If you do have a chart inside a scrollable view, even if it has no interactions,
- * you have to set its `touchAction` config to the following:
- *
- * touchAction: {
- * panX: true,
- * panY: true
- * }
- *
- * Otherwise, if a touch action started on a chart, a swipe will not scroll
- * the view.
- *
- * For more information about the axes and series configurations please check
- * the documentation of each series (Line, Bar, Pie, etc).
- *
- */
- Ext.define('Ext.chart.AbstractChart', {
- extend: 'Ext.draw.Container',
- requires: [
- 'Ext.chart.theme.Default',
- 'Ext.chart.series.Series',
- 'Ext.chart.interactions.Abstract',
- 'Ext.chart.axis.Axis',
- 'Ext.chart.Util',
- 'Ext.data.StoreManager',
- 'Ext.chart.legend.Legend',
- 'Ext.chart.legend.SpriteLegend',
- 'Ext.chart.Caption',
- 'Ext.chart.legend.store.Store',
- 'Ext.data.Store'
- ],
- isChart: true,
- defaultBindProperty: 'store',
- /**
- * @event beforerefresh
- * Fires before a refresh to the chart data is called. If the `beforerefresh`
- * handler returns `false` the {@link #refresh} action will be canceled.
- * @param {Ext.chart.AbstractChart} this
- */
- /**
- * @event refresh
- * Fires after the chart data has been refreshed.
- * @param {Ext.chart.AbstractChart} this
- */
- /**
- * @event redraw
- * Fires after each {@link #event!redraw} call.
- * @param {Ext.chart.AbstractChart} this
- */
- /**
- * @private
- * @event layout
- * Fires after the final layout is done.
- * (Two layouts may be required to fully render a chart.
- * Typically for the initial render and every time thickness
- * of the chart's axes changes.)
- * @param {Ext.chart.AbstractChart} this
- */
- /**
- * @event itemhighlight
- * Fires when an item is highlighted.
- * @param {Ext.chart.AbstractChart} this
- * @param {Object} newItem The new highlight item.
- * @param {Object} oldItem The old highlight item.
- */
- /**
- * @event itemhighlightchange
- * Fires when an item's highlight changes.
- * @param this
- * @param {Object} newItem The new highlight item.
- * @param {Object} oldItem The old highlight item.
- */
- /**
- * @event itemmousemove
- * Fires when the mouse is moved on a series item.
- * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
- * plugin be added to the chart.
- * @param {Ext.chart.AbstractChart} chart
- * @param {Object} item
- * @param {Event} event
- */
- /**
- * @event itemmouseup
- * Fires when a mouseup event occurs on a series item.
- * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
- * plugin be added to the chart.
- * @param {Ext.chart.AbstractChart} chart
- * @param {Object} item
- * @param {Event} event
- */
- /**
- * @event itemmousedown
- * Fires when a mousedown event occurs on a series item.
- * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
- * plugin be added to the chart.
- * @param {Ext.chart.AbstractChart} chart
- * @param {Object} item
- * @param {Event} event
- */
- /**
- * @event itemmouseover
- * Fires when the mouse enters a series item.
- * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
- * plugin be added to the chart.
- * @param {Ext.chart.AbstractChart} chart
- * @param {Object} item
- * @param {Event} event
- */
- /**
- * @event itemmouseout
- * Fires when the mouse exits a series item.
- * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
- * plugin be added to the chart.
- * @param {Ext.chart.AbstractChart} chart
- * @param {Object} item
- * @param {Event} event
- */
- /**
- * @event itemclick
- * Fires when a click event occurs on a series item.
- * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
- * plugin be added to the chart.
- * @param {Ext.chart.AbstractChart} chart
- * @param {Object} item
- * @param {Event} event
- */
- /**
- * @event itemdblclick
- * Fires when a double click event occurs on a series item.
- * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
- * plugin be added to the chart.
- * @param {Ext.chart.AbstractChart} chart
- * @param {Object} item
- * @param {Event} event
- */
- /**
- * @event itemtap
- * Fires when a tap event occurs on a series item.
- * *Note*: This event requires the {@link Ext.chart.plugin.ItemEvents chartitemevents}
- * plugin be added to the chart.
- * @param {Ext.chart.AbstractChart} chart
- * @param {Object} item
- * @param {Event} event
- */
- /**
- * @event storechange
- * Fires when the store of the chart changes.
- * @param {Ext.chart.AbstractChart} chart
- * @param {Ext.data.Store} newStore
- * @param {Ext.data.Store} oldStore
- */
- config: {
- /**
- * @cfg {Ext.data.Store/String/Object} store
- * The data source to which the chart is bound.
- * Acceptable values for this property are:
- *
- * - **any {@link Ext.data.Store Store} class / subclass**
- * - **an {@link Ext.data.Store#storeId ID of a store}**
- * - **a {@link Ext.data.Store Store} config object**. When passing a config you can
- * specify the store type by alias. Passing a config object with a store type will
- * dynamically create a new store of that type when the chart is instantiated.
- *
- * For example:
- *
- * Ext.define('MyApp.store.Customer', {
- * extend: 'Ext.data.Store',
- * alias: 'store.customerstore',
- *
- * fields: ['name', 'value']
- * });
- *
- *
- * Ext.create({
- * xtype: 'cartesian',
- * renderTo: document.body,
- * height: 400,
- * width: 400,
- * store: {
- * type: 'customerstore',
- * data: [{
- * name: 'metric one',
- * value: 10
- * }]
- * },
- * axes: [{
- * type: 'numeric',
- * position: 'left',
- * title: {
- * text: 'Sample Values',
- * fontSize: 15
- * },
- * fields: 'value'
- * }, {
- * type: 'category',
- * position: 'bottom',
- * title: {
- * text: 'Sample Values',
- * fontSize: 15
- * },
- * fields: 'name'
- * }],
- * series: {
- * type: 'bar',
- * xField: 'name',
- * yField: 'value'
- * }
- * });
- */
- store: 'ext-empty-store',
- /**
- * @cfg {String} [theme="default"]
- * The name of the theme to be used. A theme defines the colors and styles
- * used by the series, axes, markers and other chart components.
- * Please see the documentation for the {@link Ext.chart.theme.Base} class
- * for more information.
- *
- * Possible theme values are:
- * - 'green', 'sky', 'red', 'purple', 'blue', 'yellow'
- * - 'category1' to 'category6'
- * - and the above theme names with the '-gradients' suffix, e.g. 'green-gradients'
- *
- * IMPORTANT: You should require the themes you use; for example, to use:
- *
- * theme: 'blue'
- *
- * the `Ext.chart.theme.Blue` class should be required:
- *
- * requires: 'Ext.chart.theme.Blue'
- *
- * To require all chart themes:
- *
- * requires: 'Ext.chart.theme.*'
- */
- theme: 'default',
- /**
- * Chart captions can be used to place titles, subtitles, credits and other captions
- * inside a chart. For example:
- *
- * captions: {
- * title: {
- * text: 'Consumer Price Index'
- * },
- * subtitle: {
- * text: 'from 2007 to 2017'
- * },
- * credits: {
- * text: 'Source: 'bls.gov'
- * }
- * }
- *
- * One can use any names for properties in the `captions` config, but the `title`,
- * `subtitle` and `credits` ones have a special meaning - they are automatically
- * themeable. The `title` and `subtitle` are automatically docked to the top of
- * a chart and the `credits` to the bottom. The `title` uses the largest and
- * the heaviest font, while the `credits` - the smallest and the lightest.
- *
- * Other captions besides those three can be easily defined as well:
- *
- * captions: {
- * myFancyCaption: {
- * docked: 'bottom',
- * align: 'left',
- * style: {
- * fontSize: 18,
- * fontWeight: 'bold',
- * fontFamily: 'Verdana'
- * }
- * }
- * }
- *
- * If a caption config only specifies text, a shorthand syntax is also possible:
- *
- * captions: {
- * title: 'Consumer Price Index'
- * }
- *
- * @cfg {Object} captions
- * @cfg {Ext.chart.Caption} captions.title
- * @cfg {Ext.chart.Caption} captions.subtitle
- * @cfg {Ext.chart.Caption} captions.credits
- */
- captions: null,
- /**
- * @cfg {Object} style
- * The style for the chart component.
- */
- style: null,
- /**
- * @cfg {Boolean/Object} [animation=true]
- * Defaults to `easeInOut` easing with a 500ms duration.
- * See {@link Ext.draw.modifier.Animation} for possible configuration options.
- */
- animation: !Ext.isIE8,
- /**
- * @cfg {Ext.chart.series.Series/Array} series
- * Array of {@link Ext.chart.series.Series Series} instances or config objects.
- * For example:
- *
- * series: [{
- * type: 'column',
- * axis: 'left',
- * listeners: {
- * 'afterrender': function() {
- * console.log('afterrender');
- * }
- * },
- * xField: 'category',
- * yField: 'data1'
- * }]
- */
- series: [],
- /**
- * @cfg {Ext.chart.axis.Axis/Array/Object} axes
- * Array of {@link Ext.chart.axis.Axis Axis} instances or config objects.
- * For example:
- *
- * axes: [{
- * type: 'numeric',
- * position: 'left',
- * title: 'Number of Hits',
- * minimum: 0
- * }, {
- * type: 'category',
- * position: 'bottom',
- * title: 'Month of the Year'
- * }]
- */
- axes: [],
- /**
- * @cfg {Ext.chart.legend.Legend/Ext.chart.legend.SpriteLegend/Boolean} legend
- * The legend config for the chart. If specified, a legend block will be shown
- * next to the chart.
- * Each legend item displays the {@link Ext.chart.series.Series#title title}
- * of the series, the color of the series and allows to toggle the visibility
- * of the series (at least one series should remain visible).
- *
- * Sencha Charts support two types of legends: sprite based and DOM based.
- *
- * The sprite based legend can be shown in chart {@link Ext.draw.Container#preview preview}
- * and is a part of the downloaded {@link Ext.draw.Container#download chart image}.
- * The sprite based legend is always displayed in full and takes as much space as necessary,
- * the legend items are split into columns to use the available space efficiently.
- * The sprite based legend is styled via a {@link Ext.chart.theme.Base chart theme}.
- *
- * The DOM based legend supports RTL.
- * It occupies a fixed width or height and scrolls when the content overflows.
- * The DOM based legend is styled via CSS rules.
- *
- * By default the sprite legend is used. The type can be explicitly specified:
- *
- * legend: {
- * type: 'dom', // 'sprite' is another possible value
- * docked: 'top'
- * }
- *
- * If the legend config is set to `true`, the sprite legend will be used
- * docked to the bottom.
- */
- legend: null,
- /**
- * @cfg {Array} colors
- * Array of colors/gradients to override the color of items and legends.
- */
- colors: null,
- /**
- * @cfg {Object/Number/String} insetPadding
- * The amount of inset padding in pixels for the chart.
- * Inset padding is the padding from the boundary of the chart to any
- * of its contents.
- */
- insetPadding: {
- top: 10,
- left: 10,
- right: 10,
- bottom: 10
- },
- /**
- * @cfg {Object} background Set the chart background.
- * This can be a gradient object, image, or color.
- *
- * For example, if `background` were to be a color we could set the object as
- *
- * background: '#ccc'
- *
- * You can specify an image by using:
- *
- * background: {
- * type: 'image',
- * src: 'http://path.to.image/'
- * }
- *
- * Also you can specify a gradient by using the gradient object syntax:
- *
- * background: {
- * type: 'linear',
- * degrees: 0,
- * stops: [
- * {
- * offset: 0,
- * color: 'white'
- * },
- * {
- * offset: 1,
- * color: 'blue'
- * }
- * ]
- * }
- */
- background: null,
- /**
- * @cfg {Array} interactions
- * Interactions are optional modules that can be plugged in to a chart
- * to allow the user to interact with the chart and its data in special ways.
- * The `interactions` config takes an Array of Object configurations,
- * each one corresponding to a particular interaction class identified
- * by a `type` property:
- *
- * new Ext.chart.AbstractChart({
- * renderTo: Ext.getBody(),
- * width: 800,
- * height: 600,
- * store: store1,
- * axes: [
- * // ...some axes options...
- * ],
- * series: [
- * // ...some series options...
- * ],
- * interactions: [{
- * type: 'interactiontype'
- * // ...additional configs for the interaction...
- * }]
- * });
- *
- * When adding an interaction which uses only its default configuration
- * (no extra properties other than `type`), you can alternately specify
- * only the type as a String rather than the full Object:
- *
- * interactions: ['reset', 'rotate']
- *
- * The current supported interaction types include:
- *
- * - {@link Ext.chart.interactions.PanZoom panzoom} - allows pan and zoom of axes
- * - {@link Ext.chart.interactions.ItemHighlight itemhighlight} - allows highlighting
- * of series data points
- * - {@link Ext.chart.interactions.ItemInfo iteminfo} - allows displaying details of
- * a data point in a popup panel
- * - {@link Ext.chart.interactions.Rotate rotate} - allows rotation of pie and radar series
- *
- * See the documentation for each of those interaction classes to see how they
- * can be configured.
- *
- * Additional custom interactions can be registered using `'interactions.'` alias prefix.
- */
- interactions: [],
- /**
- * @private
- * The main area of the chart where grid and series are drawn.
- */
- mainRect: null,
- /**
- * @private
- * Override value.
- */
- resizeHandler: null,
- /**
- * @cfg {Object} highlightItem
- * The current highlight item in the chart.
- * The object must be the one that you get from item events.
- *
- * Note that series can also own highlight items.
- * This notion is separate from this one and should not be used at the same time.
- */
- highlightItem: null,
- /* eslint-disable indent */
- surfaceZIndexes: {
- background: 0,
- // Contains the backround 'rect' sprite.
- main: 1,
- // Contains grid lines and CrossZoom overlay 'rect' sprite.
- grid: 2,
- // Reserved.
- series: 3,
- // Contains series sprites.
- axis: 4,
- // No actual `axis` surface is created, but this zIndex is used
- // for all axis surfaces (one surface is created per axis).
- chart: 5,
- // Covers whole chart, minus the legend area.
- // Contains sprites defined in the `sprites` config,
- // title, subtitle and credits.
- caption: 6,
- // Contains title, subtitle and credits sprites.
- overlay: 7,
- // This surface will typically contain chart labels
- // and interaction sprites like crosshair lines.
- // With cartesian charts, equivalent in size to the `series` surface.
- // With polar charts, equivalent in size to the `chart` surface.
- legend: 8
- }
- },
- // `SpriteLegend` surface.
- /* eslint-enable indent */
- /**
- * @private
- */
- legendStore: null,
- /**
- * When this is non-zero, changes to sprite attributes apply instantly.
- * See {@link #getAnimation}.
- * @private
- */
- animationSuspendCount: 0,
- /**
- * @private
- */
- chartLayoutSuspendCount: 0,
- /**
- * @private
- */
- chartLayoutCount: 0,
- /**
- * @private
- */
- scheduledLayoutId: null,
- /**
- * @private
- */
- axisThicknessSuspendCount: 0,
- /**
- * @private
- * Indicates that thickness of one or more axes has changed,
- * at the time of {@link #performLayout} call. I.e. 'performLayout'
- * should be called again when current layout is done.
- */
- isThicknessChanged: false,
- constructor: function(config) {
- var me = this;
- me.itemListeners = {};
- me.surfaceMap = {};
- me.chartComponents = {};
- me.isInitializing = true;
- me.suspendChartLayout();
- me.animationSuspendCount++;
- me.callParent(arguments);
- me.isInitializing = false;
- me.getSurface('main');
- me.getSurface('chart').setFlipRtlText(me.getInherited().rtl);
- me.getSurface('overlay').waitFor(me.getSurface('series'));
- me.animationSuspendCount--;
- me.resumeChartLayout();
- },
- applyAnimation: function(animation, oldAnimation) {
- return Ext.chart.Util.applyAnimation(animation, oldAnimation);
- },
- updateAnimation: function() {
- if (this.isConfiguring) {
- return;
- }
- // eslint-disable-next-line vars-on-top
- var seriesList = this.getSeries(),
- ln = seriesList.length,
- i, series;
- this.isSettingSeriesAnimation = true;
- for (i = 0; i < ln; i++) {
- series = seriesList[i];
- // Don't update the series animation config, if it was set by
- // a user, unless 'suspendAnimation' was called.
- if (!series.isUserAnimation || this.animationSuspendCount) {
- series.setAnimation(series.getAnimation());
- }
- }
- this.isSettingSeriesAnimation = false;
- },
- getAnimation: function() {
- var result;
- if (this.animationSuspendCount) {
- result = {
- duration: 0
- };
- } else {
- result = this.callParent();
- }
- return result;
- },
- suspendAnimation: function() {
- this.animationSuspendCount++;
- if (this.animationSuspendCount === 1) {
- this.updateAnimation();
- }
- },
- resumeAnimation: function() {
- this.animationSuspendCount--;
- if (this.animationSuspendCount === 0) {
- this.updateAnimation();
- }
- },
- applyInsetPadding: function(padding, oldPadding) {
- var result;
- if (!Ext.isObject(padding)) {
- result = Ext.util.Format.parseBox(padding);
- } else if (!oldPadding) {
- result = padding;
- } else {
- result = Ext.apply(oldPadding, padding);
- }
- return result;
- },
- /**
- * Suspends chart's layout.
- */
- suspendChartLayout: function() {
- var me = this;
- me.chartLayoutSuspendCount++;
- if (me.chartLayoutSuspendCount === 1) {
- if (me.scheduledLayoutId) {
- me.layoutInSuspension = true;
- me.cancelChartLayout();
- } else {
- me.layoutInSuspension = false;
- }
- }
- },
- /**
- * Decrements chart's layout suspend count.
- * When the suspend count is decremented to zero,
- * a layout is scheduled.
- */
- resumeChartLayout: function() {
- var me = this;
- me.chartLayoutSuspendCount--;
- if (me.chartLayoutSuspendCount === 0) {
- if (me.layoutInSuspension) {
- me.scheduleLayout();
- }
- }
- },
- /**
- * Cancel a scheduled layout.
- */
- cancelChartLayout: function() {
- if (this.scheduledLayoutId) {
- Ext.draw.Animator.cancel(this.scheduledLayoutId);
- this.scheduledLayoutId = null;
- this.checkLayoutEnd();
- }
- },
- /**
- * Schedule a layout at next frame.
- */
- scheduleLayout: function() {
- var me = this;
- if (me.allowSchedule() && !me.scheduledLayoutId) {
- me.scheduledLayoutId = Ext.draw.Animator.schedule('doScheduleLayout', me);
- }
- },
- allowSchedule: function() {
- return true;
- },
- doScheduleLayout: function() {
- var me = this;
- me.scheduledLayoutId = null;
- if (me.chartLayoutSuspendCount) {
- me.layoutInSuspension = true;
- } else {
- me.performLayout();
- }
- },
- /**
- * Prevent axes from triggering chart layout when their thickness changes.
- * E.g. during an interaction that makes changes to the axes,
- * or when chart layout was triggered by something else,
- * for example a chart resize event.
- */
- suspendThicknessChanged: function() {
- this.axisThicknessSuspendCount++;
- },
- /**
- * Decrements axis thickness suspend count.
- * When axis thickness suspend count is decremented to zero,
- * chart layout is performed.
- */
- resumeThicknessChanged: function() {
- if (this.axisThicknessSuspendCount > 0) {
- this.axisThicknessSuspendCount--;
- if (this.axisThicknessSuspendCount === 0 && this.isThicknessChanged) {
- this.onThicknessChanged();
- }
- }
- },
- onThicknessChanged: function() {
- if (this.axisThicknessSuspendCount === 0) {
- this.isThicknessChanged = false;
- this.performLayout();
- } else {
- this.isThicknessChanged = true;
- }
- },
- applySprites: function(sprites) {
- var surface = this.getSurface('chart');
- sprites = Ext.Array.from(sprites);
- surface.removeAll(true);
- surface.add(sprites);
- return sprites;
- },
- initItems: function() {
- var items = this.items,
- i, ln, item;
- if (items && !items.isMixedCollection) {
- this.items = [];
- items = Ext.Array.from(items);
- for (i = 0 , ln = items.length; i < ln; i++) {
- item = items[i];
- if (item.type) {
- Ext.raise("To add custom sprites to the chart use the 'sprites' config.");
- } else {
- this.items.push(item);
- }
- }
- }
- // @noOptimize.callParent
- this.callParent();
- },
- // noOptimize is needed because in the ext build we have a parent method to call,
- // but in touch we do not so we need to suppress the cmd warning during optimized build
- applyBackground: function(newBackground, oldBackground) {
- var surface = this.getSurface('background');
- return this.refreshBackground(surface, newBackground, oldBackground);
- },
- /**
- * @private
- * The background updater. Used by both the chart and the sprite legend.
- * @param surface The surface to put the background in.
- * @param newBackground
- * @param oldBackground
- * @return {Ext.draw.sprite.Rect/Ext.draw.sprite.Sprite}
- */
- refreshBackground: function(surface, newBackground, oldBackground) {
- var width, height, isUpdateOld;
- if (newBackground) {
- if (oldBackground) {
- width = oldBackground.attr.width;
- height = oldBackground.attr.height;
- isUpdateOld = oldBackground.type === (newBackground.type || 'rect');
- }
- if (newBackground.isSprite) {
- oldBackground = newBackground;
- } else if (newBackground.type === 'image' && Ext.isString(newBackground.src)) {
- if (isUpdateOld) {
- oldBackground.setAttributes({
- src: newBackground.src
- });
- } else {
- surface.remove(oldBackground, true);
- oldBackground = surface.add(newBackground);
- }
- } else {
- if (isUpdateOld) {
- oldBackground.setAttributes({
- fillStyle: newBackground
- });
- } else {
- surface.remove(oldBackground, true);
- oldBackground = surface.add({
- type: 'rect',
- fillStyle: newBackground,
- animation: {
- customDurations: {
- x: 0,
- y: 0,
- width: 0,
- height: 0
- }
- }
- });
- }
- }
- }
- if (width && height) {
- oldBackground.setAttributes({
- width: width,
- height: height
- });
- }
- oldBackground.setAnimation(this.getAnimation());
- return oldBackground;
- },
- defaultResizeHandler: function(size) {
- this.scheduleLayout();
- return false;
- },
- applyMainRect: function(newRect, rect) {
- if (!rect) {
- return newRect;
- }
- this.getSeries();
- this.getAxes();
- if (newRect[0] === rect[0] && newRect[1] === rect[1] && newRect[2] === rect[2] && newRect[3] === rect[3]) {
- return rect;
- } else {
- return newRect;
- }
- },
- register: function(component) {
- var map = this.chartComponents,
- id = component.getId();
- //<debug>
- if (id === undefined) {
- Ext.raise('Chart component id is undefined. ' + 'Please ensure the component has an id.');
- }
- if (id in map) {
- Ext.raise('Registering duplicate chart component id "' + id + '"');
- }
- //</debug>
- map[id] = component;
- },
- unregister: function(component) {
- var map = this.chartComponents,
- id = component.getId();
- delete map[id];
- },
- get: function(id) {
- return this.chartComponents[id];
- },
- /**
- * @method getAxis Returns an axis instance based on the type of data passed.
- * @param {String/Number/Ext.chart.axis.Axis} axis You may request an axis by passing
- * an id, the number of the array key returned by {@link #getAxes}, or an axis instance.
- * @return {Ext.chart.axis.Axis} The axis requested.
- */
- getAxis: function(axis) {
- if (axis instanceof Ext.chart.axis.Axis) {
- return axis;
- } else if (Ext.isNumber(axis)) {
- return this.getAxes()[axis];
- } else if (Ext.isString(axis)) {
- return this.get(axis);
- }
- },
- getSurface: function(id, type) {
- var me = this,
- map = me.surfaceMap,
- surface;
- id = id || 'main';
- type = type || id;
- surface = this.callParent([
- id,
- type
- ]);
- if (!map[type]) {
- map[type] = [];
- }
- if (Ext.Array.indexOf(map[type], surface) < 0) {
- surface.type = type;
- map[type].push(surface);
- surface.on('destroy', me.forgetSurface, me);
- }
- return surface;
- },
- forgetSurface: function(surface) {
- var map = this.surfaceMap,
- group, index;
- if (!map || this.destroying) {
- return;
- }
- group = map[surface.type];
- index = group ? Ext.Array.indexOf(group, surface) : -1;
- if (index >= 0) {
- group.splice(index, 1);
- }
- },
- applyAxes: function(newAxes, oldAxes) {
- var me = this,
- positions = {
- left: 'right',
- right: 'left'
- },
- result = [],
- axis, oldAxis, linkedTo, id, oldMap, series, i, j, ln;
- me.animationSuspendCount++;
- me.getStore();
- if (!oldAxes) {
- oldAxes = [];
- oldAxes.map = {};
- }
- oldMap = oldAxes.map;
- result.map = {};
- newAxes = Ext.Array.from(newAxes, true);
- for (i = 0 , ln = newAxes.length; i < ln; i++) {
- axis = newAxes[i];
- if (!axis) {
-
- continue;
- }
- if (axis instanceof Ext.chart.axis.Axis) {
- oldAxis = oldMap[axis.getId()];
- axis.setChart(me);
- } else {
- axis = Ext.Object.chain(axis);
- linkedTo = axis.linkedTo;
- id = axis.id;
- if (Ext.isNumber(linkedTo)) {
- axis = Ext.merge({}, newAxes[linkedTo], axis);
- } else if (Ext.isString(linkedTo)) {
- Ext.Array.each(newAxes, function(item) {
- if (item.id === axis.linkedTo) {
- axis = Ext.merge({}, item, axis);
- return false;
- }
- });
- }
- axis.id = id;
- axis.chart = me;
- if (me.getInherited().rtl) {
- axis.position = positions[axis.position] || axis.position;
- }
- id = axis.getId && axis.getId() || axis.id;
- axis = Ext.factory(axis, null, oldAxis = oldMap[id], 'axis');
- }
- if (axis) {
- result.push(axis);
- result.map[axis.getId()] = axis;
- }
- }
- me.axesChangeSeries = {};
- for (i in oldMap) {
- if (!result.map[i]) {
- oldAxis = oldMap[i];
- if (oldAxis && !oldAxis.destroyed) {
- // At this point the series still have their `xAxis` and `yAxis` configs
- // set to old axes. We need to update such series with new matching axes
- // by calling their `onAxesChange` method.
- for (j = 0 , ln = oldAxis.boundSeries.length; j < ln; j++) {
- series = oldAxis.boundSeries[j];
- me.axesChangeSeries[series.getId()] = series;
- }
- oldAxis.destroy();
- }
- }
- }
- me.animationSuspendCount--;
- return result;
- },
- updateAxes: function(axes) {
- var me = this,
- seriesMap = me.axesChangeSeries,
- series, id, i, ln, axis;
- for (id in seriesMap) {
- series = seriesMap[id];
- // `true` to force set series' axes, even if they are already set
- // (in this case to old axes that were just destroyed in the `axes` applier).
- series.onAxesChange(me, true);
- }
- // If changes to the `axes` config are made post chart creation, without making any
- // changes to the series afterwards, we need to figure out the new axes' `boundSeries`
- // manually, as the 'serieschange' event won't be fired in this case.
- for (i = 0 , ln = axes.length; i < ln; i++) {
- axis = axes[i];
- axis.onSeriesChange(me);
- }
- if (!me.isConfiguring && !me.destroying) {
- me.scheduleLayout();
- }
- },
- circularCopyArray: function(inArray, startIndex, count) {
- var outArray = [],
- i,
- len = inArray && inArray.length;
- if (len) {
- for (i = 0; i < count; i++) {
- outArray.push(inArray[(startIndex + i) % len]);
- }
- }
- return outArray;
- },
- circularCopyObject: function(inObject, startIndex, count) {
- var me = this,
- name, value,
- outObject = {};
- if (count) {
- for (name in inObject) {
- if (inObject.hasOwnProperty(name)) {
- value = inObject[name];
- if (Ext.isArray(value)) {
- outObject[name] = me.circularCopyArray(value, startIndex, count);
- } else {
- outObject[name] = value;
- }
- }
- }
- }
- return outObject;
- },
- getColors: function() {
- var me = this,
- configColors = me.config.colors,
- theme = me.getTheme();
- if (Ext.isArray(configColors) && configColors.length > 0) {
- configColors = me.applyColors(configColors);
- }
- return configColors || (theme && theme.getColors());
- },
- applyColors: function(newColors) {
- newColors = Ext.Array.map(newColors, function(color) {
- if (Ext.isString(color)) {
- return color;
- } else {
- return color.toString();
- }
- });
- return newColors;
- },
- updateColors: function(newColors) {
- var me = this,
- theme = me.getTheme(),
- colors = newColors || (theme && theme.getColors()),
- colorIndex = 0,
- series = me.getSeries(),
- seriesCount = series && series.length,
- i, seriesItem, seriesColors, seriesColorCount;
- if (colors.length) {
- for (i = 0; i < seriesCount; i++) {
- seriesItem = series[i];
- seriesColorCount = seriesItem.themeColorCount();
- seriesColors = me.circularCopyArray(colors, colorIndex, seriesColorCount);
- colorIndex += seriesColorCount;
- seriesItem.updateChartColors(seriesColors);
- }
- }
- if (!me.isConfiguring) {
- me.refreshLegendStore();
- }
- },
- applyTheme: function(theme) {
- if (theme && theme.isTheme) {
- return theme;
- }
- return Ext.Factory.chartTheme(theme);
- },
- updateGradients: function(gradients) {
- if (!Ext.isEmpty(gradients)) {
- this.updateTheme(this.getTheme());
- }
- },
- updateTheme: function(theme, oldTheme) {
- var me = this,
- axes = me.getAxes(),
- series = me.getSeries(),
- colors = me.getColors(),
- i;
- if (!series) {
- return;
- }
- me.updateChartTheme(theme);
- for (i = 0; i < axes.length; i++) {
- axes[i].updateTheme(theme);
- }
- for (i = 0; i < series.length; i++) {
- series[i].setTheme(theme);
- }
- me.updateSpriteTheme(theme);
- me.updateColors(colors);
- // It may be necessary to perform a layout here.
- // But instead of the 'chart.scheduleLayout' call, we can call
- // 'chart.redraw'. If after the redraw call the thickness
- // of any axis changes, this will automatically trigger
- // chart layout (see Ext.chart.axis.sprite.Axis.doThicknessChanged).
- // Otherwise, no layout is necessary.
- me.redraw();
- me.fireEvent('themechange', me, theme, oldTheme);
- },
- themeOnlyIfConfigured: {
- captions: true
- },
- updateChartTheme: function(theme) {
- var me = this,
- chartTheme = theme.getChart(),
- initialConfig = me.getInitialConfig(),
- defaultConfig = me.defaultConfig,
- configs = me.self.getConfigurator().configs,
- genericChartTheme = chartTheme.defaults,
- specificChartTheme = chartTheme[me.xtype],
- themeOnlyIfConfigured = me.themeOnlyIfConfigured,
- key, value, isObjValue, isUnusedConfig, initialValue, cfg;
- chartTheme = Ext.merge({}, genericChartTheme, specificChartTheme);
- for (key in chartTheme) {
- value = chartTheme[key];
- cfg = configs[key];
- if (value !== null && value !== undefined && cfg) {
- initialValue = initialConfig[key];
- isObjValue = Ext.isObject(value);
- isUnusedConfig = initialValue === defaultConfig[key];
- if (isObjValue) {
- if (isUnusedConfig && themeOnlyIfConfigured[key]) {
-
- continue;
- }
- value = Ext.merge({}, value, initialValue);
- }
- if (isUnusedConfig || isObjValue) {
- me[cfg.names.set](value);
- }
- }
- }
- },
- updateSpriteTheme: function(theme) {
- var me = this,
- chartSurface, sprites, styles, sprite, style, key, attr, isText, i, ln;
- me.getSprites();
- chartSurface = me.getSurface('chart');
- sprites = chartSurface.getItems();
- styles = theme.getSprites();
- for (i = 0 , ln = sprites.length; i < ln; i++) {
- sprite = sprites[i];
- style = styles[sprite.type];
- if (style) {
- attr = {};
- isText = sprite.type === 'text';
- for (key in style) {
- if (!(key in sprite.config)) {
- // Setting individual font attributes will take over the 'font' shorthand
- // attribute, but this behavior is undesireable for theming.
- if (!(isText && key.indexOf('font') === 0 && sprite.config.font)) {
- attr[key] = style[key];
- }
- }
- }
- sprite.setAttributes(attr);
- }
- }
- },
- /**
- * Adds a {@link Ext.chart.series.Series Series} to this chart.
- *
- * The Series (or array) passed will be added to the existing series. If an `id` is specified
- * in a new Series, any existing Series of that `id` will be updated.
- *
- * The chart will be redrawn in response to the change.
- *
- * @param {Object/Object[]/Ext.chart.series.Series/Ext.chart.series.Series[]} newSeries A
- * config object describing the Series to add, or an instantiated Series object. Or an array
- * of these.
- */
- addSeries: function(newSeries) {
- var series = this.getSeries();
- series = series.concat(Ext.Array.from(newSeries));
- this.setSeries(series);
- },
- /**
- * Remove a {@link Ext.chart.series.Series Series} from this chart.
- * The Series (or array) passed will be removed from the existing series.
- *
- * The chart will be redrawn in response to the change.
- *
- * @param {Ext.chart.series.Series/String} series The Series or the `id` of the Series
- * to remove. May be an array.
- */
- removeSeries: function(series) {
- var existingSeries = this.getSeries(),
- newSeries = [],
- removeMap = {},
- i, len, s;
- series = Ext.Array.from(series);
- // Build a map of the Series IDs that are to be removed
- for (i = 0 , len = series.length; i < len; i++) {
- s = series[i];
- // If they passed a Series Object
- if (typeof s !== 'string') {
- s = s.getId();
- }
- removeMap[s] = true;
- }
- // Build a new Series array that excludes those Series scheduled for removal
- for (i = 0 , len = existingSeries.length; i < len; i++) {
- if (!removeMap[existingSeries[i].getId()]) {
- newSeries.push(existingSeries[i]);
- }
- }
- this.setSeries(newSeries);
- },
- applySeries: function(newSeries, oldSeries) {
- var me = this,
- result = [],
- oldMap, oldSeriesItem, i, ln, series;
- me.animationSuspendCount++;
- me.getAxes();
- if (oldSeries) {
- oldMap = oldSeries.map;
- } else {
- oldSeries = [];
- oldMap = oldSeries.map = {};
- }
- result.map = {};
- newSeries = Ext.Array.from(newSeries, true);
- for (i = 0 , ln = newSeries.length; i < ln; i++) {
- series = newSeries[i];
- if (!series) {
-
- continue;
- }
- oldSeriesItem = oldMap[series.getId && series.getId() || series.id];
- // New Series instance passed in
- if (series instanceof Ext.chart.series.Series) {
- // Replacing
- if (oldSeriesItem && oldSeriesItem !== series) {
- oldSeriesItem.destroy();
- }
- series.setChart(me);
- }
- // Series config object passed in
- else if (Ext.isObject(series)) {
- // Config object matched an existing Series item by id;
- // update its configuration
- if (oldSeriesItem) {
- oldSeriesItem.setConfig(series);
- series = oldSeriesItem;
- } else // Create a new Series
- {
- if (Ext.isString(series)) {
- series = {
- type: series
- };
- }
- series.chart = me;
- series = Ext.create(series.xclass || ('series.' + series.type), series);
- }
- }
- result.push(series);
- result.map[series.getId()] = series;
- }
- for (i in oldMap) {
- if (!result.map[oldMap[i].id]) {
- oldMap[i].destroy();
- }
- }
- me.animationSuspendCount--;
- return result;
- },
- updateSeries: function(newSeries, oldSeries) {
- var me = this;
- if (me.destroying) {
- return;
- }
- me.animationSuspendCount++;
- me.fireEvent('serieschange', me, newSeries, oldSeries);
- if (!Ext.isEmpty(newSeries)) {
- me.updateTheme(me.getTheme());
- }
- me.refreshLegendStore();
- if (!me.isConfiguring && !me.destroying) {
- me.scheduleLayout();
- }
- me.animationSuspendCount--;
- },
- defaultLegendType: 'sprite',
- applyLegend: function(legend, oldLegend) {
- var me = this,
- result = null,
- alias;
- if (oldLegend && !(oldLegend.destroyed || oldLegend.destroying)) {
- if (me.legendStoreListeners) {
- me.legendStoreListeners.destroy();
- }
- if (me.legendStore) {
- me.legendStore.destroy();
- }
- oldLegend.destroy();
- }
- if (legend) {
- if (Ext.isBoolean(legend)) {
- result = Ext.create('legend.' + me.defaultLegendType, {
- docked: 'bottom',
- chart: me
- });
- } else {
- legend.docked = legend.docked || 'bottom';
- legend.chart = me;
- alias = 'legend.' + (legend.type || me.defaultLegendType);
- result = Ext.create(alias, legend);
- }
- }
- return result;
- },
- updateLegend: function(legend) {
- var me = this;
- // Probably has been already destroyed with the old legend,
- // but making sure.
- me.destroyLegendStore();
- if (legend) {
- me.getItems();
- legend.setStore(me.refreshLegendStore());
- }
- if (!me.isConfiguring) {
- me.scheduleLayout();
- }
- },
- captionApplier: function(caption, oldCaption) {
- var me = this,
- result;
- if (oldCaption && !(oldCaption.destroyed || oldCaption.destroying)) {
- oldCaption.destroy();
- }
- if (caption) {
- caption.chart = me;
- result = new Ext.chart.Caption(caption);
- }
- return result;
- },
- applyCaptions: function(captions, oldCaptions) {
- var map = {},
- caption, oldCaption, name, any;
- for (name in captions) {
- caption = captions[name];
- if (caption && !caption.length && !(caption.text && caption.text.length)) {
- caption = null;
- } else if (typeof caption === 'string') {
- caption = {
- text: caption
- };
- // Initial config is used for proper theming (see `updateChartTheme`)
- // and config merging, however, mergin won't work as expected, if
- // the initial config value remains a string, so we modify it here.
- this.getInitialConfig().captions[name] = caption;
- }
- oldCaption = oldCaptions && oldCaptions[name];
- caption = this.captionApplier(caption, oldCaption);
- if (caption) {
- any = true;
- map[name] = caption;
- }
- }
- return any && map;
- },
- updateCaptions: function() {
- var me = this;
- if (!me.isConfiguring) {
- me.scheduleLayout();
- }
- },
- /**
- * Return the legend store that contains all the legend information.
- * This information is collected from all the series.
- * @return {Ext.chart.legend.store.Store}
- */
- getLegendStore: function() {
- var me = this,
- store = me.legendStore;
- if (!store) {
- store = me.legendStore = new Ext.chart.legend.store.Store({
- chart: me
- });
- me.legendStoreListeners = store.on({
- scope: me,
- update: 'onLegendStoreUpdate',
- destroyable: true
- });
- }
- return store;
- },
- destroyLegendStore: function() {
- var store = this.legendStore;
- if (store && !(store.destroyed || store.destroying)) {
- store.destroy();
- }
- this.legendStore = null;
- },
- refreshLegendStore: function() {
- var me = this,
- legendStore = me.getLegendStore(),
- series, seriesList, legendData, i, ln;
- if (legendStore) {
- seriesList = me.getSeries();
- legendData = [];
- for (i = 0 , ln = seriesList.length; i < ln; i++) {
- series = seriesList[i];
- if (series.getShowInLegend()) {
- series.provideLegendInfo(legendData);
- }
- }
- legendStore.setData(legendData);
- }
- return legendStore;
- },
- onLegendStoreUpdate: function(store, record) {
- var me = this,
- series;
- if (record) {
- series = this.getSeries().map[record.get('series')];
- if (series) {
- series.setHiddenByIndex(record.get('index'), record.get('disabled'));
- me.redraw();
- }
- }
- },
- applyInteractions: function(interactions, oldInteractions) {
- var me = this,
- result = [],
- oldMap, interaction, i, ln;
- interactions = Ext.Array.from(interactions, true);
- if (!oldInteractions) {
- oldInteractions = [];
- oldInteractions.map = {};
- }
- oldMap = oldInteractions.map;
- result.map = {};
- for (i = 0 , ln = interactions.length; i < ln; i++) {
- interaction = interactions[i];
- if (!interaction) {
-
- continue;
- }
- // eslint-disable-next-line max-len
- interaction = Ext.factory(interaction, null, oldMap[interaction.getId && interaction.getId() || interaction.id], 'interaction');
- if (interaction) {
- interaction.setChart(me);
- result.push(interaction);
- result.map[interaction.getId()] = interaction;
- }
- }
- for (i in oldMap) {
- if (!result.map[i]) {
- oldMap[i].destroy();
- }
- }
- return result;
- },
- /**
- * Get an interaction by type.
- * @param {String} type The type of the interaction.
- * @return {Ext.chart.interactions.Abstract} The interaction. `null`
- * if not found.
- */
- getInteraction: function(type) {
- var interactions = this.getInteractions(),
- len = interactions && interactions.length,
- out = null,
- interaction, i;
- if (len) {
- for (i = 0; i < len; ++i) {
- interaction = interactions[i];
- if (interaction.type === type) {
- out = interaction;
- break;
- }
- }
- }
- return out;
- },
- applyStore: function(store) {
- return store && Ext.StoreManager.lookup(store);
- },
- updateStore: function(newStore, oldStore) {
- var me = this;
- if (oldStore && !oldStore.destroyed) {
- oldStore.un({
- datachanged: 'onDataChanged',
- update: 'onDataChanged',
- scope: me,
- order: 'after'
- });
- if (oldStore.autoDestroy) {
- oldStore.destroy();
- }
- }
- if (newStore) {
- newStore.on({
- datachanged: 'onDataChanged',
- update: 'onDataChanged',
- scope: me,
- order: 'after'
- });
- }
- me.fireEvent('storechange', me, newStore, oldStore);
- me.onDataChanged();
- },
- /**
- * Redraw the chart. If animations are set this will animate the chart too.
- * Note: the actual redraw is performed in a subclass.
- */
- redraw: function() {
- this.fireEvent('redraw', this);
- },
- /**
- * @private
- * Lays out chart components and triggers a {@link #event!redraw}.
- * Note: the actual layout is performed in a subclass.
- * A subclass should not perform a layout, if this parent method
- * returns `false`.
- * @return {Boolean}
- */
- performLayout: function() {
- if (this.destroying || this.destroyed) {
- //<debug>
- Ext.raise('Attempting to lay out a dead chart: ' + this.getId());
- //</debug>
- return false;
- }
- // Cancel subclass layout.
- // eslint-disable-next-line vars-on-top
- var me = this,
- legend = me.getLegend(),
- chartRect = me.getChartRect(true),
- background = me.getBackground(),
- result = true,
- legendRect;
- me.cancelChartLayout();
- //<debug>
- // Unlike the 'layout' event that is called after all chart layouts are done
- // and none are pending, this event fires before the start of each layout.
- me.fireEvent('beforelayout', me);
- //</debug>
- if (background) {
- me.getSurface('background').setRect(chartRect.slice());
- background.setAttributes({
- width: chartRect[2],
- height: chartRect[3]
- });
- }
- // The top docked legend is a special case and should be laid out after captions.
- if (legend && legend.isSpriteLegend && !legend.isTop) {
- legendRect = legend.computeRect(chartRect);
- }
- me.layoutCaptions(chartRect);
- if (legend && legend.isSpriteLegend && legend.isTop) {
- legendRect = legend.computeRect(chartRect);
- }
- if (legendRect) {
- me.getSurface('legend').setRect(legendRect);
- result = legend.performLayout();
- }
- me.getSurface('chart').setRect(chartRect);
- if (result) {
- me.hasFirstLayout = true;
- }
- return result;
- },
- layoutCaptions: function(chartRect) {
- var captions = this.getCaptions(),
- shrinkRect = {
- left: 0,
- top: 0,
- right: chartRect[2],
- bottom: chartRect[3]
- },
- caption, captionName, captionList, i, ln;
- if (captions) {
- captionList = [];
- for (captionName in captions) {
- captionList.push(captions[captionName]);
- }
- captionList.sort(function(a, b) {
- return a.getWeight() - b.getWeight();
- });
- for (i = 0 , ln = captionList.length; i < ln; i++) {
- caption = captionList[i];
- if (!i) {
- this.getSurface(caption.surfaceName).setRect(chartRect.slice());
- }
- caption.computeRect(chartRect, shrinkRect);
- }
- this.captionList = captionList;
- }
- },
- /**
- * @private
- */
- checkLayoutEnd: function() {
- // not running not pending
- if (!this.chartLayoutCount && !this.scheduledLayoutId) {
- this.onLayoutEnd();
- }
- },
- /**
- * @private
- */
- onLayoutEnd: function() {
- var me = this;
- me.fireEvent('layout', me);
- },
- /**
- * @private
- * The area of the chart minus the legend, title, subtitle and credits.
- * Cache chart rect as element.getSize() results in
- * a relatively expensive call to the getComputedStyle().
- */
- getChartRect: function(isRecompute) {
- var me = this,
- chartRect, bodySize;
- if (isRecompute) {
- me.chartRect = null;
- }
- if (me.chartRect) {
- chartRect = me.chartRect;
- } else {
- bodySize = me.bodyElement.getSize();
- chartRect = me.chartRect = [
- 0,
- 0,
- bodySize.width,
- bodySize.height
- ];
- }
- return chartRect;
- },
- /**
- * @private
- * Converts page coordinates into chart's 'series' surface coordinates.
- */
- getEventXY: function(e) {
- return this.getSurface('series').getEventXY(e);
- },
- /**
- * Given an x/y point relative to the chart, find and return the first series item that
- * matches that point.
- * @param {Number} x
- * @param {Number} y
- * @return {Object} An object with `series` and `item` properties, or `false` if no item found.
- */
- getItemForPoint: function(x, y) {
- var me = this,
- seriesList = me.getSeries(),
- rect = me.getMainRect(),
- ln = seriesList.length,
- minDistance = Infinity,
- result = null,
- i, item;
- // The x,y here are already converted to the 'main' surface coordinates.
- // Series surface rect matches the main surface rect.
- if (!(me.hasFirstLayout && rect && x >= 0 && x <= rect[2] && y >= 0 && y <= rect[3])) {
- return null;
- }
- // Iterate in reverse order so that the series that render later (on top)
- // get hit tested first.
- for (i = ln - 1; i >= 0; i--) {
- item = seriesList[i].getItemForPoint(x, y);
- if (item) {
- // Imagine a chart with multiple series, e.g. 'line', 'scatter' and 'bar'.
- // For 'line' and 'scatter' series, the method will look for the nearest
- // marker, but for 'bar' series, it will look for the first bar that
- // contains the given point. For such series, the 'distance' information
- // is absent and meaningless.
- if (!item.distance) {
- result = item;
- break;
- }
- if (item.distance < minDistance) {
- minDistance = item.distance;
- result = item;
- }
- }
- }
- return result;
- },
- /**
- * @private
- * Given an x/y point relative to the chart, find and return all series items that match
- * that point.
- * @param {Number} x
- * @param {Number} y
- * @return {Array} An array of objects with `series` and `item` properties.
- * @deprecated 6.5.2 This method is deprecated
- */
- getItemsForPoint: function(x, y) {
- var me = this,
- seriesList = me.getSeries(),
- ln = seriesList.length,
- // If we haven't drawn yet, don't attempt to find any items.
- i = me.hasFirstLayout ? ln - 1 : -1,
- items = [],
- series, item;
- // Iterate from the end so that the series that are drawn later get hit tested first.
- for (; i >= 0; i--) {
- series = seriesList[i];
- item = series.getItemForPoint(x, y);
- if (item && (item.category === 'items' || item.category === 'markers')) {
- items.push(item);
- }
- }
- return items;
- },
- /**
- * @private
- */
- onDataChanged: function() {
- var me = this,
- rect, store, series, axes;
- if (me.isInitializing) {
- return;
- }
- rect = me.getMainRect();
- store = me.getStore();
- series = me.getSeries();
- axes = me.getAxes();
- if (!store || !axes || !series) {
- return;
- }
- if (!rect) {
- // The chart hasn't been rendered yet.
- me.on({
- redraw: me.onDataChanged,
- scope: me,
- single: true
- });
- return;
- }
- me.processData();
- me.redraw();
- },
- /**
- * @private
- * The number of records in the chart's store last time the data was changed.
- */
- recordCount: 0,
- /**
- * @private
- */
- processData: function() {
- var me = this,
- recordCount = me.getStore().getCount(),
- seriesList = me.getSeries(),
- ln = seriesList.length,
- isNeedUpdateColors = false,
- i = 0,
- series;
- for (; i < ln; i++) {
- series = seriesList[i];
- series.processData();
- if (!isNeedUpdateColors && series.isStoreDependantColorCount) {
- isNeedUpdateColors = true;
- }
- }
- if (isNeedUpdateColors && recordCount > me.recordCount) {
- me.updateColors(me.getColors());
- me.recordCount = recordCount;
- }
- // 'refreshLegendStore' will attemp to grab the 'series',
- // which are still configuring at this point.
- // The legend store will be refreshed inside the chart.series
- // updater anyway.
- if (!me.isConfiguring) {
- me.refreshLegendStore();
- }
- },
- /**
- * Changes the data store bound to this chart and refreshes it.
- * @param {Ext.data.Store} store The store to bind to this chart.
- */
- bindStore: function(store) {
- this.setStore(store);
- },
- applyHighlightItem: function(newHighlightItem, oldHighlightItem) {
- var i1, i2, s1, s2;
- if (newHighlightItem === oldHighlightItem) {
- return;
- }
- if (Ext.isObject(newHighlightItem) && Ext.isObject(oldHighlightItem)) {
- i1 = newHighlightItem;
- i2 = oldHighlightItem;
- s1 = i1.sprite && (i1.sprite[0] || i1.sprite);
- s2 = i2.sprite && (i2.sprite[0] || i2.sprite);
- if (s1 === s2 && i1.index === i2.index) {
- return;
- }
- }
- return newHighlightItem;
- },
- updateHighlightItem: function(newHighlightItem, oldHighlightItem) {
- var newHighlight, oldHighlight;
- if (oldHighlightItem) {
- oldHighlight = oldHighlightItem.series.getHighlight();
- if (oldHighlight) {
- oldHighlightItem.series.setAttributesForItem(oldHighlightItem, {
- highlighted: false
- });
- }
- }
- if (newHighlightItem) {
- newHighlight = newHighlightItem.series.getHighlight();
- if (newHighlight) {
- newHighlightItem.series.setAttributesForItem(newHighlightItem, {
- highlighted: true
- });
- }
- }
- if (oldHighlight || newHighlight) {
- this.fireEvent('itemhighlight', this, newHighlightItem, oldHighlightItem);
- }
- },
- destroyChart: function() {
- var me = this;
- // The order is important here.
- me.setInteractions(null);
- me.setAxes(null);
- me.setSeries(null);
- me.setLegend(null);
- me.setStore(null);
- me.cancelChartLayout();
- },
- /* ---------------------------------
- Methods needed for ComponentQuery
- ---------------------------------- */
- /**
- * @private
- * @param {Boolean} deep
- * @return {Array}
- */
- getRefItems: function(deep) {
- var me = this,
- series = me.getSeries(),
- axes = me.getAxes(),
- interaction = me.getInteractions(),
- legend = me.getLegend(),
- ans = [],
- i, ln;
- for (i = 0 , ln = series.length; i < ln; i++) {
- ans.push(series[i]);
- if (series[i].getRefItems) {
- ans.push.apply(ans, series[i].getRefItems(deep));
- }
- }
- for (i = 0 , ln = axes.length; i < ln; i++) {
- ans.push(axes[i]);
- if (axes[i].getRefItems) {
- ans.push.apply(ans, axes[i].getRefItems(deep));
- }
- }
- for (i = 0 , ln = interaction.length; i < ln; i++) {
- ans.push(interaction[i]);
- if (interaction[i].getRefItems) {
- ans.push.apply(ans, interaction[i].getRefItems(deep));
- }
- }
- if (legend) {
- ans.push(legend);
- }
- return ans;
- }
- });
- Ext.define('Ext.chart.overrides.AbstractChart', {
- override: 'Ext.chart.AbstractChart',
- // In Modern toolkit, if chart element style has no z-index specified,
- // some chart surfaces with higher z-indexes (e.g. overlay)
- // may end up on top of modal dialogs shown over the chart.
- zIndex: 0,
- updateLegend: function(legend, oldLegend) {
- this.callParent([
- legend,
- oldLegend
- ]);
- if (legend && legend.isDomLegend) {
- this.add(legend);
- }
- },
- onItemRemove: function(item, index, destroy) {
- var map = this.surfaceMap,
- type = item.type,
- items = map && map[type];
- this.callParent([
- item,
- index,
- destroy
- ]);
- if (items) {
- Ext.Array.remove(items, item);
- if (items.length === 0) {
- delete map[type];
- }
- }
- },
- doDestroy: function() {
- this.destroyChart();
- this.callParent();
- }
- });
- /**
- * @class Ext.chart.grid.HorizontalGrid
- * @extends Ext.draw.sprite.Sprite
- *
- * Horizontal Grid sprite. Used in Cartesian Charts.
- */
- Ext.define('Ext.chart.grid.HorizontalGrid', {
- extend: 'Ext.draw.sprite.Sprite',
- alias: 'grid.horizontal',
- inheritableStatics: {
- def: {
- processors: {
- x: 'number',
- y: 'number',
- width: 'number',
- height: 'number'
- },
- defaults: {
- x: 0,
- y: 0,
- width: 1,
- height: 1,
- strokeStyle: '#DDD'
- }
- }
- },
- render: function(surface, ctx, rect) {
- var attr = this.attr,
- y = surface.roundPixel(attr.y),
- halfLineWidth = ctx.lineWidth * 0.5;
- ctx.beginPath();
- ctx.rect(rect[0] - surface.matrix.getDX(), y + halfLineWidth, +rect[2], attr.height);
- ctx.fill();
- ctx.beginPath();
- ctx.moveTo(rect[0] - surface.matrix.getDX(), y + halfLineWidth);
- ctx.lineTo(rect[0] + rect[2] - surface.matrix.getDX(), y + halfLineWidth);
- ctx.stroke();
- }
- });
- /**
- * @class Ext.chart.grid.VerticalGrid
- * @extends Ext.draw.sprite.Sprite
- *
- * Vertical Grid sprite. Used in Cartesian Charts.
- */
- Ext.define('Ext.chart.grid.VerticalGrid', {
- extend: 'Ext.draw.sprite.Sprite',
- alias: 'grid.vertical',
- inheritableStatics: {
- def: {
- processors: {
- x: 'number',
- y: 'number',
- width: 'number',
- height: 'number'
- },
- defaults: {
- x: 0,
- y: 0,
- width: 1,
- height: 1,
- strokeStyle: '#DDD'
- }
- }
- },
- render: function(surface, ctx, rect) {
- var attr = this.attr,
- x = surface.roundPixel(attr.x),
- halfLineWidth = ctx.lineWidth * 0.5;
- ctx.beginPath();
- ctx.rect(x - halfLineWidth, rect[1] - surface.matrix.getDY(), attr.width, rect[3]);
- ctx.fill();
- ctx.beginPath();
- ctx.moveTo(x - halfLineWidth, rect[1] - surface.matrix.getDY());
- ctx.lineTo(x - halfLineWidth, rect[1] + rect[3] - surface.matrix.getDY());
- ctx.stroke();
- }
- });
- /**
- * Represents a chart that uses cartesian coordinates.
- * A cartesian chart has two directions, X direction and Y direction.
- * The series and axes are coordinated along these directions.
- * By default the x direction is horizontal and y direction is vertical,
- * You can swap the direction by setting the {@link #flipXY} config to `true`.
- *
- * Cartesian series often treats x direction an y direction differently.
- * In most cases, data on x direction are assumed to be monotonically increasing.
- * Based on this property, cartesian series can be trimmed and summarized properly
- * to gain a better performance.
- *
- * Please check out the summary for the {@link Ext.chart.AbstractChart} as well,
- * for helpful tips and important details.
- *
- */
- Ext.define('Ext.chart.CartesianChart', {
- extend: 'Ext.chart.AbstractChart',
- alternateClassName: 'Ext.chart.Chart',
- requires: [
- 'Ext.chart.grid.HorizontalGrid',
- 'Ext.chart.grid.VerticalGrid'
- ],
- xtype: [
- 'cartesian',
- 'chart'
- ],
- isCartesian: true,
- config: {
- /**
- * @cfg {Boolean} flipXY Flip the direction of X and Y axis.
- * If flipXY is `true`, the X axes will be vertical and Y axes will be horizontal.
- * Note that {@link Ext.chart.axis.Axis#position positions} of chart axes have
- * to be updated accordingly: axes positioned to the `top` and `bottom` should
- * be positioned to the `left` or `right` and vice versa.
- */
- flipXY: false,
- /*
- While it may seem tedious to change the position config of all axes every time
- when the value of the flipXY config is changed, it's hard to predict the
- expectaction of the user here, as illustrated below.
- The 'num' and 'cat' here stand for the numeric and the category axis, respectively.
- And the right column shows the expected (subjective) result of setting the flipXY
- config of the chart to 'true'.
- As one can see, there's no single rule (e.g. position swapping, clockwise 90° chart
- rotation) that will produce a universally accepted result.
- So we are letting the user decide, instead of doing it for them.
- ---------------------------------------------
- | flipXY: false | flipXY: true |
- ---------------------------------------------
- | ^ | ^ |
- | | * | | * * * |
- | num1 | * * | cat | * * |
- | | * * * | | * |
- | --------> | --------> |
- | cat | num1 |
- ---------------------------------------------
- | | num1 |
- | ^ ^ | ^-------> |
- | | * | | | * * * |
- | num1 | * * | num2 | cat | * * |
- | | * * * | | | * |
- | --------> | --------> |
- | cat | num2 |
- ---------------------------------------------
- */
- innerRect: [
- 0,
- 0,
- 1,
- 1
- ],
- /**
- * @cfg {Object} innerPadding The amount of inner padding in pixels.
- * Inner padding is the padding from the innermost axes to the series.
- */
- innerPadding: {
- top: 0,
- left: 0,
- right: 0,
- bottom: 0
- }
- },
- applyInnerPadding: function(padding, oldPadding) {
- if (!Ext.isObject(padding)) {
- return Ext.util.Format.parseBox(padding);
- } else if (!oldPadding) {
- return padding;
- } else {
- return Ext.apply(oldPadding, padding);
- }
- },
- getDirectionForAxis: function(position) {
- var flipXY = this.getFlipXY(),
- direction;
- if (position === 'left' || position === 'right') {
- direction = flipXY ? 'X' : 'Y';
- } else {
- direction = flipXY ? 'Y' : 'X';
- }
- return direction;
- },
- /**
- * Layout the axes and series.
- */
- performLayout: function() {
- var me = this;
- if (me.callParent() === false) {
- return;
- }
- me.chartLayoutCount++;
- me.suspendAnimation();
- // 'chart' surface rect is the size of the chart's inner element
- // (see chart.getChartBox), i.e. the portion of the chart minus
- // the legend area (whether DOM or sprite based).
- // eslint-disable-next-line vars-on-top, one-var
- var chartRect = me.getSurface('chart').getRect(),
- left = chartRect[0],
- top = chartRect[1],
- width = chartRect[2],
- height = chartRect[3],
- captionList = me.captionList,
- axes = me.getAxes(),
- axis,
- seriesList = me.getSeries(),
- series, axisSurface, thickness,
- insetPadding = me.getInsetPadding(),
- innerPadding = me.getInnerPadding(),
- surface, gridSurface,
- // shrinkBox represents padding added on each side by
- // innerPadding & insetPadding configs and the legend.
- shrinkBox = Ext.apply({}, insetPadding),
- mainRect, innerWidth, innerHeight, elements, floating, floatingValue, matrix, i, ln,
- isRtl = me.getInherited().rtl,
- flipXY = me.getFlipXY(),
- caption;
- if (width <= 0 || height <= 0) {
- return;
- }
- me.suspendThicknessChanged();
- for (i = 0; i < axes.length; i++) {
- axis = axes[i];
- axisSurface = axis.getSurface();
- floating = axis.getFloating();
- floatingValue = floating ? floating.value : null;
- thickness = axis.getThickness();
- switch (axis.getPosition()) {
- case 'top':
- axisSurface.setRect([
- left,
- top + shrinkBox.top + 1,
- width,
- thickness
- ]);
- break;
- case 'bottom':
- axisSurface.setRect([
- left,
- top + height - (shrinkBox.bottom + thickness),
- width,
- thickness
- ]);
- break;
- case 'left':
- axisSurface.setRect([
- left + shrinkBox.left,
- top,
- thickness,
- height
- ]);
- break;
- case 'right':
- axisSurface.setRect([
- left + width - (shrinkBox.right + thickness),
- top,
- thickness,
- height
- ]);
- break;
- }
- if (floatingValue === null) {
- shrinkBox[axis.getPosition()] += thickness;
- }
- }
- width -= shrinkBox.left + shrinkBox.right;
- height -= shrinkBox.top + shrinkBox.bottom;
- mainRect = [
- left + shrinkBox.left,
- top + shrinkBox.top,
- width,
- height
- ];
- shrinkBox.left += innerPadding.left;
- shrinkBox.top += innerPadding.top;
- shrinkBox.right += innerPadding.right;
- shrinkBox.bottom += innerPadding.bottom;
- innerWidth = width - innerPadding.left - innerPadding.right;
- innerHeight = height - innerPadding.top - innerPadding.bottom;
- me.setInnerRect([
- shrinkBox.left,
- shrinkBox.top,
- innerWidth,
- innerHeight
- ]);
- if (innerWidth <= 0 || innerHeight <= 0) {
- return;
- }
- me.setMainRect(mainRect);
- me.getSurface().setRect(mainRect);
- for (i = 0 , ln = me.surfaceMap.grid && me.surfaceMap.grid.length; i < ln; i++) {
- gridSurface = me.surfaceMap.grid[i];
- gridSurface.setRect(mainRect);
- gridSurface.matrix.set(1, 0, 0, 1, innerPadding.left, innerPadding.top);
- gridSurface.matrix.inverse(gridSurface.inverseMatrix);
- }
- for (i = 0; i < axes.length; i++) {
- axis = axes[i];
- axis.getRange(true);
- axisSurface = axis.getSurface();
- matrix = axisSurface.matrix;
- elements = matrix.elements;
- switch (axis.getPosition()) {
- case 'top':
- case 'bottom':
- elements[4] = shrinkBox.left;
- axis.setLength(innerWidth);
- break;
- case 'left':
- case 'right':
- elements[5] = shrinkBox.top;
- axis.setLength(innerHeight);
- break;
- }
- axis.updateTitleSprite();
- matrix.inverse(axisSurface.inverseMatrix);
- }
- for (i = 0 , ln = seriesList.length; i < ln; i++) {
- series = seriesList[i];
- surface = series.getSurface();
- surface.setRect(mainRect);
- if (flipXY) {
- if (isRtl) {
- surface.matrix.set(0, -1, -1, 0, innerPadding.left + innerWidth, innerPadding.top + innerHeight);
- } else {
- surface.matrix.set(0, -1, 1, 0, innerPadding.left, innerPadding.top + innerHeight);
- }
- } else {
- surface.matrix.set(1, 0, 0, -1, innerPadding.left, innerPadding.top + innerHeight);
- }
- surface.matrix.inverse(surface.inverseMatrix);
- series.getOverlaySurface().setRect(mainRect);
- }
- if (captionList) {
- for (i = 0 , ln = captionList.length; i < ln; i++) {
- caption = captionList[i];
- if (caption.getAlignTo() === 'series') {
- caption.alignRect(mainRect);
- }
- caption.performLayout();
- }
- }
- // In certain cases 'performLayout' override is not an option without major code duplication
- // 'afterChartLayout' can be a cleaner solution in such cases (because of the timing
- // of its call).
- me.afterChartLayout();
- // currently in cartesian charts only (used by Navigator)
- me.redraw();
- me.resumeAnimation();
- // 'resumeThicknessChanged' may trigger another layout, if the 'redraw' call above
- // resulted in a situation where an axis is no longer 'thick' enough to accommodate
- // the new labels. E.g. the labels were: 'Bob', 'Ann', 'Joe' and now they are 'Jonathan',
- // 'Rachael', 'Michael'. An axis has to be made thicker now, and another layout should be
- // performed. This second layout is not scheduled, but performed immediately, which will
- // increment the 'chartLayoutCount' again.
- me.resumeThicknessChanged();
- me.chartLayoutCount--;
- // 'checkLayoutEnd' will check if another layout is already running or scheduled and,
- // if neither is the case, will fire the 'layout' event, meaning we are totally done
- // with layout at this point.
- me.checkLayoutEnd();
- },
- afterChartLayout: Ext.emptyFn,
- refloatAxes: function() {
- var me = this,
- axes = me.getAxes(),
- axesCount = (axes && axes.length) || 0,
- axis, axisSurface, axisRect, floating, value, alongAxis, matrix,
- chartRect = me.getChartRect(),
- inset = me.getInsetPadding(),
- inner = me.getInnerPadding(),
- width = chartRect[2] - inset.left - inset.right,
- height = chartRect[3] - inset.top - inset.bottom,
- isHorizontal, i;
- for (i = 0; i < axesCount; i++) {
- axis = axes[i];
- floating = axis.getFloating();
- value = floating ? floating.value : null;
- if (value === null) {
- axis.floatingAtCoord = null;
-
- continue;
- }
- axisSurface = axis.getSurface();
- axisRect = axisSurface.getRect();
- if (!axisRect) {
-
- continue;
- }
- axisRect = axisRect.slice();
- alongAxis = me.getAxis(floating.alongAxis);
- if (alongAxis) {
- isHorizontal = alongAxis.getAlignment() === 'horizontal';
- if (Ext.isString(value)) {
- value = alongAxis.getCoordFor(value);
- }
- alongAxis.floatingAxes[axis.getId()] = value;
- matrix = alongAxis.getSprites()[0].attr.matrix;
- if (isHorizontal) {
- value = value * matrix.getXX() + matrix.getDX();
- axis.floatingAtCoord = value + inner.left + inner.right;
- } else {
- value = value * matrix.getYY() + matrix.getDY();
- axis.floatingAtCoord = value + inner.top + inner.bottom;
- }
- } else {
- isHorizontal = axis.getAlignment() === 'horizontal';
- if (isHorizontal) {
- axis.floatingAtCoord = value + inner.top + inner.bottom;
- } else {
- axis.floatingAtCoord = value + inner.left + inner.right;
- }
- value = axisSurface.roundPixel(0.01 * value * (isHorizontal ? height : width));
- }
- switch (axis.getPosition()) {
- case 'top':
- axisRect[1] = inset.top + inner.top + value - axisRect[3] + 1;
- break;
- case 'bottom':
- axisRect[1] = inset.top + inner.top + (alongAxis ? value : height - value);
- break;
- case 'left':
- axisRect[0] = inset.left + inner.left + value - axisRect[2];
- break;
- case 'right':
- axisRect[0] = inset.left + inner.left + (alongAxis ? value : width - value) - 1;
- break;
- }
- axisSurface.setRect(axisRect);
- }
- },
- redraw: function() {
- var me = this,
- seriesList = me.getSeries(),
- axes = me.getAxes(),
- rect = me.getMainRect(),
- innerWidth, innerHeight,
- innerPadding = me.getInnerPadding(),
- sprites, xRange, yRange, isSide, attr, i, j, ln, axis, axisX, axisY, range, visibleRange,
- flipXY = me.getFlipXY(),
- zBase = 1000,
- zIndex, markersZIndex, series, sprite, markers;
- if (!rect) {
- return;
- }
- innerWidth = rect[2] - innerPadding.left - innerPadding.right;
- innerHeight = rect[3] - innerPadding.top - innerPadding.bottom;
- for (i = 0; i < seriesList.length; i++) {
- series = seriesList[i];
- axisX = series.getXAxis();
- if (axisX) {
- visibleRange = axisX.getVisibleRange();
- xRange = axisX.getRange();
- xRange = [
- xRange[0] + (xRange[1] - xRange[0]) * visibleRange[0],
- xRange[0] + (xRange[1] - xRange[0]) * visibleRange[1]
- ];
- } else {
- xRange = series.getXRange();
- }
- axisY = series.getYAxis();
- if (axisY) {
- visibleRange = axisY.getVisibleRange();
- yRange = axisY.getRange();
- yRange = [
- yRange[0] + (yRange[1] - yRange[0]) * visibleRange[0],
- yRange[0] + (yRange[1] - yRange[0]) * visibleRange[1]
- ];
- } else {
- yRange = series.getYRange();
- }
- attr = {
- visibleMinX: xRange[0],
- visibleMaxX: xRange[1],
- visibleMinY: yRange[0],
- visibleMaxY: yRange[1],
- innerWidth: innerWidth,
- innerHeight: innerHeight,
- flipXY: flipXY
- };
- sprites = series.getSprites();
- for (j = 0 , ln = sprites.length; j < ln; j++) {
- // All the series now share the same surface, so we must assign
- // the sprites a zIndex that depends on the index of their series.
- sprite = sprites[j];
- zIndex = sprite.attr.zIndex;
- if (zIndex < zBase) {
- // Set the sprite's zIndex
- zIndex += (i + 1) * 100 + zBase;
- sprite.attr.zIndex = zIndex;
- // If the sprite is a MarkerHolder, set zIndex of the bound markers as well.
- // Do this for the 'items' markers only, as those are the only ones
- // that go into the 'series' surface. 'labels' and 'markers' markers
- // go into the 'overlay' surface instead.
- markers = sprite.getMarker('items');
- if (markers) {
- markersZIndex = markers.attr.zIndex;
- if (markersZIndex === Number.MAX_VALUE) {
- markers.attr.zIndex = zIndex;
- } else if (markersZIndex < zBase) {
- markers.attr.zIndex = zIndex + markersZIndex;
- }
- }
- }
- sprite.setAttributes(attr, true);
- }
- }
- for (i = 0; i < axes.length; i++) {
- axis = axes[i];
- isSide = axis.isSide();
- sprites = axis.getSprites();
- range = axis.getRange();
- visibleRange = axis.getVisibleRange();
- attr = {
- dataMin: range[0],
- dataMax: range[1],
- visibleMin: visibleRange[0],
- visibleMax: visibleRange[1]
- };
- if (isSide) {
- attr.length = innerHeight;
- attr.startGap = innerPadding.bottom;
- attr.endGap = innerPadding.top;
- } else {
- attr.length = innerWidth;
- attr.startGap = innerPadding.left;
- attr.endGap = innerPadding.right;
- }
- for (j = 0 , ln = sprites.length; j < ln; j++) {
- sprites[j].setAttributes(attr, true);
- }
- }
- me.renderFrame();
- me.callParent();
- },
- renderFrame: function() {
- this.refloatAxes();
- this.callParent();
- }
- });
- /**
- * @class Ext.chart.grid.CircularGrid
- * @extends Ext.draw.sprite.Circle
- *
- * Circular Grid sprite. Used by Radar chart to render a series of concentric circles.
- */
- Ext.define('Ext.chart.grid.CircularGrid', {
- extend: 'Ext.draw.sprite.Circle',
- alias: 'grid.circular',
- inheritableStatics: {
- def: {
- defaults: {
- r: 1,
- strokeStyle: '#DDD'
- }
- }
- }
- });
- /**
- * @class Ext.chart.grid.RadialGrid
- * @extends Ext.draw.sprite.Path
- *
- * Radial Grid sprite. Used by Radar chart to render a series of radial lines.
- * Represents the scale of the radar chart on the yField.
- */
- Ext.define('Ext.chart.grid.RadialGrid', {
- extend: 'Ext.draw.sprite.Path',
- alias: 'grid.radial',
- inheritableStatics: {
- def: {
- processors: {
- startRadius: 'number',
- endRadius: 'number'
- },
- defaults: {
- startRadius: 0,
- endRadius: 1,
- scalingCenterX: 0,
- scalingCenterY: 0,
- strokeStyle: '#DDD'
- },
- triggers: {
- startRadius: 'path,bbox',
- endRadius: 'path,bbox'
- }
- }
- },
- render: function() {
- this.callParent(arguments);
- },
- updatePath: function(path, attr) {
- var startRadius = attr.startRadius,
- endRadius = attr.endRadius;
- path.moveTo(startRadius, 0);
- path.lineTo(endRadius, 0);
- }
- });
- /**
- * @class Ext.chart.PolarChart
- * @extends Ext.chart.AbstractChart
- * @xtype polar
- *
- * Represent a chart that uses polar coordinates.
- * A polar chart has two axes: an angular axis (which is a circle) and
- * a radial axis (a straight line from the center to the edge of the circle).
- * The angular axis is usually a Category axis while the radial axis is
- * typically numerical.
- *
- * Pie charts and Radar charts are common examples of Polar charts.
- *
- * Please check out the summary for the {@link Ext.chart.AbstractChart} as well,
- * for helpful tips and important details.
- *
- */
- Ext.define('Ext.chart.PolarChart', {
- extend: 'Ext.chart.AbstractChart',
- requires: [
- 'Ext.chart.grid.CircularGrid',
- 'Ext.chart.grid.RadialGrid'
- ],
- xtype: 'polar',
- isPolar: true,
- config: {
- /**
- * @cfg {Array} center Determines the center of the polar chart.
- * Updated when the chart performs layout.
- */
- center: [
- 0,
- 0
- ],
- /**
- * @cfg {Number} radius Determines the radius of the polar chart.
- * Updated when the chart performs layout.
- */
- radius: 0,
- /**
- * @cfg {Number} innerPadding The amount of inner padding in pixels.
- * Inner padding is the padding from the outermost angular axis to the series.
- */
- innerPadding: 0
- },
- getDirectionForAxis: function(position) {
- return position === 'radial' ? 'Y' : 'X';
- },
- updateCenter: function(center) {
- var me = this,
- axes = me.getAxes(),
- series = me.getSeries(),
- i, ln, axis, seriesItem;
- for (i = 0 , ln = axes.length; i < ln; i++) {
- axis = axes[i];
- axis.setCenter(center);
- }
- for (i = 0 , ln = series.length; i < ln; i++) {
- seriesItem = series[i];
- seriesItem.setCenter(center);
- }
- },
- applyInnerPadding: function(padding, oldPadding) {
- return Ext.isNumber(padding) ? padding : oldPadding;
- },
- updateInnerPadding: function() {
- if (!this.isConfiguring) {
- this.performLayout();
- }
- },
- doSetSurfaceRect: function(surface, rect) {
- var mainRect = this.getMainRect();
- surface.setRect(rect);
- surface.matrix.set(1, 0, 0, 1, mainRect[0] - rect[0], mainRect[1] - rect[1]);
- surface.inverseMatrix.set(1, 0, 0, 1, rect[0] - mainRect[0], rect[1] - mainRect[1]);
- },
- applyAxes: function(newAxes, oldAxes) {
- var me = this,
- firstSeries = Ext.Array.from(me.config.series)[0],
- i, ln, axis, foundAngular;
- if (firstSeries && firstSeries.type === 'radar' && newAxes && newAxes.length) {
- // For compatibility with ExtJS: add a default angular axis if it's missing
- for (i = 0 , ln = newAxes.length; i < ln; i++) {
- axis = newAxes[i];
- if (axis.position === 'angular') {
- foundAngular = true;
- break;
- }
- }
- if (!foundAngular) {
- newAxes.push({
- type: 'category',
- position: 'angular',
- fields: firstSeries.xField || firstSeries.angleField,
- style: {
- estStepSize: 1
- },
- grid: true
- });
- }
- }
- return this.callParent([
- newAxes,
- oldAxes
- ]);
- },
- performLayout: function() {
- var me = this,
- applyThickness = true;
- try {
- me.chartLayoutCount++;
- me.suspendAnimation();
- if (this.callParent() === false) {
- applyThickness = false;
- // Animation will be decremented in finally block
- return;
- }
- me.suspendThicknessChanged();
- // eslint-disable-next-line vars-on-top, one-var
- var chartRect = me.getSurface('chart').getRect(),
- inset = me.getInsetPadding(),
- inner = me.getInnerPadding(),
- shrinkBox = Ext.apply({}, inset),
- width = Math.max(1, chartRect[2] - chartRect[0] - inset.left - inset.right),
- height = Math.max(1, chartRect[3] - chartRect[1] - inset.top - inset.bottom),
- mainRect = [
- chartRect[0] + inset.left,
- chartRect[1] + inset.top,
- width + chartRect[0],
- height + chartRect[1]
- ],
- seriesList = me.getSeries(),
- innerWidth = width - inner * 2,
- innerHeight = height - inner * 2,
- center = [
- (chartRect[0] + innerWidth) * 0.5 + inner,
- (chartRect[1] + innerHeight) * 0.5 + inner
- ],
- radius = Math.min(innerWidth, innerHeight) * 0.5,
- axes = me.getAxes(),
- angularAxes = [],
- radialAxes = [],
- seriesRadius = radius - inner,
- grid = me.surfaceMap.grid,
- captionList = me.captionList,
- i, ln, shrinkRadius, floating, floatingValue, // eslint-disable-line no-unused-vars
- gaugeSeries, gaugeRadius, side, series, axis, thickness, halfLineWidth, caption;
- me.setMainRect(mainRect);
- me.doSetSurfaceRect(me.getSurface(), mainRect);
- if (grid) {
- for (i = 0 , ln = grid.length; i < ln; i++) {
- me.doSetSurfaceRect(grid[i], chartRect);
- }
- }
- for (i = 0 , ln = axes.length; i < ln; i++) {
- axis = axes[i];
- switch (axis.getPosition()) {
- case 'angular':
- angularAxes.push(axis);
- break;
- case 'radial':
- radialAxes.push(axis);
- break;
- }
- }
- for (i = 0 , ln = angularAxes.length; i < ln; i++) {
- axis = angularAxes[i];
- floating = axis.getFloating();
- floatingValue = floating ? floating.value : null;
- me.doSetSurfaceRect(axis.getSurface(), chartRect);
- thickness = axis.getThickness();
- for (side in shrinkBox) {
- shrinkBox[side] += thickness;
- }
- width = chartRect[2] - shrinkBox.left - shrinkBox.right;
- height = chartRect[3] - shrinkBox.top - shrinkBox.bottom;
- shrinkRadius = Math.min(width, height) * 0.5;
- if (i === 0) {
- seriesRadius = shrinkRadius - inner;
- }
- axis.setMinimum(0);
- axis.setLength(shrinkRadius);
- axis.getSprites();
- halfLineWidth = axis.sprites[0].attr.lineWidth * 0.5;
- for (side in shrinkBox) {
- shrinkBox[side] += halfLineWidth;
- }
- }
- for (i = 0 , ln = radialAxes.length; i < ln; i++) {
- axis = radialAxes[i];
- me.doSetSurfaceRect(axis.getSurface(), chartRect);
- axis.setMinimum(0);
- axis.setLength(seriesRadius);
- axis.getSprites();
- }
- for (i = 0 , ln = seriesList.length; i < ln; i++) {
- series = seriesList[i];
- if (series.type === 'gauge' && !gaugeSeries) {
- gaugeSeries = series;
- } else {
- series.setRadius(seriesRadius);
- }
- me.doSetSurfaceRect(series.getSurface(), mainRect);
- }
- me.doSetSurfaceRect(me.getSurface('overlay'), chartRect);
- if (gaugeSeries) {
- gaugeSeries.setRect(mainRect);
- gaugeRadius = gaugeSeries.getRadius() - inner;
- me.setRadius(gaugeRadius);
- me.setCenter(gaugeSeries.getCenter());
- gaugeSeries.setRadius(gaugeRadius);
- if (axes.length && axes[0].getPosition() === 'gauge') {
- axis = axes[0];
- me.doSetSurfaceRect(axis.getSurface(), chartRect);
- axis.setTotalAngle(gaugeSeries.getTotalAngle());
- axis.setLength(gaugeRadius);
- }
- } else {
- me.setRadius(radius);
- me.setCenter(center);
- }
- if (captionList) {
- for (i = 0 , ln = captionList.length; i < ln; i++) {
- caption = captionList[i];
- if (caption.getAlignTo() === 'series') {
- caption.alignRect(mainRect);
- }
- caption.performLayout();
- }
- }
- me.redraw();
- } finally {
- me.resumeAnimation();
- if (applyThickness) {
- me.resumeThicknessChanged();
- }
- me.chartLayoutCount--;
- me.checkLayoutEnd();
- }
- },
- refloatAxes: function() {
- var me = this,
- axes = me.getAxes(),
- mainRect = me.getMainRect(),
- floating, value, alongAxis, i, n, axis, radius;
- if (!mainRect) {
- return;
- }
- radius = 0.5 * Math.min(mainRect[2], mainRect[3]);
- for (i = 0 , n = axes.length; i < n; i++) {
- axis = axes[i];
- floating = axis.getFloating();
- value = floating ? floating.value : null;
- if (value !== null) {
- alongAxis = me.getAxis(floating.alongAxis);
- if (axis.getPosition() === 'angular') {
- if (alongAxis) {
- value = alongAxis.getLength() * value / alongAxis.getRange()[1];
- } else {
- value = 0.01 * value * radius;
- }
- axis.sprites[0].setAttributes({
- length: value
- }, true);
- } else {
- if (alongAxis) {
- if (Ext.isString(value)) {
- value = alongAxis.getCoordFor(value);
- }
- value = value / (alongAxis.getRange()[1] + 1) * Math.PI * 2 - Math.PI * 1.5 + axis.getRotation();
- } else {
- value = Ext.draw.Draw.rad(value);
- }
- axis.sprites[0].setAttributes({
- baseRotation: value
- }, true);
- }
- }
- }
- },
- redraw: function() {
- var me = this,
- axes = me.getAxes(),
- axis,
- seriesList = me.getSeries(),
- series, i, ln;
- for (i = 0 , ln = axes.length; i < ln; i++) {
- axis = axes[i];
- axis.getSprites();
- }
- for (i = 0 , ln = seriesList.length; i < ln; i++) {
- series = seriesList[i];
- series.getSprites();
- }
- me.renderFrame();
- me.callParent();
- },
- renderFrame: function() {
- this.refloatAxes();
- this.callParent();
- }
- });
- /**
- * @class Ext.chart.SpaceFillingChart
- * @extends Ext.chart.AbstractChart
- *
- * Creates a chart that fills the entire area of the chart.
- * e.g. Gauge Charts
- */
- Ext.define('Ext.chart.SpaceFillingChart', {
- extend: 'Ext.chart.AbstractChart',
- xtype: 'spacefilling',
- config: {},
- performLayout: function() {
- var me = this;
- try {
- me.chartLayoutCount++;
- me.suspendAnimation();
- if (me.callParent() === false) {
- // animationSuspendCount will still be decremented
- return;
- }
- // eslint-disable-next-line vars-on-top, one-var
- var chartRect = me.getSurface('chart').getRect(),
- padding = me.getInsetPadding(),
- width = chartRect[2] - padding.left - padding.right,
- height = chartRect[3] - padding.top - padding.bottom,
- mainRect = [
- padding.left,
- padding.top,
- width,
- height
- ],
- seriesList = me.getSeries(),
- series, i, ln;
- me.getSurface().setRect(mainRect);
- me.setMainRect(mainRect);
- for (i = 0 , ln = seriesList.length; i < ln; i++) {
- series = seriesList[i];
- series.getSurface().setRect(mainRect);
- if (series.setRect) {
- series.setRect(mainRect);
- }
- series.getOverlaySurface().setRect(chartRect);
- }
- me.redraw();
- } finally {
- me.resumeAnimation();
- me.chartLayoutCount--;
- me.checkLayoutEnd();
- }
- },
- redraw: function() {
- var me = this,
- seriesList = me.getSeries(),
- series, i, ln;
- for (i = 0 , ln = seriesList.length; i < ln; i++) {
- series = seriesList[i];
- series.getSprites();
- }
- me.renderFrame();
- me.callParent();
- }
- });
- /**
- * @private
- * @class Ext.chart.axis.sprite.Axis3D
- * @extends Ext.chart.axis.sprite.Axis
- *
- * The {@link Ext.chart.axis.Axis3D 3D axis} sprite.
- * Only 3D cartesian axes are rendered with this sprite.
- */
- Ext.define('Ext.chart.axis.sprite.Axis3D', {
- extend: 'Ext.chart.axis.sprite.Axis',
- alias: 'sprite.axis3d',
- type: 'axis3d',
- inheritableStatics: {
- def: {
- processors: {
- depth: 'number'
- },
- defaults: {
- depth: 0
- },
- triggers: {
- depth: 'layout'
- }
- }
- },
- config: {
- animation: {
- customDurations: {
- depth: 0
- }
- }
- },
- layoutUpdater: function() {
- var me = this,
- chart = me.getAxis().getChart();
- if (chart.isInitializing) {
- return;
- }
- // eslint-disable-next-line vars-on-top, one-var
- var attr = me.attr,
- layout = me.getLayout(),
- depth = layout.isDiscrete ? 0 : attr.depth,
- isRtl = chart.getInherited().rtl,
- min = attr.dataMin + (attr.dataMax - attr.dataMin) * attr.visibleMin,
- max = attr.dataMin + (attr.dataMax - attr.dataMin) * attr.visibleMax,
- context = {
- attr: attr,
- segmenter: me.getSegmenter(),
- renderer: me.defaultRenderer
- };
- if (attr.position === 'left' || attr.position === 'right') {
- attr.translationX = 0;
- attr.translationY = max * (attr.length - depth) / (max - min) + depth;
- attr.scalingX = 1;
- attr.scalingY = (-attr.length + depth) / (max - min);
- attr.scalingCenterY = 0;
- attr.scalingCenterX = 0;
- me.applyTransformations(true);
- } else if (attr.position === 'top' || attr.position === 'bottom') {
- if (isRtl) {
- attr.translationX = attr.length + min * attr.length / (max - min) + 1;
- } else {
- attr.translationX = -min * attr.length / (max - min);
- }
- attr.translationY = 0;
- attr.scalingX = (isRtl ? -1 : 1) * (attr.length - depth) / (max - min);
- attr.scalingY = 1;
- attr.scalingCenterY = 0;
- attr.scalingCenterX = 0;
- me.applyTransformations(true);
- }
- if (layout) {
- layout.calculateLayout(context);
- me.setLayoutContext(context);
- }
- },
- renderAxisLine: function(surface, ctx, layout, clipRect) {
- var me = this,
- attr = me.attr,
- halfLineWidth = attr.lineWidth * 0.5,
- layout = me.getLayout(),
- // eslint-disable-line no-redeclare
- depth = layout.isDiscrete ? 0 : attr.depth,
- docked = attr.position,
- position, gaugeAngles;
- if (attr.axisLine && attr.length) {
- switch (docked) {
- case 'left':
- position = surface.roundPixel(clipRect[2]) - halfLineWidth;
- ctx.moveTo(position, -attr.endGap + depth);
- ctx.lineTo(position, attr.length + attr.startGap);
- break;
- case 'right':
- ctx.moveTo(halfLineWidth, -attr.endGap);
- ctx.lineTo(halfLineWidth, attr.length + attr.startGap);
- break;
- case 'bottom':
- ctx.moveTo(-attr.startGap, halfLineWidth);
- ctx.lineTo(attr.length - depth + attr.endGap, halfLineWidth);
- break;
- case 'top':
- position = surface.roundPixel(clipRect[3]) - halfLineWidth;
- ctx.moveTo(-attr.startGap, position);
- ctx.lineTo(attr.length + attr.endGap, position);
- break;
- case 'angular':
- ctx.moveTo(attr.centerX + attr.length, attr.centerY);
- ctx.arc(attr.centerX, attr.centerY, attr.length, 0, Math.PI * 2, true);
- break;
- case 'gauge':
- gaugeAngles = me.getGaugeAngles();
- ctx.moveTo(attr.centerX + Math.cos(gaugeAngles.start) * attr.length, attr.centerY + Math.sin(gaugeAngles.start) * attr.length);
- ctx.arc(attr.centerX, attr.centerY, attr.length, gaugeAngles.start, gaugeAngles.end, true);
- break;
- }
- }
- }
- });
- /**
- * @class Ext.chart.axis.Axis3D
- * @extends Ext.chart.axis.Axis
- * @xtype axis3d
- *
- * Defines a 3D axis for charts.
- *
- * A 3D axis has the same properties as the regular {@link Ext.chart.axis.Axis axis},
- * plus a notion of depth. The depth of the 3D axis is determined automatically
- * based on the depth of the bound series.
- *
- * This type of axis has the following limitations compared to the regular axis class:
- * - supported {@link Ext.chart.axis.Axis#position positions} are 'left' and 'bottom' only;
- * - floating axes are not supported.
- *
- * At the present moment only {@link Ext.chart.series.Bar3D} series can make use of the 3D axis.
- */
- Ext.define('Ext.chart.axis.Axis3D', {
- extend: 'Ext.chart.axis.Axis',
- xtype: 'axis3d',
- requires: [
- 'Ext.chart.axis.sprite.Axis3D'
- ],
- config: {
- /**
- * @private
- * The depth of the axis. Determined automatically.
- */
- depth: 0
- },
- /**
- * @cfg {String} position
- * Where to set the axis. Available options are `left` and `bottom`.
- */
- onSeriesChange: function(chart) {
- var me = this,
- eventName = 'depthchange',
- listenerName = 'onSeriesDepthChange',
- i, series;
- function toggle(action) {
- var boundSeries = me.boundSeries;
- for (i = 0; i < boundSeries.length; i++) {
- series = boundSeries[i];
- series[action](eventName, listenerName, me);
- }
- }
- // Remove 'depthchange' listeners from old bound series, if any.
- toggle('un');
- me.callParent(arguments);
- // Add 'depthchange' listeners to new bound series.
- toggle('on');
- },
- onSeriesDepthChange: function(series, depth) {
- var me = this,
- maxDepth = depth,
- boundSeries = me.boundSeries,
- i, item;
- if (depth > me.getDepth()) {
- maxDepth = depth;
- } else {
- for (i = 0; i < boundSeries.length; i++) {
- item = boundSeries[i];
- if (item !== series && item.getDepth) {
- depth = item.getDepth();
- if (depth > maxDepth) {
- maxDepth = depth;
- }
- }
- }
- }
- me.setDepth(maxDepth);
- },
- updateDepth: function(depth) {
- var me = this,
- sprites = me.getSprites(),
- attr = {
- depth: depth
- };
- if (sprites && sprites.length) {
- sprites[0].setAttributes(attr);
- }
- if (me.gridSpriteEven && me.gridSpriteOdd) {
- me.gridSpriteEven.getTemplate().setAttributes(attr);
- me.gridSpriteOdd.getTemplate().setAttributes(attr);
- }
- },
- getGridAlignment: function() {
- switch (this.getPosition()) {
- case 'left':
- case 'right':
- return 'horizontal3d';
- case 'top':
- case 'bottom':
- return 'vertical3d';
- }
- }
- });
- /**
- * @class Ext.chart.axis.Category
- * @extends Ext.chart.axis.Axis
- *
- * A type of axis that displays items in categories. This axis is generally used to
- * display categorical information like names of items, month names, quarters, etc.
- * but no quantitative values. For that other type of information
- * {@link Ext.chart.axis.Numeric Numeric} axis are more suitable.
- *
- * As with other axis you can set the position of the axis and its title. For example:
- *
- * @example
- * Ext.create({
- * xtype: 'cartesian',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * innerPadding: '0 40 0 40',
- * store: {
- * fields: ['name', 'data1', 'data2', 'data3'],
- * data: [{
- * 'name': 'metric one',
- * 'data1': 10,
- * 'data2': 12,
- * 'data3': 14
- * }, {
- * 'name': 'metric two',
- * 'data1': 7,
- * 'data2': 8,
- * 'data3': 16
- * }, {
- * 'name': 'metric three',
- * 'data1': 5,
- * 'data2': 2,
- * 'data3': 14
- * }, {
- * 'name': 'metric four',
- * 'data1': 2,
- * 'data2': 14,
- * 'data3': 6
- * }, {
- * 'name': 'metric five',
- * 'data1': 27,
- * 'data2': 38,
- * 'data3': 36
- * }]
- * },
- * axes: {
- * type: 'category',
- * position: 'bottom',
- * fields: ['name'],
- * title: {
- * text: 'Sample Values',
- * fontSize: 15
- * }
- * },
- * series: {
- * type: 'area',
- * subStyle: {
- * fill: ['#0A3F50', '#30BDA7', '#96D4C6']
- * },
- * xField: 'name',
- * yField: ['data1', 'data2', 'data3']
- * }
- * });
- *
- * In this example with set the category axis to the bottom of the surface, bound the axis to
- * the `name` property and set as title "Sample Values".
- */
- Ext.define('Ext.chart.axis.Category', {
- requires: [
- 'Ext.chart.axis.layout.CombineDuplicate',
- 'Ext.chart.axis.segmenter.Names'
- ],
- extend: 'Ext.chart.axis.Axis',
- alias: 'axis.category',
- type: 'category',
- isCategory: true,
- config: {
- layout: 'combineDuplicate',
- segmenter: 'names'
- }
- });
- /**
- * Category 3D Axis
- */
- Ext.define('Ext.chart.axis.Category3D', {
- requires: [
- 'Ext.chart.axis.layout.CombineDuplicate',
- 'Ext.chart.axis.segmenter.Names'
- ],
- extend: 'Ext.chart.axis.Axis3D',
- alias: 'axis.category3d',
- type: 'category3d',
- config: {
- layout: 'combineDuplicate',
- segmenter: 'names'
- }
- });
- /**
- * @class Ext.chart.axis.Numeric
- * @extends Ext.chart.axis.Axis
- *
- * An axis to handle numeric values. This axis is used for quantitative data as
- * opposed to the category axis. You can set minimum and maximum values to the
- * axis so that the values are bound to that. If no values are set, then the
- * scale will auto-adjust to the values.
- *
- * @example
- * Ext.create({
- * xtype: 'cartesian',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * store: {
- * fields: ['name', 'data1', 'data2', 'data3'],
- * data: [{
- * 'name': 1,
- * 'data1': 10,
- * 'data2': 12,
- * 'data3': 14
- * }, {
- * 'name': 2,
- * 'data1': 7,
- * 'data2': 8,
- * 'data3': 16
- * }, {
- * 'name': 3,
- * 'data1': 5,
- * 'data2': 2,
- * 'data3': 14
- * }, {
- * 'name': 4,
- * 'data1': 2,
- * 'data2': 14,
- * 'data3': 6
- * }, {
- * 'name': 5,
- * 'data1': 27,
- * 'data2': 38,
- * 'data3': 36
- * }]
- * },
- * axes: {
- * type: 'numeric',
- * position: 'left',
- * minimum: 0,
- * fields: ['data1', 'data2', 'data3'],
- * title: 'Sample Values',
- * grid: {
- * odd: {
- * opacity: 1,
- * fill: '#F2F2F2',
- * stroke: '#DDD',
- * 'lineWidth': 1
- * }
- * }
- * },
- * series: {
- * type: 'area',
- * subStyle: {
- * fill: ['#0A3F50', '#30BDA7', '#96D4C6']
- * },
- * xField: 'name',
- * yField: ['data1', 'data2', 'data3']
- * }
- * });
- *
- * In this example we create an axis of Numeric type. We set a minimum value so that
- * even if all series have values greater than zero, the grid starts at zero. We bind
- * the axis onto the left part of the surface by setting _position_ to _left_.
- * We bind three different store fields to this axis by setting _fields_ to an array.
- * We set the title of the axis to _Number of Hits_ by using the _title_ property.
- * We use a _grid_ configuration to set odd background rows to a certain style and even rows
- * to be transparent/ignored.
- *
- */
- Ext.define('Ext.chart.axis.Numeric', {
- extend: 'Ext.chart.axis.Axis',
- type: 'numeric',
- alias: [
- 'axis.numeric',
- 'axis.radial'
- ],
- // legacy charts compatibility
- requires: [
- 'Ext.chart.axis.layout.Continuous',
- 'Ext.chart.axis.segmenter.Numeric'
- ],
- config: {
- layout: 'continuous',
- segmenter: 'numeric',
- aggregator: 'double'
- }
- });
- /**
- * @class Ext.chart.axis.Numeric3D
- */
- Ext.define('Ext.chart.axis.Numeric3D', {
- extend: 'Ext.chart.axis.Axis3D',
- alias: [
- 'axis.numeric3d'
- ],
- type: 'numeric3d',
- requires: [
- 'Ext.chart.axis.layout.Continuous',
- 'Ext.chart.axis.segmenter.Numeric'
- ],
- config: {
- layout: 'continuous',
- segmenter: 'numeric',
- aggregator: 'double'
- }
- });
- /**
- * @class Ext.chart.axis.Time
- * @extends Ext.chart.axis.Numeric
- *
- * A type of axis whose units are measured in time values. Use this axis
- * for listing dates that you will want to group or dynamically change.
- * If you just want to display dates as categories then use the
- * Category class for axis instead.
- *
- * @example
- * Ext.create({
- * xtype: 'cartesian',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * store: {
- * fields: ['time', 'open', 'high', 'low', 'close'],
- * data: [{
- * 'time': new Date('Jan 1 2010').getTime(),
- * 'open': 600,
- * 'high': 614,
- * 'low': 578,
- * 'close': 590
- * }, {
- * 'time': new Date('Jan 2 2010').getTime(),
- * 'open': 590,
- * 'high': 609,
- * 'low': 580,
- * 'close': 580
- * }, {
- * 'time': new Date('Jan 3 2010').getTime(),
- * 'open': 580,
- * 'high': 602,
- * 'low': 578,
- * 'close': 602
- * }, {
- * 'time': new Date('Jan 4 2010').getTime(),
- * 'open': 602,
- * 'high': 614,
- * 'low': 586,
- * 'close': 586
- * }]
- * },
- * axes: [{
- * type: 'numeric',
- * position: 'left',
- * fields: ['open', 'high', 'low', 'close'],
- * title: {
- * text: 'Sample Values',
- * fontSize: 15
- * },
- * grid: true,
- * minimum: 560,
- * maximum: 640
- * }, {
- * type: 'time',
- * position: 'bottom',
- * fields: ['time'],
- * fromDate: new Date('Dec 31 2009'),
- * toDate: new Date('Jan 5 2010'),
- * title: {
- * text: 'Sample Values',
- * fontSize: 15
- * },
- * style: {
- * axisLine: false
- * }
- * }],
- * series: {
- * type: 'candlestick',
- * xField: 'time',
- * openField: 'open',
- * highField: 'high',
- * lowField: 'low',
- * closeField: 'close',
- * style: {
- * ohlcType: 'ohlc',
- * dropStyle: {
- * fill: 'rgb(255, 128, 128)',
- * stroke: 'rgb(255, 128, 128)',
- * lineWidth: 3
- * },
- * raiseStyle: {
- * fill: 'rgb(48, 189, 167)',
- * stroke: 'rgb(48, 189, 167)',
- * lineWidth: 3
- * }
- * }
- * }
- * });
- */
- Ext.define('Ext.chart.axis.Time', {
- extend: 'Ext.chart.axis.Numeric',
- alias: 'axis.time',
- type: 'time',
- requires: [
- 'Ext.chart.axis.layout.Continuous',
- 'Ext.chart.axis.segmenter.Time'
- ],
- config: {
- /**
- * @cfg {String} dateFormat
- * Indicates the format the date will be rendered in.
- * For example: 'M d' will render the dates as 'Jan 30'.
- * This config works by setting the {@link #renderer} config
- * to a function that uses {@link Ext.Date#format} to format the dates
- * using the given `dateFormat`.
- * If the {@link #renderer} config was set by the user, changes to this config
- * won't replace the user set renderer (until the user removes the renderer by
- * setting the `renderer` config to `null`). In this case the way the `dateFormat`
- * is used (if at all) is up to the user.
- */
- dateFormat: null,
- /**
- * @cfg {Date} fromDate The starting date for the time axis.
- */
- fromDate: null,
- /**
- * @cfg {Date} toDate The ending date for the time axis.
- */
- toDate: null,
- layout: 'continuous',
- segmenter: 'time',
- aggregator: 'time'
- },
- updateDateFormat: function(format) {
- var renderer = this.getRenderer();
- if (!renderer || renderer.isDefault) {
- renderer = function(axis, date) {
- return Ext.Date.format(new Date(date), format);
- };
- renderer.isDefault = true;
- this.setRenderer(renderer);
- this.performLayout();
- }
- },
- updateRenderer: function(renderer) {
- var dateFormat = this.getDateFormat();
- if (renderer) {
- this.performLayout();
- } else if (dateFormat) {
- // If the user removes custom `renderer` and `dateFormat` is set,
- // set the `renderer` to the default one based on `dateFormat`.
- this.updateDateFormat(dateFormat);
- }
- },
- updateFromDate: function(date) {
- this.setMinimum(+date);
- },
- updateToDate: function(date) {
- this.setMaximum(+date);
- },
- getCoordFor: function(value) {
- if (Ext.isString(value)) {
- value = new Date(value);
- }
- return +value;
- }
- });
- /**
- * @class Ext.chart.axis.Time3D
- */
- Ext.define('Ext.chart.axis.Time3D', {
- extend: 'Ext.chart.axis.Numeric3D',
- alias: 'axis.time3d',
- type: 'time3d',
- requires: [
- 'Ext.chart.axis.layout.Continuous',
- 'Ext.chart.axis.segmenter.Time'
- ],
- config: {
- /**
- * @cfg {String/Boolean} dateFormat
- * Indicates the format the date will be rendered on.
- * For example: 'M d' will render the dates as 'Jan 30', etc.
- */
- dateFormat: null,
- /**
- * @cfg {Date} fromDate The starting date for the time axis.
- */
- fromDate: null,
- /**
- * @cfg {Date} toDate The ending date for the time axis.
- */
- toDate: null,
- layout: 'continuous',
- segmenter: 'time',
- aggregator: 'time'
- },
- updateDateFormat: function(format) {
- this.setRenderer(function(axis, date) {
- return Ext.Date.format(new Date(date), format);
- });
- },
- updateFromDate: function(date) {
- this.setMinimum(+date);
- },
- updateToDate: function(date) {
- this.setMaximum(+date);
- },
- getCoordFor: function(value) {
- if (Ext.isString(value)) {
- value = new Date(value);
- }
- return +value;
- }
- });
- /**
- * @class Ext.chart.grid.HorizontalGrid3D
- * @extends Ext.chart.grid.HorizontalGrid
- *
- * Horizontal 3D Grid sprite. Used in 3D Cartesian Charts.
- */
- Ext.define('Ext.chart.grid.HorizontalGrid3D', {
- extend: 'Ext.chart.grid.HorizontalGrid',
- alias: 'grid.horizontal3d',
- inheritableStatics: {
- def: {
- processors: {
- depth: 'number'
- },
- defaults: {
- depth: 0
- }
- }
- },
- render: function(surface, ctx, rect) {
- var attr = this.attr,
- x = surface.roundPixel(attr.x),
- y = surface.roundPixel(attr.y),
- dx = surface.matrix.getDX(),
- halfLineWidth = ctx.lineWidth * 0.5,
- height = attr.height,
- depth = attr.depth,
- left, top;
- if (y <= rect[1]) {
- return;
- }
- // Horizontal stripe.
- left = rect[0] + depth - dx;
- top = y + halfLineWidth - depth;
- ctx.beginPath();
- ctx.rect(left, top, rect[2], height);
- ctx.fill();
- // Horizontal line.
- ctx.beginPath();
- ctx.moveTo(left, top);
- ctx.lineTo(left + rect[2], top);
- ctx.stroke();
- // Diagonal stripe.
- left = rect[0] + x - dx;
- top = y + halfLineWidth;
- ctx.beginPath();
- ctx.moveTo(left, top);
- ctx.lineTo(left + depth, top - depth);
- ctx.lineTo(left + depth, top - depth + height);
- ctx.lineTo(left, top + height);
- ctx.closePath();
- ctx.fill();
- // Diagonal line.
- ctx.beginPath();
- ctx.moveTo(left, top);
- ctx.lineTo(left + depth, top - depth);
- ctx.stroke();
- }
- });
- /**
- * @class Ext.chart.grid.VerticalGrid3D
- * @extends Ext.chart.grid.VerticalGrid
- *
- * Vertical 3D Grid sprite. Used in 3D Cartesian Charts.
- */
- Ext.define('Ext.chart.grid.VerticalGrid3D', {
- extend: 'Ext.chart.grid.VerticalGrid',
- alias: 'grid.vertical3d',
- inheritableStatics: {
- def: {
- processors: {
- depth: 'number'
- },
- defaults: {
- depth: 0
- }
- }
- },
- render: function(surface, ctx, clipRect) {
- var attr = this.attr,
- x = surface.roundPixel(attr.x),
- dy = surface.matrix.getDY(),
- halfLineWidth = ctx.lineWidth * 0.5,
- width = attr.width,
- depth = attr.depth,
- left, top;
- if (x >= clipRect[2]) {
- return;
- }
- // Vertical stripe.
- left = x - halfLineWidth + depth;
- top = clipRect[1] - depth - dy;
- ctx.beginPath();
- ctx.rect(left, top, width, clipRect[3]);
- ctx.fill();
- // Vertical line.
- ctx.beginPath();
- ctx.moveTo(left, top);
- ctx.lineTo(left, top + clipRect[3]);
- ctx.stroke();
- // Diagonal stripe.
- left = x - halfLineWidth;
- top = clipRect[3];
- ctx.beginPath();
- ctx.moveTo(left, top);
- ctx.lineTo(left + depth, top - depth);
- ctx.lineTo(left + depth + width, top - depth);
- ctx.lineTo(left + width, top);
- ctx.closePath();
- ctx.fill();
- // Diagonal line.
- left = x - halfLineWidth;
- top = clipRect[3];
- ctx.beginPath();
- ctx.moveTo(left, top);
- ctx.lineTo(left + depth, top - depth);
- ctx.stroke();
- }
- });
- /**
- * @class Ext.chart.interactions.CrossZoom
- * @extends Ext.chart.interactions.Abstract
- *
- * The CrossZoom interaction allows the user to zoom in on a selected area of the chart.
- *
- * @example
- * Ext.create({
- * xtype: 'cartesian',
- * renderTo: Ext.getBody(),
- * width: 600,
- * height: 400,
- * insetPadding: 40,
- * interactions: 'crosszoom',
- * store: {
- * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
- * data: [{
- * 'name': 'metric one',
- * 'data1': 10,
- * 'data2': 12,
- * 'data3': 14,
- * 'data4': 8,
- * 'data5': 13
- * }, {
- * 'name': 'metric two',
- * 'data1': 7,
- * 'data2': 8,
- * 'data3': 16,
- * 'data4': 10,
- * 'data5': 3
- * }, {
- * 'name': 'metric three',
- * 'data1': 5,
- * 'data2': 2,
- * 'data3': 14,
- * 'data4': 12,
- * 'data5': 7
- * }, {
- * 'name': 'metric four',
- * 'data1': 2,
- * 'data2': 14,
- * 'data3': 6,
- * 'data4': 1,
- * 'data5': 23
- * }, {
- * 'name': 'metric five',
- * 'data1': 27,
- * 'data2': 38,
- * 'data3': 36,
- * 'data4': 13,
- * 'data5': 33
- * }]
- * },
- * axes: [{
- * type: 'numeric',
- * position: 'left',
- * fields: ['data1'],
- * title: {
- * text: 'Sample Values',
- * fontSize: 15
- * },
- * grid: true,
- * minimum: 0
- * }, {
- * type: 'category',
- * position: 'bottom',
- * fields: ['name'],
- * title: {
- * text: 'Sample Values',
- * fontSize: 15
- * }
- * }],
- * series: [{
- * type: 'line',
- * highlight: {
- * size: 7,
- * radius: 7
- * },
- * style: {
- * stroke: 'rgb(143,203,203)'
- * },
- * xField: 'name',
- * yField: 'data1',
- * marker: {
- * type: 'path',
- * path: ['M', - 2, 0, 0, 2, 2, 0, 0, - 2, 'Z'],
- * stroke: 'blue',
- * lineWidth: 0
- * }
- * }, {
- * type: 'line',
- * highlight: {
- * size: 7,
- * radius: 7
- * },
- * fill: true,
- * xField: 'name',
- * yField: 'data3',
- * marker: {
- * type: 'circle',
- * radius: 4,
- * lineWidth: 0
- * }
- * }]
- * });
- */
- Ext.define('Ext.chart.interactions.CrossZoom', {
- extend: 'Ext.chart.interactions.Abstract',
- type: 'crosszoom',
- alias: 'interaction.crosszoom',
- isCrossZoom: true,
- config: {
- /**
- * @cfg {Object/Array} axes
- * Specifies which axes should be made navigable. The config value can take the following
- * formats:
- *
- * - An Object whose keys correspond to the {@link Ext.chart.axis.Axis#position position}
- * of each axis that should be made navigable. Each key's value can either be an Object
- * with further configuration options for each axis or simply `true` for a default
- * set of options.
- * {
- * type: 'crosszoom',
- * axes: {
- * left: {
- * maxZoom: 5,
- * allowPan: false
- * },
- * bottom: true
- * }
- * }
- *
- * If using the full Object form, the following options can be specified for each axis:
- *
- * - minZoom (Number) A minimum zoom level for the axis. Defaults to `1` which is its
- * natural size.
- * - maxZoom (Number) A maximum zoom level for the axis. Defaults to `10`.
- * - startZoom (Number) A starting zoom level for the axis. Defaults to `1`.
- * - allowZoom (Boolean) Whether zooming is allowed for the axis. Defaults to `true`.
- * - allowPan (Boolean) Whether panning is allowed for the axis. Defaults to `true`.
- * - startPan (Boolean) A starting panning offset for the axis. Defaults to `0`.
- *
- * - An Array of strings, each one corresponding to the
- * {@link Ext.chart.axis.Axis#position position} of an axis that should be made navigable.
- * The default options will be used for each named axis.
- *
- * {
- * type: 'crosszoom',
- * axes: ['left', 'bottom']
- * }
- *
- * If the `axes` config is not specified, it will default to making all axes navigable
- * with the default axis options.
- */
- axes: true,
- gestures: {
- dragstart: 'onGestureStart',
- drag: 'onGesture',
- dragend: 'onGestureEnd',
- dblclick: 'onDoubleTap'
- },
- undoButton: {}
- },
- stopAnimationBeforeSync: false,
- zoomAnimationInProgress: false,
- constructor: function() {
- this.callParent(arguments);
- this.zoomHistory = [];
- },
- applyAxes: function(axesConfig) {
- var result = {};
- if (axesConfig === true) {
- return {
- top: {},
- right: {},
- bottom: {},
- left: {}
- };
- } else if (Ext.isArray(axesConfig)) {
- // array of axis names - translate to full object form
- result = {};
- Ext.each(axesConfig, function(axis) {
- result[axis] = {};
- });
- } else if (Ext.isObject(axesConfig)) {
- Ext.iterate(axesConfig, function(key, val) {
- // axis name with `true` value -> translate to object
- if (val === true) {
- result[key] = {};
- } else if (val !== false) {
- result[key] = val;
- }
- });
- }
- return result;
- },
- applyUndoButton: function(button, oldButton) {
- var me = this;
- if (oldButton) {
- oldButton.destroy();
- }
- if (button) {
- return Ext.create('Ext.Button', Ext.apply({
- cls: [],
- text: 'Undo Zoom',
- disabled: true,
- handler: function() {
- me.undoZoom();
- }
- }, button));
- }
- },
- getSurface: function() {
- return this.getChart() && this.getChart().getSurface('overlay');
- },
- setSeriesOpacity: function(opacity) {
- var surface = this.getChart() && this.getChart().getSurface('series');
- if (surface) {
- surface.element.setStyle('opacity', opacity);
- }
- },
- onGestureStart: function(e) {
- var me = this,
- chart = me.getChart(),
- surface = me.getSurface(),
- rect = chart.getInnerRect(),
- innerPadding = chart.getInnerPadding(),
- minX = innerPadding.left,
- maxX = minX + rect[2],
- minY = innerPadding.top,
- maxY = minY + rect[3],
- xy = chart.getEventXY(e),
- x = xy[0],
- y = xy[1];
- e.claimGesture();
- if (me.zoomAnimationInProgress) {
- return;
- }
- if (x > minX && x < maxX && y > minY && y < maxY) {
- me.gestureEvent = 'drag';
- me.lockEvents(me.gestureEvent);
- me.startX = x;
- me.startY = y;
- me.selectionRect = surface.add({
- type: 'rect',
- globalAlpha: 0.5,
- fillStyle: 'rgba(80,80,140,0.5)',
- strokeStyle: 'rgba(80,80,140,1)',
- lineWidth: 2,
- x: x,
- y: y,
- width: 0,
- height: 0,
- zIndex: 10000
- });
- me.setSeriesOpacity(0.8);
- return false;
- }
- },
- onGesture: function(e) {
- var me = this;
- if (me.zoomAnimationInProgress) {
- return;
- }
- if (me.getLocks()[me.gestureEvent] === me) {
- // eslint-disable-next-line vars-on-top, one-var
- var chart = me.getChart(),
- surface = me.getSurface(),
- rect = chart.getInnerRect(),
- innerPadding = chart.getInnerPadding(),
- minX = innerPadding.left,
- maxX = minX + rect[2],
- minY = innerPadding.top,
- maxY = minY + rect[3],
- xy = chart.getEventXY(e),
- x = xy[0],
- y = xy[1];
- if (x < minX) {
- x = minX;
- } else if (x > maxX) {
- x = maxX;
- }
- if (y < minY) {
- y = minY;
- } else if (y > maxY) {
- y = maxY;
- }
- me.selectionRect.setAttributes({
- width: x - me.startX,
- height: y - me.startY
- });
- if (Math.abs(me.startX - x) < 11 || Math.abs(me.startY - y) < 11) {
- me.selectionRect.setAttributes({
- globalAlpha: 0.5
- });
- } else {
- me.selectionRect.setAttributes({
- globalAlpha: 1
- });
- }
- surface.renderFrame();
- return false;
- }
- },
- onGestureEnd: function(e) {
- var me = this;
- if (me.zoomAnimationInProgress) {
- return;
- }
- if (me.getLocks()[me.gestureEvent] === me) {
- // eslint-disable-next-line vars-on-top, one-var
- var chart = me.getChart(),
- surface = me.getSurface(),
- rect = chart.getInnerRect(),
- innerPadding = chart.getInnerPadding(),
- minX = innerPadding.left,
- maxX = minX + rect[2],
- minY = innerPadding.top,
- maxY = minY + rect[3],
- rectWidth = rect[2],
- rectHeight = rect[3],
- xy = chart.getEventXY(e),
- x = xy[0],
- y = xy[1];
- if (x < minX) {
- x = minX;
- } else if (x > maxX) {
- x = maxX;
- }
- if (y < minY) {
- y = minY;
- } else if (y > maxY) {
- y = maxY;
- }
- if (Math.abs(me.startX - x) < 11 || Math.abs(me.startY - y) < 11) {
- surface.remove(me.selectionRect);
- } else {
- me.zoomBy([
- Math.min(me.startX, x) / rectWidth,
- 1 - Math.max(me.startY, y) / rectHeight,
- Math.max(me.startX, x) / rectWidth,
- 1 - Math.min(me.startY, y) / rectHeight
- ]);
- me.selectionRect.setAttributes({
- x: Math.min(me.startX, x),
- y: Math.min(me.startY, y),
- width: Math.abs(me.startX - x),
- height: Math.abs(me.startY - y)
- });
- me.selectionRect.setAnimation(chart.getAnimation() || {
- duration: 0
- });
- me.selectionRect.setAttributes({
- globalAlpha: 0,
- x: 0,
- y: 0,
- width: rectWidth,
- height: rectHeight
- });
- me.zoomAnimationInProgress = true;
- chart.suspendThicknessChanged();
- me.selectionRect.getAnimation().on('animationend', function() {
- chart.resumeThicknessChanged();
- surface.remove(me.selectionRect);
- me.selectionRect = null;
- me.zoomAnimationInProgress = false;
- });
- }
- surface.renderFrame();
- me.sync();
- me.unlockEvents(me.gestureEvent);
- me.setSeriesOpacity(1);
- if (!me.zoomAnimationInProgress) {
- surface.remove(me.selectionRect);
- me.selectionRect = null;
- }
- }
- },
- zoomBy: function(rect) {
- var me = this,
- axisConfigs = me.getAxes(),
- chart = me.getChart(),
- axes = chart.getAxes(),
- isRtl = chart.getInherited().rtl,
- zoomMap = {},
- config, axis, x1, x2, isSide, oldRange, i;
- if (isRtl) {
- rect = rect.slice();
- x1 = 1 - rect[0];
- x2 = 1 - rect[2];
- rect[0] = Math.min(x1, x2);
- rect[2] = Math.max(x1, x2);
- }
- for (i = 0; i < axes.length; i++) {
- axis = axes[i];
- config = axisConfigs[axis.getPosition()];
- if (config && config.allowZoom !== false) {
- isSide = axis.isSide();
- oldRange = axis.getVisibleRange();
- zoomMap[axis.getId()] = oldRange.slice(0);
- if (!isSide) {
- axis.setVisibleRange([
- (oldRange[1] - oldRange[0]) * rect[0] + oldRange[0],
- (oldRange[1] - oldRange[0]) * rect[2] + oldRange[0]
- ]);
- } else {
- axis.setVisibleRange([
- (oldRange[1] - oldRange[0]) * rect[1] + oldRange[0],
- (oldRange[1] - oldRange[0]) * rect[3] + oldRange[0]
- ]);
- }
- }
- }
- me.zoomHistory.push(zoomMap);
- me.getUndoButton().setDisabled(false);
- },
- undoZoom: function() {
- var zoomMap = this.zoomHistory.pop(),
- axes = this.getChart().getAxes(),
- axis, i;
- if (zoomMap) {
- for (i = 0; i < axes.length; i++) {
- axis = axes[i];
- if (zoomMap[axis.getId()]) {
- axis.setVisibleRange(zoomMap[axis.getId()]);
- }
- }
- }
- this.getUndoButton().setDisabled(this.zoomHistory.length === 0);
- this.sync();
- },
- onDoubleTap: function(e) {
- this.undoZoom();
- },
- destroy: function() {
- this.setUndoButton(null);
- this.callParent();
- }
- });
- /**
- * The Crosshair interaction allows the user to get precise values for a specific point
- * on the chart. The values are obtained by single-touch dragging on the chart.
- *
- * @example
- * Ext.create('Ext.Container', {
- * renderTo: Ext.getBody(),
- * width: 600,
- * height: 400,
- * layout: 'fit',
- * items: {
- * xtype: 'cartesian',
- * innerPadding: 20,
- * interactions: {
- * type: 'crosshair',
- * axes: {
- * left: {
- * label: {
- * fillStyle: 'white'
- * },
- * rect: {
- * fillStyle: 'brown',
- * radius: 6
- * }
- * },
- * bottom: {
- * label: {
- * fontSize: '14px',
- * fontWeight: 'bold'
- * }
- * }
- * },
- * lines: {
- * horizontal: {
- * strokeStyle: 'brown',
- * lineWidth: 2,
- * lineDash: [20, 2, 2, 2, 2, 2, 2, 2]
- * }
- * }
- * },
- * store: {
- * fields: ['name', 'data'],
- * data: [
- * {name: 'apple', data: 300},
- * {name: 'orange', data: 900},
- * {name: 'banana', data: 800},
- * {name: 'pear', data: 400},
- * {name: 'grape', data: 500}
- * ]
- * },
- * axes: [{
- * type: 'numeric',
- * position: 'left',
- * fields: ['data'],
- * title: {
- * text: 'Value',
- * fontSize: 15
- * },
- * grid: true,
- * label: {
- * rotationRads: -Math.PI / 4
- * }
- * }, {
- * type: 'category',
- * position: 'bottom',
- * fields: ['name'],
- * title: {
- * text: 'Category',
- * fontSize: 15
- * }
- * }],
- * series: {
- * type: 'line',
- * style: {
- * strokeStyle: 'black'
- * },
- * xField: 'name',
- * yField: 'data',
- * marker: {
- * type: 'circle',
- * radius: 5,
- * fillStyle: 'lightblue'
- * }
- * }
- * }
- * });
- */
- Ext.define('Ext.chart.interactions.Crosshair', {
- extend: 'Ext.chart.interactions.Abstract',
- requires: [
- 'Ext.chart.grid.HorizontalGrid',
- 'Ext.chart.grid.VerticalGrid',
- 'Ext.chart.CartesianChart',
- 'Ext.chart.axis.layout.Discrete'
- ],
- type: 'crosshair',
- alias: 'interaction.crosshair',
- config: {
- /**
- * @cfg {Object} axes
- * Specifies label text and label rect configs on per axis basis or as a single config
- * for all axes.
- *
- * {
- * type: 'crosshair',
- * axes: {
- * label: { fillStyle: 'white' },
- * rect: { fillStyle: 'maroon'}
- * }
- * }
- *
- * In case per axis configuration is used, an object with keys corresponding
- * to the {@link Ext.chart.axis.Axis#position position} must be provided.
- *
- * {
- * type: 'crosshair',
- * axes: {
- * left: {
- * label: { fillStyle: 'white' },
- * rect: {
- * fillStyle: 'maroon',
- * radius: 4
- * }
- * },
- * bottom: {
- * label: {
- * fontSize: '14px',
- * fontWeight: 'bold'
- * },
- * rect: { fillStyle: 'white' }
- * }
- * }
- *
- * If the `axes` config is not specified, the following defaults will be used:
- * - `label` will use values from the {@link Ext.chart.axis.Axis#label label} config.
- * - `rect` will use the 'white' fillStyle.
- */
- axes: {
- top: {
- label: {},
- rect: {}
- },
- right: {
- label: {},
- rect: {}
- },
- bottom: {
- label: {},
- rect: {}
- },
- left: {
- label: {},
- rect: {}
- }
- },
- /**
- * @cfg {Object} lines
- * Specifies attributes of horizontal and vertical lines that make up the crosshair.
- * If this config is missing, black dashed lines will be used.
- *
- * {
- * horizontal: {
- * strokeStyle: 'red',
- * lineDash: [] // solid line
- * },
- * vertical: {
- * lineWidth: 2,
- * lineDash: [15, 5, 5, 5]
- * }
- * }
- */
- lines: {
- horizontal: {
- strokeStyle: 'black',
- lineDash: [
- 5,
- 5
- ]
- },
- vertical: {
- strokeStyle: 'black',
- lineDash: [
- 5,
- 5
- ]
- }
- },
- /**
- * @cfg {String} gesture
- * Specifies which gesture should be used for starting/maintaining/ending the interaction.
- */
- gesture: 'drag'
- },
- applyAxes: function(axesConfig, oldAxesConfig) {
- return Ext.merge(oldAxesConfig || {}, axesConfig);
- },
- applyLines: function(linesConfig, oldLinesConfig) {
- return Ext.merge(oldLinesConfig || {}, linesConfig);
- },
- updateChart: function(chart) {
- if (chart && !chart.isCartesian) {
- Ext.raise("Crosshair interaction can only be used on cartesian charts.");
- }
- this.callParent(arguments);
- },
- getGestures: function() {
- var me = this,
- gestures = {},
- gesture = me.getGesture();
- gestures[gesture] = 'onGesture';
- gestures[gesture + 'start'] = 'onGestureStart';
- gestures[gesture + 'end'] = 'onGestureEnd';
- gestures[gesture + 'cancel'] = 'onGestureCancel';
- return gestures;
- },
- onGestureStart: function(e) {
- var me = this,
- chart = me.getChart(),
- axesTheme = chart.getTheme().getAxis(),
- axisTheme,
- surface = chart.getSurface('overlay'),
- rect = chart.getInnerRect(),
- chartWidth = rect[2],
- chartHeight = rect[3],
- xy = chart.getEventXY(e),
- x = xy[0],
- y = xy[1],
- axes = chart.getAxes(),
- axesConfig = me.getAxes(),
- linesConfig = me.getLines(),
- axis, axisSurface, axisRect, axisWidth, axisHeight, axisPosition, axisAlignment, axisLabel, axisLabelConfig, crosshairLabelConfig, tickPadding, axisSprite, attr, lineWidth, halfLineWidth, title, titleBBox, horizontalLineCfg, verticalLineCfg, i;
- e.claimGesture();
- if (x > 0 && x < chartWidth && y > 0 && y < chartHeight) {
- me.lockEvents(me.getGesture());
- horizontalLineCfg = Ext.apply({
- xclass: 'Ext.chart.grid.HorizontalGrid',
- x: 0,
- y: y,
- width: chartWidth
- }, linesConfig.horizontal);
- verticalLineCfg = Ext.apply({
- xclass: 'Ext.chart.grid.VerticalGrid',
- x: x,
- y: 0,
- height: chartHeight
- }, linesConfig.vertical);
- me.axesLabels = me.axesLabels || {};
- for (i = 0; i < axes.length; i++) {
- axis = axes[i];
- axisSurface = axis.getSurface();
- axisRect = axisSurface.getRect();
- axisSprite = axis.getSprites()[0];
- axisWidth = axisRect[2];
- axisHeight = axisRect[3];
- axisPosition = axis.getPosition();
- axisAlignment = axis.getAlignment();
- title = axis.getTitle();
- titleBBox = title && title.attr.text !== '' && title.getBBox();
- attr = axisSprite.attr;
- lineWidth = attr.axisLine ? attr.lineWidth : 0;
- halfLineWidth = lineWidth / 2;
- tickPadding = Math.max(attr.majorTickSize, attr.minorTickSize) + lineWidth;
- axisLabel = me.axesLabels[axisPosition] = axisSurface.add({
- type: 'composite'
- });
- axisLabel.labelRect = axisLabel.addSprite(Ext.apply({
- type: 'rect',
- fillStyle: 'white',
- x: axisPosition === 'right' ? lineWidth : 0,
- y: axisPosition === 'bottom' ? lineWidth : 0,
- width: axisWidth - lineWidth - (axisAlignment === 'vertical' && titleBBox ? titleBBox.width : 0),
- height: axisHeight - lineWidth - (axisAlignment === 'horizontal' && titleBBox ? titleBBox.height : 0),
- translationX: axisPosition === 'left' && titleBBox ? titleBBox.width : 0,
- translationY: axisPosition === 'top' && titleBBox ? titleBBox.height : 0
- }, axesConfig.rect || axesConfig[axisPosition].rect));
- if (axisAlignment === 'vertical' && !verticalLineCfg.strokeStyle) {
- verticalLineCfg.strokeStyle = attr.strokeStyle;
- }
- if (axisAlignment === 'horizontal' && !horizontalLineCfg.strokeStyle) {
- horizontalLineCfg.strokeStyle = attr.strokeStyle;
- }
- axisTheme = Ext.merge({}, axesTheme.defaults, axesTheme[axisPosition]);
- axisLabelConfig = Ext.apply({}, axis.config.label, axisTheme.label);
- crosshairLabelConfig = axesConfig.label || axesConfig[axisPosition].label;
- axisLabel.labelText = axisLabel.addSprite(Ext.apply(axisLabelConfig, crosshairLabelConfig, {
- type: 'text',
- x: me.calculateLabelTextPoint(false, axisPosition, tickPadding, titleBBox, axisWidth, halfLineWidth),
- y: me.calculateLabelTextPoint(true, axisPosition, tickPadding, titleBBox, axisHeight, halfLineWidth)
- }));
- }
- me.horizontalLine = surface.add(horizontalLineCfg);
- me.verticalLine = surface.add(verticalLineCfg);
- return false;
- }
- },
- onGesture: function(e) {
- var me = this;
- if (me.getLocks()[me.getGesture()] !== me) {
- return;
- }
- // eslint-disable-next-line vars-on-top, one-var
- var chart = me.getChart(),
- surface = chart.getSurface('overlay'),
- rect = Ext.Array.slice(chart.getInnerRect()),
- padding = chart.getInnerPadding(),
- px = padding.left,
- py = padding.top,
- chartWidth = rect[2],
- chartHeight = rect[3],
- xy = chart.getEventXY(e),
- x = xy[0],
- y = xy[1],
- axes = chart.getAxes(),
- axis, axisPosition, axisAlignment, axisSurface, axisSprite, axisMatrix, axisLayoutContext, axisSegmenter, axisLabel, labelBBox, textPadding, xx, yy, dx, dy, xValue, yValue, text, i;
- if (x < 0) {
- x = 0;
- } else if (x > chartWidth) {
- x = chartWidth;
- }
- if (y < 0) {
- y = 0;
- } else if (y > chartHeight) {
- y = chartHeight;
- }
- x += px;
- y += py;
- for (i = 0; i < axes.length; i++) {
- axis = axes[i];
- axisPosition = axis.getPosition();
- axisAlignment = axis.getAlignment();
- axisSurface = axis.getSurface();
- axisSprite = axis.getSprites()[0];
- axisMatrix = axisSprite.attr.matrix;
- textPadding = axisSprite.attr.textPadding * 2;
- axisLabel = me.axesLabels[axisPosition];
- axisLayoutContext = axisSprite.getLayoutContext();
- axisSegmenter = axis.getSegmenter();
- if (axisLabel) {
- if (axisAlignment === 'vertical') {
- yy = axisMatrix.getYY();
- dy = axisMatrix.getDY();
- yValue = (y - dy - py) / yy;
- if (axis.getLayout() instanceof Ext.chart.axis.layout.Discrete) {
- y = Math.round(yValue) * yy + dy + py;
- yValue = axisSegmenter.from(Math.round(yValue));
- yValue = axisSprite.attr.data[yValue];
- } else {
- yValue = axisSegmenter.from(yValue);
- }
- text = axisSegmenter.renderer(yValue, axisLayoutContext);
- axisLabel.setAttributes({
- translationY: y - py
- });
- axisLabel.labelText.setAttributes({
- text: text
- });
- labelBBox = axisLabel.labelText.getBBox();
- axisLabel.labelRect.setAttributes({
- height: labelBBox.height + textPadding,
- y: -(labelBBox.height + textPadding) / 2
- });
- axisSurface.renderFrame();
- } else {
- xx = axisMatrix.getXX();
- dx = axisMatrix.getDX();
- xValue = (x - dx - px) / xx;
- if (axis.getLayout() instanceof Ext.chart.axis.layout.Discrete) {
- x = Math.round(xValue) * xx + dx + px;
- xValue = axisSegmenter.from(Math.round(xValue));
- xValue = axisSprite.attr.data[xValue];
- } else {
- xValue = axisSegmenter.from(xValue);
- }
- text = axisSegmenter.renderer(xValue, axisLayoutContext);
- axisLabel.setAttributes({
- translationX: x - px
- });
- axisLabel.labelText.setAttributes({
- text: text
- });
- labelBBox = axisLabel.labelText.getBBox();
- axisLabel.labelRect.setAttributes({
- width: labelBBox.width + textPadding,
- x: -(labelBBox.width + textPadding) / 2
- });
- axisSurface.renderFrame();
- }
- }
- }
- me.horizontalLine.setAttributes({
- y: y,
- strokeStyle: axisSprite.attr.strokeStyle
- });
- me.verticalLine.setAttributes({
- x: x,
- strokeStyle: axisSprite.attr.strokeStyle
- });
- surface.renderFrame();
- return false;
- },
- onGestureEnd: function(e) {
- var me = this,
- chart = me.getChart(),
- surface = chart.getSurface('overlay'),
- axes = chart.getAxes(),
- axis, axisPosition, axisSurface, axisLabel, i;
- surface.remove(me.verticalLine);
- surface.remove(me.horizontalLine);
- for (i = 0; i < axes.length; i++) {
- axis = axes[i];
- axisPosition = axis.getPosition();
- axisSurface = axis.getSurface();
- axisLabel = me.axesLabels[axisPosition];
- if (axisLabel) {
- delete me.axesLabels[axisPosition];
- axisSurface.remove(axisLabel);
- }
- axisSurface.renderFrame();
- }
- surface.renderFrame();
- me.unlockEvents(me.getGesture());
- },
- onGestureCancel: function(e) {
- this.onGestureEnd(e);
- },
- privates: {
- vertMap: {
- top: 'start',
- bottom: 'end'
- },
- horzMap: {
- left: 'start',
- right: 'end'
- },
- calculateLabelTextPoint: function(vertical, position, tickPadding, titleBBox, axisSize, halfLineWidth) {
- var titlePadding, sizeProp, pointProp;
- if (vertical) {
- pointProp = 'y';
- sizeProp = 'height';
- position = this.vertMap[position];
- } else {
- pointProp = 'x';
- sizeProp = 'width';
- position = this.horzMap[position];
- }
- switch (position) {
- case 'start':
- titlePadding = titleBBox ? titleBBox[pointProp] + titleBBox[sizeProp] : 0;
- return titlePadding + (axisSize - titlePadding - tickPadding) / 2 - halfLineWidth;
- case 'end':
- titlePadding = titleBBox ? axisSize - titleBBox[pointProp] : 0;
- return tickPadding + (axisSize - tickPadding - titlePadding) / 2 + halfLineWidth;
- default:
- return 0;
- }
- }
- }
- });
- /**
- * @class Ext.chart.interactions.ItemHighlight
- * @extends Ext.chart.interactions.Abstract
- *
- * The 'itemhighlight' interaction allows the user to highlight series items in the chart.
- */
- Ext.define('Ext.chart.interactions.ItemHighlight', {
- extend: 'Ext.chart.interactions.Abstract',
- type: 'itemhighlight',
- alias: 'interaction.itemhighlight',
- isItemHighlight: true,
- config: {
- gestures: {
- tap: 'onTapGesture',
- mousemove: 'onMouseMoveGesture',
- mousedown: 'onMouseDownGesture',
- mouseup: 'onMouseUpGesture',
- mouseleave: 'onMouseUpGesture'
- },
- /**
- * @cfg {Boolean} [sticky=false]
- * Disables mouse tracking.
- * Series items will only be highlighted/unhighlighted on mouse click.
- * This config has no effect on touch devices.
- */
- sticky: false,
- /**
- * @cfg {Boolean} [multiTooltips=false]
- * Enable displaying multiple tooltips for overlapping or adjacent series items within
- * {@link Ext.chart.series.Line#selectionTolerance} radius.
- * Default is to display a tooltip only for the last series item rendered.
- * When multiple tooltips are displayed, they may overlap partially or completely;
- * it is up to the developer to ensure tooltip positioning is satisfactory.
- *
- * @since 6.6.0
- */
- multiTooltips: false
- },
- constructor: function(config) {
- this.callParent([
- config
- ]);
- this.stickyHighlightItem = null;
- this.tooltipItems = [];
- },
- destroy: function() {
- this.stickyHighlightItem = this.tooltipItems = null;
- this.callParent();
- },
- onMouseMoveGesture: function(e) {
- var me = this,
- tooltipItems = me.tooltipItems,
- isMousePointer = e.pointerType === 'mouse',
- tooltips = [],
- item, oldItem, items, tooltip, oldTooltip, i, len, j, jLen;
- if (me.getSticky()) {
- return true;
- }
- if (isMousePointer && me.stickyHighlightItem) {
- me.stickyHighlightItem = null;
- me.highlight(null);
- }
- if (me.isDragging) {
- if (tooltipItems.length && isMousePointer) {
- me.hideTooltips(tooltipItems);
- tooltipItems.length = 0;
- }
- } else if (!me.stickyHighlightItem) {
- if (me.getMultiTooltips()) {
- items = me.getItemsForEvent(e);
- } else {
- item = me.getItemForEvent(e);
- items = item ? [
- item
- ] : [];
- }
- for (i = 0 , len = items.length; i < len; i++) {
- item = items[i];
- // Items are returned top to down, so first item is the top one.
- // Chart can only have one highlighted item.
- if (i === 0 && item !== me.getChart().getHighlightItem()) {
- me.highlight(item);
- me.sync();
- }
- tooltip = item.series.getTooltip();
- if (tooltip) {
- tooltips.push(tooltip);
- }
- }
- if (isMousePointer) {
- // If we detected a mouse hit, show/refresh the tooltip
- if (items.length) {
- for (i = 0 , len = items.length; i < len; i++) {
- item = items[i];
- tooltip = item.series.getTooltip();
- if (tooltip) {
- // If there were different previously active items
- // that are not going to be included in current active items,
- // ask them to hide their tooltips. Unless those are
- // the same tooltip instances that we are about to show,
- // in which case we are just going to reposition them.
- for (j = 0 , jLen = tooltipItems.length; j < jLen; j++) {
- oldItem = tooltipItems[j];
- if (!Ext.Array.contains(items, oldItem)) {
- oldTooltip = oldItem.series.getTooltip();
- if (!Ext.Array.contains(tooltips, oldTooltip)) {
- oldItem.series.hideTooltip(oldItem, true);
- }
- }
- }
- if (tooltip.getTrackMouse()) {
- item.series.showTooltip(item, e);
- } else {
- me.showUntracked(item);
- }
- }
- }
- me.tooltipItems = items;
- } else // No mouse hit - schedule a hide for hideDelay ms.
- // If pointer enters another item within that time,
- // there will be no flickery reshow.
- {
- me.hideTooltips(tooltipItems);
- tooltipItems.length = 0;
- }
- }
- return false;
- }
- },
- highlight: function(item) {
- // This is its own function to make it easier for subclasses
- // to enhance the behavior. An alternative would be to listen
- // for the chart's 'itemhighlight' event.
- this.getChart().setHighlightItem(item);
- },
- showTooltip: function(e, item) {
- item.series.showTooltip(item, e);
- Ext.Array.include(this.tooltipItems, item);
- },
- showUntracked: function(item) {
- var marker = item.sprite.getMarker(item.category),
- surface, surfaceXY, isInverseY, itemBBox, matrix;
- if (marker) {
- surface = marker.getSurface();
- isInverseY = surface.matrix.elements[3] < 0;
- surfaceXY = surface.element.getXY();
- itemBBox = Ext.clone(marker.getBBoxFor(item.index));
- if (isInverseY) {
- // The item.category for bar series will be 'items'.
- // The item.category for line series will be 'markers'.
- // 'items' are in the 'series' surface, which is flipped vertically
- // for cartesian series.
- // 'markers' are in the 'overlay' surface, which isn't flipped.
- // So for 'markers' we already have the bbox in a coordinate system
- // with the origin at the top-left of the surface, but for 'items'
- // we need to do a conversion.
- if (surface.getInherited().rtl) {
- matrix = surface.inverseMatrix.clone().flipX().translate(item.sprite.attr.innerWidth, 0, true);
- } else {
- matrix = surface.inverseMatrix;
- }
- itemBBox = matrix.transformBBox(itemBBox);
- }
- itemBBox.x += surfaceXY[0];
- itemBBox.y += surfaceXY[1];
- item.series.showTooltipAt(item, itemBBox.x + itemBBox.width * 0.5, itemBBox.y + itemBBox.height * 0.5);
- }
- },
- onMouseDownGesture: function() {
- this.isDragging = true;
- },
- onMouseUpGesture: function() {
- this.isDragging = false;
- },
- isSameItem: function(a, b) {
- return a && b && a.series === b.series && a.field === b.field && a.index === b.index;
- },
- onTapGesture: function(e) {
- var me = this,
- item;
- // A click/tap on an item makes its highlight sticky.
- // It requires another click/tap to unhighlight.
- if (e.pointerType === 'mouse' && !me.getSticky()) {
- return;
- }
- item = me.getItemForEvent(e);
- if (me.isSameItem(me.stickyHighlightItem, item)) {
- item = null;
- }
- // toggle
- me.stickyHighlightItem = item;
- me.highlight(item);
- },
- privates: {
- hideTooltips: function(items, force) {
- var item, i, len;
- items = Ext.isArray(items) ? items : [
- items
- ];
- for (i = 0 , len = items.length; i < len; i++) {
- item = items[i];
- if (item && item.series && !item.series.destroyed) {
- item.series.hideTooltip(item, force);
- }
- }
- }
- }
- });
- /**
- * @class Ext.chart.interactions.ItemEdit
- * @extends Ext.chart.interactions.ItemHighlight
- *
- * The 'itemedit' interaction allows the user to edit store data
- * by dragging series items in the chart.
- *
- * The 'itemedit' interaction extends the
- * {@link Ext.chart.interactions.ItemHighlight 'itemhighlight'} interaction,
- * so it also acts like one. If you need both interactions in a single chart,
- * 'itemedit' should be sufficient. Hovering/tapping will result in highlighting,
- * and dragging will result in editing.
- */
- Ext.define('Ext.chart.interactions.ItemEdit', {
- extend: 'Ext.chart.interactions.ItemHighlight',
- requires: [
- 'Ext.tip.ToolTip'
- ],
- type: 'itemedit',
- alias: 'interaction.itemedit',
- isItemEdit: true,
- config: {
- /**
- * @cfg {Object} [style=null]
- * The style that will be applied to the series item on dragging.
- * By default, series item will have no fill,
- * and will have a dashed stroke of the same color.
- */
- style: null,
- /**
- * @cfg {Function/String} [renderer=null]
- * A function that returns style attributes for the item that's being dragged.
- * This is useful if you want to give a visual feedback to the user when
- * they dragged to a certain point.
- *
- * @param {Object} [data] The following properties are available:
- *
- * @param {Object} data.target The object containing the xField/xValue or/and
- * yField/yValue properties, where the xField/yField specify the store records
- * being edited and the xValue/yValue the target values to be set when
- * the interaction ends. The object also contains the 'index' of the record
- * being edited.
- * @param {Object} data.style The style that is going to be used for the dragged item.
- * The attributes returned by the renderer will be applied on top of this style.
- * @param {Object} data.item The series item being dragged.
- * This is actually the {@link Ext.chart.AbstractChart#highlightItem}.
- *
- * @return {Object} The style attributes to be set on the dragged item.
- */
- renderer: null,
- /**
- * @cfg {Object/Boolean} [tooltip=true]
- */
- tooltip: true,
- gestures: {
- dragstart: 'onDragStart',
- drag: 'onDrag',
- dragend: 'onDragEnd'
- },
- cursors: {
- ewResize: 'ew-resize',
- nsResize: 'ns-resize',
- move: 'move'
- }
- },
- /**
- * @private
- * @cfg {Boolean} [sticky=false]
- */
- /**
- * @event beginitemedit
- * Fires when item edit operation (dragging) begins.
- * @param {Ext.chart.AbstractChart} chart The chart the interaction belongs to.
- * @param {Ext.chart.interactions.ItemEdit} interaction The interaction.
- * @param {Object} item The item that is about to be edited.
- */
- /**
- * @event enditemedit
- * Fires when item edit operation (dragging) ends.
- * @param {Ext.chart.AbstractChart} chart The chart the interaction belongs to.
- * @param {Ext.chart.interactions.ItemEdit} interaction The interaction.
- * @param {Object} item The item that was edited.
- * @param {Object} target The object containing target values the were used.
- */
- item: null,
- // Item being edited.
- applyTooltip: function(tooltip) {
- var config;
- if (tooltip) {
- config = Ext.apply({}, tooltip, {
- renderer: this.defaultTooltipRenderer,
- constrainPosition: true,
- shrinkWrapDock: true,
- autoHide: true,
- trackMouse: true,
- mouseOffset: [
- 20,
- 20
- ]
- });
- tooltip = new Ext.tip.ToolTip(config);
- }
- return tooltip;
- },
- defaultTooltipRenderer: function(tooltip, item, target, e) {
- var parts = [];
- if (target.xField) {
- parts.push(target.xField + ': ' + target.xValue);
- }
- if (target.yField) {
- parts.push(target.yField + ': ' + target.yValue);
- }
- tooltip.setHtml(parts.join('<br>'));
- },
- onDragStart: function(e) {
- var me = this,
- chart = me.getChart(),
- item = chart.getHighlightItem();
- e.claimGesture();
- if (item) {
- chart.fireEvent('beginitemedit', chart, me, me.item = item);
- // If ItemEdit interaction comes before other interactions
- // in the chart's 'interactions' config, this will
- // prevent other interactions hijacking the 'dragstart'
- // event. We only stop event propagation is there's
- // an item to edit under cursor/finger, otherwise we
- // let other interactions (e.g. 'panzoom') handle the event.
- return false;
- }
- },
- onDrag: function(e) {
- var me = this,
- chart = me.getChart(),
- item = chart.getHighlightItem(),
- type = item && item.sprite.type;
- if (item) {
- switch (type) {
- case 'barSeries':
- return me.onDragBar(e);
- case 'scatterSeries':
- return me.onDragScatter(e);
- }
- }
- },
- highlight: function(item) {
- var me = this,
- chart = me.getChart(),
- flipXY = chart.getFlipXY(),
- cursors = me.getCursors(),
- type = item && item.sprite.type,
- style = chart.el.dom.style;
- me.callParent([
- item
- ]);
- if (item) {
- switch (type) {
- case 'barSeries':
- if (flipXY) {
- style.cursor = cursors.ewResize;
- } else {
- style.cursor = cursors.nsResize;
- };
- break;
- case 'scatterSeries':
- style.cursor = cursors.move;
- break;
- }
- } else {
- chart.el.dom.style.cursor = 'default';
- }
- },
- onDragBar: function(e) {
- var me = this,
- chart = me.getChart(),
- isRtl = chart.getInherited().rtl,
- flipXY = chart.isCartesian && chart.getFlipXY(),
- item = chart.getHighlightItem(),
- marker = item.sprite.getMarker('items'),
- instance = marker.getMarkerFor(item.sprite.getId(), item.index),
- surface = item.sprite.getSurface(),
- surfaceRect = surface.getRect(),
- xy = surface.getEventXY(e),
- matrix = item.sprite.attr.matrix,
- renderer = me.getRenderer(),
- style, changes, params, positionY;
- if (flipXY) {
- positionY = isRtl ? surfaceRect[2] - xy[0] : xy[0];
- } else {
- positionY = surfaceRect[3] - xy[1];
- }
- style = {
- x: instance.x,
- y: positionY,
- width: instance.width,
- height: instance.height + (instance.y - positionY),
- radius: instance.radius,
- fillStyle: 'none',
- lineDash: [
- 4,
- 4
- ],
- zIndex: 100
- };
- Ext.apply(style, me.getStyle());
- if (Ext.isArray(item.series.getYField())) {
- // stacked bars
- positionY = positionY - instance.y - instance.height;
- }
- me.target = {
- index: item.index,
- yField: item.field,
- yValue: (positionY - matrix.getDY()) / matrix.getYY()
- };
- params = [
- chart,
- {
- target: me.target,
- style: style,
- item: item
- }
- ];
- changes = Ext.callback(renderer, null, params, 0, chart);
- if (changes) {
- Ext.apply(style, changes);
- }
- // The interaction works by putting another series item instance
- // under 'itemedit' ID with a slightly different style (default) or
- // whatever style the user provided.
- item.sprite.putMarker('items', style, 'itemedit');
- me.showTooltip(e, me.target, item);
- surface.renderFrame();
- },
- onDragScatter: function(e) {
- var me = this,
- chart = me.getChart(),
- isRtl = chart.getInherited().rtl,
- flipXY = chart.isCartesian && chart.getFlipXY(),
- item = chart.getHighlightItem(),
- marker = item.sprite.getMarker('markers'),
- instance = marker.getMarkerFor(item.sprite.getId(), item.index),
- surface = item.sprite.getSurface(),
- surfaceRect = surface.getRect(),
- xy = surface.getEventXY(e),
- matrix = item.sprite.attr.matrix,
- xAxis = item.series.getXAxis(),
- isEditableX = xAxis && xAxis.getLayout().isContinuous,
- renderer = me.getRenderer(),
- style, changes, params, positionX, positionY, hintX, hintY;
- if (flipXY) {
- positionY = isRtl ? surfaceRect[2] - xy[0] : xy[0];
- } else {
- positionY = surfaceRect[3] - xy[1];
- }
- if (isEditableX) {
- if (flipXY) {
- positionX = surfaceRect[3] - xy[1];
- } else {
- positionX = xy[0];
- }
- } else {
- positionX = instance.translationX;
- }
- if (isEditableX) {
- hintX = xy[0];
- hintY = xy[1];
- } else {
- if (flipXY) {
- hintX = xy[0];
- hintY = instance.translationY;
- } else // no change
- {
- hintX = instance.translationX;
- hintY = xy[1];
- }
- }
- // no change
- style = {
- translationX: hintX,
- translationY: hintY,
- scalingX: instance.scalingX,
- scalingY: instance.scalingY,
- r: instance.r,
- fillStyle: 'none',
- lineDash: [
- 4,
- 4
- ],
- zIndex: 100
- };
- Ext.apply(style, me.getStyle());
- me.target = {
- index: item.index,
- yField: item.field,
- yValue: (positionY - matrix.getDY()) / matrix.getYY()
- };
- if (isEditableX) {
- Ext.apply(me.target, {
- xField: item.series.getXField(),
- xValue: (positionX - matrix.getDX()) / matrix.getXX()
- });
- }
- params = [
- chart,
- {
- target: me.target,
- style: style,
- item: item
- }
- ];
- changes = Ext.callback(renderer, null, params, 0, chart);
- if (changes) {
- Ext.apply(style, changes);
- }
- // This marker acts as a visual hint while dragging.
- item.sprite.putMarker('markers', style, 'itemedit');
- me.showTooltip(e, me.target, item);
- surface.renderFrame();
- },
- showTooltip: function(e, target, item) {
- var tooltip = this.getTooltip(),
- config, chart;
- if (tooltip && Ext.toolkit !== 'modern') {
- config = tooltip.config;
- chart = this.getChart();
- Ext.callback(config.renderer, null, [
- tooltip,
- item,
- target,
- e
- ], 0, chart);
- // If trackMouse is set, a ToolTip shows by its pointerEvent
- tooltip.pointerEvent = e;
- if (tooltip.isVisible()) {
- // After show handling repositions according
- // to configuration. trackMouse uses the pointerEvent
- // If aligning to an element, it uses a currentTarget
- // flyweight which may be attached to any DOM element.
- tooltip.realignToTarget();
- } else {
- tooltip.show();
- }
- }
- },
- hideTooltip: function() {
- var tooltip = this.getTooltip();
- if (tooltip && Ext.toolkit !== 'modern') {
- tooltip.hide();
- }
- },
- onDragEnd: function(e) {
- var me = this,
- target = me.target,
- chart = me.getChart(),
- store = chart.getStore(),
- record;
- if (target) {
- record = store.getAt(target.index);
- if (target.yField) {
- record.set(target.yField, target.yValue, {
- convert: false
- });
- }
- if (target.xField) {
- record.set(target.xField, target.xValue, {
- convert: false
- });
- }
- if (target.yField || target.xField) {
- me.getChart().onDataChanged();
- }
- me.target = null;
- }
- me.hideTooltip();
- if (me.item) {
- chart.fireEvent('enditemedit', chart, me, me.item, target);
- }
- me.highlight(me.item = null);
- },
- destroy: function() {
- // Peek at the config, so we don't create one just to destroy it,
- // if a user has set 'tooltip' config to 'false'.
- var tooltip = this.getConfig('tooltip', true);
- Ext.destroy(tooltip);
- this.callParent();
- }
- });
- /**
- * The PanZoom interaction allows the user to navigate the data for one or more chart
- * axes by panning and/or zooming. Navigation can be limited to particular axes. Zooming is
- * performed by pinching on the chart or axis area; panning is performed by single-touch dragging.
- * The interaction only works with cartesian charts/series.
- *
- * For devices which do not support multiple-touch events, zooming can not be done via pinch
- * gestures; in this case the interaction will allow the user to perform both zooming and panning
- * using the same single-touch drag gesture.
- * {@link #modeToggleButton} provides a button to indicate and toggle between two modes.
- *
- * @example
- * Ext.create({
- * renderTo: document.body,
- * xtype: 'cartesian',
- * width: 600,
- * height: 400,
- * insetPadding: 40,
- * interactions: [{
- * type: 'panzoom',
- * zoomOnPan: true
- * }],
- * store: {
- * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
- * data: [{
- * 'name': 'metric one',
- * 'data1': 10,
- * 'data2': 12,
- * 'data3': 14,
- * 'data4': 8,
- * 'data5': 13
- * }, {
- * 'name': 'metric two',
- * 'data1': 7,
- * 'data2': 8,
- * 'data3': 16,
- * 'data4': 10,
- * 'data5': 3
- * }, {
- * 'name': 'metric three',
- * 'data1': 5,
- * 'data2': 2,
- * 'data3': 14,
- * 'data4': 12,
- * 'data5': 7
- * }, {
- * 'name': 'metric four',
- * 'data1': 2,
- * 'data2': 14,
- * 'data3': 6,
- * 'data4': 1,
- * 'data5': 23
- * }, {
- * 'name': 'metric five',
- * 'data1': 27,
- * 'data2': 38,
- * 'data3': 36,
- * 'data4': 13,
- * 'data5': 33
- * }]
- * },
- * axes: [{
- * type: 'numeric',
- * position: 'left',
- * fields: ['data1'],
- * title: {
- * text: 'Sample Values',
- * fontSize: 15
- * },
- * grid: true,
- * minimum: 0
- * }, {
- * type: 'category',
- * position: 'bottom',
- * fields: ['name'],
- * title: {
- * text: 'Sample Values',
- * fontSize: 15
- * }
- * }],
- * series: [{
- * type: 'line',
- * highlight: {
- * size: 7,
- * radius: 7
- * },
- * style: {
- * stroke: 'rgb(143,203,203)'
- * },
- * xField: 'name',
- * yField: 'data1',
- * marker: {
- * type: 'path',
- * path: ['M', - 2, 0, 0, 2, 2, 0, 0, - 2, 'Z'],
- * stroke: 'blue',
- * lineWidth: 0
- * }
- * }, {
- * type: 'line',
- * highlight: {
- * size: 7,
- * radius: 7
- * },
- * fill: true,
- * xField: 'name',
- * yField: 'data3',
- * marker: {
- * type: 'circle',
- * radius: 4,
- * lineWidth: 0
- * }
- * }]
- * });
- *
- * The configuration object for the `panzoom` interaction type should specify which axes
- * will be made navigable via the `axes` config. See the {@link #axes} config documentation
- * for details on the allowed formats. If the `axes` config is not specified, it will default
- * to making all axes navigable with the default axis options.
- *
- */
- Ext.define('Ext.chart.interactions.PanZoom', {
- extend: 'Ext.chart.interactions.Abstract',
- type: 'panzoom',
- alias: 'interaction.panzoom',
- requires: [
- 'Ext.draw.Animator'
- ],
- config: {
- /**
- * @cfg {Object/Array} axes
- * Specifies which axes should be made navigable. The config value can take the following
- * formats:
- *
- * - An Object with keys corresponding to the {@link Ext.chart.axis.Axis#position position}
- * of each axis that should be made navigable. Each key's value can either be an Object
- * with further configuration options for each axis or simply `true` for a default set
- * of options.
- *
- * {
- * type: 'panzoom',
- * axes: {
- * left: {
- * maxZoom: 5,
- * allowPan: false
- * },
- * bottom: true
- * }
- * }
- *
- * If using the full Object form, the following options can be specified for each axis:
- *
- * - minZoom (Number) A minimum zoom level for the axis. Defaults to `1` which is its
- * natural size.
- * - maxZoom (Number) A maximum zoom level for the axis. Defaults to `10`.
- * - startZoom (Number) A starting zoom level for the axis. Defaults to `1`.
- * - allowZoom (Boolean) Whether zooming is allowed for the axis. Defaults to `true`.
- * - allowPan (Boolean) Whether panning is allowed for the axis. Defaults to `true`.
- * - startPan (Boolean) A starting panning offset for the axis. Defaults to `0`.
- *
- * - An Array of strings, each one corresponding to the {@link Ext.chart.axis.Axis#position
- * position} of an axis that should be made navigable. The default options will be used
- * for each named axis.
- *
- * {
- * type: 'panzoom',
- * axes: ['left', 'bottom']
- * }
- *
- * If the `axes` config is not specified, it will default to making all axes navigable
- * with the default axis options.
- */
- axes: {
- top: {},
- right: {},
- bottom: {},
- left: {}
- },
- minZoom: null,
- maxZoom: null,
- /**
- * @cfg {Boolean} showOverflowArrows
- * If `true`, arrows will be conditionally shown at either end of each axis to indicate that
- * the axis is overflowing and can therefore be panned in that direction. Set this
- * to `false` to prevent the arrows from being displayed.
- */
- showOverflowArrows: true,
- /**
- * @cfg {Object} overflowArrowOptions
- * A set of optional overrides for the overflow arrow sprites' options. Only relevant when
- * {@link #showOverflowArrows} is `true`.
- */
- /**
- * @cfg {String} panGesture
- * Defines the gesture that initiates panning.
- * @private
- */
- panGesture: 'drag',
- /**
- * @cfg {String} zoomGesture
- * Defines the gesture that initiates zooming.
- * @private
- */
- zoomGesture: 'pinch',
- /**
- * @cfg {Boolean} zoomOnPanGesture
- * @deprecated 6.2 Please use {@link #zoomOnPan} instead.
- * If `true`, the pan gesture will zoom the chart.
- */
- zoomOnPanGesture: null,
- /**
- * @cfg {Boolean} zoomOnPan
- * If `true`, the pan gesture will zoom the chart.
- */
- zoomOnPan: false,
- /**
- * @cfg {Boolean} [doubleTapReset=false]
- * If `true`, the double tap on a chart will reset the current pan/zoom to show the whole
- * chart.
- */
- doubleTapReset: false,
- modeToggleButton: {
- xtype: 'segmentedbutton',
- width: 200,
- defaults: {
- ui: 'default-toolbar'
- },
- cls: Ext.baseCSSPrefix + 'panzoom-toggle',
- items: [
- {
- text: 'Pan',
- value: 'pan'
- },
- {
- text: 'Zoom',
- value: 'zoom'
- }
- ]
- },
- hideLabelInGesture: false
- },
- // Ext.os.is.Android
- stopAnimationBeforeSync: true,
- applyAxes: function(axesConfig, oldAxesConfig) {
- return Ext.merge(oldAxesConfig || {}, axesConfig);
- },
- updateZoomOnPan: function(zoomOnPan) {
- var button = this.getModeToggleButton();
- button.setValue(zoomOnPan ? 'zoom' : 'pan');
- },
- updateZoomOnPanGesture: function(zoomOnPanGesture) {
- this.setZoomOnPan(zoomOnPanGesture);
- },
- getZoomOnPanGesture: function() {
- return this.getZoomOnPan();
- },
- applyModeToggleButton: function(button, oldButton) {
- return Ext.factory(button, 'Ext.button.Segmented', oldButton);
- },
- updateModeToggleButton: function(button) {
- if (button) {
- button.on('change', 'onModeToggleChange', this);
- }
- },
- onModeToggleChange: function(segmentedButton, value) {
- this.setZoomOnPan(value === 'zoom');
- },
- getGestures: function() {
- var me = this,
- gestures = {},
- pan = me.getPanGesture(),
- zoom = me.getZoomGesture();
- gestures[zoom] = 'onZoomGestureMove';
- gestures[zoom + 'start'] = 'onZoomGestureStart';
- gestures[zoom + 'end'] = 'onZoomGestureEnd';
- gestures[pan] = 'onPanGestureMove';
- gestures[pan + 'start'] = 'onPanGestureStart';
- gestures[pan + 'end'] = 'onPanGestureEnd';
- gestures.doubletap = 'onDoubleTap';
- return gestures;
- },
- onDoubleTap: function(e) {
- var me = this,
- doubleTapReset = me.getDoubleTapReset(),
- chart, axes, axis, i, ln;
- if (doubleTapReset) {
- chart = me.getChart();
- axes = chart.getAxes();
- for (i = 0 , ln = axes.length; i < ln; i++) {
- axis = axes[i];
- axis.setVisibleRange([
- 0,
- 1
- ]);
- }
- chart.redraw();
- }
- },
- onPanGestureStart: function(e) {
- var me = this,
- chart, rect, xy;
- if (!e || !e.touches || e.touches.length < 2) {
- // Limit drags to single touch
- chart = me.getChart();
- rect = chart.getInnerRect();
- xy = chart.element.getXY();
- e.claimGesture();
- chart.suspendAnimation();
- me.startX = e.getX() - xy[0] - rect[0];
- me.startY = e.getY() - xy[1] - rect[1];
- me.oldVisibleRanges = null;
- me.hideLabels();
- chart.suspendThicknessChanged();
- me.lockEvents(me.getPanGesture());
- return false;
- }
- },
- onPanGestureMove: function(e) {
- var me = this,
- isMouse = e.pointerType === 'mouse',
- isZoomOnPan = isMouse && me.getZoomOnPan(),
- chart, rect, xy;
- if (me.getLocks()[me.getPanGesture()] === me) {
- // Limit drags to single touch.
- chart = me.getChart();
- rect = chart.getInnerRect();
- xy = chart.element.getXY();
- if (isZoomOnPan) {
- me.transformAxesBy(me.getZoomableAxes(e), 0, 0, (e.getX() - xy[0] - rect[0]) / me.startX, me.startY / (e.getY() - xy[1] - rect[1]));
- } else {
- me.transformAxesBy(me.getPannableAxes(e), e.getX() - xy[0] - rect[0] - me.startX, e.getY() - xy[1] - rect[1] - me.startY, 1, 1);
- }
- me.sync();
- return false;
- }
- },
- onPanGestureEnd: function(e) {
- var me = this,
- pan = me.getPanGesture(),
- chart;
- if (me.getLocks()[pan] === me) {
- chart = me.getChart();
- chart.resumeThicknessChanged();
- me.showLabels();
- me.sync();
- me.unlockEvents(pan);
- chart.resumeAnimation();
- return false;
- }
- },
- onZoomGestureStart: function(e) {
- if (e.touches && e.touches.length === 2) {
- // eslint-disable-next-line vars-on-top
- var me = this,
- chart = me.getChart(),
- xy = chart.element.getXY(),
- rect = chart.getInnerRect(),
- x = xy[0] + rect[0],
- y = xy[1] + rect[1],
- newPoints = [
- e.touches[0].point.x - x,
- e.touches[0].point.y - y,
- e.touches[1].point.x - x,
- e.touches[1].point.y - y
- ],
- xDistance = Math.max(44, Math.abs(newPoints[2] - newPoints[0])),
- yDistance = Math.max(44, Math.abs(newPoints[3] - newPoints[1]));
- e.claimGesture();
- chart.suspendAnimation();
- chart.suspendThicknessChanged();
- me.lastZoomDistances = [
- xDistance,
- yDistance
- ];
- me.lastPoints = newPoints;
- me.oldVisibleRanges = null;
- me.hideLabels();
- me.lockEvents(me.getZoomGesture());
- return false;
- }
- },
- onZoomGestureMove: function(e) {
- var me = this;
- if (me.getLocks()[me.getZoomGesture()] === me) {
- // eslint-disable-next-line vars-on-top
- var chart = me.getChart(),
- rect = chart.getInnerRect(),
- xy = chart.element.getXY(),
- x = xy[0] + rect[0],
- y = xy[1] + rect[1],
- abs = Math.abs,
- lastPoints = me.lastPoints,
- newPoints = [
- e.touches[0].point.x - x,
- e.touches[0].point.y - y,
- e.touches[1].point.x - x,
- e.touches[1].point.y - y
- ],
- xDistance = Math.max(44, abs(newPoints[2] - newPoints[0])),
- yDistance = Math.max(44, abs(newPoints[3] - newPoints[1])),
- lastDistances = this.lastZoomDistances || [
- xDistance,
- yDistance
- ],
- zoomX = xDistance / lastDistances[0],
- zoomY = yDistance / lastDistances[1];
- 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);
- me.sync();
- return false;
- }
- },
- onZoomGestureEnd: function(e) {
- var me = this,
- zoom = me.getZoomGesture(),
- chart;
- if (me.getLocks()[zoom] === me) {
- chart = me.getChart();
- chart.resumeThicknessChanged();
- me.showLabels();
- me.sync();
- me.unlockEvents(zoom);
- chart.resumeAnimation();
- return false;
- }
- },
- hideLabels: function() {
- if (this.getHideLabelInGesture()) {
- this.eachInteractiveAxes(function(axis) {
- axis.hideLabels();
- });
- }
- },
- showLabels: function() {
- if (this.getHideLabelInGesture()) {
- this.eachInteractiveAxes(function(axis) {
- axis.showLabels();
- });
- }
- },
- isEventOnAxis: function(e, axis) {
- // TODO: right now this uses the current event position but really we want to only
- // use the gesture's start event. Pinch does not give that to us though.
- var rect = axis.getSurface().getRect();
- return rect[0] <= e.getX() && e.getX() <= rect[0] + rect[2] && rect[1] <= e.getY() && e.getY() <= rect[1] + rect[3];
- },
- getPannableAxes: function(e) {
- var me = this,
- axisConfigs = me.getAxes(),
- axes = me.getChart().getAxes(),
- i,
- ln = axes.length,
- result = [],
- isEventOnAxis = false,
- config;
- if (e) {
- for (i = 0; i < ln; i++) {
- if (this.isEventOnAxis(e, axes[i])) {
- isEventOnAxis = true;
- break;
- }
- }
- }
- for (i = 0; i < ln; i++) {
- config = axisConfigs[axes[i].getPosition()];
- if (config && config.allowPan !== false && (!isEventOnAxis || this.isEventOnAxis(e, axes[i]))) {
- result.push(axes[i]);
- }
- }
- return result;
- },
- getZoomableAxes: function(e) {
- var me = this,
- axisConfigs = me.getAxes(),
- axes = me.getChart().getAxes(),
- result = [],
- i,
- ln = axes.length,
- axis,
- isEventOnAxis = false,
- config;
- if (e) {
- for (i = 0; i < ln; i++) {
- if (this.isEventOnAxis(e, axes[i])) {
- isEventOnAxis = true;
- break;
- }
- }
- }
- for (i = 0; i < ln; i++) {
- axis = axes[i];
- config = axisConfigs[axis.getPosition()];
- if (config && config.allowZoom !== false && (!isEventOnAxis || this.isEventOnAxis(e, axis))) {
- result.push(axis);
- }
- }
- return result;
- },
- eachInteractiveAxes: function(fn) {
- var me = this,
- axisConfigs = me.getAxes(),
- axes = me.getChart().getAxes(),
- i;
- for (i = 0; i < axes.length; i++) {
- if (axisConfigs[axes[i].getPosition()]) {
- if (false === fn.call(this, axes[i])) {
- return;
- }
- }
- }
- },
- transformAxesBy: function(axes, panX, panY, sx, sy) {
- var rect = this.getChart().getInnerRect(),
- axesCfg = this.getAxes(),
- oldVisibleRanges = this.oldVisibleRanges,
- result = false,
- axisCfg, i;
- if (!oldVisibleRanges) {
- this.oldVisibleRanges = oldVisibleRanges = {};
- this.eachInteractiveAxes(function(axis) {
- oldVisibleRanges[axis.getId()] = axis.getVisibleRange();
- });
- }
- if (!rect) {
- return;
- }
- for (i = 0; i < axes.length; i++) {
- axisCfg = axesCfg[axes[i].getPosition()];
- result = this.transformAxisBy(axes[i], oldVisibleRanges[axes[i].getId()], panX, panY, sx, sy, this.minZoom || axisCfg.minZoom, this.maxZoom || axisCfg.maxZoom) || result;
- }
- return result;
- },
- transformAxisBy: function(axis, oldVisibleRange, panX, panY, sx, sy, minZoom, maxZoom) {
- var me = this,
- visibleLength = oldVisibleRange[1] - oldVisibleRange[0],
- visibleRange = axis.getVisibleRange(),
- actualMinZoom = minZoom || me.getMinZoom() || axis.config.minZoom,
- actualMaxZoom = maxZoom || me.getMaxZoom() || axis.config.maxZoom,
- rect = me.getChart().getInnerRect(),
- left, right, isSide, pan, length;
- if (!rect) {
- return;
- }
- isSide = axis.isSide();
- length = isSide ? rect[3] : rect[2];
- pan = isSide ? -panY : panX;
- visibleLength /= isSide ? sy : sx;
- if (visibleLength < 0) {
- visibleLength = -visibleLength;
- }
- if (visibleLength * actualMinZoom > 1) {
- visibleLength = 1;
- }
- if (visibleLength * actualMaxZoom < 1) {
- visibleLength = 1 / actualMaxZoom;
- }
- left = oldVisibleRange[0];
- right = oldVisibleRange[1];
- visibleRange = visibleRange[1] - visibleRange[0];
- if (visibleLength === visibleRange && visibleRange === 1) {
- return;
- }
- axis.setVisibleRange([
- (oldVisibleRange[0] + oldVisibleRange[1] - visibleLength) * 0.5 - pan / length * visibleLength,
- (oldVisibleRange[0] + oldVisibleRange[1] + visibleLength) * 0.5 - pan / length * visibleLength
- ]);
- return Math.abs(left - axis.getVisibleRange()[0]) > 1.0E-10 || Math.abs(right - axis.getVisibleRange()[1]) > 1.0E-10;
- },
- destroy: function() {
- this.setModeToggleButton(null);
- this.callParent();
- }
- });
- /* eslint-disable max-len */
- /**
- * @class Ext.chart.interactions.Rotate
- * @extends Ext.chart.interactions.Abstract
- *
- * The Rotate interaction allows the user to rotate a polar chart about its central point.
- *
- * @example
- * Ext.create('Ext.Container', {
- * renderTo: Ext.getBody(),
- * width: 600,
- * height: 400,
- * layout: 'fit',
- * items: {
- * xtype: 'polar',
- * interactions: 'rotate',
- * colors: ["#115fa6", "#94ae0a", "#a61120", "#ff8809", "#ffd13e"],
- * store: {
- * fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
- * data: [
- * {'name':'metric one', 'data1':10, 'data2':12, 'data3':14, 'data4':8, 'data5':13},
- * {'name':'metric two', 'data1':7, 'data2':8, 'data3':16, 'data4':10, 'data5':3},
- * {'name':'metric three', 'data1':5, 'data2':2, 'data3':14, 'data4':12, 'data5':7},
- * {'name':'metric four', 'data1':2, 'data2':14, 'data3':6, 'data4':1, 'data5':23},
- * {'name':'metric five', 'data1':27, 'data2':38, 'data3':36, 'data4':13, 'data5':33}
- * ]
- * },
- * series: {
- * type: 'pie',
- * label: {
- * field: 'name',
- * display: 'rotate'
- * },
- * xField: 'data3',
- * donut: 30
- * }
- * }
- * });
- */
- /* eslint-enable max-len */
- Ext.define('Ext.chart.interactions.Rotate', {
- extend: 'Ext.chart.interactions.Abstract',
- type: 'rotate',
- alternateClassName: 'Ext.chart.interactions.RotatePie3D',
- alias: [
- 'interaction.rotate',
- 'interaction.rotatePie3d'
- ],
- /**
- * @event rotate
- * Fires on every tick of the rotation.
- * @param {Ext.chart.interactions.Rotate} this This interaction.
- * @param {Number} angle The new current rotation angle.
- */
- /**
- * @event rotatestart
- * Fires when a user initiates the rotation.
- * @param {Ext.chart.interactions.Rotate} this This interaction.
- * @param {Number} angle The new current rotation angle.
- */
- /**
- * @event rotateend
- * Fires after a user finishes the rotation.
- * @param {Ext.chart.interactions.Rotate} this This interaction.
- * @param {Number} angle The new current rotation angle.
- */
- /**
- * @deprecated 6.5.1 Use the 'rotateend' event instead.
- * @event rotationEnd
- * Fires after a user finishes the rotation
- * @param {Ext.chart.interactions.Rotate} this This interaction.
- * @param {Number} angle The new current rotation angle.
- */
- config: {
- /**
- * @cfg {String} gesture
- * Defines the gesture type that will be used to rotate the chart. Currently only
- * supports `pinch` for two-finger rotation and `drag` for single-finger rotation.
- * @private
- */
- gesture: 'rotate',
- gestures: {
- dragstart: 'onGestureStart',
- drag: 'onGesture',
- dragend: 'onGestureEnd'
- },
- /**
- * @cfg {Number} rotation
- * Saves the current rotation of the series. Accepts negative values
- * and values > 360 ( / 180 * Math.PI)
- * @private
- */
- rotation: 0
- },
- oldRotations: null,
- getAngle: function(e) {
- var me = this,
- chart = me.getChart(),
- xy = chart.getEventXY(e),
- center = chart.getCenter();
- return Math.atan2(xy[1] - center[1], xy[0] - center[0]);
- },
- onGestureStart: function(e) {
- var me = this;
- e.claimGesture();
- me.lockEvents('drag');
- me.angle = me.getAngle(e);
- me.oldRotations = {};
- me.getChart().suspendAnimation();
- me.fireEvent('rotatestart', me, me.getRotation());
- return false;
- },
- onGesture: function(e) {
- var me = this,
- angle = me.getAngle(e) - me.angle;
- if (me.getLocks().drag === me) {
- me.doRotateTo(angle, true);
- return false;
- }
- },
- /**
- * @private
- */
- doRotateTo: function(angle, relative) {
- var me = this,
- chart = me.getChart(),
- axes = chart.getAxes(),
- seriesList = chart.getSeries(),
- oldRotations = me.oldRotations,
- rotation, oldRotation, axis, series, id, i, ln;
- for (i = 0 , ln = axes.length; i < ln; i++) {
- axis = axes[i];
- id = axis.getId();
- oldRotation = oldRotations[id] || (oldRotations[id] = axis.getRotation());
- rotation = angle + (relative ? oldRotation : 0);
- axis.setRotation(rotation);
- }
- for (i = 0 , ln = seriesList.length; i < ln; i++) {
- series = seriesList[i];
- id = series.getId();
- oldRotation = oldRotations[id] || (oldRotations[id] = series.getRotation());
- // Unline axis's 'rotation', Polar series' 'rotation' is a public config and in degrees.
- rotation = Ext.draw.Draw.degrees(angle + (relative ? oldRotation : 0));
- series.setRotation(rotation);
- }
- me.setRotation(rotation);
- me.fireEvent('rotate', me, me.getRotation());
- me.sync();
- },
- /**
- * Rotates a polar chart about its center point to the specified angle.
- * @param {Number} angle The angle to rotate to.
- * @param {Boolean} [relative=false] Whether the rotation is relative to the current angle
- * or not.
- * @param {Boolean} [animate=false] Whether to animate the rotation or not.
- */
- rotateTo: function(angle, relative, animate) {
- var me = this,
- chart = me.getChart();
- if (!animate) {
- chart.suspendAnimation();
- }
- me.doRotateTo(angle, relative, animate);
- me.oldRotations = {};
- if (!animate) {
- chart.resumeAnimation();
- }
- },
- onGestureEnd: function(e) {
- var me = this;
- if (me.getLocks().drag === me) {
- me.onGesture(e);
- me.unlockEvents('drag');
- me.getChart().resumeAnimation();
- me.fireEvent('rotateend', me, me.getRotation());
- me.fireEvent('rotationEnd', me, me.getRotation());
- return false;
- }
- }
- });
- /**
- *
- */
- Ext.define('Ext.chart.navigator.ContainerBase', {
- extend: 'Ext.Container',
- updateNavigator: function(navigator, oldNavigator) {
- if (oldNavigator) {
- this.remove(oldNavigator, true);
- }
- this.add(navigator);
- }
- });
- /**
- *
- */
- Ext.define('Ext.chart.navigator.NavigatorBase', {
- extend: 'Ext.chart.CartesianChart',
- initialize: function() {
- var me = this;
- me.callParent();
- me.setupEvents();
- }
- });
- /**
- * The overlay sprite used by the {@link Ext.chart.navigator.Navigator} component
- * to render the selected visible range or a chart's horizontal axis.
- */
- Ext.define('Ext.chart.navigator.sprite.RangeMask', {
- extend: 'Ext.draw.sprite.Sprite',
- alias: 'sprite.rangemask',
- inheritableStatics: {
- def: {
- processors: {
- min: 'limited01',
- max: 'limited01',
- thumbOpacity: 'limited01'
- },
- defaults: {
- min: 0,
- max: 1,
- lineWidth: 2,
- miterLimit: 1,
- strokeStyle: '#787878',
- thumbOpacity: 1
- }
- }
- },
- getBBox: function(isWithoutTransform) {
- var me = this,
- attr = me.attr,
- bbox = attr.bbox;
- bbox.plain = {
- x: 0,
- y: 0,
- width: 1,
- height: 1
- };
- if (isWithoutTransform) {
- return bbox.plain;
- }
- return bbox.transform || (bbox.transform = attr.matrix.transformBBox(bbox.plain));
- },
- renderThumb: function(surface, ctx, x, y) {
- var me = this,
- shapeSprite = me.shapeSprite,
- textureSprite = me.textureSprite,
- thumbOpacity = me.attr.thumbOpacity,
- thumbAttributes = {
- opacity: thumbOpacity,
- translationX: x,
- translationY: y
- };
- if (!shapeSprite) {
- shapeSprite = me.shapeSprite = new Ext.draw.sprite.Rect({
- x: -9.5,
- y: -9.5,
- width: 19,
- height: 19,
- radius: 4,
- lineWidth: 1,
- fillStyle: {
- type: 'linear',
- degrees: 90,
- stops: [
- {
- offset: 0,
- color: '#EEE'
- },
- {
- offset: 1,
- color: '#FFF'
- }
- ]
- },
- strokeStyle: '#999'
- });
- textureSprite = me.textureSprite = new Ext.draw.sprite.Path({
- path: 'M -4, -5, -4, 5 M 0, -5, 0, 5 M 4, -5, 4, 5',
- strokeStyle: {
- type: 'linear',
- degrees: 90,
- stops: [
- {
- offset: 0,
- color: '#CCC'
- },
- {
- offset: 1,
- color: '#BBB'
- }
- ]
- },
- lineWidth: 2
- });
- }
- ctx.save();
- shapeSprite.setAttributes(thumbAttributes);
- shapeSprite.applyTransformations();
- textureSprite.setAttributes(thumbAttributes);
- textureSprite.applyTransformations();
- shapeSprite.useAttributes(ctx);
- shapeSprite.render(surface, ctx);
- textureSprite.useAttributes(ctx);
- textureSprite.render(surface, ctx);
- ctx.restore();
- },
- render: function(surface, ctx) {
- var me = this,
- attr = me.attr,
- matrix = attr.matrix.elements,
- sx = matrix[0],
- sy = matrix[3],
- tx = matrix[4],
- ty = matrix[5],
- min = attr.min,
- max = attr.max,
- // s_min and s_max are range values in screen coordinates (scaled and translated)
- s_min = min * sx + tx,
- s_max = max * sx + tx,
- s_y = Math.round(0.5 * sy + ty);
- // thumb position in screen coordinates (mid-height)
- ctx.beginPath();
- // Rect that represents the whole range.
- ctx.moveTo(tx, ty);
- ctx.lineTo(sx + tx, ty);
- ctx.lineTo(sx + tx, sy + ty);
- ctx.lineTo(tx, sy + ty);
- ctx.lineTo(tx, ty);
- // Rect that represents the visible range.
- ctx.moveTo(s_min, ty);
- ctx.lineTo(s_min, sy + ty);
- ctx.lineTo(s_max, sy + ty);
- ctx.lineTo(s_max, ty);
- ctx.lineTo(s_min, ty);
- ctx.fillStroke(attr, true);
- me.renderThumb(surface, ctx, Math.round(s_min), s_y);
- me.renderThumb(surface, ctx, Math.round(s_max), s_y);
- }
- });
- /**
- * The Navigator component is used to visually set the visible range of the x-axis
- * of a cartesian chart.
- *
- * This component is meant to be used with the Navigator Container
- * via its {@link Ext.chart.navigator.Container#navigator} config.
- *
- * IMPORTANT: even though the Navigator component is a kind of chart, it should not be
- * treated as such. Correct behavior is not guaranteed when using hidden/private configs.
- */
- Ext.define('Ext.chart.navigator.Navigator', {
- extend: 'Ext.chart.navigator.NavigatorBase',
- isNavigator: true,
- requires: [
- 'Ext.chart.navigator.sprite.RangeMask'
- ],
- config: {
- /**
- * @cfg {'bottom'/'top'} [docked='bottom']
- */
- docked: 'bottom',
- /**
- * @cfg {'series'/'chart'} [span='series']
- * Whether the navigator should span the 'series' (default) or the whole 'chart'.
- */
- span: 'series',
- insetPadding: 0,
- innerPadding: 0,
- /**
- * @cfg {Ext.chart.navigator.Container} navigatorContainer
- * 'parent' is reserved in Modern, 'container' is reserved in Classic,
- * so we use 'navigatorContainer' as a config name.
- * @private
- */
- navigatorContainer: null,
- /**
- * @cfg {String} axis (required)
- * The ID of the {@link #chart chart's} axis to link to.
- * The axis should be positioned to 'bottom' or 'top' in the chart.
- */
- axis: null,
- /**
- * @cfg {Number} [tolerance=20]
- * The maximum horizontal delta between the pointer/finger and the center of a navigator
- * thumb. Used for hit testing.
- */
- tolerance: 20,
- /**
- * @cfg {Number} [minimum=0.8]
- * The start of the visible range, where the visible range is a [0, 1] interval.
- */
- minimum: 0.8,
- /**
- * @cfg {Number} [maximum=1]
- * The end of the visible range, where the visible range is a [0, 1] interval.
- */
- maximum: 1,
- /**
- * @cfg {Number} [thumbGap=30]
- * Minimum gap between navigator thumbs in pixels.
- */
- thumbGap: 30,
- autoHideThumbs: true,
- width: '100%',
- /**
- * @cfg {Number} [height=75]
- * The height of the navigator component.
- */
- height: 75
- },
- /**
- * @cfg flipXY
- * @hide
- */
- /**
- * @cfg series
- * @hide
- */
- /**
- * @cfg axes
- * @hide
- */
- /**
- * @cfg store
- * @hide
- */
- /**
- * @cfg legend
- * @hide
- */
- /**
- * @cfg interactions
- * @hide
- */
- /**
- * @cfg highlightItem
- * @hide
- */
- /**
- * @cfg theme
- * @hide
- */
- /**
- * @cfg innerPadding
- * @hide
- */
- /**
- * @cfg insetPadding
- * @hide
- */
- dragType: null,
- constructor: function(config) {
- var me = this,
- visibleRange, overlay;
- config = config || {};
- visibleRange = [
- config.minimum || 0.8,
- config.maximum || 1
- ];
- me.callParent([
- config
- ]);
- overlay = me.overlaySurface;
- overlay.element.setStyle({
- zIndex: 100
- });
- me.rangeMask = overlay.add({
- type: 'rangemask',
- min: visibleRange[0],
- max: visibleRange[1],
- fillStyle: 'rgba(0, 0, 0, .25)'
- });
- me.onDragEnd();
- // Set 'thumbOpacity' of the range mask sprite to 0, if needed,
- // and apply animation modifier changes after that, so that the attribute is set
- // instantly.
- me.rangeMask.setAnimation({
- duration: 500,
- customDurations: {
- min: 0,
- max: 0,
- translationX: 0,
- translationY: 0,
- scalingX: 0,
- scalingY: 0,
- scalingCenterX: 0,
- scalingCenterY: 0,
- fillStyle: 0,
- strokeStyle: 0
- }
- });
- me.setVisibleRange(visibleRange);
- },
- createSurface: function(id) {
- var surface = this.callParent([
- id
- ]);
- if (id === 'overlay') {
- this.overlaySurface = surface;
- }
- return surface;
- },
- // Note: 'applyDock' and 'updateDock' won't ever be called in Classic.
- // See Classic NavigatorBase.
- applyAxis: function(axis) {
- return this.getNavigatorContainer().getChart().getAxis(axis);
- },
- updateAxis: function(axis, oldAxis) {
- var me = this,
- eventName = 'visiblerangechange',
- eventHandler = 'onAxisVisibleRangeChange';
- if (oldAxis) {
- oldAxis.un(eventName, eventHandler, me);
- }
- if (axis) {
- axis.on(eventName, eventHandler, me);
- }
- me.axis = axis;
- },
- getAxis: function() {
- // The superclass doesn't have the 'axis' config, but it has the same method,
- // which we override here to act as a getter for the config. The user is not
- // expected to use the original method in this subclass anyway.
- return this.axis;
- },
- onAxisVisibleRangeChange: function(axis, visibleRange) {
- this.setVisibleRange(visibleRange);
- },
- updateNavigatorContainer: function(navigatorContainer) {
- var me = this,
- oldChart = me.chart,
- chart = me.chart = navigatorContainer && navigatorContainer.getChart(),
- chartSeriesList = chart && chart.getSeries(),
- // 'legendStore' already exists in the base class.
- chartLegendStore = me.chartLegendStore,
- navigatorSeriesList = [],
- storeEventName = 'update',
- // 'onLegendStoreUpdate' already exists in the base class.
- storeEventHandler = 'onChartLegendStoreUpdate',
- chartSeries, navigatorSeries, seriesConfig, i;
- if (oldChart) {
- oldChart.un('layout', 'afterBoundChartLayout', me);
- oldChart.un('themechange', 'onChartThemeChange', me);
- oldChart.un('storechange', 'onChartStoreChange', me);
- }
- chart.on('layout', 'afterBoundChartLayout', me);
- for (i = 0; i < chartSeriesList.length; i++) {
- chartSeries = chartSeriesList[i];
- seriesConfig = me.getSeriesConfig(chartSeries);
- navigatorSeries = Ext.create('series.' + seriesConfig.type, seriesConfig);
- navigatorSeries.parentSeries = chartSeries;
- chartSeries.navigatorSeries = navigatorSeries;
- navigatorSeriesList.push(navigatorSeries);
- }
- if (chartLegendStore) {
- chartLegendStore.un(storeEventName, storeEventHandler, me);
- me.chartLegendStore = null;
- }
- if (chart) {
- me.setStore(chart.getStore());
- me.chartLegendStore = chartLegendStore = chart.getLegendStore();
- if (chartLegendStore) {
- chartLegendStore.on(storeEventName, storeEventHandler, me);
- }
- chart.on('themechange', 'onChartThemeChange', me);
- chart.on('storechange', 'onChartStoreChange', me);
- me.onChartThemeChange(chart, chart.getTheme());
- }
- me.setSeries(navigatorSeriesList);
- },
- onChartThemeChange: function(chart, theme) {
- this.setTheme(theme);
- },
- onChartStoreChange: function(chart, store) {
- this.setStore(store);
- },
- addCustomStyle: function(config, style, subStyle) {
- var fillStyle, strokeStyle;
- style = style || {};
- subStyle = subStyle || {};
- config.style = config.style || {};
- config.subStyle = config.subStyle || {};
- fillStyle = style && (style.fillStyle || style.fill);
- strokeStyle = style && (style.strokeStyle || style.stroke);
- if (fillStyle) {
- config.style.fillStyle = fillStyle;
- }
- if (strokeStyle) {
- config.style.strokeStyle = strokeStyle;
- }
- fillStyle = subStyle && (subStyle.fillStyle || subStyle.fill);
- strokeStyle = subStyle && (subStyle.strokeStyle || subStyle.stroke);
- if (fillStyle) {
- config.subStyle.fillStyle = fillStyle;
- }
- if (strokeStyle) {
- config.subStyle.strokeStyle = strokeStyle;
- }
- return config;
- },
- getSeriesConfig: function(chartSeries) {
- var me = this,
- style = chartSeries.getStyle(),
- config;
- if (chartSeries.isLine) {
- config = me.addCustomStyle({
- type: 'line',
- fill: true,
- xField: chartSeries.getXField(),
- yField: chartSeries.getYField(),
- smooth: chartSeries.getSmooth()
- }, style);
- } else if (chartSeries.isCandleStick) {
- config = me.addCustomStyle({
- type: 'line',
- fill: true,
- xField: chartSeries.getXField(),
- yField: chartSeries.getCloseField()
- }, style.raiseStyle);
- } else if (chartSeries.isArea || chartSeries.isBar) {
- config = me.addCustomStyle({
- type: 'area',
- xField: chartSeries.getXField(),
- yField: chartSeries.getYField()
- }, style, chartSeries.getSubStyle());
- } else {
- Ext.raise("Navigator only works with 'line', 'bar', 'candlestick' and 'area' series.");
- }
- config.style.fillOpacity = 0.2;
- return config;
- },
- onChartLegendStoreUpdate: function(store, record) {
- var me = this,
- chart = me.chart,
- series;
- if (chart && record) {
- series = chart.getSeries().map[record.get('series')];
- if (series && series.navigatorSeries) {
- series.navigatorSeries.setHiddenByIndex(record.get('index'), record.get('disabled'));
- me.redraw();
- }
- }
- },
- setupEvents: function() {
- // Called from NavigatorBase classes.
- var me = this,
- overlayEl = me.overlaySurface.element;
- overlayEl.on({
- scope: me,
- drag: 'onDrag',
- dragstart: 'onDragStart',
- dragend: 'onDragEnd',
- dragcancel: 'onDragEnd',
- mousemove: 'onMouseMove'
- });
- },
- onMouseMove: function(e) {
- var me = this,
- overlayEl = me.overlaySurface.element,
- style = overlayEl.dom.style,
- dragType = me.getDragType(e.pageX - overlayEl.getXY()[0]);
- switch (dragType) {
- case 'min':
- case 'max':
- style.cursor = 'ew-resize';
- break;
- case 'pan':
- style.cursor = 'move';
- break;
- default:
- style.cursor = 'default';
- }
- },
- getDragType: function(x) {
- var me = this,
- t = me.getTolerance(),
- width = me.overlaySurface.element.getSize().width,
- rangeMask = me.rangeMask,
- min = width * rangeMask.attr.min,
- max = width * rangeMask.attr.max,
- dragType;
- if (x > min + t && x < max - t) {
- dragType = 'pan';
- } else if (x <= min + t && x > min - t) {
- dragType = 'min';
- } else if (x >= max - t && x < max + t) {
- dragType = 'max';
- }
- return dragType;
- },
- onDragStart: function(e) {
- var me = this,
- x, dragType;
- // Limit drags to single touch.
- if (me.dragType || e && e.touches && e.touches.length > 1) {
- return;
- }
- x = e.touches[0].pageX - me.overlaySurface.element.getXY()[0];
- dragType = me.getDragType(x);
- me.rangeMask.attr.thumbOpacity = 1;
- if (dragType) {
- me.dragType = dragType;
- me.touchId = e.touches[0].identifier;
- me.dragX = x;
- }
- },
- onDrag: function(e) {
- if (e.touch.identifier !== this.touchId) {
- return;
- }
- // eslint-disable-next-line vars-on-top
- var me = this,
- overlayEl = me.overlaySurface.element,
- width = overlayEl.getSize().width,
- x = e.touches[0].pageX - overlayEl.getXY()[0],
- thumbGap = me.getThumbGap() / width,
- rangeMask = me.rangeMask,
- min = rangeMask.attr.min,
- max = rangeMask.attr.max,
- delta = max - min,
- dragType = me.dragType,
- drag = me.dragX,
- dx = (x - drag) / width;
- if (dragType === 'pan') {
- min += dx;
- max += dx;
- if (min < 0) {
- min = 0;
- max = delta;
- }
- if (max > 1) {
- max = 1;
- min = max - delta;
- }
- } else if (dragType === 'min') {
- min += dx;
- if (min < 0) {
- min = 0;
- }
- if (min > max - thumbGap) {
- min = max - thumbGap;
- }
- } else if (dragType === 'max') {
- max += dx;
- if (max > 1) {
- max = 1;
- }
- if (max < min + thumbGap) {
- max = min + thumbGap;
- }
- } else {
- return;
- }
- me.dragX = x;
- me.setVisibleRange([
- min,
- max
- ]);
- },
- onDragEnd: function() {
- var me = this,
- autoHideThumbs = me.getAutoHideThumbs();
- me.dragType = null;
- if (autoHideThumbs) {
- me.rangeMask.setAttributes({
- thumbOpacity: 0
- });
- }
- },
- updateMinimum: function(mininum) {
- if (!this.isConfiguring) {
- this.setVisibleRange([
- mininum,
- this.getMaximum()
- ]);
- }
- },
- updateMaximum: function(maximum) {
- if (!this.isConfiguring) {
- this.setVisibleRange([
- this.getMinimum(),
- maximum
- ]);
- }
- },
- getMinimum: function() {
- return this.rangeMask.attr.min;
- },
- getMaximum: function() {
- return this.rangeMask.attr.max;
- },
- setVisibleRange: function(visibleRange) {
- var me = this,
- chart = me.chart;
- me.axis.setVisibleRange(visibleRange);
- me.rangeMask.setAttributes({
- min: visibleRange[0],
- max: visibleRange[1]
- });
- me.getSurface('overlay').renderFrame();
- chart.suspendAnimation();
- chart.redraw();
- chart.resumeAnimation();
- },
- afterBoundChartLayout: function() {
- var me = this,
- spanSeries = me.getSpan() === 'series',
- mainRect = me.chart.getMainRect(),
- size = me.element.getSize();
- if (mainRect && spanSeries) {
- me.setInsetPadding({
- left: mainRect[0],
- right: size.width - mainRect[2] - mainRect[0],
- top: 0,
- bottom: 0
- });
- me.performLayout();
- }
- },
- afterChartLayout: function() {
- var me = this,
- size = me.overlaySurface.element.getSize();
- me.rangeMask.setAttributes({
- scalingCenterX: 0,
- scalingCenterY: 0,
- scalingX: size.width,
- scalingY: size.height
- });
- },
- doDestroy: function() {
- var chart = this.chart;
- if (chart && !chart.destroyed) {
- chart.un('layout', 'afterBoundChartLayout', this);
- }
- this.callParent();
- }
- });
- /**
- * The Navigator Container is a component used to lay out the chart and its
- * {@link Ext.chart.navigator.Navigator navigator}, where the navigator is docked
- * to the top/bottom, and the chart fills the rest of the container's space.
- *
- * For example:
- *
- * @example
- * Ext.create({
- * xtype: 'chartnavigator',
- * renderTo: Ext.getBody(),
- * width: 600,
- * height: 400,
- *
- * chart: {
- * xtype: 'cartesian',
- *
- * store: {
- * data: (function () {
- * var data = [];
- * for (var i = 0; i < 360; i++) {
- * data.push({
- * x: i,
- * y: Math.sin(i / 45 * Math.PI)
- * });
- * }
- * return data;
- * })()
- * },
- * axes: [
- * {
- * id: 'navigable-axis',
- *
- * type: 'numeric',
- * position: 'bottom'
- * },
- * {
- * type: 'numeric',
- * position: 'left'
- * }
- * ],
- * series: {
- * type: 'line',
- * xField: 'x',
- * yField: 'y'
- * }
- * },
- *
- * navigator: {
- * axis: 'navigable-axis'
- * }
- * });
- *
- */
- Ext.define('Ext.chart.navigator.Container', {
- // We are interested in the docking functionality that's available in
- // the Container in Modern and in the Panel in Classic.
- extend: 'Ext.chart.navigator.ContainerBase',
- requires: [
- 'Ext.chart.CartesianChart',
- 'Ext.chart.navigator.Navigator'
- ],
- xtype: 'chartnavigator',
- config: {
- /**
- * @cfg {Ext.chart.CartesianChart} chart
- * The chart to make navigable.
- */
- chart: null,
- /**
- * @cfg {Ext.chart.navigator.Navigator} navigator
- */
- navigator: {}
- },
- layout: 'fit',
- applyChart: function(chart, oldChart) {
- if (oldChart) {
- oldChart.destroy();
- }
- if (chart) {
- if (chart.isCartesian) {
- Ext.raise('Only cartesian charts are supported.');
- }
- if (!chart.isChart) {
- chart.$initParent = this;
- chart = new Ext.chart.CartesianChart(chart);
- delete chart.$initParent;
- }
- }
- return chart;
- },
- legendStore: null,
- surfaceRects: null,
- updateChart: function(chart, oldChart) {
- var me = this;
- if (chart) {
- me.legendStore = chart.getLegendStore();
- if (!me.items && me.initItems) {
- me.initItems();
- }
- me.add(chart);
- }
- },
- applyNavigator: function(navigator, oldNavigator) {
- var instance;
- if (oldNavigator) {
- oldNavigator.destroy();
- }
- if (navigator) {
- navigator.navigatorContainer = navigator.parent = this;
- instance = new Ext.chart.navigator.Navigator(navigator);
- }
- return instance;
- },
- preview: function() {
- this.getNavigator().preview(this.getImage());
- },
- download: function(config) {
- config = config || {};
- config.data = this.getImage().data;
- this.getNavigator().download(config);
- },
- setVisibleRange: function(visibleRange) {
- this.getNavigator().setVisibleRange(visibleRange);
- },
- getImage: function(format) {
- var me = this,
- chart = me.getChart(),
- navigator = me.getNavigator(),
- docked = navigator.getDocked(),
- chartImageSize = chart.bodyElement.getSize(),
- navigatorImageSize = navigator.bodyElement.getSize(),
- chartSurfaces = chart.getSurfaces(true),
- navigatorSurfaces = navigator.getSurfaces(true),
- size = {
- width: chartImageSize.width,
- height: chartImageSize.height + navigatorImageSize.height
- },
- image, imageElement, surfaces, surface;
- if (docked === 'top') {
- me.shiftSurfaces(chartSurfaces, 0, navigatorImageSize.height);
- } else {
- me.shiftSurfaces(navigatorSurfaces, 0, chartImageSize.height);
- }
- surfaces = chartSurfaces.concat(navigatorSurfaces);
- surface = surfaces[0];
- if ((Ext.isIE || Ext.isEdge) && surface.isSVG) {
- // SVG data URLs don't work in IE/Edge as a source for an 'img' element,
- // so we need to render SVG the usual way.
- image = {
- data: surface.toSVG(size, surfaces),
- type: 'svg-markup'
- };
- } else {
- image = surface.flatten(size, surfaces);
- if (format === 'image') {
- imageElement = new Image();
- imageElement.src = image.data;
- image.data = imageElement;
- return image;
- }
- if (format === 'stream') {
- image.data = image.data.replace(/^data:image\/[^;]+/, 'data:application/octet-stream');
- return image;
- }
- }
- me.unshiftSurfaces(surfaces);
- return image;
- },
- shiftSurfaces: function(surfaces, x, y) {
- var ln = surfaces.length,
- i = 0,
- surface;
- this.surfaceRects = {};
- for (; i < ln; i++) {
- surface = surfaces[i];
- this.shiftSurface(surface, x, y);
- }
- },
- shiftSurface: function(surface, x, y) {
- var rect = surface.getRect();
- this.surfaceRects[surface.getId()] = rect.slice();
- rect[0] += x;
- rect[1] += y;
- },
- unshiftSurfaces: function(surfaces) {
- var rects = this.surfaceRects,
- ln = surfaces.length,
- i = 0,
- surface, rect, oldRect;
- if (rects) {
- for (; i < ln; i++) {
- surface = surfaces[i];
- rect = surface.getRect();
- oldRect = rects[surface.getId()];
- if (oldRect) {
- rect[0] = oldRect[0];
- rect[1] = oldRect[1];
- }
- }
- }
- this.surfaceRects = null;
- }
- });
- /**
- * A chart {@link Ext.AbstractPlugin plugin} that adds ability to listen to chart series
- * items events. Item event listeners are passed two parameters: the target item and the
- * event itself. The item object has the following properties:
- *
- * * **category** - the category the item falls under: 'items' or 'markers'
- * * **field** - the store field used by this series item
- * * **index** - the index of the series item
- * * **record** - the store record associated with this series item
- * * **series** - the series the item belongs to
- * * **sprite** - the sprite used to represents this series item
- *
- * For example:
- *
- * Ext.create('Ext.chart.CartesianChart', {
- * plugins: {
- * chartitemevents: {
- * moveEvents: true
- * }
- * },
- * store: {
- * fields: ['pet', 'households', 'total'],
- * data: [
- * {pet: 'Cats', households: 38, total: 93},
- * {pet: 'Dogs', households: 45, total: 79},
- * {pet: 'Fish', households: 13, total: 171}
- * ]
- * },
- * axes: [{
- * type: 'numeric',
- * position: 'left'
- * }, {
- * type: 'category',
- * position: 'bottom'
- * }],
- * series: [{
- * type: 'bar',
- * xField: 'pet',
- * yField: 'households',
- * listeners: {
- * itemmousemove: function (series, item, event) {
- * console.log('itemmousemove', item.category, item.field);
- * }
- * }
- * }, {
- * type: 'line',
- * xField: 'pet',
- * yField: 'total',
- * marker: true
- * }],
- * listeners: { // Listen to itemclick events on all series.
- * itemclick: function (chart, item, event) {
- * console.log('itemclick', item.category, item.field);
- * }
- * }
- * });
- *
- */
- Ext.define('Ext.chart.plugin.ItemEvents', {
- extend: 'Ext.plugin.Abstract',
- alias: 'plugin.chartitemevents',
- /**
- * @cfg {Boolean} [moveEvents=false]
- * If `itemmousemove`, `itemmouseover` or `itemmouseout` event listeners are attached
- * to the chart, the plugin will detect those and will hit test series items on
- * every move. However, if the above item events are attached on the series level
- * only, this config has to be set to true, as the plugin won't perform a similar
- * detection on every series.
- */
- moveEvents: false,
- mouseMoveEvents: {
- mousemove: true,
- mouseover: true,
- mouseout: true
- },
- itemMouseMoveEvents: {
- itemmousemove: true,
- itemmouseover: true,
- itemmouseout: true
- },
- init: function(chart) {
- var handleEvent = 'handleEvent';
- this.chart = chart;
- chart.addElementListener({
- click: handleEvent,
- tap: handleEvent,
- dblclick: handleEvent,
- mousedown: handleEvent,
- mousemove: handleEvent,
- mouseup: handleEvent,
- mouseover: handleEvent,
- mouseout: handleEvent,
- // run our handlers before user code
- priority: 1001,
- scope: this
- });
- },
- hasItemMouseMoveListeners: function() {
- var listeners = this.chart.hasListeners,
- name;
- for (name in this.itemMouseMoveEvents) {
- if (name in listeners) {
- return true;
- }
- }
- return false;
- },
- handleEvent: function(e) {
- var me = this,
- chart = me.chart,
- isMouseMoveEvent = e.type in me.mouseMoveEvents,
- lastItem = me.lastItem,
- chartXY, item;
- if (isMouseMoveEvent && !me.hasItemMouseMoveListeners() && !me.moveEvents) {
- return;
- }
- chartXY = chart.getEventXY(e);
- item = chart.getItemForPoint(chartXY[0], chartXY[1]);
- if (isMouseMoveEvent && !Ext.Object.equals(item, lastItem)) {
- if (lastItem) {
- chart.fireEvent('itemmouseout', chart, lastItem, e);
- lastItem.series.fireEvent('itemmouseout', lastItem.series, lastItem, e);
- }
- if (item) {
- chart.fireEvent('itemmouseover', chart, item, e);
- item.series.fireEvent('itemmouseover', item.series, item, e);
- }
- }
- if (item) {
- chart.fireEvent('item' + e.type, chart, item, e);
- item.series.fireEvent('item' + e.type, item.series, item, e);
- }
- me.lastItem = item;
- }
- });
- /**
- * @abstract
- * @class Ext.chart.series.Cartesian
- * @extends Ext.chart.series.Series
- *
- * Common base class for series implementations that plot values using cartesian coordinates.
- *
- * @constructor
- */
- Ext.define('Ext.chart.series.Cartesian', {
- extend: 'Ext.chart.series.Series',
- config: {
- /**
- * @cfg {String} xField
- * The field used to access the x axis value from the items from the data source.
- */
- xField: null,
- /**
- * @cfg {String|String[]} yField
- * The field(s) used to access the y-axis value(s) of the items from the data source.
- */
- yField: null,
- /**
- * @cfg {Ext.chart.axis.Axis|Number|String}
- * xAxis The chart axis the series is bound to in the 'X' direction.
- * Normally, this would be set automatically by the series.
- * For charts with multiple x-axes, this defines which x-axis is used by the series.
- * It refers to either axis' ID or the (zero-based) index of the axis
- * in the chart's {@link Ext.chart.AbstractChart#axes axes} config.
- */
- xAxis: null,
- /**
- * @cfg {Ext.chart.axis.Axis|Number|String}
- * yAxis The chart axis the series is bound to in the 'Y' direction.
- * Normally, this would be set automatically by the series.
- * For charts with multiple y-axes, this defines which y-axis is used by the series.
- * It refers to either axis' ID or the (zero-based) index of the axis
- * in the chart's {@link Ext.chart.AbstractChart#axes axes} config.
- */
- yAxis: null
- },
- directions: [
- 'X',
- 'Y'
- ],
- /**
- * @private
- *
- * Tells which store record fields should be used for a specific axis direction. E.g. for
- *
- * fieldCategory<direction>: ['<fieldConfig1>', '<fieldConfig2>', ...]
- *
- * the field names from the following configs will be used:
- *
- * series.<fieldConfig1>Field, series.<fieldConfig2>Field, ...
- *
- * See {@link Ext.chart.series.StackedCartesian#getFields}.
- *
- */
- fieldCategoryX: [
- 'X'
- ],
- fieldCategoryY: [
- 'Y'
- ],
- applyXAxis: function(newAxis, oldAxis) {
- return this.getChart().getAxis(newAxis) || oldAxis;
- },
- applyYAxis: function(newAxis, oldAxis) {
- return this.getChart().getAxis(newAxis) || oldAxis;
- },
- updateXAxis: function(axis) {
- axis.processData(this);
- },
- updateYAxis: function(axis) {
- axis.processData(this);
- },
- coordinateX: function() {
- return this.coordinate('X', 0, 2);
- },
- coordinateY: function() {
- return this.coordinate('Y', 1, 2);
- },
- getItemForPoint: function(x, y) {
- var me = this,
- sprite = me.getSprites()[0],
- store = me.getStore(),
- point;
- if (sprite && !me.getHidden()) {
- point = sprite.getNearestDataPoint(x, y);
- }
- return point ? {
- series: me,
- sprite: sprite,
- category: me.getItemInstancing() ? 'items' : 'markers',
- index: point.index,
- record: store.getData().items[point.index],
- field: me.getYField(),
- distance: point.distance
- } : null;
- },
- createSprite: function() {
- var me = this,
- sprite = me.callParent(),
- chart = me.getChart(),
- xAxis = me.getXAxis();
- sprite.setAttributes({
- flipXY: chart.getFlipXY(),
- xAxis: xAxis
- });
- if (sprite.setAggregator && xAxis && xAxis.getAggregator) {
- if (xAxis.getAggregator) {
- sprite.setAggregator({
- strategy: xAxis.getAggregator()
- });
- } else {
- sprite.setAggregator({});
- }
- }
- return sprite;
- },
- getSprites: function() {
- var me = this,
- chart = this.getChart(),
- sprites = me.sprites;
- if (!chart) {
- return Ext.emptyArray;
- }
- if (!sprites.length) {
- me.createSprite();
- }
- return sprites;
- },
- getXRange: function() {
- return [
- this.dataRange[0],
- this.dataRange[2]
- ];
- },
- getYRange: function() {
- return [
- this.dataRange[1],
- this.dataRange[3]
- ];
- }
- });
- /**
- * @abstract
- * @extends Ext.chart.series.Cartesian
- * Abstract class for all the stacked cartesian series including area series
- * and bar series.
- */
- Ext.define('Ext.chart.series.StackedCartesian', {
- extend: 'Ext.chart.series.Cartesian',
- config: {
- /**
- * @cfg {Boolean} [stacked=true]
- * `true` to display the series in its stacked configuration.
- */
- stacked: true,
- /**
- * @cfg {Boolean} [splitStacks=true]
- * `true` to stack negative/positive values in respective y-axis directions.
- */
- splitStacks: true,
- /**
- * @cfg {Boolean} [fullStack=false]
- * If `true`, the height of a stacked bar is always the full height of the chart,
- * with individual components viewed as shares of the whole determined by the
- * {@link #fullStackTotal} config.
- */
- fullStack: false,
- /**
- * @cfg {Boolean} [fullStackTotal=100]
- * If the {@link #fullStack} config is set to `true`, this will determine
- * the absolute total value of each stack.
- */
- fullStackTotal: 100,
- /**
- * @cfg {Array} hidden
- */
- hidden: []
- },
- /**
- * @private
- * @property
- * If `true`, each subsequent sprite has a lower zIndex so that the stroke of previous
- * sprite in the stack is not covered by the next sprite (which makes the very top
- * segment look odd in flat bar and area series, especially when wide strokes are used).
- */
- reversedSpriteZOrder: true,
- spriteAnimationCount: 0,
- themeColorCount: function() {
- var me = this,
- yField = me.getYField();
- return Ext.isArray(yField) ? yField.length : 1;
- },
- updateStacked: function() {
- this.processData();
- },
- updateSplitStacks: function() {
- this.processData();
- },
- coordinateY: function() {
- return this.coordinateStacked('Y', 1, 2);
- },
- coordinateStacked: function(direction, directionOffset, directionCount) {
- var me = this,
- store = me.getStore(),
- items = store.getData().items,
- itemCount = items.length,
- axis = me['get' + direction + 'Axis'](),
- hidden = me.getHidden(),
- splitStacks = me.getSplitStacks(),
- fullStack = me.getFullStack(),
- fullStackTotal = me.getFullStackTotal(),
- range = [
- 0,
- 0
- ],
- directions = me['fieldCategory' + direction],
- dataStart = [],
- posDataStart = [],
- negDataStart = [],
- dataEnd,
- stacked = me.getStacked(),
- sprites = me.getSprites(),
- coordinatedData = [],
- i, j, k, fields, fieldCount, posTotals, negTotals, fieldCategoriesItem, data, attr;
- if (!sprites.length) {
- return;
- }
- for (i = 0; i < directions.length; i++) {
- fieldCategoriesItem = directions[i];
- fields = me.getFields([
- fieldCategoriesItem
- ]);
- fieldCount = fields.length;
- for (j = 0; j < itemCount; j++) {
- dataStart[j] = 0;
- posDataStart[j] = 0;
- negDataStart[j] = 0;
- }
- for (j = 0; j < fieldCount; j++) {
- if (!hidden[j]) {
- coordinatedData[j] = me.coordinateData(items, fields[j], axis);
- }
- }
- if (stacked && fullStack) {
- posTotals = [];
- if (splitStacks) {
- negTotals = [];
- }
- for (j = 0; j < itemCount; j++) {
- posTotals[j] = 0;
- if (splitStacks) {
- negTotals[j] = 0;
- }
- for (k = 0; k < fieldCount; k++) {
- data = coordinatedData[k];
- if (!data) {
- // If the field is hidden there's no coordinated data for it.
-
- continue;
- }
- data = data[j];
- if (data >= 0 || !splitStacks) {
- posTotals[j] += data;
- } else if (data < 0) {
- negTotals[j] += data;
- }
- }
- }
- }
- // else not a valid number
- for (j = 0; j < fieldCount; j++) {
- attr = {};
- if (hidden[j]) {
- attr['dataStart' + fieldCategoriesItem] = dataStart;
- attr['data' + fieldCategoriesItem] = dataStart;
- sprites[j].setAttributes(attr);
-
- continue;
- }
- data = coordinatedData[j];
- if (stacked) {
- dataEnd = [];
- for (k = 0; k < itemCount; k++) {
- if (!data[k]) {
- data[k] = 0;
- }
- if (data[k] >= 0 || !splitStacks) {
- if (fullStack && posTotals[k]) {
- data[k] *= fullStackTotal / posTotals[k];
- }
- dataStart[k] = posDataStart[k];
- posDataStart[k] += data[k];
- dataEnd[k] = posDataStart[k];
- } else {
- if (fullStack && negTotals[k]) {
- data[k] *= fullStackTotal / negTotals[k];
- }
- dataStart[k] = negDataStart[k];
- negDataStart[k] += data[k];
- dataEnd[k] = negDataStart[k];
- }
- }
- attr['dataStart' + fieldCategoriesItem] = dataStart;
- attr['data' + fieldCategoriesItem] = dataEnd;
- Ext.chart.Util.expandRange(range, dataStart);
- Ext.chart.Util.expandRange(range, dataEnd);
- } else {
- attr['dataStart' + fieldCategoriesItem] = dataStart;
- attr['data' + fieldCategoriesItem] = data;
- Ext.chart.Util.expandRange(range, data);
- }
- sprites[j].setAttributes(attr);
- }
- }
- range = Ext.chart.Util.validateRange(range, me.defaultRange);
- me.dataRange[directionOffset] = range[0];
- me.dataRange[directionOffset + directionCount] = range[1];
- attr = {};
- attr['dataMin' + direction] = range[0];
- attr['dataMax' + direction] = range[1];
- for (i = 0; i < sprites.length; i++) {
- sprites[i].setAttributes(attr);
- }
- },
- getFields: function(fieldCategory) {
- var me = this,
- fields = [],
- ln = fieldCategory.length,
- i, fieldsItem;
- for (i = 0; i < ln; i++) {
- fieldsItem = me['get' + fieldCategory[i] + 'Field']();
- if (Ext.isArray(fieldsItem)) {
- fields.push.apply(fields, fieldsItem);
- } else {
- fields.push(fieldsItem);
- }
- }
- return fields;
- },
- updateLabelOverflowPadding: function(labelOverflowPadding) {
- var me = this,
- label;
- if (!me.isConfiguring) {
- label = me.getLabel();
- if (label) {
- label.setAttributes({
- labelOverflowPadding: labelOverflowPadding
- });
- }
- }
- },
- updateLabelData: function() {
- var me = this,
- label = me.getLabel();
- if (label) {
- label.setAttributes({
- labelOverflowPadding: me.getLabelOverflowPadding()
- });
- }
- me.callParent();
- },
- getSprites: function() {
- var me = this,
- chart = me.getChart(),
- fields = me.getFields(me.fieldCategoryY),
- itemInstancing = me.getItemInstancing(),
- sprites = me.sprites,
- hidden = me.getHidden(),
- spritesCreated = false,
- fieldCount = fields.length,
- i, sprite;
- if (!chart) {
- return [];
- }
- // Create one Ext.chart.series.sprite.StackedCartesian sprite per field.
- for (i = 0; i < fieldCount; i++) {
- sprite = sprites[i];
- if (!sprite) {
- sprite = me.createSprite();
- sprite.setAttributes({
- zIndex: (me.reversedSpriteZOrder ? -1 : 1) * i
- });
- sprite.setField(fields[i]);
- spritesCreated = true;
- hidden.push(false);
- if (itemInstancing) {
- sprite.getMarker('items').getTemplate().setAttributes(me.getStyleByIndex(i));
- } else {
- sprite.setAttributes(me.getStyleByIndex(i));
- }
- }
- }
- if (spritesCreated) {
- me.updateHidden(hidden);
- }
- return sprites;
- },
- getItemForPoint: function(x, y) {
- var me = this,
- sprites = me.getSprites(),
- store = me.getStore(),
- hidden = me.getHidden(),
- minDistance = Infinity,
- item = null,
- spriteIndex = -1,
- pointIndex = -1,
- point, yField, sprite, i, ln;
- for (i = 0 , ln = sprites.length; i < ln; i++) {
- if (hidden[i]) {
-
- continue;
- }
- sprite = sprites[i];
- point = sprite.getNearestDataPoint(x, y);
- // Don't stop when the first matching point is found.
- // Keep looking for the nearest point.
- if (point) {
- if (point.distance < minDistance) {
- minDistance = point.distance;
- pointIndex = point.index;
- spriteIndex = i;
- }
- }
- }
- if (spriteIndex > -1) {
- yField = me.getYField();
- item = {
- series: me,
- sprite: sprites[spriteIndex],
- category: me.getItemInstancing() ? 'items' : 'markers',
- index: pointIndex,
- record: store.getData().items[pointIndex],
- // Handle the case where we're stacked but a single segment
- field: typeof yField === 'string' ? yField : yField[spriteIndex],
- distance: minDistance
- };
- }
- return item;
- },
- provideLegendInfo: function(target) {
- var me = this,
- sprites = me.getSprites(),
- title = me.getTitle(),
- field = me.getYField(),
- hidden = me.getHidden(),
- single = sprites.length === 1,
- style, fill, i, name;
- for (i = 0; i < sprites.length; i++) {
- style = me.getStyleByIndex(i);
- fill = style.fillStyle;
- if (title) {
- if (Ext.isArray(title)) {
- name = title[i];
- } else if (single) {
- name = title;
- }
- }
- if (!title || !name) {
- if (Ext.isArray(field)) {
- name = field[i];
- } else {
- name = me.getId();
- }
- }
- target.push({
- name: name,
- mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
- disabled: hidden[i],
- series: me.getId(),
- index: i
- });
- }
- },
- onSpriteAnimationStart: function(sprite) {
- this.spriteAnimationCount++;
- if (this.spriteAnimationCount === 1) {
- this.fireEvent('animationstart');
- }
- },
- onSpriteAnimationEnd: function(sprite) {
- this.spriteAnimationCount--;
- if (this.spriteAnimationCount === 0) {
- this.fireEvent('animationend');
- }
- }
- });
- /**
- * Base class for all series sprites.
- * Defines attributes common to all series sprites, like data in x/y directions and its
- * min/max values, and configs, like the {@link Ext.chart.series.Series} instance that manages
- * the sprite.
- *
- */
- Ext.define('Ext.chart.series.sprite.Series', {
- extend: 'Ext.draw.sprite.Sprite',
- mixins: {
- markerHolder: 'Ext.chart.MarkerHolder'
- },
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Number} [dataMinX=0] Data minimum on the x-axis.
- */
- dataMinX: 'number',
- /**
- * @cfg {Number} [dataMaxX=1] Data maximum on the x-axis.
- */
- dataMaxX: 'number',
- /**
- * @cfg {Number} [dataMinY=0] Data minimum on the y-axis.
- */
- dataMinY: 'number',
- /**
- * @cfg {Number} [dataMaxY=1] Data maximum on the y-axis.
- */
- dataMaxY: 'number',
- /**
- * @cfg {Array} [rangeX=null] Data range derived from all the series bound
- * to the x-axis.
- */
- rangeX: 'data',
- /**
- * @cfg {Array} [rangeY=null] Data range derived from all the series bound
- * to the y-axis.
- */
- rangeY: 'data',
- /**
- * @cfg {Object} [dataX=null] Data items on the x-axis.
- */
- dataX: 'data',
- /**
- * @cfg {Object} [dataY=null] Data items on the y-axis.
- */
- dataY: 'data',
- /**
- * @cfg {Object} [labels=null] Labels used in the series.
- */
- labels: 'default',
- /**
- * @cfg {Number} [labelOverflowPadding=10] Padding around labels to determine
- * overlap.
- */
- labelOverflowPadding: 'number'
- },
- defaults: {
- dataMinX: 0,
- dataMaxX: 1,
- dataMinY: 0,
- dataMaxY: 1,
- rangeX: null,
- rangeY: null,
- dataX: null,
- dataY: null,
- labels: null,
- labelOverflowPadding: 10
- },
- triggers: {
- dataX: 'bbox',
- dataY: 'bbox',
- dataMinX: 'bbox',
- dataMaxX: 'bbox',
- dataMinY: 'bbox',
- dataMaxY: 'bbox'
- }
- }
- },
- config: {
- /**
- * @private
- * @cfg {Object} store The store that is passed to the renderer.
- */
- store: null,
- series: null,
- /**
- * @cfg {String} field The store field used by the series.
- */
- field: null
- }
- });
- /**
- * Cartesian sprite.
- */
- Ext.define('Ext.chart.series.sprite.Cartesian', {
- extend: 'Ext.chart.series.sprite.Series',
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Number} [selectionTolerance=20]
- * The distance from the event position to the sprite's data points to trigger
- * interactions (used for 'iteminfo', etc).
- */
- selectionTolerance: 'number',
- /**
- * @cfg {Boolean} flipXY If flipXY is 'true', the series is flipped.
- */
- flipXY: 'bool',
- renderer: 'default',
- // Visible range of data (pan/zoom) information.
- visibleMinX: 'number',
- visibleMinY: 'number',
- visibleMaxX: 'number',
- visibleMaxY: 'number',
- innerWidth: 'number',
- innerHeight: 'number'
- },
- defaults: {
- selectionTolerance: 20,
- flipXY: false,
- renderer: null,
- transformFillStroke: false,
- visibleMinX: 0,
- visibleMinY: 0,
- visibleMaxX: 1,
- visibleMaxY: 1,
- innerWidth: 1,
- innerHeight: 1
- },
- triggers: {
- dataX: 'dataX,bbox',
- dataY: 'dataY,bbox',
- visibleMinX: 'panzoom',
- visibleMinY: 'panzoom',
- visibleMaxX: 'panzoom',
- visibleMaxY: 'panzoom',
- innerWidth: 'panzoom',
- innerHeight: 'panzoom'
- },
- updaters: {
- dataX: function(attr) {
- this.processDataX();
- this.scheduleUpdater(attr, 'dataY', [
- 'dataY'
- ]);
- },
- dataY: function() {
- this.processDataY();
- },
- panzoom: function(attr) {
- // dx, dy are deltas between min & max of coordinated data values.
- var dx = attr.visibleMaxX - attr.visibleMinX,
- dy = attr.visibleMaxY - attr.visibleMinY,
- innerWidth = attr.flipXY ? attr.innerHeight : attr.innerWidth,
- innerHeight = !attr.flipXY ? attr.innerHeight : attr.innerWidth,
- surface = this.getSurface(),
- isRtl = surface ? surface.getInherited().rtl : false;
- attr.scalingCenterX = 0;
- attr.scalingCenterY = 0;
- attr.scalingX = innerWidth / dx;
- attr.scalingY = innerHeight / dy;
- // (attr.visibleMinY * attr.scalingY) will be the vertical position of
- // our minimum data points, which we want to be at zero, so we offset
- // by this amount.
- attr.translationX = -(attr.visibleMinX * attr.scalingX);
- attr.translationY = -(attr.visibleMinY * attr.scalingY);
- if (isRtl && !attr.flipXY) {
- attr.scalingX *= -1;
- attr.translationX *= -1;
- attr.translationX += innerWidth;
- }
- this.applyTransformations(true);
- }
- }
- }
- },
- processDataY: Ext.emptyFn,
- processDataX: Ext.emptyFn,
- updatePlainBBox: function(plain) {
- var attr = this.attr;
- plain.x = attr.dataMinX;
- plain.y = attr.dataMinY;
- plain.width = attr.dataMaxX - attr.dataMinX;
- plain.height = attr.dataMaxY - attr.dataMinY;
- },
- /**
- * Does a binary search of the data on the x-axis using the given key.
- * @param {String} key
- * @return {*}
- */
- binarySearch: function(key) {
- var dx = this.attr.dataX,
- start = 0,
- end = dx.length,
- mid, val;
- if (key <= dx[0]) {
- return start;
- }
- if (key >= dx[end - 1]) {
- return end - 1;
- }
- while (start + 1 < end) {
- mid = (start + end) >> 1;
- val = dx[mid];
- if (val === key) {
- return mid;
- } else if (val < key) {
- start = mid;
- } else {
- end = mid;
- }
- }
- return start;
- },
- render: function(surface, ctx, surfaceClipRect) {
- var me = this,
- attr = me.attr,
- margin = 1,
- // TODO: why do we need it?
- inverseMatrix = attr.inverseMatrix.clone(),
- dataClipRect;
- // The sprite's `attr.matrix` is stretching/shrinking data coordinates
- // to surface coordinates.
- // This matrix is set (indirectly) by the 'panzoom' updater.
- // The sprite's `attr.inverseMatrix` does the opposite.
- //
- // The `surface.matrix` of the 'series' surface of a cartesian chart flips the
- // surface content vertically, so that y=0 is at the bottom (look for
- // `surface.matrix.set` call in the CartesianChart.performLayout method).
- // This matrix is set in the 'performLayout' of the CartesianChart.
- // The `surface.inverseMatrix` flips the content back.
- //
- // By combining the inverse matrices of the series surface and the series sprite,
- // we essentially get a transformation that allows us to go from surface coordinates
- // in a final flipped drawing back to data points.
- //
- // For example
- //
- // inverseMatrix.transformPoint([ 0, rect[3] ])
- // inverseMatrix.transformPoint([ rect[2], 0 ])
- //
- // will return
- //
- // [attr.dataMinX, attr.dataMinY]
- // [attr.dataMaxX, attr.dataMaxY]
- //
- // because left/bottom and top/right of the series surface is where the first smallest
- // and last largest data points would be (given no pan/zoom), respectively.
- //
- // So the `dataClipRect` passed to the `renderClipped` call below is effectively
- // the visible rect in data (not surface!) coordinates.
- // It is important to note, that the all the scaling and translation is defined
- // by the sprite's matrix, the 'series' surface matrix does not contain scaling
- // or translation components, except for the vertical flipping.
- // This is important because there is a common pattern in chart series sprites
- // (MarkerHolders) - instead of using transform attributes for their Markers
- // (e.g. instances of a 'rect' sprite in case of 'bar' series), the attributes
- // that would position a sprite with no transformations are transformed.
- // For example, to draw a rect with coordinates TL(10, 10), BR(20, 40),
- // we could use the folling 'rect' sprite attributes:
- //
- // {
- // x: 0,
- // y: 0
- // width: 10,
- // height: 30
- //
- // translationX: 10,
- // translationY: 10
- //
- // But the correct thing to do here is
- //
- // {
- // x: 10,
- // y: 10,
- // width: 10,
- // height: 30
- // }
- //
- // Similarly, if the sprite was scaled, the 'x', 'y', 'width', 'height' attributes
- // would have to account for that as well.
- //
- // This is done, so that the attribute values a marker gets by the time it renders,
- // are the final values, and are not affected later by other transforms, such as
- // surface matrix scaling, which could ruin the visual result, if the attributes
- // values are doctored to make lines align to the pixel grid (which is typically
- // the case).
- inverseMatrix.appendMatrix(surface.inverseMatrix);
- if (attr.dataX === null || attr.dataX === undefined) {
- return;
- }
- if (attr.dataY === null || attr.dataY === undefined) {
- return;
- }
- if (inverseMatrix.getXX() * inverseMatrix.getYX() || inverseMatrix.getXY() * inverseMatrix.getYY()) {
- Ext.Logger.warn('Cartesian Series sprite does not support rotation/sheering');
- return;
- }
- dataClipRect = inverseMatrix.transformList([
- [
- surfaceClipRect[0] - margin,
- surfaceClipRect[3] + margin
- ],
- // (left, height)
- [
- surfaceClipRect[0] + surfaceClipRect[2] + margin,
- -margin
- ]
- ]);
- // (width, top)
- dataClipRect = dataClipRect[0].concat(dataClipRect[1]);
- // TODO: RTL improvements:
- // TODO: produce such a dataClipRect here, so that we don't have to do:
- // TODO: min = Math.min(dataClipRect[0], dataClipRect[2])
- // TODO: max = Math.max(dataClipRect[0], dataClipRect[2])
- // TODO: inside each 'renderClipped' call
- me.renderClipped(surface, ctx, dataClipRect, surfaceClipRect);
- },
- /**
- * Render the given visible clip range.
- * @param {Ext.draw.Surface} surface A draw container surface.
- * @param {CanvasRenderingContext2D} ctx A context object that is API compatible with the native
- * [CanvasRenderingContext2D](https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D).
- * @param {Number[]} dataClipRect The clip rect in data coordinates, roughly equivalent to
- * [attr.dataMinX, attr.dataMinY, attr.dataMaxX, attr.dataMaxY] for an untranslated/unscaled
- * surface/sprite.
- * @param {Number[]} surfaceClipRect The clip rect in surface coordinates:
- * [left, top, width, height].
- * @method
- */
- renderClipped: Ext.emptyFn,
- /**
- * Get the nearest item index from point (x, y). -1 as not found.
- * @param {Number} x
- * @param {Number} y
- * @return {Number} The index
- * @deprecated 6.5.2 Use {@link #getNearestDataPoint} instead.
- */
- getIndexNearPoint: function(x, y) {
- var result = this.getNearestDataPoint(x, y);
- return result ? result.index : -1;
- },
- /**
- * Given a point in 'series' surface element coordinates, returns the `index` of the
- * sprite's data point that is nearest to that point, along with the `distance`
- * between points.
- * If the `selectionTolerance` attribute of the sprite is not zero, only the data points
- * that are within that pixel distance from the given point will be checked.
- * In the event no such data points exist or the data is empty, `null` is returned.
- *
- * Notes:
- * 1) given a mouse/pointer event object, the surface coordinates of the event can be
- * obtained with the `getEventXY` method of the chart;
- * 2) using `selectionTolerance` of zero is useful for series with no visible markers,
- * such as the Area series, where this attribute becomes meaningless.
- *
- * @param {Number} x
- * @param {Number} y
- * @return {Object}
- */
- getNearestDataPoint: function(x, y) {
- var me = this,
- attr = me.attr,
- series = me.getSeries(),
- surface = me.getSurface(),
- items = me.boundMarkers.items,
- matrix = attr.matrix,
- dataX = attr.dataX,
- dataY = attr.dataY,
- selectionTolerance = attr.selectionTolerance,
- minDistance = Infinity,
- index = -1,
- result = null,
- distance, dx, dy, xy, i, ln, end, inc, bbox;
- // Notes:
- // Instead of converting the given point from surface coordinates to data coordinates
- // and then measuring the distances between it and the data points, we have to
- // convert all the data points to surface coordinates and measure the distances
- // between them and the given point. This is because the data coordinates can use
- // different scales, which makes distance measurement impossible.
- // For example, if the x-axis is a `category` axis, the categories will be assigned
- // indexes starting from 0, that's what the `attr.dataX` array will contain;
- // and if the y-axis is a `numeric` axis, the `attr.dataY` array will simply contain
- // the original values.
- //
- // Either 'items' or 'markers' will be highlighted. If a sprite has both (for example,
- // 'bar' series with the 'marker' config, where the bars are 'items' and marker instances
- // are 'markers'), only the 'items' (bars) will be highlighted.
- if (items) {
- ln = dataX.length;
- if (series.reversedSpriteZOrder) {
- i = ln - 1;
- end = -1;
- inc = -1;
- } else {
- i = 0;
- end = ln;
- inc = 1;
- }
- for (; i !== end; i += inc) {
- bbox = me.getMarkerBBox('items', i);
- // Transform the given surface element coordinates to logical coordinates
- // of the surface (the ones the bbox uses).
- xy = surface.inverseMatrix.transformPoint([
- x,
- y
- ]);
- if (Ext.draw.Draw.isPointInBBox(xy[0], xy[1], bbox)) {
- index = i;
- minDistance = 0;
- // Return the first item that contains our touch point.
- break;
- }
- }
- } else {
- // markers
- for (i = 0 , ln = dataX.length; i < ln; i++) {
- // Convert from data coordinates to coordinates within inner size rectangle.
- // See `panzoom` method for more details.
- xy = matrix.transformPoint([
- dataX[i],
- dataY[i]
- ]);
- // Flip back vertically and padding adjust (see `render` method comments).
- xy = surface.matrix.transformPoint(xy);
- // Essentially sprites go through the same two transformations when they render
- // data points.
- dx = x - xy[0];
- dy = y - xy[1];
- distance = Math.sqrt(dx * dx + dy * dy);
- if (selectionTolerance && distance > selectionTolerance) {
-
- continue;
- }
- if (distance < minDistance) {
- minDistance = distance;
- index = i;
- }
- }
- }
- // Keep looking for the nearest marker.
- if (index > -1) {
- result = {
- index: index,
- distance: minDistance
- };
- }
- return result;
- }
- });
- /**
- * @class Ext.chart.series.sprite.StackedCartesian
- * @extends Ext.chart.series.sprite.Cartesian
- *
- * Stacked cartesian sprite.
- */
- Ext.define('Ext.chart.series.sprite.StackedCartesian', {
- extend: 'Ext.chart.series.sprite.Cartesian',
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @private
- * @cfg {Number} [groupCount=1] The number of items (e.g. bars) in a group.
- */
- groupCount: 'number',
- /**
- * @private
- * @cfg {Number} [groupOffset=0] The group index of the series sprite.
- */
- groupOffset: 'number',
- /**
- * @private
- * @cfg {Object} [dataStartY=null] The starting point of the data
- * used in the series.
- */
- dataStartY: 'data'
- },
- defaults: {
- selectionTolerance: 20,
- groupCount: 1,
- groupOffset: 0,
- dataStartY: null
- },
- triggers: {
- dataStartY: 'dataY,bbox'
- }
- }
- }
- });
- /**
- * @class Ext.chart.series.sprite.Area
- * @extends Ext.chart.series.sprite.StackedCartesian
- *
- * Area series sprite.
- */
- Ext.define('Ext.chart.series.sprite.Area', {
- alias: 'sprite.areaSeries',
- extend: 'Ext.chart.series.sprite.StackedCartesian',
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Boolean} [step=false] 'true' if the area is represented with steps
- * instead of lines.
- */
- step: 'bool'
- },
- defaults: {
- selectionTolerance: 0,
- step: false
- }
- }
- },
- renderClipped: function(surface, ctx, dataClipRect) {
- var me = this,
- store = me.getStore(),
- series = me.getSeries(),
- attr = me.attr,
- dataX = attr.dataX,
- dataY = attr.dataY,
- dataStartY = attr.dataStartY,
- matrix = attr.matrix,
- x, y, i, lastX, lastY, startX, startY,
- xx = matrix.elements[0],
- dx = matrix.elements[4],
- yy = matrix.elements[3],
- dy = matrix.elements[5],
- surfaceMatrix = me.surfaceMatrix,
- markerCfg = {},
- min = Math.min(dataClipRect[0], dataClipRect[2]),
- max = Math.max(dataClipRect[0], dataClipRect[2]),
- start = Math.max(0, this.binarySearch(min)),
- end = Math.min(dataX.length - 1, this.binarySearch(max) + 1),
- renderer = attr.renderer,
- rendererData = {
- store: store
- },
- rendererChanges;
- ctx.beginPath();
- startX = dataX[start] * xx + dx;
- startY = dataY[start] * yy + dy;
- ctx.moveTo(startX, startY);
- if (attr.step) {
- lastY = startY;
- for (i = start; i <= end; i++) {
- x = dataX[i] * xx + dx;
- y = dataY[i] * yy + dy;
- ctx.lineTo(x, lastY);
- ctx.lineTo(x, lastY = y);
- }
- } else {
- for (i = start; i <= end; i++) {
- x = dataX[i] * xx + dx;
- y = dataY[i] * yy + dy;
- ctx.lineTo(x, y);
- }
- }
- if (dataStartY) {
- if (attr.step) {
- lastX = dataX[end] * xx + dx;
- for (i = end; i >= start; i--) {
- x = dataX[i] * xx + dx;
- y = dataStartY[i] * yy + dy;
- ctx.lineTo(lastX, y);
- ctx.lineTo(lastX = x, y);
- }
- } else {
- for (i = end; i >= start; i--) {
- x = dataX[i] * xx + dx;
- y = dataStartY[i] * yy + dy;
- ctx.lineTo(x, y);
- }
- }
- } else {
- ctx.lineTo(dataX[end] * xx + dx, y);
- ctx.lineTo(dataX[end] * xx + dx, dy);
- ctx.lineTo(startX, dy);
- ctx.lineTo(startX, dataY[i] * yy + dy);
- }
- if (attr.transformFillStroke) {
- attr.matrix.toContext(ctx);
- }
- ctx.fill();
- if (attr.transformFillStroke) {
- attr.inverseMatrix.toContext(ctx);
- }
- ctx.beginPath();
- ctx.moveTo(startX, startY);
- if (attr.step) {
- for (i = start; i <= end; i++) {
- x = dataX[i] * xx + dx;
- y = dataY[i] * yy + dy;
- ctx.lineTo(x, lastY);
- ctx.lineTo(x, lastY = y);
- markerCfg.translationX = surfaceMatrix.x(x, y);
- markerCfg.translationY = surfaceMatrix.y(x, y);
- if (renderer) {
- // callback(fn, scope, args, delay, caller)
- rendererChanges = Ext.callback(renderer, null, [
- me,
- markerCfg,
- rendererData,
- i
- ], 0, series);
- Ext.apply(markerCfg, rendererChanges);
- }
- me.putMarker('markers', markerCfg, i, !renderer);
- }
- } else {
- for (i = start; i <= end; i++) {
- x = dataX[i] * xx + dx;
- y = dataY[i] * yy + dy;
- ctx.lineTo(x, y);
- markerCfg.translationX = surfaceMatrix.x(x, y);
- markerCfg.translationY = surfaceMatrix.y(x, y);
- if (renderer) {
- rendererChanges = Ext.callback(renderer, null, [
- me,
- markerCfg,
- rendererData,
- i
- ], 0, series);
- Ext.apply(markerCfg, rendererChanges);
- }
- me.putMarker('markers', markerCfg, i, !renderer);
- }
- }
- if (attr.transformFillStroke) {
- attr.matrix.toContext(ctx);
- }
- ctx.stroke();
- }
- });
- /**
- * @class Ext.chart.series.Area
- * @extends Ext.chart.series.StackedCartesian
- *
- * Creates an Area Chart.
- *
- * @example
- * Ext.create({
- * xtype: 'cartesian',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * insetPadding: 40,
- * store: {
- * fields: ['name', 'data1', 'data2', 'data3'],
- * data: [{
- * name: 'metric one',
- * data1: 10,
- * data2: 12,
- * data3: 14
- * }, {
- * name: 'metric two',
- * data1: 7,
- * data2: 8,
- * data3: 16
- * }, {
- * name: 'metric three',
- * data1: 5,
- * data2: 2,
- * data3: 14
- * }, {
- * name: 'metric four',
- * data1: 2,
- * data2: 14,
- * data3: 6
- * }, {
- * name: 'metric five',
- * data1: 27,
- * data2: 38,
- * data3: 36
- * }]
- * },
- * axes: [{
- * type: 'numeric',
- * position: 'left',
- * fields: ['data1'],
- * grid: true,
- * minimum: 0
- * }, {
- * type: 'category',
- * position: 'bottom',
- * fields: ['name']
- * }],
- * series: {
- * type: 'area',
- * subStyle: {
- * fill: ['#0A3F50', '#30BDA7', '#96D4C6']
- * },
- * xField: 'name',
- * yField: ['data1', 'data2', 'data3']
- * }
- * });
- */
- Ext.define('Ext.chart.series.Area', {
- extend: 'Ext.chart.series.StackedCartesian',
- alias: 'series.area',
- type: 'area',
- /**
- * @property seriesType
- * @inheritdoc
- */
- seriesType: 'areaSeries',
- isArea: true,
- requires: [
- 'Ext.chart.series.sprite.Area'
- ],
- config: {
- /**
- * @cfg splitStacks
- * @inheritdoc
- */
- splitStacks: false
- }
- });
- /**
- * @cfg renderer
- * @inheritdoc
- * Area series renderers only affect markers.
- * For styling individual segments with a renderer it is possible to use
- * the Line series with {@link Ext.chart.series.Line#fill} config set to `true`,
- * which makes Line series look like Area series.
- */
- /**
- * @class Ext.chart.series.sprite.Bar
- * @extends Ext.chart.series.sprite.StackedCartesian
- *
- * Draws a sprite used in the bar series.
- */
- Ext.define('Ext.chart.series.sprite.Bar', {
- alias: 'sprite.barSeries',
- extend: 'Ext.chart.series.sprite.StackedCartesian',
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Number} [minBarWidth=2] The minimum bar width.
- */
- minBarWidth: 'number',
- /**
- * @cfg {Number} [maxBarWidth=100] The maximum bar width.
- */
- maxBarWidth: 'number',
- /**
- * @cfg {Number} [minGapWidth=5] The minimum gap between bars.
- */
- minGapWidth: 'number',
- /**
- * @cfg {Number} [radius=0] The degree of rounding for rounded bars.
- */
- radius: 'number',
- /**
- * @cfg {Number} [inGroupGapWidth=3] The gap between grouped bars.
- */
- inGroupGapWidth: 'number'
- },
- defaults: {
- minBarWidth: 2,
- maxBarWidth: 100,
- minGapWidth: 5,
- inGroupGapWidth: 3,
- radius: 0
- }
- }
- },
- drawLabel: function(text, dataX, dataStartY, dataY, labelId) {
- var me = this,
- attr = me.attr,
- label = me.getMarker('labels'),
- labelTpl = label.getTemplate(),
- labelCfg = me.labelCfg || (me.labelCfg = {}),
- surfaceMatrix = me.surfaceMatrix,
- labelOverflowPadding = attr.labelOverflowPadding,
- labelDisplay = labelTpl.attr.display,
- labelOrientation = labelTpl.attr.orientation,
- isVerticalText = (labelOrientation === 'horizontal' && attr.flipXY) || (labelOrientation === 'vertical' && !attr.flipXY) || !labelOrientation,
- calloutLine = labelTpl.getCalloutLine(),
- labelY, halfText, labelBBox, calloutLineLength, changes, hasPendingChanges, params;
- // The coordinates below (data point converted to surface coordinates)
- // are just for the renderer to give it a notion of where the label will be positioned.
- // The actual position of the label will be different
- // (unless the renderer returns x/y coordinates in the changes object)
- // and depend on several things including the size of the text,
- // which has to be measured after the renderer call,
- // since text can be modified by the renderer.
- labelCfg.x = surfaceMatrix.x(dataX, dataY);
- labelCfg.y = surfaceMatrix.y(dataX, dataY);
- if (calloutLine) {
- calloutLineLength = calloutLine.length;
- } else {
- calloutLineLength = 0;
- }
- // Set defaults
- if (!attr.flipXY) {
- labelCfg.rotationRads = -Math.PI * 0.5;
- } else {
- labelCfg.rotationRads = 0;
- }
- labelCfg.calloutVertical = !attr.flipXY;
- // Check if we have a specific orientation specified, if so, set
- // the appropriate values.
- switch (labelOrientation) {
- case 'horizontal':
- labelCfg.rotationRads = 0;
- labelCfg.calloutVertical = false;
- break;
- case 'vertical':
- labelCfg.rotationRads = -Math.PI * 0.5;
- labelCfg.calloutVertical = true;
- break;
- }
- labelCfg.text = text;
- if (labelTpl.attr.renderer) {
- // The label instance won't exist on first render before the renderer is called,
- // it's only created later by `me.putMarker` after the renderer call. To make
- // sure the renderer always can access the label instance, we make this check here.
- if (!label.get(labelId)) {
- label.putMarkerFor('labels', {}, labelId);
- }
- params = [
- text,
- label,
- labelCfg,
- {
- store: me.getStore()
- },
- labelId
- ];
- changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
- if (typeof changes === 'string') {
- labelCfg.text = changes;
- } else if (typeof changes === 'object') {
- if ('text' in changes) {
- labelCfg.text = changes.text;
- }
- hasPendingChanges = true;
- }
- }
- labelBBox = me.getMarkerBBox('labels', labelId, true);
- if (!labelBBox) {
- me.putMarker('labels', labelCfg, labelId);
- labelBBox = me.getMarkerBBox('labels', labelId, true);
- }
- if (calloutLineLength > 0) {
- halfText = calloutLineLength;
- } else if (calloutLineLength === 0) {
- halfText = (isVerticalText ? labelBBox.width : labelBBox.height) / 2;
- } else {
- halfText = (isVerticalText ? labelBBox.width : labelBBox.height) / 2 + labelOverflowPadding;
- }
- if (dataStartY > dataY) {
- halfText = -halfText;
- }
- if (isVerticalText) {
- labelY = (labelDisplay === 'insideStart') ? dataStartY + halfText : dataY - halfText;
- } else {
- labelY = (labelDisplay === 'insideStart') ? dataStartY + labelOverflowPadding * 2 : dataY - labelOverflowPadding * 2;
- }
- labelCfg.x = surfaceMatrix.x(dataX, labelY);
- labelCfg.y = surfaceMatrix.y(dataX, labelY);
- labelY = (labelDisplay === 'insideStart') ? dataStartY : dataY;
- labelCfg.calloutStartX = surfaceMatrix.x(dataX, labelY);
- labelCfg.calloutStartY = surfaceMatrix.y(dataX, labelY);
- labelY = (labelDisplay === 'insideStart') ? dataStartY - halfText : dataY + halfText;
- labelCfg.calloutPlaceX = surfaceMatrix.x(dataX, labelY);
- labelCfg.calloutPlaceY = surfaceMatrix.y(dataX, labelY);
- labelCfg.calloutColor = (calloutLine && calloutLine.color) || me.attr.fillStyle;
- if (calloutLine) {
- if (calloutLine.width) {
- labelCfg.calloutWidth = calloutLine.width;
- }
- } else {
- labelCfg.calloutColor = 'none';
- }
- if (dataStartY > dataY) {
- halfText = -halfText;
- }
- if (Math.abs(dataY - dataStartY) <= halfText * 2 || labelDisplay === 'outside') {
- labelCfg.callout = 1;
- } else {
- labelCfg.callout = 0;
- }
- if (hasPendingChanges) {
- Ext.apply(labelCfg, changes);
- }
- me.putMarker('labels', labelCfg, labelId);
- },
- drawBar: function(ctx, surface, rect, left, top, right, bottom, index) {
- var me = this,
- itemCfg = {},
- renderer = me.attr.renderer,
- changes;
- itemCfg.x = left;
- itemCfg.y = top;
- itemCfg.width = right - left;
- itemCfg.height = bottom - top;
- itemCfg.radius = me.attr.radius;
- if (renderer) {
- changes = Ext.callback(renderer, null, [
- me,
- itemCfg,
- {
- store: me.getStore()
- },
- index
- ], 0, me.getSeries());
- Ext.apply(itemCfg, changes);
- }
- me.putMarker('items', itemCfg, index, !renderer);
- },
- renderClipped: function(surface, ctx, dataClipRect) {
- if (this.cleanRedraw) {
- return;
- }
- // eslint-disable-next-line vars-on-top
- var me = this,
- attr = me.attr,
- dataX = attr.dataX,
- dataY = attr.dataY,
- dataText = attr.labels,
- dataStartY = attr.dataStartY,
- groupCount = attr.groupCount,
- groupOffset = attr.groupOffset - (groupCount - 1) * 0.5,
- inGroupGapWidth = attr.inGroupGapWidth,
- lineWidth = ctx.lineWidth,
- matrix = attr.matrix,
- xx = matrix.elements[0],
- yy = matrix.elements[3],
- dx = matrix.elements[4],
- dy = surface.roundPixel(matrix.elements[5]) - 1,
- maxBarWidth = Math.abs(xx) - attr.minGapWidth,
- minBarWidth = (Math.min(maxBarWidth, attr.maxBarWidth) - inGroupGapWidth * (groupCount - 1)) / groupCount,
- barWidth = surface.roundPixel(Math.max(attr.minBarWidth, minBarWidth)),
- surfaceMatrix = me.surfaceMatrix,
- left, right, bottom, top, i, center,
- halfLineWidth = 0.5 * attr.lineWidth,
- // Finding min/max so that bars render properly in both LTR and RTL modes.
- min = Math.min(dataClipRect[0], dataClipRect[2]),
- max = Math.max(dataClipRect[0], dataClipRect[2]),
- start = Math.max(0, Math.floor(min)),
- end = Math.min(dataX.length - 1, Math.ceil(max)),
- isDrawLabels = dataText && me.getMarker('labels'),
- yLow, yHi;
- // The scaling (xx) and translation (dx) here will already be such that the midpoints
- // of the first and last bars are not at the surface edges (which would mean that
- // bars are half-clipped), but padded, so that those bars are fully visible
- // (assuming no pan/zoom).
- for (i = start; i <= end; i++) {
- yLow = dataStartY ? dataStartY[i] : 0;
- yHi = dataY[i];
- center = dataX[i] * xx + dx + groupOffset * (barWidth + inGroupGapWidth);
- left = surface.roundPixel(center - barWidth / 2) + halfLineWidth;
- top = surface.roundPixel(yHi * yy + dy + lineWidth);
- right = surface.roundPixel(center + barWidth / 2) - halfLineWidth;
- bottom = surface.roundPixel(yLow * yy + dy + lineWidth);
- me.drawBar(ctx, surface, dataClipRect, left, top - halfLineWidth, right, bottom - halfLineWidth, i);
- // We want 0 values to be passed to the renderer
- if (isDrawLabels && dataText[i] != null) {
- me.drawLabel(dataText[i], center, bottom, top, i);
- }
- me.putMarker('markers', {
- translationX: surfaceMatrix.x(center, top),
- translationY: surfaceMatrix.y(center, top)
- }, i, true);
- }
- }
- });
- /**
- * @class Ext.chart.series.Bar
- * @extends Ext.chart.series.StackedCartesian
- *
- * Creates a Bar or Column Chart (depending on the value of the
- * {@link Ext.chart.CartesianChart#flipXY flipXY} config).
- *
- * Note: 'bar' series is meant to be used with the
- * {@link Ext.chart.axis.Category 'category'} axis as its x-axis.
- *
- * @example
- * Ext.create({
- * xtype: 'cartesian',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * store: {
- * fields: ['name', 'value'],
- * data: [{
- * name: 'metric one',
- * value: 10
- * }, {
- * name: 'metric two',
- * value: 7
- * }, {
- * name: 'metric three',
- * value: 5
- * }, {
- * name: 'metric four',
- * value: 2
- * }, {
- * name: 'metric five',
- * value: 27
- * }]
- * },
- * axes: [{
- * type: 'numeric',
- * position: 'left',
- * title: {
- * text: 'Sample Values',
- * fontSize: 15
- * },
- * fields: 'value'
- * }, {
- * type: 'category',
- * position: 'bottom',
- * title: {
- * text: 'Sample Values',
- * fontSize: 15
- * },
- * fields: 'name'
- * }],
- * series: {
- * type: 'bar',
- * subStyle: {
- * fill: ['#388FAD'],
- * stroke: '#1F6D91'
- * },
- * xField: 'name',
- * yField: 'value'
- * }
- * });
- */
- Ext.define('Ext.chart.series.Bar', {
- extend: 'Ext.chart.series.StackedCartesian',
- alias: 'series.bar',
- type: 'bar',
- seriesType: 'barSeries',
- isBar: true,
- requires: [
- 'Ext.chart.series.sprite.Bar',
- 'Ext.draw.sprite.Rect'
- ],
- config: {
- /**
- * @private
- * @cfg {Object} itemInstancing Sprite template used for series.
- */
- itemInstancing: {
- type: 'rect',
- animation: {
- customDurations: {
- x: 0,
- y: 0,
- width: 0,
- height: 0,
- radius: 0
- }
- }
- }
- },
- getItemForPoint: function(x, y) {
- var chart, padding, isRtl;
- if (this.getSprites().length) {
- chart = this.getChart();
- padding = chart.getInnerPadding();
- isRtl = chart.getInherited().rtl;
- // Convert the coordinates because the "items" sprites that draw
- // the bars ignore the chart's InnerPadding.
- arguments[0] = x + (isRtl ? padding.right : -padding.left);
- arguments[1] = y + padding.bottom;
- return this.callParent(arguments);
- }
- },
- updateXAxis: function(xAxis) {
- //<debug>
- if (!this.is3D && !xAxis.isCategory) {
- Ext.raise("'bar' series should be used with a 'category' axis. " + "Please refer to the bar series docs.");
- }
- //</debug>
- xAxis.setExpandRangeBy(0.5);
- this.callParent(arguments);
- },
- updateHidden: function(hidden) {
- this.callParent(arguments);
- this.updateStacked();
- },
- updateStacked: function(stacked) {
- var me = this,
- attributes = {},
- sprites = me.getSprites(),
- spriteCount = sprites.length,
- visibleSprites = [],
- visibleSpriteCount, i;
- for (i = 0; i < spriteCount; i++) {
- if (!sprites[i].attr.hidden) {
- visibleSprites.push(sprites[i]);
- }
- }
- visibleSpriteCount = visibleSprites.length;
- if (me.getStacked()) {
- attributes.groupCount = 1;
- attributes.groupOffset = 0;
- for (i = 0; i < visibleSpriteCount; i++) {
- visibleSprites[i].setAttributes(attributes);
- }
- } else {
- attributes.groupCount = visibleSpriteCount;
- for (i = 0; i < visibleSpriteCount; i++) {
- attributes.groupOffset = i;
- visibleSprites[i].setAttributes(attributes);
- }
- }
- me.callParent(arguments);
- }
- });
- /**
- * @class Ext.chart.series.sprite.Bar3D
- * @extends Ext.chart.series.sprite.Bar
- *
- * Draws a sprite used in {@link Ext.chart.series.Bar3D} series.
- */
- Ext.define('Ext.chart.series.sprite.Bar3D', {
- extend: 'Ext.chart.series.sprite.Bar',
- alias: 'sprite.bar3dSeries',
- requires: [
- 'Ext.draw.gradient.Linear'
- ],
- inheritableStatics: {
- def: {
- processors: {
- depthWidthRatio: 'number',
- /**
- * @cfg {Number} [saturationFactor=1]
- * The factor applied to the saturation of the bars.
- */
- saturationFactor: 'number',
- /**
- * @cfg {Number} [brightnessFactor=1]
- * The factor applied to the brightness of the bars.
- */
- brightnessFactor: 'number',
- /**
- * @cfg {Number} [colorSpread=1]
- * An attribute used to control how flat the bar gradient looks.
- * A value of 0 essentially means no gradient (flat color).
- */
- colorSpread: 'number'
- },
- defaults: {
- depthWidthRatio: 1 / 3,
- saturationFactor: 1,
- brightnessFactor: 1,
- colorSpread: 1,
- transformFillStroke: true
- },
- triggers: {
- groupCount: 'panzoom'
- },
- updaters: {
- panzoom: function(attr) {
- var me = this,
- dx = attr.visibleMaxX - attr.visibleMinX,
- dy = attr.visibleMaxY - attr.visibleMinY,
- innerWidth = attr.flipXY ? attr.innerHeight : attr.innerWidth,
- innerHeight = !attr.flipXY ? attr.innerHeight : attr.innerWidth,
- surface = me.getSurface(),
- isRtl = surface ? surface.getInherited().rtl : false;
- if (isRtl && !attr.flipXY) {
- attr.translationX = innerWidth + attr.visibleMinX * innerWidth / dx;
- } else {
- attr.translationX = -attr.visibleMinX * innerWidth / dx;
- }
- attr.translationY = -attr.visibleMinY * (innerHeight - me.depth) / dy;
- attr.scalingX = (isRtl && !attr.flipXY ? -1 : 1) * innerWidth / dx;
- attr.scalingY = (innerHeight - me.depth) / dy;
- attr.scalingCenterX = 0;
- attr.scalingCenterY = 0;
- me.applyTransformations(true);
- }
- }
- }
- },
- config: {
- showStroke: false
- },
- depth: 0,
- drawBar: function(ctx, surface, clip, left, top, right, bottom, index) {
- var me = this,
- attr = me.attr,
- itemCfg = {},
- renderer = attr.renderer,
- changes, depth, series, params;
- itemCfg.x = (left + right) * 0.5;
- itemCfg.y = top;
- itemCfg.width = (right - left) * 0.75;
- itemCfg.height = bottom - top;
- itemCfg.depth = depth = itemCfg.width * attr.depthWidthRatio;
- itemCfg.orientation = attr.flipXY ? 'horizontal' : 'vertical';
- itemCfg.saturationFactor = attr.saturationFactor;
- itemCfg.brightnessFactor = attr.brightnessFactor;
- itemCfg.colorSpread = attr.colorSpread;
- if (depth !== me.depth) {
- me.depth = depth;
- series = me.getSeries();
- series.fireEvent('depthchange', series, depth);
- }
- if (renderer) {
- params = [
- me,
- itemCfg,
- {
- store: me.getStore()
- },
- index
- ];
- changes = Ext.callback(renderer, null, params, 0, me.getSeries());
- Ext.apply(itemCfg, changes);
- }
- me.putMarker('items', itemCfg, index, !renderer);
- }
- });
- /**
- * @class Ext.chart.sprite.Bar3D
- * @extends Ext.draw.sprite.Sprite
- *
- * A sprite that represents a 3D bar or column.
- * Used as an item template by the {@link Ext.chart.series.sprite.Bar3D} marker holder.
- *
- */
- Ext.define('Ext.chart.sprite.Bar3D', {
- extend: 'Ext.draw.sprite.Sprite',
- alias: 'sprite.bar3d',
- type: 'bar3d',
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Number} [x=0]
- * The position of the sprite on the x-axis.
- * Corresponds to the center of the front face of the box.
- */
- x: 'number',
- /**
- * @cfg {Number} [y=0]
- * The position of the sprite on the y-axis.
- * Corresponds to the top of the front face of the box.
- */
- y: 'number',
- /**
- * @cfg {Number} [width=8] The width of the box.
- */
- width: 'number',
- /**
- * @cfg {Number} [height=8] The height of the box.
- */
- height: 'number',
- /**
- * @cfg {Number} [depth=8] The depth of the box.
- */
- depth: 'number',
- /**
- * @cfg {String} [orientation='vertical'] The orientation of the box.
- */
- orientation: 'enums(vertical,horizontal)',
- /**
- * @cfg {Boolean} [showStroke=false]
- * Whether to render the stroke or not.
- */
- showStroke: 'bool',
- /**
- * @cfg {Number} [saturationFactor=1]
- * The factor applied to the saturation of the box.
- */
- saturationFactor: 'number',
- /**
- * @cfg {Number} [brightnessFactor=1]
- * The factor applied to the brightness of the box.
- */
- brightnessFactor: 'number',
- /**
- * @cfg {Number} [colorSpread=1]
- * An attribute used to control how flat the bar gradient looks.
- * A value of 0 essentially means no gradient (flat color).
- */
- colorSpread: 'number'
- },
- triggers: {
- x: 'bbox',
- y: 'bbox',
- width: 'bbox',
- height: 'bbox',
- depth: 'bbox',
- orientation: 'bbox'
- },
- defaults: {
- x: 0,
- y: 0,
- width: 8,
- height: 8,
- depth: 8,
- orientation: 'vertical',
- showStroke: false,
- saturationFactor: 1,
- brightnessFactor: 1,
- colorSpread: 1,
- lineJoin: 'bevel'
- }
- }
- },
- constructor: function(config) {
- this.callParent([
- config
- ]);
- this.topGradient = new Ext.draw.gradient.Linear({});
- this.rightGradient = new Ext.draw.gradient.Linear({});
- this.frontGradient = new Ext.draw.gradient.Linear({});
- },
- updatePlainBBox: function(plain) {
- var attr = this.attr,
- x = attr.x,
- y = attr.y,
- width = attr.width,
- height = attr.height,
- depth = attr.depth;
- plain.x = x - width * 0.5;
- plain.width = width + depth;
- if (height > 0) {
- plain.y = y;
- plain.height = height + depth;
- } else {
- plain.y = y + depth;
- plain.height = height - depth;
- }
- },
- render: function(surface, ctx) {
- var me = this,
- attr = me.attr,
- center = attr.x,
- top = attr.y,
- bottom = top + attr.height,
- isNegative = top < bottom,
- halfWidth = attr.width * 0.5,
- depth = attr.depth,
- isHorizontal = attr.orientation === 'horizontal',
- isTransparent = attr.globalAlpha < 1,
- fillStyle = attr.fillStyle,
- color = Ext.util.Color.create(fillStyle.isGradient ? fillStyle.getStops()[0].color : fillStyle),
- saturationFactor = attr.saturationFactor,
- brightnessFactor = attr.brightnessFactor,
- colorSpread = attr.colorSpread,
- hsv = color.getHSV(),
- bbox = {},
- roundX, roundY, temp;
- if (!attr.showStroke) {
- ctx.strokeStyle = Ext.util.Color.RGBA_NONE;
- }
- if (isNegative) {
- temp = top;
- top = bottom;
- bottom = temp;
- }
- // Refresh gradients based on sprite's fillStyle and other attributes.
- me.topGradient.setDegrees(isHorizontal ? 0 : 80);
- me.topGradient.setStops([
- {
- offset: 0,
- 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))
- },
- {
- offset: 1,
- 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))
- }
- ]);
- me.rightGradient.setDegrees(isHorizontal ? 45 : 90);
- me.rightGradient.setStops([
- {
- offset: 0,
- 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))
- },
- {
- offset: 1,
- 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))
- }
- ]);
- if (isHorizontal) {
- // 0° angle looks like 90° angle because the chart is flipped
- me.frontGradient.setDegrees(0);
- } else {
- me.frontGradient.setRadians(Math.atan2(top - bottom, halfWidth * 2));
- }
- me.frontGradient.setStops([
- {
- offset: 0,
- 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))
- },
- {
- offset: 1,
- 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))
- }
- ]);
- if (isTransparent || isNegative) {
- // Bottom side.
- ctx.beginPath();
- ctx.moveTo(center - halfWidth, bottom);
- ctx.lineTo(center - halfWidth + depth, bottom + depth);
- ctx.lineTo(center + halfWidth + depth, bottom + depth);
- ctx.lineTo(center + halfWidth, bottom);
- ctx.closePath();
- bbox.x = center - halfWidth;
- bbox.y = top;
- bbox.width = halfWidth + depth;
- bbox.height = depth;
- // eslint-disable-next-line max-len
- ctx.fillStyle = (isHorizontal ? me.rightGradient : me.topGradient).generateGradient(ctx, bbox);
- ctx.fillStroke(attr);
- }
- if (isTransparent) {
- // Left side.
- ctx.beginPath();
- ctx.moveTo(center - halfWidth, top);
- ctx.lineTo(center - halfWidth + depth, top + depth);
- ctx.lineTo(center - halfWidth + depth, bottom + depth);
- ctx.lineTo(center - halfWidth, bottom);
- ctx.closePath();
- bbox.x = center + halfWidth;
- bbox.y = bottom;
- bbox.width = depth;
- bbox.height = top + depth - bottom;
- // eslint-disable-next-line max-len
- ctx.fillStyle = (isHorizontal ? me.topGradient : me.rightGradient).generateGradient(ctx, bbox);
- ctx.fillStroke(attr);
- }
- // Top side.
- roundY = surface.roundPixel(top);
- ctx.beginPath();
- ctx.moveTo(center - halfWidth, roundY);
- ctx.lineTo(center - halfWidth + depth, top + depth);
- ctx.lineTo(center + halfWidth + depth, top + depth);
- ctx.lineTo(center + halfWidth, roundY);
- ctx.closePath();
- bbox.x = center - halfWidth;
- bbox.y = top;
- bbox.width = halfWidth + depth;
- bbox.height = depth;
- // eslint-disable-next-line max-len
- ctx.fillStyle = (isHorizontal ? me.rightGradient : me.topGradient).generateGradient(ctx, bbox);
- ctx.fillStroke(attr);
- // Right side.
- roundX = surface.roundPixel(center + halfWidth);
- ctx.beginPath();
- ctx.moveTo(roundX, surface.roundPixel(top));
- ctx.lineTo(center + halfWidth + depth, top + depth);
- ctx.lineTo(center + halfWidth + depth, bottom + depth);
- ctx.lineTo(roundX, bottom);
- ctx.closePath();
- bbox.x = center + halfWidth;
- bbox.y = bottom;
- bbox.width = depth;
- bbox.height = top + depth - bottom;
- // eslint-disable-next-line max-len
- ctx.fillStyle = (isHorizontal ? me.topGradient : me.rightGradient).generateGradient(ctx, bbox);
- ctx.fillStroke(attr);
- // Front side.
- roundX = surface.roundPixel(center + halfWidth);
- roundY = surface.roundPixel(top);
- ctx.beginPath();
- ctx.moveTo(center - halfWidth, bottom);
- ctx.lineTo(center - halfWidth, roundY);
- ctx.lineTo(roundX, roundY);
- ctx.lineTo(roundX, bottom);
- ctx.closePath();
- bbox.x = center - halfWidth;
- bbox.y = bottom;
- bbox.width = halfWidth * 2;
- bbox.height = top - bottom;
- ctx.fillStyle = me.frontGradient.generateGradient(ctx, bbox);
- ctx.fillStroke(attr);
- }
- });
- /**
- * @class Ext.chart.series.Bar3D
- * @extends Ext.chart.series.Bar
- *
- * Creates a 3D Bar or 3D Column Chart (depending on the value of the
- * {@link Ext.chart.CartesianChart#flipXY flipXY} config).
- *
- * Note: 'bar3d' series is meant to be used with the
- * {@link Ext.chart.axis.Category 'category3d'} axis as its x-axis.
- *
- * @example
- * Ext.create({
- * xtype: 'cartesian',
- * renderTo: Ext.getBody(),
- * width: 600,
- * height: 400,
- * innerPadding: '0 10 0 10',
- * store: {
- * fields: ['name', 'apples', 'oranges'],
- * data: [{
- * name: 'Eric',
- * apples: 10,
- * oranges: 3
- * }, {
- * name: 'Mary',
- * apples: 7,
- * oranges: 2
- * }, {
- * name: 'John',
- * apples: 5,
- * oranges: 2
- * }, {
- * name: 'Bob',
- * apples: 2,
- * oranges: 3
- * }, {
- * name: 'Joe',
- * apples: 19,
- * oranges: 1
- * }, {
- * name: 'Macy',
- * apples: 13,
- * oranges: 4
- * }]
- * },
- * axes: [{
- * type: 'numeric3d',
- * position: 'left',
- * fields: ['apples', 'oranges'],
- * title: {
- * text: 'Inventory',
- * fontSize: 15
- * },
- * grid: {
- * odd: {
- * fillStyle: 'rgba(255, 255, 255, 0.06)'
- * },
- * even: {
- * fillStyle: 'rgba(0, 0, 0, 0.03)'
- * }
- * }
- * }, {
- * type: 'category3d',
- * position: 'bottom',
- * title: {
- * text: 'People',
- * fontSize: 15
- * },
- * fields: 'name'
- * }],
- * series: {
- * type: 'bar3d',
- * xField: 'name',
- * yField: ['apples', 'oranges']
- * }
- * });
- */
- Ext.define('Ext.chart.series.Bar3D', {
- extend: 'Ext.chart.series.Bar',
- requires: [
- 'Ext.chart.series.sprite.Bar3D',
- 'Ext.chart.sprite.Bar3D'
- ],
- alias: 'series.bar3d',
- type: 'bar3d',
- seriesType: 'bar3dSeries',
- is3D: true,
- config: {
- itemInstancing: {
- type: 'bar3d',
- animation: {
- customDurations: {
- x: 0,
- y: 0,
- width: 0,
- height: 0,
- depth: 0
- }
- }
- },
- highlightCfg: {
- opacity: 0.8
- }
- },
- /**
- * For 3D series, it's quite the opposite. It would be extremely odd,
- * if top segments were rendered as if they were under the bottom ones.
- */
- reversedSpriteZOrder: false,
- updateXAxis: function(xAxis, oldXAxis) {
- //<debug>
- if (xAxis.type !== 'category3d') {
- Ext.raise("'bar3d' series should be used with a 'category3d' axis." + " Please refer to the 'bar3d' series docs.");
- }
- //</debug>
- this.callParent([
- xAxis,
- oldXAxis
- ]);
- },
- getDepth: function() {
- var sprite = this.getSprites()[0];
- return sprite ? (sprite.depth || 0) : 0;
- }
- });
- /**
- * BoxPlot series sprite that manages {@link Ext.chart.sprite.BoxPlot} instances.
- */
- Ext.define('Ext.chart.series.sprite.BoxPlot', {
- alias: 'sprite.boxplotSeries',
- extend: 'Ext.chart.series.sprite.Cartesian',
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Number[]} [dataLow=null] Array of coordinated minimum values.
- */
- dataLow: 'data',
- /**
- * @cfg {Number[]} [dataQ1=null] Array of coordinated 1-st quartile values.
- */
- dataQ1: 'data',
- /**
- * @cfg {Number[]} [dataQ3=null] Array of coordinated 3-rd quartile values.
- */
- dataQ3: 'data',
- /**
- * @cfg {Number[]} [dataHigh=null] Array of coordinated maximum values.
- */
- dataHigh: 'data',
- /**
- * @cfg {Number} [minBoxWidth=2] The minimum box width.
- */
- minBoxWidth: 'number',
- /**
- * @cfg {Number} [maxBoxWidth=20] The maximum box width.
- */
- maxBoxWidth: 'number',
- /**
- * @cfg {Number} [minGapWidth=5] The minimum gap between boxes.
- */
- minGapWidth: 'number'
- },
- aliases: {
- /**
- * The `dataMedian` attribute can be used to set the value of
- * the `dataY` attribute. E.g.:
- *
- * sprite.setAttributes({
- * dataMedian: [...]
- * });
- *
- * To fetch the value of the attribute one has to use
- *
- * sprite.attr.dataY // array of coordinated median values
- *
- * and not
- *
- * sprite.attr.dataMedian // WRONG!
- *
- * `dataY` attribute is defined by the `Ext.chart.series.sprite.Series`.
- *
- * @cfg {Number[]} [dataMedian=null] Array of coordinated median values.
- */
- dataMedian: 'dataY'
- },
- defaults: {
- minBoxWidth: 2,
- maxBoxWidth: 40,
- minGapWidth: 5
- }
- }
- },
- renderClipped: function(surface, ctx, dataClipRect) {
- if (this.cleanRedraw) {
- return;
- }
- // eslint-disable-next-line vars-on-top
- var me = this,
- attr = me.attr,
- series = me.getSeries(),
- renderer = attr.renderer,
- rendererData = {
- store: me.getStore()
- },
- itemCfg = {},
- dataX = attr.dataX,
- dataLow = attr.dataLow,
- dataQ1 = attr.dataQ1,
- dataMedian = attr.dataY,
- dataQ3 = attr.dataQ3,
- dataHigh = attr.dataHigh,
- min = Math.min(dataClipRect[0], dataClipRect[2]),
- max = Math.max(dataClipRect[0], dataClipRect[2]),
- start = Math.max(0, Math.floor(min)),
- end = Math.min(dataX.length - 1, Math.ceil(max)),
- // surfaceMatrix = me.surfaceMatrix,
- matrix = attr.matrix,
- xx = matrix.elements[0],
- // horizontal scaling can be < 0, if RTL
- yy = matrix.elements[3],
- dx = matrix.elements[4],
- dy = matrix.elements[5],
- // `xx` essentially represents the distance between data points in surface coordinates.
- maxBoxWidth = Math.abs(xx) - attr.minGapWidth,
- minBoxWidth = Math.min(maxBoxWidth, attr.maxBoxWidth),
- boxWidth = Math.round(Math.max(attr.minBoxWidth, minBoxWidth)),
- x, low, q1, median, q3, high, rendererParams, changes, i;
- if (renderer) {
- rendererParams = [
- me,
- itemCfg,
- rendererData
- ];
- }
- for (i = start; i <= end; i++) {
- x = dataX[i] * xx + dx;
- low = dataLow[i] * yy + dy;
- q1 = dataQ1[i] * yy + dy;
- median = dataMedian[i] * yy + dy;
- q3 = dataQ3[i] * yy + dy;
- high = dataHigh[i] * yy + dy;
- // --- Draw Box ---
- // Reuse 'itemCfg' object and 'rendererParams' arrays for better performance.
- itemCfg.x = x;
- itemCfg.low = low;
- itemCfg.q1 = q1;
- itemCfg.median = median;
- itemCfg.q3 = q3;
- itemCfg.high = high;
- itemCfg.boxWidth = boxWidth;
- if (renderer) {
- rendererParams[3] = i;
- changes = Ext.callback(renderer, null, rendererParams, 0, series);
- Ext.apply(itemCfg, changes);
- }
- me.putMarker('items', itemCfg, i, !renderer);
- }
- }
- });
- /**
- * A sprite that represents an individual box with whiskers.
- * This sprite is meant to be managed by the {@link Ext.chart.series.sprite.BoxPlot}
- * {@link Ext.chart.MarkerHolder MarkerHolder}, but can also be used independently:
- *
- * @example
- * new Ext.draw.Container({
- * width: 100,
- * height: 100,
- * renderTo: Ext.getBody(),
- * sprites: [{
- * type: 'boxplot',
- * translationX: 50,
- * translationY: 50
- * }]
- * });
- *
- * IMPORTANT: the attributes that represent y-coordinates are in screen coordinates,
- * just like with any other sprite. For this particular sprite this means that, if 'low'
- * and 'high' attributes are 10 and 90, then the minimium whisker is rendered at the top
- * of a draw container {@link Ext.draw.Surface surface} at y = 10, and the maximum whisker
- * is rendered at the bottom at y = 90. But because the series surface is flipped vertically
- * in cartesian charts, this means that there minimum is rendered at the bottom and maximum
- * at the top, just as one would expect.
- */
- Ext.define('Ext.chart.sprite.BoxPlot', {
- extend: 'Ext.draw.sprite.Sprite',
- alias: 'sprite.boxplot',
- type: 'boxplot',
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Number} [x=0] The coordinate of the horizontal center of a boxplot.
- */
- x: 'number',
- /**
- * @cfg {Number} [low=-20] The y-coordinate of the whisker that represents
- * the minimum.
- */
- low: 'number',
- /**
- * @cfg {Number} [q1=-10] The y-coordinate of the box edge that represents
- * the 1-st quartile.
- */
- q1: 'number',
- /**
- * @cfg {Number} [median=0] The y-coordinate of the line that represents the median.
- */
- median: 'number',
- /**
- * @cfg {Number} [q3=10] The y-coordinate of the box edge that represents
- * the 3-rd quartile.
- */
- q3: 'number',
- /**
- * @cfg {Number} [high=20] The y-coordinate of the whisker that represents
- * the maximum.
- */
- high: 'number',
- /**
- * @cfg {Number} [boxWidth=12] The width of the box in pixels.
- */
- boxWidth: 'number',
- /**
- * @cfg {Number} [whiskerWidth=0.5] The length of the lines at the ends
- * of the whiskers, as a ratio of `boxWidth`.
- */
- whiskerWidth: 'number',
- /**
- * @cfg {Boolean} [crisp=true] Whether to snap the rendered lines to the pixel grid
- * of not. Generally, it's best to have this set to `true` (which is the default)
- * for pixel perfect results (especially on non-HiDPI displays), but for boxplots
- * with small `boxWidth` visible artifacts caused by pixel grid snapping may become
- * noticeable, and setting this to `false` can be a remedy at the expense
- * of clarity.
- */
- crisp: 'bool'
- },
- triggers: {
- x: 'bbox',
- low: 'bbox',
- high: 'bbox',
- boxWidth: 'bbox',
- whiskerWidth: 'bbox',
- crisp: 'bbox'
- },
- defaults: {
- x: 0,
- low: -20,
- q1: -10,
- median: 0,
- q3: 10,
- high: 20,
- boxWidth: 12,
- whiskerWidth: 0.5,
- crisp: true,
- fillStyle: '#ccc',
- strokeStyle: '#000'
- }
- }
- },
- updatePlainBBox: function(plain) {
- var me = this,
- attr = me.attr,
- halfLineWidth = attr.lineWidth / 2,
- x = attr.x - attr.boxWidth / 2 - halfLineWidth,
- y = attr.high - halfLineWidth,
- width = attr.boxWidth + attr.lineWidth,
- height = attr.low - attr.high + attr.lineWidth;
- plain.x = x;
- plain.y = y;
- plain.width = width;
- plain.height = height;
- },
- render: function(surface, ctx) {
- var me = this,
- attr = me.attr;
- attr.matrix.toContext(ctx);
- // enable sprite transformations
- if (attr.crisp) {
- me.crispRender(surface, ctx);
- } else {
- me.softRender(surface, ctx);
- }
- //<debug>
- // eslint-disable-next-line vars-on-top, one-var
- var debug = attr.debug || this.statics().debug || Ext.draw.sprite.Sprite.debug;
- if (debug) {
- // This assumes no part of the sprite is rendered after this call.
- // If it is, we need to re-apply transformations.
- // But the bounding box should always be rendered as is, untransformed.
- this.attr.inverseMatrix.toContext(ctx);
- if (debug.bbox) {
- this.renderBBox(surface, ctx);
- }
- }
- },
- //</debug>
- /**
- * @private
- * Renders a single box with whiskers.
- * Changes to this method have to be reflected in the {@link #crispRender} as well.
- * @param surface
- * @param ctx
- */
- softRender: function(surface, ctx) {
- var me = this,
- attr = me.attr,
- x = attr.x,
- low = attr.low,
- q1 = attr.q1,
- median = attr.median,
- q3 = attr.q3,
- high = attr.high,
- halfBoxWidth = attr.boxWidth / 2,
- halfWhiskerWidth = attr.boxWidth * attr.whiskerWidth / 2,
- dash = ctx.getLineDash();
- ctx.setLineDash([]);
- // Only stem can be dashed.
- // Box.
- ctx.beginPath();
- ctx.moveTo(x - halfBoxWidth, q3);
- ctx.lineTo(x + halfBoxWidth, q3);
- ctx.lineTo(x + halfBoxWidth, q1);
- ctx.lineTo(x - halfBoxWidth, q1);
- ctx.closePath();
- ctx.fillStroke(attr, true);
- // Stem.
- ctx.setLineDash(dash);
- ctx.beginPath();
- ctx.moveTo(x, q3);
- ctx.lineTo(x, high);
- ctx.moveTo(x, q1);
- ctx.lineTo(x, low);
- ctx.stroke();
- ctx.setLineDash([]);
- // Whiskers.
- ctx.beginPath();
- ctx.moveTo(x - halfWhiskerWidth, low);
- ctx.lineTo(x + halfWhiskerWidth, low);
- ctx.moveTo(x - halfBoxWidth, median);
- ctx.lineTo(x + halfBoxWidth, median);
- ctx.moveTo(x - halfWhiskerWidth, high);
- ctx.lineTo(x + halfWhiskerWidth, high);
- ctx.stroke();
- },
- alignLine: function(x, lineWidth) {
- lineWidth = lineWidth || this.attr.lineWidth;
- x = Math.round(x);
- if (lineWidth % 2 === 1) {
- x -= 0.5;
- }
- return x;
- },
- /**
- * @private
- * Renders a pixel-perfect single box with whiskers by aligning to the pixel grid.
- * Changes to this method have to be reflected in the {@link #softRender} as well.
- *
- * Note: crisp image is only guaranteed when `attr.lineWidth` is a whole number.
- * @param surface
- * @param ctx
- */
- crispRender: function(surface, ctx) {
- var me = this,
- attr = me.attr,
- x = attr.x,
- low = me.alignLine(attr.low),
- q1 = me.alignLine(attr.q1),
- median = me.alignLine(attr.median),
- q3 = me.alignLine(attr.q3),
- high = me.alignLine(attr.high),
- halfBoxWidth = attr.boxWidth / 2,
- halfWhiskerWidth = attr.boxWidth * attr.whiskerWidth / 2,
- stemX = me.alignLine(x),
- boxLeft = me.alignLine(x - halfBoxWidth),
- boxRight = me.alignLine(x + halfBoxWidth),
- whiskerLeft = stemX + Math.round(-halfWhiskerWidth),
- whiskerRight = stemX + Math.round(halfWhiskerWidth),
- dash = ctx.getLineDash();
- ctx.setLineDash([]);
- // Only stem can be dashed.
- // Box.
- ctx.beginPath();
- ctx.moveTo(boxLeft, q3);
- ctx.lineTo(boxRight, q3);
- ctx.lineTo(boxRight, q1);
- ctx.lineTo(boxLeft, q1);
- ctx.closePath();
- ctx.fillStroke(attr, true);
- // Stem.
- ctx.setLineDash(dash);
- ctx.beginPath();
- ctx.moveTo(stemX, q3);
- ctx.lineTo(stemX, high);
- ctx.moveTo(stemX, q1);
- ctx.lineTo(stemX, low);
- ctx.stroke();
- ctx.setLineDash([]);
- // Whiskers.
- ctx.beginPath();
- ctx.moveTo(whiskerLeft, low);
- ctx.lineTo(whiskerRight, low);
- ctx.moveTo(boxLeft, median);
- ctx.lineTo(boxRight, median);
- ctx.moveTo(whiskerLeft, high);
- ctx.lineTo(whiskerRight, high);
- ctx.stroke();
- }
- });
- /**
- * A box plot chart is a useful tool for visializing data distribution within datasets.
- * For example, salary ranges for a set of occupations, or life expectancy for a set
- * of countries. A single box with whiskers displays the following values for a dataset:
- *
- * * minimum
- * * lower quartile (Q1)
- * * median (Q2)
- * * higher quartile (Q3)
- * * maximum
- *
- * For example:
- *
- * @example
- * Ext.create({
- * xtype: 'cartesian',
- * width: 400,
- * height: 400,
- * renderTo: Ext.getBody(),
- * insetPadding: '20 20 10 10',
- * store: {
- * data: [{
- * category: 'Engineer IV',
- * low: 110, q1: 130, median: 175, q3: 200, high: 225
- * }, {
- * category: 'Market',
- * low: 75, q1: 125, median: 210, q3: 230, high: 255
- * }]
- * },
- * axes: [
- * {
- * type: 'numeric',
- * position: 'left',
- * renderer: function (axis, text) {
- * return '$' + text + ' K'
- * }
- * },
- * {
- * type: 'category',
- * position: 'bottom'
- * }
- * ],
- * series: {
- * type: 'boxplot',
- * xField: 'category',
- * style: {
- * maxBoxWidth: 50,
- * lineWidth: 2
- * }
- * }
- * });
- *
- */
- Ext.define('Ext.chart.series.BoxPlot', {
- extend: 'Ext.chart.series.Cartesian',
- alias: 'series.boxplot',
- type: 'boxplot',
- seriesType: 'boxplotSeries',
- isBoxPlot: true,
- requires: [
- 'Ext.chart.series.sprite.BoxPlot',
- 'Ext.chart.sprite.BoxPlot'
- ],
- config: {
- itemInstancing: {
- type: 'boxplot',
- animation: {
- // Setting the duration of these attributes to zero because
- // the 'data' attributes of the series sprite (MarkerHolder)
- // will be animated instead, and then changes applied to
- // the attributes of 'boxplot' instances instantly.
- customDurations: {
- x: 0,
- low: 0,
- q1: 0,
- median: 0,
- q3: 0,
- high: 0
- }
- }
- },
- /**
- * @cfg {String} [lowField='low']
- * The name of the store record field that represents the smallest value of a dataset.
- */
- lowField: 'low',
- /**
- * @cfg {String} [q1Field='q1']
- * The name of the store record field that represents the lower (1-st) quartile
- * value of a dataset.
- */
- q1Field: 'q1',
- /**
- * @cfg {String} [medianField='median']
- * The name of the store record field that represents the median of a dataset.
- */
- medianField: 'median',
- /**
- * @cfg {String} [q3Field='q3']
- * The name of the store record field that represents the upper (3-rd) quartile
- * value of a dataset.
- */
- q3Field: 'q3',
- /**
- * @cfg {String} [highField='high']
- * The name of the store record field that represents the largest value of a dataset.
- */
- highField: 'high'
- },
- fieldCategoryY: [
- 'Low',
- 'Q1',
- 'Median',
- 'Q3',
- 'High'
- ],
- updateXAxis: function(xAxis) {
- xAxis.setExpandRangeBy(0.5);
- this.callParent(arguments);
- }
- });
- /**
- * Limited cache is a size limited cache container that stores limited number of objects.
- *
- * When {@link #get} is called, the container will try to find the object in the list.
- * If failed it will call the {@link #feeder} to create that object. If there are too many
- * objects in the container, the old ones are removed.
- *
- * __Note:__ This is not using a Least Recently Used policy due to simplicity and performance
- * consideration.
- * @private
- */
- Ext.define('Ext.draw.LimitedCache', {
- config: {
- /**
- * @cfg {Number}
- * The amount limit of the cache.
- */
- limit: 40,
- /**
- * @cfg {Function}
- * Function that generates the object when look-up failed.
- * @return {Number}
- */
- feeder: function() {
- return 0;
- },
- /**
- * @cfg {Object}
- * The scope for {@link #feeder}
- */
- scope: null
- },
- cache: null,
- constructor: function(config) {
- this.cache = {};
- this.cache.list = [];
- this.cache.tail = 0;
- this.initConfig(config);
- },
- /**
- * Get a cached object.
- * @param {String} id
- * @return {Object}
- */
- get: function(id) {
- // TODO: Implement cache hit optimization
- var cache = this.cache,
- limit = this.getLimit(),
- feeder = this.getFeeder(),
- scope = this.getScope() || this;
- if (cache[id]) {
- return cache[id].value;
- }
- if (cache.list[cache.tail]) {
- delete cache[cache.list[cache.tail].cacheId];
- }
- cache[id] = cache.list[cache.tail] = {
- value: feeder.apply(scope, Array.prototype.slice.call(arguments, 1)),
- cacheId: id
- };
- cache.tail++;
- if (cache.tail === limit) {
- cache.tail = 0;
- }
- return cache[id].value;
- },
- /**
- * Clear all the objects.
- */
- clear: function() {
- this.cache = {};
- this.cache.list = [];
- this.cache.tail = 0;
- }
- });
- /**
- * This class we summarize the data and returns it when required.
- */
- Ext.define("Ext.draw.SegmentTree", {
- config: {
- strategy: "double"
- },
- /**
- * @private
- * @param {Object} result
- * @param {Number} last
- * @param {Number} dataX
- * @param {Number} dataOpen
- * @param {Number} dataHigh
- * @param {Number} dataLow
- * @param {Number} dataClose
- */
- time: function(result, last, dataX, dataOpen, dataHigh, dataLow, dataClose) {
- var start = 0,
- lastOffset, lastOffsetEnd,
- minimum = new Date(dataX[result.startIdx[0]]),
- maximum = new Date(dataX[result.endIdx[last - 1]]),
- extDate = Ext.Date,
- units = [
- [
- extDate.MILLI,
- 1,
- 'ms1',
- null
- ],
- [
- extDate.MILLI,
- 2,
- 'ms2',
- 'ms1'
- ],
- [
- extDate.MILLI,
- 5,
- 'ms5',
- 'ms1'
- ],
- [
- extDate.MILLI,
- 10,
- 'ms10',
- 'ms5'
- ],
- [
- extDate.MILLI,
- 50,
- 'ms50',
- 'ms10'
- ],
- [
- extDate.MILLI,
- 100,
- 'ms100',
- 'ms50'
- ],
- [
- extDate.MILLI,
- 500,
- 'ms500',
- 'ms100'
- ],
- [
- extDate.SECOND,
- 1,
- 's1',
- 'ms500'
- ],
- [
- extDate.SECOND,
- 10,
- 's10',
- 's1'
- ],
- [
- extDate.SECOND,
- 30,
- 's30',
- 's10'
- ],
- [
- extDate.MINUTE,
- 1,
- 'mi1',
- 's10'
- ],
- [
- extDate.MINUTE,
- 5,
- 'mi5',
- 'mi1'
- ],
- [
- extDate.MINUTE,
- 10,
- 'mi10',
- 'mi5'
- ],
- [
- extDate.MINUTE,
- 30,
- 'mi30',
- 'mi10'
- ],
- [
- extDate.HOUR,
- 1,
- 'h1',
- 'mi30'
- ],
- [
- extDate.HOUR,
- 6,
- 'h6',
- 'h1'
- ],
- [
- extDate.HOUR,
- 12,
- 'h12',
- 'h6'
- ],
- [
- extDate.DAY,
- 1,
- 'd1',
- 'h12'
- ],
- [
- extDate.DAY,
- 7,
- 'd7',
- 'd1'
- ],
- [
- extDate.MONTH,
- 1,
- 'mo1',
- 'd1'
- ],
- [
- extDate.MONTH,
- 3,
- 'mo3',
- 'mo1'
- ],
- [
- extDate.MONTH,
- 6,
- 'mo6',
- 'mo3'
- ],
- [
- extDate.YEAR,
- 1,
- 'y1',
- 'mo3'
- ],
- [
- extDate.YEAR,
- 5,
- 'y5',
- 'y1'
- ],
- [
- extDate.YEAR,
- 10,
- 'y10',
- 'y5'
- ],
- [
- extDate.YEAR,
- 100,
- 'y100',
- 'y10'
- ]
- ],
- unitIdx, currentUnit,
- plainStart = start,
- plainEnd = last,
- startIdxs = result.startIdx,
- endIdxs = result.endIdx,
- minIdxs = result.minIdx,
- maxIdxs = result.maxIdx,
- opens = result.open,
- closes = result.close,
- minXs = result.minX,
- minYs = result.minY,
- maxXs = result.maxX,
- maxYs = result.maxY,
- i, current;
- for (unitIdx = 0; last > start + 1 && unitIdx < units.length; unitIdx++) {
- minimum = new Date(dataX[startIdxs[0]]);
- currentUnit = units[unitIdx];
- minimum = extDate.align(minimum, currentUnit[0], currentUnit[1]);
- // eslint-disable-next-line max-len
- if (extDate.diff(minimum, maximum, currentUnit[0]) > dataX.length * 2 * currentUnit[1]) {
-
- continue;
- }
- if (currentUnit[3] && result.map['time_' + currentUnit[3]]) {
- lastOffset = result.map['time_' + currentUnit[3]][0];
- lastOffsetEnd = result.map['time_' + currentUnit[3]][1];
- } else {
- lastOffset = plainStart;
- lastOffsetEnd = plainEnd;
- }
- start = last;
- current = minimum;
- startIdxs[last] = startIdxs[lastOffset];
- endIdxs[last] = endIdxs[lastOffset];
- minIdxs[last] = minIdxs[lastOffset];
- maxIdxs[last] = maxIdxs[lastOffset];
- opens[last] = opens[lastOffset];
- closes[last] = closes[lastOffset];
- minXs[last] = minXs[lastOffset];
- minYs[last] = minYs[lastOffset];
- maxXs[last] = maxXs[lastOffset];
- maxYs[last] = maxYs[lastOffset];
- current = Ext.Date.add(current, currentUnit[0], currentUnit[1]);
- for (i = lastOffset + 1; i < lastOffsetEnd; i++) {
- if (dataX[endIdxs[i]] < +current) {
- endIdxs[last] = endIdxs[i];
- closes[last] = closes[i];
- if (maxYs[i] > maxYs[last]) {
- maxYs[last] = maxYs[i];
- maxXs[last] = maxXs[i];
- maxIdxs[last] = maxIdxs[i];
- }
- if (minYs[i] < minYs[last]) {
- minYs[last] = minYs[i];
- minXs[last] = minXs[i];
- minIdxs[last] = minIdxs[i];
- }
- } else {
- last++;
- startIdxs[last] = startIdxs[i];
- endIdxs[last] = endIdxs[i];
- minIdxs[last] = minIdxs[i];
- maxIdxs[last] = maxIdxs[i];
- opens[last] = opens[i];
- closes[last] = closes[i];
- minXs[last] = minXs[i];
- minYs[last] = minYs[i];
- maxXs[last] = maxXs[i];
- maxYs[last] = maxYs[i];
- current = Ext.Date.add(current, currentUnit[0], currentUnit[1]);
- }
- }
- if (last > start) {
- result.map['time_' + currentUnit[2]] = [
- start,
- last
- ];
- }
- }
- },
- /**
- * @private
- * @param {Object} result
- * @param {Number} position
- * @param {Number} dataX
- * @param {Number} dataOpen
- * @param {Number} dataHigh
- * @param {Number} dataLow
- * @param {Number} dataClose
- */
- "double": function(result, position, dataX, dataOpen, dataHigh, dataLow, dataClose) {
- var offset = 0,
- lastOffset,
- step = 1,
- i, startIdx, endIdx, minIdx, maxIdx, open, close, minX, minY, maxX, maxY;
- while (position > offset + 1) {
- lastOffset = offset;
- offset = position;
- step += step;
- for (i = lastOffset; i < offset; i += 2) {
- if (i === offset - 1) {
- startIdx = result.startIdx[i];
- endIdx = result.endIdx[i];
- minIdx = result.minIdx[i];
- maxIdx = result.maxIdx[i];
- open = result.open[i];
- close = result.close[i];
- minX = result.minX[i];
- minY = result.minY[i];
- maxX = result.maxX[i];
- maxY = result.maxY[i];
- } else {
- startIdx = result.startIdx[i];
- endIdx = result.endIdx[i + 1];
- open = result.open[i];
- close = result.close[i];
- if (result.minY[i] <= result.minY[i + 1]) {
- minIdx = result.minIdx[i];
- minX = result.minX[i];
- minY = result.minY[i];
- } else {
- minIdx = result.minIdx[i + 1];
- minX = result.minX[i + 1];
- minY = result.minY[i + 1];
- }
- if (result.maxY[i] >= result.maxY[i + 1]) {
- maxIdx = result.maxIdx[i];
- maxX = result.maxX[i];
- maxY = result.maxY[i];
- } else {
- maxIdx = result.maxIdx[i + 1];
- maxX = result.maxX[i + 1];
- maxY = result.maxY[i + 1];
- }
- }
- result.startIdx[position] = startIdx;
- result.endIdx[position] = endIdx;
- result.minIdx[position] = minIdx;
- result.maxIdx[position] = maxIdx;
- result.open[position] = open;
- result.close[position] = close;
- result.minX[position] = minX;
- result.minY[position] = minY;
- result.maxX[position] = maxX;
- result.maxY[position] = maxY;
- position++;
- }
- result.map['double_' + step] = [
- offset,
- position
- ];
- }
- },
- /**
- * @method
- * @private
- */
- none: Ext.emptyFn,
- /**
- * @private
- *
- * @param {Number} dataX
- * @param {Number} dataOpen
- * @param {Number} dataHigh
- * @param {Number} dataLow
- * @param {Number} dataClose
- * @return {Object}
- */
- aggregateData: function(dataX, dataOpen, dataHigh, dataLow, dataClose) {
- var length = dataX.length,
- startIdx = [],
- endIdx = [],
- minIdx = [],
- maxIdx = [],
- open = [],
- minX = [],
- minY = [],
- maxX = [],
- maxY = [],
- close = [],
- result = {
- startIdx: startIdx,
- endIdx: endIdx,
- minIdx: minIdx,
- maxIdx: maxIdx,
- open: open,
- minX: minX,
- minY: minY,
- maxX: maxX,
- maxY: maxY,
- close: close
- },
- i;
- for (i = 0; i < length; i++) {
- startIdx[i] = i;
- endIdx[i] = i;
- minIdx[i] = i;
- maxIdx[i] = i;
- open[i] = dataOpen[i];
- minX[i] = dataX[i];
- minY[i] = dataLow[i];
- maxX[i] = dataX[i];
- maxY[i] = dataHigh[i];
- close[i] = dataClose[i];
- }
- result.map = {
- original: [
- 0,
- length
- ]
- };
- if (length) {
- this[this.getStrategy()](result, length, dataX, dataOpen, dataHigh, dataLow, dataClose);
- }
- return result;
- },
- /**
- * @private
- * @param {Object} items
- * @param {Number} start
- * @param {Number} end
- * @param {Number} key
- * @return {*}
- */
- binarySearchMin: function(items, start, end, key) {
- var dx = this.dataX,
- mid, val;
- if (key <= dx[items.startIdx[0]]) {
- return start;
- }
- if (key >= dx[items.startIdx[end - 1]]) {
- return end - 1;
- }
- while (start + 1 < end) {
- mid = (start + end) >> 1;
- val = dx[items.startIdx[mid]];
- if (val === key) {
- return mid;
- } else if (val < key) {
- start = mid;
- } else {
- end = mid;
- }
- }
- return start;
- },
- /**
- * @private
- * @param {Object} items
- * @param {Number} start
- * @param {Number} end
- * @param {Number} key
- * @return {*}
- */
- binarySearchMax: function(items, start, end, key) {
- var dx = this.dataX,
- mid, val;
- if (key <= dx[items.endIdx[0]]) {
- return start;
- }
- if (key >= dx[items.endIdx[end - 1]]) {
- return end - 1;
- }
- while (start + 1 < end) {
- mid = (start + end) >> 1;
- val = dx[items.endIdx[mid]];
- if (val === key) {
- return mid;
- } else if (val < key) {
- start = mid;
- } else {
- end = mid;
- }
- }
- return end;
- },
- constructor: function(config) {
- this.initConfig(config);
- },
- /**
- * Sets the data of the segment tree.
- * @param {Number} dataX
- * @param {Number} dataOpen
- * @param {Number} dataHigh
- * @param {Number} dataLow
- * @param {Number} dataClose
- */
- setData: function(dataX, dataOpen, dataHigh, dataLow, dataClose) {
- if (!dataHigh) {
- dataClose = dataLow = dataHigh = dataOpen;
- }
- this.dataX = dataX;
- this.dataOpen = dataOpen;
- this.dataHigh = dataHigh;
- this.dataLow = dataLow;
- this.dataClose = dataClose;
- if (dataX.length === dataHigh.length && dataX.length === dataLow.length) {
- this.cache = this.aggregateData(dataX, dataOpen, dataHigh, dataLow, dataClose);
- }
- },
- /**
- * Returns the minimum range of data that fits the given range and step size.
- *
- * @param {Number} min
- * @param {Number} max
- * @param {Number} estStep
- * @return {Object} The aggregation information.
- * @return {Number} return.start
- * @return {Number} return.end
- * @return {Object} return.data The aggregated data
- */
- getAggregation: function(min, max, estStep) {
- if (!this.cache) {
- return null;
- }
- // eslint-disable-next-line vars-on-top
- var minStep = Infinity,
- range = this.dataX[this.dataX.length - 1] - this.dataX[0],
- cacheMap = this.cache.map,
- result = cacheMap.original,
- name, positions, ln, step, minIdx, maxIdx;
- for (name in cacheMap) {
- positions = cacheMap[name];
- ln = positions[1] - positions[0] - 1;
- step = range / ln;
- if (estStep <= step && step < minStep) {
- result = positions;
- minStep = step;
- }
- }
- minIdx = Math.max(this.binarySearchMin(this.cache, result[0], result[1], min), result[0]);
- maxIdx = Math.min(this.binarySearchMax(this.cache, result[0], result[1], max) + 1, result[1]);
- return {
- data: this.cache,
- start: minIdx,
- end: maxIdx
- };
- }
- });
- /**
- *
- */
- Ext.define('Ext.chart.series.sprite.Aggregative', {
- extend: 'Ext.chart.series.sprite.Cartesian',
- requires: [
- 'Ext.draw.LimitedCache',
- 'Ext.draw.SegmentTree'
- ],
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Number[]} [dataHigh=null] Data items representing the high values
- * of the aggregated data.
- */
- dataHigh: 'data',
- /**
- * @cfg {Number[]} [dataLow=null] Data items representing the low values
- * of the aggregated data.
- */
- dataLow: 'data',
- /**
- * @cfg {Number[]} [dataClose=null] Data items representing the closing values
- * of the aggregated data.
- */
- dataClose: 'data'
- },
- aliases: {
- /**
- * @cfg {Number[]} [dataOpen=null] Data items representing the opening values
- * of the aggregated data.
- */
- dataOpen: 'dataY'
- },
- defaults: {
- dataHigh: null,
- dataLow: null,
- dataClose: null
- }
- }
- },
- config: {
- aggregator: {}
- },
- applyAggregator: function(aggregator, oldAggr) {
- return Ext.factory(aggregator, Ext.draw.SegmentTree, oldAggr);
- },
- constructor: function() {
- this.callParent(arguments);
- },
- processDataY: function() {
- var me = this,
- attr = me.attr,
- high = attr.dataHigh,
- low = attr.dataLow,
- close = attr.dataClose,
- open = attr.dataY,
- aggregator;
- me.callParent(arguments);
- if (attr.dataX && open && open.length > 0) {
- aggregator = me.getAggregator();
- if (high) {
- aggregator.setData(attr.dataX, attr.dataY, high, low, close);
- } else {
- aggregator.setData(attr.dataX, attr.dataY);
- }
- }
- },
- getGapWidth: function() {
- return 1;
- },
- renderClipped: function(surface, ctx, dataClipRect, surfaceClipRect) {
- var me = this,
- min = Math.min(dataClipRect[0], dataClipRect[2]),
- max = Math.max(dataClipRect[0], dataClipRect[2]),
- aggregator = me.getAggregator(),
- aggregates = aggregator && aggregator.getAggregation(min, max, (max - min) / surfaceClipRect[2] * me.getGapWidth());
- if (aggregates) {
- me.dataStart = aggregates.data.startIdx[aggregates.start];
- me.dataEnd = aggregates.data.endIdx[aggregates.end - 1];
- me.renderAggregates(aggregates.data, aggregates.start, aggregates.end, surface, ctx, dataClipRect, surfaceClipRect);
- }
- }
- });
- /**
- * @class Ext.chart.series.sprite.CandleStick
- * @extends Ext.chart.series.sprite.Aggregative
- *
- * CandleStick series sprite.
- */
- Ext.define('Ext.chart.series.sprite.CandleStick', {
- alias: 'sprite.candlestickSeries',
- extend: 'Ext.chart.series.sprite.Aggregative',
- inheritableStatics: {
- def: {
- processors: {
- raiseStyle: function(n, o) {
- return Ext.merge({}, o || {}, n);
- },
- dropStyle: function(n, o) {
- return Ext.merge({}, o || {}, n);
- },
- /**
- * @cfg {Number} [barWidth=15] The bar width of the candles.
- */
- barWidth: 'number',
- /**
- * @cfg {Number} [padding=3] The amount of padding between candles.
- */
- padding: 'number',
- /**
- * @cfg {String} [ohlcType='candlestick'] Determines whether candlestick
- * or ohlc is used.
- */
- ohlcType: 'enums(candlestick,ohlc)'
- },
- defaults: {
- raiseStyle: {
- strokeStyle: 'green',
- fillStyle: 'green'
- },
- dropStyle: {
- strokeStyle: 'red',
- fillStyle: 'red'
- },
- barWidth: 15,
- padding: 3,
- lineJoin: 'miter',
- miterLimit: 5,
- ohlcType: 'candlestick'
- },
- triggers: {
- raiseStyle: 'raiseStyle',
- dropStyle: 'dropStyle'
- },
- updaters: {
- raiseStyle: function() {
- var me = this,
- tpl = me.raiseTemplate;
- if (tpl) {
- tpl.setAttributes(me.attr.raiseStyle);
- }
- },
- dropStyle: function() {
- var me = this,
- tpl = me.dropTemplate;
- if (tpl) {
- tpl.setAttributes(me.attr.dropStyle);
- }
- }
- }
- }
- },
- candlestick: function(ctx, open, high, low, close, mid, halfWidth) {
- var minOC = Math.min(open, close),
- maxOC = Math.max(open, close);
- // lower stick
- ctx.moveTo(mid, low);
- ctx.lineTo(mid, minOC);
- // body rect
- ctx.moveTo(mid + halfWidth, maxOC);
- ctx.lineTo(mid + halfWidth, minOC);
- ctx.lineTo(mid - halfWidth, minOC);
- ctx.lineTo(mid - halfWidth, maxOC);
- ctx.closePath();
- // upper stick
- ctx.moveTo(mid, high);
- ctx.lineTo(mid, maxOC);
- },
- ohlc: function(ctx, open, high, low, close, mid, halfWidth) {
- ctx.moveTo(mid, high);
- ctx.lineTo(mid, low);
- ctx.moveTo(mid, open);
- ctx.lineTo(mid - halfWidth, open);
- ctx.moveTo(mid, close);
- ctx.lineTo(mid + halfWidth, close);
- },
- constructor: function() {
- var me = this,
- Rect = Ext.draw.sprite.Rect;
- me.callParent(arguments);
- me.raiseTemplate = new Rect({
- parent: me
- });
- me.dropTemplate = new Rect({
- parent: me
- });
- },
- getGapWidth: function() {
- var attr = this.attr,
- barWidth = attr.barWidth,
- padding = attr.padding;
- return barWidth + padding;
- },
- renderAggregates: function(aggregates, start, end, surface, ctx, clip) {
- var me = this,
- attr = me.attr,
- ohlcType = attr.ohlcType,
- series = me.getSeries(),
- matrix = attr.matrix,
- xx = matrix.getXX(),
- yy = matrix.getYY(),
- dx = matrix.getDX(),
- dy = matrix.getDY(),
- halfWidth = Math.round(attr.barWidth * 0.5),
- dataX = attr.dataX,
- opens = aggregates.open,
- closes = aggregates.close,
- maxYs = aggregates.maxY,
- minYs = aggregates.minY,
- startIdxs = aggregates.startIdx,
- pixelAdjust = attr.lineWidth * surface.devicePixelRatio / 2,
- renderer = attr.renderer,
- rendererConfig = renderer && {},
- rendererParams, rendererChanges, open, high, low, close, mid, i, template;
- me.rendererData = me.rendererData || {
- store: me.getStore()
- };
- pixelAdjust -= Math.floor(pixelAdjust);
- // Render raises.
- ctx.save();
- template = me.raiseTemplate;
- template.useAttributes(ctx, clip);
- if (!renderer) {
- ctx.beginPath();
- }
- for (i = start; i < end; i++) {
- if (opens[i] <= closes[i]) {
- open = Math.round(opens[i] * yy + dy) + pixelAdjust;
- high = Math.round(maxYs[i] * yy + dy) + pixelAdjust;
- low = Math.round(minYs[i] * yy + dy) + pixelAdjust;
- close = Math.round(closes[i] * yy + dy) + pixelAdjust;
- mid = Math.round(dataX[startIdxs[i]] * xx + dx) + pixelAdjust;
- if (renderer) {
- ctx.save();
- ctx.beginPath();
- rendererConfig.open = open;
- rendererConfig.high = high;
- rendererConfig.low = low;
- rendererConfig.close = close;
- rendererConfig.mid = mid;
- rendererConfig.halfWidth = halfWidth;
- rendererParams = [
- me,
- rendererConfig,
- me.rendererData,
- i
- ];
- rendererChanges = Ext.callback(renderer, null, rendererParams, 0, series);
- Ext.apply(ctx, rendererChanges);
- }
- me[ohlcType](ctx, open, high, low, close, mid, halfWidth);
- if (renderer) {
- ctx.fillStroke(template.attr);
- ctx.restore();
- }
- }
- }
- if (!renderer) {
- ctx.fillStroke(template.attr);
- }
- ctx.restore();
- // Render drops.
- ctx.save();
- template = me.dropTemplate;
- template.useAttributes(ctx, clip);
- if (!renderer) {
- ctx.beginPath();
- }
- for (i = start; i < end; i++) {
- if (opens[i] > closes[i]) {
- open = Math.round(opens[i] * yy + dy) + pixelAdjust;
- high = Math.round(maxYs[i] * yy + dy) + pixelAdjust;
- low = Math.round(minYs[i] * yy + dy) + pixelAdjust;
- close = Math.round(closes[i] * yy + dy) + pixelAdjust;
- mid = Math.round(dataX[startIdxs[i]] * xx + dx) + pixelAdjust;
- if (renderer) {
- ctx.save();
- ctx.beginPath();
- rendererConfig.open = open;
- rendererConfig.high = high;
- rendererConfig.low = low;
- rendererConfig.close = close;
- rendererConfig.mid = mid;
- rendererConfig.halfWidth = halfWidth;
- rendererParams = [
- me,
- rendererConfig,
- me.rendererData,
- i
- ];
- rendererChanges = Ext.callback(renderer, null, rendererParams, 0, me.getSeries());
- Ext.apply(ctx, rendererChanges);
- }
- me[ohlcType](ctx, open, high, low, close, mid, halfWidth);
- if (renderer) {
- ctx.fillStroke(template.attr);
- ctx.restore();
- }
- }
- }
- if (!renderer) {
- ctx.fillStroke(template.attr);
- }
- ctx.restore();
- }
- });
- /**
- * @class Ext.chart.series.CandleStick
- * @extends Ext.chart.series.Cartesian
- *
- * Creates a candlestick or OHLC Chart.
- *
- * CandleStick series are typically used to plot price movements of a security on an exchange
- * over time. The series can be used with the 'time' axis, but since exchanges often close
- * for weekends, and the price data has gaps for those days, it's more practical to use this series
- * with the 'category' axis to avoid rendering those data gaps. The 'category' axis has no notion
- * of time (and thus gaps) and treats every Date object (value of the 'xField') as a unique
- * category. However, it also means that it doesn't support the 'dateFormat' config,
- * which can be easily remedied with a 'renderer' that formats a Date object for use
- * as an axis label. For example:
- *
- * @example
- * new Ext.chart.CartesianChart({
- * xtype: 'cartesian',
- * renderTo: document.body,
- * width: 700,
- * height: 500,
- * insetPadding: 20,
- * innerPadding: '0 20 0 20',
- *
- * store: {
- * data: [
- * {
- * time: new Date('Nov 17 2016'),
- * o: 52.40, h: 52.74, l: 52.18, c: 52.29
- * },
- * {
- * time: new Date('Nov 18 2016'),
- * o: 51.87, h: 52.22, l: 51.51, c: 52.04
- * },
- * {
- * time: new Date('Nov 21 2016'),
- * o: 53.02, h: 53.40, l: 53.02, c: 53.33
- * },
- * {
- * time: new Date('Nov 22 2016'),
- * o: 53.48, h: 53.80, l: 53.13, c: 53.70
- * },
- * {
- * time: new Date('Nov 23 2016'),
- * o: 52.85, h: 53.39, l: 52.76, c: 53.28
- * },
- * {
- * time: new Date('Nov 25 2016'),
- * o: 53.28, h: 53.45, l: 53.20, c: 53.40
- * },
- * {
- * time: new Date('Nov 28 2016'),
- * o: 52.51, h: 52.58, l: 51.96, c: 52.00
- * },
- * {
- * time: new Date('Nov 29 2016'),
- * o: 51.25, h: 51.98, l: 51.10, c: 51.79
- * },
- * {
- * time: new Date('Nov 30 2016'),
- * o: 53.65, h: 54.56, l: 53.60, c: 54.17
- * },
- * {
- * time: new Date('Dec 01 2016'),
- * o: 55.26, h: 55.75, l: 54.94, c: 55.13
- * }
- * ]
- * },
- * axes: [
- * {
- * type: 'numeric',
- * position: 'left'
- * },
- * {
- * type: 'category',
- * position: 'bottom',
- *
- * renderer: function (axis, value) {
- * return Ext.Date.format(value, 'M j\nY');
- * }
- * }
- * ],
- * series: {
- * type: 'candlestick',
- *
- * xField: 'time',
- *
- * openField: 'o',
- * highField: 'h',
- * lowField: 'l',
- * closeField: 'c',
- *
- * style: {
- * barWidth: 10,
- *
- * dropStyle: {
- * fill: 'rgb(222, 87, 87)',
- * stroke: 'rgb(222, 87, 87)',
- * lineWidth: 3
- * },
- * raiseStyle: {
- * fill: 'rgb(48, 189, 167)',
- * stroke: 'rgb(48, 189, 167)',
- * lineWidth: 3
- * }
- * }
- * }
- * });
- */
- Ext.define('Ext.chart.series.CandleStick', {
- extend: 'Ext.chart.series.Cartesian',
- requires: [
- 'Ext.chart.series.sprite.CandleStick'
- ],
- alias: 'series.candlestick',
- type: 'candlestick',
- seriesType: 'candlestickSeries',
- isCandleStick: true,
- config: {
- /**
- * @cfg {String} openField
- * The store record field name that represents the opening value of the given period.
- */
- openField: null,
- /**
- * @cfg {String} highField
- * The store record field name that represents the highest value of the time interval
- * represented.
- */
- highField: null,
- /**
- * @cfg {String} lowField
- * The store record field name that represents the lowest value of the time interval
- * represented.
- */
- lowField: null,
- /**
- * @cfg {String} closeField
- * The store record field name that represents the closing value of the given period.
- */
- closeField: null
- },
- fieldCategoryY: [
- 'Open',
- 'High',
- 'Low',
- 'Close'
- ],
- themeColorCount: function() {
- return 2;
- }
- });
- /**
- * @abstract
- * @class Ext.chart.series.Polar
- * @extends Ext.chart.series.Series
- *
- * Common base class for series implementations that plot values using polar coordinates.
- *
- * Polar charts accept angles in radians. You can calculate radians with the following
- * formula:
- *
- * radians = degrees x Π/180
- */
- Ext.define('Ext.chart.series.Polar', {
- extend: 'Ext.chart.series.Series',
- config: {
- /**
- * @cfg {Number} [rotation=0]
- * The angle in radians at which the first polar series item should start.
- */
- rotation: 0,
- /**
- * @cfg {Number} radius
- * @private
- * Use {@link Ext.chart.series.Pie#cfg!radiusFactor radiusFactor} instead.
- *
- * The internally used radius of the polar series. Set to `null` will fit the
- * polar series to the boundary.
- */
- radius: null,
- /**
- * @cfg {Array} center for the polar series.
- */
- center: [
- 0,
- 0
- ],
- /**
- * @cfg {Number} [offsetX=0]
- * The x-offset of center of the polar series related to the center of the boundary.
- */
- offsetX: 0,
- /**
- * @cfg {Number} [offsetY=0]
- * The y-offset of center of the polar series related to the center of the boundary.
- */
- offsetY: 0,
- /**
- * @cfg {Boolean} [showInLegend=true]
- * Whether to add the series elements as legend items.
- */
- showInLegend: true,
- /**
- * @private
- * @cfg {String} xField
- */
- xField: null,
- /**
- * @private
- * @cfg {String} yField
- */
- yField: null,
- /**
- * @cfg {String} angleField
- * The store record field name for the angular axes in radar charts,
- * or the size of the slices in pie charts.
- */
- angleField: null,
- /**
- * @cfg {String} radiusField
- * The store record field name for the radial axes in radar charts,
- * or the radius of the slices in pie charts.
- */
- radiusField: null,
- xAxis: null,
- yAxis: null
- },
- directions: [
- 'X',
- 'Y'
- ],
- fieldCategoryX: [
- 'X'
- ],
- fieldCategoryY: [
- 'Y'
- ],
- deprecatedConfigs: {
- field: 'angleField',
- lengthField: 'radiusField'
- },
- constructor: function(config) {
- var me = this,
- configurator = me.self.getConfigurator(),
- configs = configurator.configs,
- p;
- if (config) {
- for (p in me.deprecatedConfigs) {
- if (p in config && !(config in configs)) {
- Ext.raise("'" + p + "' config has been deprecated. Please use the '" + me.deprecatedConfigs[p] + "' config instead.");
- }
- }
- }
- me.callParent([
- config
- ]);
- },
- getXField: function() {
- return this.getAngleField();
- },
- updateXField: function(value) {
- this.setAngleField(value);
- },
- getYField: function() {
- return this.getRadiusField();
- },
- updateYField: function(value) {
- this.setRadiusField(value);
- },
- applyXAxis: function(newAxis, oldAxis) {
- return this.getChart().getAxis(newAxis) || oldAxis;
- },
- applyYAxis: function(newAxis, oldAxis) {
- return this.getChart().getAxis(newAxis) || oldAxis;
- },
- getXRange: function() {
- return [
- this.dataRange[0],
- this.dataRange[2]
- ];
- },
- getYRange: function() {
- return [
- this.dataRange[1],
- this.dataRange[3]
- ];
- },
- themeColorCount: function() {
- var me = this,
- store = me.getStore(),
- count = store && store.getCount() || 0;
- return count;
- },
- isStoreDependantColorCount: true,
- getDefaultSpriteConfig: function() {
- return {
- type: this.seriesType,
- renderer: this.getRenderer(),
- centerX: 0,
- centerY: 0,
- rotationCenterX: 0,
- rotationCenterY: 0
- };
- },
- applyRotation: function(rotation) {
- return Ext.draw.sprite.AttributeParser.angle(Ext.draw.Draw.rad(rotation));
- },
- updateRotation: function(rotation) {
- var sprites = this.getSprites();
- if (sprites && sprites[0]) {
- sprites[0].setAttributes({
- baseRotation: rotation
- });
- }
- }
- });
- /**
- * Displays a gauge chart.
- *
- * @example
- * Ext.create({
- * xtype: 'polar',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * store: {
- * fields: ['mph', 'fuel', 'temp', 'rpm'],
- * data: [{
- * mph: 65,
- * fuel: 50,
- * temp: 150,
- * rpm: 6000
- * }]
- * },
- * series: {
- * type: 'gauge',
- * colors: ['#1F6D91', '#90BCC9'],
- * angleField: 'mph',
- * needle: true,
- * donut: 30
- * }
- * });
- */
- Ext.define('Ext.chart.series.Gauge', {
- alias: 'series.gauge',
- extend: 'Ext.chart.series.Polar',
- type: 'gauge',
- seriesType: 'pieslice',
- requires: [
- 'Ext.draw.sprite.Sector'
- ],
- config: {
- /**
- * @cfg {String} angleField
- * The store record field name to be used for the gauge value.
- * The values bound to this field name must be positive real numbers.
- */
- /**
- * @cfg {Boolean} needle
- * If true, display the gauge as a needle, otherwise as a sector.
- */
- needle: false,
- /**
- * @cfg {Number} needleLength
- * Percentage of the length of needle compared to the radius of the entire disk.
- */
- needleLength: 90,
- /**
- * @cfg {Number} needleWidth
- * Width of the needle in pixels.
- */
- needleWidth: 4,
- /**
- * @cfg {Number} donut
- * Percentage of the radius of the donut hole compared to the entire disk.
- */
- donut: 30,
- /**
- * @cfg {Boolean} showInLegend
- * Whether to add the gauge chart elements as legend items.
- */
- showInLegend: false,
- /**
- * @cfg {Number} value
- * Directly sets the displayed value of the gauge.
- * It is ignored if {@link #angleField} is provided.
- */
- value: null,
- /**
- * @cfg {Array} colors (required)
- * An array of color values which is used for the needle and the `sectors`.
- */
- colors: null,
- /**
- * @cfg {Array} sectors
- * Allows to paint sectors of different colors in the background of the gauge,
- * with optional labels.
- *
- * It can be an array of numbers (each between `minimum` and `maximum`) that
- * define the highest value of each sector. For N sectors, only (N-1) values are
- * needed because it is assumed that the first sector starts at `minimum` and the
- * last sector ends at `maximum`. Example: a water temperature gauge that is blue
- * below 20C, red above 80C, gray in-between, and with an orange needle...
- *
- * minimum: 0,
- * maximum: 100,
- * sectors: [20, 80],
- * colors: ['orange', 'blue', 'lightgray', 'red']
- *
- * It can be also an array of objects, each with the following properties:
- *
- * @cfg {Number} sectors.start The starting value of the sector. If omitted, it
- * uses the previous sector's `end` value or the chart's `minimum`.
- * @cfg {Number} sectors.end The ending value of the sector. If omitted, it uses
- * the `maximum` defined for the chart.
- * @cfg {String} sectors.label The label for this sector. Labels are styled using
- * the series' {@link Ext.chart.series.Series#label label} config.
- * @cfg {String} sectors.color The color of the sector. If omitted, it uses one
- * of the `colors` defined for the series or for the chart.
- * @cfg {Object} sectors.style An additional style object for the sector (for
- * instance to set the opacity or to draw a line of a different color around the
- * sector).
- *
- * minimum: 0,
- * maximum: 100,
- * sectors: [{
- * end: 20,
- * label: 'Cold',
- * color: 'aqua'
- * },
- * {
- * end: 80,
- * label: 'Temp.',
- * color: 'lightgray',
- * style: { strokeStyle:'black', strokeOpacity:1, lineWidth:1 }
- * },
- * {
- * label: 'Hot',
- * color: 'tomato'
- * }]
- */
- sectors: null,
- /**
- * @cfg {Number} minimum
- * The minimum value of the gauge.
- */
- minimum: 0,
- /**
- * @cfg {Number} maximum
- * The maximum value of the gauge.
- */
- maximum: 100,
- rotation: 0,
- /**
- * @cfg {Number} totalAngle
- * The size of the sector that the series will occupy.
- */
- totalAngle: Math.PI / 2,
- rect: [
- 0,
- 0,
- 1,
- 1
- ],
- center: [
- 0.5,
- 0.75
- ],
- radius: 0.5,
- /**
- * @cfg {Boolean} wholeDisk Indicates whether to show the whole disk
- * or only the marked part.
- */
- wholeDisk: false
- },
- coordinateX: function() {
- return this.coordinate('X', 0, 2);
- },
- coordinateY: function() {
- return this.coordinate('Y', 1, 2);
- },
- updateNeedle: function(needle) {
- var me = this,
- sprites = me.getSprites(),
- angle = me.valueToAngle(me.getValue());
- if (sprites && sprites.length) {
- sprites[0].setAttributes({
- startAngle: (needle ? angle : 0),
- endAngle: angle,
- strokeOpacity: (needle ? 1 : 0),
- lineWidth: (needle ? me.getNeedleWidth() : 0)
- });
- me.doUpdateStyles();
- }
- },
- themeColorCount: function() {
- var me = this,
- store = me.getStore(),
- count = store && store.getCount() || 0;
- return count + (me.getNeedle() ? 0 : 1);
- },
- updateColors: function(colors, oldColors) {
- var me = this,
- sectors = me.getSectors(),
- sectorCount = sectors && sectors.length,
- sprites = me.getSprites(),
- newColors = Ext.Array.clone(colors),
- colorCount = colors && colors.length,
- i;
- if (!colorCount || !colors[0]) {
- return;
- }
- // Make sure the 'sectors' colors are not overridden.
- for (i = 0; i < sectorCount; i++) {
- newColors[i + 1] = sectors[i].color || newColors[i + 1] || colors[i % colorCount];
- }
- if (sprites.length) {
- sprites[0].setAttributes({
- strokeStyle: newColors[0]
- });
- }
- this.setSubStyle({
- fillStyle: newColors,
- strokeStyle: newColors
- });
- this.doUpdateStyles();
- },
- updateRect: function(rect) {
- var wholeDisk = this.getWholeDisk(),
- halfTotalAngle = wholeDisk ? Math.PI : this.getTotalAngle() / 2,
- donut = this.getDonut() / 100,
- width, height, radius;
- if (halfTotalAngle <= Math.PI / 2) {
- width = 2 * Math.sin(halfTotalAngle);
- height = 1 - donut * Math.cos(halfTotalAngle);
- } else {
- width = 2;
- height = 1 - Math.cos(halfTotalAngle);
- }
- radius = Math.min(rect[2] / width, rect[3] / height);
- this.setRadius(radius);
- this.setCenter([
- rect[2] / 2,
- radius + (rect[3] - height * radius) / 2
- ]);
- },
- updateCenter: function(center) {
- this.setStyle({
- centerX: center[0],
- centerY: center[1],
- rotationCenterX: center[0],
- rotationCenterY: center[1]
- });
- this.doUpdateStyles();
- },
- updateRotation: function(rotation) {
- this.setStyle({
- rotationRads: rotation - (this.getTotalAngle() + Math.PI) / 2
- });
- this.doUpdateStyles();
- },
- doUpdateShape: function(radius, donut) {
- var me = this,
- sectors = me.getSectors(),
- sectorCount = (sectors && sectors.length) || 0,
- needleLength = me.getNeedleLength() / 100,
- endRhoArray;
- // Initialize an array that contains the endRho for each sprite.
- // The first sprite is for the needle, the others for the gauge background sectors.
- // Note: SubStyle arrays are handled in series.getStyleByIndex().
- endRhoArray = [
- radius * needleLength,
- radius
- ];
- while (sectorCount--) {
- endRhoArray.push(radius);
- }
- me.setSubStyle({
- endRho: endRhoArray,
- startRho: radius / 100 * donut
- });
- me.doUpdateStyles();
- },
- updateRadius: function(radius) {
- var donut = this.getDonut();
- this.doUpdateShape(radius, donut);
- },
- updateDonut: function(donut) {
- var radius = this.getRadius();
- this.doUpdateShape(radius, donut);
- },
- valueToAngle: function(value) {
- value = this.applyValue(value);
- return this.getTotalAngle() * (value - this.getMinimum()) / (this.getMaximum() - this.getMinimum());
- },
- applyValue: function(value) {
- return Math.min(this.getMaximum(), Math.max(value, this.getMinimum()));
- },
- updateValue: function(value) {
- var me = this,
- needle = me.getNeedle(),
- angle = me.valueToAngle(value),
- sprites = me.getSprites();
- sprites[0].getRendererData().value = value;
- sprites[0].setAttributes({
- startAngle: (needle ? angle : 0),
- endAngle: angle
- });
- me.doUpdateStyles();
- },
- processData: function() {
- var me = this,
- store = me.getStore(),
- record = store && store.first(),
- animation, duration, axis, min, max, xField, value;
- if (record) {
- xField = me.getXField();
- if (xField) {
- value = record.get(xField);
- }
- }
- axis = me.getXAxis();
- if (axis) {
- min = axis.getMinimum();
- max = axis.getMaximum();
- // Animating the axis here can lead to weird looking results.
- animation = axis.getSprites()[0].getAnimation();
- duration = animation.getDuration();
- animation.setDuration(0);
- if (Ext.isNumber(min)) {
- me.setMinimum(min);
- } else {
- axis.setMinimum(me.getMinimum());
- }
- if (Ext.isNumber(max)) {
- me.setMaximum(max);
- } else {
- axis.setMaximum(me.getMaximum());
- }
- animation.setDuration(duration);
- }
- if (!Ext.isNumber(value)) {
- value = me.getMinimum();
- }
- me.setValue(value);
- },
- getDefaultSpriteConfig: function() {
- return {
- type: this.seriesType,
- renderer: this.getRenderer(),
- animation: {
- customDurations: {
- translationX: 0,
- translationY: 0,
- rotationCenterX: 0,
- rotationCenterY: 0,
- centerX: 0,
- centerY: 0,
- startRho: 0,
- endRho: 0,
- baseRotation: 0
- }
- }
- };
- },
- normalizeSectors: function(sectors) {
- // Make sure all the sectors in the array have a legit start and end.
- // Note: the array is modified in-place.
- var me = this,
- sectorCount = (sectors && sectors.length) || 0,
- i, value, start, end;
- if (sectorCount) {
- for (i = 0; i < sectorCount; i++) {
- value = sectors[i];
- if (typeof value === 'number') {
- sectors[i] = {
- start: (i > 0 ? sectors[i - 1].end : me.getMinimum()),
- end: Math.min(value, me.getMaximum())
- };
- if (i === (sectorCount - 1) && sectors[i].end < me.getMaximum()) {
- sectors[i + 1] = {
- start: sectors[i].end,
- end: me.getMaximum()
- };
- }
- } else {
- if (typeof value.start === 'number') {
- start = Math.max(value.start, me.getMinimum());
- } else {
- start = (i > 0 ? sectors[i - 1].end : me.getMinimum());
- }
- if (typeof value.end === 'number') {
- end = Math.min(value.end, me.getMaximum());
- } else {
- end = me.getMaximum();
- }
- sectors[i].start = start;
- sectors[i].end = end;
- }
- }
- } else {
- sectors = [
- {
- start: me.getMinimum(),
- end: me.getMaximum()
- }
- ];
- }
- return sectors;
- },
- getSprites: function() {
- var me = this,
- store = me.getStore(),
- value = me.getValue(),
- label = me.getLabel(),
- i, ln;
- // The store must be initialized, or the value must be set
- if (!store && !Ext.isNumber(value)) {
- return Ext.emptyArray;
- }
- // Return cached sprites
- // eslint-disable-next-line vars-on-top, one-var
- var chart = me.getChart(),
- animation = me.getAnimation() || chart && chart.getAnimation(),
- sprites = me.sprites,
- spriteIndex = 0,
- sprite, sectors, attr, rendererData,
- // Hack to avoid having the lineWidths overwritten by the one specified in the theme.
- // In fact, all the style properties from the needle and sectors should go
- // to the series subStyle.
- lineWidths = [];
- if (sprites && sprites.length) {
- sprites[0].setAnimation(animation);
- return sprites;
- }
- rendererData = {
- store: store,
- field: me.getXField(),
- // for backward compatibility only (deprecated in 5.5)
- angleField: me.getXField(),
- value: value,
- series: me
- };
- // Create needle sprite
- me.needleSprite = sprite = me.createSprite();
- sprite.setAttributes({
- zIndex: 10
- }, true);
- sprite.setRendererData(rendererData);
- sprite.setRendererIndex(spriteIndex++);
- lineWidths.push(me.getNeedleWidth());
- if (label) {
- label.getTemplate().setField(true);
- }
- // Enable labels
- // Create background sprite(s)
- sectors = me.normalizeSectors(me.getSectors());
- for (i = 0 , ln = sectors.length; i < ln; i++) {
- attr = {
- startAngle: me.valueToAngle(sectors[i].start),
- endAngle: me.valueToAngle(sectors[i].end),
- label: sectors[i].label,
- fillStyle: sectors[i].color,
- strokeOpacity: 0,
- doCallout: false,
- // Show labels inside sectors.
- labelOverflowPadding: -1
- };
- // Allow labels to overlap.
- Ext.apply(attr, sectors[i].style);
- sprite = me.createSprite();
- sprite.setRendererData(rendererData);
- sprite.setRendererIndex(spriteIndex++);
- sprite.setAttributes(attr, true);
- lineWidths.push(attr.lineWidth);
- }
- me.setSubStyle({
- lineWidth: lineWidths
- });
- me.doUpdateStyles();
- return sprites;
- },
- doUpdateStyles: function() {
- var me = this;
- me.callParent();
- if (me.sprites.length) {
- me.needleSprite.setAttributes({
- startRho: me.getNeedle() ? 0 : (me.getRadius() / 100 * me.getDonut())
- });
- }
- }
- });
- /**
- * @class Ext.chart.series.sprite.Line
- * @extends Ext.chart.series.sprite.Aggregative
- *
- * Line series sprite.
- */
- Ext.define('Ext.chart.series.sprite.Line', {
- alias: 'sprite.lineSeries',
- extend: 'Ext.chart.series.sprite.Aggregative',
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Object} [curve={type: 'linear'}]
- * The type of curve that connects the data points.
- *
- * For example:
- *
- * // The data points are connected by line segments.
- * // This is the default setting.
- * curve: {
- * type: 'linear'
- * }
- *
- * // Cardinal spline interpolation is used to produce the curve
- * // that connects the data points. The `tension` parameter can
- * // be used to control the smoothness of the curve. A tension
- * // of 0 corresponds to infinite tension, which results in straight
- * // lines between data points. A tension of 1 corresponds to
- * // no tension, allowing the spline to take the path of least
- * // total bend. With tension values greater than 1, the curve
- * // behaves like a compressed spring, pushed to take a longer path.
- * // A cardinal spline with a tension of 0.5 is a special case.
- * // It is then called a Catmull-Rom spline. Catmull-Rom splines are
- * // thought to be esthetically pleasing and are quite common.
- * // Note: spline interpolation only works on gapless data.
- * curve: {
- * type: 'cardinal,
- * tension: 0.5
- * }
- *
- * // Produces a natural cubic spline with the second derivative
- * // of the spline set to zero at the endpoints.
- * curve: {
- * type: 'natural'
- * }
- *
- * // The data points are connected by alternating horizontal and
- * // vertical lines. The y-value changes after the x-value.
- * curve: {
- * type: 'step-after'
- * }
- *
- */
- curve: 'default',
- /**
- * @cfg {Boolean} [fillArea=false]
- * `true` if the sprite paints the area underneath the line.
- */
- fillArea: 'bool',
- /**
- * @cfg {"gap"/"connect"/"origin"} [nullStyle="gap"]
- * Possible values:
- * 'gap' - null points are rendered as gaps.
- * 'connect' - non-null points are connected across null points, so that
- * there is no gap, unless null points are at the beginning/end of the line.
- * Only the visible data points are connected - if a visible data point
- * is followed by a series of null points that go off screen and eventually
- * terminate with a non-null point, the connection won't be made.
- * 'origin' - null data points are rendered at the origin,
- * which is the y-coordinate of a point where the x and y axes meet.
- * This requires that at least the x-coordinate of a point is a valid value.
- */
- nullStyle: 'enums(gap,connect,origin)',
- /**
- * @cfg {Boolean} [preciseStroke=true]
- * `true` if the line uses precise stroke.
- */
- preciseStroke: 'bool',
- /**
- * @private
- * The x-axis associated with the Line series.
- * We need to know the position of the x-axis to fill the area underneath
- * the stroke properly.
- */
- xAxis: 'default',
- /**
- * @cfg {Number} [yCap=Math.pow(2, 20)]
- * Absolute maximum y-value.
- * Larger values will be capped to avoid rendering issues.
- */
- // The 'default' processor is used here as we don't want this attribute to animate.
- yCap: 'default'
- },
- defaults: {
- curve: {
- type: 'linear'
- },
- nullStyle: 'connect',
- fillArea: false,
- preciseStroke: true,
- xAxis: null,
- yCap: Math.pow(2, 20),
- yJump: 50
- },
- triggers: {
- dataX: 'dataX,bbox,curve',
- dataY: 'dataY,bbox,curve',
- curve: 'curve'
- },
- updaters: {
- curve: 'curveUpdater'
- }
- }
- },
- list: null,
- curveUpdater: function(attr) {
- var me = this,
- dataX = attr.dataX,
- dataY = attr.dataY,
- curve = attr.curve,
- smoothable = dataX && dataY && dataX.length > 2 && dataY.length > 2,
- type = curve.type;
- if (smoothable) {
- if (type === 'natural') {
- me.smoothX = Ext.draw.Draw.naturalSpline(dataX);
- me.smoothY = Ext.draw.Draw.naturalSpline(dataY);
- } else if (type === 'cardinal') {
- me.smoothX = Ext.draw.Draw.cardinalSpline(dataX, curve.tension);
- me.smoothY = Ext.draw.Draw.cardinalSpline(dataY, curve.tension);
- } else {
- smoothable = false;
- }
- }
- if (!smoothable) {
- delete me.smoothX;
- delete me.smoothY;
- }
- },
- updatePlainBBox: function(plain) {
- var attr = this.attr,
- ymin = Math.min(0, attr.dataMinY),
- ymax = Math.max(0, attr.dataMaxY);
- plain.x = attr.dataMinX;
- plain.y = ymin;
- plain.width = attr.dataMaxX - attr.dataMinX;
- plain.height = ymax - ymin;
- },
- drawStrip: function(ctx, strip) {
- var i, ln;
- ctx.moveTo(strip[0], strip[1]);
- for (i = 2 , ln = strip.length; i < ln; i += 2) {
- ctx.lineTo(strip[i], strip[i + 1]);
- }
- },
- drawStraightStroke: function(surface, ctx, start, end, list, xAxis) {
- var me = this,
- attr = me.attr,
- nullStyle = attr.nullStyle,
- isConnect = nullStyle === 'connect',
- isOrigin = nullStyle === 'origin',
- renderer = attr.renderer,
- curve = attr.curve,
- step = curve.type === 'step-after',
- needMoveTo = true,
- ln = list.length,
- lineConfig = {
- type: 'line',
- smooth: false,
- step: step
- },
- rendererChanges, params, stripStartX, isValidX0, isValidX, isValidX1, isValidPoint0, isValidPoint, isValidPoint1, isGap, lastValidPoint, px, py, x, y, x0, y0, x1, y1, i,
- // 'strip' stores last continuous segment of the stroke,
- // which we may need to re-build, if there's a fill as well.
- // For example, if the renderer returned a style that needs
- // to be applied to the current step, or we reached a null
- // point in the data, where we have to fill the current continuous
- // segment, we build and close a path that will be filled, then
- // re-build the stroke path, using coordinates saved in the 'strip',
- // and render the stroke on top of the fill.
- strip = [];
- ctx.beginPath();
- for (i = 3; i < ln; i += 3) {
- x0 = list[i - 3];
- y0 = list[i - 2];
- x = list[i];
- y = list[i + 1];
- x1 = list[i + 3];
- y1 = list[i + 4];
- isValidX0 = Ext.isNumber(x0);
- isValidX = Ext.isNumber(x);
- isValidX1 = Ext.isNumber(x1);
- isValidPoint0 = isValidX0 && Ext.isNumber(y0);
- isValidPoint = isValidX && Ext.isNumber(y);
- isValidPoint1 = isValidX1 && Ext.isNumber(y1);
- if (isOrigin) {
- // If only the y-component isn't a valid number,
- // we can 'fix' it by setting it to value of y-origin.
- if (!isValidPoint0 && isValidX0) {
- y0 = xAxis;
- isValidPoint0 = true;
- }
- if (!isValidPoint && isValidX) {
- y = xAxis;
- isValidPoint = true;
- }
- if (!isValidPoint1 && isValidX1) {
- y1 = xAxis;
- isValidPoint1 = true;
- }
- }
- if (renderer) {
- lineConfig.x = x;
- lineConfig.y = y;
- lineConfig.x0 = x0;
- lineConfig.y0 = y0;
- params = [
- me,
- lineConfig,
- me.rendererData,
- start + i / 3
- ];
- // callback(fn, scope, args, delay, caller)
- rendererChanges = Ext.callback(renderer, null, params, 0, me.getSeries());
- }
- if (isGap && isConnect && isValidPoint0 && lastValidPoint) {
- px = lastValidPoint[0];
- py = lastValidPoint[1];
- if (needMoveTo) {
- ctx.beginPath();
- ctx.moveTo(px, py);
- strip.push(px, py);
- stripStartX = px;
- needMoveTo = false;
- }
- if (step) {
- ctx.lineTo(x0, py);
- strip.push(x0, py);
- }
- ctx.lineTo(x0, y0);
- strip.push(x0, y0);
- lastValidPoint = [
- x0,
- y0
- ];
- isGap = false;
- }
- // Special case where we have an uninterrupted segment, followed
- // by a gap, then a valid point, then another gap. The uninterrupted
- // segment should be connenected with the dot situated between the gaps.
- if (isConnect && lastValidPoint && isValidPoint && !isValidPoint0) {
- x0 = lastValidPoint[0];
- y0 = lastValidPoint[1];
- isValidPoint0 = true;
- }
- // Remember last valid point to connect the gap
- // when the next valid point is encountered.
- if (isValidPoint) {
- lastValidPoint = [
- x,
- y
- ];
- }
- if (isValidPoint0 && isValidPoint) {
- if (needMoveTo) {
- ctx.beginPath();
- ctx.moveTo(x0, y0);
- strip.push(x0, y0);
- stripStartX = x0;
- needMoveTo = false;
- }
- } else {
- isGap = true;
-
- continue;
- }
- if (step) {
- ctx.lineTo(x, y0);
- strip.push(x, y0);
- }
- ctx.lineTo(x, y);
- strip.push(x, y);
- // If the next point is a gap, then we need to fill what
- // has been already rendered so far. The same applies
- // if the renderer returned some changes to apply to
- // the current step.
- if (rendererChanges || !isValidPoint1) {
- ctx.save();
- Ext.apply(ctx, rendererChanges);
- rendererChanges = null;
- if (attr.fillArea) {
- ctx.lineTo(x, xAxis);
- ctx.lineTo(stripStartX, xAxis);
- ctx.closePath();
- ctx.fill();
- }
- // Draw the line on top of the filled area.
- ctx.beginPath();
- me.drawStrip(ctx, strip);
- strip = [];
- ctx.stroke();
- ctx.restore();
- ctx.beginPath();
- // Take note that the starting point of a path has been reset
- // (as a result of filling a sub-path) and needs to be set again
- // for the line to continue in a proper manner.
- needMoveTo = true;
- }
- }
- },
- calculateScale: function(count, end) {
- var power = 0,
- n = count;
- while (n < end && count > 0) {
- power++;
- n += count >> power;
- }
- return Math.pow(2, power > 0 ? power - 1 : power);
- },
- drawSmoothStroke: function(surface, ctx, start, end, list, xAxis) {
- var me = this,
- attr = me.attr,
- step = attr.step,
- matrix = attr.matrix,
- renderer = attr.renderer,
- xx = matrix.getXX(),
- yy = matrix.getYY(),
- dx = matrix.getDX(),
- dy = matrix.getDY(),
- smoothX = me.smoothX,
- smoothY = me.smoothY,
- scale = me.calculateScale(attr.dataX.length, end),
- cx1, cy1, cx2, cy2, x, y, x0, y0, i, j, changes, params,
- lineConfig = {
- type: 'line',
- smooth: true,
- step: step
- };
- ctx.beginPath();
- ctx.moveTo(smoothX[start * 3] * xx + dx, smoothY[start * 3] * yy + dy);
- for (i = 0 , j = start * 3 + 1; i < list.length - 3; i += 3 , j += 3 * scale) {
- cx1 = smoothX[j] * xx + dx;
- cy1 = smoothY[j] * yy + dy;
- cx2 = smoothX[j + 1] * xx + dx;
- cy2 = smoothY[j + 1] * yy + dy;
- x = surface.roundPixel(list[i + 3]);
- y = list[i + 4];
- x0 = surface.roundPixel(list[i]);
- y0 = list[i + 1];
- if (renderer) {
- lineConfig.x0 = x0;
- lineConfig.y0 = y0;
- lineConfig.cx1 = cx1;
- lineConfig.cy1 = cy1;
- lineConfig.cx2 = cx2;
- lineConfig.cy2 = cy2;
- lineConfig.x = x;
- lineConfig.y = y;
- params = [
- me,
- lineConfig,
- me.rendererData,
- start + i / 3 + 1
- ];
- changes = Ext.callback(renderer, null, params, 0, me.getSeries());
- ctx.save();
- Ext.apply(ctx, changes);
- }
- if (attr.fillArea) {
- ctx.moveTo(x0, y0);
- ctx.bezierCurveTo(cx1, cy1, cx2, cy2, x, y);
- ctx.lineTo(x, xAxis);
- ctx.lineTo(x0, xAxis);
- ctx.lineTo(x0, y0);
- ctx.closePath();
- ctx.fill();
- ctx.beginPath();
- }
- // Draw the line on top of the filled area.
- ctx.moveTo(x0, y0);
- ctx.bezierCurveTo(cx1, cy1, cx2, cy2, x, y);
- ctx.stroke();
- ctx.moveTo(x0, y0);
- ctx.closePath();
- if (renderer) {
- ctx.restore();
- }
- ctx.beginPath();
- ctx.moveTo(x, y);
- }
- // Prevent the last visible segment from being stroked twice
- // (second time by the ctx.fillStroke inside Path sprite 'render' method)
- ctx.beginPath();
- },
- drawLabel: function(text, dataX, dataY, labelId, rect) {
- var me = this,
- attr = me.attr,
- label = me.getMarker('labels'),
- labelTpl = label.getTemplate(),
- labelCfg = me.labelCfg || (me.labelCfg = {}),
- surfaceMatrix = me.surfaceMatrix,
- labelX, labelY,
- labelOverflowPadding = attr.labelOverflowPadding,
- halfHeight, labelBBox, changes, params, hasPendingChanges;
- // The coordinates below (data point converted to surface coordinates)
- // are just for the renderer to give it a notion of where the label will be positioned.
- // The actual position of the label will be different
- // (unless the renderer returns x/y coordinates in the changes object)
- // and depend on several things including the size of the text,
- // which has to be measured after the renderer call,
- // since text can be modified by the renderer.
- labelCfg.x = surfaceMatrix.x(dataX, dataY);
- labelCfg.y = surfaceMatrix.y(dataX, dataY);
- if (attr.flipXY) {
- labelCfg.rotationRads = Math.PI * 0.5;
- } else {
- labelCfg.rotationRads = 0;
- }
- labelCfg.text = text;
- if (labelTpl.attr.renderer) {
- params = [
- text,
- label,
- labelCfg,
- me.rendererData,
- labelId
- ];
- changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
- if (typeof changes === 'string') {
- labelCfg.text = changes;
- } else if (typeof changes === 'object') {
- if ('text' in changes) {
- labelCfg.text = changes.text;
- }
- hasPendingChanges = true;
- }
- }
- labelBBox = me.getMarkerBBox('labels', labelId, true);
- if (!labelBBox) {
- me.putMarker('labels', labelCfg, labelId);
- labelBBox = me.getMarkerBBox('labels', labelId, true);
- }
- halfHeight = labelBBox.height / 2;
- labelX = dataX;
- switch (labelTpl.attr.display) {
- case 'under':
- labelY = dataY - halfHeight - labelOverflowPadding;
- break;
- case 'rotate':
- labelX += labelOverflowPadding;
- labelY = dataY - labelOverflowPadding;
- labelCfg.rotationRads = -Math.PI / 4;
- break;
- default:
- // 'over'
- labelY = dataY + halfHeight + labelOverflowPadding;
- }
- labelCfg.x = surfaceMatrix.x(labelX, labelY);
- labelCfg.y = surfaceMatrix.y(labelX, labelY);
- if (hasPendingChanges) {
- Ext.apply(labelCfg, changes);
- }
- me.putMarker('labels', labelCfg, labelId);
- },
- drawMarker: function(x, y, index) {
- var me = this,
- attr = me.attr,
- renderer = attr.renderer,
- surfaceMatrix = me.surfaceMatrix,
- markerCfg = {},
- changes, params;
- if (renderer && me.getMarker('markers')) {
- markerCfg.type = 'marker';
- markerCfg.x = x;
- markerCfg.y = y;
- params = [
- me,
- markerCfg,
- me.rendererData,
- index
- ];
- changes = Ext.callback(renderer, null, params, 0, me.getSeries());
- if (changes) {
- Ext.apply(markerCfg, changes);
- }
- }
- markerCfg.translationX = surfaceMatrix.x(x, y);
- markerCfg.translationY = surfaceMatrix.y(x, y);
- delete markerCfg.x;
- delete markerCfg.y;
- me.putMarker('markers', markerCfg, index, !renderer);
- },
- drawStroke: function(surface, ctx, start, end, list, xAxis) {
- var me = this,
- isSmooth = me.smoothX && me.smoothY;
- if (isSmooth) {
- me.drawSmoothStroke(surface, ctx, start, end, list, xAxis);
- } else {
- me.drawStraightStroke(surface, ctx, start, end, list, xAxis);
- }
- },
- renderAggregates: function(aggregates, start, end, surface, ctx, clip, rect) {
- var me = this,
- attr = me.attr,
- dataX = attr.dataX,
- dataY = attr.dataY,
- labels = attr.labels,
- xAxis = attr.xAxis,
- yCap = attr.yCap,
- isSmooth = attr.smooth && me.smoothX && me.smoothY,
- isDrawLabels = labels && me.getMarker('labels'),
- isDrawMarkers = me.getMarker('markers'),
- matrix = attr.matrix,
- pixel = surface.devicePixelRatio,
- xx = matrix.getXX(),
- yy = matrix.getYY(),
- dx = matrix.getDX(),
- dy = matrix.getDY(),
- list = me.list || (me.list = []),
- minXs = aggregates.minX,
- maxXs = aggregates.maxX,
- minYs = aggregates.minY,
- maxYs = aggregates.maxY,
- idx = aggregates.startIdx,
- isContinuousLine = true,
- isValidMinX, isValidMaxX, isValidMinY, isValidMaxY, xAxisOrigin, isVerticalX, x, y, i, index, minX, maxX, minY, maxY, lastPointX, lastPointY, firstPointX, firstPointY;
- me.rendererData = {
- store: me.getStore()
- };
- list.length = 0;
- // Say we have 7 y-items (attr.dataY): [20, 19, 17, 15, 11, 10, 14]
- // and 7 x-items (attr.dataX): [0, 1, 2, 3, 4, 5, 6].
- // Then aggregates.startIdx is an aggregated index,
- // where every other item is skipped on each aggregation level:
- // [0, 1, 2, 3, 4, 5, 6,
- // 0, 2, 4, 6,
- // 0, 4,
- // 0]
- // aggregates.minY
- // [20, 19, 17, 15, 11, 10, 14,
- // 19, 15, 10, 14,
- // 15, 10,
- // 10]
- // aggregates.maxY
- // [20, 19, 17, 15, 11, 10, 14,
- // 20, 17, 11, 14,
- // 20, 14,
- // 20]
- // aggregates.minX is
- // [0, 1, 2, 3, 4, 5, 6,
- // 1, 3, 5, 6, // TODO: why this order for min?
- // 3, 5, // TODO: why this inconsistency?
- // 5]
- // aggregates.maxX is
- // [0, 1, 2, 3, 4, 5, 6,
- // 0, 2, 4, 6,
- // 0, 6,
- // 0]
- // Create a list of the form [x0, y0, idx0, x1, y1, idx1, ...],
- // where each x,y pair is a coordinate representing original data point
- // at the idx position.
- for (i = start; i < end; i++) {
- minX = minXs[i];
- maxX = maxXs[i];
- minY = minYs[i];
- maxY = maxYs[i];
- isValidMinX = Ext.isNumber(minX);
- isValidMinY = Ext.isNumber(minY);
- isValidMaxX = Ext.isNumber(maxX);
- isValidMaxY = Ext.isNumber(maxY);
- if (minX < maxX) {
- list.push(isValidMinX ? (minX * xx + dx) : null, isValidMinY ? (minY * yy + dy) : null, idx[i]);
- list.push(isValidMaxX ? (maxX * xx + dx) : null, isValidMaxY ? (maxY * yy + dy) : null, idx[i]);
- } else if (minX > maxX) {
- list.push(isValidMaxX ? (maxX * xx + dx) : null, isValidMaxY ? (maxY * yy + dy) : null, idx[i]);
- list.push(isValidMinX ? (minX * xx + dx) : null, isValidMinY ? (minY * yy + dy) : null, idx[i]);
- } else {
- list.push(isValidMaxX ? (maxX * xx + dx) : null, isValidMaxY ? (maxY * yy + dy) : null, idx[i]);
- }
- }
- if (list.length) {
- for (i = 0; i < list.length; i += 3) {
- x = list[i];
- y = list[i + 1];
- if (Ext.isNumber(x) && Ext.isNumber(y)) {
- if (y > yCap) {
- y = yCap;
- } else if (y < -yCap) {
- y = -yCap;
- }
- list[i + 1] = y;
- } else {
- isContinuousLine = false;
-
- continue;
- }
- index = list[i + 2];
- if (isDrawMarkers) {
- me.drawMarker(x, y, index);
- }
- if (isDrawLabels && labels[index]) {
- me.drawLabel(labels[index], x, y, index, rect);
- }
- }
- me.isContinuousLine = isContinuousLine;
- if (isSmooth && !isContinuousLine) {
- Ext.raise("Line smoothing in only supported for gapless data, " + "where all data points are finite numbers.");
- }
- if (xAxis) {
- isVerticalX = xAxis.getAlignment() === 'vertical';
- if (Ext.isNumber(xAxis.floatingAtCoord)) {
- xAxisOrigin = (isVerticalX ? rect[2] : rect[3]) - xAxis.floatingAtCoord;
- } else {
- xAxisOrigin = isVerticalX ? rect[0] : rect[1];
- }
- } else {
- xAxisOrigin = attr.flipXY ? rect[0] : rect[1];
- }
- if (attr.preciseStroke) {
- if (attr.fillArea) {
- ctx.fill();
- }
- if (attr.transformFillStroke) {
- attr.inverseMatrix.toContext(ctx);
- }
- me.drawStroke(surface, ctx, start, end, list, xAxisOrigin);
- if (attr.transformFillStroke) {
- attr.matrix.toContext(ctx);
- }
- ctx.stroke();
- } else {
- me.drawStroke(surface, ctx, start, end, list, xAxisOrigin);
- if (isContinuousLine && isSmooth && attr.fillArea && !attr.renderer) {
- lastPointX = dataX[dataX.length - 1] * xx + dx + pixel;
- lastPointY = dataY[dataY.length - 1] * yy + dy;
- firstPointX = dataX[0] * xx + dx - pixel;
- firstPointY = dataY[0] * yy + dy;
- // Fill the area from the series to the xAxis in case there
- // are no gaps and no renderer is used, in which case the
- // area would be filled per uninterrupted segment or per
- // step, instead of being filled a single pass.
- ctx.lineTo(lastPointX, lastPointY);
- ctx.lineTo(lastPointX, xAxisOrigin - attr.lineWidth);
- ctx.lineTo(firstPointX, xAxisOrigin - attr.lineWidth);
- ctx.lineTo(firstPointX, firstPointY);
- }
- if (attr.transformFillStroke) {
- attr.matrix.toContext(ctx);
- }
- // Prevent the reverse transform to fix floating point error.
- if (attr.fillArea) {
- ctx.fillStroke(attr, true);
- } else {
- ctx.stroke(true);
- }
- }
- }
- }
- });
- /**
- * @class Ext.chart.series.Line
- * @extends Ext.chart.series.Cartesian
- *
- * Creates a Line Chart. A Line Chart is a useful visualization technique to display quantitative
- * information for different categories or other real values (as opposed to the bar chart),
- * that can show some progression (or regression) in the dataset.
- * As with all other series, the Line Series must be appended in the *series* Chart array
- * configuration. See the Chart documentation for more information. A typical configuration object
- * for the line series could be:
- *
- * @example
- * Ext.create({
- * xtype: 'cartesian',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * insetPadding: 40,
- * store: {
- * fields: ['name', 'data1', 'data2'],
- * data: [{
- * 'name': 'metric one',
- * 'data1': 10,
- * 'data2': 14
- * }, {
- * 'name': 'metric two',
- * 'data1': 7,
- * 'data2': 16
- * }, {
- * 'name': 'metric three',
- * 'data1': 5,
- * 'data2': 14
- * }, {
- * 'name': 'metric four',
- * 'data1': 2,
- * 'data2': 6
- * }, {
- * 'name': 'metric five',
- * 'data1': 27,
- * 'data2': 36
- * }]
- * },
- * axes: [{
- * type: 'numeric',
- * position: 'left',
- * fields: ['data1'],
- * title: {
- * text: 'Sample Values',
- * fontSize: 15
- * },
- * grid: true,
- * minimum: 0
- * }, {
- * type: 'category',
- * position: 'bottom',
- * fields: ['name'],
- * title: {
- * text: 'Sample Values',
- * fontSize: 15
- * }
- * }],
- * series: [{
- * type: 'line',
- * style: {
- * stroke: '#30BDA7',
- * lineWidth: 2
- * },
- * xField: 'name',
- * yField: 'data1',
- * marker: {
- * type: 'path',
- * path: ['M', - 4, 0, 0, 4, 4, 0, 0, - 4, 'Z'],
- * stroke: '#30BDA7',
- * lineWidth: 2,
- * fill: 'white'
- * }
- * }, {
- * type: 'line',
- * fill: true,
- * style: {
- * fill: '#96D4C6',
- * fillOpacity: .6,
- * stroke: '#0A3F50',
- * strokeOpacity: .6,
- * },
- * xField: 'name',
- * yField: 'data2',
- * marker: {
- * type: 'circle',
- * radius: 4,
- * lineWidth: 2,
- * fill: 'white'
- * }
- * }]
- * });
- *
- * In this configuration we're adding two series (or lines), one bound to the `data1`
- * property of the store and the other to `data3`. The type for both configurations is
- * `line`. The `xField` for both series is the same, the `name` property of the store.
- * Both line series share the same axis, the left axis. You can set particular marker
- * configuration by adding properties onto the marker object. Both series have
- * an object as highlight so that markers animate smoothly to the properties in highlight
- * when hovered. The second series has `fill = true` which means that the line will also
- * have an area below it of the same color.
- *
- * **Note:** In the series definition remember to explicitly set the axis to bind the
- * values of the line series to. This can be done by using the `axis` configuration property.
- */
- Ext.define('Ext.chart.series.Line', {
- extend: 'Ext.chart.series.Cartesian',
- alias: 'series.line',
- type: 'line',
- seriesType: 'lineSeries',
- isLine: true,
- requires: [
- 'Ext.chart.series.sprite.Line'
- ],
- config: {
- /**
- * @cfg {Number} selectionTolerance
- * The offset distance from the cursor position to the line series to trigger events
- * (then used for highlighting series, etc).
- */
- selectionTolerance: 20,
- /**
- * @cfg {Object} curve
- * The type of curve that connects the data points.
- * Please see {@link Ext.chart.series.sprite.Line#curve line sprite documentation}
- * for the full description.
- */
- curve: {
- type: 'linear'
- },
- /**
- * @cfg {Object} style
- * An object containing styles for the visualization lines. These styles will override
- * the theme styles.
- * Some options contained within the style object will are described next.
- */
- /**
- * @cfg {Boolean} smooth
- * `true` if the series' line should be smoothed.
- * Line smoothing only works with gapless data.
- * @deprecated 6.5.0 Use the {@link #curve} config instead.
- */
- smooth: null,
- /**
- * @cfg {Boolean} step
- * If set to `true`, the line uses steps instead of straight lines to connect the dots.
- * It is ignored if `smooth` is true.
- * @deprecated 6.5.0 Use the {@link #curve} config instead.
- */
- step: null,
- /**
- * @cfg {"gap"/"connect"/"origin"} [nullStyle="gap"]
- * Possible values:
- * 'gap' - null points are rendered as gaps.
- * 'connect' - non-null points are connected across null points, so that
- * there is no gap, unless null points are at the beginning/end of the line.
- * Only the visible data points are connected - if a visible data point
- * is followed by a series of null points that go off screen and eventually
- * terminate with a non-null point, the connection won't be made.
- * 'origin' - null data points are rendered at the origin,
- * which is the y-coordinate of a point where the x and y axes meet.
- * This requires that at least the x-coordinate of a point is a valid value.
- */
- nullStyle: 'gap',
- /**
- * @cfg {Boolean} fill
- * If set to `true`, the area underneath the line is filled with the color defined
- * as follows, listed by priority:
- * - The color that is configured for this series ({@link Ext.chart.series.Series#colors}).
- * - The color that is configured for this chart ({@link Ext.chart.AbstractChart#colors}).
- * - The fill color that is set in the {@link #style} config.
- * - The stroke color that is set in the {@link #style} config, or the same color
- * as the line.
- *
- * Note: Do not confuse `series.config.fill` (which is a boolean) with `series.style.fill'
- * (which is an alias for the `fillStyle` property and contains a color). For compatibility
- * with previous versions of the API, if `config.fill` is undefined but a `style.fill' color
- * is provided, `config.fill` is considered true. So the default value below must be
- * undefined, not false.
- */
- fill: undefined,
- aggregator: {
- strategy: 'double'
- }
- },
- themeMarkerCount: function() {
- return 1;
- },
- /**
- * @private
- * Override {@link Ext.chart.series.Series#getDefaultSpriteConfig}
- */
- getDefaultSpriteConfig: function() {
- var me = this,
- parentConfig = me.callParent(arguments),
- style = Ext.apply({}, me.getStyle()),
- styleWithTheme,
- fillArea = false;
- if (me.config.fill !== undefined) {
- // If config.fill is present but there is no fillStyle, then use the
- // strokeStyle to fill (and paint the area the same color as the line).
- if (me.config.fill) {
- fillArea = true;
- if (style.fillStyle === undefined) {
- if (style.strokeStyle === undefined) {
- styleWithTheme = me.getStyleWithTheme();
- style.fillStyle = styleWithTheme.fillStyle;
- style.strokeStyle = styleWithTheme.strokeStyle;
- } else {
- style.fillStyle = style.strokeStyle;
- }
- }
- }
- } else {
- // For compatibility with previous versions of the API, if config.fill
- // is undefined but style.fillStyle is provided, we fill the area.
- if (style.fillStyle) {
- fillArea = true;
- }
- }
- // If we don't fill, then delete the fillStyle because that's what is used by
- // the Line sprite to fill below the line.
- if (!fillArea) {
- delete style.fillStyle;
- }
- style = Ext.apply(parentConfig || {}, style);
- return Ext.apply(style, {
- fillArea: fillArea,
- selectionTolerance: me.config.selectionTolerance
- });
- },
- updateFill: function(fill) {
- this.withSprite(function(sprite) {
- return sprite.setAttributes({
- fillArea: fill
- });
- });
- },
- updateCurve: function(curve) {
- this.withSprite(function(sprite) {
- return sprite.setAttributes({
- curve: curve
- });
- });
- },
- getCurve: function() {
- return this.withSprite(function(sprite) {
- return sprite.attr.curve;
- });
- },
- updateNullStyle: function(nullStyle) {
- this.withSprite(function(sprite) {
- return sprite.setAttributes({
- nullStyle: nullStyle
- });
- });
- },
- updateSmooth: function(smooth) {
- this.setCurve({
- type: smooth ? 'natural' : 'linear'
- });
- },
- updateStep: function(step) {
- this.setCurve({
- type: step ? 'step-after' : 'linear'
- });
- }
- });
- /**
- * @class Ext.chart.series.sprite.PieSlice
- *
- * Pie slice sprite.
- */
- Ext.define('Ext.chart.series.sprite.PieSlice', {
- extend: 'Ext.draw.sprite.Sector',
- mixins: {
- markerHolder: 'Ext.chart.MarkerHolder'
- },
- alias: 'sprite.pieslice',
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Boolean} [doCallout=true]
- * 'true' if the pie series uses label callouts.
- */
- doCallout: 'bool',
- /**
- * @cfg {String} [label='']
- * Label associated with the Pie sprite.
- */
- label: 'string',
- // @deprecated Use series.label.orientation config instead.
- // @since 5.0.1
- rotateLabels: 'bool',
- /**
- * @cfg {Number} [labelOverflowPadding=10]
- * Padding around labels to determine overlap.
- * Any negative number allows the labels to overlap.
- */
- labelOverflowPadding: 'number',
- renderer: 'default'
- },
- defaults: {
- doCallout: true,
- rotateLabels: true,
- label: '',
- labelOverflowPadding: 10,
- renderer: null
- }
- }
- },
- config: {
- /**
- * @private
- * @cfg {Object} rendererData The object that is passed to the renderer.
- *
- * For instance when the PieSlice sprite is used in a Gauge chart, the object
- * contains the 'store' and 'angleField' properties, and the 'value' as well
- * for that one PieSlice that is used to draw the needle of the Gauge.
- */
- rendererData: null,
- rendererIndex: 0,
- series: null
- },
- setGradientBBox: function(ctx, rect) {
- var me = this,
- attr = me.attr,
- hasGradients = (attr.fillStyle && attr.fillStyle.isGradient) || (attr.strokeStyle && attr.strokeStyle.isGradient);
- if (hasGradients && !attr.constrainGradients) {
- // eslint-disable-next-line vars-on-top, one-var
- var midAngle = me.getMidAngle(),
- margin = attr.margin,
- cx = attr.centerX,
- cy = attr.centerY,
- r = attr.endRho,
- matrix = attr.matrix,
- scaleX = matrix.getScaleX(),
- scaleY = matrix.getScaleY(),
- w = scaleX * r,
- h = scaleY * r,
- bbox = {
- width: w + w,
- height: h + h
- };
- if (margin) {
- cx += margin * Math.cos(midAngle);
- cy += margin * Math.sin(midAngle);
- }
- bbox.x = matrix.x(cx, cy) - w;
- bbox.y = matrix.y(cx, cy) - h;
- ctx.setGradientBBox(bbox);
- } else {
- me.callParent([
- ctx,
- rect
- ]);
- }
- },
- render: function(surface, ctx, rect) {
- var me = this,
- attr = me.attr,
- itemCfg = {},
- changes;
- if (attr.renderer) {
- itemCfg = {
- type: 'sector',
- centerX: attr.centerX,
- centerY: attr.centerY,
- margin: attr.margin,
- startAngle: Math.min(attr.startAngle, attr.endAngle),
- endAngle: Math.max(attr.startAngle, attr.endAngle),
- startRho: Math.min(attr.startRho, attr.endRho),
- endRho: Math.max(attr.startRho, attr.endRho)
- };
- changes = Ext.callback(attr.renderer, null, [
- me,
- itemCfg,
- me.getRendererData(),
- me.getRendererIndex()
- ], 0, me.getSeries());
- me.setAttributes(changes);
- me.useAttributes(ctx, rect);
- }
- // Draw the sector
- me.callParent([
- surface,
- ctx,
- rect
- ]);
- // Draw the labels
- if (attr.label && me.getMarker('labels')) {
- me.placeLabel();
- }
- },
- placeLabel: function() {
- var me = this,
- attr = me.attr,
- attributeId = attr.attributeId,
- startAngle = Math.min(attr.startAngle, attr.endAngle),
- endAngle = Math.max(attr.startAngle, attr.endAngle),
- midAngle = (startAngle + endAngle) * 0.5,
- margin = attr.margin,
- centerX = attr.centerX,
- centerY = attr.centerY,
- sinMidAngle = Math.sin(midAngle),
- cosMidAngle = Math.cos(midAngle),
- startRho = Math.min(attr.startRho, attr.endRho) + margin,
- endRho = Math.max(attr.startRho, attr.endRho) + margin,
- midRho = (startRho + endRho) * 0.5,
- surfaceMatrix = me.surfaceMatrix,
- labelCfg = me.labelCfg || (me.labelCfg = {}),
- label = me.getMarker('labels'),
- labelTpl = label.getTemplate(),
- hideLessThan = labelTpl.getHideLessThan(),
- calloutLine = labelTpl.getCalloutLine(),
- labelBox, x, y, changes, params, calloutLineLength;
- if (calloutLine) {
- calloutLineLength = calloutLine.length || 40;
- } else {
- calloutLineLength = 0;
- }
- surfaceMatrix.appendMatrix(attr.matrix);
- labelCfg.text = attr.label;
- x = centerX + cosMidAngle * midRho;
- y = centerY + sinMidAngle * midRho;
- labelCfg.x = surfaceMatrix.x(x, y);
- labelCfg.y = surfaceMatrix.y(x, y);
- x = centerX + cosMidAngle * endRho;
- y = centerY + sinMidAngle * endRho;
- labelCfg.calloutStartX = surfaceMatrix.x(x, y);
- labelCfg.calloutStartY = surfaceMatrix.y(x, y);
- x = centerX + cosMidAngle * (endRho + calloutLineLength);
- y = centerY + sinMidAngle * (endRho + calloutLineLength);
- labelCfg.calloutPlaceX = surfaceMatrix.x(x, y);
- labelCfg.calloutPlaceY = surfaceMatrix.y(x, y);
- if (!attr.rotateLabels) {
- labelCfg.rotationRads = 0;
- //<debug>
- Ext.log.warn("'series.style.rotateLabels' config is deprecated. " + "Use 'series.label.orientation' config instead.");
- } else //</debug>
- {
- switch (labelTpl.attr.orientation) {
- case 'horizontal':
- labelCfg.rotationRads = midAngle + Math.atan2(surfaceMatrix.y(1, 0) - surfaceMatrix.y(0, 0), surfaceMatrix.x(1, 0) - surfaceMatrix.x(0, 0)) + Math.PI / 2;
- break;
- case 'vertical':
- labelCfg.rotationRads = midAngle + Math.atan2(surfaceMatrix.y(1, 0) - surfaceMatrix.y(0, 0), surfaceMatrix.x(1, 0) - surfaceMatrix.x(0, 0));
- break;
- }
- }
- labelCfg.calloutColor = (calloutLine && calloutLine.color) || me.attr.fillStyle;
- if (calloutLine) {
- if (calloutLine.width) {
- labelCfg.calloutWidth = calloutLine.width;
- }
- } else {
- labelCfg.calloutColor = 'none';
- }
- labelCfg.globalAlpha = attr.globalAlpha * attr.fillOpacity;
- // If a slice is empty, don't display the label.
- // This behavior can be overridden by a renderer.
- if (labelTpl.display !== 'none') {
- // eslint-disable-next-line eqeqeq
- labelCfg.hidden = (attr.startAngle == attr.endAngle);
- }
- if (labelTpl.attr.renderer) {
- // Note: the labels are 'put' by the Ext.chart.series.Pie.updateLabelData, so we can
- // be sure the label sprite instances will exist and can be accessed from the label
- // renderer on first render. For example, with 'bar' series this isn't the case,
- // so we make a check and create a label instance if necessary.
- params = [
- me.attr.label,
- label,
- labelCfg,
- me.getRendererData(),
- me.getRendererIndex()
- ];
- changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
- if (typeof changes === 'string') {
- labelCfg.text = changes;
- } else {
- Ext.apply(labelCfg, changes);
- }
- }
- me.putMarker('labels', labelCfg, attributeId);
- labelBox = me.getMarkerBBox('labels', attributeId, true);
- if (labelBox) {
- if (attr.doCallout && ((endAngle - startAngle) * endRho > hideLessThan || attr.highlighted)) {
- if (labelTpl.attr.display === 'outside') {
- me.putMarker('labels', {
- callout: 1
- }, attributeId);
- } else if (labelTpl.attr.display === 'inside') {
- me.putMarker('labels', {
- callout: 0
- }, attributeId);
- } else {
- me.putMarker('labels', {
- callout: 1 - me.sliceContainsLabel(attr, labelBox)
- }, attributeId);
- }
- } else {
- me.putMarker('labels', {
- globalAlpha: me.sliceContainsLabel(attr, labelBox)
- }, attributeId);
- }
- }
- },
- sliceContainsLabel: function(attr, bbox) {
- var padding = attr.labelOverflowPadding,
- middle = (attr.endRho + attr.startRho) / 2,
- outer = middle + (bbox.width + padding) / 2,
- inner = middle - (bbox.width + padding) / 2,
- sliceAngle, l1, l2, l3;
- if (padding < 0) {
- return 1;
- }
- if (bbox.width + padding * 2 > (attr.endRho - attr.startRho)) {
- return 0;
- }
- l1 = Math.sqrt(attr.endRho * attr.endRho - outer * outer);
- l2 = Math.sqrt(attr.endRho * attr.endRho - inner * inner);
- sliceAngle = Math.abs(attr.endAngle - attr.startAngle);
- l3 = (sliceAngle > Math.PI / 2 ? inner : Math.abs(Math.tan(sliceAngle / 2)) * inner);
- if (bbox.height + padding * 2 > Math.min(l1, l2, l3) * 2) {
- return 0;
- }
- return 1;
- }
- });
- /**
- * @class Ext.chart.series.Pie
- * @extends Ext.chart.series.Polar
- *
- * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display
- * quantitative information for different categories that also have a meaning as a whole.
- * As with all other series, the Pie Series must be appended in the *series* Chart array
- * configuration. See the Chart documentation for more information. A typical configuration
- * object for the pie series could be:
- *
- * @example
- * Ext.create({
- * xtype: 'polar',
- * renderTo: document.body,
- * width: 400,
- * height: 400,
- * theme: 'green',
- * interactions: ['rotate', 'itemhighlight'],
- * store: {
- * fields: ['name', 'data1'],
- * data: [{
- * name: 'metric one',
- * data1: 14
- * }, {
- * name: 'metric two',
- * data1: 16
- * }, {
- * name: 'metric three',
- * data1: 14
- * }, {
- * name: 'metric four',
- * data1: 6
- * }, {
- * name: 'metric five',
- * data1: 36
- * }]
- * },
- * series: {
- * type: 'pie',
- * highlight: true,
- * angleField: 'data1',
- * label: {
- * field: 'name',
- * display: 'rotate'
- * },
- * donut: 30
- * }
- * });
- *
- * In this configuration we set `pie` as the type for the series, then set the `highlight` config
- * to `true` (we can also specify an object with specific style properties for highlighting options)
- * which is triggered when hovering or tapping elements.
- * We set `data1` as the value of the `angleField` to determine the angular span for each pie slice.
- * We also set a label configuration object where we set the name of the store field
- * to be rendered as text for the label. The labels will also be displayed rotated.
- * And finally, we specify the donut hole radius for the pie series in percentages of the series
- * radius.
- *
- */
- Ext.define('Ext.chart.series.Pie', {
- extend: 'Ext.chart.series.Polar',
- requires: [
- 'Ext.chart.series.sprite.PieSlice'
- ],
- type: 'pie',
- alias: 'series.pie',
- seriesType: 'pieslice',
- isPie: true,
- config: {
- /**
- * @cfg {String} radiusField
- * The store record field name to be used for the pie slice lengths.
- * The values bound to this field name must be positive real numbers.
- */
- /**
- * @cfg {Number} donut Specifies the radius of the donut hole, as a percentage
- * of the chart's radius.
- * Defaults to 0 (no donut hole).
- */
- donut: 0,
- /**
- * @cfg {Number} rotation The starting angle of the pie slices.
- */
- rotation: 0,
- /**
- * @cfg {Boolean} clockwise
- * Whether the pie slices are displayed clockwise. Default's true.
- */
- clockwise: true,
- /**
- * @cfg {Number} [totalAngle=2*PI] The total angle of the pie series.
- */
- totalAngle: 2 * Math.PI,
- /**
- * @cfg {Array} hidden Determines which pie slices are hidden.
- */
- hidden: [],
- /**
- * @cfg {Number} [radiusFactor=100] Allows adjustment of the radius
- * by a specific percentage.
- */
- radiusFactor: 100,
- /**
- * @cfg {Ext.chart.series.sprite.PieSlice/Object} highlightCfg
- * Default highlight config for the pie series.
- * Slides highlighted pie sector outward by default.
- *
- * highlightCfg accepts as its value a config object (or array of configs) for a
- * {@link Ext.chart.series.sprite.PieSlice pie sprite}.
- *
- *
- * Example config:
- *
- * Ext.create('Ext.chart.PolarChart', {
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * innerPadding: 5,
- * store: {
- * fields: ['name', 'data1'],
- * data: [{
- * name: 'metric one',
- * data1: 10
- * }, {
- * name: 'metric two',
- * data1: 7
- * }, {
- * name: 'metric three',
- * data1: 5
- * }]
- * },
- * series: {
- * type: 'pie',
- * label: {
- * field: 'name',
- * display: 'rotate'
- * },
- * xField: 'data1',
- * donut: 30,
- * highlightCfg: {
- * margin: 10,
- * fillOpacity: .7
- * }
- * }
- * });
- */
- highlightCfg: {
- margin: 20
- },
- style: {}
- },
- directions: [
- 'X'
- ],
- applyLabel: function(newLabel, oldLabel) {
- if (Ext.isObject(newLabel) && !Ext.isString(newLabel.orientation)) {
- // Override default label orientation from '' to 'vertical'.
- Ext.apply(newLabel = Ext.Object.chain(newLabel), {
- orientation: 'vertical'
- });
- }
- return this.callParent([
- newLabel,
- oldLabel
- ]);
- },
- updateLabelData: function() {
- var me = this,
- store = me.getStore(),
- items = store.getData().items,
- sprites = me.getSprites(),
- label = me.getLabel(),
- labelField = label && label.getTemplate().getField(),
- hidden = me.getHidden(),
- i, ln, labels, sprite;
- if (sprites.length && labelField) {
- labels = [];
- for (i = 0 , ln = items.length; i < ln; i++) {
- labels.push(items[i].get(labelField));
- }
- for (i = 0 , ln = sprites.length; i < ln; i++) {
- sprite = sprites[i];
- sprite.setAttributes({
- label: labels[i]
- });
- sprite.putMarker('labels', {
- hidden: hidden[i]
- }, sprite.attr.attributeId);
- }
- }
- },
- coordinateX: function() {
- var me = this,
- store = me.getStore(),
- records = store.getData().items,
- recordCount = records.length,
- xField = me.getXField(),
- yField = me.getYField(),
- x,
- sumX = 0,
- unit, y,
- maxY = 0,
- hidden = me.getHidden(),
- summation = [],
- i,
- lastAngle = 0,
- totalAngle = me.getTotalAngle(),
- clockwise = me.getClockwise() ? 1 : -1,
- sprites = me.getSprites(),
- sprite, labels;
- if (!sprites) {
- return;
- }
- for (i = 0; i < recordCount; i++) {
- x = Math.abs(Number(records[i].get(xField))) || 0;
- y = yField && Math.abs(Number(records[i].get(yField))) || 0;
- if (!hidden[i]) {
- sumX += x;
- if (y > maxY) {
- maxY = y;
- }
- }
- summation[i] = sumX;
- if (i >= hidden.length) {
- hidden[i] = false;
- }
- }
- hidden.length = recordCount;
- me.maxY = maxY;
- if (sumX !== 0) {
- unit = totalAngle / sumX;
- }
- for (i = 0; i < recordCount; i++) {
- sprites[i].setAttributes({
- startAngle: lastAngle,
- endAngle: lastAngle = (unit ? clockwise * summation[i] * unit : 0),
- globalAlpha: 1
- });
- }
- if (recordCount < sprites.length) {
- for (i = recordCount; i < sprites.length; i++) {
- sprite = sprites[i];
- labels = sprite.getMarker('labels');
- if (labels) {
- // Don't want the 'labels' Markers and its 'template' sprite to be destroyed
- // with the PieSlice MarkerHolder, as it is also used by other pie slices.
- // So we release 'labels' before destroying the PieSlice.
- // But first, we have to clear the instances of the 'labels'
- // Markers created by the PieSlice MarkerHolder.
- labels.clear(sprite.getId());
- sprite.releaseMarker('labels');
- }
- sprite.destroy();
- }
- sprites.length = recordCount;
- }
- for (i = recordCount; i < sprites.length; i++) {
- sprites[i].setAttributes({
- startAngle: totalAngle,
- endAngle: totalAngle,
- globalAlpha: 0
- });
- }
- },
- updateCenter: function(center) {
- this.setStyle({
- translationX: center[0] + this.getOffsetX(),
- translationY: center[1] + this.getOffsetY()
- });
- this.doUpdateStyles();
- },
- updateRadius: function(radius) {
- this.setStyle({
- startRho: radius * this.getDonut() * 0.01,
- endRho: radius * this.getRadiusFactor() * 0.01
- });
- this.doUpdateStyles();
- },
- getStyleByIndex: function(i) {
- var me = this,
- store = me.getStore(),
- item = store.getAt(i),
- yField = me.getYField(),
- radius = me.getRadius(),
- style = {},
- startRho, endRho, y;
- if (item) {
- y = yField && Math.abs(Number(item.get(yField))) || 0;
- startRho = radius * me.getDonut() * 0.01;
- endRho = radius * me.getRadiusFactor() * 0.01;
- style = me.callParent([
- i
- ]);
- style.startRho = startRho;
- style.endRho = me.maxY ? (startRho + (endRho - startRho) * y / me.maxY) : endRho;
- }
- return style;
- },
- updateDonut: function(donut) {
- var radius = this.getRadius();
- this.setStyle({
- startRho: radius * donut * 0.01,
- endRho: radius * this.getRadiusFactor() * 0.01
- });
- this.doUpdateStyles();
- },
- // Subtract 90 degrees from rotation, so that `rotation` config's default
- // value of 0 makes first pie sector start at noon, rather than 3 o'clock.
- rotationOffset: -Math.PI / 2,
- updateRotation: function(rotation) {
- this.setStyle({
- rotationRads: rotation + this.rotationOffset
- });
- this.doUpdateStyles();
- },
- updateTotalAngle: function(totalAngle) {
- this.processData();
- },
- getSprites: function() {
- var me = this,
- chart = me.getChart(),
- store = me.getStore();
- if (!chart || !store) {
- return Ext.emptyArray;
- }
- me.getColors();
- me.getSubStyle();
- // eslint-disable-next-line vars-on-top, one-var
- var items = store.getData().items,
- length = items.length,
- animation = me.getAnimation() || chart && chart.getAnimation(),
- sprites = me.sprites,
- sprite,
- spriteCreated = false,
- spriteIndex = 0,
- label = me.getLabel(),
- labelTpl = label && label.getTemplate(),
- i, rendererData;
- rendererData = {
- store: store,
- field: me.getXField(),
- // for backward compatibility only (deprecated in 5.5)
- angleField: me.getXField(),
- radiusField: me.getYField(),
- series: me
- };
- for (i = 0; i < length; i++) {
- sprite = sprites[i];
- if (!sprite) {
- sprite = me.createSprite();
- if (me.getHighlight()) {
- sprite.config.highlight = me.getHighlight();
- sprite.addModifier('highlight', true);
- }
- if (labelTpl && labelTpl.getField()) {
- labelTpl.setAttributes({
- labelOverflowPadding: me.getLabelOverflowPadding()
- });
- labelTpl.getAnimation().setCustomDurations({
- 'callout': 200
- });
- }
- sprite.setAttributes(me.getStyleByIndex(i));
- sprite.setRendererData(rendererData);
- spriteCreated = true;
- }
- sprite.setRendererIndex(spriteIndex++);
- sprite.setAnimation(animation);
- }
- if (spriteCreated) {
- me.doUpdateStyles();
- }
- return me.sprites;
- },
- betweenAngle: function(x, a, b) {
- var pp = Math.PI * 2,
- offset = this.rotationOffset;
- if (a === b) {
- return false;
- }
- if (!this.getClockwise()) {
- x *= -1;
- a *= -1;
- b *= -1;
- a -= offset;
- b -= offset;
- } else {
- a += offset;
- b += offset;
- }
- x -= a;
- b -= a;
- // Normalize, so that both x and b are in the [0,360) interval.
- x %= pp;
- b %= pp;
- x += pp;
- b += pp;
- x %= pp;
- b %= pp;
- // Because 360 * n angles will be normalized to 0,
- // we need to treat b ~= 0 as a special case.
- return x < b || Ext.Number.isEqual(b, 0, 1.0E-8);
- },
- getItemByIndex: function(index, category) {
- category = category || 'sprites';
- return this.callParent([
- index,
- category
- ]);
- },
- /**
- * Returns the pie slice for a given angle
- * @param {Number} angle The angle to search for the slice
- * @return {Object} An object containing the reocord, sprite, scope etc.
- */
- getItemForAngle: function(angle) {
- var me = this,
- sprites = me.getSprites(),
- attr, store, items, hidden, i, ln;
- angle %= Math.PI * 2;
- while (angle < 0) {
- angle += Math.PI * 2;
- }
- if (sprites) {
- store = me.getStore();
- items = store.getData().items;
- hidden = me.getHidden();
- for (i = 0 , ln = store.getCount(); i < ln; i++) {
- if (!hidden[i]) {
- // Fortunately, item's id equals its index in the instances list.
- attr = sprites[i].attr;
- if (attr.startAngle <= angle && attr.endAngle >= angle) {
- return {
- series: me,
- sprite: sprites[i],
- index: i,
- record: items[i],
- field: me.getXField()
- };
- }
- }
- }
- }
- return null;
- },
- getItemForPoint: function(x, y) {
- var me = this,
- sprites = me.getSprites(),
- center = me.getCenter(),
- offsetX = me.getOffsetX(),
- offsetY = me.getOffsetY(),
- // Distance from the center of the series to the cursor.
- dx = x - center[0] + offsetX,
- dy = y - center[1] + offsetY,
- store = me.getStore(),
- donut = me.getDonut(),
- records = store.getData().items,
- direction = Math.atan2(dy, dx) - me.getRotation(),
- radius = Math.sqrt(dx * dx + dy * dy),
- startRadius = me.getRadius() * donut * 0.01,
- hidden = me.getHidden(),
- result = null,
- i, ln, attr, sprite;
- for (i = 0 , ln = records.length; i < ln; i++) {
- if (hidden[i]) {
-
- continue;
- }
- sprite = sprites[i];
- if (!sprite) {
- break;
- }
- attr = sprite.attr;
- if (radius >= startRadius + attr.margin && radius <= attr.endRho + attr.margin && me.betweenAngle(direction, attr.startAngle, attr.endAngle)) {
- result = {
- series: me,
- sprite: sprites[i],
- index: i,
- record: records[i],
- field: me.getXField()
- };
- break;
- }
- }
- return result;
- },
- provideLegendInfo: function(target) {
- var me = this,
- store = me.getStore(),
- items, labelField, xField, hidden, i, style, fill;
- if (store) {
- items = store.getData().items;
- labelField = me.getLabel().getTemplate().getField();
- xField = me.getXField();
- hidden = me.getHidden();
- for (i = 0; i < items.length; i++) {
- style = me.getStyleByIndex(i);
- fill = style.fillStyle;
- if (Ext.isObject(fill)) {
- fill = fill.stops && fill.stops[0].color;
- }
- target.push({
- name: labelField ? String(items[i].get(labelField)) : xField + ' ' + i,
- mark: fill || style.strokeStyle || 'black',
- disabled: hidden[i],
- series: me.getId(),
- index: i
- });
- }
- }
- }
- });
- /**
- * @class Ext.chart.series.sprite.Pie3DPart
- * @extends Ext.draw.sprite.Path
- *
- * Pie3D series sprite.
- */
- Ext.define('Ext.chart.series.sprite.Pie3DPart', {
- extend: 'Ext.draw.sprite.Path',
- mixins: {
- markerHolder: 'Ext.chart.MarkerHolder'
- },
- alias: 'sprite.pie3dPart',
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Number} [centerX=0]
- * The central point of the series on the x-axis.
- */
- centerX: 'number',
- /**
- * @cfg {Number} [centerY=0]
- * The central point of the series on the x-axis.
- */
- centerY: 'number',
- /**
- * @cfg {Number} [startAngle=0]
- * The starting angle of the polar series.
- */
- startAngle: 'number',
- /**
- * @cfg {Number} [endAngle=Math.PI]
- * The ending angle of the polar series.
- */
- endAngle: 'number',
- /**
- * @cfg {Number} [startRho=0]
- * The starting radius of the polar series.
- */
- startRho: 'number',
- /**
- * @cfg {Number} [endRho=150]
- * The ending radius of the polar series.
- */
- endRho: 'number',
- /**
- * @cfg {Number} [margin=0]
- * Margin from the center of the pie. Used for donut.
- */
- margin: 'number',
- /**
- * @cfg {Number} [thickness=0]
- * The thickness of the 3D pie part.
- */
- thickness: 'number',
- /**
- * @cfg {Number} [bevelWidth=5]
- * The size of the 3D pie bevel.
- */
- bevelWidth: 'number',
- /**
- * @cfg {Number} [distortion=0]
- * The distortion of the 3D pie part.
- */
- distortion: 'number',
- /**
- * @cfg {Object} [baseColor='white']
- * The color of the 3D pie part before adding the 3D effect.
- */
- baseColor: 'color',
- /**
- * @cfg {Number} [colorSpread=0.7]
- * An attribute used to control how flat the gradient of the sprite looks.
- * A value of 0 essentially means no gradient (flat color).
- */
- colorSpread: 'number',
- /**
- * @cfg {Number} [baseRotation=0]
- * The starting rotation of the polar series.
- */
- baseRotation: 'number',
- /**
- * @cfg {String} [part='top']
- * The part of the 3D Pie represented by the sprite.
- */
- part: 'enums(top,bottom,start,end,innerFront,innerBack,outerFront,outerBack)',
- /**
- * @cfg {String} [label='']
- * The label associated with the 'top' part of the sprite.
- */
- label: 'string'
- },
- aliases: {
- rho: 'endRho'
- },
- triggers: {
- centerX: 'path,bbox',
- centerY: 'path,bbox',
- startAngle: 'path,partZIndex',
- endAngle: 'path,partZIndex',
- startRho: 'path',
- endRho: 'path,bbox',
- margin: 'path,bbox',
- thickness: 'path',
- distortion: 'path',
- baseRotation: 'path,partZIndex',
- baseColor: 'partZIndex,partColor',
- colorSpread: 'partColor',
- part: 'path,partZIndex',
- globalAlpha: 'canvas,alpha',
- fillOpacity: 'canvas,alpha'
- },
- defaults: {
- centerX: 0,
- centerY: 0,
- startAngle: Math.PI * 2,
- endAngle: Math.PI * 2,
- startRho: 0,
- endRho: 150,
- margin: 0,
- thickness: 35,
- distortion: 0.5,
- baseRotation: 0,
- baseColor: 'white',
- colorSpread: 0.5,
- miterLimit: 1,
- bevelWidth: 5,
- strokeOpacity: 0,
- part: 'top',
- label: ''
- },
- updaters: {
- alpha: 'alphaUpdater',
- partColor: 'partColorUpdater',
- partZIndex: 'partZIndexUpdater'
- }
- }
- },
- config: {
- renderer: null,
- rendererData: null,
- rendererIndex: 0,
- series: null
- },
- bevelParams: [],
- constructor: function(config) {
- this.callParent([
- config
- ]);
- this.bevelGradient = new Ext.draw.gradient.Linear({
- stops: [
- {
- offset: 0,
- color: 'rgba(255,255,255,0)'
- },
- {
- offset: 0.7,
- color: 'rgba(255,255,255,0.6)'
- },
- {
- offset: 1,
- color: 'rgba(255,255,255,0)'
- }
- ]
- });
- },
- updateRenderer: function() {
- this.setDirty(true);
- },
- updateRendererData: function() {
- this.setDirty(true);
- },
- updateRendererIndex: function() {
- this.setDirty(true);
- },
- alphaUpdater: function(attr) {
- var me = this,
- opacity = attr.globalAlpha,
- fillOpacity = attr.fillOpacity,
- oldOpacity = me.oldOpacity,
- oldFillOpacity = me.oldFillOpacity;
- // Update the path when the sprite becomes translucent or completely opaque.
- if ((opacity !== oldOpacity && (opacity === 1 || oldOpacity === 1)) || (fillOpacity !== oldFillOpacity && (fillOpacity === 1 || oldFillOpacity === 1))) {
- me.scheduleUpdater(attr, 'path', [
- 'globalAlpha'
- ]);
- me.oldOpacity = opacity;
- me.oldFillOpacity = fillOpacity;
- }
- },
- partColorUpdater: function(attr) {
- var color = Ext.util.Color.fly(attr.baseColor),
- colorString = color.toString(),
- colorSpread = attr.colorSpread,
- fillStyle;
- switch (attr.part) {
- case 'top':
- fillStyle = new Ext.draw.gradient.Radial({
- start: {
- x: 0,
- y: 0,
- r: 0
- },
- end: {
- x: 0,
- y: 0,
- r: 1
- },
- stops: [
- {
- offset: 0,
- color: color.createLighter(0.1 * colorSpread)
- },
- {
- offset: 1,
- color: color.createDarker(0.1 * colorSpread)
- }
- ]
- });
- break;
- case 'bottom':
- fillStyle = new Ext.draw.gradient.Radial({
- start: {
- x: 0,
- y: 0,
- r: 0
- },
- end: {
- x: 0,
- y: 0,
- r: 1
- },
- stops: [
- {
- offset: 0,
- color: color.createDarker(0.2 * colorSpread)
- },
- {
- offset: 1,
- color: color.toString()
- }
- ]
- });
- break;
- case 'outerFront':
- case 'outerBack':
- fillStyle = new Ext.draw.gradient.Linear({
- stops: [
- {
- offset: 0,
- color: color.createDarker(0.15 * colorSpread).toString()
- },
- {
- offset: 0.3,
- color: colorString
- },
- {
- offset: 0.8,
- color: color.createLighter(0.2 * colorSpread).toString()
- },
- {
- offset: 1,
- color: color.createDarker(0.25 * colorSpread).toString()
- }
- ]
- });
- break;
- case 'start':
- fillStyle = new Ext.draw.gradient.Linear({
- stops: [
- {
- offset: 0,
- color: color.createDarker(0.1 * colorSpread).toString()
- },
- {
- offset: 1,
- color: color.createLighter(0.2 * colorSpread).toString()
- }
- ]
- });
- break;
- case 'end':
- fillStyle = new Ext.draw.gradient.Linear({
- stops: [
- {
- offset: 0,
- color: color.createDarker(0.1 * colorSpread).toString()
- },
- {
- offset: 1,
- color: color.createLighter(0.2 * colorSpread).toString()
- }
- ]
- });
- break;
- case 'innerFront':
- case 'innerBack':
- fillStyle = new Ext.draw.gradient.Linear({
- stops: [
- {
- offset: 0,
- color: color.createDarker(0.1 * colorSpread).toString()
- },
- {
- offset: 0.2,
- color: color.createLighter(0.2 * colorSpread).toString()
- },
- {
- offset: 0.7,
- color: colorString
- },
- {
- offset: 1,
- color: color.createDarker(0.1 * colorSpread).toString()
- }
- ]
- });
- break;
- }
- attr.fillStyle = fillStyle;
- attr.canvasAttributes.fillStyle = fillStyle;
- },
- partZIndexUpdater: function(attr) {
- var normalize = Ext.draw.sprite.AttributeParser.angle,
- rotation = attr.baseRotation,
- startAngle = attr.startAngle,
- endAngle = attr.endAngle,
- depth;
- switch (attr.part) {
- case 'top':
- attr.zIndex = 6;
- break;
- case 'outerFront':
- startAngle = normalize(startAngle + rotation);
- endAngle = normalize(endAngle + rotation);
- if (startAngle >= 0 && endAngle < 0) {
- depth = Math.sin(startAngle);
- } else if (startAngle <= 0 && endAngle > 0) {
- depth = Math.sin(endAngle);
- } else if (startAngle >= 0 && endAngle > 0) {
- if (startAngle > endAngle) {
- depth = 0;
- } else {
- depth = Math.max(Math.sin(startAngle), Math.sin(endAngle));
- }
- } else {
- depth = 1;
- };
- attr.zIndex = 4 + depth;
- break;
- case 'outerBack':
- attr.zIndex = 1;
- break;
- case 'start':
- attr.zIndex = 4 + Math.sin(normalize(startAngle + rotation));
- break;
- case 'end':
- attr.zIndex = 4 + Math.sin(normalize(endAngle + rotation));
- break;
- case 'innerFront':
- attr.zIndex = 2;
- break;
- case 'innerBack':
- attr.zIndex = 4 + Math.sin(normalize((startAngle + endAngle) / 2 + rotation));
- break;
- case 'bottom':
- attr.zIndex = 0;
- break;
- }
- attr.dirtyZIndex = true;
- },
- updatePlainBBox: function(plain) {
- var attr = this.attr,
- part = attr.part,
- baseRotation = attr.baseRotation,
- centerX = attr.centerX,
- centerY = attr.centerY,
- rho, angle, x, y, sin, cos;
- if (part === 'start') {
- angle = attr.startAngle + baseRotation;
- } else if (part === 'end') {
- angle = attr.endAngle + baseRotation;
- }
- if (Ext.isNumber(angle)) {
- sin = Math.sin(angle);
- cos = Math.cos(angle);
- x = Math.min(centerX + cos * attr.startRho, centerX + cos * attr.endRho);
- y = centerY + sin * attr.startRho * attr.distortion;
- plain.x = x;
- plain.y = y;
- plain.width = cos * (attr.endRho - attr.startRho);
- plain.height = attr.thickness + sin * (attr.endRho - attr.startRho) * 2;
- return;
- }
- if (part === 'innerFront' || part === 'innerBack') {
- rho = attr.startRho;
- } else {
- rho = attr.endRho;
- }
- plain.width = rho * 2;
- plain.height = rho * attr.distortion * 2 + attr.thickness;
- plain.x = attr.centerX - rho;
- plain.y = attr.centerY - rho * attr.distortion;
- },
- updateTransformedBBox: function(transform) {
- if (this.attr.part === 'start' || this.attr.part === 'end') {
- return this.callParent(arguments);
- }
- return this.updatePlainBBox(transform);
- },
- updatePath: function(path) {
- if (!this.attr.globalAlpha) {
- return;
- }
- if (this.attr.endAngle < this.attr.startAngle) {
- return;
- }
- this[this.attr.part + 'Renderer'](path);
- },
- render: function(surface, ctx, rect) {
- var me = this,
- renderer = me.getRenderer(),
- attr = me.attr,
- part = attr.part,
- itemCfg, changes;
- if (!attr.globalAlpha || Ext.Number.isEqual(attr.startAngle, attr.endAngle, 1.0E-8)) {
- return;
- }
- if (renderer) {
- itemCfg = {
- type: 'pie3dPart',
- part: attr.part,
- margin: attr.margin,
- distortion: attr.distortion,
- centerX: attr.centerX,
- centerY: attr.centerY,
- baseRotation: attr.baseRotation,
- startAngle: attr.startAngle,
- endAngle: attr.endAngle,
- startRho: attr.startRho,
- endRho: attr.endRho
- };
- changes = Ext.callback(renderer, null, [
- me,
- itemCfg,
- me.getRendererData(),
- me.getRendererIndex()
- ], 0, me.getSeries());
- if (changes) {
- if (changes.part) {
- // Can't let users change the nature of the sprite.
- changes.part = part;
- }
- me.setAttributes(changes);
- me.useAttributes(ctx, rect);
- }
- }
- me.callParent([
- surface,
- ctx
- ]);
- me.bevelRenderer(surface, ctx);
- // Only the top part will have the label attribute (set by the series).
- if (attr.label && me.getMarker('labels')) {
- me.placeLabel();
- }
- },
- placeLabel: function() {
- var me = this,
- attr = me.attr,
- attributeId = attr.attributeId,
- margin = attr.margin,
- distortion = attr.distortion,
- centerX = attr.centerX,
- centerY = attr.centerY,
- baseRotation = attr.baseRotation,
- startAngle = attr.startAngle + baseRotation,
- endAngle = attr.endAngle + baseRotation,
- midAngle = (startAngle + endAngle) / 2,
- startRho = attr.startRho + margin,
- endRho = attr.endRho + margin,
- midRho = (startRho + endRho) / 2,
- sin = Math.sin(midAngle),
- cos = Math.cos(midAngle),
- surfaceMatrix = me.surfaceMatrix,
- label = me.getMarker('labels'),
- labelTpl = label.getTemplate(),
- calloutLine = labelTpl.getCalloutLine(),
- calloutLineLength = calloutLine && calloutLine.length || 40,
- labelCfg = {},
- rendererParams, rendererChanges, x, y;
- surfaceMatrix.appendMatrix(attr.matrix);
- labelCfg.text = attr.label;
- x = centerX + cos * midRho;
- y = centerY + sin * midRho * distortion;
- labelCfg.x = surfaceMatrix.x(x, y);
- labelCfg.y = surfaceMatrix.y(x, y);
- x = centerX + cos * endRho;
- y = centerY + sin * endRho * distortion;
- labelCfg.calloutStartX = surfaceMatrix.x(x, y);
- labelCfg.calloutStartY = surfaceMatrix.y(x, y);
- x = centerX + cos * (endRho + calloutLineLength);
- y = centerY + sin * (endRho + calloutLineLength) * distortion;
- labelCfg.calloutPlaceX = surfaceMatrix.x(x, y);
- labelCfg.calloutPlaceY = surfaceMatrix.y(x, y);
- labelCfg.calloutWidth = 2;
- if (labelTpl.attr.renderer) {
- rendererParams = [
- me.attr.label,
- label,
- labelCfg,
- me.getRendererData(),
- me.getRendererIndex()
- ];
- rendererChanges = Ext.callback(labelTpl.attr.renderer, null, rendererParams, 0, me.getSeries());
- if (typeof rendererChanges === 'string') {
- labelCfg.text = rendererChanges;
- } else {
- Ext.apply(labelCfg, rendererChanges);
- }
- }
- me.putMarker('labels', labelCfg, attributeId);
- me.putMarker('labels', {
- callout: 1
- }, attributeId);
- },
- bevelRenderer: function(surface, ctx) {
- var me = this,
- attr = me.attr,
- bevelWidth = attr.bevelWidth,
- params = me.bevelParams,
- i;
- for (i = 0; i < params.length; i++) {
- ctx.beginPath();
- ctx.ellipse.apply(ctx, params[i]);
- ctx.save();
- ctx.lineWidth = bevelWidth;
- ctx.strokeOpacity = bevelWidth ? 1 : 0;
- ctx.strokeGradient = me.bevelGradient;
- ctx.stroke(attr);
- ctx.restore();
- }
- },
- lidRenderer: function(path, thickness) {
- var attr = this.attr,
- margin = attr.margin,
- distortion = attr.distortion,
- centerX = attr.centerX,
- centerY = attr.centerY,
- baseRotation = attr.baseRotation,
- startAngle = attr.startAngle + baseRotation,
- endAngle = attr.endAngle + baseRotation,
- midAngle = (startAngle + endAngle) / 2,
- startRho = attr.startRho,
- endRho = attr.endRho,
- sinEnd = Math.sin(endAngle),
- cosEnd = Math.cos(endAngle);
- centerX += Math.cos(midAngle) * margin;
- centerY += Math.sin(midAngle) * margin * distortion;
- path.ellipse(centerX, centerY + thickness, startRho, startRho * distortion, 0, startAngle, endAngle, false);
- path.lineTo(centerX + cosEnd * endRho, centerY + thickness + sinEnd * endRho * distortion);
- path.ellipse(centerX, centerY + thickness, endRho, endRho * distortion, 0, endAngle, startAngle, true);
- path.closePath();
- },
- topRenderer: function(path) {
- this.lidRenderer(path, 0);
- },
- bottomRenderer: function(path) {
- var attr = this.attr,
- none = Ext.util.Color.RGBA_NONE;
- if (attr.globalAlpha < 1 || attr.fillOpacity < 1 || attr.shadowColor !== none) {
- this.lidRenderer(path, attr.thickness);
- }
- },
- sideRenderer: function(path, position) {
- var attr = this.attr,
- margin = attr.margin,
- centerX = attr.centerX,
- centerY = attr.centerY,
- distortion = attr.distortion,
- baseRotation = attr.baseRotation,
- startAngle = attr.startAngle + baseRotation,
- endAngle = attr.endAngle + baseRotation,
- // eslint-disable-next-line max-len
- isFullPie = (!attr.startAngle && Ext.Number.isEqual(Math.PI * 2, attr.endAngle, 1.0E-7)),
- thickness = attr.thickness,
- startRho = attr.startRho,
- endRho = attr.endRho,
- angle = (position === 'start' && startAngle) || (position === 'end' && endAngle),
- sin = Math.sin(angle),
- cos = Math.cos(angle),
- isTranslucent = attr.globalAlpha < 1,
- isVisible = position === 'start' && cos < 0 || position === 'end' && cos > 0 || isTranslucent,
- midAngle;
- if (isVisible && !isFullPie) {
- midAngle = (startAngle + endAngle) / 2;
- centerX += Math.cos(midAngle) * margin;
- centerY += Math.sin(midAngle) * margin * distortion;
- path.moveTo(centerX + cos * startRho, centerY + sin * startRho * distortion);
- path.lineTo(centerX + cos * endRho, centerY + sin * endRho * distortion);
- path.lineTo(centerX + cos * endRho, centerY + sin * endRho * distortion + thickness);
- path.lineTo(centerX + cos * startRho, centerY + sin * startRho * distortion + thickness);
- path.closePath();
- }
- },
- startRenderer: function(path) {
- this.sideRenderer(path, 'start');
- },
- endRenderer: function(path) {
- this.sideRenderer(path, 'end');
- },
- rimRenderer: function(path, radius, isDonut, isFront) {
- var me = this,
- attr = me.attr,
- margin = attr.margin,
- centerX = attr.centerX,
- centerY = attr.centerY,
- distortion = attr.distortion,
- baseRotation = attr.baseRotation,
- normalize = Ext.draw.sprite.AttributeParser.angle,
- startAngle = attr.startAngle + baseRotation,
- endAngle = attr.endAngle + baseRotation,
- // It's critical to use non-normalized start and end angles
- // for middle angle calculation. Consider a situation where the
- // start angle is +170 degrees and the end engle is -170 degrees
- // after normalization (the middle angle is 0 then, but it should be 180 degrees).
- midAngle = normalize((startAngle + endAngle) / 2),
- thickness = attr.thickness,
- isTranslucent = attr.globalAlpha < 1,
- isAllFront, isAllBack, params;
- me.bevelParams = [];
- startAngle = normalize(startAngle);
- endAngle = normalize(endAngle);
- centerX += Math.cos(midAngle) * margin;
- centerY += Math.sin(midAngle) * margin * distortion;
- isAllFront = startAngle >= 0 && endAngle >= 0;
- isAllBack = startAngle <= 0 && endAngle <= 0;
- function renderLeftFrontChunk() {
- path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, Math.PI, startAngle, true);
- path.lineTo(centerX + Math.cos(startAngle) * radius, centerY + Math.sin(startAngle) * radius * distortion);
- params = [
- centerX,
- centerY,
- radius,
- radius * distortion,
- 0,
- startAngle,
- Math.PI,
- false
- ];
- if (!isDonut) {
- me.bevelParams.push(params);
- }
- path.ellipse.apply(path, params);
- path.closePath();
- }
- function renderRightFrontChunk() {
- path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, 0, endAngle, false);
- path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion);
- params = [
- centerX,
- centerY,
- radius,
- radius * distortion,
- 0,
- endAngle,
- 0,
- true
- ];
- if (!isDonut) {
- me.bevelParams.push(params);
- }
- path.ellipse.apply(path, params);
- path.closePath();
- }
- function renderLeftBackChunk() {
- path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, Math.PI, endAngle, false);
- path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion);
- params = [
- centerX,
- centerY,
- radius,
- radius * distortion,
- 0,
- endAngle,
- Math.PI,
- true
- ];
- if (isDonut) {
- me.bevelParams.push(params);
- }
- path.ellipse.apply(path, params);
- path.closePath();
- }
- function renderRightBackChunk() {
- path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, startAngle, 0, false);
- path.lineTo(centerX + radius, centerY);
- params = [
- centerX,
- centerY,
- radius,
- radius * distortion,
- 0,
- 0,
- startAngle,
- true
- ];
- if (isDonut) {
- me.bevelParams.push(params);
- }
- path.ellipse.apply(path, params);
- path.closePath();
- }
- if (isFront) {
- if (!isDonut || isTranslucent) {
- if (startAngle >= 0 && endAngle < 0) {
- renderLeftFrontChunk();
- } else if (startAngle <= 0 && endAngle > 0) {
- renderRightFrontChunk();
- } else if (startAngle <= 0 && endAngle < 0) {
- if (startAngle > endAngle) {
- path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, 0, Math.PI, false);
- path.lineTo(centerX - radius, centerY);
- params = [
- centerX,
- centerY,
- radius,
- radius * distortion,
- 0,
- Math.PI,
- 0,
- true
- ];
- if (!isDonut) {
- me.bevelParams.push(params);
- }
- path.ellipse.apply(path, params);
- path.closePath();
- }
- } else {
- // startAngle >= 0 && endAngle > 0
- // obtuse horseshoe-like slice with the gap facing forward
- if (startAngle > endAngle) {
- renderLeftFrontChunk();
- renderRightFrontChunk();
- } else {
- // acute slice facing forward
- params = [
- centerX,
- centerY,
- radius,
- radius * distortion,
- 0,
- startAngle,
- endAngle,
- false
- ];
- if (isAllFront && !isDonut || isAllBack && isDonut) {
- me.bevelParams.push(params);
- }
- path.ellipse.apply(path, params);
- path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion + thickness);
- path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, endAngle, startAngle, true);
- path.closePath();
- }
- }
- }
- } else {
- if (isDonut || isTranslucent) {
- if (startAngle >= 0 && endAngle < 0) {
- renderLeftBackChunk();
- } else if (startAngle <= 0 && endAngle > 0) {
- renderRightBackChunk();
- } else if (startAngle <= 0 && endAngle < 0) {
- if (startAngle > endAngle) {
- renderLeftBackChunk();
- renderRightBackChunk();
- } else {
- path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, startAngle, endAngle, false);
- path.lineTo(centerX + Math.cos(endAngle) * radius, centerY + Math.sin(endAngle) * radius * distortion);
- params = [
- centerX,
- centerY,
- radius,
- radius * distortion,
- 0,
- endAngle,
- startAngle,
- true
- ];
- if (isDonut) {
- me.bevelParams.push(params);
- }
- path.ellipse.apply(path, params);
- path.closePath();
- }
- } else {
- // startAngle >= 0 && endAngle > 0
- if (startAngle > endAngle) {
- path.ellipse(centerX, centerY + thickness, radius, radius * distortion, 0, -Math.PI, 0, false);
- path.lineTo(centerX + radius, centerY);
- params = [
- centerX,
- centerY,
- radius,
- radius * distortion,
- 0,
- 0,
- -Math.PI,
- true
- ];
- if (isDonut) {
- me.bevelParams.push(params);
- }
- path.ellipse.apply(path, params);
- path.closePath();
- }
- }
- }
- }
- },
- innerFrontRenderer: function(path) {
- this.rimRenderer(path, this.attr.startRho, true, true);
- },
- innerBackRenderer: function(path) {
- this.rimRenderer(path, this.attr.startRho, true, false);
- },
- outerFrontRenderer: function(path) {
- this.rimRenderer(path, this.attr.endRho, false, true);
- },
- outerBackRenderer: function(path) {
- this.rimRenderer(path, this.attr.endRho, false, false);
- }
- });
- /**
- * @class Ext.chart.series.Pie3D
- * @extends Ext.chart.series.Polar
- *
- * Creates a 3D Pie Chart.
- *
- * **Note:** Labels, legends, and lines are not currently available when using the
- * 3D Pie chart series.
- *
- * @example
- * Ext.create({
- * xtype: 'polar',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * theme: 'green',
- * interactions: 'rotate',
- * store: {
- * fields: ['data3'],
- * data: [{
- * 'data3': 14
- * }, {
- * 'data3': 16
- * }, {
- * 'data3': 14
- * }, {
- * 'data3': 6
- * }, {
- * 'data3': 36
- * }]
- * },
- * series: {
- * type: 'pie3d',
- * angleField: 'data3',
- * donut: 30
- * }
- * });
- */
- Ext.define('Ext.chart.series.Pie3D', {
- extend: 'Ext.chart.series.Polar',
- requires: [
- 'Ext.chart.series.sprite.Pie3DPart',
- 'Ext.draw.PathUtil'
- ],
- type: 'pie3d',
- seriesType: 'pie3d',
- alias: 'series.pie3d',
- is3D: true,
- config: {
- rect: [
- 0,
- 0,
- 0,
- 0
- ],
- thickness: 35,
- distortion: 0.5,
- /**
- * @cfg {String} angleField (required)
- * The store record field name to be used for the pie angles.
- * The values bound to this field name must be positive real numbers.
- */
- /**
- * @private
- * @cfg {String} radiusField
- * Not supported.
- */
- /**
- * @cfg {Number} donut Specifies the radius of the donut hole, as a percentage
- * of the chart's radius.
- * Defaults to 0 (no donut hole).
- */
- donut: 0,
- /**
- * @cfg {Array} hidden Determines which pie slices are hidden.
- */
- hidden: [],
- // Populated by the coordinateX method.
- /**
- * @cfg {Object} highlightCfg Default {@link #highlight} config for the 3D pie series.
- * Slides highlighted pie sector outward.
- */
- highlightCfg: {
- margin: 20
- },
- /**
- * @cfg {Number} [rotation=0] The starting angle of the pie slices.
- */
- /**
- * @private
- * @cfg {Boolean/Object} [shadow=false]
- */
- shadow: false
- },
- // Subtract 90 degrees from rotation, so that `rotation` config's default
- // zero value makes first pie sector start at noon, rather than 3 o'clock.
- rotationOffset: -Math.PI / 2,
- setField: function(value) {
- return this.setXField(value);
- },
- getField: function() {
- return this.getXField();
- },
- updateRotation: function(rotation) {
- var attributes = {
- baseRotation: rotation + this.rotationOffset
- };
- this.forEachSprite(function(sprite) {
- sprite.setAttributes(attributes);
- });
- },
- updateColors: function(colors) {
- var chart;
- this.setSubStyle({
- baseColor: colors
- });
- if (!this.isConfiguring) {
- chart = this.getChart();
- if (chart) {
- chart.refreshLegendStore();
- }
- }
- },
- applyShadow: function(shadow) {
- if (shadow === true) {
- shadow = {
- shadowColor: 'rgba(0,0,0,0.8)',
- shadowBlur: 30
- };
- } else if (!Ext.isObject(shadow)) {
- shadow = {
- shadowColor: Ext.util.Color.RGBA_NONE
- };
- }
- return shadow;
- },
- updateShadow: function(shadow) {
- var me = this,
- sprites = me.getSprites(),
- spritesPerSlice = me.spritesPerSlice,
- ln = sprites && sprites.length,
- i, sprite;
- for (i = 1; i < ln; i += spritesPerSlice) {
- sprite = sprites[i];
- if (sprite.attr.part === 'bottom') {
- sprite.setAttributes(shadow);
- }
- }
- },
- // This is a temporary solution until the Series.getStyleByIndex is fixed
- // to give user styles the priority over theme ones. Also, for sprites of
- // this particular series, the fillStyle shouldn't be set directly. Instead,
- // the 'baseColor' attribute should be set, from which the stops of the
- // gradient (used for fillStyle) will be calculated. Themes can't handle
- // situations like that properly.
- getStyleByIndex: function(i) {
- var indexStyle = this.callParent([
- i
- ]),
- style = this.getStyle(),
- // 'fill' and 'color' are 'fillStyle' aliases
- // (see Ext.draw.sprite.Sprite.inheritableStatics.def.aliases)
- fillStyle = indexStyle.fillStyle || indexStyle.fill || indexStyle.color,
- strokeStyle = style.strokeStyle || style.stroke;
- if (fillStyle) {
- indexStyle.baseColor = fillStyle;
- delete indexStyle.fillStyle;
- delete indexStyle.fill;
- delete indexStyle.color;
- }
- if (strokeStyle) {
- indexStyle.strokeStyle = strokeStyle;
- }
- return indexStyle;
- },
- doUpdateStyles: function() {
- var me = this,
- sprites = me.getSprites(),
- spritesPerSlice = me.spritesPerSlice,
- ln = sprites && sprites.length,
- i = 0,
- j = 0,
- k, style;
- for (; i < ln; i += spritesPerSlice , j++) {
- style = me.getStyleByIndex(j);
- for (k = 0; k < spritesPerSlice; k++) {
- sprites[i + k].setAttributes(style);
- }
- }
- },
- coordinateX: function() {
- var me = this,
- store = me.getStore(),
- records = store.getData().items,
- recordCount = records.length,
- xField = me.getXField(),
- animation = me.getAnimation(),
- rotation = me.getRotation(),
- hidden = me.getHidden(),
- sprites = me.getSprites(true),
- spriteCount = sprites.length,
- spritesPerSlice = me.spritesPerSlice,
- center = me.getCenter(),
- offsetX = me.getOffsetX(),
- offsetY = me.getOffsetY(),
- radius = me.getRadius(),
- thickness = me.getThickness(),
- distortion = me.getDistortion(),
- renderer = me.getRenderer(),
- rendererData = me.getRendererData(),
- highlight = me.getHighlight(),
- // eslint-disable-line no-unused-vars
- lastAngle = 0,
- twoPi = Math.PI * 2,
- // To avoid adjacent start/end part blinking (z-index jitter)
- // when rotating a translucent pie chart.
- delta = 1.0E-10,
- endAngles = [],
- sum = 0,
- value, unit, sprite, style, i, j;
- for (i = 0; i < recordCount; i++) {
- value = Math.abs(+records[i].get(xField)) || 0;
- if (!hidden[i]) {
- sum += value;
- }
- endAngles[i] = sum;
- if (i >= hidden.length) {
- hidden[i] = false;
- }
- }
- if (sum === 0) {
- return;
- }
- // Angular value of 1 in radians.
- unit = 2 * Math.PI / sum;
- for (i = 0; i < recordCount; i++) {
- endAngles[i] *= unit;
- }
- for (i = 0; i < recordCount; i++) {
- style = this.getStyleByIndex(i);
- for (j = 0; j < spritesPerSlice; j++) {
- sprite = sprites[i * spritesPerSlice + j];
- sprite.setAnimation(animation);
- sprite.setAttributes({
- centerX: center[0] + offsetX,
- centerY: center[1] + offsetY - thickness / 2,
- endRho: radius,
- startRho: radius * me.getDonut() / 100,
- baseRotation: rotation + me.rotationOffset,
- startAngle: lastAngle,
- endAngle: endAngles[i] - delta,
- thickness: thickness,
- distortion: distortion,
- globalAlpha: 1
- });
- sprite.setAttributes(style);
- sprite.setConfig({
- renderer: renderer,
- rendererData: rendererData,
- rendererIndex: i
- });
- }
- // if (highlight) {
- // if (!sprite.modifiers.highlight) {
- // debugger
- // sprite.addModifier(highlight, true);
- // }
- // // sprite.modifiers.highlight.setConfig(highlight);
- // }
- lastAngle = endAngles[i];
- }
- for (i *= spritesPerSlice; i < spriteCount; i++) {
- sprite = sprites[i];
- sprite.setAnimation(animation);
- sprite.setAttributes({
- startAngle: twoPi,
- endAngle: twoPi,
- globalAlpha: 0,
- baseRotation: rotation + me.rotationOffset
- });
- }
- },
- updateHighlight: function(highlight, oldHighlight) {
- this.callParent([
- highlight,
- oldHighlight
- ]);
- this.forEachSprite(function(sprite) {
- if (highlight) {
- if (sprite.modifiers.highlight) {
- sprite.modifiers.highlight.setConfig(highlight);
- } else {
- sprite.config.highlight = highlight;
- sprite.addModifier(highlight, true);
- }
- }
- });
- },
- updateLabelData: function() {
- var me = this,
- store = me.getStore(),
- items = store.getData().items,
- sprites = me.getSprites(),
- label = me.getLabel(),
- labelField = label && label.getTemplate().getField(),
- hidden = me.getHidden(),
- spritesPerSlice = me.spritesPerSlice,
- ln, labels, sprite,
- name = 'labels',
- i, // sprite index
- j;
- // record index
- if (sprites.length) {
- if (labelField) {
- labels = [];
- for (j = 0 , ln = items.length; j < ln; j++) {
- labels.push(items[j].get(labelField));
- }
- }
- // Only set labels for the sprites that compose the top lid of the pie.
- for (i = 0 , j = 0 , ln = sprites.length; i < ln; i += spritesPerSlice , j++) {
- sprite = sprites[i];
- if (label) {
- if (!sprite.getMarker(name)) {
- sprite.bindMarker(name, label);
- }
- if (labels) {
- sprite.setAttributes({
- label: labels[j]
- });
- }
- sprite.putMarker(name, {
- hidden: hidden[j]
- }, sprite.attr.attributeId);
- } else {
- sprite.releaseMarker(name);
- }
- }
- }
- },
- // The radius here will normally be set by the PolarChart.performLayout,
- // where it's half the width or height (whichever is smaller) of the chart's rect.
- // But for 3D pie series we have to take the thickness of the pie and the
- // distortion into account to calculate the proper radius.
- // The passed value is never used (or derived from) since the radius config
- // is not really meant to be used directly, as it will be reset by the next layout.
- applyRadius: function() {
- var me = this,
- chart = me.getChart(),
- padding = chart.getInnerPadding(),
- rect = chart.getMainRect() || [
- 0,
- 0,
- 1,
- 1
- ],
- width = rect[2] - padding * 2,
- height = rect[3] - padding * 2 - me.getThickness(),
- horizontalRadius = width / 2,
- verticalRadius = horizontalRadius * me.getDistortion(),
- result;
- if (verticalRadius > height / 2) {
- result = height / (me.getDistortion() * 2);
- } else {
- result = horizontalRadius;
- }
- return Math.max(result, 0);
- },
- forEachSprite: function(fn) {
- var sprites = this.sprites,
- ln = sprites.length,
- i;
- for (i = 0; i < ln; i++) {
- fn(sprites[i], Math.floor(i / this.spritesPerSlice));
- }
- },
- updateRadius: function(radius) {
- var donut;
- // The side effects of the 'getChart' call will result
- // in the 'coordinateX' method call, which we want to have called
- // first, to coordinate the data and create sprites for pie slices,
- // before we set their attributes here.
- // updateChart -> onChartAttached -> processData -> coordinateX
- this.getChart();
- donut = this.getDonut();
- this.forEachSprite(function(sprite) {
- sprite.setAttributes({
- endRho: radius,
- startRho: radius * donut / 100
- });
- });
- },
- updateDonut: function(donut) {
- var radius;
- // See 'updateRadius' comments.
- this.getChart();
- radius = this.getRadius();
- this.forEachSprite(function(sprite) {
- sprite.setAttributes({
- startRho: radius * donut / 100
- });
- });
- },
- updateCenter: function(center) {
- var offsetX, offsetY, thickness;
- // See 'updateRadius' comments.
- this.getChart();
- offsetX = this.getOffsetX();
- offsetY = this.getOffsetY();
- thickness = this.getThickness();
- this.forEachSprite(function(sprite) {
- sprite.setAttributes({
- centerX: center[0] + offsetX,
- centerY: center[1] + offsetY - thickness / 2
- });
- });
- },
- updateThickness: function(thickness) {
- var center, offsetY;
- // See 'updateRadius' comments.
- this.getChart();
- // Radius depends on thickness and distortion,
- // this will trigger its recalculation in the applier.
- this.setRadius();
- center = this.getCenter();
- offsetY = this.getOffsetY();
- this.forEachSprite(function(sprite) {
- sprite.setAttributes({
- thickness: thickness,
- centerY: center[1] + offsetY - thickness / 2
- });
- });
- },
- updateDistortion: function(distortion) {
- // See 'updateRadius' comments.
- this.getChart();
- // Radius depends on thickness and distortion,
- // this will trigger its recalculation in the applier.
- this.setRadius();
- this.forEachSprite(function(sprite) {
- sprite.setAttributes({
- distortion: distortion
- });
- });
- },
- updateOffsetX: function(offsetX) {
- var center;
- // See 'updateRadius' comments.
- this.getChart();
- center = this.getCenter();
- this.forEachSprite(function(sprite) {
- sprite.setAttributes({
- centerX: center[0] + offsetX
- });
- });
- },
- updateOffsetY: function(offsetY) {
- var center, thickness;
- // See 'updateRadius' comments.
- this.getChart();
- center = this.getCenter();
- thickness = this.getThickness();
- this.forEachSprite(function(sprite) {
- sprite.setAttributes({
- centerY: center[1] + offsetY - thickness / 2
- });
- });
- },
- updateAnimation: function(animation) {
- // See 'updateRadius' comments.
- this.getChart();
- this.forEachSprite(function(sprite) {
- sprite.setAnimation(animation);
- });
- },
- updateRenderer: function(renderer) {
- var rendererData;
- // See 'updateRadius' comments.
- this.getChart();
- rendererData = this.getRendererData();
- this.forEachSprite(function(sprite, itemIndex) {
- sprite.setConfig({
- renderer: renderer,
- rendererData: rendererData,
- rendererIndex: itemIndex
- });
- });
- },
- getRendererData: function() {
- return {
- store: this.getStore(),
- angleField: this.getXField(),
- radiusField: this.getYField(),
- series: this
- };
- },
- getSprites: function(createMissing) {
- var me = this,
- store = me.getStore(),
- sprites = me.sprites;
- if (!store) {
- return Ext.emptyArray;
- }
- if (sprites && !createMissing) {
- return sprites;
- }
- // eslint-disable-next-line vars-on-top, one-var
- var surface = me.getSurface(),
- records = store.getData().items,
- spritesPerSlice = me.spritesPerSlice,
- partCount = me.partNames.length,
- recordCount = records.length,
- sprite, i, j;
- for (i = 0; i < recordCount; i++) {
- if (!sprites[i * spritesPerSlice]) {
- for (j = 0; j < partCount; j++) {
- sprite = surface.add({
- type: 'pie3dPart',
- part: me.partNames[j],
- series: me
- });
- sprite.getAnimation().setDurationOn('baseRotation', 0);
- sprites.push(sprite);
- }
- }
- }
- return sprites;
- },
- betweenAngle: function(x, a, b) {
- var pp = Math.PI * 2,
- offset = this.rotationOffset;
- a += offset;
- b += offset;
- x -= a;
- b -= a;
- // Normalize, so that both x and b are in the [0,360) interval.
- // Since 360 * n angles will be normalized to 0,
- // we need to treat b === 0 as a special case.
- x %= pp;
- b %= pp;
- x += pp;
- b += pp;
- x %= pp;
- b %= pp;
- return x < b || b === 0;
- },
- getItemForPoint: function(x, y) {
- var me = this,
- sprites = me.getSprites(),
- spritesPerSlice = me.spritesPerSlice,
- result = null,
- store, records, hidden, i, ln, sprite, topPartIndex;
- if (!sprites) {
- return result;
- }
- store = me.getStore();
- records = store.getData().items;
- hidden = me.getHidden();
- for (i = 0 , ln = records.length; i < ln; i++) {
- if (hidden[i]) {
-
- continue;
- }
- topPartIndex = i * spritesPerSlice;
- sprite = sprites[topPartIndex];
- // This is CPU intensive on mousemove (no visial slowdown
- // on a fast machine, but some throttling might be desirable
- // on slower machines).
- // On touch devices performance/battery hit is negligible.
- if (sprite.hitTest([
- x,
- y
- ])) {
- result = {
- series: me,
- sprite: sprites.slice(topPartIndex, topPartIndex + spritesPerSlice),
- index: i,
- record: records[i],
- category: 'sprites',
- field: me.getXField()
- };
- break;
- }
- }
- return result;
- },
- provideLegendInfo: function(target) {
- var me = this,
- store = me.getStore(),
- items, labelField, field, hidden, style, color, i;
- if (store) {
- items = store.getData().items;
- labelField = me.getLabel().getTemplate().getField();
- field = me.getField();
- hidden = me.getHidden();
- for (i = 0; i < items.length; i++) {
- style = me.getStyleByIndex(i);
- color = style.baseColor;
- target.push({
- name: labelField ? String(items[i].get(labelField)) : field + ' ' + i,
- mark: color || 'black',
- disabled: hidden[i],
- series: me.getId(),
- index: i
- });
- }
- }
- }
- }, function() {
- var proto = this.prototype,
- definition = Ext.chart.series.sprite.Pie3DPart.def.getInitialConfig().processors.part;
- proto.partNames = definition.replace(/^enums\(|\)/g, '').split(',');
- proto.spritesPerSlice = proto.partNames.length;
- });
- /**
- * Polar sprite.
- */
- Ext.define('Ext.chart.series.sprite.Polar', {
- extend: 'Ext.chart.series.sprite.Series',
- inheritableStatics: {
- def: {
- processors: {
- /**
- * @cfg {Number} [centerX=0] The central point of the series on the x-axis.
- */
- centerX: 'number',
- /**
- * @cfg {Number} [centerY=0] The central point of the series on the y-axis.
- */
- centerY: 'number',
- /**
- * @cfg {Number} [startAngle=0] The starting angle of the polar series.
- */
- startAngle: 'number',
- /**
- * @cfg {Number} [endAngle=Math.PI] The ending angle of the polar series.
- */
- endAngle: 'number',
- /**
- * @cfg {Number} [startRho=0] The starting radius of the polar series.
- */
- startRho: 'number',
- /**
- * @cfg {Number} [endRho=150] The ending radius of the polar series.
- */
- endRho: 'number',
- /**
- * @cfg {Number} [baseRotation=0] The starting rotation of the polar series.
- */
- baseRotation: 'number'
- },
- defaults: {
- centerX: 0,
- centerY: 0,
- startAngle: 0,
- endAngle: Math.PI,
- startRho: 0,
- endRho: 150,
- baseRotation: 0
- },
- triggers: {
- centerX: 'bbox',
- centerY: 'bbox',
- startAngle: 'bbox',
- endAngle: 'bbox',
- startRho: 'bbox',
- endRho: 'bbox',
- baseRotation: 'bbox'
- }
- }
- },
- updatePlainBBox: function(plain) {
- var attr = this.attr;
- plain.x = attr.centerX - attr.endRho;
- plain.y = attr.centerY + attr.endRho;
- plain.width = attr.endRho * 2;
- plain.height = attr.endRho * 2;
- }
- });
- /**
- * @class Ext.chart.series.sprite.Radar
- * @extends Ext.chart.series.sprite.Polar
- *
- * Radar series sprite.
- */
- Ext.define('Ext.chart.series.sprite.Radar', {
- alias: 'sprite.radar',
- extend: 'Ext.chart.series.sprite.Polar',
- getDataPointXY: function(index) {
- var me = this,
- attr = me.attr,
- centerX = attr.centerX,
- centerY = attr.centerY,
- matrix = attr.matrix,
- minX = attr.dataMinX,
- maxX = attr.dataMaxX,
- dataX = attr.dataX,
- dataY = attr.dataY,
- endRho = attr.endRho,
- startRho = attr.startRho,
- baseRotation = attr.baseRotation,
- x, y, r, th, ox, oy, maxY;
- if (attr.rangeY) {
- maxY = attr.rangeY[1];
- } else {
- maxY = attr.dataMaxY;
- }
- th = (dataX[index] - minX) / (maxX - minX + 1) * 2 * Math.PI + baseRotation;
- r = dataY[index] / maxY * (endRho - startRho) + startRho;
- // Original coordinates.
- ox = centerX + Math.cos(th) * r;
- oy = centerY + Math.sin(th) * r;
- // Transformed coordinates.
- x = matrix.x(ox, oy);
- y = matrix.y(ox, oy);
- return [
- x,
- y
- ];
- },
- render: function(surface, ctx) {
- var me = this,
- attr = me.attr,
- dataX = attr.dataX,
- length = dataX.length,
- surfaceMatrix = me.surfaceMatrix,
- markerCfg = {},
- i, x, y, xy;
- ctx.beginPath();
- for (i = 0; i < length; i++) {
- xy = me.getDataPointXY(i);
- x = xy[0];
- y = xy[1];
- if (i === 0) {
- ctx.moveTo(x, y);
- }
- ctx.lineTo(x, y);
- markerCfg.translationX = surfaceMatrix.x(x, y);
- markerCfg.translationY = surfaceMatrix.y(x, y);
- me.putMarker('markers', markerCfg, i, true);
- }
- ctx.closePath();
- ctx.fillStroke(attr);
- }
- });
- /**
- * @class Ext.chart.series.Radar
- * @extends Ext.chart.series.Polar
- *
- * Creates a Radar Chart. A Radar Chart is a useful visualization technique for comparing different
- * quantitative values for a constrained number of categories.
- * As with all other series, the Radar series must be appended in the *series* Chart array
- * configuration. See the Chart documentation for more information. A typical configuration object
- * for the radar series could be:
- *
- * @example
- * Ext.create({
- * xtype: 'polar',
- * renderTo: document.body,
- * width: 500,
- * height: 400,
- * interactions: 'rotate',
- * store: {
- * fields: ['name', 'data1'],
- * data: [{
- * 'name': 'metric one',
- * 'data1': 8
- * }, {
- * 'name': 'metric two',
- * 'data1': 10
- * }, {
- * 'name': 'metric three',
- * 'data1': 12
- * }, {
- * 'name': 'metric four',
- * 'data1': 1
- * }, {
- * 'name': 'metric five',
- * 'data1': 13
- * }]
- * },
- * series: {
- * type: 'radar',
- * angleField: 'name',
- * radiusField: 'data1',
- * style: {
- * fillStyle: '#388FAD',
- * fillOpacity: .1,
- * strokeStyle: '#388FAD',
- * strokeOpacity: .8,
- * lineWidth: 1
- * }
- * },
- * axes: [{
- * type: 'numeric',
- * position: 'radial',
- * fields: 'data1',
- * style: {
- * estStepSize: 10
- * },
- * grid: true
- * }, {
- * type: 'category',
- * position: 'angular',
- * fields: 'name',
- * style: {
- * estStepSize: 1
- * },
- * grid: true
- * }]
- * });
- *
- */
- Ext.define('Ext.chart.series.Radar', {
- extend: 'Ext.chart.series.Polar',
- type: 'radar',
- seriesType: 'radar',
- alias: 'series.radar',
- requires: [
- 'Ext.chart.series.sprite.Radar'
- ],
- themeColorCount: function() {
- return 1;
- },
- isStoreDependantColorCount: false,
- themeMarkerCount: function() {
- return 1;
- },
- updateAngularAxis: function(axis) {
- axis.processData(this);
- },
- updateRadialAxis: function(axis) {
- axis.processData(this);
- },
- coordinateX: function() {
- return this.coordinate('X', 0, 2);
- },
- coordinateY: function() {
- return this.coordinate('Y', 1, 2);
- },
- updateCenter: function(center) {
- this.setStyle({
- translationX: center[0] + this.getOffsetX(),
- translationY: center[1] + this.getOffsetY()
- });
- this.doUpdateStyles();
- },
- updateRadius: function(radius) {
- this.setStyle({
- endRho: radius
- });
- this.doUpdateStyles();
- },
- updateRotation: function(rotation) {
- // Overrides base class method.
- var me = this,
- chart = me.getChart(),
- axes = chart.getAxes(),
- i, ln, axis;
- for (i = 0 , ln = axes.length; i < ln; i++) {
- axis = axes[i];
- axis.setRotation(rotation);
- }
- me.setStyle({
- rotationRads: rotation
- });
- me.doUpdateStyles();
- },
- updateTotalAngle: function(totalAngle) {
- this.processData();
- },
- getItemForPoint: function(x, y) {
- var me = this,
- sprite = me.sprites && me.sprites[0],
- attr = sprite.attr,
- dataX = attr.dataX,
- length = dataX.length,
- store = me.getStore(),
- marker = me.getMarker(),
- threshhold, item, xy, i, bbox, markers;
- if (me.getHidden()) {
- return null;
- }
- if (sprite && marker) {
- markers = sprite.getMarker('markers');
- for (i = 0; i < length; i++) {
- bbox = markers.getBBoxFor(i);
- threshhold = (bbox.width + bbox.height) * 0.25;
- xy = sprite.getDataPointXY(i);
- if (Math.abs(xy[0] - x) < threshhold && Math.abs(xy[1] - y) < threshhold) {
- item = {
- series: me,
- sprite: sprite,
- index: i,
- category: 'markers',
- record: store.getData().items[i],
- field: me.getYField()
- };
- return item;
- }
- }
- }
- return me.callParent(arguments);
- },
- getDefaultSpriteConfig: function() {
- var config = this.callParent(),
- animation = {
- customDurations: {
- translationX: 0,
- translationY: 0,
- rotationRads: 0,
- // Prevent animation of 'dataMinX' and 'dataMaxX' attributes in order
- // to react instantaniously to changes to the 'hidden' attribute.
- dataMinX: 0,
- dataMaxX: 0
- }
- };
- if (config.animation) {
- Ext.apply(config.animation, animation);
- } else {
- config.animation = animation;
- }
- return config;
- },
- getSprites: function() {
- var me = this,
- chart = me.getChart(),
- sprites = me.sprites;
- if (!chart) {
- return Ext.emptyArray;
- }
- if (!sprites.length) {
- me.createSprite();
- }
- return sprites;
- },
- provideLegendInfo: function(target) {
- var me = this,
- style = me.getSubStyleWithTheme(),
- fill = style.fillStyle;
- if (Ext.isArray(fill)) {
- fill = fill[0];
- }
- target.push({
- name: me.getTitle() || me.getYField() || me.getId(),
- mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
- disabled: me.getHidden(),
- series: me.getId(),
- index: 0
- });
- }
- });
- /**
- * @class Ext.chart.series.sprite.Scatter
- * @extends Ext.chart.series.sprite.Cartesian
- *
- * Scatter series sprite.
- */
- Ext.define('Ext.chart.series.sprite.Scatter', {
- alias: 'sprite.scatterSeries',
- extend: 'Ext.chart.series.sprite.Cartesian',
- renderClipped: function(surface, ctx, dataClipRect, surfaceClipRect) {
- if (this.cleanRedraw) {
- return;
- }
- // eslint-disable-next-line vars-on-top
- var me = this,
- attr = me.attr,
- dataX = attr.dataX,
- dataY = attr.dataY,
- labels = attr.labels,
- series = me.getSeries(),
- isDrawLabels = labels && me.getMarker('labels'),
- surfaceMatrix = me.surfaceMatrix,
- matrix = me.attr.matrix,
- xx = matrix.getXX(),
- yy = matrix.getYY(),
- dx = matrix.getDX(),
- dy = matrix.getDY(),
- markerCfg = {},
- changes, params,
- xScalingDirection = surface.getInherited().rtl && !attr.flipXY ? -1 : 1,
- left, right, top, bottom, x, y, i;
- if (attr.flipXY) {
- left = surfaceClipRect[1] - xx * xScalingDirection;
- right = surfaceClipRect[1] + surfaceClipRect[3] + xx * xScalingDirection;
- top = surfaceClipRect[0] - yy;
- bottom = surfaceClipRect[0] + surfaceClipRect[2] + yy;
- } else {
- left = surfaceClipRect[0] - xx * xScalingDirection;
- right = surfaceClipRect[0] + surfaceClipRect[2] + xx * xScalingDirection;
- top = surfaceClipRect[1] - yy;
- bottom = surfaceClipRect[1] + surfaceClipRect[3] + yy;
- }
- for (i = 0; i < dataX.length; i++) {
- x = dataX[i];
- y = dataY[i];
- x = x * xx + dx;
- y = y * yy + dy;
- if (left <= x && x <= right && top <= y && y <= bottom) {
- if (attr.renderer) {
- markerCfg = {
- type: 'markers',
- translationX: surfaceMatrix.x(x, y),
- translationY: surfaceMatrix.y(x, y)
- };
- params = [
- me,
- markerCfg,
- {
- store: me.getStore()
- },
- i
- ];
- changes = Ext.callback(attr.renderer, null, params, 0, series);
- markerCfg = Ext.apply(markerCfg, changes);
- } else {
- markerCfg.translationX = surfaceMatrix.x(x, y);
- markerCfg.translationY = surfaceMatrix.y(x, y);
- }
- me.putMarker('markers', markerCfg, i, !attr.renderer);
- if (isDrawLabels && labels[i]) {
- me.drawLabel(labels[i], x, y, i, surfaceClipRect);
- }
- }
- }
- },
- drawLabel: function(text, dataX, dataY, labelId, rect) {
- var me = this,
- attr = me.attr,
- label = me.getMarker('labels'),
- labelTpl = label.getTemplate(),
- labelCfg = me.labelCfg || (me.labelCfg = {}),
- surfaceMatrix = me.surfaceMatrix,
- labelX, labelY,
- labelOverflowPadding = attr.labelOverflowPadding,
- flipXY = attr.flipXY,
- halfHeight, labelBox, changes, params;
- labelCfg.text = text;
- labelBox = me.getMarkerBBox('labels', labelId, true);
- if (!labelBox) {
- me.putMarker('labels', labelCfg, labelId);
- labelBox = me.getMarkerBBox('labels', labelId, true);
- }
- if (flipXY) {
- labelCfg.rotationRads = Math.PI * 0.5;
- } else {
- labelCfg.rotationRads = 0;
- }
- halfHeight = labelBox.height / 2;
- labelX = dataX;
- switch (labelTpl.attr.display) {
- case 'under':
- labelY = dataY - halfHeight - labelOverflowPadding;
- break;
- case 'rotate':
- labelX += labelOverflowPadding;
- labelY = dataY - labelOverflowPadding;
- labelCfg.rotationRads = -Math.PI / 4;
- break;
- default:
- // 'over'
- labelY = dataY + halfHeight + labelOverflowPadding;
- }
- labelCfg.x = surfaceMatrix.x(labelX, labelY);
- labelCfg.y = surfaceMatrix.y(labelX, labelY);
- if (labelTpl.attr.renderer) {
- params = [
- text,
- label,
- labelCfg,
- {
- store: me.getStore()
- },
- labelId
- ];
- changes = Ext.callback(labelTpl.attr.renderer, null, params, 0, me.getSeries());
- if (typeof changes === 'string') {
- labelCfg.text = changes;
- } else {
- Ext.apply(labelCfg, changes);
- }
- }
- me.putMarker('labels', labelCfg, labelId);
- }
- });
- /**
- * @class Ext.chart.series.Scatter
- * @extends Ext.chart.series.Cartesian
- *
- * Creates a Scatter Chart. The scatter plot is useful when trying to display more than
- * two variables in the same visualization. These variables can be mapped into x, y coordinates
- * and also to an element's radius/size, color, etc. As with all other series, the Scatter Series
- * must be appended in the *series* Chart array configuration. See the Chart documentation for more
- * information on creating charts. A typical configuration object for the scatter could be:
- *
- * @example
- * Ext.create({
- * xtype: 'cartesian',
- * renderTo: document.body,
- * width: 600,
- * height: 400,
- * insetPadding: 40,
- * interactions: ['itemhighlight'],
- * store: {
- * fields: ['name', 'data1', 'data2'],
- * data: [{
- * 'name': 'metric one',
- * 'data1': 10,
- * 'data2': 14
- * }, {
- * 'name': 'metric two',
- * 'data1': 7,
- * 'data2': 16
- * }, {
- * 'name': 'metric three',
- * 'data1': 5,
- * 'data2': 14
- * }, {
- * 'name': 'metric four',
- * 'data1': 2,
- * 'data2': 6
- * }, {
- * 'name': 'metric five',
- * 'data1': 27,
- * 'data2': 36
- * }]
- * },
- * axes: [{
- * type: 'numeric',
- * position: 'left',
- * fields: ['data1'],
- * title: {
- * text: 'Sample Values',
- * fontSize: 15
- * },
- * grid: true,
- * minimum: 0
- * }, {
- * type: 'category',
- * position: 'bottom',
- * fields: ['name'],
- * title: {
- * text: 'Sample Values',
- * fontSize: 15
- * }
- * }],
- * series: {
- * type: 'scatter',
- * highlight: {
- * size: 12,
- * radius: 12,
- * fill: '#96D4C6',
- * stroke: '#30BDA7'
- * },
- * fill: true,
- * xField: 'name',
- * yField: 'data2',
- * marker: {
- * type: 'circle',
- * fill: '#30BDA7',
- * radius: 10,
- * lineWidth: 0
- * }
- * }
- * });
- *
- * 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, `data1`, `data2` and `data3` respectively.
- * All x-fields for the series must be the same field, in this case `name`. Each scatter series
- * has a different styling configuration for markers, specified by the `marker` object. Finally
- * we set the left axis as axis to show the current values of the elements.
- *
- */
- Ext.define('Ext.chart.series.Scatter', {
- extend: 'Ext.chart.series.Cartesian',
- alias: 'series.scatter',
- type: 'scatter',
- seriesType: 'scatterSeries',
- requires: [
- 'Ext.chart.series.sprite.Scatter'
- ],
- config: {
- itemInstancing: null,
- marker: true
- },
- themeMarkerCount: function() {
- return 1;
- },
- provideLegendInfo: function(target) {
- var me = this,
- style = me.getMarkerStyleByIndex(0),
- fill = style.fillStyle;
- target.push({
- name: me.getTitle() || me.getYField() || me.getId(),
- mark: (Ext.isObject(fill) ? fill.stops && fill.stops[0].color : fill) || style.strokeStyle || 'black',
- disabled: me.getHidden(),
- series: me.getId(),
- index: 0
- });
- }
- });
- Ext.define('Ext.chart.theme.Blue', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.blue',
- 'chart.theme.Blue'
- ],
- config: {
- baseColor: '#4d7fe6'
- }
- });
- Ext.define('Ext.chart.theme.BlueGradients', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.blue-gradients',
- 'chart.theme.Blue:gradients'
- ],
- config: {
- baseColor: '#4d7fe6',
- gradients: {
- type: 'linear',
- degrees: 90
- }
- }
- });
- Ext.define('Ext.chart.theme.Category1', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.category1',
- 'chart.theme.Category1'
- ],
- config: {
- colors: [
- '#f0a50a',
- '#c20024',
- '#2044ba',
- '#810065',
- '#7eae29'
- ]
- }
- });
- Ext.define('Ext.chart.theme.Category1Gradients', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.category1-gradients',
- 'chart.theme.Category1:gradients'
- ],
- config: {
- colors: [
- '#f0a50a',
- '#c20024',
- '#2044ba',
- '#810065',
- '#7eae29'
- ],
- gradients: {
- type: 'linear',
- degrees: 90
- }
- }
- });
- Ext.define('Ext.chart.theme.Category2', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.category2',
- 'chart.theme.Category2'
- ],
- config: {
- colors: [
- '#6d9824',
- '#87146e',
- '#2a9196',
- '#d39006',
- '#1e40ac'
- ]
- }
- });
- Ext.define('Ext.chart.theme.Category2Gradients', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.category2-gradients',
- 'chart.theme.Category2:gradients'
- ],
- config: {
- colors: [
- '#6d9824',
- '#87146e',
- '#2a9196',
- '#d39006',
- '#1e40ac'
- ],
- gradients: {
- type: 'linear',
- degrees: 90
- }
- }
- });
- Ext.define('Ext.chart.theme.Category3', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.category3',
- 'chart.theme.Category3'
- ],
- config: {
- colors: [
- '#fbbc29',
- '#ce2e4e',
- '#7e0062',
- '#158b90',
- '#57880e'
- ]
- }
- });
- Ext.define('Ext.chart.theme.Category3Gradients', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.category3-gradients',
- 'chart.theme.Category3:gradients'
- ],
- config: {
- colors: [
- '#fbbc29',
- '#ce2e4e',
- '#7e0062',
- '#158b90',
- '#57880e'
- ],
- gradients: {
- type: 'linear',
- degrees: 90
- }
- }
- });
- Ext.define('Ext.chart.theme.Category4', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.category4',
- 'chart.theme.Category4'
- ],
- config: {
- colors: [
- '#ef5773',
- '#fcbd2a',
- '#4f770d',
- '#1d3eaa',
- '#9b001f'
- ]
- }
- });
- Ext.define('Ext.chart.theme.Category4Gradients', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.category4-gradients',
- 'chart.theme.Category4:gradients'
- ],
- config: {
- colors: [
- '#ef5773',
- '#fcbd2a',
- '#4f770d',
- '#1d3eaa',
- '#9b001f'
- ],
- gradients: {
- type: 'linear',
- degrees: 90
- }
- }
- });
- Ext.define('Ext.chart.theme.Category5', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.category5',
- 'chart.theme.Category5'
- ],
- config: {
- colors: [
- '#7eae29',
- '#fdbe2a',
- '#910019',
- '#27b4bc',
- '#d74dbc'
- ]
- }
- });
- Ext.define('Ext.chart.theme.Category5Gradients', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.category5-gradients',
- 'chart.theme.Category5:gradients'
- ],
- config: {
- colors: [
- '#7eae29',
- '#fdbe2a',
- '#910019',
- '#27b4bc',
- '#d74dbc'
- ],
- gradients: {
- type: 'linear',
- degrees: 90
- }
- }
- });
- Ext.define('Ext.chart.theme.Category6', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.category6',
- 'chart.theme.Category6'
- ],
- config: {
- colors: [
- '#44dce1',
- '#0b2592',
- '#996e05',
- '#7fb325',
- '#b821a1'
- ]
- }
- });
- Ext.define('Ext.chart.theme.Category6Gradients', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.category6-gradients',
- 'chart.theme.Category6:gradients'
- ],
- config: {
- colors: [
- '#44dce1',
- '#0b2592',
- '#996e05',
- '#7fb325',
- '#b821a1'
- ],
- gradients: {
- type: 'linear',
- degrees: 90
- }
- }
- });
- Ext.define('Ext.chart.theme.DefaultGradients', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.default-gradients',
- 'chart.theme.Base:gradients'
- ],
- config: {
- gradients: {
- type: 'linear',
- degrees: 90
- }
- }
- });
- Ext.define('Ext.chart.theme.Green', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.green',
- 'chart.theme.Green'
- ],
- config: {
- baseColor: '#b1da5a'
- }
- });
- Ext.define('Ext.chart.theme.GreenGradients', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.green-gradients',
- 'chart.theme.Green:gradients'
- ],
- config: {
- baseColor: '#b1da5a',
- gradients: {
- type: 'linear',
- degrees: 90
- }
- }
- });
- Ext.define('Ext.chart.theme.Midnight', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.midnight',
- 'chart.theme.Midnight'
- ],
- config: {
- colors: [
- '#a837ff',
- '#4ac0f2',
- '#ff4d35',
- '#ff8809',
- '#61c102',
- '#ff37ea'
- ],
- chart: {
- defaults: {
- captions: {
- title: {
- docked: 'top',
- padding: 5,
- style: {
- textAlign: 'center',
- fontFamily: 'default',
- fontWeight: 'bold',
- fillStyle: 'rgb(224, 224, 227)',
- fontSize: 'default*1.6'
- }
- },
- subtitle: {
- docked: 'top',
- style: {
- textAlign: 'center',
- fontFamily: 'default',
- fontWeight: 'normal',
- fillStyle: 'rgb(224, 224, 227)',
- fontSize: 'default*1.3'
- }
- },
- credits: {
- docked: 'bottom',
- padding: 5,
- style: {
- textAlign: 'left',
- fontFamily: 'default',
- fontWeight: 'lighter',
- fillStyle: 'rgb(224, 224, 227)',
- fontSize: 'default'
- }
- }
- },
- background: 'rgb(52, 52, 53)'
- }
- },
- axis: {
- defaults: {
- style: {
- strokeStyle: 'rgb(224, 224, 227)'
- },
- label: {
- fillStyle: 'rgb(224, 224, 227)'
- },
- title: {
- fillStyle: 'rgb(224, 224, 227)'
- },
- grid: {
- strokeStyle: 'rgb(112, 112, 115)'
- }
- }
- },
- series: {
- defaults: {
- label: {
- fillStyle: 'rgb(224, 224, 227)'
- }
- }
- },
- sprites: {
- text: {
- fillStyle: 'rgb(224, 224, 227)'
- }
- },
- legend: {
- label: {
- fillStyle: 'white'
- },
- border: {
- lineWidth: 2,
- fillStyle: 'rgba(255, 255, 255, 0.3)',
- strokeStyle: 'rgb(150, 150, 150)'
- },
- background: 'rgb(52, 52, 53)'
- }
- }
- });
- Ext.define('Ext.chart.theme.Muted', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.muted',
- 'chart.theme.Muted'
- ],
- config: {
- colors: [
- '#8ca640',
- '#974144',
- '#4091ba',
- '#8e658e',
- '#3b8d8b',
- '#b86465',
- '#d2af69',
- '#6e8852',
- '#3dcc7e',
- '#a6bed1',
- '#cbaa4b',
- '#998baa'
- ]
- }
- });
- Ext.define('Ext.chart.theme.Purple', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.purple',
- 'chart.theme.Purple'
- ],
- config: {
- baseColor: '#da5abd'
- }
- });
- Ext.define('Ext.chart.theme.PurpleGradients', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.purple-gradients',
- 'chart.theme.Purple:gradients'
- ],
- config: {
- baseColor: '#da5abd',
- gradients: {
- type: 'linear',
- degrees: 90
- }
- }
- });
- Ext.define('Ext.chart.theme.Red', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.red',
- 'chart.theme.Red'
- ],
- config: {
- baseColor: '#e84b67'
- }
- });
- Ext.define('Ext.chart.theme.RedGradients', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.red-gradients',
- 'chart.theme.Red:gradients'
- ],
- config: {
- baseColor: '#e84b67',
- gradients: {
- type: 'linear',
- degrees: 90
- }
- }
- });
- Ext.define('Ext.chart.theme.Sky', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.sky',
- 'chart.theme.Sky'
- ],
- config: {
- baseColor: '#4ce0e7'
- }
- });
- Ext.define('Ext.chart.theme.SkyGradients', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.sky-gradients',
- 'chart.theme.Sky:gradients'
- ],
- config: {
- baseColor: '#4ce0e7',
- gradients: {
- type: 'linear',
- degrees: 90
- }
- }
- });
- Ext.define('Ext.chart.theme.Yellow', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.yellow',
- 'chart.theme.Yellow'
- ],
- config: {
- baseColor: '#fec935'
- }
- });
- Ext.define('Ext.chart.theme.YellowGradients', {
- extend: 'Ext.chart.theme.Base',
- singleton: true,
- alias: [
- 'chart.theme.yellow-gradients',
- 'chart.theme.Yellow:gradients'
- ],
- config: {
- baseColor: '#fec935',
- gradients: {
- type: 'linear',
- degrees: 90
- }
- }
- });
- /**
- * A helper class to facilitate common operations on points and vectors.
- */
- Ext.define('Ext.draw.Point', {
- requires: [
- 'Ext.draw.Draw',
- 'Ext.draw.Matrix'
- ],
- isPoint: true,
- x: 0,
- y: 0,
- length: 0,
- angle: 0,
- angleUnits: 'degrees',
- statics: {
- /**
- * @method
- * @static
- * Creates a flyweight Ext.draw.Point instance.
- * Takes the same parameters as the {@link Ext.draw.Point#method!constructor}.
- * Do not hold the instance of the flyweight point.
- *
- * @param {Number/Number[]/Object/Ext.draw.Point} point
- * @return {Ext.draw.Point}
- */
- fly: (function() {
- var point = null;
- return function(x, y) {
- if (!point) {
- point = new Ext.draw.Point();
- }
- point.constructor(x, y);
- return point;
- };
- })()
- },
- /**
- * Creates a point.
- *
- * new Ext.draw.Point(3, 4);
- * new Ext.draw.Point(3); // both x and y equal 3
- * new Ext.draw.Point([3, 4]);
- * new Ext.draw.Point({x: 3, y: 4});
- * new Ext.draw.Point(p); // where `p` is a Ext.draw.Point instance.
- *
- * @param {Number/Number[]/Object/Ext.draw.Point} x
- * @param {Number/Number[]/Object/Ext.draw.Point} y
- */
- constructor: function(x, y) {
- var me = this;
- if (typeof x === 'number') {
- me.x = x;
- if (typeof y === 'number') {
- me.y = y;
- } else {
- me.y = x;
- }
- } else if (Ext.isArray(x)) {
- me.x = x[0];
- me.y = x[1];
- } else if (x) {
- me.x = x.x;
- me.y = x.y;
- }
- me.calculatePolar();
- },
- calculateCartesian: function() {
- var me = this,
- length = me.length,
- angle = me.angle;
- if (me.angleUnits === 'degrees') {
- angle = Ext.draw.Draw.rad(angle);
- }
- me.x = Math.cos(angle) * length;
- me.y = Math.sin(angle) * length;
- },
- calculatePolar: function() {
- var me = this,
- x = me.x,
- y = me.y;
- me.length = Math.sqrt(x * x + y * y);
- me.angle = Math.atan2(y, x);
- if (me.angleUnits === 'degrees') {
- me.angle = Ext.draw.Draw.degrees(me.angle);
- }
- },
- /**
- * Sets the x-coordinate of the point.
- * @param {Number} x
- */
- setX: function(x) {
- this.x = x;
- this.calculatePolar();
- },
- /**
- * Sets the y-coordinate of the point.
- * @param {Number} y
- */
- setY: function(y) {
- this.y = y;
- this.calculatePolar();
- },
- /**
- * Sets coordinates of the point.
- * Takes the same parameters as the {@link #method!constructor}.
- * @param {Number/Number[]/Object/Ext.draw.Point} x
- * @param {Number/Number[]/Object/Ext.draw.Point} y
- */
- set: function(x, y) {
- this.constructor(x, y);
- },
- /**
- * Sets the angle of the vector (measured from the x-axis to the vector)
- * without changing its length.
- * @param {Number} angle
- */
- setAngle: function(angle) {
- this.angle = angle;
- this.calculateCartesian();
- },
- /**
- * Sets the length of the vector without changing its angle.
- * @param {Number} length
- */
- setLength: function(length) {
- this.length = length;
- this.calculateCartesian();
- },
- /**
- * Sets both the angle and the length of the vector.
- * A point can be thought of as a vector pointing from the origin to the point's location.
- * This can also be interpreted as setting coordinates of a point in the polar
- * coordinate system.
- * @param {Number} angle
- * @param {Number} length
- */
- setPolar: function(angle, length) {
- this.angle = angle;
- this.length = length;
- this.calculateCartesian();
- },
- /**
- * Returns a copy of the point.
- * @return {Ext.draw.Point}
- */
- clone: function() {
- return new Ext.draw.Point(this.x, this.y);
- },
- /**
- * Adds another vector to this one and returns the resulting vector
- * without changing this vector.
- * @param {Number/Number[]/Object/Ext.draw.Point} x
- * @param {Number/Number[]/Object/Ext.draw.Point} y
- * @return {Ext.draw.Point}
- */
- add: function(x, y) {
- var fly = Ext.draw.Point.fly(x, y);
- return new Ext.draw.Point(this.x + fly.x, this.y + fly.y);
- },
- /**
- * Subtracts another vector from this one and returns the resulting vector
- * without changing this vector.
- * @param {Number/Number[]/Object/Ext.draw.Point} x
- * @param {Number/Number[]/Object/Ext.draw.Point} y
- * @return {Ext.draw.Point}
- */
- sub: function(x, y) {
- var fly = Ext.draw.Point.fly(x, y);
- return new Ext.draw.Point(this.x - fly.x, this.y - fly.y);
- },
- /**
- * Returns the result of scalar multiplication of this vector by the given factor.
- * This vector is not modified.
- * @param {Number} n The factor.
- * @return {Ext.draw.Point}
- */
- mul: function(n) {
- return new Ext.draw.Point(this.x * n, this.y * n);
- },
- /**
- * Returns a vector which coordinates are the result of division of this vector's
- * coordinates by the given number. This vector is not modified.
- * This vector is not modified.
- * @param {Number} n The denominator.
- * @return {Ext.draw.Point}
- */
- div: function(n) {
- return new Ext.draw.Point(this.x / n, this.y / n);
- },
- /**
- * Returns the dot product of this vector and the given vector.
- * @param {Number/Number[]/Object/Ext.draw.Point} x
- * @param {Number/Number[]/Object/Ext.draw.Point} y
- * @return {Number}
- */
- dot: function(x, y) {
- var fly = Ext.draw.Point.fly(x, y);
- return this.x * fly.x + this.y * fly.y;
- },
- /**
- * Checks whether coordinates of the point match those of the point provided.
- * @param {Number/Number[]/Object/Ext.draw.Point} x
- * @param {Number/Number[]/Object/Ext.draw.Point} y
- * @return {Boolean}
- */
- equals: function(x, y) {
- var fly = Ext.draw.Point.fly(x, y);
- return this.x === fly.x && this.y === fly.y;
- },
- /**
- * Rotates the point by the given angle. This point is not modified.
- * @param {Number} angle The rotation angle.
- * @param {Ext.draw.Point} [center] The center of rotation (optional). Defaults to origin.
- * @return {Ext.draw.Point} The rotated point.
- */
- rotate: function(angle, center) {
- var sin, cos, cx, cy, point;
- if (this.angleUnits === 'degrees') {
- angle = Ext.draw.Draw.rad(angle);
- sin = Math.sin(angle);
- cos = Math.cos(angle);
- }
- if (center) {
- cx = center.x;
- cy = center.y;
- } else {
- cx = 0;
- cy = 0;
- }
- point = Ext.draw.Matrix.fly([
- cos,
- sin,
- -sin,
- cos,
- cx - cos * cx + cy * sin,
- cy - cos * cy + cx * -sin
- ]).transformPoint(this);
- return new Ext.draw.Point(point);
- },
- /**
- * Transforms the point from one coordinate system to another
- * using the transformation matrix provided. This point is not modified.
- * @param {Ext.draw.Matrix/Number[]} matrix A trasformation matrix or its elements.
- * @return {Ext.draw.Point}
- */
- transform: function(matrix) {
- if (matrix && matrix.isMatrix) {
- return new Ext.draw.Point(matrix.transformPoint(this));
- } else if (arguments.length === 6) {
- return new Ext.draw.Point(Ext.draw.Matrix.fly(arguments).transformPoint(this));
- } else {
- Ext.raise("Invalid parameters.");
- }
- },
- /**
- * Returns a new point with rounded x and y values. This point is not modified.
- * @return {Ext.draw.Point}
- */
- round: function() {
- return new Ext.draw.Point(Math.round(this.x), Math.round(this.y));
- },
- /**
- * Returns a new point with ceiled x and y values. This point is not modified.
- * @return {Ext.draw.Point}
- */
- ceil: function() {
- return new Ext.draw.Point(Math.ceil(this.x), Math.ceil(this.y));
- },
- /**
- * Returns a new point with floored x and y values. This point is not modified.
- * @return {Ext.draw.Point}
- */
- floor: function() {
- return new Ext.draw.Point(Math.floor(this.x), Math.floor(this.y));
- },
- /**
- * Returns a new point with absolute values of the x and y values of this point.
- * This point is not modified.
- * @return {Ext.draw.Point}
- */
- abs: function(x, y) {
- return new Ext.draw.Point(Math.abs(this.x), Math.abs(this.y));
- },
- /**
- * Normalizes the vector by changing its length to 1 without changing its angle.
- * The returned result is a normalized vector. This vector is not modified.
- * @param {Number} [factor=1] Multiplication factor. Defaults to 1.
- * @return {Ext.draw.Point}
- */
- normalize: function(factor) {
- var x = this.x,
- y = this.y,
- k = (factor || 1) / Math.sqrt(x * x + y * y);
- return new Ext.draw.Point(x * k, y * k);
- },
- /**
- * Returns the vector from the point perpendicular to the line (shortest distance).
- * Where line is specified using two points or the coordinates of those points.
- * @param {Ext.draw.Point} p1
- * @param {Ext.draw.Point} p2
- * @return {Ext.draw.Point}
- */
- getDistanceToLine: function(p1, p2) {
- var n, pp1;
- if (arguments.length === 4) {
- p1 = new Ext.draw.Point(arguments[0], arguments[1]);
- p2 = new Ext.draw.Point(arguments[2], arguments[3]);
- }
- // See http://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Vector_formulation
- n = p2.sub(p1).normalize();
- pp1 = p1.sub(this);
- return pp1.sub(n.mul(pp1.dot(n)));
- },
- /**
- * Checks if both x and y coordinates of the point are zero.
- * @return {Boolean}
- */
- isZero: function() {
- return this.x === 0 && this.y === 0;
- },
- /**
- * Checks if both x and y coordinates of the point are valid numbers.
- * @return {Boolean}
- */
- isNumber: function() {
- return Ext.isNumber(this.x) && Ext.isNumber(this.y);
- }
- });
- /**
- * A draw container {@link Ext.AbstractPlugin plugin} that adds ability to listen
- * to sprite events. For example:
- *
- * var drawContainer = Ext.create('Ext.draw.Container', {
- * plugins: {
- * spriteevents: true
- * },
- * renderTo: Ext.getBody(),
- * width: 200,
- * height: 200,
- * sprites: [{
- * type: 'circle',
- * fillStyle: '#79BB3F',
- * r: 50,
- * x: 100,
- * y: 100
- * }],
- * listeners: {
- * spriteclick: function (item, event) {
- * var sprite = item && item.sprite;
- * if (sprite) {
- * sprite.setAttributes({fillStyle: 'red'});
- sprite.getSurface().renderFrame();
- * }
- * }
- * }
- * });
- */
- Ext.define('Ext.draw.plugin.SpriteEvents', {
- extend: 'Ext.plugin.Abstract',
- alias: 'plugin.spriteevents',
- requires: [
- 'Ext.draw.overrides.hittest.All'
- ],
- /**
- * @event spritemousemove
- * Fires when the mouse is moved on a sprite.
- * @param {Object} sprite
- * @param {Event} event
- */
- /**
- * @event spritemouseup
- * Fires when a mouseup event occurs on a sprite.
- * @param {Object} sprite
- * @param {Event} event
- */
- /**
- * @event spritemousedown
- * Fires when a mousedown event occurs on a sprite.
- * @param {Object} sprite
- * @param {Event} event
- */
- /**
- * @event spritemouseover
- * Fires when the mouse enters a sprite.
- * @param {Object} sprite
- * @param {Event} event
- */
- /**
- * @event spritemouseout
- * Fires when the mouse exits a sprite.
- * @param {Object} sprite
- * @param {Event} event
- */
- /**
- * @event spriteclick
- * Fires when a click event occurs on a sprite.
- * @param {Object} sprite
- * @param {Event} event
- */
- /**
- * @event spritedblclick
- * Fires when a double click event occurs on a sprite.
- * @param {Object} sprite
- * @param {Event} event
- */
- /**
- * @event spritetap
- * Fires when a tap event occurs on a sprite.
- * @param {Object} sprite
- * @param {Event} event
- */
- mouseMoveEvents: {
- mousemove: true,
- mouseover: true,
- mouseout: true
- },
- spriteMouseMoveEvents: {
- spritemousemove: true,
- spritemouseover: true,
- spritemouseout: true
- },
- init: function(drawContainer) {
- var handleEvent = 'handleEvent';
- this.drawContainer = drawContainer;
- drawContainer.addElementListener({
- click: handleEvent,
- dblclick: handleEvent,
- mousedown: handleEvent,
- mousemove: handleEvent,
- mouseup: handleEvent,
- mouseover: handleEvent,
- mouseout: handleEvent,
- // run our handlers before user code
- priority: 1001,
- scope: this
- });
- },
- hasSpriteMouseMoveListeners: function() {
- var listeners = this.drawContainer.hasListeners,
- name;
- for (name in this.spriteMouseMoveEvents) {
- if (name in listeners) {
- return true;
- }
- }
- return false;
- },
- hitTestEvent: function(e) {
- var items = this.drawContainer.getItems(),
- surface, sprite, i;
- for (i = items.length - 1; i >= 0; i--) {
- surface = items.get(i);
- sprite = surface.hitTestEvent(e);
- if (sprite) {
- return sprite;
- }
- }
- return null;
- },
- handleEvent: function(e) {
- var me = this,
- drawContainer = me.drawContainer,
- isMouseMoveEvent = e.type in me.mouseMoveEvents,
- lastSprite = me.lastSprite,
- sprite;
- if (isMouseMoveEvent && !me.hasSpriteMouseMoveListeners()) {
- return;
- }
- sprite = me.hitTestEvent(e);
- if (isMouseMoveEvent && !Ext.Object.equals(sprite, lastSprite)) {
- if (lastSprite) {
- drawContainer.fireEvent('spritemouseout', lastSprite, e);
- }
- if (sprite) {
- drawContainer.fireEvent('spritemouseover', sprite, e);
- }
- }
- if (sprite) {
- drawContainer.fireEvent('sprite' + e.type, sprite, e);
- }
- me.lastSprite = sprite;
- }
- });
- /**
- * The ItemInfo interaction allows displaying detailed information about a series data
- * point in a popup panel.
- *
- * To attach this interaction to a chart, include an entry in the chart's
- * {@link Ext.chart.AbstractChart#interactions interactions} config with the `iteminfo` type:
- *
- * new Ext.chart.AbstractChart({
- * renderTo: Ext.getBody(),
- * width: 800,
- * height: 600,
- * store: store1,
- * axes: [ ...some axes options... ],
- * series: [ ...some series options... ],
- * interactions: [{
- * type: 'iteminfo',
- * listeners: {
- * show: function(me, item, panel) {
- * panel.setHtml('Stock Price: $' + item.record.get('price'));
- * }
- * }
- * }]
- * });
- */
- Ext.define('Ext.chart.interactions.ItemInfo', {
- extend: 'Ext.chart.interactions.Abstract',
- type: 'iteminfo',
- alias: 'interaction.iteminfo',
- /**
- * @event show
- * Fires when the info panel is shown.
- * @param {Ext.chart.interactions.ItemInfo} this The interaction instance
- * @param {Object} item The item whose info is being displayed
- * @param {Ext.Panel} panel The panel for displaying the info
- */
- config: {
- /**
- * @cfg {Object} gestures
- * Defines the gestures that should trigger the item info panel to be displayed.
- */
- gestures: {
- tap: 'onInfoGesture'
- },
- /**
- * @cfg {Object} panel
- * An optional set of configuration overrides for the {@link Ext.Panel} that gets
- * displayed. This object will be merged with the default panel configuration.
- */
- panel: {
- modal: true,
- centered: true,
- width: 300,
- height: 200,
- scrollable: 'vertical',
- hideOnMaskTap: true,
- fullscreen: false,
- hidden: false,
- zIndex: 30
- }
- },
- item: null,
- applyPanel: function(panel, oldPanel) {
- return Ext.factory(panel, 'Ext.Panel', oldPanel);
- },
- updatePanel: function(panel, oldPanel) {
- if (panel) {
- panel.on('hide', "reset", this);
- }
- if (oldPanel) {
- oldPanel.un('hide', "reset", this);
- }
- },
- onInfoGesture: function(e, element) {
- var me = this,
- panel = me.getPanel(),
- item = me.getItemForEvent(e);
- if (item) {
- me.item = item;
- me.fireEvent('show', me, item, panel);
- Ext.Viewport.add(panel);
- panel.show('pop');
- item.series.setAttributesForItem(item, {
- highlighted: true
- });
- me.sync();
- }
- return false;
- },
- reset: function() {
- var me = this,
- item = me.item;
- if (item) {
- item.series.setAttributesForItem(item, {
- highlighted: false
- });
- me.item = null;
- me.sync();
- }
- }
- });
|