Skip to content

Commit

Permalink
Merge pull request #39 from pat310/28filteringSorting
Browse files Browse the repository at this point in the history
28filtering sorting
  • Loading branch information
pat310 committed Mar 25, 2017
2 parents 35528f0 + a2f42f0 commit 0ecabb4
Show file tree
Hide file tree
Showing 31 changed files with 2,155 additions and 1,345 deletions.
7 changes: 0 additions & 7 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,6 @@ engines:
config:
languages:
- javascript
checks:
wrap-iife:
enabled: false
no-loop-func:
enabled: false
guard-for-in:
enabled: false
fixme:
enabled: true
ratings:
Expand Down
5 changes: 3 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
"linebreak-style": 0,
"max-depth": 0,
"max-len": [2, 80, 4],
"max-len": [2, 80, 4, {"ignoreComments": true}],
"max-nested-callbacks": 0,
"max-params": 0,
"max-statements": 0,
Expand Down Expand Up @@ -151,14 +151,15 @@
"no-unused-expressions": 0,
"no-unused-vars": [2, { "vars": "all", "args": "none" }],
"no-use-before-define": 2,
"no-var": 0,
"no-var": 2,
"no-void": 0,
"no-warning-comments": 0,
"no-with": 2,
"one-var": 0,
"operator-assignment": 0,
"operator-linebreak": [2, "after"],
"padded-blocks": 0,
"prefer-const": 1,
"quote-props": 0,
"quotes": [2, "single", "avoid-escape"],
"radix": 2,
Expand Down
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,15 @@ build/Release
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules

#ignore jsdoc output
out

# Ignore built files
lib

# Ignore browser test files
test.html
test.js

# Ignore mac files
.DS_Store
.DS_Store
12 changes: 11 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Ignore mac .DS_Store
.DS_Store

# Logs
logs
*.log
Expand Down Expand Up @@ -27,9 +30,16 @@ build/Release
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules

#ignore jsdoc output
out

# Ignore browser test files
test.html
test.js

screenshots
src
text
test
.babelrc
.codeclimate.yml
.eslintrc
Expand Down
10 changes: 10 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## [v2.2.0]
> Mar 25, 2017
- No longer adds `undefined` as a collapsed row
- Adding the following methods to the `Pivot` class
- `filter`
- `getUniqueValues`

[#38]: https://github.com/pat310/quick-pivot/pull/38

## [v2.1.1]
> Mar 12, 2017
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,14 @@ Toggles data from collapsed to expanded or vice-versa. The `toggle` method is ch
#### `.getData(rowNum)`
Returns the data that comprises a collapsed row

#### `.getUniqueValues(fieldName)`
Returns all the unique values for a particular field as an array

#### `.filter([fieldName or CBfunction], filterValues, [filterType])`
Filters out values based on either:
- string `fieldName` field to filter on, array `filterValues` values to filter, string `filterType` option enumerated string either `'include'` or `'exclude'` (defaults to exclude if not provided)
- function `CBfunction(element, index, array)` which iterates over each element in array (similar to Javascript array `.filter` method)


### Example with callback function
Check out [the test spec for more examples](/test/index.spec.js).
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"cover": "nyc --reporter=lcov mocha --compilers js:babel-core/register --colors ./test/*.spec.js",
"coveralls": "npm run cover && nyc report --reporter=text-lcov | coveralls",
"coverage": "nyc mocha --compilers js:babel-core/register --colors ./test/*.spec.js",
"preversion": "npm run build"
"preversion": "npm run build",
"create-jsdoc": "./node_modules/.bin/jsdoc ./lib/quick-pivot.js"
},
"repository": {
"type": "git",
Expand All @@ -39,12 +40,14 @@
"babel-eslint": "5.0.0",
"babel-loader": "6.1.0",
"babel-plugin-add-module-exports": "0.1.2",
"babel-polyfill": "^6.23.0",
"babel-preset-es2015": "6.3.13",
"chai": "3.4.1",
"coveralls": "^2.11.15",
"eslint": "1.7.2",
"eslint-loader": "1.1.0",
"istanbul": "^0.4.5",
"jsdoc": "^3.4.3",
"mocha": "2.3.4",
"mocha-lcov-reporter": "^1.2.0",
"nyc": "^10.1.2",
Expand Down
51 changes: 51 additions & 0 deletions src/filtering.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Construct an object of field names with the unique values for each field
* @param {!Array<Object>} data
* @returns {!Object} Keys are field names, each value is an object with the unique values as keys
*/
export function createUniqueValues(data) {
return data.reduce((acc, curr) => {
Object.keys(curr).forEach((fieldName) => {
if (!acc[fieldName]) acc[fieldName] = {};
/** by using an object instead of an array, we avoid extra work checking the array for duplicates*/
acc[fieldName][curr[fieldName]] = true;
});

return acc;
}, {});
};

/**
* Filter a provided data set
* @param {!Array<Object>} data
* @param {string|function} fieldName Callback function or a field name
* @param {Array<string>} filterValues Values to perform filter on
* @param {string} filterType Enumerated string either 'include' or 'exclude'
* @returns {!Array<Object>} filtered data set
*/
export function filter(data, fieldName, filterValues, filterType) {
/** check that fieldName isn't actually a callback */
if (typeof fieldName !== 'function') {
/** filter out the data set based on the provided fieldName and filterValues */
return data.filter(({[fieldName]: value}) => {
switch (filterType) {
case ('include'): {
return filterValues.indexOf(value) !== -1;
}

case ('exclude'): {
return filterValues.indexOf(value) === -1;
}

default: {
return filterValues.indexOf(value) === -1;
}
}
});
}

/** if custom callback provided, filter using the callback */
return data.filter((dataRow, index, array) => {
return fieldName(dataRow, index, array);
});
};
99 changes: 91 additions & 8 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,42 @@
import { tableCreator } from './logic';
import { tableCreator, fixDataFormat } from './logic';
import { collapse, expand } from './progressiveDiscovery.js';
import { createUniqueValues, filter } from './filtering.js';

export default class Pivot {

constructor(data, rows, cols, agg, type, header) {
if (!data) {
this.originalData = {};
}else {
if (!data) this.originalData = {};
else {
data = fixDataFormat(data);
this.originalArgs = {data, rows, cols, agg, type, header};
this.originalData = tableCreator(data, rows, cols, agg, type, header);
this.uniqueValues = createUniqueValues(data);
}

this.data = this.originalData;
this.collapsedRows = {};
}

update(data, rows, cols, agg, type, header) {
update(data, rows, cols, agg, type, header, isFiltering) {
data = fixDataFormat(data);
/** if update isn't being used by filter, need to reset the original arguments */
if (!isFiltering) this.originalArgs = {data, rows, cols, agg, type, header};
this.originalData = tableCreator(data, rows, cols, agg, type, header);
this.data = this.originalData;
this.uniqueValues = createUniqueValues(data);
this.collapsedRows = {};

return this;
}

collapse(rowNum) {
let returnedData = collapse(rowNum, this.data);
const returnedData = collapse(rowNum, this.data);

if (returnedData.collapsed) {
this.collapsedRows[this.data.table[rowNum].row] =
returnedData.collapsed;
this.collapsedRows[this.data.table[rowNum].row] = returnedData.collapsed;
}
this.data = returnedData.uncollapsed;

return this;
}

Expand All @@ -38,6 +47,7 @@ export default class Pivot {
this.collapsedRows[this.data.table[rowNum].row],
);
delete this.collapsedRows[this.data.table[rowNum].row];

return this;
}

Expand All @@ -57,4 +67,77 @@ export default class Pivot {
return this.originalData.rawData[this.data.table[rowNum].row];
}

/**
* Gets all the unique values for a given field name
* @param {string} fieldName
* @returns {!Array<string>}
*/
getUniqueValues(fieldName) {
/** if the field name does not exist, return empty []*/
if (this.uniqueValues[fieldName]) {
/** uniqueValues stores unique values in an object so it can be quickly constructed */
return Object.keys(this.uniqueValues[fieldName]);
}

return [];
}

/**
* Filters the data based on provided parameters
* @param {string|function} fieldName Either the field to filter on or a callback function
* @param {Array<string>} filterValues Values in field to filter
* @param {?string} filterType Enumerated string either 'include' or 'exclude'
* @returns {Object} Returns the pivot object so it is chainable
*/
filter(fieldName, filterValues, filterType) {
/** check that the parameters are of allowed type */
if ((typeof fieldName === 'function') ||
(typeof fieldName === 'string' && Array.isArray(filterValues))) {
/** filter out the data set based on the parameters*/
const filteredData =
filter(this.originalArgs.data, fieldName, filterValues, filterType);
/** collect the original arguments provided */
const {rows, cols, agg, type, header} = this.originalArgs;

/**
* get the current rows that are collapsed in reverse because we
* will recollapse them from bottom to top to ensure nested collapses
* note: can't use numbered rows unfortunately because when we filter
* the data, the row numbers will change
*/
const collapsedRowKeys = Object.keys(this.collapsedRows).reverse();
const collapsedRows = collapsedRowKeys.map((num) => {
return this.originalData.table[num].value[0];
});

/** update the pivot table with the new filtered data */
this.update(filteredData, rows, cols, agg, type, header, true);

/**
* set a pointer to end of table length
* doing this rather than rebuilding an array for efficiency
* also so we don't need to reloop through entire data array
* since we know that the filters should be in order
*/
let pointer = this.data.table.length - 1;

/**
* loop through each of the collapsed row names
* if the header row matches the collapsed row, then we need to collapse it
*/
collapsedRows.forEach((rowValue) => {
while (pointer >= 0) {
if ((this.data.table[pointer].type === 'rowHeader') &&
(rowValue === this.data.table[pointer].value[0])) {
this.collapse(pointer);
break;
}
pointer -= 1;
}
});
}

return this;
}

}
Loading

0 comments on commit 0ecabb4

Please sign in to comment.