HandsOnTable cell value changes when renderer is used - number-formatting

When I use a function to change the background of a HandsOnTable cell, the value rendered in the cell changes to 1 decimal place. I thought this was because I was inadvertently removing the format string, but that appears to be incorrect.
This is the renderer, cell function and column definition:
function negativeValueRenderer(instance, td, row, col, prop, value, cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
if (value !== instance.getData()[row][2])
td.style.background = 'yellow';
}
Handsontable.renderers.registerRenderer('negativeValueRenderer', negativeValueRenderer);
function cells(row, col, prop) {
if (col === 1)
return { format: '0.00', renderer: negativeValueRenderer }
else
return { format: '0.00', }
}
var colDefs = [
{
dateFormat: 'DD/MM/YYYY HH:mm',
correctFormat: true,
width: 150,
editor: false,
disableVisualSelection: true,
readOnly: true,
}, {
type: 'numeric',
format: '0.00',
width: 75
}, {
type: 'numeric',
format: '0.00',
width: 75,
editor: false,
readOnly: true,
}
];
How can I ensure that cells which have, eg, 1254.23 retain the two decimal places - in my table the third column is rendered with 2 decimal places but the second is with only 1 place.

I had a similar problem.
At same page I have two handsontables, and I needed to color the active row on both. So I follow the example from link https://docs.handsontable.com/0.31.1/demo-conditional-formatting.html
Which has the line:
Handsontable.renderers.TextRenderer.apply(this, arguments);
And that line brings me a lot of problems with rows that has checkbox, numbers, dropdowns and so on. I may be mistaken but as far I could understand they transform anything to text. I solved my problem by a track on issue https://github.com/handsontable/handsontable/issues/732
As you could see all cells has your own type, and by the type has to be applied a different type of renderer.
case 'text':
Handsontable.TextCell.renderer.apply(this, arguments);
break;
case 'autocomplete':
Handsontable.AutocompleteCell.renderer.apply(this, arguments);
break;
case 'checkbox':
Handsontable.CheckboxCell.renderer.apply(this, arguments);
break;
case 'numeric':
Handsontable.NumericCell.renderer.apply(this, arguments);
break;
case 'date':
Handsontable.DateCell.renderer.apply(this, arguments);
break;
case 'handsontable':
Handsontable.HandsontableCell.renderer.apply(this, arguments);
break;
default:
Handsontable.TextCell.renderer.apply(this, arguments);
break;
From your colDefs you will get the type of column-cell.
Hope that it helps you.
Good lucky

Related

How to make/create a population pyramid in echarts?

I'm trying to create a population pyramid chart, using echarts (echarts.apache.org). I can't find an example of that kind, and couldn't find how to do it. The values of the chart should all be displayed as positive. A chart example: https://www.internetgeography.net/wp-content/uploads/2019/12/united-kingdom-population-pyramid-2016.gif
I have tried using reverse axes bar chart, with negative numbers, but I couldn't find a way to hack the displayed negative numbers into positive ones.
I tried modifying the stack graph and using negative input values and overwriting the display as positive (Math.abs()).
When importing the data, I multiply by -1, so the output must be positive. valueFormatter: (value) => Math.abs(value)
Modify tooltip.valueFormatter
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
valueFormatter: (value) => Math.abs(value)
}
And modify series.label.formatter display to be positive as well Math.abs(params.value)
label: {
show: true,
position: 'left',
formatter: function (params) {
return Math.abs(params.value);
}
}
And custom xAxis.axisLabel.formatter
xAxis: [
{
type: 'value',
show: true,
axisLabel: {
formatter: function (params) {
return Math.abs(params);
}
}
}
],
Example
Example code with Echarts editor

ChartJS - How to choose the visible datalabels

I would like to ask how to choose which data labels should be visible on the line chart? It looks like on the screen
But it's not nice. I would like to have visible only every second the label. I thought to check how many labels there are. Then if more than X choose only some, but maybe there is something easier.
Here is the code from labels:
options: {
plugins: {
datalabels: {
align: 'top',
color: '#000000',
font: {
weight: 'bold',
size: '16'
}
}
},
title: {
display: true,
text: 'Level'
}
}
Any ideas?
You can define a formatter that returns the value or an empty string in case you want to skip the data label.
The following formatter for example makes sure, only every second data label appears on your chart.
options: {
plugins: {
datalabels: {
...
formatter: (value, context) => context.dataIndex % 2 == 0 ? value : ''
}
...
This can be extended with a check for the total number of values. The following formatter returns every value as long as the total number of values is lower than 100. Otherwise, it only returns every second value.
options: {
plugins: {
datalabels: {
...
formatter: (value, context) => context.chart.data.datasets[0].data.length < 100 || context.dataIndex % 2 == 0 ? value : ''
}
...

agGrid with Angular, using agRichSelectCellEditor

I have an agGrid populated with Employee records in JSON format from my web service.
[
{
id: 123,
firstName: 'Mike',
lastName: 'Jones',
countryId: 1001,
DOB: '1980-01-01T00:00:00',
. . .
}
I have a second web service returning a list of country codes:
[
{ id: 1000, name: 'France' },
{ id: 1001, name: 'Spain' },
{ id: 1002, name: 'Belguim' }
]
What I'm trying to do is get my agGrid to have a column showing the user's details, including the name of their country, and when they edit this cell, a list of country codes will appear, where they can select one, and it'll update the record with the id of that country.
Basic stuff, no ?
But has anyone managed to get agGrid to successfully use the "agRichSelectCellEditor" to do this successfully ?
{ headerName: 'Country', width: 120, field: 'countryId', editable: true,
cellEditor:'agRichSelectCellEditor',
cellEditorParams: {
// This tells agGrid that when we edit the country cell, we want a popup to be displayed
// showing (just) the names of the countries in our reference data
values: listOfCountries.map(s => s.name)
},
// The "cellRenderer" tells agGrid to display the country name in each row, rather than the
// numeric countryId value
cellRenderer: (params) => listOfCountries.find(refData => refData.id == params.data.countryId)?.name,
valueSetter: function(params) {
// When we select a value from our drop down list, this function will make sure
// that our row's record receives the "id" (not the text value) of the chosen selection.
params.data.countryId = listOfCountries.find(refData => refData.name == params.newValue)?.id;
return true;
}
},
My code seems to be almost correct.. it manages to:
display the country name in each row of the agGrid
display a popup, listing the country names, from our "list of countries" array
when I select an item in the popup, it successfully updates the countryId field with the (numeric) id value of my chosen country
The only problem is that at the top of the popup, it shows the countryId value, rather than the user's current country name.
Has anyone managed to get this to work ?
The only workaround I could come up with was to override the CSS on this popup and hide that top element:
.ag-rich-select-value
{
display: none !important;
}
It works... but you no longer get to see what your previously selected value was.
(I really wish the agGrid website had some decent, real-life, working Angular examples... or at least let developers post comments on there, to help each other out.)
The solution was to use a valueGetter, rather than a cellRenderer:
{
headerName: 'Country', width: 120, field: 'countryId', editable: true,
cellEditor:'agRichSelectCellEditor',
cellEditorParams: {
// This tells agGrid that when we edit the country cell, we want a popup to be displayed
// showing (just) the names of the countries in our reference data
values: listOfCountries.map(s => s.name)
},
valueSetter: function(params) {
// When we select a value from our drop down list, this function will make sure
// that our row's record receives the "id" (not the text value) of the chosen selection.
params.data.countryId = listOfCountries.find(refData => refData.name == params.newValue)?.id;
return true;
},
valueGetter: function(params) {
// We don't want to display the raw "countryId" value.. we actually want
// the "Country Name" string for that id.
return listOfCountries.find(refData => refData.id == params.data.countryId)?.name;
}
},
I hope this is useful...
I was able to get my similar situation (id:name pairs in a list, but not using Angular though) working without the problem you mentioned above, and without a valueGetter/valueSetter and only a renderer. The benefit is that you don't need to double click the cell to see the list, the cell appears as a selection box always, and you avoid a bug should the user double click the cell when the list is displayed.
The renderer is a lot clunkier than I was wanting (one line like yours) and it didn't seem that aggrid had built in support for this pretty basic function (and I already have spent enough time on this).
Anyway, this is what I had, which at least works, but keen to see further improvements on it. (You will need to at least change 2 lines for the option related code since my defaultValue object is specific to me).
The column definition:
{field: 'defaultValueID', headerName: "Default Value", cellEditor:'agRichSelectCellEditor', cellRenderer: defaultValueRenderer}
And the renderer code:
function defaultValueRenderer(params) {
var input = document.createElement("select");
// allow it to be cleared
var option = document.createElement("option");
option.innerHTML = '[None]';
option.value = null;
input.appendChild(option);
for (var i=0; i < defaultValueList.length; i++) {
var option = document.createElement("option");
option.innerHTML = defaultValueList[i].name;
option.value = defaultValueList[i].gltID;
input.appendChild(option);
}
input.value = params.value;
input.onchange = function() {
params.setValue(this.value);
params.data.defaultValueID = this.value;
}
input.style="width: 100%; height: 100%"; // default looks too small
return input;
}
Here Is Example Of agRichSelectCellEditor...
{
headerName: 'Dropdown', field: 'dropdown',
cellEditor: 'agRichSelectCellEditor',
width: 140,
editable: true,
cellEditorParams: (params) => {
values: Get All Dropdown List Like ["Hello","Hiii","How Are You?"]
},
valueSetter: (params) => {
if (params.newValue) {
params.data.dropdown= params.newValue;
return true;
}
return false;
}
}
Much simpler solution: use cellEditorParams formatValue, along with valueFormatter
{
field: 'foo',
cellEditor: 'agRichSelectCellEditor',
cellEditorParams: {
values: [1,2,3, 4, other ids... ],
formatValue: (id: number): string => this.getLabelFromId(value)
},
valueFormatter: (params: ValueFormatterParams): string => this.getLabelFromId(params.value as number)
}

ChartJS / MomentJS - Unable to remove deprecation warning. Graph not showing in firefox/opera

so browsers throw
warning about using momentJS incorrectly.
Deprecation warning: value provided is not in a recognized ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.
Arguments:
[0] _isAMomentObject: true, _isUTC: false, _useUTC: false, _l: undefined, _i: 12.30, _f: false, _strict: undefined, _locale: [object Object]
Error
So i looked at my code
data: {
labels: ['01.01', '02.01', '03.01', '04.01', '05.01', '06.01', '07.01', '08.01', '09.01', '10.01', '11.01', '12.01'],
datasets: createChatterData(data, this)
},
And read that I should provide a format when dealing with non iso strings.
labels: [moment('01.01', 'MM.DD'), moment('02.01', 'MM.DD'), ...];
Ok that removed first deprecation.
But my datasets data also contains of date
dataset.data.pushObject({
x: moment(datum).format('MM.DD'),
y: parseInt(moment(datum).format('YYYY'))
});
So I tried different variations to that (premodified ambigious datetime)
x: moment(date, 'YYYY.MM.DD').format('MM.DD')
and
x: moment(date, 'MM.DD')
But my graph doesnt map correctly anymore.
Example of codepen chart working in chrome: http://codepen.io/kristjanrein/pen/wJrQLE
Does not display in firefox/opera
I see a couple of issues here.
1) Since you want your X axis to be a time scale, then you should leave your X data value as a moment object. Your current implementation is creating a moment object from a date string and then formatting it back to a string. When you do this, chart.js then takes the string and tries to create a moment object internally when it builds the chart.
Therefore, It is best to keep the data as either a Date or Moment object and use the time scale configuration properties to determine how the data is displayed on the chart. This prevents chart.js from having to construct the moment object and guess at the string format.
2) You are using the pre-2.0 way to create a chart when you use Chart.Scatter. Instead you should use the new style (new Chart()) and pass in a type property.
Here is a modified version of you code that results in no browser warnings and works in Chrome and Firefox (I did not test in Opera).
var getData = function() {
var dummyDataset = [
'2007-11-09T00:00:00.000Z',
'2006-08-04T00:00:00.000Z',
'2006-08-06T00:00:00.000Z',
'2008-01-10T00:00:00.000Z'
];
return dummyDataset.map(function(datum) {
var myMoment = moment(datum);
return {
x: myMoment,
y: parseInt(myMoment.format('YYYY')),
};
});
};
var ctx = document.getElementById("chart1").getContext("2d");
var myScatter = new Chart(ctx, {
type: 'line',
data: {
datasets: [{
label: "My First dataset",
borderColor: 'rgb(255, 99, 132)',
fill: false,
pointRadius: 4,
pointHoverRadius: 8,
showLine: false,
data: getData()
}]
},
options: {
responsive: true,
title: {
display: true,
text: 'Random Data'
},
legend: {
display: true,
labels: {
fontSize: 10,
boxWidth: 20
}
},
elements: {
point: {
pointStyle: 'rect'
}
},
hover: {
mode: 'nearest'
},
scales: {
xAxes: [{
type: 'time',
position: 'bottom',
scaleLabel: {
display: true,
labelString: 'Months'
},
time: {
unit: 'month',
displayFormats: {
month: 'MM'
},
}
}],
yAxes: [ {
type: 'linear',
ticks: {
min: 2005,
max: 2015,
stepSize: 1
},
scaleLabel: {
display: true,
labelString: 'Year'
}
}]
}
}
});
You can see it in action at this forked codepen.
One other thing to keep in mind is that because your data spans multiple years, you will see duplicate months on the X axis. Remember, a time scale is used to plot dates so even if you only display the months, a data point with the same month but with different years will not be plotted at the same location.
If you are actually only wanting to show month string/number values in the X axis, then you should not use the time scale at all and use the linear scale instead. Then when you build your data values, you would extract the month from the data (the same way you are already doing for your Y value).
var getData = function() {
var dummyDataset = [
'2007-11-09T00:00:00.000Z',
'2006-08-04T00:00:00.000Z',
'2006-08-06T00:00:00.000Z',
'2008-01-10T00:00:00.000Z'
];
return dummyDataset.map(function(datum) {
var myMoment = moment(datum);
return {
x: parseInt(myMoment.format('MM')),
y: parseInt(myMoment.format('YYYY')),
};
});
};
So in addition to jordan's answer
I changed my labels and x axis from
['01.01', '02.01', ...] to [1,2,...]
and
from type: 'time' to type: 'linear'
And to make it map not only by month but also by day. I had to make date objects to correct floats. 05.20 to 5.66
const date = datum.key;
const day = parseInt(moment(date).format('DD')) / 30 * 100;
const fullDate = parseFloat(moment(date).format('MM') + '.' + Math.round(day))
// 05.10 would be 5.3 (10 of 30 is 33%)
{
x: fullDate,
y: parseInt(moment(date).format('YYYY'))
date: date, // for tooltip
count: count // for tooltip
}
And i also had to make corrections to my tooltips
callbacks: {
title: function([tooltipItem], data) {
const tooltipInfo = getTooltip(tooltipItem, data.datasets);
return tooltipInfo.date;
},
label: function(tooltipItem, data) {
const tooltipInfo = getTooltip(tooltipItem, data.datasets);
return i18n.t('chart.count') + ': ' + tooltipInfo.count;
},
}
corresponding tooltip dataset
function getTooltip(tooltipItem, datasets) {
return datasets[tooltipItem.datasetIndex].data.find(datum => {
return datum.x === tooltipItem.xLabel && datum.y === tooltipItem.yLabel;
});
}

Ag-Grid: Number Formatting eg:123456.78 to 123,457

I have huge sets of numeric data.
this needs to be rendered as comma separated value.
For Ex.
123456.78 to be rendered as 123,457 using Ag-Grid.
Kindly help me on achieving this.
As per the cell rendering flow documentation (here), you can use the colDef.valueFormatter, like this:
var columnDefs = [
{headerName: "Number", field: "number"},
{headerName: "Formatted", field: "number", valueFormatter: currencyFormatter}
];
function currencyFormatter(params) {
return '£' + formatNumber(params.value);
}
function formatNumber(number) {
// this puts commas into the number eg 1000 goes to 1,000,
// i pulled this from stack overflow, i have no idea how it works
return Math.floor(number).toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
}
You could also use a cellRenderer as other posts describe, but that's typically for more complex rendering, whereas the valueFormatter is specifically for this case. From the ag-grid documentation:
valueFormatter's are for text formatting.
cellRenderer's are for when you want to include HTML markup and
potentially functionality to the cell. So for example, if you want to
put punctuation into a value, use a valueFormatter, if you want to put
buttons or HTML links use a cellRenderer. It is possible to use a
combination of both, in which case the result of the valueFormatter
will be passed to the cellRenderer.
{
headerName: 'Salary', field: 'sal'
cellRenderer: this.CurrencyCellRenderer
}
private CurrencyCellRenderer(params:any) {
var usdFormate = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 4
});
return usdFormate.format(params.value);
}
Like these we can mention in Angular2 Typescript code.
You can do this by writing a "customcellRenderer", when you create a column definition provide a function to "cellRenderer " attribute and in renderer use number filter, something like this
var colDef = {
name: 'Col Name',
field' 'Col Field',
cellRenderer: function(params) {
var eCell = document.createElement('span');
var number;
if (!param.value || !isFinite(param.value)) {
number = '';
} else {
number = $filter('number')(param.value, 0);
}
eCell.innerHTML = number;
return eCell;
}
}