From 135909f814e989c2314ddbb5da90e5364cd36d17 Mon Sep 17 00:00:00 2001 From: Evan Rusackas Date: Tue, 20 Sep 2022 06:36:36 -0700 Subject: [PATCH] feat: Making bar graphs in Table viz from fixed-size divs instead of calculated gradients (#21482) --- .../plugins/plugin-chart-table/package.json | 1 + .../plugin-chart-table/src/TableChart.tsx | 103 +++++++++++++----- 2 files changed, 77 insertions(+), 27 deletions(-) diff --git a/superset-frontend/plugins/plugin-chart-table/package.json b/superset-frontend/plugins/plugin-chart-table/package.json index 0577c731a6ba..be035aa599b7 100644 --- a/superset-frontend/plugins/plugin-chart-table/package.json +++ b/superset-frontend/plugins/plugin-chart-table/package.json @@ -39,6 +39,7 @@ }, "peerDependencies": { "@types/react": "*", + "@types/classnames": "*", "@superset-ui/chart-controls": "*", "@superset-ui/core": "*", "react": "^16.13.1", diff --git a/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx b/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx index 98c19f20ba63..24791013fbbe 100644 --- a/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx +++ b/superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx @@ -32,6 +32,7 @@ import { extent as d3Extent, max as d3Max } from 'd3-array'; import { FaSort } from '@react-icons/all-files/fa/FaSort'; import { FaSortDown as FaSortDesc } from '@react-icons/all-files/fa/FaSortDown'; import { FaSortUp as FaSortAsc } from '@react-icons/all-files/fa/FaSortUp'; +import cx from 'classnames'; import { DataRecord, DataRecordValue, @@ -41,6 +42,7 @@ import { getTimeFormatterForGranularity, QueryObjectFilterClause, styled, + css, t, tn, } from '@superset-ui/core'; @@ -80,44 +82,64 @@ function getSortTypeByDataType(dataType: GenericDataType): DefaultSortTypes { } /** - * Cell background to render columns as horizontal bar chart + * Cell background width calculation for horizontal bar chart */ -function cellBar({ +function cellWidth({ value, valueRange, - colorPositiveNegative = false, alignPositiveNegative, }: { value: number; valueRange: ValueRange; - colorPositiveNegative: boolean; alignPositiveNegative: boolean; }) { const [minValue, maxValue] = valueRange; - const r = colorPositiveNegative && value < 0 ? 150 : 0; if (alignPositiveNegative) { const perc = Math.abs(Math.round((value / maxValue) * 100)); - // The 0.01 to 0.001 is a workaround for what appears to be a - // CSS rendering bug on flat, transparent colors - return ( - `linear-gradient(to right, rgba(${r},0,0,0.2), rgba(${r},0,0,0.2) ${perc}%, ` + - `rgba(0,0,0,0.01) ${perc}%, rgba(0,0,0,0.001) 100%)` - ); + return perc; } const posExtent = Math.abs(Math.max(maxValue, 0)); const negExtent = Math.abs(Math.min(minValue, 0)); const tot = posExtent + negExtent; - const perc1 = Math.round( - (Math.min(negExtent + value, negExtent) / tot) * 100, - ); const perc2 = Math.round((Math.abs(value) / tot) * 100); - // The 0.01 to 0.001 is a workaround for what appears to be a - // CSS rendering bug on flat, transparent colors - return ( - `linear-gradient(to right, rgba(0,0,0,0.01), rgba(0,0,0,0.001) ${perc1}%, ` + - `rgba(${r},0,0,0.2) ${perc1}%, rgba(${r},0,0,0.2) ${perc1 + perc2}%, ` + - `rgba(0,0,0,0.01) ${perc1 + perc2}%, rgba(0,0,0,0.001) 100%)` - ); + return perc2; +} + +/** + * Cell left margin (offset) calculation for horizontal bar chart elements + * when alignPositiveNegative is not set + */ +function cellOffset({ + value, + valueRange, + alignPositiveNegative, +}: { + value: number; + valueRange: ValueRange; + alignPositiveNegative: boolean; +}) { + if (alignPositiveNegative) { + return 0; + } + const [minValue, maxValue] = valueRange; + const posExtent = Math.abs(Math.max(maxValue, 0)); + const negExtent = Math.abs(Math.min(minValue, 0)); + const tot = posExtent + negExtent; + return Math.round((Math.min(negExtent + value, negExtent) / tot) * 100); +} + +/** + * Cell background color calculation for horizontal bar chart + */ +function cellBackground({ + value, + colorPositiveNegative = false, +}: { + value: number; + colorPositiveNegative: boolean; +}) { + const r = colorPositiveNegative && value < 0 ? 150 : 0; + return `rgba(${r},0,0,0.2)`; } function SortIcon({ column }: { column: ColumnInstance }) { @@ -404,16 +426,33 @@ export default function TableChart( const StyledCell = styled.td` text-align: ${sharedStyle.textAlign}; - background: ${backgroundColor || - (valueRange - ? cellBar({ + white-space: ${value instanceof Date ? 'nowrap' : undefined}; + position: relative; + background: ${backgroundColor || undefined}; + `; + + const cellBarStyles = css` + position: absolute; + height: 100%; + display: block; + top: 0; + ${valueRange && + ` + width: ${`${cellWidth({ value: value as number, valueRange, alignPositiveNegative, + })}%`}; + left: ${`${cellOffset({ + value: value as number, + valueRange, + alignPositiveNegative, + })}%`}; + background-color: ${cellBackground({ + value: value as number, colorPositiveNegative, - }) - : undefined)}; - white-space: ${value instanceof Date ? 'nowrap' : undefined}; + })}; + `} `; const cellProps = { @@ -449,6 +488,16 @@ export default function TableChart( // render `Cell`. This saves some time for large tables. return ( + {valueRange && ( +
+ )} {truncateLongCells ? (