uPlot.esm.js 96 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401
  1. /**
  2. * Copyright (c) 2021, Leon Sorokin
  3. * All rights reserved. (MIT Licensed)
  4. *
  5. * uPlot.js (μPlot)
  6. * A small, fast chart for time series, lines, areas, ohlc & bars
  7. * https://github.com/leeoniya/uPlot (v1.6.1)
  8. */
  9. const FEAT_TIME = true;
  10. function debounce(fn, time) {
  11. let pending = null;
  12. function run() {
  13. pending = null;
  14. fn();
  15. }
  16. return function() {
  17. clearTimeout(pending);
  18. pending = setTimeout(run, time);
  19. }
  20. }
  21. // binary search for index of closest value
  22. function closestIdx(num, arr, lo, hi) {
  23. let mid;
  24. lo = lo || 0;
  25. hi = hi || arr.length - 1;
  26. let bitwise = hi <= 2147483647;
  27. while (hi - lo > 1) {
  28. mid = bitwise ? (lo + hi) >> 1 : floor((lo + hi) / 2);
  29. if (arr[mid] < num)
  30. lo = mid;
  31. else
  32. hi = mid;
  33. }
  34. if (num - arr[lo] <= arr[hi] - num)
  35. return lo;
  36. return hi;
  37. }
  38. function nonNullIdx(data, _i0, _i1, dir) {
  39. for (let i = dir == 1 ? _i0 : _i1; i >= _i0 && i <= _i1; i += dir) {
  40. if (data[i] != null)
  41. return i;
  42. }
  43. return -1;
  44. }
  45. function getMinMax(data, _i0, _i1, sorted) {
  46. // console.log("getMinMax()");
  47. let _min = inf;
  48. let _max = -inf;
  49. if (sorted == 1) {
  50. _min = data[_i0];
  51. _max = data[_i1];
  52. }
  53. else if (sorted == -1) {
  54. _min = data[_i1];
  55. _max = data[_i0];
  56. }
  57. else {
  58. for (let i = _i0; i <= _i1; i++) {
  59. if (data[i] != null) {
  60. _min = min(_min, data[i]);
  61. _max = max(_max, data[i]);
  62. }
  63. }
  64. }
  65. return [_min, _max];
  66. }
  67. function getMinMaxLog(data, _i0, _i1) {
  68. // console.log("getMinMax()");
  69. let _min = inf;
  70. let _max = -inf;
  71. for (let i = _i0; i <= _i1; i++) {
  72. if (data[i] > 0) {
  73. _min = min(_min, data[i]);
  74. _max = max(_max, data[i]);
  75. }
  76. }
  77. return [
  78. _min == inf ? 1 : _min,
  79. _max == -inf ? 10 : _max,
  80. ];
  81. }
  82. const _fixedTuple = [0, 0];
  83. function fixIncr(minIncr, maxIncr, minExp, maxExp) {
  84. _fixedTuple[0] = minExp < 0 ? roundDec(minIncr, -minExp) : minIncr;
  85. _fixedTuple[1] = maxExp < 0 ? roundDec(maxIncr, -maxExp) : maxIncr;
  86. return _fixedTuple;
  87. }
  88. function rangeLog(min, max, base, fullMags) {
  89. let logFn = base == 10 ? log10 : log2;
  90. if (min == max) {
  91. min /= base;
  92. max *= base;
  93. }
  94. let minExp, maxExp, minMaxIncrs;
  95. if (fullMags) {
  96. minExp = floor(logFn(min));
  97. maxExp = ceil(logFn(max));
  98. minMaxIncrs = fixIncr(pow(base, minExp), pow(base, maxExp), minExp, maxExp);
  99. min = minMaxIncrs[0];
  100. max = minMaxIncrs[1];
  101. }
  102. else {
  103. minExp = floor(logFn(min));
  104. maxExp = floor(logFn(max));
  105. minMaxIncrs = fixIncr(pow(base, minExp), pow(base, maxExp), minExp, maxExp);
  106. min = incrRoundDn(min, minMaxIncrs[0]);
  107. max = incrRoundUp(max, minMaxIncrs[1]);
  108. }
  109. return [min, max];
  110. }
  111. const _eqRangePart = {
  112. pad: 0,
  113. soft: null,
  114. mode: 0,
  115. };
  116. const _eqRange = {
  117. min: _eqRangePart,
  118. max: _eqRangePart,
  119. };
  120. // this ensures that non-temporal/numeric y-axes get multiple-snapped padding added above/below
  121. // TODO: also account for incrs when snapping to ensure top of axis gets a tick & value
  122. function rangeNum(_min, _max, mult, extra) {
  123. if (isObj(mult))
  124. return _rangeNum(_min, _max, mult);
  125. _eqRangePart.pad = mult;
  126. _eqRangePart.soft = extra ? 0 : null;
  127. _eqRangePart.mode = extra ? 3 : 0;
  128. return _rangeNum(_min, _max, _eqRange);
  129. }
  130. // nullish coalesce
  131. function ifNull(lh, rh) {
  132. return lh == null ? rh : lh;
  133. }
  134. function _rangeNum(_min, _max, cfg) {
  135. let cmin = cfg.min;
  136. let cmax = cfg.max;
  137. let padMin = ifNull(cmin.pad, 0);
  138. let padMax = ifNull(cmax.pad, 0);
  139. let hardMin = ifNull(cmin.hard, -inf);
  140. let hardMax = ifNull(cmax.hard, inf);
  141. let softMin = ifNull(cmin.soft, inf);
  142. let softMax = ifNull(cmax.soft, -inf);
  143. let softMinMode = ifNull(cmin.mode, 0);
  144. let softMaxMode = ifNull(cmax.mode, 0);
  145. let delta = _max - _min;
  146. let nonZeroDelta = delta || abs(_max) || 1e3;
  147. let mag = log10(nonZeroDelta);
  148. let base = pow(10, floor(mag));
  149. let _padMin = nonZeroDelta * (delta == 0 ? (_min == 0 ? .1 : 1) : padMin);
  150. let _newMin = roundDec(incrRoundDn(_min - _padMin, base/10), 6);
  151. let _softMin = _min >= softMin && (softMinMode == 1 || softMinMode == 3 && _newMin <= softMin || softMinMode == 2 && _newMin >= softMin) ? softMin : inf;
  152. let minLim = max(hardMin, _newMin < _softMin && _min >= _softMin ? _softMin : min(_softMin, _newMin));
  153. let _padMax = nonZeroDelta * (delta == 0 ? (_max == 0 ? .1 : 1) : padMax);
  154. let _newMax = roundDec(incrRoundUp(_max + _padMax, base/10), 6);
  155. let _softMax = _max <= softMax && (softMaxMode == 1 || softMaxMode == 3 && _newMax >= softMax || softMaxMode == 2 && _newMax <= softMax) ? softMax : -inf;
  156. let maxLim = min(hardMax, _newMax > _softMax && _max <= _softMax ? _softMax : max(_softMax, _newMax));
  157. if (minLim == maxLim && minLim == 0)
  158. maxLim = 100;
  159. return [minLim, maxLim];
  160. }
  161. // alternative: https://stackoverflow.com/a/2254896
  162. const fmtNum = new Intl.NumberFormat(navigator.language).format;
  163. const M = Math;
  164. const abs = M.abs;
  165. const floor = M.floor;
  166. const round = M.round;
  167. const ceil = M.ceil;
  168. const min = M.min;
  169. const max = M.max;
  170. const pow = M.pow;
  171. const sqrt = M.sqrt;
  172. const log10 = M.log10;
  173. const log2 = M.log2;
  174. const PI = M.PI;
  175. const inf = Infinity;
  176. function incrRound(num, incr) {
  177. return round(num/incr)*incr;
  178. }
  179. function clamp(num, _min, _max) {
  180. return min(max(num, _min), _max);
  181. }
  182. function fnOrSelf(v) {
  183. return typeof v == "function" ? v : () => v;
  184. }
  185. const retArg1 = (_0, _1) => _1;
  186. const retNull = _ => null;
  187. function incrRoundUp(num, incr) {
  188. return ceil(num/incr)*incr;
  189. }
  190. function incrRoundDn(num, incr) {
  191. return floor(num/incr)*incr;
  192. }
  193. function roundDec(val, dec) {
  194. return round(val * (dec = 10**dec)) / dec;
  195. }
  196. const fixedDec = new Map();
  197. function guessDec(num) {
  198. return ((""+num).split(".")[1] || "").length;
  199. }
  200. function genIncrs(base, minExp, maxExp, mults) {
  201. let incrs = [];
  202. let multDec = mults.map(guessDec);
  203. for (let exp = minExp; exp < maxExp; exp++) {
  204. let expa = abs(exp);
  205. let mag = roundDec(pow(base, exp), expa);
  206. for (let i = 0; i < mults.length; i++) {
  207. let _incr = mults[i] * mag;
  208. let dec = (_incr >= 0 && exp >= 0 ? 0 : expa) + (exp >= multDec[i] ? 0 : multDec[i]);
  209. let incr = roundDec(_incr, dec);
  210. incrs.push(incr);
  211. fixedDec.set(incr, dec);
  212. }
  213. }
  214. return incrs;
  215. }
  216. //export const assign = Object.assign;
  217. const EMPTY_OBJ = {};
  218. const isArr = Array.isArray;
  219. function isStr(v) {
  220. return typeof v == 'string';
  221. }
  222. function isObj(v) {
  223. let is = false;
  224. if (v != null) {
  225. let c = v.constructor;
  226. is = c == null || c == Object;
  227. }
  228. return is;
  229. }
  230. function copy(o) {
  231. let out;
  232. if (isArr(o))
  233. out = o.map(copy);
  234. else if (isObj(o)) {
  235. out = {};
  236. for (var k in o)
  237. out[k] = copy(o[k]);
  238. }
  239. else
  240. out = o;
  241. return out;
  242. }
  243. function assign(targ) {
  244. let args = arguments;
  245. for (let i = 1; i < args.length; i++) {
  246. let src = args[i];
  247. for (let key in src) {
  248. if (isObj(targ[key]))
  249. assign(targ[key], copy(src[key]));
  250. else
  251. targ[key] = copy(src[key]);
  252. }
  253. }
  254. return targ;
  255. }
  256. // nullModes
  257. const NULL_IGNORE = 0; // all nulls are ignored by isGap
  258. const NULL_GAP = 1; // alignment nulls are ignored by isGap (default)
  259. const NULL_EXPAND = 2; // nulls are expand to include adjacent alignment nulls
  260. // nullModes is a tables-matched array indicating how to treat nulls in each series
  261. function join(tables, nullModes) {
  262. if (tables.length == 1) {
  263. return {
  264. data: tables[0],
  265. isGap: nullModes ? (u, seriesIdx, dataIdx) => nullModes[0][seriesIdx] != NULL_IGNORE : () => true,
  266. };
  267. }
  268. let xVals = new Set();
  269. let xNulls = [new Set()];
  270. for (let ti = 0; ti < tables.length; ti++) {
  271. let t = tables[ti];
  272. let xs = t[0];
  273. let len = xs.length;
  274. for (let i = 0; i < len; i++)
  275. xVals.add(xs[i]);
  276. for (let si = 1; si < t.length; si++) {
  277. let nulls = new Set();
  278. // cache original nulls for isGap lookup
  279. if (nullModes == null || nullModes[ti][si] == NULL_GAP || nullModes[ti][si] == NULL_EXPAND) {
  280. let ys = t[si];
  281. for (let i = 0; i < len; i++) {
  282. if (ys[i] == null)
  283. nulls.add(xs[i]);
  284. }
  285. }
  286. xNulls.push(nulls);
  287. }
  288. }
  289. let data = [Array.from(xVals).sort((a, b) => a - b)];
  290. let alignedLen = data[0].length;
  291. let xIdxs = new Map();
  292. for (let i = 0; i < alignedLen; i++)
  293. xIdxs.set(data[0][i], i);
  294. let gsi = 1;
  295. for (let ti = 0; ti < tables.length; ti++) {
  296. let t = tables[ti];
  297. let xs = t[0];
  298. for (let si = 1; si < t.length; si++) {
  299. let ys = t[si];
  300. let yVals = Array(alignedLen).fill(null);
  301. for (let i = 0; i < ys.length; i++)
  302. yVals[xIdxs.get(xs[i])] = ys[i];
  303. // mark all filler nulls as explicit when adjacent to existing explicit nulls (minesweeper)
  304. if (nullModes && nullModes[ti][si] == NULL_EXPAND) {
  305. let nulls = xNulls[gsi];
  306. let size = nulls.size;
  307. let i = 0;
  308. let xi;
  309. let lastAddedX = -inf;
  310. for (let xVal of nulls.values()) {
  311. if (i++ == size)
  312. break;
  313. if (xVal > lastAddedX) {
  314. let xIdx = xIdxs.get(xVal);
  315. xi = xIdx - 1;
  316. while (yVals[xi] === null) {
  317. nulls.add(data[0][xi]);
  318. xi--;
  319. }
  320. xi = xIdx + 1;
  321. while (yVals[xi] === null) {
  322. nulls.add(lastAddedX = data[0][xi]);
  323. xi++;
  324. }
  325. }
  326. }
  327. }
  328. data.push(yVals);
  329. gsi++;
  330. }
  331. }
  332. return {
  333. data: data,
  334. isGap(u, seriesIdx, dataIdx) {
  335. let xVal = u._data[0][dataIdx];
  336. return xNulls[seriesIdx].has(xVal);
  337. },
  338. };
  339. }
  340. const microTask = typeof queueMicrotask == "undefined" ? fn => Promise.resolve().then(fn) : queueMicrotask;
  341. const WIDTH = "width";
  342. const HEIGHT = "height";
  343. const TOP = "top";
  344. const BOTTOM = "bottom";
  345. const LEFT = "left";
  346. const RIGHT = "right";
  347. const hexBlack = "#000";
  348. const transparent = hexBlack + "0";
  349. const mousemove = "mousemove";
  350. const mousedown = "mousedown";
  351. const mouseup = "mouseup";
  352. const mouseenter = "mouseenter";
  353. const mouseleave = "mouseleave";
  354. const dblclick = "dblclick";
  355. const resize = "resize";
  356. const scroll = "scroll";
  357. const pre = "u-";
  358. const UPLOT = "uplot";
  359. const ORI_HZ = pre + "hz";
  360. const ORI_VT = pre + "vt";
  361. const TITLE = pre + "title";
  362. const WRAP = pre + "wrap";
  363. const UNDER = pre + "under";
  364. const OVER = pre + "over";
  365. const OFF = pre + "off";
  366. const SELECT = pre + "select";
  367. const CURSOR_X = pre + "cursor-x";
  368. const CURSOR_Y = pre + "cursor-y";
  369. const CURSOR_PT = pre + "cursor-pt";
  370. const LEGEND = pre + "legend";
  371. const LEGEND_LIVE = pre + "live";
  372. const LEGEND_INLINE = pre + "inline";
  373. const LEGEND_THEAD = pre + "thead";
  374. const LEGEND_SERIES = pre + "series";
  375. const LEGEND_MARKER = pre + "marker";
  376. const LEGEND_LABEL = pre + "label";
  377. const LEGEND_VALUE = pre + "value";
  378. const doc = document;
  379. const win = window;
  380. const pxRatio = devicePixelRatio;
  381. function addClass(el, c) {
  382. c != null && el.classList.add(c);
  383. }
  384. function remClass(el, c) {
  385. el.classList.remove(c);
  386. }
  387. function setStylePx(el, name, value) {
  388. el.style[name] = value + "px";
  389. }
  390. function placeTag(tag, cls, targ, refEl) {
  391. let el = doc.createElement(tag);
  392. if (cls != null)
  393. addClass(el, cls);
  394. if (targ != null)
  395. targ.insertBefore(el, refEl);
  396. return el;
  397. }
  398. function placeDiv(cls, targ) {
  399. return placeTag("div", cls, targ);
  400. }
  401. function trans(el, xPos, yPos, xMax, yMax) {
  402. el.style.transform = "translate(" + xPos + "px," + yPos + "px)";
  403. if (xPos < 0 || yPos < 0 || xPos > xMax || yPos > yMax)
  404. addClass(el, OFF);
  405. else
  406. remClass(el, OFF);
  407. }
  408. const evOpts = {passive: true};
  409. function on(ev, el, cb) {
  410. el.addEventListener(ev, cb, evOpts);
  411. }
  412. function off(ev, el, cb) {
  413. el.removeEventListener(ev, cb, evOpts);
  414. }
  415. const months = [
  416. "January",
  417. "February",
  418. "March",
  419. "April",
  420. "May",
  421. "June",
  422. "July",
  423. "August",
  424. "September",
  425. "October",
  426. "November",
  427. "December",
  428. ];
  429. const days = [
  430. "Sunday",
  431. "Monday",
  432. "Tuesday",
  433. "Wednesday",
  434. "Thursday",
  435. "Friday",
  436. "Saturday",
  437. ];
  438. function slice3(str) {
  439. return str.slice(0, 3);
  440. }
  441. const days3 = days.map(slice3);
  442. const months3 = months.map(slice3);
  443. const engNames = {
  444. MMMM: months,
  445. MMM: months3,
  446. WWWW: days,
  447. WWW: days3,
  448. };
  449. function zeroPad2(int) {
  450. return (int < 10 ? '0' : '') + int;
  451. }
  452. function zeroPad3(int) {
  453. return (int < 10 ? '00' : int < 100 ? '0' : '') + int;
  454. }
  455. /*
  456. function suffix(int) {
  457. let mod10 = int % 10;
  458. return int + (
  459. mod10 == 1 && int != 11 ? "st" :
  460. mod10 == 2 && int != 12 ? "nd" :
  461. mod10 == 3 && int != 13 ? "rd" : "th"
  462. );
  463. }
  464. */
  465. const getFullYear = 'getFullYear';
  466. const getMonth = 'getMonth';
  467. const getDate = 'getDate';
  468. const getDay = 'getDay';
  469. const getHours = 'getHours';
  470. const getMinutes = 'getMinutes';
  471. const getSeconds = 'getSeconds';
  472. const getMilliseconds = 'getMilliseconds';
  473. const subs = {
  474. // 2019
  475. YYYY: d => d[getFullYear](),
  476. // 19
  477. YY: d => (d[getFullYear]()+'').slice(2),
  478. // July
  479. MMMM: (d, names) => names.MMMM[d[getMonth]()],
  480. // Jul
  481. MMM: (d, names) => names.MMM[d[getMonth]()],
  482. // 07
  483. MM: d => zeroPad2(d[getMonth]()+1),
  484. // 7
  485. M: d => d[getMonth]()+1,
  486. // 09
  487. DD: d => zeroPad2(d[getDate]()),
  488. // 9
  489. D: d => d[getDate](),
  490. // Monday
  491. WWWW: (d, names) => names.WWWW[d[getDay]()],
  492. // Mon
  493. WWW: (d, names) => names.WWW[d[getDay]()],
  494. // 03
  495. HH: d => zeroPad2(d[getHours]()),
  496. // 3
  497. H: d => d[getHours](),
  498. // 9 (12hr, unpadded)
  499. h: d => {let h = d[getHours](); return h == 0 ? 12 : h > 12 ? h - 12 : h;},
  500. // AM
  501. AA: d => d[getHours]() >= 12 ? 'PM' : 'AM',
  502. // am
  503. aa: d => d[getHours]() >= 12 ? 'pm' : 'am',
  504. // a
  505. a: d => d[getHours]() >= 12 ? 'p' : 'a',
  506. // 09
  507. mm: d => zeroPad2(d[getMinutes]()),
  508. // 9
  509. m: d => d[getMinutes](),
  510. // 09
  511. ss: d => zeroPad2(d[getSeconds]()),
  512. // 9
  513. s: d => d[getSeconds](),
  514. // 374
  515. fff: d => zeroPad3(d[getMilliseconds]()),
  516. };
  517. function fmtDate(tpl, names) {
  518. names = names || engNames;
  519. let parts = [];
  520. let R = /\{([a-z]+)\}|[^{]+/gi, m;
  521. while (m = R.exec(tpl))
  522. parts.push(m[0][0] == '{' ? subs[m[1]] : m[0]);
  523. return d => {
  524. let out = '';
  525. for (let i = 0; i < parts.length; i++)
  526. out += typeof parts[i] == "string" ? parts[i] : parts[i](d, names);
  527. return out;
  528. }
  529. }
  530. const localTz = new Intl.DateTimeFormat().resolvedOptions().timeZone;
  531. // https://stackoverflow.com/questions/15141762/how-to-initialize-a-javascript-date-to-a-particular-time-zone/53652131#53652131
  532. function tzDate(date, tz) {
  533. let date2;
  534. // perf optimization
  535. if (tz == 'Etc/UTC')
  536. date2 = new Date(+date + date.getTimezoneOffset() * 6e4);
  537. else if (tz == localTz)
  538. date2 = date;
  539. else {
  540. date2 = new Date(date.toLocaleString('en-US', {timeZone: tz}));
  541. date2.setMilliseconds(date[getMilliseconds]());
  542. }
  543. return date2;
  544. }
  545. //export const series = [];
  546. // default formatters:
  547. const onlyWhole = v => v % 1 == 0;
  548. const allMults = [1,2,2.5,5];
  549. // ...0.01, 0.02, 0.025, 0.05, 0.1, 0.2, 0.25, 0.5
  550. const decIncrs = genIncrs(10, -16, 0, allMults);
  551. // 1, 2, 2.5, 5, 10, 20, 25, 50...
  552. const oneIncrs = genIncrs(10, 0, 16, allMults);
  553. // 1, 2, 5, 10, 20, 25, 50...
  554. const wholeIncrs = oneIncrs.filter(onlyWhole);
  555. const numIncrs = decIncrs.concat(oneIncrs);
  556. const NL = "\n";
  557. const yyyy = "{YYYY}";
  558. const NLyyyy = NL + yyyy;
  559. const md = "{M}/{D}";
  560. const NLmd = NL + md;
  561. const NLmdyy = NLmd + "/{YY}";
  562. const aa = "{aa}";
  563. const hmm = "{h}:{mm}";
  564. const hmmaa = hmm + aa;
  565. const NLhmmaa = NL + hmmaa;
  566. const ss = ":{ss}";
  567. const _ = null;
  568. function genTimeStuffs(ms) {
  569. let s = ms * 1e3,
  570. m = s * 60,
  571. h = m * 60,
  572. d = h * 24,
  573. mo = d * 30,
  574. y = d * 365;
  575. // min of 1e-3 prevents setting a temporal x ticks too small since Date objects cannot advance ticks smaller than 1ms
  576. let subSecIncrs = ms == 1 ? genIncrs(10, 0, 3, allMults).filter(onlyWhole) : genIncrs(10, -3, 0, allMults);
  577. let timeIncrs = subSecIncrs.concat([
  578. // minute divisors (# of secs)
  579. s,
  580. s * 5,
  581. s * 10,
  582. s * 15,
  583. s * 30,
  584. // hour divisors (# of mins)
  585. m,
  586. m * 5,
  587. m * 10,
  588. m * 15,
  589. m * 30,
  590. // day divisors (# of hrs)
  591. h,
  592. h * 2,
  593. h * 3,
  594. h * 4,
  595. h * 6,
  596. h * 8,
  597. h * 12,
  598. // month divisors TODO: need more?
  599. d,
  600. d * 2,
  601. d * 3,
  602. d * 4,
  603. d * 5,
  604. d * 6,
  605. d * 7,
  606. d * 8,
  607. d * 9,
  608. d * 10,
  609. d * 15,
  610. // year divisors (# months, approx)
  611. mo,
  612. mo * 2,
  613. mo * 3,
  614. mo * 4,
  615. mo * 6,
  616. // century divisors
  617. y,
  618. y * 2,
  619. y * 5,
  620. y * 10,
  621. y * 25,
  622. y * 50,
  623. y * 100,
  624. ]);
  625. // [0]: minimum num secs in the tick incr
  626. // [1]: default tick format
  627. // [2-7]: rollover tick formats
  628. // [8]: mode: 0: replace [1] -> [2-7], 1: concat [1] + [2-7]
  629. const _timeAxisStamps = [
  630. // tick incr default year month day hour min sec mode
  631. [y, yyyy, _, _, _, _, _, _, 1],
  632. [d * 28, "{MMM}", NLyyyy, _, _, _, _, _, 1],
  633. [d, md, NLyyyy, _, _, _, _, _, 1],
  634. [h, "{h}" + aa, NLmdyy, _, NLmd, _, _, _, 1],
  635. [m, hmmaa, NLmdyy, _, NLmd, _, _, _, 1],
  636. [s, ss, NLmdyy + " " + hmmaa, _, NLmd + " " + hmmaa, _, NLhmmaa, _, 1],
  637. [ms, ss + ".{fff}", NLmdyy + " " + hmmaa, _, NLmd + " " + hmmaa, _, NLhmmaa, _, 1],
  638. ];
  639. // the ensures that axis ticks, values & grid are aligned to logical temporal breakpoints and not an arbitrary timestamp
  640. // https://www.timeanddate.com/time/dst/
  641. // https://www.timeanddate.com/time/dst/2019.html
  642. // https://www.epochconverter.com/timezones
  643. function timeAxisSplits(tzDate) {
  644. return (self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace) => {
  645. let splits = [];
  646. let isYr = foundIncr >= y;
  647. let isMo = foundIncr >= mo && foundIncr < y;
  648. // get the timezone-adjusted date
  649. let minDate = tzDate(scaleMin);
  650. let minDateTs = minDate * ms;
  651. // get ts of 12am (this lands us at or before the original scaleMin)
  652. let minMin = mkDate(minDate[getFullYear](), isYr ? 0 : minDate[getMonth](), isMo || isYr ? 1 : minDate[getDate]());
  653. let minMinTs = minMin * ms;
  654. if (isMo || isYr) {
  655. let moIncr = isMo ? foundIncr / mo : 0;
  656. let yrIncr = isYr ? foundIncr / y : 0;
  657. // let tzOffset = scaleMin - minDateTs; // needed?
  658. let split = minDateTs == minMinTs ? minDateTs : mkDate(minMin[getFullYear]() + yrIncr, minMin[getMonth]() + moIncr, 1) * ms;
  659. let splitDate = new Date(split / ms);
  660. let baseYear = splitDate[getFullYear]();
  661. let baseMonth = splitDate[getMonth]();
  662. for (let i = 0; split <= scaleMax; i++) {
  663. let next = mkDate(baseYear + yrIncr * i, baseMonth + moIncr * i, 1);
  664. let offs = next - tzDate(next * ms);
  665. split = (+next + offs) * ms;
  666. if (split <= scaleMax)
  667. splits.push(split);
  668. }
  669. }
  670. else {
  671. let incr0 = foundIncr >= d ? d : foundIncr;
  672. let tzOffset = floor(scaleMin) - floor(minDateTs);
  673. let split = minMinTs + tzOffset + incrRoundUp(minDateTs - minMinTs, incr0);
  674. splits.push(split);
  675. let date0 = tzDate(split);
  676. let prevHour = date0[getHours]() + (date0[getMinutes]() / m) + (date0[getSeconds]() / h);
  677. let incrHours = foundIncr / h;
  678. let minSpace = self.axes[axisIdx]._space;
  679. let pctSpace = foundSpace / minSpace;
  680. while (1) {
  681. split = roundDec(split + foundIncr, ms == 1 ? 0 : 3);
  682. if (split > scaleMax)
  683. break;
  684. if (incrHours > 1) {
  685. let expectedHour = floor(roundDec(prevHour + incrHours, 6)) % 24;
  686. let splitDate = tzDate(split);
  687. let actualHour = splitDate.getHours();
  688. let dstShift = actualHour - expectedHour;
  689. if (dstShift > 1)
  690. dstShift = -1;
  691. split -= dstShift * h;
  692. prevHour = (prevHour + incrHours) % 24;
  693. // add a tick only if it's further than 70% of the min allowed label spacing
  694. let prevSplit = splits[splits.length - 1];
  695. let pctIncr = roundDec((split - prevSplit) / foundIncr, 3);
  696. if (pctIncr * pctSpace >= .7)
  697. splits.push(split);
  698. }
  699. else
  700. splits.push(split);
  701. }
  702. }
  703. return splits;
  704. }
  705. }
  706. return [
  707. timeIncrs,
  708. _timeAxisStamps,
  709. timeAxisSplits,
  710. ];
  711. }
  712. const [ timeIncrsMs, _timeAxisStampsMs, timeAxisSplitsMs ] = genTimeStuffs(1);
  713. const [ timeIncrsS, _timeAxisStampsS, timeAxisSplitsS ] = genTimeStuffs(1e-3);
  714. // base 2
  715. const binIncrs = genIncrs(2, -53, 53, [1]);
  716. /*
  717. console.log({
  718. decIncrs,
  719. oneIncrs,
  720. wholeIncrs,
  721. numIncrs,
  722. timeIncrs,
  723. fixedDec,
  724. });
  725. */
  726. function timeAxisStamps(stampCfg, fmtDate) {
  727. return stampCfg.map(s => s.map((v, i) =>
  728. i == 0 || i == 8 || v == null ? v : fmtDate(i == 1 || s[8] == 0 ? v : s[1] + v)
  729. ));
  730. }
  731. // TODO: will need to accept spaces[] and pull incr into the loop when grid will be non-uniform, eg for log scales.
  732. // currently we ignore this for months since they're *nearly* uniform and the added complexity is not worth it
  733. function timeAxisVals(tzDate, stamps) {
  734. return (self, splits, axisIdx, foundSpace, foundIncr) => {
  735. let s = stamps.find(s => foundIncr >= s[0]) || stamps[stamps.length - 1];
  736. // these track boundaries when a full label is needed again
  737. let prevYear;
  738. let prevMnth;
  739. let prevDate;
  740. let prevHour;
  741. let prevMins;
  742. let prevSecs;
  743. return splits.map(split => {
  744. let date = tzDate(split);
  745. let newYear = date[getFullYear]();
  746. let newMnth = date[getMonth]();
  747. let newDate = date[getDate]();
  748. let newHour = date[getHours]();
  749. let newMins = date[getMinutes]();
  750. let newSecs = date[getSeconds]();
  751. let stamp = (
  752. newYear != prevYear && s[2] ||
  753. newMnth != prevMnth && s[3] ||
  754. newDate != prevDate && s[4] ||
  755. newHour != prevHour && s[5] ||
  756. newMins != prevMins && s[6] ||
  757. newSecs != prevSecs && s[7] ||
  758. s[1]
  759. );
  760. prevYear = newYear;
  761. prevMnth = newMnth;
  762. prevDate = newDate;
  763. prevHour = newHour;
  764. prevMins = newMins;
  765. prevSecs = newSecs;
  766. return stamp(date);
  767. });
  768. }
  769. }
  770. // for when axis.values is defined as a static fmtDate template string
  771. function timeAxisVal(tzDate, dateTpl) {
  772. let stamp = fmtDate(dateTpl);
  773. return (self, splits, axisIdx, foundSpace, foundIncr) => splits.map(split => stamp(tzDate(split)));
  774. }
  775. function mkDate(y, m, d) {
  776. return new Date(y, m, d);
  777. }
  778. function timeSeriesStamp(stampCfg, fmtDate) {
  779. return fmtDate(stampCfg);
  780. }
  781. const _timeSeriesStamp = '{YYYY}-{MM}-{DD} {h}:{mm}{aa}';
  782. function timeSeriesVal(tzDate, stamp) {
  783. return (self, val) => stamp(tzDate(val));
  784. }
  785. const legendWidth = 2;
  786. const legendDash = "solid";
  787. function legendStroke(self, seriesIdx) {
  788. let s = self.series[seriesIdx];
  789. return s.width ? s.stroke(self, seriesIdx) : s.points.width ? s.points.stroke(self, seriesIdx) : null;
  790. }
  791. function legendFill(self, seriesIdx) {
  792. return self.series[seriesIdx].fill(self, seriesIdx);
  793. }
  794. function cursorPointShow(self, si) {
  795. let o = self.cursor.points;
  796. let pt = placeDiv();
  797. let stroke = o.stroke(self, si);
  798. let fill = o.fill(self, si);
  799. pt.style.background = fill || stroke;
  800. let size = o.size(self, si);
  801. let width = o.width(self, si, size);
  802. if (width)
  803. pt.style.border = width + "px solid " + stroke;
  804. let mar = size / -2;
  805. setStylePx(pt, WIDTH, size);
  806. setStylePx(pt, HEIGHT, size);
  807. setStylePx(pt, "marginLeft", mar);
  808. setStylePx(pt, "marginTop", mar);
  809. return pt;
  810. }
  811. function cursorPointFill(self, si) {
  812. let s = self.series[si];
  813. return s.stroke(self, si);
  814. }
  815. function cursorPointStroke(self, si) {
  816. let s = self.series[si];
  817. return s.stroke(self, si);
  818. }
  819. function cursorPointSize(self, si) {
  820. let s = self.series[si];
  821. return ptDia(s.width, 1);
  822. }
  823. function dataIdx(self, seriesIdx, cursorIdx) {
  824. return cursorIdx;
  825. }
  826. const moveTuple = [0,0];
  827. function cursorMove(self, mouseLeft1, mouseTop1) {
  828. moveTuple[0] = mouseLeft1;
  829. moveTuple[1] = mouseTop1;
  830. return moveTuple;
  831. }
  832. function filtBtn0(self, targ, handle) {
  833. return e => {
  834. e.button == 0 && handle(e);
  835. };
  836. }
  837. function passThru(self, targ, handle) {
  838. return handle;
  839. }
  840. const cursorOpts = {
  841. show: true,
  842. x: true,
  843. y: true,
  844. lock: false,
  845. move: cursorMove,
  846. points: {
  847. show: cursorPointShow,
  848. size: cursorPointSize,
  849. width: 0,
  850. stroke: cursorPointStroke,
  851. fill: cursorPointFill,
  852. },
  853. bind: {
  854. mousedown: filtBtn0,
  855. mouseup: filtBtn0,
  856. click: filtBtn0,
  857. dblclick: filtBtn0,
  858. mousemove: passThru,
  859. mouseleave: passThru,
  860. mouseenter: passThru,
  861. },
  862. drag: {
  863. setScale: true,
  864. x: true,
  865. y: false,
  866. dist: 0,
  867. uni: null,
  868. _x: false,
  869. _y: false,
  870. },
  871. focus: {
  872. prox: -1,
  873. },
  874. left: -10,
  875. top: -10,
  876. idx: null,
  877. dataIdx,
  878. };
  879. const grid = {
  880. show: true,
  881. stroke: "rgba(0,0,0,0.07)",
  882. width: 2,
  883. // dash: [],
  884. filter: retArg1,
  885. };
  886. const ticks = assign({}, grid, {size: 10});
  887. const font = '12px system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"';
  888. const labelFont = "bold " + font;
  889. const lineMult = 1.5; // font-size multiplier
  890. const xAxisOpts = {
  891. show: true,
  892. scale: "x",
  893. space: 50,
  894. gap: 5,
  895. size: 50,
  896. labelSize: 30,
  897. labelFont,
  898. side: 2,
  899. // class: "x-vals",
  900. // incrs: timeIncrs,
  901. // values: timeVals,
  902. // filter: retArg1,
  903. grid,
  904. ticks,
  905. font,
  906. rotate: 0,
  907. };
  908. const numSeriesLabel = "Value";
  909. const timeSeriesLabel = "Time";
  910. const xSeriesOpts = {
  911. show: true,
  912. scale: "x",
  913. auto: false,
  914. sorted: 1,
  915. // label: "Time",
  916. // value: v => stamp(new Date(v * 1e3)),
  917. // internal caches
  918. min: inf,
  919. max: -inf,
  920. idxs: [],
  921. };
  922. function numAxisVals(self, splits, axisIdx, foundSpace, foundIncr) {
  923. return splits.map(v => v == null ? "" : fmtNum(v));
  924. }
  925. function numAxisSplits(self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace, forceMin) {
  926. let splits = [];
  927. let numDec = fixedDec.get(foundIncr) || 0;
  928. scaleMin = forceMin ? scaleMin : roundDec(incrRoundUp(scaleMin, foundIncr), numDec);
  929. for (let val = scaleMin; val <= scaleMax; val = roundDec(val + foundIncr, numDec))
  930. splits.push(Object.is(val, -0) ? 0 : val); // coalesces -0
  931. return splits;
  932. }
  933. function logAxisSplits(self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace, forceMin) {
  934. const splits = [];
  935. const logBase = self.scales[self.axes[axisIdx].scale].log;
  936. const logFn = logBase == 10 ? log10 : log2;
  937. const exp = floor(logFn(scaleMin));
  938. foundIncr = pow(logBase, exp);
  939. if (exp < 0)
  940. foundIncr = roundDec(foundIncr, -exp);
  941. let split = scaleMin;
  942. do {
  943. splits.push(split);
  944. split = roundDec(split + foundIncr, fixedDec.get(foundIncr));
  945. if (split >= foundIncr * logBase)
  946. foundIncr = split;
  947. } while (split <= scaleMax);
  948. return splits;
  949. }
  950. const RE_ALL = /./;
  951. const RE_12357 = /[12357]/;
  952. const RE_125 = /[125]/;
  953. const RE_1 = /1/;
  954. function logAxisValsFilt(self, splits, axisIdx, foundSpace, foundIncr) {
  955. let axis = self.axes[axisIdx];
  956. let scaleKey = axis.scale;
  957. if (self.scales[scaleKey].log == 2)
  958. return splits;
  959. let valToPos = self.valToPos;
  960. let minSpace = axis._space;
  961. let _10 = valToPos(10, scaleKey);
  962. let re = (
  963. valToPos(9, scaleKey) - _10 >= minSpace ? RE_ALL :
  964. valToPos(7, scaleKey) - _10 >= minSpace ? RE_12357 :
  965. valToPos(5, scaleKey) - _10 >= minSpace ? RE_125 :
  966. RE_1
  967. );
  968. return splits.map(v => re.test(v) ? v : null);
  969. }
  970. function numSeriesVal(self, val) {
  971. return val == null ? "" : fmtNum(val);
  972. }
  973. const yAxisOpts = {
  974. show: true,
  975. scale: "y",
  976. space: 30,
  977. gap: 5,
  978. size: 50,
  979. labelSize: 30,
  980. labelFont,
  981. side: 3,
  982. // class: "y-vals",
  983. // incrs: numIncrs,
  984. // values: (vals, space) => vals,
  985. // filter: retArg1,
  986. grid,
  987. ticks,
  988. font,
  989. rotate: 0,
  990. };
  991. // takes stroke width
  992. function ptDia(width, mult) {
  993. let dia = 3 + (width || 1) * 2;
  994. return roundDec(dia * mult, 3);
  995. }
  996. function seriesPoints(self, si) {
  997. const xsc = self.scales[self.series[0].scale];
  998. const dim = xsc.ori == 0 ? self.bbox.width : self.bbox.height;
  999. const s = self.series[si];
  1000. // const dia = ptDia(s.width, pxRatio);
  1001. let maxPts = dim / (s.points.space * pxRatio);
  1002. let idxs = self.series[0].idxs;
  1003. return idxs[1] - idxs[0] <= maxPts;
  1004. }
  1005. function seriesFillTo(self, seriesIdx, dataMin, dataMax) {
  1006. let scale = self.scales[self.series[seriesIdx].scale];
  1007. return scale.distr == 3 ? scale.min : 0;
  1008. }
  1009. const ySeriesOpts = {
  1010. scale: "y",
  1011. auto: true,
  1012. sorted: 0,
  1013. show: true,
  1014. band: false,
  1015. spanGaps: false,
  1016. isGap: (self, seriesIdx, dataIdx) => true,
  1017. alpha: 1,
  1018. points: {
  1019. show: seriesPoints,
  1020. // stroke: "#000",
  1021. // fill: "#fff",
  1022. // width: 1,
  1023. // size: 10,
  1024. },
  1025. // label: "Value",
  1026. // value: v => v,
  1027. values: null,
  1028. // internal caches
  1029. min: inf,
  1030. max: -inf,
  1031. idxs: [],
  1032. path: null,
  1033. clip: null,
  1034. };
  1035. function clampScale(self, val, scaleMin, scaleMax, scaleKey) {
  1036. /*
  1037. if (val < 0) {
  1038. let cssHgt = self.bbox.height / pxRatio;
  1039. let absPos = self.valToPos(abs(val), scaleKey);
  1040. let fromBtm = cssHgt - absPos;
  1041. return self.posToVal(cssHgt + fromBtm, scaleKey);
  1042. }
  1043. */
  1044. return scaleMin / 10;
  1045. }
  1046. const xScaleOpts = {
  1047. time: FEAT_TIME,
  1048. auto: true,
  1049. distr: 1,
  1050. log: 10,
  1051. min: null,
  1052. max: null,
  1053. dir: 1,
  1054. ori: 0,
  1055. };
  1056. const yScaleOpts = assign({}, xScaleOpts, {
  1057. time: false,
  1058. ori: 1,
  1059. });
  1060. const syncs = {};
  1061. function _sync(opts) {
  1062. let clients = [];
  1063. return {
  1064. sub(client) {
  1065. clients.push(client);
  1066. },
  1067. unsub(client) {
  1068. clients = clients.filter(c => c != client);
  1069. },
  1070. pub(type, self, x, y, w, h, i) {
  1071. if (clients.length > 1) {
  1072. clients.forEach(client => {
  1073. client != self && client.pub(type, self, x, y, w, h, i);
  1074. });
  1075. }
  1076. }
  1077. };
  1078. }
  1079. function orient(u, seriesIdx, cb) {
  1080. const series = u.series[seriesIdx];
  1081. const scales = u.scales;
  1082. const bbox = u.bbox;
  1083. const scaleX = scales[u.series[0].scale];
  1084. let dx = u._data[0],
  1085. dy = u._data[seriesIdx],
  1086. sx = scaleX,
  1087. sy = scales[series.scale],
  1088. l = bbox.left,
  1089. t = bbox.top,
  1090. w = bbox.width,
  1091. h = bbox.height,
  1092. H = u.valToPosH,
  1093. V = u.valToPosV;
  1094. return (sx.ori == 0
  1095. ? cb(
  1096. series,
  1097. dx,
  1098. dy,
  1099. sx,
  1100. sy,
  1101. H,
  1102. V,
  1103. l,
  1104. t,
  1105. w,
  1106. h,
  1107. moveToH,
  1108. lineToH,
  1109. rectH,
  1110. arcH,
  1111. bezierCurveToH,
  1112. )
  1113. : cb(
  1114. series,
  1115. dx,
  1116. dy,
  1117. sx,
  1118. sy,
  1119. V,
  1120. H,
  1121. t,
  1122. l,
  1123. h,
  1124. w,
  1125. moveToV,
  1126. lineToV,
  1127. rectV,
  1128. arcV,
  1129. bezierCurveToV,
  1130. )
  1131. );
  1132. }
  1133. // creates inverted band clip path (towards from stroke path -> yMax)
  1134. function clipBandLine(self, seriesIdx, idx0, idx1, strokePath) {
  1135. return orient(self, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
  1136. const dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
  1137. const lineTo = scaleX.ori == 0 ? lineToH : lineToV;
  1138. let frIdx, toIdx;
  1139. if (dir == 1) {
  1140. frIdx = idx0;
  1141. toIdx = idx1;
  1142. }
  1143. else {
  1144. frIdx = idx1;
  1145. toIdx = idx0;
  1146. }
  1147. // path start
  1148. let x0 = incrRound(valToPosX(dataX[frIdx], scaleX, xDim, xOff), 0.5);
  1149. let y0 = incrRound(valToPosY(dataY[frIdx], scaleY, yDim, yOff), 0.5);
  1150. // path end x
  1151. let x1 = incrRound(valToPosX(dataX[toIdx], scaleX, xDim, xOff), 0.5);
  1152. // upper y limit
  1153. let yLimit = incrRound(valToPosY(scaleY.max, scaleY, yDim, yOff), 0.5);
  1154. let clip = new Path2D(strokePath);
  1155. lineTo(clip, x1, yLimit);
  1156. lineTo(clip, x0, yLimit);
  1157. lineTo(clip, x0, y0);
  1158. return clip;
  1159. });
  1160. }
  1161. function clipGaps(gaps, ori, plotLft, plotTop, plotWid, plotHgt) {
  1162. let clip = null;
  1163. // create clip path (invert gaps and non-gaps)
  1164. if (gaps.length > 0) {
  1165. clip = new Path2D();
  1166. const rect = ori == 0 ? rectH : rectV;
  1167. let prevGapEnd = plotLft;
  1168. for (let i = 0; i < gaps.length; i++) {
  1169. let g = gaps[i];
  1170. rect(clip, prevGapEnd, plotTop, g[0] - prevGapEnd, plotTop + plotHgt);
  1171. prevGapEnd = g[1];
  1172. }
  1173. rect(clip, prevGapEnd, plotTop, plotLft + plotWid - prevGapEnd, plotTop + plotHgt);
  1174. }
  1175. return clip;
  1176. }
  1177. function addGap(gaps, fromX, toX) {
  1178. if (toX > fromX) {
  1179. let prevGap = gaps[gaps.length - 1];
  1180. if (prevGap && prevGap[0] == fromX) // TODO: gaps must be encoded at stroke widths?
  1181. prevGap[1] = toX;
  1182. else
  1183. gaps.push([fromX, toX]);
  1184. }
  1185. }
  1186. // orientation-inverting canvas functions
  1187. function moveToH(p, x, y) { p.moveTo(x, y); }
  1188. function moveToV(p, y, x) { p.moveTo(x, y); }
  1189. function lineToH(p, x, y) { p.lineTo(x, y); }
  1190. function lineToV(p, y, x) { p.lineTo(x, y); }
  1191. function rectH(p, x, y, w, h) { p.rect(x, y, w, h); }
  1192. function rectV(p, y, x, h, w) { p.rect(x, y, w, h); }
  1193. function arcH(p, x, y, r, startAngle, endAngle) { p.arc(x, y, r, startAngle, endAngle); }
  1194. function arcV(p, y, x, r, startAngle, endAngle) { p.arc(x, y, r, startAngle, endAngle); }
  1195. function bezierCurveToH(p, bp1x, bp1y, bp2x, bp2y, p2x, p2y) { p.bezierCurveTo(bp1x, bp1y, bp2x, bp2y, p2x, p2y); }function bezierCurveToV(p, bp1y, bp1x, bp2y, bp2x, p2y, p2x) { p.bezierCurveTo(bp1x, bp1y, bp2x, bp2y, p2x, p2y); }
  1196. function _drawAcc(lineTo) {
  1197. return (stroke, accX, minY, maxY, outY) => {
  1198. lineTo(stroke, accX, minY);
  1199. lineTo(stroke, accX, maxY);
  1200. lineTo(stroke, accX, outY);
  1201. };
  1202. }
  1203. const drawAccH = _drawAcc(lineToH);
  1204. const drawAccV = _drawAcc(lineToV);
  1205. function linear() {
  1206. return (u, seriesIdx, idx0, idx1) => {
  1207. return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
  1208. let lineTo, drawAcc;
  1209. if (scaleX.ori == 0) {
  1210. lineTo = lineToH;
  1211. drawAcc = drawAccH;
  1212. }
  1213. else {
  1214. lineTo = lineToV;
  1215. drawAcc = drawAccV;
  1216. }
  1217. const isGap = series.isGap;
  1218. const dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
  1219. const _paths = {stroke: new Path2D(), fill: null, clip: null, band: null};
  1220. const stroke = _paths.stroke;
  1221. let minY = inf,
  1222. maxY = -inf,
  1223. outY, outX, drawnAtX;
  1224. let gaps = [];
  1225. let accX = round(valToPosX(dataX[dir == 1 ? idx0 : idx1], scaleX, xDim, xOff));
  1226. let accGaps = false;
  1227. // data edges
  1228. let lftIdx = nonNullIdx(dataY, idx0, idx1, 1 * dir);
  1229. let rgtIdx = nonNullIdx(dataY, idx0, idx1, -1 * dir);
  1230. let lftX = incrRound(valToPosX(dataX[lftIdx], scaleX, xDim, xOff), 0.5);
  1231. let rgtX = incrRound(valToPosX(dataX[rgtIdx], scaleX, xDim, xOff), 0.5);
  1232. if (lftX > xOff)
  1233. addGap(gaps, xOff, lftX);
  1234. for (let i = dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += dir) {
  1235. let x = round(valToPosX(dataX[i], scaleX, xDim, xOff));
  1236. if (x == accX) {
  1237. if (dataY[i] != null) {
  1238. outY = round(valToPosY(dataY[i], scaleY, yDim, yOff));
  1239. if (minY == inf)
  1240. lineTo(stroke, x, outY);
  1241. minY = min(outY, minY);
  1242. maxY = max(outY, maxY);
  1243. }
  1244. else if (!accGaps && isGap(u, seriesIdx, i))
  1245. accGaps = true;
  1246. }
  1247. else {
  1248. let _addGap = false;
  1249. if (minY != inf) {
  1250. drawAcc(stroke, accX, minY, maxY, outY);
  1251. outX = drawnAtX = accX;
  1252. }
  1253. else if (accGaps) {
  1254. _addGap = true;
  1255. accGaps = false;
  1256. }
  1257. if (dataY[i] != null) {
  1258. outY = round(valToPosY(dataY[i], scaleY, yDim, yOff));
  1259. lineTo(stroke, x, outY);
  1260. minY = maxY = outY;
  1261. // prior pixel can have data but still start a gap if ends with null
  1262. if (x - accX > 1 && dataY[i - dir] == null && isGap(u, seriesIdx, i - dir))
  1263. _addGap = true;
  1264. }
  1265. else {
  1266. minY = inf;
  1267. maxY = -inf;
  1268. if (!accGaps && isGap(u, seriesIdx, i))
  1269. accGaps = true;
  1270. }
  1271. _addGap && addGap(gaps, outX, x);
  1272. accX = x;
  1273. }
  1274. }
  1275. if (minY != inf && minY != maxY && drawnAtX != accX)
  1276. drawAcc(stroke, accX, minY, maxY, outY);
  1277. if (rgtX < xOff + xDim)
  1278. addGap(gaps, rgtX, xOff + xDim);
  1279. if (series.fill != null) {
  1280. let fill = _paths.fill = new Path2D(stroke);
  1281. let fillTo = round(valToPosY(series.fillTo(u, seriesIdx, series.min, series.max), scaleY, yDim, yOff));
  1282. lineTo(fill, rgtX, fillTo);
  1283. lineTo(fill, lftX, fillTo);
  1284. }
  1285. if (!series.spanGaps)
  1286. _paths.clip = clipGaps(gaps, scaleX.ori, xOff, yOff, xDim, yDim);
  1287. if (u.bands.length > 0) {
  1288. // ADDL OPT: only create band clips for series that are band lower edges
  1289. // if (b.series[1] == i && _paths.band == null)
  1290. _paths.band = clipBandLine(u, seriesIdx, idx0, idx1, stroke);
  1291. }
  1292. return _paths;
  1293. });
  1294. };
  1295. }
  1296. function spline(opts) {
  1297. return (u, seriesIdx, idx0, idx1) => {
  1298. return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
  1299. let moveTo, bezierCurveTo, lineTo;
  1300. if (scaleX.ori == 0) {
  1301. moveTo = moveToH;
  1302. lineTo = lineToH;
  1303. bezierCurveTo = bezierCurveToH;
  1304. }
  1305. else {
  1306. moveTo = moveToV;
  1307. lineTo = lineToV;
  1308. bezierCurveTo = bezierCurveToV;
  1309. }
  1310. const _dir = 1 * scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
  1311. idx0 = nonNullIdx(dataY, idx0, idx1, 1);
  1312. idx1 = nonNullIdx(dataY, idx0, idx1, -1);
  1313. let gaps = [];
  1314. let inGap = false;
  1315. let firstXPos = round(valToPosX(dataX[_dir == 1 ? idx0 : idx1], scaleX, xDim, xOff));
  1316. let prevXPos = firstXPos;
  1317. let xCoords = [];
  1318. let yCoords = [];
  1319. for (let i = _dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += _dir) {
  1320. let yVal = dataY[i];
  1321. let xVal = dataX[i];
  1322. let xPos = valToPosX(xVal, scaleX, xDim, xOff);
  1323. if (yVal == null) {
  1324. if (series.isGap(u, seriesIdx, i)) {
  1325. addGap(gaps, prevXPos, xPos);
  1326. inGap = true;
  1327. }
  1328. continue;
  1329. }
  1330. else {
  1331. if (inGap) {
  1332. addGap(gaps, prevXPos, xPos);
  1333. inGap = false;
  1334. }
  1335. xCoords.push((prevXPos = xPos));
  1336. yCoords.push(valToPosY(dataY[i], scaleY, yDim, yOff));
  1337. }
  1338. }
  1339. const _paths = {stroke: catmullRomFitting(xCoords, yCoords, 0.5, moveTo, bezierCurveTo), fill: null, clip: null, band: null};
  1340. const stroke = _paths.stroke;
  1341. if (series.fill != null) {
  1342. let fill = _paths.fill = new Path2D(stroke);
  1343. let fillTo = series.fillTo(u, seriesIdx, series.min, series.max);
  1344. let minY = round(valToPosY(fillTo, scaleY, yDim, yOff));
  1345. lineTo(fill, prevXPos, minY);
  1346. lineTo(fill, firstXPos, minY);
  1347. }
  1348. if (!series.spanGaps)
  1349. _paths.clip = clipGaps(gaps, scaleX.ori, xOff, yOff, xDim, yDim);
  1350. if (u.bands.length > 0) {
  1351. // ADDL OPT: only create band clips for series that are band lower edges
  1352. // if (b.series[1] == i && _paths.band == null)
  1353. _paths.band = clipBandLine(u, seriesIdx, idx0, idx1, stroke);
  1354. }
  1355. return _paths;
  1356. // if FEAT_PATHS: false in rollup.config.js
  1357. // u.ctx.save();
  1358. // u.ctx.beginPath();
  1359. // u.ctx.rect(u.bbox.left, u.bbox.top, u.bbox.width, u.bbox.height);
  1360. // u.ctx.clip();
  1361. // u.ctx.strokeStyle = u.series[sidx].stroke;
  1362. // u.ctx.stroke(stroke);
  1363. // u.ctx.fillStyle = u.series[sidx].fill;
  1364. // u.ctx.fill(fill);
  1365. // u.ctx.restore();
  1366. // return null;
  1367. });
  1368. };
  1369. }
  1370. // adapted from https://gist.github.com/nicholaswmin/c2661eb11cad5671d816 (MIT)
  1371. /**
  1372. * Interpolates a Catmull-Rom Spline through a series of x/y points
  1373. * Converts the CR Spline to Cubic Beziers for use with SVG items
  1374. *
  1375. * If 'alpha' is 0.5 then the 'Centripetal' variant is used
  1376. * If 'alpha' is 1 then the 'Chordal' variant is used
  1377. *
  1378. */
  1379. function catmullRomFitting(xCoords, yCoords, alpha, moveTo, bezierCurveTo) {
  1380. const path = new Path2D();
  1381. const dataLen = xCoords.length;
  1382. let p0x,
  1383. p0y,
  1384. p1x,
  1385. p1y,
  1386. p2x,
  1387. p2y,
  1388. p3x,
  1389. p3y,
  1390. bp1x,
  1391. bp1y,
  1392. bp2x,
  1393. bp2y,
  1394. d1,
  1395. d2,
  1396. d3,
  1397. A,
  1398. B,
  1399. N,
  1400. M,
  1401. d3powA,
  1402. d2powA,
  1403. d3pow2A,
  1404. d2pow2A,
  1405. d1pow2A,
  1406. d1powA;
  1407. moveTo(path, round(xCoords[0]), round(yCoords[0]));
  1408. for (let i = 0; i < dataLen - 1; i++) {
  1409. let p0i = i == 0 ? 0 : i - 1;
  1410. p0x = xCoords[p0i];
  1411. p0y = yCoords[p0i];
  1412. p1x = xCoords[i];
  1413. p1y = yCoords[i];
  1414. p2x = xCoords[i + 1];
  1415. p2y = yCoords[i + 1];
  1416. if (i + 2 < dataLen) {
  1417. p3x = xCoords[i + 2];
  1418. p3y = yCoords[i + 2];
  1419. } else {
  1420. p3x = p2x;
  1421. p3y = p2y;
  1422. }
  1423. d1 = sqrt(pow(p0x - p1x, 2) + pow(p0y - p1y, 2));
  1424. d2 = sqrt(pow(p1x - p2x, 2) + pow(p1y - p2y, 2));
  1425. d3 = sqrt(pow(p2x - p3x, 2) + pow(p2y - p3y, 2));
  1426. // Catmull-Rom to Cubic Bezier conversion matrix
  1427. // A = 2d1^2a + 3d1^a * d2^a + d3^2a
  1428. // B = 2d3^2a + 3d3^a * d2^a + d2^2a
  1429. // [ 0 1 0 0 ]
  1430. // [ -d2^2a /N A/N d1^2a /N 0 ]
  1431. // [ 0 d3^2a /M B/M -d2^2a /M ]
  1432. // [ 0 0 1 0 ]
  1433. d3powA = pow(d3, alpha);
  1434. d3pow2A = pow(d3, alpha * 2);
  1435. d2powA = pow(d2, alpha);
  1436. d2pow2A = pow(d2, alpha * 2);
  1437. d1powA = pow(d1, alpha);
  1438. d1pow2A = pow(d1, alpha * 2);
  1439. A = 2 * d1pow2A + 3 * d1powA * d2powA + d2pow2A;
  1440. B = 2 * d3pow2A + 3 * d3powA * d2powA + d2pow2A;
  1441. N = 3 * d1powA * (d1powA + d2powA);
  1442. if (N > 0)
  1443. N = 1 / N;
  1444. M = 3 * d3powA * (d3powA + d2powA);
  1445. if (M > 0)
  1446. M = 1 / M;
  1447. bp1x = (-d2pow2A * p0x + A * p1x + d1pow2A * p2x) * N;
  1448. bp1y = (-d2pow2A * p0y + A * p1y + d1pow2A * p2y) * N;
  1449. bp2x = (d3pow2A * p1x + B * p2x - d2pow2A * p3x) * M;
  1450. bp2y = (d3pow2A * p1y + B * p2y - d2pow2A * p3y) * M;
  1451. if (bp1x == 0 && bp1y == 0) {
  1452. bp1x = p1x;
  1453. bp1y = p1y;
  1454. }
  1455. if (bp2x == 0 && bp2y == 0) {
  1456. bp2x = p2x;
  1457. bp2y = p2y;
  1458. }
  1459. bezierCurveTo(path, bp1x, bp1y, bp2x, bp2y, p2x, p2y);
  1460. }
  1461. return path;
  1462. }
  1463. function stepped(opts) {
  1464. const align = ifNull(opts.align, 1);
  1465. return (u, seriesIdx, idx0, idx1) => {
  1466. return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
  1467. let lineTo = scaleX.ori == 0 ? lineToH : lineToV;
  1468. const _paths = {stroke: new Path2D(), fill: null, clip: null, band: null};
  1469. const stroke = _paths.stroke;
  1470. const _dir = 1 * scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
  1471. idx0 = nonNullIdx(dataY, idx0, idx1, 1);
  1472. idx1 = nonNullIdx(dataY, idx0, idx1, -1);
  1473. let gaps = [];
  1474. let inGap = false;
  1475. let prevYPos = round(valToPosY(dataY[_dir == 1 ? idx0 : idx1], scaleY, yDim, yOff));
  1476. let firstXPos = round(valToPosX(dataX[_dir == 1 ? idx0 : idx1], scaleX, xDim, xOff));
  1477. let prevXPos = firstXPos;
  1478. lineTo(stroke, firstXPos, prevYPos);
  1479. for (let i = _dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += _dir) {
  1480. let yVal1 = dataY[i];
  1481. let x1 = round(valToPosX(dataX[i], scaleX, xDim, xOff));
  1482. if (yVal1 == null) {
  1483. if (series.isGap(u, seriesIdx, i)) {
  1484. addGap(gaps, prevXPos, x1);
  1485. inGap = true;
  1486. }
  1487. continue;
  1488. }
  1489. let y1 = round(valToPosY(yVal1, scaleY, yDim, yOff));
  1490. if (inGap) {
  1491. addGap(gaps, prevXPos, x1);
  1492. // don't clip vertical extenders
  1493. if (prevYPos != y1) {
  1494. let halfStroke = (series.width * pxRatio) / 2;
  1495. let lastGap = gaps[gaps.length - 1];
  1496. lastGap[0] += halfStroke;
  1497. lastGap[1] -= halfStroke;
  1498. }
  1499. inGap = false;
  1500. }
  1501. if (align == 1)
  1502. lineTo(stroke, x1, prevYPos);
  1503. else
  1504. lineTo(stroke, prevXPos, y1);
  1505. lineTo(stroke, x1, y1);
  1506. prevYPos = y1;
  1507. prevXPos = x1;
  1508. }
  1509. if (series.fill != null) {
  1510. let fill = _paths.fill = new Path2D(stroke);
  1511. let fillTo = series.fillTo(u, seriesIdx, series.min, series.max);
  1512. let minY = round(valToPosY(fillTo, scaleY, yDim, yOff));
  1513. lineTo(fill, prevXPos, minY);
  1514. lineTo(fill, firstXPos, minY);
  1515. }
  1516. if (!series.spanGaps)
  1517. _paths.clip = clipGaps(gaps, scaleX.ori, xOff, yOff, xDim, yDim);
  1518. if (u.bands.length > 0) {
  1519. // ADDL OPT: only create band clips for series that are band lower edges
  1520. // if (b.series[1] == i && _paths.band == null)
  1521. _paths.band = clipBandLine(u, seriesIdx, idx0, idx1, stroke);
  1522. }
  1523. return _paths;
  1524. });
  1525. };
  1526. }
  1527. function bars(opts) {
  1528. opts = opts || EMPTY_OBJ;
  1529. const size = ifNull(opts.size, [0.6, inf]);
  1530. const align = opts.align || 0;
  1531. const gapFactor = 1 - size[0];
  1532. const maxWidth = ifNull(size[1], inf) * pxRatio;
  1533. return (u, seriesIdx, idx0, idx1) => {
  1534. return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
  1535. let rect = scaleX.ori == 0 ? rectH : rectV;
  1536. let colWid = valToPosX(dataX[1], scaleX, xDim, xOff) - valToPosX(dataX[0], scaleX, xDim, xOff);
  1537. let gapWid = colWid * gapFactor;
  1538. let fillToY = series.fillTo(u, seriesIdx, series.min, series.max);
  1539. let y0Pos = valToPosY(fillToY, scaleY, yDim, yOff);
  1540. let strokeWidth = round(series.width * pxRatio);
  1541. let barWid = round(min(maxWidth, colWid - gapWid) - strokeWidth);
  1542. let xShift = align == 1 ? 0 : align == -1 ? barWid : barWid / 2;
  1543. const _paths = {stroke: new Path2D(), fill: null, clip: null, band: null};
  1544. const hasBands = u.bands.length > 0;
  1545. let yLimit;
  1546. if (hasBands) {
  1547. // ADDL OPT: only create band clips for series that are band lower edges
  1548. // if (b.series[1] == i && _paths.band == null)
  1549. _paths.band = new Path2D();
  1550. yLimit = incrRound(valToPosY(scaleY.max, scaleY, yDim, yOff), 0.5);
  1551. }
  1552. const stroke = _paths.stroke;
  1553. const band = _paths.band;
  1554. const _dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
  1555. for (let i = _dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += _dir) {
  1556. let yVal = dataY[i];
  1557. // interpolate upwards band clips
  1558. if (yVal == null) {
  1559. if (hasBands) {
  1560. // simple, but inefficient bi-directinal linear scans on each iteration
  1561. let prevNonNull = nonNullIdx(dataY, _dir == 1 ? idx0 : idx1, i, -_dir);
  1562. let nextNonNull = nonNullIdx(dataY, i, _dir == 1 ? idx1 : idx0, _dir);
  1563. let prevVal = dataY[prevNonNull];
  1564. let nextVal = dataY[nextNonNull];
  1565. yVal = prevVal + (i - prevNonNull) / (nextNonNull - prevNonNull) * (nextVal - prevVal);
  1566. }
  1567. else
  1568. continue;
  1569. }
  1570. let xVal = scaleX.distr == 2 ? i : dataX[i];
  1571. // TODO: all xPos can be pre-computed once for all series in aligned set
  1572. let xPos = valToPosX(xVal, scaleX, xDim, xOff);
  1573. let yPos = valToPosY(yVal, scaleY, yDim, yOff);
  1574. let lft = round(xPos - xShift);
  1575. let btm = round(max(yPos, y0Pos));
  1576. let top = round(min(yPos, y0Pos));
  1577. let barHgt = btm - top;
  1578. dataY[i] != null && rect(stroke, lft, top, barWid, barHgt);
  1579. if (hasBands) {
  1580. btm = top;
  1581. top = yLimit;
  1582. barHgt = btm - top;
  1583. rect(band, lft, top, barWid, barHgt);
  1584. }
  1585. }
  1586. if (series.fill != null)
  1587. _paths.fill = new Path2D(stroke);
  1588. return _paths;
  1589. });
  1590. };
  1591. }
  1592. const linearPath = linear() ;
  1593. function setDefaults(d, xo, yo, initY) {
  1594. let d2 = initY ? [d[0], d[1]].concat(d.slice(2)) : [d[0]].concat(d.slice(1));
  1595. return d2.map((o, i) => setDefault(o, i, xo, yo));
  1596. }
  1597. function setDefault(o, i, xo, yo) {
  1598. return assign({}, (i == 0 ? xo : yo), o);
  1599. }
  1600. const nullMinMax = [null, null];
  1601. function snapNumX(self, dataMin, dataMax) {
  1602. return dataMin == null ? nullMinMax : [dataMin, dataMax];
  1603. }
  1604. const snapTimeX = snapNumX;
  1605. // this ensures that non-temporal/numeric y-axes get multiple-snapped padding added above/below
  1606. // TODO: also account for incrs when snapping to ensure top of axis gets a tick & value
  1607. function snapNumY(self, dataMin, dataMax) {
  1608. return dataMin == null ? nullMinMax : rangeNum(dataMin, dataMax, 0.1, true);
  1609. }
  1610. function snapLogY(self, dataMin, dataMax, scale) {
  1611. return dataMin == null ? nullMinMax : rangeLog(dataMin, dataMax, self.scales[scale].log, false);
  1612. }
  1613. const snapLogX = snapLogY;
  1614. // dim is logical (getClientBoundingRect) pixels, not canvas pixels
  1615. function findIncr(min, max, incrs, dim, minSpace) {
  1616. let pxPerUnit = dim / (max - min);
  1617. let minDec = (""+floor(min)).length;
  1618. for (var i = 0; i < incrs.length; i++) {
  1619. let space = incrs[i] * pxPerUnit;
  1620. let incrDec = incrs[i] < 10 ? fixedDec.get(incrs[i]) : 0;
  1621. if (space >= minSpace && minDec + incrDec < 17)
  1622. return [incrs[i], space];
  1623. }
  1624. return [0, 0];
  1625. }
  1626. function pxRatioFont(font) {
  1627. let fontSize;
  1628. font = font.replace(/(\d+)px/, (m, p1) => (fontSize = round(p1 * pxRatio)) + 'px');
  1629. return [font, fontSize];
  1630. }
  1631. function uPlot(opts, data, then) {
  1632. const self = {};
  1633. function getValPct(val, scale) {
  1634. return (
  1635. scale.distr == 3
  1636. ? log10((val > 0 ? val : scale.clamp(self, val, scale.min, scale.max, scale.key)) / scale.min) / log10(scale.max / scale.min)
  1637. : (val - scale.min) / (scale.max - scale.min)
  1638. );
  1639. }
  1640. function getHPos(val, scale, dim, off) {
  1641. let pct = getValPct(val, scale);
  1642. return off + dim * (scale.dir == -1 ? (1 - pct) : pct);
  1643. }
  1644. function getVPos(val, scale, dim, off) {
  1645. let pct = getValPct(val, scale);
  1646. return off + dim * (scale.dir == -1 ? pct : (1 - pct));
  1647. }
  1648. function getPos(val, scale, dim, off) {
  1649. return scale.ori == 0 ? getHPos(val, scale, dim, off) : getVPos(val, scale, dim, off);
  1650. }
  1651. self.valToPosH = getHPos;
  1652. self.valToPosV = getVPos;
  1653. let ready = false;
  1654. self.status = 0;
  1655. const root = self.root = placeDiv(UPLOT);
  1656. if (opts.id != null)
  1657. root.id = opts.id;
  1658. addClass(root, opts.class);
  1659. if (opts.title) {
  1660. let title = placeDiv(TITLE, root);
  1661. title.textContent = opts.title;
  1662. }
  1663. const can = placeTag("canvas");
  1664. const ctx = self.ctx = can.getContext("2d");
  1665. const wrap = placeDiv(WRAP, root);
  1666. const under = placeDiv(UNDER, wrap);
  1667. wrap.appendChild(can);
  1668. const over = placeDiv(OVER, wrap);
  1669. opts = copy(opts);
  1670. (opts.plugins || []).forEach(p => {
  1671. if (p.opts)
  1672. opts = p.opts(self, opts) || opts;
  1673. });
  1674. const ms = opts.ms || 1e-3;
  1675. const series = self.series = setDefaults(opts.series || [], xSeriesOpts, ySeriesOpts, false);
  1676. const axes = self.axes = setDefaults(opts.axes || [], xAxisOpts, yAxisOpts, true);
  1677. const scales = self.scales = {};
  1678. const bands = self.bands = opts.bands || [];
  1679. bands.forEach(b => {
  1680. b.fill = fnOrSelf(b.fill || null);
  1681. });
  1682. const xScaleKey = series[0].scale;
  1683. const drawOrderMap = {
  1684. axes: drawAxesGrid,
  1685. series: drawSeries,
  1686. };
  1687. const drawOrder = (opts.drawOrder || ["axes", "series"]).map(key => drawOrderMap[key]);
  1688. function initScale(scaleKey) {
  1689. let sc = scales[scaleKey];
  1690. if (sc == null) {
  1691. let scaleOpts = (opts.scales || EMPTY_OBJ)[scaleKey] || EMPTY_OBJ;
  1692. if (scaleOpts.from != null) {
  1693. // ensure parent is initialized
  1694. initScale(scaleOpts.from);
  1695. // dependent scales inherit
  1696. scales[scaleKey] = assign({}, scales[scaleOpts.from], scaleOpts);
  1697. }
  1698. else {
  1699. sc = scales[scaleKey] = assign({}, (scaleKey == xScaleKey ? xScaleOpts : yScaleOpts), scaleOpts);
  1700. sc.key = scaleKey;
  1701. let isTime = sc.time;
  1702. let isLog = sc.distr == 3;
  1703. let rn = sc.range;
  1704. if (scaleKey != xScaleKey && !isArr(rn) && isObj(rn)) {
  1705. let cfg = rn;
  1706. // this is similar to snapNumY
  1707. rn = (self, dataMin, dataMax) => dataMin == null ? nullMinMax : rangeNum(dataMin, dataMax, cfg);
  1708. }
  1709. sc.range = fnOrSelf(rn || (isTime ? snapTimeX : scaleKey == xScaleKey ? (isLog ? snapLogX : snapNumX) : (isLog ? snapLogY : snapNumY)));
  1710. sc.auto = fnOrSelf(sc.auto);
  1711. sc.clamp = fnOrSelf(sc.clamp || clampScale);
  1712. }
  1713. }
  1714. }
  1715. initScale("x");
  1716. initScale("y");
  1717. series.forEach(s => {
  1718. initScale(s.scale);
  1719. });
  1720. axes.forEach(a => {
  1721. initScale(a.scale);
  1722. });
  1723. for (let k in opts.scales)
  1724. initScale(k);
  1725. const scaleX = scales[xScaleKey];
  1726. const xScaleDistr = scaleX.distr;
  1727. let valToPosX, valToPosY, moveTo, arc;
  1728. if (scaleX.ori == 0) {
  1729. addClass(root, ORI_HZ);
  1730. valToPosX = getHPos;
  1731. valToPosY = getVPos;
  1732. moveTo = moveToH;
  1733. arc = arcH;
  1734. /*
  1735. updOriDims = () => {
  1736. xDimCan = plotWid;
  1737. xOffCan = plotLft;
  1738. yDimCan = plotHgt;
  1739. yOffCan = plotTop;
  1740. xDimCss = plotWidCss;
  1741. xOffCss = plotLftCss;
  1742. yDimCss = plotHgtCss;
  1743. yOffCss = plotTopCss;
  1744. };
  1745. */
  1746. }
  1747. else {
  1748. addClass(root, ORI_VT);
  1749. valToPosX = getVPos;
  1750. valToPosY = getHPos;
  1751. moveTo = moveToV;
  1752. arc = arcV;
  1753. /*
  1754. updOriDims = () => {
  1755. xDimCan = plotHgt;
  1756. xOffCan = plotTop;
  1757. yDimCan = plotWid;
  1758. yOffCan = plotLft;
  1759. xDimCss = plotHgtCss;
  1760. xOffCss = plotTopCss;
  1761. yDimCss = plotWidCss;
  1762. yOffCss = plotLftCss;
  1763. };
  1764. */
  1765. }
  1766. const pendScales = {};
  1767. // explicitly-set initial scales
  1768. for (let k in scales) {
  1769. let sc = scales[k];
  1770. if (sc.min != null || sc.max != null)
  1771. pendScales[k] = {min: sc.min, max: sc.max};
  1772. }
  1773. // self.tz = opts.tz || Intl.DateTimeFormat().resolvedOptions().timeZone;
  1774. const _tzDate = (opts.tzDate || (ts => new Date(ts / ms)));
  1775. const _fmtDate = (opts.fmtDate || fmtDate);
  1776. const _timeAxisSplits = (ms == 1 ? timeAxisSplitsMs(_tzDate) : timeAxisSplitsS(_tzDate));
  1777. const _timeAxisVals = timeAxisVals(_tzDate, timeAxisStamps((ms == 1 ? _timeAxisStampsMs : _timeAxisStampsS), _fmtDate));
  1778. const _timeSeriesVal = timeSeriesVal(_tzDate, timeSeriesStamp(_timeSeriesStamp, _fmtDate));
  1779. const legend = assign({show: true, live: true}, opts.legend);
  1780. const showLegend = legend.show;
  1781. {
  1782. legend.width = fnOrSelf(ifNull(legend.width, legendWidth));
  1783. legend.dash = fnOrSelf(legend.dash || legendDash);
  1784. legend.stroke = fnOrSelf(legend.stroke || legendStroke);
  1785. legend.fill = fnOrSelf(legend.fill || legendFill);
  1786. }
  1787. let legendEl;
  1788. let legendRows = [];
  1789. let legendCols;
  1790. let multiValLegend = false;
  1791. if (showLegend) {
  1792. legendEl = placeTag("table", LEGEND, root);
  1793. const getMultiVals = series[1] ? series[1].values : null;
  1794. multiValLegend = getMultiVals != null;
  1795. if (multiValLegend) {
  1796. let head = placeTag("tr", LEGEND_THEAD, legendEl);
  1797. placeTag("th", null, head);
  1798. legendCols = getMultiVals(self, 1, 0);
  1799. for (var key in legendCols)
  1800. placeTag("th", LEGEND_LABEL, head).textContent = key;
  1801. }
  1802. else {
  1803. legendCols = {_: 0};
  1804. addClass(legendEl, LEGEND_INLINE);
  1805. legend.live && addClass(legendEl, LEGEND_LIVE);
  1806. }
  1807. }
  1808. function initLegendRow(s, i) {
  1809. if (i == 0 && (multiValLegend || !legend.live))
  1810. return null;
  1811. let _row = [];
  1812. let row = placeTag("tr", LEGEND_SERIES, legendEl, legendEl.childNodes[i]);
  1813. addClass(row, s.class);
  1814. if (!s.show)
  1815. addClass(row, OFF);
  1816. let label = placeTag("th", null, row);
  1817. let indic = placeDiv(LEGEND_MARKER, label);
  1818. if (i > 0) {
  1819. let width = legend.width(self, i);
  1820. if (width)
  1821. indic.style.border = width + "px " + legend.dash(self, i) + " " + legend.stroke(self, i);
  1822. indic.style.background = legend.fill(self, i);
  1823. }
  1824. let text = placeDiv(LEGEND_LABEL, label);
  1825. text.textContent = s.label;
  1826. if (i > 0) {
  1827. onMouse("click", label, e => {
  1828. if ( cursor._lock)
  1829. return;
  1830. setSeries(series.indexOf(s), {show: !s.show}, syncOpts.setSeries);
  1831. });
  1832. if (cursorFocus) {
  1833. onMouse(mouseenter, label, e => {
  1834. if (cursor._lock)
  1835. return;
  1836. setSeries(series.indexOf(s), FOCUS_TRUE, syncOpts.setSeries);
  1837. });
  1838. }
  1839. }
  1840. for (var key in legendCols) {
  1841. let v = placeTag("td", LEGEND_VALUE, row);
  1842. v.textContent = "--";
  1843. _row.push(v);
  1844. }
  1845. return _row;
  1846. }
  1847. const mouseListeners = new Map();
  1848. function onMouse(ev, targ, fn) {
  1849. const targListeners = mouseListeners.get(targ) || {};
  1850. const listener = cursor.bind[ev](self, targ, fn);
  1851. if (listener) {
  1852. on(ev, targ, targListeners[ev] = listener);
  1853. mouseListeners.set(targ, targListeners);
  1854. }
  1855. }
  1856. function offMouse(ev, targ, fn) {
  1857. const targListeners = mouseListeners.get(targ) || {};
  1858. off(ev, targ, targListeners[ev]);
  1859. targListeners[ev] = null;
  1860. }
  1861. let fullWidCss = 0;
  1862. let fullHgtCss = 0;
  1863. let plotWidCss = 0;
  1864. let plotHgtCss = 0;
  1865. // plot margins to account for axes
  1866. let plotLftCss = 0;
  1867. let plotTopCss = 0;
  1868. let plotLft = 0;
  1869. let plotTop = 0;
  1870. let plotWid = 0;
  1871. let plotHgt = 0;
  1872. self.bbox = {};
  1873. let shouldSetScales = false;
  1874. let shouldSetSize = false;
  1875. let shouldConvergeSize = false;
  1876. let shouldSetCursor = false;
  1877. let shouldSetLegend = false;
  1878. function _setSize(width, height) {
  1879. if (width != self.width || height != self.height)
  1880. calcSize(width, height);
  1881. resetYSeries(false);
  1882. shouldConvergeSize = true;
  1883. shouldSetSize = true;
  1884. shouldSetCursor = true;
  1885. shouldSetLegend = true;
  1886. commit();
  1887. }
  1888. function calcSize(width, height) {
  1889. // log("calcSize()", arguments);
  1890. self.width = fullWidCss = plotWidCss = width;
  1891. self.height = fullHgtCss = plotHgtCss = height;
  1892. plotLftCss = plotTopCss = 0;
  1893. calcPlotRect();
  1894. calcAxesRects();
  1895. let bb = self.bbox;
  1896. plotLft = bb.left = incrRound(plotLftCss * pxRatio, 0.5);
  1897. plotTop = bb.top = incrRound(plotTopCss * pxRatio, 0.5);
  1898. plotWid = bb.width = incrRound(plotWidCss * pxRatio, 0.5);
  1899. plotHgt = bb.height = incrRound(plotHgtCss * pxRatio, 0.5);
  1900. // updOriDims();
  1901. }
  1902. function convergeSize() {
  1903. let converged = false;
  1904. let cycleNum = 0;
  1905. while (!converged) {
  1906. cycleNum++;
  1907. let axesConverged = axesCalc(cycleNum);
  1908. let paddingConverged = paddingCalc(cycleNum);
  1909. converged = axesConverged && paddingConverged;
  1910. if (!converged) {
  1911. calcSize(self.width, self.height);
  1912. shouldSetSize = true;
  1913. }
  1914. }
  1915. }
  1916. function setSize({width, height}) {
  1917. _setSize(width, height);
  1918. }
  1919. self.setSize = setSize;
  1920. // accumulate axis offsets, reduce canvas width
  1921. function calcPlotRect() {
  1922. // easements for edge labels
  1923. let hasTopAxis = false;
  1924. let hasBtmAxis = false;
  1925. let hasRgtAxis = false;
  1926. let hasLftAxis = false;
  1927. axes.forEach((axis, i) => {
  1928. if (axis.show && axis._show) {
  1929. let {side, _size} = axis;
  1930. let isVt = side % 2;
  1931. let labelSize = axis.labelSize = (axis.label != null ? (axis.labelSize || 30) : 0);
  1932. let fullSize = _size + labelSize;
  1933. if (fullSize > 0) {
  1934. if (isVt) {
  1935. plotWidCss -= fullSize;
  1936. if (side == 3) {
  1937. plotLftCss += fullSize;
  1938. hasLftAxis = true;
  1939. }
  1940. else
  1941. hasRgtAxis = true;
  1942. }
  1943. else {
  1944. plotHgtCss -= fullSize;
  1945. if (side == 0) {
  1946. plotTopCss += fullSize;
  1947. hasTopAxis = true;
  1948. }
  1949. else
  1950. hasBtmAxis = true;
  1951. }
  1952. }
  1953. }
  1954. });
  1955. sidesWithAxes[0] = hasTopAxis;
  1956. sidesWithAxes[1] = hasRgtAxis;
  1957. sidesWithAxes[2] = hasBtmAxis;
  1958. sidesWithAxes[3] = hasLftAxis;
  1959. // hz padding
  1960. plotWidCss -= _padding[1] + _padding[3];
  1961. plotLftCss += _padding[3];
  1962. // vt padding
  1963. plotHgtCss -= _padding[2] + _padding[0];
  1964. plotTopCss += _padding[0];
  1965. }
  1966. function calcAxesRects() {
  1967. // will accum +
  1968. let off1 = plotLftCss + plotWidCss;
  1969. let off2 = plotTopCss + plotHgtCss;
  1970. // will accum -
  1971. let off3 = plotLftCss;
  1972. let off0 = plotTopCss;
  1973. function incrOffset(side, size) {
  1974. switch (side) {
  1975. case 1: off1 += size; return off1 - size;
  1976. case 2: off2 += size; return off2 - size;
  1977. case 3: off3 -= size; return off3 + size;
  1978. case 0: off0 -= size; return off0 + size;
  1979. }
  1980. }
  1981. axes.forEach((axis, i) => {
  1982. if (axis.show && axis._show) {
  1983. let side = axis.side;
  1984. axis._pos = incrOffset(side, axis._size);
  1985. if (axis.label != null)
  1986. axis._lpos = incrOffset(side, axis.labelSize);
  1987. }
  1988. });
  1989. }
  1990. const cursor = (self.cursor = assign({}, cursorOpts, opts.cursor));
  1991. {
  1992. cursor._lock = false;
  1993. let points = cursor.points;
  1994. points.show = fnOrSelf(points.show);
  1995. points.size = fnOrSelf(points.size);
  1996. points.stroke = fnOrSelf(points.stroke);
  1997. points.width = fnOrSelf(points.width);
  1998. points.fill = fnOrSelf(points.fill);
  1999. }
  2000. const focus = self.focus = assign({}, opts.focus || {alpha: 0.3}, cursor.focus);
  2001. const cursorFocus = focus.prox >= 0;
  2002. // series-intersection markers
  2003. let cursorPts = [null];
  2004. function initCursorPt(s, si) {
  2005. if (si > 0) {
  2006. let pt = cursor.points.show(self, si);
  2007. if (pt) {
  2008. addClass(pt, CURSOR_PT);
  2009. addClass(pt, s.class);
  2010. trans(pt, -10, -10, plotWidCss, plotHgtCss);
  2011. over.insertBefore(pt, cursorPts[si]);
  2012. return pt;
  2013. }
  2014. }
  2015. }
  2016. function initSeries(s, i) {
  2017. let isTime = scales[s.scale].time;
  2018. let sv = s.value;
  2019. s.value = isTime ? (isStr(sv) ? timeSeriesVal(_tzDate, timeSeriesStamp(sv, _fmtDate)) : sv || _timeSeriesVal) : sv || numSeriesVal;
  2020. s.label = s.label || (isTime ? timeSeriesLabel : numSeriesLabel);
  2021. if (i > 0) {
  2022. s.width = s.width == null ? 1 : s.width;
  2023. s.paths = s.paths || linearPath || retNull;
  2024. s.fillTo = fnOrSelf(s.fillTo || seriesFillTo);
  2025. s.stroke = fnOrSelf(s.stroke || hexBlack);
  2026. s.fill = fnOrSelf(s.fill || null);
  2027. s._stroke = s._fill = s._paths = null;
  2028. let _ptDia = ptDia(s.width, 1);
  2029. let points = s.points = assign({}, {
  2030. size: _ptDia,
  2031. width: max(1, _ptDia * .2),
  2032. stroke: s.stroke,
  2033. space: _ptDia * 2,
  2034. _stroke: null,
  2035. _fill: null,
  2036. }, s.points);
  2037. points.show = fnOrSelf(points.show);
  2038. points.fill = fnOrSelf(points.fill);
  2039. points.stroke = fnOrSelf(points.stroke);
  2040. }
  2041. if (showLegend)
  2042. legendRows.splice(i, 0, initLegendRow(s, i));
  2043. if ( cursor.show) {
  2044. let pt = initCursorPt(s, i);
  2045. pt && cursorPts.splice(i, 0, pt);
  2046. }
  2047. }
  2048. function addSeries(opts, si) {
  2049. si = si == null ? series.length : si;
  2050. opts = setDefault(opts, si, xSeriesOpts, ySeriesOpts);
  2051. series.splice(si, 0, opts);
  2052. initSeries(series[si], si);
  2053. }
  2054. self.addSeries = addSeries;
  2055. function delSeries(i) {
  2056. series.splice(i, 1);
  2057. showLegend && legendRows.splice(i, 1)[0][0].parentNode.remove();
  2058. cursorPts.length > 1 && cursorPts.splice(i, 1)[0].remove();
  2059. // TODO: de-init no-longer-needed scales?
  2060. }
  2061. self.delSeries = delSeries;
  2062. series.forEach(initSeries);
  2063. const sidesWithAxes = [false, false, false, false];
  2064. function initAxis(axis, i) {
  2065. axis._show = axis.show;
  2066. if (axis.show) {
  2067. let isVt = axis.side % 2;
  2068. let sc = scales[axis.scale];
  2069. // this can occur if all series specify non-default scales
  2070. if (sc == null) {
  2071. axis.scale = isVt ? series[1].scale : xScaleKey;
  2072. sc = scales[axis.scale];
  2073. }
  2074. // also set defaults for incrs & values based on axis distr
  2075. let isTime = sc.time;
  2076. axis.size = fnOrSelf(axis.size);
  2077. axis.space = fnOrSelf(axis.space);
  2078. axis.rotate = fnOrSelf(axis.rotate);
  2079. axis.incrs = fnOrSelf(axis.incrs || ( sc.distr == 2 ? wholeIncrs : (isTime ? (ms == 1 ? timeIncrsMs : timeIncrsS) : numIncrs)));
  2080. axis.splits = fnOrSelf(axis.splits || (isTime && sc.distr == 1 ? _timeAxisSplits : sc.distr == 3 ? logAxisSplits : numAxisSplits));
  2081. let av = axis.values;
  2082. axis.values = (
  2083. isTime ? (
  2084. isArr(av) ?
  2085. timeAxisVals(_tzDate, timeAxisStamps(av, _fmtDate)) :
  2086. isStr(av) ?
  2087. timeAxisVal(_tzDate, av) :
  2088. av || _timeAxisVals
  2089. ) : av || numAxisVals
  2090. );
  2091. axis.filter = fnOrSelf(axis.filter || ( sc.distr == 3 ? logAxisValsFilt : retArg1));
  2092. axis.font = pxRatioFont(axis.font);
  2093. axis.labelFont = pxRatioFont(axis.labelFont);
  2094. axis._size = axis.size(self, null, i, 0);
  2095. axis._space =
  2096. axis._rotate =
  2097. axis._incrs =
  2098. axis._found = // foundIncrSpace
  2099. axis._splits =
  2100. axis._values = null;
  2101. if (axis._size > 0)
  2102. sidesWithAxes[i] = true;
  2103. }
  2104. }
  2105. // set axis defaults
  2106. axes.forEach(initAxis);
  2107. function autoPadSide(self, side, sidesWithAxes, cycleNum) {
  2108. let [hasTopAxis, hasRgtAxis, hasBtmAxis, hasLftAxis] = sidesWithAxes;
  2109. let ori = side % 2;
  2110. let size = 0;
  2111. if (ori == 0 && (hasLftAxis || hasRgtAxis))
  2112. size = (side == 0 && !hasTopAxis || side == 2 && !hasBtmAxis ? round(xAxisOpts.size / 3) : 0);
  2113. if (ori == 1 && (hasTopAxis || hasBtmAxis))
  2114. size = (side == 1 && !hasRgtAxis || side == 3 && !hasLftAxis ? round(yAxisOpts.size / 2) : 0);
  2115. return size;
  2116. }
  2117. const padding = self.padding = (opts.padding || [autoPadSide,autoPadSide,autoPadSide,autoPadSide]).map(p => fnOrSelf(ifNull(p, autoPadSide)));
  2118. const _padding = self._padding = padding.map((p, i) => p(self, i, sidesWithAxes, 0));
  2119. let dataLen;
  2120. // rendered data window
  2121. let i0 = null;
  2122. let i1 = null;
  2123. const idxs = series[0].idxs;
  2124. let data0 = null;
  2125. let viaAutoScaleX = false;
  2126. function setData(_data, _resetScales) {
  2127. if (!isArr(_data) && isObj(_data)) {
  2128. _data.isGap && series.forEach(s => { s.isGap = _data.isGap; });
  2129. _data = _data.data;
  2130. }
  2131. _data = _data || [];
  2132. _data[0] = _data[0] || [];
  2133. self.data = _data;
  2134. data = _data.slice();
  2135. data0 = data[0];
  2136. dataLen = data0.length;
  2137. if (xScaleDistr == 2)
  2138. data[0] = data0.map((v, i) => i);
  2139. self._data = data;
  2140. resetYSeries(true);
  2141. fire("setData");
  2142. if (_resetScales !== false) {
  2143. let xsc = scaleX;
  2144. if (xsc.auto(self, viaAutoScaleX))
  2145. autoScaleX();
  2146. else
  2147. _setScale(xScaleKey, xsc.min, xsc.max);
  2148. shouldSetCursor = true;
  2149. shouldSetLegend = true;
  2150. commit();
  2151. }
  2152. }
  2153. self.setData = setData;
  2154. function autoScaleX() {
  2155. viaAutoScaleX = true;
  2156. let _min, _max;
  2157. if (dataLen > 0) {
  2158. i0 = idxs[0] = 0;
  2159. i1 = idxs[1] = dataLen - 1;
  2160. _min = data[0][i0];
  2161. _max = data[0][i1];
  2162. if (xScaleDistr == 2) {
  2163. _min = i0;
  2164. _max = i1;
  2165. }
  2166. else if (dataLen == 1) {
  2167. if (xScaleDistr == 3)
  2168. [_min, _max] = rangeLog(_min, _min, scaleX.log, false);
  2169. else if (scaleX.time)
  2170. _max = _min + 86400 / ms;
  2171. else
  2172. [_min, _max] = rangeNum(_min, _max, 0.1, true);
  2173. }
  2174. }
  2175. else {
  2176. i0 = idxs[0] = _min = null;
  2177. i1 = idxs[1] = _max = null;
  2178. }
  2179. _setScale(xScaleKey, _min, _max);
  2180. }
  2181. function setCtxStyle(stroke, width, dash, cap, fill) {
  2182. ctx.strokeStyle = stroke || transparent;
  2183. ctx.lineWidth = width;
  2184. ctx.lineJoin = "round";
  2185. ctx.lineCap = cap || "butt"; // (‿|‿)
  2186. ctx.setLineDash(dash || []);
  2187. ctx.fillStyle = fill || transparent;
  2188. }
  2189. function setScales() {
  2190. // log("setScales()", arguments);
  2191. // wip scales
  2192. let wipScales = copy(scales);
  2193. for (let k in wipScales) {
  2194. let wsc = wipScales[k];
  2195. let psc = pendScales[k];
  2196. if (psc != null && psc.min != null) {
  2197. assign(wsc, psc);
  2198. // explicitly setting the x-scale invalidates everything (acts as redraw)
  2199. if (k == xScaleKey)
  2200. resetYSeries(true);
  2201. }
  2202. else if (k != xScaleKey) {
  2203. if (dataLen == 0 && wsc.from == null) {
  2204. let minMax = wsc.range(self, null, null, k);
  2205. wsc.min = minMax[0];
  2206. wsc.max = minMax[1];
  2207. }
  2208. else {
  2209. wsc.min = inf;
  2210. wsc.max = -inf;
  2211. }
  2212. }
  2213. }
  2214. if (dataLen > 0) {
  2215. // pre-range y-scales from y series' data values
  2216. series.forEach((s, i) => {
  2217. let k = s.scale;
  2218. let wsc = wipScales[k];
  2219. let psc = pendScales[k];
  2220. if (i == 0) {
  2221. let minMax = wsc.range(self, wsc.min, wsc.max, k);
  2222. wsc.min = minMax[0];
  2223. wsc.max = minMax[1];
  2224. i0 = closestIdx(wsc.min, data[0]);
  2225. i1 = closestIdx(wsc.max, data[0]);
  2226. // closest indices can be outside of view
  2227. if (data[0][i0] < wsc.min)
  2228. i0++;
  2229. if (data[0][i1] > wsc.max)
  2230. i1--;
  2231. s.min = data0[i0];
  2232. s.max = data0[i1];
  2233. }
  2234. else if (s.show && s.auto && wsc.auto(self, viaAutoScaleX) && (psc == null || psc.min == null)) {
  2235. // only run getMinMax() for invalidated series data, else reuse
  2236. let minMax = s.min == null ? (wsc.distr == 3 ? getMinMaxLog(data[i], i0, i1) : getMinMax(data[i], i0, i1, s.sorted)) : [s.min, s.max];
  2237. // initial min/max
  2238. wsc.min = min(wsc.min, s.min = minMax[0]);
  2239. wsc.max = max(wsc.max, s.max = minMax[1]);
  2240. }
  2241. s.idxs[0] = i0;
  2242. s.idxs[1] = i1;
  2243. });
  2244. // range independent scales
  2245. for (let k in wipScales) {
  2246. let wsc = wipScales[k];
  2247. let psc = pendScales[k];
  2248. if (wsc.from == null && (psc == null || psc.min == null)) {
  2249. let minMax = wsc.range(
  2250. self,
  2251. wsc.min == inf ? null : wsc.min,
  2252. wsc.max == -inf ? null : wsc.max,
  2253. k
  2254. );
  2255. wsc.min = minMax[0];
  2256. wsc.max = minMax[1];
  2257. }
  2258. }
  2259. }
  2260. // range dependent scales
  2261. for (let k in wipScales) {
  2262. let wsc = wipScales[k];
  2263. if (wsc.from != null) {
  2264. let base = wipScales[wsc.from];
  2265. let minMax = wsc.range(self, base.min, base.max, k);
  2266. wsc.min = minMax[0];
  2267. wsc.max = minMax[1];
  2268. }
  2269. }
  2270. let changed = {};
  2271. let anyChanged = false;
  2272. for (let k in wipScales) {
  2273. let wsc = wipScales[k];
  2274. let sc = scales[k];
  2275. if (sc.min != wsc.min || sc.max != wsc.max) {
  2276. sc.min = wsc.min;
  2277. sc.max = wsc.max;
  2278. changed[k] = anyChanged = true;
  2279. }
  2280. }
  2281. if (anyChanged) {
  2282. // invalidate paths of all series on changed scales
  2283. series.forEach(s => {
  2284. if (changed[s.scale])
  2285. s._paths = null;
  2286. });
  2287. for (let k in changed) {
  2288. shouldConvergeSize = true;
  2289. fire("setScale", k);
  2290. }
  2291. if ( cursor.show)
  2292. shouldSetCursor = true;
  2293. }
  2294. for (let k in pendScales)
  2295. pendScales[k] = null;
  2296. }
  2297. // TODO: drawWrap(si, drawPoints) (save, restore, translate, clip)
  2298. function drawPoints(si) {
  2299. // log("drawPoints()", arguments);
  2300. let s = series[si];
  2301. let p = s.points;
  2302. const width = roundDec(p.width * pxRatio, 3);
  2303. const offset = (width % 2) / 2;
  2304. const isStroked = p.width > 0;
  2305. let rad = (p.size - p.width) / 2 * pxRatio;
  2306. let dia = roundDec(rad * 2, 3);
  2307. ctx.translate(offset, offset);
  2308. ctx.save();
  2309. ctx.beginPath();
  2310. ctx.rect(
  2311. plotLft - dia,
  2312. plotTop - dia,
  2313. plotWid + dia * 2,
  2314. plotHgt + dia * 2,
  2315. );
  2316. ctx.clip();
  2317. ctx.globalAlpha = s.alpha;
  2318. const path = new Path2D();
  2319. const scaleY = scales[s.scale];
  2320. let xDim, xOff, yDim, yOff;
  2321. if (scaleX.ori == 0) {
  2322. xDim = plotWid;
  2323. xOff = plotLft;
  2324. yDim = plotHgt;
  2325. yOff = plotTop;
  2326. }
  2327. else {
  2328. xDim = plotHgt;
  2329. xOff = plotTop;
  2330. yDim = plotWid;
  2331. yOff = plotLft;
  2332. }
  2333. for (let pi = i0; pi <= i1; pi++) {
  2334. if (data[si][pi] != null) {
  2335. let x = round(valToPosX(data[0][pi], scaleX, xDim, xOff));
  2336. let y = round(valToPosY(data[si][pi], scaleY, yDim, yOff));
  2337. moveTo(path, x + rad, y);
  2338. arc(path, x, y, rad, 0, PI * 2);
  2339. }
  2340. }
  2341. const _stroke = p._stroke = p.stroke(self, si);
  2342. const _fill = p._fill = p.fill(self, si);
  2343. setCtxStyle(
  2344. _stroke,
  2345. width,
  2346. p.dash,
  2347. p.cap,
  2348. _fill || (isStroked ? "#fff" : s._stroke),
  2349. );
  2350. ctx.fill(path);
  2351. isStroked && ctx.stroke(path);
  2352. ctx.globalAlpha = 1;
  2353. ctx.restore();
  2354. ctx.translate(-offset, -offset);
  2355. }
  2356. // grabs the nearest indices with y data outside of x-scale limits
  2357. function getOuterIdxs(ydata) {
  2358. let _i0 = clamp(i0 - 1, 0, dataLen - 1);
  2359. let _i1 = clamp(i1 + 1, 0, dataLen - 1);
  2360. while (ydata[_i0] == null && _i0 > 0)
  2361. _i0--;
  2362. while (ydata[_i1] == null && _i1 < dataLen - 1)
  2363. _i1++;
  2364. return [_i0, _i1];
  2365. }
  2366. function drawSeries() {
  2367. if (dataLen > 0) {
  2368. series.forEach((s, i) => {
  2369. if (i > 0 && s.show && s._paths == null) {
  2370. let _idxs = getOuterIdxs(data[i]);
  2371. s._paths = s.paths(self, i, _idxs[0], _idxs[1]);
  2372. }
  2373. });
  2374. series.forEach((s, i) => {
  2375. if (i > 0 && s.show) {
  2376. if (s._paths)
  2377. drawPath(i);
  2378. if (s.points.show(self, i, i0, i1))
  2379. drawPoints(i);
  2380. fire("drawSeries", i);
  2381. }
  2382. });
  2383. }
  2384. }
  2385. function drawPath(si) {
  2386. const s = series[si];
  2387. const { stroke, fill, clip } = s._paths;
  2388. const width = roundDec(s.width * pxRatio, 3);
  2389. const offset = (width % 2) / 2;
  2390. const _stroke = s._stroke = s.stroke(self, si);
  2391. const _fill = s._fill = s.fill(self, si);
  2392. setCtxStyle(_stroke, width, s.dash, s.cap, _fill);
  2393. ctx.globalAlpha = s.alpha;
  2394. ctx.translate(offset, offset);
  2395. ctx.save();
  2396. let lft = plotLft,
  2397. top = plotTop,
  2398. wid = plotWid,
  2399. hgt = plotHgt;
  2400. let halfWid = width * pxRatio / 2;
  2401. if (s.min == 0)
  2402. hgt += halfWid;
  2403. if (s.max == 0) {
  2404. top -= halfWid;
  2405. hgt += halfWid;
  2406. }
  2407. ctx.beginPath();
  2408. ctx.rect(lft, top, wid, hgt);
  2409. ctx.clip();
  2410. if (clip != null)
  2411. ctx.clip(clip);
  2412. if (!fillBands(si, _fill) && _fill != null)
  2413. ctx.fill(fill);
  2414. width && ctx.stroke(stroke);
  2415. ctx.restore();
  2416. ctx.translate(-offset, -offset);
  2417. ctx.globalAlpha = 1;
  2418. }
  2419. function fillBands(si, seriesFill) {
  2420. let isUpperEdge = false;
  2421. let s = series[si];
  2422. // for all bands where this series is the top edge, create upwards clips using the bottom edges
  2423. // and apply clips + fill with band fill or dfltFill
  2424. bands.forEach((b, bi) => {
  2425. if (b.series[0] == si) {
  2426. isUpperEdge = true;
  2427. let lowerEdge = series[b.series[1]];
  2428. let clip = (lowerEdge._paths || EMPTY_OBJ).band;
  2429. if (lowerEdge.show && clip) {
  2430. ctx.save();
  2431. setCtxStyle(null, null, null, null, b.fill(self, bi) || seriesFill);
  2432. ctx.clip(clip);
  2433. ctx.fill(s._paths.fill);
  2434. ctx.restore();
  2435. }
  2436. }
  2437. });
  2438. return isUpperEdge;
  2439. }
  2440. function getIncrSpace(axisIdx, min, max, fullDim) {
  2441. let axis = axes[axisIdx];
  2442. let incrSpace;
  2443. if (fullDim <= 0)
  2444. incrSpace = [0, 0];
  2445. else {
  2446. let minSpace = axis._space = axis.space(self, axisIdx, min, max, fullDim);
  2447. let incrs = axis._incrs = axis.incrs(self, axisIdx, min, max, fullDim, minSpace);
  2448. incrSpace = axis._found = findIncr(min, max, incrs, fullDim, minSpace);
  2449. }
  2450. return incrSpace;
  2451. }
  2452. function drawOrthoLines(offs, filts, ori, side, pos0, len, width, stroke, dash, cap) {
  2453. let offset = (width % 2) / 2;
  2454. ctx.translate(offset, offset);
  2455. setCtxStyle(stroke, width, dash, cap);
  2456. ctx.beginPath();
  2457. let x0, y0, x1, y1, pos1 = pos0 + (side == 0 || side == 3 ? -len : len);
  2458. if (ori == 0) {
  2459. y0 = pos0;
  2460. y1 = pos1;
  2461. }
  2462. else {
  2463. x0 = pos0;
  2464. x1 = pos1;
  2465. }
  2466. offs.forEach((off, i) => {
  2467. if (filts[i] == null)
  2468. return;
  2469. if (ori == 0)
  2470. x0 = x1 = off;
  2471. else
  2472. y0 = y1 = off;
  2473. ctx.moveTo(x0, y0);
  2474. ctx.lineTo(x1, y1);
  2475. });
  2476. ctx.stroke();
  2477. ctx.translate(-offset, -offset);
  2478. }
  2479. function axesCalc(cycleNum) {
  2480. // log("axesCalc()", arguments);
  2481. let converged = true;
  2482. axes.forEach((axis, i) => {
  2483. if (!axis.show)
  2484. return;
  2485. let scale = scales[axis.scale];
  2486. if (scale.min == null) {
  2487. if (axis._show) {
  2488. converged = false;
  2489. axis._show = false;
  2490. resetYSeries(false);
  2491. }
  2492. return;
  2493. }
  2494. else {
  2495. if (!axis._show) {
  2496. converged = false;
  2497. axis._show = true;
  2498. resetYSeries(false);
  2499. }
  2500. }
  2501. let side = axis.side;
  2502. let ori = side % 2;
  2503. let {min, max} = scale; // // should this toggle them ._show = false
  2504. let [_incr, _space] = getIncrSpace(i, min, max, ori == 0 ? plotWidCss : plotHgtCss);
  2505. if (_space == 0)
  2506. return;
  2507. // if we're using index positions, force first tick to match passed index
  2508. let forceMin = scale.distr == 2;
  2509. let _splits = axis._splits = axis.splits(self, i, min, max, _incr, _space, forceMin);
  2510. // tick labels
  2511. // BOO this assumes a specific data/series
  2512. let splits = scale.distr == 2 ? _splits.map(i => data0[i]) : _splits;
  2513. let incr = scale.distr == 2 ? data0[_splits[1]] - data0[_splits[0]] : _incr;
  2514. let values = axis._values = axis.values(self, axis.filter(self, splits, i, _space, incr), i, _space, incr);
  2515. // rotating of labels only supported on bottom x axis
  2516. axis._rotate = side == 2 ? axis.rotate(self, values, i, _space) : 0;
  2517. let oldSize = axis._size;
  2518. axis._size = ceil(axis.size(self, values, i, cycleNum));
  2519. if (oldSize != null && axis._size != oldSize) // ready && ?
  2520. converged = false;
  2521. });
  2522. return converged;
  2523. }
  2524. function paddingCalc(cycleNum) {
  2525. let converged = true;
  2526. padding.forEach((p, i) => {
  2527. let _p = p(self, i, sidesWithAxes, cycleNum);
  2528. if (_p != _padding[i])
  2529. converged = false;
  2530. _padding[i] = _p;
  2531. });
  2532. return converged;
  2533. }
  2534. function drawAxesGrid() {
  2535. axes.forEach((axis, i) => {
  2536. if (!axis.show || !axis._show)
  2537. return;
  2538. let scale = scales[axis.scale];
  2539. let side = axis.side;
  2540. let ori = side % 2;
  2541. let plotDim = ori == 0 ? plotWid : plotHgt;
  2542. let plotOff = ori == 0 ? plotLft : plotTop;
  2543. let axisGap = round(axis.gap * pxRatio);
  2544. let ticks = axis.ticks;
  2545. let tickSize = ticks.show ? round(ticks.size * pxRatio) : 0;
  2546. let [_incr, _space] = axis._found;
  2547. let _splits = axis._splits;
  2548. // tick labels
  2549. // BOO this assumes a specific data/series
  2550. let splits = scale.distr == 2 ? _splits.map(i => data0[i]) : _splits;
  2551. let incr = scale.distr == 2 ? data0[_splits[1]] - data0[_splits[0]] : _incr;
  2552. // rotating of labels only supported on bottom x axis
  2553. let angle = axis._rotate * -PI/180;
  2554. let basePos = round(axis._pos * pxRatio);
  2555. let shiftAmt = tickSize + axisGap;
  2556. let shiftDir = ori == 0 && side == 0 || ori == 1 && side == 3 ? -1 : 1;
  2557. let finalPos = basePos + shiftAmt * shiftDir;
  2558. let y = ori == 0 ? finalPos : 0;
  2559. let x = ori == 1 ? finalPos : 0;
  2560. ctx.font = axis.font[0];
  2561. ctx.fillStyle = axis.stroke || hexBlack; // rgba?
  2562. ctx.textAlign = axis.align == 1 ? LEFT :
  2563. axis.align == 2 ? RIGHT :
  2564. angle > 0 ? LEFT :
  2565. angle < 0 ? RIGHT :
  2566. ori == 0 ? "center" : side == 3 ? RIGHT : LEFT;
  2567. ctx.textBaseline = angle ||
  2568. ori == 1 ? "middle" : side == 2 ? TOP : BOTTOM;
  2569. let lineHeight = axis.font[1] * lineMult;
  2570. let canOffs = _splits.map(val => round(getPos(val, scale, plotDim, plotOff)));
  2571. axis._values.forEach((val, i) => {
  2572. if (val == null)
  2573. return;
  2574. if (ori == 0)
  2575. x = canOffs[i];
  2576. else
  2577. y = canOffs[i];
  2578. (""+val).split(/\n/gm).forEach((text, j) => {
  2579. if (angle) {
  2580. ctx.save();
  2581. ctx.translate(x, y + j * lineHeight);
  2582. ctx.rotate(angle);
  2583. ctx.fillText(text, 0, 0);
  2584. ctx.restore();
  2585. }
  2586. else
  2587. ctx.fillText(text, x, y + j * lineHeight);
  2588. });
  2589. });
  2590. // axis label
  2591. if (axis.label) {
  2592. ctx.save();
  2593. let baseLpos = round(axis._lpos * pxRatio);
  2594. if (ori == 1) {
  2595. x = y = 0;
  2596. ctx.translate(
  2597. baseLpos,
  2598. round(plotTop + plotHgt / 2),
  2599. );
  2600. ctx.rotate((side == 3 ? -PI : PI) / 2);
  2601. }
  2602. else {
  2603. x = round(plotLft + plotWid / 2);
  2604. y = baseLpos;
  2605. }
  2606. ctx.font = axis.labelFont[0];
  2607. // ctx.fillStyle = axis.labelStroke || hexBlack; // rgba?
  2608. ctx.textAlign = "center";
  2609. ctx.textBaseline = side == 2 ? TOP : BOTTOM;
  2610. ctx.fillText(axis.label, x, y);
  2611. ctx.restore();
  2612. }
  2613. // ticks
  2614. if (ticks.show) {
  2615. drawOrthoLines(
  2616. canOffs,
  2617. ticks.filter(self, splits, i, _space, incr),
  2618. ori,
  2619. side,
  2620. basePos,
  2621. tickSize,
  2622. roundDec(ticks.width * pxRatio, 3),
  2623. ticks.stroke,
  2624. ticks.dash,
  2625. ticks.cap,
  2626. );
  2627. }
  2628. // grid
  2629. let grid = axis.grid;
  2630. if (grid.show) {
  2631. drawOrthoLines(
  2632. canOffs,
  2633. grid.filter(self, splits, i, _space, incr),
  2634. ori,
  2635. ori == 0 ? 2 : 1,
  2636. ori == 0 ? plotTop : plotLft,
  2637. ori == 0 ? plotHgt : plotWid,
  2638. roundDec(grid.width * pxRatio, 3),
  2639. grid.stroke,
  2640. grid.dash,
  2641. grid.cap,
  2642. );
  2643. }
  2644. });
  2645. fire("drawAxes");
  2646. }
  2647. function resetYSeries(minMax) {
  2648. // log("resetYSeries()", arguments);
  2649. series.forEach((s, i) => {
  2650. if (i > 0) {
  2651. s._paths = null;
  2652. if (minMax) {
  2653. s.min = null;
  2654. s.max = null;
  2655. }
  2656. }
  2657. });
  2658. }
  2659. let queuedCommit = false;
  2660. function commit() {
  2661. if (!queuedCommit) {
  2662. microTask(_commit);
  2663. queuedCommit = true;
  2664. }
  2665. }
  2666. function _commit() {
  2667. // log("_commit()", arguments);
  2668. if (shouldSetScales) {
  2669. setScales();
  2670. shouldSetScales = false;
  2671. }
  2672. if (shouldConvergeSize) {
  2673. convergeSize();
  2674. shouldConvergeSize = false;
  2675. }
  2676. if (shouldSetSize) {
  2677. setStylePx(under, LEFT, plotLftCss);
  2678. setStylePx(under, TOP, plotTopCss);
  2679. setStylePx(under, WIDTH, plotWidCss);
  2680. setStylePx(under, HEIGHT, plotHgtCss);
  2681. setStylePx(over, LEFT, plotLftCss);
  2682. setStylePx(over, TOP, plotTopCss);
  2683. setStylePx(over, WIDTH, plotWidCss);
  2684. setStylePx(over, HEIGHT, plotHgtCss);
  2685. setStylePx(wrap, WIDTH, fullWidCss);
  2686. setStylePx(wrap, HEIGHT, fullHgtCss);
  2687. can.width = round(fullWidCss * pxRatio);
  2688. can.height = round(fullHgtCss * pxRatio);
  2689. syncRect();
  2690. fire("setSize");
  2691. shouldSetSize = false;
  2692. }
  2693. // if (shouldSetSelect) {
  2694. // TODO: update .u-select metrics (if visible)
  2695. // setStylePx(selectDiv, TOP, select.top = 0);
  2696. // setStylePx(selectDiv, LEFT, select.left = 0);
  2697. // setStylePx(selectDiv, WIDTH, select.width = 0);
  2698. // setStylePx(selectDiv, HEIGHT, select.height = 0);
  2699. // shouldSetSelect = false;
  2700. // }
  2701. if ( cursor.show && shouldSetCursor) {
  2702. updateCursor();
  2703. shouldSetCursor = false;
  2704. }
  2705. // if (FEAT_LEGEND && legend.show && legend.live && shouldSetLegend) {}
  2706. if (fullWidCss > 0 && fullHgtCss > 0) {
  2707. ctx.clearRect(0, 0, can.width, can.height);
  2708. fire("drawClear");
  2709. drawOrder.forEach(fn => fn());
  2710. fire("draw");
  2711. }
  2712. if (!ready) {
  2713. ready = true;
  2714. self.status = 1;
  2715. fire("ready");
  2716. }
  2717. viaAutoScaleX = false;
  2718. queuedCommit = false;
  2719. }
  2720. self.redraw = rebuildPaths => {
  2721. if (rebuildPaths !== false)
  2722. _setScale(xScaleKey, scaleX.min, scaleX.max);
  2723. else
  2724. commit();
  2725. };
  2726. // redraw() => setScale('x', scales.x.min, scales.x.max);
  2727. // explicit, never re-ranged (is this actually true? for x and y)
  2728. function setScale(key, opts) {
  2729. let sc = scales[key];
  2730. if (sc.from == null) {
  2731. if (dataLen == 0) {
  2732. let minMax = sc.range(self, opts.min, opts.max, key);
  2733. opts.min = minMax[0];
  2734. opts.max = minMax[1];
  2735. }
  2736. if (opts.min > opts.max) {
  2737. let _min = opts.min;
  2738. opts.min = opts.max;
  2739. opts.max = _min;
  2740. }
  2741. if (dataLen > 1 && opts.min != null && opts.max != null && opts.max - opts.min < 1e-16)
  2742. return;
  2743. if (key == xScaleKey) {
  2744. if (sc.distr == 2 && dataLen > 0) {
  2745. opts.min = closestIdx(opts.min, data[0]);
  2746. opts.max = closestIdx(opts.max, data[0]);
  2747. }
  2748. }
  2749. // log("setScale()", arguments);
  2750. pendScales[key] = opts;
  2751. shouldSetScales = true;
  2752. commit();
  2753. }
  2754. }
  2755. self.setScale = setScale;
  2756. // INTERACTION
  2757. let xCursor;
  2758. let yCursor;
  2759. let vCursor;
  2760. let hCursor;
  2761. // starting position before cursor.move
  2762. let rawMouseLeft0;
  2763. let rawMouseTop0;
  2764. // starting position
  2765. let mouseLeft0;
  2766. let mouseTop0;
  2767. // current position before cursor.move
  2768. let rawMouseLeft1;
  2769. let rawMouseTop1;
  2770. // current position
  2771. let mouseLeft1;
  2772. let mouseTop1;
  2773. let dragging = false;
  2774. const drag = cursor.drag;
  2775. let dragX = drag.x;
  2776. let dragY = drag.y;
  2777. if ( cursor.show) {
  2778. if (cursor.x)
  2779. xCursor = placeDiv(CURSOR_X, over);
  2780. if (cursor.y)
  2781. yCursor = placeDiv(CURSOR_Y, over);
  2782. if (scaleX.ori == 0) {
  2783. vCursor = xCursor;
  2784. hCursor = yCursor;
  2785. }
  2786. else {
  2787. vCursor = yCursor;
  2788. hCursor = xCursor;
  2789. }
  2790. mouseLeft1 = cursor.left;
  2791. mouseTop1 = cursor.top;
  2792. }
  2793. const select = self.select = assign({
  2794. show: true,
  2795. over: true,
  2796. left: 0,
  2797. width: 0,
  2798. top: 0,
  2799. height: 0,
  2800. }, opts.select);
  2801. const selectDiv = select.show ? placeDiv(SELECT, select.over ? over : under) : null;
  2802. function setSelect(opts, _fire) {
  2803. if (select.show) {
  2804. for (let prop in opts)
  2805. setStylePx(selectDiv, prop, select[prop] = opts[prop]);
  2806. _fire !== false && fire("setSelect");
  2807. }
  2808. }
  2809. self.setSelect = setSelect;
  2810. function toggleDOM(i, onOff) {
  2811. let s = series[i];
  2812. let label = showLegend ? legendRows[i][0].parentNode : null;
  2813. if (s.show)
  2814. label && remClass(label, OFF);
  2815. else {
  2816. label && addClass(label, OFF);
  2817. cursorPts.length > 1 && trans(cursorPts[i], -10, -10, plotWidCss, plotHgtCss);
  2818. }
  2819. }
  2820. function _setScale(key, min, max) {
  2821. setScale(key, {min, max});
  2822. }
  2823. function setSeries(i, opts, pub) {
  2824. // log("setSeries()", arguments);
  2825. let s = series[i];
  2826. // will this cause redundant commit() if both show and focus are set?
  2827. if (opts.focus != null)
  2828. setFocus(i);
  2829. if (opts.show != null) {
  2830. s.show = opts.show;
  2831. toggleDOM(i, opts.show);
  2832. _setScale(s.scale, null, null);
  2833. commit();
  2834. }
  2835. fire("setSeries", i, opts);
  2836. pub && sync.pub("setSeries", self, i, opts);
  2837. }
  2838. self.setSeries = setSeries;
  2839. function _alpha(i, value) {
  2840. series[i].alpha = value;
  2841. if ( cursor.show && cursorPts[i])
  2842. cursorPts[i].style.opacity = value;
  2843. if ( showLegend && legendRows[i])
  2844. legendRows[i][0].parentNode.style.opacity = value;
  2845. }
  2846. function _setAlpha(i, value) {
  2847. _alpha(i, value);
  2848. }
  2849. // y-distance
  2850. let closestDist;
  2851. let closestSeries;
  2852. let focusedSeries;
  2853. const FOCUS_TRUE = {focus: true};
  2854. const FOCUS_FALSE = {focus: false};
  2855. function setFocus(i) {
  2856. if (i != focusedSeries) {
  2857. // log("setFocus()", arguments);
  2858. series.forEach((s, i2) => {
  2859. _setAlpha(i2, i == null || i2 == 0 || i2 == i ? 1 : focus.alpha);
  2860. });
  2861. focusedSeries = i;
  2862. commit();
  2863. }
  2864. }
  2865. if (showLegend && cursorFocus) {
  2866. on(mouseleave, legendEl, e => {
  2867. if (cursor._lock)
  2868. return;
  2869. setSeries(null, FOCUS_FALSE, syncOpts.setSeries);
  2870. updateCursor();
  2871. });
  2872. }
  2873. function posToVal(pos, scale) {
  2874. let sc = scales[scale];
  2875. let dim = plotWidCss;
  2876. if (sc.ori == 1) {
  2877. dim = plotHgtCss;
  2878. pos = dim - pos;
  2879. }
  2880. if (sc.dir == -1)
  2881. pos = dim - pos;
  2882. let _min = sc.min,
  2883. _max = sc.max,
  2884. pct = pos / dim;
  2885. if (sc.distr == 3) {
  2886. _min = log10(_min);
  2887. _max = log10(_max);
  2888. return pow(10, _min + (_max - _min) * pct);
  2889. }
  2890. else
  2891. return _min + (_max - _min) * pct;
  2892. }
  2893. function closestIdxFromXpos(pos) {
  2894. let v = posToVal(pos, xScaleKey);
  2895. return closestIdx(v, data[0], i0, i1);
  2896. }
  2897. self.valToIdx = val => closestIdx(val, data[0]);
  2898. self.posToIdx = closestIdxFromXpos;
  2899. self.posToVal = posToVal;
  2900. self.valToPos = (val, scale, can) => (
  2901. scales[scale].ori == 0 ?
  2902. getHPos(val, scales[scale],
  2903. can ? plotWid : plotWidCss,
  2904. can ? plotLft : 0,
  2905. ) :
  2906. getVPos(val, scales[scale],
  2907. can ? plotHgt : plotHgtCss,
  2908. can ? plotTop : 0,
  2909. )
  2910. );
  2911. // defers calling expensive functions
  2912. function batch(fn) {
  2913. fn(self);
  2914. commit();
  2915. }
  2916. self.batch = batch;
  2917. (self.setCursor = opts => {
  2918. mouseLeft1 = opts.left;
  2919. mouseTop1 = opts.top;
  2920. // assign(cursor, opts);
  2921. updateCursor();
  2922. });
  2923. function setSelH(off, dim) {
  2924. setStylePx(selectDiv, LEFT, select.left = off);
  2925. setStylePx(selectDiv, WIDTH, select.width = dim);
  2926. }
  2927. function setSelV(off, dim) {
  2928. setStylePx(selectDiv, TOP, select.top = off);
  2929. setStylePx(selectDiv, HEIGHT, select.height = dim);
  2930. }
  2931. let setSelX = scaleX.ori == 0 ? setSelH : setSelV;
  2932. let setSelY = scaleX.ori == 1 ? setSelH : setSelV;
  2933. function updateCursor(ts, src) {
  2934. // ts == null && log("updateCursor()", arguments);
  2935. rawMouseLeft1 = mouseLeft1;
  2936. rawMouseTop1 = mouseTop1;
  2937. [mouseLeft1, mouseTop1] = cursor.move(self, mouseLeft1, mouseTop1);
  2938. if (cursor.show) {
  2939. vCursor && trans(vCursor, round(mouseLeft1), 0, plotWidCss, plotHgtCss);
  2940. hCursor && trans(hCursor, 0, round(mouseTop1), plotWidCss, plotHgtCss);
  2941. }
  2942. let idx;
  2943. // when zooming to an x scale range between datapoints the binary search
  2944. // for nearest min/max indices results in this condition. cheap hack :D
  2945. let noDataInRange = i0 > i1;
  2946. closestDist = inf;
  2947. // TODO: extract
  2948. let xDim = scaleX.ori == 0 ? plotWidCss : plotHgtCss;
  2949. let yDim = scaleX.ori == 1 ? plotWidCss : plotHgtCss;
  2950. // if cursor hidden, hide points & clear legend vals
  2951. if (mouseLeft1 < 0 || dataLen == 0 || noDataInRange) {
  2952. idx = null;
  2953. for (let i = 0; i < series.length; i++) {
  2954. if (i > 0) {
  2955. cursorPts.length > 1 && trans(cursorPts[i], -10, -10, plotWidCss, plotHgtCss);
  2956. }
  2957. if (showLegend && legend.live) {
  2958. if (i == 0 && multiValLegend)
  2959. continue;
  2960. for (let j = 0; j < legendRows[i].length; j++)
  2961. legendRows[i][j].firstChild.nodeValue = '--';
  2962. }
  2963. }
  2964. if (cursorFocus)
  2965. setSeries(null, FOCUS_TRUE, syncOpts.setSeries);
  2966. }
  2967. else {
  2968. // let pctY = 1 - (y / rect.height);
  2969. let mouseXPos = scaleX.ori == 0 ? mouseLeft1 : mouseTop1;
  2970. let valAtPosX = posToVal(mouseXPos, xScaleKey);
  2971. idx = closestIdx(valAtPosX, data[0], i0, i1);
  2972. let xPos = incrRoundUp(valToPosX(data[0][idx], scaleX, xDim, 0), 0.5);
  2973. for (let i = 0; i < series.length; i++) {
  2974. let s = series[i];
  2975. let idx2 = cursor.dataIdx(self, i, idx, valAtPosX);
  2976. let xPos2 = idx2 == idx ? xPos : incrRoundUp(valToPosX(data[0][idx2], scaleX, xDim, 0), 0.5);
  2977. if (i > 0 && s.show) {
  2978. let valAtIdx = data[i][idx2];
  2979. let yPos = valAtIdx == null ? -10 : incrRoundUp(valToPosY(valAtIdx, scales[s.scale], yDim, 0), 0.5);
  2980. if (yPos > 0) {
  2981. let dist = abs(yPos - mouseTop1);
  2982. if (dist <= closestDist) {
  2983. closestDist = dist;
  2984. closestSeries = i;
  2985. }
  2986. }
  2987. let hPos, vPos;
  2988. if (scaleX.ori == 0) {
  2989. hPos = xPos2;
  2990. vPos = yPos;
  2991. }
  2992. else {
  2993. hPos = yPos;
  2994. vPos = xPos2;
  2995. }
  2996. cursorPts.length > 1 && trans(cursorPts[i], hPos, vPos, plotWidCss, plotHgtCss);
  2997. }
  2998. if (showLegend && legend.live) {
  2999. if ((idx2 == cursor.idx && !shouldSetLegend) || i == 0 && multiValLegend)
  3000. continue;
  3001. let src = i == 0 && xScaleDistr == 2 ? data0 : data[i];
  3002. let vals = multiValLegend ? s.values(self, i, idx2) : {_: s.value(self, src[idx2], i, idx2)};
  3003. let j = 0;
  3004. for (let k in vals)
  3005. legendRows[i][j++].firstChild.nodeValue = vals[k];
  3006. }
  3007. }
  3008. shouldSetLegend = false;
  3009. }
  3010. // nit: cursor.drag.setSelect is assumed always true
  3011. if (select.show && dragging) {
  3012. if (src != null) {
  3013. let [xKey, yKey] = syncOpts.scales;
  3014. // match the dragX/dragY implicitness/explicitness of src
  3015. let sdrag = src.cursor.drag;
  3016. dragX = sdrag._x;
  3017. dragY = sdrag._y;
  3018. let { left, top, width, height } = src.select;
  3019. let sori = src.scales[xKey].ori;
  3020. let sPosToVal = src.posToVal;
  3021. let sOff, sDim, sc, a, b;
  3022. if (xKey) {
  3023. if (sori == 0) {
  3024. sOff = left;
  3025. sDim = width;
  3026. }
  3027. else {
  3028. sOff = top;
  3029. sDim = height;
  3030. }
  3031. sc = scales[xKey];
  3032. a = valToPosX(sPosToVal(sOff, xKey), sc, xDim, 0);
  3033. b = valToPosX(sPosToVal(sOff + sDim, xKey), sc, xDim, 0);
  3034. setSelX(min(a,b), abs(b-a));
  3035. if (!yKey)
  3036. setSelY(0, yDim);
  3037. }
  3038. if (yKey) {
  3039. if (sori == 1) {
  3040. sOff = left;
  3041. sDim = width;
  3042. }
  3043. else {
  3044. sOff = top;
  3045. sDim = height;
  3046. }
  3047. sc = scales[yKey];
  3048. a = valToPosY(sPosToVal(sOff, yKey), sc, yDim, 0);
  3049. b = valToPosY(sPosToVal(sOff + sDim, yKey), sc, yDim, 0);
  3050. setSelY(min(a,b), abs(b-a));
  3051. if (!xKey)
  3052. setSelX(0, xDim);
  3053. }
  3054. }
  3055. else {
  3056. let rawDX = abs(rawMouseLeft1 - rawMouseLeft0);
  3057. let rawDY = abs(rawMouseTop1 - rawMouseTop0);
  3058. if (scaleX.ori == 1) {
  3059. let _rawDX = rawDX;
  3060. rawDX = rawDY;
  3061. rawDY = _rawDX;
  3062. }
  3063. dragX = drag.x && rawDX >= drag.dist;
  3064. dragY = drag.y && rawDY >= drag.dist;
  3065. let uni = drag.uni;
  3066. if (uni != null) {
  3067. // only calc drag status if they pass the dist thresh
  3068. if (dragX && dragY) {
  3069. dragX = rawDX >= uni;
  3070. dragY = rawDY >= uni;
  3071. // force unidirectionality when both are under uni limit
  3072. if (!dragX && !dragY) {
  3073. if (rawDY > rawDX)
  3074. dragY = true;
  3075. else
  3076. dragX = true;
  3077. }
  3078. }
  3079. }
  3080. else if (drag.x && drag.y && (dragX || dragY))
  3081. // if omni with no uni then both dragX / dragY should be true if either is true
  3082. dragX = dragY = true;
  3083. let p0, p1;
  3084. if (dragX) {
  3085. if (scaleX.ori == 0) {
  3086. p0 = mouseLeft0;
  3087. p1 = mouseLeft1;
  3088. }
  3089. else {
  3090. p0 = mouseTop0;
  3091. p1 = mouseTop1;
  3092. }
  3093. setSelX(min(p0, p1), abs(p1 - p0));
  3094. if (!dragY)
  3095. setSelY(0, yDim);
  3096. }
  3097. if (dragY) {
  3098. if (scaleX.ori == 1) {
  3099. p0 = mouseLeft0;
  3100. p1 = mouseLeft1;
  3101. }
  3102. else {
  3103. p0 = mouseTop0;
  3104. p1 = mouseTop1;
  3105. }
  3106. setSelY(min(p0, p1), abs(p1 - p0));
  3107. if (!dragX)
  3108. setSelX(0, xDim);
  3109. }
  3110. // the drag didn't pass the dist requirement
  3111. if (!dragX && !dragY) {
  3112. setSelX(0, 0);
  3113. setSelY(0, 0);
  3114. }
  3115. }
  3116. }
  3117. cursor.idx = idx;
  3118. cursor.left = mouseLeft1;
  3119. cursor.top = mouseTop1;
  3120. drag._x = dragX;
  3121. drag._y = dragY;
  3122. // if ts is present, means we're implicitly syncing own cursor
  3123. if (ts != null) {
  3124. // this is not technically a "mousemove" event, since it's debounced, rename to setCursor?
  3125. // since this is internal, we can tweak it later
  3126. sync.pub(mousemove, self, mouseLeft1, mouseTop1, xDim, yDim, idx);
  3127. if (cursorFocus) {
  3128. let o = syncOpts.setSeries;
  3129. let p = focus.prox;
  3130. if (focusedSeries == null) {
  3131. if (closestDist <= p)
  3132. setSeries(closestSeries, FOCUS_TRUE, o);
  3133. }
  3134. else {
  3135. if (closestDist > p)
  3136. setSeries(null, FOCUS_TRUE, o);
  3137. else if (closestSeries != focusedSeries)
  3138. setSeries(closestSeries, FOCUS_TRUE, o);
  3139. }
  3140. }
  3141. }
  3142. ready && fire("setCursor");
  3143. }
  3144. let rect = null;
  3145. function syncRect() {
  3146. rect = over.getBoundingClientRect();
  3147. }
  3148. function mouseMove(e, src, _l, _t, _w, _h, _i) {
  3149. if (cursor._lock)
  3150. return;
  3151. cacheMouse(e, src, _l, _t, _w, _h, _i, false, e != null);
  3152. if (e != null)
  3153. updateCursor(1);
  3154. else
  3155. updateCursor(null, src);
  3156. }
  3157. function cacheMouse(e, src, _l, _t, _w, _h, _i, initial, snap) {
  3158. if (e != null) {
  3159. _l = e.clientX - rect.left;
  3160. _t = e.clientY - rect.top;
  3161. }
  3162. else {
  3163. if (_l < 0 || _t < 0) {
  3164. mouseLeft1 = -10;
  3165. mouseTop1 = -10;
  3166. return;
  3167. }
  3168. let xDim = plotWidCss,
  3169. yDim = plotHgtCss,
  3170. _xDim = _w,
  3171. _yDim = _h,
  3172. _xPos = _l,
  3173. _yPos = _t;
  3174. if (scaleX.ori == 1) {
  3175. xDim = plotHgtCss;
  3176. yDim = plotWidCss;
  3177. }
  3178. let [xKey, yKey] = syncOpts.scales;
  3179. if (src.scales[xKey].ori == 1) {
  3180. _xDim = _h;
  3181. _yDim = _w;
  3182. _xPos = _t;
  3183. _yPos = _l;
  3184. }
  3185. if (xKey != null)
  3186. _l = getPos(src.posToVal(_xPos, xKey), scales[xKey], xDim, 0);
  3187. else
  3188. _l = xDim * (_xPos/_xDim);
  3189. if (yKey != null)
  3190. _t = getPos(src.posToVal(_yPos, yKey), scales[yKey], yDim, 0);
  3191. else
  3192. _t = yDim * (_yPos/_yDim);
  3193. if (scaleX.ori == 1) {
  3194. let __l = _l;
  3195. _l = _t;
  3196. _t = __l;
  3197. }
  3198. }
  3199. if (snap) {
  3200. if (_l <= 1 || _l >= plotWidCss - 1)
  3201. _l = incrRound(_l, plotWidCss);
  3202. if (_t <= 1 || _t >= plotHgtCss - 1)
  3203. _t = incrRound(_t, plotHgtCss);
  3204. }
  3205. if (initial) {
  3206. rawMouseLeft0 = _l;
  3207. rawMouseTop0 = _t;
  3208. [mouseLeft0, mouseTop0] = cursor.move(self, _l, _t);
  3209. }
  3210. else {
  3211. mouseLeft1 = _l;
  3212. mouseTop1 = _t;
  3213. }
  3214. }
  3215. function hideSelect() {
  3216. setSelect({
  3217. width: 0,
  3218. height: 0,
  3219. }, false);
  3220. }
  3221. function mouseDown(e, src, _l, _t, _w, _h, _i) {
  3222. dragging = true;
  3223. dragX = dragY = drag._x = drag._y = false;
  3224. cacheMouse(e, src, _l, _t, _w, _h, _i, true, false);
  3225. if (e != null) {
  3226. onMouse(mouseup, doc, mouseUp);
  3227. sync.pub(mousedown, self, mouseLeft0, mouseTop0, plotWidCss, plotHgtCss, null);
  3228. }
  3229. }
  3230. function mouseUp(e, src, _l, _t, _w, _h, _i) {
  3231. dragging = drag._x = drag._y = false;
  3232. cacheMouse(e, src, _l, _t, _w, _h, _i, false, true);
  3233. let { left, top, width, height } = select;
  3234. let hasSelect = width > 0 || height > 0;
  3235. hasSelect && setSelect(select);
  3236. if (drag.setScale && hasSelect) {
  3237. // if (syncKey != null) {
  3238. // dragX = drag.x;
  3239. // dragY = drag.y;
  3240. // }
  3241. let xOff = left,
  3242. xDim = width,
  3243. yOff = top,
  3244. yDim = height;
  3245. if (scaleX.ori == 1) {
  3246. xOff = top,
  3247. xDim = height,
  3248. yOff = left,
  3249. yDim = width;
  3250. }
  3251. if (dragX) {
  3252. _setScale(xScaleKey,
  3253. posToVal(xOff, xScaleKey),
  3254. posToVal(xOff + xDim, xScaleKey)
  3255. );
  3256. }
  3257. if (dragY) {
  3258. for (let k in scales) {
  3259. let sc = scales[k];
  3260. if (k != xScaleKey && sc.from == null && sc.min != inf) {
  3261. _setScale(k,
  3262. posToVal(yOff + yDim, k),
  3263. posToVal(yOff, k)
  3264. );
  3265. }
  3266. }
  3267. }
  3268. hideSelect();
  3269. }
  3270. else if (cursor.lock) {
  3271. cursor._lock = !cursor._lock;
  3272. if (!cursor._lock)
  3273. updateCursor();
  3274. }
  3275. if (e != null) {
  3276. offMouse(mouseup, doc);
  3277. sync.pub(mouseup, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, null);
  3278. }
  3279. }
  3280. function mouseLeave(e, src, _l, _t, _w, _h, _i) {
  3281. if (!cursor._lock) {
  3282. let _dragging = dragging;
  3283. if (dragging) {
  3284. // handle case when mousemove aren't fired all the way to edges by browser
  3285. let snapH = true;
  3286. let snapV = true;
  3287. let snapProx = 10;
  3288. let dragH, dragV;
  3289. if (scaleX.ori == 0) {
  3290. dragH = dragX;
  3291. dragV = dragY;
  3292. }
  3293. else {
  3294. dragH = dragY;
  3295. dragV = dragX;
  3296. }
  3297. if (dragH && dragV) {
  3298. // maybe omni corner snap
  3299. snapH = mouseLeft1 <= snapProx || mouseLeft1 >= plotWidCss - snapProx;
  3300. snapV = mouseTop1 <= snapProx || mouseTop1 >= plotHgtCss - snapProx;
  3301. }
  3302. if (dragH && snapH)
  3303. mouseLeft1 = mouseLeft1 < mouseLeft0 ? 0 : plotWidCss;
  3304. if (dragV && snapV)
  3305. mouseTop1 = mouseTop1 < mouseTop0 ? 0 : plotHgtCss;
  3306. updateCursor(1);
  3307. dragging = false;
  3308. }
  3309. mouseLeft1 = -10;
  3310. mouseTop1 = -10;
  3311. // passing a non-null timestamp to force sync/mousemove event
  3312. updateCursor(1);
  3313. if (_dragging)
  3314. dragging = _dragging;
  3315. }
  3316. }
  3317. function dblClick(e, src, _l, _t, _w, _h, _i) {
  3318. autoScaleX();
  3319. hideSelect();
  3320. if (e != null)
  3321. sync.pub(dblclick, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, null);
  3322. }
  3323. // internal pub/sub
  3324. const events = {};
  3325. events.mousedown = mouseDown;
  3326. events.mousemove = mouseMove;
  3327. events.mouseup = mouseUp;
  3328. events.dblclick = dblClick;
  3329. events["setSeries"] = (e, src, idx, opts) => {
  3330. setSeries(idx, opts);
  3331. };
  3332. let deb;
  3333. if ( cursor.show) {
  3334. onMouse(mousedown, over, mouseDown);
  3335. onMouse(mousemove, over, mouseMove);
  3336. onMouse(mouseenter, over, syncRect);
  3337. onMouse(mouseleave, over, mouseLeave);
  3338. onMouse(dblclick, over, dblClick);
  3339. deb = debounce(syncRect, 100);
  3340. on(resize, win, deb);
  3341. on(scroll, win, deb);
  3342. self.syncRect = syncRect;
  3343. }
  3344. // external on/off
  3345. const hooks = self.hooks = opts.hooks || {};
  3346. function fire(evName, a1, a2) {
  3347. if (evName in hooks) {
  3348. hooks[evName].forEach(fn => {
  3349. fn.call(null, self, a1, a2);
  3350. });
  3351. }
  3352. }
  3353. (opts.plugins || []).forEach(p => {
  3354. for (let evName in p.hooks)
  3355. hooks[evName] = (hooks[evName] || []).concat(p.hooks[evName]);
  3356. });
  3357. const syncOpts = assign({
  3358. key: null,
  3359. setSeries: false,
  3360. scales: [xScaleKey, null]
  3361. }, cursor.sync);
  3362. const syncKey = syncOpts.key;
  3363. const sync = (syncKey != null ? (syncs[syncKey] = syncs[syncKey] || _sync()) : _sync());
  3364. sync.sub(self);
  3365. function pub(type, src, x, y, w, h, i) {
  3366. events[type](null, src, x, y, w, h, i);
  3367. }
  3368. (self.pub = pub);
  3369. function destroy() {
  3370. sync.unsub(self);
  3371. off(resize, win, deb);
  3372. off(scroll, win, deb);
  3373. root.remove();
  3374. fire("destroy");
  3375. }
  3376. self.destroy = destroy;
  3377. function _init() {
  3378. fire("init", opts, data);
  3379. setData(data || opts.data, false);
  3380. if (pendScales[xScaleKey])
  3381. setScale(xScaleKey, pendScales[xScaleKey]);
  3382. else
  3383. autoScaleX();
  3384. _setSize(opts.width, opts.height);
  3385. setSelect(select, false);
  3386. }
  3387. if (then) {
  3388. if (then instanceof HTMLElement) {
  3389. then.appendChild(root);
  3390. _init();
  3391. }
  3392. else
  3393. then(self, _init);
  3394. }
  3395. else
  3396. _init();
  3397. return self;
  3398. }
  3399. uPlot.assign = assign;
  3400. uPlot.fmtNum = fmtNum;
  3401. uPlot.rangeNum = rangeNum;
  3402. uPlot.rangeLog = rangeLog;
  3403. uPlot.orient = orient;
  3404. {
  3405. uPlot.join = join;
  3406. }
  3407. {
  3408. uPlot.fmtDate = fmtDate;
  3409. uPlot.tzDate = tzDate;
  3410. }
  3411. {
  3412. uPlot.addGap = addGap;
  3413. uPlot.clipGaps = clipGaps;
  3414. let paths = uPlot.paths = {};
  3415. (paths.linear = linear);
  3416. (paths.spline = spline);
  3417. (paths.stepped = stepped);
  3418. (paths.bars = bars);
  3419. }
  3420. export default uPlot;