Skip to content

Commit

Permalink
fix(QB-23021): make ARGB for cell data work
Browse files Browse the repository at this point in the history
  • Loading branch information
Johan Lohmander committed Apr 23, 2024
1 parent e2f7170 commit 2817235
Show file tree
Hide file tree
Showing 75 changed files with 8,582 additions and 12 deletions.
3 changes: 2 additions & 1 deletion example/TableApp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"lint": "eslint ."
},
"dependencies": {
"@nebula.js/sn-table": "file:../../prebuilt/nebula.js-react-native-sn-table-1.14.121.tgz",
"@nebula.js/sn-table": "file:../../packages/react-native-sn-table",
"@nebula.js/stardust": "2.9.0",
"@qlik/carbon-core": "2.0.4",
"@qlik/react-native-carbon": "2.1.18",
Expand Down Expand Up @@ -59,6 +59,7 @@
"babel-jest": "26.6.3",
"eslint": "7.32.0",
"jest": "26.6.3",
"jest-environment-jsdom": "^29.7.0",
"metro-react-native-babel-preset": "0.67.0",
"react-test-renderer": "17.0.2",
"typescript": "4.7.3"
Expand Down
372 changes: 363 additions & 9 deletions example/TableApp/yarn.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"eslint-config-prettier": "8.8.0",
"eslint-plugin-prettier": "4.2.1",
"jest": "29.5.0",
"jest-environment-jsdom": "^29.7.0",
"jotai": "1.13.1",
"prettier": "2.8.7",
"react": "18.2.0",
Expand Down
254 changes: 254 additions & 0 deletions packages/react-native-sn-table/conditional-colors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/* eslint no-bitwise: ["error", { "allow": ["&", ">>", "<<", "|"] }] */
import {TableCell} from './types';

export type PaletteColor = {
color: string;
icon: string;
index: number;
};

export type Limit = {
value: number;
gradient: boolean;
normal: number;
};

export type Segments = {
limits: Array<Limit>;
paletteColors: Array<PaletteColor>;
};

export type ConditionalColoring = {
segments: Segments;
};

export type Indicator = {
applySegmentColors: boolean;
position: string;
showTextValues: boolean;
};

export type Respresentaton = {
indicator: Indicator;
};

export type Column = {
conditionalColoring: ConditionalColoring;
representation: Respresentaton;
qMin?: number;
qMax?: number;
};

export type LimitBound = {
upperIndex: number;
lowerIndex: number;
};

export type ConditionalColoringMeasureInfo = EngineAPI.INxMeasureInfo & {
conditionalColoring?: ConditionalColoring;
};

function interpolate(a: number, b: number, amount: number) {
return a + (b - a) * amount;
}

export function lerp(a: string, b: string, amount: number) {
if (!a.startsWith('#') || !b.startsWith('#')) {
return 'none';
}
const from = parseInt(a.slice(1, 7), 16);
const to = parseInt(b.slice(1, 7), 16);
const ar = (from & 0xff0000) >> 16;
const ag = (from & 0x00ff00) >> 8;
const ab = from & 0x0000ff;
const br = (to & 0xff0000) >> 16;
const bg = (to & 0x00ff00) >> 8;
const bb = to & 0x0000ff;
const rr = interpolate(ar, br, amount);
const rg = interpolate(ag, bg, amount);
const rb = interpolate(ab, bb, amount);

// 6 because it's a hex
return `#${((rr << 16) + (rg << 8) + (rb | 0))
.toString(16)
.padStart(6, '0')
.slice(-6)}`;
}

function getAutoMinMax(lmts: Array<Limit>, val: number) {
let limits = lmts.map((l: Limit) => l.value);

let range;
let delta = 0;

limits = limits.sort((a, b) => a - b);
const nanLimits = limits.some((l) => Number.isNaN(+l));

const value = !Number.isNaN(+val)
? val
: !Number.isNaN(+limits[0])
? limits[0]
: 0.5;

if (limits.length === 0 || nanLimits) {
if (value === 0) {
delta = 0;
} else {
delta = value * 0.5;
}
} else if (limits.length === 1) {
range = limits[0] - value;

if (range === 0 || Number.isNaN(+range)) {
if (value === 0) {
delta = 0.5;
} else {
delta = Math.abs(value * 0.5);
}
} else if (range > 0) {
// limit on right side of value
return {
min: value - Math.abs(value) * 0.5,
max: limits[0] + 2 * range,
};
} else {
return {
min: limits[0] + 2 * range,
max: value + Math.abs(value) * 0.5,
};
}
} else {
// 2 or more limits
range = limits[limits.length - 1] - limits[0];
range = range > 0 ? range : 1;
return {
min: Math.min(value, limits[0]) - range / 2,
max: Math.max(value, limits[limits.length - 1]) + range / 2,
};
}

return {
min: value - delta,
max: value + delta,
};
}

function shouldBlend(segmentInfo: Segments, colorIndex: number) {
if (segmentInfo.limits.length === 0) {
return false;
}

let returnBoolean;

if (colorIndex === 0) {
// first color
returnBoolean = segmentInfo.limits[colorIndex].gradient;
} else if (colorIndex === segmentInfo.limits.length) {
returnBoolean = segmentInfo.limits[colorIndex - 1].gradient;
} else {
returnBoolean =
segmentInfo.limits[colorIndex].gradient ||
segmentInfo.limits[colorIndex - 1].gradient;
}

return returnBoolean;
}

export function getConditionalColor(
paletteIndex: number,
value: number,
column: Column,
): PaletteColor {
if (!shouldBlend(column.conditionalColoring.segments, paletteIndex)) {
return {
...column.conditionalColoring.segments.paletteColors[paletteIndex],
...column.representation.indicator,
};
}

const {paletteColors} = column.conditionalColoring.segments;
const {limits} = column.conditionalColoring.segments;
const {min, max} = getAutoMinMax(limits, value);
const segmentLimits = limits.map((l) => {
const limitNormal = max === min ? 0.5 : (l.value - min) / (max - min);
return {...l, normal: limitNormal};
});
const mag = max - min;
let t = 1.0;
let color = 'none';

const normal = (value - min) / mag;
const lowerLimit =
paletteIndex > 0
? segmentLimits[paletteIndex - 1]
: {normal: 0, value: 0, gradient: false};
const upperLimit = segmentLimits[paletteIndex]
? segmentLimits[paletteIndex]
: {normal: 1, value: 0, gradient: false};
const prevColor = paletteColors[paletteIndex - (paletteIndex > 0 ? 1 : 0)];
const nextColor =
paletteColors[
paletteIndex + (paletteIndex < paletteColors.length - 1 ? 1 : 0)
];
const normValueInLimits =
(normal - lowerLimit.normal) / (upperLimit.normal - lowerLimit.normal);
if (lowerLimit && lowerLimit.gradient && upperLimit && upperLimit.gradient) {
// triple gradient
if (normal - lowerLimit.value < upperLimit.value - normal) {
// closer to the lower limit
color = prevColor.color;
t = 0.5 - normValueInLimits;
} else {
color = nextColor.color;
t = normValueInLimits - 0.5;
}
} else if (lowerLimit && lowerLimit.gradient) {
color = prevColor.color;
t = 1 - (0.5 + normValueInLimits / 2);
} else if (upperLimit && upperLimit.gradient) {
color = nextColor.color;
t = normValueInLimits / 2;
} else {
t = normal;
}

return {
...column.representation.indicator,
...paletteColors[paletteIndex],
color: lerp(paletteColors[paletteIndex].color, color, t),
};
}

export function getIndicator(
column: Column,
tableCell: TableCell,
): PaletteColor | undefined {
if (
column.conditionalColoring &&
tableCell.qNum !== undefined &&
column.conditionalColoring.segments.limits.length > 0
) {
let index = 0;
const cl = column.conditionalColoring.segments.limits.length;

while (
index < cl &&
tableCell.qNum > column.conditionalColoring.segments.limits[index].value
) {
index++;
}
const idc = getConditionalColor(index, tableCell.qNum, column);
return idc;
}
if (
column.conditionalColoring &&
tableCell.qNum &&
column.conditionalColoring.segments.paletteColors.length === 1
) {
return {
...column.conditionalColoring.segments.paletteColors[0],
...column.representation.indicator,
};
}
return undefined;
}
15 changes: 15 additions & 0 deletions packages/react-native-sn-table/data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default () => ({
targets: [
{
path: '/qHyperCubeDef',
dimensions: {
min: 0,
max: 1000,
},
measures: {
min: 0,
max: 1000,
},
},
],
});
Loading

0 comments on commit 2817235

Please sign in to comment.