fix chart
This commit is contained in:
@@ -0,0 +1,61 @@
|
|||||||
|
type ChartPoint = [number, number]
|
||||||
|
|
||||||
|
/** ضریب بزرگنمایی بصری نوسان — فقط مختصات نمودار؛ tooltip قیمت واقعی را نشان میدهد */
|
||||||
|
const KATOD_CHART_DEVIATION_FACTOR = 3
|
||||||
|
|
||||||
|
/** نمودار boursecu وقتی metal_type = کاتد مس — محور Y فشرده تا نوسان کم هم دیده شود */
|
||||||
|
export function shouldAmplifyKatodMesChart(options: {
|
||||||
|
isBoursecu: boolean
|
||||||
|
metalTypeSetting?: string | number | null
|
||||||
|
}): boolean {
|
||||||
|
if (!options.isBoursecu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const metal = String(options.metalTypeSetting ?? '').trim()
|
||||||
|
return metal === 'کاتد مس'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function computeTightChartYBounds(prices: number[]): { min?: number; max?: number } {
|
||||||
|
const values = prices.filter((value) => Number.isFinite(value) && value > 0)
|
||||||
|
if (values.length < 2) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataMin = Math.min(...values)
|
||||||
|
const dataMax = Math.max(...values)
|
||||||
|
const mid = (dataMin + dataMax) / 2
|
||||||
|
let span = dataMax - dataMin
|
||||||
|
|
||||||
|
// حداقل بازه عمودی: ~۰٫۵٪ قیمت میانی (یا ۸۰هزار ریال) تا خط خیلی صاف نباشد
|
||||||
|
const minSpan = Math.max(mid * 0.005, 80_000)
|
||||||
|
if (span < minSpan) {
|
||||||
|
span = minSpan
|
||||||
|
}
|
||||||
|
|
||||||
|
const padding = span * 0.08
|
||||||
|
const center = (dataMin + dataMax) / 2
|
||||||
|
|
||||||
|
return {
|
||||||
|
min: Math.floor(center - span / 2 - padding),
|
||||||
|
max: Math.ceil(center + span / 2 + padding),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** بزرگنمایی انحراف از میانگین — خط نمودار پرتحرکتر میشود بدون تغییر قیمت واقعی در tooltip */
|
||||||
|
export function amplifyChartDeviation(
|
||||||
|
points: ChartPoint[],
|
||||||
|
factor = KATOD_CHART_DEVIATION_FACTOR
|
||||||
|
): ChartPoint[] {
|
||||||
|
if (points.length < 2) {
|
||||||
|
return points
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = points.map((point) => point[1])
|
||||||
|
const mean = values.reduce((sum, value) => sum + value, 0) / values.length
|
||||||
|
|
||||||
|
return points.map(([timestamp, value]) => [
|
||||||
|
timestamp,
|
||||||
|
Math.round(mean + (value - mean) * factor),
|
||||||
|
])
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
width="100%"
|
width="100%"
|
||||||
:height="chartHeight"
|
:height="chartHeight"
|
||||||
:options="chartOptions"
|
:options="chartOptions"
|
||||||
:series="props.priceChartSeries"
|
:series="displayChartSeries"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -51,6 +51,8 @@ import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
|||||||
import { useConfigStore } from '@/store/configStore'
|
import { useConfigStore } from '@/store/configStore'
|
||||||
import { usePrice } from '@/utils/usePrice'
|
import { usePrice } from '@/utils/usePrice'
|
||||||
import { getProjectFontFamily } from '@/utils/config'
|
import { getProjectFontFamily } from '@/utils/config'
|
||||||
|
import { computeTightChartYBounds, amplifyChartDeviation, shouldAmplifyKatodMesChart } from '@/utils/chartPriceAxis'
|
||||||
|
import { useSettingsStore } from '@/store/settingsStore'
|
||||||
import { useElementSize } from '@vueuse/core'
|
import { useElementSize } from '@vueuse/core'
|
||||||
|
|
||||||
import type { ChartMetalType } from '../composables/usePriceChart'
|
import type { ChartMetalType } from '../composables/usePriceChart'
|
||||||
@@ -90,6 +92,7 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const config = useConfigStore()
|
const config = useConfigStore()
|
||||||
|
const settingsStore = useSettingsStore()
|
||||||
const chartContainerRef = ref<HTMLElement | null>(null)
|
const chartContainerRef = ref<HTMLElement | null>(null)
|
||||||
const chartMounted = ref(false)
|
const chartMounted = ref(false)
|
||||||
const selectedValue = ref(props.chartSelection)
|
const selectedValue = ref(props.chartSelection)
|
||||||
@@ -97,6 +100,37 @@ const selectedValue = ref(props.chartSelection)
|
|||||||
const { width: chartWidth } = useElementSize(chartContainerRef)
|
const { width: chartWidth } = useElementSize(chartContainerRef)
|
||||||
|
|
||||||
const seriesLength = computed(() => props.priceChartSeries?.[0]?.data?.length ?? 0)
|
const seriesLength = computed(() => props.priceChartSeries?.[0]?.data?.length ?? 0)
|
||||||
|
const amplifyKatodChart = computed(() =>
|
||||||
|
shouldAmplifyKatodMesChart({
|
||||||
|
isBoursecu: props.layout === 'boursecu',
|
||||||
|
metalTypeSetting: settingsStore.metalTypeSettingValue,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const displayChartSeries = computed(() => {
|
||||||
|
const baseSeries = props.priceChartSeries?.[0]
|
||||||
|
if (!baseSeries?.data?.length || !amplifyKatodChart.value) {
|
||||||
|
return props.priceChartSeries
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
...baseSeries,
|
||||||
|
data: amplifyChartDeviation(baseSeries.data as [number, number][]),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
const chartYBounds = computed(() => {
|
||||||
|
if (!amplifyKatodChart.value) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const prices =
|
||||||
|
displayChartSeries.value?.[0]?.data
|
||||||
|
?.map((point: [number, number]) => Number(point?.[1]))
|
||||||
|
.filter((value: number) => Number.isFinite(value)) ?? []
|
||||||
|
|
||||||
|
return computeTightChartYBounds(prices)
|
||||||
|
})
|
||||||
const canRenderChart = computed(
|
const canRenderChart = computed(
|
||||||
() => chartMounted.value && chartWidth.value > 0 && seriesLength.value > 0
|
() => chartMounted.value && chartWidth.value > 0 && seriesLength.value > 0
|
||||||
)
|
)
|
||||||
@@ -115,6 +149,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
const chartOptions = computed(() => {
|
const chartOptions = computed(() => {
|
||||||
const fontFamily = getProjectFontFamily()
|
const fontFamily = getProjectFontFamily()
|
||||||
|
const yBounds = chartYBounds.value
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chart: {
|
chart: {
|
||||||
@@ -122,14 +157,17 @@ const chartOptions = computed(() => {
|
|||||||
type: 'area',
|
type: 'area',
|
||||||
height: 200,
|
height: 200,
|
||||||
toolbar: { show: false },
|
toolbar: { show: false },
|
||||||
zoom: { autoScaleYaxis: true },
|
zoom: { autoScaleYaxis: !amplifyKatodChart.value },
|
||||||
redrawOnParentResize: true,
|
redrawOnParentResize: true,
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
||||||
yaxis: { lines: { show: false } },
|
yaxis: { lines: { show: false } },
|
||||||
},
|
},
|
||||||
stroke: { width: 2 },
|
stroke: {
|
||||||
|
width: amplifyKatodChart.value ? 2.5 : 2,
|
||||||
|
curve: amplifyKatodChart.value ? 'smooth' : 'straight',
|
||||||
|
},
|
||||||
dataLabels: {
|
dataLabels: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
style: {
|
style: {
|
||||||
@@ -139,6 +177,8 @@ const chartOptions = computed(() => {
|
|||||||
yaxis: {
|
yaxis: {
|
||||||
tickAmount: 8,
|
tickAmount: 8,
|
||||||
show: false,
|
show: false,
|
||||||
|
min: yBounds.min,
|
||||||
|
max: yBounds.max,
|
||||||
labels: {
|
labels: {
|
||||||
formatter: (val: number) => usePrice(val, true, 'ه.ریال'),
|
formatter: (val: number) => usePrice(val, true, 'ه.ریال'),
|
||||||
style: { fontSize: '13px', fontFamily },
|
style: { fontSize: '13px', fontFamily },
|
||||||
@@ -161,7 +201,32 @@ const chartOptions = computed(() => {
|
|||||||
})}`
|
})}`
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
formatter: (val: number) => usePrice(val, true, 'ه.ریال'),
|
...(amplifyKatodChart.value
|
||||||
|
? {
|
||||||
|
custom: ({ dataPointIndex, w }: { dataPointIndex: number; w: { globals: { seriesX: number[][] } } }) => {
|
||||||
|
const timestamp = w.globals.seriesX[0]?.[dataPointIndex]
|
||||||
|
const realPrice = props.priceChartSeries?.[0]?.data?.[dataPointIndex]?.[1]
|
||||||
|
if (timestamp == null || realPrice == null) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const date = new Date(timestamp)
|
||||||
|
const timeLabel = `${date.toLocaleDateString('fa-IR')} - ${date.toLocaleTimeString('fa-IR', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
})}`
|
||||||
|
|
||||||
|
return `<div class="apexcharts-tooltip-title" style="font-family:${fontFamily}">${timeLabel}</div>
|
||||||
|
<div class="apexcharts-tooltip-series-group apexcharts-active" style="order:1;display:flex">
|
||||||
|
<span class="apexcharts-tooltip-text" style="font-family:${fontFamily}">
|
||||||
|
<span class="apexcharts-tooltip-text-y-value">${usePrice(realPrice, true, 'ه.ریال')}</span>
|
||||||
|
</span>
|
||||||
|
</div>`
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
formatter: (val: number) => usePrice(val, true, 'ه.ریال'),
|
||||||
|
}),
|
||||||
style: { fontFamily },
|
style: { fontFamily },
|
||||||
},
|
},
|
||||||
colors: [config.config.color],
|
colors: [config.config.color],
|
||||||
|
|||||||
Reference in New Issue
Block a user