`),t=a.shift().split(" ");s.transaction(()=>a.forEach((o,i)=>{const n=o.split(" ");n.length==t.length&&n.forEach((c,p)=>{c!=""&&(z.test(c)&&(c=parseFloat(c)),s.setCell("cars",i,t[p],c))})}))},V=()=>{const[s,a]=y(["Manufacturer"]),[t,o]=y(["MPG","Horsepower"]),[i,n]=y("Average"),[c,p]=y(!1),h=J(s,t,i);return b(C,{children:[b("aside",{children:[e("b",{children:"Dimensions"}),e(S,{options:F,selected:s,onOptionsChange:a}),e("hr",{}),e("b",{children:"Measures"}),e(S,{options:Y,selected:t,onOptionsChange:o}),e("hr",{}),e("b",{children:"Aggregate"}),e(S,{options:Object.keys(I),selected:[i],onOptionsChange:n,multiple:!1}),e("hr",{}),e("input",{id:"showTable",type:"checkbox",checked:c,onChange:A(({target:d})=>p(d.checked),[])}),e("label",{for:"showTable",children:"Show table"}),e("br",{}),e("small",{children:e("a",{href:"https://github.com/vega/vega-datasets/blob/next/data/cars.json",children:"Source"})})]}),c?e(K,{queryId:h,columns:[...s,...t]}):e(ee,{queryId:h,dimensions:s,measures:t})]})},J=(s,a,t)=>R(()=>(q().setQueryDefinition("query","cars",({select:o,group:i})=>{s.forEach(n=>o(n)),a.forEach(n=>{o(n),i(n,I[t])})}),"query"),[s,a,t]),K=({queryId:s,columns:a})=>e(Q,{queryId:s,sortOnClick:!0,paginator:!0,limit:10,idColumn:!1,customCells:R(()=>Object.fromEntries(a.map(t=>[t,{component:Z}])),[...a])}),Z=({queryId:s,rowId:a,cellId:t})=>{const o=H(s,a,t);return e("span",{className:t,children:Number.isFinite(o)?$(o):o})},ee=({queryId:s,dimensions:a,measures:t})=>{const o=O(null),[{width:i=0,height:n=0},c]=y({});G(()=>{const r=new ResizeObserver(([{contentRect:u}])=>c(u));return r.observe(o.current),()=>r.disconnect()},[o]);const[p,h,d]=te(s,a,t),[l,m,x,v,E]=se(i,n,p,d);return i==0||n==0?e("main",{ref:o}):e("main",{ref:o,children:b("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:`0 0 ${i} ${n}`,children:[e("path",{d:`M${l(0)} ${m(0)}H${l(E)}`}),e(C,{children:x.map((r,u)=>{const f=l(u)-w/2,g=m(0)+M;return r&&e("text",{transform:`translate(${f} ${g}) rotate(90)`,children:r},u)})}),e(C,{children:v.map(r=>{const u=l(0)-M,f=m(r)+w/2;return e("text",{transform:`translate(${u} ${f})`,"text-anchor":"end",children:r},r)})}),h.map((r,u)=>b("g",{className:t[u],children:[e("path",{d:r.map((f,g)=>`${g==0?"M":"L"}${l(g)} ${m(f)}`).join("")}),r.map((f,g)=>b(C,{children:[e("circle",{cx:l(g),cy:m(f),r:M}),b("text",{x:l(g),y:m(f)-w,children:[p[g]," ",t[u],": ",$(f)]})]}))]},u))]})})},te=(s,a,t)=>{const o=j(s),i=_(s,t[0]??void 0);return R(()=>{const n=[1],c=[],p=t.map(()=>[]);return i.forEach(h=>{const d=o[h];c.push(a.map(l=>d[l]).join(", ")),t.forEach((l,m)=>{n.push(d[l]),p[m].push(d[l])})}),[c,p,Math.max(...n)]},[o,i,t,a])},se=(s,a,t,o)=>R(()=>{const i=a/4,n=s/15,c=s-n-M,p=a-i-M,h=t.length-1,d=t.map((r,u)=>u%Math.ceil(w*h/c)==0?r:null),l=Math.pow(10,Math.floor(Math.log10(o))),m=Math.ceil(o/l)*l,x=Math.ceil(o/l),v=x<=2?5:x<=5?2:1,E=Array(v*x+1).fill().map((r,u)=>u*l/v);return[r=>n+r*c/h,r=>M+p-r*p/m,d,E,h,m]},[s,a,t,o]),S=({options:s,selected:a,onOptionsChange:t,multiple:o=!0})=>{const i=A(({target:n})=>t(o?[...n.selectedOptions].map(c=>c.value):n.value),[t]);return e("select",{multiple:o,size:s.length,onChange:i,children:s.map(n=>e("option",{value:n,selected:a.includes(n),className:n,children:n}))})};</script></html>"></iframe><p>In this demo, we build an app that showcases the query capabilities of TinyBase v2.0, grouping and sorting dimensional data for lightweight analytical usage.</p><p>The data from this demo is derived from <code>cars.json</code> in the <a href="https://github.com/vega/vega-datasets">Vega datasets</a> - thank you <a href="https://idl.cs.washington.edu/">UW Interactive Data Lab</a>!</p><h3 id="an-overview-of-the-data">An Overview Of The Data</h3><p>Before looking at code, let's familiarize ourselves with the data used in this application.</p><p>The raw data is loaded from a TSV file into one single <a href="/api/store/type-aliases/store/table/"><code>Table</code></a> object: <code>cars</code>, and comprises almost 400 records of cars made in the 1970s and 1980s.</p><p>For each, the data includes the manufacturer, the car name, year, and region. These <a href="/api/store/type-aliases/store/cell/"><code>Cell</code></a> values are 'dimensions' with which the data can be grouped.</p><p>Each record also includes a number of quantitative fields, including the car's miles-per-gallon (MPG), the number of cylinders, their displacement, its horsepower, weight, and acceleration. These <a href="/api/store/type-aliases/store/cell/"><code>Cell</code></a> values are 'measures' which can be aggregated together - in this basic app, to find their average, maximum, or minimum.</p><p>The app is oriented around one single query. As the user picks different dimensions or measures in the app's sidebar, that query is re-run and the results (either in graphical or tabular form) reactively update immediately.</p><h3 id="boilerplate">Boilerplate</h3><p>First, we create the import aliases for TinyBase and React modules we'll need:</p><pre><code><span class="tag"><span class="tag"><span class="punctuation"><</span>script</span> <span class="attr-name">type</span><span class="attr-value"><span class="punctuation">=</span><span class="punctuation">"</span>importmap<span class="punctuation">"</span></span><span class="punctuation">></span></span><span class="script"><span class="language-javascript">
0 commit comments