123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976149771497814979149801498114982149831498414985149861498714988149891499014991149921499314994149951499614997149981499915000150011500215003150041500515006150071500815009150101501115012150131501415015150161501715018150191502015021150221502315024150251502615027150281502915030150311503215033150341503515036150371503815039150401504115042150431504415045150461504715048150491505015051150521505315054150551505615057150581505915060150611506215063150641506515066150671506815069150701507115072150731507415075150761507715078150791508015081150821508315084150851508615087150881508915090150911509215093150941509515096150971509815099151001510115102151031510415105151061510715108151091511015111151121511315114151151511615117151181511915120151211512215123151241512515126151271512815129151301513115132151331513415135151361513715138151391514015141151421514315144151451514615147151481514915150151511515215153151541515515156151571515815159151601516115162151631516415165151661516715168151691517015171151721517315174151751517615177151781517915180151811518215183151841518515186151871518815189151901519115192151931519415195151961519715198151991520015201152021520315204152051520615207152081520915210152111521215213152141521515216152171521815219152201522115222152231522415225152261522715228152291523015231152321523315234152351523615237152381523915240152411524215243152441524515246152471524815249152501525115252152531525415255152561525715258152591526015261152621526315264152651526615267152681526915270152711527215273152741527515276152771527815279152801528115282152831528415285152861528715288152891529015291152921529315294152951529615297152981529915300153011530215303153041530515306153071530815309153101531115312153131531415315153161531715318153191532015321153221532315324153251532615327153281532915330153311533215333153341533515336153371533815339153401534115342153431534415345153461534715348153491535015351153521535315354153551535615357153581535915360153611536215363153641536515366153671536815369153701537115372153731537415375153761537715378153791538015381153821538315384153851538615387153881538915390153911539215393153941539515396153971539815399154001540115402154031540415405154061540715408154091541015411154121541315414154151541615417154181541915420154211542215423154241542515426154271542815429154301543115432154331543415435154361543715438154391544015441154421544315444154451544615447154481544915450154511545215453154541545515456154571545815459154601546115462154631546415465154661546715468154691547015471154721547315474154751547615477154781547915480154811548215483154841548515486154871548815489154901549115492154931549415495154961549715498154991550015501155021550315504155051550615507155081550915510155111551215513155141551515516155171551815519155201552115522155231552415525155261552715528155291553015531155321553315534155351553615537155381553915540155411554215543155441554515546155471554815549155501555115552155531555415555155561555715558155591556015561155621556315564155651556615567155681556915570155711557215573155741557515576155771557815579155801558115582155831558415585155861558715588155891559015591155921559315594155951559615597155981559915600156011560215603156041560515606156071560815609156101561115612156131561415615156161561715618156191562015621156221562315624156251562615627156281562915630156311563215633156341563515636156371563815639156401564115642156431564415645156461564715648156491565015651156521565315654156551565615657156581565915660156611566215663156641566515666156671566815669156701567115672156731567415675156761567715678156791568015681156821568315684156851568615687156881568915690156911569215693156941569515696156971569815699157001570115702157031570415705157061570715708157091571015711157121571315714157151571615717157181571915720157211572215723157241572515726157271572815729157301573115732157331573415735157361573715738157391574015741157421574315744157451574615747157481574915750157511575215753157541575515756157571575815759157601576115762157631576415765157661576715768157691577015771157721577315774157751577615777157781577915780157811578215783157841578515786157871578815789157901579115792157931579415795157961579715798157991580015801158021580315804158051580615807158081580915810158111581215813158141581515816158171581815819158201582115822158231582415825158261582715828158291583015831158321583315834158351583615837158381583915840158411584215843158441584515846158471584815849158501585115852158531585415855158561585715858158591586015861158621586315864158651586615867158681586915870158711587215873158741587515876158771587815879158801588115882158831588415885158861588715888158891589015891158921589315894158951589615897158981589915900159011590215903159041590515906159071590815909159101591115912159131591415915159161591715918159191592015921159221592315924159251592615927159281592915930159311593215933159341593515936159371593815939159401594115942159431594415945159461594715948159491595015951159521595315954159551595615957159581595915960159611596215963159641596515966159671596815969159701597115972159731597415975159761597715978159791598015981159821598315984159851598615987159881598915990159911599215993159941599515996159971599815999160001600116002160031600416005160061600716008160091601016011160121601316014160151601616017160181601916020160211602216023160241602516026160271602816029160301603116032160331603416035160361603716038160391604016041160421604316044160451604616047160481604916050160511605216053160541605516056160571605816059160601606116062160631606416065160661606716068160691607016071160721607316074160751607616077160781607916080160811608216083160841608516086160871608816089160901609116092160931609416095160961609716098160991610016101161021610316104161051610616107161081610916110161111611216113161141611516116161171611816119161201612116122161231612416125161261612716128161291613016131161321613316134161351613616137161381613916140161411614216143161441614516146161471614816149161501615116152161531615416155161561615716158161591616016161161621616316164161651616616167161681616916170161711617216173161741617516176161771617816179161801618116182161831618416185161861618716188161891619016191161921619316194161951619616197161981619916200162011620216203162041620516206162071620816209162101621116212162131621416215162161621716218162191622016221162221622316224162251622616227162281622916230162311623216233162341623516236162371623816239162401624116242162431624416245162461624716248162491625016251162521625316254162551625616257162581625916260162611626216263162641626516266162671626816269162701627116272162731627416275162761627716278162791628016281162821628316284162851628616287162881628916290162911629216293162941629516296162971629816299163001630116302163031630416305163061630716308163091631016311163121631316314163151631616317163181631916320163211632216323163241632516326163271632816329163301633116332163331633416335163361633716338163391634016341163421634316344163451634616347163481634916350163511635216353163541635516356163571635816359163601636116362163631636416365163661636716368163691637016371163721637316374163751637616377163781637916380163811638216383163841638516386163871638816389163901639116392163931639416395163961639716398163991640016401164021640316404164051640616407164081640916410164111641216413164141641516416164171641816419164201642116422164231642416425164261642716428164291643016431164321643316434164351643616437164381643916440164411644216443164441644516446164471644816449164501645116452164531645416455164561645716458164591646016461164621646316464164651646616467164681646916470164711647216473164741647516476164771647816479164801648116482164831648416485164861648716488164891649016491164921649316494164951649616497164981649916500165011650216503165041650516506165071650816509165101651116512165131651416515165161651716518165191652016521165221652316524165251652616527165281652916530165311653216533165341653516536165371653816539165401654116542165431654416545165461654716548165491655016551165521655316554165551655616557165581655916560165611656216563165641656516566165671656816569165701657116572165731657416575165761657716578165791658016581165821658316584165851658616587165881658916590165911659216593165941659516596165971659816599166001660116602166031660416605166061660716608166091661016611166121661316614166151661616617166181661916620166211662216623166241662516626166271662816629166301663116632166331663416635166361663716638166391664016641166421664316644166451664616647166481664916650166511665216653166541665516656166571665816659166601666116662166631666416665166661666716668166691667016671166721667316674166751667616677166781667916680166811668216683166841668516686166871668816689166901669116692166931669416695166961669716698166991670016701167021670316704167051670616707167081670916710167111671216713167141671516716167171671816719167201672116722167231672416725167261672716728167291673016731167321673316734167351673616737167381673916740167411674216743167441674516746167471674816749167501675116752167531675416755167561675716758167591676016761167621676316764167651676616767167681676916770167711677216773167741677516776167771677816779167801678116782167831678416785167861678716788167891679016791167921679316794167951679616797167981679916800168011680216803168041680516806168071680816809168101681116812168131681416815168161681716818168191682016821168221682316824168251682616827168281682916830168311683216833168341683516836168371683816839168401684116842168431684416845168461684716848168491685016851168521685316854168551685616857168581685916860168611686216863168641686516866168671686816869168701687116872168731687416875168761687716878168791688016881168821688316884168851688616887168881688916890168911689216893168941689516896168971689816899169001690116902169031690416905169061690716908169091691016911169121691316914169151691616917169181691916920169211692216923169241692516926169271692816929169301693116932169331693416935169361693716938169391694016941169421694316944169451694616947169481694916950169511695216953169541695516956169571695816959169601696116962169631696416965169661696716968169691697016971169721697316974169751697616977169781697916980169811698216983169841698516986169871698816989169901699116992169931699416995169961699716998169991700017001170021700317004170051700617007170081700917010170111701217013170141701517016170171701817019170201702117022170231702417025170261702717028170291703017031170321703317034170351703617037170381703917040170411704217043170441704517046170471704817049170501705117052170531705417055170561705717058170591706017061170621706317064170651706617067170681706917070170711707217073170741707517076170771707817079170801708117082170831708417085170861708717088170891709017091170921709317094170951709617097170981709917100171011710217103171041710517106171071710817109171101711117112171131711417115171161711717118171191712017121171221712317124171251712617127171281712917130171311713217133171341713517136171371713817139171401714117142171431714417145171461714717148171491715017151171521715317154171551715617157171581715917160171611716217163171641716517166171671716817169171701717117172171731717417175171761717717178171791718017181171821718317184171851718617187171881718917190171911719217193171941719517196171971719817199172001720117202172031720417205172061720717208172091721017211172121721317214172151721617217172181721917220172211722217223172241722517226172271722817229172301723117232172331723417235172361723717238172391724017241172421724317244172451724617247172481724917250172511725217253172541725517256172571725817259172601726117262172631726417265172661726717268172691727017271172721727317274172751727617277172781727917280172811728217283172841728517286172871728817289172901729117292172931729417295172961729717298172991730017301173021730317304173051730617307173081730917310173111731217313173141731517316173171731817319173201732117322173231732417325173261732717328173291733017331173321733317334173351733617337173381733917340173411734217343173441734517346173471734817349173501735117352173531735417355173561735717358173591736017361173621736317364173651736617367173681736917370173711737217373173741737517376173771737817379173801738117382173831738417385173861738717388173891739017391173921739317394173951739617397173981739917400174011740217403174041740517406174071740817409174101741117412174131741417415174161741717418174191742017421174221742317424174251742617427174281742917430174311743217433174341743517436174371743817439174401744117442174431744417445174461744717448174491745017451174521745317454174551745617457174581745917460174611746217463174641746517466174671746817469174701747117472174731747417475174761747717478174791748017481174821748317484174851748617487174881748917490174911749217493174941749517496174971749817499175001750117502175031750417505175061750717508175091751017511175121751317514175151751617517175181751917520175211752217523175241752517526175271752817529175301753117532175331753417535175361753717538175391754017541175421754317544175451754617547175481754917550175511755217553175541755517556175571755817559175601756117562175631756417565175661756717568175691757017571175721757317574175751757617577175781757917580175811758217583175841758517586175871758817589175901759117592175931759417595175961759717598175991760017601176021760317604176051760617607176081760917610176111761217613176141761517616176171761817619176201762117622176231762417625176261762717628176291763017631176321763317634176351763617637176381763917640176411764217643176441764517646176471764817649176501765117652176531765417655176561765717658176591766017661176621766317664176651766617667176681766917670176711767217673176741767517676176771767817679176801768117682176831768417685176861768717688176891769017691176921769317694176951769617697176981769917700177011770217703177041770517706177071770817709177101771117712177131771417715177161771717718177191772017721177221772317724177251772617727177281772917730177311773217733177341773517736177371773817739177401774117742177431774417745177461774717748177491775017751177521775317754177551775617757177581775917760177611776217763177641776517766177671776817769177701777117772177731777417775177761777717778177791778017781177821778317784177851778617787177881778917790177911779217793177941779517796177971779817799178001780117802178031780417805178061780717808178091781017811178121781317814178151781617817178181781917820178211782217823178241782517826178271782817829178301783117832178331783417835178361783717838178391784017841178421784317844178451784617847178481784917850178511785217853178541785517856178571785817859178601786117862178631786417865178661786717868178691787017871178721787317874178751787617877178781787917880178811788217883178841788517886178871788817889178901789117892178931789417895178961789717898178991790017901179021790317904179051790617907179081790917910179111791217913179141791517916179171791817919179201792117922179231792417925179261792717928179291793017931179321793317934179351793617937179381793917940179411794217943179441794517946179471794817949179501795117952179531795417955179561795717958179591796017961179621796317964179651796617967179681796917970179711797217973179741797517976179771797817979179801798117982179831798417985179861798717988179891799017991179921799317994179951799617997179981799918000180011800218003180041800518006180071800818009180101801118012180131801418015180161801718018180191802018021180221802318024180251802618027180281802918030180311803218033180341803518036180371803818039180401804118042180431804418045180461804718048180491805018051180521805318054180551805618057180581805918060180611806218063180641806518066180671806818069180701807118072180731807418075180761807718078180791808018081180821808318084180851808618087180881808918090180911809218093180941809518096180971809818099181001810118102181031810418105181061810718108181091811018111181121811318114181151811618117181181811918120181211812218123181241812518126181271812818129181301813118132181331813418135181361813718138181391814018141181421814318144181451814618147181481814918150181511815218153181541815518156181571815818159181601816118162181631816418165181661816718168181691817018171181721817318174181751817618177181781817918180181811818218183181841818518186181871818818189181901819118192181931819418195181961819718198181991820018201182021820318204182051820618207182081820918210182111821218213182141821518216182171821818219182201822118222182231822418225182261822718228182291823018231182321823318234182351823618237182381823918240182411824218243182441824518246182471824818249182501825118252182531825418255182561825718258182591826018261182621826318264182651826618267182681826918270182711827218273182741827518276182771827818279182801828118282182831828418285182861828718288182891829018291182921829318294182951829618297182981829918300183011830218303183041830518306183071830818309183101831118312183131831418315183161831718318183191832018321183221832318324183251832618327183281832918330183311833218333183341833518336183371833818339183401834118342183431834418345183461834718348183491835018351183521835318354183551835618357183581835918360183611836218363183641836518366183671836818369183701837118372183731837418375183761837718378183791838018381183821838318384183851838618387183881838918390183911839218393183941839518396183971839818399184001840118402184031840418405184061840718408184091841018411184121841318414184151841618417184181841918420184211842218423184241842518426184271842818429184301843118432184331843418435184361843718438184391844018441184421844318444184451844618447184481844918450184511845218453184541845518456184571845818459184601846118462184631846418465184661846718468184691847018471184721847318474184751847618477184781847918480184811848218483184841848518486184871848818489184901849118492184931849418495184961849718498184991850018501185021850318504185051850618507185081850918510185111851218513185141851518516185171851818519185201852118522185231852418525185261852718528185291853018531185321853318534185351853618537185381853918540185411854218543185441854518546185471854818549185501855118552185531855418555185561855718558185591856018561185621856318564185651856618567185681856918570185711857218573185741857518576185771857818579185801858118582185831858418585185861858718588185891859018591185921859318594185951859618597185981859918600186011860218603186041860518606186071860818609186101861118612186131861418615186161861718618186191862018621186221862318624186251862618627186281862918630186311863218633186341863518636186371863818639186401864118642186431864418645186461864718648186491865018651186521865318654186551865618657186581865918660186611866218663186641866518666186671866818669186701867118672186731867418675186761867718678186791868018681186821868318684186851868618687186881868918690186911869218693186941869518696186971869818699187001870118702187031870418705187061870718708187091871018711187121871318714187151871618717187181871918720187211872218723187241872518726187271872818729187301873118732187331873418735187361873718738187391874018741187421874318744187451874618747187481874918750187511875218753187541875518756187571875818759187601876118762187631876418765187661876718768187691877018771187721877318774187751877618777187781877918780187811878218783187841878518786187871878818789187901879118792187931879418795187961879718798187991880018801188021880318804188051880618807188081880918810188111881218813188141881518816188171881818819188201882118822188231882418825188261882718828188291883018831188321883318834188351883618837188381883918840188411884218843188441884518846188471884818849188501885118852188531885418855188561885718858188591886018861188621886318864188651886618867188681886918870188711887218873188741887518876188771887818879188801888118882188831888418885188861888718888188891889018891188921889318894188951889618897188981889918900189011890218903189041890518906189071890818909189101891118912189131891418915189161891718918189191892018921189221892318924189251892618927189281892918930189311893218933189341893518936189371893818939189401894118942189431894418945189461894718948189491895018951189521895318954189551895618957189581895918960189611896218963189641896518966189671896818969189701897118972189731897418975189761897718978189791898018981189821898318984189851898618987189881898918990189911899218993189941899518996189971899818999190001900119002190031900419005190061900719008190091901019011190121901319014190151901619017190181901919020190211902219023190241902519026190271902819029190301903119032190331903419035190361903719038190391904019041190421904319044190451904619047190481904919050190511905219053190541905519056190571905819059190601906119062190631906419065190661906719068190691907019071190721907319074190751907619077190781907919080190811908219083190841908519086190871908819089190901909119092190931909419095190961909719098190991910019101191021910319104191051910619107191081910919110191111911219113191141911519116191171911819119191201912119122191231912419125191261912719128191291913019131191321913319134191351913619137191381913919140191411914219143191441914519146191471914819149191501915119152191531915419155191561915719158191591916019161191621916319164191651916619167191681916919170191711917219173191741917519176191771917819179191801918119182191831918419185191861918719188191891919019191191921919319194191951919619197191981919919200192011920219203192041920519206192071920819209192101921119212192131921419215192161921719218192191922019221192221922319224192251922619227192281922919230192311923219233192341923519236192371923819239192401924119242192431924419245192461924719248192491925019251192521925319254192551925619257192581925919260192611926219263192641926519266192671926819269192701927119272192731927419275192761927719278192791928019281192821928319284192851928619287192881928919290192911929219293192941929519296192971929819299193001930119302193031930419305193061930719308193091931019311193121931319314193151931619317193181931919320193211932219323193241932519326193271932819329193301933119332193331933419335193361933719338193391934019341193421934319344193451934619347193481934919350193511935219353193541935519356193571935819359193601936119362193631936419365193661936719368193691937019371193721937319374193751937619377193781937919380193811938219383193841938519386193871938819389193901939119392193931939419395193961939719398193991940019401194021940319404194051940619407194081940919410194111941219413194141941519416194171941819419194201942119422194231942419425194261942719428194291943019431194321943319434194351943619437194381943919440194411944219443194441944519446194471944819449194501945119452194531945419455194561945719458194591946019461194621946319464194651946619467194681946919470194711947219473194741947519476194771947819479194801948119482194831948419485194861948719488194891949019491194921949319494194951949619497194981949919500195011950219503195041950519506195071950819509195101951119512195131951419515195161951719518195191952019521195221952319524195251952619527195281952919530195311953219533195341953519536195371953819539195401954119542195431954419545195461954719548195491955019551195521955319554195551955619557195581955919560195611956219563195641956519566195671956819569195701957119572195731957419575195761957719578195791958019581195821958319584195851958619587195881958919590195911959219593195941959519596195971959819599196001960119602196031960419605196061960719608196091961019611196121961319614196151961619617196181961919620196211962219623196241962519626196271962819629196301963119632196331963419635196361963719638196391964019641196421964319644196451964619647196481964919650196511965219653196541965519656196571965819659196601966119662196631966419665196661966719668196691967019671196721967319674196751967619677196781967919680196811968219683196841968519686196871968819689196901969119692196931969419695196961969719698196991970019701197021970319704197051970619707197081970919710197111971219713197141971519716197171971819719197201972119722197231972419725197261972719728197291973019731197321973319734197351973619737197381973919740197411974219743197441974519746197471974819749197501975119752197531975419755197561975719758197591976019761197621976319764197651976619767197681976919770197711977219773197741977519776197771977819779197801978119782197831978419785197861978719788197891979019791197921979319794197951979619797197981979919800198011980219803198041980519806198071980819809198101981119812198131981419815198161981719818198191982019821198221982319824198251982619827198281982919830198311983219833198341983519836198371983819839198401984119842198431984419845198461984719848198491985019851198521985319854198551985619857198581985919860198611986219863198641986519866198671986819869198701987119872198731987419875198761987719878198791988019881198821988319884198851988619887198881988919890198911989219893198941989519896198971989819899199001990119902199031990419905199061990719908199091991019911199121991319914199151991619917199181991919920199211992219923199241992519926199271992819929199301993119932199331993419935199361993719938199391994019941199421994319944199451994619947199481994919950199511995219953199541995519956199571995819959199601996119962199631996419965199661996719968199691997019971199721997319974199751997619977199781997919980199811998219983199841998519986199871998819989199901999119992199931999419995199961999719998199992000020001200022000320004200052000620007200082000920010200112001220013200142001520016200172001820019200202002120022200232002420025200262002720028200292003020031200322003320034200352003620037200382003920040200412004220043200442004520046200472004820049200502005120052200532005420055200562005720058200592006020061200622006320064200652006620067200682006920070200712007220073200742007520076200772007820079200802008120082200832008420085200862008720088200892009020091200922009320094200952009620097200982009920100201012010220103201042010520106201072010820109201102011120112201132011420115201162011720118201192012020121201222012320124201252012620127201282012920130201312013220133201342013520136201372013820139201402014120142201432014420145201462014720148201492015020151201522015320154201552015620157201582015920160201612016220163201642016520166201672016820169201702017120172201732017420175201762017720178201792018020181201822018320184201852018620187201882018920190201912019220193201942019520196201972019820199202002020120202202032020420205202062020720208202092021020211202122021320214202152021620217202182021920220202212022220223202242022520226202272022820229202302023120232202332023420235202362023720238202392024020241202422024320244202452024620247202482024920250202512025220253202542025520256202572025820259202602026120262202632026420265202662026720268202692027020271202722027320274202752027620277202782027920280202812028220283202842028520286202872028820289202902029120292202932029420295202962029720298202992030020301203022030320304203052030620307203082030920310203112031220313203142031520316203172031820319203202032120322203232032420325203262032720328203292033020331203322033320334203352033620337203382033920340203412034220343203442034520346203472034820349203502035120352203532035420355203562035720358203592036020361203622036320364203652036620367203682036920370203712037220373203742037520376203772037820379203802038120382203832038420385203862038720388203892039020391203922039320394203952039620397203982039920400204012040220403204042040520406204072040820409204102041120412204132041420415204162041720418204192042020421204222042320424204252042620427204282042920430204312043220433204342043520436204372043820439204402044120442204432044420445204462044720448204492045020451204522045320454204552045620457204582045920460204612046220463204642046520466204672046820469204702047120472204732047420475204762047720478204792048020481204822048320484204852048620487204882048920490204912049220493204942049520496204972049820499205002050120502205032050420505205062050720508205092051020511205122051320514205152051620517205182051920520205212052220523205242052520526205272052820529205302053120532205332053420535205362053720538205392054020541205422054320544205452054620547205482054920550205512055220553205542055520556205572055820559205602056120562205632056420565205662056720568205692057020571205722057320574205752057620577205782057920580205812058220583205842058520586205872058820589205902059120592205932059420595205962059720598205992060020601206022060320604206052060620607206082060920610206112061220613206142061520616206172061820619206202062120622206232062420625206262062720628206292063020631206322063320634206352063620637206382063920640206412064220643206442064520646206472064820649206502065120652206532065420655206562065720658206592066020661206622066320664206652066620667206682066920670206712067220673206742067520676206772067820679206802068120682206832068420685206862068720688206892069020691206922069320694206952069620697206982069920700207012070220703207042070520706207072070820709207102071120712207132071420715207162071720718207192072020721207222072320724207252072620727207282072920730207312073220733207342073520736207372073820739207402074120742207432074420745207462074720748207492075020751207522075320754207552075620757207582075920760207612076220763207642076520766207672076820769207702077120772207732077420775207762077720778207792078020781207822078320784207852078620787207882078920790207912079220793207942079520796207972079820799208002080120802208032080420805208062080720808208092081020811208122081320814208152081620817208182081920820208212082220823208242082520826208272082820829208302083120832208332083420835208362083720838208392084020841208422084320844208452084620847208482084920850208512085220853208542085520856208572085820859208602086120862208632086420865208662086720868208692087020871208722087320874208752087620877208782087920880208812088220883208842088520886208872088820889208902089120892208932089420895208962089720898208992090020901209022090320904209052090620907209082090920910209112091220913209142091520916209172091820919209202092120922209232092420925209262092720928209292093020931209322093320934209352093620937209382093920940209412094220943209442094520946209472094820949209502095120952209532095420955209562095720958209592096020961209622096320964209652096620967209682096920970209712097220973209742097520976209772097820979209802098120982209832098420985209862098720988209892099020991209922099320994209952099620997209982099921000210012100221003210042100521006210072100821009210102101121012210132101421015210162101721018210192102021021210222102321024210252102621027210282102921030210312103221033210342103521036210372103821039210402104121042210432104421045210462104721048210492105021051210522105321054210552105621057210582105921060210612106221063210642106521066210672106821069210702107121072210732107421075210762107721078210792108021081210822108321084210852108621087210882108921090210912109221093210942109521096210972109821099211002110121102211032110421105211062110721108211092111021111211122111321114211152111621117211182111921120211212112221123211242112521126211272112821129211302113121132211332113421135211362113721138211392114021141211422114321144211452114621147211482114921150211512115221153211542115521156211572115821159211602116121162211632116421165211662116721168211692117021171211722117321174211752117621177211782117921180211812118221183211842118521186211872118821189211902119121192211932119421195211962119721198211992120021201212022120321204212052120621207212082120921210212112121221213212142121521216212172121821219212202122121222212232122421225212262122721228212292123021231212322123321234212352123621237212382123921240212412124221243212442124521246212472124821249212502125121252212532125421255212562125721258212592126021261212622126321264212652126621267212682126921270212712127221273212742127521276212772127821279212802128121282212832128421285212862128721288212892129021291212922129321294212952129621297212982129921300213012130221303213042130521306213072130821309213102131121312213132131421315213162131721318213192132021321213222132321324213252132621327213282132921330213312133221333213342133521336213372133821339213402134121342213432134421345213462134721348213492135021351213522135321354213552135621357213582135921360213612136221363213642136521366213672136821369213702137121372213732137421375213762137721378213792138021381213822138321384213852138621387213882138921390213912139221393213942139521396213972139821399214002140121402214032140421405214062140721408214092141021411214122141321414214152141621417214182141921420214212142221423214242142521426214272142821429214302143121432214332143421435214362143721438214392144021441214422144321444214452144621447214482144921450214512145221453214542145521456214572145821459214602146121462214632146421465214662146721468214692147021471214722147321474214752147621477214782147921480214812148221483214842148521486214872148821489214902149121492214932149421495214962149721498214992150021501215022150321504215052150621507215082150921510215112151221513215142151521516215172151821519215202152121522215232152421525215262152721528215292153021531215322153321534215352153621537215382153921540215412154221543215442154521546215472154821549215502155121552215532155421555215562155721558215592156021561215622156321564215652156621567215682156921570215712157221573215742157521576215772157821579215802158121582215832158421585215862158721588215892159021591215922159321594215952159621597215982159921600216012160221603216042160521606216072160821609216102161121612216132161421615216162161721618216192162021621216222162321624216252162621627216282162921630216312163221633216342163521636216372163821639216402164121642216432164421645216462164721648216492165021651216522165321654216552165621657216582165921660216612166221663216642166521666216672166821669216702167121672216732167421675216762167721678216792168021681216822168321684216852168621687216882168921690216912169221693216942169521696216972169821699217002170121702217032170421705217062170721708217092171021711217122171321714217152171621717217182171921720217212172221723217242172521726217272172821729217302173121732217332173421735217362173721738217392174021741217422174321744217452174621747217482174921750217512175221753217542175521756217572175821759217602176121762217632176421765217662176721768217692177021771217722177321774217752177621777217782177921780217812178221783217842178521786217872178821789217902179121792217932179421795217962179721798217992180021801218022180321804218052180621807218082180921810218112181221813218142181521816218172181821819218202182121822218232182421825218262182721828218292183021831218322183321834218352183621837218382183921840218412184221843218442184521846218472184821849218502185121852218532185421855218562185721858218592186021861218622186321864218652186621867218682186921870218712187221873218742187521876218772187821879218802188121882218832188421885218862188721888218892189021891218922189321894218952189621897218982189921900219012190221903219042190521906219072190821909219102191121912219132191421915219162191721918219192192021921219222192321924219252192621927219282192921930219312193221933219342193521936219372193821939219402194121942219432194421945219462194721948219492195021951219522195321954219552195621957219582195921960219612196221963219642196521966219672196821969219702197121972219732197421975219762197721978219792198021981219822198321984219852198621987219882198921990219912199221993219942199521996219972199821999220002200122002220032200422005220062200722008220092201022011220122201322014220152201622017220182201922020220212202222023220242202522026220272202822029220302203122032220332203422035220362203722038220392204022041220422204322044220452204622047220482204922050220512205222053220542205522056220572205822059220602206122062220632206422065220662206722068220692207022071220722207322074220752207622077220782207922080220812208222083220842208522086220872208822089220902209122092220932209422095220962209722098220992210022101221022210322104221052210622107221082210922110221112211222113221142211522116221172211822119221202212122122221232212422125221262212722128221292213022131221322213322134221352213622137221382213922140221412214222143221442214522146221472214822149221502215122152221532215422155221562215722158221592216022161221622216322164221652216622167221682216922170221712217222173221742217522176221772217822179221802218122182221832218422185221862218722188221892219022191221922219322194221952219622197221982219922200222012220222203222042220522206222072220822209222102221122212222132221422215222162221722218222192222022221222222222322224222252222622227222282222922230222312223222233222342223522236222372223822239222402224122242222432224422245222462224722248222492225022251222522225322254222552225622257222582225922260222612226222263222642226522266222672226822269222702227122272222732227422275222762227722278222792228022281222822228322284222852228622287222882228922290222912229222293222942229522296222972229822299223002230122302223032230422305223062230722308223092231022311223122231322314223152231622317223182231922320223212232222323223242232522326223272232822329223302233122332223332233422335223362233722338223392234022341223422234322344223452234622347223482234922350223512235222353223542235522356223572235822359223602236122362223632236422365223662236722368223692237022371223722237322374223752237622377223782237922380223812238222383223842238522386223872238822389223902239122392223932239422395223962239722398223992240022401224022240322404224052240622407224082240922410224112241222413224142241522416224172241822419224202242122422224232242422425224262242722428224292243022431224322243322434224352243622437224382243922440224412244222443224442244522446224472244822449224502245122452224532245422455224562245722458224592246022461224622246322464224652246622467224682246922470224712247222473224742247522476224772247822479224802248122482224832248422485224862248722488224892249022491224922249322494224952249622497224982249922500225012250222503225042250522506225072250822509225102251122512225132251422515225162251722518225192252022521225222252322524225252252622527225282252922530225312253222533225342253522536225372253822539225402254122542225432254422545225462254722548225492255022551225522255322554225552255622557225582255922560225612256222563225642256522566225672256822569225702257122572225732257422575225762257722578225792258022581225822258322584225852258622587225882258922590225912259222593225942259522596225972259822599226002260122602226032260422605226062260722608226092261022611226122261322614226152261622617226182261922620226212262222623226242262522626226272262822629226302263122632226332263422635226362263722638226392264022641226422264322644226452264622647226482264922650226512265222653226542265522656226572265822659226602266122662226632266422665226662266722668226692267022671226722267322674226752267622677226782267922680226812268222683226842268522686226872268822689226902269122692226932269422695226962269722698226992270022701227022270322704227052270622707227082270922710227112271222713227142271522716227172271822719227202272122722227232272422725227262272722728227292273022731227322273322734227352273622737227382273922740227412274222743227442274522746227472274822749227502275122752227532275422755227562275722758227592276022761227622276322764227652276622767227682276922770227712277222773227742277522776227772277822779227802278122782227832278422785227862278722788227892279022791227922279322794227952279622797227982279922800228012280222803228042280522806228072280822809228102281122812228132281422815228162281722818228192282022821228222282322824228252282622827228282282922830228312283222833228342283522836228372283822839228402284122842228432284422845228462284722848228492285022851228522285322854228552285622857228582285922860228612286222863228642286522866228672286822869228702287122872228732287422875228762287722878228792288022881228822288322884228852288622887228882288922890228912289222893228942289522896228972289822899229002290122902229032290422905229062290722908229092291022911229122291322914229152291622917229182291922920229212292222923229242292522926229272292822929229302293122932229332293422935229362293722938229392294022941229422294322944229452294622947229482294922950229512295222953229542295522956229572295822959229602296122962229632296422965229662296722968229692297022971229722297322974229752297622977229782297922980229812298222983229842298522986229872298822989229902299122992229932299422995229962299722998229992300023001230022300323004230052300623007230082300923010230112301223013230142301523016230172301823019230202302123022230232302423025230262302723028230292303023031230322303323034230352303623037230382303923040230412304223043230442304523046230472304823049230502305123052230532305423055230562305723058230592306023061230622306323064230652306623067230682306923070230712307223073230742307523076230772307823079230802308123082230832308423085230862308723088230892309023091230922309323094230952309623097230982309923100231012310223103231042310523106231072310823109231102311123112231132311423115231162311723118231192312023121231222312323124231252312623127231282312923130231312313223133231342313523136231372313823139231402314123142231432314423145231462314723148231492315023151231522315323154231552315623157231582315923160231612316223163231642316523166231672316823169231702317123172231732317423175231762317723178231792318023181231822318323184231852318623187231882318923190231912319223193231942319523196231972319823199232002320123202232032320423205232062320723208232092321023211232122321323214232152321623217232182321923220232212322223223232242322523226232272322823229232302323123232232332323423235232362323723238232392324023241232422324323244232452324623247232482324923250232512325223253232542325523256232572325823259232602326123262232632326423265232662326723268232692327023271232722327323274232752327623277232782327923280232812328223283232842328523286232872328823289232902329123292232932329423295232962329723298232992330023301233022330323304233052330623307233082330923310233112331223313233142331523316233172331823319233202332123322233232332423325233262332723328233292333023331233322333323334233352333623337233382333923340233412334223343233442334523346233472334823349233502335123352233532335423355233562335723358233592336023361233622336323364233652336623367233682336923370233712337223373233742337523376233772337823379233802338123382233832338423385233862338723388233892339023391233922339323394233952339623397233982339923400234012340223403234042340523406234072340823409234102341123412234132341423415234162341723418234192342023421234222342323424234252342623427234282342923430234312343223433234342343523436234372343823439234402344123442234432344423445234462344723448234492345023451234522345323454234552345623457234582345923460234612346223463234642346523466234672346823469234702347123472234732347423475234762347723478234792348023481234822348323484234852348623487234882348923490234912349223493234942349523496234972349823499235002350123502235032350423505235062350723508235092351023511235122351323514235152351623517235182351923520235212352223523235242352523526235272352823529235302353123532235332353423535235362353723538235392354023541235422354323544235452354623547235482354923550235512355223553235542355523556235572355823559235602356123562235632356423565235662356723568235692357023571235722357323574235752357623577235782357923580235812358223583235842358523586235872358823589235902359123592235932359423595235962359723598235992360023601236022360323604236052360623607236082360923610236112361223613236142361523616236172361823619236202362123622236232362423625236262362723628236292363023631236322363323634236352363623637236382363923640236412364223643236442364523646236472364823649236502365123652236532365423655236562365723658236592366023661236622366323664236652366623667236682366923670236712367223673236742367523676236772367823679236802368123682236832368423685236862368723688236892369023691236922369323694236952369623697236982369923700237012370223703237042370523706237072370823709237102371123712237132371423715237162371723718237192372023721237222372323724237252372623727237282372923730237312373223733237342373523736237372373823739237402374123742237432374423745237462374723748237492375023751237522375323754237552375623757237582375923760237612376223763237642376523766237672376823769237702377123772237732377423775237762377723778237792378023781237822378323784237852378623787237882378923790237912379223793237942379523796237972379823799238002380123802238032380423805238062380723808238092381023811238122381323814238152381623817238182381923820238212382223823238242382523826238272382823829238302383123832238332383423835238362383723838238392384023841238422384323844238452384623847238482384923850238512385223853238542385523856238572385823859238602386123862238632386423865238662386723868238692387023871238722387323874238752387623877238782387923880238812388223883238842388523886238872388823889238902389123892238932389423895238962389723898238992390023901239022390323904239052390623907239082390923910239112391223913239142391523916239172391823919239202392123922239232392423925239262392723928239292393023931239322393323934239352393623937239382393923940239412394223943239442394523946239472394823949239502395123952239532395423955239562395723958239592396023961239622396323964239652396623967239682396923970239712397223973239742397523976239772397823979239802398123982239832398423985239862398723988239892399023991239922399323994239952399623997239982399924000240012400224003240042400524006240072400824009240102401124012240132401424015240162401724018240192402024021240222402324024240252402624027240282402924030240312403224033240342403524036240372403824039240402404124042240432404424045240462404724048240492405024051240522405324054240552405624057240582405924060240612406224063240642406524066240672406824069240702407124072240732407424075240762407724078240792408024081240822408324084240852408624087240882408924090240912409224093240942409524096240972409824099241002410124102241032410424105241062410724108241092411024111241122411324114241152411624117241182411924120241212412224123241242412524126241272412824129241302413124132241332413424135241362413724138241392414024141241422414324144241452414624147241482414924150241512415224153241542415524156241572415824159241602416124162241632416424165241662416724168241692417024171241722417324174241752417624177241782417924180241812418224183241842418524186241872418824189241902419124192241932419424195241962419724198241992420024201242022420324204242052420624207242082420924210242112421224213242142421524216242172421824219242202422124222242232422424225242262422724228242292423024231242322423324234242352423624237242382423924240242412424224243242442424524246242472424824249242502425124252242532425424255242562425724258242592426024261242622426324264242652426624267242682426924270242712427224273242742427524276242772427824279242802428124282242832428424285242862428724288242892429024291242922429324294242952429624297242982429924300243012430224303243042430524306243072430824309243102431124312243132431424315243162431724318243192432024321243222432324324243252432624327243282432924330243312433224333243342433524336243372433824339243402434124342243432434424345243462434724348243492435024351243522435324354243552435624357243582435924360243612436224363243642436524366243672436824369243702437124372243732437424375243762437724378243792438024381243822438324384243852438624387243882438924390243912439224393243942439524396243972439824399244002440124402244032440424405244062440724408244092441024411244122441324414244152441624417244182441924420244212442224423244242442524426244272442824429244302443124432244332443424435244362443724438244392444024441244422444324444244452444624447244482444924450244512445224453244542445524456244572445824459244602446124462244632446424465244662446724468244692447024471244722447324474244752447624477244782447924480244812448224483244842448524486244872448824489244902449124492244932449424495244962449724498244992450024501245022450324504245052450624507245082450924510245112451224513245142451524516245172451824519245202452124522245232452424525245262452724528245292453024531245322453324534245352453624537245382453924540245412454224543245442454524546245472454824549245502455124552245532455424555245562455724558245592456024561245622456324564245652456624567245682456924570245712457224573245742457524576245772457824579245802458124582245832458424585245862458724588245892459024591245922459324594245952459624597245982459924600246012460224603246042460524606246072460824609246102461124612246132461424615246162461724618246192462024621246222462324624246252462624627246282462924630246312463224633246342463524636246372463824639246402464124642246432464424645246462464724648246492465024651246522465324654246552465624657246582465924660246612466224663246642466524666246672466824669246702467124672246732467424675246762467724678246792468024681246822468324684246852468624687246882468924690246912469224693246942469524696246972469824699247002470124702247032470424705247062470724708247092471024711247122471324714247152471624717247182471924720247212472224723247242472524726247272472824729247302473124732247332473424735247362473724738247392474024741247422474324744247452474624747247482474924750247512475224753247542475524756247572475824759247602476124762247632476424765247662476724768247692477024771247722477324774247752477624777247782477924780247812478224783247842478524786247872478824789247902479124792247932479424795247962479724798247992480024801248022480324804248052480624807248082480924810248112481224813248142481524816248172481824819248202482124822248232482424825248262482724828248292483024831248322483324834248352483624837248382483924840248412484224843248442484524846248472484824849248502485124852248532485424855248562485724858248592486024861248622486324864248652486624867248682486924870248712487224873248742487524876248772487824879248802488124882248832488424885248862488724888248892489024891248922489324894248952489624897248982489924900249012490224903249042490524906249072490824909249102491124912249132491424915249162491724918249192492024921249222492324924249252492624927249282492924930249312493224933249342493524936249372493824939249402494124942249432494424945249462494724948249492495024951249522495324954249552495624957249582495924960249612496224963249642496524966249672496824969249702497124972249732497424975249762497724978249792498024981249822498324984249852498624987249882498924990249912499224993249942499524996249972499824999250002500125002250032500425005250062500725008250092501025011250122501325014250152501625017250182501925020250212502225023250242502525026250272502825029250302503125032250332503425035250362503725038250392504025041250422504325044250452504625047250482504925050250512505225053250542505525056250572505825059250602506125062250632506425065250662506725068250692507025071250722507325074250752507625077250782507925080250812508225083250842508525086250872508825089250902509125092250932509425095250962509725098250992510025101251022510325104251052510625107251082510925110251112511225113251142511525116251172511825119251202512125122251232512425125251262512725128251292513025131251322513325134251352513625137251382513925140251412514225143251442514525146251472514825149251502515125152251532515425155251562515725158251592516025161251622516325164251652516625167251682516925170251712517225173251742517525176251772517825179251802518125182251832518425185251862518725188251892519025191251922519325194251952519625197251982519925200252012520225203252042520525206252072520825209252102521125212252132521425215252162521725218252192522025221252222522325224252252522625227252282522925230252312523225233252342523525236252372523825239252402524125242252432524425245252462524725248252492525025251252522525325254252552525625257252582525925260252612526225263252642526525266252672526825269252702527125272252732527425275252762527725278252792528025281252822528325284252852528625287252882528925290252912529225293252942529525296252972529825299253002530125302253032530425305253062530725308253092531025311253122531325314253152531625317253182531925320253212532225323253242532525326253272532825329253302533125332253332533425335253362533725338253392534025341253422534325344253452534625347253482534925350253512535225353253542535525356253572535825359253602536125362253632536425365253662536725368253692537025371253722537325374253752537625377253782537925380253812538225383253842538525386253872538825389253902539125392253932539425395253962539725398253992540025401254022540325404254052540625407254082540925410254112541225413254142541525416254172541825419254202542125422254232542425425254262542725428254292543025431254322543325434254352543625437254382543925440254412544225443254442544525446254472544825449254502545125452254532545425455254562545725458254592546025461254622546325464254652546625467254682546925470254712547225473254742547525476254772547825479254802548125482254832548425485254862548725488254892549025491254922549325494254952549625497254982549925500255012550225503255042550525506255072550825509255102551125512255132551425515255162551725518255192552025521255222552325524255252552625527255282552925530255312553225533255342553525536255372553825539255402554125542255432554425545255462554725548255492555025551255522555325554255552555625557255582555925560255612556225563255642556525566255672556825569255702557125572255732557425575255762557725578255792558025581255822558325584255852558625587255882558925590255912559225593255942559525596255972559825599256002560125602256032560425605256062560725608256092561025611256122561325614256152561625617256182561925620256212562225623256242562525626256272562825629256302563125632256332563425635256362563725638256392564025641256422564325644256452564625647256482564925650256512565225653256542565525656256572565825659256602566125662256632566425665256662566725668256692567025671256722567325674256752567625677256782567925680256812568225683256842568525686256872568825689256902569125692256932569425695256962569725698256992570025701257022570325704257052570625707257082570925710257112571225713257142571525716257172571825719257202572125722257232572425725257262572725728257292573025731257322573325734257352573625737257382573925740257412574225743257442574525746257472574825749257502575125752257532575425755257562575725758257592576025761257622576325764257652576625767257682576925770257712577225773257742577525776257772577825779257802578125782257832578425785257862578725788257892579025791257922579325794257952579625797257982579925800258012580225803258042580525806258072580825809258102581125812258132581425815258162581725818258192582025821258222582325824258252582625827258282582925830258312583225833258342583525836258372583825839258402584125842258432584425845258462584725848258492585025851258522585325854258552585625857258582585925860258612586225863258642586525866258672586825869258702587125872258732587425875258762587725878258792588025881258822588325884258852588625887258882588925890258912589225893258942589525896258972589825899259002590125902259032590425905259062590725908259092591025911259122591325914259152591625917259182591925920259212592225923259242592525926259272592825929259302593125932259332593425935259362593725938259392594025941259422594325944259452594625947259482594925950259512595225953259542595525956259572595825959259602596125962259632596425965259662596725968259692597025971259722597325974259752597625977259782597925980259812598225983259842598525986259872598825989259902599125992259932599425995259962599725998259992600026001260022600326004260052600626007260082600926010260112601226013260142601526016260172601826019260202602126022260232602426025260262602726028260292603026031260322603326034260352603626037260382603926040260412604226043260442604526046260472604826049260502605126052260532605426055260562605726058260592606026061260622606326064260652606626067260682606926070260712607226073260742607526076260772607826079260802608126082260832608426085260862608726088260892609026091260922609326094260952609626097260982609926100261012610226103261042610526106261072610826109261102611126112261132611426115261162611726118261192612026121261222612326124261252612626127261282612926130261312613226133261342613526136261372613826139261402614126142261432614426145261462614726148261492615026151261522615326154261552615626157261582615926160261612616226163261642616526166261672616826169261702617126172261732617426175261762617726178261792618026181261822618326184261852618626187261882618926190261912619226193261942619526196261972619826199262002620126202262032620426205262062620726208262092621026211262122621326214262152621626217262182621926220262212622226223262242622526226262272622826229262302623126232262332623426235262362623726238262392624026241262422624326244262452624626247262482624926250262512625226253262542625526256262572625826259262602626126262262632626426265262662626726268262692627026271262722627326274262752627626277262782627926280262812628226283262842628526286262872628826289262902629126292262932629426295262962629726298262992630026301263022630326304263052630626307263082630926310263112631226313263142631526316263172631826319263202632126322263232632426325263262632726328263292633026331263322633326334263352633626337263382633926340263412634226343263442634526346263472634826349263502635126352263532635426355263562635726358263592636026361263622636326364263652636626367263682636926370263712637226373263742637526376263772637826379263802638126382263832638426385263862638726388263892639026391263922639326394263952639626397263982639926400264012640226403264042640526406264072640826409264102641126412264132641426415264162641726418264192642026421264222642326424264252642626427264282642926430264312643226433264342643526436264372643826439264402644126442264432644426445264462644726448264492645026451264522645326454264552645626457264582645926460264612646226463264642646526466264672646826469264702647126472264732647426475264762647726478264792648026481264822648326484264852648626487264882648926490264912649226493264942649526496264972649826499265002650126502265032650426505265062650726508265092651026511265122651326514265152651626517265182651926520265212652226523265242652526526265272652826529265302653126532265332653426535265362653726538265392654026541265422654326544265452654626547265482654926550265512655226553265542655526556265572655826559265602656126562265632656426565265662656726568265692657026571265722657326574265752657626577265782657926580265812658226583265842658526586265872658826589265902659126592265932659426595265962659726598265992660026601266022660326604266052660626607266082660926610266112661226613266142661526616266172661826619266202662126622266232662426625266262662726628266292663026631266322663326634266352663626637266382663926640266412664226643266442664526646266472664826649266502665126652266532665426655266562665726658266592666026661266622666326664266652666626667266682666926670266712667226673266742667526676266772667826679266802668126682266832668426685266862668726688266892669026691266922669326694266952669626697266982669926700267012670226703267042670526706267072670826709267102671126712267132671426715267162671726718267192672026721267222672326724267252672626727267282672926730267312673226733267342673526736267372673826739267402674126742267432674426745267462674726748267492675026751267522675326754267552675626757267582675926760267612676226763267642676526766267672676826769267702677126772267732677426775267762677726778267792678026781267822678326784267852678626787267882678926790267912679226793267942679526796267972679826799268002680126802268032680426805268062680726808268092681026811268122681326814268152681626817268182681926820268212682226823268242682526826268272682826829268302683126832268332683426835268362683726838268392684026841268422684326844268452684626847268482684926850268512685226853268542685526856268572685826859268602686126862268632686426865268662686726868268692687026871268722687326874268752687626877268782687926880268812688226883268842688526886268872688826889268902689126892268932689426895268962689726898268992690026901269022690326904269052690626907269082690926910269112691226913269142691526916269172691826919269202692126922269232692426925269262692726928269292693026931269322693326934269352693626937269382693926940269412694226943269442694526946269472694826949269502695126952269532695426955269562695726958269592696026961269622696326964269652696626967269682696926970269712697226973269742697526976269772697826979269802698126982269832698426985269862698726988269892699026991269922699326994269952699626997269982699927000270012700227003270042700527006270072700827009270102701127012270132701427015270162701727018270192702027021270222702327024270252702627027270282702927030270312703227033270342703527036270372703827039270402704127042270432704427045270462704727048270492705027051270522705327054270552705627057270582705927060270612706227063270642706527066270672706827069270702707127072270732707427075270762707727078270792708027081270822708327084270852708627087270882708927090270912709227093270942709527096270972709827099271002710127102271032710427105271062710727108271092711027111271122711327114271152711627117271182711927120271212712227123271242712527126271272712827129271302713127132271332713427135271362713727138271392714027141271422714327144271452714627147271482714927150271512715227153271542715527156271572715827159271602716127162271632716427165271662716727168271692717027171271722717327174271752717627177271782717927180271812718227183271842718527186271872718827189271902719127192271932719427195271962719727198271992720027201272022720327204272052720627207272082720927210272112721227213272142721527216272172721827219272202722127222272232722427225272262722727228272292723027231272322723327234272352723627237272382723927240272412724227243272442724527246272472724827249272502725127252272532725427255272562725727258272592726027261272622726327264272652726627267272682726927270272712727227273272742727527276272772727827279272802728127282272832728427285272862728727288272892729027291272922729327294272952729627297272982729927300273012730227303273042730527306273072730827309273102731127312273132731427315273162731727318273192732027321273222732327324273252732627327273282732927330273312733227333273342733527336273372733827339273402734127342273432734427345273462734727348273492735027351273522735327354273552735627357273582735927360273612736227363273642736527366273672736827369273702737127372273732737427375273762737727378273792738027381273822738327384273852738627387273882738927390273912739227393273942739527396273972739827399274002740127402274032740427405274062740727408274092741027411274122741327414274152741627417274182741927420274212742227423274242742527426274272742827429274302743127432274332743427435274362743727438274392744027441274422744327444274452744627447274482744927450274512745227453274542745527456274572745827459274602746127462274632746427465274662746727468274692747027471274722747327474274752747627477274782747927480274812748227483274842748527486274872748827489274902749127492274932749427495274962749727498274992750027501275022750327504275052750627507275082750927510275112751227513275142751527516275172751827519275202752127522275232752427525275262752727528275292753027531275322753327534275352753627537275382753927540275412754227543275442754527546275472754827549275502755127552275532755427555275562755727558275592756027561275622756327564275652756627567275682756927570275712757227573275742757527576275772757827579275802758127582275832758427585275862758727588275892759027591275922759327594275952759627597275982759927600276012760227603276042760527606276072760827609276102761127612276132761427615276162761727618276192762027621276222762327624276252762627627276282762927630276312763227633276342763527636276372763827639276402764127642276432764427645276462764727648276492765027651276522765327654276552765627657276582765927660276612766227663276642766527666276672766827669276702767127672276732767427675276762767727678276792768027681276822768327684276852768627687276882768927690276912769227693276942769527696276972769827699277002770127702277032770427705277062770727708277092771027711277122771327714277152771627717277182771927720277212772227723277242772527726277272772827729277302773127732277332773427735277362773727738277392774027741277422774327744277452774627747277482774927750277512775227753277542775527756277572775827759277602776127762277632776427765277662776727768277692777027771277722777327774277752777627777277782777927780277812778227783277842778527786277872778827789277902779127792277932779427795277962779727798277992780027801278022780327804278052780627807278082780927810278112781227813278142781527816278172781827819278202782127822278232782427825278262782727828278292783027831278322783327834278352783627837278382783927840278412784227843278442784527846278472784827849278502785127852278532785427855278562785727858278592786027861278622786327864278652786627867278682786927870278712787227873278742787527876278772787827879278802788127882278832788427885278862788727888278892789027891278922789327894278952789627897278982789927900279012790227903279042790527906279072790827909279102791127912279132791427915279162791727918279192792027921279222792327924279252792627927279282792927930279312793227933279342793527936279372793827939279402794127942279432794427945279462794727948279492795027951279522795327954279552795627957279582795927960279612796227963279642796527966279672796827969279702797127972279732797427975279762797727978279792798027981279822798327984279852798627987279882798927990279912799227993279942799527996279972799827999280002800128002280032800428005280062800728008280092801028011280122801328014280152801628017280182801928020280212802228023280242802528026280272802828029280302803128032280332803428035280362803728038280392804028041280422804328044280452804628047280482804928050280512805228053280542805528056280572805828059280602806128062280632806428065280662806728068280692807028071280722807328074280752807628077280782807928080280812808228083280842808528086280872808828089280902809128092280932809428095280962809728098280992810028101281022810328104281052810628107281082810928110281112811228113281142811528116281172811828119281202812128122281232812428125281262812728128281292813028131281322813328134281352813628137281382813928140281412814228143281442814528146281472814828149281502815128152281532815428155281562815728158281592816028161281622816328164281652816628167281682816928170281712817228173281742817528176281772817828179281802818128182281832818428185281862818728188281892819028191281922819328194281952819628197281982819928200282012820228203282042820528206282072820828209282102821128212282132821428215282162821728218282192822028221282222822328224282252822628227282282822928230282312823228233282342823528236282372823828239282402824128242282432824428245282462824728248282492825028251282522825328254282552825628257282582825928260282612826228263282642826528266282672826828269282702827128272282732827428275282762827728278282792828028281282822828328284282852828628287282882828928290282912829228293282942829528296282972829828299283002830128302283032830428305283062830728308283092831028311283122831328314283152831628317283182831928320283212832228323283242832528326283272832828329283302833128332283332833428335283362833728338283392834028341283422834328344283452834628347283482834928350283512835228353283542835528356283572835828359283602836128362283632836428365283662836728368283692837028371283722837328374283752837628377283782837928380283812838228383283842838528386283872838828389283902839128392283932839428395283962839728398283992840028401284022840328404284052840628407284082840928410284112841228413284142841528416284172841828419284202842128422284232842428425284262842728428284292843028431284322843328434284352843628437284382843928440284412844228443284442844528446284472844828449284502845128452284532845428455284562845728458284592846028461284622846328464284652846628467284682846928470284712847228473284742847528476284772847828479284802848128482284832848428485284862848728488284892849028491284922849328494284952849628497284982849928500285012850228503285042850528506285072850828509285102851128512285132851428515285162851728518285192852028521285222852328524285252852628527285282852928530285312853228533285342853528536285372853828539285402854128542285432854428545285462854728548285492855028551285522855328554285552855628557285582855928560285612856228563285642856528566285672856828569285702857128572285732857428575285762857728578285792858028581285822858328584285852858628587285882858928590285912859228593285942859528596285972859828599286002860128602286032860428605286062860728608286092861028611286122861328614286152861628617286182861928620286212862228623286242862528626286272862828629286302863128632286332863428635286362863728638286392864028641286422864328644286452864628647286482864928650286512865228653286542865528656286572865828659286602866128662286632866428665286662866728668286692867028671286722867328674286752867628677286782867928680286812868228683286842868528686286872868828689286902869128692286932869428695286962869728698286992870028701287022870328704287052870628707287082870928710287112871228713287142871528716287172871828719287202872128722287232872428725287262872728728287292873028731287322873328734287352873628737287382873928740287412874228743287442874528746287472874828749287502875128752287532875428755287562875728758287592876028761287622876328764287652876628767287682876928770287712877228773287742877528776287772877828779287802878128782287832878428785287862878728788287892879028791287922879328794287952879628797287982879928800288012880228803288042880528806288072880828809288102881128812288132881428815288162881728818288192882028821288222882328824288252882628827288282882928830288312883228833288342883528836288372883828839288402884128842288432884428845288462884728848288492885028851288522885328854288552885628857288582885928860288612886228863288642886528866288672886828869288702887128872288732887428875288762887728878288792888028881288822888328884288852888628887288882888928890288912889228893288942889528896288972889828899289002890128902289032890428905289062890728908289092891028911289122891328914289152891628917289182891928920289212892228923289242892528926289272892828929289302893128932289332893428935289362893728938289392894028941289422894328944289452894628947289482894928950289512895228953289542895528956289572895828959289602896128962289632896428965289662896728968289692897028971289722897328974289752897628977289782897928980289812898228983289842898528986289872898828989289902899128992289932899428995289962899728998289992900029001290022900329004290052900629007290082900929010290112901229013290142901529016290172901829019290202902129022290232902429025290262902729028290292903029031290322903329034290352903629037290382903929040290412904229043290442904529046290472904829049290502905129052290532905429055290562905729058290592906029061290622906329064290652906629067290682906929070290712907229073290742907529076290772907829079290802908129082290832908429085290862908729088290892909029091290922909329094290952909629097290982909929100291012910229103291042910529106291072910829109291102911129112291132911429115291162911729118291192912029121291222912329124291252912629127291282912929130291312913229133291342913529136291372913829139291402914129142291432914429145291462914729148291492915029151291522915329154291552915629157291582915929160291612916229163291642916529166291672916829169291702917129172291732917429175291762917729178291792918029181291822918329184291852918629187291882918929190291912919229193291942919529196291972919829199292002920129202292032920429205292062920729208292092921029211292122921329214292152921629217292182921929220292212922229223292242922529226292272922829229292302923129232292332923429235292362923729238292392924029241292422924329244292452924629247292482924929250292512925229253292542925529256292572925829259292602926129262292632926429265292662926729268292692927029271292722927329274292752927629277292782927929280292812928229283292842928529286292872928829289292902929129292292932929429295292962929729298292992930029301293022930329304293052930629307293082930929310293112931229313293142931529316293172931829319293202932129322293232932429325293262932729328293292933029331293322933329334293352933629337293382933929340293412934229343293442934529346293472934829349293502935129352293532935429355293562935729358293592936029361293622936329364293652936629367293682936929370293712937229373293742937529376293772937829379293802938129382293832938429385293862938729388293892939029391293922939329394293952939629397293982939929400294012940229403294042940529406294072940829409294102941129412294132941429415294162941729418294192942029421294222942329424294252942629427294282942929430294312943229433294342943529436294372943829439294402944129442294432944429445294462944729448294492945029451294522945329454294552945629457294582945929460294612946229463294642946529466294672946829469294702947129472294732947429475294762947729478294792948029481294822948329484294852948629487294882948929490294912949229493294942949529496294972949829499295002950129502295032950429505295062950729508295092951029511295122951329514295152951629517295182951929520295212952229523295242952529526295272952829529295302953129532295332953429535295362953729538295392954029541295422954329544295452954629547295482954929550295512955229553295542955529556295572955829559295602956129562295632956429565295662956729568295692957029571295722957329574295752957629577295782957929580295812958229583295842958529586295872958829589295902959129592295932959429595295962959729598295992960029601296022960329604296052960629607296082960929610296112961229613296142961529616296172961829619296202962129622296232962429625296262962729628296292963029631296322963329634296352963629637296382963929640296412964229643296442964529646296472964829649296502965129652296532965429655296562965729658296592966029661296622966329664296652966629667296682966929670296712967229673296742967529676296772967829679296802968129682296832968429685296862968729688296892969029691296922969329694296952969629697296982969929700297012970229703297042970529706297072970829709297102971129712297132971429715297162971729718297192972029721297222972329724297252972629727297282972929730297312973229733297342973529736297372973829739297402974129742297432974429745297462974729748297492975029751297522975329754297552975629757297582975929760297612976229763297642976529766297672976829769297702977129772297732977429775297762977729778297792978029781297822978329784297852978629787297882978929790297912979229793297942979529796297972979829799298002980129802298032980429805298062980729808298092981029811298122981329814298152981629817298182981929820298212982229823298242982529826298272982829829298302983129832298332983429835298362983729838298392984029841298422984329844298452984629847298482984929850298512985229853298542985529856298572985829859298602986129862298632986429865298662986729868298692987029871298722987329874298752987629877298782987929880298812988229883298842988529886298872988829889298902989129892298932989429895298962989729898298992990029901299022990329904299052990629907299082990929910299112991229913299142991529916299172991829919299202992129922299232992429925299262992729928299292993029931299322993329934299352993629937299382993929940299412994229943299442994529946299472994829949299502995129952299532995429955299562995729958299592996029961299622996329964299652996629967299682996929970299712997229973299742997529976299772997829979299802998129982299832998429985299862998729988299892999029991299922999329994299952999629997299982999930000300013000230003300043000530006300073000830009300103001130012300133001430015300163001730018300193002030021300223002330024300253002630027300283002930030300313003230033300343003530036300373003830039300403004130042300433004430045300463004730048300493005030051300523005330054300553005630057300583005930060300613006230063300643006530066300673006830069300703007130072300733007430075300763007730078300793008030081300823008330084300853008630087300883008930090300913009230093300943009530096300973009830099301003010130102301033010430105301063010730108301093011030111301123011330114301153011630117301183011930120301213012230123301243012530126301273012830129301303013130132301333013430135301363013730138301393014030141301423014330144301453014630147301483014930150301513015230153301543015530156301573015830159301603016130162301633016430165301663016730168301693017030171301723017330174301753017630177301783017930180301813018230183301843018530186301873018830189301903019130192301933019430195301963019730198301993020030201302023020330204302053020630207302083020930210302113021230213302143021530216302173021830219302203022130222302233022430225302263022730228302293023030231302323023330234302353023630237302383023930240302413024230243302443024530246302473024830249302503025130252302533025430255302563025730258302593026030261302623026330264302653026630267302683026930270302713027230273302743027530276302773027830279302803028130282302833028430285302863028730288302893029030291302923029330294302953029630297302983029930300303013030230303303043030530306303073030830309303103031130312303133031430315303163031730318303193032030321303223032330324303253032630327303283032930330303313033230333303343033530336303373033830339303403034130342303433034430345303463034730348303493035030351303523035330354303553035630357303583035930360303613036230363303643036530366303673036830369303703037130372303733037430375303763037730378303793038030381303823038330384303853038630387303883038930390303913039230393303943039530396303973039830399304003040130402304033040430405304063040730408304093041030411304123041330414304153041630417304183041930420304213042230423304243042530426304273042830429304303043130432304333043430435304363043730438304393044030441304423044330444304453044630447304483044930450304513045230453304543045530456304573045830459304603046130462304633046430465304663046730468304693047030471304723047330474304753047630477304783047930480304813048230483304843048530486304873048830489304903049130492304933049430495304963049730498304993050030501305023050330504305053050630507305083050930510305113051230513305143051530516305173051830519305203052130522305233052430525305263052730528305293053030531305323053330534305353053630537305383053930540305413054230543305443054530546305473054830549305503055130552305533055430555305563055730558305593056030561305623056330564305653056630567305683056930570305713057230573305743057530576305773057830579305803058130582305833058430585305863058730588305893059030591305923059330594305953059630597305983059930600306013060230603306043060530606306073060830609306103061130612306133061430615306163061730618306193062030621306223062330624306253062630627306283062930630306313063230633306343063530636306373063830639306403064130642306433064430645306463064730648306493065030651306523065330654306553065630657306583065930660306613066230663306643066530666306673066830669306703067130672306733067430675306763067730678306793068030681306823068330684306853068630687306883068930690306913069230693306943069530696306973069830699307003070130702307033070430705307063070730708307093071030711307123071330714307153071630717307183071930720307213072230723307243072530726307273072830729307303073130732307333073430735307363073730738307393074030741307423074330744307453074630747307483074930750307513075230753307543075530756307573075830759307603076130762307633076430765307663076730768307693077030771307723077330774307753077630777307783077930780307813078230783307843078530786307873078830789307903079130792307933079430795307963079730798307993080030801308023080330804308053080630807308083080930810308113081230813308143081530816308173081830819308203082130822308233082430825308263082730828308293083030831308323083330834308353083630837308383083930840308413084230843308443084530846308473084830849308503085130852308533085430855308563085730858308593086030861308623086330864308653086630867308683086930870308713087230873308743087530876308773087830879308803088130882308833088430885308863088730888308893089030891308923089330894308953089630897308983089930900309013090230903309043090530906309073090830909309103091130912309133091430915309163091730918309193092030921309223092330924309253092630927309283092930930309313093230933309343093530936309373093830939309403094130942309433094430945309463094730948309493095030951309523095330954309553095630957309583095930960309613096230963309643096530966309673096830969309703097130972309733097430975309763097730978309793098030981309823098330984309853098630987309883098930990309913099230993309943099530996309973099830999310003100131002310033100431005310063100731008310093101031011310123101331014310153101631017310183101931020310213102231023310243102531026310273102831029310303103131032310333103431035310363103731038310393104031041310423104331044310453104631047310483104931050310513105231053310543105531056310573105831059310603106131062310633106431065310663106731068310693107031071310723107331074310753107631077310783107931080310813108231083310843108531086310873108831089310903109131092310933109431095310963109731098310993110031101311023110331104311053110631107311083110931110311113111231113311143111531116311173111831119311203112131122311233112431125311263112731128311293113031131311323113331134311353113631137311383113931140311413114231143311443114531146311473114831149311503115131152311533115431155311563115731158311593116031161311623116331164311653116631167311683116931170311713117231173311743117531176311773117831179311803118131182311833118431185311863118731188311893119031191311923119331194311953119631197311983119931200312013120231203312043120531206312073120831209312103121131212312133121431215312163121731218312193122031221312223122331224312253122631227312283122931230312313123231233312343123531236312373123831239312403124131242312433124431245312463124731248312493125031251312523125331254312553125631257312583125931260312613126231263312643126531266312673126831269312703127131272312733127431275312763127731278312793128031281312823128331284312853128631287312883128931290312913129231293312943129531296312973129831299313003130131302313033130431305313063130731308313093131031311313123131331314313153131631317313183131931320313213132231323313243132531326313273132831329313303133131332313333133431335313363133731338313393134031341313423134331344313453134631347313483134931350313513135231353313543135531356313573135831359313603136131362313633136431365313663136731368313693137031371313723137331374313753137631377313783137931380313813138231383313843138531386313873138831389313903139131392313933139431395313963139731398313993140031401314023140331404314053140631407314083140931410314113141231413314143141531416314173141831419314203142131422314233142431425314263142731428314293143031431314323143331434314353143631437314383143931440314413144231443314443144531446314473144831449314503145131452314533145431455314563145731458314593146031461314623146331464314653146631467314683146931470314713147231473314743147531476314773147831479314803148131482314833148431485314863148731488314893149031491314923149331494314953149631497314983149931500315013150231503315043150531506315073150831509315103151131512315133151431515315163151731518315193152031521315223152331524315253152631527315283152931530315313153231533315343153531536315373153831539315403154131542315433154431545315463154731548315493155031551315523155331554315553155631557315583155931560315613156231563315643156531566315673156831569315703157131572315733157431575315763157731578315793158031581315823158331584315853158631587315883158931590315913159231593315943159531596315973159831599316003160131602316033160431605316063160731608316093161031611316123161331614316153161631617316183161931620316213162231623316243162531626316273162831629316303163131632316333163431635316363163731638316393164031641316423164331644316453164631647316483164931650316513165231653316543165531656316573165831659316603166131662316633166431665316663166731668316693167031671316723167331674316753167631677316783167931680316813168231683316843168531686316873168831689316903169131692316933169431695316963169731698316993170031701317023170331704317053170631707317083170931710317113171231713317143171531716317173171831719317203172131722317233172431725317263172731728317293173031731317323173331734317353173631737317383173931740317413174231743317443174531746317473174831749317503175131752317533175431755317563175731758317593176031761317623176331764317653176631767317683176931770317713177231773317743177531776317773177831779317803178131782317833178431785317863178731788317893179031791317923179331794317953179631797317983179931800318013180231803318043180531806318073180831809318103181131812318133181431815318163181731818318193182031821318223182331824318253182631827318283182931830318313183231833318343183531836318373183831839318403184131842318433184431845318463184731848318493185031851318523185331854318553185631857318583185931860318613186231863318643186531866318673186831869318703187131872318733187431875318763187731878318793188031881318823188331884318853188631887318883188931890318913189231893318943189531896318973189831899319003190131902319033190431905319063190731908319093191031911319123191331914319153191631917319183191931920319213192231923319243192531926319273192831929319303193131932319333193431935319363193731938319393194031941319423194331944319453194631947319483194931950319513195231953319543195531956319573195831959319603196131962319633196431965319663196731968319693197031971319723197331974319753197631977319783197931980319813198231983319843198531986319873198831989319903199131992319933199431995319963199731998319993200032001320023200332004320053200632007320083200932010320113201232013320143201532016320173201832019320203202132022320233202432025320263202732028320293203032031320323203332034320353203632037320383203932040320413204232043320443204532046320473204832049320503205132052320533205432055320563205732058320593206032061320623206332064320653206632067320683206932070320713207232073320743207532076320773207832079320803208132082320833208432085320863208732088320893209032091320923209332094320953209632097320983209932100321013210232103321043210532106321073210832109321103211132112321133211432115321163211732118321193212032121321223212332124321253212632127321283212932130321313213232133321343213532136321373213832139321403214132142321433214432145321463214732148321493215032151321523215332154321553215632157321583215932160321613216232163321643216532166321673216832169321703217132172321733217432175321763217732178321793218032181321823218332184321853218632187321883218932190321913219232193321943219532196321973219832199322003220132202322033220432205322063220732208322093221032211322123221332214322153221632217322183221932220322213222232223322243222532226322273222832229322303223132232322333223432235322363223732238322393224032241322423224332244322453224632247322483224932250322513225232253322543225532256322573225832259322603226132262322633226432265322663226732268322693227032271322723227332274322753227632277322783227932280322813228232283322843228532286322873228832289322903229132292322933229432295322963229732298322993230032301323023230332304323053230632307323083230932310323113231232313323143231532316323173231832319323203232132322323233232432325323263232732328323293233032331323323233332334323353233632337323383233932340323413234232343323443234532346323473234832349323503235132352323533235432355323563235732358323593236032361323623236332364323653236632367323683236932370323713237232373323743237532376323773237832379323803238132382323833238432385323863238732388323893239032391323923239332394323953239632397323983239932400324013240232403324043240532406324073240832409324103241132412324133241432415324163241732418324193242032421324223242332424324253242632427324283242932430324313243232433324343243532436324373243832439324403244132442324433244432445324463244732448324493245032451324523245332454324553245632457324583245932460324613246232463324643246532466324673246832469324703247132472324733247432475324763247732478324793248032481324823248332484324853248632487324883248932490324913249232493324943249532496324973249832499325003250132502325033250432505325063250732508325093251032511325123251332514325153251632517325183251932520325213252232523325243252532526325273252832529325303253132532325333253432535325363253732538325393254032541325423254332544325453254632547325483254932550325513255232553325543255532556325573255832559325603256132562325633256432565325663256732568325693257032571325723257332574325753257632577325783257932580325813258232583325843258532586325873258832589325903259132592325933259432595325963259732598325993260032601326023260332604326053260632607326083260932610326113261232613326143261532616326173261832619326203262132622326233262432625326263262732628326293263032631326323263332634326353263632637326383263932640326413264232643326443264532646326473264832649326503265132652326533265432655326563265732658326593266032661326623266332664326653266632667326683266932670326713267232673326743267532676326773267832679326803268132682326833268432685326863268732688326893269032691326923269332694326953269632697326983269932700327013270232703327043270532706327073270832709327103271132712327133271432715327163271732718327193272032721327223272332724327253272632727327283272932730327313273232733327343273532736327373273832739327403274132742327433274432745327463274732748327493275032751327523275332754327553275632757327583275932760327613276232763327643276532766327673276832769327703277132772327733277432775327763277732778327793278032781327823278332784327853278632787327883278932790327913279232793327943279532796327973279832799328003280132802328033280432805328063280732808328093281032811328123281332814328153281632817328183281932820328213282232823328243282532826328273282832829328303283132832328333283432835328363283732838328393284032841328423284332844328453284632847328483284932850328513285232853328543285532856328573285832859328603286132862328633286432865328663286732868328693287032871328723287332874328753287632877328783287932880328813288232883328843288532886328873288832889328903289132892328933289432895328963289732898328993290032901329023290332904329053290632907329083290932910329113291232913329143291532916329173291832919329203292132922329233292432925329263292732928329293293032931329323293332934329353293632937329383293932940329413294232943329443294532946329473294832949329503295132952329533295432955329563295732958329593296032961329623296332964329653296632967329683296932970329713297232973329743297532976329773297832979329803298132982329833298432985329863298732988329893299032991329923299332994329953299632997329983299933000330013300233003330043300533006330073300833009330103301133012330133301433015330163301733018330193302033021330223302333024330253302633027330283302933030330313303233033330343303533036330373303833039330403304133042330433304433045330463304733048330493305033051330523305333054330553305633057330583305933060330613306233063330643306533066330673306833069330703307133072330733307433075330763307733078330793308033081330823308333084330853308633087330883308933090330913309233093330943309533096330973309833099331003310133102331033310433105331063310733108331093311033111331123311333114331153311633117331183311933120331213312233123331243312533126331273312833129331303313133132331333313433135331363313733138331393314033141331423314333144331453314633147331483314933150331513315233153331543315533156331573315833159331603316133162331633316433165331663316733168331693317033171331723317333174331753317633177331783317933180331813318233183331843318533186331873318833189331903319133192331933319433195331963319733198331993320033201332023320333204332053320633207332083320933210332113321233213332143321533216332173321833219332203322133222332233322433225332263322733228332293323033231332323323333234332353323633237332383323933240332413324233243332443324533246332473324833249332503325133252332533325433255332563325733258332593326033261332623326333264332653326633267332683326933270332713327233273332743327533276332773327833279332803328133282332833328433285332863328733288332893329033291332923329333294332953329633297332983329933300333013330233303333043330533306333073330833309333103331133312333133331433315333163331733318333193332033321333223332333324333253332633327333283332933330333313333233333333343333533336333373333833339333403334133342333433334433345333463334733348333493335033351333523335333354333553335633357333583335933360333613336233363333643336533366333673336833369333703337133372333733337433375333763337733378333793338033381333823338333384333853338633387333883338933390333913339233393333943339533396333973339833399334003340133402334033340433405334063340733408334093341033411334123341333414334153341633417334183341933420334213342233423334243342533426334273342833429334303343133432334333343433435334363343733438334393344033441334423344333444334453344633447334483344933450334513345233453334543345533456334573345833459334603346133462334633346433465334663346733468334693347033471334723347333474334753347633477334783347933480334813348233483334843348533486334873348833489334903349133492334933349433495334963349733498334993350033501335023350333504335053350633507335083350933510335113351233513335143351533516335173351833519335203352133522335233352433525335263352733528335293353033531335323353333534335353353633537335383353933540335413354233543335443354533546335473354833549335503355133552335533355433555335563355733558335593356033561335623356333564335653356633567335683356933570335713357233573335743357533576335773357833579335803358133582335833358433585335863358733588335893359033591335923359333594335953359633597335983359933600336013360233603336043360533606336073360833609336103361133612336133361433615336163361733618336193362033621336223362333624336253362633627336283362933630336313363233633336343363533636336373363833639336403364133642336433364433645336463364733648336493365033651336523365333654336553365633657336583365933660336613366233663336643366533666336673366833669336703367133672336733367433675336763367733678336793368033681336823368333684336853368633687336883368933690336913369233693336943369533696336973369833699337003370133702337033370433705337063370733708337093371033711337123371333714337153371633717337183371933720337213372233723337243372533726337273372833729337303373133732337333373433735337363373733738337393374033741337423374333744337453374633747337483374933750337513375233753337543375533756337573375833759337603376133762337633376433765337663376733768337693377033771337723377333774337753377633777337783377933780337813378233783337843378533786337873378833789337903379133792337933379433795337963379733798337993380033801338023380333804338053380633807338083380933810338113381233813338143381533816338173381833819338203382133822338233382433825338263382733828338293383033831338323383333834338353383633837338383383933840338413384233843338443384533846338473384833849338503385133852338533385433855338563385733858338593386033861338623386333864338653386633867338683386933870338713387233873338743387533876338773387833879338803388133882338833388433885338863388733888338893389033891338923389333894338953389633897338983389933900339013390233903339043390533906339073390833909339103391133912339133391433915339163391733918339193392033921339223392333924339253392633927339283392933930339313393233933339343393533936339373393833939339403394133942339433394433945339463394733948339493395033951339523395333954339553395633957339583395933960339613396233963339643396533966339673396833969339703397133972339733397433975339763397733978339793398033981339823398333984339853398633987339883398933990339913399233993339943399533996339973399833999340003400134002340033400434005340063400734008340093401034011340123401334014340153401634017340183401934020340213402234023340243402534026340273402834029340303403134032340333403434035340363403734038340393404034041340423404334044340453404634047340483404934050340513405234053340543405534056340573405834059340603406134062340633406434065340663406734068340693407034071340723407334074340753407634077340783407934080340813408234083340843408534086340873408834089340903409134092340933409434095340963409734098340993410034101341023410334104341053410634107341083410934110341113411234113341143411534116341173411834119341203412134122341233412434125341263412734128341293413034131341323413334134341353413634137341383413934140341413414234143341443414534146341473414834149341503415134152341533415434155341563415734158341593416034161341623416334164341653416634167341683416934170341713417234173341743417534176341773417834179341803418134182341833418434185341863418734188341893419034191341923419334194341953419634197341983419934200342013420234203342043420534206342073420834209342103421134212342133421434215342163421734218342193422034221342223422334224342253422634227342283422934230342313423234233342343423534236342373423834239342403424134242342433424434245342463424734248342493425034251342523425334254342553425634257342583425934260342613426234263342643426534266342673426834269342703427134272342733427434275342763427734278342793428034281342823428334284342853428634287342883428934290342913429234293342943429534296342973429834299343003430134302343033430434305343063430734308343093431034311343123431334314343153431634317343183431934320343213432234323343243432534326343273432834329343303433134332343333433434335343363433734338343393434034341343423434334344343453434634347343483434934350343513435234353343543435534356343573435834359343603436134362343633436434365343663436734368343693437034371343723437334374343753437634377343783437934380343813438234383343843438534386343873438834389343903439134392343933439434395343963439734398343993440034401344023440334404344053440634407344083440934410344113441234413344143441534416344173441834419344203442134422344233442434425344263442734428344293443034431344323443334434344353443634437344383443934440344413444234443344443444534446344473444834449344503445134452344533445434455344563445734458344593446034461344623446334464344653446634467344683446934470344713447234473344743447534476344773447834479344803448134482344833448434485344863448734488344893449034491344923449334494344953449634497344983449934500345013450234503345043450534506345073450834509345103451134512345133451434515345163451734518345193452034521345223452334524345253452634527345283452934530345313453234533345343453534536345373453834539345403454134542345433454434545345463454734548345493455034551345523455334554345553455634557345583455934560345613456234563345643456534566345673456834569345703457134572345733457434575345763457734578345793458034581345823458334584345853458634587345883458934590345913459234593345943459534596345973459834599346003460134602346033460434605346063460734608346093461034611346123461334614346153461634617346183461934620346213462234623346243462534626346273462834629346303463134632346333463434635346363463734638346393464034641346423464334644346453464634647346483464934650346513465234653346543465534656346573465834659346603466134662346633466434665346663466734668346693467034671346723467334674346753467634677346783467934680346813468234683346843468534686346873468834689346903469134692346933469434695346963469734698346993470034701347023470334704347053470634707347083470934710347113471234713347143471534716347173471834719347203472134722347233472434725347263472734728347293473034731347323473334734347353473634737347383473934740347413474234743347443474534746347473474834749347503475134752347533475434755347563475734758347593476034761347623476334764347653476634767347683476934770347713477234773347743477534776347773477834779347803478134782347833478434785347863478734788347893479034791347923479334794347953479634797347983479934800348013480234803348043480534806348073480834809348103481134812348133481434815348163481734818348193482034821348223482334824348253482634827348283482934830348313483234833348343483534836348373483834839348403484134842348433484434845348463484734848348493485034851348523485334854348553485634857348583485934860348613486234863348643486534866348673486834869348703487134872348733487434875348763487734878348793488034881348823488334884348853488634887348883488934890348913489234893348943489534896348973489834899349003490134902349033490434905349063490734908349093491034911349123491334914349153491634917349183491934920349213492234923349243492534926349273492834929349303493134932349333493434935349363493734938349393494034941349423494334944349453494634947349483494934950349513495234953349543495534956349573495834959349603496134962349633496434965349663496734968349693497034971349723497334974349753497634977349783497934980349813498234983349843498534986349873498834989349903499134992349933499434995349963499734998349993500035001350023500335004350053500635007350083500935010350113501235013350143501535016350173501835019350203502135022350233502435025350263502735028350293503035031350323503335034350353503635037350383503935040350413504235043350443504535046350473504835049350503505135052350533505435055350563505735058350593506035061350623506335064350653506635067350683506935070350713507235073350743507535076350773507835079350803508135082350833508435085350863508735088350893509035091350923509335094350953509635097350983509935100351013510235103351043510535106351073510835109351103511135112351133511435115351163511735118351193512035121351223512335124351253512635127351283512935130351313513235133351343513535136351373513835139351403514135142351433514435145351463514735148351493515035151351523515335154351553515635157351583515935160351613516235163351643516535166351673516835169351703517135172351733517435175351763517735178351793518035181351823518335184351853518635187351883518935190351913519235193351943519535196351973519835199352003520135202352033520435205352063520735208352093521035211352123521335214352153521635217352183521935220352213522235223352243522535226352273522835229352303523135232352333523435235352363523735238352393524035241352423524335244352453524635247352483524935250352513525235253352543525535256352573525835259352603526135262352633526435265352663526735268352693527035271352723527335274352753527635277352783527935280352813528235283352843528535286352873528835289352903529135292352933529435295352963529735298352993530035301353023530335304353053530635307353083530935310353113531235313353143531535316353173531835319353203532135322353233532435325353263532735328353293533035331353323533335334353353533635337353383533935340353413534235343353443534535346353473534835349353503535135352353533535435355353563535735358353593536035361353623536335364353653536635367353683536935370353713537235373353743537535376353773537835379353803538135382353833538435385353863538735388353893539035391353923539335394353953539635397353983539935400354013540235403354043540535406354073540835409354103541135412354133541435415354163541735418354193542035421354223542335424354253542635427354283542935430354313543235433354343543535436354373543835439354403544135442354433544435445354463544735448354493545035451354523545335454354553545635457354583545935460354613546235463354643546535466354673546835469354703547135472354733547435475354763547735478354793548035481354823548335484354853548635487354883548935490354913549235493354943549535496354973549835499355003550135502355033550435505355063550735508355093551035511355123551335514355153551635517355183551935520355213552235523355243552535526355273552835529355303553135532355333553435535355363553735538355393554035541355423554335544355453554635547355483554935550355513555235553355543555535556355573555835559355603556135562355633556435565355663556735568355693557035571355723557335574355753557635577355783557935580355813558235583355843558535586355873558835589355903559135592355933559435595355963559735598355993560035601356023560335604356053560635607356083560935610356113561235613356143561535616356173561835619356203562135622356233562435625356263562735628356293563035631356323563335634356353563635637356383563935640356413564235643356443564535646356473564835649356503565135652356533565435655356563565735658356593566035661356623566335664356653566635667356683566935670356713567235673356743567535676356773567835679356803568135682356833568435685356863568735688356893569035691356923569335694356953569635697356983569935700357013570235703357043570535706357073570835709357103571135712357133571435715357163571735718357193572035721357223572335724357253572635727357283572935730357313573235733357343573535736357373573835739357403574135742357433574435745357463574735748357493575035751357523575335754357553575635757357583575935760357613576235763357643576535766357673576835769357703577135772357733577435775357763577735778357793578035781357823578335784357853578635787357883578935790357913579235793357943579535796357973579835799358003580135802358033580435805358063580735808358093581035811358123581335814358153581635817358183581935820358213582235823358243582535826358273582835829358303583135832358333583435835358363583735838358393584035841358423584335844358453584635847358483584935850358513585235853358543585535856358573585835859358603586135862358633586435865358663586735868358693587035871358723587335874358753587635877358783587935880358813588235883358843588535886358873588835889358903589135892358933589435895358963589735898358993590035901359023590335904359053590635907359083590935910359113591235913359143591535916359173591835919359203592135922359233592435925359263592735928359293593035931359323593335934359353593635937359383593935940359413594235943359443594535946359473594835949359503595135952359533595435955359563595735958359593596035961359623596335964359653596635967359683596935970359713597235973359743597535976359773597835979359803598135982359833598435985359863598735988359893599035991359923599335994359953599635997359983599936000360013600236003360043600536006360073600836009360103601136012360133601436015360163601736018360193602036021360223602336024360253602636027360283602936030360313603236033360343603536036360373603836039360403604136042360433604436045360463604736048360493605036051360523605336054360553605636057360583605936060360613606236063360643606536066360673606836069360703607136072360733607436075360763607736078360793608036081360823608336084360853608636087360883608936090360913609236093360943609536096360973609836099361003610136102361033610436105361063610736108361093611036111361123611336114361153611636117361183611936120361213612236123361243612536126361273612836129361303613136132361333613436135361363613736138361393614036141361423614336144361453614636147361483614936150361513615236153361543615536156361573615836159361603616136162361633616436165361663616736168361693617036171361723617336174361753617636177361783617936180361813618236183361843618536186361873618836189361903619136192361933619436195361963619736198361993620036201362023620336204362053620636207362083620936210362113621236213362143621536216362173621836219362203622136222362233622436225362263622736228362293623036231362323623336234362353623636237362383623936240362413624236243362443624536246362473624836249362503625136252362533625436255362563625736258362593626036261362623626336264362653626636267362683626936270362713627236273362743627536276362773627836279362803628136282362833628436285362863628736288362893629036291362923629336294362953629636297362983629936300363013630236303363043630536306363073630836309363103631136312363133631436315363163631736318363193632036321363223632336324363253632636327363283632936330363313633236333363343633536336363373633836339363403634136342363433634436345363463634736348363493635036351363523635336354363553635636357363583635936360363613636236363363643636536366363673636836369363703637136372363733637436375363763637736378363793638036381363823638336384363853638636387363883638936390363913639236393363943639536396363973639836399364003640136402364033640436405364063640736408364093641036411364123641336414364153641636417364183641936420364213642236423364243642536426364273642836429364303643136432364333643436435364363643736438364393644036441364423644336444364453644636447364483644936450364513645236453364543645536456364573645836459364603646136462364633646436465364663646736468364693647036471364723647336474364753647636477364783647936480364813648236483364843648536486364873648836489364903649136492364933649436495364963649736498364993650036501365023650336504365053650636507365083650936510365113651236513365143651536516365173651836519365203652136522365233652436525365263652736528365293653036531365323653336534365353653636537365383653936540365413654236543365443654536546365473654836549365503655136552365533655436555365563655736558365593656036561365623656336564365653656636567365683656936570365713657236573365743657536576365773657836579365803658136582365833658436585365863658736588365893659036591365923659336594365953659636597365983659936600366013660236603366043660536606366073660836609366103661136612366133661436615366163661736618366193662036621366223662336624366253662636627366283662936630366313663236633366343663536636366373663836639366403664136642366433664436645366463664736648366493665036651366523665336654366553665636657366583665936660366613666236663366643666536666366673666836669366703667136672366733667436675366763667736678366793668036681366823668336684366853668636687366883668936690366913669236693366943669536696366973669836699367003670136702367033670436705367063670736708367093671036711367123671336714367153671636717367183671936720367213672236723367243672536726367273672836729367303673136732367333673436735367363673736738367393674036741367423674336744367453674636747367483674936750367513675236753367543675536756367573675836759367603676136762367633676436765367663676736768367693677036771367723677336774367753677636777367783677936780367813678236783367843678536786367873678836789367903679136792367933679436795367963679736798367993680036801368023680336804368053680636807368083680936810368113681236813368143681536816368173681836819368203682136822368233682436825368263682736828368293683036831368323683336834368353683636837368383683936840368413684236843368443684536846368473684836849368503685136852368533685436855368563685736858368593686036861368623686336864368653686636867368683686936870368713687236873368743687536876368773687836879368803688136882368833688436885368863688736888368893689036891368923689336894368953689636897368983689936900369013690236903369043690536906369073690836909369103691136912369133691436915369163691736918369193692036921369223692336924369253692636927369283692936930369313693236933369343693536936369373693836939369403694136942369433694436945369463694736948369493695036951369523695336954369553695636957369583695936960369613696236963369643696536966369673696836969369703697136972369733697436975369763697736978369793698036981369823698336984369853698636987369883698936990369913699236993369943699536996369973699836999370003700137002370033700437005370063700737008370093701037011370123701337014370153701637017370183701937020370213702237023370243702537026370273702837029370303703137032370333703437035370363703737038370393704037041370423704337044370453704637047370483704937050370513705237053370543705537056370573705837059370603706137062370633706437065370663706737068370693707037071370723707337074370753707637077370783707937080370813708237083370843708537086370873708837089370903709137092370933709437095370963709737098370993710037101371023710337104371053710637107371083710937110371113711237113371143711537116371173711837119371203712137122371233712437125371263712737128371293713037131371323713337134371353713637137371383713937140371413714237143371443714537146371473714837149371503715137152371533715437155371563715737158371593716037161371623716337164371653716637167371683716937170371713717237173371743717537176371773717837179371803718137182371833718437185371863718737188371893719037191371923719337194371953719637197371983719937200372013720237203372043720537206372073720837209372103721137212372133721437215372163721737218372193722037221372223722337224372253722637227372283722937230372313723237233372343723537236372373723837239372403724137242372433724437245372463724737248372493725037251372523725337254372553725637257372583725937260372613726237263372643726537266372673726837269372703727137272372733727437275372763727737278372793728037281372823728337284372853728637287372883728937290372913729237293372943729537296372973729837299373003730137302373033730437305373063730737308373093731037311373123731337314373153731637317373183731937320373213732237323373243732537326373273732837329373303733137332373333733437335373363733737338373393734037341373423734337344373453734637347373483734937350373513735237353373543735537356373573735837359373603736137362373633736437365373663736737368373693737037371373723737337374373753737637377373783737937380373813738237383373843738537386373873738837389373903739137392373933739437395373963739737398373993740037401374023740337404374053740637407374083740937410374113741237413374143741537416374173741837419374203742137422374233742437425374263742737428374293743037431374323743337434374353743637437374383743937440374413744237443374443744537446374473744837449374503745137452374533745437455374563745737458374593746037461374623746337464374653746637467374683746937470374713747237473374743747537476374773747837479374803748137482374833748437485374863748737488374893749037491374923749337494374953749637497374983749937500375013750237503375043750537506375073750837509375103751137512375133751437515375163751737518375193752037521375223752337524375253752637527375283752937530375313753237533375343753537536375373753837539375403754137542375433754437545375463754737548375493755037551375523755337554375553755637557375583755937560375613756237563375643756537566375673756837569375703757137572375733757437575375763757737578375793758037581375823758337584375853758637587375883758937590375913759237593375943759537596375973759837599376003760137602376033760437605376063760737608376093761037611376123761337614376153761637617376183761937620376213762237623376243762537626376273762837629376303763137632376333763437635376363763737638376393764037641376423764337644376453764637647376483764937650376513765237653376543765537656376573765837659376603766137662376633766437665376663766737668376693767037671376723767337674376753767637677376783767937680376813768237683376843768537686376873768837689376903769137692376933769437695376963769737698376993770037701377023770337704377053770637707377083770937710377113771237713377143771537716377173771837719377203772137722377233772437725377263772737728377293773037731377323773337734377353773637737377383773937740377413774237743377443774537746377473774837749377503775137752377533775437755377563775737758377593776037761377623776337764377653776637767377683776937770377713777237773377743777537776377773777837779377803778137782377833778437785377863778737788377893779037791377923779337794377953779637797377983779937800378013780237803378043780537806378073780837809378103781137812378133781437815378163781737818378193782037821378223782337824378253782637827378283782937830378313783237833378343783537836378373783837839378403784137842378433784437845378463784737848378493785037851378523785337854378553785637857378583785937860378613786237863378643786537866378673786837869378703787137872378733787437875378763787737878378793788037881378823788337884378853788637887378883788937890378913789237893378943789537896378973789837899379003790137902379033790437905379063790737908379093791037911379123791337914379153791637917379183791937920379213792237923379243792537926379273792837929379303793137932379333793437935379363793737938379393794037941379423794337944379453794637947379483794937950379513795237953379543795537956379573795837959379603796137962379633796437965379663796737968379693797037971379723797337974379753797637977379783797937980379813798237983379843798537986379873798837989379903799137992379933799437995379963799737998379993800038001380023800338004380053800638007380083800938010380113801238013380143801538016380173801838019380203802138022380233802438025380263802738028380293803038031380323803338034380353803638037380383803938040380413804238043380443804538046380473804838049380503805138052380533805438055380563805738058380593806038061380623806338064380653806638067380683806938070380713807238073380743807538076380773807838079380803808138082380833808438085380863808738088380893809038091380923809338094380953809638097380983809938100381013810238103381043810538106381073810838109381103811138112381133811438115381163811738118381193812038121381223812338124381253812638127381283812938130381313813238133381343813538136381373813838139381403814138142381433814438145381463814738148381493815038151381523815338154381553815638157381583815938160381613816238163381643816538166381673816838169381703817138172381733817438175381763817738178381793818038181381823818338184381853818638187381883818938190381913819238193381943819538196381973819838199382003820138202382033820438205382063820738208382093821038211382123821338214382153821638217382183821938220382213822238223382243822538226382273822838229382303823138232382333823438235382363823738238382393824038241382423824338244382453824638247382483824938250382513825238253382543825538256382573825838259382603826138262382633826438265382663826738268382693827038271382723827338274382753827638277382783827938280382813828238283382843828538286382873828838289382903829138292382933829438295382963829738298382993830038301383023830338304383053830638307383083830938310383113831238313383143831538316383173831838319383203832138322383233832438325383263832738328383293833038331383323833338334383353833638337383383833938340383413834238343383443834538346383473834838349383503835138352383533835438355383563835738358383593836038361383623836338364383653836638367383683836938370383713837238373383743837538376383773837838379383803838138382383833838438385383863838738388383893839038391383923839338394383953839638397383983839938400384013840238403384043840538406384073840838409384103841138412384133841438415384163841738418384193842038421384223842338424384253842638427384283842938430384313843238433384343843538436384373843838439384403844138442384433844438445384463844738448384493845038451384523845338454384553845638457384583845938460384613846238463384643846538466384673846838469384703847138472384733847438475384763847738478384793848038481384823848338484384853848638487384883848938490384913849238493384943849538496384973849838499385003850138502385033850438505385063850738508385093851038511385123851338514385153851638517385183851938520385213852238523385243852538526385273852838529385303853138532385333853438535385363853738538385393854038541385423854338544385453854638547385483854938550385513855238553385543855538556385573855838559385603856138562385633856438565385663856738568385693857038571385723857338574385753857638577385783857938580385813858238583385843858538586385873858838589385903859138592385933859438595385963859738598385993860038601386023860338604386053860638607386083860938610386113861238613386143861538616386173861838619386203862138622386233862438625386263862738628386293863038631386323863338634386353863638637386383863938640386413864238643386443864538646386473864838649386503865138652386533865438655386563865738658386593866038661386623866338664386653866638667386683866938670386713867238673386743867538676386773867838679386803868138682386833868438685386863868738688386893869038691386923869338694386953869638697386983869938700387013870238703387043870538706387073870838709387103871138712387133871438715387163871738718387193872038721387223872338724387253872638727387283872938730387313873238733387343873538736387373873838739387403874138742387433874438745387463874738748387493875038751387523875338754387553875638757387583875938760387613876238763387643876538766387673876838769387703877138772387733877438775 |
- /**
- * @class Ext.draw.ContainerBase
- * @private
- */
- Ext.define('Ext.draw.ContainerBase', {
- extend: 'Ext.panel.Panel',
- requires: [
- 'Ext.window.Window'
- ],
- /**
- * @cfg {String} previewTitleText The text to place in Preview Chart window title.
- */
- previewTitleText: 'Chart Preview',
- /**
- * @cfg {String} previewAltText The text to place in the Preview image alt attribute.
- */
- previewAltText: 'Chart preview',
- layout: 'container',
- // Adds a listener to this draw container's element. If the element does not yet exist
- // addition of the listener will be deferred until onRender. Useful when listeners
- // need to be attached during initConfig.
- addElementListener: function() {
- var me = this,
- args = arguments;
- if (me.rendered) {
- me.el.on.apply(me.el, args);
- } else {
- me.on('render', function() {
- me.el.on.apply(me.el, args);
- });
- }
- },
- removeElementListener: function() {
- var me = this;
- if (me.rendered) {
- me.el.un.apply(me.el, arguments);
- }
- },
- afterRender: function() {
- this.callParent(arguments);
- this.initAnimator();
- },
- getItems: function() {
- var me = this,
- items = me.items;
- if (!items || !items.isMixedCollection) {
- // getItems may be called before initItems has run and created the items
- // collection, so we have to create it here just in case (this can happen
- // if getItems is called during initConfig)
- me.initItems();
- }
- return me.items;
- },
- onRender: function() {
- this.callParent(arguments);
- this.element = this.el;
- this.bodyElement = this.body;
- },
- setItems: function(items) {
- this.items = items;
- return items;
- },
- setSurfaceSize: function(width, height) {
- this.resizeHandler({
- width: width,
- height: height
- });
- this.renderFrame();
- },
- onResize: function(width, height, oldWidth, oldHeight) {
- this.handleResize({
- width: width,
- height: height
- }, !this.size);
- },
- // First resize should be performed without any delay.
- preview: function(image) {
- var items;
- if (Ext.isIE8) {
- return false;
- }
- image = image || this.getImage();
- if (image.type === 'svg-markup') {
- items = {
- xtype: 'container',
- html: image.data
- };
- } else {
- items = {
- xtype: 'image',
- mode: 'img',
- cls: Ext.baseCSSPrefix + 'chart-image',
- alt: this.previewAltText,
- src: image.data,
- listeners: {
- afterrender: function() {
- var me = this,
- img = me.imgEl.dom,
- // eslint-disable-next-line dot-notation
- ratio = image.type === 'svg' ? 1 : (window['devicePixelRatio'] || 1),
- size;
- if (!img.naturalWidth || !img.naturalHeight) {
- img.onload = function() {
- var width = img.naturalWidth,
- height = img.naturalHeight;
- me.setWidth(Math.floor(width / ratio));
- me.setHeight(Math.floor(height / ratio));
- };
- } else {
- size = me.getSize();
- me.setWidth(Math.floor(size.width / ratio));
- me.setHeight(Math.floor(size.height / ratio));
- }
- }
- }
- };
- }
- new Ext.window.Window({
- title: this.previewTitleText,
- closable: true,
- renderTo: Ext.getBody(),
- autoShow: true,
- maximizeable: true,
- maximized: true,
- border: true,
- layout: {
- type: 'hbox',
- pack: 'center',
- align: 'middle'
- },
- items: {
- xtype: 'container',
- items: items
- }
- });
- },
- privates: {
- getTargetEl: function() {
- return this.bodyElement;
- },
- reattachToBody: function() {
- // This is to ensure charts work properly as grid column widgets.
- var me = this;
- if (me.pendingDetachSize) {
- me.handleResize();
- }
- me.pendingDetachSize = false;
- me.callParent();
- }
- }
- });
- /**
- * @private
- * @class Ext.draw.SurfaceBase (Classic)
- */
- Ext.define('Ext.draw.SurfaceBase', {
- extend: 'Ext.Widget',
- getOwnerBody: function() {
- return this.ownerCt.body;
- }
- });
- /**
- * @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-component'
- });
- /**
- * 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 classic toolkit.
- */
- Ext.define('Ext.chart.legend.LegendBase', {
- extend: 'Ext.view.View',
- config: {
- /* eslint-disable indent, max-len */
- tpl: [
- '<div class="',
- Ext.baseCSSPrefix,
- 'legend-inner">',
- // for IE8 vertical centering
- '<div class="',
- Ext.baseCSSPrefix,
- 'legend-container">',
- '<tpl for=".">',
- '<div class="',
- Ext.baseCSSPrefix,
- 'legend-item">',
- '<span ',
- 'class="',
- Ext.baseCSSPrefix,
- 'legend-item-marker {[ values.disabled ? Ext.baseCSSPrefix + \'legend-item-inactive\' : \'\' ]}" ',
- 'style="background:{mark};">',
- '</span>{name}',
- '</div>',
- '</tpl>',
- '</div>',
- '</div>'
- ],
- /* eslint-enable indent,max-len */
- // element that contains rows (see AbstractView)
- nodeContainerSelector: 'div.' + Ext.baseCSSPrefix + 'legend-inner',
- // row element (see AbstractView)
- itemSelector: 'div.' + Ext.baseCSSPrefix + 'legend-item',
- /**
- * @cfg {String} docked
- * The dock position of this component in its container. Can be `left`, `top`, `right`,
- * or `bottom`.
- */
- docked: 'bottom'
- },
- /**
- * @cfg dock
- * @hide
- */
- setDocked: function(docked) {
- // If we call the method 'updateDocked' instead of 'setDocked', the following error
- // is thrown: "Ext.Component#setDocked" is deprecated. Please use "setDock" instead.
- var me = this,
- panel = me.ownerCt;
- me.docked = me.dock = docked;
- switch (docked) {
- case 'top':
- case 'bottom':
- me.addCls(me.horizontalCls);
- me.removeCls(me.verticalCls);
- break;
- case 'left':
- case 'right':
- me.addCls(me.verticalCls);
- me.removeCls(me.horizontalCls);
- break;
- }
- if (panel) {
- panel.setDock(docked);
- }
- },
- setStore: function(store) {
- this.bindStore(store);
- },
- clearViewEl: function() {
- this.callParent(arguments);
- // The legend-container div is not removed automatically.
- Ext.removeNode(this.getNodeContainer());
- },
- onItemClick: function(record, item, index, e) {
- this.callParent(arguments);
- this.toggleItem(index);
- }
- });
- /**
- * 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;
- }
- });
- /**
- * @class Ext.chart.overrides.AbstractChart
- */
- Ext.define('Ext.chart.overrides.AbstractChart', {
- override: 'Ext.chart.AbstractChart',
- updateLegend: function(legend, oldLegend) {
- this.callParent([
- legend,
- oldLegend
- ]);
- if (legend && legend.isDomLegend) {
- this.addDocked(legend);
- }
- },
- performLayout: function() {
- if (this.isVisible(true)) {
- return this.callParent();
- }
- this.cancelChartLayout();
- return false;
- },
- afterComponentLayout: function(width, height, oldWidth, oldHeight) {
- this.callParent([
- width,
- height,
- oldWidth,
- oldHeight
- ]);
- if (!this.hasFirstLayout) {
- this.scheduleLayout();
- }
- },
- allowSchedule: function() {
- return this.rendered;
- },
- 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.panel.Panel'
- });
- /**
- *
- */
- Ext.define('Ext.chart.navigator.NavigatorBase', {
- extend: 'Ext.chart.CartesianChart',
- onRender: function() {
- this.callParent();
- this.setupEvents();
- },
- // Note: 'applyDock' and 'updateDock' won't ever be called in Classic.
- // See the Classic Component's 'setDock' method, which is overridden here.
- setDocked: function(docked) {
- var me = this,
- ownerCt = me.getNavigatorContainer();
- if (!(docked === 'top' || docked === 'bottom')) {
- Ext.raise("Can only dock to 'top' or 'bottom'.");
- }
- if (docked !== me.dock) {
- if (ownerCt && ownerCt.moveDocked) {
- ownerCt.moveDocked(me, docked);
- } else {
- me.dock = docked;
- }
- }
- return me;
- },
- getDocked: function() {
- return this.dock;
- }
- });
- /**
- * 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} extjsGestures
- * Defines the gestures that should trigger the item info panel to be displayed in ExtJS.
- */
- extjsGestures: {
- 'start': {
- event: 'click',
- handler: 'onInfoGesture'
- },
- 'move': {
- event: 'mousemove',
- handler: 'onInfoGesture'
- },
- 'end': {
- event: 'mouseleave',
- handler: 'onInfoGesture'
- }
- }
- },
- // TODO:ps The trigger above should be 'itemclick', not 'click'.
- item: null,
- onInfoGesture: function(e, element) {
- var me = this,
- item = me.getItemForEvent(e),
- tooltip = item && item.series.tooltip;
- if (tooltip) {
- tooltip.onMouseMove.call(tooltip, e);
- }
- if (item !== me.item) {
- if (item) {
- item.series.showTip(item);
- } else {
- me.item.series.hideTip(me.item);
- }
- me.item = item;
- }
- return false;
- }
- });
|