Skip to content

Conversation

@Facyla
Copy link

@Facyla Facyla commented Nov 13, 2025

SUMMARY

Add a JSON-based customisation block that allows to override linearColorScheme colors and thresholds.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

A new setting is available is "Chart Options" panel
image

TESTING INSTRUCTIONS

  • Cut sample rules in "Custom Color Scale": the overrided configuration is removed, resulting in the original linear Color Scheme to apply.
  • Edit sample values to update colors and thresholds, or add/remove thresholds: the new configuration should apply.

ADDITIONAL INFORMATION

  • Has associated issue: mentioned in Implement custom formatting similar to Table Chart into Country Map plugin #34427
  • Required feature flags:
  • Changes UI: new setting added, modal for editing
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

@bito-code-review
Copy link
Contributor

bito-code-review bot commented Nov 13, 2025

Code Review Agent Run #3fbf4f

Actionable Suggestions - 0
Review Details
  • Files reviewed - 4 · Commit Range: 58885b0..58885b0
    • superset-frontend/plugins/legacy-plugin-chart-country-map/src/CountryMap.js
    • superset-frontend/plugins/legacy-plugin-chart-country-map/src/ReactCountryMap.jsx
    • superset-frontend/plugins/legacy-plugin-chart-country-map/src/controlPanel.ts
    • superset-frontend/plugins/legacy-plugin-chart-country-map/src/transformProps.js
  • Files skipped - 0
  • Tools
    • Eslint (Linter) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Default Agent You can customize the agent settings here or contact your Bito workspace admin at [email protected].

Documentation & Help

AI Code Review powered by Bito Logo

Copy link

@korbit-ai korbit-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've completed my review and didn't find any issues.

Files scanned
File Path Reviewed
superset-frontend/plugins/legacy-plugin-chart-country-map/src/transformProps.js
superset-frontend/plugins/legacy-plugin-chart-country-map/src/ReactCountryMap.jsx
superset-frontend/plugins/legacy-plugin-chart-country-map/src/controlPanel.ts
superset-frontend/plugins/legacy-plugin-chart-country-map/src/CountryMap.js

Explore our documentation to understand the languages and file types we support and the files we ignore.

Check out our docs on how you can make Korbit work best for you and your team.

Loving Korbit!? Share us on LinkedIn Reddit and X

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds JSON-based customization capabilities to the CountryMap plugin, allowing users to override the default linear color scheme with custom colors and percentage-based thresholds. The feature is configurable through a new "Custom Color Scale" setting in the Chart Options panel.

Key Changes

  • Added a new customColorScale TextAreaControl that accepts JSON configuration for color thresholds defined by percentages
  • Implemented color priority logic: conditional rules > custom percentage scale > linear palette > gradient fallback
  • Added helper functions for color normalization, safe number parsing, and RGBA to hex conversion

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 17 comments.

File Description
transformProps.js Parses JSON configuration from form data for custom color rules and scales
controlPanel.ts Adds new TextAreaControl for custom color scale configuration with JSON syntax
ReactCountryMap.jsx Minor formatting adjustments (whitespace)
CountryMap.js Core implementation of custom color scaling logic with multiple fallback strategies

Comment on lines +30 to +37
function normalizeColorKeyword(color) {
if (!color && color !== '') return '#000000';
const c = String(color).trim().toLowerCase();
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(c)) return c;
if (/^[a-z]+$/.test(c)) return c;

return '#000000';
}
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The normalizeColorKeyword function doesn't validate or normalize other valid CSS color formats like rgb(), rgba(), hsl(), hsla(), or named colors beyond simple keywords. The function name suggests it normalizes color keywords, but the current implementation will return '#000000' for valid CSS colors that don't match the two regex patterns, which could lead to unexpected behavior when users provide valid CSS colors in their custom configuration.

Consider either:

  1. Renaming the function to reflect its limited scope (e.g., validateBasicColor)
  2. Expanding validation to handle more CSS color formats
  3. Adding documentation that only hex colors and simple lowercase color names are supported

Copilot uses AI. Check for mistakes.
Comment on lines +73 to +107
{
name: 'customColorScale',
config: {
type: 'TextAreaControl',
label: t('Custom Color Scale (by %)'),
description: t(
"Custom configuration that overrides the linear color scheme color codes and thresholds. Thresholds are defined in percentage, and color codes accept any valid CSS value. Copy-paste and edit following sample configuration to define your own thresholds and colors :\n\n" +
"[\n" +
" { \"percent\": 0, \"color\": \"#600000\" },\n" +
" { \"percent\": 0.01, \"color\": \"#A00000\" },\n" +
" { \"percent\": 20, \"color\": \"#E52B50\" },\n" +
" { \"percent\": 35, \"color\": \"#FFA500\" },\n" +
" { \"percent\": 50, \"color\": \"#FFFF99\" },\n" +
" { \"percent\": 65, \"color\": \"#9ACD32\" },\n" +
" { \"percent\": 80, \"color\": \"#3CB371\" },\n" +
" { \"percent\": 99.99, \"color\": \"#228B22\" },\n" +
" { \"percent\": 100, \"color\": \"#006400\" }\n" +
"]"
),
default: `[
{ "percent": 0, "color": "#600000" },
{ "percent": 0.01, "color": "#A00000" },
{ "percent": 20, "color": "#E52B50" },
{ "percent": 35, "color": "#FFA500" },
{ "percent": 50, "color": "#FFFF99" },
{ "percent": 65, "color": "#9ACD32" },
{ "percent": 80, "color": "#3CB371" },
{ "percent": 99.99, "color": "#228B22" },
{ "percent": 100, "color": "#006400" }
]`,
language: 'json',
rows: 10,
renderTrigger: true,
},
},
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Missing input validation for the JSON in the TextAreaControl. Users could enter malformed JSON or data that doesn't match the expected schema (objects without 'percent' or 'color' fields, incorrect types, etc.). While there is try-catch in transformProps.js, invalid data that successfully parses as JSON will silently fail or produce unexpected results.

Consider adding validation logic or using a more structured control (like a JSON editor with schema validation) to provide immediate feedback to users when they enter invalid configurations.

Copilot uses AI. Check for mistakes.
Comment on lines +266 to +272
const percentNormalized = ((value - minValue) / valueRangeNonZero) * 100;
const p = Math.max(0, Math.min(100, percentNormalized));
try {
colorMap[iso] = percentColorScale(p);
return;
} catch {
// continue regardless of error
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The percentage calculation on line 266 uses valueRangeNonZero to avoid division by zero, but the logic for handling this edge case could lead to incorrect results. When minValue === maxValue, all values will be normalized to 0% (since (value - minValue) / 1 * 100 = 0), but they should probably all map to 100% or 50% to indicate they're at the singular data point.

Consider handling this case explicitly:

if (valueRange === 0) {
  colorMap[iso] = percentColorScale(50); // or 100, depending on desired behavior
  return;
}
Suggested change
const percentNormalized = ((value - minValue) / valueRangeNonZero) * 100;
const p = Math.max(0, Math.min(100, percentNormalized));
try {
colorMap[iso] = percentColorScale(p);
return;
} catch {
// continue regardless of error
if (minValue === maxValue) {
// All values are the same; map to central color (e.g., 50%)
try {
colorMap[iso] = percentColorScale(50);
return;
} catch {
// continue regardless of error
}
} else {
const percentNormalized = ((value - minValue) / valueRangeNonZero) * 100;
const p = Math.max(0, Math.min(100, percentNormalized));
try {
colorMap[iso] = percentColorScale(p);
return;
} catch {
// continue regardless of error
}

Copilot uses AI. Check for mistakes.
customColorRules: PropTypes.array,
minColor: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
maxColor: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
customColorScale: PropTypes.oneOfType([PropTypes.array, PropTypes.string]),
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PropType for customColorScale is defined as PropTypes.oneOfType([PropTypes.array, PropTypes.string]) but based on the parsing logic in transformProps.js, it should always be an array by the time it reaches this component. The string type appears unnecessary and creates confusion about the expected data type.

Consider changing to PropTypes.array only, or add a comment explaining why it can be a string.

Suggested change
customColorScale: PropTypes.oneOfType([PropTypes.array, PropTypes.string]),
customColorScale: PropTypes.array,

Copilot uses AI. Check for mistakes.
Comment on lines +153 to +179
const getColorFromRules = value => {
if (!Array.isArray(customColorRules) || !Number.isFinite(value))
return null;
for (const rule of customColorRules) {
if (
rule &&
typeof rule.color === 'string' &&
(('min' in rule && 'max' in rule) || 'value' in rule)
) {
if ('value' in rule && Number(rule.value) === value) {
// 🆕 normalize possible keyword in conditional rules as well
return normalizeColorKeyword(rule.color);
}
if ('min' in rule && 'max' in rule) {
const minR = safeNumber(rule.min);
const maxR = safeNumber(rule.max);
if (
Number.isFinite(minR) &&
Number.isFinite(maxR) &&
value >= minR &&
value <= maxR
) {
return normalizeColorKeyword(rule.color);
}
}
}
}
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The color mapping logic iterates through customColorRules array for every data point on line 156. If there are many rules and many data points, this could lead to O(n*m) complexity. Consider optimizing by:

  1. Pre-processing rules into a more efficient data structure (e.g., sorted array for binary search, or hash map for exact value matches)
  2. Early termination when a rule matches (which is already done with return)

However, this is likely acceptable for typical use cases with small numbers of rules.

Suggested change
const getColorFromRules = value => {
if (!Array.isArray(customColorRules) || !Number.isFinite(value))
return null;
for (const rule of customColorRules) {
if (
rule &&
typeof rule.color === 'string' &&
(('min' in rule && 'max' in rule) || 'value' in rule)
) {
if ('value' in rule && Number(rule.value) === value) {
// 🆕 normalize possible keyword in conditional rules as well
return normalizeColorKeyword(rule.color);
}
if ('min' in rule && 'max' in rule) {
const minR = safeNumber(rule.min);
const maxR = safeNumber(rule.max);
if (
Number.isFinite(minR) &&
Number.isFinite(maxR) &&
value >= minR &&
value <= maxR
) {
return normalizeColorKeyword(rule.color);
}
}
}
}
// Preprocess customColorRules for efficient lookup
let valueRuleMap = {};
let rangeRules = [];
if (Array.isArray(customColorRules)) {
for (const rule of customColorRules) {
if (
rule &&
typeof rule.color === 'string' &&
(('min' in rule && 'max' in rule) || 'value' in rule)
) {
if ('value' in rule && Number.isFinite(Number(rule.value))) {
valueRuleMap[Number(rule.value)] = normalizeColorKeyword(rule.color);
} else if ('min' in rule && 'max' in rule) {
const minR = safeNumber(rule.min);
const maxR = safeNumber(rule.max);
if (Number.isFinite(minR) && Number.isFinite(maxR)) {
rangeRules.push({
min: minR,
max: maxR,
color: normalizeColorKeyword(rule.color),
});
}
}
}
}
// Sort rangeRules by min for possible future optimizations
rangeRules.sort((a, b) => a.min - b.min);
}
const getColorFromRules = value => {
if (!Number.isFinite(value)) return null;
// Check for exact value match first
if (Object.prototype.hasOwnProperty.call(valueRuleMap, value)) {
return valueRuleMap[value];
}
// Check range rules
for (const rule of rangeRules) {
if (value >= rule.min && value <= rule.max) {
return rule.color;
}
}

Copilot uses AI. Check for mistakes.
try {
parsedColorScale = customColorScale ? JSON.parse(customColorScale) : [];
} catch (error) {
console.warn('Invalid JSON in customColorScale:', error);
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The console.warn messages for JSON parsing errors will only show generic error messages. This makes it difficult for users to debug their JSON configuration issues. Consider providing more specific error messages that include what went wrong and where.

Example:

console.warn('Invalid JSON in customColorScale. Please check your configuration syntax:', error.message);
Suggested change
console.warn('Invalid JSON in customColorScale:', error);
console.warn(
'Invalid JSON in customColorScale. Please check your configuration syntax. Error:',
error && error.message ? error.message : error,
'Input:',
customColorScale,
);

Copilot uses AI. Check for mistakes.
}

/** -------------------------
* 3) Linear palette from registry (SI défini)
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in French comment: "SI défini" should be "if defined" in English to maintain consistency with the rest of the codebase.

Suggested change
* 3) Linear palette from registry (SI défini)
* 3) Linear palette from registry (if defined)

Copilot uses AI. Check for mistakes.
if (!color && color !== '') return '#000000';
const c = String(color).trim().toLowerCase();
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(c)) return c;
if (/^[a-z]+$/.test(c)) return c;
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern /^[a-z]+$/ only matches lowercase color keywords, but CSS color keywords are case-insensitive and could be provided in uppercase or mixed case (e.g., "Red", "BLUE"). This will cause valid CSS color names to fall through to the default '#000000'.

Consider making the pattern case-insensitive: /^[a-z]+$/i or converting the color to lowercase before testing: if (/^[a-z]+$/.test(c.toLowerCase()))

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +43
console.warn('Invalid JSON in customColorRules:', error);
}

try {
parsedColorScale = customColorScale ? JSON.parse(customColorScale) : [];
} catch (error) {
console.warn('Invalid JSON in customColorScale:', error);
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The console.warn messages for JSON parsing errors will only show generic error messages. This makes it difficult for users to debug their JSON configuration issues. Consider providing more specific error messages that include what went wrong and where.

Example:

console.warn('Invalid JSON in customColorRules. Please check your configuration syntax:', error.message);
Suggested change
console.warn('Invalid JSON in customColorRules:', error);
}
try {
parsedColorScale = customColorScale ? JSON.parse(customColorScale) : [];
} catch (error) {
console.warn('Invalid JSON in customColorScale:', error);
console.warn(
'Invalid JSON in customColorRules. Please check your configuration syntax:',
error && error.message ? error.message : error
);
}
try {
parsedColorScale = customColorScale ? JSON.parse(customColorScale) : [];
} catch (error) {
console.warn(
'Invalid JSON in customColorScale. Please check your configuration syntax:',
error && error.message ? error.message : error
);

Copilot uses AI. Check for mistakes.
Comment on lines 124 to 126
const linearColorScale = getSequentialSchemeRegistry()
.get(linearColorScheme)
.createLinearScale(d3Extent(data, v => v.metric));
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable linearColorScale is created on line 124-126 but is never used in the updated code. The new implementation uses linearPaletteScale instead. This unused variable should be removed to avoid confusion and maintain code cleanliness.

Suggested change
const linearColorScale = getSequentialSchemeRegistry()
.get(linearColorScheme)
.createLinearScale(d3Extent(data, v => v.metric));

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant