Close Editor Run Reset Auto Update CJS const lcjs = require('@lightningchart/lcjs')
const { lightningChart, Themes, BarChartTypes, BarChartSorting, AxisTickStrategies, ColorHEX, ColorRGBA, emptyLine, SolidLine, SolidFill, emptyFill } = lcjs
const titles = {
sunBurstChart: 'Total Revenue by Category',
chartXY: 'Monthly Revenue vs. Profit',
barChart: 'Revenue vs. Profit by Category',
horizontalChart: 'TOP5 Subcategories by Revenue'
}
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
const monthlyRegionTotals = {}
const categoryRegionTotals = {}
let dataByRegion = []
let categories = []
let currentRegion = 'All'
// Initialize LightningChart JS
const lc = lightningChart()
const exampleContainer = document.getElementById('chart') || document.body
if (exampleContainer === document.body) {
exampleContainer.style.width = '100vw'
exampleContainer.style.height = '100vh'
exampleContainer.style.margin = '0px'
}
const kpiPanel = document.createElement('div')
exampleContainer.append(kpiPanel)
// Sunburst chart
const containerSunBurst = document.createElement('div')
exampleContainer.append(containerSunBurst)
const sunBurstChart = lc.
SunBurstChart({
container: containerSunBurst,
// theme: Themes.darkGold,
})
Object.assign(containerSunBurst.style, {
position: 'absolute',
left: '0px',
top: '10%',
width: '50%',
height: '90%',
})
const theme = sunBurstChart.getTheme()
const bgColor = theme.isDark ? '#0a0a0a' : '#fafafa'
const textColor = theme.isDark ? '#e6e4e4' : '#0a0a0a'
const bgFill = new SolidFill({ color: ColorHEX(bgColor) })
const fill1 = new SolidFill({ color: theme.sunBurstChartNodeColors[6] })
const fill2 = new SolidFill({ color: theme.sunBurstChartNodeColors[9] })
sunBurstChart
.setTitle(titles.sunBurstChart)
.setCursorFormatting((_, hit) => { return [`${hit.name} $${hit.value.toLocaleString('en-US', { maximumFractionDigits: 0 })}`] })
.setBackgroundFillStyle(bgFill)
// Add an EventListener to recognize what region is chosen
.addEventListener('viewchange', (event) => {
console.log(event)
if (event.nodes.length == 4) onSunburstClick('All')
else onSunburstClick(event.nodes[0].path[0])
})
sunBurstChart.engine.setBackgroundFillStyle(bgFill)
// KPI panel
const kpis = [
{title: "Total Revenue", id: "totRevenue"},
{title: "Total Profit", id: "totProfit"},
{title: "Total Orders", id: "totOrders"},
{title: "Average Order Value", id: "avgValue"}
]
kpis.forEach((kpi) => {
const childDiv = document.createElement('div')
childDiv.innerHTML = `
<p style="margin: 0; font-size: clamp(0.7rem, 1.2vw, 1.0rem); opacity: 0.8;">${kpi.title}</p>
<p style="margin: 0; font-size: clamp(0.7rem, 1.2vw, 1.0rem); font-weight: bold; padding-top: 4px;" id=${kpi.id}></p>
`
Object.assign(childDiv.style, {
flex: '1 1 0px',
textAlign: 'center',
padding: '2px',
whiteSpace: 'nowrap'
})
kpiPanel.appendChild(childDiv)
})
Object.assign(kpiPanel.style, {
position: 'absolute',
left: '0px',
top: '0px',
width: '50%',
height: '10%',
display: 'flex',
flexWrap: 'nowrap',
justifyContent: 'space-around',
alignItems: 'center',
padding: '6px',
boxSizing: 'border-box',
cursor: 'default',
fontFamily: theme.barChartTitleFont.family,
background: bgColor,
color: textColor,
overflow: 'hidden'
})
// XY chart
const containerXY = document.createElement('div')
exampleContainer.append(containerXY)
const chartXY = lc.
ChartXY({
container: containerXY,
// theme: Themes.darkGold,
})
Object.assign(containerXY.style, {
position: 'absolute',
right: '0px',
top: '0px',
width: '50%',
height: '35%',
})
chartXY
.setTitle(titles.chartXY)
.setBackgroundFillStyle(bgFill)
.setCursor((cursor) => cursor.setTickMarkerXVisible(false))
.setCursorFormatting((_, hit, hits) => {
const month = months[hit.x.toFixed(0) - 1]
return [
[{ text: month, font: { weight: 'bold' } }],
[`Revenue $${hits[0].y.toLocaleString('en-US', { maximumFractionDigits: 0 })}`],
[`Profit $${hits[1].y.toLocaleString('en-US', { maximumFractionDigits: 0 })}`],
]
})
chartXY.engine.setBackgroundFillStyle(bgFill)
chartXY.getDefaultAxisX()
.setTitle('Month')
.setTickStrategy(AxisTickStrategies.Numeric, ticks => ticks.setMajorFormattingFunction((value) => `${value.toFixed(0)}`))
const axisY1 = chartXY.getDefaultAxisY().setTitle('Revenue').setInterval({ start: 0, end: 2700000 })
const axisY2 = chartXY.addAxisY({ opposite: true }).setTitle('Profit').setInterval({ start: 0, end: 2700000 })
const revenueSeries = chartXY
.addLineSeries({
yAxis: axisY1,
schema: { x: { pattern: 'progressive' }, y: { pattern: null } },
})
.setStrokeStyle(new SolidLine({ thickness: 2, fillStyle: fill1 }))
const profitSeries = chartXY
.addLineSeries({
yAxis: axisY2,
schema: { x: { pattern: 'progressive' }, y: { pattern: null } },
})
.setStrokeStyle(new SolidLine({ thickness: 2, fillStyle: fill2 }))
// Vertical bar chart
const containerBar = document.createElement('div')
exampleContainer.append(containerBar)
const barChart = lc.
BarChart({
container: containerBar,
// theme: Themes.darkGold,
})
Object.assign(containerBar.style, {
position: 'absolute',
right: '0px',
top: '35%',
width: '50%',
height: '35%',
})
barChart
.setTitle(titles.barChart)
.setValueLabels({ formatter: (info) => `$${(info.value/1000000).toFixed(2)}M` })
.setBackgroundFillStyle(bgFill)
.setCursorFormatting((_, hit) => {
return [`${hit.subCategory} $${hit.value.toLocaleString('en-US', { maximumFractionDigits: 0 })}`]
})
barChart.engine.setBackgroundFillStyle(bgFill)
barChart.valueAxis.setInterval({ start: 0, end: 6_250_000 }).setTickStrategy(AxisTickStrategies.Empty)
// Horizontal bar chart
const containerHorizontal = document.createElement('div')
exampleContainer.append(containerHorizontal)
const horizontalChart = lc.
BarChart({
container: containerHorizontal,
type: BarChartTypes.Horizontal,
legend: {visible: false },
// theme: Themes.darkGold,
})
Object.assign(containerHorizontal.style, {
position: 'absolute',
right: '0px',
top: '70%',
width: '50%',
height: '30%',
})
horizontalChart
.setTitle(titles.horizontalChart)
.setBackgroundFillStyle(bgFill)
.setPadding({ left: 16, right: 6 })
.setValueLabels({
position: 'inside-bar',
formatter: (info) => `$${(info.value/1000000).toFixed(2)}M`,
})
.setCursorFormatting((_, hit) => {
return [`${hit.category} $${hit.value.toLocaleString('en-US', { maximumFractionDigits: 0 })}`]
})
horizontalChart.engine.setBackgroundFillStyle(bgFill)
horizontalChart.valueAxis.setInterval({ start: 0, end: 2_510_000 }).setTickStrategy(AxisTickStrategies.Empty)
fetch(document.head.baseURI + 'examples/assets/1713/product_sales.json')
.then((r) => r.json())
.then((data) => {
let totRevenue = 0
let totProfit = 0
let ordersCount = 0
// Format data for Sunburst chart
const formatData = (data) => {
const grouped = data.reduce((result, item) => {
const { Order_ID, Order_Date, Region, Items } = item
const month = Order_Date.split('-')[0]
let orderRevenue = 0
let orderProfit = 0
ordersCount++
Items.forEach(i => {
if (!result[Region]) result[Region] = { value: 0, children: {} }
result[Region].value += i.Line_Revenue
if (!result[Region].children[i.Category]) result[Region].children[i.Category] = { value: 0, children: {} }
result[Region].children[i.Category].value += i.Line_Revenue
if (!result[Region].children[i.Category].children[i.Sub_Category]) result[Region].children[i.Category].children[i.Sub_Category] = { value: 0 }
result[Region].children[i.Category].children[i.Sub_Category].value += i.Line_Revenue
if (!categoryRegionTotals[Region]) categoryRegionTotals[Region] = {}
if (!categoryRegionTotals[Region][i.Category]) categoryRegionTotals[Region][i.Category] = { revenue: 0, profit: 0 }
categoryRegionTotals[Region][i.Category].revenue += i.Line_Revenue
categoryRegionTotals[Region][i.Category].profit += i.Line_Profit
if (!categoryRegionTotals['All']) categoryRegionTotals['All'] = {}
if (!categoryRegionTotals['All'][i.Category]) categoryRegionTotals['All'][i.Category] = { revenue: 0, profit: 0 }
categoryRegionTotals['All'][i.Category].revenue += i.Line_Revenue
categoryRegionTotals['All'][i.Category].profit += i.Line_Profit
totRevenue += i.Line_Revenue
totProfit += i.Line_Profit
orderRevenue += i.Line_Revenue
orderProfit += i.Line_Profit
})
if (!monthlyRegionTotals[Region]) monthlyRegionTotals[Region] = {}
if (!monthlyRegionTotals[Region][month]) monthlyRegionTotals[Region][month] = { revenue: 0, profit: 0 }
monthlyRegionTotals[Region][month].revenue += orderRevenue
monthlyRegionTotals[Region][month].profit += orderProfit
if (!monthlyRegionTotals['All']) monthlyRegionTotals['All'] = {}
if (!monthlyRegionTotals['All'][month]) monthlyRegionTotals['All'][month] = { revenue: 0, profit: 0 }
monthlyRegionTotals['All'][month].revenue += orderRevenue
monthlyRegionTotals['All'][month].profit += orderProfit
return result
}, {})
return Object.keys(grouped).map(regionName => {
const region = grouped[regionName]
const categories = region.children
const children = Object.keys(categories).map(catName => {
const cat = categories[catName]
const subCategories = cat.children
return {
name: catName,
children: Object.keys(subCategories).map(subName => ({
name: subName,
value: subCategories[subName].value
}))
}
})
return { name: regionName, children: children }
})
}
// Add initial data to charts
dataByRegion = formatData(data)
categories = [...new Set(dataByRegion[0].children.map((c) => c.name))]
const avgValue = ordersCount > 0 ? totRevenue / ordersCount : 0
sunBurstChart.setData(dataByRegion)
revenueSeries.appendJSON(updateChartData(titles.chartXY, 'All', 'revenue'))
profitSeries.appendJSON(updateChartData(titles.chartXY, 'All', 'profit'))
barChart.setDataGrouped(categories, updateChartData(titles.barChart, 'All', ''))
barChart.getBars().forEach((bar) => {
if (bar.subCategory == 'Revenue') bar.setFillStyle(fill1)
else bar.setFillStyle(fill2)
})
horizontalChart.setData(updateChartData(titles.horizontalChart, 'All', ''))
//Format numbers on KPI panel
document.getElementById('totRevenue').innerHTML = `$${totRevenue.toLocaleString('en-US', { maximumFractionDigits: 0 })}`
document.getElementById('totProfit').innerHTML = `$${totProfit.toLocaleString('en-US', { maximumFractionDigits: 0 })}`
document.getElementById('totOrders').innerHTML = `${ordersCount.toLocaleString('en-US', { maximumFractionDigits: 0 })}`
document.getElementById('avgValue').innerHTML = `$${avgValue.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
})
.catch(error => console.error("Error loading data:", error))
// Update charts when clicking Sunburst chart node
const onSunburstClick = (chosenRegion) => {
if (!monthlyRegionTotals || !dataByRegion) return
if (chosenRegion != currentRegion) {
currentRegion = chosenRegion
revenueSeries.clear()
profitSeries.clear()
if (chosenRegion == 'All') {
sunBurstChart.setTitle(titles.sunBurstChart)
chartXY.setTitle(titles.chartXY)
barChart.setTitle(titles.barChart)
horizontalChart.setTitle(titles.horizontalChart)
} else {
sunBurstChart.setTitle(`${titles.sunBurstChart}: ${chosenRegion}`)
chartXY.setTitle(`${titles.chartXY}: ${chosenRegion}`)
barChart.setTitle(`${titles.barChart}: ${chosenRegion}`)
horizontalChart.setTitle(`${titles.horizontalChart}: ${chosenRegion}`)
}
revenueSeries.appendJSON(updateChartData(titles.chartXY, chosenRegion, 'revenue'))
profitSeries.appendJSON(updateChartData(titles.chartXY, chosenRegion, 'profit'))
barChart.setDataGrouped(categories, updateChartData(titles.barChart, chosenRegion, ''))
horizontalChart.setData(updateChartData(titles.horizontalChart, chosenRegion, ''))
}
}
// Filter chart data by chosen region
const updateChartData = (title, region, value) => {
let chartData = []
switch (title) {
case titles.chartXY:
const monthlyData = monthlyRegionTotals[region]
chartData = Object.keys(monthlyData)
.sort((a, b) => parseInt(a) - parseInt(b))
.map(monthStr => {
return {
x: parseInt(monthStr, 10),
y: monthlyData[monthStr][value]
}
})
break;
case titles.barChart:
const categoryData = categoryRegionTotals[region]
const revenueValues = []
const profitValues = []
categories.forEach(cat => {
revenueValues.push(categoryData[cat].revenue)
profitValues.push(categoryData[cat].profit)
})
chartData = [
{ subCategory: 'Revenue', values: revenueValues },
{ subCategory: 'Profit', values: profitValues }
]
break;
case titles.horizontalChart:
const subCategories = {}
const regions = region === 'All' ? dataByRegion : dataByRegion.filter(r => r.name === region)
regions.forEach(reg => {
reg.children.forEach(cat => {
cat.children.forEach(subCat => {
if (!subCategories[subCat.name]) subCategories[subCat.name] = 0
subCategories[subCat.name] += subCat.value
})
})
})
chartData = Object.keys(subCategories)
.map(subName => ({ category: subName, value: subCategories[subName] }))
.sort((a, b) => b.value - a.value)
.slice(0, 5)
break;
default:
break;
}
return chartData
} SunBurst Chart Dashboard - Editor A Sunburst Chart dashboard displaying simulated regional product sales data.
The SunBurstChart provides a hierarchical drill-down interface, allowing users to navigate through multi-level data segments including Region, Category, and Subcategory. This example utilizes a viewchange event listener to identify the selected node's path, enabling the application to interpret the user's focus within the radial hierarchy and filter the dataset accordingly.
Below and beside the primary visualization, several additional components visualize the filtered data:
KPI Panel displaying Total Revenue, Total Profit, Total Orders, and Average Order Value. ChartXY with two Y-axes to visualize Monthly Revenue vs. Profit trends.Vertical BarChart comparing Revenue and Profit across major product categories. Horizontal BarChart highlighting the TOP5 Subcategories by Revenue. The peripheral charts dynamically update to reflect the currently selected node in the Sunburst Chart.
Original data source: Product Sales Dataset 2023-2024 . The data has been restructured to support hierarchical visualization and to simulate realistic regional sales variances.