Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Making bar graphs in Table viz from fixed-size divs instead of calculated gradients #21482

Merged
merged 10 commits into from
Sep 20, 2022
1 change: 1 addition & 0 deletions superset-frontend/plugins/plugin-chart-table/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
},
"peerDependencies": {
"@types/react": "*",
"@types/classnames": "*",
"@superset-ui/chart-controls": "*",
"@superset-ui/core": "*",
"react": "^16.13.1",
Expand Down
103 changes: 76 additions & 27 deletions superset-frontend/plugins/plugin-chart-table/src/TableChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -41,6 +42,7 @@ import {
getTimeFormatterForGranularity,
QueryObjectFilterClause,
styled,
css,
t,
tn,
} from '@superset-ui/core';
Expand Down Expand Up @@ -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<D extends object>({ column }: { column: ColumnInstance<D> }) {
Expand Down Expand Up @@ -404,16 +426,33 @@ export default function TableChart<D extends DataRecord = DataRecord>(

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 &&
rusackas marked this conversation as resolved.
Show resolved Hide resolved
`
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 = {
Expand Down Expand Up @@ -449,6 +488,16 @@ export default function TableChart<D extends DataRecord = DataRecord>(
// render `Cell`. This saves some time for large tables.
return (
<StyledCell {...cellProps}>
{valueRange && (
<div
/* The following classes are added to support custom CSS styling */
className={cx(
'cell-bar',
value && value < 0 ? 'negative' : 'positive',
rusackas marked this conversation as resolved.
Show resolved Hide resolved
)}
css={cellBarStyles}
/>
)}
{truncateLongCells ? (
<div
className="dt-truncate-cell"
Expand Down