/* * @version: 3.2.3 * @author: Preline Labs Ltd. * @license: Licensed under MIT and Preline UI Fair Use License (https://preline.co/docs/license.html) * Copyright 2024 Preline Labs Ltd. */ import { IBuildTooltipHelperOptions, IBuildTooltipHelperSingleOptions, IChartDonutProps, IChartProps, IChartPropsSeries } from './interfaces' import ApexCharts from 'apexcharts' function buildTooltip(props: IChartProps, options: IBuildTooltipHelperOptions) { const { title, valuePrefix = '$', isValueDivided = true, valuePostfix = '', hasTextLabel = false, invertGroup = false, labelDivider = '', wrapperClasses = 'bg-base-100 min-w-28 text-base-content/80 rounded-lg !border-none', wrapperExtClasses = '', seriesClasses = 'text-xs items-center', seriesExtClasses = '', titleClasses = '!text-sm !font-semibold !bg-base-100 !border-base-content/40 text-base-content rounded-t-lg !px-2.5', titleExtClasses = '', markerClasses = '!w-2.5 !h-2.5 !me-1.5 rtl:!mr-0 !rounded-full', markerExtClasses = '', valueClasses = '!font-medium text-base-content/80 !ms-auto', valueExtClasses = '', labelClasses = 'text-base-content', labelExtClasses = '', thousandsShortName = 'k' } = options const { dataPointIndex } = props const { colors } = props.ctx.opts const series = props.ctx.opts.series as IChartPropsSeries[] let seriesGroups = '' series.forEach((_, i) => { const val = props.series[i][dataPointIndex] || (typeof series[i].data[dataPointIndex] !== 'object' ? series[i].data[dataPointIndex] : props.series[i][dataPointIndex]) const label = series[i].name const groupData = invertGroup ? { left: `${hasTextLabel ? label : ''}${labelDivider}`, right: `${valuePrefix}${val >= 1000 && isValueDivided ? `${val / 1000}${thousandsShortName}` : val}${valuePostfix}` } : { left: `${valuePrefix}${val >= 1000 && isValueDivided ? `${val / 1000}${thousandsShortName}` : val}${valuePostfix}`, right: `${hasTextLabel ? label : ''}${labelDivider}` } const labelMarkup = `${groupData.left}` seriesGroups += `
${groupData.right}
${labelMarkup}
` }) return `
${title}
${seriesGroups}
` } function buildHeatmapTooltip(props: IChartProps, options: IBuildTooltipHelperSingleOptions) { const { valuePrefix = '$', valuePostfix = '', divider = '', wrapperClasses = 'ms-0.5 mb-2 bg-base-100 border border-base-content/20 text-base-content rounded-box shadow-md', wrapperExtClasses = '', markerClasses = '!w-2.5 !h-2.5 !me-1.5', markerStyles = '', markerExtClasses = '!rounded-xs', valueClasses = '!font-medium text-base-content !ms-auto', valueExtClasses = '' } = options const { dataPointIndex, seriesIndex, series } = props const { name } = props.ctx.opts.series[seriesIndex] as IChartPropsSeries const val = `${valuePrefix}${series[seriesIndex][dataPointIndex]}${valuePostfix}` return `
${name}${divider}
${val}
` } function buildTooltipCompareTwo(props: IChartProps, options: IBuildTooltipHelperOptions) { const { title, valuePrefix = '$', isValueDivided = true, valuePostfix = '', hasCategory = true, hasTextLabel = false, labelDivider = '', wrapperClasses = 'bg-base-100 min-w-48 text-base-content/80 rounded-lg !border-none', wrapperExtClasses = '', seriesClasses = 'text-xs items-center !justify-between', seriesExtClasses = '', titleClasses = '!text-sm !font-semibold !bg-base-100 !border-base-content/40 text-base-content rounded-t-lg !px-2.5', titleExtClasses = 'flex justify-between', markerClasses = '!w-2.5 !h-2.5 !me-1.5 !rounded-full', markerExtClasses = '', valueClasses = '!font-medium text-base-content/80 !ms-auto', valueExtClasses = '', labelClasses = 'text-base-content !font-medium', labelExtClasses = '', thousandsShortName = 'k' } = options const { dataPointIndex } = props const { categories } = props.ctx.opts.xaxis const { colors } = props.ctx.opts const series = props.ctx.opts.series as IChartPropsSeries[] let seriesGroups = '' const s0 = series[0].data[dataPointIndex] const s1 = series[1].data[dataPointIndex] const category = categories[dataPointIndex].split(' ') const newCategory = hasCategory ? `${category[0]}${category[1] ? ' ' : ''}${category[1] ? category[1].slice(0, 3) : ''}` : '' // const isGrowing = s0 > s1; // const isDifferenceIsNull = s0 / s1 === 1; // const difference = isDifferenceIsNull ? 0 : (s0 / s1) * 100; // TODO: test this before deleting the code above const isPrevZero = s1 === 0 const difference = isPrevZero ? 0 : ((s0 - s1) / Math.abs(s1)) * 100 const isDifferenceIsNull = difference === 0 const isGrowing = difference > 0 const icon = isGrowing ? `` : `` series.forEach((_, i) => { const val = props.series[i][dataPointIndex] || (typeof series[i].data[dataPointIndex] !== 'object' ? series[i].data[dataPointIndex] : props.series[i][dataPointIndex]) const label = series[i].name const altValue = series[i].altValue || null const labelMarkup = `${newCategory} ${label || ''}` const valueMarkup = altValue || `${valuePrefix}${val >= 1000 && isValueDivided ? `${val / 1000}${thousandsShortName}` : val}${valuePostfix}${labelDivider}` seriesGroups += `
${valueMarkup}
${hasTextLabel ? labelMarkup : ''}
` }) return `
${title} ${!isDifferenceIsNull ? icon : ''} ${difference.toFixed(1)}%
${seriesGroups}
` } function buildTooltipCompareTwoAlt(props: IChartProps, options: IBuildTooltipHelperOptions) { const { title, valuePrefix = '$', isValueDivided = true, valuePostfix = '', hasCategory = true, hasTextLabel = false, labelDivider = '', wrapperClasses = 'bg-base-100 min-w-48 text-base-content/80 rounded-lg !border-none', wrapperExtClasses = '', seriesClasses = 'text-xs items-center !justify-between', seriesExtClasses = '', titleClasses = '!text-sm !font-semibold !bg-base-100 !border-base-content/40 text-base-content rounded-t-lg flex !justify-between !px-2.5', titleExtClasses = '', markerClasses = '!w-2.5 !h-2.5 !me-1.5 !rounded-full', markerExtClasses = '', valueClasses = '!font-medium text-base-content/80 !ms-auto', valueExtClasses = '', labelClasses = 'text-base-content !font-medium', labelExtClasses = '', thousandsShortName = 'k' } = options const { dataPointIndex } = props const { categories } = props.ctx.opts.xaxis const { colors } = props.ctx.opts const series = props.ctx.opts.series as IChartPropsSeries[] let seriesGroups = '' const s0 = series[0].data[dataPointIndex] const s1 = series[1].data[dataPointIndex] const category = categories[dataPointIndex].split(' ') const newCategory = hasCategory ? `${category[0]}${category[1] ? ' ' : ''}${category[1] ? category[1].slice(0, 3) : ''}` : '' // const isGrowing = s0 > s1; // const isDifferenceIsNull = s0 / s1 === 1; // const difference = isDifferenceIsNull ? 0 : (s0 / s1) * 100; // TODO: test this before deleting the code above const isPrevZero = s1 === 0 const difference = isPrevZero ? 0 : ((s0 - s1) / Math.abs(s1)) * 100 const isDifferenceIsNull = difference === 0 const isGrowing = difference > 0 const icon = isGrowing ? `` : `` series.forEach((_, i) => { const val = props.series[i][dataPointIndex] || (typeof series[i].data[dataPointIndex] !== 'object' ? series[i].data[dataPointIndex] : props.series[i][dataPointIndex]) const label = series[i].name const labelMarkup = `${valuePrefix}${val >= 1000 && isValueDivided ? `${val / 1000}${thousandsShortName}` : val}${valuePostfix}` seriesGroups += `
${newCategory} ${label || ''}${labelDivider}
${hasTextLabel ? labelMarkup : ''}
` }) return `
${title} ${!isDifferenceIsNull ? icon : ''} ${difference.toFixed(1)}%
${seriesGroups}
` } function buildTooltipForDonut({ series, seriesIndex, w }: IChartDonutProps, textColor: string[]) { const { globals } = w const { colors } = globals return `
${globals.labels[seriesIndex]}: ${series[seriesIndex]}
` } function buildChart(id: string, shared: Function) { const $chart = document.querySelector(id) let chart: any = null if (!$chart) return false const optionsFn = () => shared() // Initialize and render the chart if ($chart) { chart = new ApexCharts($chart, optionsFn()) chart.render() } return chart } function fullBarHoverEffect( chartCtx: ApexCharts & { el: HTMLElement w: { config: { xaxis?: { categories?: any[] } } } }, { shadowClasses = 'fill-gray-200' }: { shadowClasses?: string } = {} ): void { const grid = chartCtx.el.querySelector('.apexcharts-grid') const svg = chartCtx.el.querySelector('svg') if (!grid || !svg) return const categories: any[] = chartCtx.w.config.xaxis?.categories || [] if (categories.length === 0) return let shadowRect: SVGRectElement | null = null let isVisible = false let isRemoving = false function cleanup() { shadowRect?.remove() shadowRect = null isVisible = false isRemoving = false } function showForIndex(index: number) { const seriesGroup = chartCtx.el.querySelector('.apexcharts-bar-series') if (!seriesGroup) return const bars = seriesGroup.querySelectorAll('path') const bar = bars[index] if (!bar) return const bbox = bar.getBBox() const x = bbox.x const y = bbox.y const width = bbox.width if (y <= 0) return if (!shadowRect) { shadowRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect') shadowRect.setAttribute('y', '0') shadowRect.setAttribute('class', shadowClasses) bar.parentNode?.insertBefore(shadowRect, bar) } shadowRect.setAttribute('x', x.toString()) shadowRect.setAttribute('width', width.toString()) shadowRect.setAttribute('height', y.toString()) requestAnimationFrame(() => { shadowRect?.classList.add('opacity-100') }) isVisible = true isRemoving = false } function hide() { if (!shadowRect || !isVisible || isRemoving) return isRemoving = true shadowRect.classList.remove('opacity-100') cleanup() } svg.addEventListener('mousemove', (e: MouseEvent) => { const gridRect = grid.getBoundingClientRect() if ( e.clientX < gridRect.left || e.clientX > gridRect.right || e.clientY < gridRect.top || e.clientY > gridRect.bottom ) { hide() return } const relativeX = e.clientX - gridRect.left const ratio = relativeX / gridRect.width const index = Math.floor(ratio * categories.length) if (index < 0 || index >= categories.length) { hide() return } showForIndex(index) }) svg.addEventListener('mouseleave', hide) } export { buildChart, buildHeatmapTooltip, buildTooltip, buildTooltipCompareTwo, buildTooltipCompareTwoAlt, buildTooltipForDonut, fullBarHoverEffect }