import { isNumber } from 'lodash-es'
|
import { DEFAULT_TITLE_STYLE } from '../editor/util/chart'
|
import { equalsAny, includesAny } from '../editor/util/StringUtils'
|
import { FeatureCollection } from '@antv/l7plot/dist/esm/plots/choropleth/types'
|
import { useMapStoreWithOut } from '@/store/modules/map'
|
import { getGeoJson } from '@/api/map'
|
import { computed, toRaw } from 'vue'
|
import { Options } from '@antv/g2plot/esm'
|
import { PickOptions } from '@antv/g2plot/esm/core/plot'
|
import { innerExportDataSetDetails, innerExportDetails } from '@/api/chart'
|
import { ElMessage } from 'element-plus-secondary'
|
import { useI18n } from '@/hooks/web/useI18n'
|
import { useLinkStoreWithOut } from '@/store/modules/link'
|
import { useAppStoreWithOut } from '@/store/modules/app'
|
import { Decimal } from 'decimal.js'
|
|
const appStore = useAppStoreWithOut()
|
const isDataEaseBi = computed(() => appStore.getIsDataEaseBi)
|
|
const { t } = useI18n()
|
// 同时支持将hex和rgb,转换成rgba
|
export function hexColorToRGBA(hex, alpha) {
|
let rgb = [] // 定义rgb数组
|
if (hex.indexOf('#') > -1) {
|
if (/^\#[0-9A-F]{3}$/i.test(hex)) {
|
// 判断传入是否为#三位十六进制数
|
let sixHex = '#'
|
hex.replace(/[0-9A-F]/gi, function (kw) {
|
sixHex += kw + kw // 把三位16进制数转化为六位
|
})
|
hex = sixHex // 保存回hex
|
}
|
if (/^#[0-9A-F]{6}$/i.test(hex)) {
|
// 判断传入是否为#六位十六进制数
|
hex.replace(/[0-9A-F]{2}/gi, function (kw) {
|
// eslint-disable-next-line no-eval
|
rgb.push(eval('0x' + kw)) // 十六进制转化为十进制并存如数组
|
})
|
return `rgba(${rgb.join(',')},${alpha / 100})` // 输出RGB格式颜色
|
} else {
|
return 'rgb(0,0,0)'
|
}
|
} else {
|
rgb = hex.match(/\d+/g)
|
return `rgba(${rgb.join(',')},${alpha / 100})`
|
}
|
}
|
|
export function digToHex(dig) {
|
let prefix = ''
|
const num = parseInt((dig * 2.55).toString())
|
if (num < 16) {
|
prefix = '0'
|
}
|
return prefix.concat(num.toString(16).toUpperCase())
|
}
|
|
export function customSort(custom, data) {
|
const indexArr = []
|
const joinArr = []
|
for (let i = 0; i < custom.length; i++) {
|
const ele = custom[i]
|
for (let j = 0; j < data.length; j++) {
|
const d = data[j]
|
if (ele === d.field) {
|
joinArr.push(d)
|
indexArr.push(j)
|
}
|
}
|
}
|
// 取得 joinArr 就是两者的交集
|
const indexArrData = []
|
for (let i = 0; i < data.length; i++) {
|
indexArrData.push(i)
|
}
|
const indexResult = []
|
for (let i = 0; i < indexArrData.length; i++) {
|
if (indexArr.indexOf(indexArrData[i]) === -1) {
|
indexResult.push(indexArrData[i])
|
}
|
}
|
|
const subArr = []
|
for (let i = 0; i < indexResult.length; i++) {
|
subArr.push(data[indexResult[i]])
|
}
|
|
return joinArr.concat(subArr)
|
}
|
|
export function customColor(custom, res) {
|
const result = []
|
for (let i = 0; i < res.length; i++) {
|
const r = res[i]
|
let flag = false
|
for (let j = 0; j < custom.length; j++) {
|
const c = custom[j]
|
if (r.name === c.name) {
|
flag = true
|
result.push(c)
|
}
|
}
|
if (!flag) {
|
result.push(r)
|
}
|
}
|
return result
|
}
|
|
export function getColors(chart, colors, reset) {
|
// 自定义颜色,先按照没有设定的情况,并排好序,当做最终结果
|
let seriesColors = []
|
let series
|
if (chart.type.includes('stack')) {
|
if (chart.data) {
|
const data = chart.data.data
|
const stackData = []
|
for (let i = 0; i < data.length; i++) {
|
const s = data[i]
|
stackData.push(s.category)
|
}
|
const sArr = stackData.filter(function (item, index, stackData) {
|
return stackData.indexOf(item, 0) === index
|
})
|
|
for (let i = 0; i < sArr.length; i++) {
|
const s = sArr[i]
|
seriesColors.push({
|
name: s,
|
color: colors[i % colors.length],
|
isCustom: false
|
})
|
}
|
}
|
} else if (
|
includesAny(chart.type, 'bar', 'scatter', 'radar', 'area') &&
|
!chart.type.includes('group')
|
) {
|
if (Object.prototype.toString.call(chart.yAxis) === '[object Array]') {
|
series = JSON.parse(JSON.stringify(chart.yAxis))
|
} else {
|
series = JSON.parse(chart.yAxis)
|
}
|
if (series) {
|
for (let i = 0; i < series.length; i++) {
|
const s = series[i]
|
seriesColors.push({
|
name: s.name,
|
color: colors[i % colors.length],
|
isCustom: false
|
})
|
}
|
}
|
} else if (equalsAny(chart.type, 'bar-group', 'line')) {
|
// 拿到data中的category,并去重,然后构建seriesColor
|
if (chart.data) {
|
const data = chart.data.data
|
const s = []
|
if (data) {
|
data.forEach(cur => {
|
if (s.indexOf(cur.category) < 0) {
|
s.push(cur.category)
|
}
|
})
|
}
|
for (let i = 0; i < s.length; i++) {
|
seriesColors.push({
|
name: s[i],
|
color: colors[i % colors.length],
|
isCustom: false
|
})
|
}
|
}
|
} else {
|
if (chart.data) {
|
const data = chart.data.data
|
for (let i = 0; i < data.length; i++) {
|
const s = data[i]
|
seriesColors.push({
|
name: s.field,
|
color: colors[i % colors.length],
|
isCustom: false
|
})
|
}
|
}
|
}
|
// 如果有自定义,则与上述中的结果合并。
|
// res,custom,以custom为准,去掉res中不存在的,并将custom中name一样的color赋值给res,不存在的name,即新增值,使用i % colors.length,从配色方案中选
|
if (!reset) {
|
let sc = null
|
if (Object.prototype.toString.call(chart.customAttr) === '[object Object]') {
|
sc = JSON.parse(JSON.stringify(chart.customAttr)).color.seriesColors
|
} else {
|
sc = JSON.parse(chart.customAttr)['color'].seriesColors
|
}
|
if (sc && sc.length > 0) {
|
seriesColors = customColor(sc, seriesColors)
|
}
|
// 根据isCustom字段,修正color
|
for (let i = 0; i < seriesColors.length; i++) {
|
if (!seriesColors[i].isCustom) {
|
seriesColors[i].color = colors[i % colors.length]
|
}
|
}
|
}
|
return seriesColors
|
}
|
|
export function antVCustomColor(chart) {
|
const colors = []
|
if (chart.customAttr) {
|
const customAttr = JSON.parse(JSON.stringify(chart.customAttr))
|
// color
|
if (customAttr.basicStyle) {
|
const basicStyle = JSON.parse(JSON.stringify(customAttr.basicStyle))
|
|
const customColors = getColors(chart, basicStyle.colors, false)
|
for (let i = 0; i < customColors.length; i++) {
|
colors.push(hexColorToRGBA(customColors[i].color, basicStyle.alpha))
|
}
|
}
|
}
|
return colors
|
}
|
|
export function getRemark(chart) {
|
const remark = {} as any
|
if (chart.customStyle) {
|
const customStyle = JSON.parse(JSON.stringify(chart.customStyle))
|
if (customStyle.text) {
|
const title = JSON.parse(JSON.stringify(customStyle.text))
|
remark.show = title.remarkShow ? title.remarkShow : DEFAULT_TITLE_STYLE.remarkShow
|
remark.content = title.remark ? title.remark : DEFAULT_TITLE_STYLE.remark
|
remark.bgFill = title.remarkBackgroundColor
|
? title.remarkBackgroundColor
|
: DEFAULT_TITLE_STYLE.remarkBackgroundColor
|
}
|
}
|
return remark
|
}
|
|
export const quotaViews = ['label', 'richTextView', 'indicator', 'gauge', 'liquid']
|
// 地图
|
const mapChartTypes = ['bubble-map', 'flow-map', 'heat-map', 'map', 'symbolic-map']
|
// 分布图
|
const distributionChartTypes = [
|
'pie',
|
'pie-donut',
|
'pie-rose',
|
'pie-donut-rose',
|
'radar',
|
'treemap',
|
'word-cloud'
|
]
|
// 关系图
|
const relationChartTypes = ['scatter', 'quadrant', 'funnel', 'sankey', 'circle-packing']
|
// 不支持指标累加的图表
|
export const notSupportAccumulateViews = [
|
...quotaViews,
|
...mapChartTypes,
|
...distributionChartTypes,
|
...relationChartTypes,
|
'table-info',
|
't-heatmap',
|
'percentage-bar-stack',
|
'percentage-bar-stack-horizontal',
|
'progress-bar',
|
'stock-line'
|
]
|
|
export function handleEmptyDataStrategy<O extends PickOptions>(chart: Chart, options: O): O {
|
const { data } = options as unknown as Options
|
const isChartMix = chart.type.includes('chart-mix')
|
if (!data?.length) {
|
return options
|
}
|
const strategy = parseJson(chart.senior).functionCfg.emptyDataStrategy
|
if (strategy === 'ignoreData') {
|
if (isChartMix) {
|
for (let i = 0; i < data.length; i++) {
|
handleIgnoreData(data[i] as Record<string, any>[])
|
}
|
} else {
|
handleIgnoreData(data)
|
}
|
return options
|
}
|
const { yAxis, xAxisExt, extStack, extBubble } = chart
|
const multiDimension = yAxis?.length >= 2 || xAxisExt?.length > 0 || extStack?.length > 0
|
switch (strategy) {
|
case 'breakLine': {
|
if (isChartMix) {
|
if (data[0]) {
|
if (xAxisExt?.length > 0 || extStack?.length > 0) {
|
handleBreakLineMultiDimension(data[0] as Record<string, any>[])
|
}
|
}
|
if (data[1]) {
|
if (extBubble?.length > 0) {
|
handleBreakLineMultiDimension(data[1] as Record<string, any>[])
|
}
|
}
|
} else {
|
if (multiDimension) {
|
handleBreakLineMultiDimension(data)
|
}
|
}
|
return {
|
...options,
|
connectNulls: false
|
}
|
}
|
case 'setZero': {
|
if (isChartMix) {
|
if (data[0]) {
|
if (xAxisExt?.length > 0 || extStack?.length > 0) {
|
handleSetZeroMultiDimension(data[0] as Record<string, any>[])
|
} else {
|
handleSetZeroSingleDimension(data[0] as Record<string, any>[])
|
}
|
}
|
if (data[1]) {
|
if (extBubble?.length > 0) {
|
handleSetZeroMultiDimension(data[1] as Record<string, any>[], true)
|
} else {
|
handleSetZeroSingleDimension(data[1] as Record<string, any>[], true)
|
}
|
}
|
} else {
|
if (multiDimension) {
|
// 多维度置0
|
handleSetZeroMultiDimension(data)
|
} else {
|
// 单维度置0
|
handleSetZeroSingleDimension(data)
|
}
|
}
|
break
|
}
|
}
|
return options
|
}
|
|
function handleBreakLineMultiDimension(data) {
|
const dimensionInfoMap = new Map()
|
const subDimensionSet = new Set()
|
const quotaMap = new Map<string, { id: string }[]>()
|
for (let i = 0; i < data.length; i++) {
|
const item = data[i]
|
const dimensionInfo = dimensionInfoMap.get(item.field)
|
if (dimensionInfo) {
|
dimensionInfo.set.add(item.category)
|
} else {
|
dimensionInfoMap.set(item.field, { set: new Set([item.category]), index: i })
|
}
|
subDimensionSet.add(item.category)
|
quotaMap.set(item.category, item.quotaList)
|
}
|
// Map 是按照插入顺序排序的,所以插入索引往后推
|
let insertCount = 0
|
dimensionInfoMap.forEach((dimensionInfo, field) => {
|
if (dimensionInfo.set.size < subDimensionSet.size) {
|
let subInsertIndex = 0
|
subDimensionSet.forEach(dimension => {
|
if (!dimensionInfo.set.has(dimension)) {
|
data.splice(dimensionInfo.index + insertCount + subInsertIndex, 0, {
|
field,
|
value: null,
|
category: dimension,
|
quotaList: quotaMap.get(dimension as string)
|
})
|
}
|
subInsertIndex++
|
})
|
insertCount += subDimensionSet.size - dimensionInfo.set.size
|
}
|
})
|
}
|
|
function handleSetZeroMultiDimension(data: Record<string, any>[], isExt = false) {
|
const dimensionInfoMap = new Map()
|
const subDimensionSet = new Set()
|
const quotaMap = new Map<string, { id: string }[]>()
|
for (let i = 0; i < data.length; i++) {
|
const item = data[i]
|
if (item.value === null) {
|
item.value = 0
|
if (isExt) {
|
item.valueExt = 0
|
}
|
}
|
const dimensionInfo = dimensionInfoMap.get(item.field)
|
if (dimensionInfo) {
|
dimensionInfo.set.add(item.category)
|
} else {
|
dimensionInfoMap.set(item.field, { set: new Set([item.category]), index: i })
|
}
|
subDimensionSet.add(item.category)
|
quotaMap.set(item.category, item.quotaList)
|
}
|
let insertCount = 0
|
dimensionInfoMap.forEach((dimensionInfo, field) => {
|
if (dimensionInfo.set.size < subDimensionSet.size) {
|
let subInsertIndex = 0
|
subDimensionSet.forEach(dimension => {
|
if (!dimensionInfo.set.has(dimension)) {
|
const _temp = {
|
field,
|
value: 0,
|
category: dimension,
|
quotaList: quotaMap.get(dimension as string)
|
} as any
|
if (isExt) {
|
_temp.valueExt = 0
|
}
|
|
data.splice(dimensionInfo.index + insertCount + subInsertIndex, 0, _temp)
|
}
|
subInsertIndex++
|
})
|
insertCount += subDimensionSet.size - dimensionInfo.set.size
|
}
|
})
|
}
|
|
function handleSetZeroSingleDimension(data: Record<string, any>[], isExt = false) {
|
data.forEach(item => {
|
if (item.value === null) {
|
if (!isExt) {
|
item.value = 0
|
} else {
|
item.valueExt = 0
|
}
|
}
|
})
|
}
|
|
function handleIgnoreData(data: Record<string, any>[]) {
|
for (let i = data.length - 1; i >= 0; i--) {
|
const item = data[i]
|
if (item.value === null) {
|
data.splice(i, 1)
|
}
|
}
|
}
|
|
export function resetRgbOpacity(sourceColor: string, times: number): string {
|
if (sourceColor?.startsWith('rgb')) {
|
const numbers = sourceColor.match(/(\d(\.\d+)?)+/g)
|
if (numbers?.length === 4) {
|
const opacity = parseFloat(numbers[3])
|
if (isNumber(opacity)) {
|
let resultOpacity = parseFloat((opacity * times).toFixed(2))
|
if (resultOpacity > 1) {
|
resultOpacity = 1
|
}
|
const colorArr = numbers.slice(0, 3).concat(resultOpacity.toString())
|
return `rgba(${colorArr.join(',')})`
|
}
|
}
|
}
|
return sourceColor
|
}
|
|
export function parseJson<T>(str: T | JSONString<T>): T {
|
if (typeof str !== 'string') {
|
return str as T
|
}
|
return JSON.parse(str) as T
|
}
|
|
type FlowFunction<P, R> = (param: P, result: R, context?: Record<string, any>, thisArg?: any) => R
|
|
export function flow<P, R>(...flows: FlowFunction<P, R>[]): FlowFunction<P, R> {
|
return (param: P, result: R, context?: Record<string, any>, thisArg?: any) => {
|
return flows.reduce((result: R, flow: FlowFunction<P, R>) => {
|
if (thisArg) {
|
return flow.call(thisArg, param, result, context)
|
} else {
|
return flow(param, result, context)
|
}
|
}, result)
|
}
|
}
|
|
export const isParent = (type: any, parentType: any) => {
|
let _type = type
|
while (_type) {
|
if (_type === parentType) {
|
return true
|
}
|
_type = _type.__proto__
|
}
|
return false
|
}
|
|
export const getGeoJsonFile = async (areaId: string): Promise<FeatureCollection> => {
|
const mapStore = useMapStoreWithOut()
|
let geoJson = mapStore.mapCache[areaId]
|
if (!geoJson) {
|
const res = await getGeoJson(areaId)
|
geoJson = res?.data
|
mapStore.setMap({ id: areaId, geoJson })
|
}
|
return toRaw(geoJson)
|
}
|
|
const getExcelDownloadRequest = (data, type?) => {
|
let fields = JSON.parse(JSON.stringify(data.fields))
|
// liquid gauge 只需要导出一个字段
|
if (['gauge', 'liquid'].includes(type) && fields.length > 1) {
|
fields = fields.slice(1)
|
}
|
const tableRow = JSON.parse(JSON.stringify(data.tableRow))
|
const excelHeader = fields.map(item => item.chartShowName ?? item.name)
|
const excelTypes = fields.map(item => item.deType)
|
const excelHeaderKeys = fields.map(item => item.dataeaseName)
|
let excelData = tableRow.map(item => excelHeaderKeys.map(i => item[i]))
|
let detailFields = []
|
if (data.detailFields?.length) {
|
detailFields = data.detailFields.map(item => {
|
return {
|
name: item.name,
|
deType: item.deType,
|
dataeaseName: item.dataeaseName
|
}
|
})
|
excelData = tableRow.map(item => {
|
return excelHeaderKeys.map(i => {
|
if (i === 'detail' && !item[i] && Array.isArray(item['details'])) {
|
const arr = item['details']
|
if (arr?.length) {
|
return arr.map(ele => detailFields.map(field => ele[field.dataeaseName]))
|
}
|
return null
|
}
|
return item[i]
|
})
|
})
|
}
|
return {
|
header: excelHeader,
|
details: excelData,
|
excelTypes: excelTypes,
|
excelHeaderKeys: excelHeaderKeys,
|
detailFields: detailFields
|
}
|
}
|
|
function getChartExcelTitle(preFix, viewTitle) {
|
const now = new Date()
|
const pad = n => n.toString().padStart(2, '0')
|
const year = now.getFullYear()
|
const month = pad(now.getMonth() + 1) // 月份从 0 开始
|
const day = pad(now.getDate())
|
const hour = pad(now.getHours())
|
const minute = pad(now.getMinutes())
|
const second = pad(now.getSeconds())
|
return `${preFix}_${viewTitle}_${year}${month}${day}_${hour}${minute}${second}`
|
}
|
|
export const exportExcelDownload = (chart, preFix, callBack?) => {
|
const excelName = getChartExcelTitle(preFix, chart.title)
|
let request: any = {
|
proxy: null,
|
dvId: chart.sceneId,
|
viewId: chart.id,
|
viewInfo: chart,
|
viewName: excelName,
|
busiFlag: chart.busiFlag,
|
downloadType: chart.downloadType
|
}
|
if (chart.type.includes('chart-mix')) {
|
const req1 = getExcelDownloadRequest(chart.data.left)
|
const req2 = getExcelDownloadRequest(chart.data.right)
|
request = {
|
...request,
|
multiInfo: [req1, req2]
|
}
|
if (chart.downloadType === 'dataset') {
|
delete request.multiInfo
|
}
|
} else {
|
const req = getExcelDownloadRequest(chart.data, chart.type)
|
request = {
|
...request,
|
...req
|
}
|
}
|
|
if (chart.type.includes('symbolic-map')) {
|
request.detailFields = []
|
}
|
|
const linkStore = useLinkStoreWithOut()
|
|
if (isDataEaseBi.value || appStore.getIsIframe) {
|
request.dataEaseBi = true
|
}
|
const method = request.downloadType === 'dataset' ? innerExportDataSetDetails : innerExportDetails
|
if (request.viewInfo?.customAttr?.basicStyle?.tablePageMode) {
|
request.viewInfo.customAttr.basicStyle.tablePageMode = 'page'
|
}
|
method(request)
|
.then(res => {
|
if (linkStore.getLinkToken || isDataEaseBi.value || appStore.getIsIframe) {
|
const blob = new Blob([res.data], { type: 'application/vnd.ms-excel' })
|
const link = document.createElement('a')
|
link.style.display = 'none'
|
link.href = URL.createObjectURL(blob)
|
link.download = excelName + '.xlsx' // 下载的文件名
|
document.body.appendChild(link)
|
link.click()
|
document.body.removeChild(link)
|
} else {
|
callBack && callBack(res)
|
}
|
})
|
.catch(() => {
|
console.error('Excel download error')
|
callBack('error')
|
})
|
}
|
|
export const copyString = (content: string, notify = false) => {
|
let clipboard = navigator.clipboard as Pick<Clipboard, 'writeText'>
|
if (!clipboard || window.top !== window.self) {
|
clipboard = {
|
writeText: data => {
|
return new Promise<void>(resolve => {
|
const textareaDom = document.createElement('textarea')
|
textareaDom.setAttribute('style', 'z-index: -1;position: fixed;opacity: 0;')
|
textareaDom.value = data
|
document.body.appendChild(textareaDom)
|
textareaDom.select()
|
document.execCommand('copy')
|
textareaDom.remove()
|
resolve()
|
})
|
}
|
}
|
}
|
clipboard.writeText(content).then(() => {
|
if (notify) {
|
ElMessage.success(t('commons.copy_success'))
|
}
|
})
|
}
|
|
/**
|
* 计算动态区间和颜色
|
* @param minValue
|
* @param maxValue
|
* @param intervals
|
* @param colors
|
*/
|
export const getDynamicColorScale = (
|
minValue: number,
|
maxValue: number,
|
intervals: number,
|
colors?: string[]
|
) => {
|
const step = (maxValue - minValue) / intervals
|
|
const colorScale = []
|
for (let i = 0; i < intervals; i++) {
|
colorScale.push({
|
value: [minValue + i * step, minValue + (i + 1) * step],
|
color: colors?.[i],
|
label: `${(minValue + i * step).toFixed(0)} - ${(minValue + (i + 1) * step).toFixed(0)}`
|
})
|
}
|
|
return colorScale
|
}
|
/**
|
* 过滤掉不在区间的数据
|
* @param data
|
* @param maxValue
|
* @param minValue
|
*/
|
export const filterChartDataByRange = (data: any[], maxValue: number, minValue: number) => {
|
return data.filter(
|
item =>
|
item.value === null ||
|
item.value === undefined ||
|
(item.value >= minValue && item.value <= maxValue)
|
)
|
}
|
|
/**
|
* 获取数据最大最小值
|
* @param data
|
* @param field 值字段
|
* @param maxValue
|
* @param minValue
|
* @param callback
|
*/
|
export const getMaxAndMinValueByData = (
|
data: any[],
|
field: string,
|
maxValue: number,
|
minValue: number,
|
callback: (max: number, min: number) => void
|
) => {
|
// 定义一个辅助函数来计算最大值或最小值
|
const calculateExtreme = (isMax: boolean) => {
|
return data.reduce(
|
(extreme, current) => {
|
return isMax
|
? current[field] > extreme
|
? current[field]
|
: extreme
|
: current[field] < extreme
|
? current[field]
|
: extreme
|
},
|
isMax ? Number.MIN_SAFE_INTEGER : Number.MAX_SAFE_INTEGER
|
)
|
}
|
if (minValue === null || maxValue === null) {
|
let maxResult = maxValue
|
let minResult = minValue
|
if (maxResult === null) {
|
maxResult = calculateExtreme(true)
|
}
|
if (minResult === null) {
|
minResult = calculateExtreme(false)
|
}
|
callback(maxResult, minResult)
|
}
|
if (minValue === 0 && maxValue === 0) {
|
const maxResult = calculateExtreme(true)
|
const minResult = calculateExtreme(false)
|
callback(maxResult, minResult)
|
}
|
}
|
|
export const stepsColor = (start, end, steps, gamma) => {
|
let i
|
let j
|
let ms
|
let me
|
const output = []
|
const so = []
|
gamma = gamma || 1
|
const normalize = function (channel) {
|
return Math.pow(channel / 255, gamma)
|
}
|
start = parseColor(start).map(normalize)
|
end = parseColor(end).map(normalize)
|
for (i = 0; i < steps; i++) {
|
ms = steps - 1 === 0 ? 0 : i / (steps - 1)
|
me = 1 - ms
|
for (j = 0; j < 3; j++) {
|
so[j] = pad(Math.round(Math.pow(start[j] * me + end[j] * ms, 1 / gamma) * 255).toString(16))
|
}
|
output.push('#' + so.join(''))
|
}
|
function parseColor(hexStr) {
|
return hexStr.length === 4
|
? hexStr
|
.substr(1)
|
.split('')
|
.map(function (s) {
|
return 0x11 * parseInt(s, 16)
|
})
|
: [hexStr.substr(1, 2), hexStr.substr(3, 2), hexStr.substr(5, 2)].map(function (s) {
|
return parseInt(s, 16)
|
})
|
}
|
function pad(s) {
|
return s.length === 1 ? '0' + s : s
|
}
|
return output
|
}
|
|
export const getMapColorCases = colorCases => {
|
const cloneColorCases = JSON.parse(JSON.stringify(colorCases))
|
return cloneColorCases.map(colorItem => {
|
let curColors = colorItem.colors
|
if (['fresh', 'red', 'spiritual'].includes(colorItem.value)) {
|
curColors = colorItem.colors.reverse()
|
}
|
const len = curColors.length
|
const start = curColors[0]
|
const end = curColors[len - 1]
|
const itemResult = {
|
name: colorItem.name,
|
value: colorItem.value + '_split_gradient',
|
baseColors: [start, end],
|
colors: stepsColor(start, end, 9, 1)
|
}
|
return itemResult
|
})
|
}
|
|
export function getColor(chart: Chart) {
|
const basicStyle = parseJson(chart.customAttr).basicStyle
|
const { seriesColor } = basicStyle
|
if (seriesColor?.length) {
|
const { yAxis } = chart
|
const seriesMap = seriesColor.reduce((p, n) => {
|
p[n.id] = n
|
return p
|
}, {})
|
yAxis?.forEach((axis, index) => {
|
const curAxisColor = seriesMap[axis.id]
|
if (curAxisColor) {
|
if (index + 1 > basicStyle.colors.length) {
|
basicStyle.colors.push(curAxisColor.color)
|
} else {
|
basicStyle.colors[index] = curAxisColor.color
|
}
|
}
|
})
|
const color = basicStyle.colors.map(c => hexColorToRGBA(c, basicStyle.alpha))
|
return color
|
}
|
}
|
|
export function setupSeriesColor(chart: ChartObj): ChartBasicStyle['seriesColor'] {
|
const result: ChartBasicStyle['seriesColor'] = []
|
const seriesSet = new Set<string>()
|
const colors = chart.customAttr.basicStyle.colors
|
const yAxis = chart.yAxis
|
yAxis?.forEach(axis => {
|
if (seriesSet.has(axis.id)) {
|
return
|
}
|
seriesSet.add(axis.id)
|
result.push({
|
id: axis.id,
|
name: axis.chartShowName ?? axis.name,
|
color: colors[(seriesSet.size - 1) % colors.length]
|
})
|
})
|
return result
|
}
|
|
export function getGroupColor<O extends PickOptions = Options>(chart: Chart, options: O) {
|
const { basicStyle } = parseJson(chart.customAttr)
|
const { seriesColor } = basicStyle
|
if (!seriesColor?.length) {
|
return
|
}
|
const seriesMap = seriesColor.reduce((p, n) => {
|
p[n.id] = n
|
return p
|
}, {})
|
const { yAxis, xAxisExt } = chart
|
const { data } = options as unknown as Options
|
if (xAxisExt?.length) {
|
const seriesSet = new Set()
|
data?.forEach(d => d.category !== null && seriesSet.add(d.category))
|
const tmp = [...seriesSet]
|
tmp.forEach((c, i) => {
|
const curAxisColor = seriesMap[c as string]
|
if (curAxisColor) {
|
if (i + 1 > basicStyle.colors.length) {
|
basicStyle.colors.push(curAxisColor.color)
|
} else {
|
basicStyle.colors[i] = curAxisColor.color
|
}
|
}
|
})
|
} else {
|
yAxis?.forEach((axis, index) => {
|
const curAxisColor = seriesMap[axis.id]
|
if (curAxisColor) {
|
if (index + 1 > basicStyle.colors.length) {
|
basicStyle.colors.push(curAxisColor.color)
|
} else {
|
basicStyle.colors[index] = curAxisColor.color
|
}
|
}
|
})
|
}
|
const color = basicStyle.colors.map(c => hexColorToRGBA(c, basicStyle.alpha))
|
return color
|
}
|
|
export function setUpGroupSeriesColor(
|
chart: ChartObj,
|
data?: any[]
|
): ChartBasicStyle['seriesColor'] {
|
const result: ChartBasicStyle['seriesColor'] = []
|
const seriesSet = new Set<string>()
|
const colors = chart.customAttr.basicStyle.colors
|
const { yAxis, xAxisExt } = chart
|
if (xAxisExt?.length) {
|
data?.forEach(d => {
|
if (d.value === null || d.category === null || seriesSet.has(d.category)) {
|
return
|
}
|
seriesSet.add(d.category)
|
result.push({
|
id: d.category,
|
name: d.category,
|
color: colors[(seriesSet.size - 1) % colors.length]
|
})
|
})
|
} else {
|
yAxis?.forEach(axis => {
|
if (seriesSet.has(axis.id)) {
|
return
|
}
|
seriesSet.add(axis.id)
|
result.push({
|
id: axis.id,
|
name: axis.chartShowName ?? axis.name,
|
color: colors[(seriesSet.size - 1) % colors.length]
|
})
|
})
|
}
|
return result
|
}
|
|
export function getStackColor<O extends PickOptions = Options>(chart: Chart, options: O) {
|
const { basicStyle } = parseJson(chart.customAttr)
|
const { seriesColor } = basicStyle
|
if (!seriesColor?.length) {
|
return
|
}
|
const seriesMap = seriesColor.reduce((p, n) => {
|
p[n.id] = n
|
return p
|
}, {})
|
const { yAxis, extStack } = chart
|
const { data } = options as unknown as Options
|
if (extStack?.length) {
|
const seriesSet = new Set()
|
data?.forEach(d => d.category !== null && seriesSet.add(d.category))
|
const tmp = [...seriesSet]
|
const values = options.meta?.category?.values
|
if (values?.length) {
|
tmp.sort((a, b) => values.indexOf(a) - values.indexOf(b))
|
}
|
tmp.forEach((c, i) => {
|
const curAxisColor = seriesMap[c as string]
|
if (curAxisColor) {
|
if (i + 1 > basicStyle.colors.length) {
|
basicStyle.colors.push(curAxisColor.color)
|
} else {
|
basicStyle.colors[i] = curAxisColor.color
|
}
|
}
|
})
|
} else {
|
yAxis?.forEach((axis, index) => {
|
const curAxisColor = seriesMap[axis.id]
|
if (curAxisColor) {
|
if (index + 1 > basicStyle.colors.length) {
|
basicStyle.colors.push(curAxisColor.color)
|
} else {
|
basicStyle.colors[index] = curAxisColor.color
|
}
|
}
|
})
|
}
|
const color = basicStyle.colors.map(c => hexColorToRGBA(c, basicStyle.alpha))
|
return color
|
}
|
|
export function setUpStackSeriesColor(
|
chart: ChartObj,
|
data?: any[]
|
): ChartBasicStyle['seriesColor'] {
|
const result: ChartBasicStyle['seriesColor'] = []
|
const seriesSet = new Set<string>()
|
const colors = chart.customAttr.basicStyle.colors
|
const { yAxis, extStack } = chart
|
if (extStack?.length) {
|
data?.forEach(d => {
|
if (d.value === null || d.category === null || seriesSet.has(d.category)) {
|
return
|
}
|
seriesSet.add(d.category)
|
})
|
const cats = [...seriesSet]
|
const stackAxis = extStack[0]
|
if (stackAxis.sort === 'custom_sort' && stackAxis.customSort?.length) {
|
cats.sort((a, b) => {
|
const aIndex = stackAxis.customSort.indexOf(a)
|
const bIndex = stackAxis.customSort.indexOf(b)
|
return aIndex - bIndex
|
})
|
}
|
cats.forEach((c, i) => {
|
result.push({
|
id: c,
|
name: c,
|
color: colors[i % colors.length]
|
})
|
})
|
} else {
|
yAxis?.forEach(axis => {
|
if (seriesSet.has(axis.id)) {
|
return
|
}
|
seriesSet.add(axis.id)
|
result.push({
|
id: axis.id,
|
name: axis.chartShowName ?? axis.name,
|
color: colors[(seriesSet.size - 1) % colors.length]
|
})
|
})
|
}
|
return result
|
}
|
|
export function getSingleDimensionColor<O extends PickOptions = Options>(chart: Chart, options: O) {
|
const { basicStyle } = parseJson(chart.customAttr)
|
const { seriesColor } = basicStyle
|
if (!seriesColor?.length) {
|
return
|
}
|
const seriesMap = seriesColor.reduce((p, n) => {
|
p[n.id] = n
|
return p
|
}, {})
|
const { xAxis, yAxis } = chart
|
const { data } = options as unknown as Options
|
if (xAxis?.length && yAxis?.length) {
|
const seriesSet = new Set()
|
data?.forEach(d => d.field !== null && seriesSet.add(d.field))
|
const tmp = [...seriesSet]
|
tmp.forEach((c, i) => {
|
const curAxisColor = seriesMap[c as string]
|
if (curAxisColor) {
|
if (i + 1 > basicStyle.colors.length) {
|
basicStyle.colors.push(curAxisColor.color)
|
} else {
|
basicStyle.colors[i] = curAxisColor.color
|
}
|
}
|
})
|
}
|
const color = basicStyle.colors.map(c => hexColorToRGBA(c, basicStyle.alpha))
|
return color
|
}
|
|
export function setUpSingleDimensionSeriesColor(
|
chart: ChartObj,
|
data?: any[]
|
): ChartBasicStyle['seriesColor'] {
|
const result: ChartBasicStyle['seriesColor'] = []
|
const seriesSet = new Set<string>()
|
const colors = chart.customAttr.basicStyle.colors
|
const { xAxis, yAxis } = chart
|
if (!(xAxis?.length && yAxis?.length)) {
|
return result
|
}
|
data?.forEach(item => {
|
if (seriesSet.has(item.field)) {
|
return
|
}
|
seriesSet.add(item.field)
|
result.push({
|
id: item.field,
|
name: item.field,
|
color: colors[(seriesSet.size - 1) % colors.length]
|
})
|
})
|
return result
|
}
|
|
export function isAlphaColor(color: string): boolean {
|
if (!color?.trim()) {
|
return false
|
}
|
if (color.startsWith('#')) {
|
return color.length === 9
|
}
|
if (color.startsWith('rgb')) {
|
return color.split(',').length === 4
|
}
|
return false
|
}
|
|
export function getColorFormAlphaColor(color: string): string {
|
if (isAlphaColor(color)) {
|
if (color.startsWith('#')) {
|
return color.slice(0, 7)
|
}
|
if (color.startsWith('rgb') || color.startsWith('RGB')) {
|
const list = color.split(',')
|
return list[0] + ',' + list[1] + ',' + list[2] + ')'
|
}
|
}
|
return color
|
}
|
|
export function isTransparent(color: string): boolean {
|
if (!color?.trim()) {
|
return true
|
}
|
if (color.startsWith('#')) {
|
const tmp = color.substring(1, color.length)
|
if (tmp.length === 3 || tmp.length === 6) {
|
return false
|
}
|
if (tmp.length === 8) {
|
return tmp.substring(6, 8) === '00'
|
}
|
}
|
if (color.startsWith('rgb')) {
|
const tmp = color.split(',')
|
if (tmp.length !== 4) {
|
return false
|
}
|
const alpha = tmp[3].substring(0, tmp[3].length - 1)
|
return alpha.trim() === '0'
|
}
|
return false
|
}
|
|
export function convertToAlphaColor(color: string, alpha: number): string {
|
if (!color?.trim()) {
|
return 'rgba(255,255,255,1)'
|
}
|
if (color.startsWith('#')) {
|
let colorStr = color.trim().substring(1)
|
if (colorStr.length === 3) {
|
const tmp = colorStr.split('')
|
colorStr = `${tmp[0]}${tmp[0]}${tmp[1]}${tmp[1]}${tmp[2]}${tmp[2]}`
|
}
|
if (colorStr.length !== 6) {
|
return '#FFFFFFFF'
|
}
|
const alphaHex = parseInt((alpha * 2.55).toFixed(0))
|
.toString(16)
|
.toUpperCase()
|
return `#${colorStr}${alphaHex}`
|
}
|
if (color.startsWith('rgb')) {
|
const rgb = color.match(/\d+/g)
|
return `rgba(${rgb.join(',')},${alpha / 100})`
|
}
|
return 'rgba(255,255,255,1)'
|
}
|
|
export function svgStrToUrl(svgStr: string): string {
|
let file = ''
|
try {
|
if (svgStr) {
|
const blob = new Blob([svgStr], { type: 'image/svg+xml' })
|
file = URL.createObjectURL(blob)
|
}
|
} catch (e) {}
|
return file
|
}
|
|
/**
|
* 获取非空数据的最小值
|
* @param sourceData
|
* @param field
|
* @private
|
*/
|
export function filterEmptyMinValue(sourceData, field) {
|
let notEmptyMinValue = 0
|
getMaxAndMinValueByData(
|
sourceData.filter(item => item[field]),
|
'value',
|
0,
|
0,
|
(max, min) => {
|
notEmptyMinValue = min
|
}
|
)
|
return notEmptyMinValue
|
}
|
|
/**
|
* 获取折线条件样式
|
* @param chart
|
*/
|
export function getLineConditions(chart) {
|
const { threshold } = parseJson(chart.senior)
|
const conditions = []
|
if (threshold.enable) {
|
threshold.lineThreshold?.forEach(item =>
|
item.conditions?.forEach(c =>
|
conditions.push({
|
fieldId: item.fieldId,
|
term: c.term,
|
value: c.value,
|
color: c.color,
|
min: c.min,
|
max: c.max
|
})
|
)
|
)
|
}
|
return conditions
|
}
|
|
/**
|
* 根据折线阈值条件获取新的标签颜色
|
* @param conditions
|
* @param value
|
* @param fieldId
|
*/
|
export function getLineLabelColorByCondition(conditions, value, fieldId) {
|
const fieldConditions = conditions.filter(item => item.fieldId === fieldId)
|
let color = undefined
|
if (fieldConditions.length) {
|
fieldConditions.some(item => {
|
if (
|
(item.term === 'lt' && value < item.value) ||
|
(item.term === 'le' && value <= item.value) ||
|
(item.term === 'gt' && value > item.value) ||
|
(item.term === 'ge' && value >= item.value) ||
|
(item.term === 'between' && value >= item.min && value <= item.max)
|
) {
|
color = item.color
|
return true
|
}
|
})
|
}
|
return color
|
}
|
|
/**
|
* 获取文本在画布中的测量信息
|
* @param chart 图表内容
|
* @param text 测量文本
|
* @param font 文本样式
|
* @param type 测量类型,高度宽度
|
**/
|
export const measureText = (chart, text, font, type) => {
|
const container = document.getElementById(chart.container)
|
const canvas = container.querySelector('canvas')
|
const ctx = canvas.getContext('2d')
|
const { fontWeight, fontSize, fontFamily } = font
|
ctx.font = [fontWeight, `${fontSize}px`, fontFamily].join(' ').trim()
|
const textMetrics = ctx.measureText(text)
|
if (type === 'height') {
|
return textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent
|
}
|
if (type === 'width') {
|
return textMetrics.actualBoundingBoxRight + textMetrics.actualBoundingBoxLeft
|
}
|
return 0
|
}
|
|
/**
|
* 获取十六进制颜色值
|
* @param hex
|
* @param alpha
|
*/
|
export const hexToRgba = (hex, alpha = 1) => {
|
if (!hex.startsWith('#')) {
|
return hex
|
}
|
// 去掉 # 号
|
hex = hex.replace('#', '')
|
// 转换为 RGB 分量
|
const r = parseInt(hex.slice(0, 2), 16)
|
const g = parseInt(hex.slice(2, 4), 16)
|
const b = parseInt(hex.slice(4, 6), 16)
|
const hexAlpha = hex.slice(6, 8)
|
const a = hexAlpha ? parseInt(hex.slice(6, 8), 16) / 255 : alpha
|
// 返回 RGBA 格式
|
return `rgba(${r}, ${g}, ${b}, ${a})`
|
}
|
|
// 安全计算数值字段的总和,使用 Decimal 避免浮点数精度问题
|
export function safeDecimalSum(data, field) {
|
// 使用 reduce 累加所有行的指定字段值
|
return data
|
.reduce((acc, row) => {
|
// 将字段值转换为 Decimal 类型并累加到累加器
|
return acc.plus(new Decimal(row[field] ?? 0))
|
}, new Decimal(0))
|
.toNumber() // 最终结果转换为普通数字返回
|
}
|
|
// 安全计算数值字段的平均值,使用 Decimal 避免浮点数精度问题
|
export function safeDecimalMean(data, field) {
|
// 如果数据为空,直接返回 0
|
if (!data.length) return 0
|
// 计算所有行的指定字段值的总和
|
const sum = data.reduce((acc, row) => {
|
// 将字段值转换为 Decimal 类型并累加到累加器
|
return acc.plus(new Decimal(row[field] ?? 0))
|
}, new Decimal(0))
|
// 将总和除以数据行数,得到平均值,并转换为普通数字返回
|
return sum.dividedBy(data.length).toNumber()
|
}
|