diff --git a/packages/shared/examples/_previews/dual-axis-chart-dark.png b/packages/shared/examples/_previews/dual-axis-chart-dark.png
new file mode 100644
index 000000000..220afee22
Binary files /dev/null and b/packages/shared/examples/_previews/dual-axis-chart-dark.png differ
diff --git a/packages/shared/examples/_previews/dual-axis-chart.png b/packages/shared/examples/_previews/dual-axis-chart.png
new file mode 100644
index 000000000..9a567ec57
Binary files /dev/null and b/packages/shared/examples/_previews/dual-axis-chart.png differ
diff --git a/packages/shared/examples/dual-axis-chart/data.ts b/packages/shared/examples/dual-axis-chart/data.ts
new file mode 100644
index 000000000..c2c097b98
--- /dev/null
+++ b/packages/shared/examples/dual-axis-chart/data.ts
@@ -0,0 +1,15 @@
+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(),
+ }))
+}
diff --git a/packages/shared/examples/dual-axis-chart/dual-axis-chart.component.html b/packages/shared/examples/dual-axis-chart/dual-axis-chart.component.html
new file mode 100644
index 000000000..feafb7de2
--- /dev/null
+++ b/packages/shared/examples/dual-axis-chart/dual-axis-chart.component.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/shared/examples/dual-axis-chart/dual-axis-chart.component.ts b/packages/shared/examples/dual-axis-chart/dual-axis-chart.component.ts
new file mode 100644
index 000000000..363bdc59e
--- /dev/null
+++ b/packages/shared/examples/dual-axis-chart/dual-axis-chart.component.ts
@@ -0,0 +1,20 @@
+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`
+}
diff --git a/packages/shared/examples/dual-axis-chart/dual-axis-chart.module.ts b/packages/shared/examples/dual-axis-chart/dual-axis-chart.module.ts
new file mode 100644
index 000000000..d89c64190
--- /dev/null
+++ b/packages/shared/examples/dual-axis-chart/dual-axis-chart.module.ts
@@ -0,0 +1,11 @@
+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 { }
diff --git a/packages/shared/examples/dual-axis-chart/dual-axis-chart.svelte b/packages/shared/examples/dual-axis-chart/dual-axis-chart.svelte
new file mode 100644
index 000000000..c9addae6b
--- /dev/null
+++ b/packages/shared/examples/dual-axis-chart/dual-axis-chart.svelte
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/shared/examples/dual-axis-chart/dual-axis-chart.ts b/packages/shared/examples/dual-axis-chart/dual-axis-chart.ts
new file mode 100644
index 000000000..4fe95c7c9
--- /dev/null
+++ b/packages/shared/examples/dual-axis-chart/dual-axis-chart.ts
@@ -0,0 +1,59 @@
+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({
+ x: (d: XYDataRecord) => d.x,
+ y: (d: XYDataRecord, i: number) => i * (d.y || 0),
+ opacity: 0.9,
+ color: '#FF6B7E',
+})
+
+const line = new Line({
+ x: (d: XYDataRecord) => d.x,
+ y: (d: XYDataRecord, i: number) => 20 + 10 * (d.y2 || 0),
+})
+
+// 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: '#FF6B7E',
+ labelColor: '#FF6B7E',
+ }),
+}, 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: '#4D8CFD',
+ labelColor: '#4D8CFD',
+ label: 'Signal Strength',
+ }),
+}, generateXYDataRecords(150))
diff --git a/packages/shared/examples/dual-axis-chart/dual-axis-chart.tsx b/packages/shared/examples/dual-axis-chart/dual-axis-chart.tsx
new file mode 100644
index 000000000..ac9127c96
--- /dev/null
+++ b/packages/shared/examples/dual-axis-chart/dual-axis-chart.tsx
@@ -0,0 +1,47 @@
+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 (<>
+
+ x={d => d.x} y={(d: XYDataRecord, i: number) => i * (d.y || 0)} opacity={0.9} color='#FF6B7E' />
+ `${x}ms`} label='Time' />
+ `${y}bps`}
+ tickTextWidth={60}
+ tickTextColor='#FF6B7E'
+ labelColor='#FF6B7E'
+ label='Traffic'
+ />
+
+
+ x={d => d.x} y={d => 20 + 10 * (d.y2 || 0)} />
+ `${y}db`}
+ gridLine={false}
+ tickTextColor='#4D8CFD'
+ labelColor='#4D8CFD'
+ label='Signal Strength'
+ />
+
+ >
+ )
+}
diff --git a/packages/shared/examples/dual-axis-chart/dual-axis-chart.vue b/packages/shared/examples/dual-axis-chart/dual-axis-chart.vue
new file mode 100644
index 000000000..e41bb0eac
--- /dev/null
+++ b/packages/shared/examples/dual-axis-chart/dual-axis-chart.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/shared/examples/dual-axis-chart/index.tsx b/packages/shared/examples/dual-axis-chart/index.tsx
new file mode 100644
index 000000000..6d190cb06
--- /dev/null
+++ b/packages/shared/examples/dual-axis-chart/index.tsx
@@ -0,0 +1,32 @@
+/* eslint-disable import/no-unresolved, import/no-webpack-loader-syntax, @typescript-eslint/no-var-requires */
+import React from 'react'
+import type { Example } from '../types'
+
+const pathname = 'dual-axis-chart'
+const example: Example = {
+ component: () => {
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ const Component = require(`./${pathname}.tsx`).default
+ return
+ },
+ pathname,
+ title: 'Dual Axis Chart',
+ description:
+ Randomly Generated Data
+
*This example shows how to create dual axis chart by overlapping two charts
+
,
+ codeReact: require(`!!raw-loader!./${pathname}.tsx`).default,
+ codeTs: require(`!!raw-loader!./${pathname}.ts`).default,
+ codeAngular: {
+ html: require(`!!raw-loader!./${pathname}.component.html`).default,
+ component: require(`!!raw-loader!./${pathname}.component.ts`).default,
+ module: require(`!!raw-loader!./${pathname}.module.ts`).default,
+ },
+ codeSvelte: require(`!!raw-loader!./${pathname}.svelte`).default,
+ codeVue: require(`!!raw-loader!./${pathname}.vue`).default,
+ data: require('!!raw-loader!./data').default,
+ preview: require(`../_previews/${pathname}.png`).default,
+ previewDark: require(`../_previews/${pathname}-dark.png`).default,
+}
+
+export default example
diff --git a/packages/shared/examples/dual-axis-chart/styles.css b/packages/shared/examples/dual-axis-chart/styles.css
new file mode 100644
index 000000000..e7dd4b020
--- /dev/null
+++ b/packages/shared/examples/dual-axis-chart/styles.css
@@ -0,0 +1,7 @@
+.chartContainer {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 40vh;
+}
diff --git a/packages/shared/examples/examples-list.tsx b/packages/shared/examples/examples-list.tsx
index 3c1a21a79..cf380f3a1 100644
--- a/packages/shared/examples/examples-list.tsx
+++ b/packages/shared/examples/examples-list.tsx
@@ -75,6 +75,13 @@ export const examples: ExampleCollection[] = [
require('./sunburst-nested-donut').default,
],
},
+ {
+ title: 'Composite Charts',
+ description: '',
+ examples: [
+ require('./dual-axis-chart').default,
+ ],
+ },
{
title: 'Auxiliary Components',
description: 'Annotations, Brushes, Tooltips and more',
diff --git a/packages/svelte/src-demo/svelte-gallery.svelte b/packages/svelte/src-demo/svelte-gallery.svelte
index bfba47781..f15683a2e 100644
--- a/packages/svelte/src-demo/svelte-gallery.svelte
+++ b/packages/svelte/src-demo/svelte-gallery.svelte
@@ -1,5 +1,6 @@