Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

grouping and folding feature #131

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2017 Plotly, Inc
Copyright (c) 2021 Jakub Jagielka <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
159 changes: 93 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
# react-pivottable-grouping

This is a fork of [react-pivottable](https://react-pivottable.js.org/) with added capacity of grouping and displaying subtotals.
It adds an option `grouping: true` to the possible options. The rest of the API remains unchaged.

## Preview
See the working [demo](https://jjagielka.github.io/react-pivottable-demo/) here.

Left image is the default [react-pivottable](https://github.com/plotly/react-pivottable) rendering, while right images shows [react-pivottable-grouping](https://jjagielka.github.com/react-pivottable-grouping) with the default _grouping:true_ enabled.

<img src="http://jjagielka.github.io/pivottable-grouping-demo/images/grouping_false.png" width="50%"> <img src="http://jjagielka.github.io/pivottable-grouping-demo/images/grouping_true.png" width="49%">
---

Original [react-pivottable](https://react-pivottable.js.org/) README.md

# react-pivottable

`react-pivottable` is a React-based pivot table library with drag'n'drop
Expand Down Expand Up @@ -35,25 +50,29 @@ import React from 'react';
import ReactDOM from 'react-dom';
import PivotTableUI from 'react-pivottable/PivotTableUI';
import 'react-pivottable/pivottable.css';
import 'react-pivottable/grouping.css';

// see documentation for supported input formats
const data = [['attribute', 'attribute2'], ['value1', 'value2']];
const data = [
['attribute', 'attribute2'],
['value1', 'value2'],
];

class App extends React.Component {
constructor(props) {
super(props);
this.state = props;
}

render() {
return (
<PivotTableUI
data={data}
onChange={s => this.setState(s)}
{...this.state}
/>
);
}
constructor(props) {
super(props);
this.state = props;
}

render() {
return (
<PivotTableUI
data={data}
onChange={s => this.setState(s)}
{...this.state}
/>
);
}
}

ReactDOM.render(<App />, document.body);
Expand All @@ -79,6 +98,7 @@ To add the Plotly renderers to your app, you can use the following pattern:
import React from 'react';
import PivotTableUI from 'react-pivottable/PivotTableUI';
import 'react-pivottable/pivottable.css';
import 'react-pivottable/grouping.css';
import TableRenderers from 'react-pivottable/TableRenderers';
import Plot from 'react-plotly.js';
import createPlotlyRenderers from 'react-pivottable/PlotlyRenderers';
Expand All @@ -87,24 +107,27 @@ import createPlotlyRenderers from 'react-pivottable/PlotlyRenderers';
const PlotlyRenderers = createPlotlyRenderers(Plot);

// see documentation for supported input formats
const data = [['attribute', 'attribute2'], ['value1', 'value2']];
const data = [
['attribute', 'attribute2'],
['value1', 'value2'],
];

class App extends React.Component {
constructor(props) {
super(props);
this.state = props;
}

render() {
return (
<PivotTableUI
data={data}
onChange={s => this.setState(s)}
renderers={Object.assign({}, TableRenderers, PlotlyRenderers)}
{...this.state}
/>
);
}
constructor(props) {
super(props);
this.state = props;
}

render() {
return (
<PivotTableUI
data={data}
onChange={s => this.setState(s)}
renderers={Object.assign({}, TableRenderers, PlotlyRenderers)}
{...this.state}
/>
);
}
}

ReactDOM.render(<App />, document.body);
Expand All @@ -120,6 +143,7 @@ peer-dependcy warning and handle the dependency injection like this:
import React from 'react';
import PivotTableUI from 'react-pivottable/PivotTableUI';
import 'react-pivottable/pivottable.css';
import 'react-pivottable/grouping.css';
import TableRenderers from 'react-pivottable/TableRenderers';
import createPlotlyComponent from 'react-plotly.js/factory';
import createPlotlyRenderers from 'react-pivottable/PlotlyRenderers';
Expand All @@ -131,35 +155,38 @@ const Plot = createPlotlyComponent(window.Plotly);
const PlotlyRenderers = createPlotlyRenderers(Plot);

// see documentation for supported input formats
const data = [['attribute', 'attribute2'], ['value1', 'value2']];
const data = [
['attribute', 'attribute2'],
['value1', 'value2'],
];

class App extends React.Component {
constructor(props) {
super(props);
this.state = props;
}

render() {
return (
<PivotTableUI
data={data}
onChange={s => this.setState(s)}
renderers={Object.assign({}, TableRenderers, PlotlyRenderers)}
{...this.state}
/>
);
}
constructor(props) {
super(props);
this.state = props;
}

render() {
return (
<PivotTableUI
data={data}
onChange={s => this.setState(s)}
renderers={Object.assign({}, TableRenderers, PlotlyRenderers)}
{...this.state}
/>
);
}
}

ReactDOM.render(<App />, document.body);
```

## Properties and layered architecture

* `<PivotTableUI {...props} />`
* `<PivotTable {...props} />`
* `<Renderer {...props} />`
* `PivotData(props)`
- `<PivotTableUI {...props} />`
- `<PivotTable {...props} />`
- `<Renderer {...props} />`
- `PivotData(props)`

The interactive component provided by `react-pivottable` is `PivotTableUI`, but
output rendering is delegated to the non-interactive `PivotTable` component,
Expand All @@ -182,7 +209,7 @@ indication of which layer consumes each, from the bottom up:
| `PivotData` | `vals` <br /> array of strings | `[]` | attribute names used as arguments to aggregator (gets passed to aggregator generating function) |
| `PivotData` | `aggregators` <br /> object of functions | `aggregators` from `Utilites` | dictionary of generators for aggregation functions in dropdown (see [original PivotTable.js documentation](https://github.com/nicolaskruchten/pivottable/wiki/Aggregators)) |
| `PivotData` | `aggregatorName` <br /> string | first key in `aggregators` | key to `aggregators` object specifying the aggregator to use for computations |
| `PivotData` | `valueFilter` <br /> object of arrays of strings | `{}` | object whose keys are attribute names and values are objects of attribute value-boolean pairs which denote records to include or exclude from computation and rendering; used to prepopulate the filter menus that appear on double-click |
| `PivotData` | `valueFilter` <br /> object of arrays of strings | `{}` | object whose keys are attribute names and values are objects of attribute value-boolean pairs which denote records to include or exclude from computation and rendering; used to prepopulate the filter menus that appear on double-click |
| `PivotData` | `sorters` <br /> object or function | `{}` | accessed or called with an attribute name and can return a [function which can be used as an argument to `array.sort`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) for output purposes. If no function is returned, the default sorting mechanism is a built-in "natural sort" implementation. Useful for sorting attributes like month names, see [original PivotTable.js example 1](http://nicolas.kruchten.com/pivottable/examples/mps_agg.html) and [original PivotTable.js example 2](http://nicolas.kruchten.com/pivottable/examples/montreal_2014.html). |
| `PivotData` | `rowOrder` <br /> string | `"key_a_to_z"` | the order in which row data is provided to the renderer, must be one of `"key_a_to_z"`, `"value_a_to_z"`, `"value_z_to_a"`, ordering by value orders by row total |
| `PivotData` | `colOrder` <br /> string | `"key_a_to_z"` | the order in which column data is provided to the renderer, must be one of `"key_a_to_z"`, `"value_a_to_z"`, `"value_z_to_a"`, ordering by value orders by column total |
Expand All @@ -208,17 +235,17 @@ if the value was the string `"null"`.

```js
const data = [
{
attr1: 'value1_attr1',
attr2: 'value1_attr2',
//...
},
{
attr1: 'value2_attr1',
attr2: 'value2_attr2',
//...
},
{
attr1: 'value1_attr1',
attr2: 'value1_attr2',
//...
},
{
attr1: 'value2_attr1',
attr2: 'value2_attr2',
//...
},
//...
];
```

Expand All @@ -232,10 +259,10 @@ compatible with the output of CSV parsing libraries like PapaParse.

```js
const data = [
['attr1', 'attr2'],
['value1_attr1', 'value1_attr2'],
['value2_attr1', 'value2_attr2'],
//...
['attr1', 'attr2'],
['value1_attr1', 'value1_attr2'],
['value2_attr1', 'value2_attr2'],
//...
];
```

Expand Down
65 changes: 58 additions & 7 deletions examples/App.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,54 @@
import React from 'react';
import React, { useState } from 'react';
import tips from './tips';
import {sortAs} from '../src/Utilities';
import TableRenderers from '../src/TableRenderers';
import createPlotlyComponent from 'react-plotly.js/factory';
import createPlotlyRenderers from '../src/PlotlyRenderers';
import PivotTableUI from '../src/PivotTableUI';
import '../src/pivottable.css';
import '../src/grouping.css';
import Dropzone from 'react-dropzone';
import Papa from 'papaparse';

const Plot = createPlotlyComponent(window.Plotly);

function Checkbox(props) {
return <label className=" checkbox-inline" style={{textTransform: "capitalize"}}>
<input type="checkbox"
// onChange={e => props.update(e, props.name)}
name={props.name}
onChange={props.onChange}
defaultChecked={!props.unchecked}></input> {props.name.replace( /([A-Z])/g, " $1" )}
</label>
}

function Grouping(props) {
const [disabled, setDisabled] = useState(true);

const visible = !!props.rendererName && props.rendererName.startsWith('Table');

if(!visible)
return <div></div>;

const onChange = e => {
setDisabled(!e.target.checked);
props.onChange(e);
};

return <div className="row text-center">
<div className="col-md-2 col-md-offset-3">
<Checkbox onChange={onChange} name="grouping" unchecked={true} />
</div>
<fieldset className="col-md-6" disabled={disabled}>
<Checkbox onChange={props.onChange} name="compactRows"/>
<Checkbox onChange={props.onChange} name="rowGroupBefore"/>
<Checkbox onChange={props.onChange} name="colGroupBefore" unchecked={true} />
</fieldset>
<br/>
<br/>
</div>
}

class PivotTableUISmartWrapper extends React.PureComponent {
constructor(props) {
super(props);
Expand All @@ -30,7 +68,7 @@ class PivotTableUISmartWrapper extends React.PureComponent {
createPlotlyRenderers(Plot)
)}
{...this.state.pivotState}
onChange={s => this.setState({pivotState: s})}
// onChange={s => this.setState({pivotState: s}))}
unusedOrientationCutoff={Infinity}
/>
);
Expand All @@ -44,11 +82,12 @@ export default class App extends React.Component {
filename: 'Sample Dataset: Tips',
pivotState: {
data: tips,
rows: ['Payer Gender'],
cols: ['Party Size'],
aggregatorName: 'Sum over Sum',
rows: ['Payer Gender', "Meal"],
cols: ["Payer Smoker", 'Party Size',],
// aggregatorName: 'Sum over Sum',
vals: ['Tip', 'Total Bill'],
rendererName: 'Grouped Column Chart',
// rendererName: 'Grouped Column Chart',
rendererName: 'Table',
sorters: {
Meal: sortAs(['Lunch', 'Dinner']),
'Day of Week': sortAs([
Expand Down Expand Up @@ -111,6 +150,12 @@ export default class App extends React.Component {
});
}

onGrouping({target: {name, checked}}) {
var pivotState = Object.assign({}, this.state.pivotState);
pivotState[name] = checked;
this.setState({pivotState});
}

render() {
return (
<div>
Expand Down Expand Up @@ -149,7 +194,13 @@ export default class App extends React.Component {
<h2 className="text-center">{this.state.filename}</h2>
<br />

<PivotTableUISmartWrapper {...this.state.pivotState} />
</div>
<Grouping
onChange={this.onGrouping.bind(this)}
rendererName={this.state.pivotState.rendererName}
/>
<div className="row">
<PivotTableUISmartWrapper {...this.state.pivotState} onChange={s => this.setState({pivotState: s})}/>
</div>
</div>
);
Expand Down
Loading