diff --git a/core/core-backend/src/main/java/io/dataease/chart/manage/ChartDataManage.java b/core/core-backend/src/main/java/io/dataease/chart/manage/ChartDataManage.java index e66e1234ed..290001372d 100644 --- a/core/core-backend/src/main/java/io/dataease/chart/manage/ChartDataManage.java +++ b/core/core-backend/src/main/java/io/dataease/chart/manage/ChartDataManage.java @@ -121,7 +121,8 @@ public class ChartDataManage { List yAxis = new ArrayList<>(view.getYAxis()); if (StringUtils.equalsIgnoreCase(view.getType(), "chart-mix") || StringUtils.equalsIgnoreCase(view.getType(), "bidirectional-bar") - || StringUtils.equalsIgnoreCase(view.getType(), "quadrant")) { + || StringUtils.equalsIgnoreCase(view.getType(), "quadrant") + || StringUtils.containsIgnoreCase(view.getType(), "progress-bar")) { List yAxisExt = new ArrayList<>(view.getYAxisExt()); yAxis.addAll(yAxisExt); } @@ -767,7 +768,8 @@ public class ChartDataManage { || StringUtils.equalsIgnoreCase("liquid", view.getType())) { mapChart = ChartDataBuild.transNormalChartData(xAxis, yAxis, view, data, isDrill); } else if (StringUtils.containsIgnoreCase(view.getType(), "chart-mix") - || StringUtils.containsIgnoreCase(view.getType(), "bidirectional-bar")) { + || StringUtils.containsIgnoreCase(view.getType(), "bidirectional-bar") + || StringUtils.containsIgnoreCase(view.getType(), "progress-bar")) { mapChart = ChartDataBuild.transMixChartDataAntV(xAxis, yAxis, view, data, isDrill); } else if (StringUtils.containsIgnoreCase(view.getType(), "label")) { mapChart = ChartDataBuild.transLabelChartData(xAxis, yAxis, view, data, isDrill); diff --git a/core/core-frontend/src/locales/zh-CN.ts b/core/core-frontend/src/locales/zh-CN.ts index 68e7b0ad82..079983f094 100644 --- a/core/core-frontend/src/locales/zh-CN.ts +++ b/core/core-frontend/src/locales/zh-CN.ts @@ -682,6 +682,7 @@ export default { chart_percentage_bar_stack_horizontal: '横向百分比柱状图', chart_bar_range: '区间条形图', chart_bidirectional_bar: '对称柱状图', + chart_progress_bar: '进度条', chart_line: '基础折线图', chart_area_stack: '堆叠折线图', chart_pie: '饼图', @@ -1126,7 +1127,9 @@ export default { top_n_desc: '合并数据', top_n_input_1: '显示 Top', top_n_input_2: ', 其余合并至其他', - top_n_label: '其他项名称' + top_n_label: '其他项名称', + progress_target: '目标值', + progress_current: '实际值', }, dataset: { scope_edit: '仅编辑时生效', diff --git a/core/core-frontend/src/views/chart/components/editor/util/chart.ts b/core/core-frontend/src/views/chart/components/editor/util/chart.ts index fafc63ad8c..5f01aaa493 100644 --- a/core/core-frontend/src/views/chart/components/editor/util/chart.ts +++ b/core/core-frontend/src/views/chart/components/editor/util/chart.ts @@ -1212,6 +1212,13 @@ export const CHART_TYPE_CONFIGS = [ value: 'bidirectional-bar', title: t('chart.chart_bidirectional_bar'), icon: 'percentage-bar-stack-horizontal' + }, + { + render: 'antv', + category: 'compare', + value: 'progress-bar', + title: t('chart.chart_progress_bar'), + icon: 'percentage-bar-stack-horizontal' } ] }, diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/bar/bidirectional-bar.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/bar/bidirectional-bar.ts index 330085e1af..bc716bef67 100644 --- a/core/core-frontend/src/views/chart/components/js/panel/charts/bar/bidirectional-bar.ts +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/bar/bidirectional-bar.ts @@ -365,7 +365,7 @@ export class BidirectionalHorizontalBar extends G2PlotChartView< if (l.show) { label = { position: l.position, - layout: [{ type: 'fixed-overlap' }], + layout: [{ type: 'limit-in-canvas' }], style: { fill: l.color, fontSize: l.fontSize diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/bar/progress-bar.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/bar/progress-bar.ts new file mode 100644 index 0000000000..9dea6034a2 --- /dev/null +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/bar/progress-bar.ts @@ -0,0 +1,292 @@ +import { G2PlotChartView, G2PlotDrawOptions } from '../../types/impl/g2plot' +import { flow, hexColorToRGBA, parseJson } from '../../../util' +import { setGradientColor } from '../../common/common_antv' +import { useI18n } from '@/hooks/web/useI18n' +import { Bar as G2Progress, BarOptions } from '@antv/g2plot/esm/plots/bar' +import { + BAR_AXIS_TYPE, + BAR_EDITOR_PROPERTY_INNER +} from '@/views/chart/components/js/panel/charts/bar/common' +import { cloneDeep, defaultTo } from 'lodash-es' +import { valueFormatter } from '@/views/chart/components/js/formatter' + +const { t } = useI18n() + +export class ProgressBar extends G2PlotChartView { + axisConfig = { + ...this['axisConfig'], + xAxis: { + name: `${t('chart.form_type')} / ${t('chart.dimension')}`, + type: 'd', + limit: 1 + }, + yAxis: { + name: `${t('chart.progress_target')} / ${t('chart.quota')}`, + type: 'q', + limit: 1 + }, + yAxisExt: { + name: `${t('chart.progress_current')} / ${t('chart.quota')}`, + type: 'q', + limit: 1 + } + } + properties: EditorProperty[] = [ + 'background-overall-component', + 'basic-style-selector', + 'label-selector', + 'tooltip-selector', + 'y-axis-selector', + 'title-selector', + 'jump-set', + 'linkage' + ] + propertyInner = { + ...BAR_EDITOR_PROPERTY_INNER, + 'legend-selector': null, + 'background-overall-component': ['all'], + 'basic-style-selector': ['colors', 'gradient'], + 'label-selector': ['hPosition', 'color', 'fontSize'], + 'tooltip-selector': ['fontSize', 'color', 'backgroundColor', 'tooltipFormatter'], + 'y-axis-selector': ['name', 'color', 'fontSize', 'axisForm', 'axisLabel', 'position'] + } + axis: AxisType[] = [...BAR_AXIS_TYPE, 'yAxisExt'] + protected baseOptions: BarOptions = { + data: [], + xField: 'progress', + yField: 'title', + seriesField: 'type', + isGroup: false, + isPercent: true, + isStack: true, + xAxis: false + } + + drawChart(drawOptions: G2PlotDrawOptions): G2Progress { + const { chart, container, action } = drawOptions + if (!chart.data?.data?.length) { + return + } + const getCompletionRate = (target: number, current: number) => { + if (target === 0) { + return 100 + } + // 目标为正 当前为负 + if (target > 0 && current < 0) { + return 0 + } + // 目标为负 当前为正 正向 + if ((target < 0 && current > 0) || (target < 0 && current === 0)) { + return (2 - current / target) * 100 + } + // 目标与当前都为正 + if (target > 0 && current > 0) { + return (current / target) * 100 + } + // 目标与当前都为负 负向小于0为0 + if (target < 0 && current < 0) { + const completionRate = (2 - current / target) * 100 + return Math.max(completionRate, 0) + } + return 0 + } + // data + const sourceData: Array = cloneDeep(chart.data.data) + const data1 = defaultTo(sourceData[0]?.data, []) + const data2 = defaultTo(sourceData[1]?.data, []) + const currentData = data2.map(item => { + return { + ...item, + type: 'current', + title: item.field, + id: item.quotaList[0].id, + originalValue: item.value, + progress: getCompletionRate(data1.find(i => i.field === item.field)?.value, item.value) + } + }) + const targetData = data1.map(item => { + const progress = 100 - currentData.find(i => i.title === item.field)?.progress + return { + ...item, + type: 'target', + title: item.field, + id: item.quotaList[0].id, + originalValue: item.value, + progress: progress + } + }) + // options + const initOptions: BarOptions = { + ...this.baseOptions, + data: currentData.concat(targetData).flat() + } + const options = this.setupOptions(chart, initOptions) + + // 开始渲染 + const newChart = new G2Progress(container, options) + + newChart.on('interval:click', action) + + return newChart + } + protected configBasicStyle(chart: Chart, options: BarOptions): BarOptions { + const basicStyle = parseJson(chart.customAttr).basicStyle + let color1 = basicStyle.colors?.map((ele, index) => { + if (index === 1) { + return hexColorToRGBA(ele, 10) + } else { + return ele + } + }) + if (basicStyle.gradient) { + color1 = color1.map((ele, index) => { + if (index === 1) { + return ele + } + const tmp = hexColorToRGBA(ele, basicStyle.alpha) + return setGradientColor(tmp, true, 0) + }) + } + options = { + ...options, + color: datum => { + if (datum.type === 'target') { + return 'rgba(0, 0, 0, 0)' + } + return color1[0] + }, + barBackground: { + style: { + fill: color1[1] + } + } + } + return options + } + protected configTooltip(chart: Chart, options: BarOptions): BarOptions { + const tooltipAttr = parseJson(chart.customAttr).tooltip + if (!tooltipAttr.show) { + return { + ...options, + tooltip: { + showContent: false + } + } + } + const yAxis = cloneDeep(chart.yAxis)[0] + const yAxisExt = cloneDeep(chart.yAxisExt)[0] + return { + ...options, + tooltip: { + showContent: true, + domStyles: { + 'g2-tooltip-marker': null + }, + customItems(originalItems) { + const result = [] + originalItems.forEach(item => { + if (item.data) { + const value = valueFormatter(item.data.originalValue, tooltipAttr.tooltipFormatter) + if (item.data.id === yAxis.id) { + result.push({ + ...item, + marker: false, + name: yAxis.chartShowName ? yAxis.chartShowName : yAxis.name, + value: value + }) + } + if (item.data.id === yAxisExt.id) { + result.push({ + ...item, + marker: false, + name: yAxisExt.chartShowName ? yAxisExt.chartShowName : yAxisExt.name, + value: value + }) + } + } + }) + return result.length == 0 ? originalItems : result + } + } + } + } + + protected configLabel(chart: Chart, options: BarOptions): BarOptions { + const baseOptions = super.configLabel(chart, options) + if (!baseOptions.label) { + return baseOptions + } + const { label: labelAttr } = parseJson(chart.customAttr) + baseOptions.label.style.fill = labelAttr.color + const label = { + ...baseOptions.label, + content: item => { + if (item.type === 'target') { + return '' + } + return (item.progress * 100).toFixed(2) + '%' + } + } + if (label.position === 'top') { + label.position = 'right' + } + return { + ...baseOptions, + label + } + } + protected configYAxis(chart: Chart, options: BarOptions): BarOptions { + const baseOption = super.configYAxis(chart, options) + if (!baseOption.yAxis) { + return baseOption + } + if (baseOption.yAxis.position === 'left') { + baseOption.yAxis.position = 'bottom' + } + if (baseOption.yAxis.position === 'right') { + baseOption.yAxis.position = 'top' + } + return baseOption + } + setupDefaultOptions(chart: ChartObj): ChartObj { + chart.customStyle.yAxis = { + ...chart.customStyle.yAxis, + position: 'left', + axisLine: { + show: false, + lineStyle: chart.customStyle.yAxis.axisLine.lineStyle + }, + splitLine: { + show: false, + lineStyle: chart.customStyle.yAxis.axisLine.lineStyle + } + } + chart.customStyle.legend.show = false + chart.customAttr.label.show = true + chart.customAttr.label.position = 'right' + return chart + } + + protected configLegend(chart: Chart, options: BarOptions): BarOptions { + const o = super.configLegend(chart, options) + return { + ...o, + legend: false + } + } + + protected setupOptions(chart: Chart, options: BarOptions): BarOptions { + return flow( + this.configTheme, + this.configBasicStyle, + this.configLabel, + this.configTooltip, + this.configLegend, + this.configYAxis + )(chart, options) + } + + constructor() { + super('progress-bar', []) + } +}