❮ Back to Gallery
Dual Axis Chart
Randomly Generated Data
This example shows how to create dual axis chart by overlapping two charts. Dual Axis chart maybe good at showing correlations, or compare two datasets with different measurement units. But we suggest using it with caution. Here are some good articles from Datawrapper and Florish.
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
- Data
import React from 'react'
import { VisXYContainer, VisArea, VisLine, VisAxis } from '@unovis/react'
import { XYDataRecord, generateXYDataRecords } from './data'
export default function component (): JSX.Element {
const margin = { left: 100, right: 100, top: 40, bottom: 60 }
const style: React.CSSProperties = { position: 'absolute', top: 0, left: 0, width: '100%', height: '40vh' }
return (<>
<VisXYContainer
data={generateXYDataRecords(150)}
margin={margin}
autoMargin={false}
width= {'100%'}
height= {'40vh'}
>
<VisArea<XYDataRecord> x={d => d.x} y={(d: XYDataRecord, i: number) => i * (d.y || 0)} opacity={0.9} />
<VisAxis type='x' numTicks={3} tickFormat={(x: number) => `${x}ms`} label='Time' />
<VisAxis type='y'
tickFormat={(y: number) => `${y}bps`}
tickTextWidth={60}
tickTextColor='#4D8CFD'
labelColor='#4D8CFD'
label='Traffic'
/>
</VisXYContainer>
<VisXYContainer
data={generateXYDataRecords(150)}
yDomain={[0, 150]}
margin={margin}
autoMargin={false}
style={style}
>
<VisLine<XYDataRecord> x={d => d.x} y={d => 20 + 10 * (d.y2 || 0)} color='#FF6B7E' />
<VisAxis
type='y'
position={'right'}
tickFormat={(y: number) => `${y}db`}
gridLine={false}
tickTextColor='#FF6B7E'
labelColor='#FF6B7E'
label='Signal Strength'
/>
</VisXYContainer>
</>
)
}
dual-axis-chart.html
<vis-xy-container
[data]="data"
[margin]="margin"
[autoMargin]="false"
[width]="'100%'"
[height]="'40vh'"
>
<vis-area [x]="chartX" [y]="chartAY" opacity="0.9" />
<vis-axis type='x' [numTicks]="3" [tickFormat]="xTicks" label="Time"/>
<vis-axis type='y'
[tickFormat]="chartAYTicks"
[tickTextWidth]="60"
[tickTextColor]="'#FF6B7E'"
[labelColor]="'#FF6B7E'"
label="Traffic"
/>
</vis-xy-container>
<vis-xy-container
[data]="data"
[yDomain]="[0, 150]"
[margin]="margin"
[autoMargin]="false"
[style]="style"
>
<vis-line [x]="chartX" [y]="chartBY" color='#FF6B7E' />
<vis-axis
type="y"
[position]="'right'"
[tickFormat]="chartBYTicks"
[gridLine]="false"
[tickTextColor]="'#4D8CFD'"
[labelColor]="'#4D8CFD'"
label="Signal Strength"
/>
</vis-xy-container>
dual-axis-chart.component.ts
import { Component } from '@angular/core'
import { XYDataRecord, generateXYDataRecords } from './data'
@Component({
selector: 'dual-axis-chart',
templateUrl: './dual-axis-chart.component.html',
})
export class DualAxisChartComponent {
data = generateXYDataRecords(150)
margin = { left: 100, right: 100, top: 40, bottom: 60 }
style = { position: 'absolute', top: 0, left: 0, width: '100%', height: '40vh' }
chartX = (d: XYDataRecord): number => d.x
chartAY = (d: XYDataRecord, i: number): number => i * (d.y || 0)
chartBY = (d: XYDataRecord): number => 20 + 10 * (d.y2 || 0)
xTicks = (x: number): string => `${x}ms`
chartAYTicks = (y: number): string => `${y}bps`
chartBYTicks = (y: number): string => `${y}db`
}
dual-axis-chart.module.ts
import { NgModule } from '@angular/core'
import { VisXYContainerModule, VisAxisModule, VisLineModule, VisAreaModule } from '@unovis/angular'
import { DualAxisChartComponent } from './dual-axis-chart.component'
@NgModule({
imports: [VisXYContainerModule, VisAreaModule, VisAxisModule, VisLineModule],
declarations: [DualAxisChartComponent],
exports: [DualAxisChartComponent],
})
export class DualAxisChartModule { }
<script lang='ts'>
import { VisXYContainer, VisArea, VisAxis, VisLine } from '@unovis/svelte'
import { XYDataRecord, generateXYDataRecords } from './data'
const margin = { left: 100, right: 100, top: 40, bottom: 60 }
const style: React.CSSProperties = { position: 'absolute', top: 0, left: 0, width: '100%', height: '40vh' }
const chartX = d => d.x
const chartAY = (d: XYDataRecord, i: number) => i * (d.y || 0)
const chartBY = (d: XYDataRecord) => 20 + 10 * (d.y2 || 0)
const xTicks = (x: number) => `${x}ms`
const chartAYTicks = (y: number) => `${y}bps`
const chartBYTicks = (y: number) => `${y}db`
</script>
<div>
<VisXYContainer
data={generateXYDataRecords(150)}
autoMargin={false}
margin={margin}
width= {'100%'}
height= {'40vh'}
>
<VisArea x={chartX} y={chartAY} opacity={0.9} />
<VisAxis type='x' numTicks={3} tickFormat={xTicks} label='Time'/>
<VisAxis type='y'
tickFormat={chartAYTicks}
tickTextWidth={60}
tickTextColor={'#4D8CFD'}
labelColor={'#4D8CFD'}
label='Traffic'
/>
</VisXYContainer>
<VisXYContainer
data={generateXYDataRecords(150)}
yDomain={[0, 150]}
margin={margin}
autoMargin={false}
width= {'100%'}
height= {'40vh'}
class={'chartContainer'}
>
<VisLine x={chartX} y={chartBY} color={'#FF6B7E'} />
<VisAxis
gridLine={false}
tickTextColor={'#FF6B7E'}
labelColor={'#FF6B7E'}
label='Signal Strength'
/>
</VisXYContainer>
</div>
<style>
div :global(.chartContainer) {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 40vh;
}
</style>
<script setup lang="ts">
import { VisXYContainer, VisArea, VisAxis, VisLine } from '@unovis/vue'
import { XYDataRecord, generateXYDataRecords } from './data'
const margin = { left: 100, right: 100, top: 40, bottom: 60 }
const style: React.CSSProperties = { position: 'absolute', top: 0, left: 0, width: '100%', height: '40vh' }
const chartX = d => d.x
const chartAY = (d: XYDataRecord, i: number) => i * (d.y || 0)
const chartBY = (d: XYDataRecord) => 20 + 10 * (d.y2 || 0)
const xTicks = (x: number) => `${x}ms`
const chartAYTicks = (y: number) => `${y}bps`
const chartBYTicks = (y: number) => `${y}db`
</script>
<template>
<VisXYContainer
:data=generateXYDataRecords(150)
:margin= margin
:autoMargin= false
:width="'100%'"
:height= "'40vh'"
>
<VisArea :x=chartX :y=chartAY :opacity=0.9 />
<VisAxis type='x' :numTicks=3 :tickFormat="xTicks" :label="'Time'"/>
<VisAxis type='y'
:tickFormat="chartAYTicks"
:tickTextWidth=60
:tickTextColor="'#4D8CFD'"
:labelColor="'#4D8CFD'"
:label="'Traffic'"
/>
</VisXYContainer>
<VisXYContainer
:data=generateXYDataRecords(150)
:yDomain="[0, 150]"
:margin=margin
:autoMargin=false
:style=style
>
<VisLine :x=chartX :y=chartBY :color="'#FF6B7E'" />
<VisAxis
type='y'
:position="'right'"
:tickFormat="chartBYTicks"
:gridLine=false
:tickTextColor="'#FF6B7E'"
:labelColor="'#FF6B7E'"
:label="'Signal Strength'"
/>
</VisXYContainer>
</template>
import { JSX } from 'solid-js'
import { VisArea, VisAxis, VisLine, VisXYContainer } from '@unovis/solid'
import { Position } from '@unovis/ts'
import { XYDataRecord, generateXYDataRecords } from './data'
const DualAxisChart = (): JSX.Element => {
const margin = { left: 100, right: 100, top: 40, bottom: 60 }
const style: JSX.CSSProperties = {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '40vh',
}
const chartX = (d: XYDataRecord) => d.x
const chartAY = (d: XYDataRecord, i: number) => i * (d.y || 0)
const chartBY = (d: XYDataRecord) => 20 + 10 * (d.y2 || 0)
const xTicks = (x: number) => `${x}ms`
const chartAYTicks = (y: number) => `${y}bps`
const chartBYTicks = (y: number) => `${y}db`
return (
<div>
<VisXYContainer
data={generateXYDataRecords(150)}
margin={margin}
height='40dvh'
width='100%'
autoMargin={false}
>
<VisArea x={chartX} y={chartAY} opacity={0.9} />
<VisAxis type='x' numTicks={3} tickFormat={xTicks} label='Time' />
<VisAxis
type='y'
tickFormat={chartAYTicks}
tickTextWidth={60}
tickTextColor='#4D8CFD'
labelColor='#4D8CFD'
label='Traffic'
/>
</VisXYContainer>
<VisXYContainer
data={generateXYDataRecords(150)}
yDomain={[0, 100]}
margin={margin}
autoMargin={false}
style={style}
>
<VisLine x={chartX} y={chartBY} color='#FF6B7E' />
<VisAxis
type='y'
position={Position.Right}
tickFormat={chartBYTicks}
gridLine={false}
tickTextColor='#FF6B7E'
labelColor='#FF6B7E'
label='Signal Strength'
/>
</VisXYContainer>
</div>
)
}
export default DualAxisChart
import { XYContainer, Area, Line, Axis } from '@unovis/ts'
import { XYDataRecord, generateXYDataRecords } from './data'
import './styles.css'
const container = document.getElementById('vis-container')
const chartAContainer = document.createElement('div')
container.appendChild(chartAContainer)
const chartBContainer = document.createElement('div')
chartBContainer.className = 'chartContainer'
container.appendChild(chartBContainer)
const margin = { left: 100, right: 100, top: 40, bottom: 60 }
// Area
const area = new Area<XYDataRecord>({
x: (d: XYDataRecord) => d.x,
y: (d: XYDataRecord, i: number) => i * (d.y || 0),
opacity: 0.9,
})
const line = new Line<XYDataRecord>({
x: (d: XYDataRecord) => d.x,
y: (d: XYDataRecord, i: number) => 20 + 10 * (d.y2 || 0),
color: '#FF6B7E',
})
// Container
const chartA = new XYContainer(chartAContainer, {
height: '40vh',
width: '100%',
position: 'absolute',
components: [area],
margin: margin,
autoMargin: false,
xAxis: new Axis({ label: 'Time' }),
yAxis: new Axis({
label: 'Traffic',
tickFormat: (y: number) => `${y}bps`,
tickTextWidth: 60,
tickTextColor: '#4D8CFD',
labelColor: '#4D8CFD',
}),
}, generateXYDataRecords(150))
const chartB = new XYContainer(chartBContainer, {
components: [line],
yDomain: [0, 150],
margin: margin,
autoMargin: false,
yAxis: new Axis({
position: 'right',
tickFormat: (y: number) => `${y}db`,
gridLine: false,
tickTextColor: '#FF6B7E',
labelColor: '#FF6B7E',
label: 'Signal Strength',
}),
}, generateXYDataRecords(150))
export type XYDataRecord = {
x: number;
y: number | undefined;
y1?: number;
y2?: number;
}
export function generateXYDataRecords (n = 10): XYDataRecord[] {
return Array(n).fill(0).map((_, i) => ({
x: i,
y: 5 + 5 * Math.random(),
y1: 1 + 3 * Math.random(),
y2: 2 * Math.random(),
}))
}