You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by li...@apache.org on 2024/03/27 16:28:03 UTC

(superset) 02/04: implement basic color formatter

This is an automated email from the ASF dual-hosted git repository.

lilykuang pushed a commit to branch table-time-comparison-color
in repository https://gitbox.apache.org/repos/asf/superset.git

commit c71c472b976df316cc6a4b901ec2ee9a3a83e454
Author: lilykuang <ji...@gmail.com>
AuthorDate: Mon Mar 25 20:22:14 2024 -0700

    implement basic color formatter
---
 .../plugins/plugin-chart-table/src/TableChart.tsx  | 37 +++++++++-
 .../plugin-chart-table/src/controlPanel.tsx        | 75 ++++++++++++++++++++-
 .../plugin-chart-table/src/transformProps.ts       | 78 +++++++++++++++++++++-
 .../plugins/plugin-chart-table/src/types.ts        |  6 ++
 4 files changed, 188 insertions(+), 8 deletions(-)

diff --git a/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx b/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx
index c274296ccd..ef564fdf27 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx
+++ b/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx
@@ -59,7 +59,11 @@ import {
 } from '@ant-design/icons';
 
 import { isEmpty } from 'lodash';
-import { DataColumnMeta, TableChartTransformedProps } from './types';
+import {
+  ColorSchemeEnum,
+  DataColumnMeta,
+  TableChartTransformedProps,
+} from './types';
 import DataTable, {
   DataTableProps,
   SearchInputProps,
@@ -248,6 +252,7 @@ export default function TableChart<D extends DataRecord = DataRecord>(
     onContextMenu,
     emitCrossFilters,
     enableTimeComparison,
+    basicColorFormatter,
   } = props;
   const comparisonColumns = [
     { key: 'all', label: t('Display all') },
@@ -692,7 +697,11 @@ export default function TableChart<D extends DataRecord = DataRecord>(
         Array.isArray(columnColorFormatters) &&
         columnColorFormatters.length > 0;
 
+      const hasBasicColorFormatter =
+        Array.isArray(basicColorFormatter) && basicColorFormatter.length > 0;
+
       const valueRange =
+        !hasBasicColorFormatter &&
         !hasColumnColorFormatters &&
         (config.showCellBars === undefined
           ? showCellBars
@@ -728,6 +737,17 @@ export default function TableChart<D extends DataRecord = DataRecord>(
           const html = isHtml ? { __html: text } : undefined;
 
           let backgroundColor;
+          let arrow = '';
+          const originKey = column.key.substring(column.label.length).trim();
+          if (!hasColumnColorFormatters && hasBasicColorFormatter) {
+            backgroundColor =
+              basicColorFormatter[row.index][originKey]?.backgroundColor;
+            arrow =
+              column.label === comparisonLabels[0]
+                ? basicColorFormatter[row.index][originKey]?.mainArrow
+                : '';
+          }
+
           if (hasColumnColorFormatters) {
             columnColorFormatters!
               .filter(formatter => formatter.column === column.key)
@@ -773,6 +793,15 @@ export default function TableChart<D extends DataRecord = DataRecord>(
               `}
           `;
 
+          const arrowStyles = css`
+            color: ${basicColorFormatter &&
+            basicColorFormatter[row.index][originKey]?.arrowColor ===
+              ColorSchemeEnum.Green
+              ? theme.colors.success.base
+              : theme.colors.error.base};
+            margin-right: ${theme.gridUnit}px;
+          `;
+
           const cellProps = {
             // show raw number in title in case of numeric values
             title: typeof value === 'number' ? String(value) : undefined,
@@ -838,10 +867,14 @@ export default function TableChart<D extends DataRecord = DataRecord>(
                   className="dt-truncate-cell"
                   style={columnWidth ? { width: columnWidth } : undefined}
                 >
+                  {arrow && <span css={arrowStyles}>{arrow}</span>}
                   {text}
                 </div>
               ) : (
-                text
+                <>
+                  {arrow && <span css={arrowStyles}>{arrow}</span>}
+                  {text}
+                </>
               )}
             </StyledCell>
           );
diff --git a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
index 93402978b7..6fcdcd825d 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
@@ -50,7 +50,8 @@ import {
   getStandardizedControls,
 } from '@superset-ui/chart-controls';
 
-import { PAGE_SIZE_OPTIONS } from './consts';
+import { COMPARISON_PREFIX, PAGE_SIZE_OPTIONS } from './consts';
+import { ColorSchemeEnum } from './types';
 
 function getQueryMode(controls: ControlStateMapping): QueryMode {
   const mode = controls?.query_mode?.value;
@@ -153,6 +154,33 @@ const percentMetricsControl: typeof sharedControls.metrics = {
   validators: [],
 };
 
+const processComparisonColumns = (columns: any[]) =>
+  columns
+    .map(col => {
+      if (!col.label.includes(COMPARISON_PREFIX)) {
+        return [
+          {
+            label: `${t('Main')} ${col.label}`,
+            value: `${t('Main')} ${col.value}`,
+          },
+          {
+            label: `# ${col.label}`,
+            value: `# ${col.value}`,
+          },
+          {
+            label: `△ ${col.label}`,
+            value: `△ ${col.value}`,
+          },
+          {
+            label: `% ${col.label}`,
+            value: `% ${col.value}`,
+          },
+        ];
+      }
+      return [];
+    })
+    .flat();
+
 const config: ControlPanelConfig = {
   controlPanelSections: [
     {
@@ -568,13 +596,47 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        [
+          {
+            name: 'comparison_color_enabled',
+            config: {
+              type: 'CheckboxControl',
+              label: t('basic conditional formatting for comparison '),
+              renderTrigger: true,
+              default: false,
+              description: t('Add color for positive/negative change'),
+            },
+          },
+        ],
+        [
+          {
+            name: 'comparison_color_scheme',
+            config: {
+              type: 'SelectControl',
+              label: t('color scheme for comparison'),
+              default: ColorSchemeEnum.Green,
+              renderTrigger: true,
+              choices: [
+                [ColorSchemeEnum.Green, 'Green for increase, red for decrease'],
+                [ColorSchemeEnum.Red, 'Red for increase, green for decrease'],
+              ],
+              visibility: ({ controls }) =>
+                controls?.comparison_color_enabled?.value === true,
+              description: t(
+                'Adds color to the chart symbols based on the positive or ' +
+                  'negative change from the comparison value.',
+              ),
+            },
+          },
+        ],
         [
           {
             name: 'conditional_formatting',
             config: {
               type: 'ConditionalFormattingControl',
               renderTrigger: true,
-              label: t('Conditional formatting'),
+              label: t('Custom Conditional Formatting'),
+              multi: true,
               description: t(
                 'Apply conditional color formatting to numeric columns',
               ),
@@ -602,9 +664,16 @@ const config: ControlPanelConfig = {
                           label: verboseMap[colname] ?? colname,
                         }))
                     : [];
+
+                const columnOptions = Boolean(
+                  explore?.controls?.enable_time_comparison?.value,
+                )
+                  ? processComparisonColumns(numericColumns || [])
+                  : numericColumns;
+
                 return {
                   removeIrrelevantConditions: chartStatus === 'success',
-                  columnOptions: numericColumns,
+                  columnOptions,
                   verboseMap,
                 };
               },
diff --git a/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts b/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
index 64fb198016..8a39f4b4dd 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
@@ -43,6 +43,7 @@ import {
 import isEqualColumns from './utils/isEqualColumns';
 import DateWithFormatter from './utils/DateWithFormatter';
 import {
+  ColorSchemeEnum,
   DataColumnMeta,
   TableChartProps,
   TableChartTransformedProps,
@@ -374,7 +375,74 @@ const transformProps = (
     conditional_formatting: conditionalFormatting,
     allow_rearrange_columns: allowRearrangeColumns,
     enable_time_comparison: enableTimeComparison = false,
+    comparison_color_enabled: comparisonColorEnabled = false,
+    comparison_color_scheme: comparisonColorScheme = ColorSchemeEnum.Green,
   } = formData;
+
+  const calculateColorAndArrow = (percentDifferenceNum: number) => {
+    if (percentDifferenceNum === 0) {
+      return {
+        arrow: '',
+        arrowColor: '',
+        basicBackgroundColor: 'rgba(255, 191, 161, 0.2)',
+      };
+    }
+    const isPositive = percentDifferenceNum > 0;
+    const arrow = isPositive ? '↑' : '↓';
+    const arrowColor =
+      comparisonColorScheme === ColorSchemeEnum.Green
+        ? isPositive
+          ? ColorSchemeEnum.Green
+          : ColorSchemeEnum.Red
+        : isPositive
+          ? ColorSchemeEnum.Red
+          : ColorSchemeEnum.Green;
+    const basicBackgroundColor =
+      comparisonColorScheme === ColorSchemeEnum.Green
+        ? `rgba(${isPositive ? '0,150,0' : '150,0,0'},0.2)`
+        : `rgba(${isPositive ? '150,0,0' : '0,150,0'},0.2)`;
+
+    return { arrow, arrowColor, basicBackgroundColor };
+  };
+
+  const getBasicColorFormatter = memoizeOne(
+    function processComparisonDataRecords(
+      originalData: DataRecord[] | undefined,
+      originalColumns: DataColumnMeta[],
+    ) {
+      // Transform data
+      return originalData?.map(originalItem => {
+        const item: any = {};
+        originalColumns.forEach(origCol => {
+          if (
+            (origCol.isMetric || origCol.isPercentMetric) &&
+            !origCol.key.includes(COMPARISON_PREFIX) &&
+            origCol.isNumeric
+          ) {
+            const originalValue = originalItem[origCol.key] || 0;
+            const comparisonValue = origCol.isMetric
+              ? originalItem?.[`${COMPARISON_PREFIX}${origCol.key}`] || 0
+              : originalItem[`%${COMPARISON_PREFIX}${origCol.key.slice(1)}`] ||
+                0;
+            const { percentDifferenceNum } = calculateDifferences(
+              originalValue as number,
+              comparisonValue as number,
+            );
+
+            const { arrow, arrowColor, basicBackgroundColor } =
+              calculateColorAndArrow(percentDifferenceNum);
+
+            item[`${origCol.key}`] = {};
+            item[`${origCol.key}`].mainArrow = arrow;
+            item[`${origCol.key}`].arrowColor = arrowColor;
+            item[`${origCol.key}`].backgroundColor = basicBackgroundColor;
+          }
+        });
+        return item;
+      });
+    },
+  );
+
   const canUseTimeComparison =
     enableTimeComparison &&
     isFeatureEnabled(FeatureFlag.ChartPluginsExperimental) &&
@@ -407,15 +475,18 @@ const transformProps = (
     showTotals && queryMode === QueryMode.Aggregate
       ? totalQuery?.data[0]
       : undefined;
-  const columnColorFormatters =
-    getColorFormatters(conditionalFormatting, data) ?? defaultColorFormatters;
 
   const comparisonTotals = processComparisonTotals(totals);
-
   const passedData = canUseTimeComparison ? comparisonData || [] : data;
   const passedTotals = canUseTimeComparison ? comparisonTotals : totals;
   const passedColumns = canUseTimeComparison ? comparisonColumns : columns;
 
+  const basicColorFormatter =
+    comparisonColorEnabled && getBasicColorFormatter(baseQuery?.data, columns);
+  const columnColorFormatters =
+    getColorFormatters(conditionalFormatting, passedData) ??
+    defaultColorFormatters;
+
   return {
     height,
     width,
@@ -447,6 +518,7 @@ const transformProps = (
     allowRearrangeColumns,
     onContextMenu,
     enableTimeComparison: canUseTimeComparison,
+    basicColorFormatter,
   };
 };
 
diff --git a/superset-frontend/plugins/plugin-chart-table/src/types.ts b/superset-frontend/plugins/plugin-chart-table/src/types.ts
index 1806eddb1a..f4ca8473f1 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/types.ts
+++ b/superset-frontend/plugins/plugin-chart-table/src/types.ts
@@ -137,6 +137,12 @@ export interface TableChartTransformedProps<D extends DataRecord = DataRecord> {
     filters?: ContextMenuFilters,
   ) => void;
   enableTimeComparison?: boolean;
+  basicColorFormatter?: any;
+}
+
+export enum ColorSchemeEnum {
+  'Green' = 'Green',
+  'Red' = 'Red',
 }
 
 export default {};