UNPKG

@chart-plugins/superset-indicator-chart

Version:

Indicator chart plugin for Apache Superset

344 lines (319 loc) 12.7 kB
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' }; } }