Framework Independent
Use with React, Angular, Svelte, Vue, Solid,
or without any UI framework.
Smooth
We pay attention to how the library components look and how they react to data changes. Styles are customizable via CSS variables.
Built with Typescript
Unovis is built with Typescript and allows you to import individual component modules to reduce your app bundle size.
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
- Data
importReact,{ useCallback}from'react'
import{VisXYContainer,VisAxis,VisArea,VisXYLabels}from'@unovis/react'
import{ data, formats,DataRecord, getLabels}from'./data'
exportdefaultfunctionStackedArea():JSX.Element{
const labels=getLabels(data)
return(
<>
<VisXYContainerdata={data}height={'50vh'}>
<VisAreax={useCallback((d:DataRecord)=> d.year,[])}y={formats.map(g=>useCallback((d:DataRecord)=> d[g],[]))}/>
<VisAxistype='x'label='Year'numTicks={10}gridLine={false}domainLine={false}/>
<VisAxistype='y'label='Revenue (USD, billions)'numTicks={10}/>
<VisXYLabels
x={useCallback((d:DataRecord)=> labels[d.year]? d.year:undefined,[])}
y={useCallback((d:DataRecord)=> labels[d.year]?.value,[])}
label={useCallback((d:DataRecord)=> labels[d.year]?.label,[])}
backgroundColor={useCallback((d:DataRecord)=> labels[d.year]?.color??'none',[])}
clusterBackgroundColor="none"
clusterLabel={()=>''}
/>
</VisXYContainer>
</>
)
}
stacked-area-chart-with-xy-labels.html
<vis-xy-container[data]="data"[height]="500">
<vis-area[x]="x"[y]="y"></vis-area>
<vis-axistype="x"label="Year"[numTicks]="10"[domainLine]="false"[gridLine]="false"></vis-axis>
<vis-axistype="y"label="Number of Votes(millions)"[numTicks]="10"></vis-axis>
<vis-xy-labels
[x]="x"
[y]="labelY"
[label]="labelText"
[backgroundColor]="labelColor"
[clusterLabel]="noLabel"
clusterBackgroundColor="none"
></vis-xy-labels>
</vis-xy-container>
stacked-area-chart-with-xy-labels.component.ts
import{ Component}from'@angular/core'
import{ data, formats, DataRecord, getLabels}from'./data'
@Component({
selector:'stacked-area-chart',
templateUrl:'./stacked-area-chart.component.html',
})
exportclassStackedAreaComponent{
data= data
x=(d: DataRecord):number=> d.year
y= formats.map(f=>(d: DataRecord)=> d[f])
labelItems=getLabels(this.data)
labelY=(d: DataRecord):number=>this.labelItems[d.year]?.value??0
labelText=(d: DataRecord):string=>this.labelItems[d.year]?.label??''
labelColor=(d: DataRecord):string=>this.labelItems[d.year]?.color??'none'
noLabel=():string=>''
}
stacked-area-chart-with-xy-labels.module.ts
import{ NgModule}from'@angular/core'
import{ VisXYContainerModule, VisAreaModule, VisXYLabelsModule, VisAxisModule}from'@unovis/angular'
import{ StackedAreaComponent}from'./stacked-area-chart.component'
@NgModule({
imports:[VisXYContainerModule, VisAreaModule, VisXYLabelsModule, VisAxisModule],
declarations:[StackedAreaComponent],
exports:[StackedAreaComponent],
})
exportclassStackedAreaModule{}
<scriptlang='ts'>
import{VisXYContainer,VisAxis,VisArea,VisXYLabels}from'@unovis/svelte'
import{ data, formats,DataRecord, getLabels}from'./data'
const labels=getLabels(data)
constx=(d:DataRecord)=> d.year
const y= formats.map(g=>(d:DataRecord)=> d[g])
const labelProps={
x:(d:DataRecord)=> labels[d.year]? d.year:undefined,
y:(d:DataRecord)=> labels[d.year]?.value,
label:(d:DataRecord)=> labels[d.year]?.label,
backgroundColor:(d:DataRecord)=> labels[d.year]?.color??'none',
}
</script>
<VisXYContainer{data}height={500}>
<VisXYLabels {...labelProps} clusterBackgroundColor="none" clusterLabel={() => ''}/>
<VisArea{x}{y}/>
<VisAxistype='x'label='Year'numTicks={10}gridLine={false}domainLine={false}/>
<VisAxistype='y'label='Revenue (USD, billions)'numTicks={10}/>
</VisXYContainer>
<scriptsetuplang="ts">
import{VisXYContainer,VisAxis,VisArea,VisXYLabels}from'@unovis/vue'
import{ data, formats,DataRecord, getLabels}from'./data'
const labels=getLabels(data)
constx=(d:DataRecord)=> d.year
const y= formats.map(g=>(d:DataRecord)=> d[g])
const labelProps={
x:(d:DataRecord)=> labels[d.year]? d.year:undefined,
y:(d:DataRecord)=> labels[d.year]?.value,
label:(d:DataRecord)=> labels[d.year]?.label,
backgroundColor:(d:DataRecord)=> labels[d.year]?.color??'none',
}
</script>
<template>
<VisXYContainer:data="data":height="500">
<VisXYLabelsv-bind="labelProps"clusterBackgroundColor="none":clusterLabel="() => ''"/>
<VisArea:x="x":y="y"/>
<VisAxistype='x'label='Year':numTicks="10":gridLine="false":domainLine="false"/>
<VisAxistype='y'label='Revenue (USD, billions)':numTicks="10"/>
</VisXYContainer>
</template>
import{JSX}from'solid-js'
import{VisArea,VisAxis,VisXYContainer,VisXYLabels}from'@unovis/solid'
import{ data, formats,DataRecord, getLabels}from'./data'
constStackedAreaChart=():JSX.Element=>{
const labels=getLabels(data)
constx=(d:DataRecord)=> d.year
const y= formats.map((g)=>(d:DataRecord)=> d[g])
const labelProps={
x:(d:DataRecord)=>(labels[d.year]? d.year:undefined),
y:(d:DataRecord)=> labels[d.year]?.value,
label:(d:DataRecord)=> labels[d.year]?.label,
backgroundColor:(d:DataRecord)=> labels[d.year]?.color??'none',
}
return(
<VisXYContainerdata={data}height='50dvh'>
<VisAreax={x}y={y}/>
<VisAxis
type='x'
label='Year'
numTicks={10}
gridLine={false}
domainLine={false}
/>
<VisAxistype='y'label='Revenue (USD, billions)'numTicks={10}/>
<VisXYLabels
{...labelProps}
clusterBackgroundColor='none'
clusterLabel={()=>''}
/>
</VisXYContainer>
)
}
exportdefaultStackedAreaChart
import{Area,Axis,XYContainer,XYLabels}from'@unovis/ts'
import{ data, formats,DataRecord, getLabels}from'./data'
const labels=getLabels(data)
const container=document.getElementById('vis-container')
const chart=newXYContainer(container,{
height:500,
components:[
// area chart
newArea<DataRecord>({
x:(d:DataRecord)=> d.year,
y: formats.map(f=>(d:DataRecord)=> d[f]),
}),
// labels
newXYLabels({
x:(d:DataRecord)=> labels[d.year]? d.year:undefined,
y:(d:DataRecord)=> labels[d.year]?.value,
label:(d:DataRecord)=> labels[d.year]?.label??'',
backgroundColor:(d:DataRecord)=> labels[d.year]?.color,
clusterBackgroundColor:'none',
clusterLabel:()=>'',
}),
],
xAxis:newAxis({ label:'Year', numTicks:10, domainLine:false, gridLine:false}),
yAxis:newAxis({ label:'Revenue (USD, billions)', numTicks:10}),
}, data)
enum Format{
Vinyl='vinyl',
Cassette='cassette',
Cd='cd',
Download='download',
Streaming='streaming',
}
exportconst formats: Format[]=[Format.Vinyl, Format.Cassette, Format.Cd, Format.Download, Format.Streaming]
exporttypeDataRecord= Record<Format,number>&{ year:number};
exporttypeLabel={
label:string;
value:number;
color:string;
}
exportfunctiongetMaxItems<Textends Record<string,number>>(
array:T[],
keys:(keyofT)[]
):{[keyinkeyofT]?:T}{
const maxIndex=(k:keyofT):number=> array.reduce((max, curr, i)=> curr[k]> array[max][k]? i: max,0)
const entries= keys.map(key=>[
key,
array[maxIndex(key)],
])
return Object.fromEntries(entries)
}
exportfunctiongetLabels(data: DataRecord[]): Record<number, Label>{
// map formats to their maximum data records
const peakItems=getMaxItems(data.slice(0, data.length-3), formats)
// place labels at [x,y] where x = peak year and y = area midpoint
return formats.reduce((obj, format, i)=>{
const offset=Array(i).fill(0).reduce((sum, _, j)=> sum+ peakItems[format][formats[j]],0)
const[x, y]=[peakItems[format].year, offset+ peakItems[format][format]/2]
obj[x]={
label: format==='cd'? format.toUpperCase(): format.charAt(0).toUpperCase()+ format.slice(1),
value: y,
color:'none',
}
return obj
},{})
}
exportconst data: DataRecord[]=[
{
year:1973,
vinyl:1.436,
cd:0,
streaming:0,
cassette:0.5806,
download:0,
},
{
year:1974,
vinyl:1.55,
cd:0,
streaming:0,
cassette:0.6497,
download:0,
},
{
year:1975,
vinyl:1.6965,
cd:0,
streaming:0,
cassette:0.692,
download:0,
},
{
year:1976,
vinyl:1.9081,
cd:0,
streaming:0,
cassette:0.829,
download:0,
},
{
year:1977,
vinyl:2.4402,
cd:0,
streaming:0,
cassette:1.0606,
download:0,
},
{
year:1978,
vinyl:2.7336,
cd:0,
streaming:0,
cassette:1.3978,
download:0,
},
{
year:1979,
vinyl:2.4106,
cd:0,
streaming:0,
cassette:1.2649,
download:0,
},
{
year:1980,
vinyl:2.45,
cd:0,
streaming:0,
cassette:1.232,
download:0,
},
{
year:1981,
vinyl:2.5981,
cd:0,
streaming:0,
cassette:1.3758,
download:0,
},
{
year:1982,
vinyl:2.2081,
cd:0,
streaming:0,
cassette:1.4205,
download:0,
},
{
year:1983,
vinyl:1.9583,
cd:0.0172,
streaming:0,
cassette:1.8109,
download:0,
},
{
year:1984,
vinyl:1.8475,
cd:0.1033,
streaming:0,
cassette:2.3839,
download:0,
},
{
year:1985,
vinyl:1.5615,
cd:0.3895,
streaming:0,
cassette:2.4115,
download:0,
},
{
year:1986,
vinyl:1.2111,
cd:0.9301,
streaming:0,
cassette:2.4995,
download:0,
},
{
year:1987,
vinyl:0.9964,
cd:1.5936,
streaming:0,
cassette:2.974,
download:0,
},
{
year:1988,
vinyl:0.7126,
cd:2.0997,
streaming:0,
cassette:3.4424,
download:0,
},
{
year:1989,
vinyl:0.3367,
cd:2.7024,
streaming:0,
cassette:3.5404,
download:0,
},
{
year:1990,
vinyl:0.1809,
cd:3.6299,
streaming:0,
cassette:3.7303,
download:0,
},
{
year:1991,
vinyl:0.0933,
cd:4.4909,
streaming:0,
cassette:3.25,
download:0,
},
{
year:1992,
vinyl:0.0799,
cd:5.529,
streaming:0,
cassette:3.4151,
download:0,
},
{
year:1993,
vinyl:0.0618,
cd:6.7705,
streaming:0,
cassette:3.2143,
download:0,
},
{
year:1994,
vinyl:0.065,
cd:8.7517,
streaming:0,
cassette:3.2513,
download:0,
},
{
year:1995,
vinyl:0.0718,
cd:9.7086,
streaming:0,
cassette:2.5399,
download:0,
},
{
year:1996,
vinyl:0.0843,
cd:10.3549,
streaming:0,
cassette:2.0946,
download:0,
},
{
year:1997,
vinyl:0.0689,
cd:10.5117,
streaming:0,
cassette:1.6562,
download:0,
},
{
year:1998,
vinyl:0.0597,
cd:12.1372,
streaming:0,
cassette:1.5143,
download:0,
},
{
year:1999,
vinyl:0.0597,
cd:13.4154,
streaming:0,
cassette:1.1096,
download:0,
},
{
year:2000,
vinyl:0.054,
cd:13.6391,
streaming:0,
cassette:0.6306,
download:0,
},
{
year:2001,
vinyl:0.0588,
cd:13.324,
streaming:0,
cassette:0.3581,
download:0,
},
{
year:2002,
vinyl:0.0454,
cd:12.3606,
streaming:0,
cassette:0.2082,
download:0,
},
{
year:2003,
vinyl:0.0432,
cd:11.7031,
streaming:0,
cassette:0.1081,
download:0,
},
{
year:2004,
vinyl:0.0392,
cd:12.0918,
streaming:0.0069,
cassette:0.0237,
download:0.1835,
},
{
year:2005,
vinyl:0.0274,
cd:11.1545,
streaming:0.1709,
cassette:0.0131,
download:0.9243,
},
{
year:2006,
vinyl:0.0256,
cd:9.8393,
streaming:0.2407,
cassette:0.0037,
download:1.65,
},
{
year:2007,
vinyl:0.0269,
cd:7.9558,
streaming:0.272,
cassette:0.003,
download:2.3924,
},
{
year:2008,
vinyl:0.0596,
cd:5.7064,
streaming:0.323,
cassette:0.0009,
download:2.6859,
},
{
year:2009,
vinyl:0.0663,
cd:4.5355,
streaming:0.3629,
cassette:0.0,
download:2.66,
},
{
year:2010,
vinyl:0.0912,
cd:3.5725,
streaming:0.4631,
cassette:0.0,
download:2.6934,
},
{
year:2011,
vinyl:0.124,
cd:3.257,
streaming:0.6554,
cassette:0.0,
download:2.9018,
},
{
year:2012,
vinyl:0.1655,
cd:2.607,
streaming:1.0362,
cassette:0,
download:3.0162,
},
{
year:2013,
vinyl:0.2137,
cd:2.2501,
streaming:1.4608,
cassette:0,
download:2.9203,
},
{
year:2014,
vinyl:0.2493,
cd:1.8725,
streaming:1.8352,
cassette:0,
download:2.5531,
},
{
year:2015,
vinyl:0.3391,
cd:1.5231,
streaming:2.3421,
cassette:0,
download:2.3107,
},
{
year:2016,
vinyl:0.3603,
cd:1.192,
streaming:4.0019,
cassette:0,
download:1.8294,
},
{
year:2017,
vinyl:0.3946,
cd:1.1009,
streaming:5.7167,
cassette:0,
download:1.3853,
},
{
year:2018,
vinyl:0.4245,
cd:0.7303,
streaming:7.4368,
cassette:0,
download:1.0173,
},
{
year:2019,
vinyl:0.5044,
cd:0.6439,
streaming:8.9132,
cassette:0,
download:0.8326,
},
]