I'm using the grid from http://ui-grid.info/ in a project. I've created a hierarchical grid, which works nicely, but when I do an export, it only exports the data from the top-level grid.
This is by design and is standard functionality for the grid, so there's no point in me putting up any example code. Any example from http://ui-grid.info/docs/#/tutorial will do.
Is there a way to export the subgrid (preferably both the main grid AND the subgrid together as they appear in the grid)?
Sadly the answer is no.
As you can see here the function getData iterates through all rows and then through all columns, adding to an array of extractedFields the columns to be extracted and aggregating those in an array of extractedRows.
This means that no data, other than what's defined in gridOptions' columnDef will be read, converted and extracted.
By design, subgrid information are stored inside a property of any row entity's subGridOptions but this property is never accessed inside of the exporter feature.
The motivation behind this behaviour is that expandable grid feature is still at an alpha stage, so, supporting this in other features is not a compelling priority.
Furthermore, adding subgrid to a CSV could be quite hard to design, if we wanted to provide a general solution (for example I don't even think it would be compliant to CSV standard if you had different number of columns in the main grid and in subgrids).
That said, ui-grid is an open source project, so, if you have a working design in mind, feel free to open a discussion about it on the project gitHub page or, even better, if you can design a working (and tested) solution and create a pull request, even better!
I managed to get it working, although if I had the time I would do it a bit better than what I've done by actually creating a branch of the code and doing it properly, but given time constraints, what I've got is working nicely.
FYI, here's the way I ended up getting it to do what I wanted:
In my grid options, I turned off the CSV export options in the grid menu (because I've only implemented the changes for PDF).
I made a copy of exporter.js, named it custom.exporter.js and changed my reference to point to the new file.
In custom.exporter.js, I made a copy of the getData function and named it getGridRows. getGridRows is the same as getData, except it just returns the rows object without all the stuff that gets the columns and so on. For now, I'm coding it to work with a known set of columns, so I don't need all that.
I modified the pdfExport function to be as follows:
pdfExport: function (grid, rowTypes, colTypes) {
var self = this;
var exportData = self.getGridRows(grid, rowTypes, colTypes);
var docContent = [];
$(exportData).each(function () {
docContent.push(
{
table: {
headerRows: 1,
widths: [70, 80, 150, 180],
body: [
[{ text: 'Job Raised', bold: true, fillColor: 'lightgray' }, { text: 'Job Number', bold: true, fillColor: 'lightgray' }, { text: 'Client', bold: true, fillColor: 'lightgray' }, { text: 'Job Title', bold: true, fillColor: 'lightgray' }],
[formattedDateTime(this.entity.JobDate,false), this.entity.JobNumber, this.entity.Client, this.entity.JobTitle],
]
}
});
var subGridContentBody = [];
subGridContentBody.push([{ text: 'Defect', bold: true, fillColor: 'lightgray' }, { text: 'Vendor', bold: true, fillColor: 'lightgray' }, { text: 'Status', bold: true, fillColor: 'lightgray' }, { text: 'Sign off', bold: true, fillColor: 'lightgray' }]);
$(this.entity.Defects).each(function () {
subGridContentBody.push([this.DefectName, this.DefectVendor, this.DefectStatus, '']);
});
docContent.push({
table: {
headerRows: 1,
widths: [159, 150, 50, 121],
body: subGridContentBody
}
});
docContent.push({ text: '', margin: 15 });
});
var docDefinition = {
content: docContent
}
if (self.isIE()) {
self.downloadPDF(grid.options.exporterPdfFilename, docDefinition);
} else {
pdfMake.createPdf(docDefinition).open();
}
}
No, there is no direct way to export subgrid. rather you can create youur own json data to generate csv file.
Please check the below code
function jsonToCsvConvertor(JSONData, reportTitle) {
//If JSONData is not an object then JSON.parse will parse the JSON string in an Object
var arrData = typeof JSONData !== 'object' ? JSON.parse(JSONData) : JSONData,
csv = '',
row,
key1,
i,
subGridData;
//Set Report title in first row or line
csv += reportTitle + '\r\n\n';
row = '';
for (key1 in arrData[0]) {
if(key1 !== 'subGridOptions' && key1 !== '$$hashKey'){
row += key1 + ',';
}
}
csv += row + '\r\n';
for (i = 0; i < arrData.length; i++) {
row = '';
subGridData = '';
for (key1 in arrData[i]) {
if(key1 !== 'subGridOptions' && key1 !== '$$hashKey'){
row += '"' + arrData[i][key1] + '",';
}
else if(key1 === 'subGridOptions'){
//csv += row + '\r\n';
subGridData = writeSubGridData(arrData[i][key1].data);
}
}
csv += row + '\r\n';
csv = subGridData ? csv + subGridData + '\r\n' : csv;
}
if (csv === '') {
console.log('Invalid data');
}
return csv;
}
//Generates subgrid Data to exportable form
function writeSubGridData(subgridData){
var j,
key2,
csv = '',
row = '';
for (key2 in subgridData[0]){
if(key2 !== '$$hashKey'){
row += key2 + ',';
}
}
csv = row + '\r\n';
for (j=0; j < subgridData.length ; j++){
row = '';
for(key2 in subgridData[j]){
if(key2 !== '$$hashKey'){
row += '"' + subgridData[j][key2]+ '",';
}
}
csv += row + '\r\n';
}
return csv;
}
jsonToCsvConvertor(exportData, 'New-Report');
Related
I am building a project using React with a doughnut and bar chart. Working with Chart.js 3.xx.
I am trying to make my custom legend functional. I want to make data fractions disappear when the user clicks my legend items - like in the native legend, and optimally also remove the data and make the chart present it's updated data after removal.
I also use data labels to present percentage of the data on the fractions.
import ChartDataLabels from 'chartjs-plugin-datalabels';
I came across this topic: ChartJS - Show/hide data individually instead of entire dataset on bar/line charts
and used this suggested code there:
function chartOnClick(evt) {
let chart = evt.chart
const points = chart.getElementsAtEventForMode(evt, 'nearest', {}, true);
if (points.length) {
const firstPoint = points[0];
//var label = myChart.data.labels[firstPoint.index];
//var value = myChart.data.datasets[firstPoint.datasetIndex].data[firstPoint.index];
let datasetIndex = firstPoint.datasetIndex, index = firstPoint.index;
if (firstPoint.element.hidden != true) {
chart.hide(datasetIndex, index);
} else {
chart.show(datasetIndex, index);
}
}
}
options: { // chart options
onClick: chartOnClick
}
It almost works, but the hide() method doesn't remove the fraction's percentage data label when activated, whereas when clicking the native legend it does remove it entirely.
I tried looking in the plugin's docs but didn't manage to find how to remove a single label.
How can I achieve what I am looking for?
EDIT:
Options Object:
export const doughnutOptsObj = {
onClick: chartOnClick,
responsive: true,
maintainAspectRatio: false,
layout: { padding: { top: 16, bottom: 16 } },
hoverOffset: 32,
plugins: {
legend: {
display: true,
position: 'bottom',
},
datalabels: {
formatter: (value, dnct1) => {
let sum = 0;
let dataArr = dnct1.chart.data.datasets[0].data;
dataArr.map((data) => {
sum += Number(data);
});
let percentage = ((value * 100) / sum).toFixed() + '%';
return percentage;
},
color: ['#fbfcfd'],
font: { weight: 'bold' },
// display: false, <-- this works and makes all of the data labels disappear
},
},
};
It seems that the onClick function is working properly.
I have tried the attached code, leveraging on toggleDataVisibility API, and it's working as requested (codepen: https://codepen.io/stockinail/pen/abKNJqJ):
function chartOnClick(evt) {
let chart = evt.chart
const points = chart.getElementsAtEventForMode(evt, 'nearest', {}, true);
if (points.length) {
const firstPoint = points[0];
chart.toggleDataVisibility(firstPoint.index);
chart.update();
}
}
I am using google chart to display my data on screen. Data is aggregated using using one of the column as shown below...
var dataGroup = google.visualization.data.group(
data, // data table
[0], // group by column
[
{ // aggregation column
column: 1,
type: 'number',
aggregation: google.visualization.data.avg
}
]
);
chart = new google.visualization.ColumnChart(document.getElementById('chart_div'));
chart.draw(dataGroup, $scope.getOptionsForChart());
Now what I want to do is add row count in tooltip.
THanks in advance.
there are no standard options for adding information to the tooltip,
you must replace the entire tooltip with your own custom one...
two things must be in place to use custom html tooltips...
1) must use the following chart option...
tooltip: {
isHtml: true
}
2) the tooltip column must have the following property...
p: {html: true}
see following working snippet...
an aggregation column is added for the count
then a DataView is used to convert the count column to a tooltip column
however, when using a DataView, for some reason the html column property is ignored
so when the chart is drawn, convert the DataView back to a DataTable,
chart.draw(view.toDataTable(), ...
this will allow the chart to recognize the column property...
google.charts.load('current', {
callback: function () {
var datastring = '{"PerformanceData" : [{"ReportName":"ABC","ReportTime":"48"},{"ReportName":"ABC","ReportTime":"48"},{"ReportName":"XYZ","ReportTime":"50"},{"ReportName":"ABC","ReportTime":"48"},{"ReportName":"XYZ","ReportTime":"50"},{"ReportName":"ABC","ReportTime":"48"},{"ReportName":"XYZ","ReportTime":"50"}]}';
var jsonData = JSON.parse(datastring);
var chartData = [];
// load chart data
jsonData.PerformanceData.forEach(function (row, rowIndex) {
// column headings
var columns = Object.keys(row);
if (rowIndex === 0) {
chartData.push(columns);
}
// row values
var chartRow = [];
columns.forEach(function (column, colIndex) {
var chartCell = row[column];
if (colIndex > 0) {
chartCell = parseFloat(chartCell);
}
chartRow.push(chartCell);
});
chartData.push(chartRow);
});
var data = google.visualization.arrayToDataTable(chartData);
// group data
var dataGroup = google.visualization.data.group(
data, // data table
[0], // group by column
[
{ // average
column: 1,
type: 'number',
aggregation: google.visualization.data.avg,
label: data.getColumnLabel(1)
},
{ // count
column: 1,
type: 'number',
aggregation: google.visualization.data.count
}
]
);
var view = new google.visualization.DataView(dataGroup);
view.setColumns([0, 1, {
type: 'string',
role: 'tooltip',
calc: function (dt, row) {
return '<div class="tooltip">' +
'<div><span>' + dt.getValue(row, 0) + '</span></div>' +
'<div><span>' + dt.getColumnLabel(1) + ' (avg): </span>' + dt.getValue(row, 1) + '</div>' +
'<div><span>Row Count: </span>' + dt.getValue(row, 2) + '</div>' +
'</div>';
},
p: {html: true}
}]);
var chart = new google.visualization.ColumnChart(document.getElementById('chart_div'));
// use group data to draw chart
chart.draw(view.toDataTable(), {
tooltip: {
isHtml: true
}
});
},
packages: ['corechart']
});
.tooltip {
font-size: 12pt;
padding: 8px;
}
.tooltip div {
padding: 4px;
}
.tooltip span {
font-weight: bold;
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>
I am using ui-grid in my project where I have an added roe aggregated average row which reflects the average of the value of that column in the grid. Now I have implemented the Export to PDF option, but when exporting the grid doesn't include the footer row which reflects the average value. I looked into http://ui-grid.info/docs/#/api/ui.grid.exporter.api:GridOptions and tried as mentioned there:
gridOptions.exporterPdfCustomFormatter = function ( docDefinition ) {
docDefinition.styles.footerStyle = { bold: true, fontSize: 10 };
return docDefinition;
}
gridOptions.exporterPdfFooter = { text: 'My footer', style: 'footerStyle' }
or
gridOptions.exporterPdfFooter = 'My Footer';
or
gridOptions.exporterPdfFooter = {
columns: [
'Left part',
{ text: 'Right part', alignment: 'right' }
]
};
Seems like these options do not work or I am doing it wrong. Any suggestions?
Here is one way I was able to figure it out.
Create a footer method
$scope.getFooterValue = function(i){
return $scope.gridApi.grid.columns[i].getAggregationValue();
}
then create a table for the footer when your 'print pdf' btn is clicked
$scope.$on('print', function(event, filter) {
$scope.gridOptions.exporterPdfFooter = {
table: {
widths: [ '*'],
body: [
[ 'Totals: '],
]
}
};
$scope.export();
}
I try to implement grid with grouping similar to this example:
http://dev.sencha.com/deploy/ext-4.0.0/examples/grid/groupgrid.html
Here data is grouped by column "Cuisine" and sorting by this column sort groups accordingly.
When I paste code of this example into a project, which uses 4.2.1, or in code editor at ExtJS 4.2.1 docs site, the view is exactly the same, sorting works for column "Name", but it doesn't work by column "Cuisine".
Did they remove sorting by grouping column in 4.2.1? If not, how to make it work?
The same example is present in 4.2.1 SDK, and indeed sorting by the grouped column doesn't work anymore. Sounds like a regression to me, you should notify Sencha.
Edit:
That's the code of the method Ext.data.Store#sort that has changed. Restoring the previous version fixes the behaviors (see my comments to find the modified lines):
Ext.define(null, {
override: 'Ext.data.Store'
,sort: function(sorters, direction, where, doSort) {
var me = this,
sorter,
newSorters;
if (Ext.isArray(sorters)) {
doSort = where;
where = direction;
newSorters = sorters;
}
else if (Ext.isObject(sorters)) {
doSort = where;
where = direction;
newSorters = [sorters];
}
else if (Ext.isString(sorters)) {
sorter = me.sorters.get(sorters);
if (!sorter) {
sorter = {
property : sorters,
direction: direction
};
newSorters = [sorter];
}
else if (direction === undefined) {
sorter.toggle();
}
else {
sorter.setDirection(direction);
}
}
if (newSorters && newSorters.length) {
newSorters = me.decodeSorters(newSorters);
if (Ext.isString(where)) {
if (where === 'prepend') {
// <code from 4.2.1>
// me.sorters.insert(0, newSorters);
// </code from 4.2.1>
// <code from 4.2.0>
sorters = me.sorters.clone().items;
me.sorters.clear();
me.sorters.addAll(newSorters);
me.sorters.addAll(sorters);
// </code from 4.2.0>
}
else {
me.sorters.addAll(newSorters);
}
}
else {
me.sorters.clear();
me.sorters.addAll(newSorters);
}
}
if (doSort !== false) {
me.fireEvent('beforesort', me, newSorters);
me.onBeforeSort(newSorters);
sorters = me.sorters.items;
if (sorters.length) {
me.doSort(me.generateComparator());
}
}
}
});
set sortable: true either on a defaults config for the grouping column or as a config on the child columns themselves. e.g.
{
// NOTE: these two are grouped columns
text: 'Close',
columns: [{
text: 'Value',
minWidth: 100,
flex: 100,
sortable: true,
dataIndex: 'ValueHeld_End'
}, {
text: 'Total',
minWidth: 110,
flex: 110,
sortable: true,
dataIndex: 'TotalPnL'
}]
}
In the extjs 4.1.1a code below is a working example of a line chart. Now I need to highlight a part of that chart on a given min and max timestamp.
{'xtype' : 'chart',
'store' : 'ChartData',
'height' : '100%',
'width' : '100%',
'legend' : {'position' : top},
'axes': [{
'title': 'Power',
'type': 'Numeric',
'position': 'left',
'fields': ['power']
},{
'title': 'Timestamp',
'type': 'Numeric',
'position': 'bottom',
'fields': ['timestamp'],
'minorTickSteps': 3
}],
'series': [{
'type': 'line',
'fill': true,
'axis' : 'left',
'xField': 'timestamp',
'yField': 'power'
}]}
I've searched the sencha forum and found nothing in particular that meets my requirements.
For now I managed to change the color of the points on the line chart with a custom renderer.
'renderer': function(sprite, record, attr, index, store) {
var item = store.getAt(index);
if(item != undefined && (item.get('timestamp') < startdate || item.get('timestamp') > enddate)){
return Ext.apply(attr, {'fill': '#00cc00', 'stroke-width': 3, 'radius': 4});
}else{
return Ext.apply(attr, {'fill': '#ff0000', 'stroke-width': 3, 'radius': 4});
}}
But I have not found a way to change the color below the line.
Any suggestions on that?
UPDATE - Working fine now
I implement a solution based on the answer given by Colombo.
doCustomDrawing: function () {: function (p){
var me = this, chart = me.chart;
if(chart.rendered){
var series = chart.series.items[0];
if (me.groupChain != null) {
me.groupChain.destroy();
me.groupChain = null;
}
me.groupChain = Ext.create('Ext.draw.CompositeSprite', {
surface: chart.surface
});
if(series != null && series.items != null){
var surface = chart.surface;
var pathV = 'M';
var first = true;
// need first and last x cooridnate
var mX = 0,hX = 0;
Ext.each(series.items, function(item){
var storeItem = item.storeItem,
pointX = item.point[0],
pointY = item.point[1];
// based on given startdate and enddate start collection path coordinates
if(!(storeItem.get('timestamp') < startdate || storeItem.get('timestamp') > enddate)){
if(hX<pointX){
hX = pointX;
}
if(first){
first = false;
mX = pointX;
pathV+= + pointX + ' ' + pointY;
}else{
pathV+= ' L' + pointX + ' ' + pointY;
}
}
});
var sprite = Ext.create('Ext.draw.Sprite', {
type: 'path',
fill: '#f00',
surface: surface,
// to draw a sprite with the area below the line we need the y coordinate of the x axe which is in my case items[1]
path : pathV + ' L'+ hX + ' ' + chart.axes.items[1].y + ' L'+ mX + ' ' + chart.axes.items[1].y + 'z'
});
me.groupChain.add(sprite);
me.groupChain.show(true);
}
}}
This looks really good and has the effect I was hoping for and in case you resize the container the new sprite is cleared from the chart. Thx to Colombo again.
This is possible to implement. Here is how I would do it.
1. Add a listener for afterrender event for series.
listeners: {
afterrender: function (p) {
this.doCustomDrawing();
},
scope: me
}
2. Create a CompositeSprite
doCustomDrawing: function () {
var me = this, chart = me.chart;
if (chart.rendered) {
var series = chart.series.items[0];
if (me.groupChain != null) {
me.groupChain.destroy();
me.groupChain = null;
}
me.groupChain = Ext.create('Ext.draw.CompositeSprite', {
surface: chart.surface
});
// Draw hilight here
Ext.each(series.items, function (item) {
var storeItem = item.storeItem,
pointX = item.point[0],
pointY = item.point[1];
//TODO: Create your new line sprite using pointX and pointY
// and add it to CompositeSprite me.groupChain
});
me.groupChain.show(true);
}
},
There is no "built-in" way to go about this. You are probably running into some difficulties looking for it because "highlighting" a line chart currently means something totally different in the ExtJS API. It normally refers to making the line and markers bold when you hover the mouse over a data series.
However your idea sounds interesting, I might need use this in a project I have coming up.
I don't have the time to work out the exact code right now but this could be done by creating a rectangular sprite and adding it to the chart's surface property.
You can do that even after the chart has been all rendered using the Ext.draw.Surface.add method as described here in the docs.
You will have to come up with logic for determining width and positioning, if I remember the chart API properly, you should be able to extract x coordinates (horizontal location) of individual records by fishing around in the items property of the Ext.chart.series.Line object inside the chart.
The height of the highlight should be easy though - just read the height of the chart.