Transitive transforms appear to be happening out of order - style-dictionary

I'm working on a refactor of our style-dictionary implementation. I'm working on applying alpha values through a transform rather than predefining values with alpha values.
It looks something like this:
In color.json
{
color: {
text: {
primary: {
value: '{options.color.warm-grey-1150.value}',
alpha: .75,
category: 'color',
docs: {
category: 'colors',
type: 'text',
example: 'color',
description: 'The default, primary text color',
},
}
}
}
The value for warm-grey-1150 is #0C0B08 and is in another file.
I have already successfully created a simple alpha transform for scss, less, and js and it works just fine:
const tinycolor = require('tinycolor2');
module.exports = (StyleDictionary) => {
StyleDictionary.registerTransform({
name: 'color/alpha',
type: 'value',
transitive: true,
matcher(prop) {
return (prop.attributes.category === 'color') && prop.alpha;
},
transformer(prop) {
const { value, alpha } = prop;
let color = tinycolor(value);
color.setAlpha(alpha)
return color.toRgbString();
},
});
};
However, I'm stuck on the IOS UIColor transform. My initial approach was to convert the colors to a hex8 value, as those were the original values that we were converting. (We had a value already created which mapped to #0C0B08BF and just plugged that into UIColor).
So I created a separate transform for IOS to set the alpha value and then extended the UI-color transform to make it transitive.
const tinycolor = require('tinycolor2');
module.exports = (StyleDictionary) => {
StyleDictionary.registerTransform({
name: 'color/alpha-hex',
type: 'value',
transitive: true,
matcher(prop) {
return (prop.attributes.category === 'color') && prop.alpha;
},
transformer(prop) {
let { value, alpha } = prop;
let color = tinycolor(value);
color.setAlpha(alpha);
return color.toHex8String();
},
});
};
In the transform group I made sure that the alpha-hex transform happened before UIColor:
module.exports = (StyleDictionary) => {
StyleDictionary.registerTransformGroup({
name: 'custom/ios',
transforms: [
//Other non-color related transforms
'color/alpha-hex',
'color/UIColor-transitive',
//Other non-color related transforms
],
});
};
The results were strange, as all the UIColor values that happened to undergo the alpha transform had a red, green and blue value of zero, but the alpha value was set:
[UIColor colorWithRed:0.000f green:0.000f blue:0.000f alpha:0.749f]
I decided to experiment and tried using chroma-js instead of tinycolor2 and chroma threw up an error:
Error: unknown format: [UIColor colorWithRed:0.047f green:0.043f blue:0.031f alpha:1.000f]
(Apparently, tinycolor doesn't throw up an error when passed an invalid format and instead creates an instance of tinycolor with #000000 as its value.)
For some reason, the UIColor formatted values are already being piped to the alpha-hex transform, even though I specified that I wanted the alpha-hex transform to run before. I've tried several things like not running the transform if value.indexOf('UIColor') !== -1) and that didn't seem to work. I also copied/pasted the UIColor transform and tried to run my hex transform in the same transform function but that didn't seem to work either.
Any ideas on what I'm missing here?

We had this exact same issue. This was happening because non-transitive transforms were occurring in our mix function, even if the matcher excluded that particular token.
What ended up working for us was to first console.log what was being used in the mix method and using Regex to parse it into a color format that would work.
We ended up splitting color transforms up into two separate transforms:
One that is non-transitive, and would output in the final format we needed but had a matcher that excluded all color-blended tokens (ex. UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1)
The other that is transitive transform targeting color-blended tokens, that would then take in each prop and transform those already transformed values into a color format supported by tinyColor.
By using tinyColor's isValid() method, we could validate what was being passed in or throw an error. This helped us debug but would also help future contributions that required color blending.

I figured out the issue for me. We had a whole bunch of options structure like this:
These are one of our color options tokens that get filtered out of the final result. These options get used for tokens like color-text-primary
'warm-grey-200': {
value: '#f4f2ed',
category: 'color',
},
When we use a transitive transform we are going through every single transform in a transform group for each of the options. With ios UIColor the problem is the final result of the transform can't get transformed again because it's a different format.
So this is what solved it for us. We changed the options to bare values like so:
'warm-grey-200': '#f4f2ed',
'warm-grey-300': '#edeae3',
we removed the "value" keyword as well as the category. However, this meant that now we had to make all of our transforms transitive (at least those dealing with color) and this meant extending some of the existing predefined transforms so they were transitive like so:
module.exports = (StyleDictionary) => {
StyleDictionary.registerTransform(
Object.assign({}, StyleDictionary.transform[`predefined/transform`], {
name: 'predefined/transform-transitive',
transitive: true
}),
)
}
This worked great for us. To sum up the two steps we needed to make were:
Restructure our color options to bare values so that transforms
don't happen to them until AFTER they are referenced
Make sure that all transforms that deal with these color options are transitive--otherwise you'll just end up with the original,non-transformed values.

Related

MapBox Randomizing Building Color

Is it possible to loop through all of the buildings on the map an assign them different colours, or a different property value that drives a thematic grouping?
I have code to loop through the buildings:
features = map.queryRenderedFeatures({ layers: ["building"], filter: ['==', 'extrude', 'true']});
features.forEach(function(feature){
// how to change feature colour or property in here?
}
There's a couple of ways achieving something like what you want.
Feature state
If your buildings layer has feature IDs, you can use setFeatureState to set a state on each building.
features = map.querySourceFeatures('building', { sourceLayer: 'buildings' }));
features.forEach(function(feature){
if (!map.getFeatureState({ id: feature.id, source: 'building', sourceLayer: 'buildings' }).color) {
map.setFeatureState({ id: feature.id, source: 'building', sourceLayer: 'buildings' }, { color: makeRandomColor() }
}
});
You can then use ['feature-state', 'color'] in an expression.
Quasi random
If there's no feature ID, you might be able to use some other attribute in a way that appears to be random. For instance, if there's some other kind of ID, you can use a mod function to map it to a color, in a way that might look kind of random.

Hide data series from tooltip

How can I exclude a specific data series from showing in the tooltip with tooltip.trigger = axis?
I'm asking because I have a very complex graph with one line chart, two bar charts and one heatmap. And the heatmap has so many data that the tooltip ends up with too many lines. The heatmap values are not very important, so I would like to remove them from showing in the tooltip.
Right now I'm using formatter to exclude them, but is there any other way?
I do exactly the same: adding a new attribute to the series and checking it from formatter.
Something like this:
series: [{
// ...
showInTooltip: true
// ...
}]
// ----
formatter: series => {
var displayed = series.filter(item => item.showInTooltip);
// work with displayed where showInTooltip === true
}
Also you can store callback instead attribute and it will work.
Updated: Suddenly I found undocumented feature and it will solved you trouble by the right way, apparently.
series: [{
// ...
tooltip: {
show: false
}
// ...
}]

Vizframe Datalabel show for one Line

I'm using Vizframe component where there is two lines, when I try to show the Datalabel, the modifications is applied to both lines, I want to apply it just to one of them (A line with Datalabel set to True and a line with DataLabel set to False.)
Here's how it looks
This is how I change DataLabel property:
plotArea: {
dataLabel: {
visible: false
},
},
Well I know that this question might be older, but I had a similar issue recently. You can use the renderer function to customize the data labels to your needs.
For me, it worked, when I checked the context data for the correct measure and decide then, if I return nothing, which shows the label normally or return an empty DOM node.
dataLabel: {
visible: true,
style: {
color: "#000"
},
renderer: function (ctx) {
var node = document.createElement("text");
if (ctx.ctx.measureNames === "Optimum") {
var text = document.createTextNode("");
node.appendChild(text);
return node;
}
}
},
As you can see in the picture below, the label for the orange line (Optimum) is gone.
You can hide the values when they are overlapped using this:
plotArea: {
dataLabel: {
visible: true,
hideWhenOverlap: true
}
}
Julian's answer is useful yet in order for the change to take effect on the document we need to attach the returned node to the parent data point node.
The renderer function does not provide data regarding the graph itself but only the selected label, which leaves us clueless on how to render the node effectively.
Something like this would make me happier:
renderer: function(...) {
var parent = document.getElementById(some_given_data_point_id);
parent.appendChild(text_node);
return parent;
}

vue-chartjs: is it possible to dynamically change color based on value

Using vue-chartjs is it possible to change to color of the bar?
This is similar to this question which is based on chart.js
chart.js bar chart color change based on value, and the jsfiddle provided in the answer is exactly what I'm looking for, except within vue-chartjs.
Any help appreciated
So this should be your chart calling (in my case, I named it bar-chart)
<bar-chart
:chart-data="barData"
:options="barChartOptions">
</bar-chart>
:chart-data and :options are 2 props defined when I create my bar component:
Vue.component('bar-chart', {
extends: VueChartJs.Bar,
props: ['chartData', 'options'],
So your barData, should be an object like this:
{datasets: [...], labels: [...]}
Your dataset is an array with the charts you want to show. So if you want to show only 1 data, than your array only has one position. So let's assume that by now. We'll use dataset = dataset[0]
Your dataset accepts some properties, 2 of them are a must:
Data (an array with the data you want to show)
Label (the name of the label when you hover on the bardata. It should display "Label: value"
It also accepts some other properties like:
fill
hoverBackgroundColor
backgroundColor
check more here: https://www.chartjs.org/docs/latest/charts/bar.html
so now, your backgroundColor property is either a color value (e.g. red, #FF0000), or an array.
If it is an array, then this should be true dataset.bgColors.length === dataset.data.length
and each dataset.bgColors array position is the color of the respective value in the dataset.data array.
dataset: {
data: [1,2,-3,-4,2,1]
backgroundColor: ['green', 'green', 'red', 'red', 'green', 'green']
...
}
So now, you can just build a bgColors array with the color you want, based on your data.
------------- UPDATING THE DATA -----------------
To anybody else who is looking for a way to UPDATE your chart data after it was rendered. It's a different question, but to help the community:
When you set your chart component, you can define a watch for the chartData prop, so when the chartData changes, the method is called and you re-render the chart:
Vue.component('bar-chart', {
extends: VueChartJs.Bar,
props: ['chartData', 'options'],
methods: {
renderLineChart () {
this.renderChart(this.chartData, this.options)
}
},
mounted () {
this.renderLineChart()
},
watch: {
chartData: function () {
this.$data._chart.destroy()
this.renderLineChart()
}
}
})
IMPORTANT: Make sure you make a new copy of the new chartData object info because the watch will only check if the object itself changed, not its inner properties.
However, if you really want to change ONLY the dataset.data or dataset.backgroundColor or any other, than the watcher will not know it changed. You can use the property deep: true in the watcher for this, as it will check changes deep inside the chartData object:
watch:
chartData: {
handler: function () {
this.$data._chart.destroy()
this.renderLineChart()
},
deep: true
}
}
I hope the answer was clear for everyone.
Best regards

How to provide a background color for an entire row in ag grid based on a certain value in a column?

I need to provide a background color for an entire row in ag grid based on a condition in a column. I found no such examples where entire row is colored based on a certain value in a column..
The previous answer is somewhat outdated (although still correct and working) and now we have some more control over the styling of the grid. You could use getRowStyle(params) for this job, just like this:
gridOptions.getRowStyle(params) {
if (params.data.myColumnToCheck === myValueToCheck) {
return {'background-color': 'yellow'}
}
return null;
}
Obviously, myColumnToCheck would be the column you're checking your value against (the same name you input in the id/field property of the colDef object), and myValueToCheck would be the value you want said column to have to make the row all yellow.
I hope this helps others. A very common use case in any table or grid including AG Grid is going to be to set the even/odd background color of the whole row of the entire table in a performant way. ALSO, this needs to still work when SORTING.
ALL OF THESE WAYS OF DOING THIS IN AG-GRID ARE WRONG. Even though they WILL work without sort, they will not update properly when you go to use sorting. This is due to something the ag-grid team refers to in this issue https://github.com/ag-grid/ag-grid-react/issues/77 as initialization time properties.
// Initialization problem
getRowClass = (params) => {
if (params.node.rowIndex % 2 === 0) {
return this.props.classes.rowEven;
}
};
<AgGridReact
getRowClass={this.getRowClass}
>
// Initialization problem
getRowStyle = (params) => {
if (params.node.rowIndex % 2 === 0) {
return this.props.classes.rowEven;
}
};
<AgGridReact
getRowStyle={this.getRowStyle}
>
// Initialization problem
rowClassRules = {
rowEven: 'node.rowIndex % 2 === 0',
}
rowClassRules = {
rowEven: (params) => params.node.rowIndex % 2 === 0,
}
<AgGridReact
rowClassRules={this.rowClassRules}
>
// Trying to change the key so a rerender happens
// Grid also listens to this so an infinite loop is likely
sortChanged = (data) => {
this.setState({ sort: Math.random()})
}
<AgGridReact
key={this.state.sort}
onSortChanged={this.sortChanged}
>
Basically, most stuff in grid is just read once and not again, probably for performance reasons to save rerenders.
You end up with this problem when sorting when doing any of the above:
THE FOLLOWIUNG IS THE RIGHT WAY TO ACHIEVE EVEN ODD COLORING:
The correct way to add even/odd functionality in ag-grid is to apply custom css styles as follows:
You will need to overwrite/use ag variables as mentioned in the docs here:https://www.ag-grid.com/javascript-grid-styling/#customizing-sass-variables
The names of the variables in our case are
.ag-grid-even class name, or the .ag-grid-odd class name. You of course only need one if you just want an alternating color to help with visibility. For our purposes we only needed one.
Here is how this process looked in our repo:
1. Make a custom css file that overwrites/uses some of these ag- class variable names. We call it ag-theme-custom.css (I believe it needs to be a css file).
Note: We also have sass variables so this file just has a comment that this color I am adding in css is the value for our variable $GREY_100 so you don't need that part
You now will get the same result but it will still work when sorting.
Answer 2 is correct, but the syntax used is wrong, and caused me several problems trying to sort it out. Trying to minify the answer 2 code barfed, for example. It did work, but it's not proper syntax as far as I can see.
Note, this can be done inline, or with an external
function. For example an external function.
vm.gridOptions = {
columnDefs: columnDefs,
getRowStyle: getRowStyleScheduled
}
function getRowStyleScheduled(params) {
if (params.selected && params.data.status === 'SCHEDULED') {
return {
'background-color': '#455A64',
'color': '#9AA3A8'
}
} else if (params.data.status === 'SCHEDULED') {
return {
'background-color': '#4CAF50',
'color': '#F4F8F5'
};
}
return null;
};
You can add CSS classes to each row in the following ways:
rowClass: Property to set CSS class for all rows. Provide either a string (class name) or array of strings (array of class names).
getRowClass: Callback to set class for each row individually.
<ag-grid-angular
[rowClass]="rowClass"
[getRowClass]="getRowClass"
/* other grid options ... */>
</ag-grid-angular>
// all rows assigned CSS class 'my-green-class'
this.rowClass = 'my-green-class';
// all even rows assigned 'my-shaded-effect'
this.getRowClass = params => {
if (params.node.rowIndex % 2 === 0) {
return 'my-shaded-effect';
}
};
You can define rules which can be applied to include certain CSS classes via the grid option rowClassRules.
The following snippet shows rowClassRules that use functions and the value from the year column:
<ag-grid-angular
[rowClassRules]="rowClassRules"
/* other grid options ... */>
</ag-grid-angular>
this.rowClassRules = {
// apply green to 2008
'rag-green-outer': function(params) { return params.data.year === 2008; },
// apply amber 2004
'rag-amber-outer': function(params) { return params.data.year === 2004; },
// apply red to 2000
'rag-red-outer': function(params) { return params.data.year === 2000; }
};
You can't change the background color of an entire row in one command. You need to do it through the cellStyle callback setup in the columnDefs. This callback will be called per each cell in the row. You need to change the color of the row by changing the color of all the cells.
See the following column definition
{
headerName: "Street Address", field: "StreetAddress", cellStyle: changeRowColor
}
You need to do this for all your columns.
Here is your changeRowColor function.
function changeRowColor(params) {
if(params.node.data[4] === 100){
return {'background-color': 'yellow'};
}
}
It changes the color of a row if the value of the third cell is 100.
I set different color for even and odd rows you can do it in any way..
$scope.gridOptions.getRowStyle = function getRowStyleScheduled(params){
if(parseInt(params.node.id)%2==0) {
return {'background-color': 'rgb(87, 90, 90)'}
}else {
return {'background-color': 'rgb(74, 72, 72)'}
}
};
If you don't need to set the background color conditionally(based on the row data), it is not recommended to use rowStyle, as written on the row style documentation page:
// set background color on even rows
// again, this looks bad, should be using CSS classes
gridOptions.getRowStyle = function(params) {
if (params.node.rowIndex % 2 === 0) {
return { background: 'red' };
}
}
Instead, you can change the row colors using css:
#import "~ag-grid-community/dist/styles/ag-grid.css";
#import "~ag-grid-community/dist/styles/ag-theme-alpine.css";
#import "~ag-grid-community/dist/styles/ag-theme-balham.css";
#import "~ag-grid-community/src/styles/ag-theme-balham/sass/ag-theme-balham-mixin";
.ag-theme-balham {
#include ag-theme-balham((
// use theme parameters where possible
odd-row-background-color: red
));
}
If you are using AdapTable then the simplest way is to use a Conditional Style and apply it to a whole row.
The advantage of this is that it can be at run-time easily by users also.
https://demo.adaptabletools.com/style/aggridconditionalstyledemo