diff --git a/core/core-frontend/src/assets/svg/t-heatmap-dark.svg b/core/core-frontend/src/assets/svg/t-heatmap-dark.svg new file mode 100644 index 0000000000..0e550c0c4b --- /dev/null +++ b/core/core-frontend/src/assets/svg/t-heatmap-dark.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/core/core-frontend/src/assets/svg/t-heatmap-origin.svg b/core/core-frontend/src/assets/svg/t-heatmap-origin.svg new file mode 100644 index 0000000000..a19b1d00ed --- /dev/null +++ b/core/core-frontend/src/assets/svg/t-heatmap-origin.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/core/core-frontend/src/assets/svg/t-heatmap.svg b/core/core-frontend/src/assets/svg/t-heatmap.svg new file mode 100644 index 0000000000..b6c865615f --- /dev/null +++ b/core/core-frontend/src/assets/svg/t-heatmap.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/core/core-frontend/src/components/data-visualization/canvas/Shape.vue b/core/core-frontend/src/components/data-visualization/canvas/Shape.vue index 6d41a1aa60..436510023e 100644 --- a/core/core-frontend/src/components/data-visualization/canvas/Shape.vue +++ b/core/core-frontend/src/components/data-visualization/canvas/Shape.vue @@ -316,7 +316,8 @@ const boardMoveActive = computed(() => { 'table-normal', 'table-pivot', 'symbolic-map', - 'heat-map' + 'heat-map', + 't-heatmap' ] return element.value.isPlugin || CHARTS.includes(element.value.innerType) }) diff --git a/core/core-frontend/src/components/icon-custom/src/Icon.vue b/core/core-frontend/src/components/icon-custom/src/Icon.vue index c476fd042e..9558839098 100644 --- a/core/core-frontend/src/components/icon-custom/src/Icon.vue +++ b/core/core-frontend/src/components/icon-custom/src/Icon.vue @@ -679,6 +679,9 @@ import icon_single_line_outlined from '@/assets/svg/icon_single-line_outlined.sv import icon_todo_outlined from '@/assets/svg/icon_todo_outlined.svg' import icon_file_doc_colorful from '@/assets/svg/icon_file-doc_colorful.svg' import icon_font from '@/assets/svg/icon_font.svg' +import tHeatmap from '@/assets/svg/t-heatmap.svg' +import tHeatmapDark from '@/assets/svg/t-heatmap-dark.svg' +import tHeatmapOrigin from '@/assets/svg/t-heatmap-origin.svg' const iconMap = { '401': _401, icon_adjustment_outlined, @@ -1356,7 +1359,10 @@ const iconMap = { calculate, 'icon_file-doc_colorful': icon_file_doc_colorful, icon_font, - clock + clock, + 't-heatmap': tHeatmap, + 't-heatmap-dark': tHeatmapDark, + 't-heatmap-origin': tHeatmapOrigin } const props = defineProps({ diff --git a/core/core-frontend/src/locales/zh-CN.ts b/core/core-frontend/src/locales/zh-CN.ts index a00d722c6a..449b1651e9 100644 --- a/core/core-frontend/src/locales/zh-CN.ts +++ b/core/core-frontend/src/locales/zh-CN.ts @@ -1116,6 +1116,7 @@ export default { table_column_fixed: '固定列宽', table_column_custom: '自定义', chart_table_pivot: '透视表', + chart_table_heatmap: '热力图', table_pivot_row: '数据行', field_error_tips: '该字段所对应的数据集原始字段发生变更(包括维度、指标,字段类型,字段被删除等),建议重新编辑', diff --git a/core/core-frontend/src/models/chart/chart.d.ts b/core/core-frontend/src/models/chart/chart.d.ts index 2dff80aff4..0365d5f5f0 100644 --- a/core/core-frontend/src/models/chart/chart.d.ts +++ b/core/core-frontend/src/models/chart/chart.d.ts @@ -68,6 +68,8 @@ declare interface Chart { flowMapStartName?: Axis[] flowMapEndName?: Axis[] showPosition: string + + extColor: Axis[] } declare type CustomAttr = DeepPartial | JSONString> declare type CustomStyle = DeepPartial | JSONString> diff --git a/core/core-frontend/src/models/chart/editor.d.ts b/core/core-frontend/src/models/chart/editor.d.ts index 72186e22dc..b2e21a1170 100644 --- a/core/core-frontend/src/models/chart/editor.d.ts +++ b/core/core-frontend/src/models/chart/editor.d.ts @@ -57,7 +57,7 @@ declare type AxisType = | 'area' | 'flowMapStartName' | 'flowMapEndName' - | 'flowMapColor' + | 'extColor' /** * 轴配置 */ diff --git a/core/core-frontend/src/views/chart/components/editor/filter/auth-tree/RowAuth.vue b/core/core-frontend/src/views/chart/components/editor/filter/auth-tree/RowAuth.vue index d969d385d4..62b70de1f6 100644 --- a/core/core-frontend/src/views/chart/components/editor/filter/auth-tree/RowAuth.vue +++ b/core/core-frontend/src/views/chart/components/editor/filter/auth-tree/RowAuth.vue @@ -144,7 +144,7 @@ const getTimeValue = dynamicTimeSetting => { label: '年初', value: 'yearBeginning' } - ].find(ele => ele.value === relativeToCurrent).label + ].find(ele => ele.value === relativeToCurrent)?.label return timeValue } timeValue = `${timeNum}${relativeToCurrentTypeMap[relativeToCurrentType]}${ diff --git a/core/core-frontend/src/views/chart/components/editor/index.vue b/core/core-frontend/src/views/chart/components/editor/index.vue index e1e958e836..b88c890b5c 100644 --- a/core/core-frontend/src/views/chart/components/editor/index.vue +++ b/core/core-frontend/src/views/chart/components/editor/index.vue @@ -413,6 +413,8 @@ const dimensionItemRemove = item => { view.value.flowMapStartName.splice(item.index, 1) } else if (item.removeType === 'flowMapEndName') { view.value.flowMapEndName.splice(item.index, 1) + } else if (item.removeType === 'extColor') { + view.value.extColor.splice(item.index, 1) } } @@ -524,7 +526,12 @@ const onCustomFlowMapEndNameSort = item => { customSortAxis.value = 'flowMapEndName' customSort() } - +const onCustomExtColorSort = item => { + recordSnapshotInfo('render') + state.customSortField = view.value.extColor[item.index] + customSortAxis.value = 'extColor' + customSort() +} const onMove = e => { recordSnapshotInfo('calcData') state.moveId = e.draggedContext.element.id @@ -798,6 +805,10 @@ const addFlowMapEndName = e => { addAxis(e, 'flowMapEndName') } +const addExtColor = e => { + addAxis(e, 'extColor') +} + const onAxisChange = (e, axis: AxisType) => { if (e.removed) { const { element } = e.removed @@ -1110,6 +1121,11 @@ const onChangeFlowMapPointForm = val => { renderChart(view.value) } +const onChangExtColorForm = val => { + view.value.extColor = val + renderChart(view.value) +} + const showRename = val => { recordSnapshotInfo('render') state.itemForm = JSON.parse(JSON.stringify(val)) @@ -1135,6 +1151,7 @@ const removeItems = ( | 'drillFields' | 'flowMapStartName' | 'flowMapEndName' + | 'extColor' ) => { recordSnapshotInfo('calcData') let axis = [] @@ -1170,6 +1187,9 @@ const removeItems = ( case 'flowMapEndName': axis = view.value.flowMapEndName?.splice(0) break + case 'extColor': + axis = view.value.extColor?.splice(0) + break } axis?.length && emitter.emit('removeAxis', { axisType: _type, axis, editType: 'remove' }) } @@ -1223,6 +1243,11 @@ const saveRename = ref => { axis = view.value.flowMapEndName[index] view.value.flowMapEndName[index].chartShowName = chartShowName break + case 'extColor': + axisType = 'extColor' + axis = view.value.extColor[index] + view.value.extColor[index].chartShowName = chartShowName + break default: break } @@ -2131,6 +2156,64 @@ const deleteChartFieldItem = id => { + + + + {{ chartViewInstance.axisConfig.extColor.name }} + + + + + + + + drop($event, 'extColor')" + @dragenter="dragEnter" + @dragover="$event => dragOver($event)" + > + onAxisChange(e, 'extColor')" + > + + + + + + + + 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 48fe4ebaad..2d62e71245 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 @@ -1183,6 +1183,13 @@ export const CHART_TYPE_CONFIGS = [ value: 'table-pivot', title: t('chart.chart_table_pivot'), icon: 'table-pivot' + }, + { + render: 'antv', + category: 'table', + value: 't-heatmap', + title: t('chart.chart_table_heatmap'), + icon: 't-heatmap' } ] }, diff --git a/core/core-frontend/src/views/chart/components/js/panel/charts/table/t-heatmap.ts b/core/core-frontend/src/views/chart/components/js/panel/charts/table/t-heatmap.ts new file mode 100644 index 0000000000..32942ca190 --- /dev/null +++ b/core/core-frontend/src/views/chart/components/js/panel/charts/table/t-heatmap.ts @@ -0,0 +1,316 @@ +import { + G2PlotChartView, + G2PlotDrawOptions +} from '@/views/chart/components/js/panel/types/impl/g2plot' +import type { Heatmap, HeatmapOptions } from '@antv/g2plot/esm/plots/heatmap' +import { flow, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util' +import { useI18n } from '@/hooks/web/useI18n' +import { deepCopy } from '@/utils/utils' +import { cloneDeep } from 'lodash-es' +import { + getLegend, + getPadding, + getXAxis, + getYAxis +} from '@/views/chart/components/js/panel/common/common_antv' +import { valueFormatter } from '@/views/chart/components/js/formatter' +import { Datum } from '@antv/g2plot/esm/types/common' + +const { t } = useI18n() +const DEFAULT_DATA = [] +/** + * 热力图 + */ +export class TableHeatmap extends G2PlotChartView { + properties: EditorProperty[] = [ + 'basic-style-selector', + 'background-overall-component', + 'label-selector', + 'legend-selector', + 'x-axis-selector', + 'y-axis-selector', + 'title-selector', + 'tooltip-selector', + 'jump-set', + 'linkage' + ] + propertyInner: EditorPropertyInner = { + 'background-overall-component': ['all'], + 'basic-style-selector': ['colors'], + 'label-selector': ['fontSize', 'color'], + 'x-axis-selector': ['name', 'color', 'fontSize', 'position', 'axisLabel', 'axisLine'], + 'y-axis-selector': ['name', 'color', 'fontSize', 'position', 'axisLabel', 'axisLine'], + 'title-selector': [ + 'title', + 'fontSize', + 'color', + 'hPosition', + 'isItalic', + 'isBolder', + 'remarkShow', + 'fontFamily', + 'letterSpace', + 'fontShadow' + ], + 'legend-selector': ['orient', 'color', 'fontSize', 'hPosition', 'vPosition'], + 'tooltip-selector': ['color', 'fontSize', 'backgroundColor'] + } + axis: AxisType[] = ['xAxis', 'xAxisExt', 'extColor', 'filter'] + axisConfig: AxisConfig = { + xAxis: { + name: `横轴 / ${t('chart.dimension')}`, + type: 'd', + limit: 1 + }, + xAxisExt: { + name: `纵轴 / ${t('chart.dimension')}`, + type: 'd', + limit: 1 + }, + extColor: { + name: `${t('chart.color')} / ${t('chart.dimension_or_quota')}`, + limit: 1 + } + } + + async drawChart(drawOptions: G2PlotDrawOptions): Promise { + const { chart, container, action } = drawOptions + const xAxis = deepCopy(chart.xAxis) + const xAxisExt = deepCopy(chart.xAxisExt) + const extColor = deepCopy(chart.extColor) + if (!xAxis?.length || !xAxisExt?.length || !extColor?.length) { + return + } + const xField = xAxis[0].dataeaseName + const xFieldExt = xAxisExt[0].dataeaseName + const extColorField = extColor[0].dataeaseName + const containerDom = document.getElementById(container) + const containerHeight = containerDom?.clientHeight || 100 + // data + const data = cloneDeep(chart.data.tableRow) + data.forEach(i => { + Object.keys(i).forEach(key => { + if (key === '*') { + i['@'] = i[key] + } + }) + }) + // options + const initOptions: HeatmapOptions = { + data: data, + xField: xField, + yField: xFieldExt, + colorField: extColorField === '*' ? '@' : extColorField, + appendPadding: getPadding(chart), + meta: { + [xField]: { + type: 'cat' + }, + [xFieldExt]: { + type: 'cat' + } + }, + legend: { + layout: 'vertical', + position: 'right', + slidable: true, + maxHeight: containerHeight - containerHeight * 0.2, + label: { + align: 'left', + spacing: 10 + } + } + } + const options = this.setupOptions(chart, initOptions) + const { Heatmap } = await import('@antv/g2plot/esm/plots/heatmap') + const newChart = new Heatmap(container, options) + newChart.on('plot:click', param => { + if (!param.data?.data) { + return + } + const pointData = param.data.data + const dimensionList = [] + const quotaList = [] + chart.data.fields.forEach((item, index) => { + Object.keys(pointData).forEach(key => { + if (key.startsWith('f_') && item.dataeaseName === key) { + dimensionList.push({ + id: item.id, + dataeaseName: item.dataeaseName, + value: pointData[key] + }) + } + if (!key.startsWith('f_')) { + quotaList.push({ + id: item.id, + dataeaseName: item.dataeaseName, + value: pointData[key] + }) + } + }) + }) + action({ + x: param.data.x, + y: param.data.y, + data: { + data: { + ...param.data.data, + value: quotaList[0].value, + name: dimensionList[0].id, + dimensionList: dimensionList, + quotaList: quotaList + } + } + }) + }) + newChart.on('afterrender', ev => { + const l = JSON.parse(JSON.stringify(parseJson(chart.customStyle).legend)) + if (l.show) { + const extColor = deepCopy(chart.extColor) + const containerDom = document.getElementById(container) + const containerHeight = containerDom?.clientHeight || 100 + const containerWidth = containerDom?.clientWidth || 100 + let defaultLength = getLegend(chart) + if (l.orient === 'vertical') { + defaultLength = containerHeight - containerHeight * 0.5 + } else { + defaultLength = containerWidth - containerWidth * 0.5 + } + ev.view.getController('legend').option[extColor[0].dataeaseName]['rail'].defaultLength = + defaultLength + } + }) + return newChart + } + + protected configBasicStyle(chart: Chart, options: HeatmapOptions): HeatmapOptions { + const basicStyle = parseJson(chart.customAttr).basicStyle + const color = basicStyle.colors?.map((ele, index) => { + return hexColorToRGBA(ele, basicStyle.alpha) + }) + return { + ...options, + color + } + } + protected configTooltip(chart: Chart, options: HeatmapOptions): HeatmapOptions { + let tooltip + let customAttr: DeepPartial + if (chart.customAttr) { + customAttr = parseJson(chart.customAttr) + // tooltip + if (customAttr.tooltip) { + const extColor = deepCopy(chart.extColor) + const t = JSON.parse(JSON.stringify(customAttr.tooltip)) + if (t.show) { + tooltip = { + showTitle: true, + customItems(originalItems) { + const name = extColor[0]?.chartShowName + ? extColor[0]?.chartShowName + : extColor[0]?.name + let value = originalItems[0].value + if (!isNaN(Number(value))) { + value = valueFormatter(originalItems[0].value, extColor[0]?.formatterCfg) + } + const newItems = { + ...originalItems[0], + name: name, + value: value + } + return [newItems] + } + } + } else { + tooltip = false + } + } + } + return { + ...options, + tooltip + } + } + + protected configXAxis(chart: Chart, options: HeatmapOptions): HeatmapOptions { + const xAxis = getXAxis(chart, options) + return { ...options, xAxis: { ...xAxis, grid: null } } + } + + protected configYAxis(chart: Chart, options: HeatmapOptions): HeatmapOptions { + const yAxis = getYAxis(chart) + return { ...options, yAxis: { ...yAxis, grid: null } } + } + + protected configLegend(chart: Chart, options: HeatmapOptions): HeatmapOptions { + const tmpOptions = super.configLegend(chart, options) + if (tmpOptions.legend) { + const l = JSON.parse(JSON.stringify(parseJson(chart.customStyle).legend)) + tmpOptions.legend.slidable = true + tmpOptions.legend.minHeight = 10 + tmpOptions.legend.minWidth = 10 + tmpOptions.legend.maxHeight = 600 + tmpOptions.legend.maxWidth = 600 + if (l.orient === 'vertical') { + tmpOptions.legend.offsetY = -5 + } + tmpOptions.legend.rail = { + defaultLength: 100 + } + tmpOptions.legend.label = { + spacing: 10, + style: { + fill: l.color, + fontSize: l.fontSize + } + } + } + return tmpOptions + } + + setupDefaultOptions(chart: ChartObj): ChartObj { + chart.customStyle.legend.orient = 'vertical' + chart.customStyle.legend.vPosition = 'center' + chart.customStyle.legend.hPosition = 'right' + return chart + } + + protected configLabel(chart: Chart, options: HeatmapOptions): HeatmapOptions { + const tmpOptions = super.configLabel(chart, options) + if (tmpOptions.label) { + const extColor = deepCopy(chart.extColor) + const label = { + ...tmpOptions.label, + position: 'middle', + layout: [{ type: 'hide-overlap' }, { type: 'limit-in-canvas' }], + formatter: data => { + const value = data[extColor[0]?.dataeaseName] + if (!isNaN(Number(value))) { + return valueFormatter(value, extColor[0]?.formatterCfg) + } + return value + } + } + return { + ...tmpOptions, + label + } + } + return tmpOptions + } + + protected setupOptions(chart: Chart, options: HeatmapOptions): HeatmapOptions { + return flow( + this.configXAxis, + this.configYAxis, + this.configBasicStyle, + this.configLegend, + this.configTooltip, + this.configLabel + )(chart, options) + } + + constructor() { + super('t-heatmap', DEFAULT_DATA) + } +} diff --git a/sdk/extensions/extensions-view/src/main/java/io/dataease/extensions/view/dto/ChartAxis.java b/sdk/extensions/extensions-view/src/main/java/io/dataease/extensions/view/dto/ChartAxis.java index 1ad9a6d3b3..fe76307324 100644 --- a/sdk/extensions/extensions-view/src/main/java/io/dataease/extensions/view/dto/ChartAxis.java +++ b/sdk/extensions/extensions-view/src/main/java/io/dataease/extensions/view/dto/ChartAxis.java @@ -9,5 +9,6 @@ public enum ChartAxis { yAxis, yAxisExt, drill, + extColor, extBubble; } diff --git a/sdk/extensions/extensions-view/src/main/java/io/dataease/extensions/view/dto/ChartViewBaseDTO.java b/sdk/extensions/extensions-view/src/main/java/io/dataease/extensions/view/dto/ChartViewBaseDTO.java index f72c935b13..62112aeccc 100644 --- a/sdk/extensions/extensions-view/src/main/java/io/dataease/extensions/view/dto/ChartViewBaseDTO.java +++ b/sdk/extensions/extensions-view/src/main/java/io/dataease/extensions/view/dto/ChartViewBaseDTO.java @@ -224,4 +224,6 @@ public class ChartViewBaseDTO implements Serializable { */ private List calParams; + private List extColor; + }