[e.toLowerCase(),t]))}function au(n,e,t){var r=D.exec(e.slice(t,t+1));return r?(n.w=+r[0],t+r[0].length):-1}function ou(n,e,t){var r=D.exec(e.slice(t,t+1));return r?(n.u=+r[0],t+r[0].length):-1}function lu(n,e,t){var r=D.exec(e.slice(t,t+2));return r?(n.U=+r[0],t+r[0].length):-1}function cu(n,e,t){var r=D.exec(e.slice(t,t+2));return r?(n.V=+r[0],t+r[0].length):-1}function fu(n,e,t){var r=D.exec(e.slice(t,t+2));return r?(n.W=+r[0],t+r[0].length):-1}function Mt(n,e,t){var r=D.exec(e.slice(t,t+4));return r?(n.y=+r[0],t+r[0].length):-1}function wt(n,e,t){var r=D.exec(e.slice(t,t+2));return r?(n.y=+r[0]+(+r[0]>68?1900:2e3),t+r[0].length):-1}function su(n,e,t){var r=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(e.slice(t,t+6));return r?(n.Z=r[1]?0:-(r[2]+(r[3]||"00")),t+r[0].length):-1}function hu(n,e,t){var r=D.exec(e.slice(t,t+1));return r?(n.q=r[0]*3-3,t+r[0].length):-1}function gu(n,e,t){var r=D.exec(e.slice(t,t+2));return r?(n.m=r[0]-1,t+r[0].length):-1}function vt(n,e,t){var r=D.exec(e.slice(t,t+2));return r?(n.d=+r[0],t+r[0].length):-1}function mu(n,e,t){var r=D.exec(e.slice(t,t+3));return r?(n.m=0,n.d=+r[0],t+r[0].length):-1}function Tt(n,e,t){var r=D.exec(e.slice(t,t+2));return r?(n.H=+r[0],t+r[0].length):-1}function du(n,e,t){var r=D.exec(e.slice(t,t+2));return r?(n.M=+r[0],t+r[0].length):-1}function pu(n,e,t){var r=D.exec(e.slice(t,t+2));return r?(n.S=+r[0],t+r[0].length):-1}function yu(n,e,t){var r=D.exec(e.slice(t,t+3));return r?(n.L=+r[0],t+r[0].length):-1}function xu(n,e,t){var r=D.exec(e.slice(t,t+6));return r?(n.L=Math.floor(r[0]/1e3),t+r[0].length):-1}function bu(n,e,t){var r=ru.exec(e.slice(t,t+1));return r?t+r[0].length:-1}function Mu(n,e,t){var r=D.exec(e.slice(t));return r?(n.Q=+r[0],t+r[0].length):-1}function wu(n,e,t){var r=D.exec(e.slice(t));return r?(n.s=+r[0],t+r[0].length):-1}function kt(n,e){return v(n.getDate(),e,2)}function vu(n,e){return v(n.getHours(),e,2)}function Tu(n,e){return v(n.getHours()%12||12,e,2)}function ku(n,e){return v(1+Fn.count(G(n),n),e,3)}function fr(n,e){return v(n.getMilliseconds(),e,3)}function Cu(n,e){return fr(n,e)+"000"}function Su(n,e){return v(n.getMonth()+1,e,2)}function Nu(n,e){return v(n.getMinutes(),e,2)}function _u(n,e){return v(n.getSeconds(),e,2)}function Uu(n){var e=n.getDay();return e===0?7:e}function Du(n,e){return v(qn.count(G(n)-1,n),e,2)}function sr(n){var e=n.getDay();return e>=4||e===0?_n(n):_n.ceil(n)}function Fu(n,e){return n=sr(n),v(_n.count(G(n),n)+(G(n).getDay()===4),e,2)}function Lu(n){return n.getDay()}function Au(n,e){return v(we.count(G(n)-1,n),e,2)}function Hu(n,e){return v(n.getFullYear()%100,e,2)}function Pu(n,e){return n=sr(n),v(n.getFullYear()%100,e,2)}function Yu(n,e){return v(n.getFullYear()%1e4,e,4)}function Iu(n,e){var t=n.getDay();return n=t>=4||t===0?_n(n):_n.ceil(n),v(n.getFullYear()%1e4,e,4)}function Ou(n){var e=n.getTimezoneOffset();return(e>0?"-":(e*=-1,"+"))+v(e/60|0,"0",2)+v(e%60,"0",2)}function Ct(n,e){return v(n.getUTCDate(),e,2)}function Eu(n,e){return v(n.getUTCHours(),e,2)}function $u(n,e){return v(n.getUTCHours()%12||12,e,2)}function Ru(n,e){return v(1+Ln.count(J(n),n),e,3)}function hr(n,e){return v(n.getUTCMilliseconds(),e,3)}function Bu(n,e){return hr(n,e)+"000"}function Wu(n,e){return v(n.getUTCMonth()+1,e,2)}function zu(n,e){return v(n.getUTCMinutes(),e,2)}function qu(n,e){return v(n.getUTCSeconds(),e,2)}function ju(n){var e=n.getUTCDay();return e===0?7:e}function Zu(n,e){return v(jn.count(J(n)-1,n),e,2)}function gr(n){var e=n.getUTCDay();return e>=4||e===0?Un(n):Un.ceil(n)}function Vu(n,e){return n=gr(n),v(Un.count(J(n),n)+(J(n).getUTCDay()===4),e,2)}function Qu(n){return n.getUTCDay()}function Xu(n,e){return v(ve.count(J(n)-1,n),e,2)}function Gu(n,e){return v(n.getUTCFullYear()%100,e,2)}function Ju(n,e){return n=gr(n),v(n.getUTCFullYear()%100,e,2)}function Ku(n,e){return v(n.getUTCFullYear()%1e4,e,4)}function na(n,e){var t=n.getUTCDay();return n=t>=4||t===0?Un(n):Un.ceil(n),v(n.getUTCFullYear()%1e4,e,4)}function ea(){return"+0000"}function St(){return"%"}function Nt(n){return+n}function _t(n){return Math.floor(+n/1e3)}var On,mr,dr;ta({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});function ta(n){return On=tu(n),mr=On.format,dr=On.utcFormat,On}function ra(n){return new Date(n)}function ia(n){return n instanceof Date?+n:+new Date(+n)}function qe(n,e,t,r,i,u,a,o,l,c){var f=Kt(),s=f.invert,g=f.domain,x=c(".%L"),y=c(":%S"),k=c("%I:%M"),C=c("%I %p"),S=c("%a %d"),R=c("%b %d"),N=c("%B"),H=c("%Y");function L(p){return(l(p)e.has(r));return function(i,u){return typeof u!="undefined"&&t.forEach(a=>{ka[a](i,u)}),i}}const Ca=en("domain","range","reverse","align","padding","round");function Sa(n){return Ca(Pt(),n)}const Na=en("domain","range","reverse","clamp","interpolate","nice","round","zero");function _a(n){return Na(tr(),n)}const Ua=en("domain","range","reverse","clamp","interpolate","nice","round");function Da(n){return Ua(ua(),n)}const Fa=en("domain","range","reverse","base","clamp","interpolate","nice","round");function La(n){return Fa(ir(),n)}const Aa=en("domain","range","reverse","unknown");function Ha(n){return Aa(Ue(),n)}const Pa=en("domain","range","reverse");function Ya(n){return Pa(ur(),n)}const Ia=en("domain","range","reverse");function Oa(n){return Ia(ar(),n)}const Ea=en("domain","range","reverse","clamp","interpolate","nice","round");function $a(n){return Ea(aa(),n)}const Te=Math.PI,ke=2*Te,un=1e-6,Ra=ke-un;function Ce(){this._x0=this._y0=this._x1=this._y1=null,this._=""}function pr(){return new Ce}Ce.prototype=pr.prototype={constructor:Ce,moveTo:function(n,e){this._+="M"+(this._x0=this._x1=+n)+","+(this._y0=this._y1=+e)},closePath:function(){this._x1!==null&&(this._x1=this._x0,this._y1=this._y0,this._+="Z")},lineTo:function(n,e){this._+="L"+(this._x1=+n)+","+(this._y1=+e)},quadraticCurveTo:function(n,e,t,r){this._+="Q"+ +n+","+ +e+","+(this._x1=+t)+","+(this._y1=+r)},bezierCurveTo:function(n,e,t,r,i,u){this._+="C"+ +n+","+ +e+","+ +t+","+ +r+","+(this._x1=+i)+","+(this._y1=+u)},arcTo:function(n,e,t,r,i){n=+n,e=+e,t=+t,r=+r,i=+i;var u=this._x1,a=this._y1,o=t-n,l=r-e,c=u-n,f=a-e,s=c*c+f*f;if(i<0)throw new Error("negative radius: "+i);if(this._x1===null)this._+="M"+(this._x1=n)+","+(this._y1=e);else if(s>un)if(!(Math.abs(f*o-l*c)>un)||!i)this._+="L"+(this._x1=n)+","+(this._y1=e);else{var g=t-u,x=r-a,y=o*o+l*l,k=g*g+x*x,C=Math.sqrt(y),S=Math.sqrt(s),R=i*Math.tan((Te-Math.acos((y+s-k)/(2*C*S)))/2),N=R/S,H=R/C;Math.abs(N-1)>un&&(this._+="L"+(n+N*c)+","+(e+N*f)),this._+="A"+i+","+i+",0,0,"+ +(f*g>c*x)+","+(this._x1=n+H*o)+","+(this._y1=e+H*l)}},arc:function(n,e,t,r,i,u){n=+n,e=+e,t=+t,u=!!u;var a=t*Math.cos(r),o=t*Math.sin(r),l=n+a,c=e+o,f=1^u,s=u?r-i:i-r;if(t<0)throw new Error("negative radius: "+t);this._x1===null?this._+="M"+l+","+c:(Math.abs(this._x1-l)>un||Math.abs(this._y1-c)>un)&&(this._+="L"+l+","+c),t&&(s<0&&(s=s%ke+ke),s>Ra?this._+="A"+t+","+t+",0,1,"+f+","+(n-a)+","+(e-o)+"A"+t+","+t+",0,1,"+f+","+(this._x1=l)+","+(this._y1=c):s>un&&(this._+="A"+t+","+t+",0,"+ +(s>=Te)+","+f+","+(this._x1=n+t*Math.cos(i))+","+(this._y1=e+t*Math.sin(i))))},rect:function(n,e,t,r){this._+="M"+(this._x0=this._x1=+n)+","+(this._y0=this._y1=+e)+"h"+ +t+"v"+ +r+"h"+-t+"Z"},toString:function(){return this._}};function mn(n){return function(){return n}}function Ba(n){return typeof n=="object"&&"length"in n?n:Array.from(n)}function yr(n){this._context=n}yr.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(n,e){switch(n=+n,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(n,e):this._context.moveTo(n,e);break;case 1:this._point=2;default:this._context.lineTo(n,e);break}}};function Wa(n){return new yr(n)}function za(n){return n[0]}function qa(n){return n[1]}function ja(n,e){var t=mn(!0),r=null,i=Wa,u=null;n=typeof n=="function"?n:n===void 0?za:mn(n),e=typeof e=="function"?e:e===void 0?qa:mn(e);function a(o){var l,c=(o=Ba(o)).length,f,s=!1,g;for(r==null&&(u=i(g=pr())),l=0;l<=c;++l)!(l(m.openBlock(),m.createElementBlock("rect",m.mergeProps({class:e.class},t.$attrs),null,16))}}),Va=["className","d","fill"],Qa=m.defineComponent({props:{data:null,fill:{default:"transparent"},className:null,defined:{type:Function,default:()=>!0},curve:null,x:null,y:null},setup(n){const e=n,t=m.computed(()=>{const r=e.x,i=e.y,u=e.defined,a=e.curve;return xr({x:r,y:i,defined:u,curve:a})});return(r,i)=>(m.openBlock(),m.createElementBlock("path",m.mergeProps({className:e.className,d:n.data?m.unref(t)(n.data)||"":m.unref(t)([])||"",fill:e.fill,strokeLinecap:"round"},r.$attrs),null,16,Va))}}),Xa=m.defineComponent({props:{class:null},setup(n){const e=n;return(t,r)=>(m.openBlock(),m.createElementBlock("circle",m.mergeProps({class:e.class},t.$attrs),null,16))}});function Ga({steps:n,scale:e}){const t=e.domain(),r=(t[t.length-1]-t[0])/(n-1),i=new Array(5);i[0]=t[0];for(let u=1;u({datum:t,index:r,text:`${e(t,r)}`,value:n(t)})}function je(n){return n&&typeof n=="object"&&"value"in n&&typeof n.value!="undefined"?n.value:n}function Se(n){return String(je(n))}const br=m.defineComponent({props:{flexDirection:{default:"row"},alignItems:{default:"center"},margin:{default:"0"},display:{default:"flex"}},setup(n){const e=n,t=m.computed(()=>{const{flexDirection:r,alignItems:i,margin:u,display:a}=e;return{flexDirection:r,alignItems:i,margin:u,display:a}});return(r,i)=>(m.openBlock(),m.createElementBlock("div",m.mergeProps({class:"vuenique-legend-item",style:m.unref(t)},r.$attrs),[m.renderSlot(r.$slots,"default")],16))}}),Mr=m.defineComponent({props:{align:{default:"left"},label:null,flex:{default:1},margin:{default:"5px 0"},children:null},setup(n){const e=n;return(t,r)=>(m.openBlock(),m.createElementBlock("text",m.mergeProps({className:"legendLabels"},t.$attrs,{style:{justifyContent:e.align,display:"flex",flex:e.flex,margin:e.margin}}),m.toDisplayString(e.label),17))}}),Ka={width:10,height:10},no=["width","height","fill"],eo=["v-bind"],to={width:10,height:10},ro=["r","fill"],wr=m.defineComponent({props:{label:null,itemIndex:null,margin:{default:5},fill:null,shape:{default:"rect"},width:{default:"10"},height:{default:"10"},radius:{default:"5"}},setup(n){const e=n,t=m.computed(()=>{const{fill:r,label:i}=e;return r(Pn({},i))});return(r,i)=>e.shape==="rect"?(m.openBlock(),m.createElementBlock("g",m.mergeProps({key:0,className:"legendShape"},r.$attrs),[(m.openBlock(),m.createElementBlock("svg",Ka,[m.createElementVNode("rect",{width:e.width,height:e.height,fill:m.unref(t)},null,8,no)]))],16)):(m.openBlock(),m.createElementBlock("g",{key:1,className:"legendShape","v-bind":r.$attrs},[(m.openBlock(),m.createElementBlock("svg",to,[m.createElementVNode("circle",{cx:5,cy:5,r:e.radius,fill:m.unref(t)},null,8,ro)]))],8,eo))}}),vr=m.defineComponent({props:{scale:null,style:{default:{display:"flex"}},domain:null,shapeWidth:{default:15},shapeHeight:{default:15},shapeMargin:{default:"2px 4px 2px 0"},labelAlign:{default:"left"},labelFlex:{default:"1"},labelMargin:{default:"0 4px"},itemMargin:{default:"0"},direction:{default:"column"},itemDirection:{default:"row"},fill:{type:Function,default:Se},size:{type:Function,default:Se},shape:{default:"circle"},shapeStyle:null,labelFormat:{type:Function,default:je},labelTransform:{type:Function,default:Ja},legendLabelProps:null},setup(n){const e=n,t=e.domain||[],r=m.computed(()=>{const{scale:a,labelFormat:o,labelTransform:l}=e,c=l({scale:a,labelFormat:o});return t.map(c)}),i=m.computed(()=>r.value.map((a,o)=>{const l=`legend-${a.text}-${o}`,c=t[o];return{key:l,item:c,itemIndex:o,label:a}})),u=m.computed(()=>{const{style:a,direction:o}=e;return Vn(Pn({},a),{direction:o})});return(a,o)=>(m.openBlock(),m.createElementBlock("g",{class:"vuenique-legend",style:m.normalizeStyle(m.unref(u).value)},[(m.openBlock(!0),m.createElementBlock(m.Fragment,null,m.renderList(m.unref(i),l=>(m.openBlock(),m.createBlock(br,m.mergeProps({key:l.key,margin:e.itemMargin,flexDirection:e.itemDirection,label:l.label},a.$attrs),{default:m.withCtx(()=>[m.createVNode(wr,{item:l.item,itemIndex:l.itemIndex,shape:e.shape,width:e.shapeWidth,height:e.shapeHeight,margin:e.shapeMargin,label:l.label,fill:e.fill,size:e.size,shapeStyle:e.shapeStyle},null,8,["item","itemIndex","shape","width","height","margin","label","fill","size","shapeStyle"]),m.createVNode(Mr,{label:l.label.text,flex:e.labelFlex,margin:e.labelMargin,align:e.labelAlign,legendLabelProps:e.legendLabelProps},null,8,["label","flex","margin","align","legendLabelProps"])]),_:2},1040,["margin","flexDirection","label"]))),128))],4))}}),io=m.defineComponent({props:{scale:null,style:null,domain:null,shapeWidth:null,shapeHeight:null,shapeMargin:null,labelAlign:null,labelFlex:null,labelMargin:null,itemMargin:null,direction:null,itemDirection:null,fill:null,size:null,shape:null,shapeStyle:null,labelFormat:null,labelTransform:null,legendLabelProps:null,steps:{default:5}},setup(n){const e=n,t=m.computed(()=>{if(e.domain)return e.domain;const{steps:r,scale:i}=e;return Ga({steps:r,scale:i})});return(r,i)=>(m.openBlock(),m.createBlock(vr,m.mergeProps(r.$attrs,{scale:e.scale,domain:m.unref(t)}),null,16,["scale","domain"]))}}),uo=["transform"],ao={inheritAttrs:!0},oo=m.defineComponent(Vn(Pn({},ao),{props:{top:{default:0},left:{default:0},transform:null,class:null},setup(n){const e=n;return(t,r)=>(m.openBlock(),m.createElementBlock("g",m.mergeProps({class:e.class,transform:e.transform?e.transform:`translate(${e.left}, ${e.top})`},t.$attrs),[m.renderSlot(t.$slots,"default")],16,uo))}}));exports.Bar=Za;exports.Circle=Xa;exports.Group=oo;exports.Legend=vr;exports.LegendItem=br;exports.LegendLabel=Mr;exports.LegendLinear=io;exports.LegendShape=wr;exports.LinePath=Qa;exports.line=xr;exports.scaleBand=Sa;exports.scaleLinear=_a;exports.scaleLog=La;exports.scaleOrdinal=Ha;exports.scaleQuantile=Ya;exports.scaleThreshold=Oa;exports.scaleTime=Da;exports.scaleUtc=$a;exports.valueOrIdentity=je;exports.valueOrIdentityString=Se;
2 |
--------------------------------------------------------------------------------
/packages/index.js:
--------------------------------------------------------------------------------
1 | export * from "./vuenique-scale/index.ts";
2 | export * from "./vuenique-shape/index.ts";
3 | export * from "./vuenique-legend/index.ts";
4 | export * from "./vuenique-group/index.ts";
5 |
--------------------------------------------------------------------------------
/packages/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@vueniquejs/vuenique",
3 | "version": "1.0.3",
4 | "description": "Low level visualization component library built for Vue",
5 | "main": "build/index.cjs.js",
6 | "module": "build/index.es.js",
7 | "scripts": {
8 | "test": "npx vitest",
9 | "build": "npx tsc"
10 | },
11 | "dependencies": {
12 | "d3-array": "^3.1.3",
13 | "d3-interpolate": "^3.0.1",
14 | "d3-scale": "^4.0.2",
15 | "d3-shape": "^3.1.0",
16 | "d3-time": "^3.0.0",
17 | "timezone-mock": "^1.3.1",
18 | "vue": "^3.2.31"
19 | },
20 | "devDependencies": {
21 | "@rushstack/eslint-patch": "^1.1.0",
22 | "@types/d3-array": "^3.0.2",
23 | "@types/d3-scale": "^4.0.2",
24 | "@types/d3-shape": "^3.0.2",
25 | "@types/jsdom": "^16.2.14",
26 | "@types/node": "^16.11.25",
27 | "@vitejs/plugin-vue": "^2.2.2",
28 | "@vitejs/plugin-vue-jsx": "^1.3.7",
29 | "@vue/eslint-config-prettier": "^7.0.0",
30 | "@vue/eslint-config-typescript": "^10.0.0",
31 | "@vue/test-utils": "^2.0.0-rc.18",
32 | "@vue/tsconfig": "^0.1.3",
33 | "cypress": "^9.5.0",
34 | "eslint": "^8.5.0",
35 | "eslint-plugin-cypress": "^2.12.1",
36 | "eslint-plugin-vue": "^8.2.0",
37 | "jsdom": "^19.0.0",
38 | "prettier": "^2.5.1",
39 | "start-server-and-test": "^1.14.0",
40 | "typescript": "~4.5.5",
41 | "vite": "^2.8.4",
42 | "vitest": "^0.5.0",
43 | "vue-tsc": "^0.31.4"
44 | },
45 | "repository": {
46 | "type": "git",
47 | "url": "git+https://github.com/oslabs-beta/Vuenique.git"
48 | },
49 | "keywords": [
50 | "vuenique",
51 | "vue",
52 | "d3",
53 | "visualizations",
54 | "charts"
55 | ],
56 | "author": "@jamesma1",
57 | "contributors": [
58 | "James Ma",
59 | "Trevor Gray",
60 | "Miaowen Zeng",
61 | "Alex Corlin",
62 | "Alex Haile"
63 | ],
64 | "license": "MIT",
65 | "bugs": {
66 | "url": "https://github.com/oslabs-beta/Vuenique/issues"
67 | },
68 | "homepage": "https://vuenique.net/"
69 | }
70 |
--------------------------------------------------------------------------------
/packages/vuenique-group/Group.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
26 |
27 |
28 |
29 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/packages/vuenique-group/__tests__/Group.spec.ts:
--------------------------------------------------------------------------------
1 | import {ref} from 'vue';
2 | import { describe, it, expect, test } from 'vitest';
3 | import { mount } from '@vue/test-utils';
4 |
5 | import Group from "../Group.vue"
6 |
7 | const groupWrapper = (props = {}, attrs = {}) => mount(Group, {
8 | shallow: true,
9 | propsData: {
10 | ...props,
11 | },
12 | attrs: {
13 | ...attrs,
14 | }
15 | });
16 |
17 | describe(' component', () => {
18 |
19 | test('should be defined', () => {
20 | expect(Group).toBeDefined();
21 | });
22 |
23 | test('should have default props top=0 and left=0', () => {
24 | const groupWrapper1 = groupWrapper();
25 | expect(groupWrapper1.find('g').attributes().transform).toEqual('translate(0, 0)');
26 | });
27 |
28 | test('should setprops top, left, and class', () => {
29 | const groupWrapper2 = groupWrapper({ class : 'test', left: 4, top: 5 });
30 | expect(groupWrapper2.find('g').attributes().transform).toEqual('translate(4, 5)');
31 | expect(groupWrapper2.props('class')).toEqual('test');
32 | });
33 |
34 | test('should set restProps', () => {
35 | const groupWrapper3 = groupWrapper({clipPath: 'url(#vuenique)', stroke: 'orange'});
36 | expect(groupWrapper3.vm.$attrs.clipPath).toEqual('url(#vuenique)');
37 | expect(groupWrapper3.vm.$attrs.stroke).toEqual('orange');
38 | });
39 |
40 | });
41 |
--------------------------------------------------------------------------------
/packages/vuenique-group/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Group } from "./Group.vue";
2 |
--------------------------------------------------------------------------------
/packages/vuenique-legend/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./utility/defaultDomain";
2 | export * from "./utility/identity";
3 | export * from "./utility/labelTransformFactory";
4 | export * from "./utility/valueOrIdentity";
5 |
6 | export { default as Legend } from "./legends/Legend/index.vue";
7 | export { default as LegendItem } from "./legends/Legend/LegendItem.vue";
8 | export { default as LegendLabel } from "./legends/Legend/LegendLabel.vue";
9 | export { default as LegendShape } from "./legends/Legend/LegendShape.vue";
10 | export { default as LegendLinear } from "./legends/Linear.vue";
11 |
--------------------------------------------------------------------------------
/packages/vuenique-legend/legends/Legend/LegendItem.vue:
--------------------------------------------------------------------------------
1 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/packages/vuenique-legend/legends/Legend/LegendLabel.vue:
--------------------------------------------------------------------------------
1 |
20 |
21 |
31 | {{ legendLabelProps.label }}
32 |
33 |
34 |
--------------------------------------------------------------------------------
/packages/vuenique-legend/legends/Legend/LegendShape.vue:
--------------------------------------------------------------------------------
1 |
30 |
31 |
32 |
37 |
38 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/packages/vuenique-legend/legends/Legend/Threshold.vue:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Vuenique/f3dd4b71c12f3861001122bf53f34e9fbb2d60a7/packages/vuenique-legend/legends/Legend/Threshold.vue
--------------------------------------------------------------------------------
/packages/vuenique-legend/legends/Legend/index.vue:
--------------------------------------------------------------------------------
1 |
120 |
121 |
122 |
123 |
131 |
143 |
150 |
151 |
152 |
153 |
154 |
155 |
--------------------------------------------------------------------------------
/packages/vuenique-legend/legends/Linear.vue:
--------------------------------------------------------------------------------
1 |
63 |
64 |
69 |
70 |
--------------------------------------------------------------------------------
/packages/vuenique-legend/legends/Ordinal.vue:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Vuenique/f3dd4b71c12f3861001122bf53f34e9fbb2d60a7/packages/vuenique-legend/legends/Ordinal.vue
--------------------------------------------------------------------------------
/packages/vuenique-legend/legends/Quantile.vue:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Vuenique/f3dd4b71c12f3861001122bf53f34e9fbb2d60a7/packages/vuenique-legend/legends/Quantile.vue
--------------------------------------------------------------------------------
/packages/vuenique-legend/legends/Size.vue:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Vuenique/f3dd4b71c12f3861001122bf53f34e9fbb2d60a7/packages/vuenique-legend/legends/Size.vue
--------------------------------------------------------------------------------
/packages/vuenique-legend/types/index.ts:
--------------------------------------------------------------------------------
1 | import type * as Vue from "vue";
2 |
3 | // legend
4 | export type LegendProps = {
5 | // name for legend wrapper/container
6 | className?: string;
7 | // styles for legend container
8 | style?: any;
9 | // domain of legend
10 | domain?: any[];
11 | // legend width
12 | shapeWidth?: string | number;
13 | // legend height
14 | shapeHeight?: string | number;
15 | // legend margin
16 | shapeMargin?: string | number;
17 | // alignment of legend labels
18 | labelAlign?: string;
19 | // scale function
20 | scale: any;
21 | // flex-box flex of legend item labels
22 | labelFlex?: string | number;
23 | // margin for the legend labels
24 | labelMargin?: string | number;
25 | // margin for legend items
26 | itemMargin?: string | number;
27 | // legend's own direction
28 | direction?: FlexDirection;
29 | // direction of items
30 | itemDirection?: FlexDirection;
31 | // fill accessor function
32 | fill?: (label: any) => string | undefined;
33 | // size accessor function
34 | size?: (label: any) => string | number | undefined;
35 | // legend shape string preset or Element or Component
36 | shape?: LegendShape;
37 | // styles for the legend shapes
38 | shapeStyle?: (label: any) => Vue.CSSProperties;
39 | // given a legend item and its index, returns an item label
40 | labelFormat?: any;
41 | // given the legend scale and labelFormatter, returns a label with datum, index, value, and label
42 | labelTransform?: any;
43 | // extra props
44 | legendLabelProps?: Partial;
45 | };
46 |
47 | // types for legendLinear
48 | export type LinearProps = LegendProps & { steps?: number };
49 |
50 | // shape types
51 | export type LegendShape = "rect" | "circle" | "line";
52 |
53 | // label formatter function
54 | export type LabelFormatter = (
55 | item: Datum,
56 | itemIndex: number
57 | ) => Datum | string | number | undefined;
58 |
59 | // label object
60 | export type FormattedLabel = {
61 | datum: Datum;
62 | index: number;
63 | text: string;
64 | value?: Output;
65 | } & ExtraAttributes;
66 |
67 | // renderShape props
68 | export type RenderShapeProvidedProps = {
69 | width?: string | number;
70 | height?: string | number;
71 | label: FormattedLabel;
72 | item: Data;
73 | itemIndex: number;
74 | fill?: string;
75 | size?: string | number;
76 | style?: Vue.CSSProperties;
77 | };
78 |
79 | // accepted types for flexDirection attribute
80 | export type FlexDirection =
81 | | "inherit"
82 | | "initial"
83 | | "revert"
84 | | "unset"
85 | | "column"
86 | | "column-reverse"
87 | | "row"
88 | | "row-reverse";
89 |
90 | // legendLabel own properties
91 | export type LegendLabelOwnProps = {
92 | align?: string;
93 | label?: Vue.VNode;
94 | flex?: string | number;
95 | margin?: string | number;
96 | children?: Vue.VNode;
97 | };
98 |
99 | // all legendLabel accepted props
100 | export type LegendLabelProps = LegendLabelOwnProps &
101 | Omit;
102 |
103 | // legendItem own props
104 | export type LegendItemOwnProps = {
105 | flexDirection?: FlexDirection;
106 | alignItems?: string;
107 | margin?: string | number;
108 | // children?: Vue.VNode; // don't need to type check children because of slots
109 | display?: string;
110 | };
111 |
112 | // all accepted legendItem props
113 | export type LegendItemProps = LegendItemOwnProps &
114 | Omit;
115 |
116 | // legendShape
117 | export type LegendShapeProps = {
118 | label?: any;
119 | //confused on label, left as any for now
120 | //item?: string;
121 | //item?: Data;
122 | itemIndex: number;
123 | margin?: string | number;
124 | shape?: string;
125 | //shape?: LegendShape;
126 | //fill?: () => string | undefined;
127 | //size?: () => string | number | undefined;
128 | //shapeStyle?: any;
129 | // width?: string | number;
130 | // height?: string | number;
131 | //EXPLAINER: removed a bunch of stuff for the shape, since we will be doing just rect and circle for now
132 | //we will be leveraging the svg defaults for these then
133 | };
134 |
--------------------------------------------------------------------------------
/packages/vuenique-legend/utility/defaultDomain.ts:
--------------------------------------------------------------------------------
1 | export default function defaultDomain({
2 | steps,
3 | scale,
4 | }: {
5 | steps: number;
6 | scale: any;
7 | }) {
8 | const domain = scale.domain();
9 | const increment = (domain[domain.length - 1] - domain[0]) / (steps - 1);
10 | const result = new Array(5);
11 | result[0] = domain[0];
12 | for (let i = 1; i < steps; i++) {
13 | result[i] = result[i - 1] + increment;
14 | }
15 | return result;
16 | }
17 |
--------------------------------------------------------------------------------
/packages/vuenique-legend/utility/identity.ts:
--------------------------------------------------------------------------------
1 | export default function identity(x: any) {
2 | return x;
3 | }
4 |
--------------------------------------------------------------------------------
/packages/vuenique-legend/utility/labelTransformFactory.ts:
--------------------------------------------------------------------------------
1 | export default function labelTransformFactory({
2 | scale,
3 | labelFormat,
4 | }: {
5 | scale: any;
6 | labelFormat: any;
7 | }) {
8 | return (d, i) => ({
9 | datum: d,
10 | index: i,
11 | // text of legend item
12 | text: `${labelFormat(d, i)}`,
13 | value: scale(d),
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/packages/vuenique-legend/utility/valueOrIdentity.ts:
--------------------------------------------------------------------------------
1 | export type ValueOrIdentity = T | { value?: T };
2 |
3 | /** returns the value of the argument's value property, if defined, or the argument itself. */
4 | export function valueOrIdentity(_: ValueOrIdentity): T {
5 | if (
6 | _ &&
7 | typeof _ === "object" &&
8 | "value" in _ &&
9 | typeof _.value !== "undefined"
10 | )
11 | return _.value;
12 | return _ as T;
13 | }
14 |
15 | /** returns the value of the argument's value property, if defined, or the argument itself coerced to a string. */
16 | export function valueOrIdentityString(_: ValueOrIdentity): string {
17 | return String(valueOrIdentity(_));
18 | }
19 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/__tests__/band.spec.ts:
--------------------------------------------------------------------------------
1 | import band from '../scales/band.ts';
2 |
3 | describe('scaleBand as band', () => {
4 |
5 | test('should be defined', () => {
6 | expect(band).toBeDefined();
7 | });
8 |
9 | test('set domain', () => {
10 | const domain = [0, 350];
11 | const scale = band({ domain });
12 | expect(scale.domain()).toEqual(domain);
13 | });
14 |
15 | test('set range', () => {
16 | const range = [2, 3]
17 | const scale = band({ range });
18 | expect(scale.range()).toEqual(range);
19 | });
20 |
21 | test('set align', () => {
22 | expect(band({ align: 0.7}).align()).toEqual(0.7);
23 | });
24 |
25 | test('set padding', () => {
26 | expect(band({ padding: 0.2 }).padding()).toEqual(0.2);
27 | });
28 |
29 | test('set paddingInner', () => {
30 | expect(band({ paddingInner: 0.5 }).paddingInner()).toEqual(0.5);
31 | });
32 |
33 | test('set paddingOuter', () => {
34 | expect(band({paddingOuter: 0.9}).paddingOuter()).toEqual(0.9);
35 | });
36 |
37 | describe('set round', () => {
38 | test('true', () => {
39 | const scale = band({ domain: ['a', 'b', 'c', 'd'], range: [1.1, 3.5], round: true });
40 | expect(scale('a')).toEqual(2);
41 | expect(scale('b')).toEqual(2);
42 | expect(scale('c')).toEqual(2);
43 | expect(scale('c')).toEqual(2);
44 | });
45 | test('false', () => {
46 | const scale = band({ domain: ['a', 'b', 'c', 'd'], range: [1.1, 3.1], round: false });
47 | expect(scale('a')).toEqual(1.1);
48 | expect(scale('b')).toEqual(1.6);
49 | expect(scale('c')).toEqual(2.1);
50 | expect(scale('d')).toEqual(2.6);
51 | })
52 |
53 | })
54 | })
--------------------------------------------------------------------------------
/packages/vuenique-scale/__tests__/scaleLinear.spec.ts:
--------------------------------------------------------------------------------
1 | import { scaleLinear } from '..';
2 |
3 | describe('scaleLinear()', () => {
4 |
5 | test('should be defined', () =>{
6 | expect(scaleLinear).toBeDefined();
7 | });
8 |
9 | test('set domain', () => {
10 | const domain = [1, 2];
11 | expect(scaleLinear({ domain }).domain()).toEqual([1, 2]);
12 | });
13 |
14 | test('set range', () => {
15 | const range = [1, 2];
16 | expect(scaleLinear({ range }).range()).toEqual([1, 2]);
17 | });
18 |
19 | test('set reverse', () => {
20 | expect(scaleLinear({ reverse: true }).range()).toEqual([1, 0]);
21 | expect(scaleLinear({ range: [2, 5], reverse: true }).range()).toEqual([5, 2]);
22 | });
23 |
24 | describe('set clamp', () => {
25 | test('true', () => {
26 | const scale = scaleLinear({ clamp: true});
27 | expect(scale(20)).toEqual(1);
28 | });
29 |
30 | test('false', () => {
31 | const scale = scaleLinear({ clamp: false});
32 | expect(scale(20)).toEqual(20);
33 | });
34 | });
35 |
36 | describe('set color interpolate', () => {
37 |
38 | test('string', () => {
39 | const scale = scaleLinear({
40 | domain: [0, 10],
41 | range: ['#ff0000', '#000000'],
42 | interpolate: 'lab',
43 | });
44 | expect(scale(5)).toEqual('rgb(122, 27, 11)');
45 | });
46 |
47 | test('config object', () => {
48 | const scale = scaleLinear({
49 | domain: [0, 10],
50 | range: ['#ff0000', '#000000'],
51 | interpolate: {
52 | type: 'rgb',
53 | },
54 | });
55 | expect(scale(5)).toEqual('rgb(128, 0, 0)');
56 | });
57 |
58 | test('config object with gamma', () => {
59 | const scale = scaleLinear({
60 | domain: [0, 10],
61 | range:['#ff0000', '#000000'],
62 | interpolate:{
63 | type: 'rgb',
64 | gamma: 0.9,
65 | },
66 | });
67 | expect(scale(5)).toEqual('rgb(118, 0, 0)');
68 | });
69 | });
70 |
71 | describe('set nice', () => {
72 |
73 | test('true', () => {
74 | const scale = scaleLinear({domain: [0.1, 0.91], nice: true});
75 | expect(scale.domain()).toEqual([0.1, 1]);
76 | });
77 |
78 | test('false', () => {
79 | const scale = scaleLinear({ domain: [0.1, 0.91], nice: false});
80 | });
81 | });
82 |
83 | describe('set round', () => {
84 |
85 | test('true', () => {
86 | const scale = scaleLinear({ domain: [0, 10], range: [0, 10], round: true });
87 | expect(scale(2.2)).toEqual(2);
88 | expect(scale(2.6)).toEqual(3);
89 | });
90 |
91 | test('false', () => {
92 | const scale = scaleLinear({ domain: [0, 10], range: [0, 10], round: false });
93 | expect(scale(2.2)).toEqual(2.2);
94 | expect(scale(2.6)).toEqual(2.6);
95 | });
96 |
97 | test('warns if do both interpolate and round', () => {
98 | //can be accomplished either using global to access warn or spyOn
99 | global.console = {warn: vi.fn()};
100 | scaleLinear({
101 | domain: [0, 10],
102 | range: [0, 10],
103 | interpolate: 'hsl',
104 | round: true,
105 | });
106 | //vi.spyOn(global.console, 'warn');
107 | expect(console.warn).toHaveBeenCalledTimes(1);
108 | });
109 | });
110 |
111 | describe('set zero', () => {
112 | test('true', () => {
113 | expect(scaleLinear({ domain: [1, 2], zero: true }).domain()).toEqual([0, 2]);
114 | expect(scaleLinear({ domain: [1, -2], zero: true }).domain()).toEqual([1, -2]);
115 | expect(scaleLinear({ domain: [-2, -1], zero: true }).domain()).toEqual([-2, 0]);
116 | expect(scaleLinear({ domain: [-2, 3], zero: true }).domain()).toEqual([-2, 3]);
117 | });
118 | test('false', () => {
119 | expect(scaleLinear({ domain: [1, 2], zero: false }).domain()).toEqual([1, 2]);
120 | expect(scaleLinear({ domain: [-2, -1], zero: false }).domain()).toEqual([-2, -1]);
121 | expect(scaleLinear({ domain: [-2, 3], zero: false }).domain()).toEqual([-2, 3]);
122 | });
123 |
124 | });
125 | });
--------------------------------------------------------------------------------
/packages/vuenique-scale/__tests__/scaleLog.spec.ts:
--------------------------------------------------------------------------------
1 | import { scaleLog } from '..';
2 |
3 | describe('scaleLog()', () => {
4 |
5 | test('should be defined', () => {
6 | expect(scaleLog).toBeDefined();
7 | });
8 |
9 | test('set domain', () => {
10 | const domain = [2, 3];
11 | expect(scaleLog({ domain: [2, 3] }).domain()).toEqual(domain);
12 | });
13 |
14 | test('set range', () => {
15 | const range = [3, 4];
16 | expect(scaleLog({ range: [3, 4] }).range()).toEqual(range);
17 | });
18 |
19 | test('set base', () => {
20 | expect(scaleLog({ base: 5 }).base()).toEqual(5);
21 | });
22 |
23 | describe('set clamp', () => {
24 |
25 | test('true', () => {
26 | const scale = scaleLog({ range: [1, 4], clamp: true });
27 | expect(scale(100)).toEqual(4);
28 | });
29 |
30 | test('false', () => {
31 | const scale = scaleLog({ range: [1, 2], clamp: false });
32 | expect(scale(10000)).toEqual(5);
33 | });
34 | });
35 |
36 | test('set (color) interpolate', () => {
37 | const scale = scaleLog({
38 | domain: [1, 100],
39 | range: ['#ff0000', '#000000'],
40 | interpolate: 'lab',
41 | });
42 | expect(scale(10)).toEqual('rgb(122, 27, 11)');
43 | });
44 |
45 | describe('set nice', () => {
46 |
47 | test('true', () => {
48 | const scale = scaleLog({ domain: [0.1, 0.89], nice: true });
49 | expect(scale.domain()).toEqual([0.1, 1]);
50 | });
51 |
52 | test('false', () => {
53 | const scale = scaleLog({ domain: [0.1, 0.78], nice: false });
54 | expect(scale.domain()).toEqual([0.1, 0.78]);
55 | });
56 | });
57 |
58 | describe('set round', () => {
59 |
60 | test('true', () => {
61 | const scale = scaleLog({ domain: [1, 10], range: [1, 10], round: true });
62 | expect(scale(2.2)).toEqual(4);
63 | });
64 |
65 | test('false', () => {
66 | const scale = scaleLog({ domain: [1, 10], range: [1, 10], round: false });
67 | expect(scale(5)?.toFixed(2)).toEqual('7.29');
68 | });
69 | });
70 | });
--------------------------------------------------------------------------------
/packages/vuenique-scale/__tests__/scaleOrdinal.spec.ts:
--------------------------------------------------------------------------------
1 | import { scaleOrdinal } from '..';
2 |
3 | describe('scaleOrdinal', () => {
4 |
5 | test('should be defined', () => {
6 | expect(scaleOrdinal).toBeDefined();
7 | });
8 |
9 | test('set domain', () => {
10 | const domain = ['noodle', 'burger'];
11 | const scale = scaleOrdinal({ domain });
12 | expect(scale.domain()).toEqual(domain);
13 | });
14 |
15 | test('set range', () => {
16 | const range = ['red', 'green'];
17 | const scale = scaleOrdinal({ range });
18 | expect(scale.range()).toEqual(range);
19 | });
20 |
21 | test('set unknown', () => {
22 | const scale = scaleOrdinal({ domain: ['noodle', 'burger'], unknown: 'green' });
23 | expect(scale('hello')).toEqual('green');
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/__tests__/scaleQuantile.spec.ts:
--------------------------------------------------------------------------------
1 | import { scaleQuantile } from '..';
2 |
3 | describe('scaleQuantile', () => {
4 |
5 | test('should be defined', () => {
6 | expect(scaleQuantile).toBeDefined();
7 | });
8 |
9 | test('set domain', () => {
10 | const domain = [0, 350];
11 | const scale = scaleQuantile({ domain });
12 | expect(scale.domain()).toEqual(domain);
13 | });
14 |
15 | test('set range', () => {
16 | const range = [2, 3];
17 | const scale = scaleQuantile({ range });
18 | expect(scale.range()).toEqual(range);
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/__tests__/scaleThreshold.spec.ts:
--------------------------------------------------------------------------------
1 | import { scaleThreshold } from '..';
2 |
3 | describe('scaleThreshold', () => {
4 |
5 | test('should be defined', () => {
6 | expect(scaleThreshold).toBeDefined();
7 | });
8 |
9 | test('set domain', () => {
10 | const domain = [0, 350];
11 | const scale = scaleThreshold({ domain });
12 | expect(scale.domain()).toEqual(domain);
13 | });
14 |
15 | test('set range', () => {
16 | const range = [2, 3];
17 | const scale = scaleThreshold({ range });
18 | expect(scale.range()).toEqual(range);
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/__tests__/scaleUtc.spec.ts:
--------------------------------------------------------------------------------
1 | import TimezoneMock from 'timezone-mock';
2 | import { scaleUtc } from '..';
3 |
4 | describe('scaleUtc()', () => {
5 | let domain: [Date, Date];
6 | let unniceDomain: [Date, Date];
7 |
8 | beforeAll(() => {
9 | TimezoneMock.register('US/Pacific');
10 | domain = [new Date(Date.UTC(2020, 0, 1)), new Date(Date.UTC(2020, 0, 10))];
11 | unniceDomain = [new Date(Date.UTC(2020, 0, 1)), new Date(Date.UTC(2020, 0, 9, 20))];
12 | });
13 |
14 | afterAll(() => {
15 | TimezoneMock.unregister();
16 | });
17 |
18 | test('should be defined', () => {
19 | expect(scaleUtc).toBeDefined();
20 | });
21 |
22 | test('set domain', () => {
23 | expect(scaleUtc({ domain }).domain()).toEqual(domain);
24 | });
25 |
26 | test('set range', () => {
27 | const range = [1, 2];
28 | expect(scaleUtc({ range: [1, 2] }).range()).toEqual(range);
29 | });
30 |
31 | describe('set clamp', () => {
32 |
33 | test('true', () => {
34 | const scale = scaleUtc({ domain, range: [0, 10], clamp: true });
35 | expect(scale(new Date(Date.UTC(2019, 11, 31)))).toEqual(0);
36 | });
37 |
38 | test('false', () => {
39 | const scale = scaleUtc({ domain, range: [0, 10], clamp: false });
40 | expect(scale(new Date(Date.UTC(2019, 11, 31)))?.toFixed(2)).toEqual('-1.11');
41 | });
42 | });
43 |
44 | test('set (color) interpolate', () => {
45 | const scale = scaleUtc({
46 | domain,
47 | range: ['#ff0000', '#000000'],
48 | interpolate: 'lab',
49 | });
50 | expect(scale(new Date(Date.UTC(2020, 0, 5)))).toEqual('rgb(136, 28, 11)');
51 | });
52 |
53 | describe('set nice', () => {
54 |
55 | test('true', () => {
56 | const scale = scaleUtc({
57 | domain: unniceDomain,
58 | nice: true,
59 | });
60 | expect(scale.domain()).toEqual(domain);
61 | });
62 |
63 | test('false', () => {
64 | const scale = scaleUtc({ domain: unniceDomain, nice: false });
65 | expect(scale.domain()).toEqual(unniceDomain);
66 | });
67 |
68 | test('number', () => {
69 | const scale = scaleUtc({ domain: unniceDomain, nice: 5 });
70 | expect(scale.domain()).toEqual([
71 | new Date(Date.UTC(2020, 0, 1)),
72 | new Date(Date.UTC(2020, 0, 11)),
73 | ]);
74 | });
75 |
76 | test('time unit string', () => {
77 | const scale = scaleUtc({ domain: unniceDomain, nice: 'hour' });
78 | expect(scale.domain()).toEqual(unniceDomain);
79 | });
80 |
81 | test('nice object', () => {
82 | const scale = scaleUtc({ domain: unniceDomain, nice: { interval: 'hour', step: 3 } });
83 | expect(scale.domain()).toEqual([
84 | new Date(Date.UTC(2020, 0, 1)),
85 | new Date(Date.UTC(2020, 0, 9, 21)),
86 | ]);
87 | });
88 | });
89 |
90 | describe('set round', () => {
91 |
92 | test('true', () => {
93 | const scale = scaleUtc({
94 | domain,
95 | range: [1, 5],
96 | round: true,
97 | });
98 | expect(scale(new Date(Date.UTC(2020, 0, 5)))).toEqual(3);
99 | });
100 |
101 | test('false', () => {
102 | const scale = scaleUtc({
103 | domain,
104 | range: [1, 5],
105 | round: false,
106 | });
107 | expect(scale(new Date(Date.UTC(2020, 0, 5)))?.toFixed(2)).toEqual('2.78');
108 | });
109 | });
110 | });
111 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/__tests__/time.spec.ts:
--------------------------------------------------------------------------------
1 | import TimezoneMock from 'timezone-mock';
2 | import time from '../scales/time.ts';
3 |
4 | describe('time()', () => {
5 | let domain: [Date, Date];
6 | let unniceDomain: [Date, Date];
7 |
8 | beforeAll(() => {
9 | TimezoneMock.register('US/Pacific');
10 | domain = [new Date(2020, 0, 1), new Date(2020, 0, 10)];
11 | unniceDomain = [new Date(2020, 0, 1), new Date(2020, 0, 9, 20)];
12 | });
13 |
14 | afterAll(() => {
15 | TimezoneMock.unregister();
16 | });
17 |
18 | test('should be defined', () => {
19 | expect(time).toBeDefined();
20 | });
21 |
22 | test('set domain', () => {
23 | expect(time({ domain }).domain()).toEqual(domain);
24 | });
25 |
26 | test('set range', () => {
27 | const range = [1, 2];
28 | expect(time({ range: [1, 2] }).range()).toEqual(range);
29 | });
30 |
31 | describe('set clamp', () => {
32 |
33 | test('true', () => {
34 | const scale = time({ domain, range: [0, 10], clamp: true });
35 | expect(scale(new Date(2019, 11, 31))).toEqual(0);
36 | });
37 |
38 | test('false', () => {
39 | const scale = time({ domain, range: [0, 10], clamp: false });
40 | expect(scale(new Date(2019, 11, 31))?.toFixed(2)).toEqual('-1.11');
41 | });
42 | });
43 |
44 | test('set (color) interpolate', () => {
45 | const scale = time({
46 | domain,
47 | range: ['#ff0000', '#000000'],
48 | interpolate: 'lab',
49 | });
50 | expect(scale(new Date(2020, 0, 5))).toEqual('rgb(136, 28, 11)');
51 | });
52 |
53 | describe('set nice', () => {
54 | test('true', () => {
55 | const scale = time({
56 | domain: unniceDomain,
57 | nice: true,
58 | });
59 | expect(scale.domain()).toEqual(domain);
60 | });
61 |
62 | test('false', () => {
63 | const scale = time({ domain: unniceDomain, nice: false });
64 | expect(scale.domain()).toEqual(unniceDomain);
65 | });
66 |
67 | test('number', () => {
68 | const scale = time({ domain: unniceDomain, nice: 5 });
69 | expect(scale.domain()).toEqual([new Date(2020, 0, 1), new Date(2020, 0, 11)]);
70 | });
71 |
72 | test('time unit string', () => {
73 | const scale = time({ domain: unniceDomain, nice: 'hour' });
74 | expect(scale.domain()).toEqual(unniceDomain);
75 | });
76 |
77 | test('nice object', () => {
78 | const scale = time({ domain: unniceDomain, nice: { interval: 'hour', step: 3 } });
79 | expect(scale.domain()).toEqual([new Date(2020, 0, 1), new Date(2020, 0, 9, 21)]);
80 | });
81 | test('invalid nice object', () => {
82 | const scale = time({ domain: unniceDomain, nice: { interval: 'hour', step: NaN } });
83 | expect(scale.domain()).toEqual(unniceDomain);
84 | });
85 | });
86 |
87 | describe('set round', () => {
88 | test('true', () => {
89 | const scale = time({
90 | domain,
91 | range: [1, 5],
92 | round: true,
93 | });
94 | expect(scale(new Date(2020, 0, 5))).toEqual(3);
95 | });
96 | test('false', () => {
97 | const scale = time({
98 | domain,
99 | range: [1, 5],
100 | round: false,
101 | });
102 | expect(scale(new Date(2020, 0, 5))?.toFixed(2)).toEqual('2.78');
103 | });
104 | });
105 | });
--------------------------------------------------------------------------------
/packages/vuenique-scale/index.ts:
--------------------------------------------------------------------------------
1 | export { default as scaleBand } from './scales/band';
2 | export { default as scaleLinear } from './scales/linear';
3 | export { default as scaleTime } from './scales/time';
4 | export { default as scaleLog } from './scales/log';
5 | export { default as scaleOrdinal } from './scales/ordinal';
6 | export { default as scaleQuantile } from './scales/quantile';
7 | export { default as scaleThreshold } from './scales/threshold';
8 | export { default as scaleUtc } from './scales/utc';
--------------------------------------------------------------------------------
/packages/vuenique-scale/operators/align.ts:
--------------------------------------------------------------------------------
1 | /** Operator function to apply the D3 base method with the user's align value as the argument
2 | Requires the config object to have the align property
3 |
4 | Refer to D3-Scale for D3 align method: {@link https://github.com/d3/d3-scale/}
5 | **/
6 | export default function applyAlign(scale: any, config: any) {
7 | if (
8 | "align" in scale &&
9 | "align" in config &&
10 | typeof config.align !== "undefined"
11 | ) {
12 | scale.align(config.align);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/operators/base.ts:
--------------------------------------------------------------------------------
1 | /** Operator function to apply the D3 base method with the user's base value as the argument
2 | Requires the config object to have the base property
3 |
4 | Refer to D3-Scale for more info on D3 base method: {@link https://github.com/d3/d3-scale/}
5 | **/
6 | export default function applyBase(scale: any, config: any) {
7 | if (
8 | "base" in scale &&
9 | "base" in config &&
10 | typeof config.base !== "undefined"
11 | ) {
12 | scale.base(config.base);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/operators/clamp.ts:
--------------------------------------------------------------------------------
1 | /** Operator function to apply the D3 clamp method with the user's clamp value as the argument
2 | Requires the config object to have the clamp property
3 |
4 | Refer to D3-Scale for more info on D3 clamp method: {@link https://github.com/d3/d3-scale/}
5 | **/
6 |
7 | export default function applyClamp(scale: any, config: any) {
8 | if (
9 | "clamp" in scale &&
10 | "clamp" in config &&
11 | typeof config.clamp !== "undefined"
12 | ) {
13 | scale.clamp(config.clamp);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/operators/domain.ts:
--------------------------------------------------------------------------------
1 | /** Operator function that applys the D3 domain method and asserts the passed in domain's argument type
2 | Refer to D3-Scale for info on D3 domain method: {@link https://github.com/d3/d3-scale/}
3 |
4 | Plan on adding type distinctions between scales with padding properties and scales without
5 | **/
6 |
7 | export default function applyDomain(scale: any, config: any) {
8 | if (config.domain) {
9 | if ("nice" in scale || "quantiles" in scale) {
10 | // continuous input scales
11 | scale.domain(config.domain as number[] | Date[]);
12 | } else if ("padding" in scale) {
13 | // point and band scales
14 | scale.domain(config.domain);
15 | } else {
16 | // ordinal and threshold scale
17 | scale.domain(config.domain);
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/operators/interpolate.ts:
--------------------------------------------------------------------------------
1 | import createColorInterpolator from "../utils/createColorInterpolator";
2 |
3 | /** Operator function to apply the D3 interpolate method with the returned color interpolatation as the argument
4 | Requires the config object to have the interpolate property
5 |
6 | Refer to D3-Scale for more info on D3 interpolate method: {@link https://github.com/d3/d3-scale/}
7 | **/
8 |
9 | export default function applyInterpolate(scale: any, config: any) {
10 | if (
11 | "interpolate" in config &&
12 | "interpolate" in scale &&
13 | typeof config.interpolate !== "undefined"
14 | ) {
15 | const interpolator = createColorInterpolator(config.interpolate);
16 | scale.interpolate(interpolator);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/operators/nice.ts:
--------------------------------------------------------------------------------
1 | import {
2 | timeSecond,
3 | timeMinute,
4 | timeHour,
5 | timeDay,
6 | timeYear,
7 | timeMonth,
8 | timeWeek,
9 | utcSecond,
10 | utcMinute,
11 | utcHour,
12 | utcDay,
13 | utcWeek,
14 | utcMonth,
15 | utcYear,
16 | // CountableTimeInterval,
17 | } from "d3-time";
18 |
19 | import isUtcScale from "../utils/isUtcScale";
20 |
21 | const localTimeIntervals = {
22 | day: timeDay,
23 | hour: timeHour,
24 | minute: timeMinute,
25 | month: timeMonth,
26 | second: timeSecond,
27 | week: timeWeek,
28 | year: timeYear,
29 | };
30 |
31 | const utcIntervals = {
32 | day: utcDay,
33 | hour: utcHour,
34 | minute: utcMinute,
35 | month: utcMonth,
36 | second: utcSecond,
37 | week: utcWeek,
38 | year: utcYear,
39 | };
40 |
41 | /** Operator function to apply the D3 nice method on different arguments dependent on the argument data type
42 |
43 | Refer to D3-Scale for more info on D3 interpolate method: {@link https://github.com/d3/d3-scale/}
44 | **/
45 |
46 | export default function applyNice(scale: any, config: any) {
47 | if (
48 | "nice" in config &&
49 | typeof config.nice !== "undefined" &&
50 | "nice" in scale
51 | ) {
52 | const { nice } = config;
53 | if (typeof nice === "boolean") {
54 | if (nice) {
55 | scale.nice();
56 | }
57 | } else if (typeof nice === "number") {
58 | scale.nice(nice);
59 | } else {
60 | const timeScale = scale;
61 | const isUtc = isUtcScale(timeScale);
62 | if (typeof nice === "string") {
63 | timeScale.nice(isUtc ? utcIntervals[nice] : localTimeIntervals[nice]);
64 | } else {
65 | const { interval, step } = nice;
66 | const parsedInterval = (
67 | isUtc ? utcIntervals[interval] : localTimeIntervals[interval]
68 | ).every(step);
69 | if (parsedInterval != null) {
70 | timeScale.nice(parsedInterval);
71 | }
72 | }
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/operators/padding.ts:
--------------------------------------------------------------------------------
1 | /** Operator function applying various types of padding
2 |
3 | Refer to D3-Scale for more info on D3 padding methods: {@link https://github.com/d3/d3-scale/}
4 | **/
5 | export default function applyPadding(scale: any, config: any) {
6 | if (
7 | "padding" in scale &&
8 | "padding" in config &&
9 | typeof config.padding !== "undefined"
10 | ) {
11 | scale.padding(config.padding);
12 | }
13 | if (
14 | "paddingInner" in scale &&
15 | "paddingInner" in config &&
16 | typeof config.paddingInner !== "undefined"
17 | ) {
18 | scale.paddingInner(config.paddingInner);
19 | }
20 | if (
21 | "paddingOuter" in scale &&
22 | "paddingOuter" in config &&
23 | typeof config.paddingOuter !== "undefined"
24 | ) {
25 | scale.paddingOuter(config.paddingOuter);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/operators/range.ts:
--------------------------------------------------------------------------------
1 | // set the range of the scale according to
2 | // the range property passed within config object
3 | export default function applyRange( scale: any, config: any ) {
4 | if (config.range) {
5 | if ('padding' in scale) {
6 | // config.domain will be type [number, number]
7 | // this only apply to band scale
8 | scale.range(config.range as [number, number]);
9 | } else {
10 | // config.domain will have different type if there isn't a padding in scale
11 | scale.range(config.range);
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/packages/vuenique-scale/operators/reverse.ts:
--------------------------------------------------------------------------------
1 | // reverse the range of the scale if there
2 | // is a defined reverse property passed within config object
3 | export default function applyReverse(scale: any, config: any) {
4 | if (config.reverse) {
5 | const reversedRange = scale.range().slice().reverse();
6 | if ("padding" in scale) {
7 | // reverseRnge will be type [number, number]
8 | // only apply to band scale
9 | scale.range(reversedRange);
10 | } else {
11 | // reverseRange will have different type if there isn't a padding in scale
12 | scale.range(reversedRange);
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/operators/round.ts:
--------------------------------------------------------------------------------
1 | import { interpolateRound } from 'd3-interpolate';
2 |
3 | // set round property of the scale if there
4 | // is a defined round property passed within config object
5 | export default function applyRound( scale: any, config: any ) {
6 | if ('round' in config && typeof config.round !== 'undefined') {
7 | // Warns the user if passed config has both round and interpolate
8 | if (config.round && 'interpolate' in config && typeof config.interpolate !== 'undefined') {
9 | console.warn(
10 | `[visx/scale/applyRound] ignoring round: scale config contains round and interpolate. only applying interpolate. config:`,
11 | config
12 | );
13 | } else if ('round' in scale) {
14 | // for band scales, set the round property of it
15 | scale.round(config.round);
16 | } else if ('interpolate' in scale && config.round) {
17 | // for continuous output scales
18 | // setting config.round = true
19 | // is actually setting interpolator to interpolateRound
20 | // as these scales do not have scale.round() function
21 | scale.interpolate(interpolateRound);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/operators/scaleOperator.ts:
--------------------------------------------------------------------------------
1 | // imports for all scale operators
2 | import domain from "./domain";
3 | import range from "./range";
4 | import align from "./align";
5 | import base from "./base";
6 | import clamp from "./clamp";
7 | // import constant from './constant';
8 | // import exponent from './exponent';
9 | import interpolate from "./interpolate";
10 | import nice from "./nice";
11 | import padding from "./padding";
12 | import reverse from "./reverse";
13 | import round from "./round";
14 | import unknown from "./unknown";
15 | import zero from "./zero";
16 |
17 | // hold all operators in order and prevents array from being modified during compilation
18 | export const ALL_OPERATORS = [
19 | // domain => nice => zero
20 | "domain",
21 | "nice",
22 | "zero",
23 |
24 | // interpolate before round
25 | "interpolate",
26 | "round",
27 |
28 | // set range then reverse
29 | "range",
30 | "reverse",
31 |
32 | // Order does not matter for these operators
33 | "align",
34 | "base",
35 | "clamp",
36 | // 'constant',
37 | // 'exponent',
38 | "padding",
39 | "unknown",
40 | ] as const;
41 |
42 | type OperatorType = typeof ALL_OPERATORS[number];
43 |
44 | // Use Record to enforce that all keys in OperatorType must exist.
45 | const operators: Record = {
46 | domain,
47 | nice,
48 | zero,
49 | interpolate,
50 | round,
51 | align,
52 | base,
53 | clamp,
54 | // constant,
55 | // exponent,
56 | padding,
57 | range,
58 | reverse,
59 | unknown,
60 | };
61 |
62 | export default function scaleOperator(...ops: OperatorType[]) {
63 | const selection = new Set(ops);
64 | const selectedOps = ALL_OPERATORS.filter((o) => selection.has(o));
65 |
66 | return function applyOperators(scale: any, config?: any) {
67 | if (typeof config !== "undefined") {
68 | selectedOps.forEach((op) => {
69 | operators[op](scale, config);
70 | });
71 | }
72 |
73 | return scale;
74 | };
75 | }
76 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/operators/unknown.ts:
--------------------------------------------------------------------------------
1 | // set of the scale unknown property if there is a defined unknow property
2 | // passed within the config object
3 | export default function applyUnknown(scale: any, config: any) {
4 | if (
5 | "unknown" in scale &&
6 | "unknown" in config &&
7 | typeof config.unknown !== "undefined"
8 | ) {
9 | (scale.unknown as Function)(config.unknown);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/operators/zero.ts:
--------------------------------------------------------------------------------
1 | // update the domain property of the scale if the zero property
2 | // passed within the config object is set to true
3 | export default function applyZero(scale: any, config: any) {
4 | if ("zero" in config && config.zero === true) {
5 | const domain = scale.domain() as number[];
6 | const [a, b] = domain;
7 | const isDescending = b < a;
8 | // if domain of the scale is in descending order, change it to be ascending order
9 | // with lower bound less than or equal to zero, higher bound greater or equal to zero
10 | const [min, max] = isDescending ? [b, a] : [a, b];
11 | const domainWithZero = [Math.min(0, min), Math.max(0, max)];
12 | scale.domain(isDescending ? domainWithZero.reverse() : domainWithZero);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/scales/band.ts:
--------------------------------------------------------------------------------
1 | import { scaleBand } from "d3-scale";
2 | import scaleOperator from "../operators/scaleOperator";
3 |
4 | /** Returns applyOperator function */
5 | export const updateBandScale = scaleOperator(
6 | "domain",
7 | "range",
8 | "reverse",
9 | "align",
10 | "padding",
11 | "round"
12 | );
13 |
14 | export default function createBandScale(config?: any) {
15 | return updateBandScale(scaleBand(), config);
16 | }
17 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/scales/linear.ts:
--------------------------------------------------------------------------------
1 | import { scaleLinear } from "d3-scale";
2 | import scaleOperator from "../operators/scaleOperator";
3 |
4 | /** Returns applyOperator function */
5 | export const updateLinearScale = scaleOperator(
6 | "domain",
7 | "range",
8 | "reverse",
9 | "clamp",
10 | "interpolate",
11 | "nice",
12 | "round",
13 | "zero"
14 | );
15 |
16 | export default function createLinearScale(config?: any) {
17 | return updateLinearScale(scaleLinear(), config);
18 | }
19 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/scales/log.ts:
--------------------------------------------------------------------------------
1 | import { scaleLog } from "d3-scale";
2 |
3 | import scaleOperator from "../operators/scaleOperator";
4 |
5 | /** Returns applyOperator function */
6 | export const updateLogScale = scaleOperator(
7 | "domain",
8 | "range",
9 | "reverse",
10 | "base",
11 | "clamp",
12 | "interpolate",
13 | "nice",
14 | "round"
15 | );
16 |
17 | export default function createLogScale(config?: any) {
18 | return updateLogScale(scaleLog(), config);
19 | }
20 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/scales/ordinal.ts:
--------------------------------------------------------------------------------
1 | import { scaleOrdinal } from "d3-scale";
2 | import scaleOperator from "../operators/scaleOperator";
3 |
4 | /** Returns applyOperator function */
5 | export const updateOrdinalScale = scaleOperator(
6 | "domain",
7 | "range",
8 | "reverse",
9 | "unknown"
10 | );
11 |
12 | export default function createOrdinalScale(config?: any) {
13 | return updateOrdinalScale(scaleOrdinal(), config);
14 | }
15 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/scales/quantile.ts:
--------------------------------------------------------------------------------
1 | import { scaleQuantile } from "d3-scale";
2 |
3 | import scaleOperator from "../operators/scaleOperator";
4 |
5 | /** Returns applyOperator function */
6 | export const updateQuantileScale = scaleOperator("domain", "range", "reverse");
7 |
8 | export default function createQuantileScale(config?: any) {
9 | return updateQuantileScale(scaleQuantile(), config);
10 | }
11 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/scales/threshold.ts:
--------------------------------------------------------------------------------
1 | import { scaleThreshold } from "d3-scale";
2 | import scaleOperator from "../operators/scaleOperator";
3 |
4 | export const updateThresholdScale = scaleOperator("domain", "range", "reverse");
5 |
6 | /** Returns applyOperator function */
7 | export default function createThresholdScale(config?: any) {
8 | return updateThresholdScale(scaleThreshold(), config);
9 | }
10 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/scales/time.ts:
--------------------------------------------------------------------------------
1 | import { scaleTime } from "d3-scale";
2 |
3 | import scaleOperator from "../operators/scaleOperator";
4 |
5 | type DefaultOutput = number | string | boolean | null;
6 |
7 | /** Returns applyOperator function */
8 | export const updateTimeScale = scaleOperator(
9 | "domain",
10 | "range",
11 | "reverse",
12 | "clamp",
13 | "interpolate",
14 | "nice",
15 | "round"
16 | );
17 |
18 | export default function createTimeScale(config?: any) {
19 | return updateTimeScale(scaleTime(), config);
20 | }
21 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/scales/utc.ts:
--------------------------------------------------------------------------------
1 | import { scaleUtc } from "d3-scale";
2 | import scaleOperator from "../operators/scaleOperator";
3 |
4 | /** Returns applyOperator function */
5 | export const updateUtcScale = scaleOperator(
6 | "domain",
7 | "range",
8 | "reverse",
9 | "clamp",
10 | "interpolate",
11 | "nice",
12 | "round"
13 | );
14 |
15 | export default function createUtcScale(config?: any) {
16 | return updateUtcScale(scaleUtc(), config);
17 | }
18 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/utils/coerceNumber.ts:
--------------------------------------------------------------------------------
1 | export default function coerceNumber(val: any): T | number {
2 | if (
3 | (typeof val === "function" || (typeof val === "object" && !!val)) &&
4 | "valueOf" in val
5 | ) {
6 | const num = val.valueOf();
7 | if (typeof num === "number") return num;
8 | }
9 |
10 | return val as T;
11 | }
12 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/utils/createColorInterpolator.ts:
--------------------------------------------------------------------------------
1 | import {
2 | interpolateRgb,
3 | interpolateLab,
4 | interpolateHcl,
5 | interpolateHclLong,
6 | interpolateHsl,
7 | interpolateHslLong,
8 | interpolateCubehelix,
9 | interpolateCubehelixLong,
10 | } from "d3-interpolate";
11 |
12 | const interpolatorMap = {
13 | lab: interpolateLab,
14 | hcl: interpolateHcl,
15 | "hcl-long": interpolateHclLong,
16 | hsl: interpolateHsl,
17 | "hsl-long": interpolateHslLong,
18 | cubehelix: interpolateCubehelix,
19 | "cubehelix-long": interpolateCubehelixLong,
20 | rgb: interpolateRgb,
21 | } as const;
22 |
23 | export default function createColorInterpolator(interpolate: any) {
24 | switch (interpolate) {
25 | case "lab":
26 | case "hcl":
27 | case "hcl-long":
28 | case "hsl":
29 | case "hsl-long":
30 | case "cubehelix":
31 | case "cubehelix-long":
32 | case "rgb":
33 | return interpolatorMap[interpolate];
34 | default:
35 | }
36 |
37 | const { type, gamma } = interpolate;
38 | const interpolator = interpolatorMap[type];
39 | return typeof gamma === "undefined"
40 | ? interpolator
41 | : interpolator.gamma(gamma);
42 | }
43 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/utils/getTicks.ts:
--------------------------------------------------------------------------------
1 | export default function getTicks(scale: any, numTicks?: number) {
2 | const s = scale;
3 | if ("ticks" in s) {
4 | return s.ticks(numTicks);
5 | }
6 | return s
7 | .domain()
8 | .filter(
9 | (_, index, arr) =>
10 | numTicks == null ||
11 | arr.length <= numTicks ||
12 | index % Math.round((arr.length - 1) / numTicks) === 0
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/utils/inferScaleType.ts:
--------------------------------------------------------------------------------
1 | import isUtcScale from './isUtcScale';
2 |
3 | export default function inferScaleType(scale)
4 |
5 | if ('paddingInner' in scale) {
6 | return 'band';
7 | }
8 |
9 | if ('padding' in scale) {
10 | return 'point';
11 | }
12 |
13 | if ('quantiles' in scale) {
14 | return 'quantile';
15 | }
16 |
17 | if ('base' in scale) {
18 | return 'log';
19 | }
20 |
21 | if ('exponent' in scale) {
22 | return scale.exponent() === 0.5 ? 'sqrt' : 'pow';
23 | }
24 |
25 | if ('constant' in scale) {
26 | return 'symlog';
27 | }
28 |
29 | if ('clamp' in scale) {
30 | // Linear, Time or Utc scales
31 | if (scale.ticks()[0] instanceof Date) {
32 | return isUtcScale(scale) ? 'utc' : 'time';
33 | }
34 | return 'linear';
35 | }
36 |
37 | if ('nice' in scale) {
38 | return 'quantize';
39 | }
40 |
41 | if ('invertExtent' in scale) {
42 | return 'threshold';
43 | }
44 |
45 | return 'ordinal';
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/utils/isUtcScale.ts:
--------------------------------------------------------------------------------
1 | const TEST_TIME = new Date(Date.UTC(2020, 1, 2, 3, 4, 5));
2 | const TEST_FORMAT = "%Y-%m-%d %H:%M";
3 |
4 | /**
5 | * Check if the scale is UTC or Time scale
6 | * When local time is equal to UTC, always return true
7 | * @param scale time or utc scale
8 | */
9 | export default function isUtcScale(scale) {
10 | // The only difference between time and utc scale is
11 | // whether the tick format function is utcFormat or timeFormat
12 | const output = scale.tickFormat(1, TEST_FORMAT)(TEST_TIME);
13 | return output === "2020-02-02 03:04";
14 | }
15 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/utils/scaleCanBeZeroed.ts:
--------------------------------------------------------------------------------
1 | const zeroableScaleTypes = new Set([
2 | "linear",
3 | "pow",
4 | "quantize",
5 | "sqrt",
6 | "symlog",
7 | ]);
8 |
9 | export default function scaleCanBeZeroed(scaleConfig) {
10 | return zeroableScaleTypes.has(scaleConfig.type);
11 | }
12 |
--------------------------------------------------------------------------------
/packages/vuenique-scale/utils/toString.ts:
--------------------------------------------------------------------------------
1 | export default function toString(x?: T) {
2 | return x?.toString();
3 | }
4 |
--------------------------------------------------------------------------------
/packages/vuenique-shape/Bar.vue:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/vuenique-shape/Circle.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/vuenique-shape/LinePath.vue:
--------------------------------------------------------------------------------
1 |
40 |
41 |
42 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/packages/vuenique-shape/__tests__/Bar.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from "vitest";
2 | import { mount } from "@vue/test-utils";
3 |
4 | import Bar from "../Bar.vue";
5 |
6 | // Creates wrapper component around Bar for testing
7 | const BarWrapper = (props = {}, attrs = {}) => mount(Bar, {
8 | shallow: true,
9 | propsData: {
10 | ...props,
11 | },
12 | attrs: {
13 | ...attrs,
14 | },
15 | });
16 |
17 | describe("Bar component...", () => {
18 | test("should be defined", () => {
19 | expect(Bar).toBeDefined();
20 | });
21 |
22 | test("should have test class passed in through props", () => {
23 | expect(
24 | BarWrapper({
25 | class: "test",
26 | }).props("class")
27 | ).toBe("test");
28 | });
29 |
30 | test("should have additional attributes passed in through attrs", () => {
31 | expect(
32 | BarWrapper({}, {
33 | x: 10,
34 | y: 10,
35 | }
36 | ).vm.$attrs
37 | ).toEqual({ x: 10, y: 10 });
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/packages/vuenique-shape/__tests__/Circle.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test } from "vitest";
2 | import { mount } from "@vue/test-utils";
3 |
4 | import Circle from "../Circle.vue";
5 |
6 | // test the following properties
7 | // circle defaults to black
8 | // can define fill color
9 | // can pass props
10 | // can pass attributes
11 |
12 | const CircleWrapper = (props = {}, attrs = {}) =>
13 | mount(Circle, {
14 | shallow: true,
15 | propsData: {
16 | ...props,
17 | },
18 | attrs: {
19 | ...attrs,
20 | },
21 | });
22 |
23 | describe("Circle component...", () => {
24 | test("should be defined", () => {
25 | expect(Circle).toBeDefined();
26 | });
27 |
28 | test("should have test class passed in through props", () => {
29 | expect(
30 | CircleWrapper({
31 | class: "test",
32 | }).props("class")
33 | ).toBe("test");
34 | });
35 |
36 | test("should pass additional props through to element attributes", () => {
37 | const testCircle = CircleWrapper({ cx: 50, cy: 50, r: 50 });
38 | expect(testCircle.element.getAttribute("cx")).toBe("50");
39 | expect(testCircle.element.getAttribute("cy")).toBe("50");
40 | expect(testCircle.element.getAttribute("r")).toBe("50");
41 | expect(testCircle.element.getAttribute("class")).toBeNull();
42 | });
43 |
44 | test("should have fill overwritten when passed through props", () => {
45 | const testCircle = CircleWrapper({ fill: "white" });
46 | expect(testCircle.element.getAttribute("fill")).toBe("white");
47 | })
48 | })
49 |
--------------------------------------------------------------------------------
/packages/vuenique-shape/__tests__/LinePath.spec.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, test, vi } from "vitest";
2 | import { mount } from "@vue/test-utils";
3 | import LinePath from "../LinePath.vue";
4 |
5 | interface Datum {
6 | x: number;
7 | y: number;
8 | }
9 |
10 | const linePathProps = {
11 | data: [
12 | { x: 0, y: 0 },
13 | { x: 1, y: 1 },
14 | ],
15 | x: (d: Datum) => d.x,
16 | y: (d: Datum) => d.y,
17 | };
18 |
19 | const LinePathWrapper = (props = {}, attrs = {}) =>
20 | mount(LinePath, {
21 | shallow: true,
22 | propsData: {
23 | ...props,
24 | },
25 | attrs: {
26 | ...attrs,
27 | },
28 | });
29 |
30 | describe(" ", () => {
31 | test("should be defined", () => {
32 | expect(LinePath).toBeDefined();
33 | });
34 |
35 | test('should default to strokeLinecap="round" for superior missing data rendering', () => {
36 | //seems strokeLinecap is auto converted to lower case
37 | expect(
38 | LinePathWrapper(linePathProps).find("path").attributes().strokelinecap
39 | ).toBe("round");
40 | });
41 |
42 | test("should contain paths", () => {
43 | expect(LinePathWrapper(linePathProps).find("path").getComponent);
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/packages/vuenique-shape/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./utils/D3ShapeFactory";
2 | export * from "./utils/setNumberOrNumberAccessor";
3 |
4 | export { default as Bar } from "./Bar.vue";
5 | export { default as LinePath } from "./LinePath.vue";
6 | export { default as Circle } from "./Circle.vue";
7 |
--------------------------------------------------------------------------------
/packages/vuenique-shape/types/D3ShapeConfig.ts:
--------------------------------------------------------------------------------
1 | import type { AccessorForArrayItem } from "./accessor";
2 | import type { CurveFactory, CurveFactoryLineOnly } from "d3-shape";
3 |
4 | export type LinePathConfig = {
5 | /** Function that returns a boolean when line path points are passed as arguments. Line path is representative of points that return true */
6 | defined?: AccessorForArrayItem;
7 | /** Sets the curve factory from d3-line curve for the line generator. Defaults to curveLinear. */
8 | curve?: CurveFactory | CurveFactoryLineOnly;
9 | /** Sets the x0 accessor function, and sets x1 to null. */
10 | x?: number | AccessorForArrayItem;
11 | /** Sets the y0 accessor function, and sets y1 to null. */
12 | y?: number | AccessorForArrayItem;
13 | };
14 |
--------------------------------------------------------------------------------
/packages/vuenique-shape/types/accessor.ts:
--------------------------------------------------------------------------------
1 | // used in D3ShapConfig
2 | export type AccessorForArrayItem = (
3 | d: Datum,
4 | index: number,
5 | data: Datum[]
6 | ) => Output;
7 |
--------------------------------------------------------------------------------
/packages/vuenique-shape/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./D3ShapeConfig";
2 | export * from "./accessor";
3 |
--------------------------------------------------------------------------------
/packages/vuenique-shape/utils/D3ShapeFactory.ts:
--------------------------------------------------------------------------------
1 | import { line as d3Line } from "d3-shape";
2 | import type { LinePathConfig } from "../types";
3 | import setNumberOrNumberAccessor from "./setNumberOrNumberAccessor";
4 |
5 | export function line({
6 | x,
7 | y,
8 | defined,
9 | curve,
10 | }: LinePathConfig = {}) {
11 | const path = d3Line();
12 | if (x) setNumberOrNumberAccessor(path.x, x);
13 | if (y) setNumberOrNumberAccessor(path.y, y);
14 | if (defined) path.defined(defined);
15 | if (curve) path.curve(curve);
16 | return path;
17 | }
18 |
--------------------------------------------------------------------------------
/packages/vuenique-shape/utils/setNumberOrNumberAccessor.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This is a workaround for TypeScript not inferring the correct
3 | * method overload/signature for some d3 shape methods.
4 | */
5 | export default function setNumberOrNumberAccessor(
6 | func: (d: number | NumAccessor) => void,
7 | value: number | NumAccessor
8 | ) {
9 | if (typeof value === "number") func(value);
10 | else func(value);
11 | }
12 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
92 |
--------------------------------------------------------------------------------
/src/assets/base.css:
--------------------------------------------------------------------------------
1 | /* color palette from */
2 | :root {
3 | --vt-c-white: #ffffff;
4 | --vt-c-white-soft: #f8f8f8;
5 | --vt-c-white-mute: #f2f2f2;
6 |
7 | --vt-c-black: #181818;
8 | --vt-c-black-soft: #222222;
9 | --vt-c-black-mute: #282828;
10 |
11 | --vt-c-indigo: #2c3e50;
12 |
13 | --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
14 | --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
15 | --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
16 | --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
17 |
18 | --vt-c-text-light-1: var(--vt-c-indigo);
19 | --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
20 | --vt-c-text-dark-1: var(--vt-c-white);
21 | --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
22 | }
23 |
24 | /* semantic color variables for this project */
25 | :root {
26 | --color-background: var(--vt-c-white);
27 | --color-background-soft: var(--vt-c-white-soft);
28 | --color-background-mute: var(--vt-c-white-mute);
29 |
30 | --color-border: var(--vt-c-divider-light-2);
31 | --color-border-hover: var(--vt-c-divider-light-1);
32 |
33 | --color-heading: var(--vt-c-text-light-1);
34 | --color-text: var(--vt-c-text-light-1);
35 |
36 | --section-gap: 160px;
37 | }
38 |
39 | @media (prefers-color-scheme: dark) {
40 | :root {
41 | --color-background: var(--vt-c-black);
42 | --color-background-soft: var(--vt-c-black-soft);
43 | --color-background-mute: var(--vt-c-black-mute);
44 |
45 | --color-border: var(--vt-c-divider-dark-2);
46 | --color-border-hover: var(--vt-c-divider-dark-1);
47 |
48 | --color-heading: var(--vt-c-text-dark-1);
49 | --color-text: var(--vt-c-text-dark-2);
50 | }
51 | }
52 |
53 | *,
54 | *::before,
55 | *::after {
56 | box-sizing: border-box;
57 | margin: 0;
58 | position: relative;
59 | font-weight: normal;
60 | }
61 |
62 | body {
63 | min-height: 100vh;
64 | color: var(--color-text);
65 | background: var(--color-background);
66 | transition: color 0.5s, background-color 0.5s;
67 | line-height: 1.6;
68 | font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
69 | Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
70 | font-size: 15px;
71 | text-rendering: optimizeLegibility;
72 | -webkit-font-smoothing: antialiased;
73 | -moz-osx-font-smoothing: grayscale;
74 | }
75 |
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/BarGraph.vue:
--------------------------------------------------------------------------------
1 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/src/components/LineGraph.vue:
--------------------------------------------------------------------------------
1 |
138 |
139 |
140 |
141 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/src/components/Scatter.vue:
--------------------------------------------------------------------------------
1 |
39 |
40 |
41 |
42 |
48 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/components/icons/IconCommunity.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/components/icons/IconDocumentation.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/components/icons/IconEcosystem.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/components/icons/IconSupport.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/components/icons/IconTooling.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from "vue";
2 | import App from "./App.vue";
3 |
4 | createApp(App).mount("#app");
5 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.web.json",
3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "packages/**/*"],
4 | "exclude": ["src/**/__tests__/*"],
5 | "compilerOptions": {
6 | "composite": true,
7 | "baseUrl": ".",
8 | "paths": {
9 | "@/*": ["./*"]
10 | }
11 | }
12 | }
13 |
14 | //changed compiler option paths from "@/*": ["./src/*"] to "@/*": ["./*"]
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | {
5 | "path": "./tsconfig.vite-config.json"
6 | },
7 | {
8 | "path": "./tsconfig.app.json"
9 | },
10 | {
11 | "path": "./tsconfig.vitest.json"
12 | }
13 | ],
14 | "jsx": "preserve"
15 | }
16 |
--------------------------------------------------------------------------------
/tsconfig.vite-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.node.json",
3 | "include": ["vite.config.*"],
4 | "compilerOptions": {
5 | "composite": true,
6 | "types": ["node", "vitest"],
7 | "paths": {
8 | "@/*": ["./*"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tsconfig.vitest.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.app.json",
3 | "exclude": [],
4 | "compilerOptions": {
5 | "composite": true,
6 | "lib": [],
7 | "types": ["node", "jsdom"],
8 | "paths": {
9 | "@/*": ["./*"]
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { fileURLToPath, URL } from "url";
2 | import path from "path";
3 | import { defineConfig } from "vite";
4 | import vue from "@vitejs/plugin-vue";
5 | import vueJsx from "@vitejs/plugin-vue-jsx";
6 |
7 | // https://vitejs.dev/config/
8 | export default defineConfig({
9 | build: {
10 | lib: {
11 | entry: path.resolve(__dirname, 'packages/index.js'),
12 | // name: "MyLib",
13 | fileName: (format) => `index.${format}.js`,
14 | formats: ["cjs", "es"],
15 | },
16 | rollupOptions: {
17 | external: ['vue'],
18 | output: {
19 | // Provide global variables to use in the UMD build
20 | // Add external deps here
21 | globals: {
22 | vue: 'Vue',
23 | },
24 | },
25 | },
26 | outDir: "packages/build",
27 | },
28 | plugins: [vue(), vueJsx()],
29 | resolve: {
30 | alias: {
31 | "@": fileURLToPath(new URL("./src", import.meta.url)),
32 | },
33 | },
34 | test: {
35 | globals: true,
36 | environment: "jsdom",
37 | },
38 | });
39 |
--------------------------------------------------------------------------------