12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737207382073920740207412074220743207442074520746207472074820749207502075120752207532075420755207562075720758207592076020761207622076320764207652076620767207682076920770207712077220773207742077520776207772077820779207802078120782207832078420785207862078720788207892079020791207922079320794207952079620797207982079920800208012080220803208042080520806208072080820809208102081120812208132081420815208162081720818208192082020821208222082320824208252082620827208282082920830208312083220833208342083520836208372083820839208402084120842208432084420845208462084720848208492085020851208522085320854208552085620857208582085920860208612086220863208642086520866208672086820869208702087120872208732087420875208762087720878208792088020881208822088320884208852088620887208882088920890208912089220893208942089520896208972089820899209002090120902209032090420905209062090720908209092091020911209122091320914209152091620917209182091920920209212092220923209242092520926209272092820929209302093120932209332093420935209362093720938209392094020941209422094320944209452094620947209482094920950209512095220953209542095520956209572095820959209602096120962209632096420965209662096720968209692097020971209722097320974209752097620977209782097920980209812098220983209842098520986209872098820989209902099120992209932099420995209962099720998209992100021001210022100321004210052100621007210082100921010210112101221013210142101521016210172101821019210202102121022210232102421025210262102721028210292103021031210322103321034210352103621037210382103921040210412104221043210442104521046210472104821049210502105121052210532105421055210562105721058210592106021061210622106321064210652106621067210682106921070210712107221073210742107521076210772107821079210802108121082210832108421085210862108721088210892109021091210922109321094210952109621097210982109921100211012110221103211042110521106211072110821109211102111121112211132111421115211162111721118211192112021121211222112321124211252112621127211282112921130211312113221133211342113521136211372113821139211402114121142211432114421145211462114721148211492115021151211522115321154211552115621157211582115921160211612116221163211642116521166211672116821169211702117121172211732117421175211762117721178211792118021181211822118321184211852118621187211882118921190211912119221193211942119521196211972119821199212002120121202212032120421205212062120721208212092121021211212122121321214212152121621217212182121921220212212122221223212242122521226212272122821229212302123121232212332123421235212362123721238212392124021241212422124321244212452124621247212482124921250212512125221253212542125521256212572125821259212602126121262212632126421265212662126721268212692127021271212722127321274212752127621277212782127921280212812128221283212842128521286212872128821289212902129121292212932129421295212962129721298212992130021301213022130321304213052130621307213082130921310213112131221313213142131521316213172131821319213202132121322213232132421325213262132721328213292133021331213322133321334213352133621337213382133921340213412134221343213442134521346213472134821349213502135121352213532135421355213562135721358213592136021361213622136321364213652136621367213682136921370213712137221373213742137521376213772137821379213802138121382213832138421385213862138721388213892139021391213922139321394213952139621397213982139921400214012140221403214042140521406214072140821409214102141121412214132141421415214162141721418214192142021421214222142321424214252142621427214282142921430214312143221433214342143521436214372143821439214402144121442214432144421445214462144721448214492145021451214522145321454214552145621457214582145921460214612146221463214642146521466214672146821469214702147121472214732147421475214762147721478214792148021481214822148321484214852148621487214882148921490214912149221493214942149521496214972149821499215002150121502215032150421505215062150721508215092151021511215122151321514215152151621517215182151921520215212152221523215242152521526215272152821529215302153121532215332153421535215362153721538215392154021541215422154321544215452154621547215482154921550215512155221553215542155521556215572155821559215602156121562215632156421565215662156721568215692157021571215722157321574215752157621577215782157921580215812158221583215842158521586215872158821589215902159121592215932159421595215962159721598215992160021601216022160321604216052160621607216082160921610216112161221613216142161521616216172161821619216202162121622216232162421625216262162721628216292163021631216322163321634216352163621637216382163921640216412164221643216442164521646216472164821649216502165121652216532165421655216562165721658216592166021661216622166321664216652166621667216682166921670216712167221673216742167521676216772167821679216802168121682216832168421685216862168721688216892169021691216922169321694216952169621697216982169921700217012170221703217042170521706217072170821709217102171121712217132171421715217162171721718217192172021721217222172321724217252172621727217282172921730217312173221733217342173521736217372173821739217402174121742217432174421745217462174721748217492175021751217522175321754217552175621757217582175921760217612176221763217642176521766217672176821769217702177121772217732177421775217762177721778217792178021781217822178321784217852178621787217882178921790217912179221793217942179521796217972179821799218002180121802218032180421805218062180721808218092181021811218122181321814218152181621817218182181921820218212182221823218242182521826218272182821829218302183121832218332183421835218362183721838218392184021841218422184321844218452184621847218482184921850218512185221853218542185521856218572185821859218602186121862218632186421865218662186721868218692187021871218722187321874218752187621877218782187921880218812188221883218842188521886218872188821889218902189121892218932189421895218962189721898218992190021901219022190321904219052190621907219082190921910219112191221913219142191521916219172191821919219202192121922219232192421925219262192721928219292193021931219322193321934219352193621937219382193921940219412194221943219442194521946219472194821949219502195121952219532195421955219562195721958219592196021961219622196321964219652196621967219682196921970219712197221973219742197521976219772197821979219802198121982219832198421985219862198721988219892199021991219922199321994219952199621997219982199922000220012200222003220042200522006220072200822009220102201122012220132201422015220162201722018220192202022021220222202322024220252202622027220282202922030220312203222033220342203522036220372203822039220402204122042220432204422045220462204722048220492205022051220522205322054220552205622057220582205922060220612206222063220642206522066220672206822069220702207122072220732207422075220762207722078220792208022081220822208322084220852208622087220882208922090220912209222093220942209522096220972209822099221002210122102221032210422105221062210722108221092211022111221122211322114221152211622117221182211922120221212212222123221242212522126221272212822129221302213122132221332213422135221362213722138221392214022141221422214322144221452214622147221482214922150221512215222153221542215522156221572215822159221602216122162221632216422165221662216722168221692217022171221722217322174221752217622177221782217922180221812218222183221842218522186221872218822189221902219122192221932219422195221962219722198221992220022201222022220322204222052220622207222082220922210222112221222213222142221522216222172221822219222202222122222222232222422225222262222722228222292223022231222322223322234222352223622237222382223922240222412224222243222442224522246222472224822249222502225122252222532225422255222562225722258222592226022261222622226322264222652226622267222682226922270222712227222273222742227522276222772227822279222802228122282222832228422285222862228722288222892229022291222922229322294222952229622297222982229922300223012230222303223042230522306223072230822309223102231122312223132231422315223162231722318223192232022321223222232322324223252232622327223282232922330223312233222333223342233522336223372233822339223402234122342223432234422345223462234722348223492235022351223522235322354223552235622357223582235922360223612236222363223642236522366223672236822369223702237122372223732237422375223762237722378223792238022381223822238322384223852238622387223882238922390223912239222393223942239522396223972239822399224002240122402224032240422405224062240722408224092241022411224122241322414224152241622417224182241922420224212242222423224242242522426224272242822429224302243122432224332243422435224362243722438224392244022441224422244322444224452244622447224482244922450224512245222453224542245522456224572245822459224602246122462224632246422465224662246722468224692247022471224722247322474224752247622477224782247922480224812248222483224842248522486224872248822489224902249122492224932249422495224962249722498224992250022501225022250322504225052250622507225082250922510225112251222513225142251522516225172251822519225202252122522225232252422525225262252722528225292253022531225322253322534225352253622537225382253922540225412254222543225442254522546225472254822549225502255122552225532255422555225562255722558225592256022561225622256322564225652256622567225682256922570225712257222573225742257522576225772257822579225802258122582225832258422585225862258722588225892259022591225922259322594225952259622597225982259922600226012260222603226042260522606226072260822609226102261122612226132261422615226162261722618226192262022621226222262322624226252262622627226282262922630226312263222633226342263522636226372263822639226402264122642226432264422645226462264722648226492265022651226522265322654226552265622657226582265922660226612266222663226642266522666226672266822669226702267122672226732267422675226762267722678226792268022681226822268322684226852268622687226882268922690226912269222693226942269522696226972269822699227002270122702227032270422705227062270722708227092271022711227122271322714227152271622717227182271922720227212272222723227242272522726227272272822729227302273122732227332273422735227362273722738227392274022741227422274322744227452274622747227482274922750227512275222753227542275522756227572275822759227602276122762227632276422765227662276722768227692277022771227722277322774227752277622777227782277922780227812278222783227842278522786227872278822789227902279122792227932279422795227962279722798227992280022801228022280322804228052280622807228082280922810228112281222813228142281522816228172281822819228202282122822228232282422825228262282722828228292283022831228322283322834228352283622837228382283922840228412284222843228442284522846228472284822849228502285122852228532285422855228562285722858228592286022861228622286322864228652286622867228682286922870228712287222873228742287522876228772287822879228802288122882228832288422885228862288722888228892289022891228922289322894228952289622897228982289922900229012290222903229042290522906229072290822909229102291122912229132291422915229162291722918229192292022921229222292322924229252292622927229282292922930229312293222933229342293522936229372293822939229402294122942229432294422945229462294722948229492295022951229522295322954229552295622957229582295922960229612296222963229642296522966229672296822969229702297122972229732297422975229762297722978229792298022981229822298322984229852298622987229882298922990229912299222993229942299522996229972299822999230002300123002230032300423005230062300723008230092301023011230122301323014230152301623017230182301923020230212302223023230242302523026230272302823029230302303123032230332303423035230362303723038230392304023041230422304323044230452304623047230482304923050230512305223053230542305523056230572305823059230602306123062230632306423065230662306723068230692307023071230722307323074230752307623077230782307923080230812308223083230842308523086230872308823089230902309123092230932309423095230962309723098230992310023101231022310323104231052310623107231082310923110231112311223113231142311523116231172311823119231202312123122231232312423125231262312723128231292313023131231322313323134231352313623137231382313923140231412314223143231442314523146231472314823149231502315123152231532315423155231562315723158231592316023161231622316323164231652316623167231682316923170231712317223173231742317523176231772317823179231802318123182231832318423185231862318723188231892319023191231922319323194231952319623197231982319923200232012320223203232042320523206232072320823209232102321123212232132321423215232162321723218232192322023221232222322323224232252322623227232282322923230232312323223233232342323523236232372323823239232402324123242232432324423245232462324723248232492325023251232522325323254232552325623257232582325923260232612326223263232642326523266232672326823269232702327123272232732327423275232762327723278232792328023281232822328323284232852328623287232882328923290232912329223293232942329523296232972329823299233002330123302233032330423305233062330723308233092331023311233122331323314233152331623317233182331923320233212332223323233242332523326233272332823329233302333123332233332333423335233362333723338233392334023341233422334323344233452334623347233482334923350233512335223353233542335523356233572335823359233602336123362233632336423365233662336723368233692337023371233722337323374233752337623377233782337923380233812338223383233842338523386233872338823389233902339123392233932339423395233962339723398233992340023401234022340323404234052340623407234082340923410234112341223413234142341523416234172341823419234202342123422234232342423425234262342723428234292343023431234322343323434234352343623437234382343923440234412344223443234442344523446234472344823449234502345123452234532345423455234562345723458234592346023461234622346323464234652346623467234682346923470234712347223473234742347523476234772347823479234802348123482234832348423485234862348723488234892349023491234922349323494234952349623497234982349923500235012350223503235042350523506235072350823509235102351123512235132351423515235162351723518235192352023521235222352323524235252352623527235282352923530235312353223533235342353523536235372353823539235402354123542235432354423545235462354723548235492355023551235522355323554235552355623557235582355923560235612356223563235642356523566235672356823569235702357123572235732357423575235762357723578235792358023581235822358323584235852358623587235882358923590235912359223593235942359523596235972359823599236002360123602236032360423605236062360723608236092361023611236122361323614236152361623617236182361923620236212362223623236242362523626236272362823629236302363123632236332363423635236362363723638236392364023641236422364323644236452364623647236482364923650236512365223653236542365523656236572365823659236602366123662236632366423665236662366723668236692367023671236722367323674236752367623677236782367923680236812368223683236842368523686236872368823689236902369123692236932369423695236962369723698236992370023701237022370323704237052370623707237082370923710237112371223713237142371523716237172371823719237202372123722237232372423725237262372723728237292373023731237322373323734237352373623737237382373923740237412374223743237442374523746237472374823749237502375123752237532375423755237562375723758237592376023761237622376323764237652376623767237682376923770237712377223773237742377523776237772377823779237802378123782237832378423785237862378723788237892379023791237922379323794237952379623797237982379923800238012380223803238042380523806238072380823809238102381123812238132381423815238162381723818238192382023821238222382323824238252382623827238282382923830238312383223833238342383523836238372383823839238402384123842238432384423845238462384723848238492385023851238522385323854238552385623857238582385923860238612386223863238642386523866238672386823869238702387123872238732387423875238762387723878238792388023881238822388323884238852388623887238882388923890238912389223893238942389523896238972389823899239002390123902239032390423905239062390723908239092391023911239122391323914239152391623917239182391923920239212392223923239242392523926239272392823929239302393123932239332393423935239362393723938239392394023941239422394323944239452394623947239482394923950239512395223953239542395523956239572395823959239602396123962239632396423965239662396723968239692397023971239722397323974239752397623977239782397923980239812398223983239842398523986239872398823989239902399123992239932399423995239962399723998239992400024001240022400324004240052400624007240082400924010240112401224013240142401524016240172401824019240202402124022240232402424025240262402724028240292403024031240322403324034240352403624037240382403924040240412404224043240442404524046240472404824049240502405124052240532405424055240562405724058240592406024061240622406324064240652406624067240682406924070240712407224073240742407524076240772407824079240802408124082240832408424085240862408724088240892409024091240922409324094240952409624097240982409924100241012410224103241042410524106241072410824109241102411124112241132411424115241162411724118241192412024121241222412324124241252412624127241282412924130241312413224133241342413524136241372413824139241402414124142241432414424145241462414724148241492415024151241522415324154241552415624157241582415924160241612416224163241642416524166241672416824169241702417124172241732417424175241762417724178241792418024181241822418324184241852418624187241882418924190241912419224193241942419524196241972419824199242002420124202242032420424205242062420724208242092421024211242122421324214242152421624217242182421924220242212422224223242242422524226242272422824229242302423124232242332423424235242362423724238242392424024241242422424324244242452424624247242482424924250242512425224253242542425524256242572425824259242602426124262242632426424265242662426724268242692427024271242722427324274242752427624277242782427924280242812428224283242842428524286242872428824289242902429124292242932429424295242962429724298242992430024301243022430324304243052430624307243082430924310243112431224313243142431524316243172431824319243202432124322243232432424325243262432724328243292433024331243322433324334243352433624337243382433924340243412434224343243442434524346243472434824349243502435124352243532435424355243562435724358243592436024361243622436324364243652436624367243682436924370243712437224373243742437524376243772437824379243802438124382243832438424385243862438724388243892439024391243922439324394243952439624397243982439924400244012440224403244042440524406244072440824409244102441124412244132441424415244162441724418244192442024421244222442324424244252442624427244282442924430244312443224433244342443524436244372443824439244402444124442244432444424445244462444724448244492445024451244522445324454244552445624457244582445924460244612446224463244642446524466244672446824469244702447124472244732447424475244762447724478244792448024481244822448324484244852448624487244882448924490244912449224493244942449524496244972449824499245002450124502245032450424505245062450724508245092451024511245122451324514245152451624517245182451924520245212452224523245242452524526245272452824529245302453124532245332453424535245362453724538245392454024541245422454324544245452454624547245482454924550245512455224553245542455524556245572455824559245602456124562245632456424565245662456724568245692457024571245722457324574245752457624577245782457924580245812458224583245842458524586245872458824589245902459124592245932459424595245962459724598245992460024601246022460324604246052460624607246082460924610246112461224613246142461524616246172461824619246202462124622246232462424625246262462724628246292463024631246322463324634246352463624637246382463924640246412464224643246442464524646246472464824649246502465124652246532465424655246562465724658246592466024661246622466324664246652466624667246682466924670246712467224673246742467524676246772467824679246802468124682246832468424685246862468724688246892469024691246922469324694246952469624697246982469924700247012470224703247042470524706247072470824709247102471124712247132471424715247162471724718247192472024721247222472324724247252472624727247282472924730247312473224733247342473524736247372473824739247402474124742247432474424745247462474724748247492475024751247522475324754247552475624757247582475924760247612476224763247642476524766247672476824769247702477124772247732477424775247762477724778247792478024781247822478324784247852478624787247882478924790247912479224793247942479524796247972479824799248002480124802248032480424805248062480724808248092481024811248122481324814248152481624817248182481924820248212482224823248242482524826248272482824829248302483124832248332483424835248362483724838248392484024841248422484324844248452484624847248482484924850248512485224853248542485524856248572485824859248602486124862248632486424865248662486724868248692487024871248722487324874248752487624877248782487924880248812488224883248842488524886248872488824889248902489124892248932489424895248962489724898248992490024901249022490324904249052490624907249082490924910249112491224913249142491524916249172491824919249202492124922249232492424925249262492724928249292493024931249322493324934249352493624937249382493924940249412494224943249442494524946249472494824949249502495124952249532495424955249562495724958249592496024961249622496324964249652496624967249682496924970249712497224973249742497524976249772497824979249802498124982249832498424985249862498724988249892499024991249922499324994249952499624997249982499925000250012500225003250042500525006250072500825009250102501125012250132501425015250162501725018250192502025021250222502325024250252502625027250282502925030250312503225033250342503525036250372503825039250402504125042250432504425045250462504725048250492505025051250522505325054250552505625057250582505925060250612506225063250642506525066250672506825069250702507125072250732507425075250762507725078250792508025081250822508325084250852508625087250882508925090250912509225093250942509525096250972509825099251002510125102251032510425105251062510725108251092511025111251122511325114251152511625117251182511925120251212512225123251242512525126251272512825129251302513125132251332513425135251362513725138251392514025141251422514325144251452514625147251482514925150251512515225153251542515525156251572515825159251602516125162251632516425165251662516725168251692517025171251722517325174251752517625177251782517925180251812518225183251842518525186251872518825189251902519125192251932519425195251962519725198251992520025201252022520325204252052520625207252082520925210252112521225213252142521525216252172521825219252202522125222252232522425225252262522725228252292523025231252322523325234252352523625237252382523925240252412524225243252442524525246252472524825249252502525125252252532525425255252562525725258252592526025261252622526325264252652526625267252682526925270252712527225273252742527525276252772527825279252802528125282252832528425285252862528725288252892529025291252922529325294252952529625297252982529925300253012530225303253042530525306253072530825309253102531125312253132531425315253162531725318253192532025321253222532325324253252532625327253282532925330253312533225333253342533525336253372533825339253402534125342253432534425345253462534725348253492535025351253522535325354253552535625357253582535925360253612536225363253642536525366253672536825369253702537125372253732537425375253762537725378253792538025381253822538325384253852538625387253882538925390253912539225393253942539525396253972539825399254002540125402254032540425405254062540725408254092541025411254122541325414254152541625417254182541925420254212542225423254242542525426254272542825429254302543125432254332543425435254362543725438254392544025441254422544325444254452544625447254482544925450254512545225453254542545525456254572545825459254602546125462254632546425465254662546725468254692547025471254722547325474254752547625477254782547925480254812548225483254842548525486254872548825489254902549125492254932549425495254962549725498254992550025501255022550325504255052550625507255082550925510255112551225513255142551525516255172551825519255202552125522255232552425525255262552725528255292553025531255322553325534255352553625537255382553925540255412554225543255442554525546255472554825549255502555125552255532555425555255562555725558255592556025561255622556325564255652556625567255682556925570255712557225573255742557525576255772557825579255802558125582255832558425585255862558725588255892559025591255922559325594255952559625597255982559925600256012560225603256042560525606256072560825609256102561125612256132561425615256162561725618256192562025621256222562325624256252562625627256282562925630256312563225633256342563525636256372563825639256402564125642256432564425645256462564725648256492565025651256522565325654256552565625657256582565925660256612566225663256642566525666256672566825669256702567125672256732567425675256762567725678256792568025681256822568325684256852568625687256882568925690256912569225693256942569525696256972569825699257002570125702257032570425705257062570725708257092571025711257122571325714257152571625717257182571925720257212572225723257242572525726257272572825729257302573125732257332573425735257362573725738257392574025741257422574325744257452574625747257482574925750257512575225753257542575525756257572575825759257602576125762257632576425765257662576725768257692577025771257722577325774257752577625777257782577925780257812578225783257842578525786257872578825789257902579125792257932579425795257962579725798257992580025801258022580325804258052580625807258082580925810258112581225813258142581525816258172581825819258202582125822258232582425825258262582725828258292583025831258322583325834258352583625837258382583925840258412584225843258442584525846258472584825849258502585125852258532585425855258562585725858258592586025861258622586325864258652586625867258682586925870258712587225873258742587525876258772587825879258802588125882258832588425885258862588725888258892589025891258922589325894258952589625897258982589925900259012590225903259042590525906259072590825909259102591125912259132591425915259162591725918259192592025921259222592325924259252592625927259282592925930259312593225933259342593525936259372593825939259402594125942259432594425945259462594725948259492595025951259522595325954259552595625957259582595925960259612596225963259642596525966259672596825969259702597125972259732597425975259762597725978259792598025981259822598325984259852598625987259882598925990259912599225993259942599525996259972599825999260002600126002260032600426005260062600726008260092601026011260122601326014260152601626017260182601926020260212602226023260242602526026260272602826029260302603126032260332603426035260362603726038260392604026041260422604326044260452604626047260482604926050260512605226053260542605526056260572605826059260602606126062260632606426065260662606726068260692607026071260722607326074260752607626077260782607926080260812608226083260842608526086260872608826089260902609126092260932609426095260962609726098260992610026101261022610326104261052610626107261082610926110261112611226113261142611526116261172611826119261202612126122261232612426125261262612726128261292613026131261322613326134261352613626137261382613926140261412614226143261442614526146261472614826149261502615126152261532615426155261562615726158261592616026161261622616326164261652616626167261682616926170261712617226173261742617526176261772617826179261802618126182261832618426185261862618726188261892619026191261922619326194261952619626197261982619926200262012620226203262042620526206262072620826209262102621126212262132621426215262162621726218262192622026221262222622326224262252622626227262282622926230262312623226233262342623526236262372623826239262402624126242262432624426245262462624726248262492625026251262522625326254262552625626257262582625926260262612626226263262642626526266262672626826269262702627126272262732627426275262762627726278262792628026281262822628326284262852628626287262882628926290262912629226293262942629526296262972629826299263002630126302263032630426305263062630726308263092631026311263122631326314263152631626317263182631926320263212632226323263242632526326263272632826329263302633126332263332633426335263362633726338263392634026341263422634326344263452634626347263482634926350263512635226353263542635526356263572635826359263602636126362263632636426365263662636726368263692637026371263722637326374263752637626377263782637926380263812638226383263842638526386263872638826389263902639126392263932639426395263962639726398263992640026401264022640326404264052640626407264082640926410264112641226413264142641526416264172641826419264202642126422264232642426425264262642726428264292643026431264322643326434264352643626437264382643926440264412644226443264442644526446264472644826449264502645126452264532645426455264562645726458264592646026461264622646326464264652646626467264682646926470264712647226473264742647526476264772647826479264802648126482264832648426485264862648726488264892649026491264922649326494264952649626497264982649926500265012650226503265042650526506265072650826509265102651126512265132651426515265162651726518265192652026521265222652326524265252652626527265282652926530265312653226533265342653526536265372653826539265402654126542265432654426545265462654726548265492655026551265522655326554265552655626557265582655926560265612656226563265642656526566265672656826569265702657126572265732657426575265762657726578265792658026581265822658326584265852658626587265882658926590265912659226593265942659526596265972659826599266002660126602266032660426605266062660726608266092661026611266122661326614266152661626617266182661926620266212662226623266242662526626266272662826629266302663126632266332663426635266362663726638266392664026641266422664326644266452664626647266482664926650266512665226653266542665526656266572665826659266602666126662266632666426665266662666726668266692667026671266722667326674266752667626677266782667926680266812668226683266842668526686266872668826689266902669126692266932669426695266962669726698266992670026701267022670326704267052670626707267082670926710267112671226713267142671526716267172671826719267202672126722267232672426725267262672726728267292673026731267322673326734267352673626737267382673926740267412674226743267442674526746267472674826749267502675126752267532675426755267562675726758267592676026761267622676326764267652676626767267682676926770267712677226773267742677526776267772677826779267802678126782267832678426785267862678726788267892679026791267922679326794267952679626797267982679926800268012680226803268042680526806268072680826809268102681126812268132681426815268162681726818268192682026821268222682326824268252682626827268282682926830268312683226833268342683526836268372683826839268402684126842268432684426845268462684726848268492685026851268522685326854268552685626857268582685926860268612686226863268642686526866268672686826869268702687126872268732687426875268762687726878268792688026881268822688326884268852688626887268882688926890268912689226893268942689526896268972689826899269002690126902269032690426905269062690726908269092691026911269122691326914269152691626917269182691926920269212692226923269242692526926269272692826929269302693126932269332693426935269362693726938269392694026941269422694326944269452694626947269482694926950269512695226953269542695526956269572695826959269602696126962269632696426965269662696726968269692697026971269722697326974269752697626977269782697926980269812698226983269842698526986269872698826989269902699126992269932699426995269962699726998269992700027001270022700327004270052700627007270082700927010270112701227013270142701527016270172701827019270202702127022270232702427025270262702727028270292703027031270322703327034270352703627037270382703927040270412704227043270442704527046270472704827049270502705127052270532705427055270562705727058270592706027061270622706327064270652706627067270682706927070270712707227073270742707527076270772707827079270802708127082270832708427085270862708727088270892709027091270922709327094270952709627097270982709927100271012710227103271042710527106271072710827109271102711127112271132711427115271162711727118271192712027121271222712327124271252712627127271282712927130271312713227133271342713527136271372713827139271402714127142271432714427145271462714727148271492715027151271522715327154271552715627157271582715927160271612716227163271642716527166271672716827169271702717127172271732717427175271762717727178271792718027181271822718327184271852718627187271882718927190271912719227193271942719527196271972719827199272002720127202272032720427205272062720727208272092721027211272122721327214272152721627217272182721927220272212722227223272242722527226272272722827229272302723127232272332723427235272362723727238272392724027241272422724327244272452724627247272482724927250272512725227253272542725527256272572725827259272602726127262272632726427265272662726727268272692727027271272722727327274272752727627277272782727927280272812728227283272842728527286272872728827289272902729127292272932729427295272962729727298272992730027301273022730327304273052730627307273082730927310273112731227313273142731527316273172731827319273202732127322273232732427325273262732727328273292733027331273322733327334273352733627337273382733927340273412734227343273442734527346273472734827349273502735127352273532735427355273562735727358273592736027361273622736327364273652736627367273682736927370273712737227373273742737527376273772737827379273802738127382273832738427385273862738727388273892739027391273922739327394273952739627397273982739927400274012740227403274042740527406274072740827409274102741127412274132741427415274162741727418274192742027421274222742327424274252742627427274282742927430274312743227433274342743527436274372743827439274402744127442274432744427445274462744727448274492745027451274522745327454274552745627457274582745927460274612746227463274642746527466274672746827469274702747127472274732747427475274762747727478274792748027481274822748327484274852748627487274882748927490274912749227493274942749527496274972749827499275002750127502275032750427505275062750727508275092751027511275122751327514275152751627517275182751927520275212752227523275242752527526275272752827529275302753127532275332753427535275362753727538275392754027541275422754327544275452754627547275482754927550275512755227553275542755527556275572755827559275602756127562275632756427565275662756727568275692757027571275722757327574275752757627577275782757927580275812758227583275842758527586275872758827589275902759127592275932759427595275962759727598275992760027601276022760327604276052760627607276082760927610276112761227613276142761527616276172761827619276202762127622276232762427625276262762727628276292763027631276322763327634276352763627637276382763927640276412764227643276442764527646276472764827649276502765127652276532765427655276562765727658276592766027661276622766327664276652766627667276682766927670276712767227673276742767527676276772767827679276802768127682276832768427685276862768727688276892769027691276922769327694276952769627697276982769927700277012770227703277042770527706277072770827709277102771127712277132771427715277162771727718277192772027721277222772327724277252772627727277282772927730277312773227733277342773527736277372773827739277402774127742277432774427745277462774727748277492775027751277522775327754277552775627757277582775927760277612776227763277642776527766277672776827769277702777127772277732777427775277762777727778277792778027781277822778327784277852778627787277882778927790277912779227793277942779527796277972779827799278002780127802278032780427805278062780727808278092781027811278122781327814278152781627817278182781927820278212782227823278242782527826278272782827829278302783127832278332783427835278362783727838278392784027841278422784327844278452784627847278482784927850278512785227853278542785527856278572785827859278602786127862278632786427865278662786727868278692787027871278722787327874278752787627877278782787927880278812788227883278842788527886278872788827889278902789127892278932789427895278962789727898278992790027901279022790327904279052790627907279082790927910279112791227913279142791527916279172791827919279202792127922279232792427925279262792727928279292793027931279322793327934279352793627937279382793927940279412794227943279442794527946279472794827949279502795127952279532795427955279562795727958279592796027961279622796327964279652796627967279682796927970279712797227973279742797527976279772797827979279802798127982279832798427985279862798727988279892799027991279922799327994279952799627997279982799928000280012800228003280042800528006280072800828009280102801128012280132801428015280162801728018280192802028021280222802328024280252802628027280282802928030280312803228033280342803528036280372803828039280402804128042280432804428045280462804728048280492805028051280522805328054280552805628057280582805928060280612806228063280642806528066280672806828069280702807128072280732807428075280762807728078280792808028081280822808328084280852808628087280882808928090280912809228093280942809528096280972809828099281002810128102281032810428105281062810728108281092811028111281122811328114281152811628117281182811928120281212812228123281242812528126281272812828129281302813128132281332813428135281362813728138281392814028141281422814328144281452814628147281482814928150281512815228153281542815528156281572815828159281602816128162281632816428165281662816728168281692817028171281722817328174281752817628177281782817928180281812818228183281842818528186281872818828189281902819128192281932819428195281962819728198281992820028201282022820328204282052820628207282082820928210282112821228213282142821528216282172821828219282202822128222282232822428225282262822728228282292823028231282322823328234282352823628237282382823928240282412824228243282442824528246282472824828249282502825128252282532825428255282562825728258282592826028261282622826328264282652826628267282682826928270282712827228273282742827528276282772827828279282802828128282282832828428285282862828728288282892829028291282922829328294282952829628297282982829928300283012830228303283042830528306283072830828309283102831128312283132831428315283162831728318283192832028321283222832328324283252832628327283282832928330283312833228333283342833528336283372833828339283402834128342283432834428345283462834728348283492835028351283522835328354283552835628357283582835928360283612836228363283642836528366283672836828369283702837128372283732837428375283762837728378283792838028381283822838328384283852838628387283882838928390283912839228393283942839528396283972839828399284002840128402284032840428405284062840728408284092841028411284122841328414284152841628417284182841928420284212842228423284242842528426284272842828429284302843128432284332843428435284362843728438284392844028441284422844328444284452844628447284482844928450284512845228453284542845528456284572845828459284602846128462284632846428465284662846728468284692847028471284722847328474284752847628477284782847928480284812848228483284842848528486284872848828489284902849128492284932849428495284962849728498284992850028501285022850328504285052850628507285082850928510285112851228513285142851528516285172851828519285202852128522285232852428525285262852728528285292853028531285322853328534285352853628537285382853928540285412854228543285442854528546285472854828549285502855128552285532855428555285562855728558285592856028561285622856328564285652856628567285682856928570285712857228573285742857528576285772857828579285802858128582285832858428585285862858728588285892859028591285922859328594285952859628597285982859928600286012860228603286042860528606286072860828609286102861128612286132861428615286162861728618286192862028621286222862328624286252862628627286282862928630286312863228633286342863528636286372863828639286402864128642286432864428645286462864728648286492865028651286522865328654286552865628657286582865928660286612866228663286642866528666286672866828669286702867128672286732867428675286762867728678286792868028681286822868328684286852868628687286882868928690286912869228693286942869528696286972869828699287002870128702287032870428705287062870728708287092871028711287122871328714287152871628717287182871928720287212872228723287242872528726287272872828729287302873128732287332873428735287362873728738287392874028741287422874328744287452874628747287482874928750287512875228753287542875528756287572875828759287602876128762287632876428765287662876728768287692877028771287722877328774287752877628777287782877928780287812878228783287842878528786287872878828789287902879128792287932879428795287962879728798287992880028801288022880328804288052880628807288082880928810288112881228813288142881528816288172881828819288202882128822288232882428825288262882728828288292883028831288322883328834288352883628837288382883928840288412884228843288442884528846288472884828849288502885128852288532885428855288562885728858288592886028861288622886328864288652886628867288682886928870288712887228873288742887528876288772887828879288802888128882288832888428885288862888728888288892889028891288922889328894288952889628897288982889928900289012890228903289042890528906289072890828909289102891128912289132891428915289162891728918289192892028921289222892328924289252892628927289282892928930289312893228933289342893528936289372893828939289402894128942289432894428945289462894728948289492895028951289522895328954289552895628957289582895928960289612896228963289642896528966289672896828969289702897128972289732897428975289762897728978289792898028981289822898328984289852898628987289882898928990289912899228993289942899528996289972899828999290002900129002290032900429005290062900729008290092901029011290122901329014290152901629017290182901929020290212902229023290242902529026290272902829029290302903129032290332903429035290362903729038290392904029041290422904329044290452904629047290482904929050290512905229053290542905529056290572905829059290602906129062290632906429065290662906729068290692907029071290722907329074290752907629077290782907929080290812908229083290842908529086290872908829089290902909129092290932909429095290962909729098290992910029101291022910329104291052910629107291082910929110291112911229113291142911529116291172911829119291202912129122291232912429125291262912729128291292913029131291322913329134291352913629137291382913929140291412914229143291442914529146291472914829149291502915129152291532915429155291562915729158291592916029161291622916329164291652916629167291682916929170291712917229173291742917529176291772917829179291802918129182291832918429185291862918729188291892919029191291922919329194291952919629197291982919929200292012920229203292042920529206292072920829209292102921129212292132921429215292162921729218292192922029221292222922329224292252922629227292282922929230292312923229233292342923529236292372923829239292402924129242292432924429245292462924729248292492925029251292522925329254292552925629257292582925929260292612926229263292642926529266292672926829269292702927129272292732927429275292762927729278292792928029281292822928329284292852928629287292882928929290292912929229293292942929529296292972929829299293002930129302293032930429305293062930729308293092931029311293122931329314293152931629317293182931929320293212932229323293242932529326293272932829329293302933129332293332933429335293362933729338293392934029341293422934329344293452934629347293482934929350293512935229353293542935529356293572935829359293602936129362293632936429365293662936729368293692937029371293722937329374293752937629377293782937929380293812938229383293842938529386293872938829389293902939129392293932939429395293962939729398293992940029401294022940329404294052940629407294082940929410294112941229413294142941529416294172941829419294202942129422294232942429425294262942729428294292943029431294322943329434294352943629437294382943929440294412944229443294442944529446294472944829449294502945129452294532945429455294562945729458294592946029461294622946329464294652946629467294682946929470294712947229473294742947529476294772947829479294802948129482294832948429485294862948729488294892949029491294922949329494294952949629497294982949929500295012950229503295042950529506295072950829509295102951129512295132951429515295162951729518295192952029521295222952329524295252952629527295282952929530295312953229533295342953529536295372953829539295402954129542295432954429545295462954729548295492955029551295522955329554295552955629557295582955929560295612956229563295642956529566295672956829569295702957129572295732957429575295762957729578295792958029581295822958329584295852958629587295882958929590295912959229593295942959529596295972959829599296002960129602296032960429605296062960729608296092961029611296122961329614296152961629617296182961929620296212962229623296242962529626296272962829629296302963129632296332963429635296362963729638296392964029641296422964329644296452964629647296482964929650296512965229653296542965529656296572965829659296602966129662296632966429665296662966729668296692967029671296722967329674296752967629677296782967929680296812968229683296842968529686296872968829689296902969129692296932969429695296962969729698296992970029701297022970329704297052970629707297082970929710297112971229713297142971529716297172971829719297202972129722297232972429725297262972729728297292973029731297322973329734297352973629737297382973929740297412974229743297442974529746297472974829749297502975129752297532975429755297562975729758297592976029761297622976329764297652976629767297682976929770297712977229773297742977529776297772977829779297802978129782297832978429785297862978729788297892979029791297922979329794297952979629797297982979929800298012980229803298042980529806298072980829809298102981129812298132981429815298162981729818298192982029821298222982329824298252982629827298282982929830298312983229833298342983529836298372983829839298402984129842298432984429845298462984729848298492985029851298522985329854298552985629857298582985929860298612986229863298642986529866298672986829869298702987129872298732987429875298762987729878298792988029881298822988329884298852988629887298882988929890298912989229893298942989529896298972989829899299002990129902299032990429905299062990729908299092991029911299122991329914299152991629917299182991929920299212992229923299242992529926299272992829929299302993129932299332993429935299362993729938299392994029941299422994329944299452994629947299482994929950299512995229953299542995529956299572995829959299602996129962299632996429965299662996729968299692997029971299722997329974299752997629977299782997929980299812998229983299842998529986299872998829989299902999129992299932999429995299962999729998299993000030001300023000330004300053000630007300083000930010300113001230013300143001530016300173001830019300203002130022300233002430025300263002730028300293003030031300323003330034300353003630037300383003930040300413004230043300443004530046300473004830049300503005130052300533005430055300563005730058300593006030061300623006330064300653006630067300683006930070300713007230073300743007530076300773007830079300803008130082300833008430085300863008730088300893009030091300923009330094300953009630097300983009930100301013010230103301043010530106301073010830109301103011130112301133011430115301163011730118301193012030121 |
- /***************************************************************************
- ** **
- ** QCustomPlot, an easy to use, modern plotting widget for Qt **
- ** Copyright (C) 2011-2017 Emanuel Eichhammer **
- ** **
- ** This program is free software: you can redistribute it and/or modify **
- ** it under the terms of the GNU General Public License as published by **
- ** the Free Software Foundation, either version 3 of the License, or **
- ** (at your option) any later version. **
- ** **
- ** This program is distributed in the hope that it will be useful, **
- ** but WITHOUT ANY WARRANTY; without even the implied warranty of **
- ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
- ** GNU General Public License for more details. **
- ** **
- ** You should have received a copy of the GNU General Public License **
- ** along with this program. If not, see http://www.gnu.org/licenses/. **
- ** **
- ****************************************************************************
- ** Author: Emanuel Eichhammer **
- ** Website/Contact: http://www.qcustomplot.com/ **
- ** Date: 04.09.17 **
- ** Version: 2.0.0 **
- ****************************************************************************/
- #include "qcustomplot.h"
- /* including file 'src/vector2d.cpp', size 7340 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPVector2D
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPVector2D
- \brief Represents two doubles as a mathematical 2D vector
-
- This class acts as a replacement for QVector2D with the advantage of double precision instead of
- single, and some convenience methods tailored for the QCustomPlot library.
- */
- /* start documentation of inline functions */
- /*! \fn void QCPVector2D::setX(double x)
-
- Sets the x coordinate of this vector to \a x.
-
- \see setY
- */
- /*! \fn void QCPVector2D::setY(double y)
-
- Sets the y coordinate of this vector to \a y.
-
- \see setX
- */
- /*! \fn double QCPVector2D::length() const
-
- Returns the length of this vector.
-
- \see lengthSquared
- */
- /*! \fn double QCPVector2D::lengthSquared() const
-
- Returns the squared length of this vector. In some situations, e.g. when just trying to find the
- shortest vector of a group, this is faster than calculating \ref length, because it avoids
- calculation of a square root.
-
- \see length
- */
- /*! \fn QPoint QCPVector2D::toPoint() const
-
- Returns a QPoint which has the x and y coordinates of this vector, truncating any floating point
- information.
-
- \see toPointF
- */
- /*! \fn QPointF QCPVector2D::toPointF() const
-
- Returns a QPointF which has the x and y coordinates of this vector.
-
- \see toPoint
- */
- /*! \fn bool QCPVector2D::isNull() const
-
- Returns whether this vector is null. A vector is null if \c qIsNull returns true for both x and y
- coordinates, i.e. if both are binary equal to 0.
- */
- /*! \fn QCPVector2D QCPVector2D::perpendicular() const
-
- Returns a vector perpendicular to this vector, with the same length.
- */
- /*! \fn double QCPVector2D::dot() const
-
- Returns the dot/scalar product of this vector with the specified vector \a vec.
- */
- /* end documentation of inline functions */
- /*!
- Creates a QCPVector2D object and initializes the x and y coordinates to 0.
- */
- QCPVector2D::QCPVector2D() :
- mX(0),
- mY(0)
- {
- }
- /*!
- Creates a QCPVector2D object and initializes the \a x and \a y coordinates with the specified
- values.
- */
- QCPVector2D::QCPVector2D(double x, double y) :
- mX(x),
- mY(y)
- {
- }
- /*!
- Creates a QCPVector2D object and initializes the x and y coordinates respective coordinates of
- the specified \a point.
- */
- QCPVector2D::QCPVector2D(const QPoint &point) :
- mX(point.x()),
- mY(point.y())
- {
- }
- /*!
- Creates a QCPVector2D object and initializes the x and y coordinates respective coordinates of
- the specified \a point.
- */
- QCPVector2D::QCPVector2D(const QPointF &point) :
- mX(point.x()),
- mY(point.y())
- {
- }
- /*!
- Normalizes this vector. After this operation, the length of the vector is equal to 1.
-
- \see normalized, length, lengthSquared
- */
- void QCPVector2D::normalize()
- {
- double len = length();
- mX /= len;
- mY /= len;
- }
- /*!
- Returns a normalized version of this vector. The length of the returned vector is equal to 1.
-
- \see normalize, length, lengthSquared
- */
- QCPVector2D QCPVector2D::normalized() const
- {
- QCPVector2D result(mX, mY);
- result.normalize();
- return result;
- }
- /*! \overload
-
- Returns the squared shortest distance of this vector (interpreted as a point) to the finite line
- segment given by \a start and \a end.
-
- \see distanceToStraightLine
- */
- double QCPVector2D::distanceSquaredToLine(const QCPVector2D &start, const QCPVector2D &end) const
- {
- QCPVector2D v(end-start);
- double vLengthSqr = v.lengthSquared();
- if (!qFuzzyIsNull(vLengthSqr))
- {
- double mu = v.dot(*this-start)/vLengthSqr;
- if (mu < 0)
- return (*this-start).lengthSquared();
- else if (mu > 1)
- return (*this-end).lengthSquared();
- else
- return ((start + mu*v)-*this).lengthSquared();
- } else
- return (*this-start).lengthSquared();
- }
- /*! \overload
-
- Returns the squared shortest distance of this vector (interpreted as a point) to the finite line
- segment given by \a line.
-
- \see distanceToStraightLine
- */
- double QCPVector2D::distanceSquaredToLine(const QLineF &line) const
- {
- return distanceSquaredToLine(QCPVector2D(line.p1()), QCPVector2D(line.p2()));
- }
- /*!
- Returns the shortest distance of this vector (interpreted as a point) to the infinite straight
- line given by a \a base point and a \a direction vector.
-
- \see distanceSquaredToLine
- */
- double QCPVector2D::distanceToStraightLine(const QCPVector2D &base, const QCPVector2D &direction) const
- {
- return qAbs((*this-base).dot(direction.perpendicular()))/direction.length();
- }
- /*!
- Scales this vector by the given \a factor, i.e. the x and y components are multiplied by \a
- factor.
- */
- QCPVector2D &QCPVector2D::operator*=(double factor)
- {
- mX *= factor;
- mY *= factor;
- return *this;
- }
- /*!
- Scales this vector by the given \a divisor, i.e. the x and y components are divided by \a
- divisor.
- */
- QCPVector2D &QCPVector2D::operator/=(double divisor)
- {
- mX /= divisor;
- mY /= divisor;
- return *this;
- }
- /*!
- Adds the given \a vector to this vector component-wise.
- */
- QCPVector2D &QCPVector2D::operator+=(const QCPVector2D &vector)
- {
- mX += vector.mX;
- mY += vector.mY;
- return *this;
- }
- /*!
- subtracts the given \a vector from this vector component-wise.
- */
- QCPVector2D &QCPVector2D::operator-=(const QCPVector2D &vector)
- {
- mX -= vector.mX;
- mY -= vector.mY;
- return *this;
- }
- /* end of 'src/vector2d.cpp' */
- /* including file 'src/painter.cpp', size 8670 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPPainter
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPPainter
- \brief QPainter subclass used internally
-
- This QPainter subclass is used to provide some extended functionality e.g. for tweaking position
- consistency between antialiased and non-antialiased painting. Further it provides workarounds
- for QPainter quirks.
-
- \warning This class intentionally hides non-virtual functions of QPainter, e.g. setPen, save and
- restore. So while it is possible to pass a QCPPainter instance to a function that expects a
- QPainter pointer, some of the workarounds and tweaks will be unavailable to the function (because
- it will call the base class implementations of the functions actually hidden by QCPPainter).
- */
- /*!
- Creates a new QCPPainter instance and sets default values
- */
- QCPPainter::QCPPainter() :
- QPainter(),
- mModes(pmDefault),
- mIsAntialiasing(false)
- {
- // don't setRenderHint(QPainter::NonCosmeticDefautPen) here, because painter isn't active yet and
- // a call to begin() will follow
- }
- /*!
- Creates a new QCPPainter instance on the specified paint \a device and sets default values. Just
- like the analogous QPainter constructor, begins painting on \a device immediately.
-
- Like \ref begin, this method sets QPainter::NonCosmeticDefaultPen in Qt versions before Qt5.
- */
- QCPPainter::QCPPainter(QPaintDevice *device) :
- QPainter(device),
- mModes(pmDefault),
- mIsAntialiasing(false)
- {
- #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) // before Qt5, default pens used to be cosmetic if NonCosmeticDefaultPen flag isn't set. So we set it to get consistency across Qt versions.
- if (isActive())
- setRenderHint(QPainter::NonCosmeticDefaultPen);
- #endif
- }
- /*!
- Sets the pen of the painter and applies certain fixes to it, depending on the mode of this
- QCPPainter.
-
- \note this function hides the non-virtual base class implementation.
- */
- void QCPPainter::setPen(const QPen &pen)
- {
- QPainter::setPen(pen);
- if (mModes.testFlag(pmNonCosmetic))
- makeNonCosmetic();
- }
- /*! \overload
-
- Sets the pen (by color) of the painter and applies certain fixes to it, depending on the mode of
- this QCPPainter.
-
- \note this function hides the non-virtual base class implementation.
- */
- void QCPPainter::setPen(const QColor &color)
- {
- QPainter::setPen(color);
- if (mModes.testFlag(pmNonCosmetic))
- makeNonCosmetic();
- }
- /*! \overload
-
- Sets the pen (by style) of the painter and applies certain fixes to it, depending on the mode of
- this QCPPainter.
-
- \note this function hides the non-virtual base class implementation.
- */
- void QCPPainter::setPen(Qt::PenStyle penStyle)
- {
- QPainter::setPen(penStyle);
- if (mModes.testFlag(pmNonCosmetic))
- makeNonCosmetic();
- }
- /*! \overload
-
- Works around a Qt bug introduced with Qt 4.8 which makes drawing QLineF unpredictable when
- antialiasing is disabled. Thus when antialiasing is disabled, it rounds the \a line to
- integer coordinates and then passes it to the original drawLine.
-
- \note this function hides the non-virtual base class implementation.
- */
- void QCPPainter::drawLine(const QLineF &line)
- {
- if (mIsAntialiasing || mModes.testFlag(pmVectorized))
- QPainter::drawLine(line);
- else
- QPainter::drawLine(line.toLine());
- }
- /*!
- Sets whether painting uses antialiasing or not. Use this method instead of using setRenderHint
- with QPainter::Antialiasing directly, as it allows QCPPainter to regain pixel exactness between
- antialiased and non-antialiased painting (Since Qt < 5.0 uses slightly different coordinate systems for
- AA/Non-AA painting).
- */
- void QCPPainter::setAntialiasing(bool enabled)
- {
- setRenderHint(QPainter::Antialiasing, enabled);
- if (mIsAntialiasing != enabled)
- {
- mIsAntialiasing = enabled;
- if (!mModes.testFlag(pmVectorized)) // antialiasing half-pixel shift only needed for rasterized outputs
- {
- if (mIsAntialiasing)
- translate(0.5, 0.5);
- else
- translate(-0.5, -0.5);
- }
- }
- }
- /*!
- Sets the mode of the painter. This controls whether the painter shall adjust its
- fixes/workarounds optimized for certain output devices.
- */
- void QCPPainter::setModes(QCPPainter::PainterModes modes)
- {
- mModes = modes;
- }
- /*!
- Sets the QPainter::NonCosmeticDefaultPen in Qt versions before Qt5 after beginning painting on \a
- device. This is necessary to get cosmetic pen consistency across Qt versions, because since Qt5,
- all pens are non-cosmetic by default, and in Qt4 this render hint must be set to get that
- behaviour.
-
- The Constructor \ref QCPPainter(QPaintDevice *device) which directly starts painting also sets
- the render hint as appropriate.
-
- \note this function hides the non-virtual base class implementation.
- */
- bool QCPPainter::begin(QPaintDevice *device)
- {
- bool result = QPainter::begin(device);
- #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) // before Qt5, default pens used to be cosmetic if NonCosmeticDefaultPen flag isn't set. So we set it to get consistency across Qt versions.
- if (result)
- setRenderHint(QPainter::NonCosmeticDefaultPen);
- #endif
- return result;
- }
- /*! \overload
-
- Sets the mode of the painter. This controls whether the painter shall adjust its
- fixes/workarounds optimized for certain output devices.
- */
- void QCPPainter::setMode(QCPPainter::PainterMode mode, bool enabled)
- {
- if (!enabled && mModes.testFlag(mode))
- mModes &= ~mode;
- else if (enabled && !mModes.testFlag(mode))
- mModes |= mode;
- }
- /*!
- Saves the painter (see QPainter::save). Since QCPPainter adds some new internal state to
- QPainter, the save/restore functions are reimplemented to also save/restore those members.
-
- \note this function hides the non-virtual base class implementation.
-
- \see restore
- */
- void QCPPainter::save()
- {
- mAntialiasingStack.push(mIsAntialiasing);
- QPainter::save();
- }
- /*!
- Restores the painter (see QPainter::restore). Since QCPPainter adds some new internal state to
- QPainter, the save/restore functions are reimplemented to also save/restore those members.
-
- \note this function hides the non-virtual base class implementation.
-
- \see save
- */
- void QCPPainter::restore()
- {
- if (!mAntialiasingStack.isEmpty())
- mIsAntialiasing = mAntialiasingStack.pop();
- else
- qDebug() << Q_FUNC_INFO << "Unbalanced save/restore";
- QPainter::restore();
- }
- /*!
- Changes the pen width to 1 if it currently is 0. This function is called in the \ref setPen
- overrides when the \ref pmNonCosmetic mode is set.
- */
- void QCPPainter::makeNonCosmetic()
- {
- if (qFuzzyIsNull(pen().widthF()))
- {
- QPen p = pen();
- p.setWidth(1);
- QPainter::setPen(p);
- }
- }
- /* end of 'src/painter.cpp' */
- /* including file 'src/paintbuffer.cpp', size 18502 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPAbstractPaintBuffer
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPAbstractPaintBuffer
- \brief The abstract base class for paint buffers, which define the rendering backend
- This abstract base class defines the basic interface that a paint buffer needs to provide in
- order to be usable by QCustomPlot.
- A paint buffer manages both a surface to draw onto, and the matching paint device. The size of
- the surface can be changed via \ref setSize. External classes (\ref QCustomPlot and \ref
- QCPLayer) request a painter via \ref startPainting and then perform the draw calls. Once the
- painting is complete, \ref donePainting is called, so the paint buffer implementation can do
- clean up if necessary. Before rendering a frame, each paint buffer is usually filled with a color
- using \ref clear (usually the color is \c Qt::transparent), to remove the contents of the
- previous frame.
- The simplest paint buffer implementation is \ref QCPPaintBufferPixmap which allows regular
- software rendering via the raster engine. Hardware accelerated rendering via pixel buffers and
- frame buffer objects is provided by \ref QCPPaintBufferGlPbuffer and \ref QCPPaintBufferGlFbo.
- They are used automatically if \ref QCustomPlot::setOpenGl is enabled.
- */
- /* start documentation of pure virtual functions */
- /*! \fn virtual QCPPainter *QCPAbstractPaintBuffer::startPainting() = 0
- Returns a \ref QCPPainter which is ready to draw to this buffer. The ownership and thus the
- responsibility to delete the painter after the painting operations are complete is given to the
- caller of this method.
- Once you are done using the painter, delete the painter and call \ref donePainting.
- While a painter generated with this method is active, you must not call \ref setSize, \ref
- setDevicePixelRatio or \ref clear.
- This method may return 0, if a painter couldn't be activated on the buffer. This usually
- indicates a problem with the respective painting backend.
- */
- /*! \fn virtual void QCPAbstractPaintBuffer::draw(QCPPainter *painter) const = 0
- Draws the contents of this buffer with the provided \a painter. This is the method that is used
- to finally join all paint buffers and draw them onto the screen.
- */
- /*! \fn virtual void QCPAbstractPaintBuffer::clear(const QColor &color) = 0
- Fills the entire buffer with the provided \a color. To have an empty transparent buffer, use the
- named color \c Qt::transparent.
- This method must not be called if there is currently a painter (acquired with \ref startPainting)
- active.
- */
- /*! \fn virtual void QCPAbstractPaintBuffer::reallocateBuffer() = 0
- Reallocates the internal buffer with the currently configured size (\ref setSize) and device
- pixel ratio, if applicable (\ref setDevicePixelRatio). It is called as soon as any of those
- properties are changed on this paint buffer.
- \note Subclasses of \ref QCPAbstractPaintBuffer must call their reimplementation of this method
- in their constructor, to perform the first allocation (this can not be done by the base class
- because calling pure virtual methods in base class constructors is not possible).
- */
- /* end documentation of pure virtual functions */
- /* start documentation of inline functions */
- /*! \fn virtual void QCPAbstractPaintBuffer::donePainting()
- If you have acquired a \ref QCPPainter to paint onto this paint buffer via \ref startPainting,
- call this method as soon as you are done with the painting operations and have deleted the
- painter.
- paint buffer subclasses may use this method to perform any type of cleanup that is necessary. The
- default implementation does nothing.
- */
- /* end documentation of inline functions */
- /*!
- Creates a paint buffer and initializes it with the provided \a size and \a devicePixelRatio.
- Subclasses must call their \ref reallocateBuffer implementation in their respective constructors.
- */
- QCPAbstractPaintBuffer::QCPAbstractPaintBuffer(const QSize &size, double devicePixelRatio) :
- mSize(size),
- mDevicePixelRatio(devicePixelRatio),
- mInvalidated(true)
- {
- }
- QCPAbstractPaintBuffer::~QCPAbstractPaintBuffer()
- {
- }
- /*!
- Sets the paint buffer size.
- The buffer is reallocated (by calling \ref reallocateBuffer), so any painters that were obtained
- by \ref startPainting are invalidated and must not be used after calling this method.
- If \a size is already the current buffer size, this method does nothing.
- */
- void QCPAbstractPaintBuffer::setSize(const QSize &size)
- {
- if (mSize != size)
- {
- mSize = size;
- reallocateBuffer();
- }
- }
- /*!
- Sets the invalidated flag to \a invalidated.
- This mechanism is used internally in conjunction with isolated replotting of \ref QCPLayer
- instances (in \ref QCPLayer::lmBuffered mode). If \ref QCPLayer::replot is called on a buffered
- layer, i.e. an isolated repaint of only that layer (and its dedicated paint buffer) is requested,
- QCustomPlot will decide depending on the invalidated flags of other paint buffers whether it also
- replots them, instead of only the layer on which the replot was called.
- The invalidated flag is set to true when \ref QCPLayer association has changed, i.e. if layers
- were added or removed from this buffer, or if they were reordered. It is set to false as soon as
- all associated \ref QCPLayer instances are drawn onto the buffer.
- Under normal circumstances, it is not necessary to manually call this method.
- */
- void QCPAbstractPaintBuffer::setInvalidated(bool invalidated)
- {
- mInvalidated = invalidated;
- }
- /*!
- Sets the the device pixel ratio to \a ratio. This is useful to render on high-DPI output devices.
- The ratio is automatically set to the device pixel ratio used by the parent QCustomPlot instance.
- The buffer is reallocated (by calling \ref reallocateBuffer), so any painters that were obtained
- by \ref startPainting are invalidated and must not be used after calling this method.
- \note This method is only available for Qt versions 5.4 and higher.
- */
- void QCPAbstractPaintBuffer::setDevicePixelRatio(double ratio)
- {
- if (!qFuzzyCompare(ratio, mDevicePixelRatio))
- {
- #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
- mDevicePixelRatio = ratio;
- reallocateBuffer();
- #else
- qDebug() << Q_FUNC_INFO << "Device pixel ratios not supported for Qt versions before 5.4";
- mDevicePixelRatio = 1.0;
- #endif
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPPaintBufferPixmap
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPPaintBufferPixmap
- \brief A paint buffer based on QPixmap, using software raster rendering
- This paint buffer is the default and fall-back paint buffer which uses software rendering and
- QPixmap as internal buffer. It is used if \ref QCustomPlot::setOpenGl is false.
- */
- /*!
- Creates a pixmap paint buffer instancen with the specified \a size and \a devicePixelRatio, if
- applicable.
- */
- QCPPaintBufferPixmap::QCPPaintBufferPixmap(const QSize &size, double devicePixelRatio) :
- QCPAbstractPaintBuffer(size, devicePixelRatio)
- {
- QCPPaintBufferPixmap::reallocateBuffer();
- }
- QCPPaintBufferPixmap::~QCPPaintBufferPixmap()
- {
- }
- /* inherits documentation from base class */
- QCPPainter *QCPPaintBufferPixmap::startPainting()
- {
- QCPPainter *result = new QCPPainter(&mBuffer);
- result->setRenderHint(QPainter::HighQualityAntialiasing);
- return result;
- }
- /* inherits documentation from base class */
- void QCPPaintBufferPixmap::draw(QCPPainter *painter) const
- {
- if (painter && painter->isActive())
- painter->drawPixmap(0, 0, mBuffer);
- else
- qDebug() << Q_FUNC_INFO << "invalid or inactive painter passed";
- }
- /* inherits documentation from base class */
- void QCPPaintBufferPixmap::clear(const QColor &color)
- {
- mBuffer.fill(color);
- }
- /* inherits documentation from base class */
- void QCPPaintBufferPixmap::reallocateBuffer()
- {
- setInvalidated();
- if (!qFuzzyCompare(1.0, mDevicePixelRatio))
- {
- #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
- mBuffer = QPixmap(mSize*mDevicePixelRatio);
- mBuffer.setDevicePixelRatio(mDevicePixelRatio);
- #else
- qDebug() << Q_FUNC_INFO << "Device pixel ratios not supported for Qt versions before 5.4";
- mDevicePixelRatio = 1.0;
- mBuffer = QPixmap(mSize);
- #endif
- } else
- {
- mBuffer = QPixmap(mSize);
- }
- }
- #ifdef QCP_OPENGL_PBUFFER
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPPaintBufferGlPbuffer
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPPaintBufferGlPbuffer
- \brief A paint buffer based on OpenGL pixel buffers, using hardware accelerated rendering
- This paint buffer is one of the OpenGL paint buffers which facilitate hardware accelerated plot
- rendering. It is based on OpenGL pixel buffers (pbuffer) and is used in Qt versions before 5.0.
- (See \ref QCPPaintBufferGlFbo used in newer Qt versions.)
- The OpenGL paint buffers are used if \ref QCustomPlot::setOpenGl is set to true, and if they are
- supported by the system.
- */
- /*!
- Creates a \ref QCPPaintBufferGlPbuffer instance with the specified \a size and \a
- devicePixelRatio, if applicable.
- The parameter \a multisamples defines how many samples are used per pixel. Higher values thus
- result in higher quality antialiasing. If the specified \a multisamples value exceeds the
- capability of the graphics hardware, the highest supported multisampling is used.
- */
- QCPPaintBufferGlPbuffer::QCPPaintBufferGlPbuffer(const QSize &size, double devicePixelRatio, int multisamples) :
- QCPAbstractPaintBuffer(size, devicePixelRatio),
- mGlPBuffer(0),
- mMultisamples(qMax(0, multisamples))
- {
- QCPPaintBufferGlPbuffer::reallocateBuffer();
- }
- QCPPaintBufferGlPbuffer::~QCPPaintBufferGlPbuffer()
- {
- if (mGlPBuffer)
- delete mGlPBuffer;
- }
- /* inherits documentation from base class */
- QCPPainter *QCPPaintBufferGlPbuffer::startPainting()
- {
- if (!mGlPBuffer->isValid())
- {
- qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?";
- return 0;
- }
-
- QCPPainter *result = new QCPPainter(mGlPBuffer);
- result->setRenderHint(QPainter::HighQualityAntialiasing);
- return result;
- }
- /* inherits documentation from base class */
- void QCPPaintBufferGlPbuffer::draw(QCPPainter *painter) const
- {
- if (!painter || !painter->isActive())
- {
- qDebug() << Q_FUNC_INFO << "invalid or inactive painter passed";
- return;
- }
- if (!mGlPBuffer->isValid())
- {
- qDebug() << Q_FUNC_INFO << "OpenGL pbuffer isn't valid, reallocateBuffer was not called?";
- return;
- }
- painter->drawImage(0, 0, mGlPBuffer->toImage());
- }
- /* inherits documentation from base class */
- void QCPPaintBufferGlPbuffer::clear(const QColor &color)
- {
- if (mGlPBuffer->isValid())
- {
- mGlPBuffer->makeCurrent();
- glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF());
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- mGlPBuffer->doneCurrent();
- } else
- qDebug() << Q_FUNC_INFO << "OpenGL pbuffer invalid or context not current";
- }
- /* inherits documentation from base class */
- void QCPPaintBufferGlPbuffer::reallocateBuffer()
- {
- if (mGlPBuffer)
- delete mGlPBuffer;
-
- QGLFormat format;
- format.setAlpha(true);
- format.setSamples(mMultisamples);
- mGlPBuffer = new QGLPixelBuffer(mSize, format);
- }
- #endif // QCP_OPENGL_PBUFFER
- #ifdef QCP_OPENGL_FBO
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPPaintBufferGlFbo
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPPaintBufferGlFbo
- \brief A paint buffer based on OpenGL frame buffers objects, using hardware accelerated rendering
- This paint buffer is one of the OpenGL paint buffers which facilitate hardware accelerated plot
- rendering. It is based on OpenGL frame buffer objects (fbo) and is used in Qt versions 5.0 and
- higher. (See \ref QCPPaintBufferGlPbuffer used in older Qt versions.)
- The OpenGL paint buffers are used if \ref QCustomPlot::setOpenGl is set to true, and if they are
- supported by the system.
- */
- /*!
- Creates a \ref QCPPaintBufferGlFbo instance with the specified \a size and \a devicePixelRatio,
- if applicable.
- All frame buffer objects shall share one OpenGL context and paint device, which need to be set up
- externally and passed via \a glContext and \a glPaintDevice. The set-up is done in \ref
- QCustomPlot::setupOpenGl and the context and paint device are managed by the parent QCustomPlot
- instance.
- */
- QCPPaintBufferGlFbo::QCPPaintBufferGlFbo(const QSize &size, double devicePixelRatio, QWeakPointer<QOpenGLContext> glContext, QWeakPointer<QOpenGLPaintDevice> glPaintDevice) :
- QCPAbstractPaintBuffer(size, devicePixelRatio),
- mGlContext(glContext),
- mGlPaintDevice(glPaintDevice),
- mGlFrameBuffer(0)
- {
- QCPPaintBufferGlFbo::reallocateBuffer();
- }
- QCPPaintBufferGlFbo::~QCPPaintBufferGlFbo()
- {
- if (mGlFrameBuffer)
- delete mGlFrameBuffer;
- }
- /* inherits documentation from base class */
- QCPPainter *QCPPaintBufferGlFbo::startPainting()
- {
- if (mGlPaintDevice.isNull())
- {
- qDebug() << Q_FUNC_INFO << "OpenGL paint device doesn't exist";
- return 0;
- }
- if (!mGlFrameBuffer)
- {
- qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?";
- return 0;
- }
-
- if (QOpenGLContext::currentContext() != mGlContext.data())
- mGlContext.data()->makeCurrent(mGlContext.data()->surface());
- mGlFrameBuffer->bind();
- QCPPainter *result = new QCPPainter(mGlPaintDevice.data());
- result->setRenderHint(QPainter::HighQualityAntialiasing);
- return result;
- }
- /* inherits documentation from base class */
- void QCPPaintBufferGlFbo::donePainting()
- {
- if (mGlFrameBuffer && mGlFrameBuffer->isBound())
- mGlFrameBuffer->release();
- else
- qDebug() << Q_FUNC_INFO << "Either OpenGL frame buffer not valid or was not bound";
- }
- /* inherits documentation from base class */
- void QCPPaintBufferGlFbo::draw(QCPPainter *painter) const
- {
- if (!painter || !painter->isActive())
- {
- qDebug() << Q_FUNC_INFO << "invalid or inactive painter passed";
- return;
- }
- if (!mGlFrameBuffer)
- {
- qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?";
- return;
- }
- painter->drawImage(0, 0, mGlFrameBuffer->toImage());
- }
- /* inherits documentation from base class */
- void QCPPaintBufferGlFbo::clear(const QColor &color)
- {
- if (mGlContext.isNull())
- {
- qDebug() << Q_FUNC_INFO << "OpenGL context doesn't exist";
- return;
- }
- if (!mGlFrameBuffer)
- {
- qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?";
- return;
- }
-
- if (QOpenGLContext::currentContext() != mGlContext.data())
- mGlContext.data()->makeCurrent(mGlContext.data()->surface());
- mGlFrameBuffer->bind();
- glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF());
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- mGlFrameBuffer->release();
- }
- /* inherits documentation from base class */
- void QCPPaintBufferGlFbo::reallocateBuffer()
- {
- // release and delete possibly existing framebuffer:
- if (mGlFrameBuffer)
- {
- if (mGlFrameBuffer->isBound())
- mGlFrameBuffer->release();
- delete mGlFrameBuffer;
- mGlFrameBuffer = 0;
- }
-
- if (mGlContext.isNull())
- {
- qDebug() << Q_FUNC_INFO << "OpenGL context doesn't exist";
- return;
- }
- if (mGlPaintDevice.isNull())
- {
- qDebug() << Q_FUNC_INFO << "OpenGL paint device doesn't exist";
- return;
- }
-
- // create new fbo with appropriate size:
- mGlContext.data()->makeCurrent(mGlContext.data()->surface());
- QOpenGLFramebufferObjectFormat frameBufferFormat;
- frameBufferFormat.setSamples(mGlContext.data()->format().samples());
- frameBufferFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
- mGlFrameBuffer = new QOpenGLFramebufferObject(mSize*mDevicePixelRatio, frameBufferFormat);
- if (mGlPaintDevice.data()->size() != mSize*mDevicePixelRatio)
- mGlPaintDevice.data()->setSize(mSize*mDevicePixelRatio);
- #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
- mGlPaintDevice.data()->setDevicePixelRatio(mDevicePixelRatio);
- #endif
- }
- #endif // QCP_OPENGL_FBO
- /* end of 'src/paintbuffer.cpp' */
- /* including file 'src/layer.cpp', size 37064 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPLayer
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPLayer
- \brief A layer that may contain objects, to control the rendering order
- The Layering system of QCustomPlot is the mechanism to control the rendering order of the
- elements inside the plot.
- It is based on the two classes QCPLayer and QCPLayerable. QCustomPlot holds an ordered list of
- one or more instances of QCPLayer (see QCustomPlot::addLayer, QCustomPlot::layer,
- QCustomPlot::moveLayer, etc.). When replotting, QCustomPlot goes through the list of layers
- bottom to top and successively draws the layerables of the layers into the paint buffer(s).
- A QCPLayer contains an ordered list of QCPLayerable instances. QCPLayerable is an abstract base
- class from which almost all visible objects derive, like axes, grids, graphs, items, etc.
- \section qcplayer-defaultlayers Default layers
- Initially, QCustomPlot has six layers: "background", "grid", "main", "axes", "legend" and
- "overlay" (in that order). On top is the "overlay" layer, which only contains the QCustomPlot's
- selection rect (\ref QCustomPlot::selectionRect). The next two layers "axes" and "legend" contain
- the default axes and legend, so they will be drawn above plottables. In the middle, there is the
- "main" layer. It is initially empty and set as the current layer (see
- QCustomPlot::setCurrentLayer). This means, all new plottables, items etc. are created on this
- layer by default. Then comes the "grid" layer which contains the QCPGrid instances (which belong
- tightly to QCPAxis, see \ref QCPAxis::grid). The Axis rect background shall be drawn behind
- everything else, thus the default QCPAxisRect instance is placed on the "background" layer. Of
- course, the layer affiliation of the individual objects can be changed as required (\ref
- QCPLayerable::setLayer).
- \section qcplayer-ordering Controlling the rendering order via layers
- Controlling the ordering of layerables in the plot is easy: Create a new layer in the position
- you want the layerable to be in, e.g. above "main", with \ref QCustomPlot::addLayer. Then set the
- current layer with \ref QCustomPlot::setCurrentLayer to that new layer and finally create the
- objects normally. They will be placed on the new layer automatically, due to the current layer
- setting. Alternatively you could have also ignored the current layer setting and just moved the
- objects with \ref QCPLayerable::setLayer to the desired layer after creating them.
- It is also possible to move whole layers. For example, If you want the grid to be shown in front
- of all plottables/items on the "main" layer, just move it above "main" with
- QCustomPlot::moveLayer.
- The rendering order within one layer is simply by order of creation or insertion. The item
- created last (or added last to the layer), is drawn on top of all other objects on that layer.
- When a layer is deleted, the objects on it are not deleted with it, but fall on the layer below
- the deleted layer, see QCustomPlot::removeLayer.
- \section qcplayer-buffering Replotting only a specific layer
- If the layer mode (\ref setMode) is set to \ref lmBuffered, you can replot only this specific
- layer by calling \ref replot. In certain situations this can provide better replot performance,
- compared with a full replot of all layers. Upon creation of a new layer, the layer mode is
- initialized to \ref lmLogical. The only layer that is set to \ref lmBuffered in a new \ref
- QCustomPlot instance is the "overlay" layer, containing the selection rect.
- */
- /* start documentation of inline functions */
- /*! \fn QList<QCPLayerable*> QCPLayer::children() const
-
- Returns a list of all layerables on this layer. The order corresponds to the rendering order:
- layerables with higher indices are drawn above layerables with lower indices.
- */
- /*! \fn int QCPLayer::index() const
-
- Returns the index this layer has in the QCustomPlot. The index is the integer number by which this layer can be
- accessed via \ref QCustomPlot::layer.
-
- Layers with higher indices will be drawn above layers with lower indices.
- */
- /* end documentation of inline functions */
- /*!
- Creates a new QCPLayer instance.
-
- Normally you shouldn't directly instantiate layers, use \ref QCustomPlot::addLayer instead.
-
- \warning It is not checked that \a layerName is actually a unique layer name in \a parentPlot.
- This check is only performed by \ref QCustomPlot::addLayer.
- */
- QCPLayer::QCPLayer(QCustomPlot *parentPlot, const QString &layerName) :
- QObject(parentPlot),
- mParentPlot(parentPlot),
- mName(layerName),
- mIndex(-1), // will be set to a proper value by the QCustomPlot layer creation function
- mVisible(true),
- mMode(lmLogical)
- {
- // Note: no need to make sure layerName is unique, because layer
- // management is done with QCustomPlot functions.
- }
- QCPLayer::~QCPLayer()
- {
- // If child layerables are still on this layer, detach them, so they don't try to reach back to this
- // then invalid layer once they get deleted/moved themselves. This only happens when layers are deleted
- // directly, like in the QCustomPlot destructor. (The regular layer removal procedure for the user is to
- // call QCustomPlot::removeLayer, which moves all layerables off this layer before deleting it.)
-
- while (!mChildren.isEmpty())
- mChildren.last()->setLayer(0); // removes itself from mChildren via removeChild()
-
- if (mParentPlot->currentLayer() == this)
- qDebug() << Q_FUNC_INFO << "The parent plot's mCurrentLayer will be a dangling pointer. Should have been set to a valid layer or 0 beforehand.";
- }
- /*!
- Sets whether this layer is visible or not. If \a visible is set to false, all layerables on this
- layer will be invisible.
- This function doesn't change the visibility property of the layerables (\ref
- QCPLayerable::setVisible), but the \ref QCPLayerable::realVisibility of each layerable takes the
- visibility of the parent layer into account.
- */
- void QCPLayer::setVisible(bool visible)
- {
- mVisible = visible;
- }
- /*!
- Sets the rendering mode of this layer.
- If \a mode is set to \ref lmBuffered for a layer, it will be given a dedicated paint buffer by
- the parent QCustomPlot instance. This means it may be replotted individually by calling \ref
- QCPLayer::replot, without needing to replot all other layers.
- Layers which are set to \ref lmLogical (the default) are used only to define the rendering order
- and can't be replotted individually.
- Note that each layer which is set to \ref lmBuffered requires additional paint buffers for the
- layers below, above and for the layer itself. This increases the memory consumption and
- (slightly) decreases the repainting speed because multiple paint buffers need to be joined. So
- you should carefully choose which layers benefit from having their own paint buffer. A typical
- example would be a layer which contains certain layerables (e.g. items) that need to be changed
- and thus replotted regularly, while all other layerables on other layers stay static. By default,
- only the topmost layer called "overlay" is in mode \ref lmBuffered, and contains the selection
- rect.
- \see replot
- */
- void QCPLayer::setMode(QCPLayer::LayerMode mode)
- {
- if (mMode != mode)
- {
- mMode = mode;
- if (!mPaintBuffer.isNull())
- mPaintBuffer.data()->setInvalidated();
- }
- }
- /*! \internal
- Draws the contents of this layer with the provided \a painter.
- \see replot, drawToPaintBuffer
- */
- void QCPLayer::draw(QCPPainter *painter)
- {
- foreach (QCPLayerable *child, mChildren)
- {
- if (child->realVisibility())
- {
- painter->save();
- painter->setClipRect(child->clipRect().translated(0, -1));
- child->applyDefaultAntialiasingHint(painter);
- child->draw(painter);
- painter->restore();
- }
- }
- }
- /*! \internal
- Draws the contents of this layer into the paint buffer which is associated with this layer. The
- association is established by the parent QCustomPlot, which manages all paint buffers (see \ref
- QCustomPlot::setupPaintBuffers).
- \see draw
- */
- void QCPLayer::drawToPaintBuffer()
- {
- if (!mPaintBuffer.isNull())
- {
- if (QCPPainter *painter = mPaintBuffer.data()->startPainting())
- {
- if (painter->isActive())
- draw(painter);
- else
- qDebug() << Q_FUNC_INFO << "paint buffer returned inactive painter";
- delete painter;
- mPaintBuffer.data()->donePainting();
- } else
- qDebug() << Q_FUNC_INFO << "paint buffer returned zero painter";
- } else
- qDebug() << Q_FUNC_INFO << "no valid paint buffer associated with this layer";
- }
- /*!
- If the layer mode (\ref setMode) is set to \ref lmBuffered, this method allows replotting only
- the layerables on this specific layer, without the need to replot all other layers (as a call to
- \ref QCustomPlot::replot would do).
- If the layer mode is \ref lmLogical however, this method simply calls \ref QCustomPlot::replot on
- the parent QCustomPlot instance.
- QCustomPlot also makes sure to replot all layers instead of only this one, if the layer ordering
- has changed since the last full replot and the other paint buffers were thus invalidated.
- \see draw
- */
- void QCPLayer::replot()
- {
- if (mMode == lmBuffered && !mParentPlot->hasInvalidatedPaintBuffers())
- {
- if (!mPaintBuffer.isNull())
- {
- mPaintBuffer.data()->clear(Qt::transparent);
- drawToPaintBuffer();
- mPaintBuffer.data()->setInvalidated(false);
- mParentPlot->update();
- } else
- qDebug() << Q_FUNC_INFO << "no valid paint buffer associated with this layer";
- } else if (mMode == lmLogical)
- mParentPlot->replot();
- }
- /*! \internal
-
- Adds the \a layerable to the list of this layer. If \a prepend is set to true, the layerable will
- be prepended to the list, i.e. be drawn beneath the other layerables already in the list.
-
- This function does not change the \a mLayer member of \a layerable to this layer. (Use
- QCPLayerable::setLayer to change the layer of an object, not this function.)
-
- \see removeChild
- */
- void QCPLayer::addChild(QCPLayerable *layerable, bool prepend)
- {
- if (!mChildren.contains(layerable))
- {
- if (prepend)
- mChildren.prepend(layerable);
- else
- mChildren.append(layerable);
- if (!mPaintBuffer.isNull())
- mPaintBuffer.data()->setInvalidated();
- } else
- qDebug() << Q_FUNC_INFO << "layerable is already child of this layer" << reinterpret_cast<quintptr>(layerable);
- }
- /*! \internal
-
- Removes the \a layerable from the list of this layer.
-
- This function does not change the \a mLayer member of \a layerable. (Use QCPLayerable::setLayer
- to change the layer of an object, not this function.)
-
- \see addChild
- */
- void QCPLayer::removeChild(QCPLayerable *layerable)
- {
- if (mChildren.removeOne(layerable))
- {
- if (!mPaintBuffer.isNull())
- mPaintBuffer.data()->setInvalidated();
- } else
- qDebug() << Q_FUNC_INFO << "layerable is not child of this layer" << reinterpret_cast<quintptr>(layerable);
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPLayerable
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPLayerable
- \brief Base class for all drawable objects
-
- This is the abstract base class most visible objects derive from, e.g. plottables, axes, grid
- etc.
- Every layerable is on a layer (QCPLayer) which allows controlling the rendering order by stacking
- the layers accordingly.
-
- For details about the layering mechanism, see the QCPLayer documentation.
- */
- /* start documentation of inline functions */
- /*! \fn QCPLayerable *QCPLayerable::parentLayerable() const
-
- Returns the parent layerable of this layerable. The parent layerable is used to provide
- visibility hierarchies in conjunction with the method \ref realVisibility. This way, layerables
- only get drawn if their parent layerables are visible, too.
-
- Note that a parent layerable is not necessarily also the QObject parent for memory management.
- Further, a layerable doesn't always have a parent layerable, so this function may return 0.
-
- A parent layerable is set implicitly when placed inside layout elements and doesn't need to be
- set manually by the user.
- */
- /* end documentation of inline functions */
- /* start documentation of pure virtual functions */
- /*! \fn virtual void QCPLayerable::applyDefaultAntialiasingHint(QCPPainter *painter) const = 0
- \internal
-
- This function applies the default antialiasing setting to the specified \a painter, using the
- function \ref applyAntialiasingHint. It is the antialiasing state the painter is put in, when
- \ref draw is called on the layerable. If the layerable has multiple entities whose antialiasing
- setting may be specified individually, this function should set the antialiasing state of the
- most prominent entity. In this case however, the \ref draw function usually calls the specialized
- versions of this function before drawing each entity, effectively overriding the setting of the
- default antialiasing hint.
-
- <b>First example:</b> QCPGraph has multiple entities that have an antialiasing setting: The graph
- line, fills and scatters. Those can be configured via QCPGraph::setAntialiased,
- QCPGraph::setAntialiasedFill and QCPGraph::setAntialiasedScatters. Consequently, there isn't only
- the QCPGraph::applyDefaultAntialiasingHint function (which corresponds to the graph line's
- antialiasing), but specialized ones like QCPGraph::applyFillAntialiasingHint and
- QCPGraph::applyScattersAntialiasingHint. So before drawing one of those entities, QCPGraph::draw
- calls the respective specialized applyAntialiasingHint function.
-
- <b>Second example:</b> QCPItemLine consists only of a line so there is only one antialiasing
- setting which can be controlled with QCPItemLine::setAntialiased. (This function is inherited by
- all layerables. The specialized functions, as seen on QCPGraph, must be added explicitly to the
- respective layerable subclass.) Consequently it only has the normal
- QCPItemLine::applyDefaultAntialiasingHint. The \ref QCPItemLine::draw function doesn't need to
- care about setting any antialiasing states, because the default antialiasing hint is already set
- on the painter when the \ref draw function is called, and that's the state it wants to draw the
- line with.
- */
- /*! \fn virtual void QCPLayerable::draw(QCPPainter *painter) const = 0
- \internal
-
- This function draws the layerable with the specified \a painter. It is only called by
- QCustomPlot, if the layerable is visible (\ref setVisible).
-
- Before this function is called, the painter's antialiasing state is set via \ref
- applyDefaultAntialiasingHint, see the documentation there. Further, the clipping rectangle was
- set to \ref clipRect.
- */
- /* end documentation of pure virtual functions */
- /* start documentation of signals */
- /*! \fn void QCPLayerable::layerChanged(QCPLayer *newLayer);
-
- This signal is emitted when the layer of this layerable changes, i.e. this layerable is moved to
- a different layer.
-
- \see setLayer
- */
- /* end documentation of signals */
- /*!
- Creates a new QCPLayerable instance.
-
- Since QCPLayerable is an abstract base class, it can't be instantiated directly. Use one of the
- derived classes.
-
- If \a plot is provided, it automatically places itself on the layer named \a targetLayer. If \a
- targetLayer is an empty string, it places itself on the current layer of the plot (see \ref
- QCustomPlot::setCurrentLayer).
-
- It is possible to provide 0 as \a plot. In that case, you should assign a parent plot at a later
- time with \ref initializeParentPlot.
-
- The layerable's parent layerable is set to \a parentLayerable, if provided. Direct layerable
- parents are mainly used to control visibility in a hierarchy of layerables. This means a
- layerable is only drawn, if all its ancestor layerables are also visible. Note that \a
- parentLayerable does not become the QObject-parent (for memory management) of this layerable, \a
- plot does. It is not uncommon to set the QObject-parent to something else in the constructors of
- QCPLayerable subclasses, to guarantee a working destruction hierarchy.
- */
- QCPLayerable::QCPLayerable(QCustomPlot *plot, QString targetLayer, QCPLayerable *parentLayerable) :
- QObject(plot),
- mVisible(true),
- mParentPlot(plot),
- mParentLayerable(parentLayerable),
- mLayer(0),
- mAntialiased(true)
- {
- if (mParentPlot)
- {
- if (targetLayer.isEmpty())
- setLayer(mParentPlot->currentLayer());
- else if (!setLayer(targetLayer))
- qDebug() << Q_FUNC_INFO << "setting QCPlayerable initial layer to" << targetLayer << "failed.";
- }
- }
- QCPLayerable::~QCPLayerable()
- {
- if (mLayer)
- {
- mLayer->removeChild(this);
- mLayer = 0;
- }
- }
- /*!
- Sets the visibility of this layerable object. If an object is not visible, it will not be drawn
- on the QCustomPlot surface, and user interaction with it (e.g. click and selection) is not
- possible.
- */
- void QCPLayerable::setVisible(bool on)
- {
- mVisible = on;
- }
- /*!
- Sets the \a layer of this layerable object. The object will be placed on top of the other objects
- already on \a layer.
-
- If \a layer is 0, this layerable will not be on any layer and thus not appear in the plot (or
- interact/receive events).
-
- Returns true if the layer of this layerable was successfully changed to \a layer.
- */
- bool QCPLayerable::setLayer(QCPLayer *layer)
- {
- return moveToLayer(layer, false);
- }
- /*! \overload
- Sets the layer of this layerable object by name
-
- Returns true on success, i.e. if \a layerName is a valid layer name.
- */
- bool QCPLayerable::setLayer(const QString &layerName)
- {
- if (!mParentPlot)
- {
- qDebug() << Q_FUNC_INFO << "no parent QCustomPlot set";
- return false;
- }
- if (QCPLayer *layer = mParentPlot->layer(layerName))
- {
- return setLayer(layer);
- } else
- {
- qDebug() << Q_FUNC_INFO << "there is no layer with name" << layerName;
- return false;
- }
- }
- /*!
- Sets whether this object will be drawn antialiased or not.
-
- Note that antialiasing settings may be overridden by QCustomPlot::setAntialiasedElements and
- QCustomPlot::setNotAntialiasedElements.
- */
- void QCPLayerable::setAntialiased(bool enabled)
- {
- mAntialiased = enabled;
- }
- /*!
- Returns whether this layerable is visible, taking the visibility of the layerable parent and the
- visibility of this layerable's layer into account. This is the method that is consulted to decide
- whether a layerable shall be drawn or not.
-
- If this layerable has a direct layerable parent (usually set via hierarchies implemented in
- subclasses, like in the case of \ref QCPLayoutElement), this function returns true only if this
- layerable has its visibility set to true and the parent layerable's \ref realVisibility returns
- true.
- */
- bool QCPLayerable::realVisibility() const
- {
- return mVisible && (!mLayer || mLayer->visible()) && (!mParentLayerable || mParentLayerable.data()->realVisibility());
- }
- /*!
- This function is used to decide whether a click hits a layerable object or not.
- \a pos is a point in pixel coordinates on the QCustomPlot surface. This function returns the
- shortest pixel distance of this point to the object. If the object is either invisible or the
- distance couldn't be determined, -1.0 is returned. Further, if \a onlySelectable is true and the
- object is not selectable, -1.0 is returned, too.
- If the object is represented not by single lines but by an area like a \ref QCPItemText or the
- bars of a \ref QCPBars plottable, a click inside the area should also be considered a hit. In
- these cases this function thus returns a constant value greater zero but still below the parent
- plot's selection tolerance. (typically the selectionTolerance multiplied by 0.99).
-
- Providing a constant value for area objects allows selecting line objects even when they are
- obscured by such area objects, by clicking close to the lines (i.e. closer than
- 0.99*selectionTolerance).
-
- The actual setting of the selection state is not done by this function. This is handled by the
- parent QCustomPlot when the mouseReleaseEvent occurs, and the finally selected object is notified
- via the \ref selectEvent/\ref deselectEvent methods.
-
- \a details is an optional output parameter. Every layerable subclass may place any information
- in \a details. This information will be passed to \ref selectEvent when the parent QCustomPlot
- decides on the basis of this selectTest call, that the object was successfully selected. The
- subsequent call to \ref selectEvent will carry the \a details. This is useful for multi-part
- objects (like QCPAxis). This way, a possibly complex calculation to decide which part was clicked
- is only done once in \ref selectTest. The result (i.e. the actually clicked part) can then be
- placed in \a details. So in the subsequent \ref selectEvent, the decition which part was
- selected doesn't have to be done a second time for a single selection operation.
-
- You may pass 0 as \a details to indicate that you are not interested in those selection details.
-
- \see selectEvent, deselectEvent, mousePressEvent, wheelEvent, QCustomPlot::setInteractions
- */
- double QCPLayerable::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- Q_UNUSED(pos)
- Q_UNUSED(onlySelectable)
- Q_UNUSED(details)
- return -1.0;
- }
- /*! \internal
-
- Sets the parent plot of this layerable. Use this function once to set the parent plot if you have
- passed 0 in the constructor. It can not be used to move a layerable from one QCustomPlot to
- another one.
-
- Note that, unlike when passing a non-null parent plot in the constructor, this function does not
- make \a parentPlot the QObject-parent of this layerable. If you want this, call
- QObject::setParent(\a parentPlot) in addition to this function.
-
- Further, you will probably want to set a layer (\ref setLayer) after calling this function, to
- make the layerable appear on the QCustomPlot.
-
- The parent plot change will be propagated to subclasses via a call to \ref parentPlotInitialized
- so they can react accordingly (e.g. also initialize the parent plot of child layerables, like
- QCPLayout does).
- */
- void QCPLayerable::initializeParentPlot(QCustomPlot *parentPlot)
- {
- if (mParentPlot)
- {
- qDebug() << Q_FUNC_INFO << "called with mParentPlot already initialized";
- return;
- }
-
- if (!parentPlot)
- qDebug() << Q_FUNC_INFO << "called with parentPlot zero";
-
- mParentPlot = parentPlot;
- parentPlotInitialized(mParentPlot);
- }
- /*! \internal
-
- Sets the parent layerable of this layerable to \a parentLayerable. Note that \a parentLayerable does not
- become the QObject-parent (for memory management) of this layerable.
-
- The parent layerable has influence on the return value of the \ref realVisibility method. Only
- layerables with a fully visible parent tree will return true for \ref realVisibility, and thus be
- drawn.
-
- \see realVisibility
- */
- void QCPLayerable::setParentLayerable(QCPLayerable *parentLayerable)
- {
- mParentLayerable = parentLayerable;
- }
- /*! \internal
-
- Moves this layerable object to \a layer. If \a prepend is true, this object will be prepended to
- the new layer's list, i.e. it will be drawn below the objects already on the layer. If it is
- false, the object will be appended.
-
- Returns true on success, i.e. if \a layer is a valid layer.
- */
- bool QCPLayerable::moveToLayer(QCPLayer *layer, bool prepend)
- {
- if (layer && !mParentPlot)
- {
- qDebug() << Q_FUNC_INFO << "no parent QCustomPlot set";
- return false;
- }
- if (layer && layer->parentPlot() != mParentPlot)
- {
- qDebug() << Q_FUNC_INFO << "layer" << layer->name() << "is not in same QCustomPlot as this layerable";
- return false;
- }
-
- QCPLayer *oldLayer = mLayer;
- if (mLayer)
- mLayer->removeChild(this);
- mLayer = layer;
- if (mLayer)
- mLayer->addChild(this, prepend);
- if (mLayer != oldLayer)
- emit layerChanged(mLayer);
- return true;
- }
- /*! \internal
- Sets the QCPainter::setAntialiasing state on the provided \a painter, depending on the \a
- localAntialiased value as well as the overrides \ref QCustomPlot::setAntialiasedElements and \ref
- QCustomPlot::setNotAntialiasedElements. Which override enum this function takes into account is
- controlled via \a overrideElement.
- */
- void QCPLayerable::applyAntialiasingHint(QCPPainter *painter, bool localAntialiased, QCP::AntialiasedElement overrideElement) const
- {
- if (mParentPlot && mParentPlot->notAntialiasedElements().testFlag(overrideElement))
- painter->setAntialiasing(false);
- else if (mParentPlot && mParentPlot->antialiasedElements().testFlag(overrideElement))
- painter->setAntialiasing(true);
- else
- painter->setAntialiasing(localAntialiased);
- }
- /*! \internal
- This function is called by \ref initializeParentPlot, to allow subclasses to react on the setting
- of a parent plot. This is the case when 0 was passed as parent plot in the constructor, and the
- parent plot is set at a later time.
-
- For example, QCPLayoutElement/QCPLayout hierarchies may be created independently of any
- QCustomPlot at first. When they are then added to a layout inside the QCustomPlot, the top level
- element of the hierarchy gets its parent plot initialized with \ref initializeParentPlot. To
- propagate the parent plot to all the children of the hierarchy, the top level element then uses
- this function to pass the parent plot on to its child elements.
-
- The default implementation does nothing.
-
- \see initializeParentPlot
- */
- void QCPLayerable::parentPlotInitialized(QCustomPlot *parentPlot)
- {
- Q_UNUSED(parentPlot)
- }
- /*! \internal
- Returns the selection category this layerable shall belong to. The selection category is used in
- conjunction with \ref QCustomPlot::setInteractions to control which objects are selectable and
- which aren't.
-
- Subclasses that don't fit any of the normal \ref QCP::Interaction values can use \ref
- QCP::iSelectOther. This is what the default implementation returns.
-
- \see QCustomPlot::setInteractions
- */
- QCP::Interaction QCPLayerable::selectionCategory() const
- {
- return QCP::iSelectOther;
- }
- /*! \internal
-
- Returns the clipping rectangle of this layerable object. By default, this is the viewport of the
- parent QCustomPlot. Specific subclasses may reimplement this function to provide different
- clipping rects.
-
- The returned clipping rect is set on the painter before the draw function of the respective
- object is called.
- */
- QRect QCPLayerable::clipRect() const
- {
- if (mParentPlot)
- return mParentPlot->viewport();
- else
- return QRect();
- }
- /*! \internal
-
- This event is called when the layerable shall be selected, as a consequence of a click by the
- user. Subclasses should react to it by setting their selection state appropriately. The default
- implementation does nothing.
-
- \a event is the mouse event that caused the selection. \a additive indicates, whether the user
- was holding the multi-select-modifier while performing the selection (see \ref
- QCustomPlot::setMultiSelectModifier). if \a additive is true, the selection state must be toggled
- (i.e. become selected when unselected and unselected when selected).
-
- Every selectEvent is preceded by a call to \ref selectTest, which has returned positively (i.e.
- returned a value greater than 0 and less than the selection tolerance of the parent QCustomPlot).
- The \a details data you output from \ref selectTest is fed back via \a details here. You may
- use it to transport any kind of information from the selectTest to the possibly subsequent
- selectEvent. Usually \a details is used to transfer which part was clicked, if it is a layerable
- that has multiple individually selectable parts (like QCPAxis). This way selectEvent doesn't need
- to do the calculation again to find out which part was actually clicked.
-
- \a selectionStateChanged is an output parameter. If the pointer is non-null, this function must
- set the value either to true or false, depending on whether the selection state of this layerable
- was actually changed. For layerables that only are selectable as a whole and not in parts, this
- is simple: if \a additive is true, \a selectionStateChanged must also be set to true, because the
- selection toggles. If \a additive is false, \a selectionStateChanged is only set to true, if the
- layerable was previously unselected and now is switched to the selected state.
-
- \see selectTest, deselectEvent
- */
- void QCPLayerable::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
- {
- Q_UNUSED(event)
- Q_UNUSED(additive)
- Q_UNUSED(details)
- Q_UNUSED(selectionStateChanged)
- }
- /*! \internal
-
- This event is called when the layerable shall be deselected, either as consequence of a user
- interaction or a call to \ref QCustomPlot::deselectAll. Subclasses should react to it by
- unsetting their selection appropriately.
-
- just as in \ref selectEvent, the output parameter \a selectionStateChanged (if non-null), must
- return true or false when the selection state of this layerable has changed or not changed,
- respectively.
-
- \see selectTest, selectEvent
- */
- void QCPLayerable::deselectEvent(bool *selectionStateChanged)
- {
- Q_UNUSED(selectionStateChanged)
- }
- /*!
- This event gets called when the user presses a mouse button while the cursor is over the
- layerable. Whether a cursor is over the layerable is decided by a preceding call to \ref
- selectTest.
- The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
- event->pos(). The parameter \a details contains layerable-specific details about the hit, which
- were generated in the previous call to \ref selectTest. For example, One-dimensional plottables
- like \ref QCPGraph or \ref QCPBars convey the clicked data point in the \a details parameter, as
- \ref QCPDataSelection packed as QVariant. Multi-part objects convey the specific \c
- SelectablePart that was hit (e.g. \ref QCPAxis::SelectablePart in the case of axes).
- QCustomPlot uses an event propagation system that works the same as Qt's system. If your
- layerable doesn't reimplement the \ref mousePressEvent or explicitly calls \c event->ignore() in
- its reimplementation, the event will be propagated to the next layerable in the stacking order.
- Once a layerable has accepted the \ref mousePressEvent, it is considered the mouse grabber and
- will receive all following calls to \ref mouseMoveEvent or \ref mouseReleaseEvent for this mouse
- interaction (a "mouse interaction" in this context ends with the release).
- The default implementation does nothing except explicitly ignoring the event with \c
- event->ignore().
- \see mouseMoveEvent, mouseReleaseEvent, mouseDoubleClickEvent, wheelEvent
- */
- void QCPLayerable::mousePressEvent(QMouseEvent *event, const QVariant &details)
- {
- Q_UNUSED(details)
- event->ignore();
- }
- /*!
- This event gets called when the user moves the mouse while holding a mouse button, after this
- layerable has become the mouse grabber by accepting the preceding \ref mousePressEvent.
- The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
- event->pos(). The parameter \a startPos indicates the position where the initial \ref
- mousePressEvent occured, that started the mouse interaction.
- The default implementation does nothing.
- \see mousePressEvent, mouseReleaseEvent, mouseDoubleClickEvent, wheelEvent
- */
- void QCPLayerable::mouseMoveEvent(QMouseEvent *event, const QPointF &startPos)
- {
- Q_UNUSED(startPos)
- event->ignore();
- }
- /*!
- This event gets called when the user releases the mouse button, after this layerable has become
- the mouse grabber by accepting the preceding \ref mousePressEvent.
- The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
- event->pos(). The parameter \a startPos indicates the position where the initial \ref
- mousePressEvent occured, that started the mouse interaction.
- The default implementation does nothing.
- \see mousePressEvent, mouseMoveEvent, mouseDoubleClickEvent, wheelEvent
- */
- void QCPLayerable::mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos)
- {
- Q_UNUSED(startPos)
- event->ignore();
- }
- /*!
- This event gets called when the user presses the mouse button a second time in a double-click,
- while the cursor is over the layerable. Whether a cursor is over the layerable is decided by a
- preceding call to \ref selectTest.
- The \ref mouseDoubleClickEvent is called instead of the second \ref mousePressEvent. So in the
- case of a double-click, the event succession is
- <i>pressEvent – releaseEvent – doubleClickEvent – releaseEvent</i>.
- The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
- event->pos(). The parameter \a details contains layerable-specific details about the hit, which
- were generated in the previous call to \ref selectTest. For example, One-dimensional plottables
- like \ref QCPGraph or \ref QCPBars convey the clicked data point in the \a details parameter, as
- \ref QCPDataSelection packed as QVariant. Multi-part objects convey the specific \c
- SelectablePart that was hit (e.g. \ref QCPAxis::SelectablePart in the case of axes).
- Similarly to \ref mousePressEvent, once a layerable has accepted the \ref mouseDoubleClickEvent,
- it is considered the mouse grabber and will receive all following calls to \ref mouseMoveEvent
- and \ref mouseReleaseEvent for this mouse interaction (a "mouse interaction" in this context ends
- with the release).
- The default implementation does nothing except explicitly ignoring the event with \c
- event->ignore().
- \see mousePressEvent, mouseMoveEvent, mouseReleaseEvent, wheelEvent
- */
- void QCPLayerable::mouseDoubleClickEvent(QMouseEvent *event, const QVariant &details)
- {
- Q_UNUSED(details)
- event->ignore();
- }
- /*!
- This event gets called when the user turns the mouse scroll wheel while the cursor is over the
- layerable. Whether a cursor is over the layerable is decided by a preceding call to \ref
- selectTest.
- The current pixel position of the cursor on the QCustomPlot widget is accessible via \c
- event->pos().
- The \c event->delta() indicates how far the mouse wheel was turned, which is usually +/- 120 for
- single rotation steps. However, if the mouse wheel is turned rapidly, multiple steps may
- accumulate to one event, making \c event->delta() larger. On the other hand, if the wheel has
- very smooth steps or none at all, the delta may be smaller.
- The default implementation does nothing.
- \see mousePressEvent, mouseMoveEvent, mouseReleaseEvent, mouseDoubleClickEvent
- */
- void QCPLayerable::wheelEvent(QWheelEvent *event)
- {
- event->ignore();
- }
- /* end of 'src/layer.cpp' */
- /* including file 'src/axis/range.cpp', size 12221 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPRange
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPRange
- \brief Represents the range an axis is encompassing.
-
- contains a \a lower and \a upper double value and provides convenience input, output and
- modification functions.
-
- \see QCPAxis::setRange
- */
- /* start of documentation of inline functions */
- /*! \fn double QCPRange::size() const
- Returns the size of the range, i.e. \a upper-\a lower
- */
- /*! \fn double QCPRange::center() const
- Returns the center of the range, i.e. (\a upper+\a lower)*0.5
- */
- /*! \fn void QCPRange::normalize()
- Makes sure \a lower is numerically smaller than \a upper. If this is not the case, the values are
- swapped.
- */
- /*! \fn bool QCPRange::contains(double value) const
- Returns true when \a value lies within or exactly on the borders of the range.
- */
- /*! \fn QCPRange &QCPRange::operator+=(const double& value)
- Adds \a value to both boundaries of the range.
- */
- /*! \fn QCPRange &QCPRange::operator-=(const double& value)
- Subtracts \a value from both boundaries of the range.
- */
- /*! \fn QCPRange &QCPRange::operator*=(const double& value)
- Multiplies both boundaries of the range by \a value.
- */
- /*! \fn QCPRange &QCPRange::operator/=(const double& value)
- Divides both boundaries of the range by \a value.
- */
- /* end of documentation of inline functions */
- /*!
- Minimum range size (\a upper - \a lower) the range changing functions will accept. Smaller
- intervals would cause errors due to the 11-bit exponent of double precision numbers,
- corresponding to a minimum magnitude of roughly 1e-308.
- \warning Do not use this constant to indicate "arbitrarily small" values in plotting logic (as
- values that will appear in the plot)! It is intended only as a bound to compare against, e.g. to
- prevent axis ranges from obtaining underflowing ranges.
- \see validRange, maxRange
- */
- const double QCPRange::minRange = 1e-280;
- /*!
- Maximum values (negative and positive) the range will accept in range-changing functions.
- Larger absolute values would cause errors due to the 11-bit exponent of double precision numbers,
- corresponding to a maximum magnitude of roughly 1e308.
- \warning Do not use this constant to indicate "arbitrarily large" values in plotting logic (as
- values that will appear in the plot)! It is intended only as a bound to compare against, e.g. to
- prevent axis ranges from obtaining overflowing ranges.
- \see validRange, minRange
- */
- const double QCPRange::maxRange = 1e250;
- /*!
- Constructs a range with \a lower and \a upper set to zero.
- */
- QCPRange::QCPRange() :
- lower(0),
- upper(0)
- {
- }
- /*! \overload
- Constructs a range with the specified \a lower and \a upper values.
- The resulting range will be normalized (see \ref normalize), so if \a lower is not numerically
- smaller than \a upper, they will be swapped.
- */
- QCPRange::QCPRange(double lower, double upper) :
- lower(lower),
- upper(upper)
- {
- normalize();
- }
- /*! \overload
- Expands this range such that \a otherRange is contained in the new range. It is assumed that both
- this range and \a otherRange are normalized (see \ref normalize).
- If this range contains NaN as lower or upper bound, it will be replaced by the respective bound
- of \a otherRange.
- If \a otherRange is already inside the current range, this function does nothing.
- \see expanded
- */
- void QCPRange::expand(const QCPRange &otherRange)
- {
- if (lower > otherRange.lower || qIsNaN(lower))
- lower = otherRange.lower;
- if (upper < otherRange.upper || qIsNaN(upper))
- upper = otherRange.upper;
- }
- /*! \overload
- Expands this range such that \a includeCoord is contained in the new range. It is assumed that
- this range is normalized (see \ref normalize).
- If this range contains NaN as lower or upper bound, the respective bound will be set to \a
- includeCoord.
- If \a includeCoord is already inside the current range, this function does nothing.
- \see expand
- */
- void QCPRange::expand(double includeCoord)
- {
- if (lower > includeCoord || qIsNaN(lower))
- lower = includeCoord;
- if (upper < includeCoord || qIsNaN(upper))
- upper = includeCoord;
- }
- /*! \overload
- Returns an expanded range that contains this and \a otherRange. It is assumed that both this
- range and \a otherRange are normalized (see \ref normalize).
- If this range contains NaN as lower or upper bound, the returned range's bound will be taken from
- \a otherRange.
- \see expand
- */
- QCPRange QCPRange::expanded(const QCPRange &otherRange) const
- {
- QCPRange result = *this;
- result.expand(otherRange);
- return result;
- }
- /*! \overload
- Returns an expanded range that includes the specified \a includeCoord. It is assumed that this
- range is normalized (see \ref normalize).
- If this range contains NaN as lower or upper bound, the returned range's bound will be set to \a
- includeCoord.
- \see expand
- */
- QCPRange QCPRange::expanded(double includeCoord) const
- {
- QCPRange result = *this;
- result.expand(includeCoord);
- return result;
- }
- /*!
- Returns this range, possibly modified to not exceed the bounds provided as \a lowerBound and \a
- upperBound. If possible, the size of the current range is preserved in the process.
-
- If the range shall only be bounded at the lower side, you can set \a upperBound to \ref
- QCPRange::maxRange. If it shall only be bounded at the upper side, set \a lowerBound to -\ref
- QCPRange::maxRange.
- */
- QCPRange QCPRange::bounded(double lowerBound, double upperBound) const
- {
- if (lowerBound > upperBound)
- qSwap(lowerBound, upperBound);
-
- QCPRange result(lower, upper);
- if (result.lower < lowerBound)
- {
- result.lower = lowerBound;
- result.upper = lowerBound + size();
- if (result.upper > upperBound || qFuzzyCompare(size(), upperBound-lowerBound))
- result.upper = upperBound;
- } else if (result.upper > upperBound)
- {
- result.upper = upperBound;
- result.lower = upperBound - size();
- if (result.lower < lowerBound || qFuzzyCompare(size(), upperBound-lowerBound))
- result.lower = lowerBound;
- }
-
- return result;
- }
- /*!
- Returns a sanitized version of the range. Sanitized means for logarithmic scales, that
- the range won't span the positive and negative sign domain, i.e. contain zero. Further
- \a lower will always be numerically smaller (or equal) to \a upper.
-
- If the original range does span positive and negative sign domains or contains zero,
- the returned range will try to approximate the original range as good as possible.
- If the positive interval of the original range is wider than the negative interval, the
- returned range will only contain the positive interval, with lower bound set to \a rangeFac or
- \a rangeFac *\a upper, whichever is closer to zero. Same procedure is used if the negative interval
- is wider than the positive interval, this time by changing the \a upper bound.
- */
- QCPRange QCPRange::sanitizedForLogScale() const
- {
- double rangeFac = 1e-3;
- QCPRange sanitizedRange(lower, upper);
- sanitizedRange.normalize();
- // can't have range spanning negative and positive values in log plot, so change range to fix it
- //if (qFuzzyCompare(sanitizedRange.lower+1, 1) && !qFuzzyCompare(sanitizedRange.upper+1, 1))
- if (sanitizedRange.lower == 0.0 && sanitizedRange.upper != 0.0)
- {
- // case lower is 0
- if (rangeFac < sanitizedRange.upper*rangeFac)
- sanitizedRange.lower = rangeFac;
- else
- sanitizedRange.lower = sanitizedRange.upper*rangeFac;
- } //else if (!qFuzzyCompare(lower+1, 1) && qFuzzyCompare(upper+1, 1))
- else if (sanitizedRange.lower != 0.0 && sanitizedRange.upper == 0.0)
- {
- // case upper is 0
- if (-rangeFac > sanitizedRange.lower*rangeFac)
- sanitizedRange.upper = -rangeFac;
- else
- sanitizedRange.upper = sanitizedRange.lower*rangeFac;
- } else if (sanitizedRange.lower < 0 && sanitizedRange.upper > 0)
- {
- // find out whether negative or positive interval is wider to decide which sign domain will be chosen
- if (-sanitizedRange.lower > sanitizedRange.upper)
- {
- // negative is wider, do same as in case upper is 0
- if (-rangeFac > sanitizedRange.lower*rangeFac)
- sanitizedRange.upper = -rangeFac;
- else
- sanitizedRange.upper = sanitizedRange.lower*rangeFac;
- } else
- {
- // positive is wider, do same as in case lower is 0
- if (rangeFac < sanitizedRange.upper*rangeFac)
- sanitizedRange.lower = rangeFac;
- else
- sanitizedRange.lower = sanitizedRange.upper*rangeFac;
- }
- }
- // due to normalization, case lower>0 && upper<0 should never occur, because that implies upper<lower
- return sanitizedRange;
- }
- /*!
- Returns a sanitized version of the range. Sanitized means for linear scales, that
- \a lower will always be numerically smaller (or equal) to \a upper.
- */
- QCPRange QCPRange::sanitizedForLinScale() const
- {
- QCPRange sanitizedRange(lower, upper);
- sanitizedRange.normalize();
- return sanitizedRange;
- }
- /*!
- Checks, whether the specified range is within valid bounds, which are defined
- as QCPRange::maxRange and QCPRange::minRange.
- A valid range means:
- \li range bounds within -maxRange and maxRange
- \li range size above minRange
- \li range size below maxRange
- */
- bool QCPRange::validRange(double lower, double upper)
- {
- return (lower > -maxRange &&
- upper < maxRange &&
- qAbs(lower-upper) > minRange &&
- qAbs(lower-upper) < maxRange &&
- !(lower > 0 && qIsInf(upper/lower)) &&
- !(upper < 0 && qIsInf(lower/upper)));
- }
- /*!
- \overload
- Checks, whether the specified range is within valid bounds, which are defined
- as QCPRange::maxRange and QCPRange::minRange.
- A valid range means:
- \li range bounds within -maxRange and maxRange
- \li range size above minRange
- \li range size below maxRange
- */
- bool QCPRange::validRange(const QCPRange &range)
- {
- return (range.lower > -maxRange &&
- range.upper < maxRange &&
- qAbs(range.lower-range.upper) > minRange &&
- qAbs(range.lower-range.upper) < maxRange &&
- !(range.lower > 0 && qIsInf(range.upper/range.lower)) &&
- !(range.upper < 0 && qIsInf(range.lower/range.upper)));
- }
- /* end of 'src/axis/range.cpp' */
- /* including file 'src/selection.cpp', size 21906 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPDataRange
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPDataRange
- \brief Describes a data range given by begin and end index
-
- QCPDataRange holds two integers describing the begin (\ref setBegin) and end (\ref setEnd) index
- of a contiguous set of data points. The end index points to the data point above the last data point that's part of
- the data range, similarly to the nomenclature used in standard iterators.
-
- Data Ranges are not bound to a certain plottable, thus they can be freely exchanged, created and
- modified. If a non-contiguous data set shall be described, the class \ref QCPDataSelection is
- used, which holds and manages multiple instances of \ref QCPDataRange. In most situations, \ref
- QCPDataSelection is thus used.
-
- Both \ref QCPDataRange and \ref QCPDataSelection offer convenience methods to work with them,
- e.g. \ref bounded, \ref expanded, \ref intersects, \ref intersection, \ref adjusted, \ref
- contains. Further, addition and subtraction operators (defined in \ref QCPDataSelection) can be
- used to join/subtract data ranges and data selections (or mixtures), to retrieve a corresponding
- \ref QCPDataSelection.
-
- %QCustomPlot's \ref dataselection "data selection mechanism" is based on \ref QCPDataSelection and
- QCPDataRange.
-
- \note Do not confuse \ref QCPDataRange with \ref QCPRange. A \ref QCPRange describes an interval
- in floating point plot coordinates, e.g. the current axis range.
- */
- /* start documentation of inline functions */
- /*! \fn int QCPDataRange::size() const
-
- Returns the number of data points described by this data range. This is equal to the end index
- minus the begin index.
-
- \see length
- */
- /*! \fn int QCPDataRange::length() const
-
- Returns the number of data points described by this data range. Equivalent to \ref size.
- */
- /*! \fn void QCPDataRange::setBegin(int begin)
-
- Sets the begin of this data range. The \a begin index points to the first data point that is part
- of the data range.
-
- No checks or corrections are made to ensure the resulting range is valid (\ref isValid).
-
- \see setEnd
- */
- /*! \fn void QCPDataRange::setEnd(int end)
-
- Sets the end of this data range. The \a end index points to the data point just above the last
- data point that is part of the data range.
-
- No checks or corrections are made to ensure the resulting range is valid (\ref isValid).
-
- \see setBegin
- */
- /*! \fn bool QCPDataRange::isValid() const
-
- Returns whether this range is valid. A valid range has a begin index greater or equal to 0, and
- an end index greater or equal to the begin index.
-
- \note Invalid ranges should be avoided and are never the result of any of QCustomPlot's methods
- (unless they are themselves fed with invalid ranges). Do not pass invalid ranges to QCustomPlot's
- methods. The invalid range is not inherently prevented in QCPDataRange, to allow temporary
- invalid begin/end values while manipulating the range. An invalid range is not necessarily empty
- (\ref isEmpty), since its \ref length can be negative and thus non-zero.
- */
- /*! \fn bool QCPDataRange::isEmpty() const
-
- Returns whether this range is empty, i.e. whether its begin index equals its end index.
-
- \see size, length
- */
- /*! \fn QCPDataRange QCPDataRange::adjusted(int changeBegin, int changeEnd) const
-
- Returns a data range where \a changeBegin and \a changeEnd were added to the begin and end
- indices, respectively.
- */
- /* end documentation of inline functions */
- /*!
- Creates an empty QCPDataRange, with begin and end set to 0.
- */
- QCPDataRange::QCPDataRange() :
- mBegin(0),
- mEnd(0)
- {
- }
- /*!
- Creates a QCPDataRange, initialized with the specified \a begin and \a end.
-
- No checks or corrections are made to ensure the resulting range is valid (\ref isValid).
- */
- QCPDataRange::QCPDataRange(int begin, int end) :
- mBegin(begin),
- mEnd(end)
- {
- }
- /*!
- Returns a data range that matches this data range, except that parts exceeding \a other are
- excluded.
-
- This method is very similar to \ref intersection, with one distinction: If this range and the \a
- other range share no intersection, the returned data range will be empty with begin and end set
- to the respective boundary side of \a other, at which this range is residing. (\ref intersection
- would just return a range with begin and end set to 0.)
- */
- QCPDataRange QCPDataRange::bounded(const QCPDataRange &other) const
- {
- QCPDataRange result(intersection(other));
- if (result.isEmpty()) // no intersection, preserve respective bounding side of otherRange as both begin and end of return value
- {
- if (mEnd <= other.mBegin)
- result = QCPDataRange(other.mBegin, other.mBegin);
- else
- result = QCPDataRange(other.mEnd, other.mEnd);
- }
- return result;
- }
- /*!
- Returns a data range that contains both this data range as well as \a other.
- */
- QCPDataRange QCPDataRange::expanded(const QCPDataRange &other) const
- {
- return QCPDataRange(qMin(mBegin, other.mBegin), qMax(mEnd, other.mEnd));
- }
- /*!
- Returns the data range which is contained in both this data range and \a other.
-
- This method is very similar to \ref bounded, with one distinction: If this range and the \a other
- range share no intersection, the returned data range will be empty with begin and end set to 0.
- (\ref bounded would return a range with begin and end set to one of the boundaries of \a other,
- depending on which side this range is on.)
-
- \see QCPDataSelection::intersection
- */
- QCPDataRange QCPDataRange::intersection(const QCPDataRange &other) const
- {
- QCPDataRange result(qMax(mBegin, other.mBegin), qMin(mEnd, other.mEnd));
- if (result.isValid())
- return result;
- else
- return QCPDataRange();
- }
- /*!
- Returns whether this data range and \a other share common data points.
-
- \see intersection, contains
- */
- bool QCPDataRange::intersects(const QCPDataRange &other) const
- {
- return !( (mBegin > other.mBegin && mBegin >= other.mEnd) ||
- (mEnd <= other.mBegin && mEnd < other.mEnd) );
- }
- /*!
- Returns whether all data points described by this data range are also in \a other.
-
- \see intersects
- */
- bool QCPDataRange::contains(const QCPDataRange &other) const
- {
- return mBegin <= other.mBegin && mEnd >= other.mEnd;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPDataSelection
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPDataSelection
- \brief Describes a data set by holding multiple QCPDataRange instances
-
- QCPDataSelection manages multiple instances of QCPDataRange in order to represent any (possibly
- disjoint) set of data selection.
-
- The data selection can be modified with addition and subtraction operators which take
- QCPDataSelection and QCPDataRange instances, as well as methods such as \ref addDataRange and
- \ref clear. Read access is provided by \ref dataRange, \ref dataRanges, \ref dataRangeCount, etc.
-
- The method \ref simplify is used to join directly adjacent or even overlapping QCPDataRange
- instances. QCPDataSelection automatically simplifies when using the addition/subtraction
- operators. The only case when \ref simplify is left to the user, is when calling \ref
- addDataRange, with the parameter \a simplify explicitly set to false. This is useful if many data
- ranges will be added to the selection successively and the overhead for simplifying after each
- iteration shall be avoided. In this case, you should make sure to call \ref simplify after
- completing the operation.
-
- Use \ref enforceType to bring the data selection into a state complying with the constraints for
- selections defined in \ref QCP::SelectionType.
-
- %QCustomPlot's \ref dataselection "data selection mechanism" is based on QCPDataSelection and
- QCPDataRange.
-
- \section qcpdataselection-iterating Iterating over a data selection
-
- As an example, the following code snippet calculates the average value of a graph's data
- \ref QCPAbstractPlottable::selection "selection":
-
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpdataselection-iterating-1
-
- */
- /* start documentation of inline functions */
- /*! \fn int QCPDataSelection::dataRangeCount() const
-
- Returns the number of ranges that make up the data selection. The ranges can be accessed by \ref
- dataRange via their index.
-
- \see dataRange, dataPointCount
- */
- /*! \fn QList<QCPDataRange> QCPDataSelection::dataRanges() const
-
- Returns all data ranges that make up the data selection. If the data selection is simplified (the
- usual state of the selection, see \ref simplify), the ranges are sorted by ascending data point
- index.
-
- \see dataRange
- */
- /*! \fn bool QCPDataSelection::isEmpty() const
-
- Returns true if there are no data ranges, and thus no data points, in this QCPDataSelection
- instance.
-
- \see dataRangeCount
- */
- /* end documentation of inline functions */
- /*!
- Creates an empty QCPDataSelection.
- */
- QCPDataSelection::QCPDataSelection()
- {
- }
- /*!
- Creates a QCPDataSelection containing the provided \a range.
- */
- QCPDataSelection::QCPDataSelection(const QCPDataRange &range)
- {
- mDataRanges.append(range);
- }
- /*!
- Returns true if this selection is identical (contains the same data ranges with the same begin
- and end indices) to \a other.
- Note that both data selections must be in simplified state (the usual state of the selection, see
- \ref simplify) for this operator to return correct results.
- */
- bool QCPDataSelection::operator==(const QCPDataSelection &other) const
- {
- if (mDataRanges.size() != other.mDataRanges.size())
- return false;
- for (int i=0; i<mDataRanges.size(); ++i)
- {
- if (mDataRanges.at(i) != other.mDataRanges.at(i))
- return false;
- }
- return true;
- }
- /*!
- Adds the data selection of \a other to this data selection, and then simplifies this data
- selection (see \ref simplify).
- */
- QCPDataSelection &QCPDataSelection::operator+=(const QCPDataSelection &other)
- {
- mDataRanges << other.mDataRanges;
- simplify();
- return *this;
- }
- /*!
- Adds the data range \a other to this data selection, and then simplifies this data selection (see
- \ref simplify).
- */
- QCPDataSelection &QCPDataSelection::operator+=(const QCPDataRange &other)
- {
- addDataRange(other);
- return *this;
- }
- /*!
- Removes all data point indices that are described by \a other from this data selection.
- */
- QCPDataSelection &QCPDataSelection::operator-=(const QCPDataSelection &other)
- {
- for (int i=0; i<other.dataRangeCount(); ++i)
- *this -= other.dataRange(i);
-
- return *this;
- }
- /*!
- Removes all data point indices that are described by \a other from this data selection.
- */
- QCPDataSelection &QCPDataSelection::operator-=(const QCPDataRange &other)
- {
- if (other.isEmpty() || isEmpty())
- return *this;
-
- simplify();
- int i=0;
- while (i < mDataRanges.size())
- {
- const int thisBegin = mDataRanges.at(i).begin();
- const int thisEnd = mDataRanges.at(i).end();
- if (thisBegin >= other.end())
- break; // since data ranges are sorted after the simplify() call, no ranges which contain other will come after this
-
- if (thisEnd > other.begin()) // ranges which don't fulfill this are entirely before other and can be ignored
- {
- if (thisBegin >= other.begin()) // range leading segment is encompassed
- {
- if (thisEnd <= other.end()) // range fully encompassed, remove completely
- {
- mDataRanges.removeAt(i);
- continue;
- } else // only leading segment is encompassed, trim accordingly
- mDataRanges[i].setBegin(other.end());
- } else // leading segment is not encompassed
- {
- if (thisEnd <= other.end()) // only trailing segment is encompassed, trim accordingly
- {
- mDataRanges[i].setEnd(other.begin());
- } else // other lies inside this range, so split range
- {
- mDataRanges[i].setEnd(other.begin());
- mDataRanges.insert(i+1, QCPDataRange(other.end(), thisEnd));
- break; // since data ranges are sorted (and don't overlap) after simplify() call, we're done here
- }
- }
- }
- ++i;
- }
-
- return *this;
- }
- /*!
- Returns the total number of data points contained in all data ranges that make up this data
- selection.
- */
- int QCPDataSelection::dataPointCount() const
- {
- int result = 0;
- for (int i=0; i<mDataRanges.size(); ++i)
- result += mDataRanges.at(i).length();
- return result;
- }
- /*!
- Returns the data range with the specified \a index.
-
- If the data selection is simplified (the usual state of the selection, see \ref simplify), the
- ranges are sorted by ascending data point index.
-
- \see dataRangeCount
- */
- QCPDataRange QCPDataSelection::dataRange(int index) const
- {
- if (index >= 0 && index < mDataRanges.size())
- {
- return mDataRanges.at(index);
- } else
- {
- qDebug() << Q_FUNC_INFO << "index out of range:" << index;
- return QCPDataRange();
- }
- }
- /*!
- Returns a \ref QCPDataRange which spans the entire data selection, including possible
- intermediate segments which are not part of the original data selection.
- */
- QCPDataRange QCPDataSelection::span() const
- {
- if (isEmpty())
- return QCPDataRange();
- else
- return QCPDataRange(mDataRanges.first().begin(), mDataRanges.last().end());
- }
- /*!
- Adds the given \a dataRange to this data selection. This is equivalent to the += operator but
- allows disabling immediate simplification by setting \a simplify to false. This can improve
- performance if adding a very large amount of data ranges successively. In this case, make sure to
- call \ref simplify manually, after the operation.
- */
- void QCPDataSelection::addDataRange(const QCPDataRange &dataRange, bool simplify)
- {
- mDataRanges.append(dataRange);
- if (simplify)
- this->simplify();
- }
- /*!
- Removes all data ranges. The data selection then contains no data points.
-
- \ref isEmpty
- */
- void QCPDataSelection::clear()
- {
- mDataRanges.clear();
- }
- /*!
- Sorts all data ranges by range begin index in ascending order, and then joins directly adjacent
- or overlapping ranges. This can reduce the number of individual data ranges in the selection, and
- prevents possible double-counting when iterating over the data points held by the data ranges.
- This method is automatically called when using the addition/subtraction operators. The only case
- when \ref simplify is left to the user, is when calling \ref addDataRange, with the parameter \a
- simplify explicitly set to false.
- */
- void QCPDataSelection::simplify()
- {
- // remove any empty ranges:
- for (int i=mDataRanges.size()-1; i>=0; --i)
- {
- if (mDataRanges.at(i).isEmpty())
- mDataRanges.removeAt(i);
- }
- if (mDataRanges.isEmpty())
- return;
-
- // sort ranges by starting value, ascending:
- std::sort(mDataRanges.begin(), mDataRanges.end(), lessThanDataRangeBegin);
-
- // join overlapping/contiguous ranges:
- int i = 1;
- while (i < mDataRanges.size())
- {
- if (mDataRanges.at(i-1).end() >= mDataRanges.at(i).begin()) // range i overlaps/joins with i-1, so expand range i-1 appropriately and remove range i from list
- {
- mDataRanges[i-1].setEnd(qMax(mDataRanges.at(i-1).end(), mDataRanges.at(i).end()));
- mDataRanges.removeAt(i);
- } else
- ++i;
- }
- }
- /*!
- Makes sure this data selection conforms to the specified \a type selection type. Before the type
- is enforced, \ref simplify is called.
-
- Depending on \a type, enforcing means adding new data points that were previously not part of the
- selection, or removing data points from the selection. If the current selection already conforms
- to \a type, the data selection is not changed.
-
- \see QCP::SelectionType
- */
- void QCPDataSelection::enforceType(QCP::SelectionType type)
- {
- simplify();
- switch (type)
- {
- case QCP::stNone:
- {
- mDataRanges.clear();
- break;
- }
- case QCP::stWhole:
- {
- // whole selection isn't defined by data range, so don't change anything (is handled in plottable methods)
- break;
- }
- case QCP::stSingleData:
- {
- // reduce all data ranges to the single first data point:
- if (!mDataRanges.isEmpty())
- {
- if (mDataRanges.size() > 1)
- mDataRanges = QList<QCPDataRange>() << mDataRanges.first();
- if (mDataRanges.first().length() > 1)
- mDataRanges.first().setEnd(mDataRanges.first().begin()+1);
- }
- break;
- }
- case QCP::stDataRange:
- {
- mDataRanges = QList<QCPDataRange>() << span();
- break;
- }
- case QCP::stMultipleDataRanges:
- {
- // this is the selection type that allows all concievable combinations of ranges, so do nothing
- break;
- }
- }
- }
- /*!
- Returns true if the data selection \a other is contained entirely in this data selection, i.e.
- all data point indices that are in \a other are also in this data selection.
-
- \see QCPDataRange::contains
- */
- bool QCPDataSelection::contains(const QCPDataSelection &other) const
- {
- if (other.isEmpty()) return false;
-
- int otherIndex = 0;
- int thisIndex = 0;
- while (thisIndex < mDataRanges.size() && otherIndex < other.mDataRanges.size())
- {
- if (mDataRanges.at(thisIndex).contains(other.mDataRanges.at(otherIndex)))
- ++otherIndex;
- else
- ++thisIndex;
- }
- return thisIndex < mDataRanges.size(); // if thisIndex ran all the way to the end to find a containing range for the current otherIndex, other is not contained in this
- }
- /*!
- Returns a data selection containing the points which are both in this data selection and in the
- data range \a other.
- A common use case is to limit an unknown data selection to the valid range of a data container,
- using \ref QCPDataContainer::dataRange as \a other. One can then safely iterate over the returned
- data selection without exceeding the data container's bounds.
- */
- QCPDataSelection QCPDataSelection::intersection(const QCPDataRange &other) const
- {
- QCPDataSelection result;
- for (int i=0; i<mDataRanges.size(); ++i)
- result.addDataRange(mDataRanges.at(i).intersection(other), false);
- result.simplify();
- return result;
- }
- /*!
- Returns a data selection containing the points which are both in this data selection and in the
- data selection \a other.
- */
- QCPDataSelection QCPDataSelection::intersection(const QCPDataSelection &other) const
- {
- QCPDataSelection result;
- for (int i=0; i<other.dataRangeCount(); ++i)
- result += intersection(other.dataRange(i));
- result.simplify();
- return result;
- }
- /*!
- Returns a data selection which is the exact inverse of this data selection, with \a outerRange
- defining the base range on which to invert. If \a outerRange is smaller than the \ref span of
- this data selection, it is expanded accordingly.
- For example, this method can be used to retrieve all unselected segments by setting \a outerRange
- to the full data range of the plottable, and calling this method on a data selection holding the
- selected segments.
- */
- QCPDataSelection QCPDataSelection::inverse(const QCPDataRange &outerRange) const
- {
- if (isEmpty())
- return QCPDataSelection(outerRange);
- QCPDataRange fullRange = outerRange.expanded(span());
-
- QCPDataSelection result;
- // first unselected segment:
- if (mDataRanges.first().begin() != fullRange.begin())
- result.addDataRange(QCPDataRange(fullRange.begin(), mDataRanges.first().begin()), false);
- // intermediate unselected segments:
- for (int i=1; i<mDataRanges.size(); ++i)
- result.addDataRange(QCPDataRange(mDataRanges.at(i-1).end(), mDataRanges.at(i).begin()), false);
- // last unselected segment:
- if (mDataRanges.last().end() != fullRange.end())
- result.addDataRange(QCPDataRange(mDataRanges.last().end(), fullRange.end()), false);
- result.simplify();
- return result;
- }
- /* end of 'src/selection.cpp' */
- /* including file 'src/selectionrect.cpp', size 9224 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPSelectionRect
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPSelectionRect
- \brief Provides rect/rubber-band data selection and range zoom interaction
-
- QCPSelectionRect is used by QCustomPlot when the \ref QCustomPlot::setSelectionRectMode is not
- \ref QCP::srmNone. When the user drags the mouse across the plot, the current selection rect
- instance (\ref QCustomPlot::setSelectionRect) is forwarded these events and makes sure an
- according rect shape is drawn. At the begin, during, and after completion of the interaction, it
- emits the corresponding signals \ref started, \ref changed, \ref canceled, and \ref accepted.
-
- The QCustomPlot instance connects own slots to the current selection rect instance, in order to
- react to an accepted selection rect interaction accordingly.
-
- \ref isActive can be used to check whether the selection rect is currently active. An ongoing
- selection interaction can be cancelled programmatically via calling \ref cancel at any time.
-
- The appearance of the selection rect can be controlled via \ref setPen and \ref setBrush.
- If you wish to provide custom behaviour, e.g. a different visual representation of the selection
- rect (\ref QCPSelectionRect::draw), you can subclass QCPSelectionRect and pass an instance of
- your subclass to \ref QCustomPlot::setSelectionRect.
- */
- /* start of documentation of inline functions */
- /*! \fn bool QCPSelectionRect::isActive() const
-
- Returns true if there is currently a selection going on, i.e. the user has started dragging a
- selection rect, but hasn't released the mouse button yet.
-
- \see cancel
- */
- /* end of documentation of inline functions */
- /* start documentation of signals */
- /*! \fn void QCPSelectionRect::started(QMouseEvent *event);
-
- This signal is emitted when a selection rect interaction was initiated, i.e. the user just
- started dragging the selection rect with the mouse.
- */
- /*! \fn void QCPSelectionRect::changed(const QRect &rect, QMouseEvent *event);
-
- This signal is emitted while the selection rect interaction is ongoing and the \a rect has
- changed its size due to the user moving the mouse.
-
- Note that \a rect may have a negative width or height, if the selection is being dragged to the
- upper or left side of the selection rect origin.
- */
- /*! \fn void QCPSelectionRect::canceled(const QRect &rect, QInputEvent *event);
-
- This signal is emitted when the selection interaction was cancelled. Note that \a event is 0 if
- the selection interaction was cancelled programmatically, by a call to \ref cancel.
-
- The user may cancel the selection interaction by pressing the escape key. In this case, \a event
- holds the respective input event.
-
- Note that \a rect may have a negative width or height, if the selection is being dragged to the
- upper or left side of the selection rect origin.
- */
- /*! \fn void QCPSelectionRect::accepted(const QRect &rect, QMouseEvent *event);
-
- This signal is emitted when the selection interaction was completed by the user releasing the
- mouse button.
-
- Note that \a rect may have a negative width or height, if the selection is being dragged to the
- upper or left side of the selection rect origin.
- */
- /* end documentation of signals */
- /*!
- Creates a new QCPSelectionRect instance. To make QCustomPlot use the selection rect instance,
- pass it to \ref QCustomPlot::setSelectionRect. \a parentPlot should be set to the same
- QCustomPlot widget.
- */
- QCPSelectionRect::QCPSelectionRect(QCustomPlot *parentPlot) :
- QCPLayerable(parentPlot),
- mPen(QBrush(Qt::gray), 0, Qt::DashLine),
- mBrush(Qt::NoBrush),
- mActive(false)
- {
- }
- QCPSelectionRect::~QCPSelectionRect()
- {
- cancel();
- }
- /*!
- A convenience function which returns the coordinate range of the provided \a axis, that this
- selection rect currently encompasses.
- */
- QCPRange QCPSelectionRect::range(const QCPAxis *axis) const
- {
- if (axis)
- {
- if (axis->orientation() == Qt::Horizontal)
- return QCPRange(axis->pixelToCoord(mRect.left()), axis->pixelToCoord(mRect.left()+mRect.width()));
- else
- return QCPRange(axis->pixelToCoord(mRect.top()+mRect.height()), axis->pixelToCoord(mRect.top()));
- } else
- {
- qDebug() << Q_FUNC_INFO << "called with axis zero";
- return QCPRange();
- }
- }
- /*!
- Sets the pen that will be used to draw the selection rect outline.
-
- \see setBrush
- */
- void QCPSelectionRect::setPen(const QPen &pen)
- {
- mPen = pen;
- }
- /*!
- Sets the brush that will be used to fill the selection rect. By default the selection rect is not
- filled, i.e. \a brush is <tt>Qt::NoBrush</tt>.
-
- \see setPen
- */
- void QCPSelectionRect::setBrush(const QBrush &brush)
- {
- mBrush = brush;
- }
- /*!
- If there is currently a selection interaction going on (\ref isActive), the interaction is
- canceled. The selection rect will emit the \ref canceled signal.
- */
- void QCPSelectionRect::cancel()
- {
- if (mActive)
- {
- mActive = false;
- emit canceled(mRect, 0);
- }
- }
- /*! \internal
-
- This method is called by QCustomPlot to indicate that a selection rect interaction was initiated.
- The default implementation sets the selection rect to active, initializes the selection rect
- geometry and emits the \ref started signal.
- */
- void QCPSelectionRect::startSelection(QMouseEvent *event)
- {
- mActive = true;
- mRect = QRect(event->pos(), event->pos());
- emit started(event);
- }
- /*! \internal
-
- This method is called by QCustomPlot to indicate that an ongoing selection rect interaction needs
- to update its geometry. The default implementation updates the rect and emits the \ref changed
- signal.
- */
- void QCPSelectionRect::moveSelection(QMouseEvent *event)
- {
- mRect.setBottomRight(event->pos());
- emit changed(mRect, event);
- layer()->replot();
- }
- /*! \internal
-
- This method is called by QCustomPlot to indicate that an ongoing selection rect interaction has
- finished by the user releasing the mouse button. The default implementation deactivates the
- selection rect and emits the \ref accepted signal.
- */
- void QCPSelectionRect::endSelection(QMouseEvent *event)
- {
- mRect.setBottomRight(event->pos());
- mActive = false;
- emit accepted(mRect, event);
- }
- /*! \internal
-
- This method is called by QCustomPlot when a key has been pressed by the user while the selection
- rect interaction is active. The default implementation allows to \ref cancel the interaction by
- hitting the escape key.
- */
- void QCPSelectionRect::keyPressEvent(QKeyEvent *event)
- {
- if (event->key() == Qt::Key_Escape && mActive)
- {
- mActive = false;
- emit canceled(mRect, event);
- }
- }
- /* inherits documentation from base class */
- void QCPSelectionRect::applyDefaultAntialiasingHint(QCPPainter *painter) const
- {
- applyAntialiasingHint(painter, mAntialiased, QCP::aeOther);
- }
- /*! \internal
-
- If the selection rect is active (\ref isActive), draws the selection rect defined by \a mRect.
-
- \seebaseclassmethod
- */
- void QCPSelectionRect::draw(QCPPainter *painter)
- {
- if (mActive)
- {
- painter->setPen(mPen);
- painter->setBrush(mBrush);
- painter->drawRect(mRect);
- }
- }
- /* end of 'src/selectionrect.cpp' */
- /* including file 'src/layout.cpp', size 79064 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPMarginGroup
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPMarginGroup
- \brief A margin group allows synchronization of margin sides if working with multiple layout elements.
-
- QCPMarginGroup allows you to tie a margin side of two or more layout elements together, such that
- they will all have the same size, based on the largest required margin in the group.
-
- \n
- \image html QCPMarginGroup.png "Demonstration of QCPMarginGroup"
- \n
-
- In certain situations it is desirable that margins at specific sides are synchronized across
- layout elements. For example, if one QCPAxisRect is below another one in a grid layout, it will
- provide a cleaner look to the user if the left and right margins of the two axis rects are of the
- same size. The left axis of the top axis rect will then be at the same horizontal position as the
- left axis of the lower axis rect, making them appear aligned. The same applies for the right
- axes. This is what QCPMarginGroup makes possible.
-
- To add/remove a specific side of a layout element to/from a margin group, use the \ref
- QCPLayoutElement::setMarginGroup method. To completely break apart the margin group, either call
- \ref clear, or just delete the margin group.
-
- \section QCPMarginGroup-example Example
-
- First create a margin group:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpmargingroup-creation-1
- Then set this group on the layout element sides:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpmargingroup-creation-2
- Here, we've used the first two axis rects of the plot and synchronized their left margins with
- each other and their right margins with each other.
- */
- /* start documentation of inline functions */
- /*! \fn QList<QCPLayoutElement*> QCPMarginGroup::elements(QCP::MarginSide side) const
-
- Returns a list of all layout elements that have their margin \a side associated with this margin
- group.
- */
- /* end documentation of inline functions */
- /*!
- Creates a new QCPMarginGroup instance in \a parentPlot.
- */
- QCPMarginGroup::QCPMarginGroup(QCustomPlot *parentPlot) :
- QObject(parentPlot),
- mParentPlot(parentPlot)
- {
- mChildren.insert(QCP::msLeft, QList<QCPLayoutElement*>());
- mChildren.insert(QCP::msRight, QList<QCPLayoutElement*>());
- mChildren.insert(QCP::msTop, QList<QCPLayoutElement*>());
- mChildren.insert(QCP::msBottom, QList<QCPLayoutElement*>());
- }
- QCPMarginGroup::~QCPMarginGroup()
- {
- clear();
- }
- /*!
- Returns whether this margin group is empty. If this function returns true, no layout elements use
- this margin group to synchronize margin sides.
- */
- bool QCPMarginGroup::isEmpty() const
- {
- QHashIterator<QCP::MarginSide, QList<QCPLayoutElement*> > it(mChildren);
- while (it.hasNext())
- {
- it.next();
- if (!it.value().isEmpty())
- return false;
- }
- return true;
- }
- /*!
- Clears this margin group. The synchronization of the margin sides that use this margin group is
- lifted and they will use their individual margin sizes again.
- */
- void QCPMarginGroup::clear()
- {
- // make all children remove themselves from this margin group:
- QHashIterator<QCP::MarginSide, QList<QCPLayoutElement*> > it(mChildren);
- while (it.hasNext())
- {
- it.next();
- const QList<QCPLayoutElement*> elements = it.value();
- for (int i=elements.size()-1; i>=0; --i)
- elements.at(i)->setMarginGroup(it.key(), 0); // removes itself from mChildren via removeChild
- }
- }
- /*! \internal
-
- Returns the synchronized common margin for \a side. This is the margin value that will be used by
- the layout element on the respective side, if it is part of this margin group.
-
- The common margin is calculated by requesting the automatic margin (\ref
- QCPLayoutElement::calculateAutoMargin) of each element associated with \a side in this margin
- group, and choosing the largest returned value. (QCPLayoutElement::minimumMargins is taken into
- account, too.)
- */
- int QCPMarginGroup::commonMargin(QCP::MarginSide side) const
- {
- // query all automatic margins of the layout elements in this margin group side and find maximum:
- int result = 0;
- const QList<QCPLayoutElement*> elements = mChildren.value(side);
- for (int i=0; i<elements.size(); ++i)
- {
- if (!elements.at(i)->autoMargins().testFlag(side))
- continue;
- int m = qMax(elements.at(i)->calculateAutoMargin(side), QCP::getMarginValue(elements.at(i)->minimumMargins(), side));
- if (m > result)
- result = m;
- }
- return result;
- }
- /*! \internal
-
- Adds \a element to the internal list of child elements, for the margin \a side.
-
- This function does not modify the margin group property of \a element.
- */
- void QCPMarginGroup::addChild(QCP::MarginSide side, QCPLayoutElement *element)
- {
- if (!mChildren[side].contains(element))
- mChildren[side].append(element);
- else
- qDebug() << Q_FUNC_INFO << "element is already child of this margin group side" << reinterpret_cast<quintptr>(element);
- }
- /*! \internal
-
- Removes \a element from the internal list of child elements, for the margin \a side.
-
- This function does not modify the margin group property of \a element.
- */
- void QCPMarginGroup::removeChild(QCP::MarginSide side, QCPLayoutElement *element)
- {
- if (!mChildren[side].removeOne(element))
- qDebug() << Q_FUNC_INFO << "element is not child of this margin group side" << reinterpret_cast<quintptr>(element);
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPLayoutElement
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPLayoutElement
- \brief The abstract base class for all objects that form \ref thelayoutsystem "the layout system".
-
- This is an abstract base class. As such, it can't be instantiated directly, rather use one of its subclasses.
-
- A Layout element is a rectangular object which can be placed in layouts. It has an outer rect
- (QCPLayoutElement::outerRect) and an inner rect (\ref QCPLayoutElement::rect). The difference
- between outer and inner rect is called its margin. The margin can either be set to automatic or
- manual (\ref setAutoMargins) on a per-side basis. If a side is set to manual, that margin can be
- set explicitly with \ref setMargins and will stay fixed at that value. If it's set to automatic,
- the layout element subclass will control the value itself (via \ref calculateAutoMargin).
-
- Layout elements can be placed in layouts (base class QCPLayout) like QCPLayoutGrid. The top level
- layout is reachable via \ref QCustomPlot::plotLayout, and is a \ref QCPLayoutGrid. Since \ref
- QCPLayout itself derives from \ref QCPLayoutElement, layouts can be nested.
-
- Thus in QCustomPlot one can divide layout elements into two categories: The ones that are
- invisible by themselves, because they don't draw anything. Their only purpose is to manage the
- position and size of other layout elements. This category of layout elements usually use
- QCPLayout as base class. Then there is the category of layout elements which actually draw
- something. For example, QCPAxisRect, QCPLegend and QCPTextElement are of this category. This does
- not necessarily mean that the latter category can't have child layout elements. QCPLegend for
- instance, actually derives from QCPLayoutGrid and the individual legend items are child layout
- elements in the grid layout.
- */
- /* start documentation of inline functions */
- /*! \fn QCPLayout *QCPLayoutElement::layout() const
-
- Returns the parent layout of this layout element.
- */
- /*! \fn QRect QCPLayoutElement::rect() const
-
- Returns the inner rect of this layout element. The inner rect is the outer rect (\ref outerRect, \ref
- setOuterRect) shrinked by the margins (\ref setMargins, \ref setAutoMargins).
-
- In some cases, the area between outer and inner rect is left blank. In other cases the margin
- area is used to display peripheral graphics while the main content is in the inner rect. This is
- where automatic margin calculation becomes interesting because it allows the layout element to
- adapt the margins to the peripheral graphics it wants to draw. For example, \ref QCPAxisRect
- draws the axis labels and tick labels in the margin area, thus needs to adjust the margins (if
- \ref setAutoMargins is enabled) according to the space required by the labels of the axes.
-
- \see outerRect
- */
- /*! \fn QRect QCPLayoutElement::outerRect() const
-
- Returns the outer rect of this layout element. The outer rect is the inner rect expanded by the
- margins (\ref setMargins, \ref setAutoMargins). The outer rect is used (and set via \ref
- setOuterRect) by the parent \ref QCPLayout to control the size of this layout element.
-
- \see rect
- */
- /* end documentation of inline functions */
- /*!
- Creates an instance of QCPLayoutElement and sets default values.
- */
- QCPLayoutElement::QCPLayoutElement(QCustomPlot *parentPlot) :
- QCPLayerable(parentPlot), // parenthood is changed as soon as layout element gets inserted into a layout (except for top level layout)
- mParentLayout(0),
- mMinimumSize(),
- mMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX),
- mSizeConstraintRect(scrInnerRect),
- mRect(0, 0, 0, 0),
- mOuterRect(0, 0, 0, 0),
- mMargins(0, 0, 0, 0),
- mMinimumMargins(0, 0, 0, 0),
- mAutoMargins(QCP::msAll)
- {
- }
- QCPLayoutElement::~QCPLayoutElement()
- {
- setMarginGroup(QCP::msAll, 0); // unregister at margin groups, if there are any
- // unregister at layout:
- if (qobject_cast<QCPLayout*>(mParentLayout)) // the qobject_cast is just a safeguard in case the layout forgets to call clear() in its dtor and this dtor is called by QObject dtor
- mParentLayout->take(this);
- }
- /*!
- Sets the outer rect of this layout element. If the layout element is inside a layout, the layout
- sets the position and size of this layout element using this function.
-
- Calling this function externally has no effect, since the layout will overwrite any changes to
- the outer rect upon the next replot.
-
- The layout element will adapt its inner \ref rect by applying the margins inward to the outer rect.
-
- \see rect
- */
- void QCPLayoutElement::setOuterRect(const QRect &rect)
- {
- if (mOuterRect != rect)
- {
- mOuterRect = rect;
- mRect = mOuterRect.adjusted(mMargins.left(), mMargins.top(), -mMargins.right(), -mMargins.bottom());
- }
- }
- /*!
- Sets the margins of this layout element. If \ref setAutoMargins is disabled for some or all
- sides, this function is used to manually set the margin on those sides. Sides that are still set
- to be handled automatically are ignored and may have any value in \a margins.
-
- The margin is the distance between the outer rect (controlled by the parent layout via \ref
- setOuterRect) and the inner \ref rect (which usually contains the main content of this layout
- element).
-
- \see setAutoMargins
- */
- void QCPLayoutElement::setMargins(const QMargins &margins)
- {
- if (mMargins != margins)
- {
- mMargins = margins;
- mRect = mOuterRect.adjusted(mMargins.left(), mMargins.top(), -mMargins.right(), -mMargins.bottom());
- }
- }
- /*!
- If \ref setAutoMargins is enabled on some or all margins, this function is used to provide
- minimum values for those margins.
-
- The minimum values are not enforced on margin sides that were set to be under manual control via
- \ref setAutoMargins.
-
- \see setAutoMargins
- */
- void QCPLayoutElement::setMinimumMargins(const QMargins &margins)
- {
- if (mMinimumMargins != margins)
- {
- mMinimumMargins = margins;
- }
- }
- /*!
- Sets on which sides the margin shall be calculated automatically. If a side is calculated
- automatically, a minimum margin value may be provided with \ref setMinimumMargins. If a side is
- set to be controlled manually, the value may be specified with \ref setMargins.
-
- Margin sides that are under automatic control may participate in a \ref QCPMarginGroup (see \ref
- setMarginGroup), to synchronize (align) it with other layout elements in the plot.
-
- \see setMinimumMargins, setMargins, QCP::MarginSide
- */
- void QCPLayoutElement::setAutoMargins(QCP::MarginSides sides)
- {
- mAutoMargins = sides;
- }
- /*!
- Sets the minimum size of this layout element. A parent layout tries to respect the \a size here
- by changing row/column sizes in the layout accordingly.
-
- If the parent layout size is not sufficient to satisfy all minimum size constraints of its child
- layout elements, the layout may set a size that is actually smaller than \a size. QCustomPlot
- propagates the layout's size constraints to the outside by setting its own minimum QWidget size
- accordingly, so violations of \a size should be exceptions.
-
- Whether this constraint applies to the inner or the outer rect can be specified with \ref
- setSizeConstraintRect (see \ref rect and \ref outerRect).
- */
- void QCPLayoutElement::setMinimumSize(const QSize &size)
- {
- if (mMinimumSize != size)
- {
- mMinimumSize = size;
- if (mParentLayout)
- mParentLayout->sizeConstraintsChanged();
- }
- }
- /*! \overload
-
- Sets the minimum size of this layout element.
-
- Whether this constraint applies to the inner or the outer rect can be specified with \ref
- setSizeConstraintRect (see \ref rect and \ref outerRect).
- */
- void QCPLayoutElement::setMinimumSize(int width, int height)
- {
- setMinimumSize(QSize(width, height));
- }
- /*!
- Sets the maximum size of this layout element. A parent layout tries to respect the \a size here
- by changing row/column sizes in the layout accordingly.
-
- Whether this constraint applies to the inner or the outer rect can be specified with \ref
- setSizeConstraintRect (see \ref rect and \ref outerRect).
- */
- void QCPLayoutElement::setMaximumSize(const QSize &size)
- {
- if (mMaximumSize != size)
- {
- mMaximumSize = size;
- if (mParentLayout)
- mParentLayout->sizeConstraintsChanged();
- }
- }
- /*! \overload
-
- Sets the maximum size of this layout element.
-
- Whether this constraint applies to the inner or the outer rect can be specified with \ref
- setSizeConstraintRect (see \ref rect and \ref outerRect).
- */
- void QCPLayoutElement::setMaximumSize(int width, int height)
- {
- setMaximumSize(QSize(width, height));
- }
- /*!
- Sets to which rect of a layout element the size constraints apply. Size constraints can be set
- via \ref setMinimumSize and \ref setMaximumSize.
-
- The outer rect (\ref outerRect) includes the margins (e.g. in the case of a QCPAxisRect the axis
- labels), whereas the inner rect (\ref rect) does not.
-
- \see setMinimumSize, setMaximumSize
- */
- void QCPLayoutElement::setSizeConstraintRect(SizeConstraintRect constraintRect)
- {
- if (mSizeConstraintRect != constraintRect)
- {
- mSizeConstraintRect = constraintRect;
- if (mParentLayout)
- mParentLayout->sizeConstraintsChanged();
- }
- }
- /*!
- Sets the margin \a group of the specified margin \a sides.
-
- Margin groups allow synchronizing specified margins across layout elements, see the documentation
- of \ref QCPMarginGroup.
-
- To unset the margin group of \a sides, set \a group to 0.
-
- Note that margin groups only work for margin sides that are set to automatic (\ref
- setAutoMargins).
-
- \see QCP::MarginSide
- */
- void QCPLayoutElement::setMarginGroup(QCP::MarginSides sides, QCPMarginGroup *group)
- {
- QVector<QCP::MarginSide> sideVector;
- if (sides.testFlag(QCP::msLeft)) sideVector.append(QCP::msLeft);
- if (sides.testFlag(QCP::msRight)) sideVector.append(QCP::msRight);
- if (sides.testFlag(QCP::msTop)) sideVector.append(QCP::msTop);
- if (sides.testFlag(QCP::msBottom)) sideVector.append(QCP::msBottom);
-
- for (int i=0; i<sideVector.size(); ++i)
- {
- QCP::MarginSide side = sideVector.at(i);
- if (marginGroup(side) != group)
- {
- QCPMarginGroup *oldGroup = marginGroup(side);
- if (oldGroup) // unregister at old group
- oldGroup->removeChild(side, this);
-
- if (!group) // if setting to 0, remove hash entry. Else set hash entry to new group and register there
- {
- mMarginGroups.remove(side);
- } else // setting to a new group
- {
- mMarginGroups[side] = group;
- group->addChild(side, this);
- }
- }
- }
- }
- /*!
- Updates the layout element and sub-elements. This function is automatically called before every
- replot by the parent layout element. It is called multiple times, once for every \ref
- UpdatePhase. The phases are run through in the order of the enum values. For details about what
- happens at the different phases, see the documentation of \ref UpdatePhase.
-
- Layout elements that have child elements should call the \ref update method of their child
- elements, and pass the current \a phase unchanged.
-
- The default implementation executes the automatic margin mechanism in the \ref upMargins phase.
- Subclasses should make sure to call the base class implementation.
- */
- void QCPLayoutElement::update(UpdatePhase phase)
- {
- if (phase == upMargins)
- {
- if (mAutoMargins != QCP::msNone)
- {
- // set the margins of this layout element according to automatic margin calculation, either directly or via a margin group:
- QMargins newMargins = mMargins;
- QList<QCP::MarginSide> allMarginSides = QList<QCP::MarginSide>() << QCP::msLeft << QCP::msRight << QCP::msTop << QCP::msBottom;
- foreach (QCP::MarginSide side, allMarginSides)
- {
- if (mAutoMargins.testFlag(side)) // this side's margin shall be calculated automatically
- {
- if (mMarginGroups.contains(side))
- QCP::setMarginValue(newMargins, side, mMarginGroups[side]->commonMargin(side)); // this side is part of a margin group, so get the margin value from that group
- else
- QCP::setMarginValue(newMargins, side, calculateAutoMargin(side)); // this side is not part of a group, so calculate the value directly
- // apply minimum margin restrictions:
- if (QCP::getMarginValue(newMargins, side) < QCP::getMarginValue(mMinimumMargins, side))
- QCP::setMarginValue(newMargins, side, QCP::getMarginValue(mMinimumMargins, side));
- }
- }
- setMargins(newMargins);
- }
- }
- }
- /*!
- Returns the suggested minimum size this layout element (the \ref outerRect) may be compressed to,
- if no manual minimum size is set.
-
- if a minimum size (\ref setMinimumSize) was not set manually, parent layouts use the returned size
- (usually indirectly through \ref QCPLayout::getFinalMinimumOuterSize) to determine the minimum
- allowed size of this layout element.
- A manual minimum size is considered set if it is non-zero.
-
- The default implementation simply returns the sum of the horizontal margins for the width and the
- sum of the vertical margins for the height. Reimplementations may use their detailed knowledge
- about the layout element's content to provide size hints.
- */
- QSize QCPLayoutElement::minimumOuterSizeHint() const
- {
- return QSize(mMargins.left()+mMargins.right(), mMargins.top()+mMargins.bottom());
- }
- /*!
- Returns the suggested maximum size this layout element (the \ref outerRect) may be expanded to,
- if no manual maximum size is set.
-
- if a maximum size (\ref setMaximumSize) was not set manually, parent layouts use the returned
- size (usually indirectly through \ref QCPLayout::getFinalMaximumOuterSize) to determine the
- maximum allowed size of this layout element.
- A manual maximum size is considered set if it is smaller than Qt's \c QWIDGETSIZE_MAX.
-
- The default implementation simply returns \c QWIDGETSIZE_MAX for both width and height, implying
- no suggested maximum size. Reimplementations may use their detailed knowledge about the layout
- element's content to provide size hints.
- */
- QSize QCPLayoutElement::maximumOuterSizeHint() const
- {
- return QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
- }
- /*!
- Returns a list of all child elements in this layout element. If \a recursive is true, all
- sub-child elements are included in the list, too.
-
- \warning There may be entries with value 0 in the returned list. (For example, QCPLayoutGrid may have
- empty cells which yield 0 at the respective index.)
- */
- QList<QCPLayoutElement*> QCPLayoutElement::elements(bool recursive) const
- {
- Q_UNUSED(recursive)
- return QList<QCPLayoutElement*>();
- }
- /*!
- Layout elements are sensitive to events inside their outer rect. If \a pos is within the outer
- rect, this method returns a value corresponding to 0.99 times the parent plot's selection
- tolerance. However, layout elements are not selectable by default. So if \a onlySelectable is
- true, -1.0 is returned.
-
- See \ref QCPLayerable::selectTest for a general explanation of this virtual method.
-
- QCPLayoutElement subclasses may reimplement this method to provide more specific selection test
- behaviour.
- */
- double QCPLayoutElement::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- Q_UNUSED(details)
-
- if (onlySelectable)
- return -1;
-
- if (QRectF(mOuterRect).contains(pos))
- {
- if (mParentPlot)
- return mParentPlot->selectionTolerance()*0.99;
- else
- {
- qDebug() << Q_FUNC_INFO << "parent plot not defined";
- return -1;
- }
- } else
- return -1;
- }
- /*! \internal
-
- propagates the parent plot initialization to all child elements, by calling \ref
- QCPLayerable::initializeParentPlot on them.
- */
- void QCPLayoutElement::parentPlotInitialized(QCustomPlot *parentPlot)
- {
- foreach (QCPLayoutElement* el, elements(false))
- {
- if (!el->parentPlot())
- el->initializeParentPlot(parentPlot);
- }
- }
- /*! \internal
-
- Returns the margin size for this \a side. It is used if automatic margins is enabled for this \a
- side (see \ref setAutoMargins). If a minimum margin was set with \ref setMinimumMargins, the
- returned value will not be smaller than the specified minimum margin.
-
- The default implementation just returns the respective manual margin (\ref setMargins) or the
- minimum margin, whichever is larger.
- */
- int QCPLayoutElement::calculateAutoMargin(QCP::MarginSide side)
- {
- return qMax(QCP::getMarginValue(mMargins, side), QCP::getMarginValue(mMinimumMargins, side));
- }
- /*! \internal
-
- This virtual method is called when this layout element was moved to a different QCPLayout, or
- when this layout element has changed its logical position (e.g. row and/or column) within the
- same QCPLayout. Subclasses may use this to react accordingly.
-
- Since this method is called after the completion of the move, you can access the new parent
- layout via \ref layout().
-
- The default implementation does nothing.
- */
- void QCPLayoutElement::layoutChanged()
- {
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPLayout
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPLayout
- \brief The abstract base class for layouts
-
- This is an abstract base class for layout elements whose main purpose is to define the position
- and size of other child layout elements. In most cases, layouts don't draw anything themselves
- (but there are exceptions to this, e.g. QCPLegend).
-
- QCPLayout derives from QCPLayoutElement, and thus can itself be nested in other layouts.
-
- QCPLayout introduces a common interface for accessing and manipulating the child elements. Those
- functions are most notably \ref elementCount, \ref elementAt, \ref takeAt, \ref take, \ref
- simplify, \ref removeAt, \ref remove and \ref clear. Individual subclasses may add more functions
- to this interface which are more specialized to the form of the layout. For example, \ref
- QCPLayoutGrid adds functions that take row and column indices to access cells of the layout grid
- more conveniently.
-
- Since this is an abstract base class, you can't instantiate it directly. Rather use one of its
- subclasses like QCPLayoutGrid or QCPLayoutInset.
-
- For a general introduction to the layout system, see the dedicated documentation page \ref
- thelayoutsystem "The Layout System".
- */
- /* start documentation of pure virtual functions */
- /*! \fn virtual int QCPLayout::elementCount() const = 0
-
- Returns the number of elements/cells in the layout.
-
- \see elements, elementAt
- */
- /*! \fn virtual QCPLayoutElement* QCPLayout::elementAt(int index) const = 0
-
- Returns the element in the cell with the given \a index. If \a index is invalid, returns 0.
-
- Note that even if \a index is valid, the respective cell may be empty in some layouts (e.g.
- QCPLayoutGrid), so this function may return 0 in those cases. You may use this function to check
- whether a cell is empty or not.
-
- \see elements, elementCount, takeAt
- */
- /*! \fn virtual QCPLayoutElement* QCPLayout::takeAt(int index) = 0
-
- Removes the element with the given \a index from the layout and returns it.
-
- If the \a index is invalid or the cell with that index is empty, returns 0.
-
- Note that some layouts don't remove the respective cell right away but leave an empty cell after
- successful removal of the layout element. To collapse empty cells, use \ref simplify.
-
- \see elementAt, take
- */
- /*! \fn virtual bool QCPLayout::take(QCPLayoutElement* element) = 0
-
- Removes the specified \a element from the layout and returns true on success.
-
- If the \a element isn't in this layout, returns false.
-
- Note that some layouts don't remove the respective cell right away but leave an empty cell after
- successful removal of the layout element. To collapse empty cells, use \ref simplify.
-
- \see takeAt
- */
- /* end documentation of pure virtual functions */
- /*!
- Creates an instance of QCPLayout and sets default values. Note that since QCPLayout
- is an abstract base class, it can't be instantiated directly.
- */
- QCPLayout::QCPLayout()
- {
- }
- /*!
- If \a phase is \ref upLayout, calls \ref updateLayout, which subclasses may reimplement to
- reposition and resize their cells.
-
- Finally, the call is propagated down to all child \ref QCPLayoutElement "QCPLayoutElements".
-
- For details about this method and the update phases, see the documentation of \ref
- QCPLayoutElement::update.
- */
- void QCPLayout::update(UpdatePhase phase)
- {
- QCPLayoutElement::update(phase);
-
- // set child element rects according to layout:
- if (phase == upLayout)
- updateLayout();
-
- // propagate update call to child elements:
- const int elCount = elementCount();
- for (int i=0; i<elCount; ++i)
- {
- if (QCPLayoutElement *el = elementAt(i))
- el->update(phase);
- }
- }
- /* inherits documentation from base class */
- QList<QCPLayoutElement*> QCPLayout::elements(bool recursive) const
- {
- const int c = elementCount();
- QList<QCPLayoutElement*> result;
- #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
- result.reserve(c);
- #endif
- for (int i=0; i<c; ++i)
- result.append(elementAt(i));
- if (recursive)
- {
- for (int i=0; i<c; ++i)
- {
- if (result.at(i))
- result << result.at(i)->elements(recursive);
- }
- }
- return result;
- }
- /*!
- Simplifies the layout by collapsing empty cells. The exact behavior depends on subclasses, the
- default implementation does nothing.
-
- Not all layouts need simplification. For example, QCPLayoutInset doesn't use explicit
- simplification while QCPLayoutGrid does.
- */
- void QCPLayout::simplify()
- {
- }
- /*!
- Removes and deletes the element at the provided \a index. Returns true on success. If \a index is
- invalid or points to an empty cell, returns false.
-
- This function internally uses \ref takeAt to remove the element from the layout and then deletes
- the returned element. Note that some layouts don't remove the respective cell right away but leave an
- empty cell after successful removal of the layout element. To collapse empty cells, use \ref
- simplify.
-
- \see remove, takeAt
- */
- bool QCPLayout::removeAt(int index)
- {
- if (QCPLayoutElement *el = takeAt(index))
- {
- delete el;
- return true;
- } else
- return false;
- }
- /*!
- Removes and deletes the provided \a element. Returns true on success. If \a element is not in the
- layout, returns false.
-
- This function internally uses \ref takeAt to remove the element from the layout and then deletes
- the element. Note that some layouts don't remove the respective cell right away but leave an
- empty cell after successful removal of the layout element. To collapse empty cells, use \ref
- simplify.
-
- \see removeAt, take
- */
- bool QCPLayout::remove(QCPLayoutElement *element)
- {
- if (take(element))
- {
- delete element;
- return true;
- } else
- return false;
- }
- /*!
- Removes and deletes all layout elements in this layout. Finally calls \ref simplify to make sure
- all empty cells are collapsed.
-
- \see remove, removeAt
- */
- void QCPLayout::clear()
- {
- for (int i=elementCount()-1; i>=0; --i)
- {
- if (elementAt(i))
- removeAt(i);
- }
- simplify();
- }
- /*!
- Subclasses call this method to report changed (minimum/maximum) size constraints.
-
- If the parent of this layout is again a QCPLayout, forwards the call to the parent's \ref
- sizeConstraintsChanged. If the parent is a QWidget (i.e. is the \ref QCustomPlot::plotLayout of
- QCustomPlot), calls QWidget::updateGeometry, so if the QCustomPlot widget is inside a Qt QLayout,
- it may update itself and resize cells accordingly.
- */
- void QCPLayout::sizeConstraintsChanged() const
- {
- if (QWidget *w = qobject_cast<QWidget*>(parent()))
- w->updateGeometry();
- else if (QCPLayout *l = qobject_cast<QCPLayout*>(parent()))
- l->sizeConstraintsChanged();
- }
- /*! \internal
-
- Subclasses reimplement this method to update the position and sizes of the child elements/cells
- via calling their \ref QCPLayoutElement::setOuterRect. The default implementation does nothing.
-
- The geometry used as a reference is the inner \ref rect of this layout. Child elements should stay
- within that rect.
-
- \ref getSectionSizes may help with the reimplementation of this function.
-
- \see update
- */
- void QCPLayout::updateLayout()
- {
- }
- /*! \internal
-
- Associates \a el with this layout. This is done by setting the \ref QCPLayoutElement::layout, the
- \ref QCPLayerable::parentLayerable and the QObject parent to this layout.
-
- Further, if \a el didn't previously have a parent plot, calls \ref
- QCPLayerable::initializeParentPlot on \a el to set the paret plot.
-
- This method is used by subclass specific methods that add elements to the layout. Note that this
- method only changes properties in \a el. The removal from the old layout and the insertion into
- the new layout must be done additionally.
- */
- void QCPLayout::adoptElement(QCPLayoutElement *el)
- {
- if (el)
- {
- el->mParentLayout = this;
- el->setParentLayerable(this);
- el->setParent(this);
- if (!el->parentPlot())
- el->initializeParentPlot(mParentPlot);
- el->layoutChanged();
- } else
- qDebug() << Q_FUNC_INFO << "Null element passed";
- }
- /*! \internal
-
- Disassociates \a el from this layout. This is done by setting the \ref QCPLayoutElement::layout
- and the \ref QCPLayerable::parentLayerable to zero. The QObject parent is set to the parent
- QCustomPlot.
-
- This method is used by subclass specific methods that remove elements from the layout (e.g. \ref
- take or \ref takeAt). Note that this method only changes properties in \a el. The removal from
- the old layout must be done additionally.
- */
- void QCPLayout::releaseElement(QCPLayoutElement *el)
- {
- if (el)
- {
- el->mParentLayout = 0;
- el->setParentLayerable(0);
- el->setParent(mParentPlot);
- // Note: Don't initializeParentPlot(0) here, because layout element will stay in same parent plot
- } else
- qDebug() << Q_FUNC_INFO << "Null element passed";
- }
- /*! \internal
-
- This is a helper function for the implementation of \ref updateLayout in subclasses.
-
- It calculates the sizes of one-dimensional sections with provided constraints on maximum section
- sizes, minimum section sizes, relative stretch factors and the final total size of all sections.
-
- The QVector entries refer to the sections. Thus all QVectors must have the same size.
-
- \a maxSizes gives the maximum allowed size of each section. If there shall be no maximum size
- imposed, set all vector values to Qt's QWIDGETSIZE_MAX.
-
- \a minSizes gives the minimum allowed size of each section. If there shall be no minimum size
- imposed, set all vector values to zero. If the \a minSizes entries add up to a value greater than
- \a totalSize, sections will be scaled smaller than the proposed minimum sizes. (In other words,
- not exceeding the allowed total size is taken to be more important than not going below minimum
- section sizes.)
-
- \a stretchFactors give the relative proportions of the sections to each other. If all sections
- shall be scaled equally, set all values equal. If the first section shall be double the size of
- each individual other section, set the first number of \a stretchFactors to double the value of
- the other individual values (e.g. {2, 1, 1, 1}).
-
- \a totalSize is the value that the final section sizes will add up to. Due to rounding, the
- actual sum may differ slightly. If you want the section sizes to sum up to exactly that value,
- you could distribute the remaining difference on the sections.
-
- The return value is a QVector containing the section sizes.
- */
- QVector<int> QCPLayout::getSectionSizes(QVector<int> maxSizes, QVector<int> minSizes, QVector<double> stretchFactors, int totalSize) const
- {
- if (maxSizes.size() != minSizes.size() || minSizes.size() != stretchFactors.size())
- {
- qDebug() << Q_FUNC_INFO << "Passed vector sizes aren't equal:" << maxSizes << minSizes << stretchFactors;
- return QVector<int>();
- }
- if (stretchFactors.isEmpty())
- return QVector<int>();
- int sectionCount = stretchFactors.size();
- QVector<double> sectionSizes(sectionCount);
- // if provided total size is forced smaller than total minimum size, ignore minimum sizes (squeeze sections):
- int minSizeSum = 0;
- for (int i=0; i<sectionCount; ++i)
- minSizeSum += minSizes.at(i);
- if (totalSize < minSizeSum)
- {
- // new stretch factors are minimum sizes and minimum sizes are set to zero:
- for (int i=0; i<sectionCount; ++i)
- {
- stretchFactors[i] = minSizes.at(i);
- minSizes[i] = 0;
- }
- }
-
- QList<int> minimumLockedSections;
- QList<int> unfinishedSections;
- for (int i=0; i<sectionCount; ++i)
- unfinishedSections.append(i);
- double freeSize = totalSize;
-
- int outerIterations = 0;
- while (!unfinishedSections.isEmpty() && outerIterations < sectionCount*2) // the iteration check ist just a failsafe in case something really strange happens
- {
- ++outerIterations;
- int innerIterations = 0;
- while (!unfinishedSections.isEmpty() && innerIterations < sectionCount*2) // the iteration check ist just a failsafe in case something really strange happens
- {
- ++innerIterations;
- // find section that hits its maximum next:
- int nextId = -1;
- double nextMax = 1e12;
- for (int i=0; i<unfinishedSections.size(); ++i)
- {
- int secId = unfinishedSections.at(i);
- double hitsMaxAt = (maxSizes.at(secId)-sectionSizes.at(secId))/stretchFactors.at(secId);
- if (hitsMaxAt < nextMax)
- {
- nextMax = hitsMaxAt;
- nextId = secId;
- }
- }
- // check if that maximum is actually within the bounds of the total size (i.e. can we stretch all remaining sections so far that the found section
- // actually hits its maximum, without exceeding the total size when we add up all sections)
- double stretchFactorSum = 0;
- for (int i=0; i<unfinishedSections.size(); ++i)
- stretchFactorSum += stretchFactors.at(unfinishedSections.at(i));
- double nextMaxLimit = freeSize/stretchFactorSum;
- if (nextMax < nextMaxLimit) // next maximum is actually hit, move forward to that point and fix the size of that section
- {
- for (int i=0; i<unfinishedSections.size(); ++i)
- {
- sectionSizes[unfinishedSections.at(i)] += nextMax*stretchFactors.at(unfinishedSections.at(i)); // increment all sections
- freeSize -= nextMax*stretchFactors.at(unfinishedSections.at(i));
- }
- unfinishedSections.removeOne(nextId); // exclude the section that is now at maximum from further changes
- } else // next maximum isn't hit, just distribute rest of free space on remaining sections
- {
- for (int i=0; i<unfinishedSections.size(); ++i)
- sectionSizes[unfinishedSections.at(i)] += nextMaxLimit*stretchFactors.at(unfinishedSections.at(i)); // increment all sections
- unfinishedSections.clear();
- }
- }
- if (innerIterations == sectionCount*2)
- qDebug() << Q_FUNC_INFO << "Exceeded maximum expected inner iteration count, layouting aborted. Input was:" << maxSizes << minSizes << stretchFactors << totalSize;
-
- // now check whether the resulting section sizes violate minimum restrictions:
- bool foundMinimumViolation = false;
- for (int i=0; i<sectionSizes.size(); ++i)
- {
- if (minimumLockedSections.contains(i))
- continue;
- if (sectionSizes.at(i) < minSizes.at(i)) // section violates minimum
- {
- sectionSizes[i] = minSizes.at(i); // set it to minimum
- foundMinimumViolation = true; // make sure we repeat the whole optimization process
- minimumLockedSections.append(i);
- }
- }
- if (foundMinimumViolation)
- {
- freeSize = totalSize;
- for (int i=0; i<sectionCount; ++i)
- {
- if (!minimumLockedSections.contains(i)) // only put sections that haven't hit their minimum back into the pool
- unfinishedSections.append(i);
- else
- freeSize -= sectionSizes.at(i); // remove size of minimum locked sections from available space in next round
- }
- // reset all section sizes to zero that are in unfinished sections (all others have been set to their minimum):
- for (int i=0; i<unfinishedSections.size(); ++i)
- sectionSizes[unfinishedSections.at(i)] = 0;
- }
- }
- if (outerIterations == sectionCount*2)
- qDebug() << Q_FUNC_INFO << "Exceeded maximum expected outer iteration count, layouting aborted. Input was:" << maxSizes << minSizes << stretchFactors << totalSize;
-
- QVector<int> result(sectionCount);
- for (int i=0; i<sectionCount; ++i)
- result[i] = qRound(sectionSizes.at(i));
- return result;
- }
- /*! \internal
-
- This is a helper function for the implementation of subclasses.
-
- It returns the minimum size that should finally be used for the outer rect of the passed layout
- element \a el.
-
- It takes into account whether a manual minimum size is set (\ref
- QCPLayoutElement::setMinimumSize), which size constraint is set (\ref
- QCPLayoutElement::setSizeConstraintRect), as well as the minimum size hint, if no manual minimum
- size was set (\ref QCPLayoutElement::minimumOuterSizeHint).
- */
- QSize QCPLayout::getFinalMinimumOuterSize(const QCPLayoutElement *el)
- {
- QSize minOuterHint = el->minimumOuterSizeHint();
- QSize minOuter = el->minimumSize(); // depending on sizeConstraitRect this might be with respect to inner rect, so possibly add margins in next four lines (preserving unset minimum of 0)
- if (minOuter.width() > 0 && el->sizeConstraintRect() == QCPLayoutElement::scrInnerRect)
- minOuter.rwidth() += el->margins().left() + el->margins().right();
- if (minOuter.height() > 0 && el->sizeConstraintRect() == QCPLayoutElement::scrInnerRect)
- minOuter.rheight() += el->margins().top() + el->margins().bottom();
-
- return QSize(minOuter.width() > 0 ? minOuter.width() : minOuterHint.width(),
- minOuter.height() > 0 ? minOuter.height() : minOuterHint.height());;
- }
- /*! \internal
-
- This is a helper function for the implementation of subclasses.
-
- It returns the maximum size that should finally be used for the outer rect of the passed layout
- element \a el.
-
- It takes into account whether a manual maximum size is set (\ref
- QCPLayoutElement::setMaximumSize), which size constraint is set (\ref
- QCPLayoutElement::setSizeConstraintRect), as well as the maximum size hint, if no manual maximum
- size was set (\ref QCPLayoutElement::maximumOuterSizeHint).
- */
- QSize QCPLayout::getFinalMaximumOuterSize(const QCPLayoutElement *el)
- {
- QSize maxOuterHint = el->maximumOuterSizeHint();
- QSize maxOuter = el->maximumSize(); // depending on sizeConstraitRect this might be with respect to inner rect, so possibly add margins in next four lines (preserving unset maximum of QWIDGETSIZE_MAX)
- if (maxOuter.width() < QWIDGETSIZE_MAX && el->sizeConstraintRect() == QCPLayoutElement::scrInnerRect)
- maxOuter.rwidth() += el->margins().left() + el->margins().right();
- if (maxOuter.height() < QWIDGETSIZE_MAX && el->sizeConstraintRect() == QCPLayoutElement::scrInnerRect)
- maxOuter.rheight() += el->margins().top() + el->margins().bottom();
-
- return QSize(maxOuter.width() < QWIDGETSIZE_MAX ? maxOuter.width() : maxOuterHint.width(),
- maxOuter.height() < QWIDGETSIZE_MAX ? maxOuter.height() : maxOuterHint.height());
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPLayoutGrid
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPLayoutGrid
- \brief A layout that arranges child elements in a grid
- Elements are laid out in a grid with configurable stretch factors (\ref setColumnStretchFactor,
- \ref setRowStretchFactor) and spacing (\ref setColumnSpacing, \ref setRowSpacing).
- Elements can be added to cells via \ref addElement. The grid is expanded if the specified row or
- column doesn't exist yet. Whether a cell contains a valid layout element can be checked with \ref
- hasElement, that element can be retrieved with \ref element. If rows and columns that only have
- empty cells shall be removed, call \ref simplify. Removal of elements is either done by just
- adding the element to a different layout or by using the QCPLayout interface \ref take or \ref
- remove.
- If you use \ref addElement(QCPLayoutElement*) without explicit parameters for \a row and \a
- column, the grid layout will choose the position according to the current \ref setFillOrder and
- the wrapping (\ref setWrap).
- Row and column insertion can be performed with \ref insertRow and \ref insertColumn.
- */
- /* start documentation of inline functions */
- /*! \fn int QCPLayoutGrid::rowCount() const
- Returns the number of rows in the layout.
- \see columnCount
- */
- /*! \fn int QCPLayoutGrid::columnCount() const
- Returns the number of columns in the layout.
- \see rowCount
- */
- /* end documentation of inline functions */
- /*!
- Creates an instance of QCPLayoutGrid and sets default values.
- */
- QCPLayoutGrid::QCPLayoutGrid() :
- mColumnSpacing(5),
- mRowSpacing(5),
- mWrap(0),
- mFillOrder(foRowsFirst)
- {
- }
- QCPLayoutGrid::~QCPLayoutGrid()
- {
- // clear all child layout elements. This is important because only the specific layouts know how
- // to handle removing elements (clear calls virtual removeAt method to do that).
- clear();
- }
- /*!
- Returns the element in the cell in \a row and \a column.
-
- Returns 0 if either the row/column is invalid or if the cell is empty. In those cases, a qDebug
- message is printed. To check whether a cell exists and isn't empty, use \ref hasElement.
-
- \see addElement, hasElement
- */
- QCPLayoutElement *QCPLayoutGrid::element(int row, int column) const
- {
- if (row >= 0 && row < mElements.size())
- {
- if (column >= 0 && column < mElements.first().size())
- {
- if (QCPLayoutElement *result = mElements.at(row).at(column))
- return result;
- else
- qDebug() << Q_FUNC_INFO << "Requested cell is empty. Row:" << row << "Column:" << column;
- } else
- qDebug() << Q_FUNC_INFO << "Invalid column. Row:" << row << "Column:" << column;
- } else
- qDebug() << Q_FUNC_INFO << "Invalid row. Row:" << row << "Column:" << column;
- return 0;
- }
- /*! \overload
- Adds the \a element to cell with \a row and \a column. If \a element is already in a layout, it
- is first removed from there. If \a row or \a column don't exist yet, the layout is expanded
- accordingly.
- Returns true if the element was added successfully, i.e. if the cell at \a row and \a column
- didn't already have an element.
- Use the overload of this method without explicit row/column index to place the element according
- to the configured fill order and wrapping settings.
- \see element, hasElement, take, remove
- */
- bool QCPLayoutGrid::addElement(int row, int column, QCPLayoutElement *element)
- {
- if (!hasElement(row, column))
- {
- if (element && element->layout()) // remove from old layout first
- element->layout()->take(element);
- expandTo(row+1, column+1);
- mElements[row][column] = element;
- if (element)
- adoptElement(element);
- return true;
- } else
- qDebug() << Q_FUNC_INFO << "There is already an element in the specified row/column:" << row << column;
- return false;
- }
- /*! \overload
- Adds the \a element to the next empty cell according to the current fill order (\ref
- setFillOrder) and wrapping (\ref setWrap). If \a element is already in a layout, it is first
- removed from there. If necessary, the layout is expanded to hold the new element.
- Returns true if the element was added successfully.
- \see setFillOrder, setWrap, element, hasElement, take, remove
- */
- bool QCPLayoutGrid::addElement(QCPLayoutElement *element)
- {
- int rowIndex = 0;
- int colIndex = 0;
- if (mFillOrder == foColumnsFirst)
- {
- while (hasElement(rowIndex, colIndex))
- {
- ++colIndex;
- if (colIndex >= mWrap && mWrap > 0)
- {
- colIndex = 0;
- ++rowIndex;
- }
- }
- } else
- {
- while (hasElement(rowIndex, colIndex))
- {
- ++rowIndex;
- if (rowIndex >= mWrap && mWrap > 0)
- {
- rowIndex = 0;
- ++colIndex;
- }
- }
- }
- return addElement(rowIndex, colIndex, element);
- }
- /*!
- Returns whether the cell at \a row and \a column exists and contains a valid element, i.e. isn't
- empty.
-
- \see element
- */
- bool QCPLayoutGrid::hasElement(int row, int column)
- {
- if (row >= 0 && row < rowCount() && column >= 0 && column < columnCount())
- return mElements.at(row).at(column);
- else
- return false;
- }
- /*!
- Sets the stretch \a factor of \a column.
-
- Stretch factors control the relative sizes of rows and columns. Cells will not be resized beyond
- their minimum and maximum widths/heights, regardless of the stretch factor. (see \ref
- QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize, \ref
- QCPLayoutElement::setSizeConstraintRect.)
-
- The default stretch factor of newly created rows/columns is 1.
-
- \see setColumnStretchFactors, setRowStretchFactor
- */
- void QCPLayoutGrid::setColumnStretchFactor(int column, double factor)
- {
- if (column >= 0 && column < columnCount())
- {
- if (factor > 0)
- mColumnStretchFactors[column] = factor;
- else
- qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << factor;
- } else
- qDebug() << Q_FUNC_INFO << "Invalid column:" << column;
- }
- /*!
- Sets the stretch \a factors of all columns. \a factors must have the size \ref columnCount.
-
- Stretch factors control the relative sizes of rows and columns. Cells will not be resized beyond
- their minimum and maximum widths/heights, regardless of the stretch factor. (see \ref
- QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize, \ref
- QCPLayoutElement::setSizeConstraintRect.)
-
- The default stretch factor of newly created rows/columns is 1.
-
- \see setColumnStretchFactor, setRowStretchFactors
- */
- void QCPLayoutGrid::setColumnStretchFactors(const QList<double> &factors)
- {
- if (factors.size() == mColumnStretchFactors.size())
- {
- mColumnStretchFactors = factors;
- for (int i=0; i<mColumnStretchFactors.size(); ++i)
- {
- if (mColumnStretchFactors.at(i) <= 0)
- {
- qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << mColumnStretchFactors.at(i);
- mColumnStretchFactors[i] = 1;
- }
- }
- } else
- qDebug() << Q_FUNC_INFO << "Column count not equal to passed stretch factor count:" << factors;
- }
- /*!
- Sets the stretch \a factor of \a row.
-
- Stretch factors control the relative sizes of rows and columns. Cells will not be resized beyond
- their minimum and maximum widths/heights, regardless of the stretch factor. (see \ref
- QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize, \ref
- QCPLayoutElement::setSizeConstraintRect.)
-
- The default stretch factor of newly created rows/columns is 1.
-
- \see setColumnStretchFactors, setRowStretchFactor
- */
- void QCPLayoutGrid::setRowStretchFactor(int row, double factor)
- {
- if (row >= 0 && row < rowCount())
- {
- if (factor > 0)
- mRowStretchFactors[row] = factor;
- else
- qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << factor;
- } else
- qDebug() << Q_FUNC_INFO << "Invalid row:" << row;
- }
- /*!
- Sets the stretch \a factors of all rows. \a factors must have the size \ref rowCount.
-
- Stretch factors control the relative sizes of rows and columns. Cells will not be resized beyond
- their minimum and maximum widths/heights, regardless of the stretch factor. (see \ref
- QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize, \ref
- QCPLayoutElement::setSizeConstraintRect.)
-
- The default stretch factor of newly created rows/columns is 1.
-
- \see setRowStretchFactor, setColumnStretchFactors
- */
- void QCPLayoutGrid::setRowStretchFactors(const QList<double> &factors)
- {
- if (factors.size() == mRowStretchFactors.size())
- {
- mRowStretchFactors = factors;
- for (int i=0; i<mRowStretchFactors.size(); ++i)
- {
- if (mRowStretchFactors.at(i) <= 0)
- {
- qDebug() << Q_FUNC_INFO << "Invalid stretch factor, must be positive:" << mRowStretchFactors.at(i);
- mRowStretchFactors[i] = 1;
- }
- }
- } else
- qDebug() << Q_FUNC_INFO << "Row count not equal to passed stretch factor count:" << factors;
- }
- /*!
- Sets the gap that is left blank between columns to \a pixels.
-
- \see setRowSpacing
- */
- void QCPLayoutGrid::setColumnSpacing(int pixels)
- {
- mColumnSpacing = pixels;
- }
- /*!
- Sets the gap that is left blank between rows to \a pixels.
-
- \see setColumnSpacing
- */
- void QCPLayoutGrid::setRowSpacing(int pixels)
- {
- mRowSpacing = pixels;
- }
- /*!
- Sets the maximum number of columns or rows that are used, before new elements added with \ref
- addElement(QCPLayoutElement*) will start to fill the next row or column, respectively. It depends
- on \ref setFillOrder, whether rows or columns are wrapped.
- If \a count is set to zero, no wrapping will ever occur.
-
- If you wish to re-wrap the elements currently in the layout, call \ref setFillOrder with \a
- rearrange set to true (the actual fill order doesn't need to be changed for the rearranging to be
- done).
- Note that the method \ref addElement(int row, int column, QCPLayoutElement *element) with
- explicitly stated row and column is not subject to wrapping and can place elements even beyond
- the specified wrapping point.
- \see setFillOrder
- */
- void QCPLayoutGrid::setWrap(int count)
- {
- mWrap = qMax(0, count);
- }
- /*!
- Sets the filling order and wrapping behaviour that is used when adding new elements with the
- method \ref addElement(QCPLayoutElement*).
- The specified \a order defines whether rows or columns are filled first. Using \ref setWrap, you
- can control at which row/column count wrapping into the next column/row will occur. If you set it
- to zero, no wrapping will ever occur. Changing the fill order also changes the meaning of the
- linear index used e.g. in \ref elementAt and \ref takeAt.
- If you want to have all current elements arranged in the new order, set \a rearrange to true. The
- elements will be rearranged in a way that tries to preserve their linear index. However, empty
- cells are skipped during build-up of the new cell order, which shifts the succeeding element's
- index. The rearranging is performed even if the specified \a order is already the current fill
- order. Thus this method can be used to re-wrap the current elements.
- If \a rearrange is false, the current element arrangement is not changed, which means the
- linear indexes change (because the linear index is dependent on the fill order).
- Note that the method \ref addElement(int row, int column, QCPLayoutElement *element) with
- explicitly stated row and column is not subject to wrapping and can place elements even beyond
- the specified wrapping point.
- \see setWrap, addElement(QCPLayoutElement*)
- */
- void QCPLayoutGrid::setFillOrder(FillOrder order, bool rearrange)
- {
- // if rearranging, take all elements via linear index of old fill order:
- const int elCount = elementCount();
- QVector<QCPLayoutElement*> tempElements;
- if (rearrange)
- {
- tempElements.reserve(elCount);
- for (int i=0; i<elCount; ++i)
- {
- if (elementAt(i))
- tempElements.append(takeAt(i));
- }
- simplify();
- }
- // change fill order as requested:
- mFillOrder = order;
- // if rearranging, re-insert via linear index according to new fill order:
- if (rearrange)
- {
- for (int i=0; i<tempElements.size(); ++i)
- addElement(tempElements.at(i));
- }
- }
- /*!
- Expands the layout to have \a newRowCount rows and \a newColumnCount columns. So the last valid
- row index will be \a newRowCount-1, the last valid column index will be \a newColumnCount-1.
-
- If the current column/row count is already larger or equal to \a newColumnCount/\a newRowCount,
- this function does nothing in that dimension.
-
- Newly created cells are empty, new rows and columns have the stretch factor 1.
-
- Note that upon a call to \ref addElement, the layout is expanded automatically to contain the
- specified row and column, using this function.
-
- \see simplify
- */
- void QCPLayoutGrid::expandTo(int newRowCount, int newColumnCount)
- {
- // add rows as necessary:
- while (rowCount() < newRowCount)
- {
- mElements.append(QList<QCPLayoutElement*>());
- mRowStretchFactors.append(1);
- }
- // go through rows and expand columns as necessary:
- int newColCount = qMax(columnCount(), newColumnCount);
- for (int i=0; i<rowCount(); ++i)
- {
- while (mElements.at(i).size() < newColCount)
- mElements[i].append(0);
- }
- while (mColumnStretchFactors.size() < newColCount)
- mColumnStretchFactors.append(1);
- }
- /*!
- Inserts a new row with empty cells at the row index \a newIndex. Valid values for \a newIndex
- range from 0 (inserts a row at the top) to \a rowCount (appends a row at the bottom).
-
- \see insertColumn
- */
- void QCPLayoutGrid::insertRow(int newIndex)
- {
- if (mElements.isEmpty() || mElements.first().isEmpty()) // if grid is completely empty, add first cell
- {
- expandTo(1, 1);
- return;
- }
-
- if (newIndex < 0)
- newIndex = 0;
- if (newIndex > rowCount())
- newIndex = rowCount();
-
- mRowStretchFactors.insert(newIndex, 1);
- QList<QCPLayoutElement*> newRow;
- for (int col=0; col<columnCount(); ++col)
- newRow.append((QCPLayoutElement*)0);
- mElements.insert(newIndex, newRow);
- }
- /*!
- Inserts a new column with empty cells at the column index \a newIndex. Valid values for \a
- newIndex range from 0 (inserts a column at the left) to \a columnCount (appends a column at the
- right).
-
- \see insertRow
- */
- void QCPLayoutGrid::insertColumn(int newIndex)
- {
- if (mElements.isEmpty() || mElements.first().isEmpty()) // if grid is completely empty, add first cell
- {
- expandTo(1, 1);
- return;
- }
-
- if (newIndex < 0)
- newIndex = 0;
- if (newIndex > columnCount())
- newIndex = columnCount();
-
- mColumnStretchFactors.insert(newIndex, 1);
- for (int row=0; row<rowCount(); ++row)
- mElements[row].insert(newIndex, (QCPLayoutElement*)0);
- }
- /*!
- Converts the given \a row and \a column to the linear index used by some methods of \ref
- QCPLayoutGrid and \ref QCPLayout.
- The way the cells are indexed depends on \ref setFillOrder. If it is \ref foRowsFirst, the
- indices increase left to right and then top to bottom. If it is \ref foColumnsFirst, the indices
- increase top to bottom and then left to right.
- For the returned index to be valid, \a row and \a column must be valid indices themselves, i.e.
- greater or equal to zero and smaller than the current \ref rowCount/\ref columnCount.
- \see indexToRowCol
- */
- int QCPLayoutGrid::rowColToIndex(int row, int column) const
- {
- if (row >= 0 && row < rowCount())
- {
- if (column >= 0 && column < columnCount())
- {
- switch (mFillOrder)
- {
- case foRowsFirst: return column*rowCount() + row;
- case foColumnsFirst: return row*columnCount() + column;
- }
- } else
- qDebug() << Q_FUNC_INFO << "row index out of bounds:" << row;
- } else
- qDebug() << Q_FUNC_INFO << "column index out of bounds:" << column;
- return 0;
- }
- /*!
- Converts the linear index to row and column indices and writes the result to \a row and \a
- column.
- The way the cells are indexed depends on \ref setFillOrder. If it is \ref foRowsFirst, the
- indices increase left to right and then top to bottom. If it is \ref foColumnsFirst, the indices
- increase top to bottom and then left to right.
- If there are no cells (i.e. column or row count is zero), sets \a row and \a column to -1.
- For the retrieved \a row and \a column to be valid, the passed \a index must be valid itself,
- i.e. greater or equal to zero and smaller than the current \ref elementCount.
- \see rowColToIndex
- */
- void QCPLayoutGrid::indexToRowCol(int index, int &row, int &column) const
- {
- row = -1;
- column = -1;
- const int nCols = columnCount();
- const int nRows = rowCount();
- if (nCols == 0 || nRows == 0)
- return;
- if (index < 0 || index >= elementCount())
- {
- qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
- return;
- }
-
- switch (mFillOrder)
- {
- case foRowsFirst:
- {
- column = index / nRows;
- row = index % nRows;
- break;
- }
- case foColumnsFirst:
- {
- row = index / nCols;
- column = index % nCols;
- break;
- }
- }
- }
- /* inherits documentation from base class */
- void QCPLayoutGrid::updateLayout()
- {
- QVector<int> minColWidths, minRowHeights, maxColWidths, maxRowHeights;
- getMinimumRowColSizes(&minColWidths, &minRowHeights);
- getMaximumRowColSizes(&maxColWidths, &maxRowHeights);
-
- int totalRowSpacing = (rowCount()-1) * mRowSpacing;
- int totalColSpacing = (columnCount()-1) * mColumnSpacing;
- QVector<int> colWidths = getSectionSizes(maxColWidths, minColWidths, mColumnStretchFactors.toVector(), mRect.width()-totalColSpacing);
- QVector<int> rowHeights = getSectionSizes(maxRowHeights, minRowHeights, mRowStretchFactors.toVector(), mRect.height()-totalRowSpacing);
-
- // go through cells and set rects accordingly:
- int yOffset = mRect.top();
- for (int row=0; row<rowCount(); ++row)
- {
- if (row > 0)
- yOffset += rowHeights.at(row-1)+mRowSpacing;
- int xOffset = mRect.left();
- for (int col=0; col<columnCount(); ++col)
- {
- if (col > 0)
- xOffset += colWidths.at(col-1)+mColumnSpacing;
- if (mElements.at(row).at(col))
- mElements.at(row).at(col)->setOuterRect(QRect(xOffset, yOffset, colWidths.at(col), rowHeights.at(row)));
- }
- }
- }
- /*!
- \seebaseclassmethod
- Note that the association of the linear \a index to the row/column based cells depends on the
- current setting of \ref setFillOrder.
- \see rowColToIndex
- */
- QCPLayoutElement *QCPLayoutGrid::elementAt(int index) const
- {
- if (index >= 0 && index < elementCount())
- {
- int row, col;
- indexToRowCol(index, row, col);
- return mElements.at(row).at(col);
- } else
- return 0;
- }
- /*!
- \seebaseclassmethod
- Note that the association of the linear \a index to the row/column based cells depends on the
- current setting of \ref setFillOrder.
- \see rowColToIndex
- */
- QCPLayoutElement *QCPLayoutGrid::takeAt(int index)
- {
- if (QCPLayoutElement *el = elementAt(index))
- {
- releaseElement(el);
- int row, col;
- indexToRowCol(index, row, col);
- mElements[row][col] = 0;
- return el;
- } else
- {
- qDebug() << Q_FUNC_INFO << "Attempt to take invalid index:" << index;
- return 0;
- }
- }
- /* inherits documentation from base class */
- bool QCPLayoutGrid::take(QCPLayoutElement *element)
- {
- if (element)
- {
- for (int i=0; i<elementCount(); ++i)
- {
- if (elementAt(i) == element)
- {
- takeAt(i);
- return true;
- }
- }
- qDebug() << Q_FUNC_INFO << "Element not in this layout, couldn't take";
- } else
- qDebug() << Q_FUNC_INFO << "Can't take null element";
- return false;
- }
- /* inherits documentation from base class */
- QList<QCPLayoutElement*> QCPLayoutGrid::elements(bool recursive) const
- {
- QList<QCPLayoutElement*> result;
- const int elCount = elementCount();
- #if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
- result.reserve(elCount);
- #endif
- for (int i=0; i<elCount; ++i)
- result.append(elementAt(i));
- if (recursive)
- {
- for (int i=0; i<elCount; ++i)
- {
- if (result.at(i))
- result << result.at(i)->elements(recursive);
- }
- }
- return result;
- }
- /*!
- Simplifies the layout by collapsing rows and columns which only contain empty cells.
- */
- void QCPLayoutGrid::simplify()
- {
- // remove rows with only empty cells:
- for (int row=rowCount()-1; row>=0; --row)
- {
- bool hasElements = false;
- for (int col=0; col<columnCount(); ++col)
- {
- if (mElements.at(row).at(col))
- {
- hasElements = true;
- break;
- }
- }
- if (!hasElements)
- {
- mRowStretchFactors.removeAt(row);
- mElements.removeAt(row);
- if (mElements.isEmpty()) // removed last element, also remove stretch factor (wouldn't happen below because also columnCount changed to 0 now)
- mColumnStretchFactors.clear();
- }
- }
-
- // remove columns with only empty cells:
- for (int col=columnCount()-1; col>=0; --col)
- {
- bool hasElements = false;
- for (int row=0; row<rowCount(); ++row)
- {
- if (mElements.at(row).at(col))
- {
- hasElements = true;
- break;
- }
- }
- if (!hasElements)
- {
- mColumnStretchFactors.removeAt(col);
- for (int row=0; row<rowCount(); ++row)
- mElements[row].removeAt(col);
- }
- }
- }
- /* inherits documentation from base class */
- QSize QCPLayoutGrid::minimumOuterSizeHint() const
- {
- QVector<int> minColWidths, minRowHeights;
- getMinimumRowColSizes(&minColWidths, &minRowHeights);
- QSize result(0, 0);
- for (int i=0; i<minColWidths.size(); ++i)
- result.rwidth() += minColWidths.at(i);
- for (int i=0; i<minRowHeights.size(); ++i)
- result.rheight() += minRowHeights.at(i);
- result.rwidth() += qMax(0, columnCount()-1) * mColumnSpacing;
- result.rheight() += qMax(0, rowCount()-1) * mRowSpacing;
- result.rwidth() += mMargins.left()+mMargins.right();
- result.rheight() += mMargins.top()+mMargins.bottom();
- return result;
- }
- /* inherits documentation from base class */
- QSize QCPLayoutGrid::maximumOuterSizeHint() const
- {
- QVector<int> maxColWidths, maxRowHeights;
- getMaximumRowColSizes(&maxColWidths, &maxRowHeights);
-
- QSize result(0, 0);
- for (int i=0; i<maxColWidths.size(); ++i)
- result.setWidth(qMin(result.width()+maxColWidths.at(i), QWIDGETSIZE_MAX));
- for (int i=0; i<maxRowHeights.size(); ++i)
- result.setHeight(qMin(result.height()+maxRowHeights.at(i), QWIDGETSIZE_MAX));
- result.rwidth() += qMax(0, columnCount()-1) * mColumnSpacing;
- result.rheight() += qMax(0, rowCount()-1) * mRowSpacing;
- result.rwidth() += mMargins.left()+mMargins.right();
- result.rheight() += mMargins.top()+mMargins.bottom();
- if (result.height() > QWIDGETSIZE_MAX)
- result.setHeight(QWIDGETSIZE_MAX);
- if (result.width() > QWIDGETSIZE_MAX)
- result.setWidth(QWIDGETSIZE_MAX);
- return result;
- }
- /*! \internal
-
- Places the minimum column widths and row heights into \a minColWidths and \a minRowHeights
- respectively.
-
- The minimum height of a row is the largest minimum height of any element's outer rect in that
- row. The minimum width of a column is the largest minimum width of any element's outer rect in
- that column.
-
- This is a helper function for \ref updateLayout.
-
- \see getMaximumRowColSizes
- */
- void QCPLayoutGrid::getMinimumRowColSizes(QVector<int> *minColWidths, QVector<int> *minRowHeights) const
- {
- *minColWidths = QVector<int>(columnCount(), 0);
- *minRowHeights = QVector<int>(rowCount(), 0);
- for (int row=0; row<rowCount(); ++row)
- {
- for (int col=0; col<columnCount(); ++col)
- {
- if (QCPLayoutElement *el = mElements.at(row).at(col))
- {
- QSize minSize = getFinalMinimumOuterSize(el);
- if (minColWidths->at(col) < minSize.width())
- (*minColWidths)[col] = minSize.width();
- if (minRowHeights->at(row) < minSize.height())
- (*minRowHeights)[row] = minSize.height();
- }
- }
- }
- }
- /*! \internal
-
- Places the maximum column widths and row heights into \a maxColWidths and \a maxRowHeights
- respectively.
-
- The maximum height of a row is the smallest maximum height of any element's outer rect in that
- row. The maximum width of a column is the smallest maximum width of any element's outer rect in
- that column.
-
- This is a helper function for \ref updateLayout.
-
- \see getMinimumRowColSizes
- */
- void QCPLayoutGrid::getMaximumRowColSizes(QVector<int> *maxColWidths, QVector<int> *maxRowHeights) const
- {
- *maxColWidths = QVector<int>(columnCount(), QWIDGETSIZE_MAX);
- *maxRowHeights = QVector<int>(rowCount(), QWIDGETSIZE_MAX);
- for (int row=0; row<rowCount(); ++row)
- {
- for (int col=0; col<columnCount(); ++col)
- {
- if (QCPLayoutElement *el = mElements.at(row).at(col))
- {
- QSize maxSize = getFinalMaximumOuterSize(el);
- if (maxColWidths->at(col) > maxSize.width())
- (*maxColWidths)[col] = maxSize.width();
- if (maxRowHeights->at(row) > maxSize.height())
- (*maxRowHeights)[row] = maxSize.height();
- }
- }
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPLayoutInset
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPLayoutInset
- \brief A layout that places child elements aligned to the border or arbitrarily positioned
-
- Elements are placed either aligned to the border or at arbitrary position in the area of the
- layout. Which placement applies is controlled with the \ref InsetPlacement (\ref
- setInsetPlacement).
- Elements are added via \ref addElement(QCPLayoutElement *element, Qt::Alignment alignment) or
- addElement(QCPLayoutElement *element, const QRectF &rect). If the first method is used, the inset
- placement will default to \ref ipBorderAligned and the element will be aligned according to the
- \a alignment parameter. The second method defaults to \ref ipFree and allows placing elements at
- arbitrary position and size, defined by \a rect.
-
- The alignment or rect can be set via \ref setInsetAlignment or \ref setInsetRect, respectively.
-
- This is the layout that every QCPAxisRect has as \ref QCPAxisRect::insetLayout.
- */
- /* start documentation of inline functions */
- /*! \fn virtual void QCPLayoutInset::simplify()
-
- The QCPInsetLayout does not need simplification since it can never have empty cells due to its
- linear index structure. This method does nothing.
- */
- /* end documentation of inline functions */
- /*!
- Creates an instance of QCPLayoutInset and sets default values.
- */
- QCPLayoutInset::QCPLayoutInset()
- {
- }
- QCPLayoutInset::~QCPLayoutInset()
- {
- // clear all child layout elements. This is important because only the specific layouts know how
- // to handle removing elements (clear calls virtual removeAt method to do that).
- clear();
- }
- /*!
- Returns the placement type of the element with the specified \a index.
- */
- QCPLayoutInset::InsetPlacement QCPLayoutInset::insetPlacement(int index) const
- {
- if (elementAt(index))
- return mInsetPlacement.at(index);
- else
- {
- qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
- return ipFree;
- }
- }
- /*!
- Returns the alignment of the element with the specified \a index. The alignment only has a
- meaning, if the inset placement (\ref setInsetPlacement) is \ref ipBorderAligned.
- */
- Qt::Alignment QCPLayoutInset::insetAlignment(int index) const
- {
- if (elementAt(index))
- return mInsetAlignment.at(index);
- else
- {
- qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
- return 0;
- }
- }
- /*!
- Returns the rect of the element with the specified \a index. The rect only has a
- meaning, if the inset placement (\ref setInsetPlacement) is \ref ipFree.
- */
- QRectF QCPLayoutInset::insetRect(int index) const
- {
- if (elementAt(index))
- return mInsetRect.at(index);
- else
- {
- qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
- return QRectF();
- }
- }
- /*!
- Sets the inset placement type of the element with the specified \a index to \a placement.
-
- \see InsetPlacement
- */
- void QCPLayoutInset::setInsetPlacement(int index, QCPLayoutInset::InsetPlacement placement)
- {
- if (elementAt(index))
- mInsetPlacement[index] = placement;
- else
- qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
- }
- /*!
- If the inset placement (\ref setInsetPlacement) is \ref ipBorderAligned, this function
- is used to set the alignment of the element with the specified \a index to \a alignment.
-
- \a alignment is an or combination of the following alignment flags: Qt::AlignLeft,
- Qt::AlignHCenter, Qt::AlighRight, Qt::AlignTop, Qt::AlignVCenter, Qt::AlignBottom. Any other
- alignment flags will be ignored.
- */
- void QCPLayoutInset::setInsetAlignment(int index, Qt::Alignment alignment)
- {
- if (elementAt(index))
- mInsetAlignment[index] = alignment;
- else
- qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
- }
- /*!
- If the inset placement (\ref setInsetPlacement) is \ref ipFree, this function is used to set the
- position and size of the element with the specified \a index to \a rect.
-
- \a rect is given in fractions of the whole inset layout rect. So an inset with rect (0, 0, 1, 1)
- will span the entire layout. An inset with rect (0.6, 0.1, 0.35, 0.35) will be in the top right
- corner of the layout, with 35% width and height of the parent layout.
-
- Note that the minimum and maximum sizes of the embedded element (\ref
- QCPLayoutElement::setMinimumSize, \ref QCPLayoutElement::setMaximumSize) are enforced.
- */
- void QCPLayoutInset::setInsetRect(int index, const QRectF &rect)
- {
- if (elementAt(index))
- mInsetRect[index] = rect;
- else
- qDebug() << Q_FUNC_INFO << "Invalid element index:" << index;
- }
- /* inherits documentation from base class */
- void QCPLayoutInset::updateLayout()
- {
- for (int i=0; i<mElements.size(); ++i)
- {
- QCPLayoutElement *el = mElements.at(i);
- QRect insetRect;
- QSize finalMinSize = getFinalMinimumOuterSize(el);
- QSize finalMaxSize = getFinalMaximumOuterSize(el);
- if (mInsetPlacement.at(i) == ipFree)
- {
- insetRect = QRect(rect().x()+rect().width()*mInsetRect.at(i).x(),
- rect().y()+rect().height()*mInsetRect.at(i).y(),
- rect().width()*mInsetRect.at(i).width(),
- rect().height()*mInsetRect.at(i).height());
- if (insetRect.size().width() < finalMinSize.width())
- insetRect.setWidth(finalMinSize.width());
- if (insetRect.size().height() < finalMinSize.height())
- insetRect.setHeight(finalMinSize.height());
- if (insetRect.size().width() > finalMaxSize.width())
- insetRect.setWidth(finalMaxSize.width());
- if (insetRect.size().height() > finalMaxSize.height())
- insetRect.setHeight(finalMaxSize.height());
- } else if (mInsetPlacement.at(i) == ipBorderAligned)
- {
- insetRect.setSize(finalMinSize);
- Qt::Alignment al = mInsetAlignment.at(i);
- if (al.testFlag(Qt::AlignLeft)) insetRect.moveLeft(rect().x());
- else if (al.testFlag(Qt::AlignRight)) insetRect.moveRight(rect().x()+rect().width());
- else insetRect.moveLeft(rect().x()+rect().width()*0.5-finalMinSize.width()*0.5); // default to Qt::AlignHCenter
- if (al.testFlag(Qt::AlignTop)) insetRect.moveTop(rect().y());
- else if (al.testFlag(Qt::AlignBottom)) insetRect.moveBottom(rect().y()+rect().height());
- else insetRect.moveTop(rect().y()+rect().height()*0.5-finalMinSize.height()*0.5); // default to Qt::AlignVCenter
- }
- mElements.at(i)->setOuterRect(insetRect);
- }
- }
- /* inherits documentation from base class */
- int QCPLayoutInset::elementCount() const
- {
- return mElements.size();
- }
- /* inherits documentation from base class */
- QCPLayoutElement *QCPLayoutInset::elementAt(int index) const
- {
- if (index >= 0 && index < mElements.size())
- return mElements.at(index);
- else
- return 0;
- }
- /* inherits documentation from base class */
- QCPLayoutElement *QCPLayoutInset::takeAt(int index)
- {
- if (QCPLayoutElement *el = elementAt(index))
- {
- releaseElement(el);
- mElements.removeAt(index);
- mInsetPlacement.removeAt(index);
- mInsetAlignment.removeAt(index);
- mInsetRect.removeAt(index);
- return el;
- } else
- {
- qDebug() << Q_FUNC_INFO << "Attempt to take invalid index:" << index;
- return 0;
- }
- }
- /* inherits documentation from base class */
- bool QCPLayoutInset::take(QCPLayoutElement *element)
- {
- if (element)
- {
- for (int i=0; i<elementCount(); ++i)
- {
- if (elementAt(i) == element)
- {
- takeAt(i);
- return true;
- }
- }
- qDebug() << Q_FUNC_INFO << "Element not in this layout, couldn't take";
- } else
- qDebug() << Q_FUNC_INFO << "Can't take null element";
- return false;
- }
- /*!
- The inset layout is sensitive to events only at areas where its (visible) child elements are
- sensitive. If the selectTest method of any of the child elements returns a positive number for \a
- pos, this method returns a value corresponding to 0.99 times the parent plot's selection
- tolerance. The inset layout is not selectable itself by default. So if \a onlySelectable is true,
- -1.0 is returned.
-
- See \ref QCPLayerable::selectTest for a general explanation of this virtual method.
- */
- double QCPLayoutInset::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- Q_UNUSED(details)
- if (onlySelectable)
- return -1;
-
- for (int i=0; i<mElements.size(); ++i)
- {
- // inset layout shall only return positive selectTest, if actually an inset object is at pos
- // else it would block the entire underlying QCPAxisRect with its surface.
- if (mElements.at(i)->realVisibility() && mElements.at(i)->selectTest(pos, onlySelectable) >= 0)
- return mParentPlot->selectionTolerance()*0.99;
- }
- return -1;
- }
- /*!
- Adds the specified \a element to the layout as an inset aligned at the border (\ref
- setInsetAlignment is initialized with \ref ipBorderAligned). The alignment is set to \a
- alignment.
-
- \a alignment is an or combination of the following alignment flags: Qt::AlignLeft,
- Qt::AlignHCenter, Qt::AlighRight, Qt::AlignTop, Qt::AlignVCenter, Qt::AlignBottom. Any other
- alignment flags will be ignored.
-
- \see addElement(QCPLayoutElement *element, const QRectF &rect)
- */
- void QCPLayoutInset::addElement(QCPLayoutElement *element, Qt::Alignment alignment)
- {
- if (element)
- {
- if (element->layout()) // remove from old layout first
- element->layout()->take(element);
- mElements.append(element);
- mInsetPlacement.append(ipBorderAligned);
- mInsetAlignment.append(alignment);
- mInsetRect.append(QRectF(0.6, 0.6, 0.4, 0.4));
- adoptElement(element);
- } else
- qDebug() << Q_FUNC_INFO << "Can't add null element";
- }
- /*!
- Adds the specified \a element to the layout as an inset with free positioning/sizing (\ref
- setInsetAlignment is initialized with \ref ipFree). The position and size is set to \a
- rect.
-
- \a rect is given in fractions of the whole inset layout rect. So an inset with rect (0, 0, 1, 1)
- will span the entire layout. An inset with rect (0.6, 0.1, 0.35, 0.35) will be in the top right
- corner of the layout, with 35% width and height of the parent layout.
-
- \see addElement(QCPLayoutElement *element, Qt::Alignment alignment)
- */
- void QCPLayoutInset::addElement(QCPLayoutElement *element, const QRectF &rect)
- {
- if (element)
- {
- if (element->layout()) // remove from old layout first
- element->layout()->take(element);
- mElements.append(element);
- mInsetPlacement.append(ipFree);
- mInsetAlignment.append(Qt::AlignRight|Qt::AlignTop);
- mInsetRect.append(rect);
- adoptElement(element);
- } else
- qDebug() << Q_FUNC_INFO << "Can't add null element";
- }
- /* end of 'src/layout.cpp' */
- /* including file 'src/lineending.cpp', size 11536 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPLineEnding
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPLineEnding
- \brief Handles the different ending decorations for line-like items
-
- \image html QCPLineEnding.png "The various ending styles currently supported"
-
- For every ending a line-like item has, an instance of this class exists. For example, QCPItemLine
- has two endings which can be set with QCPItemLine::setHead and QCPItemLine::setTail.
-
- The styles themselves are defined via the enum QCPLineEnding::EndingStyle. Most decorations can
- be modified regarding width and length, see \ref setWidth and \ref setLength. The direction of
- the ending decoration (e.g. direction an arrow is pointing) is controlled by the line-like item.
- For example, when both endings of a QCPItemLine are set to be arrows, they will point to opposite
- directions, e.g. "outward". This can be changed by \ref setInverted, which would make the
- respective arrow point inward.
-
- Note that due to the overloaded QCPLineEnding constructor, you may directly specify a
- QCPLineEnding::EndingStyle where actually a QCPLineEnding is expected, e.g.
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcplineending-sethead
- */
- /*!
- Creates a QCPLineEnding instance with default values (style \ref esNone).
- */
- QCPLineEnding::QCPLineEnding() :
- mStyle(esNone),
- mWidth(8),
- mLength(10),
- mInverted(false)
- {
- }
- /*!
- Creates a QCPLineEnding instance with the specified values.
- */
- QCPLineEnding::QCPLineEnding(QCPLineEnding::EndingStyle style, double width, double length, bool inverted) :
- mStyle(style),
- mWidth(width),
- mLength(length),
- mInverted(inverted)
- {
- }
- /*!
- Sets the style of the ending decoration.
- */
- void QCPLineEnding::setStyle(QCPLineEnding::EndingStyle style)
- {
- mStyle = style;
- }
- /*!
- Sets the width of the ending decoration, if the style supports it. On arrows, for example, the
- width defines the size perpendicular to the arrow's pointing direction.
-
- \see setLength
- */
- void QCPLineEnding::setWidth(double width)
- {
- mWidth = width;
- }
- /*!
- Sets the length of the ending decoration, if the style supports it. On arrows, for example, the
- length defines the size in pointing direction.
-
- \see setWidth
- */
- void QCPLineEnding::setLength(double length)
- {
- mLength = length;
- }
- /*!
- Sets whether the ending decoration shall be inverted. For example, an arrow decoration will point
- inward when \a inverted is set to true.
- Note that also the \a width direction is inverted. For symmetrical ending styles like arrows or
- discs, this doesn't make a difference. However, asymmetric styles like \ref esHalfBar are
- affected by it, which can be used to control to which side the half bar points to.
- */
- void QCPLineEnding::setInverted(bool inverted)
- {
- mInverted = inverted;
- }
- /*! \internal
-
- Returns the maximum pixel radius the ending decoration might cover, starting from the position
- the decoration is drawn at (typically a line ending/\ref QCPItemPosition of an item).
-
- This is relevant for clipping. Only omit painting of the decoration when the position where the
- decoration is supposed to be drawn is farther away from the clipping rect than the returned
- distance.
- */
- double QCPLineEnding::boundingDistance() const
- {
- switch (mStyle)
- {
- case esNone:
- return 0;
-
- case esFlatArrow:
- case esSpikeArrow:
- case esLineArrow:
- case esSkewedBar:
- return qSqrt(mWidth*mWidth+mLength*mLength); // items that have width and length
-
- case esDisc:
- case esSquare:
- case esDiamond:
- case esBar:
- case esHalfBar:
- return mWidth*1.42; // items that only have a width -> width*sqrt(2)
- }
- return 0;
- }
- /*!
- Starting from the origin of this line ending (which is style specific), returns the length
- covered by the line ending symbol, in backward direction.
-
- For example, the \ref esSpikeArrow has a shorter real length than a \ref esFlatArrow, even if
- both have the same \ref setLength value, because the spike arrow has an inward curved back, which
- reduces the length along its center axis (the drawing origin for arrows is at the tip).
-
- This function is used for precise, style specific placement of line endings, for example in
- QCPAxes.
- */
- double QCPLineEnding::realLength() const
- {
- switch (mStyle)
- {
- case esNone:
- case esLineArrow:
- case esSkewedBar:
- case esBar:
- case esHalfBar:
- return 0;
-
- case esFlatArrow:
- return mLength;
-
- case esDisc:
- case esSquare:
- case esDiamond:
- return mWidth*0.5;
-
- case esSpikeArrow:
- return mLength*0.8;
- }
- return 0;
- }
- /*! \internal
-
- Draws the line ending with the specified \a painter at the position \a pos. The direction of the
- line ending is controlled with \a dir.
- */
- void QCPLineEnding::draw(QCPPainter *painter, const QCPVector2D &pos, const QCPVector2D &dir) const
- {
- if (mStyle == esNone)
- return;
-
- QCPVector2D lengthVec = dir.normalized() * mLength*(mInverted ? -1 : 1);
- if (lengthVec.isNull())
- lengthVec = QCPVector2D(1, 0);
- QCPVector2D widthVec = dir.normalized().perpendicular() * mWidth*0.5*(mInverted ? -1 : 1);
-
- QPen penBackup = painter->pen();
- QBrush brushBackup = painter->brush();
- QPen miterPen = penBackup;
- miterPen.setJoinStyle(Qt::MiterJoin); // to make arrow heads spikey
- QBrush brush(painter->pen().color(), Qt::SolidPattern);
- switch (mStyle)
- {
- case esNone: break;
- case esFlatArrow:
- {
- QPointF points[3] = {pos.toPointF(),
- (pos-lengthVec+widthVec).toPointF(),
- (pos-lengthVec-widthVec).toPointF()
- };
- painter->setPen(miterPen);
- painter->setBrush(brush);
- painter->drawConvexPolygon(points, 3);
- painter->setBrush(brushBackup);
- painter->setPen(penBackup);
- break;
- }
- case esSpikeArrow:
- {
- QPointF points[4] = {pos.toPointF(),
- (pos-lengthVec+widthVec).toPointF(),
- (pos-lengthVec*0.8).toPointF(),
- (pos-lengthVec-widthVec).toPointF()
- };
- painter->setPen(miterPen);
- painter->setBrush(brush);
- painter->drawConvexPolygon(points, 4);
- painter->setBrush(brushBackup);
- painter->setPen(penBackup);
- break;
- }
- case esLineArrow:
- {
- QPointF points[3] = {(pos-lengthVec+widthVec).toPointF(),
- pos.toPointF(),
- (pos-lengthVec-widthVec).toPointF()
- };
- painter->setPen(miterPen);
- painter->drawPolyline(points, 3);
- painter->setPen(penBackup);
- break;
- }
- case esDisc:
- {
- painter->setBrush(brush);
- painter->drawEllipse(pos.toPointF(), mWidth*0.5, mWidth*0.5);
- painter->setBrush(brushBackup);
- break;
- }
- case esSquare:
- {
- QCPVector2D widthVecPerp = widthVec.perpendicular();
- QPointF points[4] = {(pos-widthVecPerp+widthVec).toPointF(),
- (pos-widthVecPerp-widthVec).toPointF(),
- (pos+widthVecPerp-widthVec).toPointF(),
- (pos+widthVecPerp+widthVec).toPointF()
- };
- painter->setPen(miterPen);
- painter->setBrush(brush);
- painter->drawConvexPolygon(points, 4);
- painter->setBrush(brushBackup);
- painter->setPen(penBackup);
- break;
- }
- case esDiamond:
- {
- QCPVector2D widthVecPerp = widthVec.perpendicular();
- QPointF points[4] = {(pos-widthVecPerp).toPointF(),
- (pos-widthVec).toPointF(),
- (pos+widthVecPerp).toPointF(),
- (pos+widthVec).toPointF()
- };
- painter->setPen(miterPen);
- painter->setBrush(brush);
- painter->drawConvexPolygon(points, 4);
- painter->setBrush(brushBackup);
- painter->setPen(penBackup);
- break;
- }
- case esBar:
- {
- painter->drawLine((pos+widthVec).toPointF(), (pos-widthVec).toPointF());
- break;
- }
- case esHalfBar:
- {
- painter->drawLine((pos+widthVec).toPointF(), pos.toPointF());
- break;
- }
- case esSkewedBar:
- {
- if (qFuzzyIsNull(painter->pen().widthF()) && !painter->modes().testFlag(QCPPainter::pmNonCosmetic))
- {
- // if drawing with cosmetic pen (perfectly thin stroke, happens only in vector exports), draw bar exactly on tip of line
- painter->drawLine((pos+widthVec+lengthVec*0.2*(mInverted?-1:1)).toPointF(),
- (pos-widthVec-lengthVec*0.2*(mInverted?-1:1)).toPointF());
- } else
- {
- // if drawing with thick (non-cosmetic) pen, shift bar a little in line direction to prevent line from sticking through bar slightly
- painter->drawLine((pos+widthVec+lengthVec*0.2*(mInverted?-1:1)+dir.normalized()*qMax(1.0f, (float)painter->pen().widthF())*0.5f).toPointF(),
- (pos-widthVec-lengthVec*0.2*(mInverted?-1:1)+dir.normalized()*qMax(1.0f, (float)painter->pen().widthF())*0.5f).toPointF());
- }
- break;
- }
- }
- }
- /*! \internal
- \overload
-
- Draws the line ending. The direction is controlled with the \a angle parameter in radians.
- */
- void QCPLineEnding::draw(QCPPainter *painter, const QCPVector2D &pos, double angle) const
- {
- draw(painter, pos, QCPVector2D(qCos(angle), qSin(angle)));
- }
- /* end of 'src/lineending.cpp' */
- /* including file 'src/axis/axisticker.cpp', size 18664 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPAxisTicker
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPAxisTicker
- \brief The base class tick generator used by QCPAxis to create tick positions and tick labels
-
- Each QCPAxis has an internal QCPAxisTicker (or a subclass) in order to generate tick positions
- and tick labels for the current axis range. The ticker of an axis can be set via \ref
- QCPAxis::setTicker. Since that method takes a <tt>QSharedPointer<QCPAxisTicker></tt>, multiple
- axes can share the same ticker instance.
-
- This base class generates normal tick coordinates and numeric labels for linear axes. It picks a
- reasonable tick step (the separation between ticks) which results in readable tick labels. The
- number of ticks that should be approximately generated can be set via \ref setTickCount.
- Depending on the current tick step strategy (\ref setTickStepStrategy), the algorithm either
- sacrifices readability to better match the specified tick count (\ref
- QCPAxisTicker::tssMeetTickCount) or relaxes the tick count in favor of better tick steps (\ref
- QCPAxisTicker::tssReadability), which is the default.
-
- The following more specialized axis ticker subclasses are available, see details in the
- respective class documentation:
-
- <center>
- <table>
- <tr><td style="text-align:right; padding: 0 1em">QCPAxisTickerFixed</td><td>\image html axisticker-fixed.png</td></tr>
- <tr><td style="text-align:right; padding: 0 1em">QCPAxisTickerLog</td><td>\image html axisticker-log.png</td></tr>
- <tr><td style="text-align:right; padding: 0 1em">QCPAxisTickerPi</td><td>\image html axisticker-pi.png</td></tr>
- <tr><td style="text-align:right; padding: 0 1em">QCPAxisTickerText</td><td>\image html axisticker-text.png</td></tr>
- <tr><td style="text-align:right; padding: 0 1em">QCPAxisTickerDateTime</td><td>\image html axisticker-datetime.png</td></tr>
- <tr><td style="text-align:right; padding: 0 1em">QCPAxisTickerTime</td><td>\image html axisticker-time.png
- \image html axisticker-time2.png</td></tr>
- </table>
- </center>
-
- \section axisticker-subclassing Creating own axis tickers
-
- Creating own axis tickers can be achieved very easily by sublassing QCPAxisTicker and
- reimplementing some or all of the available virtual methods.
- In the simplest case you might wish to just generate different tick steps than the other tickers,
- so you only reimplement the method \ref getTickStep. If you additionally want control over the
- string that will be shown as tick label, reimplement \ref getTickLabel.
-
- If you wish to have complete control, you can generate the tick vectors and tick label vectors
- yourself by reimplementing \ref createTickVector and \ref createLabelVector. The default
- implementations use the previously mentioned virtual methods \ref getTickStep and \ref
- getTickLabel, but your reimplementations don't necessarily need to do so. For example in the case
- of unequal tick steps, the method \ref getTickStep loses its usefulness and can be ignored.
-
- The sub tick count between major ticks can be controlled with \ref getSubTickCount. Full sub tick
- placement control is obtained by reimplementing \ref createSubTickVector.
-
- See the documentation of all these virtual methods in QCPAxisTicker for detailed information
- about the parameters and expected return values.
- */
- /*!
- Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
- managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
- */
- QCPAxisTicker::QCPAxisTicker() :
- mTickStepStrategy(tssReadability),
- mTickCount(5),
- mTickOrigin(0)
- {
- }
- QCPAxisTicker::~QCPAxisTicker()
- {
-
- }
- /*!
- Sets which strategy the axis ticker follows when choosing the size of the tick step. For the
- available strategies, see \ref TickStepStrategy.
- */
- void QCPAxisTicker::setTickStepStrategy(QCPAxisTicker::TickStepStrategy strategy)
- {
- mTickStepStrategy = strategy;
- }
- /*!
- Sets how many ticks this ticker shall aim to generate across the axis range. Note that \a count
- is not guaranteed to be matched exactly, as generating readable tick intervals may conflict with
- the requested number of ticks.
- Whether the readability has priority over meeting the requested \a count can be specified with
- \ref setTickStepStrategy.
- */
- void QCPAxisTicker::setTickCount(int count)
- {
- if (count > 0)
- mTickCount = count;
- else
- qDebug() << Q_FUNC_INFO << "tick count must be greater than zero:" << count;
- }
- /*!
- Sets the mathematical coordinate (or "offset") of the zeroth tick. This tick coordinate is just a
- concept and doesn't need to be inside the currently visible axis range.
-
- By default \a origin is zero, which for example yields ticks {-5, 0, 5, 10, 15,...} when the tick
- step is five. If \a origin is now set to 1 instead, the correspondingly generated ticks would be
- {-4, 1, 6, 11, 16,...}.
- */
- void QCPAxisTicker::setTickOrigin(double origin)
- {
- mTickOrigin = origin;
- }
- /*!
- This is the method called by QCPAxis in order to actually generate tick coordinates (\a ticks),
- tick label strings (\a tickLabels) and sub tick coordinates (\a subTicks).
-
- The ticks are generated for the specified \a range. The generated labels typically follow the
- specified \a locale, \a formatChar and number \a precision, however this might be different (or
- even irrelevant) for certain QCPAxisTicker subclasses.
-
- The output parameter \a ticks is filled with the generated tick positions in axis coordinates.
- The output parameters \a subTicks and \a tickLabels are optional (set them to 0 if not needed)
- and are respectively filled with sub tick coordinates, and tick label strings belonging to \a
- ticks by index.
- */
- void QCPAxisTicker::generate(const QCPRange &range, const QLocale &locale, QChar formatChar, int precision, QVector<double> &ticks, QVector<double> *subTicks, QVector<QString> *tickLabels)
- {
- // generate (major) ticks:
- double tickStep = getTickStep(range);
- ticks = createTickVector(tickStep, range);
- trimTicks(range, ticks, true); // trim ticks to visible range plus one outer tick on each side (incase a subclass createTickVector creates more)
-
- // generate sub ticks between major ticks:
- if (subTicks)
- {
- if (ticks.size() > 0)
- {
- *subTicks = createSubTickVector(getSubTickCount(tickStep), ticks);
- trimTicks(range, *subTicks, false);
- } else
- *subTicks = QVector<double>();
- }
-
- // finally trim also outliers (no further clipping happens in axis drawing):
- trimTicks(range, ticks, false);
- // generate labels for visible ticks if requested:
- if (tickLabels)
- *tickLabels = createLabelVector(ticks, locale, formatChar, precision);
- }
- /*! \internal
-
- Takes the entire currently visible axis range and returns a sensible tick step in
- order to provide readable tick labels as well as a reasonable number of tick counts (see \ref
- setTickCount, \ref setTickStepStrategy).
-
- If a QCPAxisTicker subclass only wants a different tick step behaviour than the default
- implementation, it should reimplement this method. See \ref cleanMantissa for a possible helper
- function.
- */
- double QCPAxisTicker::getTickStep(const QCPRange &range)
- {
- double exactStep = range.size()/(double)(mTickCount+1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers
- return cleanMantissa(exactStep);
- }
- /*! \internal
-
- Takes the \a tickStep, i.e. the distance between two consecutive ticks, and returns
- an appropriate number of sub ticks for that specific tick step.
-
- Note that a returned sub tick count of e.g. 4 will split each tick interval into 5 sections.
- */
- int QCPAxisTicker::getSubTickCount(double tickStep)
- {
- int result = 1; // default to 1, if no proper value can be found
-
- // separate integer and fractional part of mantissa:
- double epsilon = 0.01;
- double intPartf;
- int intPart;
- double fracPart = modf(getMantissa(tickStep), &intPartf);
- intPart = intPartf;
-
- // handle cases with (almost) integer mantissa:
- if (fracPart < epsilon || 1.0-fracPart < epsilon)
- {
- if (1.0-fracPart < epsilon)
- ++intPart;
- switch (intPart)
- {
- case 1: result = 4; break; // 1.0 -> 0.2 substep
- case 2: result = 3; break; // 2.0 -> 0.5 substep
- case 3: result = 2; break; // 3.0 -> 1.0 substep
- case 4: result = 3; break; // 4.0 -> 1.0 substep
- case 5: result = 4; break; // 5.0 -> 1.0 substep
- case 6: result = 2; break; // 6.0 -> 2.0 substep
- case 7: result = 6; break; // 7.0 -> 1.0 substep
- case 8: result = 3; break; // 8.0 -> 2.0 substep
- case 9: result = 2; break; // 9.0 -> 3.0 substep
- }
- } else
- {
- // handle cases with significantly fractional mantissa:
- if (qAbs(fracPart-0.5) < epsilon) // *.5 mantissa
- {
- switch (intPart)
- {
- case 1: result = 2; break; // 1.5 -> 0.5 substep
- case 2: result = 4; break; // 2.5 -> 0.5 substep
- case 3: result = 4; break; // 3.5 -> 0.7 substep
- case 4: result = 2; break; // 4.5 -> 1.5 substep
- case 5: result = 4; break; // 5.5 -> 1.1 substep (won't occur with default getTickStep from here on)
- case 6: result = 4; break; // 6.5 -> 1.3 substep
- case 7: result = 2; break; // 7.5 -> 2.5 substep
- case 8: result = 4; break; // 8.5 -> 1.7 substep
- case 9: result = 4; break; // 9.5 -> 1.9 substep
- }
- }
- // if mantissa fraction isn't 0.0 or 0.5, don't bother finding good sub tick marks, leave default
- }
-
- return result;
- }
- /*! \internal
-
- This method returns the tick label string as it should be printed under the \a tick coordinate.
- If a textual number is returned, it should respect the provided \a locale, \a formatChar and \a
- precision.
-
- If the returned value contains exponentials of the form "2e5" and beautifully typeset powers is
- enabled in the QCPAxis number format (\ref QCPAxis::setNumberFormat), the exponential part will
- be formatted accordingly using multiplication symbol and superscript during rendering of the
- label automatically.
- */
- QString QCPAxisTicker::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision)
- {
- return locale.toString(tick, formatChar.toLatin1(), precision);
- }
- /*! \internal
-
- Returns a vector containing all coordinates of sub ticks that should be drawn. It generates \a
- subTickCount sub ticks between each tick pair given in \a ticks.
-
- If a QCPAxisTicker subclass needs maximal control over the generated sub ticks, it should
- reimplement this method. Depending on the purpose of the subclass it doesn't necessarily need to
- base its result on \a subTickCount or \a ticks.
- */
- QVector<double> QCPAxisTicker::createSubTickVector(int subTickCount, const QVector<double> &ticks)
- {
- QVector<double> result;
- if (subTickCount <= 0 || ticks.size() < 2)
- return result;
-
- result.reserve((ticks.size()-1)*subTickCount);
- for (int i=1; i<ticks.size(); ++i)
- {
- double subTickStep = (ticks.at(i)-ticks.at(i-1))/(double)(subTickCount+1);
- for (int k=1; k<=subTickCount; ++k)
- result.append(ticks.at(i-1) + k*subTickStep);
- }
- return result;
- }
- /*! \internal
-
- Returns a vector containing all coordinates of ticks that should be drawn. The default
- implementation generates ticks with a spacing of \a tickStep (mathematically starting at the tick
- step origin, see \ref setTickOrigin) distributed over the passed \a range.
-
- In order for the axis ticker to generate proper sub ticks, it is necessary that the first and
- last tick coordinates returned by this method are just below/above the provided \a range.
- Otherwise the outer intervals won't contain any sub ticks.
-
- If a QCPAxisTicker subclass needs maximal control over the generated ticks, it should reimplement
- this method. Depending on the purpose of the subclass it doesn't necessarily need to base its
- result on \a tickStep, e.g. when the ticks are spaced unequally like in the case of
- QCPAxisTickerLog.
- */
- QVector<double> QCPAxisTicker::createTickVector(double tickStep, const QCPRange &range)
- {
- QVector<double> result;
- // Generate tick positions according to tickStep:
- qint64 firstStep = floor((range.lower-mTickOrigin)/tickStep); // do not use qFloor here, or we'll lose 64 bit precision
- qint64 lastStep = ceil((range.upper-mTickOrigin)/tickStep); // do not use qCeil here, or we'll lose 64 bit precision
- int tickcount = lastStep-firstStep+1;
- if (tickcount < 0) tickcount = 0;
- result.resize(tickcount);
- for (int i=0; i<tickcount; ++i)
- result[i] = mTickOrigin + (firstStep+i)*tickStep;
- return result;
- }
- /*! \internal
-
- Returns a vector containing all tick label strings corresponding to the tick coordinates provided
- in \a ticks. The default implementation calls \ref getTickLabel to generate the respective
- strings.
-
- It is possible but uncommon for QCPAxisTicker subclasses to reimplement this method, as
- reimplementing \ref getTickLabel often achieves the intended result easier.
- */
- QVector<QString> QCPAxisTicker::createLabelVector(const QVector<double> &ticks, const QLocale &locale, QChar formatChar, int precision)
- {
- QVector<QString> result;
- result.reserve(ticks.size());
- for (int i=0; i<ticks.size(); ++i)
- result.append(getTickLabel(ticks.at(i), locale, formatChar, precision));
- return result;
- }
- /*! \internal
-
- Removes tick coordinates from \a ticks which lie outside the specified \a range. If \a
- keepOneOutlier is true, it preserves one tick just outside the range on both sides, if present.
-
- The passed \a ticks must be sorted in ascending order.
- */
- void QCPAxisTicker::trimTicks(const QCPRange &range, QVector<double> &ticks, bool keepOneOutlier) const
- {
- bool lowFound = false;
- bool highFound = false;
- int lowIndex = 0;
- int highIndex = -1;
-
- for (int i=0; i < ticks.size(); ++i)
- {
- if (ticks.at(i) >= range.lower)
- {
- lowFound = true;
- lowIndex = i;
- break;
- }
- }
- for (int i=ticks.size()-1; i >= 0; --i)
- {
- if (ticks.at(i) <= range.upper)
- {
- highFound = true;
- highIndex = i;
- break;
- }
- }
-
- if (highFound && lowFound)
- {
- int trimFront = qMax(0, lowIndex-(keepOneOutlier ? 1 : 0));
- int trimBack = qMax(0, ticks.size()-(keepOneOutlier ? 2 : 1)-highIndex);
- if (trimFront > 0 || trimBack > 0)
- ticks = ticks.mid(trimFront, ticks.size()-trimFront-trimBack);
- } else // all ticks are either all below or all above the range
- ticks.clear();
- }
- /*! \internal
-
- Returns the coordinate contained in \a candidates which is closest to the provided \a target.
-
- This method assumes \a candidates is not empty and sorted in ascending order.
- */
- double QCPAxisTicker::pickClosest(double target, const QVector<double> &candidates) const
- {
- if (candidates.size() == 1)
- return candidates.first();
- QVector<double>::const_iterator it = std::lower_bound(candidates.constBegin(), candidates.constEnd(), target);
- if (it == candidates.constEnd())
- return *(it-1);
- else if (it == candidates.constBegin())
- return *it;
- else
- return target-*(it-1) < *it-target ? *(it-1) : *it;
- }
- /*! \internal
-
- Returns the decimal mantissa of \a input. Optionally, if \a magnitude is not set to zero, it also
- returns the magnitude of \a input as a power of 10.
-
- For example, an input of 142.6 will return a mantissa of 1.426 and a magnitude of 100.
- */
- double QCPAxisTicker::getMantissa(double input, double *magnitude) const
- {
- const double mag = qPow(10.0, qFloor(qLn(input)/qLn(10.0)));
- if (magnitude) *magnitude = mag;
- return input/mag;
- }
- /*! \internal
-
- Returns a number that is close to \a input but has a clean, easier human readable mantissa. How
- strongly the mantissa is altered, and thus how strong the result deviates from the original \a
- input, depends on the current tick step strategy (see \ref setTickStepStrategy).
- */
- double QCPAxisTicker::cleanMantissa(double input) const
- {
- double magnitude;
- const double mantissa = getMantissa(input, &magnitude);
- switch (mTickStepStrategy)
- {
- case tssReadability:
- {
- return pickClosest(mantissa, QVector<double>() << 1.0 << 2.0 << 2.5 << 5.0 << 10.0)*magnitude;
- }
- case tssMeetTickCount:
- {
- // this gives effectively a mantissa of 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 6.0, 8.0, 10.0
- if (mantissa <= 5.0)
- return (int)(mantissa*2)/2.0*magnitude; // round digit after decimal point to 0.5
- else
- return (int)(mantissa/2.0)*2.0*magnitude; // round to first digit in multiples of 2
- }
- }
- return input;
- }
- /* end of 'src/axis/axisticker.cpp' */
- /* including file 'src/axis/axistickerdatetime.cpp', size 14443 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPAxisTickerDateTime
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPAxisTickerDateTime
- \brief Specialized axis ticker for calendar dates and times as axis ticks
-
- \image html axisticker-datetime.png
-
- This QCPAxisTicker subclass generates ticks that correspond to real calendar dates and times. The
- plot axis coordinate is interpreted as Unix Time, so seconds since Epoch (January 1, 1970, 00:00
- UTC). This is also used for example by QDateTime in the <tt>toTime_t()/setTime_t()</tt> methods
- with a precision of one second. Since Qt 4.7, millisecond accuracy can be obtained from QDateTime
- by using <tt>QDateTime::fromMSecsSinceEpoch()/1000.0</tt>. The static methods \ref dateTimeToKey
- and \ref keyToDateTime conveniently perform this conversion achieving a precision of one
- millisecond on all Qt versions.
-
- The format of the date/time display in the tick labels is controlled with \ref setDateTimeFormat.
- If a different time spec (time zone) shall be used, see \ref setDateTimeSpec.
-
- This ticker produces unequal tick spacing in order to provide intuitive date and time-of-day
- ticks. For example, if the axis range spans a few years such that there is one tick per year,
- ticks will be positioned on 1. January of every year. This is intuitive but, due to leap years,
- will result in slightly unequal tick intervals (visually unnoticeable). The same can be seen in
- the image above: even though the number of days varies month by month, this ticker generates
- ticks on the same day of each month.
-
- If you would like to change the date/time that is used as a (mathematical) starting date for the
- ticks, use the \ref setTickOrigin(const QDateTime &origin) method overload, which takes a
- QDateTime. If you pass 15. July, 9:45 to this method, the yearly ticks will end up on 15. July at
- 9:45 of every year.
-
- The ticker can be created and assigned to an axis like this:
- \snippet documentation/doc-image-generator/mainwindow.cpp axistickerdatetime-creation
-
- \note If you rather wish to display relative times in terms of days, hours, minutes, seconds and
- milliseconds, and are not interested in the intricacies of real calendar dates with months and
- (leap) years, have a look at QCPAxisTickerTime instead.
- */
- /*!
- Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
- managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
- */
- QCPAxisTickerDateTime::QCPAxisTickerDateTime() :
- mDateTimeFormat(QLatin1String("hh:mm:ss\ndd.MM.yy")),
- mDateTimeSpec(Qt::LocalTime),
- mDateStrategy(dsNone)
- {
- setTickCount(4);
- }
- /*!
- Sets the format in which dates and times are displayed as tick labels. For details about the \a
- format string, see the documentation of QDateTime::toString().
-
- Newlines can be inserted with "\n".
-
- \see setDateTimeSpec
- */
- void QCPAxisTickerDateTime::setDateTimeFormat(const QString &format)
- {
- mDateTimeFormat = format;
- }
- /*!
- Sets the time spec that is used for creating the tick labels from corresponding dates/times.
- The default value of QDateTime objects (and also QCPAxisTickerDateTime) is
- <tt>Qt::LocalTime</tt>. However, if the date time values passed to QCustomPlot (e.g. in the form
- of axis ranges or keys of a plottable) are given in the UTC spec, set \a spec to <tt>Qt::UTC</tt>
- to get the correct axis labels.
-
- \see setDateTimeFormat
- */
- void QCPAxisTickerDateTime::setDateTimeSpec(Qt::TimeSpec spec)
- {
- mDateTimeSpec = spec;
- }
- /*!
- Sets the tick origin (see \ref QCPAxisTicker::setTickOrigin) in seconds since Epoch (1. Jan 1970,
- 00:00 UTC). For the date time ticker it might be more intuitive to use the overload which
- directly takes a QDateTime, see \ref setTickOrigin(const QDateTime &origin).
-
- This is useful to define the month/day/time recurring at greater tick interval steps. For
- example, If you pass 15. July, 9:45 to this method and the tick interval happens to be one tick
- per year, the ticks will end up on 15. July at 9:45 of every year.
- */
- void QCPAxisTickerDateTime::setTickOrigin(double origin)
- {
- QCPAxisTicker::setTickOrigin(origin);
- }
- /*!
- Sets the tick origin (see \ref QCPAxisTicker::setTickOrigin) as a QDateTime \a origin.
-
- This is useful to define the month/day/time recurring at greater tick interval steps. For
- example, If you pass 15. July, 9:45 to this method and the tick interval happens to be one tick
- per year, the ticks will end up on 15. July at 9:45 of every year.
- */
- void QCPAxisTickerDateTime::setTickOrigin(const QDateTime &origin)
- {
- setTickOrigin(dateTimeToKey(origin));
- }
- /*! \internal
-
- Returns a sensible tick step with intervals appropriate for a date-time-display, such as weekly,
- monthly, bi-monthly, etc.
-
- Note that this tick step isn't used exactly when generating the tick vector in \ref
- createTickVector, but only as a guiding value requiring some correction for each individual tick
- interval. Otherwise this would lead to unintuitive date displays, e.g. jumping between first day
- in the month to the last day in the previous month from tick to tick, due to the non-uniform
- length of months. The same problem arises with leap years.
-
- \seebaseclassmethod
- */
- double QCPAxisTickerDateTime::getTickStep(const QCPRange &range)
- {
- double result = range.size()/(double)(mTickCount+1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers
-
- mDateStrategy = dsNone;
- if (result < 1) // ideal tick step is below 1 second -> use normal clean mantissa algorithm in units of seconds
- {
- result = cleanMantissa(result);
- } else if (result < 86400*30.4375*12) // below a year
- {
- result = pickClosest(result, QVector<double>()
- << 1 << 2.5 << 5 << 10 << 15 << 30 << 60 << 2.5*60 << 5*60 << 10*60 << 15*60 << 30*60 << 60*60 // second, minute, hour range
- << 3600*2 << 3600*3 << 3600*6 << 3600*12 << 3600*24 // hour to day range
- << 86400*2 << 86400*5 << 86400*7 << 86400*14 << 86400*30.4375 << 86400*30.4375*2 << 86400*30.4375*3 << 86400*30.4375*6 << 86400*30.4375*12); // day, week, month range (avg. days per month includes leap years)
- if (result > 86400*30.4375-1) // month tick intervals or larger
- mDateStrategy = dsUniformDayInMonth;
- else if (result > 3600*24-1) // day tick intervals or larger
- mDateStrategy = dsUniformTimeInDay;
- } else // more than a year, go back to normal clean mantissa algorithm but in units of years
- {
- const double secondsPerYear = 86400*30.4375*12; // average including leap years
- result = cleanMantissa(result/secondsPerYear)*secondsPerYear;
- mDateStrategy = dsUniformDayInMonth;
- }
- return result;
- }
- /*! \internal
-
- Returns a sensible sub tick count with intervals appropriate for a date-time-display, such as weekly,
- monthly, bi-monthly, etc.
-
- \seebaseclassmethod
- */
- int QCPAxisTickerDateTime::getSubTickCount(double tickStep)
- {
- int result = QCPAxisTicker::getSubTickCount(tickStep);
- switch (qRound(tickStep)) // hand chosen subticks for specific minute/hour/day/week/month range (as specified in getTickStep)
- {
- case 5*60: result = 4; break;
- case 10*60: result = 1; break;
- case 15*60: result = 2; break;
- case 30*60: result = 1; break;
- case 60*60: result = 3; break;
- case 3600*2: result = 3; break;
- case 3600*3: result = 2; break;
- case 3600*6: result = 1; break;
- case 3600*12: result = 3; break;
- case 3600*24: result = 3; break;
- case 86400*2: result = 1; break;
- case 86400*5: result = 4; break;
- case 86400*7: result = 6; break;
- case 86400*14: result = 1; break;
- case (int)(86400*30.4375+0.5): result = 3; break;
- case (int)(86400*30.4375*2+0.5): result = 1; break;
- case (int)(86400*30.4375*3+0.5): result = 2; break;
- case (int)(86400*30.4375*6+0.5): result = 5; break;
- case (int)(86400*30.4375*12+0.5): result = 3; break;
- }
- return result;
- }
- /*! \internal
-
- Generates a date/time tick label for tick coordinate \a tick, based on the currently set format
- (\ref setDateTimeFormat) and time spec (\ref setDateTimeSpec).
-
- \seebaseclassmethod
- */
- QString QCPAxisTickerDateTime::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision)
- {
- Q_UNUSED(precision)
- Q_UNUSED(formatChar)
- return locale.toString(keyToDateTime(tick).toTimeSpec(mDateTimeSpec), mDateTimeFormat);
- }
- /*! \internal
-
- Uses the passed \a tickStep as a guiding value and applies corrections in order to obtain
- non-uniform tick intervals but intuitive tick labels, e.g. falling on the same day of each month.
-
- \seebaseclassmethod
- */
- QVector<double> QCPAxisTickerDateTime::createTickVector(double tickStep, const QCPRange &range)
- {
- QVector<double> result = QCPAxisTicker::createTickVector(tickStep, range);
- if (!result.isEmpty())
- {
- if (mDateStrategy == dsUniformTimeInDay)
- {
- QDateTime uniformDateTime = keyToDateTime(mTickOrigin); // the time of this datetime will be set for all other ticks, if possible
- QDateTime tickDateTime;
- for (int i=0; i<result.size(); ++i)
- {
- tickDateTime = keyToDateTime(result.at(i));
- tickDateTime.setTime(uniformDateTime.time());
- result[i] = dateTimeToKey(tickDateTime);
- }
- } else if (mDateStrategy == dsUniformDayInMonth)
- {
- QDateTime uniformDateTime = keyToDateTime(mTickOrigin); // this day (in month) and time will be set for all other ticks, if possible
- QDateTime tickDateTime;
- for (int i=0; i<result.size(); ++i)
- {
- tickDateTime = keyToDateTime(result.at(i));
- tickDateTime.setTime(uniformDateTime.time());
- int thisUniformDay = uniformDateTime.date().day() <= tickDateTime.date().daysInMonth() ? uniformDateTime.date().day() : tickDateTime.date().daysInMonth(); // don't exceed month (e.g. try to set day 31 in February)
- if (thisUniformDay-tickDateTime.date().day() < -15) // with leap years involved, date month may jump backwards or forwards, and needs to be corrected before setting day
- tickDateTime = tickDateTime.addMonths(1);
- else if (thisUniformDay-tickDateTime.date().day() > 15) // with leap years involved, date month may jump backwards or forwards, and needs to be corrected before setting day
- tickDateTime = tickDateTime.addMonths(-1);
- tickDateTime.setDate(QDate(tickDateTime.date().year(), tickDateTime.date().month(), thisUniformDay));
- result[i] = dateTimeToKey(tickDateTime);
- }
- }
- }
- return result;
- }
- /*!
- A convenience method which turns \a key (in seconds since Epoch 1. Jan 1970, 00:00 UTC) into a
- QDateTime object. This can be used to turn axis coordinates to actual QDateTimes.
-
- The accuracy achieved by this method is one millisecond, irrespective of the used Qt version (it
- works around the lack of a QDateTime::fromMSecsSinceEpoch in Qt 4.6)
-
- \see dateTimeToKey
- */
- QDateTime QCPAxisTickerDateTime::keyToDateTime(double key)
- {
- # if QT_VERSION < QT_VERSION_CHECK(4, 7, 0)
- return QDateTime::fromTime_t(key).addMSecs((key-(qint64)key)*1000);
- # else
- return QDateTime::fromMSecsSinceEpoch(key*1000.0);
- # endif
- }
- /*! \overload
-
- A convenience method which turns a QDateTime object into a double value that corresponds to
- seconds since Epoch (1. Jan 1970, 00:00 UTC). This is the format used as axis coordinates by
- QCPAxisTickerDateTime.
-
- The accuracy achieved by this method is one millisecond, irrespective of the used Qt version (it
- works around the lack of a QDateTime::toMSecsSinceEpoch in Qt 4.6)
-
- \see keyToDateTime
- */
- double QCPAxisTickerDateTime::dateTimeToKey(const QDateTime dateTime)
- {
- # if QT_VERSION < QT_VERSION_CHECK(4, 7, 0)
- return dateTime.toTime_t()+dateTime.time().msec()/1000.0;
- # else
- return dateTime.toMSecsSinceEpoch()/1000.0;
- # endif
- }
- /*! \overload
-
- A convenience method which turns a QDate object into a double value that corresponds to
- seconds since Epoch (1. Jan 1970, 00:00 UTC). This is the format used as axis coordinates by
- QCPAxisTickerDateTime.
-
- \see keyToDateTime
- */
- double QCPAxisTickerDateTime::dateTimeToKey(const QDate date)
- {
- # if QT_VERSION < QT_VERSION_CHECK(4, 7, 0)
- return QDateTime(date).toTime_t();
- # else
- return QDateTime(date).toMSecsSinceEpoch()/1000.0;
- # endif
- }
- /* end of 'src/axis/axistickerdatetime.cpp' */
- /* including file 'src/axis/axistickertime.cpp', size 11747 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPAxisTickerTime
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPAxisTickerTime
- \brief Specialized axis ticker for time spans in units of milliseconds to days
-
- \image html axisticker-time.png
-
- This QCPAxisTicker subclass generates ticks that corresponds to time intervals.
-
- The format of the time display in the tick labels is controlled with \ref setTimeFormat and \ref
- setFieldWidth. The time coordinate is in the unit of seconds with respect to the time coordinate
- zero. Unlike with QCPAxisTickerDateTime, the ticks don't correspond to a specific calendar date
- and time.
-
- The time can be displayed in milliseconds, seconds, minutes, hours and days. Depending on the
- largest available unit in the format specified with \ref setTimeFormat, any time spans above will
- be carried in that largest unit. So for example if the format string is "%m:%s" and a tick at
- coordinate value 7815 (being 2 hours, 10 minutes and 15 seconds) is created, the resulting tick
- label will show "130:15" (130 minutes, 15 seconds). If the format string is "%h:%m:%s", the hour
- unit will be used and the label will thus be "02:10:15". Negative times with respect to the axis
- zero will carry a leading minus sign.
-
- The ticker can be created and assigned to an axis like this:
- \snippet documentation/doc-image-generator/mainwindow.cpp axistickertime-creation
-
- Here is an example of a time axis providing time information in days, hours and minutes. Due to
- the axis range spanning a few days and the wanted tick count (\ref setTickCount), the ticker
- decided to use tick steps of 12 hours:
-
- \image html axisticker-time2.png
-
- The format string for this example is
- \snippet documentation/doc-image-generator/mainwindow.cpp axistickertime-creation-2
-
- \note If you rather wish to display calendar dates and times, have a look at QCPAxisTickerDateTime
- instead.
- */
- /*!
- Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
- managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
- */
- QCPAxisTickerTime::QCPAxisTickerTime() :
- mTimeFormat(QLatin1String("%h:%m:%s")),
- mSmallestUnit(tuSeconds),
- mBiggestUnit(tuHours)
- {
- setTickCount(4);
- mFieldWidth[tuMilliseconds] = 3;
- mFieldWidth[tuSeconds] = 2;
- mFieldWidth[tuMinutes] = 2;
- mFieldWidth[tuHours] = 2;
- mFieldWidth[tuDays] = 1;
-
- mFormatPattern[tuMilliseconds] = QLatin1String("%z");
- mFormatPattern[tuSeconds] = QLatin1String("%s");
- mFormatPattern[tuMinutes] = QLatin1String("%m");
- mFormatPattern[tuHours] = QLatin1String("%h");
- mFormatPattern[tuDays] = QLatin1String("%d");
- }
- /*!
- Sets the format that will be used to display time in the tick labels.
-
- The available patterns are:
- - %%z for milliseconds
- - %%s for seconds
- - %%m for minutes
- - %%h for hours
- - %%d for days
-
- The field width (zero padding) can be controlled for each unit with \ref setFieldWidth.
-
- The largest unit that appears in \a format will carry all the remaining time of a certain tick
- coordinate, even if it overflows the natural limit of the unit. For example, if %%m is the
- largest unit it might become larger than 59 in order to consume larger time values. If on the
- other hand %%h is available, the minutes will wrap around to zero after 59 and the time will
- carry to the hour digit.
- */
- void QCPAxisTickerTime::setTimeFormat(const QString &format)
- {
- mTimeFormat = format;
-
- // determine smallest and biggest unit in format, to optimize unit replacement and allow biggest
- // unit to consume remaining time of a tick value and grow beyond its modulo (e.g. min > 59)
- mSmallestUnit = tuMilliseconds;
- mBiggestUnit = tuMilliseconds;
- bool hasSmallest = false;
- for (int i = tuMilliseconds; i <= tuDays; ++i)
- {
- TimeUnit unit = static_cast<TimeUnit>(i);
- if (mTimeFormat.contains(mFormatPattern.value(unit)))
- {
- if (!hasSmallest)
- {
- mSmallestUnit = unit;
- hasSmallest = true;
- }
- mBiggestUnit = unit;
- }
- }
- }
- /*!
- Sets the field widh of the specified \a unit to be \a width digits, when displayed in the tick
- label. If the number for the specific unit is shorter than \a width, it will be padded with an
- according number of zeros to the left in order to reach the field width.
-
- \see setTimeFormat
- */
- void QCPAxisTickerTime::setFieldWidth(QCPAxisTickerTime::TimeUnit unit, int width)
- {
- mFieldWidth[unit] = qMax(width, 1);
- }
- /*! \internal
- Returns the tick step appropriate for time displays, depending on the provided \a range and the
- smallest available time unit in the current format (\ref setTimeFormat). For example if the unit
- of seconds isn't available in the format, this method will not generate steps (like 2.5 minutes)
- that require sub-minute precision to be displayed correctly.
-
- \seebaseclassmethod
- */
- double QCPAxisTickerTime::getTickStep(const QCPRange &range)
- {
- double result = range.size()/(double)(mTickCount+1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers
-
- if (result < 1) // ideal tick step is below 1 second -> use normal clean mantissa algorithm in units of seconds
- {
- if (mSmallestUnit == tuMilliseconds)
- result = qMax(cleanMantissa(result), 0.001); // smallest tick step is 1 millisecond
- else // have no milliseconds available in format, so stick with 1 second tickstep
- result = 1.0;
- } else if (result < 3600*24) // below a day
- {
- // the filling of availableSteps seems a bit contorted but it fills in a sorted fashion and thus saves a post-fill sorting run
- QVector<double> availableSteps;
- // seconds range:
- if (mSmallestUnit <= tuSeconds)
- availableSteps << 1;
- if (mSmallestUnit == tuMilliseconds)
- availableSteps << 2.5; // only allow half second steps if milliseconds are there to display it
- else if (mSmallestUnit == tuSeconds)
- availableSteps << 2;
- if (mSmallestUnit <= tuSeconds)
- availableSteps << 5 << 10 << 15 << 30;
- // minutes range:
- if (mSmallestUnit <= tuMinutes)
- availableSteps << 1*60;
- if (mSmallestUnit <= tuSeconds)
- availableSteps << 2.5*60; // only allow half minute steps if seconds are there to display it
- else if (mSmallestUnit == tuMinutes)
- availableSteps << 2*60;
- if (mSmallestUnit <= tuMinutes)
- availableSteps << 5*60 << 10*60 << 15*60 << 30*60;
- // hours range:
- if (mSmallestUnit <= tuHours)
- availableSteps << 1*3600 << 2*3600 << 3*3600 << 6*3600 << 12*3600 << 24*3600;
- // pick available step that is most appropriate to approximate ideal step:
- result = pickClosest(result, availableSteps);
- } else // more than a day, go back to normal clean mantissa algorithm but in units of days
- {
- const double secondsPerDay = 3600*24;
- result = cleanMantissa(result/secondsPerDay)*secondsPerDay;
- }
- return result;
- }
- /*! \internal
- Returns the sub tick count appropriate for the provided \a tickStep and time displays.
-
- \seebaseclassmethod
- */
- int QCPAxisTickerTime::getSubTickCount(double tickStep)
- {
- int result = QCPAxisTicker::getSubTickCount(tickStep);
- switch (qRound(tickStep)) // hand chosen subticks for specific minute/hour/day range (as specified in getTickStep)
- {
- case 5*60: result = 4; break;
- case 10*60: result = 1; break;
- case 15*60: result = 2; break;
- case 30*60: result = 1; break;
- case 60*60: result = 3; break;
- case 3600*2: result = 3; break;
- case 3600*3: result = 2; break;
- case 3600*6: result = 1; break;
- case 3600*12: result = 3; break;
- case 3600*24: result = 3; break;
- }
- return result;
- }
- /*! \internal
-
- Returns the tick label corresponding to the provided \a tick and the configured format and field
- widths (\ref setTimeFormat, \ref setFieldWidth).
-
- \seebaseclassmethod
- */
- QString QCPAxisTickerTime::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision)
- {
- Q_UNUSED(precision)
- Q_UNUSED(formatChar)
- Q_UNUSED(locale)
- bool negative = tick < 0;
- if (negative) tick *= -1;
- double values[tuDays+1]; // contains the msec/sec/min/... value with its respective modulo (e.g. minute 0..59)
- double restValues[tuDays+1]; // contains the msec/sec/min/... value as if it's the largest available unit and thus consumes the remaining time
-
- restValues[tuMilliseconds] = tick*1000;
- values[tuMilliseconds] = modf(restValues[tuMilliseconds]/1000, &restValues[tuSeconds])*1000;
- values[tuSeconds] = modf(restValues[tuSeconds]/60, &restValues[tuMinutes])*60;
- values[tuMinutes] = modf(restValues[tuMinutes]/60, &restValues[tuHours])*60;
- values[tuHours] = modf(restValues[tuHours]/24, &restValues[tuDays])*24;
- // no need to set values[tuDays] because days are always a rest value (there is no higher unit so it consumes all remaining time)
-
- QString result = mTimeFormat;
- for (int i = mSmallestUnit; i <= mBiggestUnit; ++i)
- {
- TimeUnit iUnit = static_cast<TimeUnit>(i);
- replaceUnit(result, iUnit, qRound(iUnit == mBiggestUnit ? restValues[iUnit] : values[iUnit]));
- }
- if (negative)
- result.prepend(QLatin1Char('-'));
- return result;
- }
- /*! \internal
-
- Replaces all occurrences of the format pattern belonging to \a unit in \a text with the specified
- \a value, using the field width as specified with \ref setFieldWidth for the \a unit.
- */
- void QCPAxisTickerTime::replaceUnit(QString &text, QCPAxisTickerTime::TimeUnit unit, int value) const
- {
- QString valueStr = QString::number(value);
- while (valueStr.size() < mFieldWidth.value(unit))
- valueStr.prepend(QLatin1Char('0'));
-
- text.replace(mFormatPattern.value(unit), valueStr);
- }
- /* end of 'src/axis/axistickertime.cpp' */
- /* including file 'src/axis/axistickerfixed.cpp', size 5583 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPAxisTickerFixed
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPAxisTickerFixed
- \brief Specialized axis ticker with a fixed tick step
-
- \image html axisticker-fixed.png
-
- This QCPAxisTicker subclass generates ticks with a fixed tick step set with \ref setTickStep. It
- is also possible to allow integer multiples and integer powers of the specified tick step with
- \ref setScaleStrategy.
-
- A typical application of this ticker is to make an axis only display integers, by setting the
- tick step of the ticker to 1.0 and the scale strategy to \ref ssMultiples.
-
- Another case is when a certain number has a special meaning and axis ticks should only appear at
- multiples of that value. In this case you might also want to consider \ref QCPAxisTickerPi
- because despite the name it is not limited to only pi symbols/values.
-
- The ticker can be created and assigned to an axis like this:
- \snippet documentation/doc-image-generator/mainwindow.cpp axistickerfixed-creation
- */
- /*!
- Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
- managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
- */
- QCPAxisTickerFixed::QCPAxisTickerFixed() :
- mTickStep(1.0),
- mScaleStrategy(ssNone)
- {
- }
- /*!
- Sets the fixed tick interval to \a step.
-
- The axis ticker will only use this tick step when generating axis ticks. This might cause a very
- high tick density and overlapping labels if the axis range is zoomed out. Using \ref
- setScaleStrategy it is possible to relax the fixed step and also allow multiples or powers of \a
- step. This will enable the ticker to reduce the number of ticks to a reasonable amount (see \ref
- setTickCount).
- */
- void QCPAxisTickerFixed::setTickStep(double step)
- {
- if (step > 0)
- mTickStep = step;
- else
- qDebug() << Q_FUNC_INFO << "tick step must be greater than zero:" << step;
- }
- /*!
- Sets whether the specified tick step (\ref setTickStep) is absolutely fixed or whether
- modifications may be applied to it before calculating the finally used tick step, such as
- permitting multiples or powers. See \ref ScaleStrategy for details.
-
- The default strategy is \ref ssNone, which means the tick step is absolutely fixed.
- */
- void QCPAxisTickerFixed::setScaleStrategy(QCPAxisTickerFixed::ScaleStrategy strategy)
- {
- mScaleStrategy = strategy;
- }
- /*! \internal
-
- Determines the actually used tick step from the specified tick step and scale strategy (\ref
- setTickStep, \ref setScaleStrategy).
-
- This method either returns the specified tick step exactly, or, if the scale strategy is not \ref
- ssNone, a modification of it to allow varying the number of ticks in the current axis range.
-
- \seebaseclassmethod
- */
- double QCPAxisTickerFixed::getTickStep(const QCPRange &range)
- {
- switch (mScaleStrategy)
- {
- case ssNone:
- {
- return mTickStep;
- }
- case ssMultiples:
- {
- double exactStep = range.size()/(double)(mTickCount+1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers
- if (exactStep < mTickStep)
- return mTickStep;
- else
- return (qint64)(cleanMantissa(exactStep/mTickStep)+0.5)*mTickStep;
- }
- case ssPowers:
- {
- double exactStep = range.size()/(double)(mTickCount+1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers
- return qPow(mTickStep, (int)(qLn(exactStep)/qLn(mTickStep)+0.5));
- }
- }
- return mTickStep;
- }
- /* end of 'src/axis/axistickerfixed.cpp' */
- /* including file 'src/axis/axistickertext.cpp', size 8653 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPAxisTickerText
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPAxisTickerText
- \brief Specialized axis ticker which allows arbitrary labels at specified coordinates
-
- \image html axisticker-text.png
-
- This QCPAxisTicker subclass generates ticks which can be directly specified by the user as
- coordinates and associated strings. They can be passed as a whole with \ref setTicks or one at a
- time with \ref addTick. Alternatively you can directly access the internal storage via \ref ticks
- and modify the tick/label data there.
-
- This is useful for cases where the axis represents categories rather than numerical values.
-
- If you are updating the ticks of this ticker regularly and in a dynamic fasion (e.g. dependent on
- the axis range), it is a sign that you should probably create an own ticker by subclassing
- QCPAxisTicker, instead of using this one.
-
- The ticker can be created and assigned to an axis like this:
- \snippet documentation/doc-image-generator/mainwindow.cpp axistickertext-creation
- */
- /* start of documentation of inline functions */
- /*! \fn QMap<double, QString> &QCPAxisTickerText::ticks()
-
- Returns a non-const reference to the internal map which stores the tick coordinates and their
- labels.
- You can access the map directly in order to add, remove or manipulate ticks, as an alternative to
- using the methods provided by QCPAxisTickerText, such as \ref setTicks and \ref addTick.
- */
- /* end of documentation of inline functions */
- /*!
- Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
- managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
- */
- QCPAxisTickerText::QCPAxisTickerText() :
- mSubTickCount(0)
- {
- }
- /*! \overload
-
- Sets the ticks that shall appear on the axis. The map key of \a ticks corresponds to the axis
- coordinate, and the map value is the string that will appear as tick label.
-
- An alternative to manipulate ticks is to directly access the internal storage with the \ref ticks
- getter.
-
- \see addTicks, addTick, clear
- */
- void QCPAxisTickerText::setTicks(const QMap<double, QString> &ticks)
- {
- mTicks = ticks;
- }
- /*! \overload
-
- Sets the ticks that shall appear on the axis. The entries of \a positions correspond to the axis
- coordinates, and the entries of \a labels are the respective strings that will appear as tick
- labels.
-
- \see addTicks, addTick, clear
- */
- void QCPAxisTickerText::setTicks(const QVector<double> &positions, const QVector<QString> labels)
- {
- clear();
- addTicks(positions, labels);
- }
- /*!
- Sets the number of sub ticks that shall appear between ticks. For QCPAxisTickerText, there is no
- automatic sub tick count calculation. So if sub ticks are needed, they must be configured with this
- method.
- */
- void QCPAxisTickerText::setSubTickCount(int subTicks)
- {
- if (subTicks >= 0)
- mSubTickCount = subTicks;
- else
- qDebug() << Q_FUNC_INFO << "sub tick count can't be negative:" << subTicks;
- }
- /*!
- Clears all ticks.
-
- An alternative to manipulate ticks is to directly access the internal storage with the \ref ticks
- getter.
-
- \see setTicks, addTicks, addTick
- */
- void QCPAxisTickerText::clear()
- {
- mTicks.clear();
- }
- /*!
- Adds a single tick to the axis at the given axis coordinate \a position, with the provided tick \a
- label.
-
- \see addTicks, setTicks, clear
- */
- void QCPAxisTickerText::addTick(double position, QString label)
- {
- mTicks.insert(position, label);
- }
- /*! \overload
-
- Adds the provided \a ticks to the ones already existing. The map key of \a ticks corresponds to
- the axis coordinate, and the map value is the string that will appear as tick label.
-
- An alternative to manipulate ticks is to directly access the internal storage with the \ref ticks
- getter.
-
- \see addTick, setTicks, clear
- */
- void QCPAxisTickerText::addTicks(const QMap<double, QString> &ticks)
- {
- mTicks.unite(ticks);
- }
- /*! \overload
-
- Adds the provided ticks to the ones already existing. The entries of \a positions correspond to
- the axis coordinates, and the entries of \a labels are the respective strings that will appear as
- tick labels.
-
- An alternative to manipulate ticks is to directly access the internal storage with the \ref ticks
- getter.
-
- \see addTick, setTicks, clear
- */
- void QCPAxisTickerText::addTicks(const QVector<double> &positions, const QVector<QString> &labels)
- {
- if (positions.size() != labels.size())
- qDebug() << Q_FUNC_INFO << "passed unequal length vectors for positions and labels:" << positions.size() << labels.size();
- int n = qMin(positions.size(), labels.size());
- for (int i=0; i<n; ++i)
- mTicks.insert(positions.at(i), labels.at(i));
- }
- /*!
- Since the tick coordinates are provided externally, this method implementation does nothing.
-
- \seebaseclassmethod
- */
- double QCPAxisTickerText::getTickStep(const QCPRange &range)
- {
- // text axis ticker has manual tick positions, so doesn't need this method
- Q_UNUSED(range)
- return 1.0;
- }
- /*!
- Returns the sub tick count that was configured with \ref setSubTickCount.
-
- \seebaseclassmethod
- */
- int QCPAxisTickerText::getSubTickCount(double tickStep)
- {
- Q_UNUSED(tickStep)
- return mSubTickCount;
- }
- /*!
- Returns the tick label which corresponds to the key \a tick in the internal tick storage. Since
- the labels are provided externally, \a locale, \a formatChar, and \a precision are ignored.
-
- \seebaseclassmethod
- */
- QString QCPAxisTickerText::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision)
- {
- Q_UNUSED(locale)
- Q_UNUSED(formatChar)
- Q_UNUSED(precision)
- return mTicks.value(tick);
- }
- /*!
- Returns the externally provided tick coordinates which are in the specified \a range. If
- available, one tick above and below the range is provided in addition, to allow possible sub tick
- calculation. The parameter \a tickStep is ignored.
-
- \seebaseclassmethod
- */
- QVector<double> QCPAxisTickerText::createTickVector(double tickStep, const QCPRange &range)
- {
- Q_UNUSED(tickStep)
- QVector<double> result;
- if (mTicks.isEmpty())
- return result;
-
- QMap<double, QString>::const_iterator start = mTicks.lowerBound(range.lower);
- QMap<double, QString>::const_iterator end = mTicks.upperBound(range.upper);
- // this method should try to give one tick outside of range so proper subticks can be generated:
- if (start != mTicks.constBegin()) --start;
- if (end != mTicks.constEnd()) ++end;
- for (QMap<double, QString>::const_iterator it = start; it != end; ++it)
- result.append(it.key());
-
- return result;
- }
- /* end of 'src/axis/axistickertext.cpp' */
- /* including file 'src/axis/axistickerpi.cpp', size 11170 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPAxisTickerPi
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPAxisTickerPi
- \brief Specialized axis ticker to display ticks in units of an arbitrary constant, for example pi
-
- \image html axisticker-pi.png
-
- This QCPAxisTicker subclass generates ticks that are expressed with respect to a given symbolic
- constant with a numerical value specified with \ref setPiValue and an appearance in the tick
- labels specified with \ref setPiSymbol.
-
- Ticks may be generated at fractions of the symbolic constant. How these fractions appear in the
- tick label can be configured with \ref setFractionStyle.
-
- The ticker can be created and assigned to an axis like this:
- \snippet documentation/doc-image-generator/mainwindow.cpp axistickerpi-creation
- */
- /*!
- Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
- managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
- */
- QCPAxisTickerPi::QCPAxisTickerPi() :
- mPiSymbol(QLatin1String(" ")+QChar(0x03C0)),
- mPiValue(M_PI),
- mPeriodicity(0),
- mFractionStyle(fsUnicodeFractions),
- mPiTickStep(0)
- {
- setTickCount(4);
- }
- /*!
- Sets how the symbol part (which is always a suffix to the number) shall appear in the axis tick
- label.
-
- If a space shall appear between the number and the symbol, make sure the space is contained in \a
- symbol.
- */
- void QCPAxisTickerPi::setPiSymbol(QString symbol)
- {
- mPiSymbol = symbol;
- }
- /*!
- Sets the numerical value that the symbolic constant has.
- This will be used to place the appropriate fractions of the symbol at the respective axis
- coordinates.
- */
- void QCPAxisTickerPi::setPiValue(double pi)
- {
- mPiValue = pi;
- }
- /*!
- Sets whether the axis labels shall appear periodicly and if so, at which multiplicity of the
- symbolic constant.
-
- To disable periodicity, set \a multiplesOfPi to zero.
-
- For example, an axis that identifies 0 with 2pi would set \a multiplesOfPi to two.
- */
- void QCPAxisTickerPi::setPeriodicity(int multiplesOfPi)
- {
- mPeriodicity = qAbs(multiplesOfPi);
- }
- /*!
- Sets how the numerical/fractional part preceding the symbolic constant is displayed in tick
- labels. See \ref FractionStyle for the various options.
- */
- void QCPAxisTickerPi::setFractionStyle(QCPAxisTickerPi::FractionStyle style)
- {
- mFractionStyle = style;
- }
- /*! \internal
-
- Returns the tick step, using the constant's value (\ref setPiValue) as base unit. In consequence
- the numerical/fractional part preceding the symbolic constant is made to have a readable
- mantissa.
-
- \seebaseclassmethod
- */
- double QCPAxisTickerPi::getTickStep(const QCPRange &range)
- {
- mPiTickStep = range.size()/mPiValue/(double)(mTickCount+1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers
- mPiTickStep = cleanMantissa(mPiTickStep);
- return mPiTickStep*mPiValue;
- }
- /*! \internal
-
- Returns the sub tick count, using the constant's value (\ref setPiValue) as base unit. In
- consequence the sub ticks divide the numerical/fractional part preceding the symbolic constant
- reasonably, and not the total tick coordinate.
-
- \seebaseclassmethod
- */
- int QCPAxisTickerPi::getSubTickCount(double tickStep)
- {
- return QCPAxisTicker::getSubTickCount(tickStep/mPiValue);
- }
- /*! \internal
-
- Returns the tick label as a fractional/numerical part and a symbolic string as suffix. The
- formatting of the fraction is done according to the specified \ref setFractionStyle. The appended
- symbol is specified with \ref setPiSymbol.
-
- \seebaseclassmethod
- */
- QString QCPAxisTickerPi::getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision)
- {
- double tickInPis = tick/mPiValue;
- if (mPeriodicity > 0)
- tickInPis = fmod(tickInPis, mPeriodicity);
-
- if (mFractionStyle != fsFloatingPoint && mPiTickStep > 0.09 && mPiTickStep < 50)
- {
- // simply construct fraction from decimal like 1.234 -> 1234/1000 and then simplify fraction, smaller digits are irrelevant due to mPiTickStep conditional above
- int denominator = 1000;
- int numerator = qRound(tickInPis*denominator);
- simplifyFraction(numerator, denominator);
- if (qAbs(numerator) == 1 && denominator == 1)
- return (numerator < 0 ? QLatin1String("-") : QLatin1String("")) + mPiSymbol.trimmed();
- else if (numerator == 0)
- return QLatin1String("0");
- else
- return fractionToString(numerator, denominator) + mPiSymbol;
- } else
- {
- if (qFuzzyIsNull(tickInPis))
- return QLatin1String("0");
- else if (qFuzzyCompare(qAbs(tickInPis), 1.0))
- return (tickInPis < 0 ? QLatin1String("-") : QLatin1String("")) + mPiSymbol.trimmed();
- else
- return QCPAxisTicker::getTickLabel(tickInPis, locale, formatChar, precision) + mPiSymbol;
- }
- }
- /*! \internal
-
- Takes the fraction given by \a numerator and \a denominator and modifies the values to make sure
- the fraction is in irreducible form, i.e. numerator and denominator don't share any common
- factors which could be cancelled.
- */
- void QCPAxisTickerPi::simplifyFraction(int &numerator, int &denominator) const
- {
- if (numerator == 0 || denominator == 0)
- return;
-
- int num = numerator;
- int denom = denominator;
- while (denom != 0) // euclidean gcd algorithm
- {
- int oldDenom = denom;
- denom = num % denom;
- num = oldDenom;
- }
- // num is now gcd of numerator and denominator
- numerator /= num;
- denominator /= num;
- }
- /*! \internal
-
- Takes the fraction given by \a numerator and \a denominator and returns a string representation.
- The result depends on the configured fraction style (\ref setFractionStyle).
-
- This method is used to format the numerical/fractional part when generating tick labels. It
- simplifies the passed fraction to an irreducible form using \ref simplifyFraction and factors out
- any integer parts of the fraction (e.g. "10/4" becomes "2 1/2").
- */
- QString QCPAxisTickerPi::fractionToString(int numerator, int denominator) const
- {
- if (denominator == 0)
- {
- qDebug() << Q_FUNC_INFO << "called with zero denominator";
- return QString();
- }
- if (mFractionStyle == fsFloatingPoint) // should never be the case when calling this function
- {
- qDebug() << Q_FUNC_INFO << "shouldn't be called with fraction style fsDecimal";
- return QString::number(numerator/(double)denominator); // failsafe
- }
- int sign = numerator*denominator < 0 ? -1 : 1;
- numerator = qAbs(numerator);
- denominator = qAbs(denominator);
-
- if (denominator == 1)
- {
- return QString::number(sign*numerator);
- } else
- {
- int integerPart = numerator/denominator;
- int remainder = numerator%denominator;
- if (remainder == 0)
- {
- return QString::number(sign*integerPart);
- } else
- {
- if (mFractionStyle == fsAsciiFractions)
- {
- return QString(QLatin1String("%1%2%3/%4"))
- .arg(sign == -1 ? QLatin1String("-") : QLatin1String(""))
- .arg(integerPart > 0 ? QString::number(integerPart)+QLatin1String(" ") : QLatin1String(""))
- .arg(remainder)
- .arg(denominator);
- } else if (mFractionStyle == fsUnicodeFractions)
- {
- return QString(QLatin1String("%1%2%3"))
- .arg(sign == -1 ? QLatin1String("-") : QLatin1String(""))
- .arg(integerPart > 0 ? QString::number(integerPart) : QLatin1String(""))
- .arg(unicodeFraction(remainder, denominator));
- }
- }
- }
- return QString();
- }
- /*! \internal
-
- Returns the unicode string representation of the fraction given by \a numerator and \a
- denominator. This is the representation used in \ref fractionToString when the fraction style
- (\ref setFractionStyle) is \ref fsUnicodeFractions.
-
- This method doesn't use the single-character common fractions but builds each fraction from a
- superscript unicode number, the unicode fraction character, and a subscript unicode number.
- */
- QString QCPAxisTickerPi::unicodeFraction(int numerator, int denominator) const
- {
- return unicodeSuperscript(numerator)+QChar(0x2044)+unicodeSubscript(denominator);
- }
- /*! \internal
-
- Returns the unicode string representing \a number as superscript. This is used to build
- unicode fractions in \ref unicodeFraction.
- */
- QString QCPAxisTickerPi::unicodeSuperscript(int number) const
- {
- if (number == 0)
- return QString(QChar(0x2070));
-
- QString result;
- while (number > 0)
- {
- const int digit = number%10;
- switch (digit)
- {
- case 1: { result.prepend(QChar(0x00B9)); break; }
- case 2: { result.prepend(QChar(0x00B2)); break; }
- case 3: { result.prepend(QChar(0x00B3)); break; }
- default: { result.prepend(QChar(0x2070+digit)); break; }
- }
- number /= 10;
- }
- return result;
- }
- /*! \internal
-
- Returns the unicode string representing \a number as subscript. This is used to build unicode
- fractions in \ref unicodeFraction.
- */
- QString QCPAxisTickerPi::unicodeSubscript(int number) const
- {
- if (number == 0)
- return QString(QChar(0x2080));
-
- QString result;
- while (number > 0)
- {
- result.prepend(QChar(0x2080+number%10));
- number /= 10;
- }
- return result;
- }
- /* end of 'src/axis/axistickerpi.cpp' */
- /* including file 'src/axis/axistickerlog.cpp', size 7106 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPAxisTickerLog
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPAxisTickerLog
- \brief Specialized axis ticker suited for logarithmic axes
-
- \image html axisticker-log.png
-
- This QCPAxisTicker subclass generates ticks with unequal tick intervals suited for logarithmic
- axis scales. The ticks are placed at powers of the specified log base (\ref setLogBase).
-
- Especially in the case of a log base equal to 10 (the default), it might be desirable to have
- tick labels in the form of powers of ten without mantissa display. To achieve this, set the
- number precision (\ref QCPAxis::setNumberPrecision) to zero and the number format (\ref
- QCPAxis::setNumberFormat) to scientific (exponential) display with beautifully typeset decimal
- powers, so a format string of <tt>"eb"</tt>. This will result in the following axis tick labels:
-
- \image html axisticker-log-powers.png
- The ticker can be created and assigned to an axis like this:
- \snippet documentation/doc-image-generator/mainwindow.cpp axistickerlog-creation
- */
- /*!
- Constructs the ticker and sets reasonable default values. Axis tickers are commonly created
- managed by a QSharedPointer, which then can be passed to QCPAxis::setTicker.
- */
- QCPAxisTickerLog::QCPAxisTickerLog() :
- mLogBase(10.0),
- mSubTickCount(8), // generates 10 intervals
- mLogBaseLnInv(1.0/qLn(mLogBase))
- {
- }
- /*!
- Sets the logarithm base used for tick coordinate generation. The ticks will be placed at integer
- powers of \a base.
- */
- void QCPAxisTickerLog::setLogBase(double base)
- {
- if (base > 0)
- {
- mLogBase = base;
- mLogBaseLnInv = 1.0/qLn(mLogBase);
- } else
- qDebug() << Q_FUNC_INFO << "log base has to be greater than zero:" << base;
- }
- /*!
- Sets the number of sub ticks in a tick interval. Within each interval, the sub ticks are spaced
- linearly to provide a better visual guide, so the sub tick density increases toward the higher
- tick.
-
- Note that \a subTicks is the number of sub ticks (not sub intervals) in one tick interval. So in
- the case of logarithm base 10 an intuitive sub tick spacing would be achieved with eight sub
- ticks (the default). This means e.g. between the ticks 10 and 100 there will be eight ticks,
- namely at 20, 30, 40, 50, 60, 70, 80 and 90.
- */
- void QCPAxisTickerLog::setSubTickCount(int subTicks)
- {
- if (subTicks >= 0)
- mSubTickCount = subTicks;
- else
- qDebug() << Q_FUNC_INFO << "sub tick count can't be negative:" << subTicks;
- }
- /*! \internal
-
- Since logarithmic tick steps are necessarily different for each tick interval, this method does
- nothing in the case of QCPAxisTickerLog
-
- \seebaseclassmethod
- */
- double QCPAxisTickerLog::getTickStep(const QCPRange &range)
- {
- // Logarithmic axis ticker has unequal tick spacing, so doesn't need this method
- Q_UNUSED(range)
- return 1.0;
- }
- /*! \internal
-
- Returns the sub tick count specified in \ref setSubTickCount. For QCPAxisTickerLog, there is no
- automatic sub tick count calculation necessary.
-
- \seebaseclassmethod
- */
- int QCPAxisTickerLog::getSubTickCount(double tickStep)
- {
- Q_UNUSED(tickStep)
- return mSubTickCount;
- }
- /*! \internal
-
- Creates ticks with a spacing given by the logarithm base and an increasing integer power in the
- provided \a range. The step in which the power increases tick by tick is chosen in order to keep
- the total number of ticks as close as possible to the tick count (\ref setTickCount). The
- parameter \a tickStep is ignored for QCPAxisTickerLog
-
- \seebaseclassmethod
- */
- QVector<double> QCPAxisTickerLog::createTickVector(double tickStep, const QCPRange &range)
- {
- Q_UNUSED(tickStep)
- QVector<double> result;
- if (range.lower > 0 && range.upper > 0) // positive range
- {
- double exactPowerStep = qLn(range.upper/range.lower)*mLogBaseLnInv/(double)(mTickCount+1e-10);
- double newLogBase = qPow(mLogBase, qMax((int)cleanMantissa(exactPowerStep), 1));
- double currentTick = qPow(newLogBase, qFloor(qLn(range.lower)/qLn(newLogBase)));
- result.append(currentTick);
- while (currentTick < range.upper && currentTick > 0) // currentMag might be zero for ranges ~1e-300, just cancel in that case
- {
- currentTick *= newLogBase;
- result.append(currentTick);
- }
- } else if (range.lower < 0 && range.upper < 0) // negative range
- {
- double exactPowerStep = qLn(range.lower/range.upper)*mLogBaseLnInv/(double)(mTickCount+1e-10);
- double newLogBase = qPow(mLogBase, qMax((int)cleanMantissa(exactPowerStep), 1));
- double currentTick = -qPow(newLogBase, qCeil(qLn(-range.lower)/qLn(newLogBase)));
- result.append(currentTick);
- while (currentTick < range.upper && currentTick < 0) // currentMag might be zero for ranges ~1e-300, just cancel in that case
- {
- currentTick /= newLogBase;
- result.append(currentTick);
- }
- } else // invalid range for logarithmic scale, because lower and upper have different sign
- {
- qDebug() << Q_FUNC_INFO << "Invalid range for logarithmic plot: " << range.lower << ".." << range.upper;
- }
-
- return result;
- }
- /* end of 'src/axis/axistickerlog.cpp' */
- /* including file 'src/axis/axis.cpp', size 99397 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPGrid
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPGrid
- \brief Responsible for drawing the grid of a QCPAxis.
-
- This class is tightly bound to QCPAxis. Every axis owns a grid instance and uses it to draw the
- grid lines, sub grid lines and zero-line. You can interact with the grid of an axis via \ref
- QCPAxis::grid. Normally, you don't need to create an instance of QCPGrid yourself.
-
- The axis and grid drawing was split into two classes to allow them to be placed on different
- layers (both QCPAxis and QCPGrid inherit from QCPLayerable). Thus it is possible to have the grid
- in the background and the axes in the foreground, and any plottables/items in between. This
- described situation is the default setup, see the QCPLayer documentation.
- */
- /*!
- Creates a QCPGrid instance and sets default values.
-
- You shouldn't instantiate grids on their own, since every QCPAxis brings its own QCPGrid.
- */
- QCPGrid::QCPGrid(QCPAxis *parentAxis) :
- QCPLayerable(parentAxis->parentPlot(), QString(), parentAxis),
- mParentAxis(parentAxis)
- {
- // warning: this is called in QCPAxis constructor, so parentAxis members should not be accessed/called
- setParent(parentAxis);
- setPen(QPen(QColor(200,200,200), 0, Qt::DotLine));
- setSubGridPen(QPen(QColor(220,220,220), 0, Qt::DotLine));
- setZeroLinePen(QPen(QColor(200,200,200), 0, Qt::SolidLine));
- setSubGridVisible(false);
- setAntialiased(false);
- setAntialiasedSubGrid(false);
- setAntialiasedZeroLine(false);
- }
- /*!
- Sets whether grid lines at sub tick marks are drawn.
-
- \see setSubGridPen
- */
- void QCPGrid::setSubGridVisible(bool visible)
- {
- mSubGridVisible = visible;
- }
- /*!
- Sets whether sub grid lines are drawn antialiased.
- */
- void QCPGrid::setAntialiasedSubGrid(bool enabled)
- {
- mAntialiasedSubGrid = enabled;
- }
- /*!
- Sets whether zero lines are drawn antialiased.
- */
- void QCPGrid::setAntialiasedZeroLine(bool enabled)
- {
- mAntialiasedZeroLine = enabled;
- }
- /*!
- Sets the pen with which (major) grid lines are drawn.
- */
- void QCPGrid::setPen(const QPen &pen)
- {
- mPen = pen;
- }
- /*!
- Sets the pen with which sub grid lines are drawn.
- */
- void QCPGrid::setSubGridPen(const QPen &pen)
- {
- mSubGridPen = pen;
- }
- /*!
- Sets the pen with which zero lines are drawn.
-
- Zero lines are lines at value coordinate 0 which may be drawn with a different pen than other grid
- lines. To disable zero lines and just draw normal grid lines at zero, set \a pen to Qt::NoPen.
- */
- void QCPGrid::setZeroLinePen(const QPen &pen)
- {
- mZeroLinePen = pen;
- }
- /*! \internal
- A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
- before drawing the major grid lines.
- This is the antialiasing state the painter passed to the \ref draw method is in by default.
-
- This function takes into account the local setting of the antialiasing flag as well as the
- overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
- QCustomPlot::setNotAntialiasedElements.
-
- \see setAntialiased
- */
- void QCPGrid::applyDefaultAntialiasingHint(QCPPainter *painter) const
- {
- applyAntialiasingHint(painter, mAntialiased, QCP::aeGrid);
- }
- /*! \internal
-
- Draws grid lines and sub grid lines at the positions of (sub) ticks of the parent axis, spanning
- over the complete axis rect. Also draws the zero line, if appropriate (\ref setZeroLinePen).
- */
- void QCPGrid::draw(QCPPainter *painter)
- {
- if (!mParentAxis) { qDebug() << Q_FUNC_INFO << "invalid parent axis"; return; }
-
- if (mParentAxis->subTicks() && mSubGridVisible)
- drawSubGridLines(painter);
- drawGridLines(painter);
- }
- /*! \internal
-
- Draws the main grid lines and possibly a zero line with the specified painter.
-
- This is a helper function called by \ref draw.
- */
- void QCPGrid::drawGridLines(QCPPainter *painter) const
- {
- if (!mParentAxis) { qDebug() << Q_FUNC_INFO << "invalid parent axis"; return; }
-
- const int tickCount = mParentAxis->mTickVector.size();
- double t; // helper variable, result of coordinate-to-pixel transforms
- if (mParentAxis->orientation() == Qt::Horizontal)
- {
- // draw zeroline:
- int zeroLineIndex = -1;
- if (mZeroLinePen.style() != Qt::NoPen && mParentAxis->mRange.lower < 0 && mParentAxis->mRange.upper > 0)
- {
- applyAntialiasingHint(painter, mAntialiasedZeroLine, QCP::aeZeroLine);
- painter->setPen(mZeroLinePen);
- double epsilon = mParentAxis->range().size()*1E-6; // for comparing double to zero
- for (int i=0; i<tickCount; ++i)
- {
- if (qAbs(mParentAxis->mTickVector.at(i)) < epsilon)
- {
- zeroLineIndex = i;
- t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // x
- painter->drawLine(QLineF(t, mParentAxis->mAxisRect->bottom(), t, mParentAxis->mAxisRect->top()));
- break;
- }
- }
- }
- // draw grid lines:
- applyDefaultAntialiasingHint(painter);
- painter->setPen(mPen);
- for (int i=0; i<tickCount; ++i)
- {
- if (i == zeroLineIndex) continue; // don't draw a gridline on top of the zeroline
- t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // x
- painter->drawLine(QLineF(t, mParentAxis->mAxisRect->bottom(), t, mParentAxis->mAxisRect->top()));
- }
- } else
- {
- // draw zeroline:
- int zeroLineIndex = -1;
- if (mZeroLinePen.style() != Qt::NoPen && mParentAxis->mRange.lower < 0 && mParentAxis->mRange.upper > 0)
- {
- applyAntialiasingHint(painter, mAntialiasedZeroLine, QCP::aeZeroLine);
- painter->setPen(mZeroLinePen);
- double epsilon = mParentAxis->mRange.size()*1E-6; // for comparing double to zero
- for (int i=0; i<tickCount; ++i)
- {
- if (qAbs(mParentAxis->mTickVector.at(i)) < epsilon)
- {
- zeroLineIndex = i;
- t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // y
- painter->drawLine(QLineF(mParentAxis->mAxisRect->left(), t, mParentAxis->mAxisRect->right(), t));
- break;
- }
- }
- }
- // draw grid lines:
- applyDefaultAntialiasingHint(painter);
- painter->setPen(mPen);
- for (int i=0; i<tickCount; ++i)
- {
- if (i == zeroLineIndex) continue; // don't draw a gridline on top of the zeroline
- t = mParentAxis->coordToPixel(mParentAxis->mTickVector.at(i)); // y
- painter->drawLine(QLineF(mParentAxis->mAxisRect->left(), t, mParentAxis->mAxisRect->right(), t));
- }
- }
- }
- /*! \internal
-
- Draws the sub grid lines with the specified painter.
-
- This is a helper function called by \ref draw.
- */
- void QCPGrid::drawSubGridLines(QCPPainter *painter) const
- {
- if (!mParentAxis) { qDebug() << Q_FUNC_INFO << "invalid parent axis"; return; }
-
- applyAntialiasingHint(painter, mAntialiasedSubGrid, QCP::aeSubGrid);
- double t; // helper variable, result of coordinate-to-pixel transforms
- painter->setPen(mSubGridPen);
- if (mParentAxis->orientation() == Qt::Horizontal)
- {
- for (int i=0; i<mParentAxis->mSubTickVector.size(); ++i)
- {
- t = mParentAxis->coordToPixel(mParentAxis->mSubTickVector.at(i)); // x
- painter->drawLine(QLineF(t, mParentAxis->mAxisRect->bottom(), t, mParentAxis->mAxisRect->top()));
- }
- } else
- {
- for (int i=0; i<mParentAxis->mSubTickVector.size(); ++i)
- {
- t = mParentAxis->coordToPixel(mParentAxis->mSubTickVector.at(i)); // y
- painter->drawLine(QLineF(mParentAxis->mAxisRect->left(), t, mParentAxis->mAxisRect->right(), t));
- }
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPAxis
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPAxis
- \brief Manages a single axis inside a QCustomPlot.
- Usually doesn't need to be instantiated externally. Access %QCustomPlot's default four axes via
- QCustomPlot::xAxis (bottom), QCustomPlot::yAxis (left), QCustomPlot::xAxis2 (top) and
- QCustomPlot::yAxis2 (right).
-
- Axes are always part of an axis rect, see QCPAxisRect.
- \image html AxisNamesOverview.png
- <center>Naming convention of axis parts</center>
- \n
-
- \image html AxisRectSpacingOverview.png
- <center>Overview of the spacings and paddings that define the geometry of an axis. The dashed gray line
- on the left represents the QCustomPlot widget border.</center>
-
- Each axis holds an instance of QCPAxisTicker which is used to generate the tick coordinates and
- tick labels. You can access the currently installed \ref ticker or set a new one (possibly one of
- the specialized subclasses, or your own subclass) via \ref setTicker. For details, see the
- documentation of QCPAxisTicker.
- */
- /* start of documentation of inline functions */
- /*! \fn Qt::Orientation QCPAxis::orientation() const
- Returns the orientation of this axis. The axis orientation (horizontal or vertical) is deduced
- from the axis type (left, top, right or bottom).
- \see orientation(AxisType type), pixelOrientation
- */
- /*! \fn QCPGrid *QCPAxis::grid() const
-
- Returns the \ref QCPGrid instance belonging to this axis. Access it to set details about the way the
- grid is displayed.
- */
- /*! \fn static Qt::Orientation QCPAxis::orientation(AxisType type)
- Returns the orientation of the specified axis type
- \see orientation(), pixelOrientation
- */
- /*! \fn int QCPAxis::pixelOrientation() const
- Returns which direction points towards higher coordinate values/keys, in pixel space.
- This method returns either 1 or -1. If it returns 1, then going in the positive direction along
- the orientation of the axis in pixels corresponds to going from lower to higher axis coordinates.
- On the other hand, if this method returns -1, going to smaller pixel values corresponds to going
- from lower to higher axis coordinates.
- For example, this is useful to easily shift axis coordinates by a certain amount given in pixels,
- without having to care about reversed or vertically aligned axes:
- \code
- double newKey = keyAxis->pixelToCoord(keyAxis->coordToPixel(oldKey)+10*keyAxis->pixelOrientation());
- \endcode
- \a newKey will then contain a key that is ten pixels towards higher keys, starting from \a oldKey.
- */
- /*! \fn QSharedPointer<QCPAxisTicker> QCPAxis::ticker() const
- Returns a modifiable shared pointer to the currently installed axis ticker. The axis ticker is
- responsible for generating the tick positions and tick labels of this axis. You can access the
- \ref QCPAxisTicker with this method and modify basic properties such as the approximate tick count
- (\ref QCPAxisTicker::setTickCount).
- You can gain more control over the axis ticks by setting a different \ref QCPAxisTicker subclass, see
- the documentation there. A new axis ticker can be set with \ref setTicker.
- Since the ticker is stored in the axis as a shared pointer, multiple axes may share the same axis
- ticker simply by passing the same shared pointer to multiple axes.
- \see setTicker
- */
- /* end of documentation of inline functions */
- /* start of documentation of signals */
- /*! \fn void QCPAxis::rangeChanged(const QCPRange &newRange)
- This signal is emitted when the range of this axis has changed. You can connect it to the \ref
- setRange slot of another axis to communicate the new range to the other axis, in order for it to
- be synchronized.
-
- You may also manipulate/correct the range with \ref setRange in a slot connected to this signal.
- This is useful if for example a maximum range span shall not be exceeded, or if the lower/upper
- range shouldn't go beyond certain values (see \ref QCPRange::bounded). For example, the following
- slot would limit the x axis to ranges between 0 and 10:
- \code
- customPlot->xAxis->setRange(newRange.bounded(0, 10))
- \endcode
- */
- /*! \fn void QCPAxis::rangeChanged(const QCPRange &newRange, const QCPRange &oldRange)
- \overload
-
- Additionally to the new range, this signal also provides the previous range held by the axis as
- \a oldRange.
- */
- /*! \fn void QCPAxis::scaleTypeChanged(QCPAxis::ScaleType scaleType);
-
- This signal is emitted when the scale type changes, by calls to \ref setScaleType
- */
- /*! \fn void QCPAxis::selectionChanged(QCPAxis::SelectableParts selection)
-
- This signal is emitted when the selection state of this axis has changed, either by user interaction
- or by a direct call to \ref setSelectedParts.
- */
- /*! \fn void QCPAxis::selectableChanged(const QCPAxis::SelectableParts &parts);
-
- This signal is emitted when the selectability changes, by calls to \ref setSelectableParts
- */
- /* end of documentation of signals */
- /*!
- Constructs an Axis instance of Type \a type for the axis rect \a parent.
-
- Usually it isn't necessary to instantiate axes directly, because you can let QCustomPlot create
- them for you with \ref QCPAxisRect::addAxis. If you want to use own QCPAxis-subclasses however,
- create them manually and then inject them also via \ref QCPAxisRect::addAxis.
- */
- QCPAxis::QCPAxis(QCPAxisRect *parent, AxisType type) :
- QCPLayerable(parent->parentPlot(), QString(), parent),
- // axis base:
- mAxisType(type),
- mAxisRect(parent),
- mPadding(5),
- mOrientation(orientation(type)),
- mSelectableParts(spAxis | spTickLabels | spAxisLabel),
- mSelectedParts(spNone),
- mBasePen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
- mSelectedBasePen(QPen(Qt::blue, 2)),
- // axis label:
- mLabel(),
- mLabelFont(mParentPlot->font()),
- mSelectedLabelFont(QFont(mLabelFont.family(), mLabelFont.pointSize(), QFont::Bold)),
- mLabelColor(Qt::black),
- mSelectedLabelColor(Qt::blue),
- // tick labels:
- mTickLabels(true),
- mTickLabelFont(mParentPlot->font()),
- mSelectedTickLabelFont(QFont(mTickLabelFont.family(), mTickLabelFont.pointSize(), QFont::Bold)),
- mTickLabelColor(Qt::black),
- mSelectedTickLabelColor(Qt::blue),
- mNumberPrecision(6),
- mNumberFormatChar('g'),
- mNumberBeautifulPowers(true),
- // ticks and subticks:
- mTicks(true),
- mSubTicks(true),
- mTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
- mSelectedTickPen(QPen(Qt::blue, 2)),
- mSubTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
- mSelectedSubTickPen(QPen(Qt::blue, 2)),
- // scale and range:
- mRange(0, 5),
- mRangeReversed(false),
- mScaleType(stLinear),
- // internal members:
- mGrid(new QCPGrid(this)),
- mAxisPainter(new QCPAxisPainterPrivate(parent->parentPlot())),
- mTicker(new QCPAxisTicker),
- mCachedMarginValid(false),
- mCachedMargin(0)
- {
- setParent(parent);
- mGrid->setVisible(false);
- setAntialiased(false);
- setLayer(mParentPlot->currentLayer()); // it's actually on that layer already, but we want it in front of the grid, so we place it on there again
-
- if (type == atTop)
- {
- setTickLabelPadding(3);
- setLabelPadding(6);
- } else if (type == atRight)
- {
- setTickLabelPadding(7);
- setLabelPadding(12);
- } else if (type == atBottom)
- {
- setTickLabelPadding(3);
- setLabelPadding(3);
- } else if (type == atLeft)
- {
- setTickLabelPadding(5);
- setLabelPadding(10);
- }
- }
- QCPAxis::~QCPAxis()
- {
- delete mAxisPainter;
- delete mGrid; // delete grid here instead of via parent ~QObject for better defined deletion order
- }
- /* No documentation as it is a property getter */
- int QCPAxis::tickLabelPadding() const
- {
- return mAxisPainter->tickLabelPadding;
- }
- /* No documentation as it is a property getter */
- double QCPAxis::tickLabelRotation() const
- {
- return mAxisPainter->tickLabelRotation;
- }
- /* No documentation as it is a property getter */
- QCPAxis::LabelSide QCPAxis::tickLabelSide() const
- {
- return mAxisPainter->tickLabelSide;
- }
- /* No documentation as it is a property getter */
- QString QCPAxis::numberFormat() const
- {
- QString result;
- result.append(mNumberFormatChar);
- if (mNumberBeautifulPowers)
- {
- result.append(QLatin1Char('b'));
- if (mAxisPainter->numberMultiplyCross)
- result.append(QLatin1Char('c'));
- }
- return result;
- }
- /* No documentation as it is a property getter */
- int QCPAxis::tickLengthIn() const
- {
- return mAxisPainter->tickLengthIn;
- }
- /* No documentation as it is a property getter */
- int QCPAxis::tickLengthOut() const
- {
- return mAxisPainter->tickLengthOut;
- }
- /* No documentation as it is a property getter */
- int QCPAxis::subTickLengthIn() const
- {
- return mAxisPainter->subTickLengthIn;
- }
- /* No documentation as it is a property getter */
- int QCPAxis::subTickLengthOut() const
- {
- return mAxisPainter->subTickLengthOut;
- }
- /* No documentation as it is a property getter */
- int QCPAxis::labelPadding() const
- {
- return mAxisPainter->labelPadding;
- }
- /* No documentation as it is a property getter */
- int QCPAxis::offset() const
- {
- return mAxisPainter->offset;
- }
- /* No documentation as it is a property getter */
- QCPLineEnding QCPAxis::lowerEnding() const
- {
- return mAxisPainter->lowerEnding;
- }
- /* No documentation as it is a property getter */
- QCPLineEnding QCPAxis::upperEnding() const
- {
- return mAxisPainter->upperEnding;
- }
- /*!
- Sets whether the axis uses a linear scale or a logarithmic scale.
-
- Note that this method controls the coordinate transformation. You will likely also want to use a
- logarithmic tick spacing and labeling, which can be achieved by setting an instance of \ref
- QCPAxisTickerLog via \ref setTicker. See the documentation of \ref QCPAxisTickerLog about the
- details of logarithmic axis tick creation.
-
- \ref setNumberPrecision
- */
- void QCPAxis::setScaleType(QCPAxis::ScaleType type)
- {
- if (mScaleType != type)
- {
- mScaleType = type;
- if (mScaleType == stLogarithmic)
- setRange(mRange.sanitizedForLogScale());
- mCachedMarginValid = false;
- emit scaleTypeChanged(mScaleType);
- }
- }
- /*!
- Sets the range of the axis.
-
- This slot may be connected with the \ref rangeChanged signal of another axis so this axis
- is always synchronized with the other axis range, when it changes.
-
- To invert the direction of an axis, use \ref setRangeReversed.
- */
- void QCPAxis::setRange(const QCPRange &range)
- {
- if (range.lower == mRange.lower && range.upper == mRange.upper)
- return;
-
- if (!QCPRange::validRange(range)) return;
- QCPRange oldRange = mRange;
- if (mScaleType == stLogarithmic)
- {
- mRange = range.sanitizedForLogScale();
- } else
- {
- mRange = range.sanitizedForLinScale();
- }
- emit rangeChanged(mRange);
- emit rangeChanged(mRange, oldRange);
- }
- /*!
- Sets whether the user can (de-)select the parts in \a selectable by clicking on the QCustomPlot surface.
- (When \ref QCustomPlot::setInteractions contains iSelectAxes.)
-
- However, even when \a selectable is set to a value not allowing the selection of a specific part,
- it is still possible to set the selection of this part manually, by calling \ref setSelectedParts
- directly.
-
- \see SelectablePart, setSelectedParts
- */
- void QCPAxis::setSelectableParts(const SelectableParts &selectable)
- {
- if (mSelectableParts != selectable)
- {
- mSelectableParts = selectable;
- emit selectableChanged(mSelectableParts);
- }
- }
- /*!
- Sets the selected state of the respective axis parts described by \ref SelectablePart. When a part
- is selected, it uses a different pen/font.
-
- The entire selection mechanism for axes is handled automatically when \ref
- QCustomPlot::setInteractions contains iSelectAxes. You only need to call this function when you
- wish to change the selection state manually.
-
- This function can change the selection state of a part, independent of the \ref setSelectableParts setting.
-
- emits the \ref selectionChanged signal when \a selected is different from the previous selection state.
-
- \see SelectablePart, setSelectableParts, selectTest, setSelectedBasePen, setSelectedTickPen, setSelectedSubTickPen,
- setSelectedTickLabelFont, setSelectedLabelFont, setSelectedTickLabelColor, setSelectedLabelColor
- */
- void QCPAxis::setSelectedParts(const SelectableParts &selected)
- {
- if (mSelectedParts != selected)
- {
- mSelectedParts = selected;
- emit selectionChanged(mSelectedParts);
- }
- }
- /*!
- \overload
-
- Sets the lower and upper bound of the axis range.
-
- To invert the direction of an axis, use \ref setRangeReversed.
-
- There is also a slot to set a range, see \ref setRange(const QCPRange &range).
- */
- void QCPAxis::setRange(double lower, double upper)
- {
- if (lower == mRange.lower && upper == mRange.upper)
- return;
-
- if (!QCPRange::validRange(lower, upper)) return;
- QCPRange oldRange = mRange;
- mRange.lower = lower;
- mRange.upper = upper;
- if (mScaleType == stLogarithmic)
- {
- mRange = mRange.sanitizedForLogScale();
- } else
- {
- mRange = mRange.sanitizedForLinScale();
- }
- emit rangeChanged(mRange);
- emit rangeChanged(mRange, oldRange);
- }
- /*!
- \overload
-
- Sets the range of the axis.
-
- The \a position coordinate indicates together with the \a alignment parameter, where the new
- range will be positioned. \a size defines the size of the new axis range. \a alignment may be
- Qt::AlignLeft, Qt::AlignRight or Qt::AlignCenter. This will cause the left border, right border,
- or center of the range to be aligned with \a position. Any other values of \a alignment will
- default to Qt::AlignCenter.
- */
- void QCPAxis::setRange(double position, double size, Qt::AlignmentFlag alignment)
- {
- if (alignment == Qt::AlignLeft)
- setRange(position, position+size);
- else if (alignment == Qt::AlignRight)
- setRange(position-size, position);
- else // alignment == Qt::AlignCenter
- setRange(position-size/2.0, position+size/2.0);
- }
- /*!
- Sets the lower bound of the axis range. The upper bound is not changed.
- \see setRange
- */
- void QCPAxis::setRangeLower(double lower)
- {
- if (mRange.lower == lower)
- return;
-
- QCPRange oldRange = mRange;
- mRange.lower = lower;
- if (mScaleType == stLogarithmic)
- {
- mRange = mRange.sanitizedForLogScale();
- } else
- {
- mRange = mRange.sanitizedForLinScale();
- }
- emit rangeChanged(mRange);
- emit rangeChanged(mRange, oldRange);
- }
- /*!
- Sets the upper bound of the axis range. The lower bound is not changed.
- \see setRange
- */
- void QCPAxis::setRangeUpper(double upper)
- {
- if (mRange.upper == upper)
- return;
-
- QCPRange oldRange = mRange;
- mRange.upper = upper;
- if (mScaleType == stLogarithmic)
- {
- mRange = mRange.sanitizedForLogScale();
- } else
- {
- mRange = mRange.sanitizedForLinScale();
- }
- emit rangeChanged(mRange);
- emit rangeChanged(mRange, oldRange);
- }
- /*!
- Sets whether the axis range (direction) is displayed reversed. Normally, the values on horizontal
- axes increase left to right, on vertical axes bottom to top. When \a reversed is set to true, the
- direction of increasing values is inverted.
- Note that the range and data interface stays the same for reversed axes, e.g. the \a lower part
- of the \ref setRange interface will still reference the mathematically smaller number than the \a
- upper part.
- */
- void QCPAxis::setRangeReversed(bool reversed)
- {
- mRangeReversed = reversed;
- }
- /*!
- The axis ticker is responsible for generating the tick positions and tick labels. See the
- documentation of QCPAxisTicker for details on how to work with axis tickers.
-
- You can change the tick positioning/labeling behaviour of this axis by setting a different
- QCPAxisTicker subclass using this method. If you only wish to modify the currently installed axis
- ticker, access it via \ref ticker.
-
- Since the ticker is stored in the axis as a shared pointer, multiple axes may share the same axis
- ticker simply by passing the same shared pointer to multiple axes.
-
- \see ticker
- */
- void QCPAxis::setTicker(QSharedPointer<QCPAxisTicker> ticker)
- {
- if (ticker)
- mTicker = ticker;
- else
- qDebug() << Q_FUNC_INFO << "can not set 0 as axis ticker";
- // no need to invalidate margin cache here because produced tick labels are checked for changes in setupTickVector
- }
- /*!
- Sets whether tick marks are displayed.
- Note that setting \a show to false does not imply that tick labels are invisible, too. To achieve
- that, see \ref setTickLabels.
-
- \see setSubTicks
- */
- void QCPAxis::setTicks(bool show)
- {
- if (mTicks != show)
- {
- mTicks = show;
- mCachedMarginValid = false;
- }
- }
- /*!
- Sets whether tick labels are displayed. Tick labels are the numbers drawn next to tick marks.
- */
- void QCPAxis::setTickLabels(bool show)
- {
- if (mTickLabels != show)
- {
- mTickLabels = show;
- mCachedMarginValid = false;
- if (!mTickLabels)
- mTickVectorLabels.clear();
- }
- }
- /*!
- Sets the distance between the axis base line (including any outward ticks) and the tick labels.
- \see setLabelPadding, setPadding
- */
- void QCPAxis::setTickLabelPadding(int padding)
- {
- if (mAxisPainter->tickLabelPadding != padding)
- {
- mAxisPainter->tickLabelPadding = padding;
- mCachedMarginValid = false;
- }
- }
- /*!
- Sets the font of the tick labels.
-
- \see setTickLabels, setTickLabelColor
- */
- void QCPAxis::setTickLabelFont(const QFont &font)
- {
- if (font != mTickLabelFont)
- {
- mTickLabelFont = font;
- mCachedMarginValid = false;
- }
- }
- /*!
- Sets the color of the tick labels.
-
- \see setTickLabels, setTickLabelFont
- */
- void QCPAxis::setTickLabelColor(const QColor &color)
- {
- mTickLabelColor = color;
- }
- /*!
- Sets the rotation of the tick labels. If \a degrees is zero, the labels are drawn normally. Else,
- the tick labels are drawn rotated by \a degrees clockwise. The specified angle is bound to values
- from -90 to 90 degrees.
-
- If \a degrees is exactly -90, 0 or 90, the tick labels are centered on the tick coordinate. For
- other angles, the label is drawn with an offset such that it seems to point toward or away from
- the tick mark.
- */
- void QCPAxis::setTickLabelRotation(double degrees)
- {
- if (!qFuzzyIsNull(degrees-mAxisPainter->tickLabelRotation))
- {
- mAxisPainter->tickLabelRotation = qBound(-90.0, degrees, 90.0);
- mCachedMarginValid = false;
- }
- }
- /*!
- Sets whether the tick labels (numbers) shall appear inside or outside the axis rect.
-
- The usual and default setting is \ref lsOutside. Very compact plots sometimes require tick labels
- to be inside the axis rect, to save space. If \a side is set to \ref lsInside, the tick labels
- appear on the inside are additionally clipped to the axis rect.
- */
- void QCPAxis::setTickLabelSide(LabelSide side)
- {
- mAxisPainter->tickLabelSide = side;
- mCachedMarginValid = false;
- }
- /*!
- Sets the number format for the numbers in tick labels. This \a formatCode is an extended version
- of the format code used e.g. by QString::number() and QLocale::toString(). For reference about
- that, see the "Argument Formats" section in the detailed description of the QString class.
-
- \a formatCode is a string of one, two or three characters. The first character is identical to
- the normal format code used by Qt. In short, this means: 'e'/'E' scientific format, 'f' fixed
- format, 'g'/'G' scientific or fixed, whichever is shorter.
-
- The second and third characters are optional and specific to QCustomPlot:\n
- If the first char was 'e' or 'g', numbers are/might be displayed in the scientific format, e.g.
- "5.5e9", which is ugly in a plot. So when the second char of \a formatCode is set to 'b' (for
- "beautiful"), those exponential numbers are formatted in a more natural way, i.e. "5.5
- [multiplication sign] 10 [superscript] 9". By default, the multiplication sign is a centered dot.
- If instead a cross should be shown (as is usual in the USA), the third char of \a formatCode can
- be set to 'c'. The inserted multiplication signs are the UTF-8 characters 215 (0xD7) for the
- cross and 183 (0xB7) for the dot.
-
- Examples for \a formatCode:
- \li \c g normal format code behaviour. If number is small, fixed format is used, if number is large,
- normal scientific format is used
- \li \c gb If number is small, fixed format is used, if number is large, scientific format is used with
- beautifully typeset decimal powers and a dot as multiplication sign
- \li \c ebc All numbers are in scientific format with beautifully typeset decimal power and a cross as
- multiplication sign
- \li \c fb illegal format code, since fixed format doesn't support (or need) beautifully typeset decimal
- powers. Format code will be reduced to 'f'.
- \li \c hello illegal format code, since first char is not 'e', 'E', 'f', 'g' or 'G'. Current format
- code will not be changed.
- */
- void QCPAxis::setNumberFormat(const QString &formatCode)
- {
- if (formatCode.isEmpty())
- {
- qDebug() << Q_FUNC_INFO << "Passed formatCode is empty";
- return;
- }
- mCachedMarginValid = false;
-
- // interpret first char as number format char:
- QString allowedFormatChars(QLatin1String("eEfgG"));
- if (allowedFormatChars.contains(formatCode.at(0)))
- {
- mNumberFormatChar = QLatin1Char(formatCode.at(0).toLatin1());
- } else
- {
- qDebug() << Q_FUNC_INFO << "Invalid number format code (first char not in 'eEfgG'):" << formatCode;
- return;
- }
- if (formatCode.length() < 2)
- {
- mNumberBeautifulPowers = false;
- mAxisPainter->numberMultiplyCross = false;
- return;
- }
-
- // interpret second char as indicator for beautiful decimal powers:
- if (formatCode.at(1) == QLatin1Char('b') && (mNumberFormatChar == QLatin1Char('e') || mNumberFormatChar == QLatin1Char('g')))
- {
- mNumberBeautifulPowers = true;
- } else
- {
- qDebug() << Q_FUNC_INFO << "Invalid number format code (second char not 'b' or first char neither 'e' nor 'g'):" << formatCode;
- return;
- }
- if (formatCode.length() < 3)
- {
- mAxisPainter->numberMultiplyCross = false;
- return;
- }
-
- // interpret third char as indicator for dot or cross multiplication symbol:
- if (formatCode.at(2) == QLatin1Char('c'))
- {
- mAxisPainter->numberMultiplyCross = true;
- } else if (formatCode.at(2) == QLatin1Char('d'))
- {
- mAxisPainter->numberMultiplyCross = false;
- } else
- {
- qDebug() << Q_FUNC_INFO << "Invalid number format code (third char neither 'c' nor 'd'):" << formatCode;
- return;
- }
- }
- /*!
- Sets the precision of the tick label numbers. See QLocale::toString(double i, char f, int prec)
- for details. The effect of precisions are most notably for number Formats starting with 'e', see
- \ref setNumberFormat
- */
- void QCPAxis::setNumberPrecision(int precision)
- {
- if (mNumberPrecision != precision)
- {
- mNumberPrecision = precision;
- mCachedMarginValid = false;
- }
- }
- /*!
- Sets the length of the ticks in pixels. \a inside is the length the ticks will reach inside the
- plot and \a outside is the length they will reach outside the plot. If \a outside is greater than
- zero, the tick labels and axis label will increase their distance to the axis accordingly, so
- they won't collide with the ticks.
-
- \see setSubTickLength, setTickLengthIn, setTickLengthOut
- */
- void QCPAxis::setTickLength(int inside, int outside)
- {
- setTickLengthIn(inside);
- setTickLengthOut(outside);
- }
- /*!
- Sets the length of the inward ticks in pixels. \a inside is the length the ticks will reach
- inside the plot.
-
- \see setTickLengthOut, setTickLength, setSubTickLength
- */
- void QCPAxis::setTickLengthIn(int inside)
- {
- if (mAxisPainter->tickLengthIn != inside)
- {
- mAxisPainter->tickLengthIn = inside;
- }
- }
- /*!
- Sets the length of the outward ticks in pixels. \a outside is the length the ticks will reach
- outside the plot. If \a outside is greater than zero, the tick labels and axis label will
- increase their distance to the axis accordingly, so they won't collide with the ticks.
-
- \see setTickLengthIn, setTickLength, setSubTickLength
- */
- void QCPAxis::setTickLengthOut(int outside)
- {
- if (mAxisPainter->tickLengthOut != outside)
- {
- mAxisPainter->tickLengthOut = outside;
- mCachedMarginValid = false; // only outside tick length can change margin
- }
- }
- /*!
- Sets whether sub tick marks are displayed.
-
- Sub ticks are only potentially visible if (major) ticks are also visible (see \ref setTicks)
-
- \see setTicks
- */
- void QCPAxis::setSubTicks(bool show)
- {
- if (mSubTicks != show)
- {
- mSubTicks = show;
- mCachedMarginValid = false;
- }
- }
- /*!
- Sets the length of the subticks in pixels. \a inside is the length the subticks will reach inside
- the plot and \a outside is the length they will reach outside the plot. If \a outside is greater
- than zero, the tick labels and axis label will increase their distance to the axis accordingly,
- so they won't collide with the ticks.
-
- \see setTickLength, setSubTickLengthIn, setSubTickLengthOut
- */
- void QCPAxis::setSubTickLength(int inside, int outside)
- {
- setSubTickLengthIn(inside);
- setSubTickLengthOut(outside);
- }
- /*!
- Sets the length of the inward subticks in pixels. \a inside is the length the subticks will reach inside
- the plot.
-
- \see setSubTickLengthOut, setSubTickLength, setTickLength
- */
- void QCPAxis::setSubTickLengthIn(int inside)
- {
- if (mAxisPainter->subTickLengthIn != inside)
- {
- mAxisPainter->subTickLengthIn = inside;
- }
- }
- /*!
- Sets the length of the outward subticks in pixels. \a outside is the length the subticks will reach
- outside the plot. If \a outside is greater than zero, the tick labels will increase their
- distance to the axis accordingly, so they won't collide with the ticks.
-
- \see setSubTickLengthIn, setSubTickLength, setTickLength
- */
- void QCPAxis::setSubTickLengthOut(int outside)
- {
- if (mAxisPainter->subTickLengthOut != outside)
- {
- mAxisPainter->subTickLengthOut = outside;
- mCachedMarginValid = false; // only outside tick length can change margin
- }
- }
- /*!
- Sets the pen, the axis base line is drawn with.
-
- \see setTickPen, setSubTickPen
- */
- void QCPAxis::setBasePen(const QPen &pen)
- {
- mBasePen = pen;
- }
- /*!
- Sets the pen, tick marks will be drawn with.
-
- \see setTickLength, setBasePen
- */
- void QCPAxis::setTickPen(const QPen &pen)
- {
- mTickPen = pen;
- }
- /*!
- Sets the pen, subtick marks will be drawn with.
-
- \see setSubTickCount, setSubTickLength, setBasePen
- */
- void QCPAxis::setSubTickPen(const QPen &pen)
- {
- mSubTickPen = pen;
- }
- /*!
- Sets the font of the axis label.
-
- \see setLabelColor
- */
- void QCPAxis::setLabelFont(const QFont &font)
- {
- if (mLabelFont != font)
- {
- mLabelFont = font;
- mCachedMarginValid = false;
- }
- }
- /*!
- Sets the color of the axis label.
-
- \see setLabelFont
- */
- void QCPAxis::setLabelColor(const QColor &color)
- {
- mLabelColor = color;
- }
- /*!
- Sets the text of the axis label that will be shown below/above or next to the axis, depending on
- its orientation. To disable axis labels, pass an empty string as \a str.
- */
- void QCPAxis::setLabel(const QString &str)
- {
- if (mLabel != str)
- {
- mLabel = str;
- mCachedMarginValid = false;
- }
- }
- /*!
- Sets the distance between the tick labels and the axis label.
-
- \see setTickLabelPadding, setPadding
- */
- void QCPAxis::setLabelPadding(int padding)
- {
- if (mAxisPainter->labelPadding != padding)
- {
- mAxisPainter->labelPadding = padding;
- mCachedMarginValid = false;
- }
- }
- /*!
- Sets the padding of the axis.
- When \ref QCPAxisRect::setAutoMargins is enabled, the padding is the additional outer most space,
- that is left blank.
-
- The axis padding has no meaning if \ref QCPAxisRect::setAutoMargins is disabled.
-
- \see setLabelPadding, setTickLabelPadding
- */
- void QCPAxis::setPadding(int padding)
- {
- if (mPadding != padding)
- {
- mPadding = padding;
- mCachedMarginValid = false;
- }
- }
- /*!
- Sets the offset the axis has to its axis rect side.
-
- If an axis rect side has multiple axes and automatic margin calculation is enabled for that side,
- only the offset of the inner most axis has meaning (even if it is set to be invisible). The
- offset of the other, outer axes is controlled automatically, to place them at appropriate
- positions.
- */
- void QCPAxis::setOffset(int offset)
- {
- mAxisPainter->offset = offset;
- }
- /*!
- Sets the font that is used for tick labels when they are selected.
-
- \see setTickLabelFont, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
- */
- void QCPAxis::setSelectedTickLabelFont(const QFont &font)
- {
- if (font != mSelectedTickLabelFont)
- {
- mSelectedTickLabelFont = font;
- // don't set mCachedMarginValid to false here because margin calculation is always done with non-selected fonts
- }
- }
- /*!
- Sets the font that is used for the axis label when it is selected.
-
- \see setLabelFont, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
- */
- void QCPAxis::setSelectedLabelFont(const QFont &font)
- {
- mSelectedLabelFont = font;
- // don't set mCachedMarginValid to false here because margin calculation is always done with non-selected fonts
- }
- /*!
- Sets the color that is used for tick labels when they are selected.
-
- \see setTickLabelColor, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
- */
- void QCPAxis::setSelectedTickLabelColor(const QColor &color)
- {
- if (color != mSelectedTickLabelColor)
- {
- mSelectedTickLabelColor = color;
- }
- }
- /*!
- Sets the color that is used for the axis label when it is selected.
-
- \see setLabelColor, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
- */
- void QCPAxis::setSelectedLabelColor(const QColor &color)
- {
- mSelectedLabelColor = color;
- }
- /*!
- Sets the pen that is used to draw the axis base line when selected.
-
- \see setBasePen, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
- */
- void QCPAxis::setSelectedBasePen(const QPen &pen)
- {
- mSelectedBasePen = pen;
- }
- /*!
- Sets the pen that is used to draw the (major) ticks when selected.
-
- \see setTickPen, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
- */
- void QCPAxis::setSelectedTickPen(const QPen &pen)
- {
- mSelectedTickPen = pen;
- }
- /*!
- Sets the pen that is used to draw the subticks when selected.
-
- \see setSubTickPen, setSelectableParts, setSelectedParts, QCustomPlot::setInteractions
- */
- void QCPAxis::setSelectedSubTickPen(const QPen &pen)
- {
- mSelectedSubTickPen = pen;
- }
- /*!
- Sets the style for the lower axis ending. See the documentation of QCPLineEnding for available
- styles.
-
- For horizontal axes, this method refers to the left ending, for vertical axes the bottom ending.
- Note that this meaning does not change when the axis range is reversed with \ref
- setRangeReversed.
-
- \see setUpperEnding
- */
- void QCPAxis::setLowerEnding(const QCPLineEnding &ending)
- {
- mAxisPainter->lowerEnding = ending;
- }
- /*!
- Sets the style for the upper axis ending. See the documentation of QCPLineEnding for available
- styles.
-
- For horizontal axes, this method refers to the right ending, for vertical axes the top ending.
- Note that this meaning does not change when the axis range is reversed with \ref
- setRangeReversed.
-
- \see setLowerEnding
- */
- void QCPAxis::setUpperEnding(const QCPLineEnding &ending)
- {
- mAxisPainter->upperEnding = ending;
- }
- /*!
- If the scale type (\ref setScaleType) is \ref stLinear, \a diff is added to the lower and upper
- bounds of the range. The range is simply moved by \a diff.
-
- If the scale type is \ref stLogarithmic, the range bounds are multiplied by \a diff. This
- corresponds to an apparent "linear" move in logarithmic scaling by a distance of log(diff).
- */
- void QCPAxis::moveRange(double diff)
- {
- QCPRange oldRange = mRange;
- if (mScaleType == stLinear)
- {
- mRange.lower += diff;
- mRange.upper += diff;
- } else // mScaleType == stLogarithmic
- {
- mRange.lower *= diff;
- mRange.upper *= diff;
- }
- emit rangeChanged(mRange);
- emit rangeChanged(mRange, oldRange);
- }
- /*!
- Scales the range of this axis by \a factor around the center of the current axis range. For
- example, if \a factor is 2.0, then the axis range will double its size, and the point at the axis
- range center won't have changed its position in the QCustomPlot widget (i.e. coordinates around
- the center will have moved symmetrically closer).
- If you wish to scale around a different coordinate than the current axis range center, use the
- overload \ref scaleRange(double factor, double center).
- */
- void QCPAxis::scaleRange(double factor)
- {
- scaleRange(factor, range().center());
- }
- /*! \overload
- Scales the range of this axis by \a factor around the coordinate \a center. For example, if \a
- factor is 2.0, \a center is 1.0, then the axis range will double its size, and the point at
- coordinate 1.0 won't have changed its position in the QCustomPlot widget (i.e. coordinates
- around 1.0 will have moved symmetrically closer to 1.0).
- \see scaleRange(double factor)
- */
- void QCPAxis::scaleRange(double factor, double center)
- {
- QCPRange oldRange = mRange;
- if (mScaleType == stLinear)
- {
- QCPRange newRange;
- newRange.lower = (mRange.lower-center)*factor + center;
- newRange.upper = (mRange.upper-center)*factor + center;
- if (QCPRange::validRange(newRange))
- mRange = newRange.sanitizedForLinScale();
- } else // mScaleType == stLogarithmic
- {
- if ((mRange.upper < 0 && center < 0) || (mRange.upper > 0 && center > 0)) // make sure center has same sign as range
- {
- QCPRange newRange;
- newRange.lower = qPow(mRange.lower/center, factor)*center;
- newRange.upper = qPow(mRange.upper/center, factor)*center;
- if (QCPRange::validRange(newRange))
- mRange = newRange.sanitizedForLogScale();
- } else
- qDebug() << Q_FUNC_INFO << "Center of scaling operation doesn't lie in same logarithmic sign domain as range:" << center;
- }
- emit rangeChanged(mRange);
- emit rangeChanged(mRange, oldRange);
- }
- /*!
- Scales the range of this axis to have a certain scale \a ratio to \a otherAxis. The scaling will
- be done around the center of the current axis range.
- For example, if \a ratio is 1, this axis is the \a yAxis and \a otherAxis is \a xAxis, graphs
- plotted with those axes will appear in a 1:1 aspect ratio, independent of the aspect ratio the
- axis rect has.
- This is an operation that changes the range of this axis once, it doesn't fix the scale ratio
- indefinitely. Note that calling this function in the constructor of the QCustomPlot's parent
- won't have the desired effect, since the widget dimensions aren't defined yet, and a resizeEvent
- will follow.
- */
- void QCPAxis::setScaleRatio(const QCPAxis *otherAxis, double ratio)
- {
- int otherPixelSize, ownPixelSize;
-
- if (otherAxis->orientation() == Qt::Horizontal)
- otherPixelSize = otherAxis->axisRect()->width();
- else
- otherPixelSize = otherAxis->axisRect()->height();
-
- if (orientation() == Qt::Horizontal)
- ownPixelSize = axisRect()->width();
- else
- ownPixelSize = axisRect()->height();
-
- double newRangeSize = ratio*otherAxis->range().size()*ownPixelSize/(double)otherPixelSize;
- setRange(range().center(), newRangeSize, Qt::AlignCenter);
- }
- /*!
- Changes the axis range such that all plottables associated with this axis are fully visible in
- that dimension.
-
- \see QCPAbstractPlottable::rescaleAxes, QCustomPlot::rescaleAxes
- */
- void QCPAxis::rescale(bool onlyVisiblePlottables)
- {
- QList<QCPAbstractPlottable*> p = plottables();
- QCPRange newRange;
- bool haveRange = false;
- for (int i=0; i<p.size(); ++i)
- {
- if (!p.at(i)->realVisibility() && onlyVisiblePlottables)
- continue;
- QCPRange plottableRange;
- bool currentFoundRange;
- QCP::SignDomain signDomain = QCP::sdBoth;
- if (mScaleType == stLogarithmic)
- signDomain = (mRange.upper < 0 ? QCP::sdNegative : QCP::sdPositive);
- if (p.at(i)->keyAxis() == this)
- plottableRange = p.at(i)->getKeyRange(currentFoundRange, signDomain);
- else
- plottableRange = p.at(i)->getValueRange(currentFoundRange, signDomain);
- if (currentFoundRange)
- {
- if (!haveRange)
- newRange = plottableRange;
- else
- newRange.expand(plottableRange);
- haveRange = true;
- }
- }
- if (haveRange)
- {
- if (!QCPRange::validRange(newRange)) // likely due to range being zero (plottable has only constant data in this axis dimension), shift current range to at least center the plottable
- {
- double center = (newRange.lower+newRange.upper)*0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason
- if (mScaleType == stLinear)
- {
- newRange.lower = center-mRange.size()/2.0;
- newRange.upper = center+mRange.size()/2.0;
- } else // mScaleType == stLogarithmic
- {
- newRange.lower = center/qSqrt(mRange.upper/mRange.lower);
- newRange.upper = center*qSqrt(mRange.upper/mRange.lower);
- }
- }
- setRange(newRange);
- }
- }
- /*!
- Transforms \a value, in pixel coordinates of the QCustomPlot widget, to axis coordinates.
- */
- double QCPAxis::pixelToCoord(double value) const
- {
- if (orientation() == Qt::Horizontal)
- {
- if (mScaleType == stLinear)
- {
- if (!mRangeReversed)
- return (value-mAxisRect->left())/(double)mAxisRect->width()*mRange.size()+mRange.lower;
- else
- return -(value-mAxisRect->left())/(double)mAxisRect->width()*mRange.size()+mRange.upper;
- } else // mScaleType == stLogarithmic
- {
- if (!mRangeReversed)
- return qPow(mRange.upper/mRange.lower, (value-mAxisRect->left())/(double)mAxisRect->width())*mRange.lower;
- else
- return qPow(mRange.upper/mRange.lower, (mAxisRect->left()-value)/(double)mAxisRect->width())*mRange.upper;
- }
- } else // orientation() == Qt::Vertical
- {
- if (mScaleType == stLinear)
- {
- if (!mRangeReversed)
- return (mAxisRect->bottom()-value)/(double)mAxisRect->height()*mRange.size()+mRange.lower;
- else
- return -(mAxisRect->bottom()-value)/(double)mAxisRect->height()*mRange.size()+mRange.upper;
- } else // mScaleType == stLogarithmic
- {
- if (!mRangeReversed)
- return qPow(mRange.upper/mRange.lower, (mAxisRect->bottom()-value)/(double)mAxisRect->height())*mRange.lower;
- else
- return qPow(mRange.upper/mRange.lower, (value-mAxisRect->bottom())/(double)mAxisRect->height())*mRange.upper;
- }
- }
- }
- /*!
- Transforms \a value, in coordinates of the axis, to pixel coordinates of the QCustomPlot widget.
- */
- double QCPAxis::coordToPixel(double value) const
- {
- if (orientation() == Qt::Horizontal)
- {
- if (mScaleType == stLinear)
- {
- if (!mRangeReversed)
- return (value-mRange.lower)/mRange.size()*mAxisRect->width()+mAxisRect->left();
- else
- return (mRange.upper-value)/mRange.size()*mAxisRect->width()+mAxisRect->left();
- } else // mScaleType == stLogarithmic
- {
- if (value >= 0.0 && mRange.upper < 0.0) // invalid value for logarithmic scale, just draw it outside visible range
- return !mRangeReversed ? mAxisRect->right()+200 : mAxisRect->left()-200;
- else if (value <= 0.0 && mRange.upper >= 0.0) // invalid value for logarithmic scale, just draw it outside visible range
- return !mRangeReversed ? mAxisRect->left()-200 : mAxisRect->right()+200;
- else
- {
- if (!mRangeReversed)
- return qLn(value/mRange.lower)/qLn(mRange.upper/mRange.lower)*mAxisRect->width()+mAxisRect->left();
- else
- return qLn(mRange.upper/value)/qLn(mRange.upper/mRange.lower)*mAxisRect->width()+mAxisRect->left();
- }
- }
- } else // orientation() == Qt::Vertical
- {
- if (mScaleType == stLinear)
- {
- if (!mRangeReversed)
- return mAxisRect->bottom()-(value-mRange.lower)/mRange.size()*mAxisRect->height();
- else
- return mAxisRect->bottom()-(mRange.upper-value)/mRange.size()*mAxisRect->height();
- } else // mScaleType == stLogarithmic
- {
- if (value >= 0.0 && mRange.upper < 0.0) // invalid value for logarithmic scale, just draw it outside visible range
- return !mRangeReversed ? mAxisRect->top()-200 : mAxisRect->bottom()+200;
- else if (value <= 0.0 && mRange.upper >= 0.0) // invalid value for logarithmic scale, just draw it outside visible range
- return !mRangeReversed ? mAxisRect->bottom()+200 : mAxisRect->top()-200;
- else
- {
- if (!mRangeReversed)
- return mAxisRect->bottom()-qLn(value/mRange.lower)/qLn(mRange.upper/mRange.lower)*mAxisRect->height();
- else
- return mAxisRect->bottom()-qLn(mRange.upper/value)/qLn(mRange.upper/mRange.lower)*mAxisRect->height();
- }
- }
- }
- }
- /*!
- Returns the part of the axis that is hit by \a pos (in pixels). The return value of this function
- is independent of the user-selectable parts defined with \ref setSelectableParts. Further, this
- function does not change the current selection state of the axis.
-
- If the axis is not visible (\ref setVisible), this function always returns \ref spNone.
-
- \see setSelectedParts, setSelectableParts, QCustomPlot::setInteractions
- */
- QCPAxis::SelectablePart QCPAxis::getPartAt(const QPointF &pos) const
- {
- if (!mVisible)
- return spNone;
-
- if (mAxisPainter->axisSelectionBox().contains(pos.toPoint()))
- return spAxis;
- else if (mAxisPainter->tickLabelsSelectionBox().contains(pos.toPoint()))
- return spTickLabels;
- else if (mAxisPainter->labelSelectionBox().contains(pos.toPoint()))
- return spAxisLabel;
- else
- return spNone;
- }
- /* inherits documentation from base class */
- double QCPAxis::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- if (!mParentPlot) return -1;
- SelectablePart part = getPartAt(pos);
- if ((onlySelectable && !mSelectableParts.testFlag(part)) || part == spNone)
- return -1;
-
- if (details)
- details->setValue(part);
- return mParentPlot->selectionTolerance()*0.99;
- }
- /*!
- Returns a list of all the plottables that have this axis as key or value axis.
-
- If you are only interested in plottables of type QCPGraph, see \ref graphs.
-
- \see graphs, items
- */
- QList<QCPAbstractPlottable*> QCPAxis::plottables() const
- {
- QList<QCPAbstractPlottable*> result;
- if (!mParentPlot) return result;
-
- for (int i=0; i<mParentPlot->mPlottables.size(); ++i)
- {
- if (mParentPlot->mPlottables.at(i)->keyAxis() == this ||mParentPlot->mPlottables.at(i)->valueAxis() == this)
- result.append(mParentPlot->mPlottables.at(i));
- }
- return result;
- }
- /*!
- Returns a list of all the graphs that have this axis as key or value axis.
-
- \see plottables, items
- */
- QList<QCPGraph*> QCPAxis::graphs() const
- {
- QList<QCPGraph*> result;
- if (!mParentPlot) return result;
-
- for (int i=0; i<mParentPlot->mGraphs.size(); ++i)
- {
- if (mParentPlot->mGraphs.at(i)->keyAxis() == this || mParentPlot->mGraphs.at(i)->valueAxis() == this)
- result.append(mParentPlot->mGraphs.at(i));
- }
- return result;
- }
- /*!
- Returns a list of all the items that are associated with this axis. An item is considered
- associated with an axis if at least one of its positions uses the axis as key or value axis.
-
- \see plottables, graphs
- */
- QList<QCPAbstractItem*> QCPAxis::items() const
- {
- QList<QCPAbstractItem*> result;
- if (!mParentPlot) return result;
-
- for (int itemId=0; itemId<mParentPlot->mItems.size(); ++itemId)
- {
- QList<QCPItemPosition*> positions = mParentPlot->mItems.at(itemId)->positions();
- for (int posId=0; posId<positions.size(); ++posId)
- {
- if (positions.at(posId)->keyAxis() == this || positions.at(posId)->valueAxis() == this)
- {
- result.append(mParentPlot->mItems.at(itemId));
- break;
- }
- }
- }
- return result;
- }
- /*!
- Transforms a margin side to the logically corresponding axis type. (QCP::msLeft to
- QCPAxis::atLeft, QCP::msRight to QCPAxis::atRight, etc.)
- */
- QCPAxis::AxisType QCPAxis::marginSideToAxisType(QCP::MarginSide side)
- {
- switch (side)
- {
- case QCP::msLeft: return atLeft;
- case QCP::msRight: return atRight;
- case QCP::msTop: return atTop;
- case QCP::msBottom: return atBottom;
- default: break;
- }
- qDebug() << Q_FUNC_INFO << "Invalid margin side passed:" << (int)side;
- return atLeft;
- }
- /*!
- Returns the axis type that describes the opposite axis of an axis with the specified \a type.
- */
- QCPAxis::AxisType QCPAxis::opposite(QCPAxis::AxisType type)
- {
- switch (type)
- {
- case atLeft: return atRight; break;
- case atRight: return atLeft; break;
- case atBottom: return atTop; break;
- case atTop: return atBottom; break;
- default: qDebug() << Q_FUNC_INFO << "invalid axis type"; return atLeft; break;
- }
- }
- /* inherits documentation from base class */
- void QCPAxis::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
- {
- Q_UNUSED(event)
- SelectablePart part = details.value<SelectablePart>();
- if (mSelectableParts.testFlag(part))
- {
- SelectableParts selBefore = mSelectedParts;
- setSelectedParts(additive ? mSelectedParts^part : part);
- if (selectionStateChanged)
- *selectionStateChanged = mSelectedParts != selBefore;
- }
- }
- /* inherits documentation from base class */
- void QCPAxis::deselectEvent(bool *selectionStateChanged)
- {
- SelectableParts selBefore = mSelectedParts;
- setSelectedParts(mSelectedParts & ~mSelectableParts);
- if (selectionStateChanged)
- *selectionStateChanged = mSelectedParts != selBefore;
- }
- /*! \internal
-
- This mouse event reimplementation provides the functionality to let the user drag individual axes
- exclusively, by startig the drag on top of the axis.
- For the axis to accept this event and perform the single axis drag, the parent \ref QCPAxisRect
- must be configured accordingly, i.e. it must allow range dragging in the orientation of this axis
- (\ref QCPAxisRect::setRangeDrag) and this axis must be a draggable axis (\ref
- QCPAxisRect::setRangeDragAxes)
-
- \seebaseclassmethod
-
- \note The dragging of possibly multiple axes at once by starting the drag anywhere in the axis
- rect is handled by the axis rect's mouse event, e.g. \ref QCPAxisRect::mousePressEvent.
- */
- void QCPAxis::mousePressEvent(QMouseEvent *event, const QVariant &details)
- {
- Q_UNUSED(details)
- if (!mParentPlot->interactions().testFlag(QCP::iRangeDrag) ||
- !mAxisRect->rangeDrag().testFlag(orientation()) ||
- !mAxisRect->rangeDragAxes(orientation()).contains(this))
- {
- event->ignore();
- return;
- }
-
- if (event->buttons() & Qt::LeftButton)
- {
- mDragging = true;
- // initialize antialiasing backup in case we start dragging:
- if (mParentPlot->noAntialiasingOnDrag())
- {
- mAADragBackup = mParentPlot->antialiasedElements();
- mNotAADragBackup = mParentPlot->notAntialiasedElements();
- }
- // Mouse range dragging interaction:
- if (mParentPlot->interactions().testFlag(QCP::iRangeDrag))
- mDragStartRange = mRange;
- }
- }
- /*! \internal
-
- This mouse event reimplementation provides the functionality to let the user drag individual axes
- exclusively, by startig the drag on top of the axis.
-
- \seebaseclassmethod
-
- \note The dragging of possibly multiple axes at once by starting the drag anywhere in the axis
- rect is handled by the axis rect's mouse event, e.g. \ref QCPAxisRect::mousePressEvent.
-
- \see QCPAxis::mousePressEvent
- */
- void QCPAxis::mouseMoveEvent(QMouseEvent *event, const QPointF &startPos)
- {
- if (mDragging)
- {
- const double startPixel = orientation() == Qt::Horizontal ? startPos.x() : startPos.y();
- const double currentPixel = orientation() == Qt::Horizontal ? event->pos().x() : event->pos().y();
- if (mScaleType == QCPAxis::stLinear)
- {
- const double diff = pixelToCoord(startPixel) - pixelToCoord(currentPixel);
- setRange(mDragStartRange.lower+diff, mDragStartRange.upper+diff);
- } else if (mScaleType == QCPAxis::stLogarithmic)
- {
- const double diff = pixelToCoord(startPixel) / pixelToCoord(currentPixel);
- setRange(mDragStartRange.lower*diff, mDragStartRange.upper*diff);
- }
-
- if (mParentPlot->noAntialiasingOnDrag())
- mParentPlot->setNotAntialiasedElements(QCP::aeAll);
- mParentPlot->replot(QCustomPlot::rpQueuedReplot);
- }
- }
- /*! \internal
-
- This mouse event reimplementation provides the functionality to let the user drag individual axes
- exclusively, by startig the drag on top of the axis.
-
- \seebaseclassmethod
-
- \note The dragging of possibly multiple axes at once by starting the drag anywhere in the axis
- rect is handled by the axis rect's mouse event, e.g. \ref QCPAxisRect::mousePressEvent.
-
- \see QCPAxis::mousePressEvent
- */
- void QCPAxis::mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos)
- {
- Q_UNUSED(event)
- Q_UNUSED(startPos)
- mDragging = false;
- if (mParentPlot->noAntialiasingOnDrag())
- {
- mParentPlot->setAntialiasedElements(mAADragBackup);
- mParentPlot->setNotAntialiasedElements(mNotAADragBackup);
- }
- }
- /*! \internal
-
- This mouse event reimplementation provides the functionality to let the user zoom individual axes
- exclusively, by performing the wheel event on top of the axis.
- For the axis to accept this event and perform the single axis zoom, the parent \ref QCPAxisRect
- must be configured accordingly, i.e. it must allow range zooming in the orientation of this axis
- (\ref QCPAxisRect::setRangeZoom) and this axis must be a zoomable axis (\ref
- QCPAxisRect::setRangeZoomAxes)
-
- \seebaseclassmethod
-
- \note The zooming of possibly multiple axes at once by performing the wheel event anywhere in the
- axis rect is handled by the axis rect's mouse event, e.g. \ref QCPAxisRect::wheelEvent.
- */
- void QCPAxis::wheelEvent(QWheelEvent *event)
- {
- // Mouse range zooming interaction:
- if (!mParentPlot->interactions().testFlag(QCP::iRangeZoom) ||
- !mAxisRect->rangeZoom().testFlag(orientation()) ||
- !mAxisRect->rangeZoomAxes(orientation()).contains(this))
- {
- event->ignore();
- return;
- }
-
- const double wheelSteps = event->delta()/120.0; // a single step delta is +/-120 usually
- const double factor = qPow(mAxisRect->rangeZoomFactor(orientation()), wheelSteps);
- scaleRange(factor, pixelToCoord(orientation() == Qt::Horizontal ? event->pos().x() : event->pos().y()));
- mParentPlot->replot();
- }
- /*! \internal
- A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
- before drawing axis lines.
- This is the antialiasing state the painter passed to the \ref draw method is in by default.
-
- This function takes into account the local setting of the antialiasing flag as well as the
- overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
- QCustomPlot::setNotAntialiasedElements.
-
- \seebaseclassmethod
-
- \see setAntialiased
- */
- void QCPAxis::applyDefaultAntialiasingHint(QCPPainter *painter) const
- {
- applyAntialiasingHint(painter, mAntialiased, QCP::aeAxes);
- }
- /*! \internal
-
- Draws the axis with the specified \a painter, using the internal QCPAxisPainterPrivate instance.
- \seebaseclassmethod
- */
- void QCPAxis::draw(QCPPainter *painter)
- {
- QVector<double> subTickPositions; // the final coordToPixel transformed vector passed to QCPAxisPainter
- QVector<double> tickPositions; // the final coordToPixel transformed vector passed to QCPAxisPainter
- QVector<QString> tickLabels; // the final vector passed to QCPAxisPainter
- tickPositions.reserve(mTickVector.size());
- tickLabels.reserve(mTickVector.size());
- subTickPositions.reserve(mSubTickVector.size());
-
- if (mTicks)
- {
- for (int i=0; i<mTickVector.size(); ++i)
- {
- tickPositions.append(coordToPixel(mTickVector.at(i)));
- if (mTickLabels)
- tickLabels.append(mTickVectorLabels.at(i));
- }
- if (mSubTicks)
- {
- const int subTickCount = mSubTickVector.size();
- for (int i=0; i<subTickCount; ++i)
- subTickPositions.append(coordToPixel(mSubTickVector.at(i)));
- }
- }
-
- // transfer all properties of this axis to QCPAxisPainterPrivate which it needs to draw the axis.
- // Note that some axis painter properties are already set by direct feed-through with QCPAxis setters
- mAxisPainter->type = mAxisType;
- mAxisPainter->basePen = getBasePen();
- mAxisPainter->labelFont = getLabelFont();
- mAxisPainter->labelColor = getLabelColor();
- mAxisPainter->label = mLabel;
- mAxisPainter->substituteExponent = mNumberBeautifulPowers;
- mAxisPainter->tickPen = getTickPen();
- mAxisPainter->subTickPen = getSubTickPen();
- mAxisPainter->tickLabelFont = getTickLabelFont();
- mAxisPainter->tickLabelColor = getTickLabelColor();
- mAxisPainter->axisRect = mAxisRect->rect();
- mAxisPainter->viewportRect = mParentPlot->viewport();
- mAxisPainter->abbreviateDecimalPowers = mScaleType == stLogarithmic;
- mAxisPainter->reversedEndings = mRangeReversed;
- mAxisPainter->tickPositions = tickPositions;
- mAxisPainter->tickLabels = tickLabels;
- mAxisPainter->subTickPositions = subTickPositions;
- mAxisPainter->draw(painter);
- }
- /*! \internal
-
- Prepares the internal tick vector, sub tick vector and tick label vector. This is done by calling
- QCPAxisTicker::generate on the currently installed ticker.
-
- If a change in the label text/count is detected, the cached axis margin is invalidated to make
- sure the next margin calculation recalculates the label sizes and returns an up-to-date value.
- */
- void QCPAxis::setupTickVectors()
- {
- if (!mParentPlot) return;
- if ((!mTicks && !mTickLabels && !mGrid->visible()) || mRange.size() <= 0) return;
-
- QVector<QString> oldLabels = mTickVectorLabels;
- mTicker->generate(mRange, mParentPlot->locale(), mNumberFormatChar, mNumberPrecision, mTickVector, mSubTicks ? &mSubTickVector : 0, mTickLabels ? &mTickVectorLabels : 0);
- mCachedMarginValid &= mTickVectorLabels == oldLabels; // if labels have changed, margin might have changed, too
- }
- /*! \internal
-
- Returns the pen that is used to draw the axis base line. Depending on the selection state, this
- is either mSelectedBasePen or mBasePen.
- */
- QPen QCPAxis::getBasePen() const
- {
- return mSelectedParts.testFlag(spAxis) ? mSelectedBasePen : mBasePen;
- }
- /*! \internal
-
- Returns the pen that is used to draw the (major) ticks. Depending on the selection state, this
- is either mSelectedTickPen or mTickPen.
- */
- QPen QCPAxis::getTickPen() const
- {
- return mSelectedParts.testFlag(spAxis) ? mSelectedTickPen : mTickPen;
- }
- /*! \internal
-
- Returns the pen that is used to draw the subticks. Depending on the selection state, this
- is either mSelectedSubTickPen or mSubTickPen.
- */
- QPen QCPAxis::getSubTickPen() const
- {
- return mSelectedParts.testFlag(spAxis) ? mSelectedSubTickPen : mSubTickPen;
- }
- /*! \internal
-
- Returns the font that is used to draw the tick labels. Depending on the selection state, this
- is either mSelectedTickLabelFont or mTickLabelFont.
- */
- QFont QCPAxis::getTickLabelFont() const
- {
- return mSelectedParts.testFlag(spTickLabels) ? mSelectedTickLabelFont : mTickLabelFont;
- }
- /*! \internal
-
- Returns the font that is used to draw the axis label. Depending on the selection state, this
- is either mSelectedLabelFont or mLabelFont.
- */
- QFont QCPAxis::getLabelFont() const
- {
- return mSelectedParts.testFlag(spAxisLabel) ? mSelectedLabelFont : mLabelFont;
- }
- /*! \internal
-
- Returns the color that is used to draw the tick labels. Depending on the selection state, this
- is either mSelectedTickLabelColor or mTickLabelColor.
- */
- QColor QCPAxis::getTickLabelColor() const
- {
- return mSelectedParts.testFlag(spTickLabels) ? mSelectedTickLabelColor : mTickLabelColor;
- }
- /*! \internal
-
- Returns the color that is used to draw the axis label. Depending on the selection state, this
- is either mSelectedLabelColor or mLabelColor.
- */
- QColor QCPAxis::getLabelColor() const
- {
- return mSelectedParts.testFlag(spAxisLabel) ? mSelectedLabelColor : mLabelColor;
- }
- /*! \internal
-
- Returns the appropriate outward margin for this axis. It is needed if \ref
- QCPAxisRect::setAutoMargins is set to true on the parent axis rect. An axis with axis type \ref
- atLeft will return an appropriate left margin, \ref atBottom will return an appropriate bottom
- margin and so forth. For the calculation, this function goes through similar steps as \ref draw,
- so changing one function likely requires the modification of the other one as well.
-
- The margin consists of the outward tick length, tick label padding, tick label size, label
- padding, label size, and padding.
-
- The margin is cached internally, so repeated calls while leaving the axis range, fonts, etc.
- unchanged are very fast.
- */
- int QCPAxis::calculateMargin()
- {
- if (!mVisible) // if not visible, directly return 0, don't cache 0 because we can't react to setVisible in QCPAxis
- return 0;
-
- if (mCachedMarginValid)
- return mCachedMargin;
-
- // run through similar steps as QCPAxis::draw, and calculate margin needed to fit axis and its labels
- int margin = 0;
-
- QVector<double> tickPositions; // the final coordToPixel transformed vector passed to QCPAxisPainter
- QVector<QString> tickLabels; // the final vector passed to QCPAxisPainter
- tickPositions.reserve(mTickVector.size());
- tickLabels.reserve(mTickVector.size());
-
- if (mTicks)
- {
- for (int i=0; i<mTickVector.size(); ++i)
- {
- tickPositions.append(coordToPixel(mTickVector.at(i)));
- if (mTickLabels)
- tickLabels.append(mTickVectorLabels.at(i));
- }
- }
- // transfer all properties of this axis to QCPAxisPainterPrivate which it needs to calculate the size.
- // Note that some axis painter properties are already set by direct feed-through with QCPAxis setters
- mAxisPainter->type = mAxisType;
- mAxisPainter->labelFont = getLabelFont();
- mAxisPainter->label = mLabel;
- mAxisPainter->tickLabelFont = mTickLabelFont;
- mAxisPainter->axisRect = mAxisRect->rect();
- mAxisPainter->viewportRect = mParentPlot->viewport();
- mAxisPainter->tickPositions = tickPositions;
- mAxisPainter->tickLabels = tickLabels;
- margin += mAxisPainter->size();
- margin += mPadding;
- mCachedMargin = margin;
- mCachedMarginValid = true;
- return margin;
- }
- /* inherits documentation from base class */
- QCP::Interaction QCPAxis::selectionCategory() const
- {
- return QCP::iSelectAxes;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPAxisPainterPrivate
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPAxisPainterPrivate
- \internal
- \brief (Private)
-
- This is a private class and not part of the public QCustomPlot interface.
-
- It is used by QCPAxis to do the low-level drawing of axis backbone, tick marks, tick labels and
- axis label. It also buffers the labels to reduce replot times. The parameters are configured by
- directly accessing the public member variables.
- */
- /*!
- Constructs a QCPAxisPainterPrivate instance. Make sure to not create a new instance on every
- redraw, to utilize the caching mechanisms.
- */
- QCPAxisPainterPrivate::QCPAxisPainterPrivate(QCustomPlot *parentPlot) :
- type(QCPAxis::atLeft),
- basePen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
- lowerEnding(QCPLineEnding::esNone),
- upperEnding(QCPLineEnding::esNone),
- labelPadding(0),
- tickLabelPadding(0),
- tickLabelRotation(0),
- tickLabelSide(QCPAxis::lsOutside),
- substituteExponent(true),
- numberMultiplyCross(false),
- tickLengthIn(5),
- tickLengthOut(0),
- subTickLengthIn(2),
- subTickLengthOut(0),
- tickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
- subTickPen(QPen(Qt::black, 0, Qt::SolidLine, Qt::SquareCap)),
- offset(0),
- abbreviateDecimalPowers(false),
- reversedEndings(false),
- mParentPlot(parentPlot),
- mLabelCache(16) // cache at most 16 (tick) labels
- {
- }
- QCPAxisPainterPrivate::~QCPAxisPainterPrivate()
- {
- }
- /*! \internal
-
- Draws the axis with the specified \a painter.
-
- The selection boxes (mAxisSelectionBox, mTickLabelsSelectionBox, mLabelSelectionBox) are set
- here, too.
- */
- void QCPAxisPainterPrivate::draw(QCPPainter *painter)
- {
- QByteArray newHash = generateLabelParameterHash();
- if (newHash != mLabelParameterHash)
- {
- mLabelCache.clear();
- mLabelParameterHash = newHash;
- }
-
- QPoint origin;
- switch (type)
- {
- case QCPAxis::atLeft: origin = axisRect.bottomLeft() +QPoint(-offset, 0); break;
- case QCPAxis::atRight: origin = axisRect.bottomRight()+QPoint(+offset, 0); break;
- case QCPAxis::atTop: origin = axisRect.topLeft() +QPoint(0, -offset); break;
- case QCPAxis::atBottom: origin = axisRect.bottomLeft() +QPoint(0, +offset); break;
- }
- double xCor = 0, yCor = 0; // paint system correction, for pixel exact matches (affects baselines and ticks of top/right axes)
- switch (type)
- {
- case QCPAxis::atTop: yCor = -1; break;
- case QCPAxis::atRight: xCor = 1; break;
- default: break;
- }
- int margin = 0;
- // draw baseline:
- QLineF baseLine;
- painter->setPen(basePen);
- if (QCPAxis::orientation(type) == Qt::Horizontal)
- baseLine.setPoints(origin+QPointF(xCor, yCor), origin+QPointF(axisRect.width()+xCor, yCor));
- else
- baseLine.setPoints(origin+QPointF(xCor, yCor), origin+QPointF(xCor, -axisRect.height()+yCor));
- if (reversedEndings)
- baseLine = QLineF(baseLine.p2(), baseLine.p1()); // won't make a difference for line itself, but for line endings later
- painter->drawLine(baseLine);
-
- // draw ticks:
- if (!tickPositions.isEmpty())
- {
- painter->setPen(tickPen);
- int tickDir = (type == QCPAxis::atBottom || type == QCPAxis::atRight) ? -1 : 1; // direction of ticks ("inward" is right for left axis and left for right axis)
- if (QCPAxis::orientation(type) == Qt::Horizontal)
- {
- for (int i=0; i<tickPositions.size(); ++i)
- painter->drawLine(QLineF(tickPositions.at(i)+xCor, origin.y()-tickLengthOut*tickDir+yCor, tickPositions.at(i)+xCor, origin.y()+tickLengthIn*tickDir+yCor));
- } else
- {
- for (int i=0; i<tickPositions.size(); ++i)
- painter->drawLine(QLineF(origin.x()-tickLengthOut*tickDir+xCor, tickPositions.at(i)+yCor, origin.x()+tickLengthIn*tickDir+xCor, tickPositions.at(i)+yCor));
- }
- }
-
- // draw subticks:
- if (!subTickPositions.isEmpty())
- {
- painter->setPen(subTickPen);
- // direction of ticks ("inward" is right for left axis and left for right axis)
- int tickDir = (type == QCPAxis::atBottom || type == QCPAxis::atRight) ? -1 : 1;
- if (QCPAxis::orientation(type) == Qt::Horizontal)
- {
- for (int i=0; i<subTickPositions.size(); ++i)
- painter->drawLine(QLineF(subTickPositions.at(i)+xCor, origin.y()-subTickLengthOut*tickDir+yCor, subTickPositions.at(i)+xCor, origin.y()+subTickLengthIn*tickDir+yCor));
- } else
- {
- for (int i=0; i<subTickPositions.size(); ++i)
- painter->drawLine(QLineF(origin.x()-subTickLengthOut*tickDir+xCor, subTickPositions.at(i)+yCor, origin.x()+subTickLengthIn*tickDir+xCor, subTickPositions.at(i)+yCor));
- }
- }
- margin += qMax(0, qMax(tickLengthOut, subTickLengthOut));
-
- // draw axis base endings:
- bool antialiasingBackup = painter->antialiasing();
- painter->setAntialiasing(true); // always want endings to be antialiased, even if base and ticks themselves aren't
- painter->setBrush(QBrush(basePen.color()));
- QCPVector2D baseLineVector(baseLine.dx(), baseLine.dy());
- if (lowerEnding.style() != QCPLineEnding::esNone)
- lowerEnding.draw(painter, QCPVector2D(baseLine.p1())-baseLineVector.normalized()*lowerEnding.realLength()*(lowerEnding.inverted()?-1:1), -baseLineVector);
- if (upperEnding.style() != QCPLineEnding::esNone)
- upperEnding.draw(painter, QCPVector2D(baseLine.p2())+baseLineVector.normalized()*upperEnding.realLength()*(upperEnding.inverted()?-1:1), baseLineVector);
- painter->setAntialiasing(antialiasingBackup);
-
- // tick labels:
- QRect oldClipRect;
- if (tickLabelSide == QCPAxis::lsInside) // if using inside labels, clip them to the axis rect
- {
- oldClipRect = painter->clipRegion().boundingRect();
- painter->setClipRect(axisRect);
- }
- QSize tickLabelsSize(0, 0); // size of largest tick label, for offset calculation of axis label
- if (!tickLabels.isEmpty())
- {
- if (tickLabelSide == QCPAxis::lsOutside)
- margin += tickLabelPadding;
- painter->setFont(tickLabelFont);
- painter->setPen(QPen(tickLabelColor));
- const int maxLabelIndex = qMin(tickPositions.size(), tickLabels.size());
- int distanceToAxis = margin;
- if (tickLabelSide == QCPAxis::lsInside)
- distanceToAxis = -(qMax(tickLengthIn, subTickLengthIn)+tickLabelPadding);
- for (int i=0; i<maxLabelIndex; ++i)
- placeTickLabel(painter, tickPositions.at(i), distanceToAxis, tickLabels.at(i), &tickLabelsSize);
- if (tickLabelSide == QCPAxis::lsOutside)
- margin += (QCPAxis::orientation(type) == Qt::Horizontal) ? tickLabelsSize.height() : tickLabelsSize.width();
- }
- if (tickLabelSide == QCPAxis::lsInside)
- painter->setClipRect(oldClipRect);
-
- // axis label:
- QRect labelBounds;
- if (!label.isEmpty())
- {
- margin += labelPadding;
- painter->setFont(labelFont);
- painter->setPen(QPen(labelColor));
- labelBounds = painter->fontMetrics().boundingRect(0, 0, 0, 0, Qt::TextDontClip, label);
- if (type == QCPAxis::atLeft)
- {
- QTransform oldTransform = painter->transform();
- painter->translate((origin.x()-margin-labelBounds.height()), origin.y());
- painter->rotate(-90);
- painter->drawText(0, 0, axisRect.height(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label);
- painter->setTransform(oldTransform);
- }
- else if (type == QCPAxis::atRight)
- {
- QTransform oldTransform = painter->transform();
- painter->translate((origin.x()+margin+labelBounds.height()), origin.y()-axisRect.height());
- painter->rotate(90);
- painter->drawText(0, 0, axisRect.height(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label);
- painter->setTransform(oldTransform);
- }
- else if (type == QCPAxis::atTop)
- painter->drawText(origin.x(), origin.y()-margin-labelBounds.height(), axisRect.width(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label);
- else if (type == QCPAxis::atBottom)
- painter->drawText(origin.x(), origin.y()+margin, axisRect.width(), labelBounds.height(), Qt::TextDontClip | Qt::AlignCenter, label);
- }
-
- // set selection boxes:
- int selectionTolerance = 0;
- if (mParentPlot)
- selectionTolerance = mParentPlot->selectionTolerance();
- else
- qDebug() << Q_FUNC_INFO << "mParentPlot is null";
- int selAxisOutSize = qMax(qMax(tickLengthOut, subTickLengthOut), selectionTolerance);
- int selAxisInSize = selectionTolerance;
- int selTickLabelSize;
- int selTickLabelOffset;
- if (tickLabelSide == QCPAxis::lsOutside)
- {
- selTickLabelSize = (QCPAxis::orientation(type) == Qt::Horizontal ? tickLabelsSize.height() : tickLabelsSize.width());
- selTickLabelOffset = qMax(tickLengthOut, subTickLengthOut)+tickLabelPadding;
- } else
- {
- selTickLabelSize = -(QCPAxis::orientation(type) == Qt::Horizontal ? tickLabelsSize.height() : tickLabelsSize.width());
- selTickLabelOffset = -(qMax(tickLengthIn, subTickLengthIn)+tickLabelPadding);
- }
- int selLabelSize = labelBounds.height();
- int selLabelOffset = qMax(tickLengthOut, subTickLengthOut)+(!tickLabels.isEmpty() && tickLabelSide == QCPAxis::lsOutside ? tickLabelPadding+selTickLabelSize : 0)+labelPadding;
- if (type == QCPAxis::atLeft)
- {
- mAxisSelectionBox.setCoords(origin.x()-selAxisOutSize, axisRect.top(), origin.x()+selAxisInSize, axisRect.bottom());
- mTickLabelsSelectionBox.setCoords(origin.x()-selTickLabelOffset-selTickLabelSize, axisRect.top(), origin.x()-selTickLabelOffset, axisRect.bottom());
- mLabelSelectionBox.setCoords(origin.x()-selLabelOffset-selLabelSize, axisRect.top(), origin.x()-selLabelOffset, axisRect.bottom());
- } else if (type == QCPAxis::atRight)
- {
- mAxisSelectionBox.setCoords(origin.x()-selAxisInSize, axisRect.top(), origin.x()+selAxisOutSize, axisRect.bottom());
- mTickLabelsSelectionBox.setCoords(origin.x()+selTickLabelOffset+selTickLabelSize, axisRect.top(), origin.x()+selTickLabelOffset, axisRect.bottom());
- mLabelSelectionBox.setCoords(origin.x()+selLabelOffset+selLabelSize, axisRect.top(), origin.x()+selLabelOffset, axisRect.bottom());
- } else if (type == QCPAxis::atTop)
- {
- mAxisSelectionBox.setCoords(axisRect.left(), origin.y()-selAxisOutSize, axisRect.right(), origin.y()+selAxisInSize);
- mTickLabelsSelectionBox.setCoords(axisRect.left(), origin.y()-selTickLabelOffset-selTickLabelSize, axisRect.right(), origin.y()-selTickLabelOffset);
- mLabelSelectionBox.setCoords(axisRect.left(), origin.y()-selLabelOffset-selLabelSize, axisRect.right(), origin.y()-selLabelOffset);
- } else if (type == QCPAxis::atBottom)
- {
- mAxisSelectionBox.setCoords(axisRect.left(), origin.y()-selAxisInSize, axisRect.right(), origin.y()+selAxisOutSize);
- mTickLabelsSelectionBox.setCoords(axisRect.left(), origin.y()+selTickLabelOffset+selTickLabelSize, axisRect.right(), origin.y()+selTickLabelOffset);
- mLabelSelectionBox.setCoords(axisRect.left(), origin.y()+selLabelOffset+selLabelSize, axisRect.right(), origin.y()+selLabelOffset);
- }
- mAxisSelectionBox = mAxisSelectionBox.normalized();
- mTickLabelsSelectionBox = mTickLabelsSelectionBox.normalized();
- mLabelSelectionBox = mLabelSelectionBox.normalized();
- // draw hitboxes for debug purposes:
- //painter->setBrush(Qt::NoBrush);
- //painter->drawRects(QVector<QRect>() << mAxisSelectionBox << mTickLabelsSelectionBox << mLabelSelectionBox);
- }
- /*! \internal
-
- Returns the size ("margin" in QCPAxisRect context, so measured perpendicular to the axis backbone
- direction) needed to fit the axis.
- */
- int QCPAxisPainterPrivate::size() const
- {
- int result = 0;
-
- // get length of tick marks pointing outwards:
- if (!tickPositions.isEmpty())
- result += qMax(0, qMax(tickLengthOut, subTickLengthOut));
-
- // calculate size of tick labels:
- if (tickLabelSide == QCPAxis::lsOutside)
- {
- QSize tickLabelsSize(0, 0);
- if (!tickLabels.isEmpty())
- {
- for (int i=0; i<tickLabels.size(); ++i)
- getMaxTickLabelSize(tickLabelFont, tickLabels.at(i), &tickLabelsSize);
- result += QCPAxis::orientation(type) == Qt::Horizontal ? tickLabelsSize.height() : tickLabelsSize.width();
- result += tickLabelPadding;
- }
- }
-
- // calculate size of axis label (only height needed, because left/right labels are rotated by 90 degrees):
- if (!label.isEmpty())
- {
- QFontMetrics fontMetrics(labelFont);
- QRect bounds;
- bounds = fontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip | Qt::AlignHCenter | Qt::AlignVCenter, label);
- result += bounds.height() + labelPadding;
- }
-
- return result;
- }
- /*! \internal
-
- Clears the internal label cache. Upon the next \ref draw, all labels will be created new. This
- method is called automatically in \ref draw, if any parameters have changed that invalidate the
- cached labels, such as font, color, etc.
- */
- void QCPAxisPainterPrivate::clearCache()
- {
- mLabelCache.clear();
- }
- /*! \internal
-
- Returns a hash that allows uniquely identifying whether the label parameters have changed such
- that the cached labels must be refreshed (\ref clearCache). It is used in \ref draw. If the
- return value of this method hasn't changed since the last redraw, the respective label parameters
- haven't changed and cached labels may be used.
- */
- QByteArray QCPAxisPainterPrivate::generateLabelParameterHash() const
- {
- QByteArray result;
- result.append(QByteArray::number(mParentPlot->bufferDevicePixelRatio()));
- result.append(QByteArray::number(tickLabelRotation));
- result.append(QByteArray::number((int)tickLabelSide));
- result.append(QByteArray::number((int)substituteExponent));
- result.append(QByteArray::number((int)numberMultiplyCross));
- result.append(tickLabelColor.name().toLatin1()+QByteArray::number(tickLabelColor.alpha(), 16));
- result.append(tickLabelFont.toString().toLatin1());
- return result;
- }
- /*! \internal
-
- Draws a single tick label with the provided \a painter, utilizing the internal label cache to
- significantly speed up drawing of labels that were drawn in previous calls. The tick label is
- always bound to an axis, the distance to the axis is controllable via \a distanceToAxis in
- pixels. The pixel position in the axis direction is passed in the \a position parameter. Hence
- for the bottom axis, \a position would indicate the horizontal pixel position (not coordinate),
- at which the label should be drawn.
-
- In order to later draw the axis label in a place that doesn't overlap with the tick labels, the
- largest tick label size is needed. This is acquired by passing a \a tickLabelsSize to the \ref
- drawTickLabel calls during the process of drawing all tick labels of one axis. In every call, \a
- tickLabelsSize is expanded, if the drawn label exceeds the value \a tickLabelsSize currently
- holds.
-
- The label is drawn with the font and pen that are currently set on the \a painter. To draw
- superscripted powers, the font is temporarily made smaller by a fixed factor (see \ref
- getTickLabelData).
- */
- void QCPAxisPainterPrivate::placeTickLabel(QCPPainter *painter, double position, int distanceToAxis, const QString &text, QSize *tickLabelsSize)
- {
- // warning: if you change anything here, also adapt getMaxTickLabelSize() accordingly!
- if (text.isEmpty()) return;
- QSize finalSize;
- QPointF labelAnchor;
- switch (type)
- {
- case QCPAxis::atLeft: labelAnchor = QPointF(axisRect.left()-distanceToAxis-offset, position); break;
- case QCPAxis::atRight: labelAnchor = QPointF(axisRect.right()+distanceToAxis+offset, position); break;
- case QCPAxis::atTop: labelAnchor = QPointF(position, axisRect.top()-distanceToAxis-offset); break;
- case QCPAxis::atBottom: labelAnchor = QPointF(position, axisRect.bottom()+distanceToAxis+offset); break;
- }
- if (mParentPlot->plottingHints().testFlag(QCP::phCacheLabels) && !painter->modes().testFlag(QCPPainter::pmNoCaching)) // label caching enabled
- {
- CachedLabel *cachedLabel = mLabelCache.take(text); // attempt to get label from cache
- if (!cachedLabel) // no cached label existed, create it
- {
- cachedLabel = new CachedLabel;
- TickLabelData labelData = getTickLabelData(painter->font(), text);
- cachedLabel->offset = getTickLabelDrawOffset(labelData)+labelData.rotatedTotalBounds.topLeft();
- if (!qFuzzyCompare(1.0, mParentPlot->bufferDevicePixelRatio()))
- {
- cachedLabel->pixmap = QPixmap(labelData.rotatedTotalBounds.size()*mParentPlot->bufferDevicePixelRatio());
- #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
- # ifdef QCP_DEVICEPIXELRATIO_FLOAT
- cachedLabel->pixmap.setDevicePixelRatio(mParentPlot->devicePixelRatioF());
- # else
- cachedLabel->pixmap.setDevicePixelRatio(mParentPlot->devicePixelRatio());
- # endif
- #endif
- } else
- cachedLabel->pixmap = QPixmap(labelData.rotatedTotalBounds.size());
- cachedLabel->pixmap.fill(Qt::transparent);
- QCPPainter cachePainter(&cachedLabel->pixmap);
- cachePainter.setPen(painter->pen());
- drawTickLabel(&cachePainter, -labelData.rotatedTotalBounds.topLeft().x(), -labelData.rotatedTotalBounds.topLeft().y(), labelData);
- }
- // if label would be partly clipped by widget border on sides, don't draw it (only for outside tick labels):
- bool labelClippedByBorder = false;
- if (tickLabelSide == QCPAxis::lsOutside)
- {
- if (QCPAxis::orientation(type) == Qt::Horizontal)
- labelClippedByBorder = labelAnchor.x()+cachedLabel->offset.x()+cachedLabel->pixmap.width()/mParentPlot->bufferDevicePixelRatio() > viewportRect.right() || labelAnchor.x()+cachedLabel->offset.x() < viewportRect.left();
- else
- labelClippedByBorder = labelAnchor.y()+cachedLabel->offset.y()+cachedLabel->pixmap.height()/mParentPlot->bufferDevicePixelRatio() > viewportRect.bottom() || labelAnchor.y()+cachedLabel->offset.y() < viewportRect.top();
- }
- if (!labelClippedByBorder)
- {
- painter->drawPixmap(labelAnchor+cachedLabel->offset, cachedLabel->pixmap);
- finalSize = cachedLabel->pixmap.size()/mParentPlot->bufferDevicePixelRatio();
- }
- mLabelCache.insert(text, cachedLabel); // return label to cache or insert for the first time if newly created
- } else // label caching disabled, draw text directly on surface:
- {
- TickLabelData labelData = getTickLabelData(painter->font(), text);
- QPointF finalPosition = labelAnchor + getTickLabelDrawOffset(labelData);
- // if label would be partly clipped by widget border on sides, don't draw it (only for outside tick labels):
- bool labelClippedByBorder = false;
- if (tickLabelSide == QCPAxis::lsOutside)
- {
- if (QCPAxis::orientation(type) == Qt::Horizontal)
- labelClippedByBorder = finalPosition.x()+(labelData.rotatedTotalBounds.width()+labelData.rotatedTotalBounds.left()) > viewportRect.right() || finalPosition.x()+labelData.rotatedTotalBounds.left() < viewportRect.left();
- else
- labelClippedByBorder = finalPosition.y()+(labelData.rotatedTotalBounds.height()+labelData.rotatedTotalBounds.top()) > viewportRect.bottom() || finalPosition.y()+labelData.rotatedTotalBounds.top() < viewportRect.top();
- }
- if (!labelClippedByBorder)
- {
- drawTickLabel(painter, finalPosition.x(), finalPosition.y(), labelData);
- finalSize = labelData.rotatedTotalBounds.size();
- }
- }
-
- // expand passed tickLabelsSize if current tick label is larger:
- if (finalSize.width() > tickLabelsSize->width())
- tickLabelsSize->setWidth(finalSize.width());
- if (finalSize.height() > tickLabelsSize->height())
- tickLabelsSize->setHeight(finalSize.height());
- }
- /*! \internal
-
- This is a \ref placeTickLabel helper function.
-
- Draws the tick label specified in \a labelData with \a painter at the pixel positions \a x and \a
- y. This function is used by \ref placeTickLabel to create new tick labels for the cache, or to
- directly draw the labels on the QCustomPlot surface when label caching is disabled, i.e. when
- QCP::phCacheLabels plotting hint is not set.
- */
- void QCPAxisPainterPrivate::drawTickLabel(QCPPainter *painter, double x, double y, const TickLabelData &labelData) const
- {
- // backup painter settings that we're about to change:
- QTransform oldTransform = painter->transform();
- QFont oldFont = painter->font();
-
- // transform painter to position/rotation:
- painter->translate(x, y);
- if (!qFuzzyIsNull(tickLabelRotation))
- painter->rotate(tickLabelRotation);
-
- // draw text:
- if (!labelData.expPart.isEmpty()) // indicator that beautiful powers must be used
- {
- painter->setFont(labelData.baseFont);
- painter->drawText(0, 0, 0, 0, Qt::TextDontClip, labelData.basePart);
- if (!labelData.suffixPart.isEmpty())
- painter->drawText(labelData.baseBounds.width()+1+labelData.expBounds.width(), 0, 0, 0, Qt::TextDontClip, labelData.suffixPart);
- painter->setFont(labelData.expFont);
- painter->drawText(labelData.baseBounds.width()+1, 0, labelData.expBounds.width(), labelData.expBounds.height(), Qt::TextDontClip, labelData.expPart);
- } else
- {
- painter->setFont(labelData.baseFont);
- painter->drawText(0, 0, labelData.totalBounds.width(), labelData.totalBounds.height(), Qt::TextDontClip | Qt::AlignHCenter, labelData.basePart);
- }
-
- // reset painter settings to what it was before:
- painter->setTransform(oldTransform);
- painter->setFont(oldFont);
- }
- /*! \internal
-
- This is a \ref placeTickLabel helper function.
-
- Transforms the passed \a text and \a font to a tickLabelData structure that can then be further
- processed by \ref getTickLabelDrawOffset and \ref drawTickLabel. It splits the text into base and
- exponent if necessary (member substituteExponent) and calculates appropriate bounding boxes.
- */
- QCPAxisPainterPrivate::TickLabelData QCPAxisPainterPrivate::getTickLabelData(const QFont &font, const QString &text) const
- {
- TickLabelData result;
-
- // determine whether beautiful decimal powers should be used
- bool useBeautifulPowers = false;
- int ePos = -1; // first index of exponent part, text before that will be basePart, text until eLast will be expPart
- int eLast = -1; // last index of exponent part, rest of text after this will be suffixPart
- if (substituteExponent)
- {
- ePos = text.indexOf(QLatin1Char('e'));
- if (ePos > 0 && text.at(ePos-1).isDigit())
- {
- eLast = ePos;
- while (eLast+1 < text.size() && (text.at(eLast+1) == QLatin1Char('+') || text.at(eLast+1) == QLatin1Char('-') || text.at(eLast+1).isDigit()))
- ++eLast;
- if (eLast > ePos) // only if also to right of 'e' is a digit/+/- interpret it as beautifiable power
- useBeautifulPowers = true;
- }
- }
-
- // calculate text bounding rects and do string preparation for beautiful decimal powers:
- result.baseFont = font;
- if (result.baseFont.pointSizeF() > 0) // might return -1 if specified with setPixelSize, in that case we can't do correction in next line
- result.baseFont.setPointSizeF(result.baseFont.pointSizeF()+0.05); // QFontMetrics.boundingRect has a bug for exact point sizes that make the results oscillate due to internal rounding
- if (useBeautifulPowers)
- {
- // split text into parts of number/symbol that will be drawn normally and part that will be drawn as exponent:
- result.basePart = text.left(ePos);
- result.suffixPart = text.mid(eLast+1); // also drawn normally but after exponent
- // in log scaling, we want to turn "1*10^n" into "10^n", else add multiplication sign and decimal base:
- if (abbreviateDecimalPowers && result.basePart == QLatin1String("1"))
- result.basePart = QLatin1String("10");
- else
- result.basePart += (numberMultiplyCross ? QString(QChar(215)) : QString(QChar(183))) + QLatin1String("10");
- result.expPart = text.mid(ePos+1, eLast-ePos);
- // clip "+" and leading zeros off expPart:
- while (result.expPart.length() > 2 && result.expPart.at(1) == QLatin1Char('0')) // length > 2 so we leave one zero when numberFormatChar is 'e'
- result.expPart.remove(1, 1);
- if (!result.expPart.isEmpty() && result.expPart.at(0) == QLatin1Char('+'))
- result.expPart.remove(0, 1);
- // prepare smaller font for exponent:
- result.expFont = font;
- if (result.expFont.pointSize() > 0)
- result.expFont.setPointSize(result.expFont.pointSize()*0.75);
- else
- result.expFont.setPixelSize(result.expFont.pixelSize()*0.75);
- // calculate bounding rects of base part(s), exponent part and total one:
- result.baseBounds = QFontMetrics(result.baseFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.basePart);
- result.expBounds = QFontMetrics(result.expFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.expPart);
- if (!result.suffixPart.isEmpty())
- result.suffixBounds = QFontMetrics(result.baseFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip, result.suffixPart);
- result.totalBounds = result.baseBounds.adjusted(0, 0, result.expBounds.width()+result.suffixBounds.width()+2, 0); // +2 consists of the 1 pixel spacing between base and exponent (see drawTickLabel) and an extra pixel to include AA
- } else // useBeautifulPowers == false
- {
- result.basePart = text;
- result.totalBounds = QFontMetrics(result.baseFont).boundingRect(0, 0, 0, 0, Qt::TextDontClip | Qt::AlignHCenter, result.basePart);
- }
- result.totalBounds.moveTopLeft(QPoint(0, 0)); // want bounding box aligned top left at origin, independent of how it was created, to make further processing simpler
-
- // calculate possibly different bounding rect after rotation:
- result.rotatedTotalBounds = result.totalBounds;
- if (!qFuzzyIsNull(tickLabelRotation))
- {
- QTransform transform;
- transform.rotate(tickLabelRotation);
- result.rotatedTotalBounds = transform.mapRect(result.rotatedTotalBounds);
- }
-
- return result;
- }
- /*! \internal
-
- This is a \ref placeTickLabel helper function.
-
- Calculates the offset at which the top left corner of the specified tick label shall be drawn.
- The offset is relative to a point right next to the tick the label belongs to.
-
- This function is thus responsible for e.g. centering tick labels under ticks and positioning them
- appropriately when they are rotated.
- */
- QPointF QCPAxisPainterPrivate::getTickLabelDrawOffset(const TickLabelData &labelData) const
- {
- /*
- calculate label offset from base point at tick (non-trivial, for best visual appearance): short
- explanation for bottom axis: The anchor, i.e. the point in the label that is placed
- horizontally under the corresponding tick is always on the label side that is closer to the
- axis (e.g. the left side of the text when we're rotating clockwise). On that side, the height
- is halved and the resulting point is defined the anchor. This way, a 90 degree rotated text
- will be centered under the tick (i.e. displaced horizontally by half its height). At the same
- time, a 45 degree rotated text will "point toward" its tick, as is typical for rotated tick
- labels.
- */
- bool doRotation = !qFuzzyIsNull(tickLabelRotation);
- bool flip = qFuzzyCompare(qAbs(tickLabelRotation), 90.0); // perfect +/-90 degree flip. Indicates vertical label centering on vertical axes.
- double radians = tickLabelRotation/180.0*M_PI;
- int x=0, y=0;
- if ((type == QCPAxis::atLeft && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atRight && tickLabelSide == QCPAxis::lsInside)) // Anchor at right side of tick label
- {
- if (doRotation)
- {
- if (tickLabelRotation > 0)
- {
- x = -qCos(radians)*labelData.totalBounds.width();
- y = flip ? -labelData.totalBounds.width()/2.0 : -qSin(radians)*labelData.totalBounds.width()-qCos(radians)*labelData.totalBounds.height()/2.0;
- } else
- {
- x = -qCos(-radians)*labelData.totalBounds.width()-qSin(-radians)*labelData.totalBounds.height();
- y = flip ? +labelData.totalBounds.width()/2.0 : +qSin(-radians)*labelData.totalBounds.width()-qCos(-radians)*labelData.totalBounds.height()/2.0;
- }
- } else
- {
- x = -labelData.totalBounds.width();
- y = -labelData.totalBounds.height()/2.0;
- }
- } else if ((type == QCPAxis::atRight && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atLeft && tickLabelSide == QCPAxis::lsInside)) // Anchor at left side of tick label
- {
- if (doRotation)
- {
- if (tickLabelRotation > 0)
- {
- x = +qSin(radians)*labelData.totalBounds.height();
- y = flip ? -labelData.totalBounds.width()/2.0 : -qCos(radians)*labelData.totalBounds.height()/2.0;
- } else
- {
- x = 0;
- y = flip ? +labelData.totalBounds.width()/2.0 : -qCos(-radians)*labelData.totalBounds.height()/2.0;
- }
- } else
- {
- x = 0;
- y = -labelData.totalBounds.height()/2.0;
- }
- } else if ((type == QCPAxis::atTop && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atBottom && tickLabelSide == QCPAxis::lsInside)) // Anchor at bottom side of tick label
- {
- if (doRotation)
- {
- if (tickLabelRotation > 0)
- {
- x = -qCos(radians)*labelData.totalBounds.width()+qSin(radians)*labelData.totalBounds.height()/2.0;
- y = -qSin(radians)*labelData.totalBounds.width()-qCos(radians)*labelData.totalBounds.height();
- } else
- {
- x = -qSin(-radians)*labelData.totalBounds.height()/2.0;
- y = -qCos(-radians)*labelData.totalBounds.height();
- }
- } else
- {
- x = -labelData.totalBounds.width()/2.0;
- y = -labelData.totalBounds.height();
- }
- } else if ((type == QCPAxis::atBottom && tickLabelSide == QCPAxis::lsOutside) || (type == QCPAxis::atTop && tickLabelSide == QCPAxis::lsInside)) // Anchor at top side of tick label
- {
- if (doRotation)
- {
- if (tickLabelRotation > 0)
- {
- x = +qSin(radians)*labelData.totalBounds.height()/2.0;
- y = 0;
- } else
- {
- x = -qCos(-radians)*labelData.totalBounds.width()-qSin(-radians)*labelData.totalBounds.height()/2.0;
- y = +qSin(-radians)*labelData.totalBounds.width();
- }
- } else
- {
- x = -labelData.totalBounds.width()/2.0;
- y = 0;
- }
- }
-
- return QPointF(x, y);
- }
- /*! \internal
-
- Simulates the steps done by \ref placeTickLabel by calculating bounding boxes of the text label
- to be drawn, depending on number format etc. Since only the largest tick label is wanted for the
- margin calculation, the passed \a tickLabelsSize is only expanded, if it's currently set to a
- smaller width/height.
- */
- void QCPAxisPainterPrivate::getMaxTickLabelSize(const QFont &font, const QString &text, QSize *tickLabelsSize) const
- {
- // note: this function must return the same tick label sizes as the placeTickLabel function.
- QSize finalSize;
- if (mParentPlot->plottingHints().testFlag(QCP::phCacheLabels) && mLabelCache.contains(text)) // label caching enabled and have cached label
- {
- const CachedLabel *cachedLabel = mLabelCache.object(text);
- finalSize = cachedLabel->pixmap.size()/mParentPlot->bufferDevicePixelRatio();
- } else // label caching disabled or no label with this text cached:
- {
- TickLabelData labelData = getTickLabelData(font, text);
- finalSize = labelData.rotatedTotalBounds.size();
- }
-
- // expand passed tickLabelsSize if current tick label is larger:
- if (finalSize.width() > tickLabelsSize->width())
- tickLabelsSize->setWidth(finalSize.width());
- if (finalSize.height() > tickLabelsSize->height())
- tickLabelsSize->setHeight(finalSize.height());
- }
- /* end of 'src/axis/axis.cpp' */
- /* including file 'src/scatterstyle.cpp', size 17450 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPScatterStyle
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPScatterStyle
- \brief Represents the visual appearance of scatter points
-
- This class holds information about shape, color and size of scatter points. In plottables like
- QCPGraph it is used to store how scatter points shall be drawn. For example, \ref
- QCPGraph::setScatterStyle takes a QCPScatterStyle instance.
-
- A scatter style consists of a shape (\ref setShape), a line color (\ref setPen) and possibly a
- fill (\ref setBrush), if the shape provides a fillable area. Further, the size of the shape can
- be controlled with \ref setSize.
- \section QCPScatterStyle-defining Specifying a scatter style
-
- You can set all these configurations either by calling the respective functions on an instance:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpscatterstyle-creation-1
-
- Or you can use one of the various constructors that take different parameter combinations, making
- it easy to specify a scatter style in a single call, like so:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpscatterstyle-creation-2
-
- \section QCPScatterStyle-undefinedpen Leaving the color/pen up to the plottable
-
- There are two constructors which leave the pen undefined: \ref QCPScatterStyle() and \ref
- QCPScatterStyle(ScatterShape shape, double size). If those constructors are used, a call to \ref
- isPenDefined will return false. It leads to scatter points that inherit the pen from the
- plottable that uses the scatter style. Thus, if such a scatter style is passed to QCPGraph, the line
- color of the graph (\ref QCPGraph::setPen) will be used by the scatter points. This makes
- it very convenient to set up typical scatter settings:
-
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpscatterstyle-shortcreation
- Notice that it wasn't even necessary to explicitly call a QCPScatterStyle constructor. This works
- because QCPScatterStyle provides a constructor that can transform a \ref ScatterShape directly
- into a QCPScatterStyle instance (that's the \ref QCPScatterStyle(ScatterShape shape, double size)
- constructor with a default for \a size). In those cases, C++ allows directly supplying a \ref
- ScatterShape, where actually a QCPScatterStyle is expected.
-
- \section QCPScatterStyle-custompath-and-pixmap Custom shapes and pixmaps
-
- QCPScatterStyle supports drawing custom shapes and arbitrary pixmaps as scatter points.
- For custom shapes, you can provide a QPainterPath with the desired shape to the \ref
- setCustomPath function or call the constructor that takes a painter path. The scatter shape will
- automatically be set to \ref ssCustom.
-
- For pixmaps, you call \ref setPixmap with the desired QPixmap. Alternatively you can use the
- constructor that takes a QPixmap. The scatter shape will automatically be set to \ref ssPixmap.
- Note that \ref setSize does not influence the appearance of the pixmap.
- */
- /* start documentation of inline functions */
- /*! \fn bool QCPScatterStyle::isNone() const
-
- Returns whether the scatter shape is \ref ssNone.
-
- \see setShape
- */
- /*! \fn bool QCPScatterStyle::isPenDefined() const
-
- Returns whether a pen has been defined for this scatter style.
-
- The pen is undefined if a constructor is called that does not carry \a pen as parameter. Those
- are \ref QCPScatterStyle() and \ref QCPScatterStyle(ScatterShape shape, double size). If the pen
- is undefined, the pen of the respective plottable will be used for drawing scatters.
-
- If a pen was defined for this scatter style instance, and you now wish to undefine the pen, call
- \ref undefinePen.
-
- \see setPen
- */
- /* end documentation of inline functions */
- /*!
- Creates a new QCPScatterStyle instance with size set to 6. No shape, pen or brush is defined.
-
- Since the pen is undefined (\ref isPenDefined returns false), the scatter color will be inherited
- from the plottable that uses this scatter style.
- */
- QCPScatterStyle::QCPScatterStyle() :
- mSize(6),
- mShape(ssNone),
- mPen(Qt::NoPen),
- mBrush(Qt::NoBrush),
- mPenDefined(false)
- {
- }
- /*!
- Creates a new QCPScatterStyle instance with shape set to \a shape and size to \a size. No pen or
- brush is defined.
-
- Since the pen is undefined (\ref isPenDefined returns false), the scatter color will be inherited
- from the plottable that uses this scatter style.
- */
- QCPScatterStyle::QCPScatterStyle(ScatterShape shape, double size) :
- mSize(size),
- mShape(shape),
- mPen(Qt::NoPen),
- mBrush(Qt::NoBrush),
- mPenDefined(false)
- {
- }
- /*!
- Creates a new QCPScatterStyle instance with shape set to \a shape, the pen color set to \a color,
- and size to \a size. No brush is defined, i.e. the scatter point will not be filled.
- */
- QCPScatterStyle::QCPScatterStyle(ScatterShape shape, const QColor &color, double size) :
- mSize(size),
- mShape(shape),
- mPen(QPen(color)),
- mBrush(Qt::NoBrush),
- mPenDefined(true)
- {
- }
- /*!
- Creates a new QCPScatterStyle instance with shape set to \a shape, the pen color set to \a color,
- the brush color to \a fill (with a solid pattern), and size to \a size.
- */
- QCPScatterStyle::QCPScatterStyle(ScatterShape shape, const QColor &color, const QColor &fill, double size) :
- mSize(size),
- mShape(shape),
- mPen(QPen(color)),
- mBrush(QBrush(fill)),
- mPenDefined(true)
- {
- }
- /*!
- Creates a new QCPScatterStyle instance with shape set to \a shape, the pen set to \a pen, the
- brush to \a brush, and size to \a size.
-
- \warning In some cases it might be tempting to directly use a pen style like <tt>Qt::NoPen</tt> as \a pen
- and a color like <tt>Qt::blue</tt> as \a brush. Notice however, that the corresponding call\n
- <tt>QCPScatterStyle(QCPScatterShape::ssCircle, Qt::NoPen, Qt::blue, 5)</tt>\n
- doesn't necessarily lead C++ to use this constructor in some cases, but might mistake
- <tt>Qt::NoPen</tt> for a QColor and use the
- \ref QCPScatterStyle(ScatterShape shape, const QColor &color, const QColor &fill, double size)
- constructor instead (which will lead to an unexpected look of the scatter points). To prevent
- this, be more explicit with the parameter types. For example, use <tt>QBrush(Qt::blue)</tt>
- instead of just <tt>Qt::blue</tt>, to clearly point out to the compiler that this constructor is
- wanted.
- */
- QCPScatterStyle::QCPScatterStyle(ScatterShape shape, const QPen &pen, const QBrush &brush, double size) :
- mSize(size),
- mShape(shape),
- mPen(pen),
- mBrush(brush),
- mPenDefined(pen.style() != Qt::NoPen)
- {
- }
- /*!
- Creates a new QCPScatterStyle instance which will show the specified \a pixmap. The scatter shape
- is set to \ref ssPixmap.
- */
- QCPScatterStyle::QCPScatterStyle(const QPixmap &pixmap) :
- mSize(5),
- mShape(ssPixmap),
- mPen(Qt::NoPen),
- mBrush(Qt::NoBrush),
- mPixmap(pixmap),
- mPenDefined(false)
- {
- }
- /*!
- Creates a new QCPScatterStyle instance with a custom shape that is defined via \a customPath. The
- scatter shape is set to \ref ssCustom.
-
- The custom shape line will be drawn with \a pen and filled with \a brush. The size has a slightly
- different meaning than for built-in scatter points: The custom path will be drawn scaled by a
- factor of \a size/6.0. Since the default \a size is 6, the custom path will appear in its
- original size by default. To for example double the size of the path, set \a size to 12.
- */
- QCPScatterStyle::QCPScatterStyle(const QPainterPath &customPath, const QPen &pen, const QBrush &brush, double size) :
- mSize(size),
- mShape(ssCustom),
- mPen(pen),
- mBrush(brush),
- mCustomPath(customPath),
- mPenDefined(pen.style() != Qt::NoPen)
- {
- }
- /*!
- Copies the specified \a properties from the \a other scatter style to this scatter style.
- */
- void QCPScatterStyle::setFromOther(const QCPScatterStyle &other, ScatterProperties properties)
- {
- if (properties.testFlag(spPen))
- {
- setPen(other.pen());
- if (!other.isPenDefined())
- undefinePen();
- }
- if (properties.testFlag(spBrush))
- setBrush(other.brush());
- if (properties.testFlag(spSize))
- setSize(other.size());
- if (properties.testFlag(spShape))
- {
- setShape(other.shape());
- if (other.shape() == ssPixmap)
- setPixmap(other.pixmap());
- else if (other.shape() == ssCustom)
- setCustomPath(other.customPath());
- }
- }
- /*!
- Sets the size (pixel diameter) of the drawn scatter points to \a size.
-
- \see setShape
- */
- void QCPScatterStyle::setSize(double size)
- {
- mSize = size;
- }
- /*!
- Sets the shape to \a shape.
-
- Note that the calls \ref setPixmap and \ref setCustomPath automatically set the shape to \ref
- ssPixmap and \ref ssCustom, respectively.
-
- \see setSize
- */
- void QCPScatterStyle::setShape(QCPScatterStyle::ScatterShape shape)
- {
- mShape = shape;
- }
- /*!
- Sets the pen that will be used to draw scatter points to \a pen.
-
- If the pen was previously undefined (see \ref isPenDefined), the pen is considered defined after
- a call to this function, even if \a pen is <tt>Qt::NoPen</tt>. If you have defined a pen
- previously by calling this function and now wish to undefine the pen, call \ref undefinePen.
-
- \see setBrush
- */
- void QCPScatterStyle::setPen(const QPen &pen)
- {
- mPenDefined = true;
- mPen = pen;
- }
- /*!
- Sets the brush that will be used to fill scatter points to \a brush. Note that not all scatter
- shapes have fillable areas. For example, \ref ssPlus does not while \ref ssCircle does.
-
- \see setPen
- */
- void QCPScatterStyle::setBrush(const QBrush &brush)
- {
- mBrush = brush;
- }
- /*!
- Sets the pixmap that will be drawn as scatter point to \a pixmap.
-
- Note that \ref setSize does not influence the appearance of the pixmap.
-
- The scatter shape is automatically set to \ref ssPixmap.
- */
- void QCPScatterStyle::setPixmap(const QPixmap &pixmap)
- {
- setShape(ssPixmap);
- mPixmap = pixmap;
- }
- /*!
- Sets the custom shape that will be drawn as scatter point to \a customPath.
-
- The scatter shape is automatically set to \ref ssCustom.
- */
- void QCPScatterStyle::setCustomPath(const QPainterPath &customPath)
- {
- setShape(ssCustom);
- mCustomPath = customPath;
- }
- /*!
- Sets this scatter style to have an undefined pen (see \ref isPenDefined for what an undefined pen
- implies).
- A call to \ref setPen will define a pen.
- */
- void QCPScatterStyle::undefinePen()
- {
- mPenDefined = false;
- }
- /*!
- Applies the pen and the brush of this scatter style to \a painter. If this scatter style has an
- undefined pen (\ref isPenDefined), sets the pen of \a painter to \a defaultPen instead.
-
- This function is used by plottables (or any class that wants to draw scatters) just before a
- number of scatters with this style shall be drawn with the \a painter.
-
- \see drawShape
- */
- void QCPScatterStyle::applyTo(QCPPainter *painter, const QPen &defaultPen) const
- {
- painter->setPen(mPenDefined ? mPen : defaultPen);
- painter->setBrush(mBrush);
- }
- /*!
- Draws the scatter shape with \a painter at position \a pos.
-
- This function does not modify the pen or the brush on the painter, as \ref applyTo is meant to be
- called before scatter points are drawn with \ref drawShape.
-
- \see applyTo
- */
- void QCPScatterStyle::drawShape(QCPPainter *painter, const QPointF &pos) const
- {
- drawShape(painter, pos.x(), pos.y());
- }
- /*! \overload
- Draws the scatter shape with \a painter at position \a x and \a y.
- */
- void QCPScatterStyle::drawShape(QCPPainter *painter, double x, double y) const
- {
- double w = mSize/2.0;
- switch (mShape)
- {
- case ssNone: break;
- case ssDot:
- {
- painter->drawLine(QPointF(x, y), QPointF(x+0.0001, y));
- break;
- }
- case ssCross:
- {
- painter->drawLine(QLineF(x-w, y-w, x+w, y+w));
- painter->drawLine(QLineF(x-w, y+w, x+w, y-w));
- break;
- }
- case ssPlus:
- {
- painter->drawLine(QLineF(x-w, y, x+w, y));
- painter->drawLine(QLineF( x, y+w, x, y-w));
- break;
- }
- case ssCircle:
- {
- painter->drawEllipse(QPointF(x , y), w, w);
- break;
- }
- case ssDisc:
- {
- QBrush b = painter->brush();
- painter->setBrush(painter->pen().color());
- painter->drawEllipse(QPointF(x , y), w, w);
- painter->setBrush(b);
- break;
- }
- case ssSquare:
- {
- painter->drawRect(QRectF(x-w, y-w, mSize, mSize));
- break;
- }
- case ssDiamond:
- {
- QPointF lineArray[4] = {QPointF(x-w, y),
- QPointF( x, y-w),
- QPointF(x+w, y),
- QPointF( x, y+w)};
- painter->drawPolygon(lineArray, 4);
- break;
- }
- case ssStar:
- {
- painter->drawLine(QLineF(x-w, y, x+w, y));
- painter->drawLine(QLineF( x, y+w, x, y-w));
- painter->drawLine(QLineF(x-w*0.707, y-w*0.707, x+w*0.707, y+w*0.707));
- painter->drawLine(QLineF(x-w*0.707, y+w*0.707, x+w*0.707, y-w*0.707));
- break;
- }
- case ssTriangle:
- {
- QPointF lineArray[3] = {QPointF(x-w, y+0.755*w),
- QPointF(x+w, y+0.755*w),
- QPointF( x, y-0.977*w)};
- painter->drawPolygon(lineArray, 3);
- break;
- }
- case ssTriangleInverted:
- {
- QPointF lineArray[3] = {QPointF(x-w, y-0.755*w),
- QPointF(x+w, y-0.755*w),
- QPointF( x, y+0.977*w)};
- painter->drawPolygon(lineArray, 3);
- break;
- }
- case ssCrossSquare:
- {
- painter->drawRect(QRectF(x-w, y-w, mSize, mSize));
- painter->drawLine(QLineF(x-w, y-w, x+w*0.95, y+w*0.95));
- painter->drawLine(QLineF(x-w, y+w*0.95, x+w*0.95, y-w));
- break;
- }
- case ssPlusSquare:
- {
- painter->drawRect(QRectF(x-w, y-w, mSize, mSize));
- painter->drawLine(QLineF(x-w, y, x+w*0.95, y));
- painter->drawLine(QLineF( x, y+w, x, y-w));
- break;
- }
- case ssCrossCircle:
- {
- painter->drawEllipse(QPointF(x, y), w, w);
- painter->drawLine(QLineF(x-w*0.707, y-w*0.707, x+w*0.670, y+w*0.670));
- painter->drawLine(QLineF(x-w*0.707, y+w*0.670, x+w*0.670, y-w*0.707));
- break;
- }
- case ssPlusCircle:
- {
- painter->drawEllipse(QPointF(x, y), w, w);
- painter->drawLine(QLineF(x-w, y, x+w, y));
- painter->drawLine(QLineF( x, y+w, x, y-w));
- break;
- }
- case ssPeace:
- {
- painter->drawEllipse(QPointF(x, y), w, w);
- painter->drawLine(QLineF(x, y-w, x, y+w));
- painter->drawLine(QLineF(x, y, x-w*0.707, y+w*0.707));
- painter->drawLine(QLineF(x, y, x+w*0.707, y+w*0.707));
- break;
- }
- case ssPixmap:
- {
- const double widthHalf = mPixmap.width()*0.5;
- const double heightHalf = mPixmap.height()*0.5;
- #if QT_VERSION < QT_VERSION_CHECK(4, 8, 0)
- const QRectF clipRect = painter->clipRegion().boundingRect().adjusted(-widthHalf, -heightHalf, widthHalf, heightHalf);
- #else
- const QRectF clipRect = painter->clipBoundingRect().adjusted(-widthHalf, -heightHalf, widthHalf, heightHalf);
- #endif
- if (clipRect.contains(x, y))
- painter->drawPixmap(x-widthHalf, y-heightHalf, mPixmap);
- break;
- }
- case ssCustom:
- {
- QTransform oldTransform = painter->transform();
- painter->translate(x, y);
- painter->scale(mSize/6.0, mSize/6.0);
- painter->drawPath(mCustomPath);
- painter->setTransform(oldTransform);
- break;
- }
- }
- }
- /* end of 'src/scatterstyle.cpp' */
- //amalgamation: add datacontainer.cpp
- /* including file 'src/plottable.cpp', size 38845 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPSelectionDecorator
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPSelectionDecorator
- \brief Controls how a plottable's data selection is drawn
-
- Each \ref QCPAbstractPlottable instance has one \ref QCPSelectionDecorator (accessible via \ref
- QCPAbstractPlottable::selectionDecorator) and uses it when drawing selected segments of its data.
-
- The selection decorator controls both pen (\ref setPen) and brush (\ref setBrush), as well as the
- scatter style (\ref setScatterStyle) if the plottable draws scatters. Since a \ref
- QCPScatterStyle is itself composed of different properties such as color shape and size, the
- decorator allows specifying exactly which of those properties shall be used for the selected data
- point, via \ref setUsedScatterProperties.
-
- A \ref QCPSelectionDecorator subclass instance can be passed to a plottable via \ref
- QCPAbstractPlottable::setSelectionDecorator, allowing greater customizability of the appearance
- of selected segments.
-
- Use \ref copyFrom to easily transfer the settings of one decorator to another one. This is
- especially useful since plottables take ownership of the passed selection decorator, and thus the
- same decorator instance can not be passed to multiple plottables.
-
- Selection decorators can also themselves perform drawing operations by reimplementing \ref
- drawDecoration, which is called by the plottable's draw method. The base class \ref
- QCPSelectionDecorator does not make use of this however. For example, \ref
- QCPSelectionDecoratorBracket draws brackets around selected data segments.
- */
- /*!
- Creates a new QCPSelectionDecorator instance with default values
- */
- QCPSelectionDecorator::QCPSelectionDecorator() :
- mPen(QColor(80, 80, 255), 2.5),
- mBrush(Qt::NoBrush),
- mScatterStyle(),
- mUsedScatterProperties(QCPScatterStyle::spNone),
- mPlottable(0)
- {
- }
- QCPSelectionDecorator::~QCPSelectionDecorator()
- {
- }
- /*!
- Sets the pen that will be used by the parent plottable to draw selected data segments.
- */
- void QCPSelectionDecorator::setPen(const QPen &pen)
- {
- mPen = pen;
- }
- /*!
- Sets the brush that will be used by the parent plottable to draw selected data segments.
- */
- void QCPSelectionDecorator::setBrush(const QBrush &brush)
- {
- mBrush = brush;
- }
- /*!
- Sets the scatter style that will be used by the parent plottable to draw scatters in selected
- data segments.
-
- \a usedProperties specifies which parts of the passed \a scatterStyle will be used by the
- plottable. The used properties can also be changed via \ref setUsedScatterProperties.
- */
- void QCPSelectionDecorator::setScatterStyle(const QCPScatterStyle &scatterStyle, QCPScatterStyle::ScatterProperties usedProperties)
- {
- mScatterStyle = scatterStyle;
- setUsedScatterProperties(usedProperties);
- }
- /*!
- Use this method to define which properties of the scatter style (set via \ref setScatterStyle)
- will be used for selected data segments. All properties of the scatter style that are not
- specified in \a properties will remain as specified in the plottable's original scatter style.
-
- \see QCPScatterStyle::ScatterProperty
- */
- void QCPSelectionDecorator::setUsedScatterProperties(const QCPScatterStyle::ScatterProperties &properties)
- {
- mUsedScatterProperties = properties;
- }
- /*!
- Sets the pen of \a painter to the pen of this selection decorator.
-
- \see applyBrush, getFinalScatterStyle
- */
- void QCPSelectionDecorator::applyPen(QCPPainter *painter) const
- {
- painter->setPen(mPen);
- }
- /*!
- Sets the brush of \a painter to the brush of this selection decorator.
-
- \see applyPen, getFinalScatterStyle
- */
- void QCPSelectionDecorator::applyBrush(QCPPainter *painter) const
- {
- painter->setBrush(mBrush);
- }
- /*!
- Returns the scatter style that the parent plottable shall use for selected scatter points. The
- plottable's original (unselected) scatter style must be passed as \a unselectedStyle. Depending
- on the setting of \ref setUsedScatterProperties, the returned scatter style is a mixture of this
- selecion decorator's scatter style (\ref setScatterStyle), and \a unselectedStyle.
-
- \see applyPen, applyBrush, setScatterStyle
- */
- QCPScatterStyle QCPSelectionDecorator::getFinalScatterStyle(const QCPScatterStyle &unselectedStyle) const
- {
- QCPScatterStyle result(unselectedStyle);
- result.setFromOther(mScatterStyle, mUsedScatterProperties);
-
- // if style shall inherit pen from plottable (has no own pen defined), give it the selected
- // plottable pen explicitly, so it doesn't use the unselected plottable pen when used in the
- // plottable:
- if (!result.isPenDefined())
- result.setPen(mPen);
-
- return result;
- }
- /*!
- Copies all properties (e.g. color, fill, scatter style) of the \a other selection decorator to
- this selection decorator.
- */
- void QCPSelectionDecorator::copyFrom(const QCPSelectionDecorator *other)
- {
- setPen(other->pen());
- setBrush(other->brush());
- setScatterStyle(other->scatterStyle(), other->usedScatterProperties());
- }
- /*!
- This method is called by all plottables' draw methods to allow custom selection decorations to be
- drawn. Use the passed \a painter to perform the drawing operations. \a selection carries the data
- selection for which the decoration shall be drawn.
-
- The default base class implementation of \ref QCPSelectionDecorator has no special decoration, so
- this method does nothing.
- */
- void QCPSelectionDecorator::drawDecoration(QCPPainter *painter, QCPDataSelection selection)
- {
- Q_UNUSED(painter)
- Q_UNUSED(selection)
- }
- /*! \internal
-
- This method is called as soon as a selection decorator is associated with a plottable, by a call
- to \ref QCPAbstractPlottable::setSelectionDecorator. This way the selection decorator can obtain a pointer to the plottable that uses it (e.g. to access
- data points via the \ref QCPAbstractPlottable::interface1D interface).
-
- If the selection decorator was already added to a different plottable before, this method aborts
- the registration and returns false.
- */
- bool QCPSelectionDecorator::registerWithPlottable(QCPAbstractPlottable *plottable)
- {
- if (!mPlottable)
- {
- mPlottable = plottable;
- return true;
- } else
- {
- qDebug() << Q_FUNC_INFO << "This selection decorator is already registered with plottable:" << reinterpret_cast<quintptr>(mPlottable);
- return false;
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPAbstractPlottable
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPAbstractPlottable
- \brief The abstract base class for all data representing objects in a plot.
- It defines a very basic interface like name, pen, brush, visibility etc. Since this class is
- abstract, it can't be instantiated. Use one of the subclasses or create a subclass yourself to
- create new ways of displaying data (see "Creating own plottables" below). Plottables that display
- one-dimensional data (i.e. data points have a single key dimension and one or multiple values at
- each key) are based off of the template subclass \ref QCPAbstractPlottable1D, see details
- there.
-
- All further specifics are in the subclasses, for example:
- \li A normal graph with possibly a line and/or scatter points \ref QCPGraph
- (typically created with \ref QCustomPlot::addGraph)
- \li A parametric curve: \ref QCPCurve
- \li A bar chart: \ref QCPBars
- \li A statistical box plot: \ref QCPStatisticalBox
- \li A color encoded two-dimensional map: \ref QCPColorMap
- \li An OHLC/Candlestick chart: \ref QCPFinancial
-
- \section plottables-subclassing Creating own plottables
-
- Subclassing directly from QCPAbstractPlottable is only recommended if you wish to display
- two-dimensional data like \ref QCPColorMap, i.e. two logical key dimensions and one (or more)
- data dimensions. If you want to display data with only one logical key dimension, you should
- rather derive from \ref QCPAbstractPlottable1D.
-
- If subclassing QCPAbstractPlottable directly, these are the pure virtual functions you must
- implement:
- \li \ref selectTest
- \li \ref draw
- \li \ref drawLegendIcon
- \li \ref getKeyRange
- \li \ref getValueRange
-
- See the documentation of those functions for what they need to do.
-
- For drawing your plot, you can use the \ref coordsToPixels functions to translate a point in plot
- coordinates to pixel coordinates. This function is quite convenient, because it takes the
- orientation of the key and value axes into account for you (x and y are swapped when the key axis
- is vertical and the value axis horizontal). If you are worried about performance (i.e. you need
- to translate many points in a loop like QCPGraph), you can directly use \ref
- QCPAxis::coordToPixel. However, you must then take care about the orientation of the axis
- yourself.
-
- Here are some important members you inherit from QCPAbstractPlottable:
- <table>
- <tr>
- <td>QCustomPlot *\b mParentPlot</td>
- <td>A pointer to the parent QCustomPlot instance. The parent plot is inferred from the axes that are passed in the constructor.</td>
- </tr><tr>
- <td>QString \b mName</td>
- <td>The name of the plottable.</td>
- </tr><tr>
- <td>QPen \b mPen</td>
- <td>The generic pen of the plottable. You should use this pen for the most prominent data representing lines in the plottable
- (e.g QCPGraph uses this pen for its graph lines and scatters)</td>
- </tr><tr>
- <td>QBrush \b mBrush</td>
- <td>The generic brush of the plottable. You should use this brush for the most prominent fillable structures in the plottable
- (e.g. QCPGraph uses this brush to control filling under the graph)</td>
- </tr><tr>
- <td>QPointer<\ref QCPAxis> \b mKeyAxis, \b mValueAxis</td>
- <td>The key and value axes this plottable is attached to. Call their QCPAxis::coordToPixel functions to translate coordinates
- to pixels in either the key or value dimension. Make sure to check whether the pointer is null before using it. If one of
- the axes is null, don't draw the plottable.</td>
- </tr><tr>
- <td>\ref QCPSelectionDecorator \b mSelectionDecorator</td>
- <td>The currently set selection decorator which specifies how selected data of the plottable shall be drawn and decorated.
- When drawing your data, you must consult this decorator for the appropriate pen/brush before drawing unselected/selected data segments.
- Finally, you should call its \ref QCPSelectionDecorator::drawDecoration method at the end of your \ref draw implementation.</td>
- </tr><tr>
- <td>\ref QCP::SelectionType \b mSelectable</td>
- <td>In which composition, if at all, this plottable's data may be selected. Enforcing this setting on the data selection is done
- by QCPAbstractPlottable automatically.</td>
- </tr><tr>
- <td>\ref QCPDataSelection \b mSelection</td>
- <td>Holds the current selection state of the plottable's data, i.e. the selected data ranges (\ref QCPDataRange).</td>
- </tr>
- </table>
- */
- /* start of documentation of inline functions */
- /*! \fn QCPSelectionDecorator *QCPAbstractPlottable::selectionDecorator() const
-
- Provides access to the selection decorator of this plottable. The selection decorator controls
- how selected data ranges are drawn (e.g. their pen color and fill), see \ref
- QCPSelectionDecorator for details.
-
- If you wish to use an own \ref QCPSelectionDecorator subclass, pass an instance of it to \ref
- setSelectionDecorator.
- */
- /*! \fn bool QCPAbstractPlottable::selected() const
-
- Returns true if there are any data points of the plottable currently selected. Use \ref selection
- to retrieve the current \ref QCPDataSelection.
- */
- /*! \fn QCPDataSelection QCPAbstractPlottable::selection() const
-
- Returns a \ref QCPDataSelection encompassing all the data points that are currently selected on
- this plottable.
-
- \see selected, setSelection, setSelectable
- */
- /*! \fn virtual QCPPlottableInterface1D *QCPAbstractPlottable::interface1D()
-
- If this plottable is a one-dimensional plottable, i.e. it implements the \ref
- QCPPlottableInterface1D, returns the \a this pointer with that type. Otherwise (e.g. in the case
- of a \ref QCPColorMap) returns zero.
-
- You can use this method to gain read access to data coordinates while holding a pointer to the
- abstract base class only.
- */
- /* end of documentation of inline functions */
- /* start of documentation of pure virtual functions */
- /*! \fn void QCPAbstractPlottable::drawLegendIcon(QCPPainter *painter, const QRect &rect) const = 0
- \internal
-
- called by QCPLegend::draw (via QCPPlottableLegendItem::draw) to create a graphical representation
- of this plottable inside \a rect, next to the plottable name.
-
- The passed \a painter has its cliprect set to \a rect, so painting outside of \a rect won't
- appear outside the legend icon border.
- */
- /*! \fn QCPRange QCPAbstractPlottable::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const = 0
-
- Returns the coordinate range that all data in this plottable span in the key axis dimension. For
- logarithmic plots, one can set \a inSignDomain to either \ref QCP::sdNegative or \ref
- QCP::sdPositive in order to restrict the returned range to that sign domain. E.g. when only
- negative range is wanted, set \a inSignDomain to \ref QCP::sdNegative and all positive points
- will be ignored for range calculation. For no restriction, just set \a inSignDomain to \ref
- QCP::sdBoth (default). \a foundRange is an output parameter that indicates whether a range could
- be found or not. If this is false, you shouldn't use the returned range (e.g. no points in data).
- Note that \a foundRange is not the same as \ref QCPRange::validRange, since the range returned by
- this function may have size zero (e.g. when there is only one data point). In this case \a
- foundRange would return true, but the returned range is not a valid range in terms of \ref
- QCPRange::validRange.
-
- \see rescaleAxes, getValueRange
- */
- /*! \fn QCPRange QCPAbstractPlottable::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const = 0
-
- Returns the coordinate range that the data points in the specified key range (\a inKeyRange) span
- in the value axis dimension. For logarithmic plots, one can set \a inSignDomain to either \ref
- QCP::sdNegative or \ref QCP::sdPositive in order to restrict the returned range to that sign
- domain. E.g. when only negative range is wanted, set \a inSignDomain to \ref QCP::sdNegative and
- all positive points will be ignored for range calculation. For no restriction, just set \a
- inSignDomain to \ref QCP::sdBoth (default). \a foundRange is an output parameter that indicates
- whether a range could be found or not. If this is false, you shouldn't use the returned range
- (e.g. no points in data).
-
- If \a inKeyRange has both lower and upper bound set to zero (is equal to <tt>QCPRange()</tt>),
- all data points are considered, without any restriction on the keys.
- Note that \a foundRange is not the same as \ref QCPRange::validRange, since the range returned by
- this function may have size zero (e.g. when there is only one data point). In this case \a
- foundRange would return true, but the returned range is not a valid range in terms of \ref
- QCPRange::validRange.
-
- \see rescaleAxes, getKeyRange
- */
- /* end of documentation of pure virtual functions */
- /* start of documentation of signals */
- /*! \fn void QCPAbstractPlottable::selectionChanged(bool selected)
-
- This signal is emitted when the selection state of this plottable has changed, either by user
- interaction or by a direct call to \ref setSelection. The parameter \a selected indicates whether
- there are any points selected or not.
-
- \see selectionChanged(const QCPDataSelection &selection)
- */
- /*! \fn void QCPAbstractPlottable::selectionChanged(const QCPDataSelection &selection)
-
- This signal is emitted when the selection state of this plottable has changed, either by user
- interaction or by a direct call to \ref setSelection. The parameter \a selection holds the
- currently selected data ranges.
-
- \see selectionChanged(bool selected)
- */
- /*! \fn void QCPAbstractPlottable::selectableChanged(QCP::SelectionType selectable);
-
- This signal is emitted when the selectability of this plottable has changed.
-
- \see setSelectable
- */
- /* end of documentation of signals */
- /*!
- Constructs an abstract plottable which uses \a keyAxis as its key axis ("x") and \a valueAxis as
- its value axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance
- and have perpendicular orientations. If either of these restrictions is violated, a corresponding
- message is printed to the debug output (qDebug), the construction is not aborted, though.
-
- Since QCPAbstractPlottable is an abstract class that defines the basic interface to plottables,
- it can't be directly instantiated.
-
- You probably want one of the subclasses like \ref QCPGraph or \ref QCPCurve instead.
- */
- QCPAbstractPlottable::QCPAbstractPlottable(QCPAxis *keyAxis, QCPAxis *valueAxis) :
- QCPLayerable(keyAxis->parentPlot(), QString(), keyAxis->axisRect()),
- mName(),
- mAntialiasedFill(true),
- mAntialiasedScatters(true),
- mPen(Qt::black),
- mBrush(Qt::NoBrush),
- mKeyAxis(keyAxis),
- mValueAxis(valueAxis),
- mSelectable(QCP::stWhole),
- mSelectionDecorator(0)
- {
- if (keyAxis->parentPlot() != valueAxis->parentPlot())
- qDebug() << Q_FUNC_INFO << "Parent plot of keyAxis is not the same as that of valueAxis.";
- if (keyAxis->orientation() == valueAxis->orientation())
- qDebug() << Q_FUNC_INFO << "keyAxis and valueAxis must be orthogonal to each other.";
-
- mParentPlot->registerPlottable(this);
- setSelectionDecorator(new QCPSelectionDecorator);
- }
- QCPAbstractPlottable::~QCPAbstractPlottable()
- {
- if (mSelectionDecorator)
- {
- delete mSelectionDecorator;
- mSelectionDecorator = 0;
- }
- }
- /*!
- The name is the textual representation of this plottable as it is displayed in the legend
- (\ref QCPLegend). It may contain any UTF-8 characters, including newlines.
- */
- void QCPAbstractPlottable::setName(const QString &name)
- {
- mName = name;
- }
- /*!
- Sets whether fills of this plottable are drawn antialiased or not.
-
- Note that this setting may be overridden by \ref QCustomPlot::setAntialiasedElements and \ref
- QCustomPlot::setNotAntialiasedElements.
- */
- void QCPAbstractPlottable::setAntialiasedFill(bool enabled)
- {
- mAntialiasedFill = enabled;
- }
- /*!
- Sets whether the scatter symbols of this plottable are drawn antialiased or not.
-
- Note that this setting may be overridden by \ref QCustomPlot::setAntialiasedElements and \ref
- QCustomPlot::setNotAntialiasedElements.
- */
- void QCPAbstractPlottable::setAntialiasedScatters(bool enabled)
- {
- mAntialiasedScatters = enabled;
- }
- /*!
- The pen is used to draw basic lines that make up the plottable representation in the
- plot.
-
- For example, the \ref QCPGraph subclass draws its graph lines with this pen.
- \see setBrush
- */
- void QCPAbstractPlottable::setPen(const QPen &pen)
- {
- mPen = pen;
- }
- /*!
- The brush is used to draw basic fills of the plottable representation in the
- plot. The Fill can be a color, gradient or texture, see the usage of QBrush.
-
- For example, the \ref QCPGraph subclass draws the fill under the graph with this brush, when
- it's not set to Qt::NoBrush.
- \see setPen
- */
- void QCPAbstractPlottable::setBrush(const QBrush &brush)
- {
- mBrush = brush;
- }
- /*!
- The key axis of a plottable can be set to any axis of a QCustomPlot, as long as it is orthogonal
- to the plottable's value axis. This function performs no checks to make sure this is the case.
- The typical mathematical choice is to use the x-axis (QCustomPlot::xAxis) as key axis and the
- y-axis (QCustomPlot::yAxis) as value axis.
-
- Normally, the key and value axes are set in the constructor of the plottable (or \ref
- QCustomPlot::addGraph when working with QCPGraphs through the dedicated graph interface).
- \see setValueAxis
- */
- void QCPAbstractPlottable::setKeyAxis(QCPAxis *axis)
- {
- mKeyAxis = axis;
- }
- /*!
- The value axis of a plottable can be set to any axis of a QCustomPlot, as long as it is
- orthogonal to the plottable's key axis. This function performs no checks to make sure this is the
- case. The typical mathematical choice is to use the x-axis (QCustomPlot::xAxis) as key axis and
- the y-axis (QCustomPlot::yAxis) as value axis.
- Normally, the key and value axes are set in the constructor of the plottable (or \ref
- QCustomPlot::addGraph when working with QCPGraphs through the dedicated graph interface).
-
- \see setKeyAxis
- */
- void QCPAbstractPlottable::setValueAxis(QCPAxis *axis)
- {
- mValueAxis = axis;
- }
- /*!
- Sets which data ranges of this plottable are selected. Selected data ranges are drawn differently
- (e.g. color) in the plot. This can be controlled via the selection decorator (see \ref
- selectionDecorator).
-
- The entire selection mechanism for plottables is handled automatically when \ref
- QCustomPlot::setInteractions contains iSelectPlottables. You only need to call this function when
- you wish to change the selection state programmatically.
-
- Using \ref setSelectable you can further specify for each plottable whether and to which
- granularity it is selectable. If \a selection is not compatible with the current \ref
- QCP::SelectionType set via \ref setSelectable, the resulting selection will be adjusted
- accordingly (see \ref QCPDataSelection::enforceType).
-
- emits the \ref selectionChanged signal when \a selected is different from the previous selection state.
-
- \see setSelectable, selectTest
- */
- void QCPAbstractPlottable::setSelection(QCPDataSelection selection)
- {
- selection.enforceType(mSelectable);
- if (mSelection != selection)
- {
- mSelection = selection;
- emit selectionChanged(selected());
- emit selectionChanged(mSelection);
- }
- }
- /*!
- Use this method to set an own QCPSelectionDecorator (subclass) instance. This allows you to
- customize the visual representation of selected data ranges further than by using the default
- QCPSelectionDecorator.
-
- The plottable takes ownership of the \a decorator.
-
- The currently set decorator can be accessed via \ref selectionDecorator.
- */
- void QCPAbstractPlottable::setSelectionDecorator(QCPSelectionDecorator *decorator)
- {
- if (decorator)
- {
- if (decorator->registerWithPlottable(this))
- {
- if (mSelectionDecorator) // delete old decorator if necessary
- delete mSelectionDecorator;
- mSelectionDecorator = decorator;
- }
- } else if (mSelectionDecorator) // just clear decorator
- {
- delete mSelectionDecorator;
- mSelectionDecorator = 0;
- }
- }
- /*!
- Sets whether and to which granularity this plottable can be selected.
- A selection can happen by clicking on the QCustomPlot surface (When \ref
- QCustomPlot::setInteractions contains \ref QCP::iSelectPlottables), by dragging a selection rect
- (When \ref QCustomPlot::setSelectionRectMode is \ref QCP::srmSelect), or programmatically by
- calling \ref setSelection.
-
- \see setSelection, QCP::SelectionType
- */
- void QCPAbstractPlottable::setSelectable(QCP::SelectionType selectable)
- {
- if (mSelectable != selectable)
- {
- mSelectable = selectable;
- QCPDataSelection oldSelection = mSelection;
- mSelection.enforceType(mSelectable);
- emit selectableChanged(mSelectable);
- if (mSelection != oldSelection)
- {
- emit selectionChanged(selected());
- emit selectionChanged(mSelection);
- }
- }
- }
- /*!
- Convenience function for transforming a key/value pair to pixels on the QCustomPlot surface,
- taking the orientations of the axes associated with this plottable into account (e.g. whether key
- represents x or y).
- \a key and \a value are transformed to the coodinates in pixels and are written to \a x and \a y.
- \see pixelsToCoords, QCPAxis::coordToPixel
- */
- void QCPAbstractPlottable::coordsToPixels(double key, double value, double &x, double &y) const
- {
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
-
- if (keyAxis->orientation() == Qt::Horizontal)
- {
- x = keyAxis->coordToPixel(key);
- y = valueAxis->coordToPixel(value);
- } else
- {
- y = keyAxis->coordToPixel(key);
- x = valueAxis->coordToPixel(value);
- }
- }
- /*! \overload
- Transforms the given \a key and \a value to pixel coordinates and returns them in a QPointF.
- */
- const QPointF QCPAbstractPlottable::coordsToPixels(double key, double value) const
- {
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPointF(); }
-
- if (keyAxis->orientation() == Qt::Horizontal)
- return QPointF(keyAxis->coordToPixel(key), valueAxis->coordToPixel(value));
- else
- return QPointF(valueAxis->coordToPixel(value), keyAxis->coordToPixel(key));
- }
- /*!
- Convenience function for transforming a x/y pixel pair on the QCustomPlot surface to plot coordinates,
- taking the orientations of the axes associated with this plottable into account (e.g. whether key
- represents x or y).
- \a x and \a y are transformed to the plot coodinates and are written to \a key and \a value.
- \see coordsToPixels, QCPAxis::coordToPixel
- */
- void QCPAbstractPlottable::pixelsToCoords(double x, double y, double &key, double &value) const
- {
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
-
- if (keyAxis->orientation() == Qt::Horizontal)
- {
- key = keyAxis->pixelToCoord(x);
- value = valueAxis->pixelToCoord(y);
- } else
- {
- key = keyAxis->pixelToCoord(y);
- value = valueAxis->pixelToCoord(x);
- }
- }
- /*! \overload
- Returns the pixel input \a pixelPos as plot coordinates \a key and \a value.
- */
- void QCPAbstractPlottable::pixelsToCoords(const QPointF &pixelPos, double &key, double &value) const
- {
- pixelsToCoords(pixelPos.x(), pixelPos.y(), key, value);
- }
- /*!
- Rescales the key and value axes associated with this plottable to contain all displayed data, so
- the whole plottable is visible. If the scaling of an axis is logarithmic, rescaleAxes will make
- sure not to rescale to an illegal range i.e. a range containing different signs and/or zero.
- Instead it will stay in the current sign domain and ignore all parts of the plottable that lie
- outside of that domain.
-
- \a onlyEnlarge makes sure the ranges are only expanded, never reduced. So it's possible to show
- multiple plottables in their entirety by multiple calls to rescaleAxes where the first call has
- \a onlyEnlarge set to false (the default), and all subsequent set to true.
-
- \see rescaleKeyAxis, rescaleValueAxis, QCustomPlot::rescaleAxes, QCPAxis::rescale
- */
- void QCPAbstractPlottable::rescaleAxes(bool onlyEnlarge) const
- {
- rescaleKeyAxis(onlyEnlarge);
- rescaleValueAxis(onlyEnlarge);
- }
- /*!
- Rescales the key axis of the plottable so the whole plottable is visible.
-
- See \ref rescaleAxes for detailed behaviour.
- */
- void QCPAbstractPlottable::rescaleKeyAxis(bool onlyEnlarge) const
- {
- QCPAxis *keyAxis = mKeyAxis.data();
- if (!keyAxis) { qDebug() << Q_FUNC_INFO << "invalid key axis"; return; }
-
- QCP::SignDomain signDomain = QCP::sdBoth;
- if (keyAxis->scaleType() == QCPAxis::stLogarithmic)
- signDomain = (keyAxis->range().upper < 0 ? QCP::sdNegative : QCP::sdPositive);
-
- bool foundRange;
- QCPRange newRange = getKeyRange(foundRange, signDomain);
- if (foundRange)
- {
- if (onlyEnlarge)
- newRange.expand(keyAxis->range());
- if (!QCPRange::validRange(newRange)) // likely due to range being zero (plottable has only constant data in this axis dimension), shift current range to at least center the plottable
- {
- double center = (newRange.lower+newRange.upper)*0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason
- if (keyAxis->scaleType() == QCPAxis::stLinear)
- {
- newRange.lower = center-keyAxis->range().size()/2.0;
- newRange.upper = center+keyAxis->range().size()/2.0;
- } else // scaleType() == stLogarithmic
- {
- newRange.lower = center/qSqrt(keyAxis->range().upper/keyAxis->range().lower);
- newRange.upper = center*qSqrt(keyAxis->range().upper/keyAxis->range().lower);
- }
- }
- keyAxis->setRange(newRange);
- }
- }
- /*!
- Rescales the value axis of the plottable so the whole plottable is visible. If \a inKeyRange is
- set to true, only the data points which are in the currently visible key axis range are
- considered.
- Returns true if the axis was actually scaled. This might not be the case if this plottable has an
- invalid range, e.g. because it has no data points.
- See \ref rescaleAxes for detailed behaviour.
- */
- void QCPAbstractPlottable::rescaleValueAxis(bool onlyEnlarge, bool inKeyRange) const
- {
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
-
- QCP::SignDomain signDomain = QCP::sdBoth;
- if (valueAxis->scaleType() == QCPAxis::stLogarithmic)
- signDomain = (valueAxis->range().upper < 0 ? QCP::sdNegative : QCP::sdPositive);
-
- bool foundRange;
- QCPRange newRange = getValueRange(foundRange, signDomain, inKeyRange ? keyAxis->range() : QCPRange());
- if (foundRange)
- {
- if (onlyEnlarge)
- newRange.expand(valueAxis->range());
- if (!QCPRange::validRange(newRange)) // likely due to range being zero (plottable has only constant data in this axis dimension), shift current range to at least center the plottable
- {
- double center = (newRange.lower+newRange.upper)*0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason
- if (valueAxis->scaleType() == QCPAxis::stLinear)
- {
- newRange.lower = center-valueAxis->range().size()/2.0;
- newRange.upper = center+valueAxis->range().size()/2.0;
- } else // scaleType() == stLogarithmic
- {
- newRange.lower = center/qSqrt(valueAxis->range().upper/valueAxis->range().lower);
- newRange.upper = center*qSqrt(valueAxis->range().upper/valueAxis->range().lower);
- }
- }
- valueAxis->setRange(newRange);
- }
- }
- /*! \overload
- Adds this plottable to the specified \a legend.
- Creates a QCPPlottableLegendItem which is inserted into the legend. Returns true on success, i.e.
- when the legend exists and a legend item associated with this plottable isn't already in the
- legend.
- If the plottable needs a more specialized representation in the legend, you can create a
- corresponding subclass of \ref QCPPlottableLegendItem and add it to the legend manually instead
- of calling this method.
- \see removeFromLegend, QCPLegend::addItem
- */
- bool QCPAbstractPlottable::addToLegend(QCPLegend *legend)
- {
- if (!legend)
- {
- qDebug() << Q_FUNC_INFO << "passed legend is null";
- return false;
- }
- if (legend->parentPlot() != mParentPlot)
- {
- qDebug() << Q_FUNC_INFO << "passed legend isn't in the same QCustomPlot as this plottable";
- return false;
- }
-
- if (!legend->hasItemWithPlottable(this))
- {
- legend->addItem(new QCPPlottableLegendItem(legend, this));
- return true;
- } else
- return false;
- }
- /*! \overload
- Adds this plottable to the legend of the parent QCustomPlot (\ref QCustomPlot::legend).
- \see removeFromLegend
- */
- bool QCPAbstractPlottable::addToLegend()
- {
- if (!mParentPlot || !mParentPlot->legend)
- return false;
- else
- return addToLegend(mParentPlot->legend);
- }
- /*! \overload
- Removes the plottable from the specifed \a legend. This means the \ref QCPPlottableLegendItem
- that is associated with this plottable is removed.
- Returns true on success, i.e. if the legend exists and a legend item associated with this
- plottable was found and removed.
- \see addToLegend, QCPLegend::removeItem
- */
- bool QCPAbstractPlottable::removeFromLegend(QCPLegend *legend) const
- {
- if (!legend)
- {
- qDebug() << Q_FUNC_INFO << "passed legend is null";
- return false;
- }
-
- if (QCPPlottableLegendItem *lip = legend->itemWithPlottable(this))
- return legend->removeItem(lip);
- else
- return false;
- }
- /*! \overload
- Removes the plottable from the legend of the parent QCustomPlot.
- \see addToLegend
- */
- bool QCPAbstractPlottable::removeFromLegend() const
- {
- if (!mParentPlot || !mParentPlot->legend)
- return false;
- else
- return removeFromLegend(mParentPlot->legend);
- }
- /* inherits documentation from base class */
- QRect QCPAbstractPlottable::clipRect() const
- {
- if (mKeyAxis && mValueAxis)
- return mKeyAxis.data()->axisRect()->rect() & mValueAxis.data()->axisRect()->rect();
- else
- return QRect();
- }
- /* inherits documentation from base class */
- QCP::Interaction QCPAbstractPlottable::selectionCategory() const
- {
- return QCP::iSelectPlottables;
- }
- /*! \internal
- A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
- before drawing plottable lines.
- This is the antialiasing state the painter passed to the \ref draw method is in by default.
-
- This function takes into account the local setting of the antialiasing flag as well as the
- overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
- QCustomPlot::setNotAntialiasedElements.
-
- \seebaseclassmethod
-
- \see setAntialiased, applyFillAntialiasingHint, applyScattersAntialiasingHint
- */
- void QCPAbstractPlottable::applyDefaultAntialiasingHint(QCPPainter *painter) const
- {
- applyAntialiasingHint(painter, mAntialiased, QCP::aePlottables);
- }
- /*! \internal
- A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
- before drawing plottable fills.
-
- This function takes into account the local setting of the antialiasing flag as well as the
- overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
- QCustomPlot::setNotAntialiasedElements.
-
- \see setAntialiased, applyDefaultAntialiasingHint, applyScattersAntialiasingHint
- */
- void QCPAbstractPlottable::applyFillAntialiasingHint(QCPPainter *painter) const
- {
- applyAntialiasingHint(painter, mAntialiasedFill, QCP::aeFills);
- }
- /*! \internal
- A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
- before drawing plottable scatter points.
-
- This function takes into account the local setting of the antialiasing flag as well as the
- overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
- QCustomPlot::setNotAntialiasedElements.
-
- \see setAntialiased, applyFillAntialiasingHint, applyDefaultAntialiasingHint
- */
- void QCPAbstractPlottable::applyScattersAntialiasingHint(QCPPainter *painter) const
- {
- applyAntialiasingHint(painter, mAntialiasedScatters, QCP::aeScatters);
- }
- /* inherits documentation from base class */
- void QCPAbstractPlottable::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
- {
- Q_UNUSED(event)
-
- if (mSelectable != QCP::stNone)
- {
- QCPDataSelection newSelection = details.value<QCPDataSelection>();
- QCPDataSelection selectionBefore = mSelection;
- if (additive)
- {
- if (mSelectable == QCP::stWhole) // in whole selection mode, we toggle to no selection even if currently unselected point was hit
- {
- if (selected())
- setSelection(QCPDataSelection());
- else
- setSelection(newSelection);
- } else // in all other selection modes we toggle selections of homogeneously selected/unselected segments
- {
- if (mSelection.contains(newSelection)) // if entire newSelection is already selected, toggle selection
- setSelection(mSelection-newSelection);
- else
- setSelection(mSelection+newSelection);
- }
- } else
- setSelection(newSelection);
- if (selectionStateChanged)
- *selectionStateChanged = mSelection != selectionBefore;
- }
- }
- /* inherits documentation from base class */
- void QCPAbstractPlottable::deselectEvent(bool *selectionStateChanged)
- {
- if (mSelectable != QCP::stNone)
- {
- QCPDataSelection selectionBefore = mSelection;
- setSelection(QCPDataSelection());
- if (selectionStateChanged)
- *selectionStateChanged = mSelection != selectionBefore;
- }
- }
- /* end of 'src/plottable.cpp' */
- /* including file 'src/item.cpp', size 49269 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPItemAnchor
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPItemAnchor
- \brief An anchor of an item to which positions can be attached to.
-
- An item (QCPAbstractItem) may have one or more anchors. Unlike QCPItemPosition, an anchor doesn't
- control anything on its item, but provides a way to tie other items via their positions to the
- anchor.
- For example, a QCPItemRect is defined by its positions \a topLeft and \a bottomRight.
- Additionally it has various anchors like \a top, \a topRight or \a bottomLeft etc. So you can
- attach the \a start (which is a QCPItemPosition) of a QCPItemLine to one of the anchors by
- calling QCPItemPosition::setParentAnchor on \a start, passing the wanted anchor of the
- QCPItemRect. This way the start of the line will now always follow the respective anchor location
- on the rect item.
-
- Note that QCPItemPosition derives from QCPItemAnchor, so every position can also serve as an
- anchor to other positions.
-
- To learn how to provide anchors in your own item subclasses, see the subclassing section of the
- QCPAbstractItem documentation.
- */
- /* start documentation of inline functions */
- /*! \fn virtual QCPItemPosition *QCPItemAnchor::toQCPItemPosition()
-
- Returns 0 if this instance is merely a QCPItemAnchor, and a valid pointer of type QCPItemPosition* if
- it actually is a QCPItemPosition (which is a subclass of QCPItemAnchor).
-
- This safe downcast functionality could also be achieved with a dynamic_cast. However, QCustomPlot avoids
- dynamic_cast to work with projects that don't have RTTI support enabled (e.g. -fno-rtti flag with
- gcc compiler).
- */
- /* end documentation of inline functions */
- /*!
- Creates a new QCPItemAnchor. You shouldn't create QCPItemAnchor instances directly, even if
- you want to make a new item subclass. Use \ref QCPAbstractItem::createAnchor instead, as
- explained in the subclassing section of the QCPAbstractItem documentation.
- */
- QCPItemAnchor::QCPItemAnchor(QCustomPlot *parentPlot, QCPAbstractItem *parentItem, const QString &name, int anchorId) :
- mName(name),
- mParentPlot(parentPlot),
- mParentItem(parentItem),
- mAnchorId(anchorId)
- {
- }
- QCPItemAnchor::~QCPItemAnchor()
- {
- // unregister as parent at children:
- foreach (QCPItemPosition *child, mChildrenX.toList())
- {
- if (child->parentAnchorX() == this)
- child->setParentAnchorX(0); // this acts back on this anchor and child removes itself from mChildrenX
- }
- foreach (QCPItemPosition *child, mChildrenY.toList())
- {
- if (child->parentAnchorY() == this)
- child->setParentAnchorY(0); // this acts back on this anchor and child removes itself from mChildrenY
- }
- }
- /*!
- Returns the final absolute pixel position of the QCPItemAnchor on the QCustomPlot surface.
-
- The pixel information is internally retrieved via QCPAbstractItem::anchorPixelPosition of the
- parent item, QCPItemAnchor is just an intermediary.
- */
- QPointF QCPItemAnchor::pixelPosition() const
- {
- if (mParentItem)
- {
- if (mAnchorId > -1)
- {
- return mParentItem->anchorPixelPosition(mAnchorId);
- } else
- {
- qDebug() << Q_FUNC_INFO << "no valid anchor id set:" << mAnchorId;
- return QPointF();
- }
- } else
- {
- qDebug() << Q_FUNC_INFO << "no parent item set";
- return QPointF();
- }
- }
- /*! \internal
- Adds \a pos to the childX list of this anchor, which keeps track of which children use this
- anchor as parent anchor for the respective coordinate. This is necessary to notify the children
- prior to destruction of the anchor.
-
- Note that this function does not change the parent setting in \a pos.
- */
- void QCPItemAnchor::addChildX(QCPItemPosition *pos)
- {
- if (!mChildrenX.contains(pos))
- mChildrenX.insert(pos);
- else
- qDebug() << Q_FUNC_INFO << "provided pos is child already" << reinterpret_cast<quintptr>(pos);
- }
- /*! \internal
- Removes \a pos from the childX list of this anchor.
-
- Note that this function does not change the parent setting in \a pos.
- */
- void QCPItemAnchor::removeChildX(QCPItemPosition *pos)
- {
- if (!mChildrenX.remove(pos))
- qDebug() << Q_FUNC_INFO << "provided pos isn't child" << reinterpret_cast<quintptr>(pos);
- }
- /*! \internal
- Adds \a pos to the childY list of this anchor, which keeps track of which children use this
- anchor as parent anchor for the respective coordinate. This is necessary to notify the children
- prior to destruction of the anchor.
-
- Note that this function does not change the parent setting in \a pos.
- */
- void QCPItemAnchor::addChildY(QCPItemPosition *pos)
- {
- if (!mChildrenY.contains(pos))
- mChildrenY.insert(pos);
- else
- qDebug() << Q_FUNC_INFO << "provided pos is child already" << reinterpret_cast<quintptr>(pos);
- }
- /*! \internal
- Removes \a pos from the childY list of this anchor.
-
- Note that this function does not change the parent setting in \a pos.
- */
- void QCPItemAnchor::removeChildY(QCPItemPosition *pos)
- {
- if (!mChildrenY.remove(pos))
- qDebug() << Q_FUNC_INFO << "provided pos isn't child" << reinterpret_cast<quintptr>(pos);
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPItemPosition
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPItemPosition
- \brief Manages the position of an item.
-
- Every item has at least one public QCPItemPosition member pointer which provides ways to position the
- item on the QCustomPlot surface. Some items have multiple positions, for example QCPItemRect has two:
- \a topLeft and \a bottomRight.
- QCPItemPosition has a type (\ref PositionType) that can be set with \ref setType. This type
- defines how coordinates passed to \ref setCoords are to be interpreted, e.g. as absolute pixel
- coordinates, as plot coordinates of certain axes, etc. For more advanced plots it is also
- possible to assign different types per X/Y coordinate of the position (see \ref setTypeX, \ref
- setTypeY). This way an item could be positioned at a fixed pixel distance from the top in the Y
- direction, while following a plot coordinate in the X direction.
- A QCPItemPosition may have a parent QCPItemAnchor, see \ref setParentAnchor. This way you can tie
- multiple items together. If the QCPItemPosition has a parent, its coordinates (\ref setCoords)
- are considered to be absolute pixels in the reference frame of the parent anchor, where (0, 0)
- means directly ontop of the parent anchor. For example, You could attach the \a start position of
- a QCPItemLine to the \a bottom anchor of a QCPItemText to make the starting point of the line
- always be centered under the text label, no matter where the text is moved to. For more advanced
- plots, it is possible to assign different parent anchors per X/Y coordinate of the position, see
- \ref setParentAnchorX, \ref setParentAnchorY. This way an item could follow another item in the X
- direction but stay at a fixed position in the Y direction. Or even follow item A in X, and item B
- in Y.
- Note that every QCPItemPosition inherits from QCPItemAnchor and thus can itself be used as parent
- anchor for other positions.
- To set the apparent pixel position on the QCustomPlot surface directly, use \ref setPixelPosition. This
- works no matter what type this QCPItemPosition is or what parent-child situation it is in, as \ref
- setPixelPosition transforms the coordinates appropriately, to make the position appear at the specified
- pixel values.
- */
- /* start documentation of inline functions */
- /*! \fn QCPItemPosition::PositionType *QCPItemPosition::type() const
-
- Returns the current position type.
-
- If different types were set for X and Y (\ref setTypeX, \ref setTypeY), this method returns the
- type of the X coordinate. In that case rather use \a typeX() and \a typeY().
-
- \see setType
- */
- /*! \fn QCPItemAnchor *QCPItemPosition::parentAnchor() const
-
- Returns the current parent anchor.
-
- If different parent anchors were set for X and Y (\ref setParentAnchorX, \ref setParentAnchorY),
- this method returns the parent anchor of the Y coordinate. In that case rather use \a
- parentAnchorX() and \a parentAnchorY().
-
- \see setParentAnchor
- */
- /* end documentation of inline functions */
- /*!
- Creates a new QCPItemPosition. You shouldn't create QCPItemPosition instances directly, even if
- you want to make a new item subclass. Use \ref QCPAbstractItem::createPosition instead, as
- explained in the subclassing section of the QCPAbstractItem documentation.
- */
- QCPItemPosition::QCPItemPosition(QCustomPlot *parentPlot, QCPAbstractItem *parentItem, const QString &name) :
- QCPItemAnchor(parentPlot, parentItem, name),
- mPositionTypeX(ptAbsolute),
- mPositionTypeY(ptAbsolute),
- mKey(0),
- mValue(0),
- mParentAnchorX(0),
- mParentAnchorY(0)
- {
- }
- QCPItemPosition::~QCPItemPosition()
- {
- // unregister as parent at children:
- // Note: this is done in ~QCPItemAnchor again, but it's important QCPItemPosition does it itself, because only then
- // the setParentAnchor(0) call the correct QCPItemPosition::pixelPosition function instead of QCPItemAnchor::pixelPosition
- foreach (QCPItemPosition *child, mChildrenX.toList())
- {
- if (child->parentAnchorX() == this)
- child->setParentAnchorX(0); // this acts back on this anchor and child removes itself from mChildrenX
- }
- foreach (QCPItemPosition *child, mChildrenY.toList())
- {
- if (child->parentAnchorY() == this)
- child->setParentAnchorY(0); // this acts back on this anchor and child removes itself from mChildrenY
- }
- // unregister as child in parent:
- if (mParentAnchorX)
- mParentAnchorX->removeChildX(this);
- if (mParentAnchorY)
- mParentAnchorY->removeChildY(this);
- }
- /* can't make this a header inline function, because QPointer breaks with forward declared types, see QTBUG-29588 */
- QCPAxisRect *QCPItemPosition::axisRect() const
- {
- return mAxisRect.data();
- }
- /*!
- Sets the type of the position. The type defines how the coordinates passed to \ref setCoords
- should be handled and how the QCPItemPosition should behave in the plot.
-
- The possible values for \a type can be separated in two main categories:
- \li The position is regarded as a point in plot coordinates. This corresponds to \ref ptPlotCoords
- and requires two axes that define the plot coordinate system. They can be specified with \ref setAxes.
- By default, the QCustomPlot's x- and yAxis are used.
-
- \li The position is fixed on the QCustomPlot surface, i.e. independent of axis ranges. This
- corresponds to all other types, i.e. \ref ptAbsolute, \ref ptViewportRatio and \ref
- ptAxisRectRatio. They differ only in the way the absolute position is described, see the
- documentation of \ref PositionType for details. For \ref ptAxisRectRatio, note that you can specify
- the axis rect with \ref setAxisRect. By default this is set to the main axis rect.
-
- Note that the position type \ref ptPlotCoords is only available (and sensible) when the position
- has no parent anchor (\ref setParentAnchor).
-
- If the type is changed, the apparent pixel position on the plot is preserved. This means
- the coordinates as retrieved with coords() and set with \ref setCoords may change in the process.
-
- This method sets the type for both X and Y directions. It is also possible to set different types
- for X and Y, see \ref setTypeX, \ref setTypeY.
- */
- void QCPItemPosition::setType(QCPItemPosition::PositionType type)
- {
- setTypeX(type);
- setTypeY(type);
- }
- /*!
- This method sets the position type of the X coordinate to \a type.
-
- For a detailed description of what a position type is, see the documentation of \ref setType.
-
- \see setType, setTypeY
- */
- void QCPItemPosition::setTypeX(QCPItemPosition::PositionType type)
- {
- if (mPositionTypeX != type)
- {
- // if switching from or to coordinate type that isn't valid (e.g. because axes or axis rect
- // were deleted), don't try to recover the pixelPosition() because it would output a qDebug warning.
- bool retainPixelPosition = true;
- if ((mPositionTypeX == ptPlotCoords || type == ptPlotCoords) && (!mKeyAxis || !mValueAxis))
- retainPixelPosition = false;
- if ((mPositionTypeX == ptAxisRectRatio || type == ptAxisRectRatio) && (!mAxisRect))
- retainPixelPosition = false;
-
- QPointF pixel;
- if (retainPixelPosition)
- pixel = pixelPosition();
-
- mPositionTypeX = type;
-
- if (retainPixelPosition)
- setPixelPosition(pixel);
- }
- }
- /*!
- This method sets the position type of the Y coordinate to \a type.
-
- For a detailed description of what a position type is, see the documentation of \ref setType.
-
- \see setType, setTypeX
- */
- void QCPItemPosition::setTypeY(QCPItemPosition::PositionType type)
- {
- if (mPositionTypeY != type)
- {
- // if switching from or to coordinate type that isn't valid (e.g. because axes or axis rect
- // were deleted), don't try to recover the pixelPosition() because it would output a qDebug warning.
- bool retainPixelPosition = true;
- if ((mPositionTypeY == ptPlotCoords || type == ptPlotCoords) && (!mKeyAxis || !mValueAxis))
- retainPixelPosition = false;
- if ((mPositionTypeY == ptAxisRectRatio || type == ptAxisRectRatio) && (!mAxisRect))
- retainPixelPosition = false;
-
- QPointF pixel;
- if (retainPixelPosition)
- pixel = pixelPosition();
-
- mPositionTypeY = type;
-
- if (retainPixelPosition)
- setPixelPosition(pixel);
- }
- }
- /*!
- Sets the parent of this QCPItemPosition to \a parentAnchor. This means the position will now
- follow any position changes of the anchor. The local coordinate system of positions with a parent
- anchor always is absolute pixels, with (0, 0) being exactly on top of the parent anchor. (Hence
- the type shouldn't be set to \ref ptPlotCoords for positions with parent anchors.)
-
- if \a keepPixelPosition is true, the current pixel position of the QCPItemPosition is preserved
- during reparenting. If it's set to false, the coordinates are set to (0, 0), i.e. the position
- will be exactly on top of the parent anchor.
-
- To remove this QCPItemPosition from any parent anchor, set \a parentAnchor to 0.
-
- If the QCPItemPosition previously had no parent and the type is \ref ptPlotCoords, the type is
- set to \ref ptAbsolute, to keep the position in a valid state.
-
- This method sets the parent anchor for both X and Y directions. It is also possible to set
- different parents for X and Y, see \ref setParentAnchorX, \ref setParentAnchorY.
- */
- bool QCPItemPosition::setParentAnchor(QCPItemAnchor *parentAnchor, bool keepPixelPosition)
- {
- bool successX = setParentAnchorX(parentAnchor, keepPixelPosition);
- bool successY = setParentAnchorY(parentAnchor, keepPixelPosition);
- return successX && successY;
- }
- /*!
- This method sets the parent anchor of the X coordinate to \a parentAnchor.
-
- For a detailed description of what a parent anchor is, see the documentation of \ref setParentAnchor.
-
- \see setParentAnchor, setParentAnchorY
- */
- bool QCPItemPosition::setParentAnchorX(QCPItemAnchor *parentAnchor, bool keepPixelPosition)
- {
- // make sure self is not assigned as parent:
- if (parentAnchor == this)
- {
- qDebug() << Q_FUNC_INFO << "can't set self as parent anchor" << reinterpret_cast<quintptr>(parentAnchor);
- return false;
- }
- // make sure no recursive parent-child-relationships are created:
- QCPItemAnchor *currentParent = parentAnchor;
- while (currentParent)
- {
- if (QCPItemPosition *currentParentPos = currentParent->toQCPItemPosition())
- {
- // is a QCPItemPosition, might have further parent, so keep iterating
- if (currentParentPos == this)
- {
- qDebug() << Q_FUNC_INFO << "can't create recursive parent-child-relationship" << reinterpret_cast<quintptr>(parentAnchor);
- return false;
- }
- currentParent = currentParentPos->parentAnchorX();
- } else
- {
- // is a QCPItemAnchor, can't have further parent. Now make sure the parent items aren't the
- // same, to prevent a position being child of an anchor which itself depends on the position,
- // because they're both on the same item:
- if (currentParent->mParentItem == mParentItem)
- {
- qDebug() << Q_FUNC_INFO << "can't set parent to be an anchor which itself depends on this position" << reinterpret_cast<quintptr>(parentAnchor);
- return false;
- }
- break;
- }
- }
-
- // if previously no parent set and PosType is still ptPlotCoords, set to ptAbsolute:
- if (!mParentAnchorX && mPositionTypeX == ptPlotCoords)
- setTypeX(ptAbsolute);
-
- // save pixel position:
- QPointF pixelP;
- if (keepPixelPosition)
- pixelP = pixelPosition();
- // unregister at current parent anchor:
- if (mParentAnchorX)
- mParentAnchorX->removeChildX(this);
- // register at new parent anchor:
- if (parentAnchor)
- parentAnchor->addChildX(this);
- mParentAnchorX = parentAnchor;
- // restore pixel position under new parent:
- if (keepPixelPosition)
- setPixelPosition(pixelP);
- else
- setCoords(0, coords().y());
- return true;
- }
- /*!
- This method sets the parent anchor of the Y coordinate to \a parentAnchor.
-
- For a detailed description of what a parent anchor is, see the documentation of \ref setParentAnchor.
-
- \see setParentAnchor, setParentAnchorX
- */
- bool QCPItemPosition::setParentAnchorY(QCPItemAnchor *parentAnchor, bool keepPixelPosition)
- {
- // make sure self is not assigned as parent:
- if (parentAnchor == this)
- {
- qDebug() << Q_FUNC_INFO << "can't set self as parent anchor" << reinterpret_cast<quintptr>(parentAnchor);
- return false;
- }
- // make sure no recursive parent-child-relationships are created:
- QCPItemAnchor *currentParent = parentAnchor;
- while (currentParent)
- {
- if (QCPItemPosition *currentParentPos = currentParent->toQCPItemPosition())
- {
- // is a QCPItemPosition, might have further parent, so keep iterating
- if (currentParentPos == this)
- {
- qDebug() << Q_FUNC_INFO << "can't create recursive parent-child-relationship" << reinterpret_cast<quintptr>(parentAnchor);
- return false;
- }
- currentParent = currentParentPos->parentAnchorY();
- } else
- {
- // is a QCPItemAnchor, can't have further parent. Now make sure the parent items aren't the
- // same, to prevent a position being child of an anchor which itself depends on the position,
- // because they're both on the same item:
- if (currentParent->mParentItem == mParentItem)
- {
- qDebug() << Q_FUNC_INFO << "can't set parent to be an anchor which itself depends on this position" << reinterpret_cast<quintptr>(parentAnchor);
- return false;
- }
- break;
- }
- }
-
- // if previously no parent set and PosType is still ptPlotCoords, set to ptAbsolute:
- if (!mParentAnchorY && mPositionTypeY == ptPlotCoords)
- setTypeY(ptAbsolute);
-
- // save pixel position:
- QPointF pixelP;
- if (keepPixelPosition)
- pixelP = pixelPosition();
- // unregister at current parent anchor:
- if (mParentAnchorY)
- mParentAnchorY->removeChildY(this);
- // register at new parent anchor:
- if (parentAnchor)
- parentAnchor->addChildY(this);
- mParentAnchorY = parentAnchor;
- // restore pixel position under new parent:
- if (keepPixelPosition)
- setPixelPosition(pixelP);
- else
- setCoords(coords().x(), 0);
- return true;
- }
- /*!
- Sets the coordinates of this QCPItemPosition. What the coordinates mean, is defined by the type
- (\ref setType, \ref setTypeX, \ref setTypeY).
-
- For example, if the type is \ref ptAbsolute, \a key and \a value mean the x and y pixel position
- on the QCustomPlot surface. In that case the origin (0, 0) is in the top left corner of the
- QCustomPlot viewport. If the type is \ref ptPlotCoords, \a key and \a value mean a point in the
- plot coordinate system defined by the axes set by \ref setAxes. By default those are the
- QCustomPlot's xAxis and yAxis. See the documentation of \ref setType for other available
- coordinate types and their meaning.
-
- If different types were configured for X and Y (\ref setTypeX, \ref setTypeY), \a key and \a
- value must also be provided in the different coordinate systems. Here, the X type refers to \a
- key, and the Y type refers to \a value.
- \see setPixelPosition
- */
- void QCPItemPosition::setCoords(double key, double value)
- {
- mKey = key;
- mValue = value;
- }
- /*! \overload
- Sets the coordinates as a QPointF \a pos where pos.x has the meaning of \a key and pos.y the
- meaning of \a value of the \ref setCoords(double key, double value) method.
- */
- void QCPItemPosition::setCoords(const QPointF &pos)
- {
- setCoords(pos.x(), pos.y());
- }
- /*!
- Returns the final absolute pixel position of the QCPItemPosition on the QCustomPlot surface. It
- includes all effects of type (\ref setType) and possible parent anchors (\ref setParentAnchor).
- \see setPixelPosition
- */
- QPointF QCPItemPosition::pixelPosition() const
- {
- QPointF result;
-
- // determine X:
- switch (mPositionTypeX)
- {
- case ptAbsolute:
- {
- result.rx() = mKey;
- if (mParentAnchorX)
- result.rx() += mParentAnchorX->pixelPosition().x();
- break;
- }
- case ptViewportRatio:
- {
- result.rx() = mKey*mParentPlot->viewport().width();
- if (mParentAnchorX)
- result.rx() += mParentAnchorX->pixelPosition().x();
- else
- result.rx() += mParentPlot->viewport().left();
- break;
- }
- case ptAxisRectRatio:
- {
- if (mAxisRect)
- {
- result.rx() = mKey*mAxisRect.data()->width();
- if (mParentAnchorX)
- result.rx() += mParentAnchorX->pixelPosition().x();
- else
- result.rx() += mAxisRect.data()->left();
- } else
- qDebug() << Q_FUNC_INFO << "Item position type x is ptAxisRectRatio, but no axis rect was defined";
- break;
- }
- case ptPlotCoords:
- {
- if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Horizontal)
- result.rx() = mKeyAxis.data()->coordToPixel(mKey);
- else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Horizontal)
- result.rx() = mValueAxis.data()->coordToPixel(mValue);
- else
- qDebug() << Q_FUNC_INFO << "Item position type x is ptPlotCoords, but no axes were defined";
- break;
- }
- }
-
- // determine Y:
- switch (mPositionTypeY)
- {
- case ptAbsolute:
- {
- result.ry() = mValue;
- if (mParentAnchorY)
- result.ry() += mParentAnchorY->pixelPosition().y();
- break;
- }
- case ptViewportRatio:
- {
- result.ry() = mValue*mParentPlot->viewport().height();
- if (mParentAnchorY)
- result.ry() += mParentAnchorY->pixelPosition().y();
- else
- result.ry() += mParentPlot->viewport().top();
- break;
- }
- case ptAxisRectRatio:
- {
- if (mAxisRect)
- {
- result.ry() = mValue*mAxisRect.data()->height();
- if (mParentAnchorY)
- result.ry() += mParentAnchorY->pixelPosition().y();
- else
- result.ry() += mAxisRect.data()->top();
- } else
- qDebug() << Q_FUNC_INFO << "Item position type y is ptAxisRectRatio, but no axis rect was defined";
- break;
- }
- case ptPlotCoords:
- {
- if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Vertical)
- result.ry() = mKeyAxis.data()->coordToPixel(mKey);
- else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Vertical)
- result.ry() = mValueAxis.data()->coordToPixel(mValue);
- else
- qDebug() << Q_FUNC_INFO << "Item position type y is ptPlotCoords, but no axes were defined";
- break;
- }
- }
-
- return result;
- }
- /*!
- When \ref setType is \ref ptPlotCoords, this function may be used to specify the axes the
- coordinates set with \ref setCoords relate to. By default they are set to the initial xAxis and
- yAxis of the QCustomPlot.
- */
- void QCPItemPosition::setAxes(QCPAxis *keyAxis, QCPAxis *valueAxis)
- {
- mKeyAxis = keyAxis;
- mValueAxis = valueAxis;
- }
- /*!
- When \ref setType is \ref ptAxisRectRatio, this function may be used to specify the axis rect the
- coordinates set with \ref setCoords relate to. By default this is set to the main axis rect of
- the QCustomPlot.
- */
- void QCPItemPosition::setAxisRect(QCPAxisRect *axisRect)
- {
- mAxisRect = axisRect;
- }
- /*!
- Sets the apparent pixel position. This works no matter what type (\ref setType) this
- QCPItemPosition is or what parent-child situation it is in, as coordinates are transformed
- appropriately, to make the position finally appear at the specified pixel values.
- Only if the type is \ref ptAbsolute and no parent anchor is set, this function's effect is
- identical to that of \ref setCoords.
- \see pixelPosition, setCoords
- */
- void QCPItemPosition::setPixelPosition(const QPointF &pixelPosition)
- {
- double x = pixelPosition.x();
- double y = pixelPosition.y();
-
- switch (mPositionTypeX)
- {
- case ptAbsolute:
- {
- if (mParentAnchorX)
- x -= mParentAnchorX->pixelPosition().x();
- break;
- }
- case ptViewportRatio:
- {
- if (mParentAnchorX)
- x -= mParentAnchorX->pixelPosition().x();
- else
- x -= mParentPlot->viewport().left();
- x /= (double)mParentPlot->viewport().width();
- break;
- }
- case ptAxisRectRatio:
- {
- if (mAxisRect)
- {
- if (mParentAnchorX)
- x -= mParentAnchorX->pixelPosition().x();
- else
- x -= mAxisRect.data()->left();
- x /= (double)mAxisRect.data()->width();
- } else
- qDebug() << Q_FUNC_INFO << "Item position type x is ptAxisRectRatio, but no axis rect was defined";
- break;
- }
- case ptPlotCoords:
- {
- if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Horizontal)
- x = mKeyAxis.data()->pixelToCoord(x);
- else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Horizontal)
- y = mValueAxis.data()->pixelToCoord(x);
- else
- qDebug() << Q_FUNC_INFO << "Item position type x is ptPlotCoords, but no axes were defined";
- break;
- }
- }
-
- switch (mPositionTypeY)
- {
- case ptAbsolute:
- {
- if (mParentAnchorY)
- y -= mParentAnchorY->pixelPosition().y();
- break;
- }
- case ptViewportRatio:
- {
- if (mParentAnchorY)
- y -= mParentAnchorY->pixelPosition().y();
- else
- y -= mParentPlot->viewport().top();
- y /= (double)mParentPlot->viewport().height();
- break;
- }
- case ptAxisRectRatio:
- {
- if (mAxisRect)
- {
- if (mParentAnchorY)
- y -= mParentAnchorY->pixelPosition().y();
- else
- y -= mAxisRect.data()->top();
- y /= (double)mAxisRect.data()->height();
- } else
- qDebug() << Q_FUNC_INFO << "Item position type y is ptAxisRectRatio, but no axis rect was defined";
- break;
- }
- case ptPlotCoords:
- {
- if (mKeyAxis && mKeyAxis.data()->orientation() == Qt::Vertical)
- x = mKeyAxis.data()->pixelToCoord(y);
- else if (mValueAxis && mValueAxis.data()->orientation() == Qt::Vertical)
- y = mValueAxis.data()->pixelToCoord(y);
- else
- qDebug() << Q_FUNC_INFO << "Item position type y is ptPlotCoords, but no axes were defined";
- break;
- }
- }
-
- setCoords(x, y);
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPAbstractItem
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPAbstractItem
- \brief The abstract base class for all items in a plot.
-
- In QCustomPlot, items are supplemental graphical elements that are neither plottables
- (QCPAbstractPlottable) nor axes (QCPAxis). While plottables are always tied to two axes and thus
- plot coordinates, items can also be placed in absolute coordinates independent of any axes. Each
- specific item has at least one QCPItemPosition member which controls the positioning. Some items
- are defined by more than one coordinate and thus have two or more QCPItemPosition members (For
- example, QCPItemRect has \a topLeft and \a bottomRight).
-
- This abstract base class defines a very basic interface like visibility and clipping. Since this
- class is abstract, it can't be instantiated. Use one of the subclasses or create a subclass
- yourself to create new items.
-
- The built-in items are:
- <table>
- <tr><td>QCPItemLine</td><td>A line defined by a start and an end point. May have different ending styles on each side (e.g. arrows).</td></tr>
- <tr><td>QCPItemStraightLine</td><td>A straight line defined by a start and a direction point. Unlike QCPItemLine, the straight line is infinitely long and has no endings.</td></tr>
- <tr><td>QCPItemCurve</td><td>A curve defined by start, end and two intermediate control points. May have different ending styles on each side (e.g. arrows).</td></tr>
- <tr><td>QCPItemRect</td><td>A rectangle</td></tr>
- <tr><td>QCPItemEllipse</td><td>An ellipse</td></tr>
- <tr><td>QCPItemPixmap</td><td>An arbitrary pixmap</td></tr>
- <tr><td>QCPItemText</td><td>A text label</td></tr>
- <tr><td>QCPItemBracket</td><td>A bracket which may be used to reference/highlight certain parts in the plot.</td></tr>
- <tr><td>QCPItemTracer</td><td>An item that can be attached to a QCPGraph and sticks to its data points, given a key coordinate.</td></tr>
- </table>
-
- \section items-clipping Clipping
- Items are by default clipped to the main axis rect (they are only visible inside the axis rect).
- To make an item visible outside that axis rect, disable clipping via \ref setClipToAxisRect
- "setClipToAxisRect(false)".
- On the other hand if you want the item to be clipped to a different axis rect, specify it via
- \ref setClipAxisRect. This clipAxisRect property of an item is only used for clipping behaviour, and
- in principle is independent of the coordinate axes the item might be tied to via its position
- members (\ref QCPItemPosition::setAxes). However, it is common that the axis rect for clipping
- also contains the axes used for the item positions.
-
- \section items-using Using items
-
- First you instantiate the item you want to use and add it to the plot:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-1
- by default, the positions of the item are bound to the x- and y-Axis of the plot. So we can just
- set the plot coordinates where the line should start/end:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-2
- If we don't want the line to be positioned in plot coordinates but a different coordinate system,
- e.g. absolute pixel positions on the QCustomPlot surface, we need to change the position type like this:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-3
- Then we can set the coordinates, this time in pixels:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-4
- and make the line visible on the entire QCustomPlot, by disabling clipping to the axis rect:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpitemline-creation-5
-
- For more advanced plots, it is even possible to set different types and parent anchors per X/Y
- coordinate of an item position, using for example \ref QCPItemPosition::setTypeX or \ref
- QCPItemPosition::setParentAnchorX. For details, see the documentation of \ref QCPItemPosition.
-
- \section items-subclassing Creating own items
-
- To create an own item, you implement a subclass of QCPAbstractItem. These are the pure
- virtual functions, you must implement:
- \li \ref selectTest
- \li \ref draw
-
- See the documentation of those functions for what they need to do.
-
- \subsection items-positioning Allowing the item to be positioned
-
- As mentioned, item positions are represented by QCPItemPosition members. Let's assume the new item shall
- have only one point as its position (as opposed to two like a rect or multiple like a polygon). You then add
- a public member of type QCPItemPosition like so:
-
- \code QCPItemPosition * const myPosition;\endcode
-
- the const makes sure the pointer itself can't be modified from the user of your new item (the QCPItemPosition
- instance it points to, can be modified, of course).
- The initialization of this pointer is made easy with the \ref createPosition function. Just assign
- the return value of this function to each QCPItemPosition in the constructor of your item. \ref createPosition
- takes a string which is the name of the position, typically this is identical to the variable name.
- For example, the constructor of QCPItemExample could look like this:
-
- \code
- QCPItemExample::QCPItemExample(QCustomPlot *parentPlot) :
- QCPAbstractItem(parentPlot),
- myPosition(createPosition("myPosition"))
- {
- // other constructor code
- }
- \endcode
-
- \subsection items-drawing The draw function
-
- To give your item a visual representation, reimplement the \ref draw function and use the passed
- QCPPainter to draw the item. You can retrieve the item position in pixel coordinates from the
- position member(s) via \ref QCPItemPosition::pixelPosition.
- To optimize performance you should calculate a bounding rect first (don't forget to take the pen
- width into account), check whether it intersects the \ref clipRect, and only draw the item at all
- if this is the case.
-
- \subsection items-selection The selectTest function
-
- Your implementation of the \ref selectTest function may use the helpers \ref
- QCPVector2D::distanceSquaredToLine and \ref rectDistance. With these, the implementation of the
- selection test becomes significantly simpler for most items. See the documentation of \ref
- selectTest for what the function parameters mean and what the function should return.
-
- \subsection anchors Providing anchors
-
- Providing anchors (QCPItemAnchor) starts off like adding a position. First you create a public
- member, e.g.
-
- \code QCPItemAnchor * const bottom;\endcode
- and create it in the constructor with the \ref createAnchor function, assigning it a name and an
- anchor id (an integer enumerating all anchors on the item, you may create an own enum for this).
- Since anchors can be placed anywhere, relative to the item's position(s), your item needs to
- provide the position of every anchor with the reimplementation of the \ref anchorPixelPosition(int
- anchorId) function.
-
- In essence the QCPItemAnchor is merely an intermediary that itself asks your item for the pixel
- position when anything attached to the anchor needs to know the coordinates.
- */
- /* start of documentation of inline functions */
- /*! \fn QList<QCPItemPosition*> QCPAbstractItem::positions() const
-
- Returns all positions of the item in a list.
-
- \see anchors, position
- */
- /*! \fn QList<QCPItemAnchor*> QCPAbstractItem::anchors() const
-
- Returns all anchors of the item in a list. Note that since a position (QCPItemPosition) is always
- also an anchor, the list will also contain the positions of this item.
-
- \see positions, anchor
- */
- /* end of documentation of inline functions */
- /* start documentation of pure virtual functions */
- /*! \fn void QCPAbstractItem::draw(QCPPainter *painter) = 0
- \internal
-
- Draws this item with the provided \a painter.
-
- The cliprect of the provided painter is set to the rect returned by \ref clipRect before this
- function is called. The clipRect depends on the clipping settings defined by \ref
- setClipToAxisRect and \ref setClipAxisRect.
- */
- /* end documentation of pure virtual functions */
- /* start documentation of signals */
- /*! \fn void QCPAbstractItem::selectionChanged(bool selected)
- This signal is emitted when the selection state of this item has changed, either by user interaction
- or by a direct call to \ref setSelected.
- */
- /* end documentation of signals */
- /*!
- Base class constructor which initializes base class members.
- */
- QCPAbstractItem::QCPAbstractItem(QCustomPlot *parentPlot) :
- QCPLayerable(parentPlot),
- mClipToAxisRect(false),
- mSelectable(true),
- mSelected(false)
- {
- parentPlot->registerItem(this);
-
- QList<QCPAxisRect*> rects = parentPlot->axisRects();
- if (rects.size() > 0)
- {
- setClipToAxisRect(true);
- setClipAxisRect(rects.first());
- }
- }
- QCPAbstractItem::~QCPAbstractItem()
- {
- // don't delete mPositions because every position is also an anchor and thus in mAnchors
- qDeleteAll(mAnchors);
- }
- /* can't make this a header inline function, because QPointer breaks with forward declared types, see QTBUG-29588 */
- QCPAxisRect *QCPAbstractItem::clipAxisRect() const
- {
- return mClipAxisRect.data();
- }
- /*!
- Sets whether the item shall be clipped to an axis rect or whether it shall be visible on the
- entire QCustomPlot. The axis rect can be set with \ref setClipAxisRect.
-
- \see setClipAxisRect
- */
- void QCPAbstractItem::setClipToAxisRect(bool clip)
- {
- mClipToAxisRect = clip;
- if (mClipToAxisRect)
- setParentLayerable(mClipAxisRect.data());
- }
- /*!
- Sets the clip axis rect. It defines the rect that will be used to clip the item when \ref
- setClipToAxisRect is set to true.
-
- \see setClipToAxisRect
- */
- void QCPAbstractItem::setClipAxisRect(QCPAxisRect *rect)
- {
- mClipAxisRect = rect;
- if (mClipToAxisRect)
- setParentLayerable(mClipAxisRect.data());
- }
- /*!
- Sets whether the user can (de-)select this item by clicking on the QCustomPlot surface.
- (When \ref QCustomPlot::setInteractions contains QCustomPlot::iSelectItems.)
-
- However, even when \a selectable was set to false, it is possible to set the selection manually,
- by calling \ref setSelected.
-
- \see QCustomPlot::setInteractions, setSelected
- */
- void QCPAbstractItem::setSelectable(bool selectable)
- {
- if (mSelectable != selectable)
- {
- mSelectable = selectable;
- emit selectableChanged(mSelectable);
- }
- }
- /*!
- Sets whether this item is selected or not. When selected, it might use a different visual
- appearance (e.g. pen and brush), this depends on the specific item though.
- The entire selection mechanism for items is handled automatically when \ref
- QCustomPlot::setInteractions contains QCustomPlot::iSelectItems. You only need to call this
- function when you wish to change the selection state manually.
-
- This function can change the selection state even when \ref setSelectable was set to false.
-
- emits the \ref selectionChanged signal when \a selected is different from the previous selection state.
-
- \see setSelectable, selectTest
- */
- void QCPAbstractItem::setSelected(bool selected)
- {
- if (mSelected != selected)
- {
- mSelected = selected;
- emit selectionChanged(mSelected);
- }
- }
- /*!
- Returns the QCPItemPosition with the specified \a name. If this item doesn't have a position by
- that name, returns 0.
-
- This function provides an alternative way to access item positions. Normally, you access
- positions direcly by their member pointers (which typically have the same variable name as \a
- name).
-
- \see positions, anchor
- */
- QCPItemPosition *QCPAbstractItem::position(const QString &name) const
- {
- for (int i=0; i<mPositions.size(); ++i)
- {
- if (mPositions.at(i)->name() == name)
- return mPositions.at(i);
- }
- qDebug() << Q_FUNC_INFO << "position with name not found:" << name;
- return 0;
- }
- /*!
- Returns the QCPItemAnchor with the specified \a name. If this item doesn't have an anchor by
- that name, returns 0.
-
- This function provides an alternative way to access item anchors. Normally, you access
- anchors direcly by their member pointers (which typically have the same variable name as \a
- name).
-
- \see anchors, position
- */
- QCPItemAnchor *QCPAbstractItem::anchor(const QString &name) const
- {
- for (int i=0; i<mAnchors.size(); ++i)
- {
- if (mAnchors.at(i)->name() == name)
- return mAnchors.at(i);
- }
- qDebug() << Q_FUNC_INFO << "anchor with name not found:" << name;
- return 0;
- }
- /*!
- Returns whether this item has an anchor with the specified \a name.
-
- Note that you can check for positions with this function, too. This is because every position is
- also an anchor (QCPItemPosition inherits from QCPItemAnchor).
-
- \see anchor, position
- */
- bool QCPAbstractItem::hasAnchor(const QString &name) const
- {
- for (int i=0; i<mAnchors.size(); ++i)
- {
- if (mAnchors.at(i)->name() == name)
- return true;
- }
- return false;
- }
- /*! \internal
-
- Returns the rect the visual representation of this item is clipped to. This depends on the
- current setting of \ref setClipToAxisRect as well as the axis rect set with \ref setClipAxisRect.
-
- If the item is not clipped to an axis rect, QCustomPlot's viewport rect is returned.
-
- \see draw
- */
- QRect QCPAbstractItem::clipRect() const
- {
- if (mClipToAxisRect && mClipAxisRect)
- return mClipAxisRect.data()->rect();
- else
- return mParentPlot->viewport();
- }
- /*! \internal
- A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
- before drawing item lines.
- This is the antialiasing state the painter passed to the \ref draw method is in by default.
-
- This function takes into account the local setting of the antialiasing flag as well as the
- overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
- QCustomPlot::setNotAntialiasedElements.
-
- \see setAntialiased
- */
- void QCPAbstractItem::applyDefaultAntialiasingHint(QCPPainter *painter) const
- {
- applyAntialiasingHint(painter, mAntialiased, QCP::aeItems);
- }
- /*! \internal
- A convenience function which returns the selectTest value for a specified \a rect and a specified
- click position \a pos. \a filledRect defines whether a click inside the rect should also be
- considered a hit or whether only the rect border is sensitive to hits.
-
- This function may be used to help with the implementation of the \ref selectTest function for
- specific items.
-
- For example, if your item consists of four rects, call this function four times, once for each
- rect, in your \ref selectTest reimplementation. Finally, return the minimum (non -1) of all four
- returned values.
- */
- double QCPAbstractItem::rectDistance(const QRectF &rect, const QPointF &pos, bool filledRect) const
- {
- double result = -1;
- // distance to border:
- QList<QLineF> lines;
- lines << QLineF(rect.topLeft(), rect.topRight()) << QLineF(rect.bottomLeft(), rect.bottomRight())
- << QLineF(rect.topLeft(), rect.bottomLeft()) << QLineF(rect.topRight(), rect.bottomRight());
- double minDistSqr = std::numeric_limits<double>::max();
- for (int i=0; i<lines.size(); ++i)
- {
- double distSqr = QCPVector2D(pos).distanceSquaredToLine(lines.at(i).p1(), lines.at(i).p2());
- if (distSqr < minDistSqr)
- minDistSqr = distSqr;
- }
- result = qSqrt(minDistSqr);
-
- // filled rect, allow click inside to count as hit:
- if (filledRect && result > mParentPlot->selectionTolerance()*0.99)
- {
- if (rect.contains(pos))
- result = mParentPlot->selectionTolerance()*0.99;
- }
- return result;
- }
- /*! \internal
- Returns the pixel position of the anchor with Id \a anchorId. This function must be reimplemented in
- item subclasses if they want to provide anchors (QCPItemAnchor).
-
- For example, if the item has two anchors with id 0 and 1, this function takes one of these anchor
- ids and returns the respective pixel points of the specified anchor.
-
- \see createAnchor
- */
- QPointF QCPAbstractItem::anchorPixelPosition(int anchorId) const
- {
- qDebug() << Q_FUNC_INFO << "called on item which shouldn't have any anchors (this method not reimplemented). anchorId" << anchorId;
- return QPointF();
- }
- /*! \internal
- Creates a QCPItemPosition, registers it with this item and returns a pointer to it. The specified
- \a name must be a unique string that is usually identical to the variable name of the position
- member (This is needed to provide the name-based \ref position access to positions).
-
- Don't delete positions created by this function manually, as the item will take care of it.
-
- Use this function in the constructor (initialization list) of the specific item subclass to
- create each position member. Don't create QCPItemPositions with \b new yourself, because they
- won't be registered with the item properly.
-
- \see createAnchor
- */
- QCPItemPosition *QCPAbstractItem::createPosition(const QString &name)
- {
- if (hasAnchor(name))
- qDebug() << Q_FUNC_INFO << "anchor/position with name exists already:" << name;
- QCPItemPosition *newPosition = new QCPItemPosition(mParentPlot, this, name);
- mPositions.append(newPosition);
- mAnchors.append(newPosition); // every position is also an anchor
- newPosition->setAxes(mParentPlot->xAxis, mParentPlot->yAxis);
- newPosition->setType(QCPItemPosition::ptPlotCoords);
- if (mParentPlot->axisRect())
- newPosition->setAxisRect(mParentPlot->axisRect());
- newPosition->setCoords(0, 0);
- return newPosition;
- }
- /*! \internal
- Creates a QCPItemAnchor, registers it with this item and returns a pointer to it. The specified
- \a name must be a unique string that is usually identical to the variable name of the anchor
- member (This is needed to provide the name based \ref anchor access to anchors).
-
- The \a anchorId must be a number identifying the created anchor. It is recommended to create an
- enum (e.g. "AnchorIndex") for this on each item that uses anchors. This id is used by the anchor
- to identify itself when it calls QCPAbstractItem::anchorPixelPosition. That function then returns
- the correct pixel coordinates for the passed anchor id.
-
- Don't delete anchors created by this function manually, as the item will take care of it.
-
- Use this function in the constructor (initialization list) of the specific item subclass to
- create each anchor member. Don't create QCPItemAnchors with \b new yourself, because then they
- won't be registered with the item properly.
-
- \see createPosition
- */
- QCPItemAnchor *QCPAbstractItem::createAnchor(const QString &name, int anchorId)
- {
- if (hasAnchor(name))
- qDebug() << Q_FUNC_INFO << "anchor/position with name exists already:" << name;
- QCPItemAnchor *newAnchor = new QCPItemAnchor(mParentPlot, this, name, anchorId);
- mAnchors.append(newAnchor);
- return newAnchor;
- }
- /* inherits documentation from base class */
- void QCPAbstractItem::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
- {
- Q_UNUSED(event)
- Q_UNUSED(details)
- if (mSelectable)
- {
- bool selBefore = mSelected;
- setSelected(additive ? !mSelected : true);
- if (selectionStateChanged)
- *selectionStateChanged = mSelected != selBefore;
- }
- }
- /* inherits documentation from base class */
- void QCPAbstractItem::deselectEvent(bool *selectionStateChanged)
- {
- if (mSelectable)
- {
- bool selBefore = mSelected;
- setSelected(false);
- if (selectionStateChanged)
- *selectionStateChanged = mSelected != selBefore;
- }
- }
- /* inherits documentation from base class */
- QCP::Interaction QCPAbstractItem::selectionCategory() const
- {
- return QCP::iSelectItems;
- }
- /* end of 'src/item.cpp' */
- /* including file 'src/core.cpp', size 125037 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCustomPlot
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCustomPlot
-
- \brief The central class of the library. This is the QWidget which displays the plot and
- interacts with the user.
-
- For tutorials on how to use QCustomPlot, see the website\n
- http://www.qcustomplot.com/
- */
- /* start of documentation of inline functions */
- /*! \fn QCPSelectionRect *QCustomPlot::selectionRect() const
-
- Allows access to the currently used QCPSelectionRect instance (or subclass thereof), that is used
- to handle and draw selection rect interactions (see \ref setSelectionRectMode).
-
- \see setSelectionRect
- */
- /*! \fn QCPLayoutGrid *QCustomPlot::plotLayout() const
-
- Returns the top level layout of this QCustomPlot instance. It is a \ref QCPLayoutGrid, initially containing just
- one cell with the main QCPAxisRect inside.
- */
- /* end of documentation of inline functions */
- /* start of documentation of signals */
- /*! \fn void QCustomPlot::mouseDoubleClick(QMouseEvent *event)
- This signal is emitted when the QCustomPlot receives a mouse double click event.
- */
- /*! \fn void QCustomPlot::mousePress(QMouseEvent *event)
- This signal is emitted when the QCustomPlot receives a mouse press event.
-
- It is emitted before QCustomPlot handles any other mechanism like range dragging. So a slot
- connected to this signal can still influence the behaviour e.g. with \ref QCPAxisRect::setRangeDrag or \ref
- QCPAxisRect::setRangeDragAxes.
- */
- /*! \fn void QCustomPlot::mouseMove(QMouseEvent *event)
- This signal is emitted when the QCustomPlot receives a mouse move event.
-
- It is emitted before QCustomPlot handles any other mechanism like range dragging. So a slot
- connected to this signal can still influence the behaviour e.g. with \ref QCPAxisRect::setRangeDrag or \ref
- QCPAxisRect::setRangeDragAxes.
-
- \warning It is discouraged to change the drag-axes with \ref QCPAxisRect::setRangeDragAxes here,
- because the dragging starting point was saved the moment the mouse was pressed. Thus it only has
- a meaning for the range drag axes that were set at that moment. If you want to change the drag
- axes, consider doing this in the \ref mousePress signal instead.
- */
- /*! \fn void QCustomPlot::mouseRelease(QMouseEvent *event)
- This signal is emitted when the QCustomPlot receives a mouse release event.
-
- It is emitted before QCustomPlot handles any other mechanisms like object selection. So a
- slot connected to this signal can still influence the behaviour e.g. with \ref setInteractions or
- \ref QCPAbstractPlottable::setSelectable.
- */
- /*! \fn void QCustomPlot::mouseWheel(QMouseEvent *event)
- This signal is emitted when the QCustomPlot receives a mouse wheel event.
-
- It is emitted before QCustomPlot handles any other mechanisms like range zooming. So a slot
- connected to this signal can still influence the behaviour e.g. with \ref QCPAxisRect::setRangeZoom, \ref
- QCPAxisRect::setRangeZoomAxes or \ref QCPAxisRect::setRangeZoomFactor.
- */
- /*! \fn void QCustomPlot::plottableClick(QCPAbstractPlottable *plottable, int dataIndex, QMouseEvent *event)
- This signal is emitted when a plottable is clicked.
- \a event is the mouse event that caused the click and \a plottable is the plottable that received
- the click. The parameter \a dataIndex indicates the data point that was closest to the click
- position.
- \see plottableDoubleClick
- */
- /*! \fn void QCustomPlot::plottableDoubleClick(QCPAbstractPlottable *plottable, int dataIndex, QMouseEvent *event)
- This signal is emitted when a plottable is double clicked.
- \a event is the mouse event that caused the click and \a plottable is the plottable that received
- the click. The parameter \a dataIndex indicates the data point that was closest to the click
- position.
- \see plottableClick
- */
- /*! \fn void QCustomPlot::itemClick(QCPAbstractItem *item, QMouseEvent *event)
-
- This signal is emitted when an item is clicked.
- \a event is the mouse event that caused the click and \a item is the item that received the
- click.
-
- \see itemDoubleClick
- */
- /*! \fn void QCustomPlot::itemDoubleClick(QCPAbstractItem *item, QMouseEvent *event)
-
- This signal is emitted when an item is double clicked.
-
- \a event is the mouse event that caused the click and \a item is the item that received the
- click.
-
- \see itemClick
- */
- /*! \fn void QCustomPlot::axisClick(QCPAxis *axis, QCPAxis::SelectablePart part, QMouseEvent *event)
-
- This signal is emitted when an axis is clicked.
-
- \a event is the mouse event that caused the click, \a axis is the axis that received the click and
- \a part indicates the part of the axis that was clicked.
-
- \see axisDoubleClick
- */
- /*! \fn void QCustomPlot::axisDoubleClick(QCPAxis *axis, QCPAxis::SelectablePart part, QMouseEvent *event)
- This signal is emitted when an axis is double clicked.
-
- \a event is the mouse event that caused the click, \a axis is the axis that received the click and
- \a part indicates the part of the axis that was clicked.
-
- \see axisClick
- */
- /*! \fn void QCustomPlot::legendClick(QCPLegend *legend, QCPAbstractLegendItem *item, QMouseEvent *event)
- This signal is emitted when a legend (item) is clicked.
-
- \a event is the mouse event that caused the click, \a legend is the legend that received the
- click and \a item is the legend item that received the click. If only the legend and no item is
- clicked, \a item is 0. This happens for a click inside the legend padding or the space between
- two items.
-
- \see legendDoubleClick
- */
- /*! \fn void QCustomPlot::legendDoubleClick(QCPLegend *legend, QCPAbstractLegendItem *item, QMouseEvent *event)
- This signal is emitted when a legend (item) is double clicked.
-
- \a event is the mouse event that caused the click, \a legend is the legend that received the
- click and \a item is the legend item that received the click. If only the legend and no item is
- clicked, \a item is 0. This happens for a click inside the legend padding or the space between
- two items.
-
- \see legendClick
- */
- /*! \fn void QCustomPlot::selectionChangedByUser()
-
- This signal is emitted after the user has changed the selection in the QCustomPlot, e.g. by
- clicking. It is not emitted when the selection state of an object has changed programmatically by
- a direct call to <tt>setSelected()</tt>/<tt>setSelection()</tt> on an object or by calling \ref
- deselectAll.
-
- In addition to this signal, selectable objects also provide individual signals, for example \ref
- QCPAxis::selectionChanged or \ref QCPAbstractPlottable::selectionChanged. Note that those signals
- are emitted even if the selection state is changed programmatically.
-
- See the documentation of \ref setInteractions for details about the selection mechanism.
-
- \see selectedPlottables, selectedGraphs, selectedItems, selectedAxes, selectedLegends
- */
- /*! \fn void QCustomPlot::beforeReplot()
-
- This signal is emitted immediately before a replot takes place (caused by a call to the slot \ref
- replot).
-
- It is safe to mutually connect the replot slot with this signal on two QCustomPlots to make them
- replot synchronously, it won't cause an infinite recursion.
-
- \see replot, afterReplot
- */
- /*! \fn void QCustomPlot::afterReplot()
-
- This signal is emitted immediately after a replot has taken place (caused by a call to the slot \ref
- replot).
-
- It is safe to mutually connect the replot slot with this signal on two QCustomPlots to make them
- replot synchronously, it won't cause an infinite recursion.
-
- \see replot, beforeReplot
- */
- /* end of documentation of signals */
- /* start of documentation of public members */
- /*! \var QCPAxis *QCustomPlot::xAxis
- A pointer to the primary x Axis (bottom) of the main axis rect of the plot.
-
- QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref
- yAxis2) and the \ref legend. They make it very easy working with plots that only have a single
- axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the
- layout system\endlink to add multiple axis rects or multiple axes to one side, use the \ref
- QCPAxisRect::axis interface to access the new axes. If one of the four default axes or the
- default legend is removed due to manipulation of the layout system (e.g. by removing the main
- axis rect), the corresponding pointers become 0.
-
- If an axis convenience pointer is currently zero and a new axis rect or a corresponding axis is
- added in the place of the main axis rect, QCustomPlot resets the convenience pointers to the
- according new axes. Similarly the \ref legend convenience pointer will be reset if a legend is
- added after the main legend was removed before.
- */
- /*! \var QCPAxis *QCustomPlot::yAxis
- A pointer to the primary y Axis (left) of the main axis rect of the plot.
-
- QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref
- yAxis2) and the \ref legend. They make it very easy working with plots that only have a single
- axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the
- layout system\endlink to add multiple axis rects or multiple axes to one side, use the \ref
- QCPAxisRect::axis interface to access the new axes. If one of the four default axes or the
- default legend is removed due to manipulation of the layout system (e.g. by removing the main
- axis rect), the corresponding pointers become 0.
-
- If an axis convenience pointer is currently zero and a new axis rect or a corresponding axis is
- added in the place of the main axis rect, QCustomPlot resets the convenience pointers to the
- according new axes. Similarly the \ref legend convenience pointer will be reset if a legend is
- added after the main legend was removed before.
- */
- /*! \var QCPAxis *QCustomPlot::xAxis2
- A pointer to the secondary x Axis (top) of the main axis rect of the plot. Secondary axes are
- invisible by default. Use QCPAxis::setVisible to change this (or use \ref
- QCPAxisRect::setupFullAxesBox).
-
- QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref
- yAxis2) and the \ref legend. They make it very easy working with plots that only have a single
- axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the
- layout system\endlink to add multiple axis rects or multiple axes to one side, use the \ref
- QCPAxisRect::axis interface to access the new axes. If one of the four default axes or the
- default legend is removed due to manipulation of the layout system (e.g. by removing the main
- axis rect), the corresponding pointers become 0.
-
- If an axis convenience pointer is currently zero and a new axis rect or a corresponding axis is
- added in the place of the main axis rect, QCustomPlot resets the convenience pointers to the
- according new axes. Similarly the \ref legend convenience pointer will be reset if a legend is
- added after the main legend was removed before.
- */
- /*! \var QCPAxis *QCustomPlot::yAxis2
- A pointer to the secondary y Axis (right) of the main axis rect of the plot. Secondary axes are
- invisible by default. Use QCPAxis::setVisible to change this (or use \ref
- QCPAxisRect::setupFullAxesBox).
-
- QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref
- yAxis2) and the \ref legend. They make it very easy working with plots that only have a single
- axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the
- layout system\endlink to add multiple axis rects or multiple axes to one side, use the \ref
- QCPAxisRect::axis interface to access the new axes. If one of the four default axes or the
- default legend is removed due to manipulation of the layout system (e.g. by removing the main
- axis rect), the corresponding pointers become 0.
-
- If an axis convenience pointer is currently zero and a new axis rect or a corresponding axis is
- added in the place of the main axis rect, QCustomPlot resets the convenience pointers to the
- according new axes. Similarly the \ref legend convenience pointer will be reset if a legend is
- added after the main legend was removed before.
- */
- /*! \var QCPLegend *QCustomPlot::legend
- A pointer to the default legend of the main axis rect. The legend is invisible by default. Use
- QCPLegend::setVisible to change this.
-
- QCustomPlot offers convenient pointers to the axes (\ref xAxis, \ref yAxis, \ref xAxis2, \ref
- yAxis2) and the \ref legend. They make it very easy working with plots that only have a single
- axis rect and at most one axis at each axis rect side. If you use \link thelayoutsystem the
- layout system\endlink to add multiple legends to the plot, use the layout system interface to
- access the new legend. For example, legends can be placed inside an axis rect's \ref
- QCPAxisRect::insetLayout "inset layout", and must then also be accessed via the inset layout. If
- the default legend is removed due to manipulation of the layout system (e.g. by removing the main
- axis rect), the corresponding pointer becomes 0.
-
- If an axis convenience pointer is currently zero and a new axis rect or a corresponding axis is
- added in the place of the main axis rect, QCustomPlot resets the convenience pointers to the
- according new axes. Similarly the \ref legend convenience pointer will be reset if a legend is
- added after the main legend was removed before.
- */
- /* end of documentation of public members */
- /*!
- Constructs a QCustomPlot and sets reasonable default values.
- */
- QCustomPlot::QCustomPlot(QWidget *parent) :
- QWidget(parent),
- xAxis(0),
- yAxis(0),
- xAxis2(0),
- yAxis2(0),
- legend(0),
- mBufferDevicePixelRatio(1.0), // will be adapted to primary screen below
- mPlotLayout(0),
- mAutoAddPlottableToLegend(true),
- mAntialiasedElements(QCP::aeNone),
- mNotAntialiasedElements(QCP::aeNone),
- mInteractions(0),
- mSelectionTolerance(8),
- mNoAntialiasingOnDrag(false),
- mBackgroundBrush(Qt::white, Qt::SolidPattern),
- mBackgroundScaled(true),
- mBackgroundScaledMode(Qt::KeepAspectRatioByExpanding),
- mCurrentLayer(0),
- mPlottingHints(QCP::phCacheLabels|QCP::phImmediateRefresh),
- mMultiSelectModifier(Qt::ControlModifier),
- mSelectionRectMode(QCP::srmNone),
- mSelectionRect(0),
- mOpenGl(false),
- mMouseHasMoved(false),
- mMouseEventLayerable(0),
- mMouseSignalLayerable(0),
- mReplotting(false),
- mReplotQueued(false),
- mOpenGlMultisamples(16),
- mOpenGlAntialiasedElementsBackup(QCP::aeNone),
- mOpenGlCacheLabelsBackup(true)
- {
- setAttribute(Qt::WA_NoMousePropagation);
- setAttribute(Qt::WA_OpaquePaintEvent);
- setFocusPolicy(Qt::ClickFocus);
- setMouseTracking(true);
- QLocale currentLocale = locale();
- currentLocale.setNumberOptions(QLocale::OmitGroupSeparator);
- setLocale(currentLocale);
- #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
- # ifdef QCP_DEVICEPIXELRATIO_FLOAT
- setBufferDevicePixelRatio(QWidget::devicePixelRatioF());
- # else
- setBufferDevicePixelRatio(QWidget::devicePixelRatio());
- # endif
- #endif
-
- mOpenGlAntialiasedElementsBackup = mAntialiasedElements;
- mOpenGlCacheLabelsBackup = mPlottingHints.testFlag(QCP::phCacheLabels);
- // create initial layers:
- mLayers.append(new QCPLayer(this, QLatin1String("background")));
- mLayers.append(new QCPLayer(this, QLatin1String("grid")));
- mLayers.append(new QCPLayer(this, QLatin1String("main")));
- mLayers.append(new QCPLayer(this, QLatin1String("axes")));
- mLayers.append(new QCPLayer(this, QLatin1String("legend")));
- mLayers.append(new QCPLayer(this, QLatin1String("overlay")));
- updateLayerIndices();
- setCurrentLayer(QLatin1String("main"));
- layer(QLatin1String("overlay"))->setMode(QCPLayer::lmBuffered);
-
- // create initial layout, axis rect and legend:
- mPlotLayout = new QCPLayoutGrid;
- mPlotLayout->initializeParentPlot(this);
- mPlotLayout->setParent(this); // important because if parent is QWidget, QCPLayout::sizeConstraintsChanged will call QWidget::updateGeometry
- mPlotLayout->setLayer(QLatin1String("main"));
- QCPAxisRect *defaultAxisRect = new QCPAxisRect(this, true);
- mPlotLayout->addElement(0, 0, defaultAxisRect);
- xAxis = defaultAxisRect->axis(QCPAxis::atBottom);
- yAxis = defaultAxisRect->axis(QCPAxis::atLeft);
- xAxis2 = defaultAxisRect->axis(QCPAxis::atTop);
- yAxis2 = defaultAxisRect->axis(QCPAxis::atRight);
- legend = new QCPLegend;
- legend->setVisible(false);
- defaultAxisRect->insetLayout()->addElement(legend, Qt::AlignRight|Qt::AlignTop);
- defaultAxisRect->insetLayout()->setMargins(QMargins(12, 12, 12, 12));
-
- defaultAxisRect->setLayer(QLatin1String("background"));
- xAxis->setLayer(QLatin1String("axes"));
- yAxis->setLayer(QLatin1String("axes"));
- xAxis2->setLayer(QLatin1String("axes"));
- yAxis2->setLayer(QLatin1String("axes"));
- xAxis->grid()->setLayer(QLatin1String("grid"));
- yAxis->grid()->setLayer(QLatin1String("grid"));
- xAxis2->grid()->setLayer(QLatin1String("grid"));
- yAxis2->grid()->setLayer(QLatin1String("grid"));
- legend->setLayer(QLatin1String("legend"));
-
- // create selection rect instance:
- mSelectionRect = new QCPSelectionRect(this);
- mSelectionRect->setLayer(QLatin1String("overlay"));
-
- setViewport(rect()); // needs to be called after mPlotLayout has been created
-
- replot(rpQueuedReplot);
- }
- QCustomPlot::~QCustomPlot()
- {
- clearPlottables();
- clearItems();
- if (mPlotLayout)
- {
- delete mPlotLayout;
- mPlotLayout = 0;
- }
-
- mCurrentLayer = 0;
- qDeleteAll(mLayers); // don't use removeLayer, because it would prevent the last layer to be removed
- mLayers.clear();
- }
- /*!
- Sets which elements are forcibly drawn antialiased as an \a or combination of QCP::AntialiasedElement.
-
- This overrides the antialiasing settings for whole element groups, normally controlled with the
- \a setAntialiasing function on the individual elements. If an element is neither specified in
- \ref setAntialiasedElements nor in \ref setNotAntialiasedElements, the antialiasing setting on
- each individual element instance is used.
-
- For example, if \a antialiasedElements contains \ref QCP::aePlottables, all plottables will be
- drawn antialiased, no matter what the specific QCPAbstractPlottable::setAntialiased value was set
- to.
-
- if an element in \a antialiasedElements is already set in \ref setNotAntialiasedElements, it is
- removed from there.
-
- \see setNotAntialiasedElements
- */
- void QCustomPlot::setAntialiasedElements(const QCP::AntialiasedElements &antialiasedElements)
- {
- mAntialiasedElements = antialiasedElements;
-
- // make sure elements aren't in mNotAntialiasedElements and mAntialiasedElements simultaneously:
- if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
- mNotAntialiasedElements |= ~mAntialiasedElements;
- }
- /*!
- Sets whether the specified \a antialiasedElement is forcibly drawn antialiased.
-
- See \ref setAntialiasedElements for details.
-
- \see setNotAntialiasedElement
- */
- void QCustomPlot::setAntialiasedElement(QCP::AntialiasedElement antialiasedElement, bool enabled)
- {
- if (!enabled && mAntialiasedElements.testFlag(antialiasedElement))
- mAntialiasedElements &= ~antialiasedElement;
- else if (enabled && !mAntialiasedElements.testFlag(antialiasedElement))
- mAntialiasedElements |= antialiasedElement;
-
- // make sure elements aren't in mNotAntialiasedElements and mAntialiasedElements simultaneously:
- if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
- mNotAntialiasedElements |= ~mAntialiasedElements;
- }
- /*!
- Sets which elements are forcibly drawn not antialiased as an \a or combination of
- QCP::AntialiasedElement.
-
- This overrides the antialiasing settings for whole element groups, normally controlled with the
- \a setAntialiasing function on the individual elements. If an element is neither specified in
- \ref setAntialiasedElements nor in \ref setNotAntialiasedElements, the antialiasing setting on
- each individual element instance is used.
-
- For example, if \a notAntialiasedElements contains \ref QCP::aePlottables, no plottables will be
- drawn antialiased, no matter what the specific QCPAbstractPlottable::setAntialiased value was set
- to.
-
- if an element in \a notAntialiasedElements is already set in \ref setAntialiasedElements, it is
- removed from there.
-
- \see setAntialiasedElements
- */
- void QCustomPlot::setNotAntialiasedElements(const QCP::AntialiasedElements ¬AntialiasedElements)
- {
- mNotAntialiasedElements = notAntialiasedElements;
-
- // make sure elements aren't in mNotAntialiasedElements and mAntialiasedElements simultaneously:
- if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
- mAntialiasedElements |= ~mNotAntialiasedElements;
- }
- /*!
- Sets whether the specified \a notAntialiasedElement is forcibly drawn not antialiased.
-
- See \ref setNotAntialiasedElements for details.
-
- \see setAntialiasedElement
- */
- void QCustomPlot::setNotAntialiasedElement(QCP::AntialiasedElement notAntialiasedElement, bool enabled)
- {
- if (!enabled && mNotAntialiasedElements.testFlag(notAntialiasedElement))
- mNotAntialiasedElements &= ~notAntialiasedElement;
- else if (enabled && !mNotAntialiasedElements.testFlag(notAntialiasedElement))
- mNotAntialiasedElements |= notAntialiasedElement;
-
- // make sure elements aren't in mNotAntialiasedElements and mAntialiasedElements simultaneously:
- if ((mNotAntialiasedElements & mAntialiasedElements) != 0)
- mAntialiasedElements |= ~mNotAntialiasedElements;
- }
- /*!
- If set to true, adding a plottable (e.g. a graph) to the QCustomPlot automatically also adds the
- plottable to the legend (QCustomPlot::legend).
-
- \see addGraph, QCPLegend::addItem
- */
- void QCustomPlot::setAutoAddPlottableToLegend(bool on)
- {
- mAutoAddPlottableToLegend = on;
- }
- /*!
- Sets the possible interactions of this QCustomPlot as an or-combination of \ref QCP::Interaction
- enums. There are the following types of interactions:
-
- <b>Axis range manipulation</b> is controlled via \ref QCP::iRangeDrag and \ref QCP::iRangeZoom. When the
- respective interaction is enabled, the user may drag axes ranges and zoom with the mouse wheel.
- For details how to control which axes the user may drag/zoom and in what orientations, see \ref
- QCPAxisRect::setRangeDrag, \ref QCPAxisRect::setRangeZoom, \ref QCPAxisRect::setRangeDragAxes,
- \ref QCPAxisRect::setRangeZoomAxes.
-
- <b>Plottable data selection</b> is controlled by \ref QCP::iSelectPlottables. If \ref
- QCP::iSelectPlottables is set, the user may select plottables (graphs, curves, bars,...) and
- their data by clicking on them or in their vicinity (\ref setSelectionTolerance). Whether the
- user can actually select a plottable and its data can further be restricted with the \ref
- QCPAbstractPlottable::setSelectable method on the specific plottable. For details, see the
- special page about the \ref dataselection "data selection mechanism". To retrieve a list of all
- currently selected plottables, call \ref selectedPlottables. If you're only interested in
- QCPGraphs, you may use the convenience function \ref selectedGraphs.
-
- <b>Item selection</b> is controlled by \ref QCP::iSelectItems. If \ref QCP::iSelectItems is set, the user
- may select items (QCPItemLine, QCPItemText,...) by clicking on them or in their vicinity. To find
- out whether a specific item is selected, call QCPAbstractItem::selected(). To retrieve a list of
- all currently selected items, call \ref selectedItems.
-
- <b>Axis selection</b> is controlled with \ref QCP::iSelectAxes. If \ref QCP::iSelectAxes is set, the user
- may select parts of the axes by clicking on them. What parts exactly (e.g. Axis base line, tick
- labels, axis label) are selectable can be controlled via \ref QCPAxis::setSelectableParts for
- each axis. To retrieve a list of all axes that currently contain selected parts, call \ref
- selectedAxes. Which parts of an axis are selected, can be retrieved with QCPAxis::selectedParts().
-
- <b>Legend selection</b> is controlled with \ref QCP::iSelectLegend. If this is set, the user may
- select the legend itself or individual items by clicking on them. What parts exactly are
- selectable can be controlled via \ref QCPLegend::setSelectableParts. To find out whether the
- legend or any of its child items are selected, check the value of QCPLegend::selectedParts. To
- find out which child items are selected, call \ref QCPLegend::selectedItems.
-
- <b>All other selectable elements</b> The selection of all other selectable objects (e.g.
- QCPTextElement, or your own layerable subclasses) is controlled with \ref QCP::iSelectOther. If set, the
- user may select those objects by clicking on them. To find out which are currently selected, you
- need to check their selected state explicitly.
-
- If the selection state has changed by user interaction, the \ref selectionChangedByUser signal is
- emitted. Each selectable object additionally emits an individual selectionChanged signal whenever
- their selection state has changed, i.e. not only by user interaction.
-
- To allow multiple objects to be selected by holding the selection modifier (\ref
- setMultiSelectModifier), set the flag \ref QCP::iMultiSelect.
-
- \note In addition to the selection mechanism presented here, QCustomPlot always emits
- corresponding signals, when an object is clicked or double clicked. see \ref plottableClick and
- \ref plottableDoubleClick for example.
-
- \see setInteraction, setSelectionTolerance
- */
- void QCustomPlot::setInteractions(const QCP::Interactions &interactions)
- {
- mInteractions = interactions;
- }
- /*!
- Sets the single \a interaction of this QCustomPlot to \a enabled.
-
- For details about the interaction system, see \ref setInteractions.
-
- \see setInteractions
- */
- void QCustomPlot::setInteraction(const QCP::Interaction &interaction, bool enabled)
- {
- if (!enabled && mInteractions.testFlag(interaction))
- mInteractions &= ~interaction;
- else if (enabled && !mInteractions.testFlag(interaction))
- mInteractions |= interaction;
- }
- /*!
- Sets the tolerance that is used to decide whether a click selects an object (e.g. a plottable) or
- not.
-
- If the user clicks in the vicinity of the line of e.g. a QCPGraph, it's only regarded as a
- potential selection when the minimum distance between the click position and the graph line is
- smaller than \a pixels. Objects that are defined by an area (e.g. QCPBars) only react to clicks
- directly inside the area and ignore this selection tolerance. In other words, it only has meaning
- for parts of objects that are too thin to exactly hit with a click and thus need such a
- tolerance.
-
- \see setInteractions, QCPLayerable::selectTest
- */
- void QCustomPlot::setSelectionTolerance(int pixels)
- {
- mSelectionTolerance = pixels;
- }
- /*!
- Sets whether antialiasing is disabled for this QCustomPlot while the user is dragging axes
- ranges. If many objects, especially plottables, are drawn antialiased, this greatly improves
- performance during dragging. Thus it creates a more responsive user experience. As soon as the
- user stops dragging, the last replot is done with normal antialiasing, to restore high image
- quality.
-
- \see setAntialiasedElements, setNotAntialiasedElements
- */
- void QCustomPlot::setNoAntialiasingOnDrag(bool enabled)
- {
- mNoAntialiasingOnDrag = enabled;
- }
- /*!
- Sets the plotting hints for this QCustomPlot instance as an \a or combination of QCP::PlottingHint.
-
- \see setPlottingHint
- */
- void QCustomPlot::setPlottingHints(const QCP::PlottingHints &hints)
- {
- mPlottingHints = hints;
- }
- /*!
- Sets the specified plotting \a hint to \a enabled.
-
- \see setPlottingHints
- */
- void QCustomPlot::setPlottingHint(QCP::PlottingHint hint, bool enabled)
- {
- QCP::PlottingHints newHints = mPlottingHints;
- if (!enabled)
- newHints &= ~hint;
- else
- newHints |= hint;
-
- if (newHints != mPlottingHints)
- setPlottingHints(newHints);
- }
- /*!
- Sets the keyboard modifier that will be recognized as multi-select-modifier.
-
- If \ref QCP::iMultiSelect is specified in \ref setInteractions, the user may select multiple
- objects (or data points) by clicking on them one after the other while holding down \a modifier.
-
- By default the multi-select-modifier is set to Qt::ControlModifier.
-
- \see setInteractions
- */
- void QCustomPlot::setMultiSelectModifier(Qt::KeyboardModifier modifier)
- {
- mMultiSelectModifier = modifier;
- }
- /*!
- Sets how QCustomPlot processes mouse click-and-drag interactions by the user.
- If \a mode is \ref QCP::srmNone, the mouse drag is forwarded to the underlying objects. For
- example, QCPAxisRect may process a mouse drag by dragging axis ranges, see \ref
- QCPAxisRect::setRangeDrag. If \a mode is not \ref QCP::srmNone, the current selection rect (\ref
- selectionRect) becomes activated and allows e.g. rect zooming and data point selection.
-
- If you wish to provide your user both with axis range dragging and data selection/range zooming,
- use this method to switch between the modes just before the interaction is processed, e.g. in
- reaction to the \ref mousePress or \ref mouseMove signals. For example you could check whether
- the user is holding a certain keyboard modifier, and then decide which \a mode shall be set.
-
- If a selection rect interaction is currently active, and \a mode is set to \ref QCP::srmNone, the
- interaction is canceled (\ref QCPSelectionRect::cancel). Switching between any of the other modes
- will keep the selection rect active. Upon completion of the interaction, the behaviour is as
- defined by the currently set \a mode, not the mode that was set when the interaction started.
-
- \see setInteractions, setSelectionRect, QCPSelectionRect
- */
- void QCustomPlot::setSelectionRectMode(QCP::SelectionRectMode mode)
- {
- if (mSelectionRect)
- {
- if (mode == QCP::srmNone)
- mSelectionRect->cancel(); // when switching to none, we immediately want to abort a potentially active selection rect
-
- // disconnect old connections:
- if (mSelectionRectMode == QCP::srmSelect)
- disconnect(mSelectionRect, SIGNAL(accepted(QRect,QMouseEvent*)), this, SLOT(processRectSelection(QRect,QMouseEvent*)));
- else if (mSelectionRectMode == QCP::srmZoom)
- disconnect(mSelectionRect, SIGNAL(accepted(QRect,QMouseEvent*)), this, SLOT(processRectZoom(QRect,QMouseEvent*)));
-
- // establish new ones:
- if (mode == QCP::srmSelect)
- connect(mSelectionRect, SIGNAL(accepted(QRect,QMouseEvent*)), this, SLOT(processRectSelection(QRect,QMouseEvent*)));
- else if (mode == QCP::srmZoom)
- connect(mSelectionRect, SIGNAL(accepted(QRect,QMouseEvent*)), this, SLOT(processRectZoom(QRect,QMouseEvent*)));
- }
-
- mSelectionRectMode = mode;
- }
- /*!
- Sets the \ref QCPSelectionRect instance that QCustomPlot will use if \a mode is not \ref
- QCP::srmNone and the user performs a click-and-drag interaction. QCustomPlot takes ownership of
- the passed \a selectionRect. It can be accessed later via \ref selectionRect.
-
- This method is useful if you wish to replace the default QCPSelectionRect instance with an
- instance of a QCPSelectionRect subclass, to introduce custom behaviour of the selection rect.
-
- \see setSelectionRectMode
- */
- void QCustomPlot::setSelectionRect(QCPSelectionRect *selectionRect)
- {
- if (mSelectionRect)
- delete mSelectionRect;
-
- mSelectionRect = selectionRect;
-
- if (mSelectionRect)
- {
- // establish connections with new selection rect:
- if (mSelectionRectMode == QCP::srmSelect)
- connect(mSelectionRect, SIGNAL(accepted(QRect,QMouseEvent*)), this, SLOT(processRectSelection(QRect,QMouseEvent*)));
- else if (mSelectionRectMode == QCP::srmZoom)
- connect(mSelectionRect, SIGNAL(accepted(QRect,QMouseEvent*)), this, SLOT(processRectZoom(QRect,QMouseEvent*)));
- }
- }
- /*!
- \warning This is still an experimental feature and its performance depends on the system that it
- runs on. Having multiple QCustomPlot widgets in one application with enabled OpenGL rendering
- might cause context conflicts on some systems.
-
- This method allows to enable OpenGL plot rendering, for increased plotting performance of
- graphically demanding plots (thick lines, translucent fills, etc.).
- If \a enabled is set to true, QCustomPlot will try to initialize OpenGL and, if successful,
- continue plotting with hardware acceleration. The parameter \a multisampling controls how many
- samples will be used per pixel, it essentially controls the antialiasing quality. If \a
- multisampling is set too high for the current graphics hardware, the maximum allowed value will
- be used.
- You can test whether switching to OpenGL rendering was successful by checking whether the
- according getter \a QCustomPlot::openGl() returns true. If the OpenGL initialization fails,
- rendering continues with the regular software rasterizer, and an according qDebug output is
- generated.
- If switching to OpenGL was successful, this method disables label caching (\ref setPlottingHint
- "setPlottingHint(QCP::phCacheLabels, false)") and turns on QCustomPlot's antialiasing override
- for all elements (\ref setAntialiasedElements "setAntialiasedElements(QCP::aeAll)"), leading to a
- higher quality output. The antialiasing override allows for pixel-grid aligned drawing in the
- OpenGL paint device. As stated before, in OpenGL rendering the actual antialiasing of the plot is
- controlled with \a multisampling. If \a enabled is set to false, the antialiasing/label caching
- settings are restored to what they were before OpenGL was enabled, if they weren't altered in the
- meantime.
- \note OpenGL support is only enabled if QCustomPlot is compiled with the macro \c QCUSTOMPLOT_USE_OPENGL
- defined. This define must be set before including the QCustomPlot header both during compilation
- of the QCustomPlot library as well as when compiling your application. It is best to just include
- the line <tt>DEFINES += QCUSTOMPLOT_USE_OPENGL</tt> in the respective qmake project files.
- \note If you are using a Qt version before 5.0, you must also add the module "opengl" to your \c
- QT variable in the qmake project files. For Qt versions 5.0 and higher, QCustomPlot switches to a
- newer OpenGL interface which is already in the "gui" module.
- */
- void QCustomPlot::setOpenGl(bool enabled, int multisampling)
- {
- mOpenGlMultisamples = qMax(0, multisampling);
- #ifdef QCUSTOMPLOT_USE_OPENGL
- mOpenGl = enabled;
- if (mOpenGl)
- {
- if (setupOpenGl())
- {
- // backup antialiasing override and labelcaching setting so we can restore upon disabling OpenGL
- mOpenGlAntialiasedElementsBackup = mAntialiasedElements;
- mOpenGlCacheLabelsBackup = mPlottingHints.testFlag(QCP::phCacheLabels);
- // set antialiasing override to antialias all (aligns gl pixel grid properly), and disable label caching (would use software rasterizer for pixmap caches):
- setAntialiasedElements(QCP::aeAll);
- setPlottingHint(QCP::phCacheLabels, false);
- } else
- {
- qDebug() << Q_FUNC_INFO << "Failed to enable OpenGL, continuing plotting without hardware acceleration.";
- mOpenGl = false;
- }
- } else
- {
- // restore antialiasing override and labelcaching to what it was before enabling OpenGL, if nobody changed it in the meantime:
- if (mAntialiasedElements == QCP::aeAll)
- setAntialiasedElements(mOpenGlAntialiasedElementsBackup);
- if (!mPlottingHints.testFlag(QCP::phCacheLabels))
- setPlottingHint(QCP::phCacheLabels, mOpenGlCacheLabelsBackup);
- freeOpenGl();
- }
- // recreate all paint buffers:
- mPaintBuffers.clear();
- setupPaintBuffers();
- #else
- Q_UNUSED(enabled)
- qDebug() << Q_FUNC_INFO << "QCustomPlot can't use OpenGL because QCUSTOMPLOT_USE_OPENGL was not defined during compilation (add 'DEFINES += QCUSTOMPLOT_USE_OPENGL' to your qmake .pro file)";
- #endif
- }
- /*!
- Sets the viewport of this QCustomPlot. Usually users of QCustomPlot don't need to change the
- viewport manually.
- The viewport is the area in which the plot is drawn. All mechanisms, e.g. margin caluclation take
- the viewport to be the outer border of the plot. The viewport normally is the rect() of the
- QCustomPlot widget, i.e. a rect with top left (0, 0) and size of the QCustomPlot widget.
- Don't confuse the viewport with the axis rect (QCustomPlot::axisRect). An axis rect is typically
- an area enclosed by four axes, where the graphs/plottables are drawn in. The viewport is larger
- and contains also the axes themselves, their tick numbers, their labels, or even additional axis
- rects, color scales and other layout elements.
- This function is used to allow arbitrary size exports with \ref toPixmap, \ref savePng, \ref
- savePdf, etc. by temporarily changing the viewport size.
- */
- void QCustomPlot::setViewport(const QRect &rect)
- {
- mViewport = rect;
- if (mPlotLayout)
- mPlotLayout->setOuterRect(mViewport);
- }
- /*!
- Sets the device pixel ratio used by the paint buffers of this QCustomPlot instance.
- Normally, this doesn't need to be set manually, because it is initialized with the regular \a
- QWidget::devicePixelRatio which is configured by Qt to fit the display device (e.g. 1 for normal
- displays, 2 for High-DPI displays).
- Device pixel ratios are supported by Qt only for Qt versions since 5.4. If this method is called
- when QCustomPlot is being used with older Qt versions, outputs an according qDebug message and
- leaves the internal buffer device pixel ratio at 1.0.
- */
- void QCustomPlot::setBufferDevicePixelRatio(double ratio)
- {
- if (!qFuzzyCompare(ratio, mBufferDevicePixelRatio))
- {
- #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
- mBufferDevicePixelRatio = ratio;
- for (int i=0; i<mPaintBuffers.size(); ++i)
- mPaintBuffers.at(i)->setDevicePixelRatio(mBufferDevicePixelRatio);
- // Note: axis label cache has devicePixelRatio as part of cache hash, so no need to manually clear cache here
- #else
- qDebug() << Q_FUNC_INFO << "Device pixel ratios not supported for Qt versions before 5.4";
- mBufferDevicePixelRatio = 1.0;
- #endif
- }
- }
- /*!
- Sets \a pm as the viewport background pixmap (see \ref setViewport). The pixmap is always drawn
- below all other objects in the plot.
- For cases where the provided pixmap doesn't have the same size as the viewport, scaling can be
- enabled with \ref setBackgroundScaled and the scaling mode (whether and how the aspect ratio is
- preserved) can be set with \ref setBackgroundScaledMode. To set all these options in one call,
- consider using the overloaded version of this function.
-
- If a background brush was set with \ref setBackground(const QBrush &brush), the viewport will
- first be filled with that brush, before drawing the background pixmap. This can be useful for
- background pixmaps with translucent areas.
- \see setBackgroundScaled, setBackgroundScaledMode
- */
- void QCustomPlot::setBackground(const QPixmap &pm)
- {
- mBackgroundPixmap = pm;
- mScaledBackgroundPixmap = QPixmap();
- }
- /*!
- Sets the background brush of the viewport (see \ref setViewport).
- Before drawing everything else, the background is filled with \a brush. If a background pixmap
- was set with \ref setBackground(const QPixmap &pm), this brush will be used to fill the viewport
- before the background pixmap is drawn. This can be useful for background pixmaps with translucent
- areas.
-
- Set \a brush to Qt::NoBrush or Qt::Transparent to leave background transparent. This can be
- useful for exporting to image formats which support transparency, e.g. \ref savePng.
- \see setBackgroundScaled, setBackgroundScaledMode
- */
- void QCustomPlot::setBackground(const QBrush &brush)
- {
- mBackgroundBrush = brush;
- }
- /*! \overload
-
- Allows setting the background pixmap of the viewport, whether it shall be scaled and how it
- shall be scaled in one call.
- \see setBackground(const QPixmap &pm), setBackgroundScaled, setBackgroundScaledMode
- */
- void QCustomPlot::setBackground(const QPixmap &pm, bool scaled, Qt::AspectRatioMode mode)
- {
- mBackgroundPixmap = pm;
- mScaledBackgroundPixmap = QPixmap();
- mBackgroundScaled = scaled;
- mBackgroundScaledMode = mode;
- }
- /*!
- Sets whether the viewport background pixmap shall be scaled to fit the viewport. If \a scaled is
- set to true, control whether and how the aspect ratio of the original pixmap is preserved with
- \ref setBackgroundScaledMode.
-
- Note that the scaled version of the original pixmap is buffered, so there is no performance
- penalty on replots. (Except when the viewport dimensions are changed continuously.)
-
- \see setBackground, setBackgroundScaledMode
- */
- void QCustomPlot::setBackgroundScaled(bool scaled)
- {
- mBackgroundScaled = scaled;
- }
- /*!
- If scaling of the viewport background pixmap is enabled (\ref setBackgroundScaled), use this
- function to define whether and how the aspect ratio of the original pixmap is preserved.
-
- \see setBackground, setBackgroundScaled
- */
- void QCustomPlot::setBackgroundScaledMode(Qt::AspectRatioMode mode)
- {
- mBackgroundScaledMode = mode;
- }
- /*!
- Returns the plottable with \a index. If the index is invalid, returns 0.
-
- There is an overloaded version of this function with no parameter which returns the last added
- plottable, see QCustomPlot::plottable()
-
- \see plottableCount
- */
- QCPAbstractPlottable *QCustomPlot::plottable(int index)
- {
- if (index >= 0 && index < mPlottables.size())
- {
- return mPlottables.at(index);
- } else
- {
- qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
- return 0;
- }
- }
- /*! \overload
-
- Returns the last plottable that was added to the plot. If there are no plottables in the plot,
- returns 0.
-
- \see plottableCount
- */
- QCPAbstractPlottable *QCustomPlot::plottable()
- {
- if (!mPlottables.isEmpty())
- {
- return mPlottables.last();
- } else
- return 0;
- }
- /*!
- Removes the specified plottable from the plot and deletes it. If necessary, the corresponding
- legend item is also removed from the default legend (QCustomPlot::legend).
-
- Returns true on success.
-
- \see clearPlottables
- */
- bool QCustomPlot::removePlottable(QCPAbstractPlottable *plottable)
- {
- if (!mPlottables.contains(plottable))
- {
- qDebug() << Q_FUNC_INFO << "plottable not in list:" << reinterpret_cast<quintptr>(plottable);
- return false;
- }
-
- // remove plottable from legend:
- plottable->removeFromLegend();
- // special handling for QCPGraphs to maintain the simple graph interface:
- if (QCPGraph *graph = qobject_cast<QCPGraph*>(plottable))
- mGraphs.removeOne(graph);
- // remove plottable:
- delete plottable;
- mPlottables.removeOne(plottable);
- return true;
- }
- /*! \overload
-
- Removes and deletes the plottable by its \a index.
- */
- bool QCustomPlot::removePlottable(int index)
- {
- if (index >= 0 && index < mPlottables.size())
- return removePlottable(mPlottables[index]);
- else
- {
- qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
- return false;
- }
- }
- /*!
- Removes all plottables from the plot and deletes them. Corresponding legend items are also
- removed from the default legend (QCustomPlot::legend).
-
- Returns the number of plottables removed.
-
- \see removePlottable
- */
- int QCustomPlot::clearPlottables()
- {
- int c = mPlottables.size();
- for (int i=c-1; i >= 0; --i)
- removePlottable(mPlottables[i]);
- return c;
- }
- /*!
- Returns the number of currently existing plottables in the plot
-
- \see plottable
- */
- int QCustomPlot::plottableCount() const
- {
- return mPlottables.size();
- }
- /*!
- Returns a list of the selected plottables. If no plottables are currently selected, the list is empty.
-
- There is a convenience function if you're only interested in selected graphs, see \ref selectedGraphs.
-
- \see setInteractions, QCPAbstractPlottable::setSelectable, QCPAbstractPlottable::setSelection
- */
- QList<QCPAbstractPlottable*> QCustomPlot::selectedPlottables() const
- {
- QList<QCPAbstractPlottable*> result;
- foreach (QCPAbstractPlottable *plottable, mPlottables)
- {
- if (plottable->selected())
- result.append(plottable);
- }
- return result;
- }
- /*!
- Returns the plottable at the pixel position \a pos. Plottables that only consist of single lines
- (like graphs) have a tolerance band around them, see \ref setSelectionTolerance. If multiple
- plottables come into consideration, the one closest to \a pos is returned.
-
- If \a onlySelectable is true, only plottables that are selectable
- (QCPAbstractPlottable::setSelectable) are considered.
-
- If there is no plottable at \a pos, the return value is 0.
-
- \see itemAt, layoutElementAt
- */
- QCPAbstractPlottable *QCustomPlot::plottableAt(const QPointF &pos, bool onlySelectable) const
- {
- QCPAbstractPlottable *resultPlottable = 0;
- double resultDistance = mSelectionTolerance; // only regard clicks with distances smaller than mSelectionTolerance as selections, so initialize with that value
-
- foreach (QCPAbstractPlottable *plottable, mPlottables)
- {
- if (onlySelectable && !plottable->selectable()) // we could have also passed onlySelectable to the selectTest function, but checking here is faster, because we have access to QCPabstractPlottable::selectable
- continue;
- if ((plottable->keyAxis()->axisRect()->rect() & plottable->valueAxis()->axisRect()->rect()).contains(pos.toPoint())) // only consider clicks inside the rect that is spanned by the plottable's key/value axes
- {
- double currentDistance = plottable->selectTest(pos, false);
- if (currentDistance >= 0 && currentDistance < resultDistance)
- {
- resultPlottable = plottable;
- resultDistance = currentDistance;
- }
- }
- }
-
- return resultPlottable;
- }
- /*!
- Returns whether this QCustomPlot instance contains the \a plottable.
- */
- bool QCustomPlot::hasPlottable(QCPAbstractPlottable *plottable) const
- {
- return mPlottables.contains(plottable);
- }
- /*!
- Returns the graph with \a index. If the index is invalid, returns 0.
-
- There is an overloaded version of this function with no parameter which returns the last created
- graph, see QCustomPlot::graph()
-
- \see graphCount, addGraph
- */
- QCPGraph *QCustomPlot::graph(int index) const
- {
- if (index >= 0 && index < mGraphs.size())
- {
- return mGraphs.at(index);
- } else
- {
- qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
- return 0;
- }
- }
- /*! \overload
-
- Returns the last graph, that was created with \ref addGraph. If there are no graphs in the plot,
- returns 0.
-
- \see graphCount, addGraph
- */
- QCPGraph *QCustomPlot::graph() const
- {
- if (!mGraphs.isEmpty())
- {
- return mGraphs.last();
- } else
- return 0;
- }
- /*!
- Creates a new graph inside the plot. If \a keyAxis and \a valueAxis are left unspecified (0), the
- bottom (xAxis) is used as key and the left (yAxis) is used as value axis. If specified, \a
- keyAxis and \a valueAxis must reside in this QCustomPlot.
-
- \a keyAxis will be used as key axis (typically "x") and \a valueAxis as value axis (typically
- "y") for the graph.
-
- Returns a pointer to the newly created graph, or 0 if adding the graph failed.
-
- \see graph, graphCount, removeGraph, clearGraphs
- */
- QCPGraph *QCustomPlot::addGraph(QCPAxis *keyAxis, QCPAxis *valueAxis)
- {
- if (!keyAxis) keyAxis = xAxis;
- if (!valueAxis) valueAxis = yAxis;
- if (!keyAxis || !valueAxis)
- {
- qDebug() << Q_FUNC_INFO << "can't use default QCustomPlot xAxis or yAxis, because at least one is invalid (has been deleted)";
- return 0;
- }
- if (keyAxis->parentPlot() != this || valueAxis->parentPlot() != this)
- {
- qDebug() << Q_FUNC_INFO << "passed keyAxis or valueAxis doesn't have this QCustomPlot as parent";
- return 0;
- }
-
- QCPGraph *newGraph = new QCPGraph(keyAxis, valueAxis);
- newGraph->setName(QLatin1String("Graph ")+QString::number(mGraphs.size()));
- return newGraph;
- }
- /*!
- Removes the specified \a graph from the plot and deletes it. If necessary, the corresponding
- legend item is also removed from the default legend (QCustomPlot::legend). If any other graphs in
- the plot have a channel fill set towards the removed graph, the channel fill property of those
- graphs is reset to zero (no channel fill).
-
- Returns true on success.
-
- \see clearGraphs
- */
- bool QCustomPlot::removeGraph(QCPGraph *graph)
- {
- return removePlottable(graph);
- }
- /*! \overload
-
- Removes and deletes the graph by its \a index.
- */
- bool QCustomPlot::removeGraph(int index)
- {
- if (index >= 0 && index < mGraphs.size())
- return removeGraph(mGraphs[index]);
- else
- return false;
- }
- /*!
- Removes all graphs from the plot and deletes them. Corresponding legend items are also removed
- from the default legend (QCustomPlot::legend).
- Returns the number of graphs removed.
-
- \see removeGraph
- */
- int QCustomPlot::clearGraphs()
- {
- int c = mGraphs.size();
- for (int i=c-1; i >= 0; --i)
- removeGraph(mGraphs[i]);
- return c;
- }
- /*!
- Returns the number of currently existing graphs in the plot
-
- \see graph, addGraph
- */
- int QCustomPlot::graphCount() const
- {
- return mGraphs.size();
- }
- /*!
- Returns a list of the selected graphs. If no graphs are currently selected, the list is empty.
-
- If you are not only interested in selected graphs but other plottables like QCPCurve, QCPBars,
- etc., use \ref selectedPlottables.
-
- \see setInteractions, selectedPlottables, QCPAbstractPlottable::setSelectable, QCPAbstractPlottable::setSelection
- */
- QList<QCPGraph*> QCustomPlot::selectedGraphs() const
- {
- QList<QCPGraph*> result;
- foreach (QCPGraph *graph, mGraphs)
- {
- if (graph->selected())
- result.append(graph);
- }
- return result;
- }
- /*!
- Returns the item with \a index. If the index is invalid, returns 0.
-
- There is an overloaded version of this function with no parameter which returns the last added
- item, see QCustomPlot::item()
-
- \see itemCount
- */
- QCPAbstractItem *QCustomPlot::item(int index) const
- {
- if (index >= 0 && index < mItems.size())
- {
- return mItems.at(index);
- } else
- {
- qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
- return 0;
- }
- }
- /*! \overload
-
- Returns the last item that was added to this plot. If there are no items in the plot,
- returns 0.
-
- \see itemCount
- */
- QCPAbstractItem *QCustomPlot::item() const
- {
- if (!mItems.isEmpty())
- {
- return mItems.last();
- } else
- return 0;
- }
- /*!
- Removes the specified item from the plot and deletes it.
-
- Returns true on success.
-
- \see clearItems
- */
- bool QCustomPlot::removeItem(QCPAbstractItem *item)
- {
- if (mItems.contains(item))
- {
- delete item;
- mItems.removeOne(item);
- return true;
- } else
- {
- qDebug() << Q_FUNC_INFO << "item not in list:" << reinterpret_cast<quintptr>(item);
- return false;
- }
- }
- /*! \overload
-
- Removes and deletes the item by its \a index.
- */
- bool QCustomPlot::removeItem(int index)
- {
- if (index >= 0 && index < mItems.size())
- return removeItem(mItems[index]);
- else
- {
- qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
- return false;
- }
- }
- /*!
- Removes all items from the plot and deletes them.
-
- Returns the number of items removed.
-
- \see removeItem
- */
- int QCustomPlot::clearItems()
- {
- int c = mItems.size();
- for (int i=c-1; i >= 0; --i)
- removeItem(mItems[i]);
- return c;
- }
- /*!
- Returns the number of currently existing items in the plot
-
- \see item
- */
- int QCustomPlot::itemCount() const
- {
- return mItems.size();
- }
- /*!
- Returns a list of the selected items. If no items are currently selected, the list is empty.
-
- \see setInteractions, QCPAbstractItem::setSelectable, QCPAbstractItem::setSelected
- */
- QList<QCPAbstractItem*> QCustomPlot::selectedItems() const
- {
- QList<QCPAbstractItem*> result;
- foreach (QCPAbstractItem *item, mItems)
- {
- if (item->selected())
- result.append(item);
- }
- return result;
- }
- /*!
- Returns the item at the pixel position \a pos. Items that only consist of single lines (e.g. \ref
- QCPItemLine or \ref QCPItemCurve) have a tolerance band around them, see \ref
- setSelectionTolerance. If multiple items come into consideration, the one closest to \a pos is
- returned.
-
- If \a onlySelectable is true, only items that are selectable (QCPAbstractItem::setSelectable) are
- considered.
-
- If there is no item at \a pos, the return value is 0.
-
- \see plottableAt, layoutElementAt
- */
- QCPAbstractItem *QCustomPlot::itemAt(const QPointF &pos, bool onlySelectable) const
- {
- QCPAbstractItem *resultItem = 0;
- double resultDistance = mSelectionTolerance; // only regard clicks with distances smaller than mSelectionTolerance as selections, so initialize with that value
-
- foreach (QCPAbstractItem *item, mItems)
- {
- if (onlySelectable && !item->selectable()) // we could have also passed onlySelectable to the selectTest function, but checking here is faster, because we have access to QCPAbstractItem::selectable
- continue;
- if (!item->clipToAxisRect() || item->clipRect().contains(pos.toPoint())) // only consider clicks inside axis cliprect of the item if actually clipped to it
- {
- double currentDistance = item->selectTest(pos, false);
- if (currentDistance >= 0 && currentDistance < resultDistance)
- {
- resultItem = item;
- resultDistance = currentDistance;
- }
- }
- }
-
- return resultItem;
- }
- /*!
- Returns whether this QCustomPlot contains the \a item.
-
- \see item
- */
- bool QCustomPlot::hasItem(QCPAbstractItem *item) const
- {
- return mItems.contains(item);
- }
- /*!
- Returns the layer with the specified \a name. If there is no layer with the specified name, 0 is
- returned.
-
- Layer names are case-sensitive.
-
- \see addLayer, moveLayer, removeLayer
- */
- QCPLayer *QCustomPlot::layer(const QString &name) const
- {
- foreach (QCPLayer *layer, mLayers)
- {
- if (layer->name() == name)
- return layer;
- }
- return 0;
- }
- /*! \overload
-
- Returns the layer by \a index. If the index is invalid, 0 is returned.
-
- \see addLayer, moveLayer, removeLayer
- */
- QCPLayer *QCustomPlot::layer(int index) const
- {
- if (index >= 0 && index < mLayers.size())
- {
- return mLayers.at(index);
- } else
- {
- qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
- return 0;
- }
- }
- /*!
- Returns the layer that is set as current layer (see \ref setCurrentLayer).
- */
- QCPLayer *QCustomPlot::currentLayer() const
- {
- return mCurrentLayer;
- }
- /*!
- Sets the layer with the specified \a name to be the current layer. All layerables (\ref
- QCPLayerable), e.g. plottables and items, are created on the current layer.
-
- Returns true on success, i.e. if there is a layer with the specified \a name in the QCustomPlot.
-
- Layer names are case-sensitive.
-
- \see addLayer, moveLayer, removeLayer, QCPLayerable::setLayer
- */
- bool QCustomPlot::setCurrentLayer(const QString &name)
- {
- if (QCPLayer *newCurrentLayer = layer(name))
- {
- return setCurrentLayer(newCurrentLayer);
- } else
- {
- qDebug() << Q_FUNC_INFO << "layer with name doesn't exist:" << name;
- return false;
- }
- }
- /*! \overload
-
- Sets the provided \a layer to be the current layer.
-
- Returns true on success, i.e. when \a layer is a valid layer in the QCustomPlot.
-
- \see addLayer, moveLayer, removeLayer
- */
- bool QCustomPlot::setCurrentLayer(QCPLayer *layer)
- {
- if (!mLayers.contains(layer))
- {
- qDebug() << Q_FUNC_INFO << "layer not a layer of this QCustomPlot:" << reinterpret_cast<quintptr>(layer);
- return false;
- }
-
- mCurrentLayer = layer;
- return true;
- }
- /*!
- Returns the number of currently existing layers in the plot
-
- \see layer, addLayer
- */
- int QCustomPlot::layerCount() const
- {
- return mLayers.size();
- }
- /*!
- Adds a new layer to this QCustomPlot instance. The new layer will have the name \a name, which
- must be unique. Depending on \a insertMode, it is positioned either below or above \a otherLayer.
-
- Returns true on success, i.e. if there is no other layer named \a name and \a otherLayer is a
- valid layer inside this QCustomPlot.
-
- If \a otherLayer is 0, the highest layer in the QCustomPlot will be used.
-
- For an explanation of what layers are in QCustomPlot, see the documentation of \ref QCPLayer.
-
- \see layer, moveLayer, removeLayer
- */
- bool QCustomPlot::addLayer(const QString &name, QCPLayer *otherLayer, QCustomPlot::LayerInsertMode insertMode)
- {
- if (!otherLayer)
- otherLayer = mLayers.last();
- if (!mLayers.contains(otherLayer))
- {
- qDebug() << Q_FUNC_INFO << "otherLayer not a layer of this QCustomPlot:" << reinterpret_cast<quintptr>(otherLayer);
- return false;
- }
- if (layer(name))
- {
- qDebug() << Q_FUNC_INFO << "A layer exists already with the name" << name;
- return false;
- }
-
- QCPLayer *newLayer = new QCPLayer(this, name);
- mLayers.insert(otherLayer->index() + (insertMode==limAbove ? 1:0), newLayer);
- updateLayerIndices();
- setupPaintBuffers(); // associates new layer with the appropriate paint buffer
- return true;
- }
- /*!
- Removes the specified \a layer and returns true on success.
-
- All layerables (e.g. plottables and items) on the removed layer will be moved to the layer below
- \a layer. If \a layer is the bottom layer, the layerables are moved to the layer above. In both
- cases, the total rendering order of all layerables in the QCustomPlot is preserved.
-
- If \a layer is the current layer (\ref setCurrentLayer), the layer below (or above, if bottom
- layer) becomes the new current layer.
-
- It is not possible to remove the last layer of the plot.
-
- \see layer, addLayer, moveLayer
- */
- bool QCustomPlot::removeLayer(QCPLayer *layer)
- {
- if (!mLayers.contains(layer))
- {
- qDebug() << Q_FUNC_INFO << "layer not a layer of this QCustomPlot:" << reinterpret_cast<quintptr>(layer);
- return false;
- }
- if (mLayers.size() < 2)
- {
- qDebug() << Q_FUNC_INFO << "can't remove last layer";
- return false;
- }
-
- // append all children of this layer to layer below (if this is lowest layer, prepend to layer above)
- int removedIndex = layer->index();
- bool isFirstLayer = removedIndex==0;
- QCPLayer *targetLayer = isFirstLayer ? mLayers.at(removedIndex+1) : mLayers.at(removedIndex-1);
- QList<QCPLayerable*> children = layer->children();
- if (isFirstLayer) // prepend in reverse order (so order relative to each other stays the same)
- {
- for (int i=children.size()-1; i>=0; --i)
- children.at(i)->moveToLayer(targetLayer, true);
- } else // append normally
- {
- for (int i=0; i<children.size(); ++i)
- children.at(i)->moveToLayer(targetLayer, false);
- }
- // if removed layer is current layer, change current layer to layer below/above:
- if (layer == mCurrentLayer)
- setCurrentLayer(targetLayer);
- // invalidate the paint buffer that was responsible for this layer:
- if (!layer->mPaintBuffer.isNull())
- layer->mPaintBuffer.data()->setInvalidated();
- // remove layer:
- delete layer;
- mLayers.removeOne(layer);
- updateLayerIndices();
- return true;
- }
- /*!
- Moves the specified \a layer either above or below \a otherLayer. Whether it's placed above or
- below is controlled with \a insertMode.
-
- Returns true on success, i.e. when both \a layer and \a otherLayer are valid layers in the
- QCustomPlot.
-
- \see layer, addLayer, moveLayer
- */
- bool QCustomPlot::moveLayer(QCPLayer *layer, QCPLayer *otherLayer, QCustomPlot::LayerInsertMode insertMode)
- {
- if (!mLayers.contains(layer))
- {
- qDebug() << Q_FUNC_INFO << "layer not a layer of this QCustomPlot:" << reinterpret_cast<quintptr>(layer);
- return false;
- }
- if (!mLayers.contains(otherLayer))
- {
- qDebug() << Q_FUNC_INFO << "otherLayer not a layer of this QCustomPlot:" << reinterpret_cast<quintptr>(otherLayer);
- return false;
- }
-
- if (layer->index() > otherLayer->index())
- mLayers.move(layer->index(), otherLayer->index() + (insertMode==limAbove ? 1:0));
- else if (layer->index() < otherLayer->index())
- mLayers.move(layer->index(), otherLayer->index() + (insertMode==limAbove ? 0:-1));
-
- // invalidate the paint buffers that are responsible for the layers:
- if (!layer->mPaintBuffer.isNull())
- layer->mPaintBuffer.data()->setInvalidated();
- if (!otherLayer->mPaintBuffer.isNull())
- otherLayer->mPaintBuffer.data()->setInvalidated();
-
- updateLayerIndices();
- return true;
- }
- /*!
- Returns the number of axis rects in the plot.
-
- All axis rects can be accessed via QCustomPlot::axisRect().
-
- Initially, only one axis rect exists in the plot.
-
- \see axisRect, axisRects
- */
- int QCustomPlot::axisRectCount() const
- {
- return axisRects().size();
- }
- /*!
- Returns the axis rect with \a index.
-
- Initially, only one axis rect (with index 0) exists in the plot. If multiple axis rects were
- added, all of them may be accessed with this function in a linear fashion (even when they are
- nested in a layout hierarchy or inside other axis rects via QCPAxisRect::insetLayout).
-
- \see axisRectCount, axisRects
- */
- QCPAxisRect *QCustomPlot::axisRect(int index) const
- {
- const QList<QCPAxisRect*> rectList = axisRects();
- if (index >= 0 && index < rectList.size())
- {
- return rectList.at(index);
- } else
- {
- qDebug() << Q_FUNC_INFO << "invalid axis rect index" << index;
- return 0;
- }
- }
- /*!
- Returns all axis rects in the plot.
-
- \see axisRectCount, axisRect
- */
- QList<QCPAxisRect*> QCustomPlot::axisRects() const
- {
- QList<QCPAxisRect*> result;
- QStack<QCPLayoutElement*> elementStack;
- if (mPlotLayout)
- elementStack.push(mPlotLayout);
-
- while (!elementStack.isEmpty())
- {
- foreach (QCPLayoutElement *element, elementStack.pop()->elements(false))
- {
- if (element)
- {
- elementStack.push(element);
- if (QCPAxisRect *ar = qobject_cast<QCPAxisRect*>(element))
- result.append(ar);
- }
- }
- }
-
- return result;
- }
- /*!
- Returns the layout element at pixel position \a pos. If there is no element at that position,
- returns 0.
-
- Only visible elements are used. If \ref QCPLayoutElement::setVisible on the element itself or on
- any of its parent elements is set to false, it will not be considered.
-
- \see itemAt, plottableAt
- */
- QCPLayoutElement *QCustomPlot::layoutElementAt(const QPointF &pos) const
- {
- QCPLayoutElement *currentElement = mPlotLayout;
- bool searchSubElements = true;
- while (searchSubElements && currentElement)
- {
- searchSubElements = false;
- foreach (QCPLayoutElement *subElement, currentElement->elements(false))
- {
- if (subElement && subElement->realVisibility() && subElement->selectTest(pos, false) >= 0)
- {
- currentElement = subElement;
- searchSubElements = true;
- break;
- }
- }
- }
- return currentElement;
- }
- /*!
- Returns the layout element of type \ref QCPAxisRect at pixel position \a pos. This method ignores
- other layout elements even if they are visually in front of the axis rect (e.g. a \ref
- QCPLegend). If there is no axis rect at that position, returns 0.
- Only visible axis rects are used. If \ref QCPLayoutElement::setVisible on the axis rect itself or
- on any of its parent elements is set to false, it will not be considered.
- \see layoutElementAt
- */
- QCPAxisRect *QCustomPlot::axisRectAt(const QPointF &pos) const
- {
- QCPAxisRect *result = 0;
- QCPLayoutElement *currentElement = mPlotLayout;
- bool searchSubElements = true;
- while (searchSubElements && currentElement)
- {
- searchSubElements = false;
- foreach (QCPLayoutElement *subElement, currentElement->elements(false))
- {
- if (subElement && subElement->realVisibility() && subElement->selectTest(pos, false) >= 0)
- {
- currentElement = subElement;
- searchSubElements = true;
- if (QCPAxisRect *ar = qobject_cast<QCPAxisRect*>(currentElement))
- result = ar;
- break;
- }
- }
- }
- return result;
- }
- /*!
- Returns the axes that currently have selected parts, i.e. whose selection state is not \ref
- QCPAxis::spNone.
-
- \see selectedPlottables, selectedLegends, setInteractions, QCPAxis::setSelectedParts,
- QCPAxis::setSelectableParts
- */
- QList<QCPAxis*> QCustomPlot::selectedAxes() const
- {
- QList<QCPAxis*> result, allAxes;
- foreach (QCPAxisRect *rect, axisRects())
- allAxes << rect->axes();
-
- foreach (QCPAxis *axis, allAxes)
- {
- if (axis->selectedParts() != QCPAxis::spNone)
- result.append(axis);
- }
-
- return result;
- }
- /*!
- Returns the legends that currently have selected parts, i.e. whose selection state is not \ref
- QCPLegend::spNone.
-
- \see selectedPlottables, selectedAxes, setInteractions, QCPLegend::setSelectedParts,
- QCPLegend::setSelectableParts, QCPLegend::selectedItems
- */
- QList<QCPLegend*> QCustomPlot::selectedLegends() const
- {
- QList<QCPLegend*> result;
-
- QStack<QCPLayoutElement*> elementStack;
- if (mPlotLayout)
- elementStack.push(mPlotLayout);
-
- while (!elementStack.isEmpty())
- {
- foreach (QCPLayoutElement *subElement, elementStack.pop()->elements(false))
- {
- if (subElement)
- {
- elementStack.push(subElement);
- if (QCPLegend *leg = qobject_cast<QCPLegend*>(subElement))
- {
- if (leg->selectedParts() != QCPLegend::spNone)
- result.append(leg);
- }
- }
- }
- }
-
- return result;
- }
- /*!
- Deselects all layerables (plottables, items, axes, legends,...) of the QCustomPlot.
-
- Since calling this function is not a user interaction, this does not emit the \ref
- selectionChangedByUser signal. The individual selectionChanged signals are emitted though, if the
- objects were previously selected.
-
- \see setInteractions, selectedPlottables, selectedItems, selectedAxes, selectedLegends
- */
- void QCustomPlot::deselectAll()
- {
- foreach (QCPLayer *layer, mLayers)
- {
- foreach (QCPLayerable *layerable, layer->children())
- layerable->deselectEvent(0);
- }
- }
- /*!
- Causes a complete replot into the internal paint buffer(s). Finally, the widget surface is
- refreshed with the new buffer contents. This is the method that must be called to make changes to
- the plot, e.g. on the axis ranges or data points of graphs, visible.
- The parameter \a refreshPriority can be used to fine-tune the timing of the replot. For example
- if your application calls \ref replot very quickly in succession (e.g. multiple independent
- functions change some aspects of the plot and each wants to make sure the change gets replotted),
- it is advisable to set \a refreshPriority to \ref QCustomPlot::rpQueuedReplot. This way, the
- actual replotting is deferred to the next event loop iteration. Multiple successive calls of \ref
- replot with this priority will only cause a single replot, avoiding redundant replots and
- improving performance.
- Under a few circumstances, QCustomPlot causes a replot by itself. Those are resize events of the
- QCustomPlot widget and user interactions (object selection and range dragging/zooming).
- Before the replot happens, the signal \ref beforeReplot is emitted. After the replot, \ref
- afterReplot is emitted. It is safe to mutually connect the replot slot with any of those two
- signals on two QCustomPlots to make them replot synchronously, it won't cause an infinite
- recursion.
- If a layer is in mode \ref QCPLayer::lmBuffered (\ref QCPLayer::setMode), it is also possible to
- replot only that specific layer via \ref QCPLayer::replot. See the documentation there for
- details.
- */
- void QCustomPlot::replot(QCustomPlot::RefreshPriority refreshPriority)
- {
- if (refreshPriority == QCustomPlot::rpQueuedReplot)
- {
- if (!mReplotQueued)
- {
- mReplotQueued = true;
- QTimer::singleShot(0, this, SLOT(replot()));
- }
- return;
- }
-
- if (mReplotting) // incase signals loop back to replot slot
- return;
- mReplotting = true;
- mReplotQueued = false;
- emit beforeReplot();
-
- updateLayout();
- // draw all layered objects (grid, axes, plottables, items, legend,...) into their buffers:
- setupPaintBuffers();
- foreach (QCPLayer *layer, mLayers)
- layer->drawToPaintBuffer();
- for (int i=0; i<mPaintBuffers.size(); ++i)
- mPaintBuffers.at(i)->setInvalidated(false);
-
- if ((refreshPriority == rpRefreshHint && mPlottingHints.testFlag(QCP::phImmediateRefresh)) || refreshPriority==rpImmediateRefresh)
- repaint();
- else
- update();
-
- emit afterReplot();
- mReplotting = false;
- }
- /*!
- Rescales the axes such that all plottables (like graphs) in the plot are fully visible.
-
- if \a onlyVisiblePlottables is set to true, only the plottables that have their visibility set to true
- (QCPLayerable::setVisible), will be used to rescale the axes.
-
- \see QCPAbstractPlottable::rescaleAxes, QCPAxis::rescale
- */
- void QCustomPlot::rescaleAxes(bool onlyVisiblePlottables)
- {
- QList<QCPAxis*> allAxes;
- foreach (QCPAxisRect *rect, axisRects())
- allAxes << rect->axes();
-
- foreach (QCPAxis *axis, allAxes)
- axis->rescale(onlyVisiblePlottables);
- }
- /*!
- Saves a PDF with the vectorized plot to the file \a fileName. The axis ratio as well as the scale
- of texts and lines will be derived from the specified \a width and \a height. This means, the
- output will look like the normal on-screen output of a QCustomPlot widget with the corresponding
- pixel width and height. If either \a width or \a height is zero, the exported image will have the
- same dimensions as the QCustomPlot widget currently has.
- Setting \a exportPen to \ref QCP::epNoCosmetic allows to disable the use of cosmetic pens when
- drawing to the PDF file. Cosmetic pens are pens with numerical width 0, which are always drawn as
- a one pixel wide line, no matter what zoom factor is set in the PDF-Viewer. For more information
- about cosmetic pens, see the QPainter and QPen documentation.
- The objects of the plot will appear in the current selection state. If you don't want any
- selected objects to be painted in their selected look, deselect everything with \ref deselectAll
- before calling this function.
- Returns true on success.
- \warning
- \li If you plan on editing the exported PDF file with a vector graphics editor like Inkscape, it
- is advised to set \a exportPen to \ref QCP::epNoCosmetic to avoid losing those cosmetic lines
- (which might be quite many, because cosmetic pens are the default for e.g. axes and tick marks).
- \li If calling this function inside the constructor of the parent of the QCustomPlot widget
- (i.e. the MainWindow constructor, if QCustomPlot is inside the MainWindow), always provide
- explicit non-zero widths and heights. If you leave \a width or \a height as 0 (default), this
- function uses the current width and height of the QCustomPlot widget. However, in Qt, these
- aren't defined yet inside the constructor, so you would get an image that has strange
- widths/heights.
- \a pdfCreator and \a pdfTitle may be used to set the according metadata fields in the resulting
- PDF file.
- \note On Android systems, this method does nothing and issues an according qDebug warning
- message. This is also the case if for other reasons the define flag \c QT_NO_PRINTER is set.
- \see savePng, saveBmp, saveJpg, saveRastered
- */
- bool QCustomPlot::savePdf(const QString &fileName, int width, int height, QCP::ExportPen exportPen, const QString &pdfCreator, const QString &pdfTitle)
- {
- bool success = false;
- #ifdef QT_NO_PRINTER
- Q_UNUSED(fileName)
- Q_UNUSED(exportPen)
- Q_UNUSED(width)
- Q_UNUSED(height)
- Q_UNUSED(pdfCreator)
- Q_UNUSED(pdfTitle)
- qDebug() << Q_FUNC_INFO << "Qt was built without printer support (QT_NO_PRINTER). PDF not created.";
- #else
- int newWidth, newHeight;
- if (width == 0 || height == 0)
- {
- newWidth = this->width();
- newHeight = this->height();
- } else
- {
- newWidth = width;
- newHeight = height;
- }
-
- QPrinter printer(QPrinter::ScreenResolution);
- printer.setOutputFileName(fileName);
- printer.setOutputFormat(QPrinter::PdfFormat);
- printer.setColorMode(QPrinter::Color);
- printer.printEngine()->setProperty(QPrintEngine::PPK_Creator, pdfCreator);
- printer.printEngine()->setProperty(QPrintEngine::PPK_DocumentName, pdfTitle);
- QRect oldViewport = viewport();
- setViewport(QRect(0, 0, newWidth, newHeight));
- #if QT_VERSION < QT_VERSION_CHECK(5, 3, 0)
- printer.setFullPage(true);
- printer.setPaperSize(viewport().size(), QPrinter::DevicePixel);
- #else
- QPageLayout pageLayout;
- pageLayout.setMode(QPageLayout::FullPageMode);
- pageLayout.setOrientation(QPageLayout::Portrait);
- pageLayout.setMargins(QMarginsF(0, 0, 0, 0));
- pageLayout.setPageSize(QPageSize(viewport().size(), QPageSize::Point, QString(), QPageSize::ExactMatch));
- printer.setPageLayout(pageLayout);
- #endif
- QCPPainter printpainter;
- if (printpainter.begin(&printer))
- {
- printpainter.setMode(QCPPainter::pmVectorized);
- printpainter.setMode(QCPPainter::pmNoCaching);
- printpainter.setMode(QCPPainter::pmNonCosmetic, exportPen==QCP::epNoCosmetic);
- printpainter.setWindow(mViewport);
- if (mBackgroundBrush.style() != Qt::NoBrush &&
- mBackgroundBrush.color() != Qt::white &&
- mBackgroundBrush.color() != Qt::transparent &&
- mBackgroundBrush.color().alpha() > 0) // draw pdf background color if not white/transparent
- printpainter.fillRect(viewport(), mBackgroundBrush);
- draw(&printpainter);
- printpainter.end();
- success = true;
- }
- setViewport(oldViewport);
- #endif // QT_NO_PRINTER
- return success;
- }
- /*!
- Saves a PNG image file to \a fileName on disc. The output plot will have the dimensions \a width
- and \a height in pixels, multiplied by \a scale. If either \a width or \a height is zero, the
- current width and height of the QCustomPlot widget is used instead. Line widths and texts etc.
- are not scaled up when larger widths/heights are used. If you want that effect, use the \a scale
- parameter.
- For example, if you set both \a width and \a height to 100 and \a scale to 2, you will end up with an
- image file of size 200*200 in which all graphical elements are scaled up by factor 2 (line widths,
- texts, etc.). This scaling is not done by stretching a 100*100 image, the result will have full
- 200*200 pixel resolution.
- If you use a high scaling factor, it is recommended to enable antialiasing for all elements by
- temporarily setting \ref QCustomPlot::setAntialiasedElements to \ref QCP::aeAll as this allows
- QCustomPlot to place objects with sub-pixel accuracy.
- image compression can be controlled with the \a quality parameter which must be between 0 and 100
- or -1 to use the default setting.
- The \a resolution will be written to the image file header and has no direct consequence for the
- quality or the pixel size. However, if opening the image with a tool which respects the metadata,
- it will be able to scale the image to match either a given size in real units of length (inch,
- centimeters, etc.), or the target display DPI. You can specify in which units \a resolution is
- given, by setting \a resolutionUnit. The \a resolution is converted to the format's expected
- resolution unit internally.
- Returns true on success. If this function fails, most likely the PNG format isn't supported by
- the system, see Qt docs about QImageWriter::supportedImageFormats().
- The objects of the plot will appear in the current selection state. If you don't want any selected
- objects to be painted in their selected look, deselect everything with \ref deselectAll before calling
- this function.
- If you want the PNG to have a transparent background, call \ref setBackground(const QBrush &brush)
- with no brush (Qt::NoBrush) or a transparent color (Qt::transparent), before saving.
- \warning If calling this function inside the constructor of the parent of the QCustomPlot widget
- (i.e. the MainWindow constructor, if QCustomPlot is inside the MainWindow), always provide
- explicit non-zero widths and heights. If you leave \a width or \a height as 0 (default), this
- function uses the current width and height of the QCustomPlot widget. However, in Qt, these
- aren't defined yet inside the constructor, so you would get an image that has strange
- widths/heights.
- \see savePdf, saveBmp, saveJpg, saveRastered
- */
- bool QCustomPlot::savePng(const QString &fileName, int width, int height, double scale, int quality, int resolution, QCP::ResolutionUnit resolutionUnit)
- {
- return saveRastered(fileName, width, height, scale, "PNG", quality, resolution, resolutionUnit);
- }
- /*!
- Saves a JPEG image file to \a fileName on disc. The output plot will have the dimensions \a width
- and \a height in pixels, multiplied by \a scale. If either \a width or \a height is zero, the
- current width and height of the QCustomPlot widget is used instead. Line widths and texts etc.
- are not scaled up when larger widths/heights are used. If you want that effect, use the \a scale
- parameter.
- For example, if you set both \a width and \a height to 100 and \a scale to 2, you will end up with an
- image file of size 200*200 in which all graphical elements are scaled up by factor 2 (line widths,
- texts, etc.). This scaling is not done by stretching a 100*100 image, the result will have full
- 200*200 pixel resolution.
- If you use a high scaling factor, it is recommended to enable antialiasing for all elements by
- temporarily setting \ref QCustomPlot::setAntialiasedElements to \ref QCP::aeAll as this allows
- QCustomPlot to place objects with sub-pixel accuracy.
- image compression can be controlled with the \a quality parameter which must be between 0 and 100
- or -1 to use the default setting.
- The \a resolution will be written to the image file header and has no direct consequence for the
- quality or the pixel size. However, if opening the image with a tool which respects the metadata,
- it will be able to scale the image to match either a given size in real units of length (inch,
- centimeters, etc.), or the target display DPI. You can specify in which units \a resolution is
- given, by setting \a resolutionUnit. The \a resolution is converted to the format's expected
- resolution unit internally.
- Returns true on success. If this function fails, most likely the JPEG format isn't supported by
- the system, see Qt docs about QImageWriter::supportedImageFormats().
- The objects of the plot will appear in the current selection state. If you don't want any selected
- objects to be painted in their selected look, deselect everything with \ref deselectAll before calling
- this function.
- \warning If calling this function inside the constructor of the parent of the QCustomPlot widget
- (i.e. the MainWindow constructor, if QCustomPlot is inside the MainWindow), always provide
- explicit non-zero widths and heights. If you leave \a width or \a height as 0 (default), this
- function uses the current width and height of the QCustomPlot widget. However, in Qt, these
- aren't defined yet inside the constructor, so you would get an image that has strange
- widths/heights.
- \see savePdf, savePng, saveBmp, saveRastered
- */
- bool QCustomPlot::saveJpg(const QString &fileName, int width, int height, double scale, int quality, int resolution, QCP::ResolutionUnit resolutionUnit)
- {
- return saveRastered(fileName, width, height, scale, "JPG", quality, resolution, resolutionUnit);
- }
- /*!
- Saves a BMP image file to \a fileName on disc. The output plot will have the dimensions \a width
- and \a height in pixels, multiplied by \a scale. If either \a width or \a height is zero, the
- current width and height of the QCustomPlot widget is used instead. Line widths and texts etc.
- are not scaled up when larger widths/heights are used. If you want that effect, use the \a scale
- parameter.
- For example, if you set both \a width and \a height to 100 and \a scale to 2, you will end up with an
- image file of size 200*200 in which all graphical elements are scaled up by factor 2 (line widths,
- texts, etc.). This scaling is not done by stretching a 100*100 image, the result will have full
- 200*200 pixel resolution.
- If you use a high scaling factor, it is recommended to enable antialiasing for all elements by
- temporarily setting \ref QCustomPlot::setAntialiasedElements to \ref QCP::aeAll as this allows
- QCustomPlot to place objects with sub-pixel accuracy.
- The \a resolution will be written to the image file header and has no direct consequence for the
- quality or the pixel size. However, if opening the image with a tool which respects the metadata,
- it will be able to scale the image to match either a given size in real units of length (inch,
- centimeters, etc.), or the target display DPI. You can specify in which units \a resolution is
- given, by setting \a resolutionUnit. The \a resolution is converted to the format's expected
- resolution unit internally.
- Returns true on success. If this function fails, most likely the BMP format isn't supported by
- the system, see Qt docs about QImageWriter::supportedImageFormats().
- The objects of the plot will appear in the current selection state. If you don't want any selected
- objects to be painted in their selected look, deselect everything with \ref deselectAll before calling
- this function.
- \warning If calling this function inside the constructor of the parent of the QCustomPlot widget
- (i.e. the MainWindow constructor, if QCustomPlot is inside the MainWindow), always provide
- explicit non-zero widths and heights. If you leave \a width or \a height as 0 (default), this
- function uses the current width and height of the QCustomPlot widget. However, in Qt, these
- aren't defined yet inside the constructor, so you would get an image that has strange
- widths/heights.
- \see savePdf, savePng, saveJpg, saveRastered
- */
- bool QCustomPlot::saveBmp(const QString &fileName, int width, int height, double scale, int resolution, QCP::ResolutionUnit resolutionUnit)
- {
- return saveRastered(fileName, width, height, scale, "BMP", -1, resolution, resolutionUnit);
- }
- /*! \internal
-
- Returns a minimum size hint that corresponds to the minimum size of the top level layout
- (\ref plotLayout). To prevent QCustomPlot from being collapsed to size/width zero, set a minimum
- size (setMinimumSize) either on the whole QCustomPlot or on any layout elements inside the plot.
- This is especially important, when placed in a QLayout where other components try to take in as
- much space as possible (e.g. QMdiArea).
- */
- QSize QCustomPlot::minimumSizeHint() const
- {
- return mPlotLayout->minimumOuterSizeHint();
- }
- /*! \internal
-
- Returns a size hint that is the same as \ref minimumSizeHint.
-
- */
- QSize QCustomPlot::sizeHint() const
- {
- return mPlotLayout->minimumOuterSizeHint();
- }
- /*! \internal
-
- Event handler for when the QCustomPlot widget needs repainting. This does not cause a \ref replot, but
- draws the internal buffer on the widget surface.
- */
- void QCustomPlot::paintEvent(QPaintEvent *event)
- {
- Q_UNUSED(event);
- QCPPainter painter(this);
- if (painter.isActive())
- {
- painter.setRenderHint(QPainter::HighQualityAntialiasing); // to make Antialiasing look good if using the OpenGL graphicssystem
- if (mBackgroundBrush.style() != Qt::NoBrush)
- painter.fillRect(mViewport, mBackgroundBrush);
- drawBackground(&painter);
- for (int bufferIndex = 0; bufferIndex < mPaintBuffers.size(); ++bufferIndex)
- mPaintBuffers.at(bufferIndex)->draw(&painter);
- }
- }
- /*! \internal
-
- Event handler for a resize of the QCustomPlot widget. The viewport (which becomes the outer rect
- of mPlotLayout) is resized appropriately. Finally a \ref replot is performed.
- */
- void QCustomPlot::resizeEvent(QResizeEvent *event)
- {
- Q_UNUSED(event)
- // resize and repaint the buffer:
- setViewport(rect());
- replot(rpQueuedRefresh); // queued refresh is important here, to prevent painting issues in some contexts (e.g. MDI subwindow)
- }
- /*! \internal
-
- Event handler for when a double click occurs. Emits the \ref mouseDoubleClick signal, then
- determines the layerable under the cursor and forwards the event to it. Finally, emits the
- specialized signals when certain objecs are clicked (e.g. \ref plottableDoubleClick, \ref
- axisDoubleClick, etc.).
-
- \see mousePressEvent, mouseReleaseEvent
- */
- void QCustomPlot::mouseDoubleClickEvent(QMouseEvent *event)
- {
- emit mouseDoubleClick(event);
- mMouseHasMoved = false;
- mMousePressPos = event->pos();
-
- // determine layerable under the cursor (this event is called instead of the second press event in a double-click):
- QList<QVariant> details;
- QList<QCPLayerable*> candidates = layerableListAt(mMousePressPos, false, &details);
- for (int i=0; i<candidates.size(); ++i)
- {
- event->accept(); // default impl of QCPLayerable's mouse events ignore the event, in that case propagate to next candidate in list
- candidates.at(i)->mouseDoubleClickEvent(event, details.at(i));
- if (event->isAccepted())
- {
- mMouseEventLayerable = candidates.at(i);
- mMouseEventLayerableDetails = details.at(i);
- break;
- }
- }
-
- // emit specialized object double click signals:
- if (!candidates.isEmpty())
- {
- if (QCPAbstractPlottable *ap = qobject_cast<QCPAbstractPlottable*>(candidates.first()))
- {
- int dataIndex = 0;
- if (!details.first().value<QCPDataSelection>().isEmpty())
- dataIndex = details.first().value<QCPDataSelection>().dataRange().begin();
- emit plottableDoubleClick(ap, dataIndex, event);
- } else if (QCPAxis *ax = qobject_cast<QCPAxis*>(candidates.first()))
- emit axisDoubleClick(ax, details.first().value<QCPAxis::SelectablePart>(), event);
- else if (QCPAbstractItem *ai = qobject_cast<QCPAbstractItem*>(candidates.first()))
- emit itemDoubleClick(ai, event);
- else if (QCPLegend *lg = qobject_cast<QCPLegend*>(candidates.first()))
- emit legendDoubleClick(lg, 0, event);
- else if (QCPAbstractLegendItem *li = qobject_cast<QCPAbstractLegendItem*>(candidates.first()))
- emit legendDoubleClick(li->parentLegend(), li, event);
- }
-
- event->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event.
- }
- /*! \internal
-
- Event handler for when a mouse button is pressed. Emits the mousePress signal.
- If the current \ref setSelectionRectMode is not \ref QCP::srmNone, passes the event to the
- selection rect. Otherwise determines the layerable under the cursor and forwards the event to it.
-
- \see mouseMoveEvent, mouseReleaseEvent
- */
- void QCustomPlot::mousePressEvent(QMouseEvent *event)
- {
- emit mousePress(event);
- // save some state to tell in releaseEvent whether it was a click:
- mMouseHasMoved = false;
- mMousePressPos = event->pos();
-
- if (mSelectionRect && mSelectionRectMode != QCP::srmNone)
- {
- if (mSelectionRectMode != QCP::srmZoom || qobject_cast<QCPAxisRect*>(axisRectAt(mMousePressPos))) // in zoom mode only activate selection rect if on an axis rect
- mSelectionRect->startSelection(event);
- } else
- {
- // no selection rect interaction, prepare for click signal emission and forward event to layerable under the cursor:
- QList<QVariant> details;
- QList<QCPLayerable*> candidates = layerableListAt(mMousePressPos, false, &details);
- if (!candidates.isEmpty())
- {
- mMouseSignalLayerable = candidates.first(); // candidate for signal emission is always topmost hit layerable (signal emitted in release event)
- mMouseSignalLayerableDetails = details.first();
- }
- // forward event to topmost candidate which accepts the event:
- for (int i=0; i<candidates.size(); ++i)
- {
- event->accept(); // default impl of QCPLayerable's mouse events call ignore() on the event, in that case propagate to next candidate in list
- candidates.at(i)->mousePressEvent(event, details.at(i));
- if (event->isAccepted())
- {
- mMouseEventLayerable = candidates.at(i);
- mMouseEventLayerableDetails = details.at(i);
- break;
- }
- }
- }
-
- event->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event.
- }
- /*! \internal
-
- Event handler for when the cursor is moved. Emits the \ref mouseMove signal.
- If the selection rect (\ref setSelectionRect) is currently active, the event is forwarded to it
- in order to update the rect geometry.
-
- Otherwise, if a layout element has mouse capture focus (a mousePressEvent happened on top of the
- layout element before), the mouseMoveEvent is forwarded to that element.
-
- \see mousePressEvent, mouseReleaseEvent
- */
- void QCustomPlot::mouseMoveEvent(QMouseEvent *event)
- {
- emit mouseMove(event);
-
- if (!mMouseHasMoved && (mMousePressPos-event->pos()).manhattanLength() > 3)
- mMouseHasMoved = true; // moved too far from mouse press position, don't handle as click on mouse release
-
- if (mSelectionRect && mSelectionRect->isActive())
- mSelectionRect->moveSelection(event);
- else if (mMouseEventLayerable) // call event of affected layerable:
- mMouseEventLayerable->mouseMoveEvent(event, mMousePressPos);
-
- event->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event.
- }
- /*! \internal
- Event handler for when a mouse button is released. Emits the \ref mouseRelease signal.
- If the mouse was moved less than a certain threshold in any direction since the \ref
- mousePressEvent, it is considered a click which causes the selection mechanism (if activated via
- \ref setInteractions) to possibly change selection states accordingly. Further, specialized mouse
- click signals are emitted (e.g. \ref plottableClick, \ref axisClick, etc.)
- If a layerable is the mouse capturer (a \ref mousePressEvent happened on top of the layerable
- before), the \ref mouseReleaseEvent is forwarded to that element.
- \see mousePressEvent, mouseMoveEvent
- */
- void QCustomPlot::mouseReleaseEvent(QMouseEvent *event)
- {
- emit mouseRelease(event);
-
- if (!mMouseHasMoved) // mouse hasn't moved (much) between press and release, so handle as click
- {
- if (mSelectionRect && mSelectionRect->isActive()) // a simple click shouldn't successfully finish a selection rect, so cancel it here
- mSelectionRect->cancel();
- if (event->button() == Qt::LeftButton)
- processPointSelection(event);
-
- // emit specialized click signals of QCustomPlot instance:
- if (QCPAbstractPlottable *ap = qobject_cast<QCPAbstractPlottable*>(mMouseSignalLayerable))
- {
- int dataIndex = 0;
- if (!mMouseSignalLayerableDetails.value<QCPDataSelection>().isEmpty())
- dataIndex = mMouseSignalLayerableDetails.value<QCPDataSelection>().dataRange().begin();
- emit plottableClick(ap, dataIndex, event);
- } else if (QCPAxis *ax = qobject_cast<QCPAxis*>(mMouseSignalLayerable))
- emit axisClick(ax, mMouseSignalLayerableDetails.value<QCPAxis::SelectablePart>(), event);
- else if (QCPAbstractItem *ai = qobject_cast<QCPAbstractItem*>(mMouseSignalLayerable))
- emit itemClick(ai, event);
- else if (QCPLegend *lg = qobject_cast<QCPLegend*>(mMouseSignalLayerable))
- emit legendClick(lg, 0, event);
- else if (QCPAbstractLegendItem *li = qobject_cast<QCPAbstractLegendItem*>(mMouseSignalLayerable))
- emit legendClick(li->parentLegend(), li, event);
- mMouseSignalLayerable = 0;
- }
-
- if (mSelectionRect && mSelectionRect->isActive()) // Note: if a click was detected above, the selection rect is canceled there
- {
- // finish selection rect, the appropriate action will be taken via signal-slot connection:
- mSelectionRect->endSelection(event);
- } else
- {
- // call event of affected layerable:
- if (mMouseEventLayerable)
- {
- mMouseEventLayerable->mouseReleaseEvent(event, mMousePressPos);
- mMouseEventLayerable = 0;
- }
- }
-
- if (noAntialiasingOnDrag())
- replot(rpQueuedReplot);
-
- event->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event.
- }
- /*! \internal
- Event handler for mouse wheel events. First, the \ref mouseWheel signal is emitted. Then
- determines the affected layerable and forwards the event to it.
- */
- void QCustomPlot::wheelEvent(QWheelEvent *event)
- {
- emit mouseWheel(event);
- // forward event to layerable under cursor:
- QList<QCPLayerable*> candidates = layerableListAt(event->pos(), false);
- for (int i=0; i<candidates.size(); ++i)
- {
- event->accept(); // default impl of QCPLayerable's mouse events ignore the event, in that case propagate to next candidate in list
- candidates.at(i)->wheelEvent(event);
- if (event->isAccepted())
- break;
- }
- event->accept(); // in case QCPLayerable reimplementation manipulates event accepted state. In QWidget event system, QCustomPlot wants to accept the event.
- }
- /*! \internal
-
- This function draws the entire plot, including background pixmap, with the specified \a painter.
- It does not make use of the paint buffers like \ref replot, so this is the function typically
- used by saving/exporting methods such as \ref savePdf or \ref toPainter.
- Note that it does not fill the background with the background brush (as the user may specify with
- \ref setBackground(const QBrush &brush)), this is up to the respective functions calling this
- method.
- */
- void QCustomPlot::draw(QCPPainter *painter)
- {
- updateLayout();
-
- // draw viewport background pixmap:
- drawBackground(painter);
- // draw all layered objects (grid, axes, plottables, items, legend,...):
- foreach (QCPLayer *layer, mLayers)
- layer->draw(painter);
-
- /* Debug code to draw all layout element rects
- foreach (QCPLayoutElement* el, findChildren<QCPLayoutElement*>())
- {
- painter->setBrush(Qt::NoBrush);
- painter->setPen(QPen(QColor(0, 0, 0, 100), 0, Qt::DashLine));
- painter->drawRect(el->rect());
- painter->setPen(QPen(QColor(255, 0, 0, 100), 0, Qt::DashLine));
- painter->drawRect(el->outerRect());
- }
- */
- }
- /*! \internal
- Performs the layout update steps defined by \ref QCPLayoutElement::UpdatePhase, by calling \ref
- QCPLayoutElement::update on the main plot layout.
- Here, the layout elements calculate their positions and margins, and prepare for the following
- draw call.
- */
- void QCustomPlot::updateLayout()
- {
- // run through layout phases:
- mPlotLayout->update(QCPLayoutElement::upPreparation);
- mPlotLayout->update(QCPLayoutElement::upMargins);
- mPlotLayout->update(QCPLayoutElement::upLayout);
- }
- /*! \internal
-
- Draws the viewport background pixmap of the plot.
-
- If a pixmap was provided via \ref setBackground, this function buffers the scaled version
- depending on \ref setBackgroundScaled and \ref setBackgroundScaledMode and then draws it inside
- the viewport with the provided \a painter. The scaled version is buffered in
- mScaledBackgroundPixmap to prevent expensive rescaling at every redraw. It is only updated, when
- the axis rect has changed in a way that requires a rescale of the background pixmap (this is
- dependent on the \ref setBackgroundScaledMode), or when a differend axis background pixmap was
- set.
-
- Note that this function does not draw a fill with the background brush
- (\ref setBackground(const QBrush &brush)) beneath the pixmap.
-
- \see setBackground, setBackgroundScaled, setBackgroundScaledMode
- */
- void QCustomPlot::drawBackground(QCPPainter *painter)
- {
- // Note: background color is handled in individual replot/save functions
- // draw background pixmap (on top of fill, if brush specified):
- if (!mBackgroundPixmap.isNull())
- {
- if (mBackgroundScaled)
- {
- // check whether mScaledBackground needs to be updated:
- QSize scaledSize(mBackgroundPixmap.size());
- scaledSize.scale(mViewport.size(), mBackgroundScaledMode);
- if (mScaledBackgroundPixmap.size() != scaledSize)
- mScaledBackgroundPixmap = mBackgroundPixmap.scaled(mViewport.size(), mBackgroundScaledMode, Qt::SmoothTransformation);
- painter->drawPixmap(mViewport.topLeft(), mScaledBackgroundPixmap, QRect(0, 0, mViewport.width(), mViewport.height()) & mScaledBackgroundPixmap.rect());
- } else
- {
- painter->drawPixmap(mViewport.topLeft(), mBackgroundPixmap, QRect(0, 0, mViewport.width(), mViewport.height()));
- }
- }
- }
- /*! \internal
- Goes through the layers and makes sure this QCustomPlot instance holds the correct number of
- paint buffers and that they have the correct configuration (size, pixel ratio, etc.).
- Allocations, reallocations and deletions of paint buffers are performed as necessary. It also
- associates the paint buffers with the layers, so they draw themselves into the right buffer when
- \ref QCPLayer::drawToPaintBuffer is called. This means it associates adjacent \ref
- QCPLayer::lmLogical layers to a mutual paint buffer and creates dedicated paint buffers for
- layers in \ref QCPLayer::lmBuffered mode.
- This method uses \ref createPaintBuffer to create new paint buffers.
- After this method, the paint buffers are empty (filled with \c Qt::transparent) and invalidated
- (so an attempt to replot only a single buffered layer causes a full replot).
- This method is called in every \ref replot call, prior to actually drawing the layers (into their
- associated paint buffer). If the paint buffers don't need changing/reallocating, this method
- basically leaves them alone and thus finishes very fast.
- */
- void QCustomPlot::setupPaintBuffers()
- {
- int bufferIndex = 0;
- if (mPaintBuffers.isEmpty())
- mPaintBuffers.append(QSharedPointer<QCPAbstractPaintBuffer>(createPaintBuffer()));
-
- for (int layerIndex = 0; layerIndex < mLayers.size(); ++layerIndex)
- {
- QCPLayer *layer = mLayers.at(layerIndex);
- if (layer->mode() == QCPLayer::lmLogical)
- {
- layer->mPaintBuffer = mPaintBuffers.at(bufferIndex).toWeakRef();
- } else if (layer->mode() == QCPLayer::lmBuffered)
- {
- ++bufferIndex;
- if (bufferIndex >= mPaintBuffers.size())
- mPaintBuffers.append(QSharedPointer<QCPAbstractPaintBuffer>(createPaintBuffer()));
- layer->mPaintBuffer = mPaintBuffers.at(bufferIndex).toWeakRef();
- if (layerIndex < mLayers.size()-1 && mLayers.at(layerIndex+1)->mode() == QCPLayer::lmLogical) // not last layer, and next one is logical, so prepare another buffer for next layerables
- {
- ++bufferIndex;
- if (bufferIndex >= mPaintBuffers.size())
- mPaintBuffers.append(QSharedPointer<QCPAbstractPaintBuffer>(createPaintBuffer()));
- }
- }
- }
- // remove unneeded buffers:
- while (mPaintBuffers.size()-1 > bufferIndex)
- mPaintBuffers.removeLast();
- // resize buffers to viewport size and clear contents:
- for (int i=0; i<mPaintBuffers.size(); ++i)
- {
- mPaintBuffers.at(i)->setSize(viewport().size()); // won't do anything if already correct size
- mPaintBuffers.at(i)->clear(Qt::transparent);
- mPaintBuffers.at(i)->setInvalidated();
- }
- }
- /*! \internal
- This method is used by \ref setupPaintBuffers when it needs to create new paint buffers.
- Depending on the current setting of \ref setOpenGl, and the current Qt version, different
- backends (subclasses of \ref QCPAbstractPaintBuffer) are created, initialized with the proper
- size and device pixel ratio, and returned.
- */
- QCPAbstractPaintBuffer *QCustomPlot::createPaintBuffer()
- {
- if (mOpenGl)
- {
- #if defined(QCP_OPENGL_FBO)
- return new QCPPaintBufferGlFbo(viewport().size(), mBufferDevicePixelRatio, mGlContext, mGlPaintDevice);
- #elif defined(QCP_OPENGL_PBUFFER)
- return new QCPPaintBufferGlPbuffer(viewport().size(), mBufferDevicePixelRatio, mOpenGlMultisamples);
- #else
- qDebug() << Q_FUNC_INFO << "OpenGL enabled even though no support for it compiled in, this shouldn't have happened. Falling back to pixmap paint buffer.";
- return new QCPPaintBufferPixmap(viewport().size(), mBufferDevicePixelRatio);
- #endif
- } else
- return new QCPPaintBufferPixmap(viewport().size(), mBufferDevicePixelRatio);
- }
- /*!
- This method returns whether any of the paint buffers held by this QCustomPlot instance are
- invalidated.
- If any buffer is invalidated, a partial replot (\ref QCPLayer::replot) is not allowed and always
- causes a full replot (\ref QCustomPlot::replot) of all layers. This is the case when for example
- the layer order has changed, new layers were added, layers were removed, or layer modes were
- changed (\ref QCPLayer::setMode).
- \see QCPAbstractPaintBuffer::setInvalidated
- */
- bool QCustomPlot::hasInvalidatedPaintBuffers()
- {
- for (int i=0; i<mPaintBuffers.size(); ++i)
- {
- if (mPaintBuffers.at(i)->invalidated())
- return true;
- }
- return false;
- }
- /*! \internal
- When \ref setOpenGl is set to true, this method is used to initialize OpenGL (create a context,
- surface, paint device).
- Returns true on success.
- If this method is successful, all paint buffers should be deleted and then reallocated by calling
- \ref setupPaintBuffers, so the OpenGL-based paint buffer subclasses (\ref
- QCPPaintBufferGlPbuffer, \ref QCPPaintBufferGlFbo) are used for subsequent replots.
- \see freeOpenGl
- */
- bool QCustomPlot::setupOpenGl()
- {
- #ifdef QCP_OPENGL_FBO
- freeOpenGl();
- QSurfaceFormat proposedSurfaceFormat;
- proposedSurfaceFormat.setSamples(mOpenGlMultisamples);
- #ifdef QCP_OPENGL_OFFSCREENSURFACE
- QOffscreenSurface *surface = new QOffscreenSurface;
- #else
- QWindow *surface = new QWindow;
- surface->setSurfaceType(QSurface::OpenGLSurface);
- #endif
- surface->setFormat(proposedSurfaceFormat);
- surface->create();
- mGlSurface = QSharedPointer<QSurface>(surface);
- mGlContext = QSharedPointer<QOpenGLContext>(new QOpenGLContext);
- mGlContext->setFormat(mGlSurface->format());
- if (!mGlContext->create())
- {
- qDebug() << Q_FUNC_INFO << "Failed to create OpenGL context";
- mGlContext.clear();
- mGlSurface.clear();
- return false;
- }
- if (!mGlContext->makeCurrent(mGlSurface.data())) // context needs to be current to create paint device
- {
- qDebug() << Q_FUNC_INFO << "Failed to make opengl context current";
- mGlContext.clear();
- mGlSurface.clear();
- return false;
- }
- if (!QOpenGLFramebufferObject::hasOpenGLFramebufferObjects())
- {
- qDebug() << Q_FUNC_INFO << "OpenGL of this system doesn't support frame buffer objects";
- mGlContext.clear();
- mGlSurface.clear();
- return false;
- }
- mGlPaintDevice = QSharedPointer<QOpenGLPaintDevice>(new QOpenGLPaintDevice);
- return true;
- #elif defined(QCP_OPENGL_PBUFFER)
- return QGLFormat::hasOpenGL();
- #else
- return false;
- #endif
- }
- /*! \internal
- When \ref setOpenGl is set to false, this method is used to deinitialize OpenGL (releases the
- context and frees resources).
- After OpenGL is disabled, all paint buffers should be deleted and then reallocated by calling
- \ref setupPaintBuffers, so the standard software rendering paint buffer subclass (\ref
- QCPPaintBufferPixmap) is used for subsequent replots.
- \see setupOpenGl
- */
- void QCustomPlot::freeOpenGl()
- {
- #ifdef QCP_OPENGL_FBO
- mGlPaintDevice.clear();
- mGlContext.clear();
- mGlSurface.clear();
- #endif
- }
- /*! \internal
-
- This method is used by \ref QCPAxisRect::removeAxis to report removed axes to the QCustomPlot
- so it may clear its QCustomPlot::xAxis, yAxis, xAxis2 and yAxis2 members accordingly.
- */
- void QCustomPlot::axisRemoved(QCPAxis *axis)
- {
- if (xAxis == axis)
- xAxis = 0;
- if (xAxis2 == axis)
- xAxis2 = 0;
- if (yAxis == axis)
- yAxis = 0;
- if (yAxis2 == axis)
- yAxis2 = 0;
-
- // Note: No need to take care of range drag axes and range zoom axes, because they are stored in smart pointers
- }
- /*! \internal
-
- This method is used by the QCPLegend destructor to report legend removal to the QCustomPlot so
- it may clear its QCustomPlot::legend member accordingly.
- */
- void QCustomPlot::legendRemoved(QCPLegend *legend)
- {
- if (this->legend == legend)
- this->legend = 0;
- }
- /*! \internal
-
- This slot is connected to the selection rect's \ref QCPSelectionRect::accepted signal when \ref
- setSelectionRectMode is set to \ref QCP::srmSelect.
- First, it determines which axis rect was the origin of the selection rect judging by the starting
- point of the selection. Then it goes through the plottables (\ref QCPAbstractPlottable1D to be
- precise) associated with that axis rect and finds the data points that are in \a rect. It does
- this by querying their \ref QCPAbstractPlottable1D::selectTestRect method.
-
- Then, the actual selection is done by calling the plottables' \ref
- QCPAbstractPlottable::selectEvent, placing the found selected data points in the \a details
- parameter as <tt>QVariant(\ref QCPDataSelection)</tt>. All plottables that weren't touched by \a
- rect receive a \ref QCPAbstractPlottable::deselectEvent.
-
- \see processRectZoom
- */
- void QCustomPlot::processRectSelection(QRect rect, QMouseEvent *event)
- {
- bool selectionStateChanged = false;
-
- if (mInteractions.testFlag(QCP::iSelectPlottables))
- {
- QMap<int, QPair<QCPAbstractPlottable*, QCPDataSelection> > potentialSelections; // map key is number of selected data points, so we have selections sorted by size
- QRectF rectF(rect.normalized());
- if (QCPAxisRect *affectedAxisRect = axisRectAt(rectF.topLeft()))
- {
- // determine plottables that were hit by the rect and thus are candidates for selection:
- foreach (QCPAbstractPlottable *plottable, affectedAxisRect->plottables())
- {
- if (QCPPlottableInterface1D *plottableInterface = plottable->interface1D())
- {
- QCPDataSelection dataSel = plottableInterface->selectTestRect(rectF, true);
- if (!dataSel.isEmpty())
- potentialSelections.insertMulti(dataSel.dataPointCount(), QPair<QCPAbstractPlottable*, QCPDataSelection>(plottable, dataSel));
- }
- }
-
- if (!mInteractions.testFlag(QCP::iMultiSelect))
- {
- // only leave plottable with most selected points in map, since we will only select a single plottable:
- if (!potentialSelections.isEmpty())
- {
- QMap<int, QPair<QCPAbstractPlottable*, QCPDataSelection> >::iterator it = potentialSelections.begin();
- while (it != potentialSelections.end()-1) // erase all except last element
- it = potentialSelections.erase(it);
- }
- }
-
- bool additive = event->modifiers().testFlag(mMultiSelectModifier);
- // deselect all other layerables if not additive selection:
- if (!additive)
- {
- // emit deselection except to those plottables who will be selected afterwards:
- foreach (QCPLayer *layer, mLayers)
- {
- foreach (QCPLayerable *layerable, layer->children())
- {
- if ((potentialSelections.isEmpty() || potentialSelections.constBegin()->first != layerable) && mInteractions.testFlag(layerable->selectionCategory()))
- {
- bool selChanged = false;
- layerable->deselectEvent(&selChanged);
- selectionStateChanged |= selChanged;
- }
- }
- }
- }
-
- // go through selections in reverse (largest selection first) and emit select events:
- QMap<int, QPair<QCPAbstractPlottable*, QCPDataSelection> >::const_iterator it = potentialSelections.constEnd();
- while (it != potentialSelections.constBegin())
- {
- --it;
- if (mInteractions.testFlag(it.value().first->selectionCategory()))
- {
- bool selChanged = false;
- it.value().first->selectEvent(event, additive, QVariant::fromValue(it.value().second), &selChanged);
- selectionStateChanged |= selChanged;
- }
- }
- }
- }
-
- if (selectionStateChanged)
- {
- emit selectionChangedByUser();
- replot(rpQueuedReplot);
- } else if (mSelectionRect)
- mSelectionRect->layer()->replot();
- }
- /*! \internal
-
- This slot is connected to the selection rect's \ref QCPSelectionRect::accepted signal when \ref
- setSelectionRectMode is set to \ref QCP::srmZoom.
- It determines which axis rect was the origin of the selection rect judging by the starting point
- of the selection, and then zooms the axes defined via \ref QCPAxisRect::setRangeZoomAxes to the
- provided \a rect (see \ref QCPAxisRect::zoom).
-
- \see processRectSelection
- */
- void QCustomPlot::processRectZoom(QRect rect, QMouseEvent *event)
- {
- Q_UNUSED(event)
- if (QCPAxisRect *axisRect = axisRectAt(rect.topLeft()))
- {
- QList<QCPAxis*> affectedAxes = QList<QCPAxis*>() << axisRect->rangeZoomAxes(Qt::Horizontal) << axisRect->rangeZoomAxes(Qt::Vertical);
- affectedAxes.removeAll(static_cast<QCPAxis*>(0));
- axisRect->zoom(QRectF(rect), affectedAxes);
- }
- replot(rpQueuedReplot); // always replot to make selection rect disappear
- }
- /*! \internal
- This method is called when a simple left mouse click was detected on the QCustomPlot surface.
- It first determines the layerable that was hit by the click, and then calls its \ref
- QCPLayerable::selectEvent. All other layerables receive a QCPLayerable::deselectEvent (unless the
- multi-select modifier was pressed, see \ref setMultiSelectModifier).
- In this method the hit layerable is determined a second time using \ref layerableAt (after the
- one in \ref mousePressEvent), because we want \a onlySelectable set to true this time. This
- implies that the mouse event grabber (mMouseEventLayerable) may be a different one from the
- clicked layerable determined here. For example, if a non-selectable layerable is in front of a
- selectable layerable at the click position, the front layerable will receive mouse events but the
- selectable one in the back will receive the \ref QCPLayerable::selectEvent.
- \see processRectSelection, QCPLayerable::selectTest
- */
- void QCustomPlot::processPointSelection(QMouseEvent *event)
- {
- QVariant details;
- QCPLayerable *clickedLayerable = layerableAt(event->pos(), true, &details);
- bool selectionStateChanged = false;
- bool additive = mInteractions.testFlag(QCP::iMultiSelect) && event->modifiers().testFlag(mMultiSelectModifier);
- // deselect all other layerables if not additive selection:
- if (!additive)
- {
- foreach (QCPLayer *layer, mLayers)
- {
- foreach (QCPLayerable *layerable, layer->children())
- {
- if (layerable != clickedLayerable && mInteractions.testFlag(layerable->selectionCategory()))
- {
- bool selChanged = false;
- layerable->deselectEvent(&selChanged);
- selectionStateChanged |= selChanged;
- }
- }
- }
- }
- if (clickedLayerable && mInteractions.testFlag(clickedLayerable->selectionCategory()))
- {
- // a layerable was actually clicked, call its selectEvent:
- bool selChanged = false;
- clickedLayerable->selectEvent(event, additive, details, &selChanged);
- selectionStateChanged |= selChanged;
- }
- if (selectionStateChanged)
- {
- emit selectionChangedByUser();
- replot(rpQueuedReplot);
- }
- }
- /*! \internal
-
- Registers the specified plottable with this QCustomPlot and, if \ref setAutoAddPlottableToLegend
- is enabled, adds it to the legend (QCustomPlot::legend). QCustomPlot takes ownership of the
- plottable.
-
- Returns true on success, i.e. when \a plottable isn't already in this plot and the parent plot of
- \a plottable is this QCustomPlot.
-
- This method is called automatically in the QCPAbstractPlottable base class constructor.
- */
- bool QCustomPlot::registerPlottable(QCPAbstractPlottable *plottable)
- {
- if (mPlottables.contains(plottable))
- {
- qDebug() << Q_FUNC_INFO << "plottable already added to this QCustomPlot:" << reinterpret_cast<quintptr>(plottable);
- return false;
- }
- if (plottable->parentPlot() != this)
- {
- qDebug() << Q_FUNC_INFO << "plottable not created with this QCustomPlot as parent:" << reinterpret_cast<quintptr>(plottable);
- return false;
- }
-
- mPlottables.append(plottable);
- // possibly add plottable to legend:
- if (mAutoAddPlottableToLegend)
- plottable->addToLegend();
- if (!plottable->layer()) // usually the layer is already set in the constructor of the plottable (via QCPLayerable constructor)
- plottable->setLayer(currentLayer());
- return true;
- }
- /*! \internal
-
- In order to maintain the simplified graph interface of QCustomPlot, this method is called by the
- QCPGraph constructor to register itself with this QCustomPlot's internal graph list. Returns true
- on success, i.e. if \a graph is valid and wasn't already registered with this QCustomPlot.
-
- This graph specific registration happens in addition to the call to \ref registerPlottable by the
- QCPAbstractPlottable base class.
- */
- bool QCustomPlot::registerGraph(QCPGraph *graph)
- {
- if (!graph)
- {
- qDebug() << Q_FUNC_INFO << "passed graph is zero";
- return false;
- }
- if (mGraphs.contains(graph))
- {
- qDebug() << Q_FUNC_INFO << "graph already registered with this QCustomPlot";
- return false;
- }
-
- mGraphs.append(graph);
- return true;
- }
- /*! \internal
- Registers the specified item with this QCustomPlot. QCustomPlot takes ownership of the item.
-
- Returns true on success, i.e. when \a item wasn't already in the plot and the parent plot of \a
- item is this QCustomPlot.
-
- This method is called automatically in the QCPAbstractItem base class constructor.
- */
- bool QCustomPlot::registerItem(QCPAbstractItem *item)
- {
- if (mItems.contains(item))
- {
- qDebug() << Q_FUNC_INFO << "item already added to this QCustomPlot:" << reinterpret_cast<quintptr>(item);
- return false;
- }
- if (item->parentPlot() != this)
- {
- qDebug() << Q_FUNC_INFO << "item not created with this QCustomPlot as parent:" << reinterpret_cast<quintptr>(item);
- return false;
- }
-
- mItems.append(item);
- if (!item->layer()) // usually the layer is already set in the constructor of the item (via QCPLayerable constructor)
- item->setLayer(currentLayer());
- return true;
- }
- /*! \internal
-
- Assigns all layers their index (QCPLayer::mIndex) in the mLayers list. This method is thus called
- after every operation that changes the layer indices, like layer removal, layer creation, layer
- moving.
- */
- void QCustomPlot::updateLayerIndices() const
- {
- for (int i=0; i<mLayers.size(); ++i)
- mLayers.at(i)->mIndex = i;
- }
- /*! \internal
- Returns the top-most layerable at pixel position \a pos. If \a onlySelectable is set to true,
- only those layerables that are selectable will be considered. (Layerable subclasses communicate
- their selectability via the QCPLayerable::selectTest method, by returning -1.)
- \a selectionDetails is an output parameter that contains selection specifics of the affected
- layerable. This is useful if the respective layerable shall be given a subsequent
- QCPLayerable::selectEvent (like in \ref mouseReleaseEvent). \a selectionDetails usually contains
- information about which part of the layerable was hit, in multi-part layerables (e.g.
- QCPAxis::SelectablePart). If the layerable is a plottable, \a selectionDetails contains a \ref
- QCPDataSelection instance with the single data point which is closest to \a pos.
-
- \see layerableListAt, layoutElementAt, axisRectAt
- */
- QCPLayerable *QCustomPlot::layerableAt(const QPointF &pos, bool onlySelectable, QVariant *selectionDetails) const
- {
- QList<QVariant> details;
- QList<QCPLayerable*> candidates = layerableListAt(pos, onlySelectable, selectionDetails ? &details : 0);
- if (selectionDetails && !details.isEmpty())
- *selectionDetails = details.first();
- if (!candidates.isEmpty())
- return candidates.first();
- else
- return 0;
- }
- /*! \internal
- Returns the layerables at pixel position \a pos. If \a onlySelectable is set to true, only those
- layerables that are selectable will be considered. (Layerable subclasses communicate their
- selectability via the QCPLayerable::selectTest method, by returning -1.)
- The returned list is sorted by the layerable/drawing order. If you only need to know the top-most
- layerable, rather use \ref layerableAt.
- \a selectionDetails is an output parameter that contains selection specifics of the affected
- layerable. This is useful if the respective layerable shall be given a subsequent
- QCPLayerable::selectEvent (like in \ref mouseReleaseEvent). \a selectionDetails usually contains
- information about which part of the layerable was hit, in multi-part layerables (e.g.
- QCPAxis::SelectablePart). If the layerable is a plottable, \a selectionDetails contains a \ref
- QCPDataSelection instance with the single data point which is closest to \a pos.
-
- \see layerableAt, layoutElementAt, axisRectAt
- */
- QList<QCPLayerable*> QCustomPlot::layerableListAt(const QPointF &pos, bool onlySelectable, QList<QVariant> *selectionDetails) const
- {
- QList<QCPLayerable*> result;
- for (int layerIndex=mLayers.size()-1; layerIndex>=0; --layerIndex)
- {
- const QList<QCPLayerable*> layerables = mLayers.at(layerIndex)->children();
- for (int i=layerables.size()-1; i>=0; --i)
- {
- if (!layerables.at(i)->realVisibility())
- continue;
- QVariant details;
- double dist = layerables.at(i)->selectTest(pos, onlySelectable, selectionDetails ? &details : 0);
- if (dist >= 0 && dist < selectionTolerance())
- {
- result.append(layerables.at(i));
- if (selectionDetails)
- selectionDetails->append(details);
- }
- }
- }
- return result;
- }
- /*!
- Saves the plot to a rastered image file \a fileName in the image format \a format. The plot is
- sized to \a width and \a height in pixels and scaled with \a scale. (width 100 and scale 2.0 lead
- to a full resolution file with width 200.) If the \a format supports compression, \a quality may
- be between 0 and 100 to control it.
- Returns true on success. If this function fails, most likely the given \a format isn't supported
- by the system, see Qt docs about QImageWriter::supportedImageFormats().
- The \a resolution will be written to the image file header (if the file format supports this) and
- has no direct consequence for the quality or the pixel size. However, if opening the image with a
- tool which respects the metadata, it will be able to scale the image to match either a given size
- in real units of length (inch, centimeters, etc.), or the target display DPI. You can specify in
- which units \a resolution is given, by setting \a resolutionUnit. The \a resolution is converted
- to the format's expected resolution unit internally.
- \see saveBmp, saveJpg, savePng, savePdf
- */
- bool QCustomPlot::saveRastered(const QString &fileName, int width, int height, double scale, const char *format, int quality, int resolution, QCP::ResolutionUnit resolutionUnit)
- {
- QImage buffer = toPixmap(width, height, scale).toImage();
-
- int dotsPerMeter = 0;
- switch (resolutionUnit)
- {
- case QCP::ruDotsPerMeter: dotsPerMeter = resolution; break;
- case QCP::ruDotsPerCentimeter: dotsPerMeter = resolution*100; break;
- case QCP::ruDotsPerInch: dotsPerMeter = resolution/0.0254; break;
- }
- buffer.setDotsPerMeterX(dotsPerMeter); // this is saved together with some image formats, e.g. PNG, and is relevant when opening image in other tools
- buffer.setDotsPerMeterY(dotsPerMeter); // this is saved together with some image formats, e.g. PNG, and is relevant when opening image in other tools
- if (!buffer.isNull())
- return buffer.save(fileName, format, quality);
- else
- return false;
- }
- /*!
- Renders the plot to a pixmap and returns it.
-
- The plot is sized to \a width and \a height in pixels and scaled with \a scale. (width 100 and
- scale 2.0 lead to a full resolution pixmap with width 200.)
-
- \see toPainter, saveRastered, saveBmp, savePng, saveJpg, savePdf
- */
- QPixmap QCustomPlot::toPixmap(int width, int height, double scale)
- {
- // this method is somewhat similar to toPainter. Change something here, and a change in toPainter might be necessary, too.
- int newWidth, newHeight;
- if (width == 0 || height == 0)
- {
- newWidth = this->width();
- newHeight = this->height();
- } else
- {
- newWidth = width;
- newHeight = height;
- }
- int scaledWidth = qRound(scale*newWidth);
- int scaledHeight = qRound(scale*newHeight);
- QPixmap result(scaledWidth, scaledHeight);
- result.fill(mBackgroundBrush.style() == Qt::SolidPattern ? mBackgroundBrush.color() : Qt::transparent); // if using non-solid pattern, make transparent now and draw brush pattern later
- QCPPainter painter;
- painter.begin(&result);
- if (painter.isActive())
- {
- QRect oldViewport = viewport();
- setViewport(QRect(0, 0, newWidth, newHeight));
- painter.setMode(QCPPainter::pmNoCaching);
- if (!qFuzzyCompare(scale, 1.0))
- {
- if (scale > 1.0) // for scale < 1 we always want cosmetic pens where possible, because else lines might disappear for very small scales
- painter.setMode(QCPPainter::pmNonCosmetic);
- painter.scale(scale, scale);
- }
- if (mBackgroundBrush.style() != Qt::SolidPattern && mBackgroundBrush.style() != Qt::NoBrush) // solid fills were done a few lines above with QPixmap::fill
- painter.fillRect(mViewport, mBackgroundBrush);
- draw(&painter);
- setViewport(oldViewport);
- painter.end();
- } else // might happen if pixmap has width or height zero
- {
- qDebug() << Q_FUNC_INFO << "Couldn't activate painter on pixmap";
- return QPixmap();
- }
- return result;
- }
- /*!
- Renders the plot using the passed \a painter.
-
- The plot is sized to \a width and \a height in pixels. If the \a painter's scale is not 1.0, the resulting plot will
- appear scaled accordingly.
-
- \note If you are restricted to using a QPainter (instead of QCPPainter), create a temporary QPicture and open a QCPPainter
- on it. Then call \ref toPainter with this QCPPainter. After ending the paint operation on the picture, draw it with
- the QPainter. This will reproduce the painter actions the QCPPainter took, with a QPainter.
-
- \see toPixmap
- */
- void QCustomPlot::toPainter(QCPPainter *painter, int width, int height)
- {
- // this method is somewhat similar to toPixmap. Change something here, and a change in toPixmap might be necessary, too.
- int newWidth, newHeight;
- if (width == 0 || height == 0)
- {
- newWidth = this->width();
- newHeight = this->height();
- } else
- {
- newWidth = width;
- newHeight = height;
- }
- if (painter->isActive())
- {
- QRect oldViewport = viewport();
- setViewport(QRect(0, 0, newWidth, newHeight));
- painter->setMode(QCPPainter::pmNoCaching);
- if (mBackgroundBrush.style() != Qt::NoBrush) // unlike in toPixmap, we can't do QPixmap::fill for Qt::SolidPattern brush style, so we also draw solid fills with fillRect here
- painter->fillRect(mViewport, mBackgroundBrush);
- draw(painter);
- setViewport(oldViewport);
- } else
- qDebug() << Q_FUNC_INFO << "Passed painter is not active";
- }
- /* end of 'src/core.cpp' */
- //amalgamation: add plottable1d.cpp
- /* including file 'src/colorgradient.cpp', size 24646 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPColorGradient
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPColorGradient
- \brief Defines a color gradient for use with e.g. \ref QCPColorMap
-
- This class describes a color gradient which can be used to encode data with color. For example,
- QCPColorMap and QCPColorScale have \ref QCPColorMap::setGradient "setGradient" methods which
- take an instance of this class. Colors are set with \ref setColorStopAt(double position, const QColor &color)
- with a \a position from 0 to 1. In between these defined color positions, the
- color will be interpolated linearly either in RGB or HSV space, see \ref setColorInterpolation.
- Alternatively, load one of the preset color gradients shown in the image below, with \ref
- loadPreset, or by directly specifying the preset in the constructor.
-
- Apart from red, green and blue components, the gradient also interpolates the alpha values of the
- configured color stops. This allows to display some portions of the data range as transparent in
- the plot.
-
- \image html QCPColorGradient.png
-
- The \ref QCPColorGradient(GradientPreset preset) constructor allows directly converting a \ref
- GradientPreset to a QCPColorGradient. This means that you can directly pass \ref GradientPreset
- to all the \a setGradient methods, e.g.:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolorgradient-setgradient
-
- The total number of levels used in the gradient can be set with \ref setLevelCount. Whether the
- color gradient shall be applied periodically (wrapping around) to data values that lie outside
- the data range specified on the plottable instance can be controlled with \ref setPeriodic.
- */
- /*!
- Constructs a new, empty QCPColorGradient with no predefined color stops. You can add own color
- stops with \ref setColorStopAt.
- The color level count is initialized to 350.
- */
- QCPColorGradient::QCPColorGradient() :
- mLevelCount(350),
- mColorInterpolation(ciRGB),
- mPeriodic(false),
- mColorBufferInvalidated(true)
- {
- mColorBuffer.fill(qRgb(0, 0, 0), mLevelCount);
- }
- /*!
- Constructs a new QCPColorGradient initialized with the colors and color interpolation according
- to \a preset.
- The color level count is initialized to 350.
- */
- QCPColorGradient::QCPColorGradient(GradientPreset preset) :
- mLevelCount(350),
- mColorInterpolation(ciRGB),
- mPeriodic(false),
- mColorBufferInvalidated(true)
- {
- mColorBuffer.fill(qRgb(0, 0, 0), mLevelCount);
- loadPreset(preset);
- }
- /* undocumented operator */
- bool QCPColorGradient::operator==(const QCPColorGradient &other) const
- {
- return ((other.mLevelCount == this->mLevelCount) &&
- (other.mColorInterpolation == this->mColorInterpolation) &&
- (other.mPeriodic == this->mPeriodic) &&
- (other.mColorStops == this->mColorStops));
- }
- /*!
- Sets the number of discretization levels of the color gradient to \a n. The default is 350 which
- is typically enough to create a smooth appearance. The minimum number of levels is 2.
- \image html QCPColorGradient-levelcount.png
- */
- void QCPColorGradient::setLevelCount(int n)
- {
- if (n < 2)
- {
- qDebug() << Q_FUNC_INFO << "n must be greater or equal 2 but was" << n;
- n = 2;
- }
- if (n != mLevelCount)
- {
- mLevelCount = n;
- mColorBufferInvalidated = true;
- }
- }
- /*!
- Sets at which positions from 0 to 1 which color shall occur. The positions are the keys, the
- colors are the values of the passed QMap \a colorStops. In between these color stops, the color
- is interpolated according to \ref setColorInterpolation.
-
- A more convenient way to create a custom gradient may be to clear all color stops with \ref
- clearColorStops (or creating a new, empty QCPColorGradient) and then adding them one by one with
- \ref setColorStopAt.
-
- \see clearColorStops
- */
- void QCPColorGradient::setColorStops(const QMap<double, QColor> &colorStops)
- {
- mColorStops = colorStops;
- mColorBufferInvalidated = true;
- }
- /*!
- Sets the \a color the gradient will have at the specified \a position (from 0 to 1). In between
- these color stops, the color is interpolated according to \ref setColorInterpolation.
-
- \see setColorStops, clearColorStops
- */
- void QCPColorGradient::setColorStopAt(double position, const QColor &color)
- {
- mColorStops.insert(position, color);
- mColorBufferInvalidated = true;
- }
- /*!
- Sets whether the colors in between the configured color stops (see \ref setColorStopAt) shall be
- interpolated linearly in RGB or in HSV color space.
-
- For example, a sweep in RGB space from red to green will have a muddy brown intermediate color,
- whereas in HSV space the intermediate color is yellow.
- */
- void QCPColorGradient::setColorInterpolation(QCPColorGradient::ColorInterpolation interpolation)
- {
- if (interpolation != mColorInterpolation)
- {
- mColorInterpolation = interpolation;
- mColorBufferInvalidated = true;
- }
- }
- /*!
- Sets whether data points that are outside the configured data range (e.g. \ref
- QCPColorMap::setDataRange) are colored by periodically repeating the color gradient or whether
- they all have the same color, corresponding to the respective gradient boundary color.
-
- \image html QCPColorGradient-periodic.png
-
- As shown in the image above, gradients that have the same start and end color are especially
- suitable for a periodic gradient mapping, since they produce smooth color transitions throughout
- the color map. A preset that has this property is \ref gpHues.
-
- In practice, using periodic color gradients makes sense when the data corresponds to a periodic
- dimension, such as an angle or a phase. If this is not the case, the color encoding might become
- ambiguous, because multiple different data values are shown as the same color.
- */
- void QCPColorGradient::setPeriodic(bool enabled)
- {
- mPeriodic = enabled;
- }
- /*! \overload
-
- This method is used to quickly convert a \a data array to colors. The colors will be output in
- the array \a scanLine. Both \a data and \a scanLine must have the length \a n when passed to this
- function. The data range that shall be used for mapping the data value to the gradient is passed
- in \a range. \a logarithmic indicates whether the data values shall be mapped to colors
- logarithmically.
- if \a data actually contains 2D-data linearized via <tt>[row*columnCount + column]</tt>, you can
- set \a dataIndexFactor to <tt>columnCount</tt> to convert a column instead of a row of the data
- array, in \a scanLine. \a scanLine will remain a regular (1D) array. This works because \a data
- is addressed <tt>data[i*dataIndexFactor]</tt>.
-
- Use the overloaded method to additionally provide alpha map data.
- The QRgb values that are placed in \a scanLine have their r, g and b components premultiplied
- with alpha (see QImage::Format_ARGB32_Premultiplied).
- */
- void QCPColorGradient::colorize(const double *data, const QCPRange &range, QRgb *scanLine, int n, int dataIndexFactor, bool logarithmic)
- {
- // If you change something here, make sure to also adapt color() and the other colorize() overload
- if (!data)
- {
- qDebug() << Q_FUNC_INFO << "null pointer given as data";
- return;
- }
- if (!scanLine)
- {
- qDebug() << Q_FUNC_INFO << "null pointer given as scanLine";
- return;
- }
- if (mColorBufferInvalidated)
- updateColorBuffer();
-
- if (!logarithmic)
- {
- const double posToIndexFactor = (mLevelCount-1)/range.size();
- if (mPeriodic)
- {
- for (int i=0; i<n; ++i)
- {
- int index = (int)((data[dataIndexFactor*i]-range.lower)*posToIndexFactor) % mLevelCount;
- if (index < 0)
- index += mLevelCount;
- scanLine[i] = mColorBuffer.at(index);
- }
- } else
- {
- for (int i=0; i<n; ++i)
- {
- int index = (data[dataIndexFactor*i]-range.lower)*posToIndexFactor;
- if (index < 0)
- index = 0;
- else if (index >= mLevelCount)
- index = mLevelCount-1;
- scanLine[i] = mColorBuffer.at(index);
- }
- }
- } else // logarithmic == true
- {
- if (mPeriodic)
- {
- for (int i=0; i<n; ++i)
- {
- int index = (int)(qLn(data[dataIndexFactor*i]/range.lower)/qLn(range.upper/range.lower)*(mLevelCount-1)) % mLevelCount;
- if (index < 0)
- index += mLevelCount;
- scanLine[i] = mColorBuffer.at(index);
- }
- } else
- {
- for (int i=0; i<n; ++i)
- {
- int index = qLn(data[dataIndexFactor*i]/range.lower)/qLn(range.upper/range.lower)*(mLevelCount-1);
- if (index < 0)
- index = 0;
- else if (index >= mLevelCount)
- index = mLevelCount-1;
- scanLine[i] = mColorBuffer.at(index);
- }
- }
- }
- }
- /*! \overload
- Additionally to the other overload of \ref colorize, this method takes the array \a alpha, which
- has the same size and structure as \a data and encodes the alpha information per data point.
- The QRgb values that are placed in \a scanLine have their r, g and b components premultiplied
- with alpha (see QImage::Format_ARGB32_Premultiplied).
- */
- void QCPColorGradient::colorize(const double *data, const unsigned char *alpha, const QCPRange &range, QRgb *scanLine, int n, int dataIndexFactor, bool logarithmic)
- {
- // If you change something here, make sure to also adapt color() and the other colorize() overload
- if (!data)
- {
- qDebug() << Q_FUNC_INFO << "null pointer given as data";
- return;
- }
- if (!alpha)
- {
- qDebug() << Q_FUNC_INFO << "null pointer given as alpha";
- return;
- }
- if (!scanLine)
- {
- qDebug() << Q_FUNC_INFO << "null pointer given as scanLine";
- return;
- }
- if (mColorBufferInvalidated)
- updateColorBuffer();
-
- if (!logarithmic)
- {
- const double posToIndexFactor = (mLevelCount-1)/range.size();
- if (mPeriodic)
- {
- for (int i=0; i<n; ++i)
- {
- int index = (int)((data[dataIndexFactor*i]-range.lower)*posToIndexFactor) % mLevelCount;
- if (index < 0)
- index += mLevelCount;
- if (alpha[dataIndexFactor*i] == 255)
- {
- scanLine[i] = mColorBuffer.at(index);
- } else
- {
- const QRgb rgb = mColorBuffer.at(index);
- const float alphaF = alpha[dataIndexFactor*i]/255.0f;
- scanLine[i] = qRgba(qRed(rgb)*alphaF, qGreen(rgb)*alphaF, qBlue(rgb)*alphaF, qAlpha(rgb)*alphaF);
- }
- }
- } else
- {
- for (int i=0; i<n; ++i)
- {
- int index = (data[dataIndexFactor*i]-range.lower)*posToIndexFactor;
- if (index < 0)
- index = 0;
- else if (index >= mLevelCount)
- index = mLevelCount-1;
- if (alpha[dataIndexFactor*i] == 255)
- {
- scanLine[i] = mColorBuffer.at(index);
- } else
- {
- const QRgb rgb = mColorBuffer.at(index);
- const float alphaF = alpha[dataIndexFactor*i]/255.0f;
- scanLine[i] = qRgba(qRed(rgb)*alphaF, qGreen(rgb)*alphaF, qBlue(rgb)*alphaF, qAlpha(rgb)*alphaF);
- }
- }
- }
- } else // logarithmic == true
- {
- if (mPeriodic)
- {
- for (int i=0; i<n; ++i)
- {
- int index = (int)(qLn(data[dataIndexFactor*i]/range.lower)/qLn(range.upper/range.lower)*(mLevelCount-1)) % mLevelCount;
- if (index < 0)
- index += mLevelCount;
- if (alpha[dataIndexFactor*i] == 255)
- {
- scanLine[i] = mColorBuffer.at(index);
- } else
- {
- const QRgb rgb = mColorBuffer.at(index);
- const float alphaF = alpha[dataIndexFactor*i]/255.0f;
- scanLine[i] = qRgba(qRed(rgb)*alphaF, qGreen(rgb)*alphaF, qBlue(rgb)*alphaF, qAlpha(rgb)*alphaF);
- }
- }
- } else
- {
- for (int i=0; i<n; ++i)
- {
- int index = qLn(data[dataIndexFactor*i]/range.lower)/qLn(range.upper/range.lower)*(mLevelCount-1);
- if (index < 0)
- index = 0;
- else if (index >= mLevelCount)
- index = mLevelCount-1;
- if (alpha[dataIndexFactor*i] == 255)
- {
- scanLine[i] = mColorBuffer.at(index);
- } else
- {
- const QRgb rgb = mColorBuffer.at(index);
- const float alphaF = alpha[dataIndexFactor*i]/255.0f;
- scanLine[i] = qRgba(qRed(rgb)*alphaF, qGreen(rgb)*alphaF, qBlue(rgb)*alphaF, qAlpha(rgb)*alphaF);
- }
- }
- }
- }
- }
- /*! \internal
- This method is used to colorize a single data value given in \a position, to colors. The data
- range that shall be used for mapping the data value to the gradient is passed in \a range. \a
- logarithmic indicates whether the data value shall be mapped to a color logarithmically.
- If an entire array of data values shall be converted, rather use \ref colorize, for better
- performance.
- The returned QRgb has its r, g and b components premultiplied with alpha (see
- QImage::Format_ARGB32_Premultiplied).
- */
- QRgb QCPColorGradient::color(double position, const QCPRange &range, bool logarithmic)
- {
- // If you change something here, make sure to also adapt ::colorize()
- if (mColorBufferInvalidated)
- updateColorBuffer();
- int index = 0;
- if (!logarithmic)
- index = (position-range.lower)*(mLevelCount-1)/range.size();
- else
- index = qLn(position/range.lower)/qLn(range.upper/range.lower)*(mLevelCount-1);
- if (mPeriodic)
- {
- index = index % mLevelCount;
- if (index < 0)
- index += mLevelCount;
- } else
- {
- if (index < 0)
- index = 0;
- else if (index >= mLevelCount)
- index = mLevelCount-1;
- }
- return mColorBuffer.at(index);
- }
- /*!
- Clears the current color stops and loads the specified \a preset. A preset consists of predefined
- color stops and the corresponding color interpolation method.
-
- The available presets are:
- \image html QCPColorGradient.png
- */
- void QCPColorGradient::loadPreset(GradientPreset preset)
- {
- clearColorStops();
- switch (preset)
- {
- case gpGrayscale:
- setColorInterpolation(ciRGB);
- setColorStopAt(0, Qt::black);
- setColorStopAt(1, Qt::white);
- break;
- case gpHot:
- setColorInterpolation(ciRGB);
- setColorStopAt(0, QColor(50, 0, 0));
- setColorStopAt(0.2, QColor(180, 10, 0));
- setColorStopAt(0.4, QColor(245, 50, 0));
- setColorStopAt(0.6, QColor(255, 150, 10));
- setColorStopAt(0.8, QColor(255, 255, 50));
- setColorStopAt(1, QColor(255, 255, 255));
- break;
- case gpCold:
- setColorInterpolation(ciRGB);
- setColorStopAt(0, QColor(0, 0, 50));
- setColorStopAt(0.2, QColor(0, 10, 180));
- setColorStopAt(0.4, QColor(0, 50, 245));
- setColorStopAt(0.6, QColor(10, 150, 255));
- setColorStopAt(0.8, QColor(50, 255, 255));
- setColorStopAt(1, QColor(255, 255, 255));
- break;
- case gpNight:
- setColorInterpolation(ciHSV);
- setColorStopAt(0, QColor(10, 20, 30));
- setColorStopAt(1, QColor(250, 255, 250));
- break;
- case gpCandy:
- setColorInterpolation(ciHSV);
- setColorStopAt(0, QColor(0, 0, 255));
- setColorStopAt(1, QColor(255, 250, 250));
- break;
- case gpGeography:
- setColorInterpolation(ciRGB);
- setColorStopAt(0, QColor(70, 170, 210));
- setColorStopAt(0.20, QColor(90, 160, 180));
- setColorStopAt(0.25, QColor(45, 130, 175));
- setColorStopAt(0.30, QColor(100, 140, 125));
- setColorStopAt(0.5, QColor(100, 140, 100));
- setColorStopAt(0.6, QColor(130, 145, 120));
- setColorStopAt(0.7, QColor(140, 130, 120));
- setColorStopAt(0.9, QColor(180, 190, 190));
- setColorStopAt(1, QColor(210, 210, 230));
- break;
- case gpIon:
- setColorInterpolation(ciHSV);
- setColorStopAt(0, QColor(50, 10, 10));
- setColorStopAt(0.45, QColor(0, 0, 255));
- setColorStopAt(0.8, QColor(0, 255, 255));
- setColorStopAt(1, QColor(0, 255, 0));
- break;
- case gpThermal:
- setColorInterpolation(ciRGB);
- setColorStopAt(0, QColor(0, 0, 50));
- setColorStopAt(0.15, QColor(20, 0, 120));
- setColorStopAt(0.33, QColor(200, 30, 140));
- setColorStopAt(0.6, QColor(255, 100, 0));
- setColorStopAt(0.85, QColor(255, 255, 40));
- setColorStopAt(1, QColor(255, 255, 255));
- break;
- case gpPolar:
- setColorInterpolation(ciRGB);
- setColorStopAt(0, QColor(50, 255, 255));
- setColorStopAt(0.18, QColor(10, 70, 255));
- setColorStopAt(0.28, QColor(10, 10, 190));
- setColorStopAt(0.5, QColor(0, 0, 0));
- setColorStopAt(0.72, QColor(190, 10, 10));
- setColorStopAt(0.82, QColor(255, 70, 10));
- setColorStopAt(1, QColor(255, 255, 50));
- break;
- case gpSpectrum:
- setColorInterpolation(ciHSV);
- setColorStopAt(0, QColor(50, 0, 50));
- setColorStopAt(0.15, QColor(0, 0, 255));
- setColorStopAt(0.35, QColor(0, 255, 255));
- setColorStopAt(0.6, QColor(255, 255, 0));
- setColorStopAt(0.75, QColor(255, 30, 0));
- setColorStopAt(1, QColor(50, 0, 0));
- break;
- case gpJet:
- setColorInterpolation(ciRGB);
- setColorStopAt(0, QColor(0, 0, 100));
- setColorStopAt(0.15, QColor(0, 50, 255));
- setColorStopAt(0.35, QColor(0, 255, 255));
- setColorStopAt(0.65, QColor(255, 255, 0));
- setColorStopAt(0.85, QColor(255, 30, 0));
- setColorStopAt(1, QColor(100, 0, 0));
- break;
- case gpHues:
- setColorInterpolation(ciHSV);
- setColorStopAt(0, QColor(255, 0, 0));
- setColorStopAt(1.0/3.0, QColor(0, 0, 255));
- setColorStopAt(2.0/3.0, QColor(0, 255, 0));
- setColorStopAt(1, QColor(255, 0, 0));
- break;
- }
- }
- /*!
- Clears all color stops.
-
- \see setColorStops, setColorStopAt
- */
- void QCPColorGradient::clearColorStops()
- {
- mColorStops.clear();
- mColorBufferInvalidated = true;
- }
- /*!
- Returns an inverted gradient. The inverted gradient has all properties as this \ref
- QCPColorGradient, but the order of the color stops is inverted.
-
- \see setColorStops, setColorStopAt
- */
- QCPColorGradient QCPColorGradient::inverted() const
- {
- QCPColorGradient result(*this);
- result.clearColorStops();
- for (QMap<double, QColor>::const_iterator it=mColorStops.constBegin(); it!=mColorStops.constEnd(); ++it)
- result.setColorStopAt(1.0-it.key(), it.value());
- return result;
- }
- /*! \internal
-
- Returns true if the color gradient uses transparency, i.e. if any of the configured color stops
- has an alpha value below 255.
- */
- bool QCPColorGradient::stopsUseAlpha() const
- {
- for (QMap<double, QColor>::const_iterator it=mColorStops.constBegin(); it!=mColorStops.constEnd(); ++it)
- {
- if (it.value().alpha() < 255)
- return true;
- }
- return false;
- }
- /*! \internal
-
- Updates the internal color buffer which will be used by \ref colorize and \ref color, to quickly
- convert positions to colors. This is where the interpolation between color stops is calculated.
- */
- void QCPColorGradient::updateColorBuffer()
- {
- if (mColorBuffer.size() != mLevelCount)
- mColorBuffer.resize(mLevelCount);
- if (mColorStops.size() > 1)
- {
- double indexToPosFactor = 1.0/(double)(mLevelCount-1);
- const bool useAlpha = stopsUseAlpha();
- for (int i=0; i<mLevelCount; ++i)
- {
- double position = i*indexToPosFactor;
- QMap<double, QColor>::const_iterator it = mColorStops.lowerBound(position);
- if (it == mColorStops.constEnd()) // position is on or after last stop, use color of last stop
- {
- mColorBuffer[i] = (it-1).value().rgba();
- } else if (it == mColorStops.constBegin()) // position is on or before first stop, use color of first stop
- {
- mColorBuffer[i] = it.value().rgba();
- } else // position is in between stops (or on an intermediate stop), interpolate color
- {
- QMap<double, QColor>::const_iterator high = it;
- QMap<double, QColor>::const_iterator low = it-1;
- double t = (position-low.key())/(high.key()-low.key()); // interpolation factor 0..1
- switch (mColorInterpolation)
- {
- case ciRGB:
- {
- if (useAlpha)
- {
- const int alpha = (1-t)*low.value().alpha() + t*high.value().alpha();
- const float alphaPremultiplier = alpha/255.0f; // since we use QImage::Format_ARGB32_Premultiplied
- mColorBuffer[i] = qRgba(((1-t)*low.value().red() + t*high.value().red())*alphaPremultiplier,
- ((1-t)*low.value().green() + t*high.value().green())*alphaPremultiplier,
- ((1-t)*low.value().blue() + t*high.value().blue())*alphaPremultiplier,
- alpha);
- } else
- {
- mColorBuffer[i] = qRgb(((1-t)*low.value().red() + t*high.value().red()),
- ((1-t)*low.value().green() + t*high.value().green()),
- ((1-t)*low.value().blue() + t*high.value().blue()));
- }
- break;
- }
- case ciHSV:
- {
- QColor lowHsv = low.value().toHsv();
- QColor highHsv = high.value().toHsv();
- double hue = 0;
- double hueDiff = highHsv.hueF()-lowHsv.hueF();
- if (hueDiff > 0.5)
- hue = lowHsv.hueF() - t*(1.0-hueDiff);
- else if (hueDiff < -0.5)
- hue = lowHsv.hueF() + t*(1.0+hueDiff);
- else
- hue = lowHsv.hueF() + t*hueDiff;
- if (hue < 0) hue += 1.0;
- else if (hue >= 1.0) hue -= 1.0;
- if (useAlpha)
- {
- const QRgb rgb = QColor::fromHsvF(hue,
- (1-t)*lowHsv.saturationF() + t*highHsv.saturationF(),
- (1-t)*lowHsv.valueF() + t*highHsv.valueF()).rgb();
- const float alpha = (1-t)*lowHsv.alphaF() + t*highHsv.alphaF();
- mColorBuffer[i] = qRgba(qRed(rgb)*alpha, qGreen(rgb)*alpha, qBlue(rgb)*alpha, 255*alpha);
- }
- else
- {
- mColorBuffer[i] = QColor::fromHsvF(hue,
- (1-t)*lowHsv.saturationF() + t*highHsv.saturationF(),
- (1-t)*lowHsv.valueF() + t*highHsv.valueF()).rgb();
- }
- break;
- }
- }
- }
- }
- } else if (mColorStops.size() == 1)
- {
- const QRgb rgb = mColorStops.constBegin().value().rgb();
- const float alpha = mColorStops.constBegin().value().alphaF();
- mColorBuffer.fill(qRgba(qRed(rgb)*alpha, qGreen(rgb)*alpha, qBlue(rgb)*alpha, 255*alpha));
- } else // mColorStops is empty, fill color buffer with black
- {
- mColorBuffer.fill(qRgb(0, 0, 0));
- }
- mColorBufferInvalidated = false;
- }
- /* end of 'src/colorgradient.cpp' */
- /* including file 'src/selectiondecorator-bracket.cpp', size 12313 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPSelectionDecoratorBracket
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPSelectionDecoratorBracket
- \brief A selection decorator which draws brackets around each selected data segment
-
- Additionally to the regular highlighting of selected segments via color, fill and scatter style,
- this \ref QCPSelectionDecorator subclass draws markers at the begin and end of each selected data
- segment of the plottable.
-
- The shape of the markers can be controlled with \ref setBracketStyle, \ref setBracketWidth and
- \ref setBracketHeight. The color/fill can be controlled with \ref setBracketPen and \ref
- setBracketBrush.
-
- To introduce custom bracket styles, it is only necessary to sublcass \ref
- QCPSelectionDecoratorBracket and reimplement \ref drawBracket. The rest will be managed by the
- base class.
- */
- /*!
- Creates a new QCPSelectionDecoratorBracket instance with default values.
- */
- QCPSelectionDecoratorBracket::QCPSelectionDecoratorBracket() :
- mBracketPen(QPen(Qt::black)),
- mBracketBrush(Qt::NoBrush),
- mBracketWidth(5),
- mBracketHeight(50),
- mBracketStyle(bsSquareBracket),
- mTangentToData(false),
- mTangentAverage(2)
- {
-
- }
- QCPSelectionDecoratorBracket::~QCPSelectionDecoratorBracket()
- {
- }
- /*!
- Sets the pen that will be used to draw the brackets at the beginning and end of each selected
- data segment.
- */
- void QCPSelectionDecoratorBracket::setBracketPen(const QPen &pen)
- {
- mBracketPen = pen;
- }
- /*!
- Sets the brush that will be used to draw the brackets at the beginning and end of each selected
- data segment.
- */
- void QCPSelectionDecoratorBracket::setBracketBrush(const QBrush &brush)
- {
- mBracketBrush = brush;
- }
- /*!
- Sets the width of the drawn bracket. The width dimension is always parallel to the key axis of
- the data, or the tangent direction of the current data slope, if \ref setTangentToData is
- enabled.
- */
- void QCPSelectionDecoratorBracket::setBracketWidth(int width)
- {
- mBracketWidth = width;
- }
- /*!
- Sets the height of the drawn bracket. The height dimension is always perpendicular to the key axis
- of the data, or the tangent direction of the current data slope, if \ref setTangentToData is
- enabled.
- */
- void QCPSelectionDecoratorBracket::setBracketHeight(int height)
- {
- mBracketHeight = height;
- }
- /*!
- Sets the shape that the bracket/marker will have.
-
- \see setBracketWidth, setBracketHeight
- */
- void QCPSelectionDecoratorBracket::setBracketStyle(QCPSelectionDecoratorBracket::BracketStyle style)
- {
- mBracketStyle = style;
- }
- /*!
- Sets whether the brackets will be rotated such that they align with the slope of the data at the
- position that they appear in.
-
- For noisy data, it might be more visually appealing to average the slope over multiple data
- points. This can be configured via \ref setTangentAverage.
- */
- void QCPSelectionDecoratorBracket::setTangentToData(bool enabled)
- {
- mTangentToData = enabled;
- }
- /*!
- Controls over how many data points the slope shall be averaged, when brackets shall be aligned
- with the data (if \ref setTangentToData is true).
-
- From the position of the bracket, \a pointCount points towards the selected data range will be
- taken into account. The smallest value of \a pointCount is 1, which is effectively equivalent to
- disabling \ref setTangentToData.
- */
- void QCPSelectionDecoratorBracket::setTangentAverage(int pointCount)
- {
- mTangentAverage = pointCount;
- if (mTangentAverage < 1)
- mTangentAverage = 1;
- }
- /*!
- Draws the bracket shape with \a painter. The parameter \a direction is either -1 or 1 and
- indicates whether the bracket shall point to the left or the right (i.e. is a closing or opening
- bracket, respectively).
-
- The passed \a painter already contains all transformations that are necessary to position and
- rotate the bracket appropriately. Painting operations can be performed as if drawing upright
- brackets on flat data with horizontal key axis, with (0, 0) being the center of the bracket.
-
- If you wish to sublcass \ref QCPSelectionDecoratorBracket in order to provide custom bracket
- shapes (see \ref QCPSelectionDecoratorBracket::bsUserStyle), this is the method you should
- reimplement.
- */
- void QCPSelectionDecoratorBracket::drawBracket(QCPPainter *painter, int direction) const
- {
- switch (mBracketStyle)
- {
- case bsSquareBracket:
- {
- painter->drawLine(QLineF(mBracketWidth*direction, -mBracketHeight*0.5, 0, -mBracketHeight*0.5));
- painter->drawLine(QLineF(mBracketWidth*direction, mBracketHeight*0.5, 0, mBracketHeight*0.5));
- painter->drawLine(QLineF(0, -mBracketHeight*0.5, 0, mBracketHeight*0.5));
- break;
- }
- case bsHalfEllipse:
- {
- painter->drawArc(-mBracketWidth*0.5, -mBracketHeight*0.5, mBracketWidth, mBracketHeight, -90*16, -180*16*direction);
- break;
- }
- case bsEllipse:
- {
- painter->drawEllipse(-mBracketWidth*0.5, -mBracketHeight*0.5, mBracketWidth, mBracketHeight);
- break;
- }
- case bsPlus:
- {
- painter->drawLine(QLineF(0, -mBracketHeight*0.5, 0, mBracketHeight*0.5));
- painter->drawLine(QLineF(-mBracketWidth*0.5, 0, mBracketWidth*0.5, 0));
- break;
- }
- default:
- {
- qDebug() << Q_FUNC_INFO << "unknown/custom bracket style can't be handeld by default implementation:" << static_cast<int>(mBracketStyle);
- break;
- }
- }
- }
- /*!
- Draws the bracket decoration on the data points at the begin and end of each selected data
- segment given in \a seletion.
-
- It uses the method \ref drawBracket to actually draw the shapes.
-
- \seebaseclassmethod
- */
- void QCPSelectionDecoratorBracket::drawDecoration(QCPPainter *painter, QCPDataSelection selection)
- {
- if (!mPlottable || selection.isEmpty()) return;
-
- if (QCPPlottableInterface1D *interface1d = mPlottable->interface1D())
- {
- foreach (const QCPDataRange &dataRange, selection.dataRanges())
- {
- // determine position and (if tangent mode is enabled) angle of brackets:
- int openBracketDir = (mPlottable->keyAxis() && !mPlottable->keyAxis()->rangeReversed()) ? 1 : -1;
- int closeBracketDir = -openBracketDir;
- QPointF openBracketPos = getPixelCoordinates(interface1d, dataRange.begin());
- QPointF closeBracketPos = getPixelCoordinates(interface1d, dataRange.end()-1);
- double openBracketAngle = 0;
- double closeBracketAngle = 0;
- if (mTangentToData)
- {
- openBracketAngle = getTangentAngle(interface1d, dataRange.begin(), openBracketDir);
- closeBracketAngle = getTangentAngle(interface1d, dataRange.end()-1, closeBracketDir);
- }
- // draw opening bracket:
- QTransform oldTransform = painter->transform();
- painter->setPen(mBracketPen);
- painter->setBrush(mBracketBrush);
- painter->translate(openBracketPos);
- painter->rotate(openBracketAngle/M_PI*180.0);
- drawBracket(painter, openBracketDir);
- painter->setTransform(oldTransform);
- // draw closing bracket:
- painter->setPen(mBracketPen);
- painter->setBrush(mBracketBrush);
- painter->translate(closeBracketPos);
- painter->rotate(closeBracketAngle/M_PI*180.0);
- drawBracket(painter, closeBracketDir);
- painter->setTransform(oldTransform);
- }
- }
- }
- /*! \internal
-
- If \ref setTangentToData is enabled, brackets need to be rotated according to the data slope.
- This method returns the angle in radians by which a bracket at the given \a dataIndex must be
- rotated.
-
- The parameter \a direction must be set to either -1 or 1, representing whether it is an opening
- or closing bracket. Since for slope calculation multiple data points are required, this defines
- the direction in which the algorithm walks, starting at \a dataIndex, to average those data
- points. (see \ref setTangentToData and \ref setTangentAverage)
-
- \a interface1d is the interface to the plottable's data which is used to query data coordinates.
- */
- double QCPSelectionDecoratorBracket::getTangentAngle(const QCPPlottableInterface1D *interface1d, int dataIndex, int direction) const
- {
- if (!interface1d || dataIndex < 0 || dataIndex >= interface1d->dataCount())
- return 0;
- direction = direction < 0 ? -1 : 1; // enforce direction is either -1 or 1
-
- // how many steps we can actually go from index in the given direction without exceeding data bounds:
- int averageCount;
- if (direction < 0)
- averageCount = qMin(mTangentAverage, dataIndex);
- else
- averageCount = qMin(mTangentAverage, interface1d->dataCount()-1-dataIndex);
- qDebug() << averageCount;
- // calculate point average of averageCount points:
- QVector<QPointF> points(averageCount);
- QPointF pointsAverage;
- int currentIndex = dataIndex;
- for (int i=0; i<averageCount; ++i)
- {
- points[i] = getPixelCoordinates(interface1d, currentIndex);
- pointsAverage += points[i];
- currentIndex += direction;
- }
- pointsAverage /= (double)averageCount;
-
- // calculate slope of linear regression through points:
- double numSum = 0;
- double denomSum = 0;
- for (int i=0; i<averageCount; ++i)
- {
- const double dx = points.at(i).x()-pointsAverage.x();
- const double dy = points.at(i).y()-pointsAverage.y();
- numSum += dx*dy;
- denomSum += dx*dx;
- }
- if (!qFuzzyIsNull(denomSum) && !qFuzzyIsNull(numSum))
- {
- return qAtan2(numSum, denomSum);
- } else // undetermined angle, probably mTangentAverage == 1, so using only one data point
- return 0;
- }
- /*! \internal
-
- Returns the pixel coordinates of the data point at \a dataIndex, using \a interface1d to access
- the data points.
- */
- QPointF QCPSelectionDecoratorBracket::getPixelCoordinates(const QCPPlottableInterface1D *interface1d, int dataIndex) const
- {
- QCPAxis *keyAxis = mPlottable->keyAxis();
- QCPAxis *valueAxis = mPlottable->valueAxis();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPointF(0, 0); }
-
- if (keyAxis->orientation() == Qt::Horizontal)
- return QPointF(keyAxis->coordToPixel(interface1d->dataMainKey(dataIndex)), valueAxis->coordToPixel(interface1d->dataMainValue(dataIndex)));
- else
- return QPointF(valueAxis->coordToPixel(interface1d->dataMainValue(dataIndex)), keyAxis->coordToPixel(interface1d->dataMainKey(dataIndex)));
- }
- /* end of 'src/selectiondecorator-bracket.cpp' */
- /* including file 'src/layoutelements/layoutelement-axisrect.cpp', size 47584 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPAxisRect
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPAxisRect
- \brief Holds multiple axes and arranges them in a rectangular shape.
-
- This class represents an axis rect, a rectangular area that is bounded on all sides with an
- arbitrary number of axes.
-
- Initially QCustomPlot has one axis rect, accessible via QCustomPlot::axisRect(). However, the
- layout system allows to have multiple axis rects, e.g. arranged in a grid layout
- (QCustomPlot::plotLayout).
-
- By default, QCPAxisRect comes with four axes, at bottom, top, left and right. They can be
- accessed via \ref axis by providing the respective axis type (\ref QCPAxis::AxisType) and index.
- If you need all axes in the axis rect, use \ref axes. The top and right axes are set to be
- invisible initially (QCPAxis::setVisible). To add more axes to a side, use \ref addAxis or \ref
- addAxes. To remove an axis, use \ref removeAxis.
-
- The axis rect layerable itself only draws a background pixmap or color, if specified (\ref
- setBackground). It is placed on the "background" layer initially (see \ref QCPLayer for an
- explanation of the QCustomPlot layer system). The axes that are held by the axis rect can be
- placed on other layers, independently of the axis rect.
-
- Every axis rect has a child layout of type \ref QCPLayoutInset. It is accessible via \ref
- insetLayout and can be used to have other layout elements (or even other layouts with multiple
- elements) hovering inside the axis rect.
-
- If an axis rect is clicked and dragged, it processes this by moving certain axis ranges. The
- behaviour can be controlled with \ref setRangeDrag and \ref setRangeDragAxes. If the mouse wheel
- is scrolled while the cursor is on the axis rect, certain axes are scaled. This is controllable
- via \ref setRangeZoom, \ref setRangeZoomAxes and \ref setRangeZoomFactor. These interactions are
- only enabled if \ref QCustomPlot::setInteractions contains \ref QCP::iRangeDrag and \ref
- QCP::iRangeZoom.
-
- \image html AxisRectSpacingOverview.png
- <center>Overview of the spacings and paddings that define the geometry of an axis. The dashed
- line on the far left indicates the viewport/widget border.</center>
- */
- /* start documentation of inline functions */
- /*! \fn QCPLayoutInset *QCPAxisRect::insetLayout() const
-
- Returns the inset layout of this axis rect. It can be used to place other layout elements (or
- even layouts with multiple other elements) inside/on top of an axis rect.
-
- \see QCPLayoutInset
- */
- /*! \fn int QCPAxisRect::left() const
-
- Returns the pixel position of the left border of this axis rect. Margins are not taken into
- account here, so the returned value is with respect to the inner \ref rect.
- */
- /*! \fn int QCPAxisRect::right() const
-
- Returns the pixel position of the right border of this axis rect. Margins are not taken into
- account here, so the returned value is with respect to the inner \ref rect.
- */
- /*! \fn int QCPAxisRect::top() const
-
- Returns the pixel position of the top border of this axis rect. Margins are not taken into
- account here, so the returned value is with respect to the inner \ref rect.
- */
- /*! \fn int QCPAxisRect::bottom() const
-
- Returns the pixel position of the bottom border of this axis rect. Margins are not taken into
- account here, so the returned value is with respect to the inner \ref rect.
- */
- /*! \fn int QCPAxisRect::width() const
-
- Returns the pixel width of this axis rect. Margins are not taken into account here, so the
- returned value is with respect to the inner \ref rect.
- */
- /*! \fn int QCPAxisRect::height() const
-
- Returns the pixel height of this axis rect. Margins are not taken into account here, so the
- returned value is with respect to the inner \ref rect.
- */
- /*! \fn QSize QCPAxisRect::size() const
-
- Returns the pixel size of this axis rect. Margins are not taken into account here, so the
- returned value is with respect to the inner \ref rect.
- */
- /*! \fn QPoint QCPAxisRect::topLeft() const
-
- Returns the top left corner of this axis rect in pixels. Margins are not taken into account here,
- so the returned value is with respect to the inner \ref rect.
- */
- /*! \fn QPoint QCPAxisRect::topRight() const
-
- Returns the top right corner of this axis rect in pixels. Margins are not taken into account
- here, so the returned value is with respect to the inner \ref rect.
- */
- /*! \fn QPoint QCPAxisRect::bottomLeft() const
-
- Returns the bottom left corner of this axis rect in pixels. Margins are not taken into account
- here, so the returned value is with respect to the inner \ref rect.
- */
- /*! \fn QPoint QCPAxisRect::bottomRight() const
-
- Returns the bottom right corner of this axis rect in pixels. Margins are not taken into account
- here, so the returned value is with respect to the inner \ref rect.
- */
- /*! \fn QPoint QCPAxisRect::center() const
-
- Returns the center of this axis rect in pixels. Margins are not taken into account here, so the
- returned value is with respect to the inner \ref rect.
- */
- /* end documentation of inline functions */
- /*!
- Creates a QCPAxisRect instance and sets default values. An axis is added for each of the four
- sides, the top and right axes are set invisible initially.
- */
- QCPAxisRect::QCPAxisRect(QCustomPlot *parentPlot, bool setupDefaultAxes) :
- QCPLayoutElement(parentPlot),
- mBackgroundBrush(Qt::NoBrush),
- mBackgroundScaled(true),
- mBackgroundScaledMode(Qt::KeepAspectRatioByExpanding),
- mInsetLayout(new QCPLayoutInset),
- mRangeDrag(Qt::Horizontal|Qt::Vertical),
- mRangeZoom(Qt::Horizontal|Qt::Vertical),
- mRangeZoomFactorHorz(0.85),
- mRangeZoomFactorVert(0.85),
- mDragging(false)
- {
- mInsetLayout->initializeParentPlot(mParentPlot);
- mInsetLayout->setParentLayerable(this);
- mInsetLayout->setParent(this);
-
- setMinimumSize(50, 50);
- setMinimumMargins(QMargins(15, 15, 15, 15));
- mAxes.insert(QCPAxis::atLeft, QList<QCPAxis*>());
- mAxes.insert(QCPAxis::atRight, QList<QCPAxis*>());
- mAxes.insert(QCPAxis::atTop, QList<QCPAxis*>());
- mAxes.insert(QCPAxis::atBottom, QList<QCPAxis*>());
-
- if (setupDefaultAxes)
- {
- QCPAxis *xAxis = addAxis(QCPAxis::atBottom);
- QCPAxis *yAxis = addAxis(QCPAxis::atLeft);
- QCPAxis *xAxis2 = addAxis(QCPAxis::atTop);
- QCPAxis *yAxis2 = addAxis(QCPAxis::atRight);
- setRangeDragAxes(xAxis, yAxis);
- setRangeZoomAxes(xAxis, yAxis);
- xAxis2->setVisible(false);
- yAxis2->setVisible(false);
- xAxis->grid()->setVisible(true);
- yAxis->grid()->setVisible(true);
- xAxis2->grid()->setVisible(false);
- yAxis2->grid()->setVisible(false);
- xAxis2->grid()->setZeroLinePen(Qt::NoPen);
- yAxis2->grid()->setZeroLinePen(Qt::NoPen);
- xAxis2->grid()->setVisible(false);
- yAxis2->grid()->setVisible(false);
- }
- }
- QCPAxisRect::~QCPAxisRect()
- {
- delete mInsetLayout;
- mInsetLayout = 0;
-
- QList<QCPAxis*> axesList = axes();
- for (int i=0; i<axesList.size(); ++i)
- removeAxis(axesList.at(i));
- }
- /*!
- Returns the number of axes on the axis rect side specified with \a type.
-
- \see axis
- */
- int QCPAxisRect::axisCount(QCPAxis::AxisType type) const
- {
- return mAxes.value(type).size();
- }
- /*!
- Returns the axis with the given \a index on the axis rect side specified with \a type.
-
- \see axisCount, axes
- */
- QCPAxis *QCPAxisRect::axis(QCPAxis::AxisType type, int index) const
- {
- QList<QCPAxis*> ax(mAxes.value(type));
- if (index >= 0 && index < ax.size())
- {
- return ax.at(index);
- } else
- {
- qDebug() << Q_FUNC_INFO << "Axis index out of bounds:" << index;
- return 0;
- }
- }
- /*!
- Returns all axes on the axis rect sides specified with \a types.
-
- \a types may be a single \ref QCPAxis::AxisType or an <tt>or</tt>-combination, to get the axes of
- multiple sides.
-
- \see axis
- */
- QList<QCPAxis*> QCPAxisRect::axes(QCPAxis::AxisTypes types) const
- {
- QList<QCPAxis*> result;
- if (types.testFlag(QCPAxis::atLeft))
- result << mAxes.value(QCPAxis::atLeft);
- if (types.testFlag(QCPAxis::atRight))
- result << mAxes.value(QCPAxis::atRight);
- if (types.testFlag(QCPAxis::atTop))
- result << mAxes.value(QCPAxis::atTop);
- if (types.testFlag(QCPAxis::atBottom))
- result << mAxes.value(QCPAxis::atBottom);
- return result;
- }
- /*! \overload
-
- Returns all axes of this axis rect.
- */
- QList<QCPAxis*> QCPAxisRect::axes() const
- {
- QList<QCPAxis*> result;
- QHashIterator<QCPAxis::AxisType, QList<QCPAxis*> > it(mAxes);
- while (it.hasNext())
- {
- it.next();
- result << it.value();
- }
- return result;
- }
- /*!
- Adds a new axis to the axis rect side specified with \a type, and returns it. If \a axis is 0, a
- new QCPAxis instance is created internally. QCustomPlot owns the returned axis, so if you want to
- remove an axis, use \ref removeAxis instead of deleting it manually.
- You may inject QCPAxis instances (or subclasses of QCPAxis) by setting \a axis to an axis that was
- previously created outside QCustomPlot. It is important to note that QCustomPlot takes ownership
- of the axis, so you may not delete it afterwards. Further, the \a axis must have been created
- with this axis rect as parent and with the same axis type as specified in \a type. If this is not
- the case, a debug output is generated, the axis is not added, and the method returns 0.
- This method can not be used to move \a axis between axis rects. The same \a axis instance must
- not be added multiple times to the same or different axis rects.
- If an axis rect side already contains one or more axes, the lower and upper endings of the new
- axis (\ref QCPAxis::setLowerEnding, \ref QCPAxis::setUpperEnding) are set to \ref
- QCPLineEnding::esHalfBar.
- \see addAxes, setupFullAxesBox
- */
- QCPAxis *QCPAxisRect::addAxis(QCPAxis::AxisType type, QCPAxis *axis)
- {
- QCPAxis *newAxis = axis;
- if (!newAxis)
- {
- newAxis = new QCPAxis(this, type);
- } else // user provided existing axis instance, do some sanity checks
- {
- if (newAxis->axisType() != type)
- {
- qDebug() << Q_FUNC_INFO << "passed axis has different axis type than specified in type parameter";
- return 0;
- }
- if (newAxis->axisRect() != this)
- {
- qDebug() << Q_FUNC_INFO << "passed axis doesn't have this axis rect as parent axis rect";
- return 0;
- }
- if (axes().contains(newAxis))
- {
- qDebug() << Q_FUNC_INFO << "passed axis is already owned by this axis rect";
- return 0;
- }
- }
- if (mAxes[type].size() > 0) // multiple axes on one side, add half-bar axis ending to additional axes with offset
- {
- bool invert = (type == QCPAxis::atRight) || (type == QCPAxis::atBottom);
- newAxis->setLowerEnding(QCPLineEnding(QCPLineEnding::esHalfBar, 6, 10, !invert));
- newAxis->setUpperEnding(QCPLineEnding(QCPLineEnding::esHalfBar, 6, 10, invert));
- }
- mAxes[type].append(newAxis);
-
- // reset convenience axis pointers on parent QCustomPlot if they are unset:
- if (mParentPlot && mParentPlot->axisRectCount() > 0 && mParentPlot->axisRect(0) == this)
- {
- switch (type)
- {
- case QCPAxis::atBottom: { if (!mParentPlot->xAxis) mParentPlot->xAxis = newAxis; break; }
- case QCPAxis::atLeft: { if (!mParentPlot->yAxis) mParentPlot->yAxis = newAxis; break; }
- case QCPAxis::atTop: { if (!mParentPlot->xAxis2) mParentPlot->xAxis2 = newAxis; break; }
- case QCPAxis::atRight: { if (!mParentPlot->yAxis2) mParentPlot->yAxis2 = newAxis; break; }
- }
- }
-
- return newAxis;
- }
- /*!
- Adds a new axis with \ref addAxis to each axis rect side specified in \a types. This may be an
- <tt>or</tt>-combination of QCPAxis::AxisType, so axes can be added to multiple sides at once.
-
- Returns a list of the added axes.
-
- \see addAxis, setupFullAxesBox
- */
- QList<QCPAxis*> QCPAxisRect::addAxes(QCPAxis::AxisTypes types)
- {
- QList<QCPAxis*> result;
- if (types.testFlag(QCPAxis::atLeft))
- result << addAxis(QCPAxis::atLeft);
- if (types.testFlag(QCPAxis::atRight))
- result << addAxis(QCPAxis::atRight);
- if (types.testFlag(QCPAxis::atTop))
- result << addAxis(QCPAxis::atTop);
- if (types.testFlag(QCPAxis::atBottom))
- result << addAxis(QCPAxis::atBottom);
- return result;
- }
- /*!
- Removes the specified \a axis from the axis rect and deletes it.
-
- Returns true on success, i.e. if \a axis was a valid axis in this axis rect.
-
- \see addAxis
- */
- bool QCPAxisRect::removeAxis(QCPAxis *axis)
- {
- // don't access axis->axisType() to provide safety when axis is an invalid pointer, rather go through all axis containers:
- QHashIterator<QCPAxis::AxisType, QList<QCPAxis*> > it(mAxes);
- while (it.hasNext())
- {
- it.next();
- if (it.value().contains(axis))
- {
- if (it.value().first() == axis && it.value().size() > 1) // if removing first axis, transfer axis offset to the new first axis (which at this point is the second axis, if it exists)
- it.value()[1]->setOffset(axis->offset());
- mAxes[it.key()].removeOne(axis);
- if (qobject_cast<QCustomPlot*>(parentPlot())) // make sure this isn't called from QObject dtor when QCustomPlot is already destructed (happens when the axis rect is not in any layout and thus QObject-child of QCustomPlot)
- parentPlot()->axisRemoved(axis);
- delete axis;
- return true;
- }
- }
- qDebug() << Q_FUNC_INFO << "Axis isn't in axis rect:" << reinterpret_cast<quintptr>(axis);
- return false;
- }
- /*!
- Zooms in (or out) to the passed rectangular region \a pixelRect, given in pixel coordinates.
- All axes of this axis rect will have their range zoomed accordingly. If you only wish to zoom
- specific axes, use the overloaded version of this method.
-
- \see QCustomPlot::setSelectionRectMode
- */
- void QCPAxisRect::zoom(const QRectF &pixelRect)
- {
- zoom(pixelRect, axes());
- }
- /*! \overload
-
- Zooms in (or out) to the passed rectangular region \a pixelRect, given in pixel coordinates.
-
- Only the axes passed in \a affectedAxes will have their ranges zoomed accordingly.
-
- \see QCustomPlot::setSelectionRectMode
- */
- void QCPAxisRect::zoom(const QRectF &pixelRect, const QList<QCPAxis*> &affectedAxes)
- {
- foreach (QCPAxis *axis, affectedAxes)
- {
- if (!axis)
- {
- qDebug() << Q_FUNC_INFO << "a passed axis was zero";
- continue;
- }
- QCPRange pixelRange;
- if (axis->orientation() == Qt::Horizontal)
- pixelRange = QCPRange(pixelRect.left(), pixelRect.right());
- else
- pixelRange = QCPRange(pixelRect.top(), pixelRect.bottom());
- axis->setRange(axis->pixelToCoord(pixelRange.lower), axis->pixelToCoord(pixelRange.upper));
- }
- }
- /*!
- Convenience function to create an axis on each side that doesn't have any axes yet and set their
- visibility to true. Further, the top/right axes are assigned the following properties of the
- bottom/left axes:
- \li range (\ref QCPAxis::setRange)
- \li range reversed (\ref QCPAxis::setRangeReversed)
- \li scale type (\ref QCPAxis::setScaleType)
- \li tick visibility (\ref QCPAxis::setTicks)
- \li number format (\ref QCPAxis::setNumberFormat)
- \li number precision (\ref QCPAxis::setNumberPrecision)
- \li tick count of ticker (\ref QCPAxisTicker::setTickCount)
- \li tick origin of ticker (\ref QCPAxisTicker::setTickOrigin)
- Tick label visibility (\ref QCPAxis::setTickLabels) of the right and top axes are set to false.
- If \a connectRanges is true, the \ref QCPAxis::rangeChanged "rangeChanged" signals of the bottom
- and left axes are connected to the \ref QCPAxis::setRange slots of the top and right axes.
- */
- void QCPAxisRect::setupFullAxesBox(bool connectRanges)
- {
- QCPAxis *xAxis, *yAxis, *xAxis2, *yAxis2;
- if (axisCount(QCPAxis::atBottom) == 0)
- xAxis = addAxis(QCPAxis::atBottom);
- else
- xAxis = axis(QCPAxis::atBottom);
-
- if (axisCount(QCPAxis::atLeft) == 0)
- yAxis = addAxis(QCPAxis::atLeft);
- else
- yAxis = axis(QCPAxis::atLeft);
-
- if (axisCount(QCPAxis::atTop) == 0)
- xAxis2 = addAxis(QCPAxis::atTop);
- else
- xAxis2 = axis(QCPAxis::atTop);
-
- if (axisCount(QCPAxis::atRight) == 0)
- yAxis2 = addAxis(QCPAxis::atRight);
- else
- yAxis2 = axis(QCPAxis::atRight);
-
- xAxis->setVisible(true);
- yAxis->setVisible(true);
- xAxis2->setVisible(true);
- yAxis2->setVisible(true);
- xAxis2->setTickLabels(false);
- yAxis2->setTickLabels(false);
-
- xAxis2->setRange(xAxis->range());
- xAxis2->setRangeReversed(xAxis->rangeReversed());
- xAxis2->setScaleType(xAxis->scaleType());
- xAxis2->setTicks(xAxis->ticks());
- xAxis2->setNumberFormat(xAxis->numberFormat());
- xAxis2->setNumberPrecision(xAxis->numberPrecision());
- xAxis2->ticker()->setTickCount(xAxis->ticker()->tickCount());
- xAxis2->ticker()->setTickOrigin(xAxis->ticker()->tickOrigin());
-
- yAxis2->setRange(yAxis->range());
- yAxis2->setRangeReversed(yAxis->rangeReversed());
- yAxis2->setScaleType(yAxis->scaleType());
- yAxis2->setTicks(yAxis->ticks());
- yAxis2->setNumberFormat(yAxis->numberFormat());
- yAxis2->setNumberPrecision(yAxis->numberPrecision());
- yAxis2->ticker()->setTickCount(yAxis->ticker()->tickCount());
- yAxis2->ticker()->setTickOrigin(yAxis->ticker()->tickOrigin());
-
- if (connectRanges)
- {
- connect(xAxis, SIGNAL(rangeChanged(QCPRange)), xAxis2, SLOT(setRange(QCPRange)));
- connect(yAxis, SIGNAL(rangeChanged(QCPRange)), yAxis2, SLOT(setRange(QCPRange)));
- }
- }
- /*!
- Returns a list of all the plottables that are associated with this axis rect.
-
- A plottable is considered associated with an axis rect if its key or value axis (or both) is in
- this axis rect.
-
- \see graphs, items
- */
- QList<QCPAbstractPlottable*> QCPAxisRect::plottables() const
- {
- // Note: don't append all QCPAxis::plottables() into a list, because we might get duplicate entries
- QList<QCPAbstractPlottable*> result;
- for (int i=0; i<mParentPlot->mPlottables.size(); ++i)
- {
- if (mParentPlot->mPlottables.at(i)->keyAxis()->axisRect() == this || mParentPlot->mPlottables.at(i)->valueAxis()->axisRect() == this)
- result.append(mParentPlot->mPlottables.at(i));
- }
- return result;
- }
- /*!
- Returns a list of all the graphs that are associated with this axis rect.
-
- A graph is considered associated with an axis rect if its key or value axis (or both) is in
- this axis rect.
-
- \see plottables, items
- */
- QList<QCPGraph*> QCPAxisRect::graphs() const
- {
- // Note: don't append all QCPAxis::graphs() into a list, because we might get duplicate entries
- QList<QCPGraph*> result;
- for (int i=0; i<mParentPlot->mGraphs.size(); ++i)
- {
- if (mParentPlot->mGraphs.at(i)->keyAxis()->axisRect() == this || mParentPlot->mGraphs.at(i)->valueAxis()->axisRect() == this)
- result.append(mParentPlot->mGraphs.at(i));
- }
- return result;
- }
- /*!
- Returns a list of all the items that are associated with this axis rect.
-
- An item is considered associated with an axis rect if any of its positions has key or value axis
- set to an axis that is in this axis rect, or if any of its positions has \ref
- QCPItemPosition::setAxisRect set to the axis rect, or if the clip axis rect (\ref
- QCPAbstractItem::setClipAxisRect) is set to this axis rect.
-
- \see plottables, graphs
- */
- QList<QCPAbstractItem *> QCPAxisRect::items() const
- {
- // Note: don't just append all QCPAxis::items() into a list, because we might get duplicate entries
- // and miss those items that have this axis rect as clipAxisRect.
- QList<QCPAbstractItem*> result;
- for (int itemId=0; itemId<mParentPlot->mItems.size(); ++itemId)
- {
- if (mParentPlot->mItems.at(itemId)->clipAxisRect() == this)
- {
- result.append(mParentPlot->mItems.at(itemId));
- continue;
- }
- QList<QCPItemPosition*> positions = mParentPlot->mItems.at(itemId)->positions();
- for (int posId=0; posId<positions.size(); ++posId)
- {
- if (positions.at(posId)->axisRect() == this ||
- positions.at(posId)->keyAxis()->axisRect() == this ||
- positions.at(posId)->valueAxis()->axisRect() == this)
- {
- result.append(mParentPlot->mItems.at(itemId));
- break;
- }
- }
- }
- return result;
- }
- /*!
- This method is called automatically upon replot and doesn't need to be called by users of
- QCPAxisRect.
-
- Calls the base class implementation to update the margins (see \ref QCPLayoutElement::update),
- and finally passes the \ref rect to the inset layout (\ref insetLayout) and calls its
- QCPInsetLayout::update function.
-
- \seebaseclassmethod
- */
- void QCPAxisRect::update(UpdatePhase phase)
- {
- QCPLayoutElement::update(phase);
-
- switch (phase)
- {
- case upPreparation:
- {
- QList<QCPAxis*> allAxes = axes();
- for (int i=0; i<allAxes.size(); ++i)
- allAxes.at(i)->setupTickVectors();
- break;
- }
- case upLayout:
- {
- mInsetLayout->setOuterRect(rect());
- break;
- }
- default: break;
- }
-
- // pass update call on to inset layout (doesn't happen automatically, because QCPAxisRect doesn't derive from QCPLayout):
- mInsetLayout->update(phase);
- }
- /* inherits documentation from base class */
- QList<QCPLayoutElement*> QCPAxisRect::elements(bool recursive) const
- {
- QList<QCPLayoutElement*> result;
- if (mInsetLayout)
- {
- result << mInsetLayout;
- if (recursive)
- result << mInsetLayout->elements(recursive);
- }
- return result;
- }
- /* inherits documentation from base class */
- void QCPAxisRect::applyDefaultAntialiasingHint(QCPPainter *painter) const
- {
- painter->setAntialiasing(false);
- }
- /* inherits documentation from base class */
- void QCPAxisRect::draw(QCPPainter *painter)
- {
- drawBackground(painter);
- }
- /*!
- Sets \a pm as the axis background pixmap. The axis background pixmap will be drawn inside the
- axis rect. Since axis rects place themselves on the "background" layer by default, the axis rect
- backgrounds are usually drawn below everything else.
- For cases where the provided pixmap doesn't have the same size as the axis rect, scaling can be
- enabled with \ref setBackgroundScaled and the scaling mode (i.e. whether and how the aspect ratio
- is preserved) can be set with \ref setBackgroundScaledMode. To set all these options in one call,
- consider using the overloaded version of this function.
- Below the pixmap, the axis rect may be optionally filled with a brush, if specified with \ref
- setBackground(const QBrush &brush).
-
- \see setBackgroundScaled, setBackgroundScaledMode, setBackground(const QBrush &brush)
- */
- void QCPAxisRect::setBackground(const QPixmap &pm)
- {
- mBackgroundPixmap = pm;
- mScaledBackgroundPixmap = QPixmap();
- }
- /*! \overload
-
- Sets \a brush as the background brush. The axis rect background will be filled with this brush.
- Since axis rects place themselves on the "background" layer by default, the axis rect backgrounds
- are usually drawn below everything else.
- The brush will be drawn before (under) any background pixmap, which may be specified with \ref
- setBackground(const QPixmap &pm).
- To disable drawing of a background brush, set \a brush to Qt::NoBrush.
-
- \see setBackground(const QPixmap &pm)
- */
- void QCPAxisRect::setBackground(const QBrush &brush)
- {
- mBackgroundBrush = brush;
- }
- /*! \overload
-
- Allows setting the background pixmap of the axis rect, whether it shall be scaled and how it
- shall be scaled in one call.
- \see setBackground(const QPixmap &pm), setBackgroundScaled, setBackgroundScaledMode
- */
- void QCPAxisRect::setBackground(const QPixmap &pm, bool scaled, Qt::AspectRatioMode mode)
- {
- mBackgroundPixmap = pm;
- mScaledBackgroundPixmap = QPixmap();
- mBackgroundScaled = scaled;
- mBackgroundScaledMode = mode;
- }
- /*!
- Sets whether the axis background pixmap shall be scaled to fit the axis rect or not. If \a scaled
- is set to true, you may control whether and how the aspect ratio of the original pixmap is
- preserved with \ref setBackgroundScaledMode.
-
- Note that the scaled version of the original pixmap is buffered, so there is no performance
- penalty on replots. (Except when the axis rect dimensions are changed continuously.)
-
- \see setBackground, setBackgroundScaledMode
- */
- void QCPAxisRect::setBackgroundScaled(bool scaled)
- {
- mBackgroundScaled = scaled;
- }
- /*!
- If scaling of the axis background pixmap is enabled (\ref setBackgroundScaled), use this function to
- define whether and how the aspect ratio of the original pixmap passed to \ref setBackground is preserved.
- \see setBackground, setBackgroundScaled
- */
- void QCPAxisRect::setBackgroundScaledMode(Qt::AspectRatioMode mode)
- {
- mBackgroundScaledMode = mode;
- }
- /*!
- Returns the range drag axis of the \a orientation provided. If multiple axes were set, returns
- the first one (use \ref rangeDragAxes to retrieve a list with all set axes).
- \see setRangeDragAxes
- */
- QCPAxis *QCPAxisRect::rangeDragAxis(Qt::Orientation orientation)
- {
- if (orientation == Qt::Horizontal)
- return mRangeDragHorzAxis.isEmpty() ? 0 : mRangeDragHorzAxis.first().data();
- else
- return mRangeDragVertAxis.isEmpty() ? 0 : mRangeDragVertAxis.first().data();
- }
- /*!
- Returns the range zoom axis of the \a orientation provided. If multiple axes were set, returns
- the first one (use \ref rangeZoomAxes to retrieve a list with all set axes).
- \see setRangeZoomAxes
- */
- QCPAxis *QCPAxisRect::rangeZoomAxis(Qt::Orientation orientation)
- {
- if (orientation == Qt::Horizontal)
- return mRangeZoomHorzAxis.isEmpty() ? 0 : mRangeZoomHorzAxis.first().data();
- else
- return mRangeZoomVertAxis.isEmpty() ? 0 : mRangeZoomVertAxis.first().data();
- }
- /*!
- Returns all range drag axes of the \a orientation provided.
- \see rangeZoomAxis, setRangeZoomAxes
- */
- QList<QCPAxis*> QCPAxisRect::rangeDragAxes(Qt::Orientation orientation)
- {
- QList<QCPAxis*> result;
- if (orientation == Qt::Horizontal)
- {
- for (int i=0; i<mRangeDragHorzAxis.size(); ++i)
- {
- if (!mRangeDragHorzAxis.at(i).isNull())
- result.append(mRangeDragHorzAxis.at(i).data());
- }
- } else
- {
- for (int i=0; i<mRangeDragVertAxis.size(); ++i)
- {
- if (!mRangeDragVertAxis.at(i).isNull())
- result.append(mRangeDragVertAxis.at(i).data());
- }
- }
- return result;
- }
- /*!
- Returns all range zoom axes of the \a orientation provided.
- \see rangeDragAxis, setRangeDragAxes
- */
- QList<QCPAxis*> QCPAxisRect::rangeZoomAxes(Qt::Orientation orientation)
- {
- QList<QCPAxis*> result;
- if (orientation == Qt::Horizontal)
- {
- for (int i=0; i<mRangeZoomHorzAxis.size(); ++i)
- {
- if (!mRangeZoomHorzAxis.at(i).isNull())
- result.append(mRangeZoomHorzAxis.at(i).data());
- }
- } else
- {
- for (int i=0; i<mRangeZoomVertAxis.size(); ++i)
- {
- if (!mRangeZoomVertAxis.at(i).isNull())
- result.append(mRangeZoomVertAxis.at(i).data());
- }
- }
- return result;
- }
- /*!
- Returns the range zoom factor of the \a orientation provided.
-
- \see setRangeZoomFactor
- */
- double QCPAxisRect::rangeZoomFactor(Qt::Orientation orientation)
- {
- return (orientation == Qt::Horizontal ? mRangeZoomFactorHorz : mRangeZoomFactorVert);
- }
- /*!
- Sets which axis orientation may be range dragged by the user with mouse interaction.
- What orientation corresponds to which specific axis can be set with
- \ref setRangeDragAxes(QCPAxis *horizontal, QCPAxis *vertical). By
- default, the horizontal axis is the bottom axis (xAxis) and the vertical axis
- is the left axis (yAxis).
-
- To disable range dragging entirely, pass 0 as \a orientations or remove \ref QCP::iRangeDrag from \ref
- QCustomPlot::setInteractions. To enable range dragging for both directions, pass <tt>Qt::Horizontal |
- Qt::Vertical</tt> as \a orientations.
-
- In addition to setting \a orientations to a non-zero value, make sure \ref QCustomPlot::setInteractions
- contains \ref QCP::iRangeDrag to enable the range dragging interaction.
-
- \see setRangeZoom, setRangeDragAxes, QCustomPlot::setNoAntialiasingOnDrag
- */
- void QCPAxisRect::setRangeDrag(Qt::Orientations orientations)
- {
- mRangeDrag = orientations;
- }
- /*!
- Sets which axis orientation may be zoomed by the user with the mouse wheel. What orientation
- corresponds to which specific axis can be set with \ref setRangeZoomAxes(QCPAxis *horizontal,
- QCPAxis *vertical). By default, the horizontal axis is the bottom axis (xAxis) and the vertical
- axis is the left axis (yAxis).
- To disable range zooming entirely, pass 0 as \a orientations or remove \ref QCP::iRangeZoom from \ref
- QCustomPlot::setInteractions. To enable range zooming for both directions, pass <tt>Qt::Horizontal |
- Qt::Vertical</tt> as \a orientations.
-
- In addition to setting \a orientations to a non-zero value, make sure \ref QCustomPlot::setInteractions
- contains \ref QCP::iRangeZoom to enable the range zooming interaction.
-
- \see setRangeZoomFactor, setRangeZoomAxes, setRangeDrag
- */
- void QCPAxisRect::setRangeZoom(Qt::Orientations orientations)
- {
- mRangeZoom = orientations;
- }
- /*! \overload
-
- Sets the axes whose range will be dragged when \ref setRangeDrag enables mouse range dragging on
- the QCustomPlot widget. Pass 0 if no axis shall be dragged in the respective orientation.
- Use the overload taking a list of axes, if multiple axes (more than one per orientation) shall
- react to dragging interactions.
- \see setRangeZoomAxes
- */
- void QCPAxisRect::setRangeDragAxes(QCPAxis *horizontal, QCPAxis *vertical)
- {
- QList<QCPAxis*> horz, vert;
- if (horizontal)
- horz.append(horizontal);
- if (vertical)
- vert.append(vertical);
- setRangeDragAxes(horz, vert);
- }
- /*! \overload
- This method allows to set up multiple axes to react to horizontal and vertical dragging. The drag
- orientation that the respective axis will react to is deduced from its orientation (\ref
- QCPAxis::orientation).
- In the unusual case that you wish to e.g. drag a vertically oriented axis with a horizontal drag
- motion, use the overload taking two separate lists for horizontal and vertical dragging.
- */
- void QCPAxisRect::setRangeDragAxes(QList<QCPAxis*> axes)
- {
- QList<QCPAxis*> horz, vert;
- foreach (QCPAxis *ax, axes)
- {
- if (ax->orientation() == Qt::Horizontal)
- horz.append(ax);
- else
- vert.append(ax);
- }
- setRangeDragAxes(horz, vert);
- }
- /*! \overload
- This method allows to set multiple axes up to react to horizontal and vertical dragging, and
- define specifically which axis reacts to which drag orientation (irrespective of the axis
- orientation).
- */
- void QCPAxisRect::setRangeDragAxes(QList<QCPAxis*> horizontal, QList<QCPAxis*> vertical)
- {
- mRangeDragHorzAxis.clear();
- foreach (QCPAxis *ax, horizontal)
- {
- QPointer<QCPAxis> axPointer(ax);
- if (!axPointer.isNull())
- mRangeDragHorzAxis.append(axPointer);
- else
- qDebug() << Q_FUNC_INFO << "invalid axis passed in horizontal list:" << reinterpret_cast<quintptr>(ax);
- }
- mRangeDragVertAxis.clear();
- foreach (QCPAxis *ax, vertical)
- {
- QPointer<QCPAxis> axPointer(ax);
- if (!axPointer.isNull())
- mRangeDragVertAxis.append(axPointer);
- else
- qDebug() << Q_FUNC_INFO << "invalid axis passed in vertical list:" << reinterpret_cast<quintptr>(ax);
- }
- }
- /*!
- Sets the axes whose range will be zoomed when \ref setRangeZoom enables mouse wheel zooming on
- the QCustomPlot widget. Pass 0 if no axis shall be zoomed in the respective orientation.
- The two axes can be zoomed with different strengths, when different factors are passed to \ref
- setRangeZoomFactor(double horizontalFactor, double verticalFactor).
- Use the overload taking a list of axes, if multiple axes (more than one per orientation) shall
- react to zooming interactions.
- \see setRangeDragAxes
- */
- void QCPAxisRect::setRangeZoomAxes(QCPAxis *horizontal, QCPAxis *vertical)
- {
- QList<QCPAxis*> horz, vert;
- if (horizontal)
- horz.append(horizontal);
- if (vertical)
- vert.append(vertical);
- setRangeZoomAxes(horz, vert);
- }
- /*! \overload
- This method allows to set up multiple axes to react to horizontal and vertical range zooming. The
- zoom orientation that the respective axis will react to is deduced from its orientation (\ref
- QCPAxis::orientation).
- In the unusual case that you wish to e.g. zoom a vertically oriented axis with a horizontal zoom
- interaction, use the overload taking two separate lists for horizontal and vertical zooming.
- */
- void QCPAxisRect::setRangeZoomAxes(QList<QCPAxis*> axes)
- {
- QList<QCPAxis*> horz, vert;
- foreach (QCPAxis *ax, axes)
- {
- if (ax->orientation() == Qt::Horizontal)
- horz.append(ax);
- else
- vert.append(ax);
- }
- setRangeZoomAxes(horz, vert);
- }
- /*! \overload
- This method allows to set multiple axes up to react to horizontal and vertical zooming, and
- define specifically which axis reacts to which zoom orientation (irrespective of the axis
- orientation).
- */
- void QCPAxisRect::setRangeZoomAxes(QList<QCPAxis*> horizontal, QList<QCPAxis*> vertical)
- {
- mRangeZoomHorzAxis.clear();
- foreach (QCPAxis *ax, horizontal)
- {
- QPointer<QCPAxis> axPointer(ax);
- if (!axPointer.isNull())
- mRangeZoomHorzAxis.append(axPointer);
- else
- qDebug() << Q_FUNC_INFO << "invalid axis passed in horizontal list:" << reinterpret_cast<quintptr>(ax);
- }
- mRangeZoomVertAxis.clear();
- foreach (QCPAxis *ax, vertical)
- {
- QPointer<QCPAxis> axPointer(ax);
- if (!axPointer.isNull())
- mRangeZoomVertAxis.append(axPointer);
- else
- qDebug() << Q_FUNC_INFO << "invalid axis passed in vertical list:" << reinterpret_cast<quintptr>(ax);
- }
- }
- /*!
- Sets how strong one rotation step of the mouse wheel zooms, when range zoom was activated with
- \ref setRangeZoom. The two parameters \a horizontalFactor and \a verticalFactor provide a way to
- let the horizontal axis zoom at different rates than the vertical axis. Which axis is horizontal
- and which is vertical, can be set with \ref setRangeZoomAxes.
- When the zoom factor is greater than one, scrolling the mouse wheel backwards (towards the user)
- will zoom in (make the currently visible range smaller). For zoom factors smaller than one, the
- same scrolling direction will zoom out.
- */
- void QCPAxisRect::setRangeZoomFactor(double horizontalFactor, double verticalFactor)
- {
- mRangeZoomFactorHorz = horizontalFactor;
- mRangeZoomFactorVert = verticalFactor;
- }
- /*! \overload
-
- Sets both the horizontal and vertical zoom \a factor.
- */
- void QCPAxisRect::setRangeZoomFactor(double factor)
- {
- mRangeZoomFactorHorz = factor;
- mRangeZoomFactorVert = factor;
- }
- /*! \internal
-
- Draws the background of this axis rect. It may consist of a background fill (a QBrush) and a
- pixmap.
-
- If a brush was given via \ref setBackground(const QBrush &brush), this function first draws an
- according filling inside the axis rect with the provided \a painter.
-
- Then, if a pixmap was provided via \ref setBackground, this function buffers the scaled version
- depending on \ref setBackgroundScaled and \ref setBackgroundScaledMode and then draws it inside
- the axis rect with the provided \a painter. The scaled version is buffered in
- mScaledBackgroundPixmap to prevent expensive rescaling at every redraw. It is only updated, when
- the axis rect has changed in a way that requires a rescale of the background pixmap (this is
- dependent on the \ref setBackgroundScaledMode), or when a differend axis background pixmap was
- set.
-
- \see setBackground, setBackgroundScaled, setBackgroundScaledMode
- */
- void QCPAxisRect::drawBackground(QCPPainter *painter)
- {
- // draw background fill:
- if (mBackgroundBrush != Qt::NoBrush)
- painter->fillRect(mRect, mBackgroundBrush);
-
- // draw background pixmap (on top of fill, if brush specified):
- if (!mBackgroundPixmap.isNull())
- {
- if (mBackgroundScaled)
- {
- // check whether mScaledBackground needs to be updated:
- QSize scaledSize(mBackgroundPixmap.size());
- scaledSize.scale(mRect.size(), mBackgroundScaledMode);
- if (mScaledBackgroundPixmap.size() != scaledSize)
- mScaledBackgroundPixmap = mBackgroundPixmap.scaled(mRect.size(), mBackgroundScaledMode, Qt::SmoothTransformation);
- painter->drawPixmap(mRect.topLeft()+QPoint(0, -1), mScaledBackgroundPixmap, QRect(0, 0, mRect.width(), mRect.height()) & mScaledBackgroundPixmap.rect());
- } else
- {
- painter->drawPixmap(mRect.topLeft()+QPoint(0, -1), mBackgroundPixmap, QRect(0, 0, mRect.width(), mRect.height()));
- }
- }
- }
- /*! \internal
-
- This function makes sure multiple axes on the side specified with \a type don't collide, but are
- distributed according to their respective space requirement (QCPAxis::calculateMargin).
-
- It does this by setting an appropriate offset (\ref QCPAxis::setOffset) on all axes except the
- one with index zero.
-
- This function is called by \ref calculateAutoMargin.
- */
- void QCPAxisRect::updateAxesOffset(QCPAxis::AxisType type)
- {
- const QList<QCPAxis*> axesList = mAxes.value(type);
- if (axesList.isEmpty())
- return;
-
- bool isFirstVisible = !axesList.first()->visible(); // if the first axis is visible, the second axis (which is where the loop starts) isn't the first visible axis, so initialize with false
- for (int i=1; i<axesList.size(); ++i)
- {
- int offset = axesList.at(i-1)->offset() + axesList.at(i-1)->calculateMargin();
- if (axesList.at(i)->visible()) // only add inner tick length to offset if this axis is visible and it's not the first visible one (might happen if true first axis is invisible)
- {
- if (!isFirstVisible)
- offset += axesList.at(i)->tickLengthIn();
- isFirstVisible = false;
- }
- axesList.at(i)->setOffset(offset);
- }
- }
- /* inherits documentation from base class */
- int QCPAxisRect::calculateAutoMargin(QCP::MarginSide side)
- {
- if (!mAutoMargins.testFlag(side))
- qDebug() << Q_FUNC_INFO << "Called with side that isn't specified as auto margin";
-
- updateAxesOffset(QCPAxis::marginSideToAxisType(side));
-
- // note: only need to look at the last (outer most) axis to determine the total margin, due to updateAxisOffset call
- const QList<QCPAxis*> axesList = mAxes.value(QCPAxis::marginSideToAxisType(side));
- if (axesList.size() > 0)
- return axesList.last()->offset() + axesList.last()->calculateMargin();
- else
- return 0;
- }
- /*! \internal
-
- Reacts to a change in layout to potentially set the convenience axis pointers \ref
- QCustomPlot::xAxis, \ref QCustomPlot::yAxis, etc. of the parent QCustomPlot to the respective
- axes of this axis rect. This is only done if the respective convenience pointer is currently zero
- and if there is no QCPAxisRect at position (0, 0) of the plot layout.
-
- This automation makes it simpler to replace the main axis rect with a newly created one, without
- the need to manually reset the convenience pointers.
- */
- void QCPAxisRect::layoutChanged()
- {
- if (mParentPlot && mParentPlot->axisRectCount() > 0 && mParentPlot->axisRect(0) == this)
- {
- if (axisCount(QCPAxis::atBottom) > 0 && !mParentPlot->xAxis)
- mParentPlot->xAxis = axis(QCPAxis::atBottom);
- if (axisCount(QCPAxis::atLeft) > 0 && !mParentPlot->yAxis)
- mParentPlot->yAxis = axis(QCPAxis::atLeft);
- if (axisCount(QCPAxis::atTop) > 0 && !mParentPlot->xAxis2)
- mParentPlot->xAxis2 = axis(QCPAxis::atTop);
- if (axisCount(QCPAxis::atRight) > 0 && !mParentPlot->yAxis2)
- mParentPlot->yAxis2 = axis(QCPAxis::atRight);
- }
- }
- /*! \internal
-
- Event handler for when a mouse button is pressed on the axis rect. If the left mouse button is
- pressed, the range dragging interaction is initialized (the actual range manipulation happens in
- the \ref mouseMoveEvent).
- The mDragging flag is set to true and some anchor points are set that are needed to determine the
- distance the mouse was dragged in the mouse move/release events later.
-
- \see mouseMoveEvent, mouseReleaseEvent
- */
- void QCPAxisRect::mousePressEvent(QMouseEvent *event, const QVariant &details)
- {
- Q_UNUSED(details)
- if (event->buttons() & Qt::LeftButton)
- {
- mDragging = true;
- // initialize antialiasing backup in case we start dragging:
- if (mParentPlot->noAntialiasingOnDrag())
- {
- mAADragBackup = mParentPlot->antialiasedElements();
- mNotAADragBackup = mParentPlot->notAntialiasedElements();
- }
- // Mouse range dragging interaction:
- if (mParentPlot->interactions().testFlag(QCP::iRangeDrag))
- {
- mDragStartHorzRange.clear();
- for (int i=0; i<mRangeDragHorzAxis.size(); ++i)
- mDragStartHorzRange.append(mRangeDragHorzAxis.at(i).isNull() ? QCPRange() : mRangeDragHorzAxis.at(i)->range());
- mDragStartVertRange.clear();
- for (int i=0; i<mRangeDragVertAxis.size(); ++i)
- mDragStartVertRange.append(mRangeDragVertAxis.at(i).isNull() ? QCPRange() : mRangeDragVertAxis.at(i)->range());
- }
- }
- }
- /*! \internal
-
- Event handler for when the mouse is moved on the axis rect. If range dragging was activated in a
- preceding \ref mousePressEvent, the range is moved accordingly.
-
- \see mousePressEvent, mouseReleaseEvent
- */
- void QCPAxisRect::mouseMoveEvent(QMouseEvent *event, const QPointF &startPos)
- {
- Q_UNUSED(startPos)
- // Mouse range dragging interaction:
- if (mDragging && mParentPlot->interactions().testFlag(QCP::iRangeDrag))
- {
-
- if (mRangeDrag.testFlag(Qt::Horizontal))
- {
- for (int i=0; i<mRangeDragHorzAxis.size(); ++i)
- {
- QCPAxis *ax = mRangeDragHorzAxis.at(i).data();
- if (!ax)
- continue;
- if (i >= mDragStartHorzRange.size())
- break;
- if (ax->mScaleType == QCPAxis::stLinear)
- {
- double diff = ax->pixelToCoord(startPos.x()) - ax->pixelToCoord(event->pos().x());
- ax->setRange(mDragStartHorzRange.at(i).lower+diff, mDragStartHorzRange.at(i).upper+diff);
- } else if (ax->mScaleType == QCPAxis::stLogarithmic)
- {
- double diff = ax->pixelToCoord(startPos.x()) / ax->pixelToCoord(event->pos().x());
- ax->setRange(mDragStartHorzRange.at(i).lower*diff, mDragStartHorzRange.at(i).upper*diff);
- }
- }
- }
-
- if (mRangeDrag.testFlag(Qt::Vertical))
- {
- for (int i=0; i<mRangeDragVertAxis.size(); ++i)
- {
- QCPAxis *ax = mRangeDragVertAxis.at(i).data();
- if (!ax)
- continue;
- if (i >= mDragStartVertRange.size())
- break;
- if (ax->mScaleType == QCPAxis::stLinear)
- {
- double diff = ax->pixelToCoord(startPos.y()) - ax->pixelToCoord(event->pos().y());
- ax->setRange(mDragStartVertRange.at(i).lower+diff, mDragStartVertRange.at(i).upper+diff);
- } else if (ax->mScaleType == QCPAxis::stLogarithmic)
- {
- double diff = ax->pixelToCoord(startPos.y()) / ax->pixelToCoord(event->pos().y());
- ax->setRange(mDragStartVertRange.at(i).lower*diff, mDragStartVertRange.at(i).upper*diff);
- }
- }
- }
-
- if (mRangeDrag != 0) // if either vertical or horizontal drag was enabled, do a replot
- {
- if (mParentPlot->noAntialiasingOnDrag())
- mParentPlot->setNotAntialiasedElements(QCP::aeAll);
- mParentPlot->replot(QCustomPlot::rpQueuedReplot);
- }
-
- }
- }
- /* inherits documentation from base class */
- void QCPAxisRect::mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos)
- {
- Q_UNUSED(event)
- Q_UNUSED(startPos)
- mDragging = false;
- if (mParentPlot->noAntialiasingOnDrag())
- {
- mParentPlot->setAntialiasedElements(mAADragBackup);
- mParentPlot->setNotAntialiasedElements(mNotAADragBackup);
- }
- }
- /*! \internal
-
- Event handler for mouse wheel events. If rangeZoom is Qt::Horizontal, Qt::Vertical or both, the
- ranges of the axes defined as rangeZoomHorzAxis and rangeZoomVertAxis are scaled. The center of
- the scaling operation is the current cursor position inside the axis rect. The scaling factor is
- dependent on the mouse wheel delta (which direction the wheel was rotated) to provide a natural
- zooming feel. The Strength of the zoom can be controlled via \ref setRangeZoomFactor.
-
- Note, that event->delta() is usually +/-120 for single rotation steps. However, if the mouse
- wheel is turned rapidly, many steps may bunch up to one event, so the event->delta() may then be
- multiples of 120. This is taken into account here, by calculating \a wheelSteps and using it as
- exponent of the range zoom factor. This takes care of the wheel direction automatically, by
- inverting the factor, when the wheel step is negative (f^-1 = 1/f).
- */
- void QCPAxisRect::wheelEvent(QWheelEvent *event)
- {
- // Mouse range zooming interaction:
- if (mParentPlot->interactions().testFlag(QCP::iRangeZoom))
- {
- if (mRangeZoom != 0)
- {
- double factor;
- double wheelSteps = event->delta()/120.0; // a single step delta is +/-120 usually
- if (mRangeZoom.testFlag(Qt::Horizontal))
- {
- factor = qPow(mRangeZoomFactorHorz, wheelSteps);
- for (int i=0; i<mRangeZoomHorzAxis.size(); ++i)
- {
- if (!mRangeZoomHorzAxis.at(i).isNull())
- mRangeZoomHorzAxis.at(i)->scaleRange(factor, mRangeZoomHorzAxis.at(i)->pixelToCoord(event->pos().x()));
- }
- }
- if (mRangeZoom.testFlag(Qt::Vertical))
- {
- factor = qPow(mRangeZoomFactorVert, wheelSteps);
- for (int i=0; i<mRangeZoomVertAxis.size(); ++i)
- {
- if (!mRangeZoomVertAxis.at(i).isNull())
- mRangeZoomVertAxis.at(i)->scaleRange(factor, mRangeZoomVertAxis.at(i)->pixelToCoord(event->pos().y()));
- }
- }
- mParentPlot->replot();
- }
- }
- }
- /* end of 'src/layoutelements/layoutelement-axisrect.cpp' */
- /* including file 'src/layoutelements/layoutelement-legend.cpp', size 31097 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPAbstractLegendItem
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPAbstractLegendItem
- \brief The abstract base class for all entries in a QCPLegend.
-
- It defines a very basic interface for entries in a QCPLegend. For representing plottables in the
- legend, the subclass \ref QCPPlottableLegendItem is more suitable.
-
- Only derive directly from this class when you need absolute freedom (e.g. a custom legend entry
- that's not even associated with a plottable).
- You must implement the following pure virtual functions:
- \li \ref draw (from QCPLayerable)
-
- You inherit the following members you may use:
- <table>
- <tr>
- <td>QCPLegend *\b mParentLegend</td>
- <td>A pointer to the parent QCPLegend.</td>
- </tr><tr>
- <td>QFont \b mFont</td>
- <td>The generic font of the item. You should use this font for all or at least the most prominent text of the item.</td>
- </tr>
- </table>
- */
- /* start of documentation of signals */
- /*! \fn void QCPAbstractLegendItem::selectionChanged(bool selected)
-
- This signal is emitted when the selection state of this legend item has changed, either by user
- interaction or by a direct call to \ref setSelected.
- */
- /* end of documentation of signals */
- /*!
- Constructs a QCPAbstractLegendItem and associates it with the QCPLegend \a parent. This does not
- cause the item to be added to \a parent, so \ref QCPLegend::addItem must be called separately.
- */
- QCPAbstractLegendItem::QCPAbstractLegendItem(QCPLegend *parent) :
- QCPLayoutElement(parent->parentPlot()),
- mParentLegend(parent),
- mFont(parent->font()),
- mTextColor(parent->textColor()),
- mSelectedFont(parent->selectedFont()),
- mSelectedTextColor(parent->selectedTextColor()),
- mSelectable(true),
- mSelected(false)
- {
- setLayer(QLatin1String("legend"));
- setMargins(QMargins(0, 0, 0, 0));
- }
- /*!
- Sets the default font of this specific legend item to \a font.
-
- \see setTextColor, QCPLegend::setFont
- */
- void QCPAbstractLegendItem::setFont(const QFont &font)
- {
- mFont = font;
- }
- /*!
- Sets the default text color of this specific legend item to \a color.
-
- \see setFont, QCPLegend::setTextColor
- */
- void QCPAbstractLegendItem::setTextColor(const QColor &color)
- {
- mTextColor = color;
- }
- /*!
- When this legend item is selected, \a font is used to draw generic text, instead of the normal
- font set with \ref setFont.
-
- \see setFont, QCPLegend::setSelectedFont
- */
- void QCPAbstractLegendItem::setSelectedFont(const QFont &font)
- {
- mSelectedFont = font;
- }
- /*!
- When this legend item is selected, \a color is used to draw generic text, instead of the normal
- color set with \ref setTextColor.
-
- \see setTextColor, QCPLegend::setSelectedTextColor
- */
- void QCPAbstractLegendItem::setSelectedTextColor(const QColor &color)
- {
- mSelectedTextColor = color;
- }
- /*!
- Sets whether this specific legend item is selectable.
-
- \see setSelectedParts, QCustomPlot::setInteractions
- */
- void QCPAbstractLegendItem::setSelectable(bool selectable)
- {
- if (mSelectable != selectable)
- {
- mSelectable = selectable;
- emit selectableChanged(mSelectable);
- }
- }
- /*!
- Sets whether this specific legend item is selected.
-
- It is possible to set the selection state of this item by calling this function directly, even if
- setSelectable is set to false.
-
- \see setSelectableParts, QCustomPlot::setInteractions
- */
- void QCPAbstractLegendItem::setSelected(bool selected)
- {
- if (mSelected != selected)
- {
- mSelected = selected;
- emit selectionChanged(mSelected);
- }
- }
- /* inherits documentation from base class */
- double QCPAbstractLegendItem::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- Q_UNUSED(details)
- if (!mParentPlot) return -1;
- if (onlySelectable && (!mSelectable || !mParentLegend->selectableParts().testFlag(QCPLegend::spItems)))
- return -1;
-
- if (mRect.contains(pos.toPoint()))
- return mParentPlot->selectionTolerance()*0.99;
- else
- return -1;
- }
- /* inherits documentation from base class */
- void QCPAbstractLegendItem::applyDefaultAntialiasingHint(QCPPainter *painter) const
- {
- applyAntialiasingHint(painter, mAntialiased, QCP::aeLegendItems);
- }
- /* inherits documentation from base class */
- QRect QCPAbstractLegendItem::clipRect() const
- {
- return mOuterRect;
- }
- /* inherits documentation from base class */
- void QCPAbstractLegendItem::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
- {
- Q_UNUSED(event)
- Q_UNUSED(details)
- if (mSelectable && mParentLegend->selectableParts().testFlag(QCPLegend::spItems))
- {
- bool selBefore = mSelected;
- setSelected(additive ? !mSelected : true);
- if (selectionStateChanged)
- *selectionStateChanged = mSelected != selBefore;
- }
- }
- /* inherits documentation from base class */
- void QCPAbstractLegendItem::deselectEvent(bool *selectionStateChanged)
- {
- if (mSelectable && mParentLegend->selectableParts().testFlag(QCPLegend::spItems))
- {
- bool selBefore = mSelected;
- setSelected(false);
- if (selectionStateChanged)
- *selectionStateChanged = mSelected != selBefore;
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPPlottableLegendItem
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPPlottableLegendItem
- \brief A legend item representing a plottable with an icon and the plottable name.
-
- This is the standard legend item for plottables. It displays an icon of the plottable next to the
- plottable name. The icon is drawn by the respective plottable itself (\ref
- QCPAbstractPlottable::drawLegendIcon), and tries to give an intuitive symbol for the plottable.
- For example, the QCPGraph draws a centered horizontal line and/or a single scatter point in the
- middle.
-
- Legend items of this type are always associated with one plottable (retrievable via the
- plottable() function and settable with the constructor). You may change the font of the plottable
- name with \ref setFont. Icon padding and border pen is taken from the parent QCPLegend, see \ref
- QCPLegend::setIconBorderPen and \ref QCPLegend::setIconTextPadding.
- The function \ref QCPAbstractPlottable::addToLegend/\ref QCPAbstractPlottable::removeFromLegend
- creates/removes legend items of this type.
-
- Since QCPLegend is based on QCPLayoutGrid, a legend item itself is just a subclass of
- QCPLayoutElement. While it could be added to a legend (or any other layout) via the normal layout
- interface, QCPLegend has specialized functions for handling legend items conveniently, see the
- documentation of \ref QCPLegend.
- */
- /*!
- Creates a new legend item associated with \a plottable.
-
- Once it's created, it can be added to the legend via \ref QCPLegend::addItem.
-
- A more convenient way of adding/removing a plottable to/from the legend is via the functions \ref
- QCPAbstractPlottable::addToLegend and \ref QCPAbstractPlottable::removeFromLegend.
- */
- QCPPlottableLegendItem::QCPPlottableLegendItem(QCPLegend *parent, QCPAbstractPlottable *plottable) :
- QCPAbstractLegendItem(parent),
- mPlottable(plottable)
- {
- setAntialiased(false);
- }
- /*! \internal
-
- Returns the pen that shall be used to draw the icon border, taking into account the selection
- state of this item.
- */
- QPen QCPPlottableLegendItem::getIconBorderPen() const
- {
- return mSelected ? mParentLegend->selectedIconBorderPen() : mParentLegend->iconBorderPen();
- }
- /*! \internal
-
- Returns the text color that shall be used to draw text, taking into account the selection state
- of this item.
- */
- QColor QCPPlottableLegendItem::getTextColor() const
- {
- return mSelected ? mSelectedTextColor : mTextColor;
- }
- /*! \internal
-
- Returns the font that shall be used to draw text, taking into account the selection state of this
- item.
- */
- QFont QCPPlottableLegendItem::getFont() const
- {
- return mSelected ? mSelectedFont : mFont;
- }
- /*! \internal
-
- Draws the item with \a painter. The size and position of the drawn legend item is defined by the
- parent layout (typically a \ref QCPLegend) and the \ref minimumOuterSizeHint and \ref
- maximumOuterSizeHint of this legend item.
- */
- void QCPPlottableLegendItem::draw(QCPPainter *painter)
- {
- if (!mPlottable) return;
- painter->setFont(getFont());
- painter->setPen(QPen(getTextColor()));
- QSizeF iconSize = mParentLegend->iconSize();
- QRectF textRect = painter->fontMetrics().boundingRect(0, 0, 0, iconSize.height(), Qt::TextDontClip, mPlottable->name());
- QRectF iconRect(mRect.topLeft(), iconSize);
- int textHeight = qMax(textRect.height(), iconSize.height()); // if text has smaller height than icon, center text vertically in icon height, else align tops
- painter->drawText(mRect.x()+iconSize.width()+mParentLegend->iconTextPadding(), mRect.y(), textRect.width(), textHeight, Qt::TextDontClip, mPlottable->name());
- // draw icon:
- painter->save();
- painter->setClipRect(iconRect, Qt::IntersectClip);
- mPlottable->drawLegendIcon(painter, iconRect);
- painter->restore();
- // draw icon border:
- if (getIconBorderPen().style() != Qt::NoPen)
- {
- painter->setPen(getIconBorderPen());
- painter->setBrush(Qt::NoBrush);
- int halfPen = qCeil(painter->pen().widthF()*0.5)+1;
- painter->setClipRect(mOuterRect.adjusted(-halfPen, -halfPen, halfPen, halfPen)); // extend default clip rect so thicker pens (especially during selection) are not clipped
- painter->drawRect(iconRect);
- }
- }
- /*! \internal
-
- Calculates and returns the size of this item. This includes the icon, the text and the padding in
- between.
-
- \seebaseclassmethod
- */
- QSize QCPPlottableLegendItem::minimumOuterSizeHint() const
- {
- if (!mPlottable) return QSize();
- QSize result(0, 0);
- QRect textRect;
- QFontMetrics fontMetrics(getFont());
- QSize iconSize = mParentLegend->iconSize();
- textRect = fontMetrics.boundingRect(0, 0, 0, iconSize.height(), Qt::TextDontClip, mPlottable->name());
- result.setWidth(iconSize.width() + mParentLegend->iconTextPadding() + textRect.width());
- result.setHeight(qMax(textRect.height(), iconSize.height()));
- result.rwidth() += mMargins.left()+mMargins.right();
- result.rheight() += mMargins.top()+mMargins.bottom();
- return result;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPLegend
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPLegend
- \brief Manages a legend inside a QCustomPlot.
- A legend is a small box somewhere in the plot which lists plottables with their name and icon.
- A legend is populated with legend items by calling \ref QCPAbstractPlottable::addToLegend on the
- plottable, for which a legend item shall be created. In the case of the main legend (\ref
- QCustomPlot::legend), simply adding plottables to the plot while \ref
- QCustomPlot::setAutoAddPlottableToLegend is set to true (the default) creates corresponding
- legend items. The legend item associated with a certain plottable can be removed with \ref
- QCPAbstractPlottable::removeFromLegend. However, QCPLegend also offers an interface to add and
- manipulate legend items directly: \ref item, \ref itemWithPlottable, \ref itemCount, \ref
- addItem, \ref removeItem, etc.
- Since \ref QCPLegend derives from \ref QCPLayoutGrid, it can be placed in any position a \ref
- QCPLayoutElement may be positioned. The legend items are themselves \ref QCPLayoutElement
- "QCPLayoutElements" which are placed in the grid layout of the legend. \ref QCPLegend only adds
- an interface specialized for handling child elements of type \ref QCPAbstractLegendItem, as
- mentioned above. In principle, any other layout elements may also be added to a legend via the
- normal \ref QCPLayoutGrid interface. See the special page about \link thelayoutsystem The Layout
- System\endlink for examples on how to add other elements to the legend and move it outside the axis
- rect.
- Use the methods \ref setFillOrder and \ref setWrap inherited from \ref QCPLayoutGrid to control
- in which order (column first or row first) the legend is filled up when calling \ref addItem, and
- at which column or row wrapping occurs.
- By default, every QCustomPlot has one legend (\ref QCustomPlot::legend) which is placed in the
- inset layout of the main axis rect (\ref QCPAxisRect::insetLayout). To move the legend to another
- position inside the axis rect, use the methods of the \ref QCPLayoutInset. To move the legend
- outside of the axis rect, place it anywhere else with the \ref QCPLayout/\ref QCPLayoutElement
- interface.
- */
- /* start of documentation of signals */
- /*! \fn void QCPLegend::selectionChanged(QCPLegend::SelectableParts selection);
- This signal is emitted when the selection state of this legend has changed.
-
- \see setSelectedParts, setSelectableParts
- */
- /* end of documentation of signals */
- /*!
- Constructs a new QCPLegend instance with default values.
-
- Note that by default, QCustomPlot already contains a legend ready to be used as \ref
- QCustomPlot::legend
- */
- QCPLegend::QCPLegend()
- {
- setFillOrder(QCPLayoutGrid::foRowsFirst);
- setWrap(0);
-
- setRowSpacing(3);
- setColumnSpacing(8);
- setMargins(QMargins(7, 5, 7, 4));
- setAntialiased(false);
- setIconSize(32, 18);
-
- setIconTextPadding(7);
-
- setSelectableParts(spLegendBox | spItems);
- setSelectedParts(spNone);
-
- setBorderPen(QPen(Qt::black, 0));
- setSelectedBorderPen(QPen(Qt::blue, 2));
- setIconBorderPen(Qt::NoPen);
- setSelectedIconBorderPen(QPen(Qt::blue, 2));
- setBrush(Qt::white);
- setSelectedBrush(Qt::white);
- setTextColor(Qt::black);
- setSelectedTextColor(Qt::blue);
- }
- QCPLegend::~QCPLegend()
- {
- clearItems();
- if (qobject_cast<QCustomPlot*>(mParentPlot)) // make sure this isn't called from QObject dtor when QCustomPlot is already destructed (happens when the legend is not in any layout and thus QObject-child of QCustomPlot)
- mParentPlot->legendRemoved(this);
- }
- /* no doc for getter, see setSelectedParts */
- QCPLegend::SelectableParts QCPLegend::selectedParts() const
- {
- // check whether any legend elements selected, if yes, add spItems to return value
- bool hasSelectedItems = false;
- for (int i=0; i<itemCount(); ++i)
- {
- if (item(i) && item(i)->selected())
- {
- hasSelectedItems = true;
- break;
- }
- }
- if (hasSelectedItems)
- return mSelectedParts | spItems;
- else
- return mSelectedParts & ~spItems;
- }
- /*!
- Sets the pen, the border of the entire legend is drawn with.
- */
- void QCPLegend::setBorderPen(const QPen &pen)
- {
- mBorderPen = pen;
- }
- /*!
- Sets the brush of the legend background.
- */
- void QCPLegend::setBrush(const QBrush &brush)
- {
- mBrush = brush;
- }
- /*!
- Sets the default font of legend text. Legend items that draw text (e.g. the name of a graph) will
- use this font by default. However, a different font can be specified on a per-item-basis by
- accessing the specific legend item.
-
- This function will also set \a font on all already existing legend items.
-
- \see QCPAbstractLegendItem::setFont
- */
- void QCPLegend::setFont(const QFont &font)
- {
- mFont = font;
- for (int i=0; i<itemCount(); ++i)
- {
- if (item(i))
- item(i)->setFont(mFont);
- }
- }
- /*!
- Sets the default color of legend text. Legend items that draw text (e.g. the name of a graph)
- will use this color by default. However, a different colors can be specified on a per-item-basis
- by accessing the specific legend item.
-
- This function will also set \a color on all already existing legend items.
-
- \see QCPAbstractLegendItem::setTextColor
- */
- void QCPLegend::setTextColor(const QColor &color)
- {
- mTextColor = color;
- for (int i=0; i<itemCount(); ++i)
- {
- if (item(i))
- item(i)->setTextColor(color);
- }
- }
- /*!
- Sets the size of legend icons. Legend items that draw an icon (e.g. a visual
- representation of the graph) will use this size by default.
- */
- void QCPLegend::setIconSize(const QSize &size)
- {
- mIconSize = size;
- }
- /*! \overload
- */
- void QCPLegend::setIconSize(int width, int height)
- {
- mIconSize.setWidth(width);
- mIconSize.setHeight(height);
- }
- /*!
- Sets the horizontal space in pixels between the legend icon and the text next to it.
- Legend items that draw an icon (e.g. a visual representation of the graph) and text (e.g. the
- name of the graph) will use this space by default.
- */
- void QCPLegend::setIconTextPadding(int padding)
- {
- mIconTextPadding = padding;
- }
- /*!
- Sets the pen used to draw a border around each legend icon. Legend items that draw an
- icon (e.g. a visual representation of the graph) will use this pen by default.
-
- If no border is wanted, set this to \a Qt::NoPen.
- */
- void QCPLegend::setIconBorderPen(const QPen &pen)
- {
- mIconBorderPen = pen;
- }
- /*!
- Sets whether the user can (de-)select the parts in \a selectable by clicking on the QCustomPlot surface.
- (When \ref QCustomPlot::setInteractions contains \ref QCP::iSelectLegend.)
-
- However, even when \a selectable is set to a value not allowing the selection of a specific part,
- it is still possible to set the selection of this part manually, by calling \ref setSelectedParts
- directly.
-
- \see SelectablePart, setSelectedParts
- */
- void QCPLegend::setSelectableParts(const SelectableParts &selectable)
- {
- if (mSelectableParts != selectable)
- {
- mSelectableParts = selectable;
- emit selectableChanged(mSelectableParts);
- }
- }
- /*!
- Sets the selected state of the respective legend parts described by \ref SelectablePart. When a part
- is selected, it uses a different pen/font and brush. If some legend items are selected and \a selected
- doesn't contain \ref spItems, those items become deselected.
-
- The entire selection mechanism is handled automatically when \ref QCustomPlot::setInteractions
- contains iSelectLegend. You only need to call this function when you wish to change the selection
- state manually.
-
- This function can change the selection state of a part even when \ref setSelectableParts was set to a
- value that actually excludes the part.
-
- emits the \ref selectionChanged signal when \a selected is different from the previous selection state.
-
- Note that it doesn't make sense to set the selected state \ref spItems here when it wasn't set
- before, because there's no way to specify which exact items to newly select. Do this by calling
- \ref QCPAbstractLegendItem::setSelected directly on the legend item you wish to select.
-
- \see SelectablePart, setSelectableParts, selectTest, setSelectedBorderPen, setSelectedIconBorderPen, setSelectedBrush,
- setSelectedFont
- */
- void QCPLegend::setSelectedParts(const SelectableParts &selected)
- {
- SelectableParts newSelected = selected;
- mSelectedParts = this->selectedParts(); // update mSelectedParts in case item selection changed
- if (mSelectedParts != newSelected)
- {
- if (!mSelectedParts.testFlag(spItems) && newSelected.testFlag(spItems)) // attempt to set spItems flag (can't do that)
- {
- qDebug() << Q_FUNC_INFO << "spItems flag can not be set, it can only be unset with this function";
- newSelected &= ~spItems;
- }
- if (mSelectedParts.testFlag(spItems) && !newSelected.testFlag(spItems)) // spItems flag was unset, so clear item selection
- {
- for (int i=0; i<itemCount(); ++i)
- {
- if (item(i))
- item(i)->setSelected(false);
- }
- }
- mSelectedParts = newSelected;
- emit selectionChanged(mSelectedParts);
- }
- }
- /*!
- When the legend box is selected, this pen is used to draw the border instead of the normal pen
- set via \ref setBorderPen.
- \see setSelectedParts, setSelectableParts, setSelectedBrush
- */
- void QCPLegend::setSelectedBorderPen(const QPen &pen)
- {
- mSelectedBorderPen = pen;
- }
- /*!
- Sets the pen legend items will use to draw their icon borders, when they are selected.
- \see setSelectedParts, setSelectableParts, setSelectedFont
- */
- void QCPLegend::setSelectedIconBorderPen(const QPen &pen)
- {
- mSelectedIconBorderPen = pen;
- }
- /*!
- When the legend box is selected, this brush is used to draw the legend background instead of the normal brush
- set via \ref setBrush.
- \see setSelectedParts, setSelectableParts, setSelectedBorderPen
- */
- void QCPLegend::setSelectedBrush(const QBrush &brush)
- {
- mSelectedBrush = brush;
- }
- /*!
- Sets the default font that is used by legend items when they are selected.
-
- This function will also set \a font on all already existing legend items.
- \see setFont, QCPAbstractLegendItem::setSelectedFont
- */
- void QCPLegend::setSelectedFont(const QFont &font)
- {
- mSelectedFont = font;
- for (int i=0; i<itemCount(); ++i)
- {
- if (item(i))
- item(i)->setSelectedFont(font);
- }
- }
- /*!
- Sets the default text color that is used by legend items when they are selected.
-
- This function will also set \a color on all already existing legend items.
- \see setTextColor, QCPAbstractLegendItem::setSelectedTextColor
- */
- void QCPLegend::setSelectedTextColor(const QColor &color)
- {
- mSelectedTextColor = color;
- for (int i=0; i<itemCount(); ++i)
- {
- if (item(i))
- item(i)->setSelectedTextColor(color);
- }
- }
- /*!
- Returns the item with index \a i.
- Note that the linear index depends on the current fill order (\ref setFillOrder).
- \see itemCount, addItem, itemWithPlottable
- */
- QCPAbstractLegendItem *QCPLegend::item(int index) const
- {
- return qobject_cast<QCPAbstractLegendItem*>(elementAt(index));
- }
- /*!
- Returns the QCPPlottableLegendItem which is associated with \a plottable (e.g. a \ref QCPGraph*).
- If such an item isn't in the legend, returns 0.
-
- \see hasItemWithPlottable
- */
- QCPPlottableLegendItem *QCPLegend::itemWithPlottable(const QCPAbstractPlottable *plottable) const
- {
- for (int i=0; i<itemCount(); ++i)
- {
- if (QCPPlottableLegendItem *pli = qobject_cast<QCPPlottableLegendItem*>(item(i)))
- {
- if (pli->plottable() == plottable)
- return pli;
- }
- }
- return 0;
- }
- /*!
- Returns the number of items currently in the legend.
- Note that if empty cells are in the legend (e.g. by calling methods of the \ref QCPLayoutGrid
- base class which allows creating empty cells), they are included in the returned count.
- \see item
- */
- int QCPLegend::itemCount() const
- {
- return elementCount();
- }
- /*!
- Returns whether the legend contains \a item.
-
- \see hasItemWithPlottable
- */
- bool QCPLegend::hasItem(QCPAbstractLegendItem *item) const
- {
- for (int i=0; i<itemCount(); ++i)
- {
- if (item == this->item(i))
- return true;
- }
- return false;
- }
- /*!
- Returns whether the legend contains a QCPPlottableLegendItem which is associated with \a plottable (e.g. a \ref QCPGraph*).
- If such an item isn't in the legend, returns false.
-
- \see itemWithPlottable
- */
- bool QCPLegend::hasItemWithPlottable(const QCPAbstractPlottable *plottable) const
- {
- return itemWithPlottable(plottable);
- }
- /*!
- Adds \a item to the legend, if it's not present already. The element is arranged according to the
- current fill order (\ref setFillOrder) and wrapping (\ref setWrap).
- Returns true on sucess, i.e. if the item wasn't in the list already and has been successfuly added.
- The legend takes ownership of the item.
- \see removeItem, item, hasItem
- */
- bool QCPLegend::addItem(QCPAbstractLegendItem *item)
- {
- return addElement(item);
- }
- /*! \overload
- Removes the item with the specified \a index from the legend and deletes it.
- After successful removal, the legend is reordered according to the current fill order (\ref
- setFillOrder) and wrapping (\ref setWrap), so no empty cell remains where the removed \a item
- was. If you don't want this, rather use the raw element interface of \ref QCPLayoutGrid.
- Returns true, if successful. Unlike \ref QCPLayoutGrid::removeAt, this method only removes
- elements derived from \ref QCPAbstractLegendItem.
- \see itemCount, clearItems
- */
- bool QCPLegend::removeItem(int index)
- {
- if (QCPAbstractLegendItem *ali = item(index))
- {
- bool success = remove(ali);
- if (success)
- setFillOrder(fillOrder(), true); // gets rid of empty cell by reordering
- return success;
- } else
- return false;
- }
- /*! \overload
- Removes \a item from the legend and deletes it.
- After successful removal, the legend is reordered according to the current fill order (\ref
- setFillOrder) and wrapping (\ref setWrap), so no empty cell remains where the removed \a item
- was. If you don't want this, rather use the raw element interface of \ref QCPLayoutGrid.
- Returns true, if successful.
- \see clearItems
- */
- bool QCPLegend::removeItem(QCPAbstractLegendItem *item)
- {
- bool success = remove(item);
- if (success)
- setFillOrder(fillOrder(), true); // gets rid of empty cell by reordering
- return success;
- }
- /*!
- Removes all items from the legend.
- */
- void QCPLegend::clearItems()
- {
- for (int i=itemCount()-1; i>=0; --i)
- removeItem(i);
- }
- /*!
- Returns the legend items that are currently selected. If no items are selected,
- the list is empty.
-
- \see QCPAbstractLegendItem::setSelected, setSelectable
- */
- QList<QCPAbstractLegendItem *> QCPLegend::selectedItems() const
- {
- QList<QCPAbstractLegendItem*> result;
- for (int i=0; i<itemCount(); ++i)
- {
- if (QCPAbstractLegendItem *ali = item(i))
- {
- if (ali->selected())
- result.append(ali);
- }
- }
- return result;
- }
- /*! \internal
- A convenience function to easily set the QPainter::Antialiased hint on the provided \a painter
- before drawing main legend elements.
- This is the antialiasing state the painter passed to the \ref draw method is in by default.
-
- This function takes into account the local setting of the antialiasing flag as well as the
- overrides set with \ref QCustomPlot::setAntialiasedElements and \ref
- QCustomPlot::setNotAntialiasedElements.
-
- \seebaseclassmethod
-
- \see setAntialiased
- */
- void QCPLegend::applyDefaultAntialiasingHint(QCPPainter *painter) const
- {
- applyAntialiasingHint(painter, mAntialiased, QCP::aeLegend);
- }
- /*! \internal
-
- Returns the pen used to paint the border of the legend, taking into account the selection state
- of the legend box.
- */
- QPen QCPLegend::getBorderPen() const
- {
- return mSelectedParts.testFlag(spLegendBox) ? mSelectedBorderPen : mBorderPen;
- }
- /*! \internal
-
- Returns the brush used to paint the background of the legend, taking into account the selection
- state of the legend box.
- */
- QBrush QCPLegend::getBrush() const
- {
- return mSelectedParts.testFlag(spLegendBox) ? mSelectedBrush : mBrush;
- }
- /*! \internal
-
- Draws the legend box with the provided \a painter. The individual legend items are layerables
- themselves, thus are drawn independently.
- */
- void QCPLegend::draw(QCPPainter *painter)
- {
- // draw background rect:
- painter->setBrush(getBrush());
- painter->setPen(getBorderPen());
- painter->drawRect(mOuterRect);
- }
- /* inherits documentation from base class */
- double QCPLegend::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- if (!mParentPlot) return -1;
- if (onlySelectable && !mSelectableParts.testFlag(spLegendBox))
- return -1;
-
- if (mOuterRect.contains(pos.toPoint()))
- {
- if (details) details->setValue(spLegendBox);
- return mParentPlot->selectionTolerance()*0.99;
- }
- return -1;
- }
- /* inherits documentation from base class */
- void QCPLegend::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
- {
- Q_UNUSED(event)
- mSelectedParts = selectedParts(); // in case item selection has changed
- if (details.value<SelectablePart>() == spLegendBox && mSelectableParts.testFlag(spLegendBox))
- {
- SelectableParts selBefore = mSelectedParts;
- setSelectedParts(additive ? mSelectedParts^spLegendBox : mSelectedParts|spLegendBox); // no need to unset spItems in !additive case, because they will be deselected by QCustomPlot (they're normal QCPLayerables with own deselectEvent)
- if (selectionStateChanged)
- *selectionStateChanged = mSelectedParts != selBefore;
- }
- }
- /* inherits documentation from base class */
- void QCPLegend::deselectEvent(bool *selectionStateChanged)
- {
- mSelectedParts = selectedParts(); // in case item selection has changed
- if (mSelectableParts.testFlag(spLegendBox))
- {
- SelectableParts selBefore = mSelectedParts;
- setSelectedParts(selectedParts() & ~spLegendBox);
- if (selectionStateChanged)
- *selectionStateChanged = mSelectedParts != selBefore;
- }
- }
- /* inherits documentation from base class */
- QCP::Interaction QCPLegend::selectionCategory() const
- {
- return QCP::iSelectLegend;
- }
- /* inherits documentation from base class */
- QCP::Interaction QCPAbstractLegendItem::selectionCategory() const
- {
- return QCP::iSelectLegend;
- }
- /* inherits documentation from base class */
- void QCPLegend::parentPlotInitialized(QCustomPlot *parentPlot)
- {
- if (parentPlot && !parentPlot->legend)
- parentPlot->legend = this;
- }
- /* end of 'src/layoutelements/layoutelement-legend.cpp' */
- /* including file 'src/layoutelements/layoutelement-textelement.cpp', size 12761 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPTextElement
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPTextElement
- \brief A layout element displaying a text
- The text may be specified with \ref setText, the formatting can be controlled with \ref setFont,
- \ref setTextColor, and \ref setTextFlags.
- A text element can be added as follows:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcptextelement-creation
- */
- /* start documentation of signals */
- /*! \fn void QCPTextElement::selectionChanged(bool selected)
-
- This signal is emitted when the selection state has changed to \a selected, either by user
- interaction or by a direct call to \ref setSelected.
-
- \see setSelected, setSelectable
- */
- /*! \fn void QCPTextElement::clicked(QMouseEvent *event)
- This signal is emitted when the text element is clicked.
- \see doubleClicked, selectTest
- */
- /*! \fn void QCPTextElement::doubleClicked(QMouseEvent *event)
- This signal is emitted when the text element is double clicked.
- \see clicked, selectTest
- */
- /* end documentation of signals */
- /*! \overload
-
- Creates a new QCPTextElement instance and sets default values. The initial text is empty (\ref
- setText).
- */
- QCPTextElement::QCPTextElement(QCustomPlot *parentPlot) :
- QCPLayoutElement(parentPlot),
- mText(),
- mTextFlags(Qt::AlignCenter|Qt::TextWordWrap),
- mFont(QFont(QLatin1String("sans serif"), 12)), // will be taken from parentPlot if available, see below
- mTextColor(Qt::black),
- mSelectedFont(QFont(QLatin1String("sans serif"), 12)), // will be taken from parentPlot if available, see below
- mSelectedTextColor(Qt::blue),
- mSelectable(false),
- mSelected(false)
- {
- if (parentPlot)
- {
- mFont = parentPlot->font();
- mSelectedFont = parentPlot->font();
- }
- setMargins(QMargins(2, 2, 2, 2));
- }
- /*! \overload
-
- Creates a new QCPTextElement instance and sets default values.
- The initial text is set to \a text.
- */
- QCPTextElement::QCPTextElement(QCustomPlot *parentPlot, const QString &text) :
- QCPLayoutElement(parentPlot),
- mText(text),
- mTextFlags(Qt::AlignCenter|Qt::TextWordWrap),
- mFont(QFont(QLatin1String("sans serif"), 12)), // will be taken from parentPlot if available, see below
- mTextColor(Qt::black),
- mSelectedFont(QFont(QLatin1String("sans serif"), 12)), // will be taken from parentPlot if available, see below
- mSelectedTextColor(Qt::blue),
- mSelectable(false),
- mSelected(false)
- {
- if (parentPlot)
- {
- mFont = parentPlot->font();
- mSelectedFont = parentPlot->font();
- }
- setMargins(QMargins(2, 2, 2, 2));
- }
- /*! \overload
-
- Creates a new QCPTextElement instance and sets default values.
- The initial text is set to \a text with \a pointSize.
- */
- QCPTextElement::QCPTextElement(QCustomPlot *parentPlot, const QString &text, double pointSize) :
- QCPLayoutElement(parentPlot),
- mText(text),
- mTextFlags(Qt::AlignCenter|Qt::TextWordWrap),
- mFont(QFont(QLatin1String("sans serif"), pointSize)), // will be taken from parentPlot if available, see below
- mTextColor(Qt::black),
- mSelectedFont(QFont(QLatin1String("sans serif"), pointSize)), // will be taken from parentPlot if available, see below
- mSelectedTextColor(Qt::blue),
- mSelectable(false),
- mSelected(false)
- {
- if (parentPlot)
- {
- mFont = parentPlot->font();
- mFont.setPointSizeF(pointSize);
- mSelectedFont = parentPlot->font();
- mSelectedFont.setPointSizeF(pointSize);
- }
- setMargins(QMargins(2, 2, 2, 2));
- }
- /*! \overload
-
- Creates a new QCPTextElement instance and sets default values.
- The initial text is set to \a text with \a pointSize and the specified \a fontFamily.
- */
- QCPTextElement::QCPTextElement(QCustomPlot *parentPlot, const QString &text, const QString &fontFamily, double pointSize) :
- QCPLayoutElement(parentPlot),
- mText(text),
- mTextFlags(Qt::AlignCenter|Qt::TextWordWrap),
- mFont(QFont(fontFamily, pointSize)),
- mTextColor(Qt::black),
- mSelectedFont(QFont(fontFamily, pointSize)),
- mSelectedTextColor(Qt::blue),
- mSelectable(false),
- mSelected(false)
- {
- setMargins(QMargins(2, 2, 2, 2));
- }
- /*! \overload
-
- Creates a new QCPTextElement instance and sets default values.
- The initial text is set to \a text with the specified \a font.
- */
- QCPTextElement::QCPTextElement(QCustomPlot *parentPlot, const QString &text, const QFont &font) :
- QCPLayoutElement(parentPlot),
- mText(text),
- mTextFlags(Qt::AlignCenter|Qt::TextWordWrap),
- mFont(font),
- mTextColor(Qt::black),
- mSelectedFont(font),
- mSelectedTextColor(Qt::blue),
- mSelectable(false),
- mSelected(false)
- {
- setMargins(QMargins(2, 2, 2, 2));
- }
- /*!
- Sets the text that will be displayed to \a text. Multiple lines can be created by insertion of "\n".
-
- \see setFont, setTextColor, setTextFlags
- */
- void QCPTextElement::setText(const QString &text)
- {
- mText = text;
- }
- /*!
- Sets options for text alignment and wrapping behaviour. \a flags is a bitwise OR-combination of
- \c Qt::AlignmentFlag and \c Qt::TextFlag enums.
-
- Possible enums are:
- - Qt::AlignLeft
- - Qt::AlignRight
- - Qt::AlignHCenter
- - Qt::AlignJustify
- - Qt::AlignTop
- - Qt::AlignBottom
- - Qt::AlignVCenter
- - Qt::AlignCenter
- - Qt::TextDontClip
- - Qt::TextSingleLine
- - Qt::TextExpandTabs
- - Qt::TextShowMnemonic
- - Qt::TextWordWrap
- - Qt::TextIncludeTrailingSpaces
- */
- void QCPTextElement::setTextFlags(int flags)
- {
- mTextFlags = flags;
- }
- /*!
- Sets the \a font of the text.
-
- \see setTextColor, setSelectedFont
- */
- void QCPTextElement::setFont(const QFont &font)
- {
- mFont = font;
- }
- /*!
- Sets the \a color of the text.
-
- \see setFont, setSelectedTextColor
- */
- void QCPTextElement::setTextColor(const QColor &color)
- {
- mTextColor = color;
- }
- /*!
- Sets the \a font of the text that will be used if the text element is selected (\ref setSelected).
-
- \see setFont
- */
- void QCPTextElement::setSelectedFont(const QFont &font)
- {
- mSelectedFont = font;
- }
- /*!
- Sets the \a color of the text that will be used if the text element is selected (\ref setSelected).
-
- \see setTextColor
- */
- void QCPTextElement::setSelectedTextColor(const QColor &color)
- {
- mSelectedTextColor = color;
- }
- /*!
- Sets whether the user may select this text element.
- Note that even when \a selectable is set to <tt>false</tt>, the selection state may be changed
- programmatically via \ref setSelected.
- */
- void QCPTextElement::setSelectable(bool selectable)
- {
- if (mSelectable != selectable)
- {
- mSelectable = selectable;
- emit selectableChanged(mSelectable);
- }
- }
- /*!
- Sets the selection state of this text element to \a selected. If the selection has changed, \ref
- selectionChanged is emitted.
-
- Note that this function can change the selection state independently of the current \ref
- setSelectable state.
- */
- void QCPTextElement::setSelected(bool selected)
- {
- if (mSelected != selected)
- {
- mSelected = selected;
- emit selectionChanged(mSelected);
- }
- }
- /* inherits documentation from base class */
- void QCPTextElement::applyDefaultAntialiasingHint(QCPPainter *painter) const
- {
- applyAntialiasingHint(painter, mAntialiased, QCP::aeOther);
- }
- /* inherits documentation from base class */
- void QCPTextElement::draw(QCPPainter *painter)
- {
- painter->setFont(mainFont());
- painter->setPen(QPen(mainTextColor()));
- painter->drawText(mRect, Qt::AlignCenter, mText, &mTextBoundingRect);
- }
- /* inherits documentation from base class */
- QSize QCPTextElement::minimumOuterSizeHint() const
- {
- QFontMetrics metrics(mFont);
- QSize result(metrics.boundingRect(0, 0, 0, 0, Qt::AlignCenter, mText).size());
- result.rwidth() += mMargins.left()+mMargins.right();
- result.rheight() += mMargins.top()+mMargins.bottom();
- return result;
- }
- /* inherits documentation from base class */
- QSize QCPTextElement::maximumOuterSizeHint() const
- {
- QFontMetrics metrics(mFont);
- QSize result(metrics.boundingRect(0, 0, 0, 0, Qt::AlignCenter, mText).size());
- result.setWidth(QWIDGETSIZE_MAX);
- result.rheight() += mMargins.top()+mMargins.bottom();
- return result;
- }
- /* inherits documentation from base class */
- void QCPTextElement::selectEvent(QMouseEvent *event, bool additive, const QVariant &details, bool *selectionStateChanged)
- {
- Q_UNUSED(event)
- Q_UNUSED(details)
- if (mSelectable)
- {
- bool selBefore = mSelected;
- setSelected(additive ? !mSelected : true);
- if (selectionStateChanged)
- *selectionStateChanged = mSelected != selBefore;
- }
- }
- /* inherits documentation from base class */
- void QCPTextElement::deselectEvent(bool *selectionStateChanged)
- {
- if (mSelectable)
- {
- bool selBefore = mSelected;
- setSelected(false);
- if (selectionStateChanged)
- *selectionStateChanged = mSelected != selBefore;
- }
- }
- /*!
- Returns 0.99*selectionTolerance (see \ref QCustomPlot::setSelectionTolerance) when \a pos is
- within the bounding box of the text element's text. Note that this bounding box is updated in the
- draw call.
- If \a pos is outside the text's bounding box or if \a onlySelectable is true and this text
- element is not selectable (\ref setSelectable), returns -1.
- \seebaseclassmethod
- */
- double QCPTextElement::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- Q_UNUSED(details)
- if (onlySelectable && !mSelectable)
- return -1;
-
- if (mTextBoundingRect.contains(pos.toPoint()))
- return mParentPlot->selectionTolerance()*0.99;
- else
- return -1;
- }
- /*!
- Accepts the mouse event in order to emit the according click signal in the \ref
- mouseReleaseEvent.
- \seebaseclassmethod
- */
- void QCPTextElement::mousePressEvent(QMouseEvent *event, const QVariant &details)
- {
- Q_UNUSED(details)
- event->accept();
- }
- /*!
- Emits the \ref clicked signal if the cursor hasn't moved by more than a few pixels since the \ref
- mousePressEvent.
- \seebaseclassmethod
- */
- void QCPTextElement::mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos)
- {
- if ((QPointF(event->pos())-startPos).manhattanLength() <= 3)
- emit clicked(event);
- }
- /*!
- Emits the \ref doubleClicked signal.
- \seebaseclassmethod
- */
- void QCPTextElement::mouseDoubleClickEvent(QMouseEvent *event, const QVariant &details)
- {
- Q_UNUSED(details)
- emit doubleClicked(event);
- }
- /*! \internal
-
- Returns the main font to be used. This is mSelectedFont if \ref setSelected is set to
- <tt>true</tt>, else mFont is returned.
- */
- QFont QCPTextElement::mainFont() const
- {
- return mSelected ? mSelectedFont : mFont;
- }
- /*! \internal
-
- Returns the main color to be used. This is mSelectedTextColor if \ref setSelected is set to
- <tt>true</tt>, else mTextColor is returned.
- */
- QColor QCPTextElement::mainTextColor() const
- {
- return mSelected ? mSelectedTextColor : mTextColor;
- }
- /* end of 'src/layoutelements/layoutelement-textelement.cpp' */
- /* including file 'src/layoutelements/layoutelement-colorscale.cpp', size 25770 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPColorScale
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPColorScale
- \brief A color scale for use with color coding data such as QCPColorMap
-
- This layout element can be placed on the plot to correlate a color gradient with data values. It
- is usually used in combination with one or multiple \ref QCPColorMap "QCPColorMaps".
- \image html QCPColorScale.png
-
- The color scale can be either horizontal or vertical, as shown in the image above. The
- orientation and the side where the numbers appear is controlled with \ref setType.
-
- Use \ref QCPColorMap::setColorScale to connect a color map with a color scale. Once they are
- connected, they share their gradient, data range and data scale type (\ref setGradient, \ref
- setDataRange, \ref setDataScaleType). Multiple color maps may be associated with a single color
- scale, to make them all synchronize these properties.
-
- To have finer control over the number display and axis behaviour, you can directly access the
- \ref axis. See the documentation of QCPAxis for details about configuring axes. For example, if
- you want to change the number of automatically generated ticks, call
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolorscale-tickcount
-
- Placing a color scale next to the main axis rect works like with any other layout element:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolorscale-creation
- In this case we have placed it to the right of the default axis rect, so it wasn't necessary to
- call \ref setType, since \ref QCPAxis::atRight is already the default. The text next to the color
- scale can be set with \ref setLabel.
-
- For optimum appearance (like in the image above), it may be desirable to line up the axis rect and
- the borders of the color scale. Use a \ref QCPMarginGroup to achieve this:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolorscale-margingroup
-
- Color scales are initialized with a non-zero minimum top and bottom margin (\ref
- setMinimumMargins), because vertical color scales are most common and the minimum top/bottom
- margin makes sure it keeps some distance to the top/bottom widget border. So if you change to a
- horizontal color scale by setting \ref setType to \ref QCPAxis::atBottom or \ref QCPAxis::atTop, you
- might want to also change the minimum margins accordingly, e.g. <tt>setMinimumMargins(QMargins(6, 0, 6, 0))</tt>.
- */
- /* start documentation of inline functions */
- /*! \fn QCPAxis *QCPColorScale::axis() const
-
- Returns the internal \ref QCPAxis instance of this color scale. You can access it to alter the
- appearance and behaviour of the axis. \ref QCPColorScale duplicates some properties in its
- interface for convenience. Those are \ref setDataRange (\ref QCPAxis::setRange), \ref
- setDataScaleType (\ref QCPAxis::setScaleType), and the method \ref setLabel (\ref
- QCPAxis::setLabel). As they each are connected, it does not matter whether you use the method on
- the QCPColorScale or on its QCPAxis.
-
- If the type of the color scale is changed with \ref setType, the axis returned by this method
- will change, too, to either the left, right, bottom or top axis, depending on which type was set.
- */
- /* end documentation of signals */
- /* start documentation of signals */
- /*! \fn void QCPColorScale::dataRangeChanged(const QCPRange &newRange);
-
- This signal is emitted when the data range changes.
-
- \see setDataRange
- */
- /*! \fn void QCPColorScale::dataScaleTypeChanged(QCPAxis::ScaleType scaleType);
-
- This signal is emitted when the data scale type changes.
-
- \see setDataScaleType
- */
- /*! \fn void QCPColorScale::gradientChanged(const QCPColorGradient &newGradient);
-
- This signal is emitted when the gradient changes.
-
- \see setGradient
- */
- /* end documentation of signals */
- /*!
- Constructs a new QCPColorScale.
- */
- QCPColorScale::QCPColorScale(QCustomPlot *parentPlot) :
- QCPLayoutElement(parentPlot),
- mType(QCPAxis::atTop), // set to atTop such that setType(QCPAxis::atRight) below doesn't skip work because it thinks it's already atRight
- mDataScaleType(QCPAxis::stLinear),
- mBarWidth(20),
- mAxisRect(new QCPColorScaleAxisRectPrivate(this))
- {
- setMinimumMargins(QMargins(0, 6, 0, 6)); // for default right color scale types, keep some room at bottom and top (important if no margin group is used)
- setType(QCPAxis::atRight);
- setDataRange(QCPRange(0, 6));
- }
- QCPColorScale::~QCPColorScale()
- {
- delete mAxisRect;
- }
- /* undocumented getter */
- QString QCPColorScale::label() const
- {
- if (!mColorAxis)
- {
- qDebug() << Q_FUNC_INFO << "internal color axis undefined";
- return QString();
- }
-
- return mColorAxis.data()->label();
- }
- /* undocumented getter */
- bool QCPColorScale::rangeDrag() const
- {
- if (!mAxisRect)
- {
- qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
- return false;
- }
-
- return mAxisRect.data()->rangeDrag().testFlag(QCPAxis::orientation(mType)) &&
- mAxisRect.data()->rangeDragAxis(QCPAxis::orientation(mType)) &&
- mAxisRect.data()->rangeDragAxis(QCPAxis::orientation(mType))->orientation() == QCPAxis::orientation(mType);
- }
- /* undocumented getter */
- bool QCPColorScale::rangeZoom() const
- {
- if (!mAxisRect)
- {
- qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
- return false;
- }
-
- return mAxisRect.data()->rangeZoom().testFlag(QCPAxis::orientation(mType)) &&
- mAxisRect.data()->rangeZoomAxis(QCPAxis::orientation(mType)) &&
- mAxisRect.data()->rangeZoomAxis(QCPAxis::orientation(mType))->orientation() == QCPAxis::orientation(mType);
- }
- /*!
- Sets at which side of the color scale the axis is placed, and thus also its orientation.
-
- Note that after setting \a type to a different value, the axis returned by \ref axis() will
- be a different one. The new axis will adopt the following properties from the previous axis: The
- range, scale type, label and ticker (the latter will be shared and not copied).
- */
- void QCPColorScale::setType(QCPAxis::AxisType type)
- {
- if (!mAxisRect)
- {
- qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
- return;
- }
- if (mType != type)
- {
- mType = type;
- QCPRange rangeTransfer(0, 6);
- QString labelTransfer;
- QSharedPointer<QCPAxisTicker> tickerTransfer;
- // transfer/revert some settings on old axis if it exists:
- bool doTransfer = (bool)mColorAxis;
- if (doTransfer)
- {
- rangeTransfer = mColorAxis.data()->range();
- labelTransfer = mColorAxis.data()->label();
- tickerTransfer = mColorAxis.data()->ticker();
- mColorAxis.data()->setLabel(QString());
- disconnect(mColorAxis.data(), SIGNAL(rangeChanged(QCPRange)), this, SLOT(setDataRange(QCPRange)));
- disconnect(mColorAxis.data(), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), this, SLOT(setDataScaleType(QCPAxis::ScaleType)));
- }
- QList<QCPAxis::AxisType> allAxisTypes = QList<QCPAxis::AxisType>() << QCPAxis::atLeft << QCPAxis::atRight << QCPAxis::atBottom << QCPAxis::atTop;
- foreach (QCPAxis::AxisType atype, allAxisTypes)
- {
- mAxisRect.data()->axis(atype)->setTicks(atype == mType);
- mAxisRect.data()->axis(atype)->setTickLabels(atype== mType);
- }
- // set new mColorAxis pointer:
- mColorAxis = mAxisRect.data()->axis(mType);
- // transfer settings to new axis:
- if (doTransfer)
- {
- mColorAxis.data()->setRange(rangeTransfer); // range transfer necessary if axis changes from vertical to horizontal or vice versa (axes with same orientation are synchronized via signals)
- mColorAxis.data()->setLabel(labelTransfer);
- mColorAxis.data()->setTicker(tickerTransfer);
- }
- connect(mColorAxis.data(), SIGNAL(rangeChanged(QCPRange)), this, SLOT(setDataRange(QCPRange)));
- connect(mColorAxis.data(), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), this, SLOT(setDataScaleType(QCPAxis::ScaleType)));
- mAxisRect.data()->setRangeDragAxes(QList<QCPAxis*>() << mColorAxis.data());
- }
- }
- /*!
- Sets the range spanned by the color gradient and that is shown by the axis in the color scale.
-
- It is equivalent to calling QCPColorMap::setDataRange on any of the connected color maps. It is
- also equivalent to directly accessing the \ref axis and setting its range with \ref
- QCPAxis::setRange.
-
- \see setDataScaleType, setGradient, rescaleDataRange
- */
- void QCPColorScale::setDataRange(const QCPRange &dataRange)
- {
- if (mDataRange.lower != dataRange.lower || mDataRange.upper != dataRange.upper)
- {
- mDataRange = dataRange;
- if (mColorAxis)
- mColorAxis.data()->setRange(mDataRange);
- emit dataRangeChanged(mDataRange);
- }
- }
- /*!
- Sets the scale type of the color scale, i.e. whether values are linearly associated with colors
- or logarithmically.
-
- It is equivalent to calling QCPColorMap::setDataScaleType on any of the connected color maps. It is
- also equivalent to directly accessing the \ref axis and setting its scale type with \ref
- QCPAxis::setScaleType.
-
- \see setDataRange, setGradient
- */
- void QCPColorScale::setDataScaleType(QCPAxis::ScaleType scaleType)
- {
- if (mDataScaleType != scaleType)
- {
- mDataScaleType = scaleType;
- if (mColorAxis)
- mColorAxis.data()->setScaleType(mDataScaleType);
- if (mDataScaleType == QCPAxis::stLogarithmic)
- setDataRange(mDataRange.sanitizedForLogScale());
- emit dataScaleTypeChanged(mDataScaleType);
- }
- }
- /*!
- Sets the color gradient that will be used to represent data values.
-
- It is equivalent to calling QCPColorMap::setGradient on any of the connected color maps.
-
- \see setDataRange, setDataScaleType
- */
- void QCPColorScale::setGradient(const QCPColorGradient &gradient)
- {
- if (mGradient != gradient)
- {
- mGradient = gradient;
- if (mAxisRect)
- mAxisRect.data()->mGradientImageInvalidated = true;
- emit gradientChanged(mGradient);
- }
- }
- /*!
- Sets the axis label of the color scale. This is equivalent to calling \ref QCPAxis::setLabel on
- the internal \ref axis.
- */
- void QCPColorScale::setLabel(const QString &str)
- {
- if (!mColorAxis)
- {
- qDebug() << Q_FUNC_INFO << "internal color axis undefined";
- return;
- }
-
- mColorAxis.data()->setLabel(str);
- }
- /*!
- Sets the width (or height, for horizontal color scales) the bar where the gradient is displayed
- will have.
- */
- void QCPColorScale::setBarWidth(int width)
- {
- mBarWidth = width;
- }
- /*!
- Sets whether the user can drag the data range (\ref setDataRange).
-
- Note that \ref QCP::iRangeDrag must be in the QCustomPlot's interactions (\ref
- QCustomPlot::setInteractions) to allow range dragging.
- */
- void QCPColorScale::setRangeDrag(bool enabled)
- {
- if (!mAxisRect)
- {
- qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
- return;
- }
-
- if (enabled)
- mAxisRect.data()->setRangeDrag(QCPAxis::orientation(mType));
- else
- mAxisRect.data()->setRangeDrag(0);
- }
- /*!
- Sets whether the user can zoom the data range (\ref setDataRange) by scrolling the mouse wheel.
-
- Note that \ref QCP::iRangeZoom must be in the QCustomPlot's interactions (\ref
- QCustomPlot::setInteractions) to allow range dragging.
- */
- void QCPColorScale::setRangeZoom(bool enabled)
- {
- if (!mAxisRect)
- {
- qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
- return;
- }
-
- if (enabled)
- mAxisRect.data()->setRangeZoom(QCPAxis::orientation(mType));
- else
- mAxisRect.data()->setRangeZoom(0);
- }
- /*!
- Returns a list of all the color maps associated with this color scale.
- */
- QList<QCPColorMap*> QCPColorScale::colorMaps() const
- {
- QList<QCPColorMap*> result;
- for (int i=0; i<mParentPlot->plottableCount(); ++i)
- {
- if (QCPColorMap *cm = qobject_cast<QCPColorMap*>(mParentPlot->plottable(i)))
- if (cm->colorScale() == this)
- result.append(cm);
- }
- return result;
- }
- /*!
- Changes the data range such that all color maps associated with this color scale are fully mapped
- to the gradient in the data dimension.
-
- \see setDataRange
- */
- void QCPColorScale::rescaleDataRange(bool onlyVisibleMaps)
- {
- QList<QCPColorMap*> maps = colorMaps();
- QCPRange newRange;
- bool haveRange = false;
- QCP::SignDomain sign = QCP::sdBoth;
- if (mDataScaleType == QCPAxis::stLogarithmic)
- sign = (mDataRange.upper < 0 ? QCP::sdNegative : QCP::sdPositive);
- for (int i=0; i<maps.size(); ++i)
- {
- if (!maps.at(i)->realVisibility() && onlyVisibleMaps)
- continue;
- QCPRange mapRange;
- if (maps.at(i)->colorScale() == this)
- {
- bool currentFoundRange = true;
- mapRange = maps.at(i)->data()->dataBounds();
- if (sign == QCP::sdPositive)
- {
- if (mapRange.lower <= 0 && mapRange.upper > 0)
- mapRange.lower = mapRange.upper*1e-3;
- else if (mapRange.lower <= 0 && mapRange.upper <= 0)
- currentFoundRange = false;
- } else if (sign == QCP::sdNegative)
- {
- if (mapRange.upper >= 0 && mapRange.lower < 0)
- mapRange.upper = mapRange.lower*1e-3;
- else if (mapRange.upper >= 0 && mapRange.lower >= 0)
- currentFoundRange = false;
- }
- if (currentFoundRange)
- {
- if (!haveRange)
- newRange = mapRange;
- else
- newRange.expand(mapRange);
- haveRange = true;
- }
- }
- }
- if (haveRange)
- {
- if (!QCPRange::validRange(newRange)) // likely due to range being zero (plottable has only constant data in this dimension), shift current range to at least center the data
- {
- double center = (newRange.lower+newRange.upper)*0.5; // upper and lower should be equal anyway, but just to make sure, incase validRange returned false for other reason
- if (mDataScaleType == QCPAxis::stLinear)
- {
- newRange.lower = center-mDataRange.size()/2.0;
- newRange.upper = center+mDataRange.size()/2.0;
- } else // mScaleType == stLogarithmic
- {
- newRange.lower = center/qSqrt(mDataRange.upper/mDataRange.lower);
- newRange.upper = center*qSqrt(mDataRange.upper/mDataRange.lower);
- }
- }
- setDataRange(newRange);
- }
- }
- /* inherits documentation from base class */
- void QCPColorScale::update(UpdatePhase phase)
- {
- QCPLayoutElement::update(phase);
- if (!mAxisRect)
- {
- qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
- return;
- }
-
- mAxisRect.data()->update(phase);
-
- switch (phase)
- {
- case upMargins:
- {
- if (mType == QCPAxis::atBottom || mType == QCPAxis::atTop)
- {
- setMaximumSize(QWIDGETSIZE_MAX, mBarWidth+mAxisRect.data()->margins().top()+mAxisRect.data()->margins().bottom());
- setMinimumSize(0, mBarWidth+mAxisRect.data()->margins().top()+mAxisRect.data()->margins().bottom());
- } else
- {
- setMaximumSize(mBarWidth+mAxisRect.data()->margins().left()+mAxisRect.data()->margins().right(), QWIDGETSIZE_MAX);
- setMinimumSize(mBarWidth+mAxisRect.data()->margins().left()+mAxisRect.data()->margins().right(), 0);
- }
- break;
- }
- case upLayout:
- {
- mAxisRect.data()->setOuterRect(rect());
- break;
- }
- default: break;
- }
- }
- /* inherits documentation from base class */
- void QCPColorScale::applyDefaultAntialiasingHint(QCPPainter *painter) const
- {
- painter->setAntialiasing(false);
- }
- /* inherits documentation from base class */
- void QCPColorScale::mousePressEvent(QMouseEvent *event, const QVariant &details)
- {
- if (!mAxisRect)
- {
- qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
- return;
- }
- mAxisRect.data()->mousePressEvent(event, details);
- }
- /* inherits documentation from base class */
- void QCPColorScale::mouseMoveEvent(QMouseEvent *event, const QPointF &startPos)
- {
- if (!mAxisRect)
- {
- qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
- return;
- }
- mAxisRect.data()->mouseMoveEvent(event, startPos);
- }
- /* inherits documentation from base class */
- void QCPColorScale::mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos)
- {
- if (!mAxisRect)
- {
- qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
- return;
- }
- mAxisRect.data()->mouseReleaseEvent(event, startPos);
- }
- /* inherits documentation from base class */
- void QCPColorScale::wheelEvent(QWheelEvent *event)
- {
- if (!mAxisRect)
- {
- qDebug() << Q_FUNC_INFO << "internal axis rect was deleted";
- return;
- }
- mAxisRect.data()->wheelEvent(event);
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPColorScaleAxisRectPrivate
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPColorScaleAxisRectPrivate
- \internal
- \brief An axis rect subclass for use in a QCPColorScale
-
- This is a private class and not part of the public QCustomPlot interface.
-
- It provides the axis rect functionality for the QCPColorScale class.
- */
- /*!
- Creates a new instance, as a child of \a parentColorScale.
- */
- QCPColorScaleAxisRectPrivate::QCPColorScaleAxisRectPrivate(QCPColorScale *parentColorScale) :
- QCPAxisRect(parentColorScale->parentPlot(), true),
- mParentColorScale(parentColorScale),
- mGradientImageInvalidated(true)
- {
- setParentLayerable(parentColorScale);
- setMinimumMargins(QMargins(0, 0, 0, 0));
- QList<QCPAxis::AxisType> allAxisTypes = QList<QCPAxis::AxisType>() << QCPAxis::atBottom << QCPAxis::atTop << QCPAxis::atLeft << QCPAxis::atRight;
- foreach (QCPAxis::AxisType type, allAxisTypes)
- {
- axis(type)->setVisible(true);
- axis(type)->grid()->setVisible(false);
- axis(type)->setPadding(0);
- connect(axis(type), SIGNAL(selectionChanged(QCPAxis::SelectableParts)), this, SLOT(axisSelectionChanged(QCPAxis::SelectableParts)));
- connect(axis(type), SIGNAL(selectableChanged(QCPAxis::SelectableParts)), this, SLOT(axisSelectableChanged(QCPAxis::SelectableParts)));
- }
- connect(axis(QCPAxis::atLeft), SIGNAL(rangeChanged(QCPRange)), axis(QCPAxis::atRight), SLOT(setRange(QCPRange)));
- connect(axis(QCPAxis::atRight), SIGNAL(rangeChanged(QCPRange)), axis(QCPAxis::atLeft), SLOT(setRange(QCPRange)));
- connect(axis(QCPAxis::atBottom), SIGNAL(rangeChanged(QCPRange)), axis(QCPAxis::atTop), SLOT(setRange(QCPRange)));
- connect(axis(QCPAxis::atTop), SIGNAL(rangeChanged(QCPRange)), axis(QCPAxis::atBottom), SLOT(setRange(QCPRange)));
- connect(axis(QCPAxis::atLeft), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), axis(QCPAxis::atRight), SLOT(setScaleType(QCPAxis::ScaleType)));
- connect(axis(QCPAxis::atRight), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), axis(QCPAxis::atLeft), SLOT(setScaleType(QCPAxis::ScaleType)));
- connect(axis(QCPAxis::atBottom), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), axis(QCPAxis::atTop), SLOT(setScaleType(QCPAxis::ScaleType)));
- connect(axis(QCPAxis::atTop), SIGNAL(scaleTypeChanged(QCPAxis::ScaleType)), axis(QCPAxis::atBottom), SLOT(setScaleType(QCPAxis::ScaleType)));
-
- // make layer transfers of color scale transfer to axis rect and axes
- // the axes must be set after axis rect, such that they appear above color gradient drawn by axis rect:
- connect(parentColorScale, SIGNAL(layerChanged(QCPLayer*)), this, SLOT(setLayer(QCPLayer*)));
- foreach (QCPAxis::AxisType type, allAxisTypes)
- connect(parentColorScale, SIGNAL(layerChanged(QCPLayer*)), axis(type), SLOT(setLayer(QCPLayer*)));
- }
- /*! \internal
-
- Updates the color gradient image if necessary, by calling \ref updateGradientImage, then draws
- it. Then the axes are drawn by calling the \ref QCPAxisRect::draw base class implementation.
-
- \seebaseclassmethod
- */
- void QCPColorScaleAxisRectPrivate::draw(QCPPainter *painter)
- {
- if (mGradientImageInvalidated)
- updateGradientImage();
-
- bool mirrorHorz = false;
- bool mirrorVert = false;
- if (mParentColorScale->mColorAxis)
- {
- mirrorHorz = mParentColorScale->mColorAxis.data()->rangeReversed() && (mParentColorScale->type() == QCPAxis::atBottom || mParentColorScale->type() == QCPAxis::atTop);
- mirrorVert = mParentColorScale->mColorAxis.data()->rangeReversed() && (mParentColorScale->type() == QCPAxis::atLeft || mParentColorScale->type() == QCPAxis::atRight);
- }
-
- painter->drawImage(rect().adjusted(0, -1, 0, -1), mGradientImage.mirrored(mirrorHorz, mirrorVert));
- QCPAxisRect::draw(painter);
- }
- /*! \internal
- Uses the current gradient of the parent \ref QCPColorScale (specified in the constructor) to
- generate a gradient image. This gradient image will be used in the \ref draw method.
- */
- void QCPColorScaleAxisRectPrivate::updateGradientImage()
- {
- if (rect().isEmpty())
- return;
-
- const QImage::Format format = QImage::Format_ARGB32_Premultiplied;
- int n = mParentColorScale->mGradient.levelCount();
- int w, h;
- QVector<double> data(n);
- for (int i=0; i<n; ++i)
- data[i] = i;
- if (mParentColorScale->mType == QCPAxis::atBottom || mParentColorScale->mType == QCPAxis::atTop)
- {
- w = n;
- h = rect().height();
- mGradientImage = QImage(w, h, format);
- QVector<QRgb*> pixels;
- for (int y=0; y<h; ++y)
- pixels.append(reinterpret_cast<QRgb*>(mGradientImage.scanLine(y)));
- mParentColorScale->mGradient.colorize(data.constData(), QCPRange(0, n-1), pixels.first(), n);
- for (int y=1; y<h; ++y)
- memcpy(pixels.at(y), pixels.first(), n*sizeof(QRgb));
- } else
- {
- w = rect().width();
- h = n;
- mGradientImage = QImage(w, h, format);
- for (int y=0; y<h; ++y)
- {
- QRgb *pixels = reinterpret_cast<QRgb*>(mGradientImage.scanLine(y));
- const QRgb lineColor = mParentColorScale->mGradient.color(data[h-1-y], QCPRange(0, n-1));
- for (int x=0; x<w; ++x)
- pixels[x] = lineColor;
- }
- }
- mGradientImageInvalidated = false;
- }
- /*! \internal
- This slot is connected to the selectionChanged signals of the four axes in the constructor. It
- synchronizes the selection state of the axes.
- */
- void QCPColorScaleAxisRectPrivate::axisSelectionChanged(QCPAxis::SelectableParts selectedParts)
- {
- // axis bases of four axes shall always (de-)selected synchronously:
- QList<QCPAxis::AxisType> allAxisTypes = QList<QCPAxis::AxisType>() << QCPAxis::atBottom << QCPAxis::atTop << QCPAxis::atLeft << QCPAxis::atRight;
- foreach (QCPAxis::AxisType type, allAxisTypes)
- {
- if (QCPAxis *senderAxis = qobject_cast<QCPAxis*>(sender()))
- if (senderAxis->axisType() == type)
- continue;
-
- if (axis(type)->selectableParts().testFlag(QCPAxis::spAxis))
- {
- if (selectedParts.testFlag(QCPAxis::spAxis))
- axis(type)->setSelectedParts(axis(type)->selectedParts() | QCPAxis::spAxis);
- else
- axis(type)->setSelectedParts(axis(type)->selectedParts() & ~QCPAxis::spAxis);
- }
- }
- }
- /*! \internal
- This slot is connected to the selectableChanged signals of the four axes in the constructor. It
- synchronizes the selectability of the axes.
- */
- void QCPColorScaleAxisRectPrivate::axisSelectableChanged(QCPAxis::SelectableParts selectableParts)
- {
- // synchronize axis base selectability:
- QList<QCPAxis::AxisType> allAxisTypes = QList<QCPAxis::AxisType>() << QCPAxis::atBottom << QCPAxis::atTop << QCPAxis::atLeft << QCPAxis::atRight;
- foreach (QCPAxis::AxisType type, allAxisTypes)
- {
- if (QCPAxis *senderAxis = qobject_cast<QCPAxis*>(sender()))
- if (senderAxis->axisType() == type)
- continue;
-
- if (axis(type)->selectableParts().testFlag(QCPAxis::spAxis))
- {
- if (selectableParts.testFlag(QCPAxis::spAxis))
- axis(type)->setSelectableParts(axis(type)->selectableParts() | QCPAxis::spAxis);
- else
- axis(type)->setSelectableParts(axis(type)->selectableParts() & ~QCPAxis::spAxis);
- }
- }
- }
- /* end of 'src/layoutelements/layoutelement-colorscale.cpp' */
- /* including file 'src/plottables/plottable-graph.cpp', size 73960 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPGraphData
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPGraphData
- \brief Holds the data of one single data point for QCPGraph.
-
- The stored data is:
- \li \a key: coordinate on the key axis of this data point (this is the \a mainKey and the \a sortKey)
- \li \a value: coordinate on the value axis of this data point (this is the \a mainValue)
-
- The container for storing multiple data points is \ref QCPGraphDataContainer. It is a typedef for
- \ref QCPDataContainer with \ref QCPGraphData as the DataType template parameter. See the
- documentation there for an explanation regarding the data type's generic methods.
-
- \see QCPGraphDataContainer
- */
- /* start documentation of inline functions */
- /*! \fn double QCPGraphData::sortKey() const
-
- Returns the \a key member of this data point.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn static QCPGraphData QCPGraphData::fromSortKey(double sortKey)
-
- Returns a data point with the specified \a sortKey. All other members are set to zero.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn static static bool QCPGraphData::sortKeyIsMainKey()
-
- Since the member \a key is both the data point key coordinate and the data ordering parameter,
- this method returns true.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn double QCPGraphData::mainKey() const
-
- Returns the \a key member of this data point.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn double QCPGraphData::mainValue() const
-
- Returns the \a value member of this data point.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn QCPRange QCPGraphData::valueRange() const
-
- Returns a QCPRange with both lower and upper boundary set to \a value of this data point.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /* end documentation of inline functions */
- /*!
- Constructs a data point with key and value set to zero.
- */
- QCPGraphData::QCPGraphData() :
- key(0),
- value(0)
- {
- }
- /*!
- Constructs a data point with the specified \a key and \a value.
- */
- QCPGraphData::QCPGraphData(double key, double value) :
- key(key),
- value(value)
- {
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPGraph
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPGraph
- \brief A plottable representing a graph in a plot.
- \image html QCPGraph.png
-
- Usually you create new graphs by calling QCustomPlot::addGraph. The resulting instance can be
- accessed via QCustomPlot::graph.
- To plot data, assign it with the \ref setData or \ref addData functions. Alternatively, you can
- also access and modify the data via the \ref data method, which returns a pointer to the internal
- \ref QCPGraphDataContainer.
-
- Graphs are used to display single-valued data. Single-valued means that there should only be one
- data point per unique key coordinate. In other words, the graph can't have \a loops. If you do
- want to plot non-single-valued curves, rather use the QCPCurve plottable.
-
- Gaps in the graph line can be created by adding data points with NaN as value
- (<tt>qQNaN()</tt> or <tt>std::numeric_limits<double>::quiet_NaN()</tt>) in between the two data points that shall be
- separated.
-
- \section qcpgraph-appearance Changing the appearance
-
- The appearance of the graph is mainly determined by the line style, scatter style, brush and pen
- of the graph (\ref setLineStyle, \ref setScatterStyle, \ref setBrush, \ref setPen).
-
- \subsection filling Filling under or between graphs
-
- QCPGraph knows two types of fills: Normal graph fills towards the zero-value-line parallel to
- the key axis of the graph, and fills between two graphs, called channel fills. To enable a fill,
- just set a brush with \ref setBrush which is neither Qt::NoBrush nor fully transparent.
-
- By default, a normal fill towards the zero-value-line will be drawn. To set up a channel fill
- between this graph and another one, call \ref setChannelFillGraph with the other graph as
- parameter.
- \see QCustomPlot::addGraph, QCustomPlot::graph
- */
- /* start of documentation of inline functions */
- /*! \fn QSharedPointer<QCPGraphDataContainer> QCPGraph::data() const
-
- Returns a shared pointer to the internal data storage of type \ref QCPGraphDataContainer. You may
- use it to directly manipulate the data, which may be more convenient and faster than using the
- regular \ref setData or \ref addData methods.
- */
- /* end of documentation of inline functions */
- /*!
- Constructs a graph which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value
- axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have
- the same orientation. If either of these restrictions is violated, a corresponding message is
- printed to the debug output (qDebug), the construction is not aborted, though.
-
- The created QCPGraph is automatically registered with the QCustomPlot instance inferred from \a
- keyAxis. This QCustomPlot instance takes ownership of the QCPGraph, so do not delete it manually
- but use QCustomPlot::removePlottable() instead.
-
- To directly create a graph inside a plot, you can also use the simpler QCustomPlot::addGraph function.
- */
- QCPGraph::QCPGraph(QCPAxis *keyAxis, QCPAxis *valueAxis) :
- QCPAbstractPlottable1D<QCPGraphData>(keyAxis, valueAxis)
- {
- // special handling for QCPGraphs to maintain the simple graph interface:
- mParentPlot->registerGraph(this);
- setPen(QPen(Qt::blue, 0));
- setBrush(Qt::NoBrush);
-
- setLineStyle(lsLine);
- setScatterSkip(0);
- setChannelFillGraph(0);
- setAdaptiveSampling(true);
- }
- QCPGraph::~QCPGraph()
- {
- }
- /*! \overload
-
- Replaces the current data container with the provided \a data container.
-
- Since a QSharedPointer is used, multiple QCPGraphs may share the same data container safely.
- Modifying the data in the container will then affect all graphs that share the container. Sharing
- can be achieved by simply exchanging the data containers wrapped in shared pointers:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpgraph-datasharing-1
-
- If you do not wish to share containers, but create a copy from an existing container, rather use
- the \ref QCPDataContainer<DataType>::set method on the graph's data container directly:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpgraph-datasharing-2
-
- \see addData
- */
- void QCPGraph::setData(QSharedPointer<QCPGraphDataContainer> data)
- {
- mDataContainer = data;
- }
- /*! \overload
-
- Replaces the current data with the provided points in \a keys and \a values. The provided
- vectors should have equal length. Else, the number of added points will be the size of the
- smallest vector.
-
- If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
- can set \a alreadySorted to true, to improve performance by saving a sorting run.
-
- \see addData
- */
- void QCPGraph::setData(const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
- {
- mDataContainer->clear();
- addData(keys, values, alreadySorted);
- }
- /*!
- Sets how the single data points are connected in the plot. For scatter-only plots, set \a ls to
- \ref lsNone and \ref setScatterStyle to the desired scatter style.
-
- \see setScatterStyle
- */
- void QCPGraph::setLineStyle(LineStyle ls)
- {
- mLineStyle = ls;
- }
- /*!
- Sets the visual appearance of single data points in the plot. If set to \ref QCPScatterStyle::ssNone, no scatter points
- are drawn (e.g. for line-only-plots with appropriate line style).
-
- \see QCPScatterStyle, setLineStyle
- */
- void QCPGraph::setScatterStyle(const QCPScatterStyle &style)
- {
- mScatterStyle = style;
- }
- /*!
- If scatters are displayed (scatter style not \ref QCPScatterStyle::ssNone), \a skip number of
- scatter points are skipped/not drawn after every drawn scatter point.
- This can be used to make the data appear sparser while for example still having a smooth line,
- and to improve performance for very high density plots.
- If \a skip is set to 0 (default), all scatter points are drawn.
- \see setScatterStyle
- */
- void QCPGraph::setScatterSkip(int skip)
- {
- mScatterSkip = qMax(0, skip);
- }
- /*!
- Sets the target graph for filling the area between this graph and \a targetGraph with the current
- brush (\ref setBrush).
-
- When \a targetGraph is set to 0, a normal graph fill to the zero-value-line will be shown. To
- disable any filling, set the brush to Qt::NoBrush.
- \see setBrush
- */
- void QCPGraph::setChannelFillGraph(QCPGraph *targetGraph)
- {
- // prevent setting channel target to this graph itself:
- if (targetGraph == this)
- {
- qDebug() << Q_FUNC_INFO << "targetGraph is this graph itself";
- mChannelFillGraph = 0;
- return;
- }
- // prevent setting channel target to a graph not in the plot:
- if (targetGraph && targetGraph->mParentPlot != mParentPlot)
- {
- qDebug() << Q_FUNC_INFO << "targetGraph not in same plot";
- mChannelFillGraph = 0;
- return;
- }
-
- mChannelFillGraph = targetGraph;
- }
- /*!
- Sets whether adaptive sampling shall be used when plotting this graph. QCustomPlot's adaptive
- sampling technique can drastically improve the replot performance for graphs with a larger number
- of points (e.g. above 10,000), without notably changing the appearance of the graph.
-
- By default, adaptive sampling is enabled. Even if enabled, QCustomPlot decides whether adaptive
- sampling shall actually be used on a per-graph basis. So leaving adaptive sampling enabled has no
- disadvantage in almost all cases.
-
- \image html adaptive-sampling-line.png "A line plot of 500,000 points without and with adaptive sampling"
-
- As can be seen, line plots experience no visual degradation from adaptive sampling. Outliers are
- reproduced reliably, as well as the overall shape of the data set. The replot time reduces
- dramatically though. This allows QCustomPlot to display large amounts of data in realtime.
-
- \image html adaptive-sampling-scatter.png "A scatter plot of 100,000 points without and with adaptive sampling"
-
- Care must be taken when using high-density scatter plots in combination with adaptive sampling.
- The adaptive sampling algorithm treats scatter plots more carefully than line plots which still
- gives a significant reduction of replot times, but not quite as much as for line plots. This is
- because scatter plots inherently need more data points to be preserved in order to still resemble
- the original, non-adaptive-sampling plot. As shown above, the results still aren't quite
- identical, as banding occurs for the outer data points. This is in fact intentional, such that
- the boundaries of the data cloud stay visible to the viewer. How strong the banding appears,
- depends on the point density, i.e. the number of points in the plot.
-
- For some situations with scatter plots it might thus be desirable to manually turn adaptive
- sampling off. For example, when saving the plot to disk. This can be achieved by setting \a
- enabled to false before issuing a command like \ref QCustomPlot::savePng, and setting \a enabled
- back to true afterwards.
- */
- void QCPGraph::setAdaptiveSampling(bool enabled)
- {
- mAdaptiveSampling = enabled;
- }
- /*! \overload
-
- Adds the provided points in \a keys and \a values to the current data. The provided vectors
- should have equal length. Else, the number of added points will be the size of the smallest
- vector.
-
- If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
- can set \a alreadySorted to true, to improve performance by saving a sorting run.
-
- Alternatively, you can also access and modify the data directly via the \ref data method, which
- returns a pointer to the internal data container.
- */
- void QCPGraph::addData(const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
- {
- if (keys.size() != values.size())
- qDebug() << Q_FUNC_INFO << "keys and values have different sizes:" << keys.size() << values.size();
- const int n = qMin(keys.size(), values.size());
- QVector<QCPGraphData> tempData(n);
- QVector<QCPGraphData>::iterator it = tempData.begin();
- const QVector<QCPGraphData>::iterator itEnd = tempData.end();
- int i = 0;
- while (it != itEnd)
- {
- it->key = keys[i];
- it->value = values[i];
- ++it;
- ++i;
- }
- mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write
- }
- /*! \overload
-
- Adds the provided data point as \a key and \a value to the current data.
-
- Alternatively, you can also access and modify the data directly via the \ref data method, which
- returns a pointer to the internal data container.
- */
- void QCPGraph::addData(double key, double value)
- {
- mDataContainer->add(QCPGraphData(key, value));
- }
- /* inherits documentation from base class */
- double QCPGraph::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
- return -1;
- if (!mKeyAxis || !mValueAxis)
- return -1;
-
- if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()))
- {
- QCPGraphDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd();
- double result = pointDistance(pos, closestDataPoint);
- if (details)
- {
- int pointIndex = closestDataPoint-mDataContainer->constBegin();
- details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex+1)));
- }
- return result;
- } else
- return -1;
- }
- /* inherits documentation from base class */
- QCPRange QCPGraph::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
- {
- return mDataContainer->keyRange(foundRange, inSignDomain);
- }
- /* inherits documentation from base class */
- QCPRange QCPGraph::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
- {
- return mDataContainer->valueRange(foundRange, inSignDomain, inKeyRange);
- }
- /* inherits documentation from base class */
- void QCPGraph::draw(QCPPainter *painter)
- {
- if (!mKeyAxis || !mValueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
- if (mKeyAxis.data()->range().size() <= 0 || mDataContainer->isEmpty()) return;
- if (mLineStyle == lsNone && mScatterStyle.isNone()) return;
-
- QVector<QPointF> lines, scatters; // line and (if necessary) scatter pixel coordinates will be stored here while iterating over segments
-
- // loop over and draw segments of unselected/selected data:
- QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
- getDataSegments(selectedSegments, unselectedSegments);
- allSegments << unselectedSegments << selectedSegments;
- for (int i=0; i<allSegments.size(); ++i)
- {
- bool isSelectedSegment = i >= unselectedSegments.size();
- // get line pixel points appropriate to line style:
- QCPDataRange lineDataRange = isSelectedSegment ? allSegments.at(i) : allSegments.at(i).adjusted(-1, 1); // unselected segments extend lines to bordering selected data point (safe to exceed total data bounds in first/last segment, getLines takes care)
- getLines(&lines, lineDataRange);
-
- // check data validity if flag set:
- #ifdef QCUSTOMPLOT_CHECK_DATA
- QCPGraphDataContainer::const_iterator it;
- for (it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it)
- {
- if (QCP::isInvalidData(it->key, it->value))
- qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "invalid." << "Plottable name:" << name();
- }
- #endif
-
- // draw fill of graph:
- if (isSelectedSegment && mSelectionDecorator)
- mSelectionDecorator->applyBrush(painter);
- else
- painter->setBrush(mBrush);
- painter->setPen(Qt::NoPen);
- drawFill(painter, &lines);
-
- // draw line:
- if (mLineStyle != lsNone)
- {
- if (isSelectedSegment && mSelectionDecorator)
- mSelectionDecorator->applyPen(painter);
- else
- painter->setPen(mPen);
- painter->setBrush(Qt::NoBrush);
- if (mLineStyle == lsImpulse)
- drawImpulsePlot(painter, lines);
- else
- drawLinePlot(painter, lines); // also step plots can be drawn as a line plot
- }
-
- // draw scatters:
- QCPScatterStyle finalScatterStyle = mScatterStyle;
- if (isSelectedSegment && mSelectionDecorator)
- finalScatterStyle = mSelectionDecorator->getFinalScatterStyle(mScatterStyle);
- if (!finalScatterStyle.isNone())
- {
- getScatters(&scatters, allSegments.at(i));
- drawScatterPlot(painter, scatters, finalScatterStyle);
- }
- }
-
- // draw other selection decoration that isn't just line/scatter pens and brushes:
- if (mSelectionDecorator)
- mSelectionDecorator->drawDecoration(painter, selection());
- }
- /* inherits documentation from base class */
- void QCPGraph::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
- {
- // draw fill:
- if (mBrush.style() != Qt::NoBrush)
- {
- applyFillAntialiasingHint(painter);
- painter->fillRect(QRectF(rect.left(), rect.top()+rect.height()/2.0, rect.width(), rect.height()/3.0), mBrush);
- }
- // draw line vertically centered:
- if (mLineStyle != lsNone)
- {
- applyDefaultAntialiasingHint(painter);
- painter->setPen(mPen);
- painter->drawLine(QLineF(rect.left(), rect.top()+rect.height()/2.0, rect.right()+5, rect.top()+rect.height()/2.0)); // +5 on x2 else last segment is missing from dashed/dotted pens
- }
- // draw scatter symbol:
- if (!mScatterStyle.isNone())
- {
- applyScattersAntialiasingHint(painter);
- // scale scatter pixmap if it's too large to fit in legend icon rect:
- if (mScatterStyle.shape() == QCPScatterStyle::ssPixmap && (mScatterStyle.pixmap().size().width() > rect.width() || mScatterStyle.pixmap().size().height() > rect.height()))
- {
- QCPScatterStyle scaledStyle(mScatterStyle);
- scaledStyle.setPixmap(scaledStyle.pixmap().scaled(rect.size().toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
- scaledStyle.applyTo(painter, mPen);
- scaledStyle.drawShape(painter, QRectF(rect).center());
- } else
- {
- mScatterStyle.applyTo(painter, mPen);
- mScatterStyle.drawShape(painter, QRectF(rect).center());
- }
- }
- }
- /*! \internal
- This method retrieves an optimized set of data points via \ref getOptimizedLineData, an branches
- out to the line style specific functions such as \ref dataToLines, \ref dataToStepLeftLines, etc.
- according to the line style of the graph.
- \a lines will be filled with points in pixel coordinates, that can be drawn with the according
- draw functions like \ref drawLinePlot and \ref drawImpulsePlot. The points returned in \a lines
- aren't necessarily the original data points. For example, step line styles require additional
- points to form the steps when drawn. If the line style of the graph is \ref lsNone, the \a
- lines vector will be empty.
- \a dataRange specifies the beginning and ending data indices that will be taken into account for
- conversion. In this function, the specified range may exceed the total data bounds without harm:
- a correspondingly trimmed data range will be used. This takes the burden off the user of this
- function to check for valid indices in \a dataRange, e.g. when extending ranges coming from \ref
- getDataSegments.
- \see getScatters
- */
- void QCPGraph::getLines(QVector<QPointF> *lines, const QCPDataRange &dataRange) const
- {
- if (!lines) return;
- QCPGraphDataContainer::const_iterator begin, end;
- getVisibleDataBounds(begin, end, dataRange);
- if (begin == end)
- {
- lines->clear();
- return;
- }
-
- QVector<QCPGraphData> lineData;
- if (mLineStyle != lsNone)
- getOptimizedLineData(&lineData, begin, end);
-
- if (mKeyAxis->rangeReversed() != (mKeyAxis->orientation() == Qt::Vertical)) // make sure key pixels are sorted ascending in lineData (significantly simplifies following processing)
- std::reverse(lineData.begin(), lineData.end());
- switch (mLineStyle)
- {
- case lsNone: lines->clear(); break;
- case lsLine: *lines = dataToLines(lineData); break;
- case lsStepLeft: *lines = dataToStepLeftLines(lineData); break;
- case lsStepRight: *lines = dataToStepRightLines(lineData); break;
- case lsStepCenter: *lines = dataToStepCenterLines(lineData); break;
- case lsImpulse: *lines = dataToImpulseLines(lineData); break;
- }
- }
- /*! \internal
- This method retrieves an optimized set of data points via \ref getOptimizedScatterData and then
- converts them to pixel coordinates. The resulting points are returned in \a scatters, and can be
- passed to \ref drawScatterPlot.
- \a dataRange specifies the beginning and ending data indices that will be taken into account for
- conversion. In this function, the specified range may exceed the total data bounds without harm:
- a correspondingly trimmed data range will be used. This takes the burden off the user of this
- function to check for valid indices in \a dataRange, e.g. when extending ranges coming from \ref
- getDataSegments.
- */
- void QCPGraph::getScatters(QVector<QPointF> *scatters, const QCPDataRange &dataRange) const
- {
- if (!scatters) return;
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; scatters->clear(); return; }
-
- QCPGraphDataContainer::const_iterator begin, end;
- getVisibleDataBounds(begin, end, dataRange);
- if (begin == end)
- {
- scatters->clear();
- return;
- }
-
- QVector<QCPGraphData> data;
- getOptimizedScatterData(&data, begin, end);
-
- if (mKeyAxis->rangeReversed() != (mKeyAxis->orientation() == Qt::Vertical)) // make sure key pixels are sorted ascending in data (significantly simplifies following processing)
- std::reverse(data.begin(), data.end());
-
- scatters->resize(data.size());
- if (keyAxis->orientation() == Qt::Vertical)
- {
- for (int i=0; i<data.size(); ++i)
- {
- if (!qIsNaN(data.at(i).value))
- {
- (*scatters)[i].setX(valueAxis->coordToPixel(data.at(i).value));
- (*scatters)[i].setY(keyAxis->coordToPixel(data.at(i).key));
- }
- }
- } else
- {
- for (int i=0; i<data.size(); ++i)
- {
- if (!qIsNaN(data.at(i).value))
- {
- (*scatters)[i].setX(keyAxis->coordToPixel(data.at(i).key));
- (*scatters)[i].setY(valueAxis->coordToPixel(data.at(i).value));
- }
- }
- }
- }
- /*! \internal
- Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel
- coordinate points which are suitable for drawing the line style \ref lsLine.
-
- The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a
- getLines if the line style is set accordingly.
- \see dataToStepLeftLines, dataToStepRightLines, dataToStepCenterLines, dataToImpulseLines, getLines, drawLinePlot
- */
- QVector<QPointF> QCPGraph::dataToLines(const QVector<QCPGraphData> &data) const
- {
- QVector<QPointF> result;
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; }
- result.resize(data.size());
-
- // transform data points to pixels:
- if (keyAxis->orientation() == Qt::Vertical)
- {
- for (int i=0; i<data.size(); ++i)
- {
- result[i].setX(valueAxis->coordToPixel(data.at(i).value));
- result[i].setY(keyAxis->coordToPixel(data.at(i).key));
- }
- } else // key axis is horizontal
- {
- for (int i=0; i<data.size(); ++i)
- {
- result[i].setX(keyAxis->coordToPixel(data.at(i).key));
- result[i].setY(valueAxis->coordToPixel(data.at(i).value));
- }
- }
- return result;
- }
- /*! \internal
- Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel
- coordinate points which are suitable for drawing the line style \ref lsStepLeft.
-
- The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a
- getLines if the line style is set accordingly.
- \see dataToLines, dataToStepRightLines, dataToStepCenterLines, dataToImpulseLines, getLines, drawLinePlot
- */
- QVector<QPointF> QCPGraph::dataToStepLeftLines(const QVector<QCPGraphData> &data) const
- {
- QVector<QPointF> result;
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; }
-
- result.resize(data.size()*2);
-
- // calculate steps from data and transform to pixel coordinates:
- if (keyAxis->orientation() == Qt::Vertical)
- {
- double lastValue = valueAxis->coordToPixel(data.first().value);
- for (int i=0; i<data.size(); ++i)
- {
- const double key = keyAxis->coordToPixel(data.at(i).key);
- result[i*2+0].setX(lastValue);
- result[i*2+0].setY(key);
- lastValue = valueAxis->coordToPixel(data.at(i).value);
- result[i*2+1].setX(lastValue);
- result[i*2+1].setY(key);
- }
- } else // key axis is horizontal
- {
- double lastValue = valueAxis->coordToPixel(data.first().value);
- for (int i=0; i<data.size(); ++i)
- {
- const double key = keyAxis->coordToPixel(data.at(i).key);
- result[i*2+0].setX(key);
- result[i*2+0].setY(lastValue);
- lastValue = valueAxis->coordToPixel(data.at(i).value);
- result[i*2+1].setX(key);
- result[i*2+1].setY(lastValue);
- }
- }
- return result;
- }
- /*! \internal
- Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel
- coordinate points which are suitable for drawing the line style \ref lsStepRight.
-
- The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a
- getLines if the line style is set accordingly.
- \see dataToLines, dataToStepLeftLines, dataToStepCenterLines, dataToImpulseLines, getLines, drawLinePlot
- */
- QVector<QPointF> QCPGraph::dataToStepRightLines(const QVector<QCPGraphData> &data) const
- {
- QVector<QPointF> result;
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; }
-
- result.resize(data.size()*2);
-
- // calculate steps from data and transform to pixel coordinates:
- if (keyAxis->orientation() == Qt::Vertical)
- {
- double lastKey = keyAxis->coordToPixel(data.first().key);
- for (int i=0; i<data.size(); ++i)
- {
- const double value = valueAxis->coordToPixel(data.at(i).value);
- result[i*2+0].setX(value);
- result[i*2+0].setY(lastKey);
- lastKey = keyAxis->coordToPixel(data.at(i).key);
- result[i*2+1].setX(value);
- result[i*2+1].setY(lastKey);
- }
- } else // key axis is horizontal
- {
- double lastKey = keyAxis->coordToPixel(data.first().key);
- for (int i=0; i<data.size(); ++i)
- {
- const double value = valueAxis->coordToPixel(data.at(i).value);
- result[i*2+0].setX(lastKey);
- result[i*2+0].setY(value);
- lastKey = keyAxis->coordToPixel(data.at(i).key);
- result[i*2+1].setX(lastKey);
- result[i*2+1].setY(value);
- }
- }
- return result;
- }
- /*! \internal
- Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel
- coordinate points which are suitable for drawing the line style \ref lsStepCenter.
-
- The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a
- getLines if the line style is set accordingly.
- \see dataToLines, dataToStepLeftLines, dataToStepRightLines, dataToImpulseLines, getLines, drawLinePlot
- */
- QVector<QPointF> QCPGraph::dataToStepCenterLines(const QVector<QCPGraphData> &data) const
- {
- QVector<QPointF> result;
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; }
-
- result.resize(data.size()*2);
-
- // calculate steps from data and transform to pixel coordinates:
- if (keyAxis->orientation() == Qt::Vertical)
- {
- double lastKey = keyAxis->coordToPixel(data.first().key);
- double lastValue = valueAxis->coordToPixel(data.first().value);
- result[0].setX(lastValue);
- result[0].setY(lastKey);
- for (int i=1; i<data.size(); ++i)
- {
- const double key = (keyAxis->coordToPixel(data.at(i).key)+lastKey)*0.5;
- result[i*2-1].setX(lastValue);
- result[i*2-1].setY(key);
- lastValue = valueAxis->coordToPixel(data.at(i).value);
- lastKey = keyAxis->coordToPixel(data.at(i).key);
- result[i*2+0].setX(lastValue);
- result[i*2+0].setY(key);
- }
- result[data.size()*2-1].setX(lastValue);
- result[data.size()*2-1].setY(lastKey);
- } else // key axis is horizontal
- {
- double lastKey = keyAxis->coordToPixel(data.first().key);
- double lastValue = valueAxis->coordToPixel(data.first().value);
- result[0].setX(lastKey);
- result[0].setY(lastValue);
- for (int i=1; i<data.size(); ++i)
- {
- const double key = (keyAxis->coordToPixel(data.at(i).key)+lastKey)*0.5;
- result[i*2-1].setX(key);
- result[i*2-1].setY(lastValue);
- lastValue = valueAxis->coordToPixel(data.at(i).value);
- lastKey = keyAxis->coordToPixel(data.at(i).key);
- result[i*2+0].setX(key);
- result[i*2+0].setY(lastValue);
- }
- result[data.size()*2-1].setX(lastKey);
- result[data.size()*2-1].setY(lastValue);
- }
- return result;
- }
- /*! \internal
- Takes raw data points in plot coordinates as \a data, and returns a vector containing pixel
- coordinate points which are suitable for drawing the line style \ref lsImpulse.
-
- The source of \a data is usually \ref getOptimizedLineData, and this method is called in \a
- getLines if the line style is set accordingly.
- \see dataToLines, dataToStepLeftLines, dataToStepRightLines, dataToStepCenterLines, getLines, drawImpulsePlot
- */
- QVector<QPointF> QCPGraph::dataToImpulseLines(const QVector<QCPGraphData> &data) const
- {
- QVector<QPointF> result;
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return result; }
-
- result.resize(data.size()*2);
-
- // transform data points to pixels:
- if (keyAxis->orientation() == Qt::Vertical)
- {
- for (int i=0; i<data.size(); ++i)
- {
- const double key = keyAxis->coordToPixel(data.at(i).key);
- result[i*2+0].setX(valueAxis->coordToPixel(0));
- result[i*2+0].setY(key);
- result[i*2+1].setX(valueAxis->coordToPixel(data.at(i).value));
- result[i*2+1].setY(key);
- }
- } else // key axis is horizontal
- {
- for (int i=0; i<data.size(); ++i)
- {
- const double key = keyAxis->coordToPixel(data.at(i).key);
- result[i*2+0].setX(key);
- result[i*2+0].setY(valueAxis->coordToPixel(0));
- result[i*2+1].setX(key);
- result[i*2+1].setY(valueAxis->coordToPixel(data.at(i).value));
- }
- }
- return result;
- }
- /*! \internal
-
- Draws the fill of the graph using the specified \a painter, with the currently set brush.
-
- Depending on whether a normal fill or a channel fill (\ref setChannelFillGraph) is needed, \ref
- getFillPolygon or \ref getChannelFillPolygon are used to find the according fill polygons.
-
- In order to handle NaN Data points correctly (the fill needs to be split into disjoint areas),
- this method first determines a list of non-NaN segments with \ref getNonNanSegments, on which to
- operate. In the channel fill case, \ref getOverlappingSegments is used to consolidate the non-NaN
- segments of the two involved graphs, before passing the overlapping pairs to \ref
- getChannelFillPolygon.
-
- Pass the points of this graph's line as \a lines, in pixel coordinates.
- \see drawLinePlot, drawImpulsePlot, drawScatterPlot
- */
- void QCPGraph::drawFill(QCPPainter *painter, QVector<QPointF> *lines) const
- {
- if (mLineStyle == lsImpulse) return; // fill doesn't make sense for impulse plot
- if (painter->brush().style() == Qt::NoBrush || painter->brush().color().alpha() == 0) return;
-
- applyFillAntialiasingHint(painter);
- QVector<QCPDataRange> segments = getNonNanSegments(lines, keyAxis()->orientation());
- if (!mChannelFillGraph)
- {
- // draw base fill under graph, fill goes all the way to the zero-value-line:
- for (int i=0; i<segments.size(); ++i)
- painter->drawPolygon(getFillPolygon(lines, segments.at(i)));
- } else
- {
- // draw fill between this graph and mChannelFillGraph:
- QVector<QPointF> otherLines;
- mChannelFillGraph->getLines(&otherLines, QCPDataRange(0, mChannelFillGraph->dataCount()));
- if (!otherLines.isEmpty())
- {
- QVector<QCPDataRange> otherSegments = getNonNanSegments(&otherLines, mChannelFillGraph->keyAxis()->orientation());
- QVector<QPair<QCPDataRange, QCPDataRange> > segmentPairs = getOverlappingSegments(segments, lines, otherSegments, &otherLines);
- for (int i=0; i<segmentPairs.size(); ++i)
- painter->drawPolygon(getChannelFillPolygon(lines, segmentPairs.at(i).first, &otherLines, segmentPairs.at(i).second));
- }
- }
- }
- /*! \internal
- Draws scatter symbols at every point passed in \a scatters, given in pixel coordinates. The
- scatters will be drawn with \a painter and have the appearance as specified in \a style.
- \see drawLinePlot, drawImpulsePlot
- */
- void QCPGraph::drawScatterPlot(QCPPainter *painter, const QVector<QPointF> &scatters, const QCPScatterStyle &style) const
- {
- applyScattersAntialiasingHint(painter);
- style.applyTo(painter, mPen);
- for (int i=0; i<scatters.size(); ++i)
- style.drawShape(painter, scatters.at(i).x(), scatters.at(i).y());
- }
- /*! \internal
-
- Draws lines between the points in \a lines, given in pixel coordinates.
-
- \see drawScatterPlot, drawImpulsePlot, QCPAbstractPlottable1D::drawPolyline
- */
- void QCPGraph::drawLinePlot(QCPPainter *painter, const QVector<QPointF> &lines) const
- {
- if (painter->pen().style() != Qt::NoPen && painter->pen().color().alpha() != 0)
- {
- applyDefaultAntialiasingHint(painter);
- drawPolyline(painter, lines);
- }
- }
- /*! \internal
- Draws impulses from the provided data, i.e. it connects all line pairs in \a lines, given in
- pixel coordinates. The \a lines necessary for impulses are generated by \ref dataToImpulseLines
- from the regular graph data points.
- \see drawLinePlot, drawScatterPlot
- */
- void QCPGraph::drawImpulsePlot(QCPPainter *painter, const QVector<QPointF> &lines) const
- {
- if (painter->pen().style() != Qt::NoPen && painter->pen().color().alpha() != 0)
- {
- applyDefaultAntialiasingHint(painter);
- QPen oldPen = painter->pen();
- QPen newPen = painter->pen();
- newPen.setCapStyle(Qt::FlatCap); // so impulse line doesn't reach beyond zero-line
- painter->setPen(newPen);
- painter->drawLines(lines);
- painter->setPen(oldPen);
- }
- }
- /*! \internal
- Returns via \a lineData the data points that need to be visualized for this graph when plotting
- graph lines, taking into consideration the currently visible axis ranges and, if \ref
- setAdaptiveSampling is enabled, local point densities. The considered data can be restricted
- further by \a begin and \a end, e.g. to only plot a certain segment of the data (see \ref
- getDataSegments).
- This method is used by \ref getLines to retrieve the basic working set of data.
- \see getOptimizedScatterData
- */
- void QCPGraph::getOptimizedLineData(QVector<QCPGraphData> *lineData, const QCPGraphDataContainer::const_iterator &begin, const QCPGraphDataContainer::const_iterator &end) const
- {
- if (!lineData) return;
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
- if (begin == end) return;
-
- int dataCount = end-begin;
- int maxCount = std::numeric_limits<int>::max();
- if (mAdaptiveSampling)
- {
- double keyPixelSpan = qAbs(keyAxis->coordToPixel(begin->key)-keyAxis->coordToPixel((end-1)->key));
- if (2*keyPixelSpan+2 < (double)std::numeric_limits<int>::max())
- maxCount = 2*keyPixelSpan+2;
- }
-
- if (mAdaptiveSampling && dataCount >= maxCount) // use adaptive sampling only if there are at least two points per pixel on average
- {
- QCPGraphDataContainer::const_iterator it = begin;
- double minValue = it->value;
- double maxValue = it->value;
- QCPGraphDataContainer::const_iterator currentIntervalFirstPoint = it;
- int reversedFactor = keyAxis->pixelOrientation(); // is used to calculate keyEpsilon pixel into the correct direction
- int reversedRound = reversedFactor==-1 ? 1 : 0; // is used to switch between floor (normal) and ceil (reversed) rounding of currentIntervalStartKey
- double currentIntervalStartKey = keyAxis->pixelToCoord((int)(keyAxis->coordToPixel(begin->key)+reversedRound));
- double lastIntervalEndKey = currentIntervalStartKey;
- double keyEpsilon = qAbs(currentIntervalStartKey-keyAxis->pixelToCoord(keyAxis->coordToPixel(currentIntervalStartKey)+1.0*reversedFactor)); // interval of one pixel on screen when mapped to plot key coordinates
- bool keyEpsilonVariable = keyAxis->scaleType() == QCPAxis::stLogarithmic; // indicates whether keyEpsilon needs to be updated after every interval (for log axes)
- int intervalDataCount = 1;
- ++it; // advance iterator to second data point because adaptive sampling works in 1 point retrospect
- while (it != end)
- {
- if (it->key < currentIntervalStartKey+keyEpsilon) // data point is still within same pixel, so skip it and expand value span of this cluster if necessary
- {
- if (it->value < minValue)
- minValue = it->value;
- else if (it->value > maxValue)
- maxValue = it->value;
- ++intervalDataCount;
- } else // new pixel interval started
- {
- if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them to a cluster
- {
- if (lastIntervalEndKey < currentIntervalStartKey-keyEpsilon) // last point is further away, so first point of this cluster must be at a real data point
- lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.2, currentIntervalFirstPoint->value));
- lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.25, minValue));
- lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.75, maxValue));
- if (it->key > currentIntervalStartKey+keyEpsilon*2) // new pixel started further away from previous cluster, so make sure the last point of the cluster is at a real data point
- lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.8, (it-1)->value));
- } else
- lineData->append(QCPGraphData(currentIntervalFirstPoint->key, currentIntervalFirstPoint->value));
- lastIntervalEndKey = (it-1)->key;
- minValue = it->value;
- maxValue = it->value;
- currentIntervalFirstPoint = it;
- currentIntervalStartKey = keyAxis->pixelToCoord((int)(keyAxis->coordToPixel(it->key)+reversedRound));
- if (keyEpsilonVariable)
- keyEpsilon = qAbs(currentIntervalStartKey-keyAxis->pixelToCoord(keyAxis->coordToPixel(currentIntervalStartKey)+1.0*reversedFactor));
- intervalDataCount = 1;
- }
- ++it;
- }
- // handle last interval:
- if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them to a cluster
- {
- if (lastIntervalEndKey < currentIntervalStartKey-keyEpsilon) // last point wasn't a cluster, so first point of this cluster must be at a real data point
- lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.2, currentIntervalFirstPoint->value));
- lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.25, minValue));
- lineData->append(QCPGraphData(currentIntervalStartKey+keyEpsilon*0.75, maxValue));
- } else
- lineData->append(QCPGraphData(currentIntervalFirstPoint->key, currentIntervalFirstPoint->value));
-
- } else // don't use adaptive sampling algorithm, transfer points one-to-one from the data container into the output
- {
- lineData->resize(dataCount);
- std::copy(begin, end, lineData->begin());
- }
- }
- /*! \internal
- Returns via \a scatterData the data points that need to be visualized for this graph when
- plotting scatter points, taking into consideration the currently visible axis ranges and, if \ref
- setAdaptiveSampling is enabled, local point densities. The considered data can be restricted
- further by \a begin and \a end, e.g. to only plot a certain segment of the data (see \ref
- getDataSegments).
- This method is used by \ref getScatters to retrieve the basic working set of data.
- \see getOptimizedLineData
- */
- void QCPGraph::getOptimizedScatterData(QVector<QCPGraphData> *scatterData, QCPGraphDataContainer::const_iterator begin, QCPGraphDataContainer::const_iterator end) const
- {
- if (!scatterData) return;
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
-
- const int scatterModulo = mScatterSkip+1;
- const bool doScatterSkip = mScatterSkip > 0;
- int beginIndex = begin-mDataContainer->constBegin();
- int endIndex = end-mDataContainer->constBegin();
- while (doScatterSkip && begin != end && beginIndex % scatterModulo != 0) // advance begin iterator to first non-skipped scatter
- {
- ++beginIndex;
- ++begin;
- }
- if (begin == end) return;
- int dataCount = end-begin;
- int maxCount = std::numeric_limits<int>::max();
- if (mAdaptiveSampling)
- {
- int keyPixelSpan = qAbs(keyAxis->coordToPixel(begin->key)-keyAxis->coordToPixel((end-1)->key));
- maxCount = 2*keyPixelSpan+2;
- }
-
- if (mAdaptiveSampling && dataCount >= maxCount) // use adaptive sampling only if there are at least two points per pixel on average
- {
- double valueMaxRange = valueAxis->range().upper;
- double valueMinRange = valueAxis->range().lower;
- QCPGraphDataContainer::const_iterator it = begin;
- int itIndex = beginIndex;
- double minValue = it->value;
- double maxValue = it->value;
- QCPGraphDataContainer::const_iterator minValueIt = it;
- QCPGraphDataContainer::const_iterator maxValueIt = it;
- QCPGraphDataContainer::const_iterator currentIntervalStart = it;
- int reversedFactor = keyAxis->pixelOrientation(); // is used to calculate keyEpsilon pixel into the correct direction
- int reversedRound = reversedFactor==-1 ? 1 : 0; // is used to switch between floor (normal) and ceil (reversed) rounding of currentIntervalStartKey
- double currentIntervalStartKey = keyAxis->pixelToCoord((int)(keyAxis->coordToPixel(begin->key)+reversedRound));
- double keyEpsilon = qAbs(currentIntervalStartKey-keyAxis->pixelToCoord(keyAxis->coordToPixel(currentIntervalStartKey)+1.0*reversedFactor)); // interval of one pixel on screen when mapped to plot key coordinates
- bool keyEpsilonVariable = keyAxis->scaleType() == QCPAxis::stLogarithmic; // indicates whether keyEpsilon needs to be updated after every interval (for log axes)
- int intervalDataCount = 1;
- // advance iterator to second (non-skipped) data point because adaptive sampling works in 1 point retrospect:
- if (!doScatterSkip)
- ++it;
- else
- {
- itIndex += scatterModulo;
- if (itIndex < endIndex) // make sure we didn't jump over end
- it += scatterModulo;
- else
- {
- it = end;
- itIndex = endIndex;
- }
- }
- // main loop over data points:
- while (it != end)
- {
- if (it->key < currentIntervalStartKey+keyEpsilon) // data point is still within same pixel, so skip it and expand value span of this pixel if necessary
- {
- if (it->value < minValue && it->value > valueMinRange && it->value < valueMaxRange)
- {
- minValue = it->value;
- minValueIt = it;
- } else if (it->value > maxValue && it->value > valueMinRange && it->value < valueMaxRange)
- {
- maxValue = it->value;
- maxValueIt = it;
- }
- ++intervalDataCount;
- } else // new pixel started
- {
- if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them
- {
- // determine value pixel span and add as many points in interval to maintain certain vertical data density (this is specific to scatter plot):
- double valuePixelSpan = qAbs(valueAxis->coordToPixel(minValue)-valueAxis->coordToPixel(maxValue));
- int dataModulo = qMax(1, qRound(intervalDataCount/(valuePixelSpan/4.0))); // approximately every 4 value pixels one data point on average
- QCPGraphDataContainer::const_iterator intervalIt = currentIntervalStart;
- int c = 0;
- while (intervalIt != it)
- {
- if ((c % dataModulo == 0 || intervalIt == minValueIt || intervalIt == maxValueIt) && intervalIt->value > valueMinRange && intervalIt->value < valueMaxRange)
- scatterData->append(*intervalIt);
- ++c;
- if (!doScatterSkip)
- ++intervalIt;
- else
- intervalIt += scatterModulo; // since we know indices of "currentIntervalStart", "intervalIt" and "it" are multiples of scatterModulo, we can't accidentally jump over "it" here
- }
- } else if (currentIntervalStart->value > valueMinRange && currentIntervalStart->value < valueMaxRange)
- scatterData->append(*currentIntervalStart);
- minValue = it->value;
- maxValue = it->value;
- currentIntervalStart = it;
- currentIntervalStartKey = keyAxis->pixelToCoord((int)(keyAxis->coordToPixel(it->key)+reversedRound));
- if (keyEpsilonVariable)
- keyEpsilon = qAbs(currentIntervalStartKey-keyAxis->pixelToCoord(keyAxis->coordToPixel(currentIntervalStartKey)+1.0*reversedFactor));
- intervalDataCount = 1;
- }
- // advance to next data point:
- if (!doScatterSkip)
- ++it;
- else
- {
- itIndex += scatterModulo;
- if (itIndex < endIndex) // make sure we didn't jump over end
- it += scatterModulo;
- else
- {
- it = end;
- itIndex = endIndex;
- }
- }
- }
- // handle last interval:
- if (intervalDataCount >= 2) // last pixel had multiple data points, consolidate them
- {
- // determine value pixel span and add as many points in interval to maintain certain vertical data density (this is specific to scatter plot):
- double valuePixelSpan = qAbs(valueAxis->coordToPixel(minValue)-valueAxis->coordToPixel(maxValue));
- int dataModulo = qMax(1, qRound(intervalDataCount/(valuePixelSpan/4.0))); // approximately every 4 value pixels one data point on average
- QCPGraphDataContainer::const_iterator intervalIt = currentIntervalStart;
- int intervalItIndex = intervalIt-mDataContainer->constBegin();
- int c = 0;
- while (intervalIt != it)
- {
- if ((c % dataModulo == 0 || intervalIt == minValueIt || intervalIt == maxValueIt) && intervalIt->value > valueMinRange && intervalIt->value < valueMaxRange)
- scatterData->append(*intervalIt);
- ++c;
- if (!doScatterSkip)
- ++intervalIt;
- else // here we can't guarantee that adding scatterModulo doesn't exceed "it" (because "it" is equal to "end" here, and "end" isn't scatterModulo-aligned), so check via index comparison:
- {
- intervalItIndex += scatterModulo;
- if (intervalItIndex < itIndex)
- intervalIt += scatterModulo;
- else
- {
- intervalIt = it;
- intervalItIndex = itIndex;
- }
- }
- }
- } else if (currentIntervalStart->value > valueMinRange && currentIntervalStart->value < valueMaxRange)
- scatterData->append(*currentIntervalStart);
-
- } else // don't use adaptive sampling algorithm, transfer points one-to-one from the data container into the output
- {
- QCPGraphDataContainer::const_iterator it = begin;
- int itIndex = beginIndex;
- scatterData->reserve(dataCount);
- while (it != end)
- {
- scatterData->append(*it);
- // advance to next data point:
- if (!doScatterSkip)
- ++it;
- else
- {
- itIndex += scatterModulo;
- if (itIndex < endIndex)
- it += scatterModulo;
- else
- {
- it = end;
- itIndex = endIndex;
- }
- }
- }
- }
- }
- /*!
- This method outputs the currently visible data range via \a begin and \a end. The returned range
- will also never exceed \a rangeRestriction.
- This method takes into account that the drawing of data lines at the axis rect border always
- requires the points just outside the visible axis range. So \a begin and \a end may actually
- indicate a range that contains one additional data point to the left and right of the visible
- axis range.
- */
- void QCPGraph::getVisibleDataBounds(QCPGraphDataContainer::const_iterator &begin, QCPGraphDataContainer::const_iterator &end, const QCPDataRange &rangeRestriction) const
- {
- if (rangeRestriction.isEmpty())
- {
- end = mDataContainer->constEnd();
- begin = end;
- } else
- {
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
- // get visible data range:
- begin = mDataContainer->findBegin(keyAxis->range().lower);
- end = mDataContainer->findEnd(keyAxis->range().upper);
- // limit lower/upperEnd to rangeRestriction:
- mDataContainer->limitIteratorsToDataRange(begin, end, rangeRestriction); // this also ensures rangeRestriction outside data bounds doesn't break anything
- }
- }
- /*! \internal
-
- This method goes through the passed points in \a lineData and returns a list of the segments
- which don't contain NaN data points.
-
- \a keyOrientation defines whether the \a x or \a y member of the passed QPointF is used to check
- for NaN. If \a keyOrientation is \c Qt::Horizontal, the \a y member is checked, if it is \c
- Qt::Vertical, the \a x member is checked.
-
- \see getOverlappingSegments, drawFill
- */
- QVector<QCPDataRange> QCPGraph::getNonNanSegments(const QVector<QPointF> *lineData, Qt::Orientation keyOrientation) const
- {
- QVector<QCPDataRange> result;
- const int n = lineData->size();
-
- QCPDataRange currentSegment(-1, -1);
- int i = 0;
-
- if (keyOrientation == Qt::Horizontal)
- {
- while (i < n)
- {
- while (i < n && qIsNaN(lineData->at(i).y())) // seek next non-NaN data point
- ++i;
- if (i == n)
- break;
- currentSegment.setBegin(i++);
- while (i < n && !qIsNaN(lineData->at(i).y())) // seek next NaN data point or end of data
- ++i;
- currentSegment.setEnd(i++);
- result.append(currentSegment);
- }
- } else // keyOrientation == Qt::Vertical
- {
- while (i < n)
- {
- while (i < n && qIsNaN(lineData->at(i).x())) // seek next non-NaN data point
- ++i;
- if (i == n)
- break;
- currentSegment.setBegin(i++);
- while (i < n && !qIsNaN(lineData->at(i).x())) // seek next NaN data point or end of data
- ++i;
- currentSegment.setEnd(i++);
- result.append(currentSegment);
- }
- }
- return result;
- }
- /*! \internal
-
- This method takes two segment lists (e.g. created by \ref getNonNanSegments) \a thisSegments and
- \a otherSegments, and their associated point data \a thisData and \a otherData.
- It returns all pairs of segments (the first from \a thisSegments, the second from \a
- otherSegments), which overlap in plot coordinates.
-
- This method is useful in the case of a channel fill between two graphs, when only those non-NaN
- segments which actually overlap in their key coordinate shall be considered for drawing a channel
- fill polygon.
-
- It is assumed that the passed segments in \a thisSegments are ordered ascending by index, and
- that the segments don't overlap themselves. The same is assumed for the segments in \a
- otherSegments. This is fulfilled when the segments are obtained via \ref getNonNanSegments.
-
- \see getNonNanSegments, segmentsIntersect, drawFill, getChannelFillPolygon
- */
- QVector<QPair<QCPDataRange, QCPDataRange> > QCPGraph::getOverlappingSegments(QVector<QCPDataRange> thisSegments, const QVector<QPointF> *thisData, QVector<QCPDataRange> otherSegments, const QVector<QPointF> *otherData) const
- {
- QVector<QPair<QCPDataRange, QCPDataRange> > result;
- if (thisData->isEmpty() || otherData->isEmpty() || thisSegments.isEmpty() || otherSegments.isEmpty())
- return result;
-
- int thisIndex = 0;
- int otherIndex = 0;
- const bool verticalKey = mKeyAxis->orientation() == Qt::Vertical;
- while (thisIndex < thisSegments.size() && otherIndex < otherSegments.size())
- {
- if (thisSegments.at(thisIndex).size() < 2) // segments with fewer than two points won't have a fill anyhow
- {
- ++thisIndex;
- continue;
- }
- if (otherSegments.at(otherIndex).size() < 2) // segments with fewer than two points won't have a fill anyhow
- {
- ++otherIndex;
- continue;
- }
- double thisLower, thisUpper, otherLower, otherUpper;
- if (!verticalKey)
- {
- thisLower = thisData->at(thisSegments.at(thisIndex).begin()).x();
- thisUpper = thisData->at(thisSegments.at(thisIndex).end()-1).x();
- otherLower = otherData->at(otherSegments.at(otherIndex).begin()).x();
- otherUpper = otherData->at(otherSegments.at(otherIndex).end()-1).x();
- } else
- {
- thisLower = thisData->at(thisSegments.at(thisIndex).begin()).y();
- thisUpper = thisData->at(thisSegments.at(thisIndex).end()-1).y();
- otherLower = otherData->at(otherSegments.at(otherIndex).begin()).y();
- otherUpper = otherData->at(otherSegments.at(otherIndex).end()-1).y();
- }
-
- int bPrecedence;
- if (segmentsIntersect(thisLower, thisUpper, otherLower, otherUpper, bPrecedence))
- result.append(QPair<QCPDataRange, QCPDataRange>(thisSegments.at(thisIndex), otherSegments.at(otherIndex)));
-
- if (bPrecedence <= 0) // otherSegment doesn't reach as far as thisSegment, so continue with next otherSegment, keeping current thisSegment
- ++otherIndex;
- else // otherSegment reaches further than thisSegment, so continue with next thisSegment, keeping current otherSegment
- ++thisIndex;
- }
-
- return result;
- }
- /*! \internal
-
- Returns whether the segments defined by the coordinates (aLower, aUpper) and (bLower, bUpper)
- have overlap.
-
- The output parameter \a bPrecedence indicates whether the \a b segment reaches farther than the
- \a a segment or not. If \a bPrecedence returns 1, segment \a b reaches the farthest to higher
- coordinates (i.e. bUpper > aUpper). If it returns -1, segment \a a reaches the farthest. Only if
- both segment's upper bounds are identical, 0 is returned as \a bPrecedence.
-
- It is assumed that the lower bounds always have smaller or equal values than the upper bounds.
-
- \see getOverlappingSegments
- */
- bool QCPGraph::segmentsIntersect(double aLower, double aUpper, double bLower, double bUpper, int &bPrecedence) const
- {
- bPrecedence = 0;
- if (aLower > bUpper)
- {
- bPrecedence = -1;
- return false;
- } else if (bLower > aUpper)
- {
- bPrecedence = 1;
- return false;
- } else
- {
- if (aUpper > bUpper)
- bPrecedence = -1;
- else if (aUpper < bUpper)
- bPrecedence = 1;
-
- return true;
- }
- }
- /*! \internal
-
- Returns the point which closes the fill polygon on the zero-value-line parallel to the key axis.
- The logarithmic axis scale case is a bit special, since the zero-value-line in pixel coordinates
- is in positive or negative infinity. So this case is handled separately by just closing the fill
- polygon on the axis which lies in the direction towards the zero value.
- \a matchingDataPoint will provide the key (in pixels) of the returned point. Depending on whether
- the key axis of this graph is horizontal or vertical, \a matchingDataPoint will provide the x or
- y value of the returned point, respectively.
- */
- QPointF QCPGraph::getFillBasePoint(QPointF matchingDataPoint) const
- {
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPointF(); }
-
- QPointF result;
- if (valueAxis->scaleType() == QCPAxis::stLinear)
- {
- if (keyAxis->orientation() == Qt::Horizontal)
- {
- result.setX(matchingDataPoint.x());
- result.setY(valueAxis->coordToPixel(0));
- } else // keyAxis->orientation() == Qt::Vertical
- {
- result.setX(valueAxis->coordToPixel(0));
- result.setY(matchingDataPoint.y());
- }
- } else // valueAxis->mScaleType == QCPAxis::stLogarithmic
- {
- // In logarithmic scaling we can't just draw to value 0 so we just fill all the way
- // to the axis which is in the direction towards 0
- if (keyAxis->orientation() == Qt::Vertical)
- {
- if ((valueAxis->range().upper < 0 && !valueAxis->rangeReversed()) ||
- (valueAxis->range().upper > 0 && valueAxis->rangeReversed())) // if range is negative, zero is on opposite side of key axis
- result.setX(keyAxis->axisRect()->right());
- else
- result.setX(keyAxis->axisRect()->left());
- result.setY(matchingDataPoint.y());
- } else if (keyAxis->axisType() == QCPAxis::atTop || keyAxis->axisType() == QCPAxis::atBottom)
- {
- result.setX(matchingDataPoint.x());
- if ((valueAxis->range().upper < 0 && !valueAxis->rangeReversed()) ||
- (valueAxis->range().upper > 0 && valueAxis->rangeReversed())) // if range is negative, zero is on opposite side of key axis
- result.setY(keyAxis->axisRect()->top());
- else
- result.setY(keyAxis->axisRect()->bottom());
- }
- }
- return result;
- }
- /*! \internal
-
- Returns the polygon needed for drawing normal fills between this graph and the key axis.
-
- Pass the graph's data points (in pixel coordinates) as \a lineData, and specify the \a segment
- which shall be used for the fill. The collection of \a lineData points described by \a segment
- must not contain NaN data points (see \ref getNonNanSegments).
-
- The returned fill polygon will be closed at the key axis (the zero-value line) for linear value
- axes. For logarithmic value axes the polygon will reach just beyond the corresponding axis rect
- side (see \ref getFillBasePoint).
- For increased performance (due to implicit sharing), keep the returned QPolygonF const.
-
- \see drawFill, getNonNanSegments
- */
- const QPolygonF QCPGraph::getFillPolygon(const QVector<QPointF> *lineData, QCPDataRange segment) const
- {
- if (segment.size() < 2)
- return QPolygonF();
- QPolygonF result(segment.size()+2);
-
- result[0] = getFillBasePoint(lineData->at(segment.begin()));
- std::copy(lineData->constBegin()+segment.begin(), lineData->constBegin()+segment.end(), result.begin()+1);
- result[result.size()-1] = getFillBasePoint(lineData->at(segment.end()-1));
-
- return result;
- }
- /*! \internal
-
- Returns the polygon needed for drawing (partial) channel fills between this graph and the graph
- specified by \ref setChannelFillGraph.
-
- The data points of this graph are passed as pixel coordinates via \a thisData, the data of the
- other graph as \a otherData. The returned polygon will be calculated for the specified data
- segments \a thisSegment and \a otherSegment, pertaining to the respective \a thisData and \a
- otherData, respectively.
-
- The passed \a thisSegment and \a otherSegment should correspond to the segment pairs returned by
- \ref getOverlappingSegments, to make sure only segments that actually have key coordinate overlap
- need to be processed here.
-
- For increased performance due to implicit sharing, keep the returned QPolygonF const.
-
- \see drawFill, getOverlappingSegments, getNonNanSegments
- */
- const QPolygonF QCPGraph::getChannelFillPolygon(const QVector<QPointF> *thisData, QCPDataRange thisSegment, const QVector<QPointF> *otherData, QCPDataRange otherSegment) const
- {
- if (!mChannelFillGraph)
- return QPolygonF();
-
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPolygonF(); }
- if (!mChannelFillGraph.data()->mKeyAxis) { qDebug() << Q_FUNC_INFO << "channel fill target key axis invalid"; return QPolygonF(); }
-
- if (mChannelFillGraph.data()->mKeyAxis.data()->orientation() != keyAxis->orientation())
- return QPolygonF(); // don't have same axis orientation, can't fill that (Note: if keyAxis fits, valueAxis will fit too, because it's always orthogonal to keyAxis)
-
- if (thisData->isEmpty()) return QPolygonF();
- QVector<QPointF> thisSegmentData(thisSegment.size());
- QVector<QPointF> otherSegmentData(otherSegment.size());
- std::copy(thisData->constBegin()+thisSegment.begin(), thisData->constBegin()+thisSegment.end(), thisSegmentData.begin());
- std::copy(otherData->constBegin()+otherSegment.begin(), otherData->constBegin()+otherSegment.end(), otherSegmentData.begin());
- // pointers to be able to swap them, depending which data range needs cropping:
- QVector<QPointF> *staticData = &thisSegmentData;
- QVector<QPointF> *croppedData = &otherSegmentData;
-
- // crop both vectors to ranges in which the keys overlap (which coord is key, depends on axisType):
- if (keyAxis->orientation() == Qt::Horizontal)
- {
- // x is key
- // crop lower bound:
- if (staticData->first().x() < croppedData->first().x()) // other one must be cropped
- qSwap(staticData, croppedData);
- const int lowBound = findIndexBelowX(croppedData, staticData->first().x());
- if (lowBound == -1) return QPolygonF(); // key ranges have no overlap
- croppedData->remove(0, lowBound);
- // set lowest point of cropped data to fit exactly key position of first static data point via linear interpolation:
- if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation
- double slope;
- if (!qFuzzyCompare(croppedData->at(1).x(), croppedData->at(0).x()))
- slope = (croppedData->at(1).y()-croppedData->at(0).y())/(croppedData->at(1).x()-croppedData->at(0).x());
- else
- slope = 0;
- (*croppedData)[0].setY(croppedData->at(0).y()+slope*(staticData->first().x()-croppedData->at(0).x()));
- (*croppedData)[0].setX(staticData->first().x());
-
- // crop upper bound:
- if (staticData->last().x() > croppedData->last().x()) // other one must be cropped
- qSwap(staticData, croppedData);
- int highBound = findIndexAboveX(croppedData, staticData->last().x());
- if (highBound == -1) return QPolygonF(); // key ranges have no overlap
- croppedData->remove(highBound+1, croppedData->size()-(highBound+1));
- // set highest point of cropped data to fit exactly key position of last static data point via linear interpolation:
- if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation
- const int li = croppedData->size()-1; // last index
- if (!qFuzzyCompare(croppedData->at(li).x(), croppedData->at(li-1).x()))
- slope = (croppedData->at(li).y()-croppedData->at(li-1).y())/(croppedData->at(li).x()-croppedData->at(li-1).x());
- else
- slope = 0;
- (*croppedData)[li].setY(croppedData->at(li-1).y()+slope*(staticData->last().x()-croppedData->at(li-1).x()));
- (*croppedData)[li].setX(staticData->last().x());
- } else // mKeyAxis->orientation() == Qt::Vertical
- {
- // y is key
- // crop lower bound:
- if (staticData->first().y() < croppedData->first().y()) // other one must be cropped
- qSwap(staticData, croppedData);
- int lowBound = findIndexBelowY(croppedData, staticData->first().y());
- if (lowBound == -1) return QPolygonF(); // key ranges have no overlap
- croppedData->remove(0, lowBound);
- // set lowest point of cropped data to fit exactly key position of first static data point via linear interpolation:
- if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation
- double slope;
- if (!qFuzzyCompare(croppedData->at(1).y(), croppedData->at(0).y())) // avoid division by zero in step plots
- slope = (croppedData->at(1).x()-croppedData->at(0).x())/(croppedData->at(1).y()-croppedData->at(0).y());
- else
- slope = 0;
- (*croppedData)[0].setX(croppedData->at(0).x()+slope*(staticData->first().y()-croppedData->at(0).y()));
- (*croppedData)[0].setY(staticData->first().y());
-
- // crop upper bound:
- if (staticData->last().y() > croppedData->last().y()) // other one must be cropped
- qSwap(staticData, croppedData);
- int highBound = findIndexAboveY(croppedData, staticData->last().y());
- if (highBound == -1) return QPolygonF(); // key ranges have no overlap
- croppedData->remove(highBound+1, croppedData->size()-(highBound+1));
- // set highest point of cropped data to fit exactly key position of last static data point via linear interpolation:
- if (croppedData->size() < 2) return QPolygonF(); // need at least two points for interpolation
- int li = croppedData->size()-1; // last index
- if (!qFuzzyCompare(croppedData->at(li).y(), croppedData->at(li-1).y())) // avoid division by zero in step plots
- slope = (croppedData->at(li).x()-croppedData->at(li-1).x())/(croppedData->at(li).y()-croppedData->at(li-1).y());
- else
- slope = 0;
- (*croppedData)[li].setX(croppedData->at(li-1).x()+slope*(staticData->last().y()-croppedData->at(li-1).y()));
- (*croppedData)[li].setY(staticData->last().y());
- }
-
- // return joined:
- for (int i=otherSegmentData.size()-1; i>=0; --i) // insert reversed, otherwise the polygon will be twisted
- thisSegmentData << otherSegmentData.at(i);
- return QPolygonF(thisSegmentData);
- }
- /*! \internal
-
- Finds the smallest index of \a data, whose points x value is just above \a x. Assumes x values in
- \a data points are ordered ascending, as is ensured by \ref getLines/\ref getScatters if the key
- axis is horizontal.
- Used to calculate the channel fill polygon, see \ref getChannelFillPolygon.
- */
- int QCPGraph::findIndexAboveX(const QVector<QPointF> *data, double x) const
- {
- for (int i=data->size()-1; i>=0; --i)
- {
- if (data->at(i).x() < x)
- {
- if (i<data->size()-1)
- return i+1;
- else
- return data->size()-1;
- }
- }
- return -1;
- }
- /*! \internal
-
- Finds the highest index of \a data, whose points x value is just below \a x. Assumes x values in
- \a data points are ordered ascending, as is ensured by \ref getLines/\ref getScatters if the key
- axis is horizontal.
-
- Used to calculate the channel fill polygon, see \ref getChannelFillPolygon.
- */
- int QCPGraph::findIndexBelowX(const QVector<QPointF> *data, double x) const
- {
- for (int i=0; i<data->size(); ++i)
- {
- if (data->at(i).x() > x)
- {
- if (i>0)
- return i-1;
- else
- return 0;
- }
- }
- return -1;
- }
- /*! \internal
-
- Finds the smallest index of \a data, whose points y value is just above \a y. Assumes y values in
- \a data points are ordered ascending, as is ensured by \ref getLines/\ref getScatters if the key
- axis is vertical.
-
- Used to calculate the channel fill polygon, see \ref getChannelFillPolygon.
- */
- int QCPGraph::findIndexAboveY(const QVector<QPointF> *data, double y) const
- {
- for (int i=data->size()-1; i>=0; --i)
- {
- if (data->at(i).y() < y)
- {
- if (i<data->size()-1)
- return i+1;
- else
- return data->size()-1;
- }
- }
- return -1;
- }
- /*! \internal
-
- Calculates the minimum distance in pixels the graph's representation has from the given \a
- pixelPoint. This is used to determine whether the graph was clicked or not, e.g. in \ref
- selectTest. The closest data point to \a pixelPoint is returned in \a closestData. Note that if
- the graph has a line representation, the returned distance may be smaller than the distance to
- the \a closestData point, since the distance to the graph line is also taken into account.
-
- If either the graph has no data or if the line style is \ref lsNone and the scatter style's shape
- is \ref QCPScatterStyle::ssNone (i.e. there is no visual representation of the graph), returns -1.0.
- */
- double QCPGraph::pointDistance(const QPointF &pixelPoint, QCPGraphDataContainer::const_iterator &closestData) const
- {
- closestData = mDataContainer->constEnd();
- if (mDataContainer->isEmpty())
- return -1.0;
- if (mLineStyle == lsNone && mScatterStyle.isNone())
- return -1.0;
-
- // calculate minimum distances to graph data points and find closestData iterator:
- double minDistSqr = std::numeric_limits<double>::max();
- // determine which key range comes into question, taking selection tolerance around pos into account:
- double posKeyMin, posKeyMax, dummy;
- pixelsToCoords(pixelPoint-QPointF(mParentPlot->selectionTolerance(), mParentPlot->selectionTolerance()), posKeyMin, dummy);
- pixelsToCoords(pixelPoint+QPointF(mParentPlot->selectionTolerance(), mParentPlot->selectionTolerance()), posKeyMax, dummy);
- if (posKeyMin > posKeyMax)
- qSwap(posKeyMin, posKeyMax);
- // iterate over found data points and then choose the one with the shortest distance to pos:
- QCPGraphDataContainer::const_iterator begin = mDataContainer->findBegin(posKeyMin, true);
- QCPGraphDataContainer::const_iterator end = mDataContainer->findEnd(posKeyMax, true);
- for (QCPGraphDataContainer::const_iterator it=begin; it!=end; ++it)
- {
- const double currentDistSqr = QCPVector2D(coordsToPixels(it->key, it->value)-pixelPoint).lengthSquared();
- if (currentDistSqr < minDistSqr)
- {
- minDistSqr = currentDistSqr;
- closestData = it;
- }
- }
-
- // calculate distance to graph line if there is one (if so, will probably be smaller than distance to closest data point):
- if (mLineStyle != lsNone)
- {
- // line displayed, calculate distance to line segments:
- QVector<QPointF> lineData;
- getLines(&lineData, QCPDataRange(0, dataCount()));
- QCPVector2D p(pixelPoint);
- const int step = mLineStyle==lsImpulse ? 2 : 1; // impulse plot differs from other line styles in that the lineData points are only pairwise connected
- for (int i=0; i<lineData.size()-1; i+=step)
- {
- const double currentDistSqr = p.distanceSquaredToLine(lineData.at(i), lineData.at(i+1));
- if (currentDistSqr < minDistSqr)
- minDistSqr = currentDistSqr;
- }
- }
-
- return qSqrt(minDistSqr);
- }
- /*! \internal
-
- Finds the highest index of \a data, whose points y value is just below \a y. Assumes y values in
- \a data points are ordered ascending, as is ensured by \ref getLines/\ref getScatters if the key
- axis is vertical.
- Used to calculate the channel fill polygon, see \ref getChannelFillPolygon.
- */
- int QCPGraph::findIndexBelowY(const QVector<QPointF> *data, double y) const
- {
- for (int i=0; i<data->size(); ++i)
- {
- if (data->at(i).y() > y)
- {
- if (i>0)
- return i-1;
- else
- return 0;
- }
- }
- return -1;
- }
- /* end of 'src/plottables/plottable-graph.cpp' */
- /* including file 'src/plottables/plottable-curve.cpp', size 63527 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPCurveData
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPCurveData
- \brief Holds the data of one single data point for QCPCurve.
-
- The stored data is:
- \li \a t: the free ordering parameter of this curve point, like in the mathematical vector <em>(x(t), y(t))</em>. (This is the \a sortKey)
- \li \a key: coordinate on the key axis of this curve point (this is the \a mainKey)
- \li \a value: coordinate on the value axis of this curve point (this is the \a mainValue)
-
- The container for storing multiple data points is \ref QCPCurveDataContainer. It is a typedef for
- \ref QCPDataContainer with \ref QCPCurveData as the DataType template parameter. See the
- documentation there for an explanation regarding the data type's generic methods.
-
- \see QCPCurveDataContainer
- */
- /* start documentation of inline functions */
- /*! \fn double QCPCurveData::sortKey() const
-
- Returns the \a t member of this data point.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn static QCPCurveData QCPCurveData::fromSortKey(double sortKey)
-
- Returns a data point with the specified \a sortKey (assigned to the data point's \a t member).
- All other members are set to zero.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn static static bool QCPCurveData::sortKeyIsMainKey()
-
- Since the member \a key is the data point key coordinate and the member \a t is the data ordering
- parameter, this method returns false.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn double QCPCurveData::mainKey() const
-
- Returns the \a key member of this data point.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn double QCPCurveData::mainValue() const
-
- Returns the \a value member of this data point.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn QCPRange QCPCurveData::valueRange() const
-
- Returns a QCPRange with both lower and upper boundary set to \a value of this data point.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /* end documentation of inline functions */
- /*!
- Constructs a curve data point with t, key and value set to zero.
- */
- QCPCurveData::QCPCurveData() :
- t(0),
- key(0),
- value(0)
- {
- }
- /*!
- Constructs a curve data point with the specified \a t, \a key and \a value.
- */
- QCPCurveData::QCPCurveData(double t, double key, double value) :
- t(t),
- key(key),
- value(value)
- {
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPCurve
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPCurve
- \brief A plottable representing a parametric curve in a plot.
-
- \image html QCPCurve.png
-
- Unlike QCPGraph, plottables of this type may have multiple points with the same key coordinate,
- so their visual representation can have \a loops. This is realized by introducing a third
- coordinate \a t, which defines the order of the points described by the other two coordinates \a
- x and \a y.
- To plot data, assign it with the \ref setData or \ref addData functions. Alternatively, you can
- also access and modify the curve's data via the \ref data method, which returns a pointer to the
- internal \ref QCPCurveDataContainer.
-
- Gaps in the curve can be created by adding data points with NaN as key and value
- (<tt>qQNaN()</tt> or <tt>std::numeric_limits<double>::quiet_NaN()</tt>) in between the two data points that shall be
- separated.
-
- \section qcpcurve-appearance Changing the appearance
-
- The appearance of the curve is determined by the pen and the brush (\ref setPen, \ref setBrush).
-
- \section qcpcurve-usage Usage
-
- Like all data representing objects in QCustomPlot, the QCPCurve is a plottable
- (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies
- (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.)
-
- Usually, you first create an instance:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-creation-1
- which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot instance takes
- ownership of the plottable, so do not delete it manually but use QCustomPlot::removePlottable() instead.
- The newly created plottable can be modified, e.g.:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-creation-2
- */
- /* start of documentation of inline functions */
- /*! \fn QSharedPointer<QCPCurveDataContainer> QCPCurve::data() const
-
- Returns a shared pointer to the internal data storage of type \ref QCPCurveDataContainer. You may
- use it to directly manipulate the data, which may be more convenient and faster than using the
- regular \ref setData or \ref addData methods.
- */
- /* end of documentation of inline functions */
- /*!
- Constructs a curve which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value
- axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have
- the same orientation. If either of these restrictions is violated, a corresponding message is
- printed to the debug output (qDebug), the construction is not aborted, though.
-
- The created QCPCurve is automatically registered with the QCustomPlot instance inferred from \a
- keyAxis. This QCustomPlot instance takes ownership of the QCPCurve, so do not delete it manually
- but use QCustomPlot::removePlottable() instead.
- */
- QCPCurve::QCPCurve(QCPAxis *keyAxis, QCPAxis *valueAxis) :
- QCPAbstractPlottable1D<QCPCurveData>(keyAxis, valueAxis)
- {
- // modify inherited properties from abstract plottable:
- setPen(QPen(Qt::blue, 0));
- setBrush(Qt::NoBrush);
-
- setScatterStyle(QCPScatterStyle());
- setLineStyle(lsLine);
- setScatterSkip(0);
- }
- QCPCurve::~QCPCurve()
- {
- }
- /*! \overload
-
- Replaces the current data container with the provided \a data container.
-
- Since a QSharedPointer is used, multiple QCPCurves may share the same data container safely.
- Modifying the data in the container will then affect all curves that share the container. Sharing
- can be achieved by simply exchanging the data containers wrapped in shared pointers:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-datasharing-1
-
- If you do not wish to share containers, but create a copy from an existing container, rather use
- the \ref QCPDataContainer<DataType>::set method on the curve's data container directly:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcurve-datasharing-2
-
- \see addData
- */
- void QCPCurve::setData(QSharedPointer<QCPCurveDataContainer> data)
- {
- mDataContainer = data;
- }
- /*! \overload
-
- Replaces the current data with the provided points in \a t, \a keys and \a values. The provided
- vectors should have equal length. Else, the number of added points will be the size of the
- smallest vector.
-
- If you can guarantee that the passed data points are sorted by \a t in ascending order, you can
- set \a alreadySorted to true, to improve performance by saving a sorting run.
-
- \see addData
- */
- void QCPCurve::setData(const QVector<double> &t, const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
- {
- mDataContainer->clear();
- addData(t, keys, values, alreadySorted);
- }
- /*! \overload
-
- Replaces the current data with the provided points in \a keys and \a values. The provided vectors
- should have equal length. Else, the number of added points will be the size of the smallest
- vector.
-
- The t parameter of each data point will be set to the integer index of the respective key/value
- pair.
-
- \see addData
- */
- void QCPCurve::setData(const QVector<double> &keys, const QVector<double> &values)
- {
- mDataContainer->clear();
- addData(keys, values);
- }
- /*!
- Sets the visual appearance of single data points in the plot. If set to \ref
- QCPScatterStyle::ssNone, no scatter points are drawn (e.g. for line-only plots with appropriate
- line style).
-
- \see QCPScatterStyle, setLineStyle
- */
- void QCPCurve::setScatterStyle(const QCPScatterStyle &style)
- {
- mScatterStyle = style;
- }
- /*!
- If scatters are displayed (scatter style not \ref QCPScatterStyle::ssNone), \a skip number of
- scatter points are skipped/not drawn after every drawn scatter point.
- This can be used to make the data appear sparser while for example still having a smooth line,
- and to improve performance for very high density plots.
- If \a skip is set to 0 (default), all scatter points are drawn.
- \see setScatterStyle
- */
- void QCPCurve::setScatterSkip(int skip)
- {
- mScatterSkip = qMax(0, skip);
- }
- /*!
- Sets how the single data points are connected in the plot or how they are represented visually
- apart from the scatter symbol. For scatter-only plots, set \a style to \ref lsNone and \ref
- setScatterStyle to the desired scatter style.
-
- \see setScatterStyle
- */
- void QCPCurve::setLineStyle(QCPCurve::LineStyle style)
- {
- mLineStyle = style;
- }
- /*! \overload
-
- Adds the provided points in \a t, \a keys and \a values to the current data. The provided vectors
- should have equal length. Else, the number of added points will be the size of the smallest
- vector.
-
- If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
- can set \a alreadySorted to true, to improve performance by saving a sorting run.
-
- Alternatively, you can also access and modify the data directly via the \ref data method, which
- returns a pointer to the internal data container.
- */
- void QCPCurve::addData(const QVector<double> &t, const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
- {
- if (t.size() != keys.size() || t.size() != values.size())
- qDebug() << Q_FUNC_INFO << "ts, keys and values have different sizes:" << t.size() << keys.size() << values.size();
- const int n = qMin(qMin(t.size(), keys.size()), values.size());
- QVector<QCPCurveData> tempData(n);
- QVector<QCPCurveData>::iterator it = tempData.begin();
- const QVector<QCPCurveData>::iterator itEnd = tempData.end();
- int i = 0;
- while (it != itEnd)
- {
- it->t = t[i];
- it->key = keys[i];
- it->value = values[i];
- ++it;
- ++i;
- }
- mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write
- }
- /*! \overload
-
- Adds the provided points in \a keys and \a values to the current data. The provided vectors
- should have equal length. Else, the number of added points will be the size of the smallest
- vector.
-
- The t parameter of each data point will be set to the integer index of the respective key/value
- pair.
-
- Alternatively, you can also access and modify the data directly via the \ref data method, which
- returns a pointer to the internal data container.
- */
- void QCPCurve::addData(const QVector<double> &keys, const QVector<double> &values)
- {
- if (keys.size() != values.size())
- qDebug() << Q_FUNC_INFO << "keys and values have different sizes:" << keys.size() << values.size();
- const int n = qMin(keys.size(), values.size());
- double tStart;
- if (!mDataContainer->isEmpty())
- tStart = (mDataContainer->constEnd()-1)->t + 1.0;
- else
- tStart = 0;
- QVector<QCPCurveData> tempData(n);
- QVector<QCPCurveData>::iterator it = tempData.begin();
- const QVector<QCPCurveData>::iterator itEnd = tempData.end();
- int i = 0;
- while (it != itEnd)
- {
- it->t = tStart + i;
- it->key = keys[i];
- it->value = values[i];
- ++it;
- ++i;
- }
- mDataContainer->add(tempData, true); // don't modify tempData beyond this to prevent copy on write
- }
- /*! \overload
- Adds the provided data point as \a t, \a key and \a value to the current data.
-
- Alternatively, you can also access and modify the data directly via the \ref data method, which
- returns a pointer to the internal data container.
- */
- void QCPCurve::addData(double t, double key, double value)
- {
- mDataContainer->add(QCPCurveData(t, key, value));
- }
- /*! \overload
-
- Adds the provided data point as \a key and \a value to the current data.
-
- The t parameter is generated automatically by increments of 1 for each point, starting at the
- highest t of previously existing data or 0, if the curve data is empty.
-
- Alternatively, you can also access and modify the data directly via the \ref data method, which
- returns a pointer to the internal data container.
- */
- void QCPCurve::addData(double key, double value)
- {
- if (!mDataContainer->isEmpty())
- mDataContainer->add(QCPCurveData((mDataContainer->constEnd()-1)->t + 1.0, key, value));
- else
- mDataContainer->add(QCPCurveData(0.0, key, value));
- }
- /* inherits documentation from base class */
- double QCPCurve::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
- return -1;
- if (!mKeyAxis || !mValueAxis)
- return -1;
-
- if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()))
- {
- QCPCurveDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd();
- double result = pointDistance(pos, closestDataPoint);
- if (details)
- {
- int pointIndex = closestDataPoint-mDataContainer->constBegin();
- details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex+1)));
- }
- return result;
- } else
- return -1;
- }
- /* inherits documentation from base class */
- QCPRange QCPCurve::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
- {
- return mDataContainer->keyRange(foundRange, inSignDomain);
- }
- /* inherits documentation from base class */
- QCPRange QCPCurve::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
- {
- return mDataContainer->valueRange(foundRange, inSignDomain, inKeyRange);
- }
- /* inherits documentation from base class */
- void QCPCurve::draw(QCPPainter *painter)
- {
- if (mDataContainer->isEmpty()) return;
-
- // allocate line vector:
- QVector<QPointF> lines, scatters;
-
- // loop over and draw segments of unselected/selected data:
- QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
- getDataSegments(selectedSegments, unselectedSegments);
- allSegments << unselectedSegments << selectedSegments;
- for (int i=0; i<allSegments.size(); ++i)
- {
- bool isSelectedSegment = i >= unselectedSegments.size();
-
- // fill with curve data:
- QPen finalCurvePen = mPen; // determine the final pen already here, because the line optimization depends on its stroke width
- if (isSelectedSegment && mSelectionDecorator)
- finalCurvePen = mSelectionDecorator->pen();
-
- QCPDataRange lineDataRange = isSelectedSegment ? allSegments.at(i) : allSegments.at(i).adjusted(-1, 1); // unselected segments extend lines to bordering selected data point (safe to exceed total data bounds in first/last segment, getCurveLines takes care)
- getCurveLines(&lines, lineDataRange, finalCurvePen.widthF());
-
- // check data validity if flag set:
- #ifdef QCUSTOMPLOT_CHECK_DATA
- for (QCPCurveDataContainer::const_iterator it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it)
- {
- if (QCP::isInvalidData(it->t) ||
- QCP::isInvalidData(it->key, it->value))
- qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "invalid." << "Plottable name:" << name();
- }
- #endif
-
- // draw curve fill:
- applyFillAntialiasingHint(painter);
- if (isSelectedSegment && mSelectionDecorator)
- mSelectionDecorator->applyBrush(painter);
- else
- painter->setBrush(mBrush);
- painter->setPen(Qt::NoPen);
- if (painter->brush().style() != Qt::NoBrush && painter->brush().color().alpha() != 0)
- painter->drawPolygon(QPolygonF(lines));
-
- // draw curve line:
- if (mLineStyle != lsNone)
- {
- painter->setPen(finalCurvePen);
- painter->setBrush(Qt::NoBrush);
- drawCurveLine(painter, lines);
- }
-
- // draw scatters:
- QCPScatterStyle finalScatterStyle = mScatterStyle;
- if (isSelectedSegment && mSelectionDecorator)
- finalScatterStyle = mSelectionDecorator->getFinalScatterStyle(mScatterStyle);
- if (!finalScatterStyle.isNone())
- {
- getScatters(&scatters, allSegments.at(i), finalScatterStyle.size());
- drawScatterPlot(painter, scatters, finalScatterStyle);
- }
- }
-
- // draw other selection decoration that isn't just line/scatter pens and brushes:
- if (mSelectionDecorator)
- mSelectionDecorator->drawDecoration(painter, selection());
- }
- /* inherits documentation from base class */
- void QCPCurve::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
- {
- // draw fill:
- if (mBrush.style() != Qt::NoBrush)
- {
- applyFillAntialiasingHint(painter);
- painter->fillRect(QRectF(rect.left(), rect.top()+rect.height()/2.0, rect.width(), rect.height()/3.0), mBrush);
- }
- // draw line vertically centered:
- if (mLineStyle != lsNone)
- {
- applyDefaultAntialiasingHint(painter);
- painter->setPen(mPen);
- painter->drawLine(QLineF(rect.left(), rect.top()+rect.height()/2.0, rect.right()+5, rect.top()+rect.height()/2.0)); // +5 on x2 else last segment is missing from dashed/dotted pens
- }
- // draw scatter symbol:
- if (!mScatterStyle.isNone())
- {
- applyScattersAntialiasingHint(painter);
- // scale scatter pixmap if it's too large to fit in legend icon rect:
- if (mScatterStyle.shape() == QCPScatterStyle::ssPixmap && (mScatterStyle.pixmap().size().width() > rect.width() || mScatterStyle.pixmap().size().height() > rect.height()))
- {
- QCPScatterStyle scaledStyle(mScatterStyle);
- scaledStyle.setPixmap(scaledStyle.pixmap().scaled(rect.size().toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
- scaledStyle.applyTo(painter, mPen);
- scaledStyle.drawShape(painter, QRectF(rect).center());
- } else
- {
- mScatterStyle.applyTo(painter, mPen);
- mScatterStyle.drawShape(painter, QRectF(rect).center());
- }
- }
- }
- /*! \internal
- Draws lines between the points in \a lines, given in pixel coordinates.
- \see drawScatterPlot, getCurveLines
- */
- void QCPCurve::drawCurveLine(QCPPainter *painter, const QVector<QPointF> &lines) const
- {
- if (painter->pen().style() != Qt::NoPen && painter->pen().color().alpha() != 0)
- {
- applyDefaultAntialiasingHint(painter);
- drawPolyline(painter, lines);
- }
- }
- /*! \internal
- Draws scatter symbols at every point passed in \a points, given in pixel coordinates. The
- scatters will be drawn with \a painter and have the appearance as specified in \a style.
- \see drawCurveLine, getCurveLines
- */
- void QCPCurve::drawScatterPlot(QCPPainter *painter, const QVector<QPointF> &points, const QCPScatterStyle &style) const
- {
- // draw scatter point symbols:
- applyScattersAntialiasingHint(painter);
- style.applyTo(painter, mPen);
- for (int i=0; i<points.size(); ++i)
- if (!qIsNaN(points.at(i).x()) && !qIsNaN(points.at(i).y()))
- style.drawShape(painter, points.at(i));
- }
- /*! \internal
- Called by \ref draw to generate points in pixel coordinates which represent the line of the
- curve.
- Line segments that aren't visible in the current axis rect are handled in an optimized way. They
- are projected onto a rectangle slightly larger than the visible axis rect and simplified
- regarding point count. The algorithm makes sure to preserve appearance of lines and fills inside
- the visible axis rect by generating new temporary points on the outer rect if necessary.
- \a lines will be filled with points in pixel coordinates, that can be drawn with \ref
- drawCurveLine.
- \a dataRange specifies the beginning and ending data indices that will be taken into account for
- conversion. In this function, the specified range may exceed the total data bounds without harm:
- a correspondingly trimmed data range will be used. This takes the burden off the user of this
- function to check for valid indices in \a dataRange, e.g. when extending ranges coming from \ref
- getDataSegments.
- \a penWidth specifies the pen width that will be used to later draw the lines generated by this
- function. This is needed here to calculate an accordingly wider margin around the axis rect when
- performing the line optimization.
- Methods that are also involved in the algorithm are: \ref getRegion, \ref getOptimizedPoint, \ref
- getOptimizedCornerPoints \ref mayTraverse, \ref getTraverse, \ref getTraverseCornerPoints.
- \see drawCurveLine, drawScatterPlot
- */
- void QCPCurve::getCurveLines(QVector<QPointF> *lines, const QCPDataRange &dataRange, double penWidth) const
- {
- if (!lines) return;
- lines->clear();
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
-
- // add margins to rect to compensate for stroke width
- const double strokeMargin = qMax(qreal(1.0), qreal(penWidth*0.75)); // stroke radius + 50% safety
- const double keyMin = keyAxis->pixelToCoord(keyAxis->coordToPixel(keyAxis->range().lower)-strokeMargin*keyAxis->pixelOrientation());
- const double keyMax = keyAxis->pixelToCoord(keyAxis->coordToPixel(keyAxis->range().upper)+strokeMargin*keyAxis->pixelOrientation());
- const double valueMin = valueAxis->pixelToCoord(valueAxis->coordToPixel(valueAxis->range().lower)-strokeMargin*valueAxis->pixelOrientation());
- const double valueMax = valueAxis->pixelToCoord(valueAxis->coordToPixel(valueAxis->range().upper)+strokeMargin*valueAxis->pixelOrientation());
- QCPCurveDataContainer::const_iterator itBegin = mDataContainer->constBegin();
- QCPCurveDataContainer::const_iterator itEnd = mDataContainer->constEnd();
- mDataContainer->limitIteratorsToDataRange(itBegin, itEnd, dataRange);
- if (itBegin == itEnd)
- return;
- QCPCurveDataContainer::const_iterator it = itBegin;
- QCPCurveDataContainer::const_iterator prevIt = itEnd-1;
- int prevRegion = getRegion(prevIt->key, prevIt->value, keyMin, valueMax, keyMax, valueMin);
- QVector<QPointF> trailingPoints; // points that must be applied after all other points (are generated only when handling first point to get virtual segment between last and first point right)
- while (it != itEnd)
- {
- const int currentRegion = getRegion(it->key, it->value, keyMin, valueMax, keyMax, valueMin);
- if (currentRegion != prevRegion) // changed region, possibly need to add some optimized edge points or original points if entering R
- {
- if (currentRegion != 5) // segment doesn't end in R, so it's a candidate for removal
- {
- QPointF crossA, crossB;
- if (prevRegion == 5) // we're coming from R, so add this point optimized
- {
- lines->append(getOptimizedPoint(currentRegion, it->key, it->value, prevIt->key, prevIt->value, keyMin, valueMax, keyMax, valueMin));
- // in the situations 5->1/7/9/3 the segment may leave R and directly cross through two outer regions. In these cases we need to add an additional corner point
- *lines << getOptimizedCornerPoints(prevRegion, currentRegion, prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin);
- } else if (mayTraverse(prevRegion, currentRegion) &&
- getTraverse(prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin, crossA, crossB))
- {
- // add the two cross points optimized if segment crosses R and if segment isn't virtual zeroth segment between last and first curve point:
- QVector<QPointF> beforeTraverseCornerPoints, afterTraverseCornerPoints;
- getTraverseCornerPoints(prevRegion, currentRegion, keyMin, valueMax, keyMax, valueMin, beforeTraverseCornerPoints, afterTraverseCornerPoints);
- if (it != itBegin)
- {
- *lines << beforeTraverseCornerPoints;
- lines->append(crossA);
- lines->append(crossB);
- *lines << afterTraverseCornerPoints;
- } else
- {
- lines->append(crossB);
- *lines << afterTraverseCornerPoints;
- trailingPoints << beforeTraverseCornerPoints << crossA ;
- }
- } else // doesn't cross R, line is just moving around in outside regions, so only need to add optimized point(s) at the boundary corner(s)
- {
- *lines << getOptimizedCornerPoints(prevRegion, currentRegion, prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin);
- }
- } else // segment does end in R, so we add previous point optimized and this point at original position
- {
- if (it == itBegin) // it is first point in curve and prevIt is last one. So save optimized point for adding it to the lineData in the end
- trailingPoints << getOptimizedPoint(prevRegion, prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin);
- else
- lines->append(getOptimizedPoint(prevRegion, prevIt->key, prevIt->value, it->key, it->value, keyMin, valueMax, keyMax, valueMin));
- lines->append(coordsToPixels(it->key, it->value));
- }
- } else // region didn't change
- {
- if (currentRegion == 5) // still in R, keep adding original points
- {
- lines->append(coordsToPixels(it->key, it->value));
- } else // still outside R, no need to add anything
- {
- // see how this is not doing anything? That's the main optimization...
- }
- }
- prevIt = it;
- prevRegion = currentRegion;
- ++it;
- }
- *lines << trailingPoints;
- }
- /*! \internal
- Called by \ref draw to generate points in pixel coordinates which represent the scatters of the
- curve. If a scatter skip is configured (\ref setScatterSkip), the returned points are accordingly
- sparser.
- Scatters that aren't visible in the current axis rect are optimized away.
- \a scatters will be filled with points in pixel coordinates, that can be drawn with \ref
- drawScatterPlot.
- \a dataRange specifies the beginning and ending data indices that will be taken into account for
- conversion.
- \a scatterWidth specifies the scatter width that will be used to later draw the scatters at pixel
- coordinates generated by this function. This is needed here to calculate an accordingly wider
- margin around the axis rect when performing the data point reduction.
- \see draw, drawScatterPlot
- */
- void QCPCurve::getScatters(QVector<QPointF> *scatters, const QCPDataRange &dataRange, double scatterWidth) const
- {
- if (!scatters) return;
- scatters->clear();
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
-
- QCPCurveDataContainer::const_iterator begin = mDataContainer->constBegin();
- QCPCurveDataContainer::const_iterator end = mDataContainer->constEnd();
- mDataContainer->limitIteratorsToDataRange(begin, end, dataRange);
- if (begin == end)
- return;
- const int scatterModulo = mScatterSkip+1;
- const bool doScatterSkip = mScatterSkip > 0;
- int endIndex = end-mDataContainer->constBegin();
-
- QCPRange keyRange = keyAxis->range();
- QCPRange valueRange = valueAxis->range();
- // extend range to include width of scatter symbols:
- keyRange.lower = keyAxis->pixelToCoord(keyAxis->coordToPixel(keyRange.lower)-scatterWidth*keyAxis->pixelOrientation());
- keyRange.upper = keyAxis->pixelToCoord(keyAxis->coordToPixel(keyRange.upper)+scatterWidth*keyAxis->pixelOrientation());
- valueRange.lower = valueAxis->pixelToCoord(valueAxis->coordToPixel(valueRange.lower)-scatterWidth*valueAxis->pixelOrientation());
- valueRange.upper = valueAxis->pixelToCoord(valueAxis->coordToPixel(valueRange.upper)+scatterWidth*valueAxis->pixelOrientation());
-
- QCPCurveDataContainer::const_iterator it = begin;
- int itIndex = begin-mDataContainer->constBegin();
- while (doScatterSkip && it != end && itIndex % scatterModulo != 0) // advance begin iterator to first non-skipped scatter
- {
- ++itIndex;
- ++it;
- }
- if (keyAxis->orientation() == Qt::Vertical)
- {
- while (it != end)
- {
- if (!qIsNaN(it->value) && keyRange.contains(it->key) && valueRange.contains(it->value))
- scatters->append(QPointF(valueAxis->coordToPixel(it->value), keyAxis->coordToPixel(it->key)));
-
- // advance iterator to next (non-skipped) data point:
- if (!doScatterSkip)
- ++it;
- else
- {
- itIndex += scatterModulo;
- if (itIndex < endIndex) // make sure we didn't jump over end
- it += scatterModulo;
- else
- {
- it = end;
- itIndex = endIndex;
- }
- }
- }
- } else
- {
- while (it != end)
- {
- if (!qIsNaN(it->value) && keyRange.contains(it->key) && valueRange.contains(it->value))
- scatters->append(QPointF(keyAxis->coordToPixel(it->key), valueAxis->coordToPixel(it->value)));
-
- // advance iterator to next (non-skipped) data point:
- if (!doScatterSkip)
- ++it;
- else
- {
- itIndex += scatterModulo;
- if (itIndex < endIndex) // make sure we didn't jump over end
- it += scatterModulo;
- else
- {
- it = end;
- itIndex = endIndex;
- }
- }
- }
- }
- }
- /*! \internal
- This function is part of the curve optimization algorithm of \ref getCurveLines.
- It returns the region of the given point (\a key, \a value) with respect to a rectangle defined
- by \a keyMin, \a keyMax, \a valueMin, and \a valueMax.
- The regions are enumerated from top to bottom (\a valueMin to \a valueMax) and left to right (\a
- keyMin to \a keyMax):
- <table style="width:10em; text-align:center">
- <tr><td>1</td><td>4</td><td>7</td></tr>
- <tr><td>2</td><td style="border:1px solid black">5</td><td>8</td></tr>
- <tr><td>3</td><td>6</td><td>9</td></tr>
- </table>
- With the rectangle being region 5, and the outer regions extending infinitely outwards. In the
- curve optimization algorithm, region 5 is considered to be the visible portion of the plot.
- */
- int QCPCurve::getRegion(double key, double value, double keyMin, double valueMax, double keyMax, double valueMin) const
- {
- if (key < keyMin) // region 123
- {
- if (value > valueMax)
- return 1;
- else if (value < valueMin)
- return 3;
- else
- return 2;
- } else if (key > keyMax) // region 789
- {
- if (value > valueMax)
- return 7;
- else if (value < valueMin)
- return 9;
- else
- return 8;
- } else // region 456
- {
- if (value > valueMax)
- return 4;
- else if (value < valueMin)
- return 6;
- else
- return 5;
- }
- }
- /*! \internal
-
- This function is part of the curve optimization algorithm of \ref getCurveLines.
-
- This method is used in case the current segment passes from inside the visible rect (region 5,
- see \ref getRegion) to any of the outer regions (\a otherRegion). The current segment is given by
- the line connecting (\a key, \a value) with (\a otherKey, \a otherValue).
-
- It returns the intersection point of the segment with the border of region 5.
-
- For this function it doesn't matter whether (\a key, \a value) is the point inside region 5 or
- whether it's (\a otherKey, \a otherValue), i.e. whether the segment is coming from region 5 or
- leaving it. It is important though that \a otherRegion correctly identifies the other region not
- equal to 5.
- */
- QPointF QCPCurve::getOptimizedPoint(int otherRegion, double otherKey, double otherValue, double key, double value, double keyMin, double valueMax, double keyMax, double valueMin) const
- {
- // The intersection point interpolation here is done in pixel coordinates, so we don't need to
- // differentiate between different axis scale types. Note that the nomenclature
- // top/left/bottom/right/min/max is with respect to the rect in plot coordinates, wich may be
- // different in pixel coordinates (horz/vert key axes, reversed ranges)
-
- const double keyMinPx = mKeyAxis->coordToPixel(keyMin);
- const double keyMaxPx = mKeyAxis->coordToPixel(keyMax);
- const double valueMinPx = mValueAxis->coordToPixel(valueMin);
- const double valueMaxPx = mValueAxis->coordToPixel(valueMax);
- const double otherValuePx = mValueAxis->coordToPixel(otherValue);
- const double valuePx = mValueAxis->coordToPixel(value);
- const double otherKeyPx = mKeyAxis->coordToPixel(otherKey);
- const double keyPx = mKeyAxis->coordToPixel(key);
- double intersectKeyPx = keyMinPx; // initial key just a fail-safe
- double intersectValuePx = valueMinPx; // initial value just a fail-safe
- switch (otherRegion)
- {
- case 1: // top and left edge
- {
- intersectValuePx = valueMaxPx;
- intersectKeyPx = otherKeyPx + (keyPx-otherKeyPx)/(valuePx-otherValuePx)*(intersectValuePx-otherValuePx);
- if (intersectKeyPx < qMin(keyMinPx, keyMaxPx) || intersectKeyPx > qMax(keyMinPx, keyMaxPx)) // check whether top edge is not intersected, then it must be left edge (qMin/qMax necessary since axes may be reversed)
- {
- intersectKeyPx = keyMinPx;
- intersectValuePx = otherValuePx + (valuePx-otherValuePx)/(keyPx-otherKeyPx)*(intersectKeyPx-otherKeyPx);
- }
- break;
- }
- case 2: // left edge
- {
- intersectKeyPx = keyMinPx;
- intersectValuePx = otherValuePx + (valuePx-otherValuePx)/(keyPx-otherKeyPx)*(intersectKeyPx-otherKeyPx);
- break;
- }
- case 3: // bottom and left edge
- {
- intersectValuePx = valueMinPx;
- intersectKeyPx = otherKeyPx + (keyPx-otherKeyPx)/(valuePx-otherValuePx)*(intersectValuePx-otherValuePx);
- if (intersectKeyPx < qMin(keyMinPx, keyMaxPx) || intersectKeyPx > qMax(keyMinPx, keyMaxPx)) // check whether bottom edge is not intersected, then it must be left edge (qMin/qMax necessary since axes may be reversed)
- {
- intersectKeyPx = keyMinPx;
- intersectValuePx = otherValuePx + (valuePx-otherValuePx)/(keyPx-otherKeyPx)*(intersectKeyPx-otherKeyPx);
- }
- break;
- }
- case 4: // top edge
- {
- intersectValuePx = valueMaxPx;
- intersectKeyPx = otherKeyPx + (keyPx-otherKeyPx)/(valuePx-otherValuePx)*(intersectValuePx-otherValuePx);
- break;
- }
- case 5:
- {
- break; // case 5 shouldn't happen for this function but we add it anyway to prevent potential discontinuity in branch table
- }
- case 6: // bottom edge
- {
- intersectValuePx = valueMinPx;
- intersectKeyPx = otherKeyPx + (keyPx-otherKeyPx)/(valuePx-otherValuePx)*(intersectValuePx-otherValuePx);
- break;
- }
- case 7: // top and right edge
- {
- intersectValuePx = valueMaxPx;
- intersectKeyPx = otherKeyPx + (keyPx-otherKeyPx)/(valuePx-otherValuePx)*(intersectValuePx-otherValuePx);
- if (intersectKeyPx < qMin(keyMinPx, keyMaxPx) || intersectKeyPx > qMax(keyMinPx, keyMaxPx)) // check whether top edge is not intersected, then it must be right edge (qMin/qMax necessary since axes may be reversed)
- {
- intersectKeyPx = keyMaxPx;
- intersectValuePx = otherValuePx + (valuePx-otherValuePx)/(keyPx-otherKeyPx)*(intersectKeyPx-otherKeyPx);
- }
- break;
- }
- case 8: // right edge
- {
- intersectKeyPx = keyMaxPx;
- intersectValuePx = otherValuePx + (valuePx-otherValuePx)/(keyPx-otherKeyPx)*(intersectKeyPx-otherKeyPx);
- break;
- }
- case 9: // bottom and right edge
- {
- intersectValuePx = valueMinPx;
- intersectKeyPx = otherKeyPx + (keyPx-otherKeyPx)/(valuePx-otherValuePx)*(intersectValuePx-otherValuePx);
- if (intersectKeyPx < qMin(keyMinPx, keyMaxPx) || intersectKeyPx > qMax(keyMinPx, keyMaxPx)) // check whether bottom edge is not intersected, then it must be right edge (qMin/qMax necessary since axes may be reversed)
- {
- intersectKeyPx = keyMaxPx;
- intersectValuePx = otherValuePx + (valuePx-otherValuePx)/(keyPx-otherKeyPx)*(intersectKeyPx-otherKeyPx);
- }
- break;
- }
- }
- if (mKeyAxis->orientation() == Qt::Horizontal)
- return QPointF(intersectKeyPx, intersectValuePx);
- else
- return QPointF(intersectValuePx, intersectKeyPx);
- }
- /*! \internal
-
- This function is part of the curve optimization algorithm of \ref getCurveLines.
-
- In situations where a single segment skips over multiple regions it might become necessary to add
- extra points at the corners of region 5 (see \ref getRegion) such that the optimized segment
- doesn't unintentionally cut through the visible area of the axis rect and create plot artifacts.
- This method provides these points that must be added, assuming the original segment doesn't
- start, end, or traverse region 5. (Corner points where region 5 is traversed are calculated by
- \ref getTraverseCornerPoints.)
-
- For example, consider a segment which directly goes from region 4 to 2 but originally is far out
- to the top left such that it doesn't cross region 5. Naively optimizing these points by
- projecting them on the top and left borders of region 5 will create a segment that surely crosses
- 5, creating a visual artifact in the plot. This method prevents this by providing extra points at
- the top left corner, making the optimized curve correctly pass from region 4 to 1 to 2 without
- traversing 5.
- */
- QVector<QPointF> QCPCurve::getOptimizedCornerPoints(int prevRegion, int currentRegion, double prevKey, double prevValue, double key, double value, double keyMin, double valueMax, double keyMax, double valueMin) const
- {
- QVector<QPointF> result;
- switch (prevRegion)
- {
- case 1:
- {
- switch (currentRegion)
- {
- case 2: { result << coordsToPixels(keyMin, valueMax); break; }
- case 4: { result << coordsToPixels(keyMin, valueMax); break; }
- case 3: { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMin, valueMin); break; }
- case 7: { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMax, valueMax); break; }
- case 6: { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMin, valueMin); result.append(result.last()); break; }
- case 8: { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMax, valueMax); result.append(result.last()); break; }
- case 9: { // in this case we need another distinction of cases: segment may pass below or above rect, requiring either bottom right or top left corner points
- if ((value-prevValue)/(key-prevKey)*(keyMin-key)+value < valueMin) // segment passes below R
- { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMin, valueMin); result.append(result.last()); result << coordsToPixels(keyMax, valueMin); }
- else
- { result << coordsToPixels(keyMin, valueMax) << coordsToPixels(keyMax, valueMax); result.append(result.last()); result << coordsToPixels(keyMax, valueMin); }
- break;
- }
- }
- break;
- }
- case 2:
- {
- switch (currentRegion)
- {
- case 1: { result << coordsToPixels(keyMin, valueMax); break; }
- case 3: { result << coordsToPixels(keyMin, valueMin); break; }
- case 4: { result << coordsToPixels(keyMin, valueMax); result.append(result.last()); break; }
- case 6: { result << coordsToPixels(keyMin, valueMin); result.append(result.last()); break; }
- case 7: { result << coordsToPixels(keyMin, valueMax); result.append(result.last()); result << coordsToPixels(keyMax, valueMax); break; }
- case 9: { result << coordsToPixels(keyMin, valueMin); result.append(result.last()); result << coordsToPixels(keyMax, valueMin); break; }
- }
- break;
- }
- case 3:
- {
- switch (currentRegion)
- {
- case 2: { result << coordsToPixels(keyMin, valueMin); break; }
- case 6: { result << coordsToPixels(keyMin, valueMin); break; }
- case 1: { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMin, valueMax); break; }
- case 9: { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMax, valueMin); break; }
- case 4: { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMin, valueMax); result.append(result.last()); break; }
- case 8: { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMax, valueMin); result.append(result.last()); break; }
- case 7: { // in this case we need another distinction of cases: segment may pass below or above rect, requiring either bottom right or top left corner points
- if ((value-prevValue)/(key-prevKey)*(keyMax-key)+value < valueMin) // segment passes below R
- { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMax, valueMin); result.append(result.last()); result << coordsToPixels(keyMax, valueMax); }
- else
- { result << coordsToPixels(keyMin, valueMin) << coordsToPixels(keyMin, valueMax); result.append(result.last()); result << coordsToPixels(keyMax, valueMax); }
- break;
- }
- }
- break;
- }
- case 4:
- {
- switch (currentRegion)
- {
- case 1: { result << coordsToPixels(keyMin, valueMax); break; }
- case 7: { result << coordsToPixels(keyMax, valueMax); break; }
- case 2: { result << coordsToPixels(keyMin, valueMax); result.append(result.last()); break; }
- case 8: { result << coordsToPixels(keyMax, valueMax); result.append(result.last()); break; }
- case 3: { result << coordsToPixels(keyMin, valueMax); result.append(result.last()); result << coordsToPixels(keyMin, valueMin); break; }
- case 9: { result << coordsToPixels(keyMax, valueMax); result.append(result.last()); result << coordsToPixels(keyMax, valueMin); break; }
- }
- break;
- }
- case 5:
- {
- switch (currentRegion)
- {
- case 1: { result << coordsToPixels(keyMin, valueMax); break; }
- case 7: { result << coordsToPixels(keyMax, valueMax); break; }
- case 9: { result << coordsToPixels(keyMax, valueMin); break; }
- case 3: { result << coordsToPixels(keyMin, valueMin); break; }
- }
- break;
- }
- case 6:
- {
- switch (currentRegion)
- {
- case 3: { result << coordsToPixels(keyMin, valueMin); break; }
- case 9: { result << coordsToPixels(keyMax, valueMin); break; }
- case 2: { result << coordsToPixels(keyMin, valueMin); result.append(result.last()); break; }
- case 8: { result << coordsToPixels(keyMax, valueMin); result.append(result.last()); break; }
- case 1: { result << coordsToPixels(keyMin, valueMin); result.append(result.last()); result << coordsToPixels(keyMin, valueMax); break; }
- case 7: { result << coordsToPixels(keyMax, valueMin); result.append(result.last()); result << coordsToPixels(keyMax, valueMax); break; }
- }
- break;
- }
- case 7:
- {
- switch (currentRegion)
- {
- case 4: { result << coordsToPixels(keyMax, valueMax); break; }
- case 8: { result << coordsToPixels(keyMax, valueMax); break; }
- case 1: { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMin, valueMax); break; }
- case 9: { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMax, valueMin); break; }
- case 2: { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMin, valueMax); result.append(result.last()); break; }
- case 6: { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMax, valueMin); result.append(result.last()); break; }
- case 3: { // in this case we need another distinction of cases: segment may pass below or above rect, requiring either bottom right or top left corner points
- if ((value-prevValue)/(key-prevKey)*(keyMax-key)+value < valueMin) // segment passes below R
- { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMax, valueMin); result.append(result.last()); result << coordsToPixels(keyMin, valueMin); }
- else
- { result << coordsToPixels(keyMax, valueMax) << coordsToPixels(keyMin, valueMax); result.append(result.last()); result << coordsToPixels(keyMin, valueMin); }
- break;
- }
- }
- break;
- }
- case 8:
- {
- switch (currentRegion)
- {
- case 7: { result << coordsToPixels(keyMax, valueMax); break; }
- case 9: { result << coordsToPixels(keyMax, valueMin); break; }
- case 4: { result << coordsToPixels(keyMax, valueMax); result.append(result.last()); break; }
- case 6: { result << coordsToPixels(keyMax, valueMin); result.append(result.last()); break; }
- case 1: { result << coordsToPixels(keyMax, valueMax); result.append(result.last()); result << coordsToPixels(keyMin, valueMax); break; }
- case 3: { result << coordsToPixels(keyMax, valueMin); result.append(result.last()); result << coordsToPixels(keyMin, valueMin); break; }
- }
- break;
- }
- case 9:
- {
- switch (currentRegion)
- {
- case 6: { result << coordsToPixels(keyMax, valueMin); break; }
- case 8: { result << coordsToPixels(keyMax, valueMin); break; }
- case 3: { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMin, valueMin); break; }
- case 7: { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMax, valueMax); break; }
- case 2: { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMin, valueMin); result.append(result.last()); break; }
- case 4: { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMax, valueMax); result.append(result.last()); break; }
- case 1: { // in this case we need another distinction of cases: segment may pass below or above rect, requiring either bottom right or top left corner points
- if ((value-prevValue)/(key-prevKey)*(keyMin-key)+value < valueMin) // segment passes below R
- { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMin, valueMin); result.append(result.last()); result << coordsToPixels(keyMin, valueMax); }
- else
- { result << coordsToPixels(keyMax, valueMin) << coordsToPixels(keyMax, valueMax); result.append(result.last()); result << coordsToPixels(keyMin, valueMax); }
- break;
- }
- }
- break;
- }
- }
- return result;
- }
- /*! \internal
-
- This function is part of the curve optimization algorithm of \ref getCurveLines.
-
- This method returns whether a segment going from \a prevRegion to \a currentRegion (see \ref
- getRegion) may traverse the visible region 5. This function assumes that neither \a prevRegion
- nor \a currentRegion is 5 itself.
-
- If this method returns false, the segment for sure doesn't pass region 5. If it returns true, the
- segment may or may not pass region 5 and a more fine-grained calculation must be used (\ref
- getTraverse).
- */
- bool QCPCurve::mayTraverse(int prevRegion, int currentRegion) const
- {
- switch (prevRegion)
- {
- case 1:
- {
- switch (currentRegion)
- {
- case 4:
- case 7:
- case 2:
- case 3: return false;
- default: return true;
- }
- }
- case 2:
- {
- switch (currentRegion)
- {
- case 1:
- case 3: return false;
- default: return true;
- }
- }
- case 3:
- {
- switch (currentRegion)
- {
- case 1:
- case 2:
- case 6:
- case 9: return false;
- default: return true;
- }
- }
- case 4:
- {
- switch (currentRegion)
- {
- case 1:
- case 7: return false;
- default: return true;
- }
- }
- case 5: return false; // should never occur
- case 6:
- {
- switch (currentRegion)
- {
- case 3:
- case 9: return false;
- default: return true;
- }
- }
- case 7:
- {
- switch (currentRegion)
- {
- case 1:
- case 4:
- case 8:
- case 9: return false;
- default: return true;
- }
- }
- case 8:
- {
- switch (currentRegion)
- {
- case 7:
- case 9: return false;
- default: return true;
- }
- }
- case 9:
- {
- switch (currentRegion)
- {
- case 3:
- case 6:
- case 8:
- case 7: return false;
- default: return true;
- }
- }
- default: return true;
- }
- }
- /*! \internal
-
- This function is part of the curve optimization algorithm of \ref getCurveLines.
-
- This method assumes that the \ref mayTraverse test has returned true, so there is a chance the
- segment defined by (\a prevKey, \a prevValue) and (\a key, \a value) goes through the visible
- region 5.
-
- The return value of this method indicates whether the segment actually traverses region 5 or not.
-
- If the segment traverses 5, the output parameters \a crossA and \a crossB indicate the entry and
- exit points of region 5. They will become the optimized points for that segment.
- */
- bool QCPCurve::getTraverse(double prevKey, double prevValue, double key, double value, double keyMin, double valueMax, double keyMax, double valueMin, QPointF &crossA, QPointF &crossB) const
- {
- // The intersection point interpolation here is done in pixel coordinates, so we don't need to
- // differentiate between different axis scale types. Note that the nomenclature
- // top/left/bottom/right/min/max is with respect to the rect in plot coordinates, wich may be
- // different in pixel coordinates (horz/vert key axes, reversed ranges)
-
- QList<QPointF> intersections;
- const double valueMinPx = mValueAxis->coordToPixel(valueMin);
- const double valueMaxPx = mValueAxis->coordToPixel(valueMax);
- const double keyMinPx = mKeyAxis->coordToPixel(keyMin);
- const double keyMaxPx = mKeyAxis->coordToPixel(keyMax);
- const double keyPx = mKeyAxis->coordToPixel(key);
- const double valuePx = mValueAxis->coordToPixel(value);
- const double prevKeyPx = mKeyAxis->coordToPixel(prevKey);
- const double prevValuePx = mValueAxis->coordToPixel(prevValue);
- if (qFuzzyIsNull(key-prevKey)) // line is parallel to value axis
- {
- // due to region filter in mayTraverse(), if line is parallel to value or key axis, region 5 is traversed here
- intersections.append(mKeyAxis->orientation() == Qt::Horizontal ? QPointF(keyPx, valueMinPx) : QPointF(valueMinPx, keyPx)); // direction will be taken care of at end of method
- intersections.append(mKeyAxis->orientation() == Qt::Horizontal ? QPointF(keyPx, valueMaxPx) : QPointF(valueMaxPx, keyPx));
- } else if (qFuzzyIsNull(value-prevValue)) // line is parallel to key axis
- {
- // due to region filter in mayTraverse(), if line is parallel to value or key axis, region 5 is traversed here
- intersections.append(mKeyAxis->orientation() == Qt::Horizontal ? QPointF(keyMinPx, valuePx) : QPointF(valuePx, keyMinPx)); // direction will be taken care of at end of method
- intersections.append(mKeyAxis->orientation() == Qt::Horizontal ? QPointF(keyMaxPx, valuePx) : QPointF(valuePx, keyMaxPx));
- } else // line is skewed
- {
- double gamma;
- double keyPerValuePx = (keyPx-prevKeyPx)/(valuePx-prevValuePx);
- // check top of rect:
- gamma = prevKeyPx + (valueMaxPx-prevValuePx)*keyPerValuePx;
- if (gamma >= qMin(keyMinPx, keyMaxPx) && gamma <= qMax(keyMinPx, keyMaxPx)) // qMin/qMax necessary since axes may be reversed
- intersections.append(mKeyAxis->orientation() == Qt::Horizontal ? QPointF(gamma, valueMaxPx) : QPointF(valueMaxPx, gamma));
- // check bottom of rect:
- gamma = prevKeyPx + (valueMinPx-prevValuePx)*keyPerValuePx;
- if (gamma >= qMin(keyMinPx, keyMaxPx) && gamma <= qMax(keyMinPx, keyMaxPx)) // qMin/qMax necessary since axes may be reversed
- intersections.append(mKeyAxis->orientation() == Qt::Horizontal ? QPointF(gamma, valueMinPx) : QPointF(valueMinPx, gamma));
- const double valuePerKeyPx = 1.0/keyPerValuePx;
- // check left of rect:
- gamma = prevValuePx + (keyMinPx-prevKeyPx)*valuePerKeyPx;
- if (gamma >= qMin(valueMinPx, valueMaxPx) && gamma <= qMax(valueMinPx, valueMaxPx)) // qMin/qMax necessary since axes may be reversed
- intersections.append(mKeyAxis->orientation() == Qt::Horizontal ? QPointF(keyMinPx, gamma) : QPointF(gamma, keyMinPx));
- // check right of rect:
- gamma = prevValuePx + (keyMaxPx-prevKeyPx)*valuePerKeyPx;
- if (gamma >= qMin(valueMinPx, valueMaxPx) && gamma <= qMax(valueMinPx, valueMaxPx)) // qMin/qMax necessary since axes may be reversed
- intersections.append(mKeyAxis->orientation() == Qt::Horizontal ? QPointF(keyMaxPx, gamma) : QPointF(gamma, keyMaxPx));
- }
-
- // handle cases where found points isn't exactly 2:
- if (intersections.size() > 2)
- {
- // line probably goes through corner of rect, and we got duplicate points there. single out the point pair with greatest distance in between:
- double distSqrMax = 0;
- QPointF pv1, pv2;
- for (int i=0; i<intersections.size()-1; ++i)
- {
- for (int k=i+1; k<intersections.size(); ++k)
- {
- QPointF distPoint = intersections.at(i)-intersections.at(k);
- double distSqr = distPoint.x()*distPoint.x()+distPoint.y()+distPoint.y();
- if (distSqr > distSqrMax)
- {
- pv1 = intersections.at(i);
- pv2 = intersections.at(k);
- distSqrMax = distSqr;
- }
- }
- }
- intersections = QList<QPointF>() << pv1 << pv2;
- } else if (intersections.size() != 2)
- {
- // one or even zero points found (shouldn't happen unless line perfectly tangent to corner), no need to draw segment
- return false;
- }
-
- // possibly re-sort points so optimized point segment has same direction as original segment:
- double xDelta = keyPx-prevKeyPx;
- double yDelta = valuePx-prevValuePx;
- if (mKeyAxis->orientation() != Qt::Horizontal)
- qSwap(xDelta, yDelta);
- if (xDelta*(intersections.at(1).x()-intersections.at(0).x()) + yDelta*(intersections.at(1).y()-intersections.at(0).y()) < 0) // scalar product of both segments < 0 -> opposite direction
- intersections.move(0, 1);
- crossA = intersections.at(0);
- crossB = intersections.at(1);
- return true;
- }
- /*! \internal
-
- This function is part of the curve optimization algorithm of \ref getCurveLines.
-
- This method assumes that the \ref getTraverse test has returned true, so the segment definitely
- traverses the visible region 5 when going from \a prevRegion to \a currentRegion.
-
- In certain situations it is not sufficient to merely generate the entry and exit points of the
- segment into/out of region 5, as \ref getTraverse provides. It may happen that a single segment, in
- addition to traversing region 5, skips another region outside of region 5, which makes it
- necessary to add an optimized corner point there (very similar to the job \ref
- getOptimizedCornerPoints does for segments that are completely in outside regions and don't
- traverse 5).
-
- As an example, consider a segment going from region 1 to region 6, traversing the lower left
- corner of region 5. In this configuration, the segment additionally crosses the border between
- region 1 and 2 before entering region 5. This makes it necessary to add an additional point in
- the top left corner, before adding the optimized traverse points. So in this case, the output
- parameter \a beforeTraverse will contain the top left corner point, and \a afterTraverse will be
- empty.
-
- In some cases, such as when going from region 1 to 9, it may even be necessary to add additional
- corner points before and after the traverse. Then both \a beforeTraverse and \a afterTraverse
- return the respective corner points.
- */
- void QCPCurve::getTraverseCornerPoints(int prevRegion, int currentRegion, double keyMin, double valueMax, double keyMax, double valueMin, QVector<QPointF> &beforeTraverse, QVector<QPointF> &afterTraverse) const
- {
- switch (prevRegion)
- {
- case 1:
- {
- switch (currentRegion)
- {
- case 6: { beforeTraverse << coordsToPixels(keyMin, valueMax); break; }
- case 9: { beforeTraverse << coordsToPixels(keyMin, valueMax); afterTraverse << coordsToPixels(keyMax, valueMin); break; }
- case 8: { beforeTraverse << coordsToPixels(keyMin, valueMax); break; }
- }
- break;
- }
- case 2:
- {
- switch (currentRegion)
- {
- case 7: { afterTraverse << coordsToPixels(keyMax, valueMax); break; }
- case 9: { afterTraverse << coordsToPixels(keyMax, valueMin); break; }
- }
- break;
- }
- case 3:
- {
- switch (currentRegion)
- {
- case 4: { beforeTraverse << coordsToPixels(keyMin, valueMin); break; }
- case 7: { beforeTraverse << coordsToPixels(keyMin, valueMin); afterTraverse << coordsToPixels(keyMax, valueMax); break; }
- case 8: { beforeTraverse << coordsToPixels(keyMin, valueMin); break; }
- }
- break;
- }
- case 4:
- {
- switch (currentRegion)
- {
- case 3: { afterTraverse << coordsToPixels(keyMin, valueMin); break; }
- case 9: { afterTraverse << coordsToPixels(keyMax, valueMin); break; }
- }
- break;
- }
- case 5: { break; } // shouldn't happen because this method only handles full traverses
- case 6:
- {
- switch (currentRegion)
- {
- case 1: { afterTraverse << coordsToPixels(keyMin, valueMax); break; }
- case 7: { afterTraverse << coordsToPixels(keyMax, valueMax); break; }
- }
- break;
- }
- case 7:
- {
- switch (currentRegion)
- {
- case 2: { beforeTraverse << coordsToPixels(keyMax, valueMax); break; }
- case 3: { beforeTraverse << coordsToPixels(keyMax, valueMax); afterTraverse << coordsToPixels(keyMin, valueMin); break; }
- case 6: { beforeTraverse << coordsToPixels(keyMax, valueMax); break; }
- }
- break;
- }
- case 8:
- {
- switch (currentRegion)
- {
- case 1: { afterTraverse << coordsToPixels(keyMin, valueMax); break; }
- case 3: { afterTraverse << coordsToPixels(keyMin, valueMin); break; }
- }
- break;
- }
- case 9:
- {
- switch (currentRegion)
- {
- case 2: { beforeTraverse << coordsToPixels(keyMax, valueMin); break; }
- case 1: { beforeTraverse << coordsToPixels(keyMax, valueMin); afterTraverse << coordsToPixels(keyMin, valueMax); break; }
- case 4: { beforeTraverse << coordsToPixels(keyMax, valueMin); break; }
- }
- break;
- }
- }
- }
- /*! \internal
-
- Calculates the (minimum) distance (in pixels) the curve's representation has from the given \a
- pixelPoint in pixels. This is used to determine whether the curve was clicked or not, e.g. in
- \ref selectTest. The closest data point to \a pixelPoint is returned in \a closestData. Note that
- if the curve has a line representation, the returned distance may be smaller than the distance to
- the \a closestData point, since the distance to the curve line is also taken into account.
-
- If either the curve has no data or if the line style is \ref lsNone and the scatter style's shape
- is \ref QCPScatterStyle::ssNone (i.e. there is no visual representation of the curve), returns
- -1.0.
- */
- double QCPCurve::pointDistance(const QPointF &pixelPoint, QCPCurveDataContainer::const_iterator &closestData) const
- {
- closestData = mDataContainer->constEnd();
- if (mDataContainer->isEmpty())
- return -1.0;
- if (mLineStyle == lsNone && mScatterStyle.isNone())
- return -1.0;
-
- if (mDataContainer->size() == 1)
- {
- QPointF dataPoint = coordsToPixels(mDataContainer->constBegin()->key, mDataContainer->constBegin()->value);
- closestData = mDataContainer->constBegin();
- return QCPVector2D(dataPoint-pixelPoint).length();
- }
-
- // calculate minimum distances to curve data points and find closestData iterator:
- double minDistSqr = std::numeric_limits<double>::max();
- // iterate over found data points and then choose the one with the shortest distance to pos:
- QCPCurveDataContainer::const_iterator begin = mDataContainer->constBegin();
- QCPCurveDataContainer::const_iterator end = mDataContainer->constEnd();
- for (QCPCurveDataContainer::const_iterator it=begin; it!=end; ++it)
- {
- const double currentDistSqr = QCPVector2D(coordsToPixels(it->key, it->value)-pixelPoint).lengthSquared();
- if (currentDistSqr < minDistSqr)
- {
- minDistSqr = currentDistSqr;
- closestData = it;
- }
- }
-
- // calculate distance to line if there is one (if so, will probably be smaller than distance to closest data point):
- if (mLineStyle != lsNone)
- {
- QVector<QPointF> lines;
- getCurveLines(&lines, QCPDataRange(0, dataCount()), mParentPlot->selectionTolerance()*1.2); // optimized lines outside axis rect shouldn't respond to clicks at the edge, so use 1.2*tolerance as pen width
- for (int i=0; i<lines.size()-1; ++i)
- {
- double currentDistSqr = QCPVector2D(pixelPoint).distanceSquaredToLine(lines.at(i), lines.at(i+1));
- if (currentDistSqr < minDistSqr)
- minDistSqr = currentDistSqr;
- }
- }
-
- return qSqrt(minDistSqr);
- }
- /* end of 'src/plottables/plottable-curve.cpp' */
- /* including file 'src/plottables/plottable-bars.cpp', size 43512 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPBarsGroup
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPBarsGroup
- \brief Groups multiple QCPBars together so they appear side by side
-
- \image html QCPBarsGroup.png
-
- When showing multiple QCPBars in one plot which have bars at identical keys, it may be desirable
- to have them appearing next to each other at each key. This is what adding the respective QCPBars
- plottables to a QCPBarsGroup achieves. (An alternative approach is to stack them on top of each
- other, see \ref QCPBars::moveAbove.)
-
- \section qcpbarsgroup-usage Usage
-
- To add a QCPBars plottable to the group, create a new group and then add the respective bars
- intances:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbarsgroup-creation
- Alternatively to appending to the group like shown above, you can also set the group on the
- QCPBars plottable via \ref QCPBars::setBarsGroup.
-
- The spacing between the bars can be configured via \ref setSpacingType and \ref setSpacing. The
- bars in this group appear in the plot in the order they were appended. To insert a bars plottable
- at a certain index position, or to reposition a bars plottable which is already in the group, use
- \ref insert.
-
- To remove specific bars from the group, use either \ref remove or call \ref
- QCPBars::setBarsGroup "QCPBars::setBarsGroup(0)" on the respective bars plottable.
-
- To clear the entire group, call \ref clear, or simply delete the group.
-
- \section qcpbarsgroup-example Example
-
- The image above is generated with the following code:
- \snippet documentation/doc-image-generator/mainwindow.cpp qcpbarsgroup-example
- */
- /* start of documentation of inline functions */
- /*! \fn QList<QCPBars*> QCPBarsGroup::bars() const
-
- Returns all bars currently in this group.
-
- \see bars(int index)
- */
- /*! \fn int QCPBarsGroup::size() const
-
- Returns the number of QCPBars plottables that are part of this group.
-
- */
- /*! \fn bool QCPBarsGroup::isEmpty() const
-
- Returns whether this bars group is empty.
-
- \see size
- */
- /*! \fn bool QCPBarsGroup::contains(QCPBars *bars)
-
- Returns whether the specified \a bars plottable is part of this group.
-
- */
- /* end of documentation of inline functions */
- /*!
- Constructs a new bars group for the specified QCustomPlot instance.
- */
- QCPBarsGroup::QCPBarsGroup(QCustomPlot *parentPlot) :
- QObject(parentPlot),
- mParentPlot(parentPlot),
- mSpacingType(stAbsolute),
- mSpacing(4)
- {
- }
- QCPBarsGroup::~QCPBarsGroup()
- {
- clear();
- }
- /*!
- Sets how the spacing between adjacent bars is interpreted. See \ref SpacingType.
-
- The actual spacing can then be specified with \ref setSpacing.
- \see setSpacing
- */
- void QCPBarsGroup::setSpacingType(SpacingType spacingType)
- {
- mSpacingType = spacingType;
- }
- /*!
- Sets the spacing between adjacent bars. What the number passed as \a spacing actually means, is
- defined by the current \ref SpacingType, which can be set with \ref setSpacingType.
- \see setSpacingType
- */
- void QCPBarsGroup::setSpacing(double spacing)
- {
- mSpacing = spacing;
- }
- /*!
- Returns the QCPBars instance with the specified \a index in this group. If no such QCPBars
- exists, returns 0.
- \see bars(), size
- */
- QCPBars *QCPBarsGroup::bars(int index) const
- {
- if (index >= 0 && index < mBars.size())
- {
- return mBars.at(index);
- } else
- {
- qDebug() << Q_FUNC_INFO << "index out of bounds:" << index;
- return 0;
- }
- }
- /*!
- Removes all QCPBars plottables from this group.
- \see isEmpty
- */
- void QCPBarsGroup::clear()
- {
- foreach (QCPBars *bars, mBars) // since foreach takes a copy, removing bars in the loop is okay
- bars->setBarsGroup(0); // removes itself via removeBars
- }
- /*!
- Adds the specified \a bars plottable to this group. Alternatively, you can also use \ref
- QCPBars::setBarsGroup on the \a bars instance.
- \see insert, remove
- */
- void QCPBarsGroup::append(QCPBars *bars)
- {
- if (!bars)
- {
- qDebug() << Q_FUNC_INFO << "bars is 0";
- return;
- }
-
- if (!mBars.contains(bars))
- bars->setBarsGroup(this);
- else
- qDebug() << Q_FUNC_INFO << "bars plottable is already in this bars group:" << reinterpret_cast<quintptr>(bars);
- }
- /*!
- Inserts the specified \a bars plottable into this group at the specified index position \a i.
- This gives you full control over the ordering of the bars.
-
- \a bars may already be part of this group. In that case, \a bars is just moved to the new index
- position.
- \see append, remove
- */
- void QCPBarsGroup::insert(int i, QCPBars *bars)
- {
- if (!bars)
- {
- qDebug() << Q_FUNC_INFO << "bars is 0";
- return;
- }
-
- // first append to bars list normally:
- if (!mBars.contains(bars))
- bars->setBarsGroup(this);
- // then move to according position:
- mBars.move(mBars.indexOf(bars), qBound(0, i, mBars.size()-1));
- }
- /*!
- Removes the specified \a bars plottable from this group.
-
- \see contains, clear
- */
- void QCPBarsGroup::remove(QCPBars *bars)
- {
- if (!bars)
- {
- qDebug() << Q_FUNC_INFO << "bars is 0";
- return;
- }
-
- if (mBars.contains(bars))
- bars->setBarsGroup(0);
- else
- qDebug() << Q_FUNC_INFO << "bars plottable is not in this bars group:" << reinterpret_cast<quintptr>(bars);
- }
- /*! \internal
-
- Adds the specified \a bars to the internal mBars list of bars. This method does not change the
- barsGroup property on \a bars.
-
- \see unregisterBars
- */
- void QCPBarsGroup::registerBars(QCPBars *bars)
- {
- if (!mBars.contains(bars))
- mBars.append(bars);
- }
- /*! \internal
-
- Removes the specified \a bars from the internal mBars list of bars. This method does not change
- the barsGroup property on \a bars.
-
- \see registerBars
- */
- void QCPBarsGroup::unregisterBars(QCPBars *bars)
- {
- mBars.removeOne(bars);
- }
- /*! \internal
-
- Returns the pixel offset in the key dimension the specified \a bars plottable should have at the
- given key coordinate \a keyCoord. The offset is relative to the pixel position of the key
- coordinate \a keyCoord.
- */
- double QCPBarsGroup::keyPixelOffset(const QCPBars *bars, double keyCoord)
- {
- // find list of all base bars in case some mBars are stacked:
- QList<const QCPBars*> baseBars;
- foreach (const QCPBars *b, mBars)
- {
- while (b->barBelow())
- b = b->barBelow();
- if (!baseBars.contains(b))
- baseBars.append(b);
- }
- // find base bar this "bars" is stacked on:
- const QCPBars *thisBase = bars;
- while (thisBase->barBelow())
- thisBase = thisBase->barBelow();
-
- // determine key pixel offset of this base bars considering all other base bars in this barsgroup:
- double result = 0;
- int index = baseBars.indexOf(thisBase);
- if (index >= 0)
- {
- if (baseBars.size() % 2 == 1 && index == (baseBars.size()-1)/2) // is center bar (int division on purpose)
- {
- return result;
- } else
- {
- double lowerPixelWidth, upperPixelWidth;
- int startIndex;
- int dir = (index <= (baseBars.size()-1)/2) ? -1 : 1; // if bar is to lower keys of center, dir is negative
- if (baseBars.size() % 2 == 0) // even number of bars
- {
- startIndex = baseBars.size()/2 + (dir < 0 ? -1 : 0);
- result += getPixelSpacing(baseBars.at(startIndex), keyCoord)*0.5; // half of middle spacing
- } else // uneven number of bars
- {
- startIndex = (baseBars.size()-1)/2+dir;
- baseBars.at((baseBars.size()-1)/2)->getPixelWidth(keyCoord, lowerPixelWidth, upperPixelWidth);
- result += qAbs(upperPixelWidth-lowerPixelWidth)*0.5; // half of center bar
- result += getPixelSpacing(baseBars.at((baseBars.size()-1)/2), keyCoord); // center bar spacing
- }
- for (int i = startIndex; i != index; i += dir) // add widths and spacings of bars in between center and our bars
- {
- baseBars.at(i)->getPixelWidth(keyCoord, lowerPixelWidth, upperPixelWidth);
- result += qAbs(upperPixelWidth-lowerPixelWidth);
- result += getPixelSpacing(baseBars.at(i), keyCoord);
- }
- // finally half of our bars width:
- baseBars.at(index)->getPixelWidth(keyCoord, lowerPixelWidth, upperPixelWidth);
- result += qAbs(upperPixelWidth-lowerPixelWidth)*0.5;
- // correct sign of result depending on orientation and direction of key axis:
- result *= dir*thisBase->keyAxis()->pixelOrientation();
- }
- }
- return result;
- }
- /*! \internal
-
- Returns the spacing in pixels which is between this \a bars and the following one, both at the
- key coordinate \a keyCoord.
-
- \note Typically the returned value doesn't depend on \a bars or \a keyCoord. \a bars is only
- needed to get access to the key axis transformation and axis rect for the modes \ref
- stAxisRectRatio and \ref stPlotCoords. The \a keyCoord is only relevant for spacings given in
- \ref stPlotCoords on a logarithmic axis.
- */
- double QCPBarsGroup::getPixelSpacing(const QCPBars *bars, double keyCoord)
- {
- switch (mSpacingType)
- {
- case stAbsolute:
- {
- return mSpacing;
- }
- case stAxisRectRatio:
- {
- if (bars->keyAxis()->orientation() == Qt::Horizontal)
- return bars->keyAxis()->axisRect()->width()*mSpacing;
- else
- return bars->keyAxis()->axisRect()->height()*mSpacing;
- }
- case stPlotCoords:
- {
- double keyPixel = bars->keyAxis()->coordToPixel(keyCoord);
- return qAbs(bars->keyAxis()->coordToPixel(keyCoord+mSpacing)-keyPixel);
- }
- }
- return 0;
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPBarsData
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPBarsData
- \brief Holds the data of one single data point (one bar) for QCPBars.
-
- The stored data is:
- \li \a key: coordinate on the key axis of this bar (this is the \a mainKey and the \a sortKey)
- \li \a value: height coordinate on the value axis of this bar (this is the \a mainValue)
-
- The container for storing multiple data points is \ref QCPBarsDataContainer. It is a typedef for
- \ref QCPDataContainer with \ref QCPBarsData as the DataType template parameter. See the
- documentation there for an explanation regarding the data type's generic methods.
-
- \see QCPBarsDataContainer
- */
- /* start documentation of inline functions */
- /*! \fn double QCPBarsData::sortKey() const
-
- Returns the \a key member of this data point.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn static QCPBarsData QCPBarsData::fromSortKey(double sortKey)
-
- Returns a data point with the specified \a sortKey. All other members are set to zero.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn static static bool QCPBarsData::sortKeyIsMainKey()
-
- Since the member \a key is both the data point key coordinate and the data ordering parameter,
- this method returns true.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn double QCPBarsData::mainKey() const
-
- Returns the \a key member of this data point.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn double QCPBarsData::mainValue() const
-
- Returns the \a value member of this data point.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn QCPRange QCPBarsData::valueRange() const
-
- Returns a QCPRange with both lower and upper boundary set to \a value of this data point.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /* end documentation of inline functions */
- /*!
- Constructs a bar data point with key and value set to zero.
- */
- QCPBarsData::QCPBarsData() :
- key(0),
- value(0)
- {
- }
- /*!
- Constructs a bar data point with the specified \a key and \a value.
- */
- QCPBarsData::QCPBarsData(double key, double value) :
- key(key),
- value(value)
- {
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPBars
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPBars
- \brief A plottable representing a bar chart in a plot.
- \image html QCPBars.png
-
- To plot data, assign it with the \ref setData or \ref addData functions.
-
- \section qcpbars-appearance Changing the appearance
-
- The appearance of the bars is determined by the pen and the brush (\ref setPen, \ref setBrush).
- The width of the individual bars can be controlled with \ref setWidthType and \ref setWidth.
-
- Bar charts are stackable. This means, two QCPBars plottables can be placed on top of each other
- (see \ref QCPBars::moveAbove). So when two bars are at the same key position, they will appear
- stacked.
-
- If you would like to group multiple QCPBars plottables together so they appear side by side as
- shown below, use QCPBarsGroup.
-
- \image html QCPBarsGroup.png
-
- \section qcpbars-usage Usage
-
- Like all data representing objects in QCustomPlot, the QCPBars is a plottable
- (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies
- (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.)
-
- Usually, you first create an instance:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-creation-1
- which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot instance takes
- ownership of the plottable, so do not delete it manually but use QCustomPlot::removePlottable() instead.
- The newly created plottable can be modified, e.g.:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-creation-2
- */
- /* start of documentation of inline functions */
- /*! \fn QSharedPointer<QCPBarsDataContainer> QCPBars::data() const
-
- Returns a shared pointer to the internal data storage of type \ref QCPBarsDataContainer. You may
- use it to directly manipulate the data, which may be more convenient and faster than using the
- regular \ref setData or \ref addData methods.
- */
- /*! \fn QCPBars *QCPBars::barBelow() const
- Returns the bars plottable that is directly below this bars plottable.
- If there is no such plottable, returns 0.
-
- \see barAbove, moveBelow, moveAbove
- */
- /*! \fn QCPBars *QCPBars::barAbove() const
- Returns the bars plottable that is directly above this bars plottable.
- If there is no such plottable, returns 0.
-
- \see barBelow, moveBelow, moveAbove
- */
- /* end of documentation of inline functions */
- /*!
- Constructs a bar chart which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value
- axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have
- the same orientation. If either of these restrictions is violated, a corresponding message is
- printed to the debug output (qDebug), the construction is not aborted, though.
-
- The created QCPBars is automatically registered with the QCustomPlot instance inferred from \a
- keyAxis. This QCustomPlot instance takes ownership of the QCPBars, so do not delete it manually
- but use QCustomPlot::removePlottable() instead.
- */
- QCPBars::QCPBars(QCPAxis *keyAxis, QCPAxis *valueAxis) :
- QCPAbstractPlottable1D<QCPBarsData>(keyAxis, valueAxis),
- mWidth(0.75),
- mWidthType(wtPlotCoords),
- mBarsGroup(0),
- mBaseValue(0),
- mStackingGap(0)
- {
- // modify inherited properties from abstract plottable:
- mPen.setColor(Qt::blue);
- mPen.setStyle(Qt::SolidLine);
- mBrush.setColor(QColor(40, 50, 255, 30));
- mBrush.setStyle(Qt::SolidPattern);
- mSelectionDecorator->setBrush(QBrush(QColor(160, 160, 255)));
- }
- QCPBars::~QCPBars()
- {
- setBarsGroup(0);
- if (mBarBelow || mBarAbove)
- connectBars(mBarBelow.data(), mBarAbove.data()); // take this bar out of any stacking
- }
- /*! \overload
-
- Replaces the current data container with the provided \a data container.
-
- Since a QSharedPointer is used, multiple QCPBars may share the same data container safely.
- Modifying the data in the container will then affect all bars that share the container. Sharing
- can be achieved by simply exchanging the data containers wrapped in shared pointers:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-datasharing-1
-
- If you do not wish to share containers, but create a copy from an existing container, rather use
- the \ref QCPDataContainer<DataType>::set method on the bar's data container directly:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpbars-datasharing-2
-
- \see addData
- */
- void QCPBars::setData(QSharedPointer<QCPBarsDataContainer> data)
- {
- mDataContainer = data;
- }
- /*! \overload
-
- Replaces the current data with the provided points in \a keys and \a values. The provided
- vectors should have equal length. Else, the number of added points will be the size of the
- smallest vector.
-
- If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
- can set \a alreadySorted to true, to improve performance by saving a sorting run.
-
- \see addData
- */
- void QCPBars::setData(const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
- {
- mDataContainer->clear();
- addData(keys, values, alreadySorted);
- }
- /*!
- Sets the width of the bars.
- How the number passed as \a width is interpreted (e.g. screen pixels, plot coordinates,...),
- depends on the currently set width type, see \ref setWidthType and \ref WidthType.
- */
- void QCPBars::setWidth(double width)
- {
- mWidth = width;
- }
- /*!
- Sets how the width of the bars is defined. See the documentation of \ref WidthType for an
- explanation of the possible values for \a widthType.
-
- The default value is \ref wtPlotCoords.
-
- \see setWidth
- */
- void QCPBars::setWidthType(QCPBars::WidthType widthType)
- {
- mWidthType = widthType;
- }
- /*!
- Sets to which QCPBarsGroup this QCPBars instance belongs to. Alternatively, you can also use \ref
- QCPBarsGroup::append.
-
- To remove this QCPBars from any group, set \a barsGroup to 0.
- */
- void QCPBars::setBarsGroup(QCPBarsGroup *barsGroup)
- {
- // deregister at old group:
- if (mBarsGroup)
- mBarsGroup->unregisterBars(this);
- mBarsGroup = barsGroup;
- // register at new group:
- if (mBarsGroup)
- mBarsGroup->registerBars(this);
- }
- /*!
- Sets the base value of this bars plottable.
- The base value defines where on the value coordinate the bars start. How far the bars extend from
- the base value is given by their individual value data. For example, if the base value is set to
- 1, a bar with data value 2 will have its lowest point at value coordinate 1 and highest point at
- 3.
-
- For stacked bars, only the base value of the bottom-most QCPBars has meaning.
-
- The default base value is 0.
- */
- void QCPBars::setBaseValue(double baseValue)
- {
- mBaseValue = baseValue;
- }
- /*!
- If this bars plottable is stacked on top of another bars plottable (\ref moveAbove), this method
- allows specifying a distance in \a pixels, by which the drawn bar rectangles will be separated by
- the bars below it.
- */
- void QCPBars::setStackingGap(double pixels)
- {
- mStackingGap = pixels;
- }
- /*! \overload
-
- Adds the provided points in \a keys and \a values to the current data. The provided vectors
- should have equal length. Else, the number of added points will be the size of the smallest
- vector.
-
- If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
- can set \a alreadySorted to true, to improve performance by saving a sorting run.
-
- Alternatively, you can also access and modify the data directly via the \ref data method, which
- returns a pointer to the internal data container.
- */
- void QCPBars::addData(const QVector<double> &keys, const QVector<double> &values, bool alreadySorted)
- {
- if (keys.size() != values.size())
- qDebug() << Q_FUNC_INFO << "keys and values have different sizes:" << keys.size() << values.size();
- const int n = qMin(keys.size(), values.size());
- QVector<QCPBarsData> tempData(n);
- QVector<QCPBarsData>::iterator it = tempData.begin();
- const QVector<QCPBarsData>::iterator itEnd = tempData.end();
- int i = 0;
- while (it != itEnd)
- {
- it->key = keys[i];
- it->value = values[i];
- ++it;
- ++i;
- }
- mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write
- }
- /*! \overload
- Adds the provided data point as \a key and \a value to the current data.
-
- Alternatively, you can also access and modify the data directly via the \ref data method, which
- returns a pointer to the internal data container.
- */
- void QCPBars::addData(double key, double value)
- {
- mDataContainer->add(QCPBarsData(key, value));
- }
- /*!
- Moves this bars plottable below \a bars. In other words, the bars of this plottable will appear
- below the bars of \a bars. The move target \a bars must use the same key and value axis as this
- plottable.
-
- Inserting into and removing from existing bar stacking is handled gracefully. If \a bars already
- has a bars object below itself, this bars object is inserted between the two. If this bars object
- is already between two other bars, the two other bars will be stacked on top of each other after
- the operation.
-
- To remove this bars plottable from any stacking, set \a bars to 0.
-
- \see moveBelow, barAbove, barBelow
- */
- void QCPBars::moveBelow(QCPBars *bars)
- {
- if (bars == this) return;
- if (bars && (bars->keyAxis() != mKeyAxis.data() || bars->valueAxis() != mValueAxis.data()))
- {
- qDebug() << Q_FUNC_INFO << "passed QCPBars* doesn't have same key and value axis as this QCPBars";
- return;
- }
- // remove from stacking:
- connectBars(mBarBelow.data(), mBarAbove.data()); // Note: also works if one (or both) of them is 0
- // if new bar given, insert this bar below it:
- if (bars)
- {
- if (bars->mBarBelow)
- connectBars(bars->mBarBelow.data(), this);
- connectBars(this, bars);
- }
- }
- /*!
- Moves this bars plottable above \a bars. In other words, the bars of this plottable will appear
- above the bars of \a bars. The move target \a bars must use the same key and value axis as this
- plottable.
-
- Inserting into and removing from existing bar stacking is handled gracefully. If \a bars already
- has a bars object above itself, this bars object is inserted between the two. If this bars object
- is already between two other bars, the two other bars will be stacked on top of each other after
- the operation.
-
- To remove this bars plottable from any stacking, set \a bars to 0.
-
- \see moveBelow, barBelow, barAbove
- */
- void QCPBars::moveAbove(QCPBars *bars)
- {
- if (bars == this) return;
- if (bars && (bars->keyAxis() != mKeyAxis.data() || bars->valueAxis() != mValueAxis.data()))
- {
- qDebug() << Q_FUNC_INFO << "passed QCPBars* doesn't have same key and value axis as this QCPBars";
- return;
- }
- // remove from stacking:
- connectBars(mBarBelow.data(), mBarAbove.data()); // Note: also works if one (or both) of them is 0
- // if new bar given, insert this bar above it:
- if (bars)
- {
- if (bars->mBarAbove)
- connectBars(this, bars->mBarAbove.data());
- connectBars(bars, this);
- }
- }
- /*!
- \copydoc QCPPlottableInterface1D::selectTestRect
- */
- QCPDataSelection QCPBars::selectTestRect(const QRectF &rect, bool onlySelectable) const
- {
- QCPDataSelection result;
- if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
- return result;
- if (!mKeyAxis || !mValueAxis)
- return result;
-
- QCPBarsDataContainer::const_iterator visibleBegin, visibleEnd;
- getVisibleDataBounds(visibleBegin, visibleEnd);
-
- for (QCPBarsDataContainer::const_iterator it=visibleBegin; it!=visibleEnd; ++it)
- {
- if (rect.intersects(getBarRect(it->key, it->value)))
- result.addDataRange(QCPDataRange(it-mDataContainer->constBegin(), it-mDataContainer->constBegin()+1), false);
- }
- result.simplify();
- return result;
- }
- /* inherits documentation from base class */
- double QCPBars::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- Q_UNUSED(details)
- if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
- return -1;
- if (!mKeyAxis || !mValueAxis)
- return -1;
-
- if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()))
- {
- // get visible data range:
- QCPBarsDataContainer::const_iterator visibleBegin, visibleEnd;
- getVisibleDataBounds(visibleBegin, visibleEnd);
- for (QCPBarsDataContainer::const_iterator it=visibleBegin; it!=visibleEnd; ++it)
- {
- if (getBarRect(it->key, it->value).contains(pos))
- {
- if (details)
- {
- int pointIndex = it-mDataContainer->constBegin();
- details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex+1)));
- }
- return mParentPlot->selectionTolerance()*0.99;
- }
- }
- }
- return -1;
- }
- /* inherits documentation from base class */
- QCPRange QCPBars::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
- {
- /* Note: If this QCPBars uses absolute pixels as width (or is in a QCPBarsGroup with spacing in
- absolute pixels), using this method to adapt the key axis range to fit the bars into the
- currently visible axis range will not work perfectly. Because in the moment the axis range is
- changed to the new range, the fixed pixel widths/spacings will represent different coordinate
- spans than before, which in turn would require a different key range to perfectly fit, and so on.
- The only solution would be to iteratively approach the perfect fitting axis range, but the
- mismatch isn't large enough in most applications, to warrant this here. If a user does need a
- better fit, he should call the corresponding axis rescale multiple times in a row.
- */
- QCPRange range;
- range = mDataContainer->keyRange(foundRange, inSignDomain);
-
- // determine exact range of bars by including bar width and barsgroup offset:
- if (foundRange && mKeyAxis)
- {
- double lowerPixelWidth, upperPixelWidth, keyPixel;
- // lower range bound:
- getPixelWidth(range.lower, lowerPixelWidth, upperPixelWidth);
- keyPixel = mKeyAxis.data()->coordToPixel(range.lower) + lowerPixelWidth;
- if (mBarsGroup)
- keyPixel += mBarsGroup->keyPixelOffset(this, range.lower);
- const double lowerCorrected = mKeyAxis.data()->pixelToCoord(keyPixel);
- if (!qIsNaN(lowerCorrected) && qIsFinite(lowerCorrected) && range.lower > lowerCorrected)
- range.lower = lowerCorrected;
- // upper range bound:
- getPixelWidth(range.upper, lowerPixelWidth, upperPixelWidth);
- keyPixel = mKeyAxis.data()->coordToPixel(range.upper) + upperPixelWidth;
- if (mBarsGroup)
- keyPixel += mBarsGroup->keyPixelOffset(this, range.upper);
- const double upperCorrected = mKeyAxis.data()->pixelToCoord(keyPixel);
- if (!qIsNaN(upperCorrected) && qIsFinite(upperCorrected) && range.upper < upperCorrected)
- range.upper = upperCorrected;
- }
- return range;
- }
- /* inherits documentation from base class */
- QCPRange QCPBars::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
- {
- // Note: can't simply use mDataContainer->valueRange here because we need to
- // take into account bar base value and possible stacking of multiple bars
- QCPRange range;
- range.lower = mBaseValue;
- range.upper = mBaseValue;
- bool haveLower = true; // set to true, because baseValue should always be visible in bar charts
- bool haveUpper = true; // set to true, because baseValue should always be visible in bar charts
- QCPBarsDataContainer::const_iterator itBegin = mDataContainer->constBegin();
- QCPBarsDataContainer::const_iterator itEnd = mDataContainer->constEnd();
- if (inKeyRange != QCPRange())
- {
- itBegin = mDataContainer->findBegin(inKeyRange.lower);
- itEnd = mDataContainer->findEnd(inKeyRange.upper);
- }
- for (QCPBarsDataContainer::const_iterator it = itBegin; it != itEnd; ++it)
- {
- const double current = it->value + getStackedBaseValue(it->key, it->value >= 0);
- if (qIsNaN(current)) continue;
- if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
- {
- if (current < range.lower || !haveLower)
- {
- range.lower = current;
- haveLower = true;
- }
- if (current > range.upper || !haveUpper)
- {
- range.upper = current;
- haveUpper = true;
- }
- }
- }
-
- foundRange = true; // return true because bar charts always have the 0-line visible
- return range;
- }
- /* inherits documentation from base class */
- QPointF QCPBars::dataPixelPosition(int index) const
- {
- if (index >= 0 && index < mDataContainer->size())
- {
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QPointF(); }
-
- const QCPDataContainer<QCPBarsData>::const_iterator it = mDataContainer->constBegin()+index;
- const double valuePixel = valueAxis->coordToPixel(getStackedBaseValue(it->key, it->value >= 0) + it->value);
- const double keyPixel = keyAxis->coordToPixel(it->key) + (mBarsGroup ? mBarsGroup->keyPixelOffset(this, it->key) : 0);
- if (keyAxis->orientation() == Qt::Horizontal)
- return QPointF(keyPixel, valuePixel);
- else
- return QPointF(valuePixel, keyPixel);
- } else
- {
- qDebug() << Q_FUNC_INFO << "Index out of bounds" << index;
- return QPointF();
- }
- }
- /* inherits documentation from base class */
- void QCPBars::draw(QCPPainter *painter)
- {
- if (!mKeyAxis || !mValueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
- if (mDataContainer->isEmpty()) return;
-
- QCPBarsDataContainer::const_iterator visibleBegin, visibleEnd;
- getVisibleDataBounds(visibleBegin, visibleEnd);
-
- // loop over and draw segments of unselected/selected data:
- QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
- getDataSegments(selectedSegments, unselectedSegments);
- allSegments << unselectedSegments << selectedSegments;
- for (int i=0; i<allSegments.size(); ++i)
- {
- bool isSelectedSegment = i >= unselectedSegments.size();
- QCPBarsDataContainer::const_iterator begin = visibleBegin;
- QCPBarsDataContainer::const_iterator end = visibleEnd;
- mDataContainer->limitIteratorsToDataRange(begin, end, allSegments.at(i));
- if (begin == end)
- continue;
-
- for (QCPBarsDataContainer::const_iterator it=begin; it!=end; ++it)
- {
- // check data validity if flag set:
- #ifdef QCUSTOMPLOT_CHECK_DATA
- if (QCP::isInvalidData(it->key, it->value))
- qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "of drawn range invalid." << "Plottable name:" << name();
- #endif
- // draw bar:
- if (isSelectedSegment && mSelectionDecorator)
- {
- mSelectionDecorator->applyBrush(painter);
- mSelectionDecorator->applyPen(painter);
- } else
- {
- painter->setBrush(mBrush);
- painter->setPen(mPen);
- }
- applyDefaultAntialiasingHint(painter);
- painter->drawPolygon(getBarRect(it->key, it->value));
- }
- }
-
- // draw other selection decoration that isn't just line/scatter pens and brushes:
- if (mSelectionDecorator)
- mSelectionDecorator->drawDecoration(painter, selection());
- }
- /* inherits documentation from base class */
- void QCPBars::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
- {
- // draw filled rect:
- applyDefaultAntialiasingHint(painter);
- painter->setBrush(mBrush);
- painter->setPen(mPen);
- QRectF r = QRectF(0, 0, rect.width()*0.67, rect.height()*0.67);
- r.moveCenter(rect.center());
- painter->drawRect(r);
- }
- /*! \internal
-
- called by \ref draw to determine which data (key) range is visible at the current key axis range
- setting, so only that needs to be processed. It also takes into account the bar width.
-
- \a begin returns an iterator to the lowest data point that needs to be taken into account when
- plotting. Note that in order to get a clean plot all the way to the edge of the axis rect, \a
- lower may still be just outside the visible range.
-
- \a end returns an iterator one higher than the highest visible data point. Same as before, \a end
- may also lie just outside of the visible range.
-
- if the plottable contains no data, both \a begin and \a end point to constEnd.
- */
- void QCPBars::getVisibleDataBounds(QCPBarsDataContainer::const_iterator &begin, QCPBarsDataContainer::const_iterator &end) const
- {
- if (!mKeyAxis)
- {
- qDebug() << Q_FUNC_INFO << "invalid key axis";
- begin = mDataContainer->constEnd();
- end = mDataContainer->constEnd();
- return;
- }
- if (mDataContainer->isEmpty())
- {
- begin = mDataContainer->constEnd();
- end = mDataContainer->constEnd();
- return;
- }
-
- // get visible data range as QMap iterators
- begin = mDataContainer->findBegin(mKeyAxis.data()->range().lower);
- end = mDataContainer->findEnd(mKeyAxis.data()->range().upper);
- double lowerPixelBound = mKeyAxis.data()->coordToPixel(mKeyAxis.data()->range().lower);
- double upperPixelBound = mKeyAxis.data()->coordToPixel(mKeyAxis.data()->range().upper);
- bool isVisible = false;
- // walk left from begin to find lower bar that actually is completely outside visible pixel range:
- QCPBarsDataContainer::const_iterator it = begin;
- while (it != mDataContainer->constBegin())
- {
- --it;
- const QRectF barRect = getBarRect(it->key, it->value);
- if (mKeyAxis.data()->orientation() == Qt::Horizontal)
- isVisible = ((!mKeyAxis.data()->rangeReversed() && barRect.right() >= lowerPixelBound) || (mKeyAxis.data()->rangeReversed() && barRect.left() <= lowerPixelBound));
- else // keyaxis is vertical
- isVisible = ((!mKeyAxis.data()->rangeReversed() && barRect.top() <= lowerPixelBound) || (mKeyAxis.data()->rangeReversed() && barRect.bottom() >= lowerPixelBound));
- if (isVisible)
- begin = it;
- else
- break;
- }
- // walk right from ubound to find upper bar that actually is completely outside visible pixel range:
- it = end;
- while (it != mDataContainer->constEnd())
- {
- const QRectF barRect = getBarRect(it->key, it->value);
- if (mKeyAxis.data()->orientation() == Qt::Horizontal)
- isVisible = ((!mKeyAxis.data()->rangeReversed() && barRect.left() <= upperPixelBound) || (mKeyAxis.data()->rangeReversed() && barRect.right() >= upperPixelBound));
- else // keyaxis is vertical
- isVisible = ((!mKeyAxis.data()->rangeReversed() && barRect.bottom() >= upperPixelBound) || (mKeyAxis.data()->rangeReversed() && barRect.top() <= upperPixelBound));
- if (isVisible)
- end = it+1;
- else
- break;
- ++it;
- }
- }
- /*! \internal
-
- Returns the rect in pixel coordinates of a single bar with the specified \a key and \a value. The
- rect is shifted according to the bar stacking (see \ref moveAbove) and base value (see \ref
- setBaseValue), and to have non-overlapping border lines with the bars stacked below.
- */
- QRectF QCPBars::getBarRect(double key, double value) const
- {
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QRectF(); }
-
- double lowerPixelWidth, upperPixelWidth;
- getPixelWidth(key, lowerPixelWidth, upperPixelWidth);
- double base = getStackedBaseValue(key, value >= 0);
- double basePixel = valueAxis->coordToPixel(base);
- double valuePixel = valueAxis->coordToPixel(base+value);
- double keyPixel = keyAxis->coordToPixel(key);
- if (mBarsGroup)
- keyPixel += mBarsGroup->keyPixelOffset(this, key);
- double bottomOffset = (mBarBelow && mPen != Qt::NoPen ? 1 : 0)*(mPen.isCosmetic() ? 1 : mPen.widthF());
- bottomOffset += mBarBelow ? mStackingGap : 0;
- bottomOffset *= (value<0 ? -1 : 1)*valueAxis->pixelOrientation();
- if (qAbs(valuePixel-basePixel) <= qAbs(bottomOffset))
- bottomOffset = valuePixel-basePixel;
- if (keyAxis->orientation() == Qt::Horizontal)
- {
- return QRectF(QPointF(keyPixel+lowerPixelWidth, valuePixel), QPointF(keyPixel+upperPixelWidth, basePixel+bottomOffset)).normalized();
- } else
- {
- return QRectF(QPointF(basePixel+bottomOffset, keyPixel+lowerPixelWidth), QPointF(valuePixel, keyPixel+upperPixelWidth)).normalized();
- }
- }
- /*! \internal
-
- This function is used to determine the width of the bar at coordinate \a key, according to the
- specified width (\ref setWidth) and width type (\ref setWidthType).
-
- The output parameters \a lower and \a upper return the number of pixels the bar extends to lower
- and higher keys, relative to the \a key coordinate (so with a non-reversed horizontal axis, \a
- lower is negative and \a upper positive).
- */
- void QCPBars::getPixelWidth(double key, double &lower, double &upper) const
- {
- lower = 0;
- upper = 0;
- switch (mWidthType)
- {
- case wtAbsolute:
- {
- upper = mWidth*0.5*mKeyAxis.data()->pixelOrientation();
- lower = -upper;
- break;
- }
- case wtAxisRectRatio:
- {
- if (mKeyAxis && mKeyAxis.data()->axisRect())
- {
- if (mKeyAxis.data()->orientation() == Qt::Horizontal)
- upper = mKeyAxis.data()->axisRect()->width()*mWidth*0.5*mKeyAxis.data()->pixelOrientation();
- else
- upper = mKeyAxis.data()->axisRect()->height()*mWidth*0.5*mKeyAxis.data()->pixelOrientation();
- lower = -upper;
- } else
- qDebug() << Q_FUNC_INFO << "No key axis or axis rect defined";
- break;
- }
- case wtPlotCoords:
- {
- if (mKeyAxis)
- {
- double keyPixel = mKeyAxis.data()->coordToPixel(key);
- upper = mKeyAxis.data()->coordToPixel(key+mWidth*0.5)-keyPixel;
- lower = mKeyAxis.data()->coordToPixel(key-mWidth*0.5)-keyPixel;
- // no need to qSwap(lower, higher) when range reversed, because higher/lower are gained by
- // coordinate transform which includes range direction
- } else
- qDebug() << Q_FUNC_INFO << "No key axis defined";
- break;
- }
- }
- }
- /*! \internal
-
- This function is called to find at which value to start drawing the base of a bar at \a key, when
- it is stacked on top of another QCPBars (e.g. with \ref moveAbove).
-
- positive and negative bars are separated per stack (positive are stacked above baseValue upwards,
- negative are stacked below baseValue downwards). This can be indicated with \a positive. So if the
- bar for which we need the base value is negative, set \a positive to false.
- */
- double QCPBars::getStackedBaseValue(double key, bool positive) const
- {
- if (mBarBelow)
- {
- double max = 0; // don't initialize with mBaseValue here because only base value of bottom-most bar has meaning in a bar stack
- // find bars of mBarBelow that are approximately at key and find largest one:
- double epsilon = qAbs(key)*(sizeof(key)==4 ? 1e-6 : 1e-14); // should be safe even when changed to use float at some point
- if (key == 0)
- epsilon = (sizeof(key)==4 ? 1e-6 : 1e-14);
- QCPBarsDataContainer::const_iterator it = mBarBelow.data()->mDataContainer->findBegin(key-epsilon);
- QCPBarsDataContainer::const_iterator itEnd = mBarBelow.data()->mDataContainer->findEnd(key+epsilon);
- while (it != itEnd)
- {
- if (it->key > key-epsilon && it->key < key+epsilon)
- {
- if ((positive && it->value > max) ||
- (!positive && it->value < max))
- max = it->value;
- }
- ++it;
- }
- // recurse down the bar-stack to find the total height:
- return max + mBarBelow.data()->getStackedBaseValue(key, positive);
- } else
- return mBaseValue;
- }
- /*! \internal
- Connects \a below and \a above to each other via their mBarAbove/mBarBelow properties. The bar(s)
- currently above lower and below upper will become disconnected to lower/upper.
-
- If lower is zero, upper will be disconnected at the bottom.
- If upper is zero, lower will be disconnected at the top.
- */
- void QCPBars::connectBars(QCPBars *lower, QCPBars *upper)
- {
- if (!lower && !upper) return;
-
- if (!lower) // disconnect upper at bottom
- {
- // disconnect old bar below upper:
- if (upper->mBarBelow && upper->mBarBelow.data()->mBarAbove.data() == upper)
- upper->mBarBelow.data()->mBarAbove = 0;
- upper->mBarBelow = 0;
- } else if (!upper) // disconnect lower at top
- {
- // disconnect old bar above lower:
- if (lower->mBarAbove && lower->mBarAbove.data()->mBarBelow.data() == lower)
- lower->mBarAbove.data()->mBarBelow = 0;
- lower->mBarAbove = 0;
- } else // connect lower and upper
- {
- // disconnect old bar above lower:
- if (lower->mBarAbove && lower->mBarAbove.data()->mBarBelow.data() == lower)
- lower->mBarAbove.data()->mBarBelow = 0;
- // disconnect old bar below upper:
- if (upper->mBarBelow && upper->mBarBelow.data()->mBarAbove.data() == upper)
- upper->mBarBelow.data()->mBarAbove = 0;
- lower->mBarAbove = upper;
- upper->mBarBelow = lower;
- }
- }
- /* end of 'src/plottables/plottable-bars.cpp' */
- /* including file 'src/plottables/plottable-statisticalbox.cpp', size 28622 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPStatisticalBoxData
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPStatisticalBoxData
- \brief Holds the data of one single data point for QCPStatisticalBox.
-
- The stored data is:
-
- \li \a key: coordinate on the key axis of this data point (this is the \a mainKey and the \a sortKey)
-
- \li \a minimum: the position of the lower whisker, typically the minimum measurement of the
- sample that's not considered an outlier.
-
- \li \a lowerQuartile: the lower end of the box. The lower and the upper quartiles are the two
- statistical quartiles around the median of the sample, they should contain 50% of the sample
- data.
-
- \li \a median: the value of the median mark inside the quartile box. The median separates the
- sample data in half (50% of the sample data is below/above the median). (This is the \a mainValue)
-
- \li \a upperQuartile: the upper end of the box. The lower and the upper quartiles are the two
- statistical quartiles around the median of the sample, they should contain 50% of the sample
- data.
-
- \li \a maximum: the position of the upper whisker, typically the maximum measurement of the
- sample that's not considered an outlier.
-
- \li \a outliers: a QVector of outlier values that will be drawn as scatter points at the \a key
- coordinate of this data point (see \ref QCPStatisticalBox::setOutlierStyle)
-
- The container for storing multiple data points is \ref QCPStatisticalBoxDataContainer. It is a
- typedef for \ref QCPDataContainer with \ref QCPStatisticalBoxData as the DataType template
- parameter. See the documentation there for an explanation regarding the data type's generic
- methods.
-
- \see QCPStatisticalBoxDataContainer
- */
- /* start documentation of inline functions */
- /*! \fn double QCPStatisticalBoxData::sortKey() const
-
- Returns the \a key member of this data point.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn static QCPStatisticalBoxData QCPStatisticalBoxData::fromSortKey(double sortKey)
-
- Returns a data point with the specified \a sortKey. All other members are set to zero.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn static static bool QCPStatisticalBoxData::sortKeyIsMainKey()
-
- Since the member \a key is both the data point key coordinate and the data ordering parameter,
- this method returns true.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn double QCPStatisticalBoxData::mainKey() const
-
- Returns the \a key member of this data point.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn double QCPStatisticalBoxData::mainValue() const
-
- Returns the \a median member of this data point.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn QCPRange QCPStatisticalBoxData::valueRange() const
-
- Returns a QCPRange spanning from the \a minimum to the \a maximum member of this statistical box
- data point, possibly further expanded by outliers.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /* end documentation of inline functions */
- /*!
- Constructs a data point with key and all values set to zero.
- */
- QCPStatisticalBoxData::QCPStatisticalBoxData() :
- key(0),
- minimum(0),
- lowerQuartile(0),
- median(0),
- upperQuartile(0),
- maximum(0)
- {
- }
- /*!
- Constructs a data point with the specified \a key, \a minimum, \a lowerQuartile, \a median, \a
- upperQuartile, \a maximum and optionally a number of \a outliers.
- */
- QCPStatisticalBoxData::QCPStatisticalBoxData(double key, double minimum, double lowerQuartile, double median, double upperQuartile, double maximum, const QVector<double> &outliers) :
- key(key),
- minimum(minimum),
- lowerQuartile(lowerQuartile),
- median(median),
- upperQuartile(upperQuartile),
- maximum(maximum),
- outliers(outliers)
- {
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPStatisticalBox
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPStatisticalBox
- \brief A plottable representing a single statistical box in a plot.
- \image html QCPStatisticalBox.png
-
- To plot data, assign it with the \ref setData or \ref addData functions. Alternatively, you can
- also access and modify the data via the \ref data method, which returns a pointer to the internal
- \ref QCPStatisticalBoxDataContainer.
-
- Additionally each data point can itself have a list of outliers, drawn as scatter points at the
- key coordinate of the respective statistical box data point. They can either be set by using the
- respective \ref addData(double,double,double,double,double,double,const QVector<double>&)
- "addData" method or accessing the individual data points through \ref data, and setting the
- <tt>QVector<double> outliers</tt> of the data points directly.
-
- \section qcpstatisticalbox-appearance Changing the appearance
-
- The appearance of each data point box, ranging from the lower to the upper quartile, is
- controlled via \ref setPen and \ref setBrush. You may change the width of the boxes with \ref
- setWidth in plot coordinates.
- Each data point's visual representation also consists of two whiskers. Whiskers are the lines
- which reach from the upper quartile to the maximum, and from the lower quartile to the minimum.
- The appearance of the whiskers can be modified with: \ref setWhiskerPen, \ref setWhiskerBarPen,
- \ref setWhiskerWidth. The whisker width is the width of the bar perpendicular to the whisker at
- the top (for maximum) and bottom (for minimum). If the whisker pen is changed, make sure to set
- the \c capStyle to \c Qt::FlatCap. Otherwise the backbone line might exceed the whisker bars by a
- few pixels due to the pen cap being not perfectly flat.
-
- The median indicator line inside the box has its own pen, \ref setMedianPen.
-
- The outlier data points are drawn as normal scatter points. Their look can be controlled with
- \ref setOutlierStyle
-
- \section qcpstatisticalbox-usage Usage
-
- Like all data representing objects in QCustomPlot, the QCPStatisticalBox is a plottable
- (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies
- (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.)
-
- Usually, you first create an instance:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpstatisticalbox-creation-1
- which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot instance takes
- ownership of the plottable, so do not delete it manually but use QCustomPlot::removePlottable() instead.
- The newly created plottable can be modified, e.g.:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpstatisticalbox-creation-2
- */
- /* start documentation of inline functions */
- /*! \fn QSharedPointer<QCPStatisticalBoxDataContainer> QCPStatisticalBox::data() const
-
- Returns a shared pointer to the internal data storage of type \ref
- QCPStatisticalBoxDataContainer. You may use it to directly manipulate the data, which may be more
- convenient and faster than using the regular \ref setData or \ref addData methods.
- */
- /* end documentation of inline functions */
- /*!
- Constructs a statistical box which uses \a keyAxis as its key axis ("x") and \a valueAxis as its
- value axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and
- not have the same orientation. If either of these restrictions is violated, a corresponding
- message is printed to the debug output (qDebug), the construction is not aborted, though.
-
- The created QCPStatisticalBox is automatically registered with the QCustomPlot instance inferred
- from \a keyAxis. This QCustomPlot instance takes ownership of the QCPStatisticalBox, so do not
- delete it manually but use QCustomPlot::removePlottable() instead.
- */
- QCPStatisticalBox::QCPStatisticalBox(QCPAxis *keyAxis, QCPAxis *valueAxis) :
- QCPAbstractPlottable1D<QCPStatisticalBoxData>(keyAxis, valueAxis),
- mWidth(0.5),
- mWhiskerWidth(0.2),
- mWhiskerPen(Qt::black, 0, Qt::DashLine, Qt::FlatCap),
- mWhiskerBarPen(Qt::black),
- mWhiskerAntialiased(false),
- mMedianPen(Qt::black, 3, Qt::SolidLine, Qt::FlatCap),
- mOutlierStyle(QCPScatterStyle::ssCircle, Qt::blue, 6)
- {
- setPen(QPen(Qt::black));
- setBrush(Qt::NoBrush);
- }
- /*! \overload
-
- Replaces the current data container with the provided \a data container.
-
- Since a QSharedPointer is used, multiple QCPStatisticalBoxes may share the same data container
- safely. Modifying the data in the container will then affect all statistical boxes that share the
- container. Sharing can be achieved by simply exchanging the data containers wrapped in shared
- pointers:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpstatisticalbox-datasharing-1
-
- If you do not wish to share containers, but create a copy from an existing container, rather use
- the \ref QCPDataContainer<DataType>::set method on the statistical box data container directly:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpstatisticalbox-datasharing-2
-
- \see addData
- */
- void QCPStatisticalBox::setData(QSharedPointer<QCPStatisticalBoxDataContainer> data)
- {
- mDataContainer = data;
- }
- /*! \overload
-
- Replaces the current data with the provided points in \a keys, \a minimum, \a lowerQuartile, \a
- median, \a upperQuartile and \a maximum. The provided vectors should have equal length. Else, the
- number of added points will be the size of the smallest vector.
-
- If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
- can set \a alreadySorted to true, to improve performance by saving a sorting run.
-
- \see addData
- */
- void QCPStatisticalBox::setData(const QVector<double> &keys, const QVector<double> &minimum, const QVector<double> &lowerQuartile, const QVector<double> &median, const QVector<double> &upperQuartile, const QVector<double> &maximum, bool alreadySorted)
- {
- mDataContainer->clear();
- addData(keys, minimum, lowerQuartile, median, upperQuartile, maximum, alreadySorted);
- }
- /*!
- Sets the width of the boxes in key coordinates.
-
- \see setWhiskerWidth
- */
- void QCPStatisticalBox::setWidth(double width)
- {
- mWidth = width;
- }
- /*!
- Sets the width of the whiskers in key coordinates.
-
- Whiskers are the lines which reach from the upper quartile to the maximum, and from the lower
- quartile to the minimum.
-
- \see setWidth
- */
- void QCPStatisticalBox::setWhiskerWidth(double width)
- {
- mWhiskerWidth = width;
- }
- /*!
- Sets the pen used for drawing the whisker backbone.
-
- Whiskers are the lines which reach from the upper quartile to the maximum, and from the lower
- quartile to the minimum.
-
- Make sure to set the \c capStyle of the passed \a pen to \c Qt::FlatCap. Otherwise the backbone
- line might exceed the whisker bars by a few pixels due to the pen cap being not perfectly flat.
-
- \see setWhiskerBarPen
- */
- void QCPStatisticalBox::setWhiskerPen(const QPen &pen)
- {
- mWhiskerPen = pen;
- }
- /*!
- Sets the pen used for drawing the whisker bars. Those are the lines parallel to the key axis at
- each end of the whisker backbone.
-
- Whiskers are the lines which reach from the upper quartile to the maximum, and from the lower
- quartile to the minimum.
-
- \see setWhiskerPen
- */
- void QCPStatisticalBox::setWhiskerBarPen(const QPen &pen)
- {
- mWhiskerBarPen = pen;
- }
- /*!
- Sets whether the statistical boxes whiskers are drawn with antialiasing or not.
- Note that antialiasing settings may be overridden by QCustomPlot::setAntialiasedElements and
- QCustomPlot::setNotAntialiasedElements.
- */
- void QCPStatisticalBox::setWhiskerAntialiased(bool enabled)
- {
- mWhiskerAntialiased = enabled;
- }
- /*!
- Sets the pen used for drawing the median indicator line inside the statistical boxes.
- */
- void QCPStatisticalBox::setMedianPen(const QPen &pen)
- {
- mMedianPen = pen;
- }
- /*!
- Sets the appearance of the outlier data points.
- Outliers can be specified with the method
- \ref addData(double key, double minimum, double lowerQuartile, double median, double upperQuartile, double maximum, const QVector<double> &outliers)
- */
- void QCPStatisticalBox::setOutlierStyle(const QCPScatterStyle &style)
- {
- mOutlierStyle = style;
- }
- /*! \overload
-
- Adds the provided points in \a keys, \a minimum, \a lowerQuartile, \a median, \a upperQuartile and
- \a maximum to the current data. The provided vectors should have equal length. Else, the number
- of added points will be the size of the smallest vector.
-
- If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
- can set \a alreadySorted to true, to improve performance by saving a sorting run.
-
- Alternatively, you can also access and modify the data directly via the \ref data method, which
- returns a pointer to the internal data container.
- */
- void QCPStatisticalBox::addData(const QVector<double> &keys, const QVector<double> &minimum, const QVector<double> &lowerQuartile, const QVector<double> &median, const QVector<double> &upperQuartile, const QVector<double> &maximum, bool alreadySorted)
- {
- if (keys.size() != minimum.size() || minimum.size() != lowerQuartile.size() || lowerQuartile.size() != median.size() ||
- median.size() != upperQuartile.size() || upperQuartile.size() != maximum.size() || maximum.size() != keys.size())
- qDebug() << Q_FUNC_INFO << "keys, minimum, lowerQuartile, median, upperQuartile, maximum have different sizes:"
- << keys.size() << minimum.size() << lowerQuartile.size() << median.size() << upperQuartile.size() << maximum.size();
- const int n = qMin(keys.size(), qMin(minimum.size(), qMin(lowerQuartile.size(), qMin(median.size(), qMin(upperQuartile.size(), maximum.size())))));
- QVector<QCPStatisticalBoxData> tempData(n);
- QVector<QCPStatisticalBoxData>::iterator it = tempData.begin();
- const QVector<QCPStatisticalBoxData>::iterator itEnd = tempData.end();
- int i = 0;
- while (it != itEnd)
- {
- it->key = keys[i];
- it->minimum = minimum[i];
- it->lowerQuartile = lowerQuartile[i];
- it->median = median[i];
- it->upperQuartile = upperQuartile[i];
- it->maximum = maximum[i];
- ++it;
- ++i;
- }
- mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write
- }
- /*! \overload
-
- Adds the provided data point as \a key, \a minimum, \a lowerQuartile, \a median, \a upperQuartile
- and \a maximum to the current data.
-
- Alternatively, you can also access and modify the data directly via the \ref data method, which
- returns a pointer to the internal data container.
- */
- void QCPStatisticalBox::addData(double key, double minimum, double lowerQuartile, double median, double upperQuartile, double maximum, const QVector<double> &outliers)
- {
- mDataContainer->add(QCPStatisticalBoxData(key, minimum, lowerQuartile, median, upperQuartile, maximum, outliers));
- }
- /*!
- \copydoc QCPPlottableInterface1D::selectTestRect
- */
- QCPDataSelection QCPStatisticalBox::selectTestRect(const QRectF &rect, bool onlySelectable) const
- {
- QCPDataSelection result;
- if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
- return result;
- if (!mKeyAxis || !mValueAxis)
- return result;
-
- QCPStatisticalBoxDataContainer::const_iterator visibleBegin, visibleEnd;
- getVisibleDataBounds(visibleBegin, visibleEnd);
-
- for (QCPStatisticalBoxDataContainer::const_iterator it=visibleBegin; it!=visibleEnd; ++it)
- {
- if (rect.intersects(getQuartileBox(it)))
- result.addDataRange(QCPDataRange(it-mDataContainer->constBegin(), it-mDataContainer->constBegin()+1), false);
- }
- result.simplify();
- return result;
- }
- /* inherits documentation from base class */
- double QCPStatisticalBox::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- Q_UNUSED(details)
- if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
- return -1;
- if (!mKeyAxis || !mValueAxis)
- return -1;
-
- if (mKeyAxis->axisRect()->rect().contains(pos.toPoint()))
- {
- // get visible data range:
- QCPStatisticalBoxDataContainer::const_iterator visibleBegin, visibleEnd;
- QCPStatisticalBoxDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd();
- getVisibleDataBounds(visibleBegin, visibleEnd);
- double minDistSqr = std::numeric_limits<double>::max();
- for (QCPStatisticalBoxDataContainer::const_iterator it=visibleBegin; it!=visibleEnd; ++it)
- {
- if (getQuartileBox(it).contains(pos)) // quartile box
- {
- double currentDistSqr = mParentPlot->selectionTolerance()*0.99 * mParentPlot->selectionTolerance()*0.99;
- if (currentDistSqr < minDistSqr)
- {
- minDistSqr = currentDistSqr;
- closestDataPoint = it;
- }
- } else // whiskers
- {
- const QVector<QLineF> whiskerBackbones(getWhiskerBackboneLines(it));
- for (int i=0; i<whiskerBackbones.size(); ++i)
- {
- double currentDistSqr = QCPVector2D(pos).distanceSquaredToLine(whiskerBackbones.at(i));
- if (currentDistSqr < minDistSqr)
- {
- minDistSqr = currentDistSqr;
- closestDataPoint = it;
- }
- }
- }
- }
- if (details)
- {
- int pointIndex = closestDataPoint-mDataContainer->constBegin();
- details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex+1)));
- }
- return qSqrt(minDistSqr);
- }
- return -1;
- }
- /* inherits documentation from base class */
- QCPRange QCPStatisticalBox::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
- {
- QCPRange range = mDataContainer->keyRange(foundRange, inSignDomain);
- // determine exact range by including width of bars/flags:
- if (foundRange)
- {
- if (inSignDomain != QCP::sdPositive || range.lower-mWidth*0.5 > 0)
- range.lower -= mWidth*0.5;
- if (inSignDomain != QCP::sdNegative || range.upper+mWidth*0.5 < 0)
- range.upper += mWidth*0.5;
- }
- return range;
- }
- /* inherits documentation from base class */
- QCPRange QCPStatisticalBox::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
- {
- return mDataContainer->valueRange(foundRange, inSignDomain, inKeyRange);
- }
- /* inherits documentation from base class */
- void QCPStatisticalBox::draw(QCPPainter *painter)
- {
- if (mDataContainer->isEmpty()) return;
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
-
- QCPStatisticalBoxDataContainer::const_iterator visibleBegin, visibleEnd;
- getVisibleDataBounds(visibleBegin, visibleEnd);
-
- // loop over and draw segments of unselected/selected data:
- QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
- getDataSegments(selectedSegments, unselectedSegments);
- allSegments << unselectedSegments << selectedSegments;
- for (int i=0; i<allSegments.size(); ++i)
- {
- bool isSelectedSegment = i >= unselectedSegments.size();
- QCPStatisticalBoxDataContainer::const_iterator begin = visibleBegin;
- QCPStatisticalBoxDataContainer::const_iterator end = visibleEnd;
- mDataContainer->limitIteratorsToDataRange(begin, end, allSegments.at(i));
- if (begin == end)
- continue;
-
- for (QCPStatisticalBoxDataContainer::const_iterator it=begin; it!=end; ++it)
- {
- // check data validity if flag set:
- # ifdef QCUSTOMPLOT_CHECK_DATA
- if (QCP::isInvalidData(it->key, it->minimum) ||
- QCP::isInvalidData(it->lowerQuartile, it->median) ||
- QCP::isInvalidData(it->upperQuartile, it->maximum))
- qDebug() << Q_FUNC_INFO << "Data point at" << it->key << "of drawn range has invalid data." << "Plottable name:" << name();
- for (int i=0; i<it->outliers.size(); ++i)
- if (QCP::isInvalidData(it->outliers.at(i)))
- qDebug() << Q_FUNC_INFO << "Data point outlier at" << it->key << "of drawn range invalid." << "Plottable name:" << name();
- # endif
-
- if (isSelectedSegment && mSelectionDecorator)
- {
- mSelectionDecorator->applyPen(painter);
- mSelectionDecorator->applyBrush(painter);
- } else
- {
- painter->setPen(mPen);
- painter->setBrush(mBrush);
- }
- QCPScatterStyle finalOutlierStyle = mOutlierStyle;
- if (isSelectedSegment && mSelectionDecorator)
- finalOutlierStyle = mSelectionDecorator->getFinalScatterStyle(mOutlierStyle);
- drawStatisticalBox(painter, it, finalOutlierStyle);
- }
- }
-
- // draw other selection decoration that isn't just line/scatter pens and brushes:
- if (mSelectionDecorator)
- mSelectionDecorator->drawDecoration(painter, selection());
- }
- /* inherits documentation from base class */
- void QCPStatisticalBox::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
- {
- // draw filled rect:
- applyDefaultAntialiasingHint(painter);
- painter->setPen(mPen);
- painter->setBrush(mBrush);
- QRectF r = QRectF(0, 0, rect.width()*0.67, rect.height()*0.67);
- r.moveCenter(rect.center());
- painter->drawRect(r);
- }
- /*!
- Draws the graphical representation of a single statistical box with the data given by the
- iterator \a it with the provided \a painter.
- If the statistical box has a set of outlier data points, they are drawn with \a outlierStyle.
- \see getQuartileBox, getWhiskerBackboneLines, getWhiskerBarLines
- */
- void QCPStatisticalBox::drawStatisticalBox(QCPPainter *painter, QCPStatisticalBoxDataContainer::const_iterator it, const QCPScatterStyle &outlierStyle) const
- {
- // draw quartile box:
- applyDefaultAntialiasingHint(painter);
- const QRectF quartileBox = getQuartileBox(it);
- painter->drawRect(quartileBox);
- // draw median line with cliprect set to quartile box:
- painter->save();
- painter->setClipRect(quartileBox, Qt::IntersectClip);
- painter->setPen(mMedianPen);
- painter->drawLine(QLineF(coordsToPixels(it->key-mWidth*0.5, it->median), coordsToPixels(it->key+mWidth*0.5, it->median)));
- painter->restore();
- // draw whisker lines:
- applyAntialiasingHint(painter, mWhiskerAntialiased, QCP::aePlottables);
- painter->setPen(mWhiskerPen);
- painter->drawLines(getWhiskerBackboneLines(it));
- painter->setPen(mWhiskerBarPen);
- painter->drawLines(getWhiskerBarLines(it));
- // draw outliers:
- applyScattersAntialiasingHint(painter);
- outlierStyle.applyTo(painter, mPen);
- for (int i=0; i<it->outliers.size(); ++i)
- outlierStyle.drawShape(painter, coordsToPixels(it->key, it->outliers.at(i)));
- }
- /*! \internal
-
- called by \ref draw to determine which data (key) range is visible at the current key axis range
- setting, so only that needs to be processed. It also takes into account the bar width.
-
- \a begin returns an iterator to the lowest data point that needs to be taken into account when
- plotting. Note that in order to get a clean plot all the way to the edge of the axis rect, \a
- lower may still be just outside the visible range.
-
- \a end returns an iterator one higher than the highest visible data point. Same as before, \a end
- may also lie just outside of the visible range.
-
- if the plottable contains no data, both \a begin and \a end point to constEnd.
- */
- void QCPStatisticalBox::getVisibleDataBounds(QCPStatisticalBoxDataContainer::const_iterator &begin, QCPStatisticalBoxDataContainer::const_iterator &end) const
- {
- if (!mKeyAxis)
- {
- qDebug() << Q_FUNC_INFO << "invalid key axis";
- begin = mDataContainer->constEnd();
- end = mDataContainer->constEnd();
- return;
- }
- begin = mDataContainer->findBegin(mKeyAxis.data()->range().lower-mWidth*0.5); // subtract half width of box to include partially visible data points
- end = mDataContainer->findEnd(mKeyAxis.data()->range().upper+mWidth*0.5); // add half width of box to include partially visible data points
- }
- /*! \internal
- Returns the box in plot coordinates (keys in x, values in y of the returned rect) that covers the
- value range from the lower to the upper quartile, of the data given by \a it.
- \see drawStatisticalBox, getWhiskerBackboneLines, getWhiskerBarLines
- */
- QRectF QCPStatisticalBox::getQuartileBox(QCPStatisticalBoxDataContainer::const_iterator it) const
- {
- QRectF result;
- result.setTopLeft(coordsToPixels(it->key-mWidth*0.5, it->upperQuartile));
- result.setBottomRight(coordsToPixels(it->key+mWidth*0.5, it->lowerQuartile));
- return result;
- }
- /*! \internal
- Returns the whisker backbones (keys in x, values in y of the returned lines) that cover the value
- range from the minimum to the lower quartile, and from the upper quartile to the maximum of the
- data given by \a it.
- \see drawStatisticalBox, getQuartileBox, getWhiskerBarLines
- */
- QVector<QLineF> QCPStatisticalBox::getWhiskerBackboneLines(QCPStatisticalBoxDataContainer::const_iterator it) const
- {
- QVector<QLineF> result(2);
- result[0].setPoints(coordsToPixels(it->key, it->lowerQuartile), coordsToPixels(it->key, it->minimum)); // min backbone
- result[1].setPoints(coordsToPixels(it->key, it->upperQuartile), coordsToPixels(it->key, it->maximum)); // max backbone
- return result;
- }
- /*! \internal
- Returns the whisker bars (keys in x, values in y of the returned lines) that are placed at the
- end of the whisker backbones, at the minimum and maximum of the data given by \a it.
- \see drawStatisticalBox, getQuartileBox, getWhiskerBackboneLines
- */
- QVector<QLineF> QCPStatisticalBox::getWhiskerBarLines(QCPStatisticalBoxDataContainer::const_iterator it) const
- {
- QVector<QLineF> result(2);
- result[0].setPoints(coordsToPixels(it->key-mWhiskerWidth*0.5, it->minimum), coordsToPixels(it->key+mWhiskerWidth*0.5, it->minimum)); // min bar
- result[1].setPoints(coordsToPixels(it->key-mWhiskerWidth*0.5, it->maximum), coordsToPixels(it->key+mWhiskerWidth*0.5, it->maximum)); // max bar
- return result;
- }
- /* end of 'src/plottables/plottable-statisticalbox.cpp' */
- /* including file 'src/plottables/plottable-colormap.cpp', size 47881 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPColorMapData
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPColorMapData
- \brief Holds the two-dimensional data of a QCPColorMap plottable.
-
- This class is a data storage for \ref QCPColorMap. It holds a two-dimensional array, which \ref
- QCPColorMap then displays as a 2D image in the plot, where the array values are represented by a
- color, depending on the value.
-
- The size of the array can be controlled via \ref setSize (or \ref setKeySize, \ref setValueSize).
- Which plot coordinates these cells correspond to can be configured with \ref setRange (or \ref
- setKeyRange, \ref setValueRange).
-
- The data cells can be accessed in two ways: They can be directly addressed by an integer index
- with \ref setCell. This is the fastest method. Alternatively, they can be addressed by their plot
- coordinate with \ref setData. plot coordinate to cell index transformations and vice versa are
- provided by the functions \ref coordToCell and \ref cellToCoord.
-
- A \ref QCPColorMapData also holds an on-demand two-dimensional array of alpha values which (if
- allocated) has the same size as the data map. It can be accessed via \ref setAlpha, \ref
- fillAlpha and \ref clearAlpha. The memory for the alpha map is only allocated if needed, i.e. on
- the first call of \ref setAlpha. \ref clearAlpha restores full opacity and frees the alpha map.
-
- This class also buffers the minimum and maximum values that are in the data set, to provide
- QCPColorMap::rescaleDataRange with the necessary information quickly. Setting a cell to a value
- that is greater than the current maximum increases this maximum to the new value. However,
- setting the cell that currently holds the maximum value to a smaller value doesn't decrease the
- maximum again, because finding the true new maximum would require going through the entire data
- array, which might be time consuming. The same holds for the data minimum. This functionality is
- given by \ref recalculateDataBounds, such that you can decide when it is sensible to find the
- true current minimum and maximum. The method QCPColorMap::rescaleDataRange offers a convenience
- parameter \a recalculateDataBounds which may be set to true to automatically call \ref
- recalculateDataBounds internally.
- */
- /* start of documentation of inline functions */
- /*! \fn bool QCPColorMapData::isEmpty() const
-
- Returns whether this instance carries no data. This is equivalent to having a size where at least
- one of the dimensions is 0 (see \ref setSize).
- */
- /* end of documentation of inline functions */
- /*!
- Constructs a new QCPColorMapData instance. The instance has \a keySize cells in the key direction
- and \a valueSize cells in the value direction. These cells will be displayed by the \ref QCPColorMap
- at the coordinates \a keyRange and \a valueRange.
-
- \see setSize, setKeySize, setValueSize, setRange, setKeyRange, setValueRange
- */
- QCPColorMapData::QCPColorMapData(int keySize, int valueSize, const QCPRange &keyRange, const QCPRange &valueRange) :
- mKeySize(0),
- mValueSize(0),
- mKeyRange(keyRange),
- mValueRange(valueRange),
- mIsEmpty(true),
- mData(0),
- mAlpha(0),
- mDataModified(true)
- {
- setSize(keySize, valueSize);
- fill(0);
- }
- QCPColorMapData::~QCPColorMapData()
- {
- if (mData)
- delete[] mData;
- if (mAlpha)
- delete[] mAlpha;
- }
- /*!
- Constructs a new QCPColorMapData instance copying the data and range of \a other.
- */
- QCPColorMapData::QCPColorMapData(const QCPColorMapData &other) :
- mKeySize(0),
- mValueSize(0),
- mIsEmpty(true),
- mData(0),
- mAlpha(0),
- mDataModified(true)
- {
- *this = other;
- }
- /*!
- Overwrites this color map data instance with the data stored in \a other. The alpha map state is
- transferred, too.
- */
- QCPColorMapData &QCPColorMapData::operator=(const QCPColorMapData &other)
- {
- if (&other != this)
- {
- const int keySize = other.keySize();
- const int valueSize = other.valueSize();
- if (!other.mAlpha && mAlpha)
- clearAlpha();
- setSize(keySize, valueSize);
- if (other.mAlpha && !mAlpha)
- createAlpha(false);
- setRange(other.keyRange(), other.valueRange());
- if (!isEmpty())
- {
- memcpy(mData, other.mData, sizeof(mData[0])*keySize*valueSize);
- if (mAlpha)
- memcpy(mAlpha, other.mAlpha, sizeof(mAlpha[0])*keySize*valueSize);
- }
- mDataBounds = other.mDataBounds;
- mDataModified = true;
- }
- return *this;
- }
- /* undocumented getter */
- double QCPColorMapData::data(double key, double value)
- {
- int keyCell = (key-mKeyRange.lower)/(mKeyRange.upper-mKeyRange.lower)*(mKeySize-1)+0.5;
- int valueCell = (value-mValueRange.lower)/(mValueRange.upper-mValueRange.lower)*(mValueSize-1)+0.5;
- if (keyCell >= 0 && keyCell < mKeySize && valueCell >= 0 && valueCell < mValueSize)
- return mData[valueCell*mKeySize + keyCell];
- else
- return 0;
- }
- /* undocumented getter */
- double QCPColorMapData::cell(int keyIndex, int valueIndex)
- {
- if (keyIndex >= 0 && keyIndex < mKeySize && valueIndex >= 0 && valueIndex < mValueSize)
- return mData[valueIndex*mKeySize + keyIndex];
- else
- return 0;
- }
- /*!
- Returns the alpha map value of the cell with the indices \a keyIndex and \a valueIndex.
- If this color map data doesn't have an alpha map (because \ref setAlpha was never called after
- creation or after a call to \ref clearAlpha), returns 255, which corresponds to full opacity.
- \see setAlpha
- */
- unsigned char QCPColorMapData::alpha(int keyIndex, int valueIndex)
- {
- if (mAlpha && keyIndex >= 0 && keyIndex < mKeySize && valueIndex >= 0 && valueIndex < mValueSize)
- return mAlpha[valueIndex*mKeySize + keyIndex];
- else
- return 255;
- }
- /*!
- Resizes the data array to have \a keySize cells in the key dimension and \a valueSize cells in
- the value dimension.
- The current data is discarded and the map cells are set to 0, unless the map had already the
- requested size.
-
- Setting at least one of \a keySize or \a valueSize to zero frees the internal data array and \ref
- isEmpty returns true.
- \see setRange, setKeySize, setValueSize
- */
- void QCPColorMapData::setSize(int keySize, int valueSize)
- {
- if (keySize != mKeySize || valueSize != mValueSize)
- {
- mKeySize = keySize;
- mValueSize = valueSize;
- if (mData)
- delete[] mData;
- mIsEmpty = mKeySize == 0 || mValueSize == 0;
- if (!mIsEmpty)
- {
- #ifdef __EXCEPTIONS
- try { // 2D arrays get memory intensive fast. So if the allocation fails, at least output debug message
- #endif
- mData = new double[mKeySize*mValueSize];
- #ifdef __EXCEPTIONS
- } catch (...) { mData = 0; }
- #endif
- if (mData)
- fill(0);
- else
- qDebug() << Q_FUNC_INFO << "out of memory for data dimensions "<< mKeySize << "*" << mValueSize;
- } else
- mData = 0;
-
- if (mAlpha) // if we had an alpha map, recreate it with new size
- createAlpha();
-
- mDataModified = true;
- }
- }
- /*!
- Resizes the data array to have \a keySize cells in the key dimension.
- The current data is discarded and the map cells are set to 0, unless the map had already the
- requested size.
-
- Setting \a keySize to zero frees the internal data array and \ref isEmpty returns true.
- \see setKeyRange, setSize, setValueSize
- */
- void QCPColorMapData::setKeySize(int keySize)
- {
- setSize(keySize, mValueSize);
- }
- /*!
- Resizes the data array to have \a valueSize cells in the value dimension.
- The current data is discarded and the map cells are set to 0, unless the map had already the
- requested size.
-
- Setting \a valueSize to zero frees the internal data array and \ref isEmpty returns true.
- \see setValueRange, setSize, setKeySize
- */
- void QCPColorMapData::setValueSize(int valueSize)
- {
- setSize(mKeySize, valueSize);
- }
- /*!
- Sets the coordinate ranges the data shall be distributed over. This defines the rectangular area
- covered by the color map in plot coordinates.
-
- The outer cells will be centered on the range boundaries given to this function. For example, if
- the key size (\ref setKeySize) is 3 and \a keyRange is set to <tt>QCPRange(2, 3)</tt> there will
- be cells centered on the key coordinates 2, 2.5 and 3.
-
- \see setSize
- */
- void QCPColorMapData::setRange(const QCPRange &keyRange, const QCPRange &valueRange)
- {
- setKeyRange(keyRange);
- setValueRange(valueRange);
- }
- /*!
- Sets the coordinate range the data shall be distributed over in the key dimension. Together with
- the value range, This defines the rectangular area covered by the color map in plot coordinates.
-
- The outer cells will be centered on the range boundaries given to this function. For example, if
- the key size (\ref setKeySize) is 3 and \a keyRange is set to <tt>QCPRange(2, 3)</tt> there will
- be cells centered on the key coordinates 2, 2.5 and 3.
-
- \see setRange, setValueRange, setSize
- */
- void QCPColorMapData::setKeyRange(const QCPRange &keyRange)
- {
- mKeyRange = keyRange;
- }
- /*!
- Sets the coordinate range the data shall be distributed over in the value dimension. Together with
- the key range, This defines the rectangular area covered by the color map in plot coordinates.
-
- The outer cells will be centered on the range boundaries given to this function. For example, if
- the value size (\ref setValueSize) is 3 and \a valueRange is set to <tt>QCPRange(2, 3)</tt> there
- will be cells centered on the value coordinates 2, 2.5 and 3.
-
- \see setRange, setKeyRange, setSize
- */
- void QCPColorMapData::setValueRange(const QCPRange &valueRange)
- {
- mValueRange = valueRange;
- }
- /*!
- Sets the data of the cell, which lies at the plot coordinates given by \a key and \a value, to \a
- z.
-
- \note The QCPColorMap always displays the data at equal key/value intervals, even if the key or
- value axis is set to a logarithmic scaling. If you want to use QCPColorMap with logarithmic axes,
- you shouldn't use the \ref QCPColorMapData::setData method as it uses a linear transformation to
- determine the cell index. Rather directly access the cell index with \ref
- QCPColorMapData::setCell.
-
- \see setCell, setRange
- */
- void QCPColorMapData::setData(double key, double value, double z)
- {
- int keyCell = (key-mKeyRange.lower)/(mKeyRange.upper-mKeyRange.lower)*(mKeySize-1)+0.5;
- int valueCell = (value-mValueRange.lower)/(mValueRange.upper-mValueRange.lower)*(mValueSize-1)+0.5;
- if (keyCell >= 0 && keyCell < mKeySize && valueCell >= 0 && valueCell < mValueSize)
- {
- mData[valueCell*mKeySize + keyCell] = z;
- if (z < mDataBounds.lower)
- mDataBounds.lower = z;
- if (z > mDataBounds.upper)
- mDataBounds.upper = z;
- mDataModified = true;
- }
- }
- /*!
- Sets the data of the cell with indices \a keyIndex and \a valueIndex to \a z. The indices
- enumerate the cells starting from zero, up to the map's size-1 in the respective dimension (see
- \ref setSize).
-
- In the standard plot configuration (horizontal key axis and vertical value axis, both not
- range-reversed), the cell with indices (0, 0) is in the bottom left corner and the cell with
- indices (keySize-1, valueSize-1) is in the top right corner of the color map.
-
- \see setData, setSize
- */
- void QCPColorMapData::setCell(int keyIndex, int valueIndex, double z)
- {
- if (keyIndex >= 0 && keyIndex < mKeySize && valueIndex >= 0 && valueIndex < mValueSize)
- {
- mData[valueIndex*mKeySize + keyIndex] = z;
- if (z < mDataBounds.lower)
- mDataBounds.lower = z;
- if (z > mDataBounds.upper)
- mDataBounds.upper = z;
- mDataModified = true;
- } else
- qDebug() << Q_FUNC_INFO << "index out of bounds:" << keyIndex << valueIndex;
- }
- /*!
- Sets the alpha of the color map cell given by \a keyIndex and \a valueIndex to \a alpha. A value
- of 0 for \a alpha results in a fully transparent cell, and a value of 255 results in a fully
- opaque cell.
- If an alpha map doesn't exist yet for this color map data, it will be created here. If you wish
- to restore full opacity and free any allocated memory of the alpha map, call \ref clearAlpha.
- Note that the cell-wise alpha which can be configured here is independent of any alpha configured
- in the color map's gradient (\ref QCPColorGradient). If a cell is affected both by the cell-wise
- and gradient alpha, the alpha values will be blended accordingly during rendering of the color
- map.
- \see fillAlpha, clearAlpha
- */
- void QCPColorMapData::setAlpha(int keyIndex, int valueIndex, unsigned char alpha)
- {
- if (keyIndex >= 0 && keyIndex < mKeySize && valueIndex >= 0 && valueIndex < mValueSize)
- {
- if (mAlpha || createAlpha())
- {
- mAlpha[valueIndex*mKeySize + keyIndex] = alpha;
- mDataModified = true;
- }
- } else
- qDebug() << Q_FUNC_INFO << "index out of bounds:" << keyIndex << valueIndex;
- }
- /*!
- Goes through the data and updates the buffered minimum and maximum data values.
-
- Calling this method is only advised if you are about to call \ref QCPColorMap::rescaleDataRange
- and can not guarantee that the cells holding the maximum or minimum data haven't been overwritten
- with a smaller or larger value respectively, since the buffered maximum/minimum values have been
- updated the last time. Why this is the case is explained in the class description (\ref
- QCPColorMapData).
-
- Note that the method \ref QCPColorMap::rescaleDataRange provides a parameter \a
- recalculateDataBounds for convenience. Setting this to true will call this method for you, before
- doing the rescale.
- */
- void QCPColorMapData::recalculateDataBounds()
- {
- if (mKeySize > 0 && mValueSize > 0)
- {
- double minHeight = mData[0];
- double maxHeight = mData[0];
- const int dataCount = mValueSize*mKeySize;
- for (int i=0; i<dataCount; ++i)
- {
- if (mData[i] > maxHeight)
- maxHeight = mData[i];
- if (mData[i] < minHeight)
- minHeight = mData[i];
- }
- mDataBounds.lower = minHeight;
- mDataBounds.upper = maxHeight;
- }
- }
- /*!
- Frees the internal data memory.
-
- This is equivalent to calling \ref setSize "setSize(0, 0)".
- */
- void QCPColorMapData::clear()
- {
- setSize(0, 0);
- }
- /*!
- Frees the internal alpha map. The color map will have full opacity again.
- */
- void QCPColorMapData::clearAlpha()
- {
- if (mAlpha)
- {
- delete[] mAlpha;
- mAlpha = 0;
- mDataModified = true;
- }
- }
- /*!
- Sets all cells to the value \a z.
- */
- void QCPColorMapData::fill(double z)
- {
- const int dataCount = mValueSize*mKeySize;
- for (int i=0; i<dataCount; ++i)
- mData[i] = z;
- mDataBounds = QCPRange(z, z);
- mDataModified = true;
- }
- /*!
- Sets the opacity of all color map cells to \a alpha. A value of 0 for \a alpha results in a fully
- transparent color map, and a value of 255 results in a fully opaque color map.
- If you wish to restore opacity to 100% and free any used memory for the alpha map, rather use
- \ref clearAlpha.
- \see setAlpha
- */
- void QCPColorMapData::fillAlpha(unsigned char alpha)
- {
- if (mAlpha || createAlpha(false))
- {
- const int dataCount = mValueSize*mKeySize;
- for (int i=0; i<dataCount; ++i)
- mAlpha[i] = alpha;
- mDataModified = true;
- }
- }
- /*!
- Transforms plot coordinates given by \a key and \a value to cell indices of this QCPColorMapData
- instance. The resulting cell indices are returned via the output parameters \a keyIndex and \a
- valueIndex.
-
- The retrieved key/value cell indices can then be used for example with \ref setCell.
-
- If you are only interested in a key or value index, you may pass 0 as \a valueIndex or \a
- keyIndex.
-
- \note The QCPColorMap always displays the data at equal key/value intervals, even if the key or
- value axis is set to a logarithmic scaling. If you want to use QCPColorMap with logarithmic axes,
- you shouldn't use the \ref QCPColorMapData::coordToCell method as it uses a linear transformation to
- determine the cell index.
-
- \see cellToCoord, QCPAxis::coordToPixel
- */
- void QCPColorMapData::coordToCell(double key, double value, int *keyIndex, int *valueIndex) const
- {
- if (keyIndex)
- *keyIndex = (key-mKeyRange.lower)/(mKeyRange.upper-mKeyRange.lower)*(mKeySize-1)+0.5;
- if (valueIndex)
- *valueIndex = (value-mValueRange.lower)/(mValueRange.upper-mValueRange.lower)*(mValueSize-1)+0.5;
- }
- /*!
- Transforms cell indices given by \a keyIndex and \a valueIndex to cell indices of this QCPColorMapData
- instance. The resulting coordinates are returned via the output parameters \a key and \a
- value.
-
- If you are only interested in a key or value coordinate, you may pass 0 as \a key or \a
- value.
-
- \note The QCPColorMap always displays the data at equal key/value intervals, even if the key or
- value axis is set to a logarithmic scaling. If you want to use QCPColorMap with logarithmic axes,
- you shouldn't use the \ref QCPColorMapData::cellToCoord method as it uses a linear transformation to
- determine the cell index.
-
- \see coordToCell, QCPAxis::pixelToCoord
- */
- void QCPColorMapData::cellToCoord(int keyIndex, int valueIndex, double *key, double *value) const
- {
- if (key)
- *key = keyIndex/(double)(mKeySize-1)*(mKeyRange.upper-mKeyRange.lower)+mKeyRange.lower;
- if (value)
- *value = valueIndex/(double)(mValueSize-1)*(mValueRange.upper-mValueRange.lower)+mValueRange.lower;
- }
- /*! \internal
- Allocates the internal alpha map with the current data map key/value size and, if \a
- initializeOpaque is true, initializes all values to 255. If \a initializeOpaque is false, the
- values are not initialized at all. In this case, the alpha map should be initialized manually,
- e.g. with \ref fillAlpha.
- If an alpha map exists already, it is deleted first. If this color map is empty (has either key
- or value size zero, see \ref isEmpty), the alpha map is cleared.
- The return value indicates the existence of the alpha map after the call. So this method returns
- true if the data map isn't empty and an alpha map was successfully allocated.
- */
- bool QCPColorMapData::createAlpha(bool initializeOpaque)
- {
- clearAlpha();
- if (isEmpty())
- return false;
-
- #ifdef __EXCEPTIONS
- try { // 2D arrays get memory intensive fast. So if the allocation fails, at least output debug message
- #endif
- mAlpha = new unsigned char[mKeySize*mValueSize];
- #ifdef __EXCEPTIONS
- } catch (...) { mAlpha = 0; }
- #endif
- if (mAlpha)
- {
- if (initializeOpaque)
- fillAlpha(255);
- return true;
- } else
- {
- qDebug() << Q_FUNC_INFO << "out of memory for data dimensions "<< mKeySize << "*" << mValueSize;
- return false;
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPColorMap
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPColorMap
- \brief A plottable representing a two-dimensional color map in a plot.
- \image html QCPColorMap.png
-
- The data is stored in the class \ref QCPColorMapData, which can be accessed via the data()
- method.
-
- A color map has three dimensions to represent a data point: The \a key dimension, the \a value
- dimension and the \a data dimension. As with other plottables such as graphs, \a key and \a value
- correspond to two orthogonal axes on the QCustomPlot surface that you specify in the QCPColorMap
- constructor. The \a data dimension however is encoded as the color of the point at (\a key, \a
- value).
- Set the number of points (or \a cells) in the key/value dimension via \ref
- QCPColorMapData::setSize. The plot coordinate range over which these points will be displayed is
- specified via \ref QCPColorMapData::setRange. The first cell will be centered on the lower range
- boundary and the last cell will be centered on the upper range boundary. The data can be set by
- either accessing the cells directly with QCPColorMapData::setCell or by addressing the cells via
- their plot coordinates with \ref QCPColorMapData::setData. If possible, you should prefer
- setCell, since it doesn't need to do any coordinate transformation and thus performs a bit
- better.
-
- The cell with index (0, 0) is at the bottom left, if the color map uses normal (i.e. not reversed)
- key and value axes.
-
- To show the user which colors correspond to which \a data values, a \ref QCPColorScale is
- typically placed to the right of the axis rect. See the documentation there for details on how to
- add and use a color scale.
-
- \section qcpcolormap-appearance Changing the appearance
-
- The central part of the appearance is the color gradient, which can be specified via \ref
- setGradient. See the documentation of \ref QCPColorGradient for details on configuring a color
- gradient.
-
- The \a data range that is mapped to the colors of the gradient can be specified with \ref
- setDataRange. To make the data range encompass the whole data set minimum to maximum, call \ref
- rescaleDataRange.
-
- \section qcpcolormap-transparency Transparency
-
- Transparency in color maps can be achieved by two mechanisms. On one hand, you can specify alpha
- values for color stops of the \ref QCPColorGradient, via the regular QColor interface. This will
- cause the color map data which gets mapped to colors around those color stops to appear with the
- accordingly interpolated transparency.
-
- On the other hand you can also directly apply an alpha value to each cell independent of its
- data, by using the alpha map feature of \ref QCPColorMapData. The relevant methods are \ref
- QCPColorMapData::setAlpha, QCPColorMapData::fillAlpha and \ref QCPColorMapData::clearAlpha().
-
- The two transparencies will be joined together in the plot and otherwise not interfere with each
- other. They are mixed in a multiplicative matter, so an alpha of e.g. 50% (128/255) in both modes
- simultaneously, will result in a total transparency of 25% (64/255).
-
- \section qcpcolormap-usage Usage
-
- Like all data representing objects in QCustomPlot, the QCPColorMap is a plottable
- (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies
- (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.)
-
- Usually, you first create an instance:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolormap-creation-1
- which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot instance takes
- ownership of the plottable, so do not delete it manually but use QCustomPlot::removePlottable() instead.
- The newly created plottable can be modified, e.g.:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpcolormap-creation-2
-
- \note The QCPColorMap always displays the data at equal key/value intervals, even if the key or
- value axis is set to a logarithmic scaling. If you want to use QCPColorMap with logarithmic axes,
- you shouldn't use the \ref QCPColorMapData::setData method as it uses a linear transformation to
- determine the cell index. Rather directly access the cell index with \ref
- QCPColorMapData::setCell.
- */
- /* start documentation of inline functions */
- /*! \fn QCPColorMapData *QCPColorMap::data() const
-
- Returns a pointer to the internal data storage of type \ref QCPColorMapData. Access this to
- modify data points (cells) and the color map key/value range.
-
- \see setData
- */
- /* end documentation of inline functions */
- /* start documentation of signals */
- /*! \fn void QCPColorMap::dataRangeChanged(const QCPRange &newRange);
-
- This signal is emitted when the data range changes.
-
- \see setDataRange
- */
- /*! \fn void QCPColorMap::dataScaleTypeChanged(QCPAxis::ScaleType scaleType);
-
- This signal is emitted when the data scale type changes.
-
- \see setDataScaleType
- */
- /*! \fn void QCPColorMap::gradientChanged(const QCPColorGradient &newGradient);
-
- This signal is emitted when the gradient changes.
-
- \see setGradient
- */
- /* end documentation of signals */
- /*!
- Constructs a color map with the specified \a keyAxis and \a valueAxis.
-
- The created QCPColorMap is automatically registered with the QCustomPlot instance inferred from
- \a keyAxis. This QCustomPlot instance takes ownership of the QCPColorMap, so do not delete it
- manually but use QCustomPlot::removePlottable() instead.
- */
- QCPColorMap::QCPColorMap(QCPAxis *keyAxis, QCPAxis *valueAxis) :
- QCPAbstractPlottable(keyAxis, valueAxis),
- mDataScaleType(QCPAxis::stLinear),
- mMapData(new QCPColorMapData(10, 10, QCPRange(0, 5), QCPRange(0, 5))),
- mGradient(QCPColorGradient::gpCold),
- mInterpolate(true),
- mTightBoundary(false),
- mMapImageInvalidated(true)
- {
- }
- QCPColorMap::~QCPColorMap()
- {
- delete mMapData;
- }
- /*!
- Replaces the current \ref data with the provided \a data.
-
- If \a copy is set to true, the \a data object will only be copied. if false, the color map
- takes ownership of the passed data and replaces the internal data pointer with it. This is
- significantly faster than copying for large datasets.
- */
- void QCPColorMap::setData(QCPColorMapData *data, bool copy)
- {
- if (mMapData == data)
- {
- qDebug() << Q_FUNC_INFO << "The data pointer is already in (and owned by) this plottable" << reinterpret_cast<quintptr>(data);
- return;
- }
- if (copy)
- {
- *mMapData = *data;
- } else
- {
- delete mMapData;
- mMapData = data;
- }
- mMapImageInvalidated = true;
- }
- /*!
- Sets the data range of this color map to \a dataRange. The data range defines which data values
- are mapped to the color gradient.
-
- To make the data range span the full range of the data set, use \ref rescaleDataRange.
-
- \see QCPColorScale::setDataRange
- */
- void QCPColorMap::setDataRange(const QCPRange &dataRange)
- {
- if (!QCPRange::validRange(dataRange)) return;
- if (mDataRange.lower != dataRange.lower || mDataRange.upper != dataRange.upper)
- {
- if (mDataScaleType == QCPAxis::stLogarithmic)
- mDataRange = dataRange.sanitizedForLogScale();
- else
- mDataRange = dataRange.sanitizedForLinScale();
- mMapImageInvalidated = true;
- emit dataRangeChanged(mDataRange);
- }
- }
- /*!
- Sets whether the data is correlated with the color gradient linearly or logarithmically.
-
- \see QCPColorScale::setDataScaleType
- */
- void QCPColorMap::setDataScaleType(QCPAxis::ScaleType scaleType)
- {
- if (mDataScaleType != scaleType)
- {
- mDataScaleType = scaleType;
- mMapImageInvalidated = true;
- emit dataScaleTypeChanged(mDataScaleType);
- if (mDataScaleType == QCPAxis::stLogarithmic)
- setDataRange(mDataRange.sanitizedForLogScale());
- }
- }
- /*!
- Sets the color gradient that is used to represent the data. For more details on how to create an
- own gradient or use one of the preset gradients, see \ref QCPColorGradient.
-
- The colors defined by the gradient will be used to represent data values in the currently set
- data range, see \ref setDataRange. Data points that are outside this data range will either be
- colored uniformly with the respective gradient boundary color, or the gradient will repeat,
- depending on \ref QCPColorGradient::setPeriodic.
-
- \see QCPColorScale::setGradient
- */
- void QCPColorMap::setGradient(const QCPColorGradient &gradient)
- {
- if (mGradient != gradient)
- {
- mGradient = gradient;
- mMapImageInvalidated = true;
- emit gradientChanged(mGradient);
- }
- }
- /*!
- Sets whether the color map image shall use bicubic interpolation when displaying the color map
- shrinked or expanded, and not at a 1:1 pixel-to-data scale.
-
- \image html QCPColorMap-interpolate.png "A 10*10 color map, with interpolation and without interpolation enabled"
- */
- void QCPColorMap::setInterpolate(bool enabled)
- {
- mInterpolate = enabled;
- mMapImageInvalidated = true; // because oversampling factors might need to change
- }
- /*!
- Sets whether the outer most data rows and columns are clipped to the specified key and value
- range (see \ref QCPColorMapData::setKeyRange, \ref QCPColorMapData::setValueRange).
-
- if \a enabled is set to false, the data points at the border of the color map are drawn with the
- same width and height as all other data points. Since the data points are represented by
- rectangles of one color centered on the data coordinate, this means that the shown color map
- extends by half a data point over the specified key/value range in each direction.
-
- \image html QCPColorMap-tightboundary.png "A color map, with tight boundary enabled and disabled"
- */
- void QCPColorMap::setTightBoundary(bool enabled)
- {
- mTightBoundary = enabled;
- }
- /*!
- Associates the color scale \a colorScale with this color map.
-
- This means that both the color scale and the color map synchronize their gradient, data range and
- data scale type (\ref setGradient, \ref setDataRange, \ref setDataScaleType). Multiple color maps
- can be associated with one single color scale. This causes the color maps to also synchronize
- those properties, via the mutual color scale.
-
- This function causes the color map to adopt the current color gradient, data range and data scale
- type of \a colorScale. After this call, you may change these properties at either the color map
- or the color scale, and the setting will be applied to both.
-
- Pass 0 as \a colorScale to disconnect the color scale from this color map again.
- */
- void QCPColorMap::setColorScale(QCPColorScale *colorScale)
- {
- if (mColorScale) // unconnect signals from old color scale
- {
- disconnect(this, SIGNAL(dataRangeChanged(QCPRange)), mColorScale.data(), SLOT(setDataRange(QCPRange)));
- disconnect(this, SIGNAL(dataScaleTypeChanged(QCPAxis::ScaleType)), mColorScale.data(), SLOT(setDataScaleType(QCPAxis::ScaleType)));
- disconnect(this, SIGNAL(gradientChanged(QCPColorGradient)), mColorScale.data(), SLOT(setGradient(QCPColorGradient)));
- disconnect(mColorScale.data(), SIGNAL(dataRangeChanged(QCPRange)), this, SLOT(setDataRange(QCPRange)));
- disconnect(mColorScale.data(), SIGNAL(gradientChanged(QCPColorGradient)), this, SLOT(setGradient(QCPColorGradient)));
- disconnect(mColorScale.data(), SIGNAL(dataScaleTypeChanged(QCPAxis::ScaleType)), this, SLOT(setDataScaleType(QCPAxis::ScaleType)));
- }
- mColorScale = colorScale;
- if (mColorScale) // connect signals to new color scale
- {
- setGradient(mColorScale.data()->gradient());
- setDataRange(mColorScale.data()->dataRange());
- setDataScaleType(mColorScale.data()->dataScaleType());
- connect(this, SIGNAL(dataRangeChanged(QCPRange)), mColorScale.data(), SLOT(setDataRange(QCPRange)));
- connect(this, SIGNAL(dataScaleTypeChanged(QCPAxis::ScaleType)), mColorScale.data(), SLOT(setDataScaleType(QCPAxis::ScaleType)));
- connect(this, SIGNAL(gradientChanged(QCPColorGradient)), mColorScale.data(), SLOT(setGradient(QCPColorGradient)));
- connect(mColorScale.data(), SIGNAL(dataRangeChanged(QCPRange)), this, SLOT(setDataRange(QCPRange)));
- connect(mColorScale.data(), SIGNAL(gradientChanged(QCPColorGradient)), this, SLOT(setGradient(QCPColorGradient)));
- connect(mColorScale.data(), SIGNAL(dataScaleTypeChanged(QCPAxis::ScaleType)), this, SLOT(setDataScaleType(QCPAxis::ScaleType)));
- }
- }
- /*!
- Sets the data range (\ref setDataRange) to span the minimum and maximum values that occur in the
- current data set. This corresponds to the \ref rescaleKeyAxis or \ref rescaleValueAxis methods,
- only for the third data dimension of the color map.
-
- The minimum and maximum values of the data set are buffered in the internal QCPColorMapData
- instance (\ref data). As data is updated via its \ref QCPColorMapData::setCell or \ref
- QCPColorMapData::setData, the buffered minimum and maximum values are updated, too. For
- performance reasons, however, they are only updated in an expanding fashion. So the buffered
- maximum can only increase and the buffered minimum can only decrease. In consequence, changes to
- the data that actually lower the maximum of the data set (by overwriting the cell holding the
- current maximum with a smaller value), aren't recognized and the buffered maximum overestimates
- the true maximum of the data set. The same happens for the buffered minimum. To recalculate the
- true minimum and maximum by explicitly looking at each cell, the method
- QCPColorMapData::recalculateDataBounds can be used. For convenience, setting the parameter \a
- recalculateDataBounds calls this method before setting the data range to the buffered minimum and
- maximum.
-
- \see setDataRange
- */
- void QCPColorMap::rescaleDataRange(bool recalculateDataBounds)
- {
- if (recalculateDataBounds)
- mMapData->recalculateDataBounds();
- setDataRange(mMapData->dataBounds());
- }
- /*!
- Takes the current appearance of the color map and updates the legend icon, which is used to
- represent this color map in the legend (see \ref QCPLegend).
-
- The \a transformMode specifies whether the rescaling is done by a faster, low quality image
- scaling algorithm (Qt::FastTransformation) or by a slower, higher quality algorithm
- (Qt::SmoothTransformation).
-
- The current color map appearance is scaled down to \a thumbSize. Ideally, this should be equal to
- the size of the legend icon (see \ref QCPLegend::setIconSize). If it isn't exactly the configured
- legend icon size, the thumb will be rescaled during drawing of the legend item.
-
- \see setDataRange
- */
- void QCPColorMap::updateLegendIcon(Qt::TransformationMode transformMode, const QSize &thumbSize)
- {
- if (mMapImage.isNull() && !data()->isEmpty())
- updateMapImage(); // try to update map image if it's null (happens if no draw has happened yet)
-
- if (!mMapImage.isNull()) // might still be null, e.g. if data is empty, so check here again
- {
- bool mirrorX = (keyAxis()->orientation() == Qt::Horizontal ? keyAxis() : valueAxis())->rangeReversed();
- bool mirrorY = (valueAxis()->orientation() == Qt::Vertical ? valueAxis() : keyAxis())->rangeReversed();
- mLegendIcon = QPixmap::fromImage(mMapImage.mirrored(mirrorX, mirrorY)).scaled(thumbSize, Qt::KeepAspectRatio, transformMode);
- }
- }
- /* inherits documentation from base class */
- double QCPColorMap::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- Q_UNUSED(details)
- if ((onlySelectable && mSelectable == QCP::stNone) || mMapData->isEmpty())
- return -1;
- if (!mKeyAxis || !mValueAxis)
- return -1;
-
- if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()))
- {
- double posKey, posValue;
- pixelsToCoords(pos, posKey, posValue);
- if (mMapData->keyRange().contains(posKey) && mMapData->valueRange().contains(posValue))
- {
- if (details)
- details->setValue(QCPDataSelection(QCPDataRange(0, 1))); // temporary solution, to facilitate whole-plottable selection. Replace in future version with segmented 2D selection.
- return mParentPlot->selectionTolerance()*0.99;
- }
- }
- return -1;
- }
- /* inherits documentation from base class */
- QCPRange QCPColorMap::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
- {
- foundRange = true;
- QCPRange result = mMapData->keyRange();
- result.normalize();
- if (inSignDomain == QCP::sdPositive)
- {
- if (result.lower <= 0 && result.upper > 0)
- result.lower = result.upper*1e-3;
- else if (result.lower <= 0 && result.upper <= 0)
- foundRange = false;
- } else if (inSignDomain == QCP::sdNegative)
- {
- if (result.upper >= 0 && result.lower < 0)
- result.upper = result.lower*1e-3;
- else if (result.upper >= 0 && result.lower >= 0)
- foundRange = false;
- }
- return result;
- }
- /* inherits documentation from base class */
- QCPRange QCPColorMap::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
- {
- if (inKeyRange != QCPRange())
- {
- if (mMapData->keyRange().upper < inKeyRange.lower || mMapData->keyRange().lower > inKeyRange.upper)
- {
- foundRange = false;
- return QCPRange();
- }
- }
-
- foundRange = true;
- QCPRange result = mMapData->valueRange();
- result.normalize();
- if (inSignDomain == QCP::sdPositive)
- {
- if (result.lower <= 0 && result.upper > 0)
- result.lower = result.upper*1e-3;
- else if (result.lower <= 0 && result.upper <= 0)
- foundRange = false;
- } else if (inSignDomain == QCP::sdNegative)
- {
- if (result.upper >= 0 && result.lower < 0)
- result.upper = result.lower*1e-3;
- else if (result.upper >= 0 && result.lower >= 0)
- foundRange = false;
- }
- return result;
- }
- /*! \internal
-
- Updates the internal map image buffer by going through the internal \ref QCPColorMapData and
- turning the data values into color pixels with \ref QCPColorGradient::colorize.
-
- This method is called by \ref QCPColorMap::draw if either the data has been modified or the map image
- has been invalidated for a different reason (e.g. a change of the data range with \ref
- setDataRange).
-
- If the map cell count is low, the image created will be oversampled in order to avoid a
- QPainter::drawImage bug which makes inner pixel boundaries jitter when stretch-drawing images
- without smooth transform enabled. Accordingly, oversampling isn't performed if \ref
- setInterpolate is true.
- */
- void QCPColorMap::updateMapImage()
- {
- QCPAxis *keyAxis = mKeyAxis.data();
- if (!keyAxis) return;
- if (mMapData->isEmpty()) return;
-
- const QImage::Format format = QImage::Format_ARGB32_Premultiplied;
- const int keySize = mMapData->keySize();
- const int valueSize = mMapData->valueSize();
- int keyOversamplingFactor = mInterpolate ? 1 : (int)(1.0+100.0/(double)keySize); // make mMapImage have at least size 100, factor becomes 1 if size > 200 or interpolation is on
- int valueOversamplingFactor = mInterpolate ? 1 : (int)(1.0+100.0/(double)valueSize); // make mMapImage have at least size 100, factor becomes 1 if size > 200 or interpolation is on
-
- // resize mMapImage to correct dimensions including possible oversampling factors, according to key/value axes orientation:
- if (keyAxis->orientation() == Qt::Horizontal && (mMapImage.width() != keySize*keyOversamplingFactor || mMapImage.height() != valueSize*valueOversamplingFactor))
- mMapImage = QImage(QSize(keySize*keyOversamplingFactor, valueSize*valueOversamplingFactor), format);
- else if (keyAxis->orientation() == Qt::Vertical && (mMapImage.width() != valueSize*valueOversamplingFactor || mMapImage.height() != keySize*keyOversamplingFactor))
- mMapImage = QImage(QSize(valueSize*valueOversamplingFactor, keySize*keyOversamplingFactor), format);
-
- if (mMapImage.isNull())
- {
- qDebug() << Q_FUNC_INFO << "Couldn't create map image (possibly too large for memory)";
- mMapImage = QImage(QSize(10, 10), format);
- mMapImage.fill(Qt::black);
- } else
- {
- QImage *localMapImage = &mMapImage; // this is the image on which the colorization operates. Either the final mMapImage, or if we need oversampling, mUndersampledMapImage
- if (keyOversamplingFactor > 1 || valueOversamplingFactor > 1)
- {
- // resize undersampled map image to actual key/value cell sizes:
- if (keyAxis->orientation() == Qt::Horizontal && (mUndersampledMapImage.width() != keySize || mUndersampledMapImage.height() != valueSize))
- mUndersampledMapImage = QImage(QSize(keySize, valueSize), format);
- else if (keyAxis->orientation() == Qt::Vertical && (mUndersampledMapImage.width() != valueSize || mUndersampledMapImage.height() != keySize))
- mUndersampledMapImage = QImage(QSize(valueSize, keySize), format);
- localMapImage = &mUndersampledMapImage; // make the colorization run on the undersampled image
- } else if (!mUndersampledMapImage.isNull())
- mUndersampledMapImage = QImage(); // don't need oversampling mechanism anymore (map size has changed) but mUndersampledMapImage still has nonzero size, free it
-
- const double *rawData = mMapData->mData;
- const unsigned char *rawAlpha = mMapData->mAlpha;
- if (keyAxis->orientation() == Qt::Horizontal)
- {
- const int lineCount = valueSize;
- const int rowCount = keySize;
- for (int line=0; line<lineCount; ++line)
- {
- QRgb* pixels = reinterpret_cast<QRgb*>(localMapImage->scanLine(lineCount-1-line)); // invert scanline index because QImage counts scanlines from top, but our vertical index counts from bottom (mathematical coordinate system)
- if (rawAlpha)
- mGradient.colorize(rawData+line*rowCount, rawAlpha+line*rowCount, mDataRange, pixels, rowCount, 1, mDataScaleType==QCPAxis::stLogarithmic);
- else
- mGradient.colorize(rawData+line*rowCount, mDataRange, pixels, rowCount, 1, mDataScaleType==QCPAxis::stLogarithmic);
- }
- } else // keyAxis->orientation() == Qt::Vertical
- {
- const int lineCount = keySize;
- const int rowCount = valueSize;
- for (int line=0; line<lineCount; ++line)
- {
- QRgb* pixels = reinterpret_cast<QRgb*>(localMapImage->scanLine(lineCount-1-line)); // invert scanline index because QImage counts scanlines from top, but our vertical index counts from bottom (mathematical coordinate system)
- if (rawAlpha)
- mGradient.colorize(rawData+line, rawAlpha+line, mDataRange, pixels, rowCount, lineCount, mDataScaleType==QCPAxis::stLogarithmic);
- else
- mGradient.colorize(rawData+line, mDataRange, pixels, rowCount, lineCount, mDataScaleType==QCPAxis::stLogarithmic);
- }
- }
-
- if (keyOversamplingFactor > 1 || valueOversamplingFactor > 1)
- {
- if (keyAxis->orientation() == Qt::Horizontal)
- mMapImage = mUndersampledMapImage.scaled(keySize*keyOversamplingFactor, valueSize*valueOversamplingFactor, Qt::IgnoreAspectRatio, Qt::FastTransformation);
- else
- mMapImage = mUndersampledMapImage.scaled(valueSize*valueOversamplingFactor, keySize*keyOversamplingFactor, Qt::IgnoreAspectRatio, Qt::FastTransformation);
- }
- }
- mMapData->mDataModified = false;
- mMapImageInvalidated = false;
- }
- /* inherits documentation from base class */
- void QCPColorMap::draw(QCPPainter *painter)
- {
- if (mMapData->isEmpty()) return;
- if (!mKeyAxis || !mValueAxis) return;
- applyDefaultAntialiasingHint(painter);
-
- if (mMapData->mDataModified || mMapImageInvalidated)
- updateMapImage();
-
- // use buffer if painting vectorized (PDF):
- const bool useBuffer = painter->modes().testFlag(QCPPainter::pmVectorized);
- QCPPainter *localPainter = painter; // will be redirected to paint on mapBuffer if painting vectorized
- QRectF mapBufferTarget; // the rect in absolute widget coordinates where the visible map portion/buffer will end up in
- QPixmap mapBuffer;
- if (useBuffer)
- {
- const double mapBufferPixelRatio = 3; // factor by which DPI is increased in embedded bitmaps
- mapBufferTarget = painter->clipRegion().boundingRect();
- mapBuffer = QPixmap((mapBufferTarget.size()*mapBufferPixelRatio).toSize());
- mapBuffer.fill(Qt::transparent);
- localPainter = new QCPPainter(&mapBuffer);
- localPainter->scale(mapBufferPixelRatio, mapBufferPixelRatio);
- localPainter->translate(-mapBufferTarget.topLeft());
- }
-
- QRectF imageRect = QRectF(coordsToPixels(mMapData->keyRange().lower, mMapData->valueRange().lower),
- coordsToPixels(mMapData->keyRange().upper, mMapData->valueRange().upper)).normalized();
- // extend imageRect to contain outer halves/quarters of bordering/cornering pixels (cells are centered on map range boundary):
- double halfCellWidth = 0; // in pixels
- double halfCellHeight = 0; // in pixels
- if (keyAxis()->orientation() == Qt::Horizontal)
- {
- if (mMapData->keySize() > 1)
- halfCellWidth = 0.5*imageRect.width()/(double)(mMapData->keySize()-1);
- if (mMapData->valueSize() > 1)
- halfCellHeight = 0.5*imageRect.height()/(double)(mMapData->valueSize()-1);
- } else // keyAxis orientation is Qt::Vertical
- {
- if (mMapData->keySize() > 1)
- halfCellHeight = 0.5*imageRect.height()/(double)(mMapData->keySize()-1);
- if (mMapData->valueSize() > 1)
- halfCellWidth = 0.5*imageRect.width()/(double)(mMapData->valueSize()-1);
- }
- imageRect.adjust(-halfCellWidth, -halfCellHeight, halfCellWidth, halfCellHeight);
- const bool mirrorX = (keyAxis()->orientation() == Qt::Horizontal ? keyAxis() : valueAxis())->rangeReversed();
- const bool mirrorY = (valueAxis()->orientation() == Qt::Vertical ? valueAxis() : keyAxis())->rangeReversed();
- const bool smoothBackup = localPainter->renderHints().testFlag(QPainter::SmoothPixmapTransform);
- localPainter->setRenderHint(QPainter::SmoothPixmapTransform, mInterpolate);
- QRegion clipBackup;
- if (mTightBoundary)
- {
- clipBackup = localPainter->clipRegion();
- QRectF tightClipRect = QRectF(coordsToPixels(mMapData->keyRange().lower, mMapData->valueRange().lower),
- coordsToPixels(mMapData->keyRange().upper, mMapData->valueRange().upper)).normalized();
- localPainter->setClipRect(tightClipRect, Qt::IntersectClip);
- }
- localPainter->drawImage(imageRect, mMapImage.mirrored(mirrorX, mirrorY));
- if (mTightBoundary)
- localPainter->setClipRegion(clipBackup);
- localPainter->setRenderHint(QPainter::SmoothPixmapTransform, smoothBackup);
-
- if (useBuffer) // localPainter painted to mapBuffer, so now draw buffer with original painter
- {
- delete localPainter;
- painter->drawPixmap(mapBufferTarget.toRect(), mapBuffer);
- }
- }
- /* inherits documentation from base class */
- void QCPColorMap::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
- {
- applyDefaultAntialiasingHint(painter);
- // draw map thumbnail:
- if (!mLegendIcon.isNull())
- {
- QPixmap scaledIcon = mLegendIcon.scaled(rect.size().toSize(), Qt::KeepAspectRatio, Qt::FastTransformation);
- QRectF iconRect = QRectF(0, 0, scaledIcon.width(), scaledIcon.height());
- iconRect.moveCenter(rect.center());
- painter->drawPixmap(iconRect.topLeft(), scaledIcon);
- }
- /*
- // draw frame:
- painter->setBrush(Qt::NoBrush);
- painter->setPen(Qt::black);
- painter->drawRect(rect.adjusted(1, 1, 0, 0));
- */
- }
- /* end of 'src/plottables/plottable-colormap.cpp' */
- /* including file 'src/plottables/plottable-financial.cpp', size 42610 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPFinancialData
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPFinancialData
- \brief Holds the data of one single data point for QCPFinancial.
-
- The stored data is:
- \li \a key: coordinate on the key axis of this data point (this is the \a mainKey and the \a sortKey)
- \li \a open: The opening value at the data point (this is the \a mainValue)
- \li \a high: The high/maximum value at the data point
- \li \a low: The low/minimum value at the data point
- \li \a close: The closing value at the data point
-
- The container for storing multiple data points is \ref QCPFinancialDataContainer. It is a typedef
- for \ref QCPDataContainer with \ref QCPFinancialData as the DataType template parameter. See the
- documentation there for an explanation regarding the data type's generic methods.
-
- \see QCPFinancialDataContainer
- */
- /* start documentation of inline functions */
- /*! \fn double QCPFinancialData::sortKey() const
-
- Returns the \a key member of this data point.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn static QCPFinancialData QCPFinancialData::fromSortKey(double sortKey)
-
- Returns a data point with the specified \a sortKey. All other members are set to zero.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn static static bool QCPFinancialData::sortKeyIsMainKey()
-
- Since the member \a key is both the data point key coordinate and the data ordering parameter,
- this method returns true.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn double QCPFinancialData::mainKey() const
-
- Returns the \a key member of this data point.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn double QCPFinancialData::mainValue() const
-
- Returns the \a open member of this data point.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /*! \fn QCPRange QCPFinancialData::valueRange() const
-
- Returns a QCPRange spanning from the \a low to the \a high value of this data point.
-
- For a general explanation of what this method is good for in the context of the data container,
- see the documentation of \ref QCPDataContainer.
- */
- /* end documentation of inline functions */
- /*!
- Constructs a data point with key and all values set to zero.
- */
- QCPFinancialData::QCPFinancialData() :
- key(0),
- open(0),
- high(0),
- low(0),
- close(0)
- {
- }
- /*!
- Constructs a data point with the specified \a key and OHLC values.
- */
- QCPFinancialData::QCPFinancialData(double key, double open, double high, double low, double close) :
- key(key),
- open(open),
- high(high),
- low(low),
- close(close)
- {
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPFinancial
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPFinancial
- \brief A plottable representing a financial stock chart
- \image html QCPFinancial.png
- This plottable represents time series data binned to certain intervals, mainly used for stock
- charts. The two common representations OHLC (Open-High-Low-Close) bars and Candlesticks can be
- set via \ref setChartStyle.
- The data is passed via \ref setData as a set of open/high/low/close values at certain keys
- (typically times). This means the data must be already binned appropriately. If data is only
- available as a series of values (e.g. \a price against \a time), you can use the static
- convenience function \ref timeSeriesToOhlc to generate binned OHLC-data which can then be passed
- to \ref setData.
- The width of the OHLC bars/candlesticks can be controlled with \ref setWidth and \ref
- setWidthType. A typical choice is to set the width type to \ref wtPlotCoords (the default) and
- the width to (or slightly less than) one time bin interval width.
- \section qcpfinancial-appearance Changing the appearance
- Charts can be either single- or two-colored (\ref setTwoColored). If set to be single-colored,
- lines are drawn with the plottable's pen (\ref setPen) and fills with the brush (\ref setBrush).
- If set to two-colored, positive changes of the value during an interval (\a close >= \a open) are
- represented with a different pen and brush than negative changes (\a close < \a open). These can
- be configured with \ref setPenPositive, \ref setPenNegative, \ref setBrushPositive, and \ref
- setBrushNegative. In two-colored mode, the normal plottable pen/brush is ignored. Upon selection
- however, the normal selected pen/brush (provided by the \ref selectionDecorator) is used,
- irrespective of whether the chart is single- or two-colored.
- \section qcpfinancial-usage Usage
- Like all data representing objects in QCustomPlot, the QCPFinancial is a plottable
- (QCPAbstractPlottable). So the plottable-interface of QCustomPlot applies
- (QCustomPlot::plottable, QCustomPlot::removePlottable, etc.)
- Usually, you first create an instance:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpfinancial-creation-1
- which registers it with the QCustomPlot instance of the passed axes. Note that this QCustomPlot
- instance takes ownership of the plottable, so do not delete it manually but use
- QCustomPlot::removePlottable() instead. The newly created plottable can be modified, e.g.:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpfinancial-creation-2
- Here we have used the static helper method \ref timeSeriesToOhlc, to turn a time-price data
- series into a 24-hour binned open-high-low-close data series as QCPFinancial uses.
- */
- /* start of documentation of inline functions */
- /*! \fn QCPFinancialDataContainer *QCPFinancial::data() const
-
- Returns a pointer to the internal data storage of type \ref QCPFinancialDataContainer. You may
- use it to directly manipulate the data, which may be more convenient and faster than using the
- regular \ref setData or \ref addData methods, in certain situations.
- */
- /* end of documentation of inline functions */
- /*!
- Constructs a financial chart which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value
- axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have
- the same orientation. If either of these restrictions is violated, a corresponding message is
- printed to the debug output (qDebug), the construction is not aborted, though.
-
- The created QCPFinancial is automatically registered with the QCustomPlot instance inferred from \a
- keyAxis. This QCustomPlot instance takes ownership of the QCPFinancial, so do not delete it manually
- but use QCustomPlot::removePlottable() instead.
- */
- QCPFinancial::QCPFinancial(QCPAxis *keyAxis, QCPAxis *valueAxis) :
- QCPAbstractPlottable1D<QCPFinancialData>(keyAxis, valueAxis),
- mChartStyle(csCandlestick),
- mWidth(0.5),
- mWidthType(wtPlotCoords),
- mTwoColored(true),
- mBrushPositive(QBrush(QColor(50, 160, 0))),
- mBrushNegative(QBrush(QColor(180, 0, 15))),
- mPenPositive(QPen(QColor(40, 150, 0))),
- mPenNegative(QPen(QColor(170, 5, 5)))
- {
- mSelectionDecorator->setBrush(QBrush(QColor(160, 160, 255)));
- }
- QCPFinancial::~QCPFinancial()
- {
- }
- /*! \overload
-
- Replaces the current data container with the provided \a data container.
-
- Since a QSharedPointer is used, multiple QCPFinancials may share the same data container safely.
- Modifying the data in the container will then affect all financials that share the container.
- Sharing can be achieved by simply exchanging the data containers wrapped in shared pointers:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpfinancial-datasharing-1
-
- If you do not wish to share containers, but create a copy from an existing container, rather use
- the \ref QCPDataContainer<DataType>::set method on the financial's data container directly:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcpfinancial-datasharing-2
-
- \see addData, timeSeriesToOhlc
- */
- void QCPFinancial::setData(QSharedPointer<QCPFinancialDataContainer> data)
- {
- mDataContainer = data;
- }
- /*! \overload
-
- Replaces the current data with the provided points in \a keys, \a open, \a high, \a low and \a
- close. The provided vectors should have equal length. Else, the number of added points will be
- the size of the smallest vector.
-
- If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
- can set \a alreadySorted to true, to improve performance by saving a sorting run.
-
- \see addData, timeSeriesToOhlc
- */
- void QCPFinancial::setData(const QVector<double> &keys, const QVector<double> &open, const QVector<double> &high, const QVector<double> &low, const QVector<double> &close, bool alreadySorted)
- {
- mDataContainer->clear();
- addData(keys, open, high, low, close, alreadySorted);
- }
- /*!
- Sets which representation style shall be used to display the OHLC data.
- */
- void QCPFinancial::setChartStyle(QCPFinancial::ChartStyle style)
- {
- mChartStyle = style;
- }
- /*!
- Sets the width of the individual bars/candlesticks to \a width in plot key coordinates.
-
- A typical choice is to set it to (or slightly less than) one bin interval width.
- */
- void QCPFinancial::setWidth(double width)
- {
- mWidth = width;
- }
- /*!
- Sets how the width of the financial bars is defined. See the documentation of \ref WidthType for
- an explanation of the possible values for \a widthType.
- The default value is \ref wtPlotCoords.
- \see setWidth
- */
- void QCPFinancial::setWidthType(QCPFinancial::WidthType widthType)
- {
- mWidthType = widthType;
- }
- /*!
- Sets whether this chart shall contrast positive from negative trends per data point by using two
- separate colors to draw the respective bars/candlesticks.
-
- If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref
- setBrush).
-
- \see setPenPositive, setPenNegative, setBrushPositive, setBrushNegative
- */
- void QCPFinancial::setTwoColored(bool twoColored)
- {
- mTwoColored = twoColored;
- }
- /*!
- If \ref setTwoColored is set to true, this function controls the brush that is used to draw fills
- of data points with a positive trend (i.e. bars/candlesticks with close >= open).
-
- If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref
- setBrush).
-
- \see setBrushNegative, setPenPositive, setPenNegative
- */
- void QCPFinancial::setBrushPositive(const QBrush &brush)
- {
- mBrushPositive = brush;
- }
- /*!
- If \ref setTwoColored is set to true, this function controls the brush that is used to draw fills
- of data points with a negative trend (i.e. bars/candlesticks with close < open).
-
- If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref
- setBrush).
-
- \see setBrushPositive, setPenNegative, setPenPositive
- */
- void QCPFinancial::setBrushNegative(const QBrush &brush)
- {
- mBrushNegative = brush;
- }
- /*!
- If \ref setTwoColored is set to true, this function controls the pen that is used to draw
- outlines of data points with a positive trend (i.e. bars/candlesticks with close >= open).
-
- If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref
- setBrush).
-
- \see setPenNegative, setBrushPositive, setBrushNegative
- */
- void QCPFinancial::setPenPositive(const QPen &pen)
- {
- mPenPositive = pen;
- }
- /*!
- If \ref setTwoColored is set to true, this function controls the pen that is used to draw
- outlines of data points with a negative trend (i.e. bars/candlesticks with close < open).
-
- If \a twoColored is false, the normal plottable's pen and brush are used (\ref setPen, \ref
- setBrush).
-
- \see setPenPositive, setBrushNegative, setBrushPositive
- */
- void QCPFinancial::setPenNegative(const QPen &pen)
- {
- mPenNegative = pen;
- }
- /*! \overload
-
- Adds the provided points in \a keys, \a open, \a high, \a low and \a close to the current data.
- The provided vectors should have equal length. Else, the number of added points will be the size
- of the smallest vector.
-
- If you can guarantee that the passed data points are sorted by \a keys in ascending order, you
- can set \a alreadySorted to true, to improve performance by saving a sorting run.
-
- Alternatively, you can also access and modify the data directly via the \ref data method, which
- returns a pointer to the internal data container.
-
- \see timeSeriesToOhlc
- */
- void QCPFinancial::addData(const QVector<double> &keys, const QVector<double> &open, const QVector<double> &high, const QVector<double> &low, const QVector<double> &close, bool alreadySorted)
- {
- if (keys.size() != open.size() || open.size() != high.size() || high.size() != low.size() || low.size() != close.size() || close.size() != keys.size())
- qDebug() << Q_FUNC_INFO << "keys, open, high, low, close have different sizes:" << keys.size() << open.size() << high.size() << low.size() << close.size();
- const int n = qMin(keys.size(), qMin(open.size(), qMin(high.size(), qMin(low.size(), close.size()))));
- QVector<QCPFinancialData> tempData(n);
- QVector<QCPFinancialData>::iterator it = tempData.begin();
- const QVector<QCPFinancialData>::iterator itEnd = tempData.end();
- int i = 0;
- while (it != itEnd)
- {
- it->key = keys[i];
- it->open = open[i];
- it->high = high[i];
- it->low = low[i];
- it->close = close[i];
- ++it;
- ++i;
- }
- mDataContainer->add(tempData, alreadySorted); // don't modify tempData beyond this to prevent copy on write
- }
- /*! \overload
-
- Adds the provided data point as \a key, \a open, \a high, \a low and \a close to the current
- data.
-
- Alternatively, you can also access and modify the data directly via the \ref data method, which
- returns a pointer to the internal data container.
-
- \see timeSeriesToOhlc
- */
- void QCPFinancial::addData(double key, double open, double high, double low, double close)
- {
- mDataContainer->add(QCPFinancialData(key, open, high, low, close));
- }
- /*!
- \copydoc QCPPlottableInterface1D::selectTestRect
- */
- QCPDataSelection QCPFinancial::selectTestRect(const QRectF &rect, bool onlySelectable) const
- {
- QCPDataSelection result;
- if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
- return result;
- if (!mKeyAxis || !mValueAxis)
- return result;
-
- QCPFinancialDataContainer::const_iterator visibleBegin, visibleEnd;
- getVisibleDataBounds(visibleBegin, visibleEnd);
-
- for (QCPFinancialDataContainer::const_iterator it=visibleBegin; it!=visibleEnd; ++it)
- {
- if (rect.intersects(selectionHitBox(it)))
- result.addDataRange(QCPDataRange(it-mDataContainer->constBegin(), it-mDataContainer->constBegin()+1), false);
- }
- result.simplify();
- return result;
- }
- /* inherits documentation from base class */
- double QCPFinancial::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- Q_UNUSED(details)
- if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
- return -1;
- if (!mKeyAxis || !mValueAxis)
- return -1;
-
- if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()))
- {
- // get visible data range:
- QCPFinancialDataContainer::const_iterator visibleBegin, visibleEnd;
- QCPFinancialDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd();
- getVisibleDataBounds(visibleBegin, visibleEnd);
- // perform select test according to configured style:
- double result = -1;
- switch (mChartStyle)
- {
- case QCPFinancial::csOhlc:
- result = ohlcSelectTest(pos, visibleBegin, visibleEnd, closestDataPoint); break;
- case QCPFinancial::csCandlestick:
- result = candlestickSelectTest(pos, visibleBegin, visibleEnd, closestDataPoint); break;
- }
- if (details)
- {
- int pointIndex = closestDataPoint-mDataContainer->constBegin();
- details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex+1)));
- }
- return result;
- }
-
- return -1;
- }
- /* inherits documentation from base class */
- QCPRange QCPFinancial::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
- {
- QCPRange range = mDataContainer->keyRange(foundRange, inSignDomain);
- // determine exact range by including width of bars/flags:
- if (foundRange)
- {
- if (inSignDomain != QCP::sdPositive || range.lower-mWidth*0.5 > 0)
- range.lower -= mWidth*0.5;
- if (inSignDomain != QCP::sdNegative || range.upper+mWidth*0.5 < 0)
- range.upper += mWidth*0.5;
- }
- return range;
- }
- /* inherits documentation from base class */
- QCPRange QCPFinancial::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
- {
- return mDataContainer->valueRange(foundRange, inSignDomain, inKeyRange);
- }
- /*!
- A convenience function that converts time series data (\a value against \a time) to OHLC binned
- data points. The return value can then be passed on to \ref QCPFinancialDataContainer::set(const
- QCPFinancialDataContainer&).
-
- The size of the bins can be controlled with \a timeBinSize in the same units as \a time is given.
- For example, if the unit of \a time is seconds and single OHLC/Candlesticks should span an hour
- each, set \a timeBinSize to 3600.
-
- \a timeBinOffset allows to control precisely at what \a time coordinate a bin should start. The
- value passed as \a timeBinOffset doesn't need to be in the range encompassed by the \a time keys.
- It merely defines the mathematical offset/phase of the bins that will be used to process the
- data.
- */
- QCPFinancialDataContainer QCPFinancial::timeSeriesToOhlc(const QVector<double> &time, const QVector<double> &value, double timeBinSize, double timeBinOffset)
- {
- QCPFinancialDataContainer data;
- int count = qMin(time.size(), value.size());
- if (count == 0)
- return QCPFinancialDataContainer();
-
- QCPFinancialData currentBinData(0, value.first(), value.first(), value.first(), value.first());
- int currentBinIndex = qFloor((time.first()-timeBinOffset)/timeBinSize+0.5);
- for (int i=0; i<count; ++i)
- {
- int index = qFloor((time.at(i)-timeBinOffset)/timeBinSize+0.5);
- if (currentBinIndex == index) // data point still in current bin, extend high/low:
- {
- if (value.at(i) < currentBinData.low) currentBinData.low = value.at(i);
- if (value.at(i) > currentBinData.high) currentBinData.high = value.at(i);
- if (i == count-1) // last data point is in current bin, finalize bin:
- {
- currentBinData.close = value.at(i);
- currentBinData.key = timeBinOffset+(index)*timeBinSize;
- data.add(currentBinData);
- }
- } else // data point not anymore in current bin, set close of old and open of new bin, and add old to map:
- {
- // finalize current bin:
- currentBinData.close = value.at(i-1);
- currentBinData.key = timeBinOffset+(index-1)*timeBinSize;
- data.add(currentBinData);
- // start next bin:
- currentBinIndex = index;
- currentBinData.open = value.at(i);
- currentBinData.high = value.at(i);
- currentBinData.low = value.at(i);
- }
- }
-
- return data;
- }
- /* inherits documentation from base class */
- void QCPFinancial::draw(QCPPainter *painter)
- {
- // get visible data range:
- QCPFinancialDataContainer::const_iterator visibleBegin, visibleEnd;
- getVisibleDataBounds(visibleBegin, visibleEnd);
-
- // loop over and draw segments of unselected/selected data:
- QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
- getDataSegments(selectedSegments, unselectedSegments);
- allSegments << unselectedSegments << selectedSegments;
- for (int i=0; i<allSegments.size(); ++i)
- {
- bool isSelectedSegment = i >= unselectedSegments.size();
- QCPFinancialDataContainer::const_iterator begin = visibleBegin;
- QCPFinancialDataContainer::const_iterator end = visibleEnd;
- mDataContainer->limitIteratorsToDataRange(begin, end, allSegments.at(i));
- if (begin == end)
- continue;
-
- // draw data segment according to configured style:
- switch (mChartStyle)
- {
- case QCPFinancial::csOhlc:
- drawOhlcPlot(painter, begin, end, isSelectedSegment); break;
- case QCPFinancial::csCandlestick:
- drawCandlestickPlot(painter, begin, end, isSelectedSegment); break;
- }
- }
-
- // draw other selection decoration that isn't just line/scatter pens and brushes:
- if (mSelectionDecorator)
- mSelectionDecorator->drawDecoration(painter, selection());
- }
- /* inherits documentation from base class */
- void QCPFinancial::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
- {
- painter->setAntialiasing(false); // legend icon especially of csCandlestick looks better without antialiasing
- if (mChartStyle == csOhlc)
- {
- if (mTwoColored)
- {
- // draw upper left half icon with positive color:
- painter->setBrush(mBrushPositive);
- painter->setPen(mPenPositive);
- painter->setClipRegion(QRegion(QPolygon() << rect.bottomLeft().toPoint() << rect.topRight().toPoint() << rect.topLeft().toPoint()));
- painter->drawLine(QLineF(0, rect.height()*0.5, rect.width(), rect.height()*0.5).translated(rect.topLeft()));
- painter->drawLine(QLineF(rect.width()*0.2, rect.height()*0.3, rect.width()*0.2, rect.height()*0.5).translated(rect.topLeft()));
- painter->drawLine(QLineF(rect.width()*0.8, rect.height()*0.5, rect.width()*0.8, rect.height()*0.7).translated(rect.topLeft()));
- // draw bottom right half icon with negative color:
- painter->setBrush(mBrushNegative);
- painter->setPen(mPenNegative);
- painter->setClipRegion(QRegion(QPolygon() << rect.bottomLeft().toPoint() << rect.topRight().toPoint() << rect.bottomRight().toPoint()));
- painter->drawLine(QLineF(0, rect.height()*0.5, rect.width(), rect.height()*0.5).translated(rect.topLeft()));
- painter->drawLine(QLineF(rect.width()*0.2, rect.height()*0.3, rect.width()*0.2, rect.height()*0.5).translated(rect.topLeft()));
- painter->drawLine(QLineF(rect.width()*0.8, rect.height()*0.5, rect.width()*0.8, rect.height()*0.7).translated(rect.topLeft()));
- } else
- {
- painter->setBrush(mBrush);
- painter->setPen(mPen);
- painter->drawLine(QLineF(0, rect.height()*0.5, rect.width(), rect.height()*0.5).translated(rect.topLeft()));
- painter->drawLine(QLineF(rect.width()*0.2, rect.height()*0.3, rect.width()*0.2, rect.height()*0.5).translated(rect.topLeft()));
- painter->drawLine(QLineF(rect.width()*0.8, rect.height()*0.5, rect.width()*0.8, rect.height()*0.7).translated(rect.topLeft()));
- }
- } else if (mChartStyle == csCandlestick)
- {
- if (mTwoColored)
- {
- // draw upper left half icon with positive color:
- painter->setBrush(mBrushPositive);
- painter->setPen(mPenPositive);
- painter->setClipRegion(QRegion(QPolygon() << rect.bottomLeft().toPoint() << rect.topRight().toPoint() << rect.topLeft().toPoint()));
- painter->drawLine(QLineF(0, rect.height()*0.5, rect.width()*0.25, rect.height()*0.5).translated(rect.topLeft()));
- painter->drawLine(QLineF(rect.width()*0.75, rect.height()*0.5, rect.width(), rect.height()*0.5).translated(rect.topLeft()));
- painter->drawRect(QRectF(rect.width()*0.25, rect.height()*0.25, rect.width()*0.5, rect.height()*0.5).translated(rect.topLeft()));
- // draw bottom right half icon with negative color:
- painter->setBrush(mBrushNegative);
- painter->setPen(mPenNegative);
- painter->setClipRegion(QRegion(QPolygon() << rect.bottomLeft().toPoint() << rect.topRight().toPoint() << rect.bottomRight().toPoint()));
- painter->drawLine(QLineF(0, rect.height()*0.5, rect.width()*0.25, rect.height()*0.5).translated(rect.topLeft()));
- painter->drawLine(QLineF(rect.width()*0.75, rect.height()*0.5, rect.width(), rect.height()*0.5).translated(rect.topLeft()));
- painter->drawRect(QRectF(rect.width()*0.25, rect.height()*0.25, rect.width()*0.5, rect.height()*0.5).translated(rect.topLeft()));
- } else
- {
- painter->setBrush(mBrush);
- painter->setPen(mPen);
- painter->drawLine(QLineF(0, rect.height()*0.5, rect.width()*0.25, rect.height()*0.5).translated(rect.topLeft()));
- painter->drawLine(QLineF(rect.width()*0.75, rect.height()*0.5, rect.width(), rect.height()*0.5).translated(rect.topLeft()));
- painter->drawRect(QRectF(rect.width()*0.25, rect.height()*0.25, rect.width()*0.5, rect.height()*0.5).translated(rect.topLeft()));
- }
- }
- }
- /*! \internal
-
- Draws the data from \a begin to \a end-1 as OHLC bars with the provided \a painter.
- This method is a helper function for \ref draw. It is used when the chart style is \ref csOhlc.
- */
- void QCPFinancial::drawOhlcPlot(QCPPainter *painter, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, bool isSelected)
- {
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
-
- if (keyAxis->orientation() == Qt::Horizontal)
- {
- for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it)
- {
- if (isSelected && mSelectionDecorator)
- mSelectionDecorator->applyPen(painter);
- else if (mTwoColored)
- painter->setPen(it->close >= it->open ? mPenPositive : mPenNegative);
- else
- painter->setPen(mPen);
- double keyPixel = keyAxis->coordToPixel(it->key);
- double openPixel = valueAxis->coordToPixel(it->open);
- double closePixel = valueAxis->coordToPixel(it->close);
- // draw backbone:
- painter->drawLine(QPointF(keyPixel, valueAxis->coordToPixel(it->high)), QPointF(keyPixel, valueAxis->coordToPixel(it->low)));
- // draw open:
- double pixelWidth = getPixelWidth(it->key, keyPixel); // sign of this makes sure open/close are on correct sides
- painter->drawLine(QPointF(keyPixel-pixelWidth, openPixel), QPointF(keyPixel, openPixel));
- // draw close:
- painter->drawLine(QPointF(keyPixel, closePixel), QPointF(keyPixel+pixelWidth, closePixel));
- }
- } else
- {
- for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it)
- {
- if (isSelected && mSelectionDecorator)
- mSelectionDecorator->applyPen(painter);
- else if (mTwoColored)
- painter->setPen(it->close >= it->open ? mPenPositive : mPenNegative);
- else
- painter->setPen(mPen);
- double keyPixel = keyAxis->coordToPixel(it->key);
- double openPixel = valueAxis->coordToPixel(it->open);
- double closePixel = valueAxis->coordToPixel(it->close);
- // draw backbone:
- painter->drawLine(QPointF(valueAxis->coordToPixel(it->high), keyPixel), QPointF(valueAxis->coordToPixel(it->low), keyPixel));
- // draw open:
- double pixelWidth = getPixelWidth(it->key, keyPixel); // sign of this makes sure open/close are on correct sides
- painter->drawLine(QPointF(openPixel, keyPixel-pixelWidth), QPointF(openPixel, keyPixel));
- // draw close:
- painter->drawLine(QPointF(closePixel, keyPixel), QPointF(closePixel, keyPixel+pixelWidth));
- }
- }
- }
- /*! \internal
-
- Draws the data from \a begin to \a end-1 as Candlesticks with the provided \a painter.
- This method is a helper function for \ref draw. It is used when the chart style is \ref csCandlestick.
- */
- void QCPFinancial::drawCandlestickPlot(QCPPainter *painter, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, bool isSelected)
- {
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
-
- if (keyAxis->orientation() == Qt::Horizontal)
- {
- for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it)
- {
- if (isSelected && mSelectionDecorator)
- {
- mSelectionDecorator->applyPen(painter);
- mSelectionDecorator->applyBrush(painter);
- } else if (mTwoColored)
- {
- painter->setPen(it->close >= it->open ? mPenPositive : mPenNegative);
- painter->setBrush(it->close >= it->open ? mBrushPositive : mBrushNegative);
- } else
- {
- painter->setPen(mPen);
- painter->setBrush(mBrush);
- }
- double keyPixel = keyAxis->coordToPixel(it->key);
- double openPixel = valueAxis->coordToPixel(it->open);
- double closePixel = valueAxis->coordToPixel(it->close);
- // draw high:
- painter->drawLine(QPointF(keyPixel, valueAxis->coordToPixel(it->high)), QPointF(keyPixel, valueAxis->coordToPixel(qMax(it->open, it->close))));
- // draw low:
- painter->drawLine(QPointF(keyPixel, valueAxis->coordToPixel(it->low)), QPointF(keyPixel, valueAxis->coordToPixel(qMin(it->open, it->close))));
- // draw open-close box:
- double pixelWidth = getPixelWidth(it->key, keyPixel);
- painter->drawRect(QRectF(QPointF(keyPixel-pixelWidth, closePixel), QPointF(keyPixel+pixelWidth, openPixel)));
- }
- } else // keyAxis->orientation() == Qt::Vertical
- {
- for (QCPFinancialDataContainer::const_iterator it = begin; it != end; ++it)
- {
- if (isSelected && mSelectionDecorator)
- {
- mSelectionDecorator->applyPen(painter);
- mSelectionDecorator->applyBrush(painter);
- } else if (mTwoColored)
- {
- painter->setPen(it->close >= it->open ? mPenPositive : mPenNegative);
- painter->setBrush(it->close >= it->open ? mBrushPositive : mBrushNegative);
- } else
- {
- painter->setPen(mPen);
- painter->setBrush(mBrush);
- }
- double keyPixel = keyAxis->coordToPixel(it->key);
- double openPixel = valueAxis->coordToPixel(it->open);
- double closePixel = valueAxis->coordToPixel(it->close);
- // draw high:
- painter->drawLine(QPointF(valueAxis->coordToPixel(it->high), keyPixel), QPointF(valueAxis->coordToPixel(qMax(it->open, it->close)), keyPixel));
- // draw low:
- painter->drawLine(QPointF(valueAxis->coordToPixel(it->low), keyPixel), QPointF(valueAxis->coordToPixel(qMin(it->open, it->close)), keyPixel));
- // draw open-close box:
- double pixelWidth = getPixelWidth(it->key, keyPixel);
- painter->drawRect(QRectF(QPointF(closePixel, keyPixel-pixelWidth), QPointF(openPixel, keyPixel+pixelWidth)));
- }
- }
- }
- /*! \internal
- This function is used to determine the width of the bar at coordinate \a key, according to the
- specified width (\ref setWidth) and width type (\ref setWidthType). Provide the pixel position of
- \a key in \a keyPixel (because usually this was already calculated via \ref QCPAxis::coordToPixel
- when this function is called).
- It returns the number of pixels the bar extends to higher keys, relative to the \a key
- coordinate. So with a non-reversed horizontal axis, the return value is positive. With a reversed
- horizontal axis, the return value is negative. This is important so the open/close flags on the
- \ref csOhlc bar are drawn to the correct side.
- */
- double QCPFinancial::getPixelWidth(double key, double keyPixel) const
- {
- double result = 0;
- switch (mWidthType)
- {
- case wtAbsolute:
- {
- if (mKeyAxis)
- result = mWidth*0.5*mKeyAxis.data()->pixelOrientation();
- break;
- }
- case wtAxisRectRatio:
- {
- if (mKeyAxis && mKeyAxis.data()->axisRect())
- {
- if (mKeyAxis.data()->orientation() == Qt::Horizontal)
- result = mKeyAxis.data()->axisRect()->width()*mWidth*0.5*mKeyAxis.data()->pixelOrientation();
- else
- result = mKeyAxis.data()->axisRect()->height()*mWidth*0.5*mKeyAxis.data()->pixelOrientation();
- } else
- qDebug() << Q_FUNC_INFO << "No key axis or axis rect defined";
- break;
- }
- case wtPlotCoords:
- {
- if (mKeyAxis)
- result = mKeyAxis.data()->coordToPixel(key+mWidth*0.5)-keyPixel;
- else
- qDebug() << Q_FUNC_INFO << "No key axis defined";
- break;
- }
- }
- return result;
- }
- /*! \internal
- This method is a helper function for \ref selectTest. It is used to test for selection when the
- chart style is \ref csOhlc. It only tests against the data points between \a begin and \a end.
-
- Like \ref selectTest, this method returns the shortest distance of \a pos to the graphical
- representation of the plottable, and \a closestDataPoint will point to the respective data point.
- */
- double QCPFinancial::ohlcSelectTest(const QPointF &pos, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, QCPFinancialDataContainer::const_iterator &closestDataPoint) const
- {
- closestDataPoint = mDataContainer->constEnd();
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return -1; }
- double minDistSqr = std::numeric_limits<double>::max();
- if (keyAxis->orientation() == Qt::Horizontal)
- {
- for (QCPFinancialDataContainer::const_iterator it=begin; it!=end; ++it)
- {
- double keyPixel = keyAxis->coordToPixel(it->key);
- // calculate distance to backbone:
- double currentDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(keyPixel, valueAxis->coordToPixel(it->high)), QCPVector2D(keyPixel, valueAxis->coordToPixel(it->low)));
- if (currentDistSqr < minDistSqr)
- {
- minDistSqr = currentDistSqr;
- closestDataPoint = it;
- }
- }
- } else // keyAxis->orientation() == Qt::Vertical
- {
- for (QCPFinancialDataContainer::const_iterator it=begin; it!=end; ++it)
- {
- double keyPixel = keyAxis->coordToPixel(it->key);
- // calculate distance to backbone:
- double currentDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(valueAxis->coordToPixel(it->high), keyPixel), QCPVector2D(valueAxis->coordToPixel(it->low), keyPixel));
- if (currentDistSqr < minDistSqr)
- {
- minDistSqr = currentDistSqr;
- closestDataPoint = it;
- }
- }
- }
- return qSqrt(minDistSqr);
- }
- /*! \internal
-
- This method is a helper function for \ref selectTest. It is used to test for selection when the
- chart style is \ref csCandlestick. It only tests against the data points between \a begin and \a
- end.
-
- Like \ref selectTest, this method returns the shortest distance of \a pos to the graphical
- representation of the plottable, and \a closestDataPoint will point to the respective data point.
- */
- double QCPFinancial::candlestickSelectTest(const QPointF &pos, const QCPFinancialDataContainer::const_iterator &begin, const QCPFinancialDataContainer::const_iterator &end, QCPFinancialDataContainer::const_iterator &closestDataPoint) const
- {
- closestDataPoint = mDataContainer->constEnd();
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return -1; }
- double minDistSqr = std::numeric_limits<double>::max();
- if (keyAxis->orientation() == Qt::Horizontal)
- {
- for (QCPFinancialDataContainer::const_iterator it=begin; it!=end; ++it)
- {
- double currentDistSqr;
- // determine whether pos is in open-close-box:
- QCPRange boxKeyRange(it->key-mWidth*0.5, it->key+mWidth*0.5);
- QCPRange boxValueRange(it->close, it->open);
- double posKey, posValue;
- pixelsToCoords(pos, posKey, posValue);
- if (boxKeyRange.contains(posKey) && boxValueRange.contains(posValue)) // is in open-close-box
- {
- currentDistSqr = mParentPlot->selectionTolerance()*0.99 * mParentPlot->selectionTolerance()*0.99;
- } else
- {
- // calculate distance to high/low lines:
- double keyPixel = keyAxis->coordToPixel(it->key);
- double highLineDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(keyPixel, valueAxis->coordToPixel(it->high)), QCPVector2D(keyPixel, valueAxis->coordToPixel(qMax(it->open, it->close))));
- double lowLineDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(keyPixel, valueAxis->coordToPixel(it->low)), QCPVector2D(keyPixel, valueAxis->coordToPixel(qMin(it->open, it->close))));
- currentDistSqr = qMin(highLineDistSqr, lowLineDistSqr);
- }
- if (currentDistSqr < minDistSqr)
- {
- minDistSqr = currentDistSqr;
- closestDataPoint = it;
- }
- }
- } else // keyAxis->orientation() == Qt::Vertical
- {
- for (QCPFinancialDataContainer::const_iterator it=begin; it!=end; ++it)
- {
- double currentDistSqr;
- // determine whether pos is in open-close-box:
- QCPRange boxKeyRange(it->key-mWidth*0.5, it->key+mWidth*0.5);
- QCPRange boxValueRange(it->close, it->open);
- double posKey, posValue;
- pixelsToCoords(pos, posKey, posValue);
- if (boxKeyRange.contains(posKey) && boxValueRange.contains(posValue)) // is in open-close-box
- {
- currentDistSqr = mParentPlot->selectionTolerance()*0.99 * mParentPlot->selectionTolerance()*0.99;
- } else
- {
- // calculate distance to high/low lines:
- double keyPixel = keyAxis->coordToPixel(it->key);
- double highLineDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(valueAxis->coordToPixel(it->high), keyPixel), QCPVector2D(valueAxis->coordToPixel(qMax(it->open, it->close)), keyPixel));
- double lowLineDistSqr = QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(valueAxis->coordToPixel(it->low), keyPixel), QCPVector2D(valueAxis->coordToPixel(qMin(it->open, it->close)), keyPixel));
- currentDistSqr = qMin(highLineDistSqr, lowLineDistSqr);
- }
- if (currentDistSqr < minDistSqr)
- {
- minDistSqr = currentDistSqr;
- closestDataPoint = it;
- }
- }
- }
- return qSqrt(minDistSqr);
- }
- /*! \internal
-
- called by the drawing methods to determine which data (key) range is visible at the current key
- axis range setting, so only that needs to be processed.
-
- \a begin returns an iterator to the lowest data point that needs to be taken into account when
- plotting. Note that in order to get a clean plot all the way to the edge of the axis rect, \a
- begin may still be just outside the visible range.
-
- \a end returns the iterator just above the highest data point that needs to be taken into
- account. Same as before, \a end may also lie just outside of the visible range
-
- if the plottable contains no data, both \a begin and \a end point to \c constEnd.
- */
- void QCPFinancial::getVisibleDataBounds(QCPFinancialDataContainer::const_iterator &begin, QCPFinancialDataContainer::const_iterator &end) const
- {
- if (!mKeyAxis)
- {
- qDebug() << Q_FUNC_INFO << "invalid key axis";
- begin = mDataContainer->constEnd();
- end = mDataContainer->constEnd();
- return;
- }
- begin = mDataContainer->findBegin(mKeyAxis.data()->range().lower-mWidth*0.5); // subtract half width of ohlc/candlestick to include partially visible data points
- end = mDataContainer->findEnd(mKeyAxis.data()->range().upper+mWidth*0.5); // add half width of ohlc/candlestick to include partially visible data points
- }
- /*! \internal
- Returns the hit box in pixel coordinates that will be used for data selection with the selection
- rect (\ref selectTestRect), of the data point given by \a it.
- */
- QRectF QCPFinancial::selectionHitBox(QCPFinancialDataContainer::const_iterator it) const
- {
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return QRectF(); }
-
- double keyPixel = keyAxis->coordToPixel(it->key);
- double highPixel = valueAxis->coordToPixel(it->high);
- double lowPixel = valueAxis->coordToPixel(it->low);
- double keyWidthPixels = keyPixel-keyAxis->coordToPixel(it->key-mWidth*0.5);
- if (keyAxis->orientation() == Qt::Horizontal)
- return QRectF(keyPixel-keyWidthPixels, highPixel, keyWidthPixels*2, lowPixel-highPixel).normalized();
- else
- return QRectF(highPixel, keyPixel-keyWidthPixels, lowPixel-highPixel, keyWidthPixels*2).normalized();
- }
- /* end of 'src/plottables/plottable-financial.cpp' */
- /* including file 'src/plottables/plottable-errorbar.cpp', size 37355 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPErrorBarsData
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPErrorBarsData
- \brief Holds the data of one single error bar for QCPErrorBars.
- The stored data is:
- \li \a errorMinus: how much the error bar extends towards negative coordinates from the data
- point position
- \li \a errorPlus: how much the error bar extends towards positive coordinates from the data point
- position
- The container for storing the error bar information is \ref QCPErrorBarsDataContainer. It is a
- typedef for <tt>QVector<\ref QCPErrorBarsData></tt>.
- \see QCPErrorBarsDataContainer
- */
- /*!
- Constructs an error bar with errors set to zero.
- */
- QCPErrorBarsData::QCPErrorBarsData() :
- errorMinus(0),
- errorPlus(0)
- {
- }
- /*!
- Constructs an error bar with equal \a error in both negative and positive direction.
- */
- QCPErrorBarsData::QCPErrorBarsData(double error) :
- errorMinus(error),
- errorPlus(error)
- {
- }
- /*!
- Constructs an error bar with negative and positive errors set to \a errorMinus and \a errorPlus,
- respectively.
- */
- QCPErrorBarsData::QCPErrorBarsData(double errorMinus, double errorPlus) :
- errorMinus(errorMinus),
- errorPlus(errorPlus)
- {
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPErrorBars
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPErrorBars
- \brief A plottable that adds a set of error bars to other plottables.
- \image html QCPErrorBars.png
- The \ref QCPErrorBars plottable can be attached to other one-dimensional plottables (e.g. \ref
- QCPGraph, \ref QCPCurve, \ref QCPBars, etc.) and equips them with error bars.
- Use \ref setDataPlottable to define for which plottable the \ref QCPErrorBars shall display the
- error bars. The orientation of the error bars can be controlled with \ref setErrorType.
- By using \ref setData, you can supply the actual error data, either as symmetric error or
- plus/minus asymmetric errors. \ref QCPErrorBars only stores the error data. The absolute
- key/value position of each error bar will be adopted from the configured data plottable. The
- error data of the \ref QCPErrorBars are associated one-to-one via their index to the data points
- of the data plottable. You can directly access and manipulate the error bar data via \ref data.
- Set either of the plus/minus errors to NaN (<tt>qQNaN()</tt> or
- <tt>std::numeric_limits<double>::quiet_NaN()</tt>) to not show the respective error bar on the data point at
- that index.
- \section qcperrorbars-appearance Changing the appearance
- The appearance of the error bars is defined by the pen (\ref setPen), and the width of the
- whiskers (\ref setWhiskerWidth). Further, the error bar backbones may leave a gap around the data
- point center to prevent that error bars are drawn too close to or even through scatter points.
- This gap size can be controlled via \ref setSymbolGap.
- */
- /* start of documentation of inline functions */
- /*! \fn QSharedPointer<QCPErrorBarsDataContainer> QCPErrorBars::data() const
- Returns a shared pointer to the internal data storage of type \ref QCPErrorBarsDataContainer. You
- may use it to directly manipulate the error values, which may be more convenient and faster than
- using the regular \ref setData methods.
- */
- /* end of documentation of inline functions */
- /*!
- Constructs an error bars plottable which uses \a keyAxis as its key axis ("x") and \a valueAxis as its value
- axis ("y"). \a keyAxis and \a valueAxis must reside in the same QCustomPlot instance and not have
- the same orientation. If either of these restrictions is violated, a corresponding message is
- printed to the debug output (qDebug), the construction is not aborted, though.
- It is also important that the \a keyAxis and \a valueAxis are the same for the error bars
- plottable and the data plottable that the error bars shall be drawn on (\ref setDataPlottable).
- The created \ref QCPErrorBars is automatically registered with the QCustomPlot instance inferred
- from \a keyAxis. This QCustomPlot instance takes ownership of the \ref QCPErrorBars, so do not
- delete it manually but use \ref QCustomPlot::removePlottable() instead.
- */
- QCPErrorBars::QCPErrorBars(QCPAxis *keyAxis, QCPAxis *valueAxis) :
- QCPAbstractPlottable(keyAxis, valueAxis),
- mDataContainer(new QVector<QCPErrorBarsData>),
- mErrorType(etValueError),
- mWhiskerWidth(9),
- mSymbolGap(10)
- {
- setPen(QPen(Qt::black, 0));
- setBrush(Qt::NoBrush);
- }
- QCPErrorBars::~QCPErrorBars()
- {
- }
- /*! \overload
- Replaces the current data container with the provided \a data container.
- Since a QSharedPointer is used, multiple \ref QCPErrorBars instances may share the same data
- container safely. Modifying the data in the container will then affect all \ref QCPErrorBars
- instances that share the container. Sharing can be achieved by simply exchanging the data
- containers wrapped in shared pointers:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcperrorbars-datasharing-1
- If you do not wish to share containers, but create a copy from an existing container, assign the
- data containers directly:
- \snippet documentation/doc-code-snippets/mainwindow.cpp qcperrorbars-datasharing-2
- (This uses different notation compared with other plottables, because the \ref QCPErrorBars
- uses a \c QVector<QCPErrorBarsData> as its data container, instead of a \ref QCPDataContainer.)
- \see addData
- */
- void QCPErrorBars::setData(QSharedPointer<QCPErrorBarsDataContainer> data)
- {
- mDataContainer = data;
- }
- /*! \overload
- Sets symmetrical error values as specified in \a error. The errors will be associated one-to-one
- by the data point index to the associated data plottable (\ref setDataPlottable).
- You can directly access and manipulate the error bar data via \ref data.
- \see addData
- */
- void QCPErrorBars::setData(const QVector<double> &error)
- {
- mDataContainer->clear();
- addData(error);
- }
- /*! \overload
- Sets asymmetrical errors as specified in \a errorMinus and \a errorPlus. The errors will be
- associated one-to-one by the data point index to the associated data plottable (\ref
- setDataPlottable).
- You can directly access and manipulate the error bar data via \ref data.
- \see addData
- */
- void QCPErrorBars::setData(const QVector<double> &errorMinus, const QVector<double> &errorPlus)
- {
- mDataContainer->clear();
- addData(errorMinus, errorPlus);
- }
- /*!
- Sets the data plottable to which the error bars will be applied. The error values specified e.g.
- via \ref setData will be associated one-to-one by the data point index to the data points of \a
- plottable. This means that the error bars will adopt the key/value coordinates of the data point
- with the same index.
- The passed \a plottable must be a one-dimensional plottable, i.e. it must implement the \ref
- QCPPlottableInterface1D. Further, it must not be a \ref QCPErrorBars instance itself. If either
- of these restrictions is violated, a corresponding qDebug output is generated, and the data
- plottable of this \ref QCPErrorBars instance is set to zero.
- For proper display, care must also be taken that the key and value axes of the \a plottable match
- those configured for this \ref QCPErrorBars instance.
- */
- void QCPErrorBars::setDataPlottable(QCPAbstractPlottable *plottable)
- {
- if (plottable && qobject_cast<QCPErrorBars*>(plottable))
- {
- mDataPlottable = 0;
- qDebug() << Q_FUNC_INFO << "can't set another QCPErrorBars instance as data plottable";
- return;
- }
- if (plottable && !plottable->interface1D())
- {
- mDataPlottable = 0;
- qDebug() << Q_FUNC_INFO << "passed plottable doesn't implement 1d interface, can't associate with QCPErrorBars";
- return;
- }
-
- mDataPlottable = plottable;
- }
- /*!
- Sets in which orientation the error bars shall appear on the data points. If your data needs both
- error dimensions, create two \ref QCPErrorBars with different \a type.
- */
- void QCPErrorBars::setErrorType(ErrorType type)
- {
- mErrorType = type;
- }
- /*!
- Sets the width of the whiskers (the short bars at the end of the actual error bar backbones) to
- \a pixels.
- */
- void QCPErrorBars::setWhiskerWidth(double pixels)
- {
- mWhiskerWidth = pixels;
- }
- /*!
- Sets the gap diameter around the data points that will be left out when drawing the error bar
- backbones. This gap prevents that error bars are drawn too close to or even through scatter
- points.
- */
- void QCPErrorBars::setSymbolGap(double pixels)
- {
- mSymbolGap = pixels;
- }
- /*! \overload
- Adds symmetrical error values as specified in \a error. The errors will be associated one-to-one
- by the data point index to the associated data plottable (\ref setDataPlottable).
- You can directly access and manipulate the error bar data via \ref data.
- \see setData
- */
- void QCPErrorBars::addData(const QVector<double> &error)
- {
- addData(error, error);
- }
- /*! \overload
- Adds asymmetrical errors as specified in \a errorMinus and \a errorPlus. The errors will be
- associated one-to-one by the data point index to the associated data plottable (\ref
- setDataPlottable).
- You can directly access and manipulate the error bar data via \ref data.
- \see setData
- */
- void QCPErrorBars::addData(const QVector<double> &errorMinus, const QVector<double> &errorPlus)
- {
- if (errorMinus.size() != errorPlus.size())
- qDebug() << Q_FUNC_INFO << "minus and plus error vectors have different sizes:" << errorMinus.size() << errorPlus.size();
- const int n = qMin(errorMinus.size(), errorPlus.size());
- mDataContainer->reserve(n);
- for (int i=0; i<n; ++i)
- mDataContainer->append(QCPErrorBarsData(errorMinus.at(i), errorPlus.at(i)));
- }
- /*! \overload
- Adds a single symmetrical error bar as specified in \a error. The errors will be associated
- one-to-one by the data point index to the associated data plottable (\ref setDataPlottable).
- You can directly access and manipulate the error bar data via \ref data.
- \see setData
- */
- void QCPErrorBars::addData(double error)
- {
- mDataContainer->append(QCPErrorBarsData(error));
- }
- /*! \overload
- Adds a single asymmetrical error bar as specified in \a errorMinus and \a errorPlus. The errors
- will be associated one-to-one by the data point index to the associated data plottable (\ref
- setDataPlottable).
- You can directly access and manipulate the error bar data via \ref data.
- \see setData
- */
- void QCPErrorBars::addData(double errorMinus, double errorPlus)
- {
- mDataContainer->append(QCPErrorBarsData(errorMinus, errorPlus));
- }
- /* inherits documentation from base class */
- int QCPErrorBars::dataCount() const
- {
- return mDataContainer->size();
- }
- /* inherits documentation from base class */
- double QCPErrorBars::dataMainKey(int index) const
- {
- if (mDataPlottable)
- return mDataPlottable->interface1D()->dataMainKey(index);
- else
- qDebug() << Q_FUNC_INFO << "no data plottable set";
- return 0;
- }
- /* inherits documentation from base class */
- double QCPErrorBars::dataSortKey(int index) const
- {
- if (mDataPlottable)
- return mDataPlottable->interface1D()->dataSortKey(index);
- else
- qDebug() << Q_FUNC_INFO << "no data plottable set";
- return 0;
- }
- /* inherits documentation from base class */
- double QCPErrorBars::dataMainValue(int index) const
- {
- if (mDataPlottable)
- return mDataPlottable->interface1D()->dataMainValue(index);
- else
- qDebug() << Q_FUNC_INFO << "no data plottable set";
- return 0;
- }
- /* inherits documentation from base class */
- QCPRange QCPErrorBars::dataValueRange(int index) const
- {
- if (mDataPlottable)
- {
- const double value = mDataPlottable->interface1D()->dataMainValue(index);
- if (index >= 0 && index < mDataContainer->size() && mErrorType == etValueError)
- return QCPRange(value-mDataContainer->at(index).errorMinus, value+mDataContainer->at(index).errorPlus);
- else
- return QCPRange(value, value);
- } else
- {
- qDebug() << Q_FUNC_INFO << "no data plottable set";
- return QCPRange();
- }
- }
- /* inherits documentation from base class */
- QPointF QCPErrorBars::dataPixelPosition(int index) const
- {
- if (mDataPlottable)
- return mDataPlottable->interface1D()->dataPixelPosition(index);
- else
- qDebug() << Q_FUNC_INFO << "no data plottable set";
- return QPointF();
- }
- /* inherits documentation from base class */
- bool QCPErrorBars::sortKeyIsMainKey() const
- {
- if (mDataPlottable)
- {
- return mDataPlottable->interface1D()->sortKeyIsMainKey();
- } else
- {
- qDebug() << Q_FUNC_INFO << "no data plottable set";
- return true;
- }
- }
- /*!
- \copydoc QCPPlottableInterface1D::selectTestRect
- */
- QCPDataSelection QCPErrorBars::selectTestRect(const QRectF &rect, bool onlySelectable) const
- {
- QCPDataSelection result;
- if (!mDataPlottable)
- return result;
- if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
- return result;
- if (!mKeyAxis || !mValueAxis)
- return result;
-
- QCPErrorBarsDataContainer::const_iterator visibleBegin, visibleEnd;
- getVisibleDataBounds(visibleBegin, visibleEnd, QCPDataRange(0, dataCount()));
-
- QVector<QLineF> backbones, whiskers;
- for (QCPErrorBarsDataContainer::const_iterator it=visibleBegin; it!=visibleEnd; ++it)
- {
- backbones.clear();
- whiskers.clear();
- getErrorBarLines(it, backbones, whiskers);
- for (int i=0; i<backbones.size(); ++i)
- {
- if (rectIntersectsLine(rect, backbones.at(i)))
- {
- result.addDataRange(QCPDataRange(it-mDataContainer->constBegin(), it-mDataContainer->constBegin()+1), false);
- break;
- }
- }
- }
- result.simplify();
- return result;
- }
- /* inherits documentation from base class */
- int QCPErrorBars::findBegin(double sortKey, bool expandedRange) const
- {
- if (mDataPlottable)
- {
- if (mDataContainer->isEmpty())
- return 0;
- int beginIndex = mDataPlottable->interface1D()->findBegin(sortKey, expandedRange);
- if (beginIndex >= mDataContainer->size())
- beginIndex = mDataContainer->size()-1;
- return beginIndex;
- } else
- qDebug() << Q_FUNC_INFO << "no data plottable set";
- return 0;
- }
- /* inherits documentation from base class */
- int QCPErrorBars::findEnd(double sortKey, bool expandedRange) const
- {
- if (mDataPlottable)
- {
- if (mDataContainer->isEmpty())
- return 0;
- int endIndex = mDataPlottable->interface1D()->findEnd(sortKey, expandedRange);
- if (endIndex > mDataContainer->size())
- endIndex = mDataContainer->size();
- return endIndex;
- } else
- qDebug() << Q_FUNC_INFO << "no data plottable set";
- return 0;
- }
- /* inherits documentation from base class */
- double QCPErrorBars::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- if (!mDataPlottable) return -1;
-
- if ((onlySelectable && mSelectable == QCP::stNone) || mDataContainer->isEmpty())
- return -1;
- if (!mKeyAxis || !mValueAxis)
- return -1;
-
- if (mKeyAxis.data()->axisRect()->rect().contains(pos.toPoint()))
- {
- QCPErrorBarsDataContainer::const_iterator closestDataPoint = mDataContainer->constEnd();
- double result = pointDistance(pos, closestDataPoint);
- if (details)
- {
- int pointIndex = closestDataPoint-mDataContainer->constBegin();
- details->setValue(QCPDataSelection(QCPDataRange(pointIndex, pointIndex+1)));
- }
- return result;
- } else
- return -1;
- }
- /* inherits documentation from base class */
- void QCPErrorBars::draw(QCPPainter *painter)
- {
- if (!mDataPlottable) return;
- if (!mKeyAxis || !mValueAxis) { qDebug() << Q_FUNC_INFO << "invalid key or value axis"; return; }
- if (mKeyAxis.data()->range().size() <= 0 || mDataContainer->isEmpty()) return;
-
- // if the sort key isn't the main key, we must check the visibility for each data point/error bar individually
- // (getVisibleDataBounds applies range restriction, but otherwise can only return full data range):
- bool checkPointVisibility = !mDataPlottable->interface1D()->sortKeyIsMainKey();
-
- // check data validity if flag set:
- #ifdef QCUSTOMPLOT_CHECK_DATA
- QCPErrorBarsDataContainer::const_iterator it;
- for (it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it)
- {
- if (QCP::isInvalidData(it->errorMinus, it->errorPlus))
- qDebug() << Q_FUNC_INFO << "Data point at index" << it-mDataContainer->constBegin() << "invalid." << "Plottable name:" << name();
- }
- #endif
-
- applyDefaultAntialiasingHint(painter);
- painter->setBrush(Qt::NoBrush);
- // loop over and draw segments of unselected/selected data:
- QList<QCPDataRange> selectedSegments, unselectedSegments, allSegments;
- getDataSegments(selectedSegments, unselectedSegments);
- allSegments << unselectedSegments << selectedSegments;
- QVector<QLineF> backbones, whiskers;
- for (int i=0; i<allSegments.size(); ++i)
- {
- QCPErrorBarsDataContainer::const_iterator begin, end;
- getVisibleDataBounds(begin, end, allSegments.at(i));
- if (begin == end)
- continue;
-
- bool isSelectedSegment = i >= unselectedSegments.size();
- if (isSelectedSegment && mSelectionDecorator)
- mSelectionDecorator->applyPen(painter);
- else
- painter->setPen(mPen);
- if (painter->pen().capStyle() == Qt::SquareCap)
- {
- QPen capFixPen(painter->pen());
- capFixPen.setCapStyle(Qt::FlatCap);
- painter->setPen(capFixPen);
- }
- backbones.clear();
- whiskers.clear();
- for (QCPErrorBarsDataContainer::const_iterator it=begin; it!=end; ++it)
- {
- if (!checkPointVisibility || errorBarVisible(it-mDataContainer->constBegin()))
- getErrorBarLines(it, backbones, whiskers);
- }
- painter->drawLines(backbones);
- painter->drawLines(whiskers);
- }
-
- // draw other selection decoration that isn't just line/scatter pens and brushes:
- if (mSelectionDecorator)
- mSelectionDecorator->drawDecoration(painter, selection());
- }
- /* inherits documentation from base class */
- void QCPErrorBars::drawLegendIcon(QCPPainter *painter, const QRectF &rect) const
- {
- applyDefaultAntialiasingHint(painter);
- painter->setPen(mPen);
- if (mErrorType == etValueError && mValueAxis && mValueAxis->orientation() == Qt::Vertical)
- {
- painter->drawLine(QLineF(rect.center().x(), rect.top()+2, rect.center().x(), rect.bottom()-1));
- painter->drawLine(QLineF(rect.center().x()-4, rect.top()+2, rect.center().x()+4, rect.top()+2));
- painter->drawLine(QLineF(rect.center().x()-4, rect.bottom()-1, rect.center().x()+4, rect.bottom()-1));
- } else
- {
- painter->drawLine(QLineF(rect.left()+2, rect.center().y(), rect.right()-2, rect.center().y()));
- painter->drawLine(QLineF(rect.left()+2, rect.center().y()-4, rect.left()+2, rect.center().y()+4));
- painter->drawLine(QLineF(rect.right()-2, rect.center().y()-4, rect.right()-2, rect.center().y()+4));
- }
- }
- /* inherits documentation from base class */
- QCPRange QCPErrorBars::getKeyRange(bool &foundRange, QCP::SignDomain inSignDomain) const
- {
- if (!mDataPlottable)
- {
- foundRange = false;
- return QCPRange();
- }
-
- QCPRange range;
- bool haveLower = false;
- bool haveUpper = false;
- QCPErrorBarsDataContainer::const_iterator it;
- for (it = mDataContainer->constBegin(); it != mDataContainer->constEnd(); ++it)
- {
- if (mErrorType == etValueError)
- {
- // error bar doesn't extend in key dimension (except whisker but we ignore that here), so only use data point center
- const double current = mDataPlottable->interface1D()->dataMainKey(it-mDataContainer->constBegin());
- if (qIsNaN(current)) continue;
- if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
- {
- if (current < range.lower || !haveLower)
- {
- range.lower = current;
- haveLower = true;
- }
- if (current > range.upper || !haveUpper)
- {
- range.upper = current;
- haveUpper = true;
- }
- }
- } else // mErrorType == etKeyError
- {
- const double dataKey = mDataPlottable->interface1D()->dataMainKey(it-mDataContainer->constBegin());
- if (qIsNaN(dataKey)) continue;
- // plus error:
- double current = dataKey + (qIsNaN(it->errorPlus) ? 0 : it->errorPlus);
- if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
- {
- if (current > range.upper || !haveUpper)
- {
- range.upper = current;
- haveUpper = true;
- }
- }
- // minus error:
- current = dataKey - (qIsNaN(it->errorMinus) ? 0 : it->errorMinus);
- if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
- {
- if (current < range.lower || !haveLower)
- {
- range.lower = current;
- haveLower = true;
- }
- }
- }
- }
-
- if (haveUpper && !haveLower)
- {
- range.lower = range.upper;
- haveLower = true;
- } else if (haveLower && !haveUpper)
- {
- range.upper = range.lower;
- haveUpper = true;
- }
-
- foundRange = haveLower && haveUpper;
- return range;
- }
- /* inherits documentation from base class */
- QCPRange QCPErrorBars::getValueRange(bool &foundRange, QCP::SignDomain inSignDomain, const QCPRange &inKeyRange) const
- {
- if (!mDataPlottable)
- {
- foundRange = false;
- return QCPRange();
- }
-
- QCPRange range;
- const bool restrictKeyRange = inKeyRange != QCPRange();
- bool haveLower = false;
- bool haveUpper = false;
- QCPErrorBarsDataContainer::const_iterator itBegin = mDataContainer->constBegin();
- QCPErrorBarsDataContainer::const_iterator itEnd = mDataContainer->constEnd();
- if (mDataPlottable->interface1D()->sortKeyIsMainKey() && restrictKeyRange)
- {
- itBegin = mDataContainer->constBegin()+findBegin(inKeyRange.lower);
- itEnd = mDataContainer->constBegin()+findEnd(inKeyRange.upper);
- }
- for (QCPErrorBarsDataContainer::const_iterator it = itBegin; it != itEnd; ++it)
- {
- if (restrictKeyRange)
- {
- const double dataKey = mDataPlottable->interface1D()->dataMainKey(it-mDataContainer->constBegin());
- if (dataKey < inKeyRange.lower || dataKey > inKeyRange.upper)
- continue;
- }
- if (mErrorType == etValueError)
- {
- const double dataValue = mDataPlottable->interface1D()->dataMainValue(it-mDataContainer->constBegin());
- if (qIsNaN(dataValue)) continue;
- // plus error:
- double current = dataValue + (qIsNaN(it->errorPlus) ? 0 : it->errorPlus);
- if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
- {
- if (current > range.upper || !haveUpper)
- {
- range.upper = current;
- haveUpper = true;
- }
- }
- // minus error:
- current = dataValue - (qIsNaN(it->errorMinus) ? 0 : it->errorMinus);
- if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
- {
- if (current < range.lower || !haveLower)
- {
- range.lower = current;
- haveLower = true;
- }
- }
- } else // mErrorType == etKeyError
- {
- // error bar doesn't extend in value dimension (except whisker but we ignore that here), so only use data point center
- const double current = mDataPlottable->interface1D()->dataMainValue(it-mDataContainer->constBegin());
- if (qIsNaN(current)) continue;
- if (inSignDomain == QCP::sdBoth || (inSignDomain == QCP::sdNegative && current < 0) || (inSignDomain == QCP::sdPositive && current > 0))
- {
- if (current < range.lower || !haveLower)
- {
- range.lower = current;
- haveLower = true;
- }
- if (current > range.upper || !haveUpper)
- {
- range.upper = current;
- haveUpper = true;
- }
- }
- }
- }
-
- if (haveUpper && !haveLower)
- {
- range.lower = range.upper;
- haveLower = true;
- } else if (haveLower && !haveUpper)
- {
- range.upper = range.lower;
- haveUpper = true;
- }
-
- foundRange = haveLower && haveUpper;
- return range;
- }
- /*! \internal
- Calculates the lines that make up the error bar belonging to the data point \a it.
- The resulting lines are added to \a backbones and \a whiskers. The vectors are not cleared, so
- calling this method with different \a it but the same \a backbones and \a whiskers allows to
- accumulate lines for multiple data points.
- This method assumes that \a it is a valid iterator within the bounds of this \ref QCPErrorBars
- instance and within the bounds of the associated data plottable.
- */
- void QCPErrorBars::getErrorBarLines(QCPErrorBarsDataContainer::const_iterator it, QVector<QLineF> &backbones, QVector<QLineF> &whiskers) const
- {
- if (!mDataPlottable) return;
-
- int index = it-mDataContainer->constBegin();
- QPointF centerPixel = mDataPlottable->interface1D()->dataPixelPosition(index);
- if (qIsNaN(centerPixel.x()) || qIsNaN(centerPixel.y()))
- return;
- QCPAxis *errorAxis = mErrorType == etValueError ? mValueAxis.data() : mKeyAxis.data();
- QCPAxis *orthoAxis = mErrorType == etValueError ? mKeyAxis.data() : mValueAxis.data();
- const double centerErrorAxisPixel = errorAxis->orientation() == Qt::Horizontal ? centerPixel.x() : centerPixel.y();
- const double centerOrthoAxisPixel = orthoAxis->orientation() == Qt::Horizontal ? centerPixel.x() : centerPixel.y();
- const double centerErrorAxisCoord = errorAxis->pixelToCoord(centerErrorAxisPixel); // depending on plottable, this might be different from just mDataPlottable->interface1D()->dataMainKey/Value
- const double symbolGap = mSymbolGap*0.5*errorAxis->pixelOrientation();
- // plus error:
- double errorStart, errorEnd;
- if (!qIsNaN(it->errorPlus))
- {
- errorStart = centerErrorAxisPixel+symbolGap;
- errorEnd = errorAxis->coordToPixel(centerErrorAxisCoord+it->errorPlus);
- if (errorAxis->orientation() == Qt::Vertical)
- {
- if ((errorStart > errorEnd) != errorAxis->rangeReversed())
- backbones.append(QLineF(centerOrthoAxisPixel, errorStart, centerOrthoAxisPixel, errorEnd));
- whiskers.append(QLineF(centerOrthoAxisPixel-mWhiskerWidth*0.5, errorEnd, centerOrthoAxisPixel+mWhiskerWidth*0.5, errorEnd));
- } else
- {
- if ((errorStart < errorEnd) != errorAxis->rangeReversed())
- backbones.append(QLineF(errorStart, centerOrthoAxisPixel, errorEnd, centerOrthoAxisPixel));
- whiskers.append(QLineF(errorEnd, centerOrthoAxisPixel-mWhiskerWidth*0.5, errorEnd, centerOrthoAxisPixel+mWhiskerWidth*0.5));
- }
- }
- // minus error:
- if (!qIsNaN(it->errorMinus))
- {
- errorStart = centerErrorAxisPixel-symbolGap;
- errorEnd = errorAxis->coordToPixel(centerErrorAxisCoord-it->errorMinus);
- if (errorAxis->orientation() == Qt::Vertical)
- {
- if ((errorStart < errorEnd) != errorAxis->rangeReversed())
- backbones.append(QLineF(centerOrthoAxisPixel, errorStart, centerOrthoAxisPixel, errorEnd));
- whiskers.append(QLineF(centerOrthoAxisPixel-mWhiskerWidth*0.5, errorEnd, centerOrthoAxisPixel+mWhiskerWidth*0.5, errorEnd));
- } else
- {
- if ((errorStart > errorEnd) != errorAxis->rangeReversed())
- backbones.append(QLineF(errorStart, centerOrthoAxisPixel, errorEnd, centerOrthoAxisPixel));
- whiskers.append(QLineF(errorEnd, centerOrthoAxisPixel-mWhiskerWidth*0.5, errorEnd, centerOrthoAxisPixel+mWhiskerWidth*0.5));
- }
- }
- }
- /*! \internal
- This method outputs the currently visible data range via \a begin and \a end. The returned range
- will also never exceed \a rangeRestriction.
- Since error bars with type \ref etKeyError may extend to arbitrarily positive and negative key
- coordinates relative to their data point key, this method checks all outer error bars whether
- they truly don't reach into the visible portion of the axis rect, by calling \ref
- errorBarVisible. On the other hand error bars with type \ref etValueError that are associated
- with data plottables whose sort key is equal to the main key (see \ref qcpdatacontainer-datatype
- "QCPDataContainer DataType") can be handled very efficiently by finding the visible range of
- error bars through binary search (\ref QCPPlottableInterface1D::findBegin and \ref
- QCPPlottableInterface1D::findEnd).
- If the plottable's sort key is not equal to the main key, this method returns the full data
- range, only restricted by \a rangeRestriction. Drawing optimization then has to be done on a
- point-by-point basis in the \ref draw method.
- */
- void QCPErrorBars::getVisibleDataBounds(QCPErrorBarsDataContainer::const_iterator &begin, QCPErrorBarsDataContainer::const_iterator &end, const QCPDataRange &rangeRestriction) const
- {
- QCPAxis *keyAxis = mKeyAxis.data();
- QCPAxis *valueAxis = mValueAxis.data();
- if (!keyAxis || !valueAxis)
- {
- qDebug() << Q_FUNC_INFO << "invalid key or value axis";
- end = mDataContainer->constEnd();
- begin = end;
- return;
- }
- if (!mDataPlottable || rangeRestriction.isEmpty())
- {
- end = mDataContainer->constEnd();
- begin = end;
- return;
- }
- if (!mDataPlottable->interface1D()->sortKeyIsMainKey())
- {
- // if the sort key isn't the main key, it's not possible to find a contiguous range of visible
- // data points, so this method then only applies the range restriction and otherwise returns
- // the full data range. Visibility checks must be done on a per-datapoin-basis during drawing
- QCPDataRange dataRange(0, mDataContainer->size());
- dataRange = dataRange.bounded(rangeRestriction);
- begin = mDataContainer->constBegin()+dataRange.begin();
- end = mDataContainer->constBegin()+dataRange.end();
- return;
- }
-
- // get visible data range via interface from data plottable, and then restrict to available error data points:
- const int n = qMin(mDataContainer->size(), mDataPlottable->interface1D()->dataCount());
- int beginIndex = mDataPlottable->interface1D()->findBegin(keyAxis->range().lower);
- int endIndex = mDataPlottable->interface1D()->findEnd(keyAxis->range().upper);
- int i = beginIndex;
- while (i > 0 && i < n && i > rangeRestriction.begin())
- {
- if (errorBarVisible(i))
- beginIndex = i;
- --i;
- }
- i = endIndex;
- while (i >= 0 && i < n && i < rangeRestriction.end())
- {
- if (errorBarVisible(i))
- endIndex = i+1;
- ++i;
- }
- QCPDataRange dataRange(beginIndex, endIndex);
- dataRange = dataRange.bounded(rangeRestriction.bounded(QCPDataRange(0, mDataContainer->size())));
- begin = mDataContainer->constBegin()+dataRange.begin();
- end = mDataContainer->constBegin()+dataRange.end();
- }
- /*! \internal
- Calculates the minimum distance in pixels the error bars' representation has from the given \a
- pixelPoint. This is used to determine whether the error bar was clicked or not, e.g. in \ref
- selectTest. The closest data point to \a pixelPoint is returned in \a closestData.
- */
- double QCPErrorBars::pointDistance(const QPointF &pixelPoint, QCPErrorBarsDataContainer::const_iterator &closestData) const
- {
- closestData = mDataContainer->constEnd();
- if (!mDataPlottable || mDataContainer->isEmpty())
- return -1.0;
- if (!mKeyAxis || !mValueAxis)
- {
- qDebug() << Q_FUNC_INFO << "invalid key or value axis";
- return -1.0;
- }
-
- QCPErrorBarsDataContainer::const_iterator begin, end;
- getVisibleDataBounds(begin, end, QCPDataRange(0, dataCount()));
-
- // calculate minimum distances to error backbones (whiskers are ignored for speed) and find closestData iterator:
- double minDistSqr = std::numeric_limits<double>::max();
- QVector<QLineF> backbones, whiskers;
- for (QCPErrorBarsDataContainer::const_iterator it=begin; it!=end; ++it)
- {
- getErrorBarLines(it, backbones, whiskers);
- for (int i=0; i<backbones.size(); ++i)
- {
- const double currentDistSqr = QCPVector2D(pixelPoint).distanceSquaredToLine(backbones.at(i));
- if (currentDistSqr < minDistSqr)
- {
- minDistSqr = currentDistSqr;
- closestData = it;
- }
- }
- }
- return qSqrt(minDistSqr);
- }
- /*! \internal
- \note This method is identical to \ref QCPAbstractPlottable1D::getDataSegments but needs to be
- reproduced here since the \ref QCPErrorBars plottable, as a special case that doesn't have its
- own key/value data coordinates, doesn't derive from \ref QCPAbstractPlottable1D. See the
- documentation there for details.
- */
- void QCPErrorBars::getDataSegments(QList<QCPDataRange> &selectedSegments, QList<QCPDataRange> &unselectedSegments) const
- {
- selectedSegments.clear();
- unselectedSegments.clear();
- if (mSelectable == QCP::stWhole) // stWhole selection type draws the entire plottable with selected style if mSelection isn't empty
- {
- if (selected())
- selectedSegments << QCPDataRange(0, dataCount());
- else
- unselectedSegments << QCPDataRange(0, dataCount());
- } else
- {
- QCPDataSelection sel(selection());
- sel.simplify();
- selectedSegments = sel.dataRanges();
- unselectedSegments = sel.inverse(QCPDataRange(0, dataCount())).dataRanges();
- }
- }
- /*! \internal
- Returns whether the error bar at the specified \a index is visible within the current key axis
- range.
- This method assumes for performance reasons without checking that the key axis, the value axis,
- and the data plottable (\ref setDataPlottable) are not zero and that \a index is within valid
- bounds of this \ref QCPErrorBars instance and the bounds of the data plottable.
- */
- bool QCPErrorBars::errorBarVisible(int index) const
- {
- QPointF centerPixel = mDataPlottable->interface1D()->dataPixelPosition(index);
- const double centerKeyPixel = mKeyAxis->orientation() == Qt::Horizontal ? centerPixel.x() : centerPixel.y();
- if (qIsNaN(centerKeyPixel))
- return false;
-
- double keyMin, keyMax;
- if (mErrorType == etKeyError)
- {
- const double centerKey = mKeyAxis->pixelToCoord(centerKeyPixel);
- const double errorPlus = mDataContainer->at(index).errorPlus;
- const double errorMinus = mDataContainer->at(index).errorMinus;
- keyMax = centerKey+(qIsNaN(errorPlus) ? 0 : errorPlus);
- keyMin = centerKey-(qIsNaN(errorMinus) ? 0 : errorMinus);
- } else // mErrorType == etValueError
- {
- keyMax = mKeyAxis->pixelToCoord(centerKeyPixel+mWhiskerWidth*0.5*mKeyAxis->pixelOrientation());
- keyMin = mKeyAxis->pixelToCoord(centerKeyPixel-mWhiskerWidth*0.5*mKeyAxis->pixelOrientation());
- }
- return ((keyMax > mKeyAxis->range().lower) && (keyMin < mKeyAxis->range().upper));
- }
- /*! \internal
- Returns whether \a line intersects (or is contained in) \a pixelRect.
- \a line is assumed to be either perfectly horizontal or perfectly vertical, as is the case for
- error bar lines.
- */
- bool QCPErrorBars::rectIntersectsLine(const QRectF &pixelRect, const QLineF &line) const
- {
- if (pixelRect.left() > line.x1() && pixelRect.left() > line.x2())
- return false;
- else if (pixelRect.right() < line.x1() && pixelRect.right() < line.x2())
- return false;
- else if (pixelRect.top() > line.y1() && pixelRect.top() > line.y2())
- return false;
- else if (pixelRect.bottom() < line.y1() && pixelRect.bottom() < line.y2())
- return false;
- else
- return true;
- }
- /* end of 'src/plottables/plottable-errorbar.cpp' */
- /* including file 'src/items/item-straightline.cpp', size 7592 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPItemStraightLine
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPItemStraightLine
- \brief A straight line that spans infinitely in both directions
- \image html QCPItemStraightLine.png "Straight line example. Blue dotted circles are anchors, solid blue discs are positions."
- It has two positions, \a point1 and \a point2, which define the straight line.
- */
- /*!
- Creates a straight line item and sets default values.
-
- The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
- ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
- */
- QCPItemStraightLine::QCPItemStraightLine(QCustomPlot *parentPlot) :
- QCPAbstractItem(parentPlot),
- point1(createPosition(QLatin1String("point1"))),
- point2(createPosition(QLatin1String("point2")))
- {
- point1->setCoords(0, 0);
- point2->setCoords(1, 1);
-
- setPen(QPen(Qt::black));
- setSelectedPen(QPen(Qt::blue,2));
- }
- QCPItemStraightLine::~QCPItemStraightLine()
- {
- }
- /*!
- Sets the pen that will be used to draw the line
-
- \see setSelectedPen
- */
- void QCPItemStraightLine::setPen(const QPen &pen)
- {
- mPen = pen;
- }
- /*!
- Sets the pen that will be used to draw the line when selected
-
- \see setPen, setSelected
- */
- void QCPItemStraightLine::setSelectedPen(const QPen &pen)
- {
- mSelectedPen = pen;
- }
- /* inherits documentation from base class */
- double QCPItemStraightLine::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- Q_UNUSED(details)
- if (onlySelectable && !mSelectable)
- return -1;
-
- return QCPVector2D(pos).distanceToStraightLine(point1->pixelPosition(), point2->pixelPosition()-point1->pixelPosition());
- }
- /* inherits documentation from base class */
- void QCPItemStraightLine::draw(QCPPainter *painter)
- {
- QCPVector2D start(point1->pixelPosition());
- QCPVector2D end(point2->pixelPosition());
- // get visible segment of straight line inside clipRect:
- double clipPad = mainPen().widthF();
- QLineF line = getRectClippedStraightLine(start, end-start, clipRect().adjusted(-clipPad, -clipPad, clipPad, clipPad));
- // paint visible segment, if existent:
- if (!line.isNull())
- {
- painter->setPen(mainPen());
- painter->drawLine(line);
- }
- }
- /*! \internal
- Returns the section of the straight line defined by \a base and direction vector \a
- vec, that is visible in the specified \a rect.
-
- This is a helper function for \ref draw.
- */
- QLineF QCPItemStraightLine::getRectClippedStraightLine(const QCPVector2D &base, const QCPVector2D &vec, const QRect &rect) const
- {
- double bx, by;
- double gamma;
- QLineF result;
- if (vec.x() == 0 && vec.y() == 0)
- return result;
- if (qFuzzyIsNull(vec.x())) // line is vertical
- {
- // check top of rect:
- bx = rect.left();
- by = rect.top();
- gamma = base.x()-bx + (by-base.y())*vec.x()/vec.y();
- if (gamma >= 0 && gamma <= rect.width())
- result.setLine(bx+gamma, rect.top(), bx+gamma, rect.bottom()); // no need to check bottom because we know line is vertical
- } else if (qFuzzyIsNull(vec.y())) // line is horizontal
- {
- // check left of rect:
- bx = rect.left();
- by = rect.top();
- gamma = base.y()-by + (bx-base.x())*vec.y()/vec.x();
- if (gamma >= 0 && gamma <= rect.height())
- result.setLine(rect.left(), by+gamma, rect.right(), by+gamma); // no need to check right because we know line is horizontal
- } else // line is skewed
- {
- QList<QCPVector2D> pointVectors;
- // check top of rect:
- bx = rect.left();
- by = rect.top();
- gamma = base.x()-bx + (by-base.y())*vec.x()/vec.y();
- if (gamma >= 0 && gamma <= rect.width())
- pointVectors.append(QCPVector2D(bx+gamma, by));
- // check bottom of rect:
- bx = rect.left();
- by = rect.bottom();
- gamma = base.x()-bx + (by-base.y())*vec.x()/vec.y();
- if (gamma >= 0 && gamma <= rect.width())
- pointVectors.append(QCPVector2D(bx+gamma, by));
- // check left of rect:
- bx = rect.left();
- by = rect.top();
- gamma = base.y()-by + (bx-base.x())*vec.y()/vec.x();
- if (gamma >= 0 && gamma <= rect.height())
- pointVectors.append(QCPVector2D(bx, by+gamma));
- // check right of rect:
- bx = rect.right();
- by = rect.top();
- gamma = base.y()-by + (bx-base.x())*vec.y()/vec.x();
- if (gamma >= 0 && gamma <= rect.height())
- pointVectors.append(QCPVector2D(bx, by+gamma));
-
- // evaluate points:
- if (pointVectors.size() == 2)
- {
- result.setPoints(pointVectors.at(0).toPointF(), pointVectors.at(1).toPointF());
- } else if (pointVectors.size() > 2)
- {
- // line probably goes through corner of rect, and we got two points there. single out the point pair with greatest distance:
- double distSqrMax = 0;
- QCPVector2D pv1, pv2;
- for (int i=0; i<pointVectors.size()-1; ++i)
- {
- for (int k=i+1; k<pointVectors.size(); ++k)
- {
- double distSqr = (pointVectors.at(i)-pointVectors.at(k)).lengthSquared();
- if (distSqr > distSqrMax)
- {
- pv1 = pointVectors.at(i);
- pv2 = pointVectors.at(k);
- distSqrMax = distSqr;
- }
- }
- }
- result.setPoints(pv1.toPointF(), pv2.toPointF());
- }
- }
- return result;
- }
- /*! \internal
- Returns the pen that should be used for drawing lines. Returns mPen when the
- item is not selected and mSelectedPen when it is.
- */
- QPen QCPItemStraightLine::mainPen() const
- {
- return mSelected ? mSelectedPen : mPen;
- }
- /* end of 'src/items/item-straightline.cpp' */
- /* including file 'src/items/item-line.cpp', size 8498 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPItemLine
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPItemLine
- \brief A line from one point to another
- \image html QCPItemLine.png "Line example. Blue dotted circles are anchors, solid blue discs are positions."
- It has two positions, \a start and \a end, which define the end points of the line.
-
- With \ref setHead and \ref setTail you may set different line ending styles, e.g. to create an arrow.
- */
- /*!
- Creates a line item and sets default values.
-
- The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
- ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
- */
- QCPItemLine::QCPItemLine(QCustomPlot *parentPlot) :
- QCPAbstractItem(parentPlot),
- start(createPosition(QLatin1String("start"))),
- end(createPosition(QLatin1String("end")))
- {
- start->setCoords(0, 0);
- end->setCoords(1, 1);
-
- setPen(QPen(Qt::black));
- setSelectedPen(QPen(Qt::blue,2));
- }
- QCPItemLine::~QCPItemLine()
- {
- }
- /*!
- Sets the pen that will be used to draw the line
-
- \see setSelectedPen
- */
- void QCPItemLine::setPen(const QPen &pen)
- {
- mPen = pen;
- }
- /*!
- Sets the pen that will be used to draw the line when selected
-
- \see setPen, setSelected
- */
- void QCPItemLine::setSelectedPen(const QPen &pen)
- {
- mSelectedPen = pen;
- }
- /*!
- Sets the line ending style of the head. The head corresponds to the \a end position.
-
- Note that due to the overloaded QCPLineEnding constructor, you may directly specify
- a QCPLineEnding::EndingStyle here, e.g. \code setHead(QCPLineEnding::esSpikeArrow) \endcode
-
- \see setTail
- */
- void QCPItemLine::setHead(const QCPLineEnding &head)
- {
- mHead = head;
- }
- /*!
- Sets the line ending style of the tail. The tail corresponds to the \a start position.
-
- Note that due to the overloaded QCPLineEnding constructor, you may directly specify
- a QCPLineEnding::EndingStyle here, e.g. \code setTail(QCPLineEnding::esSpikeArrow) \endcode
-
- \see setHead
- */
- void QCPItemLine::setTail(const QCPLineEnding &tail)
- {
- mTail = tail;
- }
- /* inherits documentation from base class */
- double QCPItemLine::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- Q_UNUSED(details)
- if (onlySelectable && !mSelectable)
- return -1;
-
- return qSqrt(QCPVector2D(pos).distanceSquaredToLine(start->pixelPosition(), end->pixelPosition()));
- }
- /* inherits documentation from base class */
- void QCPItemLine::draw(QCPPainter *painter)
- {
- QCPVector2D startVec(start->pixelPosition());
- QCPVector2D endVec(end->pixelPosition());
- if (qFuzzyIsNull((startVec-endVec).lengthSquared()))
- return;
- // get visible segment of straight line inside clipRect:
- double clipPad = qMax(mHead.boundingDistance(), mTail.boundingDistance());
- clipPad = qMax(clipPad, (double)mainPen().widthF());
- QLineF line = getRectClippedLine(startVec, endVec, clipRect().adjusted(-clipPad, -clipPad, clipPad, clipPad));
- // paint visible segment, if existent:
- if (!line.isNull())
- {
- painter->setPen(mainPen());
- painter->drawLine(line);
- painter->setBrush(Qt::SolidPattern);
- if (mTail.style() != QCPLineEnding::esNone)
- mTail.draw(painter, startVec, startVec-endVec);
- if (mHead.style() != QCPLineEnding::esNone)
- mHead.draw(painter, endVec, endVec-startVec);
- }
- }
- /*! \internal
- Returns the section of the line defined by \a start and \a end, that is visible in the specified
- \a rect.
-
- This is a helper function for \ref draw.
- */
- QLineF QCPItemLine::getRectClippedLine(const QCPVector2D &start, const QCPVector2D &end, const QRect &rect) const
- {
- bool containsStart = rect.contains(start.x(), start.y());
- bool containsEnd = rect.contains(end.x(), end.y());
- if (containsStart && containsEnd)
- return QLineF(start.toPointF(), end.toPointF());
-
- QCPVector2D base = start;
- QCPVector2D vec = end-start;
- double bx, by;
- double gamma, mu;
- QLineF result;
- QList<QCPVector2D> pointVectors;
- if (!qFuzzyIsNull(vec.y())) // line is not horizontal
- {
- // check top of rect:
- bx = rect.left();
- by = rect.top();
- mu = (by-base.y())/vec.y();
- if (mu >= 0 && mu <= 1)
- {
- gamma = base.x()-bx + mu*vec.x();
- if (gamma >= 0 && gamma <= rect.width())
- pointVectors.append(QCPVector2D(bx+gamma, by));
- }
- // check bottom of rect:
- bx = rect.left();
- by = rect.bottom();
- mu = (by-base.y())/vec.y();
- if (mu >= 0 && mu <= 1)
- {
- gamma = base.x()-bx + mu*vec.x();
- if (gamma >= 0 && gamma <= rect.width())
- pointVectors.append(QCPVector2D(bx+gamma, by));
- }
- }
- if (!qFuzzyIsNull(vec.x())) // line is not vertical
- {
- // check left of rect:
- bx = rect.left();
- by = rect.top();
- mu = (bx-base.x())/vec.x();
- if (mu >= 0 && mu <= 1)
- {
- gamma = base.y()-by + mu*vec.y();
- if (gamma >= 0 && gamma <= rect.height())
- pointVectors.append(QCPVector2D(bx, by+gamma));
- }
- // check right of rect:
- bx = rect.right();
- by = rect.top();
- mu = (bx-base.x())/vec.x();
- if (mu >= 0 && mu <= 1)
- {
- gamma = base.y()-by + mu*vec.y();
- if (gamma >= 0 && gamma <= rect.height())
- pointVectors.append(QCPVector2D(bx, by+gamma));
- }
- }
-
- if (containsStart)
- pointVectors.append(start);
- if (containsEnd)
- pointVectors.append(end);
-
- // evaluate points:
- if (pointVectors.size() == 2)
- {
- result.setPoints(pointVectors.at(0).toPointF(), pointVectors.at(1).toPointF());
- } else if (pointVectors.size() > 2)
- {
- // line probably goes through corner of rect, and we got two points there. single out the point pair with greatest distance:
- double distSqrMax = 0;
- QCPVector2D pv1, pv2;
- for (int i=0; i<pointVectors.size()-1; ++i)
- {
- for (int k=i+1; k<pointVectors.size(); ++k)
- {
- double distSqr = (pointVectors.at(i)-pointVectors.at(k)).lengthSquared();
- if (distSqr > distSqrMax)
- {
- pv1 = pointVectors.at(i);
- pv2 = pointVectors.at(k);
- distSqrMax = distSqr;
- }
- }
- }
- result.setPoints(pv1.toPointF(), pv2.toPointF());
- }
- return result;
- }
- /*! \internal
- Returns the pen that should be used for drawing lines. Returns mPen when the
- item is not selected and mSelectedPen when it is.
- */
- QPen QCPItemLine::mainPen() const
- {
- return mSelected ? mSelectedPen : mPen;
- }
- /* end of 'src/items/item-line.cpp' */
- /* including file 'src/items/item-curve.cpp', size 7159 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPItemCurve
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPItemCurve
- \brief A curved line from one point to another
- \image html QCPItemCurve.png "Curve example. Blue dotted circles are anchors, solid blue discs are positions."
- It has four positions, \a start and \a end, which define the end points of the line, and two
- control points which define the direction the line exits from the start and the direction from
- which it approaches the end: \a startDir and \a endDir.
-
- With \ref setHead and \ref setTail you may set different line ending styles, e.g. to create an
- arrow.
-
- Often it is desirable for the control points to stay at fixed relative positions to the start/end
- point. This can be achieved by setting the parent anchor e.g. of \a startDir simply to \a start,
- and then specify the desired pixel offset with QCPItemPosition::setCoords on \a startDir.
- */
- /*!
- Creates a curve item and sets default values.
-
- The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
- ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
- */
- QCPItemCurve::QCPItemCurve(QCustomPlot *parentPlot) :
- QCPAbstractItem(parentPlot),
- start(createPosition(QLatin1String("start"))),
- startDir(createPosition(QLatin1String("startDir"))),
- endDir(createPosition(QLatin1String("endDir"))),
- end(createPosition(QLatin1String("end")))
- {
- start->setCoords(0, 0);
- startDir->setCoords(0.5, 0);
- endDir->setCoords(0, 0.5);
- end->setCoords(1, 1);
-
- setPen(QPen(Qt::black));
- setSelectedPen(QPen(Qt::blue,2));
- }
- QCPItemCurve::~QCPItemCurve()
- {
- }
- /*!
- Sets the pen that will be used to draw the line
-
- \see setSelectedPen
- */
- void QCPItemCurve::setPen(const QPen &pen)
- {
- mPen = pen;
- }
- /*!
- Sets the pen that will be used to draw the line when selected
-
- \see setPen, setSelected
- */
- void QCPItemCurve::setSelectedPen(const QPen &pen)
- {
- mSelectedPen = pen;
- }
- /*!
- Sets the line ending style of the head. The head corresponds to the \a end position.
-
- Note that due to the overloaded QCPLineEnding constructor, you may directly specify
- a QCPLineEnding::EndingStyle here, e.g. \code setHead(QCPLineEnding::esSpikeArrow) \endcode
-
- \see setTail
- */
- void QCPItemCurve::setHead(const QCPLineEnding &head)
- {
- mHead = head;
- }
- /*!
- Sets the line ending style of the tail. The tail corresponds to the \a start position.
-
- Note that due to the overloaded QCPLineEnding constructor, you may directly specify
- a QCPLineEnding::EndingStyle here, e.g. \code setTail(QCPLineEnding::esSpikeArrow) \endcode
-
- \see setHead
- */
- void QCPItemCurve::setTail(const QCPLineEnding &tail)
- {
- mTail = tail;
- }
- /* inherits documentation from base class */
- double QCPItemCurve::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- Q_UNUSED(details)
- if (onlySelectable && !mSelectable)
- return -1;
-
- QPointF startVec(start->pixelPosition());
- QPointF startDirVec(startDir->pixelPosition());
- QPointF endDirVec(endDir->pixelPosition());
- QPointF endVec(end->pixelPosition());
- QPainterPath cubicPath(startVec);
- cubicPath.cubicTo(startDirVec, endDirVec, endVec);
-
- QPolygonF polygon = cubicPath.toSubpathPolygons().first();
- QCPVector2D p(pos);
- double minDistSqr = std::numeric_limits<double>::max();
- for (int i=1; i<polygon.size(); ++i)
- {
- double distSqr = p.distanceSquaredToLine(polygon.at(i-1), polygon.at(i));
- if (distSqr < minDistSqr)
- minDistSqr = distSqr;
- }
- return qSqrt(minDistSqr);
- }
- /* inherits documentation from base class */
- void QCPItemCurve::draw(QCPPainter *painter)
- {
- QCPVector2D startVec(start->pixelPosition());
- QCPVector2D startDirVec(startDir->pixelPosition());
- QCPVector2D endDirVec(endDir->pixelPosition());
- QCPVector2D endVec(end->pixelPosition());
- if ((endVec-startVec).length() > 1e10) // too large curves cause crash
- return;
- QPainterPath cubicPath(startVec.toPointF());
- cubicPath.cubicTo(startDirVec.toPointF(), endDirVec.toPointF(), endVec.toPointF());
- // paint visible segment, if existent:
- QRect clip = clipRect().adjusted(-mainPen().widthF(), -mainPen().widthF(), mainPen().widthF(), mainPen().widthF());
- QRect cubicRect = cubicPath.controlPointRect().toRect();
- if (cubicRect.isEmpty()) // may happen when start and end exactly on same x or y position
- cubicRect.adjust(0, 0, 1, 1);
- if (clip.intersects(cubicRect))
- {
- painter->setPen(mainPen());
- painter->drawPath(cubicPath);
- painter->setBrush(Qt::SolidPattern);
- if (mTail.style() != QCPLineEnding::esNone)
- mTail.draw(painter, startVec, M_PI-cubicPath.angleAtPercent(0)/180.0*M_PI);
- if (mHead.style() != QCPLineEnding::esNone)
- mHead.draw(painter, endVec, -cubicPath.angleAtPercent(1)/180.0*M_PI);
- }
- }
- /*! \internal
- Returns the pen that should be used for drawing lines. Returns mPen when the
- item is not selected and mSelectedPen when it is.
- */
- QPen QCPItemCurve::mainPen() const
- {
- return mSelected ? mSelectedPen : mPen;
- }
- /* end of 'src/items/item-curve.cpp' */
- /* including file 'src/items/item-rect.cpp', size 6479 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPItemRect
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPItemRect
- \brief A rectangle
- \image html QCPItemRect.png "Rectangle example. Blue dotted circles are anchors, solid blue discs are positions."
- It has two positions, \a topLeft and \a bottomRight, which define the rectangle.
- */
- /*!
- Creates a rectangle item and sets default values.
-
- The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
- ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
- */
- QCPItemRect::QCPItemRect(QCustomPlot *parentPlot) :
- QCPAbstractItem(parentPlot),
- topLeft(createPosition(QLatin1String("topLeft"))),
- bottomRight(createPosition(QLatin1String("bottomRight"))),
- top(createAnchor(QLatin1String("top"), aiTop)),
- topRight(createAnchor(QLatin1String("topRight"), aiTopRight)),
- right(createAnchor(QLatin1String("right"), aiRight)),
- bottom(createAnchor(QLatin1String("bottom"), aiBottom)),
- bottomLeft(createAnchor(QLatin1String("bottomLeft"), aiBottomLeft)),
- left(createAnchor(QLatin1String("left"), aiLeft))
- {
- topLeft->setCoords(0, 1);
- bottomRight->setCoords(1, 0);
-
- setPen(QPen(Qt::black));
- setSelectedPen(QPen(Qt::blue,2));
- setBrush(Qt::NoBrush);
- setSelectedBrush(Qt::NoBrush);
- }
- QCPItemRect::~QCPItemRect()
- {
- }
- /*!
- Sets the pen that will be used to draw the line of the rectangle
-
- \see setSelectedPen, setBrush
- */
- void QCPItemRect::setPen(const QPen &pen)
- {
- mPen = pen;
- }
- /*!
- Sets the pen that will be used to draw the line of the rectangle when selected
-
- \see setPen, setSelected
- */
- void QCPItemRect::setSelectedPen(const QPen &pen)
- {
- mSelectedPen = pen;
- }
- /*!
- Sets the brush that will be used to fill the rectangle. To disable filling, set \a brush to
- Qt::NoBrush.
-
- \see setSelectedBrush, setPen
- */
- void QCPItemRect::setBrush(const QBrush &brush)
- {
- mBrush = brush;
- }
- /*!
- Sets the brush that will be used to fill the rectangle when selected. To disable filling, set \a
- brush to Qt::NoBrush.
-
- \see setBrush
- */
- void QCPItemRect::setSelectedBrush(const QBrush &brush)
- {
- mSelectedBrush = brush;
- }
- /* inherits documentation from base class */
- double QCPItemRect::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- Q_UNUSED(details)
- if (onlySelectable && !mSelectable)
- return -1;
-
- QRectF rect = QRectF(topLeft->pixelPosition(), bottomRight->pixelPosition()).normalized();
- bool filledRect = mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0;
- return rectDistance(rect, pos, filledRect);
- }
- /* inherits documentation from base class */
- void QCPItemRect::draw(QCPPainter *painter)
- {
- QPointF p1 = topLeft->pixelPosition();
- QPointF p2 = bottomRight->pixelPosition();
- if (p1.toPoint() == p2.toPoint())
- return;
- QRectF rect = QRectF(p1, p2).normalized();
- double clipPad = mainPen().widthF();
- QRectF boundingRect = rect.adjusted(-clipPad, -clipPad, clipPad, clipPad);
- if (boundingRect.intersects(clipRect())) // only draw if bounding rect of rect item is visible in cliprect
- {
- painter->setPen(mainPen());
- painter->setBrush(mainBrush());
- painter->drawRect(rect);
- }
- }
- /* inherits documentation from base class */
- QPointF QCPItemRect::anchorPixelPosition(int anchorId) const
- {
- QRectF rect = QRectF(topLeft->pixelPosition(), bottomRight->pixelPosition());
- switch (anchorId)
- {
- case aiTop: return (rect.topLeft()+rect.topRight())*0.5;
- case aiTopRight: return rect.topRight();
- case aiRight: return (rect.topRight()+rect.bottomRight())*0.5;
- case aiBottom: return (rect.bottomLeft()+rect.bottomRight())*0.5;
- case aiBottomLeft: return rect.bottomLeft();
- case aiLeft: return (rect.topLeft()+rect.bottomLeft())*0.5;
- }
-
- qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
- return QPointF();
- }
- /*! \internal
- Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected
- and mSelectedPen when it is.
- */
- QPen QCPItemRect::mainPen() const
- {
- return mSelected ? mSelectedPen : mPen;
- }
- /*! \internal
- Returns the brush that should be used for drawing fills of the item. Returns mBrush when the item
- is not selected and mSelectedBrush when it is.
- */
- QBrush QCPItemRect::mainBrush() const
- {
- return mSelected ? mSelectedBrush : mBrush;
- }
- /* end of 'src/items/item-rect.cpp' */
- /* including file 'src/items/item-text.cpp', size 13338 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPItemText
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPItemText
- \brief A text label
- \image html QCPItemText.png "Text example. Blue dotted circles are anchors, solid blue discs are positions."
- Its position is defined by the member \a position and the setting of \ref setPositionAlignment.
- The latter controls which part of the text rect shall be aligned with \a position.
-
- The text alignment itself (i.e. left, center, right) can be controlled with \ref
- setTextAlignment.
-
- The text may be rotated around the \a position point with \ref setRotation.
- */
- /*!
- Creates a text item and sets default values.
-
- The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
- ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
- */
- QCPItemText::QCPItemText(QCustomPlot *parentPlot) :
- QCPAbstractItem(parentPlot),
- position(createPosition(QLatin1String("position"))),
- topLeft(createAnchor(QLatin1String("topLeft"), aiTopLeft)),
- top(createAnchor(QLatin1String("top"), aiTop)),
- topRight(createAnchor(QLatin1String("topRight"), aiTopRight)),
- right(createAnchor(QLatin1String("right"), aiRight)),
- bottomRight(createAnchor(QLatin1String("bottomRight"), aiBottomRight)),
- bottom(createAnchor(QLatin1String("bottom"), aiBottom)),
- bottomLeft(createAnchor(QLatin1String("bottomLeft"), aiBottomLeft)),
- left(createAnchor(QLatin1String("left"), aiLeft)),
- mText(QLatin1String("text")),
- mPositionAlignment(Qt::AlignCenter),
- mTextAlignment(Qt::AlignTop|Qt::AlignHCenter),
- mRotation(0)
- {
- position->setCoords(0, 0);
-
- setPen(Qt::NoPen);
- setSelectedPen(Qt::NoPen);
- setBrush(Qt::NoBrush);
- setSelectedBrush(Qt::NoBrush);
- setColor(Qt::black);
- setSelectedColor(Qt::blue);
- }
- QCPItemText::~QCPItemText()
- {
- }
- /*!
- Sets the color of the text.
- */
- void QCPItemText::setColor(const QColor &color)
- {
- mColor = color;
- }
- /*!
- Sets the color of the text that will be used when the item is selected.
- */
- void QCPItemText::setSelectedColor(const QColor &color)
- {
- mSelectedColor = color;
- }
- /*!
- Sets the pen that will be used do draw a rectangular border around the text. To disable the
- border, set \a pen to Qt::NoPen.
-
- \see setSelectedPen, setBrush, setPadding
- */
- void QCPItemText::setPen(const QPen &pen)
- {
- mPen = pen;
- }
- /*!
- Sets the pen that will be used do draw a rectangular border around the text, when the item is
- selected. To disable the border, set \a pen to Qt::NoPen.
-
- \see setPen
- */
- void QCPItemText::setSelectedPen(const QPen &pen)
- {
- mSelectedPen = pen;
- }
- /*!
- Sets the brush that will be used do fill the background of the text. To disable the
- background, set \a brush to Qt::NoBrush.
-
- \see setSelectedBrush, setPen, setPadding
- */
- void QCPItemText::setBrush(const QBrush &brush)
- {
- mBrush = brush;
- }
- /*!
- Sets the brush that will be used do fill the background of the text, when the item is selected. To disable the
- background, set \a brush to Qt::NoBrush.
-
- \see setBrush
- */
- void QCPItemText::setSelectedBrush(const QBrush &brush)
- {
- mSelectedBrush = brush;
- }
- /*!
- Sets the font of the text.
-
- \see setSelectedFont, setColor
- */
- void QCPItemText::setFont(const QFont &font)
- {
- mFont = font;
- }
- /*!
- Sets the font of the text that will be used when the item is selected.
-
- \see setFont
- */
- void QCPItemText::setSelectedFont(const QFont &font)
- {
- mSelectedFont = font;
- }
- /*!
- Sets the text that will be displayed. Multi-line texts are supported by inserting a line break
- character, e.g. '\n'.
-
- \see setFont, setColor, setTextAlignment
- */
- void QCPItemText::setText(const QString &text)
- {
- mText = text;
- }
- /*!
- Sets which point of the text rect shall be aligned with \a position.
-
- Examples:
- \li If \a alignment is <tt>Qt::AlignHCenter | Qt::AlignTop</tt>, the text will be positioned such
- that the top of the text rect will be horizontally centered on \a position.
- \li If \a alignment is <tt>Qt::AlignLeft | Qt::AlignBottom</tt>, \a position will indicate the
- bottom left corner of the text rect.
-
- If you want to control the alignment of (multi-lined) text within the text rect, use \ref
- setTextAlignment.
- */
- void QCPItemText::setPositionAlignment(Qt::Alignment alignment)
- {
- mPositionAlignment = alignment;
- }
- /*!
- Controls how (multi-lined) text is aligned inside the text rect (typically Qt::AlignLeft, Qt::AlignCenter or Qt::AlignRight).
- */
- void QCPItemText::setTextAlignment(Qt::Alignment alignment)
- {
- mTextAlignment = alignment;
- }
- /*!
- Sets the angle in degrees by which the text (and the text rectangle, if visible) will be rotated
- around \a position.
- */
- void QCPItemText::setRotation(double degrees)
- {
- mRotation = degrees;
- }
- /*!
- Sets the distance between the border of the text rectangle and the text. The appearance (and
- visibility) of the text rectangle can be controlled with \ref setPen and \ref setBrush.
- */
- void QCPItemText::setPadding(const QMargins &padding)
- {
- mPadding = padding;
- }
- /* inherits documentation from base class */
- double QCPItemText::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- Q_UNUSED(details)
- if (onlySelectable && !mSelectable)
- return -1;
-
- // The rect may be rotated, so we transform the actual clicked pos to the rotated
- // coordinate system, so we can use the normal rectDistance function for non-rotated rects:
- QPointF positionPixels(position->pixelPosition());
- QTransform inputTransform;
- inputTransform.translate(positionPixels.x(), positionPixels.y());
- inputTransform.rotate(-mRotation);
- inputTransform.translate(-positionPixels.x(), -positionPixels.y());
- QPointF rotatedPos = inputTransform.map(pos);
- QFontMetrics fontMetrics(mFont);
- QRect textRect = fontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip|mTextAlignment, mText);
- QRect textBoxRect = textRect.adjusted(-mPadding.left(), -mPadding.top(), mPadding.right(), mPadding.bottom());
- QPointF textPos = getTextDrawPoint(positionPixels, textBoxRect, mPositionAlignment);
- textBoxRect.moveTopLeft(textPos.toPoint());
- return rectDistance(textBoxRect, rotatedPos, true);
- }
- /* inherits documentation from base class */
- void QCPItemText::draw(QCPPainter *painter)
- {
- QPointF pos(position->pixelPosition());
- QTransform transform = painter->transform();
- transform.translate(pos.x(), pos.y());
- if (!qFuzzyIsNull(mRotation))
- transform.rotate(mRotation);
- painter->setFont(mainFont());
- QRect textRect = painter->fontMetrics().boundingRect(0, 0, 0, 0, Qt::TextDontClip|mTextAlignment, mText);
- QRect textBoxRect = textRect.adjusted(-mPadding.left(), -mPadding.top(), mPadding.right(), mPadding.bottom());
- QPointF textPos = getTextDrawPoint(QPointF(0, 0), textBoxRect, mPositionAlignment); // 0, 0 because the transform does the translation
- textRect.moveTopLeft(textPos.toPoint()+QPoint(mPadding.left(), mPadding.top()));
- textBoxRect.moveTopLeft(textPos.toPoint());
- double clipPad = mainPen().widthF();
- QRect boundingRect = textBoxRect.adjusted(-clipPad, -clipPad, clipPad, clipPad);
- if (transform.mapRect(boundingRect).intersects(painter->transform().mapRect(clipRect())))
- {
- painter->setTransform(transform);
- if ((mainBrush().style() != Qt::NoBrush && mainBrush().color().alpha() != 0) ||
- (mainPen().style() != Qt::NoPen && mainPen().color().alpha() != 0))
- {
- painter->setPen(mainPen());
- painter->setBrush(mainBrush());
- painter->drawRect(textBoxRect);
- }
- painter->setBrush(Qt::NoBrush);
- painter->setPen(QPen(mainColor()));
- painter->drawText(textRect, Qt::TextDontClip|mTextAlignment, mText);
- }
- }
- /* inherits documentation from base class */
- QPointF QCPItemText::anchorPixelPosition(int anchorId) const
- {
- // get actual rect points (pretty much copied from draw function):
- QPointF pos(position->pixelPosition());
- QTransform transform;
- transform.translate(pos.x(), pos.y());
- if (!qFuzzyIsNull(mRotation))
- transform.rotate(mRotation);
- QFontMetrics fontMetrics(mainFont());
- QRect textRect = fontMetrics.boundingRect(0, 0, 0, 0, Qt::TextDontClip|mTextAlignment, mText);
- QRectF textBoxRect = textRect.adjusted(-mPadding.left(), -mPadding.top(), mPadding.right(), mPadding.bottom());
- QPointF textPos = getTextDrawPoint(QPointF(0, 0), textBoxRect, mPositionAlignment); // 0, 0 because the transform does the translation
- textBoxRect.moveTopLeft(textPos.toPoint());
- QPolygonF rectPoly = transform.map(QPolygonF(textBoxRect));
-
- switch (anchorId)
- {
- case aiTopLeft: return rectPoly.at(0);
- case aiTop: return (rectPoly.at(0)+rectPoly.at(1))*0.5;
- case aiTopRight: return rectPoly.at(1);
- case aiRight: return (rectPoly.at(1)+rectPoly.at(2))*0.5;
- case aiBottomRight: return rectPoly.at(2);
- case aiBottom: return (rectPoly.at(2)+rectPoly.at(3))*0.5;
- case aiBottomLeft: return rectPoly.at(3);
- case aiLeft: return (rectPoly.at(3)+rectPoly.at(0))*0.5;
- }
-
- qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
- return QPointF();
- }
- /*! \internal
-
- Returns the point that must be given to the QPainter::drawText function (which expects the top
- left point of the text rect), according to the position \a pos, the text bounding box \a rect and
- the requested \a positionAlignment.
-
- For example, if \a positionAlignment is <tt>Qt::AlignLeft | Qt::AlignBottom</tt> the returned point
- will be shifted upward by the height of \a rect, starting from \a pos. So if the text is finally
- drawn at that point, the lower left corner of the resulting text rect is at \a pos.
- */
- QPointF QCPItemText::getTextDrawPoint(const QPointF &pos, const QRectF &rect, Qt::Alignment positionAlignment) const
- {
- if (positionAlignment == 0 || positionAlignment == (Qt::AlignLeft|Qt::AlignTop))
- return pos;
-
- QPointF result = pos; // start at top left
- if (positionAlignment.testFlag(Qt::AlignHCenter))
- result.rx() -= rect.width()/2.0;
- else if (positionAlignment.testFlag(Qt::AlignRight))
- result.rx() -= rect.width();
- if (positionAlignment.testFlag(Qt::AlignVCenter))
- result.ry() -= rect.height()/2.0;
- else if (positionAlignment.testFlag(Qt::AlignBottom))
- result.ry() -= rect.height();
- return result;
- }
- /*! \internal
- Returns the font that should be used for drawing text. Returns mFont when the item is not selected
- and mSelectedFont when it is.
- */
- QFont QCPItemText::mainFont() const
- {
- return mSelected ? mSelectedFont : mFont;
- }
- /*! \internal
- Returns the color that should be used for drawing text. Returns mColor when the item is not
- selected and mSelectedColor when it is.
- */
- QColor QCPItemText::mainColor() const
- {
- return mSelected ? mSelectedColor : mColor;
- }
- /*! \internal
- Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected
- and mSelectedPen when it is.
- */
- QPen QCPItemText::mainPen() const
- {
- return mSelected ? mSelectedPen : mPen;
- }
- /*! \internal
- Returns the brush that should be used for drawing fills of the item. Returns mBrush when the item
- is not selected and mSelectedBrush when it is.
- */
- QBrush QCPItemText::mainBrush() const
- {
- return mSelected ? mSelectedBrush : mBrush;
- }
- /* end of 'src/items/item-text.cpp' */
- /* including file 'src/items/item-ellipse.cpp', size 7863 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPItemEllipse
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPItemEllipse
- \brief An ellipse
- \image html QCPItemEllipse.png "Ellipse example. Blue dotted circles are anchors, solid blue discs are positions."
- It has two positions, \a topLeft and \a bottomRight, which define the rect the ellipse will be drawn in.
- */
- /*!
- Creates an ellipse item and sets default values.
-
- The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
- ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
- */
- QCPItemEllipse::QCPItemEllipse(QCustomPlot *parentPlot) :
- QCPAbstractItem(parentPlot),
- topLeft(createPosition(QLatin1String("topLeft"))),
- bottomRight(createPosition(QLatin1String("bottomRight"))),
- topLeftRim(createAnchor(QLatin1String("topLeftRim"), aiTopLeftRim)),
- top(createAnchor(QLatin1String("top"), aiTop)),
- topRightRim(createAnchor(QLatin1String("topRightRim"), aiTopRightRim)),
- right(createAnchor(QLatin1String("right"), aiRight)),
- bottomRightRim(createAnchor(QLatin1String("bottomRightRim"), aiBottomRightRim)),
- bottom(createAnchor(QLatin1String("bottom"), aiBottom)),
- bottomLeftRim(createAnchor(QLatin1String("bottomLeftRim"), aiBottomLeftRim)),
- left(createAnchor(QLatin1String("left"), aiLeft)),
- center(createAnchor(QLatin1String("center"), aiCenter))
- {
- topLeft->setCoords(0, 1);
- bottomRight->setCoords(1, 0);
-
- setPen(QPen(Qt::black));
- setSelectedPen(QPen(Qt::blue, 2));
- setBrush(Qt::NoBrush);
- setSelectedBrush(Qt::NoBrush);
- }
- QCPItemEllipse::~QCPItemEllipse()
- {
- }
- /*!
- Sets the pen that will be used to draw the line of the ellipse
-
- \see setSelectedPen, setBrush
- */
- void QCPItemEllipse::setPen(const QPen &pen)
- {
- mPen = pen;
- }
- /*!
- Sets the pen that will be used to draw the line of the ellipse when selected
-
- \see setPen, setSelected
- */
- void QCPItemEllipse::setSelectedPen(const QPen &pen)
- {
- mSelectedPen = pen;
- }
- /*!
- Sets the brush that will be used to fill the ellipse. To disable filling, set \a brush to
- Qt::NoBrush.
-
- \see setSelectedBrush, setPen
- */
- void QCPItemEllipse::setBrush(const QBrush &brush)
- {
- mBrush = brush;
- }
- /*!
- Sets the brush that will be used to fill the ellipse when selected. To disable filling, set \a
- brush to Qt::NoBrush.
-
- \see setBrush
- */
- void QCPItemEllipse::setSelectedBrush(const QBrush &brush)
- {
- mSelectedBrush = brush;
- }
- /* inherits documentation from base class */
- double QCPItemEllipse::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- Q_UNUSED(details)
- if (onlySelectable && !mSelectable)
- return -1;
-
- QPointF p1 = topLeft->pixelPosition();
- QPointF p2 = bottomRight->pixelPosition();
- QPointF center((p1+p2)/2.0);
- double a = qAbs(p1.x()-p2.x())/2.0;
- double b = qAbs(p1.y()-p2.y())/2.0;
- double x = pos.x()-center.x();
- double y = pos.y()-center.y();
-
- // distance to border:
- double c = 1.0/qSqrt(x*x/(a*a)+y*y/(b*b));
- double result = qAbs(c-1)*qSqrt(x*x+y*y);
- // filled ellipse, allow click inside to count as hit:
- if (result > mParentPlot->selectionTolerance()*0.99 && mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0)
- {
- if (x*x/(a*a) + y*y/(b*b) <= 1)
- result = mParentPlot->selectionTolerance()*0.99;
- }
- return result;
- }
- /* inherits documentation from base class */
- void QCPItemEllipse::draw(QCPPainter *painter)
- {
- QPointF p1 = topLeft->pixelPosition();
- QPointF p2 = bottomRight->pixelPosition();
- if (p1.toPoint() == p2.toPoint())
- return;
- QRectF ellipseRect = QRectF(p1, p2).normalized();
- QRect clip = clipRect().adjusted(-mainPen().widthF(), -mainPen().widthF(), mainPen().widthF(), mainPen().widthF());
- if (ellipseRect.intersects(clip)) // only draw if bounding rect of ellipse is visible in cliprect
- {
- painter->setPen(mainPen());
- painter->setBrush(mainBrush());
- #ifdef __EXCEPTIONS
- try // drawEllipse sometimes throws exceptions if ellipse is too big
- {
- #endif
- painter->drawEllipse(ellipseRect);
- #ifdef __EXCEPTIONS
- } catch (...)
- {
- qDebug() << Q_FUNC_INFO << "Item too large for memory, setting invisible";
- setVisible(false);
- }
- #endif
- }
- }
- /* inherits documentation from base class */
- QPointF QCPItemEllipse::anchorPixelPosition(int anchorId) const
- {
- QRectF rect = QRectF(topLeft->pixelPosition(), bottomRight->pixelPosition());
- switch (anchorId)
- {
- case aiTopLeftRim: return rect.center()+(rect.topLeft()-rect.center())*1/qSqrt(2);
- case aiTop: return (rect.topLeft()+rect.topRight())*0.5;
- case aiTopRightRim: return rect.center()+(rect.topRight()-rect.center())*1/qSqrt(2);
- case aiRight: return (rect.topRight()+rect.bottomRight())*0.5;
- case aiBottomRightRim: return rect.center()+(rect.bottomRight()-rect.center())*1/qSqrt(2);
- case aiBottom: return (rect.bottomLeft()+rect.bottomRight())*0.5;
- case aiBottomLeftRim: return rect.center()+(rect.bottomLeft()-rect.center())*1/qSqrt(2);
- case aiLeft: return (rect.topLeft()+rect.bottomLeft())*0.5;
- case aiCenter: return (rect.topLeft()+rect.bottomRight())*0.5;
- }
-
- qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
- return QPointF();
- }
- /*! \internal
- Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected
- and mSelectedPen when it is.
- */
- QPen QCPItemEllipse::mainPen() const
- {
- return mSelected ? mSelectedPen : mPen;
- }
- /*! \internal
- Returns the brush that should be used for drawing fills of the item. Returns mBrush when the item
- is not selected and mSelectedBrush when it is.
- */
- QBrush QCPItemEllipse::mainBrush() const
- {
- return mSelected ? mSelectedBrush : mBrush;
- }
- /* end of 'src/items/item-ellipse.cpp' */
- /* including file 'src/items/item-pixmap.cpp', size 10615 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPItemPixmap
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPItemPixmap
- \brief An arbitrary pixmap
- \image html QCPItemPixmap.png "Pixmap example. Blue dotted circles are anchors, solid blue discs are positions."
- It has two positions, \a topLeft and \a bottomRight, which define the rectangle the pixmap will
- be drawn in. Depending on the scale setting (\ref setScaled), the pixmap will be either scaled to
- fit the rectangle or be drawn aligned to the topLeft position.
-
- If scaling is enabled and \a topLeft is further to the bottom/right than \a bottomRight (as shown
- on the right side of the example image), the pixmap will be flipped in the respective
- orientations.
- */
- /*!
- Creates a rectangle item and sets default values.
-
- The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
- ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
- */
- QCPItemPixmap::QCPItemPixmap(QCustomPlot *parentPlot) :
- QCPAbstractItem(parentPlot),
- topLeft(createPosition(QLatin1String("topLeft"))),
- bottomRight(createPosition(QLatin1String("bottomRight"))),
- top(createAnchor(QLatin1String("top"), aiTop)),
- topRight(createAnchor(QLatin1String("topRight"), aiTopRight)),
- right(createAnchor(QLatin1String("right"), aiRight)),
- bottom(createAnchor(QLatin1String("bottom"), aiBottom)),
- bottomLeft(createAnchor(QLatin1String("bottomLeft"), aiBottomLeft)),
- left(createAnchor(QLatin1String("left"), aiLeft)),
- mScaled(false),
- mScaledPixmapInvalidated(true),
- mAspectRatioMode(Qt::KeepAspectRatio),
- mTransformationMode(Qt::SmoothTransformation)
- {
- topLeft->setCoords(0, 1);
- bottomRight->setCoords(1, 0);
-
- setPen(Qt::NoPen);
- setSelectedPen(QPen(Qt::blue));
- }
- QCPItemPixmap::~QCPItemPixmap()
- {
- }
- /*!
- Sets the pixmap that will be displayed.
- */
- void QCPItemPixmap::setPixmap(const QPixmap &pixmap)
- {
- mPixmap = pixmap;
- mScaledPixmapInvalidated = true;
- if (mPixmap.isNull())
- qDebug() << Q_FUNC_INFO << "pixmap is null";
- }
- /*!
- Sets whether the pixmap will be scaled to fit the rectangle defined by the \a topLeft and \a
- bottomRight positions.
- */
- void QCPItemPixmap::setScaled(bool scaled, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformationMode)
- {
- mScaled = scaled;
- mAspectRatioMode = aspectRatioMode;
- mTransformationMode = transformationMode;
- mScaledPixmapInvalidated = true;
- }
- /*!
- Sets the pen that will be used to draw a border around the pixmap.
-
- \see setSelectedPen, setBrush
- */
- void QCPItemPixmap::setPen(const QPen &pen)
- {
- mPen = pen;
- }
- /*!
- Sets the pen that will be used to draw a border around the pixmap when selected
-
- \see setPen, setSelected
- */
- void QCPItemPixmap::setSelectedPen(const QPen &pen)
- {
- mSelectedPen = pen;
- }
- /* inherits documentation from base class */
- double QCPItemPixmap::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- Q_UNUSED(details)
- if (onlySelectable && !mSelectable)
- return -1;
-
- return rectDistance(getFinalRect(), pos, true);
- }
- /* inherits documentation from base class */
- void QCPItemPixmap::draw(QCPPainter *painter)
- {
- bool flipHorz = false;
- bool flipVert = false;
- QRect rect = getFinalRect(&flipHorz, &flipVert);
- double clipPad = mainPen().style() == Qt::NoPen ? 0 : mainPen().widthF();
- QRect boundingRect = rect.adjusted(-clipPad, -clipPad, clipPad, clipPad);
- if (boundingRect.intersects(clipRect()))
- {
- updateScaledPixmap(rect, flipHorz, flipVert);
- painter->drawPixmap(rect.topLeft(), mScaled ? mScaledPixmap : mPixmap);
- QPen pen = mainPen();
- if (pen.style() != Qt::NoPen)
- {
- painter->setPen(pen);
- painter->setBrush(Qt::NoBrush);
- painter->drawRect(rect);
- }
- }
- }
- /* inherits documentation from base class */
- QPointF QCPItemPixmap::anchorPixelPosition(int anchorId) const
- {
- bool flipHorz;
- bool flipVert;
- QRect rect = getFinalRect(&flipHorz, &flipVert);
- // we actually want denormal rects (negative width/height) here, so restore
- // the flipped state:
- if (flipHorz)
- rect.adjust(rect.width(), 0, -rect.width(), 0);
- if (flipVert)
- rect.adjust(0, rect.height(), 0, -rect.height());
-
- switch (anchorId)
- {
- case aiTop: return (rect.topLeft()+rect.topRight())*0.5;
- case aiTopRight: return rect.topRight();
- case aiRight: return (rect.topRight()+rect.bottomRight())*0.5;
- case aiBottom: return (rect.bottomLeft()+rect.bottomRight())*0.5;
- case aiBottomLeft: return rect.bottomLeft();
- case aiLeft: return (rect.topLeft()+rect.bottomLeft())*0.5;;
- }
-
- qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
- return QPointF();
- }
- /*! \internal
-
- Creates the buffered scaled image (\a mScaledPixmap) to fit the specified \a finalRect. The
- parameters \a flipHorz and \a flipVert control whether the resulting image shall be flipped
- horizontally or vertically. (This is used when \a topLeft is further to the bottom/right than \a
- bottomRight.)
-
- This function only creates the scaled pixmap when the buffered pixmap has a different size than
- the expected result, so calling this function repeatedly, e.g. in the \ref draw function, does
- not cause expensive rescaling every time.
-
- If scaling is disabled, sets mScaledPixmap to a null QPixmap.
- */
- void QCPItemPixmap::updateScaledPixmap(QRect finalRect, bool flipHorz, bool flipVert)
- {
- if (mPixmap.isNull())
- return;
-
- if (mScaled)
- {
- #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
- double devicePixelRatio = mPixmap.devicePixelRatio();
- #else
- double devicePixelRatio = 1.0;
- #endif
- if (finalRect.isNull())
- finalRect = getFinalRect(&flipHorz, &flipVert);
- if (mScaledPixmapInvalidated || finalRect.size() != mScaledPixmap.size()/devicePixelRatio)
- {
- mScaledPixmap = mPixmap.scaled(finalRect.size()*devicePixelRatio, mAspectRatioMode, mTransformationMode);
- if (flipHorz || flipVert)
- mScaledPixmap = QPixmap::fromImage(mScaledPixmap.toImage().mirrored(flipHorz, flipVert));
- #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
- mScaledPixmap.setDevicePixelRatio(devicePixelRatio);
- #endif
- }
- } else if (!mScaledPixmap.isNull())
- mScaledPixmap = QPixmap();
- mScaledPixmapInvalidated = false;
- }
- /*! \internal
-
- Returns the final (tight) rect the pixmap is drawn in, depending on the current item positions
- and scaling settings.
-
- The output parameters \a flippedHorz and \a flippedVert return whether the pixmap should be drawn
- flipped horizontally or vertically in the returned rect. (The returned rect itself is always
- normalized, i.e. the top left corner of the rect is actually further to the top/left than the
- bottom right corner). This is the case when the item position \a topLeft is further to the
- bottom/right than \a bottomRight.
-
- If scaling is disabled, returns a rect with size of the original pixmap and the top left corner
- aligned with the item position \a topLeft. The position \a bottomRight is ignored.
- */
- QRect QCPItemPixmap::getFinalRect(bool *flippedHorz, bool *flippedVert) const
- {
- QRect result;
- bool flipHorz = false;
- bool flipVert = false;
- QPoint p1 = topLeft->pixelPosition().toPoint();
- QPoint p2 = bottomRight->pixelPosition().toPoint();
- if (p1 == p2)
- return QRect(p1, QSize(0, 0));
- if (mScaled)
- {
- QSize newSize = QSize(p2.x()-p1.x(), p2.y()-p1.y());
- QPoint topLeft = p1;
- if (newSize.width() < 0)
- {
- flipHorz = true;
- newSize.rwidth() *= -1;
- topLeft.setX(p2.x());
- }
- if (newSize.height() < 0)
- {
- flipVert = true;
- newSize.rheight() *= -1;
- topLeft.setY(p2.y());
- }
- QSize scaledSize = mPixmap.size();
- #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
- scaledSize /= mPixmap.devicePixelRatio();
- scaledSize.scale(newSize*mPixmap.devicePixelRatio(), mAspectRatioMode);
- #else
- scaledSize.scale(newSize, mAspectRatioMode);
- #endif
- result = QRect(topLeft, scaledSize);
- } else
- {
- #ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
- result = QRect(p1, mPixmap.size()/mPixmap.devicePixelRatio());
- #else
- result = QRect(p1, mPixmap.size());
- #endif
- }
- if (flippedHorz)
- *flippedHorz = flipHorz;
- if (flippedVert)
- *flippedVert = flipVert;
- return result;
- }
- /*! \internal
- Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected
- and mSelectedPen when it is.
- */
- QPen QCPItemPixmap::mainPen() const
- {
- return mSelected ? mSelectedPen : mPen;
- }
- /* end of 'src/items/item-pixmap.cpp' */
- /* including file 'src/items/item-tracer.cpp', size 14624 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPItemTracer
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPItemTracer
- \brief Item that sticks to QCPGraph data points
- \image html QCPItemTracer.png "Tracer example. Blue dotted circles are anchors, solid blue discs are positions."
- The tracer can be connected with a QCPGraph via \ref setGraph. Then it will automatically adopt
- the coordinate axes of the graph and update its \a position to be on the graph's data. This means
- the key stays controllable via \ref setGraphKey, but the value will follow the graph data. If a
- QCPGraph is connected, note that setting the coordinates of the tracer item directly via \a
- position will have no effect because they will be overriden in the next redraw (this is when the
- coordinate update happens).
-
- If the specified key in \ref setGraphKey is outside the key bounds of the graph, the tracer will
- stay at the corresponding end of the graph.
-
- With \ref setInterpolating you may specify whether the tracer may only stay exactly on data
- points or whether it interpolates data points linearly, if given a key that lies between two data
- points of the graph.
-
- The tracer has different visual styles, see \ref setStyle. It is also possible to make the tracer
- have no own visual appearance (set the style to \ref tsNone), and just connect other item
- positions to the tracer \a position (used as an anchor) via \ref
- QCPItemPosition::setParentAnchor.
-
- \note The tracer position is only automatically updated upon redraws. So when the data of the
- graph changes and immediately afterwards (without a redraw) the position coordinates of the
- tracer are retrieved, they will not reflect the updated data of the graph. In this case \ref
- updatePosition must be called manually, prior to reading the tracer coordinates.
- */
- /*!
- Creates a tracer item and sets default values.
-
- The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
- ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
- */
- QCPItemTracer::QCPItemTracer(QCustomPlot *parentPlot) :
- QCPAbstractItem(parentPlot),
- position(createPosition(QLatin1String("position"))),
- mSize(6),
- mStyle(tsCrosshair),
- mGraph(0),
- mGraphKey(0),
- mInterpolating(false)
- {
- position->setCoords(0, 0);
- setBrush(Qt::NoBrush);
- setSelectedBrush(Qt::NoBrush);
- setPen(QPen(Qt::black));
- setSelectedPen(QPen(Qt::blue, 2));
- }
- QCPItemTracer::~QCPItemTracer()
- {
- }
- /*!
- Sets the pen that will be used to draw the line of the tracer
-
- \see setSelectedPen, setBrush
- */
- void QCPItemTracer::setPen(const QPen &pen)
- {
- mPen = pen;
- }
- /*!
- Sets the pen that will be used to draw the line of the tracer when selected
-
- \see setPen, setSelected
- */
- void QCPItemTracer::setSelectedPen(const QPen &pen)
- {
- mSelectedPen = pen;
- }
- /*!
- Sets the brush that will be used to draw any fills of the tracer
-
- \see setSelectedBrush, setPen
- */
- void QCPItemTracer::setBrush(const QBrush &brush)
- {
- mBrush = brush;
- }
- /*!
- Sets the brush that will be used to draw any fills of the tracer, when selected.
-
- \see setBrush, setSelected
- */
- void QCPItemTracer::setSelectedBrush(const QBrush &brush)
- {
- mSelectedBrush = brush;
- }
- /*!
- Sets the size of the tracer in pixels, if the style supports setting a size (e.g. \ref tsSquare
- does, \ref tsCrosshair does not).
- */
- void QCPItemTracer::setSize(double size)
- {
- mSize = size;
- }
- /*!
- Sets the style/visual appearance of the tracer.
-
- If you only want to use the tracer \a position as an anchor for other items, set \a style to
- \ref tsNone.
- */
- void QCPItemTracer::setStyle(QCPItemTracer::TracerStyle style)
- {
- mStyle = style;
- }
- /*!
- Sets the QCPGraph this tracer sticks to. The tracer \a position will be set to type
- QCPItemPosition::ptPlotCoords and the axes will be set to the axes of \a graph.
-
- To free the tracer from any graph, set \a graph to 0. The tracer \a position can then be placed
- freely like any other item position. This is the state the tracer will assume when its graph gets
- deleted while still attached to it.
-
- \see setGraphKey
- */
- void QCPItemTracer::setGraph(QCPGraph *graph)
- {
- if (graph)
- {
- if (graph->parentPlot() == mParentPlot)
- {
- position->setType(QCPItemPosition::ptPlotCoords);
- position->setAxes(graph->keyAxis(), graph->valueAxis());
- mGraph = graph;
- updatePosition();
- } else
- qDebug() << Q_FUNC_INFO << "graph isn't in same QCustomPlot instance as this item";
- } else
- {
- mGraph = 0;
- }
- }
- /*!
- Sets the key of the graph's data point the tracer will be positioned at. This is the only free
- coordinate of a tracer when attached to a graph.
-
- Depending on \ref setInterpolating, the tracer will be either positioned on the data point
- closest to \a key, or will stay exactly at \a key and interpolate the value linearly.
-
- \see setGraph, setInterpolating
- */
- void QCPItemTracer::setGraphKey(double key)
- {
- mGraphKey = key;
- }
- /*!
- Sets whether the value of the graph's data points shall be interpolated, when positioning the
- tracer.
-
- If \a enabled is set to false and a key is given with \ref setGraphKey, the tracer is placed on
- the data point of the graph which is closest to the key, but which is not necessarily exactly
- there. If \a enabled is true, the tracer will be positioned exactly at the specified key, and
- the appropriate value will be interpolated from the graph's data points linearly.
-
- \see setGraph, setGraphKey
- */
- void QCPItemTracer::setInterpolating(bool enabled)
- {
- mInterpolating = enabled;
- }
- /* inherits documentation from base class */
- double QCPItemTracer::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- Q_UNUSED(details)
- if (onlySelectable && !mSelectable)
- return -1;
- QPointF center(position->pixelPosition());
- double w = mSize/2.0;
- QRect clip = clipRect();
- switch (mStyle)
- {
- case tsNone: return -1;
- case tsPlus:
- {
- if (clipRect().intersects(QRectF(center-QPointF(w, w), center+QPointF(w, w)).toRect()))
- return qSqrt(qMin(QCPVector2D(pos).distanceSquaredToLine(center+QPointF(-w, 0), center+QPointF(w, 0)),
- QCPVector2D(pos).distanceSquaredToLine(center+QPointF(0, -w), center+QPointF(0, w))));
- break;
- }
- case tsCrosshair:
- {
- return qSqrt(qMin(QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(clip.left(), center.y()), QCPVector2D(clip.right(), center.y())),
- QCPVector2D(pos).distanceSquaredToLine(QCPVector2D(center.x(), clip.top()), QCPVector2D(center.x(), clip.bottom()))));
- }
- case tsCircle:
- {
- if (clip.intersects(QRectF(center-QPointF(w, w), center+QPointF(w, w)).toRect()))
- {
- // distance to border:
- double centerDist = QCPVector2D(center-pos).length();
- double circleLine = w;
- double result = qAbs(centerDist-circleLine);
- // filled ellipse, allow click inside to count as hit:
- if (result > mParentPlot->selectionTolerance()*0.99 && mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0)
- {
- if (centerDist <= circleLine)
- result = mParentPlot->selectionTolerance()*0.99;
- }
- return result;
- }
- break;
- }
- case tsSquare:
- {
- if (clip.intersects(QRectF(center-QPointF(w, w), center+QPointF(w, w)).toRect()))
- {
- QRectF rect = QRectF(center-QPointF(w, w), center+QPointF(w, w));
- bool filledRect = mBrush.style() != Qt::NoBrush && mBrush.color().alpha() != 0;
- return rectDistance(rect, pos, filledRect);
- }
- break;
- }
- }
- return -1;
- }
- /* inherits documentation from base class */
- void QCPItemTracer::draw(QCPPainter *painter)
- {
- updatePosition();
- if (mStyle == tsNone)
- return;
- painter->setPen(mainPen());
- painter->setBrush(mainBrush());
- QPointF center(position->pixelPosition());
- double w = mSize/2.0;
- QRect clip = clipRect();
- switch (mStyle)
- {
- case tsNone: return;
- case tsPlus:
- {
- if (clip.intersects(QRectF(center-QPointF(w, w), center+QPointF(w, w)).toRect()))
- {
- painter->drawLine(QLineF(center+QPointF(-w, 0), center+QPointF(w, 0)));
- painter->drawLine(QLineF(center+QPointF(0, -w), center+QPointF(0, w)));
- }
- break;
- }
- case tsCrosshair:
- {
- if (center.y() > clip.top() && center.y() < clip.bottom())
- painter->drawLine(QLineF(clip.left(), center.y(), clip.right(), center.y()));
- if (center.x() > clip.left() && center.x() < clip.right())
- painter->drawLine(QLineF(center.x(), clip.top(), center.x(), clip.bottom()));
- break;
- }
- case tsCircle:
- {
- if (clip.intersects(QRectF(center-QPointF(w, w), center+QPointF(w, w)).toRect()))
- painter->drawEllipse(center, w, w);
- break;
- }
- case tsSquare:
- {
- if (clip.intersects(QRectF(center-QPointF(w, w), center+QPointF(w, w)).toRect()))
- painter->drawRect(QRectF(center-QPointF(w, w), center+QPointF(w, w)));
- break;
- }
- }
- }
- /*!
- If the tracer is connected with a graph (\ref setGraph), this function updates the tracer's \a
- position to reside on the graph data, depending on the configured key (\ref setGraphKey).
-
- It is called automatically on every redraw and normally doesn't need to be called manually. One
- exception is when you want to read the tracer coordinates via \a position and are not sure that
- the graph's data (or the tracer key with \ref setGraphKey) hasn't changed since the last redraw.
- In that situation, call this function before accessing \a position, to make sure you don't get
- out-of-date coordinates.
-
- If there is no graph set on this tracer, this function does nothing.
- */
- void QCPItemTracer::updatePosition()
- {
- if (mGraph)
- {
- if (mParentPlot->hasPlottable(mGraph))
- {
- if (mGraph->data()->size() > 1)
- {
- QCPGraphDataContainer::const_iterator first = mGraph->data()->constBegin();
- QCPGraphDataContainer::const_iterator last = mGraph->data()->constEnd()-1;
- if (mGraphKey <= first->key)
- position->setCoords(first->key, first->value);
- else if (mGraphKey >= last->key)
- position->setCoords(last->key, last->value);
- else
- {
- QCPGraphDataContainer::const_iterator it = mGraph->data()->findBegin(mGraphKey);
- if (it != mGraph->data()->constEnd()) // mGraphKey is not exactly on last iterator, but somewhere between iterators
- {
- QCPGraphDataContainer::const_iterator prevIt = it;
- ++it; // won't advance to constEnd because we handled that case (mGraphKey >= last->key) before
- if (mInterpolating)
- {
- // interpolate between iterators around mGraphKey:
- double slope = 0;
- if (!qFuzzyCompare((double)it->key, (double)prevIt->key))
- slope = (it->value-prevIt->value)/(it->key-prevIt->key);
- position->setCoords(mGraphKey, (mGraphKey-prevIt->key)*slope+prevIt->value);
- } else
- {
- // find iterator with key closest to mGraphKey:
- if (mGraphKey < (prevIt->key+it->key)*0.5)
- position->setCoords(prevIt->key, prevIt->value);
- else
- position->setCoords(it->key, it->value);
- }
- } else // mGraphKey is exactly on last iterator (should actually be caught when comparing first/last keys, but this is a failsafe for fp uncertainty)
- position->setCoords(it->key, it->value);
- }
- } else if (mGraph->data()->size() == 1)
- {
- QCPGraphDataContainer::const_iterator it = mGraph->data()->constBegin();
- position->setCoords(it->key, it->value);
- } else
- qDebug() << Q_FUNC_INFO << "graph has no data";
- } else
- qDebug() << Q_FUNC_INFO << "graph not contained in QCustomPlot instance (anymore)";
- }
- }
- /*! \internal
- Returns the pen that should be used for drawing lines. Returns mPen when the item is not selected
- and mSelectedPen when it is.
- */
- QPen QCPItemTracer::mainPen() const
- {
- return mSelected ? mSelectedPen : mPen;
- }
- /*! \internal
- Returns the brush that should be used for drawing fills of the item. Returns mBrush when the item
- is not selected and mSelectedBrush when it is.
- */
- QBrush QCPItemTracer::mainBrush() const
- {
- return mSelected ? mSelectedBrush : mBrush;
- }
- /* end of 'src/items/item-tracer.cpp' */
- /* including file 'src/items/item-bracket.cpp', size 10687 */
- /* commit 9868e55d3b412f2f89766bb482fcf299e93a0988 2017-09-04 01:56:22 +0200 */
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- //////////////////// QCPItemBracket
- ////////////////////////////////////////////////////////////////////////////////////////////////////
- /*! \class QCPItemBracket
- \brief A bracket for referencing/highlighting certain parts in the plot.
- \image html QCPItemBracket.png "Bracket example. Blue dotted circles are anchors, solid blue discs are positions."
- It has two positions, \a left and \a right, which define the span of the bracket. If \a left is
- actually farther to the left than \a right, the bracket is opened to the bottom, as shown in the
- example image.
-
- The bracket supports multiple styles via \ref setStyle. The length, i.e. how far the bracket
- stretches away from the embraced span, can be controlled with \ref setLength.
-
- \image html QCPItemBracket-length.png
- <center>Demonstrating the effect of different values for \ref setLength, for styles \ref
- bsCalligraphic and \ref bsSquare. Anchors and positions are displayed for reference.</center>
-
- It provides an anchor \a center, to allow connection of other items, e.g. an arrow (QCPItemLine
- or QCPItemCurve) or a text label (QCPItemText), to the bracket.
- */
- /*!
- Creates a bracket item and sets default values.
-
- The created item is automatically registered with \a parentPlot. This QCustomPlot instance takes
- ownership of the item, so do not delete it manually but use QCustomPlot::removeItem() instead.
- */
- QCPItemBracket::QCPItemBracket(QCustomPlot *parentPlot) :
- QCPAbstractItem(parentPlot),
- left(createPosition(QLatin1String("left"))),
- right(createPosition(QLatin1String("right"))),
- center(createAnchor(QLatin1String("center"), aiCenter)),
- mLength(8),
- mStyle(bsCalligraphic)
- {
- left->setCoords(0, 0);
- right->setCoords(1, 1);
-
- setPen(QPen(Qt::black));
- setSelectedPen(QPen(Qt::blue, 2));
- }
- QCPItemBracket::~QCPItemBracket()
- {
- }
- /*!
- Sets the pen that will be used to draw the bracket.
-
- Note that when the style is \ref bsCalligraphic, only the color will be taken from the pen, the
- stroke and width are ignored. To change the apparent stroke width of a calligraphic bracket, use
- \ref setLength, which has a similar effect.
-
- \see setSelectedPen
- */
- void QCPItemBracket::setPen(const QPen &pen)
- {
- mPen = pen;
- }
- /*!
- Sets the pen that will be used to draw the bracket when selected
-
- \see setPen, setSelected
- */
- void QCPItemBracket::setSelectedPen(const QPen &pen)
- {
- mSelectedPen = pen;
- }
- /*!
- Sets the \a length in pixels how far the bracket extends in the direction towards the embraced
- span of the bracket (i.e. perpendicular to the <i>left</i>-<i>right</i>-direction)
-
- \image html QCPItemBracket-length.png
- <center>Demonstrating the effect of different values for \ref setLength, for styles \ref
- bsCalligraphic and \ref bsSquare. Anchors and positions are displayed for reference.</center>
- */
- void QCPItemBracket::setLength(double length)
- {
- mLength = length;
- }
- /*!
- Sets the style of the bracket, i.e. the shape/visual appearance.
-
- \see setPen
- */
- void QCPItemBracket::setStyle(QCPItemBracket::BracketStyle style)
- {
- mStyle = style;
- }
- /* inherits documentation from base class */
- double QCPItemBracket::selectTest(const QPointF &pos, bool onlySelectable, QVariant *details) const
- {
- Q_UNUSED(details)
- if (onlySelectable && !mSelectable)
- return -1;
-
- QCPVector2D p(pos);
- QCPVector2D leftVec(left->pixelPosition());
- QCPVector2D rightVec(right->pixelPosition());
- if (leftVec.toPoint() == rightVec.toPoint())
- return -1;
-
- QCPVector2D widthVec = (rightVec-leftVec)*0.5;
- QCPVector2D lengthVec = widthVec.perpendicular().normalized()*mLength;
- QCPVector2D centerVec = (rightVec+leftVec)*0.5-lengthVec;
-
- switch (mStyle)
- {
- case QCPItemBracket::bsSquare:
- case QCPItemBracket::bsRound:
- {
- double a = p.distanceSquaredToLine(centerVec-widthVec, centerVec+widthVec);
- double b = p.distanceSquaredToLine(centerVec-widthVec+lengthVec, centerVec-widthVec);
- double c = p.distanceSquaredToLine(centerVec+widthVec+lengthVec, centerVec+widthVec);
- return qSqrt(qMin(qMin(a, b), c));
- }
- case QCPItemBracket::bsCurly:
- case QCPItemBracket::bsCalligraphic:
- {
- double a = p.distanceSquaredToLine(centerVec-widthVec*0.75+lengthVec*0.15, centerVec+lengthVec*0.3);
- double b = p.distanceSquaredToLine(centerVec-widthVec+lengthVec*0.7, centerVec-widthVec*0.75+lengthVec*0.15);
- double c = p.distanceSquaredToLine(centerVec+widthVec*0.75+lengthVec*0.15, centerVec+lengthVec*0.3);
- double d = p.distanceSquaredToLine(centerVec+widthVec+lengthVec*0.7, centerVec+widthVec*0.75+lengthVec*0.15);
- return qSqrt(qMin(qMin(a, b), qMin(c, d)));
- }
- }
- return -1;
- }
- /* inherits documentation from base class */
- void QCPItemBracket::draw(QCPPainter *painter)
- {
- QCPVector2D leftVec(left->pixelPosition());
- QCPVector2D rightVec(right->pixelPosition());
- if (leftVec.toPoint() == rightVec.toPoint())
- return;
-
- QCPVector2D widthVec = (rightVec-leftVec)*0.5;
- QCPVector2D lengthVec = widthVec.perpendicular().normalized()*mLength;
- QCPVector2D centerVec = (rightVec+leftVec)*0.5-lengthVec;
- QPolygon boundingPoly;
- boundingPoly << leftVec.toPoint() << rightVec.toPoint()
- << (rightVec-lengthVec).toPoint() << (leftVec-lengthVec).toPoint();
- QRect clip = clipRect().adjusted(-mainPen().widthF(), -mainPen().widthF(), mainPen().widthF(), mainPen().widthF());
- if (clip.intersects(boundingPoly.boundingRect()))
- {
- painter->setPen(mainPen());
- switch (mStyle)
- {
- case bsSquare:
- {
- painter->drawLine((centerVec+widthVec).toPointF(), (centerVec-widthVec).toPointF());
- painter->drawLine((centerVec+widthVec).toPointF(), (centerVec+widthVec+lengthVec).toPointF());
- painter->drawLine((centerVec-widthVec).toPointF(), (centerVec-widthVec+lengthVec).toPointF());
- break;
- }
- case bsRound:
- {
- painter->setBrush(Qt::NoBrush);
- QPainterPath path;
- path.moveTo((centerVec+widthVec+lengthVec).toPointF());
- path.cubicTo((centerVec+widthVec).toPointF(), (centerVec+widthVec).toPointF(), centerVec.toPointF());
- path.cubicTo((centerVec-widthVec).toPointF(), (centerVec-widthVec).toPointF(), (centerVec-widthVec+lengthVec).toPointF());
- painter->drawPath(path);
- break;
- }
- case bsCurly:
- {
- painter->setBrush(Qt::NoBrush);
- QPainterPath path;
- path.moveTo((centerVec+widthVec+lengthVec).toPointF());
- path.cubicTo((centerVec+widthVec-lengthVec*0.8).toPointF(), (centerVec+0.4*widthVec+lengthVec).toPointF(), centerVec.toPointF());
- path.cubicTo((centerVec-0.4*widthVec+lengthVec).toPointF(), (centerVec-widthVec-lengthVec*0.8).toPointF(), (centerVec-widthVec+lengthVec).toPointF());
- painter->drawPath(path);
- break;
- }
- case bsCalligraphic:
- {
- painter->setPen(Qt::NoPen);
- painter->setBrush(QBrush(mainPen().color()));
- QPainterPath path;
- path.moveTo((centerVec+widthVec+lengthVec).toPointF());
-
- path.cubicTo((centerVec+widthVec-lengthVec*0.8).toPointF(), (centerVec+0.4*widthVec+0.8*lengthVec).toPointF(), centerVec.toPointF());
- path.cubicTo((centerVec-0.4*widthVec+0.8*lengthVec).toPointF(), (centerVec-widthVec-lengthVec*0.8).toPointF(), (centerVec-widthVec+lengthVec).toPointF());
-
- path.cubicTo((centerVec-widthVec-lengthVec*0.5).toPointF(), (centerVec-0.2*widthVec+1.2*lengthVec).toPointF(), (centerVec+lengthVec*0.2).toPointF());
- path.cubicTo((centerVec+0.2*widthVec+1.2*lengthVec).toPointF(), (centerVec+widthVec-lengthVec*0.5).toPointF(), (centerVec+widthVec+lengthVec).toPointF());
-
- painter->drawPath(path);
- break;
- }
- }
- }
- }
- /* inherits documentation from base class */
- QPointF QCPItemBracket::anchorPixelPosition(int anchorId) const
- {
- QCPVector2D leftVec(left->pixelPosition());
- QCPVector2D rightVec(right->pixelPosition());
- if (leftVec.toPoint() == rightVec.toPoint())
- return leftVec.toPointF();
-
- QCPVector2D widthVec = (rightVec-leftVec)*0.5;
- QCPVector2D lengthVec = widthVec.perpendicular().normalized()*mLength;
- QCPVector2D centerVec = (rightVec+leftVec)*0.5-lengthVec;
-
- switch (anchorId)
- {
- case aiCenter:
- return centerVec.toPointF();
- }
- qDebug() << Q_FUNC_INFO << "invalid anchorId" << anchorId;
- return QPointF();
- }
- /*! \internal
- Returns the pen that should be used for drawing lines. Returns mPen when the
- item is not selected and mSelectedPen when it is.
- */
- QPen QCPItemBracket::mainPen() const
- {
- return mSelected ? mSelectedPen : mPen;
- }
- /* end of 'src/items/item-bracket.cpp' */
|