diff --git a/frontend/src2/charts/components/BaseChart.vue b/frontend/src2/charts/components/BaseChart.vue index b55109753..cfff5cd2e 100644 --- a/frontend/src2/charts/components/BaseChart.vue +++ b/frontend/src2/charts/components/BaseChart.vue @@ -5,10 +5,10 @@ import { wheneverChanges } from '../../helpers' import ChartTitle from './ChartTitle.vue' const props = defineProps({ - title: { type: String, required: false }, - subtitle: { type: String, required: false }, - options: { type: Object, required: true }, - onClick: { type: Function, required: false }, + title: { type: String, required: false }, + subtitle: { type: String, required: false }, + options: { type: Object, required: true }, + onClick: { type: Function, required: false }, }) let eChart = null @@ -16,69 +16,64 @@ const chartRef = ref(null) let resizeObserver = null onMounted(async () => { - const series = props.options?.series?.find((s) => s.type === 'map') - const isMap = series && series.type === 'map' - const renderer = isMap ? 'canvas' : 'svg' - eChart = echarts.init(chartRef.value, 'light', { renderer }) + // Choose renderer (map requires canvas) + const isMap = props.options?.series?.some(s => s.type === 'map') + const renderer = isMap ? 'canvas' : 'svg' - await setChartOptions() - props.onClick && eChart.on('click', props.onClick) + eChart = echarts.init(chartRef.value, 'light', { renderer }) - resizeObserver = new ResizeObserver(() => eChart.resize()) - setTimeout( - () => chartRef.value && resizeObserver && resizeObserver.observe(chartRef.value), - 1000, - ) + if (Object.keys(props.options).length) { + eChart.setOption(props.options) + } + + if (props.onClick) { + eChart.on('click', props.onClick) + } + + // Auto-resize chart + resizeObserver = new ResizeObserver(() => { + try { + eChart?.resize() + } catch (_) {} + }) + + setTimeout(() => { + chartRef.value && resizeObserver.observe(chartRef.value) + }, 500) }) onBeforeUnmount(() => { - if (chartRef.value && resizeObserver) resizeObserver.unobserve(chartRef.value) + chartRef.value && resizeObserver?.unobserve(chartRef.value) }) wheneverChanges(() => props.options, setChartOptions, { deep: true }) async function setChartOptions() { - if (!eChart) return - const series = props.options?.series?.find((s) => s.type === 'map') - const isMap = series && series.type === 'map' - if (isMap) { - await registerMap(series.map) - } - eChart.setOption({ ...props.options }) -} + if (!eChart) return -async function registerMap(mapName) { - if (!mapName) return - if (mapName === 'india') { - const mapJson = await import('../../assets/maps_json/india.json') - echarts.registerMap('india', mapJson.default) - } else if (mapName === 'world') { - const mapJson = await import('../../assets/maps_json/world_map.json') - echarts.registerMap('world', mapJson.default) - } + const mapSeries = props.options?.series?.find(s => s.type === 'map') + if (mapSeries) { + await registerMap(mapSeries.map) + } + + eChart.setOption({ ...props.options }) } -defineExpose({ downloadChart }) -function downloadChart() { - const image = new Image() - const type = 'png' - image.src = eChart.getDataURL({ - type, - pixelRatio: 2, - backgroundColor: '#fff', - }) - const link = document.createElement('a') - link.href = image.src - link.download = `${props.title}.${type}` - link.click() +// Load map JSON file when required +async function registerMap(mapName) { + try { + const res = await fetch(`/assets/insights/maps/${mapName}.json`) + const geoJson = await res.json() + echarts.registerMap(mapName, geoJson) + } catch (e) { + console.warn("Map load failed:", mapName) + } } diff --git a/frontend/src2/charts/components/YAxisConfig.vue b/frontend/src2/charts/components/YAxisConfig.vue index 81f879532..299f3fb8a 100644 --- a/frontend/src2/charts/components/YAxisConfig.vue +++ b/frontend/src2/charts/components/YAxisConfig.vue @@ -2,7 +2,6 @@ import ColorInput from '@/components/Controls/ColorInput.vue' import { debounce } from 'frappe-ui' import { watchEffect } from 'vue' -import Checkbox from '../../components/Checkbox.vue' import DraggableList from '../../components/DraggableList.vue' import InlineFormControlLabel from '../../components/InlineFormControlLabel.vue' import { copy } from '../../helpers' @@ -12,13 +11,14 @@ import CollapsibleSection from './CollapsibleSection.vue' import MeasurePicker from './MeasurePicker.vue' const props = defineProps<{ columnOptions: ColumnOption[] }>() + +// y-axis config const y_axis = defineModel({ required: true, - default: () => ({ - series: [], - }), + default: () => ({ series: [] }), }) +// Ensure at least one series exists const emptySeries = { measure: {} as MeasureOption } watchEffect(() => { if (!y_axis.value?.series?.length) { @@ -30,73 +30,83 @@ function addSeries() { y_axis.value.series.push(copy(emptySeries)) } +// Color update handler const updateColor = debounce((color: string, idx: number) => { - if (!y_axis.value.series[idx].color) { - y_axis.value.series[idx].color = [] - } y_axis.value.series[idx].color = color ? [color] : [] }, 500) + +// Label position options +const labelPositionOptions = [ + { label: 'Top', value: 'top' }, + { label: 'Center', value: 'inside' }, + { label: 'Bottom', value: 'bottom' }, +]