/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import { CategoricalColorNamespace, ChartProps, SqlaFormData, VizType, } from '@superset-ui/core'; import { supersetTheme } from '@apache-superset/core/ui'; import transformProps, { getIntervalBoundsAndColors, } from '../../src/Gauge/transformProps'; import { EchartsGaugeChartProps } from '../../src/Gauge/types'; describe('Echarts Gauge transformProps', () => { const baseFormData: SqlaFormData = { datasource: '26__table', viz_type: VizType.Gauge, metric: 'count', adhocFilters: [], rowLimit: 10, minVal: 0, maxVal: 100, startAngle: 225, endAngle: -45, colorScheme: 'SUPERSET_DEFAULT', fontSize: 14, numberFormat: 'SMART_NUMBER', valueFormatter: '{value}', showPointer: true, animation: true, showAxisTick: false, showSplitLine: false, splitNumber: 10, showProgress: true, overlap: true, roundCap: false, }; it('should transform chart props for no group by column', () => { const formData: SqlaFormData = { ...baseFormData, groupby: [] }; const queriesData = [ { colnames: ['count'], data: [ { count: 16595, }, ], }, ]; const chartPropsConfig = { formData, width: 800, height: 600, queriesData, theme: supersetTheme, }; const chartProps = new ChartProps(chartPropsConfig); const result = transformProps(chartProps as EchartsGaugeChartProps); // Test core properties expect(result.width).toBe(800); expect(result.height).toBe(600); // Test series data const seriesData = (result.echartOptions as any).series[0].data; expect(seriesData).toHaveLength(1); expect(seriesData[0].value).toBe(16595); expect(seriesData[0].name).toBe(''); expect(seriesData[0].itemStyle.color).toBe('#1f77b4'); // Test detail and title positions expect(seriesData[0].title.offsetCenter).toEqual(['0%', '20%']); expect(seriesData[0].title.fontSize).toBe(14); expect(seriesData[0].detail.offsetCenter).toEqual(['0%', '32.6%']); expect(seriesData[0].detail.fontSize).toBe(16.8); }); it('should transform chart props for single group by column', () => { const formData: SqlaFormData = { ...baseFormData, groupby: ['year'], }; const queriesData = [ { colnames: ['year', 'count'], data: [ { year: 1988, count: 15, }, { year: 1995, count: 219, }, ], }, ]; const chartPropsConfig = { formData, width: 800, height: 600, queriesData, theme: supersetTheme, }; const chartProps = new ChartProps(chartPropsConfig); const result = transformProps(chartProps as EchartsGaugeChartProps); // Test core properties expect(result.width).toBe(800); expect(result.height).toBe(600); // Test series data const seriesData = (result.echartOptions as any).series[0].data; expect(seriesData).toHaveLength(2); // First data point expect(seriesData[0].value).toBe(15); expect(seriesData[0].name).toBe('year: 1988'); expect(seriesData[0].itemStyle.color).toBe('#1f77b4'); expect(seriesData[0].title.offsetCenter).toEqual(['0%', '20%']); expect(seriesData[0].title.fontSize).toBe(14); expect(seriesData[0].detail.offsetCenter).toEqual(['0%', '32.6%']); expect(seriesData[0].detail.fontSize).toBe(16.8); // Second data point expect(seriesData[1].value).toBe(219); expect(seriesData[1].name).toBe('year: 1995'); expect(seriesData[1].itemStyle.color).toBe('#ff7f0e'); expect(seriesData[1].title.offsetCenter).toEqual(['0%', '48%']); expect(seriesData[1].title.fontSize).toBe(14); expect(seriesData[1].detail.offsetCenter).toEqual(['0%', '60.6%']); expect(seriesData[1].detail.fontSize).toBe(16.8); }); it('should transform chart props for multiple group by columns', () => { const formData: SqlaFormData = { ...baseFormData, groupby: ['year', 'platform'], }; const queriesData = [ { colnames: ['year', 'platform', 'count'], data: [ { year: 2011, platform: 'PC', count: 140, }, { year: 2008, platform: 'PC', count: 76, }, ], }, ]; const chartPropsConfig = { formData, width: 800, height: 600, queriesData, theme: supersetTheme, }; const chartProps = new ChartProps(chartPropsConfig); const result = transformProps(chartProps as EchartsGaugeChartProps); // Test core properties expect(result.width).toBe(800); expect(result.height).toBe(600); // Test series data const seriesData = (result.echartOptions as any).series[0].data; expect(seriesData).toHaveLength(2); // First data point expect(seriesData[0].value).toBe(140); expect(seriesData[0].name).toBe('year: 2011, platform: PC'); expect(seriesData[0].itemStyle.color).toBe('#1f77b4'); expect(seriesData[0].title.offsetCenter).toEqual(['0%', '20%']); expect(seriesData[0].title.fontSize).toBe(14); expect(seriesData[0].detail.offsetCenter).toEqual(['0%', '32.6%']); expect(seriesData[0].detail.fontSize).toBe(16.8); // Second data point expect(seriesData[1].value).toBe(76); expect(seriesData[1].name).toBe('year: 2008, platform: PC'); expect(seriesData[1].itemStyle.color).toBe('#ff7f0e'); expect(seriesData[1].title.offsetCenter).toEqual(['0%', '48%']); expect(seriesData[1].title.fontSize).toBe(14); expect(seriesData[1].detail.offsetCenter).toEqual(['0%', '60.6%']); expect(seriesData[1].detail.fontSize).toBe(16.8); }); it('should transform chart props for intervals', () => { const formData: SqlaFormData = { ...baseFormData, groupby: ['year', 'platform'], intervals: '60,100', intervalColorIndices: '1,2', minVal: 20, }; const queriesData = [ { colnames: ['year', 'platform', 'count'], data: [ { year: 2011, platform: 'PC', count: 140, }, { year: 2008, platform: 'PC', count: 76, }, ], }, ]; const chartPropsConfig = { formData, width: 800, height: 600, queriesData, theme: supersetTheme, }; const chartProps = new ChartProps(chartPropsConfig); const result = transformProps(chartProps as EchartsGaugeChartProps); // Test core properties expect(result.width).toBe(800); expect(result.height).toBe(600); // Test axisLine intervals const { axisLine } = (result.echartOptions as any).series[0]; expect(axisLine.roundCap).toBe(false); expect(axisLine.lineStyle.width).toBe(14); expect(axisLine.lineStyle.color).toEqual([ [0.5, '#1f77b4'], [1, '#ff7f0e'], ]); // Test series data const seriesData = (result.echartOptions.series as any)[0].data; expect(seriesData).toHaveLength(2); // First data point expect(seriesData[0].value).toBe(140); expect(seriesData[0].name).toBe('year: 2011, platform: PC'); expect(seriesData[0].itemStyle.color).toBe('#1f77b4'); // Second data point expect(seriesData[1].value).toBe(76); expect(seriesData[1].name).toBe('year: 2008, platform: PC'); expect(seriesData[1].itemStyle.color).toBe('#ff7f0e'); }); }); describe('Min/Max calculation and axis labels', () => { const baseFormData: SqlaFormData = { datasource: '26__table', viz_type: VizType.Gauge, metric: 'count', adhocFilters: [], rowLimit: 10, startAngle: 225, endAngle: -45, colorScheme: 'SUPERSET_DEFAULT', fontSize: 14, numberFormat: 'SMART_NUMBER', valueFormatter: '{value}', showPointer: true, animation: true, showAxisTick: false, showSplitLine: false, splitNumber: 10, showProgress: true, overlap: true, roundCap: false, groupby: [], }; it('should use provided minVal and maxVal when valid numbers', () => { const formData: SqlaFormData = { ...baseFormData, minVal: 10, maxVal: 100, }; const queriesData = [ { colnames: ['count'], data: [{ count: 50 }, { count: 75 }], }, ]; const chartProps = new ChartProps({ formData, width: 800, height: 600, queriesData, theme: supersetTheme, }); const result = transformProps(chartProps as EchartsGaugeChartProps); const series = (result.echartOptions as any).series[0]; expect(series.min).toBe(10); expect(series.max).toBe(100); }); it('should calculate min/max from data when minVal is null', () => { const formData: SqlaFormData = { ...baseFormData, minVal: null, maxVal: 100, }; const queriesData = [ { colnames: ['count'], data: [{ count: 20 }, { count: 80 }], }, ]; const chartProps = new ChartProps({ formData, width: 800, height: 600, queriesData, theme: supersetTheme, }); const result = transformProps(chartProps as EchartsGaugeChartProps); const series = (result.echartOptions as any).series[0]; expect(series.min).toBe(0); expect(series.max).toBe(100); }); it('should calculate min/max from data when maxVal is null', () => { const formData: SqlaFormData = { ...baseFormData, minVal: 0, maxVal: null, }; const queriesData = [ { colnames: ['count'], data: [{ count: 20 }, { count: 80 }], }, ]; const chartProps = new ChartProps({ formData, width: 800, height: 600, queriesData, theme: supersetTheme, }); const result = transformProps(chartProps as EchartsGaugeChartProps); const series = (result.echartOptions as any).series[0]; expect(series.min).toBe(0); expect(series.max).toBe(160); }); it('should calculate min/max from data when both are null', () => { const formData: SqlaFormData = { ...baseFormData, minVal: null, maxVal: null, }; const queriesData = [ { colnames: ['count'], data: [{ count: 15 }, { count: 45 }], }, ]; const chartProps = new ChartProps({ formData, width: 800, height: 600, queriesData, theme: supersetTheme, }); const result = transformProps(chartProps as EchartsGaugeChartProps); const series = (result.echartOptions as any).series[0]; expect(series.min).toBe(0); expect(series.max).toBe(90); }); it('should calculate min/max from data when minVal is empty string', () => { const formData: SqlaFormData = { ...baseFormData, minVal: '' as any, maxVal: 200, }; const queriesData = [ { colnames: ['count'], data: [{ count: 30 }, { count: 60 }], }, ]; const chartProps = new ChartProps({ formData, width: 800, height: 600, queriesData, theme: supersetTheme, }); const result = transformProps(chartProps as EchartsGaugeChartProps); const series = (result.echartOptions as any).series[0]; expect(series.min).toBe(0); expect(series.max).toBe(200); }); it('should calculate min/max from data when maxVal is invalid string', () => { const formData: SqlaFormData = { ...baseFormData, minVal: 0, maxVal: 'invalid' as any, }; const queriesData = [ { colnames: ['count'], data: [{ count: 25 }, { count: 75 }], }, ]; const chartProps = new ChartProps({ formData, width: 800, height: 600, queriesData, theme: supersetTheme, }); const result = transformProps(chartProps as EchartsGaugeChartProps); const series = (result.echartOptions as any).series[0]; expect(series.min).toBe(0); expect(series.max).toBe(150); }); it('should handle negative values in min/max calculation', () => { const formData: SqlaFormData = { ...baseFormData, minVal: null, maxVal: null, }; const queriesData = [ { colnames: ['count'], data: [{ count: -20 }, { count: 40 }], }, ]; const chartProps = new ChartProps({ formData, width: 800, height: 600, queriesData, theme: supersetTheme, }); const result = transformProps(chartProps as EchartsGaugeChartProps); const series = (result.echartOptions as any).series[0]; expect(series.min).toBe(-40); expect(series.max).toBe(80); }); it('should generate axis labels correctly based on min, max, and splitNumber', () => { const formData: SqlaFormData = { ...baseFormData, minVal: 0, maxVal: 100, splitNumber: 5, }; const queriesData = [ { colnames: ['count'], data: [{ count: 50 }], }, ]; const chartProps = new ChartProps({ formData, width: 800, height: 600, queriesData, theme: supersetTheme, }); const result = transformProps(chartProps as EchartsGaugeChartProps); const series = (result.echartOptions as any).series[0]; expect(series.min).toBe(0); expect(series.max).toBe(100); expect(series.splitNumber).toBe(5); expect(series.axisLabel).toBeDefined(); expect(series.axisLabel.formatter).toBeDefined(); }); it('should calculate axis label length correctly for different number formats', () => { const formData: SqlaFormData = { ...baseFormData, minVal: 0, maxVal: 1000, splitNumber: 10, numberFormat: ',d', }; const queriesData = [ { colnames: ['count'], data: [{ count: 500 }], }, ]; const chartProps = new ChartProps({ formData, width: 800, height: 600, queriesData, theme: supersetTheme, }); const result = transformProps(chartProps as EchartsGaugeChartProps); const series = (result.echartOptions as any).series[0]; expect(series.axisLabel).toBeDefined(); expect(series.axisLabel.formatter).toBeDefined(); expect(typeof series.axisLabel.formatter).toBe('function'); }); it('should integrate interval bounds and colors with calculated min/max', () => { const formData: SqlaFormData = { ...baseFormData, minVal: null, maxVal: null, intervals: '20,60', intervalColorIndices: '1,2', }; const queriesData = [ { colnames: ['count'], data: [{ count: 10 }, { count: 50 }], }, ]; const chartProps = new ChartProps({ formData, width: 800, height: 600, queriesData, theme: supersetTheme, }); const result = transformProps(chartProps as EchartsGaugeChartProps); const series = (result.echartOptions as any).series[0]; expect(series.min).toBe(0); expect(series.max).toBe(100); const { axisLine } = series; expect(axisLine.lineStyle.color).toEqual( expect.arrayContaining([ expect.arrayContaining([expect.any(Number), expect.any(String)]), ]), ); }); it('should handle zero values in data correctly', () => { const formData: SqlaFormData = { ...baseFormData, minVal: null, maxVal: null, }; const queriesData = [ { colnames: ['count'], data: [{ count: 0 }, { count: 0 }], }, ]; const chartProps = new ChartProps({ formData, width: 800, height: 600, queriesData, theme: supersetTheme, }); const result = transformProps(chartProps as EchartsGaugeChartProps); const series = (result.echartOptions as any).series[0]; expect(series.min).toBe(0); expect(series.max).toBe(0); }); it('should handle string minVal/maxVal that can be converted to numbers', () => { const formData: SqlaFormData = { ...baseFormData, minVal: '10' as any, maxVal: '200' as any, }; const queriesData = [ { colnames: ['count'], data: [{ count: 50 }], }, ]; const chartProps = new ChartProps({ formData, width: 800, height: 600, queriesData, theme: supersetTheme, }); const result = transformProps(chartProps as EchartsGaugeChartProps); const series = (result.echartOptions as any).series[0]; expect(series.min).toBe(10); expect(series.max).toBe(200); }); it('should handle different splitNumber values', () => { const formData: SqlaFormData = { ...baseFormData, minVal: 0, maxVal: 100, splitNumber: 20, }; const queriesData = [ { colnames: ['count'], data: [{ count: 50 }], }, ]; const chartProps = new ChartProps({ formData, width: 800, height: 600, queriesData, theme: supersetTheme, }); const result = transformProps(chartProps as EchartsGaugeChartProps); const series = (result.echartOptions as any).series[0]; expect(series.splitNumber).toBe(20); }); }); describe('getIntervalBoundsAndColors', () => { it('should generate correct interval bounds and colors', () => { const colorFn = CategoricalColorNamespace.getScale( 'supersetColors' as string, ); expect(getIntervalBoundsAndColors('', '', colorFn, 0, 10)).toEqual([]); expect(getIntervalBoundsAndColors('4, 10', '1, 2', colorFn, 0, 10)).toEqual( [ [0.4, '#1f77b4'], [1, '#ff7f0e'], ], ); expect( getIntervalBoundsAndColors('4, 8, 10', '9, 8, 7', colorFn, 0, 10), ).toEqual([ [0.4, '#bcbd22'], [0.8, '#7f7f7f'], [1, '#e377c2'], ]); expect(getIntervalBoundsAndColors('4, 10', '1, 2', colorFn, 2, 10)).toEqual( [ [0.25, '#1f77b4'], [1, '#ff7f0e'], ], ); expect( getIntervalBoundsAndColors('-4, 0', '1, 2', colorFn, -10, 0), ).toEqual([ [0.6, '#1f77b4'], [1, '#ff7f0e'], ]); expect( getIntervalBoundsAndColors('-4, -2', '1, 2', colorFn, -10, -2), ).toEqual([ [0.75, '#1f77b4'], [1, '#ff7f0e'], ]); }); });