Working with data
This page describes how to aggregate data in Pivot. For the instructions about loading and exporting data refer to Loading and exporting data.
Defining fields
Use the fields
property to add fields to the Pivot table. To add a new field, you should add a new object to the fields
array.
Example
const table = new pivot.Pivot("#root", {
fields: [
{ id: "year", label: "Year", type: "number" },
{ id: "continent", label: "Continent", type: "text" },
{ id: "form", label: "Form", type: "text" },
{ id: "oil", label: "Oil", type: "number" },
{ id: "balance", label: "Balance", type: "number" }
],
data,
config: {...}
});
Defining Pivot structure
You can create the Pivot structure using the config
property that also defines how data is aggregated. By default, it has no predefined values. You need to specify this property to define the configuration of the Pivot table, namely, which fields should be applied as columns and rows. The property also allows adding data aggregation methods to be applied to the fields. Here you can also add filters. Please, refer to the config
property description for details.
Example
const table = new pivot.Pivot("#root", {
fields,
data,
config: {
rows: ["continent", "name"],
columns: ["year"],
values: [
"count(oil)",
{ field: "oil", method: "sum" },
{ field: "gdp", method: "sum" }
]
},
fields,
filters: {
name: {
contains: "B"
}
}
});
Sorting data
The widget API allows configuring the sorting settings that are applied to all areas (values, columns and rows) during aggregation. The sorting in UI is enabled by clicking the column header.
To set default sorting values, apply the sort parameter of the fields
property. It accepts either the "asc" or "desc" value, or a custom sorting function.
In the example below we add clickable field labels and the sorting functionality that is enabled with the icon click:
const bar = document.getElementById("bar");
let sorted = ["studio", "genre"];
setFields();
bar.addEventListener('click', (e) => switchSort(e.target.id), false);
function setFields(){
let html = "";
let sortedFields = fields.filter(f => (sorted.indexOf(f.id) != -1));
sortedFields.forEach((f) =>{
const order = f.sort || "asc";
html += `<div className="field" id="${f.id}">
${f.label}<i className="icon wxi-${order}" ></i>
</div>`;
});
bar.innerHTML = html;
}
function switchSort(id){
fields.forEach(f => {
if(f.id == id){
f.sort = f.sort != "desc" ? "desc" : "asc";
}
});
// change fields in Pivot config
table.setConfig({fields});
// update icons
setFields(bar, fields);
}
const table = new pivot.Pivot("#root", {
fields,
data: dataset,
config: {
rows: [
"studio",
"genre"
],
values: [
{
field: "title",
method: "count"
},
{
field: "score",
method: "max"
}
]
}
});
The sorting functionality is enabled by default. A user can click the column's header to sort data. To disable/enable sorting, apply the sort
parameter of the columnShape
property. In the example below we disable sorting.
const table = new pivot.Pivot("#root", {
fields,
data,
config: {
rows: ["studio", "genre"],
columns: [],
values: [
{
field: "title",
method: "count"
},
{
field: "score",
method: "max"
}
]
},
columnShape: {
sort: false
}
});
Filtering data
The widget allows you to set various filters for fields depending on the type of data. It's possible to specify filters both via the Pivot interface after initialization or through the corresponding API using the config
property.
In GUI, filters appear as drop-down lists for each field.
Types of filters
The Pivot widget provides the next condition types for filtering:
- for text values: equal, notEqual, contains, notContains, beginsWith, notBeginsWith, endsWith, notEndsWith
- for numeric values: greater: less, greaterOrEqual, lessOrEqual, equal, notEqual, contains, notContains, begins with, not begins with, ends with, not ends with
- for date types: greater, less, greaterOrEqual, lessOrEqual, equal, notEqual, between, notBetween
The filter provides the includes filtering rule to define the set of values to be displayed.
Adding a filter
To set a filter, add the filters object with the field ID and filter type to the config
property.
const table = new pivot.Pivot("#root", {
fields,
data: dataset,
config: {
rows: ["studio", "genre"],
values: [
{
field: "title",
method: "count"
},
{
field: "score",
method: "max"
}
],
filters: {
genre: {
contains: "D",
includes: ["Drama"]
},
title: {
// filter for another field ("title")
contains: "A"
}
}
}
});
Limiting loaded data
To interrupt data rendering and prevent the component from hanging, you can limit the number of rows and columns in the final dataset. To limit data, specify the values using the limits
property. The values define when to interrupt rendering data. The limits are applied based on the rows/columns defined within the Pivot configuration. The default value for rows is 10 000 and for columns it's 5 000.
Limits are used for large dataset. Limits values are approximate values and do not show the exact values of the rows and columns.
Example
const table = new pivot.Pivot("#root", {
fields,
data: dataset,
config: {
rows: ["studio"],
columns: ["genre"],
values: [
{
id: "title",
method: "count"
},
{
id: "score",
method: "max"
}
]
},
limits: { rows: 10, columns: 3 }
});
Applying maths methods
Default methods
The widget provides the following default methods for data aggregation:
- sum (for numeric values only) - sums all the values of the selected data; empty cells, logical values like TRUE, or text are ignored
- min (for numeric and date values)- finds and displays the minimum value of the selected data; empty cells, logical values, or text in the array or reference are ignored. If the arguments contain no numbers, MIN returns 0 (zero)
- max (for numeric and date values) - finds the largest value in a set of values; the function ignores empty cells, the logical values TRUE and FALSE, and text values. If the arguments contain no numbers, MAX returns 0 (zero)
- count (for numeric, text, and date values) - looks for all occurrences of the selected data and displays their number; generally used to count a range of cells containing numbers or dates excluding blanks; this is the default operation assigned to each newly added field
- countunique (for numeric and text values) - сounts the number of unique values in a list of specified values and ranges
- average (for numeric values only) - calculates the average (arithmetic mean) of a group of numbers; logical values, empty cells and cells that contain text in the array or reference are ignored; cells with the value zero are included
- counta (for numeric, text, and date values) - returns the number of values in a dataset; counts numbers, dates, text or a range containing a mixture of these items, but does not count blank cells
- median (for numeric values only) - calculates the median of the given numbers
- product (for numeric values only) - calculates the product of all numbers in the given range
- stdev (for numeric values only) - calculates the standard deviation of the values, treating it as a sample of a bigger set of values
- stdevp (for numeric values only) - calculates the standard deviation of the values, treating it as the entire set of values
- var (for numeric values only) - calculates the variance of the values, treating it as a sample of a bigger set of values
- varp (for numeric values only) - calculates the variance of the values, treating it as the entire set of values
Predefined methods:
const defaultMethods = {
sum: { type: "number", label: "sum" },
min: { type: ["number", "date"], label: "min" },
max: { type: ["number", "date"], label: "max" },
count: {
type: ["number", "date", "text"],
label: "count",
branchMath: "sum"
},
counta: {
type: ["number", "date", "text"],
label: "counta",
branchMath: "sum"
},
countunique: {
type: ["number", "text"],
label: "countunique",
branchMath: "sum"
},
average: { type: "number", label: "average", branchMode: "raw" },
median: { type: "number", label: "median", branchMode: "raw" },
product: { type: "number", label: "product" },
stdev: { type: "number", label: "stdev", branchMode: "raw" },
stdevp: { type: "number", label: "stdevp", branchMode: "raw" },
var: { type: "number", label: "var", branchMode: "raw" },
varp: { type: "number", label: "varp", branchMode: "raw" }
};
You can apply default methods using the values
parameter of the config
property. See Options for defining values below.
Example
const table = new pivot.Pivot("#root", {
fields,
data,
config: {
rows: ["studio", "genre"],
columns: [],
values: [
{
//field id
field: "title",
//method
method: "count"
},
{
id: "score",
method: "max"
}
]
}
});
Options for defining values
You can define values
in either of the two equally valid ways:
- option one is a string representing a data field ID:
operation(fieldID)
- option two is an object containing the field ID and the method for data aggregation (both are required):
{ field: string, method: string }
Example
values: [
"sum(sales)", // option one
{ field: "sales", method: "sum" } // option two
]
Redefining the default method
By default, the first available method for the data type is selected. You can redefine a method using the api.intercept()
method.
In the example below, we check whether a new field is added, and set the max method in case a numeric field is added.
const table = new pivot.Pivot("#root", {
fields,
data: dataset,
config: {
rows: ["studio", "genre"],
columns: [],
values: [
{
field: "title",
method: "count",
},
{
field: "score",
method: "max",
},
],
},
});
//adding values with a predefined method
table.api.intercept("add-field", (ev) => {
const { fields } = table.api.getState();
const type = fields.find((f) => f.id == ev.field).type;
if (ev.area == "values" && type == "number") {
ev.method = "max";
}
});
Adding custom maths methods
To add a custom method, use the methods
property by setting the key
parameter value to the method name and the value
parameter should be a function that defines how a method should process data. The function should take an array of numerical values as an input and return a single numerical value.
The example below shows how to calculate the count of unique and average values for the date type. The countUnique function takes an array of numbers (values) as an input and calculates the exact count of unique values using the reduce method. The countunique_date sub-property has a handler with a function that gets the unique values from an array of the date values. The average_date sub-property has a handler that calculates the average values from an array of the date values.
function countUnique(values, converter) {
const valueMap = {};
return values.reduce((acc, d) => {
if (converter) d = converter(d);
if (!valueMap[d]) {
acc++;
valueMap[d] = true;
}
return acc;
}, 0);
}
const methods = {
countunique_date: {
handler: values => countUnique(values, v => new Date(v).getTime()),
type: "date",
label: "CountUnique",
},
average_date: {
type: "date",
label: "Average",
branchMode: "raw",
handler: values => {
if (!values.length) return null;
const sum = values.reduce((acc, d) => acc + d.getTime(), 0);
const avgTime = sum / values.length;
return new Date(avgTime);
}
}
};
// show integers for "count" and "unique count" results
const templates = {};
fields.forEach(f => {
if (f.type == "number")
templates[f.id] = (v, method) =>
v && method.indexOf("count") < 0 ? parseFloat(v).toFixed(3) : v;
});
// date string to Date
const dateFields = fields.filter(f => f.type == "date");
if (dateFields.length) {
dataset.forEach(item => {
dateFields.forEach(f => {
const v = item[f.id];
if (typeof v == "string") item[f.id] = new Date(v);
});
});
}
const table = new pivot.Pivot("#root", {
fields,
data: dataset,
tableShape: { templates },
methods: { ...pivot.defaultMethods, ...methods },
config:{
rows: ["state"],
columns: [
"product_line",
"product_type"
],
values: [
{
field: "sales",
method: "sum"
},
{
field: "sales",
method: "count"
},
{
field: "date",
method: "countunique_date"
},
{
field: "date",
method: "average_date"
}
]
}
});
Processing data with predicates
Predicates or data modifiers allow you to process data in the required way before this data is used as rows or columns. For example, you can pre-process the date format before applying and displaying data. The following predicates are applied by default:
const defaultPredicates = {
year: { label: "Year", type: "date", filter: { type: "number" } },
quarter: { label: "Quarter", type: "date", filter: { type: "tuple" } },
month: { label: "Month", type: "date", filter: { type: "tuple" } },
week: { label: "Week", type: "date", filter: { type: "tuple" } },
day: { label: "Day", type: "date", filter: { type: "number" } },
hour: { label: "Hour", type: "date", filter: { type: "number" } },
minute: { label: "Minute", type: "date", filter: { type: "number" } }
};
To add a custom predicate, you should specify the parameters of the predicates
property:
- Add keys that are predicate IDs
- Add values that are objects with the predicate configuration:
- specify the
type
to define fields types for which the predicate can be applied ("number", "date", "text") - add a label that will be displayed in GUI in the drop-down among data modifiers options for a row/column
- for the custom predicate, add the
handler
function that defines how data should be processed; the function takes a single argument as the value to be processed and returns the processed value - if you want data to be displayed in the way other than the
handler
function returns, add thetemplate
that defines how data should be displayed (optional) - if necessary, add the
field
function to specify how data should be filtered for the field - apply the
filter
parameter if you need the filter type other than the one in thetype
parameter or in case you need the data format different from thetemplate
- specify the
You should also add the predicate id as the value of the method
parameter for the row/column where this predicate should be applied.
const predicates = {
monthYear: {
label: "Month-year",
type: "date",
handler: (d) => new Date(d.getFullYear(), d.getMonth(), 1),
template: (date, locale) => {
const months = locale.getRaw().calendar.monthFull;
return months[date.getMonth()] + " " + date.getFullYear();
},
},
profitSign: {
label: "Profit Sign",
type: "number",
filter: {
type: "tuple",
format: (v) => (v < 0 ? "Negative" : "Positive"),
},
field: (f) => f === "profit",
handler: (v) => (v < 0 ? -1 : 1),
template: (v) => (v < 0 ? "Negative profit" : "Positive profit"),
},
};
// date string to Date
const dateFields = fields.filter((f) => f.type == "date");
if (dateFields.length) {
dataset.forEach((item) => {
dateFields.forEach((f) => {
const v = item[f.id];
if (typeof v == "string") item[f.id] = new Date(v);
});
});
}
const table = new pivot.Pivot("#pivot", {
fields,
data: dataset,
predicates: { ...pivot.defaultPredicates, ...predicates },
tableShape: { tree: true },
config: {
rows: ["product_type", "product"],
columns: [
{ field: "profit", method: "profitSign" },
{ field: "date", method: "monthYear" },
],
values: ["sales", "expenses"],
},
});
Adding columns and rows with total values
To enable generating the rightmost column with total values, apply the tableShape
property and set the value of the totalColumn
parameter to true.
To enable generating the footer with totals, apply the tableShape
property and set the value of the totalRow
parameter to true.
Example
const table = new pivot.Pivot("#root", {
tableShape: {
totalRow: true,
totalColumn: true
},
fields,
data,
config: {
rows: ["studio"],
columns: ["type"],
values: [
{
field: "score",
method: "max"
},
{
field: "episodes",
method: "count"
},
{
field: "rank",
method: "min"
},
{
field: "members",
method: "sum"
}
]
}
});
Example
In this snippet you can see how to apply custom maths operations:
Related samples: