❮ Back to Gallery
Line Chart with Data Gaps
Select a fallback value for missing data points
National Cereal Production in metric tons (millions) (Source: World Bank Open Data)
- React
- Angular
- Svelte
- Vue
- Solid
- TypeScript
- Data
import React, { useCallback, useEffect, useState, useMemo } from 'react'
import { VisXYContainer, VisLine, VisAxis, VisXYLabels, VisBulletLegend } from '@unovis/react'
import { countries, data, legendItems, Country, DataRecord } from './data'
import './styles.css'
export default function BasicLineChart (): JSX.Element {
const [current, setCurrent] = useState(0)
const [fallbackValue, setFallbackValue] = useState<number | null | undefined>()
const [items, setLegendItems] = useState([])
const getY = useCallback<(c: Country) => ((d: DataRecord) => number)>(
(c) => (d: DataRecord) => d[c.id],
[]
)
useEffect(() => {
setFallbackValue(legendItems[current].value)
setLegendItems(legendItems.map((o, i) => ({
name: o.name,
inactive: current !== i,
color: countries[0].color,
})))
}, [current])
return (<>
<div className="fallbackValueSwitch">
Select a fallback value for missing data points
<VisBulletLegend items={items} onLegendItemClick={useCallback((_, i: number) => setCurrent(i), [])}/>
</div>
<VisXYContainer duration={0} height={'50vh'} xDomain={[1961, 2022]} yDomain={[0, 650]}>
<VisLine
data={data}
x={useCallback((d: DataRecord) => d.year, [])}
y={useMemo(() => countries.map(getY), [])}
fallbackValue={fallbackValue}
/>
<VisXYLabels
data={countries}
x={2019.5}
y={useCallback((c: Country) => getY(c)(data[data.length - 1]), [])}
label={useCallback((c: Country) => c.label, [])}
backgroundColor='none'
/>
<VisAxis type="x" numTicks={10}/>
<VisAxis
type="y"
label="National Cereal Production, tons"
tickFormat={(d: number) => `${d}${d ? 'M' : ''}`}
tickValues={[0, 200, 400, fallbackValue || undefined, 600]}
/>
</VisXYContainer>
</>)
}
data-gap-line-chart.html
<div class="fallbackValueSwitch">
Select a fallback value for missing data points:
<vis-bullet-legend [items]="legendItems" [onLegendItemClick]="legendItemClick"></vis-bullet-legend>
</div>
<vis-xy-container [duration]="0" [height]="300" [xDomain]="[1961,2022]" [yDomain]="[0, 650]">
<vis-line [data]="data" [x]="x" [y]="y" [fallbackValue]="fallbackValue"></vis-line>
<vis-xy-labels [data]="labels.data" [x]="2019.5" [y]="labels.y" [label]="labels.label" backgroundColor="none"></vis-xy-labels>
<vis-axis type="x" [numTicks]="10"></vis-axis>
<vis-axis
type="y"
label="National Cereal Production, tons"
[tickFormat]="tickFormat"
[tickValues]="[0, 200, 400, fallbackValue, 600]"
></vis-axis>
</vis-xy-container>
data-gap-line-chart.component.ts
import { Component } from '@angular/core'
import { BulletLegendItemInterface } from '@unovis/ts'
import { data, countries, legendItems, Country, DataRecord } from './data'
@Component({
selector: 'data-gap-line-chart',
templateUrl: './data-gap-line-chart.component.html',
styleUrls: ['./styles.css'],
})
export class DataGapLineChartComponent {
// shared y accessor
yAccessor (c: Country): (d: DataRecord) => number {
return (d: DataRecord) => d[c.id]
}
// index for current fallback value
private _curr = 0
// line config
data = data
x = (d: DataRecord): number => d.year
y: ((d: DataRecord) => number)[] = countries.map(this.yAccessor)
get fallbackValue (): undefined | null | number {
return legendItems[this._curr].value
}
// label config
labels = ({
data: countries,
y: (c: Country): number => this.yAccessor(c)(data[data.length - 1]),
label: (c: Country): string => c.label,
})
tickFormat = (d: number): string => `${d}${d ? 'M' : ''}`
// legend config
legendItemClick = (_, i: number): void => { this._curr = i }
get legendItems (): BulletLegendItemInterface[] {
return legendItems.map((o, i) => ({
name: o.name,
inactive: this._curr !== i,
color: countries[0].color,
}))
}
}
data-gap-line-chart.module.ts
import { NgModule } from '@angular/core'
import { VisXYContainerModule, VisLineModule, VisAxisModule, VisXYLabelsModule, VisBulletLegendModule } from '@unovis/angular'
import { DataGapLineChartComponent } from './data-gap-line-chart.component'
@NgModule({
imports: [VisXYContainerModule, VisLineModule, VisAxisModule, VisXYLabelsModule, VisBulletLegendModule],
declarations: [DataGapLineChartComponent],
exports: [DataGapLineChartComponent],
})
export class DataGapLineChartModule { }
<script lang='ts'>
import { VisXYContainer, VisLine, VisBulletLegend, VisAxis, VisXYLabels } from '@unovis/svelte'
import type { DataRecord, Country } from './data'
import { countries, data, legendItems } from './data'
function getY (c: Country): (d: DataRecord) => number {
return (d: DataRecord) => d[c.id]
}
const x = (d: DataRecord): number => d.year
const y = countries.map(getY)
const labelConfig = {
data: countries,
x: 2019.5,
y: (c: Country) => getY(c)(data[data.length - 1]),
label: (c: Country) => c.label,
}
let curr = 0
$: fallbackValue = legendItems[curr].value
$: items = legendItems.map((o, i) => ({
name: o.name,
inactive: curr !== i,
color: countries[0].color,
}))
function onLegendItemClick (_, i: number): void {
curr = i
}
</script>
<div class="fallbackValueSwitch">
Select a fallback value for missing data points:
<VisBulletLegend {items} {onLegendItemClick}/>
</div>
<VisXYContainer duration={0} height={300} xDomain={[1961, 2022]} yDomain={[0, 650]}>
<VisLine {data} {x} {y} {fallbackValue}/>
<VisXYLabels backgroundColor='none' {...labelConfig}/>
<VisAxis type='x' numTicks={10}/>
<VisAxis
type='y'
label='National Cereal Production, tons'
tickFormat={d => `${d}${d ? 'M' : ''}`}
tickValues={[0, 200, 400, fallbackValue, 600]}
/>
</VisXYContainer>
<style>
.fallbackValueSwitch {
width: 415px;
background-color: #f8f8f8;
padding: 10px 20px;
display: inline-block;
border-radius: 5px;
border: 1px solid #f4f4f4;
margin-bottom: 10px;
}
</style>
<script setup lang="ts">
import { VisXYContainer, VisLine, VisBulletLegend, VisAxis, VisXYLabels } from '@unovis/vue'
import type { DataRecord, Country } from './data'
import { countries, data, legendItems } from './data'
import { ref, computed } from "vue"
function getY(c: Country): (d: DataRecord) => number {
return (d: DataRecord) => d[c.id]
}
const x = (d: DataRecord): number => d.year
const y = countries.map(getY)
const labelConfig = {
data: countries,
x: 2019.5,
y: (c: Country) => getY(c)(data[data.length - 1]),
label: (c: Country) => c.label,
}
const curr = ref(0)
const fallbackValue = computed(() => legendItems[curr.value].value)
const items = computed(() => legendItems.map((o, i) => ({
name: o.name,
inactive: curr.value !== i,
color: countries[0].color,
})))
function onLegendItemClick(_, i: number): void {
curr.value = i
}
</script>
<template>
<div class="fallbackValueSwitch"> Select a fallback value for missing data points:
<VisBulletLegend :items="items" :onLegendItemClick="onLegendItemClick" />
</div>
<VisXYContainer :duration="0" :height="300" :xDomain="[1961, 2022]" :yDomain="[0, 650]">
<VisLine :data="data" :x="x" :y="y" :fallbackValue="fallbackValue" />
<VisXYLabels :style="{ backgroundColor: 'none' }" v-bind="labelConfig" />
<VisAxis type='x' :numTicks="10" />
<VisAxis type='y' label='National Cereal Production, tons' :tickFormat="d => `${d}${d ? 'M' : ''}`"
:tickValues="[0, 200, 400, fallbackValue, 600]" />
</VisXYContainer>
</template>
<style>
.fallbackValueSwitch {
width: 415px;
background-color: #f8f8f8;
padding: 10px 20px;
display: inline-block;
border-radius: 5px;
border: 1px solid #f4f4f4;
margin-bottom: 10px;
}
</style>
import { createMemo, createSignal, JSX } from 'solid-js'
import { VisAxis, VisBulletLegend, VisLine, VisXYContainer, VisXYLabels } from '@unovis/solid'
import './styles.css'
import { countries, data, legendItems, Country, DataRecord } from './data'
const DataGapLineChart = (): JSX.Element => {
function getY (c: Country): (d: DataRecord) => number {
return (d: DataRecord) => d[c.id]
}
const x = (d: DataRecord): number => d.year
const y = countries.map(getY)
const labelConfig = {
data: countries,
x: 2019.5,
y: (c: Country) => getY(c)(data[data.length - 1]),
label: (c: Country) => c.label,
}
const [curr, setCurr] = createSignal(0)
const fallbackValue = createMemo(() => legendItems[curr()].value)
const items = createMemo(() =>
legendItems.map((o, i) => ({
name: o.name,
inactive: curr() !== i,
color: countries[0].color,
}))
)
function onLegendItemClick (_, i: number): void {
setCurr(i)
}
return (
<div>
<div class='fallbackValueSwitch'>
Select a fallback value for missing data points:
<VisBulletLegend
items={items()}
onLegendItemClick={onLegendItemClick}
/>
</div>
<VisXYContainer
duration={0}
height='50dvh'
xDomain={[1961, 2022]}
yDomain={[0, 650]}
>
<VisLine data={data} x={x} y={y} fallbackValue={fallbackValue()} />
<VisXYLabels backgroundColor='none' {...labelConfig} />
<VisAxis type='x' numTicks={10} />
<VisAxis
type='y'
label='National Cereal Production, tons'
tickFormat={(d) => `${d}${d ? 'M' : ''}`}
tickValues={[0, 200, 400, fallbackValue(), 600]}
/>
</VisXYContainer>
</div>
)
}
export default DataGapLineChart
import { Axis, BulletLegend, Line, XYContainer, XYLabels } from '@unovis/ts'
import { data, countries, legendItems, Country, DataRecord } from './data'
const container = document.getElementById('vis-container')
function yAccessor (c: Country): (d: DataRecord) => number {
return (d: DataRecord) => d[c.id]
}
// Fallback Value Switch
const div = document.createElement('div')
div.className = 'fallbackValueSwitch'
container.appendChild(div)
div.innerHTML = 'Select a fallback value for missing data points: '
const legend = new BulletLegend(div)
// Line chart
const line = new Line<DataRecord>({
x: (d: DataRecord) => d.year,
y: countries.map(yAccessor),
})
// XY Labels
const labels = new XYLabels<Country>({
x: 2019.5,
y: (c: Country) => yAccessor(c)(data[data.length - 1]),
label: (c: Country) => c.label,
backgroundColor: 'none',
})
labels.setData(countries)
// Container
const chart = new XYContainer(container, {
duration: 0,
height: 300,
xDomain: [1961, 2022],
yDomain: [0, 650],
components: [line, labels],
xAxis: new Axis({ numTicks: 10 }),
yAxis: new Axis({
label: 'National Cereal Production, tons',
tickFormat: (d: number) => `${d}${d ? 'M' : ''}`,
tickValues: [0, 200, 400, 600],
}),
}, data)
function setFallbackValue (index: number): void {
legend.update({
items: legendItems.map((o, i) => ({
name: o.name,
inactive: index !== i,
color: countries[0].color,
})),
onLegendItemClick: (_, i: number) => setFallbackValue(i),
})
line.setConfig({ ...line.config, fallbackValue: legendItems[index].value })
line.render()
}
setFallbackValue(0)
export type Country = {
id: string;
label: string;
color: string;
}
export type DataRecord = {
year: number;
us: number;
cn?: number;
}
export const countries: Country[] = [{
id: 'cn',
label: 'China',
color: 'var(--vis-color0)',
}, {
id: 'us',
label: 'USA',
color: 'var(--vis-color1)',
}]
export const legendItems = [
{ name: 'undefined', value: undefined },
{ name: '0 or null', value: null },
{ name: '300M', value: 300 },
]
export const data = [
{
year: 1961,
cn: 109.659976,
us: 163.619978,
},
{
year: 1962,
cn: 120.421293,
us: 162.45578,
},
{
year: 1963,
cn: 137.456233,
us: 174.812487,
},
{
year: 1964,
cn: 152.356625,
us: 160.937079,
},
{
year: 1965,
cn: 162.156281,
us: 183.602617,
},
{
year: 1966,
cn: 177.613486,
us: 184.44488,
},
{
year: 1967,
cn: 181.182167,
us: 208.158055,
},
{
year: 1968,
cn: 177.133015,
us: 202.538423,
},
{
year: 1969,
cn: 176.486754,
us: 205.28817,
},
{
year: 1970,
us: 186.860751,
},
{
year: 1971,
us: 237.624461,
},
{
year: 1972,
us: 228.117866,
},
{
year: 1973,
us: 237.683006,
},
{
year: 1974,
us: 204.617505,
},
{
year: 1975,
us: 249.283743,
},
{
year: 1976,
us: 258.200097,
},
{
year: 1977,
us: 266.014469,
},
{
year: 1978,
us: 276.602542,
},
{
year: 1979,
us: 302.62558,
},
{
year: 1980,
cn: 280.287437,
us: 269.883982,
},
{
year: 1981,
cn: 286.450038,
us: 330.889528,
},
{
year: 1982,
cn: 315.36405,
us: 333.103755,
},
{
year: 1983,
cn: 345.626506,
us: 207.657604,
},
{
year: 1984,
cn: 365.937335,
us: 314.7495,
},
{
year: 1985,
cn: 339.877377,
us: 347.118216,
},
{
year: 1986,
cn: 352.084647,
us: 315.331216,
},
{
year: 1987,
cn: 359.240676,
us: 280.494047,
},
{
year: 1988,
cn: 351.82429,
us: 206.5281,
},
{
year: 1989,
cn: 367.63608,
us: 284.238058,
},
{
year: 1990,
cn: 404.719096,
us: 312.410604,
},
{
year: 1991,
cn: 398.896071,
us: 280.063391,
},
{
year: 1992,
cn: 404.275226,
us: 353.025147,
},
{
year: 1993,
cn: 407.930462,
us: 259.105342,
},
{
year: 1994,
cn: 396.46012,
us: 355.934924,
},
{
year: 1995,
cn: 418.664201,
us: 277.60121,
},
{
year: 1996,
cn: 453.4393,
us: 335.780123,
},
{
year: 1997,
cn: 445.931409,
us: 336.582161,
},
{
year: 1998,
cn: 458.394739,
us: 349.425744,
},
{
year: 1999,
cn: 455.192431,
us: 335.364364,
},
{
year: 2000,
cn: 407.336509,
us: 342.631506,
},
{
year: 2001,
us: 324.994634,
},
{
year: 2002,
us: 297.143452,
},
{
year: 2003,
us: 348.247631,
},
{
year: 2004,
us: 389.023763,
},
{
year: 2005,
us: 366.436346,
},
{
year: 2006,
cn: 452.800282,
us: 338.3368,
},
{
year: 2007,
cn: 457.809418,
us: 415.13086,
},
{
year: 2008,
cn: 480.12597,
us: 402.399936,
},
{
year: 2009,
cn: 483.277147,
us: 418.666166,
},
{
year: 2010,
cn: 497.920509,
us: 401.12633,
},
{
year: 2011,
cn: 521.171808,
us: 385.545256,
},
{
year: 2012,
cn: 541.163568,
us: 356.210124,
},
{
year: 2013,
cn: 554.422547,
us: 434.30845,
},
{
year: 2014,
cn: 559.325222,
us: 442.84909,
},
{
year: 2015,
cn: 623.197344,
us: 431.870788,
},
{
year: 2016,
cn: 618.011522,
us: 503.465267,
},
{
year: 2017,
cn: 619.879237,
us: 466.847085,
},
{
year: 2018,
cn: 612.170193,
us: 467.95114,
},
]