Skip to content

Commit

Permalink
feat(chart): LineChart
Browse files Browse the repository at this point in the history
  • Loading branch information
jiwangyihao committed Oct 6, 2024
1 parent 87012eb commit 9f208c0
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 12 deletions.
7 changes: 3 additions & 4 deletions extensions/chart/src/barChart.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<script setup lang="ts">
import { onMounted, ref, watchEffect } from 'vue'
import type { DateTime } from 'luxon'
import type { Growable } from '@vue-motion/lib'
import { Rect as VMRect } from '@vue-motion/lib'
import type { BaseSimpleChartOptions } from './baseSimpleChart.vue'
import BaseSimpleChart from './baseSimpleChart.vue'
import { useSimpleChart } from './utils/useSimpleChart.ts'
import type { BaseChartOptions, BaseChartStyle, Color } from '.'
import type { BaseChartStyle, Color } from '.'
import { ColorEnum, DataUtil } from '.'
/**
Expand All @@ -19,8 +19,7 @@ export interface BarChartStyle extends BaseChartStyle {
borderRadius?: number
}
export interface BarChartOptions extends BaseChartOptions, Growable {
labels?: string[] | DateTime[]
export interface BarChartOptions extends BaseSimpleChartOptions, Growable {
categoryPercentage?: number
barPercentage?: number
}
Expand Down
3 changes: 2 additions & 1 deletion extensions/chart/src/baseChart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { DateTime, DateTimeUnit } from 'luxon'
import type { WidgetOptions } from '@vue-motion/lib'
import type { ReturnWidget } from '@vue-motion/core'
import type { ChartLayoutConfig } from './chartLayout.vue'

Check failure on line 5 in extensions/chart/src/baseChart.ts

View workflow job for this annotation

GitHub Actions / type-check

Module '"*.vue"' has no exported member 'ChartLayoutConfig'. Did you mean to use 'import ChartLayoutConfig from "*.vue"' instead?
import type { LineChartStyle } from './lineChart.vue'

Check failure on line 6 in extensions/chart/src/baseChart.ts

View workflow job for this annotation

GitHub Actions / type-check

Module '"*.vue"' has no exported member 'LineChartStyle'. Did you mean to use 'import LineChartStyle from "*.vue"' instead?
import type { BarChartStyle, ChartDataOptions, DateTimeWithDuration } from '.'

Check failure on line 7 in extensions/chart/src/baseChart.ts

View workflow job for this annotation

GitHub Actions / type-check

'"."' has no exported member named 'BarChartStyle'. Did you mean 'ChartStyle'?

Check failure on line 7 in extensions/chart/src/baseChart.ts

View workflow job for this annotation

GitHub Actions / type-check

'"."' has no exported member named 'ChartDataOptions'. Did you mean 'ChartAxisOptions'?

Check failure on line 7 in extensions/chart/src/baseChart.ts

View workflow job for this annotation

GitHub Actions / type-check

Module '"."' has no exported member 'DateTimeWithDuration'.
// import type { default as ChartLayout } from './chartLayout'
// import type { ChartDataUnit } from './chartDataUnit'
Expand All @@ -23,7 +24,7 @@ export class ColorEnum {
static readonly DARK_GRAY = 'darkGray'
}

export type ChartStyle = BaseChartStyle & BarChartStyle
export type ChartStyle = BaseChartStyle & BarChartStyle & LineChartStyle

/**
* ChartAxisOptions
Expand Down
5 changes: 3 additions & 2 deletions extensions/chart/src/chartDataset.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { defineWidget } from '@vue-motion/core'
import type { Ref } from 'vue'
import { inject, provide, ref, unref } from 'vue'
import type { BaseChartData, ChartDataOptions, ChartStyle } from '.'
import type { BaseSimpleChartData } from './baseSimpleChart.vue'
import type { ChartDataOptions, ChartStyle } from '.'
export interface ChartDatasetOptions extends WidgetOptions {
label: string
Expand All @@ -29,7 +30,7 @@ const dataset = ref<BaseChartDataSet<ChartStyle>>({
})
provide('chartDataset', dataset)
const datasetList = inject<Ref<BaseChartData>>('chartData')
const datasetList = inject<Ref<BaseSimpleChartData>>('chartData')
datasetList?.value.datasets.push(unref(dataset))
</script>

Expand Down
2 changes: 2 additions & 0 deletions extensions/chart/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ export * from './chartDataset.vue'
export { default as ChartDataset } from './chartDataset.vue'
export * from './barChart.vue'
export { default as BarChart } from './barChart.vue'
export * from './lineChart.vue'
export { default as LineChart } from './lineChart.vue'
225 changes: 225 additions & 0 deletions extensions/chart/src/lineChart.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
<script setup lang="ts">
import { onMounted, ref, watchEffect } from 'vue'
import type { Growable } from '@vue-motion/lib'
import type { BaseSimpleChartOptions } from './baseSimpleChart.vue'
import BaseSimpleChart from './baseSimpleChart.vue'
import { useSimpleChart } from './utils/useSimpleChart.ts'
import { bezierControlPoints } from './utils/bezierControlPoints.ts'
import type { BaseChartStyle, Color } from '.'
import { ColorEnum, DataUtil } from '.'
/**
* LineChart style.
* @public
* @interface
* @category LineChart
* @extends BaseChartStyle
*/
export interface LineChartStyle extends BaseChartStyle {
/**
* @property number dotSize
* @description
* dotSize is a number that represents the size of the dots in the line chart.
* It is optional.
* If not provided, the dotSize will be `5`.
*/
dotSize?: number
/**
* @property number[] borderDashInterval
* @description
* borderDashInterval is an array of numbers that represents the intervals of the dashed border.
* It is optional.
* If not provided, the borderDashInterval will be `undefined`.
*/
borderDashInterval?: number[]
/**
* @property number borderDashOffset
* @description
* borderDashOffset is a number that represents the offset of the dashed border.
* It is optional.
* If not provided, the borderDashOffset will be `0`.
*/
borderDashOffset?: number
/**
* @property StrokeJoin borderJoinStyle
* @description
* borderJoinStyle is a string that represents the style of the border join.
* It is optional.
* If not provided, the borderJoinStyle will be `miter`.
*/
// borderJoinStyle?: StrokeJoin
/**
* @property StrokeCap borderCapStyle
* @description
* borderCapStyle is a string that represents the style of the border cap.
* It is optional.
* If not provided, the borderCapStyle will be `butt`.
*/
// borderCapStyle?: StrokeCap
/**
* @property number lineWidth
* @description
* lineWidth is a number that represents the width of the line in the line chart.
* It is optional.
* If not provided, the lineWidth will be `3`.
*/
lineWidth?: number
/**
* @property number tension
* @description
* tension is a number that represents the tension of the line in the line chart, which is used to create a Bézier curve.
* It is optional.
* If not provided, the tension will be `0.1`.
*/
tension?: number
/**
* @property boolean showLine
* @description
* showLine is a boolean that represents whether to show the line in the line chart.
* It is optional.
* If not provided, the showLine will be `true`.
*/
showLine?: boolean
/**
* @property boolean animateIndex
* @description
* animateIndex is a boolean that represents whether to add animation to the index value of the data points in the line chart.
* It is optional.
* If not provided, the animateIndex will be `false`.
*/
animateIndex?: boolean
}
export interface LineChartOptions extends BaseSimpleChartOptions, Growable {
}
const props = withDefaults(defineProps<LineChartOptions>(), {
gridAlign: false,
})
const {
options,
data,
layoutConfig,
} = useSimpleChart<LineChartOptions>(props)
const dotSets = ref<{
radius: number
x: number
y: number
style: {
fillColor: Color
borderColor: Color
borderWidth: number
}
}[][]>([])
const paths = ref<string[]>([])
onMounted(() => {
watchEffect(() => {
if (data.value.datasets.length === 0)
return
if (data.value.datasets.some(dataset => (dataset.data.length === 0)))
return
dotSets.value = data.value.datasets.map((set) => {
set.style ??= {}
set.style.backgroundColor ??= data.value.style?.backgroundColor ?? ColorEnum.WHITE
set.style.borderColor ??= data.value.style?.borderColor ?? ColorEnum.WHITE
set.style.borderWidth ??= data.value.style?.borderWidth ?? 1
set.style.dotSize ??= data.value.style?.dotSize ?? 5
if (layoutConfig.value.indexAxis === 'x') {
return set.data.map((unit) => {
return {
radius: unit.weight ?? unit.style?.dotSize ?? set.style!.dotSize!,
x: (DataUtil.indexNumber(unit) - layoutConfig.value.index!.min)
/ (layoutConfig.value.index!.max - layoutConfig.value.index!.min) * layoutConfig.value.width!,
y: layoutConfig.value.height!
- ((unit.cross * (options.progress ?? 1) - layoutConfig.value.cross!.min) * layoutConfig.value.height!)
/ (layoutConfig.value.cross!.max - layoutConfig.value.cross!.min),
style: {
fillColor: unit.style?.backgroundColor ?? set.style!.backgroundColor!,
borderColor: unit.style?.borderColor ?? set.style!.borderColor!,
borderWidth: unit.style?.borderWidth ?? set.style!.borderWidth!,
},
}
})
}
else {
return set.data.map((unit) => {
return {
radius: unit.weight ?? unit.style?.dotSize ?? set.style!.dotSize!,
x: ((unit.cross * (options.progress ?? 1) - layoutConfig.value.cross!.min) * layoutConfig.value.width!)
/ (layoutConfig.value.cross!.max - layoutConfig.value.cross!.min),
y: layoutConfig.value.height! - (DataUtil.indexNumber(unit) - layoutConfig.value.index!.min)
/ (layoutConfig.value.index!.max - layoutConfig.value.index!.min) * layoutConfig.value.height!,
style: {
fillColor: unit.style?.backgroundColor ?? set.style!.backgroundColor!,
borderColor: unit.style?.borderColor ?? set.style!.borderColor!,
borderWidth: unit.style?.borderWidth ?? set.style!.borderWidth!,
},
}
})
}
})
for (let i = 0; i < data.value.datasets.length; i++) {
const tension = data.value.datasets[i].style?.tension ?? data.value.style?.tension ?? 0.1
paths.value[i] = ''
const controlPoints = bezierControlPoints(dotSets.value[i], tension, false)
for (let j = 0; j < dotSets.value[i].length; j++) {
if (j === 0) {
paths.value[i] += `M ${dotSets.value[i][j].x} ${dotSets.value[i][j].y} `
}
else {
paths.value[i] += `C ${controlPoints[j - 1].next.x} ${controlPoints[j - 1].next.y} ${controlPoints[j].previous.x} ${controlPoints[j].previous.y} ${dotSets.value[i][j].x} ${dotSets.value[i][j].y} `
// this.paths[i].lineTo(this.dotSets[i][j].x, this.dotSets[i][j].y)
}
}
}
})
})
</script>

<template>
<BaseSimpleChart
v-bind="{
gridAlign: true,
edgeOffset: !(options.gridAlign),
...options,
}"
>
<slot />
<!-- DotSets -->
<g v-for="(dotSet, setIndex) in dotSets" :key="setIndex">
<g v-for="(dot, dotIndex) in dotSet" :key="dotIndex">
<circle
:cx="dot.x"
:cy="dot.y"
:r="dot.radius"
:fill="dot.style.fillColor"
fill-opacity="0.2"
:stroke="dot.style.borderColor"
:stroke-width="dot.style.borderWidth"
/>
</g>
</g>
<!-- Paths -->
<g v-if="data.style?.showLine ?? true">
<path
v-for="(path, pathIndex) in paths"
:key="pathIndex"
:d="path"
fill-opacity="0"
:stroke="data.datasets[pathIndex].style?.borderColor ?? data.datasets[pathIndex].data[0].style?.borderColor ?? data.style?.borderColor ?? ColorEnum.WHITE"
:stroke-width="data.datasets[pathIndex].style?.borderWidth ?? data.datasets[pathIndex].data[0].style?.borderWidth ?? data.style?.borderWidth ?? 1"
:stroke-dasharray="data.datasets[pathIndex].style?.borderDashInterval?.join(' ') ?? data.datasets[pathIndex].data[0].style?.borderDashInterval?.join(' ') ?? data.style?.borderDashInterval?.join(' ')"
:stroke-dashoffset="data.datasets[pathIndex].style?.borderDashOffset ?? data.datasets[pathIndex].data[0].style?.borderDashOffset ?? data.style?.borderDashOffset ?? 0"
/>
</g>
</BaseSimpleChart>
</template>
<style scoped>
</style>
10 changes: 5 additions & 5 deletions test/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { usePlayer, useWidget } from '@vue-motion/core'
import { Motion, grow } from '@vue-motion/lib'
import { onMounted } from 'vue'
import type { MathFunction } from '@vue-motion/extension-math'
import { BarChart, ChartData, ChartDataset, ChartUtil } from '@vue-motion/extension-chart'
import { ChartData, ChartDataset, ChartUtil, LineChart } from '@vue-motion/extension-chart'
import { DateTime, Duration } from 'luxon'
const fn1 = useWidget<InstanceType<typeof MathFunction>>('fn1')
Expand Down Expand Up @@ -35,7 +35,7 @@ onMounted(() => {
<!-- <Group> -->
<!-- <NumberPlane :ranges-x="[0, 10]" :ranges-y="[0, 10]" /> -->
<!-- </Group> -->
<BarChart
<LineChart
:labels="ChartUtil.dateSequence(
DateTime.fromISO('2021-01-01').setLocale('en-US'),
Duration.fromObject({ months: 4 }),
Expand All @@ -54,10 +54,10 @@ onMounted(() => {
</ChartDataset>
<ChartDataset label="test2" :style="{ borderColor: '#ff0', backgroundColor: '#ff0', borderRadius: 4 }">
<ChartData :cross="2" />
<ChartData :cross="2" />
<ChartData :cross="2" />
<ChartData :cross="1" />
<ChartData :cross="3" />
<ChartData :cross="2" />
</ChartDataset>
</BarChart>
</LineChart>
</Motion>
</template>

0 comments on commit 9f208c0

Please sign in to comment.