@chart-plugins/superset-indicator-chart
Version:
Indicator chart plugin for Apache Superset
344 lines (319 loc) • 12.7 kB
JavaScript
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
// @ts-ignore
import { getNumberFormatter } from '@superset-ui/core';
// @ts-ignore
import { sandboxedEval } from './utils';
// @ts-ignore
import { getColorFormatters } from '@superset-ui/chart-controls';
// Дефолтный цвет на случай ошибок
const DEFAULT_COLOR = {
r: 11,
g: 218,
b: 81,
a: 1
}; // Зеленый цвет #0BDA51
function insertMetricsIntoMarkdown(markdownTemplate, dataRecord, numberFormat, ignore = []) {
const numberFormatter = getNumberFormatter(numberFormat);
let markdown = markdownTemplate.slice(0);
const regexp = /({{(.*?)}})/g;
try {
// Fix for ES2020 feature matchAll
const matches = [];
let match;
while ((match = regexp.exec(markdown)) !== null) {
matches.push(match);
}
for (const _match of matches) {
const metricLabel = _match[2].trim();
let metric = dataRecord[metricLabel];
if (metric !== undefined) {
// Fix for ES2016 feature includes
if (typeof metric === 'number' && ignore.indexOf(metricLabel) === -1) {
metric = numberFormatter(metric);
}
markdown = markdown.replace(_match[1], String(metric));
}
}
} catch (error) {
console.error('Error processing markdown template:', error);
}
return markdown;
}
/**
* Функция для преобразования hex цвета в формат ColorObject
*/
function hexToRgba(hex) {
try {
// Убираем # если есть
hex = hex.replace(/^#/, '');
// Проверяем формат hex
if (!/^([0-9A-F]{3}){1,2}$/i.test(hex)) {
console.error('Invalid HEX color format:', hex);
return DEFAULT_COLOR;
}
// Преобразуем в RGB
let r, g, b;
if (hex.length === 3) {
r = parseInt(hex.charAt(0) + hex.charAt(0), 16);
g = parseInt(hex.charAt(1) + hex.charAt(1), 16);
b = parseInt(hex.charAt(2) + hex.charAt(2), 16);
} else {
r = parseInt(hex.substring(0, 2), 16);
g = parseInt(hex.substring(2, 4), 16);
b = parseInt(hex.substring(4, 6), 16);
}
// Проверка на корректность значений
r = isNaN(r) ? 0 : r;
g = isNaN(g) ? 0 : g;
b = isNaN(b) ? 0 : b;
return {
r,
g,
b,
a: 1
};
} catch (e) {
console.error('Error converting hex to rgba:', e);
return DEFAULT_COLOR;
}
}
/**
* Функция для создания форматтера цвета на основе JavaScript кода
*/
function getJsColorFormatters(dataColorMapper) {
if (!dataColorMapper) {
return [];
}
// Создаем ColorFormatters объект с функцией для JS-форматирования
return [{
column: 'js_formatter',
// Используем фиктивное имя колонки
// Помечаем этот форматтер специальным флагом, чтобы отличать от стандартных
isJsFormatter: true,
getColorFromValue: dataRecord => {
try {
// Если нет функции маппинга, возвращаем дефолтный цвет
if (!dataColorMapper) {
return DEFAULT_COLOR;
}
// Проверка типа dataRecord
if (typeof dataRecord === 'number') {
// Если передано просто число, создаем простой объект с этим значением
dataRecord = {
value: dataRecord
};
} else if (!dataRecord || typeof dataRecord !== 'object') {
return DEFAULT_COLOR;
}
// Используем нашу упрощенную функцию для выполнения JS кода
const jsFnMutator = sandboxedEval(dataColorMapper);
const colorString = jsFnMutator(dataRecord);
// Проверяем, что результат является строкой и содержит HEX-код цвета
if (typeof colorString === 'string') {
// Fix for ES2015 feature startsWith
if (colorString.indexOf('#') === 0) {
return hexToRgba(colorString);
} else if (colorString.match(/^rgb/i)) {
// Извлекаем RGB значения из строки вида rgb(r,g,b) или rgba(r,g,b,a)
const matches = colorString.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/i);
if (matches && matches.length >= 4) {
return {
r: parseInt(matches[1], 10) || 0,
g: parseInt(matches[2], 10) || 0,
b: parseInt(matches[3], 10) || 0,
a: parseFloat(matches[4] || '1')
};
}
}
}
console.error('JS function did not return a valid color format:', colorString);
return DEFAULT_COLOR;
} catch (error) {
console.error('Error evaluating color mapper:', error);
return DEFAULT_COLOR;
}
}
}];
}
/**
* Создаем унифицированный адаптер для применения форматтеров обоих типов
* к данным записи
*/
function applyColorFormatter(formatter, record) {
try {
// Проверяем, является ли форматтер JS-форматтером или стандартным
let result;
if (formatter.isJsFormatter) {
// JS-форматтер ожидает полную запись и возвращает ColorObject
result = formatter.getColorFromValue(record);
} else {
// Стандартный форматтер ожидает значение из конкретной колонки и возвращает hex цвет
const columnValue = record[formatter.column];
// Проверяем, что значение существует
if (columnValue !== undefined) {
const colorResult = formatter.getColorFromValue(columnValue);
// Если форматтер вернул строку (hex), конвертируем её в ColorObject
if (typeof colorResult === 'string') {
// Если форматтер вернул hex-цвет, конвертируем в ColorObject
return hexToRgba(colorResult);
} else {
// Иначе возвращаем как есть (на случай, если это уже ColorObject)
result = colorResult;
}
}
}
// Проверка результата
if (result && typeof result === 'object' && 'r' in result) {
return result;
} else if (typeof result === 'string') {
// На всякий случай, обрабатываем случай строки
return hexToRgba(result);
}
// Если не удалось получить цвет, возвращаем null
return null;
} catch (error) {
console.error('Error applying color formatter:', error);
return null;
}
}
/**
* Универсальная функция для получения форматтеров цвета
* Поддерживает оба метода форматирования: JS функцию и условное форматирование
*/
function getCustomColorFormatters(dataColorMapper, conditionalFormatting, data) {
// Подготавливаем массив форматтеров с использованием аналогичного паттерна
const defaultColorFormatters = [{
column: 'default_formatter',
getColorFromValue: () => DEFAULT_COLOR
}];
// При наличии JS-функции используем её в приоритете
if (dataColorMapper) {
const jsColorFormatters = getJsColorFormatters(dataColorMapper);
if (jsColorFormatters.length > 0) {
return jsColorFormatters;
}
}
// Иначе используем условное форматирование
if (conditionalFormatting) {
var _getColorFormatters;
const colorThresholdFormatters = (_getColorFormatters = getColorFormatters(conditionalFormatting, data, false)) != null ? _getColorFormatters : defaultColorFormatters;
if (colorThresholdFormatters.length > 0) {
return colorThresholdFormatters;
}
}
return defaultColorFormatters;
}
export default function transformProps(chartProps) {
try {
var _metrics$;
const {
width,
height,
formData,
queriesData
} = chartProps;
const {
groupby,
metrics,
orientation,
textColor,
numberFormat,
roundedCorners,
markdown,
conditionalFormatting,
dataColorMapper
} = formData;
// Проверяем наличие данных
if (!queriesData || !queriesData[0] || !queriesData[0].data) {
console.warn('No query data available');
return {
width,
height,
data: [],
colorThresholdFormatters: [],
textColor: textColor || 'dark',
groupby: groupby || [],
valueName: '',
orientation: orientation || 'horizontal',
roundedCorners: roundedCorners || false,
markdown: markdown || '',
columnNames: [],
numberFormat: numberFormat || 'SMART_NUMBER'
};
}
const data = queriesData[0].data;
const metricName = metrics && (_metrics$ = metrics[0]) != null && _metrics$.label ? metrics[0].label : metrics[0];
// default metric name
const defaultValueName = metricName || (data.length > 0 ? Object.keys(data[0])[0] : '');
const valueName = metrics && metrics.length ? defaultValueName : '';
// Форматирование числа
const formatter = getNumberFormatter(numberFormat || 'SMART_NUMBER');
// Подготовим данные для индикатора
const formattedData = data.map(item => {
const value = item[valueName];
let displayValue = '';
if (value !== null && value !== undefined) {
displayValue = formatter(value);
}
return _extends({}, item, {
displayValue,
value
});
});
// Получаем форматтеры цвета
const colorThresholdFormatters = getCustomColorFormatters(dataColorMapper, conditionalFormatting, data);
// Предварительно рассчитываем цвета для каждой записи
const processedData = formattedData.map(record => {
let backgroundColor = DEFAULT_COLOR;
// Для отладки
console.log('Applying formatters to record:', JSON.stringify(record));
console.log('Available formatters:', colorThresholdFormatters.length);
// Пробуем каждый форматтер по очереди
for (const formatter of colorThresholdFormatters) {
const color = applyColorFormatter(formatter, record);
if (color) {
backgroundColor = color;
console.log('Found color for record:', JSON.stringify(backgroundColor));
break; // Используем первый успешный результат
}
}
return _extends({}, record, {
backgroundColor
});
});
const markdowns = data.map(dataRecord => insertMetricsIntoMarkdown(markdown, dataRecord, numberFormat, groupby));
return {
width,
height,
data: processedData,
// Используем обработанные данные с предрасчитанными цветами
colorThresholdFormatters,
markdowns,
textColor: textColor || 'dark',
groupby: groupby || [],
valueName,
orientation: orientation || 'horizontal',
roundedCorners: roundedCorners || false,
markdown: markdown || '',
columnNames: data.length > 0 ? Object.keys(data[0]) : [],
numberFormat: numberFormat || 'SMART_NUMBER'
};
} catch (error) {
console.error('Error in transformProps:', error);
return {
width: 400,
height: 400,
data: [],
colorThresholdFormatters: [],
markdowns: [],
textColor: 'dark',
groupby: [],
valueName: '',
orientation: 'horizontal',
roundedCorners: false,
markdown: 'Error processing data',
columnNames: [],
numberFormat: 'SMART_NUMBER'
};
}
}