Is it possible using the framework ag-grid in JS to apply a conditional background color formatting of a cell based on its value such as Excel conditional formatting (eg the second table formatting in this link is a great example of what I am trying to achieve).
Basically, cells containing the highest values are green and tend to be red as they lower, being yellow when they reach the median (the inverse is applied in above link)
As you see, it is not a simple CellClassRules as the cell color depends cell values across the table and not only a specific row or column.
I didn’t find such option on ag-grid documentation.
Write a function for the cellStyle and have this function look at each and every value in the table, determine it's ranking, then have it return the relevant colour for the cell, i.e. the lower it is, return a more "reddish" colour, the higher it is, return a "greener" colour. Something like this:
function myCellStyleFunction(params) {
const totalCellCount = params.api.getDisplayedRowCount() * columnsCount;
let allValuesInTable = [];
rowData.forEach((x) => {
const valuesForRow = Object.keys(x).map(function (k) {
return x[k];
});
allValuesInTable = allValuesInTable.concat(valuesForRow);
});
const valuesForTableOrdered = allValuesInTable.sort(function (a, b) {
return a - b;
});
const valueIndex = valuesForTableOrdered.indexOf(params.value);
console.log(valueIndex)
debugger;
const bgColour = generateColor('#FF0000','#00FF00',totalCellCount,valueIndex)
return { backgroundColor: '#' + bgColour };
}
And apply this cellStyle in defaultColDef so it is applied to every cell.
Demo.
Why don't you use the Gradient Column feature and it will do it all for you with a couple of clicks?
https://demo.adaptabletools.com/style/aggridgradientcolumndemo
Related
I am using ag-grid, I want the columns width to be dynamic according to the grid content,
I do it by using:
const onGridReady = params => {
params.columnApi.autoSizeAllColumns();
};
but the width of the grid is alays fixed, I have a space in the side of the grid.
(I can't also send width to the grid, because I can't know what will be the size of the content)
what I need is something how to combine autoSizeAllColumns and sizeColumnsToFit functions.
ag-grid has a property called defaultColDef that can be used for grid settings. If you pass flex: 1 as one of the parameters then all columns will size to fit so that you won't have that empty space on the side (expands to fill). Check out the ag-grid documentation on this page and search for the work "flex" for more details on auto/flex sizing.
If I understand you correctly you want to have the following behaviour for resizing - make sure each column has it's content visible first, but then also make sure that the whole grid width is filled at the least.
Unfortunately there doesn't seem to be a really straightforward way to achieve that. What I did is onGridReady, I would use the autoSizeColumns function to make sure that each column's content is fully visible, and then if there is an additional space left to fill the grid's width I distribute it evenly to each column. Then apply the new column state through gridApi.applyColumnState. Here is an example in vue
that should be fairly easy to transfer to other frameworks (or vanilla js).
interface UseGridColumnResizeOptions {
// we need access to the grid container so we can calculate
// the space that is left unfilled.
gridContainerRef: Ref<null | HTMLElement>;
// for any columns that you don't want to resize for whatever reason
skipResizeColumnIds: string[];
}
export const useGridColumnResize = ({ gridContainerRef, skipResizeColumnIds }: UseGridColumnResizeOptions) => {
const handleResize = ({ columnApi }: AgGridEvent) => {
columnApi.autoSizeAllColumns();
if (!gridContainerRef.value) {
console.warn('Unable to resize columns, gridContainer ref is not provided');
return;
}
const isColumnResizable = (colDef: ColDef) => colDef.resizable && !skipResizeColumnIds.includes(colDef.colId!);
const columns = columnApi.getAllGridColumns().filter((column) => isColumnResizable(column.getColDef()));
if (columns.length === 0) {
return;
}
const lastColumn = columns[columns.length - 1];
const lastColumnLeft = lastColumn.getLeft();
const lastColumnWidth = lastColumn.getActualWidth();
const { width: gridWidth } = gridContainerRef.value.getBoundingClientRect();
const gridSpaceLeftToFill = Math.max(0, gridWidth - (lastColumnLeft! + lastColumnWidth));
if (gridSpaceLeftToFill === 0) {
return;
}
const additionalSpaceForEachColumn = gridSpaceLeftToFill / columns.length;
const columnState = columnApi.getColumnState();
columnState.forEach((column) => {
const skipResizeForColumn = !columns.some((col) => col.getColId() === column.colId);
if (skipResizeForColumn) {
return;
}
column.width = column.width! + additionalSpaceForEachColumn;
});
columnApi.applyColumnState({ state: columnState });
};
return { handleResize };
};
You can plug the handleResize function on row-data-updated event to resize columns whenever new data arrives in the grid or only once in grid-ready or first-data-rendered.
Keep in mind that this implementation plays out well in my case as columns are not movable. I am expecting the last column inside the columns array to be the last visible one in the UI, but that might not always be the case and you might end up with wrong calculation of the space that is left to fill. So you might need to change the way the last visible column in the UI is retrieved to make it work for your case.
I want to create a dynamic chart that changes the color based on a cell value. I used the example in this answer but it only uses the first color I declared in the legend as seen in this image:
function modifyChart(sheet, newCssColor) {
// Assume there is only one chart on this sheet.
var sheet = SpreadsheetApp.getActive().getActiveSheet();
const charts = sheet.getCharts();
var array = [];
var colorValues = sheet.getRange("G4:G6").getValues();
for(var i = 0; i < colorValues.length; i++){
array.push(colorValues[i][0]);
}
Logger.log(colorValues);
const barBuilder = charts[0].modify().asColumnChart().setColors(array);
sheet.updateChart(barBuilder.build());
}
But here's exactly want to do:
If score <= 49 set bar color to red
Else if score >= 50 and score <= 89 set bar color to orange
else set bar color to green
Just like how the cell background changes because I set rules to it using Conditional Formatting.
Edit: Change the the cell range to match the sample
Each of your categories is a series in your embedded chart.
You want to set the style options of each series individually.
Use setOption() and set the color options for each series.
For example:
EmbeddedChartBuilder.setOption('series.0.color', 'red').setOption('series.1.color', 'orange').setOption('series.2.color', 'green').build()
I have the following handsontable configured:
var container = document.getElementById('example1'),
hot;
var data = [];
for (var i = 0; i < 100; i++) {
data.push({"id": 1, "rgba": "204,255,204,1"});
}
function callback (row, column, prop) {
const cellProperties = {};
cellProperties.renderer = renderer;
return cellProperties;
}
function renderer (instance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
var rdata = instance.getSourceDataAtRow(row);
var colour = rdata.rgba;
td.style.backgroundColor = 'rgba(' + colour + ')';
}
hot = new Handsontable(container, {
data: data
, cells: callback
// adding either of these in isolation allows the rendering to continue working. However adding both together causes only the top row to render correctly.
, hiddenColumns: true
, colWidths: 150
});
With a running example provided in the following link:
https://jsfiddle.net/JoshAdams/sgLm5ev2/
The problem I am encountering is my custom renderer is designed to set the background colours of each handsontable row based on the value in the rgba column. Which works fine initially.
However, when both the hiddenColumns: true and colWidths: 150 (or any number) properties are introduced, it causes an issue whereby only the top row of the handsontable will render correctly.
But adding either of these properties in isolation lets the rendering work correctly.
So does anyone know why this is occurring and how to fix it?
Note
While just calling hot.render() fixes the issue, its not really a solution as this causes additional unnecessary renders of handsontables which becomes a massive performance overhead in larger tables.
We have scatter plots working great in our dashboard, but we have been thrown a curve ball. We have a new dataset that provides multiple y values for a single key. We have other datasets were this occurs but we had flatten the data first, but we do not want to flatten this dataset.
The scatter plot should us the uid for the x-axis and each value in the inj field for the y-axis values. The inj field will always be an array of numbers, but each row could have 1 .. n values in the array.
var data = [
{"uid":1, "actions": {"inj":[2,4,10], "img":[10,15,25], "res":[15,19,37]},
{"uid":2, "actions": {"inj":[5,8,15], "img":[5,8,12], "res":[33, 45,57]}
{"uid":3, "actions": {"inj":[9], "img":[2], "res":[29]}
];
We can define the dimension and group to plot the first value from the inj field.
var ndx = crossfilter(data);
var spDim = ndx.dimension(function(d){ return [d.uid, d.actions.inj[0]];});
var spGrp = spDim.group();
But are there any suggestions on how to define the scatter plot to handle multiple y values for each x value?
Here is a jsfiddle example showing how I can display the first element or the last element. But how can I show all elements of the array?
--- Additional Information ---
Above is just a simple example to demonstrate a requirement. We have developed a dynamic data explorer that is fully data driven. Currently the datasets being used are protected. We will be adding a public dataset soon to show off the various features. Below are a couple of images.
I have hidden some legends. For the Scatter Plot we added a vertical only brush that is enabled when pressing the "Selection" button. The notes section is populated on scatter plot chart initialization with the overall dataset statistics. Then when any filter is performed the notes section is updated with statistics of just the filtered data.
The field selection tree displays the metadata for the selected dataset. The user can decide which fields to show as charts and in datatables (not shown). Currently for the dataset shown we only have 89 available fields, but for another dataset there are 530 fields the user can mix and match.
I have not shown the various tabs below the charts DIV that hold several datatables with the actual data.
The metadata has several fields that are defined to help use dynamically build the explorer dashboard.
I warned you the code would not be pretty! You will probably be happier if you can flatten your data, but it's possible to make this work.
We can first aggregate all the injs within each uid, by filtering by the rows in the data and aggregating by uid. In the reduction we count the instances of each inj value:
uidDimension = ndx.dimension(function (d) {
return +d.uid;
}),
uidGroup = uidDimension.group().reduce(
function(p, v) { // add
v.actions.inj.forEach(function(i) {
p.inj[i] = (p.inj[i] || 0) + 1;
});
return p;
},
function(p, v) { // remove
v.actions.inj.forEach(function(i) {
p.inj[i] = p.inj[i] - 1;
if(!p.inj[i])
delete p.inj[i];
});
return p;
},
function() { // init
return {inj: {}};
}
);
uidDimension = ndx.dimension(function (d) {
return +d.uid;
}),
uidGroup = uidDimension.group().reduce(
function(p, v) { // add
v.actions.inj.forEach(function(i) {
p.inj[i] = (p.inj[i] || 0) + 1;
});
return p;
},
function(p, v) { // remove
v.actions.inj.forEach(function(i) {
p.inj[i] = p.inj[i] - 1;
if(!p.inj[i])
delete p.inj[i];
});
return p;
},
function() { // init
return {inj: {}};
}
);
Here we assume that there might be rows of data with the same uid and different inj arrays. This is more general than needed for your sample data: you could probably do something simpler if there is indeed only one row of data for each uid.
To flatten out the resulting group, with we can use a "fake group" to create one group-like {key, value} data item for each [uid, inj] pair:
function flatten_group(group, field) {
return {
all: function() {
var ret = [];
group.all().forEach(function(kv) {
Object.keys(kv.value[field]).forEach(function(i) {
ret.push({
key: [kv.key, +i],
value: kv.value[field][i]
});
})
});
return ret;
}
}
}
var uidinjGroup = flatten_group(uidGroup, 'inj');
Fork of your fiddle
In the fiddle, I've added a bar chart to demonstrate filtering by UID. Filtering on the bar chart works, but filtering on the scatter plot does not. If you need to filter on the scatter plot, that could probably be fixed, but it could only filter on the uid dimension because your data is too course to allow filtering by inj.
I need to create a rowchart in dc.js with inputs from multiple columns in a csv. So i need to map a column to each row and each columns total number to the row value.
There may be an obvious solution to this but i cant seem to find any examples.
many thanks
S
update:
Here's a quick sketch. Apologies for the standard
Row chart;
column1 ----------------- 64 (total of column 1)
column2 ------- 35 (total of column 2)
column3 ------------ 45 (total of column 3)
Interesting problem! It sounds somewhat similar to a pivot, requested for crossfilter here. A solution comes to mind using "fake groups" and "fake dimensions", however there are a couple of caveats:
it will reflect filters on other dimensions
but, you will not be able to click on the rows in the chart in order to filter anything else (because what records would it select?)
The fake group constructor looks like this:
function regroup(dim, cols) {
var _groupAll = dim.groupAll().reduce(
function(p, v) { // add
cols.forEach(function(c) {
p[c] += v[c];
});
return p;
},
function(p, v) { // remove
cols.forEach(function(c) {
p[c] -= v[c];
});
return p;
},
function() { // init
var p = {};
cols.forEach(function(c) {
p[c] = 0;
});
return p;
});
return {
all: function() {
// or _.pairs, anything to turn the object into an array
return d3.map(_groupAll.value()).entries();
}
};
}
What it is doing is reducing all the requested rows to an object, and then turning the object into the array format dc.js expects group.all to return.
You can pass any arbitrary dimension to this constructor - it doesn't matter what it's indexed on because you can't filter on these rows... but you probably want it to have its own dimension so it's affected by all other dimension filters. Also give this constructor an array of columns you want turned into groups, and use the result as your "group".
E.g.
var dim = ndx.dimension(function(r) { return r.a; });
var sidewaysGroup = regroup(dim, ['a', 'b', 'c', 'd']);
Full example here: https://jsfiddle.net/gordonwoodhull/j4nLt5xf/5/
(Notice how clicking on the rows in the chart results in badness, because, what is it supposed to filter?)
Are you looking for stacked row charts? For example, this chart has each row represent a category and each color represents a sub-category:
Unfortunately, this feature is not yet supported at DC.js. The feature request is at https://github.com/dc-js/dc.js/issues/397. If you are willing to wade into some non-library code, you could check out the examples referenced in that issue log.
Alternatively, you could use a stackable bar chart. This link seems to have a good description of how this works: http://www.solinea.com/blog/coloring-dcjs-stacked-bar-charts