Chart colors not consistent

Hi all! New to Retool and the community so forgive me for such a basic question.

  • Goal: I need colors to be consistent across charts.

  • Details: I have a dashboard that I recently built to assist in reporting various metrics. I have set the colors of the charts for one customer but when I select a different customer, the colors in the charts will reset to random colors even though it's the same exact data being shown. Ideally, I could somehow hardcode these colors so no matter the customer that is chosen, the charts will be consistent to keep with our branding. For example, Value A will always be 335AF1, Value B will always be A8F4FF, etc.

I appreciate the help and happy to share screenshots if needed!

Hi Kevin, welcome!

Without seeing your exact setup, I suspect this is due to something I encountered recently when attempting to use a "Manual" Color selection method: the underlying data-source object passed into the chart with labels and values fields likely has inconsistent ordering, despite being otherwise consistent from entry to entry.

Screenshot 2025-02-25 at 12.50.06 PM

I solved this using a Javascript Map object to power my chart's labels & values, as it guarantees key order. This StackOverflow answer helped put me on the right path.

While this worked, it would be nice if Retool offered a simpler way to map the same keys/labels to the same colors consistently instead of it being a toss-up based on key ordering.

1 Like

I agree with @dguzzo!

Here's a post I made previously with an example app. Let me know if you have questions.

As I mentioned there, in the near future we’ll have a ton more preset chart types and that you’ll be able to do this in the UI soon! :slight_smile:

Thank you both for the response! I created a js called ChartColorMapping. This is the query:

const reasons = Distinct_Reasons.data.map(item => item.reason.trim()); // Trim whitespace from the reason values

const colorMap = {
"Blank": "#0892D0",
"Accidental Alert": "#010F0C",
"Accidental button press": "#010F0C",
"Accidental Button Press": "#010F0C",
"Drill Alert": "#D9D9D9",
"Duplicate": "#777777",
"Duplicate Alert": "#777777",
"Duplicate event": "#777777",
"Duplicate Event": "#777777",
"Duress / handled by security": "#335AF1",
"Duress Incident": "#335AF1",
"Duress incident / handled by security": "#335AF1",
"Duress incident/handled by security": "#335AF1",
"Duress incident/Handled by Security": "#335AF1",
"Duress Incident/Handled by security": "#335AF1",
"Duress Incident/Handled by Security": "#335AF1",
"Duress Incident/ Handled By Security": "#335AF1",
"Duress Incident/Handled By Security": "#335AF1",
"Duress Incident/Handled before arrival": "#A8F4FF",
"Duress incident / resolved before arrival": "#A8F4FF",
"Duress incident/resolved before arrival": "#A8F4FF",
"Duress incident/Resolved before arrival": "#A8F4FF",
"Duress Incident/Resolved before arrival": "#A8F4FF",
"Duress Incident/Resolved Before Arrival": "#A8F4FF",
"Duress Incident Resolved Before Arrival (Physical Violence and Agression)": "#A8F4FF",
"Duress Incident Resolved Before Arrival (Verbal Violence and Agression)": "#A8F4FF",
"Duress / resolved before arrival": "#A8F4FF",
"Emergency": "#00DAFF",
"Intentional test": "#D9D9D9",
"Intentional Test": "#D9D9D9",
"Intentional Test ": "#D9D9D9",
"Non-Duress Incident": "#BFDBFE",
"Non-duress situation": "#BFDBFE",
"Non-duress Situation": "#BFDBFE",
"Non Duress Situation": "#BFDBFE",
"Non-Duress Situation": "#BFDBFE",
"Other Activity": "#70AFFF",
"Other Criminal Activity": "#70AFFF",
"Other reason": "#70AFFF",
"Other Reason": "#70AFFF",
"Other Reason . . . ": "#70AFFF",
"Patient Assist": "#98AFC7",
"Physical Violence and Aggression": "#0095FD",
"Physical Violence and Aggression - Patient": "#B3EBF2",
"Safety – Facility Management": "#0E98BA",
"Security Assist": "#6495ED",
"Test": "#D9D9D9",
"Test Alert": "#D9D9D9",
"Test Alert ": "#D9D9D9",
"TESTING": "#D9D9D9",
"Verbal Violence and Aggression": "#1E90FF",
"Verbal Violence and Aggression - Patient": "#82CAFF",
};

const colorArray = reasons.map(reason => {
const trimmedReason = reason.trim();
const color = colorMap[trimmedReason];
console.log(Reason: "${trimmedReason}", Color: "${color}");
return color || "#CCCCCC";
});

console.log('Color Array:', colorArray);

return colorArray;

The chart still does not seem to be showing the correct color values. I also tried a variable and that didn't work either.

@kjosephsmith,
I can try to help you with this.
Can you please send me the exported JSON file for your app? You can export it like this:

And if you could also upload your Distinct_Reasons.data array, that'd be great too.

Thanks very much!!

Thank you! I really appreciate you taking a look into this for me.

JSON file for app:
SL Pro Business Reivew Dashboard.json (653.9 KB)

Distinct_Reasons.data:
Distinct_Reasons.csv (1.4 KB)

@kjosephsmith,
It looks like you are missing data in your values and your labels fields. Take a look at this simple app I have built as an example ( you can create my app from the attached json). So each of the 3 fields: values, labels and array for the Colors Selection should all have an array of the same length. This length will be equal to the number of slices in your pie chart.

I'm a little unclear on what it is you want to show in your pie chart. Will it be the 53 reasons? (53 slices?) What is the value of each reasons (what percentage of the pie chart)? I don't have the raw data from your databases, so I can't really tell what it is you are trying to display.

In my pie chart example, I am showing, from my table of 100 users, what percentage of them are Viewers versus Editors versus Admin. So just 3 slices.

pie_chart_color_example.json (77.0 KB)

Thank you! I was able to get the pie chart fixed in my app so the colors seem to stick when switching customers.

The pie chart shows the percentage of reasons for a given time frame for a given customer, so despite the database having a total of 53 distinct reasons, a customer will usually only have 7-10 distinct reasons, so the chart would have a slice representing each reasons with the percentage.

Would it be the same method for a stacked bar chart? Define the values, labels, and any group by criteria in a javascript, then map everything accordingly to the chart component? Thank you again- so much- for the help!

@kjosephsmith,
The stacked bar chart is set up a bit differently. Check out how it is set up in this app:

stacked_bar_and_pie_chart_examples.json (85.0 KB)

Click on each one of these "Viewer", "Editor" and "Admin" in the inspector for the barChart to see how they are set up. You essentially have to set up each different color in the stack as its own data series, and assign one color to it in its own configuration.

Let me know if you have any questions about how to set it up.

Thank you, Linda! I am not sure if this would work in for my use case now that I have been messing with it this evening.

When an account rep uses this dashboard, they will first select their customer from a dropdown and then choose a date range. From there, a query triggers and populates two things- a reasons dropdown and a table component. The reasons dropdown only shows the ones that are used by the selected customer, which is just a handful of the 53 distinct reasons found in the database. As reasons are checked in the multi-select dropdown, the table updates along with all of the charts to show a visual of alerts for that customer. Some charts update based on other single or multi-select components.

The stacked bar charts (all 12 of them) are pretty detailed and need to be grouped by various categories (facility, building, floor, unit, area, month, day of week, time of day, etc...). This is used as our company's primary reporting tool for customer performance and utilization, which is why there is so much needed detail on these charts.

In the example you provided- with each role being mapped- I would essentially have to map all 53 reasons as it's own data series in the chart, and then create the array of whatever the grouping would be, and as the table component is updated (based on the customer, date range, and reasons dropdown), the charts would reflect that?

@kjosephsmith,
I believe if you write a query that returns the specific data, the series for that specific customer should auto populate, but I will test this and get back to you.

@kjosephsmith,
With the new stacked bar component, you have to enter each of the 53 reasons separately. However, you can use the Plotly JSON chart. I will upload an app with a demonstration momentarily...

plotly_chart_example.json (13.8 KB)

@kjosephsmith, Check out this app for an example of how to create your stacked bar chart for all your reasons

Let me know if this works for you.